当我为 3d 图形设置相等的纵横比时,
z-axis
不会更改为“相等”。所以这个:
fig = pylab.figure()
mesFig = fig.gca(projection='3d', adjustable='box')
mesFig.axis('equal')
mesFig.plot(xC, yC, zC, 'r.')
mesFig.plot(xO, yO, zO, 'b.')
pyplot.show()
给我以下内容:
显然 z 轴的单位长度不等于 x 和 y 单位。
如何使三个轴的单位长度相等?我找到的所有解决方案都不起作用。
我喜欢上面的解决方案,但它们确实有一个缺点,即您需要跟踪所有数据的范围和均值。如果您有多个要绘制在一起的数据集,这可能会很麻烦。为了解决这个问题,我使用了 ax.get_[xyz]lim3d() 方法并将整个过程放入一个独立的函数中,该函数在调用 plt.show() 之前只需调用一次。这是新版本:
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
def set_axes_equal(ax):
'''Make axes of 3D plot have equal scale so that spheres appear as spheres,
cubes as cubes, etc.. This is one possible solution to Matplotlib's
ax.set_aspect('equal') and ax.axis('equal') not working for 3D.
Input
ax: a matplotlib axis, e.g., as output from plt.gca().
'''
x_limits = ax.get_xlim3d()
y_limits = ax.get_ylim3d()
z_limits = ax.get_zlim3d()
x_range = abs(x_limits[1] - x_limits[0])
x_middle = np.mean(x_limits)
y_range = abs(y_limits[1] - y_limits[0])
y_middle = np.mean(y_limits)
z_range = abs(z_limits[1] - z_limits[0])
z_middle = np.mean(z_limits)
# The plot bounding box is a sphere in the sense of the infinity
# norm, hence I call half the max range the plot radius.
plot_radius = 0.5*max([x_range, y_range, z_range])
ax.set_xlim3d([x_middle - plot_radius, x_middle + plot_radius])
ax.set_ylim3d([y_middle - plot_radius, y_middle + plot_radius])
ax.set_zlim3d([z_middle - plot_radius, z_middle + plot_radius])
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.set_aspect('equal')
X = np.random.rand(100)*10+5
Y = np.random.rand(100)*5+2.5
Z = np.random.rand(100)*50+25
scat = ax.scatter(X, Y, Z)
set_axes_equal(ax)
plt.show()
我相信 matplotlib 还没有在 3D 中正确设置等轴......但是我前段时间发现了一个技巧(我不记得在哪里)我已经适应了它。这个概念是在您的数据周围创建一个假的立方体边界框。 您可以使用以下代码进行测试:
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.set_aspect('equal')
X = np.random.rand(100)*10+5
Y = np.random.rand(100)*5+2.5
Z = np.random.rand(100)*50+25
scat = ax.scatter(X, Y, Z)
# Create cubic bounding box to simulate equal aspect ratio
max_range = np.array([X.max()-X.min(), Y.max()-Y.min(), Z.max()-Z.min()]).max()
Xb = 0.5*max_range*np.mgrid[-1:2:2,-1:2:2,-1:2:2][0].flatten() + 0.5*(X.max()+X.min())
Yb = 0.5*max_range*np.mgrid[-1:2:2,-1:2:2,-1:2:2][1].flatten() + 0.5*(Y.max()+Y.min())
Zb = 0.5*max_range*np.mgrid[-1:2:2,-1:2:2,-1:2:2][2].flatten() + 0.5*(Z.max()+Z.min())
# Comment or uncomment following both lines to test the fake bounding box:
for xb, yb, zb in zip(Xb, Yb, Zb):
ax.plot([xb], [yb], [zb], 'w')
plt.grid()
plt.show()
z 数据大约比 x 和 y 大一个数量级,但即使使用等轴选项,matplotlib autoscale z 轴:
但是如果你添加边界框,你会得到正确的缩放:
简单修复!
我已经设法在版本 3.3.1 中实现了这个功能。
看起来这个问题可能已经在 PR#17172 中得到解决;您可以使用
ax.set_box_aspect([1,1,1])
函数来确保纵横比正确(请参阅 set_aspect 函数的注释)。当与@karlo 和/或@Matee Ulhaq 提供的边界框函数结合使用时,绘图现在在 3D 中看起来是正确的!
最小工作示例
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d
import numpy as np
# Functions from @Mateen Ulhaq and @karlo
def set_axes_equal(ax: plt.Axes):
"""Set 3D plot axes to equal scale.
Make axes of 3D plot have equal scale so that spheres appear as
spheres and cubes as cubes. Required since `ax.axis('equal')`
and `ax.set_aspect('equal')` don't work on 3D.
"""
limits = np.array([
ax.get_xlim3d(),
ax.get_ylim3d(),
ax.get_zlim3d(),
])
origin = np.mean(limits, axis=1)
radius = 0.5 * np.max(np.abs(limits[:, 1] - limits[:, 0]))
_set_axes_radius(ax, origin, radius)
def _set_axes_radius(ax, origin, radius):
x, y, z = origin
ax.set_xlim3d([x - radius, x + radius])
ax.set_ylim3d([y - radius, y + radius])
ax.set_zlim3d([z - radius, z + radius])
# Generate and plot a unit sphere
u = np.linspace(0, 2*np.pi, 100)
v = np.linspace(0, np.pi, 100)
x = np.outer(np.cos(u), np.sin(v)) # np.outer() -> outer vector product
y = np.outer(np.sin(u), np.sin(v))
z = np.outer(np.ones(np.size(u)), np.cos(v))
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.plot_surface(x, y, z)
ax.set_box_aspect([1,1,1]) # IMPORTANT - this is the new, key line
# ax.set_proj_type('ortho') # OPTIONAL - default is perspective (shown in image above)
set_axes_equal(ax) # IMPORTANT - this is also required
plt.show()
我使用
set_x/y/zlim
函数简化了Remy F的解决方案。
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.set_aspect('equal')
X = np.random.rand(100)*10+5
Y = np.random.rand(100)*5+2.5
Z = np.random.rand(100)*50+25
scat = ax.scatter(X, Y, Z)
max_range = np.array([X.max()-X.min(), Y.max()-Y.min(), Z.max()-Z.min()]).max() / 2.0
mid_x = (X.max()+X.min()) * 0.5
mid_y = (Y.max()+Y.min()) * 0.5
mid_z = (Z.max()+Z.min()) * 0.5
ax.set_xlim(mid_x - max_range, mid_x + max_range)
ax.set_ylim(mid_y - max_range, mid_y + max_range)
ax.set_zlim(mid_z - max_range, mid_z + max_range)
plt.show()
从 matplotlib 3.3.0 开始,Axes3D.set_box_aspect 似乎是推荐的方法。
import numpy as np
xs, ys, zs = <your data>
ax = <your axes>
# Option 1: aspect ratio is 1:1:1 in data space
ax.set_box_aspect((np.ptp(xs), np.ptp(ys), np.ptp(zs)))
# Option 2: aspect ratio 1:1:1 in view space
ax.set_box_aspect((1, 1, 1))
改编自@karlo 的回答,让事情变得更干净:
def set_axes_equal(ax: plt.Axes):
"""Set 3D plot axes to equal scale.
Make axes of 3D plot have equal scale so that spheres appear as
spheres and cubes as cubes. Required since `ax.axis('equal')`
and `ax.set_aspect('equal')` don't work on 3D.
"""
limits = np.array([
ax.get_xlim3d(),
ax.get_ylim3d(),
ax.get_zlim3d(),
])
origin = np.mean(limits, axis=1)
radius = 0.5 * np.max(np.abs(limits[:, 1] - limits[:, 0]))
_set_axes_radius(ax, origin, radius)
def _set_axes_radius(ax, origin, radius):
x, y, z = origin
ax.set_xlim3d([x - radius, x + radius])
ax.set_ylim3d([y - radius, y + radius])
ax.set_zlim3d([z - radius, z + radius])
用法:
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.set_aspect('equal') # important!
# ...draw here...
set_axes_equal(ax) # important!
plt.show()
pull-request #13474
中合并的更改,此答案不适用于较新版本的 Matplotlib,在 issue #17172
和 issue #1077
中进行了跟踪。作为对此的临时解决方法,可以删除 lib/matplotlib/axes/_base.py
: 中新添加的行
class _AxesBase(martist.Artist):
...
def set_aspect(self, aspect, adjustable=None, anchor=None, share=False):
...
+ if (not cbook._str_equal(aspect, 'auto')) and self.name == '3d':
+ raise NotImplementedError(
+ 'It is not currently possible to manually set the aspect '
+ 'on 3D axes')
编辑: user2525140 的代码应该工作得很好,尽管这个答案据说试图修复一个不存在的错误。下面的答案只是一个重复的(替代)实现:
def set_aspect_equal_3d(ax):
"""Fix equal aspect bug for 3D plots."""
xlim = ax.get_xlim3d()
ylim = ax.get_ylim3d()
zlim = ax.get_zlim3d()
from numpy import mean
xmean = mean(xlim)
ymean = mean(ylim)
zmean = mean(zlim)
plot_radius = max([abs(lim - mean_)
for lims, mean_ in ((xlim, xmean),
(ylim, ymean),
(zlim, zmean))
for lim in lims])
ax.set_xlim3d([xmean - plot_radius, xmean + plot_radius])
ax.set_ylim3d([ymean - plot_radius, ymean + plot_radius])
ax.set_zlim3d([zmean - plot_radius, zmean + plot_radius])
我认为自从发布了这些答案后,此功能已添加到 matplotlib 中。如果有人仍在寻找解决方案,我就是这样做的:
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=plt.figaspect(1)*2)
ax = fig.add_subplot(projection='3d', proj_type='ortho')
X = np.random.rand(100)
Y = np.random.rand(100)
Z = np.random.rand(100)
ax.scatter(X, Y, Z, color='b')
代码的关键位是
figsize=plt.figaspect(1)
,它将图形的纵横比设置为1乘1。*2
之后的figaspect(1)
将图形缩放为两倍。您可以将此比例因子设置为任何您想要的。
注意:这仅适用于具有一个图的图形。
暂时
ax.set_aspect('equal')
引起错误(带有 Anaconda 的版本 3.5.1
)。
ax.set_aspect('auto',adjustable='datalim')
也没有给出有说服力的方案
使用
ax.set_box_aspect((asx,asy,asz))
和 asx, asy, asz = np.ptp(X), np.ptp(Y), np.ptp(Z)
的精益工作循环似乎是可行的(见我的代码片段)
让我们希望具有@Scott 提到的功能的版本
3.7
很快就会成功。
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
#---- generate data
nn = 100
X = np.random.randn(nn)*20 + 0
Y = np.random.randn(nn)*50 + 30
Z = np.random.randn(nn)*10 + -5
#---- check aspect ratio
asx, asy, asz = np.ptp(X), np.ptp(Y), np.ptp(Z)
fig = plt.figure(figsize=(15,15))
ax = fig.add_subplot(projection='3d')
#---- set box aspect ratio
ax.set_box_aspect((asx,asy,asz))
scat = ax.scatter(X, Y, Z, c=X+Y+Z, s=500, alpha=0.8)
ax.set_xlabel('X-axis'); ax.set_ylabel('Y-axis'); ax.set_zlabel('Z-axis')
plt.show()