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
相关产品推荐
相关产品推荐

