Können Beschriftungen angezeigt werden, wenn Sie mit der Maus über einen Punkt in matplotlib fahren?

146

Ich benutze Matplotlib, um Streudiagramme zu erstellen. Jeder Punkt im Streudiagramm ist einem benannten Objekt zugeordnet. Ich möchte den Namen eines Objekts sehen können, wenn ich den Mauszeiger über den Punkt auf dem Streudiagramm bewege, der diesem Objekt zugeordnet ist. Insbesondere wäre es schön, schnell die Namen der Punkte sehen zu können, die Ausreißer sind. Das nächste, was ich bei der Suche hier finden konnte, ist der Befehl annotate, aber das scheint eine feste Beschriftung auf dem Plot zu erzeugen. Leider wäre bei der Anzahl der Punkte, die ich habe, das Streudiagramm nicht lesbar, wenn ich jeden Punkt beschriften würde. Kennt jemand eine Möglichkeit, Beschriftungen zu erstellen, die nur angezeigt werden, wenn sich der Cursor in der Nähe dieses Punkts befindet?

jdmcbr
quelle
2
Personen, die über die Suche hierher kommen, möchten möglicherweise auch diese Antwort überprüfen , die recht komplex ist, aber je nach den Anforderungen geeignet sein kann.
ImportanceOfBeingErnest

Antworten:

133

Es scheint, dass keine der anderen Antworten hier tatsächlich die Frage beantwortet. Hier ist also ein Code, der eine Streuung verwendet und beim Bewegen des Mauszeigers über die Streupunkte eine Anmerkung anzeigt .

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

Geben Sie hier die Bildbeschreibung ein

Da diese Lösung auch für eine Linie plotanstelle einer Streuung verwendet werden soll, ist die folgende Lösung dieselbe plot( für die etwas anders funktioniert).

Wenn jemand nach einer Lösung für Linien in zwei Achsen sucht, lesen Sie Informationen zum Anzeigen von Beschriftungen, wenn Sie mit der Maus über einen Punkt in mehreren Achsen fahren.

Falls jemand nach einer Lösung für Balkendiagramme sucht, lesen Sie bitte diese Antwort .

Wichtigkeit von BeEnest
quelle
1
Sehr schön! Eine Anmerkung, ich bemerkte, dass ind["ind"]es sich tatsächlich um eine Liste von Indizes für alle Punkte unter dem Cursor handelt. Dies bedeutet, dass Sie mit dem obigen Code tatsächlich auf alle Punkte an einer bestimmten Position zugreifen können und nicht nur auf den obersten Punkt. Wenn Sie beispielsweise zwei überlappende Punkte haben, kann der Text gelesen werden, 1 2, B Coder selbst 1 2 3, B C Dwenn Sie drei überlappende Punkte haben.
Jvinniec
@Jvinniec Genau, es gibt absichtlich einen solchen Fall in der obigen Darstellung (der grüne und rote Punkt bei x ~ 0,4). Wenn Sie den Mauszeiger darüber halten, wird es angezeigt 0 8, A I(siehe Bild ).
ImportanceOfBeingErnest
@ImportanceOfBeingErnest Dies ist ein großartiger Code, aber wenn Sie mit der Maus über einen Punkt schweben und ihn bewegen, wird er fig.canvas.draw_idle()viele Male aufgerufen (er ändert sogar den Cursor in den Leerlauf). Ich habe es gelöst, indem ich den vorherigen Index gespeichert und überprüft habe, ob ind["ind"][0] == prev_ind. Aktualisieren Sie dann nur, wenn Sie von einem Punkt zum anderen wechseln (Text aktualisieren), das Schweben beenden (die Anmerkung unsichtbar machen) oder mit dem Schweben beginnen (Anmerkung sichtbar machen). Mit dieser Änderung ist es viel sauberer und effizienter.
Sembei Norimaki
3
@Konstantin Ja, diese Lösung funktioniert bei Verwendung %matplotlib notebookin einem IPython / Jupyter-Notebook.
ImportanceOfBeingErnest
1
@OriolAbril (und alle anderen), Wenn Sie ein Problem haben, das beim Ändern des Codes aus dieser Antwort aufgetreten ist, stellen Sie bitte eine Frage dazu, verlinken Sie auf diese Antwort und zeigen Sie den Code an, den Sie versucht haben. Ich kann nicht wissen, was mit jedem Ihrer Codes nicht stimmt, ohne ihn tatsächlich zu sehen.
ImportanceOfBeingErnest
66

Diese Lösung funktioniert, wenn Sie den Mauszeiger über eine Linie bewegen, ohne darauf klicken zu müssen:

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()
mbernasocchi
quelle
1
Sehr nützlich + 1ed. Sie müssen dies wahrscheinlich "entprellen", da sich das motion_notify_event für Bewegungen innerhalb des Kurvenbereichs wiederholt. Es scheint zu funktionieren, einfach zu überprüfen, ob das Kurvenobjekt der vorherigen Kurve entspricht.
Bvanlew
5
Hmm - das hat bei mir nicht sofort funktioniert (so wenige Dinge haben mit matplotlib... zu tun ) - funktioniert das mit ipython/ jupyternotebooks? Funktioniert es auch, wenn mehrere Nebenhandlungen vorhanden sind? Was ist mit einem Balkendiagramm und nicht mit einem Liniendiagramm?
Dwanderson
12
Dadurch wird das Etikett beim Bewegen des Mauszeigers in die Konsole gedruckt. Wie wäre es , wenn das Etikett beim Schweben auf dem Bild angezeigt wird? Ich habe das als die Frage verstanden.
Nikana Reklawyks
@mbernasocchi Vielen Dank, was brauche ich, um das gid-Argument einzugeben, wenn ich ein Histogramm (ein anderes für jeden Punkt in der Streuung) oder, noch besser, eine Wärmekarte eines 2D-Histogramms sehen möchte?
Amitai
@NikanaReklawyks Ich habe eine Antwort hinzugefügt , die die Frage tatsächlich beantwortet.
ImportanceOfBeingErnest
37

Von 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()
Cyborg
quelle
Das macht genau das, was ich brauche, danke! Als Bonus habe ich mein Programm neu geschrieben, um es zu implementieren. Anstatt zwei separate Streudiagramme in verschiedenen Farben auf derselben Figur zu erstellen, um zwei Datensätze darzustellen, habe ich die Methode des Beispiels zum Zuweisen von Farbe zu einem Punkt kopiert. Dies machte mein Programm etwas einfacher zu lesen und weniger Code. Jetzt geht es los, um eine Anleitung zum Konvertieren einer Farbe in eine Zahl zu finden!
jdmcbr
1
Dies gilt für Streudiagramme. Was ist mit Liniendiagrammen? Ich habe versucht, es auf sie zu bringen, aber es funktioniert nicht. Gibt es eine Problemumgehung?
Sohaib
@ Sohaib Siehe meine Antwort
Texasflood
Ich habe eine Frage dazu. Wenn ich meine Punkte wie folgt streue: plt.scatter (X_reduced [y == i, 0], X_reduced [y == i, 1], c = c, label = target_name, picker = True) mit einem Reißverschluss für i, c und target_name, ist dann die Reihenfolge meiner Indizes durcheinander? Und ich kann nicht mehr nachsehen, zu welchem ​​Datenpunkt es gehört?
Chris
Dies scheint bei Jupyter 5-Notebooks mit ipython 5 nicht zu funktionieren. Gibt es eine einfache Möglichkeit, dies zu beheben? Die printAnweisung sollte auch Parens für die Kompatibilität mit Python 3
Nealmcb
14

Eine leichte Bearbeitung an einem Beispiel in bereitgestellt 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()

Dies zeichnet eine gerade Linie, wie Sohaib fragte

Texasflood
quelle
5

mpld3 löse es für mich. BEARBEITEN (CODE HINZUGEFÜGT):

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

Sie können dieses Beispiel überprüfen

julianisch
quelle
Bitte geben Sie Beispielcode an und verlinken Sie nicht nur auf externe Quellen ohne Kontext oder Informationen. Weitere Informationen finden Sie in der Hilfe .
Joseph Farah
5
Leider wird mpld3 ab Juli 2017 nicht mehr aktiv gewartet
Ben Lindsay
Codebeispiel schlägt mit a fehl TypeError: array([1.]) is not JSON serializable.
P-Gn
@ P-Gn folge einfach dem Trick hier stackoverflow.com/questions/48015030/mpld3-with-python-error MPLD3 ist eine einfache Lösung dafür und sobald die obige Antwort befolgt wird, funktioniert es.
Zalakain
1
@Zalakain Leider scheint mpl3d verlassen zu sein .
P-Gn
5

mplcursors hat für mich gearbeitet. mplcursors bietet anklickbare Anmerkungen für matplotlib. Es ist stark von mpldatacursor ( https://github.com/joferkington/mpldatacursor ) mit einer stark vereinfachten API inspiriert

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()
Enayat
quelle
Ich benutze dies selbst, bei weitem die einfachste Lösung für jemanden, der es eilig hat. Ich habe gerade 70 Etiketten gezeichnet und matplotlibjede 10. Zeile hat die gleiche Farbe, so ein Schmerz. mplcursorssortiert es aber aus.
Ajsp
5

Die anderen Antworten entsprachen nicht meiner Notwendigkeit, QuickInfos in einer neueren Version der Jupyter-Inline-Matplotlib-Abbildung korrekt anzuzeigen. Dieser funktioniert aber:

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

Wenn Sie mit der Maus über einen Punkt fahren, führt dies zu folgendem Bild: Geben Sie hier die Bildbeschreibung ein

Farzad Schwindel
quelle
3
Die Quelle dafür (nicht zugeordnet) ist mplcursors.readthedocs.io/en/stable/examples/hover.html
Victoria Stuart
Ich konnte das im Jupyter-Labor nicht zum Laufen bringen. Funktioniert es vielleicht in einem Jupyter-Notebook, aber nicht in einem Jupyter-Labor?
MD004
3

Wenn Sie ein Jupyter-Notebook verwenden, ist meine Lösung so einfach wie:

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

Sie können so etwas bekommen Geben Sie hier die Bildbeschreibung ein

Yuchao Jiang
quelle
Bei weitem die beste Lösung, nur wenige Codezeilen machen genau das, wonach OP gefragt hat
Tim Johnsen
0

Ich habe ein mehrzeiliges Anmerkungssystem erstellt, das hinzugefügt werden kann: https://stackoverflow.com/a/47166787/10302020 . Für die aktuellste Version: https://github.com/AidenBurgess/MultiAnnotationLineGraph

Ändern Sie einfach die Daten im unteren Bereich.

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