Strange, strange. iPython is apparently having some effect here: I tried
ipython -pylab animation.py
and I got a figure with the initial plot, but nothing further
happened. After closing the plot, I typed
run animation.py
at the ipython prompt, and saw the initial plot, erasure, and
animation that I expected. Can you try running with a plain
python animation.py

I've attached a new version of the script with some changes suggested
offline by Ryan May - he was seeing platform or wx-specific behavior
related to event handling. All bugs remaining are mine :)

-Eric

On Sun, Nov 23, 2008 at 6:20 PM, Cohen-Tanugi Johann
<[EMAIL PROTECTED]> wrote:
>
>> hi, I tried your script, commenting/uncommenting the backend line, but I
>> still get:
>> [EMAIL PROTECTED] ~]$ ipython -pylab MACROS/animation.py
>>
>> ---------------------------------------------------------------------------
>> AttributeError                            Traceback (most recent call
>> last)
>>
>> /home/cohen/MACROS/animation.py in <module>()
>>   274             p.show()
>>   275
>> --> 276     t = test()
>>   277
>>   278
>>
>> /home/cohen/MACROS/animation.py in __init__(self)
>>   270
>>   271             # cid = p.gcf().canvas.mpl_connect('idle_event',
>> self.update)
>> --> 272             wx.GetApp().Bind(wx.EVT_IDLE, self.update)
>>   273
>>   274             p.show()
>>
>> AttributeError: 'NoneType' object has no attribute 'Bind'
>> WARNING: Failure executing file: <MACROS/animation.py>
>>
>> I am using MPL revision 6440.
>> cheers,
>> Johann
>>
>> Eric Bruning wrote:
>>>
>>> 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
>>>  ------------------------------------------------------------------------
>>>
>>> -------------------------------------------------------------------------
>>> 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
>>
>
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)
        
        
        # use a timer to generate new idle_events, so that the frame rate is limited.
        # calling WakeUpIdle at the end of 
        self.idle_kick = wx.Timer()
        wx.GetApp().Bind(wx.EVT_TIMER, lambda e: wx.WakeUpIdle(), self.idle_kick)
        self.idle_kick.Start(40, oneShot=False)
        
        # 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()
    
        event = args[0]

        # the two calls below should have the same effect, now handled by the timer in the init.
        # event.RequestMore()
        # wx.WakeUpIdle()

    def control_cleanup(self):
        wx.GetApp().Unbind(wx.EVT_IDLE)
        wx.GetApp().Unbind(wx.EVT_TIMER)
        # 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