"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.simplifyPolyline = exports.removeRedundantPoint = exports.removeFrom = exports.reconstructPath = exports.pathFinder = exports.mergeBBox = exports.isSegmentsIntersected = exports.isSegmentCrossingBBox = exports.isPointOutsideBBox = exports.isHorizontalPort = exports.isBending = exports.isBBoxesOverlapping = exports.heuristicCostEstimate = exports.getSimplePolyline = exports.getPolylinePoints = exports.getPointsFromBBox = exports.getPathWithBorderRadiusByPolyline = exports.getNeighborPoints = exports.getExpandedBBoxPoint = exports.getExpandedBBox = exports.getBorderRadiusPoints = exports.getBBoxYCrossPoints = exports.getBBoxXCrossPoints = exports.getBBoxFromPoints = exports.getBBoxFromPoint = exports.getBBoxCrossPointsByPoint = exports.filterConnectPoints = exports.distance = exports._costByPoints = exports.SortedArray = void 0; var getBBoxFromPoint = exports.getBBoxFromPoint = function getBBoxFromPoint(point) { var x = point.x, y = point.y; return { x: x, y: y, centerX: x, centerY: y, minX: x, minY: y, maxX: x, maxY: y, height: 0, width: 0 }; }; var getBBoxFromPoints = exports.getBBoxFromPoints = function getBBoxFromPoints(points) { if (points === void 0) { points = []; } var xs = []; var ys = []; points.forEach(function (p) { xs.push(p.x); ys.push(p.y); }); var minX = Math.min.apply(Math, xs); var maxX = Math.max.apply(Math, xs); var minY = Math.min.apply(Math, ys); var maxY = Math.max.apply(Math, ys); return { centerX: (minX + maxX) / 2, centerY: (minY + maxY) / 2, maxX: maxX, maxY: maxY, minX: minX, minY: minY, height: maxY - minY, width: maxX - minX }; }; var isBBoxesOverlapping = exports.isBBoxesOverlapping = function isBBoxesOverlapping(b1, b2) { return Math.abs(b1.centerX - b2.centerX) * 2 < b1.width + b2.width && Math.abs(b1.centerY - b2.centerY) * 2 < b1.height + b2.height; }; var filterConnectPoints = exports.filterConnectPoints = function filterConnectPoints(points) { // pre-process: remove duplicated points var result = []; var map = {}; var pointsLength = points.length; for (var i = pointsLength - 1; i >= 0; i--) { var p = points[i]; p.id = "".concat(p.x, "|||").concat(p.y); if (!map[p.id]) { map[p.id] = p; result.push(p); } } return result; }; var simplifyPolyline = exports.simplifyPolyline = function simplifyPolyline(points) { return filterConnectPoints(points); }; var getSimplePolyline = exports.getSimplePolyline = function getSimplePolyline(sPoint, tPoint) { return [sPoint, { x: sPoint.x, y: tPoint.y }, tPoint]; }; var getExpandedBBox = exports.getExpandedBBox = function getExpandedBBox(bbox, offset) { if (bbox.width || bbox.height) { return { centerX: bbox.centerX, centerY: bbox.centerY, minX: bbox.minX - offset, minY: bbox.minY - offset, maxX: bbox.maxX + offset, maxY: bbox.maxY + offset, height: bbox.height + 2 * offset, width: bbox.width + 2 * offset }; } // when it is a point return bbox; }; var isHorizontalPort = exports.isHorizontalPort = function isHorizontalPort(port, bbox) { var dx = Math.abs(port.x - bbox.centerX); var dy = Math.abs(port.y - bbox.centerY); if (dx === 0 && dy === 0) return 0; return dx / bbox.width > dy / bbox.height; }; var getExpandedBBoxPoint = exports.getExpandedBBoxPoint = function getExpandedBBoxPoint(bbox, // 将原来节点 bbox 扩展了 offset 后的 bbox,且被 gridSize 格式化 point, // 被 gridSize 格式化后的位置(anchorPoint) anotherPoint) { var isHorizontal = isHorizontalPort(point, bbox); if (isHorizontal === 0) { // 说明锚点是节点中心,linkCenter: true。需要根据两个节点的相对关系决定方向 var x = bbox.centerX; var y = bbox.centerY; if (anotherPoint.y < point.y) { // 另一端在左上/右上方时,总是从上方走 y = bbox.minY; } else if (anotherPoint.x > point.x) { // 另一端在右下方,往右边走 x = bbox.maxX; } else if (anotherPoint.x < point.x) { // 另一端在左下方,往左边走 x = bbox.minX; } else if (anotherPoint.x === point.x) { // 另一段在正下方,往下走 y = bbox.maxY; } return { x: x, y: y }; } if (isHorizontal) { return { x: point.x > bbox.centerX ? bbox.maxX : bbox.minX, y: point.y }; } return { x: point.x, y: point.y > bbox.centerY ? bbox.maxY : bbox.minY }; }; /** * * @param b1 * @param b2 */ var mergeBBox = exports.mergeBBox = function mergeBBox(b1, b2) { var minX = Math.min(b1.minX, b2.minX); var minY = Math.min(b1.minY, b2.minY); var maxX = Math.max(b1.maxX, b2.maxX); var maxY = Math.max(b1.maxY, b2.maxY); return { centerX: (minX + maxX) / 2, centerY: (minY + maxY) / 2, minX: minX, minY: minY, maxX: maxX, maxY: maxY, height: maxY - minY, width: maxX - minX }; }; var getPointsFromBBox = exports.getPointsFromBBox = function getPointsFromBBox(bbox) { // anticlockwise return [{ x: bbox.minX, y: bbox.minY }, { x: bbox.maxX, y: bbox.minY }, { x: bbox.maxX, y: bbox.maxY }, { x: bbox.minX, y: bbox.maxY }]; }; var isPointOutsideBBox = exports.isPointOutsideBBox = function isPointOutsideBBox(point, bbox) { var x = point.x, y = point.y; return x < bbox.minX || x > bbox.maxX || y < bbox.minY || y > bbox.maxY; }; var getBBoxXCrossPoints = exports.getBBoxXCrossPoints = function getBBoxXCrossPoints(bbox, x) { if (x < bbox.minX || x > bbox.maxX) { return []; } return [{ x: x, y: bbox.minY }, { x: x, y: bbox.maxY }]; }; var getBBoxYCrossPoints = exports.getBBoxYCrossPoints = function getBBoxYCrossPoints(bbox, y) { if (y < bbox.minY || y > bbox.maxY) { return []; } return [{ x: bbox.minX, y: y }, { x: bbox.maxX, y: y }]; }; var getBBoxCrossPointsByPoint = exports.getBBoxCrossPointsByPoint = function getBBoxCrossPointsByPoint(bbox, point) { return getBBoxXCrossPoints(bbox, point.x).concat(getBBoxYCrossPoints(bbox, point.y)); }; /** * 曼哈顿距离 */ var distance = exports.distance = function distance(p1, p2) { return Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y); }; /** * 如果 points 中的一个节点 x 与 p 相等,则消耗 -2。y 同 * 即优先选择和 points 在同一水平线 / 垂直线上的点 */ // eslint-disable-next-line @typescript-eslint/naming-convention var _costByPoints = exports._costByPoints = function _costByPoints(p, points) { var offset = -2; var result = 0; points.forEach(function (point) { if (point) { if (p.x === point.x) { result += offset; } if (p.y === point.y) { result += offset; } } }); return result; }; /** * ps 经过 p 到 pt 的距离,减去其他路过节点造成的消耗 */ var heuristicCostEstimate = exports.heuristicCostEstimate = function heuristicCostEstimate(p, ps, pt, source, target) { return distance(p, ps) + distance(p, pt) + _costByPoints(p, [ps, pt, source, target]); }; var _reconstructPath = exports.reconstructPath = function reconstructPath(pathPoints, pointById, cameFrom, currentId, iterator) { if (iterator === void 0) { iterator = 0; } pathPoints.unshift(pointById[currentId]); if (cameFrom[currentId] && cameFrom[currentId] !== currentId && iterator <= 100) { _reconstructPath(pathPoints, pointById, cameFrom, cameFrom[currentId], iterator + 1); } }; /** * 从 arr 中删去 item */ var removeFrom = exports.removeFrom = function removeFrom(arr, item) { var index = arr.indexOf(item); if (index > -1) { arr.splice(index, 1); } }; var isSegmentsIntersected = exports.isSegmentsIntersected = function isSegmentsIntersected(p0, p1, p2, p3) { var v1x = p2.x - p0.x; var v1y = p2.y - p0.y; var v2x = p3.x - p0.x; var v2y = p3.y - p0.y; var v3x = p2.x - p1.x; var v3y = p2.y - p1.y; var v4x = p3.x - p1.x; var v4y = p3.y - p1.y; var pd1 = v1x * v2y - v1y * v2x; var pd2 = v3x * v4y - v3y * v4x; var pd3 = v1x * v3y - v1y * v3x; var pd4 = v2x * v4y - v2y * v4x; return pd1 * pd2 <= 0 && pd3 * pd4 <= 0; }; var isSegmentCrossingBBox = exports.isSegmentCrossingBBox = function isSegmentCrossingBBox(p1, p2, bbox) { if (bbox.width || bbox.height) { var _a = getPointsFromBBox(bbox), pa = _a[0], pb = _a[1], pc = _a[2], pd = _a[3]; return isSegmentsIntersected(p1, p2, pa, pb) || isSegmentsIntersected(p1, p2, pa, pd) || isSegmentsIntersected(p1, p2, pb, pc) || isSegmentsIntersected(p1, p2, pc, pd); } return false; }; /** * 在 points 中找到满足 x 或 y 和 point 的 x 或 y 相等,且与 point 连线不经过 bbox1 与 bbox2 的点 */ var getNeighborPoints = exports.getNeighborPoints = function getNeighborPoints(points, point, bbox1, bbox2) { var neighbors = []; points.forEach(function (p) { if (p === point) return; if (p.x === point.x || p.y === point.y) { if (isSegmentCrossingBBox(p, point, bbox1) || isSegmentCrossingBBox(p, point, bbox2)) return; neighbors.push(p); } }); return filterConnectPoints(neighbors); }; /** * sorted array ascendly * add new item to proper index when calling add */ var SortedArray = exports.SortedArray = /** @class */function () { function SortedArray() { this.arr = []; this.map = {}; this.arr = []; this.map = {}; } SortedArray.prototype._innerAdd = function (item, length) { var idxRange = [0, length - 1]; while (idxRange[1] - idxRange[0] > 1) { var midIdx = Math.floor((idxRange[0] + idxRange[1]) / 2); if (this.arr[midIdx].value > item.value) { idxRange[1] = midIdx; } else if (this.arr[midIdx].value < item.value) { idxRange[0] = midIdx; } else { this.arr.splice(midIdx, 0, item); this.map[item.id] = true; return; } } this.arr.splice(idxRange[1], 0, item); this.map[item.id] = true; }; SortedArray.prototype.add = function (item) { // 已经存在,先移除 delete this.map[item.id]; var length = this.arr.length; if (!length) { this.arr.push(item); this.map[item.id] = true; return; } // 比最后一个大,加入尾部 if (this.arr[length - 1].value < item.value) { this.arr.push(item); this.map[item.id] = true; return; } this._innerAdd(item, length); }; // only remove from the map to avoid cost // clear the invalid (not in the map) item when calling minId(true) SortedArray.prototype.remove = function (id) { if (!this.map[id]) return; delete this.map[id]; }; SortedArray.prototype._clearAndGetMinId = function () { var res; for (var i = this.arr.length - 1; i >= 0; i--) { if (this.map[this.arr[i].id]) res = this.arr[i].id;else this.arr.splice(i, 1); } return res; }; SortedArray.prototype._findFirstId = function () { while (this.arr.length) { var first = this.arr.shift(); if (this.map[first.id]) return first.id; } }; SortedArray.prototype.minId = function (clear) { if (clear) { return this._clearAndGetMinId(); } else { return this._findFirstId(); } }; return SortedArray; }(); var pathFinder = exports.pathFinder = function pathFinder(points, start, goal, sBBox, tBBox, os, ot) { var _a; // A-Star Algorithm var closedSet = []; var openSet = (_a = {}, _a[start.id] = start, _a); var cameFrom = {}; var gScore = {}; // all default values are Infinity var fScore = {}; // all default values are Infinity gScore[start.id] = 0; fScore[start.id] = heuristicCostEstimate(start, goal, start); var sortedOpenSet = new SortedArray(); sortedOpenSet.add({ id: start.id, value: fScore[start.id] }); var pointById = {}; points.forEach(function (p) { pointById[p.id] = p; }); var current; while (Object.keys(openSet).length) { var minId = sortedOpenSet.minId(false); if (minId) { current = openSet[minId]; } else { break; } // 若 openSet 中 fScore 最小的点就是终点 if (current === goal) { // ending condition var pathPoints = []; _reconstructPath(pathPoints, pointById, cameFrom, goal.id); return pathPoints; } delete openSet[current.id]; sortedOpenSet.remove(current.id); closedSet.push(current); var neighborPoints = getNeighborPoints(points, current, sBBox, tBBox); var iterateNeighbors = function iterateNeighbors(items) { items.forEach(function (neighbor) { if (closedSet.indexOf(neighbor) !== -1) { return; } var neighborId = neighbor.id; if (!openSet[neighborId]) { openSet[neighborId] = neighbor; } var tentativeGScore = fScore[current.id] + distance(current, neighbor); // + distance(neighbor, goal); if (gScore[neighborId] && tentativeGScore >= gScore[neighborId]) { sortedOpenSet.add({ id: neighborId, value: fScore[neighborId] }); return; } cameFrom[neighborId] = current.id; gScore[neighborId] = tentativeGScore; fScore[neighborId] = gScore[neighborId] + heuristicCostEstimate(neighbor, goal, start, os, ot); sortedOpenSet.add({ id: neighborId, value: fScore[neighborId] }); }); }; iterateNeighbors(neighborPoints); } // throw new Error('Cannot find path'); return [start, goal]; }; var isBending = exports.isBending = function isBending(p0, p1, p2) { return !(p0.x === p1.x && p1.x === p2.x || p0.y === p1.y && p1.y === p2.y); }; var getBorderRadiusPoints = exports.getBorderRadiusPoints = function getBorderRadiusPoints(p0, p1, p2, r) { var d0 = distance(p0, p1); var d1 = distance(p2, p1); if (d0 < r) { r = d0; } if (d1 < r) { r = d1; } var ps = { x: p1.x - r / d0 * (p1.x - p0.x), y: p1.y - r / d0 * (p1.y - p0.y) }; var pt = { x: p1.x - r / d1 * (p1.x - p2.x), y: p1.y - r / d1 * (p1.y - p2.y) }; return [ps, pt]; }; var getPathWithBorderRadiusByPolyline = exports.getPathWithBorderRadiusByPolyline = function getPathWithBorderRadiusByPolyline(points, borderRadius) { var pathSegments = []; var startPoint = points[0]; pathSegments.push("M".concat(startPoint.x, " ").concat(startPoint.y)); points.forEach(function (p, i) { var p1 = points[i + 1]; var p2 = points[i + 2]; if (p1 && p2) { if (isBending(p, p1, p2)) { var _a = getBorderRadiusPoints(p, p1, p2, borderRadius), ps = _a[0], pt = _a[1]; pathSegments.push("L".concat(ps.x, " ").concat(ps.y)); pathSegments.push("Q".concat(p1.x, " ").concat(p1.y, " ").concat(pt.x, " ").concat(pt.y)); pathSegments.push("L".concat(pt.x, " ").concat(pt.y)); } else { pathSegments.push("L".concat(p1.x, " ").concat(p1.y)); } } else if (p1) { pathSegments.push("L".concat(p1.x, " ").concat(p1.y)); } }); return pathSegments.join(''); }; var getPolylinePoints = exports.getPolylinePoints = function getPolylinePoints(start, end, sNode, tNode, offset) { var sBBox, tBBox; if (!sNode || !sNode.getType()) { sBBox = getBBoxFromPoint(start); } else if (sNode.getType() === 'combo') { var sKeyShapeBBox = sNode.getKeyShape().getBBox(); if (sKeyShapeBBox) { var _a = sNode.getModel(), sx = _a.x, sy = _a.y; sBBox = { x: sx, y: sy, width: sKeyShapeBBox.width, height: sKeyShapeBBox.height, minX: sKeyShapeBBox.minX + sx, maxX: sKeyShapeBBox.maxX + sx, minY: sKeyShapeBBox.minY + sy, maxY: sKeyShapeBBox.maxY + sy }; sBBox.centerX = (sBBox.minX + sBBox.maxX) / 2; sBBox.centerY = (sBBox.minY + sBBox.maxY) / 2; } else { sBBox = getBBoxFromPoint(start); } } else { sBBox = sNode && sNode.getBBox(); } if (!tNode || !tNode.getType()) { tBBox = getBBoxFromPoint(end); } else if (tNode.getType() === 'combo') { var tKeyShapeBBox = tNode.getKeyShape().getBBox(); if (tKeyShapeBBox) { var _b = tNode.getModel(), tx = _b.x, ty = _b.y; tBBox = { x: tx, y: ty, width: tKeyShapeBBox.width, height: tKeyShapeBBox.height, minX: tKeyShapeBBox.minX + tx, maxX: tKeyShapeBBox.maxX + tx, minY: tKeyShapeBBox.minY + ty, maxY: tKeyShapeBBox.maxY + ty }; tBBox.centerX = (tBBox.minX + tBBox.maxX) / 2; tBBox.centerY = (tBBox.minY + tBBox.maxY) / 2; } else { tBBox = getBBoxFromPoint(end); } } else { tBBox = tNode && tNode.getBBox(); } // if (isBBoxesOverlapping(sBBox, tBBox)) { // // source and target nodes are overlapping // return simplifyPolyline(getSimplePolyline(start, end)); // } var sxBBox = getExpandedBBox(sBBox, offset); var txBBox = getExpandedBBox(tBBox, offset); // if (isBBoxesOverlapping(sxBBox, txBBox)) { // // the expanded bounding boxes of source and target nodes are overlapping // return simplifyPolyline(getSimplePolyline(start, end)); // } var sPoint = getExpandedBBoxPoint(sxBBox, start, end); var tPoint = getExpandedBBoxPoint(txBBox, end, start); var lineBBox = getBBoxFromPoints([sPoint, tPoint]); var sMixBBox = mergeBBox(sxBBox, lineBBox); var tMixBBox = mergeBBox(txBBox, lineBBox); var connectPoints = []; connectPoints = connectPoints.concat(getPointsFromBBox(sMixBBox)).concat(getPointsFromBBox(tMixBBox)); var centerPoint = { x: (start.x + end.x) / 2, y: (start.y + end.y) / 2 }; [lineBBox, sMixBBox, tMixBBox].forEach(function (bbox) { connectPoints = connectPoints.concat(getBBoxCrossPointsByPoint(bbox, centerPoint).filter(function (p) { return isPointOutsideBBox(p, sxBBox) && isPointOutsideBBox(p, txBBox); })); }); [{ x: sPoint.x, y: tPoint.y }, { x: tPoint.x, y: sPoint.y }].forEach(function (p) { // impossible!! if (isPointOutsideBBox(p, sxBBox) && isPointOutsideBBox(p, txBBox) // && // isPointInsideBBox(p, sMixBBox) && isPointInsideBBox(p, tMixBBox) ) { connectPoints.push(p); } }); connectPoints.unshift(sPoint); connectPoints.push(tPoint); // filter out dulplicated points in connectPoints connectPoints = filterConnectPoints(connectPoints); // , sxBBox, txBBox, outerBBox var pathPoints = pathFinder(connectPoints, sPoint, tPoint, sBBox, tBBox, start, end); pathPoints.unshift(start); pathPoints.push(end); return simplifyPolyline(pathPoints); }; /** * 去除连续同 x 不同 y 的中间点;去除连续同 y 不同 x 的中间点 * @param points 坐标集合 { x: number, y: number, id: string }[] * @returns */ var removeRedundantPoint = exports.removeRedundantPoint = function removeRedundantPoint(points) { if (!(points === null || points === void 0 ? void 0 : points.length)) return points; var beginPoint = points[points.length - 1]; var current = { x: beginPoint.x, y: beginPoint.y }; var continueSameX = [beginPoint]; var continueSameY = [beginPoint]; for (var i = points.length - 2; i >= 0; i--) { var point = points[i]; if (point.x === current.x) { continueSameX.push(point); } else { continueSameX = [point]; current.x = point.x; } if (point.y === current.y) { continueSameY.push(point); } else { continueSameY = [point]; current.y = point.y; } if (continueSameX.length > 2) { var removeIdx = points.indexOf(continueSameX[1]); if (removeIdx > -1) points.splice(removeIdx, 1); continue; } if (continueSameY.length > 2) { var removeIdx = points.indexOf(continueSameY[1]); if (removeIdx > -1) points.splice(removeIdx, 1); } } return points; };