11

我正在尝试将 2D 数据绘制到 3D 轴上。我正在使用 3D 形状,ax.plot_surface但我无法使用ax.plot.

这是一个精简的示例代码,显示了我在使用 2D 数据时遇到的问题:

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

# Generate Example Data
x = [0.04,0,-0.04]
y = [0.04,0,-0.04]
z = [0.04,0,-0.04]

# Start plotting environment
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

# Plot 3 lines positioned against the axes "walls"
ax.plot(x,y,-0.08,zdir='z',c='r')
ax.plot(x,z, 0.08,zdir='y',c='g')
ax.plot(y,z,-0.08,zdir='x',c='b')

# Label each axis
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.set_zlabel('z')

# Set each axis limits
ax.set_xlim([-0.08,0.08])
ax.set_ylim([-0.08,0.08]) 
ax.set_zlim([-0.08,0.08]) 

# Equally stretch all axes
ax.set_aspect("equal")

# Set plot size for saving to disk
plt.gcf().set_size_inches(11.7,8.3) 

# Save figure in .eps and .png format
plt.savefig('test.eps', format='eps')
plt.savefig('test.png', format='png', dpi=300)

# Display figure
plt.show()

这给出了以下结果,您可以从中看到数据线的末端不位于轴线上(即不与 0.04 和 -0.04 对齐):显示数据到轴壁的偏移量的图

通过交互式探索绘图,我确定将ax.plot调用中的 0.08 更改为 0.083 的幅度(同时保留相关符号)可以使绘图更贴合墙壁。

我对此的解释是,该图没有强制执行我的轴限制,这通过查看轴相交的间距在图上显得很明显,但是使用ax.get_xlim()etc 显示了我设置的值,所以我错过了一些东西。

关于如何让这些地块与墙壁齐平的任何想法?

非常感谢,

蒂姆

编辑:

我还尝试使用设置轴限制

ax.set_xlim3d(-0.08,0.08)
ax.set_ylim3d(-0.08,0.08) 
ax.set_zlim3d(-0.08,0.08) 

 ax.set_xlim3d([-0.08,0.08])
 ax.set_ylim3d([-0.08,0.08]) 
 ax.set_zlim3d([-0.08,0.08])

没有任何运气。

我绝对倾向于将此归因于轴相遇的填充问题,但我无法找到任何有关此的文档。IE。我将绘图位置设置为 -0.08 并将轴限制设置为 -0.08,但绘图在末尾添加了一些填充以使限制介于 -0.082 和 -0.083 之间。

我要么想删除填充,要么获取填充值,以便我可以将其输入到ax.plot命令中。

编辑2:

遇到此问题但尚未解决问题的其他人 更改 mplot3d 图形中网格墙的位置

4

1 回答 1

8

您是对的,该mplot3d模块包含一个函数,可在渲染轴之前将填充添加到轴的最小值和最大值。

不幸的是,填充量是硬编码的,并且当前在最新可用版本的 matplotlib (v2.0) 中用户无法更改。

解决方案一:修改源代码

我发现可以通过在axis3d.py源代码文件中注释掉两行源代码来禁用额外填充。(在 matplotlib 源目录中,它位于 mpl_toolkits > mplot3d > axis3d.py 下)

在 function_get_coord_info()中,该函数首先使用 getter 函数get_w_lims()来检索您设置的 x、y 和 z 限制。它不会直接修改它们,因此为什么当您检查时ax.get_xlim(),它仍然返回 0.08 和 -0.08 的值。

def _get_coord_info(self, renderer):
    minx, maxx, miny, maxy, minz, maxz = self.axes.get_w_lims()
    if minx > maxx:
        minx, maxx = maxx, minx
    if miny > maxy:
        miny, maxy = maxy, miny
    if minz > maxz:
        minz, maxz = maxz, minz
    mins = np.array((minx, miny, minz))
    maxs = np.array((maxx, maxy, maxz))
    centers = (maxs + mins) / 2.
    deltas = (maxs - mins) / 12.
    mins = mins - deltas / 4.
    maxs = maxs + deltas / 4.

    vals = mins[0], maxs[0], mins[1], maxs[1], mins[2], maxs[2]
    tc = self.axes.tunit_cube(vals, renderer.M)
    avgz = [tc[p1][2] + tc[p2][2] + tc[p3][2] + tc[p4][2] for \
            p1, p2, p3, p4 in self._PLANES]
    highs = np.array([avgz[2*i] < avgz[2*i+1] for i in range(3)])

    return mins, maxs, centers, deltas, tc, highs

请注意,它以某种随意的方式计算填充。所以填充不是一个固定的数字,而是取决于您设置的轴限制。

    deltas = (maxs - mins) / 12.
    mins = mins - deltas / 4.
    maxs = maxs + deltas / 4.

draw()调用该函数来渲染轴时,它使用这些修改minsmaxs构建您看到的实际线,因此您总是在轴的每一端获得填充。

我的 hacky 解决方案是像这样注释掉这两行:

    #mins = mins - deltas / 4.
    #maxs = maxs + deltas / 4.

为您提供线条与 3D 轴墙壁齐平的图形。

在此处输入图像描述

但请注意底角的轴标签如何相互重叠,并且 y 标签似乎未对齐......我怀疑这就是硬编码填充是一个特征的原因。可以使用方法 中的旋转属性调整 y 轴刻度标签,set_yticklabels(...)直到它看起来适合您的需要。

解决方案 2:在交互式绘图窗口中使用缩放

另一个不需要修改源代码的(某种)解决方案是在交互式窗口中绘制图形,然后稍微放大直到线条与墙壁齐平。它需要一些试验和错误,因为它是一种“通过眼睛”的方法。请注意,这通常会删除最高刻度标记标签,但它避免了上述解决方案中的重叠问题:

在此处输入图像描述

解决方案3:结合以上所有

因此,鉴于我们知道如何mplot3d计算填充量,我们是否可以使用它来将轴限制设置为恰到好处的量以避免填充问题,而无需使用交互模式或修改源代码?

是的,有一点额外的功能:

def get_fixed_mins_maxs(mins, maxs):
    deltas = (maxs - mins) / 12.
    mins = mins + deltas / 4.
    maxs = maxs - deltas / 4.

    return [mins, maxs]

minmax = get_fixed_mins_maxs(-0.08, 0.08)

# gives us: [-0.07666666666666667, 0.07666666666666667]

# Set each axis limits with the minmax value from our function
ax.set_xlim(minmax)
ax.set_ylim(minmax) 
ax.set_zlim(minmax) 

这给出了解决方案 2 中的相同数字,而无需打开交互式绘图窗口并通过肉眼判断。

于 2017-01-21T12:09:20.450 回答