Incremental ungrouping
Double-click green nodes to incrementally expand the grouped nodes. Double-click expanded nodes to collapse their leafs.
Open in a new window.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="../build/ogma.min.js"></script>
<style>
#graph-container { top: 0; bottom: 0; left: 0; right: 0; position: absolute; margin: 0; overflow: hidden; }
</style>
</head>
<body>
<div id="graph-container"></div>
<script>
'use strict';
var ogma = new Ogma({
container: 'graph-container'
});
var duration = 500;
function isLeaf(n) { return n.getDegree({ filter: 'all' }) === 1; }
function isRoot(n) { return n.getDegree() > 3; }
// Generates graph with high-degree nodes
function generateDenseTree(N, maxR, minR) {
maxR = maxR || 5;
minR = minR || 5;
return new Promise(function(resolve, reject) {
var nodes = new Array(N).fill(0).map(function(_, i) {
return {
id: i,
attributes: {
text: 'Node #' + i,
radius: minR + Math.random() * (maxR - minR),
x: -50 + Math.random() * 100,
y: -50 + Math.random() * 100
}
};
});
var edges = new Array(N - 1).fill(0).map(function(_, i) {
return {
id: i,
source: Math.floor(Math.sqrt(i) / 2),
target: i + 1
};
});
resolve({ nodes: nodes, edges: edges });
});
}
// collapses node leafs
function collapseNode(node, duration) {
var leafs = node.getAdjacentNodes().filter(isLeaf);
var nodesToGroup = leafs.concat(node);
var pos = node.getPosition();
duration = duration || 0;
// add collapse grouping rule
return ogma.transformations.addNodeGrouping({
restorePositions: false,
selector: function(node) {
return nodesToGroup.includes(node);
},
nodeGenerator: function(nodes, id, transformation) {
return {
// keep the same position
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: duration
});
}
function getTransformationById(id) {
var transformations = ogma.transformations.getList();
for (var i = 0; i < transformations.length; i++) {
if (transformations[i].getId() === id) {
return transformations[i];
}
}
return null;
}
// Expands high-degree node
function expandNode(node, duration) {
// retrieve grouping data
var centralNode = ogma.getNode(node.getData('centralNode'));
var groupId = node.getData('groupId');
// use filters to retrieve hidden grouped nodes
var grouped = centralNode
.getAdjacentNodes({ filter: 'all' })
.filter(isLeaf)
.concat(centralNode);
// translate all nodes to origin before they re-appear on canvas
var currentPos = node.getPosition();
// remove group and use incremental positioning layout
return getTransformationById(groupId)
.delete()
.then(function () {
return grouped.setAttributes(currentPos);
})
.then(function() {
return ogma.layouts.forceLink({
nodes: grouped,
incremental: true,
randomize: 'locally', // to deal with node overlapping
duration: duration || 0
});
});
}
// Utility function to group "flower"-nodes for this demo
function collapseFlowerNodes() {
var coreNodes = ogma.getNodes().filter(isRoot);
coreNodes.setAttributes({ color: 'green' });
// collapse flower-nodes
return Promise.all(coreNodes.map(function (n) {
return collapseNode(n).whenApplied();
}));
}
// generate graph and collapse the nodes
generateDenseTree(300)
.then(function (graph) { return ogma.setGraph(graph); })
.then(collapseFlowerNodes)
.then(function () { return ogma.layouts.forceLink(); })
.then(function () { return ogma.view.locateGraph(); });
// expand or collapse on double click
ogma.events.onDoubleClick(function (evt) {
if (evt.target && evt.target.isNode) {
var node = evt.target;
if (node.getData('isMetaNode')) { // expand grouped node
expandNode(node, duration).then(function () {
ogma.view.locateGraph({ duration: duration });
});
} else if (node.getDegree() > 3) { // collapse high-degree node
collapseNode(node, duration);
}
}
});
</script>
</body>
</html>