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

Matplotlib多独立动画窗口闪烁问题求助

多Matplotlib窗口动画闪烁问题解决方案

问题核心

  • 多Matplotlib窗口同时运行动画时出现严重闪烁,单窗口无此问题
  • 需要外部驱动动画(按需调用更新函数,而非FuncAnimation的内部时钟)
  • 尝试Blitting优化但未正确实现,同时需支持动态坐标轴缩放

原复现代码

import matplotlib.pyplot as plt

class View:
    def __init__(self):
        plt.ion()
        self.sim_fig, self.sim_ax = plt.subplots(num="Simulator")
        self.energy_fig, self.energy_ax = plt.subplots(num="Energy")

        # Set axis limits
        x_min, x_max = (-3, 6)
        y_min, y_max = (-6, 3)
        self.sim_ax.set_xlim(x_min, x_max)
        self.sim_ax.set_ylim(y_min, y_max)
        self.sim_ax.axis('equal')
        self.energy_ax.set_xlim(0, 200)
        self.energy_ax.set_ylim(-20, 20)

        self.times = []
        self.energies = []

        # Create plot objects for animation
        self.posn_pt, = self.sim_ax.plot([], [], 'bo')
        self.energy_data, = self.energy_ax.plot([], [], '.-')

        plt.show(block=False)
        
    def update(self, time, x, y, energy):
        # Allows non-constant updates between steps
        self.times.append(time)

        # Update dot position
        self.posn_pt.set_xdata(x)
        self.posn_pt.set_ydata(y)

        # Append energy
        self.energies.append(energy)
        self.energy_data.set_data(self.times, self.energies)

        plt.draw()


view = View()
from random import uniform
x = 0
y = 0
energy = 0
time = 0

# Simulator is a bit like this (but obviously much more complicated!)
# Time between steps is variable (might not be constant)
view.update(time, x, y, energy)
for i in range(200):
    x += uniform(-0.02, 0.05)
    y += uniform(-0.05, 0.02)
    energy += uniform(-1, 1)
    time += uniform(0, 1)
    view.update(time, x, y, energy)
    plt.pause(0.05)

input("Simulation finished")

尝试的Blitting优化代码(存在错误)

import matplotlib.pyplot as plt

class View:
    def __init__(self):
        plt.ion()
        self.sim_fig, self.sim_ax = plt.subplots(num="Simulator")
        self.sim_fig.canvas.draw()
        self.sim_background = self.sim_fig.canvas.copy_from_bbox(self.sim_ax.bbox)

        self.energy_fig, self.energy_ax = plt.subplots(num="Energy")
        self.energy_fig.canvas.draw()
        self.energy_background = self.energy_fig.canvas.copy_from_bbox(self.energy_ax.bbox)

        # Set axis limits
        x_min, x_max = (-3, 6)
        y_min, y_max = (-6, 3)
        self.sim_ax.set_xlim(x_min, x_max)
        self.sim_ax.set_ylim(y_min, y_max)
        self.sim_ax.axis('equal')
        self.energy_ax.set_xlim(0, 200)
        self.energy_ax.set_ylim(-20, 20)

        self.times = []
        self.energies = []

        # Create plot objects for animation
        self.posn_pt, = self.sim_ax.plot([], [], 'bo')
        self.energy_data, = self.energy_ax.plot([], [], '.-')

        # Part of the flickerless-attempt animation code
        self.sim_fig.canvas.draw()
        self.energy_fig.canvas.draw()

        plt.show(block=False)
        
    def update(self, time, x, y, energy):
        # For flickerless plotting
        self.sim_fig.canvas.restore_region(self.sim_background)
        self.energy_fig.canvas.restore_region(self.energy_background)

        # Allows non-constant updates between steps
        self.times.append(time)

        # Update dot position
        self.posn_pt.set_xdata(x)
        self.posn_pt.set_ydata(y)

        # Append energy
        self.energies.append(energy)
        self.energy_data.set_data(self.times, self.energies)

        self.sim_ax.draw_artist(self.posn_pt)
        self.energy_ax.draw_artist(self.energy_data)
        self.sim_fig.canvas.blit(self.sim_ax.bbox)
        self.energy_fig.canvas.blit(self.energy_ax.bbox)


view = View()
from random import uniform
x = 0
y = 0
energy = 0
time = 0

# Simulator is a bit like this (but obviously much more complicated!)
# Time between steps is variable (might not be constant)
view.update(time, x, y, energy)
for i in range(200):
    x += uniform(-0.02, 0.05)
    y += uniform(-0.05, 0.02)
    energy += uniform(-1, 1)
    time += uniform(0, 1)
    view.update(time, x, y, energy)
    plt.pause(0.05)

input("Simulation finished")

解决方案

1. 修复Blitting核心错误

原Blitting代码的问题:

  • 背景在设置轴范围和绘图对象前保存,导致背景无效
  • 使用plt.pause()触发全局重绘,破坏Blitting优化
  • 未处理画布事件刷新,导致窗口卡顿

修复后的Blitting实现(固定轴范围):

import matplotlib.pyplot as plt
from random import uniform

class View:
    def __init__(self):
        plt.ion()
        # 创建窗口和轴
        self.sim_fig, self.sim_ax = plt.subplots(num="Simulator")
        self.energy_fig, self.energy_ax = plt.subplots(num="Energy")

        # 设置轴范围
        x_min, x_max = (-3, 6)
        y_min, y_max = (-6, 3)
        self.sim_ax.set_xlim(x_min, x_max)
        self.sim_ax.set_ylim(y_min, y_max)
        self.sim_ax.axis('equal')
        self.energy_ax.set_xlim(0, 200)
        self.energy_ax.set_ylim(-20, 20)

        self.times = []
        self.energies = []

        # 创建绘图对象
        self.posn_pt, = self.sim_ax.plot([], [], 'bo')
        self.energy_data, = self.energy_ax.plot([], [], '.-')

        # 先绘制画布,再保存背景(关键:必须在所有轴和绘图对象设置完成后)
        self.sim_fig.canvas.draw()
        self.sim_background = self.sim_fig.canvas.copy_from_bbox(self.sim_ax.bbox)
        self.energy_fig.canvas.draw()
        self.energy_background = self.energy_fig.canvas.copy_from_bbox(self.energy_ax.bbox)

        plt.show(block=False)
        
    def update(self, time, x, y, energy):
        # 恢复背景
        self.sim_fig.canvas.restore_region(self.sim_background)
        self.energy_fig.canvas.restore_region(self.energy_background)

        # 更新数据
        self.times.append(time)
        self.posn_pt.set_xdata(x)
        self.posn_pt.set_ydata(y)
        self.energies.append(energy)
        self.energy_data.set_data(self.times, self.energies)

        # 绘制更新的元素
        self.sim_ax.draw_artist(self.posn_pt)
        self.energy_ax.draw_artist(self.energy_data)

        # Blit更新区域
        self.sim_fig.canvas.blit(self.sim_ax.bbox)
        self.energy_fig.canvas.blit(self.energy_ax.bbox)

        # 刷新事件(替代plt.pause,避免全局重绘)
        self.sim_fig.canvas.flush_events()
        self.energy_fig.canvas.flush_events()


view = View()
x = 0
y = 0
energy = 0
time = 0

view.update(time, x, y, energy)
for i in range(200):
    x += uniform(-0.02, 0.05)
    y += uniform(-0.05, 0.02)
    energy += uniform(-1, 1)
    time += uniform(0, 1)
    view.update(time, x, y, energy)
    # 自定义延迟,避免plt.pause
    plt.waitforbuttonpress(0.05)

input("Simulation finished")

2. 支持动态坐标轴缩放

动态缩放时,轴范围变化会导致背景失效,需要重新绘制轴并保存新背景:

import matplotlib.pyplot as plt
from random import uniform

class View:
    def __init__(self):
        plt.ion()
        self.sim_fig, self.sim_ax = plt.subplots(num="Simulator")
        self.energy_fig, self.energy_ax = plt.subplots(num="Energy")

        self.times = []
        self.energies = []
        self.positions = []

        # 创建绘图对象
        self.posn_pt, = self.sim_ax.plot([], [], 'bo')
        self.energy_data, = self.energy_ax.plot([], [], '.-')

        plt.show(block=False)
        
    def update(self, time, x, y, energy):
        # 更新数据缓存
        self.times.append(time)
        self.positions.append((x, y))
        self.energies.append(energy)

        # 动态更新模拟窗口轴范围(自适应数据)
        xs, ys = zip(*self.positions)
        self.sim_ax.set_xlim(min(xs)-0.5, max(xs)+0.5)
        self.sim_ax.set_ylim(min(ys)-0.5, max(ys)+0.5)
        self.sim_ax.axis('equal')

        # 动态更新能量窗口轴范围
        self.energy_ax.set_xlim(0, max(self.times)+1)
        self.energy_ax.set_ylim(min(self.energies)-2, max(self.energies)+2)

        # 重新绘制整个轴(因为范围变了)
        self.sim_fig.canvas.draw()
        self.energy_fig.canvas.draw()

        # 更新绘图对象数据
        self.posn_pt.set_xdata(x)
        self.posn_pt.set_ydata(y)
        self.energy_data.set_data(self.times, self.energies)

        # Blit更新(动态缩放后无需保存固定背景,直接重绘后blit)
        self.sim_fig.canvas.blit(self.sim_ax.bbox)
        self.energy_fig.canvas.blit(self.energy_ax.bbox)

        # 刷新事件
        self.sim_fig.canvas.flush_events()
        self.energy_fig.canvas.flush_events()


view = View()
x = 0
y = 0
energy = 0
time = 0

view.update(time, x, y, energy)
for i in range(200):
    x += uniform(-0.02, 0.05)
    y += uniform(-0.05, 0.02)
    energy += uniform(-1, 1)
    time += uniform(0, 1)
    view.update(time, x, y, energy)
    plt.waitforbuttonpress(0.05)

input("Simulation finished")

关键优化点总结

  • 避免全局操作:用canvas.flush_events()替代plt.pause(),用窗口单独的blit()替代plt.draw()
  • 正确的Blitting时机:背景必须在所有轴和绘图对象设置完成后保存
  • 动态缩放处理:轴范围变化时,需重新绘制整个轴,再进行Blitting更新
  • 外部驱动兼容:直接在外部循环调用update()函数,完全自定义更新时机和间隔

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 12:55:35