Possible to make labels appear when hovering over

2018-12-31 18:03发布

问题:

I am using matplotlib to make scatter plots. Each point on the scatter plot is associated with a named object. I would like to be able to see the name of an object when I hover my cursor over the point on the scatter plot associated with that object. In particular, it would be nice to be able to quickly see the names of the points that are outliers. The closest thing I have been able to find while searching here is the annotate command, but that appears to create a fixed label on the plot. Unfortunately, with the number of points that I have, the scatter plot would be unreadable if I labeled each point. Does anyone know of a way to create labels that only appear when the cursor hovers in the vicinity of that point?

回答1:

It seems none of the other answers here actually answer the question. So here is a code that uses a scatter and shows an annotation upon hovering over the scatter points.

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

Because people suddenly also want to use this solution for a line plot instead of a scatter, the following would be the same solution for plot (which works slightly differently).

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

In case someone is looking for a solution for bar plots, please refer to e.g. this answer.



回答2:

I know it\'s an old question, but I kept on arriving here while looking for a solution to hover (not click on) a line.

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


回答3:

From 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()
  • This recipe draws an annotation on picking a data point: http://scipy-cookbook.readthedocs.io/items/Matplotlib_Interactive_Plotting.html .
  • This recipe draws a tooltip, but it requires wxPython: Point and line tooltips in matplotlib?


回答4:

A slight edit on an example provided 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()

This plots a straight line plot, as Sohaib was asking



回答5:

mpld3 solve it for me. EDIT (CODE ADDED):

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

You can check this example



回答6:

mplcursors worked for me. mplcursors provides clickable annotation for matplotlib. It is heavily inspired from mpldatacursor (https://github.com/joferkington/mpldatacursor), with a much simplified API

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