在进行动力学模拟的时候遇到了状态过程可视化问题,因此mark一下python动图的绘制,希望能有所帮助。这里主要介绍 python中 matplotlib 的 animation 模块,可以快速绘制Mp4, gif动画
class matplotlib.animation.FuncAnimation(fig, func, frames=None, init_func=None, fargs=None, save_count=None, *, cache_frame_data=True, **kwargs)
Matplotlib 的 animation 包含动图生成的几乎所有操作,包含:
Animation This class wraps the creation of an animation using matplotlib. 创建一个动画生成对象
FuncAnimation Makes an animation by repeatedly calling a function func. 使用特定的函数生成动画
ArtistAnimation Animation using a fixed set of Artist objects. 使用固定的(我理解为以及画好的图像)生成动画
为了保存画出来的动画,animation 提供了一些writer,这些需要借助安装第三方软件例如ffmpeg 和 ImageMagick.
FFMpegWriter Pipe-based ffmpeg writer.
ImageMagickWriter Pipe-based animated gif.
AVConvWriter Pipe-based avconv writer.
更多信息matplotlib.animation 官方文档都有说明。
安装ffmpeg 和 ImageMagick过程很简单,而且安装imagemagick的时候还可以选择安装ffmpeg,下面给出了安装链接。
![a2a5293ebcc7a4b4bbef249f8e3e3947.png](https://img-blog.csdnimg.cn/img_convert/a2a5293ebcc7a4b4bbef249f8e3e3947.png)
接下来是绘图代码了,目前看到的最清楚的框架是下面这个(修改自官方文档的例子,保存图片上传不了,可以自行运行试试):
import matplotlib.pyplot as plt
# plt.rcParams['animation.convert_path'] = 'E:Program FilesImageMagick-7.0.8-Q16magick.exe'
from matplotlib import animation
# writer = ImageMagickFileWriter() #如果安装了ImageMahick
writer = animation.FFMpegWriter() #如果安装了ffmpeg
# 导入相关模块
import numpy as np
import matplotlib.pyplot as plt
from IPython.display import Image
from matplotlib.animation import FuncAnimation
fig, ax = plt.subplots()
xdata, ydata = [], []
# 注意这边的 "," 不能省
ln, = ax.plot([], [], 'r-', animated=False)
# 初始化图像(譬如 坐标范围)
def init():
ax.set_xlim(0, 2*np.pi)
ax.set_ylim(-1.1, 1.1)
# 注意这边的 "," 也不能省
return ln,
# 将最新数据添加到图像中
def update(frame):
xdata.append(frame)
ydata.append(np.sin(frame))
ln.set_data(xdata, ydata)
# 注意这边的 "," 也不能省
return ln,
# 核心方法入口
ani = FuncAnimation(fig,
update,
frames=np.linspace(0, 2*np.pi, 50),
interval=5,
init_func=init,
blit=True)
# 保存动态图
ani.save('ming.mp4',writer=writer)
# 将图展示在页面上
Image(url='./ming.mp4')
下面我用可视化代码运行了Kuramoto模型的变化过程:
# 导入相关模块
import matplotlib.pyplot as plt
# 可以自行设置writer的路径,不过一般添加了环境变量可以不考虑
# plt.rcParams['animation.convert_path'] = 'E:Program FilesImageMagick-7.0.8-Q16magick.exe'
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from IPython.display import Image
from matplotlib import animation
from matplotlib.animation import FuncAnimation
#writer = animation.ImageMagickFileWriter() #如果安装了ImageMahick
writer = animation.FFMpegWriter() #如果安装了ffmpeg
data = pd.read_csv('phase_sin.csv',index_col=0) # 导入数据
datax = np.cos(data)
datay = np.sin(data)
n = data.shape[0]
s = data.shape[1]
print(s)
fig, ax = plt.subplots(figsize = (10,10))
x = np.linspace(-1, 1, 100)
ax.plot(x, np.sqrt(1-x**2),'b')
ax.plot(x, -np.sqrt(1-x**2),'b') # 画出单位圆
def get_data(frame=0,sensor_id=0):
#print(frame,sensor_id)
x = datax.iloc[sensor_id, frame]
y = datay.iloc[sensor_id, frame]
return x,y
# initialization function
def init():
ax.set_xlim(-1.1, 1.1)
ax.set_ylim(-1.1, 1.1)
# plot the first day (day=0) here:
for j in range(n):
x,y=get_data(0,j)
scat = ax.scatter(x,y,c='r')
return scat,
# animation function
def update(i):
for j in range(n):
x,y=get_data(i,j)
# print(i,col)## use this to understand "where" we are
scat = ax.scatter(x,y,c='r')
# return plot object
return scat,
# 核心方法入口
ani = FuncAnimation(fig,
update,
frames=200,
interval=100,
init_func=init,
blit=True)
# 保存 动态图
ani.save('phase.mp4',writer=writer)
![c298c4afc8cb89fce431b8edbe36913b.png](https://img-blog.csdnimg.cn/img_convert/c298c4afc8cb89fce431b8edbe36913b.png)
附录:FuncAnimation源码,供感兴趣的阅读者使用
class FuncAnimation(TimedAnimation):
"""
Makes an animation by repeatedly calling a function *func*.
Parameters
----------
fig : `~matplotlib.figure.Figure`
The figure object that is used to get draw, resize, and any
other needed events.
func : callable
The function to call at each frame. The first argument will
be the next value in *frames*. Any additional positional
arguments can be supplied via the *fargs* parameter.
The required signature is::
def func(frame, *fargs) -> iterable_of_artists
If ``blit == True``, *func* must return an iterable of all artists
that were modified or created. This information is used by the blitting
algorithm to determine which parts of the figure have to be updated.
The return value is unused if ``blit == False`` and may be omitted in
that case.
frames : iterable, int, generator function, or None, optional
Source of data to pass *func* and each frame of the animation
- If an iterable, then simply use the values provided. If the
iterable has a length, it will override the *save_count* kwarg.
- If an integer, then equivalent to passing ``range(frames)``
- If a generator function, then must have the signature::
def gen_function() -> obj
- If *None*, then equivalent to passing ``itertools.count``.
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.
init_func : callable, optional
A function used to draw a clear frame. If not given, the
results of drawing from the first item in the frames sequence
will be used. This function will be called once before the
first frame.
The required signature is::
def init_func() -> iterable_of_artists
If ``blit == True``, *init_func* must return an iterable of artists
to be re-drawn. This information is used by the blitting
algorithm to determine which parts of the figure have to be updated.
The return value is unused if ``blit == False`` and may be omitted in
that case.
fargs : tuple or None, optional
Additional arguments to pass to each call to *func*.
save_count : int, default: 100
Fallback for the number of values from *frames* to cache. This is
only used if the number of frames cannot be inferred from *frames*,
i.e. when it's an iterator without length or a generator.
interval : number, optional
Delay between frames in milliseconds. Defaults to 200.
repeat_delay : number, optional
If the animation in repeated, adds a delay in milliseconds
before repeating the animation. Defaults to *None*.
repeat : bool, optional
Controls whether the animation should repeat when the sequence
of frames is completed. Defaults to *True*.
blit : bool, optional
Controls whether blitting is used to optimize drawing. Note: when using
blitting any animated artists will be drawn according to their zorder.
However, they will be drawn on top of any previous artists, regardless
of their zorder. Defaults to *False*.
cache_frame_data : bool, optional
Controls whether frame data is cached. Defaults to *True*.
Disabling cache might be helpful when frames contain large objects.
"""
[docs] def __init__(self, fig, func, frames=None, init_func=None, fargs=None,
save_count=None, *, cache_frame_data=True, **kwargs):
if fargs:
self._args = fargs
else:
self._args = ()
self._func = func
self._init_func = init_func
# Amount of framedata to keep around for saving movies. This is only
# used if we don't know how many frames there will be: in the case
# of no generator or in the case of a callable.
self.save_count = save_count
# Set up a function that creates a new iterable when needed. If nothing
# is passed in for frames, just use itertools.count, which will just
# keep counting from 0. A callable passed in for frames is assumed to
# be a generator. An iterable will be used as is, and anything else
# will be treated as a number of frames.
if frames is None:
self._iter_gen = itertools.count
elif callable(frames):
self._iter_gen = frames
elif np.iterable(frames):
if kwargs.get('repeat', True):
def iter_frames(frames=frames):
while True:
this, frames = itertools.tee(frames, 2)
yield from this
self._iter_gen = iter_frames
else:
self._iter_gen = lambda: iter(frames)
if hasattr(frames, '__len__'):
self.save_count = len(frames)
else:
self._iter_gen = lambda: iter(range(frames))
self.save_count = frames
if self.save_count is None:
# If we're passed in and using the default, set save_count to 100.
self.save_count = 100
else:
# itertools.islice returns an error when passed a numpy int instead
# of a native python int (http://bugs.python.org/issue30537).
# As a workaround, convert save_count to a native python int.
self.save_count = int(self.save_count)
self._cache_frame_data = cache_frame_data
# Needs to be initialized so the draw functions work without checking
self._save_seq = []
TimedAnimation.__init__(self, fig, **kwargs)
# Need to reset the saved seq, since right now it will contain data
# for a single frame from init, which is not what we want.
self._save_seq = []
[docs] def new_frame_seq(self):
# Use the generating function to generate a new frame sequence
return self._iter_gen()
[docs] def new_saved_frame_seq(self):
# Generate an iterator for the sequence of saved data. If there are
# no saved frames, generate a new frame sequence and take the first
# save_count entries in it.
if self._save_seq:
# While iterating we are going to update _save_seq
# so make a copy to safely iterate over
self._old_saved_seq = list(self._save_seq)
return iter(self._old_saved_seq)
else:
if self.save_count is not None:
return itertools.islice(self.new_frame_seq(), self.save_count)
else:
frame_seq = self.new_frame_seq()
def gen():
try:
for _ in range(100):
yield next(frame_seq)
except StopIteration:
pass
else:
cbook.warn_deprecated(
"2.2", message="FuncAnimation.save has truncated "
"your animation to 100 frames. In the future, no "
"such truncation will occur; please pass "
"'save_count' accordingly.")
return gen()
def _init_draw(self):
# Initialize the drawing either using the given init_func or by
# calling the draw function with the first item of the frame sequence.
# For blitting, the init_func should return a sequence of modified
# artists.
if self._init_func is None:
self._draw_frame(next(self.new_frame_seq()))
else:
self._drawn_artists = self._init_func()
if self._blit:
if self._drawn_artists is None:
raise RuntimeError('The init_func must return a '
'sequence of Artist objects.')
for a in self._drawn_artists:
a.set_animated(self._blit)
self._save_seq = []
def _draw_frame(self, framedata):
if self._cache_frame_data:
# Save the data for potential saving of movies.
self._save_seq.append(framedata)
# Make sure to respect save_count (keep only the last save_count
# around)
self._save_seq = self._save_seq[-self.save_count:]
# Call the func with framedata and args. If blitting is desired,
# func needs to return a sequence of any artists that were modified.
self._drawn_artists = self._func(framedata, *self._args)
if self._blit:
if self._drawn_artists is None:
raise RuntimeError('The animation function must return a '
'sequence of Artist objects.')
self._drawn_artists = sorted(self._drawn_artists,
key=lambda x: x.get_zorder())
for a in self._drawn_artists:
a.set_animated(self._blit)
参考链接:
matplotlib[03]python 绘制动画animationblog.chenzhaoqiang.com 如何用 Matplotlib 画 GIF 动图www.tuicool.com![0df84bea25c1361013f3e79c159b7307.png](https://img-blog.csdnimg.cn/img_convert/0df84bea25c1361013f3e79c159b7307.png)