matplotlibよりも滑らかなリアルタイムプロットができる PyQtGraph で散布図をリアルタイムプロットしてみました。
PyQtGraphで散布図リアルタイムプロット、1000点でも60fps程度で動いてくれたので良き pic.twitter.com/R6ntYYMIQF
— Kurene (@_kurene) 2021年6月10日
1000点でアルファ値(透明度)を設定していますが、約60fpsでリアルタイムプロットできていることを確認しました。
こちらの散布図の実装方法とソースコードを紹介したいと思います。
PyQtGraphにおける散布図リアルタイムプロットの流れ
抽象的なコードで流れを説明します。
# -*- coding: utf-8 -*- from pyqtgraph.Qt import QtGui, QtCore import numpy as np import pyqtgraph as pg # (1) PyQtのウインドウ生成 app = QtGui.QApplication([]) # (2) GraphicsLayoutWidged オブジェクト生成 win = pg.GraphicsLayoutWidget() win.show() # (3) PlotItem オブジェクト生成 plot = win.addPlot(title="real-time scatter plot") # (4) PlotDataItemオブジェクト生成 curve = plot.plot(pen=...) # 初期データ(numpy.ndarrayオブジェクト)生成 n_data = 1000 x = ... y = ... # (5) 更新処理を記述した関数 def update(): global x, y, curve, plot # x, y を更新 x += ... y += ... # データ更新 curve.setData(x, y) # (6) 定期的に(5)の関数を実行 fps = 60 timer = QtCore.QTimer() timer.timeout.connect(update) timer.start(1/fps * 1000) # アプリケーション実行 if __name__ == '__main__': import sys if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): QtGui.QApplication.instance().exec_()
要点だけまとめると、
- PyQtのウインドウ生成
- PyQt5.QtWidgets.QApplication オブジェクト生成
- GraphicsLayoutWidgedオブジェクト生成
- PlotItem オブジェクト生成
- PlotDataItemオブジェクト生成
- 更新処理を記述した関数を実装
- 定期的に(5)の関数を実行
という流れになります。
1~4までで、
- QApplication
- GraphicsLayoutWidged
- PlotItem
- PlotDataItem
という4種類の異なるクラスのオブジェクトが生成されています。
これらの関係は、以下の図が参考になります。
なお、GraphicsViewクラスはPlotItemの親クラスでもあります。
すなわち、それぞれのクラスは以下のような役割を持っています。
- GraphicsLayoutWidged
- 複数のプロットのレイアウトを設定する
- PlotItem
- 標準の 2D プロット領域
- 軸、ラベル、インタラクティブなグラフ操作に関する処理を扱う
- PlotDataItem
- プロットするデータ、属性、プロット方法を扱う
散布図の属性
よく使う散布図の属性は、以下のように設定します。
ただし、
p = win.addPlot() # PlotItem オブジェクト curve = p.plot(pen=None) # PlotDataItem オブジェクト
とします。
- 散布図の線
- p.plot(pen=None) でシンボルのみのグラフ=散布図ができる
- 折れ線グラフの線に関する属性をセットできる 参考ページ
- シンボル
- matplotlib だと
marker
p.plot(symbol="o")
- matplotlib だと
- シンボルの外周の色,
p.plot(symbolPen='b')
- シンボルの内部の色
p.plot(symbolBrush='c')
- シンボルのサイズ
p.plot(symbolSize=10)
- 軸の範囲
- X軸:
p.setXRange(-5, 5)
- Y軸:
p.setYRange(-5, 5)
- XY軸のオートスケールOn/Off: p.enableAutoRange('xy', False)
- X軸:
- プロットするデータの更新
curve.setData(x, y)
アルファ値(透過度)curve.setAlpha(0.5, False)
ラベルなど軸関係は以下のWebページが参考になります
AxisItem — pyqtgraph 0.12.1 documentation
実装例・ソースコード
# -*- coding: utf-8 -*- from pyqtgraph.Qt import QtGui, QtCore import numpy as np import pyqtgraph as pg import time app = QtGui.QApplication([])# PyQtのアプリ生成 win = pg.GraphicsLayoutWidget() # GraphicsWindow() オブジェクト生成 win.resize(500, 500) win.show() p = win.addPlot(title="real-time scatter plot") # PlotItem オブジェクト生成 curve = p.plot(pen=None, symbol="o", symbolPen='b', symbolSize=10, symbolBrush='c') # PlotDataItemオブジェクト生成。pen=Noneで点のみ pg.setConfigOptions(antialias=True) n_data = 1000 x = np.random.normal(0.0, 1.0, n_data) y = np.random.normal(0.0, 1.0, n_data) p.setXRange(-5, 5) p.setYRange(-5, 5) iter = 0 pretime = time.time() def update(): global iter, p, x, y, curve global pretime # x, y座標を変更(アニメーション) x += np.random.normal(0.0, 0.1, n_data) + 0.01*np.cos(2*np.pi*iter/300) y += np.random.normal(0.0, 0.1, n_data) + 0.01*np.sin(2*np.pi*iter/120) if iter > 0: p.enableAutoRange('xy', False) # x, y軸のスケール固定 curve.setData(x, y) curve.setAlpha(0.5, False) curtime = time.time() fps = 1.0 / (curtime - pretime + 1e-16) p.setTitle(f"fps: {fps:0.1f} Hz") pretime = curtime iter += 1 fps = 60 timer = QtCore.QTimer() timer.timeout.connect(update) timer.start(1/fps * 1000) if __name__ == '__main__': import sys if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'): QtGui.QApplication.instance().exec_()
参考
Pythonにおける リアルタイムプロットは matplotlib ではなく PyQtGraph がオススメである理由は、以下の記事をご覧ください。