OG
JS
Docs
Demos
Examples
Forge editing workbench
Arm one tool, then drag directly on the graph. Every completed gesture is one undoable edit.
Join nodes
Reconnect account link
Resize case
Rename case
Snap 25
Undo
Redo
// Each toolbar action arms an intent-level edit such as join, retarget, resize, or rename. // Forge owns pointer gestures, validation, snapping, history boundaries, and settled-state // notifications, so one completed gesture becomes one undoable application action. import { Ogjs } from '../../src/index'; const og = new Ogjs({ container: '#viz', theme: 'light' }); og.setGraph({ nodes: [ { id: 'case', label: 'Case 1042', x: -150, y: 0, size: 18, color: '#d47a50' }, { id: 'account', label: 'Account', x: 15, y: 105, size: 13, color: '#4f7fbf' }, { id: 'device', label: 'Device', x: 170, y: 0, size: 13, color: '#8b72c9' }, { id: 'merchant', label: 'Merchant', x: 15, y: -115, size: 13, color: '#d39a3c' }, ], edges: [ { id: 'case-account', source: 'case', target: 'account', caption: 'owns', curvature: .12, width: 1.8 }, { id: 'case-device', source: 'case', target: 'device', caption: 'uses', curvature: -.1, width: 1.8 }, { id: 'case-merchant', source: 'case', target: 'merchant', caption: 'paid', curvature: .12, width: 1.8 }, ], }); og.camera.fit(); const controls = { join: document.querySelector('#join'), retarget: document.querySelector('#retarget'), scale: document.querySelector('#scale'), rename: document.querySelector('#rename'), grid: document.querySelector('#grid'), undo: document.querySelector('#undo'), redo: document.querySelector('#redo'), status: document.querySelector('#forge-status'), }; let grid = false; let dropSerial = 0; function update(message = '', history = og.history.state) { controls.undo.disabled = !history.canUndo; controls.redo.disabled = !history.canRedo; controls.grid.setAttribute('aria-pressed', String(grid)); const { nodes, edges } = og.stats(); const state = { nodes, edges, active: og.forge.active, undo: history.undoLabel, rejection: og.forge.lastRejection, message, }; controls.status.textContent = message || `${state.nodes} nodes · ${state.edges} links · next undo: ${state.undo ?? 'none'}`; } // Validate the completed proposal before Forge records it as one history entry. // Returning a message rejects the gesture and gives the UI a developer-readable reason. og.forge.setValidator((proposal) => { if (proposal.kind === 'join' && proposal.edge.target === 'merchant') return 'Merchant is read-only'; return true; }); og.forge.acceptDrops((data, point) => { const label = data.getData('text/plain').trim(); if (!label) return null; return { nodes: [{ id: `drop-${++dropSerial}`, label, x: point.x, y: point.y, size: 11, color: '#059669' }] }; }); controls.join.onclick = () => { og.forge.join({ color: '#4d9a78', width: 2, curvature: .14, arrow: true, caption: 'new' }); update('Drag from one node to another'); }; controls.retarget.onclick = () => { og.forge.reconnect('case-account'); update('Drag Account onto the new endpoint'); }; controls.scale.onclick = () => { og.forge.resize('case'); update('Drag outward from Case 1042'); }; controls.rename.onclick = () => { og.forge.rename('case'); update('Type a new label, then press Enter'); }; controls.grid.onclick = () => { grid = !grid; og.forge.grid(grid ? 25 : null); update(grid ? '25-unit snap enabled' : 'Snap disabled'); }; controls.undo.onclick = () => { og.history.undo(); }; controls.redo.onclick = () => { og.history.redo(); }; og.history.onChange((state) => update('', state)); og.forge.onChange(() => update()); update();
<!doctype html> <html lang="en"><head><meta charset="utf-8"/> <meta name="viewport" content="width=device-width, initial-scale=1"/> <title>Ogjs example — Forge editing workbench</title> <link rel="stylesheet" href="../site.css"/><link rel="stylesheet" href="./data-lifecycle.css"/> <style>#viz { left: 452px; } @media (max-width: 760px) { #viz { top: 300px; 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>Forge editing workbench</h1> <p>Arm one tool, then drag directly on the graph. Every completed gesture is one undoable edit.</p> <div class="controls"> <button id="join">Join nodes</button><button id="retarget">Reconnect account link</button> <button id="scale">Resize case</button><button id="rename">Rename case</button> <button id="grid" aria-pressed="false">Snap 25</button> <button id="undo" disabled>Undo</button><button id="redo" disabled>Redo</button> </div> <div class="status" id="forge-status" aria-live="polite"></div> </section> <script type="module"> // Each toolbar action arms an intent-level edit such as join, retarget, resize, or rename. // Forge owns pointer gestures, validation, snapping, history boundaries, and settled-state // notifications, so one completed gesture becomes one undoable application action. import { Ogjs } from '../../src/index'; const og = new Ogjs({ container: '#viz', theme: 'light' }); og.setGraph({ nodes: [ { id: 'case', label: 'Case 1042', x: -150, y: 0, size: 18, color: '#d47a50' }, { id: 'account', label: 'Account', x: 15, y: 105, size: 13, color: '#4f7fbf' }, { id: 'device', label: 'Device', x: 170, y: 0, size: 13, color: '#8b72c9' }, { id: 'merchant', label: 'Merchant', x: 15, y: -115, size: 13, color: '#d39a3c' }, ], edges: [ { id: 'case-account', source: 'case', target: 'account', caption: 'owns', curvature: .12, width: 1.8 }, { id: 'case-device', source: 'case', target: 'device', caption: 'uses', curvature: -.1, width: 1.8 }, { id: 'case-merchant', source: 'case', target: 'merchant', caption: 'paid', curvature: .12, width: 1.8 }, ], }); og.camera.fit(); const controls = { join: document.querySelector('#join'), retarget: document.querySelector('#retarget'), scale: document.querySelector('#scale'), rename: document.querySelector('#rename'), grid: document.querySelector('#grid'), undo: document.querySelector('#undo'), redo: document.querySelector('#redo'), status: document.querySelector('#forge-status'), }; let grid = false; let dropSerial = 0; function update(message = '', history = og.history.state) { controls.undo.disabled = !history.canUndo; controls.redo.disabled = !history.canRedo; controls.grid.setAttribute('aria-pressed', String(grid)); const { nodes, edges } = og.stats(); const state = { nodes, edges, active: og.forge.active, undo: history.undoLabel, rejection: og.forge.lastRejection, message, }; controls.status.textContent = message || `${state.nodes} nodes · ${state.edges} links · next undo: ${state.undo ?? 'none'}`; } // Validate the completed proposal before Forge records it as one history entry. // Returning a message rejects the gesture and gives the UI a developer-readable reason. og.forge.setValidator((proposal) => { if (proposal.kind === 'join' && proposal.edge.target === 'merchant') return 'Merchant is read-only'; return true; }); og.forge.acceptDrops((data, point) => { const label = data.getData('text/plain').trim(); if (!label) return null; return { nodes: [{ id: `drop-${++dropSerial}`, label, x: point.x, y: point.y, size: 11, color: '#059669' }] }; }); controls.join.onclick = () => { og.forge.join({ color: '#4d9a78', width: 2, curvature: .14, arrow: true, caption: 'new' }); update('Drag from one node to another'); }; controls.retarget.onclick = () => { og.forge.reconnect('case-account'); update('Drag Account onto the new endpoint'); }; controls.scale.onclick = () => { og.forge.resize('case'); update('Drag outward from Case 1042'); }; controls.rename.onclick = () => { og.forge.rename('case'); update('Type a new label, then press Enter'); }; controls.grid.onclick = () => { grid = !grid; og.forge.grid(grid ? 25 : null); update(grid ? '25-unit snap enabled' : 'Snap disabled'); }; controls.undo.onclick = () => { og.history.undo(); }; controls.redo.onclick = () => { og.history.redo(); }; og.history.onChange((state) => update('', state)); og.forge.onChange(() => update()); update(); </script></body></html>