Visual grouping manual
Drag nodes on each other to group them.
Drag a group over a node to add it into the group.
Drag a group over another to merge them.
<!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>
var ogma = new Ogma({
container: 'graph-container'
});
// incremental counter for group ids
var groupId = 1;
var groupColors = {};
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
function areOnTheSameGroup(nodeA, nodeB) {
var groupNodeA = nodeA.getData('parent') || nodeA.getData('group');
var groupNodeB = nodeB.getData('parent') || nodeB.getData('group');
return groupNodeA && groupNodeB && groupNodeA === groupNodeB;
}
function getNodesGroups(nodes) {
return nodes.reduce(function (groups, node) {
var groupId = node.getData('parent');
if (!groupId) {
// if the node does not have a group, just pushes it
return groups.concat(node);
}
if (
// avoid pushing twice a group in the result array
!groups.filter(function (group) {
return groupId === group.getId();
}).length
) {
return groups.concat(
ogma
.getNodes()
.filter(function (node) {
return node.getData('group') === groupId;
})
.get(0)
);
}
return groups;
}, []);
}
function merge(draggedNode, nodesAround) {
var nodePos = draggedNode.getPositionOnScreen();
var draggedNodeRadius =
draggedNode.getAttribute('radius') * ogma.view.getZoom();
// find the nodes which are intersecting with the draggedNode
var nodesToMergeWith = nodesAround.reduce(function (bestNodes, candidate) {
var pos = candidate.getPositionOnScreen();
var dist =
Ogma.geometry.distance(nodePos.x, nodePos.y, pos.x, pos.y) -
candidate.getAttribute('radius') * ogma.view.getZoom();
return dist < draggedNodeRadius
? bestNodes.concat(candidate)
: bestNodes;
}, []);
// no merge candidate found
if (!nodesToMergeWith.length) return [];
// merge the candidates with the dragged node
var nodesToMerge = nodesToMergeWith.concat(draggedNode);
// if one of the elements of the nodes to merge is a group, reuse its id
var newGroupId = nodesToMerge
.map(function (node) {
return node.getData('group');
})
.filter(function (group) {
return group;
})[0];
// if none of the elements is a group, just create a new one
if (!newGroupId) {
newGroupId = groupId++;
groupColors[newGroupId] = getRandomColor();
}
nodesToMerge.forEach(function (node) {
var subNodes = node.getSubNodes();
// if the node is a group assign the group id to its children
if (subNodes) {
subNodes.fillData('parent', newGroupId);
} else {
node.setData('parent', newGroupId);
}
});
return nodesToMerge;
}
ogma.generate
.random()
.then(function (graph) {
return ogma.setGraph(graph);
})
.then(function () {
return ogma.layouts.force({ locate: true });
});
ogma.transformations.addNodeGrouping({
groupIdFunction: function (node) {
return node.getData('parent');
},
nodeGenerator: function (nodes, groupId) {
return {
data: {
group: groupId
}
};
},
showContents: true,
onCreated: function (metaNode, visible, subNodes) {
return ogma.layouts.force({ nodes: subNodes });
}
});
ogma.styles.addRule({
nodeSelector: function (node) {
return node.getSubNodes() !== null;
},
nodeAttributes: {
color: function (node) {
return groupColors[node.getData('group')];
},
opacity: 0.25,
layer: 0
}
});
ogma.styles.addRule({
nodeSelector: function (node) {
return node.getSubNodes() === null;
},
nodeAttributes: {
layer: 2
}
});
ogma.events.onNodeDragEnd(function (data) {
var nodes = getNodesGroups(data.nodes);
var forbiddenSet = nodes.reduce(function (acc, node) {
acc[node.getId()] = true;
return acc;
}, {});
var nodesAround = ogma.getNodes();
nodes.forEach(function (draggedNode) {
var candidateNodesToMerge = nodesAround
// nodes to merge with cant be the dragged ones
// nor the nodes which have already been merged innto
// a previous iteration of the loop
.filter(function (node) {
return (
!forbiddenSet[node.getId()] &&
// nor the parent of te dragged node
!areOnTheSameGroup(node, draggedNode)
);
});
var nodesMerged = merge(draggedNode, candidateNodesToMerge);
// add the merged nodes to the forbidden set
nodesMerged.forEach(function (node) {
return (forbiddenSet[node.getId()] = true);
});
});
});
</script>
</body>
</html>