You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
274 lines
5.6 KiB
274 lines
5.6 KiB
import inherits from 'inherits-browser'; |
|
|
|
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<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 |
|
* 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; |
|
}
|
|
|