221 votes

Comment ajouter des annotations de survol dans Matplotlib ?

J'utilise matplotlib pour réaliser des diagrammes de dispersion. Chaque point du nuage de points est associé à un objet nommé. J'aimerais pouvoir voir le nom d'un objet lorsque je passe mon curseur sur le point du nuage de points associé à cet objet. En particulier, il serait agréable de pouvoir voir rapidement le nom des points qui sont des valeurs aberrantes. La chose la plus proche que j'ai pu trouver en cherchant ici est la commande annoter, mais elle semble créer une étiquette fixe sur le graphique. Malheureusement, avec le nombre de points que j'ai, le nuage de points serait illisible si j'étiquetais chaque point. Quelqu'un connaît-il un moyen de créer des étiquettes qui n'apparaissent que lorsque le curseur se trouve à proximité du point en question ?

217voto

ImportanceOfBeingErnest Points 119438

Il semble qu'aucune des autres réponses ici ne réponde réellement à la question. Voici donc un code qui utilise un se disperser et montre un annotation sur en vol stationnaire sur les points de dispersion.

import matplotlib.pyplot as plt
import numpy as np; np.random.seed(1)

x = np.random.rand(15)
y = np.random.rand(15)
names = np.array(list("ABCDEFGHIJKLMNO"))
c = np.random.randint(1,5,size=15)

norm = plt.Normalize(1,4)
cmap = plt.cm.RdYlGn

fig,ax = plt.subplots()
sc = plt.scatter(x,y,c=c, s=100, cmap=cmap, norm=norm)

annot = ax.annotate("", xy=(0,0), xytext=(20,20),textcoords="offset points",
                    bbox=dict(boxstyle="round", fc="w"),
                    arrowprops=dict(arrowstyle="->"))
annot.set_visible(False)

def update_annot(ind):

    pos = sc.get_offsets()[ind["ind"][0]]
    annot.xy = pos
    text = "{}, {}".format(" ".join(list(map(str,ind["ind"]))), 
                           " ".join([names[n] for n in ind["ind"]]))
    annot.set_text(text)
    annot.get_bbox_patch().set_facecolor(cmap(norm(c[ind["ind"][0]])))
    annot.get_bbox_patch().set_alpha(0.4)

def hover(event):
    vis = annot.get_visible()
    if event.inaxes == ax:
        cont, ind = sc.contains(event)
        if cont:
            update_annot(ind)
            annot.set_visible(True)
            fig.canvas.draw_idle()
        else:
            if vis:
                annot.set_visible(False)
                fig.canvas.draw_idle()

fig.canvas.mpl_connect("motion_notify_event", hover)

plt.show()

enter image description here

Parce que les gens veulent aussi utiliser cette solution pour une ligne plot au lieu d'une diffusion, ce qui suit serait la même solution pour plot (qui fonctionne de manière légèrement différente).

import matplotlib.pyplot as plt
import numpy as np; np.random.seed(1)

x = np.sort(np.random.rand(15))
y = np.sort(np.random.rand(15))
names = np.array(list("ABCDEFGHIJKLMNO"))

norm = plt.Normalize(1,4)
cmap = plt.cm.RdYlGn

fig,ax = plt.subplots()
line, = plt.plot(x,y, marker="o")

annot = ax.annotate("", xy=(0,0), xytext=(-20,20),textcoords="offset points",
                    bbox=dict(boxstyle="round", fc="w"),
                    arrowprops=dict(arrowstyle="->"))
annot.set_visible(False)

def update_annot(ind):
    x,y = line.get_data()
    annot.xy = (x[ind["ind"][0]], y[ind["ind"][0]])
    text = "{}, {}".format(" ".join(list(map(str,ind["ind"]))), 
                           " ".join([names[n] for n in ind["ind"]]))
    annot.set_text(text)
    annot.get_bbox_patch().set_alpha(0.4)

def hover(event):
    vis = annot.get_visible()
    if event.inaxes == ax:
        cont, ind = line.contains(event)
        if cont:
            update_annot(ind)
            annot.set_visible(True)
            fig.canvas.draw_idle()
        else:
            if vis:
                annot.set_visible(False)
                fig.canvas.draw_idle()

fig.canvas.mpl_connect("motion_notify_event", hover)

plt.show()

Au cas où quelqu'un chercherait une solution pour les lignes dans les axes jumelés, se référer à Comment faire apparaître les étiquettes lors du survol d'un point dans un axe multiple ?

Si quelqu'un cherche une solution pour les diagrammes en bâtons, veuillez vous référer, par exemple, à l'article suivant : "Les diagrammes en bâtons". cette réponse .

72voto

mbernasocchi Points 753

Cette solution fonctionne lors du survol d'une ligne sans qu'il soit nécessaire de la cliquer :

import matplotlib.pyplot as plt

# Need to create as global variable so our callback(on_plot_hover) can access
fig = plt.figure()
plot = fig.add_subplot(111)

# create some curves
for i in range(4):
    # Giving unique ids to each data member
    plot.plot(
        [i*1,i*2,i*3,i*4],
        gid=i)

def on_plot_hover(event):
    # Iterating over each data member plotted
    for curve in plot.get_lines():
        # Searching which data member corresponds to current mouse position
        if curve.contains(event)[0]:
            print("over %s" % curve.get_gid())

fig.canvas.mpl_connect('motion_notify_event', on_plot_hover)           
plt.show()

39voto

cyborg Points 5463

De http://matplotlib.sourceforge.net/examples/event_handling/pick_event_demo.html :

from matplotlib.pyplot import figure, show
import numpy as npy
from numpy.random import rand

if 1: # picking on a scatter plot (matplotlib.collections.RegularPolyCollection)

    x, y, c, s = rand(4, 100)
    def onpick3(event):
        ind = event.ind
        print('onpick3 scatter:', ind, npy.take(x, ind), npy.take(y, ind))

    fig = figure()
    ax1 = fig.add_subplot(111)
    col = ax1.scatter(x, y, 100*s, c, picker=True)
    #fig.savefig('pscoll.eps')
    fig.canvas.mpl_connect('pick_event', onpick3)

show()

29voto

Yuchao Jiang Points 652

Ma solution est aussi simple que :

import matplotlib.pyplot as plt
import mplcursors
plt.plot(...)
mplcursors.cursor(hover=True)
plt.show()

Vous pouvez obtenir quelque chose comme enter image description here

15voto

texasflood Points 1471

Une légère modification d'un exemple fourni dans http://matplotlib.org/users/shell.html :

import numpy as np
import matplotlib.pyplot as plt

fig = plt.figure()
ax = fig.add_subplot(111)
ax.set_title('click on points')

line, = ax.plot(np.random.rand(100), '-', picker=5)  # 5 points tolerance

def onpick(event):
    thisline = event.artist
    xdata = thisline.get_xdata()
    ydata = thisline.get_ydata()
    ind = event.ind
    print('onpick points:', *zip(xdata[ind], ydata[ind]))

fig.canvas.mpl_connect('pick_event', onpick)

plt.show()

Ce tracé est une ligne droite, comme le demandait Sohaib.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X