matplotlib: comment annoter un point sur une flèche scatter placée automatiquement?
si je fais un nuage de points avec matplotlib:
plt.scatter(randn(100),randn(100))
# set x, y lims
plt.xlim([...])
plt.ylim([...])
j'aimerais annoter un point donné (x, y) avec une flèche pointant vers lui et une étiquette. Je sais que cela peut être fait avec annotate , mais j'aimerais que la flèche et son étiquette soient placées "de façon optimale" de sorte que s'il est possible (étant donné les échelles/limites de l'axe actuel) que la flèche et l'étiquette ne se chevauchent pas avec les autres points. par exemple, si vous voulez marquer un point particulier. est-il un moyen pour ce faire? il ne doit pas être parfait, mais juste un placement intelligent de la flèche/étiquette, donné seulement les coordonnées (x,y) du point à étiqueter. grâce.
2 réponses
En Gros, Non, il n'y en a pas.
Les moteurs de mise en pagequi gèrent la mise en place d'étiquettes cartographiques similaires à celle-ci sont étonnamment complexes et au-delà de la portée de matplotlib. (La boîte englobante intersections sont en fait une assez mauvaise façon de décider où placer les étiquettes. À quoi bon écrire une tonne de code pour quelque chose qui ne marchera que dans un cas Sur 1000?)
autre que celui, en raison de la quantité de texte complexe rendant cette matplotlib si (par exemple latex), il est impossible de déterminer l'étendue du texte sans le rendre entièrement d'abord (ce qui est plutôt lent).
Toutefois, dans de nombreux cas, vous trouverez que l'utilisation d'une boîte transparente derrière votre étiquette placée à annoter est une solution de contournement.
E. G.
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(1)
x, y = np.random.random((2,500))
fig, ax = plt.subplots()
ax.plot(x, y, 'bo')
# The key option here is `bbox`. I'm just going a bit crazy with it.
ax.annotate('Something', xy=(x[0], y[0]), xytext=(-20,20),
textcoords='offset points', ha='center', va='bottom',
bbox=dict(boxstyle='round,pad=0.2', fc='yellow', alpha=0.3),
arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0.5',
color='red'))
plt.show()
utiliser adjustText (divulgation complète, je l'ai écrit).
marquons les 10 premiers points. Le seul paramètre que j'ai modifié était d'abaisser la force de répulsion des points, puisqu'il y en a tellement et nous voulons que l'algorithme prenne un peu plus de temps et place les annotations plus soigneusement.
import numpy as np
import matplotlib.pyplot as plt
from adjustText import adjust_text
np.random.seed(1)
x, y = np.random.random((2,500))
fig, ax = plt.subplots()
ax.plot(x, y, 'bo')
ts = []
for i in range(10):
ts.append(plt.text(x[i], y[i], 'Something'+str(i)))
adjust_text(ts, x=x, y=y, force_points=0.1, arrowprops=dict(arrowstyle='->',
color='red'))
plt.show()
Ce n'est pas idéal, mais les points sont très denses ici et parfois il n'y a aucun moyen de placer le texte près de sa cible sans en chevaucher aucune. Mais tout est automatique et facile à utiliser, et ne laisse pas les étiquettes se chevaucher.
PS Il utilise des intersections de bounding box, mais plutôt avec succès je dirais!