PySimpleGUI绘图GUI打包exe后循环选择无法更新图表求助
解决PyInstaller打包后PySimpleGUI+Matplotlib无法更新图表的问题
我之前碰到过完全一样的问题,打包后图表无法更新通常和绘图资源未清理、后端兼容性或者代码作用域混乱有关。下面是具体的分析和修复方案:
问题根源拆解
- 函数定义位置错误:你把
plotting()放在了while循环内部,每次窗口循环都会重新定义这个函数,在Spyder的交互式环境里没问题,但打包成exe后会导致作用域混乱,第二次调用时函数逻辑无法正常执行。 - 旧绘图资源未释放:每次点击Show都会创建新的
fig对象,但之前的图表窗口没有关闭,打包后Matplotlib的后端会被旧资源阻塞,新图无法渲染。 - Matplotlib后端适配问题:PyInstaller打包时,默认的Matplotlib后端可能和PySimpleGUI依赖的Tkinter环境不兼容,导致后续绘图请求无法触发。
分步修复方案
1. 调整函数位置并清理绘图资源
把plotting()函数移到while循环外面,避免重复定义;同时在每次绘图前调用plt.close('all'),强制关闭所有旧图表,释放资源。
2. 指定兼容的Matplotlib后端
在导入Matplotlib前,明确指定使用TkAgg后端,和PySimpleGUI的Tkinter框架对齐:
import matplotlib matplotlib.use('TkAgg') import matplotlib.pyplot as plt
3. 优化数据处理逻辑
把filtered_df的处理逻辑移到event=="Show"分支内,避免初始状态下values["Liste"]为空时引发错误,同时减少不必要的重复计算。
修正后的完整代码
import PySimpleGUI as sg import matplotlib matplotlib.use('TkAgg') # 指定与Tkinter兼容的后端 import matplotlib.pyplot as plt import pandas as pd def plotting(filtered_df, cycle_num): def make_format(current, other): def format_coord(x, y): display_coord = current.transData.transform((x,y)) inv = other.transData.inverted() ax_coord = inv.transform(display_coord) coords = [ax_coord, (x, y)] return ('Left: {:<40} Right: {:<}' .format(*['({:.3f}, {:.3f})'.format(x, y) for x,y in coords])) return format_coord plt.close('all') # 清理所有旧图表资源 fig, ax = plt.subplots(2,2,sharex=True) fig.suptitle(f"Temperature / Pressure Curves for Cycle-Number {cycle_num}", fontsize=16) # 子图1:t1/p1 pl1_1 = ax[0,0].plot(filtered_df.index, filtered_df["eBar1"], 'r-',label="eBar1") ax2 = ax[0,0].twinx() ax2.format_coord = make_format(ax2, ax[0,0]) pl1_2 = ax2.plot(filtered_df.index, filtered_df["eCelcius1"], 'g-',label="eCelcius1") plots1_1 = pl1_1 + pl1_2 legends_1 = [l.get_label() for l in plots1_1] ax[0,0].legend(plots1_1, legends_1, loc="best") # 子图2:t2/p2 pl2_1 = ax[0,1].plot(filtered_df.index, filtered_df["eBar2"], 'r-',label="eBar2") ax2_2 = ax[0,1].twinx() ax2_2.format_coord = make_format(ax2_2, ax[0,1]) pl2_2 = ax2_2.plot(filtered_df.index, filtered_df["eCelcius2"], 'g-',label="eCelcius2") plots2_2 = pl2_1 + pl2_2 legends_2 = [l.get_label() for l in plots2_2] ax[0,1].legend(plots2_2, legends_2, loc="best") # 子图3:t3/p3 pl3_1 = ax[1,0].plot(filtered_df.index, filtered_df["eBar3"], 'r-',label="eBar3") ax3_2 = ax[1,0].twinx() ax3_2.format_coord = make_format(ax3_2, ax[1,0]) pl3_2 = ax3_2.plot(filtered_df.index, filtered_df["eCelcius3"], 'g-',label="eCelcius3") plots3_2 = pl3_1 + pl3_2 legends_3 = [l.get_label() for l in plots3_2] ax[1,0].legend(plots3_2, legends_3, loc="best") # 子图4:t4/p4 pl4_1 = ax[1,1].plot(filtered_df.index, filtered_df["eBar4"], 'r-',label="eBar4") ax4_2 = ax[1,1].twinx() ax4_2.format_coord = make_format(ax4_2, ax[1,1]) pl4_2 = ax4_2.plot(filtered_df.index, filtered_df["eCelcius4"], 'g-',label="eCelcius4") plots4_2 = pl4_1 + pl4_2 legends_4 = [l4.get_label() for l4 in plots4_2] ax[1,1].legend(plots4_2, legends_4, loc="best") plt.show(block=False) # 非阻塞显示,避免卡住主窗口 # 主程序入口 excel_path = sg.PopupGetFile("Select CSV file", title="Temperature / Pressure Plots") if not excel_path: sg.popup("No file selected, exiting...") exit() df = pd.read_csv(str(excel_path), sep=";") df.drop(df.columns[df.columns.str.contains("Unnamed")], axis=1, inplace=True) liste_cycle_number = list(df["CycleNr"].unique()) layout = [ [sg.Text("Cycle Number", size=(20,1), justification="left"), sg.DropDown(values=liste_cycle_number, size=(10,1), key="Liste", default_value=liste_cycle_number[0])], [sg.Button('Show'), sg.Button('Exit')] ] window = sg.Window('Temperature/Pressure Plots', layout) while True: event, values = window.read() if event == None or event == 'Exit': break if event == "Show": cycle_num = values["Liste"] if not cycle_num: sg.popup("Please select a cycle number first!") continue # 仅在点击Show时处理数据 filtered_df = df.loc[df["CycleNr"] == cycle_num] usecols = filtered_df.columns[12:] filtered_df = filtered_df[usecols].T new_columns = ["eBar1","eCelcius1","eBar2","eCelcius2", "eBar3","eCelcius3", "eBar4","eCelcius4"] filtered_df.columns = new_columns filtered_df = filtered_df.iloc[1:] filtered_df = filtered_df.apply(pd.to_numeric, errors='coerce') filtered_df.index = pd.to_numeric(filtered_df.index) # 触发绘图 plotting(filtered_df, cycle_num) window.close() plt.close('all') # 退出时清理所有残留图表
打包时的额外建议
用PyInstaller打包时,添加以下参数确保Matplotlib资源被正确打包:
pyinstaller --onefile --windowed --hidden-import=matplotlib.backends.backend_tkagg your_script_name.py
参数说明:
--onefile:生成单个独立exe文件--windowed:隐藏控制台窗口(调试时可去掉)--hidden-import:强制导入Tk后端,避免打包时遗漏依赖
这样修改后,打包后的exe就能正常更新图表了,每次点击Show都会关闭旧图并渲染新的温压对比子图。
内容的提问来源于stack exchange,提问作者SMS
相关产品推荐
相关产品推荐

