如何让Matplotlib/Seaborn图表仅在Tkinter指定Frame中更新,避免每次绘图弹出独立窗口?
如何让Matplotlib/Seaborn图表仅在Tkinter指定Frame中更新,避免每次绘图弹出独立窗口?
嘿,我看了你的代码,发现几个关键细节没处理好,导致每次绘图都会弹出独立窗口。咱们一步步来解决这些问题:
问题1:plt.close未执行+错误依赖pyplot状态机
你写了plt.close但没加括号,这行代码根本没运行!而且用plt.figure()会依赖Matplotlib的全局状态机,很容易触发独立窗口。应该直接创建Figure对象(不通过pyplot),彻底脱离pyplot的默认窗口管理。
问题2:按钮初始化时就执行了绘图函数
你的plot_test_button里command=plot_MC(plotSet())是直接调用函数,程序启动时就会跑一遍绘图,而不是点击按钮时才执行。需要用lambda包装成回调函数。
问题3:没有清除旧的图表元素
每次重新绘图时,旧的Canvas和工具栏还留在Frame里,不仅会导致布局混乱,也可能触发窗口弹出。每次绘图前要先清空frame_MC里的所有子组件。
问题4:不必要的交互模式开启
plt.ion()是给pyplot的交互窗口用的,在Tkinter集成场景下完全不需要,反而可能干扰图表渲染逻辑。
修改后的完整代码
#My Imports import numpy as np import pandas as pd import matplotlib as mpl from matplotlib.figure import Figure from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk) from matplotlib.backend_bases import key_press_handler import tkinter as tk import tkinter.ttk as ttk from tkinter import filedialog, messagebox from datetime import datetime from PIL import ImageTk, Image import seaborn as sns from scipy.stats import linregress #Setting the TKinter window settings window = tk.Tk() screen_width = window.winfo_screenwidth() screen_height = window.winfo_screenheight() window.title("Data Visualizer - Plotting in TKinter") window.geometry(str(int(screen_width*.8))+"x"+str(int(screen_height*.8))) #A function so that selecting the graph doesn't auto plot def nullFunction(*args): print("nullFunction called") #Create test pd.DataFrame #Just something simple to visualize; more columns in actual data df = pd.DataFrame( {"time" : [0,1,2,3,4,5,6,7,8,9], "linear" : [0,1,2,3,4,5,6,7,8,9], "random" : [0,8,4,6,4,7,1,9,2,6], "inverse" : [9,8,7,6,5,4,3,2,1,0]} ) #List the various data columns to be selected later, don't list 'time' #Called on button press after df is created/uploaded def generate_List(): i = 0 listbox.delete('0','end') for column in df.columns: if column == 'time': pass else: listbox.insert(i,column) if (i%2) == 0: listbox.itemconfigure(i, background = '#f0f0f0') i = i+1 # 移除不必要的plt.ion() #make figure as its own element class FigureCreate: def __init__(self, figsize=(5, 5)): # 直接创建Figure对象,不通过plt.figure() self.fig = Figure(figsize=figsize) def get_figure(self): return self.fig #make axes based on listbox selection class AxesCreate: def __init__(self, figure, position=(1, 1, 1)): self.ax = figure.add_subplot(*position) self.figure = figure def get_axes(self): return self.ax def get_fig(self): return self.figure #Called on button press after list is created def plotSet(*args): # 正确关闭所有旧的pyplot窗口(如果有的话) plt.close('all') #Possibly redundant figure and axis creation methods fig_create = FigureCreate(figsize=(5,5)) fig = fig_create.get_figure() ax_create = AxesCreate(fig, position=(1,1,1)) ax = ax_create.get_axes() selection = listbox.curselection() for trace in selection: sns.lineplot(data=df, x='time', y=df.columns[trace],ax=ax) #Vlines that will be adjustable after plot issues are solved ax.axvline(df['time'][2],color='green',linestyle='--') ax.axvline(df['time'][4],color='red',linestyle='--') # 添加图例,用axes的方法而不是plt ax.legend() return (ax_create.get_fig()) def plot_MC(fig): # 清除frame_MC里的所有旧组件,避免叠加 for widget in frame_MC.winfo_children(): widget.destroy() # Creating the Tkinter canvas containing the figure canvas = FigureCanvasTkAgg(fig, master=frame_MC) canvas.draw() # Placing the canvas on the Tkinter window canvas.get_tk_widget().grid(row=0, column=0, sticky="nsew") # 让图表自适应frame大小 frame_MC.grid_rowconfigure(0, weight=1) frame_MC.grid_columnconfigure(0, weight=1) # Creating the Matplotlib toolbar toolbar = NavigationToolbar2Tk(canvas, frame_MC, pack_toolbar=False) toolbar.update() toolbar.grid(row=1, column=0, sticky="ew") def are_you_sure(): """ Just a function that opens a message box to confirm window destruction """ if messagebox.askyesno("Quit Dialog","Are you sure you want to quit the app?"): window.destroy() #MC 'Middle-Center' frame_MC = tk.Frame(window) #ML 'Middle-Left' list_frame=tk.Frame(window) listbox=tk.Listbox(list_frame, selectmode='multiple', height=30, width=30) # 这里如果不想选列表时自动绘图,可以绑定nullFunction,或者去掉绑定 listbox.bind('<<ListboxSelect>>', nullFunction) scrollbar=tk.Scrollbar(list_frame, orient="vertical") listbox.config(yscrollcommand=scrollbar.set) scrollbar.config(command = listbox.yview) #TL 'Top-Left' frame_TL = tk.Frame(window) generate_list_button = tk.Button(frame_TL, text="generate list", command=generate_List, width=30) generate_list_button.grid(row=0, column=0, columnspan=3) # 用lambda包装,点击按钮时才执行绘图 plot_test_button = tk.Button(frame_TL, text="plot selection", command=lambda: plot_MC(plotSet())) plot_test_button.grid(row=1, column=0, columnspan=3) #BL 'Bottom-Left' frame_BL = tk.Frame(window) button_quit = tk.Button(master = frame_BL, text = "Quit", command = are_you_sure, bg = '#FF2222') button_quit.grid(row = 0, column = 0) #Placing elements frame_TL.grid(row=0, column = 0) frame_MC.grid(row = 1, column = 1, sticky="nsew") frame_BL.grid(row = 2, column = 0) list_frame.grid(row = 1, column = 0) listbox.pack(side = "left", fill = "y") scrollbar.pack(side = "right", fill = "y") # 让中间区域自适应窗口大小 window.grid_rowconfigure(1, weight=1) window.grid_columnconfigure(1, weight=1) #run the gui window.mainloop()
额外的优化建议
- 把
listbox的<<ListboxSelect>>绑定改回nullFunction(我已经改了),避免每次选列表项都自动绘图,只保留按钮触发的逻辑,符合你最初的需求。 - 添加了
sticky="nsew"和权重配置,让图表能自适应窗口大小,提升用户体验。 - 把
plt.axvline改成ax.axvline,完全使用axes对象的方法,彻底脱离pyplot的全局状态,避免意外弹出窗口。
备注:内容来源于stack exchange,提问作者tnazarro
相关产品推荐
相关产品推荐

