Python 如何使用tkinter和matplotlib从绘图中选择点而不关闭绘图

Python 如何使用tkinter和matplotlib从绘图中选择点而不关闭绘图,python,matplotlib,tkinter,Python,Matplotlib,Tkinter,我试图获得一些使用Tkinter制作应用程序的经验。该实践应用程序将在两个面板的绘图中显示一些数据,有一个按钮生成新数据,并能够选择和突出显示数据区域。就在最后一部分,我被卡住了。我有一个类PointSelector,它将两个轴axarr的列表作为输入,一个要选择的整数将被高亮显示ax2select、figure fig、canvas canv、高亮显示NumRanges的范围以及数据xdat和ydat 除非在mpl_连接线之后使用命令plt.show,否则代码不会等待click事件。但是如果我

我试图获得一些使用Tkinter制作应用程序的经验。该实践应用程序将在两个面板的绘图中显示一些数据,有一个按钮生成新数据,并能够选择和突出显示数据区域。就在最后一部分,我被卡住了。我有一个类PointSelector,它将两个轴axarr的列表作为输入,一个要选择的整数将被高亮显示ax2select、figure fig、canvas canv、高亮显示NumRanges的范围以及数据xdat和ydat

除非在mpl_连接线之后使用命令plt.show,否则代码不会等待click事件。但是如果我使用plt.show,会出现一个我不想要的新绘图窗口,matplotlib将阻塞,直到所有窗口都关闭。我希望在大腿高亮显示完成后窗口保持打开状态

是否有一种方法可以在调用mpl_断开连接并且不显示第二个窗口(即使用plt.plot)时释放matplotlib以避免阻塞?或者有没有一种方法可以在不使用命令plt.show的情况下使用matplotlib的事件系统

import Tkinter as tk
import numpy as np
import matplotlib
matplotlib.use('TkAgg')

from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import matplotlib.pyplot as plt
import tkFileDialog 

LARGE_FONT = ("Verdana", 12)


class PointSelector():
'''
classdocs
'''
def __init__(self, axarr, ax2select, fig, canv, NumRanges, xdat, ydat):
    '''
    Constructor
    '''
    self.axarr = axarr
    self.ax2select = ax2select
    self.fig = fig
    self.canv = canv
    self.NumRanges = NumRanges
    SelectedPoints = []
    self.Ranges = []
    self.xdat = xdat
    self.ydat = ydat
    self.Nselected = 0
    self.SelectedL = False
    self.SelectedR = False
    self.LeftArr = []
    self.RightArr = []
    self.cid = canv.mpl_connect('button_press_event', self.onclick)
#         plt.ion()
#         plt.show()
    canv.show()




    print 'Done With ALL!'


    def PlotRange(self,rng):
        x = self.xdat[range(rng[0],rng[1]+1)]
        y = self.ydat[range(rng[0],rng[1]+1)]
        self.axarr[self.ax2select].plot(x,y,color='r')

        self.canv.draw()

    def PlotPoint(self,x,y):
        self.axarr[self.ax2select].scatter(x,y,color='r')
        self.canv.draw()

    def onclick(self, event):
        print('button=%d, x=%d, y=%d, xdata=%f, ydata=%f' %
      (event.button, event.x, event.y, event.xdata, event.ydata))

        if event.inaxes == self.axarr[self.ax2select]:

            xval = np.argmin(np.abs(event.xdata-self.xdat))
            if self.SelectedL:
                self.RightArr.append(xval)
                self.SelectedR = True
                self.PlotPoint(self.xdat[xval], self.ydat[xval])
            else:
                self.LeftArr.append(xval)
                self.SelectedL = True
                self.PlotPoint(self.xdat[xval], self.ydat[xval])

            if self.SelectedL and self.SelectedR:
                self.SelectedL = False
                self.SelectedR = False
                self.Nselected += 1
                self.PlotRange([self.LeftArr[-1], self.RightArr[-1]])
                if self.Nselected == self.NumRanges:
                    for i in range(len(self.LeftArr)):
                        self.Ranges.append([self.LeftArr[i], self.RightArr[i]])
                    self.canv.mpl_disconnect(self.cid)
                    print 'Done With Selection'


        else:
            print 'Outside Window'

class Driver(tk.Tk):
'''
classdocs
'''
    def __init__(self, *args, **kwargs):
    '''
    Constructor
    '''
        tk.Tk.__init__(self, *args, **kwargs)
        container = tk.Frame(self)
        container.pack(side="top", fill="both",expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)
        self.frames = dict()
        F = StartPage
        frame = F(container, self)
        self.frames[F] = frame
        frame.grid(row=0,column=0,sticky="nsew")
        self.show_frame(StartPage)
    def show_frame(self, cont):
        frame = self.frames[cont]
        frame.tkraise()

class StartPage(tk.Frame):
'''
classdocs
'''


    def __init__(self, parent, controller):
    '''
    Constructor
    '''
        tk.Frame.__init__(self, parent)
        self.controller = controller
        label = tk.Label(self,text="Start Page", font=LARGE_FONT)
        label.pack(pady=10,padx=10)

        quitButton = tk.Button(self, text="Quit",command=self._quit)
        quitButton.pack(side="bottom")



        NewDataButton = tk.Button(self, text="Get New Data",command=self.GetNewData)
        NewDataButton.pack(side="top")


        menu = tk.Menu(parent)
        controller.config(menu=menu)

        submenuFile = tk.Menu(menu)
        menu.add_cascade(label="File",menu=submenuFile)
        submenuFile.add_command(label='Open File', command=self.onOpen)
        submenuFile.add_command(label='Load Configuration File', command=self.onOpen)
        submenuFile.add_separator()

        submenuFile.add_command(label='Exit', command=self._quit)




        submenuContinuum = tk.Menu(menu)
        menu.add_cascade(label="Continuum",menu=submenuContinuum)



        canvas_width = 100
        canvas_height = 100
        canv = tk.Canvas(self,width=canvas_width,height=canvas_height)
        canv.pack()

        self.x = np.linspace(0.0,4.0*np.pi,100)

        self.fig = plt.figure(figsize=(6,6))

        self.axarr = []
        self.axarr.append(plt.subplot(211))
        self.axarr.append(plt.subplot(212))


        self.canv = FigureCanvasTkAgg(self.fig,master=self)
        self.canv.show()
        self.canv.get_tk_widget().pack()
        self.canv._tkcanvas.pack(side=tk.TOP,fill=tk.BOTH,expand=1)
        self.GetNewData()

        self.txt=tk.Text(self)
        submenuContinuum.add_command(label='Select Continuum', 
                                 command=lambda:PointSelector(self.axarr, 0, self.fig, self.canv, 2, 
                                                            self.x, self.y))
        submenuContinuum.add_command(label='Remove Continuum', command=donothing)

    def GetNewData(self):
        rnum = (np.random.rand(1))
        print rnum
        self.y = np.sin(self.x)*(np.random.rand(1)*4.0)*(self.x)**rnum
        self.dy = np.gradient(self.y, self.x[1]-self.x[0])
        self.DrawData()
    def DrawData(self):
        self.axarr[0].clear()
        self.axarr[1].clear()
        self.axarr[0].plot(self.x,self.y)
        self.axarr[1].plot(self.x,self.dy)

        self.canv.draw()

    def onOpen(self):

        ftypes = [('Python files', '*.py'), ('All files', '*')]
        dlg = tkFileDialog.Open(self, filetypes = ftypes)
        fl = dlg.show()

        if fl != '':
            text = self.readFile(fl)
            self.txt.insert('end', text)

    def readFile(self, filename):

        f = open(filename, "r")
        text = f.read()
        return text

    def _quit(self):
        self.controller.quit()
        self.controller.destroy()

def donothing():
    print "nothing"


if __name__ == '__main__':
    app = Driver()
    app.mainloop()

首先,您不想在GUI中使用plt.show或plt.ion是正确的,因为它们会干扰GUI窗口。 您可能还想在PointSelector的初始化方法中去掉canv.show,因为我不知道它应该在那里做什么,并且_init__;不是程序中需要重新绘制内容的方法

因此,假设点选择器在其他方面很好,实际问题来自于线

.add_command(label='...', command=lambda:PointSelector(...))
在匿名函数中实例化。问题是你现在无法在你的程序中使用这个实例,一旦它的初始化完成,它就会丢失

与此同时出现的问题是,您失去了对matplotlib回调的引用。 matplotlib声明:

画布只保留对回调的弱引用。因此,如果回调是类实例的方法,则需要保留对该实例的引用。否则实例将被垃圾收集,回调将消失

因此,为了能够在程序中使用PointSelector,您需要将其分配给类变量,例如self.selector=PointSelector。。。这可以在菜单中称为命令的方法内完成

可能的解决方案如下所示:

class StartPage(tk.Frame):

    #...

    def __init__(self, parent, controller):
        submenuContinuum.add_command(label='Select Continuum', 
                                 command=self.registerSelector)
        submenuContinuum.add_command(label='Remove Continuum', command=donothing)

    def registerSelector(self):
        self.selector = PointSelector(self.axarr, 0, self.fig, 
                                      self.canv, 2, self.x, self.y)

首先,您不想在GUI中使用plt.show或plt.ion是正确的,因为它们会干扰GUI窗口。 您可能还想在PointSelector的初始化方法中去掉canv.show,因为我不知道它应该在那里做什么,并且_init__;不是程序中需要重新绘制内容的方法

因此,假设点选择器在其他方面很好,实际问题来自于线

.add_command(label='...', command=lambda:PointSelector(...))
在匿名函数中实例化。问题是你现在无法在你的程序中使用这个实例,一旦它的初始化完成,它就会丢失

与此同时出现的问题是,您失去了对matplotlib回调的引用。 matplotlib声明:

画布只保留对回调的弱引用。因此,如果回调是类实例的方法,则需要保留对该实例的引用。否则实例将被垃圾收集,回调将消失

因此,为了能够在程序中使用PointSelector,您需要将其分配给类变量,例如self.selector=PointSelector。。。这可以在菜单中称为命令的方法内完成

可能的解决方案如下所示:

class StartPage(tk.Frame):

    #...

    def __init__(self, parent, controller):
        submenuContinuum.add_command(label='Select Continuum', 
                                 command=self.registerSelector)
        submenuContinuum.add_command(label='Remove Continuum', command=donothing)

    def registerSelector(self):
        self.selector = PointSelector(self.axarr, 0, self.fig, 
                                      self.canv, 2, self.x, self.y)
工作Python 3代码:

#copied from https://stackoverflow.com/questions/44357892/how-to-use-tkinter-and-matplotlib-to-select-points-from-a-plot-without-closing-i
#converted to python 3
import tkinter as tk
import numpy as np
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

import matplotlib.pyplot as plt

LARGE_FONT = ("Verdana", 12)

class PointSelector():
    """
    classdocs
    """
    def __init__(self, axarr, ax2select, fig, canv, NumRanges, xdat, ydat):
        """
        Constructor
        """
        self.axarr = axarr
        self.ax2select = ax2select
        self.fig = fig
        self.canv = canv
        self.NumRanges = NumRanges
        self.Ranges = []
        self.xdat = xdat
        self.ydat = ydat
        self.Nselected = 0
        self.SelectedL = False
        self.SelectedR = False
        self.LeftArr = []
        self.RightArr = []
        self.cid = canv.mpl_connect('button_press_event', self.onclick)
        print('Done With ALL!')


    def PlotRange(self,rng):
        x = self.xdat[range(rng[0],rng[1]+1)]
        y = self.ydat[range(rng[0],rng[1]+1)]
        self.axarr[self.ax2select].plot(x,y,color='r')

        self.canv.draw()

    def PlotPoint(self,x,y):
        self.axarr[self.ax2select].scatter(x,y,color='r')
        self.canv.draw()

    def onclick(self, event):
        print('button=%d, x=%d, y=%d, xdata=%f, ydata=%f' % (event.button, event.x, event.y, event.xdata, event.ydata))

        if event.inaxes == self.axarr[self.ax2select]:

            xval = np.argmin(np.abs(event.xdata-self.xdat))
            if self.SelectedL:
                self.RightArr.append(xval)
                self.SelectedR = True
                self.PlotPoint(self.xdat[xval], self.ydat[xval])
            else:
                self.LeftArr.append(xval)
                self.SelectedL = True
                self.PlotPoint(self.xdat[xval], self.ydat[xval])

            if self.SelectedL and self.SelectedR:
                self.SelectedL = False
                self.SelectedR = False
                self.Nselected += 1
                self.PlotRange([self.LeftArr[-1], self.RightArr[-1]])
                if self.Nselected == self.NumRanges:
                    for i in range(len(self.LeftArr)):
                        self.Ranges.append([self.LeftArr[i], self.RightArr[i]])
                    self.canv.mpl_disconnect(self.cid)
                    print('Done With Selection')

        else:
            print('Outside Window')

class Driver(tk.Tk):
    """
    classdocs
    """
    def __init__(self, *args, **kwargs):
        """
        Constructor
        """
        tk.Tk.__init__(self, *args, **kwargs)
        container = tk.Frame(self)
        container.pack(side="top", fill="both",expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)
        self.frames = dict()
        F = StartPage
        frame = F(container, self)
        self.frames[F] = frame
        frame.grid(row=0,column=0,sticky="nsew")
        self.show_frame(StartPage)
    def show_frame(self, cont):
        frame = self.frames[cont]
        frame.tkraise()

class StartPage(tk.Frame):
    """
    classdocs
    """
    def __init__(self, parent, controller):
        """
        Constructor
        """
        tk.Frame.__init__(self, parent)
        self.controller = controller
        label = tk.Label(self,text="Start Page", font=LARGE_FONT)
        label.pack(pady=10,padx=10)

        quitButton = tk.Button(self, text="Quit",command=self._quit)
        quitButton.pack(side="bottom")

        NewDataButton = tk.Button(self, text="Get New Data",command=self.GetNewData)
        NewDataButton.pack(side="top")

        menu = tk.Menu(parent)
        controller.config(menu=menu)

        submenuFile = tk.Menu(menu)
        menu.add_cascade(label="File",menu=submenuFile)
        submenuFile.add_command(label='Open File', command=self.onOpen)
        submenuFile.add_command(label='Load Configuration File', command=self.onOpen)
        submenuFile.add_separator()

        submenuFile.add_command(label='Exit', command=self._quit)

        submenuContinuum = tk.Menu(menu)
        menu.add_cascade(label="Continuum",menu=submenuContinuum)

        canvas_width = 100
        canvas_height = 100
        canv = tk.Canvas(self,width=canvas_width,height=canvas_height)
        canv.pack()

        self.x = np.linspace(0.0,4.0*np.pi,100)
        self.fig = plt.figure(figsize=(6,6))
        self.axarr = []
        self.axarr.append(plt.subplot(211))
        self.axarr.append(plt.subplot(212))
        self.canv = FigureCanvasTkAgg(self.fig,master=self)
        self.canv.get_tk_widget().pack()
        self.canv._tkcanvas.pack(side=tk.TOP,fill=tk.BOTH,expand=1)
        self.GetNewData()

        self.txt=tk.Text(self)
        submenuContinuum.add_command(label='Select Continuum', command=self.registerSelector)
        submenuContinuum.add_command(label='Remove Continuum', command=donothing)

    def registerSelector(self):
        self.selector = PointSelector(self.axarr, 0, self.fig, self.canv, 2, self.x, self.y)

    def GetNewData(self):
        rnum = (np.random.rand(1))
        print(rnum)
        self.y = np.sin(self.x)*(np.random.rand(1)*4.0)*(self.x)**rnum
        self.dy = np.gradient(self.y, self.x[1]-self.x[0])
        self.DrawData()
    def DrawData(self):
        self.axarr[0].clear()
        self.axarr[1].clear()
        self.axarr[0].plot(self.x,self.y)
        self.axarr[1].plot(self.x,self.dy)
        self.canv.draw()

    def onOpen(self):
        ftypes = [('Python files', '*.py'), ('All files', '*')]
        dlg = tk.filedialog.Open(self, filetypes=ftypes)
        fl = dlg.show()

        if fl != '':
            text = self.readFile(fl)
            self.txt.insert('end', text)

    def readFile(self, filename):
        f = open(filename, "r")
        text = f.read()
        return text

    def _quit(self):
        self.controller.quit()
        self.controller.destroy()

def donothing():
    print("nothing")

if __name__ == '__main__':
    app = Driver()
    app.mainloop()
工作Python 3代码:

#copied from https://stackoverflow.com/questions/44357892/how-to-use-tkinter-and-matplotlib-to-select-points-from-a-plot-without-closing-i
#converted to python 3
import tkinter as tk
import numpy as np
import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg

import matplotlib.pyplot as plt

LARGE_FONT = ("Verdana", 12)

class PointSelector():
    """
    classdocs
    """
    def __init__(self, axarr, ax2select, fig, canv, NumRanges, xdat, ydat):
        """
        Constructor
        """
        self.axarr = axarr
        self.ax2select = ax2select
        self.fig = fig
        self.canv = canv
        self.NumRanges = NumRanges
        self.Ranges = []
        self.xdat = xdat
        self.ydat = ydat
        self.Nselected = 0
        self.SelectedL = False
        self.SelectedR = False
        self.LeftArr = []
        self.RightArr = []
        self.cid = canv.mpl_connect('button_press_event', self.onclick)
        print('Done With ALL!')


    def PlotRange(self,rng):
        x = self.xdat[range(rng[0],rng[1]+1)]
        y = self.ydat[range(rng[0],rng[1]+1)]
        self.axarr[self.ax2select].plot(x,y,color='r')

        self.canv.draw()

    def PlotPoint(self,x,y):
        self.axarr[self.ax2select].scatter(x,y,color='r')
        self.canv.draw()

    def onclick(self, event):
        print('button=%d, x=%d, y=%d, xdata=%f, ydata=%f' % (event.button, event.x, event.y, event.xdata, event.ydata))

        if event.inaxes == self.axarr[self.ax2select]:

            xval = np.argmin(np.abs(event.xdata-self.xdat))
            if self.SelectedL:
                self.RightArr.append(xval)
                self.SelectedR = True
                self.PlotPoint(self.xdat[xval], self.ydat[xval])
            else:
                self.LeftArr.append(xval)
                self.SelectedL = True
                self.PlotPoint(self.xdat[xval], self.ydat[xval])

            if self.SelectedL and self.SelectedR:
                self.SelectedL = False
                self.SelectedR = False
                self.Nselected += 1
                self.PlotRange([self.LeftArr[-1], self.RightArr[-1]])
                if self.Nselected == self.NumRanges:
                    for i in range(len(self.LeftArr)):
                        self.Ranges.append([self.LeftArr[i], self.RightArr[i]])
                    self.canv.mpl_disconnect(self.cid)
                    print('Done With Selection')

        else:
            print('Outside Window')

class Driver(tk.Tk):
    """
    classdocs
    """
    def __init__(self, *args, **kwargs):
        """
        Constructor
        """
        tk.Tk.__init__(self, *args, **kwargs)
        container = tk.Frame(self)
        container.pack(side="top", fill="both",expand=True)
        container.grid_rowconfigure(0, weight=1)
        container.grid_columnconfigure(0, weight=1)
        self.frames = dict()
        F = StartPage
        frame = F(container, self)
        self.frames[F] = frame
        frame.grid(row=0,column=0,sticky="nsew")
        self.show_frame(StartPage)
    def show_frame(self, cont):
        frame = self.frames[cont]
        frame.tkraise()

class StartPage(tk.Frame):
    """
    classdocs
    """
    def __init__(self, parent, controller):
        """
        Constructor
        """
        tk.Frame.__init__(self, parent)
        self.controller = controller
        label = tk.Label(self,text="Start Page", font=LARGE_FONT)
        label.pack(pady=10,padx=10)

        quitButton = tk.Button(self, text="Quit",command=self._quit)
        quitButton.pack(side="bottom")

        NewDataButton = tk.Button(self, text="Get New Data",command=self.GetNewData)
        NewDataButton.pack(side="top")

        menu = tk.Menu(parent)
        controller.config(menu=menu)

        submenuFile = tk.Menu(menu)
        menu.add_cascade(label="File",menu=submenuFile)
        submenuFile.add_command(label='Open File', command=self.onOpen)
        submenuFile.add_command(label='Load Configuration File', command=self.onOpen)
        submenuFile.add_separator()

        submenuFile.add_command(label='Exit', command=self._quit)

        submenuContinuum = tk.Menu(menu)
        menu.add_cascade(label="Continuum",menu=submenuContinuum)

        canvas_width = 100
        canvas_height = 100
        canv = tk.Canvas(self,width=canvas_width,height=canvas_height)
        canv.pack()

        self.x = np.linspace(0.0,4.0*np.pi,100)
        self.fig = plt.figure(figsize=(6,6))
        self.axarr = []
        self.axarr.append(plt.subplot(211))
        self.axarr.append(plt.subplot(212))
        self.canv = FigureCanvasTkAgg(self.fig,master=self)
        self.canv.get_tk_widget().pack()
        self.canv._tkcanvas.pack(side=tk.TOP,fill=tk.BOTH,expand=1)
        self.GetNewData()

        self.txt=tk.Text(self)
        submenuContinuum.add_command(label='Select Continuum', command=self.registerSelector)
        submenuContinuum.add_command(label='Remove Continuum', command=donothing)

    def registerSelector(self):
        self.selector = PointSelector(self.axarr, 0, self.fig, self.canv, 2, self.x, self.y)

    def GetNewData(self):
        rnum = (np.random.rand(1))
        print(rnum)
        self.y = np.sin(self.x)*(np.random.rand(1)*4.0)*(self.x)**rnum
        self.dy = np.gradient(self.y, self.x[1]-self.x[0])
        self.DrawData()
    def DrawData(self):
        self.axarr[0].clear()
        self.axarr[1].clear()
        self.axarr[0].plot(self.x,self.y)
        self.axarr[1].plot(self.x,self.dy)
        self.canv.draw()

    def onOpen(self):
        ftypes = [('Python files', '*.py'), ('All files', '*')]
        dlg = tk.filedialog.Open(self, filetypes=ftypes)
        fl = dlg.show()

        if fl != '':
            text = self.readFile(fl)
            self.txt.insert('end', text)

    def readFile(self, filename):
        f = open(filename, "r")
        text = f.read()
        return text

    def _quit(self):
        self.controller.quit()
        self.controller.destroy()

def donothing():
    print("nothing")

if __name__ == '__main__':
    app = Driver()
    app.mainloop()

谢谢,这解决了它。如果这解决了问题,你可以这样做,这样问题就不会一直没有解决。谢谢,这解决了它。如果这解决了问题,你可以这样做,这样问题就不会一直没有解决。