"""
Frontend Visualisation Page File for AGE-ABM Visual Interface
@Author Meghan Ireland
@Co-Authors Max Hall and Matthew Fleischman
"""
import tkinter as tk
from tkinter import ttk
import subprocess as sp
import threading as thr
import importlib as il
import customtkinter
import matplotlib
import matplotlib.pyplot
from visuals import visual
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg, NavigationToolbar2Tk)
[docs]
class Visualisation:
"""Defines widgets and functionality of visualisation page.
"""
def __init__(self, ec_app_ui):
"""Defines widgets and variables for visualisation page.
"""
self.ec_app_ui = ec_app_ui
self.plot = tk.IntVar(self.ec_app_ui.root)
self.compare = tk.BooleanVar(self.ec_app_ui.root)
self.summary = tk.BooleanVar(self.ec_app_ui.root)
self.progress_var = tk.BooleanVar()
### Title ###
self.visual_title = tk.Label(self.ec_app_ui.title_frame, text="Visualise Data"
, font=self.ec_app_ui.font1, fg=self.ec_app_ui.label_text_colour
, bg=self.ec_app_ui.title_colour)
### Tab Views ##
self.visual_tabview = customtkinter.CTkTabview(master = self.ec_app_ui.page_frame, width=900
, height = 620, segmented_button_selected_color = self.ec_app_ui.button_colour
, segmented_button_selected_hover_color = self.ec_app_ui.highlight_colour
, fg_color = self.ec_app_ui.title_colour)
self.visual_tabview.add("Plot Details")
self.visual_tabview.grid_columnconfigure(0, weight=1)
### Label frames ###
self.labelframe_visual_details = tk.LabelFrame(self.visual_tabview.tab("Plot Details")
, font=self.ec_app_ui.font2, text="Visualise", height = self.ec_app_ui.entry_width
, width = 960, bg=self.ec_app_ui.title_colour, fg = self.ec_app_ui.button_font_colour)
self.labelframe_visual_agents = tk.LabelFrame(self.visual_tabview.tab("Plot Details")
, font=self.ec_app_ui.font2, text="Agents to Compare"
, height = 4*self.ec_app_ui.entry_width//5, width = 960, bg=self.ec_app_ui.title_colour
, fg = self.ec_app_ui.button_font_colour)
self.labelframe_visual_stats = tk.LabelFrame(self.visual_tabview.tab("Plot Details")
, font=self.ec_app_ui.font2, text="Plot", height = self.ec_app_ui.entry_width//3
, width = 960, bg=self.ec_app_ui.title_colour, fg = self.ec_app_ui.button_font_colour)
### Labels ###
self.label_visual_agent = tk.Label(self.labelframe_visual_details
, text="Select Agent", font=self.ec_app_ui.font2
, justify="left",bg=self.ec_app_ui.title_colour,fg=self.ec_app_ui.label_text_colour)
self.label_visual_type = tk.Label(self.labelframe_visual_stats
, text="Select Stats Type", font=self.ec_app_ui.font2
, justify="left",bg=self.ec_app_ui.title_colour,fg=self.ec_app_ui.label_text_colour)
self.label_visual_compare = tk.Label(self.labelframe_visual_details
, text="Compare to Others", font=self.ec_app_ui.font2
, justify="left",bg=self.ec_app_ui.title_colour,fg=self.ec_app_ui.label_text_colour)
self.label_visual_component = tk.Label(self.labelframe_visual_details
, text="Select Component:", font=self.ec_app_ui.font2
, justify="left",bg=self.ec_app_ui.title_colour,fg=self.ec_app_ui.label_text_colour)
self.label_visual_attribute = tk.Label(self.labelframe_visual_details
, text="Select Attribute:", font=self.ec_app_ui.font2
, justify="left",bg=self.ec_app_ui.title_colour,fg=self.ec_app_ui.label_text_colour)
self.label_visual_compare_agent = tk.Label(self.labelframe_visual_agents
, text="Select Agents to Compare to:", font=self.ec_app_ui.font2
, justify="left",bg=self.ec_app_ui.title_colour,fg=self.ec_app_ui.label_text_colour)
### Radio Buttons ###
self.radio_visual_bar = customtkinter.CTkRadioButton(self.labelframe_visual_details
, value=0, text = "Bar", font = self.ec_app_ui.font2,variable = self.plot
, hover_color = self.ec_app_ui.highlight_colour
, text_color = self.ec_app_ui.button_font_colour, fg_color = self.ec_app_ui.tbl_colour)
self.radio_visual_scatter = customtkinter.CTkRadioButton(self.labelframe_visual_details
, value=1, text = "Scatter", font = self.ec_app_ui.font2, variable = self.plot
, hover_color = self.ec_app_ui.highlight_colour
, text_color = self.ec_app_ui.button_font_colour, fg_color = self.ec_app_ui.tbl_colour)
self.radio_visual_line = customtkinter.CTkRadioButton(self.labelframe_visual_details
, value=2, text = "Line", font = self.ec_app_ui.font2, variable = self.plot
, hover_color = self.ec_app_ui.highlight_colour
, text_color = self.ec_app_ui.button_font_colour, fg_color = self.ec_app_ui.tbl_colour)
### Drop down ###
self.dropdown_visual_agents = customtkinter.CTkOptionMenu(self.labelframe_visual_details
, values = [], fg_color = self.ec_app_ui.dropdown_menu_color
, button_color=self.ec_app_ui.comp_btn_colour
, button_hover_color= self.ec_app_ui.highlight_colour, command = self.on_selected_agent
, height = self.ec_app_ui.entry_height, width=self.ec_app_ui.entry_width
, font = self.ec_app_ui.font2)
self.dropdown_visual_agents.set("Select Agent")
self.dropdown_visual_type = customtkinter.CTkOptionMenu(self.labelframe_visual_stats
, values = [], fg_color = self.ec_app_ui.dropdown_menu_color
, button_color=self.ec_app_ui.comp_btn_colour
, button_hover_color= self.ec_app_ui.highlight_colour
, height = self.ec_app_ui.entry_height, width=self.ec_app_ui.entry_width
, font = self.ec_app_ui.font2, command = self.on_select_type)
self.dropdown_visual_type.set("Select Stats Type")
self.dropdown_visual_component = customtkinter.CTkOptionMenu(self.labelframe_visual_details
, values = [], fg_color = self.ec_app_ui.dropdown_menu_color
, button_color=self.ec_app_ui.comp_btn_colour
, button_hover_color= self.ec_app_ui.highlight_colour
, command = self.on_selected_component, height = self.ec_app_ui.entry_height
, width=self.ec_app_ui.entry_width, font = self.ec_app_ui.font2)
self.dropdown_visual_attribute = customtkinter.CTkOptionMenu(self.labelframe_visual_details
, values = [], fg_color = self.ec_app_ui.dropdown_menu_color
, button_color=self.ec_app_ui.comp_btn_colour
, button_hover_color= self.ec_app_ui.highlight_colour
, height = self.ec_app_ui.entry_height, width=self.ec_app_ui.entry_width
, font = self.ec_app_ui.font2)
self.dropdown_visual_agents_compare = customtkinter.CTkOptionMenu(self.labelframe_visual_agents
, values = [], fg_color = self.ec_app_ui.dropdown_menu_color
, button_color=self.ec_app_ui.comp_btn_colour
, button_hover_color= self.ec_app_ui.highlight_colour
, height = self.ec_app_ui.entry_height, width=self.ec_app_ui.entry_width
, font = self.ec_app_ui.font2)
self.dropdown_visual_agents.set("Select Agent")
### Check Box ###
self.checkbox_visual_compare = customtkinter.CTkCheckBox(self.labelframe_visual_details
, border_color=self.ec_app_ui.tbl_colour, hover_color=self.ec_app_ui.highlight_colour
, variable=self.compare, text = "", fg_color = self.ec_app_ui.check_color
, command=self.on_tick_compare)
### Button ###
self.button_visual_plot = customtkinter.CTkButton(self.visual_tabview.tab("Plot Details")
, text="Plot", command=self.btn_visual_plot, fg_color=self.ec_app_ui.comp_btn_colour
, font = self.ec_app_ui.font2, height=self.ec_app_ui.entry_height
, width=self.ec_app_ui.entry_width)
self.button_visual_add = customtkinter.CTkButton(self.labelframe_visual_agents,text="Add"
, command=self.btn_visual_add,fg_color=self.ec_app_ui.comp_btn_colour
, font = self.ec_app_ui.font2, height=self.ec_app_ui.entry_height
, width=self.ec_app_ui.entry_width)
self.button_visual_delete = customtkinter.CTkButton(self.labelframe_visual_agents
, text="Delete", command=self.btn_visual_delete ,fg_color=self.ec_app_ui.comp_btn_colour
, font = self.ec_app_ui.font2, height=self.ec_app_ui.entry_height
, width=self.ec_app_ui.entry_width)
### Progress Bar ###
self.progressbar_visual_thread = customtkinter.CTkProgressBar(self.visual_tabview.tab("Plot Details")
, fg_color=self.ec_app_ui.tbl_colour, width= 600
, progress_color=self.ec_app_ui.button_colour)
### Table for Agents ###
self.agent_frame = customtkinter.CTkFrame(self.labelframe_visual_agents,
fg_color=self.ec_app_ui.tbl_colour, height = 100, width = self.ec_app_ui.window_width/6)
self.agent_tbl = ttk.Treeview(self.agent_frame,padding = 5,height=2)
self.agent_tbl['columns'] = 'Name'
self.agent_tbl.column("#0", width=0, stretch='NO')
self.agent_tbl.column("Name",anchor='center', width=300)
self.agent_tbl.heading("#0",text="",anchor='center')
self.agent_tbl.heading("Name",text="Agent",anchor='center')
self.agent_tbl.bind("<<TreeviewSelect>>", self.display_selected_agent)
self.selected_agent = ""
self.thread_for_visual_files = None
self.added_agents = []
self.comparable_agents = []
self.thread_output = []
self.plot_counter = 0
[docs]
def clear_visual(self):
"""Clears visualisation page.
"""
self.visual_tabview.pack_forget()
self.labelframe_visual_details.grid_forget()
self.labelframe_visual_agents.grid_forget()
self.labelframe_visual_stats.grid_forget()
self.label_visual_agent.grid_forget()
self.label_visual_type.grid_forget()
self.label_visual_compare.grid_forget()
self.label_visual_component.grid_forget()
self.label_visual_attribute.grid_forget()
self.label_visual_compare_agent.grid_forget()
self.radio_visual_bar.grid_forget()
self.radio_visual_scatter.grid_forget()
self.radio_visual_line.grid_forget()
self.dropdown_visual_agents.grid_forget()
self.dropdown_visual_type.grid_forget()
self.dropdown_visual_component.grid_forget()
self.dropdown_visual_attribute.grid_forget()
self.dropdown_visual_agents_compare.grid_forget()
self.dropdown_visual_agents.configure(values = [])
self.dropdown_visual_type.configure(values = [])
self.dropdown_visual_component.configure(values = [])
self.dropdown_visual_attribute.configure(values = [])
self.dropdown_visual_agents_compare.configure(values = [])
self.dropdown_visual_agents.set("Select Agent")
self.dropdown_visual_type.set("Select Stats Type")
self.dropdown_visual_component.set("Select Component")
self.dropdown_visual_attribute.set("Select Attribute")
self.dropdown_visual_agents_compare.set("Select Agent")
self.progressbar_visual_thread.grid_forget()
self.checkbox_visual_compare.grid_forget()
self.button_visual_plot.grid_forget()
self.button_visual_add.grid_forget()
self.button_visual_delete.grid_forget()
self.agent_frame.grid_forget()
self.agent_tbl.pack_forget()
self.visual_title.grid_forget()
[docs]
def generating_data_thread_function(self, file_name : str):
"""
This function takes a file_name as a parameter and runs the file in a subprocess.
Parameters
----------
file_name : str
file_name to be run.
Returns
-------
error term : int
Returns 0 on success
Returns 1 if data generation failed
"""
try:
if ".py" in file_name: # Makes sure there is no .py at the end of the filename
file_name = file_name.replace(".py", "")
file_name += ".py"
# Running as a subprocess
process_run = sp.run(["python", f"{file_name}"], stderr=sp.PIPE, check=False)
if process_run.returncode != 0:
raise Exception(process_run.stderr.decode())
self.progress_bar_success_func()
print("Data generation was successful!")
return 0
except Exception as exception:
self.progressbar_visual_thread.stop()
self.progressbar_visual_thread.configure(fg_color=self.ec_app_ui.off_switch
, progress_color=self.ec_app_ui.off_switch)
print("Data generation failed:", exception)
return 1
[docs]
def start_thread(self):
"""Starts the thread
"""
self.button_visual_plot.configure(state="disabled")
self.button_visual_plot.grid_forget()
self.ec_app_ui.new_model.create_model("visual.py", True)
self.thread_for_visual_files = thr.Thread(target=self.generating_data_thread_function
, args=("runtimeFiles/visual_main.py",))
self.thread_for_visual_files.daemon = True
self.thread_for_visual_files.start()
self.progressbar_visual_thread.grid(row = 3, column = 0, pady = 40, padx = (80,0))
self.progressbar_visual_thread.start()
[docs]
def clear_plots(self):
"""Clears all tabs
"""
for plot in range(self.plot_counter):
self.visual_tabview.delete("Plot"+str(plot+1))
self.ec_app_ui.visual.remove_agent_visualisation("Plot"+str(plot+1))
self.ec_app_ui.visual = visual.VisualEnv(self.ec_app_ui.new_model)
self.plot_counter = 0
[docs]
def visual_tab_btn(self):
"""Places widgets on opening of page.
"""
self.ec_app_ui.clear_all()
self.visual_tabview.grid_propagate(0)
visual_width = self.ec_app_ui.window_width - self.ec_app_ui.panel_width - 20
self.visual_tabview.configure(width=visual_width, height = 620)
self.checkbox_visual_compare.deselect()
self.dropdown_visual_agents.set("Select Agent")
self.added_agents = []
self.comparable_agents = []
self.update_tbl()
self.selected_agent = ""
self.visual_title.grid(row = 0, column = 0)
self.clear_plots()
self.visual_tabview.pack(fill = "both", expand = 1)
self.visual_tabview.grid_columnconfigure(0, weight=1)
### Top Frame ###
self.labelframe_visual_details.grid_propagate(0)
self.labelframe_visual_details.configure(width = 950,height = self.ec_app_ui.entry_width)
self.labelframe_visual_details.grid(row = 0, column = 0, padx = (80,0))
### Select Agent ###
self.dropdown_visual_agents.configure(values = self.ec_app_ui.new_model.get_list_agents_of_interest())
self.label_visual_agent.grid(row = 0, column = 0, columnspan = 3, sticky = "W", padx = 5
, pady=2)
self.dropdown_visual_agents.grid(row = 0, column = 3, sticky = "W", padx = (377,5), pady=2)
### Compare to others ###
self.label_visual_compare.grid(row = 1, column = 0, columnspan = 3, sticky = "W", padx = 5
, pady=2)
self.checkbox_visual_compare.grid(row = 1, column = 3, sticky = "W", padx = (377,5), pady=2)
### Plot type ###
self.radio_visual_bar.grid(row = 4, column = 0, sticky = "W", padx = (5,0), pady=2)
self.radio_visual_scatter.grid(row = 4, column = 1, sticky = "W", padx = (0,5), pady=2)
self.radio_visual_line.grid(row = 4, column = 2, sticky = "W", padx = (10,0), pady=2)
### Component ###
self.dropdown_visual_component.set("Select Component")
self.label_visual_component.grid(row = 2, column = 0, columnspan = 3, sticky = "W"
, padx = 5)
self.dropdown_visual_component.grid(row = 2, column = 3, sticky = "W", padx = (377,5)
, pady=2)
### Attribute ###
self.dropdown_visual_attribute.set("Select Attribute")
self.label_visual_attribute.grid(row = 3, column = 0, columnspan = 3, sticky = "W"
, padx = 5)
self.dropdown_visual_attribute.grid(row = 3, column = 3, sticky = "W", padx = (377,5)
, pady=2)
### 3rd Frame ###
self.labelframe_visual_stats.grid_propagate(0)
self.labelframe_visual_stats.configure(width = 950)
self.labelframe_visual_stats.grid(row = 2, column = 0, padx = (80,0))
### Add data type ###
self.dropdown_visual_type.set("Select Stats Type")
self.label_visual_type.grid(row = 0, column = 0, columnspan = 3, sticky = "W", padx = 5)
self.dropdown_visual_type.grid(row = 0, column = 1, sticky = "W", padx = (697,5), pady=2)
### Setting the colour for the loading bar ###
self.progressbar_visual_thread.configure(fg_color=self.ec_app_ui.tbl_colour
, progress_color=self.ec_app_ui.button_colour)
### Collects data for visualisation ###
self.start_thread()
[docs]
def btn_visual_add(self):
"""When user adds agents
"""
if self.dropdown_visual_agents_compare.get() != "Select Agent":
self.added_agents.append(self.dropdown_visual_agents_compare.get())
self.comparable_agents.remove(self.dropdown_visual_agents_compare.get())
self.dropdown_visual_agents_compare.configure(values = self.comparable_agents)
self.update_tbl()
self.dropdown_visual_agents_compare.set("Select Agent")
if self.dropdown_visual_component.get()!="Select Component":
stats_types = self.ec_app_ui.new_model.get_list_valid_plot_data_types(([self.dropdown_visual_agents.get()] + self.added_agents))
self.dropdown_visual_type.configure(values = stats_types)
else:
self.dropdown_visual_type.set("count")
self.dropdown_visual_type.configure(values = [])
[docs]
def btn_visual_delete(self):
"""When user clicks done
"""
if self.selected_agent != "":
self.comparable_agents.append(self.selected_agent)
self.dropdown_visual_agents_compare.configure(values = self.comparable_agents)
self.added_agents.remove(self.selected_agent)
self.update_tbl()
self.selected_agent = ""
if self.dropdown_visual_component.get()!="Select Component":
stats_types = self.ec_app_ui.new_model.get_list_valid_plot_data_types(([self.dropdown_visual_agents.get()] + self.added_agents))
self.dropdown_visual_type.configure(values = stats_types)
else:
self.dropdown_visual_type.set("count")
self.dropdown_visual_type.configure(values = [])
[docs]
def on_selected_agent(self, *args):
"""When user clicks dropdown of agents
"""
self.dropdown_visual_component.set("Select Component")
self.dropdown_visual_attribute.set("Select Attribute")
self.dropdown_visual_type.set("count")
self.button_visual_plot.configure(state="normal")
self.dropdown_visual_type.configure(values = [])
self.dropdown_visual_component.configure(values = self.ec_app_ui.new_model.get_agent_of_interest(self.dropdown_visual_agents.get()).get_comp_of_interest())
self.dropdown_visual_attribute.configure(values = "")
self.checkbox_visual_compare.deselect()
self.on_tick_compare()
[docs]
def progress_bar_success_func(self):
"""Removes progress bar and places the plot button
"""
self.progressbar_visual_thread.stop()
self.progressbar_visual_thread.grid_forget()
self.button_visual_plot.grid(row = 3, column = 0, sticky = "E", pady = 5)
self.button_visual_plot.configure(state="disabled")
if self.dropdown_visual_type.get() != "Select Stats Type":
self.button_visual_plot.configure(state="normal")
[docs]
def on_selected_component(self, *args):
"""When user clicks dropdown of components
"""
self.dropdown_visual_attribute.set("Select Attribute")
self.dropdown_visual_agents_compare.set("Select Agent")
self.dropdown_visual_type.set("Select Stats Type")
self.button_visual_plot.configure(state="disabled")
### Update Table ###
self.added_agents = []
self.update_tbl()
### Add Comparable Agents to dropdown ###
self.comparable_agents = []
self.comparable_agents = self.ec_app_ui.new_model.get_list_valid_agents(self.dropdown_visual_agents.get(), self.dropdown_visual_component.get())
self.dropdown_visual_agents_compare.configure(values = self.comparable_agents)
stats_types = self.ec_app_ui.new_model.get_list_valid_plot_data_types(([self.dropdown_visual_agents.get()] + self.added_agents))
self.dropdown_visual_type.configure(values = stats_types)
self.dropdown_visual_attribute.configure(values = self.ec_app_ui.new_model.get_list_attributes(self.dropdown_visual_component.get()))
[docs]
def on_selected_attribute(self, *args):
"""When user clicks dropdown of attributes
"""
[docs]
def on_select_type(self, *args):
"""When stats dropdown is selected
"""
if self.dropdown_visual_type.get() != "Select Stats Type":
self.button_visual_plot.configure(state="normal")
[docs]
def display_selected_agent(self, *args):
"""Gets the selected agent
"""
selected_items = self.agent_tbl.selection()
if selected_items:
selected_item = self.agent_tbl.selection()[0]
self.selected_agent = self.agent_tbl.item(selected_item)['values'][0]
[docs]
def on_tick_compare(self, *args):
"""When compare checkbox is clicked
"""
if self.checkbox_visual_compare.get():
self.dropdown_visual_agents_compare.set("Select Agent")
self.added_agents = []
self.comparable_agents = []
self.comparable_agents = self.ec_app_ui.new_model.get_list_valid_agents(self.dropdown_visual_agents.get(), count = True)
self.dropdown_visual_agents_compare.configure(values = self.comparable_agents)
self.update_tbl()
### 2nd Frame ###
self.labelframe_visual_agents.grid_propagate(0)
self.labelframe_visual_agents.configure(width = 950)
self.labelframe_visual_agents.grid(row = 1, column = 0, padx = (80,0))
### Agent ###
self.dropdown_visual_agents_compare.set("Select Agent")
self.label_visual_compare_agent.grid(row = 0, column = 0, sticky = "W", padx = 5)
self.dropdown_visual_agents_compare.grid(row = 0, column = 1, sticky = "W"
, padx = (363,5))
### Buttons ###
self.button_visual_add.grid(row = 1, column = 1, sticky = "W", padx = (363,5))
self.button_visual_delete.grid(row = 2, column = 1, sticky = "W", padx = (363,5))
### Table ###
self.agent_frame.grid(row = 1, column = 0, sticky = "W", padx = 5, rowspan = 2)
self.agent_tbl.pack(fill = "x", padx = 5, pady = 5)
else:
self.label_visual_compare_agent.grid_forget()
self.labelframe_visual_agents.grid_forget()
self.dropdown_visual_agents_compare.grid_forget()
self.button_visual_add.grid_forget()
self.button_visual_delete.grid_forget()
self.agent_frame.grid_forget()
self.agent_tbl.pack_forget()
[docs]
def update_tbl(self):
"""Updates the table
"""
id7 = 0
agents = self.added_agents
for agent in self.agent_tbl.get_children():
self.agent_tbl.delete(agent)
for agent_name in agents:
self.agent_tbl.insert(parent='',index='end',iid=id7,text='',values=(agent_name,))
id7+=1
[docs]
def btn_visual_plot(self):
"""Add the plot type and information
"""
self.plot_counter+=1
plot_title = "Plot"+str(self.plot_counter)
plot_type = ""
if self.plot.get() == 0:
plot_type = "bar"
elif self.plot.get() == 1:
plot_type = "scatter"
elif self.plot.get() == 2:
plot_type = "plot"
agents = [self.dropdown_visual_agents.get()] + self.added_agents
visual_type = self.dropdown_visual_type.get()
if visual_type != "Select Stats Type" and self.dropdown_visual_type.get() == "count":
print(self.ec_app_ui.visual.add_agent_visualisation(plot_title, agents
, self.dropdown_visual_type.get()
, plot_type))
else:
self.ec_app_ui.visual.add_agent_visualisation(plot_title, agents
, self.dropdown_visual_type.get()
, plot_type
, self.dropdown_visual_component.get()
, self.dropdown_visual_attribute.get())
print(self.ec_app_ui.visual.visualise_model(plot_title))
### Add Tab ###
matplotlib.use("TkAgg")
self.visual_tabview.add(plot_title)
self.visual_tabview.grid_propagate(0)
visual_lib = il.import_module(f"visualisingFiles.{plot_title}_visual")
il.reload(visual_lib)
figure = visual_lib.visualise()
canvas = FigureCanvasTkAgg(figure, master=self.visual_tabview.tab(plot_title))
canvas.draw()
canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=1)
toolbar = NavigationToolbar2Tk(canvas, self.visual_tabview.tab(plot_title))
toolbar.update()
logo_picture = tk.PhotoImage(file = 'logo.png')
self.ec_app_ui.root.iconphoto(False, logo_picture)
[docs]
def on_close_graphs(self):
"""Closes graphs when user leaves the window
"""
if self.plot_counter > 0:
matplotlib.pyplot.close('all')