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

Reply via email to