feat(ui): add type filter toggles
This commit is contained in:
419
node_modules/dagre/lib/position/bk.js
generated
vendored
Normal file
419
node_modules/dagre/lib/position/bk.js
generated
vendored
Normal file
@@ -0,0 +1,419 @@
|
||||
"use strict";
|
||||
|
||||
var _ = require("../lodash");
|
||||
var Graph = require("../graphlib").Graph;
|
||||
var util = require("../util");
|
||||
|
||||
/*
|
||||
* This module provides coordinate assignment based on Brandes and Köpf, "Fast
|
||||
* and Simple Horizontal Coordinate Assignment."
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
positionX: positionX,
|
||||
findType1Conflicts: findType1Conflicts,
|
||||
findType2Conflicts: findType2Conflicts,
|
||||
addConflict: addConflict,
|
||||
hasConflict: hasConflict,
|
||||
verticalAlignment: verticalAlignment,
|
||||
horizontalCompaction: horizontalCompaction,
|
||||
alignCoordinates: alignCoordinates,
|
||||
findSmallestWidthAlignment: findSmallestWidthAlignment,
|
||||
balance: balance
|
||||
};
|
||||
|
||||
/*
|
||||
* Marks all edges in the graph with a type-1 conflict with the "type1Conflict"
|
||||
* property. A type-1 conflict is one where a non-inner segment crosses an
|
||||
* inner segment. An inner segment is an edge with both incident nodes marked
|
||||
* with the "dummy" property.
|
||||
*
|
||||
* This algorithm scans layer by layer, starting with the second, for type-1
|
||||
* conflicts between the current layer and the previous layer. For each layer
|
||||
* it scans the nodes from left to right until it reaches one that is incident
|
||||
* on an inner segment. It then scans predecessors to determine if they have
|
||||
* edges that cross that inner segment. At the end a final scan is done for all
|
||||
* nodes on the current rank to see if they cross the last visited inner
|
||||
* segment.
|
||||
*
|
||||
* This algorithm (safely) assumes that a dummy node will only be incident on a
|
||||
* single node in the layers being scanned.
|
||||
*/
|
||||
function findType1Conflicts(g, layering) {
|
||||
var conflicts = {};
|
||||
|
||||
function visitLayer(prevLayer, layer) {
|
||||
var
|
||||
// last visited node in the previous layer that is incident on an inner
|
||||
// segment.
|
||||
k0 = 0,
|
||||
// Tracks the last node in this layer scanned for crossings with a type-1
|
||||
// segment.
|
||||
scanPos = 0,
|
||||
prevLayerLength = prevLayer.length,
|
||||
lastNode = _.last(layer);
|
||||
|
||||
_.forEach(layer, function(v, i) {
|
||||
var w = findOtherInnerSegmentNode(g, v),
|
||||
k1 = w ? g.node(w).order : prevLayerLength;
|
||||
|
||||
if (w || v === lastNode) {
|
||||
_.forEach(layer.slice(scanPos, i +1), function(scanNode) {
|
||||
_.forEach(g.predecessors(scanNode), function(u) {
|
||||
var uLabel = g.node(u),
|
||||
uPos = uLabel.order;
|
||||
if ((uPos < k0 || k1 < uPos) &&
|
||||
!(uLabel.dummy && g.node(scanNode).dummy)) {
|
||||
addConflict(conflicts, u, scanNode);
|
||||
}
|
||||
});
|
||||
});
|
||||
scanPos = i + 1;
|
||||
k0 = k1;
|
||||
}
|
||||
});
|
||||
|
||||
return layer;
|
||||
}
|
||||
|
||||
_.reduce(layering, visitLayer);
|
||||
return conflicts;
|
||||
}
|
||||
|
||||
function findType2Conflicts(g, layering) {
|
||||
var conflicts = {};
|
||||
|
||||
function scan(south, southPos, southEnd, prevNorthBorder, nextNorthBorder) {
|
||||
var v;
|
||||
_.forEach(_.range(southPos, southEnd), function(i) {
|
||||
v = south[i];
|
||||
if (g.node(v).dummy) {
|
||||
_.forEach(g.predecessors(v), function(u) {
|
||||
var uNode = g.node(u);
|
||||
if (uNode.dummy &&
|
||||
(uNode.order < prevNorthBorder || uNode.order > nextNorthBorder)) {
|
||||
addConflict(conflicts, u, v);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function visitLayer(north, south) {
|
||||
var prevNorthPos = -1,
|
||||
nextNorthPos,
|
||||
southPos = 0;
|
||||
|
||||
_.forEach(south, function(v, southLookahead) {
|
||||
if (g.node(v).dummy === "border") {
|
||||
var predecessors = g.predecessors(v);
|
||||
if (predecessors.length) {
|
||||
nextNorthPos = g.node(predecessors[0]).order;
|
||||
scan(south, southPos, southLookahead, prevNorthPos, nextNorthPos);
|
||||
southPos = southLookahead;
|
||||
prevNorthPos = nextNorthPos;
|
||||
}
|
||||
}
|
||||
scan(south, southPos, south.length, nextNorthPos, north.length);
|
||||
});
|
||||
|
||||
return south;
|
||||
}
|
||||
|
||||
_.reduce(layering, visitLayer);
|
||||
return conflicts;
|
||||
}
|
||||
|
||||
function findOtherInnerSegmentNode(g, v) {
|
||||
if (g.node(v).dummy) {
|
||||
return _.find(g.predecessors(v), function(u) {
|
||||
return g.node(u).dummy;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function addConflict(conflicts, v, w) {
|
||||
if (v > w) {
|
||||
var tmp = v;
|
||||
v = w;
|
||||
w = tmp;
|
||||
}
|
||||
|
||||
var conflictsV = conflicts[v];
|
||||
if (!conflictsV) {
|
||||
conflicts[v] = conflictsV = {};
|
||||
}
|
||||
conflictsV[w] = true;
|
||||
}
|
||||
|
||||
function hasConflict(conflicts, v, w) {
|
||||
if (v > w) {
|
||||
var tmp = v;
|
||||
v = w;
|
||||
w = tmp;
|
||||
}
|
||||
return _.has(conflicts[v], w);
|
||||
}
|
||||
|
||||
/*
|
||||
* Try to align nodes into vertical "blocks" where possible. This algorithm
|
||||
* attempts to align a node with one of its median neighbors. If the edge
|
||||
* connecting a neighbor is a type-1 conflict then we ignore that possibility.
|
||||
* If a previous node has already formed a block with a node after the node
|
||||
* we're trying to form a block with, we also ignore that possibility - our
|
||||
* blocks would be split in that scenario.
|
||||
*/
|
||||
function verticalAlignment(g, layering, conflicts, neighborFn) {
|
||||
var root = {},
|
||||
align = {},
|
||||
pos = {};
|
||||
|
||||
// We cache the position here based on the layering because the graph and
|
||||
// layering may be out of sync. The layering matrix is manipulated to
|
||||
// generate different extreme alignments.
|
||||
_.forEach(layering, function(layer) {
|
||||
_.forEach(layer, function(v, order) {
|
||||
root[v] = v;
|
||||
align[v] = v;
|
||||
pos[v] = order;
|
||||
});
|
||||
});
|
||||
|
||||
_.forEach(layering, function(layer) {
|
||||
var prevIdx = -1;
|
||||
_.forEach(layer, function(v) {
|
||||
var ws = neighborFn(v);
|
||||
if (ws.length) {
|
||||
ws = _.sortBy(ws, function(w) { return pos[w]; });
|
||||
var mp = (ws.length - 1) / 2;
|
||||
for (var i = Math.floor(mp), il = Math.ceil(mp); i <= il; ++i) {
|
||||
var w = ws[i];
|
||||
if (align[v] === v &&
|
||||
prevIdx < pos[w] &&
|
||||
!hasConflict(conflicts, v, w)) {
|
||||
align[w] = v;
|
||||
align[v] = root[v] = root[w];
|
||||
prevIdx = pos[w];
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return { root: root, align: align };
|
||||
}
|
||||
|
||||
function horizontalCompaction(g, layering, root, align, 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),
|
||||
borderType = reverseSep ? "borderLeft" : "borderRight";
|
||||
|
||||
function iterate(setXsFunc, nextNodesFunc) {
|
||||
var stack = blockG.nodes();
|
||||
var elem = stack.pop();
|
||||
var visited = {};
|
||||
while (elem) {
|
||||
if (visited[elem]) {
|
||||
setXsFunc(elem);
|
||||
} else {
|
||||
visited[elem] = true;
|
||||
stack.push(elem);
|
||||
stack = stack.concat(nextNodesFunc(elem));
|
||||
}
|
||||
|
||||
elem = stack.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// First pass, assign smallest coordinates
|
||||
function pass1(elem) {
|
||||
xs[elem] = blockG.inEdges(elem).reduce(function(acc, e) {
|
||||
return Math.max(acc, xs[e.v] + blockG.edge(e));
|
||||
}, 0);
|
||||
}
|
||||
|
||||
// Second pass, assign greatest coordinates
|
||||
function pass2(elem) {
|
||||
var min = blockG.outEdges(elem).reduce(function(acc, e) {
|
||||
return Math.min(acc, xs[e.w] - blockG.edge(e));
|
||||
}, Number.POSITIVE_INFINITY);
|
||||
|
||||
var node = g.node(elem);
|
||||
if (min !== Number.POSITIVE_INFINITY && node.borderType !== borderType) {
|
||||
xs[elem] = Math.max(xs[elem], min);
|
||||
}
|
||||
}
|
||||
|
||||
iterate(pass1, blockG.predecessors.bind(blockG));
|
||||
iterate(pass2, blockG.successors.bind(blockG));
|
||||
|
||||
// Assign x coordinates to all nodes
|
||||
_.forEach(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);
|
||||
|
||||
_.forEach(layering, function(layer) {
|
||||
var u;
|
||||
_.forEach(layer, function(v) {
|
||||
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 blockGraph;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the alignment that has the smallest width of the given alignments.
|
||||
*/
|
||||
function findSmallestWidthAlignment(g, xss) {
|
||||
return _.minBy(_.values(xss), function (xs) {
|
||||
var max = Number.NEGATIVE_INFINITY;
|
||||
var min = Number.POSITIVE_INFINITY;
|
||||
|
||||
_.forIn(xs, function (x, v) {
|
||||
var halfWidth = width(g, v) / 2;
|
||||
|
||||
max = Math.max(x + halfWidth, max);
|
||||
min = Math.min(x - halfWidth, min);
|
||||
});
|
||||
|
||||
return max - min;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Align the coordinates of each of the layout alignments such that
|
||||
* left-biased alignments have their minimum coordinate at the same point as
|
||||
* the minimum coordinate of the smallest width alignment and right-biased
|
||||
* alignments have their maximum coordinate at the same point as the maximum
|
||||
* coordinate of the smallest width alignment.
|
||||
*/
|
||||
function alignCoordinates(xss, alignTo) {
|
||||
var alignToVals = _.values(alignTo),
|
||||
alignToMin = _.min(alignToVals),
|
||||
alignToMax = _.max(alignToVals);
|
||||
|
||||
_.forEach(["u", "d"], function(vert) {
|
||||
_.forEach(["l", "r"], function(horiz) {
|
||||
var alignment = vert + horiz,
|
||||
xs = xss[alignment],
|
||||
delta;
|
||||
if (xs === alignTo) return;
|
||||
|
||||
var xsVals = _.values(xs);
|
||||
delta = horiz === "l" ? alignToMin - _.min(xsVals) : alignToMax - _.max(xsVals);
|
||||
|
||||
if (delta) {
|
||||
xss[alignment] = _.mapValues(xs, function(x) { return x + delta; });
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function balance(xss, align) {
|
||||
return _.mapValues(xss.ul, function(ignore, v) {
|
||||
if (align) {
|
||||
return xss[align.toLowerCase()][v];
|
||||
} else {
|
||||
var xs = _.sortBy(_.map(xss, v));
|
||||
return (xs[1] + xs[2]) / 2;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function positionX(g) {
|
||||
var layering = util.buildLayerMatrix(g);
|
||||
var conflicts = _.merge(
|
||||
findType1Conflicts(g, layering),
|
||||
findType2Conflicts(g, layering));
|
||||
|
||||
var xss = {};
|
||||
var adjustedLayering;
|
||||
_.forEach(["u", "d"], function(vert) {
|
||||
adjustedLayering = vert === "u" ? layering : _.values(layering).reverse();
|
||||
_.forEach(["l", "r"], function(horiz) {
|
||||
if (horiz === "r") {
|
||||
adjustedLayering = _.map(adjustedLayering, function(inner) {
|
||||
return _.values(inner).reverse();
|
||||
});
|
||||
}
|
||||
|
||||
var neighborFn = (vert === "u" ? g.predecessors : g.successors).bind(g);
|
||||
var align = verticalAlignment(g, adjustedLayering, conflicts, neighborFn);
|
||||
var xs = horizontalCompaction(g, adjustedLayering,
|
||||
align.root, align.align, horiz === "r");
|
||||
if (horiz === "r") {
|
||||
xs = _.mapValues(xs, function(x) { return -x; });
|
||||
}
|
||||
xss[vert + horiz] = xs;
|
||||
});
|
||||
});
|
||||
|
||||
var smallestWidth = findSmallestWidthAlignment(g, xss);
|
||||
alignCoordinates(xss, smallestWidth);
|
||||
return balance(xss, g.graph().align);
|
||||
}
|
||||
|
||||
function sep(nodeSep, edgeSep, reverseSep) {
|
||||
return function(g, v, w) {
|
||||
var vLabel = g.node(v);
|
||||
var wLabel = g.node(w);
|
||||
var sum = 0;
|
||||
var delta;
|
||||
|
||||
sum += vLabel.width / 2;
|
||||
if (_.has(vLabel, "labelpos")) {
|
||||
switch (vLabel.labelpos.toLowerCase()) {
|
||||
case "l": delta = -vLabel.width / 2; break;
|
||||
case "r": delta = vLabel.width / 2; break;
|
||||
}
|
||||
}
|
||||
if (delta) {
|
||||
sum += reverseSep ? delta : -delta;
|
||||
}
|
||||
delta = 0;
|
||||
|
||||
sum += (vLabel.dummy ? edgeSep : nodeSep) / 2;
|
||||
sum += (wLabel.dummy ? edgeSep : nodeSep) / 2;
|
||||
|
||||
sum += wLabel.width / 2;
|
||||
if (_.has(wLabel, "labelpos")) {
|
||||
switch (wLabel.labelpos.toLowerCase()) {
|
||||
case "l": delta = wLabel.width / 2; break;
|
||||
case "r": delta = -wLabel.width / 2; break;
|
||||
}
|
||||
}
|
||||
if (delta) {
|
||||
sum += reverseSep ? delta : -delta;
|
||||
}
|
||||
delta = 0;
|
||||
|
||||
return sum;
|
||||
};
|
||||
}
|
||||
|
||||
function width(g, v) {
|
||||
return g.node(v).width;
|
||||
}
|
||||
Reference in New Issue
Block a user