@alphaelements/aelm-renderer
v0.6.0
Published
AELM circuit-diagram renderer (WASM). Renders .aelm DSL to SVG or HTML Canvas.
Downloads
157
Maintainers
Readme
@alphaelements/aelm-renderer
WebAssembly-powered renderer for AELM circuit-diagram DSL.
Drives the same Rust pipeline that ships in the AELM VS Code extension and the aelm CLI, so a .aelm source is rendered identically across all three.
- One-shot SVG:
renderToSvg(source)→ string - One-shot Canvas:
renderToCanvas(ctx, source, viewport)paints into anHTMLCanvasElement - Interactive recalculation:
evaluate(source, overrides)re-runs formulas and plots when a variable changes — no re-parse of layout / routing (see Interactive recalculation) - No server required: pure client-side WASM (works in Node 18+, Bun, Deno, browsers, edge runtimes)
- MIT licensed
Install
npm install @alphaelements/aelm-rendererBrowser / bundler usage (Vite, Webpack, Next.js, Rollup, …)
import init, { render_to_svg, render_to_canvas } from '@alphaelements/aelm-renderer';
await init();
const source = `
module Divider {
instances {
R1: Resistor(value: 10k)
R2: Resistor(value: 10k)
}
connections {
R1.b -> R2.a
}
}
`;
const svg = render_to_svg(source, { background: { r: 255, g: 255, b: 255, a: 255 }, margin_mm: 5 });
document.getElementById('diagram')!.innerHTML = svg;Next.js dynamic import
@alphaelements/aelm-renderer ships a WASM binary so it must run on the client only:
'use client';
import { useEffect, useState } from 'react';
export function CircuitPreview({ source }: { source: string }) {
const [svg, setSvg] = useState('');
useEffect(() => {
import('@alphaelements/aelm-renderer').then(async ({ default: init, render_to_svg }) => {
await init();
setSvg(render_to_svg(source, {}));
});
}, [source]);
return <div dangerouslySetInnerHTML={{ __html: svg }} />;
}Node.js usage
import init, { render_to_svg } from '@alphaelements/aelm-renderer';
import { readFileSync, writeFileSync } from 'node:fs';
await init();
const source = readFileSync('circuit.aelm', 'utf8');
writeFileSync('out.svg', render_to_svg(source, { background: { r: 255, g: 255, b: 255, a: 255 }, margin_mm: 5 }));Canvas usage
import init, { render_to_canvas } from '@alphaelements/aelm-renderer';
await init();
const canvas = document.querySelector('canvas')!;
const ctx = canvas.getContext('2d')!;
// Auto-fit (viewport=null) or explicit:
render_to_canvas(ctx, source, null, { background: null, device_pixel_ratio: window.devicePixelRatio });Configuration
SvgConfig
| Field | Type | Default | Description |
| ------------- | ------------------- | ---------------- | ---------------------------------------- |
| background | Color \| null | {r:255,g:255,b:255,a:255} (white) | Background fill. null for transparent. |
| margin_mm | number | 5.0 | Margin around the diagram in mm. |
CanvasConfig
| Field | Type | Default | Description |
| -------------------- | ---------------- | ------- | --------------------------------------------- |
| background | Color \| null | null | Optional fill before drawing content. |
| device_pixel_ratio | number | 1.0 | DPR multiplier for crisp text on HiDPI. |
ViewportConfig (used by render_to_canvas)
| Field | Type | Description |
| --------------- | -------- | ---------------------------------------- |
| offset_x | number | World-mm at canvas centre (X). |
| offset_y | number | World-mm at canvas centre (Y). |
| zoom | number | Pixels per mm. |
| canvas_width | number | canvas.width in pixels. |
| canvas_height | number | canvas.height in pixels. |
Pass null for viewport to auto-fit to the diagram bounds.
Interactive recalculation
When a .aelm source has a calcs { } block (editable variables + formulas) and/or plots { }, you can let users change input values and recompute results without re-parsing the layout or re-routing wires. The typical flow:
list_calc_inputs(source)— discover the editable variables to build an input UI.list_plots_from_source(source)— discover the plot frames to draw.- On every value change, call
evaluate(source, overrides)and update the formula readouts and plot traces from the result. - (Optional)
render_with_overrides(source, overrides, config)to re-render the whole diagram as SVG with the new values baked in (e.g. for download).
import init, {
list_calc_inputs, list_plots_from_source, evaluate, render_with_overrides,
} from '@alphaelements/aelm-renderer';
await init();
// 1. Build the input UI.
const inputs = list_calc_inputs(source);
// → [{ id: "R", kind: "var", default_value: "10k", param_type: "Ohm", label: "R" },
// { id: "fc", kind: "formula", expr: "1 / (2 * pi * R * C)", display: "$f_c = {fc}$ Hz" }, ...]
// 2. Recompute when the user edits a value (target < 100 ms, no re-layout).
const result = evaluate(source, { R: '20k', C: '47n' });
// result.formulas → [{ id: "fc", display: "$f_c = 169.3$ Hz", value: "169.3" }, ...]
// result.plots → [{ id: "bode", traces: [{ id: "mag", x: [...], y: [...] }] }, ...]
// 3. Or re-render the whole figure with overrides applied.
const svg = render_with_overrides(source, { R: '20k' }, null);API
| Function | Returns | Purpose |
| -------- | ------- | ------- |
| list_calc_inputs(source) | CalcInputInfo[] | Calc variables + formulas, for building input UIs. |
| list_plots_from_source(source) | PlotSummary[] | Plot axis + trace metadata, for drawing plot frames. |
| evaluate(source, overrides) | EvaluateResult | Recompute formulas + plot traces with overridden variable values. |
| render_with_overrides(source, overrides, config) | string (SVG) | One-shot SVG render with overrides applied; config is the same SvgConfig as render_to_svg. |
overrides is a Record<string, string> mapping variable id → value string ("20k", "47n", "3.14", "42"). Pass null for defaults. Unknown ids are ignored; a non-numeric value on a numeric variable yields NaN in the affected formula's error.
Types
interface CalcInputInfo {
id: string;
kind: 'var' | 'formula';
default_value?: string; // var only — SI-formatted, e.g. "10k"
param_type?: string; // var only — "Ohm" | "Farad" | "Hertz" | "Volt" | "Float" | "Integer" | ...
label?: string; // var only
expr?: string; // formula only — raw expression
display?: string; // formula only — LaTeX display template with {id} slots
}
interface PlotSummary {
id: string;
title?: string;
x_axis: AxisSummary;
y_axis: AxisSummary;
y2_axis?: AxisSummary;
traces: { id: string; label?: string; kind: 'function' | 'csv' | 'samples' }[];
}
interface AxisSummary { label?: string; range?: [number, number]; log: boolean; }
interface EvaluateResult {
formulas: { id: string; display: string; value: string; error?: string }[];
plots: { id: string; traces: { id: string; label?: string; x: number[]; y: number[] }[] }[];
}Error handling
All entry points surface diagnostics on parse / IR / layout / render failure:
try {
const svg = render_to_svg(badSource, {});
} catch (err: any) {
// err.diagnostics: Array<{ severity, message, span?, code? }>
for (const d of err.diagnostics ?? []) {
console.error(d.code, d.message, d.span);
}
}What's NOT in this package
This is the render-only subset of AELM:
- ✅ Parse
.aelm - ✅ Layout + routing
- ✅ Render to SVG / Canvas
- ✅ Interactive recalculation (
evaluate/render_with_overrides) - ❌ Reverse sync (component move, wire reconnect, …)
- ❌ DRC checking
- ❌ LSP / language-server features
For those, use the AELM VS Code extension or the aelm CLI.
License
MIT — see LICENSE.
