@logicflow/core 2.2.0-alpha.2 → 2.2.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 (123) hide show
  1. package/.turbo/turbo-build$colon$dev.log +2 -2
  2. package/.turbo/turbo-build.log +6 -6
  3. package/CHANGELOG.md +16 -0
  4. package/dist/index.css +3 -2
  5. package/dist/index.min.js +1 -1
  6. package/dist/index.min.js.map +1 -1
  7. package/es/LogicFlow.d.ts +9 -0
  8. package/es/constant/index.d.ts +1 -1
  9. package/es/constant/index.js +1 -1
  10. package/es/constant/theme.d.ts +136 -0
  11. package/es/constant/theme.js +680 -0
  12. package/es/index.css +3 -2
  13. package/es/model/GraphModel.d.ts +9 -2
  14. package/es/model/GraphModel.js +45 -12
  15. package/es/model/TransformModel.js +9 -9
  16. package/es/model/edge/BaseEdgeModel.js +7 -2
  17. package/es/model/edge/PolylineEdgeModel.d.ts +6 -0
  18. package/es/model/edge/PolylineEdgeModel.js +23 -1
  19. package/es/model/node/BaseNodeModel.d.ts +12 -1
  20. package/es/model/node/BaseNodeModel.js +6 -1
  21. package/es/model/node/HtmlNodeModel.d.ts +12 -0
  22. package/es/model/node/HtmlNodeModel.js +19 -0
  23. package/es/model/node/PolygonNodeModel.js +3 -3
  24. package/es/options.d.ts +1 -1
  25. package/es/style/index.css +3 -2
  26. package/es/style/index.less +3 -2
  27. package/es/style/raw.d.ts +1 -1
  28. package/es/style/raw.js +1 -1
  29. package/es/tool/MultipleSelectTool.js +10 -5
  30. package/es/util/edge.d.ts +1 -1
  31. package/es/util/edge.js +2 -2
  32. package/es/util/geometry.d.ts +8 -0
  33. package/es/util/geometry.js +79 -0
  34. package/es/util/theme.d.ts +2 -65
  35. package/es/util/theme.js +4 -281
  36. package/es/view/Control.d.ts +5 -0
  37. package/es/view/Control.js +44 -57
  38. package/es/view/edge/PolylineEdge.js +13 -2
  39. package/es/view/node/BaseNode.d.ts +1 -0
  40. package/es/view/node/BaseNode.js +23 -10
  41. package/es/view/node/HtmlNode.js +2 -4
  42. package/es/view/overlay/CanvasOverlay.js +5 -2
  43. package/es/view/overlay/Grid.d.ts +12 -1
  44. package/es/view/overlay/Grid.js +85 -23
  45. package/es/view/overlay/OutlineOverlay.d.ts +1 -0
  46. package/es/view/overlay/OutlineOverlay.js +17 -16
  47. package/es/view/overlay/gridConfig.d.ts +46 -0
  48. package/es/view/overlay/gridConfig.js +99 -0
  49. package/es/view/shape/Polygon.d.ts +0 -7
  50. package/es/view/shape/Polygon.js +12 -43
  51. package/lib/LogicFlow.d.ts +9 -0
  52. package/lib/constant/index.d.ts +1 -1
  53. package/lib/constant/index.js +16 -2
  54. package/lib/constant/theme.d.ts +136 -0
  55. package/lib/constant/theme.js +683 -0
  56. package/lib/index.css +3 -2
  57. package/lib/model/GraphModel.d.ts +9 -2
  58. package/lib/model/GraphModel.js +46 -13
  59. package/lib/model/TransformModel.js +9 -9
  60. package/lib/model/edge/BaseEdgeModel.js +7 -2
  61. package/lib/model/edge/PolylineEdgeModel.d.ts +6 -0
  62. package/lib/model/edge/PolylineEdgeModel.js +23 -1
  63. package/lib/model/node/BaseNodeModel.d.ts +12 -1
  64. package/lib/model/node/BaseNodeModel.js +6 -1
  65. package/lib/model/node/HtmlNodeModel.d.ts +12 -0
  66. package/lib/model/node/HtmlNodeModel.js +19 -0
  67. package/lib/model/node/PolygonNodeModel.js +3 -3
  68. package/lib/options.d.ts +1 -1
  69. package/lib/style/index.css +3 -2
  70. package/lib/style/index.less +3 -2
  71. package/lib/style/raw.d.ts +1 -1
  72. package/lib/style/raw.js +1 -1
  73. package/lib/tool/MultipleSelectTool.js +10 -5
  74. package/lib/util/edge.d.ts +1 -1
  75. package/lib/util/edge.js +2 -2
  76. package/lib/util/geometry.d.ts +8 -0
  77. package/lib/util/geometry.js +81 -1
  78. package/lib/util/theme.d.ts +2 -65
  79. package/lib/util/theme.js +15 -292
  80. package/lib/view/Control.d.ts +5 -0
  81. package/lib/view/Control.js +44 -57
  82. package/lib/view/edge/PolylineEdge.js +13 -2
  83. package/lib/view/node/BaseNode.d.ts +1 -0
  84. package/lib/view/node/BaseNode.js +23 -10
  85. package/lib/view/node/HtmlNode.js +1 -3
  86. package/lib/view/overlay/CanvasOverlay.js +5 -2
  87. package/lib/view/overlay/Grid.d.ts +12 -1
  88. package/lib/view/overlay/Grid.js +83 -21
  89. package/lib/view/overlay/OutlineOverlay.d.ts +1 -0
  90. package/lib/view/overlay/OutlineOverlay.js +17 -16
  91. package/lib/view/overlay/gridConfig.d.ts +46 -0
  92. package/lib/view/overlay/gridConfig.js +104 -0
  93. package/lib/view/shape/Polygon.d.ts +0 -7
  94. package/lib/view/shape/Polygon.js +13 -45
  95. package/package.json +1 -1
  96. package/src/LogicFlow.tsx +10 -0
  97. package/src/constant/index.ts +2 -2
  98. package/src/constant/theme.ts +708 -0
  99. package/src/model/EditConfigModel.ts +4 -4
  100. package/src/model/GraphModel.ts +48 -16
  101. package/src/model/TransformModel.ts +9 -9
  102. package/src/model/edge/BaseEdgeModel.ts +10 -2
  103. package/src/model/edge/PolylineEdgeModel.ts +26 -1
  104. package/src/model/node/BaseNodeModel.ts +12 -5
  105. package/src/model/node/HtmlNodeModel.ts +14 -0
  106. package/src/model/node/PolygonNodeModel.ts +2 -0
  107. package/src/options.ts +1 -1
  108. package/src/style/index.less +3 -2
  109. package/src/style/raw.ts +3 -2
  110. package/src/tool/MultipleSelectTool.tsx +13 -5
  111. package/src/util/edge.ts +2 -1
  112. package/src/util/geometry.ts +99 -0
  113. package/src/util/theme.ts +12 -303
  114. package/src/view/Control.tsx +61 -63
  115. package/src/view/edge/PolylineEdge.tsx +14 -2
  116. package/src/view/node/BaseNode.tsx +18 -3
  117. package/src/view/node/HtmlNode.tsx +27 -10
  118. package/src/view/overlay/CanvasOverlay.tsx +4 -2
  119. package/src/view/overlay/Grid.tsx +187 -30
  120. package/src/view/overlay/OutlineOverlay.tsx +35 -47
  121. package/src/view/overlay/gridConfig.ts +103 -0
  122. package/src/view/shape/Polygon.tsx +12 -49
  123. package/stats.html +1 -1
@@ -1,4 +1,5 @@
1
1
  import {
2
+ assign,
2
3
  find,
3
4
  forEach,
4
5
  map,
@@ -26,6 +27,8 @@ import {
26
27
  ModelType,
27
28
  OverlapMode,
28
29
  TextMode,
30
+ backgroundModeMap,
31
+ gridModeMap,
29
32
  } from '../constant'
30
33
  import LogicFlow from '../LogicFlow'
31
34
  import { Options as LFOptions } from '../options'
@@ -43,8 +46,6 @@ import {
43
46
  setupTheme,
44
47
  snapToGrid,
45
48
  updateTheme,
46
- backgroundModeMap,
47
- gridModeMap,
48
49
  } from '../util'
49
50
  import EventEmitter from '../event/eventEmitter'
50
51
  import { Grid } from '../view/overlay'
@@ -69,8 +70,9 @@ export class GraphModel {
69
70
 
70
71
  // 流程图主题配置
71
72
  @observable theme: LogicFlow.Theme
73
+ @observable themeMode: LogicFlow.ThemeMode | string = 'default'
72
74
  // 初始化样式
73
- customStyles: object
75
+ customStyles: LogicFlow.Theme
74
76
  // 网格配置
75
77
  @observable grid: Grid.GridOptions
76
78
  // 事件中心
@@ -167,6 +169,10 @@ export class GraphModel {
167
169
  customTrajectory,
168
170
  customTargetAnchor,
169
171
  } = options
172
+ this.themeMode = options.themeMode || 'default'
173
+ const initialGrid = gridModeMap[this.themeMode] || gridModeMap['default']
174
+ const initialBackground =
175
+ backgroundModeMap[this.themeMode] || backgroundModeMap['default']
170
176
  this.rootEl = container
171
177
  this.partial = !!partial
172
178
  this.background = background
@@ -175,11 +181,16 @@ export class GraphModel {
175
181
  // TODO:需要让用户设置成 0 吗?后面可以讨论一下
176
182
  this.gridSize = grid.size || 1 // 默认 gridSize 设置为 1
177
183
  }
178
- this.customStyles = options.style || {}
179
- this.grid = Grid.getGridOptions(grid ?? false)
184
+ this.customStyles = (options.style || {}) as LogicFlow.Theme
180
185
  this.theme = setupTheme(options.style, options.themeMode)
186
+ this.grid = Grid.getGridOptions(assign({}, initialGrid, grid))
181
187
  this.theme.grid = cloneDeep(this.grid)
182
- this.theme.background = cloneDeep(this.background)
188
+ if (background) {
189
+ this.background = cloneDeep(assign({}, initialBackground, background))
190
+ this.theme.background = cloneDeep(
191
+ assign({}, initialBackground, background),
192
+ )
193
+ }
183
194
  this.edgeType = options.edgeType || 'polyline'
184
195
  this.animation = setupAnimation(animation)
185
196
  this.overlapMode = options.overlapMode || OverlapMode.DEFAULT
@@ -195,6 +206,9 @@ export class GraphModel {
195
206
  ((entries) => {
196
207
  for (const entry of entries) {
197
208
  if (entry.target === this.rootEl) {
209
+ // 检查元素是否仍在DOM中
210
+ const isElementInDOM = document.body.contains(this.rootEl)
211
+ if (!isElementInDOM) return
198
212
  this.resize()
199
213
  this.eventCenter.emit('graph:resize', {
200
214
  target: this.rootEl,
@@ -1516,9 +1530,10 @@ export class GraphModel {
1516
1530
  */
1517
1531
  @action setTheme(
1518
1532
  style: Partial<LogicFlow.Theme>,
1519
- themeMode?: 'radius' | 'dark' | 'colorful' | 'default' | string,
1533
+ themeMode?: LogicFlow.ThemeMode | string,
1520
1534
  ) {
1521
1535
  if (themeMode) {
1536
+ this.themeMode = themeMode
1522
1537
  // 修改背景颜色
1523
1538
  backgroundModeMap[themeMode] &&
1524
1539
  this.updateBackgroundOptions({
@@ -1589,15 +1604,32 @@ export class GraphModel {
1589
1604
  * 重新设置画布的宽高
1590
1605
  */
1591
1606
  @action resize(width?: number, height?: number): void {
1592
- this.width = width ?? this.rootEl.getBoundingClientRect().width
1593
- this.isContainerWidth = isNil(width)
1594
- this.height = height ?? this.rootEl.getBoundingClientRect().height
1595
- this.isContainerHeight = isNil(height)
1596
-
1597
- if (!this.width || !this.height) {
1598
- console.warn(
1599
- '渲染画布的时候无法获取画布宽高,请确认在container已挂载到DOM。@see https://github.com/didi/LogicFlow/issues/675',
1600
- )
1607
+ // 检查当前实例是否已被销毁或rootEl不存在
1608
+ if (!this.rootEl) return
1609
+
1610
+ // 检查元素是否仍在DOM中
1611
+ const isElementInDOM = document.body.contains(this.rootEl)
1612
+ if (!isElementInDOM) return
1613
+
1614
+ // 检查元素是否可见
1615
+ const isVisible = this.rootEl.offsetParent !== null
1616
+ if (!isVisible) return
1617
+
1618
+ try {
1619
+ this.width = width ?? this.rootEl.getBoundingClientRect().width
1620
+ this.isContainerWidth = isNil(width)
1621
+ this.height = height ?? this.rootEl.getBoundingClientRect().height
1622
+ this.isContainerHeight = isNil(height)
1623
+
1624
+ // 只有在元素可见且应该有宽高的情况下才显示警告
1625
+ if (isVisible && (!this.width || !this.height)) {
1626
+ console.warn(
1627
+ '渲染画布的时候无法获取画布宽高,请确认在container已挂载到DOM。@see https://github.com/didi/LogicFlow/issues/675',
1628
+ )
1629
+ }
1630
+ } catch (error) {
1631
+ // 捕获可能的DOM操作错误
1632
+ console.warn('获取画布宽高时发生错误:', error)
1601
1633
  }
1602
1634
  }
1603
1635
 
@@ -46,15 +46,15 @@ const translateLimitsMap = {
46
46
  }
47
47
 
48
48
  export class TransformModel implements TransformInterface {
49
- MINI_SCALE_SIZE = 0.2
50
- MAX_SCALE_SIZE = 16
51
- @observable SCALE_X = 1
52
- @observable SKEW_Y = 0
53
- @observable SKEW_X = 0
54
- @observable SCALE_Y = 1
55
- @observable TRANSLATE_X = 0
56
- @observable TRANSLATE_Y = 0
57
- @observable ZOOM_SIZE = 0.04
49
+ MINI_SCALE_SIZE = 0.2 // 缩小的最小值
50
+ MAX_SCALE_SIZE = 16 // 放大的最大值
51
+ @observable SCALE_X = 1 // x轴缩放比例
52
+ @observable SKEW_Y = 0 // y轴倾斜角度
53
+ @observable SKEW_X = 0 // x轴倾斜角度
54
+ @observable SCALE_Y = 1 // y轴缩放比例
55
+ @observable TRANSLATE_X = 0 // x轴平移距离
56
+ @observable TRANSLATE_Y = 0 // y轴平移距离
57
+ @observable ZOOM_SIZE = 0.04 // 缩放比例变化量
58
58
  eventCenter: EventEmitter
59
59
 
60
60
  // 限制画布可移动区域
@@ -265,8 +265,16 @@ export class BaseEdgeModel<P extends PropertiesType = PropertiesType>
265
265
  */
266
266
  getOutlineStyle(): LogicFlow.OutlineTheme {
267
267
  const { graphModel } = this
268
- const { outline } = graphModel.theme
269
- return cloneDeep(outline)
268
+ const { edgeOutline } = graphModel.theme
269
+ let attributes = { ...edgeOutline }
270
+ if (this.isHovered) {
271
+ const hoverStyle = edgeOutline.hover || {}
272
+ attributes = {
273
+ ...attributes,
274
+ ...hoverStyle,
275
+ }
276
+ }
277
+ return cloneDeep(attributes)
270
278
  }
271
279
 
272
280
  /**
@@ -34,13 +34,38 @@ export class PolylineEdgeModel extends BaseEdgeModel {
34
34
  @observable dbClickPosition?: Point
35
35
 
36
36
  initEdgeData(data: LogicFlow.EdgeConfig): void {
37
- this.offset = get(data, 'properties.offset', 30)
37
+ const providedOffset = get(data, 'properties.offset')
38
+ // 当用户未传入 offset 时,按“箭头与折线重叠长度 + 5”作为默认值
39
+ // 其中“重叠长度”采用箭头样式中的 offset(沿边方向的长度)
40
+ this.offset =
41
+ typeof providedOffset === 'number'
42
+ ? providedOffset
43
+ : this.getDefaultOffset()
38
44
  if (data.pointsList) {
39
45
  this.pointsList = data.pointsList
40
46
  }
41
47
  super.initEdgeData(data)
42
48
  }
43
49
 
50
+ setAttributes() {
51
+ const { offset: newOffset } = this.properties
52
+ if (newOffset && newOffset !== this.offset) {
53
+ this.offset = newOffset
54
+ this.updatePoints()
55
+ }
56
+ }
57
+
58
+ /**
59
+ * 计算默认 offset:箭头与折线重叠长度 + 5
60
+ * 重叠长度采用箭头样式中的 offset(沿边方向的长度)
61
+ */
62
+ private getDefaultOffset(): number {
63
+ const arrowStyle = this.getArrowStyle()
64
+ const arrowOverlap =
65
+ typeof arrowStyle.offset === 'number' ? arrowStyle.offset : 0
66
+ return arrowOverlap + 5
67
+ }
68
+
44
69
  getEdgeStyle() {
45
70
  const { polyline } = this.graphModel.theme
46
71
  const style = super.getEdgeStyle()
@@ -57,8 +57,7 @@ export interface IBaseNodeModel<P extends PropertiesType>
57
57
  }
58
58
 
59
59
  export class BaseNodeModel<P extends PropertiesType = PropertiesType>
60
- implements IBaseNodeModel<P>
61
- {
60
+ implements IBaseNodeModel<P> {
62
61
  readonly BaseType = ElementType.NODE
63
62
  static BaseType: ElementType = ElementType.NODE
64
63
 
@@ -226,7 +225,7 @@ export class BaseNodeModel<P extends PropertiesType = PropertiesType>
226
225
  *
227
226
  * @overridable 支持重写
228
227
  */
229
- public setAttributes() {}
228
+ public setAttributes() { }
230
229
 
231
230
  /**
232
231
  * @overridable 支持重写,自定义此类型节点默认生成方式
@@ -307,7 +306,7 @@ export class BaseNodeModel<P extends PropertiesType = PropertiesType>
307
306
  }
308
307
 
309
308
  // TODO: 等比例缩放
310
- proportionalResize() {}
309
+ proportionalResize() { }
311
310
 
312
311
  /**
313
312
  * 获取被保存时返回的数据
@@ -433,7 +432,15 @@ export class BaseNodeModel<P extends PropertiesType = PropertiesType>
433
432
 
434
433
  getResizeOutlineStyle() {
435
434
  const { resizeOutline } = this.graphModel.theme
436
- return cloneDeep(resizeOutline)
435
+ let attributes = { ...resizeOutline }
436
+ if (this.isHovered) {
437
+ const hoverStyle = resizeOutline.hover || {}
438
+ attributes = {
439
+ ...attributes,
440
+ ...hoverStyle,
441
+ }
442
+ }
443
+ return cloneDeep(attributes)
437
444
  }
438
445
 
439
446
  /**
@@ -1,3 +1,4 @@
1
+ import { cloneDeep } from 'lodash-es'
1
2
  import BaseNodeModel from './BaseNodeModel'
2
3
  import { Model } from '../BaseModel'
3
4
  import { ModelType } from '../../constant'
@@ -45,6 +46,19 @@ export class HtmlNodeModel<
45
46
  { x: x - width / 2, y, id: `${this.id}_3` },
46
47
  ]
47
48
  }
49
+
50
+ getNodeStyle() {
51
+ const style = super.getNodeStyle()
52
+ const { baseNode, html } = this.graphModel.theme
53
+ const { style: customStyle = {} } = this.properties
54
+ const finalStyle = {
55
+ ...style,
56
+ ...cloneDeep(baseNode),
57
+ ...cloneDeep(html),
58
+ ...cloneDeep(customStyle),
59
+ }
60
+ return finalStyle
61
+ }
48
62
  }
49
63
 
50
64
  export default HtmlNodeModel
@@ -67,12 +67,14 @@ export class PolygonNodeModel<
67
67
  const {
68
68
  graphModel: {
69
69
  theme: { polygon },
70
+ customStyles: { polygon: customPolygon },
70
71
  },
71
72
  } = this
72
73
  const { style: customStyle = {} } = this.properties
73
74
  return {
74
75
  ...style,
75
76
  ...cloneDeep(polygon),
77
+ ...cloneDeep(customPolygon),
76
78
  ...cloneDeep(customStyle),
77
79
  }
78
80
  }
package/src/options.ts CHANGED
@@ -110,7 +110,7 @@ export namespace Options {
110
110
 
111
111
  customTargetAnchor?: customTargetAnchorType
112
112
  customTrajectory?: (props: CustomAnchorLineProps) => h.JSX.Element
113
- themeMode?: 'radius' | 'dark' | 'colorful' // 主题模式
113
+ themeMode?: LogicFlow.ThemeMode // 主题模式
114
114
 
115
115
  parentTransform?: TransformModel // 父级变换模型,用于嵌套变换
116
116
 
@@ -215,8 +215,9 @@
215
215
 
216
216
  .lf-multiple-select {
217
217
  position: absolute;
218
- border: 2px dashed #187dffcc;
219
- box-shadow: 0 0 3px 0 #187dff80;
218
+ border: 2px dashed #4271dfcc;
219
+ border-radius: 12px;
220
+ box-shadow: 0 0 3px 0 #4271df80;
220
221
  cursor: move;
221
222
  }
222
223
 
package/src/style/raw.ts CHANGED
@@ -183,8 +183,9 @@ export const content = `.lf-graph {
183
183
  }
184
184
  .lf-multiple-select {
185
185
  position: absolute;
186
- border: 2px dashed #187dffcc;
187
- box-shadow: 0 0 3px 0 #187dff80;
186
+ border: 2px dashed #4271dfcc;
187
+ border-radius: 12px;
188
+ box-shadow: 0 0 3px 0 #4271df80;
188
189
  cursor: move;
189
190
  }
190
191
  .lf-edge-adjust-point {
@@ -27,6 +27,13 @@ export default class MultipleSelect extends Component<IToolProps> {
27
27
  }
28
28
 
29
29
  handleMouseDown = (ev: PointerEvent) => {
30
+ // 多选区域的拖拽步长随缩放变化
31
+ const {
32
+ graphModel: { gridSize },
33
+ lf,
34
+ } = this.props
35
+ const { SCALE_X } = lf.getTransform()
36
+ this.stepDrag.setStep(gridSize * SCALE_X)
30
37
  this.stepDrag.handleMouseDown(ev)
31
38
  }
32
39
  // 使多选区域的滚轮事件可以触发画布的滚轮事件
@@ -86,9 +93,10 @@ export default class MultipleSelect extends Component<IToolProps> {
86
93
 
87
94
  render() {
88
95
  const {
89
- graphModel: { selectElements, transformModel },
96
+ graphModel: { selectElements, transformModel, theme },
90
97
  } = this.props
91
98
  const { SCALE_X, SCALE_Y } = this.props.lf.getTransform()
99
+ const { xPadding = 8, yPadding = 8 } = theme.multiSelect || {}
92
100
  if (selectElements.size <= 1) return
93
101
  let x = Number.MAX_SAFE_INTEGER
94
102
  let y = Number.MAX_SAFE_INTEGER
@@ -113,10 +121,10 @@ export default class MultipleSelect extends Component<IToolProps> {
113
121
  ;[x, y] = transformModel.CanvasPointToHtmlPoint([x, y])
114
122
  ;[x1, y1] = transformModel.CanvasPointToHtmlPoint([x1, y1])
115
123
  const style = {
116
- left: `${x - (20 * SCALE_X) / 2}px`,
117
- top: `${y - (20 * SCALE_Y) / 2}px`,
118
- width: `${x1 - x + 20 * SCALE_X}px`,
119
- height: `${y1 - y + 20 * SCALE_Y}px`,
124
+ left: `${x - (20 * SCALE_X) / 2 - xPadding / 2}px`,
125
+ top: `${y - (20 * SCALE_Y) / 2 - yPadding / 2}px`,
126
+ width: `${x1 - x + 20 * SCALE_X + xPadding}px`,
127
+ height: `${y1 - y + 20 * SCALE_Y + yPadding}px`,
120
128
  'border-width': `${2 * SCALE_X}px`,
121
129
  }
122
130
  return (
package/src/util/edge.ts CHANGED
@@ -159,6 +159,7 @@ export const mergeBBox = (b1: BoxBounds, b2: BoxBounds): BoxBounds => {
159
159
  export const getBBoxOfPoints = (
160
160
  points: Point[] = [],
161
161
  offset?: number,
162
+ heightOffset?: number,
162
163
  ): BoxBounds => {
163
164
  const xList: number[] = []
164
165
  const yList: number[] = []
@@ -174,7 +175,7 @@ export const getBBoxOfPoints = (
174
175
  let height = maxY - minY
175
176
  if (offset) {
176
177
  width += offset
177
- height += offset
178
+ height += heightOffset || offset
178
179
  }
179
180
  return {
180
181
  centerX: (minX + maxX) / 2,
@@ -1,5 +1,6 @@
1
1
  import LogicFlow from '../LogicFlow'
2
2
  import PointTuple = LogicFlow.PointTuple
3
+ import Point = LogicFlow.Point
3
4
 
4
5
  export function snapToGrid(point: number, gridSize: number, snapGrid: boolean) {
5
6
  // 开启节网格对齐时才根据网格尺寸校准坐标
@@ -53,3 +54,101 @@ export function normalizePolygon(
53
54
  // 缩放顶点
54
55
  return translatedPoints.map(([x, y]) => [x * scaleFactor, y * scaleFactor])
55
56
  }
57
+
58
+ /**
59
+ * 通用圆角生成:为菱形、多边形、折线在转折处生成与矩形视觉一致的圆角
60
+ * - 圆角基于角平分线,切点距顶点的距离 t = r * tan(theta/2)
61
+ * - 半径会根据相邻边长度进行钳制,避免超过边长造成断裂
62
+ * - 多边形/菱形保持闭合;折线保持开口
63
+ */
64
+
65
+ export const generateRoundedCorners = (
66
+ points: Point[],
67
+ radius: number,
68
+ isClosedShape: boolean, // 是否是闭合图形
69
+ ): Point[] => {
70
+ const n = points.length
71
+ if (n < 2 || radius <= 0) return points.slice()
72
+
73
+ const toVec = (a: Point, b: Point) => ({ x: b.x - a.x, y: b.y - a.y })
74
+ const len = (v: { x: number; y: number }) => Math.hypot(v.x, v.y)
75
+ const norm = (v: { x: number; y: number }) => {
76
+ const l = len(v) || 1
77
+ return { x: v.x / l, y: v.y / l }
78
+ }
79
+
80
+ const result: Point[] = []
81
+
82
+ // 用二次贝塞尔近似圆角,控制点取角点,避免复杂圆心计算
83
+ const makeRoundCorner = (prev: Point, curr: Point, next: Point): Point[] => {
84
+ const vPrev = toVec(curr, prev)
85
+ const vNext = toVec(curr, next)
86
+ const dPrev = len(vPrev)
87
+ const dNext = len(vNext)
88
+ if (dPrev < 1e-6 || dNext < 1e-6) return [curr]
89
+
90
+ const uPrev = norm(vPrev)
91
+ const uNext = norm(vNext)
92
+ const t = Math.min(radius, dPrev * 0.45, dNext * 0.45)
93
+
94
+ const start = { x: curr.x + uPrev.x * t, y: curr.y + uPrev.y * t }
95
+ const end = { x: curr.x + uNext.x * t, y: curr.y + uNext.y * t }
96
+
97
+ // 二次贝塞尔采样:B(s) = (1-s)^2*start + 2(1-s)s*curr + s^2*end
98
+ const steps = 10 // 3段近似,简洁且效果稳定
99
+ const pts: Point[] = [start]
100
+ for (let k = 1; k < steps; k++) {
101
+ const s = k / steps
102
+ const a = 1 - s
103
+ pts.push({
104
+ x: a * a * start.x + 2 * a * s * curr.x + s * s * end.x,
105
+ y: a * a * start.y + 2 * a * s * curr.y + s * s * end.y,
106
+ })
107
+ }
108
+ pts.push(end)
109
+ return pts
110
+ }
111
+
112
+ for (let i = 0; i < n; i++) {
113
+ const prevIdx = i === 0 ? (isClosedShape ? n - 1 : 0) : i - 1
114
+ const nextIdx = i === n - 1 ? (isClosedShape ? 0 : n - 1) : i + 1
115
+ const prev = points[prevIdx]
116
+ const curr = points[i]
117
+ const next = points[nextIdx]
118
+
119
+ const isEndpoint = !isClosedShape && (i === 0 || i === n - 1)
120
+ if (isEndpoint) {
121
+ // 折线两端不处理圆角
122
+ result.push(curr)
123
+ } else {
124
+ const arc = makeRoundCorner(prev, curr, next)
125
+ arc.forEach((p) => result.push(p))
126
+ }
127
+ }
128
+
129
+ // 去重处理:避免连续重复点
130
+ const dedup: Point[] = []
131
+ for (let i = 0; i < result.length; i++) {
132
+ const p = result[i]
133
+ if (
134
+ dedup.length === 0 ||
135
+ Math.hypot(
136
+ p.x - dedup[dedup.length - 1].x,
137
+ p.y - dedup[dedup.length - 1].y,
138
+ ) > 1e-6
139
+ ) {
140
+ dedup.push(p)
141
+ }
142
+ }
143
+
144
+ // 闭合图形:确保首尾不重复闭合
145
+ if (isClosedShape && dedup.length > 1) {
146
+ const first = dedup[0]
147
+ const last = dedup[dedup.length - 1]
148
+ if (Math.hypot(first.x - last.x, first.y - last.y) < 1e-6) {
149
+ dedup.pop()
150
+ }
151
+ }
152
+
153
+ return dedup
154
+ }