0

I have built a simple matplotlib plotting GUI in python using tkinter. Now I would like to to ginput to obtain coordinates from the plot. Unfortunately I keep receiving the following errors:

AttributeError: 'FigureCanvasTkAgg' object has no attribute 'manager'
Figure.show works only for figures managed by pyplot, normally created by pyplot.figure().

It seems that ginput doesn't work well with tkinter, I can get this code to work just fine if I don't use tkinter and simply produce a plot window from the command line.

I know there is an alternative way to do this sort of thing with event picking functions from matplotlib but I can't seem to translate the examples I've seen towards this functionality. Can anyone walk me through this?

import numpy as np
from matplotlib.pyplot import *
from Tkinter import *
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2TkAgg


class App:

    def __init__(self, master):

        global frame
        frame = Frame(master)
        frame.pack(side=TOP)   

        self.quitbutton = Button(frame, text="QUIT", fg="red", command=frame.quit)
        self.quitbutton.grid(row=4,column=0,sticky=W)       

        self.hi_there = Button(frame, text="LOAD",command=self.loadfile)
        self.hi_there.grid(row=0,column=0,sticky=W)   

        self.cutbutton = Button(frame, text="CUT", fg="purple",command=self.cut)
        self.cutbutton.grid(row=1,column=0,sticky=W)        

        global canvas, ax, f

        f = Figure(figsize=(20,4))
        ax = f.add_subplot(111)
        ax.set_xlabel('Time(s)',fontsize=20)
        ax.set_ylabel('Current(nA)',fontsize=20)
        canvas = FigureCanvasTkAgg(f, master=root)
        canvas.show()
        canvas.get_tk_widget().pack(side=BOTTOM, fill=BOTH, expand=1)

        toolbar1 = NavigationToolbar2TkAgg( canvas, root )
        toolbar1.update()
        f.tight_layout()


    def loadfile(self):
        ax.clear()
        self.data=rand(1000)
        self.t=np.arange(0,len(self.data))       
        ##############################################plot data
        self.baseline=np.median(self.data)  
        self.var=2*std(self.data)
        ax.plot(self.t,self.data,'b')

        ax.set_xlabel('Time(s)',fontsize=20)
        ax.set_ylabel('Current(nA)',fontsize=20)
        ax.axhline(linewidth=2, y=self.baseline, color='g')
        ax.set_xlabel('Time(s)',fontsize=20)
        ax.set_ylabel('Current(nA)',fontsize=20)
        canvas.draw()

    def cut(self): 
        pts=np.array(f.ginput(2))
        pts=pts[:,0]
        print pts
        self.data=np.delete(self.data,pts)

        ax.clear()
        self.baseline=np.median(self.data)    
        self.t=np.arange(0,len(self.data))
        ax.plot(self.t,self.data,'b')
        ax.axhline(linewidth=2, y=self.baseline, color='g')
        canvas.draw()




root = Tk()

app = App(root)

root.mainloop()
root.destroy()

EDIT: it seems that the problem is in the distinction between Figure and figure

ginput seems to work with figure() but not Figure(). And tkinter only works with Figure...

is there a workaround for this?

user3495874
  • 23
  • 1
  • 8

2 Answers2

2

Once you are inside python/Tkinter .mainloop(), base your Tkinter GUI input on mouse-click Event, rather than on MATLAB/pyplot .ginput()

No serious control-system ( and the Tkinter .mainloop() is a pretty serious one ) likes to handover it's control-reins to any other, competing ( == blocking ) sub-system.

Tested to work with:

class App( Frame ):                                          # The user interface:

    def __init__( self, master = None ):

        Frame.__init__( self, master )
        self.fig        = Figure( ( 6, 6 ), dpi = 100 )    
        canvas          = FigureCanvasTkAgg( self.fig, master = self )
        # ---------------------------------------------------# cover Tk.root
        self.bind(                     "<Button-1>",         # Tk Event type
                                       self.showXY_handler   #    handler 2 call
                                       )
        # ----------------------------------------------------------------------
        canvas.get_tk_widget().grid(   row        = 0,       # this adds a plot
                                       column     = 0,       # on Tk.root .grid()
                                       columnspan = 4        # geometry manager
                                       )
        # ---------------------------------------------------cover graph area
        canvas.get_tk_widget().bind(   "<Button-1>",
                                       self.showXY_handler
                                       )
        # -----------------------------------------------------------


     #FINALLY THE INTERFACE TO A DEMO-HANDLER METHOD:
     def showXY_handler( self,  aHandledEVENT ):
         print aHandledEVENT.x, aHandledEVENT.y

You may also rather use Class instance variables rather than global-s

# global canvas, ax, f                  # rather to be avoided use of global-s
self.canvas =                           # instance variables, Class-wide visible  
self.ax     =
self.f      =
user3666197
  • 1
  • 6
  • 50
  • 92
  • Very helpful! thank you. Is there any way that I can have it return the plot coordinate instead of the canvas coordinate? Or is there some reproduceable way to convert between the two? – user3495874 Sep 29 '14 at 15:50
  • Thought about this: Either, calibrate your scale/coordinate-system conversion on a pair of known matplotlib points ( [0,0] and axis-ranges ), or in case you need not free-pointing function, but to have an ad-hoc lookup-function to find a nearest point ( within a snap-area around a click ), the calculation strategy will run a minimiser function on a distance between a click-position ( transformed into a matplotlib graph-coordinate-system ) and all subsequent discrete plot-data nodes --again in the matplotlib-GCS ( still accessible in matplotlib data-structures ) and thus having 'em "exact" – user3666197 Sep 29 '14 at 16:27
  • ah thanks for all the helpful tips! It's a shame that there doesnt seem to be an easy way to access the cursor coordinates that are given in the matplotlib toolbar – user3495874 Sep 29 '14 at 17:14
0

Using of such syntax as from module import * isn't good practice for python, as I know. It's better to import full names.

And also you do not have imports for rand and std, either. And I suppose, it'll be better not to use global variables in your case. And may be not rand(1000), but np.random.rand(1000)?

You can try another way for interface with MATLAB, such as pyqt which I tried at my project.

I suppose, this matplotlib's issue can help you mpl gitgub. But you should add smth like matplotlib.use('tkagg').

Marcus Rickert
  • 4,138
  • 3
  • 24
  • 29
yavalvas
  • 330
  • 2
  • 17
  • There are some other reasons for importing just the needed module components, so as to speed-up `namespace` searches on call. There is a big time-saving to call `np_random_rand( 10 )` after a `from numpy.random import rand as np_random_rand` as opposed to call `np.random.rand( 10 )` after just `import numpy as np` as each "dot" means a "jump" in a name-space search for the target function that shall be called (once found). – user3666197 Sep 26 '14 at 21:28
  • Having tested the difference in calling syntax, the difference may surprise to consume **as much as +10% of the time wasted** just on namespace re-searching. Worth a time to take care, isn't it? – user3666197 Sep 26 '14 at 22:45