vis-network
Version:
A dynamic, browser-based visualization library.
101 lines (87 loc) • 2.39 kB
text/typescript
type Levels = Record<string | number, number>;
interface Edge {
connected: boolean;
from: Node;
fromId: string | number;
to: Node;
toId: string | number;
}
interface Node {
id: string | number;
edges: Edge[];
}
/**
* Try to assign levels to nodes according to their positions in the cyclic “hierarchy”.
*
* @param nodes - Nodes of the graph.
* @param levels - If present levels will be added to it, if not a new object will be created.
*
* @returns Populated node levels.
*/
function fillLevelsByDirectionCyclic(nodes: Node[], levels: Levels): Levels {
const edges = new Set<Edge>();
nodes.forEach((node): void => {
node.edges.forEach((edge): void => {
if (edge.connected) {
edges.add(edge);
}
});
});
edges.forEach((edge): void => {
const fromId = edge.from.id;
const toId = edge.to.id;
if (levels[fromId] == null) {
levels[fromId] = 0;
}
if (levels[toId] == null || levels[fromId] >= levels[toId]) {
levels[toId] = levels[fromId] + 1;
}
});
return levels;
}
/**
* Assign levels to nodes according to their positions in the hierarchy.
*
* @param nodes - Nodes of the graph.
* @param levels - If present levels will be added to it, if not a new object will be created.
*
* @returns Populated node levels.
*/
export function fillLevelsByDirection(
nodes: Node[],
levels: Levels = Object.create(null)
): Levels {
const limit = nodes.length;
for (const leaf of nodes) {
if (!leaf.edges.every((edge): boolean => edge.to === leaf)) {
// Not a leaf.
continue;
}
levels[leaf.id] = 0;
const stack: Node[] = [leaf];
let done = 0;
let node: Node | undefined;
while ((node = stack.pop())) {
const edges = node.edges;
const newLevel = levels[node.id] - 1;
for (const edge of edges) {
if (!edge.connected || edge.to !== node || edge.to === edge.from) {
continue;
}
const fromId = edge.fromId;
const oldLevel = levels[fromId];
if (oldLevel == null || oldLevel > newLevel) {
levels[fromId] = newLevel;
stack.push(edge.from);
}
}
if (done > limit) {
// This would run forever on a cyclic graph.
return fillLevelsByDirectionCyclic(nodes, levels);
} else {
++done;
}
}
}
return levels;
}