When working on data analysis, I think that there are many cases where you can visually check the characteristics of data with graphs, etc., in addition to tracking the data numerically.
There are many such tasks, and I created a tool called "Glance" that can display graphs immediately while freely changing the combination of data.
I used Python's tkinter
library to create the GUI.
The file is located here [https://gitlab.com/katsuki104/glance). ** * Since it was made for use in a Windows environment, it probably needs to be modified separately in a Mac environment ... **
It is a simple tool that simply selects from the read data and displays it as a graph. There are three main actions implemented.
Read the data from the file selection and display the column in a list
Select the data and display the graph
I used tkinter for the first time this time, but one of the things that got stuck is "** I don't know how to receive the return value of the function assigned to the widget **". For that purpose, it seemed necessary to define a class to hold data and pass it by giving each object data.
For example, in the case of this tool, there is a function called fileselect
that reads from a file, but the read data frame is not returned by return, but is given to the object in the process.
def fileselect():
root = tkinter.Tk()
root.withdraw()
fTyp = [("", "*")]
iDir = os.path.abspath(os.path.dirname(__file__))
model.path = tkinter.filedialog.askopenfilename(filetypes=fTyp,
initialdir=iDir)
suffix = model.path.rsplit('.', 1)[1]
if suffix == 'csv' or suffix == 'pkl':
model.set_data(suffix) #I have the data here.
graph.config(state='active')
Entry1.insert(tk.END, model.path)
line_size = columns_list.size()
columns_list.delete(0, line_size)
for column in enumerate(model.data.columns):
columns_list.insert(tk.END, column)
else:
tkinter.messagebox.showinfo(model.path, 'Select csv or pickle file.')
When operating the tkinter widget with a function The operation function will be defined in the function on the scope of the variable.
This time, we also define new_window
to open the graph in a separate window, so
The structure of the redraw
function that updates the graph is as follows.
def main():
#processing
def new_window():
def redraw():
#processing
scale = tk.Scale(command=redraw) #Slide bar
This will cause the redraw function to be executed each time you manipulate the scale.
The inside of the redraw function is free, but after changing the content expressed by matplotlib
Please note that if canvas.draw ()
is not at the end, it will not be reflected on the GUI.
This is an unsolved problem, but in my writing style, it's in main () I couldn't go out as a function or a separate file because I have defined all the other functions. (But I only know how to operate the widget with a function ...)
As a result, main () becomes long and difficult to read. What's wrong?
import os
import tkinter as tk
import tkinter.filedialog
import tkinter.messagebox
import japanize_matplotlib
from matplotlib.backends.backend_tkagg import (
FigureCanvasTkAgg, NavigationToolbar2Tk)
from matplotlib.figure import Figure
import numpy as np
import pandas as pd
def main():
class model_data:
def __init__(self):
self.data = pd.DataFrame()
self.path = ''
self.columns = []
def set_path(self, file_path):
self.path = file_path
def set_data(self, suffix):
if suffix == 'csv':
self.data = pd.read_csv(self.path)
elif suffix == 'pkl':
self.data = pd.read_pickle(self.path)
def new_window(event):
if any(model.data):
def _quit_sub():
sub_win.destroy()
def _redraw(event):
min_lim = min_scale.get()
max_lim = max_scale.get()
val = []
if min_lim >= max_lim:
return
else:
t = target.iloc[min_lim:max_lim, :]
for index in selection:
label = target.columns[index]
val.append(t[label].max())
val.append(t[label].min())
max_val = max(val)
min_val = min(val)
ax.set_xlim(min_lim, max_lim)
ax.set_ylim(min_val, max_val)
canvas.draw()
selection = columns_list.curselection()
sub_win = tk.Toplevel(root)
sub_win.configure(bg='white')
sub_win.wm_title("Embedding in Tk")
target = model.data.copy()
target.reset_index(drop=True, inplace=True)
fig = Figure(figsize=(10, 4), dpi=100)
ax = fig.add_subplot(111)
if norm_bln.get():
for index in selection:
label = target.columns[index]
max_num = target[label].max()
min_num = target[label].min()
target[label] = (target[label] - min_num) / (max_num - min_num)
ax.plot(target.index,
target[label],
label=label)
else:
for index in selection:
label = target.columns[index]
ax.plot(target.index,
target[label],
label=label)
canvas_frame = tk.Frame(master=sub_win, bg='white')
canvas_frame.pack(side=tk.LEFT)
canvas = FigureCanvasTkAgg(fig,
master=canvas_frame)
canvas.draw()
canvas.get_tk_widget().pack(side=tk.TOP,
fill=tk.BOTH,
expand=1)
control_frame = tk.Frame(master=sub_win)
control_frame.pack(side=tk.RIGHT)
toolbar = NavigationToolbar2Tk(canvas,
canvas_frame)
toolbar.update()
canvas.get_tk_widget().pack(side=tk.TOP,
fill=tk.BOTH,
expand=True)
min_scale = tk.Scale(control_frame,
label='Min',
orient='h',
from_=0,
bg='light blue',
resolution=1,
to=model.data.shape[0]-1,
command=_redraw)
min_scale.pack(anchor=tk.NW)
max_scale = tk.Scale(control_frame,
label='Max',
orient='h',
from_=1,
bg='tan1',
resolution=1,
to=model.data.shape[0],
command=_redraw)
max_scale.set(target.shape[0])
max_scale.pack(anchor=tk.NW)
button = tkinter.Button(master=canvas_frame,
text="Quit",
command=_quit_sub)
button.pack(side=tkinter.BOTTOM)
else:
tkinter.messagebox.showinfo('Glance.py', 'Please select the data to read first')
def fileselect():
root = tkinter.Tk()
root.withdraw()
fTyp = [("", "*")]
iDir = os.path.abspath(os.path.dirname(__file__))
model.path = tkinter.filedialog.askopenfilename(filetypes=fTyp,
initialdir=iDir)
suffix = model.path.rsplit('.', 1)[1]
if suffix == 'csv' or suffix == 'pkl':
model.set_data(suffix)
graph.config(state='active')
Entry1.insert(tk.END, model.path)
line_size = columns_list.size()
columns_list.delete(0, line_size)
for column in enumerate(model.data.columns):
columns_list.insert(tk.END, column)
else:
tkinter.messagebox.showinfo(model.path, 'Select csv or pickle file.')
def _quit(event):
root.quit()
root.destroy()
root = tk.Tk()
model = model_data()
root.title('Glance')
root.geometry('680x300')
frame1 = tk.Frame(bd=3, relief=tk.SUNKEN, pady=5)
frame1.pack(padx=5, pady=5, ipadx=10, ipady=10, side=tk.LEFT)
Static1 = tk.Label(frame1, text='File Path')
Static1.pack()
Entry1 = tk.Entry(frame1, width=80)
Entry1.insert(tk.END, model.path)
Entry1.pack()
Static1 = tk.Label(frame1, text='Column Names')
Static1.pack()
var = tk.StringVar(value=[])
columns_list = tk.Listbox(frame1,
listvariable=var,
width=80,
selectmode='multiple')
scrollbar = tk.Scrollbar(frame1,
command=columns_list.yview)
columns_list.yscrollcommand = scrollbar.set
scrollbar.pack(fill=tk.BOTH,
side=tk.RIGHT)
columns_list.pack()
Button1 = tk.Button(frame1,
text='SelectFile',
width=10,
command=fileselect)
Button1.pack(pady=5)
frame2 = tk.Frame()
frame2.pack(padx=5, pady=5, ipadx=10, ipady=10, fill=tk.BOTH)
norm_bln = tk.BooleanVar()
norm_bln.set('False')
norm = tkinter.Checkbutton(frame2,
variable=norm_bln,
text='Norm')
norm.pack()
graph = tk.Button(frame2,
text='Graph',
width=10,
state='disable')
graph.bind("<Button-1>", new_window)
graph.pack()
frame3 = tk.Frame()
frame3.pack(padx=5, pady=5, ipadx=10, ipady=10, side=tk.BOTTOM)
Button2 = tk.Button(frame3, text='Exit', width=5)
Button2.bind("<Button-1>", _quit)
Button2.pack()
root.mainloop()
if __name__ == "__main__":
main()
How to display the graph and change it dynamically About tkinter widget placement