Appearance
Visual grouping manual
This is an example of what one could achieve with the grouping feature.
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.
ts
import Ogma, { Color, NodeList, Node, NodeId } from '@linkurious/ogma';
const ogma = new Ogma({
container: 'graph-container'
});
// Create an instance of Ogma and bind it to the graph-container.
let groupCount = 1;
const groups = new Map<NodeId, string>();
const colors = new Map<NodeId, Color>();
const getRandomColor = (): Color => {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
};
ogma.styles.addNodeRule(node => node.isVirtual(), {
color: node => colors.get(node.getId()),
opacity: 0.5
});
const graph = await ogma.generate.random({ nodes: 10, edges: 10 });
await ogma.setGraph(graph);
await ogma.layouts.force({ gpu: true, locate: true });
const randomNode = ogma
.getNodes()
.get(Math.floor(ogma.getNodes().size * Math.random()));
let distance = Infinity;
const closest = ogma.getNodes().reduce((acc, node) => {
const pos = node.getPosition();
const origin = randomNode.getPosition();
const currDistance = Ogma.geometry.distance(origin.x, origin.y, pos.x, pos.y);
if (currDistance < distance && node !== randomNode) {
distance = currDistance;
return node;
}
return acc;
}, randomNode);
console.log(randomNode.getId(), 'closest', closest.getId());
const initialGroupName = `group-${groupCount++}`;
groups.set(randomNode.getId(), initialGroupName);
groups.set(closest.getId(), initialGroupName);
colors.set(initialGroupName, getRandomColor());
const grouping = ogma.transformations.addNodeGrouping({
selector: node => groups.has(node.getId()),
groupIdFunction: node => groups.get(node.getId())!,
nodeGenerator: (nodes, id) => ({ id }),
showContents: true,
onGroupUpdate: (_, nodes) =>
ogma.layouts.force({
nodes,
edgeStrength: 2,
gravity: 0.1
})
});
ogma.events.on('dragEnd', ({ target }) => {
if (!target || !target.isNode) return;
const parent = target.getMetaNode();
const { x, y } = (parent || target).getPositionOnScreen();
const r =
Number((parent || target).getAttribute('radius')) * ogma.view.getZoom();
const overlapNodes = ogma.getNodes().filter(node => {
const pos = node.getPositionOnScreen();
const dist =
Ogma.geometry.distance(x, y, pos.x, pos.y) -
+node.getAttribute('radius') * ogma.view.getZoom();
return dist < r;
});
if (overlapNodes.size < 2) return;
const groupid =
overlapNodes
.map(node => (node.isVirtual() ? node.getId() : groups.get(node.getId())))
.filter(e => e)[0] || `group-${groupCount++}`;
if (!colors.has(groupid)) {
colors.set(groupid, getRandomColor());
}
overlapNodes.forEach(node => {
groups.set(node.getId(), groupid as string);
});
grouping.refresh();
});
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;
}