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 matplotlib
es 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 numpy
einige Funktionen aus der Standardbibliothek benötigt math
.
Beschreibung
Das Standardverhalten der labelLines
Funktion besteht darin, die Beschriftungen gleichmäßig entlang der x
Achse zu platzieren ( y
natü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_lines
berücksichtigt die Funktion nicht die Zeilen, denen im plot
Befehl keine Beschriftung zugewiesen wurde (oder genauer, wenn die Beschriftung enthält '_line'
).
Schlüsselwortargumente, die an den Funktionsaufruf übergeben labelLines
oder an diesen labelLine
weitergegeben 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
1
und 10
Anmerkungen im oberen linken Diagramm gezeigt. Ich bin mir nicht mal sicher, ob dies vermieden werden kann.
- Es wäre schön,
y
stattdessen 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 float
s sind
Fallstricke
- Standardmäßig geht die
labelLines
Funktion 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 x
Bereich 0.5
- 1
dann 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 xvals
Argument. Eine Liste der x
vom 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.