Animated interactive plot using matplotlib

后端 未结 2 1860
栀梦
栀梦 2021-01-05 19:21

While looking for a way to make animated interactive plot using matplotlib, I encountered this piece of code on Stack overflow documentation:

import numpy as         


        
相关标签:
2条回答
  • 2021-01-05 20:18

    You may adapt the code from this answer to include a slider.

    import numpy as np
    import matplotlib.pyplot as plt
    from matplotlib.animation import FuncAnimation
    import mpl_toolkits.axes_grid1
    import matplotlib.widgets
    
    class Player(FuncAnimation):
        def __init__(self, fig, func, frames=None, init_func=None, fargs=None,
                     save_count=None, mini=0, maxi=100, pos=(0.125, 0.92), **kwargs):
            self.i = 0
            self.min=mini
            self.max=maxi
            self.runs = True
            self.forwards = True
            self.fig = fig
            self.func = func
            self.setup(pos)
            FuncAnimation.__init__(self,self.fig, self.update, frames=self.play(), 
                                               init_func=init_func, fargs=fargs,
                                               save_count=save_count, **kwargs )    
    
        def play(self):
            while self.runs:
                self.i = self.i+self.forwards-(not self.forwards)
                if self.i > self.min and self.i < self.max:
                    yield self.i
                else:
                    self.stop()
                    yield self.i
    
        def start(self):
            self.runs=True
            self.event_source.start()
    
        def stop(self, event=None):
            self.runs = False
            self.event_source.stop()
    
        def forward(self, event=None):
            self.forwards = True
            self.start()
        def backward(self, event=None):
            self.forwards = False
            self.start()
        def oneforward(self, event=None):
            self.forwards = True
            self.onestep()
        def onebackward(self, event=None):
            self.forwards = False
            self.onestep()
    
        def onestep(self):
            if self.i > self.min and self.i < self.max:
                self.i = self.i+self.forwards-(not self.forwards)
            elif self.i == self.min and self.forwards:
                self.i+=1
            elif self.i == self.max and not self.forwards:
                self.i-=1
            self.func(self.i)
            self.slider.set_val(self.i)
            self.fig.canvas.draw_idle()
    
        def setup(self, pos):
            playerax = self.fig.add_axes([pos[0],pos[1], 0.64, 0.04])
            divider = mpl_toolkits.axes_grid1.make_axes_locatable(playerax)
            bax = divider.append_axes("right", size="80%", pad=0.05)
            sax = divider.append_axes("right", size="80%", pad=0.05)
            fax = divider.append_axes("right", size="80%", pad=0.05)
            ofax = divider.append_axes("right", size="100%", pad=0.05)
            sliderax = divider.append_axes("right", size="500%", pad=0.07)
            self.button_oneback = matplotlib.widgets.Button(playerax, label='$\u29CF$')
            self.button_back = matplotlib.widgets.Button(bax, label='$\u25C0$')
            self.button_stop = matplotlib.widgets.Button(sax, label='$\u25A0$')
            self.button_forward = matplotlib.widgets.Button(fax, label='$\u25B6$')
            self.button_oneforward = matplotlib.widgets.Button(ofax, label='$\u29D0$')
            self.button_oneback.on_clicked(self.onebackward)
            self.button_back.on_clicked(self.backward)
            self.button_stop.on_clicked(self.stop)
            self.button_forward.on_clicked(self.forward)
            self.button_oneforward.on_clicked(self.oneforward)
            self.slider = matplotlib.widgets.Slider(sliderax, '', 
                                                    self.min, self.max, valinit=self.i)
            self.slider.on_changed(self.set_pos)
    
        def set_pos(self,i):
            self.i = int(self.slider.val)
            self.func(self.i)
    
        def update(self,i):
            self.slider.set_val(i)
    
    
    ### using this class is as easy as using FuncAnimation:            
    
    fig, ax = plt.subplots()
    x = np.linspace(0,6*np.pi, num=100)
    y = np.sin(x)
    
    ax.plot(x,y)
    point, = ax.plot([],[], marker="o", color="crimson", ms=15)
    
    def update(i):
        point.set_data(x[i],y[i])
    
    ani = Player(fig, update, maxi=len(y)-1)
    
    plt.show()
    

    0 讨论(0)
  • 2021-01-05 20:27

    Here is a simple adaptation of your code to add animation:

    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.animation as animation
    from matplotlib.widgets import Slider
    
    TWOPI = 2*np.pi
    
    fig, ax = plt.subplots()
    
    t = np.arange(0.0, TWOPI, 0.001)
    initial_amp = .5
    s = initial_amp*np.sin(t)
    l, = plt.plot(t, s, lw=2)
    
    ax = plt.axis([0,TWOPI,-1,1])
    
    axamp = plt.axes([0.25, .03, 0.50, 0.02])
    # Slider
    samp = Slider(axamp, 'Amp', 0, 1, valinit=initial_amp)
    
    # Animation controls
    is_manual = False # True if user has taken control of the animation
    interval = 100 # ms, time between animation frames
    loop_len = 5.0 # seconds per loop
    scale = interval / 1000 / loop_len
    
    def update_slider(val):
        global is_manual
        is_manual=True
        update(val)
    
    def update(val):
        # update curve
        l.set_ydata(val*np.sin(t))
        # redraw canvas while idle
        fig.canvas.draw_idle()
    
    def update_plot(num):
        global is_manual
        if is_manual:
            return l, # don't change
    
        val = (samp.val + scale) % samp.valmax
        samp.set_val(val)
        is_manual = False # the above line called update_slider, so we need to reset this
        return l,
    
    def on_click(event):
        # Check where the click happened
        (xm,ym),(xM,yM) = samp.label.clipbox.get_points()
        if xm < event.x < xM and ym < event.y < yM:
            # Event happened within the slider, ignore since it is handled in update_slider
            return
        else:
            # user clicked somewhere else on canvas = unpause
            global is_manual
            is_manual=False
    
    # call update function on slider value change
    samp.on_changed(update_slider)
    
    fig.canvas.mpl_connect('button_press_event', on_click)
    
    ani = animation.FuncAnimation(fig, update_plot, interval=interval)
    
    plt.show()
    

    The main change is the addition of the update_plot function, which is used to make a FuncAnimation in the second to last line. The animation increments from the last slider value that was set.

    The variable is_manual keeps track of when the user has clicked on the slider. After the user clicks on it, the variable is set to True and the animation will no longer update the plot.

    To resume animation, I added an on_click function which sets is_manual = False when the user clicks somewhere on the canvas OTHER than the slider.

    Since this is a quick-and-dirty script I left variables as global, but you could easily write it up in a proper class.

    Note that calling samp.set_val implicitly calls the update_slider function, which is also called when the user clicks directly on the slider, so we have to reset is_manual in the update_plot function.

    0 讨论(0)
提交回复
热议问题