@antv/layout 0.2.2 → 0.2.5

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 (90) hide show
  1. package/dist/layout.min.js +1 -1
  2. package/dist/layout.min.js.map +1 -1
  3. package/es/layout/comboCombined.js +6 -21
  4. package/es/layout/comboCombined.js.map +1 -1
  5. package/es/layout/grid.js +2 -0
  6. package/es/layout/grid.js.map +1 -1
  7. package/es/layout/types.d.ts +7 -3
  8. package/lib/layout/comboCombined.js +6 -21
  9. package/lib/layout/comboCombined.js.map +1 -1
  10. package/lib/layout/grid.js +2 -0
  11. package/lib/layout/grid.js.map +1 -1
  12. package/lib/layout/types.d.ts +7 -3
  13. package/package.json +2 -1
  14. package/src/index.ts +7 -0
  15. package/src/layout/base.ts +54 -0
  16. package/src/layout/circular.ts +369 -0
  17. package/src/layout/comboCombined.ts +391 -0
  18. package/src/layout/comboForce.ts +873 -0
  19. package/src/layout/concentric.ts +289 -0
  20. package/src/layout/constants.ts +21 -0
  21. package/src/layout/dagre/graph.ts +104 -0
  22. package/src/layout/dagre/index.ts +31 -0
  23. package/src/layout/dagre/src/acyclic.ts +58 -0
  24. package/src/layout/dagre/src/add-border-segments.ts +47 -0
  25. package/src/layout/dagre/src/coordinate-system.ts +77 -0
  26. package/src/layout/dagre/src/data/list.ts +60 -0
  27. package/src/layout/dagre/src/debug.ts +30 -0
  28. package/src/layout/dagre/src/greedy-fas.ts +144 -0
  29. package/src/layout/dagre/src/layout.ts +580 -0
  30. package/src/layout/dagre/src/nesting-graph.ts +143 -0
  31. package/src/layout/dagre/src/normalize.ts +96 -0
  32. package/src/layout/dagre/src/order/add-subgraph-constraints.ts +29 -0
  33. package/src/layout/dagre/src/order/barycenter.ts +26 -0
  34. package/src/layout/dagre/src/order/build-layer-graph.ts +82 -0
  35. package/src/layout/dagre/src/order/cross-count.ts +77 -0
  36. package/src/layout/dagre/src/order/index.ts +105 -0
  37. package/src/layout/dagre/src/order/init-data-order.ts +27 -0
  38. package/src/layout/dagre/src/order/init-order.ts +56 -0
  39. package/src/layout/dagre/src/order/resolve-conflicts.ts +152 -0
  40. package/src/layout/dagre/src/order/sort-subgraph.ts +105 -0
  41. package/src/layout/dagre/src/order/sort.ts +76 -0
  42. package/src/layout/dagre/src/parent-dummy-chains.ts +102 -0
  43. package/src/layout/dagre/src/position/bk.ts +494 -0
  44. package/src/layout/dagre/src/position/index.ts +82 -0
  45. package/src/layout/dagre/src/rank/feasible-tree.ts +165 -0
  46. package/src/layout/dagre/src/rank/index.ts +54 -0
  47. package/src/layout/dagre/src/rank/network-simplex.ts +225 -0
  48. package/src/layout/dagre/src/rank/util.ts +157 -0
  49. package/src/layout/dagre/src/util.ts +308 -0
  50. package/src/layout/dagre.ts +423 -0
  51. package/src/layout/dagreCompound.ts +518 -0
  52. package/src/layout/er/core.ts +117 -0
  53. package/src/layout/er/forceGrid.ts +95 -0
  54. package/src/layout/er/grid.ts +185 -0
  55. package/src/layout/er/index.ts +68 -0
  56. package/src/layout/er/mysqlWorkbench.ts +345 -0
  57. package/src/layout/er/type.ts +39 -0
  58. package/src/layout/force/force-in-a-box.ts +400 -0
  59. package/src/layout/force/force.ts +391 -0
  60. package/src/layout/force/index.ts +1 -0
  61. package/src/layout/forceAtlas2/body.ts +115 -0
  62. package/src/layout/forceAtlas2/index.ts +556 -0
  63. package/src/layout/forceAtlas2/quad.ts +115 -0
  64. package/src/layout/forceAtlas2/quadTree.ts +107 -0
  65. package/src/layout/fruchterman.ts +361 -0
  66. package/src/layout/gForce.ts +487 -0
  67. package/src/layout/gpu/fruchterman.ts +314 -0
  68. package/src/layout/gpu/fruchtermanShader.ts +204 -0
  69. package/src/layout/gpu/gForce.ts +406 -0
  70. package/src/layout/gpu/gForceShader.ts +221 -0
  71. package/src/layout/grid.ts +393 -0
  72. package/src/layout/index.ts +45 -0
  73. package/src/layout/layout.ts +75 -0
  74. package/src/layout/mds.ts +140 -0
  75. package/src/layout/radial/index.ts +1 -0
  76. package/src/layout/radial/mds.ts +51 -0
  77. package/src/layout/radial/radial.ts +500 -0
  78. package/src/layout/radial/radialNonoverlapForce.ts +189 -0
  79. package/src/layout/random.ts +75 -0
  80. package/src/layout/types.ts +425 -0
  81. package/src/registy/index.ts +43 -0
  82. package/src/util/array.ts +1 -0
  83. package/src/util/function.ts +64 -0
  84. package/src/util/gpu.ts +254 -0
  85. package/src/util/index.ts +6 -0
  86. package/src/util/math.ts +158 -0
  87. package/src/util/number.ts +8 -0
  88. package/src/util/object.ts +28 -0
  89. package/src/util/string.ts +18 -0
  90. package/CHANGELOG.md +0 -88
@@ -0,0 +1,518 @@
1
+ import { Base } from './base';
2
+ import { Combo, DagreCompoundLayoutOptions, OutNode, Point, PointTuple } from './types';
3
+ import {
4
+ buildGraph,
5
+ DeepPartial,
6
+ flatGraph,
7
+ getEdges,
8
+ HierarchyBaseEdgeInfo,
9
+ HierarchyBaseNodeInfo,
10
+ HierarchyFlattenedGraphInfo,
11
+ HierarchyGraphCompoundDef,
12
+ HierarchyGraphDef,
13
+ HierarchyGraphNodeInfo,
14
+ HierarchyGraphOption,
15
+ HierarchyNodeType,
16
+ LAYOUT_CONFIG,
17
+ LayoutConfig,
18
+ mergeConfig,
19
+ NodeType,
20
+ ROOT_NAME
21
+ } from 'dagre-compound';
22
+ import { isArray, isObject } from '../util';
23
+
24
+ interface IPoint {
25
+ x: number;
26
+ y: number;
27
+ anchorIndex?: number;
28
+ [key: string]: number | undefined;
29
+ }
30
+
31
+ interface Edge {
32
+ source: string;
33
+ target: string;
34
+ type?: string;
35
+ startPoint?: IPoint;
36
+ endPoint?: IPoint;
37
+ controlPoints?: IPoint[];
38
+ sourceAnchor?: number;
39
+ targetAnchor?: number;
40
+ layoutOrder?: number; // 用于描述用户输入数据顺序,确保按照用户原始数据顺序排序
41
+ [key: string]: unknown;
42
+ }
43
+
44
+ type ComboType = Combo & {
45
+ x?: number;
46
+ y?: number;
47
+ label?: string;
48
+ type: string;
49
+ size?: number | number[] | undefined;
50
+ fixSize?: number[];
51
+ fixCollapseSize?: number[];
52
+ // combo 入边与出边,收起时优化连线
53
+ inEdges?: Edge[];
54
+ outEdges?: Edge[];
55
+ padding?: number[];
56
+ collapsed?: boolean;
57
+ // 手动指定偏移量
58
+ offsetX?: number;
59
+ offsetY?: number;
60
+ [key: string]: unknown;
61
+ };
62
+
63
+ type Node = OutNode & {
64
+ label?: string;
65
+ width?: number;
66
+ height?: number;
67
+ type?: string;
68
+ anchorPoints?: [number, number][];
69
+ layoutOrder?: number; // 用于描述用户输入数据顺序,确保按照用户原始数据顺序排序
70
+ };
71
+
72
+ type ModelType = {
73
+ nodes?: Node[];
74
+ edges?: Edge[];
75
+ comboEdges?: Edge[];
76
+ combos?: ComboType[];
77
+ hiddenNodes?: Node[];
78
+ hiddenEdges?: Edge[];
79
+ hiddenCombos?: ComboType[];
80
+ };
81
+
82
+ export class DagreCompoundLayout extends Base {
83
+ /** layout 方向, 可选 TB, BT, LR, RL */
84
+ public rankdir: 'TB' | 'BT' | 'LR' | 'RL' = 'TB';
85
+
86
+ /** 节点对齐方式,可选 UL, UR, DL, DR */
87
+ public align: undefined | 'UL' | 'UR' | 'DL' | 'DR';
88
+
89
+ /** 布局的起始(左上角)位置 */
90
+ public begin: PointTuple | undefined;
91
+
92
+ /** 节点大小 */
93
+ public nodeSize: number | number[] | undefined;
94
+
95
+ /** 节点水平间距(px) */
96
+ public nodesep: number = 50;
97
+
98
+ /** 边水平间距(px) */
99
+ public edgesep: number = 5;
100
+
101
+ /** 每一层节点之间间距 */
102
+ public ranksep: number = 50;
103
+
104
+ /** 是否保留布局连线的控制点 */
105
+ public controlPoints: boolean = true;
106
+
107
+ /** 是否保留使用布局计算的锚点 */
108
+ public anchorPoint: boolean = true;
109
+
110
+ /** 全局布局配置,优先级最高 */
111
+ public settings?: DeepPartial<LayoutConfig>;
112
+
113
+ public nodes: Node[] = [];
114
+ public edges: Edge[] = [];
115
+ public combos: ComboType[] = [];
116
+
117
+ /** 当前生命周期内布局配置信息 */
118
+ private graphSettings: DeepPartial<LayoutConfig> | undefined;
119
+
120
+ /** 迭代结束的回调函数 */
121
+ public onLayoutEnd: () => void = () => {};
122
+ constructor(options?: DagreCompoundLayoutOptions) {
123
+ super();
124
+ this.updateCfg(options);
125
+ }
126
+
127
+ public getDefaultCfg() {
128
+ return {
129
+ rankdir: 'TB', // layout 方向, 可选 TB, BT, LR, RL
130
+ align: undefined, // 节点对齐方式,可选 UL, UR, DL, DR
131
+ begin: undefined, // 布局的起始(左上角)位置
132
+ nodeSize: undefined, // 节点大小
133
+ nodesep: 50, // 节点水平间距(px)
134
+ ranksep: 50, // 每一层节点之间间距
135
+ controlPoints: true, // 是否保留布局连线的控制点
136
+ anchorPoint: true // 是否使用布局计算的锚点
137
+ };
138
+ }
139
+
140
+ public init(data: ModelType) {
141
+ const hiddenNodes = data.hiddenNodes || []; // 被隐藏的节点
142
+ const hiddenEdges = data.hiddenEdges || []; // 被隐藏的边
143
+ const hiddenCombos = data.hiddenCombos || []; // 赋值 hiddenCombos
144
+ // 确保此次排序按照用户输入顺序
145
+ this.nodes = this.getDataByOrder((data.nodes || []).concat(hiddenNodes));
146
+ this.edges = this.getDataByOrder((data.edges || []).concat(hiddenEdges));
147
+ this.combos = (data.combos || []).concat(hiddenCombos.map((hc) => ({ ...hc, collapsed: true })));
148
+ }
149
+
150
+ public execute() {
151
+ const self = this;
152
+ const { nodes, edges } = self;
153
+ if (!nodes) return;
154
+
155
+ const { graphDef, graphOption, graphSettings } = self.getLayoutConfig();
156
+ const renderInfo = buildGraph(graphDef, graphOption, graphSettings);
157
+ const flattenedRenderInfo = flatGraph(renderInfo, true); // 打平数据进行遍历
158
+ this.updatePosition(flattenedRenderInfo);
159
+
160
+ if (self.onLayoutEnd) self.onLayoutEnd();
161
+
162
+ return {
163
+ nodes,
164
+ edges
165
+ };
166
+ }
167
+
168
+ /**
169
+ * combo 模式下查找节点完整路径
170
+ * @param nodeId
171
+ * @private
172
+ */
173
+ private getNodePath(nodeId: string): string[] {
174
+ const self = this;
175
+ const { nodes, combos } = self;
176
+ const targetNode = nodes.find((n) => n.id === nodeId);
177
+ const findPath = (comboId: string, fullPath: string[] = []): string[] => {
178
+ const combo = combos.find((c) => c.id === comboId);
179
+ if (combo) {
180
+ fullPath.unshift(comboId);
181
+ if (combo.parentId) {
182
+ return findPath(combo.parentId, fullPath);
183
+ }
184
+ return fullPath;
185
+ }
186
+ return fullPath;
187
+ };
188
+ if (targetNode && targetNode.comboId) {
189
+ return findPath(targetNode.comboId, [nodeId]);
190
+ }
191
+ return [nodeId];
192
+ }
193
+
194
+ /** 准备 dagre-compound 布局参数 */
195
+ private getLayoutConfig(): { graphDef: HierarchyGraphDef; graphOption: HierarchyGraphOption; graphSettings: DeepPartial<LayoutConfig> } {
196
+ const self = this;
197
+ const { nodes, edges, combos, nodeSize, rankdir, align, edgesep, nodesep, ranksep, settings } = self;
198
+ const compound = (combos || []).reduce((pre: HierarchyGraphCompoundDef, cur) => {
199
+ const matchedNodes = nodes.filter((n) => n.comboId === cur.id).map((n) => n.id);
200
+ const matchedCombos = (combos || []).filter((n) => n.parentId === cur.id).map((n) => n.id);
201
+ if (matchedNodes.length || matchedCombos.length) {
202
+ pre[cur.id] = [...matchedNodes, ...matchedCombos];
203
+ }
204
+ return pre;
205
+ }, {});
206
+
207
+ /** 计算 nodeSize */
208
+ let nodeSizeFunc: (d?: Node) => number[];
209
+ if (!nodeSize) {
210
+ nodeSizeFunc = (d?: Node) => {
211
+ if (d && d.size) {
212
+ if (isArray(d.size)) {
213
+ return d.size;
214
+ }
215
+ if (isObject(d.size)) {
216
+ return [d.size.width || 40, d.size.height || 40];
217
+ }
218
+ return [d.size, d.size];
219
+ }
220
+ return [40, 40];
221
+ };
222
+ } else if (isArray(nodeSize)) {
223
+ nodeSizeFunc = () => nodeSize;
224
+ } else {
225
+ nodeSizeFunc = () => [nodeSize, nodeSize];
226
+ }
227
+
228
+ /** 计算 comboSize */
229
+ const comboSizeFunc: (d?: ComboType) => number[] = (d?: ComboType): number[] => {
230
+ if (d && d.size) {
231
+ if (isArray(d.size)) {
232
+ return d.size;
233
+ }
234
+ return [d.size, d.size];
235
+ }
236
+ return [80, 40];
237
+ };
238
+
239
+ // 接受 defaultCombo 设置的 size
240
+ const [metaWidth, metaHeight] = comboSizeFunc(combos?.[0]);
241
+ // 初始化 padding
242
+ const subSceneMeta = self.graphSettings?.subScene?.meta;
243
+ const [paddingTop, paddingRight, paddingBottom, paddingLeft] = combos.find((c) => !c.collapsed)?.padding || [20, 20, 20, 20];
244
+ const graphDef = {
245
+ compound,
246
+ nodes: [
247
+ ...(nodes || []).map((n) => {
248
+ const size = nodeSizeFunc(n);
249
+ const width = size[0];
250
+ const height = size[1];
251
+ return { ...n, width, height };
252
+ })
253
+ ],
254
+ edges: [...(edges || []).map((e) => ({ ...e, v: e.source, w: e.target }))]
255
+ };
256
+
257
+ // 需要展开的节点
258
+ const graphOption = {
259
+ expanded: (combos || []).filter((c) => !c.collapsed).map((c) => c.id)
260
+ };
261
+
262
+ // dagre-compound 布局参数
263
+ const graphMetaConfig: DeepPartial<LayoutConfig> = {
264
+ graph: {
265
+ meta: {
266
+ align,
267
+ rankDir: rankdir,
268
+ nodeSep: nodesep,
269
+ edgeSep: edgesep,
270
+ rankSep: ranksep
271
+ }
272
+ },
273
+ subScene: {
274
+ meta: {
275
+ paddingTop: paddingTop || subSceneMeta?.paddingTop || 20,
276
+ paddingRight: paddingRight || subSceneMeta?.paddingRight || 20,
277
+ paddingBottom: paddingBottom || subSceneMeta?.paddingBottom || 20,
278
+ paddingLeft: paddingLeft || subSceneMeta?.paddingLeft || 20,
279
+ labelHeight: 0
280
+ }
281
+ },
282
+ nodeSize: {
283
+ meta: {
284
+ width: metaWidth,
285
+ height: metaHeight
286
+ }
287
+ }
288
+ };
289
+
290
+ // 合并用户输入的内容
291
+ const graphSettings = mergeConfig(settings, {
292
+ ...mergeConfig(graphMetaConfig, LAYOUT_CONFIG)
293
+ });
294
+ self.graphSettings = graphSettings;
295
+
296
+ return {
297
+ graphDef,
298
+ graphOption,
299
+ graphSettings
300
+ };
301
+ }
302
+
303
+ /** 更新节点与边位置 */
304
+ private updatePosition(flattenedGraph: HierarchyFlattenedGraphInfo): void {
305
+ const { nodes, edges } = flattenedGraph;
306
+ this.updateNodePosition(nodes, edges);
307
+ this.updateEdgePosition(nodes, edges);
308
+ }
309
+
310
+ private getBegin(
311
+ flattenedNodes: (HierarchyBaseNodeInfo | HierarchyGraphNodeInfo)[],
312
+ flattenedEdges: HierarchyBaseEdgeInfo[]
313
+ ): PointTuple {
314
+ const self = this;
315
+ const { begin } = self;
316
+
317
+ const dBegin: PointTuple = [0, 0];
318
+ if (begin) {
319
+ let minX = Infinity;
320
+ let minY = Infinity;
321
+ flattenedNodes.forEach((node) => {
322
+ if (minX > node.x) minX = node.x;
323
+ if (minY > node.y) minY = node.y;
324
+ });
325
+ flattenedEdges.forEach((edge) => {
326
+ edge.points.forEach((point) => {
327
+ if (minX > point.x) minX = point.x;
328
+ if (minY > point.y) minY = point.y;
329
+ });
330
+ });
331
+ dBegin[0] = begin[0] - minX;
332
+ dBegin[1] = begin[1] - minY;
333
+ }
334
+ return dBegin;
335
+ }
336
+
337
+ private updateNodePosition(
338
+ flattenedNodes: (HierarchyBaseNodeInfo | HierarchyGraphNodeInfo)[],
339
+ flattenedEdges: HierarchyBaseEdgeInfo[]
340
+ ): void {
341
+ const self = this;
342
+ const { combos, nodes, edges, anchorPoint, graphSettings } = self;
343
+ const dBegin = this.getBegin(flattenedNodes, flattenedEdges);
344
+
345
+ flattenedNodes.forEach((node) => {
346
+ const { x, y, id, type, coreBox } = node;
347
+ if (type === HierarchyNodeType.META && id !== ROOT_NAME) {
348
+ const i = combos.findIndex((item) => item.id === id);
349
+ const subSceneMeta = graphSettings?.subScene?.meta;
350
+ // 将布局生成的 combo 位置暂存至 offsetX offsetY
351
+ combos[i].offsetX = x + dBegin[0];
352
+ combos[i].offsetY = y + dBegin[1];
353
+ combos[i].fixSize = [coreBox.width, coreBox.height];
354
+ combos[i].fixCollapseSize = [coreBox.width, coreBox.height];
355
+ // 如果设置了收起时隐藏 padding,则手动优化 combo padding 信息,展开的话则恢复
356
+ if (!node.expanded) {
357
+ combos[i].padding = [0, 0, 0, 0];
358
+ } else {
359
+ combos[i].padding = [
360
+ subSceneMeta?.paddingTop!,
361
+ subSceneMeta?.paddingRight!,
362
+ subSceneMeta?.paddingBottom!,
363
+ subSceneMeta?.paddingLeft!
364
+ ];
365
+ }
366
+ } else if (type === HierarchyNodeType.OP) {
367
+ const i = nodes.findIndex((item) => item.id === id);
368
+ nodes[i].x = x + dBegin[0];
369
+ nodes[i].y = y + dBegin[1];
370
+
371
+ if (anchorPoint) {
372
+ const anchorPoints: [number, number][] = [];
373
+ const outEdges = flattenedEdges.filter((e) => e.v === id);
374
+ const inEdges = flattenedEdges.filter((e) => e.w === id);
375
+ // 指定出边锚点,锚点中心点为 [0.5, 0.5]
376
+ if (outEdges.length > 0) {
377
+ outEdges.forEach((outEdge) => {
378
+ const firstPoint = outEdge.points[0];
379
+ const anchorPointX = (firstPoint.x - x) / node.width + 0.5;
380
+ const anchorPointY = (firstPoint.y - y) / node.height + 0.5;
381
+ anchorPoints.push([anchorPointX, anchorPointY]);
382
+ // 出边对应 source 边锚点
383
+ outEdge.baseEdgeList.forEach((baseEdge) => {
384
+ const edge = edges.find((e) => e.source === baseEdge.v && e.target === baseEdge.w);
385
+ if (edge) {
386
+ edge.sourceAnchor = anchorPoints.length - 1;
387
+ }
388
+ });
389
+ });
390
+ }
391
+ // 指定入边锚点
392
+ if (inEdges.length > 0) {
393
+ inEdges.forEach((inEdge) => {
394
+ const lastPoint = inEdge.points[inEdge.points.length - 1];
395
+ const anchorPointX = (lastPoint.x - x) / node.width + 0.5;
396
+ const anchorPointY = (lastPoint.y - y) / node.height + 0.5;
397
+ anchorPoints.push([anchorPointX, anchorPointY]);
398
+ // 出边对应 source 锚点
399
+ inEdge.baseEdgeList.forEach((baseEdge) => {
400
+ const edge = edges.find((e) => e.source === baseEdge.v && e.target === baseEdge.w);
401
+ if (edge) {
402
+ edge.targetAnchor = anchorPoints.length - 1;
403
+ }
404
+ });
405
+ });
406
+ }
407
+ nodes[i].anchorPoints = anchorPoints.length > 0 ? anchorPoints : nodes[i].anchorPoints || [];
408
+ }
409
+ }
410
+ });
411
+ }
412
+
413
+ private updateEdgePosition(
414
+ flattenedNodes: (HierarchyBaseNodeInfo | HierarchyGraphNodeInfo)[],
415
+ flattenedEdges: HierarchyBaseEdgeInfo[]
416
+ ): void {
417
+ const self = this;
418
+ const { combos, edges, controlPoints } = self;
419
+ const dBegin = this.getBegin(flattenedNodes, flattenedEdges);
420
+
421
+ if (controlPoints) {
422
+ combos.forEach((combo) => {
423
+ combo.inEdges = [];
424
+ combo.outEdges = [];
425
+ });
426
+ edges.forEach((sourceEdge) => {
427
+ let sourceNode = flattenedNodes.find((v) => v.id === sourceEdge.source);
428
+ let targetNode = flattenedNodes.find((v) => v.id === sourceEdge.target);
429
+ // Combo 收起状态,dagre-compound 不会渲染该节点,边需要使用到 group 的边作为补充
430
+ let points: Point[] = [];
431
+ let sortedEdges: HierarchyBaseEdgeInfo[] = [];
432
+ if (sourceNode && targetNode) {
433
+ sortedEdges = getEdges(sourceNode?.id, targetNode?.id, flattenedNodes);
434
+ } else if (!sourceNode || !targetNode) {
435
+ /** 存在收起节点时,需要重新计算边的 controlPoints,确保线正常 */
436
+ // 情况1:目标节点被收起了,向上寻找该节点最近一个存在的父节点
437
+ const sourceNodePath = self.getNodePath(sourceEdge.source);
438
+ const targetNodePath = self.getNodePath(sourceEdge.target);
439
+
440
+ const lastExistingSource = sourceNodePath
441
+ .reverse()
442
+ .slice(!sourceNode ? 1 : 0)
443
+ .find((parentId) => flattenedNodes.find((fNode) => fNode.id === parentId));
444
+ const lastExistingTarget = targetNodePath
445
+ .reverse()
446
+ .slice(!targetNode ? 1 : 0)
447
+ .find((parentId) => flattenedNodes.find((fNode) => fNode.id === parentId));
448
+ sourceNode = flattenedNodes.find((v) => v.id === lastExistingSource);
449
+ targetNode = flattenedNodes.find((v) => v.id === lastExistingTarget);
450
+ sortedEdges = getEdges(sourceNode?.id, targetNode?.id, flattenedNodes, { v: sourceEdge.source, w: sourceEdge.target });
451
+ }
452
+
453
+ points = sortedEdges.reduce((pre, cur) => {
454
+ return [
455
+ ...pre,
456
+ ...cur.points.map((p) => {
457
+ return {
458
+ ...p,
459
+ x: p.x + dBegin[0],
460
+ y: p.y + dBegin[1]
461
+ };
462
+ })
463
+ ];
464
+ }, [] as IPoint[]);
465
+ // 取消首尾节点
466
+ points = points.slice(1, -1);
467
+ sourceEdge.controlPoints = points;
468
+
469
+ if (targetNode?.type === NodeType.META) {
470
+ // combo 节点控制点
471
+ const i = combos.findIndex((item) => item.id === targetNode?.id);
472
+ if (!combos[i] || combos[i].inEdges?.some((inEdge) => inEdge.source === sourceNode!.id && inEdge.target === targetNode!.id)) {
473
+ return;
474
+ }
475
+ combos[i].inEdges?.push({
476
+ source: sourceNode!.id,
477
+ target: targetNode!.id,
478
+ controlPoints: points
479
+ });
480
+ }
481
+ if (sourceNode?.type === NodeType.META) {
482
+ const i = combos.findIndex((item) => item.id === sourceNode?.id);
483
+ if (!combos[i] || combos[i].outEdges?.some((oedge) => oedge.source === sourceNode!.id && oedge.target === targetNode!.id)) {
484
+ return;
485
+ }
486
+ combos[i].outEdges?.push({
487
+ source: sourceNode!.id,
488
+ target: targetNode!.id,
489
+ controlPoints: points
490
+ });
491
+ }
492
+ });
493
+ }
494
+ }
495
+
496
+ public getType() {
497
+ return 'dagreCompound';
498
+ }
499
+
500
+ /**
501
+ * 确保布局使用的数据与用户输入数据顺序一致
502
+ * 通过 layoutOrder 排序 节点 与 边
503
+ * @param list
504
+ * @private
505
+ */
506
+ private getDataByOrder(list: any[]): any[] {
507
+ if (list.every((n) => n.layoutOrder !== undefined)) {
508
+ // 所有数据均设置过索引,表示仅布局,数据未变化,无需处理
509
+ } else {
510
+ // 首次布局或动态添加删减节点时重新赋值
511
+ list.forEach((n, i) => {
512
+ n.layoutOrder = i;
513
+ });
514
+ }
515
+ // 按照 layoutOrder 排序
516
+ return list.sort((pre, cur) => pre.layoutOrder - cur.layoutOrder);
517
+ }
518
+ }
@@ -0,0 +1,117 @@
1
+ import * as d3Force from 'd3-force';
2
+ import forceGrid from './forceGrid';
3
+ import mysqlWorkbench from './mysqlWorkbench';
4
+ import { DagreLayout } from '../dagre';
5
+ import { INode, IEdge, } from "./type";
6
+
7
+ export default function layout(data: any, options: any): Promise<void> {
8
+
9
+ const { nodes, edges } = data;
10
+ const width = options.width;
11
+ const height = options.height;
12
+ if (!nodes?.length) return Promise.resolve();
13
+
14
+ // 筛选非叶子节点,做Dagre布局
15
+ const noLeafNodes : INode[] = [];
16
+ nodes.forEach((node: any) => {
17
+ const relateEdges = edges.filter((edge: IEdge) => {
18
+ return edge.source === node.id || edge.target === node.id;
19
+ });
20
+ if (relateEdges.length > 1) {
21
+ const temp = { ...node };
22
+ delete temp.size;
23
+ noLeafNodes.push(temp);
24
+ }
25
+ });
26
+ const noLeafEdge: IEdge[] = [];
27
+ edges.forEach((edge: IEdge) => {
28
+ const sourceNode = noLeafNodes.find((node: INode) => node.id === edge.source );
29
+ const targetNode = noLeafNodes.find((node: INode) => node.id === edge.target );
30
+ if (sourceNode && targetNode) {
31
+ noLeafEdge.push(edge);
32
+ }
33
+ });
34
+ const graphLayout = new DagreLayout({
35
+ type: 'dagre',
36
+ ranksep: options.nodeMinGap,
37
+ nodesep: options.nodeMinGap,
38
+ });
39
+
40
+ const { nodes: nodesTmp } = graphLayout.layout({
41
+ nodes: noLeafNodes,
42
+ edges: noLeafEdge,
43
+ });
44
+
45
+ // 布局后,坐标同步
46
+ nodes.forEach((n: INode) => {
47
+ const found = ((nodesTmp || []) as INode[]).find((temp: any) => temp.id === n.id);
48
+ n.x = found?.x || width / 2;
49
+ n.y = found?.y || height / 2;
50
+ });
51
+
52
+ const copyNodes: INode[] = JSON.parse(JSON.stringify(nodes));
53
+ const copyEdges: IEdge[] = JSON.parse(JSON.stringify(edges));
54
+ const simulation = d3Force.forceSimulation().nodes(copyNodes)
55
+ .force("link", d3Force.forceLink(copyEdges).id((d: any) => d.id).distance((d) => {
56
+ const edgeInfo = noLeafEdge.find((edge) => edge.source === d.source && edge.target === d.target);
57
+ if (edgeInfo) {
58
+ return 30;
59
+ }
60
+ return 20;
61
+ }))
62
+ .force("charge", d3Force.forceManyBody())
63
+ .force("center", d3Force.forceCenter(width / 2, height / 2))
64
+ .force("x", d3Force.forceX(width / 2))
65
+ .force("y", d3Force.forceY( height / 2))
66
+ .alpha(0.3)
67
+ .alphaDecay(0.08)
68
+ .alphaMin(0.001);
69
+
70
+ const layoutPromise = new Promise<void>((resolve) => {
71
+ simulation.on('end', () => {
72
+ // 坐标信息同步到nodes,edges中
73
+ nodes.forEach((node: INode) => {
74
+ const nodeInfo = copyNodes.find((item) => item.id === node.id);
75
+ if (nodeInfo) {
76
+ node.x = nodeInfo.x;
77
+ node.y = nodeInfo.y;
78
+ }
79
+ });
80
+
81
+ const minX = Math.min(...nodes.map((node: INode) => node.x));
82
+ const maxX = Math.max(...nodes.map((node: INode) => node.x));
83
+ const minY = Math.min(...nodes.map((node: INode) => node.y));
84
+ const maxY = Math.max(...nodes.map((node: INode) => node.y));
85
+ const scalex = width / (maxX - minX);
86
+ const scaley = height / (maxY - minY);
87
+ nodes.forEach((node: INode) => {
88
+ if (node.x !== undefined && scalex < 1) {
89
+ node.x = (node.x - minX) * scalex;
90
+ }
91
+ if (node.y !== undefined && scaley < 1) {
92
+ node.y = (node.y - minY) * scaley;
93
+ }
94
+ });
95
+
96
+ // 这一步就执行缩小空间。且不考虑节点size
97
+ nodes.forEach((node: INode) => {
98
+ node.sizeTemp = node.size;
99
+ node.size = [10, 10];
100
+ });
101
+
102
+ mysqlWorkbench(nodes, edges);
103
+ nodes.forEach((node: INode) => {
104
+ node.size = node.sizeTemp || [];
105
+ delete node.sizeTemp;
106
+ });
107
+ // 进行网格对齐+节点大小扩增
108
+ forceGrid({
109
+ nodes,
110
+ edges,
111
+ }, options);
112
+
113
+ resolve();
114
+ });
115
+ });
116
+ return layoutPromise;
117
+ }