0
1

Tkinterでグラフを表示したい!

Last updated at Posted at 2024-01-12

はじめに

pyVISAの記事で、「LabVI*Wの代わりになる!」というようなことを書いていました。

ただ、LabV*EWのいいところって、GUI上にグラフを用意して、測定値データをリアルタイムで確認できたりするところにあるんですよね。。。

そこで、Pythonで利用できるTkinterを使って、測定値のデータをGUI上にリアルタイムで表示するプログラムを考えてみました。

こんな風に出力されるGUIを書いてみます。

環境

  • python3.9.7
  • MacOS 14.2.1
    ※ Windowsでも動くと思います。

計測値データを作成

計測器が周りに無かったので(泣)、計測データっぽいものを捏造作成してみましょう。
線形に増加する値に、乱数を乗せるプログラムを書きました。
今回はこれで作成したデータを一定間隔で読み出し、擬似的に計測器からデータが返ってきた状況を表現してみようと思ます。

import numpy as np

def generate_random_data(x_range, random_range):
    np.random.seed(123)
    x = np.linspace(*x_range)
    random_values = np.random.uniform(*random_range, size=x.shape)
    y = x + random_values
    return x, y

if __name__=='__main__':
    x_range = (0, 30)
    random_range = (0, 2)
    x, y = generate_random_data(x_range=x_range, random_range=random_range)

    # save
    np.savetxt('data.csv', np.column_stack((x, y)), delimiter=',')

TkinterのGUI上にグラフを表示

Tkinterの書き方は色々と派閥があるようですが、私はclassを使った書き方が好きです。
ボタンのコマンドとして、メソッドを用意する、といった設計ができるためです。
GUI上の情報なども、メソッドを用意しておけばインスタンスから取得することもできます。

まずは下のclassをベースに、グラフを表示する方法を書いていきます。

import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
import numpy as np

class Application(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.master.title('matplotlib graph')

        # matplotlib配置用フレーム
        frame = tk.Frame(self.master)

        # matplotlibの描画領域の作成
        fig = Figure()
        self.ax = fig.add_subplot(1, 1, 1)
        # matplotlibの行場領域とウィジェットの関連付け
        self.fig_canvas = FigureCanvasTkAgg(fig, frame)
        # matplotlibのツールバーを作成
        self.toolbar = NavigationToolbar2Tk(self.fig_canvas, frame)
        # matploglibのグラフをフレームに配置
        self.fig_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

        # フレームをウィンドウに配置
        frame.pack()

        # ボタンの作成
        button = tk.Button(self.master, text='Draw Graph', command=self.button_click)
        # 配置
        button.pack(side=tk.BOTTOM)

    def button_click(self):
        with open('data.csv', 'r') as f:
            data = np.loadtxt(f, delimiter=',')
            x = data[:, 0]
            y = data[:, 1]
        self.ax.plot(x, y)
        self.fig_canvas.draw()

if __name__=='__main__':
    root = tk.Tk()
    app = Application(master=root)
    app.mainloop()

プログラムを実行し、Draw Graphのボタンを押せばグラフが表示されるはずです。

計測器「っぽい」動作をする関数を用意

計測器のように、呼び出すたびに値を返すような関数を作ってみます。
先のモジュールで作成したdata.csvを読み取り、呼び出すたびに上から順に値を返すような関数を書いてみます。
ここではiteratorを使って実装してみました。

class Iterator(object):
    def __init__(self, array):
        self._array = array
        self._i = 0

    def __iter__(self):
        return self

    def __len__(self):
        return self._array.shape[0]

    def __next__(self):
        if self._i == len(self._array.shape[0]):
            raise StopIteration()
        x, y = self._array[self._i]
        self._i += 1
        return x, y

リアルタイムに表示する

TkinterでGUIを表示する場合、mainloop()を使っています。
インスタンスからGUIの表示内容を更新することは不可なので、classにメソッドを追加していきます。

グラフをリアルタイムに生成するメソッドは、以下のように書きました。
計測器を使うときのイメージは、itrからデータを呼び出すitr.__next__()を、計測器からデータを読み込む関数に置き換えれば良いと思います。

def run_gengraph(self):
    with open('data.csv', 'r') as f:
        data = np.loadtxt(f, delimiter=',')

    # iteratorのインスタンス生成
    itr = Iterator(data)

    # グラフ描画用のlist
    x_ = []
    y_ = []

    for _ in range(len(itr)):
        # itrからデータを取得
        # 計測データはここで受ければOK
        x, y = itr.__next__()
        # リストに追加
        x_.append(x)
        y_.append(y)
        # 描画
        self.ax.clear() # グラフのリセット
        self.ax.plot(x_, y_)
        self.fig_canvas.draw()
        # GUIの更新
        self.master.update()
        # 待機時間
        time.sleep(1)

ちょっとハマったところは、ただ描画するだけではGUIの更新がされませんでした。
self.master.update()を追加することで更新され、ブラフにプロットがどんどん追加されるようになりました。
また、self.ax.clear()を追加することで、プロットごとに線が追加されることなく更新される様になってます。
(いずれもChatGPTに聞くと教えてくれました。ありがたや〜)

最終的にGUIを記述しているclassは以下の通りです。

class Application(tk.Frame):
    def __init__(self, master=None):
        super().__init__(master)
        self.master = master
        self.master.title('matplotlib graph')

        # matplotlib配置用フレーム
        frame = tk.Frame(self.master)

        # matplotlibの描画領域の作成
        fig = Figure()
        self.ax = fig.add_subplot(1, 1, 1)
        # matplotlibの行場領域とウィジェットの関連付け
        self.fig_canvas = FigureCanvasTkAgg(fig, frame)
        # matplotlibのツールバーを作成
        self.toolbar = NavigationToolbar2Tk(self.fig_canvas, frame)
        # matploglibのグラフをフレームに配置
        self.fig_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

        # フレームをウィンドウに配置
        frame.pack()

        # ボタンの作成
        button = tk.Button(self.master, text='Draw Graph', command=self.run_gengraph)
        # 配置
        button.pack(side=tk.BOTTOM)

    def run_gengraph(self):
        with open('data.csv', 'r') as f:
            data = np.loadtxt(f, delimiter=',')

        # iteratorのインスタンス生成
        itr = Iterator(data)

        # グラフ描画用のlist
        x_ = []
        y_ = []

        for _ in range(len(itr)):
            # itrからデータを取得
            x, y = itr.__next__()
            # リストに追加
            x_.append(x)
            y_.append(y)
            # 描画
            self.ax.clear() # グラフのリセット
            self.ax.plot(x_, y_)
            self.fig_canvas.draw()
            # GUIの更新
            self.master.update()
            # 待機時間
            time.sleep(1)

最後に

Tkinterを使ってGUI上にグラフを描画し、更新されるデータをリアルタイムで描画する方法をまとめました。
これでLabVIE*に少しでも近づけたかな?
今後は本家のグラフデザインに少しでも近づけるのと、やはり計測器を繋げたときにどんなプログラムになるのかをまとめたいです。

Github

参考にした記事

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1