feat(modeling): improve label positioning for boundary events
* Do not allow placing label onto host Relates to camunda/camunda-modeler#1206
This commit is contained in:
parent
2804e559e9
commit
8862865e2a
|
@ -16,6 +16,14 @@ import {
|
||||||
|
|
||||||
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
|
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
|
* A component that makes sure that external labels are added
|
||||||
|
@ -70,8 +78,6 @@ export default function AdaptiveLabelPositioningBehavior(eventBus, modeling) {
|
||||||
adjustLabelPosition(element, optimalPosition);
|
adjustLabelPosition(element, optimalPosition);
|
||||||
}
|
}
|
||||||
|
|
||||||
var ELEMENT_LABEL_DISTANCE = 10;
|
|
||||||
|
|
||||||
function adjustLabelPosition(element, orientation) {
|
function adjustLabelPosition(element, orientation) {
|
||||||
|
|
||||||
var elementMid = getMid(element),
|
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<String>}
|
||||||
|
*/
|
||||||
|
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<String>}
|
||||||
|
*/
|
||||||
|
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
|
* Return the optimal label position around an element
|
||||||
* or _undefined_, if none was found.
|
* or _undefined_, if none was found.
|
||||||
|
@ -155,16 +219,13 @@ function getOptimalPosition(element) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var takenAlignments = [].concat(
|
var takenAlignments = getTakenConnectionAlignments(element);
|
||||||
element.incoming.map(function(c) {
|
|
||||||
return c.waypoints[c.waypoints.length - 2 ];
|
if (element.host) {
|
||||||
}),
|
var takenHostAlignments = getTakenHostAlignments(element);
|
||||||
element.outgoing.map(function(c) {
|
|
||||||
return c.waypoints[1];
|
takenAlignments = takenAlignments.concat(takenHostAlignments);
|
||||||
})
|
}
|
||||||
).map(function(point) {
|
|
||||||
return getApproximateOrientation(elementMid, point);
|
|
||||||
});
|
|
||||||
|
|
||||||
var freeAlignments = ALIGNMENTS.filter(function(alignment) {
|
var freeAlignments = ALIGNMENTS.filter(function(alignment) {
|
||||||
|
|
||||||
|
@ -179,13 +240,6 @@ function getOptimalPosition(element) {
|
||||||
return freeAlignments[0];
|
return freeAlignments[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
var ALIGNMENTS = [
|
|
||||||
'top',
|
|
||||||
'bottom',
|
|
||||||
'left',
|
|
||||||
'right'
|
|
||||||
];
|
|
||||||
|
|
||||||
function getApproximateOrientation(p0, p1) {
|
function getApproximateOrientation(p0, p1) {
|
||||||
return getOrientation(p1, p0, 5);
|
return getOrientation(p1, p0, 5);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<definitions xmlns="http://www.omg.org/spec/BPMN/20100524/MODEL" xmlns:bpmndi="http://www.omg.org/spec/BPMN/20100524/DI" xmlns:omgdi="http://www.omg.org/spec/DD/20100524/DI" xmlns:omgdc="http://www.omg.org/spec/DD/20100524/DC" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" id="sid-38422fae-e03e-43a3-bef4-bd33b32041b2" targetNamespace="http://bpmn.io/bpmn" exporter="Camunda Modeler" exporterVersion="3.1.0">
|
||||||
|
<process id="Process_1" isExecutable="false">
|
||||||
|
<task id="Task_1" />
|
||||||
|
<boundaryEvent id="BoundaryEvent_1" attachedToRef="Task_1">
|
||||||
|
<outgoing>SequenceFlow_1</outgoing>
|
||||||
|
</boundaryEvent>
|
||||||
|
<endEvent id="EndEvent_1">
|
||||||
|
<incoming>SequenceFlow_1</incoming>
|
||||||
|
</endEvent>
|
||||||
|
<sequenceFlow id="SequenceFlow_1" sourceRef="BoundaryEvent_1" targetRef="EndEvent_1" />
|
||||||
|
<task id="Task_2" />
|
||||||
|
<boundaryEvent id="BoundaryEvent_2" name="foo" attachedToRef="Task_2" />
|
||||||
|
<endEvent id="EndEvent_2" />
|
||||||
|
<task id="Task_3" />
|
||||||
|
<boundaryEvent id="BoundaryEvent_3" name="foo" attachedToRef="Task_3">
|
||||||
|
<outgoing>SequenceFlow_2</outgoing>
|
||||||
|
</boundaryEvent>
|
||||||
|
<endEvent id="EndEvent_3">
|
||||||
|
<incoming>SequenceFlow_2</incoming>
|
||||||
|
</endEvent>
|
||||||
|
<sequenceFlow id="SequenceFlow_2" sourceRef="BoundaryEvent_3" targetRef="EndEvent_3" />
|
||||||
|
<task id="Task_4" />
|
||||||
|
<boundaryEvent id="BoundaryEvent_4" name="" attachedToRef="Task_4">
|
||||||
|
<outgoing>SequenceFlow_3</outgoing>
|
||||||
|
</boundaryEvent>
|
||||||
|
<endEvent id="EndEvent_4">
|
||||||
|
<incoming>SequenceFlow_3</incoming>
|
||||||
|
</endEvent>
|
||||||
|
<sequenceFlow id="SequenceFlow_3" name="" sourceRef="BoundaryEvent_4" targetRef="EndEvent_4" />
|
||||||
|
</process>
|
||||||
|
<bpmndi:BPMNDiagram id="BpmnDiagram_1">
|
||||||
|
<bpmndi:BPMNPlane id="BpmnPlane_1" bpmnElement="Process_1">
|
||||||
|
<bpmndi:BPMNShape id="Task_1_di" bpmnElement="Task_1">
|
||||||
|
<omgdc:Bounds x="156" y="110" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="BoundaryEvent_1_di" bpmnElement="BoundaryEvent_1">
|
||||||
|
<omgdc:Bounds x="168" y="172" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="EndEvent_1_di" bpmnElement="EndEvent_1">
|
||||||
|
<omgdc:Bounds x="318" y="232" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNEdge id="SequenceFlow_1_di" bpmnElement="SequenceFlow_1">
|
||||||
|
<omgdi:waypoint x="186" y="208" />
|
||||||
|
<omgdi:waypoint x="186" y="250" />
|
||||||
|
<omgdi:waypoint x="318" y="250" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNShape id="Task_2_di" bpmnElement="Task_2">
|
||||||
|
<omgdc:Bounds x="156" y="300" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="BoundaryEvent_2_di" bpmnElement="BoundaryEvent_2">
|
||||||
|
<omgdc:Bounds x="238" y="322" width="36" height="36" />
|
||||||
|
<bpmndi:BPMNLabel>
|
||||||
|
<omgdc:Bounds x="279" y="332" width="16" height="14" />
|
||||||
|
</bpmndi:BPMNLabel>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="EndEvent_2_di" bpmnElement="EndEvent_2">
|
||||||
|
<omgdc:Bounds x="318" y="422" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="Task_3_di" bpmnElement="Task_3">
|
||||||
|
<omgdc:Bounds x="484" y="110" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="BoundaryEvent_3_di" bpmnElement="BoundaryEvent_3">
|
||||||
|
<omgdc:Bounds x="516" y="172" width="36" height="36" />
|
||||||
|
<bpmndi:BPMNLabel>
|
||||||
|
<omgdc:Bounds x="526" y="216" width="16" height="14" />
|
||||||
|
</bpmndi:BPMNLabel>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="EndEvent_3_di" bpmnElement="EndEvent_3">
|
||||||
|
<omgdc:Bounds x="602" y="252" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNEdge id="SequenceFlow_2_di" bpmnElement="SequenceFlow_2">
|
||||||
|
<omgdi:waypoint x="534" y="208" />
|
||||||
|
<omgdi:waypoint x="534" y="270" />
|
||||||
|
<omgdi:waypoint x="602" y="270" />
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
<bpmndi:BPMNShape id="Task_4_di" bpmnElement="Task_4">
|
||||||
|
<omgdc:Bounds x="484" y="300" width="100" height="80" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="BoundaryEvent_4_di" bpmnElement="BoundaryEvent_4">
|
||||||
|
<omgdc:Bounds x="466" y="362" width="36" height="36" />
|
||||||
|
<bpmndi:BPMNLabel>
|
||||||
|
<omgdc:Bounds x="472" y="332" width="25" height="14" />
|
||||||
|
</bpmndi:BPMNLabel>
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNShape id="EndEvent_4_di" bpmnElement="EndEvent_4">
|
||||||
|
<omgdc:Bounds x="552" y="442" width="36" height="36" />
|
||||||
|
</bpmndi:BPMNShape>
|
||||||
|
<bpmndi:BPMNEdge id="SequenceFlow_3_di" bpmnElement="SequenceFlow_3">
|
||||||
|
<omgdi:waypoint x="484" y="398" />
|
||||||
|
<omgdi:waypoint x="484" y="460" />
|
||||||
|
<omgdi:waypoint x="552" y="460" />
|
||||||
|
<bpmndi:BPMNLabel>
|
||||||
|
<omgdc:Bounds x="490" y="426" width="19" height="14" />
|
||||||
|
</bpmndi:BPMNLabel>
|
||||||
|
</bpmndi:BPMNEdge>
|
||||||
|
</bpmndi:BPMNPlane>
|
||||||
|
</bpmndi:BPMNDiagram>
|
||||||
|
</definitions>
|
|
@ -15,6 +15,7 @@ var testModules = [
|
||||||
coreModule
|
coreModule
|
||||||
];
|
];
|
||||||
|
|
||||||
|
var ATTACH = { attach: true };
|
||||||
|
|
||||||
describe('modeling/behavior - AdaptiveLabelPositioningBehavior', function() {
|
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');
|
||||||
|
}
|
||||||
|
));
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue