From 8862865e2a6eafc884d61b1a3b02bdc2d5912c8b Mon Sep 17 00:00:00 2001 From: Niklas Kiefer Date: Wed, 5 Jun 2019 07:48:33 +0200 Subject: [PATCH] feat(modeling): improve label positioning for boundary events * Do not allow placing label onto host Relates to camunda/camunda-modeler#1206 --- .../AdaptiveLabelPositioningBehavior.js | 92 ++++++++++--- ...elPositioningBehavior.boundary-events.bpmn | 99 ++++++++++++++ .../AdaptiveLabelPositioningBehaviorSpec.js | 122 ++++++++++++++++++ 3 files changed, 294 insertions(+), 19 deletions(-) create mode 100644 test/spec/features/modeling/behavior/AdaptiveLabelPositioningBehavior.boundary-events.bpmn diff --git a/lib/features/modeling/behavior/AdaptiveLabelPositioningBehavior.js b/lib/features/modeling/behavior/AdaptiveLabelPositioningBehavior.js index 9cd18956..cd8b64ba 100644 --- a/lib/features/modeling/behavior/AdaptiveLabelPositioningBehavior.js +++ b/lib/features/modeling/behavior/AdaptiveLabelPositioningBehavior.js @@ -16,6 +16,14 @@ import { import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor'; +var ALIGNMENTS = [ + 'top', + 'bottom', + 'left', + 'right' +]; + +var ELEMENT_LABEL_DISTANCE = 10; /** * A component that makes sure that external labels are added @@ -70,8 +78,6 @@ export default function AdaptiveLabelPositioningBehavior(eventBus, modeling) { adjustLabelPosition(element, optimalPosition); } - var ELEMENT_LABEL_DISTANCE = 10; - function adjustLabelPosition(element, orientation) { var elementMid = getMid(element), @@ -135,6 +141,64 @@ AdaptiveLabelPositioningBehavior.$inject = [ ]; +// helpers ////////////////////// + +/** + * Return alignments which are taken by a boundary's host element + * + * @param {Shape} element + * + * @return {Array} + */ +function getTakenHostAlignments(element) { + + var hostElement = element.host, + elementMid = getMid(element), + hostOrientation = getOrientation(elementMid, hostElement); + + var freeAlignments; + + // check whether there is a multi-orientation, e.g. 'top-left' + if (hostOrientation.indexOf('-') >= 0) { + freeAlignments = hostOrientation.split('-'); + } else { + freeAlignments = [ hostOrientation ]; + } + + var takenAlignments = ALIGNMENTS.filter(function(alignment) { + + return freeAlignments.indexOf(alignment) === -1; + }); + + return takenAlignments; + +} + +/** + * Return alignments which are taken by related connections + * + * @param {Shape} element + * + * @return {Array} + */ +function getTakenConnectionAlignments(element) { + + var elementMid = getMid(element); + + var takenAlignments = [].concat( + element.incoming.map(function(c) { + return c.waypoints[c.waypoints.length - 2 ]; + }), + element.outgoing.map(function(c) { + return c.waypoints[1]; + }) + ).map(function(point) { + return getApproximateOrientation(elementMid, point); + }); + + return takenAlignments; +} + /** * Return the optimal label position around an element * or _undefined_, if none was found. @@ -155,16 +219,13 @@ function getOptimalPosition(element) { return; } - var takenAlignments = [].concat( - element.incoming.map(function(c) { - return c.waypoints[c.waypoints.length - 2 ]; - }), - element.outgoing.map(function(c) { - return c.waypoints[1]; - }) - ).map(function(point) { - return getApproximateOrientation(elementMid, point); - }); + var takenAlignments = getTakenConnectionAlignments(element); + + if (element.host) { + var takenHostAlignments = getTakenHostAlignments(element); + + takenAlignments = takenAlignments.concat(takenHostAlignments); + } var freeAlignments = ALIGNMENTS.filter(function(alignment) { @@ -179,13 +240,6 @@ function getOptimalPosition(element) { return freeAlignments[0]; } -var ALIGNMENTS = [ - 'top', - 'bottom', - 'left', - 'right' -]; - function getApproximateOrientation(p0, p1) { return getOrientation(p1, p0, 5); } diff --git a/test/spec/features/modeling/behavior/AdaptiveLabelPositioningBehavior.boundary-events.bpmn b/test/spec/features/modeling/behavior/AdaptiveLabelPositioningBehavior.boundary-events.bpmn new file mode 100644 index 00000000..4d4d0967 --- /dev/null +++ b/test/spec/features/modeling/behavior/AdaptiveLabelPositioningBehavior.boundary-events.bpmn @@ -0,0 +1,99 @@ + + + + + + SequenceFlow_1 + + + SequenceFlow_1 + + + + + + + + SequenceFlow_2 + + + SequenceFlow_2 + + + + + SequenceFlow_3 + + + SequenceFlow_3 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/spec/features/modeling/behavior/AdaptiveLabelPositioningBehaviorSpec.js b/test/spec/features/modeling/behavior/AdaptiveLabelPositioningBehaviorSpec.js index 259226cf..e972d736 100644 --- a/test/spec/features/modeling/behavior/AdaptiveLabelPositioningBehaviorSpec.js +++ b/test/spec/features/modeling/behavior/AdaptiveLabelPositioningBehaviorSpec.js @@ -15,6 +15,7 @@ var testModules = [ coreModule ]; +var ATTACH = { attach: true }; describe('modeling/behavior - AdaptiveLabelPositioningBehavior', function() { @@ -284,4 +285,125 @@ describe('modeling/behavior - AdaptiveLabelPositioningBehavior', function() { }); + + describe('boundary-events', function() { + + var diagramXML = require('./AdaptiveLabelPositioningBehavior.boundary-events.bpmn'); + + beforeEach(bootstrapModeler(diagramXML, { + modules: testModules + })); + + describe('on boundary label creation', function() { + + it('should NOT create label onto host', inject( + function(elementRegistry, modeling) { + + // given + var element = elementRegistry.get('BoundaryEvent_1'); + + // when + modeling.updateProperties(element, { name: 'FOO BAR' }); + + // then + expectLabelOrientation(element, 'bottom'); + } + )); + + + it('should create label to the left', inject( + function(elementRegistry, modeling) { + + // given + var element = elementRegistry.get('BoundaryEvent_4'); + + // when + modeling.updateProperties(element, { name: 'FOO BAR' }); + + // then + expectLabelOrientation(element, 'left'); + } + )); + + }); + + + describe('on connect', function() { + + it('should keep label where it is if no better position', inject( + function(elementRegistry, modeling) { + + // given + var source = elementRegistry.get('BoundaryEvent_2'), + target = elementRegistry.get('EndEvent_2'); + + // when + modeling.connect(source, target); + + // then + expectLabelOrientation(source, 'right'); + } + )); + + }); + + + describe('on reconnect', function() { + + it('should keep label where it is if no better position', inject( + function(elementRegistry, modeling) { + + // given + var source = elementRegistry.get('BoundaryEvent_3'), + target = elementRegistry.get('EndEvent_1'), + connection = elementRegistry.get('SequenceFlow_2'); + + // when + modeling.reconnectEnd(connection, target, { x: target.x + target.width / 2, y: target.y }); + + // then + expectLabelOrientation(source, 'bottom'); + } + )); + + }); + + + describe('on boundary move', function() { + + it('should move label to the left', inject( + function(elementRegistry, modeling) { + + // given + var element = elementRegistry.get('BoundaryEvent_3'), + host = elementRegistry.get('Task_3'); + + // when + modeling.moveElements([ element ], { x: -50, y: -50 }, host, ATTACH); + + // then + expectLabelOrientation(element, 'left'); + } + )); + + + it('should move label to the top', inject( + function(elementRegistry, modeling) { + + // given + var element = elementRegistry.get('BoundaryEvent_3'), + host = elementRegistry.get('Task_3'); + + // when + modeling.moveElements([ element ], { x: 50, y: -80 }, host, ATTACH); // top-right corner + + // then + expectLabelOrientation(element, 'top'); + } + )); + + }); + + }); + });