Python 是否以数据单位展开具有指定宽度的行?

Python 是否以数据单位展开具有指定宽度的行?,python,matplotlib,Python,Matplotlib,我的问题有点像。让我的问题更具挑战性的是,与链接问题不同,我希望扩展的部分是随机方向的 假设线段从(0,10)到(10,10),我希望将其扩展到6的宽度。那就简单了 x = [0, 10] y = [10, 10] ax.fill_between(x, y - 3, y + 3) 但是,我的线段是随机方向的。也就是说,它不一定沿x轴或y轴。它有一定的斜率 线段s定义为其起点和终点的列表:[(x1,y1)、(x2,y2)] 现在我希望将线段扩展到某个宽度w该解决方案适用于任何方向的线段。如何做到

我的问题有点像。让我的问题更具挑战性的是,与链接问题不同,我希望扩展的部分是随机方向的

假设线段从
(0,10)
(10,10)
,我希望将其扩展到
6的宽度。那就简单了

x = [0, 10]
y = [10, 10]
ax.fill_between(x, y - 3, y + 3)
但是,我的线段是随机方向的。也就是说,它不一定沿x轴或y轴。它有一定的斜率

线段
s
定义为其起点和终点的列表:
[(x1,y1)、(x2,y2)]

现在我希望将线段扩展到某个宽度
w
该解决方案适用于任何方向的线段。如何做到这一点

plt.plot(x,y,linewidth=6.0)
无法实现此功能,因为我希望宽度与数据的单位相同。

说明:

  • 将图形设置为已知高度,并使两个轴的比例相等(否则“数据坐标”的概念不适用)确保图形的比例与x轴和y轴的预期比例匹配

  • 将英寸乘以72,以点为单位计算整个图形的高度(包括边距)

  • 手动指定y轴范围
    yrange
    (可以通过先绘制“虚拟”系列,然后查询打印轴以获得y轴的上下限来完成此操作。)

  • 数据单元中提供所需的行宽度
    linewid

  • 在调整边距时,计算这些单位在点中的数值。在单帧打印中,打印是整个图像高度的80%

  • 使用不填充线条末端的capstyle绘制线条(在这些大线条尺寸下有很大影响)

瞧?(注意:这将在保存的文件中生成正确的图像,但如果调整打印窗口的大小,则不保证图像正确。)


为了补充前面的答案(现在还不能评论),这里有一个函数可以自动执行此过程,而不需要等轴或标签的启发式值0.8。调用此函数后,轴的数据限制和大小需要固定且不更改

def linewidth_from_data_units(linewidth, axis, reference='y'):
    """
    Convert a linewidth in data units to linewidth in points.

    Parameters
    ----------
    linewidth: float
        Linewidth in data units of the respective reference-axis
    axis: matplotlib axis
        The axis which is used to extract the relevant transformation
        data (data limits and size must not change afterwards)
    reference: string
        The axis that is taken as a reference for the data width.
        Possible values: 'x' and 'y'. Defaults to 'y'.

    Returns
    -------
    linewidth: float
        Linewidth in points
    """
    fig = axis.get_figure()
    if reference == 'x':
        length = fig.bbox_inches.width * axis.get_position().width
        value_range = np.diff(axis.get_xlim())
    elif reference == 'y':
        length = fig.bbox_inches.height * axis.get_position().height
        value_range = np.diff(axis.get_ylim())
    # Convert length to points
    length *= 72
    # Scale linewidth to value range
    return linewidth * (length / value_range)

以下代码是有关如何在matplotlib中使用数据坐标作为线宽绘制线图的通用示例。有两种解决办法;一个使用回调,一个使用子类化Line2D

使用回调。 它被实现为一个类
data\u linewidth\u plot
,可以使用与普通
plt.plot
命令非常接近的签名调用该类

l = data_linewidth_plot(x, y, ax=ax, label='some line', linewidth=1, alpha=0.4)
其中,
ax
是要打印的轴。当图中仅存在一个子图时,
ax
参数可以省略。
linewidth
参数以(y-)数据单位解释

其他特点:

  • 它独立于子地块位置、边距或图形大小
  • 如果纵横比不相等,则使用y数据坐标作为线宽
  • 它还注意正确设置图例句柄(我们可能希望在绘图中有一条大线,但肯定不会在图例中)
  • 它与图形大小、缩放或平移事件的更改兼容,因为它负责调整这些事件的线宽
  • 这是完整的代码

    import matplotlib.pyplot as plt
    
    class data_linewidth_plot():
        def __init__(self, x, y, **kwargs):
            self.ax = kwargs.pop("ax", plt.gca())
            self.fig = self.ax.get_figure()
            self.lw_data = kwargs.pop("linewidth", 1)
            self.lw = 1
            self.fig.canvas.draw()
    
            self.ppd = 72./self.fig.dpi
            self.trans = self.ax.transData.transform
            self.linehandle, = self.ax.plot([],[],**kwargs)
            if "label" in kwargs: kwargs.pop("label")
            self.line, = self.ax.plot(x, y, **kwargs)
            self.line.set_color(self.linehandle.get_color())
            self._resize()
            self.cid = self.fig.canvas.mpl_connect('draw_event', self._resize)
    
        def _resize(self, event=None):
            lw =  ((self.trans((1, self.lw_data))-self.trans((0, 0)))*self.ppd)[1]
            if lw != self.lw:
                self.line.set_linewidth(lw)
                self.lw = lw
                self._redraw_later()
    
        def _redraw_later(self):
            self.timer = self.fig.canvas.new_timer(interval=10)
            self.timer.single_shot = True
            self.timer.add_callback(lambda : self.fig.canvas.draw_idle())
            self.timer.start()
    
    fig1, ax1 = plt.subplots()
    #ax.set_aspect('equal') #<-not necessary 
    ax1.set_ylim(0,3)
    x = [0,1,2,3]
    y = [1,1,2,2]
    
    # plot a line, with 'linewidth' in (y-)data coordinates.       
    l = data_linewidth_plot(x, y, ax=ax1, label='some 1 data unit wide line', 
                            linewidth=1, alpha=0.4)
    
    plt.legend() # <- legend possible
    plt.show()
    
    导入matplotlib.pyplot作为plt
    类数据\u线宽\u绘图():
    定义初始值(self,x,y,**kwargs):
    self.ax=kwargs.pop(“ax”,plt.gca())
    self.fig=self.ax.get_figure()
    self.lw_data=kwargs.pop(“线宽”,1)
    self.lw=1
    self.fig.canvas.draw()
    self.ppd=72./self.fig.dpi
    self.trans=self.ax.transData.transform
    self.linehandle,=self.ax.plot([],[],**kwargs)
    如果kwargs中的“标签”:kwargs.pop(“标签”)
    self.line,=self.ax.plot(x,y,**kwargs)
    self.line.set_color(self.linehandle.get_color())
    self._resize()
    self.cid=self.fig.canvas.mpl\u connect('draw\u event',self.\u resize)
    def_resize(self,event=None):
    lw=((self.trans((1,self.lw_数据))-self.trans((0,0))*self.ppd)[1]
    如果lw!=self.lw:
    self.line.set_线宽(lw)
    self.lw=lw
    self.\u稍后重新绘制()
    定义稍后重新绘制(自我):
    self.timer=self.fig.canvas.new_计时器(间隔=10)
    self.timer.single_shot=True
    self.timer.add\u回调(lambda:self.fig.canvas.draw\u idle())
    self.timer.start()
    图1,ax1=plt.subplot()
    
    #ax.set_aspect('equal')#为什么不能使用线宽参数<代码>plt.plot(x,y,线宽=6.0)
    @beroe,因为我希望宽度与数据的单位相同。假设我的数据以米为单位。然后我想我的线宽是6米。我怀疑你真的想画矩形。这看起来很像这个问题@Jakob,不完全一样,如果你的目标是保存的图形,那么缩放支持就没有必要了。mavErick,我想我在下面找到了它,但是如果你想要多个子批次,你必须调整比例因子。它不是应该是
    fig.dpi
    而不是
    72
    ?经过进一步的测试,看起来确实是
    72
    ,而不是
    fig.dpi
    。我不知道为什么…完美,除非需要标量而不是数组:
    np.diff(axis.get\u lim())
    应该是
    np.diff(axis.get\u lim())[0]
    。谢谢@Phyks回答为什么
    72
    是正确的数字:线宽以点为单位。点单位通常为72点/英寸,在matplotlib中也是如此。虽然每英寸点数可能会改变,但每英寸点数保持不变。谢谢,但是。。。据你所知,
    import matplotlib.pyplot as plt
    
    class data_linewidth_plot():
        def __init__(self, x, y, **kwargs):
            self.ax = kwargs.pop("ax", plt.gca())
            self.fig = self.ax.get_figure()
            self.lw_data = kwargs.pop("linewidth", 1)
            self.lw = 1
            self.fig.canvas.draw()
    
            self.ppd = 72./self.fig.dpi
            self.trans = self.ax.transData.transform
            self.linehandle, = self.ax.plot([],[],**kwargs)
            if "label" in kwargs: kwargs.pop("label")
            self.line, = self.ax.plot(x, y, **kwargs)
            self.line.set_color(self.linehandle.get_color())
            self._resize()
            self.cid = self.fig.canvas.mpl_connect('draw_event', self._resize)
    
        def _resize(self, event=None):
            lw =  ((self.trans((1, self.lw_data))-self.trans((0, 0)))*self.ppd)[1]
            if lw != self.lw:
                self.line.set_linewidth(lw)
                self.lw = lw
                self._redraw_later()
    
        def _redraw_later(self):
            self.timer = self.fig.canvas.new_timer(interval=10)
            self.timer.single_shot = True
            self.timer.add_callback(lambda : self.fig.canvas.draw_idle())
            self.timer.start()
    
    fig1, ax1 = plt.subplots()
    #ax.set_aspect('equal') #<-not necessary 
    ax1.set_ylim(0,3)
    x = [0,1,2,3]
    y = [1,1,2,2]
    
    # plot a line, with 'linewidth' in (y-)data coordinates.       
    l = data_linewidth_plot(x, y, ax=ax1, label='some 1 data unit wide line', 
                            linewidth=1, alpha=0.4)
    
    plt.legend() # <- legend possible
    plt.show()
    
    import matplotlib.pyplot as plt
    from matplotlib.lines import Line2D
    
    class LineDataUnits(Line2D):
        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)
    
    
    fig, ax = plt.subplots()
    
    #ax.set_aspect('equal') # <-not necessary, if not given, y data is assumed 
    ax.set_xlim(0,3)
    ax.set_ylim(0,3)
    x = [0,1,2,3]
    y = [1,1,2,2]
    
    line = LineDataUnits(x, y, linewidth=1, alpha=0.4)
    ax.add_line(line)
    
    ax.legend([Line2D([],[], linewidth=3, alpha=0.4)], 
               ['some 1 data unit wide line'])    # <- legend possible via proxy artist
    plt.show()