GUIでデータファイルを選択してグラフ化

##はじめに
大量の同じフォーマットのExcel,CSVファイルのデータをまとめて一つのグラフにしたい。直感的にわかりやすいようにGUIでファイルの選択、軸範囲の設定を行いたい。

##仕様
![無題.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1499277/521861ca-4b90-c26c-9304-d3b2a2ea10f4.png)
・「ファイルを選択」ボタンでグラフ化するファイルを選択する。※ファイルは複数選択可能
・選択可能なファイルの拡張子はxls,xlsx,csv
・処理するファイルのデータのヘッダー行数を指定する。ヘッダーがない場合は、0または空白を指定する
・グラフのx列、y列となる処理するファイルのデータの列数を指定する
・x列、y列の組み合わせは最大４つまで指定可能
・x列またはy列のどちらか一方でも空欄の場合はグラフ化を行わない
・保存するグラフのファイル名は「保存ファイル名」で指定可能。空欄の場合のファイル名は「現在時刻+条件番号」となる
・グラフのx軸の範囲は「x最小」,「x最大」,y軸の範囲は「y最小」,「y最大」で設定可能。空欄の条件は自動設定になる
・「実行ボタン」でグラフ化の処理が走る
・グラフ化したファイルはGUIのコードが存在するフォルダ内に保存される
##コード

```python
import PySimpleGUI as sg
import pandas as pd
import matplotlib.pyplot as plt
import os
import numpy as np
import datetime

INPUT_BOX_SIZE = 5
HEADER_ROW = 1
SAVE_FILE_NAME_ROW = 2
OFFSET_DATA_NUM = 3
LINE_PER_DATA = 6
COND_LINE_NUM = 4
X_COL_OF_LINE = 0
Y_COL_OF_LINE = 1
X_MIN_OF_LINE = 2
X_MAX_OF_LINE = 3
Y_MIN_OF_LINE = 4
Y_MAX_OF_LINE = 5

def plotfig(axobj, x_data, y_data, filename, cmap, xlabel, ylabel, x_min, x_max, y_min, y_max):
    ##グラフ描画用の関数
    #引数の説明
    #axobj:グラフオブジェクト,x_data:x軸データ, y_data:y軸データ, filename:元データのファイル名
    #cmap:グラフのカラー, xlabel：x軸ラベル, ylabel：y軸ラベル
    #x_min：x軸最小値, x_max：x軸最大値, y_min：y軸最小値, y_max：y軸最大値
    axobj.plot(x_data, y_data, label=filename, color=cmap, marker='o')
    axobj.grid()
    axobj.legend()
    axobj.set_xlabel(xlabel)
    axobj.set_ylabel(ylabel)
    ## 軸範囲の設定 ※GUIで指定されていない場合は自動で設定
    # x軸
    if x_min.strip() and x_max.strip():
        axobj.set_xlim(left=int(x_min), right=int(x_max))
    elif x_min.strip():
        axobj.set_xlim(left=int(x_min))
    elif x_max.strip():
        axobj.set_xlim(right=int(x_max))
    # y軸
    if y_min.strip() and y_max.strip():
        axobj.set_ylim(bottom=int(y_min), top=int(y_max))
    elif y_min.strip():
        axobj.set_ylim(bottom=int(y_min))
    elif y_max.strip():
        axobj.set_ylim(top=int(y_max))

#  セクション1 - オプションの設定と標準レイアウト
sg.theme('Dark Blue 3')

layout = [
    #1行目
    [sg.InputText('ファイル一覧',enable_events=True,), sg.FilesBrowse('ファイルを選択', key='-FILES-', file_types=(("Excel ファイル", "*.xlsx"),("Excel ファイル", "*.xls"),("CSV ファイル", "*.csv")))],
    #2行目
    [sg.Text('ヘッダ行数', size=(10, 1)),sg.InputText('1', size=(INPUT_BOX_SIZE, 1)),sg.Text('保存ファイル名', size=(12, 1)),sg.InputText(' ', size=(INPUT_BOX_SIZE*5, 1))],
    #3行目
    [sg.Text('条件', size=(5, 1)),sg.Text('x列', size=(INPUT_BOX_SIZE, 1)),sg.Text('y列', size=(INPUT_BOX_SIZE, 1)),
    sg.Text('x最小', size=(INPUT_BOX_SIZE-1, 1)), sg.Text('x最大', size=(INPUT_BOX_SIZE-1, 1)), sg.Text('y最小', size=(INPUT_BOX_SIZE-1, 1)), sg.Text('y最大', size=(INPUT_BOX_SIZE-1, 1))],
    #4行目
    [sg.Text('1',size=(5, 1)),sg.InputText('1', size=(INPUT_BOX_SIZE, 1)),sg.InputText('2', size=(INPUT_BOX_SIZE, 1)),
    sg.InputText('', size=(INPUT_BOX_SIZE, 1)), sg.InputText('', size=(INPUT_BOX_SIZE, 1)), sg.InputText('', size=(INPUT_BOX_SIZE, 1)), sg.InputText('', size=(INPUT_BOX_SIZE, 1))],
    #5行目
    [sg.Text('2',size=(5, 1)),sg.InputText(' ', size=(INPUT_BOX_SIZE, 1)),sg.InputText(' ', size=(INPUT_BOX_SIZE, 1)),
    sg.InputText('', size=(INPUT_BOX_SIZE, 1)), sg.InputText('', size=(INPUT_BOX_SIZE, 1)), sg.InputText('', size=(INPUT_BOX_SIZE, 1)), sg.InputText('', size=(INPUT_BOX_SIZE, 1))],
    #6行目
    [sg.Text('3',size=(5, 1)),sg.InputText(' ', size=(INPUT_BOX_SIZE, 1)),sg.InputText(' ', size=(INPUT_BOX_SIZE, 1)),
    sg.InputText('', size=(INPUT_BOX_SIZE, 1)), sg.InputText('', size=(INPUT_BOX_SIZE, 1)), sg.InputText('', size=(INPUT_BOX_SIZE, 1)), sg.InputText('', size=(INPUT_BOX_SIZE, 1))],
    #7行目
    [sg.Text('4',size=(5, 1)),sg.InputText(' ', size=(INPUT_BOX_SIZE, 1)),sg.InputText(' ', size=(INPUT_BOX_SIZE, 1)),
    sg.InputText('', size=(INPUT_BOX_SIZE, 1)), sg.InputText('', size=(INPUT_BOX_SIZE, 1)), sg.InputText('', size=(INPUT_BOX_SIZE, 1)), sg.InputText('', size=(INPUT_BOX_SIZE, 1))],
    #8行目
    [sg.Submit(button_text='実行ボタン')]
]

# セクション 2 - ウィンドウの生成
window = sg.Window('グラフ保存', layout)

# セクション 3 - イベントループ
while True:
    event, values = window.read()

    if event is None:
        print('exit')
        break

    if event == '実行ボタン':
        # ファイルのパスを抽出
        files = values['-FILES-'].split(';')
        # ファイルの数を抽出
        files_num = len(files)
        # 処理対象のデータのヘッダー行を抽出
        header_row = values[HEADER_ROW]
        # 画像の保存ファイル名を抽出
        input_save_file_name = values[SAVE_FILE_NAME_ROW].strip()
        
        d_temp={}
        # グラフ処理
        for k, v in values.items():   # 一度pd.Seriesに変換
            # ヘッダー数を除いてデータフレームを作成
            if type(k)==str:
                continue
            # GUIの入力データ以外は無視
            if k < OFFSET_DATA_NUM:
                continue
            d_temp[k-1] = pd.Series(v)

        df = pd.DataFrame(d_temp)
        # DataFrameの行列をGUIの表示の形に成形
        pd_cond = pd.DataFrame(df.values.reshape(COND_LINE_NUM, LINE_PER_DATA))

    # グラフオブジェクトの作成
        fig = np.zeros(COND_LINE_NUM)
        ax = np.zeros(COND_LINE_NUM)
        cmap = plt.get_cmap("tab10") # カラーマップを定義
        fig_list = ['']*COND_LINE_NUM
        ax_list = ['']*COND_LINE_NUM
        for n in range(COND_LINE_NUM):
            fig_list[n] = plt.figure()
            ax_list[n] = fig_list[n].add_subplot(1,1,1)

    # ファイルからデータ読み込み
        for i, file in enumerate(files):
            _, ext = os.path.splitext(file)
            #Excelファイルの場合
            if ext==".xls" or ext==".xlsx":
                if header_row.strip() and int(header_row) > 0:
                    readdata = pd.read_excel(file, header=int(header_row)-1)#行数は１始まりを期待しいるため-1。0始まりなら-1は不要。
                else:
                    readdata = pd.read_excel(file, header=None)
            #CSVファイルの場合
            elif ext==".csv":
                if header_row.strip() and int(header_row) > 0:
                    readdata = pd.read_csv(file, header=int(header_row)-1)
                else:
                    readdata = pd.read_csv(file, header=None)
            #グラフの凡例につけるためのファイル名※拡張子は除いている
            filename = os.path.splitext(os.path.basename(file))[0]
            #　条件ごとに各ファイルのデータを重ねる
            for j in range(COND_LINE_NUM):
                #x列、y列が空欄の場合は処理を行わない
                if not pd_cond.iloc[j,X_COL_OF_LINE].strip() or not pd_cond.iloc[j,Y_COL_OF_LINE].strip():
                    continue
                # 処理対応データ格納列の情報を抽出してint化
                x_col = int(pd_cond.iloc[j, X_COL_OF_LINE])-1 #GUIの条件は1始まりなので-1
                y_col = int(pd_cond.iloc[j, Y_COL_OF_LINE])-1 #GUIの条件は1始まりなので-1
                x_data = readdata.iloc[:,x_col]
                y_data = readdata.iloc[:,y_col]
                # x_col,y_colは空欄でないことが保証されているが、最大最小値は空欄の可能性があるためここではint化しない
                x_min = pd_cond.iloc[j, X_MIN_OF_LINE]
                x_max = pd_cond.iloc[j, X_MAX_OF_LINE]
                y_min = pd_cond.iloc[j, Y_MIN_OF_LINE]
                y_max = pd_cond.iloc[j, Y_MAX_OF_LINE]
                # グラフ描画用の関数でグラフ描画
                plotfig(ax_list[j], x_data, y_data, filename, cmap(i), 
                        readdata.columns[x_col], readdata.columns[y_col],
                        x_min, x_max, y_min, y_max)

        # ファイル名に現在時刻を付けて画像を保存
        for j in range(COND_LINE_NUM):
                #x列、y列が空欄の場合は処理を行わない
                if not pd_cond.iloc[j,X_COL_OF_LINE].strip() or not pd_cond.iloc[j,Y_COL_OF_LINE].strip():
                    continue
                if not input_save_file_name:
                    dt_now = datetime.datetime.now()
                    save_file_name = dt_now.strftime('%Y年%m月%d日%H時%M分%S秒') + "_条件" + str(j+1)# GUIの条件は1始まりなので+1
                else:
                    save_file_name = input_save_file_name + "_条件" + str(j+1)# GUIの条件は1始まりなので+1
                fig_list[j].savefig(save_file_name)

        # ポップアップ
        sg.popup("終了～")

# セクション 4 - ウィンドウの破棄と終了
window.close()
```

## 注意点
importしているライブラリ以外にopenpyxlをインストールしておく必要有