カテゴリー

go

【plotly&3D Mesh】Surfaceプロットを頂点から作成

2021年11月22日

こんな人にオススメ

plotlyMesh3dって何?どうやら3Dのグラフが描けるのらしいけど、どうやって描く?

ということで、今回はplotlymesh3dを解説する。できることは3次元のグラフの作成なんだけど若干クセが強いので解読が面倒。

面倒なんだけどあまり使われていないからか情報がかなり少ない。なので今回の記事を軸に色々調べていただければ幸いだ。

python環境は以下。

  • Python 3.10.0
  • plotly 5.4.0
  • plotly-orca 3.4.2
スポンサーリンク
スポンサーリンク

運営者のメガネと申します。TwitterInstagramも運営中。

自己紹介はこちらから、お問い合わせはこちらからお願いいたします。

運営者メガネ

作成したコード全文

下準備

import plotly.graph_objects as go
import plotly.io as pio

まずは下準備としてのimport関連。今回はplotlygoを使用するのでgoimport。あとはグラフ保存のためのpio

plotlyでグラフを静止画として保存する際に出るポップアップ
【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.Mesh3dx, y, zの座標を指定する。あとは**kwargsで追加の設定ができるようにした。

kwargsは可変長キーワード引数で辞書のようにkeyvalueを指定するイメージ。これで関数に引数を追加できる。

【plotly&kwargs】グラフ作成時の設定を後から追加できるように

続きを見る

また、後ほどdataを出力したい時があるので、引数showでグラフの表示と保存をするか出力するかを選べるようにした。

データ配列

# 半分のピラミッドになるような座標
x = (0, 1, 0, 0.5)
y = (0, 0, 1, 0.5)
z = (0, 0, 0, 1)

mesh3dx, 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で面ごとの色付けに変更

最後は引数intensityintensitymodeで面ごとの色付けをカラースケールで行う。と言っても今回は面が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, where i[m] = n points to the triplet {x[n], y[n], z[n]} in the vertex arrays. Therefore, each element in i 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を設定することで自動で面を作成してくれる
)

contourlighting


最後はcontourlightingを同時に見る。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,  # 光が当たった時の反射具合
    ),
)

サクッと立体図形

今回はplotlymesh3dについて解説した。立体図形を頂点からサクッと作成するにはこの方法がかなり楽だろう。

一方で配列でデータが決まっている場合はgo.Surfaceで1点ずつ決めて滑らかに繋ぐ方がいい。

一長一短であり使い所が異なるので両方していると使い分けができていいだろう。即効性とデータ系の違い。

関連記事

【python&3Dピラミッド】plotlyで3次元ピラミッドを作成

続きを見る

【python&アニメーション】plotlyで3次元ピラミッドを回転させる

続きを見る

【plotly&3D】px.scatter_3dとpx.line_3Dで3Dグラフを作成

続きを見る

【plotly&3D】goで3Dグラフを作成

続きを見る

関連コンテンツ

スポンサーリンク

Amazonのお買い物で損したない人へ

1回のチャージ金額通常会員プライム会員
¥90,000〜2.0%2.5%
¥40,000〜1.5%2.0%
¥20,000〜1.0%1.5%
¥5,000〜0.5%1.0%

Amazonギフト券にチャージすることでお得にお買い物できる。通常のAmazon会員なら最大2.0%、プライム会員なら2.5%還元なのでバカにならない。

ゲットしたポイントは通常のAmazonでのお買い物に使えるからお得だ。一度チャージしてしまえば、好きなタイミングでお買いものできる。

なお、有効期限は10年だから安心だ。いつでも気軽にAmazonでお買い物できる。

Amazonチャージはここから出来るで

もっとお得なAmazon Prime会員はこちらから

30日間無料登録

執筆者も便利に使わせてもらってる

スポンサーリンク

  • この記事を書いた人

メガネ

独学でpythonを学び天文学系の大学院を修了。 ガジェット好きでMac×Android使い。色んなスマホやイヤホンを購入したいけどお金がなさすぎて困窮中。 元々、人見知りで根暗だったけど、人生楽しもうと思って良い方向に狂ったために今も人生めちゃくちゃ楽しい。 pythonとガジェットをメインにブログを書いていますので、興味を持たれましたらちょこちょこ訪問してくだされば幸いです🥰。 自己紹介→変わって楽しいの繰り返し

-go
-, , ,