サーバーサイドレンダリング

通常、Apache EChartsTMはブラウザでチャートを動的にレンダリングし、ユーザーの操作後に再レンダリングします。ただし、サーバーサイドでチャートをレンダリングする必要がある特定のシナリオもあります。

  • FCP時間を短縮し、チャートがすぐに表示されるようにします。
  • スクリプトをサポートしていないMarkdown、PDFなどの環境にチャートを埋め込みます。

これらのシナリオでは、EChartsはSVGとCanvasの両方のサーバーサイドレンダリング(SSR)ソリューションを提供します。

ソリューション レンダリング結果 メリット
サーバーサイドSVGレンダリング SVG文字列 Canvas画像よりも小さい;
ベクターSVG画像はぼやけません;
初期アニメーションのサポート
サーバーサイドCanvasレンダリング 画像 画像形式は、より幅広いシナリオで使用でき、SVGをサポートしていないシナリオではオプションです。

一般に、サーバーサイドSVGレンダリングソリューションが推奨されます。SVGが適用できない場合は、Canvasレンダリングソリューションを検討できます。

サーバーサイドレンダリングにもいくつかの制限があり、特にインタラクションに関連する操作はサポートできません。そのため、インタラクションの要件がある場合は、以下の「ハイドレーションを使用したサーバーサイドレンダリング」を参照してください。

サーバーサイドレンダリング

サーバーサイドSVGレンダリング

バージョン更新

  • 5.3.0: 依存関係のない新しいサーバーサイド文字列ベースのSVGレンダリングソリューションと、初期アニメーションのサポートが導入されました。
  • 5.5.0: 軽量クライアントランタイムが追加され、クライアントサイドでECharts全体を読み込むことなく、一部のインタラクションが可能になりました。

5.3.0で、依存関係のない新しいサーバーサイド文字列ベースのSVGレンダリングソリューションを導入しました。

// Server-side code
const echarts = require('echarts');

// In SSR mode the first container parameter is not required
let chart = echarts.init(null, null, {
  renderer: 'svg', // must use SVG rendering mode
  ssr: true, // enable SSR
  width: 400, // need to specify height and width
  height: 300
});

// use setOption as normal
chart.setOption({
  //...
});

// Output a string
const svgStr = chart.renderToSVGString();

// If chart is no longer useful, consider disposing it to release memory.
chart.dispose();
chart = null;

コードの全体的な構造はブラウザの場合とほぼ同じで、`init`でチャートの初期化を行い、`setOption`でチャートの設定を行います。ただし、`init`に渡されるパラメータは、ブラウザで使用されるパラメータとは異なります。

  • まず、サーバーサイドでレンダリングされるSVGは文字列ベースであるため、レンダリングされたコンテンツを表示するためのコンテナは必要ありません。そのため、`init`の最初のパラメータである`container`には`null`または`undefined`を渡すことができます。
  • 次に、`init`の3番目のパラメータで、`ssr: true`を指定することにより、サーバーサイドレンダリングモードを有効にする必要があることをEChartsに指示する必要があります。 これにより、EChartsはアニメーションループとイベントモジュールを無効にする必要があることを認識します。
  • また、チャートの`height`と`width`も指定する必要があります。そのため、チャートのサイズがコンテナに合わせて変更される必要がある場合は、サーバーサイドレンダリングがシナリオに適しているかどうかを検討する必要がある場合があります。

ブラウザでは、EChartsは`setOption`後に結果をページに自動的にレンダリングし、各フレームで再描画が必要なアニメーションがあるかどうかを判断しますが、Node.jsでは`ssr: true`を設定した後、これは行いません。 代わりに、`renderToSVGString`を使用して現在のチャートをSVG文字列にレンダリングし、HTTPレスポンスを介してフロントエンドに返送したり、ローカルファイルに保存したりできます。

ブラウザへのレスポンス (Express.jsを例として使用)

res.writeHead(200, {
  'Content-Type': 'application/xml'
});
res.write(svgStr); // svgStr is the result of chart.renderToSVGString()
res.end();

または、ローカルファイルに保存

fs.writeFile('bar.svg', svgStr, 'utf-8');

サーバーサイドレンダリングにおけるアニメーション

上記の例でわかるように、サーバーサイドレンダリングを使用する場合でも、EChartsはアニメーション効果を提供できます。これは、出力SVG文字列にCSSアニメーションを埋め込むことによって実現されます。 アニメーションを再生するために追加のJavaScriptは必要ありません。

ただし、CSSアニメーションの制限により、サーバーサイドレンダリングでは、棒グラフレーシングアニメーション、ラベルアニメーション、`lines`シリーズの特殊効果アニメーションなど、より柔軟なアニメーションを実装できません。 `pie`などの一部のシリーズのアニメーションは、サーバーサイドレンダリング用に特別に最適化されています。

このアニメーションが不要な場合は、`setOption`時に`animation: false`を設定することでオフにすることができます。

setOption({
  animation: false
});

サーバーサイドCanvasレンダリング

出力がSVG文字列ではなく画像である場合、または古いバージョンを使用している場合は、サーバーサイドレンダリングにnode-canvasを使用することをお勧めします。node-canvasは、ブラウザのCanvasとほぼ同じインターフェースを提供するNode.jsのCanvas実装です。

簡単な例を次に示します。

var echarts = require('echarts');
const { createCanvas } = require('canvas');

// In versions earlier than 5.3.0, you had to register the canvas factory with setCanvasCreator.
// Not necessary since 5.3.0
echarts.setCanvasCreator(() => {
  return createCanvas();
});

const canvas = createCanvas(800, 600);
// ECharts can use the Canvas instance created by node-canvas as a container directly
let chart = echarts.init(canvas);

// setOption as normal
chart.setOption({
  //...
});

const buffer = renderChart().toBuffer('image/png');

// If chart is no longer useful, consider disposing it to release memory.
chart.dispose();
chart = null;

// Output the PNG image via Response
res.writeHead(200, {
  'Content-Type': 'image/png'
});
res.write(buffer);
res.end();

画像の読み込み

node-canvasは、画像の読み込みのための`Image`実装を提供します。コードで画像を使用する場合は、`5.3.0`で導入された`setPlatformAPI`インターフェースを使用して適応させることができます。

echarts.setPlatformAPI({
  // Same with the old setCanvasCreator
  createCanvas() {
    return createCanvas();
  },
  loadImage(src, onload, onerror) {
    const img = new Image();
    // must be bound to this context.
    img.onload = onload.bind(img);
    img.onerror = onerror.bind(img);
    img.src = src;
    return img;
  }
});

リモートの画像を使用している場合は、レンダリング時に画像が読み込まれるように、`base64`を取得するためにhttpリクエストを介して画像をプリフェッチしてから、画像のURLとして渡すことをお勧めします。

クライアントハイドレーション

EChartsの遅延読み込み

最新バージョンのEChartsでは、サーバーサイドレンダリングソリューションは、チャートのレンダリングに加えて、次のことができます。

  • 初期アニメーションのサポート(つまり、チャートが最初にレンダリングされるときに再生されるアニメーション)
  • ハイライトスタイル(つまり、棒グラフでマウスを棒の上に移動したときのハイライト効果)

ただし、サーバーサイドレンダリングではサポートできない機能があります。

  • 動的に変化するデータ
  • 凡例をクリックして、シリーズを表示するかどうかを切り替える
  • マウスを移動してツールチップを表示する
  • その他のインタラクション関連機能

このような要件がある場合は、サーバーサイドレンダリングを使用して最初の画面チャートをすばやく出力し、`echarts.js`の読み込みが完了するのを待ってから、クライアントサイドで同じチャートを再レンダリングすることを検討できます。 これにより、通常のインタラクション効果を実現し、データを動的に変更できます。クライアントサイドでレンダリングする場合は、`tooltip: { show: true }`などのインタラクティブコンポーネントをオンにし、`animation: 0`で初期アニメーションをオフにする必要があります(初期アニメーションは、サーバーサイドでレンダリングされた結果のSVGアニメーションによって行われる必要があります)。

ご覧のとおり、ユーザーエクスペリエンスの観点からは、二次レンダリングプロセスはほとんどなく、切り替え効果全体が非常にシームレスです。 上記の例のように、`echarts.js`の読み込み中にpace-jsなどのライブラリを使用して読み込みプログレスバーを表示し、EChartsが完全に読み込まれる前にインタラクティブなフィードバックがないという問題を解決することもできます。

クライアントサイドレンダリングとともにサーバーサイドレンダリングを使用し、クライアントサイドで`echarts.js`を遅延読み込みすることは、最初の画面をすばやくレンダリングしてからインタラクションをサポートする必要があるシナリオに適したソリューションです。 ただし、`echarts.js`を読み込むには時間がかかり、完全に読み込まれるまでインタラクティブなフィードバックはありません。その場合、ユーザーに「読み込み中」のテキストが表示される場合があります。 これは、最初の画面をすばやくレンダリングしてからインタラクションをサポートする必要があるシナリオに一般的に推奨されるソリューションです。

軽量クライアントランタイム

ソリューションAは、完全なインタラクションを実装する方法を提供しますが、シナリオによっては、複雑なインタラクションは必要ありません。凡例をクリックしてシリーズを表示するかどうかを切り替えるなど、サーバーサイドレンダリングに基づいてクライアントサイドでいくつかの簡単なインタラクションを実行できるようにしたいだけです。 この場合、クライアントサイドで少なくとも数百KBのEChartsコードを読み込むことを回避できますか?

バージョンv5.5.0以降、チャートに必要な効果とインタラクションが以下の場合、サーバーサイドSVGレンダリング+クライアントサイド軽量ランタイムで実現できます。

  • 初期チャートアニメーション(実装の原則:サーバーによってレンダリングされたSVGにはCSSアニメーションが付属しています)
  • ハイライトスタイル(実装の原則:サーバーによってレンダリングされたSVGにはCSSアニメーションが付属しています)
  • 動的に変化するデータ(実装の原則:軽量ランタイムはサーバーに二次レンダリングを要求します)
  • 凡例をクリックして、シリーズを表示するかどうかを切り替えます(実装の原則:軽量ランタイムはサーバーに二次レンダリングを要求します)
<div id="chart-container" style="width:800px;height:600px"></div>

<script src="https://cdn.jsdelivr.net/npm/echarts/ssr/client/dist/index.min.js"></script>
<script>
const ssrClient = window['echarts-ssr-client'];

let isSeriesShown = {
  a: true,
  b: true
};

function updateChart(svgStr) {
  const container = document.getElementById('chart-container');
  container.innerHTML = svgStr;

  // Use the lightweight runtime to give the chart interactive capabilities
  ssrClient.hydrate(main, {
    on: {
      click: (params) => {
        if (params.ssrType === 'legend') {
          // Click the legend element, request the server for secondary rendering
          isSeriesShown[params.seriesName] = !isSeriesShown[params.seriesName];
          fetch('...?series=' + JSON.stringify(isSeriesShown))
            .then(res => res.text())
            .then(svgStr => {
              updateChart(svgStr);
            });
        }
      }
    }
  });
}

// Get the SVG string rendered by the server through an AJAX request
fetch('...')
  .then(res => res.text())
  .then(svgStr => {
    updateChart(svgStr);
  });
</script>

サーバーサイドは、各シリーズが表示されるかどうか(`isSeriesShown`)についてクライアントから渡された情報に基づいて二次レンダリングを実行し、新しいSVG文字列を返します。サーバーサイドコードは上記と同じなので、繰り返しません。

状態の記録について:純粋なクライアントサイドレンダリングと比較して、開発者はいくつかの追加情報を記録および維持する必要があります(たとえば、この例では各シリーズが表示されるかどうか)。これは、HTTPリクエストがステートレスであるため避けられません。状態を実装する場合は、クライアントが状態を記録して上記の例のように渡すか、サーバーが状態を保持します(たとえば、セッションを介しますが、より多くのサーバーメモリとより複雑な破棄ロジックが必要になるため、お勧めしません)。

サーバーサイドSVGレンダリングとクライアントサイド軽量ランタイムを併用する利点は、クライアント側で数百KBのEChartsコードを読み込む必要がなくなり、4KB未満の軽量ランタイムコードを読み込むだけで済むことです。また、ユーザーエクスペリエンスからは、ほとんど犠牲にするものはありません(初期アニメーション、マウスハイライトをサポート)。欠点は、追加の状態情報を維持するために一定の開発コストが必要であり、リアルタイム性の高いインタラクション(マウス移動時のツールチップ表示など)をサポートしていないことです。全体的に、**コード量に非常に厳しい要件がある環境での使用を推奨します**。

軽量ランタイムの使用

クライアントサイドの軽量ランタイムは、サーバーサイドでレンダリングされたSVGチャートの内容を理解することで、インタラクションを可能にします。

クライアントサイドの軽量ランタイムは、以下の方法でインポートできます。

<!-- Method one: Using CDN -->
<script src="https://cdn.jsdelivr.net/npm/echarts/ssr/client/dist/index.min.js"></script>
<!-- Method two: Using NPM -->
<script src="node_modules/echarts/ssr/client/dist/index.js"></script>

API

グローバル変数 `window['echarts-ssr-client']` には以下のAPIが提供されています。

hydrate(dom: HTMLElement, options: ECSSRClientOptions)

  • `dom`:チャートコンテナ。このメソッドを呼び出す前に、サーバーサイドでレンダリングされたSVGチャートとしてコンテンツを設定する必要があります。
  • `options`:設定項目
ECSSRClientOptions
on?: {
  mouseover?: (params: ECSSRClientEventParams) => void,
  mouseout?: (params: ECSSRClientEventParams) => void,
  click?: (params: ECSSRClientEventParams) => void
}

チャートのマウスイベントと同様に、ここでのイベントはチャートコンテナではなく、チャートアイテム(例:棒グラフの棒、折れ線グラフのデータアイテムなど)に対するものです。

ECSSRClientEventParams
{
  type: 'mouseover' | 'mouseout' | 'click';
  ssrType: 'legend' | 'chart';
  seriesIndex?: number;
  dataIndex?: number;
  event: Event;
}
  • `type`:イベントタイプ
  • `ssrType`:イベントオブジェクトタイプ、`legend` は凡例データ、`chart` はチャートデータオブジェクトを表します。
  • `seriesIndex`:シリーズインデックス
  • `dataIndex`:データインデックス
  • `event`:ネイティブイベントオブジェクト

上記の「軽量クライアントランタイム」セクションを参照してください。

まとめ

上記では、いくつかの異なるレンダリングソリューションを紹介しました。以下はその内容です。

  • クライアントサイドレンダリング
  • サーバーサイドSVGレンダリング
  • サーバーサイドCanvasレンダリング
  • クライアントサイド軽量ランタイムレンダリング

これらの4つのレンダリング方法は組み合わせて使用できます。それぞれの適用シナリオをまとめます。

レンダリングソリューション 読み込み量 機能とインタラクションの損失 相対的な開発作業量 推奨シナリオ
クライアントサイドレンダリング 最大 なし 最小 最初の画面の読み込み時間に敏感ではなく、完全な機能とインタラクションの要求が高い
クライアントサイドレンダリング(部分パッケージのオンデマンドインポート 大:含まれていないパッケージは対応する機能を使用できません 最初の画面の読み込み時間に敏感ではなく、コード量に厳密な要件はないが可能な限り小さくしたい、ECharts機能のごく一部のみを使用する、サーバーリソースがない
ワンタイムサーバーサイドSVGレンダリング 大:データを動的に変更できない、凡例切り替えシリーズ表示をサポートしない、ツールチップなどのリアルタイム性の高いインタラクションをサポートしない 最初の画面の読み込み時間に敏感、完全な機能とインタラクションの要求が低い
ワンタイムサーバーサイドCanvasレンダリング 最大:上記と同じで、初期アニメーションをサポートせず、画像容量が大きく、拡大するとぼやける 最初の画面の読み込み時間に敏感、完全な機能とインタラクションの要求が低い、プラットフォームの制限によりSVGを使用できない
サーバーサイドSVGレンダリングとクライアントサイドECharts遅延読み込み 小、その後大 中:遅延読み込みが完了するまでインタラクションできない 最初の画面の読み込み時間に敏感、完全な機能とインタラクションの要求が高い、読み込み直後にチャートをインタラクションする必要がないことが望ましい
サーバーサイドSVGレンダリングとクライアントサイド軽量ランタイム 中:リアルタイム性の高いインタラクションを実装できない 大(チャートの状態を維持し、クライアントサーバーインターフェースプロトコルを定義する必要がある) 最初の画面の読み込み時間に敏感、完全な機能とインタラクションの要求が低い、コード量に非常に厳しい要件がある、インタラクションのリアルタイム性に厳密な要件がない
サーバーサイドSVGレンダリングとクライアントサイドECharts遅延読み込み、遅延読み込みが完了するまで軽量ランタイムを使用する 小、その後大 小:遅延読み込みが完了するまで複雑なインタラクションを実行できない 最大 最初の画面の読み込み時間に敏感、完全な機能とインタラクションの要求が高い、十分な開発時間がある

もちろん、他にもいくつかの組み合わせの可能性がありますが、最も一般的なのは上記です。これらのレンダリングソリューションの特性を理解すれば、自分のシナリオに基づいて適切なソリューションを選択できると信じています。

貢献者 GitHubでこのページを編集する

(貢献者情報はそのまま)