Skip to content
GRAPHIC WALKER
Guides
Calcul de données

Calcul de données

Graphic Walker prend en charge deux modes de calcul : côté client (par défaut) et côté serveur. Choisissez en fonction de la taille de votre jeu de données et des exigences de votre architecture.

Calcul côté client

Lorsque vous passez la prop data, Graphic Walker exécute tous les calculs dans un Web Worker sur le client. C'est la configuration la plus simple — aucun backend requis.

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

Avantages :

  • Aucune configuration côté serveur
  • Fonctionne hors ligne
  • Interaction instantanée

Limitations :

  • Le jeu de données doit tenir dans la mémoire du navigateur
  • Transfert initial de toutes les données vers le client
  • Les performances dépendent du matériel client

Recommandé pour : Les jeux de données de moins de 100K lignes.

DuckDB WASM (optionnel)

Pour de meilleures performances côté client avec des jeux de données plus importants, Graphic Walker peut utiliser DuckDB WASM. Installez le package optionnel :

npm install @kanaries/graphic-walker-duckdb

Cela active l'agrégation basée sur SQL dans le navigateur, ce qui est significativement plus rapide pour les grands jeux de données.

Calcul côté serveur

Pour les grands jeux de données ou lorsque les données ne peuvent pas quitter le serveur, passez une fonction computation au lieu de data. Graphic Walker envoie des charges utiles de requête à votre fonction, et vous retournez les résultats.

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}
    />
  );
}

Avantages :

  • Gérez des jeux de données de toute taille
  • Les données restent sur le serveur
  • Exploitez les bases de données côté serveur (PostgreSQL, DuckDB, etc.)

Limitations :

  • Nécessite l'implémentation d'un endpoint de requête
  • La latence réseau affecte la vitesse d'interaction

Signature de la fonction de calcul

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

La fonction reçoit un IDataQueryPayload avec un tableau workflow — un pipeline d'étapes de traitement :

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

Étapes du workflow

Le workflow est un tableau ordonné d'étapes. Votre serveur les traite séquentiellement :

1. Étape de filtre

Appliquer des filtres au niveau des lignes avant toute agrégation :

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

2. Étape de transformation

Calculer des champs dérivés :

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

3. Étape de vue

Agréger ou sélectionner des données. C'est l'étape la plus courante :

Requête d'agrégation :

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

Requête brute (sans agrégation) :

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

Requête de dépliage (unpivot) :

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

Requête de discrétisation :

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

4. Étape de tri

Trier les résultats :

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

Exemple d'implémentation serveur

Voici un endpoint Express.js minimal utilisant SQL :

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);
});

Note de sécurité : L'exemple ci-dessus est simplifié. En production, utilisez des requêtes paramétrées pour prévenir l'injection SQL.

Délai d'expiration du calcul

Les deux modes prennent en charge un réglage de délai d'expiration :

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

Choisir le bon mode

FacteurCôté clientCôté serveur
Complexité de configurationMinimaleNécessite un endpoint backend
Taille du jeu de données< 100K lignesIllimitée
Confidentialité des donnéesDonnées envoyées au navigateurDonnées restent sur le serveur
Vitesse d'interactionRapide (pas de réseau)Dépend du réseau + serveur
Support hors ligneOuiNon