[Python] Ways to prevent tkinter from opening a graph on each plot drawn/draw in a selected...

Discussão em 'Python' iniciado por Stack, Março 12, 2025.

  1. Stack

    Stack Membro Participativo

    I am working on a python tkinter GUI, for plotting/modifying plots for device calibration and data analysis. Basically, I want to list various columns of data and then select regions of said data for analysis. I am successfully importing data, creating a dataframe in Pandas, and using Matplotlib/seaborn to graph the data I care about. However, when adjusting the plot based on my selections, I keep getting a pop-up of the graph in its own window, rather than just updating the plot in its current frame. This happens each time I recall the main plot() function. Is there something I'm missing with either calling or closing the graphs? Thanks for any information, I am still newish to python and stackoverflow.

    I have tried a few things, like directly using plt.close to attempt to remove the graph before the pop up, isolating the figure and axis creation for matplotlib, and using seaborn for the lineplot instead of mpl in case the axis handling is different; but I still seem to get erroneous pop up plots. Maybe I'm just missing something obvious with how the function does or does not return things... Below is some code to recreate the example.

    #My Imports
    import numpy as np
    import pandas as pd
    import matplotlib as mpl
    import matplotlib.pyplot as plt
    import matplotlib.dates as mdates
    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
    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)):
    self.fig = plt.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

    #plot a preselected group of elements; not currently used
    class CanvasDraw:
    def __init__(self, axes):
    self.ax = axes

    def plot(self, x, y, label=''):
    self.ax.plot(x, y, label=label)

    def show(self):
    self.ax.legend()
    plt.show()

    #Called on button press after list is created
    def plotSet(*args):
    plt.close

    #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()

    #canvas_draw = CanvasDraw(ax)

    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
    plt.axvline(df['time'][2],color='green',linestyle='--')
    plt.axvline(df['time'][4],color='red',linestyle='--')

    return (ax_create.get_fig())

    def plot_MC(fig):
    # Creating the Tkinter canvas containing the figure
    #THIS IS WHERE I THINK THE PROBLEM IS, BUT I'M NOT SURE
    #I thought this would make the canvas in the MC frame, but it seems to want a new plot since plotSet() is returning a figure
    #Is there a better way to plot specifically in the middle frame, and not force plotSet() to return anything?
    canvas = FigureCanvasTkAgg(fig, master=frame_MC)
    canvas.draw()

    # Placing the canvas on the Tkinter window
    canvas.get_tk_widget().grid(row=0, column=0)

    # Creating the Matplotlib toolbar
    toolbar = NavigationToolbar2Tk(canvas, frame_MC, pack_toolbar=False)
    toolbar.update()
    toolbar.grid(row=1, column=0)

    def are_you_sure():
    """
    Just a function that opens a message box to confirm window destruction
    """

    if tk.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)
    listbox.bind('<<ListboxSelect>>', plotSet)
    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)
    plot_test_button = tk.Button(frame_TL, text="plot selection", command=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)
    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")

    #run the gui
    window.mainloop()


    Are there any obvious improvements I could make to clean up the plotting so I don't have this problem? Thank you in advance.

    Continue reading...

Compartilhe esta Página