如何删除 3D 投影图中的偏移量?

How to remove offsets in 3D projection plot?

我发现我使用 matplotlib 在 3D 中制作的绘图有轻微的“错位”。这是一个 MWE:

import numpy as np
from matplotlib import pyplot as plt
figure = plt.figure(figsize=(8,10.7))
ax = plt.gca(projection='3d')
ax.plot_surface(np.array([[0, 0], [30, 30]]),
                np.array([[10, 10], [10, 10]]),
                np.array([[10, 20], [10, 20]]), 
                rstride=1, cstride=1
)
ax.plot_surface(np.array([[0, 0], [30, 30]]),
                np.array([[20, 20], [20, 20]]),
                np.array([[10, 20], [10, 20]]), 
                rstride=1, cstride=1
)
plt.show()
plt.close()

显然,垃圾箱没有正确居中,因为表面似乎从 10.5 开始到 20.5 结束,而不是 10 和 20。如何实现后者?

编辑:恐怕建议的答案有问题。 x轴没有黑实线,默认是这样的:

当我取出建议的包装时,我得到:

不幸的是,当我取出我正在绘制的东西时,这个问题在 Jupyter notebook 中无法重现,但是我想知道你是否可以向我指出我想要的东西必须这样做,在我的情况下,x 轴再次有一条黑线?

这是matplotlib对3DAxis坐标的处理造成的。它故意移动 minsmaxs 以创建一些人为填充:

axis3d.py#L190-L194

class Axis(maxis.XAxis):
   ...
   def _get_coord_info(self, renderer):
       ...
       # Add a small offset between min/max point and the edge of the plot
       deltas = (maxs - mins) / 12
       mins -= 0.25 * deltas
       maxs += 0.25 * deltas
       ...
       return mins, maxs, centers, deltas, bounds_proj, highs

从 v3.5.1 开始,没有控制此行为的参数。

但是,我们可以通过 _unpadded 属性使用 functools.wraps to create a wrapper around Axis._get_coord_info that unshifts mins and maxs. To prevent this wrapper from unshifting multiple times (e.g., when rerunning its Jupyter cell), track the wrapper state

from functools import wraps

def unpad(f): # where f will be Axis._get_coord_info
    @wraps(f)
    def wrapper(*args, **kwargs):
        mins, maxs, centers, deltas, bounds_proj, highs = f(*args, **kwargs)
        mins += 0.25 * deltas # undo original subtraction
        maxs -= 0.25 * deltas # undo original addition
        return mins, maxs, centers, deltas, bounds_proj, highs

    if getattr(f, '_unpadded', False): # bypass if already unpadded
        return f
    else:
        wrapper._unpadded = True # mark as unpadded
        return wrapper

在绘图前应用 unpad 包装器:

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.axis3d import Axis

X = np.array([[0, 0], [30, 30]])
Z = np.array([[10, 20], [10, 20]])
y1, y2, y3 = 10, 16, 20

# wrap Axis._get_coord_info with our unpadded version
Axis._get_coord_info = unpad(Axis._get_coord_info)

fig, ax = plt.subplots(figsize=(8, 10.7), subplot_kw={'projection': '3d'})
ax.plot_surface(X, np.tile(y1, (2, 2)), Z, rstride=1, cstride=1)
ax.plot_surface(X, np.tile(y2, (2, 2)), Z, rstride=1, cstride=1)
ax.plot_surface(X, np.tile(y3, (2, 2)), Z, rstride=1, cstride=1)

plt.show()