@logicflow/layout 2.1.0-alpha.2 → 2.1.0-alpha.4

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 (47) hide show
  1. package/.turbo/turbo-build.log +7 -6
  2. package/CHANGELOG.md +15 -0
  3. package/README.md +9 -2
  4. package/dist/index.min.js +1 -1
  5. package/dist/index.min.js.map +1 -1
  6. package/es/{dagre.d.ts → dagre/index.d.ts} +18 -20
  7. package/es/dagre/index.js +126 -0
  8. package/es/dagre/index.js.map +1 -0
  9. package/es/elkLayout/config.d.ts +26 -0
  10. package/es/elkLayout/config.js +27 -0
  11. package/es/elkLayout/config.js.map +1 -0
  12. package/es/elkLayout/index.d.ts +107 -0
  13. package/es/elkLayout/index.js +187 -0
  14. package/es/elkLayout/index.js.map +1 -0
  15. package/es/index.d.ts +1 -0
  16. package/es/index.js +1 -0
  17. package/es/index.js.map +1 -1
  18. package/es/utils/processEdge.d.ts +3 -0
  19. package/es/utils/processEdge.js +479 -0
  20. package/es/utils/processEdge.js.map +1 -0
  21. package/lib/{dagre.d.ts → dagre/index.d.ts} +18 -20
  22. package/lib/dagre/index.js +152 -0
  23. package/lib/dagre/index.js.map +1 -0
  24. package/lib/elkLayout/config.d.ts +26 -0
  25. package/lib/elkLayout/config.js +30 -0
  26. package/lib/elkLayout/config.js.map +1 -0
  27. package/lib/elkLayout/index.d.ts +107 -0
  28. package/lib/elkLayout/index.js +193 -0
  29. package/lib/elkLayout/index.js.map +1 -0
  30. package/lib/index.d.ts +1 -0
  31. package/lib/index.js +1 -0
  32. package/lib/index.js.map +1 -1
  33. package/lib/utils/processEdge.d.ts +3 -0
  34. package/lib/utils/processEdge.js +483 -0
  35. package/lib/utils/processEdge.js.map +1 -0
  36. package/package.json +3 -2
  37. package/src/dagre/index.ts +177 -0
  38. package/src/elkLayout/config.ts +26 -0
  39. package/src/elkLayout/index.ts +255 -0
  40. package/src/index.ts +2 -0
  41. package/src/utils/processEdge.ts +585 -0
  42. package/stats.html +1 -1
  43. package/es/dagre.js +0 -376
  44. package/es/dagre.js.map +0 -1
  45. package/lib/dagre.js +0 -402
  46. package/lib/dagre.js.map +0 -1
  47. package/src/dagre.ts +0 -438
@@ -0,0 +1,585 @@
1
+ import LogicFlow, { BaseEdgeModel } from '@logicflow/core'
2
+ import NodeConfig = LogicFlow.NodeConfig
3
+ import EdgeConfig = LogicFlow.EdgeConfig
4
+ import Direction = LogicFlow.Direction
5
+ import Point = LogicFlow.Point
6
+
7
+ type BaseNodeData = {
8
+ x: number
9
+ y: number
10
+ width: number
11
+ height: number
12
+ }
13
+
14
+ type IBezierControls = {
15
+ sNext: Point
16
+ ePre: Point
17
+ }
18
+
19
+ // 定义边界数据结构,左上坐标 + 右下坐标定位一个矩形
20
+ type BoxBoundsPoint = {
21
+ minX: number // Left Top X
22
+ minY: number // Left Top Y
23
+ maxX: number // Right Bottom X
24
+ maxY: number // Right Bottom Y
25
+ }
26
+
27
+ type NodeBBox = {
28
+ x: number
29
+ y: number
30
+ width: number
31
+ height: number
32
+ centerX: number
33
+ centerY: number
34
+ } & BoxBoundsPoint
35
+
36
+ interface BoxBounds extends BoxBoundsPoint {
37
+ x: number
38
+ y: number
39
+ width: number
40
+ height: number
41
+ centerX: number
42
+ centerY: number
43
+ }
44
+
45
+ enum SegmentDirection {
46
+ HORIZONTAL = 'horizontal',
47
+ VERTICAL = 'vertical',
48
+ }
49
+
50
+ export function processEdges(
51
+ lf: LogicFlow,
52
+ rankdir: string | undefined,
53
+ isDefaultAnchor: boolean | undefined,
54
+ edges: BaseEdgeModel[],
55
+ newNodes: NodeConfig[],
56
+ ) {
57
+ const newEdges: EdgeConfig[] = []
58
+ // 处理边的路径和锚点
59
+ edges.forEach((edgeModel) => {
60
+ const lfEdge: EdgeConfig = edgeModel.getData()
61
+ if (!lfEdge) {
62
+ throw new Error(`布局错误:找不到ID为 ${edgeModel.id} 的边`)
63
+ }
64
+
65
+ if (!isDefaultAnchor) {
66
+ // 自定义锚点,不调整边的关联锚点,只清除路径相关数据,让LogicFlow自动计算
67
+ delete lfEdge.pointsList
68
+ delete lfEdge.startPoint
69
+ delete lfEdge.endPoint
70
+ if (typeof lfEdge.text === 'object' && lfEdge.text && lfEdge.text.value) {
71
+ lfEdge.text = lfEdge.text.value
72
+ }
73
+ } else {
74
+ // 默认锚点,重新计算路径以及边的起点和终点(节点默认锚点为上下左右)
75
+ delete lfEdge.pointsList
76
+ delete lfEdge.startPoint
77
+ delete lfEdge.endPoint
78
+ delete lfEdge.sourceAnchorId
79
+ delete lfEdge.targetAnchorId
80
+
81
+ lfEdge.pointsList = calcPointsList(lf, rankdir, edgeModel, newNodes)
82
+
83
+ if (lfEdge.pointsList) {
84
+ // 设置边的起点和终点
85
+ const first = lfEdge.pointsList[0]
86
+ const last = lfEdge.pointsList[lfEdge.pointsList.length - 1]
87
+ lfEdge.startPoint = { x: first.x, y: first.y }
88
+ lfEdge.endPoint = { x: last.x, y: last.y }
89
+ }
90
+ if (typeof lfEdge.text === 'object' && lfEdge.text && lfEdge.text.value) {
91
+ // 保留文本内容
92
+ lfEdge.text = lfEdge.text.value
93
+ }
94
+ }
95
+
96
+ newEdges.push(lfEdge)
97
+ })
98
+ return newEdges
99
+ }
100
+
101
+ /**
102
+ * 优化折线路径点,移除冗余点
103
+ * @param points - 原始路径点数组
104
+ * @returns 优化后的路径点数组
105
+ */
106
+ function pointFilter(points: Point[]): Point[] {
107
+ const allPoints = [...points] // 创建副本避免修改原始数据
108
+ let i = 1
109
+
110
+ // 删除直线上的中间点(保持路径简洁)
111
+ while (i < allPoints.length - 1) {
112
+ const pre = allPoints[i - 1]
113
+ const current = allPoints[i]
114
+ const next = allPoints[i + 1]
115
+
116
+ // 如果三点共线,移除中间点
117
+ if (
118
+ (pre.x === current.x && current.x === next.x) || // 垂直线上的点
119
+ (pre.y === current.y && current.y === next.y)
120
+ ) {
121
+ // 水平线上的点
122
+ allPoints.splice(i, 1)
123
+ } else {
124
+ i++
125
+ }
126
+ }
127
+
128
+ return allPoints
129
+ }
130
+
131
+ /**
132
+ * 计算边的折线路径点
133
+ * @param model - 边模型
134
+ * @param nodes - 节点数据数组
135
+ * @returns 计算后的路径点数组,如果无法计算则返回undefined
136
+ */
137
+ function calcPointsList(
138
+ lf: LogicFlow,
139
+ rankdir: string | undefined,
140
+ model: BaseEdgeModel,
141
+ nodes: NodeConfig[],
142
+ ): Point[] | undefined {
143
+ const pointsList: Point[] = []
144
+
145
+ // 获取源节点和目标节点的模型与布局数据
146
+ const sourceNodeModel = lf.getNodeModelById(model.sourceNodeId)
147
+ const targetNodeModel = lf.getNodeModelById(model.targetNodeId)
148
+ const newSourceNodeData = nodes.find(
149
+ (node: NodeConfig) => node.id === model.sourceNodeId,
150
+ )
151
+ const newTargetNodeData = nodes.find(
152
+ (node: NodeConfig) => node.id === model.targetNodeId,
153
+ )
154
+
155
+ // 数据验证
156
+ if (
157
+ !sourceNodeModel ||
158
+ !targetNodeModel ||
159
+ !newSourceNodeData ||
160
+ !newTargetNodeData
161
+ ) {
162
+ return undefined
163
+ }
164
+
165
+ // 折线偏移量(用于创建合适的转折点)
166
+ const offset = Number(model.offset) || 50
167
+
168
+ // 处理从左到右(LR)布局的边路径,折线
169
+ if (rankdir === 'LR' && model.modelType === 'polyline-edge') {
170
+ // 正向连线:源节点在目标节点左侧
171
+ if (newSourceNodeData.x <= newTargetNodeData.x) {
172
+ // 从源节点右侧中心出发
173
+ pointsList.push({
174
+ x: newSourceNodeData.x + sourceNodeModel.width / 2,
175
+ y: newSourceNodeData.y,
176
+ })
177
+ // 向右延伸一段距离
178
+ pointsList.push({
179
+ x: newSourceNodeData.x + sourceNodeModel.width / 2 + offset,
180
+ y: newSourceNodeData.y,
181
+ })
182
+ // 垂直移动到目标节点的高度
183
+ pointsList.push({
184
+ x: newSourceNodeData.x + sourceNodeModel.width / 2 + offset,
185
+ y: newTargetNodeData.y,
186
+ })
187
+ // 连接到目标节点左侧中心
188
+ pointsList.push({
189
+ x: newTargetNodeData.x - targetNodeModel.width / 2,
190
+ y: newTargetNodeData.y,
191
+ })
192
+
193
+ return pointFilter(pointsList)
194
+ }
195
+
196
+ // 反向连线:源节点在目标节点右侧
197
+ if (newSourceNodeData.x > newTargetNodeData.x) {
198
+ // 根据节点相对Y轴位置选择不同路径
199
+ if (newSourceNodeData.y >= newTargetNodeData.y) {
200
+ // 源节点在目标节点的右下方,从源节点上方出发
201
+ pointsList.push({
202
+ x: newSourceNodeData.x,
203
+ y: newSourceNodeData.y + sourceNodeModel.height / 2,
204
+ })
205
+ pointsList.push({
206
+ x: newSourceNodeData.x,
207
+ y: newSourceNodeData.y + sourceNodeModel.height / 2 + offset,
208
+ })
209
+ pointsList.push({
210
+ x: newTargetNodeData.x,
211
+ y: newSourceNodeData.y + sourceNodeModel.height / 2 + offset,
212
+ })
213
+ pointsList.push({
214
+ x: newTargetNodeData.x,
215
+ y: newTargetNodeData.y + targetNodeModel.height / 2,
216
+ })
217
+ } else {
218
+ // 源节点在目标节点的右上方,从源节点下方出发
219
+ pointsList.push({
220
+ x: newSourceNodeData.x,
221
+ y: newSourceNodeData.y - sourceNodeModel.height / 2,
222
+ })
223
+ pointsList.push({
224
+ x: newSourceNodeData.x,
225
+ y: newSourceNodeData.y - sourceNodeModel.height / 2 - offset,
226
+ })
227
+ pointsList.push({
228
+ x: newTargetNodeData.x,
229
+ y: newSourceNodeData.y - sourceNodeModel.height / 2 - offset,
230
+ })
231
+ pointsList.push({
232
+ x: newTargetNodeData.x,
233
+ y: newTargetNodeData.y - targetNodeModel.height / 2,
234
+ })
235
+ }
236
+
237
+ return pointFilter(pointsList)
238
+ }
239
+ }
240
+
241
+ // 处理从上到下(TB)布局的边路径, 折线
242
+ if (rankdir === 'TB' && model.modelType === 'polyline-edge') {
243
+ // 正向连线:源节点在目标节点上方
244
+ if (newSourceNodeData.y <= newTargetNodeData.y) {
245
+ // 从源节点底部中心出发
246
+ pointsList.push({
247
+ x: newSourceNodeData.x,
248
+ y: newSourceNodeData.y + sourceNodeModel.height / 2,
249
+ })
250
+ // 向下延伸一段距离
251
+ pointsList.push({
252
+ x: newSourceNodeData.x,
253
+ y: newSourceNodeData.y + sourceNodeModel.height / 2 + offset,
254
+ })
255
+ // 水平移动到目标节点的位置
256
+ pointsList.push({
257
+ x: newTargetNodeData.x,
258
+ y: newSourceNodeData.y + sourceNodeModel.height / 2 + offset,
259
+ })
260
+ // 连接到目标节点顶部中心
261
+ pointsList.push({
262
+ x: newTargetNodeData.x,
263
+ y: newTargetNodeData.y - targetNodeModel.height / 2,
264
+ })
265
+
266
+ return pointFilter(pointsList)
267
+ }
268
+
269
+ // 反向连线:源节点在目标节点下方
270
+ if (newSourceNodeData.y > newTargetNodeData.y) {
271
+ if (newSourceNodeData.x >= newTargetNodeData.x) {
272
+ // 源节点在目标节点右下方,从源节点右侧出发
273
+ pointsList.push({
274
+ x: newSourceNodeData.x + sourceNodeModel.width / 2,
275
+ y: newSourceNodeData.y,
276
+ })
277
+ pointsList.push({
278
+ x: newSourceNodeData.x + sourceNodeModel.width / 2 + offset,
279
+ y: newSourceNodeData.y,
280
+ })
281
+ pointsList.push({
282
+ x: newSourceNodeData.x + sourceNodeModel.width / 2 + offset,
283
+ y: newTargetNodeData.y,
284
+ })
285
+ pointsList.push({
286
+ x: newTargetNodeData.x + targetNodeModel.width / 2,
287
+ y: newTargetNodeData.y,
288
+ })
289
+ } else {
290
+ // 源节点在目标节点左下方,从源节点左侧出发
291
+ pointsList.push({
292
+ x: newSourceNodeData.x - sourceNodeModel.width / 2,
293
+ y: newSourceNodeData.y,
294
+ })
295
+ pointsList.push({
296
+ x: newSourceNodeData.x - sourceNodeModel.width / 2 - offset,
297
+ y: newSourceNodeData.y,
298
+ })
299
+ pointsList.push({
300
+ x: newSourceNodeData.x - sourceNodeModel.width / 2 - offset,
301
+ y: newTargetNodeData.y,
302
+ })
303
+ pointsList.push({
304
+ x: newTargetNodeData.x - targetNodeModel.width / 2,
305
+ y: newTargetNodeData.y,
306
+ })
307
+ }
308
+
309
+ return pointFilter(pointsList)
310
+ }
311
+ }
312
+
313
+ // 处理从左到右(LR)布局的边路径, 贝塞尔曲线
314
+ if (rankdir === 'LR' && model.modelType === 'bezier-edge') {
315
+ const startPoint = {
316
+ x: newSourceNodeData.x + sourceNodeModel.width / 2,
317
+ y: newSourceNodeData.y,
318
+ }
319
+ const endPoint = {
320
+ x: newTargetNodeData.x - targetNodeModel.width / 2,
321
+ y: newTargetNodeData.y,
322
+ }
323
+ let sNext: Point = { x: 0, y: 0 }
324
+ let ePre: Point = { x: 0, y: 0 }
325
+ // 正向连线:源节点在目标节点左侧
326
+ if (newSourceNodeData.x < newTargetNodeData.x) {
327
+ const result = getBezierControlPoints({
328
+ start: startPoint,
329
+ end: endPoint,
330
+ sourceNode: {
331
+ x: newSourceNodeData.x,
332
+ y: newSourceNodeData.y,
333
+ width: sourceNodeModel.width,
334
+ height: sourceNodeModel.height,
335
+ },
336
+ targetNode: {
337
+ x: newTargetNodeData.x,
338
+ y: newTargetNodeData.y,
339
+ width: targetNodeModel.width,
340
+ height: targetNodeModel.height,
341
+ },
342
+ offset,
343
+ })
344
+ sNext = result.sNext
345
+ ePre = result.ePre
346
+ }
347
+ // 反向连线:源节点在目标节点右侧
348
+ if (newSourceNodeData.x > newTargetNodeData.x) {
349
+ if (newSourceNodeData.y >= newTargetNodeData.y) {
350
+ // 源节点在目标节点的右下方
351
+ sNext = {
352
+ x:
353
+ newSourceNodeData.x +
354
+ sourceNodeModel.width / 2 +
355
+ offset +
356
+ targetNodeModel.width / 2,
357
+ y:
358
+ newSourceNodeData.y +
359
+ sourceNodeModel.height +
360
+ targetNodeModel.height,
361
+ }
362
+ ePre = {
363
+ x:
364
+ newTargetNodeData.x -
365
+ targetNodeModel.width / 2 -
366
+ offset -
367
+ sourceNodeModel.width / 2,
368
+ y:
369
+ newTargetNodeData.y +
370
+ sourceNodeModel.height +
371
+ targetNodeModel.height,
372
+ }
373
+ } else {
374
+ // 源节点在目标节点的右上方
375
+ sNext = {
376
+ x:
377
+ newSourceNodeData.x +
378
+ sourceNodeModel.width / 2 +
379
+ offset +
380
+ targetNodeModel.width / 2,
381
+ y:
382
+ newSourceNodeData.y -
383
+ sourceNodeModel.height -
384
+ targetNodeModel.height,
385
+ }
386
+ ePre = {
387
+ x:
388
+ newTargetNodeData.x -
389
+ targetNodeModel.width / 2 -
390
+ offset -
391
+ sourceNodeModel.width / 2,
392
+ y:
393
+ newTargetNodeData.y -
394
+ sourceNodeModel.height -
395
+ targetNodeModel.height,
396
+ }
397
+ }
398
+ }
399
+ pointsList.push(startPoint, sNext, ePre, endPoint)
400
+ return pointsList
401
+ }
402
+
403
+ // 处理从上到下(TB)布局的边路径, 贝塞尔曲线
404
+ if (rankdir === 'TB' && model.modelType === 'bezier-edge') {
405
+ const startPoint = {
406
+ x: newSourceNodeData.x,
407
+ y: newSourceNodeData.y + sourceNodeModel.height / 2,
408
+ }
409
+ const endPoint = {
410
+ x: newTargetNodeData.x,
411
+ y: newTargetNodeData.y - targetNodeModel.height / 2,
412
+ }
413
+ let sNext: Point = { x: 0, y: 0 }
414
+ let ePre: Point = { x: 0, y: 0 }
415
+ if (newSourceNodeData.y <= newTargetNodeData.y) {
416
+ // 正向连线:源节点在目标节点上方
417
+ const result = getBezierControlPoints({
418
+ start: startPoint,
419
+ end: endPoint,
420
+ sourceNode: {
421
+ x: newSourceNodeData.x,
422
+ y: newSourceNodeData.y,
423
+ width: sourceNodeModel.width,
424
+ height: sourceNodeModel.height,
425
+ },
426
+ targetNode: {
427
+ x: newTargetNodeData.x,
428
+ y: newTargetNodeData.y,
429
+ width: targetNodeModel.width,
430
+ height: targetNodeModel.height,
431
+ },
432
+ offset,
433
+ })
434
+ sNext = result.sNext
435
+ ePre = result.ePre
436
+ }
437
+ if (newSourceNodeData.y > newTargetNodeData.y) {
438
+ // 反向连线:源节点在目标节点下方
439
+ if (newSourceNodeData.x >= newTargetNodeData.x) {
440
+ // 源节点在目标节点右下方
441
+ sNext = {
442
+ x:
443
+ newSourceNodeData.x +
444
+ sourceNodeModel.width / 2 +
445
+ offset +
446
+ targetNodeModel.width / 2,
447
+ y:
448
+ newSourceNodeData.y +
449
+ sourceNodeModel.height +
450
+ targetNodeModel.height,
451
+ }
452
+ ePre = {
453
+ x:
454
+ newTargetNodeData.x +
455
+ targetNodeModel.width / 2 +
456
+ offset +
457
+ sourceNodeModel.width / 2,
458
+ y:
459
+ newTargetNodeData.y -
460
+ sourceNodeModel.height -
461
+ targetNodeModel.height,
462
+ }
463
+ } else {
464
+ // 源节点在目标节点左下方
465
+ sNext = {
466
+ x:
467
+ newSourceNodeData.x -
468
+ sourceNodeModel.width / 2 -
469
+ offset -
470
+ targetNodeModel.width / 2,
471
+ y:
472
+ newSourceNodeData.y +
473
+ sourceNodeModel.height +
474
+ targetNodeModel.height,
475
+ }
476
+ ePre = {
477
+ x:
478
+ newTargetNodeData.x -
479
+ targetNodeModel.width / 2 -
480
+ offset -
481
+ sourceNodeModel.width / 2,
482
+ y:
483
+ newTargetNodeData.y -
484
+ sourceNodeModel.height -
485
+ targetNodeModel.height,
486
+ }
487
+ }
488
+ }
489
+ pointsList.push(startPoint, sNext, ePre, endPoint)
490
+ return pointsList
491
+ }
492
+
493
+ // 无法确定路径时返回undefined,让LogicFlow自行处理
494
+ return undefined
495
+ }
496
+
497
+ // bezier曲线
498
+ const getBezierControlPoints = ({
499
+ start,
500
+ end,
501
+ sourceNode,
502
+ targetNode,
503
+ offset,
504
+ }: {
505
+ start: Point
506
+ end: Point
507
+ sourceNode: BaseNodeData
508
+ targetNode: BaseNodeData
509
+ offset: number
510
+ }): IBezierControls => {
511
+ const sBBox = getNodeBBox(sourceNode)
512
+ const tBBox = getNodeBBox(targetNode)
513
+ const sExpendBBox = getExpandedBBox(sBBox, offset)
514
+ const tExpendBBox = getExpandedBBox(tBBox, offset)
515
+ const sNext = getExpandedBBoxPoint(sExpendBBox, sBBox, start)
516
+ const ePre = getExpandedBBoxPoint(tExpendBBox, tBBox, end)
517
+ return {
518
+ sNext,
519
+ ePre,
520
+ }
521
+ }
522
+
523
+ /* 获取节点bbox */
524
+ const getNodeBBox = (node: BaseNodeData): NodeBBox => {
525
+ const { x, y, width, height } = node
526
+ return {
527
+ minX: x - width / 2,
528
+ minY: y - height / 2,
529
+ maxX: x + width / 2,
530
+ maxY: y + height / 2,
531
+ x,
532
+ y,
533
+ width,
534
+ height,
535
+ centerX: x,
536
+ centerY: y,
537
+ }
538
+ }
539
+
540
+ /* 扩展的bbox,保证起始点的下一个点一定在node的垂直方向,不会与线重合, offset是点与线的垂直距离 */
541
+ const getExpandedBBox = (bbox: BoxBounds, offset: number): BoxBounds => {
542
+ if (bbox.width === 0 && bbox.height === 0) {
543
+ return bbox
544
+ }
545
+ return {
546
+ x: bbox.x,
547
+ y: bbox.y,
548
+ centerX: bbox.centerX,
549
+ centerY: bbox.centerY,
550
+ minX: bbox.minX - offset,
551
+ minY: bbox.minY - offset,
552
+ maxX: bbox.maxX + offset,
553
+ maxY: bbox.maxY + offset,
554
+ height: bbox.height + 2 * offset,
555
+ width: bbox.width + 2 * offset,
556
+ }
557
+ }
558
+
559
+ /* 判断点与中心点边的方向:是否水平,true水平,false垂直 */
560
+ const pointDirection = (point: Point, bbox: BoxBounds): Direction => {
561
+ const dx = Math.abs(point.x - bbox.centerX)
562
+ const dy = Math.abs(point.y - bbox.centerY)
563
+ return dx / bbox.width > dy / bbox.height
564
+ ? SegmentDirection.HORIZONTAL
565
+ : SegmentDirection.VERTICAL
566
+ }
567
+
568
+ /* 获取扩展图形上的点,即起始终点相邻的点,上一个或者下一个节点 */
569
+ const getExpandedBBoxPoint = (
570
+ expendBBox: BoxBounds,
571
+ bbox: BoxBounds,
572
+ point: Point,
573
+ ): Point => {
574
+ const direction = pointDirection(point, bbox)
575
+ if (direction === SegmentDirection.HORIZONTAL) {
576
+ return {
577
+ x: point.x > expendBBox.centerX ? expendBBox.maxX : expendBBox.minX,
578
+ y: point.y,
579
+ }
580
+ }
581
+ return {
582
+ x: point.x,
583
+ y: point.y > expendBBox.centerY ? expendBBox.maxY : expendBBox.minY,
584
+ }
585
+ }