如何设置所有轴(x、y、z)的“相等”纵横比

2025-02-17 09:25:00
admin
原创
96
摘要:问题描述:当我为 3D 图形设置相等纵横比时,它z-axis不会变为“相等”。因此:fig = pylab.figure() mesFig = fig.gca(projection='3d', adjustable='box') mesFig.axis('equal') mesFig.plot(xC, yC,...

问题描述:

当我为 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()

给我以下内容:

图片1

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

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


解决方案 1:

我喜欢之前发布的一些解决方案,但它们确实有一个缺点,即您需要跟踪所有数据的范围和平均值。如果您有多个要一起绘制的数据集,这可能会很麻烦。为了解决这个问题,我利用了这些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.

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

# Use this for matplotlib prior to 3.3.0 only.
#ax.set_aspect("equal")
#
# Use this for matplotlib 3.3.0 and later.
# https://github.com/matplotlib/matplotlib/pull/17515
ax.set_box_aspect([1.0, 1.0, 1.0])

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

解决方案 2:

我认为 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 也会自动缩放 z 轴:

坏的

但是如果添加边界框,您将获得正确的缩放比例:

在此处输入图片描述

解决方案 3:

简单修复!

我已经设法在 3.3.1 版本中让它运行起来。

看起来这个问题可能已在PR#17172中得到解决;您可以使用该ax.set_box_aspect([1,1,1])函数来确保方面正确(请参阅set_aspect函数的注释)。当与 @karlo 和/或 @Matee Ulhaq 提供的边界框函数结合使用时,绘图现在在 3D 中看起来是正确的!

matplotlib 具有相等轴的三维图

最小工作示例

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

解决方案 4:

我利用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()

在此处输入图片描述

解决方案 5:

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

解决方案 6:

改编自@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')

解决方案 7:

从 matplotlib 3.6.0 开始,此功能已通过命令
添加ax.set_aspect('equal')。其他选项包括'equalxy''equalxz''equalyz',用于仅将两个方向设置为相等的纵横比。这会更改数据限制,如下例所示。

在即将推出的 3.7.0 版本中,您将能够通过命令更改绘图框纵横比而不是数据限制ax.set_aspect('equal', adjustable='box')。要获取原始行为,请使用adjustable='datalim'

在此处输入图片描述

解决方案 8:

编辑: 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])

解决方案 9:

我认为自从这些答案发布以来,这个功能就被添加到了 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 x 1。*2之后figaspect(1)将图形缩放两倍。您可以将此缩放因子设置为您想要的任何值。

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

随机 3D 散点图

解决方案 10:

看起来:

ax.set_aspect('equal')

有效,但请注意,仅在所有绘图完成后才执行此指令。代码会根据已使用的范围修改绘图的限制,因此如果子绘图不完整,则不会计算准确的限制。

文档:mpl_toolkits.mplot3d.axes3d.Axes3D.set_aspect

在此处输入图片描述

尽管每个轴的范围差别很大,但黑色的三个单位向量具有相同的长度。

使用的代码

看看代码很有趣。

  • 绘图使用的范围是通过调用get_view_interval()

  • 通过调用来设置新的方面set_box_aspect(box_aspect)


view_intervals = np.array([self.xaxis.get_view_interval(),
                           self.yaxis.get_view_interval(),
                           self.zaxis.get_view_interval()])
ptp = np.ptp(view_intervals, axis=1)
if self._adjustable == 'datalim':
...
else:  # 'box'
    # Change the box aspect such that the ratio of the length of
    # the unmodified axis to the length of the diagonal
    # perpendicular to it remains unchanged.
    box_aspect = np.array(self._box_aspect)
    box_aspect[ax_indices] = ptp[ax_indices]
    remaining_ax_indices = {0, 1, 2}.difference(ax_indices)
    if remaining_ax_indices:
        remaining = remaining_ax_indices.pop()
        old_diag = np.linalg.norm(self._box_aspect[ax_indices])
        new_diag = np.linalg.norm(box_aspect[ax_indices])
        box_aspect[remaining] *= new_diag / old_diag
    self.set_box_aspect(box_aspect)
    

例子

对应上图的代码。

import numpy as np
import matplotlib.pyplot as plt


# Convert a point array to arrays of x, y and z coords
def split_xyz(points):
    _p = np.asarray(points)
    return _p[:,0], _p[:,1], _p[:,2]

# Origin
o = np.array([0,0,0])

# Vectors
u = np.array([3,3,1])
v = np.array([0,0.2,1.2])

# Projection of v onto u
n = np.linalg.norm(u)
p = u.dot(v) / n**2 * u

# Projection of v onto plane normal to u
w = v - p

# Figure
kw = dict(figsize=(7,7), layout='constrained')
fig = plt.figure(**kw)
mosaic = [['3d']]
kw = {'3d': dict(projection = '3d')}
axs = fig.subplot_mosaic(mosaic, per_subplot_kw=kw)
ax = axs['3d']

# Plot origin
ax.scatter([0],[0],[0], s=50, c='k')
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')

# Plot unit vectors
units = [(1,0,0), (0,1,0),(0,0,1)]
for _v in units: ax.plot(*split_xyz([o, _v]), c='k')

# Plot vectors
for _v, label in zip([u,v], ['u','v']):
    ax.plot(*split_xyz([o, _v]), label=label, lw=2)

# Plot projections
for _v, label in zip([p,w], ['p','w']):
    ax.plot(*split_xyz([o, _v]), label=label, lw=4, alpha=0.5)

# Plot projection lines
for _v in [p, w]: ax.plot(*split_xyz([v, _v]), c='darkgray', ls='--')

# Add legend
ax.legend()

ax.set_aspect('equal')

解决方案 11:

  • 暂时ax.set_aspect('equal')引发错误(3.5.1Anaconda 版本)。

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

在此处输入图片描述

相关推荐
  政府信创国产化的10大政策解读一、信创国产化的背景与意义信创国产化,即信息技术应用创新国产化,是当前中国信息技术领域的一个重要发展方向。其核心在于通过自主研发和创新,实现信息技术应用的自主可控,减少对外部技术的依赖,并规避潜在的技术制裁和风险。随着全球信息技术竞争的加剧,以及某些国家对中国在科技领域的打压,信创国产化显...
工程项目管理   3983  
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   2747  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Freshdesk、ClickUp、nTask、Hubstaff、Plutio、Productive、Targa、Bonsai、Wrike。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在项目管理过程中面临着诸多痛点,如任务分配不...
项目管理系统   82  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Monday、TeamGantt、Filestage、Chanty、Visor、Smartsheet、Productive、Quire、Planview。在当今快速变化的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多项目经理和团队在管理复杂项目时,常...
开源项目管理工具   90  
  本文介绍了以下10款项目管理软件工具:禅道项目管理软件、Smartsheet、GanttPRO、Backlog、Visor、ResourceGuru、Productive、Xebrio、Hive、Quire。在当今快节奏的商业环境中,项目管理已成为企业成功的关键因素之一。然而,许多企业在选择项目管理工具时常常面临困惑:...
项目管理系统   79  
热门文章
项目管理软件有哪些?
曾咪二维码

扫码咨询,免费领取项目管理大礼包!

云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用