OG
JS
Docs
Demos
Examples
Projection pipeline
Reuse named risk and exposure fields across filters and WebGL styles.
Risk threshold
All risks
60+
90+
Raise account risk
// Named projections derive reusable risk and exposure values from application data. // OGJS evaluates those fields consistently across styles and filters and refreshes only // affected entities when source data changes. import { Ogjs } from '../../src/index'; const og = new Ogjs({ container: '#viz', theme: 'light', accessibility: { label: 'Projection pipeline' }, labels: { minZoom: 0 }, }); og.setGraph({ nodes: [ { id: 'alert', x: -180, y: -60, label: 'Alert', data: { base: 80 } }, { id: 'account', x: -60, y: 80, label: 'Account', data: { base: 50 } }, { id: 'device', x: -20, y: -140, label: 'Device', data: { base: 30 } }, { id: 'merchant', x: 90, y: 80, label: 'Merchant', data: { base: 25 } }, { id: 'beneficiary', x: 180, y: -50, label: 'Beneficiary', data: { base: 30 } }, ], edges: [ { id: 'alert-account', source: 'alert', target: 'account', data: { amount: 25 } }, { id: 'alert-device', source: 'alert', target: 'device', data: { amount: 12 } }, { id: 'account-merchant', source: 'account', target: 'merchant', data: { amount: 5 } }, { id: 'account-beneficiary', source: 'account', target: 'beneficiary', data: { amount: 20 } }, { id: 'device-beneficiary', source: 'device', target: 'beneficiary', data: { amount: 10 } }, ], }); // Define each derived value once by name. Styles, filters, and application reporting can reuse // the same evaluation instead of duplicating risk formulas with different results. og.projections.defineNode('risk', ({ degree, data }) => data.base + degree * 10); og.projections.defineNode('band', ({ value }) => value('risk') >= 90 ? 'critical' : value('risk') >= 60 ? 'review' : 'normal'); og.projections.defineEdge('exposure', ({ sourceValue, targetValue, data }) => sourceValue('risk') + targetValue('risk') + data.amount); let threshold = 0; let updated = false; // Consumers request projections through `value(name)`. OGJS tracks dependencies so visibility // and presentation refresh together when source data changes. og.transformations.addFilter(({ value }) => value('risk') >= threshold); og.styles.addNodeRule(({ value }) => ({ color: value('band') === 'critical' ? '#dc2626' : value('band') === 'review' ? '#f59e0b' : '#64748b', size: 8 + value('risk') / 18, })); og.styles.addEdgeRule(({ value }) => ({ color: value('exposure') >= 180 ? '#dc2626' : '#94a3b8', width: Math.max(1, value('exposure') / 70), })); const ids = ['alert', 'account', 'device', 'merchant', 'beneficiary']; const visibleCount = document.querySelector('[data-testid="visible-count"]'); const status = document.querySelector('#projection-status'); function report() { const risks = Object.fromEntries(ids.map((id) => [id, og.projections.nodeValue(id, 'risk')])); const visible = og.getVisible().filter((id) => ids.includes(id)).length; const exposure = og.projections.edgeValue('alert-account', 'exposure'); visibleCount.textContent = `${visible} / ${ids.length} visible`; status.textContent = `alert → account exposure ${exposure} · account risk ${risks.account}`; } document.querySelector('#threshold').addEventListener('change', (event) => { threshold = Number(event.target.value); og.transformations.refreshVisibility(); report(); }); document.querySelector('#raise-account').addEventListener('click', () => { og.updateNode('account', { data: { base: 90 } }); // Refresh invalidates derived values and their dependent rules without replacing the graph, // moving nodes, or recreating projection definitions. og.projections.refresh(); updated = true; report(); }); og.camera.fit(); report();
<!doctype html> <html lang="en"><head><meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1"/> <title>Ogjs example — Projection pipeline</title> <link rel="stylesheet" href="../site.css"/><link rel="stylesheet" href="./data-lifecycle.css"/> <style>#viz { left: 452px; } @media (max-width: 760px) { #viz { top: 320px; left: 0; } }</style> </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>Projection pipeline</h1> <p>Reuse named risk and exposure fields across filters and WebGL styles.</p> <div class="controls"> <label>Risk threshold <select id="threshold" aria-label="Risk threshold"> <option value="0" selected>All risks</option><option value="60">60+</option><option value="90">90+</option> </select></label> <button id="raise-account">Raise account risk</button> </div> <div class="status" data-testid="visible-count" role="status" aria-live="polite"></div> <div class="status" id="projection-status"></div> </section> <script type="module"> // Named projections derive reusable risk and exposure values from application data. // OGJS evaluates those fields consistently across styles and filters and refreshes only // affected entities when source data changes. import { Ogjs } from '../../src/index'; const og = new Ogjs({ container: '#viz', theme: 'light', accessibility: { label: 'Projection pipeline' }, labels: { minZoom: 0 }, }); og.setGraph({ nodes: [ { id: 'alert', x: -180, y: -60, label: 'Alert', data: { base: 80 } }, { id: 'account', x: -60, y: 80, label: 'Account', data: { base: 50 } }, { id: 'device', x: -20, y: -140, label: 'Device', data: { base: 30 } }, { id: 'merchant', x: 90, y: 80, label: 'Merchant', data: { base: 25 } }, { id: 'beneficiary', x: 180, y: -50, label: 'Beneficiary', data: { base: 30 } }, ], edges: [ { id: 'alert-account', source: 'alert', target: 'account', data: { amount: 25 } }, { id: 'alert-device', source: 'alert', target: 'device', data: { amount: 12 } }, { id: 'account-merchant', source: 'account', target: 'merchant', data: { amount: 5 } }, { id: 'account-beneficiary', source: 'account', target: 'beneficiary', data: { amount: 20 } }, { id: 'device-beneficiary', source: 'device', target: 'beneficiary', data: { amount: 10 } }, ], }); // Define each derived value once by name. Styles, filters, and application reporting can reuse // the same evaluation instead of duplicating risk formulas with different results. og.projections.defineNode('risk', ({ degree, data }) => data.base + degree * 10); og.projections.defineNode('band', ({ value }) => value('risk') >= 90 ? 'critical' : value('risk') >= 60 ? 'review' : 'normal'); og.projections.defineEdge('exposure', ({ sourceValue, targetValue, data }) => sourceValue('risk') + targetValue('risk') + data.amount); let threshold = 0; let updated = false; // Consumers request projections through `value(name)`. OGJS tracks dependencies so visibility // and presentation refresh together when source data changes. og.transformations.addFilter(({ value }) => value('risk') >= threshold); og.styles.addNodeRule(({ value }) => ({ color: value('band') === 'critical' ? '#dc2626' : value('band') === 'review' ? '#f59e0b' : '#64748b', size: 8 + value('risk') / 18, })); og.styles.addEdgeRule(({ value }) => ({ color: value('exposure') >= 180 ? '#dc2626' : '#94a3b8', width: Math.max(1, value('exposure') / 70), })); const ids = ['alert', 'account', 'device', 'merchant', 'beneficiary']; const visibleCount = document.querySelector('[data-testid="visible-count"]'); const status = document.querySelector('#projection-status'); function report() { const risks = Object.fromEntries(ids.map((id) => [id, og.projections.nodeValue(id, 'risk')])); const visible = og.getVisible().filter((id) => ids.includes(id)).length; const exposure = og.projections.edgeValue('alert-account', 'exposure'); visibleCount.textContent = `${visible} / ${ids.length} visible`; status.textContent = `alert → account exposure ${exposure} · account risk ${risks.account}`; } document.querySelector('#threshold').addEventListener('change', (event) => { threshold = Number(event.target.value); og.transformations.refreshVisibility(); report(); }); document.querySelector('#raise-account').addEventListener('click', () => { og.updateNode('account', { data: { base: 90 } }); // Refresh invalidates derived values and their dependent rules without replacing the graph, // moving nodes, or recreating projection definitions. og.projections.refresh(); updated = true; report(); }); og.camera.fit(); report(); </script></body></html>