การรับพิกัดของจุดข้อมูลที่ใกล้ที่สุดบนพล็อต matplotlib


9

ฉันใช้กับmatplotlib NavigationToolbar2QTแถบเครื่องมือกำลังแสดงตำแหน่งของเคอร์เซอร์ แต่ฉันต้องการให้เคอร์เซอร์เลื่อนไปยังจุดข้อมูลที่ใกล้ที่สุด (เมื่อใกล้พอ) หรือเพียงแค่แสดงพิกัดของจุดข้อมูลที่ใกล้ที่สุด สามารถจัดเรียงอย่างใด


โปรดตรวจสอบลิงค์ด้านล่างและดูว่าจะช่วยแก้ปัญหาของคุณ ลิงค์นี้มีฟังก์ชั่น snaptocursor ซึ่งมีลักษณะคล้ายกับสิ่งที่คุณกำลังมองหา matplotlib.org/3.1.1/gallery/misc/cursor_demo_sgskip.html
Anupam Chaplot

@AnupamChaplot "ใช้ Matplotlib เพื่อวาดเคอร์เซอร์และอาจช้าเนื่องจากต้องวาดรูปใหม่ทุกครั้งที่เลื่อนเมาส์" ฉันมีประมาณ 16 แปลงที่มี 10,000 คะแนนแต่ละกราฟบนกราฟดังนั้นการวาดใหม่นี้จะค่อนข้างช้า
Pygmalion

หากคุณไม่ต้องการวาดอะไรที่มองเห็น (เพราะเหตุใดจึงถาม) คุณสามารถจัดการสิ่งที่แสดงในแถบเครื่องมือตามที่แสดงในmatplotlib.org/3.1.1/gallery/images_contours_and_fields/ ......
ImportanceOfBeingErnest

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

1
ใช่นั่นคือสิ่งที่cursor_demo_sgskipทำ แต่ถ้าคุณไม่ต้องการวาดเคอร์เซอร์คุณสามารถใช้การคำนวณจากตัวอย่างนั้นและแสดงตัวเลขผลลัพธ์ในแถบเครื่องมือแทนดังที่แสดงในimage_zcoord
ImportanceOfBeingErnest

คำตอบ:


6

หากคุณทำงานกับคะแนนจำนวนมากฉันแนะนำให้คุณใช้CKDtrees:

import matplotlib.pyplot as plt
import numpy as np
import scipy.spatial

points = np.column_stack([np.random.rand(50), np.random.rand(50)])
fig, ax = plt.subplots()
coll = ax.scatter(points[:,0], points[:,1])
ckdtree = scipy.spatial.cKDTree(points)

ฉันได้kpie'sคำตอบที่นี่เล็กน้อยrefactored เมื่อckdtreeสร้างแล้วคุณสามารถระบุจุดที่ใกล้ที่สุดได้ทันทีและข้อมูลประเภทต่าง ๆ เกี่ยวกับพวกเขาด้วยความพยายามเล็กน้อย:

def closest_point_distance(ckdtree, x, y):
    #returns distance to closest point
    return ckdtree.query([x, y])[0]

def closest_point_id(ckdtree, x, y):
    #returns index of closest point
    return ckdtree.query([x, y])[1]

def closest_point_coords(ckdtree, x, y):
    # returns coordinates of closest point
    return ckdtree.data[closest_point_id(ckdtree, x, y)]
    # ckdtree.data is the same as points

จอแสดงผลแบบโต้ตอบของตำแหน่งเคอร์เซอร์ หากคุณต้องการให้พิกัดของจุดที่ใกล้ที่สุดปรากฏบนแถบเครื่องมือนำทาง:

def val_shower(ckdtree):
    #formatter of coordinates displayed on Navigation Bar
    return lambda x, y: '[x = {}, y = {}]'.format(*closest_point_coords(ckdtree, x, y))

plt.gca().format_coord = val_shower(ckdtree)
plt.show()

ใช้กิจกรรม หากคุณต้องการการโต้ตอบแบบอื่นคุณสามารถใช้กิจกรรม:

def onclick(event):
    if event.inaxes is not None:
        print(closest_point_coords(ckdtree, event.xdata, event.ydata))

fig.canvas.mpl_connect('motion_notify_event', onclick)
plt.show()

แน่นอนว่าวิธีนี้จะทำงานได้อย่างไร้ที่ติเฉพาะเมื่อ x: y visual scale เท่ากับ 1. ความคิดเกี่ยวกับส่วนนี้ของปัญหาใด ๆ ยกเว้นการปรับขนาดอีกครั้งในpointsแต่ละครั้งที่มีการย่อส่วน
Pygmalion

การเปลี่ยนอัตราส่วนภาพจำเป็นต้องเปลี่ยนการวัดระยะทางในหน่วย ckdtrees ดูเหมือนว่าไม่สนับสนุนการใช้เมตริกที่กำหนดเองใน ckdtrees ดังนั้นคุณต้องรักษาckdtree.dataคะแนนที่เป็นจริงด้วยสเกล = 1 คุณpointsสามารถได้รับการลดขนาดและไม่มีปัญหาหากคุณจำเป็นต้องเข้าถึงดัชนีของพวกเขาเท่านั้น
mathfux

ขอบคุณ คุณรู้ไหมถ้ามีโอกาสคุณสามารถเข้าถึงอัตราส่วนสเกล reuw สำหรับแกนได้อย่างง่ายดายmatplotlibหรือไม่? สิ่งที่ฉันพบบนเว็บนั้นซับซ้อนมาก
Pygmalion

IMHO ทางออกที่ดีที่สุดสำหรับปัญหาของฉันคือการรวมสิ่งนั้นไว้ในmatplotlibห้องสมุด ท้ายที่สุดแล้วห้องสมุดได้ปรับตำแหน่งจุดให้อยู่ที่ใดที่หนึ่งหลังจากทั้งหมดมันกำลังวาดมันขึ้นมาในเนื้อเรื่อง!
Pygmalion

คุณอาจต้องการลองset_aspect: matplotlib.org/3.1.3/api/_as_gen/ …
mathfux

0

รหัสต่อไปนี้จะพิมพ์พิกัดของจุดที่อยู่ใกล้ที่สุดกับเมาส์เมื่อคุณคลิก

import matplotlib.pyplot as plt
import numpy as np
np.random.seed(19680801)
N = 50
x = np.random.rand(N)
y = np.random.rand(N)
fig,ax = plt.subplots()
plt.scatter(x, y)
points = list(zip(x,y))
def distance(a,b):
    return(sum([(k[0]-k[1])**2 for k in zip(a,b)])**0.5)
def onclick(event):
    dists = [distance([event.xdata, event.ydata],k) for k in points]
    print(points[dists.index(min(dists))])
fig.canvas.mpl_connect('button_press_event', onclick)
plt.show()

ฉันอาจจะสามารถปรับรหัสให้เหมาะกับสถานการณ์ของฉัน (16 แปลงที่มี 10,000 คะแนน) แต่แนวคิดก็คือพิกัดของจุดนั้นถูกพิมพ์ลงบนแถบเครื่องมือนำทาง เป็นไปได้ไหม
Pygmalion

0

คุณสามารถ subclass NavigationToolbar2QTและแทนที่mouse_moveตัวจัดการ xdataและydataคุณลักษณะที่มีตำแหน่งของเมาส์ในปัจจุบันในพิกัดพล็อต คุณสามารถถ่ายภาพนั้นไปยังจุดข้อมูลที่ใกล้เคียงที่สุดก่อนส่งเหตุการณ์ไปยังmouse_moveตัวจัดการคลาสพื้นฐาน

ตัวอย่างเต็มรูปแบบด้วยการเน้นจุดที่ใกล้ที่สุดในพล็อตเป็นโบนัส:

import sys

import numpy as np

from matplotlib.backends.qt_compat import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvas, NavigationToolbar2QT
from matplotlib.figure import Figure


class Snapper:
    """Snaps to data points"""

    def __init__(self, data, callback):
        self.data = data
        self.callback = callback

    def snap(self, x, y):
        pos = np.array([x, y])
        distances = np.linalg.norm(self.data - pos, axis=1)
        dataidx = np.argmin(distances)
        datapos = self.data[dataidx,:]
        self.callback(datapos[0], datapos[1])
        return datapos


class SnappingNavigationToolbar(NavigationToolbar2QT):
    """Navigation toolbar with data snapping"""

    def __init__(self, canvas, parent, coordinates=True):
        super().__init__(canvas, parent, coordinates)
        self.snapper = None

    def set_snapper(self, snapper):
        self.snapper = snapper

    def mouse_move(self, event):
        if self.snapper and event.xdata and event.ydata:
            event.xdata, event.ydata = self.snapper.snap(event.xdata, event.ydata)
        super().mouse_move(event)


class Highlighter:
    def __init__(self, ax):
        self.ax = ax
        self.marker = None
        self.markerpos = None

    def draw(self, x, y):
        """draws a marker at plot position (x,y)"""
        if (x, y) != self.markerpos:
            if self.marker:
                self.marker.remove()
                del self.marker
            self.marker = self.ax.scatter(x, y, color='yellow')
            self.markerpos = (x, y)
            self.ax.figure.canvas.draw()


class ApplicationWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()
        self._main = QtWidgets.QWidget()
        self.setCentralWidget(self._main)
        layout = QtWidgets.QVBoxLayout(self._main)
        canvas = FigureCanvas(Figure(figsize=(5,3)))
        layout.addWidget(canvas)
        toolbar = SnappingNavigationToolbar(canvas, self)
        self.addToolBar(toolbar)

        data = np.random.randn(100, 2)
        ax = canvas.figure.subplots()
        ax.scatter(data[:,0], data[:,1])

        self.highlighter = Highlighter(ax)
        snapper = Snapper(data, self.highlighter.draw)
        toolbar.set_snapper(snapper)


if __name__ == "__main__":
    qapp = QtWidgets.QApplication(sys.argv)
    app = ApplicationWindow()
    app.show()
    qapp.exec_()
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.