// Mount the navigator against the same graph instance used by the canvas.
// OGJS synchronizes keyboard focus, semantic node and edge descriptions, selection, and
// camera movement, so the accessible view cannot drift from visual graph state.
import { Ogjs } from '../../src/index';
const og = new Ogjs({
container: '#viz', theme: 'light', accessibility: { label: 'Investigation' },
});
og.setGraph({ nodes: [
{ id: 'case', x: 0, y: 0, size: 15, color: '#0f172a', label: 'Case' },
{ id: 'account', x: -170, y: 105, size: 10, color: '#2563eb', label: 'Account' },
{ id: 'device', x: 170, y: 105, size: 10, color: '#7c3aed', label: 'Device' },
{ id: 'merchant', x: 170, y: -105, size: 10, color: '#ea580c', label: 'Merchant' },
{ id: 'location', x: -170, y: -105, size: 10, color: '#059669', label: 'Location' },
], edges: [
{ source: 'case', target: 'account', data: { label: 'owns' } },
{ source: 'case', target: 'device', data: { type: 'uses' } },
{ source: 'account', target: 'merchant', data: { predicate: 'paid' } },
{ source: 'device', target: 'location', data: { label: 'observed at' } },
{ source: 'merchant', target: 'location', data: { label: 'operates at' } },
] });
// Mounting derives a semantic representation from the live graph. Labels, relationships, focus,
// and selection cannot drift because there is no duplicate application-maintained HTML graph.
og.navigator.mount(document.querySelector('#semantic'));
og.camera.fit();
document.querySelector('#add-evidence').onclick = () => {
if (og.graph.getNodeIndex('evidence') !== undefined) return;
og.addNodes([{ id: 'evidence', x: 0, y: -185, size: 9, color: '#db2777', label: 'Evidence' }]);
og.addEdges([{ source: 'case', target: 'evidence', data: { label: 'supported by' } }]);
// Mutations automatically update the semantic view; callers do not patch a second accessibility
// tree after adding evidence and its relationship.
og.camera.fit();
};
const peers = [];
function createPeer(enabled = true) {
// Each peer receives independent semantic ownership, keyboard focus, and teardown. Disabling
// accessibility for one peer does not change the primary graph or any other instance.
const host = document.createElement('div');
host.className = 'peer-host';
document.body.append(host);
const peer = new Ogjs({
container: host,
theme: 'light',
accessibility: enabled ? { label: 'Peer' } : false,
});
peer.setGraph({
nodes: [{ id: 'peer-a', label: 'Peer A' }, { id: 'peer-b', label: 'Peer B' }],
edges: [{ source: 'peer-a', target: 'peer-b' }],
});
peers.push(peer);
}
<!doctype html>
<html lang="en"><head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Ogjs example — Semantic graph navigation</title>
<link rel="stylesheet" href="../site.css"/>
<style>
#viz { position: fixed; inset: 52px 0 0 330px; }
#semantic { position: fixed; inset: 52px auto 0 0; width: 330px; z-index: 4;
padding: 22px 18px; overflow: auto; color: #172033; background: #f8fafc;
border-right: 1px solid #dbe2ea; }
#semantic h1 { font: 750 18px/1.2 var(--sans); }
#semantic > p { margin-top: 7px; color: #526174; font: 400 12.5px/1.5 var(--sans); }
#semantic .hint { margin: 14px 0; padding: 9px 10px; color: #334155; background: #eef2f7;
border-radius: 6px; font: 500 10.5px/1.6 var(--mono); }
#semantic .ogjs-semantic-list { display: grid; gap: 6px; }
#semantic .ogjs-semantic-node, #semantic .ogjs-semantic-edge { padding: 9px 10px; background: #fff; border: 1px solid #dbe2ea;
border-radius: 7px; cursor: default; font: 600 12px var(--sans); }
#semantic .ogjs-semantic-edge { margin-left: 14px; color: #475569; border-style: dashed; }
#semantic .ogjs-semantic-node[aria-selected="true"] { border-color: #f59e0b; background: #fffbeb; }
#semantic [role="option"]:focus-visible { outline: 3px solid #2563eb; outline-offset: 1px; }
#semantic .ogjs-semantic-status { margin-top: 10px; color: #526174; font: 600 10.5px var(--mono); }
#add-evidence { margin-top: 14px; padding: 8px 11px; color: #fff; background: #172033;
border: 0; border-radius: 6px; cursor: pointer; font: 650 12px var(--sans); }
.peer-host { position: fixed; width: 2px; height: 2px; overflow: hidden; left: -10px; top: -10px; }
@media (max-width: 760px) { #semantic { width: 100%; height: 300px; bottom: auto; }
#viz { inset: 352px 0 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>
<aside id="semantic"><h1>Semantic navigation</h1>
<p>The canvas and this synchronized node-and-edge list describe the same graph state.</p>
<div class="hint">↑↓ all items · node → edge → target · Enter activates · Space selects · Esc clears</div>
<button id="add-evidence">Add evidence node</button>
</aside>
<script type="module">
// Mount the navigator against the same graph instance used by the canvas.
// OGJS synchronizes keyboard focus, semantic node and edge descriptions, selection, and
// camera movement, so the accessible view cannot drift from visual graph state.
import { Ogjs } from '../../src/index';
const og = new Ogjs({
container: '#viz', theme: 'light', accessibility: { label: 'Investigation' },
});
og.setGraph({ nodes: [
{ id: 'case', x: 0, y: 0, size: 15, color: '#0f172a', label: 'Case' },
{ id: 'account', x: -170, y: 105, size: 10, color: '#2563eb', label: 'Account' },
{ id: 'device', x: 170, y: 105, size: 10, color: '#7c3aed', label: 'Device' },
{ id: 'merchant', x: 170, y: -105, size: 10, color: '#ea580c', label: 'Merchant' },
{ id: 'location', x: -170, y: -105, size: 10, color: '#059669', label: 'Location' },
], edges: [
{ source: 'case', target: 'account', data: { label: 'owns' } },
{ source: 'case', target: 'device', data: { type: 'uses' } },
{ source: 'account', target: 'merchant', data: { predicate: 'paid' } },
{ source: 'device', target: 'location', data: { label: 'observed at' } },
{ source: 'merchant', target: 'location', data: { label: 'operates at' } },
] });
// Mounting derives a semantic representation from the live graph. Labels, relationships, focus,
// and selection cannot drift because there is no duplicate application-maintained HTML graph.
og.navigator.mount(document.querySelector('#semantic'));
og.camera.fit();
document.querySelector('#add-evidence').onclick = () => {
if (og.graph.getNodeIndex('evidence') !== undefined) return;
og.addNodes([{ id: 'evidence', x: 0, y: -185, size: 9, color: '#db2777', label: 'Evidence' }]);
og.addEdges([{ source: 'case', target: 'evidence', data: { label: 'supported by' } }]);
// Mutations automatically update the semantic view; callers do not patch a second accessibility
// tree after adding evidence and its relationship.
og.camera.fit();
};
const peers = [];
function createPeer(enabled = true) {
// Each peer receives independent semantic ownership, keyboard focus, and teardown. Disabling
// accessibility for one peer does not change the primary graph or any other instance.
const host = document.createElement('div');
host.className = 'peer-host';
document.body.append(host);
const peer = new Ogjs({
container: host,
theme: 'light',
accessibility: enabled ? { label: 'Peer' } : false,
});
peer.setGraph({
nodes: [{ id: 'peer-a', label: 'Peer A' }, { id: 'peer-b', label: 'Peer B' }],
edges: [{ source: 'peer-a', target: 'peer-b' }],
});
peers.push(peer);
}
</script></body></html>