Group first, then double-click a meta-node: members bloom out in a circle-packed arrangement with a 450ms ease-out tween. Double-click a member to collapse its group.
// A cluster key is the only grouping geometry input. OGJS creates aggregate relationships,
// packs members when a group opens, animates the transition, and restores the collapsed
// state without asking the application to position members or repair the camera.
import { Ogjs } from '../../src/index';
const og = new Ogjs({ container: '#viz', theme: 'light' });
const nodes = [], edges = [];
const PAL = ['#4f8cff', '#47d78a', '#f2a33c', '#e5484d', '#9d6bff'];
for (let c = 0; c < 5; c++) for (let k = 0; k < 24; k++) {
const i = c * 24 + k;
nodes.push({ id: i, size: k === 0 ? 10 : 4, color: PAL[c],
label: k === 0 ? 'cluster ' + c : undefined, data: { cluster: c } });
if (k > 0) edges.push({ source: i, target: c * 24 + (k > 4 ? (i % k) : 0) });
}
for (let c = 0; c < 5; c++) edges.push({ source: c * 24, target: ((c + 1) % 5) * 24 });
og.setGraph({ nodes, edges });
og.layouts.force({ sync: true, iterations: 260, springLength: 46, repulsion: 1400 });
og.camera.fit();
og.enableHoverHighlight();
document.querySelector('#group').addEventListener('click', async () => {
// The key function is the complete collapse instruction. OGJS derives meta-nodes, aggregates
// relationships, animates the change, and retains the source topology for restoration.
await og.groupByAnimated(({ data }) => 'cluster ' + data.cluster);
await og.animateFit();
});
og.on('nodedblclick', async ({ nodeId: id }) => {
if (typeof id === 'string' && id.startsWith('__group:')) {
// Expand only the activated group. OGJS places its members locally while preserving every
// unrelated cluster's coordinates and collapsed state.
await og.expandGroup(id.slice(8));
} else {
// A visible member resolves back to its retained group key, allowing the same gesture to
// collapse that group without the application tracking parent objects itself.
const key = og.transformations.groupKeyOf(id);
if (key && !og.transformations.isCollapsed(key)) await og.collapseGroup(key);
}
});
<!doctype html>
<html lang="en"><head><meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<title>Ogjs example — Expand / collapse</title>
<link rel="stylesheet" href="../site.css"/>
<style>
#viz { position: fixed; inset: 52px 0 0 0; }
.panel { position: fixed; top: 66px; left: 14px; z-index: 5; max-width: 380px;
background: #ffffff; border: 1px solid #e2e6ec; border-radius: 8px;
padding: 12px 14px; box-shadow: 0 2px 12px rgba(15, 23, 42, 0.08); }
.panel h1 { font: 700 16px var(--sans); color: #0f172a; margin-bottom: 4px; }
.panel p { font: 400 12.5px/1.5 var(--sans); color: #475569; }
.panel .btns { display: flex; gap: 6px; margin-top: 10px; flex-wrap: wrap; }
.panel button { font: 600 12px var(--sans); background: #f1f5f9; color: #0f172a;
border: 1px solid #cbd5e1; border-radius: 5px; padding: 6px 11px; cursor: pointer; }
.panel button:hover { background: #e2e8f0; border-color: #94a3b8; }
</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>
<div class="panel"><h1>Expand / collapse</h1><p>Group first, then double-click a meta-node: members bloom out in a circle-packed arrangement with a 450ms ease-out tween. Double-click a member to collapse its group.</p><div class="btns"><button id="group">Group clusters</button></div></div>
<script type="module">
// A cluster key is the only grouping geometry input. OGJS creates aggregate relationships,
// packs members when a group opens, animates the transition, and restores the collapsed
// state without asking the application to position members or repair the camera.
import { Ogjs } from '../../src/index';
const og = new Ogjs({ container: '#viz', theme: 'light' });
const nodes = [], edges = [];
const PAL = ['#4f8cff', '#47d78a', '#f2a33c', '#e5484d', '#9d6bff'];
for (let c = 0; c < 5; c++) for (let k = 0; k < 24; k++) {
const i = c * 24 + k;
nodes.push({ id: i, size: k === 0 ? 10 : 4, color: PAL[c],
label: k === 0 ? 'cluster ' + c : undefined, data: { cluster: c } });
if (k > 0) edges.push({ source: i, target: c * 24 + (k > 4 ? (i % k) : 0) });
}
for (let c = 0; c < 5; c++) edges.push({ source: c * 24, target: ((c + 1) % 5) * 24 });
og.setGraph({ nodes, edges });
og.layouts.force({ sync: true, iterations: 260, springLength: 46, repulsion: 1400 });
og.camera.fit();
og.enableHoverHighlight();
document.querySelector('#group').addEventListener('click', async () => {
// The key function is the complete collapse instruction. OGJS derives meta-nodes, aggregates
// relationships, animates the change, and retains the source topology for restoration.
await og.groupByAnimated(({ data }) => 'cluster ' + data.cluster);
await og.animateFit();
});
og.on('nodedblclick', async ({ nodeId: id }) => {
if (typeof id === 'string' && id.startsWith('__group:')) {
// Expand only the activated group. OGJS places its members locally while preserving every
// unrelated cluster's coordinates and collapsed state.
await og.expandGroup(id.slice(8));
} else {
// A visible member resolves back to its retained group key, allowing the same gesture to
// collapse that group without the application tracking parent objects itself.
const key = og.transformations.groupKeyOf(id);
if (key && !og.transformations.isCollapsed(key)) await og.collapseGroup(key);
}
});</script>
</body></html>