Hi,
Out of my own needs I developed an animation class that is very similar to
FuncAnimation but is drawn asynchronously (not that this couldn't be done
with FuncAnimation, this is just a more direct path). Instead of trying to
explain the differences off the bat I'll give a base example usage
fig, ax = plt.subplots()
x = np.arange(0, 2*np.pi, 0.01)
line, = ax.plot(x, np.sin(x))
ani = AsyncAnimation(fig,interval=50)
fig.show() # starts the first 'draw' without entering the mpl mainloop
i = 0
while ani.update(): #update() flushes the animation's queue
i += 1
y = np.sin(x+i/10.0)
ani.append_artist(line,(x,y))
This is the simplest example, where one frame is queued at a time and
`update()` blocks execution until that frame has been rendered (so this is
effectively the same as a FuncAnimation).
Aesthetically, the difference is that the animation code lives inside a
loop instead of a function call. This removes the need to have the
animation code pass variables back to the user or doing any global imports
of variables which IMHO makes the application code cleaner and more
self-contained. Personally, I find this a simpler and more intuitive way to
write interactive animation applications. (Alternatively, writing
application code inside of a class that uses FuncAnimation gives you
similar benefits of cleaner code).
The other more significant difference is that having rendering being done
asynchronously basically decouples the animation code from the application
code, which
1. Makes it trivial to execute the graphics rendering as a separate
process
2. Gives the user more flexibility in their application code (you could
imagine an application "queueing up" multiple frames at a time, allowing
them to render in their own time).
Anyway, here is the code as it stands now (most important are the
`append_artist` and `update` functions; the rest of the code is very
similar to the FuncAnimation class):
class AsyncAnimation(Animation):
"""
Makes an animation by reading from a user filled queue every *interval*
milliseconds. User adds to the queue by calling *update_artists*.
Note that data is _not_ copied before being put into the queue; if a
reference
is passed (such as a numpy ndarray) whatever data the reference is
pointing
to at draw time will be plotted.
*init_func* is a function used to draw a clear frame. If not given, the
results of drawing from the first item in the frames sequence will be
used. This function will be called once before the first frame.
*event_source* Default event source to trigger poll of the artist
queue. By
default this is a timer with an *interval* millisecond timeout.
"""
def
__init__(self,fig,init_func=None,event_source=None,interval=10,blit=True):
self._data = deque()
self._lag = 0
self._queued = False
self._fig = fig
self._interval = interval
self._init_func = init_func
if event_source is None:
event_source = fig.canvas.new_timer(interval=self._interval)
Animation.__init__(self,fig,event_source=event_source,blit=blit)
def new_frame_seq(self):
return itertools.count()
def _init_draw(self):
if self._init_func is not None:
self._drawn_artists = self._init_func()
for a in self._drawn_artists:
a.set_animated(self._blit)
def _draw_next_frame(self, *args):
# carry on if there's nothing to draw right now
if self._data:
Animation._draw_next_frame(self,*args)
def _draw_frame(self,framedata):
artdata = self._data.popleft()
artists = []
for (a,d) in artdata:
artists.append(a)
if d is not None: a.set_data(d)
a.set_animated(self._blit)
self._drawn_artists = artists
def append_artist(self,artist,data=None):
self._queue_artists((artist,data),)
def extend_artists(self,artists):
self._queue_artists(*artists)
def _queue_artists(self,*artists):
if len(self._data) and self._queued:
self._data[-1] += artists
return
self._queued = True
if len(self._data) > self._lag:
warnings.warn("Artists queue is behind by %d" % len(self._data))
self._lag = len(self._data)
self._data.append(artists)
def update(self,block=True):
self._queued = False
self._fig.canvas.flush_events()
if block:
while len(self._data) and self.event_source is not None:
self._fig.canvas.flush_events()
return self.event_source is not None
So, I've developed this code enough that it fills my needs, but it needs
some work to add in all the expected matplotlib functionality. Is something
that would be useful to people?
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/matplotlib-devel/attachments/20150919/eb64a085/attachment.html>