matplotlib(等长单位长度):具有'相等'宽高比z轴不等于x-和y-

时间:2012-12-03 14:33:32

标签: python matplotlib aspect-ratio

当我为3d图设置相等的宽高比时,z轴不会变为“相等”。所以,这个:

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()

给我以下内容: enter image description here

显然,z轴的单位长度不等于x和y单位。

如何使所有三个轴的单位长度相等?我能找到的所有解决方案都不起作用。谢谢。

8 个答案:

答案 0 :(得分:56)

我相信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.gca(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也可以自动调整z轴:

bad

但是如果添加边界框,则可以获得正确的缩放:

enter image description here

答案 1 :(得分:41)

我使用set_x/y/zlim functions简化了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.gca(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()

enter image description here

答案 2 :(得分:36)

我喜欢上述解决方案,但它们确实有缺点,您需要跟踪所有数据的范围和方法。如果您有多个数据集将一起绘制,这可能很麻烦。为了解决这个问题,我使用了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.gca(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()

答案 3 :(得分:14)

简单修复!

我设法使它在3.3.1版中工作。

此问题似乎已在PR#17172中得到解决;您可以使用<ReactTable getTrProps={(_, row) => { return { onClick: () => { /* select your row here */ } }; }} /> 函数来确保外观正确(请参见set_aspect函数的注释)。当与@karlo和/或@Matee Ulhaq提供的边界框功能结合使用时,这些图现在在3D模式下看起来是正确的!

matplotlib 3d plot with equal axes

最小工作示例

ax.set_box_aspect([1,1,1])

答案 4 :(得分:9)

改编自@ karlo的答案,使事情变得更加清洁:

def set_axes_radius(ax, origin, radius):
    ax.set_xlim3d([origin[0] - radius, origin[0] + radius])
    ax.set_ylim3d([origin[1] - radius, origin[1] + radius])
    ax.set_zlim3d([origin[2] - radius, origin[2] + radius])

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().
    '''

    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)

<强>用法:

fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_aspect('equal')         # important!

# ...draw here...

set_axes_equal(ax)             # important!
plt.show()

答案 5 :(得分:5)

编辑:用户2525140的代码应该可以正常工作,尽管这个答案应该尝试修复一个不存在的错误。下面的答案只是一个重复(替代)实现:

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])

答案 6 :(得分:1)

从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))

答案 7 :(得分:0)

我认为这个功能已经添加到 matplotlib 中,因为这些答案已经发布。如果有人仍在寻找解决方案,我就是这样做的:

import matplotlib.pyplot as plt 
import numpy as np
    
fig = plt.figure(figsize=plt.figaspect(1)*2)
ax = plt.gca(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 x 1。*2 之后的 figaspect(1) 将图形缩放为 2。您可以将此比例因子设置为您想要的任何值。

注意:这仅适用于具有一个图的图形。

Random 3D scatter Plot