var cola; (function (cola) { var packingOptions = { PADDING: 10, GOLDEN_SECTION: (1 + Math.sqrt(5)) / 2, FLOAT_EPSILON: 0.0001, MAX_INERATIONS: 100 }; // assign x, y to nodes while using box packing algorithm for disconnected graphs function applyPacking(graphs, w, h, node_size, desired_ratio) { if (desired_ratio === void 0) { desired_ratio = 1; } var init_x = 0, init_y = 0, svg_width = w, svg_height = h, desired_ratio = typeof desired_ratio !== 'undefined' ? desired_ratio : 1, node_size = typeof node_size !== 'undefined' ? node_size : 0, real_width = 0, real_height = 0, min_width = 0, global_bottom = 0, line = []; if (graphs.length == 0) return; /// that would take care of single nodes problem // graphs.forEach(function (g) { // if (g.array.length == 1) { // g.array[0].x = 0; // g.array[0].y = 0; // } // }); calculate_bb(graphs); apply(graphs, desired_ratio); put_nodes_to_right_positions(graphs); // get bounding boxes for all separate graphs function calculate_bb(graphs) { graphs.forEach(function (g) { calculate_single_bb(g); }); function calculate_single_bb(graph) { var min_x = Number.MAX_VALUE, min_y = Number.MAX_VALUE, max_x = 0, max_y = 0; graph.array.forEach(function (v) { var w = typeof v.width !== 'undefined' ? v.width : node_size; var h = typeof v.height !== 'undefined' ? v.height : node_size; w /= 2; h /= 2; max_x = Math.max(v.x + w, max_x); min_x = Math.min(v.x - w, min_x); max_y = Math.max(v.y + h, max_y); min_y = Math.min(v.y - h, min_y); }); graph.width = max_x - min_x; graph.height = max_y - min_y; } } //function plot(data, left, right, opt_x, opt_y) { // // plot the cost function // var plot_svg = d3.select("body").append("svg") // .attr("width", function () { return 2 * (right - left); }) // .attr("height", 200); // var x = d3.time.scale().range([0, 2 * (right - left)]); // var xAxis = d3.svg.axis().scale(x).orient("bottom"); // plot_svg.append("g").attr("class", "x axis") // .attr("transform", "translate(0, 199)") // .call(xAxis); // var lastX = 0; // var lastY = 0; // var value = 0; // for (var r = left; r < right; r += 1) { // value = step(data, r); // // value = 1; // plot_svg.append("line").attr("x1", 2 * (lastX - left)) // .attr("y1", 200 - 30 * lastY) // .attr("x2", 2 * r - 2 * left) // .attr("y2", 200 - 30 * value) // .style("stroke", "rgb(6,120,155)"); // lastX = r; // lastY = value; // } // plot_svg.append("circle").attr("cx", 2 * opt_x - 2 * left).attr("cy", 200 - 30 * opt_y) // .attr("r", 5).style('fill', "rgba(0,0,0,0.5)"); //} // actual assigning of position to nodes function put_nodes_to_right_positions(graphs) { graphs.forEach(function (g) { // calculate current graph center: var center = { x: 0, y: 0 }; g.array.forEach(function (node) { center.x += node.x; center.y += node.y; }); center.x /= g.array.length; center.y /= g.array.length; // calculate current top left corner: var corner = { x: center.x - g.width / 2, y: center.y - g.height / 2 }; var offset = { x: g.x - corner.x + svg_width / 2 - real_width / 2, y: g.y - corner.y + svg_height / 2 - real_height / 2 }; // put nodes: g.array.forEach(function (node) { node.x += offset.x; node.y += offset.y; }); }); } // starts box packing algorithm // desired ratio is 1 by default function apply(data, desired_ratio) { var curr_best_f = Number.POSITIVE_INFINITY; var curr_best = 0; data.sort(function (a, b) { return b.height - a.height; }); min_width = data.reduce(function (a, b) { return a.width < b.width ? a.width : b.width; }); var left = x1 = min_width; var right = x2 = get_entire_width(data); var iterationCounter = 0; var f_x1 = Number.MAX_VALUE; var f_x2 = Number.MAX_VALUE; var flag = -1; // determines which among f_x1 and f_x2 to recompute var dx = Number.MAX_VALUE; var df = Number.MAX_VALUE; while ((dx > min_width) || df > packingOptions.FLOAT_EPSILON) { if (flag != 1) { var x1 = right - (right - left) / packingOptions.GOLDEN_SECTION; var f_x1 = step(data, x1); } if (flag != 0) { var x2 = left + (right - left) / packingOptions.GOLDEN_SECTION; var f_x2 = step(data, x2); } dx = Math.abs(x1 - x2); df = Math.abs(f_x1 - f_x2); if (f_x1 < curr_best_f) { curr_best_f = f_x1; curr_best = x1; } if (f_x2 < curr_best_f) { curr_best_f = f_x2; curr_best = x2; } if (f_x1 > f_x2) { left = x1; x1 = x2; f_x1 = f_x2; flag = 1; } else { right = x2; x2 = x1; f_x2 = f_x1; flag = 0; } if (iterationCounter++ > 100) { break; } } // plot(data, min_width, get_entire_width(data), curr_best, curr_best_f); step(data, curr_best); } // one iteration of the optimization method // (gives a proper, but not necessarily optimal packing) function step(data, max_width) { line = []; real_width = 0; real_height = 0; global_bottom = init_y; for (var i = 0; i < data.length; i++) { var o = data[i]; put_rect(o, max_width); } return Math.abs(get_real_ratio() - desired_ratio); } // looking for a position to one box function put_rect(rect, max_width) { var parent = undefined; for (var i = 0; i < line.length; i++) { if ((line[i].space_left >= rect.height) && (line[i].x + line[i].width + rect.width + packingOptions.PADDING - max_width) <= packingOptions.FLOAT_EPSILON) { parent = line[i]; break; } } line.push(rect); if (parent !== undefined) { rect.x = parent.x + parent.width + packingOptions.PADDING; rect.y = parent.bottom; rect.space_left = rect.height; rect.bottom = rect.y; parent.space_left -= rect.height + packingOptions.PADDING; parent.bottom += rect.height + packingOptions.PADDING; } else { rect.y = global_bottom; global_bottom += rect.height + packingOptions.PADDING; rect.x = init_x; rect.bottom = rect.y; rect.space_left = rect.height; } if (rect.y + rect.height - real_height > -packingOptions.FLOAT_EPSILON) real_height = rect.y + rect.height - init_y; if (rect.x + rect.width - real_width > -packingOptions.FLOAT_EPSILON) real_width = rect.x + rect.width - init_x; } ; function get_entire_width(data) { var width = 0; data.forEach(function (d) { return width += d.width + packingOptions.PADDING; }); return width; } function get_real_ratio() { return (real_width / real_height); } } cola.applyPacking = applyPacking; /** * connected components of graph * returns an array of {} */ function separateGraphs(nodes, links) { var marks = {}; var ways = {}; var graphs = []; var clusters = 0; for (var i = 0; i < links.length; i++) { var link = links[i]; var n1 = link.source; var n2 = link.target; if (ways[n1.index]) ways[n1.index].push(n2); else ways[n1.index] = [n2]; if (ways[n2.index]) ways[n2.index].push(n1); else ways[n2.index] = [n1]; } for (var i = 0; i < nodes.length; i++) { var node = nodes[i]; if (marks[node.index]) continue; explore_node(node, true); } function explore_node(n, is_new) { if (marks[n.index] !== undefined) return; if (is_new) { clusters++; graphs.push({ array: [] }); } marks[n.index] = clusters; graphs[clusters - 1].array.push(n); var adjacent = ways[n.index]; if (!adjacent) return; for (var j = 0; j < adjacent.length; j++) { explore_node(adjacent[j], false); } } return graphs; } cola.separateGraphs = separateGraphs; })(cola || (cola = {})); var cola; (function (cola) { var vpsc; (function (vpsc) { var PositionStats = (function () { function PositionStats(scale) { this.scale = scale; this.AB = 0; this.AD = 0; this.A2 = 0; } PositionStats.prototype.addVariable = function (v) { var ai = this.scale / v.scale; var bi = v.offset / v.scale; var wi = v.weight; this.AB += wi * ai * bi; this.AD += wi * ai * v.desiredPosition; this.A2 += wi * ai * ai; }; PositionStats.prototype.getPosn = function () { return (this.AD - this.AB) / this.A2; }; return PositionStats; })(); vpsc.PositionStats = PositionStats; var Constraint = (function () { function Constraint(left, right, gap, equality) { if (equality === void 0) { equality = false; } this.left = left; this.right = right; this.gap = gap; this.equality = equality; this.active = false; this.unsatisfiable = false; this.left = left; this.right = right; this.gap = gap; this.equality = equality; } Constraint.prototype.slack = function () { return this.unsatisfiable ? Number.MAX_VALUE : this.right.scale * this.right.position() - this.gap - this.left.scale * this.left.position(); }; return Constraint; })(); vpsc.Constraint = Constraint; var Variable = (function () { function Variable(desiredPosition, weight, scale) { if (weight === void 0) { weight = 1; } if (scale === void 0) { scale = 1; } this.desiredPosition = desiredPosition; this.weight = weight; this.scale = scale; this.offset = 0; } Variable.prototype.dfdv = function () { return 2.0 * this.weight * (this.position() - this.desiredPosition); }; Variable.prototype.position = function () { return (this.block.ps.scale * this.block.posn + this.offset) / this.scale; }; // visit neighbours by active constraints within the same block Variable.prototype.visitNeighbours = function (prev, f) { var ff = function (c, next) { return c.active && prev !== next && f(c, next); }; this.cOut.forEach(function (c) { return ff(c, c.right); }); this.cIn.forEach(function (c) { return ff(c, c.left); }); }; return Variable; })(); vpsc.Variable = Variable; var Block = (function () { function Block(v) { this.vars = []; v.offset = 0; this.ps = new PositionStats(v.scale); this.addVariable(v); } Block.prototype.addVariable = function (v) { v.block = this; this.vars.push(v); this.ps.addVariable(v); this.posn = this.ps.getPosn(); }; // move the block where it needs to be to minimize cost Block.prototype.updateWeightedPosition = function () { this.ps.AB = this.ps.AD = this.ps.A2 = 0; for (var i = 0, n = this.vars.length; i < n; ++i) this.ps.addVariable(this.vars[i]); this.posn = this.ps.getPosn(); }; Block.prototype.compute_lm = function (v, u, postAction) { var _this = this; var dfdv = v.dfdv(); v.visitNeighbours(u, function (c, next) { var _dfdv = _this.compute_lm(next, v, postAction); if (next === c.right) { dfdv += _dfdv * c.left.scale; c.lm = _dfdv; } else { dfdv += _dfdv * c.right.scale; c.lm = -_dfdv; } postAction(c); }); return dfdv / v.scale; }; Block.prototype.populateSplitBlock = function (v, prev) { var _this = this; v.visitNeighbours(prev, function (c, next) { next.offset = v.offset + (next === c.right ? c.gap : -c.gap); _this.addVariable(next); _this.populateSplitBlock(next, v); }); }; // traverse the active constraint tree applying visit to each active constraint Block.prototype.traverse = function (visit, acc, v, prev) { var _this = this; if (v === void 0) { v = this.vars[0]; } if (prev === void 0) { prev = null; } v.visitNeighbours(prev, function (c, next) { acc.push(visit(c)); _this.traverse(visit, acc, next, v); }); }; // calculate lagrangian multipliers on constraints and // find the active constraint in this block with the smallest lagrangian. // if the lagrangian is negative, then the constraint is a split candidate. Block.prototype.findMinLM = function () { var m = null; this.compute_lm(this.vars[0], null, function (c) { if (!c.equality && (m === null || c.lm < m.lm)) m = c; }); return m; }; Block.prototype.findMinLMBetween = function (lv, rv) { this.compute_lm(lv, null, function () { }); var m = null; this.findPath(lv, null, rv, function (c, next) { if (!c.equality && c.right === next && (m === null || c.lm < m.lm)) m = c; }); return m; }; Block.prototype.findPath = function (v, prev, to, visit) { var _this = this; var endFound = false; v.visitNeighbours(prev, function (c, next) { if (!endFound && (next === to || _this.findPath(next, v, to, visit))) { endFound = true; visit(c, next); } }); return endFound; }; // Search active constraint tree from u to see if there is a directed path to v. // Returns true if path is found. Block.prototype.isActiveDirectedPathBetween = function (u, v) { if (u === v) return true; var i = u.cOut.length; while (i--) { var c = u.cOut[i]; if (c.active && this.isActiveDirectedPathBetween(c.right, v)) return true; } return false; }; // split the block into two by deactivating the specified constraint Block.split = function (c) { /* DEBUG console.log("split on " + c); console.assert(c.active, "attempt to split on inactive constraint"); DEBUG */ c.active = false; return [Block.createSplitBlock(c.left), Block.createSplitBlock(c.right)]; }; Block.createSplitBlock = function (startVar) { var b = new Block(startVar); b.populateSplitBlock(startVar, null); return b; }; // find a split point somewhere between the specified variables Block.prototype.splitBetween = function (vl, vr) { /* DEBUG console.assert(vl.block === this); console.assert(vr.block === this); DEBUG */ var c = this.findMinLMBetween(vl, vr); if (c !== null) { var bs = Block.split(c); return { constraint: c, lb: bs[0], rb: bs[1] }; } // couldn't find a split point - for example the active path is all equality constraints return null; }; Block.prototype.mergeAcross = function (b, c, dist) { c.active = true; for (var i = 0, n = b.vars.length; i < n; ++i) { var v = b.vars[i]; v.offset += dist; this.addVariable(v); } this.posn = this.ps.getPosn(); }; Block.prototype.cost = function () { var sum = 0, i = this.vars.length; while (i--) { var v = this.vars[i], d = v.position() - v.desiredPosition; sum += d * d * v.weight; } return sum; }; return Block; })(); vpsc.Block = Block; var Blocks = (function () { function Blocks(vs) { this.vs = vs; var n = vs.length; this.list = new Array(n); while (n--) { var b = new Block(vs[n]); this.list[n] = b; b.blockInd = n; } } Blocks.prototype.cost = function () { var sum = 0, i = this.list.length; while (i--) sum += this.list[i].cost(); return sum; }; Blocks.prototype.insert = function (b) { /* DEBUG console.assert(!this.contains(b), "blocks error: tried to reinsert block " + b.blockInd) DEBUG */ b.blockInd = this.list.length; this.list.push(b); /* DEBUG console.log("insert block: " + b.blockInd); this.contains(b); DEBUG */ }; Blocks.prototype.remove = function (b) { /* DEBUG console.log("remove block: " + b.blockInd); console.assert(this.contains(b)); DEBUG */ var last = this.list.length - 1; var swapBlock = this.list[last]; this.list.length = last; if (b !== swapBlock) { this.list[b.blockInd] = swapBlock; swapBlock.blockInd = b.blockInd; } }; // merge the blocks on either side of the specified constraint, by copying the smaller block into the larger // and deleting the smaller. Blocks.prototype.merge = function (c) { var l = c.left.block, r = c.right.block; /* DEBUG console.assert(l!==r, "attempt to merge within the same block"); DEBUG */ var dist = c.right.offset - c.left.offset - c.gap; if (l.vars.length < r.vars.length) { r.mergeAcross(l, c, dist); this.remove(l); } else { l.mergeAcross(r, c, -dist); this.remove(r); } /* DEBUG console.assert(Math.abs(c.slack()) < 1e-6, "Error: Constraint should be at equality after merge!"); console.log("merged on " + c); DEBUG */ }; Blocks.prototype.forEach = function (f) { this.list.forEach(f); }; // useful, for example, after variable desired positions change. Blocks.prototype.updateBlockPositions = function () { this.list.forEach(function (b) { return b.updateWeightedPosition(); }); }; // split each block across its constraint with the minimum lagrangian Blocks.prototype.split = function (inactive) { var _this = this; this.updateBlockPositions(); this.list.forEach(function (b) { var v = b.findMinLM(); if (v !== null && v.lm < Solver.LAGRANGIAN_TOLERANCE) { b = v.left.block; Block.split(v).forEach(function (nb) { return _this.insert(nb); }); _this.remove(b); inactive.push(v); } }); }; return Blocks; })(); vpsc.Blocks = Blocks; var Solver = (function () { function Solver(vs, cs) { this.vs = vs; this.cs = cs; this.vs = vs; vs.forEach(function (v) { v.cIn = [], v.cOut = []; /* DEBUG v.toString = () => "v" + vs.indexOf(v); DEBUG */ }); this.cs = cs; cs.forEach(function (c) { c.left.cOut.push(c); c.right.cIn.push(c); /* DEBUG c.toString = () => c.left + "+" + c.gap + "<=" + c.right + " slack=" + c.slack() + " active=" + c.active; DEBUG */ }); this.inactive = cs.map(function (c) { c.active = false; return c; }); this.bs = null; } Solver.prototype.cost = function () { return this.bs.cost(); }; // set starting positions without changing desired positions. // Note: it throws away any previous block structure. Solver.prototype.setStartingPositions = function (ps) { this.inactive = this.cs.map(function (c) { c.active = false; return c; }); this.bs = new Blocks(this.vs); this.bs.forEach(function (b, i) { return b.posn = ps[i]; }); }; Solver.prototype.setDesiredPositions = function (ps) { this.vs.forEach(function (v, i) { return v.desiredPosition = ps[i]; }); }; /* DEBUG private getId(v: Variable): number { return this.vs.indexOf(v); } // sanity check of the index integrity of the inactive list checkInactive(): void { var inactiveCount = 0; this.cs.forEach(c=> { var i = this.inactive.indexOf(c); console.assert(!c.active && i >= 0 || c.active && i < 0, "constraint should be in the inactive list if it is not active: " + c); if (i >= 0) { inactiveCount++; } else { console.assert(c.active, "inactive constraint not found in inactive list: " + c); } }); console.assert(inactiveCount === this.inactive.length, inactiveCount + " inactive constraints found, " + this.inactive.length + "in inactive list"); } // after every call to satisfy the following should check should pass checkSatisfied(): void { this.cs.forEach(c=>console.assert(c.slack() >= vpsc.Solver.ZERO_UPPERBOUND, "Error: Unsatisfied constraint! "+c)); } DEBUG */ Solver.prototype.mostViolated = function () { var minSlack = Number.MAX_VALUE, v = null, l = this.inactive, n = l.length, deletePoint = n; for (var i = 0; i < n; ++i) { var c = l[i]; if (c.unsatisfiable) continue; var slack = c.slack(); if (c.equality || slack < minSlack) { minSlack = slack; v = c; deletePoint = i; if (c.equality) break; } } if (deletePoint !== n && (minSlack < Solver.ZERO_UPPERBOUND && !v.active || v.equality)) { l[deletePoint] = l[n - 1]; l.length = n - 1; } return v; }; // satisfy constraints by building block structure over violated constraints // and moving the blocks to their desired positions Solver.prototype.satisfy = function () { if (this.bs == null) { this.bs = new Blocks(this.vs); } /* DEBUG console.log("satisfy: " + this.bs); DEBUG */ this.bs.split(this.inactive); var v = null; while ((v = this.mostViolated()) && (v.equality || v.slack() < Solver.ZERO_UPPERBOUND && !v.active)) { var lb = v.left.block, rb = v.right.block; /* DEBUG console.log("most violated is: " + v); this.bs.contains(lb); this.bs.contains(rb); DEBUG */ if (lb !== rb) { this.bs.merge(v); } else { if (lb.isActiveDirectedPathBetween(v.right, v.left)) { // cycle found! v.unsatisfiable = true; continue; } // constraint is within block, need to split first var split = lb.splitBetween(v.left, v.right); if (split !== null) { this.bs.insert(split.lb); this.bs.insert(split.rb); this.bs.remove(lb); this.inactive.push(split.constraint); } else { /* DEBUG console.log("unsatisfiable constraint found"); DEBUG */ v.unsatisfiable = true; continue; } if (v.slack() >= 0) { /* DEBUG console.log("violated constraint indirectly satisfied: " + v); DEBUG */ // v was satisfied by the above split! this.inactive.push(v); } else { /* DEBUG console.log("merge after split:"); DEBUG */ this.bs.merge(v); } } } /* DEBUG this.checkSatisfied(); DEBUG */ }; // repeatedly build and split block structure until we converge to an optimal solution Solver.prototype.solve = function () { this.satisfy(); var lastcost = Number.MAX_VALUE, cost = this.bs.cost(); while (Math.abs(lastcost - cost) > 0.0001) { this.satisfy(); lastcost = cost; cost = this.bs.cost(); } return cost; }; Solver.LAGRANGIAN_TOLERANCE = -1e-4; Solver.ZERO_UPPERBOUND = -1e-10; return Solver; })(); vpsc.Solver = Solver; })(vpsc = cola.vpsc || (cola.vpsc = {})); })(cola || (cola = {})); var __extends = (this && this.__extends) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; function __() { this.constructor = d; } __.prototype = b.prototype; d.prototype = new __(); }; var cola; (function (cola) { var vpsc; (function (vpsc) { //Based on js_es: // //https://github.com/vadimg/js_bintrees // //Copyright (C) 2011 by Vadim Graboys // //Permission is hereby granted, free of charge, to any person obtaining a copy //of this software and associated documentation files (the "Software"), to deal //in the Software without restriction, including without limitation the rights //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell //copies of the Software, and to permit persons to whom the Software is //furnished to do so, subject to the following conditions: // //The above copyright notice and this permission notice shall be included in //all copies or substantial portions of the Software. // //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN //THE SOFTWARE. var TreeBase = (function () { function TreeBase() { // returns iterator to node if found, null otherwise this.findIter = function (data) { var res = this._root; var iter = this.iterator(); while (res !== null) { var c = this._comparator(data, res.data); if (c === 0) { iter._cursor = res; return iter; } else { iter._ancestors.push(res); res = res.get_child(c > 0); } } return null; }; } // removes all nodes from the tree TreeBase.prototype.clear = function () { this._root = null; this.size = 0; }; ; // returns node data if found, null otherwise TreeBase.prototype.find = function (data) { var res = this._root; while (res !== null) { var c = this._comparator(data, res.data); if (c === 0) { return res.data; } else { res = res.get_child(c > 0); } } return null; }; ; // Returns an interator to the tree node immediately before (or at) the element TreeBase.prototype.lowerBound = function (data) { return this._bound(data, this._comparator); }; ; // Returns an interator to the tree node immediately after (or at) the element TreeBase.prototype.upperBound = function (data) { var cmp = this._comparator; function reverse_cmp(a, b) { return cmp(b, a); } return this._bound(data, reverse_cmp); }; ; // returns null if tree is empty TreeBase.prototype.min = function () { var res = this._root; if (res === null) { return null; } while (res.left !== null) { res = res.left; } return res.data; }; ; // returns null if tree is empty TreeBase.prototype.max = function () { var res = this._root; if (res === null) { return null; } while (res.right !== null) { res = res.right; } return res.data; }; ; // returns a null iterator // call next() or prev() to point to an element TreeBase.prototype.iterator = function () { return new Iterator(this); }; ; // calls cb on each node's data, in order TreeBase.prototype.each = function (cb) { var it = this.iterator(), data; while ((data = it.next()) !== null) { cb(data); } }; ; // calls cb on each node's data, in reverse order TreeBase.prototype.reach = function (cb) { var it = this.iterator(), data; while ((data = it.prev()) !== null) { cb(data); } }; ; // used for lowerBound and upperBound TreeBase.prototype._bound = function (data, cmp) { var cur = this._root; var iter = this.iterator(); while (cur !== null) { var c = this._comparator(data, cur.data); if (c === 0) { iter._cursor = cur; return iter; } iter._ancestors.push(cur); cur = cur.get_child(c > 0); } for (var i = iter._ancestors.length - 1; i >= 0; --i) { cur = iter._ancestors[i]; if (cmp(data, cur.data) > 0) { iter._cursor = cur; iter._ancestors.length = i; return iter; } } iter._ancestors.length = 0; return iter; }; ; return TreeBase; })(); vpsc.TreeBase = TreeBase; var Iterator = (function () { function Iterator(tree) { this._tree = tree; this._ancestors = []; this._cursor = null; } Iterator.prototype.data = function () { return this._cursor !== null ? this._cursor.data : null; }; ; // if null-iterator, returns first node // otherwise, returns next node Iterator.prototype.next = function () { if (this._cursor === null) { var root = this._tree._root; if (root !== null) { this._minNode(root); } } else { if (this._cursor.right === null) { // no greater node in subtree, go up to parent // if coming from a right child, continue up the stack var save; do { save = this._cursor; if (this._ancestors.length) { this._cursor = this._ancestors.pop(); } else { this._cursor = null; break; } } while (this._cursor.right === save); } else { // get the next node from the subtree this._ancestors.push(this._cursor); this._minNode(this._cursor.right); } } return this._cursor !== null ? this._cursor.data : null; }; ; // if null-iterator, returns last node // otherwise, returns previous node Iterator.prototype.prev = function () { if (this._cursor === null) { var root = this._tree._root; if (root !== null) { this._maxNode(root); } } else { if (this._cursor.left === null) { var save; do { save = this._cursor; if (this._ancestors.length) { this._cursor = this._ancestors.pop(); } else { this._cursor = null; break; } } while (this._cursor.left === save); } else { this._ancestors.push(this._cursor); this._maxNode(this._cursor.left); } } return this._cursor !== null ? this._cursor.data : null; }; ; Iterator.prototype._minNode = function (start) { while (start.left !== null) { this._ancestors.push(start); start = start.left; } this._cursor = start; }; ; Iterator.prototype._maxNode = function (start) { while (start.right !== null) { this._ancestors.push(start); start = start.right; } this._cursor = start; }; ; return Iterator; })(); vpsc.Iterator = Iterator; var Node = (function () { function Node(data) { this.data = data; this.left = null; this.right = null; this.red = true; } Node.prototype.get_child = function (dir) { return dir ? this.right : this.left; }; ; Node.prototype.set_child = function (dir, val) { if (dir) { this.right = val; } else { this.left = val; } }; ; return Node; })(); var RBTree = (function (_super) { __extends(RBTree, _super); function RBTree(comparator) { _super.call(this); this._root = null; this._comparator = comparator; this.size = 0; } // returns true if inserted, false if duplicate RBTree.prototype.insert = function (data) { var ret = false; if (this._root === null) { // empty tree this._root = new Node(data); ret = true; this.size++; } else { var head = new Node(undefined); // fake tree root var dir = false; var last = false; // setup var gp = null; // grandparent var ggp = head; // grand-grand-parent var p = null; // parent var node = this._root; ggp.right = this._root; // search down while (true) { if (node === null) { // insert new node at the bottom node = new Node(data); p.set_child(dir, node); ret = true; this.size++; } else if (RBTree.is_red(node.left) && RBTree.is_red(node.right)) { // color flip node.red = true; node.left.red = false; node.right.red = false; } // fix red violation if (RBTree.is_red(node) && RBTree.is_red(p)) { var dir2 = ggp.right === gp; if (node === p.get_child(last)) { ggp.set_child(dir2, RBTree.single_rotate(gp, !last)); } else { ggp.set_child(dir2, RBTree.double_rotate(gp, !last)); } } var cmp = this._comparator(node.data, data); // stop if found if (cmp === 0) { break; } last = dir; dir = cmp < 0; // update helpers if (gp !== null) { ggp = gp; } gp = p; p = node; node = node.get_child(dir); } // update root this._root = head.right; } // make root black this._root.red = false; return ret; }; ; // returns true if removed, false if not found RBTree.prototype.remove = function (data) { if (this._root === null) { return false; } var head = new Node(undefined); // fake tree root var node = head; node.right = this._root; var p = null; // parent var gp = null; // grand parent var found = null; // found item var dir = true; while (node.get_child(dir) !== null) { var last = dir; // update helpers gp = p; p = node; node = node.get_child(dir); var cmp = this._comparator(data, node.data); dir = cmp > 0; // save found node if (cmp === 0) { found = node; } // push the red node down if (!RBTree.is_red(node) && !RBTree.is_red(node.get_child(dir))) { if (RBTree.is_red(node.get_child(!dir))) { var sr = RBTree.single_rotate(node, dir); p.set_child(last, sr); p = sr; } else if (!RBTree.is_red(node.get_child(!dir))) { var sibling = p.get_child(!last); if (sibling !== null) { if (!RBTree.is_red(sibling.get_child(!last)) && !RBTree.is_red(sibling.get_child(last))) { // color flip p.red = false; sibling.red = true; node.red = true; } else { var dir2 = gp.right === p; if (RBTree.is_red(sibling.get_child(last))) { gp.set_child(dir2, RBTree.double_rotate(p, last)); } else if (RBTree.is_red(sibling.get_child(!last))) { gp.set_child(dir2, RBTree.single_rotate(p, last)); } // ensure correct coloring var gpc = gp.get_child(dir2); gpc.red = true; node.red = true; gpc.left.red = false; gpc.right.red = false; } } } } } // replace and remove if found if (found !== null) { found.data = node.data; p.set_child(p.right === node, node.get_child(node.left === null)); this.size--; } // update root and make it black this._root = head.right; if (this._root !== null) { this._root.red = false; } return found !== null; }; ; RBTree.is_red = function (node) { return node !== null && node.red; }; RBTree.single_rotate = function (root, dir) { var save = root.get_child(!dir); root.set_child(!dir, save.get_child(dir)); save.set_child(dir, root); root.red = true; save.red = false; return save; }; RBTree.double_rotate = function (root, dir) { root.set_child(!dir, RBTree.single_rotate(root.get_child(!dir), !dir)); return RBTree.single_rotate(root, dir); }; return RBTree; })(TreeBase); vpsc.RBTree = RBTree; })(vpsc = cola.vpsc || (cola.vpsc = {})); })(cola || (cola = {})); /// /// var cola; (function (cola) { var vpsc; (function (vpsc) { function computeGroupBounds(g) { g.bounds = typeof g.leaves !== "undefined" ? g.leaves.reduce(function (r, c) { return c.bounds.union(r); }, Rectangle.empty()) : Rectangle.empty(); if (typeof g.groups !== "undefined") g.bounds = g.groups.reduce(function (r, c) { return computeGroupBounds(c).union(r); }, g.bounds); g.bounds = g.bounds.inflate(g.padding); return g.bounds; } vpsc.computeGroupBounds = computeGroupBounds; var Rectangle = (function () { function Rectangle(x, X, y, Y) { this.x = x; this.X = X; this.y = y; this.Y = Y; } Rectangle.empty = function () { return new Rectangle(Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY); }; Rectangle.prototype.cx = function () { return (this.x + this.X) / 2; }; Rectangle.prototype.cy = function () { return (this.y + this.Y) / 2; }; Rectangle.prototype.overlapX = function (r) { var ux = this.cx(), vx = r.cx(); if (ux <= vx && r.x < this.X) return this.X - r.x; if (vx <= ux && this.x < r.X) return r.X - this.x; return 0; }; Rectangle.prototype.overlapY = function (r) { var uy = this.cy(), vy = r.cy(); if (uy <= vy && r.y < this.Y) return this.Y - r.y; if (vy <= uy && this.y < r.Y) return r.Y - this.y; return 0; }; Rectangle.prototype.setXCentre = function (cx) { var dx = cx - this.cx(); this.x += dx; this.X += dx; }; Rectangle.prototype.setYCentre = function (cy) { var dy = cy - this.cy(); this.y += dy; this.Y += dy; }; Rectangle.prototype.width = function () { return this.X - this.x; }; Rectangle.prototype.height = function () { return this.Y - this.y; }; Rectangle.prototype.union = function (r) { return new Rectangle(Math.min(this.x, r.x), Math.max(this.X, r.X), Math.min(this.y, r.y), Math.max(this.Y, r.Y)); }; /** * return any intersection points between the given line and the sides of this rectangle * @method lineIntersection * @param x1 number first x coord of line * @param y1 number first y coord of line * @param x2 number second x coord of line * @param y2 number second y coord of line * @return any intersection points found */ Rectangle.prototype.lineIntersections = function (x1, y1, x2, y2) { var sides = [[this.x, this.y, this.X, this.y], [this.X, this.y, this.X, this.Y], [this.X, this.Y, this.x, this.Y], [this.x, this.Y, this.x, this.y]]; var intersections = []; for (var i = 0; i < 4; ++i) { var r = Rectangle.lineIntersection(x1, y1, x2, y2, sides[i][0], sides[i][1], sides[i][2], sides[i][3]); if (r !== null) intersections.push({ x: r.x, y: r.y }); } return intersections; }; /** * return any intersection points between a line extending from the centre of this rectangle to the given point, * and the sides of this rectangle * @method lineIntersection * @param x2 number second x coord of line * @param y2 number second y coord of line * @return any intersection points found */ Rectangle.prototype.rayIntersection = function (x2, y2) { var ints = this.lineIntersections(this.cx(), this.cy(), x2, y2); return ints.length > 0 ? ints[0] : null; }; Rectangle.prototype.vertices = function () { return [ { x: this.x, y: this.y }, { x: this.X, y: this.y }, { x: this.X, y: this.Y }, { x: this.x, y: this.Y }, { x: this.x, y: this.y }]; }; Rectangle.lineIntersection = function (x1, y1, x2, y2, x3, y3, x4, y4) { var dx12 = x2 - x1, dx34 = x4 - x3, dy12 = y2 - y1, dy34 = y4 - y3, denominator = dy34 * dx12 - dx34 * dy12; if (denominator == 0) return null; var dx31 = x1 - x3, dy31 = y1 - y3, numa = dx34 * dy31 - dy34 * dx31, a = numa / denominator, numb = dx12 * dy31 - dy12 * dx31, b = numb / denominator; if (a >= 0 && a <= 1 && b >= 0 && b <= 1) { return { x: x1 + a * dx12, y: y1 + a * dy12 }; } return null; }; Rectangle.prototype.inflate = function (pad) { return new Rectangle(this.x - pad, this.X + pad, this.y - pad, this.Y + pad); }; return Rectangle; })(); vpsc.Rectangle = Rectangle; function makeEdgeBetween(source, target, ah) { var si = source.rayIntersection(target.cx(), target.cy()) || { x: source.cx(), y: source.cy() }, ti = target.rayIntersection(source.cx(), source.cy()) || { x: target.cx(), y: target.cy() }, dx = ti.x - si.x, dy = ti.y - si.y, l = Math.sqrt(dx * dx + dy * dy), al = l - ah; return { sourceIntersection: si, targetIntersection: ti, arrowStart: { x: si.x + al * dx / l, y: si.y + al * dy / l } }; } vpsc.makeEdgeBetween = makeEdgeBetween; function makeEdgeTo(s, target, ah) { var ti = target.rayIntersection(s.x, s.y); if (!ti) ti = { x: target.cx(), y: target.cy() }; var dx = ti.x - s.x, dy = ti.y - s.y, l = Math.sqrt(dx * dx + dy * dy); return { x: ti.x - ah * dx / l, y: ti.y - ah * dy / l }; } vpsc.makeEdgeTo = makeEdgeTo; var Node = (function () { function Node(v, r, pos) { this.v = v; this.r = r; this.pos = pos; this.prev = makeRBTree(); this.next = makeRBTree(); } return Node; })(); var Event = (function () { function Event(isOpen, v, pos) { this.isOpen = isOpen; this.v = v; this.pos = pos; } return Event; })(); function compareEvents(a, b) { if (a.pos > b.pos) { return 1; } if (a.pos < b.pos) { return -1; } if (a.isOpen) { // open must come before close return -1; } return 0; } function makeRBTree() { return new vpsc.RBTree(function (a, b) { return a.pos - b.pos; }); } var xRect = { getCentre: function (r) { return r.cx(); }, getOpen: function (r) { return r.y; }, getClose: function (r) { return r.Y; }, getSize: function (r) { return r.width(); }, makeRect: function (open, close, center, size) { return new Rectangle(center - size / 2, center + size / 2, open, close); }, findNeighbours: findXNeighbours }; var yRect = { getCentre: function (r) { return r.cy(); }, getOpen: function (r) { return r.x; }, getClose: function (r) { return r.X; }, getSize: function (r) { return r.height(); }, makeRect: function (open, close, center, size) { return new Rectangle(open, close, center - size / 2, center + size / 2); }, findNeighbours: findYNeighbours }; function generateGroupConstraints(root, f, minSep, isContained) { if (isContained === void 0) { isContained = false; } var padding = root.padding, gn = typeof root.groups !== 'undefined' ? root.groups.length : 0, ln = typeof root.leaves !== 'undefined' ? root.leaves.length : 0, childConstraints = !gn ? [] : root.groups.reduce(function (ccs, g) { return ccs.concat(generateGroupConstraints(g, f, minSep, true)); }, []), n = (isContained ? 2 : 0) + ln + gn, vs = new Array(n), rs = new Array(n), i = 0, add = function (r, v) { rs[i] = r; vs[i++] = v; }; if (isContained) { // if this group is contained by another, then we add two dummy vars and rectangles for the borders var b = root.bounds, c = f.getCentre(b), s = f.getSize(b) / 2, open = f.getOpen(b), close = f.getClose(b), min = c - s + padding / 2, max = c + s - padding / 2; root.minVar.desiredPosition = min; add(f.makeRect(open, close, min, padding), root.minVar); root.maxVar.desiredPosition = max; add(f.makeRect(open, close, max, padding), root.maxVar); } if (ln) root.leaves.forEach(function (l) { return add(l.bounds, l.variable); }); if (gn) root.groups.forEach(function (g) { var b = g.bounds; add(f.makeRect(f.getOpen(b), f.getClose(b), f.getCentre(b), f.getSize(b)), g.minVar); }); var cs = generateConstraints(rs, vs, f, minSep); if (gn) { vs.forEach(function (v) { v.cOut = [], v.cIn = []; }); cs.forEach(function (c) { c.left.cOut.push(c), c.right.cIn.push(c); }); root.groups.forEach(function (g) { var gapAdjustment = (g.padding - f.getSize(g.bounds)) / 2; g.minVar.cIn.forEach(function (c) { return c.gap += gapAdjustment; }); g.minVar.cOut.forEach(function (c) { c.left = g.maxVar; c.gap += gapAdjustment; }); }); } return childConstraints.concat(cs); } function generateConstraints(rs, vars, rect, minSep) { var i, n = rs.length; var N = 2 * n; console.assert(vars.length >= n); var events = new Array(N); for (i = 0; i < n; ++i) { var r = rs[i]; var v = new Node(vars[i], r, rect.getCentre(r)); events[i] = new Event(true, v, rect.getOpen(r)); events[i + n] = new Event(false, v, rect.getClose(r)); } events.sort(compareEvents); var cs = new Array(); var scanline = makeRBTree(); for (i = 0; i < N; ++i) { var e = events[i]; var v = e.v; if (e.isOpen) { scanline.insert(v); rect.findNeighbours(v, scanline); } else { // close event scanline.remove(v); var makeConstraint = function (l, r) { var sep = (rect.getSize(l.r) + rect.getSize(r.r)) / 2 + minSep; cs.push(new vpsc.Constraint(l.v, r.v, sep)); }; var visitNeighbours = function (forward, reverse, mkcon) { var u, it = v[forward].iterator(); while ((u = it[forward]()) !== null) { mkcon(u, v); u[reverse].remove(v); } }; visitNeighbours("prev", "next", function (u, v) { return makeConstraint(u, v); }); visitNeighbours("next", "prev", function (u, v) { return makeConstraint(v, u); }); } } console.assert(scanline.size === 0); return cs; } function findXNeighbours(v, scanline) { var f = function (forward, reverse) { var it = scanline.findIter(v); var u; while ((u = it[forward]()) !== null) { var uovervX = u.r.overlapX(v.r); if (uovervX <= 0 || uovervX <= u.r.overlapY(v.r)) { v[forward].insert(u); u[reverse].insert(v); } if (uovervX <= 0) { break; } } }; f("next", "prev"); f("prev", "next"); } function findYNeighbours(v, scanline) { var f = function (forward, reverse) { var u = scanline.findIter(v)[forward](); if (u !== null && u.r.overlapX(v.r) > 0) { v[forward].insert(u); u[reverse].insert(v); } }; f("next", "prev"); f("prev", "next"); } function generateXConstraints(rs, vars) { return generateConstraints(rs, vars, xRect, 1e-6); } vpsc.generateXConstraints = generateXConstraints; function generateYConstraints(rs, vars) { return generateConstraints(rs, vars, yRect, 1e-6); } vpsc.generateYConstraints = generateYConstraints; function generateXGroupConstraints(root) { return generateGroupConstraints(root, xRect, 1e-6); } vpsc.generateXGroupConstraints = generateXGroupConstraints; function generateYGroupConstraints(root) { return generateGroupConstraints(root, yRect, 1e-6); } vpsc.generateYGroupConstraints = generateYGroupConstraints; function removeOverlaps(rs) { var vs = rs.map(function (r) { return new vpsc.Variable(r.cx()); }); var cs = vpsc.generateXConstraints(rs, vs); var solver = new vpsc.Solver(vs, cs); solver.solve(); vs.forEach(function (v, i) { return rs[i].setXCentre(v.position()); }); vs = rs.map(function (r) { return new vpsc.Variable(r.cy()); }); cs = vpsc.generateYConstraints(rs, vs); solver = new vpsc.Solver(vs, cs); solver.solve(); vs.forEach(function (v, i) { return rs[i].setYCentre(v.position()); }); } vpsc.removeOverlaps = removeOverlaps; var IndexedVariable = (function (_super) { __extends(IndexedVariable, _super); function IndexedVariable(index, w) { _super.call(this, 0, w); this.index = index; } return IndexedVariable; })(vpsc.Variable); vpsc.IndexedVariable = IndexedVariable; var Projection = (function () { function Projection(nodes, groups, rootGroup, constraints, avoidOverlaps) { var _this = this; if (rootGroup === void 0) { rootGroup = null; } if (constraints === void 0) { constraints = null; } if (avoidOverlaps === void 0) { avoidOverlaps = false; } this.nodes = nodes; this.groups = groups; this.rootGroup = rootGroup; this.avoidOverlaps = avoidOverlaps; this.variables = nodes.map(function (v, i) { return v.variable = new IndexedVariable(i, 1); }); if (constraints) this.createConstraints(constraints); if (avoidOverlaps && rootGroup && typeof rootGroup.groups !== 'undefined') { nodes.forEach(function (v) { if (!v.width || !v.height) { //If undefined, default to nothing v.bounds = new vpsc.Rectangle(v.x, v.x, v.y, v.y); return; } var w2 = v.width / 2, h2 = v.height / 2; v.bounds = new vpsc.Rectangle(v.x - w2, v.x + w2, v.y - h2, v.y + h2); }); computeGroupBounds(rootGroup); var i = nodes.length; groups.forEach(function (g) { _this.variables[i] = g.minVar = new IndexedVariable(i++, typeof g.stiffness !== "undefined" ? g.stiffness : 0.01); _this.variables[i] = g.maxVar = new IndexedVariable(i++, typeof g.stiffness !== "undefined" ? g.stiffness : 0.01); }); } } Projection.prototype.createSeparation = function (c) { return new vpsc.Constraint(this.nodes[c.left].variable, this.nodes[c.right].variable, c.gap, typeof c.equality !== "undefined" ? c.equality : false); }; Projection.prototype.makeFeasible = function (c) { var _this = this; if (!this.avoidOverlaps) return; var axis = 'x', dim = 'width'; if (c.axis === 'x') axis = 'y', dim = 'height'; var vs = c.offsets.map(function (o) { return _this.nodes[o.node]; }).sort(function (a, b) { return a[axis] - b[axis]; }); var p = null; vs.forEach(function (v) { if (p) v[axis] = p[axis] + p[dim] + 1; p = v; }); }; Projection.prototype.createAlignment = function (c) { var _this = this; var u = this.nodes[c.offsets[0].node].variable; this.makeFeasible(c); var cs = c.axis === 'x' ? this.xConstraints : this.yConstraints; c.offsets.slice(1).forEach(function (o) { var v = _this.nodes[o.node].variable; cs.push(new vpsc.Constraint(u, v, o.offset, true)); }); }; Projection.prototype.createConstraints = function (constraints) { var _this = this; var isSep = function (c) { return typeof c.type === 'undefined' || c.type === 'separation'; }; this.xConstraints = constraints .filter(function (c) { return c.axis === "x" && isSep(c); }) .map(function (c) { return _this.createSeparation(c); }); this.yConstraints = constraints .filter(function (c) { return c.axis === "y" && isSep(c); }) .map(function (c) { return _this.createSeparation(c); }); constraints .filter(function (c) { return c.type === 'alignment'; }) .forEach(function (c) { return _this.createAlignment(c); }); }; Projection.prototype.setupVariablesAndBounds = function (x0, y0, desired, getDesired) { this.nodes.forEach(function (v, i) { if (v.fixed) { v.variable.weight = v.fixedWeight ? v.fixedWeight : 1000; desired[i] = getDesired(v); } else { v.variable.weight = 1; } var w = (v.width || 0) / 2, h = (v.height || 0) / 2; var ix = x0[i], iy = y0[i]; v.bounds = new Rectangle(ix - w, ix + w, iy - h, iy + h); }); }; Projection.prototype.xProject = function (x0, y0, x) { if (!this.rootGroup && !(this.avoidOverlaps || this.xConstraints)) return; this.project(x0, y0, x0, x, function (v) { return v.px; }, this.xConstraints, generateXGroupConstraints, function (v) { return v.bounds.setXCentre(x[v.variable.index] = v.variable.position()); }, function (g) { var xmin = x[g.minVar.index] = g.minVar.position(); var xmax = x[g.maxVar.index] = g.maxVar.position(); var p2 = g.padding / 2; g.bounds.x = xmin - p2; g.bounds.X = xmax + p2; }); }; Projection.prototype.yProject = function (x0, y0, y) { if (!this.rootGroup && !this.yConstraints) return; this.project(x0, y0, y0, y, function (v) { return v.py; }, this.yConstraints, generateYGroupConstraints, function (v) { return v.bounds.setYCentre(y[v.variable.index] = v.variable.position()); }, function (g) { var ymin = y[g.minVar.index] = g.minVar.position(); var ymax = y[g.maxVar.index] = g.maxVar.position(); var p2 = g.padding / 2; g.bounds.y = ymin - p2; ; g.bounds.Y = ymax + p2; }); }; Projection.prototype.projectFunctions = function () { var _this = this; return [ function (x0, y0, x) { return _this.xProject(x0, y0, x); }, function (x0, y0, y) { return _this.yProject(x0, y0, y); } ]; }; Projection.prototype.project = function (x0, y0, start, desired, getDesired, cs, generateConstraints, updateNodeBounds, updateGroupBounds) { this.setupVariablesAndBounds(x0, y0, desired, getDesired); if (this.rootGroup && this.avoidOverlaps) { computeGroupBounds(this.rootGroup); cs = cs.concat(generateConstraints(this.rootGroup)); } this.solve(this.variables, cs, start, desired); this.nodes.forEach(updateNodeBounds); if (this.rootGroup && this.avoidOverlaps) { this.groups.forEach(updateGroupBounds); } }; Projection.prototype.solve = function (vs, cs, starting, desired) { var solver = new vpsc.Solver(vs, cs); solver.setStartingPositions(starting); solver.setDesiredPositions(desired); solver.solve(); }; return Projection; })(); vpsc.Projection = Projection; })(vpsc = cola.vpsc || (cola.vpsc = {})); })(cola || (cola = {})); /// /// var cola; (function (cola) { var geom; (function (geom) { var Point = (function () { function Point() { } return Point; })(); geom.Point = Point; var LineSegment = (function () { function LineSegment(x1, y1, x2, y2) { this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; } return LineSegment; })(); geom.LineSegment = LineSegment; var PolyPoint = (function (_super) { __extends(PolyPoint, _super); function PolyPoint() { _super.apply(this, arguments); } return PolyPoint; })(Point); geom.PolyPoint = PolyPoint; /** tests if a point is Left|On|Right of an infinite line. * @param points P0, P1, and P2 * @return >0 for P2 left of the line through P0 and P1 * =0 for P2 on the line * <0 for P2 right of the line */ function isLeft(P0, P1, P2) { return (P1.x - P0.x) * (P2.y - P0.y) - (P2.x - P0.x) * (P1.y - P0.y); } geom.isLeft = isLeft; function above(p, vi, vj) { return isLeft(p, vi, vj) > 0; } function below(p, vi, vj) { return isLeft(p, vi, vj) < 0; } /** * returns the convex hull of a set of points using Andrew's monotone chain algorithm * see: http://geomalgorithms.com/a10-_hull-1.html#Monotone%20Chain * @param S array of points * @return the convex hull as an array of points */ function ConvexHull(S) { var P = S.slice(0).sort(function (a, b) { return a.x !== b.x ? b.x - a.x : b.y - a.y; }); var n = S.length, i; var minmin = 0; var xmin = P[0].x; for (i = 1; i < n; ++i) { if (P[i].x !== xmin) break; } var minmax = i - 1; var H = []; H.push(P[minmin]); // push minmin point onto stack if (minmax === n - 1) { if (P[minmax].y !== P[minmin].y) H.push(P[minmax]); } else { // Get the indices of points with max x-coord and min|max y-coord var maxmin, maxmax = n - 1; var xmax = P[n - 1].x; for (i = n - 2; i >= 0; i--) if (P[i].x !== xmax) break; maxmin = i + 1; // Compute the lower hull on the stack H i = minmax; while (++i <= maxmin) { // the lower line joins P[minmin] with P[maxmin] if (isLeft(P[minmin], P[maxmin], P[i]) >= 0 && i < maxmin) continue; // ignore P[i] above or on the lower line while (H.length > 1) { // test if P[i] is left of the line at the stack top if (isLeft(H[H.length - 2], H[H.length - 1], P[i]) > 0) break; // P[i] is a new hull vertex else H.length -= 1; // pop top point off stack } if (i != minmin) H.push(P[i]); } // Next, compute the upper hull on the stack H above the bottom hull if (maxmax != maxmin) H.push(P[maxmax]); // push maxmax point onto stack var bot = H.length; // the bottom point of the upper hull stack i = maxmin; while (--i >= minmax) { // the upper line joins P[maxmax] with P[minmax] if (isLeft(P[maxmax], P[minmax], P[i]) >= 0 && i > minmax) continue; // ignore P[i] below or on the upper line while (H.length > bot) { // test if P[i] is left of the line at the stack top if (isLeft(H[H.length - 2], H[H.length - 1], P[i]) > 0) break; // P[i] is a new hull vertex else H.length -= 1; // pop top point off stack } if (i != minmin) H.push(P[i]); // push P[i] onto stack } } return H; } geom.ConvexHull = ConvexHull; // apply f to the points in P in clockwise order around the point p function clockwiseRadialSweep(p, P, f) { P.slice(0).sort(function (a, b) { return Math.atan2(a.y - p.y, a.x - p.x) - Math.atan2(b.y - p.y, b.x - p.x); }).forEach(f); } geom.clockwiseRadialSweep = clockwiseRadialSweep; function nextPolyPoint(p, ps) { if (p.polyIndex === ps.length - 1) return ps[0]; return ps[p.polyIndex + 1]; } function prevPolyPoint(p, ps) { if (p.polyIndex === 0) return ps[ps.length - 1]; return ps[p.polyIndex - 1]; } // tangent_PointPolyC(): fast binary search for tangents to a convex polygon // Input: P = a 2D point (exterior to the polygon) // n = number of polygon vertices // V = array of vertices for a 2D convex polygon with V[n] = V[0] // Output: rtan = index of rightmost tangent point V[rtan] // ltan = index of leftmost tangent point V[ltan] function tangent_PointPolyC(P, V) { return { rtan: Rtangent_PointPolyC(P, V), ltan: Ltangent_PointPolyC(P, V) }; } // Rtangent_PointPolyC(): binary search for convex polygon right tangent // Input: P = a 2D point (exterior to the polygon) // n = number of polygon vertices // V = array of vertices for a 2D convex polygon with V[n] = V[0] // Return: index "i" of rightmost tangent point V[i] function Rtangent_PointPolyC(P, V) { var n = V.length - 1; // use binary search for large convex polygons var a, b, c; // indices for edge chain endpoints var upA, dnC; // test for up direction of edges a and c // rightmost tangent = maximum for the isLeft() ordering // test if V[0] is a local maximum if (below(P, V[1], V[0]) && !above(P, V[n - 1], V[0])) return 0; // V[0] is the maximum tangent point for (a = 0, b = n;;) { if (b - a === 1) if (above(P, V[a], V[b])) return a; else return b; c = Math.floor((a + b) / 2); // midpoint of [a,b], and 0 0) this.E.push(new VisibilityEdge(p[j - 1].vv, vv)); } } for (var i = 0; i < n - 1; i++) { var Pi = P[i]; for (var j = i + 1; j < n; j++) { var Pj = P[j], t = geom.tangents(Pi, Pj); for (var q in t) { var c = t[q], source = Pi[c.t1], target = Pj[c.t2]; this.addEdgeIfVisible(source, target, i, j); } } } } else { this.V = g0.V.slice(0); this.E = g0.E.slice(0); } } TangentVisibilityGraph.prototype.addEdgeIfVisible = function (u, v, i1, i2) { if (!this.intersectsPolys(new LineSegment(u.x, u.y, v.x, v.y), i1, i2)) { this.E.push(new VisibilityEdge(u.vv, v.vv)); } }; TangentVisibilityGraph.prototype.addPoint = function (p, i1) { var n = this.P.length; this.V.push(new VisibilityVertex(this.V.length, n, 0, p)); for (var i = 0; i < n; ++i) { if (i === i1) continue; var poly = this.P[i], t = tangent_PointPolyC(p, poly); this.addEdgeIfVisible(p, poly[t.ltan], i1, i); this.addEdgeIfVisible(p, poly[t.rtan], i1, i); } return p.vv; }; TangentVisibilityGraph.prototype.intersectsPolys = function (l, i1, i2) { for (var i = 0, n = this.P.length; i < n; ++i) { if (i != i1 && i != i2 && intersects(l, this.P[i]).length > 0) { return true; } } return false; }; return TangentVisibilityGraph; })(); geom.TangentVisibilityGraph = TangentVisibilityGraph; function intersects(l, P) { var ints = []; for (var i = 1, n = P.length; i < n; ++i) { var int = cola.vpsc.Rectangle.lineIntersection(l.x1, l.y1, l.x2, l.y2, P[i - 1].x, P[i - 1].y, P[i].x, P[i].y); if (int) ints.push(int); } return ints; } function tangents(V, W) { var m = V.length - 1, n = W.length - 1; var bt = new BiTangents(); for (var i = 0; i < m; ++i) { for (var j = 0; j < n; ++j) { var v1 = V[i == 0 ? m - 1 : i - 1]; var v2 = V[i]; var v3 = V[i + 1]; var w1 = W[j == 0 ? n - 1 : j - 1]; var w2 = W[j]; var w3 = W[j + 1]; var v1v2w2 = isLeft(v1, v2, w2); var v2w1w2 = isLeft(v2, w1, w2); var v2w2w3 = isLeft(v2, w2, w3); var w1w2v2 = isLeft(w1, w2, v2); var w2v1v2 = isLeft(w2, v1, v2); var w2v2v3 = isLeft(w2, v2, v3); if (v1v2w2 >= 0 && v2w1w2 >= 0 && v2w2w3 < 0 && w1w2v2 >= 0 && w2v1v2 >= 0 && w2v2v3 < 0) { bt.ll = new BiTangent(i, j); } else if (v1v2w2 <= 0 && v2w1w2 <= 0 && v2w2w3 > 0 && w1w2v2 <= 0 && w2v1v2 <= 0 && w2v2v3 > 0) { bt.rr = new BiTangent(i, j); } else if (v1v2w2 <= 0 && v2w1w2 > 0 && v2w2w3 <= 0 && w1w2v2 >= 0 && w2v1v2 < 0 && w2v2v3 >= 0) { bt.rl = new BiTangent(i, j); } else if (v1v2w2 >= 0 && v2w1w2 < 0 && v2w2w3 >= 0 && w1w2v2 <= 0 && w2v1v2 > 0 && w2v2v3 <= 0) { bt.lr = new BiTangent(i, j); } } } return bt; } geom.tangents = tangents; function isPointInsidePoly(p, poly) { for (var i = 1, n = poly.length; i < n; ++i) if (below(poly[i - 1], poly[i], p)) return false; return true; } function isAnyPInQ(p, q) { return !p.every(function (v) { return !isPointInsidePoly(v, q); }); } function polysOverlap(p, q) { if (isAnyPInQ(p, q)) return true; if (isAnyPInQ(q, p)) return true; for (var i = 1, n = p.length; i < n; ++i) { var v = p[i], u = p[i - 1]; if (intersects(new LineSegment(u.x, u.y, v.x, v.y), q).length > 0) return true; } return false; } geom.polysOverlap = polysOverlap; })(geom = cola.geom || (cola.geom = {})); })(cola || (cola = {})); /** * @module cola */ var cola; (function (cola) { /** * Descent respects a collection of locks over nodes that should not move * @class Locks */ var Locks = (function () { function Locks() { this.locks = {}; } /** * add a lock on the node at index id * @method add * @param id index of node to be locked * @param x required position for node */ Locks.prototype.add = function (id, x) { /* DEBUG if (isNaN(x[0]) || isNaN(x[1])) debugger; DEBUG */ this.locks[id] = x; }; /** * @method clear clear all locks */ Locks.prototype.clear = function () { this.locks = {}; }; /** * @isEmpty * @returns false if no locks exist */ Locks.prototype.isEmpty = function () { for (var l in this.locks) return false; return true; }; /** * perform an operation on each lock * @apply */ Locks.prototype.apply = function (f) { for (var l in this.locks) { f(l, this.locks[l]); } }; return Locks; })(); cola.Locks = Locks; /** * Uses a gradient descent approach to reduce a stress or p-stress goal function over a graph with specified ideal edge lengths or a square matrix of dissimilarities. * The standard stress function over a graph nodes with position vectors x,y,z is (mathematica input): * stress[x_,y_,z_,D_,w_]:=Sum[w[[i,j]] (length[x[[i]],y[[i]],z[[i]],x[[j]],y[[j]],z[[j]]]-d[[i,j]])^2,{i,Length[x]-1},{j,i+1,Length[x]}] * where: D is a square matrix of ideal separations between nodes, w is matrix of weights for those separations * length[x1_, y1_, z1_, x2_, y2_, z2_] = Sqrt[(x1 - x2)^2 + (y1 - y2)^2 + (z1 - z2)^2] * below, we use wij = 1/(Dij^2) * * @class Descent */ var Descent = (function () { /** * @method constructor * @param x {number[][]} initial coordinates for nodes * @param D {number[][]} matrix of desired distances between pairs of nodes * @param G {number[][]} [default=null] if specified, G is a matrix of weights for goal terms between pairs of nodes. * If G[i][j] > 1 and the separation between nodes i and j is greater than their ideal distance, then there is no contribution for this pair to the goal * If G[i][j] <= 1 then it is used as a weighting on the contribution of the variance between ideal and actual separation between i and j to the goal function */ function Descent(x, D, G) { if (G === void 0) { G = null; } this.D = D; this.G = G; this.threshold = 0.0001; // Parameters for grid snap stress. // TODO: Make a pluggable "StressTerm" class instead of this // mess. this.numGridSnapNodes = 0; this.snapGridSize = 100; this.snapStrength = 1000; this.scaleSnapByMaxH = false; this.random = new PseudoRandom(); this.project = null; this.x = x; this.k = x.length; // dimensionality var n = this.n = x[0].length; // number of nodes this.H = new Array(this.k); this.g = new Array(this.k); this.Hd = new Array(this.k); this.a = new Array(this.k); this.b = new Array(this.k); this.c = new Array(this.k); this.d = new Array(this.k); this.e = new Array(this.k); this.ia = new Array(this.k); this.ib = new Array(this.k); this.xtmp = new Array(this.k); this.locks = new Locks(); this.minD = Number.MAX_VALUE; var i = n, j; while (i--) { j = n; while (--j > i) { var d = D[i][j]; if (d > 0 && d < this.minD) { this.minD = d; } } } if (this.minD === Number.MAX_VALUE) this.minD = 1; i = this.k; while (i--) { this.g[i] = new Array(n); this.H[i] = new Array(n); j = n; while (j--) { this.H[i][j] = new Array(n); } this.Hd[i] = new Array(n); this.a[i] = new Array(n); this.b[i] = new Array(n); this.c[i] = new Array(n); this.d[i] = new Array(n); this.e[i] = new Array(n); this.ia[i] = new Array(n); this.ib[i] = new Array(n); this.xtmp[i] = new Array(n); } } Descent.createSquareMatrix = function (n, f) { var M = new Array(n); for (var i = 0; i < n; ++i) { M[i] = new Array(n); for (var j = 0; j < n; ++j) { M[i][j] = f(i, j); } } return M; }; Descent.prototype.offsetDir = function () { var _this = this; var u = new Array(this.k); var l = 0; for (var i = 0; i < this.k; ++i) { var x = u[i] = this.random.getNextBetween(0.01, 1) - 0.5; l += x * x; } l = Math.sqrt(l); return u.map(function (x) { return x *= _this.minD / l; }); }; // compute first and second derivative information storing results in this.g and this.H Descent.prototype.computeDerivatives = function (x) { var _this = this; var n = this.n; if (n < 1) return; var i; /* DEBUG for (var u: number = 0; u < n; ++u) for (i = 0; i < this.k; ++i) if (isNaN(x[i][u])) debugger; DEBUG */ var d = new Array(this.k); var d2 = new Array(this.k); var Huu = new Array(this.k); var maxH = 0; for (var u = 0; u < n; ++u) { for (i = 0; i < this.k; ++i) Huu[i] = this.g[i][u] = 0; for (var v = 0; v < n; ++v) { if (u === v) continue; // The following loop randomly displaces nodes that are at identical positions var maxDisplaces = n; // avoid infinite loop in the case of numerical issues, such as huge values while (maxDisplaces--) { var sd2 = 0; for (i = 0; i < this.k; ++i) { var dx = d[i] = x[i][u] - x[i][v]; sd2 += d2[i] = dx * dx; } if (sd2 > 1e-9) break; var rd = this.offsetDir(); for (i = 0; i < this.k; ++i) x[i][v] += rd[i]; } var l = Math.sqrt(sd2); var D = this.D[u][v]; var weight = this.G != null ? this.G[u][v] : 1; if (weight > 1 && l > D || !isFinite(D)) { for (i = 0; i < this.k; ++i) this.H[i][u][v] = 0; continue; } if (weight > 1) { weight = 1; } var D2 = D * D; var gs = 2 * weight * (l - D) / (D2 * l); var l3 = l * l * l; var hs = 2 * -weight / (D2 * l3); if (!isFinite(gs)) console.log(gs); for (i = 0; i < this.k; ++i) { this.g[i][u] += d[i] * gs; Huu[i] -= this.H[i][u][v] = hs * (l3 + D * (d2[i] - sd2) + l * sd2); } } for (i = 0; i < this.k; ++i) maxH = Math.max(maxH, this.H[i][u][u] = Huu[i]); } // Grid snap forces var r = this.snapGridSize / 2; var g = this.snapGridSize; var w = this.snapStrength; var k = w / (r * r); var numNodes = this.numGridSnapNodes; //var numNodes = n; for (var u = 0; u < numNodes; ++u) { for (i = 0; i < this.k; ++i) { var xiu = this.x[i][u]; var m = xiu / g; var f = m % 1; var q = m - f; var a = Math.abs(f); var dx = (a <= 0.5) ? xiu - q * g : (xiu > 0) ? xiu - (q + 1) * g : xiu - (q - 1) * g; if (-r < dx && dx <= r) { if (this.scaleSnapByMaxH) { this.g[i][u] += maxH * k * dx; this.H[i][u][u] += maxH * k; } else { this.g[i][u] += k * dx; this.H[i][u][u] += k; } } } } if (!this.locks.isEmpty()) { this.locks.apply(function (u, p) { for (i = 0; i < _this.k; ++i) { _this.H[i][u][u] += maxH; _this.g[i][u] -= maxH * (p[i] - x[i][u]); } }); } /* DEBUG for (var u: number = 0; u < n; ++u) for (i = 0; i < this.k; ++i) { if (isNaN(this.g[i][u])) debugger; for (var v: number = 0; v < n; ++v) if (isNaN(this.H[i][u][v])) debugger; } DEBUG */ }; Descent.dotProd = function (a, b) { var x = 0, i = a.length; while (i--) x += a[i] * b[i]; return x; }; // result r = matrix m * vector v Descent.rightMultiply = function (m, v, r) { var i = m.length; while (i--) r[i] = Descent.dotProd(m[i], v); }; // computes the optimal step size to take in direction d using the // derivative information in this.g and this.H // returns the scalar multiplier to apply to d to get the optimal step Descent.prototype.computeStepSize = function (d) { var numerator = 0, denominator = 0; for (var i = 0; i < this.k; ++i) { numerator += Descent.dotProd(this.g[i], d[i]); Descent.rightMultiply(this.H[i], d[i], this.Hd[i]); denominator += Descent.dotProd(d[i], this.Hd[i]); } if (denominator === 0 || !isFinite(denominator)) return 0; return 1 * numerator / denominator; }; Descent.prototype.reduceStress = function () { this.computeDerivatives(this.x); var alpha = this.computeStepSize(this.g); for (var i = 0; i < this.k; ++i) { this.takeDescentStep(this.x[i], this.g[i], alpha); } return this.computeStress(); }; Descent.copy = function (a, b) { var m = a.length, n = b[0].length; for (var i = 0; i < m; ++i) { for (var j = 0; j < n; ++j) { b[i][j] = a[i][j]; } } }; // takes a step of stepSize * d from x0, and then project against any constraints. // result is returned in r. // x0: starting positions // r: result positions will be returned here // d: unconstrained descent vector // stepSize: amount to step along d Descent.prototype.stepAndProject = function (x0, r, d, stepSize) { Descent.copy(x0, r); this.takeDescentStep(r[0], d[0], stepSize); if (this.project) this.project[0](x0[0], x0[1], r[0]); this.takeDescentStep(r[1], d[1], stepSize); if (this.project) this.project[1](r[0], x0[1], r[1]); // todo: allow projection against constraints in higher dimensions for (var i = 2; i < this.k; i++) this.takeDescentStep(r[i], d[i], stepSize); // the following makes locks extra sticky... but hides the result of the projection from the consumer //if (!this.locks.isEmpty()) { // this.locks.apply((u, p) => { // for (var i = 0; i < this.k; i++) { // r[i][u] = p[i]; // } // }); //} }; Descent.mApply = function (m, n, f) { var i = m; while (i-- > 0) { var j = n; while (j-- > 0) f(i, j); } }; Descent.prototype.matrixApply = function (f) { Descent.mApply(this.k, this.n, f); }; Descent.prototype.computeNextPosition = function (x0, r) { var _this = this; this.computeDerivatives(x0); var alpha = this.computeStepSize(this.g); this.stepAndProject(x0, r, this.g, alpha); /* DEBUG for (var u: number = 0; u < this.n; ++u) for (var i = 0; i < this.k; ++i) if (isNaN(r[i][u])) debugger; DEBUG */ if (this.project) { this.matrixApply(function (i, j) { return _this.e[i][j] = x0[i][j] - r[i][j]; }); var beta = this.computeStepSize(this.e); beta = Math.max(0.2, Math.min(beta, 1)); this.stepAndProject(x0, r, this.e, beta); } }; Descent.prototype.run = function (iterations) { var stress = Number.MAX_VALUE, converged = false; while (!converged && iterations-- > 0) { var s = this.rungeKutta(); converged = Math.abs(stress / s - 1) < this.threshold; stress = s; } return stress; }; Descent.prototype.rungeKutta = function () { var _this = this; this.computeNextPosition(this.x, this.a); Descent.mid(this.x, this.a, this.ia); this.computeNextPosition(this.ia, this.b); Descent.mid(this.x, this.b, this.ib); this.computeNextPosition(this.ib, this.c); this.computeNextPosition(this.c, this.d); var disp = 0; this.matrixApply(function (i, j) { var x = (_this.a[i][j] + 2.0 * _this.b[i][j] + 2.0 * _this.c[i][j] + _this.d[i][j]) / 6.0, d = _this.x[i][j] - x; disp += d * d; _this.x[i][j] = x; }); return disp; }; Descent.mid = function (a, b, m) { Descent.mApply(a.length, a[0].length, function (i, j) { return m[i][j] = a[i][j] + (b[i][j] - a[i][j]) / 2.0; }); }; Descent.prototype.takeDescentStep = function (x, d, stepSize) { for (var i = 0; i < this.n; ++i) { x[i] = x[i] - stepSize * d[i]; } }; Descent.prototype.computeStress = function () { var stress = 0; for (var u = 0, nMinus1 = this.n - 1; u < nMinus1; ++u) { for (var v = u + 1, n = this.n; v < n; ++v) { var l = 0; for (var i = 0; i < this.k; ++i) { var dx = this.x[i][u] - this.x[i][v]; l += dx * dx; } l = Math.sqrt(l); var d = this.D[u][v]; if (!isFinite(d)) continue; var rl = d - l; var d2 = d * d; stress += rl * rl / d2; } } return stress; }; Descent.zeroDistance = 1e-10; return Descent; })(); cola.Descent = Descent; // Linear congruential pseudo random number generator var PseudoRandom = (function () { function PseudoRandom(seed) { if (seed === void 0) { seed = 1; } this.seed = seed; this.a = 214013; this.c = 2531011; this.m = 2147483648; this.range = 32767; } // random real between 0 and 1 PseudoRandom.prototype.getNext = function () { this.seed = (this.seed * this.a + this.c) % this.m; return (this.seed >> 16) / this.range; }; // random real between min and max PseudoRandom.prototype.getNextBetween = function (min, max) { return min + this.getNext() * (max - min); }; return PseudoRandom; })(); cola.PseudoRandom = PseudoRandom; })(cola || (cola = {})); var cola; (function (cola) { var powergraph; (function (powergraph) { var PowerEdge = (function () { function PowerEdge(source, target, type) { this.source = source; this.target = target; this.type = type; } return PowerEdge; })(); powergraph.PowerEdge = PowerEdge; var Configuration = (function () { function Configuration(n, edges, linkAccessor, rootGroup) { var _this = this; this.linkAccessor = linkAccessor; this.modules = new Array(n); this.roots = []; if (rootGroup) { this.initModulesFromGroup(rootGroup); } else { this.roots.push(new ModuleSet()); for (var i = 0; i < n; ++i) this.roots[0].add(this.modules[i] = new Module(i)); } this.R = edges.length; edges.forEach(function (e) { var s = _this.modules[linkAccessor.getSourceIndex(e)], t = _this.modules[linkAccessor.getTargetIndex(e)], type = linkAccessor.getType(e); s.outgoing.add(type, t); t.incoming.add(type, s); }); } Configuration.prototype.initModulesFromGroup = function (group) { var moduleSet = new ModuleSet(); this.roots.push(moduleSet); for (var i = 0; i < group.leaves.length; ++i) { var node = group.leaves[i]; var module = new Module(node.id); this.modules[node.id] = module; moduleSet.add(module); } if (group.groups) { for (var j = 0; j < group.groups.length; ++j) { var child = group.groups[j]; // Propagate group properties (like padding, stiffness, ...) as module definition so that the generated power graph group will inherit it var definition = {}; for (var prop in child) if (prop !== "leaves" && prop !== "groups" && child.hasOwnProperty(prop)) definition[prop] = child[prop]; // Use negative module id to avoid clashes between predefined and generated modules moduleSet.add(new Module(-1 - j, new LinkSets(), new LinkSets(), this.initModulesFromGroup(child), definition)); } } return moduleSet; }; // merge modules a and b keeping track of their power edges and removing the from roots Configuration.prototype.merge = function (a, b, k) { if (k === void 0) { k = 0; } var inInt = a.incoming.intersection(b.incoming), outInt = a.outgoing.intersection(b.outgoing); var children = new ModuleSet(); children.add(a); children.add(b); var m = new Module(this.modules.length, outInt, inInt, children); this.modules.push(m); var update = function (s, i, o) { s.forAll(function (ms, linktype) { ms.forAll(function (n) { var nls = n[i]; nls.add(linktype, m); nls.remove(linktype, a); nls.remove(linktype, b); a[o].remove(linktype, n); b[o].remove(linktype, n); }); }); }; update(outInt, "incoming", "outgoing"); update(inInt, "outgoing", "incoming"); this.R -= inInt.count() + outInt.count(); this.roots[k].remove(a); this.roots[k].remove(b); this.roots[k].add(m); return m; }; Configuration.prototype.rootMerges = function (k) { if (k === void 0) { k = 0; } var rs = this.roots[k].modules(); var n = rs.length; var merges = new Array(n * (n - 1)); var ctr = 0; for (var i = 0, i_ = n - 1; i < i_; ++i) { for (var j = i + 1; j < n; ++j) { var a = rs[i], b = rs[j]; merges[ctr] = { id: ctr, nEdges: this.nEdges(a, b), a: a, b: b }; ctr++; } } return merges; }; Configuration.prototype.greedyMerge = function () { for (var i = 0; i < this.roots.length; ++i) { // Handle single nested module case if (this.roots[i].modules().length < 2) continue; // find the merge that allows for the most edges to be removed. secondary ordering based on arbitrary id (for predictability) var ms = this.rootMerges(i).sort(function (a, b) { return a.nEdges == b.nEdges ? a.id - b.id : a.nEdges - b.nEdges; }); var m = ms[0]; if (m.nEdges >= this.R) continue; this.merge(m.a, m.b, i); return true; } }; Configuration.prototype.nEdges = function (a, b) { var inInt = a.incoming.intersection(b.incoming), outInt = a.outgoing.intersection(b.outgoing); return this.R - inInt.count() - outInt.count(); }; Configuration.prototype.getGroupHierarchy = function (retargetedEdges) { var _this = this; var groups = []; var root = {}; toGroups(this.roots[0], root, groups); var es = this.allEdges(); es.forEach(function (e) { var a = _this.modules[e.source]; var b = _this.modules[e.target]; retargetedEdges.push(new PowerEdge(typeof a.gid === "undefined" ? e.source : groups[a.gid], typeof b.gid === "undefined" ? e.target : groups[b.gid], e.type)); }); return groups; }; Configuration.prototype.allEdges = function () { var es = []; Configuration.getEdges(this.roots[0], es); return es; }; Configuration.getEdges = function (modules, es) { modules.forAll(function (m) { m.getEdges(es); Configuration.getEdges(m.children, es); }); }; return Configuration; })(); powergraph.Configuration = Configuration; function toGroups(modules, group, groups) { modules.forAll(function (m) { if (m.isLeaf()) { if (!group.leaves) group.leaves = []; group.leaves.push(m.id); } else { var g = group; m.gid = groups.length; if (!m.isIsland() || m.isPredefined()) { g = { id: m.gid }; if (m.isPredefined()) // Apply original group properties for (var prop in m.definition) g[prop] = m.definition[prop]; if (!group.groups) group.groups = []; group.groups.push(m.gid); groups.push(g); } toGroups(m.children, g, groups); } }); } var Module = (function () { function Module(id, outgoing, incoming, children, definition) { if (outgoing === void 0) { outgoing = new LinkSets(); } if (incoming === void 0) { incoming = new LinkSets(); } if (children === void 0) { children = new ModuleSet(); } this.id = id; this.outgoing = outgoing; this.incoming = incoming; this.children = children; this.definition = definition; } Module.prototype.getEdges = function (es) { var _this = this; this.outgoing.forAll(function (ms, edgetype) { ms.forAll(function (target) { es.push(new PowerEdge(_this.id, target.id, edgetype)); }); }); }; Module.prototype.isLeaf = function () { return this.children.count() === 0; }; Module.prototype.isIsland = function () { return this.outgoing.count() === 0 && this.incoming.count() === 0; }; Module.prototype.isPredefined = function () { return typeof this.definition !== "undefined"; }; return Module; })(); powergraph.Module = Module; function intersection(m, n) { var i = {}; for (var v in m) if (v in n) i[v] = m[v]; return i; } var ModuleSet = (function () { function ModuleSet() { this.table = {}; } ModuleSet.prototype.count = function () { return Object.keys(this.table).length; }; ModuleSet.prototype.intersection = function (other) { var result = new ModuleSet(); result.table = intersection(this.table, other.table); return result; }; ModuleSet.prototype.intersectionCount = function (other) { return this.intersection(other).count(); }; ModuleSet.prototype.contains = function (id) { return id in this.table; }; ModuleSet.prototype.add = function (m) { this.table[m.id] = m; }; ModuleSet.prototype.remove = function (m) { delete this.table[m.id]; }; ModuleSet.prototype.forAll = function (f) { for (var mid in this.table) { f(this.table[mid]); } }; ModuleSet.prototype.modules = function () { var vs = []; this.forAll(function (m) { if (!m.isPredefined()) vs.push(m); }); return vs; }; return ModuleSet; })(); powergraph.ModuleSet = ModuleSet; var LinkSets = (function () { function LinkSets() { this.sets = {}; this.n = 0; } LinkSets.prototype.count = function () { return this.n; }; LinkSets.prototype.contains = function (id) { var result = false; this.forAllModules(function (m) { if (!result && m.id == id) { result = true; } }); return result; }; LinkSets.prototype.add = function (linktype, m) { var s = linktype in this.sets ? this.sets[linktype] : this.sets[linktype] = new ModuleSet(); s.add(m); ++this.n; }; LinkSets.prototype.remove = function (linktype, m) { var ms = this.sets[linktype]; ms.remove(m); if (ms.count() === 0) { delete this.sets[linktype]; } --this.n; }; LinkSets.prototype.forAll = function (f) { for (var linktype in this.sets) { f(this.sets[linktype], linktype); } }; LinkSets.prototype.forAllModules = function (f) { this.forAll(function (ms, lt) { return ms.forAll(f); }); }; LinkSets.prototype.intersection = function (other) { var result = new LinkSets(); this.forAll(function (ms, lt) { if (lt in other.sets) { var i = ms.intersection(other.sets[lt]), n = i.count(); if (n > 0) { result.sets[lt] = i; result.n += n; } } }); return result; }; return LinkSets; })(); powergraph.LinkSets = LinkSets; function intersectionCount(m, n) { return Object.keys(intersection(m, n)).length; } function getGroups(nodes, links, la, rootGroup) { var n = nodes.length, c = new powergraph.Configuration(n, links, la, rootGroup); while (c.greedyMerge()) ; var powerEdges = []; var g = c.getGroupHierarchy(powerEdges); powerEdges.forEach(function (e) { var f = function (end) { var g = e[end]; if (typeof g == "number") e[end] = nodes[g]; }; f("source"); f("target"); }); return { groups: g, powerEdges: powerEdges }; } powergraph.getGroups = getGroups; })(powergraph = cola.powergraph || (cola.powergraph = {})); })(cola || (cola = {})); /** * @module cola */ var cola; (function (cola) { // compute the size of the union of two sets a and b function unionCount(a, b) { var u = {}; for (var i in a) u[i] = {}; for (var i in b) u[i] = {}; return Object.keys(u).length; } // compute the size of the intersection of two sets a and b function intersectionCount(a, b) { var n = 0; for (var i in a) if (typeof b[i] !== 'undefined') ++n; return n; } function getNeighbours(links, la) { var neighbours = {}; var addNeighbours = function (u, v) { if (typeof neighbours[u] === 'undefined') neighbours[u] = {}; neighbours[u][v] = {}; }; links.forEach(function (e) { var u = la.getSourceIndex(e), v = la.getTargetIndex(e); addNeighbours(u, v); addNeighbours(v, u); }); return neighbours; } // modify the lengths of the specified links by the result of function f weighted by w function computeLinkLengths(links, w, f, la) { var neighbours = getNeighbours(links, la); links.forEach(function (l) { var a = neighbours[la.getSourceIndex(l)]; var b = neighbours[la.getTargetIndex(l)]; la.setLength(l, 1 + w * f(a, b)); }); } /** modify the specified link lengths based on the symmetric difference of their neighbours * @class symmetricDiffLinkLengths */ function symmetricDiffLinkLengths(links, la, w) { if (w === void 0) { w = 1; } computeLinkLengths(links, w, function (a, b) { return Math.sqrt(unionCount(a, b) - intersectionCount(a, b)); }, la); } cola.symmetricDiffLinkLengths = symmetricDiffLinkLengths; /** modify the specified links lengths based on the jaccard difference between their neighbours * @class jaccardLinkLengths */ function jaccardLinkLengths(links, la, w) { if (w === void 0) { w = 1; } computeLinkLengths(links, w, function (a, b) { return Math.min(Object.keys(a).length, Object.keys(b).length) < 1.1 ? 0 : intersectionCount(a, b) / unionCount(a, b); }, la); } cola.jaccardLinkLengths = jaccardLinkLengths; /** generate separation constraints for all edges unless both their source and sink are in the same strongly connected component * @class generateDirectedEdgeConstraints */ function generateDirectedEdgeConstraints(n, links, axis, la) { var components = stronglyConnectedComponents(n, links, la); var nodes = {}; components.forEach(function (c, i) { return c.forEach(function (v) { return nodes[v] = i; }); }); var constraints = []; links.forEach(function (l) { var ui = la.getSourceIndex(l), vi = la.getTargetIndex(l), u = nodes[ui], v = nodes[vi]; if (u !== v) { constraints.push({ axis: axis, left: ui, right: vi, gap: la.getMinSeparation(l) }); } }); return constraints; } cola.generateDirectedEdgeConstraints = generateDirectedEdgeConstraints; /** * Tarjan's strongly connected components algorithm for directed graphs * returns an array of arrays of node indicies in each of the strongly connected components. * a vertex not in a SCC of two or more nodes is it's own SCC. * adaptation of https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm */ function stronglyConnectedComponents(numVertices, edges, la) { var nodes = []; var index = 0; var stack = []; var components = []; function strongConnect(v) { // Set the depth index for v to the smallest unused index v.index = v.lowlink = index++; stack.push(v); v.onStack = true; // Consider successors of v for (var _i = 0, _a = v.out; _i < _a.length; _i++) { var w = _a[_i]; if (typeof w.index === 'undefined') { // Successor w has not yet been visited; recurse on it strongConnect(w); v.lowlink = Math.min(v.lowlink, w.lowlink); } else if (w.onStack) { // Successor w is in stack S and hence in the current SCC v.lowlink = Math.min(v.lowlink, w.index); } } // If v is a root node, pop the stack and generate an SCC if (v.lowlink === v.index) { // start a new strongly connected component var component = []; while (stack.length) { w = stack.pop(); w.onStack = false; //add w to current strongly connected component component.push(w); if (w === v) break; } // output the current strongly connected component components.push(component.map(function (v) { return v.id; })); } } for (var i = 0; i < numVertices; i++) { nodes.push({ id: i, out: [] }); } for (var _i = 0; _i < edges.length; _i++) { var e = edges[_i]; var v_1 = nodes[la.getSourceIndex(e)], w = nodes[la.getTargetIndex(e)]; v_1.out.push(w); } for (var _a = 0; _a < nodes.length; _a++) { var v = nodes[_a]; if (typeof v.index === 'undefined') strongConnect(v); } return components; } cola.stronglyConnectedComponents = stronglyConnectedComponents; })(cola || (cola = {})); var PairingHeap = (function () { // from: https://gist.github.com/nervoussystem //{elem:object, subheaps:[array of heaps]} function PairingHeap(elem) { this.elem = elem; this.subheaps = []; } PairingHeap.prototype.toString = function (selector) { var str = "", needComma = false; for (var i = 0; i < this.subheaps.length; ++i) { var subheap = this.subheaps[i]; if (!subheap.elem) { needComma = false; continue; } if (needComma) { str = str + ","; } str = str + subheap.toString(selector); needComma = true; } if (str !== "") { str = "(" + str + ")"; } return (this.elem ? selector(this.elem) : "") + str; }; PairingHeap.prototype.forEach = function (f) { if (!this.empty()) { f(this.elem, this); this.subheaps.forEach(function (s) { return s.forEach(f); }); } }; PairingHeap.prototype.count = function () { return this.empty() ? 0 : 1 + this.subheaps.reduce(function (n, h) { return n + h.count(); }, 0); }; PairingHeap.prototype.min = function () { return this.elem; }; PairingHeap.prototype.empty = function () { return this.elem == null; }; PairingHeap.prototype.contains = function (h) { if (this === h) return true; for (var i = 0; i < this.subheaps.length; i++) { if (this.subheaps[i].contains(h)) return true; } return false; }; PairingHeap.prototype.isHeap = function (lessThan) { var _this = this; return this.subheaps.every(function (h) { return lessThan(_this.elem, h.elem) && h.isHeap(lessThan); }); }; PairingHeap.prototype.insert = function (obj, lessThan) { return this.merge(new PairingHeap(obj), lessThan); }; PairingHeap.prototype.merge = function (heap2, lessThan) { if (this.empty()) return heap2; else if (heap2.empty()) return this; else if (lessThan(this.elem, heap2.elem)) { this.subheaps.push(heap2); return this; } else { heap2.subheaps.push(this); return heap2; } }; PairingHeap.prototype.removeMin = function (lessThan) { if (this.empty()) return null; else return this.mergePairs(lessThan); }; PairingHeap.prototype.mergePairs = function (lessThan) { if (this.subheaps.length == 0) return new PairingHeap(null); else if (this.subheaps.length == 1) { return this.subheaps[0]; } else { var firstPair = this.subheaps.pop().merge(this.subheaps.pop(), lessThan); var remaining = this.mergePairs(lessThan); return firstPair.merge(remaining, lessThan); } }; PairingHeap.prototype.decreaseKey = function (subheap, newValue, setHeapNode, lessThan) { var newHeap = subheap.removeMin(lessThan); //reassign subheap values to preserve tree subheap.elem = newHeap.elem; subheap.subheaps = newHeap.subheaps; if (setHeapNode !== null && newHeap.elem !== null) { setHeapNode(subheap.elem, subheap); } var pairingNode = new PairingHeap(newValue); if (setHeapNode !== null) { setHeapNode(newValue, pairingNode); } return this.merge(pairingNode, lessThan); }; return PairingHeap; })(); /** * @class PriorityQueue a min priority queue backed by a pairing heap */ var PriorityQueue = (function () { function PriorityQueue(lessThan) { this.lessThan = lessThan; } /** * @method top * @return the top element (the min element as defined by lessThan) */ PriorityQueue.prototype.top = function () { if (this.empty()) { return null; } return this.root.elem; }; /** * @method push * put things on the heap */ PriorityQueue.prototype.push = function () { var args = []; for (var _i = 0; _i < arguments.length; _i++) { args[_i - 0] = arguments[_i]; } var pairingNode; for (var i = 0, arg; arg = args[i]; ++i) { pairingNode = new PairingHeap(arg); this.root = this.empty() ? pairingNode : this.root.merge(pairingNode, this.lessThan); } return pairingNode; }; /** * @method empty * @return true if no more elements in queue */ PriorityQueue.prototype.empty = function () { return !this.root || !this.root.elem; }; /** * @method isHeap check heap condition (for testing) * @return true if queue is in valid state */ PriorityQueue.prototype.isHeap = function () { return this.root.isHeap(this.lessThan); }; /** * @method forEach apply f to each element of the queue * @param f function to apply */ PriorityQueue.prototype.forEach = function (f) { this.root.forEach(f); }; /** * @method pop remove and return the min element from the queue */ PriorityQueue.prototype.pop = function () { if (this.empty()) { return null; } var obj = this.root.min(); this.root = this.root.removeMin(this.lessThan); return obj; }; /** * @method reduceKey reduce the key value of the specified heap node */ PriorityQueue.prototype.reduceKey = function (heapNode, newKey, setHeapNode) { if (setHeapNode === void 0) { setHeapNode = null; } this.root = this.root.decreaseKey(heapNode, newKey, setHeapNode, this.lessThan); }; PriorityQueue.prototype.toString = function (selector) { return this.root.toString(selector); }; /** * @method count * @return number of elements in queue */ PriorityQueue.prototype.count = function () { return this.root.count(); }; return PriorityQueue; })(); /// /** * @module shortestpaths */ var cola; (function (cola) { var shortestpaths; (function (shortestpaths) { var Neighbour = (function () { function Neighbour(id, distance) { this.id = id; this.distance = distance; } return Neighbour; })(); var Node = (function () { function Node(id) { this.id = id; this.neighbours = []; } return Node; })(); var QueueEntry = (function () { function QueueEntry(node, prev, d) { this.node = node; this.prev = prev; this.d = d; } return QueueEntry; })(); /** * calculates all-pairs shortest paths or shortest paths from a single node * @class Calculator * @constructor * @param n {number} number of nodes * @param es {Edge[]} array of edges */ var Calculator = (function () { function Calculator(n, es, getSourceIndex, getTargetIndex, getLength) { this.n = n; this.es = es; this.neighbours = new Array(this.n); var i = this.n; while (i--) this.neighbours[i] = new Node(i); i = this.es.length; while (i--) { var e = this.es[i]; var u = getSourceIndex(e), v = getTargetIndex(e); var d = getLength(e); this.neighbours[u].neighbours.push(new Neighbour(v, d)); this.neighbours[v].neighbours.push(new Neighbour(u, d)); } } /** * compute shortest paths for graph over n nodes with edges an array of source/target pairs * edges may optionally have a length attribute. 1 is the default. * Uses Johnson's algorithm. * * @method DistanceMatrix * @return the distance matrix */ Calculator.prototype.DistanceMatrix = function () { var D = new Array(this.n); for (var i = 0; i < this.n; ++i) { D[i] = this.dijkstraNeighbours(i); } return D; }; /** * get shortest paths from a specified start node * @method DistancesFromNode * @param start node index * @return array of path lengths */ Calculator.prototype.DistancesFromNode = function (start) { return this.dijkstraNeighbours(start); }; Calculator.prototype.PathFromNodeToNode = function (start, end) { return this.dijkstraNeighbours(start, end); }; // find shortest path from start to end, with the opportunity at // each edge traversal to compute a custom cost based on the // previous edge. For example, to penalise bends. Calculator.prototype.PathFromNodeToNodeWithPrevCost = function (start, end, prevCost) { var q = new PriorityQueue(function (a, b) { return a.d <= b.d; }), u = this.neighbours[start], qu = new QueueEntry(u, null, 0), visitedFrom = {}; q.push(qu); while (!q.empty()) { qu = q.pop(); u = qu.node; if (u.id === end) { break; } var i = u.neighbours.length; while (i--) { var neighbour = u.neighbours[i], v = this.neighbours[neighbour.id]; // don't double back if (qu.prev && v.id === qu.prev.node.id) continue; // don't retraverse an edge if it has already been explored // from a lower cost route var viduid = v.id + ',' + u.id; if (viduid in visitedFrom && visitedFrom[viduid] <= qu.d) continue; var cc = qu.prev ? prevCost(qu.prev.node.id, u.id, v.id) : 0, t = qu.d + neighbour.distance + cc; // store cost of this traversal visitedFrom[viduid] = t; q.push(new QueueEntry(v, qu, t)); } } var path = []; while (qu.prev) { qu = qu.prev; path.push(qu.node.id); } return path; }; Calculator.prototype.dijkstraNeighbours = function (start, dest) { if (dest === void 0) { dest = -1; } var q = new PriorityQueue(function (a, b) { return a.d <= b.d; }), i = this.neighbours.length, d = new Array(i); while (i--) { var node = this.neighbours[i]; node.d = i === start ? 0 : Number.POSITIVE_INFINITY; node.q = q.push(node); } while (!q.empty()) { // console.log(q.toString(function (u) { return u.id + "=" + (u.d === Number.POSITIVE_INFINITY ? "\u221E" : u.d.toFixed(2) )})); var u = q.pop(); d[u.id] = u.d; if (u.id === dest) { var path = []; var v = u; while (typeof v.prev !== 'undefined') { path.push(v.prev.id); v = v.prev; } return path; } i = u.neighbours.length; while (i--) { var neighbour = u.neighbours[i]; var v = this.neighbours[neighbour.id]; var t = u.d + neighbour.distance; if (u.d !== Number.MAX_VALUE && v.d > t) { v.d = t; v.prev = u; q.reduceKey(v.q, v, function (e, q) { return e.q = q; }); } } } return d; }; return Calculator; })(); shortestpaths.Calculator = Calculator; })(shortestpaths = cola.shortestpaths || (cola.shortestpaths = {})); })(cola || (cola = {})); /// /// /// /// /// /// /** * @module cola */ var cola; (function (cola) { /** * The layout process fires three events: * - start: layout iterations started * - tick: fired once per iteration, listen to this to animate * - end: layout converged, you might like to zoom-to-fit or something at notification of this event */ (function (EventType) { EventType[EventType["start"] = 0] = "start"; EventType[EventType["tick"] = 1] = "tick"; EventType[EventType["end"] = 2] = "end"; })(cola.EventType || (cola.EventType = {})); var EventType = cola.EventType; ; /** * Main interface to cola layout. * @class Layout */ var Layout = (function () { function Layout() { var _this = this; this._canvasSize = [1, 1]; this._linkDistance = 20; this._defaultNodeSize = 10; this._linkLengthCalculator = null; this._linkType = null; this._avoidOverlaps = false; this._handleDisconnected = true; this._running = false; this._nodes = []; this._groups = []; this._rootGroup = null; this._links = []; this._constraints = []; this._distanceMatrix = null; this._descent = null; this._directedLinkConstraints = null; this._threshold = 0.01; this._visibilityGraph = null; this._groupCompactness = 1e-6; // sub-class and override this property to replace with a more sophisticated eventing mechanism this.event = null; this.linkAccessor = { getSourceIndex: Layout.getSourceIndex, getTargetIndex: Layout.getTargetIndex, setLength: Layout.setLinkLength, getType: function (l) { return typeof _this._linkType === "function" ? _this._linkType(l) : 0; } }; } // subscribe a listener to an event // sub-class and override this method to replace with a more sophisticated eventing mechanism Layout.prototype.on = function (e, listener) { // override me! if (!this.event) this.event = {}; if (typeof e === 'string') { this.event[EventType[e]] = listener; } else { this.event[e] = listener; } return this; }; // a function that is notified of events like "tick" // sub-class and override this method to replace with a more sophisticated eventing mechanism Layout.prototype.trigger = function (e) { if (this.event && typeof this.event[e.type] !== 'undefined') { this.event[e.type](e); } }; // a function that kicks off the iteration tick loop // it calls tick() repeatedly until tick returns true (is converged) // subclass and override it with something fancier (e.g. dispatch tick on a timer) Layout.prototype.kick = function () { while (!this.tick()) ; }; /** * iterate the layout. Returns true when layout converged. */ Layout.prototype.tick = function () { if (this._alpha < this._threshold) { this._running = false; this.trigger({ type: EventType.end, alpha: this._alpha = 0, stress: this._lastStress }); return true; } var n = this._nodes.length, m = this._links.length; var o, i; this._descent.locks.clear(); for (i = 0; i < n; ++i) { o = this._nodes[i]; if (o.fixed) { if (typeof o.px === 'undefined' || typeof o.py === 'undefined') { o.px = o.x; o.py = o.y; } var p = [o.px, o.py]; this._descent.locks.add(i, p); } } var s1 = this._descent.rungeKutta(); //var s1 = descent.reduceStress(); if (s1 === 0) { this._alpha = 0; } else if (typeof this._lastStress !== 'undefined') { this._alpha = s1; //Math.abs(Math.abs(this._lastStress / s1) - 1); } this._lastStress = s1; this.updateNodePositions(); this.trigger({ type: EventType.tick, alpha: this._alpha, stress: this._lastStress }); return false; }; // copy positions out of descent instance into each of the nodes' center coords Layout.prototype.updateNodePositions = function () { var x = this._descent.x[0], y = this._descent.x[1]; var o, i = this._nodes.length; while (i--) { o = this._nodes[i]; o.x = x[i]; o.y = y[i]; } }; Layout.prototype.nodes = function (v) { if (!v) { if (this._nodes.length === 0 && this._links.length > 0) { // if we have links but no nodes, create the nodes array now with empty objects for the links to point at. // in this case the links are expected to be numeric indices for nodes in the range 0..n-1 where n is the number of nodes var n = 0; this._links.forEach(function (l) { n = Math.max(n, l.source, l.target); }); this._nodes = new Array(++n); for (var i = 0; i < n; ++i) { this._nodes[i] = {}; } } return this._nodes; } this._nodes = v; return this; }; Layout.prototype.groups = function (x) { var _this = this; if (!x) return this._groups; this._groups = x; this._rootGroup = {}; this._groups.forEach(function (g) { if (typeof g.padding === "undefined") g.padding = 1; if (typeof g.leaves !== "undefined") g.leaves.forEach(function (v, i) { (g.leaves[i] = _this._nodes[v]).parent = g; }); if (typeof g.groups !== "undefined") g.groups.forEach(function (gi, i) { (g.groups[i] = _this._groups[gi]).parent = g; }); }); this._rootGroup.leaves = this._nodes.filter(function (v) { return typeof v.parent === 'undefined'; }); this._rootGroup.groups = this._groups.filter(function (g) { return typeof g.parent === 'undefined'; }); return this; }; Layout.prototype.powerGraphGroups = function (f) { var g = cola.powergraph.getGroups(this._nodes, this._links, this.linkAccessor, this._rootGroup); this.groups(g.groups); f(g); return this; }; Layout.prototype.avoidOverlaps = function (v) { if (!arguments.length) return this._avoidOverlaps; this._avoidOverlaps = v; return this; }; Layout.prototype.handleDisconnected = function (v) { if (!arguments.length) return this._handleDisconnected; this._handleDisconnected = v; return this; }; /** * causes constraints to be generated such that directed graphs are laid out either from left-to-right or top-to-bottom. * a separation constraint is generated in the selected axis for each edge that is not involved in a cycle (part of a strongly connected component) * @param axis {string} 'x' for left-to-right, 'y' for top-to-bottom * @param minSeparation {number|link=>number} either a number specifying a minimum spacing required across all links or a function to return the minimum spacing for each link */ Layout.prototype.flowLayout = function (axis, minSeparation) { if (!arguments.length) axis = 'y'; this._directedLinkConstraints = { axis: axis, getMinSeparation: typeof minSeparation === 'number' ? function () { return minSeparation; } : minSeparation }; return this; }; Layout.prototype.links = function (x) { if (!arguments.length) return this._links; this._links = x; return this; }; Layout.prototype.constraints = function (c) { if (!arguments.length) return this._constraints; this._constraints = c; return this; }; Layout.prototype.distanceMatrix = function (d) { if (!arguments.length) return this._distanceMatrix; this._distanceMatrix = d; return this; }; Layout.prototype.size = function (x) { if (!x) return this._canvasSize; this._canvasSize = x; return this; }; Layout.prototype.defaultNodeSize = function (x) { if (!x) return this._defaultNodeSize; this._defaultNodeSize = x; return this; }; Layout.prototype.groupCompactness = function (x) { if (!x) return this._groupCompactness; this._groupCompactness = x; return this; }; Layout.prototype.linkDistance = function (x) { if (!x) { return this._linkDistance; } this._linkDistance = typeof x === "function" ? x : +x; this._linkLengthCalculator = null; return this; }; Layout.prototype.linkType = function (f) { this._linkType = f; return this; }; Layout.prototype.convergenceThreshold = function (x) { if (!x) return this._threshold; this._threshold = typeof x === "function" ? x : +x; return this; }; Layout.prototype.alpha = function (x) { if (!arguments.length) return this._alpha; else { x = +x; if (this._alpha) { if (x > 0) this._alpha = x; // we might keep it hot else this._alpha = 0; // or, next tick will dispatch "end" } else if (x > 0) { if (!this._running) { this._running = true; this.trigger({ type: EventType.start, alpha: this._alpha = x }); this.kick(); } } return this; } }; Layout.prototype.getLinkLength = function (link) { return typeof this._linkDistance === "function" ? +(this._linkDistance(link)) : this._linkDistance; }; Layout.setLinkLength = function (link, length) { link.length = length; }; Layout.prototype.getLinkType = function (link) { return typeof this._linkType === "function" ? this._linkType(link) : 0; }; /** * compute an ideal length for each link based on the graph structure around that link. * you can use this (for example) to create extra space around hub-nodes in dense graphs. * In particular this calculation is based on the "symmetric difference" in the neighbour sets of the source and target: * i.e. if neighbours of source is a and neighbours of target are b then calculation is: sqrt(|a union b| - |a intersection b|) * Actual computation based on inspection of link structure occurs in start(), so links themselves * don't have to have been assigned before invoking this function. * @param {number} [idealLength] the base length for an edge when its source and start have no other common neighbours (e.g. 40) * @param {number} [w] a multiplier for the effect of the length adjustment (e.g. 0.7) */ Layout.prototype.symmetricDiffLinkLengths = function (idealLength, w) { var _this = this; if (w === void 0) { w = 1; } this.linkDistance(function (l) { return idealLength * l.length; }); this._linkLengthCalculator = function () { return cola.symmetricDiffLinkLengths(_this._links, _this.linkAccessor, w); }; return this; }; /** * compute an ideal length for each link based on the graph structure around that link. * you can use this (for example) to create extra space around hub-nodes in dense graphs. * In particular this calculation is based on the "symmetric difference" in the neighbour sets of the source and target: * i.e. if neighbours of source is a and neighbours of target are b then calculation is: |a intersection b|/|a union b| * Actual computation based on inspection of link structure occurs in start(), so links themselves * don't have to have been assigned before invoking this function. * @param {number} [idealLength] the base length for an edge when its source and start have no other common neighbours (e.g. 40) * @param {number} [w] a multiplier for the effect of the length adjustment (e.g. 0.7) */ Layout.prototype.jaccardLinkLengths = function (idealLength, w) { var _this = this; if (w === void 0) { w = 1; } this.linkDistance(function (l) { return idealLength * l.length; }); this._linkLengthCalculator = function () { return cola.jaccardLinkLengths(_this._links, _this.linkAccessor, w); }; return this; }; /** * start the layout process * @method start * @param {number} [initialUnconstrainedIterations=0] unconstrained initial layout iterations * @param {number} [initialUserConstraintIterations=0] initial layout iterations with user-specified constraints * @param {number} [initialAllConstraintsIterations=0] initial layout iterations with all constraints including non-overlap * @param {number} [gridSnapIterations=0] iterations of "grid snap", which pulls nodes towards grid cell centers - grid of size node[0].width - only really makes sense if all nodes have the same width and height * @param [keepRunning=true] keep iterating asynchronously via the tick method */ Layout.prototype.start = function (initialUnconstrainedIterations, initialUserConstraintIterations, initialAllConstraintsIterations, gridSnapIterations, keepRunning) { var _this = this; if (initialUnconstrainedIterations === void 0) { initialUnconstrainedIterations = 0; } if (initialUserConstraintIterations === void 0) { initialUserConstraintIterations = 0; } if (initialAllConstraintsIterations === void 0) { initialAllConstraintsIterations = 0; } if (gridSnapIterations === void 0) { gridSnapIterations = 0; } if (keepRunning === void 0) { keepRunning = true; } var i, j, n = this.nodes().length, N = n + 2 * this._groups.length, m = this._links.length, w = this._canvasSize[0], h = this._canvasSize[1]; if (this._linkLengthCalculator) this._linkLengthCalculator(); var x = new Array(N), y = new Array(N); var G = null; var ao = this._avoidOverlaps; this._nodes.forEach(function (v, i) { v.index = i; if (typeof v.x === 'undefined') { v.x = w / 2, v.y = h / 2; } x[i] = v.x, y[i] = v.y; }); //should we do this to clearly label groups? //this._groups.forEach((g, i) => g.groupIndex = i); var distances; if (this._distanceMatrix) { // use the user specified distanceMatrix distances = this._distanceMatrix; } else { // construct an n X n distance matrix based on shortest paths through graph (with respect to edge.length). distances = (new cola.shortestpaths.Calculator(N, this._links, Layout.getSourceIndex, Layout.getTargetIndex, function (l) { return _this.getLinkLength(l); })).DistanceMatrix(); // G is a square matrix with G[i][j] = 1 iff there exists an edge between node i and node j // otherwise 2. ( G = cola.Descent.createSquareMatrix(N, function () { return 2; }); this._links.forEach(function (l) { if (typeof l.source == "number") l.source = _this._nodes[l.source]; if (typeof l.target == "number") l.target = _this._nodes[l.target]; }); this._links.forEach(function (e) { var u = Layout.getSourceIndex(e), v = Layout.getTargetIndex(e); G[u][v] = G[v][u] = e.weight || 1; }); } var D = cola.Descent.createSquareMatrix(N, function (i, j) { return distances[i][j]; }); if (this._rootGroup && typeof this._rootGroup.groups !== 'undefined') { var i = n; var addAttraction = function (i, j, strength, idealDistance) { G[i][j] = G[j][i] = strength; D[i][j] = D[j][i] = idealDistance; }; this._groups.forEach(function (g) { addAttraction(i, i + 1, _this._groupCompactness, 0.1); // todo: add terms here attracting children of the group to the group dummy nodes //if (typeof g.leaves !== 'undefined') // g.leaves.forEach(l => { // addAttraction(l.index, i, 1e-4, 0.1); // addAttraction(l.index, i + 1, 1e-4, 0.1); // }); //if (typeof g.groups !== 'undefined') // g.groups.forEach(g => { // var gid = n + g.groupIndex * 2; // addAttraction(gid, i, 0.1, 0.1); // addAttraction(gid + 1, i, 0.1, 0.1); // addAttraction(gid, i + 1, 0.1, 0.1); // addAttraction(gid + 1, i + 1, 0.1, 0.1); // }); x[i] = 0, y[i++] = 0; x[i] = 0, y[i++] = 0; }); } else this._rootGroup = { leaves: this._nodes, groups: [] }; var curConstraints = this._constraints || []; if (this._directedLinkConstraints) { this.linkAccessor.getMinSeparation = this._directedLinkConstraints.getMinSeparation; curConstraints = curConstraints.concat(cola.generateDirectedEdgeConstraints(n, this._links, this._directedLinkConstraints.axis, (this.linkAccessor))); } this.avoidOverlaps(false); this._descent = new cola.Descent([x, y], D); this._descent.locks.clear(); for (var i = 0; i < n; ++i) { var o = this._nodes[i]; if (o.fixed) { o.px = o.x; o.py = o.y; var p = [o.x, o.y]; this._descent.locks.add(i, p); } } this._descent.threshold = this._threshold; // apply initialIterations without user constraints or nonoverlap constraints this._descent.run(initialUnconstrainedIterations); // apply initialIterations with user constraints but no nonoverlap constraints if (curConstraints.length > 0) this._descent.project = new cola.vpsc.Projection(this._nodes, this._groups, this._rootGroup, curConstraints).projectFunctions(); this._descent.run(initialUserConstraintIterations); this.separateOverlappingComponents(w, h); // subsequent iterations will apply all constraints this.avoidOverlaps(ao); if (ao) { this._nodes.forEach(function (v, i) { v.x = x[i], v.y = y[i]; }); this._descent.project = new cola.vpsc.Projection(this._nodes, this._groups, this._rootGroup, curConstraints, true).projectFunctions(); this._nodes.forEach(function (v, i) { x[i] = v.x, y[i] = v.y; }); } // allow not immediately connected nodes to relax apart (p-stress) this._descent.G = G; this._descent.run(initialAllConstraintsIterations); if (gridSnapIterations) { this._descent.snapStrength = 1000; this._descent.snapGridSize = this._nodes[0].width; this._descent.numGridSnapNodes = n; this._descent.scaleSnapByMaxH = n != N; // if we have groups then need to scale hessian so grid forces still apply var G0 = cola.Descent.createSquareMatrix(N, function (i, j) { if (i >= n || j >= n) return G[i][j]; return 0; }); this._descent.G = G0; this._descent.run(gridSnapIterations); } this.updateNodePositions(); this.separateOverlappingComponents(w, h); return keepRunning ? this.resume() : this; }; // recalculate nodes position for disconnected graphs Layout.prototype.separateOverlappingComponents = function (width, height) { var _this = this; // recalculate nodes position for disconnected graphs if (!this._distanceMatrix && this._handleDisconnected) { var x = this._descent.x[0], y = this._descent.x[1]; this._nodes.forEach(function (v, i) { v.x = x[i], v.y = y[i]; }); var graphs = cola.separateGraphs(this._nodes, this._links); cola.applyPacking(graphs, width, height, this._defaultNodeSize); this._nodes.forEach(function (v, i) { _this._descent.x[0][i] = v.x, _this._descent.x[1][i] = v.y; if (v.bounds) { v.bounds.setXCentre(v.x); v.bounds.setYCentre(v.y); } }); } }; Layout.prototype.resume = function () { return this.alpha(0.1); }; Layout.prototype.stop = function () { return this.alpha(0); }; /// find a visibility graph over the set of nodes. assumes all nodes have a /// bounds property (a rectangle) and that no pair of bounds overlaps. Layout.prototype.prepareEdgeRouting = function (nodeMargin) { if (nodeMargin === void 0) { nodeMargin = 0; } this._visibilityGraph = new cola.geom.TangentVisibilityGraph(this._nodes.map(function (v) { return v.bounds.inflate(-nodeMargin).vertices(); })); }; /// find a route avoiding node bounds for the given edge. /// assumes the visibility graph has been created (by prepareEdgeRouting method) /// and also assumes that nodes have an index property giving their position in the /// node array. This index property is created by the start() method. Layout.prototype.routeEdge = function (edge, draw) { var lineData = []; //if (d.source.id === 10 && d.target.id === 11) { // debugger; //} var vg2 = new cola.geom.TangentVisibilityGraph(this._visibilityGraph.P, { V: this._visibilityGraph.V, E: this._visibilityGraph.E }), port1 = { x: edge.source.x, y: edge.source.y }, port2 = { x: edge.target.x, y: edge.target.y }, start = vg2.addPoint(port1, edge.source.index), end = vg2.addPoint(port2, edge.target.index); vg2.addEdgeIfVisible(port1, port2, edge.source.index, edge.target.index); if (typeof draw !== 'undefined') { draw(vg2); } var sourceInd = function (e) { return e.source.id; }, targetInd = function (e) { return e.target.id; }, length = function (e) { return e.length(); }, spCalc = new cola.shortestpaths.Calculator(vg2.V.length, vg2.E, sourceInd, targetInd, length), shortestPath = spCalc.PathFromNodeToNode(start.id, end.id); if (shortestPath.length === 1 || shortestPath.length === vg2.V.length) { var route = cola.vpsc.makeEdgeBetween(edge.source.innerBounds, edge.target.innerBounds, 5); lineData = [route.sourceIntersection, route.arrowStart]; } else { var n = shortestPath.length - 2, p = vg2.V[shortestPath[n]].p, q = vg2.V[shortestPath[0]].p, lineData = [edge.source.innerBounds.rayIntersection(p.x, p.y)]; for (var i = n; i >= 0; --i) lineData.push(vg2.V[shortestPath[i]].p); lineData.push(cola.vpsc.makeEdgeTo(q, edge.target.innerBounds, 5)); } //lineData.forEach((v, i) => { // if (i > 0) { // var u = lineData[i - 1]; // this._nodes.forEach(function (node) { // if (node.id === getSourceIndex(d) || node.id === getTargetIndex(d)) return; // var ints = node.innerBounds.lineIntersections(u.x, u.y, v.x, v.y); // if (ints.length > 0) { // debugger; // } // }) // } //}) return lineData; }; //The link source and target may be just a node index, or they may be references to nodes themselves. Layout.getSourceIndex = function (e) { return typeof e.source === 'number' ? e.source : e.source.index; }; //The link source and target may be just a node index, or they may be references to nodes themselves. Layout.getTargetIndex = function (e) { return typeof e.target === 'number' ? e.target : e.target.index; }; // Get a string ID for a given link. Layout.linkId = function (e) { return Layout.getSourceIndex(e) + "-" + Layout.getTargetIndex(e); }; // The fixed property has three bits: // Bit 1 can be set externally (e.g., d.fixed = true) and show persist. // Bit 2 stores the dragging state, from mousedown to mouseup. // Bit 3 stores the hover state, from mouseover to mouseout. // Dragend is a special case: it also clears the hover state. Layout.dragStart = function (d) { d.fixed |= 2; // set bit 2 d.px = d.x, d.py = d.y; // set velocity to zero }; Layout.dragEnd = function (d) { d.fixed &= ~6; // unset bits 2 and 3 //d.fixed = 0; }; Layout.mouseOver = function (d) { d.fixed |= 4; // set bit 3 d.px = d.x, d.py = d.y; // set velocity to zero }; Layout.mouseOut = function (d) { d.fixed &= ~4; // unset bit 3 }; return Layout; })(); cola.Layout = Layout; })(cola || (cola = {})); /// var cola; (function (cola) { var LayoutAdaptor = (function (_super) { __extends(LayoutAdaptor, _super); function LayoutAdaptor(options) { _super.call(this); // take in implementation as defined by client var self = this; var o = options; if (o.trigger) { this.trigger = o.trigger; } if (o.kick) { this.kick = o.kick; } if (o.drag) { this.drag = o.drag; } if (o.on) { this.on = o.on; } this.dragstart = this.dragStart = cola.Layout.dragStart; this.dragend = this.dragEnd = cola.Layout.dragEnd; } // dummy functions in case not defined by client LayoutAdaptor.prototype.trigger = function (e) { }; ; LayoutAdaptor.prototype.kick = function () { }; ; LayoutAdaptor.prototype.drag = function () { }; ; LayoutAdaptor.prototype.on = function (eventType, listener) { return this; }; ; return LayoutAdaptor; })(cola.Layout); cola.LayoutAdaptor = LayoutAdaptor; /** * provides an interface for use with any external graph system (e.g. Cytoscape.js): */ function adaptor(options) { return new LayoutAdaptor(options); } cola.adaptor = adaptor; })(cola || (cola = {})); var cola; (function (cola) { function gridify(pgLayout, nudgeGap, margin, groupMargin) { pgLayout.cola.start(0, 0, 0, 10, false); var gridrouter = route(pgLayout.cola.nodes(), pgLayout.cola.groups(), margin, groupMargin); return gridrouter.routeEdges(pgLayout.powerGraph.powerEdges, nudgeGap, function (e) { return e.source.routerNode.id; }, function (e) { return e.target.routerNode.id; }); } cola.gridify = gridify; function route(nodes, groups, margin, groupMargin) { nodes.forEach(function (d) { d.routerNode = { name: d.name, bounds: d.bounds.inflate(-margin) }; }); groups.forEach(function (d) { d.routerNode = { bounds: d.bounds.inflate(-groupMargin), children: (typeof d.groups !== 'undefined' ? d.groups.map(function (c) { return nodes.length + c.id; }) : []) .concat(typeof d.leaves !== 'undefined' ? d.leaves.map(function (c) { return c.index; }) : []) }; }); var gridRouterNodes = nodes.concat(groups).map(function (d, i) { d.routerNode.id = i; return d.routerNode; }); return new cola.GridRouter(gridRouterNodes, { getChildren: function (v) { return v.children; }, getBounds: function (v) { return v.bounds; } }, margin - groupMargin); } function powerGraphGridLayout(graph, size, grouppadding, margin, groupMargin) { // compute power graph var powerGraph; graph.nodes.forEach(function (v, i) { return v.index = i; }); new cola.Layout() .avoidOverlaps(false) .nodes(graph.nodes) .links(graph.links) .powerGraphGroups(function (d) { powerGraph = d; powerGraph.groups.forEach(function (v) { return v.padding = grouppadding; }); }); // construct a flat graph with dummy nodes for the groups and edges connecting group dummy nodes to their children // power edges attached to groups are replaced with edges connected to the corresponding group dummy node var n = graph.nodes.length; var edges = []; var vs = graph.nodes.slice(0); vs.forEach(function (v, i) { return v.index = i; }); powerGraph.groups.forEach(function (g) { var sourceInd = g.index = g.id + n; vs.push(g); if (typeof g.leaves !== 'undefined') g.leaves.forEach(function (v) { return edges.push({ source: sourceInd, target: v.index }); }); if (typeof g.groups !== 'undefined') g.groups.forEach(function (gg) { return edges.push({ source: sourceInd, target: gg.id + n }); }); }); powerGraph.powerEdges.forEach(function (e) { edges.push({ source: e.source.index, target: e.target.index }); }); // layout the flat graph with dummy nodes and edges new cola.Layout() .size(size) .nodes(vs) .links(edges) .avoidOverlaps(false) .linkDistance(30) .symmetricDiffLinkLengths(5) .convergenceThreshold(1e-4) .start(100, 0, 0, 0, false); // final layout taking node positions from above as starting positions // subject to group containment constraints // and then gridifying the layout return { cola: new cola.Layout() .convergenceThreshold(1e-3) .size(size) .avoidOverlaps(true) .nodes(graph.nodes) .links(graph.links) .groupCompactness(1e-4) .linkDistance(30) .symmetricDiffLinkLengths(5) .powerGraphGroups(function (d) { powerGraph = d; powerGraph.groups.forEach(function (v) { v.padding = grouppadding; }); }).start(50, 0, 100, 0, false), powerGraph: powerGraph }; } cola.powerGraphGridLayout = powerGraphGridLayout; })(cola || (cola = {})); /// /// var cola; (function (cola) { var D3StyleLayoutAdaptor = (function (_super) { __extends(D3StyleLayoutAdaptor, _super); function D3StyleLayoutAdaptor() { _super.call(this); this.event = d3.dispatch(cola.EventType[cola.EventType.start], cola.EventType[cola.EventType.tick], cola.EventType[cola.EventType.end]); // bit of trickyness remapping 'this' so we can reference it in the function body. var d3layout = this; var drag; this.drag = function () { if (!drag) { var drag = d3.behavior.drag() .origin(function (d) { return d; }) .on("dragstart.d3adaptor", cola.Layout.dragStart) .on("drag.d3adaptor", function (d) { d.px = d3.event.x, d.py = d3.event.y; d3layout.resume(); // restart annealing }) .on("dragend.d3adaptor", cola.Layout.dragEnd); } if (!arguments.length) return drag; // this is the context of the function, i.e. the d3 selection this //.on("mouseover.adaptor", colaMouseover) .call(drag); }; } D3StyleLayoutAdaptor.prototype.trigger = function (e) { var d3event = { type: cola.EventType[e.type], alpha: e.alpha, stress: e.stress }; this.event[d3event.type](d3event); // via d3 dispatcher, e.g. event.start(e); }; // iterate layout using a d3.timer, which queues calls to tick repeatedly until tick returns true D3StyleLayoutAdaptor.prototype.kick = function () { var _this = this; d3.timer(function () { return _super.prototype.tick.call(_this); }); }; // a function for binding to events on the adapter D3StyleLayoutAdaptor.prototype.on = function (eventType, listener) { if (typeof eventType === 'string') { this.event.on(eventType, listener); } else { this.event.on(cola.EventType[eventType], listener); } return this; }; return D3StyleLayoutAdaptor; })(cola.Layout); cola.D3StyleLayoutAdaptor = D3StyleLayoutAdaptor; /** * provides an interface for use with d3: * - uses the d3 event system to dispatch layout events such as: * o "start" (start layout process) * o "tick" (after each layout iteration) * o "end" (layout converged and complete). * - uses the d3 timer to queue layout iterations. * - sets up d3.behavior.drag to drag nodes * o use `node.call(.drag)` to make nodes draggable * returns an instance of the cola.Layout itself with which the user * can interact directly. */ function d3adaptor() { return new D3StyleLayoutAdaptor(); } cola.d3adaptor = d3adaptor; })(cola || (cola = {})); /// /// /// /// var cola; (function (cola) { 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; })(); cola.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; })(); cola.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; })(); cola.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'); // create parents for each node or group that is a member of another's children this.groups.forEach(function (v) { return v.children.forEach(function (c) { return _this.nodes[c].parent = v; }); }); // root claims the remaining orphans this.root = { children: [] }; this.nodes.forEach(function (v) { if (typeof v.parent === 'undefined') { v.parent = _this.root; _this.root.children.push(v.id); } // each node will have grid vertices associated with it, // some inside the node and some on the boundary // leaf nodes will have exactly one internal node at the center // and four boundary nodes // groups will have potentially many of each v.ports = []; }); // nodes ordered by their position in the group hierarchy this.backToFront = this.nodes.slice(0); this.backToFront.sort(function (x, y) { return _this.getDepth(x) - _this.getDepth(y); }); // compute boundary rectangles for each group // has to be done from front to back, i.e. inside groups to outside groups // such that each can be made large enough to enclose its interior var frontToBackGroups = this.backToFront.slice(0).reverse().filter(function (g) { return !g.leaf; }); frontToBackGroups.forEach(function (v) { var r = cola.vpsc.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; })); // setup extents of lines var rowx = colMids[0], rowX = colMids[colMids.length - 1]; var coly = rowMids[0], colY = rowMids[rowMids.length - 1]; // horizontal lines 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 }; })); // vertical lines 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 }; })); // the full set of lines var lines = hlines.concat(vlines); // we record the vertices associated with each line lines.forEach(function (l) { return l.verts = []; }); // the routing graph this.verts = []; this.edges = []; // create vertices at the crossings of horizontal and vertical grid-lines 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); // assign vertices to the nodes immediately under them 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) { // create vertices at the intersections of nodes and lines _this.nodes.forEach(function (v, i) { v.rect.lineIntersections(l.x1, l.y1, l.x2, l.y2).forEach(function (intersect, j) { //console.log(li+','+i+','+j+':'+intersect.x + ',' + intersect.y); 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); }); }); // split lines into edges joining vertices 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; }; // in the given axis, find sets of leaves overlapping in that axis // center of each GridLine is average of all nodes in column GridRouter.prototype.getGridLines = function (axis) { var columns = []; var ls = this.leaves.slice(0, this.leaves.length); while (ls.length > 0) { // find a column of all leaves overlapping in axis with the first leaf 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; }; // get the depth of the given node in the group hierarchy GridRouter.prototype.getDepth = function (v) { var depth = 0; while (v.parent !== this.root) { depth++; v = v.parent; } return depth; }; // medial axes between node centres and also boundary lines for the grid 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; }; // find path from v to root including both v and root GridRouter.prototype.findLineage = function (v) { var lineage = [v]; do { v = v.parent; lineage.push(v); } while (v !== this.root); return lineage.reverse(); }; // find path connecting a and b through their lowest common ancestor GridRouter.prototype.findAncestorPathBetween = function (a, b) { var aa = this.findLineage(a), ba = this.findLineage(b), i = 0; while (aa[i] === ba[i]) i++; // i-1 to include common ancestor only once (as first element) return { commonAncestor: aa[i - 1], lineages: aa.slice(i).concat(ba.slice(i)) }; }; // when finding a path between two nodes a and b, siblings of a and b on the // paths from a and b to their least common ancestor are obstacles 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]; }); }; // for the given routes, extract all the segments orthogonal to the axis x // and return all them grouped by x position GridRouter.getSegmentSets = function (routes, x, y) { // vsegments is a list of vertical segments sorted by x position 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]; }); // vsegmentsets is a set of sets of segments grouped by x position 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; }; // for all segments in this bundle create a vpsc problem such that // each segment's x position is a variable and separation constraints // are given by the partial order over the edges to which the segments belong // for each pair s1,s2 of segments in the open set: // e1 = edge of s1, e2 = edge of s2 // if leftOf(e1,e2) create constraint s1.x + gap <= s2.x // else if leftOf(e2,e1) create cons. s2.x + gap <= s1.x 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 cola.vpsc.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; // in page coordinates (not cartesian) the notion of 'leftof' is flipped in the horizontal axis from the vertical axis // that is, when nudging vertical segments, if they increase in the y(conj) direction the segment belonging to the // 'left' edge actually needs to be nudged to the right // when nudging horizontal segments, if the segments increase in the x direction // then the 'left' segment needs to go higher, i.e. to have y pos less than that of the right if (x == 'x') { if (leftOf(e1, e2)) { //console.log('s1: ' + s1[0][x] + ',' + s1[0][y] + '-' + s1[1][x] + ',' + s1[1][y]); 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) { //console.log(x+' constraint: ' + lind + '<' + rind); cs.push(new cola.vpsc.Constraint(vs[lind], vs[rind], gap)); } } } var solver = new cola.vpsc.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); // scan the grouped (by x) segment sets to find co-linear bundles 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 = []; } }); } }; // obtain routes for the specified edges, nicely nudged apart // warning: edge paths may be reversed such that common paths are ordered consistently within bundles! // @param edges list of edges // @param nudgeGap how much to space parallel edge segements // @param source function to retrieve the index of the source node for a given edge // @param target function to retrieve the index of the target node for a given edge // @returns an array giving, for each edge, an array of segments, each segment a pair of points in an array 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 = cola.GridRouter.orderEdges(routePaths); var routes = routePaths.map(function (e) { return cola.GridRouter.makeSegments(e); }); cola.GridRouter.nudgeSegments(routes, 'x', 'y', order, nudgeGap); cola.GridRouter.nudgeSegments(routes, 'y', 'x', order, nudgeGap); cola.GridRouter.unreverseEdges(routes, routePaths); return routes; }; // path may have been reversed by the subsequence processing in orderEdges // so now we need to restore the original order GridRouter.unreverseEdges = function (routes, routePaths) { routes.forEach(function (segments, i) { var path = routePaths[i]; if (path.reversed) { segments.reverse(); // reverse order of segments segments.forEach(function (segment) { segment.reverse(); // reverse each segment }); } }); }; 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; }; // does the path a-b-c describe a left turn? GridRouter.isLeft = function (a, b, c) { return ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)) <= 0; }; // for the given list of ordered pairs, returns a function that (efficiently) looks-up a specific pair to // see if it exists in the list 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]; }; }; // returns an ordering (a lookup function) that determines the correct order to nudge the // edge paths apart to minimize crossings 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 cola.LongestCommonSubsequence(e, f); var u, vi, vj; if (lcs.length === 0) continue; // no common subpath if (lcs.reversed) { // if we found a common subpath but one of the edges runs the wrong way, // then reverse f. f.reverse(); f.reversed = true; lcs = new cola.LongestCommonSubsequence(e, f); } if ((lcs.si <= 0 || lcs.ti <= 0) && (lcs.si + lcs.length >= e.length || lcs.ti + lcs.length >= f.length)) { // the paths do not diverge, so make an arbitrary ordering decision edgeOrder.push({ l: i, r: j }); continue; } if (lcs.si + lcs.length >= e.length || lcs.ti + lcs.length >= f.length) { // if the common subsequence of the // two edges being considered goes all the way to the // end of one (or both) of the lines then we have to // base our ordering decision on the other end of the // common subsequence 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 }); } } } //edgeOrder.forEach(function (e) { console.log('l:' + e.l + ',r:' + e.r) }); return cola.GridRouter.getOrder(edgeOrder); }; // for an orthogonal path described by a sequence of points, create a list of segments // if consecutive segments would make a straight line they are merged into a single segment // segments are over cloned points, not the original vertices 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; }; // find a route between node s and node t // returns an array of indices to verts 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); }); // add dummy segments linking ports inside source and target 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 cola.shortestpaths.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); // don't count bends from internal node edges if (a.node === source && a.node === b.node || b.node === target && b.node === c.node) return 0; return dx > 1 && dy > 1 ? 1000 : 0; }; // get shortest path var shortestPath = shortestPathCalculator.PathFromNodeToNodeWithPrevCost(source.ports[0].id, target.ports[0].id, bendPenalty); // shortest path is reversed and does not include the target port var pathPoints = shortestPath.reverse().map(function (vi) { return _this.verts[vi]; }); pathPoints.push(this.nodes[target.id].ports[0]); // filter out any extra end points that are inside the source or target (i.e. the dummy segments above) 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; //console.log(cola.GridRouter.angleBetween2Lines(li, l)) 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; })(); cola.GridRouter = GridRouter; })(cola || (cola = {})); /** * Use cola to do a layout in 3D!! Yay. * Pretty simple for the moment. */ var cola; (function (cola) { var Link3D = (function () { function Link3D(source, target) { this.source = source; this.target = target; } Link3D.prototype.actualLength = function (x) { var _this = this; return Math.sqrt(x.reduce(function (c, v) { var dx = v[_this.target] - v[_this.source]; return c + dx * dx; }, 0)); }; return Link3D; })(); cola.Link3D = Link3D; var Node3D = (function () { function Node3D(x, y, z) { if (x === void 0) { x = 0; } if (y === void 0) { y = 0; } if (z === void 0) { z = 0; } this.x = x; this.y = y; this.z = z; } return Node3D; })(); cola.Node3D = Node3D; var Layout3D = (function () { function Layout3D(nodes, links, idealLinkLength) { var _this = this; if (idealLinkLength === void 0) { idealLinkLength = 1; } this.nodes = nodes; this.links = links; this.idealLinkLength = idealLinkLength; this.constraints = null; this.useJaccardLinkLengths = true; this.result = new Array(Layout3D.k); for (var i = 0; i < Layout3D.k; ++i) { this.result[i] = new Array(nodes.length); } nodes.forEach(function (v, i) { for (var _i = 0, _a = Layout3D.dims; _i < _a.length; _i++) { var dim = _a[_i]; if (typeof v[dim] == 'undefined') v[dim] = Math.random(); } _this.result[0][i] = v.x; _this.result[1][i] = v.y; _this.result[2][i] = v.z; }); } ; Layout3D.prototype.linkLength = function (l) { return l.actualLength(this.result); }; Layout3D.prototype.start = function (iterations) { var _this = this; if (iterations === void 0) { iterations = 100; } var n = this.nodes.length; var linkAccessor = new LinkAccessor(); if (this.useJaccardLinkLengths) cola.jaccardLinkLengths(this.links, linkAccessor, 1.5); this.links.forEach(function (e) { return e.length *= _this.idealLinkLength; }); // Create the distance matrix that Cola needs var distanceMatrix = (new cola.shortestpaths.Calculator(n, this.links, function (e) { return e.source; }, function (e) { return e.target; }, function (e) { return e.length; })).DistanceMatrix(); var D = cola.Descent.createSquareMatrix(n, function (i, j) { return distanceMatrix[i][j]; }); // G is a square matrix with G[i][j] = 1 iff there exists an edge between node i and node j // otherwise 2. var G = cola.Descent.createSquareMatrix(n, function () { return 2; }); this.links.forEach(function (_a) { var source = _a.source, target = _a.target; return G[source][target] = G[target][source] = 1; }); this.descent = new cola.Descent(this.result, D); this.descent.threshold = 1e-3; this.descent.G = G; //let constraints = this.links.map(e=> { // axis: 'y', left: e.source, right: e.target, gap: e.length*1.5 //}); if (this.constraints) this.descent.project = new cola.vpsc.Projection(this.nodes, null, null, this.constraints).projectFunctions(); for (var i = 0; i < this.nodes.length; i++) { var v = this.nodes[i]; if (v.fixed) { this.descent.locks.add(i, [v.x, v.y, v.z]); } } this.descent.run(iterations); return this; }; Layout3D.prototype.tick = function () { this.descent.locks.clear(); for (var i = 0; i < this.nodes.length; i++) { var v = this.nodes[i]; if (v.fixed) { this.descent.locks.add(i, [v.x, v.y, v.z]); } } return this.descent.rungeKutta(); }; Layout3D.dims = ['x', 'y', 'z']; Layout3D.k = Layout3D.dims.length; return Layout3D; })(); cola.Layout3D = Layout3D; var LinkAccessor = (function () { function LinkAccessor() { } LinkAccessor.prototype.getSourceIndex = function (e) { return e.source; }; LinkAccessor.prototype.getTargetIndex = function (e) { return e.target; }; LinkAccessor.prototype.getLength = function (e) { return e.length; }; LinkAccessor.prototype.setLength = function (e, l) { e.length = l; }; return LinkAccessor; })(); })(cola || (cola = {})); //# sourceMappingURL=cola.js.map