2017-07-08 47 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