Interaction Tools
Nodus ships a set of opt-in interaction tools on nodus.tools.*. Each tool is a
small module you turn on when you need it and turn off when you don’t. They cover
selection gestures (lasso, rectangle), editing gestures (connect, resize,
rewire), layout aids (snapping), and a pointer/tooltip layer for clicks and
hovers.
This page shows how to enable each tool and wire up its callbacks. For reacting to graph-lifecycle changes (nodes added, attributes updated) see Handling Events; for the selection methods the selection tools feed into, see Selection.
The common tool shape
Most tools share the same two methods:
nodus.tools.lasso.enable(/* options? */); // turn onnodus.tools.lasso.disable(); // turn offWhen you build a toggle button, keep track of the on/off state yourself with a
boolean — flip it and call enable() or disable() accordingly.
The tools that follow this shape are lasso, rectangleSelect, connectNodes,
resize, rewire, and snapping. The tooltip tool is different — it’s a set
of pointer handlers plus a tooltip element — and is covered last.
Selection tools
Lasso
lasso lets the user draw a freeform loop to select every node inside it.
nodus.tools.lasso.enable();The selected nodes land in the instance selection, so you read them back with the selection API:
nodus.tools.lasso.enable();// ...user draws a loop...const picked = nodus.getSelectedNodes(); // a NodeListTry it live — draw a freeform loop around a few nodes to select them:
Rectangle select
rectangleSelect is the familiar drag-a-box gesture. Everything inside the box
becomes selected.
nodus.tools.rectangleSelect.enable();
// laternodus.tools.rectangleSelect.disable();Lasso and rectangle select are two ways to populate the same selection — pick whichever gesture fits your UI. See Selection for styling the selected state and reading the result.
Try it live — drag a box across the canvas to select everything inside it:
Connect nodes
connectNodes lets the user drag from one node to another to create an edge. It
takes a rich options object (ConnectNodesOptions) so you can control the
preview line, validate connections, and hook into the lifecycle.
nodus.tools.connectNodes.enable({ cursorStyle: 'crosshair', strokeColor: '#0f766e', strokeWidth: 2, dashed: true, dashLength: 6, // Only allow a connection when both endpoints pass this test. condition: (source, target) => source.getId() !== target?.getId(), onEdgeCreated: (edge) => { console.log('new edge', edge.getId()); }, onComplete: (source, target, edge) => { console.log('connected', source.getId(), '→', target?.getId(), edge?.getId()); },});ConnectNodesOptions
| Option | Purpose |
|---|---|
continueDrawing | Keep the tool armed after a connection so the user can chain several. |
cursorStyle | CSS cursor shown while drawing (e.g. 'crosshair'). |
strokeColor | Color of the preview line. |
strokeWidth | Width of the preview line. |
dashed | Draw the preview line dashed. |
dashLength | Dash length when dashed is on. |
createNodes | Allow dropping onto empty space to create a new target node. |
condition(source, target?) | Predicate that decides whether a connection is allowed. target is undefined while still hovering empty space. |
createNode(rawNode) | Customize the node created when createNodes is on. |
createEdge(rawEdge) | Customize the edge that will be created. |
onNodeCreated(node) | Called when a new node is created during the gesture. |
onEdgeCreated(edge) | Called when the new edge is created. |
onComplete(source, target, edge?) | Called when the gesture finishes. |
// A constrained connect: only from "source" nodes, never a self-loop,// and rejected silently otherwise.nodus.tools.connectNodes.enable({ continueDrawing: true, condition: (source, target) => source.getData()?.kind === 'source' && (!target || target.getId() !== source.getId()), onComplete: (source, target) => { if (target) console.log('linked', source.getId(), target.getId()); },});Try it live — drag from one node onto another to draw a new edge:
Resize
resize lets the user drag handles to change a node’s radius (size).
nodus.tools.resize.enable();Disable it when you leave edit mode so a stray drag doesn’t resize a node:
nodus.tools.resize.disable();Rewire
rewire lets the user drag an existing edge endpoint to a different node,
re-pointing the edge. You first tell the tool which edges are eligible with
setEdgesToRewire, then enable it.
const edges = nodus.getEdges(); // or a filtered EdgeListnodus.tools.rewire.setEdgesToRewire(edges);nodus.tools.rewire.enable();// Make only the selected edges rewireable.nodus.tools.rewire.setEdgesToRewire(nodus.getSelectedEdges());nodus.tools.rewire.enable();Snapping
snapping makes dragged nodes snap into alignment, which helps users build tidy
diagrams by hand.
nodus.tools.snapping.enable();Try it live — drag a handle to resize, drag an edge endpoint to rewire, and drag a node to feel snapping:
The tooltip tool
nodus.tools.tooltip is two things in one: a set of pointer handlers for
nodes, edges and the background, and a small tooltip element you can show and
hide. This is the recommended way to respond to clicks and hovers.
Pointer handlers
Register handlers for the interaction you care about. Each takes a handler and an optional options object.
// Nodesnodus.tools.tooltip.onNodeClick((node) => select(node));nodus.tools.tooltip.onNodeHover((node) => preview(node));nodus.tools.tooltip.onNodeDoubleClick((node) => expand(node));nodus.tools.tooltip.onNodeRightClick((node) => openMenu(node));
// Edgesnodus.tools.tooltip.onEdgeClick((edge) => select(edge));nodus.tools.tooltip.onEdgeHover((edge) => preview(edge));nodus.tools.tooltip.onEdgeDoubleClick((edge) => editEdge(edge));nodus.tools.tooltip.onEdgeRightClick((edge) => openMenu(edge));
// Hyperedges (the polygons drawn by the hypergraph subsystem). The handler// receives the hovered hyperedge id; it fires only over the polygon's body,// so a node sitting inside the polygon still wins its own node tooltip.nodus.tools.tooltip.onHyperedgeHover((hyperedgeId) => describeFact(hyperedgeId));
// Background (empty space)nodus.tools.tooltip.onBackgroundClick(() => nodus.clearSelection());nodus.tools.tooltip.onBackgroundDoubleClick(() => nodus.view.locateGraph());nodus.tools.tooltip.onBackgroundRightClick(() => openCanvasMenu());Showing a tooltip
show(content, options?) displays the tooltip element with your content;
hide() removes it. isShown() reports whether it’s currently visible, and
refresh() re-renders it in place.
nodus.tools.tooltip.onNodeHover((node) => { const label = node.getData()?.label ?? node.getId(); nodus.tools.tooltip.show(`<strong>${label}</strong>`);});
nodus.tools.tooltip.onNodeClick((node) => { if (nodus.tools.tooltip.isShown()) nodus.tools.tooltip.hide();});The content can be a string of markup; pass options to control placement and
behavior:
| Option | Default | Description |
|---|---|---|
position | 'top' | Side of the anchor to place the tooltip: 'top', 'bottom', 'left', 'right', or 'cssDefined' to position it yourself in CSS. |
autoAdjust | true | Flip/shift the tooltip back into view when it would overflow the canvas. |
delay | 0 | Milliseconds to wait after the trigger before showing. |
className | — | Extra class added to the tooltip element. |
animationDuration | 150 | Fade + slide-in duration in milliseconds. Set 0 to disable the animation. |
interactive | false | When false the tooltip is pointer-events: none, so the cursor falls through it to the nodes and canvas beneath — hovering one element never blocks reaching what it overlaps. Set true for a pinned tooltip whose own content must be clickable or scrollable. |
Tooltips fade and slide in with an ease-out curve (cubic-bezier(.16, 1, .3, 1)),
and by default they let the pointer pass straight through (interactive: false),
matching how D3, ECharts, and vis-network treat hover tooltips — so a bubble
tooltip over a cluster never seals off the nodes underneath it. The same options
apply whether the tooltip is raised on a node, an edge, or a hyperedge, and to a
direct show(content, options) call:
// Snappier tooltip with no entrance animationnodus.tools.tooltip.onNodeHover((node) => preview(node), { animationDuration: 0 });
// Slower, more deliberate fade for a hyperedge inspectornodus.tools.tooltip.onHyperedgeHover((id) => describeFact(id), { animationDuration: 260 });Combine hover-to-show with click-to-pin for a simple inspector:
let pinned = null;
nodus.tools.tooltip.onNodeHover((node) => { if (pinned) return; // don't override a pinned tooltip nodus.tools.tooltip.show(node.getData()?.label ?? String(node.getId()));});
nodus.tools.tooltip.onNodeClick((node) => { pinned = node; nodus.tools.tooltip.show(`Pinned: ${node.getData()?.label ?? node.getId()}`);});
nodus.tools.tooltip.onBackgroundClick(() => { pinned = null; nodus.tools.tooltip.hide();});Try it live — hover and click the nodes to see the tooltip handlers in action:
Putting it together
A small “edit mode” that combines several tools and the tooltip:
let editMode = false;
function enterEditMode() { editMode = true; nodus.tools.snapping.enable(); nodus.tools.resize.enable(); nodus.tools.connectNodes.enable({ cursorStyle: 'crosshair', strokeColor: '#0f766e', dashed: true, condition: (s, t) => !t || s.getId() !== t.getId(), onComplete: (s, t) => t && console.log('edge', s.getId(), t.getId()), });}
function exitEditMode() { editMode = false; nodus.tools.snapping.disable(); nodus.tools.resize.disable(); nodus.tools.connectNodes.disable();}
nodus.tools.tooltip.onNodeRightClick((node) => { if (editMode) exitEditMode(); else enterEditMode();});Next
- Selection — read and style the selection that the lasso and rectangle tools populate.
- Handling Events — graph-lifecycle and camera
events on
nodus.events. - API: Interaction — the full tools reference.