diff --git a/js/ui/mxgraph/src/js/layout/mxParallelEdgeLayout.js b/js/ui/mxgraph/src/js/layout/mxParallelEdgeLayout.js new file mode 100644 index 0000000..73d436a --- /dev/null +++ b/js/ui/mxgraph/src/js/layout/mxParallelEdgeLayout.js @@ -0,0 +1,225 @@ +/** + * Copyright (c) 2006-2015, JGraph Ltd + * Copyright (c) 2006-2015, Gaudenz Alder + */ +/** + * Class: mxParallelEdgeLayout + * + * Extends for arranging parallel edges. This layout works + * on edges for all pairs of vertices where there is more than one edge + * connecting the latter. + * + * Example: + * + * (code) + * var layout = new mxParallelEdgeLayout(graph); + * layout.execute(graph.getDefaultParent()); + * (end) + * + * To run the layout for the parallel edges of a changed edge only, the + * following code can be used. + * + * (code) + * var layout = new mxParallelEdgeLayout(graph); + * + * graph.addListener(mxEvent.CELL_CONNECTED, function(sender, evt) + * { + * var model = graph.getModel(); + * var edge = evt.getProperty('edge'); + * var src = model.getTerminal(edge, true); + * var trg = model.getTerminal(edge, false); + * + * layout.isEdgeIgnored = function(edge2) + * { + * var src2 = model.getTerminal(edge2, true); + * var trg2 = model.getTerminal(edge2, false); + * + * return !(model.isEdge(edge2) && ((src == src2 && trg == trg2) || (src == trg2 && trg == src2))); + * }; + * + * layout.execute(graph.getDefaultParent()); + * }); + * (end) + * + * Constructor: mxCompactTreeLayout + * + * Constructs a new fast organic layout for the specified graph. + */ +function mxParallelEdgeLayout(graph) +{ + mxGraphLayout.call(this, graph); +}; + +/** + * Extends mxGraphLayout. + */ +mxParallelEdgeLayout.prototype = new mxGraphLayout(); +mxParallelEdgeLayout.prototype.constructor = mxParallelEdgeLayout; + +/** + * Variable: spacing + * + * Defines the spacing between the parallels. Default is 20. + */ +mxParallelEdgeLayout.prototype.spacing = 20; + +/** + * Function: execute + * + * Implements . + */ +mxParallelEdgeLayout.prototype.execute = function(parent) +{ + var lookup = this.findParallels(parent); + + this.graph.model.beginUpdate(); + try + { + for (var i in lookup) + { + var parallels = lookup[i]; + + if (parallels.length > 1) + { + this.layout(parallels); + } + } + } + finally + { + this.graph.model.endUpdate(); + } +}; + +/** + * Function: findParallels + * + * Finds the parallel edges in the given parent. + */ +mxParallelEdgeLayout.prototype.findParallels = function(parent) +{ + var model = this.graph.getModel(); + var lookup = []; + var childCount = model.getChildCount(parent); + + for (var i = 0; i < childCount; i++) + { + var child = model.getChildAt(parent, i); + + if (!this.isEdgeIgnored(child)) + { + var id = this.getEdgeId(child); + + if (id != null) + { + if (lookup[id] == null) + { + lookup[id] = []; + } + + lookup[id].push(child); + } + } + } + + return lookup; +}; + +/** + * Function: getEdgeId + * + * Returns a unique ID for the given edge. The id is independent of the + * edge direction and is built using the visible terminal of the given + * edge. + */ +mxParallelEdgeLayout.prototype.getEdgeId = function(edge) +{ + var view = this.graph.getView(); + + // Cannot used cached visible terminal because this could be triggered in BEFORE_UNDO + var src = view.getVisibleTerminal(edge, true); + var trg = view.getVisibleTerminal(edge, false); + + if (src != null && trg != null) + { + src = mxObjectIdentity.get(src); + trg = mxObjectIdentity.get(trg); + + return (src > trg) ? trg + '-' + src : src + '-' + trg; + } + + return null; +}; + +/** + * Function: layout + * + * Lays out the parallel edges in the given array. + */ +mxParallelEdgeLayout.prototype.layout = function(parallels) +{ + var edge = parallels[0]; + var view = this.graph.getView(); + var model = this.graph.getModel(); + var src = model.getGeometry(view.getVisibleTerminal(edge, true)); + var trg = model.getGeometry(view.getVisibleTerminal(edge, false)); + + // Routes multiple loops + if (src == trg) + { + var x0 = src.x + src.width + this.spacing; + var y0 = src.y + src.height / 2; + + for (var i = 0; i < parallels.length; i++) + { + this.route(parallels[i], x0, y0); + x0 += this.spacing; + } + } + else if (src != null && trg != null) + { + // Routes parallel edges + var scx = src.x + src.width / 2; + var scy = src.y + src.height / 2; + + var tcx = trg.x + trg.width / 2; + var tcy = trg.y + trg.height / 2; + + var dx = tcx - scx; + var dy = tcy - scy; + + var len = Math.sqrt(dx * dx + dy * dy); + + if (len > 0) + { + var x0 = scx + dx / 2; + var y0 = scy + dy / 2; + + var nx = dy * this.spacing / len; + var ny = dx * this.spacing / len; + + x0 += nx * (parallels.length - 1) / 2; + y0 -= ny * (parallels.length - 1) / 2; + + for (var i = 0; i < parallels.length; i++) + { + this.route(parallels[i], x0, y0); + x0 -= nx; + y0 += ny; + } + } + } +}; + +/** + * Function: route + * + * Routes the given edge via the given point. + */ +mxParallelEdgeLayout.prototype.route = function(edge, x, y) +{ + if (this.graph.isCellMovable(edge)) + { + this.setEdgePoints(edge, [new mxPoint(x, y)]); + } +};