เป็นไปได้ที่จะทำให้ป้ายชื่อปรากฏขึ้นเมื่อวางเมาส์เหนือจุดใน matplotlib หรือไม่?


148

ฉันใช้ matplotlib เพื่อทำแผนการกระจาย แต่ละจุดบนพล็อตกระจายเกี่ยวข้องกับวัตถุที่มีชื่อ ฉันต้องการที่จะเห็นชื่อของวัตถุเมื่อฉันเลื่อนเคอร์เซอร์ไปที่จุดบนพล็อตกระจายที่เกี่ยวข้องกับวัตถุนั้น โดยเฉพาะอย่างยิ่งจะเป็นการดีที่สามารถดูชื่อของคะแนนที่ผิดปกติได้อย่างรวดเร็ว สิ่งที่ใกล้เคียงที่สุดที่ฉันสามารถค้นพบได้ในขณะค้นหาที่นี่คือคำสั่งบันทึกย่อ แต่ดูเหมือนว่าจะสร้างป้ายกำกับคงที่ในพล็อต น่าเสียดายที่มีจำนวนคะแนนที่ฉันมีพล็อตกระจายจะไม่สามารถอ่านได้หากฉันติดป้ายแต่ละจุด ไม่มีใครรู้วิธีสร้างป้ายกำกับที่จะปรากฏเฉพาะเมื่อเคอร์เซอร์อยู่ใกล้กับจุดนั้นหรือไม่?


2
คนที่ลงท้ายด้วยการค้นหาอาจต้องการตรวจสอบคำตอบนี้ซึ่งค่อนข้างซับซ้อน แต่อาจเหมาะสมขึ้นอยู่กับข้อกำหนด
ImportanceOfBeingErnest

คำตอบ:


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
ดีมาก! One note ฉันสังเกตเห็นว่า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

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 อย่างมีประโยชน์มาก คุณอาจต้อง 'debounce' สิ่งนี้เพราะ motion_notify_event จะทำซ้ำสำหรับการเคลื่อนไหวภายในพื้นที่โค้ง เพียงแค่ตรวจสอบว่าวัตถุโค้งเท่ากับเส้นโค้งก่อนหน้าดูเหมือนว่าจะทำงาน
bvanlew

5
อืม - นี่มันใช้งานไม่ได้กับฉัน (สำหรับสองสามอย่างที่ทำกับmatplotlib... ) - มันใช้กับipython/ jupyternotebooks ได้ไหม? มันยังทำงานเมื่อมีหลายย่อย? สิ่งที่เกี่ยวกับในแผนภูมิแท่งมากกว่ากราฟเส้น?
dwanderson

12
สิ่งนี้พิมพ์เลเบลลงในคอนโซลเมื่อทำการโฮเวอร์ สิ่งที่เกี่ยวกับการทำฉลากปรากฏบนภาพเมื่อโฉบ? ฉันเข้าใจว่าเป็นคำถาม
Nikana Reklawyks

@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, ตัวเลือก = True) ด้วย zip สำหรับ i, c และ target_name ดังนั้นลำดับของดัชนีของฉันจะทำให้ยุ่งเหยิง? และฉันไม่สามารถค้นหาอีกต่อไปว่าเป็นของดาต้าพอยน์ใด?
Chris

ดูเหมือนจะใช้งานไม่ได้กับโน๊ตบุ๊ค jupyter 5 ที่มี ipython 5 มีวิธีง่าย ๆ ในการแก้ไขปัญหานี้หรือไม่? printสั่งนี้ยังควรใช้ parens เข้ากันได้กับงูหลาม 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
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 ดูเหมือนว่าจะ abandonned
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
แหล่งที่มาสำหรับสิ่งนี้ (ไม่ได้แจกจ่าย
Victoria Stuart

ฉันไม่สามารถทำงานนี้ในห้องทดลองจูปีเตอร์ บางทีมันอาจทำงานในสมุดบันทึกของจูปีเตอร์ แต่ไม่ได้อยู่ในห้องทดลองจูปีเตอร์?
MD004

3

หากคุณใช้โน๊ตบุ๊ค jupyter โซลูชันของฉันนั้นง่ายเหมือน:

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

คุณจะได้รับสิ่งที่ชอบ ป้อนคำอธิบายรูปภาพที่นี่


โดยวิธีแก้ปัญหาที่ดีที่สุดมีเพียงไม่กี่บรรทัดเท่านั้นที่ทำตามที่ OP ขอ
Tim Johnsen

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()
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.