Update: Benutzer cphyc hat freundlicherweise ein Github-Repository für den Code in dieser Antwort erstellt (siehe hier ) und den Code in einem Paket gebündelt, das möglicherweise mit installiert wird pip install matplotlib-label-lines.
Schönes Bild:

In matplotlibes ist ziemlich leicht zu Etikett Kontur - Plots (entweder automatisch oder manuell durch Etiketten mit Mausklicks Platzierung). Es scheint (noch) keine äquivalente Fähigkeit zu geben, Datenreihen auf diese Weise zu kennzeichnen! Es kann einen semantischen Grund dafür geben, diese Funktion, die mir fehlt, nicht aufzunehmen.
Unabhängig davon habe ich das folgende Modul geschrieben, das halbautomatische Plotbeschriftungen zulässt. Es werden nur numpyeinige Funktionen aus der Standardbibliothek benötigt math.
Beschreibung
Das Standardverhalten der labelLinesFunktion besteht darin, die Beschriftungen gleichmäßig entlang der xAchse zu platzieren ( ynatürlich automatisch mit dem richtigen Wert). Wenn Sie möchten, können Sie einfach ein Array der x-Koordinaten der einzelnen Beschriftungen übergeben. Sie können sogar die Position eines Etiketts anpassen (siehe Abbildung unten rechts) und den Rest gleichmäßig verteilen, wenn Sie möchten.
Darüber hinaus label_linesberücksichtigt die Funktion nicht die Zeilen, denen im plotBefehl keine Beschriftung zugewiesen wurde (oder genauer, wenn die Beschriftung enthält '_line').
Schlüsselwortargumente, die an den Funktionsaufruf übergeben labelLinesoder an diesen labelLineweitergegeben werden text(einige Schlüsselwortargumente werden festgelegt, wenn der aufrufende Code keine Angabe macht).
Probleme
- Anmerkungsbegrenzungsrahmen stören manchmal unerwünschte andere Kurven. Wie durch die
1und 10Anmerkungen im oberen linken Diagramm gezeigt. Ich bin mir nicht mal sicher, ob dies vermieden werden kann.
- Es wäre schön,
ystattdessen manchmal eine Position anzugeben .
- Es ist immer noch ein iterativer Prozess, um Anmerkungen an der richtigen Stelle abzurufen
- Es funktioniert nur, wenn die
x-axis-Werte floats sind
Fallstricke
- Standardmäßig geht die
labelLinesFunktion davon aus, dass alle Datenreihen den durch die Achsengrenzen angegebenen Bereich umfassen. Schauen Sie sich die blaue Kurve oben links im hübschen Bild an. Wenn nur Daten zur Verfügung standen für den xBereich 0.5- 1dann könnten wir nicht möglicherweise eine Markierung setzen an der gewünschten Stelle (was etwas weniger als ist 0.2). In dieser Frage finden Sie ein besonders unangenehmes Beispiel. Derzeit identifiziert der Code dieses Szenario nicht intelligent und ordnet die Beschriftungen neu an. Es gibt jedoch eine angemessene Problemumgehung. Die Funktion labelLines übernimmt das xvalsArgument. Eine Liste der xvom Benutzer angegebenen Werte anstelle der standardmäßigen linearen Verteilung über die Breite. So kann der Benutzer entscheiden, welchex-Werte, die für die Etikettenplatzierung jeder Datenreihe verwendet werden sollen.
Ich glaube auch, dass dies die erste Antwort ist, um das Bonusziel zu erreichen, die Beschriftungen an der Kurve auszurichten, auf der sie sich befinden. :) :)
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)
Testcode, um das hübsche Bild oben zu erzeugen:
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()
plt.plot(x2, 3*x2**2, label="3x*x"); plt.plot(x2, 2*x2**2, label="2x*x"); plt.plot(x2, 0.5*x2**2, label="0.5x*x"); plt.plot(x2, -1*x2**2, label="-x*x"); plt.plot(x2, -2.5*x2**2, label="-2.5*x*x"); my_legend();Dadurch wird eine der Beschriftungen in der oberen linken Ecke platziert. Irgendwelche Ideen, wie man das behebt? Das Problem scheint zu sein, dass die Linien zu nahe beieinander liegen.