diff --git a/demo/right-angle-playground/css/style.css b/demo/right-angle-playground/css/style.css
new file mode 100644
index 000000000..947734566
--- /dev/null
+++ b/demo/right-angle-playground/css/style.css
@@ -0,0 +1,9 @@
+body {
+ margin: 0;
+ padding: 0;
+}
+
+#paper {
+ position: absolute;
+ inset: 0 0 0 0;
+}
diff --git a/demo/right-angle-playground/index.html b/demo/right-angle-playground/index.html
new file mode 100644
index 000000000..85ba8a3cf
--- /dev/null
+++ b/demo/right-angle-playground/index.html
@@ -0,0 +1,18 @@
+
+
+
+
+ Right angle router playground
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/demo/right-angle-playground/src/index.js b/demo/right-angle-playground/src/index.js
new file mode 100644
index 000000000..70bb8825b
--- /dev/null
+++ b/demo/right-angle-playground/src/index.js
@@ -0,0 +1,170 @@
+const { dia, shapes, linkTools, elementTools } = joint;
+class ResizeTool extends elementTools.Control {
+ getPosition(view) {
+ const model = view.model;
+ const { width, height } = model.size();
+ return { x: width, y: height };
+ }
+
+ setPosition(view, coordinates) {
+ const model = view.model;
+ model.resize(
+ Math.max(Math.round(coordinates.x / 2) * 2, 10),
+ Math.max(Math.round(coordinates.y / 2) * 2, 10)
+ );
+ }
+}
+
+const graph = new dia.Graph();
+
+const paper = new dia.Paper({
+ el: document.getElementById('paper'),
+ width: '100%',
+ height: '100%',
+ gridSize: 10,
+ async: true,
+ frozen: true,
+ model: graph,
+ defaultRouter: { name: 'rightAngle', args: { useVertices: true }},
+ defaultConnector: { name: 'rounded' },
+ background: {
+ color: '#151D29'
+ },
+ defaultLinkAnchor: {
+ name: 'connectionRatio',
+ args: {
+ ratio: 0.25
+ }
+ }
+});
+
+const rect = new shapes.standard.Rectangle({
+ position: { x: 120, y: 120 },
+ size: { width: 220, height: 60 },
+ attrs: {
+ body: {
+ stroke: 'none',
+ fill: '#DF423D',
+ rx: 10,
+ ry: 10,
+ }
+ }
+});
+
+const rect2 = rect.clone();
+
+rect2.resize(60, 220);
+rect2.position(400, 700);
+
+const link = new shapes.standard.Link({
+ attrs: {
+ line: {
+ stroke: 'white'
+ }
+ }
+});
+
+const link2 = link.clone();
+
+link.source({ id: rect.id, anchor: { name: 'top' }});
+link.target({ id: rect2.id, anchor: { name: 'right' }});
+link.vertices([
+ { x: 370, y: 420 },
+ { x: 500, y: 500 }
+]);
+
+link2.source({ x: 670, y: 100 });
+link2.target({ x: 800, y: 800 });
+link2.vertices([
+ { x: 670, y: 420 },
+ { x: 800, y: 500 },
+]);
+
+const link3 = link.clone();
+link3.attr('line/stroke', '#DF423D');
+link3.source({ x: 1000, y: 600 });
+link3.target({ id: link2.id });
+link3.vertices([{ x: 900, y: 400 }]);
+
+graph.addCells([rect, rect2, link, link2, link3]);
+
+rect.findView(paper).addTools(
+ new dia.ToolsView({
+ tools: [
+ new ResizeTool({
+ selector: 'body'
+ })
+ ]
+ })
+);
+
+rect2.findView(paper).addTools(
+ new dia.ToolsView({
+ tools: [
+ new ResizeTool({
+ selector: 'body'
+ })
+ ]
+ })
+);
+
+const linkToolsView = new dia.ToolsView({
+ tools: [
+ new linkTools.Vertices({
+ focusOpacity: 0.5,
+ }),
+ new linkTools.TargetAnchor({
+ focusOpacity: 0.5,
+ scale: 1.2
+ }),
+ new linkTools.SourceAnchor({
+ focusOpacity: 0.5,
+ scale: 1.2
+ }),
+ ]
+});
+
+link.findView(paper).addTools(linkToolsView);
+
+const link2ToolsView = new dia.ToolsView({
+ tools: [
+ new linkTools.Vertices({
+ focusOpacity: 0.5
+ }),
+ new linkTools.SourceArrowhead({
+ focusOpacity: 0.5
+ }),
+ new linkTools.TargetArrowhead({
+ focusOpacity: 0.5
+ })
+ ]
+});
+
+link2.findView(paper).addTools(link2ToolsView);
+
+const link3ToolsView = new dia.ToolsView({
+ tools: [
+ new linkTools.Vertices({
+ focusOpacity: 0.5
+ })
+ ]
+});
+
+link3.findView(paper).addTools(link3ToolsView);
+
+function scaleToFit() {
+ const graphBBox = graph.getBBox();
+ paper.scaleContentToFit({
+ contentArea: graphBBox.clone().inflate(0, 100)
+ });
+ const { sy } = paper.scale();
+ const area = paper.getArea();
+ const yTop = area.height / 2 - graphBBox.y - graphBBox.height / 2;
+ const xLeft = area.width / 2 - graphBBox.x - graphBBox.width / 2;
+ paper.translate(xLeft * sy, yTop * sy);
+}
+
+window.addEventListener('resize', () => scaleToFit());
+scaleToFit();
+
+paper.unfreeze();
\ No newline at end of file
diff --git a/src/routers/rightAngle.mjs b/src/routers/rightAngle.mjs
index 2ff1e19ea..f8f1be26d 100644
--- a/src/routers/rightAngle.mjs
+++ b/src/routers/rightAngle.mjs
@@ -12,75 +12,36 @@ const Directions = {
const DEFINED_DIRECTIONS = [Directions.LEFT, Directions.RIGHT, Directions.TOP, Directions.BOTTOM];
-function getDirectionForLinkConnection(linkOrigin, connectionPoint, linkView) {
- const tangent = linkView.getTangentAtLength(linkView.getClosestPointLength(connectionPoint));
- const roundedAngle = Math.round(tangent.angle() / 90) * 90;
-
- switch (roundedAngle) {
- case 0:
- case 360:
- return linkOrigin.y < connectionPoint.y ? Directions.TOP : Directions.BOTTOM;
- case 90:
- return linkOrigin.x < connectionPoint.x ? Directions.LEFT : Directions.RIGHT;
- case 180:
- return linkOrigin.y < connectionPoint.y ? Directions.TOP : Directions.BOTTOM;
- case 270:
- return linkOrigin.x < connectionPoint.x ? Directions.LEFT : Directions.RIGHT;
- }
-}
-
-function rightAngleRouter(_vertices, opt, linkView) {
- const margin = opt.margin || 20;
- let { sourceDirection = Directions.AUTO, targetDirection = Directions.AUTO } = opt;
-
- const sourceView = linkView.sourceView;
- const targetView = linkView.targetView;
-
- const isSourcePort = !!linkView.model.source().port;
- const isTargetPort = !!linkView.model.target().port;
-
- if (sourceDirection === Directions.AUTO) {
- sourceDirection = isSourcePort ? Directions.MAGNET_SIDE : Directions.ANCHOR_SIDE;
- }
+const OPPOSITE_DIRECTIONS = {
+ [Directions.LEFT]: Directions.RIGHT,
+ [Directions.RIGHT]: Directions.LEFT,
+ [Directions.TOP]: Directions.BOTTOM,
+ [Directions.BOTTOM]: Directions.TOP
+};
- if (targetDirection === Directions.AUTO) {
- targetDirection = isTargetPort ? Directions.MAGNET_SIDE : Directions.ANCHOR_SIDE;
- }
+const VERTICAL_DIRECTIONS = [Directions.TOP, Directions.BOTTOM];
- const sourceBBox = linkView.sourceBBox;
- const targetBBox = linkView.targetBBox;
- const sourcePoint = linkView.sourceAnchor;
- const targetPoint = linkView.targetAnchor;
- let {
- x: sx0,
- y: sy0,
- width: sourceWidth = 0,
- height: sourceHeight = 0
- } = sourceView && sourceView.model.isElement() ? g.Rect.fromRectUnion(sourceBBox, sourceView.model.getBBox()) : linkView.sourceAnchor;
-
- let {
- x: tx0,
- y: ty0,
- width: targetWidth = 0,
- height: targetHeight = 0
- } = targetView && targetView.model.isElement() ? g.Rect.fromRectUnion(targetBBox, targetView.model.getBBox()) : linkView.targetAnchor;
+const ANGLE_DIRECTION_MAP = {
+ 0: Directions.RIGHT,
+ 180: Directions.LEFT,
+ 270: Directions.TOP,
+ 90: Directions.BOTTOM
+};
- const tx1 = tx0 + targetWidth;
- const ty1 = ty0 + targetHeight;
- const sx1 = sx0 + sourceWidth;
- const sy1 = sy0 + sourceHeight;
+function getSegmentAngle(line) {
+ // TODO: the angle() method is general and therefore unnecessarily heavy for orthogonal links
+ return line.angle();
+}
- // Key coordinates including the margin
- const smx0 = sx0 - margin;
- const smx1 = sx1 + margin;
- const smy0 = sy0 - margin;
- const smy1 = sy1 + margin;
- const tmx0 = tx0 - margin;
- const tmx1 = tx1 + margin;
- const tmy0 = ty0 - margin;
- const tmy1 = ty1 + margin;
+function simplifyPoints(points) {
+ // TODO: use own more efficient implementation (filter points that do not change direction).
+ // To simplify segments that are almost aligned (start and end points differ by e.g. 0.5px), use a threshold of 1.
+ return new g.Polyline(points).simplify({ threshold: 1 }).points;
+}
- const sourceOutsidePoint = sourcePoint.clone();
+function resolveSides(source, target) {
+ const { point: sourcePoint, x0: sx0, y0: sy0, view: sourceView, bbox: sourceBBox, direction: sourceDirection } = source;
+ const { point: targetPoint, x0: tx0, y0: ty0, view: targetView, bbox: targetBBox, direction: targetDirection } = target;
let sourceSide;
@@ -97,26 +58,8 @@ function rightAngleRouter(_vertices, opt, linkView) {
sourceSide = sourceDirection;
}
- switch (sourceSide) {
- case 'left':
- sourceOutsidePoint.x = smx0;
- break;
- case 'right':
- sourceOutsidePoint.x = smx1;
- break;
- case 'top':
- sourceOutsidePoint.y = smy0;
- break;
- case 'bottom':
- sourceOutsidePoint.y = smy1;
- break;
- }
- const targetOutsidePoint = targetPoint.clone();
-
-
let targetSide;
-
if (!targetView) {
const targetLinkAnchorBBox = new g.Rect(tx0, ty0, 0, 0);
targetSide = DEFINED_DIRECTIONS.includes(targetDirection) ? targetDirection : targetLinkAnchorBBox.sideNearestToPoint(sourcePoint);
@@ -130,21 +73,274 @@ function rightAngleRouter(_vertices, opt, linkView) {
targetSide = targetDirection;
}
- switch (targetSide) {
+ return [sourceSide, targetSide];
+}
+
+function resolveForTopSourceSide(source, target, nextInLine) {
+ const { x0: sx0, y0: sy0, width, height, point: anchor, margin } = source;
+ const sx1 = sx0 + width;
+ const sy1 = sy0 + height;
+ const smx0 = sx0 - margin;
+ const smx1 = sx1 + margin;
+ const smy0 = sy0 - margin;
+
+ const { x: ax } = anchor;
+ const { x0: tx, y0: ty } = target;
+
+ if (tx === ax && ty < sy0) return Directions.BOTTOM;
+ if (tx < ax && ty < smy0) return Directions.RIGHT;
+ if (tx > ax && ty < smy0) return Directions.LEFT;
+ if (tx < smx0 && ty >= sy0) return Directions.TOP;
+ if (tx > smx1 && ty >= sy0) return Directions.TOP;
+ if (tx >= smx0 && tx <= ax && ty > sy1) {
+ if (nextInLine.point.x < tx) {
+ return Directions.RIGHT;
+ }
+
+ return Directions.LEFT;
+ }
+ if (tx <= smx1 && tx >= ax && ty > sy1) {
+ if (nextInLine.point.x < tx) {
+ return Directions.RIGHT;
+ }
+
+ return Directions.LEFT;
+ }
+
+ return Directions.TOP;
+}
+
+function resolveForBottomSourceSide(source, target, nextInLine) {
+ const { x0: sx0, y0: sy0, width, height, point: anchor, margin } = source;
+ const sx1 = sx0 + width;
+ const sy1 = sy0 + height;
+ const smx0 = sx0 - margin;
+ const smx1 = sx1 + margin;
+ const smy1 = sy1 + margin;
+
+ const { x: ax } = anchor;
+ const { x0: tx, y0: ty } = target;
+
+ if (tx === ax && ty > sy1) return Directions.TOP;
+ if (tx < ax && ty > smy1) return Directions.RIGHT;
+ if (tx > ax && ty > smy1) return Directions.LEFT;
+ if (tx < smx0 && ty <= sy1) return Directions.BOTTOM;
+ if (tx > smx1 && ty <= sy1) return Directions.BOTTOM;
+ if (tx >= smx0 && tx <= ax && ty < sy0) {
+ if (nextInLine.point.x < tx) {
+ return Directions.RIGHT;
+ }
+
+ return Directions.LEFT;
+ }
+ if (tx <= smx1 && tx >= ax && ty < sy0) {
+ if (nextInLine.point.x < tx) {
+ return Directions.RIGHT;
+ }
+
+ return Directions.LEFT;
+ }
+
+ return Directions.BOTTOM;
+}
+
+function resolveForLeftSourceSide(source, target, nextInLine) {
+ const { y0: sy0, x0: sx0, width, height, point: anchor, margin } = source;
+ const sx1 = sx0 + width;
+ const sy1 = sy0 + height;
+ const smx0 = sx0 - margin;
+ const smy0 = sy0 - margin;
+ const smy1 = sy1 + margin;
+
+ const { x: ax, y: ay } = anchor;
+ const { x0: tx, y0: ty } = target;
+
+ if (tx < ax && ty === ay) return Directions.RIGHT;
+ if (tx <= smx0 && ty < ay) return Directions.BOTTOM;
+ if (tx <= smx0 && ty > ay) return Directions.TOP;
+ if (tx >= sx0 && ty <= smy0) return Directions.LEFT;
+ if (tx >= sx0 && ty >= smy1) return Directions.LEFT;
+ if (tx > sx1 && ty >= smy0 && ty <= ay) {
+ if (nextInLine.point.y < ty) {
+ return Directions.BOTTOM;
+ }
+
+ return Directions.TOP;
+ }
+ if (tx > sx1 && ty <= smy1 && ty >= ay) {
+ if (nextInLine.point.y < ty) {
+ return Directions.BOTTOM;
+ }
+
+ return Directions.TOP;
+ }
+
+ return Directions.LEFT;
+}
+
+function resolveForRightSourceSide(source, target, nextInLine) {
+ const { y0: sy0, x0: sx0, width, height, point: anchor, margin } = source;
+ const sx1 = sx0 + width;
+ const sy1 = sy0 + height;
+ const smx1 = sx1 + margin;
+ const smy0 = sy0 - margin;
+ const smy1 = sy1 + margin;
+
+ const { x: ax, y: ay } = anchor;
+ const { x0: tx, y0: ty } = target;
+
+ if (tx > ax && ty === ay) return Directions.LEFT;
+ if (tx >= smx1 && ty < ay) return Directions.BOTTOM;
+ if (tx >= smx1 && ty > ay) return Directions.TOP;
+ if (tx <= sx1 && ty <= smy0) return Directions.RIGHT;
+ if (tx <= sx1 && ty >= smy1) return Directions.RIGHT;
+ if (tx < sx0 && ty >= smy0 && ty <= ay) {
+ if (nextInLine.point.y < ty) {
+ return Directions.BOTTOM;
+ }
+
+ return Directions.TOP;
+ }
+ if (tx < sx0 && ty <= smy1 && ty >= ay) {
+ if (nextInLine.point.y < ty) {
+ return Directions.BOTTOM;
+ }
+
+ return Directions.TOP;
+ }
+
+ return Directions.RIGHT;
+}
+
+function resolveInitialDirection(source, target, nextInLine) {
+ const [sourceSide] = resolveSides(source, target);
+
+ switch (sourceSide) {
+ case Directions.TOP:
+ return resolveForTopSourceSide(source, target, nextInLine);
+ case Directions.RIGHT:
+ return resolveForRightSourceSide(source, target, nextInLine);
+ case Directions.BOTTOM:
+ return resolveForBottomSourceSide(source, target, nextInLine);
+ case Directions.LEFT:
+ return resolveForLeftSourceSide(source, target, nextInLine);
+ }
+}
+
+function getDirectionForLinkConnection(linkOrigin, connectionPoint, linkView) {
+ const tangent = linkView.getTangentAtLength(linkView.getClosestPointLength(connectionPoint));
+ const roundedAngle = Math.round(getSegmentAngle(tangent) / 90) * 90;
+
+ if (roundedAngle % 180 === 0 && linkOrigin.y === connectionPoint.y) {
+ return linkOrigin.x < connectionPoint.x ? Directions.LEFT : Directions.RIGHT;
+ } else if (linkOrigin.x === connectionPoint.x) {
+ return linkOrigin.y < connectionPoint.y ? Directions.TOP : Directions.BOTTOM;
+ }
+
+ switch (roundedAngle) {
+ case 0:
+ case 180:
+ case 360:
+ return linkOrigin.y < connectionPoint.y ? Directions.TOP : Directions.BOTTOM;
+ case 90:
+ case 270:
+ return linkOrigin.x < connectionPoint.x ? Directions.LEFT : Directions.RIGHT;
+ }
+}
+
+function pointDataFromAnchor(view, point, bbox, direction, isPort, fallBackAnchor, margin) {
+ if (direction === Directions.AUTO) {
+ direction = isPort ? Directions.MAGNET_SIDE : Directions.ANCHOR_SIDE;
+ }
+
+ const isElement = view && view.model.isElement();
+
+ const {
+ x: x0,
+ y: y0,
+ width = 0,
+ height = 0
+ } = isElement ? g.Rect.fromRectUnion(bbox, view.model.getBBox()) : fallBackAnchor;
+
+ return {
+ point,
+ x0,
+ y0,
+ view,
+ bbox,
+ width,
+ height,
+ direction,
+ margin: isElement ? margin : 0
+ };
+}
+
+function pointDataFromVertex({ x, y }) {
+ const point = new g.Point(x, y);
+
+ return {
+ point,
+ x0: point.x,
+ y0: point.y,
+ view: null,
+ bbox: new g.Rect(x, y, 0, 0),
+ width: 0,
+ height: 0,
+ direction: null,
+ margin: 0
+ };
+}
+
+function getOutsidePoint(side, pointData, margin) {
+ const outsidePoint = pointData.point.clone();
+
+ const { x0, y0, width, height } = pointData;
+
+ switch (side) {
case 'left':
- targetOutsidePoint.x = tmx0;
+ outsidePoint.x = x0 - margin;
break;
case 'right':
- targetOutsidePoint.x = tmx1;
+ outsidePoint.x = x0 + width + margin;
break;
case 'top':
- targetOutsidePoint.y = tmy0;
+ outsidePoint.y = y0 - margin;
break;
case 'bottom':
- targetOutsidePoint.y = tmy1;
+ outsidePoint.y = y0 + height + margin;
break;
}
+ return outsidePoint;
+}
+
+function routeBetweenPoints(source, target) {
+ const { point: sourcePoint, x0: sx0, y0: sy0, view: sourceView, width: sourceWidth, height: sourceHeight, margin: sourceMargin } = source;
+ const { point: targetPoint, x0: tx0, y0: ty0, width: targetWidth, height: targetHeight, margin: targetMargin } = target;
+
+ const tx1 = tx0 + targetWidth;
+ const ty1 = ty0 + targetHeight;
+ const sx1 = sx0 + sourceWidth;
+ const sy1 = sy0 + sourceHeight;
+
+ const isSourceEl = sourceView && sourceView.model.isElement();
+
+ // Key coordinates including the margin
+ const smx0 = sx0 - sourceMargin;
+ const smx1 = sx1 + sourceMargin;
+ const smy0 = sy0 - sourceMargin;
+ const smy1 = sy1 + sourceMargin;
+
+ const tmx0 = tx0 - targetMargin;
+ const tmx1 = tx1 + targetMargin;
+ const tmy0 = ty0 - targetMargin;
+ const tmy1 = ty1 + targetMargin;
+
+ const [sourceSide, targetSide] = resolveSides(source, target);
+
+ const sourceOutsidePoint = getOutsidePoint(sourceSide, { point: sourcePoint, x0: sx0, y0: sy0, width: sourceWidth, height: sourceHeight }, sourceMargin);
+ const targetOutsidePoint = getOutsidePoint(targetSide, { point: targetPoint, x0: tx0, y0: ty0, width: targetWidth, height: targetHeight }, targetMargin);
+
const { x: sox, y: soy } = sourceOutsidePoint;
const { x: tox, y: toy } = targetOutsidePoint;
const tcx = (tx0 + tx1) / 2;
@@ -155,7 +351,7 @@ function rightAngleRouter(_vertices, opt, linkView) {
const middleOfHorizontalSides = (scy < tcy ? (sy1 + ty0) : (ty1 + sy0)) / 2;
if (sourceSide === 'left' && targetSide === 'right') {
- if (smx0 <= tx1) {
+ if (smx0 <= tmx1) {
let y = middleOfHorizontalSides;
if (sx1 <= tx0) {
if (ty1 >= smy0 && toy < soy) {
@@ -178,7 +374,7 @@ function rightAngleRouter(_vertices, opt, linkView) {
{ x, y: toy }
];
} else if (sourceSide === 'right' && targetSide === 'left') {
- if (smx1 >= tx0) {
+ if (smx1 >= tmx0) {
let y = middleOfHorizontalSides;
if (sox > tx1) {
if (ty1 >= smy0 && toy < soy) {
@@ -227,7 +423,7 @@ function rightAngleRouter(_vertices, opt, linkView) {
{ x: tox, y }
];
} else if (sourceSide === 'bottom' && targetSide === 'top') {
- if (soy - margin > toy) {
+ if (soy - sourceMargin > toy) {
let x = middleOfVerticalSides;
let y = soy;
@@ -259,8 +455,8 @@ function rightAngleRouter(_vertices, opt, linkView) {
if (toy < soy) {
if (sox >= tmx1 || sox <= tmx0) {
return [
- { x: sox, y: Math.min(soy,toy) },
- { x: tox, y: Math.min(soy,toy) }
+ { x: sox, y: Math.min(soy, toy) },
+ { x: tox, y: Math.min(soy, toy) }
];
} else if (tox > sox) {
x = Math.min(sox, tmx0);
@@ -270,8 +466,8 @@ function rightAngleRouter(_vertices, opt, linkView) {
} else {
if (tox >= smx1 || tox <= smx0) {
return [
- { x: sox, y: Math.min(soy,toy) },
- { x: tox, y: Math.min(soy,toy) }
+ { x: sox, y: Math.min(soy, toy) },
+ { x: tox, y: Math.min(soy, toy) }
];
} else if (tox >= sox) {
x = Math.max(tox, smx1);
@@ -287,34 +483,31 @@ function rightAngleRouter(_vertices, opt, linkView) {
{ x: tox, y: y1 }
];
} else if (sourceSide === 'bottom' && targetSide === 'bottom') {
- if (tx0 >= sox + margin || tx1 <= sox - margin) {
- return [
- { x: sox, y: Math.max(soy, toy) },
- { x: tox, y: Math.max(soy, toy) }
- ];
- }
-
let x;
- let y1;
- let y2;
+ let y1 = Math.max((sy0 + ty1) / 2, toy);
+ let y2 = Math.max((sy1 + ty0) / 2, soy);
if (toy > soy) {
- y1 = Math.max((sy1 + ty0) / 2, toy);
- y2 = Math.max((sy1 + ty0) / 2, soy);
-
- if (tox > sox) {
+ if (sox >= tmx1 || sox <= tmx0) {
+ return [
+ { x: sox, y: Math.max(soy, toy) },
+ { x: tox, y: Math.max(soy, toy) }
+ ];
+ } else if (tox > sox) {
x = Math.min(sox, tmx0);
} else {
x = Math.max(sox, tmx1);
}
} else {
- y1 = Math.max((sy0 + ty1) / 2, toy);
- y2 = Math.max((sy0 + ty1) / 2, soy);
-
- if (tox > sox) {
- x = Math.min(tox, smx0);
- } else {
+ if (tox >= smx1 || tox <= smx0) {
+ return [
+ { x: sox, y: Math.max(soy, toy) },
+ { x: tox, y: Math.max(soy, toy) }
+ ];
+ } else if (tox >= sox) {
x = Math.max(tox, smx1);
+ } else {
+ x = Math.min(tox, smx0);
}
}
@@ -377,8 +570,9 @@ function rightAngleRouter(_vertices, opt, linkView) {
} else if (sourceSide === 'top' && targetSide === 'right') {
if (soy > toy) {
if (sox < tox) {
- let y = (sy0 + ty1) / 2;
- if (y > tcy && y < tmy1 && sox < tmx0) {
+ let y = middleOfHorizontalSides;
+
+ if ((y > tcy || !isSourceEl) && y < tmy1 && sox < tx0) {
y = tmy0;
}
return [
@@ -387,37 +581,41 @@ function rightAngleRouter(_vertices, opt, linkView) {
{ x: tox, y: toy }
];
}
+
return [{ x: sox, y: toy }];
}
- const x = (sx0 + tx1) / 2;
+ const x = Math.max(middleOfVerticalSides, tmx1);
- if (sox > tox && sy1 >= toy) {
+ if (tox < sox && toy > sy0 && toy < sy1) {
return [
{ x: sox, y: soy },
- { x, y: soy },
- { x, y: toy }];
+ { x: x, y: soy },
+ { x: x, y: toy }
+ ];
}
- if (x > smx0 && soy < ty1) {
- const y = Math.min(sy0, ty0) - margin;
- const x = Math.max(sx1, tx1) + margin;
+ if ((x > smx0 && toy > sy0) || tx0 > sx1) {
+ const y = Math.min(sy0 - sourceMargin, ty0 - targetMargin);
+ const x = Math.max(sx1 + sourceMargin, tx1 + targetMargin);
return [
{ x: sox, y },
{ x, y },
{ x, y: toy }
];
}
+
return [
{ x: sox, y: soy },
- { x, y: soy },
- { x, y: toy }
+ { x: Math.max(x, tox), y: soy },
+ { x: Math.max(x, tox), y: toy }
];
} else if (sourceSide === 'top' && targetSide === 'left') {
if (soy > toy) {
if (sox > tox) {
- let y = (sy0 + ty1) / 2;
- if (y > tcy && y < tmy1 && sox > tmx1) {
+ let y = middleOfHorizontalSides;
+
+ if ((y > tcy || !isSourceEl) && y < tmy1 && sox > tx1) {
y = tmy0;
}
return [
@@ -429,7 +627,7 @@ function rightAngleRouter(_vertices, opt, linkView) {
return [{ x: sox, y: toy }];
}
- const x = (sx1 + tx0) / 2;
+ const x = Math.min(tmx0, middleOfVerticalSides);
if (sox < tox && sy1 >= toy) {
return [
@@ -439,8 +637,8 @@ function rightAngleRouter(_vertices, opt, linkView) {
}
if (x < smx1 && soy < ty1) {
- const y = Math.min(sy0, ty0) - margin;
- const x = Math.min(sx0, tx0) - margin;
+ const y = Math.min(smy0, tmy0);
+ const x = Math.min(smx0, tmx0);
return [
{ x: sox, y },
{ x, y },
@@ -455,8 +653,9 @@ function rightAngleRouter(_vertices, opt, linkView) {
} else if (sourceSide === 'bottom' && targetSide === 'right') {
if (soy < toy) {
if (sox < tox) {
- let y = (sy1 + ty0) / 2;
- if (y < tcy && y > tmy0 && sox < tmx0) {
+ let y = middleOfHorizontalSides;
+
+ if ((y < tcy || !isSourceEl) && y > tmy0 && sox < tx0) {
y = tmy1;
}
return [
@@ -468,8 +667,8 @@ function rightAngleRouter(_vertices, opt, linkView) {
return [{ x: sox, y: toy }];
} else {
if (sx0 < tox) {
- const y = Math.max(sy1, ty1) + margin;
- const x = Math.max(sx1, tx1) + margin;
+ const y = Math.max(smy1, tmy1);
+ const x = Math.max(smx1, tmx1);
return [
{ x: sox, y },
{ x, y },
@@ -488,8 +687,9 @@ function rightAngleRouter(_vertices, opt, linkView) {
} else if (sourceSide === 'bottom' && targetSide === 'left') {
if (soy < toy) {
if (sox > tox) {
- let y = (sy1 + ty0) / 2;
- if (y < tcy && y > tmy0 && sox > tmx1) {
+ let y = middleOfHorizontalSides;
+
+ if ((y < tcy || !isSourceEl) && y > tmy0 && sox > tx1) {
y = tmy1;
}
return [
@@ -501,8 +701,8 @@ function rightAngleRouter(_vertices, opt, linkView) {
return [{ x: sox, y: toy }];
} else {
if (sx1 > tox) {
- const y = Math.max(sy1, ty1) + margin;
- const x = Math.min(sx0, tx0) - margin;
+ const y = Math.max(smy1, tmy1);
+ const x = Math.min(smx0, tmx0);
return [
{ x: sox, y },
{ x, y },
@@ -518,13 +718,15 @@ function rightAngleRouter(_vertices, opt, linkView) {
{ x, y: soy },
{ x, y: toy }
];
- } else if (sourceSide === 'left' && targetSide === 'bottom') {
- if (sox > tox && soy >= tmy1) {
+ }
+ else if (sourceSide === 'left' && targetSide === 'bottom') {
+ if (sox >= tox && soy >= tmy1) {
return [{ x: tox, y: soy }];
}
if (sox >= tx1 && soy < toy) {
- const x = (sx1 + tx0) / 2;
+ const x = middleOfVerticalSides;
+
return [
{ x, y: soy },
{ x, y: toy },
@@ -533,7 +735,7 @@ function rightAngleRouter(_vertices, opt, linkView) {
}
if (tox < sx1 && ty1 <= sy0) {
- const y = (sy0 + ty1) / 2;
+ const y = middleOfHorizontalSides;
return [
{ x: sox, y: soy },
@@ -543,7 +745,7 @@ function rightAngleRouter(_vertices, opt, linkView) {
}
const x = Math.min(tmx0, sox);
- const y = Math.max(sy1, ty1) + margin;
+ const y = Math.max(smy1, tmy1);
return [
{ x, y: soy },
@@ -557,7 +759,8 @@ function rightAngleRouter(_vertices, opt, linkView) {
if (sox >= tx1) {
if (soy > toy) {
- const x = (sx0 + tx1) / 2;
+ const x = middleOfVerticalSides;
+
return [
{ x, y: soy },
{ x, y: toy },
@@ -567,7 +770,7 @@ function rightAngleRouter(_vertices, opt, linkView) {
}
if (tox <= sx1 && toy > soy) {
- const y = (ty0 + sy1) / 2;
+ const y = middleOfHorizontalSides;
return [
{ x: sox, y: soy },
@@ -576,8 +779,8 @@ function rightAngleRouter(_vertices, opt, linkView) {
];
}
- const x = toy < soy ? Math.min(sx0, tx0) - margin : smx0;
- const y = Math.min(sy0, ty0) - margin;
+ const x = toy < soy ? Math.min(smx0, tmx0) : smx0;
+ const y = Math.min(smy0, tmy0);
return [
{ x, y: soy },
@@ -586,12 +789,13 @@ function rightAngleRouter(_vertices, opt, linkView) {
];
} else if (sourceSide === 'right' && targetSide === 'top') {
- if (sox < tox && soy < tmy0) {
+ if (sox <= tox && soy < tmy0) {
return [{ x: tox, y: soy }];
}
if (sx1 < tx0 && soy > toy) {
- let x = (sx1 + tx0) / 2;
+ let x = middleOfVerticalSides;
+
return [
{ x, y: soy },
{ x, y: toy },
@@ -600,7 +804,7 @@ function rightAngleRouter(_vertices, opt, linkView) {
}
if (tox < sox && ty0 > sy1) {
- const y = (sy1 + ty0) / 2;
+ const y = middleOfHorizontalSides;
return [
{ x: sox, y: soy },
@@ -609,20 +813,22 @@ function rightAngleRouter(_vertices, opt, linkView) {
];
}
- const x = Math.max(sx1, tx1) + margin;
- const y = Math.min(sy0, ty0) - margin;
+ const x = Math.max(smx1, tmx1);
+ const y = Math.min(smy0, tmy0);
+
return [
{ x, y: soy },
{ x, y },
{ x: tox, y }
];
} else if (sourceSide === 'right' && targetSide === 'bottom') {
- if (sox < tox && soy >= tmy1) {
+ if (sox <= tox && soy >= tmy1) {
return [{ x: tox, y: soy }];
}
- if (sox <= tx0 && soy < toy) {
- const x = (sx1 + tx0) / 2;
+ if (sox <= tmx0 && soy < toy) {
+ const x = middleOfVerticalSides;
+
return [
{ x, y: soy },
{ x, y: toy },
@@ -631,7 +837,7 @@ function rightAngleRouter(_vertices, opt, linkView) {
}
if (tox > sx0 && ty1 < sy0) {
- const y = (sy0 + ty1) / 2;
+ const y = middleOfHorizontalSides;
return [
{ x: sox, y: soy },
@@ -641,7 +847,7 @@ function rightAngleRouter(_vertices, opt, linkView) {
}
const x = Math.max(tmx1, sox);
- const y = Math.max(sy1, ty1) + margin;
+ const y = Math.max(smy1, tmy1);
return [
{ x, y: soy },
@@ -651,6 +857,200 @@ function rightAngleRouter(_vertices, opt, linkView) {
}
}
+function rightAngleRouter(vertices, opt, linkView) {
+ const { sourceDirection = Directions.AUTO, targetDirection = Directions.AUTO } = opt;
+ const margin = opt.margin || 20;
+ const useVertices = opt.useVertices || false;
+
+ const isSourcePort = !!linkView.model.source().port;
+ const sourcePoint = pointDataFromAnchor(linkView.sourceView, linkView.sourceAnchor, linkView.sourceBBox, sourceDirection, isSourcePort, linkView.sourceAnchor, margin);
+
+ const isTargetPort = !!linkView.model.target().port;
+ const targetPoint = pointDataFromAnchor(linkView.targetView, linkView.targetAnchor, linkView.targetBBox, targetDirection, isTargetPort, linkView.targetAnchor, margin);
+
+ let resultVertices = [];
+
+ if (!useVertices || vertices.length === 0) {
+ return simplifyPoints(routeBetweenPoints(sourcePoint, targetPoint));
+ }
+
+ const verticesData = vertices.map((v) => pointDataFromVertex(v));
+ const [firstVertex] = verticesData;
+
+ if (sourcePoint.view && sourcePoint.view.model.isElement() && sourcePoint.view.model.getBBox().inflate(margin).containsPoint(firstVertex.point)) {
+ const [fromDirection] = resolveSides(sourcePoint, firstVertex);
+ const toDirection = fromDirection;
+ const dummySource = pointDataFromVertex(sourcePoint.point);
+ // Points do not usually have margin. Here we create a point with a margin.
+ dummySource.margin = margin;
+ dummySource.direction = fromDirection;
+ firstVertex.direction = toDirection;
+
+ resultVertices.push(...routeBetweenPoints(dummySource, firstVertex), firstVertex.point);
+ } else {
+ // The first point responsible for the initial direction of the route
+ const next = verticesData[1] || targetPoint;
+ const direction = resolveInitialDirection(sourcePoint, firstVertex, next);
+ firstVertex.direction = direction;
+
+ resultVertices.push(...routeBetweenPoints(sourcePoint, firstVertex), firstVertex.point);
+ }
+
+ for (let i = 0; i < verticesData.length - 1; i++) {
+ const from = verticesData[i];
+ const to = verticesData[i + 1];
+
+ const segment = new g.Line(from.point, to.point);
+ const segmentAngle = getSegmentAngle(segment);
+ if (segmentAngle % 90 === 0) {
+ // Since the segment is horizontal or vertical, we can skip the routing and just connect them with a straight line
+ const toDirection = ANGLE_DIRECTION_MAP[segmentAngle];
+ const accessDirection = OPPOSITE_DIRECTIONS[toDirection];
+
+ if (toDirection !== from.direction) {
+ resultVertices.push(from.point, to.point);
+ to.direction = accessDirection;
+ } else {
+ const angle = g.normalizeAngle(segmentAngle - 90);
+
+ let dx = 0;
+ let dy = 0;
+
+ if (angle === 90) {
+ dy = -margin;
+ } else if (angle === 180) {
+ dx = -margin;
+ } else if (angle === 270) {
+ dy = margin;
+ } else if (angle === 0) {
+ dx = margin;
+ }
+
+ const p1 = { x: from.point.x + dx, y: from.point.y + dy };
+ const p2 = { x: to.point.x + dx, y: to.point.y + dy };
+
+ const segment2 = new g.Line(to.point, p2);
+ to.direction = ANGLE_DIRECTION_MAP[getSegmentAngle(segment2)];
+
+ // Constructing a loop
+ resultVertices.push(from.point, p1, p2, to.point);
+ }
+
+ continue;
+ }
+
+ const [fromDirection, toDirection] = resolveDirection(from, to);
+
+ from.direction = fromDirection;
+ to.direction = toDirection;
+
+ resultVertices.push(...routeBetweenPoints(from, to), to.point);
+ }
+
+ const lastVertex = verticesData[verticesData.length - 1];
+
+ if (targetPoint.view && targetPoint.view.model.isElement()) {
+ if (targetPoint.view.model.getBBox().inflate(margin).containsPoint(lastVertex.point)) {
+ const [fromDirection] = resolveDirection(lastVertex, targetPoint);
+ const dummyTarget = pointDataFromVertex(targetPoint.point);
+ const [, toDirection] = resolveSides(lastVertex, targetPoint);
+ // we are creating a point that has a margin
+ dummyTarget.margin = margin;
+ dummyTarget.direction = toDirection;
+ lastVertex.direction = fromDirection;
+
+ resultVertices.push(...routeBetweenPoints(lastVertex, dummyTarget));
+ } else {
+ // the last point of `simplified` array is the last defined vertex
+ // grab the penultimate point and construct a line segment from it to the last vertex
+ // this will ensure that the last segment continues in a straight line
+
+ const simplified = simplifyPoints(resultVertices);
+ const segment = new g.Line(simplified[simplified.length - 2], lastVertex.point);
+ const definedDirection = ANGLE_DIRECTION_MAP[Math.round(getSegmentAngle(segment))];
+ lastVertex.direction = definedDirection;
+
+ let lastSegmentRoute = routeBetweenPoints(lastVertex, targetPoint);
+ const [p1, p2] = simplifyPoints([...lastSegmentRoute, targetPoint.point]);
+
+ const lastSegment = new g.Line(p1, p2);
+ const roundedLastSegmentAngle = Math.round(getSegmentAngle(lastSegment));
+ const lastSegmentDirection = ANGLE_DIRECTION_MAP[roundedLastSegmentAngle];
+
+ if (lastSegmentDirection !== definedDirection && definedDirection === OPPOSITE_DIRECTIONS[lastSegmentDirection]) {
+ lastVertex.margin = margin;
+ lastSegmentRoute = routeBetweenPoints(lastVertex, targetPoint);
+ }
+
+ resultVertices.push(...lastSegmentRoute);
+ }
+ } else {
+ // since the target is only a point we can apply the same logic as if we connected two verticesData
+ const [vertexDirection] = resolveDirection(lastVertex, targetPoint);
+ lastVertex.direction = vertexDirection;
+
+ resultVertices.push(...routeBetweenPoints(lastVertex, targetPoint));
+ }
+
+ return simplifyPoints(resultVertices);
+}
+
+function resolveDirection(from, to) {
+ const accessDirection = from.direction;
+ const isDirectionVertical = VERTICAL_DIRECTIONS.includes(accessDirection);
+
+ let sourceDirection = from.direction;
+ let targetDirection = to.direction;
+
+ if (isDirectionVertical) {
+ const isToAbove = from.point.y > to.point.y;
+ const dx = to.point.x - from.point.x;
+
+ if (accessDirection === Directions.BOTTOM) {
+ // If isToAbove === false and we need figure out if to go left or right
+ sourceDirection = isToAbove ? OPPOSITE_DIRECTIONS[accessDirection] : dx >= 0 ? Directions.RIGHT : Directions.LEFT;
+
+ if (dx > 0) {
+ targetDirection = isToAbove ? Directions.LEFT : Directions.TOP;
+ } else if (dx < 0) {
+ targetDirection = isToAbove ? Directions.RIGHT : Directions.TOP;
+ }
+ } else {
+ // If isToAbove === true and we need figure out if to go left or right
+ sourceDirection = isToAbove ? dx >= 0 ? Directions.RIGHT : Directions.LEFT : OPPOSITE_DIRECTIONS[accessDirection];
+
+ if (dx > 0) {
+ targetDirection = isToAbove ? Directions.BOTTOM : Directions.LEFT;
+ } else if (dx < 0) {
+ targetDirection = isToAbove ? Directions.BOTTOM : Directions.RIGHT;
+ }
+ }
+ } else {
+ const isToLeft = from.point.x > to.point.x;
+ const dy = to.point.y - from.point.y;
+
+ if (accessDirection === Directions.RIGHT) {
+ sourceDirection = isToLeft ? OPPOSITE_DIRECTIONS[accessDirection] : dy >= 0 ? Directions.BOTTOM : Directions.TOP;
+
+ if (dy > 0) {
+ targetDirection = isToLeft ? Directions.TOP : Directions.LEFT;
+ } else if (dy < 0) {
+ targetDirection = isToLeft ? Directions.BOTTOM : Directions.LEFT;
+ }
+ } else {
+ sourceDirection = isToLeft ? dy >= 0 ? Directions.BOTTOM : Directions.TOP : OPPOSITE_DIRECTIONS[accessDirection];
+
+ if (dy > 0) {
+ targetDirection = isToLeft ? Directions.RIGHT : Directions.TOP;
+ } else if (dy < 0) {
+ targetDirection = isToLeft ? Directions.RIGHT : Directions.BOTTOM;
+ }
+ }
+ }
+
+ return [sourceDirection, targetDirection];
+}
+
rightAngleRouter.Directions = Directions;
export const rightAngle = rightAngleRouter;
diff --git a/test/jointjs/routers.js b/test/jointjs/routers.js
index 658f76432..daea95066 100644
--- a/test/jointjs/routers.js
+++ b/test/jointjs/routers.js
@@ -333,7 +333,7 @@ QUnit.module('routers', function(hooks) {
assert.ok(spyIsPointObstacle.called);
assert.ok(spyIsPointObstacle.alwaysCalledWithExactly(sinon.match.instanceOf(g.Point)));
- assert.checkDataPath(d, 'M 140 70 L 620 70','isPointObstacle option is taken into account');
+ assert.checkDataPath(d, 'M 140 70 L 620 70', 'isPointObstacle option is taken into account');
});
@@ -443,10 +443,10 @@ QUnit.module('routers', function(hooks) {
const height = 50;
const size = { width, height };
const margin = 28;
- const router = { name: 'rightAngle', args: { margin }};
+ const rightAngleRouter = { name: 'rightAngle', args: { margin }};
const position = { x: 0, y: 150 };
- this.addTestSubjects = function(sourceSide, targetSide ){
+ this.addTestSubjects = function(sourceSide, targetSide, router = rightAngleRouter) {
const r1 = new joint.shapes.standard.Rectangle({ size });
const r2 = r1.clone().position(position.x, position.y);
const l = new joint.shapes.standard.Link({ source: { id: r1.id, anchor: { name: sourceSide }}, target: { id: r2.id, anchor: { name: targetSide }}, router });
@@ -455,6 +455,12 @@ QUnit.module('routers', function(hooks) {
return [r1, r2, l];
};
+ this.addTestSubjectsWithVertices = function(sourceSide, targetSide, vertices) {
+ const [r1, r2, l] = this.addTestSubjects(sourceSide, targetSide, { ...rightAngleRouter, args: { margin, useVertices: true }});
+ l.vertices(vertices);
+ return [r1, r2, l];
+ };
+
const topVerticalPathSegments = [
`${width / 2} 0`,
`${width / 2} -${margin}`,
@@ -502,7 +508,7 @@ QUnit.module('routers', function(hooks) {
path = `M ${moveSegment} L ${segments.join(' L ')}`;
assert.checkDataPath(d, path, 'Source on the left of target');
-
+
position1 = r1.position();
position2 = r2.position();
@@ -812,12 +818,22 @@ QUnit.module('routers', function(hooks) {
`${position.y + width} ${height / 2}`
];
+ const rightVerticalPathSegments = [
+ `${width} ${height / 2}`,
+ `${width + margin} ${height / 2}`,
+ `${width + margin} ${height / 2 + position.y}`,
+ `${width} ${height / 2 + position.y}`,
+ ];
+
QUnit.test('rightAngle routing - source: right, target: right', function(assert) {
const [r1, r2, l] = this.addTestSubjects('right', 'right');
let d = this.paper.findViewByModel(l).metrics.data;
+ let segments = joint.util.cloneDeep(rightVerticalPathSegments);
+ let moveSegment = segments.shift();
+ let path = `M ${moveSegment} L ${segments.join(' L ')}`;
- assert.checkDataPath(d, 'M 50 25 L 78 25 L 78 25 L 78 25 L 78 175 L 50 175', 'Source above target');
+ assert.checkDataPath(d, path, 'Source above target');
let position1 = r1.position();
let position2 = r2.position();
@@ -826,16 +842,19 @@ QUnit.module('routers', function(hooks) {
r2.position(position1.x, position1.y);
d = this.paper.findViewByModel(l).metrics.data;
+ segments = joint.util.cloneDeep(rightVerticalPathSegments).reverse();
+ moveSegment = segments.shift();
+ path = `M ${moveSegment} L ${segments.join(' L ')}`;
- assert.checkDataPath(d, 'M 50 175 L 78 175 L 78 175 L 78 175 L 78 25 L 50 25', 'Target above source');
+ assert.checkDataPath(d, path, 'Target above source');
r1.position(0, 0);
r2.position(position.y, position.x);
d = this.paper.findViewByModel(l).metrics.data;
- let segments = joint.util.cloneDeep(rightHorizontalPathSegments);
- let moveSegment = segments.shift();
- let path = `M ${moveSegment} L ${segments.join(' L ')}`;
+ segments = joint.util.cloneDeep(rightHorizontalPathSegments);
+ moveSegment = segments.shift();
+ path = `M ${moveSegment} L ${segments.join(' L ')}`;
assert.checkDataPath(d, path, 'Source on the left of target');
@@ -1399,12 +1418,22 @@ QUnit.module('routers', function(hooks) {
`${position.y} ${height / 2}`
];
+ const leftVerticalPathSegments = [
+ `0 ${height / 2}`,
+ `-${margin} ${height / 2}`,
+ `-${margin} ${height / 2 + position.y}`,
+ `0 ${height / 2 + position.y}`
+ ];
+
QUnit.test('rightAngle routing - source: left, target: left', function(assert) {
const [r1, r2, l] = this.addTestSubjects('left', 'left');
let d = this.paper.findViewByModel(l).metrics.data;
+ let segments = joint.util.cloneDeep(leftVerticalPathSegments);
+ let moveSegment = segments.shift();
+ let path = `M ${moveSegment} L ${segments.join(' L ')}`;
- assert.checkDataPath(d, 'M 0 25 L -28 25 L -28 25 L -28 25 L -28 175 L 0 175', 'Source above target');
+ assert.checkDataPath(d, path, 'Source above target');
let position1 = r1.position();
let position2 = r2.position();
@@ -1413,16 +1442,19 @@ QUnit.module('routers', function(hooks) {
r2.position(position1.x, position1.y);
d = this.paper.findViewByModel(l).metrics.data;
+ segments = joint.util.cloneDeep(leftVerticalPathSegments).reverse();
+ moveSegment = segments.shift();
+ path = `M ${moveSegment} L ${segments.join(' L ')}`;
- assert.checkDataPath(d, 'M 0 175 L -28 175 L -28 175 L -28 175 L -28 25 L 0 25', 'Target above source');
+ assert.checkDataPath(d, path, 'Target above source');
r1.position(0, 0);
r2.position(position.y, position.x);
d = this.paper.findViewByModel(l).metrics.data;
- let segments = joint.util.cloneDeep(leftHorizontalPathSegments);
- let moveSegment = segments.shift();
- let path = `M ${moveSegment} L ${segments.join(' L ')}`;
+ segments = joint.util.cloneDeep(leftHorizontalPathSegments);
+ moveSegment = segments.shift();
+ path = `M ${moveSegment} L ${segments.join(' L ')}`;
assert.checkDataPath(d, path, 'Source on the left of target');
@@ -1439,4 +1471,645 @@ QUnit.module('routers', function(hooks) {
assert.checkDataPath(d, path, 'Target on the left of source');
});
+
+ QUnit.test('rightAngle routing with vertex - source: top, target: top', function(assert) {
+ const vertex = { x: 100, y: 100 };
+ const [r1, r2, l] = this.addTestSubjectsWithVertices('top', 'top', [vertex]);
+
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 0 L 25 -28 L 100 -28 L 100 111 L 25 111 L 25 150', 'Source above target with vertex');
+
+ let position1 = r1.position();
+ let position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 150 L 25 100 L 100 100 L 100 -28 L 25 -28 L 25 0', 'Target above source with vertex');
+
+ r1.position(0, 0);
+ r2.position(position.y, position.x);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 0 L 25 -28 L 100 -28 L 100 100 L 125 100 L 125 -28 L 175 -28 L 175 0', 'Source on the left of target with vertex');
+
+ position1 = r1.position();
+ position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 175 0 L 175 -28 L 100 -28 L 100 100 L 75 100 L 75 -28 L 25 -28 L 25 0', 'Target on the left of source with vertex');
+ });
+
+ QUnit.test('rightAngle routing with vertex - source: top, target: right', function(assert) {
+ const vertex = { x: 100, y: 100 };
+ const [r1, r2, l] = this.addTestSubjectsWithVertices('top', 'right', [vertex]);
+
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 0 L 25 -28 L 100 -28 L 100 175 L 50 175', 'Source above target with vertex');
+
+ let position1 = r1.position();
+ let position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 150 L 25 100 L 100 100 L 100 25 L 78 25 L 50 25', 'Target above source with vertex');
+
+ r1.position(0, 0);
+ r2.position(position.y, position.x);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 0 L 25 -28 L 100 -28 L 100 100 L 228 100 L 228 25 L 200 25', 'Source on the left of target with vertex');
+
+ position1 = r1.position();
+ position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 175 0 L 175 -28 L 100 -28 L 100 100 L 75 100 L 75 25 L 50 25', 'Target on the left of source with vertex');
+ });
+
+ QUnit.test('rightAngle routing with vertex - source: top, target: bottom', function(assert) {
+ const vertex = { x: 100, y: 100 };
+ const [r1, r2, l] = this.addTestSubjectsWithVertices('top', 'bottom', [vertex]);
+
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 0 L 25 -28 L 100 -28 L 100 228 L 25 228 L 25 200', 'Source above target with vertex');
+
+ let position1 = r1.position();
+ let position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 150 L 25 100 L 128 100 L 128 128 L 25 128 L 25 50', 'Target above source with vertex');
+
+ r1.position(0, 0);
+ r2.position(position.y, position.x);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 0 L 25 -28 L 100 -28 L 100 100 L 175 100 L 175 50', 'Source on the left of target with vertex');
+
+ position1 = r1.position();
+ position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 175 0 L 175 -28 L 100 -28 L 100 100 L 25 100 L 25 50', 'Target on the left of source with vertex');
+ });
+
+ QUnit.test('rightAngle routing with vertex - source: top, target: left', function(assert) {
+ const vertex = { x: 100, y: 100 };
+ const [r1, r2, l] = this.addTestSubjectsWithVertices('top', 'left', [vertex]);
+
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 0 L 25 -28 L 100 -28 L 100 228 L -28 228 L -28 175 L 0 175', 'Source above target with vertex');
+
+ let position1 = r1.position();
+ let position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 150 L 25 100 L 100 100 L 100 75 L -28 75 L -28 25 L 0 25', 'Target above source with vertex');
+
+ r1.position(0, 0);
+ r2.position(position.y, position.x);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 0 L 25 -28 L 100 -28 L 100 100 L 125 100 L 125 25 L 150 25', 'Source on the left of target with vertex');
+
+ position1 = r1.position();
+ position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 175 0 L 175 -28 L 100 -28 L 100 100 L -28 100 L -28 25 L 0 25', 'Target on the left of source with vertex');
+ });
+
+ QUnit.test('rightAngle routing with vertex - source: right, target: top', function(assert) {
+ const vertex = { x: 100, y: 100 };
+ const [r1, r2, l] = this.addTestSubjectsWithVertices('right', 'top', [vertex]);
+
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 50 25 L 100 25 L 100 111 L 25 111 L 25 150', 'Source above target with vertex');
+
+ let position1 = r1.position();
+ let position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 50 175 L 100 175 L 100 -28 L 25 -28 L 25 0', 'Target above source with vertex');
+
+ r1.position(0, 0);
+ r2.position(position.y, position.x);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 50 25 L 100 25 L 100 100 L 125 100 L 125 -28 L 175 -28 L 175 0', 'Source on the left of target with vertex');
+
+ position1 = r1.position();
+ position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 200 25 L 228 25 L 228 100 L 75 100 L 75 -28 L 25 -28 L 25 0', 'Target on the left of source with vertex');
+ });
+
+ QUnit.test('rightAngle routing with vertex - source: right, target: right', function(assert) {
+ const vertex = { x: 100, y: 100 };
+ const [r1, r2, l] = this.addTestSubjectsWithVertices('right', 'right', [vertex]);
+
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 50 25 L 100 25 L 100 175 L 50 175', 'Source above target with vertex');
+
+ let position1 = r1.position();
+ let position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 50 175 L 100 175 L 100 25 L 50 25', 'Target above source with vertex');
+
+ r1.position(0, 0);
+ r2.position(position.y, position.x);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 50 25 L 100 25 L 100 100 L 228 100 L 228 25 L 200 25', 'Source on the left of target with vertex');
+
+ position1 = r1.position();
+ position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 200 25 L 228 25 L 228 100 L 89 100 L 89 25 L 50 25', 'Target on the left of source with vertex');
+ });
+
+ QUnit.test('rightAngle routing with vertex - source: right, target: bottom', function(assert) {
+ const vertex = { x: 100, y: 100 };
+ const [r1, r2, l] = this.addTestSubjectsWithVertices('right', 'bottom', [vertex]);
+
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 50 25 L 100 25 L 100 228 L 25 228 L 25 200', 'Source above target with vertex');
+
+ let position1 = r1.position();
+ let position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 50 175 L 100 175 L 100 89 L 25 89 L 25 50', 'Target above source with vertex');
+
+ r1.position(0, 0);
+ r2.position(position.y, position.x);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 50 25 L 100 25 L 100 100 L 175 100 L 175 50', 'Source on the left of target with vertex');
+
+ position1 = r1.position();
+ position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 200 25 L 228 25 L 228 100 L 25 100 L 25 50', 'Target on the left of source with vertex');
+ });
+
+ QUnit.test('rightAngle routing with vertex - source: right, target: left', function(assert) {
+ const vertex = { x: 100, y: 100 };
+
+ const [r1, r2, l] = this.addTestSubjectsWithVertices('right', 'left', [vertex]);
+
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 50 25 L 100 25 L 100 228 L -28 228 L -28 175 L 0 175', 'Source above target with vertex');
+
+ let position1 = r1.position();
+ let position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 50 175 L 100 175 L 100 -28 L -28 -28 L -28 25 L 0 25', 'Target above source with vertex');
+
+ r1.position(0, 0);
+ r2.position(position.y, position.x);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 50 25 L 100 25 L 100 100 L 125 100 L 125 25 L 150 25', 'Source on the left of target with vertex');
+
+ position1 = r1.position();
+ position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 200 25 L 228 25 L 228 100 L -28 100 L -28 25 L 0 25', 'Target on the left of source with vertex');
+ });
+
+ QUnit.test('rightAngle routing with vertex - source: bottom, target: top', function(assert) {
+ const vertex = { x: 100, y: 100 };
+ const [r1, r2, l] = this.addTestSubjectsWithVertices('bottom', 'top', [vertex]);
+
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 50 L 25 100 L 100 100 L 100 125 L 25 125 L 25 150', 'Source above target with vertex');
+
+ let position1 = r1.position();
+ let position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 200 L 25 228 L 100 228 L 100 -28 L 25 -28 L 25 0', 'Target above source with vertex');
+
+ r1.position(0, 0);
+ r2.position(position.y, position.x);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 50 L 25 100 L 125 100 L 125 -28 L 175 -28 L 175 0', 'Source on the left of target with vertex');
+
+ position1 = r1.position();
+ position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 175 50 L 175 100 L 75 100 L 75 -28 L 25 -28 L 25 0', 'Target on the left of source with vertex');
+ });
+
+ QUnit.test('rightAngle routing with vertex - source: bottom, target: right', function(assert) {
+ const vertex = { x: 100, y: 100 };
+ const [r1, r2, l] = this.addTestSubjectsWithVertices('bottom', 'right', [vertex]);
+
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 50 L 25 100 L 100 100 L 100 175 L 78 175 L 50 175', 'Source above target with vertex');
+
+ let position1 = r1.position();
+ let position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 200 L 25 228 L 100 228 L 100 25 L 50 25', 'Target above source with vertex');
+
+ r1.position(0, 0);
+ r2.position(position.y, position.x);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 50 L 25 100 L 228 100 L 228 25 L 200 25', 'Source on the left of target with vertex');
+
+ position1 = r1.position();
+ position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 175 50 L 175 100 L 89 100 L 89 25 L 50 25', 'Target on the left of source with vertex');
+ });
+
+ QUnit.test('rightAngle routing with vertex - source: bottom, target: bottom', function(assert) {
+ const vertex = { x: 100, y: 100 };
+ const [r1, r2, l] = this.addTestSubjectsWithVertices('bottom', 'bottom', [vertex]);
+
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 50 L 25 100 L 100 100 L 100 228 L 25 228 L 25 200', 'Source above target with vertex');
+
+ let position1 = r1.position();
+ let position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 200 L 25 228 L 100 228 L 100 89 L 25 89 L 25 50', 'Target above source with vertex');
+
+ r1.position(0, 0);
+ r2.position(position.y, position.x);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 50 L 25 100 L 175 100 L 175 50', 'Source on the left of target with vertex');
+
+ position1 = r1.position();
+ position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 175 50 L 175 100 L 25 100 L 25 50', 'Target on the left of source with vertex');
+ });
+
+ QUnit.test('rightAngle routing with vertex - source: bottom, target: left', function(assert) {
+ const vertex = { x: 100, y: 100 };
+ const [r1, r2, l] = this.addTestSubjectsWithVertices('bottom', 'left', [vertex]);
+
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 50 L 25 100 L 100 100 L 100 125 L -28 125 L -28 175 L 0 175', 'Source above target with vertex');
+
+ let position1 = r1.position();
+ let position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 200 L 25 228 L 100 228 L 100 -28 L -28 -28 L -28 25 L 0 25', 'Target above source with vertex');
+
+ r1.position(0, 0);
+ r2.position(position.y, position.x);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 50 L 25 100 L 111 100 L 111 25 L 150 25', 'Source on the left of target with vertex');
+
+ position1 = r1.position();
+ position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 175 50 L 175 100 L -28 100 L -28 25 L 0 25', 'Target on the left of source with vertex');
+ });
+
+ QUnit.test('rightAngle routing with vertex - source: left, target: top', function(assert) {
+ const vertex = { x: 100, y: 100 };
+ const [r1, r2, l] = this.addTestSubjectsWithVertices('left', 'top', [vertex]);
+
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 0 25 L -28 25 L -28 100 L 100 100 L 100 125 L 25 125 L 25 150', 'Source above target with vertex');
+
+ let position1 = r1.position();
+ let position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 0 175 L -28 175 L -28 100 L 100 100 L 100 -28 L 25 -28 L 25 0', 'Target above source with vertex');
+
+ r1.position(0, 0);
+ r2.position(position.y, position.x);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 0 25 L -28 25 L -28 100 L 125 100 L 125 -28 L 175 -28 L 175 0', 'Source on the left of target with vertex');
+
+ position1 = r1.position();
+ position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 150 25 L 100 25 L 100 100 L 75 100 L 75 -28 L 25 -28 L 25 0', 'Target on the left of source with vertex');
+ });
+
+ QUnit.test('rightAngle routing with vertex - source: left, target: right', function(assert) {
+ const vertex = { x: 100, y: 100 };
+ const [r1, r2, l] = this.addTestSubjectsWithVertices('left', 'right', [vertex]);
+
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 0 25 L -28 25 L -28 100 L 100 100 L 100 175 L 78 175 L 50 175', 'Source above target with vertex');
+
+ let position1 = r1.position();
+ let position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 0 175 L -28 175 L -28 100 L 100 100 L 100 25 L 78 25 L 50 25', 'Target above source with vertex');
+
+ r1.position(0, 0);
+ r2.position(position.y, position.x);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 0 25 L -28 25 L -28 100 L 228 100 L 228 25 L 200 25', 'Source on the left of target with vertex');
+
+ position1 = r1.position();
+ position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 150 25 L 100 25 L 100 100 L 75 100 L 75 25 L 50 25', 'Target on the left of source with vertex');
+ });
+
+ QUnit.test('rightAngle routing with vertex - source: left, target: bottom', function(assert) {
+ const vertex = { x: 100, y: 100 };
+ const [r1, r2, l] = this.addTestSubjectsWithVertices('left', 'bottom', [vertex]);
+
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 0 25 L -28 25 L -28 100 L 100 100 L 100 228 L 25 228 L 25 200', 'Source above target with vertex');
+
+ let position1 = r1.position();
+ let position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 0 175 L -28 175 L -28 100 L 128 100 L 128 128 L 25 128 L 25 50', 'Target above source with vertex');
+
+ r1.position(0, 0);
+ r2.position(position.y, position.x);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 0 25 L -28 25 L -28 100 L 175 100 L 175 50', 'Source on the left of target with vertex');
+
+ position1 = r1.position();
+ position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 150 25 L 100 25 L 100 100 L 25 100 L 25 50', 'Target on the left of source with vertex');
+ });
+
+ QUnit.test('rightAngle routing with vertex - source: left, target: left', function(assert) {
+ const vertex = { x: 100, y: 100 };
+ const [r1, r2, l] = this.addTestSubjectsWithVertices('left', 'left', [vertex]);
+
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 0 25 L -28 25 L -28 100 L 100 100 L 100 125 L -28 125 L -28 175 L 0 175', 'Source above target with vertex');
+
+ let position1 = r1.position();
+ let position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 0 175 L -28 175 L -28 100 L 100 100 L 100 75 L -28 75 L -28 25 L 0 25', 'Target above source with vertex');
+
+ r1.position(0, 0);
+ r2.position(position.y, position.x);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 0 25 L -28 25 L -28 100 L 111 100 L 111 25 L 150 25', 'Source on the left of target with vertex');
+
+ position1 = r1.position();
+ position2 = r2.position();
+
+ r1.position(position2.x, position2.y);
+ r2.position(position1.x, position1.y);
+
+ d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 150 25 L 100 25 L 100 100 L -28 100 L -28 25 L 0 25', 'Target on the left of source with vertex');
+ });
+
+ QUnit.test('rightAngle routing with vertex inside the source element bbox - source: top', function(assert) {
+ const vertices = [{ x: size.width * 0.75, y: size.height / 2 }, { x: 100, y: 100 }];
+ const [, , l] = this.addTestSubjectsWithVertices('top', 'top', vertices);
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 0 L 25 -28 L 53 -28 L 53 12.5 L 37.5 12.5 L 37.5 100 L 100 100 L 100 125 L 25 125 L 25 150', 'Source above target with vertex inside the source element bbox');
+ });
+
+ QUnit.test('rightAngle routing with vertex inside the source element bbox - source: right', function(assert) {
+ const vertices = [{ x: 0, y: size.y }, { x: 100, y: 100 }];
+ const [, , l] = this.addTestSubjectsWithVertices('right', 'top', vertices);
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 50 25 L 78 25 L 78 -3 L 25 -3 L 25 0 L 0 0 L 0 100 L 100 100 L 100 125 L 25 125 L 25 150', 'Source above target with vertex inside the source element bbox');
+ });
+
+ QUnit.test('rightAngle routing with vertex inside the source element bbox - source: bottom', function(assert) {
+ const vertices = [{ x: size.width, y: size.y }, { x: 100, y: 100 }];
+ const [, , l] = this.addTestSubjectsWithVertices('bottom', 'top', vertices);
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 50 L 25 78 L 53 78 L 53 25 L 50 25 L 50 0 L 100 0 L 100 111 L 25 111 L 25 150', 'Source above target with vertex inside the source element bbox');
+ });
+
+ QUnit.test('rightAngle routing with vertex inside the source element bbox - source: left', function(assert) {
+ const vertices = [{ x: size.width, y: size.y }, { x: 100, y: 100 }];
+ const [, , l] = this.addTestSubjectsWithVertices('left', 'top', vertices);
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 0 25 L -28 25 L -28 -3 L 25 -3 L 25 0 L 100 0 L 100 111 L 25 111 L 25 150', 'Source above target with vertex inside the source element bbox');
+ });
+
+ QUnit.test('rightAngle routing with vertex inside the target element bbox - target: top', function(assert) {
+ const vertices = [{ x: 100, y: 100 }, { x: position.x + size.width * 0.75, y: position.y + size.height / 2 }];
+ const [, , l] = this.addTestSubjectsWithVertices('top', 'top', vertices);
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 0 L 25 -28 L 100 -28 L 100 175 L 31.25 175 L 31.25 122 L 25 122 L 25 150', 'Source above target with vertex inside the target element bbox');
+ });
+
+ QUnit.test('rightAngle routing with vertex inside the target element bbox - target: right', function(assert) {
+ const vertices = [{ x: 100, y: 100 }, { x: position.x, y: position.y }];
+ const [, , l] = this.addTestSubjectsWithVertices('top', 'right', vertices);
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 0 L 25 -28 L 100 -28 L 100 150 L 0 150 L 0 203 L 78 203 L 78 175 L 50 175', 'Source above target with vertex inside the target element bbox');
+ });
+
+ QUnit.test('rightAngle routing with vertex inside the target element bbox - target: bottom', function(assert) {
+ const vertices = [{ x: 100, y: 100 }, { x: position.x + size.width, y: position.y }];
+ const [, , l] = this.addTestSubjectsWithVertices('top', 'bottom', vertices);
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 0 L 25 -28 L 100 -28 L 100 150 L 37.5 150 L 37.5 228 L 25 228 L 25 200', 'Source above target with vertex inside the target element bbox');
+ });
+
+ QUnit.test('rightAngle routing with vertex inside the target element bbox - target: left', function(assert) {
+ const vertices = [{ x: 100, y: 100 }, { x: position.x + size.width, y: position.y }];
+ const [, , l] = this.addTestSubjectsWithVertices('top', 'left', vertices);
+ let d = this.paper.findViewByModel(l).metrics.data;
+
+ assert.checkDataPath(d, 'M 25 0 L 25 -28 L 100 -28 L 100 150 L 25 150 L 25 147 L -28 147 L -28 175 L 0 175', 'Source above target with vertex inside the target element bbox');
+ });
});
diff --git a/types/joint.d.ts b/types/joint.d.ts
index d58ad2cbe..5ee543d81 100644
--- a/types/joint.d.ts
+++ b/types/joint.d.ts
@@ -3535,6 +3535,8 @@ export namespace routers {
interface RightAngleRouterArguments {
margin?: number;
+ /** @experimental before version 4.0 */
+ useVertices?: boolean;
sourceDirection?: RightAngleDirections;
targetDirection?: RightAngleDirections;
}