| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291 |
- import {
- isObject,
- sortBy
- } from 'min-dash';
- import {
- pointDistance,
- pointsOnLine
- } from '../util/Geometry';
- import intersectPaths from 'path-intersection';
- export function roundBounds(bounds) {
- return {
- x: Math.round(bounds.x),
- y: Math.round(bounds.y),
- width: Math.round(bounds.width),
- height: Math.round(bounds.height)
- };
- }
- export function roundPoint(point) {
- return {
- x: Math.round(point.x),
- y: Math.round(point.y)
- };
- }
- /**
- * Convert the given bounds to a { top, left, bottom, right } descriptor.
- *
- * @param {Bounds|Point} bounds
- *
- * @return {Object}
- */
- export function asTRBL(bounds) {
- return {
- top: bounds.y,
- right: bounds.x + (bounds.width || 0),
- bottom: bounds.y + (bounds.height || 0),
- left: bounds.x
- };
- }
- /**
- * Convert a { top, left, bottom, right } to an objects bounds.
- *
- * @param {Object} trbl
- *
- * @return {Bounds}
- */
- export function asBounds(trbl) {
- return {
- x: trbl.left,
- y: trbl.top,
- width: trbl.right - trbl.left,
- height: trbl.bottom - trbl.top
- };
- }
- /**
- * Get the mid of the given bounds or point.
- *
- * @param {Bounds|Point} bounds
- *
- * @return {Point}
- */
- export function getBoundsMid(bounds) {
- return roundPoint({
- x: bounds.x + (bounds.width || 0) / 2,
- y: bounds.y + (bounds.height || 0) / 2
- });
- }
- /**
- * Get the mid of the given Connection.
- *
- * @param {djs.Base.Connection} connection
- *
- * @return {Point}
- */
- export function getConnectionMid(connection) {
- var waypoints = connection.waypoints;
- // calculate total length and length of each segment
- var parts = waypoints.reduce(function(parts, point, index) {
- var lastPoint = waypoints[index - 1];
- if (lastPoint) {
- var lastPart = parts[parts.length - 1];
- var startLength = lastPart && lastPart.endLength || 0;
- var length = distance(lastPoint, point);
- parts.push({
- start: lastPoint,
- end: point,
- startLength: startLength,
- endLength: startLength + length,
- length: length
- });
- }
- return parts;
- }, []);
- var totalLength = parts.reduce(function(length, part) {
- return length + part.length;
- }, 0);
- // find which segement contains middle point
- var midLength = totalLength / 2;
- var i = 0;
- var midSegment = parts[i];
- while (midSegment.endLength < midLength) {
- midSegment = parts[++i];
- }
- // calculate relative position on mid segment
- var segmentProgress = (midLength - midSegment.startLength) / midSegment.length;
- var midPoint = {
- x: midSegment.start.x + (midSegment.end.x - midSegment.start.x) * segmentProgress,
- y: midSegment.start.y + (midSegment.end.y - midSegment.start.y) * segmentProgress
- };
- return midPoint;
- }
- /**
- * Get the mid of the given Element.
- *
- * @param {djs.Base.Connection} connection
- *
- * @return {Point}
- */
- export function getMid(element) {
- if (isConnection(element)) {
- return getConnectionMid(element);
- }
- return getBoundsMid(element);
- }
- // orientation utils //////////////////////
- /**
- * Get orientation of the given rectangle with respect to
- * the reference rectangle.
- *
- * A padding (positive or negative) may be passed to influence
- * horizontal / vertical orientation and intersection.
- *
- * @param {Bounds} rect
- * @param {Bounds} reference
- * @param {Point|number} padding
- *
- * @return {string} the orientation; one of top, top-left, left, ..., bottom, right or intersect.
- */
- export function getOrientation(rect, reference, padding) {
- padding = padding || 0;
- // make sure we can use an object, too
- // for individual { x, y } padding
- if (!isObject(padding)) {
- padding = { x: padding, y: padding };
- }
- var rectOrientation = asTRBL(rect),
- referenceOrientation = asTRBL(reference);
- var top = rectOrientation.bottom + padding.y <= referenceOrientation.top,
- right = rectOrientation.left - padding.x >= referenceOrientation.right,
- bottom = rectOrientation.top - padding.y >= referenceOrientation.bottom,
- left = rectOrientation.right + padding.x <= referenceOrientation.left;
- var vertical = top ? 'top' : (bottom ? 'bottom' : null),
- horizontal = left ? 'left' : (right ? 'right' : null);
- if (horizontal && vertical) {
- return vertical + '-' + horizontal;
- } else {
- return horizontal || vertical || 'intersect';
- }
- }
- // intersection utils //////////////////////
- /**
- * Get intersection between an element and a line path.
- *
- * @param {PathDef} elementPath
- * @param {PathDef} linePath
- * @param {boolean} cropStart crop from start or end
- *
- * @return {Point}
- */
- export function getElementLineIntersection(elementPath, linePath, cropStart) {
- var intersections = getIntersections(elementPath, linePath);
- // recognize intersections
- // only one -> choose
- // two close together -> choose first
- // two or more distinct -> pull out appropriate one
- // none -> ok (fallback to point itself)
- if (intersections.length === 1) {
- return roundPoint(intersections[0]);
- } else if (intersections.length === 2 && pointDistance(intersections[0], intersections[1]) < 1) {
- return roundPoint(intersections[0]);
- } else if (intersections.length > 1) {
- // sort by intersections based on connection segment +
- // distance from start
- intersections = sortBy(intersections, function(i) {
- var distance = Math.floor(i.t2 * 100) || 1;
- distance = 100 - distance;
- distance = (distance < 10 ? '0' : '') + distance;
- // create a sort string that makes sure we sort
- // line segment ASC + line segment position DESC (for cropStart)
- // line segment ASC + line segment position ASC (for cropEnd)
- return i.segment2 + '#' + distance;
- });
- return roundPoint(intersections[cropStart ? 0 : intersections.length - 1]);
- }
- return null;
- }
- export function getIntersections(a, b) {
- return intersectPaths(a, b);
- }
- export function filterRedundantWaypoints(waypoints) {
- // alter copy of waypoints, not original
- waypoints = waypoints.slice();
- var idx = 0,
- point,
- previousPoint,
- nextPoint;
- while (waypoints[idx]) {
- point = waypoints[idx];
- previousPoint = waypoints[idx - 1];
- nextPoint = waypoints[idx + 1];
- if (pointDistance(point, nextPoint) === 0 ||
- pointsOnLine(previousPoint, nextPoint, point)) {
- // remove point, if overlapping with {nextPoint}
- // or on line with {previousPoint} -> {point} -> {nextPoint}
- waypoints.splice(idx, 1);
- } else {
- idx++;
- }
- }
- return waypoints;
- }
- // helpers //////////////////////
- function distance(a, b) {
- return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
- }
- function isConnection(element) {
- return !!element.waypoints;
- }
|