Pythonでグラフをリアルタイムで描画させる方法
前回作成したラズパイ用温度センサーで取得した温度を、リアルタイムでグラフに描画させてみたいと思います。
グラフ描画ライブラリ
グラフを描画する有名な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()でグラフを表示させます。
プログラム開始からの経過時間を取得
リアルタイムでグラフを描画させるために、プログラムを開始してからの時間を取得し、それを横軸(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 : 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個が格納されるようにします。
この部分のコードがなければ、プログラムの開始から終了までの全時間が表示させることができます。
用途によって使い分けてください。
プログラム実行結果
プログラムを実行すると、グラフの動きはこのようになります。
ついでに温度をリアルタイムでグラフに描画させられるようにしました! pic.twitter.com/G7nCvSV6Wv
— りょうのすけ (@ryo_analog) 2022年7月20日