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
Retarget 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: '#0f172a' }, { id: 'account', label: 'Account', x: 15, y: 105, size: 13, color: '#2563eb' }, { id: 'device', label: 'Device', x: 170, y: 0, size: 13, color: '#7c3aed' }, { id: 'merchant', label: 'Merchant', x: 15, y: -115, size: 13, color: '#ea580c' }, ], edges: [ { id: 'case-account', source: 'case', target: 'account', caption: 'owns' }, { id: 'case-device', source: 'case', target: 'device', caption: 'uses' }, { id: 'case-merchant', source: 'case', target: 'merchant', caption: 'paid' }, ], }); 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: '#059669', arrow: true, caption: 'new' }); update('Drag from one node to another'); }; controls.retarget.onclick = () => { og.forge.retarget('case-account', 'target'); update('Drag the Account endpoint onto another node'); }; controls.scale.onclick = () => { og.forge.scale('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();