在 matplotlib 动画模块中管理动态绘图 [英] Managing dynamic plotting in matplotlib Animation module
问题描述
我想要一个迭代绘制的图形,允许跳到下一帧,停止它并返回到前一帧.
我看过 matplotlib Animation 模块,如果有办法实现前一帧的功能(比如按下一个键时向后运行几帧动画),这将是完美的
这样的事情会很好:
def update_frame(i, data):fig.set_data(data[i])
但以一种我可以明确管理 i 迭代器是增加还是减少的方式.
有没有办法在 matplotlib 中做到这一点?我应该寻找不同的 python 模块吗?
FuncAnimation
类允许 或向后 .为了控制动画,我们可以使用 matplotlib.widgets.Button
s 并创建一个向前的 或向后 功能.这类似于
注意:这不是以允许 blitting 的方式编写的.
I would like to have an iteratively plotted graph that allows for skipping to the next frame, stopping it and coming back to a previous frame.
I have looked at matplotlib Animation module which would be perfect if there was a way to implement the previous frame functionality (like run Animation backwards for a few frames when a key is pressed)
It would be nice to something like this:
def update_frame(i, data):
fig.set_data(data[i])
but in a way that I could explicitly manage whether the i iterator increases or decreases.
Is there a way to do that in matplotlib? Should I look for a different python module?
The FuncAnimation
class allows to supply a generator function to the frames
argument. This function would be expected to yield a value that is supplied to the updating function for each step of the animantion.
The FuncAnimation
doc states:
frames
: iterable, int, generator function, or None, optional [..]
If a generator function, then must have the signature
def gen_function() -> obj:
In all of these cases, the values in frames is simply passed through to the user-supplied func and thus can be of any type.
We can now create a generator function which yields integers either in forward or in backward direction such that the animation runs forwards or backwards . To steer the animation, we might use matplotlib.widgets.Button
s and also create a one-step forward or backward functionality. This is similar to my answer to the question about looping through a set of images.
The following is a class called Player
which subclasses FuncAnimation
and incoorporates all of this, allowing to start and stop the animation. It can be instantiated similarly to FuncAnimation
,
ani = Player(fig, update, mini=0, maxi=10)
where update
would be an updating function, expecting an integer as input, and mini
and maxi
denote the minimal and maximal number that the function could use. This class stores the value of the current index (self.i
), such that if the animation is stopped or reverted it will restart at the current frame.
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=u'$\u25C0$')
self.button_stop = matplotlib.widgets.Button(sax, label=u'$\u25A0$')
self.button_forward = matplotlib.widgets.Button(fax, label=u'$\u25B6$')
self.button_oneforward = matplotlib.widgets.Button(ofax, label=u'$\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()
Note: This hasn't been written in a way to allow for blitting.
这篇关于在 matplotlib 动画模块中管理动态绘图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!