Mendapatkan koordinat titik data terdekat pada plot matplotlib


9

Saya menggunakan matplotlibdengan NavigationToolbar2QT. Bilah alat menunjukkan posisi kursor. Tapi saya ingin kursor terkunci ke titik data terdekat (ketika cukup dekat) atau hanya menunjukkan koordinat titik data terdekat. Bisakah itu entah bagaimana diatur?


Silakan periksa tautan di bawah ini dan lihat apakah itu menyelesaikan masalah Anda. Tautan menyediakan fungsi snaptocursor yang mirip dengan apa yang Anda cari. matplotlib.org/3.1.1/gallery/misc/cursor_demo_sgskip.html
Anupam Chaplot

@AnupamChaplot "Ini menggunakan Matplotlib untuk menggambar kursor dan mungkin lambat karena ini membutuhkan menggambar ulang gambar dengan setiap gerakan mouse." Saya memiliki sekitar 16 plot dengan 10000 poin SETIAP pada grafik, jadi dengan menggambar ulang ini akan agak lambat.
Pygmalion

Jika Anda tidak ingin menggambar apa pun secara visual (mengapa meminta itu?), Anda dapat memanipulasi apa yang ditampilkan di bilah alat seperti yang ditunjukkan di matplotlib.org/3.1.1/gallery/images_contours_and_fields/…
ImportanceOfBeingErnest

@ImportanceOfBeingErnest Saya tidak mengerti saran Anda. Tetapi bayangkan ini: Anda memiliki 16 plot garis dan masing-masing memiliki puncak yang berbeda. Anda ingin mengetahui koordinat yang tepat dari puncak satu plot tanpa mengintip ke dalam data. Anda tidak pernah bisa meletakkan kursor tepat pada titik, jadi ini sangat tidak tepat. Jadi program seperti Origin memiliki opsi untuk menunjukkan koordinat yang tepat dari titik terdekat ke posisi kursor saat ini.
Pygmalion

1
Ya, itulah yang dilakukan cursor_demo_sgskip . Tetapi jika Anda tidak ingin menggambar kursor, Anda dapat menggunakan perhitungan dari contoh itu dan sebagai gantinya menampilkan angka yang dihasilkan di bilah alat, seperti yang ditunjukkan pada image_zcoord
ImportanceOfBeingErnest

Jawaban:


6

Jika Anda bekerja dengan sejumlah besar poin, saya menyarankan Anda untuk menggunakan 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)

Saya refactored kpie'smenjawab di sini sedikit. Setelah ckdtreedibuat, Anda dapat mengidentifikasi titik terdekat secara instan dan berbagai jenis informasi tentangnya dengan sedikit usaha:

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

Tampilan posisi kursor interaktif. Jika Anda ingin koordinat titik terdekat ditampilkan di Bilah Alat Navigasi:

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

Menggunakan acara. Jika Anda ingin jenis interaktivitas lain, Anda dapat menggunakan acara:

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

Ini tentu saja akan bekerja dengan sempurna hanya jika skala visual x: y sama dengan 1. Adakah gagasan tentang bagian dari masalah ini, kecuali untuk penskalaan ulang pointssetiap plot waktu diperbesar?
Pygmalion

Mengubah rasio aspek perlu mengubah metrik tentang bagaimana jarak diukur dalam ckdtrees. Sepertinya menggunakan metrik khusus pada ckdtrees tidak didukung. Oleh karena itu Anda harus menyimpan ckdtree.datasebagai poin realistis dengan skala = 1. Anda pointsdapat ditata ulang dan tidak ada masalah jika Anda perlu mengakses indeks mereka saja.
mathfux

Terima kasih. Apakah Anda tahu, kebetulan, jika ada cara untuk dengan mudah mengakses rasio skala reuw untuk sumbu matplotlib? Apa yang saya temukan di web sangat rumit.
Pygmalion

IMHO solusi terbaik untuk masalah saya adalah memasukkannya sebagai opsi ke matplotlibperpustakaan. Bagaimanapun, perpustakaan telah memanggil kembali posisi titik di suatu tempat - setelah semua, itu menggambar mereka dalam plot!
Pygmalion

Anda mungkin ingin mencoba set_aspect: matplotlib.org/3.1.3/api/_as_gen/…
mathfux

0

Kode berikut akan mencetak koordinat titik yang paling dekat dengan mouse saat Anda mengklik.

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

Saya mungkin dapat menyesuaikan kode dengan situasi saya (16 plot dengan masing-masing 10.000 poin), tetapi idenya adalah bahwa koordinat titik dicetak pada, katakanlah, bilah alat navigasi. Apakah itu mungkin?
Pygmalion

0

Anda bisa subkelas NavigationToolbar2QTdan menimpa mouse_movepawang. The xdatadan ydataatribut berisi posisi mouse saat ini di koordinat petak. Anda dapat mengambil itu ke titik data terdekat sebelum melewati acara ke kelas basismouse_move penangan .

Contoh lengkap, dengan menyoroti titik terdekat dalam plot sebagai bonus:

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_()
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.