Python 如何使用双击事件(例如添加标记、取消以前的操作)与matplotlib绘图交互?

Python 如何使用双击事件(例如添加标记、取消以前的操作)与matplotlib绘图交互?,python,matplotlib,pyqt,pyside2,Python,Matplotlib,Pyqt,Pyside2,我正在学习如何使用单击事件在matplotlib中触发特定操作 在我的可复制示例中,我希望: task1:单击/拖动标记时,将其拖动到不同的位置[此操作已完成] 任务2:用鼠标左键双击绘图时,在绘图中添加标记 任务3:用鼠标右键双击时取消上一次单击操作(添加标记或拖动标记) 任务1 这些帖子对实现可拖动的数据非常有用,我认为在我的示例中效果很好 任务2 我认为在下面的代码中,我应该接近实现“添加标记”事件,但是当我更新艺术家的数据时,有些地方不正确,因为新标记没有出现在绘图上 任务3 我不知

我正在学习如何使用单击事件在matplotlib中触发特定操作

在我的可复制示例中,我希望:

  • task1:单击/拖动标记时,将其拖动到不同的位置[此操作已完成]
  • 任务2:用鼠标左键双击绘图时,在绘图中添加标记
  • 任务3:用鼠标右键双击时取消上一次单击操作(添加标记或拖动标记)
任务1 这些帖子对实现可拖动的数据非常有用,我认为在我的示例中效果很好

任务2 我认为在下面的代码中,我应该接近实现“添加标记”事件,但是当我更新艺术家的数据时,有些地方不正确,因为新标记没有出现在绘图上

任务3 我不知道什么是最好的方式来实现这一点。。。我认为最好的方法是在触发单击事件之前始终在内存中保存一份绘图副本,如果在单击事件之后触发双击事件(使用鼠标右键),则恢复此副本(=添加标记或将标记拖动到其他位置)

我正在使用以下脚本:

导入系统 从PySide2.QtWidgets导入QApplication 从matplotlib.figure导入图形 从matplotlib.backends.backend_qt4agg导入图Canvasqtagg 从matplotlib.backends.backend_qt4agg导入FigureManager qt 将numpy作为np导入 类别MyFigureCanvas(FigureCanvasQTAgg): 定义初始化(自): super(MyFigureCanvas,self)。\uuuu init\uuuuu(Figure()) #初始化类属性: self.background=None self.draggable=无 self.msize=6 #绘制一些数据: x=np.rand.rand(25) self.ax=self.figure.add_子批次(111) (self.markers,)=self.ax.plot(x,marker=“o”,ms=self.msize) #定义事件连接: self.mpl\u connect(“运动通知事件”,self.on\u运动) self.mpl\u connect(“按钮按下事件”,self.on\u单击) self.mpl\u connect(“按钮释放事件”,self.on\u释放) 单击时的def(自身,事件): 如果event.dblclick: 如果event.button==1:#在绘制的直线上添加一个标记 #以像素为单位获取鼠标光标坐标: x、 y=事件.x,事件.y #获取标记xy坐标(以像素为单位): xydata=self.ax.transData.transform(self.markers.get_xydata()) 扩展数据,ydata=xydata.T #更新艺术家的数据: self.markers.set_扩展数据(扩展数据) self.markers.set_ydata(ydata) self.ax.draw_艺术家(self.markers) self.update() 打印(f“{event.button}-coords:x:{x}/y:{y}”) elif event.button==3:#取消以前的操作 打印(f“双击事件-{str(event.button)}”) 如果event.button==1:#2表示鼠标中键 #以像素为单位获取鼠标光标坐标: x=事件.x y=事件。y #获取标记xy坐标(以像素为单位): xydata=self.ax.transData.transform(self.markers.get_xydata()) 扩展数据,ydata=xydata.T #计算标记和光标之间的线性距离: r=((扩展数据-x)**2+(ydata-y)**2)**0.5 如果np.min(r)我对您的代码做了以下更改:

任务2 新标记没有出现在绘图中的原因是您使用了
event.x
event.y
而不是
event.xdata
event.ydata
(它自动获取坐标,而不是将像素转换为坐标)。我将新点附加到旧标记的坐标上,并更新了绘图

任务3 我创建了一个新的实例变量
self.memory
,其中包含标记的坐标<使用
self.save_to_memory()
函数拖动标记或添加新标记之前,code>self.memory会进行更新。
self.undo()
函数使用self.memory撤消上次更改。 我还用一个elif替换了行
if event.button==1:
,因为双击时,它访问两个if语句

import sys
from PySide2.QtWidgets import QApplication
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)

        self.memory = self.markers.get_xydata()

        # 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 save_to_memory(self):
        self.memory = self.markers.get_xydata()

    def undo(self):
        self.markers.set_xdata(self.memory[:, 0])
        self.markers.set_ydata(self.memory[:, 1])
        self.draw()
        self.ax.draw_artist(self.markers)
        self.update()

    def on_click(self, event):
        if event.dblclick:
            if event.button == 1:  # add a marker on the line plotted
                self.save_to_memory()
                # get mouse cursor coordinates in pixels:
                x, y = event.xdata, event.ydata

                # update the data of the artist:
                old_xy = self.markers.get_xydata()
                old_xy = np.vstack((old_xy, np.array([[x, y]])))
                self.markers.set_xdata(old_xy[:, 0])
                self.markers.set_ydata(old_xy[:, 1])
                self.ax.draw_artist(self.markers)
                self.update()

                print(f"{event.button} - coords: x: {x} / y: {y} ")
            elif event.button == 3:  # cancel previous action
                print(f"Double clicked event - {str(event.button)}")
                self.undo()

        elif event.button == 1:  # 2 is for middle mouse button
            # get mouse cursor coordinates in pixels:
            x = event.x
            y = event.y
            # print(f"{event.button} - coords: x: {x} / y: {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:
                self.save_to_memory()
                # 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 = QApplication(sys.argv)

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

    sys.exit(app.exec_())

非常感谢你的回答!出于好奇,您将如何编辑您的答案,以便能够
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)

        self.history = []
        self.history_depth = 10

        # 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 append_to_history(self, type, index, x=None, y=None):
        if index < 0:
            index += self.markers.get_xydata().shape[0]
        if type == "move":
            self.history.append({"type": "move",
                                 "index": index,
                                 "x": x,
                                 "y": y})
        elif type == "append":
            self.history.append({"type": "append",
                                 "index": index + 1})
        if len(self.history) > self.history_depth:
            del self.history[0]

    def undo(self):
        if len(self.history) > 0:
            last_move = self.history[-1]
            if last_move["type"] == "move":
                self.move_marker(last_move["index"], last_move["x"], last_move["y"])
            elif last_move["type"] == "append":
                self.remove_marker(last_move["index"])
            del self.history[-1]

    def update_markers(self, x_data, y_data):
        self.markers.set_xdata(x_data)
        self.markers.set_ydata(y_data)
        self.draw()
        self.ax.draw_artist(self.markers)
        self.update()

    def move_marker(self, i, x, y):
        xdata, ydata = self.markers.get_data()
        xdata[i], ydata[i] = x, y
        self.update_markers(xdata, ydata)

    def append_marker(self, x, y):
        new_xy = np.vstack((self.markers.get_xydata(), np.array([[x, y]])))
        self.update_markers(new_xy[:, 0], new_xy[:, 1])

    def remove_marker(self, i):
        new_xy = np.delete(self.markers.get_xydata(), i, axis=0)
        self.update_markers(new_xy[:, 0], new_xy[:, 1])

    def on_click(self, event):
        if event.dblclick:
            if event.button == 1:
                self.append_to_history("append", -1)
                self.append_marker(event.xdata, event.ydata)
            elif event.button == 3:
                self.undo()

        # Single Click
        elif event.button == 1:
            x, y = event.x, event.y
            xydata = self.ax.transData.transform(self.markers.get_xydata())
            xdata, ydata = xydata.T
            r = ((xdata - x) ** 2 + (ydata - y) ** 2) ** 0.5
            if np.min(r) < self.msize:
                index = np.where(r == r.min())[0][0]
                x_i, y_i = self.markers.get_xydata()[index]
                self.append_to_history("move", index, x_i, y_i)
                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()
                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:
                self.move_marker(self.draggable, event.xdata, event.ydata)

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