2
3

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散布図に回転アニメーションを付ける : 構造を"動きで理解する"3D可視化

Last updated at Posted at 2025-11-23

はじめに

3d_rotation_concept_v2.png

3D散布図は静止画でも情報量は多いですが、
回転アニメーションを付けると"奥行き構造"が飛躍的にわかりやすくなります。
特にクラスタリング可視化・異常点検出・層構造の把握などで効果的。

Plotly ではカメラ位置を時間変化させることで、
3Dグラフに"回転し続けるアニメーション"を簡単に追加できます。

この記事でできること

  • 3D散布図に「自動回転アニメーション」を追加
  • カメラの角度を時間軸で変化させる
  • クラスタ構造・外れ値を"動かして把握"する
  • Colabでもそのまま動くコード付き

① 基本の3D散布図を作る

まずは動かす対象となる散布図を作成。

import plotly.graph_objects as go
import numpy as np

np.random.seed(0)
x, y, z = np.random.randn(3, 300)

fig = go.Figure(data=[go.Scatter3d(
    x=x, y=y, z=z,
    mode='markers',
    marker=dict(size=4, color=z, colorscale='Viridis', opacity=0.7)
)])

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

image.png


② カメラ角度を変えて "回転" をつくる

Plotlyは カメラ位置(eye) を変化させることで、
3Dグラフを回転させたように見せることができます。

回転フレームを生成する関数

def make_frames():
    frames = []
    for theta in np.linspace(0, 2*np.pi, 60):
        camera = dict(
            eye=dict(
                x=2.0*np.cos(theta),
                y=2.0*np.sin(theta),
                z=1.2
            )
        )
        frames.append(go.Frame(layout=dict(scene_camera=camera)))
    return frames

ポイント:

  • theta で角度を 0 〜 2π まで変化させる
  • np.cos(theta), np.sin(theta) で円周上にカメラを配置
  • z=1.2 で視点の高さを固定

③ アニメーションを設定する

frames = make_frames()

fig = go.Figure(
    data=[go.Scatter3d(
        x=x, y=y, z=z,
        mode='markers',
        marker=dict(size=4, color=z, colorscale='Viridis', opacity=0.7)
    )],
    frames=frames
)

fig.update_layout(
    title="Rotating 3D Scatter",
    scene=dict(aspectmode='data'),
    updatemenus=[
        dict(
            type="buttons",
            buttons=[
                dict(
                    label="▶ Rotate",
                    method="animate",
                    args=[
                        None,
                        dict(
                            frame=dict(duration=80, redraw=True),
                            fromcurrent=True,
                            transition=dict(duration=0)
                        )
                    ]
                )
            ]
        )
    ]
)

fig.show()

結果

  • ▶ Rotate ボタンを押すと、3D散布図がゆっくり回転するアニメーションが再生される
  • 動かすことで「どの方向に伸びているか」が一目で把握できる

image.png


④ 停止・再生ボタンを追加する

fig.update_layout(
    updatemenus=[
        dict(
            type="buttons",
            buttons=[
                dict(
                    label="▶ Play",
                    method="animate",
                    args=[
                        None,
                        dict(
                            frame=dict(duration=80, redraw=True),
                            fromcurrent=True,
                            transition=dict(duration=0),
                            mode='immediate'
                        )
                    ]
                ),
                dict(
                    label="⏸ Pause",
                    method="animate",
                    args=[
                        [None],
                        dict(
                            frame=dict(duration=0, redraw=False),
                            mode="immediate",
                            transition=dict(duration=0)
                        )
                    ]
                )
            ]
        )
    ]
)
fig.show()

ポイント:

  • Play/Pause の2つのボタンを用意
  • Pause ボタンで duration=0 にして停止
  • 見たい角度で止めて詳細を確認できる

image.png

⑤ 視点の高さを変える(俯瞰〜仰角)

def make_frames_vertical():
    frames = []
    for phi in np.linspace(-1.5, 1.5, 40):
        camera = dict(eye=dict(x=1.8, y=1.8, z=phi))
        frames.append(go.Frame(layout=dict(scene_camera=camera)))
    return frames

frames_vertical = make_frames_vertical()

fig = go.Figure(
    data=[go.Scatter3d(
        x=x, y=y, z=z,
        mode='markers',
        marker=dict(size=4, color=z, colorscale='Viridis', opacity=0.7)
    )],
    frames=frames_vertical
)

fig.update_layout(
    title="Vertical Camera Movement",
    scene=dict(aspectmode='data'),
    updatemenus=[
        dict(
            type="buttons",
            buttons=[
                dict(
                    label="▶ Up/Down",
                    method="animate",
                    args=[
                        None,
                        dict(frame=dict(duration=100, redraw=True),
                             fromcurrent=True,
                             transition=dict(duration=0))
                    ]
                )
            ]
        )
    ]
)

fig.show()

image.png

効果:

  • 上から・横から・下から…と構造が明確に見える
  • クラスタの"高さ方向の偏り"把握に便利
  • : これは回転ではなく、視点の上下移動です

⑥ 回転+ズームでより立体的に見せる

def make_frames_zoom():
    frames = []
    eye_dist = np.linspace(1.5, 2.5, 60)
    for angle, dist in zip(np.linspace(0, 2*np.pi, 60), eye_dist):
        frames.append(go.Frame(
            layout=dict(
                scene_camera=dict(
                    eye=dict(
                        x=dist*np.cos(angle),
                        y=dist*np.sin(angle),
                        z=1.2
                    )
                )
            )
        ))
    return frames

frames_zoom = make_frames_zoom()

fig = go.Figure(
    data=[go.Scatter3d(
        x=x, y=y, z=z,
        mode='markers',
        marker=dict(size=4, color=z, colorscale='Viridis', opacity=0.7)
    )],
    frames=frames_zoom
)

fig.update_layout(
    title="Rotating + Zooming 3D Scatter",
    scene=dict(aspectmode='data'),
    updatemenus=[
        dict(
            type="buttons",
            buttons=[
                dict(
                    label="▶ Rotate & Zoom",
                    method="animate",
                    args=[
                        None,
                        dict(frame=dict(duration=80, redraw=True),
                             fromcurrent=True,
                             transition=dict(duration=0))
                    ]
                )
            ]
        )
    ]
)

fig.show()

image.png

効果:

  • 回りながら少しずつズームすると、"立体模型" のような動きになる
  • プレゼンや説明資料に最適

⑦ 背景・軸を整えるとアニメーションが見やすくなる

fig.update_layout(
    scene=dict(
        xaxis=dict(
            backgroundcolor='rgb(240,240,240)',
            gridcolor='white',
            showbackground=True
        ),
        yaxis=dict(
            backgroundcolor='rgb(240,240,240)',
            gridcolor='white',
            showbackground=True
        ),
        zaxis=dict(
            backgroundcolor='rgb(248,248,248)',
            gridcolor='white',
            showbackground=True
        ),
        aspectmode='data'
    ),
    title="Enhanced Visual 3D Scatter"
)

ポイント:

  • 背景が明るくなると、回転中の点の流れがキレイに見える
  • グリッド線を白にすると視認性が向上

トラブルシュート

症状 原因・対策
Colab で回転が重い ブラウザ負荷 → 点数を減らす(300→150)、フレーム数を減らす(60→30)
回転が速すぎる duration を 80 → 150 へ増やす
回転軸がズレる camera.eye の x/y/z 比を調整
もっと滑らかにしたい frames を 60→120 個に増やす(ただし重くなる)
アニメーションが動かない frames を正しく fig に渡しているか確認

まとめ

3d_rotation_summary.png

3Dグラフは「回して見る」ことで本領を発揮し、静止画では気づけない構造が一気に見えてきます。
camera.eye を時間変化させれば、回転アニメーションを手軽に実装できます。
クラスタ・外れ値・層構造の違いが立体的に把握しやすくなり、分析の精度も向上します。

ズームや上下移動と組み合わせれば、多角的な視点での可視化が可能です。
プレゼンやデモでも効果的で、理解が深まる見せ方になります。

参考リンク

2
3
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
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?