カテゴリー

当サイトはアフィリエイトプログラムによる収益を得ています〈景品表示法に基づく表記です)

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

運営者のメガネです。YouTubeTwitterInstagram、自己紹介はこちら、お問い合わせはこちらから。

運営者メガネ

作成したコード全文

下準備

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を指定するイメージ。これで関数に引数を追加できる。

また、後ほど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グラフを作成

続きを見る

ガジェット

2023/9/18

【デスクツアー2022下半期】モノは少なく、でも効率的に Desk Updating #0

今回はガジェットブロガーなのにデスク環境を構築していない執筆者の ...

ライフハック

2023/9/16

【Audible vs YouTube Premium】耳で聴く音声学習コンテンツを比較

ワイヤレスイヤホンが普及し耳で学習することへのハードルが格段に下 ...

完全ワイヤレスイヤホン(TWS)

2023/9/18

【SENNHEISER MOMENTUM True Wireless 3レビュー】全てが整ったイヤホン

今回は高音質・高機能なSENNHEISERのフラグシップ完全ワイヤレスイヤホン「SENNH ...

ライフハック

2023/3/11

【YouTube Premiumとは】メリットしかないから全員入れ

今回はYouTube Premiumを実際に使ってみてどうなのか、どんなメリット/デメリット ...

マウス

2023/8/17

【Logicool MX ERGOレビュー】疲れない作業効率重視トラックボールマウス

こんな人におすすめ トラックボールマウスの王道Logicool MX ERGOが気になるけどऩ ...

ベストバイ

2023/9/18

【ベストバイ2022】今年買って良かったモノのトップ10

2022年ベストバイ この1年を振り返って執筆者は何を買ったのか。ガジェッ& ...

スマホ

2023/1/15

【楽天モバイル×povo2.0の併用】月1,000円の保険付きデュアルSIM運用

こんな人におすすめ 楽天モバイルとpovo2.0のデュアルSIM運用って実際のとこ ...

マウス

2023/9/16

【Logicool MX ERGO vs MX Master 3】ERGOをメインにした決定的な理由

こんな疑問・お悩みを持っている人におすすめ 執筆者はLogicoolのハイエンӠ ...

macOSアプリケーション

2022/9/30

【Chrome拡張機能】便利で効率的に作業できるおすすめの拡張機能を18個紹介する

こんな人におすすめ Chromeの拡張機能を入れたいけど、調べても同じような ...

macOSアプリケーション

2023/5/3

【Automator活用術】Macで生産性を上げる作業の自動化術

今回はMacに標準でインストールされているアプリ「Automator」を使ってできる ...

Pythonを学びたいけど独学できる時間なんてない人へのすゝめ

執筆者は大学の研究室・大学院にて独学でPythonを習得した。

でも社会人になったら独学で行うには時間も体力もなくて大変だ。

時間がない社会人だからこそプロの教えを乞うのが効率的。

ここでは色んなタイプに合ったプログラミングスクールの紹介をする。

  • この記事を書いた人

メガネ

Webエンジニア駆け出し。独学のPythonで天文学系の大学院を修了。常時金欠のガジェット好きでM2 Pro MacBook Pro(30万円) x Galaxy S22 Ultra(17万円)使いの狂人。自己紹介と半生→変わって楽しいの繰り返しレビュー依頼など→お問い合わせ運営者情報、TwitterX@m_ten_pa、 YouTube@megatenpa、 Threads@megatenpa

-go
-, , ,