2017-07-08 48 views
2

我想要一个迭代绘制的图形,允许跳到下一帧,停止它并返回到前一帧。在matplotlib中管理动态绘图动画模块

我已经看过matplotlib动画模块,如果有(当按下一个键,像运行动画倒退几帧)实施前一帧功能

这将是很好的方式,也将是完美的这样的事情:

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

但以一种方式,我可以明确地管理是否迭代器增加或减少。

有没有办法在matplotlib中做到这一点? 我应该寻找一个不同的python模块吗?

回答

1

FuncAnimation类允许supply a generator functionframes参数。预计该函数会产生一个值,该值将被提供给动画每一步的更新函数。

FuncAnimation doc状态:

frames:迭代,INT,发电机功能,或无,可选 [..]
如果发电机功能,那么必须有签名
def gen_function() -> obj:
在所有这些情况下,帧中的值只是传递给用户提供的func,因此可以是任何类型。

我们现在可以创建其产生无论是在向前或向后方向的整数,使得动画运行向前enter image description here或向后enter image description here发电机功能。要引导动画,我们可能会使用matplotlib.widgets.Button s,并创建一个向前的enter image description here或向后的enter image description here功能。这与my answer类似于关于循环遍历一组图像的问题。

以下是一个名为Player的类,它的子类FuncAnimation并包含所有这些,允许开始和停止动画。它可类似地实例化以FuncAnimation

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

其中update将是一个更新功能,期望的整数作为输入,并minimaxi表示最小和最大数目,该功能可以使用。该类存储当前索引(self.i)的值,这样如果动画停止或恢复,它将在当前帧处重新启动。

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

enter image description here

+0

谢谢你解释。它以某种方式不会发生在我身上,这可以通过使用发生器来解决。 – LemurPwned

1

为了与动画模块正常工作答案见the answer of ImportanceOfBeingErnest

我与你的预期功能的多个问题。动画的进展如何与逆转一起工作?会不会有视频,但按下按钮开始播放?还是应该有个别的框架步骤?我不确定我了解动画如何与这种反转特征相结合;我图片matplotlib动画本质上是电影。

我的另一个问题是技术问题:我不确定这可以用matplotlib动画完成。 The docs explain一个表面上FuncAnimation执行

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

其中frames is essentially an iterable。动画中动态调整frames似乎并不简单,所以这是一个技术障碍。

实际上,您所描述的功能在基于窗口小部件的方法中效果更好。 Buttons可以传播“动画”,或者你可以有一个check button修改下一步是前进还是后退。这里是我的意思概念的一个简单证明:

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

请注意,我不是真的matplotlib小工具或GUI的一般经验,所以不要指望上面来,并在主题的最佳实践一致。我还添加了一些额外的参数在这里和那里传递,因为我厌恶使用全局名称,但在这种情况下这可能有点迷信;我真的不知道。另外,如果要在类或函数内部定义这些对象,请确保保留对小部件的引用,否则在意外收集垃圾时它们可能不响应。

生成的图形有一个用于绘制散点图的轴,并且有两个按钮用于增加切片索引。数据的形状为(ndat,100,2),其中尾部指数在2d空间中定义100个点。特定状态:

example result

(它不必是这个丑陋的,我只是不想与设计拨弄)

我甚至可以想像的设置,其中一个计时器自动更新绘图,并可以使用小部件设置更新的方向。我不知道如何做到这一点,但我会试图追求这种可视化的道路,你看起来像后。

另请注意,上述方法完全缺少blitting和FuncAnimation会执行的其他优化,但希望这不会干扰您的可视化。

+0

你说:“这似乎并不能直接给我的动画过程中动态调整帧”。你可以看看[我的回答](https://stackoverflow.com/a/44989063/4124317)这个问题,如何做到这一点。主要的一点是'frames'可以被赋予生成器函数。 – ImportanceOfBeingErnest

+0

@ImportanceOfBeingErnest谢谢,我也说过我是一个GUI noob;)我确实看到了生成器函数选项,但它让我觉得没有简单的方法来操作回调之间的函数。做得好! –

+1

谢谢,这个答案真的有帮助 – LemurPwned