Advanced & integrations
Every demo here runs live in your browser — drag, zoom and use the controls. The last section covers capabilities that need an extra dependency, with pointers rather than embedded demos.
A collaborative diagram editor
A complete Figma / Excalidraw-style whiteboard built on Nodus: rectangles,
ellipses, diamonds, sticky notes and text labels; drag-to-connect arrows; freehand
ink; marquee select and drag-to-move; undo/redo; and PNG / JSON export. Every
shape’s look is derived from its data, so the whole board serialises to JSON in a
few lines — which also makes real-time collaboration almost free: the demo syncs
over a BroadcastChannel, so open it in two browser tabs and edit together, with
live cursors and presence. No server, no dependencies.
// A shape is just a node; its visuals come from data, set once on create.nodus.addNode({ attributes: { x, y, shape: 'diamond', color, text: { content, position: 'center', maxLineLength: 16 } }, data: { kind: 'shape', shape: 'diamond', fill: color, label: content },});
// Drag from one shape to another to connect them — built in:nodus.tools.connectNodes.enable({ onEdgeCreated: (edge) => edge.setAttributes({ shape: { head: 'arrow' } }) });
// Drop a shape exactly where the pointer is, in graph space:nodus.events.on('mousedown', (e) => { const { x, y } = nodus.view.screenToGraphCoordinates({ x: e.x, y: e.y }); nodus.addNode({ attributes: { x, y /* … */ } });});
// Multiplayer with zero backend — broadcast the serialised board between tabs:const room = new BroadcastChannel('diagram');room.postMessage({ doc: serialiseBoard() }); // on every committed editroom.onmessage = (e) => applyRemoteDoc(e.data.doc); // newest revision winsPick a tool, then draw: shapes drop where you click (or drag to size) ·
Connector links two shapes with an arrow · Sticky / Text drop a label you
type inline (double-click any shape to relabel) · Pen sketches freehand ·
Eraser removes whatever you touch. Select with a click, Shift-click or a marquee,
recolour from the palette, undo/redo (Ctrl/⌘ Z), and export to PNG or JSON.
Then open the demo in a second tab to draw together live.
Custom canvas layers
Draw your own content on a Canvas 2D layer beneath (or above) the graph with
nodus.layers.addCanvasLayer(drawFn, opts, level) — backgrounds, grids,
annotations, heatmaps.
nodus.layers.addCanvasLayer((ctx) => { ctx.strokeStyle = '#eef2f7'; for (let x = 0; x < ctx.canvas.width; x += 40) { ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, ctx.canvas.height); ctx.stroke(); }}, {}, 0); // draw fn first; a low level draws underneath the graphDOM overlays
nodus.layers.addOverlay({ element, position }) pins your own DOM element to a
graph coordinate. The overlay tracks the node as you pan and zoom — handy for rich
HTML annotations, callouts or live widgets.
const el = document.createElement('div');el.textContent = 'Entry point';const p = node.getPosition();nodus.layers.addOverlay({ element: el, position: { x: p.x, y: p.y } });Exporting
Export the graph as data or an image. nodus.export.* covers json, svg,
csv, gexf, graphml, png, jpg (and xlsx with the optional dependency),
and nodus.view.getImageData() gives a raster snapshot.
const json = await nodus.export.json(); // graph dataconst svg = await nodus.export.svg(); // vector markupconst png = nodus.view.getImageData(); // raster snapshotExcel (SheetJS)
Build an .xlsx workbook from the graph with SheetJS —
one sheet of nodes, one of edges.
const wb = XLSX.utils.book_new();XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(nodeRows), 'Nodes');XLSX.utils.book_append_sheet(wb, XLSX.utils.json_to_sheet(edgeRows), 'Edges');XLSX.writeFile(wb, 'graph.xlsx');Geographic positioning
Give each node a data.latitude / data.longitude and Nodus’s geo mode
overlays the graph on a live Leaflet slippy map — pan,
zoom and the nodes stay pinned to their real-world coordinates. Install Leaflet
(npm install leaflet), make it available as window.L, then:
await nodus.geo.enable({ tiles: { url: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' }, attribution: '© OpenStreetMap contributors',});nodus.geo.setView(centroidLat, centroidLng, 2); // centre the mapThe demo uses the Canvas renderer so the map tiles show through. Without Leaflet, project the coordinates onto plain x/y for an equirectangular layout instead:
const project = (lat, lng) => ({ x: lng * 7, y: -lat * 7 }); // equirectangularnode.attributes = { x: project(lat, lng).x, y: project(lat, lng).y };Interactive geo workbench
Geo mode is fully interactive. The 'click' event gives you the pointer in
screen space and the element under it; combine it with the map’s coordinate
conversion to add, connect and delete points, or draw polygon regions and select
the points inside them.
const map = nodus.geo.getMap(); // the Leaflet handlenodus.events.on('click', (e) => { // { x, y, target, button } const { lat, lng } = map.containerPointToLatLng(L.point(e.x, e.y)); nodus.addNode({ id, attributes: { radius: 7 }, data: { latitude: lat, longitude: lng } });});// regions are plain Leaflet polygons; test membership with point-in-polygonL.polygon(corners).addTo(map);Pick a tool, then: Add point drops a node where you click · Connect links two points into a route · Draw region places corners (click the first point, or Finish / Enter, to close) and highlights the points inside · Remove deletes a point and its routes. Pan and zoom — every point stays pinned to its real-world coordinate.
Timeline
Scrub a graph over time with the visibility API — a range slider that reveals
nodes by a data.year. No plugin required.
function showUpTo(year) { nodus.getNodes('all').forEach((n) => n.setVisible(n.getData('year') <= year));}Performance: large graphs
The Rust/WebAssembly core and GPU renderer are built for scale. This demo builds 1,500 nodes by hand, lays them out, and frames them.
For the architecture that makes this fast, see Architecture and The Rendering Pipeline.
UI: legend
nodus.tools.legend.enable() renders a legend that summarises your data-driven
styles — one entry per category, with its colour and shape. Track the on/off state
yourself and call disable() to hide it.
nodus.styles.addNodeRule({ color: (n) => typeStyle[n.getData().type].color, shape: (n) => typeStyle[n.getData().type].shape,});nodus.tools.legend.enable(); // show the legend// later: nodus.tools.legend.disable();React (and other frameworks)
Nodus is framework-agnostic: create the instance in an effect and give it a container ref. The pattern is the same in any framework.
import { useEffect, useRef } from 'react';import { Nodus } from '@kortexya/nodus';
export function Graph({ data }) { const ref = useRef(null); useEffect(() => { const nodus = new Nodus({ container: ref.current }); nodus.setGraph(data).then(() => nodus.layouts.force({ duration: 0 })) .then(() => nodus.view.locateGraph()); return () => nodus.destroy(); // clean up on unmount }, [data]); return <div ref={ref} style={{ width: '100%', height: '100%' }} />;}See also
- Transformations — grouping and collapse/expand.
- Animations — animating styles and the camera.
- API: Generators, parsers & geometry — the export, layers and transformation APIs in full.