Appearance
Incremental ungrouping
This example shows how to use the incremental
parametter in the force layout. It allows to easilly expand and collapse the nodes in the graph.
Double-click green nodes to incrementally expand the grouped nodes.
Double-click expanded nodes to collapse their leafs.
ts
import Ogma, { Node, RawGraph } from '@linkurious/ogma';
const ogma = new Ogma({
container: 'graph-container'
});
const duration = 200;
const isRoot = (n: Node) => n.getDegree() > 3;
// Generates graph with high-degree nodes
const generateDenseTree = (
N: number,
maxR = 5,
minR = 5
): Promise<RawGraph> => {
return new Promise(resolve => {
const nodes = new Array(N).fill(0).map((_, i) => {
return {
id: i,
attributes: {
text: 'Node #' + i,
radius: minR + Math.random() * (maxR - minR),
x: -50 + Math.random() * 100,
y: -50 + Math.random() * 100
}
};
});
const edges = new Array(N - 1).fill(0).map((_, i) => {
return {
id: i,
source: Math.floor(Math.sqrt(i) / 2),
target: i + 1
};
});
resolve({ nodes: nodes, edges: edges });
});
};
// collapses node leafs
const collapseNode = (node: Node, duration: number = 0) => {
const group = getTransformationById(+node.getId());
if (group) {
group.enable();
return group;
}
const leafs = node
.getAdjacentNodes()
.filter(node => !node.isVirtual() && !node.getData('isCore'));
const nodesToGroup = new Set(leafs.getId());
nodesToGroup.add(node.getId());
// add collapse grouping rule
return ogma.transformations.addNodeGrouping({
restorePositions: false,
selector: node => nodesToGroup.has(node.getId()),
nodeGenerator: (nodes, id, transformation) => ({
id: 'p' + node.getId(),
attributes: {
text: 'Parent ' + node.getId(),
color: '#008800'
},
data: {
groupId: transformation.getId(),
centralNode: node.getId(), // link to central node,
isMetaNode: true
}
}),
duration
});
};
const getTransformationById = (id: number) => {
const transformations = ogma.transformations.getList();
for (let i = 0; i < transformations.length; i++) {
if (transformations[i].getId() === id) {
return transformations[i];
}
}
return null;
};
// Expands high-degree node
const expandNode = (node: Node, duration = 0) => {
// retrieve grouping data
const nodes = node.getSubNodes()!;
const groupId = node.getData('groupId');
// remove group and use incremental positioning layout
return getTransformationById(groupId)!
.disable()
.then(() => {
return ogma.layouts.force({
nodes,
incremental: true,
duration
});
});
};
// Utility function to group "flower"-nodes for this demo
const collapseFlowerNodes = () => {
const coreNodes = ogma.getNodes().filter(isRoot);
coreNodes.setData('isCore', new Array(coreNodes.size).fill(true));
coreNodes.setAttributes({ color: 'green' });
// collapse flower-nodes
return Promise.all(
coreNodes.map(n => {
return collapseNode(n).whenApplied();
})
);
};
// generate graph and collapse the nodes
const graph = await generateDenseTree(300);
await ogma.setGraph(graph);
await collapseFlowerNodes();
await ogma.layouts.force({ locate: true });
await ogma.view.setZoom(ogma.view.getZoom() * 0.25);
await new Promise(resolve => setTimeout(resolve, 2000));
expandNode(ogma.getNode('p8')!, duration);
// expand or collapse on double click
ogma.events.on('doubleclick', evt => {
if (evt.target && evt.target.isNode) {
const node = evt.target;
if (node.getData('isMetaNode')) {
// expand grouped node
expandNode(node, duration).then(() => {
ogma.view.locateGraph({ duration });
});
} else if (node.getData('isCore')) {
// collapse high-degree node
collapseNode(node, duration);
}
}
});
html
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<link type="text/css" rel="stylesheet" href="styles.css" />
</head>
<body>
<div id="graph-container"></div>
<script type="module" src="index.ts"></script>
</body>
</html>
css
#graph-container {
top: 0;
bottom: 0;
left: 0;
right: 0;
position: absolute;
margin: 0;
overflow: hidden;
}