@logicflow/core 2.2.0-alpha.7 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +6 -1
- package/.turbo/turbo-build$colon$dev.log +0 -10
- package/.turbo/turbo-build.log +0 -33
- package/CHANGELOG.md +0 -1901
- package/__tests__/algorithm/egde.test.ts +0 -131
- package/__tests__/algorithm/index.test.ts +0 -74
- package/__tests__/algorithm/outline.test.ts +0 -43
- package/__tests__/bugs/1545-spec.test.ts +0 -42
- package/__tests__/event/event.test.ts +0 -22
- package/__tests__/history/history.test.ts +0 -28
- package/__tests__/logicflow.test.ts +0 -575
- package/__tests__/model/graphmodel.test.ts +0 -87
- package/__tests__/util/compatible.test.ts +0 -48
- package/__tests__/util/edge.test.ts +0 -224
- package/__tests__/util/geometry.test.ts +0 -14
- package/__tests__/util/graph.test.ts +0 -16
- package/__tests__/util/matrix.test.ts +0 -41
- package/__tests__/util/node.test.ts +0 -68
- package/__tests__/util/sampling.test.ts +0 -12
- package/__tests__/util/vector.test.ts +0 -50
- package/__tests__/util/zIndex.test.ts +0 -10
- package/src/LogicFlow.tsx +0 -2017
- package/src/algorithm/edge.ts +0 -67
- package/src/algorithm/index.ts +0 -70
- package/src/algorithm/outline.ts +0 -77
- package/src/algorithm/rotate.ts +0 -55
- package/src/common/drag.ts +0 -219
- package/src/common/history.ts +0 -108
- package/src/common/index.ts +0 -6
- package/src/common/keyboard.ts +0 -108
- package/src/common/matrix.ts +0 -122
- package/src/common/vector.ts +0 -93
- package/src/constant/index.ts +0 -179
- package/src/constant/theme.ts +0 -708
- package/src/event/event.md +0 -66
- package/src/event/eventArgs.ts +0 -643
- package/src/event/eventEmitter.ts +0 -156
- package/src/history/index.ts +0 -119
- package/src/index.less +0 -1
- package/src/index.ts +0 -26
- package/src/keyboard/index.ts +0 -112
- package/src/keyboard/shortcut.ts +0 -200
- package/src/model/BaseModel.ts +0 -250
- package/src/model/EditConfigModel.ts +0 -334
- package/src/model/GraphModel.ts +0 -1824
- package/src/model/NestedTransformModel.ts +0 -121
- package/src/model/SnaplineModel.ts +0 -256
- package/src/model/TransformModel.ts +0 -258
- package/src/model/edge/BaseEdgeModel.ts +0 -785
- package/src/model/edge/BezierEdgeModel.ts +0 -197
- package/src/model/edge/LineEdgeModel.ts +0 -36
- package/src/model/edge/PolylineEdgeModel.ts +0 -817
- package/src/model/edge/index.ts +0 -4
- package/src/model/index.ts +0 -9
- package/src/model/node/BaseNodeModel.ts +0 -959
- package/src/model/node/CircleNodeModel.ts +0 -91
- package/src/model/node/DiamondNodeModel.ts +0 -132
- package/src/model/node/EllipseNodeModel.ts +0 -98
- package/src/model/node/HtmlNodeModel.ts +0 -64
- package/src/model/node/PolygonNodeModel.ts +0 -152
- package/src/model/node/RectNodeModel.ts +0 -69
- package/src/model/node/TextNodeModel.ts +0 -54
- package/src/model/node/index.ts +0 -8
- package/src/options.ts +0 -150
- package/src/style/index.less +0 -262
- package/src/style/raw.ts +0 -221
- package/src/tool/MultipleSelectTool.tsx +0 -140
- package/src/tool/TextEditTool.tsx +0 -193
- package/src/tool/index.ts +0 -101
- package/src/typings.d.ts +0 -5
- package/src/util/animation.ts +0 -29
- package/src/util/browser.ts +0 -4
- package/src/util/compatible.ts +0 -15
- package/src/util/drag.ts +0 -219
- package/src/util/edge.ts +0 -1094
- package/src/util/geometry.ts +0 -154
- package/src/util/graph.ts +0 -46
- package/src/util/index.ts +0 -17
- package/src/util/matrix.ts +0 -129
- package/src/util/mobx.ts +0 -23
- package/src/util/node.ts +0 -543
- package/src/util/raf.ts +0 -28
- package/src/util/resize.ts +0 -606
- package/src/util/sampling.ts +0 -85
- package/src/util/theme.ts +0 -84
- package/src/util/uuid.ts +0 -26
- package/src/util/vector.ts +0 -93
- package/src/util/zIndex.ts +0 -6
- package/src/view/Anchor.tsx +0 -462
- package/src/view/Control.tsx +0 -510
- package/src/view/Graph.tsx +0 -141
- package/src/view/Rotate.tsx +0 -113
- package/src/view/behavior/dnd.ts +0 -162
- package/src/view/behavior/index.ts +0 -2
- package/src/view/behavior/snapline.ts +0 -16
- package/src/view/edge/AdjustPoint.tsx +0 -425
- package/src/view/edge/Arrow.tsx +0 -54
- package/src/view/edge/BaseEdge.tsx +0 -660
- package/src/view/edge/BezierEdge.tsx +0 -101
- package/src/view/edge/LineEdge.tsx +0 -81
- package/src/view/edge/PolylineEdge.tsx +0 -311
- package/src/view/edge/index.ts +0 -6
- package/src/view/index.ts +0 -8
- package/src/view/node/BaseNode.tsx +0 -585
- package/src/view/node/CircleNode.tsx +0 -21
- package/src/view/node/DiamondNode.tsx +0 -24
- package/src/view/node/EllipseNode.tsx +0 -22
- package/src/view/node/HtmlNode.tsx +0 -112
- package/src/view/node/PolygonNode.tsx +0 -28
- package/src/view/node/RectNode.tsx +0 -30
- package/src/view/node/TextNode.tsx +0 -39
- package/src/view/node/index.ts +0 -8
- package/src/view/overlay/BackgroundOverlay.tsx +0 -34
- package/src/view/overlay/BezierAdjustOverlay.tsx +0 -150
- package/src/view/overlay/CanvasOverlay.tsx +0 -290
- package/src/view/overlay/Grid.tsx +0 -319
- package/src/view/overlay/ModificationOverlay.tsx +0 -31
- package/src/view/overlay/OutlineOverlay.tsx +0 -158
- package/src/view/overlay/SnaplineOverlay.tsx +0 -44
- package/src/view/overlay/ToolOverlay.tsx +0 -65
- package/src/view/overlay/getTransformHoc.tsx +0 -50
- package/src/view/overlay/gridConfig.ts +0 -103
- package/src/view/overlay/index.ts +0 -8
- package/src/view/shape/Circle.tsx +0 -41
- package/src/view/shape/Ellipse.tsx +0 -42
- package/src/view/shape/Line.tsx +0 -39
- package/src/view/shape/Path.tsx +0 -22
- package/src/view/shape/Polygon.tsx +0 -54
- package/src/view/shape/Polyline.tsx +0 -31
- package/src/view/shape/Rect.tsx +0 -44
- package/src/view/shape/Text.tsx +0 -168
- package/src/view/shape/index.ts +0 -8
- package/src/view/text/BaseText.tsx +0 -134
- package/src/view/text/LineText.tsx +0 -168
- package/src/view/text/index.ts +0 -2
- package/stats.html +0 -4842
- package/tsconfig.json +0 -18
package/src/util/edge.ts
DELETED
|
@@ -1,1094 +0,0 @@
|
|
|
1
|
-
import { pick, forEach } from 'lodash-es'
|
|
2
|
-
import { getNodeBBox, isInNode, distance, sampleCubic } from '.'
|
|
3
|
-
import LogicFlow from '../LogicFlow'
|
|
4
|
-
import { Options } from '../options'
|
|
5
|
-
import { SegmentDirection } from '../constant'
|
|
6
|
-
import {
|
|
7
|
-
getVerticalPointOfLine,
|
|
8
|
-
getCrossPointOfLine,
|
|
9
|
-
isInSegment,
|
|
10
|
-
} from '../algorithm'
|
|
11
|
-
import {
|
|
12
|
-
Model,
|
|
13
|
-
BaseNodeModel,
|
|
14
|
-
BaseEdgeModel,
|
|
15
|
-
LineEdgeModel,
|
|
16
|
-
PolylineEdgeModel,
|
|
17
|
-
GraphModel,
|
|
18
|
-
} from '../model'
|
|
19
|
-
|
|
20
|
-
import Point = LogicFlow.Point
|
|
21
|
-
import Direction = LogicFlow.Direction
|
|
22
|
-
import LineSegment = LogicFlow.LineSegment
|
|
23
|
-
import NodeData = LogicFlow.NodeData
|
|
24
|
-
import EdgeConfig = LogicFlow.EdgeConfig
|
|
25
|
-
import Position = LogicFlow.Position
|
|
26
|
-
import BoxBounds = Model.BoxBounds
|
|
27
|
-
|
|
28
|
-
type PolyPointMap = Record<string, Point>
|
|
29
|
-
type PolyPointLink = Record<string, string>
|
|
30
|
-
|
|
31
|
-
/* 手动创建边时 edge -> edgeModel */
|
|
32
|
-
export const setupEdgeModel = (edge: EdgeConfig, graphModel: GraphModel) => {
|
|
33
|
-
let model: BaseEdgeModel
|
|
34
|
-
switch (edge.type) {
|
|
35
|
-
case 'line':
|
|
36
|
-
model = new LineEdgeModel(edge, graphModel)
|
|
37
|
-
break
|
|
38
|
-
case 'polyline':
|
|
39
|
-
model = new PolylineEdgeModel(edge, graphModel)
|
|
40
|
-
break
|
|
41
|
-
default:
|
|
42
|
-
model = new LineEdgeModel(edge, graphModel)
|
|
43
|
-
break
|
|
44
|
-
}
|
|
45
|
-
return model
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/* 判断两个Bbox是否重合 */
|
|
49
|
-
export const isBboxOverLapping = (b1: BoxBounds, b2: BoxBounds): boolean =>
|
|
50
|
-
Math.abs(b1.centerX - b2.centerX) * 2 < b1.width + b2.width &&
|
|
51
|
-
Math.abs(b1.centerY - b2.centerY) * 2 < b1.height + b2.height
|
|
52
|
-
|
|
53
|
-
/* 连接点去重,去掉x,y位置重复的点 */
|
|
54
|
-
export const filterRepeatPoints = (points: Point[]): Point[] => {
|
|
55
|
-
const result: Point[] = []
|
|
56
|
-
const pointsMap: Record<string, Point> = {}
|
|
57
|
-
points.forEach((p) => {
|
|
58
|
-
const id = `${p.x}-${p.y}`
|
|
59
|
-
p.id = id
|
|
60
|
-
pointsMap[id] = p
|
|
61
|
-
})
|
|
62
|
-
Object.keys(pointsMap).forEach((p) => {
|
|
63
|
-
result.push(pointsMap[p])
|
|
64
|
-
})
|
|
65
|
-
return result
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
/* 获取简单边:边之间除了起始点,只有1个中间点 */
|
|
69
|
-
export const getSimplePolyline = (sPoint: Point, tPoint: Point): Point[] => {
|
|
70
|
-
const points = [
|
|
71
|
-
sPoint,
|
|
72
|
-
{
|
|
73
|
-
x: sPoint.x,
|
|
74
|
-
y: tPoint.y,
|
|
75
|
-
},
|
|
76
|
-
tPoint,
|
|
77
|
-
]
|
|
78
|
-
return filterRepeatPoints(points)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/* 扩展的bbox,保证起始点的下一个点一定在node的垂直方向,不会与线重合, offset是点与线的垂直距离 */
|
|
82
|
-
export const getExpandedBBox = (bbox: BoxBounds, offset: number): BoxBounds => {
|
|
83
|
-
if (bbox.width === 0 && bbox.height === 0) {
|
|
84
|
-
return bbox
|
|
85
|
-
}
|
|
86
|
-
return {
|
|
87
|
-
x: bbox.x,
|
|
88
|
-
y: bbox.y,
|
|
89
|
-
centerX: bbox.centerX,
|
|
90
|
-
centerY: bbox.centerY,
|
|
91
|
-
minX: bbox.minX - offset,
|
|
92
|
-
minY: bbox.minY - offset,
|
|
93
|
-
maxX: bbox.maxX + offset,
|
|
94
|
-
maxY: bbox.maxY + offset,
|
|
95
|
-
height: bbox.height + 2 * offset,
|
|
96
|
-
width: bbox.width + 2 * offset,
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/* 判断点与中心点边的方向:是否水平,true水平,false垂直 */
|
|
101
|
-
export const pointDirection = (point: Point, bbox: BoxBounds): Direction => {
|
|
102
|
-
const dx = Math.abs(point.x - bbox.centerX)
|
|
103
|
-
const dy = Math.abs(point.y - bbox.centerY)
|
|
104
|
-
return dx / bbox.width > dy / bbox.height
|
|
105
|
-
? SegmentDirection.HORIZONTAL
|
|
106
|
-
: SegmentDirection.VERTICAL
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/* 获取扩展图形上的点,即起始终点相邻的点,上一个或者下一个节点 */
|
|
110
|
-
/**
|
|
111
|
-
* 计算扩展包围盒上的相邻点(起点或终点的下一个/上一个拐点)
|
|
112
|
-
* - 使用原始节点 bbox 来判定点相对中心的方向,避免 offset 扩展后宽高改变导致方向误判
|
|
113
|
-
* - 若 start 相对中心为水平方向,则返回扩展盒在 x 上的边界,y 保持不变
|
|
114
|
-
* - 若为垂直方向,则返回扩展盒在 y 上的边界,x 保持不变
|
|
115
|
-
* @param expendBBox 扩展后的包围盒(包含 offset)
|
|
116
|
-
* @param bbox 原始节点包围盒(用于正确的方向判定)
|
|
117
|
-
* @param point 起点或终点坐标
|
|
118
|
-
*/
|
|
119
|
-
export const getExpandedBBoxPoint = (
|
|
120
|
-
expendBBox: BoxBounds,
|
|
121
|
-
bbox: BoxBounds,
|
|
122
|
-
point: Point,
|
|
123
|
-
): Point => {
|
|
124
|
-
// https://github.com/didi/LogicFlow/issues/817
|
|
125
|
-
// 没有修复前传入的参数bbox实际是expendBBox
|
|
126
|
-
// 由于pointDirection使用的是dx / bbox.width > dy / bbox.height判定方向
|
|
127
|
-
// 此时的bbox.width是增加了offset后的宽度,bbox.height同理
|
|
128
|
-
// 这导致了部分极端情况(宽度很大或者高度很小),计算出来的方向错误
|
|
129
|
-
const direction = pointDirection(point, bbox)
|
|
130
|
-
if (direction === SegmentDirection.HORIZONTAL) {
|
|
131
|
-
return {
|
|
132
|
-
x: point.x > expendBBox.centerX ? expendBBox.maxX : expendBBox.minX,
|
|
133
|
-
y: point.y,
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return {
|
|
137
|
-
x: point.x,
|
|
138
|
-
y: point.y > expendBBox.centerY ? expendBBox.maxY : expendBBox.minY,
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/* 获取包含2个图形的外层box */
|
|
143
|
-
export const mergeBBox = (b1: BoxBounds, b2: BoxBounds): BoxBounds => {
|
|
144
|
-
const minX = Math.min(b1.minX, b2.minX)
|
|
145
|
-
const minY = Math.min(b1.minY, b2.minY)
|
|
146
|
-
const maxX = Math.max(b1.maxX, b2.maxX)
|
|
147
|
-
const maxY = Math.max(b1.maxY, b2.maxY)
|
|
148
|
-
|
|
149
|
-
return {
|
|
150
|
-
x: (minX + maxX) / 2,
|
|
151
|
-
y: (minY + maxY) / 2,
|
|
152
|
-
centerX: (minX + maxX) / 2,
|
|
153
|
-
centerY: (minY + maxY) / 2,
|
|
154
|
-
minX,
|
|
155
|
-
minY,
|
|
156
|
-
maxX,
|
|
157
|
-
maxY,
|
|
158
|
-
height: maxY - minY,
|
|
159
|
-
width: maxX - minX,
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/* 获取多个点的外层bbox
|
|
164
|
-
* 这个函数的用处
|
|
165
|
-
* 1、获取起始终点相邻点(expendBboxPoint)的bbox
|
|
166
|
-
* 2、polylineEdge, bezierEdge,获取outline边框,这种情况传入offset
|
|
167
|
-
*/
|
|
168
|
-
export const getBBoxOfPoints = (
|
|
169
|
-
points: Point[] = [],
|
|
170
|
-
offset?: number,
|
|
171
|
-
heightOffset?: number,
|
|
172
|
-
): BoxBounds => {
|
|
173
|
-
const xList: number[] = []
|
|
174
|
-
const yList: number[] = []
|
|
175
|
-
points.forEach((p) => {
|
|
176
|
-
xList.push(p.x)
|
|
177
|
-
yList.push(p.y)
|
|
178
|
-
})
|
|
179
|
-
const minX = Math.min(...xList)
|
|
180
|
-
const maxX = Math.max(...xList)
|
|
181
|
-
const minY = Math.min(...yList)
|
|
182
|
-
const maxY = Math.max(...yList)
|
|
183
|
-
let width = maxX - minX
|
|
184
|
-
let height = maxY - minY
|
|
185
|
-
if (offset) {
|
|
186
|
-
width += offset
|
|
187
|
-
height += heightOffset || offset
|
|
188
|
-
}
|
|
189
|
-
return {
|
|
190
|
-
centerX: (minX + maxX) / 2,
|
|
191
|
-
centerY: (minY + maxY) / 2,
|
|
192
|
-
maxX,
|
|
193
|
-
maxY,
|
|
194
|
-
minX,
|
|
195
|
-
minY,
|
|
196
|
-
x: (minX + maxX) / 2,
|
|
197
|
-
y: (minY + maxY) / 2,
|
|
198
|
-
height,
|
|
199
|
-
width,
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
/* 获取box四个角上的点 */
|
|
203
|
-
export const getPointsFromBBox = (
|
|
204
|
-
bbox: BoxBounds,
|
|
205
|
-
): [Point, Point, Point, Point] => {
|
|
206
|
-
const { minX, minY, maxX, maxY } = bbox
|
|
207
|
-
return [
|
|
208
|
-
{
|
|
209
|
-
x: minX,
|
|
210
|
-
y: minY,
|
|
211
|
-
},
|
|
212
|
-
{
|
|
213
|
-
x: maxX,
|
|
214
|
-
y: minY,
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
x: maxX,
|
|
218
|
-
y: maxY,
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
x: minX,
|
|
222
|
-
y: maxY,
|
|
223
|
-
},
|
|
224
|
-
]
|
|
225
|
-
}
|
|
226
|
-
/* 判断某一点是否在box中 */
|
|
227
|
-
export const isPointOutsideBBox = (point: Point, bbox: BoxBounds): boolean => {
|
|
228
|
-
const { x, y } = point
|
|
229
|
-
return x < bbox.minX || x > bbox.maxX || y < bbox.minY || y > bbox.maxY
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/* 获取点的x方向上与box的交点 */
|
|
233
|
-
export const getBBoxXCrossPoints = (
|
|
234
|
-
bbox: BoxBounds,
|
|
235
|
-
x: number,
|
|
236
|
-
): [Point, Point] | [] => {
|
|
237
|
-
if (x < bbox.minX || x > bbox.maxX) {
|
|
238
|
-
return []
|
|
239
|
-
}
|
|
240
|
-
return [
|
|
241
|
-
{
|
|
242
|
-
x,
|
|
243
|
-
y: bbox.minY,
|
|
244
|
-
},
|
|
245
|
-
{
|
|
246
|
-
x,
|
|
247
|
-
y: bbox.maxY,
|
|
248
|
-
},
|
|
249
|
-
]
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
/* 获取点的y方向上与box的交点 */
|
|
253
|
-
export const getBBoxYCrossPoints = (
|
|
254
|
-
bbox: BoxBounds,
|
|
255
|
-
y: number,
|
|
256
|
-
): [Point, Point] | [] => {
|
|
257
|
-
if (y < bbox.minY || y > bbox.maxY) {
|
|
258
|
-
return []
|
|
259
|
-
}
|
|
260
|
-
return [
|
|
261
|
-
{
|
|
262
|
-
x: bbox.minX,
|
|
263
|
-
y,
|
|
264
|
-
},
|
|
265
|
-
{
|
|
266
|
-
x: bbox.maxX,
|
|
267
|
-
y,
|
|
268
|
-
},
|
|
269
|
-
]
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/* 获取点的x,y方向上与box的交点 */
|
|
273
|
-
export const getBBoxCrossPointsByPoint = (
|
|
274
|
-
bbox: BoxBounds,
|
|
275
|
-
point: Point,
|
|
276
|
-
): [Point, Point, Point, Point] | [Point, Point] | [] => [
|
|
277
|
-
...getBBoxXCrossPoints(bbox, point.x),
|
|
278
|
-
...getBBoxYCrossPoints(bbox, point.y),
|
|
279
|
-
]
|
|
280
|
-
|
|
281
|
-
/* 计算两点之间的预测距离(非直线距离) */
|
|
282
|
-
export const estimateDistance = (p1: Point, p2: Point): number =>
|
|
283
|
-
Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y)
|
|
284
|
-
|
|
285
|
-
/* 减少点别重复计算进距离的误差 */
|
|
286
|
-
export const costByPoints = (p: Point, points: Point[]): number => {
|
|
287
|
-
const offset = -2
|
|
288
|
-
let result = 0
|
|
289
|
-
points.forEach((point) => {
|
|
290
|
-
if (point) {
|
|
291
|
-
if (p.x === point.x) {
|
|
292
|
-
result += offset
|
|
293
|
-
}
|
|
294
|
-
if (p.y === point.y) {
|
|
295
|
-
result += offset
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
})
|
|
299
|
-
return result
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
/* 预估距离 */
|
|
303
|
-
export const heuristicCostEstimate = (
|
|
304
|
-
p: Point,
|
|
305
|
-
ps: Point,
|
|
306
|
-
pt: Point,
|
|
307
|
-
source?: Point,
|
|
308
|
-
target?: Point,
|
|
309
|
-
): number =>
|
|
310
|
-
estimateDistance(p, ps) +
|
|
311
|
-
estimateDistance(p, pt) +
|
|
312
|
-
costByPoints(p, [ps, pt, source!, target!])
|
|
313
|
-
|
|
314
|
-
/* 重建路径,根据cameFrom属性计算出从起始到结束的路径 */
|
|
315
|
-
export const rebuildPath = (
|
|
316
|
-
pathPoints: Point[],
|
|
317
|
-
pointById: PolyPointMap,
|
|
318
|
-
cameFrom: PolyPointLink,
|
|
319
|
-
currentId: string,
|
|
320
|
-
iterator?: number,
|
|
321
|
-
): void => {
|
|
322
|
-
if (!iterator) {
|
|
323
|
-
iterator = 0
|
|
324
|
-
}
|
|
325
|
-
pathPoints.unshift(pointById[currentId])
|
|
326
|
-
if (
|
|
327
|
-
cameFrom[currentId] &&
|
|
328
|
-
cameFrom[currentId] !== currentId &&
|
|
329
|
-
iterator <= 100
|
|
330
|
-
) {
|
|
331
|
-
rebuildPath(
|
|
332
|
-
pathPoints,
|
|
333
|
-
pointById,
|
|
334
|
-
cameFrom,
|
|
335
|
-
cameFrom[currentId],
|
|
336
|
-
iterator + 1,
|
|
337
|
-
)
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
/* 把计算完毕的点从开放列表中删除 */
|
|
342
|
-
export const removeClosePointFromOpenList = (
|
|
343
|
-
arr: Point[],
|
|
344
|
-
item: Point,
|
|
345
|
-
): void => {
|
|
346
|
-
const index = arr.indexOf(item)
|
|
347
|
-
if (index > -1) {
|
|
348
|
-
arr.splice(index, 1)
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
/* 通过向量判断线段之间是否是相交的 */
|
|
353
|
-
export const isSegmentsIntersected = (
|
|
354
|
-
p0: Point,
|
|
355
|
-
p1: Point,
|
|
356
|
-
p2: Point,
|
|
357
|
-
p3: Point,
|
|
358
|
-
): boolean => {
|
|
359
|
-
const s1x = p1.x - p0.x
|
|
360
|
-
const s1y = p1.y - p0.y
|
|
361
|
-
const s2x = p3.x - p2.x
|
|
362
|
-
const s2y = p3.y - p2.y
|
|
363
|
-
|
|
364
|
-
const s =
|
|
365
|
-
(-s1y * (p0.x - p2.x) + s1x * (p0.y - p2.y)) / (-s2x * s1y + s1x * s2y)
|
|
366
|
-
const t =
|
|
367
|
-
(s2x * (p0.y - p2.y) - s2y * (p0.x - p2.x)) / (-s2x * s1y + s1x * s2y)
|
|
368
|
-
|
|
369
|
-
return s >= 0 && s <= 1 && t >= 0 && t <= 1
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/* 判断线段与bbox是否是相交的,保证节点之间的边不会穿过节点自身 */
|
|
373
|
-
export const isSegmentCrossingBBox = (
|
|
374
|
-
p1: Point,
|
|
375
|
-
p2: Point,
|
|
376
|
-
bbox: BoxBounds,
|
|
377
|
-
): boolean => {
|
|
378
|
-
if (bbox.width === 0 && bbox.height === 0) {
|
|
379
|
-
return false
|
|
380
|
-
}
|
|
381
|
-
const [pa, pb, pc, pd] = getPointsFromBBox(bbox)
|
|
382
|
-
return (
|
|
383
|
-
isSegmentsIntersected(p1, p2, pa, pb) ||
|
|
384
|
-
isSegmentsIntersected(p1, p2, pa, pd) ||
|
|
385
|
-
isSegmentsIntersected(p1, p2, pb, pc) ||
|
|
386
|
-
isSegmentsIntersected(p1, p2, pc, pd)
|
|
387
|
-
)
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
/**
|
|
391
|
-
* 基于轴对齐规则获取某点的相邻可连通点(不穿越节点)
|
|
392
|
-
* - 仅考虑 x 或 y 相同的候选点,保证严格水平/垂直
|
|
393
|
-
* - 使用 isSegmentCrossingBBox 校验线段不穿越源/目标节点
|
|
394
|
-
*/
|
|
395
|
-
export const getNextNeighborPoints = (
|
|
396
|
-
points: Point[],
|
|
397
|
-
point: Point,
|
|
398
|
-
bbox1: BoxBounds,
|
|
399
|
-
bbox2: BoxBounds,
|
|
400
|
-
): Point[] => {
|
|
401
|
-
const neighbors: Point[] = []
|
|
402
|
-
points.forEach((p) => {
|
|
403
|
-
if (p !== point) {
|
|
404
|
-
if (p.x === point.x || p.y === point.y) {
|
|
405
|
-
if (
|
|
406
|
-
!isSegmentCrossingBBox(p, point, bbox1) &&
|
|
407
|
-
!isSegmentCrossingBBox(p, point, bbox2)
|
|
408
|
-
) {
|
|
409
|
-
neighbors.push(p)
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
}
|
|
413
|
-
})
|
|
414
|
-
return filterRepeatPoints(neighbors)
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* 使用 A* + 曼哈顿启发式在候选点图上查找正交路径
|
|
419
|
-
* - 开放集/关闭集管理遍历
|
|
420
|
-
* - gScore 为累计实际代价,fScore = gScore + 启发式
|
|
421
|
-
* - 邻居仅为与当前点 x 或 y 相同且不穿越节点的点
|
|
422
|
-
* 参考:https://zh.wikipedia.org/wiki/A*%E6%90%9C%E5%B0%8B%E6%BC%94%E7%AE%97%E6%B3%95
|
|
423
|
-
*/
|
|
424
|
-
export const pathFinder = (
|
|
425
|
-
points: Point[],
|
|
426
|
-
start: Point,
|
|
427
|
-
goal: Point,
|
|
428
|
-
sBBox: BoxBounds,
|
|
429
|
-
tBBox: BoxBounds,
|
|
430
|
-
os: Point,
|
|
431
|
-
ot: Point,
|
|
432
|
-
): Point[] => {
|
|
433
|
-
// 定义已经遍历过的点
|
|
434
|
-
const closedSet: Point[] = []
|
|
435
|
-
// 定义需要遍历的店
|
|
436
|
-
const openSet = [start]
|
|
437
|
-
// 定义节点的上一个节点
|
|
438
|
-
const cameFrom: PolyPointLink = {}
|
|
439
|
-
|
|
440
|
-
const gScore: {
|
|
441
|
-
[key: string]: number
|
|
442
|
-
} = {}
|
|
443
|
-
|
|
444
|
-
const fScore: {
|
|
445
|
-
[key: string]: number
|
|
446
|
-
} = {}
|
|
447
|
-
|
|
448
|
-
if (start.id) {
|
|
449
|
-
gScore[start.id] = 0
|
|
450
|
-
fScore[start.id] = heuristicCostEstimate(start, goal, start)
|
|
451
|
-
}
|
|
452
|
-
|
|
453
|
-
const pointById: PolyPointMap = {}
|
|
454
|
-
|
|
455
|
-
points.forEach((p) => {
|
|
456
|
-
if (p.id) {
|
|
457
|
-
pointById[p.id] = p
|
|
458
|
-
}
|
|
459
|
-
})
|
|
460
|
-
|
|
461
|
-
while (openSet.length) {
|
|
462
|
-
let current: Point | undefined
|
|
463
|
-
let lowestFScore = Infinity
|
|
464
|
-
openSet.forEach((p: Point) => {
|
|
465
|
-
if (p.id && fScore[p.id] < lowestFScore) {
|
|
466
|
-
lowestFScore = fScore[p.id]
|
|
467
|
-
current = p
|
|
468
|
-
}
|
|
469
|
-
})
|
|
470
|
-
|
|
471
|
-
if (current === goal && goal.id) {
|
|
472
|
-
const pathPoints: Point[] = []
|
|
473
|
-
rebuildPath(pathPoints, pointById, cameFrom, goal.id)
|
|
474
|
-
return pathPoints
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
if (!current) {
|
|
478
|
-
return [start, goal]
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
removeClosePointFromOpenList(openSet, current)
|
|
482
|
-
closedSet.push(current)
|
|
483
|
-
|
|
484
|
-
getNextNeighborPoints(points, current, sBBox, tBBox).forEach((neighbor) => {
|
|
485
|
-
if (closedSet.indexOf(neighbor) !== -1) {
|
|
486
|
-
return
|
|
487
|
-
}
|
|
488
|
-
|
|
489
|
-
if (openSet.indexOf(neighbor) === -1) {
|
|
490
|
-
openSet.push(neighbor)
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
if (current?.id && neighbor?.id) {
|
|
494
|
-
// 修复:累计代价应基于 gScore[current] 而非 fScore[current]
|
|
495
|
-
const tentativeGScore =
|
|
496
|
-
(gScore[current.id] ?? 0) + estimateDistance(current, neighbor)
|
|
497
|
-
if (gScore[neighbor.id] && tentativeGScore >= gScore[neighbor.id]) {
|
|
498
|
-
return
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
cameFrom[neighbor.id] = current.id
|
|
502
|
-
gScore[neighbor.id] = tentativeGScore
|
|
503
|
-
fScore[neighbor.id] =
|
|
504
|
-
gScore[neighbor.id] +
|
|
505
|
-
heuristicCostEstimate(neighbor, goal, start, os, ot)
|
|
506
|
-
}
|
|
507
|
-
})
|
|
508
|
-
}
|
|
509
|
-
return [start, goal]
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
export const getBoxByOriginNode = (node: BaseNodeModel): BoxBounds => {
|
|
513
|
-
return getNodeBBox(node)
|
|
514
|
-
}
|
|
515
|
-
/**
|
|
516
|
-
* 去除共线冗余中间点,保持每条直线段仅保留两端点
|
|
517
|
-
* - 若三点在同一水平线或同一垂直线,移除中间点
|
|
518
|
-
*/
|
|
519
|
-
export const pointFilter = (points: Point[]): Point[] => {
|
|
520
|
-
let i = 1
|
|
521
|
-
while (i < points.length - 1) {
|
|
522
|
-
const pre = points[i - 1]
|
|
523
|
-
const current = points[i]
|
|
524
|
-
const next = points[i + 1]
|
|
525
|
-
if (
|
|
526
|
-
(pre.x === current.x && current.x === next.x) ||
|
|
527
|
-
(pre.y === current.y && current.y === next.y)
|
|
528
|
-
) {
|
|
529
|
-
points.splice(i, 1)
|
|
530
|
-
} else {
|
|
531
|
-
i++
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
return points
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
/**
|
|
538
|
-
* 计算折线点(正交候选点 + A* 路径)
|
|
539
|
-
* 步骤:
|
|
540
|
-
* 1) 取源/目标节点的扩展包围盒与相邻点 sPoint/tPoint
|
|
541
|
-
* 2) 若两个扩展盒重合,使用简单路径 getSimplePoints
|
|
542
|
-
* 3) 构造 lineBBox/sMixBBox/tMixBBox,并收集其角点与中心交点
|
|
543
|
-
* 4) 过滤掉落在两个扩展盒内部的点,形成 connectPoints
|
|
544
|
-
* 5) 以 sPoint/tPoint 为起止,用 A* 查找路径
|
|
545
|
-
* 6) 拼入原始 start/end,并用 pointFilter 去除冗余共线点
|
|
546
|
-
*/
|
|
547
|
-
export const getPolylinePoints = (
|
|
548
|
-
start: Point,
|
|
549
|
-
end: Point,
|
|
550
|
-
sNode: BaseNodeModel,
|
|
551
|
-
tNode: BaseNodeModel,
|
|
552
|
-
offset: number,
|
|
553
|
-
): Point[] => {
|
|
554
|
-
const sBBox = getBoxByOriginNode(sNode)
|
|
555
|
-
const tBBox = getBoxByOriginNode(tNode)
|
|
556
|
-
const sxBBox = getExpandedBBox(sBBox, offset)
|
|
557
|
-
const txBBox = getExpandedBBox(tBBox, offset)
|
|
558
|
-
const sPoint = getExpandedBBoxPoint(sxBBox, sBBox, start)
|
|
559
|
-
const tPoint = getExpandedBBoxPoint(txBBox, tBBox, end)
|
|
560
|
-
// 当加上offset后的bbox有重合,直接简单计算节点
|
|
561
|
-
if (isBboxOverLapping(sxBBox, txBBox)) {
|
|
562
|
-
const points = getSimplePoints(start, end, sPoint, tPoint)
|
|
563
|
-
return [start, sPoint, ...points, tPoint, end]
|
|
564
|
-
}
|
|
565
|
-
const lineBBox = getBBoxOfPoints([sPoint, tPoint])
|
|
566
|
-
const sMixBBox = mergeBBox(sxBBox, lineBBox)
|
|
567
|
-
const tMixBBox = mergeBBox(txBBox, lineBBox)
|
|
568
|
-
let connectPoints: Point[] = []
|
|
569
|
-
connectPoints = connectPoints.concat(getPointsFromBBox(sMixBBox))
|
|
570
|
-
connectPoints = connectPoints.concat(getPointsFromBBox(tMixBBox))
|
|
571
|
-
// 中心点
|
|
572
|
-
const centerPoint = {
|
|
573
|
-
x: (start.x + end.x) / 2,
|
|
574
|
-
y: (start.y + end.y) / 2,
|
|
575
|
-
}
|
|
576
|
-
// 获取中心点与其他box的交点
|
|
577
|
-
;[lineBBox, sMixBBox, tMixBBox].forEach((bbox: BoxBounds) => {
|
|
578
|
-
connectPoints = connectPoints.concat(
|
|
579
|
-
getBBoxCrossPointsByPoint(bbox, centerPoint).filter(
|
|
580
|
-
(p) => isPointOutsideBBox(p, sxBBox) && isPointOutsideBBox(p, txBBox),
|
|
581
|
-
),
|
|
582
|
-
)
|
|
583
|
-
})
|
|
584
|
-
// 与起止终点相邻的两的,在x,y方向上的交点,这四个点组成了矩形 。。。解释图中在不中这两个点,
|
|
585
|
-
;[
|
|
586
|
-
{
|
|
587
|
-
x: sPoint.x,
|
|
588
|
-
y: tPoint.y,
|
|
589
|
-
},
|
|
590
|
-
{
|
|
591
|
-
x: tPoint.x,
|
|
592
|
-
y: sPoint.y,
|
|
593
|
-
},
|
|
594
|
-
].forEach((p) => {
|
|
595
|
-
if (isPointOutsideBBox(p, sxBBox) && isPointOutsideBBox(p, txBBox)) {
|
|
596
|
-
connectPoints.push(p)
|
|
597
|
-
}
|
|
598
|
-
})
|
|
599
|
-
connectPoints.unshift(sPoint)
|
|
600
|
-
connectPoints.push(tPoint)
|
|
601
|
-
connectPoints = filterRepeatPoints(connectPoints)
|
|
602
|
-
// 路径查找-最关键的步骤
|
|
603
|
-
let pathPoints = pathFinder(
|
|
604
|
-
connectPoints,
|
|
605
|
-
sPoint,
|
|
606
|
-
tPoint,
|
|
607
|
-
sBBox,
|
|
608
|
-
tBBox,
|
|
609
|
-
start,
|
|
610
|
-
end,
|
|
611
|
-
)
|
|
612
|
-
pathPoints.unshift(start)
|
|
613
|
-
pathPoints.push(end)
|
|
614
|
-
// 删除一条直线上的多余节点
|
|
615
|
-
if (pathPoints.length > 2) {
|
|
616
|
-
pathPoints = pointFilter(pathPoints)
|
|
617
|
-
}
|
|
618
|
-
return filterRepeatPoints(pathPoints)
|
|
619
|
-
}
|
|
620
|
-
|
|
621
|
-
/**
|
|
622
|
-
* 获取折线中最长的一个线
|
|
623
|
-
* @param pointsList 多个点组成的数组
|
|
624
|
-
*/
|
|
625
|
-
export const getLongestEdge = (pointsList: Point[]): [Point, Point] => {
|
|
626
|
-
if (pointsList.length === 1) {
|
|
627
|
-
const [point] = pointsList
|
|
628
|
-
return [point, point]
|
|
629
|
-
} else {
|
|
630
|
-
let point1 = pointsList[0]
|
|
631
|
-
let point2 = pointsList[1]
|
|
632
|
-
let edgeLength = distance(point1.x, point1.y, point2.x, point2.y)
|
|
633
|
-
for (let i = 1; i < pointsList.length - 1; i++) {
|
|
634
|
-
const newPoint1 = pointsList[i]
|
|
635
|
-
const newPoint2 = pointsList[i + 1]
|
|
636
|
-
const newEdgeLength = distance(
|
|
637
|
-
newPoint1.x,
|
|
638
|
-
newPoint1.y,
|
|
639
|
-
newPoint2.x,
|
|
640
|
-
newPoint2.y,
|
|
641
|
-
)
|
|
642
|
-
if (newEdgeLength > edgeLength) {
|
|
643
|
-
edgeLength = newEdgeLength
|
|
644
|
-
point1 = newPoint1
|
|
645
|
-
point2 = newPoint2
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
return [point1, point2]
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
/* 线段是否在节点内部,被包含了 */
|
|
653
|
-
export const isSegmentsInNode = (
|
|
654
|
-
start: Point,
|
|
655
|
-
end: Point,
|
|
656
|
-
node: BaseNodeModel,
|
|
657
|
-
): boolean => {
|
|
658
|
-
const startInNode = isInNode(start, node)
|
|
659
|
-
const endInNode = isInNode(end, node)
|
|
660
|
-
return startInNode && endInNode
|
|
661
|
-
}
|
|
662
|
-
|
|
663
|
-
/* 线段是否与节点相交 */
|
|
664
|
-
export const isSegmentsCrossNode = (
|
|
665
|
-
start: Point,
|
|
666
|
-
end: Point,
|
|
667
|
-
node: BaseNodeModel,
|
|
668
|
-
): boolean => {
|
|
669
|
-
const startInNode = isInNode(start, node)
|
|
670
|
-
const endInNode = isInNode(end, node)
|
|
671
|
-
// bothInNode,线段两个端点都在节点内
|
|
672
|
-
const bothInNode = startInNode && endInNode
|
|
673
|
-
// cross,线段有端点在节点内
|
|
674
|
-
const inNode = startInNode || endInNode
|
|
675
|
-
// 有且只有一个点在节点内部
|
|
676
|
-
return !bothInNode && inNode
|
|
677
|
-
}
|
|
678
|
-
|
|
679
|
-
/* 获取线段在矩形内部的交点
|
|
680
|
-
*/
|
|
681
|
-
export const getCrossPointInRect = (
|
|
682
|
-
start: Point,
|
|
683
|
-
end: Point,
|
|
684
|
-
node: BaseNodeModel,
|
|
685
|
-
): Point | false | undefined => {
|
|
686
|
-
let crossSegments: [Point, Point] | undefined = undefined
|
|
687
|
-
const nodeBox = getNodeBBox(node)
|
|
688
|
-
const points = getPointsFromBBox(nodeBox)
|
|
689
|
-
for (let i = 0; i < points.length; i++) {
|
|
690
|
-
const isCross = isSegmentsIntersected(
|
|
691
|
-
start,
|
|
692
|
-
end,
|
|
693
|
-
points[i],
|
|
694
|
-
points[(i + 1) % points.length],
|
|
695
|
-
)
|
|
696
|
-
if (isCross) {
|
|
697
|
-
crossSegments = [points[i], points[(i + 1) % points.length]]
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
if (crossSegments) {
|
|
701
|
-
return getCrossPointOfLine(start, end, crossSegments[0], crossSegments[1])
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
/* 判断线段的方向 */
|
|
705
|
-
export const segmentDirection = (
|
|
706
|
-
start: Point,
|
|
707
|
-
end: Point,
|
|
708
|
-
): Direction | undefined => {
|
|
709
|
-
let direction: Direction | undefined = undefined
|
|
710
|
-
if (start.x === end.x) {
|
|
711
|
-
direction = SegmentDirection.VERTICAL
|
|
712
|
-
} else if (start.y === end.y) {
|
|
713
|
-
direction = SegmentDirection.HORIZONTAL
|
|
714
|
-
}
|
|
715
|
-
return direction
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
export const points2PointsList = (points: string): Point[] => {
|
|
719
|
-
const currentPositionList = points.split(' ')
|
|
720
|
-
const pointsList: LogicFlow.Position[] = []
|
|
721
|
-
currentPositionList &&
|
|
722
|
-
currentPositionList.forEach((item) => {
|
|
723
|
-
const [x, y] = item.split(',')
|
|
724
|
-
pointsList.push({
|
|
725
|
-
x: Number(x),
|
|
726
|
-
y: Number(y),
|
|
727
|
-
})
|
|
728
|
-
})
|
|
729
|
-
return pointsList
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
/**
|
|
733
|
-
* 当扩展 bbox 重合时的简化拐点计算
|
|
734
|
-
* - 根据起止段的方向(水平/垂直)插入 1~2 个中间点,避免折线重合与穿越
|
|
735
|
-
*/
|
|
736
|
-
export const getSimplePoints = (
|
|
737
|
-
start: Point,
|
|
738
|
-
end: Point,
|
|
739
|
-
sPoint: Point,
|
|
740
|
-
tPoint: Point,
|
|
741
|
-
): Point[] => {
|
|
742
|
-
const points: LogicFlow.Position[] = []
|
|
743
|
-
// start,sPoint的方向,水平或者垂直,即路径第一条线段的方向
|
|
744
|
-
const startDirection = segmentDirection(start, sPoint)!
|
|
745
|
-
// end,tPoint的方向,水平或者垂直,即路径最后一条条线段的方向
|
|
746
|
-
const endDirection = segmentDirection(end, tPoint)!
|
|
747
|
-
// 根据两条线段的方向作了计算,调整线段经验所得,非严格最优计算,能保证不出现折线
|
|
748
|
-
// 方向相同,添加两个点,两条平行线垂直距离一半的两个端点
|
|
749
|
-
if (startDirection === endDirection) {
|
|
750
|
-
if (start.y === sPoint.y) {
|
|
751
|
-
points.push({
|
|
752
|
-
x: sPoint.x,
|
|
753
|
-
y: (sPoint.y + tPoint.y) / 2,
|
|
754
|
-
})
|
|
755
|
-
points.push({
|
|
756
|
-
x: tPoint.x,
|
|
757
|
-
y: (sPoint.y + tPoint.y) / 2,
|
|
758
|
-
})
|
|
759
|
-
} else {
|
|
760
|
-
points.push({
|
|
761
|
-
x: (sPoint.x + tPoint.x) / 2,
|
|
762
|
-
y: sPoint.y,
|
|
763
|
-
})
|
|
764
|
-
points.push({
|
|
765
|
-
x: (sPoint.x + tPoint.x) / 2,
|
|
766
|
-
y: tPoint.y,
|
|
767
|
-
})
|
|
768
|
-
}
|
|
769
|
-
} else {
|
|
770
|
-
// 方向不同,添加一个点,保证不在当前线段上(会出现重合),且不能有折线
|
|
771
|
-
let point = {
|
|
772
|
-
x: sPoint.x,
|
|
773
|
-
y: tPoint.y,
|
|
774
|
-
}
|
|
775
|
-
const inStart = isInSegment(point, start, sPoint)
|
|
776
|
-
const inEnd = isInSegment(point, end, tPoint)
|
|
777
|
-
if (inStart || inEnd) {
|
|
778
|
-
point = {
|
|
779
|
-
x: tPoint.x,
|
|
780
|
-
y: sPoint.y,
|
|
781
|
-
}
|
|
782
|
-
} else {
|
|
783
|
-
const onStart = isOnLine(point, start, sPoint)
|
|
784
|
-
const onEnd = isOnLine(point, end, tPoint)
|
|
785
|
-
if (onStart && onEnd) {
|
|
786
|
-
point = {
|
|
787
|
-
x: tPoint.x,
|
|
788
|
-
y: sPoint.y,
|
|
789
|
-
}
|
|
790
|
-
}
|
|
791
|
-
}
|
|
792
|
-
points.push(point)
|
|
793
|
-
}
|
|
794
|
-
return points
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
const isOnLine = (point: Point, start: Point, end: Point) =>
|
|
798
|
-
(point.x === start.x && point.x === end.x) ||
|
|
799
|
-
(point.y === start.y && point.y === end.y)
|
|
800
|
-
|
|
801
|
-
/* 求字符串的字节长度 */
|
|
802
|
-
export const getBytesLength = (word: string): number => {
|
|
803
|
-
if (!word) {
|
|
804
|
-
return 0
|
|
805
|
-
}
|
|
806
|
-
let totalLength = 0
|
|
807
|
-
for (let i = 0; i < word.length; i++) {
|
|
808
|
-
const c = word.charCodeAt(i)
|
|
809
|
-
if (word.match(/[A-Z]/)) {
|
|
810
|
-
totalLength += 1.5
|
|
811
|
-
} else if ((c >= 0x0001 && c <= 0x007e) || (c >= 0xff60 && c <= 0xff9f)) {
|
|
812
|
-
totalLength += 1
|
|
813
|
-
} else {
|
|
814
|
-
totalLength += 2
|
|
815
|
-
}
|
|
816
|
-
}
|
|
817
|
-
return totalLength
|
|
818
|
-
}
|
|
819
|
-
|
|
820
|
-
/**
|
|
821
|
-
* Uses canvas.measureText to compute
|
|
822
|
-
* and return the width of the given text of given font in pixels.
|
|
823
|
-
* @param {String} text The text to be rendered.
|
|
824
|
-
* @param {String} font The css font descriptor
|
|
825
|
-
* that text is to be rendered with (e.g. "bold 14px verdana").
|
|
826
|
-
* @see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393
|
|
827
|
-
*/
|
|
828
|
-
let canvas: HTMLCanvasElement | undefined = undefined
|
|
829
|
-
export const getTextWidth = (text: string, font: string) => {
|
|
830
|
-
if (!canvas) {
|
|
831
|
-
canvas = document.createElement('canvas')
|
|
832
|
-
}
|
|
833
|
-
const context = canvas.getContext('2d')
|
|
834
|
-
context!.font = font
|
|
835
|
-
const metrics = context!.measureText(text)
|
|
836
|
-
return metrics.width
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
type AppendAttributesType = {
|
|
840
|
-
d: string
|
|
841
|
-
fill: string
|
|
842
|
-
stroke: string
|
|
843
|
-
strokeWidth: number
|
|
844
|
-
strokeDasharray: string
|
|
845
|
-
}
|
|
846
|
-
// 扩大边可点区域,获取边append的信息
|
|
847
|
-
export const getAppendAttributes = (
|
|
848
|
-
appendInfo: Record<'start' | 'end', Point>,
|
|
849
|
-
): AppendAttributesType => {
|
|
850
|
-
const { start, end } = appendInfo
|
|
851
|
-
let d: string
|
|
852
|
-
if (start.x === end.x && start.y === end.y) {
|
|
853
|
-
// 拖拽过程中会出现起终点重合的情况,这时候append无法计算
|
|
854
|
-
d = ''
|
|
855
|
-
} else {
|
|
856
|
-
const config = {
|
|
857
|
-
start,
|
|
858
|
-
end,
|
|
859
|
-
offset: 10,
|
|
860
|
-
verticalLength: 5,
|
|
861
|
-
}
|
|
862
|
-
const startPosition = getVerticalPointOfLine({
|
|
863
|
-
...config,
|
|
864
|
-
type: 'start',
|
|
865
|
-
})
|
|
866
|
-
const endPosition = getVerticalPointOfLine({
|
|
867
|
-
...config,
|
|
868
|
-
type: 'end',
|
|
869
|
-
})
|
|
870
|
-
d = `M${startPosition.leftX} ${startPosition.leftY}
|
|
871
|
-
L${startPosition.rightX} ${startPosition.rightY}
|
|
872
|
-
L${endPosition.rightX} ${endPosition.rightY}
|
|
873
|
-
L${endPosition.leftX} ${endPosition.leftY} z`
|
|
874
|
-
}
|
|
875
|
-
return {
|
|
876
|
-
d,
|
|
877
|
-
fill: 'transparent',
|
|
878
|
-
stroke: 'transparent',
|
|
879
|
-
strokeWidth: 1,
|
|
880
|
-
strokeDasharray: '4, 4',
|
|
881
|
-
}
|
|
882
|
-
}
|
|
883
|
-
export type IBezierControls = {
|
|
884
|
-
sNext: Point
|
|
885
|
-
ePre: Point
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
// bezier曲线
|
|
889
|
-
export const getBezierControlPoints = ({
|
|
890
|
-
start,
|
|
891
|
-
end,
|
|
892
|
-
sourceNode,
|
|
893
|
-
targetNode,
|
|
894
|
-
offset,
|
|
895
|
-
}: {
|
|
896
|
-
start: Point
|
|
897
|
-
end: Point
|
|
898
|
-
sourceNode: BaseNodeModel
|
|
899
|
-
targetNode: BaseNodeModel
|
|
900
|
-
offset: number
|
|
901
|
-
}): IBezierControls => {
|
|
902
|
-
const sBBox = getNodeBBox(sourceNode)
|
|
903
|
-
const tBBox = getNodeBBox(targetNode)
|
|
904
|
-
const sExpendBBox = getExpandedBBox(sBBox, offset)
|
|
905
|
-
const tExpendBBox = getExpandedBBox(tBBox, offset)
|
|
906
|
-
const sNext = getExpandedBBoxPoint(sExpendBBox, sBBox, start)
|
|
907
|
-
const ePre = getExpandedBBoxPoint(tExpendBBox, tBBox, end)
|
|
908
|
-
return {
|
|
909
|
-
sNext,
|
|
910
|
-
ePre,
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
export type IBezierPoints = {
|
|
915
|
-
start: Point
|
|
916
|
-
sNext: Point
|
|
917
|
-
ePre: Point
|
|
918
|
-
end: Point
|
|
919
|
-
}
|
|
920
|
-
// 根据bezier曲线path求出Points
|
|
921
|
-
export const getBezierPoints = (path: string): [Point, Point, Point, Point] => {
|
|
922
|
-
const list = path.replace(/M/g, '').replace(/C/g, ',').split(',')
|
|
923
|
-
const start = getBezierPoint(list[0])
|
|
924
|
-
const sNext = getBezierPoint(list[1])
|
|
925
|
-
const ePre = getBezierPoint(list[2])
|
|
926
|
-
const end = getBezierPoint(list[3])
|
|
927
|
-
return [start, sNext, ePre, end]
|
|
928
|
-
}
|
|
929
|
-
// 根据bezier曲线path求出Point坐标
|
|
930
|
-
const getBezierPoint = (positionStr: string): Point => {
|
|
931
|
-
const [x, y] = positionStr.replace(/(^\s*)/g, '').split(' ')
|
|
932
|
-
return {
|
|
933
|
-
x: +x,
|
|
934
|
-
y: +y,
|
|
935
|
-
}
|
|
936
|
-
}
|
|
937
|
-
// 根据bezier曲线path求出结束切线的两点坐标
|
|
938
|
-
export const getEndTangent = (
|
|
939
|
-
pointsList: Point[],
|
|
940
|
-
offset: number,
|
|
941
|
-
): [Point, Point] => {
|
|
942
|
-
// const bezierPoints = getBezierPoints(path)
|
|
943
|
-
const [p1, cp1, cp2, p2] = pointsList
|
|
944
|
-
const start = sampleCubic(p1, cp1, cp2, p2, offset)
|
|
945
|
-
return [start, pointsList[3]]
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
/**
|
|
949
|
-
* 获取移动边后,文本位置距离边上的最近的一点
|
|
950
|
-
* @param point 边上文本的位置
|
|
951
|
-
* @param points 边的各个拐点
|
|
952
|
-
* TODO: Label实验没问题后统一改成新的计算方式,把这个方法废弃
|
|
953
|
-
*/
|
|
954
|
-
export const getClosestPointOfPolyline = (
|
|
955
|
-
point: Point,
|
|
956
|
-
points: string,
|
|
957
|
-
): Point => {
|
|
958
|
-
const { x, y } = point
|
|
959
|
-
const pointsPosition = points2PointsList(points)
|
|
960
|
-
let minDistance = Number.MAX_SAFE_INTEGER
|
|
961
|
-
let crossPoint: Point
|
|
962
|
-
const segments: LineSegment[] = []
|
|
963
|
-
for (let i = 0; i < pointsPosition.length; i++) {
|
|
964
|
-
segments.push({
|
|
965
|
-
start: pointsPosition[i],
|
|
966
|
-
end: pointsPosition[(i + 1) % pointsPosition.length],
|
|
967
|
-
})
|
|
968
|
-
}
|
|
969
|
-
segments.forEach((item) => {
|
|
970
|
-
const { start, end } = item
|
|
971
|
-
// 若线段垂直,则crossPoint的横坐标与线段一致
|
|
972
|
-
if (start.x === end.x) {
|
|
973
|
-
const pointXY = {
|
|
974
|
-
x: start.x,
|
|
975
|
-
y,
|
|
976
|
-
}
|
|
977
|
-
const inSegment = isInSegment(pointXY, start, end)
|
|
978
|
-
if (inSegment) {
|
|
979
|
-
const currentDistance = Math.abs(start.x - x)
|
|
980
|
-
if (currentDistance < minDistance) {
|
|
981
|
-
minDistance = currentDistance
|
|
982
|
-
crossPoint = pointXY
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
} else if (start.y === end.y) {
|
|
986
|
-
const pointXY = {
|
|
987
|
-
x,
|
|
988
|
-
y: start.y,
|
|
989
|
-
}
|
|
990
|
-
const inSegment = isInSegment(pointXY, start, end)
|
|
991
|
-
if (inSegment) {
|
|
992
|
-
const currentDistance = Math.abs(start.y - y)
|
|
993
|
-
if (currentDistance < minDistance) {
|
|
994
|
-
minDistance = currentDistance
|
|
995
|
-
crossPoint = pointXY
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
}
|
|
999
|
-
})
|
|
1000
|
-
// 边界:只有一条线段时,沿线段移动节点,当文本超出边后,文本没有可供参考的线段
|
|
1001
|
-
if (!crossPoint!) {
|
|
1002
|
-
const { start, end } = segments[0]
|
|
1003
|
-
crossPoint = {
|
|
1004
|
-
x: start.x + (end.x - start.x) / 2,
|
|
1005
|
-
y: start.y + (end.y - start.y) / 2,
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
return crossPoint
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
|
-
// 规范边初始化数据
|
|
1012
|
-
export const pickEdgeConfig = (data: EdgeConfig): EdgeConfig =>
|
|
1013
|
-
pick(data, [
|
|
1014
|
-
'id',
|
|
1015
|
-
'type',
|
|
1016
|
-
'sourceNodeId',
|
|
1017
|
-
'sourceAnchorId',
|
|
1018
|
-
'targetNodeId',
|
|
1019
|
-
'targetAnchorId',
|
|
1020
|
-
'pointsList',
|
|
1021
|
-
'startPoint',
|
|
1022
|
-
'endPoint',
|
|
1023
|
-
'properties',
|
|
1024
|
-
])
|
|
1025
|
-
|
|
1026
|
-
export const twoPointDistance = (source: Position, target: Position) => {
|
|
1027
|
-
// fix: 修复坐标存在负值时计算错误的问题。
|
|
1028
|
-
// const source = {
|
|
1029
|
-
// x: p1.x,
|
|
1030
|
-
// y: Math.abs(p1.y),
|
|
1031
|
-
// }
|
|
1032
|
-
// const target = {
|
|
1033
|
-
// x: Math.abs(p2.x),
|
|
1034
|
-
// y: Math.abs(p2.y),
|
|
1035
|
-
// }
|
|
1036
|
-
return Math.sqrt((source.x - target.x) ** 2 + (source.y - target.y) ** 2)
|
|
1037
|
-
}
|
|
1038
|
-
|
|
1039
|
-
/**
|
|
1040
|
-
* 包装边生成函数
|
|
1041
|
-
* @param graphModel graph model
|
|
1042
|
-
* @param generator 用户自定义的边生成函数
|
|
1043
|
-
*/
|
|
1044
|
-
export function createEdgeGenerator(
|
|
1045
|
-
graphModel: GraphModel,
|
|
1046
|
-
generator?: Options.EdgeGeneratorType | unknown,
|
|
1047
|
-
): any {
|
|
1048
|
-
// TODO: 定义返回值类型,保证 GraphModel 中类型的正确性
|
|
1049
|
-
if (typeof generator !== 'function') {
|
|
1050
|
-
return (
|
|
1051
|
-
_sourceNode: NodeData,
|
|
1052
|
-
_targetNode: NodeData,
|
|
1053
|
-
currentEdge?: EdgeConfig,
|
|
1054
|
-
) => Object.assign({ type: graphModel.edgeType }, currentEdge)
|
|
1055
|
-
}
|
|
1056
|
-
return (
|
|
1057
|
-
sourceNode: NodeData,
|
|
1058
|
-
targetNode: NodeData,
|
|
1059
|
-
currentEdge?: EdgeConfig,
|
|
1060
|
-
) => {
|
|
1061
|
-
const result = generator(sourceNode, targetNode, currentEdge)
|
|
1062
|
-
// 无结果使用默认类型
|
|
1063
|
-
if (!result) return { type: graphModel.edgeType }
|
|
1064
|
-
if (typeof result === 'string') {
|
|
1065
|
-
return Object.assign({}, currentEdge, { type: result })
|
|
1066
|
-
}
|
|
1067
|
-
return Object.assign({ type: result }, currentEdge)
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
|
|
1071
|
-
// 获取 Svg 标签文案高度,自动换行
|
|
1072
|
-
export type IGetSvgTextSizeParams = {
|
|
1073
|
-
rows: string[]
|
|
1074
|
-
rowsLength: number
|
|
1075
|
-
fontSize: number
|
|
1076
|
-
}
|
|
1077
|
-
export const getSvgTextSize = ({
|
|
1078
|
-
rows,
|
|
1079
|
-
rowsLength,
|
|
1080
|
-
fontSize,
|
|
1081
|
-
}: IGetSvgTextSizeParams): LogicFlow.RectSize => {
|
|
1082
|
-
let longestBytes = 0
|
|
1083
|
-
forEach(rows, (row) => {
|
|
1084
|
-
const rowBytesLength = getBytesLength(row)
|
|
1085
|
-
longestBytes = rowBytesLength > longestBytes ? rowBytesLength : longestBytes
|
|
1086
|
-
})
|
|
1087
|
-
|
|
1088
|
-
// 背景框宽度,最长一行字节数/2 * fontsize + 2
|
|
1089
|
-
// 背景框宽度, 行数 * fontsize + 2
|
|
1090
|
-
return {
|
|
1091
|
-
width: Math.ceil(longestBytes / 2) * fontSize + fontSize / 4,
|
|
1092
|
-
height: rowsLength * (fontSize + 2) + fontSize / 4,
|
|
1093
|
-
}
|
|
1094
|
-
}
|