Skip to content
GRAPHIC WALKER
가이드
데이터 계산

데이터 연산

Graphic Walker는 클라이언트 측 (기본값)과 서버 측 두 가지 연산 모드를 지원합니다. 데이터셋 크기와 아키텍처 요구 사항에 따라 선택하세요.

클라이언트 측 연산

data prop을 전달하면 Graphic Walker는 클라이언트의 Web Worker에서 모든 연산을 실행합니다. 가장 간단한 설정으로 -- 백엔드가 필요하지 않습니다.

<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[]>;

함수는 workflow 배열 -- 처리 단계의 파이프라인이 포함된 IDataQueryPayload를 받습니다:

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 행무제한
데이터 프라이버시데이터가 브라우저로 전송데이터가 서버에 유지
인터랙션 속도빠름 (네트워크 없음)네트워크 + 서버에 의존
오프라인 지원YesNo