| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217 |
- // @ts-nocheck
- import { asTRBL, getOrientation, getMid } from 'diagram-js/lib/layout/LayoutUtil';
- import { find, reduce } from 'min-dash';
- import { Shape } from 'diagram-js/lib/model';
- import { Point } from 'diagram-js/lib/util/Types';
- import { DEFAULT_DISTANCE } from '../config/constants';
- // padding to detect element placement
- const PLACEMENT_DETECTION_PAD = 10;
- const DEFAULT_MAX_DISTANCE = 350;
- /**
- * Get free position starting from given position.
- *
- * @param {Shape} source
- * @param {Shape} element
- * @param {Point} position
- * @param {Function} getNextPosition
- * @return {Point}
- */
- export function findFreePosition(source: Shape, element: Shape, position: Point, getNextPosition: Function): Point {
- let connectedAtPosition;
- while ((connectedAtPosition = getConnectedAtPosition(source, position, element))) {
- position = getNextPosition(element, position, connectedAtPosition);
- }
- return position;
- }
- /**
- * Returns function that returns next position.
- *
- * @param {Object} nextPositionDirection
- * @param {Object} [nextPositionDirection.x]
- * @param {Object} [nextPositionDirection.y]
- * @returns {Function}
- */
- export function generateGetNextPosition(nextPositionDirection: { x?: any; y?: any }): Function {
- return function (element: Shape, previousPosition: Point, connectedAtPosition: Point) {
- const nextPosition: any = {
- x: previousPosition.x,
- y: previousPosition.y,
- };
- ['x', 'y'].forEach(axis => {
- const nextPositionDirectionForAxis = nextPositionDirection[axis];
- if (!nextPositionDirectionForAxis) return;
- const dimension = axis === 'x' ? 'width' : 'height';
- const margin = nextPositionDirectionForAxis.margin;
- const minDistance = nextPositionDirectionForAxis.minDistance;
- if (margin < 0) nextPosition[axis] = Math.min(connectedAtPosition[axis] + margin - element[dimension] / 2, previousPosition[axis] - minDistance + margin);
- else {
- nextPosition[axis] = Math.max(
- connectedAtPosition[axis] + connectedAtPosition[dimension] + margin + element[dimension] / 2,
- previousPosition[axis] + minDistance + margin,
- );
- }
- });
- return nextPosition;
- };
- }
- /**
- * Return target at given position, if defined.
- *
- * This takes connected elements from host and attachers
- * into account, too.
- */
- export function getConnectedAtPosition(source: Shape, position: Point, element: Shape): Shape | undefined {
- const bounds = {
- x: position.x - element.width / 2,
- y: position.y - element.height / 2,
- width: element.width,
- height: element.height,
- };
- const closure = getAutoPlaceClosure(source, element);
- return find(closure, (target: Shape) => {
- if (target === element) return false;
- const orientation = getOrientation(target, bounds, PLACEMENT_DETECTION_PAD);
- return orientation === 'intersect';
- });
- }
- /**
- * Compute optimal distance between source and target based on existing connections to and from source.
- * Assumes left-to-right and top-to-down modeling.
- *
- * @param {Shape} source
- * @param {Object} [hints]
- * @param {number} [hints.defaultDistance]
- * @param {string} [hints.direction]
- * @param {Function} [hints.filter]
- * @param {Function} [hints.getWeight]
- * @param {number} [hints.maxDistance]
- * @param {string} [hints.reference]
- * @return {number}
- */
- export function getConnectedDistance(
- source: Shape,
- hints?: {
- defaultDistance?: number;
- direction?: string;
- filter?: Function;
- getWeight?: Function;
- maxDistance?: number;
- reference?: string;
- },
- ): number {
- if (!hints) hints = {};
- function getDefaultWeight(connection: any): number {
- return connection.source === source ? 1 : -1;
- }
- const defaultDistance = hints.defaultDistance || DEFAULT_DISTANCE;
- const direction = hints.direction || 'e';
- let filter = hints.filter;
- const getWeight = hints.getWeight || getDefaultWeight;
- const maxDistance = hints.maxDistance || DEFAULT_MAX_DISTANCE;
- const reference = hints.reference || 'start';
- if (!filter) filter = noneFilter;
- function getDistance(a: any, b: any): number {
- if (direction === 'n') {
- if (reference === 'start') {
- return asTRBL(a).top - asTRBL(b).bottom;
- } else if (reference === 'center') {
- return asTRBL(a).top - getMid(b).y;
- } else {
- return asTRBL(a).top - asTRBL(b).top;
- }
- } else if (direction === 'w') {
- if (reference === 'start') return asTRBL(a).left - asTRBL(b).right;
- else if (reference === 'center') return asTRBL(a).left - getMid(b).x;
- else return asTRBL(a).left - asTRBL(b).left;
- } else if (direction === 's') {
- if (reference === 'start') return asTRBL(b).top - asTRBL(a).bottom;
- else if (reference === 'center') return getMid(b).y - asTRBL(a).bottom;
- else return asTRBL(b).bottom - asTRBL(a).bottom;
- } else {
- if (reference === 'start') return asTRBL(b).left - asTRBL(a).right;
- else if (reference === 'center') return getMid(b).x - asTRBL(a).right;
- else return asTRBL(b).right - asTRBL(a).right;
- }
- }
- const sourcesDistances = source.incoming.filter(filter).map((connection: any) => {
- const weight = getWeight(connection);
- const distance = weight < 5 ? getDistance(connection.source, source) : getDistance(source, connection.source);
- return {
- id: connection.source.id,
- distance: distance,
- weight: weight,
- };
- });
- const targetsDistances = source.outgoing.filter(filter).map((connection: any) => {
- const weight = getWeight(connection);
- const distance = weight > 5 ? getDistance(source, connection.target) : getDistance(connection.target, source);
- return {
- id: connection.target.id,
- distance: distance,
- weight: weight,
- };
- });
- const distances = sourcesDistances.concat(targetsDistances).reduce((accumulator, currentValue) => {
- accumulator[currentValue.id + '__weight_' + currentValue.weight] = currentValue;
- return accumulator;
- }, {});
- const distancesGrouped = reduce(
- distances,
- (accumulator, currentValue) => {
- const distance = currentValue.distance;
- const weight = currentValue.weight;
- if (distance < 0 || distance > maxDistance) return accumulator;
- if (!accumulator[String(distance)]) accumulator[String(distance)] = 0;
- accumulator[String(distance)] += 1 * weight;
- if (!accumulator.distance || accumulator[accumulator.distance] < accumulator[String(distance)]) accumulator.distance = distance;
- return accumulator;
- },
- {},
- );
- return distancesGrouped.distance || defaultDistance;
- }
- /**
- * Returns all connected elements around the given source.
- * This includes:
- * - connected elements
- * - host connected elements
- * - attachers connected elements
- * @param {Shape} source
- * @return {Array<Shape>}
- */
- function getAutoPlaceClosure(source: Shape, element: Shape): Array<Shape> {
- let allConnected = getConnected(source);
- if (source.host) allConnected = allConnected.concat(getConnected(source.host));
- if (source.attachers)
- allConnected = allConnected.concat(
- source.attachers.reduce((shapes: Array<Shape>, attacher: Shape) => {
- return shapes.concat(getConnected(attacher));
- }, []),
- );
- return allConnected;
- }
- function getConnected(element: Shape): Array<Shape> {
- return getTargets(element).concat(getSources(element));
- }
- function getSources(shape: Shape): Array<Shape> {
- return shape.incoming.map((connection: any) => {
- return connection.source;
- });
- }
- function getTargets(shape: Shape): Array<Shape> {
- return shape.outgoing.map((connection: any) => {
- return connection.target;
- });
- }
- function noneFilter(): boolean {
- return true;
- }
|