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();