2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

[Plotly] ベクトル(矢印)を3D空間に描く - 力学・勾配・向きデータを可視化する

Last updated at Posted at 2025-11-21

はじめに

image.png

3D空間に「向き・強さ」を持つデータを描きたいとき、便利なのが ベクトル(矢印)可視化です。

速度場・勾配・力の方向・流体シミュレーション・特徴量の"向き"などに使えます。

Plotly には専用の「矢印プリミティブ」はありませんが、
線(Scatter3d)+円錐(Cone)+マーカー の組み合わせで実現できます。

この記事でできること

  • 3D空間に「方向を持つ矢印(ベクトル)」を描く
  • 始点と方向ベクトルから矢印を自動生成する
  • 複数矢印でベクトル場を表現する
  • Surface 上に勾配ベクトルを重ねる(勾配計算の注意点込み)
  • sizemode="scaled" / "absolute" / "raw" の違いを理解する(v5.21以降)

① 最小構成:1本のベクトルを3Dに描く

矢印の頭(arrowhead)は Cone(円錐) で表現する。

軸は Scatter3d(mode="lines") で描く。

import plotly.graph_objects as go

# 始点
x0, y0, z0 = 0, 0, 0
# ベクトル
vx, vy, vz = 1, 1, 1

fig = go.Figure()

# 線(矢の軸)
fig.add_trace(go.Scatter3d(
    x=[x0, x0+vx],
    y=[y0, y0+vy],
    z=[z0, z0+vz],
    mode='lines',
    line=dict(color='blue', width=6),
    name="Vector"
))

# 矢印の頭(Cone)
fig.add_trace(go.Cone(
    x=[x0+vx],
    y=[y0+vy],
    z=[z0+vz],
    u=[vx],
    v=[vy],
    w=[vz],
    sizemode="absolute",  # 一定サイズの矢頭
    sizeref=0.3,
    colorscale="Blues",
    showscale=False
))

fig.update_layout(
    title="Single 3D Vector",
    scene=dict(aspectmode='data')
)
fig.show()

image.png

② 任意の始点・方向から矢印を描くヘルパー関数

いちいち座標を書くのはつらいので、ヘルパー関数を用意しておくと便利。

def add_vector(fig, origin, vector, color='red', name="Vector"):
    ox, oy, oz = origin
    vx, vy, vz = vector
    
    # 軸(線)
    fig.add_trace(go.Scatter3d(
        x=[ox, ox+vx],
        y=[oy, oy+vy],
        z=[oz, oz+vz],
        mode='lines',
        line=dict(color=color, width=6),
        showlegend=False
    ))
    
    # 矢頭(cone)
    fig.add_trace(go.Cone(
        x=[ox+vx],
        y=[oy+vy],
        z=[oz+vz],
        u=[vx],
        v=[vy],
        w=[vz],
        sizemode="absolute",
        sizeref=0.3,
        colorscale=[[0, color], [1, color]],
        showscale=False
    ))

使用例:

import plotly.graph_objects as go

fig = go.Figure()

add_vector(fig, origin=(0,0,0), vector=(1,0.5,1),  color='red')
add_vector(fig, origin=(0,0,0), vector=(-1,0.4,0.3), color='green')

fig.update_layout(
    scene=dict(aspectmode='data'),
    title="Multiple 3D Vectors"
)
fig.show()

image.png

③ ベクトル場(多数の矢印)を Cone だけで可視化する

ベクトルの数が多いときは、軸の線を描かずに Coneのみ の方が軽い。

import numpy as np
import plotly.graph_objects as go

np.random.seed(0)

N = 40
origin = np.random.uniform(-1, 1, (N, 3))
vector = np.random.uniform(-0.5, 0.5, (N, 3))

fig = go.Figure()

fig.add_trace(go.Cone(
    x = origin[:,0],
    y = origin[:,1],
    z = origin[:,2],
    u = vector[:,0],
    v = vector[:,1],
    w = vector[:,2],
    sizemode="scaled",   # ベクトル長さに比例してスケール
    sizeref=0.4,
    colorscale="Plasma",
    showscale=True
))

fig.update_layout(
    title="Random 3D Vector Field",
    scene=dict(aspectmode='data')
)
fig.show()

image.png

④ ベクトルの「強さ」を色で表現する

ベクトルのノルム(長さ)を計算し、それを使って色付けする方法です。

go.Coneでは、u, v, w の大きさが自動的に色に反映されますが、明示的にノルムで色を制御したい場合は、以下のように複数のConeトレースに分けるか、Scatter3dと組み合わせる方法があります。

方法1:sizemodeとcolorscaleで自動的に色付け

import numpy as np
import plotly.graph_objects as go

np.random.seed(0)

N = 40
origin = np.random.uniform(-1, 1, (N, 3))
vector = np.random.uniform(-0.5, 0.5, (N, 3))

# ベクトルの長さを計算
norm = np.linalg.norm(vector, axis=1)

fig = go.Figure()
fig.add_trace(go.Cone(
    x = origin[:,0],
    y = origin[:,1],
    z = origin[:,2],
    u = vector[:,0],
    v = vector[:,1],
    w = vector[:,2],
    sizemode="scaled",
    sizeref=0.4,
    colorscale="Turbo",
    showscale=True
))

fig.update_layout(
    title="3D Vector Field with Magnitude Coloring",
    scene=dict(aspectmode='data')
)
fig.show()

image.png

方法2:長さに応じて色を明示的に指定

# 長さで色を変える場合は、個別にConeを追加
import matplotlib.cm as cm
import matplotlib.colors as mcolors

norm = np.linalg.norm(vector, axis=1)
norm_normalized = (norm - norm.min()) / (norm.max() - norm.min())

fig = go.Figure()

# 各ベクトルを個別に追加
for i in range(N):
    color_value = norm_normalized[i]
    rgb = cm.turbo(color_value)
    color_str = f'rgb({int(rgb[0]*255)},{int(rgb[1]*255)},{int(rgb[2]*255)})'
    
    fig.add_trace(go.Cone(
        x = [origin[i,0]],
        y = [origin[i,1]],
        z = [origin[i,2]],
        u = [vector[i,0]],
        v = [vector[i,1]],
        w = [vector[i,2]],
        sizemode="scaled",
        sizeref=0.4,
        colorscale=[[0, color_str], [1, color_str]],
        showscale=False,
        showlegend=False
    ))

fig.update_layout(
    title="3D Vector Field with Magnitude Coloring",
    scene=dict(aspectmode='data')
)
fig.show()

image.png

⑤ Surface の上に「勾配ベクトル」を描く(※勾配計算の軸に注意)

ここが指摘いただいた 修正ポイント。

np.gradient(Z) は
「行方向(axis=0)=Y方向」「列方向(axis=1)=X方向」
の順で返ってくる。

そのため、dZdY, dZdX = np.gradient(Z) と受け取る必要がある。

import numpy as np
import plotly.graph_objects as go

# Surface
x = np.linspace(-2, 2, 20)
y = np.linspace(-2, 2, 20)
X, Y = np.meshgrid(x, y)
Z = np.sin(np.sqrt(X**2 + Y**2))

# 勾配(∂Z/∂X, ∂Z/∂Y)
# 修正前: dZdX, dZdY = np.gradient(Z)  ← 軸の意味が逆になる
# 修正後(案2ベースでより明確に):
grad_y, grad_x = np.gradient(Z)   # axis 0 (rows/Y), axis 1 (cols/X)
dZdY, dZdX = grad_y, grad_x

fig = go.Figure()

# 曲面(少し透明)
fig.add_trace(go.Surface(
    x=X, y=Y, z=Z,
    opacity=0.6,
    colorscale='Viridis',
    showscale=False
))

# 勾配ベクトル(Z方向成分はゼロとしてXY平面内の向きを可視化)
fig.add_trace(go.Cone(
    x=X.flatten(),
    y=Y.flatten(),
    z=Z.flatten(),
    u=dZdX.flatten(),   # X方向の勾配
    v=dZdY.flatten(),   # Y方向の勾配
    w=np.zeros_like(dZdX).flatten(),
    sizemode="scaled",
    sizeref=0.5,
    colorscale="Hot",
    showscale=False
))

fig.update_layout(
    scene=dict(aspectmode='data'),
    title="Gradient Vectors on a Surface"
)
fig.show()

image.png

ポイント

  • np.gradient(Z) の戻り値の順番を正しく理解することが重要
  • 上記のように grad_y, grad_x = np.gradient(Z) と名前を付けると、軸の対応関係が分かりやすい

⑥ sizemode の違いについて(v5.21以降)

go.Cone には sizemode があり、Plotly v5.21 以降で "raw" も選べるようになっています。

主な3種類

"absolute"

  • sizeref による絶対サイズ指定(ベクトル長さにかかわらず、矢頭サイズ一定)

"scaled"

  • ベクトル長さに応じてサイズをスケーリング(相対的な比較に便利)

"raw"(v5.21+)

  • u, v, w の長さをそのまま幾何サイズとして扱うモード
  • 正規化済みベクトルや物理量を"そのまま"スケールにしたい場合に向いている(ただしスケール調整が大きく変わるので、ドキュメント確認推奨)

記事内のサンプルでの使い分け

  • 1本・少数矢印 → "absolute"
  • ベクトル場・強弱比較 → "scaled"
  • 「長さそのものを幾何的に使う」場面 → "raw" を検討

トラブルシュート

矢印(Cone)が巨大/小さすぎる

  • sizeref を 0.1〜0.5 の範囲で調整
  • "scaled" / "absolute" / "raw" を切り替えて挙動を確認

向きが逆に見える

  • ベクトルを (−u, −v, −w) にして反転してみる

勾配が「逆方向」に向いているように見える

  • np.gradient の戻り値の順番を確認(grad_y, grad_x = np.gradient(Z)

描画が重い

  • Cone の数を 500 以下にする
  • Grid を荒くする(20×20 → 15×15 など)

まとめ

image.png

3Dの矢印は、線と円錐を組み合わせて表現できます。
多数のベクトルを描く場合は、Coneだけで描画したほうが軽くて扱いやすいです。
ベクトルの長さは色でも表現でき、強弱が直感的に伝わります。
Surface に勾配ベクトルを重ねると、変化の方向や傾きが一目で理解できるようになります。

実装上の注意

  • np.gradient の戻り値の順番(Y, X)を必ず意識する
  • sizemode="absolute" / "scaled" / "raw" の違いを理解して使い分ける

参考情報

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?