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

如何让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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.13 18:30:31