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