Можливо, щоб мітки з’являлися під час наведення курсору на точку в matplotlib?


148

Я використовую matplotlib для виготовлення розкиду сюжетів. Кожна точка на ділянці розкидання пов'язана з названим об'єктом. Я хотів би мати змогу побачити ім'я об'єкта, коли наведіть курсор миші на точку на ділянці розкидання, пов'язану з цим об’єктом. Зокрема, було б непогано мати можливість швидко побачити назви пунктів, які є сторонніми. Найближче, що мені вдалося знайти під час пошуку тут, - це команда примітки, але це, здається, створює фіксовану мітку на графіку. На жаль, за кількістю очок, які я маю, сюжет розкидання був би нечитабельним, якби я позначив кожну точку. Хтось знає про спосіб створення міток, які з’являються лише тоді, коли курсор наближається до цієї точки?


2
Люди, які закінчуються тут пошуком, також можуть перевірити цю відповідь , яка є досить складною, але може бути придатною залежно від вимог.
ВажливістьBeingErnest

Відповіді:


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, (див. Малюнок ).
ВажливістьBeingErnest

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

3
@Konstantin Так, це рішення буде працювати при використанні %matplotlib notebookв ноутбуці IPython / Jupyter.
ВажливістьBeingErnest

1
@OriolAbril (та всі інші). Якщо у вас виникла проблема, яка виникла при зміні коду з цієї відповіді, будь ласка, поставте питання про це, посилання на цю відповідь та покажіть код, який ви намагалися. Я не можу дізнатися, що трапилося з кожним із ваших кодів, не бачивши його насправді.
ВажливістьOfBeingErnest

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
Дуже корисно + 1ed. Вам, ймовірно, потрібно 'дебютувати', тому що motion_notify_event повториться для руху всередині області кривої. Проста перевірка того, що об'єкт кривої дорівнює попередній кривій, здається, працює.
bvanlew

5
Хм - це для мене не вийшло поза рамками (так мало що стосується matplotlib...) - це працює з ipython/ jupyterноутбуками? Чи це також працює, коли є кілька субплотів? А як щодо гістограми, а не лінійного графіка?
dwanderson

12
Це надрукує мітку на консолі під час наведення курсору. Як щодо того, щоб ярлик з’явився на малюнку під час наведення курсора? Я зрозумів, що це питання.
Nikana Reklawyks

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

@NikanaReklawyks Я додав відповідь, яка насправді відповідає на питання.
ВажливістьOfBeingErnest

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) з поштовим індексом для 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()

Це описує сюжет прямої лінії, про що питав Сохайб


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 року
Бен Ліндсей

Зразок коду не вдається зі знаком a 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 inline 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
Джерело для цього (без дотику) - mplcursors.readthedocs.io/en/stable/examples/hover.html
Вікторія Стюарт

Я не міг змусити це працювати в лабораторії юпітера. Можливо, це працює в зошиті з юпітером, але не в лабораторії з юпітером?
MD004

3

Якщо ви використовуєте ноутбук з юпітером, моє рішення таке просто:

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

ВИ можете отримати щось подібне введіть тут опис зображення


На сьогодні найкраще рішення, лише кілька рядків коду робить саме те, про що просив ОП
Тім Джонсен

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.