首页 > 解决方案 > Matplotlib - 强制 3D 绘图使用可用的 figsize

问题描述

我想创建一个具有指定图形大小的图,其中 3D 轴尝试使用整个可用空间,同时在所有轴上保持相同的纵横比。

我当前的尝试显示了一个隐藏部分网格的剪切矩形。

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

fig = plt.figure(figsize=(5, 2.5))
ax = fig.add_subplot(projection='3d')
ax.get_proj = lambda: np.dot(Axes3D.get_proj(ax), np.diag([1, 1, 1, 0.5]))

u = v = np.linspace(0, 2 * np.pi, 50)
u, v = np.meshgrid(u, v)
X = np.cos(v) * (6 - (5/4 + np.sin(3 * u)) * np.sin(u - 3 * v))
Y = (6 - (5/4 + np.sin(3 * u)) * np.sin(u - 3 * v)) * np.sin(v)
Z = -np.cos(u - 3 * v) * (5/4 + np.sin(3 * u))

ax.plot_surface(X, Y, Z, rstride=1, cstride=1, color=[0.7] * 3, linewidth=0.25, edgecolor="k")
ax.set_box_aspect([ub - lb for lb, ub in (getattr(ax, f'get_{a}lim')() for a in 'xyz')])

plt.show()

当前地块

我能做些什么?

标签: pythonmatplotlib

解决方案


我复制了您提供的代码,并在最后添加了 4 行。

from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
plt.close()
fig = plt.figure(figsize=(5, 2.5))
ax = fig.add_subplot(projection='3d')
ax.get_proj = lambda: np.dot(Axes3D.get_proj(ax), np.diag([1, 1, 1, 0.5]))

u = v = np.linspace(0, 2 * np.pi, 50)
u, v = np.meshgrid(u, v)
X = np.cos(v) * (6 - (5/4 + np.sin(3 * u)) * np.sin(u - 3 * v))
Y = (6 - (5/4 + np.sin(3 * u)) * np.sin(u - 3 * v)) * np.sin(v)
Z = -np.cos(u - 3 * v) * (5/4 + np.sin(3 * u))

ax.plot_surface(X, Y, Z, rstride=1, cstride=1, color=[0.7] * 3, linewidth=0.25, edgecolor="k")
# ax.set_box_aspect([ub - lb for lb, ub in (getattr(ax, f'get_{a}lim')() for a in 'xyz')])


left, right = plt.xlim()
ax.set_zlim(left, right)
ax.set_ylim(left, right)
plt.tight_layout()

我注释掉该行ax.set_box_aspect,因为它给了我一个错误。上面的输出是:

在此处输入图像描述

- - 编辑 - -

我有一个解决方法的想法,让它在 matplotlib v3.4.2 中工作:

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

plt.close()

# your code starts here, with a little modification
fig = plt.figure(figsize=(5,5))
ax = fig.add_subplot(projection='3d')

u = v = np.linspace(0, 2 * np.pi, 50)
u, v = np.meshgrid(u, v)
X = np.cos(v) * (6 - (5/4 + np.sin(3 * u)) * np.sin(u - 3 * v))
Y = (6 - (5/4 + np.sin(3 * u)) * np.sin(u - 3 * v)) * np.sin(v)
Z = -np.cos(u - 3 * v) * (5/4 + np.sin(3 * u))
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, color=[0.7] * 3, linewidth=0.25, edgecolor="k")

# set the axes limits
left, right = plt.xlim()
ax.set_zlim(left, right)
ax.set_ylim(left, right)

# zoom in to the plot
ax.dist = 6

# make everything other than the plot itself transparent
fig.patch.set_alpha(0)
ax.patch.set_alpha(0)
ax.axis('off')
plt.tight_layout()

# save plot as image
plt.savefig('plotted.png')

# remove the axes where the image was plotted
ax.remove()

# resize figsize
fig = matplotlib.pyplot.gcf()
fig.set_size_inches(5, 2.5)

# add fake axes for gridlines as in 3d plot to make it look like a real plot
# skip this part if the gridlines are unnecessary
ax_bg = fig.add_subplot(111, projection='3d')
ax_bg.dist = 3

# add axes in cartesian coordinates (xy-plane) for the image
ax = fig.add_subplot(111)
fig.patch.set_alpha(1)
fig.subplots_adjust(left=0, bottom=0, right=1, top=1, wspace=0, hspace=0)
im = plt.imread('plotted.png')

h, w, dc = im.shape # (height=500, width=500, depth/color=4)
im_cropped = im[120:390, :, :] # this is manually adjusted
ax.axis('off')
ax.imshow(im_cropped)

# delete the saved image
os.remove('plotted.png')

输出是:

在此处输入图像描述

我不知道它是否适用于您的特定环境,但它仅用于解决您的问题。

如果有不清楚的地方,请告诉我。


推荐阅读