在数据单元中展开指定宽度的行 [英] Expand the line with specified width in data unit

查看:16
本文介绍了在数据单元中展开指定宽度的行的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的问题有点类似于

(由于这个问题,我更新了代码以使用计时器重绘画布)

对 Line2D 进行子类化

上述解决方案有一些缺点.它需要一个计时器和回调函数来更新轴限制或图形大小的变化.下面是一个没有这种需求的解决方案.它将使用动态属性始终根据数据坐标中的所需线宽计算线宽(以点为单位).它比上面的要短得多.这里的一个缺点是需要通过代理艺术家手动创建图例.

将 matplotlib.pyplot 导入为 plt从 matplotlib.lines 导入 Line2D类 LineDataUnits(Line2D):def __init__(self, *args, **kwargs):_lw_data = kwargs.pop("线宽", 1)super().__init__(*args, **kwargs)self._lw_data = _lw_datadef_get_lw(self):如果 self.axes 不是 None:ppd = 72./self.axes.figure.dpitrans = self.axes.transData.transform返回 ((trans((1, self._lw_data))-trans((0, 0)))*ppd)[1]别的:返回 1def _set_lw(self, lw):self._lw_data = lw_linewidth = 属性(_get_lw,_set_lw)图, ax = plt.subplots()#ax.set_aspect('equal') # <-没有必要,如果没有给出,则假设为y数据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']) # <- 通过代理艺术家可能的传说plt.show()

My question is a bit similar to this question that draws line with width given in data coordinates. What makes my question a bit more challenging is that unlike the linked question, the segment that I wish to expand is of a random orientation.

Let's say if the line segment goes from (0, 10) to (10, 10), and I wish to expand it to a width of 6. Then it is simply

x = [0, 10]
y = [10, 10]
ax.fill_between(x, y - 3, y + 3)

However, my line segment is of random orientation. That is, it is not necessarily along x-axis or y-axis. It has a certain slope.

A line segment s is defined as a list of its starting and ending points: [(x1, y1), (x2, y2)].

Now I wish to expand the line segment to a certain width w. The solution is expected to work for a line segment in any orientation. How to do this?

plt.plot(x, y, linewidth=6.0) cannot do the trick, because I want my width to be in the same unit as my data.

解决方案

The following code is a generic example on how to make a line plot in matplotlib using data coordinates as linewidth. There are two solutions; one using callbacks, one using subclassing Line2D.

Using callbacks.

It is implemted as a class data_linewidth_plot that can be called with a signature pretty close the the normal plt.plot command,

l = data_linewidth_plot(x, y, ax=ax, label='some line', linewidth=1, alpha=0.4)

where ax is the axes to plot to. The ax argument can be omitted, when only one subplot exists in the figure. The linewidth argument is interpreted in (y-)data units.

Further features:

  1. It's independend on the subplot placements, margins or figure size.
  2. If the aspect ratio is unequal, it uses y data coordinates as the linewidth.
  3. It also takes care that the legend handle is correctly set (we may want to have a huge line in the plot, but certainly not in the legend).
  4. It is compatible with changes to the figure size, zoom or pan events, as it takes care of resizing the linewidth on such events.

Here is the complete code.

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()

(I updated the code to use a timer to redraw the canvas, due to this issue)

Subclassing Line2D

The above solution has some drawbacks. It requires a timer and callbacks to update itself on changing axis limits or figure size. The following is a solution without such needs. It will use a dynamic property to always calculate the linewidth in points from the desired linewidth in data coordinates on the fly. It is much shorter than the above. A drawback here is that a legend needs to be created manually via a proxyartist.

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()

这篇关于在数据单元中展开指定宽度的行的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆