@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.
Files changed (137) hide show
  1. package/package.json +6 -1
  2. package/.turbo/turbo-build$colon$dev.log +0 -10
  3. package/.turbo/turbo-build.log +0 -33
  4. package/CHANGELOG.md +0 -1901
  5. package/__tests__/algorithm/egde.test.ts +0 -131
  6. package/__tests__/algorithm/index.test.ts +0 -74
  7. package/__tests__/algorithm/outline.test.ts +0 -43
  8. package/__tests__/bugs/1545-spec.test.ts +0 -42
  9. package/__tests__/event/event.test.ts +0 -22
  10. package/__tests__/history/history.test.ts +0 -28
  11. package/__tests__/logicflow.test.ts +0 -575
  12. package/__tests__/model/graphmodel.test.ts +0 -87
  13. package/__tests__/util/compatible.test.ts +0 -48
  14. package/__tests__/util/edge.test.ts +0 -224
  15. package/__tests__/util/geometry.test.ts +0 -14
  16. package/__tests__/util/graph.test.ts +0 -16
  17. package/__tests__/util/matrix.test.ts +0 -41
  18. package/__tests__/util/node.test.ts +0 -68
  19. package/__tests__/util/sampling.test.ts +0 -12
  20. package/__tests__/util/vector.test.ts +0 -50
  21. package/__tests__/util/zIndex.test.ts +0 -10
  22. package/src/LogicFlow.tsx +0 -2017
  23. package/src/algorithm/edge.ts +0 -67
  24. package/src/algorithm/index.ts +0 -70
  25. package/src/algorithm/outline.ts +0 -77
  26. package/src/algorithm/rotate.ts +0 -55
  27. package/src/common/drag.ts +0 -219
  28. package/src/common/history.ts +0 -108
  29. package/src/common/index.ts +0 -6
  30. package/src/common/keyboard.ts +0 -108
  31. package/src/common/matrix.ts +0 -122
  32. package/src/common/vector.ts +0 -93
  33. package/src/constant/index.ts +0 -179
  34. package/src/constant/theme.ts +0 -708
  35. package/src/event/event.md +0 -66
  36. package/src/event/eventArgs.ts +0 -643
  37. package/src/event/eventEmitter.ts +0 -156
  38. package/src/history/index.ts +0 -119
  39. package/src/index.less +0 -1
  40. package/src/index.ts +0 -26
  41. package/src/keyboard/index.ts +0 -112
  42. package/src/keyboard/shortcut.ts +0 -200
  43. package/src/model/BaseModel.ts +0 -250
  44. package/src/model/EditConfigModel.ts +0 -334
  45. package/src/model/GraphModel.ts +0 -1824
  46. package/src/model/NestedTransformModel.ts +0 -121
  47. package/src/model/SnaplineModel.ts +0 -256
  48. package/src/model/TransformModel.ts +0 -258
  49. package/src/model/edge/BaseEdgeModel.ts +0 -785
  50. package/src/model/edge/BezierEdgeModel.ts +0 -197
  51. package/src/model/edge/LineEdgeModel.ts +0 -36
  52. package/src/model/edge/PolylineEdgeModel.ts +0 -817
  53. package/src/model/edge/index.ts +0 -4
  54. package/src/model/index.ts +0 -9
  55. package/src/model/node/BaseNodeModel.ts +0 -959
  56. package/src/model/node/CircleNodeModel.ts +0 -91
  57. package/src/model/node/DiamondNodeModel.ts +0 -132
  58. package/src/model/node/EllipseNodeModel.ts +0 -98
  59. package/src/model/node/HtmlNodeModel.ts +0 -64
  60. package/src/model/node/PolygonNodeModel.ts +0 -152
  61. package/src/model/node/RectNodeModel.ts +0 -69
  62. package/src/model/node/TextNodeModel.ts +0 -54
  63. package/src/model/node/index.ts +0 -8
  64. package/src/options.ts +0 -150
  65. package/src/style/index.less +0 -262
  66. package/src/style/raw.ts +0 -221
  67. package/src/tool/MultipleSelectTool.tsx +0 -140
  68. package/src/tool/TextEditTool.tsx +0 -193
  69. package/src/tool/index.ts +0 -101
  70. package/src/typings.d.ts +0 -5
  71. package/src/util/animation.ts +0 -29
  72. package/src/util/browser.ts +0 -4
  73. package/src/util/compatible.ts +0 -15
  74. package/src/util/drag.ts +0 -219
  75. package/src/util/edge.ts +0 -1094
  76. package/src/util/geometry.ts +0 -154
  77. package/src/util/graph.ts +0 -46
  78. package/src/util/index.ts +0 -17
  79. package/src/util/matrix.ts +0 -129
  80. package/src/util/mobx.ts +0 -23
  81. package/src/util/node.ts +0 -543
  82. package/src/util/raf.ts +0 -28
  83. package/src/util/resize.ts +0 -606
  84. package/src/util/sampling.ts +0 -85
  85. package/src/util/theme.ts +0 -84
  86. package/src/util/uuid.ts +0 -26
  87. package/src/util/vector.ts +0 -93
  88. package/src/util/zIndex.ts +0 -6
  89. package/src/view/Anchor.tsx +0 -462
  90. package/src/view/Control.tsx +0 -510
  91. package/src/view/Graph.tsx +0 -141
  92. package/src/view/Rotate.tsx +0 -113
  93. package/src/view/behavior/dnd.ts +0 -162
  94. package/src/view/behavior/index.ts +0 -2
  95. package/src/view/behavior/snapline.ts +0 -16
  96. package/src/view/edge/AdjustPoint.tsx +0 -425
  97. package/src/view/edge/Arrow.tsx +0 -54
  98. package/src/view/edge/BaseEdge.tsx +0 -660
  99. package/src/view/edge/BezierEdge.tsx +0 -101
  100. package/src/view/edge/LineEdge.tsx +0 -81
  101. package/src/view/edge/PolylineEdge.tsx +0 -311
  102. package/src/view/edge/index.ts +0 -6
  103. package/src/view/index.ts +0 -8
  104. package/src/view/node/BaseNode.tsx +0 -585
  105. package/src/view/node/CircleNode.tsx +0 -21
  106. package/src/view/node/DiamondNode.tsx +0 -24
  107. package/src/view/node/EllipseNode.tsx +0 -22
  108. package/src/view/node/HtmlNode.tsx +0 -112
  109. package/src/view/node/PolygonNode.tsx +0 -28
  110. package/src/view/node/RectNode.tsx +0 -30
  111. package/src/view/node/TextNode.tsx +0 -39
  112. package/src/view/node/index.ts +0 -8
  113. package/src/view/overlay/BackgroundOverlay.tsx +0 -34
  114. package/src/view/overlay/BezierAdjustOverlay.tsx +0 -150
  115. package/src/view/overlay/CanvasOverlay.tsx +0 -290
  116. package/src/view/overlay/Grid.tsx +0 -319
  117. package/src/view/overlay/ModificationOverlay.tsx +0 -31
  118. package/src/view/overlay/OutlineOverlay.tsx +0 -158
  119. package/src/view/overlay/SnaplineOverlay.tsx +0 -44
  120. package/src/view/overlay/ToolOverlay.tsx +0 -65
  121. package/src/view/overlay/getTransformHoc.tsx +0 -50
  122. package/src/view/overlay/gridConfig.ts +0 -103
  123. package/src/view/overlay/index.ts +0 -8
  124. package/src/view/shape/Circle.tsx +0 -41
  125. package/src/view/shape/Ellipse.tsx +0 -42
  126. package/src/view/shape/Line.tsx +0 -39
  127. package/src/view/shape/Path.tsx +0 -22
  128. package/src/view/shape/Polygon.tsx +0 -54
  129. package/src/view/shape/Polyline.tsx +0 -31
  130. package/src/view/shape/Rect.tsx +0 -44
  131. package/src/view/shape/Text.tsx +0 -168
  132. package/src/view/shape/index.ts +0 -8
  133. package/src/view/text/BaseText.tsx +0 -134
  134. package/src/view/text/LineText.tsx +0 -168
  135. package/src/view/text/index.ts +0 -2
  136. package/stats.html +0 -4842
  137. 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
- }