Use Duckdb-wasm for computation in GraphicWalker
You can use duckdb-wasm to boost the computation speed for graphic walker.
We provided a package to help you use duckdb-wasm in GraphicWalker.
You can also use any database as a computation engine, by using the gw-dsl-parser to transform our queries to sql.
yarn add @kanaries/gw-dsl-parser
Usage
This Example is in Vite.
computation.ts
import initWasm, { parser_dsl_with_table } from '@kanaries/gw-dsl-parser';
import dslWasm from '@kanaries/gw-dsl-parser/gw_dsl_parser_bg.wasm?url';
import * as duckdb from '@duckdb/duckdb-wasm';
import duckdb_wasm from '@duckdb/duckdb-wasm/dist/duckdb-mvp.wasm?url';
import mvp_worker from '@duckdb/duckdb-wasm/dist/duckdb-browser-mvp.worker.js?url';
import duckdb_wasm_eh from '@duckdb/duckdb-wasm/dist/duckdb-eh.wasm?url';
import eh_worker from '@duckdb/duckdb-wasm/dist/duckdb-browser-eh.worker.js?url';
import { Table, Vector } from 'apache-arrow';
import { bigNumToString } from 'apache-arrow/util/bn';
const MANUAL_BUNDLES: duckdb.DuckDBBundles = {
mvp: {
mainModule: duckdb_wasm,
mainWorker: mvp_worker,
},
eh: {
mainModule: duckdb_wasm_eh,
mainWorker: eh_worker,
},
};
let db: duckdb.AsyncDuckDB;
let inited = false;
export async function init() {
if (inited) return;
inited = true;
// Select a bundle based on browser checks
const bundle = await duckdb.selectBundle(MANUAL_BUNDLES);
const worker_url = URL.createObjectURL(
new Blob([`importScripts("${bundle.mainWorker!}");`], {
type: 'text/javascript',
})
);
// Instantiate the asynchronus version of DuckDB-Wasm
const worker = new Worker(worker_url);
const logger = new duckdb.ConsoleLogger();
db = new duckdb.AsyncDuckDB(logger, worker);
await db.instantiate(bundle.mainModule, bundle.pthreadWorker);
URL.revokeObjectURL(worker_url);
await initWasm(dslWasm);
}
const ArrowToJSON = (v: any): any => {
if (typeof v === 'object') {
if (v instanceof Vector) {
return Array.from(v).map(ArrowToJSON);
} else {
return parseInt(bigNumToString(v as any));
}
}
if (typeof v === 'bigint') {
return Number(v);
}
return v;
};
const transformData = (table: Table) => {
return table.toArray().map((r) => Object.fromEntries(Object.entries(r.toJSON()).map(([k, v]) => [k, ArrowToJSON(v)])));
};
export async function getComputation(data: any[]) {
if (data.length === 0) {
return {
close: async () => {},
computation: async () => [],
};
}
const tableName = nanoid().replace('-', '');
const fileName = `${tableName}.json`;
const conn = await db.connect();
await db.registerFileText(fileName, JSON.stringify(data));
await conn.insertJSONFromPath(fileName, { name: tableName });
return {
close: async () => {
await conn.close();
await db.dropFile(fileName);
},
computation: async (query: any) => {
const sql = parser_dsl_with_table(tableName, JSON.stringify(query));
const res = await conn.query(sql).then(transformData);
return res;
},
};
}
app.tsx
import { useMemo, useState, useEffect } from 'react';
import { GraphicWalker } from '@kanaries/graphic-walker';
import { transData } from '@kanaries/graphic-walker/dataSource/utils';
import data from './data.json';
import { init, getComputation } from './computation';
export default function App() {
const { dataSource, fields } = useMemo(() => transData(data), [data]);
const [computation, setComputation] = useState();
useEffect(() => {
init().then(() => {
getComputation(dataSource).then(setComputation);
});
}, [dataSource]);
if (!computation) return null;
return (
<GraphicWalker computation={computation} fields={fields} />
);
}
pureRenderer.tsx
import { useMemo, useState, useEffect } from 'react';
import { PureRenderer } from '@kanaries/graphic-walker';
import { transData } from '@kanaries/graphic-walker/dataSource/utils';
import data from './data.json';
import spec from './spec.json';
import { init, getComputation } from './computation';
export default function PureRenderer() {
const { dataSource } = useMemo(() => transData(data), [data]);
const [computation, setComputation] = useState();
useEffect(() => {
init().then(() => {
getComputation(dataSource).then(setComputation);
});
}, [dataSource]);
if (!computation) return null;
return (
<PureRenderer type="remote" computation={computation} visualConfig={spec[0].config} visualState={spec[0].encodings} visualLayout={spec[0].layout} />
);
}