在数据单元中展开指定宽度的行 [英] Expand the line with specified width in data unit
问题描述
我的问题有点类似于
对 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:
- It's independend on the subplot placements, margins or figure size.
- If the aspect ratio is unequal, it uses y data coordinates as the linewidth.
- 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).
- 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屋!