こんな人にオススメ
plotly
のMesh3d
って何?どうやら3Dのグラフが描けるのらしいけど、どうやって描く?
ということで、今回はplotly
のmesh3d
を解説する。できることは3次元のグラフの作成なんだけど若干クセが強いので解読が面倒。
面倒なんだけどあまり使われていないからか情報がかなり少ない。なので今回の記事を軸に色々調べていただければ幸いだ。
python環境は以下。
- Python 3.10.0
- plotly 5.4.0
- plotly-orca 3.4.2
作成したコード全文
下準備
import plotly.graph_objects as go import plotly.io as pio
まずは下準備としてのimport
関連。今回はplotly
のgo
を使用するのでgo
をimport
。あとはグラフ保存のためのpio
。
-
-
【plotly&orca】plotlyで静止画保存(orca)
続きを見る
その他は特に使用しなくても大丈夫なのでimport
しない。
グラフを作成するための関数たち
お次はグラフを作成するための関数の定義。予め関数を定義しておくことでメインの処理を簡略化することができる。また、何か問題があった時に問題の切り分けも簡単になる。
-
-
【plotly&工夫】楽にグラフを描くためのplotlyの関数化
続きを見る
グラフ保存用の関数
# グラフ保存用の関数 def save(fig, config, save_name): pio.orca.config.executable = '/Applications/orca.app/Contents/MacOS/orca' pio.write_html(fig, f"{save_name}.html", config=config,) pio.write_image(fig, f"{save_name}.png")
まずはグラフ保存用の関数。保存形式はhtmlとpng。config
はグラフの右上に存在するツールバーのようなもの。
config
を設定しておくとこのツールバーの内容を追加したり削除したりできる。htmlで使用することで、保存後のhtmlファイルでもツールバーの設定が引き継がれる。
-
-
【plotly&config】グラフのツールバーを編集する
続きを見る
今回は基礎的な内容なのでconfig
は使用しないことにするのでNone
で指定。
mesh3dプロット用の関数
# mesh3dプロット用の関数 def mesh3d(x, y, z, save_name, show=True, **kwargs): data = go.Mesh3d(x=x, y=y, z=z, **kwargs) fig = go.Figure(data=[data]) if show: fig.show() prefix = 'plotly-mesh3d' save(fig=fig, config=None, save_name=f"{prefix}_{save_name}") else: return data
次はmesh3d
用のプロットの定義。プロットはgo.Mesh3d
でx
, y
, z
の座標を指定する。あとは**kwargs
で追加の設定ができるようにした。
kwargs
は可変長キーワード引数で辞書のようにkey
とvalue
を指定するイメージ。これで関数に引数を追加できる。
また、後ほどdata
を出力したい時があるので、引数show
でグラフの表示と保存をするか出力するかを選べるようにした。
データ配列
# 半分のピラミッドになるような座標 x = (0, 1, 0, 0.5) y = (0, 0, 1, 0.5) z = (0, 0, 0, 1)
mesh3d
はx
, y
, z
それぞれで頂点が必要。上の例だと0番目が(0, 0, 0)
の座標、1番目が(1, 0, 0)
の座標。座標点を結ぶ事で立体図形ができる。
上の座標でできるのは半分に分割したピラミッドのような感じ。以下の記事でピラミッドを作成したが、以下の記事だと2次元配列で1点ずつプロットした。
-
-
【python&3Dピラミッド】plotlyで3次元ピラミッドを作成
続きを見る
シンプルにピラミッドを作成
まずはシンプルにx
, y
, z
を指定しただけ。頂点4つから自動で面が出来上がるので、面の塗りつぶしをする必要はない。
んだけど、一部は面ができないまま。なので完全にピラミッドにしたかったら面を塞ぐように設定しないといけない。
# 単にピラミッドを作成 # シンプルに作成すると穴が開く mesh3d( x=x, y=y, z=z, save_name='simple_halfpyramid', )
図形に色付け
続いては図形に色付けを行う。さっきのシンプルな図形だと面の境界線がわかりにくい。
facecolor
で各面の色を設定
まずは引数facecolor
で面ごとに色付け。今回は赤と青を選択。面は設定したx
, y
, z
の頂点的に左が赤になりそうだけど右が赤になっている。謎。
頂点が中央下→左→右→中央上の順番で指定しているから、初めに面ができるのが右。だから1つ目の面が右なのだろう。
また、ホバーの位置が頂点ではなく各面の中心付近に設定されているので注意。
# 各面の色 mesh3d( x=x, y=y, z=z, save_name='facecolor_halfpyramid', facecolor=('green', 'blue'), )
vertexcolor
で頂点の色を設定
続いてはvertexcolor
で頂点の色を設定する。指定した色が頂点の色となり、その色と他の頂点の色が連続的になるように自動で補完してくれる。
今回は赤、青、緑を指定。頂点は全部で4つなので1色足りないが、そこはグレーっぽい色で自動で補われる。
# 各頂点の色 mesh3d( x=x, y=y, z=z, save_name='vertexcolor_halfpyramid', vertexcolor=('red', 'blue', 'green'), )
color
で全面の色を設定
引数color
を指定することで全ての面の色を設定することができる。今回はviolet
を指定。
# 全面の色 mesh3d( x=x, y=y, z=z, save_name='color_halfpyramid', color='violet', )
colorscale
でカラースケールを設定
引数colorscale
を指定することでカラースケールを使って自動で色付けすることも可能。今回は青から赤に色が変わっていくjet
を指定。基準はz
の値とした。
# カラースケールを使用 mesh3d( x=x, y=y, z=z, save_name='colorscale_halfpyramid', colorscale='jet', # カラースケール intensity=z, # どの基準値で色を決めるか )
intensitymode
で面ごとの色付けに変更
最後は引数intensity
とintensitymode
で面ごとの色付けをカラースケールで行う。と言っても今回は面が2つしかないのでインパクトはないが。
引数intensity
でカラーバーの値を指定し、各面のインデックス(今回だと0番目の面、1番目の面)が対応するカラーバーの色で色付けされる。
今回だと0番目に当たる右の面がカラーバーの0に該当、1番目の面に当たる左の面がカラーバーの1に該当する。
# intensityを使用 mesh3d( x=x, y=y, z=z, save_name='cell_halfpyramid', intensity=[0, 1, 2], # プロットされる面の枚数がカラーバーに対応 intensitymode='cell', # 各面ごとに色付け )
ijk
のベクトル指定で面を作成
mesh3d
には引数i
, j
, k
が存在する。例えば引数i
は公式では以下のような説明がなされている。
Code: fig.update_traces(i=<VALUE>, selector=dict(type='mesh3d')) Type: list, numpy array, or Pandas series of numbers, strings, or datetimes. A vector of vertex indices, i.e. integer values between 0 and the length of the vertex vectors, representing the "first" vertex of a triangle. For example,
{i[m], j[m], k[m]}
together represent face m (triangle m) in the mesh, wherei[m] = n
points to the triplet{x[n], y[n], z[n]}
in the vertex arrays. Therefore, each element ini
represents a point in space, which is the first vertex of a triangle.
よくわからん。ということで、ここでわかりやすく解説する。
i
, j
, k
は何番目に指定した頂点か
まず重要なことがi
, j
, k
はそれぞれi
番目、j
番目、k
番目の頂点を示すということ。例えば以下のように指定した場合は0番目、1番目、2番目に定義した頂点を使って面を作成する。
i=[0] j=[1] k=[2]
今回の場合だと0番目、1番目、2番目に定義した頂点は(x, y, z)=(0, 0, 0), (1, 0, 0), (0, 1, 0)
。この頂点を結んでできる面を作成してくれる。
面を1つずつ追加する
ということで、面を1つずつ追加して動きを確認する。初めは(x, y, z)=(0, 0, 0), (1, 0, 0), (0, 1, 0)
で面を作成。
その次は1つ目加えて(x, y, z)=(0, 0, 0), (0, 1, 0), (0.5, 0.5, 1)
でできる面を作成した。これを各面ごとに行うことで、頂点のプロットのみで半分のピラミッドを作成できる。
# 面をベクトルで作成 mesh3d( x=x, y=y, z=z, save_name='ijk_halfpyramid1', intensity=z, i=[0], # 0番目に指定した頂点(0, 0, 0)と j=[1], # 1番目に指定した頂点(1, 0, 0)と k=[2], # 2番目に指定した頂点(0, 1, 0)でできる面を塗りつぶし ) mesh3d( x=x, y=y, z=z, save_name='ijk_halfpyramid2', intensity=z, i=[0, 0], # 0番目に指定した頂点(0, 0, 0)と j=[1, 2], # 2番目に指定した頂点(0, 1, 0)と k=[2, 3], # 3番目に指定した頂点(0.5, 0.5, 1)でできる面を塗りつぶしを追加 ) mesh3d( x=x, y=y, z=z, save_name='ijk_halfpyramid3', intensity=z, i=[0, 0, 0], # 0番目に指定した頂点(0, 0, 0)と j=[1, 2, 1], # 1番目に指定した頂点(1, 0, 0)と k=[2, 3, 3], # 3番目に指定した頂点(0.5, 0.5, 1)でできる面を塗りつぶしを追加 ) mesh3d( x=x, y=y, z=z, save_name='ijk_halfpyramid4', intensity=z, i=[0, 0, 0, 1], # 1番目に指定した頂点(1, 0, 0)と j=[1, 2, 1, 2], # 2番目に指定した頂点(0, 1, 0)と k=[2, 3, 3, 3], # 3番目に指定した頂点(0.5, 0.5, 1)でできる面を塗りつぶしを追加 )
スライダーで動きを可視化
画像ではどんな感じかがわかったので、実際に動かせるようにした。各面ごとにスライダーにて動かせるようにグラフ化。スライダーに関しては以下。
-
-
【plotly&スライダー】plotlyのslidersにスライダーを追加
続きを見る
作成方法はシンプルで、一旦全てのプロットを作成後、各スライダーでプロットを表示するかどうかを指定。こうすることでスライダーごとにグラフが切り替わるように見える。
def mesh3d_ijk(): # 各プロットの作成 plot = [ mesh3d( x=x, y=y, z=z, save_name='ijk_halfpyramid', intensity=z, visible=True, # 初期プロットでは表示 i=[0], j=[1], k=[2], show=False, # グラフ表示ではなくdataの出力のみ ), mesh3d( x=x, y=y, z=z, save_name='ijk_halfpyramid', intensity=z, visible=False, # 初期プロットでは表示しない i=[0, 0], j=[1, 2], k=[2, 3], show=False, ), mesh3d( x=x, y=y, z=z, save_name='ijk_halfpyramid', intensity=z, visible=False, # 初期プロットでは表示しない i=[0, 0, 0], j=[1, 2, 1], k=[2, 3, 3], show=False, ), mesh3d( x=x, y=y, z=z, save_name='ijk_halfpyramid', intensity=z, visible=False, # 初期プロットでは表示しない i=[0, 0, 0, 1], j=[1, 2, 1, 2], k=[2, 3, 3, 3], show=False, ), ] # スライダー作成 steps = [] for num, p in enumerate(plot, 1): # プロットの表示・非表示 visible = [False] * len(plot) visible[:num] = [True] * num # タイトルの作成 # i, j, kの各組み合わせを(i1, j1, k1), (i2, j2, k2),...にする title = [f"({i}, {j}, {k})" for i, j, k in zip(p['i'], p['j'], p['k'])] # 各座標をコンマで区切りつつ、接頭辞を追加 title = f"vertex {', '.join(title)}" # スライダー作成 step = dict( method='update', label=f"{num} planes", args=[dict(visible=visible), dict(title=title)], ) steps.append(step) sliders = [dict(active=0, steps=steps)] fig = go.Figure(data=plot, layout=go.Layout(sliders=sliders)) fig.show() prefix = 'plotly-mesh3d' save(fig=fig, config=None, save_name=f"{prefix}_ijk_halfpyramid") mesh3d_ijk()
alphahull
で自動で面作成
面を作成する引数alphahull
も有効。なんだけど如何せん情報が少ないし解説の内容も複雑。しかも公式のコードだとグラフが表示されない。
ということで、うまくいったっぽいコードだけを紹介。できるものはシンプルに半分のピラミッドで色をjet
にしておいた。
# alphahullは情報が少ない mesh3d( x=x, y=y, z=z, save_name='alphahull_halfpyramid', colorscale='jet', intensity=z, alphahull=0, # alphahullを設定することで自動で面を作成してくれる )
contour
とlighting
最後はcontour
とlighting
を同時に見る。contour
はホバーした時の面上にできる線のこと。太さを変えられるっぽいけど、なぜか太さが変わらなかった。
一方でlighting
は図形に対しての光の当たり方。周辺光の入り方や入射光の入り方などを設定することができる。
lighting
は特に実用的ではないかもしれないが、図形を効果的に見せるには使えるかもしれない。
mesh3d( x=x, y=y, z=z, save_name='contour_lighting_halfpyramid', colorscale='jet', intensity=z, contour=dict(color='white', show=True, width=8), # widthをイジっても変わらん lighting=dict( ambient=0.1, # 周辺光(傾けた時の影の入らなさ具合) diffuse=0.1, # 入射光線8そもそもの光の当たり方) specular=2, # 光が当たった時の反射具合 ), )
サクッと立体図形
今回はplotly
のmesh3d
について解説した。立体図形を頂点からサクッと作成するにはこの方法がかなり楽だろう。
一方で配列でデータが決まっている場合はgo.Surface
で1点ずつ決めて滑らかに繋ぐ方がいい。
一長一短であり使い所が異なるので両方していると使い分けができていいだろう。即効性とデータ系の違い。
関連記事
-
-
【python&3Dピラミッド】plotlyで3次元ピラミッドを作成
続きを見る
-
-
【python&アニメーション】plotlyで3次元ピラミッドを回転させる
続きを見る
-
-
【plotly&3D】px.scatter_3dとpx.line_3Dで3Dグラフを作成
続きを見る
-
-
【plotly&3D】goで3Dグラフを作成
続きを見る