@dagrejs/dagre
Version:
Graph layout for JavaScript
96 lines (82 loc) • 2.37 kB
JavaScript
var Graph = require("@dagrejs/graphlib").Graph;
var slack = require("./util").slack;
module.exports = feasibleTree;
/*
* Constructs a spanning tree with tight edges and adjusted the input node's
* ranks to achieve this. A tight edge is one that is has a length that matches
* its "minlen" attribute.
*
* The basic structure for this function is derived from Gansner, et al., "A
* Technique for Drawing Directed Graphs."
*
* Pre-conditions:
*
* 1. Graph must be a DAG.
* 2. Graph must be connected.
* 3. Graph must have at least one node.
* 5. Graph nodes must have been previously assigned a "rank" property that
* respects the "minlen" property of incident edges.
* 6. Graph edges must have a "minlen" property.
*
* Post-conditions:
*
* - Graph nodes will have their rank adjusted to ensure that all edges are
* tight.
*
* Returns a tree (undirected graph) that is constructed using only "tight"
* edges.
*/
function feasibleTree(g) {
var t = new Graph({ directed: false });
// Choose arbitrary node from which to start our tree
var start = g.nodes()[0];
var size = g.nodeCount();
t.setNode(start, {});
var edge, delta;
while (tightTree(t, g) < size) {
edge = findMinSlackEdge(t, g);
delta = t.hasNode(edge.v) ? slack(g, edge) : -slack(g, edge);
shiftRanks(t, g, delta);
}
return t;
}
/*
* Finds a maximal tree of tight edges and returns the number of nodes in the
* tree.
*/
function tightTree(t, g) {
function dfs(v) {
g.nodeEdges(v).forEach(e => {
var edgeV = e.v,
w = (v === edgeV) ? e.w : edgeV;
if (!t.hasNode(w) && !slack(g, e)) {
t.setNode(w, {});
t.setEdge(v, w, {});
dfs(w);
}
});
}
t.nodes().forEach(dfs);
return t.nodeCount();
}
/*
* Finds the edge with the smallest slack that is incident on tree and returns
* it.
*/
function findMinSlackEdge(t, g) {
const edges = g.edges();
return edges.reduce((acc, edge) => {
let edgeSlack = Number.POSITIVE_INFINITY;
if (t.hasNode(edge.v) !== t.hasNode(edge.w)) {
edgeSlack = slack(g, edge);
}
if (edgeSlack < acc[0]) {
return [edgeSlack, edge];
}
return acc;
}, [Number.POSITIVE_INFINITY, null])[1];
}
function shiftRanks(t, g, delta) {
t.nodes().forEach(v => g.node(v).rank += delta);
}
;