Aggiornamento: l' utente cphyc ha gentilmente creato un repository Github per il codice in questa risposta (vedi qui ) e ha raggruppato il codice in un pacchetto che può essere installato utilizzando pip install matplotlib-label-lines
.
Bella immagine:
In matplotlib
è abbastanza facile etichettare i grafici di contorno (automaticamente o posizionando manualmente le etichette con i clic del mouse). Non sembra (ancora) esserci alcuna capacità equivalente per etichettare le serie di dati in questo modo! Potrebbe esserci qualche ragione semantica per non includere questa caratteristica che mi manca.
Indipendentemente da ciò, ho scritto il seguente modulo che accetta qualsiasi autorizzazione per l'etichettatura semiautomatica del diagramma. Richiede solo numpy
e un paio di funzioni dalla math
libreria standard .
Descrizione
Il comportamento predefinito della labelLines
funzione è di distanziare le etichette in modo uniforme lungo l' x
asse (posizionando automaticamente il valore corretto y
ovviamente). Se vuoi puoi semplicemente passare un array delle coordinate x di ciascuna delle etichette. Puoi anche modificare la posizione di un'etichetta (come mostrato nella trama in basso a destra) e distanziare il resto in modo uniforme, se lo desideri.
Inoltre, la label_lines
funzione non tiene conto delle righe a cui non è stata assegnata un'etichetta nel plot
comando (o più precisamente se l'etichetta contiene '_line'
).
Gli argomenti della parola chiave passati labelLines
o labelLine
vengono passati alla text
chiamata di funzione (alcuni argomenti della parola chiave vengono impostati se il codice chiamante sceglie di non specificare).
Problemi
- I rettangoli di delimitazione delle annotazioni a volte interferiscono in modo indesiderato con altre curve. Come mostrato dalle annotazioni
1
e 10
nel grafico in alto a sinistra. Non sono nemmeno sicuro che questo possa essere evitato.
- Sarebbe bello
y
invece specificare una posizione a volte.
- È ancora un processo iterativo per ottenere le annotazioni nella posizione corretta
- Funziona solo quando i
x
valori -axis sono float
s
Trabocchetti
- Per impostazione predefinita, la
labelLines
funzione presuppone che tutte le serie di dati coprano l'intervallo specificato dai limiti dell'asse. Dai un'occhiata alla curva blu nella trama in alto a sinistra della bella immagine. Se fossero disponibili solo dati per l' x
intervallo 0.5
, 1
non potremmo posizionare un'etichetta nella posizione desiderata (che è leggermente inferiore a 0.2
). Vedi questa domanda per un esempio particolarmente sgradevole. Al momento, il codice non identifica in modo intelligente questo scenario e riorganizza le etichette, tuttavia esiste una soluzione alternativa ragionevole. La funzione labelLines accetta l' xvals
argomento; un elenco di x
valori specificati dall'utente invece della distribuzione lineare predefinita sulla larghezza. Quindi l'utente può decidere qualex
-valori da utilizzare per il posizionamento dell'etichetta di ciascuna serie di dati.
Inoltre, credo che questa sia la prima risposta per completare l' obiettivo bonus di allineare le etichette con la curva su cui si trovano. :)
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)
Prova il codice per generare la bella immagine sopra:
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();
questo mette una delle etichette nell'angolo in alto a sinistra. Qualche idea su come risolvere questo problema? Sembra che il problema potrebbe essere che le linee sono troppo vicine tra loro.