Mise à jour: l' utilisateur cphyc a gentiment créé un référentiel Github pour le code de cette réponse (voir ici ) et a regroupé le code dans un package qui peut être installé à l'aide de pip install matplotlib-label-lines
.
Belle photo:
Il matplotlib
est assez facile d' étiqueter les tracés de contour (soit automatiquement, soit en plaçant manuellement des étiquettes avec des clics de souris). Il ne semble pas (encore) y avoir de capacité équivalente pour étiqueter des séries de données de cette façon! Il peut y avoir une raison sémantique pour ne pas inclure cette fonctionnalité qui me manque.
Quoi qu'il en soit, j'ai écrit le module suivant qui prend tout permet l'étiquetage semi-automatique des tracés. Il ne nécessite que numpy
quelques fonctions de la math
bibliothèque standard .
La description
Le comportement par défaut de la labelLines
fonction est d'espacer les étiquettes uniformément le long de l' x
axe (en les plaçant automatiquement à la bonne valeur y
bien sûr). Si vous le souhaitez, vous pouvez simplement passer un tableau des coordonnées x de chacune des étiquettes. Vous pouvez même modifier l'emplacement d'une étiquette (comme indiqué dans le graphique en bas à droite) et espacer le reste de manière égale si vous le souhaitez.
De plus, la label_lines
fonction ne tient pas compte des lignes qui n'ont pas eu d'étiquette affectée dans la plot
commande (ou plus précisément si l'étiquette contient '_line'
).
Arguments de mot-clé passés labelLines
ou labelLine
transmis à l' text
appel de fonction (certains arguments de mot-clé sont définis si le code appelant choisit de ne pas spécifier).
Problèmes
- Les cadres de délimitation des annotations interfèrent parfois de manière indésirable avec d'autres courbes. Comme indiqué par les annotations
1
et 10
dans le graphique en haut à gauche. Je ne suis même pas sûr que cela puisse être évité.
- Ce serait bien de spécifier une
y
position à la place parfois.
- C'est toujours un processus itératif pour obtenir des annotations au bon endroit
- Cela ne fonctionne que lorsque les
x
valeurs de -axis sont float
s
Gotchas
- Par défaut, la
labelLines
fonction suppose que toutes les séries de données couvrent la plage spécifiée par les limites de l'axe. Jetez un œil à la courbe bleue dans le tracé en haut à gauche de la jolie image. S'il n'y avait que des données disponibles pour la x
plage 0.5
- 1
alors nous ne pourrions pas placer une étiquette à l'emplacement souhaité (qui est un peu moins que 0.2
). Voir cette question pour un exemple particulièrement désagréable. À l'heure actuelle, le code n'identifie pas intelligemment ce scénario et ne réorganise pas les étiquettes, mais il existe une solution de contournement raisonnable. La fonction labelLines prend l' xvals
argument; une liste de x
valeurs spécifiées par l'utilisateur au lieu de la distribution linéaire par défaut sur la largeur. Ainsi, l'utilisateur peut décider lequelx
-valeurs à utiliser pour le placement d'étiquette de chaque série de données.
De plus, je pense que c'est la première réponse pour atteindre l' objectif bonus d'aligner les étiquettes avec la courbe sur laquelle elles se trouvent. :)
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)
Testez le code pour générer la jolie image ci-dessus:
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();
cela place l'une des étiquettes dans le coin supérieur gauche. Des idées pour résoudre le problème? Il semble que le problème soit peut-être que les lignes sont trop rapprochées.