Skip to content

Commit

Permalink
feat(modeling/BpmnLayouter): handle boundary events
Browse files Browse the repository at this point in the history
This adds proper connection layouting for sequence
flows leaving from boundary events.

If needed, such connections will be layoute with
an U-turn.

Closes #467
  • Loading branch information
philippfromme authored and nikku committed Jul 13, 2018
1 parent b3c05b6 commit 220c0a7
Show file tree
Hide file tree
Showing 3 changed files with 316 additions and 200 deletions.
202 changes: 161 additions & 41 deletions lib/features/modeling/BpmnLayouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,15 @@ import {
import BaseLayouter from 'diagram-js/lib/layout/BaseLayouter';

import {
repairConnection
repairConnection,
withoutRedundantPoints
} from 'diagram-js/lib/layout/ManhattanLayout';

import {
getMid,
getOrientation
} from 'diagram-js/lib/layout/LayoutUtil';

import {
pointsOnLine
} from 'diagram-js/lib/util/Geometry';

import {
isExpanded
} from '../../util/DiUtil';
Expand All @@ -32,7 +29,6 @@ inherits(BpmnLayouter, BaseLayouter);


BpmnLayouter.prototype.layoutConnection = function(connection, hints) {

hints = hints || {};

var source = connection.source,
Expand Down Expand Up @@ -107,29 +103,18 @@ BpmnLayouter.prototype.layoutConnection = function(connection, hints) {
//
// except for
//
// (1) outgoing of BoundaryEvents -> layout h:v or v:h based on attach orientation
// (1) outgoing of BoundaryEvents -> layout based on attach orientation and target orientation
// (2) incoming / outgoing of Gateway -> v:h (outgoing), h:v (incoming)
//
if (is(connection, 'bpmn:SequenceFlow') ||
isCompensationAssociation(connection)) {

// make sure boundary event connections do
// not look ugly =:>
if (is(source, 'bpmn:BoundaryEvent')) {

var orientation = getAttachOrientation(source);

if (/left|right/.test(orientation)) {
manhattanOptions = {
preferredLayouts: [ 'h:v' ]
};
} else
manhattanOptions = {
preferredLayouts: getBoundaryEventPreferredLayouts(source, target)
};

if (/top|bottom/.test(orientation)) {
manhattanOptions = {
preferredLayouts: [ 'v:h' ]
};
}
} else

if (is(source, 'bpmn:Gateway')) {
Expand All @@ -146,7 +131,6 @@ BpmnLayouter.prototype.layoutConnection = function(connection, hints) {
};
}

// apply horizontal love <3
else {
manhattanOptions = {
preferredLayouts: [ 'h:h' ]
Expand All @@ -159,25 +143,14 @@ BpmnLayouter.prototype.layoutConnection = function(connection, hints) {
manhattanOptions = assign(manhattanOptions, hints);

updatedWaypoints =
repairConnection(
source, target,
start, end,
waypoints,
manhattanOptions);

// filter un-needed waypoints that may be the result of
// bundle collapsing
updatedWaypoints = updatedWaypoints && updatedWaypoints.reduce(function(points, p, idx) {

var previous = points[points.length - 1],
next = updatedWaypoints[idx + 1];

if (!pointsOnLine(previous, next, p, 0)) {
points.push(p);
}

return points;
}, []);
withoutRedundantPoints(
repairConnection(
source, target,
start, end,
waypoints,
manhattanOptions
)
);
}

return updatedWaypoints || [ start, end ];
Expand Down Expand Up @@ -210,4 +183,151 @@ function isCompensationAssociation(connection) {

function isExpandedSubProcess(element) {
return is(element, 'bpmn:SubProcess') && isExpanded(element);
}

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,
targetlayout;

var isLoop = isSame(source.host, target);

var attachedToSide = isAnyOrientation(attachOrientation, [ 'top', 'right', 'bottom', 'left' ]);

var isHorizontalAttachOrientation = isHorizontalOrientation(attachOrientation);

var targetOrientation = getOrientation(targetMid, sourceMid, {
x: source.width / 2 + target.width / 2,
y: source.height / 2 + target.height / 2
});

// source layout

// attached to either top, right, bottom or left side
if (attachedToSide) {

sourceLayout = orientationDirectionMapping[
isHorizontalAttachOrientation ?
getHorizontalOrientation(attachOrientation) :
getVerticalOrientation(attachOrientation)
];

} else

// attached to either top-right, top-left, bottom-right or bottom-left corner
{

// loop, same vertical or opposite horizontal orientation
if (isLoop ||
isSame(getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation)) ||
isOppositeOrientation(getHorizontalOrientation(attachOrientation), getHorizontalOrientation(targetOrientation))
) {
sourceLayout = orientationDirectionMapping[getVerticalOrientation(attachOrientation)];
} else {
sourceLayout = orientationDirectionMapping[getHorizontalOrientation(attachOrientation)];
}

}

// target layout

// attached to either top, right, bottom or left side
if (attachedToSide) {

// loop or opposite horizontal/vertical orientation
if (
isLoop ||
(isHorizontalAttachOrientation ?
isOppositeHorizontalOrientation(attachOrientation, targetOrientation) :
isOppositeVerticalOrientation(attachOrientation, targetOrientation))
) {
targetlayout = isHorizontalAttachOrientation ? 'h' : 'v';
} else {
targetlayout = isHorizontalAttachOrientation ? 'v' : 'h';
}

} else

// attached to either top-right, top-left, bottom-right or bottom-left corner
{

// orientation is 'right', 'left'
// or same vertical orientation but also 'right' or 'left'
if (
isHorizontalOrientation(targetOrientation) ||
(isSame(getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation)) &&
getHorizontalOrientation(targetOrientation))
) {
targetlayout = 'h';
} else {
targetlayout = 'v';
}

}

return [ sourceLayout + ':' + targetlayout ];
}
Original file line number Diff line number Diff line change
@@ -1,64 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<bpmn:definitions xmlns:bpmn="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:di="http://www.omg.org/spec/DD/20100524/DI" xmlns:dc="http://www.omg.org/spec/DD/20100524/DC" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn">
<bpmn:definitions 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" id="Definitions_1" targetNamespace="http://bpmn.io/schema/bpmn" exporter="Camunda Modeler" exporterVersion="1.16.0">
<bpmn:process id="Process_1" isExecutable="false">
<bpmn:task id="Task_Left" />
<bpmn:subProcess id="SubProcess" />
<bpmn:boundaryEvent id="BoundaryEvent_D" name="D" attachedToRef="SubProcess" />
<bpmn:boundaryEvent id="BoundaryEvent_C" name="C" attachedToRef="SubProcess" />
<bpmn:boundaryEvent id="BoundaryEvent_B" name="B" attachedToRef="SubProcess" />
<bpmn:boundaryEvent id="BoundaryEvent_A" name="A" attachedToRef="SubProcess" />
<bpmn:task id="Task_1" name="1" />
<bpmn:task id="Task_2" name="2" />
<bpmn:task id="Task_3" name="3" />
<bpmn:task id="Task_4" name="4" />
<bpmn:task id="Task_5" name="5" />
<bpmn:task id="Task_6" name="6" />
<bpmn:task id="Task_Right" />
<bpmn:boundaryEvent id="BoundaryEvent_TopLeft" attachedToRef="SubProcess" />
<bpmn:boundaryEvent id="BoundaryEvent_BottomRight" attachedToRef="SubProcess" />
<bpmn:boundaryEvent id="BoundaryEvent_BottomLeft" attachedToRef="SubProcess" />
<bpmn:boundaryEvent id="BoundaryEvent_TopRight" attachedToRef="SubProcess" />
<bpmn:task id="Task_Bottom" />
<bpmn:task id="Task_Top" />
</bpmn:process>
<bpmndi:BPMNDiagram id="BPMNDiagram_1">
<bpmndi:BPMNPlane id="BPMNPlane_1" bpmnElement="Process_1">
<bpmndi:BPMNShape id="SubProcess_di" bpmnElement="SubProcess" isExpanded="true">
<dc:Bounds x="505" y="258" width="350" height="200" />
<bpmndi:BPMNShape id="Task_0b6k3xo_di" bpmnElement="Task_Left">
<dc:Bounds x="0" y="350" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BoundaryEvent_D_di" bpmnElement="BoundaryEvent_D">
<dc:Bounds x="797" y="440" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="744" y="424" width="90" height="20" />
</bpmndi:BPMNLabel>
<bpmndi:BPMNShape id="SubProcess_12qmapm_di" bpmnElement="SubProcess" isExpanded="true">
<dc:Bounds x="300" y="300" width="350" height="200" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BoundaryEvent_C_di" bpmnElement="BoundaryEvent_C">
<dc:Bounds x="837" y="275" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="775" y="284" width="90" height="20" />
</bpmndi:BPMNLabel>
<bpmndi:BPMNShape id="Task_174r9fd_di" bpmnElement="Task_Right">
<dc:Bounds x="850" y="350" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BoundaryEvent_B_di" bpmnElement="BoundaryEvent_B">
<dc:Bounds x="568" y="240" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="540" y="280" width="90" height="20" />
</bpmndi:BPMNLabel>
<bpmndi:BPMNShape id="BoundaryEvent_0s0nl1k_di" bpmnElement="BoundaryEvent_TopLeft">
<dc:Bounds x="282" y="320" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="BoundaryEvent_A_di" bpmnElement="BoundaryEvent_A">
<dc:Bounds x="487" y="399" width="36" height="36" />
<bpmndi:BPMNLabel>
<dc:Bounds x="495" y="407" width="90" height="20" />
</bpmndi:BPMNLabel>
<bpmndi:BPMNShape id="BoundaryEvent_0nomac7_di" bpmnElement="BoundaryEvent_BottomRight">
<dc:Bounds x="632" y="450" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_1_di" bpmnElement="Task_1">
<dc:Bounds x="287" y="354" width="100" height="80" />
<bpmndi:BPMNShape id="BoundaryEvent_1spolhy_di" bpmnElement="BoundaryEvent_BottomLeft">
<dc:Bounds x="282" y="482" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_2_di" bpmnElement="Task_2">
<dc:Bounds x="362" y="503" width="100" height="80" />
<bpmndi:BPMNShape id="BoundaryEvent_13iwzlu_di" bpmnElement="BoundaryEvent_TopRight">
<dc:Bounds x="632" y="282" width="36" height="36" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_3_di" bpmnElement="Task_3">
<dc:Bounds x="378" y="122" width="100" height="80" />
<bpmndi:BPMNShape id="Task_0ygk7bh_di" bpmnElement="Task_Bottom">
<dc:Bounds x="400" y="650" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_4_di" bpmnElement="Task_4">
<dc:Bounds x="536" y="81" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_5_di" bpmnElement="Task_5">
<dc:Bounds x="966" y="175" width="100" height="80" />
</bpmndi:BPMNShape>
<bpmndi:BPMNShape id="Task_6_di" bpmnElement="Task_6">
<dc:Bounds x="991" y="443" width="100" height="80" />
<bpmndi:BPMNShape id="Task_1bc634w_di" bpmnElement="Task_Top">
<dc:Bounds x="400" y="0" width="100" height="80" />
</bpmndi:BPMNShape>
</bpmndi:BPMNPlane>
</bpmndi:BPMNDiagram>
Expand Down
Loading

0 comments on commit 220c0a7

Please sign in to comment.