@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
@@ -1,1824 +0,0 @@
1
- import {
2
- assign,
3
- find,
4
- forEach,
5
- map,
6
- merge,
7
- isBoolean,
8
- debounce,
9
- cloneDeep,
10
- isNil,
11
- } from 'lodash-es'
12
- import { action, computed, observable } from 'mobx'
13
- import {
14
- BaseEdgeModel,
15
- BaseNodeModel,
16
- EditConfigModel,
17
- Model,
18
- PolylineEdgeModel,
19
- TransformModel,
20
- } from '.'
21
- import {
22
- DEFAULT_VISIBLE_SPACE,
23
- ELEMENT_MAX_Z_INDEX,
24
- ElementState,
25
- ElementType,
26
- EventType,
27
- ModelType,
28
- OverlapMode,
29
- TextMode,
30
- backgroundModeMap,
31
- gridModeMap,
32
- } from '../constant'
33
- import LogicFlow from '../LogicFlow'
34
- import { Options as LFOptions } from '../options'
35
- import {
36
- createEdgeGenerator,
37
- createUuid,
38
- formatData,
39
- getClosestPointOfPolyline,
40
- getMinIndex,
41
- getNodeAnchorPosition,
42
- getNodeBBox,
43
- getZIndex,
44
- isPointInArea,
45
- setupAnimation,
46
- setupTheme,
47
- snapToGrid,
48
- updateTheme,
49
- } from '../util'
50
- import EventEmitter from '../event/eventEmitter'
51
- import { Grid } from '../view/overlay'
52
- import NestedTransformModel from './NestedTransformModel'
53
-
54
- import Position = LogicFlow.Position
55
- import PointTuple = LogicFlow.PointTuple
56
- import GraphData = LogicFlow.GraphData
57
- import NodeConfig = LogicFlow.NodeConfig
58
- import BaseNodeModelCtor = LogicFlow.BaseNodeModelCtor
59
- import BaseEdgeModelCtor = LogicFlow.BaseEdgeModelCtor
60
-
61
- export class GraphModel {
62
- /**
63
- * LogicFlow画布挂载元素
64
- * 也就是初始化LogicFlow实例时传入的container
65
- */
66
- public readonly rootEl: HTMLElement
67
- readonly flowId?: string // 流程图 ID
68
- @observable width: number // 画布宽度
69
- @observable height: number // 画布高度
70
-
71
- // 流程图主题配置
72
- @observable theme: LogicFlow.Theme
73
- @observable themeMode: LogicFlow.ThemeMode | string = 'default'
74
- // 初始化样式
75
- customStyles: LogicFlow.Theme
76
- // 网格配置
77
- @observable grid: Grid.GridOptions
78
- // 事件中心
79
- readonly eventCenter: EventEmitter
80
- // 维护所有节点和边类型对应的 model
81
- readonly modelMap: Map<string, LogicFlow.GraphElementCtor> = new Map()
82
- /**
83
- * 位于当前画布顶部的元素
84
- * 此元素只在堆叠模式为默认模式下存在
85
- * 用于在默认模式下将之前的顶部元素回复初始高度
86
- */
87
- topElement?: BaseNodeModel | BaseEdgeModel
88
- // 控制是否开启动画
89
- animation?: boolean | LFOptions.AnimationConfig
90
- // 自定义全局 id 生成器
91
- idGenerator?: (type?: string) => string | undefined
92
- // 节点间连线、连线变更时的边的生成规则
93
- edgeGenerator: LFOptions.Definition['edgeGenerator']
94
- // 自定义目标锚点连接规则
95
- customTargetAnchor?: LFOptions.Definition['customTargetAnchor']
96
-
97
- // Remind:用于记录当前画布上所有节点和边的 model 的 Map
98
- // 现在的处理方式,用 this.nodes.map 生成的方式,如果在 new Model 的过程中依赖于其它节点的 model,会出现找不到的情况
99
- // eg: new DynamicGroupModel 时,需要获取当前 children 的 model,根据 groupModel 的 isCollapsed 状态更新子节点的 visible
100
- nodeModelMap: Map<string, BaseNodeModel> = new Map()
101
- edgeModelMap: Map<string, BaseEdgeModel> = new Map()
102
- elementsModelMap: Map<string, BaseNodeModel | BaseEdgeModel> = new Map()
103
-
104
- /**
105
- * 节点移动规则判断
106
- * 在节点移动的时候,会触发此数组中的所有规则判断
107
- */
108
- nodeMoveRules: Model.NodeMoveRule[] = []
109
- /**
110
- * 节点resize规则判断
111
- * 在节点resize的时候,会触发此数组中的所有规则判断
112
- */
113
- nodeResizeRules: Model.NodeResizeRule[] = []
114
-
115
- /**
116
- * 获取自定义连线轨迹
117
- */
118
- customTrajectory: LFOptions.Definition['customTrajectory']
119
-
120
- /**
121
- * 判断是否使用的是容器的宽度
122
- */
123
- isContainerWidth: boolean
124
- /**
125
- * 判断是否使用的是容器的高度
126
- */
127
- isContainerHeight: boolean
128
-
129
- // 在图上操作创建边时,默认使用的边类型.
130
- @observable edgeType: string
131
- // 当前图上所有节点的model
132
- @observable nodes: BaseNodeModel[] = []
133
- // 当前图上所有边的model
134
- @observable edges: BaseEdgeModel[] = []
135
- // 外部拖动节点进入画布的过程中,用fakeNode来和画布上正是的节点区分开
136
- @observable fakeNode?: BaseNodeModel | null
137
-
138
- /**
139
- * 元素重合时堆叠模式:
140
- * - DEFAULT(默认模式):节点和边被选中,会被显示在最上面。当取消选中后,元素会恢复之前的层级
141
- * - INCREASE(递增模式):节点和边被选中,会被显示在最上面。当取消选中后,元素会保持当前层级
142
- */
143
- @observable overlapMode = OverlapMode.DEFAULT
144
- // 背景配置
145
- @observable background?: boolean | LFOptions.BackgroundConfig
146
- // 网格大小
147
- @observable gridSize: number = 1
148
- // 控制画布的缩放、平移
149
- @observable transformModel: TransformModel
150
- // 控制流程图编辑相关配置项 Model
151
- @observable editConfigModel: EditConfigModel
152
- // 控制是否开启局部渲染
153
- @observable partial: boolean = false;
154
-
155
- // 用户自定义属性
156
- [propName: string]: any
157
-
158
- private waitCleanEffects: (() => void)[] = []
159
-
160
- constructor(options: LFOptions.Common) {
161
- const {
162
- container,
163
- partial,
164
- background = {},
165
- grid,
166
- idGenerator,
167
- edgeGenerator,
168
- animation,
169
- customTrajectory,
170
- customTargetAnchor,
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']
176
- this.rootEl = container
177
- this.partial = !!partial
178
- this.background = background
179
- if (typeof grid === 'object' && options.snapGrid) {
180
- // 开启网格对齐时才根据网格尺寸设置步长
181
- // TODO:需要让用户设置成 0 吗?后面可以讨论一下
182
- this.gridSize = grid.size || 1 // 默认 gridSize 设置为 1
183
- }
184
- this.customStyles = (options.style || {}) as LogicFlow.Theme
185
- this.theme = setupTheme(options.style, options.themeMode)
186
- this.grid = Grid.getGridOptions(assign({}, initialGrid, grid))
187
- this.theme.grid = cloneDeep(this.grid)
188
- if (background) {
189
- this.background = cloneDeep(assign({}, initialBackground, background))
190
- this.theme.background = cloneDeep(
191
- assign({}, initialBackground, background),
192
- )
193
- }
194
- this.edgeType = options.edgeType || 'polyline'
195
- this.animation = setupAnimation(animation)
196
- this.overlapMode = options.overlapMode || OverlapMode.DEFAULT
197
-
198
- this.isMiniMap = options.isMiniMap || false
199
- this.width = options.width ?? this.rootEl.getBoundingClientRect().width
200
- this.isContainerWidth = isNil(options.width)
201
- this.height = options.height ?? this.rootEl.getBoundingClientRect().height
202
- this.isContainerHeight = isNil(options.height)
203
-
204
- const resizeObserver = new ResizeObserver(
205
- debounce(
206
- ((entries) => {
207
- for (const entry of entries) {
208
- if (entry.target === this.rootEl) {
209
- // 检查元素是否仍在DOM中
210
- const isElementInDOM = document.body.contains(this.rootEl)
211
- if (!isElementInDOM) return
212
- this.resize()
213
- this.eventCenter.emit('graph:resize', {
214
- target: this.rootEl,
215
- contentRect: entry.contentRect,
216
- })
217
- }
218
- }
219
- }) as ResizeObserverCallback,
220
- 16,
221
- ),
222
- )
223
- resizeObserver.observe(this.rootEl)
224
- this.waitCleanEffects.push(() => {
225
- resizeObserver.disconnect()
226
- })
227
-
228
- this.eventCenter = new EventEmitter()
229
- this.editConfigModel = new EditConfigModel(options)
230
- this.transformModel = new NestedTransformModel(this.eventCenter, options)
231
-
232
- this.flowId = createUuid()
233
- this.idGenerator = idGenerator
234
- this.edgeGenerator = createEdgeGenerator(this, edgeGenerator)
235
- this.customTrajectory = customTrajectory
236
- this.customTargetAnchor = customTargetAnchor
237
- }
238
-
239
- @computed get nodesMap(): GraphModel.NodesMapType {
240
- return this.nodes.reduce((nMap, model, index) => {
241
- nMap[model.id] = {
242
- index,
243
- model,
244
- }
245
- return nMap
246
- }, {} as GraphModel.NodesMapType)
247
- }
248
-
249
- @computed get edgesMap(): GraphModel.EdgesMapType {
250
- return this.edges.reduce((eMap, model, index) => {
251
- eMap[model.id] = {
252
- index,
253
- model,
254
- }
255
- return eMap
256
- }, {})
257
- }
258
-
259
- @computed get modelsMap(): GraphModel.ModelsMapType {
260
- return [...this.nodes, ...this.edges].reduce((eMap, model) => {
261
- eMap[model.id] = model
262
- return eMap
263
- }, {})
264
- }
265
-
266
- /**
267
- * 基于zIndex对元素进行排序。
268
- * todo: 性能优化
269
- */
270
- @computed get sortElements() {
271
- const sortElement = (list) => {
272
- return [...list].sort((a, b) => a.zIndex - b.zIndex)
273
- }
274
- // 默认情况下节点与边按照 zIndex 排序
275
- const elements = sortElement([...this.nodes, ...this.edges])
276
-
277
- // 只显示可见区域的节点和边
278
- const visibleElements: (BaseNodeModel | BaseEdgeModel)[] = []
279
- // TODO: 缓存,优化计算效率 by xutao. So how?
280
- const visibleLt: PointTuple = [
281
- -DEFAULT_VISIBLE_SPACE,
282
- -DEFAULT_VISIBLE_SPACE,
283
- ]
284
- const visibleRb: PointTuple = [
285
- this.width + DEFAULT_VISIBLE_SPACE,
286
- this.height + DEFAULT_VISIBLE_SPACE,
287
- ]
288
- for (let i = 0; i < elements.length; i++) {
289
- const currentItem = elements[i]
290
- // 如果节点不在可见区域,且不是全元素显示模式,则隐藏节点。
291
- if (
292
- currentItem.visible &&
293
- (!this.partial ||
294
- currentItem.isSelected ||
295
- this.isElementInArea(currentItem, visibleLt, visibleRb, false, false))
296
- ) {
297
- visibleElements.push(currentItem)
298
- }
299
- }
300
- return visibleElements
301
- }
302
-
303
- /**
304
- * 当前编辑的元素,低频操作,先循环找。
305
- */
306
- @computed get textEditElement() {
307
- const textEditNode = this.nodes.find(
308
- (node) => node.state === ElementState.TEXT_EDIT,
309
- )
310
- const textEditEdge = this.edges.find(
311
- (edge) => edge.state === ElementState.TEXT_EDIT,
312
- )
313
- return textEditNode || textEditEdge
314
- }
315
-
316
- /**
317
- * 当前画布所有被选中的元素
318
- */
319
- @computed get selectElements() {
320
- const elements = new Map<string, BaseNodeModel | BaseEdgeModel>()
321
- this.nodes.forEach((node) => {
322
- if (node.isSelected) {
323
- elements.set(node.id, node)
324
- }
325
- })
326
- this.edges.forEach((edge) => {
327
- if (edge.isSelected) {
328
- elements.set(edge.id, edge)
329
- }
330
- })
331
- return elements
332
- }
333
-
334
- @computed get selectNodes() {
335
- const nodes: BaseNodeModel[] = []
336
- this.nodes.forEach((node) => {
337
- if (node.isSelected) {
338
- nodes.push(node)
339
- }
340
- })
341
- return nodes
342
- }
343
-
344
- /**
345
- * 获取指定区域内的所有元素
346
- * @param leftTopPoint 表示区域左上角的点
347
- * @param rightBottomPoint 表示区域右下角的点
348
- * @param wholeEdge 是否要整个边都在区域内部
349
- * @param wholeNode 是否要整个节点都在区域内部
350
- * @param ignoreHideElement 是否忽略隐藏的节点
351
- */
352
- // TODO: rename getAreaElement to getElementsInArea or getAreaElements
353
- getAreaElement(
354
- leftTopPoint: PointTuple,
355
- rightBottomPoint: PointTuple,
356
- wholeEdge = true,
357
- wholeNode = true,
358
- ignoreHideElement = false,
359
- ) {
360
- const areaElements: LogicFlow.GraphElement[] = []
361
- forEach([...this.nodes, ...this.edges], (element) => {
362
- const isElementInArea = this.isElementInArea(
363
- element,
364
- leftTopPoint,
365
- rightBottomPoint,
366
- wholeEdge,
367
- wholeNode,
368
- )
369
- if ((!ignoreHideElement || element.visible) && isElementInArea) {
370
- areaElements.push(element)
371
- }
372
- })
373
- return areaElements
374
- }
375
-
376
- /**
377
- * 获取指定类型元素对应的Model
378
- */
379
- getModel(type: string) {
380
- return this.modelMap.get(type)
381
- }
382
-
383
- /**
384
- * 基于Id获取节点的model
385
- */
386
- getNodeModelById(nodeId: string): BaseNodeModel | undefined {
387
- if (this.fakeNode && nodeId === this.fakeNode.id) {
388
- return this.fakeNode
389
- }
390
- return this.nodesMap[nodeId]?.model
391
- }
392
-
393
- /**
394
- * 因为流程图所在的位置可以是页面任何地方
395
- * 当内部事件需要获取触发事件时,其相对于画布左上角的位置
396
- * 需要事件触发位置减去画布相对于client的位置
397
- */
398
- getPointByClient({
399
- x: x1,
400
- y: y1,
401
- }: LogicFlow.Point): LogicFlow.ClientPosition {
402
- const bbox = this.rootEl.getBoundingClientRect()
403
- const domOverlayPosition: Position = {
404
- x: x1 - bbox.left,
405
- y: y1 - bbox.top,
406
- }
407
- const [x, y] = this.transformModel.HtmlPointToCanvasPoint([
408
- domOverlayPosition.x,
409
- domOverlayPosition.y,
410
- ])
411
- const canvasOverlayPosition: Position = { x, y }
412
- return {
413
- domOverlayPosition,
414
- canvasOverlayPosition,
415
- }
416
- }
417
-
418
- /**
419
- * 判断一个元素是否在指定矩形区域内。
420
- * @param element 节点或者边
421
- * @param lt 左上角点
422
- * @param rb 右下角点
423
- * @param wholeEdge 边的起点和终点都在区域内才算
424
- * @param wholeNode 节点的box都在区域内才算
425
- */
426
- isElementInArea(
427
- element: BaseEdgeModel | BaseNodeModel,
428
- lt: PointTuple,
429
- rb: PointTuple,
430
- wholeEdge = true,
431
- wholeNode = true,
432
- ) {
433
- if (element.BaseType === ElementType.NODE) {
434
- element = element as BaseNodeModel
435
- // 节点是否在选区内,判断逻辑为如果节点的bbox的四个角上的点都在选区内,则判断节点在选区内
436
- const { minX, minY, maxX, maxY } = getNodeBBox(element)
437
- const bboxPointsList: Position[] = [
438
- {
439
- x: minX,
440
- y: minY,
441
- },
442
- {
443
- x: maxX,
444
- y: minY,
445
- },
446
- {
447
- x: maxX,
448
- y: maxY,
449
- },
450
- {
451
- x: minX,
452
- y: maxY,
453
- },
454
- ]
455
- let inArea = wholeNode
456
- for (let i = 0; i < bboxPointsList.length; i++) {
457
- let { x, y } = bboxPointsList[i]
458
- ;[x, y] = this.transformModel.CanvasPointToHtmlPoint([x, y])
459
- if (isPointInArea([x, y], lt, rb) !== wholeNode) {
460
- inArea = !wholeNode
461
- break
462
- }
463
- }
464
- return inArea
465
- }
466
- if (element.BaseType === ElementType.EDGE) {
467
- element = element as BaseEdgeModel
468
- const { startPoint, endPoint } = element
469
- const startHtmlPoint = this.transformModel.CanvasPointToHtmlPoint([
470
- startPoint.x,
471
- startPoint.y,
472
- ])
473
- const endHtmlPoint = this.transformModel.CanvasPointToHtmlPoint([
474
- endPoint.x,
475
- endPoint.y,
476
- ])
477
- const isStartInArea = isPointInArea(startHtmlPoint, lt, rb)
478
- const isEndInArea = isPointInArea(endHtmlPoint, lt, rb)
479
- return wholeEdge
480
- ? isStartInArea && isEndInArea
481
- : isStartInArea || isEndInArea
482
- }
483
- return false
484
- }
485
-
486
- /**
487
- * 使用新的数据重新设置整个画布的元素
488
- * 注意:将会清除画布上所有已有的节点和边
489
- * @param { object } graphData 图数据
490
- */
491
- graphDataToModel(graphData: Partial<LogicFlow.GraphConfigData>) {
492
- // 宽度必然存在,取消重新计算
493
- // if (!this.width || !this.height) {
494
- // this.resize()
495
- // }
496
- if (!graphData) {
497
- this.clearData()
498
- return
499
- }
500
- this.elementsModelMap.clear()
501
- this.nodeModelMap.clear()
502
- this.edgeModelMap.clear()
503
- if (graphData.nodes) {
504
- this.nodes = map(graphData.nodes, (node: NodeConfig) => {
505
- const nodeModel = this.getModelAfterSnapToGrid(node)
506
- this.elementsModelMap.set(nodeModel.id, nodeModel)
507
- this.nodeModelMap.set(nodeModel.id, nodeModel)
508
- return nodeModel
509
- })
510
- } else {
511
- this.nodes = []
512
- }
513
- if (graphData.edges) {
514
- const currEdgeType = this.edgeType
515
- this.edges = map(graphData.edges, (edge) => {
516
- const Model = this.getModel(
517
- edge.type ?? currEdgeType,
518
- ) as BaseEdgeModelCtor
519
- if (!Model) {
520
- throw new Error(`找不到${edge.type}对应的边。`)
521
- }
522
- const edgeModel = new Model(edge, this)
523
- this.edgeModelMap.set(edgeModel.id, edgeModel)
524
- this.elementsModelMap.set(edgeModel.id, edgeModel)
525
-
526
- return edgeModel
527
- })
528
- } else {
529
- this.edges = []
530
- }
531
- }
532
-
533
- /**
534
- * 获取画布数据
535
- */
536
- modelToGraphData(): GraphData {
537
- const edges: LogicFlow.EdgeData[] = []
538
- this.edges.forEach((edge) => {
539
- const data = edge.getData()
540
- if (data && !edge.virtual) edges.push(data)
541
- })
542
- const nodes: LogicFlow.NodeData[] = []
543
- this.nodes.forEach((node) => {
544
- const data = node.getData()
545
- if (data && !node.virtual) nodes.push(data)
546
- })
547
- return {
548
- nodes,
549
- edges,
550
- }
551
- }
552
-
553
- // 用户history记录的数据,忽略拖拽过程中的数据变更
554
- modelToHistoryData(): GraphData | false {
555
- let nodeDragging = false
556
- const nodes: LogicFlow.NodeData[] = []
557
- // 如果有节点在拖拽中,不更新history
558
- for (let i = 0; i < this.nodes.length; i++) {
559
- const nodeModel = this.nodes[i]
560
- if (nodeModel.isDragging) {
561
- nodeDragging = true
562
- break
563
- } else {
564
- nodes.push(nodeModel.getHistoryData())
565
- }
566
- }
567
- if (nodeDragging) {
568
- return false
569
- }
570
- // 如果有边在拖拽中,不更新history
571
- let edgeDragging = false
572
- const edges: LogicFlow.EdgeData[] = []
573
- for (let j = 0; j < this.edges.length; j++) {
574
- const edgeMode = this.edges[j]
575
- if (edgeMode.isDragging) {
576
- edgeDragging = true
577
- break
578
- }
579
- if (!edgeMode.virtual) {
580
- edges.push(edgeMode.getHistoryData())
581
- }
582
- }
583
- if (edgeDragging) {
584
- return false
585
- }
586
- return {
587
- nodes,
588
- edges,
589
- }
590
- }
591
-
592
- /**
593
- * 获取边的model
594
- */
595
- getEdgeModelById(edgeId: string): BaseEdgeModel | undefined {
596
- return this.edgesMap[edgeId]?.model
597
- }
598
-
599
- /**
600
- * 获取节点或者边的model
601
- */
602
- getElement(id: string): BaseNodeModel | BaseEdgeModel | undefined {
603
- return this.modelsMap[id]
604
- }
605
-
606
- /**
607
- * 所有节点上所有边的model
608
- */
609
- getNodeEdges(nodeId: string): BaseEdgeModel[] {
610
- const edges: BaseEdgeModel[] = []
611
- for (let i = 0; i < this.edges.length; i++) {
612
- const edgeModel = this.edges[i]
613
- const nodeAsSource = edgeModel.sourceNodeId === nodeId
614
- const nodeAsTarget = edgeModel.targetNodeId === nodeId
615
- if (nodeAsSource || nodeAsTarget) {
616
- edges.push(edgeModel)
617
- }
618
- }
619
- return edges
620
- }
621
-
622
- /**
623
- * 获取选中的元素数据
624
- * @param isIgnoreCheck 是否包括sourceNode和targetNode没有被选中的边,默认包括。
625
- * 复制的时候不能包括此类边, 因为复制的时候不允许悬空的边
626
- */
627
- getSelectElements(isIgnoreCheck = true): GraphData {
628
- const elements = this.selectElements
629
- const graphData: GraphData = {
630
- nodes: [],
631
- edges: [],
632
- }
633
- elements.forEach((element) => {
634
- if (element.BaseType === ElementType.NODE) {
635
- graphData.nodes.push(element.getData())
636
- }
637
- if (element.BaseType === ElementType.EDGE) {
638
- const edgeData = element.getData()
639
- const isNodeSelected =
640
- elements.get(edgeData.sourceNodeId) &&
641
- elements.get(edgeData.targetNodeId)
642
-
643
- if (isIgnoreCheck || isNodeSelected) {
644
- graphData.edges.push(edgeData)
645
- }
646
- }
647
- })
648
- return graphData
649
- }
650
-
651
- /**
652
- * 修改对应元素 model 中的属性
653
- * 注意:此方法慎用,除非您对logicflow内部有足够的了解。
654
- * 大多数情况下,请使用setProperties、updateText、changeNodeId等方法。
655
- * 例如直接使用此方法修改节点的id,那么就是会导致连接到此节点的边的sourceNodeId出现找不到的情况。
656
- * @param {string} id 元素id
657
- * @param {object} attributes 需要更新的属性
658
- */
659
- updateAttributes(id: string, attributes: object) {
660
- const element = this.getElement(id)
661
- element?.updateAttributes(attributes)
662
- }
663
-
664
- /**
665
- * 修改节点的id, 如果不传新的id,会内部自动创建一个。
666
- * @param { string } nodeId 将要被修改的id
667
- * @param { string } newId 可选,修改后的id
668
- * @returns 修改后的节点id, 如果传入的oldId不存在,返回空字符串
669
- */
670
- changeNodeId(nodeId: string, newId?: string): string {
671
- if (!newId) {
672
- newId = createUuid()
673
- }
674
- if (this.nodesMap[newId]) {
675
- console.warn(`当前流程图已存在节点${newId}, 修改失败`)
676
- return ''
677
- }
678
- if (!this.nodesMap[nodeId]) {
679
- console.warn(`当前流程图找不到节点${nodeId}, 修改失败`)
680
- return ''
681
- }
682
- this.edges.forEach((edge) => {
683
- if (edge.sourceNodeId === nodeId) {
684
- edge.sourceNodeId = newId as string
685
- }
686
- if (edge.targetNodeId === nodeId) {
687
- edge.targetNodeId = newId as string
688
- }
689
- })
690
- this.nodesMap[nodeId].model.id = newId
691
- this.nodesMap[newId] = this.nodesMap[nodeId]
692
- return newId
693
- }
694
-
695
- /**
696
- * 修改边的id, 如果不传新的id,会内部自动创建一个。
697
- * @param { string } oldId 将要被修改的id
698
- * @param { string } newId 可选,修改后的id
699
- * @returns 修改后的节点id, 如果传入的oldId不存在,返回空字符串
700
- */
701
- changeEdgeId<T extends string>(oldId: string, newId?: string): T | string {
702
- if (!newId) {
703
- newId = createUuid()
704
- }
705
- if (this.edgesMap[newId]) {
706
- console.warn(`当前流程图已存在边: ${newId}, 修改失败`)
707
- return ''
708
- }
709
- if (!this.edgesMap[oldId]) {
710
- console.warn(`当前流程图找不到边: ${newId}, 修改失败`)
711
- return ''
712
- }
713
- this.edges.forEach((edge) => {
714
- if (edge.id === oldId) {
715
- // edge.id = newId
716
- edge.changeEdgeId(newId as string)
717
- }
718
- })
719
- return newId
720
- }
721
-
722
- /**
723
- * 获取元素的文本模式
724
- * @param model
725
- */
726
- getTextModel(model: BaseNodeModel): TextMode | undefined {
727
- const { textMode, nodeTextMode, edgeTextMode } = this.editConfigModel
728
-
729
- // textMode 的优先级:
730
- // 元素自身 model.textMode > editConfigModel.node(edge)TextMode > editConfigModel.textMode
731
- if (model.BaseType === ElementType.NODE) {
732
- return model.textMode || nodeTextMode || textMode || TextMode.TEXT
733
- }
734
-
735
- // 同上
736
- if (model.BaseType === ElementType.EDGE) {
737
- return model.textMode || edgeTextMode || textMode || TextMode.TEXT
738
- }
739
- }
740
-
741
- /**
742
- * 内部保留方法,请勿直接使用
743
- */
744
-
745
- /**
746
- * 设置重叠模式
747
- * @param mode 重叠模式
748
- */
749
- @action
750
- setOverlapMode(mode: OverlapMode) {
751
- this.overlapMode = mode
752
- this.eventCenter.emit('overlap:change', {
753
- overlapMode: mode,
754
- })
755
- }
756
-
757
- /**
758
- * 更新元素的文本模式
759
- * @param mode
760
- * @param model
761
- */
762
- @action
763
- setTextMode(mode: TextMode, model?: BaseNodeModel | BaseEdgeModel) {
764
- // 如果有传入 model,则直接更新 model 的 textMode
765
- if (model) {
766
- // model.updateTextMode(mode)
767
- }
768
- // 调用 editConfigModel 的方法更新 textMode
769
- this.editConfigModel.updateEditConfig({ textMode: mode })
770
- }
771
-
772
- /**
773
- * 内部保留方法,请勿直接使用
774
- */
775
- @action
776
- setFakeNode(nodeModel: BaseNodeModel) {
777
- this.fakeNode = nodeModel
778
- }
779
-
780
- /**
781
- * 内部保留方法,请勿直接使用
782
- */
783
- @action
784
- removeFakeNode() {
785
- this.fakeNode = null
786
- }
787
-
788
- /**
789
- * 设置指定类型的Model,请勿直接使用
790
- */
791
- @action
792
- setModel(type: string, ModelClass: LogicFlow.GraphElementCtor) {
793
- return this.modelMap.set(type, ModelClass)
794
- }
795
-
796
- /**
797
- * 将某个元素放置到顶部。
798
- * 如果堆叠模式为默认模式,则将原置顶元素重新恢复原有层级。
799
- * 如果堆叠模式为递增模式,则将需指定元素zIndex设置为当前最大zIndex + 1。
800
- * @see todo link 堆叠模式
801
- * @param id 元素Id
802
- */
803
- @action
804
- toFront(id: string) {
805
- const element = this.nodesMap[id]?.model || this.edgesMap[id]?.model
806
- if (element) {
807
- // 静态模式toFront不做处理
808
- if (this.overlapMode === OverlapMode.STATIC) {
809
- return
810
- }
811
- // 递增模式下,将需指定元素zIndex设置为当前最大zIndex + 1
812
- if (this.overlapMode === OverlapMode.INCREASE) {
813
- this.setElementZIndex(id, 'top')
814
- return
815
- }
816
- // 默认模式(节点在上)和边在上模式下,将原置顶元素重新恢复原有层级,将需指定元素zIndex设置为最大zIndex
817
- this.topElement?.setZIndex()
818
- element.setZIndex(ELEMENT_MAX_Z_INDEX)
819
- this.topElement = element
820
- }
821
- }
822
-
823
- /**
824
- * 设置元素的zIndex.
825
- * 注意:默认堆叠模式下,不建议使用此方法。
826
- * @see todo link 堆叠模式
827
- * @param id 元素id
828
- * @param zIndex zIndex的值,可以传数字,也支持传入 'top' 和 'bottom'
829
- */
830
- @action
831
- setElementZIndex(id: string, zIndex: number | 'top' | 'bottom') {
832
- const element = this.nodesMap[id]?.model || this.edgesMap[id]?.model
833
- if (element) {
834
- let index: number
835
- if (typeof zIndex === 'number') {
836
- index = zIndex
837
- } else {
838
- if (zIndex === 'top') {
839
- index = getZIndex()
840
- }
841
- if (zIndex === 'bottom') {
842
- index = getMinIndex()
843
- }
844
- }
845
- element.setZIndex(index!)
846
- }
847
- }
848
-
849
- /**
850
- * 删除节点
851
- * @param {string} nodeId 节点Id
852
- */
853
- @action
854
- deleteNode(nodeId: string) {
855
- const nodeModel = this.nodesMap[nodeId].model
856
- const nodeData = nodeModel.getData()
857
- this.deleteEdgeBySource(nodeId)
858
- this.deleteEdgeByTarget(nodeId)
859
- this.nodes.splice(this.nodesMap[nodeId].index, 1)
860
- this.eventCenter.emit(EventType.NODE_DELETE, {
861
- data: nodeData,
862
- model: nodeModel,
863
- })
864
- }
865
-
866
- /**
867
- * 添加节点
868
- * @param nodeConfig 节点配置
869
- * @param eventType 新增节点事件类型,默认EventType.NODE_ADD, 在Dnd拖拽时,会传入EventType.NODE_DND_ADD
870
- * @param event MouseEvent 鼠标事件
871
- */
872
- @action
873
- addNode(
874
- nodeConfig: NodeConfig,
875
- eventType: EventType = EventType.NODE_ADD,
876
- event?: MouseEvent,
877
- ) {
878
- const originNodeData = formatData(nodeConfig)
879
- // 添加节点的时候,如果这个节点 id 已经存在,则采用新 id
880
- const { id } = originNodeData
881
- if (id && this.nodesMap[id]) {
882
- delete originNodeData.id
883
- }
884
- const nodeModel = this.getModelAfterSnapToGrid(originNodeData)
885
- this.nodes.push(nodeModel)
886
- const nodeData = nodeModel.getData()
887
- const eventData: Record<string, any> = { data: nodeData }
888
- if (event) {
889
- eventData.e = event
890
- }
891
- this.eventCenter.emit(eventType, eventData)
892
- return nodeModel
893
- }
894
-
895
- /**
896
- * 将node节点位置进行grid修正
897
- * 同时处理node内文字的偏移量
898
- * 返回一个位置修正过的复制节点NodeModel
899
- * @param node
900
- */
901
- getModelAfterSnapToGrid(node: NodeConfig) {
902
- const Model = this.getModel(node.type) as BaseNodeModelCtor
903
- const { snapGrid } = this.editConfigModel
904
- if (!Model) {
905
- throw new Error(
906
- `找不到${node.type}对应的节点,请确认是否已注册此类型节点。`,
907
- )
908
- }
909
- const { x: nodeX, y: nodeY } = node
910
- // 根据 grid 修正节点的 x, y
911
- if (nodeX && nodeY) {
912
- node.x = snapToGrid(nodeX, this.gridSize, snapGrid)
913
- node.y = snapToGrid(nodeY, this.gridSize, snapGrid)
914
- if (typeof node.text === 'object' && node.text !== null) {
915
- // 原来的处理是:node.text.x -= getGridOffset(nodeX, this.gridSize)
916
- // 由于snapToGrid()使用了Math.round()四舍五入的做法,因此无法判断需要执行
917
- // node.text.x = node.text.x + getGridOffset()
918
- // 还是
919
- // node.text.x = node.text.x - getGridOffset()
920
- // 直接改为node.x - nodeX就可以满足上面的要求
921
- node.text.x += node.x - nodeX
922
- node.text.y += node.y - nodeY
923
- }
924
- }
925
- const nodeModel = new Model(node, this)
926
- this.nodeModelMap.set(nodeModel.id, nodeModel)
927
- this.elementsModelMap.set(nodeModel.id, nodeModel)
928
-
929
- return nodeModel
930
- }
931
-
932
- /**
933
- * 克隆节点
934
- * @param nodeId 节点Id
935
- */
936
- @action
937
- cloneNode(nodeId: string) {
938
- const targetNode = this.getNodeModelById(nodeId)
939
- const data = targetNode?.getData()
940
- if (data) {
941
- data.x += 30
942
- data.y += 30
943
- data.id = ''
944
- if (typeof data.text === 'object' && data.text !== null) {
945
- data.text.x += 30
946
- data.text.y += 30
947
- }
948
- const nodeModel = this.addNode(data)
949
- nodeModel.setSelected(true)
950
- targetNode?.setSelected(false)
951
- return nodeModel.getData()
952
- }
953
- }
954
-
955
- /**
956
- * 移动节点-相对位置
957
- * @param nodeId 节点Id
958
- * @param deltaX X轴移动距离
959
- * @param deltaY Y轴移动距离
960
- * @param isIgnoreRule 是否忽略移动规则限制
961
- */
962
- @action
963
- moveNode(
964
- nodeId: string,
965
- deltaX: number,
966
- deltaY: number,
967
- isIgnoreRule = false,
968
- ) {
969
- // 1) 移动节点
970
- const node = this.nodesMap[nodeId]
971
- if (!node) {
972
- console.warn(`不存在id为${nodeId}的节点`)
973
- return
974
- }
975
- const nodeModel = node.model
976
- ;[deltaX, deltaY] = nodeModel.getMoveDistance(deltaX, deltaY, isIgnoreRule)
977
- // 2) 移动边
978
- this.moveEdge(nodeId, deltaX, deltaY)
979
- }
980
-
981
- /**
982
- * 移动节点-绝对位置
983
- * @param nodeId 节点Id
984
- * @param x X轴目标位置
985
- * @param y Y轴目标位置
986
- * @param isIgnoreRule 是否忽略条件,默认为 false
987
- */
988
- @action
989
- moveNode2Coordinate(
990
- nodeId: string,
991
- x: number,
992
- y: number,
993
- isIgnoreRule = false,
994
- ) {
995
- // 1) 移动节点
996
- const node = this.nodesMap[nodeId]
997
- if (!node) {
998
- console.warn(`不存在id为${nodeId}的节点`)
999
- return
1000
- }
1001
- const nodeModel = node.model
1002
- const { x: originX, y: originY } = nodeModel
1003
- const deltaX = x - originX
1004
- const deltaY = y - originY
1005
- this.moveNode(nodeId, deltaX, deltaY, isIgnoreRule)
1006
- }
1007
-
1008
- /**
1009
- * 显示节点、连线文本编辑框
1010
- * @param id 节点 or 连线 id
1011
- */
1012
- @action
1013
- editText(id: string) {
1014
- this.setElementStateById(id, ElementState.TEXT_EDIT)
1015
- }
1016
-
1017
- /**
1018
- * 给两个节点之间添加一条边
1019
- * @param {object} edgeConfig
1020
- */
1021
- @action
1022
- addEdge(edgeConfig: LogicFlow.EdgeConfig): BaseEdgeModel {
1023
- const edgeOriginData = formatData(edgeConfig)
1024
- // 边的类型优先级:自定义>全局>默认
1025
- let { type } = edgeOriginData
1026
- if (!type) {
1027
- type = this.edgeType
1028
- }
1029
- if (edgeOriginData.id && this.edgesMap[edgeOriginData.id]) {
1030
- delete edgeOriginData.id
1031
- delete edgeOriginData.sourceAnchorId
1032
- delete edgeOriginData.targetAnchorId
1033
- }
1034
- const Model = this.getModel(type) as BaseEdgeModelCtor
1035
- if (!Model) {
1036
- throw new Error(`找不到${type}对应的边,请确认是否已注册此类型边。`)
1037
- }
1038
- const edgeModel = new Model(
1039
- {
1040
- ...edgeOriginData,
1041
- type,
1042
- },
1043
- this,
1044
- )
1045
- this.edgeModelMap.set(edgeModel.id, edgeModel)
1046
- this.elementsModelMap.set(edgeModel.id, edgeModel)
1047
-
1048
- const edgeData = edgeModel.getData()
1049
- this.edges.push(edgeModel)
1050
- this.eventCenter.emit(EventType.EDGE_ADD, { data: edgeData })
1051
- return edgeModel
1052
- }
1053
-
1054
- /**
1055
- * 移动边,内部方法,请勿直接使用
1056
- */
1057
- @action
1058
- moveEdge(nodeId: string, deltaX: number, deltaY: number) {
1059
- /* 更新相关边位置 */
1060
- for (let i = 0; i < this.edges.length; i++) {
1061
- const edgeModel = this.edges[i]
1062
- const { x, y } = edgeModel.textPosition
1063
- const nodeAsSource = this.edges[i].sourceNodeId === nodeId
1064
- const nodeAsTarget = this.edges[i].targetNodeId === nodeId
1065
- if (nodeAsSource) {
1066
- edgeModel.moveStartPoint(deltaX, deltaY)
1067
- }
1068
- if (nodeAsTarget) {
1069
- edgeModel.moveEndPoint(deltaX, deltaY)
1070
- }
1071
- // 如果有文案了,当节点移动引起文案位置修改时,找出当前文案位置与最新边距离最短距离的点
1072
- // 最大程度保持节点位置不变且在边上
1073
- if (nodeAsSource || nodeAsTarget) {
1074
- this.handleEdgeTextMove(edgeModel, x, y)
1075
- }
1076
- }
1077
- }
1078
-
1079
- /**
1080
- * 如果有文案了,当节点移动引起文案位置修改时,找出当前文案位置与最新边距离最短距离的点
1081
- * 最大程度保持节点位置不变且在边上
1082
- * @param edgeModel 边的数据管理类
1083
- * @param x X轴移动距离
1084
- * @param y Y轴移动距离
1085
- */
1086
- handleEdgeTextMove(edgeModel: BaseEdgeModel, x: number, y: number) {
1087
- // todo: 找到更好的边位置移动处理方式
1088
- // 如果是自定义边文本位置,则移动节点的时候重新计算其位置
1089
- if (edgeModel.customTextPosition) {
1090
- edgeModel.resetTextPosition()
1091
- return
1092
- }
1093
- if (
1094
- edgeModel.modelType === ModelType.POLYLINE_EDGE &&
1095
- edgeModel.text?.value
1096
- ) {
1097
- const textPosition = edgeModel.text
1098
- const newPoint = getClosestPointOfPolyline(textPosition, edgeModel.points)
1099
- edgeModel.moveText(
1100
- newPoint.x - textPosition.x,
1101
- newPoint.y - textPosition.y,
1102
- )
1103
- }
1104
- const { x: x1, y: y1 } = edgeModel.textPosition
1105
- edgeModel.moveText(x1 - x, y1 - y)
1106
- }
1107
-
1108
- /**
1109
- * 删除两节点之间的边
1110
- * @param sourceNodeId 边的起始节点
1111
- * @param targetNodeId 边的目的节点
1112
- */
1113
- @action
1114
- deleteEdgeBySourceAndTarget(sourceNodeId: string, targetNodeId: string) {
1115
- for (let i = 0; i < this.edges.length; i++) {
1116
- if (
1117
- this.edges[i].sourceNodeId === sourceNodeId &&
1118
- this.edges[i].targetNodeId === targetNodeId
1119
- ) {
1120
- const edgeData = this.edges[i].getData()
1121
- this.edges.splice(i, 1)
1122
- i--
1123
- this.eventCenter.emit(EventType.EDGE_DELETE, { data: edgeData })
1124
- }
1125
- }
1126
- }
1127
-
1128
- /**
1129
- * 基于边Id删除边
1130
- */
1131
- @action
1132
- deleteEdgeById(id: string) {
1133
- const edge = this.edgesMap[id]
1134
- if (!edge) {
1135
- return
1136
- }
1137
- const idx = this.edgesMap[id].index
1138
- const edgeData = this.edgesMap[id].model.getData()
1139
- this.edges.splice(idx, 1)
1140
- this.eventCenter.emit(EventType.EDGE_DELETE, { data: edgeData })
1141
- }
1142
-
1143
- /**
1144
- * 删除以节点Id为起点的所有边
1145
- */
1146
- @action
1147
- deleteEdgeBySource(sourceNodeId: string) {
1148
- for (let i = 0; i < this.edges.length; i++) {
1149
- if (this.edges[i].sourceNodeId === sourceNodeId) {
1150
- const edgeData = this.edges[i].getData()
1151
- this.edges.splice(i, 1)
1152
- i--
1153
- this.eventCenter.emit(EventType.EDGE_DELETE, { data: edgeData })
1154
- }
1155
- }
1156
- }
1157
-
1158
- /**
1159
- * 删除以节点Id为终点的所有边
1160
- */
1161
- @action
1162
- deleteEdgeByTarget(targetNodeId: string) {
1163
- for (let i = 0; i < this.edges.length; i++) {
1164
- if (this.edges[i].targetNodeId === targetNodeId) {
1165
- const edgeData = this.edges[i].getData()
1166
- this.edges.splice(i, 1)
1167
- i--
1168
- this.eventCenter.emit(EventType.EDGE_DELETE, { data: edgeData })
1169
- }
1170
- }
1171
- }
1172
-
1173
- /**
1174
- * 设置元素的状态,在需要保证整个画布上所有的元素只有一个元素拥有此状态时可以调用此方法。
1175
- * 例如文本编辑、菜单显示等。
1176
- * additionStateData: 传递的额外值,如菜单显示的时候,需要传递期望菜单显示的位置。
1177
- */
1178
- @action
1179
- setElementStateById(
1180
- id: string,
1181
- state: ElementState,
1182
- additionStateData?: Model.AdditionStateDataType,
1183
- ) {
1184
- this.nodes.forEach((node) => {
1185
- if (node.id === id) {
1186
- node.setElementState(state, additionStateData)
1187
- } else {
1188
- node.setElementState(ElementState.DEFAULT)
1189
- }
1190
- })
1191
- this.edges.forEach((edge) => {
1192
- if (edge.id === id) {
1193
- edge.setElementState(state, additionStateData)
1194
- } else {
1195
- edge.setElementState(ElementState.DEFAULT)
1196
- }
1197
- })
1198
- }
1199
-
1200
- /**
1201
- * 更新节点或边的文案
1202
- * @param id 节点或者边id
1203
- * @param value 文案内容
1204
- */
1205
- @action
1206
- updateText(id: string, value: string) {
1207
- const element = find(
1208
- [...this.nodes, ...this.edges],
1209
- (item) => item.id === id,
1210
- )
1211
- element?.updateText(value)
1212
- }
1213
-
1214
- /**
1215
- * 选中节点
1216
- * @param id 节点Id
1217
- * @param multiple 是否为多选,如果为多选,则不去掉原有已选择节点的选中状态
1218
- */
1219
- @action selectNodeById(id: string, multiple = false) {
1220
- if (!multiple) {
1221
- this.clearSelectElements()
1222
- }
1223
- const selectElement = this.nodesMap[id]?.model
1224
- selectElement?.setSelected(true)
1225
- }
1226
-
1227
- /**
1228
- * 选中边
1229
- * @param id 边Id
1230
- * @param multiple 是否为多选,如果为多选,则不去掉原已选中边的状态
1231
- */
1232
- @action selectEdgeById(id: string, multiple = false) {
1233
- if (!multiple) {
1234
- this.clearSelectElements()
1235
- }
1236
- const selectElement = this.edgesMap[id]?.model
1237
- selectElement?.setSelected(true)
1238
- }
1239
-
1240
- /**
1241
- * 将图形选中
1242
- * @param id 选择元素ID
1243
- * @param multiple 是否允许多选,如果为true,不会将上一个选中的元素重置
1244
- */
1245
- @action
1246
- selectElementById(id: string, multiple = false) {
1247
- if (!multiple) {
1248
- this.clearSelectElements()
1249
- }
1250
- const selectElement = this.getElement(id)
1251
- selectElement?.setSelected(true)
1252
- }
1253
-
1254
- @action
1255
- deselectElementById(id: string) {
1256
- const element = this.getElement(id)
1257
- if (element) {
1258
- element.setSelected(false)
1259
- }
1260
- }
1261
-
1262
- /**
1263
- * 将所有选中的元素设置为非选中
1264
- */
1265
- @action
1266
- clearSelectElements() {
1267
- this.selectElements.forEach((element) => {
1268
- element?.setSelected(false)
1269
- element?.setHovered(false)
1270
- })
1271
- this.selectElements.clear()
1272
- /**
1273
- * 如果堆叠模式为默认模式,则将置顶元素重新恢复原有层级
1274
- */
1275
- if (
1276
- [OverlapMode.DEFAULT, OverlapMode.EDGE_TOP].includes(this.overlapMode)
1277
- ) {
1278
- this.topElement?.setZIndex()
1279
- }
1280
- }
1281
-
1282
- /**
1283
- * 批量移动节点,节点移动的时候,会动态计算所有节点与未移动节点的边位置
1284
- * 移动的节点之间的边会保持相对位置
1285
- */
1286
- @action
1287
- moveNodes(
1288
- nodeIds: string[],
1289
- deltaX: number,
1290
- deltaY: number,
1291
- isIgnoreRule = false,
1292
- ) {
1293
- // FIX: https://github.com/didi/LogicFlow/issues/1015
1294
- // 如果节点之间存在连线,则只移动连线一次。
1295
- const nodeIdMap: Record<string, [number, number]> = nodeIds.reduce(
1296
- (acc, cur) => {
1297
- const nodeModel = this.nodesMap[cur]?.model
1298
- if (nodeModel) {
1299
- acc[cur] = nodeModel.getMoveDistance(deltaX, deltaY, isIgnoreRule)
1300
- }
1301
- return acc
1302
- },
1303
- {},
1304
- )
1305
- for (let i = 0; i < this.edges.length; i++) {
1306
- const edgeModel = this.edges[i]
1307
- const { x, y } = edgeModel.textPosition
1308
- const sourceMoveDistance = nodeIdMap[edgeModel.sourceNodeId]
1309
- const targetMoveDistance = nodeIdMap[edgeModel.targetNodeId]
1310
- let textDistanceX: number
1311
- let textDistanceY: number
1312
- if (
1313
- sourceMoveDistance &&
1314
- targetMoveDistance &&
1315
- edgeModel.modelType === ModelType.POLYLINE_EDGE
1316
- ) {
1317
- // 移动框选区时,如果边polyline在框选范围内,则边的轨迹pointsList也要整体移动
1318
- ;[textDistanceX, textDistanceY] = sourceMoveDistance
1319
- ;(edgeModel as PolylineEdgeModel).updatePointsList(
1320
- textDistanceX,
1321
- textDistanceY,
1322
- )
1323
- } else {
1324
- if (sourceMoveDistance) {
1325
- ;[textDistanceX, textDistanceY] = sourceMoveDistance
1326
- edgeModel.moveStartPoint(textDistanceX, textDistanceY)
1327
- }
1328
- if (targetMoveDistance) {
1329
- ;[textDistanceX, textDistanceY] = targetMoveDistance
1330
- edgeModel.moveEndPoint(textDistanceX, textDistanceY)
1331
- }
1332
- }
1333
- if (sourceMoveDistance || targetMoveDistance) {
1334
- // https://github.com/didi/LogicFlow/issues/1191
1335
- // moveNode()跟moveNodes()没有统一处理方式,moveNodes()缺失了下面的逻辑
1336
- // moveNode():当节点移动引起文案位置修改时,找出当前文案位置与最新边距离最短距离的点,最大程度保持节点位置不变且在边上
1337
- // 因此将moveNode()处理边文字的逻辑抽离出来,统一moveNode()跟moveNodes()的处理逻辑
1338
- this.handleEdgeTextMove(edgeModel, x, y)
1339
- }
1340
- }
1341
- }
1342
-
1343
- /**
1344
- * 添加节点移动限制规则,在节点移动的时候触发。
1345
- * 如果方法返回false, 则会阻止节点移动。
1346
- * @param fn function
1347
- * @example
1348
- *
1349
- * graphModel.addNodeMoveRules((nodeModel, x, y) => {
1350
- * if (nodeModel.properties.disabled) {
1351
- * return false
1352
- * }
1353
- * return true
1354
- * })
1355
- *
1356
- */
1357
- addNodeMoveRules(fn: Model.NodeMoveRule) {
1358
- if (!this.nodeMoveRules.includes(fn)) {
1359
- this.nodeMoveRules.push(fn)
1360
- }
1361
- }
1362
-
1363
- addNodeResizeRules(fn: Model.NodeResizeRule) {
1364
- if (!this.nodeResizeRules.includes(fn)) {
1365
- this.nodeResizeRules.push(fn)
1366
- }
1367
- }
1368
-
1369
- /**
1370
- * 设置默认的边类型
1371
- * 也就是设置在节点直接有用户手动绘制的连线类型。
1372
- * @param type LFOptions.EdgeType
1373
- */
1374
- @action
1375
- setDefaultEdgeType(type: LFOptions.EdgeType): void {
1376
- this.edgeType = type
1377
- }
1378
-
1379
- /**
1380
- * 修改指定节点类型
1381
- * @param id 节点id
1382
- * @param type 节点类型
1383
- */
1384
- @action
1385
- changeNodeType(id: string, type: string): void {
1386
- const nodeModel = this.getNodeModelById(id)
1387
- if (!nodeModel) {
1388
- console.warn(`找不到id为${id}的节点`)
1389
- return
1390
- }
1391
- const data = nodeModel.getData()
1392
- data.type = type
1393
- const Model = this.getModel(type) as BaseNodeModelCtor
1394
- if (!Model) {
1395
- throw new Error(`找不到${type}对应的节点,请确认是否已注册此类型节点。`)
1396
- }
1397
- const newNodeModel = new Model(data, this)
1398
- this.nodes.splice(this.nodesMap[id].index, 1, newNodeModel)
1399
- // 微调边
1400
- const edgeModels = this.getNodeEdges(id)
1401
- edgeModels.forEach((edge) => {
1402
- if (edge.sourceNodeId === id) {
1403
- const point = getNodeAnchorPosition(
1404
- newNodeModel,
1405
- edge.startPoint,
1406
- newNodeModel.width,
1407
- newNodeModel.height,
1408
- )
1409
- edge.updateStartPoint(point)
1410
- }
1411
- if (edge.targetNodeId === id) {
1412
- const point = getNodeAnchorPosition(
1413
- newNodeModel,
1414
- edge.endPoint,
1415
- newNodeModel.width,
1416
- newNodeModel.height,
1417
- )
1418
- edge.updateEndPoint(point)
1419
- }
1420
- })
1421
- }
1422
-
1423
- /**
1424
- * 切换边的类型
1425
- * @param id 边Id
1426
- * @param type 边类型
1427
- */
1428
- @action changeEdgeType(id: string, type: LFOptions.EdgeType) {
1429
- const edgeModel = this.getEdgeModelById(id)
1430
- if (!edgeModel) {
1431
- console.warn(`找不到id为${id}的边`)
1432
- return
1433
- }
1434
- if (edgeModel.type === type) {
1435
- return
1436
- }
1437
- const data = edgeModel.getData()
1438
- data.type = type
1439
- const Model = this.getModel(type) as BaseEdgeModelCtor
1440
- if (!Model) {
1441
- throw new Error(`找不到${type}对应的节点,请确认是否已注册此类型节点。`)
1442
- }
1443
- // 为了保持切换类型时不复用上一个类型的轨迹
1444
- delete data.pointsList
1445
- const newEdgeModel = new Model(data, this)
1446
- this.edges.splice(this.edgesMap[id].index, 1, newEdgeModel)
1447
- }
1448
-
1449
- /**
1450
- * 获取所有以此节点为终点的边
1451
- */
1452
- @action getNodeIncomingEdge(nodeId: string) {
1453
- const edges: BaseEdgeModel[] = []
1454
- this.edges.forEach((edge) => {
1455
- if (edge.targetNodeId === nodeId) {
1456
- edges.push(edge)
1457
- }
1458
- })
1459
- return edges
1460
- }
1461
-
1462
- /**
1463
- * 获取所有以此节点为起点的边
1464
- */
1465
- @action getNodeOutgoingEdge(nodeId: string) {
1466
- const edges: BaseEdgeModel[] = []
1467
- this.edges.forEach((edge) => {
1468
- if (edge.sourceNodeId === nodeId) {
1469
- edges.push(edge)
1470
- }
1471
- })
1472
- return edges
1473
- }
1474
-
1475
- /**
1476
- * 获取所有以此锚点为终点的边
1477
- */
1478
- @action getAnchorIncomingEdge(anchorId?: string) {
1479
- const edges: BaseEdgeModel[] = []
1480
- this.edges.forEach((edge) => {
1481
- if (edge.targetAnchorId === anchorId) {
1482
- edges.push(edge)
1483
- }
1484
- })
1485
- return edges
1486
- }
1487
-
1488
- /**
1489
- * 获取所有以此锚点为起点的边
1490
- */
1491
- @action getAnchorOutgoingEdge(anchorId?: string) {
1492
- const edges: BaseEdgeModel[] = []
1493
- this.edges.forEach((edge) => {
1494
- if (edge.sourceAnchorId === anchorId) {
1495
- edges.push(edge)
1496
- }
1497
- })
1498
- return edges
1499
- }
1500
-
1501
- /**
1502
- * 获取节点连接到的所有起始节点
1503
- */
1504
- @action getNodeIncomingNode(nodeId?: string) {
1505
- const nodes: BaseNodeModel[] = []
1506
- this.edges.forEach((edge) => {
1507
- if (edge.targetNodeId === nodeId) {
1508
- nodes.push(this.nodesMap[edge.sourceNodeId]?.model)
1509
- }
1510
- })
1511
- return nodes
1512
- }
1513
-
1514
- /**
1515
- * 获取节点所有的下一级节点
1516
- */
1517
- @action getNodeOutgoingNode(nodeId?: string) {
1518
- const nodes: BaseNodeModel[] = []
1519
- this.edges.forEach((edge) => {
1520
- if (edge.sourceNodeId === nodeId) {
1521
- nodes.push(this.nodesMap[edge.targetNodeId].model)
1522
- }
1523
- })
1524
- return nodes
1525
- }
1526
-
1527
- /**
1528
- * 设置主题
1529
- * todo docs link
1530
- */
1531
- @action setTheme(
1532
- style: Partial<LogicFlow.Theme>,
1533
- themeMode?: LogicFlow.ThemeMode | string,
1534
- ) {
1535
- if (themeMode) {
1536
- this.themeMode = themeMode
1537
- // 修改背景颜色
1538
- backgroundModeMap[themeMode] &&
1539
- this.updateBackgroundOptions({
1540
- ...(typeof this.background === 'object' ? this.background : {}),
1541
- ...backgroundModeMap[themeMode],
1542
- })
1543
- gridModeMap[themeMode] &&
1544
- this.updateGridOptions(
1545
- Grid.getGridOptions({ ...this.grid, ...gridModeMap[themeMode] }),
1546
- )
1547
- }
1548
- if (style.background) {
1549
- this.updateBackgroundOptions(style.background)
1550
- }
1551
- if (style.grid) {
1552
- const formattedGrid = Grid.getGridOptions(style.grid ?? false)
1553
- this.updateGridOptions(formattedGrid)
1554
- }
1555
- this.theme = updateTheme({ ...this.customStyles, ...style }, themeMode)
1556
- this.customStyles = { ...this.customStyles, ...style }
1557
- }
1558
-
1559
- /**
1560
- * 设置主题
1561
- * todo docs link
1562
- */
1563
- @action getTheme() {
1564
- const { background, grid } = this
1565
- const theme = {
1566
- ...cloneDeep(this.theme),
1567
- background,
1568
- grid,
1569
- }
1570
- return theme
1571
- }
1572
-
1573
- /**
1574
- * 更新网格配置
1575
- */
1576
- updateGridOptions(options: Partial<Grid.GridOptions>) {
1577
- merge(this.grid, options)
1578
- }
1579
-
1580
- /**
1581
- * 更新网格尺寸
1582
- */
1583
- updateGridSize(size: number) {
1584
- this.gridSize = size
1585
- }
1586
-
1587
- /**
1588
- * 更新背景配置
1589
- */
1590
- updateBackgroundOptions(
1591
- options: boolean | Partial<LFOptions.BackgroundConfig>,
1592
- ) {
1593
- if (isBoolean(options) || isBoolean(this.background)) {
1594
- this.background = options
1595
- } else {
1596
- this.background = {
1597
- ...this.background,
1598
- ...options,
1599
- }
1600
- }
1601
- }
1602
-
1603
- /**
1604
- * 重新设置画布的宽高
1605
- */
1606
- @action resize(width?: number, height?: number): void {
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)
1633
- }
1634
- }
1635
-
1636
- /**
1637
- * 清空画布
1638
- */
1639
- @action clearData(): void {
1640
- this.nodes = []
1641
- this.edges = []
1642
-
1643
- // 清除对已清除节点的引用
1644
- this.edgeModelMap.clear()
1645
- this.nodeModelMap.clear()
1646
- this.elementsModelMap.clear()
1647
- }
1648
-
1649
- /**
1650
- * 获取图形区域虚拟矩形的尺寸和中心坐标
1651
- * @returns
1652
- */
1653
- getVirtualRectSize(): GraphModel.VirtualRectProps {
1654
- const { nodes } = this
1655
- let nodesX: number[] = []
1656
- let nodesY: number[] = []
1657
- // 获取所有节点组成的x,y轴最大最小值,这里考虑了图形的长宽和边框
1658
- nodes.forEach((node) => {
1659
- const { x, y, width, height } = node
1660
- const { strokeWidth = 0 } = node.getNodeStyle()
1661
- const maxX = x + width / 2 + strokeWidth
1662
- const minX = x - width / 2 - strokeWidth
1663
- const maxY = y + height / 2 + strokeWidth
1664
- const minY = y - height / 2 - strokeWidth
1665
- nodesX = nodesX.concat([maxX, minX].filter((num) => !Number.isNaN(num)))
1666
- nodesY = nodesY.concat([maxY, minY].filter((num) => !Number.isNaN(num)))
1667
- })
1668
-
1669
- const minX = Math.min(...nodesX)
1670
- const maxX = Math.max(...nodesX)
1671
- const minY = Math.min(...nodesY)
1672
- const maxY = Math.max(...nodesY)
1673
-
1674
- const virtualRectWidth = maxX - minX || 0
1675
- const virtualRectHeight = maxY - minY || 0
1676
-
1677
- // 获取虚拟矩形的中心坐标
1678
- const virtualRectCenterPositionX = minX + virtualRectWidth / 2
1679
- const virtualRectCenterPositionY = minY + virtualRectHeight / 2
1680
-
1681
- return {
1682
- width: virtualRectWidth,
1683
- height: virtualRectHeight,
1684
- x: virtualRectCenterPositionX,
1685
- y: virtualRectCenterPositionY,
1686
- }
1687
- }
1688
-
1689
- /**
1690
- * 将图形整体移动到画布中心
1691
- */
1692
- @action translateCenter(): void {
1693
- const { nodes, width, height, rootEl, transformModel } = this
1694
- if (!nodes.length) {
1695
- return
1696
- }
1697
-
1698
- const containerWidth = width || rootEl.clientWidth
1699
- const containerHeight = height || rootEl.clientHeight
1700
-
1701
- const { x: virtualRectCenterPositionX, y: virtualRectCenterPositionY } =
1702
- this.getVirtualRectSize()
1703
-
1704
- // 将虚拟矩型移动到画布中心
1705
- transformModel.focusOn(
1706
- virtualRectCenterPositionX,
1707
- virtualRectCenterPositionY,
1708
- containerWidth,
1709
- containerHeight,
1710
- )
1711
- }
1712
-
1713
- /**
1714
- * 画布图形适应屏幕大小
1715
- * @param verticalOffset number 距离盒子上下的距离, 默认为20
1716
- * @param horizontalOffset number 距离盒子左右的距离, 默认为20
1717
- */
1718
- @action fitView(verticalOffset = 20, horizontalOffset = 20): void {
1719
- const { nodes, width, height, rootEl, transformModel } = this
1720
- if (!nodes.length) {
1721
- return
1722
- }
1723
- const containerWidth = width || rootEl.clientWidth
1724
- const containerHeight = height || rootEl.clientHeight
1725
-
1726
- const {
1727
- width: virtualRectWidth,
1728
- height: virtualRectHeight,
1729
- x: virtualRectCenterPositionX,
1730
- y: virtualRectCenterPositionY,
1731
- } = this.getVirtualRectSize()
1732
-
1733
- const zoomRatioX = (virtualRectWidth + horizontalOffset) / containerWidth
1734
- const zoomRatioY = (virtualRectHeight + verticalOffset) / containerHeight
1735
- const zoomRatio = 1 / Math.max(zoomRatioX, zoomRatioY)
1736
-
1737
- const point: PointTuple = [containerWidth / 2, containerHeight / 2]
1738
- // 适应画布大小
1739
- transformModel.zoom(zoomRatio, point)
1740
- // 将虚拟矩型移动到画布中心
1741
- transformModel.focusOn(
1742
- virtualRectCenterPositionX,
1743
- virtualRectCenterPositionY,
1744
- containerWidth,
1745
- containerHeight,
1746
- )
1747
- }
1748
-
1749
- /**
1750
- * 开启边的动画
1751
- * @param edgeId string
1752
- */
1753
- @action openEdgeAnimation(edgeId: string): void {
1754
- const edgeModel = this.getEdgeModelById(edgeId)
1755
- edgeModel?.openEdgeAnimation()
1756
- }
1757
-
1758
- /**
1759
- * 关闭边的动画
1760
- * @param edgeId string
1761
- */
1762
- @action closeEdgeAnimation(edgeId: string): void {
1763
- const edgeModel = this.getEdgeModelById(edgeId)
1764
- edgeModel?.closeEdgeAnimation()
1765
- }
1766
-
1767
- /**
1768
- * 获取当前局部渲染模式
1769
- * @returns boolean
1770
- */
1771
- getPartial(): boolean {
1772
- return this.partial
1773
- }
1774
-
1775
- /**
1776
- * 设置是否开启局部渲染模式
1777
- * @param partial boolean
1778
- * @returns
1779
- */
1780
- @action setPartial(partial: boolean): void {
1781
- this.partial = partial
1782
- }
1783
- /** 销毁当前实例 */
1784
- destroy() {
1785
- try {
1786
- this.waitCleanEffects.forEach((fn) => {
1787
- fn()
1788
- })
1789
- } catch (err) {
1790
- console.warn('error on destroy GraphModel', err)
1791
- }
1792
- this.waitCleanEffects.length = 0
1793
- this.eventCenter.destroy()
1794
- }
1795
- }
1796
-
1797
- export namespace GraphModel {
1798
- export type NodesMapType = Record<
1799
- string,
1800
- {
1801
- index: number
1802
- model: BaseNodeModel
1803
- }
1804
- >
1805
- export type EdgesMapType = Record<
1806
- string,
1807
- {
1808
- index: number
1809
- model: BaseEdgeModel
1810
- }
1811
- >
1812
-
1813
- export type ModelsMapType = Record<string, BaseNodeModel | BaseEdgeModel>
1814
-
1815
- // 虚拟矩阵信息类型
1816
- export type VirtualRectProps = {
1817
- width: number
1818
- height: number
1819
- x: number
1820
- y: number
1821
- }
1822
- }
1823
-
1824
- export default GraphModel