Parcela interactiva animada utilizando matplotlib

Mientras buscaba una manera de hacer un gráfico interactivo animado usando matplotlib, encontré este fragmento de código en la documentación de desbordamiento de stack:

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) def update(val): # amp is the current value of the slider amp = samp.val # update curve l.set_ydata(amp*np.sin(t)) # redraw canvas while idle fig.canvas.draw_idle() # call update function on slider value change samp.on_changed(update) plt.show() 

Este código hace casi exactamente lo que estoy buscando, pero me gustaría animar la ttwig, es decir, hacer que el control deslizante se mueva automáticamente de izquierda a derecha, por ejemplo, progresando de 0.01 cada segundo. ¿Hay alguna manera simple de hacer eso? Sabiendo que también quiero mantener el control manual en el control deslizante (usando el evento click).

Aquí hay una adaptación simple de su código para agregar animación:

 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() 

El cambio principal es la adición de la función update_plot , que se utiliza para hacer un FuncAnimation en la segunda a la última línea. La animación se incrementa desde el último valor del control deslizante que se estableció.

La variable is_manual realiza un seguimiento de cuándo el usuario ha hecho clic en el control deslizante. Después de que el usuario haga clic en él, la variable se establece en True y la animación ya no actualizará la ttwig.

Para reanudar la animación, agregué una función on_click que establece que is_manual = False cuando el usuario hace clic en algún lugar del canvas OTROS que el control deslizante.

Ya que este es un script rápido y sucio, dejé las variables como globales, pero podría escribirlas fácilmente en una clase adecuada.

Tenga en cuenta que llamar a samp.set_val implícitamente llama a la función update_slider , que también se llama cuando el usuario hace clic directamente en el control deslizante, por lo que tenemos que restablecer is_manual en la función update_plot .

Puede adaptar el código de esta respuesta para incluir un control deslizante.

 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=ur'$\u29CF$') self.button_back = matplotlib.widgets.Button(bax, label=ur'$\u25C0$') self.button_stop = matplotlib.widgets.Button(sax, label=ur'$\u25A0$') self.button_forward = matplotlib.widgets.Button(fax, label=ur'$\u25B6$') self.button_oneforward = matplotlib.widgets.Button(ofax, label=ur'$\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() 

introduzca la descripción de la imagen aquí