更新: 用户 cphyc 已为此答案中的代码创建了一个 Github 存储库(请参阅 here),并将代码捆绑到一个可以使用 pip install matplotlib-label-lines 安装的包中.
漂亮的图片:
在matplotlib 中,label contour plots 非常容易(自动或通过单击鼠标手动放置标签)。 (还)似乎没有任何等效的能力来以这种方式标记数据系列!不包括我缺少的此功能可能有一些语义原因。
无论如何,我已经编写了以下模块,它允许半自动绘图标签。它只需要numpy 和标准math 库中的几个函数。
说明
labelLines 函数的默认行为是将标签沿x 轴均匀分布(当然,自动放置在正确的y 值处)。如果你愿意,你可以只传递每个标签的 x 坐标数组。您甚至可以调整一个标签的位置(如右下图所示),如果您愿意,可以将其余标签均匀地隔开。
此外,label_lines 函数不考虑在plot 命令中未分配标签的行(或更准确地说,如果标签包含'_line')。
传递给labelLines 或labelLine 的关键字参数被传递给text 函数调用(如果调用代码选择不指定,则设置一些关键字参数)。
问题
- 注释边界框有时会干扰其他曲线。如左上图中的
1 和10 注释所示。我什至不确定这是否可以避免。
- 有时最好指定
y 位置。
- 在正确的位置获取注释仍然是一个迭代过程
- 仅当
x-axis 值为floats 时才有效
陷阱
- 默认情况下,
labelLines 函数假定所有数据系列都跨越由轴限制指定的范围。看看漂亮图片左上角的蓝色曲线。如果只有x 范围0.5-1 的数据可用,那么我们不可能在所需位置放置标签(比0.2 少一点)。请参阅this question 以获取一个特别讨厌的示例。目前,代码不能智能地识别这种情况并重新排列标签,但是有一个合理的解决方法。 labelLines 函数采用xvals 参数;由用户指定的x-values 列表,而不是宽度上的默认线性分布。因此,用户可以决定将哪些x-values 用于每个数据系列的标签放置。
另外,我相信这是完成将标签与它们所在的曲线对齐的奖励目标的第一个答案。 :)
label_lines.py:
from math import atan2,degrees
import numpy as np
#Label line with line2D label data
def labelLine(line,x,label=None,align=True,**kwargs):
ax = line.axes
xdata = line.get_xdata()
ydata = line.get_ydata()
if (x < xdata[0]) or (x > xdata[-1]):
print('x label location is outside data range!')
return
#Find corresponding y co-ordinate and angle of the line
ip = 1
for i in range(len(xdata)):
if x < xdata[i]:
ip = i
break
y = ydata[ip-1] + (ydata[ip]-ydata[ip-1])*(x-xdata[ip-1])/(xdata[ip]-xdata[ip-1])
if not label:
label = line.get_label()
if align:
#Compute the slope
dx = xdata[ip] - xdata[ip-1]
dy = ydata[ip] - ydata[ip-1]
ang = degrees(atan2(dy,dx))
#Transform to screen co-ordinates
pt = np.array([x,y]).reshape((1,2))
trans_angle = ax.transData.transform_angles(np.array((ang,)),pt)[0]
else:
trans_angle = 0
#Set a bunch of keyword arguments
if 'color' not in kwargs:
kwargs['color'] = line.get_color()
if ('horizontalalignment' not in kwargs) and ('ha' not in kwargs):
kwargs['ha'] = 'center'
if ('verticalalignment' not in kwargs) and ('va' not in kwargs):
kwargs['va'] = 'center'
if 'backgroundcolor' not in kwargs:
kwargs['backgroundcolor'] = ax.get_facecolor()
if 'clip_on' not in kwargs:
kwargs['clip_on'] = True
if 'zorder' not in kwargs:
kwargs['zorder'] = 2.5
ax.text(x,y,label,rotation=trans_angle,**kwargs)
def labelLines(lines,align=True,xvals=None,**kwargs):
ax = lines[0].axes
labLines = []
labels = []
#Take only the lines which have labels other than the default ones
for line in lines:
label = line.get_label()
if "_line" not in label:
labLines.append(line)
labels.append(label)
if xvals is None:
xmin,xmax = ax.get_xlim()
xvals = np.linspace(xmin,xmax,len(labLines)+2)[1:-1]
for line,x,label in zip(labLines,xvals,labels):
labelLine(line,x,label,align,**kwargs)
测试代码生成上面漂亮的图片:
from matplotlib import pyplot as plt
from scipy.stats import loglaplace,chi2
from labellines import *
X = np.linspace(0,1,500)
A = [1,2,5,10,20]
funcs = [np.arctan,np.sin,loglaplace(4).pdf,chi2(5).pdf]
plt.subplot(221)
for a in A:
plt.plot(X,np.arctan(a*X),label=str(a))
labelLines(plt.gca().get_lines(),zorder=2.5)
plt.subplot(222)
for a in A:
plt.plot(X,np.sin(a*X),label=str(a))
labelLines(plt.gca().get_lines(),align=False,fontsize=14)
plt.subplot(223)
for a in A:
plt.plot(X,loglaplace(4).pdf(a*X),label=str(a))
xvals = [0.8,0.55,0.22,0.104,0.045]
labelLines(plt.gca().get_lines(),align=False,xvals=xvals,color='k')
plt.subplot(224)
for a in A:
plt.plot(X,chi2(5).pdf(a*X),label=str(a))
lines = plt.gca().get_lines()
l1=lines[-1]
labelLine(l1,0.6,label=r'$Re=${}'.format(l1.get_label()),ha='left',va='bottom',align = False)
labelLines(lines[:-1],align=False)
plt.show()