90 lines
2.1 KiB
JavaScript
90 lines
2.1 KiB
JavaScript
import _ from 'lodash'
|
|
import { Graph } from 'graphlibrary'
|
|
|
|
import { slack } from './util'
|
|
|
|
/*
|
|
* 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) {
|
|
const t = new Graph({ directed: false })
|
|
|
|
// Choose arbitrary node from which to start our tree
|
|
const start = g.nodes()[0]
|
|
const size = g.nodeCount()
|
|
t.setNode(start, {})
|
|
|
|
let edge
|
|
let 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) {
|
|
_.forEach(g.nodeEdges(v), function (e) {
|
|
const edgeV = e.v
|
|
const w = (v === edgeV) ? e.w : edgeV
|
|
if (!t.hasNode(w) && !slack(g, e)) {
|
|
t.setNode(w, {})
|
|
t.setEdge(v, w, {})
|
|
dfs(w)
|
|
}
|
|
})
|
|
}
|
|
|
|
_.forEach(t.nodes(), dfs)
|
|
return t.nodeCount()
|
|
}
|
|
|
|
/*
|
|
* Finds the edge with the smallest slack that is incident on tree and returns
|
|
* it.
|
|
*/
|
|
function findMinSlackEdge (t, g) {
|
|
return _.minBy(g.edges(), function (e) {
|
|
if (t.hasNode(e.v) !== t.hasNode(e.w)) {
|
|
return slack(g, e)
|
|
}
|
|
})
|
|
}
|
|
|
|
function shiftRanks (t, g, delta) {
|
|
_.forEach(t.nodes(), function (v) {
|
|
g.node(v).rank += delta
|
|
})
|
|
}
|
|
|
|
export default feasibleTree
|