È possibile far apparire le etichette quando si passa sopra un punto in matplotlib?


148

Sto usando matplotlib per creare grafici a dispersione. Ogni punto sul grafico a dispersione è associato a un oggetto denominato. Vorrei poter vedere il nome di un oggetto quando posiziono il cursore sul punto del grafico a dispersione associato a quell'oggetto. In particolare, sarebbe bello poter vedere rapidamente i nomi dei punti che sono anomali. La cosa più vicina che sono stato in grado di trovare durante la ricerca qui è il comando annotate, ma sembra creare un'etichetta fissa sulla trama. Sfortunatamente, con il numero di punti che ho, il grafico a dispersione sarebbe illeggibile se etichettassi ogni punto. Qualcuno conosce un modo per creare etichette che appaiono solo quando il cursore si sposta in prossimità di quel punto?


2
Le persone che finiscono qui attraverso la ricerca potrebbero anche voler controllare questa risposta , che è piuttosto complessa, ma potrebbe essere adatta a seconda delle esigenze.
ImportanceOfBeingErnest

Risposte:


133

Sembra che nessuna delle altre risposte qui risponda effettivamente alla domanda. Quindi, ecco un codice che utilizza uno scatter e mostra un'annotazione al passaggio del mouse sui punti di scattering.

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

inserisci qui la descrizione dell'immagine

Poiché anche le persone vogliono utilizzare questa soluzione per una linea plotanziché per uno scatter, la seguente sarebbe la stessa soluzione per plot(che funziona in modo leggermente diverso).

Nel caso in cui qualcuno stia cercando una soluzione per le linee negli assi gemelli, fare riferimento a Come far apparire le etichette quando si passa sopra un punto su più assi?

Nel caso in cui qualcuno stia cercando una soluzione per i grafici a barre, fare riferimento ad esempio a questa risposta .


1
Molto bella! Una nota, ho notato che in ind["ind"]realtà è un elenco di indici per tutti i punti sotto il cursore. Ciò significa che il codice sopra ti dà effettivamente accesso a tutti i punti in una data posizione, e non solo al punto più alto. Ad esempio, se hai due punti sovrapposti il ​​testo potrebbe essere letto 1 2, B Co anche 1 2 3, B C Dse avessi 3 punti sovrapposti.
Jvinniec,

@Jvinniec Esatto, c'è deliberatamente uno di questi casi nella trama sopra (il punto verde e rosso a x ~ 0.4). Se lo si passa, verrà visualizzato 0 8, A I, (vedi foto ).
ImportanceOfBeingErnest

@ImportanceOfBeingErnest questo è un ottimo codice, ma quando si passa e si sposta su un punto, chiama fig.canvas.draw_idle()molte volte (cambia anche il cursore su inattivo). L'ho risolto memorizzando l'indice precedente e verificando se ind["ind"][0] == prev_ind. Quindi aggiorna solo se ti sposti da un punto a un altro (aggiorna il testo), smetti di librarti (rendi invisibile l'annotazione) o inizi a librarti (rendi visibile l'annotazione). Con questo cambiamento è molto più pulito ed efficiente.
Sembei Norimaki,

3
@Konstantin Sì, questa soluzione funzionerà quando si utilizza %matplotlib notebookin un notebook IPython / Jupyter.
ImportanceOfBeingErnest

1
@OriolAbril (e tutti gli altri), se si verifica un problema durante la modifica del codice da questa risposta, porre una domanda al riguardo, collegarsi a questa risposta e mostrare il codice che si è tentato. Non ho modo di sapere cosa c'è che non va in ciascuno dei tuoi codici senza vederlo effettivamente.
ImportanceOfBeingErnest il

66

Questa soluzione funziona quando si passa con il mouse su una linea senza la necessità di fare clic su di essa:

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

1
Molto utile + 1ed. Probabilmente dovrai "rimandare" questo perché motion_notify_event si ripeterà per il movimento all'interno dell'area della curva. Il semplice controllo che l'oggetto curva sia uguale alla curva precedente sembra funzionare.
bvanlew,

5
Hmm - questo non ha funzionato per me (quindi poche cose fanno con matplotlib...) - funziona con ipython/ jupyternotebook? Funziona anche in presenza di più sottotrame? Che dire di un grafico a barre anziché di un grafico a linee?
Dwanderson,

12
Questo stampa l'etichetta nella console durante il passaggio del mouse. Che dire di far apparire l'etichetta sull'immagine quando si passa con il mouse? Ho capito che era la domanda.
Nikana Reklawyks,

@mbernasocchi grazie mille, cosa devo inserire nell'argomento gid se voglio vedere un istogramma (diverso per ogni punto nella dispersione) o, meglio ancora, una mappa di calore di un istogramma 2D?
Amitai,

@NikanaReklawyks Ho aggiunto una risposta che risponde effettivamente alla domanda.
ImportanceOfBeingErnest

37

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

Questo fa proprio quello di cui ho bisogno, grazie! Come bonus, al fine di implementarlo, ho riscritto il mio programma in modo che invece di creare due grafici a dispersione separati in colori diversi sulla stessa figura per rappresentare due serie di dati, ho copiato il metodo dell'esempio per assegnare il colore a un punto. Ciò ha reso il mio programma un po 'più semplice da leggere e meno codice. Ora via per trovare una guida per convertire un colore in un numero!
jdmcbr,

1
Questo è per grafici a dispersione. Che dire dei grafici a linee? Ho provato a farlo funzionare su di loro ma non lo fa. C'è una soluzione?
Sohaib,

@Sohaib Vedi la mia risposta
texasflood

Ho una domanda su questo. Quando disperdo i miei punti in questo modo: plt.scatter (X_reduced [y == i, 0], X_reduced [y == i, 1], c = c, label = target_name, picker = True) con una zip per i, c e target_name, allora l'ordine dei miei indici è incasinato? E non riesco più a cercare a quale punto dati appartiene?
Chris,

Questo non sembra funzionare con i notebook jupyter 5 con ipython 5. Esiste un modo semplice per risolverlo? La printdichiarazione dovrebbe usare anche le parentesi per la compatibilità con python 3
nealmcb

14

Una leggera modifica su un esempio fornito in 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()

Questo traccia una trama in linea retta, come chiedeva Sohaib


5

mpld3 risolvilo per me. MODIFICA (CODICE AGGIUNTO):

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

Puoi controllare questo esempio


Includi il codice di esempio e non collegare solo a fonti esterne senza contesto o informazioni. Consulta il Centro assistenza per ulteriori informazioni.
Joseph Farah,

5
sfortunatamente mpld3 non è più attivamente mantenuto a partire da luglio 2017
Ben Lindsay,

L'esempio di codice non riesce con a TypeError: array([1.]) is not JSON serializable.
P-Gn,

@ P-Gn basta seguire il trucco qui stackoverflow.com/questions/48015030/mpld3-with-python-error MPLD3 è una soluzione semplice per questo e una volta che la risposta di cui sopra è seguito, funziona.
Zalakain,

1
@Zalakain Sfortunatamente, mpl3d sembra essere abbandonato .
P-Gn,

5

mplcursors ha lavorato per me. mplcursors fornisce annotazioni cliccabili per matplotlib. È fortemente ispirato a mpldatacursor ( https://github.com/joferkington/mpldatacursor ), con un'API molto semplificata

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

Lo uso da solo, di gran lunga la soluzione più semplice per qualcuno che ha fretta. Ho appena tracciato 70 etichette e matplotlibogni decima riga ha lo stesso colore, un tale dolore. mplcursorsrisolve però.
ajsp

5

Le altre risposte non rispondevano alla mia necessità di mostrare correttamente i suggerimenti in una recente versione di Jupyter inline matplotlib figure. Questo funziona però:

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

Portare a qualcosa di simile alla seguente immagine quando si supera un punto con il mouse: inserisci qui la descrizione dell'immagine



Non riuscivo a farlo funzionare nel laboratorio di Giove. Funziona forse su un quaderno jupyter ma non nel laboratorio jupyter?
MD004,

3

Se usi il notebook jupyter, la mia soluzione è semplice come:

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

Puoi ottenere qualcosa del genere inserisci qui la descrizione dell'immagine


Di gran lunga la soluzione migliore, solo poche righe di codice fanno esattamente ciò che OP ha chiesto
Tim Johnsen,

0

Ho creato un sistema di annotazione multilinea da aggiungere a: https://stackoverflow.com/a/47166787/10302020 . per la versione più aggiornata: https://github.com/AidenBurgess/MultiAnnotationLineGraph

Basta cambiare i dati nella sezione inferiore.

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()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.