feat(modeling): move labels with connections
* move on segment dragging * move on reconnect * move on bendpoint add / remove Closes #331
This commit is contained in:
parent
713021ecff
commit
fd198b6059
|
@ -4,12 +4,14 @@ var assign = require('lodash/object/assign'),
|
|||
inherits = require('inherits');
|
||||
|
||||
var LabelUtil = require('../../../util/LabelUtil'),
|
||||
LabelLayoutUtil = require('./util/LabelLayoutUtil'),
|
||||
ModelUtil = require('../../../util/ModelUtil'),
|
||||
is = ModelUtil.is,
|
||||
getBusinessObject = ModelUtil.getBusinessObject;
|
||||
|
||||
var hasExternalLabel = LabelUtil.hasExternalLabel,
|
||||
getExternalLabelMid = LabelUtil.getExternalLabelMid;
|
||||
getExternalLabelMid = LabelUtil.getExternalLabelMid,
|
||||
getLabelAdjustment = LabelLayoutUtil.getLabelAdjustment;
|
||||
|
||||
var CommandInterceptor = require('diagram-js/lib/command/CommandInterceptor');
|
||||
|
||||
|
@ -70,6 +72,55 @@ function LabelSupport(eventBus, modeling, bpmnFactory) {
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
this.postExecute([
|
||||
'connection.layout',
|
||||
'connection.reconnectEnd',
|
||||
'connection.reconnectStart'
|
||||
], function(e) {
|
||||
|
||||
var context = e.context,
|
||||
oldWaypoints = context.oldWaypoints,
|
||||
newWaypoints = context.newWaypoints || context.connection.waypoints,
|
||||
label = context.connection.label,
|
||||
hints = context.hints || {},
|
||||
delta;
|
||||
|
||||
if (e.command != 'connection.layout') {
|
||||
hints.startChanged = e.command == 'connection.reconnectStart' ? true : false;
|
||||
hints.endChanged = e.command == 'connection.reconnectEnd' ? true : false;
|
||||
}
|
||||
|
||||
if (!label) {
|
||||
return;
|
||||
}
|
||||
|
||||
delta = getLabelAdjustment(label, newWaypoints, oldWaypoints, hints);
|
||||
|
||||
modeling.moveShape(label, delta);
|
||||
});
|
||||
|
||||
this.postExecute([
|
||||
'connection.updateWaypoints'
|
||||
], function(e) {
|
||||
|
||||
var context = e.context,
|
||||
oldWaypoints = context.oldWaypoints,
|
||||
newWaypoints = context.newWaypoints,
|
||||
label = context.connection.label,
|
||||
hints = context.hints || {},
|
||||
delta;
|
||||
|
||||
if (!label) {
|
||||
return;
|
||||
}
|
||||
|
||||
delta = getLabelAdjustment(label, newWaypoints, oldWaypoints, hints);
|
||||
|
||||
modeling.moveShape(label, delta);
|
||||
});
|
||||
|
||||
this.postExecute([ 'shape.replace' ], function(e) {
|
||||
var context = e.context,
|
||||
newShape = context.newShape,
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* Returns the length of a vector
|
||||
*
|
||||
* @param {Vector}
|
||||
* @return {Float}
|
||||
*/
|
||||
function vectorLength(v) {
|
||||
return Math.sqrt( Math.pow(v.x, 2) + Math.pow(v.y, 2) );
|
||||
}
|
||||
|
||||
module.exports.vectorLength = vectorLength;
|
||||
|
||||
/**
|
||||
* Calculates the angle between a line a the yAxis
|
||||
*
|
||||
* @param {Array}
|
||||
* @return {Float}
|
||||
*/
|
||||
function getAngle(line) {
|
||||
// return value is between 0, 180 and -180, -0
|
||||
// @janstuemmel: maybe replace return a/b with b/a
|
||||
return Math.atan( (line[1].y - line[0].y) / (line[1].x - line[0].x) );
|
||||
}
|
||||
|
||||
module.exports.getAngle = getAngle;
|
||||
|
||||
/**
|
||||
* Rotates a vector by a given angle
|
||||
*
|
||||
* @param {Vector}
|
||||
* @param {Float} Angle in radians
|
||||
* @return {Vector}
|
||||
*/
|
||||
function rotateVector(vector, angle) {
|
||||
return (!angle) ? vector : {
|
||||
x: Math.cos(angle) * vector.x - Math.sin(angle) * vector.y,
|
||||
y: Math.sin(angle) * vector.x + Math.cos(angle) * vector.y
|
||||
};
|
||||
}
|
||||
|
||||
module.exports.rotateVector = rotateVector;
|
||||
|
||||
/**
|
||||
* Solves a 2D equation system
|
||||
* a + r*b = c, where a,b,c are 2D vectors
|
||||
*
|
||||
* @param {Vector}
|
||||
* @param {Vector}
|
||||
* @param {Vector}
|
||||
* @return {Float}
|
||||
*/
|
||||
function solveLambaSystem(a, b, c) {
|
||||
|
||||
// the 2d system
|
||||
var system = [
|
||||
{ n: a[0] - c[0], lambda: b[0] },
|
||||
{ n: a[1] - c[1], lambda: b[1] }
|
||||
];
|
||||
|
||||
// solve
|
||||
var n = system[0].n * b[0] + system[1].n * b[1],
|
||||
l = system[0].lambda * b[0] + system[1].lambda * b[1];
|
||||
|
||||
return -n/l;
|
||||
}
|
||||
|
||||
/**
|
||||
* Position of perpendicular foot
|
||||
*
|
||||
* @param {Point}
|
||||
* @param [ {Point}, {Point} ] line defined throug two points
|
||||
* @return {Point} the perpendicular foot position
|
||||
*/
|
||||
function perpendicularFoot(point, line) {
|
||||
|
||||
var a = line[0], b = line[1];
|
||||
|
||||
// relative position of b from a
|
||||
var bd = { x: b.x - a.x, y: b.y - a.y };
|
||||
|
||||
// solve equation system to the parametrized vectors param real value
|
||||
var r = solveLambaSystem( [ a.x, a.y ], [ bd.x, bd.y ], [ point.x, point.y ] );
|
||||
|
||||
return { x: a.x + r*bd.x, y: a.y + r*bd.y };
|
||||
|
||||
}
|
||||
|
||||
module.exports.perpendicularFoot = perpendicularFoot;
|
||||
|
||||
/**
|
||||
* Calculates the distance between a point and a line
|
||||
*
|
||||
* @param {Point}
|
||||
* @param [ {Point}, {Point} ] line defined throug two points
|
||||
* @return {Float} distance
|
||||
*/
|
||||
function getDistancePointLine(point, line) {
|
||||
|
||||
var pfPoint = perpendicularFoot(point, line);
|
||||
|
||||
// distance vector
|
||||
var connectionVector = {
|
||||
x: pfPoint.x - point.x,
|
||||
y: pfPoint.y - point.y
|
||||
};
|
||||
|
||||
return vectorLength(connectionVector);
|
||||
}
|
||||
|
||||
module.exports.getDistancePointLine = getDistancePointLine;
|
||||
|
||||
/**
|
||||
* Calculates the distance between two points
|
||||
*
|
||||
* @param {Point}
|
||||
* @param {Point}
|
||||
* @return {Float} distance
|
||||
*/
|
||||
function getDistancePointPoint(point1, point2) {
|
||||
|
||||
return vectorLength({
|
||||
x: point1.x - point2.x,
|
||||
y: point1.y - point2.y
|
||||
});
|
||||
}
|
||||
|
||||
module.exports.getDistancePointPoint = getDistancePointPoint;
|
|
@ -0,0 +1,216 @@
|
|||
'use strict';
|
||||
|
||||
var GeometricUtil = require('./GeometricUtil');
|
||||
|
||||
var getDistance = require('./GeometricUtil').getDistancePointPoint;
|
||||
|
||||
var getAttachment = require('./LineAttachmentUtil').getAttachment;
|
||||
|
||||
|
||||
function findNewLabelLineStartIndex(oldWaypoints, newWaypoints, attachment, hints) {
|
||||
|
||||
var index = attachment.segmentIndex;
|
||||
|
||||
var offset = newWaypoints.length - oldWaypoints.length;
|
||||
|
||||
// segmentMove happend
|
||||
if (hints.segmentMove) {
|
||||
|
||||
var oldSegmentStartIndex = hints.segmentMove.segmentStartIndex,
|
||||
newSegmentStartIndex = hints.segmentMove.newSegmentStartIndex;
|
||||
|
||||
// if label was on moved segment return new segment index
|
||||
if (index === oldSegmentStartIndex) {
|
||||
return newSegmentStartIndex;
|
||||
}
|
||||
|
||||
// label is after new segment index
|
||||
if (index >= newSegmentStartIndex) {
|
||||
return (index+offset < newSegmentStartIndex) ? newSegmentStartIndex : index+offset;
|
||||
}
|
||||
|
||||
// if label is before new segment index
|
||||
return index;
|
||||
}
|
||||
|
||||
// bendpointMove happend
|
||||
if (hints.bendpointMove) {
|
||||
|
||||
var insert = hints.bendpointMove.insert,
|
||||
bendpointIndex = hints.bendpointMove.bendpointIndex,
|
||||
newIndex;
|
||||
|
||||
// waypoints length didnt change
|
||||
if (offset === 0) {
|
||||
return index;
|
||||
}
|
||||
|
||||
// label behind new/removed bendpoint
|
||||
if (index >= bendpointIndex) {
|
||||
newIndex = insert ? index + 1 : index - 1;
|
||||
}
|
||||
|
||||
// label before new/removed bendpoint
|
||||
if (index < bendpointIndex) {
|
||||
|
||||
newIndex = index;
|
||||
|
||||
// decide label should take right or left segment
|
||||
if (insert && attachment.type !== 'bendpoint' && bendpointIndex-1 === index) {
|
||||
|
||||
var rel = relativePositionMidWaypoint(newWaypoints, bendpointIndex);
|
||||
|
||||
if (rel < attachment.relativeLocation) {
|
||||
newIndex++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newIndex;
|
||||
}
|
||||
|
||||
// start/end changed
|
||||
if (offset === 0) {
|
||||
return index;
|
||||
}
|
||||
|
||||
if (hints.connectionStart) {
|
||||
return (index === 0) ? 0 : null;
|
||||
}
|
||||
|
||||
if (hints.connectionEnd) {
|
||||
return (index === oldWaypoints.length - 2) ? newWaypoints.length - 2 : null;
|
||||
}
|
||||
|
||||
// if nothing fits, return null
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports.findNewLabelLineStartIndex = findNewLabelLineStartIndex;
|
||||
|
||||
|
||||
/**
|
||||
* Calculate the required adjustment (move delta) for the given label
|
||||
* after the connection waypoints got updated.
|
||||
*
|
||||
* @param {djs.model.Label} label
|
||||
* @param {Array<Point>} newWaypoints
|
||||
* @param {Array<Point>} oldWaypoints
|
||||
* @param {Object} hints
|
||||
*
|
||||
* @return {Point} delta
|
||||
*/
|
||||
function getLabelAdjustment(label, newWaypoints, oldWaypoints, hints) {
|
||||
|
||||
var x = 0,
|
||||
y = 0;
|
||||
|
||||
var labelPosition = getLabelMid(label);
|
||||
|
||||
// get closest attachment
|
||||
var attachment = getAttachment(labelPosition, oldWaypoints),
|
||||
oldLabelLineIndex = attachment.segmentIndex,
|
||||
newLabelLineIndex = findNewLabelLineStartIndex(oldWaypoints, newWaypoints, attachment, hints);
|
||||
|
||||
if ( newLabelLineIndex === null ) {
|
||||
return { x: x, y: y };
|
||||
}
|
||||
|
||||
// should never happen
|
||||
// TODO(@janstuemmel): throw an error here when connectionSegmentMove is refactored
|
||||
if (newLabelLineIndex < 0 ||
|
||||
newLabelLineIndex > newWaypoints.length - 2) {
|
||||
return { x: x, y: y };
|
||||
}
|
||||
|
||||
var oldLabelLine = getLine(oldWaypoints, oldLabelLineIndex),
|
||||
newLabelLine = getLine(newWaypoints, newLabelLineIndex),
|
||||
oldFoot = attachment.position;
|
||||
|
||||
var relativeFootPosition = getRelativeFootPosition(oldLabelLine, oldFoot),
|
||||
angleDelta = getAngleDelta(oldLabelLine, newLabelLine);
|
||||
|
||||
// special rule if label on bendpoint
|
||||
if (attachment.type === 'bendpoint') {
|
||||
|
||||
var offset = newWaypoints.length - oldWaypoints.length,
|
||||
oldBendpointIndex = attachment.bendpointIndex,
|
||||
oldBendpoint = oldWaypoints[oldBendpointIndex];
|
||||
|
||||
// bendpoint position hasnt changed, return same position
|
||||
if (newWaypoints.indexOf(oldBendpoint) !== -1) {
|
||||
return { x: x, y: y };
|
||||
}
|
||||
|
||||
// new bendpoint and old bendpoint have same index, then just return the offset
|
||||
if (offset === 0) {
|
||||
var newBendpoint = newWaypoints[oldBendpointIndex];
|
||||
|
||||
return {
|
||||
x: newBendpoint.x - attachment.position.x,
|
||||
y: newBendpoint.y - attachment.position.y
|
||||
};
|
||||
}
|
||||
|
||||
// if bendpoints get removed
|
||||
if (offset < 0 && oldBendpointIndex !== 0 && oldBendpointIndex < oldWaypoints.length - 1) {
|
||||
relativeFootPosition = relativePositionMidWaypoint(oldWaypoints, oldBendpointIndex);
|
||||
}
|
||||
}
|
||||
|
||||
var newFoot = {
|
||||
x: (newLabelLine[1].x - newLabelLine[0].x) * relativeFootPosition + newLabelLine[0].x,
|
||||
y: (newLabelLine[1].y - newLabelLine[0].y) * relativeFootPosition + newLabelLine[0].y
|
||||
};
|
||||
|
||||
// the rotated vector to label
|
||||
var newLabelVector = GeometricUtil.rotateVector({
|
||||
x: labelPosition.x - oldFoot.x,
|
||||
y: labelPosition.y - oldFoot.y
|
||||
}, angleDelta);
|
||||
|
||||
// the new relative position
|
||||
x = newFoot.x + newLabelVector.x - labelPosition.x;
|
||||
y = newFoot.y + newLabelVector.y - labelPosition.y;
|
||||
|
||||
return { x: x, y: y };
|
||||
}
|
||||
|
||||
module.exports.getLabelAdjustment = getLabelAdjustment;
|
||||
|
||||
|
||||
//// HELPERS ///////
|
||||
|
||||
function relativePositionMidWaypoint(waypoints, idx) {
|
||||
|
||||
var distanceSegment1 = getDistance(waypoints[idx-1], waypoints[idx]),
|
||||
distanceSegment2 = getDistance(waypoints[idx], waypoints[idx+1]);
|
||||
|
||||
var relativePosition = distanceSegment1 / ( distanceSegment1 + distanceSegment2 );
|
||||
|
||||
return relativePosition;
|
||||
|
||||
}
|
||||
|
||||
function getLabelMid(label) {
|
||||
return {
|
||||
x: label.x + label.width / 2,
|
||||
y: label.y + label.height / 2
|
||||
};
|
||||
}
|
||||
|
||||
function getAngleDelta(l1, l2) {
|
||||
var a1 = GeometricUtil.getAngle(l1),
|
||||
a2 = GeometricUtil.getAngle(l2);
|
||||
return a2 - a1;
|
||||
}
|
||||
|
||||
function getLine(waypoints, idx) {
|
||||
return [ waypoints[idx], waypoints[idx+1] ];
|
||||
}
|
||||
|
||||
function getRelativeFootPosition(line, foot) {
|
||||
var length = GeometricUtil.getDistancePointPoint(line[0], line[1]),
|
||||
lengthToFoot = GeometricUtil.getDistancePointPoint(line[0], foot);
|
||||
return lengthToFoot / length;
|
||||
}
|
|
@ -0,0 +1,214 @@
|
|||
'use strict';
|
||||
|
||||
var sqrt = Math.sqrt,
|
||||
min = Math.min,
|
||||
max = Math.max;
|
||||
|
||||
/**
|
||||
* Calculate the square (power to two) of a number.
|
||||
*
|
||||
* @param {Number} n
|
||||
*
|
||||
* @return {Number}
|
||||
*/
|
||||
function sq(n) {
|
||||
return Math.pow(n, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get distance between two points.
|
||||
*
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
*
|
||||
* @return {Number}
|
||||
*/
|
||||
function getDistance(p1, p2) {
|
||||
return sqrt(sq(p1.x - p2.x) + sq(p1.y - p2.y));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the attachment of the given point on the specified line.
|
||||
*
|
||||
* The attachment is either a bendpoint (attached to the given point)
|
||||
* or segment (attached to a location on a line segment) attachment:
|
||||
*
|
||||
* ```javascript
|
||||
* var pointAttachment = {
|
||||
* type: 'bendpoint',
|
||||
* bendpointIndex: 3,
|
||||
* position: { x: 10, y: 10 } // the attach point on the line
|
||||
* };
|
||||
*
|
||||
* var segmentAttachment = {
|
||||
* type: 'segment',
|
||||
* segmentIndex: 2,
|
||||
* relativeLocation: 0.31, // attach point location between 0 (at start) and 1 (at end)
|
||||
* position: { x: 10, y: 10 } // the attach point on the line
|
||||
* };
|
||||
* ```
|
||||
*
|
||||
* @param {Point} point
|
||||
* @param {Array<Point>} line
|
||||
*
|
||||
* @return {Object} attachment
|
||||
*/
|
||||
function getAttachment(point, line) {
|
||||
|
||||
var idx = 0,
|
||||
segmentStart,
|
||||
segmentEnd,
|
||||
segmentStartDistance,
|
||||
segmentEndDistance,
|
||||
attachmentPosition,
|
||||
minDistance,
|
||||
intersections,
|
||||
attachment,
|
||||
attachmentDistance,
|
||||
closestAttachmentDistance,
|
||||
closestAttachment;
|
||||
|
||||
for (idx = 0; idx < line.length - 1; idx++) {
|
||||
|
||||
segmentStart = line[idx];
|
||||
segmentEnd = line[idx + 1];
|
||||
|
||||
if (pointsEqual(segmentStart, segmentEnd)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
segmentStartDistance = getDistance(point, segmentStart);
|
||||
segmentEndDistance = getDistance(point, segmentEnd);
|
||||
|
||||
minDistance = min(segmentStartDistance, segmentEndDistance);
|
||||
|
||||
intersections = getCircleSegmentIntersections(segmentStart, segmentEnd, point, minDistance);
|
||||
|
||||
if (intersections.length < 1) {
|
||||
throw new Error('expected between [1, 2] circle -> line intersections');
|
||||
}
|
||||
|
||||
// one intersection -> bendpoint attachment
|
||||
if (intersections.length === 1) {
|
||||
attachment = {
|
||||
type: 'bendpoint',
|
||||
position: intersections[0],
|
||||
segmentIndex: idx,
|
||||
bendpointIndex: pointsEqual(segmentStart, intersections[0]) ? idx : idx + 1
|
||||
};
|
||||
}
|
||||
|
||||
// two intersections -> segment attachment
|
||||
if (intersections.length === 2) {
|
||||
|
||||
attachmentPosition = mid(intersections[0], intersections[1]);
|
||||
|
||||
attachment = {
|
||||
type: 'segment',
|
||||
position: attachmentPosition,
|
||||
segmentIndex: idx,
|
||||
relativeLocation: getDistance(segmentStart, attachmentPosition) / getDistance(segmentStart, segmentEnd)
|
||||
};
|
||||
}
|
||||
|
||||
attachmentDistance = getDistance(attachment.position, point);
|
||||
|
||||
if (!closestAttachment || closestAttachmentDistance > attachmentDistance) {
|
||||
closestAttachment = attachment;
|
||||
closestAttachmentDistance = attachmentDistance;
|
||||
}
|
||||
}
|
||||
|
||||
return closestAttachment;
|
||||
}
|
||||
|
||||
module.exports.getAttachment = getAttachment;
|
||||
|
||||
/**
|
||||
* Gets the intersection between a circle and a line segment.
|
||||
*
|
||||
* @param {Point} s1 segment start
|
||||
* @param {Point} s2 segment end
|
||||
* @param {Point} cc circle center
|
||||
* @param {Number} cr circle radius
|
||||
*
|
||||
* @return {Array<Point>} intersections
|
||||
*/
|
||||
function getCircleSegmentIntersections(s1, s2, cc, cr) {
|
||||
|
||||
var baX = s2.x - s1.x;
|
||||
var baY = s2.y - s1.y;
|
||||
var caX = cc.x - s1.x;
|
||||
var caY = cc.y - s1.y;
|
||||
|
||||
var a = baX * baX + baY * baY;
|
||||
var bBy2 = baX * caX + baY * caY;
|
||||
var c = caX * caX + caY * caY - cr * cr;
|
||||
|
||||
var pBy2 = bBy2 / a;
|
||||
var q = c / a;
|
||||
|
||||
var disc = pBy2 * pBy2 - q;
|
||||
if (disc < 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// if disc == 0 ... dealt with later
|
||||
var tmpSqrt = sqrt(disc);
|
||||
var abScalingFactor1 = -pBy2 + tmpSqrt;
|
||||
var abScalingFactor2 = -pBy2 - tmpSqrt;
|
||||
|
||||
var i1 = {
|
||||
x: round(s1.x - baX * abScalingFactor1),
|
||||
y: round(s1.y - baY * abScalingFactor1)
|
||||
};
|
||||
|
||||
if (disc === 0) { // abScalingFactor1 == abScalingFactor2
|
||||
return [ i1 ];
|
||||
}
|
||||
|
||||
var i2 = {
|
||||
x: round(s1.x - baX * abScalingFactor2),
|
||||
y: round(s1.y - baY * abScalingFactor2)
|
||||
};
|
||||
|
||||
return [ i1, i2 ].filter(function(p) {
|
||||
return isPointInSegment(p, s1, s2);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function isPointInSegment(p, segmentStart, segmentEnd) {
|
||||
return (
|
||||
fenced(p.x, segmentStart.x, segmentEnd.x) &&
|
||||
fenced(p.y, segmentStart.y, segmentEnd.y)
|
||||
);
|
||||
}
|
||||
|
||||
function fenced(n, rangeStart, rangeEnd) {
|
||||
return min(rangeStart, rangeEnd) <= n && n <= max(rangeStart, rangeEnd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate mid of two points.
|
||||
*
|
||||
* @param {Point} p1
|
||||
* @param {Point} p2
|
||||
*
|
||||
* @return {Point}
|
||||
*/
|
||||
function mid(p1, p2) {
|
||||
|
||||
return {
|
||||
x: (p1.x + p2.x) / 2,
|
||||
y: (p1.y + p2.y) / 2
|
||||
};
|
||||
}
|
||||
|
||||
function round(n) {
|
||||
return Math.round(n * 1000) / 1000;
|
||||
}
|
||||
|
||||
function pointsEqual(p1, p2) {
|
||||
return p1.x === p2.x && p1.y === p2.y;
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<bpmn:definitions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn">
|
||||
<bpmn:process id="Process_1" isExecutable="false">
|
||||
<bpmn:startEvent id="StartEvent_1">
|
||||
<bpmn:outgoing>SequenceFlow_A</bpmn:outgoing>
|
||||
</bpmn:startEvent>
|
||||
<bpmn:task id="Task_1" name="1">
|
||||
<bpmn:incoming>SequenceFlow_A</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_B</bpmn:outgoing>
|
||||
</bpmn:task>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_A" name="A" sourceRef="StartEvent_1" targetRef="Task_1" />
|
||||
<bpmn:task id="Task_3" name="3">
|
||||
<bpmn:incoming>SequenceFlow_B</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_C</bpmn:outgoing>
|
||||
<bpmn:outgoing>SequenceFlow_D</bpmn:outgoing>
|
||||
</bpmn:task>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_B" name="B" sourceRef="Task_1" targetRef="Task_3" />
|
||||
<bpmn:task id="Task_2" name="2">
|
||||
<bpmn:incoming>SequenceFlow_C</bpmn:incoming>
|
||||
</bpmn:task>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_C" name="C" sourceRef="Task_3" targetRef="Task_2" />
|
||||
<bpmn:task id="Task_4" name="4">
|
||||
<bpmn:incoming>SequenceFlow_D</bpmn:incoming>
|
||||
<bpmn:outgoing>SequenceFlow_E</bpmn:outgoing>
|
||||
</bpmn:task>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_D" name="D" sourceRef="Task_3" targetRef="Task_4" />
|
||||
<bpmn:endEvent id="EndEvent_1">
|
||||
<bpmn:incoming>SequenceFlow_E</bpmn:incoming>
|
||||
</bpmn:endEvent>
|
||||
<bpmn:sequenceFlow id="SequenceFlow_E" name="E" sourceRef="Task_4" targetRef="EndEvent_1" />
|
||||
</bpmn:process>
|
||||
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
|
||||
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
|
||||
<bpmndi:BPMNShape id="_BPMNShape_StartEvent_2" bpmnElement="StartEvent_1">
|
||||
<dc:Bounds x="173" y="102" width="36" height="36" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNShape id="Task_1_di" bpmnElement="Task_1">
|
||||
<dc:Bounds x="307" y="80" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_A_di" bpmnElement="SequenceFlow_A">
|
||||
<di:waypoint xsi:type="dc:Point" x="209" y="120" />
|
||||
<di:waypoint xsi:type="dc:Point" x="307" y="120" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="213" y="95" width="90" height="20" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="Task_3_di" bpmnElement="Task_3">
|
||||
<dc:Bounds x="504" y="235" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_B_di" bpmnElement="SequenceFlow_B">
|
||||
<di:waypoint xsi:type="dc:Point" x="407" y="120" />
|
||||
<di:waypoint xsi:type="dc:Point" x="455" y="120" />
|
||||
<di:waypoint xsi:type="dc:Point" x="455" y="275" />
|
||||
<di:waypoint xsi:type="dc:Point" x="504" y="275" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="425" y="175" width="90" height="20" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="Task_2_di" bpmnElement="Task_2">
|
||||
<dc:Bounds x="294" y="260" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_C_di" bpmnElement="SequenceFlow_C">
|
||||
<di:waypoint xsi:type="dc:Point" x="554" y="315" />
|
||||
<di:waypoint xsi:type="dc:Point" x="554" y="385" />
|
||||
<di:waypoint xsi:type="dc:Point" x="344" y="385" />
|
||||
<di:waypoint xsi:type="dc:Point" x="344" y="340" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="486" y="392" width="90" height="20" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="Task_4_di" bpmnElement="Task_4">
|
||||
<dc:Bounds x="717" y="353" width="100" height="80" />
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_D_di" bpmnElement="SequenceFlow_D">
|
||||
<di:waypoint xsi:type="dc:Point" x="602" y="243" />
|
||||
<di:waypoint xsi:type="dc:Point" x="716" y="166" />
|
||||
<di:waypoint xsi:type="dc:Point" x="661" y="393" />
|
||||
<di:waypoint xsi:type="dc:Point" x="717" y="393" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="626.5" y="201.5" width="90" height="20" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
<bpmndi:BPMNShape id="EndEvent_0za5rat_di" bpmnElement="EndEvent_1">
|
||||
<dc:Bounds x="886" y="175" width="36" height="36" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="859" y="211" width="90" height="20" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNShape>
|
||||
<bpmndi:BPMNEdge id="SequenceFlow_1ni843l_di" bpmnElement="SequenceFlow_E">
|
||||
<di:waypoint xsi:type="dc:Point" x="817" y="393" />
|
||||
<di:waypoint xsi:type="dc:Point" x="904" y="393" />
|
||||
<di:waypoint xsi:type="dc:Point" x="904" y="211" />
|
||||
<bpmndi:BPMNLabel>
|
||||
<dc:Bounds x="794" y="372.5" width="90" height="20" />
|
||||
</bpmndi:BPMNLabel>
|
||||
</bpmndi:BPMNEdge>
|
||||
</bpmndi:BPMNPlane>
|
||||
</bpmndi:BPMNDiagram>
|
||||
</bpmn:definitions>
|
|
@ -0,0 +1,559 @@
|
|||
'use strict';
|
||||
|
||||
require('../../../TestHelper');
|
||||
|
||||
/* global bootstrapModeler, inject */
|
||||
|
||||
|
||||
var coreModule = require('lib/core'),
|
||||
bendpointsModule = require('diagram-js/lib/features/bendpoints'),
|
||||
modelingModule = require('lib/features/modeling'),
|
||||
labelEditingModule = require('lib/features/label-editing');
|
||||
|
||||
var canvasEvent = require('../../../util/MockEvents').createCanvasEvent;
|
||||
|
||||
var testModules = [
|
||||
coreModule,
|
||||
modelingModule,
|
||||
labelEditingModule,
|
||||
bendpointsModule
|
||||
];
|
||||
|
||||
|
||||
describe('modeling - label layouting', function() {
|
||||
|
||||
describe('should create label', function() {
|
||||
|
||||
var diagramXML = require('./LabelLayouting.initial.bpmn');
|
||||
|
||||
beforeEach(bootstrapModeler(diagramXML, {
|
||||
modules: testModules
|
||||
}));
|
||||
|
||||
|
||||
it('horizontal', inject(function(modeling, elementRegistry) {
|
||||
|
||||
// given
|
||||
var element1 = elementRegistry.get('StartEvent_1'),
|
||||
element2 = elementRegistry.get('ExclusiveGateway_2');
|
||||
|
||||
// when
|
||||
var connection = modeling.connect(element1, element2);
|
||||
|
||||
// then
|
||||
expect(connection.label.x).to.be.equal(427);
|
||||
expect(connection.label.y).to.be.equal(332);
|
||||
}));
|
||||
|
||||
|
||||
it('vertical', inject(function(modeling, elementRegistry) {
|
||||
|
||||
// given
|
||||
var element1 = elementRegistry.get('StartEvent_1'),
|
||||
element2 = elementRegistry.get('ExclusiveGateway_1');
|
||||
|
||||
// when
|
||||
var connection = modeling.connect(element1, element2);
|
||||
|
||||
// then
|
||||
expect(connection.label.x).to.be.equal(292);
|
||||
expect(connection.label.y).to.be.equal(219.5);
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('should move label', function() {
|
||||
|
||||
var diagramXML = require('./LabelLayouting.move.bpmn');
|
||||
|
||||
beforeEach(bootstrapModeler(diagramXML, {
|
||||
modules: testModules
|
||||
}));
|
||||
|
||||
describe('on segment move', function() {
|
||||
|
||||
it('left - no relayout', inject(function(elementRegistry, connectionSegmentMove, dragging) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_B'),
|
||||
labelPosition = getLabelPosition(connection);
|
||||
|
||||
// when
|
||||
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
|
||||
|
||||
dragging.move(canvasEvent({ x: -30, y: 0 }));
|
||||
|
||||
dragging.end();
|
||||
|
||||
// then
|
||||
expectLabelMoved(connection, labelPosition, { x: -30, y: 0 });
|
||||
}));
|
||||
|
||||
|
||||
it('left - remove bendpoint', inject(function(elementRegistry, connectionSegmentMove, dragging) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_B'),
|
||||
labelPosition = getLabelPosition(connection);
|
||||
|
||||
// when
|
||||
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
|
||||
|
||||
dragging.move(canvasEvent({ x: -70, y: 0 }));
|
||||
|
||||
dragging.end();
|
||||
|
||||
// then
|
||||
expectLabelMoved(connection, labelPosition, { x: -70, y: 23 });
|
||||
}));
|
||||
|
||||
|
||||
it('right - no relayout', inject(function(elementRegistry, connectionSegmentMove, dragging) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_B'),
|
||||
labelPosition = getLabelPosition(connection);
|
||||
|
||||
// when
|
||||
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
|
||||
|
||||
dragging.move(canvasEvent({ x: 30, y: 0 }));
|
||||
|
||||
dragging.end();
|
||||
|
||||
// then
|
||||
expectLabelMoved(connection, labelPosition, { x: 30, y: 0 });
|
||||
}));
|
||||
|
||||
|
||||
it('right - remove bendpoint', inject(function(elementRegistry, connectionSegmentMove, dragging) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_B'),
|
||||
labelPosition = getLabelPosition(connection);
|
||||
|
||||
// when
|
||||
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
|
||||
|
||||
dragging.move(canvasEvent({ x: 70, y: 0 }));
|
||||
|
||||
dragging.end();
|
||||
|
||||
// then
|
||||
expectLabelMoved(connection, labelPosition, { x: 70, y: -17 });
|
||||
}));
|
||||
|
||||
|
||||
it('down', inject(function(elementRegistry, connectionSegmentMove, dragging) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_C'),
|
||||
labelPosition = getLabelPosition(connection);
|
||||
|
||||
// when
|
||||
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
|
||||
|
||||
dragging.move(canvasEvent({ x: 0, y: 70 }));
|
||||
|
||||
dragging.end();
|
||||
|
||||
// then
|
||||
expectLabelMoved(connection, labelPosition, { x: 0, y: 70 });
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('up - remove two bendpoints', inject(function(elementRegistry, connectionSegmentMove, dragging) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_C'),
|
||||
labelPosition = getLabelPosition(connection);
|
||||
|
||||
// when
|
||||
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
|
||||
|
||||
dragging.move(canvasEvent({ x: 0, y: -90 }));
|
||||
|
||||
dragging.end();
|
||||
|
||||
// then
|
||||
expectLabelMoved(connection, labelPosition, { x: -39, y: -85 });
|
||||
|
||||
}));
|
||||
|
||||
// TODO(@janstuemmel): solve by connectionSegmentMove refactoring
|
||||
it.skip('up - remove two bendpoints - redundant waypoints', inject(function(elementRegistry, connectionSegmentMove, dragging, bendpointMove) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_C');
|
||||
|
||||
bendpointMove.start(canvasEvent({ x: 0, y: 0 }), connection, 1);
|
||||
dragging.move(canvasEvent({ x: 620, y: 435 }));
|
||||
dragging.end();
|
||||
|
||||
bendpointMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
|
||||
dragging.move(canvasEvent({ x: 300, y: 435 }));
|
||||
dragging.end();
|
||||
|
||||
var labelPosition = getLabelPosition(connection);
|
||||
|
||||
// when
|
||||
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
|
||||
dragging.move(canvasEvent({ x: 0, y: -160 }));
|
||||
dragging.end();
|
||||
|
||||
// then
|
||||
expect(getLabelPosition(connection)).to.not.eql(labelPosition);
|
||||
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('on reconnect', function() {
|
||||
|
||||
it('start', inject(function(elementRegistry, modeling) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_D'),
|
||||
shape = elementRegistry.get('Task_1');
|
||||
|
||||
// when
|
||||
modeling.reconnectStart(connection, shape, { x: 0, y: 0 });
|
||||
|
||||
// then
|
||||
expect(Math.round(connection.label.x)).to.be.equal(528);
|
||||
expect(Math.round(connection.label.y)).to.be.equal(137);
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('end', inject(function(elementRegistry, modeling) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_A'),
|
||||
shape = elementRegistry.get('Task_2');
|
||||
|
||||
// when
|
||||
modeling.reconnectEnd(connection, shape, { x: 294, y: 270 });
|
||||
|
||||
// then
|
||||
expect(Math.round(connection.label.x)).to.be.equal(215);
|
||||
expect(Math.round(connection.label.y)).to.be.equal(184);
|
||||
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('on shape move', function() {
|
||||
|
||||
it('down', inject(function(elementRegistry, modeling) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_E'),
|
||||
shape = elementRegistry.get('Task_4'),
|
||||
labelPosition = getLabelPosition(connection);
|
||||
|
||||
// when
|
||||
modeling.moveShape(shape, { x: 0, y: 100 });
|
||||
|
||||
// then
|
||||
expectLabelMoved(connection, labelPosition, { x: 0, y: 100 });
|
||||
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('on bendpoint add/delete/moving', function() {
|
||||
|
||||
|
||||
it('move, label on segment', inject(function(elementRegistry, bendpointMove, dragging) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_B');
|
||||
|
||||
// when
|
||||
bendpointMove.start(canvasEvent({ x: 0, y: 0 }), connection, 1);
|
||||
|
||||
dragging.move(canvasEvent({ x: 455 + 50, y: 120 }));
|
||||
|
||||
dragging.end();
|
||||
|
||||
// then
|
||||
expect(Math.round(connection.label.x)).to.be.equal(425);
|
||||
expect(Math.round(connection.label.y)).to.be.equal(170);
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('move, label on bendpoint', inject(function(elementRegistry, bendpointMove, dragging, modeling) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_C');
|
||||
|
||||
// label out of segments, on a bendpoint (idx=1)
|
||||
modeling.moveShape(connection.label, { x: 40, y: 0 });
|
||||
|
||||
// when
|
||||
bendpointMove.start(canvasEvent({ x: 0, y: 0 }), connection, 1);
|
||||
|
||||
dragging.move(canvasEvent({ x: 455 + 50, y: 500 }));
|
||||
|
||||
dragging.end();
|
||||
|
||||
// then
|
||||
expect(Math.round(connection.label.x)).to.be.equal(477);
|
||||
expect(Math.round(connection.label.y)).to.be.equal(507);
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('remove bendpoint when label on segment', inject(function(elementRegistry, bendpointMove, dragging) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_B');
|
||||
|
||||
// when
|
||||
bendpointMove.start(canvasEvent({ x: 0, y: 0 }), connection, 1);
|
||||
|
||||
dragging.move(canvasEvent({ x: 455, y: 120 + 160 }));
|
||||
|
||||
dragging.end();
|
||||
|
||||
// then
|
||||
expect(Math.round(connection.label.x)).to.be.equal(380);
|
||||
expect(Math.round(connection.label.y)).to.be.equal(190);
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('add bendpoint, label on right segment', inject(function(elementRegistry, bendpointMove, dragging, canvas) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_A');
|
||||
|
||||
// when
|
||||
bendpointMove.start(canvasEvent({ x: 0, y: 0 }), connection, 1, true);
|
||||
|
||||
dragging.hover({
|
||||
element: connection,
|
||||
gfx: canvas.getGraphics(connection)
|
||||
});
|
||||
|
||||
dragging.move(canvasEvent({ x: 220, y: 200 }));
|
||||
|
||||
dragging.end();
|
||||
|
||||
// then
|
||||
expect(Math.round(connection.label.x)).to.be.equal(211);
|
||||
expect(Math.round(connection.label.y)).to.be.equal(152);
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('add bendpoint, label on left segment', inject(function(elementRegistry, bendpointMove, dragging, canvas) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_A');
|
||||
|
||||
// when
|
||||
bendpointMove.start(canvasEvent({ x: 0, y: 0 }), connection, 1, true);
|
||||
|
||||
dragging.hover({
|
||||
element: connection,
|
||||
gfx: canvas.getGraphics(connection)
|
||||
});
|
||||
|
||||
dragging.move(canvasEvent({ x: 260, y: 200 }));
|
||||
|
||||
dragging.end();
|
||||
|
||||
// then
|
||||
expect(Math.round(connection.label.x)).to.be.equal(197);
|
||||
expect(Math.round(connection.label.y)).to.be.equal(147);
|
||||
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('remove bendpoint when label on bendpoint', inject(function(elementRegistry, bendpointMove, dragging, modeling) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_C');
|
||||
|
||||
// label out of segments, on a bendpoint
|
||||
modeling.moveShape(connection.label, { x: 40, y: 0 });
|
||||
|
||||
// when
|
||||
bendpointMove.start(canvasEvent({ x: 0, y: 0 }), connection, 1);
|
||||
|
||||
dragging.move(canvasEvent({ x: 455, y: 320 }));
|
||||
|
||||
dragging.end();
|
||||
|
||||
// then
|
||||
expect(Math.round(connection.label.x)).to.be.equal(426);
|
||||
expect(Math.round(connection.label.y)).to.be.equal(287);
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('add benpoint, label on segment, should not move', inject(function(elementRegistry, bendpointMove, canvas, dragging, modeling) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_C');
|
||||
|
||||
// label out of segments, on a bendpoint
|
||||
modeling.moveShape(connection.label, { x: 40, y: -60 });
|
||||
var position = getLabelPosition(connection);
|
||||
|
||||
// when
|
||||
bendpointMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2, true);
|
||||
|
||||
dragging.hover({
|
||||
element: connection,
|
||||
gfx: canvas.getGraphics(connection)
|
||||
});
|
||||
|
||||
dragging.move(canvasEvent({ x: 400, y: 350 }));
|
||||
|
||||
dragging.end();
|
||||
|
||||
// then
|
||||
expectLabelMoved(connection, position, { x: 0, y: 0 });
|
||||
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
describe('special cases', function() {
|
||||
|
||||
it('should behave properly, right after importing', inject(function(elementRegistry, connectionSegmentMove, dragging, modeling) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_C'),
|
||||
labelPosition = getLabelPosition(connection),
|
||||
label = connection.label;
|
||||
|
||||
// when
|
||||
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
|
||||
|
||||
dragging.move(canvasEvent({ x: 0, y: 70 }));
|
||||
|
||||
dragging.end();
|
||||
|
||||
// move label
|
||||
modeling.moveShape(label, { x: 40, y: -30 });
|
||||
|
||||
// drag again
|
||||
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 1);
|
||||
|
||||
dragging.move(canvasEvent({ x: -20, y: 0 }));
|
||||
|
||||
dragging.end();
|
||||
|
||||
// then
|
||||
expectLabelMoved(connection, labelPosition, { x: 20, y: 40 });
|
||||
|
||||
}));
|
||||
|
||||
|
||||
it('should reposition on right segment', inject(function(elementRegistry, connectionSegmentMove, dragging) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_E'),
|
||||
labelPosition = getLabelPosition(connection);
|
||||
|
||||
// when
|
||||
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
|
||||
|
||||
dragging.move(canvasEvent({ x: -100, y: 0 }));
|
||||
|
||||
dragging.end();
|
||||
|
||||
// then
|
||||
expectLabelMoved(connection, labelPosition, { x: -45, y: -70 });
|
||||
|
||||
}));
|
||||
|
||||
|
||||
describe.skip('label out of bounds', function() {
|
||||
|
||||
|
||||
it('should not move label that is out of bounds', inject(function(elementRegistry, connectionSegmentMove, dragging, modeling) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_C');
|
||||
|
||||
// move shape away
|
||||
modeling.moveShape(connection.label, { x: 0, y: 140 });
|
||||
|
||||
var labelPosition = getLabelPosition(connection);
|
||||
|
||||
// when
|
||||
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
|
||||
|
||||
dragging.move(canvasEvent({ x: 0, y: 30 }));
|
||||
|
||||
dragging.end();
|
||||
|
||||
// then
|
||||
expectLabelMoved(connection, labelPosition, { x: 0, y: 0 });
|
||||
}));
|
||||
|
||||
|
||||
it('should not move label that is out of bounds in corner', inject(function(elementRegistry, connectionSegmentMove, dragging, modeling) {
|
||||
|
||||
// given
|
||||
var connection = elementRegistry.get('SequenceFlow_C');
|
||||
|
||||
// move shape away
|
||||
modeling.moveShape(connection.label, { x: 50, y: 0 });
|
||||
|
||||
var labelPosition = getLabelPosition(connection);
|
||||
|
||||
// when
|
||||
connectionSegmentMove.start(canvasEvent({ x: 0, y: 0 }), connection, 2);
|
||||
|
||||
dragging.move(canvasEvent({ x: 0, y: 30 }));
|
||||
|
||||
dragging.end();
|
||||
|
||||
// then
|
||||
expectLabelMoved(connection, labelPosition, { x: 0, y: 0 });
|
||||
}));
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
function getLabelPosition(connection) {
|
||||
|
||||
var label = connection.label;
|
||||
|
||||
var mid = {
|
||||
x: label.x + (label.width / 2),
|
||||
y: label.y + (label.height / 2)
|
||||
};
|
||||
|
||||
return mid;
|
||||
}
|
||||
|
||||
|
||||
function expectLabelMoved(connection, oldPosition, expectedDelta) {
|
||||
|
||||
var newPosition = getLabelPosition(connection);
|
||||
|
||||
var delta = {
|
||||
x: Math.round(newPosition.x - oldPosition.x),
|
||||
y: Math.round(newPosition.y - oldPosition.y)
|
||||
};
|
||||
|
||||
expect(delta).to.eql(expectedDelta);
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
'use strict';
|
||||
|
||||
require('../../../../../TestHelper');
|
||||
|
||||
var GeometricUtil = require('lib/features/modeling/behavior/util/GeometricUtil');
|
||||
|
||||
|
||||
describe('modeling/behavior/util - GeometricUtil', function() {
|
||||
|
||||
it('should calculate right horizontal-line/point distance', function() {
|
||||
|
||||
// given
|
||||
var testData = [
|
||||
{ point: { x: 2, y: 4 }, line: [ { x: 1, y: 1 }, { x: 4, y: 1 } ], distance: 3 },
|
||||
{ point: { x: 2, y: 2 }, line: [ { x: 1, y: 1 }, { x: 1, y: 4 } ], distance: 1 },
|
||||
{ point: { x: 0, y: 0 }, line: [ { x: 0, y: 4 }, { x: 4, y: 0 } ], distance: 3 }
|
||||
];
|
||||
|
||||
for (var i=0; i<testData.length; i++) {
|
||||
|
||||
// when
|
||||
var d = GeometricUtil.getDistancePointLine(testData[i].point, testData[i].line);
|
||||
|
||||
// then
|
||||
expect(Math.round(d)).to.be.equal(testData[i].distance);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
it('should calculate right perpendicular foot', function() {
|
||||
|
||||
// given
|
||||
var testData = [
|
||||
{ point: { x: 2, y: 2 }, line: [ { x: 1, y: 1 }, { x: 1, y: 3 } ], foot: { x: 1, y: 2 } },
|
||||
{ point: { x: 2, y: 4 }, line: [ { x: 1, y: 1 }, { x: 4, y: 1 } ], foot: { x: 2, y: 1 } }
|
||||
];
|
||||
|
||||
for (var i=0; i<testData.length; i++) {
|
||||
|
||||
// when
|
||||
var foot = GeometricUtil.perpendicularFoot(testData[i].point, testData[i].line);
|
||||
|
||||
// then
|
||||
expect(rounded(foot)).to.be.eql(testData[i].foot);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
it('should calculate right distance', function() {
|
||||
|
||||
var testData = [
|
||||
{ p1: { x: 1, y: 1 }, p2: { x: 5, y: 1 }, distance: 4 },
|
||||
{ p1: { x: 1, y: 1 }, p2: { x: 1, y: 5 }, distance: 4 },
|
||||
{ p1: { x: 1, y: 1 }, p2: { x: 5, y: 5 }, distance: 6 },
|
||||
{ p1: { x: 1, y: 1 }, p2: { x: -5, y: -5 }, distance: 8 }
|
||||
];
|
||||
|
||||
for (var i=0; i<testData.length; i++) {
|
||||
|
||||
// when
|
||||
var d = GeometricUtil.getDistancePointPoint(testData[i].p1, testData[i].p2);
|
||||
|
||||
// then
|
||||
expect(Math.round(d)).to.be.eql(testData[i].distance);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
it('should calculate right line angle', function() {
|
||||
|
||||
// given
|
||||
var testLines = [
|
||||
{ line: [ { x: 0, y: 0 }, { x: 10, y: 10 } ], angle: 45 },
|
||||
{ line: [ { x: 0, y: 0 }, { x: 0, y: 10 } ], angle: 90 },
|
||||
{ line: [ { x: 0, y: 0 }, { x: -10, y: 10 } ], angle: -45 },
|
||||
{ line: [ { x: 0, y: 0 }, { x: 10, y: 0 } ], angle: 0 },
|
||||
{ line: [ { x: 0, y: 0 }, { x: 0, y: -10 } ], angle: -90 },
|
||||
{ line: [ { x: 0, y: 0 }, { x: -10, y: 0 } ], angle: 0 }
|
||||
];
|
||||
|
||||
for (var i=0; i<testLines.length; i++) {
|
||||
|
||||
// when
|
||||
var angle = GeometricUtil.getAngle(testLines[i].line);
|
||||
|
||||
// to degree
|
||||
angle = angle * ( 180 / Math.PI );
|
||||
|
||||
//then
|
||||
expect(angle).to.be.equal(testLines[i].angle);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
it('should rotate vector', function() {
|
||||
|
||||
// given
|
||||
var testVectors = [
|
||||
// x=-10 because we have system with flipped y axis
|
||||
{ vector: { x: 0, y: 10 }, angle: 90, rotated: { x: -10, y: 0 } },
|
||||
{ vector: { x: 10, y: 0 }, angle: 90, rotated: { x: 0, y: 10 } },
|
||||
{ vector: { x: 10, y: 0 }, angle: 45, rotated: { x: 7, y: 7 } }
|
||||
];
|
||||
|
||||
for (var i=0; i<testVectors.length; i++) {
|
||||
|
||||
// degree to radian
|
||||
var angle = testVectors[i].angle * ( Math.PI / 180 );
|
||||
|
||||
// when
|
||||
var rotatedVector = GeometricUtil.rotateVector(testVectors[i].vector, angle);
|
||||
|
||||
// then
|
||||
expect(rounded(rotatedVector)).to.be.eql(testVectors[i].rotated);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
function rounded(v) {
|
||||
|
||||
return {
|
||||
x: Math.round(v.x),
|
||||
y: Math.round(v.y)
|
||||
};
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
'use strict';
|
||||
|
||||
var getAttachment = require('lib/features/modeling/behavior/util/LineAttachmentUtil').getAttachment;
|
||||
|
||||
|
||||
describe('modeling/behavior/util - LineAttachmentUtil#getAttachment', function() {
|
||||
|
||||
// test line
|
||||
//
|
||||
// *--*
|
||||
// |
|
||||
// *
|
||||
// \
|
||||
// *
|
||||
//
|
||||
var line = [
|
||||
{ x: 10, y: 10 },
|
||||
// -
|
||||
{ x: 30, y: 10 },
|
||||
// |
|
||||
{ x: 30, y: 30 },
|
||||
// \
|
||||
{ x: 130, y: 130 }
|
||||
];
|
||||
|
||||
|
||||
describe('should recognize segment', function() {
|
||||
|
||||
it('horizontal', function() {
|
||||
|
||||
// when
|
||||
var attachment = getAttachment({ x: 20, y: 5 }, line);
|
||||
|
||||
// then
|
||||
expect(attachment).to.eql({
|
||||
type: 'segment',
|
||||
position: { x: 20, y: 10 },
|
||||
segmentIndex: 0,
|
||||
relativeLocation: 0.5
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('horizontal (on line)', function() {
|
||||
|
||||
// when
|
||||
var attachment = getAttachment({ x: 20, y: 10 }, line);
|
||||
|
||||
// then
|
||||
expect(attachment).to.eql({
|
||||
type: 'segment',
|
||||
position: { x: 20, y: 10 },
|
||||
segmentIndex: 0,
|
||||
relativeLocation: 0.5
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('vertical', function() {
|
||||
|
||||
// when
|
||||
var attachment = getAttachment({ x: 40, y: 20 }, line);
|
||||
|
||||
// then
|
||||
expect(attachment).to.eql({
|
||||
type: 'segment',
|
||||
position: { x: 30, y: 20 },
|
||||
segmentIndex: 1,
|
||||
relativeLocation: 0.5
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('diagonal', function() {
|
||||
|
||||
// when
|
||||
var attachment = getAttachment({ x: 30, y: 40 }, line);
|
||||
|
||||
// then
|
||||
expect(attachment).to.eql({
|
||||
type: 'segment',
|
||||
position: { x: 35, y: 35 },
|
||||
segmentIndex: 2,
|
||||
relativeLocation: 0.05
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('diagonal (on line)', function() {
|
||||
|
||||
// when
|
||||
var attachment = getAttachment({ x: 50, y: 50 }, line);
|
||||
|
||||
// then
|
||||
expect(attachment).to.eql({
|
||||
type: 'segment',
|
||||
position: { x: 50, y: 50 },
|
||||
segmentIndex: 2,
|
||||
relativeLocation: 0.2
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('horizontal (conflicting with vertical)', function() {
|
||||
|
||||
// when
|
||||
var attachment = getAttachment({ x: 25, y: 15 }, line);
|
||||
|
||||
// then
|
||||
expect(attachment).to.eql({
|
||||
type: 'segment',
|
||||
position: { x: 25, y: 10 },
|
||||
segmentIndex: 0,
|
||||
relativeLocation: 0.75
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
describe('should recognize bendpoint', function() {
|
||||
|
||||
it('horizontal', function() {
|
||||
|
||||
// when
|
||||
var attachment = getAttachment({ x: 35, y: 5 }, line);
|
||||
|
||||
// then
|
||||
expect(attachment).to.eql({
|
||||
type: 'bendpoint',
|
||||
position: { x: 30, y: 10 },
|
||||
bendpointIndex: 1,
|
||||
segmentIndex: 0
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('horizontal (segment start)', function() {
|
||||
|
||||
// when
|
||||
var attachment = getAttachment({ x: 5, y: 5 }, line);
|
||||
|
||||
// then
|
||||
expect(attachment).to.eql({
|
||||
type: 'bendpoint',
|
||||
position: { x: 10, y: 10 },
|
||||
bendpointIndex: 0,
|
||||
segmentIndex: 0
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('vertical', function() {
|
||||
|
||||
// when
|
||||
var attachment = getAttachment({ x: 35, y: 10 }, line);
|
||||
|
||||
// then
|
||||
expect(attachment).to.eql({
|
||||
type: 'bendpoint',
|
||||
position: { x: 30, y: 10 },
|
||||
bendpointIndex: 1,
|
||||
segmentIndex: 0
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('vertical (segment start)', function() {
|
||||
|
||||
var otherLine = [
|
||||
{ x: 10, y: 10 },
|
||||
{ x: 10, y: 50 }
|
||||
];
|
||||
|
||||
// when
|
||||
var attachment = getAttachment({ x: 5, y: 5 }, otherLine);
|
||||
|
||||
// then
|
||||
expect(attachment).to.eql({
|
||||
type: 'bendpoint',
|
||||
position: { x: 10, y: 10 },
|
||||
bendpointIndex: 0,
|
||||
segmentIndex: 0
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
|
@ -1,47 +0,0 @@
|
|||
'use strict';
|
||||
|
||||
require('../../TestHelper');
|
||||
|
||||
/* global bootstrapModeler, inject */
|
||||
|
||||
var coreModule = require('../../../lib/core'),
|
||||
modelingModule = require('../../../lib/features/modeling');
|
||||
|
||||
|
||||
describe('LabelUtil', function() {
|
||||
|
||||
var diagramXML = require('./LabelUtil.bpmn');
|
||||
|
||||
beforeEach(bootstrapModeler(diagramXML, { modules: [ coreModule, modelingModule ] }));
|
||||
|
||||
|
||||
it('should correctly place horizontal label', inject(function(modeling, elementRegistry) {
|
||||
|
||||
// given
|
||||
var element1 = elementRegistry.get('StartEvent_1'),
|
||||
element2 = elementRegistry.get('ExclusiveGateway_2');
|
||||
|
||||
// when
|
||||
var connection = modeling.connect(element1, element2);
|
||||
|
||||
// then
|
||||
expect(connection.label.x).to.be.equal(427);
|
||||
expect(connection.label.y).to.be.equal(332);
|
||||
}));
|
||||
|
||||
|
||||
it('should correctly place vertical label', inject(function(modeling, elementRegistry) {
|
||||
|
||||
// given
|
||||
var element1 = elementRegistry.get('StartEvent_1'),
|
||||
element2 = elementRegistry.get('ExclusiveGateway_1');
|
||||
|
||||
// when
|
||||
var connection = modeling.connect(element1, element2);
|
||||
|
||||
// then
|
||||
expect(connection.label.x).to.be.equal(292);
|
||||
expect(connection.label.y).to.be.equal(219.5);
|
||||
}));
|
||||
|
||||
});
|
Loading…
Reference in New Issue