From bd1c10b4e6860056f139cbea96e0d55305100a03 Mon Sep 17 00:00:00 2001 From: Chris Pettitt Date: Sun, 14 Dec 2014 18:55:07 -0800 Subject: [PATCH] Redo the horizontal compaction phase I continue to come across new cases where the BK horizontal compaction algorithm fails. Instead of trying to continue to patch it, I've replaced it entirely with a simpler two-sweep DFS algorithm which works for all of the failing cases I've tested. Fixes #125 Fixes #158 --- lib/position/bk.js | 114 ++++++++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 58 deletions(-) diff --git a/lib/position/bk.js b/lib/position/bk.js index 6eab697d..70bc6466 100644 --- a/lib/position/bk.js +++ b/lib/position/bk.js @@ -1,6 +1,7 @@ "use strict"; var _ = require("../lodash"), + Graph = require("../graphlib").Graph, util = require("../util"); /* @@ -204,74 +205,71 @@ function verticalAlignment(g, layering, conflicts, neighborFn) { } function horizontalCompaction(g, layering, root, align, reverseSep) { - // We use local variables for these parameters instead of manipulating the - // graph because it becomes more verbose to access them in a chained manner. - var shift = {}, - shiftNeighbor = {}, - sink = {}, - xs = {}, - pred = {}, - graphLabel = g.graph(), - sepFn = sep(graphLabel.nodesep, graphLabel.edgesep, reverseSep); + // This portion of the algorithm differs from BK due to a number of problems. + // Instead of their algorithm we construct a new block graph and do two + // sweeps. The first sweep places blocks with the smallest possible + // coordinates. The second sweep removes unused space by moving blocks to the + // greatest coordinates without violating separation. + var xs = {}, + blockG = buildBlockGraph(g, layering, root, reverseSep); + + // First pass, assign smallest coordinates via DFS + var visited = {}; + function pass1(v) { + if (!_.has(visited, v)) { + visited[v] = true; + xs[v] = _.reduce(blockG.inEdges(v), function(max, e) { + pass1(e.v); + return Math.max(max, xs[e.v] + blockG.edge(e)); + }, 0); + } + } + _.each(blockG.nodes(), pass1); + + function pass2(v) { + if (visited[v] !== 2) { + visited[v]++; + var min = _.reduce(blockG.outEdges(v), function(min, e) { + pass2(e.w); + return Math.min(min, xs[e.w] - blockG.edge(e)); + }, Number.POSITIVE_INFINITY); + if (min !== Number.POSITIVE_INFINITY) { + xs[v] = Math.max(xs[v], min); + } + } + } + _.each(blockG.nodes(), pass2); - _.each(layering, function(layer) { - _.each(layer, function(v, order) { - sink[v] = v; - shift[v] = Number.POSITIVE_INFINITY; - pred[v] = layer[order - 1]; - }); - }); - _.each(g.nodes(), function(v) { - if (root[v] === v) { - placeBlock(g, layering, sepFn, root, align, shift, shiftNeighbor, sink, pred, xs, v); - } + // Assign x coordinates to all nodes + _.each(align, function(v) { + xs[v] = xs[root[v]]; }); + return xs; +} + + +function buildBlockGraph(g, layering, root, reverseSep) { + var blockGraph = new Graph(), + graphLabel = g.graph(), + sepFn = sep(graphLabel.nodesep, graphLabel.edgesep, reverseSep); + _.each(layering, function(layer) { + var u; _.each(layer, function(v) { - xs[v] = xs[root[v]]; - // This line differs from the source paper. See - // http://www.inf.uni-konstanz.de/~brandes/publications/ for details. - if (v === root[v] && shift[sink[root[v]]] < Number.POSITIVE_INFINITY) { - xs[v] += shift[sink[root[v]]]; - - // Cascade shifts as necessary - var w = shiftNeighbor[sink[root[v]]]; - if (w && shift[w] !== Number.POSITIVE_INFINITY) { - xs[v] += shift[w]; - } + var vRoot = root[v]; + blockGraph.setNode(vRoot); + if (u) { + var uRoot = root[u], + prevMax = blockGraph.edge(uRoot, vRoot); + blockGraph.setEdge(uRoot, vRoot, Math.max(sepFn(g, v, u), prevMax || 0)); } + u = v; }); }); - return xs; -} - -function placeBlock(g, layering, sepFn, root, align, shift, shiftNeighbor, sink, pred, xs, v) { - if (_.has(xs, v)) return; - xs[v] = 0; - - var w = v, - u; - do { - if (pred[w]) { - u = root[pred[w]]; - placeBlock(g, layering, sepFn, root, align, shift, shiftNeighbor, sink, pred, xs, u); - if (sink[v] === v) { - sink[v] = sink[u]; - } - - var delta = sepFn(g, w, pred[w]); - if (sink[v] !== sink[u]) { - shift[sink[u]] = Math.min(shift[sink[u]], xs[v] - xs[u] - delta); - shiftNeighbor[sink[u]] = sink[v]; - } else { - xs[v] = Math.max(xs[v], xs[u] + delta); - } - } - w = align[w]; - } while (w !== v); + return blockGraph; } /*