用matplotlib的Animation画动图

  |  

摘要: 用 matplotlib 的 Animation 画动画的方法和例子

【对数据分析、人工智能、金融科技、风控服务感兴趣的同学,欢迎关注我哈,阅读更多原创文章】
我的网站:潮汐朝夕的生活实验室
我的公众号:潮汐朝夕
我的知乎:潮汐朝夕
我的github:FennelDumplings
我的leetcode:FennelDumplings


我们在使用 matplotlib 时,常用的是 pyplot,可以画各种静态图,常见的代码模板如下

1
2
3
4
5
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.plot(...)
ax.plot(...)
plt.show()

有时我们希望画一些动态图,类似于放动画片的那种效果,例如再用算法拟合曲线时候,想要每隔几步就画一下当前拟合的曲线。

在 matplotlib 中,不仅可以绘制静态图形,也可以绘制动态图形。在 Matplotlib 中绘制动态图形的方法主要有两种,第一种是用模块 animation,另一种是用 pyplot 的 API。但是如果要存 gif 图片的话,还是要用 animation 模块。

animation 的用法

用 animation 绘制复杂动画,最核心的是 FuncAnimation 这个类。下面我们看一下 FuncAnimation 的用法。

1
FuncAnimation(fig, func, frames, init_func, interval, blit)

主要参数的含义如下

  • fig: 绘制动图的画布名称
  • func: 回调函数,每次更新时调用,即下边程序定义的函数update,可以在这个方法中更新 line2d 对象。
  • frames: 整个动画 frame 的取值范围,在函数运行时,其值会传递给函数 update(n) 的形参 n。
  • init_func: 自定义开始帧,即传入刚定义的函数init,初始化函数。
  • interval: frame之间的更新频率,以 ms 计。
  • blit: 选择更新所有点,还是仅更新产生变化的点。

frames

以上 FuncAnimation 构造函数中的参数中,frames 参数可以取值 iterable, int, generator 或者 None。如果取整数 n,相当于给参数赋值 range(n)

frames 会在 interval 时间内迭代一次,然后将值传给 func,直至 frames 迭代完毕。

init 与 update

init 的作用是绘制下一帧画面前清空画布窗口的当前画面。update 的作用是绘制每帧画面

注意 init 和 update 的返回值的逗号不可省略,如果不带逗号,返回的是 list,带了逗号之后,返回的才是 Line2D 对象。类似地,ax.plot 返回值 line 后面也要加逗号。

保存 gif 或 mp4

用 FuncAnimation 生成的动图,如果要存 mp4,需要安装 ffmpeg,如果要存 gif,需要安装 imagemagick

1
2
ani.save("animation.mp4", fps=20, writer="ffmpeg")
ani.save("animation.gif", fps=50, writer="imagemagick")

例子

例子1: 每一轮 x 不变,清空并一次性更新 y

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)

x = np.linspace(0, 2 * np.pi, 5000)
y = np.exp(-x) * np.cos(2 * np.pi * x)
line, = ax.plot(x, y, color="cornflowerblue", lw=3)
ax.set_ylim(-1.1, 1.1)

# 清空当前帧
def init():
line.set_ydata([np.nan] * len(x))
return line,

# 更新新一帧的数据
def update(frame):
line.set_ydata(np.exp(-x) * np.cos(2 * np.pi * x + float(frame)/100))
return line,

# 调用 FuncAnimation
ani = FuncAnimation(fig
,update
,init_func=init
,frames=200
,interval=2
,blit=True
)

ani.save("animation.gif", fps=25, writer="imagemagick")

例子2: 每一轮同时新增 x 和 y 的一个点

一个点一个点地画 sin 函数,每一帧新增一个点 (x, y)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
line, = plt.plot([], [], "r-", animated=True)
x = []
y = []

def init():
ax.set_xlim(-np.pi, np.pi)
ax.set_ylim(-1, 1)
return line,

def update(frame):
x.append(frame)
y.append(np.sin(frame))
line.set_data(x, y)
return line,

ani = FuncAnimation(fig
,update
,frames=np.linspace(-np.pi ,np.pi, 90)
,interval=10
,init_func=init
,blit=True
)
ani.save("animation.gif", fps=25, writer="imagemagick")

例子3: 同时画两条线,每轮新增两个点

代码与例子2整体差不多,但是是同时画两条线。每一轮新增两个点 (x, y1), (x, y2)

代码中的 init 函数只有一个壳,实际去掉也可以。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)
line1, = ax.plot(x, y1, color='k')
line2, = ax.plot(x, y2, color='b')

def init():
return line1, line2,

def update(num):
line1.set_data(x[:num], y1[:num])
line2.set_data(x[:num], y2[:num])
return line1, line2

ani = FuncAnimation(fig
,update
,init_func=init
,frames=len(x)
,interval=25
,blit=True
)

ani.save("3.gif", fps=25, writer="imagemagick")

例子4: 同时画两条线,每轮重新画两条线

每秒更新一帧,FuncAnimation 对象的 interval 设为 1000(ms),save 时 fps 设为 1。

在 update 中设置 ax 属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.animation import FuncAnimation
import matplotlib.font_manager as fm

font = fm.FontProperties(fname="./simsun.ttc")

fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.set_title("动态图", fontproperties=font)
ax.grid(True)
ax.set_xlabel("X轴", fontproperties=font)
ax.set_ylabel("Y轴", fontproperties=font)
line1, = ax.plot([], [], "b--", linewidth=2.0, label="sin示例")
line2, = ax.plot([], [], "g+", linewidth=2.0, label="cos示例")
ax.legend(loc="upper left", prop=font, shadow=True)

def init():
line1, = ax.plot([], [], "b--", linewidth=2.0, label="sin示例")
line2, = ax.plot([], [], "g+", linewidth=2.0, label="cos示例")
return line1, line2

def update(frame):
x = np.linspace(-np.pi + 0.1 * frame, np.pi + 0.1 * frame, 256, endpoint=True)
y_cos, y_sin = np.cos(x), np.sin(x)

ax.set_xlim(-4 + 0.1 * frame, 4 + 0.1 * frame)
ax.set_xticks(np.linspace(-4 + 0.1 * frame, 4 + 0.1 * frame, 9, endpoint=True))
ax.set_ylim(-1.0, 1.0)
ax.set_yticks(np.linspace(-1, 1, 9, endpoint=True))

line1, = ax.plot(x, y_cos, "b--", linewidth=2.0, label="sin示例")
line2, = ax.plot(x, y_sin, "g+", linewidth=2.0, label="cos示例")

return line1, line2

ani = FuncAnimation(fig
,update
,init_func=init
,frames=np.linspace(-5 ,5, 5)
,interval=1000
,blit=True
)

ani.save("4.gif", fps=1, writer="imagemagick")


Share