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();
<!doctype html> <html lang="en"><head><meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1"/> <title>Ogjs example — Investigation overlays</title> <link rel="stylesheet" href="../site.css"/><link rel="stylesheet" href="./data-lifecycle.css"/> </head><body> <header class="rail"><a class="wordmark" href="../index.html">OG<b>JS</b></a><nav> <a href="../docs.html">Docs</a><a href="../demos.html">Demos</a> <a href="./index.html" aria-current="page">Examples</a></nav></header> <div id="viz"></div> <section class="workflow-panel"><h1>Investigation overlays</h1> <p>Outline related communities and pin serializable evidence directly to graph coordinates.</p> <div class="controls"> <button id="clear-notes">Clear notes</button> <button id="restore-notes">Restore notes</button> <button id="add-note">Add node note</button> </div> <div class="status" id="overlay-status" aria-live="polite"></div> </section> <script type="module"> // 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(); </script></body></html>