この記事で学べること#

  • Plotly.js Frames APIの基本構造
  • フレームデータの作成方法
  • フレームスキップによるパフォーマンス最適化(目標300フレーム)
  • 静的トレースと動的トレースの使い分け
  • アニメーションの基本設定

対象読者#

  • D-3「Plotly.jsで3D軌道プロットを作成する」を読んだ方
  • 時系列データをアニメーション表示したい方
  • Plotly.js Frames APIの情報を探している方

前回の記事では、静的な3Dプロットの作成方法を解説しました。本記事では、Frames APIを使って時系列データを動的に表示するアニメーションの実装方法を学びます。


Frames APIとは?#

時系列データのアニメーション機能#

Plotly.js Frames APIは、複数のフレーム(コマ)を定義してアニメーションを作成する機能です。各フレームには、そ時点でのデータ状態を定義し、順次切り替えることで動的な可視化を実現します。

使用例#

  • シミュレーション結果の時間経過表示
  • 航空機の軌道追跡
  • 気象データの時系列変化
  • パラメータの変化を視覚的に表現

Frames APIの基本構造#

framesとは?#

framesは、各時点でのグラフの状態を定義するオブジェクトの配列です。

const frames = [
    {
        name: 'frame0',  // フレーム名(一意な識別子)
        data: [/* この時点でのtraceデータ */]
    },
    {
        name: 'frame1',
        data: [/* 次の時点でのtraceデータ */]
    },
    // ...
];

重要なポイント:

  • 各フレームはnameプロパティで識別されます
  • dataプロパティには、traceデータの配列を指定します
  • フレーム間で変化する部分のみを定義します

基本的なアニメーション実装例#

ステップ1: データの準備#

// シミュレーションデータ(時系列)
const time = [0.0, 0.1, 0.2, 0.3, 0.4];
const xs = [0, 1.5, 3.2, 5.1, 7.3];
const ys = [0, 0.5, 1.1, 1.8, 2.6];
const zs = [100, 101, 102, 103, 104];

ステップ2: 静的トレースの定義#

まず、軌道全体を表示する静的なトレースを定義します。

// Trace 0: 軌道全体(静的 - 常に表示)
const trace3dStatic = {
    x: xs,  // 全データ
    y: ys,
    z: zs,
    type: 'scatter3d',
    mode: 'lines',  // 線のみ
    name: '軌道',
    line: {
        color: 'lightgray',
        width: 2
    }
};

ステップ3: 動的トレースの初期状態#

次に、現在位置を示すマーカーを動的トレースとして定義します。

// Trace 1: 現在位置マーカー(動的 - フレームで更新)
const trace3dMarker = {
    x: [xs[0]],  // 初期位置(1点のみ)
    y: [ys[0]],
    z: [zs[0]],
    type: 'scatter3d',
    mode: 'markers',  // マーカーのみ
    name: '現在位置',
    marker: {
        size: 10,
        color: 'red'
    }
};

ステップ4: framesの作成#

各時点でのマーカー位置を定義したframesを作成します。

// frames配列を作成
const frames = [];
for (let i = 0; i < time.length; i++) {
    frames.push({
        name: `frame${i}`,  // ユニークな名前
        data: [
            // Trace 0は静的なので省略(変化しない)

            // Trace 1: 現在位置マーカーのみ更新
            {
                x: [xs[i]],  // この時点の位置
                y: [ys[i]],
                z: [zs[i]]
            }
        ]
    });
}

重要: framesのdata配列は、更新するtraceの順番に対応します。Trace 0(静的)は省略し、Trace 1(動的)のみを更新します。

ステップ5: layout設定#

const layout = {
    title: '3D軌道アニメーション',
    scene: {
        xaxis: { title: 'X (m)' },
        yaxis: { title: 'Y (m)' },
        zaxis: { title: 'Z (m)' }
    },
    height: 700
};

ステップ6: グラフの描画#

// Plotly.newPlot()でグラフとframesを渡す
Plotly.newPlot(
    'plot3d',  // div要素のID
    [trace3dStatic, trace3dMarker],  // traceデータ配列
    layout,  // layout
    { frames: frames }  // configオブジェクト内にframesを指定
);

注意: framesは、configオブジェクトのプロパティとして渡します。


フレームスキップ最適化#

なぜ最適化が必要か?#

シミュレーションデータは数千~数万点になることがあります。すべてのデータポイントをフレームにすると、以下の問題が発生します。

  • パフォーマンス低下: ブラウザが重くなる
  • メモリ消費: 大量のframesオブジェクトでメモリを圧迫
  • 再生速度: フレーム数が多すぎると再生が遅い

目標フレーム数の設定#

一般的に、100~300フレーム程度が適切です。滑らかな動きを維持しつつ、パフォーマンスを確保できます。

// データポイント数
const totalPoints = xs.length;  // 例: 3000点

// 目標フレーム数
const targetFrames = 300;

// スキップ間隔を計算
const skipInterval = Math.max(1, Math.floor(totalPoints / targetFrames));
// 例: 3000 / 300 = 10 → 10点ごとに1フレーム作成

フレームスキップを適用#

const frames = [];
for (let i = 0; i < totalPoints; i += skipInterval) {
    frames.push({
        name: `frame${i}`,
        data: [
            {
                x: [xs[i]],
                y: [ys[i]],
                z: [zs[i]]
            }
        ]
    });
}

結果: 3000点 → 300フレーム(10点ごとに1フレーム)


静的トレースと動的トレースの使い分け#

静的トレース(Static Traces)#

軌道全体を常に表示する場合、静的トレースを使用します。

// 軌道全体(静的)
const trace3dStatic = {
    x: xs,  // 全データ
    y: ys,
    z: zs,
    type: 'scatter3d',
    mode: 'lines',
    name: '軌道'
};

framesでは更新しないため、data配列に含めません。

動的トレース(Animated Traces)#

現在位置マーカーのように、フレームごとに変化する場合、動的トレースを使用します。

// 現在位置マーカー(動的)
const trace3dMarker = {
    x: [xs[0]],  // 初期位置
    y: [ys[0]],
    z: [zs[0]],
    type: 'scatter3d',
    mode: 'markers',
    name: '現在位置'
};

framesで毎回更新します。


アニメーションの再生方法#

自動再生#

Plotly.newPlot()実行後、自動的にアニメーションを再生するには、Plotly.animate()を呼び出します。

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

// 自動再生開始
Plotly.animate('plot3d', null, {
    frame: {
        duration: 50  // 各フレームの表示時間(ミリ秒)
    },
    transition: {
        duration: 0  // フレーム間の遷移時間(0で即切り替え)
    },
    mode: 'afterall'  // 全フレームを順次再生
});

設定項目:

  • duration: 各フレームの表示時間(ms)
  • transition.duration: フレーム間の遷移時間(ms)
  • mode: 再生モード('immediate', 'next', 'afterall'

複数トレースのアニメーション#

複数の動的トレースを更新#

複数のトレースをアニメーションさせる場合、framesのdata配列に対応する順番でトレースデータを指定します。

// Trace 0: 軌道(静的)
// Trace 1: 現在位置マーカー(動的)
// Trace 2: 速度ベクトル(動的)

const frames = [];
for (let i = 0; i < totalPoints; i += skipInterval) {
    frames.push({
        name: `frame${i}`,
        data: [
            // Trace 0(静的)は省略

            // Trace 1: 現在位置
            {
                x: [xs[i]],
                y: [ys[i]],
                z: [zs[i]]
            },
            // Trace 2: 速度ベクトル
            {
                x: [xs[i], xs[i] + vx[i]],
                y: [ys[i], ys[i] + vy[i]],
                z: [zs[i], zs[i] + vz[i]]
            }
        ]
    });
}

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

間違い1: すべてのtraceをframesに含める#

// ❌ 悪い例: 静的トレースも毎回更新
frames.push({
    name: `frame${i}`,
    data: [
        { x: xs, y: ys, z: zs },  // 軌道全体(不要な更新)
        { x: [xs[i]], y: [ys[i]], z: [zs[i]] }  // 現在位置
    ]
});

問題: 静的トレースを毎回更新するため、パフォーマンスが低下します。

対策: 静的トレースはframesに含めず、動的トレースのみ更新します。

間違い2: フレーム数が多すぎる#

// ❌ 悪い例: 全データポイントをフレーム化(3000フレーム)
for (let i = 0; i < xs.length; i++) {
    frames.push({...});
}

問題: ブラウザが重くなり、メモリを大量に消費します。

対策: フレームスキップで目標フレーム数(100~300)に抑えます。


まとめ#

本記事では、Plotly.js Frames APIの基本的な使い方を解説しました。

重要なポイント:

  • framesは各時点の状態を定義するオブジェクトの配列
  • 静的トレースと動的トレースを使い分ける
  • フレームスキップで目標300フレーム程度に最適化
  • Plotly.newPlot()のconfig引数でframesを渡す
  • Plotly.animate()で再生開始

次のステップとして、updatemenusとslidersを使ったアニメーション再生コントロール(再生/一時停止ボタン、シークバー)の実装に挑戦してみましょう。


参照資料#

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