OG
JS
Docs
Demos
Examples
Investigation overlays
Outline related communities and pin serializable evidence directly to graph coordinates.
Clear notes
Restore notes
Add node note
// Hulls receive group selectors and annotations receive graph-space anchors plus content. // OGJS retains, reprojects, serializes, and restores both overlay types with the graph, // avoiding a second canvas and application-managed camera listeners. import { Ogjs } from '../../src/index'; const og = new Ogjs({ container: '#viz', theme: 'light' }); og.setGraph({ nodes: [ { id: 'alert-a', label: 'Alert A', x: -210, y: -70, color: '#dc2626', data: { cluster: 'alerts' } }, { id: 'alert-b', label: 'Alert B', x: -120, y: -115, color: '#ea580c', data: { cluster: 'alerts' } }, { id: 'alert-c', label: 'Alert C', x: -105, y: 55, color: '#f59e0b', data: { cluster: 'alerts' } }, { id: 'alert-d', label: 'Alert D', x: -225, y: 80, color: '#e11d48', data: { cluster: 'alerts' } }, { id: 'account-a', label: 'Account A', x: 105, y: -80, color: '#2563eb', data: { cluster: 'accounts' } }, { id: 'account-b', label: 'Account B', x: 220, y: -45, color: '#059669', data: { cluster: 'accounts' } }, { id: 'account-c', label: 'Account C', x: 195, y: 95, color: '#7c3aed', data: { cluster: 'accounts' } }, { id: 'account-d', label: 'Account D', x: 80, y: 70, color: '#0891b2', data: { cluster: 'accounts' } }, ], edges: [ { source: 'alert-a', target: 'alert-b' }, { source: 'alert-b', target: 'alert-c' }, { source: 'alert-c', target: 'alert-d' }, { source: 'alert-d', target: 'alert-a' }, { source: 'account-a', target: 'account-b' }, { source: 'account-b', target: 'account-c' }, { source: 'account-c', target: 'account-d' }, { source: 'account-d', target: 'account-a' }, { source: 'alert-c', target: 'account-a', width: 2.5, color: '#f2a33c' }, ], }); og.camera.fit({ target: 'all', avoid: document.querySelector('.workflow-panel'), paddingPx: 24, maxOccupancy: .5, }); // The selector names community membership; OGJS derives and retains hull geometry around the // matching nodes and keeps it synchronized with camera and graph changes. og.hulls.show(({ data }) => data.cluster); const saved = [ { id: 'ann-1', type: 'text', x: -35, y: -175, text: 'Linked investigation', color: '#2563eb' }, { id: 'ann-2', type: 'rect', x: -270, y: -145, w: 220, h: 260, label: 'risk boundary', color: '#dc2626' }, { id: 'ann-3', type: 'arrow', x1: -55, y1: 10, x2: 65, y2: -35, label: 'shared device', color: '#f59e0b' }, { id: 'ann-4', type: 'callout', nodeId: 'account-b', text: 'high-value account', color: '#059669', dx: 70, dy: -65 }, ]; // Annotation documents use graph coordinates or stable node anchors. They can be persisted as // application data and restored without serializing pixels or camera transforms. let lastAdded = null; function publish() { const annotations = og.annotations.toJSON().length; document.querySelector('#overlay-status').textContent = `${annotations} notes · 2 community hulls`; } document.querySelector('#clear-notes').addEventListener('click', () => { og.annotations.clear(); lastAdded = null; publish(); }); document.querySelector('#restore-notes').addEventListener('click', () => { // Restoring the document recreates retained annotations through one public import boundary; // the existing graph, hulls, selection, and camera remain untouched. og.annotations.fromJSON(saved); lastAdded = null; publish(); }); document.querySelector('#add-note').addEventListener('click', () => { lastAdded = og.annotations.add({ type: 'callout', nodeId: 'alert-a', text: 'review owner', color: '#7c3aed', dx: -80, dy: 60, }); publish(); }); og.annotations.fromJSON(saved); publish();