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.

The common tool shape

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.

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 NodeList

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

Lasso select Open in new tab ↗

Rectangle select

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 ↗

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

OptionPurpose
continueDrawingKeep the tool armed after a connection so the user can chain several.
cursorStyleCSS cursor shown while drawing (e.g. 'crosshair').
strokeColorColor of the preview line.
strokeWidthWidth of the preview line.
dashedDraw the preview line dashed.
dashLengthDash length when dashed is on.
createNodesAllow 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

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

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 ↗

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.

// 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());

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:

OptionDefaultDescription
position'top'Side of the anchor to place the tooltip: 'top', 'bottom', 'left', 'right', or 'cssDefined' to position it yourself in CSS.
autoAdjusttrueFlip/shift the tooltip back into view when it would overflow the canvas.
delay0Milliseconds to wait after the trigger before showing.
classNameExtra class added to the tooltip element.
animationDuration150Fade + slide-in duration in milliseconds. Set 0 to disable the animation.
interactivefalseWhen 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 ↗

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.