2016-10-26 26 views
2

Ich brauche einen ziehbaren Datenmarker, um eine Reihe von Punkten in einem Datensatz manuell anzupassen. This Antwort und andere lösen dieses Problem mit einem Patch.Circle. Allerdings muss der Marker beim Zoomen des Plots gleich groß bleiben. Mit Kreisen zoomt der Kreis auch. Ich habe nicht versucht, mit jedem neuen Zoom dynamisch die Größe des Kreises zu ändern, weil es so aussieht, als ob man einen Marker einfacher bewegen könnte. Weiß jemand, wie man das macht?Matplotlib ziehbare Datenmarker

+0

Sie möchten die Position des Patches werden in Daten, aber die Größe des Patch-Koordinaten Koordinaten werden in Achsen? – cphlewis

Antwort

2

Unten, ich präsentiere eine MWE, die eine Möglichkeit zeigt, es mit der objektorientierten API von Matplotlib und PyQt4 zu tun. Die Datenmarker sind mit der mittleren Maustaste ziehbar. Die Strategie besteht darin, Ihre Daten mit einem line2D-Künstler zu zeichnen und anschließend die Marker zu ziehen, indem Sie die Daten des Künstlers manipulieren und den Plot aktualisieren. Dies lässt sich grob in 3 Schritten zusammenfassen:

Schritt 1 - Wenn die mittlere Maustaste angeklickt wird, werden die Koordinaten des Mauscursors in Pixel mit den xy-Daten des line2D-Künstlers in Pixeln verglichen. Wenn der lineare Abstand zwischen dem Cursor und dem nächsten Marker kleiner als ein bestimmter Wert ist (hier definiert als die Größe der Marker), wird der Index dieses Datenmarkers im Klassenattribut draggable gespeichert. Andernfalls wird draggable auf None festgelegt.

Schritt 2 - Wenn die Maus bewegt wird, wenn draggable is not None, die Koordinate der Datenmarkierung, deren Index in dem Klassenattribut gespeichert wurde draggable sind zu denjenigen des Mauszeigers eingestellt.

Schritt 3 - Wenn die mittlere Maustaste losgelassen wird, wird draggable auf None zurückgesetzt.

Hinweis: Falls gewünscht, ist es auch möglich, die pyplot-Schnittstelle zu verwenden, um die Abbildung anstelle der objektorientierten API zu generieren.

import sys 
from PyQt4 import QtGui 
from matplotlib.figure import Figure 
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg 
from matplotlib.backends.backend_qt4agg import FigureManagerQT 
import numpy as np 


class MyFigureCanvas(FigureCanvasQTAgg): 
    def __init__(self): 
     super(MyFigureCanvas, self).__init__(Figure()) 
     # init class attributes: 
     self.background = None 
     self.draggable = None 
     self.msize = 6 
     # plot some data: 
     x = np.random.rand(25) 
     self.ax = self.figure.add_subplot(111) 
     self.markers, = self.ax.plot(x, marker='o', ms=self.msize) 
     # define event connections: 
     self.mpl_connect('motion_notify_event', self.on_motion) 
     self.mpl_connect('button_press_event', self.on_click) 
     self.mpl_connect('button_release_event', self.on_release) 

    def on_click(self, event): 
     if event.button == 2: # 2 is for middle mouse button 
      # get mouse cursor coordinates in pixels: 
      x = event.x 
      y = event.y 
      # get markers xy coordinate in pixels: 
      xydata = self.ax.transData.transform(self.markers.get_xydata()) 
      xdata, ydata = xydata.T 
      # compute the linear distance between the markers and the cursor: 
      r = ((xdata - x)**2 + (ydata - y)**2)**0.5 
      if np.min(r) < self.msize: 
       # save figure background: 
       self.markers.set_visible(False) 
       self.draw() 
       self.background = self.copy_from_bbox(self.ax.bbox) 
       self.markers.set_visible(True) 
       self.ax.draw_artist(self.markers) 
       self.update() 
       # store index of draggable marker: 
       self.draggable = np.argmin(r) 
      else: 
       self.draggable = None 

    def on_motion(self, event): 
     if self.draggable is not None: 
      if event.xdata and event.ydata: 
       # get markers coordinate in data units: 
       xdata, ydata = self.markers.get_data() 
       # change the coordinate of the marker that is 
       # being dragged to the ones of the mouse cursor: 
       xdata[self.draggable] = event.xdata 
       ydata[self.draggable] = event.ydata 
       # update the data of the artist: 
       self.markers.set_xdata(xdata) 
       self.markers.set_ydata(ydata) 
       # update the plot: 
       self.restore_region(self.background) 
       self.ax.draw_artist(self.markers) 
       self.update() 

    def on_release(self, event): 
     self.draggable = None 

if __name__ == '__main__': 

    app = QtGui.QApplication(sys.argv) 

    canvas = MyFigureCanvas() 
    manager = FigureManagerQT(canvas, 1) 
    manager.show() 

    sys.exit(app.exec_()) 

enter image description here

+0

Das funktioniert super! Vielen Dank! – martinako

+0

Ich frage mich, ob es einen Vorteil gibt, den on_click() in Pixelkoordinaten zu berechnen, um ihn in Datenkoordinaten zu berechnen (wie in on_motion()). Wenn Sie mit Datenkoordinaten in on_click() arbeiten, müssen Sie die Datenkoordinaten nicht in Pixelkoordinaten umwandeln. In jedem Fall ist es für pädagogische Zwecke gut, beide Verwendungen zu sehen :-) – martinako

+1

@martinako Pixelkoordinaten ermöglichen die Berechnung linearer Abstände, die unabhängig von den Skalen der x- und y-Achse sind. Sonst müssten wir die xy-Daten sowieso transformieren, um zu berücksichtigen, dass beide Achsen nicht die gleiche Skalierung haben und auch beim Heraus- und Hineinzoomen berücksichtigen. –