bpmn-js/lib/features/modeling/behavior/AdaptiveLabelPositioningBeh...

198 lines
3.9 KiB
JavaScript

import inherits from 'inherits';
import {
getOrientation,
getMid,
asTRBL
} from 'diagram-js/lib/layout/LayoutUtil';
import {
substract
} from 'diagram-js/lib/util/Math';
import {
hasExternalLabel
} from '../../../util/LabelUtil';
import CommandInterceptor from 'diagram-js/lib/command/CommandInterceptor';
/**
* A component that makes sure that external labels are added
* together with respective elements and properly updated (DI wise)
* during move.
*
* @param {EventBus} eventBus
* @param {Modeling} modeling
*/
export default function AdaptiveLabelPositioningBehavior(eventBus, modeling) {
CommandInterceptor.call(this, eventBus);
this.postExecuted([
'connection.create',
'connection.layout',
'connection.reconnectEnd',
'connection.reconnectStart',
'connection.updateWaypoints'
], function(event) {
var context = event.context,
connection = context.connection;
var source = connection.source,
target = connection.target;
checkLabelAdjustment(source);
checkLabelAdjustment(target);
});
this.postExecuted([
'label.create'
], function(event) {
checkLabelAdjustment(event.context.shape.labelTarget);
});
function checkLabelAdjustment(element) {
// skip non-existing labels
if (!hasExternalLabel(element)) {
return;
}
var optimalPosition = getOptimalPosition(element);
// no optimal position found
if (!optimalPosition) {
return;
}
adjustLabelPosition(element, optimalPosition);
}
var ELEMENT_LABEL_DISTANCE = 10;
function adjustLabelPosition(element, orientation) {
var elementMid = getMid(element),
label = element.label,
labelMid = getMid(label);
var elementTrbl = asTRBL(element);
var newLabelMid;
switch (orientation) {
case 'top':
newLabelMid = {
x: elementMid.x,
y: elementTrbl.top - ELEMENT_LABEL_DISTANCE - label.height / 2
};
break;
case 'left':
newLabelMid = {
x: elementTrbl.left - ELEMENT_LABEL_DISTANCE - label.width / 2,
y: elementMid.y
};
break;
case 'bottom':
newLabelMid = {
x: elementMid.x,
y: elementTrbl.bottom + ELEMENT_LABEL_DISTANCE + label.height / 2
};
break;
case 'right':
newLabelMid = {
x: elementTrbl.right + ELEMENT_LABEL_DISTANCE + label.width / 2,
y: elementMid.y
};
break;
}
var delta = substract(newLabelMid, labelMid);
modeling.moveShape(label, delta);
}
}
inherits(AdaptiveLabelPositioningBehavior, CommandInterceptor);
AdaptiveLabelPositioningBehavior.$inject = [
'eventBus',
'modeling'
];
/**
* Return the optimal label position around an element
* or _undefined_, if none was found.
*
* @param {Shape} element
*
* @return {String} positioning identifier
*/
function getOptimalPosition(element) {
var labelMid = getMid(element.label);
var elementMid = getMid(element);
var labelOrientation = getApproximateOrientation(elementMid, labelMid);
if (!isAligned(labelOrientation)) {
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 freeAlignments = ALIGNMENTS.filter(function(alignment) {
return takenAlignments.indexOf(alignment) === -1;
});
// NOTHING TO DO; label already aligned a.O.K.
if (freeAlignments.indexOf(labelOrientation) !== -1) {
return;
}
return freeAlignments[0];
}
var ALIGNMENTS = [
'top',
'bottom',
'left',
'right'
];
function getApproximateOrientation(p0, p1) {
return getOrientation(p1, p0, 5);
}
function isAligned(orientation) {
return ALIGNMENTS.indexOf(orientation) !== -1;
}