tiempo real matrices graficos graficas graficar grafica coordenadas barras python animation matplotlib

real - matplotlib python



Gestión del trazado dinámico en matplotlib Módulo de animación (2)

Me gustaría tener un gráfico trazado de forma iterativa que permita saltar al siguiente fotograma, detenerlo y volver a un fotograma anterior.

He visto el módulo de animación matplotlib, que sería perfecto si hubiera una manera de implementar la funcionalidad de cuadro anterior (como ejecutar la animación hacia atrás para algunos fotogramas cuando se presiona una tecla)

Sería bueno algo como esto:

def update_frame(i, data): fig.set_data(data[i])

pero de una manera que podría administrar explícitamente si el iterador i aumenta o disminuye.

¿Hay alguna manera de hacer eso en matplotlib? ¿Debería buscar un módulo de python diferente?


Para una respuesta de trabajo adecuada con el módulo Animation, vea la respuesta de ImportanceOfBeingErnest

Tengo varios problemas con la funcionalidad prevista. ¿Cómo funcionaría el progreso de la animación junto con la inversión? ¿Habría un video, pero presionando un botón comenzará a reproducirse? ¿O debería haber pasos individuales de marcos? No estoy seguro de entender cómo se puede combinar una animación con esta función de inversión; Imagino que las animaciones matplotlib son esencialmente películas.

Mi otro problema es técnico: no estoy seguro de que esto se pueda hacer con animaciones matplotlib. Los documentos explican que una FuncAnimation se realiza superficialmente

for d in frames: artists = func(d, *fargs) fig.canvas.draw_idle() plt.pause(interval)

donde frames es esencialmente iterable . No me parece sencillo ajustar dinámicamente los frames durante la animación, por lo que este es un obstáculo técnico.

En realidad, la funcionalidad que describiste funciona mucho mejor en mi cabeza en un enfoque basado en widgets. Los botones podrían propagar la "animación", o podría tener un botón de verificación que modifique si el próximo paso avanza o retrocede. Aquí hay una prueba de concepto simple de lo que quiero decir:

import matplotlib.pyplot as plt from matplotlib.widgets import Button import numpy as np # just for dummy data generation # generate dummy data ndat = 20 x = np.linspace(0,1,ndat) phi = np.linspace(0,2*np.pi,100,endpoint=False) dat = np.transpose([x[:,None]*np.cos(phi),x[:,None]*np.sin(phi)],(1,2,0)) # create figure and axes fig = plt.figure() ax_pl = plt.subplot2grid((5,5),(0,0),colspan=5,rowspan=3) # axes_plot ax_bl = plt.subplot2grid((5,5),(4,0),colspan=2,rowspan=1) # axes_button_left ax_br = plt.subplot2grid((5,5),(4,3),colspan=2,rowspan=1) # axes_button_right # create forward/backward buttons butt_l = Button(ax_bl, ''/N{leftwards arrow}'') # or u'''' on python 2 butt_r = Button(ax_br, ''/N{rightwards arrow}'') # or u'''' on python 2 # create initial plot # store index of data and handle to plot as axes property because why not ax_pl.idat = 0 hplot = ax_pl.scatter(*dat[ax_pl.idat].T) ax_pl.hpl = hplot ax_pl.axis(''scaled'') ax_pl.axis([dat[...,0].min(),dat[...,0].max(), dat[...,1].min(),dat[...,1].max()]) ax_pl.set_autoscale_on(False) ax_pl.set_title(''{}/{}''.format(ax_pl.idat,dat.shape[0]-1)) # define and hook callback for buttons def replot_data(ax_pl,dat): ''''''replot data after button push, assumes constant data shape'''''' ax_pl.hpl.set_offsets(dat[ax_pl.idat]) ax_pl.set_title(''{}/{}''.format(ax_pl.idat,dat.shape[0]-1)) ax_pl.get_figure().canvas.draw() def left_onclicked(event,ax=ax_pl,dat=dat): ''''''try to decrement data index, replot if success'''''' if ax.idat > 0: ax.idat -= 1 replot_data(ax,dat) def right_onclicked(event,ax=ax_pl,dat=dat): ''''''try to increment data index, replot if success'''''' if ax.idat < dat.shape[0]-1: ax.idat += 1 replot_data(ax,dat) butt_l.on_clicked(left_onclicked) butt_r.on_clicked(right_onclicked) plt.show()

Tenga en cuenta que no tengo mucha experiencia con los widgets de matplotlib o las GUI en general, por lo tanto, no espere que lo anterior se ajuste a las mejores prácticas en el tema. También agregué algunos parámetros adicionales para pasar aquí y allá, porque tengo una aversión a usar nombres globales, pero esto podría ser algo supersticioso en este contexto; Honestamente, no puedo decirlo. Además, si está definiendo estos objetos dentro de una clase o función, asegúrese de mantener una referencia a los widgets, de lo contrario, podrían dejar de responder cuando se recolecte basura accidentalmente.

La figura resultante tiene un eje para trazar los gráficos de dispersión, y hay dos botones para incrementar el índice de corte. Los datos tienen forma (ndat,100,2) , donde los índices finales definen 100 puntos en el espacio 2d. Un estado específico:

(No tiene por qué ser tan feo, simplemente no quería jugar con el diseño).

Incluso podría imaginar una configuración donde un temporizador actualiza automáticamente la trama, y ​​la dirección de la actualización se puede establecer con un widget. No estoy seguro de cómo se podría hacer esto de manera adecuada, pero trataría de seguir este camino para el tipo de visualización que usted parece estar buscando.

También tenga en cuenta que el enfoque anterior no FuncAnimation y otras optimizaciones que FuncAnimation haría, pero es de esperar que esto no interfiera con su visualización.


La clase FuncAnimation permite suministrar una función de generador al argumento de frames . Se esperaría que esta función arroje un valor que se suministra a la función de actualización para cada paso de la animación.

Los estados de documentación de FuncAnimation :

frames : iterable, int, generator function, o None, opcional [..]
Si una función de generador, entonces debe tener la firma
def gen_function() -> obj:
En todos estos casos, los valores en marcos se pasan simplemente a la función proporcionada por el usuario y, por lo tanto, pueden ser de cualquier tipo.

Ahora podemos crear una función de generador que arroje números enteros, ya sea hacia adelante o hacia atrás, de modo que la animación se ejecute hacia adelante o hacia atrás . Para dirigir la animación, podemos usar matplotlib.widgets.Button y también crear un avance de un paso o hacia atrás funcionalidad. Esto es similar a mi respuesta a la pregunta sobre el bucle a través de un conjunto de imágenes.

La siguiente es una clase llamada Player que subclasifica FuncAnimation e incorpora todo esto, lo que permite iniciar y detener la animación. Se puede crear una instancia similar a FuncAnimation ,

ani = Player(fig, update, mini=0, maxi=10)

donde la update sería una función de actualización, esperando un entero como entrada, y mini y maxi denotarían el número mínimo y máximo que la función podría usar. Esta clase almacena el valor del índice actual ( self.i ), de modo que si la animación se detiene o se revierte, se reiniciará en el cuadro actual.

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.func, 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.fig.canvas.draw_idle() def setup(self, pos): playerax = self.fig.add_axes([pos[0],pos[1], 0.22, 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) 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) ### 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()