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