Skip to content

Commit bd1c10b

Browse files
committed
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
1 parent 7602baf commit bd1c10b

File tree

1 file changed

+56
-58
lines changed

1 file changed

+56
-58
lines changed

lib/position/bk.js

Lines changed: 56 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
"use strict";
22

33
var _ = require("../lodash"),
4+
Graph = require("../graphlib").Graph,
45
util = require("../util");
56

67
/*
@@ -204,74 +205,71 @@ function verticalAlignment(g, layering, conflicts, neighborFn) {
204205
}
205206

206207
function horizontalCompaction(g, layering, root, align, reverseSep) {
207-
// We use local variables for these parameters instead of manipulating the
208-
// graph because it becomes more verbose to access them in a chained manner.
209-
var shift = {},
210-
shiftNeighbor = {},
211-
sink = {},
212-
xs = {},
213-
pred = {},
214-
graphLabel = g.graph(),
215-
sepFn = sep(graphLabel.nodesep, graphLabel.edgesep, reverseSep);
208+
// This portion of the algorithm differs from BK due to a number of problems.
209+
// Instead of their algorithm we construct a new block graph and do two
210+
// sweeps. The first sweep places blocks with the smallest possible
211+
// coordinates. The second sweep removes unused space by moving blocks to the
212+
// greatest coordinates without violating separation.
213+
var xs = {},
214+
blockG = buildBlockGraph(g, layering, root, reverseSep);
215+
216+
// First pass, assign smallest coordinates via DFS
217+
var visited = {};
218+
function pass1(v) {
219+
if (!_.has(visited, v)) {
220+
visited[v] = true;
221+
xs[v] = _.reduce(blockG.inEdges(v), function(max, e) {
222+
pass1(e.v);
223+
return Math.max(max, xs[e.v] + blockG.edge(e));
224+
}, 0);
225+
}
226+
}
227+
_.each(blockG.nodes(), pass1);
228+
229+
function pass2(v) {
230+
if (visited[v] !== 2) {
231+
visited[v]++;
232+
var min = _.reduce(blockG.outEdges(v), function(min, e) {
233+
pass2(e.w);
234+
return Math.min(min, xs[e.w] - blockG.edge(e));
235+
}, Number.POSITIVE_INFINITY);
236+
if (min !== Number.POSITIVE_INFINITY) {
237+
xs[v] = Math.max(xs[v], min);
238+
}
239+
}
240+
}
241+
_.each(blockG.nodes(), pass2);
216242

217-
_.each(layering, function(layer) {
218-
_.each(layer, function(v, order) {
219-
sink[v] = v;
220-
shift[v] = Number.POSITIVE_INFINITY;
221-
pred[v] = layer[order - 1];
222-
});
223-
});
224243

225-
_.each(g.nodes(), function(v) {
226-
if (root[v] === v) {
227-
placeBlock(g, layering, sepFn, root, align, shift, shiftNeighbor, sink, pred, xs, v);
228-
}
244+
// Assign x coordinates to all nodes
245+
_.each(align, function(v) {
246+
xs[v] = xs[root[v]];
229247
});
230248

249+
return xs;
250+
}
251+
252+
253+
function buildBlockGraph(g, layering, root, reverseSep) {
254+
var blockGraph = new Graph(),
255+
graphLabel = g.graph(),
256+
sepFn = sep(graphLabel.nodesep, graphLabel.edgesep, reverseSep);
257+
231258
_.each(layering, function(layer) {
259+
var u;
232260
_.each(layer, function(v) {
233-
xs[v] = xs[root[v]];
234-
// This line differs from the source paper. See
235-
// http://www.inf.uni-konstanz.de/~brandes/publications/ for details.
236-
if (v === root[v] && shift[sink[root[v]]] < Number.POSITIVE_INFINITY) {
237-
xs[v] += shift[sink[root[v]]];
238-
239-
// Cascade shifts as necessary
240-
var w = shiftNeighbor[sink[root[v]]];
241-
if (w && shift[w] !== Number.POSITIVE_INFINITY) {
242-
xs[v] += shift[w];
243-
}
261+
var vRoot = root[v];
262+
blockGraph.setNode(vRoot);
263+
if (u) {
264+
var uRoot = root[u],
265+
prevMax = blockGraph.edge(uRoot, vRoot);
266+
blockGraph.setEdge(uRoot, vRoot, Math.max(sepFn(g, v, u), prevMax || 0));
244267
}
268+
u = v;
245269
});
246270
});
247271

248-
return xs;
249-
}
250-
251-
function placeBlock(g, layering, sepFn, root, align, shift, shiftNeighbor, sink, pred, xs, v) {
252-
if (_.has(xs, v)) return;
253-
xs[v] = 0;
254-
255-
var w = v,
256-
u;
257-
do {
258-
if (pred[w]) {
259-
u = root[pred[w]];
260-
placeBlock(g, layering, sepFn, root, align, shift, shiftNeighbor, sink, pred, xs, u);
261-
if (sink[v] === v) {
262-
sink[v] = sink[u];
263-
}
264-
265-
var delta = sepFn(g, w, pred[w]);
266-
if (sink[v] !== sink[u]) {
267-
shift[sink[u]] = Math.min(shift[sink[u]], xs[v] - xs[u] - delta);
268-
shiftNeighbor[sink[u]] = sink[v];
269-
} else {
270-
xs[v] = Math.max(xs[v], xs[u] + delta);
271-
}
272-
}
273-
w = align[w];
274-
} while (w !== v);
272+
return blockGraph;
275273
}
276274

277275
/*

0 commit comments

Comments
 (0)