无法在Tkinter窗口嵌入多个Matplotlib动态图的技术问题
问题描述
- 首次点击“Live Secondary Mission Graphs”按钮时,次级动态图表可嵌入GUI并正常动画,但会生成两个空白的主图表(分别由
primary对象和secondary对象创建)。 - 后续点击“Live Primary Mission Graphs”按钮时,仅重载图表但GUI中无新图表渲染,终端弹出警告:
UserWarning: Animation was deleted without rendering anything. This is most likely not intended. To prevent deletion, assign the Animation to a variable, e.g.
anim, that exists until you output the Animation usingplt.show()oranim.save().
问题根源
- 使用
plt.gcf()获取当前图表时,会混淆不同对象创建的Figure实例,导致动画绑定到错误的图表。 plt.show()会阻塞Tkinter主线程,且在Matplotlib嵌入GUI的场景下无需调用,应依赖Tkinter自身的事件循环。- 动画对象(
ani/anisecondary)是局部变量,函数执行结束后被垃圾回收,触发警告。 plt.cla()会清除当前活跃轴,但未明确指定目标轴,导致误操作其他图表的子轴。
解决方案
1. 绑定动画到自身实例的Figure
在图表类中创建FuncAnimation时,直接使用自身初始化的primaryfig/secondaryfig,而非全局的plt.gcf()。
2. 将动画保存为实例属性
把动画对象赋值给实例变量(如self.ani),避免函数执行结束后被垃圾回收。
3. 移除plt.show()
Matplotlib嵌入Tkinter时,图表渲染由FigureCanvasTkAgg负责,无需调用plt.show()。
4. 明确清除指定子轴
替换plt.cla()为针对每个子轴调用cla(),确保只操作当前图表的轴。
修改后的PrimaryGraphData类
import pandas as pd from matplotlib.animation import FuncAnimation import matplotlib.pyplot as plt class PrimaryGraphData(): def __init__(self) -> None: self.PRIMARYDATASOURCE = "data.csv" self.primaryfig, self.primaryaxs = plt.subplots(2, 2) self.ani = None # 初始化动画对象属性 def animate(self, i): data = pd.read_csv(self.PRIMARYDATASOURCE) time = data["Time"] temp = data["Temperature"] press = data["Pressure"] alt = data["Altitude"] # 明确清除每个子轴 self.primaryaxs[0, 0].cla() self.primaryaxs[0, 1].cla() self.primaryaxs[1, 0].cla() self.primaryaxs[1, 1].cla() # 绘制子图 self.primaryaxs[0, 0].plot(time, temp, "tab:orange") self.primaryaxs[0, 1].plot(time, press, "tab:green") self.primaryaxs[1, 0].plot(alt, temp) self.primaryaxs[1, 1].plot(time, alt, "tab:blue") def plot_primary_graphs(self): # 绑定到自身的Figure实例 self.ani = FuncAnimation(self.primaryfig, func=self.animate, interval=500, frames=50) self.primaryfig.tight_layout()
修改后的SecondaryGraphData类
import pandas as pd from matplotlib.animation import FuncAnimation import matplotlib.pyplot as plt class SecondaryGraphData(): def __init__(self) -> None: self.SECONDARYDATASOURCE = "data2.csv" self.secondaryfig, self.secondaryaxs = plt.subplots(2, 2) self.ani = None # 初始化动画对象属性 def secondaryanimate(self, i): data = pd.read_csv(self.SECONDARYDATASOURCE) time = data["Time"] alt = data["Altitude"] TVOC = data["TVOC"] eCO2 = data["eCO2"] H2 = data["H2"] Ethanol = data["Ethanol"] # 明确清除每个子轴 self.secondaryaxs[0, 0].cla() self.secondaryaxs[0, 1].cla() self.secondaryaxs[1, 0].cla() self.secondaryaxs[1, 1].cla() # 绘制子图 self.secondaryaxs[0, 0].plot(time, TVOC, "tab:green") self.secondaryaxs[0, 1].plot(alt, eCO2, "tab:green") self.secondaryaxs[1, 0].plot(alt, H2) self.secondaryaxs[1, 1].plot(time, Ethanol, "tab:blue") def plot_secondary_graphs(self): # 绑定到自身的Figure实例 self.ani = FuncAnimation(self.secondaryfig, func=self.secondaryanimate, interval=500, frames=50) self.secondaryfig.tight_layout()
修改后的GUI类(App)
import customtkinter as ctk from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk) from data_processing.primary_graph_data import PrimaryGraphData from data_processing.secondary_graph_data import SecondaryGraphData class App(ctk.CTk): def __init__(self) -> None: super().__init__() self.geometry(f"{1100}x{580}") self.tabview = ctk.CTkTabview(self, width=1080, height=570) self.tabview.pack() self.tabview.add("Live Primary Mission Graphs") self.tabview.add("Live Secondary Mission Graphs") # LIVE GRAPHS SETUP self.primary = PrimaryGraphData() # 保存为实例属性,避免被回收 canvas_primary = FigureCanvasTkAgg(self.primary.primaryfig, self.tabview.tab("Live Primary Mission Graphs")) canvas_primary.draw() canvas_primary.get_tk_widget().grid(row=0, column=0) primary_graphs_button = ctk.CTkButton( self.tabview.tab("Live Primary Mission Graphs"), text="Start Primary Mission Graphs", command=self.primary.plot_primary_graphs ) primary_graphs_button.place(x=850, y=190) self.secondary = SecondaryGraphData() # 保存为实例属性,避免被回收 canvas_secondary = FigureCanvasTkAgg(self.secondary.secondaryfig, self.tabview.tab("Live Secondary Mission Graphs")) canvas_secondary.draw() canvas_secondary.get_tk_widget().grid(row=0, column=0) secondary_graphs_button = ctk.CTkButton( self.tabview.tab("Live Secondary Mission Graphs"), text="Start Secondary Mission Graphs", command=self.secondary.plot_secondary_graphs ) secondary_graphs_button.place(x=850, y=190) if __name__ == "__main__": app = App() app.mainloop()
内容的提问来源于stack exchange,提问作者theojs
相关产品推荐
相关产品推荐

