Ottenere le coordinate del punto dati più vicino sul grafico matplotlib


9

Sto usando matplotlibcon NavigationToolbar2QT. La barra degli strumenti mostra la posizione del cursore. Ma vorrei che il cursore si agganci al punto dati più vicino (quando abbastanza vicino) o semplicemente mostri le coordinate del punto dati più vicino. Può essere organizzato in qualche modo?


Si prega di controllare il link qui sotto e vedere se risolve il problema. Il link fornisce uno snapcursor di funzioni che assomiglia a quello che stai cercando. matplotlib.org/3.1.1/gallery/misc/cursor_demo_sgskip.html
Anupam Chaplot

@AnupamChaplot "Usa Matplotlib per disegnare il cursore e può essere lento poiché ciò richiede di ridisegnare la figura ad ogni spostamento del mouse." Ho circa 16 grafici con 10000 punti OGNI sul grafico, quindi con il ridisegno questo sarebbe piuttosto lento.
Pigmalione,

Se non vuoi ridisegnare nulla visivamente (perché chiederlo allora?), Puoi manipolare ciò che viene mostrato nella barra degli strumenti come mostrato in matplotlib.org/3.1.1/gallery/images_contours_and_fields/…
ImportanceOfBeingErnest

@ImportanceOfBeingErnest Non capisco il tuo suggerimento. Ma immagina questo: hai 16 grafici di linee e ognuno di essi ha un picco distinto. Vuoi conoscere le coordinate esatte del picco di un grafico senza sbirciare nei dati. Non puoi mai posizionare il cursore esattamente sul punto, quindi questo è altamente impreciso. Quindi programmi come Origin hanno un'opzione per mostrare le coordinate esatte del punto più vicino alla posizione corrente del cursore.
Pigmalione,

1
Sì, questo è ciò che cursore_demo_sgskip fa. Ma se non vuoi disegnare il cursore, puoi usare i calcoli di quell'esempio e invece visualizzare il numero risultante nella barra degli strumenti, come mostrato in image_zcoord
ImportanceOfBeingErnest

Risposte:


6

Se lavori con grandi serie di punti, ti consiglio di usare 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)

Ho refactored kpie'squi la risposta un po '. Una volta ckdtreecreato, puoi identificare immediatamente i punti più vicini e vari tipi di informazioni su di essi con un piccolo sforzo:

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

Visualizzazione interattiva della posizione del cursore. Se si desidera visualizzare le coordinate del punto più vicino sulla barra degli strumenti di navigazione:

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

Utilizzando gli eventi. Se desideri un altro tipo di interattività, puoi utilizzare gli eventi:

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

Ovviamente funzionerà perfettamente solo se la scala visiva x: y è uguale a 1. Qualche idea su questa parte del problema, tranne che per ridimensionare pointsogni volta che viene ingrandita la trama?
Pigmalione,

La modifica delle proporzioni richiede di modificare le metriche di come viene misurata la distanza in ckdtrees. Sembra che l'utilizzo di metriche personalizzate su ckdtrees non sia supportato. Pertanto, è necessario mantenere ckdtree.datacome punti realistici con scala = 1. pointsÈ possibile riscalare e non vi è alcun problema se è necessario accedere solo ai loro indici.
Mathfux,

Grazie. Sai, per caso, se esiste un modo per accedere facilmente al rapporto di scala di ritorno per gli assi matplotlib? Quello che ho trovato sul web è stato estremamente complicato.
Pigmalione,

IMHO la migliore soluzione per il mio problema sarebbe quella di includerlo come opzione nella matplotliblibreria. Dopotutto, la biblioteca ha recuperato le posizioni dei punti da qualche parte - dopo tutto, li sta disegnando nella trama!
Pigmalione,

Potresti provare set_aspect: matplotlib.org/3.1.3/api/_as_gen/…
mathfux

0

Il seguente codice stamperà le coordinate del punto più vicino al mouse quando si fa clic.

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

Probabilmente sarei in grado di adattare il codice alla mia situazione (16 grafici con 10000 punti ciascuno), ma l'idea era che le coordinate del punto fossero stampate, diciamo, sulla barra degli strumenti di navigazione. È possibile?
Pigmalione,

0

È possibile NavigationToolbar2QTeseguire la sottoclasse e sovrascrivere il mouse_movegestore. Gli attributi xdatae ydatacontengono la posizione corrente del mouse nelle coordinate del grafico. È possibile agganciarlo al punto dati più vicino prima di passare l'evento al mouse_movegestore della classe base .

Esempio completo, con l'evidenziazione del punto più vicino nella trama come 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_()
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.