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-duckdbCela 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
| Facteur | Côté client | Côté serveur |
|---|---|---|
| Complexité de configuration | Minimale | Nécessite un endpoint backend |
| Taille du jeu de données | < 100K lignes | Illimitée |
| Confidentialité des données | Données envoyées au navigateur | Données restent sur le serveur |
| Vitesse d'interaction | Rapide (pas de réseau) | Dépend du réseau + serveur |
| Support hors ligne | Oui | Non |