【結論】
大量データを扱う場合は、まず「表示・描画の処理を最小限に留める」設計が鍵です。たとえば、
- 描画イベントや複雑なロジックを極力バッチ化・キャッシュ化する
- データ量によって dataLabel 表示・非表示を切り替える
- サンプリングや集約、仮想化の手法を取り入れる
- Wijmo 側の描画イベント・API を正しく使い分けて負荷を分散する
これらを組み合わせることで、Scatter Chart 全体のパフォーマンスを大幅に向上できます。
以下、ステップバイステップで詳しく解説します。
1. DataLabel の描画処理を高速化する改善策
-
複雑な演算・判定のオフロード・キャッシュ化
- DataLabel の描画設定 (position, font, color など) は、基本的に表示する直前に呼び出されるため、ロジックが重いと描画ごとに大きな負荷がかかります。
- 判定ロジックや filter 処理を都度行わず、事前に集約やキャッシュを行うことが重要です。
- 例えば、データ数が 1,200,000 件あっても、「dataLabel を表示するかどうか」の判定結果だけを一度生成し、描画時にはその結果を参照するだけにする方式が効果的です。
-
データ件数による切り替え
- 質問文にあるように「filter にかけたあとの length が 32 以上であれば dataLabel 非表示」にするような制御は、有効ですが、これを 描画段階で毎回判定するのではなく、データ読み込み時点または レポート再生成時点で「すでに dataLabel を表示しないフラグを立てておく」ようにすると、描画プロセスでの負荷が減少します。
- また、一定数以上のデータがある場合はそもそも dataLabel を一括で無効化するという大胆なアプローチも検討する価値があります。
-
非同期処理・Web Worker の活用(オフロード)
- JavaScript のメインスレッドで大規模データの処理を行うと UI ブロックが発生します。
- フィルタリングや集約の前処理を Web Worker (もしくは類似の非同期処理) にオフロードし、Chart 側では最適化されたデータのみを扱うようにすると、体感速度が大きく向上します。
-
アルゴリズムの見直し(バッチ処理)
- DataLabel 用のスタイル決定や位置計算をループ単位でまとめる (つまり、データ点ごとに小さい if 文を何十回も実行するのではなく、一括処理でまとめる) 工夫も効果的です。
- 繰り返しの条件判定を減らし、最小限のブロックでまとめる方法を検討してください。
2. 複雑な判定ロジックを最適化するベストプラクティス
-
キャッシュの導入
- 「過去の判定結果」をキャッシュしておき、同じロジックの繰り返しを避ける。
- 大量データがあると、同じような条件分岐が何度も発生しがちなので、キー(例: データIDやcategory) を元にした判定結果をMap/オブジェクトに保存し、再利用するだけでも高速化します。
-
必要最小限の演算だけに絞る
- 描画のタイミングでは「最終的に表示すべきかどうか」以外の複雑な計算を極力しないようにし、集計・フィルタ・判定は描画前に終わらせておくのが基本です。
-
描画前後のバッチ処理
- Wijmo のレンダリングフローで「描画前に一括で判定・準備しておく」「描画後にまとめて後処理する」ことを設計する。
- たとえば
rendering
イベント内で複雑な処理をしないようにし、必要であれば chart 生成前後で済ませるか、rendered
イベント後にバッチで後処理する。
-
メモ化 (Memoization)
- 判定ロジックの中で同じ入力に対して常に同じ結果になるものは、関数レベルでメモ化するとよいでしょう (例: lodash.memoize の利用など)。
3. Wijmo の描画イベント(rendering, rendered, itemFormatter など) の使い分け・活用方法
-
rendering
- Chart が実際に描画を行う直前に呼ばれるイベント。
- 「マウス操作イベントの登録」や「描画前にやっておきたい軽い準備」程度に留め、複雑な処理は避けるのが無難です。
- フレーム単位で何度も呼ばれる可能性があるため、重い処理はここに入れるとパフォーマンスを下げやすいです。
-
rendered
- 描画が終わったタイミングで呼ばれるイベント。
- もし描画後に DOM 要素を操作したり、ラベルの再配置が必要な場合はここで行います。
- ただし、多数データを扱う場合、この後に再度レンダリングが起きると二重負荷になるため、処理が頻発しないように工夫が必要です。
-
itemFormatter
- 個別のデータポイントを描画する際にフォーマットをカスタマイズできる機能。
- データポイントごとに実行されるため、複雑なロジックや重い処理をここで行うと、描画全体のパフォーマンスが著しく低下します。
- itemFormatter で行う処理はあくまで最小限にし、事前に整形済みの状態で値を渡すなど、工夫が必要です。
-
代替策:シンプルな binding 系設定
- 項目ごとの表示・非表示などは、できる限り 単純な binding や series の設定として行い、JavaScript コード内でのゴリゴリの制御を減らすとよいです。
4. 非常に多いデータに対する scatter chart の軽量化アプローチ
-
サンプリング
- 1,200,000 件すべてをプロットしようとすると描画負荷が桁違いに大きくなります。
- グラフの可視化では、通常そこまで高密度で描いても視認性が低いので、適切なサンプリングや集計(例: binning, ダウンサンプリング) を導入してください。
- 例えば、画面上のピクセル数に応じて、最大でも数千点程度に間引くのが一般的です。
-
レイヤー分割
- データラベルを表示するレイヤーと、実際の散布点を表示するレイヤーを分割する (またはそもそも dataLabel を大量描画しない) アプローチも考えられます。
- Wijmo で直接レイヤー分割する方法は限られますが、たとえば背景だけ Wijmo で描画し、オーバーレイレイヤーを別途 SVG や Canvas で管理するケースもあります。
-
仮想スクロール (Virtualization)
- もし表示領域が限られているなら、見えている範囲だけポイントを描画し、スクロール操作に応じて動的にデータを切り替える仕組みを考えることができます。
- これは Wijmo の標準機能ではないかもしれませんが、カスタムで実装することで体感速度を上げられます。
-
アニメーションの無効化
- Chart のアニメーションは見た目は良いですが、データ数が多いとパフォーマンスへの影響が大きくなります。
- Wijmo の Chart でアニメーションを無効化するには、
chart.animation = null;
(または animation の duration を 0 に設定) といった手段が考えられます。
5. 具体的なサンプルコードや、改善に役立つ設定オプション
ここでは、Wijmo 最新版 (5.x 系統を想定) の Scatter Chart を例に、主要な設定を示します。
// 例: サンプリング済み or フィルタ済みデータを用意する
const rawData = getHugeData(); // 1,200,000件
const optimizedData = sampleOrAggregateData(rawData);
// ↑ ここで一括処理を行い、表示用に数千点ほどに絞る
// Chart の生成
let chart = new wijmo.chart.FlexChart('#theChart', {
chartType: wijmo.chart.ChartType.Scatter,
bindingX: 'x',
series: [
{ binding: 'y', name: 'Series1' },
],
itemsSource: optimizedData,
// アニメーションを無効化
animation: null, // or new wijmo.chart.ChartAnimation(null, { duration: 0 })
// dataLabel 設定
dataLabel: {
// 大量データの場合はそもそもOFF
content: (ht) => {
// ここは必要最小限のロジックに留める
// 例: 条件に合致する場合だけ label を表示
if (shouldShowLabel(ht.item)) {
return ht.item.label;
}
return null; // null で非表示
},
position: wijmo.chart.LabelPosition.Top
},
// カスタムフォーマットが必要なら itemFormatter を使う
itemFormatter: (engine, hitTestInfo, defaultFormat) => {
// データラベルの細かい装飾だけここで行う
// 複雑なfilterや計算は事前に済ませておく
defaultFormat();
}
});
// rendering イベントで重いロジックをしない
chart.rendering.addHandler((sender, args) => {
// 軽い操作だけ
console.log('Chart rendering...');
});
// rendered イベント
chart.rendered.addHandler((sender, args) => {
// レンダリング後の後処理
console.log('Chart rendered!');
});
/**
* 例: ラベル表示判定をキャッシュする関数
*/
const labelCache = new Map();
function shouldShowLabel(item) {
if (labelCache.has(item.id)) {
return labelCache.get(item.id);
}
const result = ( /* ここに軽量な判定やキャッシュ済みフラグを参照 */ );
labelCache.set(item.id, result);
return result;
}
-
サンプリングや集約:
sampleOrAggregateData
のようなカスタム関数で、画面幅に応じた数に絞るか、あるいは統計的に代表値を計算する手段を使ってください。 -
アニメーション無効化:
animation
プロパティをnull
にするか、new wijmo.chart.ChartAnimation(chart, { duration: 0 })
などを設定して無効化してください。 - itemFormatter: 本当に最小限の装飾のみに留め、複雑な処理はやらない。
一般的なパフォーマンスチューニングの観点
-
DOM 操作を最小化
- Chart は内部で SVG (または Canvas) を描画しますが、過剰な DOM 生成・削除を繰り返すとパフォーマンスが低下します。
- label 表示の仕組みは DOM ノードを大量生成する可能性があるため、大規模データ時は注意。
-
再描画回数を減らす
- データ更新や画面サイズ変更などが頻発すると、そのたびに
render()
が走り、イベントも呼ばれます。 - 連続的な更新は debounce/throttle を使って回数を削減しましょう。
- データ更新や画面サイズ変更などが頻発すると、そのたびに
-
ビジュアル・インタラクションの要件を再検討
- 一度に 1,200,000 件の散布図を描いてもユーザが読み取れないケースが多々あります。
- まず上流の設計として、ユーザが本当に必要な粒度は何かを再確認し、適切な規模で見せることが、本質的なパフォーマンス改善につながります。
【まとめ】
- 大量データ時はサンプリングや非表示化など、まず描画負荷を下げる方向にシフトし、残ったデータに対しても dataLabel の判定ロジックを事前に済ませてキャッシュするアーキテクチャが望ましい。
- rendering/rendered/itemFormatter を使い分け、重い処理は描画前に一括実行。
- Wijmo のアニメーションは無効化し、必要な箇所だけデザインをカスタマイズする。
- これらを組み合わせることで、チャート描画のスムーズさを確保できるはずです。