views:

107

answers:

1

I've looked but didn't find previous questions specific enough, so sorry if this is repeated. Goal: GUI to continuously update figure with different matrix data plotted by pylab's pcolor such that there is a running animation. But user should be able to play, pause, stop animation by Tkinter widget buttons.

Before I get an answer for matplotlib using set_array(), draw(), and canvas.manager.after() ..., I have working code that enables me to start animation, but i can't figure out how to stop or pause it, when just using matplotlib capabilities, so I decided to use Tkinter straigt up instead of matplotlib's Tcl/Tk wrapper. Here is working code just for kicks though in case someone has any ideas. But continue for real question.

# mouse click the "Play" button widget to play animation
# PROBLEMS:
# 1. can't pause or stop animation. once loop starts it cant be broken
# 2. set_array attribute for pcolor PolyCollection object only updates the matrix
# C of pcolor, however for my actual application, I will be using pcolor(x,y,C)
# and will have new x,y, and C per plot. Unlike line object, where set_xdata and
# set_ydata are used, I can't find an analogy to pcolor. If I were updating an
# image I could use set_data(x,y,C), but i am not importing an image. I assume
# pcolor is still my best bet unless (as in Matlab) there is an equivalent
# image(x,y,C) function?

import time as t
from pylab import *
from matplotlib.widgets import Button
ion()
def pressPlay(event):
    #fig=figure()
    ax = subplot(111)
    subplots_adjust(bottom=0.2)
    c=rand(5,5)
    cplot=pcolor(c)
    draw()
    for i in range(5):
        c=rand(5,5)
        cplot.set_array(c.ravel())
        cplot.autoscale()
        title('Ionosphere '+str(i+1))
        t.sleep(3)
        draw()
axPlay = axes([0.7, 0.05, 0.1, 0.075])
bPlay = Button(axPlay, 'Play')
bPlay.on_clicked(pressPlay)

Btw: in importing pylab the TkAgg backend is automatically set for use in matplotlib... i think. Or somehow I automatically use TkAgg. I am running Linux, Python 2.6.4, Ipython 0.10.

I manipulated code found from Daniweb IT Discussion Community so that using Tkinter and the update_idletasks() function I can play, plause, stop the changing colors of a label widget. This can be run on python alone as long as Tkinter is installed. No matplotlib or pylab used. This is working code and the backbone of the final code I question.

# This is meant to draw Start and Stop buttons and a label
# The Start button should start a loop in which the label
# is configured to change color by looping through a color list.
# At each pass through the loop the variable self.stop is checked:
# if True the loop terminates.
# The Stop button terminates the loop by setting the
# variable self.stop to True.
# The Start button restarts the animation from the beginning
# if Stop was hit last, or restarts the animation from where it left off
# if pause was hit last.
# The loop also terminates on the last color of the list, as if stop were hit


from Tkinter import *
colors = ['red','green','blue','orange','brown','black','white','purple','violet']
numcol=len(colors)
class SGWidget(Frame):
    def __init__(self, parent=None):
        Frame.__init__(self, parent)
        self.top_frame = Frame(bg='green')
        self.top_frame.grid()
        # enter event loop until all idle callbacks have been called.
        self.top_frame.update_idletasks()
        self.makeToolbar()
        # construct a label widget with the parent frame top_frame
        self.label = Label(self.top_frame,text = 'Text',bg='orange')
        self.label.grid()
 # initialize (time index t)
        self.t=0

    def makeToolbar(self):
        self.toolbar_text = ['Play','Pause','Stop']
        self.toolbar_length = len(self.toolbar_text)
        self.toolbar_buttons = [None] * self.toolbar_length

        for toolbar_index in range(self.toolbar_length):
            text = self.toolbar_text[toolbar_index]
            bg = 'yellow'
            button_id = Button(self.top_frame,text=text,background=bg)
            button_id.grid(row=0, column=toolbar_index)
            self.toolbar_buttons[toolbar_index] = button_id

            def toolbar_button_handler(event, self=self, button=toolbar_index):
                return self.service_toolbar(button)

            # bind mouse click on start or stop to the toolbar_button_handler
            button_id.bind("<Button-1>", toolbar_button_handler)

    # call blink() if start and set stop when stop            
    def service_toolbar(self, toolbar_index):
        if toolbar_index == 0:
            self.stop = False
            print self.stop
            self.blink()
        elif toolbar_index == 1:
            self.stop = True
            print self.stop
        elif toolbar_index == 2:
            self.stop = True
            print self.stop
            self.t=0

    # while in start, check if stop is clicked, if not, call blink recursivly
    def blink(self):
        if not self.stop:
            print 'looping',self.stop
            self.label.configure(bg=colors[self.t])
            self.t += 1
            if self.t == numcol: # push stop button
                self.service_toolbar(2)
            self.label.update_idletasks()
            self.after(500, self.blink)

if __name__ == '__main__':
    SGWidget().mainloop()

Then, with help from matplotlib example embedding_in_tk.html, http://matplotlib.sourceforge.net/examples/user_interfaces/embedding_in_tk.html, I manipulated the previous code to animate a canvas connected to the pcolor figure. However, updating the canvas with canvas.get_tk_widget() doesn't do the trick I assume because of the command before it that re-plots pcolor(). So I'm guessing I have to reconnect the canvas with the figure every time I re-plot? But I don't know how. I hope I'm even on the right track using update_idletasks()???

So, with the following code, all I see is the same plot when the code is looping through Play, instead of an updated figure? This is my main problem and question.

from pylab import *
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from Tkinter import *

colors=[None]*10
for i in range(len(colors)):
    colors[i]=rand(5,5)
    #colors = ['red','green','blue','orange','brown','black','white','purple','violet']
numcol=len(colors)

class App(Frame):
    def __init__(self,parent=None):
        Frame.__init__(self,parent)
        self.top=Frame()
        self.top.grid()
        self.top.update_idletasks()

        self.makeWidgets()
        self.makeToolbar()

    def makeWidgets(self):
        # figsize (w,h tuple in inches) dpi (dots per inch)
        #f = Figure(figsize=(5,4), dpi=100)
        self.f = Figure()
        self.a = self.f.add_subplot(111)
        self.a.pcolor(rand(5,5))
        # a tk.DrawingArea
        self.canvas = FigureCanvasTkAgg(self.f, master=self.top)
        self.canvas.get_tk_widget().grid(row=3,column=0,columnspan=3)
        self.bClose = Button(self.top, text='Close',command=self.top.destroy)
        self.bClose.grid()
        #self.label = Label(self.top, text = 'Text',bg='orange')
        #self.label.grid()
        # initialize (time index t)
        self.t=0

    def makeToolbar(self):
        self.toolbar_text = ['Play','Pause','Stop']
        self.toolbar_length = len(self.toolbar_text)
        self.toolbar_buttons = [None] * self.toolbar_length

        for toolbar_index in range(self.toolbar_length):
            text = self.toolbar_text[toolbar_index]
            bg = 'yellow'
            button_id = Button(self.top,text=text,background=bg)
            button_id.grid(row=0, column=toolbar_index)
            self.toolbar_buttons[toolbar_index] = button_id

            def toolbar_button_handler(event, self=self, button=toolbar_index):
                return self.service_toolbar(button)

            button_id.bind("<Button-1>", toolbar_button_handler)

    # call blink() if start and set stop when stop            
    def service_toolbar(self, toolbar_index):
        if toolbar_index == 0:
            self.stop = False
            print self.stop
            self.blink()
        elif toolbar_index == 1:
            self.stop = True
            print self.stop
        elif toolbar_index == 2:
            self.stop = True
            print self.stop
            self.t=0

    # while in start, check if stop is clicked, if not, call blink recursivly
    def blink(self):
        if not self.stop:
            print 'looping',self.stop
            self.a.pcolor(colors[self.t])
            #draw()
            #self.label.configure(bg=colors[self.t])
            self.t += 1
            if self.t == numcol: # push stop button
                self.service_toolbar(2)
            self.canvas.get_tk_widget().update_idletasks()
            #self.label.update_idletasks()
            self.after(500, self.blink)

#root = Tk()
app=App()
app.mainloop()

Thanks for the help!

+1  A: 

In your blink function, add a self.canvas.show() before calling idle tasks:

self.canvas.show() # insert this line
self.canvas.get_tk_widget().update_idletasks()
ars
Wow, that was easy! I've used show() many times at the end of scripts, but I didn't realize it is also an individual canvas method.Thanks!
AmyS
ars, I will repost another question with a link to this one, but in case you can resond...If i want to animate contours instead if a colorplot, if i just change the plotting command to: self.a.contour(colors[self.t])the contour plots show, but instead of updating the canvas as a new plot everytime like pcolor did, they overplot on themselves.Do you know how I can clear the previous contour plots, without using an obvious cla() such that i don't see white?thanks
AmyS
well, if i self.a.cla() just before the self.a.contour() i don't actually see the canvas clear before the next plot, so it works. But, if there is a better way to set the animation than to cla() i would prefer it because i will also have other data on my plot and don't necessarily want to clear it too. Also, i will be using self.a.plot() as well but it seems to be the exact scenario as contour()
AmyS
AmyS, that sounds fine. The only question is performance and how smooth your animation needs to be. If it looks fine, don't worry about it. Otherwise, you can look into the "blit" operation, e.g. http://matplotlib.sourceforge.net/examples/animation/animate_decay_tk_blit.html?highlight=blit
ars
Here's another page with recipes you might find useful: http://www.scipy.org/Cookbook/Matplotlib/Animations
ars