Minimap
full mode already docks an overview of the whole graph in a canvas corner, and one line — plugins: [minimap()], as below — puts your own configured one there instead, in any mode. Click anywhere in it to bring the view there; drag the rectangle to pan. The graph below is wider than its viewport, so there is somewhere to go. The tiny arrow in the minimap's inner corner folds it away to just that button when the canvas matters more than the overview.
Even so it is a plugin, not built-in chrome — full mode simply installs it for you, and stands aside for the copy passed here. It installs itself through the PluginContext it is handed and uses nothing a plugin of your own couldn't — it reads the graph's extent with renderer.getContentBounds(), works out the visible region by inverting the canvas corners through renderer.screenToGraphCoordinates(), and moves the view with renderer.setViewport(). See Plugins for the whole surface.
It also stays cheap on a graph that actually needs one. The picture lives in a cached offscreen bitmap that is only re-rasterised when the graph changes (or every 10th simulation tick while a layout settles); panning and zooming redraw one image and one rectangle, whatever the node count. Past 1500 nodes it stops drawing each node and paints density instead, which reads better than tens of thousands of overlapping dots and resolves no per-node styles at all.
Zoom in with the viewport controls, then drag the rectangle in the minimap — the content picture never redraws, only the rectangle moves.
// import { minimap } from 'pivotick'
const options = {
UI: {
// `full` mode brings a minimap of its own; `light` and `viewer` need asking
// (`UI.minimap: true`, or the plugin below). `static` never has one.
mode: 'full',
// The other thing full mode brings. Off here: this card is about the minimap.
table: false
},
// A plugin installs itself through the PluginContext it is handed — the core never
// needs to know it exists. `graph.use(minimap())` works just as well, at any time.
// Passed here it replaces the one full mode would have mounted, configuration and all.
plugins: [
minimap({
position: 'bottom-right', // the corner the built-in chrome leaves free
width: 220
// height: omitted → follows the canvas's aspect ratio
})
],
render: {
nodeTypeAccessor: (node) => node.getData()?.kind,
nodeStyleMap: {
hub: { color: '#0072B2', size: 13 },
leaf: { color: '#E69F00', size: 7 }
}
},
simulation: {
d3LinkDistance: 60
}
}// A graph deliberately bigger than the viewport, so the minimap has something to
// navigate: six clusters of satellites around a spine.
const nodes = []
const edges = []
const hubs = ['alpha', 'beta', 'gamma', 'delta', 'epsilon', 'zeta']
hubs.forEach((hub, hubIndex) => {
const angle = (hubIndex / hubs.length) * Math.PI * 2
nodes.push({
id: hub,
data: { name: hub, kind: 'hub' },
x: Math.cos(angle) * 420,
y: Math.sin(angle) * 420
})
if (hubIndex > 0) edges.push({ from: hubs[hubIndex - 1], to: hub })
for (let leafIndex = 0; leafIndex < 9; leafIndex++) {
const id = `${hub}-${leafIndex}`
const spread = (leafIndex / 9) * Math.PI * 2
nodes.push({
id,
data: { name: id, kind: 'leaf' },
x: Math.cos(angle) * 420 + Math.cos(spread) * 130,
y: Math.sin(angle) * 420 + Math.sin(spread) * 130
})
edges.push({ from: hub, to: id })
}
})
const data = { nodes, edges }