Elenweave Canvas API
A client-only JavaScript library under canvas/lib/, framework-agnostic, shipped as ES modules. The surface is structured around Workspace, Graph, View, and Navigator.
Overview
Elenweave is a client-only canvas workspace library. It provides a multi-graph workspace layer, a low-level graph model, a renderer/controller view for a canvas, and a navigator for camera + selection.
Workspace
Creates, renames, switches, and exports boards. Moves nodes across graphs.
Graph
Low-level nodes + edges model with ordering helpers and serialization.
View
Renders a single canvas, handles interaction, selection, export, and components.
Navigator
Camera control and traversal: pan/zoom, fit, focus, directional selection, keymap.
Architecture
The Canvas library is split across canvas/lib/ and canvas/components/. The dev app in canvas/ is a test harness; canvas/v0/ is legacy.
Scope and responsibilities
- Core library: data model, rendering, and interaction (no backend adapters).
- Components: HTML-rendered widgets (inputs, charts, dashboards).
- Dev-app: validates API changes and UI behavior.
Core modules
ElenweaveGraph
Owns nodes, edges, notifications, ordering, ID generation, overlap-avoid placement, and GraphJson serialization.
ElenweaveWorkspace
Manages multiple graphs, tracks the active board, moves nodes between graphs, and exports WorkspaceJson.
ElenweaveView
Renders the active graph, owns interaction state, and blends canvas + DOM overlays for HTML nodes.
ElenweaveNavigator
Two-tier navigation: camera pan/zoom plus graph traversal with optional UI + keyboard bindings.
Rendering layers
- Canvas layer: grid/background, edges, text nodes, selection, and SVG nodes.
- HTML overlay: component nodes and html-text nodes (contenteditable).
- Link anchors opt-in via data-ew-link; drag/select via data-ew-drag and data-ew-select.
Component system
- Register via view.registerComponent(name, { render | loader }).
- Renderers return an element or { el, update, cleanup }.
- Data flows through node.props (config) and node.data (state).
- Component API includes setData, setProps, updateNode, and emit.
Import/export and theming
- Graph/workspace import/export are JSON-only; no migration layer.
- Notifications are included in GraphJson and WorkspaceJson.
- Presets: blueprint, light, dark; view applies tokens to HTML overlays and nav UI.
Quick Start
Minimal setup: instantiate a workspace, mount a view, create a board, add nodes, connect an edge.
import { ElenweaveWorkspace, ElenweaveView } from 'elenweave';
import 'elenweave/styles.css';
const workspace = new ElenweaveWorkspace();
const view = new ElenweaveView({
canvas: document.querySelector('#bp'),
workspace
});
const boardId = workspace.createGraph('Board 01');
workspace.setActiveGraph(boardId);
const a = view.addNode({ text: 'Start', x: 120, y: 120 });
const b = view.addNode({ text: 'Next', x: 420, y: 200 });
view.addEdge(a, b);
Using the Bundles
Install from npm for bundlers/Node, or use the CDN IIFE for a single global. Local dist/ files are only needed if you build from source.
Install (npm)
npm i elenweave
CDN / IIFE (single global, recommended)
<script src="https://cdn.jsdelivr.net/npm/elenweave@0.1.1/dist/elenweave.iife.min.js"></script>
<script>
const { ElenweaveWorkspace, ElenweaveView } = Elenweave;
const workspace = new ElenweaveWorkspace();
const view = new ElenweaveView({ canvas: document.querySelector('#bp'), workspace });
</script>
Local build (fallback, no CDN)
<script src="./dist/elenweave.iife.min.js"></script>
ESM/CJS (bundler or Node)
import { ElenweaveWorkspace, ElenweaveView } from 'elenweave';
import 'elenweave/styles.css';
Data Shapes
Core payloads: Node, Edge, GraphJson, WorkspaceJson, Notification.
Node
{
id: string,
type: 'text' | 'image' | 'audio' | 'html' | 'html-text' | 'svg',
x: number, y: number, w: number, h: number,
text?: string,
svg?: string,
svgUrl?: string,
img?: string,
audioUrl?: string,
color?: string,
pulse?: boolean,
fixed?: boolean,
trusted?: boolean,
component?: string,
render?: (ctx) => HTMLElement,
loader?: () => Promise<{ render: (ctx) => HTMLElement }>,
data?: Record<string, any>,
props?: Record<string, any>,
meta?: any
}
html-text renders a built-in HTML text node. Double-click enters edit mode; text persists in node.text and updates on input/blur.
svg renders SVG markup or a URL on the canvas. Provide node.svg (raw markup) or node.svgUrl (URL). External URLs may require CORS if you plan to export.
trusted marks whether a node's inline component is trusted. When allowUntrustedComponents is false, untrusted components are blocked.
Edge
{ id: string, from: string, to: string, color?: string, pulse?: boolean, label?: string }
GraphJson
{ id: string, name: string, nodeOrder?: string[], nodes: Node[], edges: Edge[], notifications?: Notification[] }
WorkspaceJson
{ version: string, activeGraphId: string | null, graphs: GraphJson[] }
Notification
{ id: string, nodeId: string, timestamp: number, read: boolean, label?: string }
ElenweaveWorkspace
Multi-graph container and navigation layer.
Workspace vs Graph
- Workspace manages many boards and the active board. It is the navigation layer (create, rename, switch, move nodes between boards).
- Graph is one board’s data model (nodes and edges) and does not know about other boards.
Methods
- createGraph(name?: string): string → returns graph id
- getGraph(id): ElenweaveGraph | null
- listGraphs(): ElenweaveGraph[]
- renameGraph(id, name): ElenweaveGraph | null
- setActiveGraph(id): void
- deleteGraph(id): boolean
- moveNode(nodeId, fromGraphId, toGraphId): string | null
- addNodesToGraph(graphId, partials): string[]
- addEdgesToGraph(graphId, entries): string[]
- updateNodesInGraph(graphId, entries): Node[]
- updateEdgesInGraph(graphId, entries): Edge[]
- addNodesBatch(entries): Array<{ graphId, id }>
- addEdgesBatch(entries): Array<{ graphId, id }>
- updateNodesBatch(entries): Array<{ graphId, node }>
- updateEdgesBatch(entries): Array<{ graphId, edge }>
- exportGraph(id): GraphJson | null
- exportWorkspace(): WorkspaceJson
- importGraph(payload, options?): string | null
- importWorkspace(payload, options?): boolean
Events
- graph:created → ElenweaveGraph
- graph:renamed → ElenweaveGraph
- graph:active → ElenweaveGraph
- graph:deleted → graphId
- node:moved → { from, to, fromId, toId }
Batch operations (multiple graphs)
Batch helpers accept per-graph entries and reduce rework when updating many boards.
workspace.addNodesBatch([
{ graphId: boardA, nodes: [{ text: 'Alpha', x: 80, y: 120 }] },
{ graph: boardB, items: [{ text: 'Beta', x: 220, y: 160 }] }
]);
workspace.updateEdgesBatch([
{ graphId: boardA, updates: [{ id: edgeId, patch: { label: 'critical' } }] },
{ boardId: boardB, entries: [{ id: edge2, label: 'ok' }] }
]);
ElenweaveGraph
Low-level graph model (nodes and edges).
Methods
- addNode(partial): string
- addNodes(partials: Node[]): string[]
- updateNode(id, patch): Node | null
- updateNodes(patches: Array<{ id, patch } | Node>): Node[]
- removeNode(id): boolean
- getNodeOrder(): string[]
- moveNodeToIndex(id, index): boolean
- moveNodeBefore(id, targetId): boolean
- moveNodeAfter(id, targetId): boolean
- addEdge(from, to, opts?): string | null
- addEdges(entries: Array<{ from, to, ...opts }>): string[]
- updateEdge(edgeId, patch): Edge | null
- updateEdges(patches: Array<{ id, patch } | Edge>): Edge[]
- removeEdge(edgeId): boolean
- getNode(id): Node | null
- clear(): void
- serialize(): { id, name, nodes, edges }
- ElenweaveGraph.hydrate(data): ElenweaveGraph
Bulk updates (single graph)
graph.updateNodes([
{ id: nodeA, patch: { x: 120, y: 220 } },
{ id: nodeB, text: 'Renamed', color: '#78dbff' }
]);
graph.updateEdges([
{ id: edgeA, patch: { label: 'hot' } },
{ id: edgeB, color: '#41ffb4' }
]);
Placement helpers
- autoPlace?: boolean — when true, nudges the node around the requested position to avoid overlaps.
- avoidOverlap?: boolean — set false to keep exact placement; auto-placement runs only when autoPlace is true or x/y are omitted.
ElenweaveView
Renderer and interaction controller for a single canvas.
Construction
const view = new ElenweaveView({
canvas: document.querySelector('#bp'),
workspace, // optional; if omitted, a workspace is created
graphId: null, // optional; locks the view to a specific graph
options: {
gridSize: 0,
minZoom: 0.3,
maxZoom: 2.5,
snap: 12,
nodeColor: '#6fe7ff',
edgeColor: '#78dbff',
themeName: 'blueprint',
theme: { nodeStroke: '#6fe7ff' },
contextMenu: true,
domPurify: window.DOMPurify, // optional sanitizer instance
sanitizeHtml: true,
sanitizeSvg: true,
allowUntrustedComponents: true,
sanitizeConfig: { html: {}, svg: {} },
virtualize: false,
virtualizePadding: 200,
virtualizeEdges: true
}
});
Security & trust
- domPurify (or sanitizer) supplies a DOMPurify instance; if omitted, the view uses globalThis.DOMPurify when available.
- sanitizeHtml / sanitizeSvg default to true.
- sanitizeConfig merges into defaults for HTML/SVG sanitizer options.
- allowUntrustedComponents defaults to true. When false, components marked untrusted are blocked.
- A component is untrusted when node.trusted === false or the registry entry has trusted: false.
Methods
- addNode(partial): string, updateNode(id, patch), removeNode(id)
- addEdge(from, to, opts?), updateEdge(edgeId, patch), removeEdge(edgeId)
- selectNode(id | null), selectEdge(id | null), deleteSelected()
- setLinkMode(boolean)
- setActiveGraph(id) (no-op if graphId was provided)
- focusNode(id), moveTo(id)
- invalidate(), destroy()
- registerComponent(name, definition), unregisterComponent(name)
- exportGraph(), exportWorkspace()
- importGraph(payload, options?), importWorkspace(payload, options?)
- setContextMenuConfig(config), enableContextMenu(), disableContextMenu()
- setVirtualization({ enabled?, padding?, edges? }), getVirtualizationConfig()
- addNotification({ nodeId, label?, timestamp? }), markNotificationRead(id, read?), clearNotifications(), getNotifications()
Theme Options
Select a preset with options.themeName (blueprint, light, dark) and override values with options.theme.
const view = new ElenweaveView({
canvas,
workspace,
options: {
themeName: 'light',
theme: {
bg: '#061526',
nodeStroke: '#6fe7ff',
edge: '#78dbff',
selection: '#41ffb4'
}
}
});
Interaction Defaults
Default interaction model for canvas nodes and navigation.
- Drag to move nodes.
- Resize from corners.
- Shift+drag to link (or toggle setLinkMode(true)).
- Wheel to zoom, Space+drag or middle mouse to pan.
- Double-click to edit text nodes.
- Set pulse: true on nodes or edges to enable a gentle border/edge pulse.
- Set fixed: true on a node to disable dragging and resizing.
Virtualization
For large graphs, enable virtualization to render only nodes within the viewport bounds (plus padding).
view.setVirtualization({ enabled: true, padding: 240, edges: true });
HTML Components
HTML nodes render in a DOM overlay so they can receive clicks and input.
1) ESM/CJS build (recommended) for components
import { ElenweaveWorkspace, ElenweaveView } from 'elenweave';
import { OptionPicker } from 'elenweave/components';
import 'elenweave/styles.css';
const workspace = new ElenweaveWorkspace();
const view = new ElenweaveView({ canvas, workspace });
view.registerComponent('OptionPicker', { render: OptionPicker });
view.addNode({
type: 'html',
component: 'OptionPicker',
x: 200, y: 120, w: 320, h: 160
});
Register a component
view.registerComponent('OptionPicker', { loader: () => import('./components/option-picker.js') });
Add a node
view.addNode({
type: 'html',
component: 'OptionPicker',
x: 200, y: 140, w: 320, h: 160,
props: { title: 'Route', options: ['A', 'B'] },
data: { choice: 'A' }
});
Dragging and selection hooks:
- Add data-ew-drag to constrain dragging to a handle.
- Add data-ew-select to elements that should select the node on pointer down.
- Add data-ew-link to start linking from an element (even when Link Mode is off).
- Resize handles appear on selected HTML nodes automatically; HTML nodes can be linked like canvas nodes.
Chart Components
The dev app ships with SVG-based chart components built on the HTML node system.
- LineChart, AreaChart, BarChart, ScatterChart, HeatmapChart, RadarChart, SparklineChart
- Charts read from node.data or node.props.
Common fields
- title: header title
- series: arrays of { name, color?, values? | points? }
- labels: x-axis labels (line/area/bar)
- axes: radar axis labels
- matrix: heatmap 2D array
- palette: series colors
- minX/maxX/minY/maxY: axis overrides
- legend: set false to hide
Example
view.addNode({
type: 'html',
component: 'LineChart',
x: 200, y: 160, w: 380, h: 240,
props: { title: 'Signal Trend', labels: ['Mon', 'Tue', 'Wed', 'Thu'] },
data: {
series: [
{ name: 'Alpha', values: [4, 6, 5, 8] },
{ name: 'Beta', values: [2, 4, 3, 5] }
]
}
});
Notes
- Build outputs live in dist/ (see docs/build.md for commands).
- Backend and network adapters are intentionally excluded.