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'; var ALIGNMENTS = [ 'top', 'bottom', 'left', 'right' ]; var ELEMENT_LABEL_DISTANCE = 10; /** * 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.updateWaypoints' ], function(event) { var context = event.context, connection = context.connection, source = connection.source, target = connection.target, hints = context.hints || {}; if (hints.createElementsBehavior !== false) { checkLabelAdjustment(source); checkLabelAdjustment(target); } }); this.postExecuted([ 'label.create' ], function(event) { var context = event.context, shape = context.shape, hints = context.hints || {}; if (hints.createElementsBehavior !== false) { checkLabelAdjustment(shape.labelTarget); } }); this.postExecuted([ 'elements.create' ], function(event) { var context = event.context, elements = context.elements, hints = context.hints || {}; if (hints.createElementsBehavior !== false) { elements.forEach(function(element) { checkLabelAdjustment(element); }); } }); 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); } function adjustLabelPosition(element, orientation) { var elementMid = getMid(element), label = element.label, labelMid = getMid(label); // ignore labels that are being created if (!label.parent) { return; } 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' ]; // 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. * * @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 = getTakenConnectionAlignments(element); if (element.host) { var takenHostAlignments = getTakenHostAlignments(element); takenAlignments = takenAlignments.concat(takenHostAlignments); } 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]; } function getApproximateOrientation(p0, p1) { return getOrientation(p1, p0, 5); } function isAligned(orientation) { return ALIGNMENTS.indexOf(orientation) !== -1; }