[PYTHON] I made a tool to easily display data as a graph by GUI operation.

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 ... **

Overview

It is a simple tool that simply selects from the read data and displays it as a graph. There are three main actions implemented.

  1. Read the data from the file selection and display the column in a list sample1.gif

  2. Select the data and display the graph

  1. Dynamically change the display range of the graph with the slider sample4.gif

A brief introduction of the addicted part

About passing values in tkinter

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.')

How to dynamically update the graph

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.

I can't separate the code

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?

Whole code

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

Reference page

How to display the graph and change it dynamically About tkinter widget placement

Recommended Posts

I made a tool to easily display data as a graph by GUI operation.
I made a script to display emoji
I made a tool to compile Hy natively
I made a tool to get new articles
I made a repeating text data generation tool "rpttxt"
I made a tool to create a word cloud from wikipedia
[Titan Craft] I made a tool to summon a giant to Minecraft
Procedure from environment construction to operation test of testinfra, a server environment test tool made by Python
I made a library to easily read config files with Python
Impressions of touching Dash, a data visualization tool made by python
I made a command to display a colorful calendar in the terminal
I made a CLI tool to convert images in each directory to PDF
I made a program to notify you by LINE when switches arrive
I made a program to input what I ate and display calories and sugar
I made a tool to convert Jupyter py to ipynb with VS Code
I made a browser automatic stamping tool.
I tried to display the altitude value of DTM in a graph
I made a tool to estimate the execution time of cron (+ PyPI debut)
I created a tool to correct GPS data with Mapbox Map Matching API (Mapbox Map Matching API)
I made a tool to notify Slack of Connpass events and made it Terraform
I tried to explain what a Python generator is for as easily as possible.
I made a module in C language to filter images loaded by Python
I made a tool to generate Markdown from the exported Scrapbox JSON file
Paste a link to the data point of the graph created by jupyterlab & matplotlib
I made a tool to automatically back up the metadata of the Salesforce organization
I want to easily create a Noise Model
I made a useful tool for Digital Ocean
I made a GUI application with Python + PyQt5
Anyway, I want to check JSON data easily
I'm addicted to Kintone as a data store
I made a router config collection tool Config Collecor
I want to easily find a delicious restaurant
I made a random number graph with Numpy
Use a cool graph to analyze PES data!
I made a system that allows you to tweet just by making a phone call
I forgot to operate VIM, so I made a video for memorization. 3 videos by level
I want to easily implement a timeout in python
I made a user management tool for Let's Chat
I made a library to separate Japanese sentences nicely
I made a cleaning tool for Google Container Registry
I made a script to put a snippet in README.md
Visualize railway line data as a graph with Cytoscape 2
I made a Python module to translate comment outs
I made a code to convert illustration2vec to keras model
I want to easily build a model-based development environment
How to display DataFrame as a table in Markdown
I made a command to markdown the table clipboard
I made a python library to do rolling rank
I made a toolsver that spits out OS, Python, modules and tool versions to Markdown
I made a tool to get the answer links of OpenAI Gym all at once
I made a class to get the analysis result by MeCab in ndarray with python
Data visualization with Python-It's too convenient to draw a graph by attribute with "Facet" at once
I made a tool to automatically generate a simple ER diagram from the CREATE TABLE statement
I made a tool that makes it convenient to set parameters for machine learning models.
I made a method to automatically select and visualize an appropriate graph for pandas DataFrame
〇✕ I made a game
I made a package to filter time series with python
I made a box to rest before Pepper gets tired
I made a class that easily performs unixtime ← → datetime conversion
I made a command to generate a table comment in Django
I made a function to check the model of DCGAN