1653 lines
42 KiB
JavaScript
1653 lines
42 KiB
JavaScript
|
/**
|
||
|
* Copyright (c) 2006-2015, JGraph Ltd
|
||
|
* Copyright (c) 2006-2015, Gaudenz Alder
|
||
|
*/
|
||
|
var mxEdgeStyle =
|
||
|
{
|
||
|
/**
|
||
|
* Class: mxEdgeStyle
|
||
|
*
|
||
|
* Provides various edge styles to be used as the values for
|
||
|
* <mxConstants.STYLE_EDGE> in a cell style.
|
||
|
*
|
||
|
* Example:
|
||
|
*
|
||
|
* (code)
|
||
|
* var style = stylesheet.getDefaultEdgeStyle();
|
||
|
* style[mxConstants.STYLE_EDGE] = mxEdgeStyle.ElbowConnector;
|
||
|
* (end)
|
||
|
*
|
||
|
* Sets the default edge style to <ElbowConnector>.
|
||
|
*
|
||
|
* Custom edge style:
|
||
|
*
|
||
|
* To write a custom edge style, a function must be added to the mxEdgeStyle
|
||
|
* object as follows:
|
||
|
*
|
||
|
* (code)
|
||
|
* mxEdgeStyle.MyStyle = function(state, source, target, points, result)
|
||
|
* {
|
||
|
* if (source != null && target != null)
|
||
|
* {
|
||
|
* var pt = new mxPoint(target.getCenterX(), source.getCenterY());
|
||
|
*
|
||
|
* if (mxUtils.contains(source, pt.x, pt.y))
|
||
|
* {
|
||
|
* pt.y = source.y + source.height;
|
||
|
* }
|
||
|
*
|
||
|
* result.push(pt);
|
||
|
* }
|
||
|
* };
|
||
|
* (end)
|
||
|
*
|
||
|
* In the above example, a right angle is created using a point on the
|
||
|
* horizontal center of the target vertex and the vertical center of the source
|
||
|
* vertex. The code checks if that point intersects the source vertex and makes
|
||
|
* the edge straight if it does. The point is then added into the result array,
|
||
|
* which acts as the return value of the function.
|
||
|
*
|
||
|
* The new edge style should then be registered in the <mxStyleRegistry> as follows:
|
||
|
* (code)
|
||
|
* mxStyleRegistry.putValue('myEdgeStyle', mxEdgeStyle.MyStyle);
|
||
|
* (end)
|
||
|
*
|
||
|
* The custom edge style above can now be used in a specific edge as follows:
|
||
|
*
|
||
|
* (code)
|
||
|
* model.setStyle(edge, 'edgeStyle=myEdgeStyle');
|
||
|
* (end)
|
||
|
*
|
||
|
* Note that the key of the <mxStyleRegistry> entry for the function should
|
||
|
* be used in string values, unless <mxGraphView.allowEval> is true, in
|
||
|
* which case you can also use mxEdgeStyle.MyStyle for the value in the
|
||
|
* cell style above.
|
||
|
*
|
||
|
* Or it can be used for all edges in the graph as follows:
|
||
|
*
|
||
|
* (code)
|
||
|
* var style = graph.getStylesheet().getDefaultEdgeStyle();
|
||
|
* style[mxConstants.STYLE_EDGE] = mxEdgeStyle.MyStyle;
|
||
|
* (end)
|
||
|
*
|
||
|
* Note that the object can be used directly when programmatically setting
|
||
|
* the value, but the key in the <mxStyleRegistry> should be used when
|
||
|
* setting the value via a key, value pair in a cell style.
|
||
|
*
|
||
|
* Function: EntityRelation
|
||
|
*
|
||
|
* Implements an entity relation style for edges (as used in database
|
||
|
* schema diagrams). At the time the function is called, the result
|
||
|
* array contains a placeholder (null) for the first absolute point,
|
||
|
* that is, the point where the edge and source terminal are connected.
|
||
|
* The implementation of the style then adds all intermediate waypoints
|
||
|
* except for the last point, that is, the connection point between the
|
||
|
* edge and the target terminal. The first ant the last point in the
|
||
|
* result array are then replaced with mxPoints that take into account
|
||
|
* the terminal's perimeter and next point on the edge.
|
||
|
*
|
||
|
* Parameters:
|
||
|
*
|
||
|
* state - <mxCellState> that represents the edge to be updated.
|
||
|
* source - <mxCellState> that represents the source terminal.
|
||
|
* target - <mxCellState> that represents the target terminal.
|
||
|
* points - List of relative control points.
|
||
|
* result - Array of <mxPoints> that represent the actual points of the
|
||
|
* edge.
|
||
|
*/
|
||
|
EntityRelation: function(state, source, target, points, result)
|
||
|
{
|
||
|
var view = state.view;
|
||
|
var graph = view.graph;
|
||
|
var segment = mxUtils.getValue(state.style,
|
||
|
mxConstants.STYLE_SEGMENT,
|
||
|
mxConstants.ENTITY_SEGMENT) * view.scale;
|
||
|
|
||
|
var pts = state.absolutePoints;
|
||
|
var p0 = pts[0];
|
||
|
var pe = pts[pts.length-1];
|
||
|
|
||
|
var isSourceLeft = false;
|
||
|
|
||
|
if (source != null)
|
||
|
{
|
||
|
var sourceGeometry = graph.getCellGeometry(source.cell);
|
||
|
|
||
|
if (sourceGeometry.relative)
|
||
|
{
|
||
|
isSourceLeft = sourceGeometry.x <= 0.5;
|
||
|
}
|
||
|
else if (target != null)
|
||
|
{
|
||
|
isSourceLeft = ((pe != null) ? pe.x : target.x + target.width) < ((p0 != null) ? p0.x : source.x);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (p0 != null)
|
||
|
{
|
||
|
source = new mxCellState();
|
||
|
source.x = p0.x;
|
||
|
source.y = p0.y;
|
||
|
}
|
||
|
else if (source != null)
|
||
|
{
|
||
|
var constraint = mxUtils.getPortConstraints(source, state, true, mxConstants.DIRECTION_MASK_NONE);
|
||
|
|
||
|
if (constraint != mxConstants.DIRECTION_MASK_NONE && constraint != mxConstants.DIRECTION_MASK_WEST +
|
||
|
mxConstants.DIRECTION_MASK_EAST)
|
||
|
{
|
||
|
isSourceLeft = constraint == mxConstants.DIRECTION_MASK_WEST;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var isTargetLeft = true;
|
||
|
|
||
|
if (target != null)
|
||
|
{
|
||
|
var targetGeometry = graph.getCellGeometry(target.cell);
|
||
|
|
||
|
if (targetGeometry.relative)
|
||
|
{
|
||
|
isTargetLeft = targetGeometry.x <= 0.5;
|
||
|
}
|
||
|
else if (source != null)
|
||
|
{
|
||
|
isTargetLeft = ((p0 != null) ? p0.x : source.x + source.width) < ((pe != null) ? pe.x : target.x);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (pe != null)
|
||
|
{
|
||
|
target = new mxCellState();
|
||
|
target.x = pe.x;
|
||
|
target.y = pe.y;
|
||
|
}
|
||
|
else if (target != null)
|
||
|
{
|
||
|
var constraint = mxUtils.getPortConstraints(target, state, false, mxConstants.DIRECTION_MASK_NONE);
|
||
|
|
||
|
if (constraint != mxConstants.DIRECTION_MASK_NONE && constraint != mxConstants.DIRECTION_MASK_WEST +
|
||
|
mxConstants.DIRECTION_MASK_EAST)
|
||
|
{
|
||
|
isTargetLeft = constraint == mxConstants.DIRECTION_MASK_WEST;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (source != null && target != null)
|
||
|
{
|
||
|
var x0 = (isSourceLeft) ? source.x : source.x + source.width;
|
||
|
var y0 = view.getRoutingCenterY(source);
|
||
|
|
||
|
var xe = (isTargetLeft) ? target.x : target.x + target.width;
|
||
|
var ye = view.getRoutingCenterY(target);
|
||
|
|
||
|
var seg = segment;
|
||
|
|
||
|
var dx = (isSourceLeft) ? -seg : seg;
|
||
|
var dep = new mxPoint(x0 + dx, y0);
|
||
|
|
||
|
dx = (isTargetLeft) ? -seg : seg;
|
||
|
var arr = new mxPoint(xe + dx, ye);
|
||
|
|
||
|
// Adds intermediate points if both go out on same side
|
||
|
if (isSourceLeft == isTargetLeft)
|
||
|
{
|
||
|
var x = (isSourceLeft) ?
|
||
|
Math.min(x0, xe)-segment :
|
||
|
Math.max(x0, xe)+segment;
|
||
|
|
||
|
result.push(new mxPoint(x, y0));
|
||
|
result.push(new mxPoint(x, ye));
|
||
|
}
|
||
|
else if ((dep.x < arr.x) == isSourceLeft)
|
||
|
{
|
||
|
var midY = y0 + (ye - y0) / 2;
|
||
|
|
||
|
result.push(dep);
|
||
|
result.push(new mxPoint(dep.x, midY));
|
||
|
result.push(new mxPoint(arr.x, midY));
|
||
|
result.push(arr);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
result.push(dep);
|
||
|
result.push(arr);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Function: Loop
|
||
|
*
|
||
|
* Implements a self-reference, aka. loop.
|
||
|
*/
|
||
|
Loop: function(state, source, target, points, result)
|
||
|
{
|
||
|
var pts = state.absolutePoints;
|
||
|
|
||
|
var p0 = pts[0];
|
||
|
var pe = pts[pts.length-1];
|
||
|
|
||
|
if (p0 != null && pe != null)
|
||
|
{
|
||
|
if (points != null && points.length > 0)
|
||
|
{
|
||
|
for (var i = 0; i < points.length; i++)
|
||
|
{
|
||
|
var pt = points[i];
|
||
|
pt = state.view.transformControlPoint(state, pt);
|
||
|
result.push(new mxPoint(pt.x, pt.y));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (source != null)
|
||
|
{
|
||
|
var view = state.view;
|
||
|
var graph = view.graph;
|
||
|
var pt = (points != null && points.length > 0) ? points[0] : null;
|
||
|
|
||
|
if (pt != null)
|
||
|
{
|
||
|
pt = view.transformControlPoint(state, pt);
|
||
|
|
||
|
if (mxUtils.contains(source, pt.x, pt.y))
|
||
|
{
|
||
|
pt = null;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var x = 0;
|
||
|
var dx = 0;
|
||
|
var y = 0;
|
||
|
var dy = 0;
|
||
|
|
||
|
var seg = mxUtils.getValue(state.style, mxConstants.STYLE_SEGMENT,
|
||
|
graph.gridSize) * view.scale;
|
||
|
var dir = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION,
|
||
|
mxConstants.DIRECTION_WEST);
|
||
|
|
||
|
if (dir == mxConstants.DIRECTION_NORTH ||
|
||
|
dir == mxConstants.DIRECTION_SOUTH)
|
||
|
{
|
||
|
x = view.getRoutingCenterX(source);
|
||
|
dx = seg;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
y = view.getRoutingCenterY(source);
|
||
|
dy = seg;
|
||
|
}
|
||
|
|
||
|
if (pt == null ||
|
||
|
pt.x < source.x ||
|
||
|
pt.x > source.x + source.width)
|
||
|
{
|
||
|
if (pt != null)
|
||
|
{
|
||
|
x = pt.x;
|
||
|
dy = Math.max(Math.abs(y - pt.y), dy);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (dir == mxConstants.DIRECTION_NORTH)
|
||
|
{
|
||
|
y = source.y - 2 * dx;
|
||
|
}
|
||
|
else if (dir == mxConstants.DIRECTION_SOUTH)
|
||
|
{
|
||
|
y = source.y + source.height + 2 * dx;
|
||
|
}
|
||
|
else if (dir == mxConstants.DIRECTION_EAST)
|
||
|
{
|
||
|
x = source.x - 2 * dy;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
x = source.x + source.width + 2 * dy;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (pt != null)
|
||
|
{
|
||
|
x = view.getRoutingCenterX(source);
|
||
|
dx = Math.max(Math.abs(x - pt.x), dy);
|
||
|
y = pt.y;
|
||
|
dy = 0;
|
||
|
}
|
||
|
|
||
|
result.push(new mxPoint(x - dx, y - dy));
|
||
|
result.push(new mxPoint(x + dx, y + dy));
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Function: ElbowConnector
|
||
|
*
|
||
|
* Uses either <SideToSide> or <TopToBottom> depending on the horizontal
|
||
|
* flag in the cell style. <SideToSide> is used if horizontal is true or
|
||
|
* unspecified. See <EntityRelation> for a description of the
|
||
|
* parameters.
|
||
|
*/
|
||
|
ElbowConnector: function(state, source, target, points, result)
|
||
|
{
|
||
|
var pt = (points != null && points.length > 0) ? points[0] : null;
|
||
|
|
||
|
var vertical = false;
|
||
|
var horizontal = false;
|
||
|
|
||
|
if (source != null && target != null)
|
||
|
{
|
||
|
if (pt != null)
|
||
|
{
|
||
|
var left = Math.min(source.x, target.x);
|
||
|
var right = Math.max(source.x + source.width,
|
||
|
target.x + target.width);
|
||
|
|
||
|
var top = Math.min(source.y, target.y);
|
||
|
var bottom = Math.max(source.y + source.height,
|
||
|
target.y + target.height);
|
||
|
|
||
|
pt = state.view.transformControlPoint(state, pt);
|
||
|
|
||
|
vertical = pt.y < top || pt.y > bottom;
|
||
|
horizontal = pt.x < left || pt.x > right;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var left = Math.max(source.x, target.x);
|
||
|
var right = Math.min(source.x + source.width,
|
||
|
target.x + target.width);
|
||
|
|
||
|
vertical = left == right;
|
||
|
|
||
|
if (!vertical)
|
||
|
{
|
||
|
var top = Math.max(source.y, target.y);
|
||
|
var bottom = Math.min(source.y + source.height,
|
||
|
target.y + target.height);
|
||
|
|
||
|
horizontal = top == bottom;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!horizontal && (vertical ||
|
||
|
state.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL))
|
||
|
{
|
||
|
mxEdgeStyle.TopToBottom(state, source, target, points, result);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
mxEdgeStyle.SideToSide(state, source, target, points, result);
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Function: SideToSide
|
||
|
*
|
||
|
* Implements a vertical elbow edge. See <EntityRelation> for a description
|
||
|
* of the parameters.
|
||
|
*/
|
||
|
SideToSide: function(state, source, target, points, result)
|
||
|
{
|
||
|
var view = state.view;
|
||
|
var pt = (points != null && points.length > 0) ? points[0] : null;
|
||
|
var pts = state.absolutePoints;
|
||
|
var p0 = pts[0];
|
||
|
var pe = pts[pts.length-1];
|
||
|
|
||
|
if (pt != null)
|
||
|
{
|
||
|
pt = view.transformControlPoint(state, pt);
|
||
|
}
|
||
|
|
||
|
if (p0 != null)
|
||
|
{
|
||
|
source = new mxCellState();
|
||
|
source.x = p0.x;
|
||
|
source.y = p0.y;
|
||
|
}
|
||
|
|
||
|
if (pe != null)
|
||
|
{
|
||
|
target = new mxCellState();
|
||
|
target.x = pe.x;
|
||
|
target.y = pe.y;
|
||
|
}
|
||
|
|
||
|
if (source != null && target != null)
|
||
|
{
|
||
|
var l = Math.max(source.x, target.x);
|
||
|
var r = Math.min(source.x + source.width,
|
||
|
target.x + target.width);
|
||
|
|
||
|
var x = (pt != null) ? pt.x : Math.round(r + (l - r) / 2);
|
||
|
|
||
|
var y1 = view.getRoutingCenterY(source);
|
||
|
var y2 = view.getRoutingCenterY(target);
|
||
|
|
||
|
if (pt != null)
|
||
|
{
|
||
|
if (pt.y >= source.y && pt.y <= source.y + source.height)
|
||
|
{
|
||
|
y1 = pt.y;
|
||
|
}
|
||
|
|
||
|
if (pt.y >= target.y && pt.y <= target.y + target.height)
|
||
|
{
|
||
|
y2 = pt.y;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!mxUtils.contains(target, x, y1) &&
|
||
|
!mxUtils.contains(source, x, y1))
|
||
|
{
|
||
|
result.push(new mxPoint(x, y1));
|
||
|
}
|
||
|
|
||
|
if (!mxUtils.contains(target, x, y2) &&
|
||
|
!mxUtils.contains(source, x, y2))
|
||
|
{
|
||
|
result.push(new mxPoint(x, y2));
|
||
|
}
|
||
|
|
||
|
if (result.length == 1)
|
||
|
{
|
||
|
if (pt != null)
|
||
|
{
|
||
|
if (!mxUtils.contains(target, x, pt.y) &&
|
||
|
!mxUtils.contains(source, x, pt.y))
|
||
|
{
|
||
|
result.push(new mxPoint(x, pt.y));
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var t = Math.max(source.y, target.y);
|
||
|
var b = Math.min(source.y + source.height,
|
||
|
target.y + target.height);
|
||
|
|
||
|
result.push(new mxPoint(x, t + (b - t) / 2));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Function: TopToBottom
|
||
|
*
|
||
|
* Implements a horizontal elbow edge. See <EntityRelation> for a
|
||
|
* description of the parameters.
|
||
|
*/
|
||
|
TopToBottom: function(state, source, target, points, result)
|
||
|
{
|
||
|
var view = state.view;
|
||
|
var pt = (points != null && points.length > 0) ? points[0] : null;
|
||
|
var pts = state.absolutePoints;
|
||
|
var p0 = pts[0];
|
||
|
var pe = pts[pts.length-1];
|
||
|
|
||
|
if (pt != null)
|
||
|
{
|
||
|
pt = view.transformControlPoint(state, pt);
|
||
|
}
|
||
|
|
||
|
if (p0 != null)
|
||
|
{
|
||
|
source = new mxCellState();
|
||
|
source.x = p0.x;
|
||
|
source.y = p0.y;
|
||
|
}
|
||
|
|
||
|
if (pe != null)
|
||
|
{
|
||
|
target = new mxCellState();
|
||
|
target.x = pe.x;
|
||
|
target.y = pe.y;
|
||
|
}
|
||
|
|
||
|
if (source != null && target != null)
|
||
|
{
|
||
|
var t = Math.max(source.y, target.y);
|
||
|
var b = Math.min(source.y + source.height,
|
||
|
target.y + target.height);
|
||
|
|
||
|
var x = view.getRoutingCenterX(source);
|
||
|
|
||
|
if (pt != null &&
|
||
|
pt.x >= source.x &&
|
||
|
pt.x <= source.x + source.width)
|
||
|
{
|
||
|
x = pt.x;
|
||
|
}
|
||
|
|
||
|
var y = (pt != null) ? pt.y : Math.round(b + (t - b) / 2);
|
||
|
|
||
|
if (!mxUtils.contains(target, x, y) &&
|
||
|
!mxUtils.contains(source, x, y))
|
||
|
{
|
||
|
result.push(new mxPoint(x, y));
|
||
|
}
|
||
|
|
||
|
if (pt != null &&
|
||
|
pt.x >= target.x &&
|
||
|
pt.x <= target.x + target.width)
|
||
|
{
|
||
|
x = pt.x;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
x = view.getRoutingCenterX(target);
|
||
|
}
|
||
|
|
||
|
if (!mxUtils.contains(target, x, y) &&
|
||
|
!mxUtils.contains(source, x, y))
|
||
|
{
|
||
|
result.push(new mxPoint(x, y));
|
||
|
}
|
||
|
|
||
|
if (result.length == 1)
|
||
|
{
|
||
|
if (pt != null && result.length == 1)
|
||
|
{
|
||
|
if (!mxUtils.contains(target, pt.x, y) &&
|
||
|
!mxUtils.contains(source, pt.x, y))
|
||
|
{
|
||
|
result.push(new mxPoint(pt.x, y));
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var l = Math.max(source.x, target.x);
|
||
|
var r = Math.min(source.x + source.width,
|
||
|
target.x + target.width);
|
||
|
|
||
|
result.push(new mxPoint(l + (r - l) / 2, y));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Function: SegmentConnector
|
||
|
*
|
||
|
* Implements an orthogonal edge style. Use <mxEdgeSegmentHandler>
|
||
|
* as an interactive handler for this style.
|
||
|
*
|
||
|
* state - <mxCellState> that represents the edge to be updated.
|
||
|
* sourceScaled - <mxCellState> that represents the source terminal.
|
||
|
* targetScaled - <mxCellState> that represents the target terminal.
|
||
|
* controlHints - List of relative control points.
|
||
|
* result - Array of <mxPoints> that represent the actual points of the
|
||
|
* edge.
|
||
|
*
|
||
|
*/
|
||
|
SegmentConnector: function(state, sourceScaled, targetScaled, controlHints, result)
|
||
|
{
|
||
|
// Creates array of all way- and terminalpoints
|
||
|
var pts = mxEdgeStyle.scalePointArray(state.absolutePoints, state.view.scale);
|
||
|
var source = mxEdgeStyle.scaleCellState(sourceScaled, state.view.scale);
|
||
|
var target = mxEdgeStyle.scaleCellState(targetScaled, state.view.scale);
|
||
|
var tol = 1;
|
||
|
|
||
|
// Whether the first segment outgoing from the source end is horizontal
|
||
|
var lastPushed = (result.length > 0) ? result[0] : null;
|
||
|
var horizontal = true;
|
||
|
var hint = null;
|
||
|
|
||
|
// Adds waypoints only if outside of tolerance
|
||
|
function pushPoint(pt)
|
||
|
{
|
||
|
pt.x = Math.round(pt.x * state.view.scale * 10) / 10;
|
||
|
pt.y = Math.round(pt.y * state.view.scale * 10) / 10;
|
||
|
|
||
|
if (lastPushed == null || Math.abs(lastPushed.x - pt.x) >= tol || Math.abs(lastPushed.y - pt.y) >= Math.max(1, state.view.scale))
|
||
|
{
|
||
|
result.push(pt);
|
||
|
lastPushed = pt;
|
||
|
}
|
||
|
|
||
|
return lastPushed;
|
||
|
};
|
||
|
|
||
|
// Adds the first point
|
||
|
var pt = pts[0];
|
||
|
|
||
|
if (pt == null && source != null)
|
||
|
{
|
||
|
pt = new mxPoint(state.view.getRoutingCenterX(source), state.view.getRoutingCenterY(source));
|
||
|
}
|
||
|
else if (pt != null)
|
||
|
{
|
||
|
pt = pt.clone();
|
||
|
}
|
||
|
|
||
|
var lastInx = pts.length - 1;
|
||
|
|
||
|
// Adds the waypoints
|
||
|
if (controlHints != null && controlHints.length > 0)
|
||
|
{
|
||
|
// Converts all hints and removes nulls
|
||
|
var hints = [];
|
||
|
|
||
|
for (var i = 0; i < controlHints.length; i++)
|
||
|
{
|
||
|
var tmp = state.view.transformControlPoint(state, controlHints[i], true);
|
||
|
|
||
|
if (tmp != null)
|
||
|
{
|
||
|
hints.push(tmp);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (hints.length == 0)
|
||
|
{
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Aligns source and target hint to fixed points
|
||
|
if (pt != null && hints[0] != null)
|
||
|
{
|
||
|
if (Math.abs(hints[0].x - pt.x) < tol)
|
||
|
{
|
||
|
hints[0].x = pt.x;
|
||
|
}
|
||
|
|
||
|
if (Math.abs(hints[0].y - pt.y) < tol)
|
||
|
{
|
||
|
hints[0].y = pt.y;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var pe = pts[lastInx];
|
||
|
|
||
|
if (pe != null && hints[hints.length - 1] != null)
|
||
|
{
|
||
|
if (Math.abs(hints[hints.length - 1].x - pe.x) < tol)
|
||
|
{
|
||
|
hints[hints.length - 1].x = pe.x;
|
||
|
}
|
||
|
|
||
|
if (Math.abs(hints[hints.length - 1].y - pe.y) < tol)
|
||
|
{
|
||
|
hints[hints.length - 1].y = pe.y;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
hint = hints[0];
|
||
|
|
||
|
var currentTerm = source;
|
||
|
var currentPt = pts[0];
|
||
|
var hozChan = false;
|
||
|
var vertChan = false;
|
||
|
var currentHint = hint;
|
||
|
|
||
|
if (currentPt != null)
|
||
|
{
|
||
|
currentTerm = null;
|
||
|
}
|
||
|
|
||
|
// Check for alignment with fixed points and with channels
|
||
|
// at source and target segments only
|
||
|
for (var i = 0; i < 2; i++)
|
||
|
{
|
||
|
var fixedVertAlign = currentPt != null && currentPt.x == currentHint.x;
|
||
|
var fixedHozAlign = currentPt != null && currentPt.y == currentHint.y;
|
||
|
|
||
|
var inHozChan = currentTerm != null && (currentHint.y >= currentTerm.y &&
|
||
|
currentHint.y <= currentTerm.y + currentTerm.height);
|
||
|
var inVertChan = currentTerm != null && (currentHint.x >= currentTerm.x &&
|
||
|
currentHint.x <= currentTerm.x + currentTerm.width);
|
||
|
|
||
|
hozChan = fixedHozAlign || (currentPt == null && inHozChan);
|
||
|
vertChan = fixedVertAlign || (currentPt == null && inVertChan);
|
||
|
|
||
|
// If the current hint falls in both the hor and vert channels in the case
|
||
|
// of a floating port, or if the hint is exactly co-incident with a
|
||
|
// fixed point, ignore the source and try to work out the orientation
|
||
|
// from the target end
|
||
|
if (i==0 && ((hozChan && vertChan) || (fixedVertAlign && fixedHozAlign)))
|
||
|
{
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (currentPt != null && (!fixedHozAlign && !fixedVertAlign) && (inHozChan || inVertChan))
|
||
|
{
|
||
|
horizontal = inHozChan ? false : true;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
if (vertChan || hozChan)
|
||
|
{
|
||
|
horizontal = hozChan;
|
||
|
|
||
|
if (i == 1)
|
||
|
{
|
||
|
// Work back from target end
|
||
|
horizontal = hints.length % 2 == 0 ? hozChan : vertChan;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
currentTerm = target;
|
||
|
currentPt = pts[lastInx];
|
||
|
|
||
|
if (currentPt != null)
|
||
|
{
|
||
|
currentTerm = null;
|
||
|
}
|
||
|
|
||
|
currentHint = hints[hints.length - 1];
|
||
|
|
||
|
if (fixedVertAlign && fixedHozAlign)
|
||
|
{
|
||
|
hints = hints.slice(1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (horizontal && ((pts[0] != null && pts[0].y != hint.y) ||
|
||
|
(pts[0] == null && source != null &&
|
||
|
(hint.y < source.y || hint.y > source.y + source.height))))
|
||
|
{
|
||
|
pushPoint(new mxPoint(pt.x, hint.y));
|
||
|
}
|
||
|
else if (!horizontal && ((pts[0] != null && pts[0].x != hint.x) ||
|
||
|
(pts[0] == null && source != null &&
|
||
|
(hint.x < source.x || hint.x > source.x + source.width))))
|
||
|
{
|
||
|
pushPoint(new mxPoint(hint.x, pt.y));
|
||
|
}
|
||
|
|
||
|
if (horizontal)
|
||
|
{
|
||
|
pt.y = hint.y;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pt.x = hint.x;
|
||
|
}
|
||
|
|
||
|
for (var i = 0; i < hints.length; i++)
|
||
|
{
|
||
|
horizontal = !horizontal;
|
||
|
hint = hints[i];
|
||
|
|
||
|
// mxLog.show();
|
||
|
// mxLog.debug('hint', i, hint.x, hint.y);
|
||
|
|
||
|
if (horizontal)
|
||
|
{
|
||
|
pt.y = hint.y;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
pt.x = hint.x;
|
||
|
}
|
||
|
|
||
|
pushPoint(pt.clone());
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
hint = pt;
|
||
|
// FIXME: First click in connect preview toggles orientation
|
||
|
horizontal = true;
|
||
|
}
|
||
|
|
||
|
// Adds the last point
|
||
|
pt = pts[lastInx];
|
||
|
|
||
|
if (pt == null && target != null)
|
||
|
{
|
||
|
pt = new mxPoint(state.view.getRoutingCenterX(target), state.view.getRoutingCenterY(target));
|
||
|
}
|
||
|
|
||
|
if (pt != null)
|
||
|
{
|
||
|
if (hint != null)
|
||
|
{
|
||
|
if (horizontal && ((pts[lastInx] != null && pts[lastInx].y != hint.y) ||
|
||
|
(pts[lastInx] == null && target != null &&
|
||
|
(hint.y < target.y || hint.y > target.y + target.height))))
|
||
|
{
|
||
|
pushPoint(new mxPoint(pt.x, hint.y));
|
||
|
}
|
||
|
else if (!horizontal && ((pts[lastInx] != null && pts[lastInx].x != hint.x) ||
|
||
|
(pts[lastInx] == null && target != null &&
|
||
|
(hint.x < target.x || hint.x > target.x + target.width))))
|
||
|
{
|
||
|
pushPoint(new mxPoint(hint.x, pt.y));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Removes bends inside the source terminal for floating ports
|
||
|
if (pts[0] == null && source != null)
|
||
|
{
|
||
|
while (result.length > 1 && result[1] != null &&
|
||
|
mxUtils.contains(source, result[1].x, result[1].y))
|
||
|
{
|
||
|
result.splice(1, 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Removes bends inside the target terminal
|
||
|
if (pts[lastInx] == null && target != null)
|
||
|
{
|
||
|
while (result.length > 1 && result[result.length - 1] != null &&
|
||
|
mxUtils.contains(target, result[result.length - 1].x, result[result.length - 1].y))
|
||
|
{
|
||
|
result.splice(result.length - 1, 1);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Removes last point if inside tolerance with end point
|
||
|
if (pe != null && result[result.length - 1] != null &&
|
||
|
Math.abs(pe.x - result[result.length - 1].x) <= tol &&
|
||
|
Math.abs(pe.y - result[result.length - 1].y) <= tol)
|
||
|
{
|
||
|
result.splice(result.length - 1, 1);
|
||
|
|
||
|
// Lines up second last point in result with end point
|
||
|
if (result[result.length - 1] != null)
|
||
|
{
|
||
|
if (Math.abs(result[result.length - 1].x - pe.x) < tol)
|
||
|
{
|
||
|
result[result.length - 1].x = pe.x;
|
||
|
}
|
||
|
|
||
|
if (Math.abs(result[result.length - 1].y - pe.y) < tol)
|
||
|
{
|
||
|
result[result.length - 1].y = pe.y;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
orthBuffer: 10,
|
||
|
|
||
|
orthPointsFallback: true,
|
||
|
|
||
|
dirVectors: [ [ -1, 0 ],
|
||
|
[ 0, -1 ], [ 1, 0 ], [ 0, 1 ], [ -1, 0 ], [ 0, -1 ], [ 1, 0 ] ],
|
||
|
|
||
|
wayPoints1: [ [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0],
|
||
|
[ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0] ],
|
||
|
|
||
|
routePatterns: [
|
||
|
[ [ 513, 2308, 2081, 2562 ], [ 513, 1090, 514, 2184, 2114, 2561 ],
|
||
|
[ 513, 1090, 514, 2564, 2184, 2562 ],
|
||
|
[ 513, 2308, 2561, 1090, 514, 2568, 2308 ] ],
|
||
|
[ [ 514, 1057, 513, 2308, 2081, 2562 ], [ 514, 2184, 2114, 2561 ],
|
||
|
[ 514, 2184, 2562, 1057, 513, 2564, 2184 ],
|
||
|
[ 514, 1057, 513, 2568, 2308, 2561 ] ],
|
||
|
[ [ 1090, 514, 1057, 513, 2308, 2081, 2562 ], [ 2114, 2561 ],
|
||
|
[ 1090, 2562, 1057, 513, 2564, 2184 ],
|
||
|
[ 1090, 514, 1057, 513, 2308, 2561, 2568 ] ],
|
||
|
[ [ 2081, 2562 ], [ 1057, 513, 1090, 514, 2184, 2114, 2561 ],
|
||
|
[ 1057, 513, 1090, 514, 2184, 2562, 2564 ],
|
||
|
[ 1057, 2561, 1090, 514, 2568, 2308 ] ] ],
|
||
|
|
||
|
inlineRoutePatterns: [
|
||
|
[ null, [ 2114, 2568 ], null, null ],
|
||
|
[ null, [ 514, 2081, 2114, 2568 ] , null, null ],
|
||
|
[ null, [ 2114, 2561 ], null, null ],
|
||
|
[ [ 2081, 2562 ], [ 1057, 2114, 2568 ],
|
||
|
[ 2184, 2562 ],
|
||
|
null ] ],
|
||
|
vertexSeperations: [],
|
||
|
|
||
|
limits: [
|
||
|
[ 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
|
||
|
[ 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ],
|
||
|
|
||
|
LEFT_MASK: 32,
|
||
|
|
||
|
TOP_MASK: 64,
|
||
|
|
||
|
RIGHT_MASK: 128,
|
||
|
|
||
|
BOTTOM_MASK: 256,
|
||
|
|
||
|
LEFT: 1,
|
||
|
|
||
|
TOP: 2,
|
||
|
|
||
|
RIGHT: 4,
|
||
|
|
||
|
BOTTOM: 8,
|
||
|
|
||
|
// TODO remove magic numbers
|
||
|
SIDE_MASK: 480,
|
||
|
//mxEdgeStyle.LEFT_MASK | mxEdgeStyle.TOP_MASK | mxEdgeStyle.RIGHT_MASK
|
||
|
//| mxEdgeStyle.BOTTOM_MASK,
|
||
|
|
||
|
CENTER_MASK: 512,
|
||
|
|
||
|
SOURCE_MASK: 1024,
|
||
|
|
||
|
TARGET_MASK: 2048,
|
||
|
|
||
|
VERTEX_MASK: 3072,
|
||
|
// mxEdgeStyle.SOURCE_MASK | mxEdgeStyle.TARGET_MASK,
|
||
|
|
||
|
getJettySize: function(state, isSource)
|
||
|
{
|
||
|
var value = mxUtils.getValue(state.style, (isSource) ? mxConstants.STYLE_SOURCE_JETTY_SIZE :
|
||
|
mxConstants.STYLE_TARGET_JETTY_SIZE, mxUtils.getValue(state.style,
|
||
|
mxConstants.STYLE_JETTY_SIZE, mxEdgeStyle.orthBuffer));
|
||
|
|
||
|
if (value == 'auto')
|
||
|
{
|
||
|
// Computes the automatic jetty size
|
||
|
var type = mxUtils.getValue(state.style, (isSource) ? mxConstants.STYLE_STARTARROW : mxConstants.STYLE_ENDARROW, mxConstants.NONE);
|
||
|
|
||
|
if (type != mxConstants.NONE)
|
||
|
{
|
||
|
var size = mxUtils.getNumber(state.style, (isSource) ? mxConstants.STYLE_STARTSIZE : mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE);
|
||
|
value = Math.max(2, Math.ceil((size + mxEdgeStyle.orthBuffer) / mxEdgeStyle.orthBuffer)) * mxEdgeStyle.orthBuffer;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
value = 2 * mxEdgeStyle.orthBuffer;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return value;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Function: scalePointArray
|
||
|
*
|
||
|
* Scales an array of <mxPoint>
|
||
|
*
|
||
|
* Parameters:
|
||
|
*
|
||
|
* points - array of <mxPoint> to scale
|
||
|
* scale - the scaling to divide by
|
||
|
*
|
||
|
*/
|
||
|
scalePointArray: function(points, scale)
|
||
|
{
|
||
|
var result = [];
|
||
|
|
||
|
if (points != null)
|
||
|
{
|
||
|
for (var i = 0; i < points.length; i++)
|
||
|
{
|
||
|
if (points[i] != null)
|
||
|
{
|
||
|
var pt = new mxPoint(Math.round(points[i].x / scale * 10) / 10,
|
||
|
Math.round(points[i].y / scale * 10) / 10);
|
||
|
result[i] = pt;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
result[i] = null;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
result = null;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Function: scaleCellState
|
||
|
*
|
||
|
* Scales an <mxCellState>
|
||
|
*
|
||
|
* Parameters:
|
||
|
*
|
||
|
* state - <mxCellState> to scale
|
||
|
* scale - the scaling to divide by
|
||
|
*
|
||
|
*/
|
||
|
scaleCellState: function(state, scale)
|
||
|
{
|
||
|
var result = null;
|
||
|
|
||
|
if (state != null)
|
||
|
{
|
||
|
result = state.clone();
|
||
|
result.setRect(Math.round(state.x / scale * 10) / 10,
|
||
|
Math.round(state.y / scale * 10) / 10,
|
||
|
Math.round(state.width / scale * 10) / 10,
|
||
|
Math.round(state.height / scale * 10) / 10);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
result = null;
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
},
|
||
|
|
||
|
/**
|
||
|
* Function: OrthConnector
|
||
|
*
|
||
|
* Implements a local orthogonal router between the given
|
||
|
* cells.
|
||
|
*
|
||
|
* Parameters:
|
||
|
*
|
||
|
* state - <mxCellState> that represents the edge to be updated.
|
||
|
* sourceScaled - <mxCellState> that represents the source terminal.
|
||
|
* targetScaled - <mxCellState> that represents the target terminal.
|
||
|
* controlHints - List of relative control points.
|
||
|
* result - Array of <mxPoints> that represent the actual points of the
|
||
|
* edge.
|
||
|
*
|
||
|
*/
|
||
|
OrthConnector: function(state, sourceScaled, targetScaled, controlHints, result)
|
||
|
{
|
||
|
var graph = state.view.graph;
|
||
|
var sourceEdge = source == null ? false : graph.getModel().isEdge(source.cell);
|
||
|
var targetEdge = target == null ? false : graph.getModel().isEdge(target.cell);
|
||
|
|
||
|
var pts = mxEdgeStyle.scalePointArray(state.absolutePoints, state.view.scale);
|
||
|
var source = mxEdgeStyle.scaleCellState(sourceScaled, state.view.scale);
|
||
|
var target = mxEdgeStyle.scaleCellState(targetScaled, state.view.scale);
|
||
|
|
||
|
var p0 = pts[0];
|
||
|
var pe = pts[pts.length-1];
|
||
|
|
||
|
var sourceX = source != null ? source.x : p0.x;
|
||
|
var sourceY = source != null ? source.y : p0.y;
|
||
|
var sourceWidth = source != null ? source.width : 0;
|
||
|
var sourceHeight = source != null ? source.height : 0;
|
||
|
|
||
|
var targetX = target != null ? target.x : pe.x;
|
||
|
var targetY = target != null ? target.y : pe.y;
|
||
|
var targetWidth = target != null ? target.width : 0;
|
||
|
var targetHeight = target != null ? target.height : 0;
|
||
|
|
||
|
var sourceBuffer = mxEdgeStyle.getJettySize(state, true);
|
||
|
var targetBuffer = mxEdgeStyle.getJettySize(state, false);
|
||
|
|
||
|
//console.log('sourceBuffer', sourceBuffer);
|
||
|
//console.log('targetBuffer', targetBuffer);
|
||
|
// Workaround for loop routing within buffer zone
|
||
|
if (source != null && target == source)
|
||
|
{
|
||
|
targetBuffer = Math.max(sourceBuffer, targetBuffer);
|
||
|
sourceBuffer = targetBuffer;
|
||
|
}
|
||
|
|
||
|
var totalBuffer = targetBuffer + sourceBuffer;
|
||
|
// console.log('totalBuffer', totalBuffer);
|
||
|
var tooShort = false;
|
||
|
|
||
|
// Checks minimum distance for fixed points and falls back to segment connector
|
||
|
if (p0 != null && pe != null)
|
||
|
{
|
||
|
var dx = pe.x - p0.x;
|
||
|
var dy = pe.y - p0.y;
|
||
|
|
||
|
tooShort = dx * dx + dy * dy < totalBuffer * totalBuffer;
|
||
|
}
|
||
|
|
||
|
if (tooShort || (mxEdgeStyle.orthPointsFallback && (controlHints != null &&
|
||
|
controlHints.length > 0)) || sourceEdge || targetEdge)
|
||
|
{
|
||
|
mxEdgeStyle.SegmentConnector(state, sourceScaled, targetScaled, controlHints, result);
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Determine the side(s) of the source and target vertices
|
||
|
// that the edge may connect to
|
||
|
// portConstraint [source, target]
|
||
|
var portConstraint = [mxConstants.DIRECTION_MASK_ALL, mxConstants.DIRECTION_MASK_ALL];
|
||
|
var rotation = 0;
|
||
|
|
||
|
if (source != null)
|
||
|
{
|
||
|
portConstraint[0] = mxUtils.getPortConstraints(source, state, true,
|
||
|
mxConstants.DIRECTION_MASK_ALL);
|
||
|
rotation = mxUtils.getValue(source.style, mxConstants.STYLE_ROTATION, 0);
|
||
|
|
||
|
//console.log('source rotation', rotation);
|
||
|
|
||
|
if (rotation != 0)
|
||
|
{
|
||
|
var newRect = mxUtils.getBoundingBox(new mxRectangle(sourceX, sourceY, sourceWidth, sourceHeight), rotation);
|
||
|
sourceX = newRect.x;
|
||
|
sourceY = newRect.y;
|
||
|
sourceWidth = newRect.width;
|
||
|
sourceHeight = newRect.height;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (target != null)
|
||
|
{
|
||
|
portConstraint[1] = mxUtils.getPortConstraints(target, state, false,
|
||
|
mxConstants.DIRECTION_MASK_ALL);
|
||
|
rotation = mxUtils.getValue(target.style, mxConstants.STYLE_ROTATION, 0);
|
||
|
|
||
|
//console.log('target rotation', rotation);
|
||
|
|
||
|
if (rotation != 0)
|
||
|
{
|
||
|
var newRect = mxUtils.getBoundingBox(new mxRectangle(targetX, targetY, targetWidth, targetHeight), rotation);
|
||
|
targetX = newRect.x;
|
||
|
targetY = newRect.y;
|
||
|
targetWidth = newRect.width;
|
||
|
targetHeight = newRect.height;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//console.log('source' , sourceX, sourceY, sourceWidth, sourceHeight);
|
||
|
//console.log('targetX' , targetX, targetY, targetWidth, targetHeight);
|
||
|
|
||
|
var dir = [0, 0];
|
||
|
|
||
|
// Work out which faces of the vertices present against each other
|
||
|
// in a way that would allow a 3-segment connection if port constraints
|
||
|
// permitted.
|
||
|
// geo -> [source, target] [x, y, width, height]
|
||
|
var geo = [ [sourceX, sourceY, sourceWidth, sourceHeight] ,
|
||
|
[targetX, targetY, targetWidth, targetHeight] ];
|
||
|
var buffer = [sourceBuffer, targetBuffer];
|
||
|
|
||
|
for (var i = 0; i < 2; i++)
|
||
|
{
|
||
|
mxEdgeStyle.limits[i][1] = geo[i][0] - buffer[i];
|
||
|
mxEdgeStyle.limits[i][2] = geo[i][1] - buffer[i];
|
||
|
mxEdgeStyle.limits[i][4] = geo[i][0] + geo[i][2] + buffer[i];
|
||
|
mxEdgeStyle.limits[i][8] = geo[i][1] + geo[i][3] + buffer[i];
|
||
|
}
|
||
|
|
||
|
// Work out which quad the target is in
|
||
|
var sourceCenX = geo[0][0] + geo[0][2] / 2.0;
|
||
|
var sourceCenY = geo[0][1] + geo[0][3] / 2.0;
|
||
|
var targetCenX = geo[1][0] + geo[1][2] / 2.0;
|
||
|
var targetCenY = geo[1][1] + geo[1][3] / 2.0;
|
||
|
|
||
|
var dx = sourceCenX - targetCenX;
|
||
|
var dy = sourceCenY - targetCenY;
|
||
|
|
||
|
var quad = 0;
|
||
|
|
||
|
// 0 | 1
|
||
|
// -----
|
||
|
// 3 | 2
|
||
|
|
||
|
if (dx < 0)
|
||
|
{
|
||
|
if (dy < 0)
|
||
|
{
|
||
|
quad = 2;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
quad = 1;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (dy <= 0)
|
||
|
{
|
||
|
quad = 3;
|
||
|
|
||
|
// Special case on x = 0 and negative y
|
||
|
if (dx == 0)
|
||
|
{
|
||
|
quad = 2;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//console.log('quad', quad);
|
||
|
|
||
|
// Check for connection constraints
|
||
|
var currentTerm = null;
|
||
|
|
||
|
if (source != null)
|
||
|
{
|
||
|
currentTerm = p0;
|
||
|
}
|
||
|
|
||
|
var constraint = [ [0.5, 0.5] , [0.5, 0.5] ];
|
||
|
|
||
|
for (var i = 0; i < 2; i++)
|
||
|
{
|
||
|
if (currentTerm != null)
|
||
|
{
|
||
|
constraint[i][0] = (currentTerm.x - geo[i][0]) / geo[i][2];
|
||
|
|
||
|
if (Math.abs(currentTerm.x - geo[i][0]) <= 1)
|
||
|
{
|
||
|
dir[i] = mxConstants.DIRECTION_MASK_WEST;
|
||
|
}
|
||
|
else if (Math.abs(currentTerm.x - geo[i][0] - geo[i][2]) <= 1)
|
||
|
{
|
||
|
dir[i] = mxConstants.DIRECTION_MASK_EAST;
|
||
|
}
|
||
|
|
||
|
constraint[i][1] = (currentTerm.y - geo[i][1]) / geo[i][3];
|
||
|
|
||
|
if (Math.abs(currentTerm.y - geo[i][1]) <= 1)
|
||
|
{
|
||
|
dir[i] = mxConstants.DIRECTION_MASK_NORTH;
|
||
|
}
|
||
|
else if (Math.abs(currentTerm.y - geo[i][1] - geo[i][3]) <= 1)
|
||
|
{
|
||
|
dir[i] = mxConstants.DIRECTION_MASK_SOUTH;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
currentTerm = null;
|
||
|
|
||
|
if (target != null)
|
||
|
{
|
||
|
currentTerm = pe;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
var sourceTopDist = geo[0][1] - (geo[1][1] + geo[1][3]);
|
||
|
var sourceLeftDist = geo[0][0] - (geo[1][0] + geo[1][2]);
|
||
|
var sourceBottomDist = geo[1][1] - (geo[0][1] + geo[0][3]);
|
||
|
var sourceRightDist = geo[1][0] - (geo[0][0] + geo[0][2]);
|
||
|
|
||
|
mxEdgeStyle.vertexSeperations[1] = Math.max(sourceLeftDist - totalBuffer, 0);
|
||
|
mxEdgeStyle.vertexSeperations[2] = Math.max(sourceTopDist - totalBuffer, 0);
|
||
|
mxEdgeStyle.vertexSeperations[4] = Math.max(sourceBottomDist - totalBuffer, 0);
|
||
|
mxEdgeStyle.vertexSeperations[3] = Math.max(sourceRightDist - totalBuffer, 0);
|
||
|
|
||
|
//==============================================================
|
||
|
// Start of source and target direction determination
|
||
|
|
||
|
// Work through the preferred orientations by relative positioning
|
||
|
// of the vertices and list them in preferred and available order
|
||
|
|
||
|
var dirPref = [];
|
||
|
var horPref = [];
|
||
|
var vertPref = [];
|
||
|
|
||
|
horPref[0] = (sourceLeftDist >= sourceRightDist) ? mxConstants.DIRECTION_MASK_WEST
|
||
|
: mxConstants.DIRECTION_MASK_EAST;
|
||
|
vertPref[0] = (sourceTopDist >= sourceBottomDist) ? mxConstants.DIRECTION_MASK_NORTH
|
||
|
: mxConstants.DIRECTION_MASK_SOUTH;
|
||
|
|
||
|
horPref[1] = mxUtils.reversePortConstraints(horPref[0]);
|
||
|
vertPref[1] = mxUtils.reversePortConstraints(vertPref[0]);
|
||
|
|
||
|
var preferredHorizDist = sourceLeftDist >= sourceRightDist ? sourceLeftDist
|
||
|
: sourceRightDist;
|
||
|
var preferredVertDist = sourceTopDist >= sourceBottomDist ? sourceTopDist
|
||
|
: sourceBottomDist;
|
||
|
|
||
|
var prefOrdering = [ [0, 0] , [0, 0] ];
|
||
|
var preferredOrderSet = false;
|
||
|
|
||
|
// If the preferred port isn't available, switch it
|
||
|
for (var i = 0; i < 2; i++)
|
||
|
{
|
||
|
if (dir[i] != 0x0)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ((horPref[i] & portConstraint[i]) == 0)
|
||
|
{
|
||
|
horPref[i] = mxUtils.reversePortConstraints(horPref[i]);
|
||
|
}
|
||
|
|
||
|
if ((vertPref[i] & portConstraint[i]) == 0)
|
||
|
{
|
||
|
vertPref[i] = mxUtils
|
||
|
.reversePortConstraints(vertPref[i]);
|
||
|
}
|
||
|
|
||
|
prefOrdering[i][0] = vertPref[i];
|
||
|
prefOrdering[i][1] = horPref[i];
|
||
|
}
|
||
|
|
||
|
if (preferredVertDist > 0
|
||
|
&& preferredHorizDist > 0)
|
||
|
{
|
||
|
// Possibility of two segment edge connection
|
||
|
if (((horPref[0] & portConstraint[0]) > 0)
|
||
|
&& ((vertPref[1] & portConstraint[1]) > 0))
|
||
|
{
|
||
|
prefOrdering[0][0] = horPref[0];
|
||
|
prefOrdering[0][1] = vertPref[0];
|
||
|
prefOrdering[1][0] = vertPref[1];
|
||
|
prefOrdering[1][1] = horPref[1];
|
||
|
preferredOrderSet = true;
|
||
|
}
|
||
|
else if (((vertPref[0] & portConstraint[0]) > 0)
|
||
|
&& ((horPref[1] & portConstraint[1]) > 0))
|
||
|
{
|
||
|
prefOrdering[0][0] = vertPref[0];
|
||
|
prefOrdering[0][1] = horPref[0];
|
||
|
prefOrdering[1][0] = horPref[1];
|
||
|
prefOrdering[1][1] = vertPref[1];
|
||
|
preferredOrderSet = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (preferredVertDist > 0 && !preferredOrderSet)
|
||
|
{
|
||
|
prefOrdering[0][0] = vertPref[0];
|
||
|
prefOrdering[0][1] = horPref[0];
|
||
|
prefOrdering[1][0] = vertPref[1];
|
||
|
prefOrdering[1][1] = horPref[1];
|
||
|
preferredOrderSet = true;
|
||
|
|
||
|
}
|
||
|
|
||
|
if (preferredHorizDist > 0 && !preferredOrderSet)
|
||
|
{
|
||
|
prefOrdering[0][0] = horPref[0];
|
||
|
prefOrdering[0][1] = vertPref[0];
|
||
|
prefOrdering[1][0] = horPref[1];
|
||
|
prefOrdering[1][1] = vertPref[1];
|
||
|
preferredOrderSet = true;
|
||
|
}
|
||
|
|
||
|
// The source and target prefs are now an ordered list of
|
||
|
// the preferred port selections
|
||
|
// If the list contains gaps, compact it
|
||
|
|
||
|
for (var i = 0; i < 2; i++)
|
||
|
{
|
||
|
if (dir[i] != 0x0)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if ((prefOrdering[i][0] & portConstraint[i]) == 0)
|
||
|
{
|
||
|
prefOrdering[i][0] = prefOrdering[i][1];
|
||
|
}
|
||
|
|
||
|
dirPref[i] = prefOrdering[i][0] & portConstraint[i];
|
||
|
dirPref[i] |= (prefOrdering[i][1] & portConstraint[i]) << 8;
|
||
|
dirPref[i] |= (prefOrdering[1 - i][i] & portConstraint[i]) << 16;
|
||
|
dirPref[i] |= (prefOrdering[1 - i][1 - i] & portConstraint[i]) << 24;
|
||
|
|
||
|
if ((dirPref[i] & 0xF) == 0)
|
||
|
{
|
||
|
dirPref[i] = dirPref[i] << 8;
|
||
|
}
|
||
|
|
||
|
if ((dirPref[i] & 0xF00) == 0)
|
||
|
{
|
||
|
dirPref[i] = (dirPref[i] & 0xF) | dirPref[i] >> 8;
|
||
|
}
|
||
|
|
||
|
if ((dirPref[i] & 0xF0000) == 0)
|
||
|
{
|
||
|
dirPref[i] = (dirPref[i] & 0xFFFF)
|
||
|
| ((dirPref[i] & 0xF000000) >> 8);
|
||
|
}
|
||
|
|
||
|
dir[i] = dirPref[i] & 0xF;
|
||
|
|
||
|
if (portConstraint[i] == mxConstants.DIRECTION_MASK_WEST
|
||
|
|| portConstraint[i] == mxConstants.DIRECTION_MASK_NORTH
|
||
|
|| portConstraint[i] == mxConstants.DIRECTION_MASK_EAST
|
||
|
|| portConstraint[i] == mxConstants.DIRECTION_MASK_SOUTH)
|
||
|
{
|
||
|
dir[i] = portConstraint[i];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//==============================================================
|
||
|
// End of source and target direction determination
|
||
|
|
||
|
var sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3
|
||
|
: dir[0];
|
||
|
var targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3
|
||
|
: dir[1];
|
||
|
|
||
|
sourceIndex -= quad;
|
||
|
targetIndex -= quad;
|
||
|
|
||
|
if (sourceIndex < 1)
|
||
|
{
|
||
|
sourceIndex += 4;
|
||
|
}
|
||
|
|
||
|
if (targetIndex < 1)
|
||
|
{
|
||
|
targetIndex += 4;
|
||
|
}
|
||
|
|
||
|
var routePattern = mxEdgeStyle.routePatterns[sourceIndex - 1][targetIndex - 1];
|
||
|
|
||
|
//console.log('routePattern', routePattern);
|
||
|
|
||
|
mxEdgeStyle.wayPoints1[0][0] = geo[0][0];
|
||
|
mxEdgeStyle.wayPoints1[0][1] = geo[0][1];
|
||
|
|
||
|
switch (dir[0])
|
||
|
{
|
||
|
case mxConstants.DIRECTION_MASK_WEST:
|
||
|
mxEdgeStyle.wayPoints1[0][0] -= sourceBuffer;
|
||
|
mxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3];
|
||
|
break;
|
||
|
case mxConstants.DIRECTION_MASK_SOUTH:
|
||
|
mxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2];
|
||
|
mxEdgeStyle.wayPoints1[0][1] += geo[0][3] + sourceBuffer;
|
||
|
break;
|
||
|
case mxConstants.DIRECTION_MASK_EAST:
|
||
|
mxEdgeStyle.wayPoints1[0][0] += geo[0][2] + sourceBuffer;
|
||
|
mxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3];
|
||
|
break;
|
||
|
case mxConstants.DIRECTION_MASK_NORTH:
|
||
|
mxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2];
|
||
|
mxEdgeStyle.wayPoints1[0][1] -= sourceBuffer;
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
var currentIndex = 0;
|
||
|
|
||
|
// Orientation, 0 horizontal, 1 vertical
|
||
|
var lastOrientation = (dir[0] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0
|
||
|
: 1;
|
||
|
var initialOrientation = lastOrientation;
|
||
|
var currentOrientation = 0;
|
||
|
|
||
|
for (var i = 0; i < routePattern.length; i++)
|
||
|
{
|
||
|
var nextDirection = routePattern[i] & 0xF;
|
||
|
|
||
|
// Rotate the index of this direction by the quad
|
||
|
// to get the real direction
|
||
|
var directionIndex = nextDirection == mxConstants.DIRECTION_MASK_EAST ? 3
|
||
|
: nextDirection;
|
||
|
|
||
|
directionIndex += quad;
|
||
|
|
||
|
if (directionIndex > 4)
|
||
|
{
|
||
|
directionIndex -= 4;
|
||
|
}
|
||
|
|
||
|
var direction = mxEdgeStyle.dirVectors[directionIndex - 1];
|
||
|
|
||
|
currentOrientation = (directionIndex % 2 > 0) ? 0 : 1;
|
||
|
// Only update the current index if the point moved
|
||
|
// in the direction of the current segment move,
|
||
|
// otherwise the same point is moved until there is
|
||
|
// a segment direction change
|
||
|
if (currentOrientation != lastOrientation)
|
||
|
{
|
||
|
currentIndex++;
|
||
|
// Copy the previous way point into the new one
|
||
|
// We can't base the new position on index - 1
|
||
|
// because sometime elbows turn out not to exist,
|
||
|
// then we'd have to rewind.
|
||
|
mxEdgeStyle.wayPoints1[currentIndex][0] = mxEdgeStyle.wayPoints1[currentIndex - 1][0];
|
||
|
mxEdgeStyle.wayPoints1[currentIndex][1] = mxEdgeStyle.wayPoints1[currentIndex - 1][1];
|
||
|
}
|
||
|
|
||
|
var tar = (routePattern[i] & mxEdgeStyle.TARGET_MASK) > 0;
|
||
|
var sou = (routePattern[i] & mxEdgeStyle.SOURCE_MASK) > 0;
|
||
|
var side = (routePattern[i] & mxEdgeStyle.SIDE_MASK) >> 5;
|
||
|
side = side << quad;
|
||
|
|
||
|
if (side > 0xF)
|
||
|
{
|
||
|
side = side >> 4;
|
||
|
}
|
||
|
|
||
|
var center = (routePattern[i] & mxEdgeStyle.CENTER_MASK) > 0;
|
||
|
|
||
|
if ((sou || tar) && side < 9)
|
||
|
{
|
||
|
var limit = 0;
|
||
|
var souTar = sou ? 0 : 1;
|
||
|
|
||
|
if (center && currentOrientation == 0)
|
||
|
{
|
||
|
limit = geo[souTar][0] + constraint[souTar][0] * geo[souTar][2];
|
||
|
}
|
||
|
else if (center)
|
||
|
{
|
||
|
limit = geo[souTar][1] + constraint[souTar][1] * geo[souTar][3];
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
limit = mxEdgeStyle.limits[souTar][side];
|
||
|
}
|
||
|
|
||
|
if (currentOrientation == 0)
|
||
|
{
|
||
|
var lastX = mxEdgeStyle.wayPoints1[currentIndex][0];
|
||
|
var deltaX = (limit - lastX) * direction[0];
|
||
|
|
||
|
if (deltaX > 0)
|
||
|
{
|
||
|
mxEdgeStyle.wayPoints1[currentIndex][0] += direction[0]
|
||
|
* deltaX;
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
var lastY = mxEdgeStyle.wayPoints1[currentIndex][1];
|
||
|
var deltaY = (limit - lastY) * direction[1];
|
||
|
|
||
|
if (deltaY > 0)
|
||
|
{
|
||
|
mxEdgeStyle.wayPoints1[currentIndex][1] += direction[1]
|
||
|
* deltaY;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
else if (center)
|
||
|
{
|
||
|
// Which center we're travelling to depend on the current direction
|
||
|
mxEdgeStyle.wayPoints1[currentIndex][0] += direction[0]
|
||
|
* Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2);
|
||
|
mxEdgeStyle.wayPoints1[currentIndex][1] += direction[1]
|
||
|
* Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2);
|
||
|
}
|
||
|
|
||
|
if (currentIndex > 0
|
||
|
&& mxEdgeStyle.wayPoints1[currentIndex][currentOrientation] == mxEdgeStyle.wayPoints1[currentIndex - 1][currentOrientation])
|
||
|
{
|
||
|
currentIndex--;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
lastOrientation = currentOrientation;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
for (var i = 0; i <= currentIndex; i++)
|
||
|
{
|
||
|
if (i == currentIndex)
|
||
|
{
|
||
|
// Last point can cause last segment to be in
|
||
|
// same direction as jetty/approach. If so,
|
||
|
// check the number of points is consistent
|
||
|
// with the relative orientation of source and target
|
||
|
// jx. Same orientation requires an even
|
||
|
// number of turns (points), different requires
|
||
|
// odd.
|
||
|
var targetOrientation = (dir[1] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0
|
||
|
: 1;
|
||
|
var sameOrient = targetOrientation == initialOrientation ? 0 : 1;
|
||
|
|
||
|
// (currentIndex + 1) % 2 is 0 for even number of points,
|
||
|
// 1 for odd
|
||
|
if (sameOrient != (currentIndex + 1) % 2)
|
||
|
{
|
||
|
// The last point isn't required
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
result.push(new mxPoint(Math.round(mxEdgeStyle.wayPoints1[i][0] * state.view.scale * 10) / 10,
|
||
|
Math.round(mxEdgeStyle.wayPoints1[i][1] * state.view.scale * 10) / 10));
|
||
|
}
|
||
|
|
||
|
//console.log(result);
|
||
|
|
||
|
// Removes duplicates
|
||
|
var index = 1;
|
||
|
|
||
|
while (index < result.length)
|
||
|
{
|
||
|
if (result[index - 1] == null || result[index] == null ||
|
||
|
result[index - 1].x != result[index].x ||
|
||
|
result[index - 1].y != result[index].y)
|
||
|
{
|
||
|
index++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
result.splice(index, 1);
|
||
|
}
|
||
|
}
|
||
|
},
|
||
|
|
||
|
getRoutePattern: function(dir, quad, dx, dy)
|
||
|
{
|
||
|
var sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3
|
||
|
: dir[0];
|
||
|
var targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3
|
||
|
: dir[1];
|
||
|
|
||
|
sourceIndex -= quad;
|
||
|
targetIndex -= quad;
|
||
|
|
||
|
if (sourceIndex < 1)
|
||
|
{
|
||
|
sourceIndex += 4;
|
||
|
}
|
||
|
if (targetIndex < 1)
|
||
|
{
|
||
|
targetIndex += 4;
|
||
|
}
|
||
|
|
||
|
var result = routePatterns[sourceIndex - 1][targetIndex - 1];
|
||
|
|
||
|
if (dx == 0 || dy == 0)
|
||
|
{
|
||
|
if (inlineRoutePatterns[sourceIndex - 1][targetIndex - 1] != null)
|
||
|
{
|
||
|
result = inlineRoutePatterns[sourceIndex - 1][targetIndex - 1];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
};
|