API Canvas

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.

Client-only ES modules Multi-board workspaces Export PNG + JSON HTML nodes (DOM overlay)
Structured interaction

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.

Scope: no backend is implied; network adapters are intentionally excluded.

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.

JavaScript
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)

Shell
npm i elenweave

CDN / IIFE (single global, recommended)

HTML
<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)

HTML
<script src="./dist/elenweave.iife.min.js"></script>

ESM/CJS (bundler or Node)

JavaScript
import { ElenweaveWorkspace, ElenweaveView } from 'elenweave';
import 'elenweave/styles.css';

Data Shapes

Core payloads: Node, Edge, GraphJson, WorkspaceJson, Notification.

Node

TypeScript
{
  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
}
Node type notes
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

TypeScript
{ id: string, from: string, to: string, color?: string, pulse?: boolean, label?: string }

GraphJson

TypeScript
{ id: string, name: string, nodeOrder?: string[], nodes: Node[], edges: Edge[], notifications?: Notification[] }

WorkspaceJson

TypeScript
{ version: string, activeGraphId: string | null, graphs: GraphJson[] }

Notification

TypeScript
{ 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:createdElenweaveGraph
  • graph:renamedElenweaveGraph
  • graph:activeElenweaveGraph
  • graph:deletedgraphId
  • node:moved{ from, to, fromId, toId }
Subscribe with workspace.on(event, handler).

Batch operations (multiple graphs)

Batch helpers accept per-graph entries and reduce rework when updating many boards.

JavaScript
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
Edge opts for addEdge supports { color?, pulse?, label?, id? }.

Bulk updates (single graph)

JavaScript
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

JavaScript
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
  }
});
If a node or edge does not specify color, the view uses options.nodeColor or options.edgeColor.

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.

JavaScript
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.

Context Menu

Right-click on empty canvas to open the context menu. Disable via options.contextMenu: false or view.disableContextMenu().

JavaScript
view.setContextMenuConfig({
  enabled: true,
  items: [
    { id: 'export-png', label: 'Export as PNG', action: 'export-png' },
    { id: 'export-json', label: 'Export as JSON', action: 'export-json' },
    { id: 'add', label: 'Add', submenu: 'add' }
  ],
  addItems: [
    { label: 'Editable Text', nodeType: 'html-text', size: { w: 320, h: 160 } },
    { label: 'Option Picker', nodeType: 'html', component: 'OptionPicker' }
  ],
  onAction: ({ item, result }) => console.log(item, result)
});
Add submenu items spawn a node at the click location (auto-placement still avoids overlap).

Virtualization

For large graphs, enable virtualization to render only nodes within the viewport bounds (plus padding).

JavaScript
view.setVirtualization({ enabled: true, padding: 240, edges: true });
edges: when true, draws edges if either endpoint is visible. When disabled, all edges render regardless of visibility.

HTML Components

HTML nodes render in a DOM overlay so they can receive clicks and input.

1) ESM/CJS build (recommended) for components

JavaScript
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

JavaScript
view.registerComponent('OptionPicker', { loader: () => import('./components/option-picker.js') });

Add a node

JavaScript
view.addNode({
  type: 'html',
  component: 'OptionPicker',
  x: 200, y: 140, w: 320, h: 160,
  props: { title: 'Route', options: ['A', 'B'] },
  data: { choice: 'A' }
});
Components receive { node, api } where api.setData() writes back user input and api.emit() can emit node:input.

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

JavaScript
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.