Aktualizacja: Użytkownik cphyc uprzejmie utworzył repozytorium Github dla kodu w tej odpowiedzi (patrz tutaj ) i umieścił kod w pakiecie, który można zainstalować za pomocą pip install matplotlib-label-lines.
Ładne zdjęcie:

W matplotlibto dość łatwe do działek konturu etykieta (automatycznie lub ręcznie umieszczania etykiet z kliknięć myszką). Wydaje się, że nie ma (jeszcze) równoważnej możliwości opisywania serii danych w ten sposób! Może istnieć jakiś semantyczny powód, aby nie uwzględnić tej funkcji, której mi brakuje.
Mimo to napisałem następujący moduł, który przyjmuje dowolne opcje półautomatycznego etykietowania działek. Wymaga tylko numpyi kilku funkcji z mathbiblioteki standardowej .
Opis
Domyślnym zachowaniem labelLinesfunkcji jest równomierne rozmieszczenie etykiet wzdłuż xosi ( yoczywiście automatyczne umieszczenie na prawidłowej wartości). Jeśli chcesz, możesz po prostu przekazać tablicę współrzędnych x każdej z etykiet. Możesz nawet dostosować położenie jednej etykiety (jak pokazano na wykresie w prawym dolnym rogu) i równomiernie rozmieścić resztę, jeśli chcesz.
Ponadto label_linesfunkcja nie uwzględnia wierszy, które nie mają przypisanej etykiety w plotpoleceniu (lub dokładniej, jeśli etykieta zawiera '_line').
Argumenty słów kluczowych przekazywane labelLineslub labelLinesą przekazywane do textwywołania funkcji (niektóre argumenty słów kluczowych są ustawiane, jeśli kod wywołujący zdecyduje się nie określać).
Zagadnienia
- Ramki ograniczające adnotacje czasami w niepożądany sposób kolidują z innymi krzywymi. Jak pokazano w adnotacjach
1i 10w lewym górnym rogu wykresu. Nie jestem nawet pewien, czy można tego uniknąć.
yPrzydałoby się czasem określić pozycję.
- Nadal jest to proces iteracyjny, aby uzyskać adnotacje we właściwej lokalizacji
- Działa tylko wtedy, gdy
xwartości -axis są floats
Gotchas
- Domyślnie
labelLinesfunkcja zakłada, że wszystkie serie danych obejmują zakres określony przez limity osi. Spójrz na niebieską krzywą w lewym górnym rogu tego ładnego obrazka. Gdyby dostępne były tylko dane dla xzakresu 0.5- 1wtedy nie moglibyśmy umieścić etykiety w żądanym miejscu (czyli trochę mniej niż 0.2). Zobacz to pytanie, aby zobaczyć szczególnie nieprzyjemny przykład. Obecnie kod nie identyfikuje w inteligentny sposób tego scenariusza i nie zmienia kolejności etykiet, jednak istnieje rozsądne obejście tego problemu. Funkcja labelLines przyjmuje xvalsargument; lista x-wartości określonych przez użytkownika zamiast domyślnego rozkładu liniowego na szerokości. Dzięki temu użytkownik może zdecydować, który plikx-wartości używane do umieszczania etykiet dla każdej serii danych.
Uważam również, że jest to pierwsza odpowiedź na realizację celu dodatkowego, jakim jest wyrównanie etykiet z krzywą, na której się znajdują. :)
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)
Przetestuj kod, aby wygenerować ładny obrazek powyżej:
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();powoduje umieszczenie jednej z etykiet w lewym górnym rogu. Jakieś pomysły, jak to naprawić? Wygląda na to, że problem może polegać na tym, że linie są zbyt blisko siebie.