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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/.turbo/turbo-build.log +7 -6
  2. package/CHANGELOG.md +15 -0
  3. package/README.md +9 -2
  4. package/dist/index.min.js +1 -1
  5. package/dist/index.min.js.map +1 -1
  6. package/es/{dagre.d.ts → dagre/index.d.ts} +18 -20
  7. package/es/dagre/index.js +126 -0
  8. package/es/dagre/index.js.map +1 -0
  9. package/es/elkLayout/config.d.ts +26 -0
  10. package/es/elkLayout/config.js +27 -0
  11. package/es/elkLayout/config.js.map +1 -0
  12. package/es/elkLayout/index.d.ts +107 -0
  13. package/es/elkLayout/index.js +187 -0
  14. package/es/elkLayout/index.js.map +1 -0
  15. package/es/index.d.ts +1 -0
  16. package/es/index.js +1 -0
  17. package/es/index.js.map +1 -1
  18. package/es/utils/processEdge.d.ts +3 -0
  19. package/es/utils/processEdge.js +479 -0
  20. package/es/utils/processEdge.js.map +1 -0
  21. package/lib/{dagre.d.ts → dagre/index.d.ts} +18 -20
  22. package/lib/dagre/index.js +152 -0
  23. package/lib/dagre/index.js.map +1 -0
  24. package/lib/elkLayout/config.d.ts +26 -0
  25. package/lib/elkLayout/config.js +30 -0
  26. package/lib/elkLayout/config.js.map +1 -0
  27. package/lib/elkLayout/index.d.ts +107 -0
  28. package/lib/elkLayout/index.js +193 -0
  29. package/lib/elkLayout/index.js.map +1 -0
  30. package/lib/index.d.ts +1 -0
  31. package/lib/index.js +1 -0
  32. package/lib/index.js.map +1 -1
  33. package/lib/utils/processEdge.d.ts +3 -0
  34. package/lib/utils/processEdge.js +483 -0
  35. package/lib/utils/processEdge.js.map +1 -0
  36. package/package.json +3 -2
  37. package/src/dagre/index.ts +177 -0
  38. package/src/elkLayout/config.ts +26 -0
  39. package/src/elkLayout/index.ts +255 -0
  40. package/src/index.ts +2 -0
  41. package/src/utils/processEdge.ts +585 -0
  42. package/stats.html +1 -1
  43. package/es/dagre.js +0 -376
  44. package/es/dagre.js.map +0 -1
  45. package/lib/dagre.js +0 -402
  46. package/lib/dagre.js.map +0 -1
  47. package/src/dagre.ts +0 -438
package/src/dagre.ts DELETED
@@ -1,438 +0,0 @@
1
- /**
2
- * @fileoverview Dagre布局插件 - 提供自动化图形布局功能
3
- *
4
- * 本插件基于dagre.js实现LogicFlow的自动化布局功能,支持多种布局方向
5
- * 可自动计算节点位置和连线路径,实现整洁的图形展示
6
- */
7
- import LogicFlow, { BaseNodeModel, BaseEdgeModel } from '@logicflow/core'
8
- import dagre, { GraphLabel, graphlib } from 'dagre'
9
-
10
- import NodeConfig = LogicFlow.NodeConfig
11
- import EdgeConfig = LogicFlow.EdgeConfig
12
- import Point = LogicFlow.Point
13
-
14
- /**
15
- * Dagre布局配置选项接口
16
- * @interface DagreOption
17
- * @extends GraphLabel - 继承dagre原生配置
18
- */
19
- export interface DagreOption extends GraphLabel {
20
- /**
21
- * 是否是默认锚点
22
- * true: 会根据布局方向自动计算边的路径点
23
- */
24
- isDefaultAnchor?: boolean
25
- }
26
-
27
- /**
28
- * Dagre插件接口定义
29
- */
30
- export interface DagrePlugin {
31
- /**
32
- * 执行布局计算
33
- * @param option - 布局配置选项
34
- */
35
- layout(option: DagreOption): void
36
- }
37
-
38
- /**
39
- * Dagre布局类 - LogicFlow自动布局插件
40
- * 基于dagre.js提供图的自动布局能力
41
- */
42
- export class Dagre {
43
- /** 插件名称,用于在LogicFlow中注册 */
44
- static pluginName = 'dagre'
45
-
46
- /** LogicFlow实例引用 */
47
- lf: LogicFlow
48
-
49
- /** 当前布局配置 */
50
- option: DagreOption // 使用已定义的DagreOption接口替代重复定义
51
-
52
- /**
53
- * 插件初始化方法,由LogicFlow自动调用
54
- * @param lf - LogicFlow实例
55
- */
56
- render(lf: LogicFlow) {
57
- this.lf = lf
58
- }
59
-
60
- /**
61
- * 执行布局算法,重新排列图中的节点和边
62
- * @param option - 布局配置选项
63
- */
64
- layout(option: DagreOption = {}) {
65
- const { nodes, edges, gridSize } = this.lf.graphModel
66
-
67
- // 根据网格大小调整节点间距
68
- let nodesep = 100
69
- let ranksep = 150
70
- if (gridSize > 20) {
71
- nodesep = gridSize * 2
72
- ranksep = gridSize * 2
73
- }
74
-
75
- // 合并默认配置和用户配置
76
- this.option = {
77
- // 默认从左到右布局
78
- rankdir: 'LR',
79
- // 默认右下角对齐
80
- align: 'UL',
81
- // 紧凑树形排名算法
82
- ranker: 'tight-tree',
83
- // 层级间距
84
- ranksep,
85
- // 同层节点间距
86
- nodesep,
87
- // 图的水平边距
88
- marginx: 120,
89
- // 图的垂直边距
90
- marginy: 120,
91
- // 用户自定义选项覆盖默认值
92
- ...option,
93
- }
94
-
95
- // 创建dagre图实例
96
- const g = new graphlib.Graph()
97
- g.setGraph(this.option)
98
- g.setDefaultEdgeLabel(() => ({}))
99
-
100
- // 将LogicFlow节点添加到dagre图中
101
- nodes.forEach((node: BaseNodeModel) => {
102
- g.setNode(node.id, {
103
- width: node.width || 150,
104
- height: node.height || 50,
105
- id: node.id,
106
- })
107
- })
108
-
109
- // 将LogicFlow边添加到dagre图中
110
- edges.forEach((edge: BaseEdgeModel) => {
111
- g.setEdge(edge.sourceNodeId, edge.targetNodeId, {
112
- id: edge.id,
113
- })
114
- })
115
-
116
- // 执行dagre布局算法
117
- dagre.layout(g)
118
-
119
- // 存储新的节点和边数据
120
- const newNodes: NodeConfig[] = []
121
- const newEdges: EdgeConfig[] = []
122
-
123
- // 更新节点位置
124
- nodes.forEach((node: BaseNodeModel) => {
125
- const { x, y } = g.node(node.id)
126
- const lfNode = node.getData()
127
- if (!lfNode) {
128
- throw new Error(`布局错误:找不到ID为 ${node.id} 的节点`)
129
- }
130
-
131
- // 更新节点坐标
132
- lfNode.x = x
133
- lfNode.y = y
134
-
135
- // 更新节点文本位置
136
- if (lfNode?.text?.x) {
137
- lfNode.text.x = x
138
- lfNode.text.y = y
139
- }
140
-
141
- newNodes.push(lfNode)
142
- })
143
-
144
- // 处理边的路径和锚点
145
- edges.forEach((edge: BaseEdgeModel) => {
146
- const lfEdge: any = edge.getData()
147
- if (!lfEdge) {
148
- return
149
- }
150
-
151
- if (!option.isDefaultAnchor) {
152
- // 自定义锚点,不调整边的关联锚点,只清除路径相关数据,让LogicFlow自动计算
153
- delete lfEdge.pointsList
154
- delete lfEdge.startPoint
155
- delete lfEdge.endPoint
156
- if (lfEdge.text && lfEdge.text.value) {
157
- lfEdge.text = lfEdge.text.value
158
- }
159
- } else {
160
- // 默认锚点,重新计算路径以及边的起点和终点(节点默认锚点为上下左右)
161
- delete lfEdge.pointsList
162
- delete lfEdge.startPoint
163
- delete lfEdge.endPoint
164
- delete lfEdge.sourceAnchorId
165
- delete lfEdge.targetAnchorId
166
-
167
- const model = this.lf.getEdgeModelById(edge.id)
168
- if (model) {
169
- // 计算自定义折线路径
170
- lfEdge.pointsList = this.calcPointsList(model, newNodes)
171
- }
172
-
173
- if (lfEdge.pointsList) {
174
- // 设置边的起点和终点
175
- const first = lfEdge.pointsList[0]
176
- const last = lfEdge.pointsList[lfEdge.pointsList.length - 1]
177
- lfEdge.startPoint = { x: first.x, y: first.y }
178
- lfEdge.endPoint = { x: last.x, y: last.y }
179
- }
180
- if (lfEdge.text && lfEdge.text.value) {
181
- // 保留文本内容
182
- lfEdge.text = lfEdge.text.value
183
- }
184
- }
185
-
186
- newEdges.push(lfEdge)
187
- })
188
- // 将计算好的布局数据应用到画布
189
- this.lf.renderRawData({
190
- nodes: newNodes,
191
- edges: newEdges,
192
- })
193
- }
194
-
195
- /**
196
- * 计算字符串显示宽度(用于文本定位)
197
- * @param word - 要计算的文本
198
- * @returns 估算的文本像素宽度
199
- */
200
- getBytesLength(word: string): number {
201
- if (!word) {
202
- return 0
203
- }
204
-
205
- let totalLength = 0
206
- for (let i = 0; i < word.length; i++) {
207
- const c = word.charCodeAt(i)
208
- // 大写字母宽度加权
209
- if (word.match(/[A-Z]/)) {
210
- totalLength += 1.5
211
- }
212
- // ASCII字符和半角字符
213
- else if ((c >= 0x0001 && c <= 0x007e) || (c >= 0xff60 && c <= 0xff9f)) {
214
- totalLength += 1
215
- }
216
- // 其他字符(如中文)
217
- else {
218
- totalLength += 2
219
- }
220
- }
221
-
222
- return totalLength
223
- }
224
-
225
- /**
226
- * 优化折线路径点,移除冗余点
227
- * @param points - 原始路径点数组
228
- * @returns 优化后的路径点数组
229
- */
230
- pointFilter(points: Point[]): Point[] {
231
- const allPoints = [...points] // 创建副本避免修改原始数据
232
- let i = 1
233
-
234
- // 删除直线上的中间点(保持路径简洁)
235
- while (i < allPoints.length - 1) {
236
- const pre = allPoints[i - 1]
237
- const current = allPoints[i]
238
- const next = allPoints[i + 1]
239
-
240
- // 如果三点共线,移除中间点
241
- if (
242
- (pre.x === current.x && current.x === next.x) || // 垂直线上的点
243
- (pre.y === current.y && current.y === next.y)
244
- ) {
245
- // 水平线上的点
246
- allPoints.splice(i, 1)
247
- } else {
248
- i++
249
- }
250
- }
251
-
252
- return allPoints
253
- }
254
-
255
- /**
256
- * 计算边的折线路径点
257
- * @param model - 边模型
258
- * @param nodes - 节点数据数组
259
- * @returns 计算后的路径点数组,如果无法计算则返回undefined
260
- */
261
- calcPointsList(
262
- model: BaseEdgeModel,
263
- nodes: NodeConfig[],
264
- ): Point[] | undefined {
265
- const pointsList: Point[] = []
266
-
267
- // 获取源节点和目标节点的模型与布局数据
268
- const sourceNodeModel = this.lf.getNodeModelById(model.sourceNodeId)
269
- const targetNodeModel = this.lf.getNodeModelById(model.targetNodeId)
270
- const newSourceNodeData = nodes.find(
271
- (node: NodeConfig) => node.id === model.sourceNodeId,
272
- )
273
- const newTargetNodeData = nodes.find(
274
- (node: NodeConfig) => node.id === model.targetNodeId,
275
- )
276
-
277
- // 数据验证
278
- if (
279
- !sourceNodeModel ||
280
- !targetNodeModel ||
281
- !newSourceNodeData ||
282
- !newTargetNodeData
283
- ) {
284
- return undefined
285
- }
286
-
287
- // 折线偏移量(用于创建合适的转折点)
288
- const offset = Number(model.offset) || 50
289
-
290
- // 处理从左到右(LR)布局的边路径
291
- if (this.option.rankdir === 'LR' && model.modelType === 'polyline-edge') {
292
- // 正向连线:源节点在目标节点左侧
293
- if (newSourceNodeData.x < newTargetNodeData.x) {
294
- // 从源节点右侧中心出发
295
- pointsList.push({
296
- x: newSourceNodeData.x + sourceNodeModel.width / 2,
297
- y: newSourceNodeData.y,
298
- })
299
- // 向右延伸一段距离
300
- pointsList.push({
301
- x: newSourceNodeData.x + sourceNodeModel.width / 2 + offset,
302
- y: newSourceNodeData.y,
303
- })
304
- // 垂直移动到目标节点的高度
305
- pointsList.push({
306
- x: newSourceNodeData.x + sourceNodeModel.width / 2 + offset,
307
- y: newTargetNodeData.y,
308
- })
309
- // 连接到目标节点左侧中心
310
- pointsList.push({
311
- x: newTargetNodeData.x - targetNodeModel.width / 2,
312
- y: newTargetNodeData.y,
313
- })
314
-
315
- return this.pointFilter(pointsList)
316
- }
317
-
318
- // 反向连线:源节点在目标节点右侧
319
- if (newSourceNodeData.x > newTargetNodeData.x) {
320
- // 根据节点相对Y轴位置选择不同路径
321
- if (newSourceNodeData.y >= newTargetNodeData.y) {
322
- // 源节点在目标节点的右下方,从源节点上方出发
323
- pointsList.push({
324
- x: newSourceNodeData.x,
325
- y: newSourceNodeData.y + sourceNodeModel.height / 2,
326
- })
327
- pointsList.push({
328
- x: newSourceNodeData.x,
329
- y: newSourceNodeData.y + sourceNodeModel.height / 2 + offset,
330
- })
331
- pointsList.push({
332
- x: newTargetNodeData.x,
333
- y: newSourceNodeData.y + sourceNodeModel.height / 2 + offset,
334
- })
335
- pointsList.push({
336
- x: newTargetNodeData.x,
337
- y: newTargetNodeData.y + targetNodeModel.height / 2,
338
- })
339
- } else {
340
- // 源节点在目标节点的右上方,从源节点下方出发
341
- pointsList.push({
342
- x: newSourceNodeData.x,
343
- y: newSourceNodeData.y - sourceNodeModel.height / 2,
344
- })
345
- pointsList.push({
346
- x: newSourceNodeData.x,
347
- y: newSourceNodeData.y - sourceNodeModel.height / 2 - offset,
348
- })
349
- pointsList.push({
350
- x: newTargetNodeData.x,
351
- y: newSourceNodeData.y - sourceNodeModel.height / 2 - offset,
352
- })
353
- pointsList.push({
354
- x: newTargetNodeData.x,
355
- y: newTargetNodeData.y - targetNodeModel.height / 2,
356
- })
357
- }
358
-
359
- return this.pointFilter(pointsList)
360
- }
361
- }
362
-
363
- // 处理从上到下(TB)布局的边路径
364
- if (this.option.rankdir === 'TB' && model.modelType === 'polyline-edge') {
365
- // 正向连线:源节点在目标节点上方
366
- if (newSourceNodeData.y < newTargetNodeData.y) {
367
- // 从源节点底部中心出发
368
- pointsList.push({
369
- x: newSourceNodeData.x,
370
- y: newSourceNodeData.y + sourceNodeModel.height / 2,
371
- })
372
- // 向下延伸一段距离
373
- pointsList.push({
374
- x: newSourceNodeData.x,
375
- y: newSourceNodeData.y + sourceNodeModel.height / 2 + offset,
376
- })
377
- // 水平移动到目标节点的位置
378
- pointsList.push({
379
- x: newTargetNodeData.x,
380
- y: newSourceNodeData.y + sourceNodeModel.height / 2 + offset,
381
- })
382
- // 连接到目标节点顶部中心
383
- pointsList.push({
384
- x: newTargetNodeData.x,
385
- y: newTargetNodeData.y - targetNodeModel.height / 2,
386
- })
387
-
388
- return this.pointFilter(pointsList)
389
- }
390
-
391
- // 反向连线:源节点在目标节点下方
392
- if (newSourceNodeData.y > newTargetNodeData.y) {
393
- if (newSourceNodeData.x >= newTargetNodeData.x) {
394
- // 源节点在目标节点右下方,从源节点右侧出发
395
- pointsList.push({
396
- x: newSourceNodeData.x + sourceNodeModel.width / 2,
397
- y: newSourceNodeData.y,
398
- })
399
- pointsList.push({
400
- x: newSourceNodeData.x + sourceNodeModel.width / 2 + offset,
401
- y: newSourceNodeData.y,
402
- })
403
- pointsList.push({
404
- x: newSourceNodeData.x + sourceNodeModel.width / 2 + offset,
405
- y: newTargetNodeData.y,
406
- })
407
- pointsList.push({
408
- x: newTargetNodeData.x + targetNodeModel.width / 2,
409
- y: newTargetNodeData.y,
410
- })
411
- } else {
412
- // 源节点在目标节点左下方,从源节点左侧出发
413
- pointsList.push({
414
- x: newSourceNodeData.x - sourceNodeModel.width / 2,
415
- y: newSourceNodeData.y,
416
- })
417
- pointsList.push({
418
- x: newSourceNodeData.x - sourceNodeModel.width / 2 - offset,
419
- y: newSourceNodeData.y,
420
- })
421
- pointsList.push({
422
- x: newSourceNodeData.x - sourceNodeModel.width / 2 - offset,
423
- y: newTargetNodeData.y,
424
- })
425
- pointsList.push({
426
- x: newTargetNodeData.x - targetNodeModel.width / 2,
427
- y: newTargetNodeData.y,
428
- })
429
- }
430
-
431
- return this.pointFilter(pointsList)
432
- }
433
- }
434
-
435
- // 无法确定路径时返回undefined,让LogicFlow自行处理
436
- return undefined
437
- }
438
- }