@logicflow/core 2.0.4 → 2.0.6

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.
@@ -148,6 +148,7 @@ export class BaseNodeModel<P extends PropertiesType = PropertiesType>
148
148
  targetRules: Model.ConnectRule[] = []
149
149
  sourceRules: Model.ConnectRule[] = []
150
150
  moveRules: Model.NodeMoveRule[] = [] // 节点移动之前的hook
151
+ resizeRules: Model.NodeResizeRule[] = [] // 节点resize之前的hook
151
152
  hasSetTargetRules = false // 用来限制rules的重复值
152
153
  hasSetSourceRules = false; // 用来限制rules的重复值
153
154
  [propName: string]: any // 支持用户自定义属性
@@ -281,6 +282,13 @@ export class BaseNodeModel<P extends PropertiesType = PropertiesType>
281
282
  */
282
283
  resize(resizeInfo: ResizeInfo): ResizeNodeData {
283
284
  const { width, height, deltaX, deltaY } = resizeInfo
285
+
286
+ const isAllowResize = this.isAllowResizeNode(deltaX, deltaY, width, height)
287
+
288
+ if (!isAllowResize) {
289
+ return this.getData()
290
+ }
291
+
284
292
  // 移动节点以及文本内容
285
293
  this.move(deltaX / 2, deltaY / 2)
286
294
 
@@ -306,6 +314,18 @@ export class BaseNodeModel<P extends PropertiesType = PropertiesType>
306
314
  if (isObservable(properties)) {
307
315
  properties = toJS(properties)
308
316
  }
317
+ if (isNil(properties.width)) {
318
+ // resize()的时候会触发this.setProperties({width,height})
319
+ // 然后返回getData(),可以从properties拿到width
320
+ // 但是初始化如果没有在properties传入width,那么getData()就一直无法从properties拿到width
321
+ properties.width = this.width
322
+ }
323
+ if (isNil(properties.height)) {
324
+ // resize()的时候会触发this.setProperties({width,height})
325
+ // 然后返回getData(),可以从properties拿到height
326
+ // 但是初始化如果没有在properties传入height,那么getData()就一直无法从properties拿到width
327
+ properties.height = this.height
328
+ }
309
329
  const data: NodeData = {
310
330
  id: this.id,
311
331
  type: this.type,
@@ -687,6 +707,10 @@ export class BaseNodeModel<P extends PropertiesType = PropertiesType>
687
707
  this.y = this.y + deltaY
688
708
  this.text && this.moveText(0, deltaY)
689
709
  }
710
+ if (isAllowMoveX || isAllowMoveY) {
711
+ // 更新x和y的同时也要更新对应的transform旋转矩阵(依赖x、y)
712
+ this.rotate = this._rotate
713
+ }
690
714
  return isAllowMoveX || isAllowMoveY
691
715
  }
692
716
 
@@ -745,6 +769,30 @@ export class BaseNodeModel<P extends PropertiesType = PropertiesType>
745
769
  }
746
770
  }
747
771
 
772
+ @action addNodeResizeRules(fn: Model.NodeResizeRule) {
773
+ if (!this.resizeRules.includes(fn)) {
774
+ this.resizeRules.push(fn)
775
+ }
776
+ }
777
+
778
+ /**
779
+ * 内部方法
780
+ * 是否允许resize节点到新的位置
781
+ */
782
+ isAllowResizeNode(
783
+ deltaX: number,
784
+ deltaY: number,
785
+ width: number,
786
+ height: number,
787
+ ): boolean {
788
+ const rules = this.resizeRules.concat(this.graphModel.nodeResizeRules)
789
+ for (const rule of rules) {
790
+ const r = rule(this, deltaX, deltaY, width, height)
791
+ if (!r) return false
792
+ }
793
+ return true
794
+ }
795
+
748
796
  @action setSelected(flag = true): void {
749
797
  this.isSelected = flag
750
798
  }
package/src/util/graph.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import LogicFlow from '..'
2
- import { EditConfigModel } from 'src/model'
2
+ import { EditConfigModel } from '..'
3
3
 
4
4
  import PointTuple = LogicFlow.PointTuple
5
5
 
@@ -5,6 +5,70 @@ import { EventType } from '../constant'
5
5
 
6
6
  import ResizeInfo = ResizeControl.ResizeInfo
7
7
  import ResizeNodeData = ResizeControl.ResizeNodeData
8
+ import {
9
+ calculatePointAfterRotateAngle,
10
+ getNewCenter,
11
+ radianToAngle,
12
+ } from '../algorithm/rotate'
13
+ import type { SimplePoint } from '../algorithm/rotate'
14
+
15
+ function recalcRotatedResizeInfo(
16
+ pct: number,
17
+ resizeInfo: ResizeInfo,
18
+ rotate: number,
19
+ controlX: number,
20
+ controlY: number,
21
+ oldCenterX: number,
22
+ oldCenterY: number,
23
+ freezeWidth = false,
24
+ freezeHeight = false,
25
+ ) {
26
+ // 假设我们触摸的点是右下角的control
27
+ const { deltaX, deltaY, width: oldWidth, height: oldHeight } = resizeInfo
28
+ const angle = radianToAngle(rotate)
29
+
30
+ // 右下角的control
31
+ const startZeroTouchControlPoint = {
32
+ x: controlX, // control锚点的坐标x
33
+ y: controlY, // control锚点的坐标y
34
+ }
35
+ const oldCenter = { x: oldCenterX, y: oldCenterY }
36
+ // 右下角的control坐标(transform后的-touchStartPoint)
37
+ const startRotatedTouchControlPoint = calculatePointAfterRotateAngle(
38
+ startZeroTouchControlPoint,
39
+ oldCenter,
40
+ angle,
41
+ )
42
+ // 右下角的control坐标(transform后的-touchEndPoint)
43
+ const endRotatedTouchControlPoint = {
44
+ x: startRotatedTouchControlPoint.x + deltaX,
45
+ y: startRotatedTouchControlPoint.y + deltaY,
46
+ }
47
+ // 计算出新的宽度和高度以及新的中心点
48
+ const {
49
+ width: newWidth,
50
+ height: newHeight,
51
+ center: newCenter,
52
+ } = calculateWidthAndHeight(
53
+ startRotatedTouchControlPoint,
54
+ endRotatedTouchControlPoint,
55
+ oldCenter,
56
+ angle,
57
+ freezeWidth,
58
+ freezeHeight,
59
+ oldWidth,
60
+ oldHeight,
61
+ )
62
+ // calculateWidthAndHeight()得到的是整个宽度,比如圆pct=0.5,此时newWidth等于整个圆直径
63
+ resizeInfo.width = newWidth * pct
64
+ resizeInfo.height = newHeight * pct
65
+
66
+ // BaseNodeModel.resize(deltaX/2, deltaY/2),因此这里要*2
67
+ resizeInfo.deltaX = (newCenter.x - oldCenter.x) * 2
68
+ resizeInfo.deltaY = (newCenter.y - oldCenter.y) * 2
69
+
70
+ return resizeInfo
71
+ }
8
72
 
9
73
  /**
10
74
  * 计算 Control 拖动后,节点的高度信息
@@ -20,6 +84,11 @@ export const recalcResizeInfo = (
20
84
  pct = 1,
21
85
  freezeWidth = false,
22
86
  freezeHeight = false,
87
+ rotate = 0,
88
+ controlX: number | undefined,
89
+ controlY: number | undefined,
90
+ oldCenterX: number,
91
+ oldCenterY: number,
23
92
  ): ResizeInfo => {
24
93
  const nextResizeInfo = cloneDeep(resizeInfo)
25
94
  let { deltaX, deltaY } = nextResizeInfo
@@ -100,6 +169,26 @@ export const recalcResizeInfo = (
100
169
  }
101
170
  return nextResizeInfo
102
171
  }
172
+ if (
173
+ rotate % (2 * Math.PI) !== 0 &&
174
+ controlX !== undefined &&
175
+ controlY !== undefined
176
+ ) {
177
+ // 角度rotate不为0,则触发另外的计算修正resize的deltaX和deltaY
178
+ // 因为rotate不为0的时候,左上角的坐标一直在变化
179
+ // 角度rotate不为0得到的resizeInfo.deltaX仅仅代表中心点的变化,而不是宽度的变化
180
+ return recalcRotatedResizeInfo(
181
+ pct,
182
+ nextResizeInfo,
183
+ rotate,
184
+ controlX,
185
+ controlY,
186
+ oldCenterX,
187
+ oldCenterY,
188
+ freezeWidth,
189
+ freezeHeight,
190
+ )
191
+ }
103
192
 
104
193
  // 如果限制了宽/高不变,对应的 width/height 保持一致
105
194
  switch (index) {
@@ -194,17 +283,21 @@ export const triggerResizeEvent = (
194
283
  }
195
284
 
196
285
  // TODO:确认 handleResize 函数的类型定义
197
- // export type IHandleResizeParams = {
198
- // deltaX: number
199
- // deltaY: number
200
- // index: ResizeControlIndex
201
- // nodeModel: BaseNodeModel
202
- // graphModel: GraphModel
203
- // cancelCallback?: () => void
204
- // }
286
+ export type IHandleResizeParams = {
287
+ x?: number
288
+ y?: number
289
+ deltaX: number
290
+ deltaY: number
291
+ index: ResizeControlIndex
292
+ nodeModel: BaseNodeModel
293
+ graphModel: GraphModel
294
+ cancelCallback?: () => void
295
+ }
205
296
 
206
297
  /**
207
298
  * 处理节点的 resize 事件,提出来放到 utils 中,方便在外面(extension)中使用
299
+ * @param x
300
+ * @param y
208
301
  * @param deltaX
209
302
  * @param deltaY
210
303
  * @param index
@@ -213,13 +306,15 @@ export const triggerResizeEvent = (
213
306
  * @param cancelCallback
214
307
  */
215
308
  export const handleResize = ({
309
+ x,
310
+ y,
216
311
  deltaX,
217
312
  deltaY,
218
313
  index,
219
314
  nodeModel,
220
315
  graphModel,
221
316
  cancelCallback,
222
- }) => {
317
+ }: IHandleResizeParams) => {
223
318
  const {
224
319
  r, // circle
225
320
  rx, // ellipse/diamond
@@ -232,6 +327,9 @@ export const handleResize = ({
232
327
  minHeight,
233
328
  maxWidth,
234
329
  maxHeight,
330
+ rotate,
331
+ x: oldCenterX,
332
+ y: oldCenterY,
235
333
  } = nodeModel
236
334
  const isFreezeWidth = minWidth === maxWidth
237
335
  const isFreezeHeight = minHeight === maxHeight
@@ -245,12 +343,19 @@ export const handleResize = ({
245
343
  }
246
344
 
247
345
  const pct = r || (rx && ry) ? 1 / 2 : 1
346
+ const controlX = x
347
+ const controlY = y
248
348
  const nextSize = recalcResizeInfo(
249
349
  index,
250
350
  resizeInfo,
251
351
  pct,
252
352
  isFreezeWidth,
253
353
  isFreezeHeight,
354
+ rotate,
355
+ controlX,
356
+ controlY,
357
+ oldCenterX,
358
+ oldCenterY,
254
359
  )
255
360
 
256
361
  // 限制放大缩小的最大最小范围
@@ -264,13 +369,29 @@ export const handleResize = ({
264
369
  cancelCallback?.()
265
370
  return
266
371
  }
267
- // 如果限制了宽高不变,对应的 x/y 不产生位移
268
- nextSize.deltaX = isFreezeWidth ? 0 : nextSize.deltaX
269
- nextSize.deltaY = isFreezeWidth ? 0 : nextSize.deltaY
372
+ if (
373
+ rotate % (2 * Math.PI) == 0 ||
374
+ PCTResizeInfo ||
375
+ controlX === undefined ||
376
+ controlY === undefined
377
+ ) {
378
+ // rotate!==0并且不是PCTResizeInfo时,即使是isFreezeWidth||isFreezeHeight
379
+ // recalcRotatedResizeInfo()计算出来的中心点会发生变化
380
+
381
+ // 如果限制了宽高不变,对应的 x/y 不产生位移
382
+ nextSize.deltaX = isFreezeWidth ? 0 : nextSize.deltaX
383
+ nextSize.deltaY = isFreezeHeight ? 0 : nextSize.deltaY
384
+ }
270
385
 
271
386
  const preNodeData = nodeModel.getData()
272
387
  const curNodeData = nodeModel.resize(nextSize)
273
388
 
389
+ // 检测preNodeData和curNodeData是否没变化
390
+ if (preNodeData.x === curNodeData.x && preNodeData.y === curNodeData.y) {
391
+ // 中心点x和y都没有变化,说明无法resize,阻止下面边的更新以及resize事件的emit
392
+ return
393
+ }
394
+
274
395
  // 更新边
275
396
  updateEdgePointByAnchors(nodeModel, graphModel)
276
397
  // 触发 resize 事件
@@ -284,3 +405,114 @@ export const handleResize = ({
284
405
  graphModel,
285
406
  )
286
407
  }
408
+
409
+ export function calculateWidthAndHeight(
410
+ startRotatedTouchControlPoint: SimplePoint,
411
+ endRotatedTouchControlPoint: SimplePoint,
412
+ oldCenter: SimplePoint,
413
+ angle: number,
414
+ freezeWidth = false,
415
+ freezeHeight = false,
416
+ oldWidth: number,
417
+ oldHeight: number,
418
+ ) {
419
+ // 假设目前触摸的是右下角的control点
420
+ // 计算出来左上角的control坐标,resize过程左上角的control坐标保持不变
421
+ const freezePoint: SimplePoint = {
422
+ x: oldCenter.x - (startRotatedTouchControlPoint.x - oldCenter.x),
423
+ y: oldCenter.y - (startRotatedTouchControlPoint.y - oldCenter.y),
424
+ }
425
+ // 【touchEndPoint】右下角 + freezePoint左上角 计算出新的中心点
426
+ const newCenter = getNewCenter(freezePoint, endRotatedTouchControlPoint)
427
+
428
+ // 得到【touchEndPoint】右下角-没有transform的坐标
429
+ let endZeroTouchControlPoint: SimplePoint = calculatePointAfterRotateAngle(
430
+ endRotatedTouchControlPoint,
431
+ newCenter,
432
+ -angle,
433
+ )
434
+
435
+ // ---------- 使用transform之前的坐标计算出新的width和height ----------
436
+
437
+ // 得到左上角---没有transform的坐标
438
+ let zeroFreezePoint: SimplePoint = calculatePointAfterRotateAngle(
439
+ freezePoint,
440
+ newCenter,
441
+ -angle,
442
+ )
443
+
444
+ if (freezeWidth) {
445
+ // 如果固定width,那么不能单纯使用endZeroTouchControlPoint.x=startZeroTouchControlPoint.x
446
+ // 因为去掉transform的左上角不一定是重合的,我们要保证的是transform后的左上角重合
447
+ const newWidth = Math.abs(endZeroTouchControlPoint.x - zeroFreezePoint.x)
448
+ const widthDx = newWidth - oldWidth
449
+
450
+ // 点击的是左边锚点,是+widthDx/2,点击是右边锚点,是-widthDx/2
451
+ if (newCenter.x > endZeroTouchControlPoint.x) {
452
+ // 当前触摸的是左边锚点
453
+ newCenter.x = newCenter.x + widthDx / 2
454
+ } else {
455
+ // 当前触摸的是右边锚点
456
+ newCenter.x = newCenter.x - widthDx / 2
457
+ }
458
+ }
459
+ if (freezeHeight) {
460
+ const newHeight = Math.abs(endZeroTouchControlPoint.y - zeroFreezePoint.y)
461
+ const heightDy = newHeight - oldHeight
462
+ if (newCenter.y > endZeroTouchControlPoint.y) {
463
+ // 当前触摸的是上边锚点
464
+ newCenter.y = newCenter.y + heightDy / 2
465
+ } else {
466
+ newCenter.y = newCenter.y - heightDy / 2
467
+ }
468
+ }
469
+
470
+ if (freezeWidth || freezeHeight) {
471
+ // 如果调整过transform之前的坐标,那么transform后的坐标也会改变,那么算出来的newCenter也得调整
472
+ // 由于无论如何rotate,中心点都是不变的,因此我们可以使用transform之前的坐标算出新的中心点
473
+ const nowFreezePoint = calculatePointAfterRotateAngle(
474
+ zeroFreezePoint,
475
+ newCenter,
476
+ angle,
477
+ )
478
+
479
+ // 得到当前新rect的左上角与实际上transform后的左上角的偏移量
480
+ const dx = nowFreezePoint.x - freezePoint.x
481
+ const dy = nowFreezePoint.y - freezePoint.y
482
+
483
+ // 修正不使用transform的坐标: 左上角、右下角、center
484
+ newCenter.x = newCenter.x - dx
485
+ newCenter.y = newCenter.y - dy
486
+ zeroFreezePoint = calculatePointAfterRotateAngle(
487
+ freezePoint,
488
+ newCenter,
489
+ -angle,
490
+ )
491
+ endZeroTouchControlPoint = {
492
+ x: newCenter.x - (zeroFreezePoint.x - newCenter.x),
493
+ y: newCenter.y - (zeroFreezePoint.y - newCenter.y),
494
+ }
495
+ }
496
+
497
+ // transform之前的坐标的左上角+右下角计算出宽度和高度
498
+ let width = Math.abs(endZeroTouchControlPoint.x - zeroFreezePoint.x)
499
+ let height = Math.abs(endZeroTouchControlPoint.y - zeroFreezePoint.y)
500
+
501
+ // ---------- 使用transform之前的坐标计算出新的width和height ----------
502
+
503
+ if (freezeWidth) {
504
+ // 理论计算出来的width应该等于oldWidth
505
+ // 但是有误差,比如oldWidth = 100; newWidth=100.000000000001
506
+ // 会在handleResize()限制放大缩小的最大最小范围中被阻止滑动
507
+ width = oldWidth
508
+ }
509
+ if (freezeHeight) {
510
+ height = oldHeight
511
+ }
512
+
513
+ return {
514
+ width,
515
+ height,
516
+ center: newCenter,
517
+ }
518
+ }
@@ -56,6 +56,10 @@ export class ResizeControl extends Component<
56
56
  })
57
57
  }
58
58
 
59
+ componentWillUnmount() {
60
+ this.dragHandler.cancelDrag()
61
+ }
62
+
59
63
  updateEdgePointByAnchors = () => {
60
64
  // https://github.com/didi/LogicFlow/issues/807
61
65
  // https://github.com/didi/LogicFlow/issues/875
@@ -241,10 +245,12 @@ export class ResizeControl extends Component<
241
245
 
242
246
  resizeNode = ({ deltaX, deltaY }: VectorData) => {
243
247
  const { index } = this
244
- const { model, graphModel } = this.props
248
+ const { model, graphModel, x, y } = this.props
245
249
 
246
250
  // DONE: 调用每个节点中更新缩放时的方法 updateNode 函数,用来各节点缩放的方法
247
251
  handleResize({
252
+ x,
253
+ y,
248
254
  deltaX,
249
255
  deltaY,
250
256
  index,