OG
JS
Docs
Demos
Examples
Hierarchy workbench
Click a node to promote it. Click an edge to promote both endpoints.
Hierarchy controls
Level distance
54
Node distance
24
Component distance
54
Direction
Top down
Bottom up
Left to right
Right to left
Arrange components
Across flow
Stacked
Priority lanes
Topology
Operational priority
Edge routing
Straight
Soft curve
Root · Company
// Provide a tree or directed graph plus hierarchy options such as direction and spacing. // OGJS assigns levels, routes edges, packs components, scales for the viewport, and animates // root changes; applications never calculate canvas width, level coordinates, or curves. import { Ogjs, synthesizeTopology } from '../../src/index'; const og = new Ogjs({ container: '#viz', theme: 'light', labels: { minZoom: 0 } }); const tree = synthesizeTopology({ model: 'tree', children: 2, height: 5, idPrefix: 'hier-', }); const upperLabels = ['Company', 'Product', 'Operations', 'Platform', 'Data', 'Sales', 'Finance']; const branchColors = ['#334155', '#2563eb', '#7c3aed']; const nodes = tree.nodes.map((node, index) => { const depth = Math.floor(Math.log2(index + 1)); let branch = index; while (branch > 2) branch = Math.floor((branch - 1) / 2); return { ...node, label: upperLabels[index], size: depth === 0 ? 12 : depth <= 2 ? 9 : 5.5, color: branchColors[branch], data: { priority: index * 3 % 5 }, }; }); const edges = [...tree.edges]; for (const companion of [ { prefix: 'support-', labels: ['Support', 'Helpdesk', 'Quality'], color: '#059669' }, { prefix: 'governance-', labels: ['Governance', 'Risk', 'Audit'], color: '#d97706' }, ]) { const draft = synthesizeTopology({ model: 'tree', nodes: 3, children: 2, idPrefix: companion.prefix, }); nodes.push(...draft.nodes.map((node, index) => ({ ...node, label: companion.labels[index], size: index === 0 ? 12 : 7, color: companion.color, data: { priority: index }, }))); edges.push(...draft.edges); } og.setGraph({ nodes, edges }); og.styles.addEdgeRule(() => ({ color: '#b9c5d4', width: 1.25 })); let roots = ['hier-n0']; const settings = { layerGap: 54, nodeGap: 24, componentGap: 54, flow: 'south', components: 'across', priority: 'topology', routing: 'straight', }; function state(moving) { } state(false); function layout() { // These are hierarchy intentions, not coordinates. OGJS ranks nodes, routes edges, and // packs disconnected components for the selected reading direction. og.layouts.hierarchical({ roots, flow: settings.flow, layerGap: settings.layerGap, nodeGap: settings.nodeGap, componentGap: settings.componentGap, components: settings.components, routing: settings.routing, rankBy: settings.priority === 'operations' ? ({ data }) => data.priority : undefined, }); } async function arrange() { state(true); await og.transitionLayout(layout, { duration: 280 }); state(false); const rootNames = roots.map((id) => { return og.getNode(id)?.label || String(id); }); document.querySelector('#status').textContent = `Root · ${rootNames.join(' + ')} · ${settings.flow}`; } og.on('nodeclick', ({ nodeId }) => { if (nodeId === undefined) return; roots = [nodeId]; void arrange(); }); og.on('edgeclick', ({ sourceId, targetId }) => { if (sourceId === undefined || targetId === undefined) return; roots = [sourceId, targetId]; void arrange(); }); for (const [id, key, output] of [ ['level-gap', 'layerGap', 'level-value'], ['node-gap', 'nodeGap', 'node-value'], ['component-gap', 'componentGap', 'component-value'], ]) { document.querySelector(`#${id}`).addEventListener('input', (event) => { settings[key] = Number(event.currentTarget.value); document.querySelector(`#${output}`).textContent = String(settings[key]); void arrange(); }); } document.querySelector('#flow').addEventListener('change', (event) => { settings.flow = event.currentTarget.value; void arrange(); }); document.querySelector('#components').addEventListener('change', (event) => { settings.components = event.currentTarget.value; void arrange(); }); document.querySelector('#priority').addEventListener('change', (event) => { settings.priority = event.currentTarget.value; void arrange(); }); document.querySelector('#routing').addEventListener('change', (event) => { settings.routing = event.currentTarget.value; layout(); state(false); }); layout(); og.camera.fit({ target: 'visible', avoid: document.querySelector('.panel'), paddingPx: 24, maxOccupancy: .72, retain: true, }); // Keep the hierarchy legible without letting root nodes dominate or overlap dense leaves. // The scale is a presentation intent; graph coordinates remain untouched. og.setNodeScale(3, { maxDiameterPx: 28 });