Можно ли отображать метки при наведении курсора на точку в matplotlib?


162

Я использую matplotlib для построения графиков разброса. Каждая точка на диаграмме рассеивания связана с именованным объектом. Я хотел бы видеть имя объекта при наведении курсора на точку на графике рассеяния, связанную с этим объектом. В частности, было бы неплохо иметь возможность быстро видеть имена точек, которые являются выбросами. Самое близкое, что мне удалось найти при поиске здесь, - это команда annotate, но она, похоже, создает фиксированную метку на графике. К сожалению, с таким количеством точек диаграмма разброса была бы нечитабельной, если бы я пометил каждую точку. Кто-нибудь знает, как создавать метки, которые появляются только при наведении курсора в окрестности этой точки?


2
Люди, попавшие сюда через поиск, также могут захотеть проверить этот ответ , который довольно сложен, но может подойти в зависимости от требований.
ImportanceOfBeingErnest

Ответы:


143

Кажется, что ни один из других ответов здесь на самом деле не отвечает на вопрос. Итак, вот код, который использует разброс и показывает аннотацию при наведении курсора на точки разброса.

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

1
@ImportanceOfBeingErnest - это отличный код, но при наведении и перемещении по точке он вызывает fig.canvas.draw_idle()много раз (он даже меняет курсор на бездействующий). Я решил, что сохранил предыдущий индекс и проверил, если ind["ind"][0] == prev_ind. Затем обновляйте только в том случае, если вы перемещаетесь из одной точки в другую (обновляете текст), прекращаете наведение (сделайте аннотацию невидимой) или начнете зависать (сделайте аннотацию видимой). С этим изменением он стал более чистым и эффективным.
Sembei Norimaki

4
@Konstantin Да, это решение будет работать при использовании %matplotlib notebookв ноутбуке IPython / Jupyter.
ImportanceOfBeingErnest

1
@OriolAbril (и все остальные), если у вас возникла проблема, возникшая при изменении кода из этого ответа, задайте вопрос об этом, укажите ссылку на этот ответ и покажите код, который вы пытались выполнить. У меня нет возможности узнать, что не так с каждым из ваших кодов, не видя этого.
ImportanceOfBeingErnest

67

Это решение работает при наведении курсора на строку без необходимости щелкать по ней:

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 05

6
Хм - это не сработало для меня из коробки (так что мало что можно сделать с matplotlib...) - это работает с ipython/ jupyternotebooks? Это также работает, когда есть несколько подзаговоров? А что насчет гистограммы, а не линейного графика?
dwanderson

13
Это печатает этикетку в консоли при наведении курсора. Как насчет того, чтобы метка появлялась на картинке при наведении курсора? Я понял, что в этом вопрос.
Никана Реклавикс

@mbernasocchi, большое спасибо, что мне нужно в аргументе gid, если я хочу увидеть гистограмму (отдельную для каждой точки разброса) или, что еще лучше, тепловую карту 2D-гистограммы?
Amitai

@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

1
Это для точечных диаграмм. А как насчет линейных графиков? Я пытался заставить его работать на них, но это не работает. Есть ли обходной путь?
Sohaib

@Sohaib См. Мой ответ
texasflood

У меня есть вопрос по этому поводу. Когда я разбросаю свои точки следующим образом: plt.scatter (X_reduced [y == i, 0], X_reduced [y == i, 1], c = c, label = target_name, picker = True) с zip для i, c и target_name, тогда порядок моих индексов испорчен? И я больше не могу посмотреть, к какой точке данных он принадлежит?
Крис

Похоже, это не работает для ноутбуков jupyter 5 с ipython 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()

Это строит прямолинейный график, как просил Сохаиб.


6

Другие ответы не касались моей потребности в правильном отображении всплывающих подсказок в последней версии встроенного рисунка matplotlib Jupyter. Хотя это работает:

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
Источник для этого (без указания
Виктория Стюарт,

1
Я не мог заставить это работать в лаборатории jupyter. Возможно, это работает в ноутбуке jupyter, но не в лаборатории jupyter?
MD004

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
к сожалению, mpld3 больше не поддерживается активно с июля 2017
Бен Линдси

Пример кода не работает с файлом TypeError: array([1.]) is not JSON serializable.
P-Gn

@ P-Gn просто выполните трюк здесь stackoverflow.com/questions/48015030/mpld3-with-python-error MPLD3 - простое решение для этого, и как только ответ выше будет соблюден, он работает.
Залакаин

1
@Zalakain К сожалению, от mpl3d, похоже , отказались .
P-Gn

5

mplcursors у меня работали. 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, мое решение очень простое:

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

Вы можете получить что-то вроде введите описание изображения здесь


1
Безусловно, лучшее решение, всего несколько строк кода делают именно то, что просил 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()
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.