设置3D图的纵横比例

34

我想绘制一个三维图像,展示在海底运行声纳时获得的数据,这些数据涵盖了一个 500 米 x 40 米的海底区域。我正在使用 matplotlib/mplot3d 库中的 Axes3D 来完成这个任务,我希望能够改变坐标轴的纵横比例,使得 x 和 y 轴具有比例关系。以下是一个使用生成数据而不是真实数据的示例脚本:

import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
import numpy as np

# Create figure.
fig = plt.figure()
ax = fig.gca(projection = '3d')

# Generate example data.
R, Y = np.meshgrid(np.arange(0, 500, 0.5), np.arange(0, 40, 0.5))
z = 0.1 * np.abs(np.sin(R/40) * np.sin(Y/6))

# Plot the data.
surf = ax.plot_surface(R, Y, z, cmap=cm.jet, linewidth=0)
fig.colorbar(surf)

# Set viewpoint.
ax.azim = -160
ax.elev = 30

# Label axes.
ax.set_xlabel('Along track (m)')
ax.set_ylabel('Range (m)')
ax.set_zlabel('Height (m)')

# Save image.
fig.savefig('data.png')

这个脚本生成的输出图像如下:

matplotlib output image

我想要改变它,使得沿轨迹(x)轴上的1米等于距离(y)轴上的1米(或者根据涉及到的相对尺寸使用不同的比例)。我还想设置z轴的比率,同样由于数据中的相对大小而不一定是1:1,但是使轴比当前绘图小。

我已经尝试构建并使用matplotlib的这个分支,并按照邮件列表中这个消息中的示例脚本进行操作,但是在我的脚本中添加ax.pbaspect = [1.0, 1.0, 0.25]行(卸载了“标准”版本的matplotlib以确保使用自定义版本)没有使生成的图像发生任何变化。

编辑:因此,期望的输出应该类似于以下(使用Inkscape粗略编辑的)图像。在这种情况下,我没有在x/y轴上设置1:1的比例,因为那看起来非常细,但我已经将它展开,使其不像原始输出那样是正方形。

Desired output


参见此问题及答案可获得解决方案。 - ImportanceOfBeingErnest
从matplotlib 3.3.0开始,建议使用set_box_aspect()。请参阅以下新答案 - user202729
5个回答

26

在保存图像之前添加以下代码:

ax.auto_scale_xyz([0, 500], [0, 500], [0, 0.15])

输入图像描述

如果您不想要方形轴:

请在 site-packages\mpl_toolkits\mplot3d\axes3d.py 中编辑get_proj函数:

xmin, xmax = np.divide(self.get_xlim3d(), self.pbaspect[0])
ymin, ymax = np.divide(self.get_ylim3d(), self.pbaspect[1])
zmin, zmax = np.divide(self.get_zlim3d(), self.pbaspect[2])

然后再添加一行代码来设置pbaspect:

ax = fig.gca(projection = '3d')
ax.pbaspect = [2.0, 0.6, 0.25]

输入图像描述


1
嗯,这确实可以正确地缩放坐标轴,但会导致很多空间浪费。虽然我可以将其保存为SVG并手动编辑(就像我刚刚更新问题时所做的期望图像),但当我需要创建大量图像时,这将变得非常繁琐,而且我不确定是否能够自动化... - Blair
1
你可以使用pbaspect修改来获得无方坐标轴。我已经编辑了答案。 - HYRY
1
我认为这是一个非常有用的技巧。请注意,必须在get_proj函数中修改此值。还要注意,在按照此处所示的方式修改后,必须给出pbaspect值。可以通过添加类似于以下内容来避免这种情况:try: self.localPbAspect=self.pbaspect except AttributeError: self.localPbAspect=[1,1,1]并除以self.localPbAspect [...]或类似的东西...因为您可能不总是想手动设置pbaspect - mikuszefski

7

这个问题的答案对我非常有效。而且你不需要设置任何比例,它会自动完成所有操作。


2

在github上有一个问题已经被提出:https://github.com/matplotlib/matplotlib/issues/8593

以上解决方案似乎不再有效。现在必须按照以下方式编辑site-packages\mpl_toolkits\mplot3d\axes3d.py中的get_proj函数:

最初的回答:

        try:
            self.localPbAspect = self.pbaspect
        except AttributeError:
            self.localPbAspect = [1,1,1]

        xmin, xmax = ( lim / self.localPbAspect[0] for lim in self.get_xlim3d() )
        ymin, ymax = ( lim / self.localPbAspect[1] for lim in self.get_ylim3d() )
        zmin, zmax = ( lim / self.localPbAspect[2] for lim in self.get_zlim3d() )

0

这是我如何解决浪费空间问题的方法:

try: 
    self.localPbAspect=self.pbaspect
    zoom_out = (self.localPbAspect[0]+self.localPbAspect[1]+self.localPbAspect[2]) 
except AttributeError: 
    self.localPbAspect=[1,1,1]
    zoom_out = 0 
xmin, xmax = self.get_xlim3d() /  self.localPbAspect[0]
ymin, ymax = self.get_ylim3d() /  self.localPbAspect[1]
zmin, zmax = self.get_zlim3d() /  self.localPbAspect[2]

# transform to uniform world coordinates 0-1.0,0-1.0,0-1.0
worldM = proj3d.world_transformation(xmin, xmax,
                                         ymin, ymax,
                                         zmin, zmax)

# look into the middle of the new coordinates
R = np.array([0.5*self.localPbAspect[0], 0.5*self.localPbAspect[1], 0.5*self.localPbAspect[2]])
xp = R[0] + np.cos(razim) * np.cos(relev) * (self.dist+zoom_out)
yp = R[1] + np.sin(razim) * np.cos(relev) * (self.dist+zoom_out)
zp = R[2] + np.sin(relev) * (self.dist+zoom_out)
E = np.array((xp, yp, zp))

0

这里先宣布完整的解决方案。 Axes3D.set_box_aspect

ax.set_box_aspect(aspect=(x_scale, y_scale, z_scale))

完整代码:

import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np

# Create figure.
fig = plt.figure(figsize=(16, 9))
ax = fig.add_subplot(projection='3d')

# Generate example data.
R, Y = np.meshgrid(np.arange(0, 500, 0.5), np.arange(0, 40, 0.5))
z = 0.1 * np.abs(np.sin(R/40) * np.sin(Y/6))

# Plot the data.
surf = ax.plot_surface(R, Y, z, cmap=cm.jet, linewidth=0)
fig.colorbar(surf)

# Set viewpoint.
ax.azim = -160
ax.elev = 30

# Label axes.
ax.set_xlabel('Along track (m)')
ax.set_ylabel('Range (m)')
ax.set_zlabel('Height (m)')

# padding for axis label not to overlap with axis ticks
ax.xaxis.labelpad=30    

# changing aspect ratio
ax.set_box_aspect(aspect=(4, 1, 0.5), zoom=1.3)

# Save image.
fig.savefig('data.jpg', dpi=200)

生成的图像

感谢 @ImportanceOfBeingErnest 提供了带有答案的问题参考。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接