将鼠标悬停在matplotlib中的某个点上时可以显示标签吗?


146

我正在使用matplotlib制作散点图。散点图上的每个点都与一个命名对象相关联。当我将光标悬停在与该对象关联的散点图上的点上时,我希望能够看到该对象的名称。尤其是,能够快速查看异常点的名称将是很好的。我在此处搜索时能够找到的最接近的东西是注释命令,但这似乎在绘图上创建了固定标签。不幸的是,根据我拥有的点数,如果我标记每个点,则散点图将无法读取。有谁知道一种创建仅在光标悬停在该点附近时才会显示的标签的方法吗?


2
通过搜索最终到达这里的人们可能还想检查一下这个答案,这很复杂,但根据要求可能合适。
ImportanceOfBeingErnest

Answers:


133

似乎这里没有其他答案可以真正回答这个问题。因此,这是一个使用散点图的代码,并在将鼠标悬停在散点上时显示了注释

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

在此处输入图片说明

由于人们也希望将此解决方案用于行plot而不是分散,因此以下内容将是相同的解决方案plot(其工作原理略有不同)。

如果有人正在寻找双轴上的线的解决方案,请参阅如何将鼠标悬停在多轴上的点上时显示标签?

如果有人正在寻找条形图的解决方案,请参考此答案


1
非常好!需要注意的是,我注意到这ind["ind"]实际上是光标下所有点的索引列表。这意味着上述代码实际上使您可以访问给定位置上的所有点,而不仅仅是最顶部的点。例如,如果您有两个重叠点,则文本可以阅读,1 2, B C或者即使1 2 3, B C D您有3个重叠点也可以阅读。
Jvinniec

@Jvinniec确实,在上面的图中故意存在一个这样的情况(x和0.4处的绿色和红色点)。如果将其悬停,它将显示0 8, A I,(参见图片)。
ImportanceOfBeingErnest

@ImportanceOfBeingErnest这是一个很棒的代码,但是当在一个点上悬停并移动时,它会调用fig.canvas.draw_idle()多次(甚至将光标更改为空闲)。我解决了它存储以前的索引,并检查是否ind["ind"][0] == prev_ind。然后仅在从一个点移动到另一点时更新(更新文本),停止悬停(使注释不可见)或开始悬停(使注释可见)才更新。有了此更改,它的方式将更干净,更高效。
Sembei Norimaki '17

3
@Konstantin是的,%matplotlib notebook在IPython / Jupyter笔记本中使用时,此解决方案将起作用。
ImportanceOfBeingErnest

1
@OriolAbril(和其他所有人),如果在修改此答案中的代码时遇到问题,请提出一个问题,链接到此答案并显示您尝试的代码。在没有实际看到它的情况下,我无法知道每个代码有什么问题。
ImportanceOfBeingErnest

66

当悬停一行而不需要单击它时,此解决方案有效:

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
+1非常有用。您可能需要对此“去抖动”,因为motion_notify_event将在曲线区域内重复运动。简单地检查曲线对象是否等于上一条曲线似乎可行。
bvanlew '16

5
嗯-这对我来说不是开箱即用的(所以...几乎没有作用matplotlib)-可以在ipython/ jupyter笔记本电脑上工作吗?当有多个子图时,它是否也起作用?在条形图而不是折线图上呢?
dwanderson

12
悬停时,这会将标签打印到控制台中。悬停时如何使标签显示在图片上呢?我明白这是个问题。
Nikana Reklawyks,

@mbernasocchi非常感谢,如果我想查看一个直方图(散点中每个点的不同),或者甚至更好地是一个2D直方图的热图,我需要输入gid参数吗?
阿米泰

@NikanaReklawyks我添加了一个实际上可以回答问题的答案。
ImportanceOfBeingErnest

37

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

这正是我需要的,谢谢!另外,为了实现它,我重新编写了程序,以代替在同一图形上创建不同颜色的两个单独的散点图来表示两组数据,而是复制了示例中为点分配颜色的方法。这使我的程序更易于阅读,并且代码更少。现在出发,找到将颜色转换为数字的指南!
jdmcbr 2011年

1
这是散点图。线图呢?我试图使其对他们起作用,但事实并非如此。有解决方法吗?
Sohaib

@Sohaib,请参阅我的回答
texasflood 2015年

我对此有疑问。当我像这样散点图时:plt.scatter(X_reduced [y == i,0],X_reduced [y == i,1],c = c,label = target_name,picker = True)带有zip我,c和target_name,那么我的索引顺序搞乱了吗?而且我无法再查找它属于哪个数据点?
克里斯

对于使用ipython 5的jupyter 5笔记本,这似乎不起作用。有没有简单的方法可以解决此问题?该print语句还应使用括号与python 3兼容
nealmcb

14

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

正如Sohaib所问的那样,这是一条直线图


5

mpld3为我解决。编辑(添加代码):

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

您可以检查这个例子


请提供示例代码,不要仅仅链接没有上下文或信息的外部资源。有关更多信息,请参见帮助中心
约瑟夫·法拉

5
不幸的是,截至2017
Ben Lindsay

代码示例失败,出现TypeError: array([1.]) is not JSON serializable
P-Gn

@ P-Gn只是遵循这里的技巧stackoverflow.com/questions/48015030/mpld3-with-python-error MPLD3是一个简单的解决方案,一旦遵循以上答案,它就会起作用。
Zalakain

1
@Zalakain不幸的是,mpl3d似乎被放弃了
P-Gn

5

放大器为我工作。mplcursors为matplotlib提供可点击的注释。它受到mpldatacursor(https://github.com/joferkington/mpldatacursor)的启发,并具有简化的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()

我自己使用此工具,这是目前急着赶上某人的最简单解决方案。我刚刚绘制了70个标签,matplotlib并使每10行使用相同的颜色,真是太痛苦了。mplcursors整理出来。
ajsp,

5

其他答案未满足我在最新版本的Jupyter内联matplotlib图形中正确显示工具提示的需求。虽然这一作品:

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

当用鼠标越过一点时会导致如下图所示: 在此处输入图片说明


3
来源(未分配
Victoria Stuart,

我无法在Jupyter实验室工作。它也许可以在jupyter笔记本中工作,但不能在jupyter实验室中工作吗?
MD004

3

如果使用jupyter笔记本,我的解决方案很简单:

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

你可以得到类似的东西 在此处输入图片说明


到目前为止,最好的解决办法,代码只有几行不正是当被问及OP为
蒂姆·约翰森

0

我制作了一个多行注释系统以添加到:https : //stackoverflow.com/a/47166787/10302020。最新版本:https : //github.com/AidenBurgess/MultiAnnotationLineGraph

只需在底部更改数据。

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()
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.