@antv/layout
Version:
graph layout algorithm
110 lines (96 loc) • 3.42 kB
text/typescript
import type { EdgeData, ID, NodeData } from '../../../types';
import { DagreGraph } from '../graph';
/*
* Constructs a graph that can be used to sort a layer of nodes. The graph will
* contain all base and subgraph nodes from the request layer in their original
* hierarchy and any edges that are incident on these nodes and are of the type
* requested by the "relationship" parameter.
*
* Nodes from the requested rank that do not have parents are assigned a root
* node in the output graph, which is set in the root graph attribute. This
* makes it easy to walk the hierarchy of movable nodes during ordering.
*
* Pre-conditions:
*
* 1. Input graph is a DAG
* 2. Base nodes in the input graph have a rank attribute
* 3. Subgraph nodes in the input graph has minRank and maxRank attributes
* 4. Edges have an assigned weight
*
* Post-conditions:
*
* 1. Output graph has all nodes in the movable rank with preserved
* hierarchy.
* 2. Root nodes in the movable layer are made children of the node
* indicated by the root attribute of the graph.
* 3. Non-movable nodes incident on movable nodes, selected by the
* relationship parameter, are included in the graph (without hierarchy).
* 4. Edges incident on movable nodes, selected by the relationship
* parameter, are added to the output graph.
* 5. The weights for copied edges are aggregated as need, since the output
* graph is not a multi-graph.
*/
export const buildLayerGraph = (
g: DagreGraph,
rank: number,
direction: 'in' | 'out',
) => {
const root = createRootNode(g);
const result = new DagreGraph<NodeData, EdgeData>();
result.addNode({ id: root, data: {} });
g.getAllNodes().forEach((v) => {
const parent = g.getParent(v.id);
if (
v.data.rank === rank ||
(v.data.minRank! <= rank && rank <= v.data.maxRank!)
) {
if (!result.hasNode(v.id)) {
result.addNode({ ...v });
}
if (parent?.id && !result.hasNode(parent?.id)) {
result.addNode({ ...parent });
}
result.setParent(v.id, parent?.id || root);
// This assumes we have only short edges!
g.getRelatedEdges(v.id, direction).forEach((e) => {
const u = e.source === v.id ? e.target : e.source;
if (!result.hasNode(u)) {
result.addNode({ ...g.getNode(u) });
}
const edge = result
.getRelatedEdges(u, 'out')
.find(({ target }) => target === v.id);
const weight = edge !== undefined ? edge.data.weight! : 0;
if (!edge) {
result.addEdge({
id: e.id,
source: u,
target: v.id,
data: {
weight: e.data.weight! + weight,
},
});
} else {
result.updateEdgeData(edge.id, {
...edge.data,
weight: e.data.weight! + weight,
});
}
});
// console.log(v);
if (v.data.hasOwnProperty('minRank')) {
result.updateNodeData(v.id, {
...v.data,
borderLeft: [(v.data.borderLeft as ID[])?.[rank - v.data.minRank!]],
borderRight: [(v.data.borderRight as ID[])?.[rank - v.data.minRank!]],
});
}
}
});
return result;
};
const createRootNode = (g: DagreGraph) => {
let v;
while (g.hasNode((v = `_root${Math.random()}`)));
return v;
};