You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Matplotlib生成MP4动画时如何实现两个函数曲线依次分层绘制

问题描述

运行环境

  • Windows 10
  • Python 3.6
  • Matplotlib 3.3.2

实现需求

需要制作MP4格式的Matplotlib分阶段动画,效果要求:

  1. 第一阶段:动态逐点绘制平方根(sqr,即y=x^(1/2))函数曲线
  2. 第一阶段结束后,sqr曲线完整固定显示在画布上
  3. 第二阶段:在sqr曲线上层,动态逐点绘制立方根(cub,即y=x^(1/3))函数曲线
    禁止两条曲线同步动态推进,必须实现递进展示效果

现存问题

先后创建两个FuncAnimation对象时出现覆盖问题,实际运行效果为:sqr曲线直接完整显示,仅cub曲线逐点动态绘制,不符合预期。原有测试代码如下:

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

# params
plt.rcParams['animation.ffmpeg_path'] = 'C:/xxx/ffmpeg.exe'

x_val = np.arange(0, 10)
lbl = ['sqr', 'cub', ]

# plot
fig, ax = plt.subplots(figsize=(4,2))
line_2, = ax.plot([], [])
line_3, = ax.plot([], [])
ax.set_xlim(x_val.min(), x_val.max())
ax.set_ylim(0, 4)
ax.set_title('sqr & cub')
ax.legend(lbl, bbox_to_anchor=(1.3, 1))
plt.tight_layout()


def func_2(x):
    return x**(1/2)


def func_3(x):
    return x**(1/3)



X, Y_2, Y_3 = [], [], []
def animation_frame_2(x):
    X.append(x)
    Y_2.append(func_2(x))
    line_2.set_xdata(X)
    line_2.set_ydata(Y_2)
    return line_2,


def animation_frame_3(x):
    X.append(x)
    Y_2 = func_2(x_val)
    line_2.set_xdata(x_val)
    line_2.set_ydata(Y_2)
    Y_3.append(func_3(x))
    line_3.set_xdata(X)
    line_3.set_ydata(Y_3)
    return line_2, line_3,
   
anim = animation.FuncAnimation(fig,
                               func=animation_frame_2,
                               frames=x_val,
                               interval=100,
                               repeat=False)


anim = animation.FuncAnimation(fig,
                               func=animation_frame_3,
                               frames=x_val,
                               interval=100,
                               repeat=False)
plt.show()

问题原因

Matplotlib中同一画布同一时间仅会运行最后一个被赋值的FuncAnimation实例,之前创建的动画实例会被垃圾回收直接失效,因此原有代码中第一个sqr绘制动画根本没有执行,直接运行的是第二个动画逻辑——而第二个动画的更新函数第一行就直接把完整的sqr曲线赋值给了line_2,所以才会出现sqr直接全量显示、只有cub逐点绘制的问题。

解决方案

不需要创建两个FuncAnimation对象,把两段动画逻辑合并到同一个更新函数里,通过帧序号判断当前处于哪个绘制阶段即可:

  1. 把总帧数设置为两个阶段的帧数之和,前N帧绘制sqr曲线,后N帧绘制cub曲线
  2. 前一阶段绘制完成后,直接把完整的sqr曲线数据固定到对应line对象上,再开始逐点绘制cub
  3. 导出MP4时直接调用anim.save()方法即可
    修正后的可运行代码:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation

# 配置ffmpeg路径
plt.rcParams['animation.ffmpeg_path'] = 'C:/xxx/ffmpeg.exe'

# 基础数据准备
x_val = np.arange(0, 10)
point_count = len(x_val)
lbl = ['sqr', 'cub']

# 画布初始化
fig, ax = plt.subplots(figsize=(4,2))
line_sqr, = ax.plot([], [], label='sqr')
line_cub, = ax.plot([], [], label='cub')
ax.set_xlim(x_val.min(), x_val.max())
ax.set_ylim(0, 4)
ax.set_title('sqr & cub')
ax.legend(lbl, bbox_to_anchor=(1.3, 1))
plt.tight_layout()

# 函数定义
def func_sqr(x):
    return x**(1/2)

def func_cub(x):
    return x**(1/3)

# 预计算全量sqr数据,第一阶段结束后直接调用
full_sqr_y = func_sqr(x_val)
cub_x, cub_y = [], []

def update(frame):
    # 前point_count帧:逐点绘制sqr曲线
    if frame < point_count:
        current_x = x_val[:frame+1]
        current_y = func_sqr(current_x)
        line_sqr.set_data(current_x, current_y)
        return line_sqr,
    # 帧序号超过point_count后:进入第二阶段,先固定完整sqr,再逐点绘制cub
    else:
        # 固定sqr为完整曲线
        line_sqr.set_data(x_val, full_sqr_y)
        # 计算当前cub要绘制到的点位置
        cub_frame_idx = frame - point_count
        cub_x.append(x_val[cub_frame_idx])
        cub_y.append(func_cub(x_val[cub_frame_idx]))
        line_cub.set_data(cub_x, cub_y)
        return line_sqr, line_cub,

# 总帧数为两个阶段帧数之和,每帧间隔100ms
anim = animation.FuncAnimation(
    fig,
    func=update,
    frames=point_count * 2,
    interval=100,
    repeat=False,
    blit=True
)

# 预览效果打开plt.show(),导出MP4注释掉show打开save即可
plt.show()
# anim.save('func_anim.mp4', writer='ffmpeg', fps=10, dpi=200)

效果说明

  • 前10帧逐点绘制平方根曲线,和第一阶段预期效果完全一致
  • 第11帧开始,平方根曲线直接固定为完整形态,开始逐点绘制上层的立方根曲线
  • 全程只有一个FuncAnimation实例,不存在实例覆盖、垃圾回收导致动画失效的问题
  • 导出MP4时直接调用save方法即可,不需要额外调整逻辑

内容的提问来源于stack exchange,提问作者Cy Bu

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 04:30:54