web-dev-qa-db-fra.com

Possible de faire apparaître des étiquettes lorsque vous survolez un point de matplotlib?

J'utilise matplotlib pour créer 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 survole le point situé sur le diagramme de dispersion associé à cet objet. En particulier, il serait bien de pouvoir voir rapidement les noms des points aberrants. La chose la plus proche que j'ai pu trouver lors de la recherche ici est la commande annoter, mais cela semble créer une étiquette fixe sur le tracé. Malheureusement, avec le nombre de points que j'ai, le diagramme de dispersion serait illisible si je marquais chaque point. Est-ce que quelqu'un connaît un moyen de créer des étiquettes qui n'apparaissent que lorsque le curseur se place au voisinage de ce point?

106
jdmcbr

Il semble qu'aucune des autres réponses ne réponde réellement à la question. Voici donc un code qui utilise une dispersion et affiche une annotation sur survolant 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 dispersion, la solution suivante serait la même solution pour plot (qui fonctionne légèrement différemment).

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()

Si quelqu'un recherche une solution pour les lignes sur deux axes, reportez-vous à Comment faire apparaître des étiquettes lorsque vous survolez un point sur plusieurs axes?

Si quelqu'un recherche une solution pour les parcelles à barres, veuillez vous reporter à la page cette réponse .

Cette solution fonctionne lorsque vous survolez une ligne sans avoir à cliquer dessus:

import matplotlib.pyplot as plt

fig = plt.figure()
plot = fig.add_subplot(111)

# create some curves
for i in range(4):
    plot.plot(
        [i*1,i*2,i*3,i*4],
        gid=i)

def on_plot_hover(event):
    for curve in plot.get_lines():
        if curve.contains(event)[0]:
            print "over %s" % curve.get_gid()

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

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()
34
cyborg

Une légère modification sur 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()

Ceci trace un tracé en ligne droite, comme le demandait Sohaib

13
texasflood

mpld3 le résoudre pour moi. MODIFIER (CODE AJOUTÉ):

import matplotlib.pyplot as plt
import numpy as np
import mpld3

fig, ax = plt.subplots(subplot_kw=dict(axisbg='#EEEEEE'))
N = 100

scatter = ax.scatter(np.random.normal(size=N),
                 np.random.normal(size=N),
                 c=np.random.random(size=N),
                 s=1000 * np.random.random(size=N),
                 alpha=0.3,
                 cmap=plt.cm.jet)
ax.grid(color='white', linestyle='solid')

ax.set_title("Scatter Plot (with tooltips!)", size=20)

labels = ['point {0}'.format(i + 1) for i in range(N)]
tooltip = mpld3.plugins.PointLabelTooltip(scatter, labels=labels)
mpld3.plugins.connect(fig, tooltip)

mpld3.show()

Vous pouvez vérifier this exemple

5
Julian

mplcursors a travaillé pour moi. mplcursors fournit des annotations cliquables pour matplotlib. Il est fortement inspiré de mpldatacursor ( https://github.com/joferkington/mpldatacursor ), avec une API très simplifiée.

import matplotlib.pyplot as plt
import numpy as np
import mplcursors

data = np.outer(range(10), range(1, 5))

fig, ax = plt.subplots()
lines = ax.plot(data)
ax.set_title("Click somewhere on a line.\nRight-click to deselect.\n"
             "Annotations can be dragged.")

mplcursors.cursor(lines) # or just mplcursors.cursor()

plt.show()
2
Enayat

Les autres réponses ne répondaient pas à mon besoin d'afficher correctement les info-bulles dans une version récente de la figure matplotlib inline de Jupyter. Celui-ci fonctionne bien:

import matplotlib.pyplot as plt
import numpy as np
import mplcursors
np.random.seed(42)

fig, ax = plt.subplots()
ax.scatter(*np.random.random((2, 26)))
ax.set_title("Mouse over a point")
crs = mplcursors.cursor(ax,hover=True)

crs.connect("add", lambda sel: sel.annotation.set_text(
    'Point {},{}'.format(sel.target[0], sel.target[1])))
plt.show()

Cela conduit à quelque chose comme l’image suivante lorsque vous passez sur un point avec la souris: enter image description here

2
Farzad Vertigo

Vous cherchez quelque chose comme ça.

URL: https://matplotlib.org/2.0.1/examples/pylab_examples/cursor_demo.html

Essayez simplement de copier-coller et voyez si cela répond à vos besoins.

J'ai beaucoup cherché et la plupart des réponses n'imprimaient les points qu'après le survol ou les étiquettes n'étaient tirées qu'après un clic de souris. Cette (solution dans le lien ci-dessus) combine les avantages des deux solutions.

1
Nishant Ingle

J'ai créé un système d'annotation multiligne à ajouter à: https://stackoverflow.com/a/47166787/1030202 . pour la version la plus récente: https://github.com/AidenBurgess/MultiAnnotationLineGraph

Modifiez simplement les données dans la section inférieure.

import matplotlib.pyplot as plt


def update_annot(ind, line, annot, ydata):
    x, y = line.get_data()
    annot.xy = (x[ind["ind"][0]], y[ind["ind"][0]])
    # Get x and y values, then format them to be displayed
    x_values = " ".join(list(map(str, ind["ind"])))
    y_values = " ".join(str(ydata[n]) for n in ind["ind"])
    text = "{}, {}".format(x_values, y_values)
    annot.set_text(text)
    annot.get_bbox_patch().set_alpha(0.4)


def hover(event, line_info):
    line, annot, ydata = line_info
    vis = annot.get_visible()
    if event.inaxes == ax:
        # Draw annotations if cursor in right position
        cont, ind = line.contains(event)
        if cont:
            update_annot(ind, line, annot, ydata)
            annot.set_visible(True)
            fig.canvas.draw_idle()
        else:
            # Don't draw annotations
            if vis:
                annot.set_visible(False)
                fig.canvas.draw_idle()


def plot_line(x, y):
    line, = plt.plot(x, y, marker="o")
    # Annotation style may be changed here
    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)
    line_info = [line, annot, y]
    fig.canvas.mpl_connect("motion_notify_event",
                           lambda event: hover(event, line_info))


# Your data values to plot
x1 = range(21)
y1 = range(0, 21)
x2 = range(21)
y2 = range(0, 42, 2)
# Plot line graphs
fig, ax = plt.subplots()
plot_line(x1, y1)
plot_line(x2, y2)
plt.show()
0
Bobs