Python `matplotlib.patches.PathPatch`的线宽以数据单位(非点)表示

Python `matplotlib.patches.PathPatch`的线宽以数据单位(非点)表示,python,matplotlib,Python,Matplotlib,我想创建一个类似matplotlib.patches.PathPatch的面片,线宽以数据单位(不是点)表示。 我知道,对于具有规则形状的面片,可以通过在彼此顶部绘制两个不同大小的面片来模拟这样的对象。 然而,对于任意形状,这种方法在计算上要复杂得多(需要计算任意形状的平行线)。 此外,我希望保留PathPath对象方法,因此最终我要寻找从PathPatch或Patch派生的类 import matplotlib.pyplot as plt from matplotlib.patches imp

我想创建一个类似matplotlib.patches.PathPatch的
面片,线宽以数据单位(不是点)表示。

我知道,对于具有规则形状的面片,可以通过在彼此顶部绘制两个不同大小的面片来模拟这样的对象。 然而,对于任意形状,这种方法在计算上要复杂得多(需要计算任意形状的平行线)。 此外,我希望保留
PathPath
对象方法,因此最终我要寻找从
PathPatch
Patch
派生的类

import matplotlib.pyplot as plt
from matplotlib.patches import PathPatch, Rectangle

fig, (ax1, ax2) = plt.subplots(1, 2, sharex=True, sharey=True)

origin = (0, 0)
width = 1
height = 2
lw = 0.25

outer = Rectangle((origin[0],    origin[1]),    width,      height,      facecolor='darkblue',  zorder=1)
inner = Rectangle((origin[0]+lw, origin[1]+lw), width-2*lw, height-2*lw, facecolor='lightblue', zorder=2)

ax1.add_patch(outer)
ax1.add_patch(inner)
ax1.axis([-0.5, 1.5, -0.5, 2.5])
ax1.set_title('Desired')

path = outer.get_path().transformed(outer.get_patch_transform())
pathpatch = PathPatch(path, facecolor='lightblue', edgecolor='darkblue', linewidth=lw)
ax2.add_patch(pathpatch)
ax2.set_title('Actual')

plt.show()

有一个从
Line2D
派生的函数,用于创建以数据单位表示宽度的行

class LineDataUnits(Line2D):
    # https://stackoverflow.com/a/42972469/2912349
    def __init__(self, *args, **kwargs):
        _lw_data = kwargs.pop("linewidth", 1)
        super().__init__(*args, **kwargs)
        self._lw_data = _lw_data

    def _get_lw(self):
        if self.axes is not None:
            ppd = 72./self.axes.figure.dpi
            trans = self.axes.transData.transform
            return ((trans((1, self._lw_data))-trans((0, 0)))*ppd)[1]
        else:
            return 1

    def _set_lw(self, lw):
        self._lw_data = lw

    _linewidth = property(_get_lw, _set_lw)
我不太明白这个实现是如何工作的(例如,
Line2D
的任何方法似乎都不会被覆盖) 尽管如此,我还是尝试过复制这种方法(
class LineDataUnits(Line2D)
->
class PathPatchDataUnits(PathPatch)
),但是——毫不奇怪——无法让它工作(
AttributeError:'NoneType'对象没有属性“set\u figure”
)。

编辑:

我成功了,并在途中获得了一些见解。 首先,代码如下:

导入matplotlib.pyplot作为plt
从matplotlib.patches导入PathPatch,矩形
#您找到的解决方案中的类,我只是更改了继承
类PatchDataUnits(PathPatch):
# https://stackoverflow.com/a/42972469/2912349
定义初始化(self,*args,**kwargs):
_lw_data=kwargs.pop(“线宽”,1)
super()
self.\u lw\u data=\u lw\u data
def_get_lw(自我):
如果self.axes不是无:
ppd=72./self.axes.figure.dpi
trans=self.axes.transData.transform
#下面提到的行
返回((trans((self.\u lw_数据,self.\u lw_数据))-trans((0,0))*ppd)[1]
其他:
返回1
def_设置_lw(自我,lw):
自。_lw_data=lw
_线宽=属性(获取、设置)
#您的代码示例
图(ax1,ax2)=plt.子批次(1,2,sharex=True,sharey=True)
原点=(0,0)
宽度=1
高度=2
lw=0.25
外部=矩形((原点[0],原点[1]),宽度,高度,面颜色='darkblue',zorder=1)
内部=矩形((原点[0]+lw,原点[1]+lw),宽度-2*lw,高度-2*lw,面颜色=浅蓝色,zorder=2)
ax1.添加_补丁(外部)
ax1.添加修补程序(内部)
ax1.轴([-0.5,1.5,-0.5,2.5])
ax1.设置标题(“所需”)
#更改lw,因为它与一些图重叠
mlw=lw/2
#创建具有调整大小的新修补程序
中间=矩形((原点[0]+mlw,原点[1]+mlw),宽度-2*mlw,高度-2*mlw,面颜色=浅蓝色,zorder=1)
path=mid.get\u path().transformed(mid.get\u patch\u transform())
#使用数据单元类创建具有原始lw的路径修补程序
pathpatch=PatchDataUnits(路径,facecolor='lightblue',edgecolor='darkblue',线宽=lw)
ax2.添加修补程序(pathpatch)
ax2.设置标题(“实际”)
plt.show()
结果是:

现在让我想想:

axes.transData
变换实际上是由两个变换组成的,一个用于x坐标,另一个用于y坐标。 但是,线宽是单个标量,因此我们需要选择将使用的两种变换中的哪一种。 在我的解决方案中,我使用y变换;但是,通过将注释下方的行更改为索引[0],可以选择使用x变换。 如果轴的纵横比相等(即,数据单位中长度为1的水平线在显示单位中的长度与数据单位中长度为1的垂直线的长度相同), 这不是问题。但是,如果纵横比不相等,则可能会产生下图所示的意外行为


可以使用命令
ax强制轴的纵横比相等。set_aspect('equal')

Hi@yotam,感谢您出色的工作,感谢您投入的所有时间。我昨天确实看到了您的其他尝试,但没有时间回应。您讨论的令人惊讶的行为对于我的用例来说不是问题。通过将轴的纵横比设置为相等(
ax.set_aspect('equal')
)也可以很容易地防止这种情况;不过,这些都只是建议,请随意拒绝。嗨@PaulBrodersen,一段时间后,让它发挥作用已经成为个人的挑战。还感谢您的编辑,我更喜欢您的措辞,而不是我原来的:)