Abrufen der Koordinaten des nächstgelegenen Datenpunkts im Matplotlib-Plot

9

Ich benutze matplotlibmit NavigationToolbar2QT. Die Symbolleiste zeigt die Position des Cursors an. Aber ich möchte, dass der Cursor zum nächsten Datenpunkt schnappt (wenn er nah genug ist) oder einfach die Koordinate des nächsten Datenpunkts anzeigt. Kann das irgendwie arrangiert werden?

Pygmalion
quelle
Bitte überprüfen Sie den folgenden Link und prüfen Sie, ob Ihr Problem dadurch behoben wird. Der Link bietet einen Funktions-Snaptocursor, der dem ähnelt, was Sie suchen. matplotlib.org/3.1.1/gallery/misc/cursor_demo_sgskip.html
Anupam Chaplot
@AnupamChaplot "Es verwendet Matplotlib zum Zeichnen des Cursors und kann langsam sein, da dies das Neuzeichnen der Figur bei jeder Mausbewegung erfordert." Ich habe ungefähr 16 Diagramme mit 10000 Punkten JEDES in der Grafik, daher wäre dies beim Neuzeichnen ziemlich langsam.
Pygmalion
Wenn Sie nichts visuell neu zeichnen möchten (warum dann danach fragen?), Können Sie die Anzeige
ImportanceOfBeingErnest
@ImportanceOfBeingErnest Ich verstehe Ihren Vorschlag nicht. Aber stellen Sie sich Folgendes vor: Sie haben 16 Liniendiagramme und jedes hat einen eigenen Peak. Sie möchten die genauen Koordinaten des Peaks eines Diagramms kennen, ohne einen Blick in die Daten zu werfen. Sie können den Cursor niemals genau auf den Punkt setzen, daher ist dies sehr ungenau. Programme wie Origin haben daher die Möglichkeit, die genauen Koordinaten des Punkts anzuzeigen, der der aktuellen Cursorposition am nächsten liegt.
Pygmalion
1
Ja, genau das macht cursor_demo_sgskip . Wenn Sie den Cursor jedoch nicht zeichnen möchten, können Sie die Berechnungen aus diesem Beispiel verwenden und stattdessen die resultierende Zahl in der Symbolleiste anzeigen, wie in image_zcoord
ImportanceOfBeingErnest

Antworten:

6

Wenn Sie mit großen Punktmengen arbeiten, empfehle ich Ihnen 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)

Ich habe die kpie'sAntwort hier ein wenig überarbeitet. Sobald ckdtreees erstellt wurde, können Sie mit ein wenig Aufwand die nächstgelegenen Punkte und verschiedene Arten von Informationen sofort identifizieren:

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

Interaktive Anzeige der Cursorposition. Wenn Sie möchten, dass die Koordinaten des nächstgelegenen Punkts in der Navigationssymbolleiste angezeigt werden:

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

Ereignisse verwenden. Wenn Sie eine andere Art von Interaktivität wünschen, können Sie Ereignisse verwenden:

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()
mathfux
quelle
Dies funktioniert natürlich nur dann einwandfrei, wenn der visuelle Maßstab x: y gleich 1 ist. Haben Sie eine Vorstellung von diesem Teil des Problems, außer der Neuskalierung pointsjedes Mal, wenn der Plot gezoomt wird?
Pygmalion
Das Ändern des Seitenverhältnisses erfordert das Ändern der Metriken für die Entfernungsmessung in ckdtrees. Es scheint, dass die Verwendung von benutzerdefinierten Metriken für ckdtrees nicht unterstützt wird. Daher müssen Sie ckdtree.dataals realistische Punkte mit scale = 1 pointsbeibehalten. Sie können neu skaliert werden und es gibt kein Problem, wenn Sie nur auf deren Indizes zugreifen müssen.
Mathfux
Vielen Dank. Wissen Sie zufällig, ob es eine Möglichkeit gibt, einfach auf das reuw-Skalierungsverhältnis für Achsen in zuzugreifen matplotlib? Was ich im Web gefunden habe, war äußerst kompliziert.
Pygmalion
IMHO wäre die beste Lösung für mein Problem, dies als Option in die matplotlibBibliothek aufzunehmen. Immerhin hat die Bibliothek irgendwo Punktpositionen abgerufen - schließlich zeichnet sie sie in der Handlung auf!
Pygmalion
Vielleichtset_aspect
möchten
0

Der folgende Code druckt die Koordinaten des Punkts, der der Maus am nächsten liegt, wenn Sie klicken.

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()
kpie
quelle
Ich könnte den Code wahrscheinlich an meine Situation anpassen (16 Diagramme mit jeweils 10000 Punkten), aber die Idee war, dass die Koordinaten des Punkts beispielsweise auf der Navigationssymbolleiste gedruckt werden. Ist das möglich?
Pygmalion
0

Sie können NavigationToolbar2QTden mouse_moveHandler unterordnen und überschreiben . Die Attribute xdataund ydataenthalten die aktuelle Mausposition in Plotkoordinaten. Sie können dies am nächsten Datenpunkt ausrichten, bevor Sie das Ereignis an den Basisklassen- mouse_moveHandler übergeben.

Vollständiges Beispiel mit Hervorhebung des nächstgelegenen Punkts in der Handlung als 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_()
Alexander Rossmanith
quelle