动画是一种高效的可视化工具,能够提升用户的吸引力和视觉体验,有助于以富有意义的方式呈现数据可视化。本文的主要介绍在Python中两种简单制作动图的方法。其中一种方法是使用matplotlib的Animations模块绘制动图,另一种方法是基于Pillow生成GIF动图。
python学习资料已打包好,需要的小伙伴可以戳这里【python资料】
1 Animations模块
Matplotlib的Animations模块提供了FuncAnimation和ArtistAnimation类来创建matplotlib绘图动画,FuncAnimation和ArtistAnimation都是Animation类的子类。它们的区别在于实现动画的方式和使用场景不同。FuncAnimation适用于根据时间更新图形状态的动画效果,且更加灵活和常用。而ArtistAnimation适用于将已有的静态图像序列组合成动画的效果。具体区别如下:
-
FuncAnimation
:FuncAnimation是基于函数的方法来创建动画的。它使用用户提供的一个或多个函数来更新图形的状态,并按照一定的时间间隔连续地调用这些函数,从而实现动画效果。用户需要定义一个更新函数,该函数在每个时间步长上更新图形对象的属性,然后FuncAnimation会根据用户指定的帧数、时间间隔等参数来自动计算动画的帧序列。这种方法适用于需要根据时间变化来更新图形状态的动画效果。 -
ArtistAnimation
:ArtistAnimation是基于静态图像的方法来创建动画的。它要求用户提供一系列的静态图像,称为艺术家对象。这些图像可以是通过Matplotlib创建的任何类型的可视化对象,例如Figure、Axes、Line2D等。用户需要将这些静态图像存储在一个列表中,然后通过ArtistAnimation来显示这些图像的序列。ArtistAnimation会按照用户指定的时间间隔逐帧地显示这些图像,从而实现动画效果。这种方法适用于已经有一系列静态图像需要组合成动画的场景。
本节将通过几个示例来介绍Animations模块的使用,所介绍的示例出自:gallery-animation。
1.1 FuncAnimation类
FuncAnimation构造函数的参数含义如下:
fig
:要绘制动画的Figure对象。func
:用于更新每一帧的函数,该函数接受一个参数frame,表示当前待绘制的数据帧。frames
:用于产生待绘制的数据,可以是整数、生成器函数或迭代器。init_func
:在绘制动画之前调用的初始化函数。fargs
:传递给func
函数的附加参数(可选)。save_count
:指定动画中缓存的帧数量(可选),默认为100。注意该参数用于确定最后生成动图和视频所用图像的数量。interval
:每一帧之间的时间间隔,以毫秒为单位,默认为200。repeat
:控制动画是否重复播放,默认为True。repeat_delay
:重复动画之间的延迟时间(以毫秒为单位),默认为0。blit
:指定是否使用blitting技术来进行绘制优化,默认为False。cache_frame_data
:指定是否缓存帧数据,默认为True。
示例-生成动态的正弦波动画
import itertools |
|
import matplotlib.pyplot as plt |
|
import numpy as np |
|
import matplotlib.animation as animation |
|
# 定义生成数据的函数 |
|
def data_gen(max_range): |
|
# 使用itertools.count()生成无限递增的计数器 |
|
for cnt in itertools.count(): |
|
# 当计数器超过最大范围时停止生成数据 |
|
if cnt > max_range: |
|
break |
|
print(cnt) |
|
# 计算时间t和对应的y值,使用np.sin()计算sin函数,np.exp()计算指数函数 |
|
t = cnt / 10 |
|
yield t, np.sin(2*np.pi*t) * np.exp(-t/10.) |
|
# 初始化函数,设置坐标轴范围和清空数据 |
|
def init(): |
|
ax.set_ylim(-1.1, 1.1) |
|
ax.set_xlim(0, 1) |
|
del xdata[:] |
|
del ydata[:] |
|
line.set_data(xdata, ydata) |
|
return line, |
|
# 创建图形对象以及子图对象 |
|
fig, ax = plt.subplots() |
|
# 创建线条对象 |
|
line, = ax.plot([], [], lw=2) |
|
# 创建文本对象用于显示 x 和 y 值 |
|
text = ax.text(0., 0., '', transform=ax.transAxes) |
|
# 设置文本位置 |
|
text.set_position((0.7, 0.95)) |
|
# 将文本对象添加到图形中 |
|
ax.add_artist(text) |
|
ax.grid() |
|
xdata, ydata = [], [] |
|
# 更新函数,将新的数据添加到图形中 |
|
def run(data): |
|
# 获取传入的数据 |
|
t, y = data |
|
# 将时间和对应的y值添加到xdata和ydata中 |
|
xdata.append(t) |
|
ydata.append(y) |
|
# 获取当前坐标轴的范围 |
|
xmin, xmax = ax.get_xlim() |
|
# 更新文本对象的值 |
|
text.set_text('x = {:.2f}, y = {:.2f}'.format(t, y)) |
|
# 如果时间t超过当前范围,更新坐标轴范围 |
|
if t >= xmax: |
|
ax.set_xlim(xmin, 2*xmax) |
|
# 重绘图形 |
|
ax.figure.canvas.draw() |
|
# 更新线条的数据 |
|
line.set_data(xdata, ydata) |
|
return line, text |
|
# 创建动画对象 |
|
# fig:图形对象 |
|
# run:更新函数,用于更新图形中的数据 |
|
# data_gen(20):生成器函数,产生数据的最大范围为20 |
|
# interval=100:每帧动画的时间间隔为100毫秒 |
|
# init_func=init:初始化函数,用于设置图形的初始状态 |
|
# repeat=True:动画重复播放 |
|
ani = animation.FuncAnimation(fig, run, data_gen(20), interval=100, init_func=init, repeat=True) |
|
# 显示图形 |
|
plt.show() |
示例-创建动态散点图与折线图
import matplotlib.pyplot as plt |
|
import numpy as np |
|
import matplotlib.animation as animation |
|
# 创建一个图形窗口和坐标轴 |
|
fig, ax = plt.subplots() |
|
# 创建时间数组 |
|
t = np.linspace(0, 3, 50) |
|
# 自由落体加速度 |
|
g = -9.81 |
|
# 初始速度 |
|
v0 = 12 |
|
# 计算高度 |
|
z = g * t**2 / 2 + v0 * t |
|
# 第二个初始速度 |
|
v02 = 5 |
|
# 计算第二个高度 |
|
z2 = g * t**2 / 2 + v02 * t |
|
# 创建散点图 |
|
scat = ax.scatter(t[0], z[0], c="b", s=5, label=f'v0 = {v0} m/s') |
|
# 创建线图 |
|
line2 = ax.plot(t[0], z2[0], label=f'v0 = {v02} m/s')[0] |
|
# 设置坐标轴范围和标签 |
|
ax.set(xlim=[0, 3], ylim=[-4, 10], xlabel='Time [s]', ylabel='Z [m]') |
|
# 添加图例 |
|
ax.legend() |
|
def update(frame): |
|
x = t[:frame] |
|
y = z[:frame] |
|
# 更新散点图 |
|
data = np.stack([x, y]).T |
|
# 更新散点图中每个点的位置 |
|
scat.set_offsets(data) |
|
# 更新线图 |
|
line2.set_xdata(t[:frame]) |
|
line2.set_ydata(z2[:frame]) |
|
return (scat, line2) |
|
# 创建动画 |
|
# frames为数值表示动画的总帧数,即每次更新参数传入当前帧号 |
|
ani = animation.FuncAnimation(fig=fig, func=update, frames=40, interval=30) |
|
# 显示图形 |
|
plt.show() |
示例-贝叶斯更新动画
import math |
|
import matplotlib.pyplot as plt |
|
import numpy as np |
|
from matplotlib.animation import FuncAnimation |
|
# 定义分布概率密度函数 |
|
def beta_pdf(x, a, b): |
|
return (x**(a-1) * (1-x)**(b-1) * math.gamma(a + b) |
|
/ (math.gamma(a) * math.gamma(b))) |
|
# 更新分布类,用于更新动态图 |
|
class UpdateDist: |
|
def __init__(self, ax, prob=0.5): |
|
self.success = 0 |
|
self.prob = prob |
|
self.line, = ax.plot([], [], 'k-') |
|
self.x = np.linspace(0, 1, 200) |
|
self.ax = ax |
|
# 设置图形参数 |
|
self.ax.set_xlim(0, 1) |
|
self.ax.set_ylim(0, 10) |
|
self.ax.grid(True) |
|
# 这条竖直线代表了理论值,图中的分布应该趋近于这个值 |
|
self.ax.axvline(prob, linestyle='--', color='black') |
|
def __call__(self, i): |
|
# 这样图形可以连续运行,我们只需不断观察过程的新实现 |
|
if i == 0: |
|