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 matplotlibest 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 numpyquelques fonctions de la mathbibliothèque standard .
La description
Le comportement par défaut de la labelLinesfonction est d'espacer les étiquettes uniformément le long de l' xaxe (en les plaçant automatiquement à la bonne valeur ybien 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_linesfonction ne tient pas compte des lignes qui n'ont pas eu d'étiquette affectée dans la plotcommande (ou plus précisément si l'étiquette contient '_line').
Arguments de mot-clé passés labelLinesou labelLinetransmis à l' textappel 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
1et 10dans 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
yposition à la place parfois.
- C'est toujours un processus itératif pour obtenir des annotations au bon endroit
- Cela ne fonctionne que lorsque les
xvaleurs de -axis sont floats
Gotchas
- Par défaut, la
labelLinesfonction 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 xplage 0.5- 1alors 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' xvalsargument; une liste de xvaleurs 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.