Interactivity
VizCraft provides click handlers for basic interactions and a mathematical hit testing API for building full editors.
Click events
Attach click handlers to nodes and edges with .onClick():
- Preview
- Code
import { viz } from 'vizcraft';
const builder = viz().view(400, 200);
builder
.node('btn').at(200, 100).rect(120, 50, 10)
.label('Click Me')
.class('interactive-node')
.onClick((id) => alert(`Clicked node ${id}`))
.done();
builder.mount(document.getElementById('container'));
Interactive elements automatically receive cursor: pointer when an onClick handler is attached.
Hit testing
VizCraft provides a DOM-independent hit testing API for building advanced interactions (drag-and-drop, connection snapping, marquee selection). All queries run against the VizScene data structure — no DOM required.
Interactive demo
Point hit testing
hitTest returns the topmost element at a given coordinate, checking in Z-index order:
import { hitTest } from 'vizcraft';
const result = hitTest(scene, { x: 250, y: 150 });
if (result?.type === 'node') {
console.log('Hit node:', result.id);
if (result.compartmentId) {
console.log('Compartment:', result.compartmentId);
}
if (result.entryId) {
console.log('Entry:', result.entryId);
}
} else if (result?.type === 'port') {
console.log('Hit port:', result.portId, 'on node:', result.nodeId);
} else if (result?.type === 'edge') {
console.log('Hit edge:', result.id);
} else {
console.log('Hit empty space');
}
Customize tolerances for edges and ports:
const easyHit = hitTest(scene, point, {
edgeTolerance: 15, // easier to click thin lines
portTolerance: 20, // easier to snap to ports
});
Rectangle selection (marquee)
hitTestRect returns all nodes and edges intersecting a rectangle:
import { hitTestRect } from 'vizcraft';
const rect = { x: 100, y: 100, w: 300, h: 200 };
const selected = hitTestRect(scene, rect);
selected.forEach((item) => {
if (item.type === 'node') {
console.log('Selected node:', item.id);
}
});
Nearest port snapping
When drawing a new connection, snap to the closest port:
import { nearestPort } from 'vizcraft';
const port = nearestPort(scene, { x: 260, y: 150 }, { maxDistance: 50 });
if (port) {
console.log(
'Snap to:',
port.nodeId,
port.portId,
'at distance:',
port.distance
);
}
Edge proximity
Check distance from a point to a specific edge:
import { edgeDistance } from 'vizcraft';
const dist = edgeDistance(scene, 'a->b', { x: 200, y: 150 });
Pan & zoom
Enable interactive viewport controls for large diagrams:
- Preview
- Code
import { viz } from 'vizcraft';
const scene = viz()
.view(800, 600)
.node('a', { circle: { r: 40 }, at: { x: 200, y: 200 }, fill: '#f6c177' })
.node('b', {
diamond: { w: 100, h: 80 },
at: { x: 600, y: 200 },
fill: '#cba6f7',
})
.edge('a', 'b', { arrow: true });
const controller = scene.mount(document.getElementById('viz'), {
panZoom: true,
minZoom: 0.1, // default
maxZoom: 5, // default
initialZoom: 'fit', // default
});
Interactions:
- Scroll wheel / trackpad to zoom
- Click and drag background to pan
- Double click background to auto-fit
Programmatic viewport control
// Fit all elements into view
controller.fitToContent(20); // 20px padding
// Zoom and center on a specific node
controller.zoomToNode('user-node');
// Manual control
controller.setZoom(2.5);
controller.setPan({ x: -100, y: -50 });
controller.reset();
// Subscribe to viewport changes
const unsubscribe = controller.onChange(({ zoom, pan }) => {
console.log('Scale:', zoom, 'Translation:', pan);
});
Tooltips
Add hover tooltips to nodes and edges. Tooltips appear after a short delay (300 ms by default), follow the cursor, and are keyboard-accessible.
Plain text tooltip
- Preview
- Code
import { viz } from 'vizcraft';
const builder = viz()
.view(400, 160)
.node('server')
.at(200, 80)
.rect(140, 50, 10)
.label('API Server')
.fill('#f6c177')
.tooltip('Handles REST requests')
.done();
builder.mount(document.getElementById('container'));
Hover over the node to see the tooltip.
Structured tooltip
Pass an object with an optional title and an array of sections (label/value
pairs) for richer content:
- Preview
- Code
import { viz } from 'vizcraft';
const builder = viz()
.view(400, 160)
.node('db')
.at(200, 80)
.cylinder(100, 60)
.label('PostgreSQL')
.fill('#9ccfd8')
.tooltip({
title: 'Database',
sections: [
{ label: 'Engine', value: 'PostgreSQL 16' },
{ label: 'Connections', value: '42 / 100' },
],
})
.done();
builder.mount(document.getElementById('container'));
Edge tooltips
Edges support the same API:
- Preview
- Code
import { viz } from 'vizcraft';
const builder = viz()
.view(400, 160)
.node('a').at(100, 80).circle(30).label('A').fill('#cba6f7').done()
.node('b').at(300, 80).circle(30).label('B').fill('#eb6f92').done()
.edge('a', 'b')
.tooltip('Latency: 12 ms')
.stroke('#666', 2)
.arrow()
.done();
builder.mount(document.getElementById('container'));
Declarative form
Tooltips can also be set through the options object:
const scene = viz()
.node('n', { tooltip: 'Quick info' })
.edge('a', 'b', {
tooltip: { title: 'Link', sections: [{ label: 'Weight', value: '3' }] },
})
.done();
Theming
The tooltip element uses CSS custom properties so you can match your application's palette:
.viz-container {
--viz-tooltip-bg: #1e1e2e;
--viz-tooltip-fg: #cdd6f4;
--viz-tooltip-label: #a6adc8;
}
Keyboard accessibility
Nodes and edges that have tooltips receive tabindex="0" automatically, so
users can tab to them and trigger the tooltip via focus. Pressing Escape
dismisses any visible tooltip.
Found a problem? Open an issue on GitHub.