PythonでGUIグラフ操作アプリを作ってみる【Tkinter初心者】

スポンサーリンク

こんにちは。
今回は、GUIグラフ操作アプリケーションを所用で作りたくなったので、備忘録として記載します。

①やりたいこと

・ファイル参照で2つのスペクトルデータ(csv形式)を読み込む。
・読み込んだデータをグラフ描写。
・シフト&スケール変更して、2つのスペクトルが一致する値を見つける。

特に3番目のシフト&スケール変更を、スライダーなどのマウス操作でグリグリ動かしたいので、今回GUIアプリケーションを作ることにしました。
GUIアプリの開発は全くの初心者ですが、とりあえずメジャーそうなTkinterというライブラリで作っていきます。

②必要なライブラリ

・Tkinter
・Pandas
・Matplotlib

はい、非常にシンプルですね。追加のライブラリは必要なさそうです。

③App1:データの読み込み&グラフ描写

[App1]


import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import matplotlib.pyplot as plt
import pandas as pd


class Application(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.master.title('Application1')
        self.pack()
        self.create_widgets()
    
    def create_widgets(self):
        #----------------------plot1-------------------------------------------------------------------------
        self.Frame1 = tk.Frame(self.master)
        self.Frame1.pack(side=tk.TOP)
        
        self.Button11 = tk.Button(self.Frame1, text="Reference", command=lambda: self.load_data(self.entry1))
        self.Button11.pack(side=tk.LEFT)
        
        self.entry1 = tk.Entry(self.Frame1, width=100)
        self.entry1.pack(side=tk.LEFT)
        
        self.Button12 = tk.Button(self.Frame1, text="Plot data", command=lambda: self.plot_data(self.entry1, self.plot1))
        self.Button12.pack(side=tk.LEFT)
        
        
        #----------------------plot2-------------------------------------------------------------------------
        self.Frame2 = tk.Frame(self.master)
        self.Frame2.pack(side=tk.TOP)
        
        self.Button21 = tk.Button(self.Frame2, text="Reference", command=lambda: self.load_data(self.entry2))
        self.Button21.pack(side=tk.LEFT)
        
        self.entry2 = tk.Entry(self.Frame2, width=100)
        self.entry2.pack(side=tk.LEFT)
        
        
        self.Button22 = tk.Button(self.Frame2, text="Plot data", command=lambda: self.plot_data(self.entry2, self.plot2))
        self.Button22.pack(side=tk.LEFT)


        #----------------------figure-------------------------------------------------------------------------
        self.Frame3 = tk.Frame(self.master)
        self.Frame3.pack(side=tk.TOP)
        
        self.canvas = FigureCanvasTkAgg(fig, self.Frame3)
        self.canvas.draw()
        self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
        
        self.plot1, = ax.plot([],[], label='data1', c='blue')
        self.plot2, = ax.plot([],[], label='data2', c='red')


        self.toolbar = NavigationToolbar2Tk(self.canvas, self.Frame3)
        self.toolbar.update()
        self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
        #----------------------end-------------------------------------------------------------------------
        
    def load_data(self, entry):
        file_path = tk.filedialog.askopenfilename()
        entry.insert(tk.END, file_path)
        
    def plot_data(self, entry, plot):
        filename = entry.get()
        try:
            df = pd.read_csv(filename, header=0)
            x_org = df.iloc[:,0]
            y_org = df.iloc[:,1]
        except:
            x_org = []
            y_org = []
            
        plot.set_xdata(x_org)
        plot.set_ydata(y_org)

        self.canvas.draw()

        
        
fig = plt.Figure(figsize=(10, 10))
ax = fig.add_subplot(111)
ax.set_xlim(-1,1)
ax.set_ylim(-1,1)
  
root = tk.Tk()
app = Application(master=root)
app.mainloop()

・パーツの配置方法

Tkinterでは基本的に、画面上のパーツの配置は「Frame」という単位で行うようです。
必要なボタン(tk.Button())などをつけたFrameを、tk.Tk()で作成したウインドウ上に配置していきます。

・ボタンへの関数の割り当て

Frame1、Frame2でデータの読み込み部分を作ります。
データ参照のためのButton1には、ファイル選択画面が出てくる関数を割り当てます。
選択したファイルのパスを、エントリーボックス(entry1、entry2)に挿入するようにしています。
なお、Tkinterのボタンに割り当てる関数には、引数をそのまま与えることはできません。

(NG例:tk.Button(command=function(arg1, arg2…) )

そこで、ラムダ式として、引数を指定した関数を指定しています。

・グラフの配置

Frame3にMatplotlibのグラフを配置しています。
なお、axisには空のプロット(ax.plot([], []))を配置しておき、データを読み込んだのちに、.set_xdata()、.set_ydata()で値を更新する形にしておきます。(App2でplotをグリグリ操作するのに必要な部分です。)
またグラフ下部には、グラフのツールバー(Pyplotで通常出てくるやつ)を表示するようにしておきます。

無事、グラフ描写するアプリケーションを作ることができました。

④App2:スライダーによる変数の更新と再描写

[App2]


import tkinter as tk
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import matplotlib.pyplot as plt
import pandas as pd


class Application(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.master.title('Application2')
        self.pack()
        self.create_widgets()
        self.scale_default()
    
    def create_widgets(self):
        #----------------------plot1-------------------------------------------------------------------------
        self.Frame1 = tk.Frame(self.master)
        self.Frame1.pack(side=tk.TOP)
        
        self.Button11 = tk.Button(self.Frame1, text="Reference", command=lambda: self.load_data(self.entry1))
        self.Button11.pack(side=tk.LEFT)
        
        self.entry1 = tk.Entry(self.Frame1, width=100)
        self.entry1.pack(side=tk.LEFT)
        
        self.Button12 = tk.Button(self.Frame1, text="Plot data", command=lambda: self.plot_data(self.entry1, self.plot1, self.org_data1))
        self.Button12.pack(side=tk.LEFT)
        
        
        #----------------------plot2-------------------------------------------------------------------------
        self.Frame2 = tk.Frame(self.master)
        self.Frame2.pack(side=tk.TOP)
        
        self.Button21 = tk.Button(self.Frame2, text="Reference", command=lambda: self.load_data(self.entry2))
        self.Button21.pack(side=tk.LEFT)
        
        self.entry2 = tk.Entry(self.Frame2, width=100)
        self.entry2.pack(side=tk.LEFT)
        
        self.Button22 = tk.Button(self.Frame2, text="Plot data", command=lambda: self.plot_data(self.entry2, self.plot2, self.org_data2))
        self.Button22.pack(side=tk.LEFT)


        #----------------------figure-------------------------------------------------------------------------
        self.Frame3 = tk.Frame(self.master)
        self.Frame3.pack(side=tk.LEFT)
        
        self.canvas = FigureCanvasTkAgg(fig, self.Frame3)
        self.canvas.draw()
        self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
        
        self.org_data1=[[],[]]
        self.plot1, = ax.plot(self.org_data1[0],self.org_data1[1], label='data1', c='blue')
        
        self.org_data2=[[],[]]
        self.plot2, = ax.plot([],[], label='data2', c='red')
        

        self.toolbar = NavigationToolbar2Tk(self.canvas, self.Frame3)
        self.toolbar.update()
        self.canvas.get_tk_widget().pack(side=tk.TOP, fill=tk.BOTH, expand=True)
        
        
        #----------------------control-------------------------------------------------------------------------
        self.Frame4 = tk.Frame(self.master)
        self.Frame4.pack(side=tk.LEFT)
        
        self.shift_plot1 = tk.DoubleVar()
        self.scale_shift1 = tk.Scale(self.Frame4, variable=self.shift_plot1, from_ = -1, to=1, resolution=0.02,length=200,
                                     label='Shift parameter : Plot1', orient=tk.HORIZONTAL, command=self.change_plot1)
        self.scale_shift1.pack(side=tk.TOP)
        
        self.expand_plot1 = tk.DoubleVar()
        self.scale_expand1 = tk.Scale(self.Frame4, variable=self.expand_plot1, from_ = 0.1, to=5, resolution=0.1,length=200,
                                     label='Expand parameter : Plot1', orient=tk.HORIZONTAL, command=self.change_plot1)
        self.scale_expand1.pack(side=tk.TOP)
        
        
        self.shift_plot2 = tk.DoubleVar()
        self.scale_shift2 = tk.Scale(self.Frame4, variable=self.shift_plot2, from_ = -1, to=1, resolution=0.02,length=200,
                                     label='Shift parameter : Plot2', orient=tk.HORIZONTAL, command=self.change_plot2)
        self.scale_shift2.pack(side=tk.TOP)
        
        self.expand_plot2 = tk.DoubleVar()
        self.scale_expand2 = tk.Scale(self.Frame4, variable=self.expand_plot2, from_ = 0.1, to=5, resolution=0.1,length=200,
                                     label='Expand parameter : Plot2', orient=tk.HORIZONTAL, command=self.change_plot2)
        self.scale_expand2.pack(side=tk.TOP)


        
        self.Button41 = tk.Button(self.Frame4, text="Reset", command=self.reset_scale)
        self.Button41.pack(side=tk.TOP)
 

        
        #----------------------create_widgets END-------------------------------------------------------------------------
        
    def load_data(self, entry):
        file_path = tk.filedialog.askopenfilename()
        entry.insert(tk.END, file_path)
        
    def plot_data(self, entry, plot, org_data):
        filename = entry.get()
        try:
            df = pd.read_csv(filename, header=0)
            x_org = df.iloc[:,0]
            y_org = df.iloc[:,1]
        except:
            x_org = []
            y_org = []
            
        org_data.clear()
        org_data.append(x_org)
        org_data.append(y_org)
            
        plot.set_xdata(x_org)
        plot.set_ydata(y_org)

        self.canvas.draw()
        

    def scale_default(self):
        self.shift_plot1.set(0.0)
        self.expand_plot1.set(1.0)
        self.shift_plot2.set(0.0)
        self.expand_plot2.set(1.0)


    def reset_scale(self):
        self.scale_default()
        self.change_plot1()
        self.change_plot2()


    def change_plot1(self, event=None):
        a=self.expand_plot1.get()
        b=self.shift_plot1.get()
        x=a*self.org_data1[0]+b
        y=self.org_data1[1]
        self.plot1.set_xdata(x)
        self.plot1.set_ydata(y)

        self.canvas.draw()        

    def change_plot2(self, event=None):
        a=self.expand_plot2.get()
        b=self.shift_plot2.get()
        x=a*self.org_data2[0]+b
        y=self.org_data2[1]
        self.plot2.set_xdata(x)
        self.plot2.set_ydata(y)

        self.canvas.draw()        


        
fig = plt.Figure(figsize=(10, 10))
ax = fig.add_subplot(111)
ax.set_xlim(-1,1)
ax.set_ylim(-1,1)
  
root = tk.Tk()
app = Application(master=root)
app.mainloop()

・制御変数とスライダー

シフトとスケールの大きさを決める変数は、shift_plot1、expand_plot1として、tk.DoubleVar()という、浮動小数点値を格納できるTkinterの変数として定義しています。(controlの部分)
この変数を作成したスライダーの変数として指定(tk.Scale(variable=))すればOKです。

・データの計算&再描写を定義

グラフデータのX方向へのシフト&スケール変更は、change_plot1()、change_plot1()で定義しました。
再描写は、最初と同じく.set_xdata()、.set_ydata()で値を更新します。
引数が多くなり面倒になったので、ここはplot1、plot2それぞれ同じ内容の関数を定義しています。

○まとめ

今回は初めて、GUIのアプリケーションを作ってみました。

ボタンの配置など、細かい指定が必要なので、普段のデータ分析などと比べると、必然的にコード文量が多くなります。
文量が多くなる分、可読性などの観点から、オブジェクト指向の書き方の習得が必要になってきます。
学習コストは若干高いですが、良い勉強になりました。

今回は以上です。
ご参考になれば幸いです!

タイトルとURLをコピーしました