You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

550 lines
23 KiB

"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var rectangle_1 = require("./rectangle");
var vpsc_1 = require("./vpsc");
var shortestpaths_1 = require("./shortestpaths");
var NodeWrapper = (function () {
function NodeWrapper(id, rect, children) {
this.id = id;
this.rect = rect;
this.children = children;
this.leaf = typeof children === 'undefined' || children.length === 0;
}
return NodeWrapper;
}());
exports.NodeWrapper = NodeWrapper;
var Vert = (function () {
function Vert(id, x, y, node, line) {
if (node === void 0) { node = null; }
if (line === void 0) { line = null; }
this.id = id;
this.x = x;
this.y = y;
this.node = node;
this.line = line;
}
return Vert;
}());
exports.Vert = Vert;
var LongestCommonSubsequence = (function () {
function LongestCommonSubsequence(s, t) {
this.s = s;
this.t = t;
var mf = LongestCommonSubsequence.findMatch(s, t);
var tr = t.slice(0).reverse();
var mr = LongestCommonSubsequence.findMatch(s, tr);
if (mf.length >= mr.length) {
this.length = mf.length;
this.si = mf.si;
this.ti = mf.ti;
this.reversed = false;
}
else {
this.length = mr.length;
this.si = mr.si;
this.ti = t.length - mr.ti - mr.length;
this.reversed = true;
}
}
LongestCommonSubsequence.findMatch = function (s, t) {
var m = s.length;
var n = t.length;
var match = { length: 0, si: -1, ti: -1 };
var l = new Array(m);
for (var i = 0; i < m; i++) {
l[i] = new Array(n);
for (var j = 0; j < n; j++)
if (s[i] === t[j]) {
var v = l[i][j] = (i === 0 || j === 0) ? 1 : l[i - 1][j - 1] + 1;
if (v > match.length) {
match.length = v;
match.si = i - v + 1;
match.ti = j - v + 1;
}
;
}
else
l[i][j] = 0;
}
return match;
};
LongestCommonSubsequence.prototype.getSequence = function () {
return this.length >= 0 ? this.s.slice(this.si, this.si + this.length) : [];
};
return LongestCommonSubsequence;
}());
exports.LongestCommonSubsequence = LongestCommonSubsequence;
var GridRouter = (function () {
function GridRouter(originalnodes, accessor, groupPadding) {
var _this = this;
if (groupPadding === void 0) { groupPadding = 12; }
this.originalnodes = originalnodes;
this.groupPadding = groupPadding;
this.leaves = null;
this.nodes = originalnodes.map(function (v, i) { return new NodeWrapper(i, accessor.getBounds(v), accessor.getChildren(v)); });
this.leaves = this.nodes.filter(function (v) { return v.leaf; });
this.groups = this.nodes.filter(function (g) { return !g.leaf; });
this.cols = this.getGridLines('x');
this.rows = this.getGridLines('y');
this.groups.forEach(function (v) {
return v.children.forEach(function (c) { return _this.nodes[c].parent = v; });
});
this.root = { children: [] };
this.nodes.forEach(function (v) {
if (typeof v.parent === 'undefined') {
v.parent = _this.root;
_this.root.children.push(v.id);
}
v.ports = [];
});
this.backToFront = this.nodes.slice(0);
this.backToFront.sort(function (x, y) { return _this.getDepth(x) - _this.getDepth(y); });
var frontToBackGroups = this.backToFront.slice(0).reverse().filter(function (g) { return !g.leaf; });
frontToBackGroups.forEach(function (v) {
var r = rectangle_1.Rectangle.empty();
v.children.forEach(function (c) { return r = r.union(_this.nodes[c].rect); });
v.rect = r.inflate(_this.groupPadding);
});
var colMids = this.midPoints(this.cols.map(function (r) { return r.pos; }));
var rowMids = this.midPoints(this.rows.map(function (r) { return r.pos; }));
var rowx = colMids[0], rowX = colMids[colMids.length - 1];
var coly = rowMids[0], colY = rowMids[rowMids.length - 1];
var hlines = this.rows.map(function (r) { return ({ x1: rowx, x2: rowX, y1: r.pos, y2: r.pos }); })
.concat(rowMids.map(function (m) { return ({ x1: rowx, x2: rowX, y1: m, y2: m }); }));
var vlines = this.cols.map(function (c) { return ({ x1: c.pos, x2: c.pos, y1: coly, y2: colY }); })
.concat(colMids.map(function (m) { return ({ x1: m, x2: m, y1: coly, y2: colY }); }));
var lines = hlines.concat(vlines);
lines.forEach(function (l) { return l.verts = []; });
this.verts = [];
this.edges = [];
hlines.forEach(function (h) {
return vlines.forEach(function (v) {
var p = new Vert(_this.verts.length, v.x1, h.y1);
h.verts.push(p);
v.verts.push(p);
_this.verts.push(p);
var i = _this.backToFront.length;
while (i-- > 0) {
var node = _this.backToFront[i], r = node.rect;
var dx = Math.abs(p.x - r.cx()), dy = Math.abs(p.y - r.cy());
if (dx < r.width() / 2 && dy < r.height() / 2) {
p.node = node;
break;
}
}
});
});
lines.forEach(function (l, li) {
_this.nodes.forEach(function (v, i) {
v.rect.lineIntersections(l.x1, l.y1, l.x2, l.y2).forEach(function (intersect, j) {
var p = new Vert(_this.verts.length, intersect.x, intersect.y, v, l);
_this.verts.push(p);
l.verts.push(p);
v.ports.push(p);
});
});
var isHoriz = Math.abs(l.y1 - l.y2) < 0.1;
var delta = function (a, b) { return isHoriz ? b.x - a.x : b.y - a.y; };
l.verts.sort(delta);
for (var i = 1; i < l.verts.length; i++) {
var u = l.verts[i - 1], v = l.verts[i];
if (u.node && u.node === v.node && u.node.leaf)
continue;
_this.edges.push({ source: u.id, target: v.id, length: Math.abs(delta(u, v)) });
}
});
}
GridRouter.prototype.avg = function (a) { return a.reduce(function (x, y) { return x + y; }) / a.length; };
GridRouter.prototype.getGridLines = function (axis) {
var columns = [];
var ls = this.leaves.slice(0, this.leaves.length);
while (ls.length > 0) {
var overlapping = ls.filter(function (v) { return v.rect['overlap' + axis.toUpperCase()](ls[0].rect); });
var col = {
nodes: overlapping,
pos: this.avg(overlapping.map(function (v) { return v.rect['c' + axis](); }))
};
columns.push(col);
col.nodes.forEach(function (v) { return ls.splice(ls.indexOf(v), 1); });
}
columns.sort(function (a, b) { return a.pos - b.pos; });
return columns;
};
GridRouter.prototype.getDepth = function (v) {
var depth = 0;
while (v.parent !== this.root) {
depth++;
v = v.parent;
}
return depth;
};
GridRouter.prototype.midPoints = function (a) {
var gap = a[1] - a[0];
var mids = [a[0] - gap / 2];
for (var i = 1; i < a.length; i++) {
mids.push((a[i] + a[i - 1]) / 2);
}
mids.push(a[a.length - 1] + gap / 2);
return mids;
};
GridRouter.prototype.findLineage = function (v) {
var lineage = [v];
do {
v = v.parent;
lineage.push(v);
} while (v !== this.root);
return lineage.reverse();
};
GridRouter.prototype.findAncestorPathBetween = function (a, b) {
var aa = this.findLineage(a), ba = this.findLineage(b), i = 0;
while (aa[i] === ba[i])
i++;
return { commonAncestor: aa[i - 1], lineages: aa.slice(i).concat(ba.slice(i)) };
};
GridRouter.prototype.siblingObstacles = function (a, b) {
var _this = this;
var path = this.findAncestorPathBetween(a, b);
var lineageLookup = {};
path.lineages.forEach(function (v) { return lineageLookup[v.id] = {}; });
var obstacles = path.commonAncestor.children.filter(function (v) { return !(v in lineageLookup); });
path.lineages
.filter(function (v) { return v.parent !== path.commonAncestor; })
.forEach(function (v) { return obstacles = obstacles.concat(v.parent.children.filter(function (c) { return c !== v.id; })); });
return obstacles.map(function (v) { return _this.nodes[v]; });
};
GridRouter.getSegmentSets = function (routes, x, y) {
var vsegments = [];
for (var ei = 0; ei < routes.length; ei++) {
var route = routes[ei];
for (var si = 0; si < route.length; si++) {
var s = route[si];
s.edgeid = ei;
s.i = si;
var sdx = s[1][x] - s[0][x];
if (Math.abs(sdx) < 0.1) {
vsegments.push(s);
}
}
}
vsegments.sort(function (a, b) { return a[0][x] - b[0][x]; });
var vsegmentsets = [];
var segmentset = null;
for (var i = 0; i < vsegments.length; i++) {
var s = vsegments[i];
if (!segmentset || Math.abs(s[0][x] - segmentset.pos) > 0.1) {
segmentset = { pos: s[0][x], segments: [] };
vsegmentsets.push(segmentset);
}
segmentset.segments.push(s);
}
return vsegmentsets;
};
GridRouter.nudgeSegs = function (x, y, routes, segments, leftOf, gap) {
var n = segments.length;
if (n <= 1)
return;
var vs = segments.map(function (s) { return new vpsc_1.Variable(s[0][x]); });
var cs = [];
for (var i = 0; i < n; i++) {
for (var j = 0; j < n; j++) {
if (i === j)
continue;
var s1 = segments[i], s2 = segments[j], e1 = s1.edgeid, e2 = s2.edgeid, lind = -1, rind = -1;
if (x == 'x') {
if (leftOf(e1, e2)) {
if (s1[0][y] < s1[1][y]) {
lind = j, rind = i;
}
else {
lind = i, rind = j;
}
}
}
else {
if (leftOf(e1, e2)) {
if (s1[0][y] < s1[1][y]) {
lind = i, rind = j;
}
else {
lind = j, rind = i;
}
}
}
if (lind >= 0) {
cs.push(new vpsc_1.Constraint(vs[lind], vs[rind], gap));
}
}
}
var solver = new vpsc_1.Solver(vs, cs);
solver.solve();
vs.forEach(function (v, i) {
var s = segments[i];
var pos = v.position();
s[0][x] = s[1][x] = pos;
var route = routes[s.edgeid];
if (s.i > 0)
route[s.i - 1][1][x] = pos;
if (s.i < route.length - 1)
route[s.i + 1][0][x] = pos;
});
};
GridRouter.nudgeSegments = function (routes, x, y, leftOf, gap) {
var vsegmentsets = GridRouter.getSegmentSets(routes, x, y);
for (var i = 0; i < vsegmentsets.length; i++) {
var ss = vsegmentsets[i];
var events = [];
for (var j = 0; j < ss.segments.length; j++) {
var s = ss.segments[j];
events.push({ type: 0, s: s, pos: Math.min(s[0][y], s[1][y]) });
events.push({ type: 1, s: s, pos: Math.max(s[0][y], s[1][y]) });
}
events.sort(function (a, b) { return a.pos - b.pos + a.type - b.type; });
var open = [];
var openCount = 0;
events.forEach(function (e) {
if (e.type === 0) {
open.push(e.s);
openCount++;
}
else {
openCount--;
}
if (openCount == 0) {
GridRouter.nudgeSegs(x, y, routes, open, leftOf, gap);
open = [];
}
});
}
};
GridRouter.prototype.routeEdges = function (edges, nudgeGap, source, target) {
var _this = this;
var routePaths = edges.map(function (e) { return _this.route(source(e), target(e)); });
var order = GridRouter.orderEdges(routePaths);
var routes = routePaths.map(function (e) { return GridRouter.makeSegments(e); });
GridRouter.nudgeSegments(routes, 'x', 'y', order, nudgeGap);
GridRouter.nudgeSegments(routes, 'y', 'x', order, nudgeGap);
GridRouter.unreverseEdges(routes, routePaths);
return routes;
};
GridRouter.unreverseEdges = function (routes, routePaths) {
routes.forEach(function (segments, i) {
var path = routePaths[i];
if (path.reversed) {
segments.reverse();
segments.forEach(function (segment) {
segment.reverse();
});
}
});
};
GridRouter.angleBetween2Lines = function (line1, line2) {
var angle1 = Math.atan2(line1[0].y - line1[1].y, line1[0].x - line1[1].x);
var angle2 = Math.atan2(line2[0].y - line2[1].y, line2[0].x - line2[1].x);
var diff = angle1 - angle2;
if (diff > Math.PI || diff < -Math.PI) {
diff = angle2 - angle1;
}
return diff;
};
GridRouter.isLeft = function (a, b, c) {
return ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)) <= 0;
};
GridRouter.getOrder = function (pairs) {
var outgoing = {};
for (var i = 0; i < pairs.length; i++) {
var p = pairs[i];
if (typeof outgoing[p.l] === 'undefined')
outgoing[p.l] = {};
outgoing[p.l][p.r] = true;
}
return function (l, r) { return typeof outgoing[l] !== 'undefined' && outgoing[l][r]; };
};
GridRouter.orderEdges = function (edges) {
var edgeOrder = [];
for (var i = 0; i < edges.length - 1; i++) {
for (var j = i + 1; j < edges.length; j++) {
var e = edges[i], f = edges[j], lcs = new LongestCommonSubsequence(e, f);
var u, vi, vj;
if (lcs.length === 0)
continue;
if (lcs.reversed) {
f.reverse();
f.reversed = true;
lcs = new LongestCommonSubsequence(e, f);
}
if ((lcs.si <= 0 || lcs.ti <= 0) &&
(lcs.si + lcs.length >= e.length || lcs.ti + lcs.length >= f.length)) {
edgeOrder.push({ l: i, r: j });
continue;
}
if (lcs.si + lcs.length >= e.length || lcs.ti + lcs.length >= f.length) {
u = e[lcs.si + 1];
vj = e[lcs.si - 1];
vi = f[lcs.ti - 1];
}
else {
u = e[lcs.si + lcs.length - 2];
vi = e[lcs.si + lcs.length];
vj = f[lcs.ti + lcs.length];
}
if (GridRouter.isLeft(u, vi, vj)) {
edgeOrder.push({ l: j, r: i });
}
else {
edgeOrder.push({ l: i, r: j });
}
}
}
return GridRouter.getOrder(edgeOrder);
};
GridRouter.makeSegments = function (path) {
function copyPoint(p) {
return { x: p.x, y: p.y };
}
var isStraight = function (a, b, c) { return Math.abs((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)) < 0.001; };
var segments = [];
var a = copyPoint(path[0]);
for (var i = 1; i < path.length; i++) {
var b = copyPoint(path[i]), c = i < path.length - 1 ? path[i + 1] : null;
if (!c || !isStraight(a, b, c)) {
segments.push([a, b]);
a = b;
}
}
return segments;
};
GridRouter.prototype.route = function (s, t) {
var _this = this;
var source = this.nodes[s], target = this.nodes[t];
this.obstacles = this.siblingObstacles(source, target);
var obstacleLookup = {};
this.obstacles.forEach(function (o) { return obstacleLookup[o.id] = o; });
this.passableEdges = this.edges.filter(function (e) {
var u = _this.verts[e.source], v = _this.verts[e.target];
return !(u.node && u.node.id in obstacleLookup
|| v.node && v.node.id in obstacleLookup);
});
for (var i = 1; i < source.ports.length; i++) {
var u = source.ports[0].id;
var v = source.ports[i].id;
this.passableEdges.push({
source: u,
target: v,
length: 0
});
}
for (var i = 1; i < target.ports.length; i++) {
var u = target.ports[0].id;
var v = target.ports[i].id;
this.passableEdges.push({
source: u,
target: v,
length: 0
});
}
var getSource = function (e) { return e.source; }, getTarget = function (e) { return e.target; }, getLength = function (e) { return e.length; };
var shortestPathCalculator = new shortestpaths_1.Calculator(this.verts.length, this.passableEdges, getSource, getTarget, getLength);
var bendPenalty = function (u, v, w) {
var a = _this.verts[u], b = _this.verts[v], c = _this.verts[w];
var dx = Math.abs(c.x - a.x), dy = Math.abs(c.y - a.y);
if (a.node === source && a.node === b.node || b.node === target && b.node === c.node)
return 0;
return dx > 1 && dy > 1 ? 1000 : 0;
};
var shortestPath = shortestPathCalculator.PathFromNodeToNodeWithPrevCost(source.ports[0].id, target.ports[0].id, bendPenalty);
var pathPoints = shortestPath.reverse().map(function (vi) { return _this.verts[vi]; });
pathPoints.push(this.nodes[target.id].ports[0]);
return pathPoints.filter(function (v, i) {
return !(i < pathPoints.length - 1 && pathPoints[i + 1].node === source && v.node === source
|| i > 0 && v.node === target && pathPoints[i - 1].node === target);
});
};
GridRouter.getRoutePath = function (route, cornerradius, arrowwidth, arrowheight) {
var result = {
routepath: 'M ' + route[0][0].x + ' ' + route[0][0].y + ' ',
arrowpath: ''
};
if (route.length > 1) {
for (var i = 0; i < route.length; i++) {
var li = route[i];
var x = li[1].x, y = li[1].y;
var dx = x - li[0].x;
var dy = y - li[0].y;
if (i < route.length - 1) {
if (Math.abs(dx) > 0) {
x -= dx / Math.abs(dx) * cornerradius;
}
else {
y -= dy / Math.abs(dy) * cornerradius;
}
result.routepath += 'L ' + x + ' ' + y + ' ';
var l = route[i + 1];
var x0 = l[0].x, y0 = l[0].y;
var x1 = l[1].x;
var y1 = l[1].y;
dx = x1 - x0;
dy = y1 - y0;
var angle = GridRouter.angleBetween2Lines(li, l) < 0 ? 1 : 0;
var x2, y2;
if (Math.abs(dx) > 0) {
x2 = x0 + dx / Math.abs(dx) * cornerradius;
y2 = y0;
}
else {
x2 = x0;
y2 = y0 + dy / Math.abs(dy) * cornerradius;
}
var cx = Math.abs(x2 - x);
var cy = Math.abs(y2 - y);
result.routepath += 'A ' + cx + ' ' + cy + ' 0 0 ' + angle + ' ' + x2 + ' ' + y2 + ' ';
}
else {
var arrowtip = [x, y];
var arrowcorner1, arrowcorner2;
if (Math.abs(dx) > 0) {
x -= dx / Math.abs(dx) * arrowheight;
arrowcorner1 = [x, y + arrowwidth];
arrowcorner2 = [x, y - arrowwidth];
}
else {
y -= dy / Math.abs(dy) * arrowheight;
arrowcorner1 = [x + arrowwidth, y];
arrowcorner2 = [x - arrowwidth, y];
}
result.routepath += 'L ' + x + ' ' + y + ' ';
if (arrowheight > 0) {
result.arrowpath = 'M ' + arrowtip[0] + ' ' + arrowtip[1] + ' L ' + arrowcorner1[0] + ' ' + arrowcorner1[1]
+ ' L ' + arrowcorner2[0] + ' ' + arrowcorner2[1];
}
}
}
}
else {
var li = route[0];
var x = li[1].x, y = li[1].y;
var dx = x - li[0].x;
var dy = y - li[0].y;
var arrowtip = [x, y];
var arrowcorner1, arrowcorner2;
if (Math.abs(dx) > 0) {
x -= dx / Math.abs(dx) * arrowheight;
arrowcorner1 = [x, y + arrowwidth];
arrowcorner2 = [x, y - arrowwidth];
}
else {
y -= dy / Math.abs(dy) * arrowheight;
arrowcorner1 = [x + arrowwidth, y];
arrowcorner2 = [x - arrowwidth, y];
}
result.routepath += 'L ' + x + ' ' + y + ' ';
if (arrowheight > 0) {
result.arrowpath = 'M ' + arrowtip[0] + ' ' + arrowtip[1] + ' L ' + arrowcorner1[0] + ' ' + arrowcorner1[1]
+ ' L ' + arrowcorner2[0] + ' ' + arrowcorner2[1];
}
}
return result;
};
return GridRouter;
}());
exports.GridRouter = GridRouter;
//# sourceMappingURL=gridrouter.js.map