Kemungkinan untuk membuat label muncul ketika mengarahkan kursor ke suatu titik di matplotlib?

146

Saya menggunakan matplotlib untuk membuat plot pencar. Setiap titik pada plot pencar dikaitkan dengan objek bernama. Saya ingin dapat melihat nama objek ketika saya mengarahkan kursor saya ke titik di sebar plot yang terkait dengan objek itu. Secara khusus, alangkah baiknya untuk dapat dengan cepat melihat nama-nama poin yang outlier. Hal terdekat yang saya dapat temukan saat mencari di sini adalah perintah anotasi, tetapi yang muncul untuk membuat label tetap pada plot. Sayangnya, dengan jumlah poin yang saya miliki, plot pencar tidak akan terbaca jika saya memberi label pada setiap poin. Adakah yang tahu cara membuat label yang hanya muncul ketika kursor berada di sekitar titik itu?

jdmcbr
sumber
2
Orang-orang yang berakhir di sini melalui pencarian mungkin juga ingin memeriksa jawaban ini , yang agak rumit, tetapi mungkin cocok tergantung pada persyaratan.
ImportanceOfBeingErnest

Jawaban:

133

Tampaknya tidak ada jawaban lain di sini yang benar-benar menjawab pertanyaan itu. Jadi di sini adalah kode yang menggunakan pencar dan menunjukkan anotasi saat melayang di atas titik pencar.

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

masukkan deskripsi gambar di sini

Karena orang-orang juga ingin menggunakan solusi ini untuk sebuah garis, plotbukannya sebar, berikut ini akan menjadi solusi yang sama untuk plot(yang bekerja sedikit berbeda).

Jika seseorang mencari solusi untuk garis dalam sumbu kembar, lihat Cara membuat label muncul ketika mengarahkan kursor ke suatu titik dalam beberapa sumbu?

Jika seseorang mencari solusi untuk plot bar, silakan lihat misalnya jawaban ini .

ImportanceOfBeingErnest
sumber
1
Sangat bagus! Satu catatan, saya perhatikan bahwa ind["ind"]sebenarnya adalah daftar indeks untuk semua poin di bawah kursor. Ini berarti bahwa kode di atas benar-benar memberi Anda akses ke semua titik pada posisi tertentu, dan bukan hanya titik paling atas. Misalnya, jika Anda memiliki dua titik yang tumpang tindih, teks dapat dibaca 1 2, B Catau bahkan 1 2 3, B C Djika Anda memiliki 3 titik yang tumpang tindih.
Jvinniec
@Jvinniec Persis, ada satu kasus seperti itu di plot di atas (titik hijau dan merah di x ~ 0,4). Jika Anda mengarahkannya, ia akan ditampilkan 0 8, A I, (lihat gambar ).
ImportanceOfBeingErnest
@ImportanceOfBeingErnest ini adalah kode yang bagus, tetapi ketika melayang dan bergerak pada suatu titik ia memanggil fig.canvas.draw_idle()berkali-kali (bahkan mengubah kursor ke siaga). Saya menyelesaikannya dengan menyimpan indeks sebelumnya dan memeriksa apakah ind["ind"][0] == prev_ind. Maka hanya perbarui jika Anda berpindah dari satu titik ke titik lainnya (perbarui teks), berhenti melayang (membuat anotasi tidak terlihat) atau mulai melayang (membuat anotasi terlihat). Dengan perubahan ini jauh lebih bersih dan efisien.
Sembei Norimaki
3
@Konstantin Ya solusi ini akan berfungsi saat digunakan %matplotlib notebookdi notebook IPython / Jupyter.
ImportanceOfBeingErnest
1
@OriolAbril (dan semua orang), Jika Anda memiliki masalah yang muncul ketika memodifikasi kode dari jawaban ini, silakan ajukan pertanyaan tentang itu, tautkan ke jawaban ini dan tunjukkan kode yang telah Anda coba. Saya tidak punya cara untuk mengetahui apa yang salah dengan masing-masing kode Anda tanpa benar-benar melihatnya.
ImportanceOfBeingErnest
66

Solusi ini berfungsi saat mengarahkan garis tanpa perlu mengkliknya:

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()
mbernasocchi
sumber
1
Sangat bermanfaat +1. Anda mungkin perlu 'debounce' ini karena motion_notify_event akan mengulangi untuk gerakan di dalam area kurva. Cukup memeriksa bahwa objek kurva sama dengan kurva sebelumnya tampaknya berfungsi.
bvanlew
5
Hmm - ini tidak berhasil bagi saya (begitu sedikit hal yang berhubungan dengan matplotlib...) - apakah ini bekerja dengan ipython/ jupyternotebook? Apakah ini juga berfungsi ketika ada banyak subplot? Bagaimana dengan diagram batang daripada grafik garis?
dwanderson
12
Ini mencetak label ke konsol ketika melayang. Bagaimana dengan membuat label muncul pada gambar saat melayang? Saya mengerti itu menjadi pertanyaan.
Nikana Reklawyks
@mbernasocchi terima kasih banyak, apa yang harus saya beri makan dalam argumen gid jika saya ingin melihat histogram (yang berbeda untuk setiap titik di sebar) atau, bahkan lebih baik, peta panas histogram 2D?
Amitai
@NikanaReklawyks Saya menambahkan jawaban yang sebenarnya menjawab pertanyaan.
ImportanceOfBeingErnest
37

Dari 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()
cyborg
sumber
Ini tidak hanya apa yang saya butuhkan, terima kasih! Sebagai bonus, untuk mengimplementasikannya, saya menulis ulang program saya sehingga alih-alih membuat dua plot pencar terpisah dalam warna berbeda pada gambar yang sama untuk mewakili dua set data, saya menyalin metode contoh untuk menetapkan warna ke suatu titik. Ini membuat program saya sedikit lebih mudah dibaca, dan lebih sedikit kode. Sekarang pergi untuk menemukan panduan untuk mengubah warna menjadi angka!
jdmcbr
1
Ini untuk plot pencar. Bagaimana dengan plot garis? Saya mencoba membuatnya bekerja pada mereka tetapi tidak. Apakah ada solusi?
Sohaib
@Sohaib Lihat jawaban saya
texasflood
Saya punya pertanyaan tentang ini. Ketika saya menyebarkan plot poin saya seperti ini: plt.scatter (X_reduced [y == i, 0], X_reduced [y == i, 1], c = c, label = target_name, picker = True) dengan zip untuk i, c dan target_name, lalu urutan indeks saya kacau? Dan saya tidak bisa melihat lagi ke datapoint miliknya?
Chris,
Ini sepertinya tidak berfungsi untuk jupyter 5 notebook dengan ipython 5. Apakah ada cara mudah untuk memperbaikinya? The printPernyataan juga harus menggunakan parens untuk kompatibilitas dengan python 3
nealmcb
14

Sedikit edit pada contoh yang disediakan di 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()

Ini plot plot garis lurus, seperti yang diminta Sohaib

texasflood
sumber
5

mpld3 menyelesaikannya untukku. EDIT (KODE DITAMBAH):

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

Anda dapat memeriksa contoh ini

Julian
sumber
Harap sertakan kode sampel dan jangan hanya menautkan ke sumber eksternal tanpa konteks atau informasi. Lihat Pusat Bantuan untuk informasi lebih lanjut.
Joseph Farah
5
sayangnya mpld3 tidak lagi dipelihara secara aktif pada Juli 2017
Ben Lindsay
Contoh kode gagal dengan a TypeError: array([1.]) is not JSON serializable.
P-Gn
@ P-Gn ikuti saja triknya di sini stackoverflow.com/questions/48015030/mpld3-with-python-error MPLD3 adalah solusi sederhana untuk ini dan begitu jawaban di atas diikuti, ia berfungsi.
Zalakain
1
@Zalakain Sayangnya, mpl3d tampaknya ditinggalkan .
P-Gn
5

mplcursors bekerja untuk saya. mplcursors menyediakan anotasi yang dapat diklik untuk matplotlib. Ini sangat terinspirasi dari mpldatacursor ( https://github.com/joferkington/mpldatacursor ), dengan API yang lebih sederhana

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()
Enayat
sumber
Saya menggunakan ini sendiri, sejauh ini solusi termudah untuk seseorang yang sedang terburu-buru. Saya baru saja menggambar 70 label dan matplotlibmembuat setiap baris ke-10 dengan warna yang sama, sangat menyebalkan. mplcursorspilah itu.
ajsp
5

Jawaban lain tidak menjawab kebutuhan saya untuk menunjukkan tooltips dengan benar dalam versi terbaru dari Jupyter inline matplotlib. Yang ini bekerja:

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

Mengarah ke sesuatu seperti gambar berikut ketika melewati suatu titik dengan mouse: masukkan deskripsi gambar di sini

Farzad Vertigo
sumber
3
Sumber untuk ini (tidak didistribusikan
Victoria Stuart
Saya tidak bisa mendapatkan ini bekerja di laboratorium jupyter. Apakah mungkin bekerja di notebook jupyter tetapi tidak di lab jupyter?
MD004
3

Jika Anda menggunakan notebook jupyter, solusi saya semudah:

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

Kamu bisa mendapatkan sesuatu seperti masukkan deskripsi gambar di sini

Yuchao Jiang
sumber
Sejauh ini solusi terbaik, hanya beberapa baris kode yang melakukan persis seperti yang diminta OP
Tim Johnsen
0

Saya telah membuat sistem anotasi multi-baris untuk ditambahkan ke: https://stackoverflow.com/a/47166787/10302020 . untuk versi terbaru: https://github.com/AidenBurgess/MultiAnnotationLineGraph

Cukup ubah data di bagian bawah.

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