Skip to content

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.

Most tools share the same two methods:

nodus.tools.lasso.enable(/* options? */); // turn on
nodus.tools.lasso.disable(); // turn off

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

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 NodeList

Try it live — draw a freeform loop around a few nodes to select them:

Lasso select Open in new tab ↗

rectangleSelect is the familiar drag-a-box gesture. Everything inside the box becomes selected.

nodus.tools.rectangleSelect.enable();
// later
nodus.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:

Rectangle select Open in new tab ↗

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());
},
});
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:

Connect nodes Open in new tab ↗

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 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 EdgeList
nodus.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 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:

Resize, rewire & snapping Open in new tab ↗

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.

Register handlers for the interaction you care about. Each takes a handler and an optional options object.

// Nodes
nodus.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));
// Edges
nodus.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());

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 animation
nodus.tools.tooltip.onNodeHover((node) => preview(node), { animationDuration: 0 });
// Slower, more deliberate fade for a hyperedge inspector
nodus.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:

Tooltips Open in new tab ↗

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();
});
  • 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.