Hi Eric,
>
> On Fri, Nov 21, 2008 at 11:19 AM, Eric Jonas <[EMAIL PROTECTED]> wrote:
>> I've looked through the latest examples as well as google and the list
>> archives, and am still at a loss -- can anyone point me to an example of
>> how to animate a scatter plot?
I've attached a somewhat unpolished but functional example that shows
time-synchronized animation of multiple scatter plots. It uses the wx
backend explicitly, due to some issues with the idle event handling.
>> The collection returned by scatter() lacks anything akin to a set_data
>> method.
Yup, you want this instead:
collection.set_offsets(xy)
collection.set_array(s)
-Eric
import matplotlib
# matplotlib.use('WxAgg')
import time
import wx
import sys
import numpy as np
class AnimationController(object):
def __init__(self, duration, animations, leader=None):
""" Single controller of animations. Manages binding to wx.
Binding to wx in each individual animator blocks drawing of all
but the last animator.
duration: Total time to taken to draw the animation. Approximate, but
won't be exceeded by more than one frame redraw interval.
animations: sequence of Animate objects that are to be animated
simultaneously.
"""
self.animations = animations
self.duration = duration
if leader == None:
self.leader = animations[0]
else:
self.leader = leader
# When animating by points, only the lead artist should animate by points,
# and the others should animate by data intervals up to the appropriate time.
if self.leader.framing == 'point_intervals':
for animation in self.animations:
if animation != self.leader:
animation.framing = 'data_intervals'
self.tstart = time.time()
wx.GetApp().Bind(wx.EVT_IDLE, self.control_update)
# self.cid = self.animations[0].artist.axes.figure.canvas.mpl_connect('idle_event', self.control_update)
def control_update(self, *args):
time_fraction = (time.time() - self.tstart) / self.duration
# only restore the background for the first frame drawn.
# otherwise, only the last animation will appear to animate
restore = True
# handle the animation leader first
animation = self.leader
selection = self.leader.get_selection(time_fraction)
current_native_order_value = self.leader._order[selection].max()
animation.update(time_fraction, selection=None, restore=restore)
# did first blit, so don't restore background again
restore = False
for animation in self.animations:
# already did the leader
if animation != self.leader:
selection = animation._order <= current_native_order_value
animation.update(time_fraction, selection=selection, restore=restore)
# restore=False
if time_fraction >= 1.0:
self.control_cleanup()
wx.WakeUpIdle()
def control_cleanup(self):
wx.GetApp().Unbind(wx.EVT_IDLE)
# self.animations[0].artist.axes.figure.canvas.mpl_disconnect(self.cid)
for animation in self.animations:
animation.cleanup()
class Animate(object):
""" Base class that provides a framework for animating Artists.
Artist-specific subclasses should implement setup and update_artist
methods that set artist specific instance variables and do artist-specifc
drawing, respectively.
"""
def update_artist(self, selection):
""" Must be implemented by an artist-specific subclass.
It is called with a boolean selection array that is used
to select data pertinent to each frame of the animation.
Subclasses should update the data in the artist instance.
"""
raise NotImplementedError
def setup(self):
""" Must be implemented by an artist-specific subclass.
Used to set up artist-specific instance variables.
"""
raise NotImplementedError
def __init__(self, artist, order, framing='data_intervals', order_min=None, order_max=None):
""" artist: An artist instance, such as from ax.scatter
order: Array of values of same length as collection items.
The range of values in order is divided up evenly
among frames.
framing: one of ('data_intervals', 'point_intervals'). Defaults
to 'data_intervals'. These options are most easily understood
by considering order as time. Framing by data_intervals displays
data with the same relative spacing in time. The effect is
of simulating what the eye would have seen compressed into
4.0 seconds. Framing by point_intervals displays the same
number of events per frame, which is useful for emphasizing
more data-dense regions.
order_min, order_max: Defaults to min/max of order. Override if drawing
multiple artists with different min/max of order that
need to be simultaneously animated,
"""
self.artist = artist
self.animated_cache = self.artist.get_animated()
self.framing = framing
total_points = order.shape[0]
if framing == 'data_intervals':
self.order = order
self._order = order
elif framing == 'point_intervals':
self._order = order # hold on to the order in data coordintes
# construct a new order vector like so:
# self._order = order = [3.0, 1.0, 2.5, 1.7, 2.6, 2.9]
# self.order = [ 5, 0, 2, 1, 3, 4]
self.order = np.empty(total_points, dtype=int)
self.order[order.argsort()] = np.arange(total_points)
order_min=None
order_max=None
else:
self.order = order
if order_min == None:
self.order_min = self.order.min()
else:
self.order_min = order_min
if order_max == None:
self.order_max = self.order.max()
else:
self.order_max = order_max
self.order_range = (self.order_max - self.order_min)
self.tstart = time.time()
self.background = None
self.artist.set_animated(True)
# hide the collection
self.artist.set_visible(False)
self.need_first_idle = True
self.setup()
self.artist.axes.figure.canvas.draw()
def get_selection(self, time_fraction):
# some percentage of the total time has passed,
# so draw up to that many points.
order_current_value = self.order_min + self.order_range * time_fraction
# no requirement that order be sorted.
sel = self.order <= order_current_value
return sel
def update(self, time_fraction, selection=None, restore=True):
ax = self.artist.axes
canvas = ax.figure.canvas
# First idle event: do nothing. allows all drawing to flush through MPL.
# Allows the set_visible=False from __init__ to take effect.
if self.need_first_idle == True:
self.need_first_idle=False
return
if (self.background == None):
# do nothing on first idle event
# should be copying a blank background
self.background = canvas.copy_from_bbox(ax.bbox)
# now, allow the collection to show up.
self.artist.set_visible(True)
# restore everything minus the scatter plot
if restore:
canvas.restore_region(self.background)
if selection == None:
sel = self.get_selection(time_fraction)
else:
sel = selection
self.update_artist(sel)
ax.draw_artist(self.artist)
canvas.blit(ax.bbox)
def cleanup(self):
self.artist.set_animated(self.animated_cache)
class CollectionAnimate(Animate):
""" Animate a scatter plot, taking some duration to draw the points. """
def setup(self):
"""set up instance variables specific to the collection artist"""
self.offsets = self.artist.get_offsets()
self.array = self.artist.get_array()
def update_artist(self, selection):
"""update the collection with plot data specifc to this frame."""
xy = self.offsets[selection,:]
s = self.array[selection]
self.artist.set_offsets(xy)
self.artist.set_array(s)
if __name__ == '__main__':
import pylab as p
class test(object):
def update(self, *args):
if not self.ran_update:
print "Allowing time to admire the original plot..."
time.sleep(2)
print "proceeding."
sc, sc2 = self.scatters
# ----- ADJUST -----
framing='data_intervals'
# framing='point_intervals'
anim = CollectionAnimate(sc, self.order, framing=framing)
anim2 = CollectionAnimate(sc2, self.order, framing=framing)
anim_ctrl = AnimationController(4.0, [anim, anim2])
self.ran_update = True
def __init__(self):
NPTS = 1000
ax = p.subplot(111)
x = np.arange(NPTS)
y = np.arange(NPTS)
# ----- ADJUST -----
# demonstrates that the colors fill in in the correct order
# t = np.random.rand(NPTS)
# demonstrates the difference between plotting by points and plotting by time.
t = np.asarray([0.2]*int(0.5*NPTS) + [0.8]*int(0.4*NPTS) + [0.9]*int(0.1*NPTS))
sc = p.scatter(x,y,c=t, vmin=0, vmax=1, animated=False, edgecolor='none')
sc2 = p.scatter(x, -y, c=t, vmin=0, vmax=1, animated=False, edgecolor='none')
ax.axis((0,NPTS,-NPTS,NPTS))
self.order = t
self.scatters = (sc, sc2)
# could have contents of the update method here, and it will animate fine.
# Delaying the animation to the first idle event allows us to
# demonstrate that the animation will work after an initial draw of
# the complete plot.
self.ran_update = False
# cid = p.gcf().canvas.mpl_connect('idle_event', self.update)
wx.GetApp().Bind(wx.EVT_IDLE, self.update)
p.show()
t = test()
-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-users