Có thể làm cho nhãn xuất hiện khi di chuột qua một điểm trong matplotlib?


146

Tôi đang sử dụng matplotlib để tạo ra các mảnh phân tán. Mỗi điểm trên biểu đồ phân tán được liên kết với một đối tượng được đặt tên. Tôi muốn có thể nhìn thấy tên của một đối tượng khi tôi di con trỏ lên điểm trên biểu đồ phân tán liên kết với đối tượng đó. Đặc biệt, thật tuyệt khi có thể nhanh chóng nhìn thấy tên của các điểm là ngoại lệ. Thứ gần nhất tôi có thể tìm thấy khi tìm kiếm ở đây là lệnh chú thích, nhưng dường như nó tạo ra một nhãn cố định trên cốt truyện. Thật không may, với số điểm mà tôi có, âm mưu phân tán sẽ không thể đọc được nếu tôi dán nhãn cho từng điểm. Có ai biết cách tạo nhãn chỉ xuất hiện khi con trỏ di chuyển trong vùng lân cận của điểm đó không?


2
Những người kết thúc ở đây thông qua tìm kiếm cũng có thể muốn kiểm tra câu trả lời này , khá phức tạp, nhưng có thể phù hợp tùy thuộc vào yêu cầu.
ImportanceOfByingErnest

Câu trả lời:


133

Dường như không có câu trả lời nào khác ở đây thực sự trả lời câu hỏi. Vì vậy, đây là một mã sử dụng một phân tán và hiển thị một chú thích khi di chuột qua các điểm phân tán.

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

nhập mô tả hình ảnh ở đây

Bởi vì mọi người cũng muốn sử dụng giải pháp này cho một dòng plotthay vì phân tán, nên sau đây sẽ là giải pháp tương tự cho plot(hoạt động hơi khác nhau).

Trong trường hợp ai đó đang tìm giải pháp cho các đường trong hai trục, hãy tham khảo Cách làm nhãn xuất hiện khi di chuột qua một điểm trong nhiều trục?

Trong trường hợp ai đó đang tìm kiếm một giải pháp cho các lô bar, vui lòng tham khảo ví dụ câu trả lời này .


1
Rất đẹp! Một lưu ý, tôi nhận thấy rằng đó ind["ind"]thực sự là một danh sách các chỉ mục cho tất cả các điểm dưới con trỏ. Điều này có nghĩa là đoạn mã trên thực sự cho phép bạn truy cập vào tất cả các điểm tại một vị trí nhất định và không chỉ là điểm cao nhất. Chẳng hạn, nếu bạn có hai điểm chồng chéo, văn bản có thể đọc 1 2, B Choặc thậm chí 1 2 3, B C Dnếu bạn có 3 điểm chồng chéo.
Jvinniec

@Jvinniec Chính xác, có một trường hợp như vậy trong âm mưu trên (dấu chấm màu xanh lá cây và màu đỏ ở x ~ 0,4). Nếu bạn di chuột nó sẽ hiển thị 0 8, A I, (xem hình ).
ImportanceOfByingErnest

@ImportanceOfByingErnest đây là một mã tuyệt vời, nhưng khi di chuột và di chuyển trên một điểm, nó gọi fig.canvas.draw_idle()nhiều lần (thậm chí nó còn thay đổi con trỏ thành không hoạt động). Tôi đã giải quyết nó lưu trữ chỉ mục trước đó và kiểm tra nếu ind["ind"][0] == prev_ind. Sau đó chỉ cập nhật nếu bạn di chuyển từ điểm này sang điểm khác (cập nhật văn bản), dừng di chuột (làm cho chú thích trở nên vô hình) hoặc bắt đầu di chuột (hiển thị chú thích). Với sự thay đổi này, đó là cách sạch sẽ và hiệu quả hơn.
Sembei Norimaki

3
@Konstantin Có giải pháp này sẽ hoạt động khi sử dụng %matplotlib notebooktrong sổ ghi chép IPython / Jupyter.
ImportanceOfByingErnest

1
@OriolAbril (và mọi người khác), Nếu bạn gặp vấn đề phát sinh khi sửa đổi mã từ câu trả lời này, vui lòng đặt câu hỏi về nó, liên kết với câu trả lời này và hiển thị mã bạn đã thử. Tôi không có cách nào để biết những gì sai với mỗi mã của bạn mà không thực sự nhìn thấy nó.
ImportanceOfByingErnest

66

Giải pháp này hoạt động khi di chuột một dòng mà không cần phải nhấp vào nó:

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
Rất hữu ích + 1ed. Bạn có thể cần phải 'gỡ lỗi' điều này bởi vì Motion_notify_event sẽ lặp lại cho chuyển động bên trong khu vực đường cong. Đơn giản chỉ cần kiểm tra xem đối tượng đường cong có bằng đường cong trước đó dường như hoạt động không.
bvanlew

5
Hmm - cái này không phù hợp với tôi (rất ít việc phải làm với matplotlib...) - cái này có hoạt động với ipython/ jupyternotebook không? Nó cũng hoạt động khi có nhiều subplots? Điều gì về một biểu đồ thanh chứ không phải là một biểu đồ đường?
lùn

12
Điều này in nhãn vào bảng điều khiển khi di chuột. Điều gì về việc làm cho nhãn xuất hiện trên hình ảnh khi di chuột? Tôi hiểu rằng đó là câu hỏi.
Nikana Reklawyks 6/2/2017

@mbernasocchi cảm ơn rất nhiều, tôi cần phải cung cấp gì trong đối số gid nếu tôi muốn xem biểu đồ (một điểm khác nhau cho mỗi điểm trong phân tán) hoặc, thậm chí tốt hơn, bản đồ nhiệt của biểu đồ 2D?
Amitai

@NikanaReklawyks Tôi đã thêm một câu trả lời thực sự trả lời câu hỏi.
ImportanceOfByingErnest

37

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

Đây không chỉ là những gì tôi cần, cảm ơn bạn! Như một phần thưởng, để thực hiện nó, tôi đã viết lại chương trình của mình để thay vì tạo hai ô phân tán riêng biệt theo các màu khác nhau trên cùng một hình để biểu thị hai bộ dữ liệu, tôi đã sao chép phương thức của ví dụ để gán màu cho một điểm. Điều này làm cho chương trình của tôi đơn giản hơn một chút để đọc và ít mã hơn. Bây giờ tắt để tìm một hướng dẫn để chuyển đổi một màu thành một số!
jdmcbr

1
Điều này là cho các mảnh phân tán. Điều gì về lô đất? Tôi đã cố gắng làm cho nó hoạt động trên chúng nhưng không được. Có một cách giải quyết?
Sohaib

@Sohaib Xem câu trả lời của tôi
texasflood

Tôi có một câu hỏi về điều này. Khi tôi phân tán biểu đồ các điểm của mình như thế này: plt.scatter (X_reduces [y == i, 0], X_reduces [y == i, 1], c = c, nhãn = target_name, picker = True) với một zip cho i, c và target_name, sau đó thứ tự các chỉ mục của tôi có bị rối không? Và tôi không thể tra cứu thêm nó thuộc về datapoint nào?
Chris

Điều này dường như không hoạt động đối với máy tính xách tay jupyter 5 với ipython 5. Có cách nào dễ dàng để khắc phục điều đó không? Các printtuyên bố cũng nên sử dụng dấu ngoặc để tương thích với python 3
nealmcb

14

Một chỉnh sửa nhỏ trên một ví dụ được cung cấp trong 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()

Điều này vẽ một âm mưu đường thẳng, như Sohaib đang hỏi


5

mpld3 giải quyết nó cho tôi. EDIT (MÃ THÊM):

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

Bạn có thể kiểm tra ví dụ này


Vui lòng bao gồm mã mẫu và không chỉ liên kết với các nguồn bên ngoài mà không có ngữ cảnh hoặc thông tin. Xem Trung tâm trợ giúp để biết thêm thông tin.
Joseph Farah

5
thật không may mpld3 không còn được duy trì tích cực kể từ tháng 7 năm 2017
Ben Lindsay

Mã mẫu thất bại với a TypeError: array([1.]) is not JSON serializable.
P-Gn

@ P-Gn chỉ cần làm theo thủ thuật ở đây stackoverflow.com/questions/48015030/mpld3-with-python-error MPLD3 là một giải pháp đơn giản cho việc này và một khi câu trả lời trên được thực hiện, nó sẽ hoạt động.
Zalakain

1
@Zalakain Thật không may, mpl3d dường như bị bỏ rơi .
P-Gn

5

mplcursors làm việc cho tôi. mplcursors cung cấp chú thích có thể nhấp cho matplotlib. Nó được truyền cảm hứng rất nhiều từ mpldatacthon ( https://github.com/joferkington/mpldatacthon ), với API đơn giản hóa hơn nhiều

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

Tôi sử dụng nó cho bản thân mình, cho đến nay là giải pháp dễ nhất cho một người vội vàng. Tôi chỉ vẽ 70 nhãn và matplotliblàm cho mỗi dòng thứ 10 cùng màu, thật là một nỗi đau. mplcursorsloại nó ra mặc dù.
ajsp

5

Các câu trả lời khác không giải quyết được nhu cầu của tôi về hiển thị các chú giải công cụ trong một phiên bản gần đây của con số matplotlib nội tuyến Jupyter. Cái này hoạt động mặc dù:

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

Dẫn đến một cái gì đó giống như hình ảnh sau đây khi đi qua một điểm bằng chuột: nhập mô tả hình ảnh ở đây


3
Nguồn cho việc này (không được phân bổ) là mplcursors.readthedocs.io/en/urdy/examples/hover.html
Victoria Stuart

Tôi không thể làm việc này trong phòng thí nghiệm jupyter. Có lẽ nó hoạt động trong một máy tính xách tay jupyter nhưng không phải trong phòng thí nghiệm jupyter?
MD004

3

Nếu bạn sử dụng máy tính xách tay jupyter, giải pháp của tôi đơn giản như:

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

YOu có thể nhận được một cái gì đó như nhập mô tả hình ảnh ở đây


Cho đến nay, giải pháp tốt nhất, chỉ có một vài dòng mã thực hiện chính xác những gì OP yêu cầu
Tim Johnsen

0

Tôi đã tạo một hệ thống chú thích nhiều dòng để thêm vào: https://stackoverflow.com/a/47166787/10302020 . cho phiên bản cập nhật nhất: https://github.com/AidenBurgess/MultiAnnotationLineGraph

Đơn giản chỉ cần thay đổi dữ liệu trong phần dưới cùng.

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()
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.