@archboard/elk-rs
v0.11.3
Published
ELK layout engine in Rust — elkjs-compatible API with WASM and native Node.js addon (fork of openedges/elk-rs)
Maintainers
Readme
@archboard/elk-rs
ELK layout engine rewritten in Rust — drop-in replacement for elkjs with WASM and native Node.js addon support.
This is a fork of openedges/elk-rs (source), published under the
@archboardscope. Besides its npm packaging it fixes these problems in the JS package:
- The WASM fallback works on Node.js and Bun when no native addon is installed (upstream loaded the web-target WASM without initialising it).
js/elk-worker.jsno longer mistakes Bun's main thread for a Web Worker.- A layout error rejects with an
Errorcarrying ELK's message and prints no Rust panic to stderr; on WASM the message is reported instead of anunreachabletrap, and the next layout starts from a fresh instance.- Every export has type declarations, and
@archboard/elk-rs/worker.browseris a module Web Worker for bundled browser apps.and lays graphs out as the elkjs build archboard used does: interactive layouts no longer deadlock and follow an edge's previous bend points, and Java's
floatarithmetic is computed indouble, as GWT compiles it.
Installation
npm install @archboard/elk-rs
# or
bun add @archboard/elk-rsOn supported platforms a native addon is installed through an optional dependency; everywhere else the package falls back to WASM.
| Platform | Package |
|---|---|
| macOS ARM64 (Apple Silicon) | @archboard/elk-rs-darwin-arm64 |
| macOS x64 (Intel) | @archboard/elk-rs-darwin-x64 |
| Linux x64 (glibc) | @archboard/elk-rs-linux-x64-gnu |
| Linux x64 (musl) | @archboard/elk-rs-linux-x64-musl |
| Linux ARM64 (glibc) | @archboard/elk-rs-linux-arm64-gnu |
| Windows x64 | @archboard/elk-rs-win32-x64-msvc |
The native addon's layout() runs synchronously on the calling thread (the
returned promise is already settled); run it in a Worker to keep a thread free.
Usage
elk-rs provides an elkjs-compatible API. In most cases you can replace elkjs with @archboard/elk-rs directly:
const ELK = require('@archboard/elk-rs');
const elk = new ELK();
const graph = {
id: 'root',
layoutOptions: { 'elk.algorithm': 'layered' },
children: [
{ id: 'n1', width: 30, height: 30 },
{ id: 'n2', width: 30, height: 30 },
],
edges: [
{ id: 'e1', sources: ['n1'], targets: ['n2'] }
]
};
elk.layout(graph).then(console.log);ESM
import ELK from '@archboard/elk-rs';
const elk = new ELK();Browser
elk-rs works in the browser via WASM. Bundlers that respect the "browser" field in package.json will automatically use the browser entry point.
Web Worker
const ELK = require('@archboard/elk-rs');
const elk = new ELK({
workerUrl: './node_modules/@archboard/elk-rs/js/elk-worker.js'
});In Bun, as with elkjs:
import ELK from '@archboard/elk-rs/js/elk-api.js';
const worker = new Worker(import.meta.resolve('@archboard/elk-rs/js/elk-worker.js'));
const elk = new ELK({ workerFactory: () => worker });Module Web Worker in a bundled browser app
@archboard/elk-rs/worker.browser is an ES module worker with no Node.js
dependencies: it instantiates the WASM build, whose binary it references with
new URL(..., import.meta.url) so bundlers emit it as an asset, and answers
elkjs's worker protocol. With Vite:
import ELK from '@archboard/elk-rs/js/elk-api.js';
import ElkWorker from '@archboard/elk-rs/worker.browser?worker';
const elk = new ELK({ workerFactory: () => new ElkWorker() });A layout that fails rejects with an Error carrying ELK's message, and the
worker replaces its WASM instance before the next layout.
A pool of workers can share one compiled module instead of each compiling the 5.5 MB binary: compile it once on the page and send it to each worker as its first message. A worker that receives no module compiles its own when its first layout arrives.
import ELK from '@archboard/elk-rs/js/elk-api.js';
import ElkWorker from '@archboard/elk-rs/worker.browser?worker';
import wasmUrl from '@archboard/elk-rs/wasm-url';
const module = await WebAssembly.compileStreaming(fetch(wasmUrl));
const createElk = () => new ELK({
workerFactory: () => {
const worker = new ElkWorker();
worker.postMessage({ cmd: 'init', module });
return worker;
},
});API
new ELK(options?)
defaultLayoutOptions— default layout options applied to everylayout()callworkerUrl— URL to the worker script (enables Web Worker mode)workerFactory— custom function to create a Worker instancealgorithms— list of algorithm IDs to register (all built-in by default)
elk.layout(graph, options?)
Returns a Promise<LayoutedGraph>. The graph follows the ELK JSON format.
elk.knownLayoutAlgorithms()
Returns a Promise with an array of registered layout algorithm descriptions.
elk.knownLayoutOptions()
Returns a Promise with an array of available layout options.
elk.knownLayoutCategories()
Returns a Promise with an array of layout categories.
elk.terminateWorker()
Terminates the Web Worker (if one was created).
Differences from elkjs
- Written in Rust — compiled to WASM instead of GWT-transpiled JavaScript
- No GWT overhead — faster startup, smaller memory footprint
- Native Node.js addon — optional NAPI binding for maximum performance
- Same API — elkjs-compatible
layout(),knownLayoutAlgorithms(), etc. - Same algorithms — layered, stress, mrtree, radial, force, disco, rectpacking, sporeOverlap, sporeCompaction
Supported Algorithms
| Algorithm | ELK ID |
|-----------|--------|
| Layered | org.eclipse.elk.layered |
| Stress | org.eclipse.elk.stress |
| MrTree | org.eclipse.elk.mrtree |
| Radial | org.eclipse.elk.radial |
| Force | org.eclipse.elk.force |
| DisCo | org.eclipse.elk.disco |
| Rect Packing | org.eclipse.elk.rectpacking |
| Spore Overlap | org.eclipse.elk.sporeOverlap |
| Spore Compaction | org.eclipse.elk.sporeCompaction |
