この記事で学べること#

  • updatemenusの実装方法(再生/一時停止ボタン)
  • slidersの実装方法(シークバー)
  • アニメーション制御ボタンの配置とスタイリング
  • フレーム間の遷移設定
  • ボタンとスライダーの連携

対象読者#

  • D-4「Plotly.js Frames APIアニメーション基礎」を読んだ方
  • アニメーションにユーザー操作を追加したい方
  • インタラクティブなUIを構築したい方

前回の記事では、Frames APIによるアニメーションの基本を学びました。本記事では、updatemenusとslidersを使って、ユーザーがアニメーションを自由に操作できるUIを実装します。


updatemenusとslidersとは?#

updatemenus: ボタンによる操作#

updatemenusは、ボタンやドロップダウンメニューを配置する機能です。アニメーションの再生/一時停止、速度変更などを実現します。

sliders: スライダーによる操作#

slidersは、スライダー(シークバー)を配置する機能です。ユーザーが任意のフレームにジャンプできます。


基本的なupdatemenusの実装#

一時停止ボタンの追加#

最もシンプルな例として、一時停止ボタンを追加します。

const layout = {
    title: '3D軌道アニメーション',
    scene: {
        xaxis: { title: 'X (m)' },
        yaxis: { title: 'Y (m)' },
        zaxis: { title: 'Z (m)' }
    },
    updatemenus: [{
        type: 'buttons',  // ボタン型
        showactive: false,  // アクティブ状態を表示しない
        buttons: [{
            label: '⏸ Pause',  // ボタンラベル
            method: 'animate',  // アニメーション制御
            args: [[null], {  // nullで一時停止
                frame: { duration: 0, redraw: false },
                mode: 'immediate',
                transition: { duration: 0 }
            }]
        }],
        direction: 'left',  // ボタンの並び方向
        x: 0.1,  // X座標(0-1の相対座標)
        y: 0,  // Y座標(0-1の相対座標)
        xanchor: 'left',
        yanchor: 'bottom'
    }],
    height: 700
};

重要なプロパティ:

  • type: 'buttons' - ボタン型のメニュー
  • method: 'animate' - アニメーション制御メソッド
  • args: [[null], {...}] - [null]で一時停止

再生/一時停止ボタンの実装#

再生ボタンと一時停止ボタンを両方追加#

updatemenus: [{
    type: 'buttons',
    showactive: false,
    buttons: [
        {
            label: '▶ Play',  // 再生ボタン
            method: 'animate',
            args: [null, {  // nullで全フレーム再生
                frame: { duration: 50, redraw: true },
                transition: { duration: 0 },
                fromcurrent: true,  // 現在のフレームから再生
                mode: 'immediate'
            }]
        },
        {
            label: '⏸ Pause',  // 一時停止ボタン
            method: 'animate',
            args: [[null], {
                frame: { duration: 0, redraw: false },
                mode: 'immediate',
                transition: { duration: 0 }
            }]
        }
    ],
    direction: 'left',
    x: 0.1,
    y: 0,
    xanchor: 'left',
    yanchor: 'bottom'
}]

再生ボタンの設定:

  • args: [null, {...}] - 第1引数nullで全フレームを再生
  • fromcurrent: true - 現在のフレームから再生を開始
  • frame.duration: 50 - 各フレームの表示時間(ミリ秒)

一時停止ボタンの設定:

  • args: [[null], {...}] - 第1引数[null]で一時停止

再生速度調整ボタンの追加#

速度オプションを提供#

updatemenus: [
    {
        // 一時停止ボタン(1つ目のupdatemenu)
        type: 'buttons',
        buttons: [{
            label: '⏸ Pause',
            method: 'animate',
            args: [[null], {
                frame: { duration: 0, redraw: false },
                mode: 'immediate',
                transition: { duration: 0 }
            }]
        }],
        x: 0.1,
        y: 0,
        xanchor: 'left',
        yanchor: 'bottom'
    },
    {
        // 速度調整ボタン(2つ目のupdatemenu)
        type: 'buttons',
        showactive: true,  // アクティブなボタンを強調表示
        buttons: [
            {
                label: '× 0.5',  // 0.5倍速
                method: 'animate',
                args: [null, {
                    frame: { duration: 100 },  // 100ms/フレーム
                    transition: { duration: 0 },
                    mode: 'immediate'
                }]
            },
            {
                label: '× 1',  // 1倍速(標準)
                method: 'animate',
                args: [null, {
                    frame: { duration: 50 },  // 50ms/フレーム
                    transition: { duration: 0 },
                    mode: 'immediate'
                }]
            },
            {
                label: '× 2',  // 2倍速
                method: 'animate',
                args: [null, {
                    frame: { duration: 25 },  // 25ms/フレーム
                    transition: { duration: 0 },
                    mode: 'immediate'
                }]
            }
        ],
        x: 0.25,  // 一時停止ボタンの右側に配置
        y: 0,
        xanchor: 'left',
        yanchor: 'bottom'
    }
]

ポイント:

  • 複数のupdatemenusを配置できます
  • frame.durationを変えることで速度調整
  • duration: 100ms(遅い)、50ms(標準)、25ms(速い)

slidersの実装#

シークバーの追加#

const frames = [];  // framesは既に作成済みと仮定

const layout = {
    title: '3D軌道アニメーション',
    scene: {/* ... */},
    sliders: [{
        active: 0,  // 初期位置(最初のフレーム)
        steps: frames.map((frame, idx) => ({
            label: `${(timeData[idx] || 0).toFixed(1)}s`,  // ラベル(時刻表示)
            method: 'animate',
            args: [[frame.name], {  // 特定のフレームにジャンプ
                frame: { duration: 0, redraw: true },
                mode: 'immediate',
                transition: { duration: 0 }
            }]
        })),
        x: 0.1,  // スライダーのX座標
        len: 0.8,  // スライダーの長さ(0-1の相対値)
        xanchor: 'left',
        y: 0,
        yanchor: 'top',
        pad: { t: 50, b: 10 },
        currentvalue: {
            visible: true,  // 現在値を表示
            prefix: 'Time: ',  // 表示テキストのプレフィックス
            xanchor: 'right',
            font: { size: 16, color: '#666' }
        },
        transition: { duration: 0 }
    }],
    height: 700
};

重要なプロパティ:

  • active: 0 - 初期位置(インデックス)
  • steps - 各フレームへのジャンプ設定
  • label - スライダーのラベル(時刻等)
  • len: 0.8 - スライダーの長さ(画面幅の80%)
  • currentvalue - 現在値の表示設定

updatemenusとslidersの配置調整#

レイアウトの最適化#

複数のコントロールを配置する場合、重ならないように調整します。

const layout = {
    title: '3D軌道アニメーション',
    scene: {/* ... */},

    // ボタン類(下部)
    updatemenus: [
        {
            // 一時停止ボタン(左)
            buttons: [{/* ... */}],
            x: 0.1,
            y: 0,
            xanchor: 'left',
            yanchor: 'bottom'
        },
        {
            // 速度調整ボタン(中央)
            buttons: [{/* ... */}],
            x: 0.25,
            y: 0,
            xanchor: 'left',
            yanchor: 'bottom'
        }
    ],

    // スライダー(下部、ボタンの上)
    sliders: [{
        steps: [{/* ... */}],
        x: 0.1,
        len: 0.8,
        xanchor: 'left',
        y: 0.05,  // ボタンより少し上
        yanchor: 'bottom',
        pad: { t: 50, b: 10 }
    }],

    margin: { t: 50, b: 80, l: 50, r: 50 },  // マージン調整
    height: 700
};

配置のポイント:

  • ボタン: y: 0(最下部)
  • スライダー: y: 0.05(ボタンの少し上)
  • マージン: b: 80(下部に80pxの余白)

完全な実装例#

再生コントロール付きアニメーション#

// データとframesは既に作成済みと仮定
const timeData = [0.0, 0.1, 0.2, /* ... */];
const frames = [/* framesデータ */];

const layout = {
    title: '3D軌道アニメーション(再生コントロール付き)',
    scene: {
        xaxis: { title: 'X (m)' },
        yaxis: { title: 'Y (m)' },
        zaxis: { title: 'Z (m)' }
    },

    // 再生コントロールボタン
    updatemenus: [
        {
            type: 'buttons',
            showactive: false,
            buttons: [
                {
                    label: '▶ Play',
                    method: 'animate',
                    args: [null, {
                        frame: { duration: 50, redraw: true },
                        fromcurrent: true,
                        transition: { duration: 0 },
                        mode: 'immediate'
                    }]
                },
                {
                    label: '⏸ Pause',
                    method: 'animate',
                    args: [[null], {
                        frame: { duration: 0, redraw: false },
                        mode: 'immediate',
                        transition: { duration: 0 }
                    }]
                }
            ],
            x: 0.1,
            y: 0,
            xanchor: 'left',
            yanchor: 'bottom'
        }
    ],

    // シークバー
    sliders: [{
        active: 0,
        steps: frames.map((frame, idx) => ({
            label: `${timeData[idx].toFixed(1)}s`,
            method: 'animate',
            args: [[frame.name], {
                frame: { duration: 0, redraw: true },
                mode: 'immediate',
                transition: { duration: 0 }
            }]
        })),
        x: 0.1,
        len: 0.8,
        xanchor: 'left',
        y: 0.05,
        yanchor: 'bottom',
        pad: { t: 50, b: 10 },
        currentvalue: {
            visible: true,
            prefix: 'Time: ',
            xanchor: 'right',
            font: { size: 16, color: '#666' }
        }
    }],

    margin: { t: 50, b: 80, l: 50, r: 50 },
    height: 700
};

// グラフ描画
Plotly.newPlot('plot3d', [/* traces */], layout, { frames: frames });

よくある間違いと対処法#

間違い1: argsの第1引数の指定ミス#

// ❌ 悪い例: 再生ボタンのargsが間違っている
{
    label: '▶ Play',
    args: [[null], {...}]  // [null]は一時停止用
}

問題: [null]は一時停止用です。再生にはnull(配列なし)を指定します。

対策:

// ✅ 良い例: 再生ボタン
{
    label: '▶ Play',
    args: [null, {...}]  // nullで全フレーム再生
}

間違い2: スライダーのstepsが不足#

// ❌ 悪い例: stepsが空
sliders: [{
    steps: []  // 空の配列
}]

問題: stepsが空だと、スライダーが表示されません。

対策: framesと対応するstepsを作成します。


まとめ#

本記事では、Plotly.js updatemenusとslidersを使ったアニメーション再生コントロールの実装方法を解説しました。

重要なポイント:

  • updatemenusで再生/一時停止ボタンを追加
  • args: [null, {...}]で再生、args: [[null], {...}]で一時停止
  • slidersでシークバー(フレームジャンプ)を実装
  • steps配列でframesと対応させる
  • 配置調整で重ならないようにレイアウト

次のステップとして、サブプロットレイアウト(複数グラフの統合表示)の実装に挑戦してみましょう。


参照資料#

本記事の執筆にあたり、以下の資料を参照しました [@plotly_js_docs_animations_2025; @plotly_js_docs_custom_buttons_2025; @plotly_js_docs_sliders_2025]。