Skip to content

データ計算

Graphic Walkerは2つの計算モードをサポートしています: クライアント側(デフォルト)とサーバー側。データセットのサイズとアーキテクチャ要件に基づいて選択してください。

クライアント側計算

dataプロパティを渡すと、Graphic Walkerはクライアントのウェブワーカーですべての計算を実行します。これは最もシンプルな設定で、バックエンドは不要です。

<GraphicWalker data={myData} fields={fields} />

利点:

  • サーバー側のセットアップが不要
  • オフラインで動作
  • 即座のインタラクション

制限事項:

  • データセットがブラウザのメモリに収まる必要がある
  • すべてのデータをクライアントに初期転送
  • パフォーマンスはクライアントのハードウェアに依存

推奨: 100K行未満のデータセット。

DuckDB WASM(オプション)

大規模データセットでのクライアント側パフォーマンスを向上させるために、Graphic WalkerはDuckDB WASMを使用できます。オプションパッケージをインストールしてください:

npm install @kanaries/graphic-walker-duckdb

これにより、ブラウザでのSQLベースの集計が有効になり、大規模データセットで大幅に高速化されます。

サーバー側計算

大規模データセットやデータがサーバーから離れることができない場合は、dataの代わりにcomputation関数を渡します。Graphic Walkerはクエリペイロードを関数に送信し、結果を返します。

import { GraphicWalker } from '@kanaries/graphic-walker';
import type { IComputationFunction } from '@kanaries/graphic-walker';
 
const computation: IComputationFunction = async (payload) => {
  const response = await fetch('/api/data/query', {
    method: 'POST',
    body: JSON.stringify(payload),
    headers: { 'Content-Type': 'application/json' },
  });
  return response.json();
};
 
function App() {
  return (
    <GraphicWalker
      computation={computation}
      fields={fields}
    />
  );
}

利点:

  • 任意のサイズのデータセットを処理
  • データはサーバーに留まる
  • サーバー側のデータベース(PostgreSQL、DuckDBなど)を活用

制限事項:

  • クエリエンドポイントの実装が必要
  • ネットワーク遅延がインタラクション速度に影響

計算関数のシグネチャ

type IComputationFunction = (payload: IDataQueryPayload) => Promise<IRow[]>;

関数はIDataQueryPayloadを受け取ります。これは処理ステップのパイプラインであるworkflow配列を含みます:

interface IDataQueryPayload {
  workflow: IDataQueryWorkflowStep[];
  limit?: number;
  offset?: number;
}

ワークフローステップ

ワークフローは順序付けられたステップの配列です。サーバーはこれらを順番に処理します:

1. フィルターステップ

集計前に行レベルのフィルターを適用します:

{
  "type": "filter",
  "filters": [
    {
      "fid": "country",
      "rule": { "type": "one of", "value": ["US", "UK", "DE"] }
    },
    {
      "fid": "revenue",
      "rule": { "type": "range", "value": [1000, null] }
    }
  ]
}

2. 変換ステップ

派生フィールドを計算します:

{
  "type": "transform",
  "transform": [
    {
      "key": "log_revenue",
      "expression": {
        "op": "log10",
        "params": [{ "type": "field", "value": "revenue" }],
        "as": "log_revenue"
      }
    }
  ]
}

3. ビューステップ

データの集計または選択を行います。これは最も一般的なステップです:

集計クエリ:

{
  "type": "view",
  "query": [{
    "op": "aggregate",
    "groupBy": ["country", "product"],
    "measures": [
      { "field": "revenue", "agg": "sum", "asFieldKey": "sum_revenue" },
      { "field": "revenue", "agg": "count", "asFieldKey": "count_records" }
    ]
  }]
}

生クエリ(集計なし):

{
  "type": "view",
  "query": [{
    "op": "raw",
    "fields": ["country", "product", "revenue", "date"]
  }]
}

フォールドクエリ(アンピボット):

{
  "type": "view",
  "query": [{
    "op": "fold",
    "foldBy": ["q1_sales", "q2_sales", "q3_sales", "q4_sales"],
    "newFoldKeyCol": "quarter",
    "newFoldValueCol": "sales"
  }]
}

ビンクエリ:

{
  "type": "view",
  "query": [{
    "op": "bin",
    "binBy": "age",
    "newBinCol": "age_bin",
    "binSize": 10
  }]
}

4. ソートステップ

結果をソートします:

{
  "type": "sort",
  "sort": "descending",
  "by": ["sum_revenue"]
}

サーバー実装例

SQLを使用した最小限のExpress.jsエンドポイントの例:

app.post('/api/data/query', async (req, res) => {
  const { workflow, limit, offset } = req.body;
  let query = 'SELECT * FROM dataset';
  const params = [];
 
  for (const step of workflow) {
    if (step.type === 'filter') {
      const conditions = step.filters.map(f => {
        if (f.rule.type === 'range') {
          const [min, max] = f.rule.value;
          if (min !== null && max !== null) return `${f.fid} BETWEEN ${min} AND ${max}`;
          if (min !== null) return `${f.fid} >= ${min}`;
          if (max !== null) return `${f.fid} <= ${max}`;
        }
        if (f.rule.type === 'one of') {
          return `${f.fid} IN (${f.rule.value.map(v => `'${v}'`).join(',')})`;
        }
        return '1=1';
      });
      query += ` WHERE ${conditions.join(' AND ')}`;
    }
 
    if (step.type === 'view') {
      for (const q of step.query) {
        if (q.op === 'aggregate') {
          const groupCols = q.groupBy.join(', ');
          const measureCols = q.measures.map(m =>
            `${m.agg.toUpperCase()}(${m.field}) AS ${m.asFieldKey}`
          ).join(', ');
          query = `SELECT ${groupCols}, ${measureCols} FROM (${query}) t GROUP BY ${groupCols}`;
        }
        if (q.op === 'raw') {
          query = `SELECT ${q.fields.join(', ')} FROM (${query}) t`;
        }
      }
    }
 
    if (step.type === 'sort') {
      query += ` ORDER BY ${step.by.join(', ')} ${step.sort === 'ascending' ? 'ASC' : 'DESC'}`;
    }
  }
 
  if (limit) query += ` LIMIT ${limit}`;
  if (offset) query += ` OFFSET ${offset}`;
 
  const results = await db.query(query);
  res.json(results);
});

セキュリティ上の注意: 上記の例は簡略化されています。本番環境では、SQLインジェクションを防ぐためにパラメータ化クエリを使用してください。

計算タイムアウト

両方のモードでタイムアウト設定をサポートしています:

<GraphicWalker
  data={data}
  fields={fields}
  computationTimeout={30000}  // 30 seconds
/>

適切なモードの選択

要因クライアント側サーバー側
セットアップの複雑さ最小限バックエンドエンドポイントが必要
データセットサイズ100K行未満無制限
データプライバシーデータがブラウザに送信されるデータはサーバーに留まる
インタラクション速度高速(ネットワーク不要)ネットワーク+サーバーに依存
オフラインサポートありなし