e-DIY
ノウハウ

Pythonでグラフをリアルタイムで描画させる方法

matplotlibでリアルタイムでグラフを描画

前回作成したラズパイ用温度センサーで取得した温度を、リアルタイムでグラフに描画させてみたいと思います。

グラフ描画ライブラリ

グラフを描画する有名なPythonライブラリがmatplotlibです。

まずはmatplotlibをインストールしましょう。
ターミナルで次のコードを実行してみてください。

pip3 install matplotlib

これでインストールは完了です。

matplotlibの基本的な使い方

簡単な折れ線グラフを描いてみましょう。

from matplotlib import pyplot as plt      # matplotlibをインポート

X = [0,1,2,3,4,5,6,7,8,9,10]
Y = [0,1,1,3,4,4,6,7,8,6,10]

plt.figure(figsize=(12,8))          # グラフのサイズを指定
plt.plot(X, Y)                      # X,Yをプロット
plt.xlim(0, 10)                     # X軸の表示範囲
plt.ylim(0, 10)                     # Y軸の表示範囲
plt.xlabel('X-axis')                # X軸ラベル
plt.ylabel('Y-axis')                # Y軸ラベル
plt.rcParams["font.size"] = 20      # フォントサイズ
plt.grid()                          # グリッドを表示
plt.show()                          # グラフを表示

1行目でmatplotlibをインポートしています。
pyplotというのはmatplotlibライブラリのモジュールのひとつで、グラフを描画するための関数です。

pyplotはpltという名前でインポートするのが一般的です。

plt.rcParamsでフォントサイズの変更、plt.grid()でグリッド(補助線)を表示させることができます。
基本的な設定をしたら、plt.show()でグラフを表示させます。

matplotlibでグラフを描画

プログラム開始からの経過時間を取得

リアルタイムでグラフを描画させるために、プログラムを開始してからの時間を取得し、それを横軸(X軸)に表示させたいと思います。

パフォーマンスカウンター

time.perf_counter()関数を使ってプログラムの経過時間を取得します。

perf_counter()関数はスリープ時間も考慮に入れたシステム全体の時間を返します。
似た関数でtime.time()がありますが、こちらは精度が悪いのでperf_counter()を使います。

開始時の時刻:t0、プログラム終了時の時刻:t1を取得し差分を取ることで経過時間を計算することができます。

import time

t0 = time.perf_counter()   #プログラム開始時刻を取得

while True:
   time.sleep(1)
   t1 = time.perf_counter()               #終了時刻を取得
   print('Elapsed time : %.2f'%(t1-t0))   #開始時刻-終了時刻(経過時間)を出力

出力結果はこのようになります。

Elapsed time : 1.00
Elapsed time : 2.00
Elapsed time : 3.00
Elapsed time : 4.01
Elapsed time : 5.01
Elapsed time : 6.01
Elapsed time : 7.01
Elapsed time : 8.01
Elapsed time : 9.01
Elapsed time : 10.01

グラフをリアルタイムで描画させる

以上を踏まえて、温度センサで取得した温度をリアルタイムで表示させてみましょう。

プログラムはこのようにしました。

import RPi.GPIO as GPIO
import time
import math                          # Mathモジュール
import smbus                         # SMBus用モジュール
from matplotlib import pyplot as plt # グラフ描画ライブラリ

class ADCDevice: 
    def __init__(self):           # コンストラクタ。初期化用の関数。
        self.cmd = 0
        self.address = 0
        self.bus=smbus.SMBus(1)   # SMBusの引数に1を指定する。ラズパイのI2Cバスの番号

class ADS7830(ADCDevice):                   # ADCDeviceクラスを継承
    def __init__(self):
        super(ADS7830, self).__init__()
        self.address = 0x48                 # ADS7830の初期アドレス(A1,A0端子を共にGND接続)  
        self.cmd = 0x84                     # コマンドバイト
        
    def analogRead(self):
        value = self.bus.read_byte_data(self.address, self.cmd)
        return value

    def close(self):
        self.bus.close()

adc = ADS7830()
plt.figure(figsize=(12,8))        # グラフのサイズを指定
plt.rcParams["font.size"] = 20    # フォントサイズ
plt.grid()                        # グリッドを表示
X, Y = [], []                     # XとYの空のリスト
start_time = time.perf_counter()  # プログラム開始時の現在の時刻

def setup():
    global adc
    
def loop():
    while True:
        value = adc.analogRead()                              # 指定したピンのAD値を読む
        voltage = value / 255.0 * 3.3                         # AD値を電圧に変換
        Rt = 10 * voltage / (3.3 - voltage)                   # サーミスタの抵抗値を計算
        tempK = 1/(1/(273.15 + 25) + math.log(Rt/10)/3950.0)  # ケルビン温度を計算
        tempC = tempK -273.15                                 # セルシウス温度に変換
        print('Time : %.2f, Temperature : %.2f'%(time.perf_counter() - start_time,tempC))
        
        if len(X) > 10:            # Xに10個以上格納されたら
                del X[0]           # Xの0番目(リストの左端)を削除
                X_max = X[0] + 14  # X軸の最大値設定
                del Y[0]           # Yの0番目(リストの左端)を削除
        else:
                X_max = 14         # Xのリストの要素が10個以下ならX軸の最大値は14とする

        plt.cla()                                    # 画面をクリア
        Y.append(float(tempC))                       # リストの末尾にアイテムを追加
        X.append(time.perf_counter() - start_time)   # プログラム開始からの経過時間をリストの末尾に追加
        plt.plot(X, Y)                               # X,Yをプロット
        plt.ylim(20, 30)                             # Y軸の表示範囲
        plt.xlim(X[0], X_max)                        # X軸の表示範囲
        plt.xlabel('Time [s]')                       # X軸ラベル
        plt.ylabel('Temperature [deg]')              # Y軸ラベル
        plt.pause(1)                                 # 更新時間間隔1秒で描画する

def destroy():
    adc.close()
    GPIO.cleanup()
    
if __name__ == '__main__':
    print ('Program is starting ... ')
    setup()
    try:
        loop()
    except KeyboardInterrupt:
        destroy()

温度取得の部分に関しては、前回のRaspberry Pi用温度センサーをサーミスタとADCを使って自作で詳細を確認してください。

X軸の表示幅を固定

今回は、X軸の表示領域の幅が14秒に固定されるようにしました。

if len(X) > 10:        # Xに10個以上格納されたら
    del X[0]           # Xの0番目(リストの左端)を削除
    X_max = X[0] + 14  # X軸の最大値設定
    del Y[0]           # Yの0番目(リストの左端)を削除
 else:
    X_max = 14         # Xのリストの要素が10個以下ならX軸の最大値は14とする

Xのリストに10個データが格納されるまではX軸の最大値(X_max)が14で固定されます。

10個を超えると、del文を使ってリストの0番目の要素を削除します。
つまり、最新の10個が常にリストに格納されることになります。
X軸の最大値(X_max)は、(リストの0番目の要素)+14としているので、X軸の幅は常に14秒となります。

Yのリストも同様に最新の10個が格納されるようにします。

この部分のコードがなければ、プログラムの開始から終了までの全時間が表示させることができます。
用途によって使い分けてください。

プログラム実行結果

プログラムを実行すると、グラフの動きはこのようになります。

関連キーワード

フリーワード検索