高级应用:动画制作

动画是展示数据随时间变化或动态过程的强大方式。Matplotlib 的 animation 模块提供了创建动画的工具, 让你能够轻松制作动态图表。本章将带你从零开始掌握动画制作,包括基础用法、保存动画以及实际案例。

🎬 1. 动画模块简介

Matplotlib 的动画功能主要通过 matplotlib.animation 实现。最常用的类是:

  • FuncAnimation:通过反复调用一个更新函数来生成动画,适合大多数场景。
  • ArtistAnimation:使用预先绘制的一系列艺术家对象生成动画,适合已有静态图像序列。

首先需要导入模块:

import matplotlib.animation as animation

🌊 2. 第一个动画:动态正弦波

使用 FuncAnimation 创建一个移动的正弦波:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

# 准备数据
x = np.linspace(0, 2*np.pi, 100)
fig, ax = plt.subplots()
line, = ax.plot(x, np.sin(x))

def update(frame):
    # frame 从0到99
    line.set_ydata(np.sin(x + frame/10))  # 相位移动
    return line,

ani = FuncAnimation(fig, update, frames=100, interval=50, blit=True)
plt.show()
✔️ frames 指定帧数,interval 为帧间隔(毫秒),blit=True 优化重绘速度。

⚙️ 3. FuncAnimation 参数详解

参数说明示例
fig动画所在的图形对象fig
func每一帧调用的更新函数,接收帧号作为参数update
frames帧数(整数)或可迭代对象100range(50)
init_func初始化函数(可选),返回初始艺术家init
fargs传递给更新函数的额外参数(param1, param2)
interval帧间隔(毫秒)50
repeat是否循环播放True (默认)
blit是否只重绘变化的区域(提升性能)True

🔄 4. 使用 init_func 初始化

在动画开始前,有时需要设置初始状态,可以定义 init_func

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

x = np.linspace(0, 2*np.pi, 100)
fig, ax = plt.subplots()
line, = ax.plot([], [])  # 初始为空
ax.set_xlim(0, 2*np.pi)
ax.set_ylim(-1.5, 1.5)

def init():
    line.set_data([], [])
    return line,

def update(frame):
    y = np.sin(x + frame/10)
    line.set_data(x, y)
    return line,

ani = FuncAnimation(fig, update, frames=100, init_func=init, blit=True)
plt.show()

💾 5. 保存动画

保存动画需要安装额外的依赖:

  • GIF:需要 imagemagickpillow(writer='pillow')
  • MP4:需要 ffmpeg
注意: 如果未安装相应程序,保存可能会失败。建议先测试。
# 保存为 GIF(需要 pillow 或 imagemagick)
ani.save('sine_wave.gif', writer='pillow', fps=20)  # fps 为每秒帧数

# 保存为 MP4(需要 ffmpeg)
ani.save('sine_wave.mp4', writer='ffmpeg', fps=20)

# 也可以指定额外参数,如 dpi
ani.save('sine_wave.mp4', writer='ffmpeg', fps=20, dpi=200)

⚫ 6. 动态散点图

模拟粒子运动:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

np.random.seed(42)
num_points = 50
x = np.random.randn(num_points)
y = np.random.randn(num_points)
colors = np.random.rand(num_points)
sizes = np.random.rand(num_points) * 100

fig, ax = plt.subplots()
scat = ax.scatter(x, y, c=colors, s=sizes, alpha=0.6)
ax.set_xlim(-3, 3)
ax.set_ylim(-3, 3)

def update(frame):
    # 随机移动
    x_new = x + np.random.randn(num_points) * 0.1
    y_new = y + np.random.randn(num_points) * 0.1
    scat.set_offsets(np.c_[x_new, y_new])
    return scat,

ani = FuncAnimation(fig, update, frames=200, interval=50, blit=True)
plt.show()

📊 7. 动态条形图(竞赛图)

模拟数据随时间变化:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

categories = ['A', 'B', 'C', 'D', 'E']
initial_values = [10, 15, 7, 12, 9]

fig, ax = plt.subplots()
bars = ax.bar(categories, initial_values, color='skyblue')
ax.set_ylim(0, 30)

def update(frame):
    # 随机增减
    new_vals = [v + np.random.randn()*2 for v in initial_values]
    for bar, val in zip(bars, new_vals):
        bar.set_height(val)
    return bars

ani = FuncAnimation(fig, update, frames=100, interval=200, blit=False)  # bar 不支持 blit 时设为 False
plt.show()
✔️ 对于某些复杂的艺术家,可能需要关闭 blit。

⏯️ 8. 控制动画:暂停/继续

通过鼠标点击可以控制动画的播放:

import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

fig, ax = plt.subplots()
x = np.linspace(0, 2*np.pi, 100)
line, = ax.plot(x, np.sin(x))

def update(frame):
    line.set_ydata(np.sin(x + frame/10))
    return line,

ani = FuncAnimation(fig, update, frames=100, interval=50, blit=True)

def on_click(event):
    if ani.running:
        ani.event_source.stop()
        ani.running = False
    else:
        ani.event_source.start()
        ani.running = True

ani.running = True
fig.canvas.mpl_connect('button_press_event', on_click)
plt.show()

🖼️ 9. ArtistAnimation:预先准备好的帧

如果已经生成了多幅静态图,可以用 ArtistAnimation 合成动画:

import matplotlib.pyplot as plt
from matplotlib.animation import ArtistAnimation

fig, ax = plt.subplots()
x = np.linspace(0, 2*np.pi, 100)
artists = []

for phase in np.linspace(0, 2*np.pi, 50):
    line, = ax.plot(x, np.sin(x + phase), 'b')
    artists.append([line])

ani = ArtistAnimation(fig, artists, interval=50, blit=True)
plt.show()

📈 10. 模拟实时数据流

不断添加新数据点,保持窗口固定长度:

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation

fig, ax = plt.subplots()
max_len = 100
x_data, y_data = [], []
line, = ax.plot([], [])

def init():
    ax.set_xlim(0, max_len)
    ax.set_ylim(-2, 2)
    return line,

def update(frame):
    # 模拟新数据
    new_y = np.random.randn() * 0.5
    y_data.append(new_y)
    x_data.append(frame)
    if len(x_data) > max_len:
        x_data.pop(0)
        y_data.pop(0)
    line.set_data(x_data, y_data)
    return line,

ani = FuncAnimation(fig, update, frames=np.arange(0, 200), init_func=init,
                    interval=100, blit=True)
plt.show()

❓ 11. 常见问题

  • 动画卡顿:尝试使用 blit=True,并确保更新函数只返回已改变的艺术家。
  • 保存时提示找不到 writer:安装相应程序(ffmpeg/ImageMagick)并确保在系统PATH中。
  • 在 Jupyter Notebook 中无法显示动画:使用 %matplotlib notebook%matplotlib widget,或者使用 animation.HTML 内嵌显示。
  • 保存的 GIF 背景为白色:设置 savefig_kwargs={'facecolor': 'white'}
提示: 在 Jupyter Notebook 中可以使用 from IPython.display import HTML; HTML(ani.to_html5_video()) 直接嵌入视频。

🏆 12. 综合示例:动态双摆

展示更复杂的物理模拟动画(代码较长,仅作演示):

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from scipy.integrate import odeint

# 双摆运动方程(略)
# ... 完整代码参考官方示例

# 这里省略具体实现,仅说明可创建复杂动画

📚 13. 更多资源


通过本章的学习,你已掌握了使用 Matplotlib 创建动画的基本和高级技巧。动画能为数据故事增添动态魅力,是数据展示的强力工具。下一章我们将探讨性能优化,帮助你在处理大规模数据时保持绘图效率。