For a proper working answer with the Animation module see the answer of ImportanceOfBeingErnest
I have multiple issues with your intended functionality. How would the animation's progress work together with reversing? Would there be a video, but pressing a button starts playing it back? Or should there be individual steps of frames? I'm not sure I understand how an animation could be coupled to this reversal feature; I picture matplotlib animations to be essentially movies.
My other issue is a technical one: I'm not sure this can be done with matplotlib animations. The docs explain that a FuncAnimation
superficially performs
for d in frames:
artists = func(d, *fargs)
fig.canvas.draw_idle()
plt.pause(interval)
where frames
is essentially an iterable. It doesn't seem straightforward to me to dynamically adjust frames
during the animation, so this is a technical obstacle.
Actually, the functionality you described works much better in my head in a widget-based approach. Buttons could propagate the "animation", or you could have a check button that modifies whether the next step goes forward or backward. Here's a simple proof of concept of what I mean:
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
import numpy as np # just for dummy data generation
# generate dummy data
ndat = 20
x = np.linspace(0,1,ndat)
phi = np.linspace(0,2*np.pi,100,endpoint=False)
dat = np.transpose([x[:,None]*np.cos(phi),x[:,None]*np.sin(phi)],(1,2,0))
# create figure and axes
fig = plt.figure()
ax_pl = plt.subplot2grid((5,5),(0,0),colspan=5,rowspan=3) # axes_plot
ax_bl = plt.subplot2grid((5,5),(4,0),colspan=2,rowspan=1) # axes_button_left
ax_br = plt.subplot2grid((5,5),(4,3),colspan=2,rowspan=1) # axes_button_right
# create forward/backward buttons
butt_l = Button(ax_bl, '\N{leftwards arrow}') # or u'' on python 2
butt_r = Button(ax_br, '\N{rightwards arrow}') # or u'' on python 2
# create initial plot
# store index of data and handle to plot as axes property because why not
ax_pl.idat = 0
hplot = ax_pl.scatter(*dat[ax_pl.idat].T)
ax_pl.hpl = hplot
ax_pl.axis('scaled')
ax_pl.axis([dat[...,0].min(),dat[...,0].max(),
dat[...,1].min(),dat[...,1].max()])
ax_pl.set_autoscale_on(False)
ax_pl.set_title('{}/{}'.format(ax_pl.idat,dat.shape[0]-1))
# define and hook callback for buttons
def replot_data(ax_pl,dat):
'''replot data after button push, assumes constant data shape'''
ax_pl.hpl.set_offsets(dat[ax_pl.idat])
ax_pl.set_title('{}/{}'.format(ax_pl.idat,dat.shape[0]-1))
ax_pl.get_figure().canvas.draw()
def left_onclicked(event,ax=ax_pl,dat=dat):
'''try to decrement data index, replot if success'''
if ax.idat > 0:
ax.idat -= 1
replot_data(ax,dat)
def right_onclicked(event,ax=ax_pl,dat=dat):
'''try to increment data index, replot if success'''
if ax.idat < dat.shape[0]-1:
ax.idat += 1
replot_data(ax,dat)
butt_l.on_clicked(left_onclicked)
butt_r.on_clicked(right_onclicked)
plt.show()
Note that I'm not really experienced with matplotlib widgets or GUIs in general, so don't expect the above to conform with best practices in the subject. I also added some additional parameters to be passed here and there, because I have an aversion to using global names, but this might be somewhat superstitious in this context; I honestly can't tell. Also, if you're defining these objects inside a class or a function, make sure to keep a reference to the widgets otherwise they might become unresponsive when accidentally garbage-collected.
The resulting figure has an axes for plotting the scatter plots, and there are two buttons to increment the slicing index. The data is shaped (ndat,100,2)
, where the trailing indices define 100 points in 2d space. A specific state:
(It doesn't have to be this ugly, I just didn't want to fiddle with the design.)
I could even imagine a setup where a timer automatically updates the plot, and the direction of the update can be set with a widget. I'm not sure how this could be done properly, but I would try to pursue this path for the kind of visualization you seem to be after.
Also note that the above approach is entirely missing blitting and other optimizations that FuncAnimation
would do, but this will hopefully not interfere with your visualization.