Datenberechnung
Graphic Walker unterstuetzt zwei Berechnungsmodi: clientseitig (Standard) und serverseitig. Waehlen Sie basierend auf Ihrer Datensatzgroesse und Architekturanforderungen.
Clientseitige Berechnung
Wenn Sie die data-Prop uebergeben, fuehrt Graphic Walker alle Berechnungen in einem Web Worker auf dem Client aus. Dies ist die einfachste Einrichtung -- kein Backend erforderlich.
<GraphicWalker data={myData} fields={fields} />Vorteile:
- Keine serverseitige Einrichtung
- Funktioniert offline
- Sofortige Interaktion
Einschraenkungen:
- Datensatz muss in den Browserspeicher passen
- Anfaengliche Uebertragung aller Daten zum Client
- Leistung haengt von der Client-Hardware ab
Empfohlen fuer: Datensaetze unter 100.000 Zeilen.
DuckDB WASM (optional)
Fuer bessere clientseitige Leistung bei groesseren Datensaetzen kann Graphic Walker DuckDB WASM verwenden. Installieren Sie das optionale Paket:
npm install @kanaries/graphic-walker-duckdbDies ermoeglicht SQL-basierte Aggregation im Browser, die bei grossen Datensaetzen deutlich schneller ist.
Serverseitige Berechnung
Fuer grosse Datensaetze oder wenn Daten den Server nicht verlassen duerfen, uebergeben Sie eine computation-Funktion anstelle von data. Graphic Walker sendet Abfrage-Payloads an Ihre Funktion, und Sie geben die Ergebnisse zurueck.
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}
/>
);
}Vorteile:
- Verarbeitung von Datensaetzen jeder Groesse
- Daten bleiben auf dem Server
- Nutzung serverseitiger Datenbanken (PostgreSQL, DuckDB usw.)
Einschraenkungen:
- Erfordert die Implementierung eines Abfrage-Endpunkts
- Netzwerklatenz beeinflusst die Interaktionsgeschwindigkeit
Signatur der Berechnungsfunktion
type IComputationFunction = (payload: IDataQueryPayload) => Promise<IRow[]>;Die Funktion empfaengt ein IDataQueryPayload mit einem workflow-Array -- einer Pipeline von Verarbeitungsschritten:
interface IDataQueryPayload {
workflow: IDataQueryWorkflowStep[];
limit?: number;
offset?: number;
}Workflow-Schritte
Der Workflow ist ein geordnetes Array von Schritten. Ihr Server verarbeitet sie sequenziell:
1. Filter-Schritt
Zeilenbasierte Filter vor jeder Aggregation anwenden:
{
"type": "filter",
"filters": [
{
"fid": "country",
"rule": { "type": "one of", "value": ["US", "UK", "DE"] }
},
{
"fid": "revenue",
"rule": { "type": "range", "value": [1000, null] }
}
]
}2. Transform-Schritt
Abgeleitete Felder berechnen:
{
"type": "transform",
"transform": [
{
"key": "log_revenue",
"expression": {
"op": "log10",
"params": [{ "type": "field", "value": "revenue" }],
"as": "log_revenue"
}
}
]
}3. View-Schritt
Daten aggregieren oder auswaehlen. Dies ist der haeufigste Schritt:
Aggregationsabfrage:
{
"type": "view",
"query": [{
"op": "aggregate",
"groupBy": ["country", "product"],
"measures": [
{ "field": "revenue", "agg": "sum", "asFieldKey": "sum_revenue" },
{ "field": "revenue", "agg": "count", "asFieldKey": "count_records" }
]
}]
}Rohdatenabfrage (keine Aggregation):
{
"type": "view",
"query": [{
"op": "raw",
"fields": ["country", "product", "revenue", "date"]
}]
}Fold-Abfrage (Entpivotierung):
{
"type": "view",
"query": [{
"op": "fold",
"foldBy": ["q1_sales", "q2_sales", "q3_sales", "q4_sales"],
"newFoldKeyCol": "quarter",
"newFoldValueCol": "sales"
}]
}Bin-Abfrage:
{
"type": "view",
"query": [{
"op": "bin",
"binBy": "age",
"newBinCol": "age_bin",
"binSize": 10
}]
}4. Sort-Schritt
Ergebnisse sortieren:
{
"type": "sort",
"sort": "descending",
"by": ["sum_revenue"]
}Beispiel fuer Server-Implementierung
Hier ist ein minimaler Express.js-Endpunkt mit 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);
});Sicherheitshinweis: Das obige Beispiel ist vereinfacht. In der Produktion verwenden Sie parametrisierte Abfragen, um SQL-Injection zu verhindern.
Berechnungs-Timeout
Beide Modi unterstuetzen eine Timeout-Einstellung:
<GraphicWalker
data={data}
fields={fields}
computationTimeout={30000} // 30 seconds
/>Den richtigen Modus waehlen
| Faktor | Clientseitig | Serverseitig |
|---|---|---|
| Einrichtungsaufwand | Minimal | Erfordert Backend-Endpunkt |
| Datensatzgroesse | < 100.000 Zeilen | Unbegrenzt |
| Datenschutz | Daten werden an den Browser gesendet | Daten bleiben auf dem Server |
| Interaktionsgeschwindigkeit | Schnell (kein Netzwerk) | Abhaengig von Netzwerk + Server |
| Offline-Unterstuetzung | Ja | Nein |