はじめに
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()
② 任意の始点・方向から矢印を描くヘルパー関数
いちいち座標を書くのはつらいので、ヘルパー関数を用意しておくと便利。
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()
③ ベクトル場(多数の矢印)を 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()
④ ベクトルの「強さ」を色で表現する
ベクトルのノルム(長さ)を計算し、それを使って色付けする方法です。
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()
方法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()
⑤ 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()
ポイント
-
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 など)
まとめ
3Dの矢印は、線と円錐を組み合わせて表現できます。
多数のベクトルを描く場合は、Coneだけで描画したほうが軽くて扱いやすいです。
ベクトルの長さは色でも表現でき、強弱が直感的に伝わります。
Surface に勾配ベクトルを重ねると、変化の方向や傾きが一目で理解できるようになります。
実装上の注意
-
np.gradientの戻り値の順番(Y, X)を必ず意識する -
sizemode="absolute"/"scaled"/"raw"の違いを理解して使い分ける
参考情報







