こんなこと既に誰かがやっていてqiitaにイイ感じにまとめているだろう、と思っていましたが、3Dグラフや、リアルタイムに更新する方法に関しては情報が少なかったのでここにまとめておきます。
目的:
- pythonで3D, 2Dのリアルタイムグラフを作る
- グラフの見た目を整える
メモ:
- グラフ化には matplotlib を使用
- プロットするデータは乱数で作成する
- このデータ配列を各種測定データに置き換えれば、様々なセンサーから得られたデータをリアルタイムに可視化できるはずです
完成イメージ:
調整したグラフ
このような3Dグラフをリアルタイムに更新していく方法を紹介します。
2Dリアルタイムグラフ
まずはリアルタイムグラフを表示するために必要な最低限のコードを書いてみましょう
はじめにライブラリを読み込みます。
import numpy as np # プロットするデータ配列を作成するため
import matplotlib.pyplot as plt # グラフ作成のため
次に各種パラメーターの定義をします。
このようにパラメーターの設定をコードの先頭の方でやると、少しだけプロットするframeを増やしたい、といった微調整が簡単に行えるのでオススメです。
dataLength = 100 # 1つのデータの配列の点数
frame = 50 # プロットするフレーム数
sleepTime = 0.0001 # 1フレーム表示する時間[s]
グラフを表示させます。
for i in range(frame): # フレーム回数分グラフを更新
data = np.random.rand(dataLength) # プロットするデータを作成
plt.plot(data) # データをプロット
plt.draw() # グラフを画面に表示開始
plt.pause(sleepTime) # SleepTime時間だけ表示を継続
plt.cla() # プロットした点を消してグラフを初期化
イメージとしては、
- データを取得 (今回は1次元乱数を使用)
- データをプロット (plt.plot())
- グラフを一定時間表示 (plt.draw() & plt.pause())
- グラフを初期化 (plt.cla())
という過程をfor文で繰り返すことで、動画のようにグラフを更新していきます。
表示するデータは先ほど定義したdataLengthの長さをもつ1次元配列なので、これを各種センサーで測定したデータに置き換えればリアルタイムに様々なデータを表示できます。
このグラフには軸やタイトルがありませんが、動画のように更新されていくことが確認できるはずです。
グラフの見た目を整える作業は後で紹介します。
2次元リアルタイムグラフのコードまとめ
# 2次元リアルタイムグラフの雛形
# Library
import numpy as np # プロットするデータ配列を作成するため
import matplotlib.pyplot as plt # グラフ作成のため
# params
dataLength = 100 # 1つのデータの配列の点数
frame = 50 # プロットするフレーム数
sleepTime = 0.0001 # 1フレーム表示する時間[s]
# plotting
for i in range(frame): # フレーム回数分グラフを更新
data = np.random.rand(dataLength) # プロットするデータを作成
plt.plot(data) # データをプロット
plt.draw() # グラフを画面に表示開始
plt.pause(sleepTime) # SleepTime時間だけ表示を継続
plt.cla() # プロットした点を消してグラフを初期化
3Dリアルタイムグラフ
2次元グラフの例の時と同じように、まずはグラフを動的に表示させる必要最低限のコードを書いてみましょう。
3次元グラフでは使用するライブラリが一つ増えます。
import numpy as np # プロットするデータ配列を作成するため
import matplotlib.pyplot as plt # グラフ表示のため
from mpl_toolkits.mplot3d import Axes3D # 3Dグラフ作成のため
パラメータの定義を行います。
frame = 100 # プロットするフレーム数
sleepTime = 0.001 # 1フレーム表示する時間[s]
dataLength = 10 # プロットするデータ配列の点数
プロットするデータを載せるfigureオブジェクトを作ります。
プロットするデータは逐一変化しますが、同じfigureオブジェクトにプロットするので、for文毎にfigureオブジェクトを作成する必要はありません。
fig = plt.figure() # figureオブジェクトを作る
ax = Axes3D(fig)
グラフを表示します。
for i in range(frame):
# getting data
x = np.arange(0, 10, 1) # 0 ~ 10 の1次元配列
y = np.arange(0, 10, 1) # 0 ~ 10 の1次元配列
z = np.random.rand(10) # 0 ~ 10 の1次元乱数配列
# plotting
ax.plot(x, y, z)
plt.draw()
plt.pause(sleepTime)
plt.cla()
3次元グラフでも基本的にやることは同じで、
- データを取得 (今回は1次元乱数配列を使用)
- データをプロット (ax.plot())
- グラフを一定時間表示 (plt.draw() & plt.pause())
- グラフを初期化 (plt.cla())
をframe回数分繰り返し行うことで、グラフを更新していきます。
3次元グラフの場合は、散布図、折れ線、等高線など、様々なプロットするオプションがありますが、プロットする種類によって指定するx, y, z座標の形式が微妙に違ったりするので、注意が必要です。これについては後にもう少し詳しく記述します。
今回はプロットする各点のx, yは固定(np.arange(0, 10, 1)) して、zを乱数にすることで、グラフが動的に変化していく様子を確認します。
3Dリアルタイムグラフのコードまとめ
# 3次元リアルタイムグラフの雛形
# Library
import numpy as np # プロットするデータ配列を作成するため
import matplotlib.pyplot as plt # グラフ表示のため
from mpl_toolkits.mplot3d import Axes3D # 3Dグラフ作成のため
# params
frame = 100 # プロットするフレーム数
sleepTime = 0.001 # 1フレーム表示する時間[s]
dataLength = 10 # プロットするデータ配列の点数
# making 3d figure object
fig = plt.figure() # figureオブジェクトを作る
ax = Axes3D(fig)
for i in range(frame):
# getting data
x = np.arange(0, 10, 1) # 0 ~ 10 の1次元配列
y = np.arange(0, 10, 1) # 0 ~ 10 の1次元配列
z = np.random.rand(10) # 0 ~ 10 の1次元乱数配列
# plotting
ax.plot(x, y, z)
plt.draw()
plt.pause(sleepTime)
plt.cla()
各軸の表示領域を指定していないので見栄えは悪いですが、2Dの例と同様にグラフが動的に変化していく様子が確認できるはずです。
また、3次元グラフでは更新されているグラフをクリック&ドラッグすることで回転させることができます。
グラフの見た目を整える
各軸の表示最大、最小値固定
例えばz軸を固定したい場合はプロットを更新するfor文内に
zMax = 1 # z軸最大値
zMin = 0 # z軸最小値
ax.set_zlim(zMin, zMax)
を追加します。
各軸のタイトル表示
xlabel = "x axis"
ylabel = "y axis"
zlabel = "z axis"
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
ax.set_zlabel(zlabel)
をプロットを更新するfor文内に追加します。
for文の外に記述した場合、plt.cla()でグラフを初期化するときにラベルも初期化されてしまうので注意が必要です。
軸の最大、最小値固定、ラベル表示を追加したコードまとめ
# Library
import numpy as np # プロットするデータ配列を作成するため
import matplotlib.pyplot as plt # グラフ表示のため
from mpl_toolkits.mplot3d import Axes3D # 3Dグラフ作成のため
# params
frame = 100 # プロットするフレーム数
sleepTime = 0.001 # 1フレーム表示する時間[s]
dataLength = 10 # プロットするデータ配列の点数
zMax = 1 # z軸最大値
zMin = 0 # z軸最小値
xlabel = "x axis" # x軸ラベル
ylabel = "y axis"
zlabel = "z axis"
# making 3d figure object
fig = plt.figure() # figureオブジェクトを作る
ax = Axes3D(fig)
for i in range(frame):
# getting data
x = np.arange(0, 10, 1) # 0 ~ 10 の1次元配列
y = np.arange(0, 10, 1) # 0 ~ 10 の1次元配列
z = np.random.rand(10) # 0 ~ 10 の1次元配列
# plot setting
ax.set_zlim(zMin, zMax) # z軸固定
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
ax.set_zlabel(zlabel)
# plotting
ax.plot(x, y, z)
plt.draw()
plt.pause(sleepTime)
plt.cla()
このようなグラフの見た目の調整は基本的に静止したグラフ表示の場合と変わらないので、”matplotlib 3d 軸ラベル” のように検索して、サンプルコードを参考にすれば大体は機能します。
plt.cla()で初期化されなければプロットを更新するfor文内に記述する必要はありませんが、どの機能が該当するかは実際に試して判断した方が早いと思います。
表示するデータを実戦に近づける
私が3Dリアルタイムグラフを作成した主な理由は、複数の1次元配列データを同時に確認したかったからです。これができれば、各種測定データを置き換えることで、例えば複数の会社の株価を同時にリアルタイムに表示するといったことが可能です。というわけで、複数の1次元配列データを3Dグラフに表示してみましょう。
今回は ax.plot_wireframe() という方法でプロットしてみます。今までとコードはそんなに変わりません。基本的には ax.plot() の部分だけです。
まずは各種パラメーターを定義します。#paramsの下に追記してください。
dataLength = 10 # 1次元配列データの長さ
dataAmount = 20 # 1度にプロットする1次元配列データの個数
次にプロットするデータを用意します。これはfor文の中に書きます。厳密に言えば乱数であるz以外はfor文で繰り返し定義する必要はありませんが、今は分かりやすさ優先で同じ場所に書いてしまいましょう。
x = np.arange(0, dataLength, 1) # 0 ~ 10 の1次元配列
y = np.arange(0, dataAmount, 1) # 0 ~ 10 の1次元配列
gx, gy = np.meshgrid(x, y) # grid
z = np.random.rand(y.shape[0], x.shape[0]) # 2次元乱数配列
最後にwireframe形式でプロットします。今まで使っていたax.plot()をコメントアウトして、ax.plot_wireframe()を追加します。
# ax.plot(x, y, z) # 直線プロット
ax.plot_wireframe(gx, gy, z) # wireframeプロット
複数の1次元配列を表示する3Dグラフのコードまとめです。
# 複数の1次元配列を表示する3次元リアルタイムグラフ
# Library
import numpy as np # プロットするデータ配列を作成するため
import matplotlib.pyplot as plt # グラフ表示のため
from mpl_toolkits.mplot3d import Axes3D # 3Dグラフ作成のため
# params
frame = 30 # プロットするフレーム数
sleepTime = 0.5 # 1フレーム表示する時間[s]
dataLength = 10 # 1次元配列データの長さ
dataAmount = 20 # 1度にプロットする1次元配列データの個数
zMax = 1 # z軸最大値
zMin = 0 # z軸最小値
xlabel = "x axis"
ylabel = "y axis"
zlabel = "z axis"
# making 3d figure object
fig = plt.figure() # figureオブジェクトを作る
ax = Axes3D(fig)
for i in range(frame):
# getting data
x = np.arange(0, dataLength, 1) # 0 ~ 10 の1次元配列
y = np.arange(0, dataAmount, 1) # 0 ~ 10 の1次元配列
gx, gy = np.meshgrid(x, y) # grid
z = np.random.rand(y.shape[0], x.shape[0]) # 2次元乱数配列
# data shape checking
print('x.shape', x.shape)
print('y.shape', y.shape)
print('gx.shape', gx.shape)
print('gy.shape', gy.shape)
print('z.shape', z.shape)
# plot setting
ax.set_zlim(zMin, zMax) # z軸固定
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
ax.set_zlabel(zlabel)
# plotting
# ax.plot(x, y, z) # 直線プロット
ax.plot_wireframe(gx, gy, z)
plt.draw()
plt.pause(sleepTime)
plt.cla()
このままでもプロットすることはできますが、wireが張り巡らされていて、少し見にくいかもしれません。
matplotlib wireframe 公式ドキュメントを参考に、wireの表示数を減らしてみましょう。
ax.plot_wireframe(gx, gy, z, rstride=1, cstride=0) # y軸方向のwireの繋がりを削除
今回はwireframeを使用しましたが、他にも散布図を示すax.scatter()など色々なプロット方法があります。今まで示したコードのax.plot()を置き換えれば、様々な形式でプロットされたグラフでもリアルタイムに更新されていくはずです。自分のデータをどのように可視化したいか、イメージがはっきりしている場合は、"matplotlib 3d グラフ"、でGoogle画像検索を行うと、比較的簡単に欲しいサンプルコードに辿り着けます。
グラフをカッコよくする
ここからはグラフの見た目をよりスタイリッシュにするコードを紹介します。ほとんどが自己満足の世界なので、興味のない方は読まなくても大丈夫です。
まずは使う色を決めましょう。自分でセンスの良い色を選べなくても、"色 組み合わせ"、"graph color set"、などと検索すればいい感じな色を紹介しているサイトがたくさん見つかるはずです。
colorsets = 'skyblue'
# making stylish graph
if colorsets == 'skyblue':
color1 = '#FFFFFF' # background
color2 = '#C7D7FF' # background2
color3 = '#5BB1E3' # accent color
color4 = '#4C4C4C' # axis
elif colorsets == 'mint':
color1 = '#222831' # background
color2 = '#393e46' # background2
color3 = '#6779FE' # accent color
color4 = '#eeeeee' # axis
このようにいい感じな組み合わせの色をcolorsetsとしてまとめておけば、colorsetsを変更するだけで簡単にグラフのテーマカラーを変更できます。
次に plt.rcParams を設定します。rcParamsの設定は最初の1度だけで済み、for文で繰り返し行う必要がないのでリアルタイムグラフの見た目を調整する際には優先して変更する部分です。
xfigSize = 14 # グラフを表示するウインドウのx方向の大きさ
yfigSize = 10 # グラフを表示するウインドウのy方向の大きさ
plt.rcParams.update({
'figure.figsize': [xfigSize, yfigSize],
# 'axes.grid': False,
'axes3d.grid': True,
'grid.alpha': 0.2,
'grid.linestyle': '-',
'axes.grid.which': 'major',
'axes.grid.axis': 'y',
'grid.linewidth': 0.1,
'font.size': 10,
'grid.color': color4,
'xtick.color': color4, # x軸の色
'ytick.color': color4, # y軸の色
'figure.facecolor': color1, # 枠の外の背景色
'figure.edgecolor': color1,
'axes.edgecolor': color1, # 枠色
'axes.facecolor': 'none', # 背景色 noneにするとfigure.facecolorと同じ色になる
'axes.labelcolor': color4, # 軸ラベル色
'figure.dpi': 75.0,
'figure.frameon': False,
})
ここは
公式ドキュメント
を参考に色々試してみて、自分の気に入る設定を探してみて下さい。
背景を透明にします。これもfor文の外で定義できます。
ax.w_xaxis.set_pane_color((0., 0., 0., 0.)) # 3dグラフの背景を透明にする 最初の一回だけでOK
ax.w_yaxis.set_pane_color((0., 0., 0., 0.)) # 3dグラフの背景を透明にする
ax.w_zaxis.set_pane_color((0., 0., 0., 0.)) # 3dグラフの背景を透明にする
最後にプロットする配列の色や透明度を指定します。
Alpha = 1.0 # プロットした線の透明度 0:透明、1:不透明
ax.plot_wireframe(gx, gy, z, rstride=1, cstride=0, color=color3, alpha=Alpha)
見た目を調整したコードまとめ
# 複数の1次元配列を表示する3次元リアルタイムグラフ
# Library
import numpy as np # プロットするデータ配列を作成するため
import matplotlib.pyplot as plt # グラフ表示のため
from mpl_toolkits.mplot3d import Axes3D # 3Dグラフ作成のため
# params
frame = 15 # プロットするフレーム数
sleepTime = 0.5 # 1フレーム表示する時間[s]
dataLength = 10 # 1次元配列データの長さ
dataAmount = 3 # 1度にプロットする1次元配列データの個数
zMax = 1 # z軸最大値
zMin = 0 # z軸最小値
xlabel = "x axis"
ylabel = "y axis"
zlabel = "z axis"
colorsets = 'skyblue'
xfigSize = 14 # グラフを表示するウインドウのx方向の大きさ
yfigSize = 10 # グラフを表示するウインドウのy方向の大きさ
Alpha = 1.0 # プロットした線の透明度
# making stylish graph
if colorsets == 'skyblue':
color1 = '#FFFFFF' # background
color2 = '#C7D7FF' # background2
color3 = '#5BB1E3' # accent color red: DE5E34 blue: 5BB1E3
color4 = '#4C4C4C' # axis
plt.rcParams.update({
'figure.figsize': [xfigSize, yfigSize],
# 'axes.grid': False,
'axes3d.grid': True,
'grid.alpha': 0.2,
'grid.linestyle': '-',
'axes.grid.which': 'major',
'axes.grid.axis': 'y',
'grid.linewidth': 0.1,
'font.size': 10,
'grid.color': color4,
'xtick.color': color4, # x軸の色
'ytick.color': color4, # y軸の色
'figure.facecolor': color1, # 枠の外の背景色
'figure.edgecolor': color1,
'axes.edgecolor': color1, # 枠色
'axes.facecolor': 'none', # 背景色 noneにするとfigure.facecolorと同じ色になった
'axes.labelcolor': color4, # 軸ラベル色
'figure.dpi': 75.0,
'figure.frameon': False,
})
# making 3d figure object
fig = plt.figure() # figureオブジェクトを作る
ax = Axes3D(fig)
ax.w_xaxis.set_pane_color((0., 0., 0., 0.)) # 3dグラフの背景を透明にする 最初の一回だけでOK
ax.w_yaxis.set_pane_color((0., 0., 0., 0.)) # 3dグラフの背景を透明にする
ax.w_zaxis.set_pane_color((0., 0., 0., 0.)) # 3dグラフの背景を透明にする
for i in range(frame):
# getting data
x = np.arange(0, dataLength, 1) # 0 ~ 10 の1次元配列
y = np.arange(0, dataAmount, 1) # 0 ~ 10 の1次元配列
gx, gy = np.meshgrid(x, y) # grid
z = np.random.rand(y.shape[0], x.shape[0]) # 0 ~ 10 の1次元配列
# data shape checking
print('x.shape', x.shape)
print('y.shape', y.shape)
print('gx.shape', gx.shape)
print('gy.shape', gy.shape)
print('z.shape', z.shape)
# plot setting
ax.set_zlim(zMin, zMax) # z軸固定
ax.set_xlabel(xlabel)
ax.set_ylabel(ylabel)
ax.set_zlabel(zlabel)
# plotting
# ax.plot(x, y, z) # 直線プロット
ax.plot_wireframe(gx, gy, z, rstride=1, cstride=0, color=color3, alpha=Alpha)
plt.draw()
plt.pause(sleepTime)
plt.cla()
以上が3次元リアルタイムグラフについての解説です。グラフを満足いく見た目に調整するのは案外手間がかかりますが、リアルタイムプロットするだけなら20行程度のコードで書けます。必要に応じてコードを調整して使って下さい。
今後はより高速にプロットする手法を探していきたいと思います。
というのも、リアルタイムに各センサーで取得したデータを可視化する際、グラフ表示等のデータ処理はなるべく高速で行いすぐに次のデータを取得する必要があるからです。
もしmatplotlibよりも高速に3Dグラフをリアルタイムで表示できる方法を知っていれば教えて頂けると助かります。