diff --git a/packages/core/src/model/GraphModel.ts b/packages/core/src/model/GraphModel.ts index 1e9967f03..f27bfcddb 100644 --- a/packages/core/src/model/GraphModel.ts +++ b/packages/core/src/model/GraphModel.ts @@ -1214,8 +1214,12 @@ class GraphModel { nodes.forEach((node) => { const { x, y, width, height } = node; const { strokeWidth = 0 } = node.getNodeStyle(); - nodesX = nodesX.concat([x + width / 2 + strokeWidth, x - width / 2 - strokeWidth]); - nodesY = nodesY.concat([y + height / 2 + strokeWidth, y - height / 2 - strokeWidth]); + const maxX = x + width / 2 + strokeWidth; + const minX = x - width / 2 - strokeWidth; + const maxY = y + height / 2 + strokeWidth; + const minY = y - height / 2 - strokeWidth; + nodesX = nodesX.concat([maxX, minX].filter(num => !Number.isNaN(num))); + nodesY = nodesY.concat([maxY, minY].filter(num => !Number.isNaN(num))); }); const minX = Math.min(...nodesX); diff --git a/packages/core/src/options.ts b/packages/core/src/options.ts index 1ce049eeb..90d2382bc 100644 --- a/packages/core/src/options.ts +++ b/packages/core/src/options.ts @@ -7,7 +7,7 @@ import { EdgeData, Extension, GraphConfigData, - Point + Point, } from './type'; import { KeyboardDef } from './keyboard'; import { EditConfigInterface } from './model/EditConfigModel'; diff --git a/packages/extension/src/components/menu/index.ts b/packages/extension/src/components/menu/index.ts index d935ade50..54341793d 100644 --- a/packages/extension/src/components/menu/index.ts +++ b/packages/extension/src/components/menu/index.ts @@ -103,6 +103,7 @@ class Menu { this.menuTypeMap.set(DefaultSelectionMenuKey, DefaultSelectionMenu); } render(lf, container) { + if (lf.options.isSilentMode) return; this.__container = container; this.__currentData = null; // 当前展示的菜单所属元素的model数据 this.__menuDOM.className = 'lf-menu'; diff --git a/packages/extension/src/materials/curved-edge/__test__/curved-edge.test.ts b/packages/extension/src/materials/curved-edge/__test__/curved-edge.test.ts new file mode 100644 index 000000000..5cf27cb1a --- /dev/null +++ b/packages/extension/src/materials/curved-edge/__test__/curved-edge.test.ts @@ -0,0 +1,43 @@ +import { getCurvedEdgePath } from '../index'; + +describe('test curved edge ', () => { + test('path calculation', () => { + const radius = 5; + + const points1 = '460,150 670,150'; + const path1 = 'M460 150 L 670 150'; + expect( + getCurvedEdgePath( + points1.split(' ').map((p) => p.split(',').map((a) => +a)), + radius, + ), + ).toBe(path1); + + const points2 = '510,250 540,250 540,175 490,175 490,100 520,100'; + const path2 = 'M510 250L 510 250L 535 250 Q 540 250 540 245L 540 245L 540 180 Q 540 175 535 175L 535 175L 495 175 Q 490 175 490 170L 490 170L 490 105 Q 490 100 495 100L 520 100'; + expect( + getCurvedEdgePath( + points2.split(' ').map((p) => p.split(',').map((a) => +a)), + radius, + ), + ).toBe(path2); + + const points3 = '690,120 720,120 720,50 560,50 560,260 690,260'; + const path3 = 'M690 120L 690 120L 715 120 Q 720 120 720 115L 720 115L 720 55 Q 720 50 715 50L 715 50L 565 50 Q 560 50 560 55L 560 55L 560 255 Q 560 260 565 260L 690 260'; + expect( + getCurvedEdgePath( + points3.split(' ').map((p) => p.split(',').map((a) => +a)), + radius, + ), + ).toBe(path3); + + const point4 = '690,180 690,210 660,210 660,190 630,190 630,220'; + const path4 = 'M690 180L 690 180L 690 205 Q 690 210 685 210L 685 210L 665 210 Q 660 210 660 205L 660 205L 660 195 Q 660 190 655 190L 655 190L 635 190 Q 630 190 630 195L 630 220'; + expect( + getCurvedEdgePath( + point4.split(' ').map((p) => p.split(',').map((a) => +a)), + radius, + ), + ).toBe(path4); + }); +}); diff --git a/packages/extension/src/materials/curved-edge/index.ts b/packages/extension/src/materials/curved-edge/index.ts index 856cdef99..c6e96f904 100644 --- a/packages/extension/src/materials/curved-edge/index.ts +++ b/packages/extension/src/materials/curved-edge/index.ts @@ -1,95 +1,188 @@ -import { PolylineEdge, PolylineEdgeModel, h } from '@logicflow/core'; -import searchMiddleIndex from './searchMiddleIndex'; +import { + PointTuple, + PolylineEdge, + PolylineEdgeModel, + h, +} from '@logicflow/core'; -class CurvedEdge extends PolylineEdge { - pointFilter(points) { - const allPoints = points; - let i = 1; - while (i < allPoints.length - 1) { - const [x, y] = allPoints[i - 1]; - const [x1, y1] = allPoints[i]; - const [x2, y2] = allPoints[i + 1]; - if ((x === x1 && x1 === x2) - || (y === y1 && y1 === y2)) { - allPoints.splice(i, 1); - } else { - i++; - } +type DirectionType = 't' | 'b' | 'l' | 'r' | ''; +type ArcPositionType = 'tl' | 'tr' | 'bl' | 'br' | '-'; + +const directionMap: any = { + tr: 'tl', + lb: 'tl', + tl: 'tr', + rb: 'tr', + br: 'bl', + lt: 'bl', + bl: 'br', + rt: 'br', +}; + +function pointFilter(points: number[][]) { + const all = points; + let i = 1; + while (i < all.length - 1) { + const [x, y] = all[i - 1]; + const [x1, y1] = all[i]; + const [x2, y2] = all[i + 1]; + if ((x === x1 && x1 === x2) || (y === y1 && y1 === y2)) { + all.splice(i, 1); + } else { + i++; } - return allPoints; } - getEdge() { - const { model } = this.props; - const { points, isAnimation, arrowConfig, radius = 5 } = model; - const style = model.getEdgeStyle(); - const animationStyle = model.getEdgeAnimationStyle(); - const points2 = this.pointFilter(points.split(' ').map((p) => p.split(',').map(a => Number(a)))); - const res = searchMiddleIndex(points2); - if (res) { - const [first, last] = res; - const firstPoint = points2[first]; - const lastPoint = points2[last]; - const flag = firstPoint.some((num, index) => num === lastPoint[index]); - if (!flag) { - const diff = (lastPoint[1] - firstPoint[1]) / 2; - const firstNextPoint = [lastPoint[0], lastPoint[1] - diff]; - const lastPrePoint = [firstPoint[0], firstPoint[1] + diff]; - points2.splice(first + 1, 0, lastPrePoint, firstNextPoint); + return all; +} + +function getMidPoints( + cur: PointTuple, + key: string, + orientation: ArcPositionType, + radius: number, +) { + const mid1 = [cur[0], cur[1]]; + const mid2 = [cur[0], cur[1]]; + switch (orientation) { + case 'tl': { + if (key === 'tr') { + mid1[1] += radius; + mid2[0] += radius; + } else if (key === 'lb') { + mid1[0] += radius; + mid2[1] += radius; } + return [mid1, mid2]; } - const [startX, startY] = points2[0]; - let d = `M${startX} ${startY}`; - // 1) 如果一个点不为开始和结束,则在这个点的前后增加弧度开始和结束点。 - // 2) 判断这个点与前一个点的坐标 - // 如果x相同则前一个点的x也不变, - // y为(这个点的y 大于前一个点的y, 则 为 这个点的y - 5;小于前一个点的y, 则为这个点的y+5) - // 同理,判断这个点与后一个点的x,y是否相同,如果x相同,则y进行加减,如果y相同,则x进行加减 - for (let i = 1; i < points2.length - 1; i++) { - const [preX, preY] = points2[i - 1]; - const [currentX, currentY] = points2[i]; - const [nextX, nextY] = points2[i + 1]; - if (currentX === preX && currentY !== preY) { - const y = currentY > preY ? currentY - radius : currentY + radius; - d = `${d} L ${currentX} ${y}`; + case 'tr': { + if (key === 'tl') { + mid1[1] += radius; + mid2[0] -= radius; + } else if (key === 'rb') { + mid1[0] -= radius; + mid2[1] += radius; } - if (currentY === preY && currentX !== preX) { - const x = currentX > preX ? currentX - radius : currentX + radius; - d = `${d} L ${x} ${currentY}`; - } - d = `${d} Q ${currentX} ${currentY}`; - if (currentX === nextX && currentY !== nextY) { - const y = currentY > nextY ? currentY - radius : currentY + radius; - d = `${d} ${currentX} ${y}`; + return [mid1, mid2]; + } + case 'bl': { + if (key === 'br') { + mid1[1] -= radius; + mid2[0] += radius; + } else if (key === 'lt') { + mid1[0] += radius; + mid2[1] -= radius; } - if (currentY === nextY && currentX !== nextX) { - const x = currentX > nextX ? currentX - radius : currentX + radius; - d = `${d} ${x} ${currentY}`; + return [mid1, mid2]; + } + case 'br': { + if (key === 'bl') { + mid1[1] -= radius; + mid2[0] -= radius; + } else if (key === 'rt') { + mid1[0] -= radius; + mid2[1] -= radius; } + return [mid1, mid2]; } - const [endX, endY] = points2[points2.length - 1]; - d = `${d} L ${endX} ${endY}`; + default: + return null; + } +} + +function getPartialPath( + prev: PointTuple, + cur: PointTuple, + next: PointTuple, + radius: number, +): string { + let dir1: DirectionType = ''; + let dir2: DirectionType = ''; + let realRadius = radius; + + if (prev[0] === cur[0]) { + dir1 = prev[1] > cur[1] ? 't' : 'b'; + realRadius = Math.min(Math.abs(prev[0] - cur[0]), radius); + } else if (prev[1] === cur[1]) { + dir1 = prev[0] > cur[0] ? 'l' : 'r'; + realRadius = Math.min(Math.abs(prev[1] - cur[1]), radius); + } + if (cur[0] === next[0]) { + dir2 = cur[1] > next[1] ? 't' : 'b'; + realRadius = Math.min(Math.abs(prev[0] - cur[0]), radius); + } else if (cur[1] === next[1]) { + dir2 = cur[0] > next[0] ? 'l' : 'r'; + realRadius = Math.min(Math.abs(prev[1] - cur[1]), radius); + } + + const key = `${dir1}${dir2}`; + const orientation: ArcPositionType = directionMap[key] || '-'; + let path = `L ${prev[0]} ${prev[1]}`; + if (orientation === '-') { + path += `L ${cur[0]} ${cur[1]} L ${next[0]} ${next[1]}`; + } else { + const [mid1, mid2] = getMidPoints(cur, key, orientation, realRadius) || []; + if (mid1 && mid2) { + path += `L ${mid1[0]} ${mid1[1]} Q ${cur[0]} ${cur[1]} ${mid2[0]} ${mid2[1]}`; + [cur[0], cur[1]] = mid2; + } + } + return path; +} + +function getCurvedEdgePath(points: number[][], radius: number): string { + let i = 0; + let d = ''; + if (points.length === 2) { + d += `M${points[i][0]} ${points[i++][1]} L ${points[i][0]} ${points[i][1]}`; + } else { + d += `M${points[i][0]} ${points[i++][1]}`; + for (; i + 1 < points.length;) { + const prev = points[i - 1] as PointTuple; + const cur = points[i] as PointTuple; + const next = points[i++ + 1] as PointTuple; + d += getPartialPath(prev, cur, next, radius as number); + } + d += `L ${points[i][0]} ${points[i][1]}`; + } + return d; +} + +class CurvedEdge extends PolylineEdge { + getEdge() { + const { model } = this.props; + const { points: pointsStr, isAnimation, arrowConfig, radius = 5 } = model; + const style = model.getEdgeStyle(); + const animationStyle = model.getEdgeAnimationStyle(); + const points = pointFilter( + pointsStr.split(' ').map((p) => p.split(',').map((a) => +a)), + ); + const d = getCurvedEdgePath(points, radius as number); const attrs = { - d, style: isAnimation ? animationStyle : {}, ...style, ...arrowConfig, fill: 'none', }; - return h( - 'path', - { - d, - ...attrs, - }, + console.log( + pointsStr, + pointsStr.split(' ').map((p) => p.split(',').map((a) => +a)), + d, ); + return h('path', { + d, + ...attrs, + }); } } -class CurvedEdgeModel extends PolylineEdgeModel { -} +class CurvedEdgeModel extends PolylineEdgeModel {} -export { - CurvedEdge, - // CurvedEdgeView, - CurvedEdgeModel, +const defaultCurvedEdge = { + type: 'curved-edge', + view: CurvedEdge, + model: CurvedEdgeModel, }; + +export default defaultCurvedEdge; + +export { CurvedEdge, CurvedEdgeModel, getCurvedEdgePath }; diff --git a/packages/extension/src/materials/curved-edge/searchMiddleIndex.ts b/packages/extension/src/materials/curved-edge/searchMiddleIndex.ts deleted file mode 100644 index 83bba4df1..000000000 --- a/packages/extension/src/materials/curved-edge/searchMiddleIndex.ts +++ /dev/null @@ -1,13 +0,0 @@ -export default function searchMiddleIndex(arr: Array): [number, number] | false { - if (arr.length <= 1) return false; - let first = 0; - let last = arr.length - 1; - while (first !== last && first + 1 !== last && last - 1 !== first) { - first++; - last--; - } - if (first === last) { - return [--first, last]; - } - return [first, last]; -}