1

I am trying to get some experience making an app using Tkinter. This practice app will display some data in a two panel plot, have a button to make new data and have the ability to select and highlight regions of the data. It is on that last part I am getting stuck. I have a class (PointSelector) that takes as inputs a list of the two axes (axarr), an integer to select which will be highlighted (ax2select), the figure (fig), the canvas (canv), # of ranges to highlight (NumRanges) and the data (xdat and ydat).

Unless I use the command plt.show() after the mpl_connect line, the code does not wait for the click event. But if I do use plt.show(), a new plot window comes up (which I don't want) and matplotlib will block until ALL windows are closed (I want the window to stay open after thigh highlighting is complete).

Is there a way to release matplotlib from blocking once the mpl_disconnect is called (and also not have the second window appear i.e. using plt.plot()) ? Or is there a way to use the event system of matplotlib without using the command plt.show()?

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

2 Answers 2

0

First, it is correct that you don't want to use plt.show() or plt.ion() in your GUI, because they would interfere with the GUI window.
You would probably also want to get rid of canv.show() in the PointSelector's initialization method (simply because I don't know what it should do there and the __init__ is not the method in the program that needs to redraws things.).

So assuming the point selector is otherwise fine, the actual problem comes from the line

.add_command(label='...', command=lambda:PointSelector(...))

where it is instantiated in an anonymous function. The problem with this is that you have now no way of using this instance in your program and that it will be lost, once its __init__ is finished.

Together with this comes the problem that you loose the reference to the matplotlib callback.
The matplotlib event handling tutorial states:

The canvas retains only weak references to the callbacks. Therefore if a callback is a method of a class instance, you need to retain a reference to that instance. Otherwise the instance will be garbage-collected and the callback will vanish.

So in order to be able to use the PointSelector in your programm you would need to assign it to a class variable, e.g. self.selector = PointSelector(..). This can be done inside a method which is called as command from the Menu.

A possible solution would then look like:

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)
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks, this fixed it.
If this solves the issue, you may accept it, such that the question will not stay unsolved.
0

Working Python 3 Code:

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.