From 085cedfda1838791e8a553123ee4121046e4f063 Mon Sep 17 00:00:00 2001 From: Maciej Barelkowski Date: Thu, 6 Jun 2019 15:05:35 +0200 Subject: [PATCH] feat(layout): correctly lay out Boundary Event loops Loops will now be laid out with respect to minimum second segment width. --- lib/features/modeling/BpmnLayouter.js | 70 +++++-- ...tSequenceFlowSpec.boundaryEventsLoops.bpmn | 42 +++-- .../modeling/layout/LayoutSequenceFlowSpec.js | 175 +++++++++++++----- 3 files changed, 201 insertions(+), 86 deletions(-) diff --git a/lib/features/modeling/BpmnLayouter.js b/lib/features/modeling/BpmnLayouter.js index 5a101803..b708e6a9 100644 --- a/lib/features/modeling/BpmnLayouter.js +++ b/lib/features/modeling/BpmnLayouter.js @@ -23,6 +23,8 @@ import { import { is } from '../../util/ModelUtil'; +var BOUNDARY_TO_HOST_THRESHOLD = 40; + export default function BpmnLayouter() {} inherits(BpmnLayouter, BaseLayouter); @@ -86,7 +88,7 @@ BpmnLayouter.prototype.layoutConnection = function(connection, hints) { if (is(source, 'bpmn:BoundaryEvent')) { manhattanOptions = { - preferredLayouts: getBoundaryEventPreferredLayouts(source, target) + preferredLayouts: getBoundaryEventPreferredLayouts(source, target, end) }; } else @@ -262,7 +264,7 @@ function isHorizontalOrientation(orientation) { return orientation === 'right' || orientation === 'left'; } -function getBoundaryEventPreferredLayouts(source, target) { +function getBoundaryEventPreferredLayouts(source, target, end) { var sourceMid = getMid(source), targetMid = getMid(target), attachOrientation = getAttachOrientation(source), @@ -278,16 +280,52 @@ function getBoundaryEventPreferredLayouts(source, target) { y: source.height / 2 + target.height / 2 }); + if (isLoop) { + return getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end); + } + // source layout - sourceLayout = getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide, isLoop); + sourceLayout = getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide); // target layout - targetLayout = getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide, isLoop); + targetLayout = getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide); return [ sourceLayout + ':' + targetLayout ]; } -function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide, isLoop) { +function getBoundaryEventLoopLayout(attachOrientation, attachedToSide, source, target, end) { + + var sourceLayout = orientationDirectionMapping[attachedToSide ? attachOrientation : getVerticalOrientation(attachOrientation)], + targetLayout; + + if (attachedToSide) { + if (isHorizontalOrientation(attachOrientation)) { + targetLayout = shouldConnectToSameSide('y', source, target, end) ? 'h' : 'b'; + } else { + targetLayout = shouldConnectToSameSide('x', source, target, end) ? 'v' : 'l'; + } + } else { + targetLayout = 'v'; + } + + return [ sourceLayout + ':' + targetLayout ]; +} + +function shouldConnectToSameSide(axis, source, target, end) { + var threshold = BOUNDARY_TO_HOST_THRESHOLD; + + return !( + areCloseOnAxis(axis, end, target, threshold) || + areCloseOnAxis(axis, end, { x: target.x + target.width, y: target.y + target.height }, threshold) || + areCloseOnAxis(axis, end, getMid(source), threshold) + ); +} + +function areCloseOnAxis(axis, a, b, threshold) { + return Math.abs(a[axis] - b[axis]) < threshold; +} + +function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, attachedToSide) { // attached to either top, right, bottom or left side if (attachedToSide) { @@ -296,14 +334,12 @@ function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, atta // 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) - )) { + // same vertical or opposite horizontal orientation + if (isSame( + getVerticalOrientation(attachOrientation), getVerticalOrientation(targetOrientation) + ) || isOppositeOrientation( + getHorizontalOrientation(attachOrientation), getHorizontalOrientation(targetOrientation) + )) { return orientationDirectionMapping[getVerticalOrientation(attachOrientation)]; } @@ -311,16 +347,15 @@ function getBoundaryEventSourceLayout(attachOrientation, targetOrientation, atta return orientationDirectionMapping[getHorizontalOrientation(attachOrientation)]; } -function getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide, isLoop) { +function getBoundaryEventTargetLayout(attachOrientation, targetOrientation, attachedToSide) { // attached to either top, right, bottom or left side if (attachedToSide) { if (isHorizontalOrientation(attachOrientation)) { // orientation is 'right' or 'left' - // loop or opposite horizontal orientation or same orientation + // opposite horizontal orientation or same orientation if ( - isLoop || isOppositeHorizontalOrientation(attachOrientation, targetOrientation) || isSame(attachOrientation, targetOrientation) ) { @@ -332,9 +367,8 @@ function getBoundaryEventTargetLayout(attachOrientation, targetOrientation, atta } else { // orientation is 'top' or 'bottom' - // loop or opposite vertical orientation or same orientation + // opposite vertical orientation or same orientation if ( - isLoop || isOppositeVerticalOrientation(attachOrientation, targetOrientation) || isSame(attachOrientation, targetOrientation) ) { diff --git a/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.boundaryEventsLoops.bpmn b/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.boundaryEventsLoops.bpmn index b18af7fe..852a5784 100644 --- a/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.boundaryEventsLoops.bpmn +++ b/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.boundaryEventsLoops.bpmn @@ -1,40 +1,48 @@ - + - - - + + + + + - + - - - - + - - - - + - + - + - - + + + + + + + + + + + + + + diff --git a/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.js b/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.js index 9dd12a03..6b572347 100644 --- a/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.js +++ b/test/spec/features/modeling/layout/LayoutSequenceFlowSpec.js @@ -26,65 +26,138 @@ describe('features/modeling - layout', function() { beforeEach(bootstrapModeler(diagramXML, { modules: testModules })); - it('attached top right', function() { - // when - var connection = connect('BoundaryEvent_TopRight', 'SubProcess'); + describe('in the corner', function() { - // then - expect(connection).to.have.waypoints([ - { original: { x: 650, y: 300 }, x: 650, y: 282 }, - { x: 650, y: 262 }, - { x: 475, y: 262 }, - { original: { x: 475, y: 400 }, x: 475, y: 300 } - ]); + it('attached top right', function() { + + // when + var connection = connect('BoundaryEvent_TopRight', 'SubProcess'); + + // then + expect(connection).to.have.waypoints([ + { original: { x: 550, y: 200 }, x: 550, y: 182 }, + { x: 550, y: 162 }, + { x: 375, y: 162 }, + { original: { x: 375, y: 300 }, x: 375, y: 200 } + ]); + }); + + + it('attached bottom right', function() { + + // when + var connection = connect('BoundaryEvent_BottomRight', 'SubProcess'); + + // then + expect(connection).to.have.waypoints([ + { original: { x: 550, y: 368 } , x: 568, y: 368 }, + { x: 588, y: 368 }, + { x: 588, y: 300 }, + { original: { x: 375, y: 300 } , x: 550, y: 300 } + ]); + }); + + + it('attached bottom left', function() { + + // when + var connection = connect('BoundaryEvent_BottomLeft', 'SubProcess'); + + // then + expect(connection).to.have.waypoints([ + { original: { x: 200, y: 500 }, x: 200, y: 418 }, + { x: 200, y: 438 }, + { x: 375, y: 438 }, + { original: { x: 375, y: 300 }, x: 375, y: 400 } + ]); + }); + + + it('attached top left', function() { + + // when + var connection = connect('BoundaryEvent_TopLeft', 'SubProcess'); + + // then + expect(connection).to.have.waypoints([ + { original: { x: 200, y: 238 }, x: 182, y: 238 }, + { x: 162, y: 238 }, + { x: 162, y: 300 }, + { original: { x: 375, y: 300 }, x: 200, y: 300 } + ]); + }); }); - it('attached bottom right', function() { + describe('on the side center', function() { - // when - var connection = connect('BoundaryEvent_BottomRight', 'SubProcess'); + var host = 'SubProcess_2'; - // then - expect(connection).to.have.waypoints([ - { original: { x: 650, y: 468 }, x: 668, y: 468 }, - { x: 688, y: 468 }, - { x: 688, y: 400 }, - { original: { x: 475, y: 400 }, x: 650, y: 400 } - ]); + + it('attached top center', function() { + + // when + var connection = connect('BoundaryEvent_TopCenter', host); + + // then + expect(connection).to.have.waypoints([ + { original: { x: 375, y: 460 }, x: 375, y: 442 }, + { x:375, y: 422 }, + { x:180, y: 422 }, + { x:180, y: 560 }, + { original:{ x: 375, y: 560 }, x: 200, y: 560 } + ]); + }); + + + it('attached center right', function() { + + // when + var connection = connect('BoundaryEvent_CenterRight', host); + + // then + expect(connection).to.have.waypoints([ + { original: { x: 550, y: 560 }, x: 568, y: 560 }, + { x: 588, y: 560 }, + { x: 588, y: 680 }, + { x: 375, y: 680 }, + { original: { x: 375, y: 560 }, x: 375, y: 660 } + ]); + }); + + + it('attached bottom center', function() { + + // when + var connection = connect('BoundaryEvent_BottomCenter', host); + + // then + expect(connection).to.have.waypoints([ + { original: { x: 375, y: 660 }, x: 375, y: 678 }, + { x: 375, y: 698 }, + { x: 180, y: 698 }, + { x: 180, y: 560 }, + { original: { x: 375, y: 560 }, x: 200, y: 560 } + ]); + }); + + + it('attached center left', function() { + + // when + var connection = connect('BoundaryEvent_CenterLeft', host); + + // then + expect(connection).to.have.waypoints([ + { original: { x: 200, y: 560 }, x: 182, y: 560 }, + { x: 162, y: 560 }, + { x: 162, y: 680 }, + { x: 375, y: 680 }, + { original: { x: 375, y: 560 }, x: 375, y: 660 } + ]); + }); }); - - - it('attached bottom left', function() { - - // when - var connection = connect('BoundaryEvent_BottomLeft', 'SubProcess'); - - // then - expect(connection).to.have.waypoints([ - { original: { x: 300, y: 500 }, x: 300, y: 518 }, - { x: 300, y: 538 }, - { x: 475, y: 538 }, - { original: { x: 475, y: 400 }, x: 475, y: 500 } - ]); - }); - - - it('attached top left', function() { - - // when - var connection = connect('BoundaryEvent_TopLeft', 'SubProcess'); - - // then - expect(connection).to.have.waypoints([ - { original: { x: 300, y: 338 }, x: 282, y: 338 }, - { x: 262, y: 338 }, - { x: 262, y: 400 }, - { original: { x: 475, y: 400 }, x: 300, y: 400 } - ]); - }); - });