123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318 |
- import {
- assign,
- isArray,
- isNumber,
- isObject,
- isUndefined,
- groupBy,
- forEach
- } from 'min-dash';
- /**
- * Adds an element to a collection and returns true if the
- * element was added.
- *
- * @param {Array<Object>} elements
- * @param {Object} e
- * @param {Boolean} unique
- */
- export function add(elements, e, unique) {
- var canAdd = !unique || elements.indexOf(e) === -1;
- if (canAdd) {
- elements.push(e);
- }
- return canAdd;
- }
- /**
- * Iterate over each element in a collection, calling the iterator function `fn`
- * with (element, index, recursionDepth).
- *
- * Recurse into all elements that are returned by `fn`.
- *
- * @param {Object|Array<Object>} elements
- * @param {Function} fn iterator function called with (element, index, recursionDepth)
- * @param {Number} [depth] maximum recursion depth
- */
- export function eachElement(elements, fn, depth) {
- depth = depth || 0;
- if (!isArray(elements)) {
- elements = [ elements ];
- }
- forEach(elements, function(s, i) {
- var filter = fn(s, i, depth);
- if (isArray(filter) && filter.length) {
- eachElement(filter, fn, depth + 1);
- }
- });
- }
- /**
- * Collects self + child elements up to a given depth from a list of elements.
- *
- * @param {djs.model.Base|Array<djs.model.Base>} elements the elements to select the children from
- * @param {Boolean} unique whether to return a unique result set (no duplicates)
- * @param {Number} maxDepth the depth to search through or -1 for infinite
- *
- * @return {Array<djs.model.Base>} found elements
- */
- export function selfAndChildren(elements, unique, maxDepth) {
- var result = [],
- processedChildren = [];
- eachElement(elements, function(element, i, depth) {
- add(result, element, unique);
- var children = element.children;
- // max traversal depth not reached yet
- if (maxDepth === -1 || depth < maxDepth) {
- // children exist && children not yet processed
- if (children && add(processedChildren, children, unique)) {
- return children;
- }
- }
- });
- return result;
- }
- /**
- * Return self + direct children for a number of elements
- *
- * @param {Array<djs.model.Base>} elements to query
- * @param {Boolean} allowDuplicates to allow duplicates in the result set
- *
- * @return {Array<djs.model.Base>} the collected elements
- */
- export function selfAndDirectChildren(elements, allowDuplicates) {
- return selfAndChildren(elements, !allowDuplicates, 1);
- }
- /**
- * Return self + ALL children for a number of elements
- *
- * @param {Array<djs.model.Base>} elements to query
- * @param {Boolean} allowDuplicates to allow duplicates in the result set
- *
- * @return {Array<djs.model.Base>} the collected elements
- */
- export function selfAndAllChildren(elements, allowDuplicates) {
- return selfAndChildren(elements, !allowDuplicates, -1);
- }
- /**
- * Gets the the closure for all selected elements,
- * their enclosed children and connections.
- *
- * @param {Array<djs.model.Base>} elements
- * @param {Boolean} [isTopLevel=true]
- * @param {Object} [existingClosure]
- *
- * @return {Object} newClosure
- */
- export function getClosure(elements, isTopLevel, closure) {
- if (isUndefined(isTopLevel)) {
- isTopLevel = true;
- }
- if (isObject(isTopLevel)) {
- closure = isTopLevel;
- isTopLevel = true;
- }
- closure = closure || {};
- var allShapes = copyObject(closure.allShapes),
- allConnections = copyObject(closure.allConnections),
- enclosedElements = copyObject(closure.enclosedElements),
- enclosedConnections = copyObject(closure.enclosedConnections);
- var topLevel = copyObject(
- closure.topLevel,
- isTopLevel && groupBy(elements, function(e) { return e.id; })
- );
- function handleConnection(c) {
- if (topLevel[c.source.id] && topLevel[c.target.id]) {
- topLevel[c.id] = [ c ];
- }
- // not enclosed as a child, but maybe logically
- // (connecting two moved elements?)
- if (allShapes[c.source.id] && allShapes[c.target.id]) {
- enclosedConnections[c.id] = enclosedElements[c.id] = c;
- }
- allConnections[c.id] = c;
- }
- function handleElement(element) {
- enclosedElements[element.id] = element;
- if (element.waypoints) {
- // remember connection
- enclosedConnections[element.id] = allConnections[element.id] = element;
- } else {
- // remember shape
- allShapes[element.id] = element;
- // remember all connections
- forEach(element.incoming, handleConnection);
- forEach(element.outgoing, handleConnection);
- // recurse into children
- return element.children;
- }
- }
- eachElement(elements, handleElement);
- return {
- allShapes: allShapes,
- allConnections: allConnections,
- topLevel: topLevel,
- enclosedConnections: enclosedConnections,
- enclosedElements: enclosedElements
- };
- }
- /**
- * Returns the surrounding bbox for all elements in
- * the array or the element primitive.
- *
- * @param {Array<djs.model.Shape>|djs.model.Shape} elements
- * @param {Boolean} stopRecursion
- */
- export function getBBox(elements, stopRecursion) {
- stopRecursion = !!stopRecursion;
- if (!isArray(elements)) {
- elements = [elements];
- }
- var minX,
- minY,
- maxX,
- maxY;
- forEach(elements, function(element) {
- // If element is a connection the bbox must be computed first
- var bbox = element;
- if (element.waypoints && !stopRecursion) {
- bbox = getBBox(element.waypoints, true);
- }
- var x = bbox.x,
- y = bbox.y,
- height = bbox.height || 0,
- width = bbox.width || 0;
- if (x < minX || minX === undefined) {
- minX = x;
- }
- if (y < minY || minY === undefined) {
- minY = y;
- }
- if ((x + width) > maxX || maxX === undefined) {
- maxX = x + width;
- }
- if ((y + height) > maxY || maxY === undefined) {
- maxY = y + height;
- }
- });
- return {
- x: minX,
- y: minY,
- height: maxY - minY,
- width: maxX - minX
- };
- }
- /**
- * Returns all elements that are enclosed from the bounding box.
- *
- * * If bbox.(width|height) is not specified the method returns
- * all elements with element.x/y > bbox.x/y
- * * If only bbox.x or bbox.y is specified, method return all elements with
- * e.x > bbox.x or e.y > bbox.y
- *
- * @param {Array<djs.model.Shape>} elements List of Elements to search through
- * @param {djs.model.Shape} bbox the enclosing bbox.
- *
- * @return {Array<djs.model.Shape>} enclosed elements
- */
- export function getEnclosedElements(elements, bbox) {
- var filteredElements = {};
- forEach(elements, function(element) {
- var e = element;
- if (e.waypoints) {
- e = getBBox(e);
- }
- if (!isNumber(bbox.y) && (e.x > bbox.x)) {
- filteredElements[element.id] = element;
- }
- if (!isNumber(bbox.x) && (e.y > bbox.y)) {
- filteredElements[element.id] = element;
- }
- if (e.x > bbox.x && e.y > bbox.y) {
- if (isNumber(bbox.width) && isNumber(bbox.height) &&
- e.width + e.x < bbox.width + bbox.x &&
- e.height + e.y < bbox.height + bbox.y) {
- filteredElements[element.id] = element;
- } else if (!isNumber(bbox.width) || !isNumber(bbox.height)) {
- filteredElements[element.id] = element;
- }
- }
- });
- return filteredElements;
- }
- export function getType(element) {
- if ('waypoints' in element) {
- return 'connection';
- }
- if ('x' in element) {
- return 'shape';
- }
- return 'root';
- }
- // helpers ///////////////////////////////
- function copyObject(src1, src2) {
- return assign({}, src1 || {}, src2 || {});
- }
|