Python PySide6によるGUIアプリの作成

 本記事では、PySide6でデスクトップアプリを作成する雛形コードを載せました。本アプリの動作検証用のサンプルのexcelファイルと雛形コードは、次のgithubにアップしています。https://github.com/hk29-ai/template_for_GUI_app_using_pyside6

■ライブラリのインストール

pip install pyside6

■作成したアプリの仕様
 本プログラムを実行すると、次のようなwindowが表示されます。上部にある「Excelファイルを選択」のボタンを押下します。

下図のようなダイアログが表示されるので、エクセルファイルを選択します。

上記の後、下図のようにエクセル内のシート名が一覧で表示されます。

次に、下図のように処理したいシート名を選択します(複数可)。そして下部にある「データ処理の実行」ボタンを押します。

処理が終わると、下図のように完了の表示がされます。

アウトプットは、下図のようにグラフ画像を出力します(一部抜粋)。

また、下図のように複数のシートを行方向に結合して、ひとつのcsvファイルに保存します。

■本プログラム

import os
import sys
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import japanize_matplotlib
plt.rcParams['font.size'] = 15 # グラフの基本フォントサイズの設定
from PySide6.QtWidgets import (
    QApplication, QFileDialog, QMainWindow,
    QPushButton, QVBoxLayout, QWidget,
    QLabel, QTextEdit, QListWidget,
    QAbstractItemView
)
from PySide6.QtCore import Qt
from PySide6.QtGui import QFont, QPalette, QColor, QIcon

class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.resize(450, 500)
        self.setWindowTitle("アプリの雛形") 
        self.x_name = 'x'
        self.y_name = 'y'

        # ファイル選択ボタンを作成
        self.select_file_button = QPushButton("1. Excelファイルを選択")
        self.select_file_button.clicked.connect(self.open_file_dialog)
        # ボタンのサイズを2行分に設定
        self.select_file_button.setFixedHeight(2 * self.select_file_button.fontMetrics().height())
        # ボタンの背景色を薄いピンク色に設定 赤:#FFB6C1 黄色:#FFFFE0 青:#ADD8E6
        self.select_file_button.setStyleSheet("background-color: #FFB6C1; font-size: 16px;")

        # ファイルパスを表示するラベル
        self.file_path_label = QLabel() 
        # 進捗状況の表示
        self.process_label = QLabel(self)
        my_font = QFont("Arial", 20)  # フォント名とサイズを指定
        my_font.setBold(True)  # 太字に設定
        self.process_label.setFont(my_font)
        self.process_label.setAlignment(Qt.AlignCenter) # 中央揃え
        
        # フォントの色
        self.palette_blue = QPalette()
        self.palette_blue.setColor(QPalette.WindowText, QColor(0, 0, 255))  # 青字を指定

        # シート名を表示するリストウィジット
        self.sheet_list = QListWidget()
        self.sheet_list.setSelectionMode(QAbstractItemView.MultiSelection)

        # テキストエディタを作成
        self.text_edit = QTextEdit()
        # テキストボックスの高さを2行分に設定
        self.text_edit.setFixedHeight(3.5 * self.text_edit.fontMetrics().height())
        # テキストボックスに表示する文章を中央揃えに設定
        self.text_edit.setAlignment(Qt.AlignCenter)
        # テキストボックスの背景色を薄い黄色に設定
        self.text_edit.setStyleSheet("background-color:  #FFFFE0; font-size: 16px;") # 黄色:#FFFFE0
        self.text_edit.insertPlainText("2. 上記に表示されているリストから、\nデータ処理するシートを選択(複数可)")

        # 実行ボタン
        self.run_button = QPushButton("3. データ処理の実行")
        self.run_button.clicked.connect(self.start_processing)
        # ボタンのサイズを2行分に設定
        self.run_button.setFixedHeight(2 * self.run_button.fontMetrics().height())
        # ボタンの背景色を薄い青色に設定
        self.run_button.setStyleSheet("background-color: #ADD8E6; font-size: 16px;")

        # レイアウト 
        layout = QVBoxLayout()
        layout.addWidget(self.select_file_button)
        layout.addWidget(self.file_path_label) 
        layout.addWidget(self.sheet_list)
        layout.addWidget(self.text_edit)

        layout.addWidget(self.run_button)
        layout.addWidget(self.process_label)

        # ウィジェットを作成し、メインウィンドウに設定 
        widget = QWidget()
        widget.setLayout(layout)
        self.setCentralWidget(widget)

    # ファイル選択のダイアログ
    def open_file_dialog(self):
        options = QFileDialog.Options()
        file_path, _ = QFileDialog.getOpenFileName(self, "excelファイルを選択", "", "Excel Files (*.xlsx);;All Files (*)", options=options)
        
        if file_path:
            # シートリストを初期化
            self.sheet_list.clear()
            self.file_path_label.setText(file_path)

            # シート名の一覧を取得する
            excel_file = pd.ExcelFile(file_path)
            sheet_names = excel_file.sheet_names
            self.sheet_list.addItems(sheet_names)
            
            # ファイルパスをインスタンス変数化
            self.selected_excel_file = file_path
            
            # フォルダパスを取得してインスタンス変数化
            self.folder_path = os.path.dirname(file_path)
            
            # ファイル名の取得
            file_name = os.path.basename(file_path)
            # ファイルパスから、拡張子なしのファイル名と拡張子の取得
            file_name_wo_extension, extension = os.path.splitext(file_name)
            self.file_name = file_name_wo_extension

    # 折れ線グラフ
    def plot_graph_1(self, df, i, select_sheet_name):
        # データを抽出
        x_data = df[self.x_name].values
        y_data = df[self.y_name].values
        
        # データを辞書へ格納
        self.data_dict[select_sheet_name] = (x_data, y_data)
        
        # 散布図
        fig = plt.figure(figsize=(6,4))
        plt.scatter(x_data, y_data)
        # 折れ線図
        plt.plot(x_data, y_data)
        
        if select_sheet_name == 'ガンマ関数':
            plt.yscale('log')
        plt.grid()
        plt.xlabel(f"{self.x_name}")
        plt.ylabel(f"{self.y_name}")
        plt.title(f"{select_sheet_name}")
        
        # 保存するファイルパス
        _save_jpg_path = f"{self.folder_path}/{str(i).zfill(2)}_{select_sheet_name}.jpg"
        
        # 画像ファイルに出力
        plt.savefig(_save_jpg_path, bbox_inches="tight")
        print(f"save {_save_jpg_path}")
        plt.clf()

    # カテゴリ別の折れ線グラフ
    def plot_graph_2(self, df):
        ### ガンマ分布の確率密度関数のデータだけ抽出してグラフ化する
        x_name = 'x_data'
        y_name = 'y_data'
        label = 'select_sheet_name'
        legend_name = ''
        title = 'ガンマ分布の確率密度関数'

        # データフレームから条件指定で行データを抽出する
        df = df[df[label].str.contains(title)]

        # プロットするカラーを指定するための処理
        cnt = len(df.groupby(label).count().index.to_list())
        color_list = []
        for j in range(cnt):
            color_list.append(cm.hsv(j/cnt)) # jet cool autumn hsv
        color_list

        # ひとつのグラフにカテゴリ別にプロットする
        handle_list = []
        for i, legend in enumerate(df[label].unique()):
            # ラベルを文字列から絞って抽出する
            index = legend.index("_")
            label_name = legend[index + 1:]
            
            # 折れ線図を作成
            plt.plot(df.loc[df[label] == legend, x_name],
                        df.loc[df[label] == legend, y_name],
                        #facecolor = 'None',
                        #edgecolors = color_list[i],
                        color = color_list[i],
                        label = label_name)
        plt.grid()
        plt.legend(bbox_to_anchor = (1, 1), title = legend_name)
        plt.xlabel(x_name)
        plt.ylabel(y_name)
        plt.title(title)

        # 保存するファイルパス
        save_jpg_path = f"{self.folder_path}/{self.file_name}.jpg"
        plt.savefig(save_jpg_path, bbox_inches="tight")
        print(f"save {save_jpg_path}")

    # 選択したシートに対して実行
    def start_processing(self):
        self.process_label.setText("処理中...")
        QApplication.processEvents()  # GUIを更新する

        # 選択したシートをリストへ格納する
        selected_items = self.sheet_list.selectedItems()
        self.selected_sheet_names = [item.text() for item in selected_items]
        print('選択されたーシート', self.selected_sheet_names) 

        # シート毎に処理する(グラフを作成して、画像ファイルに保存する)
        self.data_dict = {} # 各シートデータをひとつにするための辞書
        for _i, _select_sheet_name in enumerate(self.selected_sheet_names, start=1):
            _df = pd.read_excel(self.selected_excel_file,
                                sheet_name = _select_sheet_name,
                                usecols = "A:B", # AからB列までを選択
                                engine = "openpyxl"
            )
            # グラフ化1
            self.plot_graph_1(_df, _i, _select_sheet_name)

        # シート別のデータを1つに結合する
        rows = []
        for _select_sheet_name, (x_data, y_data) in self.data_dict.items():
            for x, y in zip(x_data, y_data):
                rows.append([_select_sheet_name, x, y])
        
        # データフレーム作成
        df1 = pd.DataFrame(rows, columns=["select_sheet_name", "x_data", "y_data"])
        save_csv_path = f"{self.folder_path}/{self.file_name}.csv"
        df1.to_csv(save_csv_path, index=False, encoding="shift-jis")
        print(f"save {save_csv_path}")
        
        # グラフ化2
        self.plot_graph_2(df1)

        print('処理が完了しました')
        self.process_label.setPalette(self.palette_blue)
        self.process_label.setText('処理が完了しました。')

if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = MainWindow()
    window.setWindowIcon(QIcon("image.ico"))
    window.show()
    sys.exit(app.exec())

以上

<広告>