diagram-js
Version:
A toolbox for displaying and modifying diagrams on the web
254 lines (204 loc) • 6.21 kB
JavaScript
import {
filter,
isNumber
} from 'min-dash';
var max = Math.max,
min = Math.min;
var DEFAULT_CHILD_BOX_PADDING = 20;
import {
getBBox
} from '../../util/Elements';
import {
asTRBL,
asBounds
} from '../../layout/LayoutUtil';
/**
* @typedef {import('../../core/Types').ElementLike} Element
* @typedef {import('../../core/Types').ShapeLike} Shape
*
* @typedef {import('../../util/Types').Direction} Direction
* @typedef {import('../../util/Types').Point} Point
* @typedef {import('../../util/Types').Rect} Rect
* @typedef {import('../../util/Types').RectTRBL} RectTRBL
*/
/**
* Substract a TRBL from another
*
* @param {RectTRBL} trblA
* @param {RectTRBL} trblB
*
* @return {RectTRBL}
*/
export function substractTRBL(trblA, trblB) {
return {
top: trblA.top - trblB.top,
right: trblA.right - trblB.right,
bottom: trblA.bottom - trblB.bottom,
left: trblA.left - trblB.left
};
}
/**
* Resize the given bounds by the specified delta from a given anchor point.
*
* @param {Rect} bounds the bounding box that should be resized
* @param {Direction} direction in which the element is resized (nw, ne, se, sw)
* @param {Point} delta of the resize operation
*
* @return {Rect} resized bounding box
*/
export function resizeBounds(bounds, direction, delta) {
var dx = delta.x,
dy = delta.y;
var newBounds = {
x: bounds.x,
y: bounds.y,
width: bounds.width,
height: bounds.height
};
if (direction.indexOf('n') !== -1) {
newBounds.y = bounds.y + dy;
newBounds.height = bounds.height - dy;
} else if (direction.indexOf('s') !== -1) {
newBounds.height = bounds.height + dy;
}
if (direction.indexOf('e') !== -1) {
newBounds.width = bounds.width + dx;
} else if (direction.indexOf('w') !== -1) {
newBounds.x = bounds.x + dx;
newBounds.width = bounds.width - dx;
}
return newBounds;
}
/**
* Resize the given bounds by applying the passed
* { top, right, bottom, left } delta.
*
* @param {Rect} bounds
* @param {RectTRBL} resize
*
* @return {Rect}
*/
export function resizeTRBL(bounds, resize) {
return {
x: bounds.x + (resize.left || 0),
y: bounds.y + (resize.top || 0),
width: bounds.width - (resize.left || 0) + (resize.right || 0),
height: bounds.height - (resize.top || 0) + (resize.bottom || 0)
};
}
export function reattachPoint(bounds, newBounds, point) {
var sx = bounds.width / newBounds.width,
sy = bounds.height / newBounds.height;
return {
x: Math.round((newBounds.x + newBounds.width / 2)) - Math.floor(((bounds.x + bounds.width / 2) - point.x) / sx),
y: Math.round((newBounds.y + newBounds.height / 2)) - Math.floor(((bounds.y + bounds.height / 2) - point.y) / sy)
};
}
function applyConstraints(attr, trbl, resizeConstraints) {
var value = trbl[attr],
minValue = resizeConstraints.min && resizeConstraints.min[attr],
maxValue = resizeConstraints.max && resizeConstraints.max[attr];
if (isNumber(minValue)) {
value = (/top|left/.test(attr) ? min : max)(value, minValue);
}
if (isNumber(maxValue)) {
value = (/top|left/.test(attr) ? max : min)(value, maxValue);
}
return value;
}
export function ensureConstraints(currentBounds, resizeConstraints) {
if (!resizeConstraints) {
return currentBounds;
}
var currentTrbl = asTRBL(currentBounds);
return asBounds({
top: applyConstraints('top', currentTrbl, resizeConstraints),
right: applyConstraints('right', currentTrbl, resizeConstraints),
bottom: applyConstraints('bottom', currentTrbl, resizeConstraints),
left: applyConstraints('left', currentTrbl, resizeConstraints)
});
}
export function getMinResizeBounds(direction, currentBounds, minDimensions, childrenBounds) {
var currentBox = asTRBL(currentBounds);
var minBox = {
top: /n/.test(direction) ? currentBox.bottom - minDimensions.height : currentBox.top,
left: /w/.test(direction) ? currentBox.right - minDimensions.width : currentBox.left,
bottom: /s/.test(direction) ? currentBox.top + minDimensions.height : currentBox.bottom,
right: /e/.test(direction) ? currentBox.left + minDimensions.width : currentBox.right
};
var childrenBox = childrenBounds ? asTRBL(childrenBounds) : minBox;
var combinedBox = {
top: min(minBox.top, childrenBox.top),
left: min(minBox.left, childrenBox.left),
bottom: max(minBox.bottom, childrenBox.bottom),
right: max(minBox.right, childrenBox.right)
};
return asBounds(combinedBox);
}
function asPadding(mayBePadding, defaultValue) {
if (typeof mayBePadding !== 'undefined') {
return mayBePadding;
} else {
return DEFAULT_CHILD_BOX_PADDING;
}
}
export function addPadding(bbox, padding) {
var left, right, top, bottom;
if (typeof padding === 'object') {
left = asPadding(padding.left);
right = asPadding(padding.right);
top = asPadding(padding.top);
bottom = asPadding(padding.bottom);
} else {
left = right = top = bottom = asPadding(padding);
}
return {
x: bbox.x - left,
y: bbox.y - top,
width: bbox.width + left + right,
height: bbox.height + top + bottom
};
}
/**
* Is the given element part of the resize
* targets min boundary box?
*
* This is the default implementation which excludes
* connections and labels.
*
* @param {Element} element
*/
function isBBoxChild(element) {
// exclude connections
if (element.waypoints) {
return false;
}
// exclude labels
if (element.type === 'label') {
return false;
}
return true;
}
/**
* Return children bounding computed from a shapes children
* or a list of prefiltered children.
*
* @param {Shape|Shape[]} shapeOrChildren
* @param {RectTRBL|number} padding
*
* @return {Rect}
*/
export function computeChildrenBBox(shapeOrChildren, padding) {
var elements;
// compute based on shape
if (shapeOrChildren.length === undefined) {
// grab all the children that are part of the
// parents children box
elements = filter(shapeOrChildren.children, isBBoxChild);
} else {
elements = shapeOrChildren;
}
if (elements.length) {
return addPadding(getBBox(elements), padding);
}
}