Skip to content

Commit

Permalink
Fix positioning algorithm / temporarily disable rank optimizer
Browse files Browse the repository at this point in the history
Rework the position algorithm to take into account that it is possible
for a class to have different shifts depending on the adjacent classes
and it is impossible to decide which should impact final shift until
we've already iterated through all nodes. Now we keep track of the
potential shifts that may be applied during the first pass and then we
calculate the actual shifts (choosing the smallest value) during final
coordinate assignment. As far as I know this addresses all issues I am
aware of with the Brandes-Köpf paper.

In addition, I have temporarily disabled the rank optimizer, which
yields some incorrect rank assignments in complex graphs. This needs to
be investigated further in another ticket. The impact for now is that we
may have longer edges than necessary between nodes until the optimizer
is re-enabled.

Fixes #67.
  • Loading branch information
cpettitt committed Aug 19, 2013
1 parent c141ea6 commit 05d390c
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 10 deletions.
2 changes: 1 addition & 1 deletion demo/demo.html
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ <h2>Graph Visualization</h2>
var layout = dagre.layout()
.nodes(nodeData)
.edges(edgeData)
.debugLevel(2)
.debugLevel(3)
.run();

if (debugAlignment) {
Expand Down
45 changes: 36 additions & 9 deletions src/layout/position.js
Original file line number Diff line number Diff line change
Expand Up @@ -189,19 +189,29 @@ dagre.layout.position = function() {
// the original algorithm that is described in Carstens, "Node and Label
// Placement in a Layered Layout Algorithm".
function horizontalCompaction(g, layering, pos, root, align) {
var sink = {}, // Mapping of node id -> sink node id for class
shift = {}, // Mapping of sink node id -> x delta
pred = {}, // Mapping of node id -> predecessor node (or null)
xs = {}; // Calculated X positions
var sink = {}, // Mapping of node id -> sink node id for class
maybeShift = {}, // Mapping of sink node id -> { class node id, min shift }
shift = {}, // Mapping of sink node id -> shift
pred = {}, // Mapping of node id -> predecessor node (or null)
xs = {}; // Calculated X positions

layering.forEach(function(layer) {
layer.forEach(function(u, i) {
sink[u] = u;
maybeShift[u] = {};
if (i > 0)
pred[u] = layer[i - 1];
});
});

function updateShift(toShift, neighbor, delta) {
if (!(neighbor in maybeShift[toShift])) {
maybeShift[toShift][neighbor] = delta;
} else {
maybeShift[toShift][neighbor] = Math.min(maybeShift[toShift][neighbor], delta);
}
}

function placeBlock(v) {
if (!(v in xs)) {
xs[v] = 0;
Expand All @@ -215,7 +225,7 @@ dagre.layout.position = function() {
}
var delta = sep(g, pred[w]) + sep(g, w);
if (sink[v] !== sink[u]) {
shift[sink[u]] = Math.min(shift[sink[u]] || Number.POSITIVE_INFINITY, xs[v] - xs[u] - delta);
updateShift(sink[u], sink[v], xs[v] - xs[u] - delta);
} else {
xs[v] = Math.max(xs[v], xs[u] + delta);
}
Expand All @@ -231,12 +241,29 @@ dagre.layout.position = function() {
});

// Absolute coordinates
// There is an assumption here that we've resolved shifts for any classes
// that begin at an earlier layer. We guarantee this by visiting layers in
// order.
layering.forEach(function(layer) {
layer.forEach(function(v) {
xs[v] = xs[root[v]];
var xDelta = shift[sink[v]];
if (root[v] === v && xDelta < Number.POSITIVE_INFINITY)
xs[v] += xDelta;
if (v === root[v] && v === sink[v]) {
var minShift = 0;
if (v in maybeShift && Object.keys(maybeShift[v]).length > 0) {
minShift = min(Object.keys(maybeShift[v])
.map(function(u) {
return maybeShift[v][u] + (u in shift ? shift[u] : 0);
}
));
}
shift[v] = minShift;
}
});
});

layering.forEach(function(layer) {
layer.forEach(function(v) {
xs[v] += shift[sink[root[v]]] || 0;
});
});

Expand Down Expand Up @@ -390,7 +417,7 @@ dagre.layout.position = function() {
if (u) {
var s = sep(g, u) + sep(g, v);
if (xV - xU < s)
console.log("Position phase: sep violation. Align: " + align + ". Layer " + li + ". " +
console.log("Position phase: sep violation. Align: " + align + ". Layer: " + li + ". " +
"U: " + u + " V: " + v + ". Actual sep: " + (xV - xU) + " Expected sep: " + s);
}
u = v;
Expand Down
2 changes: 2 additions & 0 deletions src/layout/rank.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@ dagre.layout.rank = function() {
initRank(g);
components(g).forEach(function(cmpt) {
var subgraph = g.subgraph(cmpt);
/*
feasibleTree(subgraph);
*/
normalize(subgraph);
});
}
Expand Down

0 comments on commit 05d390c

Please sign in to comment.