2018-04-02 19:01:53 +00:00
|
|
|
import inherits from 'inherits';
|
2015-04-22 07:08:38 +00:00
|
|
|
|
2018-04-02 19:01:53 +00:00
|
|
|
import {
|
|
|
|
assign
|
|
|
|
} from 'min-dash';
|
2015-04-22 07:08:38 +00:00
|
|
|
|
2018-04-02 19:01:53 +00:00
|
|
|
import BaseLayouter from 'diagram-js/lib/layout/BaseLayouter';
|
2015-04-22 07:08:38 +00:00
|
|
|
|
2018-04-02 19:01:53 +00:00
|
|
|
import {
|
2018-07-04 07:13:38 +00:00
|
|
|
repairConnection,
|
|
|
|
withoutRedundantPoints
|
2018-04-02 19:01:53 +00:00
|
|
|
} from 'diagram-js/lib/layout/ManhattanLayout';
|
2015-07-29 13:11:54 +00:00
|
|
|
|
2018-04-02 19:01:53 +00:00
|
|
|
import {
|
|
|
|
getMid,
|
|
|
|
getOrientation
|
|
|
|
} from 'diagram-js/lib/layout/LayoutUtil';
|
2017-12-10 11:29:54 +00:00
|
|
|
|
2018-04-02 19:01:53 +00:00
|
|
|
import {
|
|
|
|
isExpanded
|
|
|
|
} from '../../util/DiUtil';
|
2015-07-29 13:11:54 +00:00
|
|
|
|
2018-04-02 19:01:53 +00:00
|
|
|
import { is } from '../../util/ModelUtil';
|
2015-04-22 07:08:38 +00:00
|
|
|
|
|
|
|
|
2018-04-02 19:01:53 +00:00
|
|
|
export default function BpmnLayouter() {}
|
2015-04-22 07:08:38 +00:00
|
|
|
|
|
|
|
inherits(BpmnLayouter, BaseLayouter);
|
|
|
|
|
|
|
|
|
2016-06-22 12:38:44 +00:00
|
|
|
BpmnLayouter.prototype.layoutConnection = function(connection, hints) {
|
|
|
|
hints = hints || {};
|
|
|
|
|
2015-04-22 07:08:38 +00:00
|
|
|
var source = connection.source,
|
|
|
|
target = connection.target,
|
|
|
|
waypoints = connection.waypoints,
|
2016-06-22 12:38:44 +00:00
|
|
|
start = hints.connectionStart,
|
|
|
|
end = hints.connectionEnd;
|
2015-04-22 07:08:38 +00:00
|
|
|
|
2015-07-29 13:11:54 +00:00
|
|
|
var manhattanOptions,
|
2015-04-22 07:08:38 +00:00
|
|
|
updatedWaypoints;
|
|
|
|
|
2016-06-26 22:29:54 +00:00
|
|
|
if (!start) {
|
|
|
|
start = getConnectionDocking(waypoints && waypoints[0], source);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!end) {
|
|
|
|
end = getConnectionDocking(waypoints && waypoints[waypoints.length - 1], target);
|
|
|
|
}
|
2015-07-29 13:11:54 +00:00
|
|
|
|
2016-06-22 12:36:44 +00:00
|
|
|
// TODO(nikku): support vertical modeling
|
2015-07-29 13:11:54 +00:00
|
|
|
// and invert preferredLayouts accordingly
|
|
|
|
|
2016-01-27 08:45:05 +00:00
|
|
|
if (is(connection, 'bpmn:Association') ||
|
|
|
|
is(connection, 'bpmn:DataAssociation')) {
|
|
|
|
|
|
|
|
if (waypoints && !isCompensationAssociation(connection)) {
|
2016-06-26 22:29:54 +00:00
|
|
|
return [].concat([ start ], waypoints.slice(1, -1), [ end ]);
|
2016-01-27 08:45:05 +00:00
|
|
|
}
|
2015-10-13 09:07:35 +00:00
|
|
|
}
|
2015-04-22 07:08:38 +00:00
|
|
|
|
|
|
|
// manhattan layout sequence / message flows
|
|
|
|
if (is(connection, 'bpmn:MessageFlow')) {
|
2018-08-22 11:54:24 +00:00
|
|
|
manhattanOptions = getMessageFlowManhattanOptions(source, target);
|
2015-08-24 09:28:54 +00:00
|
|
|
|
2015-07-29 13:11:54 +00:00
|
|
|
} else
|
|
|
|
|
2015-04-22 07:08:38 +00:00
|
|
|
|
2015-07-29 13:11:54 +00:00
|
|
|
// layout all connection between flow elements h:h,
|
|
|
|
//
|
|
|
|
// except for
|
|
|
|
//
|
2018-07-04 07:13:38 +00:00
|
|
|
// (1) outgoing of BoundaryEvents -> layout based on attach orientation and target orientation
|
2015-07-29 13:11:54 +00:00
|
|
|
// (2) incoming / outgoing of Gateway -> v:h (outgoing), h:v (incoming)
|
2018-07-11 14:33:42 +00:00
|
|
|
// (3) loops from / to the same element
|
2015-07-29 13:11:54 +00:00
|
|
|
//
|
2016-01-27 08:45:05 +00:00
|
|
|
if (is(connection, 'bpmn:SequenceFlow') ||
|
|
|
|
isCompensationAssociation(connection)) {
|
2015-07-29 13:11:54 +00:00
|
|
|
|
2018-07-11 14:33:42 +00:00
|
|
|
if (source === target) {
|
|
|
|
manhattanOptions = {
|
|
|
|
preferredLayouts: [ 'b:l' ]
|
|
|
|
};
|
|
|
|
} else
|
|
|
|
|
2015-07-29 13:11:54 +00:00
|
|
|
if (is(source, 'bpmn:BoundaryEvent')) {
|
|
|
|
|
2018-07-04 07:13:38 +00:00
|
|
|
manhattanOptions = {
|
|
|
|
preferredLayouts: getBoundaryEventPreferredLayouts(source, target)
|
|
|
|
};
|
2015-07-29 13:11:54 +00:00
|
|
|
|
|
|
|
} else
|
|
|
|
|
|
|
|
if (is(source, 'bpmn:Gateway')) {
|
|
|
|
|
|
|
|
manhattanOptions = {
|
|
|
|
preferredLayouts: [ 'v:h' ]
|
|
|
|
};
|
|
|
|
} else
|
|
|
|
|
|
|
|
if (is(target, 'bpmn:Gateway')) {
|
|
|
|
|
|
|
|
manhattanOptions = {
|
|
|
|
preferredLayouts: [ 'h:v' ]
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
else {
|
|
|
|
manhattanOptions = {
|
|
|
|
preferredLayouts: [ 'h:h' ]
|
|
|
|
};
|
|
|
|
}
|
2018-07-11 14:33:42 +00:00
|
|
|
|
2015-04-22 07:08:38 +00:00
|
|
|
}
|
|
|
|
|
2015-07-29 13:11:54 +00:00
|
|
|
if (manhattanOptions) {
|
2015-04-22 07:08:38 +00:00
|
|
|
|
2016-06-22 12:38:44 +00:00
|
|
|
manhattanOptions = assign(manhattanOptions, hints);
|
2015-04-22 07:08:38 +00:00
|
|
|
|
|
|
|
updatedWaypoints =
|
2018-07-04 07:13:38 +00:00
|
|
|
withoutRedundantPoints(
|
|
|
|
repairConnection(
|
|
|
|
source, target,
|
|
|
|
start, end,
|
|
|
|
waypoints,
|
|
|
|
manhattanOptions
|
|
|
|
)
|
|
|
|
);
|
2015-04-22 07:08:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return updatedWaypoints || [ start, end ];
|
2015-07-13 08:37:43 +00:00
|
|
|
};
|
2015-07-29 13:11:54 +00:00
|
|
|
|
|
|
|
|
|
|
|
function getAttachOrientation(attachedElement) {
|
|
|
|
|
|
|
|
var hostElement = attachedElement.host,
|
|
|
|
padding = -10;
|
|
|
|
|
|
|
|
return getOrientation(getMid(attachedElement), hostElement, padding);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-08-22 11:54:24 +00:00
|
|
|
function getMessageFlowManhattanOptions(source, target) {
|
|
|
|
return {
|
|
|
|
preferredLayouts: [ 'straight', 'v:v' ],
|
|
|
|
preserveDocking: getMessageFlowPreserveDocking(source, target)
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
function getMessageFlowPreserveDocking(source, target) {
|
|
|
|
|
|
|
|
// (1) docking element connected to participant has precedence
|
|
|
|
|
|
|
|
if (is(target, 'bpmn:Participant')) {
|
|
|
|
return 'source';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is(source, 'bpmn:Participant')) {
|
|
|
|
return 'target';
|
|
|
|
}
|
|
|
|
|
|
|
|
// (2) docking element connected to expanded sub-process has precedence
|
|
|
|
|
|
|
|
if (isExpandedSubProcess(target)) {
|
|
|
|
return 'source';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isExpandedSubProcess(source)) {
|
|
|
|
return 'target';
|
|
|
|
}
|
|
|
|
|
|
|
|
// (3) docking event has precedence
|
|
|
|
|
|
|
|
if (is(target, 'bpmn:Event')) {
|
|
|
|
return 'target';
|
|
|
|
}
|
|
|
|
|
|
|
|
if (is(source, 'bpmn:Event')) {
|
|
|
|
return 'source';
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2016-06-26 22:29:54 +00:00
|
|
|
function getConnectionDocking(point, shape) {
|
|
|
|
return point ? (point.original || point) : getMid(shape);
|
2015-07-29 13:11:54 +00:00
|
|
|
}
|
2016-01-27 08:45:05 +00:00
|
|
|
|
|
|
|
function isCompensationAssociation(connection) {
|
|
|
|
|
|
|
|
var source = connection.source,
|
|
|
|
target = connection.target;
|
|
|
|
|
|
|
|
return is(target, 'bpmn:Activity') &&
|
|
|
|
is(source, 'bpmn:BoundaryEvent') &&
|
|
|
|
target.businessObject.isForCompensation;
|
2016-06-21 09:59:33 +00:00
|
|
|
}
|
2016-06-22 12:36:44 +00:00
|
|
|
|
|
|
|
|
|
|
|
function isExpandedSubProcess(element) {
|
|
|
|
return is(element, 'bpmn:SubProcess') && isExpanded(element);
|
2018-07-04 07:13:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function isSame(a, b) {
|
|
|
|
return a === b;
|
|
|
|
}
|
|
|
|
|
|
|
|
function isAnyOrientation(orientation, orientations) {
|
|
|
|
return orientations.indexOf(orientation) !== -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
var oppositeOrientationMapping = {
|
|
|
|
'top': 'bottom',
|
|
|
|
'top-right': 'bottom-left',
|
|
|
|
'top-left': 'bottom-right',
|
|
|
|
'right': 'left',
|
|
|
|
'bottom': 'top',
|
|
|
|
'bottom-right': 'top-left',
|
|
|
|
'bottom-left': 'top-right',
|
|
|
|
'left': 'right'
|
|
|
|
};
|
|
|
|
|
|
|
|
var orientationDirectionMapping = {
|
|
|
|
top: 't',
|
|
|
|
right: 'r',
|
|
|
|
bottom: 'b',
|
|
|
|
left: 'l'
|
|
|
|
};
|
|
|
|
|
|
|
|
function getHorizontalOrientation(orientation) {
|
|
|
|
var matches = /right|left/.exec(orientation);
|
|
|
|
|
|
|
|
return matches && matches[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
function getVerticalOrientation(orientation) {
|
|
|
|
var matches = /top|bottom/.exec(orientation);
|
|
|
|
|
|
|
|
return matches && matches[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
function isOppositeOrientation(a, b) {
|
|
|
|
return oppositeOrientationMapping[a] === b;
|
|
|
|
}
|
|
|
|
|
|
|
|
function isOppositeHorizontalOrientation(a, b) {
|
|
|
|
var horizontalOrientation = getHorizontalOrientation(a);
|
|
|
|
|
|
|
|
var oppositeHorizontalOrientation = oppositeOrientationMapping[horizontalOrientation];
|
|
|
|
|
|
|
|
return b.indexOf(oppositeHorizontalOrientation) !== -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
function isOppositeVerticalOrientation(a, b) {
|
|
|
|
var verticalOrientation = getVerticalOrientation(a);
|
|
|
|
|
|
|
|
var oppositeVerticalOrientation = oppositeOrientationMapping[verticalOrientation];
|
|
|
|
|
|
|
|
return b.indexOf(oppositeVerticalOrientation) !== -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
function isHorizontalOrientation(orientation) {
|
|
|
|
return orientation === 'right' || orientation === 'left';
|
|
|
|
}
|
|
|
|
|
|
|
|
function getBoundaryEventPreferredLayouts(source, target) {
|
|
|
|
var sourceMid = getMid(source),
|
|
|
|
targetMid = getMid(target),
|
|
|
|
attachOrientation = getAttachOrientation(source),
|
|
|
|
sourceLayout,
|
2019-04-24 07:53:23 +00:00
|
|
|
targetLayout;
|
2018-07-04 07:13:38 +00:00
|
|
|
|
|
|
|
var isLoop = isSame(source.host, target);
|
|
|
|
|
|
|
|
var attachedToSide = isAnyOrientation(attachOrientation, [ 'top', 'right', 'bottom', 'left' ]);
|
|
|
|
|
|
|
|
var targetOrientation = getOrientation(targetMid, sourceMid, {
|
|
|
|
x: source.width / 2 + target.width / 2,
|
|
|
|
y: source.height / 2 + target.height / 2
|
|
|
|
});
|
|
|
|
|
|
|
|
// source layout
|
2019-04-24 07:53:23 +00:00
|
|
|
sourceLayout = getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide, isLoop);
|
2018-07-04 07:13:38 +00:00
|
|
|
|
2019-04-24 07:53:23 +00:00
|
|
|
// target layout
|
|
|
|
targetLayout = getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide, isLoop);
|
2018-07-04 07:13:38 +00:00
|
|
|
|
2019-04-24 07:53:23 +00:00
|
|
|
return [ sourceLayout + ':' + targetLayout ];
|
|
|
|
}
|
2018-07-04 07:13:38 +00:00
|
|
|
|
2019-04-24 07:53:23 +00:00
|
|
|
function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide, isLoop) {
|
|
|
|
|
|
|
|
// attached to either top, right, bottom or left side
|
|
|
|
if (attachedToSide) {
|
|
|
|
return orientationDirectionMapping[attachOrientation];
|
|
|
|
}
|
2018-07-04 07:13:38 +00:00
|
|
|
|
|
|
|
// attached to either top-right, top-left, bottom-right or bottom-left corner
|
|
|
|
|
2019-04-24 07:53:23 +00:00
|
|
|
// loop, same vertical or opposite horizontal orientation
|
|
|
|
if (isLoop ||
|
|
|
|
isSame(
|
|
|
|
getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation)
|
|
|
|
) ||
|
|
|
|
isOppositeOrientation(
|
|
|
|
getHorizontalOrientation(attachOrientation), getHorizontalOrientation(targetOrientation)
|
|
|
|
)) {
|
|
|
|
return orientationDirectionMapping[getVerticalOrientation(attachOrientation)];
|
2018-07-04 07:13:38 +00:00
|
|
|
}
|
|
|
|
|
2019-04-24 07:53:23 +00:00
|
|
|
// fallback
|
|
|
|
return orientationDirectionMapping[getHorizontalOrientation(attachOrientation)];
|
|
|
|
}
|
|
|
|
|
|
|
|
function getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide, isLoop) {
|
2018-07-04 07:13:38 +00:00
|
|
|
|
|
|
|
// attached to either top, right, bottom or left side
|
|
|
|
if (attachedToSide) {
|
2019-04-24 07:53:23 +00:00
|
|
|
if (isHorizontalOrientation(attachOrientation)) {
|
|
|
|
// orientation is 'right' or 'left'
|
2018-07-04 07:13:38 +00:00
|
|
|
|
2019-04-24 09:53:51 +00:00
|
|
|
// loop or opposite horizontal orientation or same orientation
|
|
|
|
if (
|
|
|
|
isLoop ||
|
|
|
|
isOppositeHorizontalOrientation(attachOrientation, targetOrientation) ||
|
|
|
|
isSame(attachOrientation, targetOrientation)
|
|
|
|
) {
|
2019-04-24 07:53:23 +00:00
|
|
|
return 'h';
|
|
|
|
}
|
|
|
|
|
|
|
|
// fallback
|
|
|
|
return 'v';
|
2018-07-04 07:13:38 +00:00
|
|
|
} else {
|
2019-04-24 07:53:23 +00:00
|
|
|
// orientation is 'top' or 'bottom'
|
2018-07-04 07:13:38 +00:00
|
|
|
|
2019-04-24 09:53:51 +00:00
|
|
|
// loop or opposite vertical orientation or same orientation
|
|
|
|
if (
|
|
|
|
isLoop ||
|
|
|
|
isOppositeVerticalOrientation(attachOrientation, targetOrientation) ||
|
|
|
|
isSame(attachOrientation, targetOrientation)
|
|
|
|
) {
|
2019-04-24 07:53:23 +00:00
|
|
|
return 'v';
|
|
|
|
}
|
2018-07-04 07:13:38 +00:00
|
|
|
|
2019-04-24 07:53:23 +00:00
|
|
|
// fallback
|
|
|
|
return 'h';
|
2018-07-04 07:13:38 +00:00
|
|
|
}
|
|
|
|
}
|
2019-04-24 07:53:23 +00:00
|
|
|
// attached to either top-right, top-left, bottom-right or bottom-left corner
|
2018-07-04 07:13:38 +00:00
|
|
|
|
2019-04-24 07:53:23 +00:00
|
|
|
// orientation is 'right', 'left'
|
|
|
|
// or same vertical orientation but also 'right' or 'left'
|
|
|
|
if (isHorizontalOrientation(targetOrientation) ||
|
|
|
|
(isSame(getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation)) &&
|
|
|
|
getHorizontalOrientation(targetOrientation))) {
|
|
|
|
return 'h';
|
|
|
|
} else {
|
|
|
|
return 'v';
|
|
|
|
}
|
|
|
|
}
|