Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

bug fix #1373

Merged
merged 4 commits into from
Oct 19, 2023
Merged

bug fix #1373

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions packages/core/src/model/GraphModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
EdgeData,
Extension,
GraphConfigData,
Point
Point,
} from './type';
import { KeyboardDef } from './keyboard';
import { EditConfigInterface } from './model/EditConfigModel';
Expand Down
1 change: 1 addition & 0 deletions packages/extension/src/components/menu/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
});
});
241 changes: 167 additions & 74 deletions packages/extension/src/materials/curved-edge/index.ts
Original file line number Diff line number Diff line change
@@ -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 };
13 changes: 0 additions & 13 deletions packages/extension/src/materials/curved-edge/searchMiddleIndex.ts

This file was deleted.