Tkinter中的交互式PyPlot图未注册MouseEvent

时间:2018-07-14 15:56:48

标签: python matplotlib tkinter mouseevent interactive

我正在构建一个交互式Tkinter GUI,其中在GUI中绘制了空白的pyplot图形和轴以及一些按钮,用户可以在其中单击,拖动和删除点以形成自定义点图。然后,这些点的坐标就可以以一种更为复杂的Fortran代码用作输入的特定格式进行打印。除了图形/轴空间的初始交互性之外,我几乎已完成所有工作。我严重依赖于我在yuma-m用户在GitHub上找到的精彩的Draggable-Plot对象代码,链接如下:

https://github.com/yuma-m/matplotlib-draggable-plot/blob/master/draggable_plot.py

在对原始的Draggable-Plot对象进行了大量调整之后,我能够将交互式绘图集成到我的GUI中。但是,当我第一次生成绘图时,该错误会出现。设置正确的轴边界后,并第一次单击“更新轴”,将绘制图形和曲线,但不要注册任何MouseEvent。我的猜测是,当在event.inaxes in [self._axes]函数中检查_on_click条件时,self._axes的存在/放置被某种方式阻止了。

最好的情况是第二次单击“更新轴”按钮,并且在第一个轴的正下方绘制了一个新的轴对象。发生这种情况时,脚本将开始在INITIAL图中注册MouseEvent,但是将在新的SECOND图中绘制所有对应的点。当我将第二个图的位置限制在与第一个图相同的网格位置时,没有交互性被记录,因为我猜测新轴与第一个图重叠。

我只是在寻找解决这个奇怪问题的方法;显然,此GUI的理想功能是最初生成的绘图的初始交互性,任何后续生成的轴也具有相同的功能。谢谢!

Image of Two Axes state of GUI

import math
import matplotlib

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg

import matplotlib.pyplot as plt
import matplotlib.animation as animation

import tkinter as tk
from tkinter import scrolledtext
from tkinter import *
from tkinter.ttk import *

class B2PtGen(tk.Tk): 

def __init__(self):

    root = Tk()

    root.title("B2 Inputfile Point Generator")
    root.geometry("800x800")

    app = Frame(root)
    app.grid()

    self._Xmin = -0.1
    self._Xmax = 0.1
    self._Ymin = 0
    self._Ymax = 1
    self._figure, self._axes, self._line = None, None, None
    self._dragging_point = None
    self._points = {}

    Instr = Label(app,text = "Enter ranges of X and Y axes in corresponding text boxes below, then click 'Update Axes'. \nUse plot area to draw shape of Inputfile Curve \n(Left Mouse Button to create points, Right Mouse Button to delete points). \nThen click 'Generate Point List' to create Inputfile Point List")
    Instr.grid(column=0,row=0,columnspan=4)

    Lbl1 = Label(app, text = "X min")
    Lbl1.grid(column=0,row=1)

    XminT = Entry(app, width=10)
    XminT.insert(0,'-0.1')
    XminT.grid(column=0,row=2)

    Lbl2 = Label(app, text = "X max")
    Lbl2.grid(column=1,row=1)

    XmaxT = Entry(app, width=10)
    XmaxT.insert(0,'0.1')
    XmaxT.grid(column=1,row=2)

    Lbl3 = Label(app, text = "Y min")
    Lbl3.grid(column=0,row=3)

    YminT = Entry(app, width=10)
    YminT.insert(0,'0')
    YminT.grid(column=0,row=4)

    Lbl4 = Label(app, text = "Y max")
    Lbl4.grid(column=1,row=3)

    YmaxT = Entry(app, width=10)
    YmaxT.insert(0,'1')
    YmaxT.grid(column=1,row=4)

    def clicked():
        if float(XminT.get()) < float(XmaxT.get()) and float(YminT.get()) < float(YmaxT.get()): 
            self._Xmin = float(XminT.get())
            self._Xmax = float(XmaxT.get())
            self._Ymin = float(YminT.get())
            self._Ymax = float(YmaxT.get())
            Lbl1.configure(text = "Xmin = " + XminT.get())
            Lbl2.configure(text = "Xmax = " + XmaxT.get())
            Lbl3.configure(text = "Ymin = " + YminT.get())
            Lbl4.configure(text = "Ymax = " + YmaxT.get())

            self._init_plot(app)         

        else:
            print("Input values do not form valid ranges")                  

    button1 = Button(app, command=clicked)
    button1.grid(column=2,row=2,columnspan=2)
    button1['text'] = "Update Axes"

    root.mainloop() 

def _init_plot(self, app):
    if not self._figure:
        self._figure = plt.figure(num=1)

    if not self._axes:
        print('New Axes!')
        self._axes = plt.axes()

    plt.sca(self._axes)    
    self._axes.set_xlim(self._Xmin, self._Xmax)
    self._axes.set_xlabel('Radial Distance from Separatrix (along Outer Midplane) [m]')
    self._axes.set_ylabel('Normalized Coefficient Magnitude')
    self._axes.set_ylim(self._Ymin, self._Ymax)
    self._axes.grid(b=True,which="both")
    #self._axes = axes

    self._figure.canvas.mpl_connect('button_press_event', self._on_click)
    self._figure.canvas.mpl_connect('button_release_event', self._on_release)
    self._figure.canvas.mpl_connect('motion_notify_event', self._on_motion)

    canvas = FigureCanvasTkAgg(self._figure, app)
    canvas.show()
    canvas.get_tk_widget().grid(columnspan=4)

def _update_plot(self):
    if not self._points:
        return
    x, y = zip(*sorted(self._points.items()))
    # Add new plot
    if not self._line:
        self._line, = self._axes.plot(x, y, "b", marker="o", markersize=5)
    # Update current plot
    else:
        self._line.set_data(x, y)
    self._figure.canvas.draw()

def _add_point(self, x, y=None):
    if isinstance(x, MouseEvent):
        x, y = float(x.xdata), float(x.ydata)
    self._points[x] = y
    return x, y

def _remove_point(self, x, _):
    if x in self._points:
        self._points.pop(x)

def _find_neighbor_point(self, event):
    u""" Find point around mouse position
    :rtype: ((int, int)|None)
    :return: (x, y) if there are any point around mouse else None
    """
    distance_threshold = 0.05*(self._Ymax - self._Ymin)
    nearest_point = None
    min_distance = math.sqrt((self._Xmax - self._Xmin)**2 + (self._Ymax - self._Ymin)**2)
    for x, y in self._points.items():
        distance = math.hypot(event.xdata - x, event.ydata - y)
        if distance < min_distance:
            min_distance = distance
            nearest_point = (x, y)
    if min_distance < distance_threshold:
        return nearest_point
    return None

def _on_click(self, event):
    u""" callback method for mouse click event
    :type event: MouseEvent
    """
    # left click
    if event.button == 1 and event.inaxes in [self._axes]:
        point = self._find_neighbor_point(event)
        if point:
            self._dragging_point = point
            self._remove_point(*point)
        else:
            self._add_point(event)
        print('You clicked!')    
        self._update_plot()
    # right click
    elif event.button == 3 and event.inaxes in [self._axes]:
        point = self._find_neighbor_point(event)
        if point:
            self._remove_point(*point)
            self._update_plot()

def _on_release(self, event):
    u""" callback method for mouse release event
    :type event: MouseEvent
    """
    if event.button == 1 and event.inaxes in [self._axes] and self._dragging_point:
        self._add_point(event)
        self._dragging_point = None
        self._update_plot()

def _on_motion(self, event):
    u""" callback method for mouse motion event
    :type event: MouseEvent
    """
    if not self._dragging_point:
        return
    self._remove_point(*self._dragging_point)
    self._dragging_point = self._add_point(event)
    self._update_plot()

if __name__ == "__main__":
    B2PtGen()

1 个答案:

答案 0 :(得分:0)

尝试将_init_plot()函数更改为以下内容,似乎更新得更好一些...

    if not self._figure:
        self._figure = plt.figure(num=1)
        canvas = FigureCanvasTkAgg(self._figure, app)
        canvas.show()
        canvas.get_tk_widget().grid(columnspan=4)

    if not self._axes:
        print('New Axes!')
        self._axes = plt.axes()

    self._axes.set_xlim(self._Xmin, self._Xmax)
    self._axes.set_xlabel('Radial Distance from Separatrix (along Outer Midplane) [m]')
    self._axes.set_ylabel('Normalized Coefficient Magnitude')
    self._axes.set_ylim(self._Ymin, self._Ymax)
    self._axes.grid(b=True,which="both")
    self._figure.sca(self._axes)

    self._figure.canvas.mpl_connect('button_press_event', self._on_click)
    self._figure.canvas.mpl_connect('button_release_event', self._on_release)
    self._figure.canvas.mpl_connect('motion_notify_event', self._on_motion)

    self._figure.canvas.draw()