UNPKG

diagram-js

Version:

A toolbox for displaying and modifying diagrams on the web

201 lines (163 loc) 4.69 kB
import { filter, forEach, isArray, sortBy } from 'min-dash'; /** * @typedef {import('../../model/Types').Element} Element * * @typedef {import('../modeling/Modeling').default} Modeling * @typedef {import('../rules/Rules').default} Rules * * @typedef {import('../../util/Types').Axis} Axis * @typedef {import('../../util/Types').Dimension} Dimension * * @typedef { 'top' | 'right' | 'bottom' | 'left' | 'center' | 'middle' } Alignment */ function last(arr) { return arr && arr[arr.length - 1]; } function sortTopOrMiddle(element) { return element.y; } function sortLeftOrCenter(element) { return element.x; } /** * Sorting functions for different alignments. * * @type {Record<string, Function>} */ var ALIGNMENT_SORTING = { left: sortLeftOrCenter, center: sortLeftOrCenter, right: function(element) { return element.x + element.width; }, top: sortTopOrMiddle, middle: sortTopOrMiddle, bottom: function(element) { return element.y + element.height; } }; /** * @param {Modeling} modeling * @param {Rules} rules */ export default function AlignElements(modeling, rules) { this._modeling = modeling; this._rules = rules; } AlignElements.$inject = [ 'modeling', 'rules' ]; /** * Get relevant axis and dimension for given alignment. * * @param {Alignment} type * * @return { { * axis: Axis; * dimension: Dimension; * } } */ AlignElements.prototype._getOrientationDetails = function(type) { var vertical = [ 'top', 'bottom', 'middle' ], axis = 'x', dimension = 'width'; if (vertical.indexOf(type) !== -1) { axis = 'y'; dimension = 'height'; } return { axis: axis, dimension: dimension }; }; AlignElements.prototype._isType = function(type, types) { return types.indexOf(type) !== -1; }; /** * Get point on relevant axis for given alignment. * * @param {Alignment} type * @param {Element[]} sortedElements * * @return {Partial<Record<Alignment, number>>} */ AlignElements.prototype._alignmentPosition = function(type, sortedElements) { var orientation = this._getOrientationDetails(type), axis = orientation.axis, dimension = orientation.dimension, alignment = {}, centers = {}, hasSharedCenters = false, centeredElements, firstElement, lastElement; function getMiddleOrTop(first, last) { return Math.round((first[axis] + last[axis] + last[dimension]) / 2); } if (this._isType(type, [ 'left', 'top' ])) { alignment[type] = sortedElements[0][axis]; } else if (this._isType(type, [ 'right', 'bottom' ])) { lastElement = last(sortedElements); alignment[type] = lastElement[axis] + lastElement[dimension]; } else if (this._isType(type, [ 'center', 'middle' ])) { // check if there is a center shared by more than one shape // if not, just take the middle of the range forEach(sortedElements, function(element) { var center = element[axis] + Math.round(element[dimension] / 2); if (centers[center]) { centers[center].elements.push(element); } else { centers[center] = { elements: [ element ], center: center }; } }); centeredElements = sortBy(centers, function(center) { if (center.elements.length > 1) { hasSharedCenters = true; } return center.elements.length; }); if (hasSharedCenters) { alignment[type] = last(centeredElements).center; return alignment; } firstElement = sortedElements[0]; sortedElements = sortBy(sortedElements, function(element) { return element[axis] + element[dimension]; }); lastElement = last(sortedElements); alignment[type] = getMiddleOrTop(firstElement, lastElement); } return alignment; }; /** * Align elements on relevant axis for given alignment. * * @param {Element[]} elements * @param {Alignment} type */ AlignElements.prototype.trigger = function(elements, type) { var modeling = this._modeling, allowed; // filter out elements which cannot be aligned var filteredElements = filter(elements, function(element) { return !(element.waypoints || element.host || element.labelTarget); }); // filter out elements via rules allowed = this._rules.allowed('elements.align', { elements: filteredElements }); if (isArray(allowed)) { filteredElements = allowed; } if (filteredElements.length < 2 || !allowed) { return; } var sortFn = ALIGNMENT_SORTING[type]; var sortedElements = sortBy(filteredElements, sortFn); var alignment = this._alignmentPosition(type, sortedElements); modeling.alignElements(sortedElements, alignment); };