はじめに
3Dライン(軌跡)を描くだけでは「速度の変化」が分かりにくい。
しかし太さ(line width)や色(colorscale)を速度に応じて変化させることで、
どこが速く、どこがゆっくりなのかを一目で理解できるようになる。
ロボット軌道、物体の運動解析、時系列ラインの"勢い"の可視化に最適。
この記事でできること
- 3Dラインに"速度"を反映した可視化ができる
- 速度に応じて「太さ」と「色」を変化
- Colabでそのまま動くサンプルコード
- 曲線の特徴(加速・減速・急カーブ)を直感的に把握
基本:速度を計算する
3D軌跡データ(x,y,z)があれば、隣接点の差分から速度を計算できる。
import numpy as np
t = np.linspace(0, 4*np.pi, 300)
# 速度変化を持たせるため、振幅を変化させる
amplitude = 1 + 0.5 * np.sin(t * 0.5)
x = np.sin(t) * amplitude
y = np.cos(t) * amplitude
z = t * 0.2
# 速度(点と点の距離)
dx = np.diff(x)
dy = np.diff(y)
dz = np.diff(z)
speed = np.sqrt(dx**2 + dy**2 + dz**2)
# 可視化用に長さを揃える(速度配列は1つ短い)
speed = np.append(speed, speed[-1])
ポイント
- 速度=隣接点間距離として定義
- 実データなら
(位置の差分) / (時間間隔)にするだけ - 最後の点は便宜的に
speed[-1]を入れて長さを合わせる
速度を色(colorscale)で表現する
Plotlyは line.color に配列を渡すと、
各点に応じて色を変化させられる。
import plotly.graph_objects as go
fig = go.Figure()
fig.add_trace(go.Scatter3d(
x=x, y=y, z=z,
mode="lines",
line=dict(
width=6,
color=speed, # 速度で色を変える!
colorscale="Turbo",
colorbar=dict(title="Speed")
),
name="Speed Line"
))
fig.update_layout(
title="3D Line with Speed (Color)",
scene=dict(aspectmode='data')
)
fig.show()
見えること
- 速度が速い区間 → 暖色(赤・黄色)
- 遅い区間 → 寒色(青)
- カーブの部分が減速している、直線部分で加速している、などが直感的にわかる
速度を太さ(line width)に反映する
Plotlyの line.width はスカラーしか受け取れないため、
細かく分割した"短い線分"を複数描画する方法が必要。
ラインを短いセグメントに分割して太さを変える
fig = go.Figure()
# 速度を正規化(0〜1の範囲に)
speed_norm = (speed - speed.min()) / (speed.max() - speed.min())
for i in range(len(x)-1):
fig.add_trace(go.Scatter3d(
x=[x[i], x[i+1]],
y=[y[i], y[i+1]],
z=[z[i], z[i+1]],
mode="lines",
line=dict(
width=3 + speed_norm[i]*15, # 速度で太さを変える(3〜18の範囲)
color="red"
),
showlegend=False
))
fig.update_layout(
title="3D Line with Speed (Thickness)",
scene=dict(aspectmode='data')
)
fig.show()
ポイント
- 速度を正規化してから太さに反映させることで、変化を強調
- 太さの範囲を
3〜18に設定(変化が見えやすい) - 速度変化が小さい場合は係数を大きくする
見えること
- 速い部分 → 太く
- 遅い部分 → 細く
- 曲線の"勢い"がはっきり視覚化される
太さ+色のハイブリッド(最高に見やすい)
速度を色と太さの両方で反映すると説得力抜群。
import plotly.express as px
# 速度を正規化してcolormapから色を取得
norm_speed = (speed - speed.min()) / (speed.max() - speed.min())
colors = px.colors.sample_colorscale("Turbo", norm_speed)
fig = go.Figure()
for i in range(len(x)-1):
fig.add_trace(go.Scatter3d(
x=[x[i], x[i+1]],
y=[y[i], y[i+1]],
z=[z[i], z[i+1]],
mode="lines",
line=dict(
width=3 + norm_speed[i]*15, # 正規化した速度で太さを変える
color=colors[i] # 正規化した速度から色を取得
),
showlegend=False
))
# カラーバーを表示するためのダミートレース
fig.add_trace(go.Scatter3d(
x=[None], y=[None], z=[None],
mode='markers',
marker=dict(
size=0.1,
color=speed,
colorscale="Turbo",
colorbar=dict(title="Speed"),
showscale=True
),
showlegend=False
))
fig.update_layout(
title="3D Line with Speed (Color + Thickness)",
scene=dict(aspectmode='data')
)
fig.show()
見えること
- 色+太さで "速度差の特徴" が圧倒的に分かりやすい
- 曲線の"流れ"が視覚的に掴める
- カラーバーで速度の数値範囲も確認できる
背景や軸を整えて見やすくする
fig.update_layout(
scene=dict(
xaxis=dict(backgroundcolor='rgb(240,240,240)'),
yaxis=dict(backgroundcolor='rgb(240,240,240)'),
zaxis=dict(backgroundcolor='rgb(248,248,248)'),
aspectmode='data'
)
)
- 薄い背景はラインの色変化を際立たせる
-
aspectmode='data'は歪み防止のためおすすめ
トラブルシュート
太さが効かない
→ Scatter3d の場合「セグメント分割」が必須
太さの変化が見えにくい
→ 速度を正規化してから係数を調整
speed_norm = (speed - speed.min()) / (speed.max() - speed.min())
width = 3 + speed_norm * 15 # 係数を大きくする
色と太さの両方を使ったら見づらい
→ opacity や colorscale を調整
Colabで重い
→ セグメント数が多い場合は点数を間引く
# 例:5点ごとにサンプリング
indices = np.arange(0, len(x), 5)
x_sampled = x[indices]
y_sampled = y[indices]
z_sampled = z[indices]
色が飛ぶ
→ colorscale を "Viridis" や "Cividis" に変更
実装例
import numpy as np
import plotly.graph_objects as go
import plotly.express as px
# データ生成(速度変化を持たせる)
t = np.linspace(0, 4*np.pi, 300)
amplitude = 1 + 0.5 * np.sin(t * 0.5)
x = np.sin(t) * amplitude
y = np.cos(t) * amplitude
z = t * 0.2
# 速度計算
dx = np.diff(x)
dy = np.diff(y)
dz = np.diff(z)
speed = np.sqrt(dx**2 + dy**2 + dz**2)
speed = np.append(speed, speed[-1])
# 速度の正規化と色の準備
norm_speed = (speed - speed.min()) / (speed.max() - speed.min())
colors = px.colors.sample_colorscale("Turbo", norm_speed)
# プロット
fig = go.Figure()
for i in range(len(x)-1):
fig.add_trace(go.Scatter3d(
x=[x[i], x[i+1]],
y=[y[i], y[i+1]],
z=[z[i], z[i+1]],
mode="lines",
line=dict(
width=3 + norm_speed[i]*15,
color=colors[i]
),
showlegend=False
))
# カラーバー用ダミートレース
fig.add_trace(go.Scatter3d(
x=[None], y=[None], z=[None],
mode='markers',
marker=dict(
size=0.1,
color=speed,
colorscale="Turbo",
colorbar=dict(title="Speed"),
showscale=True
),
showlegend=False
))
fig.update_layout(
title="3D Line with Speed Visualization",
scene=dict(
xaxis=dict(backgroundcolor='rgb(240,240,240)'),
yaxis=dict(backgroundcolor='rgb(240,240,240)'),
zaxis=dict(backgroundcolor='rgb(248,248,248)'),
aspectmode='data'
)
)
fig.show()
まとめ
速度の大小は、色と線の太さを組み合わせて表現すると理解しやすい。
太さで表す場合は、線をセグメントに分割する必要がある。
色なら単一トレースでは配列を渡すだけで自動に変化でき、複数セグメントでは px.colors.sample_colorscale で事前計算すると便利。
この2つを併用したハイブリッド表示が、速度変化を最も直感的に把握できる。
適用例
- 移動体軌跡
- ロボットアームの動作可視化
- 時系列データの勢い
- シミュレーション動作の確認
3Dラインの速度表現は、時系列データの理解を大きく前進させる強力なテクニック。
解説動画
参考情報






