Код tkinter не продолжается после .destroy () (несколько windows) - PullRequest
0 голосов
/ 14 июля 2020

Я пытаюсь создать простое приложение tkinter с двумя отдельными windows. Первое окно выглядит так и обозначается в коде plot_window . Это позволяет пользователям выбирать, какие столбцы должны быть построены из раскрывающихся меню. Столбец категории также используется для визуализации меток на графике.

enter image description here

However, when I run the code underneath and click either the 'SAVE PLOT' button or the 'CANCEL' button at the bottom one of these methods is triggered:

def close_plot_window():
    self.plot_window.destroy() # This is reached

def set_save_plot_bool():
    print('Destroy')  # This is reached
    self.save_plot_bool = True
    self.plot_window.destroy()

и второе окно save_window должно появиться, но код не продолжается.

Странно то, что если я закомментирую этот фрагмент для третьего и последнего раскрывающегося меню, код будет работать нормально

# IF I COMMENT OUT THIS WHOLE IF STATEMENT, THE CODE RUNS

if len(data.columns) > 2:  # There exist a third columns as well -> include drop-down for category selection

    # ******** Drop-down 3: Category selection ********
    category_column = string_columns[0] if (len(string_columns) > 0) else numeric_columns[2]
    dropdown_choice_category.set(
    category_column)  # Set the default option in the dropdown with the first column
    l3 = Label(self.plot_window, text="Select category column:")
    l3.grid(row=2, column=0, sticky='e')
    dropdown_menu_category = OptionMenu(self.plot_window, dropdown_choice_category, *choices)
    dropdown_menu_category.config(width=16)
    dropdown_menu_category.grid(row=2, column=1, sticky='w')
    chosen_columns = {'x_col': x_values_column,
                              'y_col': y_values_column,
                              'category_col': category_column}

Весь код:

import matplotlib
matplotlib.use('TkAgg')
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.figure import Figure
import matplotlib.pyplot as plt

import numpy as np
from tkinter import *
from tkinter import filedialog

import pandas as pd
import seaborn as sns

from pandas.api.types import is_numeric_dtype

def center_tk_window(window, height, width):
    # Helper method for centering windows

    screen_width = window.winfo_screenwidth()
    screen_height = window.winfo_screenheight()
    x_coordinate = int((screen_width / 2) - (width / 2))
    y_coordinate = int((screen_height / 2) - (height / 2))
    window.geometry("{}x{}+{}+{}".format(width, height, x_coordinate, y_coordinate))


def plot_scatter(data, chosen_columns, ax=None, initial_box=None):
    plot_type = "scatter"
    if plot_type == "scatter":
        fig = Figure(figsize=(7, 6))
        if ax is None:
            # Create a new subplot
            ax = fig.add_subplot(111)

        # Selected x-coordinates
        print('chosen_columns', chosen_columns)
        x_data = data[chosen_columns['x_col']]

        # Selected y-coordinates
        y_data = data[chosen_columns['y_col']]

        filled_markers = ('o', 'v', '^', '<', '>', '8', 's', 'p', '*', 'h', 'H', 'D', 'd', 'P', 'X')

        # Category column
        if 'category_col' in chosen_columns:
            category_data = data[chosen_columns['category_col']]
            print(np.unique(np.array(category_data)))

            # Plotting it all
            sns.scatterplot(ax=ax, x=x_data, y=y_data, hue=category_data, style=category_data,
                            markers=filled_markers
                            )
            # Shrink current axis's height by 20% on the bottom
            if initial_box is None:
                initial_box = ax.get_position()

            ax.set_position([initial_box.x0, initial_box.y0 + initial_box.height * 0.2,
                             initial_box.width, initial_box.height * 0.80])

            # Put a legend below current axis
            ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.15),
                      fancybox=False, shadow=False, ncol=6)
            plt.tight_layout()

        else:  # Normal scatterplot without any categorical values
            sns.scatterplot(ax=ax, x=x_data, y=y_data)

    ax.set_title("User input plot name", fontsize=16)
    ax.set_ylabel("User input y label", fontsize=14)
    ax.set_xlabel("User input x label", fontsize=14)
    return fig, ax, initial_box


class GenericPlot:
    def __init__(self, data):
        # Plot window
        self.save_plot_bool = False
        self.plot_window = Tk()
        self.dynamic_plots(data)

        self.plot_window.mainloop()
        print("After first mainlooop")  # This line is never reached

        print('save_plot_bool', self.save_plot_bool)

        # Save window
        self.save_plot_dir = ''
        self.save_window = Tk()
        self.save_plot()
        self.save_window.mainloop()

    def dynamic_plots(self, data):
        """
        Input :
            window : tkinter window
            data   : DataFrame object
        """
        def close_plot_window():
            self.plot_window.destroy()

        def set_save_plot_bool():
            print('Destroy')  # This is reached
            self.save_plot_bool = True
            self.plot_window.destroy()

        center_tk_window(self.plot_window, 720, 600)

        # Drop-down variables (3 drop-downs)
        dropdown_choice_x = StringVar(self.plot_window)  # Variable holding the dropdown selection for the x column
        dropdown_choice_y = StringVar(self.plot_window)  # Variable holding the dropdown selection for the y column
        dropdown_choice_category = StringVar(
            self.plot_window)  # Variable holding the dropdown selection for the category column

        # Create set of column names in the dataset
        choices = set(data.columns.values)

        # Find numeric and string columns
        string_columns = []
        numeric_columns = []
        [numeric_columns.append(col) if is_numeric_dtype(data[col]) else string_columns.append(col) for col in
         data.columns]

        if len(numeric_columns) < 2:
            raise Exception(
                "Unable to create scatter plot- need more than two numerical columns in the imported dataset.")

        # GUI setup
        self.plot_window.columnconfigure(0, weight=1)
        self.plot_window.columnconfigure(1, weight=1)

        self.plot_window.rowconfigure(0, weight=1)
        self.plot_window.rowconfigure(1, weight=1)
        self.plot_window.rowconfigure(2, weight=1)
        self.plot_window.rowconfigure(3, weight=1)
        self.plot_window.rowconfigure(4, weight=1)

        # ******** Drop-down 1: x-value selection ********
        x_values_column = numeric_columns[0]  # Select the first numeric column as the default x values to plot
        dropdown_choice_x.set(x_values_column)  # Set the default option in the dropdown with the first column
        Label(self.plot_window, text="Select x column:").grid(row=0, column=0, sticky="e")

        choices_numeric = set(numeric_columns)  # Only show numeric columns in the drop-down for x and y

        dropdown_menu_x = OptionMenu(self.plot_window, dropdown_choice_x, *choices_numeric)
        dropdown_menu_x.grid(row=0, column=1, sticky="w")
        dropdown_menu_x.config(width=16)

        # ******** Drop-down 2: y-value selection ********
        y_values_column = numeric_columns[1]  # Select the second alternative in the dropdown list for the y values
        dropdown_choice_y.set(y_values_column)  # Set the default option in the dropdown with the first column
        l2 = Label(self.plot_window, text="Select y column:")
        l2.grid(row=1, column=0, sticky='e')
        dropdown_menu_y = OptionMenu(self.plot_window, dropdown_choice_y, *choices_numeric)
        dropdown_menu_y.config(width=16)
        dropdown_menu_y.grid(row=1, column=1, sticky='w')

        chosen_columns = {'x_col': x_values_column,
                          'y_col': y_values_column}

        #********* IF I COMMENT OUT THIS WHOLE IF STATEMENT, THE CODE RUNS***********
        if len(data.columns) > 2:  # There exist a third columns as well -> include drop-down for category selection
            # ******** Drop-down 3: Category selection ********
            category_column = string_columns[0] if (len(string_columns) > 0) else numeric_columns[2]
            dropdown_choice_category.set(
                category_column)  # Set the default option in the dropdown with the first column
            l3=Label(self.plot_window, text="Select category column:")
            l3.grid(row=2, column=0, sticky='e')
            dropdown_menu_category = OptionMenu(self.plot_window, dropdown_choice_category, *choices)
            dropdown_menu_category.config(width=16)
            dropdown_menu_category.grid(row=2, column=1, sticky='w')

            chosen_columns = {'x_col': x_values_column,
                              'y_col': y_values_column,
                              'category_col': category_column}

        # Plot the initially selected columns

        fig_initial, ax, initial_box = plot_scatter(data, chosen_columns)
        canvas = FigureCanvasTkAgg(fig_initial, master=self.plot_window)
        canvas.get_tk_widget().grid(row=3, columnspan=2, rowspan=True)
        canvas.draw()

        def change_dropdown_x(canvas, chosen_columns, ax, *args):
            # This function is triggered once a dropdown selection is made
            selected_x_col = dropdown_choice_x.get()
            chosen_columns['x_col'] = selected_x_col
            # Create a new plot now
            ax.clear()  # Clearing the previous plot
            _, ax, _ = plot_scatter(data, chosen_columns, ax, initial_box)
            canvas.draw()

        # chosen columns might not be updated...
        def change_dropdown_y(canvas, chosen_columns, ax, *args):
            # This function is triggered once a dropdown selection is made
            selected_y_col = dropdown_choice_y.get()
            chosen_columns['y_col'] = selected_y_col
            # Create a new plot now
            ax.clear()  # Clearing the previous plot
            _, ax, _ = plot_scatter(data, chosen_columns, ax, initial_box)
            canvas.draw()

        def change_dropdown_category(canvas, chosen_columns, ax, *args):
            # This function is triggered once a dropdown selection is made
            selected_category = dropdown_choice_category.get()
            chosen_columns['category_col'] = selected_category
            # Create a new plot now
            ax.clear()  # Clearing the previous plot
            _, ax, _ = plot_scatter(data, chosen_columns, ax, initial_box)
            canvas.draw()

        # Link functions to change dropdown
        dropdown_choice_x.trace('w',
                                lambda *args, canvas=canvas, chosen_columns=chosen_columns, ax=ax,
                                       initial_box=initial_box: change_dropdown_x(canvas, chosen_columns, ax,
                                                                                  initial_box,
                                                                                  *args))
        dropdown_choice_y.trace('w',
                                lambda *args, canvas=canvas, chosen_columns=chosen_columns, ax=ax,
                                       initial_box=initial_box: change_dropdown_y(canvas, chosen_columns, ax,
                                                                                  initial_box,
                                                                                  *args))
        dropdown_choice_category.trace('w', lambda *args, canvas=canvas, chosen_columns=chosen_columns, ax=ax,
                                                   initial_box=initial_box: change_dropdown_category(canvas,
                                                                                                     chosen_columns,
                                                                                                     ax,
                                                                                                     initial_box,
                                                                                                     *args))

        # Save and close buttons
        Button(self.plot_window, text="CLOSE", command=close_plot_window).grid(row=4, column=0)



        Button(self.plot_window, text="SAVE PLOT", command=set_save_plot_bool).grid(row=4, column=1)

    def save_plot(self):
        if self.save_plot_bool:
            print(self.save_plot_bool)
            self.save_window.columnconfigure(0, weight=1)
            self.save_window.columnconfigure(1, weight=1)
            self.save_window.rowconfigure(0, weight=1)
            self.save_window.rowconfigure(1, weight=1)

            self.save_window.title('Save plot')

            # Get saving path
            print("Please select a directory for saving the model...", flush=True)

            l1 = Label(self.save_window, text=self.save_plot_dir)
            l1.grid(row=0, columnspan=2)

            def get_save_dir():
                self.save_plot_dir = filedialog.askdirectory()


            def save_to_file_btn():
                print(self.save_plot_dir)
                plt.save(self.save_plot_dir + '.tif')

            Button(self.save_window, text="Choose save directory...", command=get_save_dir).grid(row=0, column=1)
            Button(self.save_window, text="SAVE PLOT TO FILE", command=save_to_file_btn).grid(row=1, columnspan=2)
            center_tk_window(self.save_window, 300, 400)  # window, height, width

# Create matrix for testing
df = pd.DataFrame(np.random.randint(0,100,size=(150, 4)), columns=list('ABCD'))

# Hard code a category column of string with length 150.
int_labels = [5, 1, 1, 4, 5, 1, 2, 0, 0, 2, 1, 2, 5, 3, 1, 4, 1, 5, 1, 4, 5, 4, 5, 3, 2, 2, 3, 4, 4, 3, 1, 3,
                   3, 2, 5, 1, 1, 5, 3, 3, 1, 2, 0, 1, 2, 0, 5, 3, 5, 1, 3, 5, 3, 5, 4, 2, 3, 3, 4, 1, 3, 3, 3, 4,
                   2, 2, 5, 2, 0, 2, 0, 5, 5, 4, 2, 0, 2, 3, 1, 5, 2, 1, 5, 3, 1, 3, 4, 4, 1, 3, 5, 1, 2, 2, 4, 0,
                   5, 0, 2, 2, 0, 4, 5, 2, 0, 2, 2, 3, 2, 0, 0, 5, 1, 0, 3, 1, 2, 2, 4, 0, 2, 2, 1, 1, 1, 1, 0, 2,
                   1, 1, 3, 2, 4, 4, 0, 2, 0, 5, 4, 4, 3, 0, 1, 0, 2, 5, 3, 0, 3, 5]
string_labels = [str(i) for i in int_labels]
df['category2'] = string_labels

GenericPlot(df)

Если у кого-то есть идея, почему код не продолжается, если я включаю раскрывающееся меню категории, сообщите мне.

1 Ответ

0 голосов
/ 14 июля 2020

Я нашел решение. Это был plt.tight_layout(), вызывающий проблемы ...

...