はじめに
3D Surface(曲面)は静止画でも情報量が多いですが、時間軸で動かすと構造の変化が強烈にわかりやすくなります。
波の伝播、熱分布の変化、ポテンシャル場の時間発展、回転・膨張・変形など、動的なデータの理解にはアニメーションが最適です。
Plotlyでは、Surfaceをフレームごとに切り替えることで、動く3D Surfaceを簡単に作れます。
この記事でできること
- 3D Surfaceを時間変化させてアニメーション表示
- フレームごとにZ値を更新して動きを表現
- Colabでそのまま動くコード
基本:動かすためのSurfaceを定義する
ここでは「波が動くSurface」を例にします。Z = sin(sqrt(x^2 + y^2) - t) のように t を時間ステップにします。
import plotly.graph_objects as go
import numpy as np
# 格子の作成
x = np.linspace(-3, 3, 40)
y = np.linspace(-3, 3, 40)
X, Y = np.meshgrid(x, y)
def surface_at(t):
"""時刻tにおける曲面のZ値を計算"""
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R - t)
return Z
最初のSurface(t=0)を表示
まず、初期状態(t=0)のSurfaceを表示して、基本的な形状を確認します。
Z0 = surface_at(0)
fig = go.Figure(
data=[go.Surface(x=X, y=Y, z=Z0, colorscale='Viridis', showscale=False)]
)
fig.update_layout(
title="Dynamic 3D Surface (t = 0)",
scene=dict(aspectmode='data')
)
fig.show()
フレームを生成してアニメーション化
時間ステップ t を 0 から 2π まで変化させます。Z値だけ更新すればOKです(Surfaceの x, y は固定のため)。
# フレームの生成
frames = []
for t in np.linspace(0, 2*np.pi, 40):
Zt = surface_at(t)
frames.append(
go.Frame(
data=[go.Surface(x=X, y=Y, z=Zt, colorscale='Viridis', showscale=False)],
name=f"t={t:.2f}"
)
)
アニメーションボタンを設定
再生ボタンを追加して、ユーザーがアニメーションを制御できるようにします。
fig = go.Figure(
data=[go.Surface(x=X, y=Y, z=surface_at(0), colorscale='Viridis', showscale=False)],
frames=frames
)
fig.update_layout(
title="Dynamic 3D Surface (Animation)",
scene=dict(aspectmode='data'),
updatemenus=[
dict(
type="buttons",
buttons=[
dict(
label="Play",
method="animate",
args=[
None,
dict(
frame=dict(duration=80, redraw=True),
transition=dict(duration=0),
fromcurrent=True
)
]
)
]
)
]
)
fig.show()
結果
Playボタンを押すと、波が外へ広がりながら動く3D Surfaceが流れるように表示されます。
色を時間変化させる(応用)
色も t に応じて変化させると、流体シミュレーション風の表現になります。
frames = []
for t in np.linspace(0, 2*np.pi, 40):
Zt = surface_at(t)
frames.append(
go.Frame(
data=[go.Surface(
x=X, y=Y, z=Zt,
colorscale='Turbo', # 動きのあるカラーマップ
showscale=False,
surfacecolor=Zt # 色も随時更新
)]
)
)
surfacecolor=Zt を更新すると、色が毎フレーム変わり、波・熱・密度の変化を表現できます。
カメラ回転と組み合わせる(さらに動的に)
Surfaceを動かしながらカメラも回転させると、プレゼンテーション向けの説得力のある可視化になります。
frames = []
angles = np.linspace(0, 2*np.pi, 40)
for t, theta in zip(np.linspace(0, 2*np.pi, 40), angles):
Zt = surface_at(t)
camera = dict(eye=dict(x=2*np.cos(theta), y=2*np.sin(theta), z=1.2))
frames.append(go.Frame(
data=[go.Surface(x=X, y=Y, z=Zt, colorscale='Viridis', showscale=False)],
layout=dict(scene_camera=camera)
))
動くSurface × 回り込む視点 で、さらに説得力のある可視化が実現できます。
背景を調整して見やすくする
動画として見たときに背景を調整すると、より見やすくなります。
fig.update_layout(
scene=dict(
xaxis=dict(backgroundcolor='rgb(240,240,240)'),
yaxis=dict(backgroundcolor='rgb(240,240,240)'),
zaxis=dict(backgroundcolor='rgb(250,250,250)'),
aspectmode='data'
)
)
トラブルシューティング
Surfaceが点滅する
redraw=True を設定してください。
dict(frame=dict(duration=80, redraw=True))
Colabで動かない
点数を減らしてみてください(40×40 → 30×30)。
x = np.linspace(-3, 3, 30)
y = np.linspace(-3, 3, 30)
FPSを上げたい
duration を 80 → 40 に変更してください。
dict(frame=dict(duration=40, redraw=True))
色が飛ぶ
surfacecolor を更新しないか、colorscale を固定してください。
まとめ
Surfaceを時間発展させるだけで、動的現象が直感的に理解できます。
キーポイント
- Z値をフレームごとに更新
- 色も変えるとより動きが強調される
- カメラ回転と組み合わせると可視化の説得力がアップ
主な用途
- 波動方程式
- 熱分布の時間変化
- ポテンシャル・エネルギー場
- 物理シミュレーション
- 3D地形データの変遷
静止画では見えない変化は、アニメーションにしてみると新たな視点が得られるかもしれません。
実装例
import plotly.graph_objects as go
import numpy as np
# 格子の作成
x = np.linspace(-3, 3, 40)
y = np.linspace(-3, 3, 40)
X, Y = np.meshgrid(x, y)
def surface_at(t):
"""時刻tにおける曲面のZ値を計算"""
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R - t)
return Z
# フレームの生成
frames = []
for t in np.linspace(0, 2*np.pi, 40):
Zt = surface_at(t)
frames.append(
go.Frame(
data=[go.Surface(x=X, y=Y, z=Zt, colorscale='Viridis', showscale=False)],
name=f"t={t:.2f}"
)
)
# Figureの作成
fig = go.Figure(
data=[go.Surface(x=X, y=Y, z=surface_at(0), colorscale='Viridis', showscale=False)],
frames=frames
)
# レイアウトの設定
fig.update_layout(
title="Dynamic 3D Surface Animation",
scene=dict(
xaxis=dict(backgroundcolor='rgb(240,240,240)'),
yaxis=dict(backgroundcolor='rgb(240,240,240)'),
zaxis=dict(backgroundcolor='rgb(250,250,250)'),
aspectmode='data'
),
updatemenus=[
dict(
type="buttons",
buttons=[
dict(
label="Play",
method="animate",
args=[
None,
dict(
frame=dict(duration=80, redraw=True),
transition=dict(duration=0),
fromcurrent=True
)
]
)
]
)
]
)
fig.show()
この記事が、動的な3D可視化の実装に役立てば幸いです。
参考情報




