[PYTHON] Multiple file processing with Kivy + Matplotlib + Draw Graph on GUI

While referring to various information on the net, I also studied GUI and worked on a GUI program that can display input / output + processing + graph.

Execution environment

Windows10 Python 3.7.7 Kivy 1.11.1 GUI seems to be popular in recent years, so I challenged Kivy.

GUI made

The upper row is a button for file input / output and processing. Below the button is the graph display area. The graph is selected and displayed from the pull-down menu on the left.

AnalysisGUI.jpg

Code made

Below is the code details. The code on the kivy side describes the contents related to the GUI (layout, file selection, function to be called when a button is pressed, etc.), and the code on the Python side describes the actual processing. First of all, Python

main.py


import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import openpyxl
import os

from kivy.uix.boxlayout import BoxLayout
from kivy.app import App
from kivy.uix.widget import Widget
from kivy.uix.floatlayout import FloatLayout
from kivy.properties import ObjectProperty 
from kivy.uix.popup import Popup
from kivy.garden.matplotlib.backend_kivyagg import FigureCanvasKivyAgg

class LoadDialog(FloatLayout):
    load = ObjectProperty(None)
    cancel = ObjectProperty(None)
    Analysis = ObjectProperty(None)        
    current_dir = os.path.dirname(os.path.abspath(__file__))
 
class SaveDialog(FloatLayout):
    save = ObjectProperty(None)
    text_input = ObjectProperty(None)
    cancel = ObjectProperty(None)
    current_dir = os.path.dirname(os.path.abspath(__file__))  

class MainBoard(BoxLayout):
    file_name = ObjectProperty(None)
    info = ObjectProperty(None)
    bar_graph = ObjectProperty(None)
    lbl4spinner = ObjectProperty([])
    
    def __init__(self, **kwargs):
        super(MainBoard, self).__init__(**kwargs)
        self.master_flg = 0
        self.lbl=[]
        self.Data=[]
        
    def dismiss_popup(self):
        self._popup.dismiss()

    def show_load(self):
        content = LoadDialog(load=self.load, cancel=self.dismiss_popup, Analysis=self.Analysis)
        self._popup = Popup(title="Load file", content=content,size_hint=(0.9, 0.9))
        self._popup.open()

    def show_save(self):
        if self.master_flg==0:
            self.info.text = 'Load masterfile first, please'
        else:
            content = SaveDialog(save=self.save, cancel=self.dismiss_popup)
            self._popup = Popup(title="Save file", content=content, size_hint=(0.9, 0.9))
            self._popup.open()

    def load(self, path, filename, chkflg):
        try:
            if chkflg==1:
                self.file_name.text=str(filename.pop(0))
            else:
                self.file_name.text=str(filename[0])
        except Exception as e:
            self.info.text=str(type(e))+' : '+str(e) 
            
        Matrix=pd.DataFrame([])
        for i in range(len(filename)):
            NewData = pd.read_excel(filename[i])
            tmp_Data=NewData.iloc[0:,1:]
            tmp_Data.index=NewData.iloc[:,0]
            Matrix=pd.concat([Matrix,tmp_Data])
        
        self.lbl=list(Matrix.index)
        self.Data = Matrix
        self.dismiss_popup()
    
    def save(self, path, dirname):
        with pd.ExcelWriter(os.path.join(path, dirname)) as writer:
            self.Data.to_excel(writer)  
        self.dismiss_popup()         
    
    def Analysis(self):
        try:
            self.Data=self.Data*2
            self.lbl4spinner=map(str,self.lbl)#List to pass to spinner
            self.master_flg=1
            self.info.text = 'Analysis completed!'
        except Exception as e:
            self.info.text=str(type(e))+' : '+str(e) 
    
    def on_spinner_select(self,text):
        row_no=self.lbl.index(text)
        Forgraph=self.Data.iloc[row_no,:]
        plt.clf()
        bar_plt=plt.bar(Forgraph.index, Forgraph)
        self.ids.bar_graph.clear_widgets()
        self.ids.bar_graph.add_widget(FigureCanvasKivyAgg(plt.gcf()))
        
class MainApp(App):
    title = 'analysis'
    def build(self):
        return MainBoard()
        
if __name__ == '__main__':
    MainApp().run()

Then Kivy

main.kv


<MainBoard>:
    info: info
    file_name: file_name
    bar_graph: bar_graph
    
    
    BoxLayout:
        orientation: 'vertical'
        pos: root.pos
        size: root.size
        
        canvas.before:
            Color: 
                rgba: 0.9, 0.9, 0.9, 1
            Rectangle:
                pos: self.pos
                size: self.size
                
        Label:
            id : info
            text: 'load file first, please'
            size_hint_y: 0.1
            canvas.before:
                Color: 
                    rgba: 0, 0, 0, 1
                Rectangle:
                    pos: self.pos
                    size: self.size
            
        BoxLayout:
            orientation: 'horizontal'
            size_hint_y: 0.1
            
            TextInput:
                id: file_name
                font_size: 12
                size_hint_x: 0.7
                           
        BoxLayout:
            orientation: 'horizontal'
            size_hint_y: 0.1
            
            Button:
                text: 'Load & Analysis'
                size_hint_x: 0.2
                on_press: root.show_load()
                
            Button:
                text: 'Save data'
                size_hint_x: 0.2
                on_press: root.show_save()
                 
            Button:
                text: "Exit"
                id: btnExit
                size_hint_x: 0.2               
                on_press: app.root_window.close()  

        BoxLayout:
            orientation: 'horizontal'
            size_hint_y: 0.7
   
            Spinner:
                text: "No."
                values: root.lbl4spinner
                size_hint: 0.1, 0.1
                pos_hint: {'center_x': .5, 'center_y': .5}
                on_text: root.on_spinner_select(self.text)
                
            BoxLayout:
                id: bar_graph
                
<LoadDialog>:

    BoxLayout:
        size: root.size
        pos: root.pos
        orientation: "vertical"
            
        FileChooser:
            id: filechooser
            path:root.current_dir
            multiselect: True
            filters: ['*.xlsx']
            FileChooserIconLayout

        BoxLayout:
            size_hint_y: None
            height: 30
 
            Button:
                text: "Load selected file(s)"
                on_press: root.load(filechooser.path, filechooser.selection,0)
                on_release: root.Analysis()
                
            Button:
                text: "Load all files"
                on_press: root.load(filechooser.path,filechooser.files,1)
                on_release: root.Analysis()
            
            Button:
                text: "Cancel"
                on_release: root.cancel()
                
<SaveDialog>:
   
    BoxLayout:
        size: root.size
        pos: root.pos
        orientation: "vertical"
        
        BoxLayout:
            size_hint_y: 0.1
            Button:
                text: 'ListView'
                on_release: filechooser.view_mode = 'list'
            Button:
                text: 'IconView'
                on_release: filechooser.view_mode = 'icon'
                
        FileChooser:
            id: filechooser
            path:root.current_dir
            on_selection: dir_name.text = self.selection and self.selection[0] or ''
            filters: ['*.xls','*.xlsx']
            FileChooserListLayout

        TextInput:
            id: dir_name
            size_hint_y: None
            height: 30
            multiline: False

        BoxLayout:
            size_hint_y: None
            height: 30

            Button:
                text: "Save"
                on_release: root.save(filechooser.path, dir_name.text)         
                            
            Button:
                text: "Cancel"
                on_release: root.cancel()

Commentary

The base program uses Kivy's Filechooser. Filechooser itself has official documents and explanations of many ancestors, so it is omitted. I will explain only where I stumbled.

Due to the flow, the program does not flow from the top, but it goes back and forth between Kivy code and Py code, so I will explain along the flow.

1 File selection

--When you press the Load button on the GUI after starting the program, the Show Load function will be called from the Kivy side.

main.kv


            Button:
                text: 'Load & Analysis'
                size_hint_x: 0.2
                on_press: root.show_load()

--The Show load function launches LoadDialog in a separate window. At that time, the specified functions (Load function, Cancel function, Analysys function this time) in the MainBoard class are linked by content. (At first, I wondered why it was such a troublesome thing, but LoadDialog is a different class, so I can't call the associated function directly.)

main.py


    def show_load(self):
        content = LoadDialog(load=self.load, cancel=self.dismiss_popup, Analysis=self.Analysis)
        self._popup = Popup(title="Load file", content=content,size_hint=(0.9, 0.9))
        self._popup.open()

--Kivy's LoadDialog code. In FileChooser, select the file. This time, it is assumed that the original data is saved in xlsx format. It is filtered so that files in unnecessary formats are not displayed. Also, multiselect is specified as True so that multiple files can be selected. The id is a variable called filechooser that holds the address of the selected file.

main.kv


        FileChooser:
            id: filechooser
            path:root.current_dir
            multiselect: True
            filters: ['*.xlsx']
            FileChooserIconLayout

--Place 3 buttons following the above code. (1) To select and specify a file to be read by clicking (filechooser.selection) (2) To read all files in the current directory (filechooser.files) (3) Cancel button. When you press the buttons ① and ②, the load function is called first. The last number in the load function argument is the identification number (because it calls the same function).

main.kv


        BoxLayout:
            size_hint_y: None
            height: 30
 
            Button:
                text: "Load selected file(s)"
                on_press: root.load(filechooser.path, filechooser.selection,0)
                on_release: root.Analysis()
                
            Button:
                text: "Load all files"
                on_press: root.load(filechooser.path,filechooser.files,1)
                on_release: root.Analysis()
            
            Button:
                text: "Cancel"
                on_release: root.cancel()

2 File reading

--In the Load function on the Python side, use the file address (passed in List format) passed from Filechooser to extract data from xls. The following part is the code to display the address on the GUI. --At this time, if you read the files in the directory all at once, be careful because the directory address (files are not linked) is at the beginning of the list. For this, we have extracted the first section from the list. --```except Ececpiion as e: `` The part after `displays the contents when an error occurs.

main.pv


    def load(self, path, filename, chkflg):
        try:
            if chkflg==1:
                self.file_name.text=str(filename.pop(0))
            else:
                self.file_name.text=str(filename[0])
        except Exception as e:
            self.info.text=str(type(e))+' : '+str(e) 

--Next, read the data. In this xlsx, the parameter name is entered in column A and the data number is entered in the first row. --The for statement repeatedly reads the data of all the selected functions and finally stores them in the variables (matrix) `self.lbl``` and ``` self.Data```. --Finally, when you close LoadDialog``` with `` dismiss_popup ()` ``, the Load function is finished.

main.pv


        Matrix=pd.DataFrame([])
        for i in range(len(filename)):
            NewData = pd.read_excel(filename[i])
            tmp_Data=NewData.iloc[0:,1:]
            tmp_Data.index=NewData.iloc[:,0]
            Matrix=pd.concat([Matrix,tmp_Data])
        
        self.lbl=list(Matrix.index)
        self.Data = Matrix
        self.dismiss_popup()

3. 3. analysis

--In the LoadDialog on the kivy side, the Analysis function is called by on_release: root.Analysis () `` `following the Load function. --Here, describe the data processing you actually want to perform. This time, we simply double the data. --The following `` `self.lbl4spinner = map (str, self.lbl) is the list to pass to the graph selection pull-down on the GUI. -- self.master_flg is a flag to see if the data has been read. It is used to judge whether or not to save.

main.pv


    def Analysis(self):
        try:
            self.Data=self.Data*2
            self.lbl4spinner=map(str,self.lbl)#List to pass to spinner
            self.master_flg=1
            self.info.text = 'Analysis completed!'
        except Exception as e:
            self.info.text=str(type(e))+' : '+str(e) 

Four. graph display

--First, the explanation on the kivy side. Use Spinner to display a pull-down menu. -- text: "No." `` `specifies the characters to display before pulling down. -- values: root.lbl4spinner will display the list obtained in the analysis section when pulling down. --on_text: root.on_spinner_select (self.text) `, call the on_spinner_select function on the python side with the item selected from the pull-down as an argument ( self.text```).

main.kv


            Spinner:
                text: "No."
                values: root.lbl4spinner
                size_hint: 0.1, 0.1
                pos_hint: {'center_x': .5, 'center_y': .5}
                on_text: root.on_spinner_select(self.text)
                
            BoxLayout:
                id: bar_graph

--The called on_spinner_select function creates a graph based on the argument information. -- self.lbl.index (text) `` `Returns the order (number of lines) that matches the information selected in the pull-down. -- Forgraph = self.Data.iloc [row_no ,:] Reads the number of rows specified by --Create a graph with bar_plt = plt.bar (Forgraph.index, Forgraph)``. This time I created a bar graph. --``` self.ids.bar_graph.add_widget (FigureCanvasKivyAgg (plt.gcf ())) ``to graph to Kivy's BoxLayout (id: bar_graph) .. -- plt.clf () , self.ids.bar_graph.clear_widgets ()` `` is for clearing the previous graph when switching the pull-down selection and redisplaying. is.

main.py


    def on_spinner_select(self,text):
        row_no=self.lbl.index(text)
        Forgraph=self.Data.iloc[row_no,:]
        plt.clf()
        bar_plt=plt.bar(Forgraph.index, Forgraph)
        self.ids.bar_graph.clear_widgets()
        self.ids.bar_graph.add_widget(FigureCanvasKivyAgg(plt.gcf()))

5.Save --Save is omitted because it is processed in the same way as Load.

6. Other

--As a preliminary preparation, in order to exchange between Kivy and Python, it is necessary to define Object at the beginning of the MainBoard class. In addition, on the Kivy side, it is necessary to associate Object and id.

main.py


class MainBoard(BoxLayout):
    file_name = ObjectProperty(None)
    info = ObjectProperty(None)
    bar_graph = ObjectProperty(None)
    lbl4spinner = ObjectProperty([])

main.kv


<MainBoard>:
    info: info
    file_name: file_name
    bar_graph: bar_graph

--Also, the variables used between the functions are declared and summarized at the initialization stage to avoid duplication.

main.py


    def __init__(self, **kwargs):
        super(MainBoard, self).__init__(**kwargs)
        self.master_flg = 0
        self.lbl=[]
        self.Data=[]

Sites that were taken care of

I especially referred to the Kivy-related information on the following site. kivy official document: Filechooser After all the first is the official document. Longing for freelance It was helpful as a starting point. The layout of this GUI is also referred to here. narito blog It was very helpful in understanding the relationship between Kivy and Python. stackoverflow : Python - Kivy framework - Spinner values list List display to Spinner, I think I would have been stuck without this post.

What I want to improve in the future

I use ids for graph drawing, but I want to describe this with Object Propaty.

Recommended Posts

Multiple file processing with Kivy + Matplotlib + Draw Graph on GUI
Draw a graph with matplotlib from a csv file
Draw Japanese with matplotlib on Ubuntu
Draw a loose graph with matplotlib
Draw a graph with PySimple GUI
How to draw a bar graph that summarizes multiple series with matplotlib
Draw a flat surface with a matplotlib 3d graph
Draw a graph by processing with Pandas groupby
[Python] How to draw multiple graphs with Matplotlib
Draw a line / scatter plot on the CSV file (2 columns) with python matplotlib
Band graph with matplotlib
[Python] How to draw a line graph with Matplotlib
Study math with Python: Draw a sympy (scipy) graph with matplotlib
Graph Excel data with matplotlib (1)
Draw a graph with NetworkX
Draw netCDF file with python
Graph drawing method with matplotlib
Graph Excel data with matplotlib (2)
Create SVG graph with matplotlib on heroku (displayed in Japanese)
Draw a graph with networkx
Animate multiple graphs with matplotlib
Draw a graph with Julia + PyQtGraph (2)
matplotlib: Insert comment on timeline graph
Draw a graph with Julia + PyQtGraph (1)
Draw a graph with Julia + PyQtGraph (3)
Draw retention rate graph in Matplotlib
Draw a graph with pandas + XlsxWriter
Easy to draw graphs with matplotlib
Draw Lyapunov Fractal with Python, matplotlib
GUI application by Kivy (including matplotlib)
(Matplotlib) I want to draw a graph with a size specified in pixels
How to title multiple figures with matplotlib
[Python] Set the graph range with matplotlib
GUI programming with kivy ~ Part 4 Various buttons ~
How to draw a graph using Matplotlib
Display Matplotlib xy graph in PySimple GUI.
Draw shapes on feature layers with ArcPy
GUI box area selection on Matplotlib plots
Write SVG graphs with matplotlib on heroku
Draw hierarchical axis labels with matplotlib + pandas
Policy-based routing on machines with multiple NICs
Graph trigonometric functions with numpy and matplotlib
Simply draw a graph by specifying a file
Draw a graph with PyQtGraph Part 1-Drawing
Create a graph with borders removed with matplotlib