Last active
December 24, 2015 14:09
-
-
Save davidjgraph/6810346 to your computer and use it in GitHub Desktop.
mxEdgeStyle.js for mxGraph 2.2.0.3
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* $Id: mxEdgeStyle.js,v 1.3 2013/10/03 14:17:11 david Exp $ | |
* Copyright (c) 2006-2010, JGraph Ltd | |
*/ | |
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 (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) | |
{ | |
isSourceLeft = constraint == mxConstants.DIRECTION_MASK_WEST; | |
} | |
else | |
{ | |
var sourceGeometry = graph.getCellGeometry(source.cell); | |
if (sourceGeometry.relative) | |
{ | |
isSourceLeft = sourceGeometry.x <= 0.5; | |
} | |
else if (target != null) | |
{ | |
isSourceLeft = target.x + target.width < source.x; | |
} | |
} | |
} | |
else | |
{ | |
return; | |
} | |
var isTargetLeft = true; | |
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) | |
{ | |
isTargetLeft = constraint == mxConstants.DIRECTION_MASK_WEST; | |
} | |
else | |
{ | |
var targetGeometry = graph.getCellGeometry(target.cell); | |
if (targetGeometry.relative) | |
{ | |
isTargetLeft = targetGeometry.x <= 0.5; | |
} | |
else if (source != null) | |
{ | |
isTargetLeft = source.x + source.width < target.x; | |
} | |
} | |
} | |
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) | |
{ | |
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 : 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 : 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. | |
*/ | |
SegmentConnector: function(state, source, target, hints, result) | |
{ | |
// Creates array of all way- and terminalpoints | |
var pts = state.absolutePoints; | |
var horizontal = true; | |
var hint = null; | |
// 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 (hints != null && hints.length > 0) | |
{ | |
hint = state.view.transformControlPoint(state, hints[0]); | |
var currentTerm = source; | |
var currentPt = pts[0]; | |
var hozChan = false; | |
var vertChan = false; | |
var currentHint = hint; | |
var hintsLen = hints.length; | |
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 (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]; | |
currentHint = state.view.transformControlPoint(state, hints[hintsLen - 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)))) | |
{ | |
result.push(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)))) | |
{ | |
result.push(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 = state.view.transformControlPoint(state, hints[i]); | |
// mxLog.show(); | |
// mxLog.debug('hint', i, hint.x, hint.y); | |
if (horizontal) | |
{ | |
pt.y = hint.y; | |
} | |
else | |
{ | |
pt.x = hint.x; | |
} | |
result.push(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 (horizontal && ((pts[lastInx] != null && pts[lastInx].y != hint.y) || | |
(pts[lastInx] == null && target != null && | |
(hint.y < target.y || hint.y > target.y + target.height)))) | |
{ | |
result.push(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)))) | |
{ | |
result.push(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 && mxUtils.contains(source, result[1].x, result[1].y)) | |
{ | |
result = result.splice(1, 1); | |
} | |
} | |
// Removes bends inside the target terminal | |
if (pts[lastInx] == null && target != null) | |
{ | |
while (result.length > 1 && mxUtils.contains(target, result[result.length - 1].x, result[result.length - 1].y)) | |
{ | |
result = result.splice(result.length - 1, 1); | |
} | |
} | |
}, | |
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, | |
/** | |
* Function: OrthConnector | |
* | |
* Implements a local orthogonal router between the given | |
* cells. | |
*/ | |
OrthConnector: function(state, source, target, points, 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); | |
if (mxEdgeStyle.orthPointsFallback && (points != null && points.length > 0) || (sourceEdge) || (targetEdge)) | |
{ | |
mxEdgeStyle.SegmentConnector(state, source, target, points, result); | |
return; | |
} | |
var pts = state.absolutePoints; | |
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 : 1; | |
var sourceHeight = source != null ? source.height : 1; | |
var targetX = target != null ? target.x : pe.x; | |
var targetY = target != null ? target.y : pe.y; | |
var targetWidth = target != null ? target.width : 1; | |
var targetHeight = target != null ? target.height : 1; | |
var scaledOrthBuffer = state.view.scale * mxEdgeStyle.orthBuffer; | |
// 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]; | |
if (source != null) | |
{ | |
portConstraint[0] = mxUtils.getPortConstraints(source, state, true, | |
mxConstants.DIRECTION_MASK_ALL); | |
} | |
if (target != null) | |
{ | |
portConstraint[1] = mxUtils.getPortConstraints(target, state, false, | |
mxConstants.DIRECTION_MASK_ALL); | |
} | |
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] ]; | |
for (var i = 0; i < 2; i++) | |
{ | |
mxEdgeStyle.limits[i][1] = geo[i][0] - scaledOrthBuffer; | |
mxEdgeStyle.limits[i][2] = geo[i][1] - scaledOrthBuffer; | |
mxEdgeStyle.limits[i][4] = geo[i][0] + geo[i][2] + scaledOrthBuffer; | |
mxEdgeStyle.limits[i][8] = geo[i][1] + geo[i][3] + scaledOrthBuffer; | |
} | |
// 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; | |
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; | |
} | |
} | |
} | |
// 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 (constraint[i][0] < 0.01) | |
{ | |
dir[i] = mxConstants.DIRECTION_MASK_WEST; | |
} | |
else if (constraint[i][0] > 0.99) | |
{ | |
dir[i] = mxConstants.DIRECTION_MASK_EAST; | |
} | |
constraint[i][1] = (currentTerm.y - geo[i][1]) / geo[i][3]; | |
if (constraint[i][1] < 0.01) | |
{ | |
dir[i] = mxConstants.DIRECTION_MASK_NORTH; | |
} | |
else if (constraint[i][1] > 0.99) | |
{ | |
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 - 2 * scaledOrthBuffer, 0); | |
mxEdgeStyle.vertexSeperations[2] = Math.max(sourceTopDist - 2 * scaledOrthBuffer, | |
0); | |
mxEdgeStyle.vertexSeperations[4] = Math.max(sourceBottomDist - 2 | |
* scaledOrthBuffer, 0); | |
mxEdgeStyle.vertexSeperations[3] = Math.max(sourceRightDist - 2 | |
* scaledOrthBuffer, 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 > scaledOrthBuffer * 2 | |
&& preferredHorizDist > scaledOrthBuffer * 2) | |
{ | |
// 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 > scaledOrthBuffer * 2 && !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 > scaledOrthBuffer * 2 && !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 | |
// It the list can contain 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]; | |
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] -= scaledOrthBuffer; | |
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] + scaledOrthBuffer; | |
break; | |
case mxConstants.DIRECTION_MASK_EAST: | |
mxEdgeStyle.wayPoints1[0][0] += geo[0][2] + scaledOrthBuffer; | |
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] -= scaledOrthBuffer; | |
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 | |
// jettys. 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(mxEdgeStyle.wayPoints1[i][0], mxEdgeStyle.wayPoints1[i][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; | |
} | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment