The Power of Generators: Simple Animation Example for MatPlotLib
I have for some time now been using matplotlib to do all my graphing in python. Its a great package! Recently I've been wanting to do basic animation of graphs, while you'd think it would be simple has turned into a bit of a trek. There is a simple animation example found here: http://matplotlib.sourceforge.net/examples/animation/simple_anim_gtk.html it works okay, but if you try to move the window it freezes, and you can't use anything on the toolbar. Sometimes merely clicking on the window causes it to freeze! Really annoying, and why not quite a bug, its definitely undesirable behavior.
So I've found a very simple fix that leaves the gui reasonably interactive and the window is freely movable. It centers around using a python generator. Calling "yield some_variable" in a function makes the function a generator and causes execution to stop at that point until the generator's next() method is called, this allows us to "pause" the execution of the function.For instance:
def foo() i = 0 while True: i += 1 yield i gen = foo() while True: print gen.next()
This prints 1,2,3,4,5, . . . . . forever until you kill the program.
Now, for doing animation in matplotlib the yield statement can be used in the animate() function to "pause" it and return control the gui thread. As it currently stands, once animate() is called it executes until completion, never allowing gui events to be handled, hence the freezing. The solution is changing two lines of code, note that this is for the GTK example but the same principle should be applicable to the other backends. And here is the end result
""" A simple example of an animated plot using a gtk backend """ import time import numpy as np import matplotlib matplotlib.use('GTKAgg') # do this before importing pylab import matplotlib.pyplot as plt fig = plt.figure() ax = fig.add_subplot(111) def animate(): tstart = time.time() # for profiling x = np.arange(0, 2*np.pi, 0.01) # x-array line, = ax.plot(x, np.sin(x)) for i in np.arange(1,200): line.set_ydata(np.sin(x+i/10.0)) # update the data fig.canvas.draw() # redraw the canvas # Here is generator magic that stops execution of animate() # till the generators next() method is called yield True # continue animating print 'FPS:' , 200/(time.time()-tstart) raise SystemExit import gobject print 'adding idle' # This is some lambda and python magic: # Python only evaluates defaults for arguments once, # so the first call to this lambda function sets iter to the generator made (automagically) # from the animate() function. # Each subsequent call to the lambda function calls iter.next() which restarts(unpauses) # execution of the animate() function, # until it yeilds again, at which point control goes back to the gui # These calls are done when the gui is idle, hence why we are registering an idle callback, gobject.idle_add(lambda iter=animate(): iter.next()) print 'showing' plt.show()