Я пытаюсь создать простое приложение tkinter с двумя отдельными windows. Первое окно выглядит так и обозначается в коде plot_window . Это позволяет пользователям выбирать, какие столбцы должны быть построены из раскрывающихся меню. Столбец категории также используется для визуализации меток на графике.
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)
Если у кого-то есть идея, почему код не продолжается, если я включаю раскрывающееся меню категории, сообщите мне.