以下代码是关于如何在 matplotlib 中使用数据坐标作为线宽制作线图的通用示例。有两种解决方案;一种使用回调,一种使用子类 Line2D。
使用回调。
它被实现为一个data_linewidth_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()
(由于这个问题,我更新了代码以使用计时器重绘画布)
子类化 Line2D
上述解决方案有一些缺点。它需要一个计时器和回调来更新自己改变轴限制或图形大小。以下是没有此类需求的解决方案。它将使用动态属性始终根据数据坐标中所需的线宽计算线宽(以点为单位)。它比上面的要短得多。这里的一个缺点是需要通过代理艺术家手动创建图例。
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()