blob: 73d436a1b8f80902549b5cee59e9b8d96f5c6cc0 [file] [log] [blame]
/**
* Copyright (c) 2006-2015, JGraph Ltd
* Copyright (c) 2006-2015, Gaudenz Alder
*/
/**
* Class: mxParallelEdgeLayout
*
* Extends <mxGraphLayout> 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 <mxGraphLayout.execute>.
*/
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)]);
}
};