mirror of
https://github.com/sartography/bpmn-js.git
synced 2025-02-23 14:18:10 +00:00
chore(auto-place): move common feature to diagram-js
https://github.com/bpmn-io/dmn-js/issues/470
This commit is contained in:
parent
6e38e2b827
commit
e03a4b2c59
@ -1,58 +0,0 @@
|
|||||||
import { getNewShapePosition } from './AutoPlaceUtil';
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A service that places elements connected to existing ones
|
|
||||||
* to an appropriate position in an _automated_ fashion.
|
|
||||||
*
|
|
||||||
* @param {EventBus} eventBus
|
|
||||||
* @param {Modeling} modeling
|
|
||||||
*/
|
|
||||||
export default function AutoPlace(eventBus, modeling) {
|
|
||||||
|
|
||||||
function emit(event, payload) {
|
|
||||||
return eventBus.fire(event, payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Append shape to source at appropriate position.
|
|
||||||
*
|
|
||||||
* @param {djs.model.Shape} source
|
|
||||||
* @param {djs.model.Shape} shape
|
|
||||||
*
|
|
||||||
* @return {djs.model.Shape} appended shape
|
|
||||||
*/
|
|
||||||
this.append = function(source, shape) {
|
|
||||||
|
|
||||||
emit('autoPlace.start', {
|
|
||||||
source: source,
|
|
||||||
shape: shape
|
|
||||||
});
|
|
||||||
|
|
||||||
// allow others to provide the position
|
|
||||||
var position = emit('autoPlace', {
|
|
||||||
source: source,
|
|
||||||
shape: shape
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!position) {
|
|
||||||
position = getNewShapePosition(source, shape);
|
|
||||||
}
|
|
||||||
|
|
||||||
var newShape = modeling.appendShape(source, shape, position, source.parent);
|
|
||||||
|
|
||||||
emit('autoPlace.end', {
|
|
||||||
source: source,
|
|
||||||
shape: newShape
|
|
||||||
});
|
|
||||||
|
|
||||||
return newShape;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoPlace.$inject = [
|
|
||||||
'eventBus',
|
|
||||||
'modeling'
|
|
||||||
];
|
|
@ -1,18 +0,0 @@
|
|||||||
/**
|
|
||||||
* Select element after auto placement.
|
|
||||||
*
|
|
||||||
* @param {EventBus} eventBus
|
|
||||||
* @param {Selection} selection
|
|
||||||
*/
|
|
||||||
export default function AutoPlaceSelectionBehavior(eventBus, selection) {
|
|
||||||
|
|
||||||
eventBus.on('autoPlace.end', 500, function(e) {
|
|
||||||
selection.select(e.shape);
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
AutoPlaceSelectionBehavior.$inject = [
|
|
||||||
'eventBus',
|
|
||||||
'selection'
|
|
||||||
];
|
|
@ -1,430 +0,0 @@
|
|||||||
import { is } from '../../util/ModelUtil';
|
|
||||||
import { isAny } from '../modeling/util/ModelingUtil';
|
|
||||||
|
|
||||||
import {
|
|
||||||
getMid,
|
|
||||||
asTRBL,
|
|
||||||
getOrientation
|
|
||||||
} from 'diagram-js/lib/layout/LayoutUtil';
|
|
||||||
|
|
||||||
import {
|
|
||||||
find,
|
|
||||||
reduce
|
|
||||||
} from 'min-dash';
|
|
||||||
|
|
||||||
var DEFAULT_HORIZONTAL_DISTANCE = 50;
|
|
||||||
|
|
||||||
var MAX_HORIZONTAL_DISTANCE = 250;
|
|
||||||
|
|
||||||
// padding to detect element placement
|
|
||||||
var PLACEMENT_DETECTION_PAD = 10;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Find the new position for the target element to
|
|
||||||
* connect to source.
|
|
||||||
*
|
|
||||||
* @param {djs.model.Shape} source
|
|
||||||
* @param {djs.model.Shape} element
|
|
||||||
*
|
|
||||||
* @return {Point}
|
|
||||||
*/
|
|
||||||
export function getNewShapePosition(source, element) {
|
|
||||||
|
|
||||||
if (is(element, 'bpmn:TextAnnotation')) {
|
|
||||||
return getTextAnnotationPosition(source, element);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAny(element, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) {
|
|
||||||
return getDataElementPosition(source, element);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is(element, 'bpmn:FlowNode')) {
|
|
||||||
return getFlowNodePosition(source, element);
|
|
||||||
}
|
|
||||||
|
|
||||||
return getDefaultPosition(source, element);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Always try to place element right of source;
|
|
||||||
* compute actual distance from previous nodes in flow.
|
|
||||||
*/
|
|
||||||
export function getFlowNodePosition(source, element) {
|
|
||||||
|
|
||||||
var sourceTrbl = asTRBL(source);
|
|
||||||
var sourceMid = getMid(source);
|
|
||||||
|
|
||||||
var horizontalDistance = getFlowNodeDistance(source, element);
|
|
||||||
|
|
||||||
var orientation = 'left',
|
|
||||||
rowSize = 80,
|
|
||||||
margin = 30;
|
|
||||||
|
|
||||||
if (is(source, 'bpmn:BoundaryEvent')) {
|
|
||||||
orientation = getOrientation(source, source.host, -25);
|
|
||||||
|
|
||||||
if (orientation.indexOf('top') !== -1) {
|
|
||||||
margin *= -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getVerticalDistance(orient) {
|
|
||||||
if (orient.indexOf('top') != -1) {
|
|
||||||
return -1 * rowSize;
|
|
||||||
} else if (orient.indexOf('bottom') != -1) {
|
|
||||||
return rowSize;
|
|
||||||
} else {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var position = {
|
|
||||||
x: sourceTrbl.right + horizontalDistance + element.width / 2,
|
|
||||||
y: sourceMid.y + getVerticalDistance(orientation)
|
|
||||||
};
|
|
||||||
|
|
||||||
var escapeDirection = {
|
|
||||||
y: {
|
|
||||||
margin: margin,
|
|
||||||
rowSize: rowSize
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return deconflictPosition(source, element, position, escapeDirection);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compute best distance between source and target,
|
|
||||||
* based on existing connections to and from source.
|
|
||||||
*
|
|
||||||
* @param {djs.model.Shape} source
|
|
||||||
* @param {djs.model.Shape} element
|
|
||||||
*
|
|
||||||
* @return {number} distance
|
|
||||||
*/
|
|
||||||
export function getFlowNodeDistance(source, element) {
|
|
||||||
|
|
||||||
var sourceTrbl = asTRBL(source);
|
|
||||||
|
|
||||||
// is connection a reference to consider?
|
|
||||||
function isReference(c) {
|
|
||||||
return is(c, 'bpmn:SequenceFlow');
|
|
||||||
}
|
|
||||||
|
|
||||||
function toTargetNode(weight) {
|
|
||||||
|
|
||||||
return function(shape) {
|
|
||||||
return {
|
|
||||||
shape: shape,
|
|
||||||
weight: weight,
|
|
||||||
distanceTo: function(shape) {
|
|
||||||
var shapeTrbl = asTRBL(shape);
|
|
||||||
|
|
||||||
return shapeTrbl.left - sourceTrbl.right;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function toSourceNode(weight) {
|
|
||||||
return function(shape) {
|
|
||||||
return {
|
|
||||||
shape: shape,
|
|
||||||
weight: weight,
|
|
||||||
distanceTo: function(shape) {
|
|
||||||
var shapeTrbl = asTRBL(shape);
|
|
||||||
|
|
||||||
return sourceTrbl.left - shapeTrbl.right;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// we create a list of nodes to take into consideration
|
|
||||||
// for calculating the optimal flow node distance
|
|
||||||
//
|
|
||||||
// * weight existing target nodes higher than source nodes
|
|
||||||
// * only take into account individual nodes once
|
|
||||||
//
|
|
||||||
var nodes = reduce([].concat(
|
|
||||||
getTargets(source, isReference).map(toTargetNode(5)),
|
|
||||||
getSources(source, isReference).map(toSourceNode(1))
|
|
||||||
), function(nodes, node) {
|
|
||||||
|
|
||||||
// filter out shapes connected twice via source or target
|
|
||||||
nodes[node.shape.id + '__weight_' + node.weight] = node;
|
|
||||||
|
|
||||||
return nodes;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
// compute distances between source and incoming nodes;
|
|
||||||
// group at the same time by distance and expose the
|
|
||||||
// favourite distance as { fav: { count, value } }.
|
|
||||||
var distancesGrouped = reduce(nodes, function(result, node) {
|
|
||||||
|
|
||||||
var shape = node.shape,
|
|
||||||
weight = node.weight,
|
|
||||||
distanceTo = node.distanceTo;
|
|
||||||
|
|
||||||
var fav = result.fav,
|
|
||||||
currentDistance,
|
|
||||||
currentDistanceCount,
|
|
||||||
currentDistanceEntry;
|
|
||||||
|
|
||||||
currentDistance = distanceTo(shape);
|
|
||||||
|
|
||||||
// ignore too far away peers
|
|
||||||
// or non-left to right modeled nodes
|
|
||||||
if (currentDistance < 0 || currentDistance > MAX_HORIZONTAL_DISTANCE) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
currentDistanceEntry = result[String(currentDistance)] =
|
|
||||||
result[String(currentDistance)] || {
|
|
||||||
value: currentDistance,
|
|
||||||
count: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
// inc diff count
|
|
||||||
currentDistanceCount = currentDistanceEntry.count += 1 * weight;
|
|
||||||
|
|
||||||
if (!fav || fav.count < currentDistanceCount) {
|
|
||||||
result.fav = currentDistanceEntry;
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}, { });
|
|
||||||
|
|
||||||
|
|
||||||
if (distancesGrouped.fav) {
|
|
||||||
return distancesGrouped.fav.value;
|
|
||||||
} else {
|
|
||||||
return DEFAULT_HORIZONTAL_DISTANCE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Always try to place text annotations top right of source.
|
|
||||||
*/
|
|
||||||
export function getTextAnnotationPosition(source, element) {
|
|
||||||
|
|
||||||
var sourceTrbl = asTRBL(source);
|
|
||||||
|
|
||||||
var position = {
|
|
||||||
x: sourceTrbl.right + element.width / 2,
|
|
||||||
y: sourceTrbl.top - 50 - element.height / 2
|
|
||||||
};
|
|
||||||
|
|
||||||
var escapeDirection = {
|
|
||||||
y: {
|
|
||||||
margin: -30,
|
|
||||||
rowSize: 20
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return deconflictPosition(source, element, position, escapeDirection);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Always put element bottom right of source.
|
|
||||||
*/
|
|
||||||
export function getDataElementPosition(source, element) {
|
|
||||||
|
|
||||||
var sourceTrbl = asTRBL(source);
|
|
||||||
|
|
||||||
var position = {
|
|
||||||
x: sourceTrbl.right - 10 + element.width / 2,
|
|
||||||
y: sourceTrbl.bottom + 40 + element.width / 2
|
|
||||||
};
|
|
||||||
|
|
||||||
var escapeDirection = {
|
|
||||||
x: {
|
|
||||||
margin: 30,
|
|
||||||
rowSize: 30
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
return deconflictPosition(source, element, position, escapeDirection);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Always put element right of source per default.
|
|
||||||
*/
|
|
||||||
export function getDefaultPosition(source, element) {
|
|
||||||
|
|
||||||
var sourceTrbl = asTRBL(source);
|
|
||||||
|
|
||||||
var sourceMid = getMid(source);
|
|
||||||
|
|
||||||
// simply put element right next to source
|
|
||||||
return {
|
|
||||||
x: sourceTrbl.right + DEFAULT_HORIZONTAL_DISTANCE + element.width / 2,
|
|
||||||
y: sourceMid.y
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns all connected elements around the given source.
|
|
||||||
*
|
|
||||||
* This includes:
|
|
||||||
*
|
|
||||||
* - connected elements
|
|
||||||
* - host connected elements
|
|
||||||
* - attachers connected elements
|
|
||||||
*
|
|
||||||
* @param {djs.model.Shape} source
|
|
||||||
* @param {djs.model.Shape} element
|
|
||||||
*
|
|
||||||
* @return {Array<djs.model.Shape>}
|
|
||||||
*/
|
|
||||||
function getAutoPlaceClosure(source, element) {
|
|
||||||
|
|
||||||
var allConnected = getConnected(source);
|
|
||||||
|
|
||||||
if (source.host) {
|
|
||||||
allConnected = allConnected.concat(getConnected(source.host));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (source.attachers) {
|
|
||||||
allConnected = allConnected.concat(source.attachers.reduce(function(shapes, attacher) {
|
|
||||||
return shapes.concat(getConnected(attacher));
|
|
||||||
}, []));
|
|
||||||
}
|
|
||||||
|
|
||||||
return allConnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Return target at given position, if defined.
|
|
||||||
*
|
|
||||||
* This takes connected elements from host and attachers
|
|
||||||
* into account, too.
|
|
||||||
*/
|
|
||||||
export function getConnectedAtPosition(source, position, element) {
|
|
||||||
|
|
||||||
var bounds = {
|
|
||||||
x: position.x - (element.width / 2),
|
|
||||||
y: position.y - (element.height / 2),
|
|
||||||
width: element.width,
|
|
||||||
height: element.height
|
|
||||||
};
|
|
||||||
|
|
||||||
var closure = getAutoPlaceClosure(source, element);
|
|
||||||
|
|
||||||
return find(closure, function(target) {
|
|
||||||
|
|
||||||
if (target === element) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var orientation = getOrientation(target, bounds, PLACEMENT_DETECTION_PAD);
|
|
||||||
|
|
||||||
return orientation === 'intersect';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a new, position for the given element
|
|
||||||
* based on the given element that is not occupied
|
|
||||||
* by some element connected to source.
|
|
||||||
*
|
|
||||||
* Take into account the escapeDirection (where to move
|
|
||||||
* on positioning clashes) in the computation.
|
|
||||||
*
|
|
||||||
* @param {djs.model.Shape} source
|
|
||||||
* @param {djs.model.Shape} element
|
|
||||||
* @param {Point} position
|
|
||||||
* @param {Object} escapeDelta
|
|
||||||
*
|
|
||||||
* @return {Point}
|
|
||||||
*/
|
|
||||||
export function deconflictPosition(source, element, position, escapeDelta) {
|
|
||||||
|
|
||||||
function nextPosition(existingElement) {
|
|
||||||
|
|
||||||
var newPosition = {
|
|
||||||
x: position.x,
|
|
||||||
y: position.y
|
|
||||||
};
|
|
||||||
|
|
||||||
[ 'x', 'y' ].forEach(function(axis) {
|
|
||||||
|
|
||||||
var axisDelta = escapeDelta[axis];
|
|
||||||
|
|
||||||
if (!axisDelta) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var dimension = axis === 'x' ? 'width' : 'height';
|
|
||||||
|
|
||||||
var margin = axisDelta.margin,
|
|
||||||
rowSize = axisDelta.rowSize;
|
|
||||||
|
|
||||||
if (margin < 0) {
|
|
||||||
newPosition[axis] = Math.min(
|
|
||||||
existingElement[axis] + margin - element[dimension] / 2,
|
|
||||||
position[axis] - rowSize + margin
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
newPosition[axis] = Math.max(
|
|
||||||
existingTarget[axis] + existingTarget[dimension] + margin + element[dimension] / 2,
|
|
||||||
position[axis] + rowSize + margin
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return newPosition;
|
|
||||||
}
|
|
||||||
|
|
||||||
var existingTarget;
|
|
||||||
|
|
||||||
// deconflict position until free slot is found
|
|
||||||
while ((existingTarget = getConnectedAtPosition(source, position, element))) {
|
|
||||||
position = nextPosition(existingTarget);
|
|
||||||
}
|
|
||||||
|
|
||||||
return position;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// helpers //////////////////////
|
|
||||||
|
|
||||||
function noneFilter() {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getConnected(element, connectionFilter) {
|
|
||||||
return [].concat(
|
|
||||||
getTargets(element, connectionFilter),
|
|
||||||
getSources(element, connectionFilter)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSources(shape, connectionFilter) {
|
|
||||||
|
|
||||||
if (!connectionFilter) {
|
|
||||||
connectionFilter = noneFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shape.incoming.filter(connectionFilter).map(function(c) {
|
|
||||||
return c.source;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getTargets(shape, connectionFilter) {
|
|
||||||
|
|
||||||
if (!connectionFilter) {
|
|
||||||
connectionFilter = noneFilter;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shape.outgoing.filter(connectionFilter).map(function(c) {
|
|
||||||
return c.target;
|
|
||||||
});
|
|
||||||
}
|
|
18
lib/features/auto-place/BpmnAutoPlace.js
Normal file
18
lib/features/auto-place/BpmnAutoPlace.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import { getNewShapePosition } from './BpmnAutoPlaceUtil';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BPMN auto-place behavior.
|
||||||
|
*
|
||||||
|
* @param {EventBus} eventBus
|
||||||
|
*/
|
||||||
|
export default function AutoPlace(eventBus) {
|
||||||
|
eventBus.on('autoPlace', function(context) {
|
||||||
|
var shape = context.shape,
|
||||||
|
source = context.source;
|
||||||
|
|
||||||
|
return getNewShapePosition(source, shape);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
AutoPlace.$inject = [ 'eventBus' ];
|
134
lib/features/auto-place/BpmnAutoPlaceUtil.js
Normal file
134
lib/features/auto-place/BpmnAutoPlaceUtil.js
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import { is } from '../../util/ModelUtil';
|
||||||
|
import { isAny } from '../modeling/util/ModelingUtil';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getMid,
|
||||||
|
asTRBL,
|
||||||
|
getOrientation
|
||||||
|
} from 'diagram-js/lib/layout/LayoutUtil';
|
||||||
|
|
||||||
|
import {
|
||||||
|
deconflictPosition,
|
||||||
|
getConnectedDistance
|
||||||
|
} from 'diagram-js/lib/features/auto-place/AutoPlaceUtil';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the new position for the target element to
|
||||||
|
* connect to source.
|
||||||
|
*
|
||||||
|
* @param {djs.model.Shape} source
|
||||||
|
* @param {djs.model.Shape} element
|
||||||
|
*
|
||||||
|
* @return {Point}
|
||||||
|
*/
|
||||||
|
export function getNewShapePosition(source, element) {
|
||||||
|
|
||||||
|
if (is(element, 'bpmn:TextAnnotation')) {
|
||||||
|
return getTextAnnotationPosition(source, element);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isAny(element, [ 'bpmn:DataObjectReference', 'bpmn:DataStoreReference' ])) {
|
||||||
|
return getDataElementPosition(source, element);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is(element, 'bpmn:FlowNode')) {
|
||||||
|
return getFlowNodePosition(source, element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Always try to place element right of source;
|
||||||
|
* compute actual distance from previous nodes in flow.
|
||||||
|
*/
|
||||||
|
export function getFlowNodePosition(source, element) {
|
||||||
|
|
||||||
|
var sourceTrbl = asTRBL(source);
|
||||||
|
var sourceMid = getMid(source);
|
||||||
|
|
||||||
|
var horizontalDistance = getConnectedDistance(source, 'x', function(connection) {
|
||||||
|
return is(connection, 'bpmn:SequenceFlow');
|
||||||
|
});
|
||||||
|
|
||||||
|
var orientation = 'left',
|
||||||
|
rowSize = 80,
|
||||||
|
margin = 30;
|
||||||
|
|
||||||
|
if (is(source, 'bpmn:BoundaryEvent')) {
|
||||||
|
orientation = getOrientation(source, source.host, -25);
|
||||||
|
|
||||||
|
if (orientation.indexOf('top') !== -1) {
|
||||||
|
margin *= -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getVerticalDistance(orient) {
|
||||||
|
if (orient.indexOf('top') != -1) {
|
||||||
|
return -1 * rowSize;
|
||||||
|
} else if (orient.indexOf('bottom') != -1) {
|
||||||
|
return rowSize;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var position = {
|
||||||
|
x: sourceTrbl.right + horizontalDistance + element.width / 2,
|
||||||
|
y: sourceMid.y + getVerticalDistance(orientation)
|
||||||
|
};
|
||||||
|
|
||||||
|
var escapeDirection = {
|
||||||
|
y: {
|
||||||
|
margin: margin,
|
||||||
|
rowSize: rowSize
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return deconflictPosition(source, element, position, escapeDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Always try to place text annotations top right of source.
|
||||||
|
*/
|
||||||
|
export function getTextAnnotationPosition(source, element) {
|
||||||
|
|
||||||
|
var sourceTrbl = asTRBL(source);
|
||||||
|
|
||||||
|
var position = {
|
||||||
|
x: sourceTrbl.right + element.width / 2,
|
||||||
|
y: sourceTrbl.top - 50 - element.height / 2
|
||||||
|
};
|
||||||
|
|
||||||
|
var escapeDirection = {
|
||||||
|
y: {
|
||||||
|
margin: -30,
|
||||||
|
rowSize: 20
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return deconflictPosition(source, element, position, escapeDirection);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Always put element bottom right of source.
|
||||||
|
*/
|
||||||
|
export function getDataElementPosition(source, element) {
|
||||||
|
|
||||||
|
var sourceTrbl = asTRBL(source);
|
||||||
|
|
||||||
|
var position = {
|
||||||
|
x: sourceTrbl.right - 10 + element.width / 2,
|
||||||
|
y: sourceTrbl.bottom + 40 + element.width / 2
|
||||||
|
};
|
||||||
|
|
||||||
|
var escapeDirection = {
|
||||||
|
x: {
|
||||||
|
margin: 30,
|
||||||
|
rowSize: 30
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return deconflictPosition(source, element, position, escapeDirection);
|
||||||
|
}
|
@ -1,8 +1,9 @@
|
|||||||
import AutoPlace from './AutoPlace';
|
import AutoPlaceModule from 'diagram-js/lib/features/auto-place';
|
||||||
import AutoPlaceSelectionBehavior from './AutoPlaceSelectionBehavior';
|
|
||||||
|
import BpmnAutoPlace from './BpmnAutoPlace';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
__init__: [ 'autoPlaceSelectionBehavior' ],
|
__depends__: [ AutoPlaceModule ],
|
||||||
autoPlace: [ 'type', AutoPlace ],
|
__init__: [ 'bpmnAutoPlace' ],
|
||||||
autoPlaceSelectionBehavior: [ 'type', AutoPlaceSelectionBehavior ]
|
bpmnAutoPlace: [ 'type', BpmnAutoPlace ]
|
||||||
};
|
};
|
@ -1,11 +1,13 @@
|
|||||||
import { getNewShapePosition } from '../../auto-place/AutoPlaceUtil';
|
import { getNewShapePosition } from '../../auto-place/BpmnAutoPlaceUtil';
|
||||||
|
|
||||||
import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
|
import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
|
||||||
import { is } from '../../../util/ModelUtil';
|
import { is } from '../../../util/ModelUtil';
|
||||||
|
|
||||||
|
var HIGH_PRIORITY = 2000;
|
||||||
|
|
||||||
|
|
||||||
export default function AutoPlaceBehavior(eventBus, gridSnapping) {
|
export default function AutoPlaceBehavior(eventBus, gridSnapping) {
|
||||||
eventBus.on('autoPlace', function(context) {
|
eventBus.on('autoPlace', HIGH_PRIORITY, function(context) {
|
||||||
var source = context.source,
|
var source = context.source,
|
||||||
sourceMid = getMid(source),
|
sourceMid = getMid(source),
|
||||||
shape = context.shape;
|
shape = context.shape;
|
||||||
|
@ -11,14 +11,12 @@ import selectionModule from 'diagram-js/lib/features/selection';
|
|||||||
|
|
||||||
import { getBusinessObject } from '../../../../lib/util/ModelUtil';
|
import { getBusinessObject } from '../../../../lib/util/ModelUtil';
|
||||||
|
|
||||||
import { getMid } from 'diagram-js/lib/layout/LayoutUtil';
|
|
||||||
|
|
||||||
|
|
||||||
describe('features/auto-place', function() {
|
describe('features/auto-place', function() {
|
||||||
|
|
||||||
describe('element placement', function() {
|
describe('element placement', function() {
|
||||||
|
|
||||||
var diagramXML = require('./AutoPlace.bpmn');
|
var diagramXML = require('./BpmnAutoPlace.bpmn');
|
||||||
|
|
||||||
before(bootstrapModeler(diagramXML, {
|
before(bootstrapModeler(diagramXML, {
|
||||||
modules: [
|
modules: [
|
||||||
@ -116,7 +114,7 @@ describe('features/auto-place', function() {
|
|||||||
|
|
||||||
describe('integration', function() {
|
describe('integration', function() {
|
||||||
|
|
||||||
var diagramXML = require('./AutoPlace.bpmn');
|
var diagramXML = require('./BpmnAutoPlace.bpmn');
|
||||||
|
|
||||||
before(bootstrapModeler(diagramXML, {
|
before(bootstrapModeler(diagramXML, {
|
||||||
modules: [
|
modules: [
|
||||||
@ -174,7 +172,7 @@ describe('features/auto-place', function() {
|
|||||||
|
|
||||||
describe('multi connection handling', function() {
|
describe('multi connection handling', function() {
|
||||||
|
|
||||||
var diagramXML = require('./AutoPlace.multi-connection.bpmn');
|
var diagramXML = require('./BpmnAutoPlace.multi-connection.bpmn');
|
||||||
|
|
||||||
before(bootstrapModeler(diagramXML, {
|
before(bootstrapModeler(diagramXML, {
|
||||||
modules: [
|
modules: [
|
||||||
@ -209,7 +207,7 @@ describe('features/auto-place', function() {
|
|||||||
|
|
||||||
describe('boundary event connection handling', function() {
|
describe('boundary event connection handling', function() {
|
||||||
|
|
||||||
var diagramXML = require('./AutoPlace.boundary-events.bpmn');
|
var diagramXML = require('./BpmnAutoPlace.boundary-events.bpmn');
|
||||||
|
|
||||||
before(bootstrapModeler(diagramXML, {
|
before(bootstrapModeler(diagramXML, {
|
||||||
modules: [
|
modules: [
|
||||||
@ -241,12 +239,14 @@ describe('features/auto-place', function() {
|
|||||||
expectedBounds: { x: 242, y: -27, width: 100, height: 80 }
|
expectedBounds: { x: 242, y: -27, width: 100, height: 80 }
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
it('should place top right of BOUNDARY_TOP_RIGHT without infinite loop', autoPlace({
|
it('should place top right of BOUNDARY_TOP_RIGHT without infinite loop', autoPlace({
|
||||||
element: 'bpmn:Task',
|
element: 'bpmn:Task',
|
||||||
behind: 'BOUNDARY_TOP_RIGHT',
|
behind: 'BOUNDARY_TOP_RIGHT',
|
||||||
expectedBounds: { x: 473, y: -27, width: 100, height: 80 }
|
expectedBounds: { x: 473, y: -27, width: 100, height: 80 }
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|
||||||
it('should place top right of BOUNDARY_SUBPROCESS_TOP', autoPlace({
|
it('should place top right of BOUNDARY_SUBPROCESS_TOP', autoPlace({
|
||||||
element: 'bpmn:Task',
|
element: 'bpmn:Task',
|
||||||
behind: 'BOUNDARY_SUBPROCESS_TOP',
|
behind: 'BOUNDARY_SUBPROCESS_TOP',
|
||||||
@ -255,114 +255,10 @@ describe('features/auto-place', function() {
|
|||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
describe('eventbus integration', function() {
|
|
||||||
|
|
||||||
var diagramXML = require('./AutoPlace.bpmn');
|
|
||||||
|
|
||||||
beforeEach(bootstrapModeler(diagramXML, {
|
|
||||||
modules: [
|
|
||||||
autoPlaceModule,
|
|
||||||
coreModule,
|
|
||||||
labelEditingModule,
|
|
||||||
modelingModule,
|
|
||||||
selectionModule
|
|
||||||
]
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
|
||||||
it('<autoPlace.start>', inject(
|
|
||||||
function(autoPlace, elementFactory, elementRegistry, eventBus) {
|
|
||||||
|
|
||||||
// given
|
|
||||||
var element = elementFactory.createShape({ type: 'bpmn:Task' });
|
|
||||||
|
|
||||||
var source = elementRegistry.get('TASK_2');
|
|
||||||
|
|
||||||
var listener = sinon.spy(function(event) {
|
|
||||||
|
|
||||||
// then
|
|
||||||
expect(event.shape).to.equal(element);
|
|
||||||
expect(event.source).to.equal(source);
|
|
||||||
});
|
|
||||||
|
|
||||||
eventBus.on('autoPlace.start', listener);
|
|
||||||
|
|
||||||
// when
|
|
||||||
autoPlace.append(source, element);
|
|
||||||
|
|
||||||
expect(listener).to.have.been.called;
|
|
||||||
}
|
|
||||||
));
|
|
||||||
|
|
||||||
|
|
||||||
it('<autoPlace>', inject(
|
|
||||||
function(autoPlace, elementFactory, elementRegistry, eventBus) {
|
|
||||||
|
|
||||||
// given
|
|
||||||
var element = elementFactory.createShape({ type: 'bpmn:Task' });
|
|
||||||
|
|
||||||
var source = elementRegistry.get('TASK_2');
|
|
||||||
|
|
||||||
var listener = sinon.spy(function(event) {
|
|
||||||
|
|
||||||
// then
|
|
||||||
expect(event.shape).to.equal(element);
|
|
||||||
expect(event.source).to.equal(source);
|
|
||||||
|
|
||||||
return {
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
eventBus.on('autoPlace', listener);
|
|
||||||
|
|
||||||
// when
|
|
||||||
autoPlace.append(source, element);
|
|
||||||
|
|
||||||
expect(listener).to.have.been.called;
|
|
||||||
|
|
||||||
expect(getMid(element)).to.eql({
|
|
||||||
x: 0,
|
|
||||||
y: 0
|
|
||||||
});
|
|
||||||
}
|
|
||||||
));
|
|
||||||
|
|
||||||
|
|
||||||
it('<autoPlace.end>', inject(
|
|
||||||
function(autoPlace, elementFactory, elementRegistry, eventBus) {
|
|
||||||
|
|
||||||
// given
|
|
||||||
var element = elementFactory.createShape({ type: 'bpmn:Task' });
|
|
||||||
|
|
||||||
var source = elementRegistry.get('TASK_2');
|
|
||||||
|
|
||||||
var listener = sinon.spy(function(event) {
|
|
||||||
|
|
||||||
// then
|
|
||||||
expect(event.shape).to.equal(element);
|
|
||||||
expect(event.source).to.equal(source);
|
|
||||||
});
|
|
||||||
|
|
||||||
eventBus.on('autoPlace.end', listener);
|
|
||||||
|
|
||||||
// when
|
|
||||||
autoPlace.append(source, element);
|
|
||||||
|
|
||||||
expect(listener).to.have.been.called;
|
|
||||||
}
|
|
||||||
));
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// helpers //////////
|
||||||
|
|
||||||
// helpers //////////////////////
|
|
||||||
|
|
||||||
function autoPlace(cfg) {
|
function autoPlace(cfg) {
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user