Bonne question, il y a quelque temps, j'ai un peu expérimenté cela, mais je ne l'ai pas beaucoup utilisé car il n'est toujours pas à l'épreuve des balles. J'ai divisé la zone de tracé en une grille 32x32 et calculé un `` champ potentiel '' pour la meilleure position d'une étiquette pour chaque ligne selon les règles suivantes:
- l'espace blanc est un bon endroit pour une étiquette
- L'étiquette doit être près de la ligne correspondante
- L'étiquette doit être éloignée des autres lignes
Le code était quelque chose comme ça:
import matplotlib.pyplot as plt
import numpy as np
from scipy import ndimage
def my_legend(axis = None):
if axis == None:
axis = plt.gca()
N = 32
Nlines = len(axis.lines)
print Nlines
xmin, xmax = axis.get_xlim()
ymin, ymax = axis.get_ylim()
# the 'point of presence' matrix
pop = np.zeros((Nlines, N, N), dtype=np.float)
for l in range(Nlines):
# get xy data and scale it to the NxN squares
xy = axis.lines[l].get_xydata()
xy = (xy - [xmin,ymin]) / ([xmax-xmin, ymax-ymin]) * N
xy = xy.astype(np.int32)
# mask stuff outside plot
mask = (xy[:,0] >= 0) & (xy[:,0] < N) & (xy[:,1] >= 0) & (xy[:,1] < N)
xy = xy[mask]
# add to pop
for p in xy:
pop[l][tuple(p)] = 1.0
# find whitespace, nice place for labels
ws = 1.0 - (np.sum(pop, axis=0) > 0) * 1.0
# don't use the borders
ws[:,0] = 0
ws[:,N-1] = 0
ws[0,:] = 0
ws[N-1,:] = 0
# blur the pop's
for l in range(Nlines):
pop[l] = ndimage.gaussian_filter(pop[l], sigma=N/5)
for l in range(Nlines):
# positive weights for current line, negative weight for others....
w = -0.3 * np.ones(Nlines, dtype=np.float)
w[l] = 0.5
# calculate a field
p = ws + np.sum(w[:, np.newaxis, np.newaxis] * pop, axis=0)
plt.figure()
plt.imshow(p, interpolation='nearest')
plt.title(axis.lines[l].get_label())
pos = np.argmax(p) # note, argmax flattens the array first
best_x, best_y = (pos / N, pos % N)
x = xmin + (xmax-xmin) * best_x / N
y = ymin + (ymax-ymin) * best_y / N
axis.text(x, y, axis.lines[l].get_label(),
horizontalalignment='center',
verticalalignment='center')
plt.close('all')
x = np.linspace(0, 1, 101)
y1 = np.sin(x * np.pi / 2)
y2 = np.cos(x * np.pi / 2)
y3 = x * x
plt.plot(x, y1, 'b', label='blue')
plt.plot(x, y2, 'r', label='red')
plt.plot(x, y3, 'g', label='green')
my_legend()
plt.show()
Et l'intrigue qui en résulte:
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.x2 = np.linspace(0,0.5,100)
.print
commande, il s'exécute et crée 4 tracés, dont 3 semblent être du charabia pixélisé (probablement quelque chose à voir avec le 32x32), et le quatrième avec des étiquettes à des endroits impairs.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 lamath
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 valeury
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 laplot
commande (ou plus précisément si l'étiquette contient'_line'
).Arguments de mot-clé passés
labelLines
oulabelLine
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
1
et10
dans le graphique en haut à gauche. Je ne suis même pas sûr que cela puisse être évité.y
position à la place parfois.x
valeurs de -axis sontfloat
sGotchas
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 lax
plage0.5
-1
alors nous ne pourrions pas placer une étiquette à l'emplacement souhaité (qui est un peu moins que0.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 dex
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:
Testez le code pour générer la jolie image ci-dessus:
la source
xvals
, vous voudrez peut-être modifierlabelLines
un peu le code: changez le code sous laif xvals is None:
portée pour créer une liste basée sur d'autres critères. Vous pourriez commencer avecxvals = [(np.min(l.get_xdata())+np.max(l.get_xdata()))/2 for l in lines]
.get_axes()
et.get_axis_bgcolor()
sont obsolètes. Veuillez remplacer par.axes
et.get_facecolor()
, resp.labellines
est que les propriétés s'y rapportentplt.text
ou s'yax.text
appliquent. Cela signifie que vous pouvez définirfontsize
etbbox
paramètres dans lalabelLines()
fonction.La réponse de @Jan Kuiken est certainement bien pensée et approfondie, mais il y a quelques mises en garde:
Une approche beaucoup plus simple consiste à annoter le dernier point de chaque graphique. Le point peut également être encerclé, pour mettre l'accent. Cela peut être accompli avec une ligne supplémentaire:
Une variante serait à utiliser
ax.annotate
.la source
-1
, 2) définir des limites d'axe appropriées pour laisser de l'espace pour les étiquettes.Une approche plus simple comme celle de Ioannis Filippidis:
code python 3 sur sageCell
la source