@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,391 @@
1
+ /**
2
+ * @fileOverview Combo force layout
3
+ * @author shiwu.wyy@antfin.com
4
+ */
5
+
6
+ import {
7
+ Edge,
8
+ Combo,
9
+ OutNode,
10
+ PointTuple,
11
+ ComboTree,
12
+ ComboCombinedLayoutOptions
13
+ } from "./types";
14
+ import { FORCE_LAYOUT_TYPE_MAP } from './constants';
15
+ import { Base } from "./base";
16
+ import { isArray, isNumber, isFunction, traverseTreeUp, isObject, findMinMaxNodeXY } from "../util";
17
+ import { CircularLayout, ConcentricLayout, GridLayout, RadialLayout, GForceLayout, MDSLayout } from ".";
18
+
19
+ type Node = OutNode & {
20
+ depth?: number;
21
+ itemType?: string;
22
+ comboId?: string;
23
+ fx?: number;
24
+ fy?: number;
25
+ mass?: number;
26
+ };
27
+
28
+ /**
29
+ * combined two layouts (inner and outer) for graph with combos
30
+ */
31
+ export class ComboCombinedLayout extends Base {
32
+
33
+ /** 布局中心 */
34
+ public center: PointTuple = [0, 0];
35
+
36
+ /** 内部计算参数 */
37
+ public nodes: Node[] = [];
38
+
39
+ public edges: Edge[] = [];
40
+
41
+ public combos: Combo[] = [];
42
+
43
+ public comboEdges: Edge[] = [];
44
+
45
+ /** 节点大小,用于防止节点之间的重叠 */
46
+ public nodeSize: number | number[] | ((d?: unknown) => number) | undefined;
47
+
48
+ /** 节点/combo最小间距,防止重叠时的间隙 */
49
+ public spacing: ((d?: unknown) => number) | number | undefined;
50
+
51
+ /** 最外层的布局算法,需要使用同步的布局算法,默认为 forceAtlas2 */
52
+ public outerLayout: any;
53
+
54
+ /** combo 内部的布局算法,默认为 concentric */
55
+ public innerLayout: ConcentricLayout | CircularLayout | GridLayout | RadialLayout;
56
+
57
+ /** Combo 内部的 padding */
58
+ public comboPadding:
59
+ | ((d?: unknown) => number)
60
+ | number
61
+ | number[]
62
+ | undefined = 10;
63
+
64
+ public comboTrees: ComboTree[] = [];
65
+
66
+ constructor(options?: ComboCombinedLayoutOptions) {
67
+ super();
68
+ this.updateCfg(options);
69
+ }
70
+
71
+ public getDefaultCfg() {
72
+ return {};
73
+ }
74
+
75
+ /**
76
+ * 执行布局
77
+ */
78
+ public execute() {
79
+ const self = this;
80
+ const nodes = self.nodes;
81
+ const center = self.center;
82
+
83
+ if (!nodes || nodes.length === 0) {
84
+ if (self.onLayoutEnd) self.onLayoutEnd();
85
+ return;
86
+ }
87
+ if (nodes.length === 1) {
88
+ nodes[0].x = center[0];
89
+ nodes[0].y = center[1];
90
+ if (self.onLayoutEnd) self.onLayoutEnd();
91
+ return;
92
+ }
93
+
94
+ self.initVals();
95
+
96
+ // layout
97
+ self.run();
98
+ if (self.onLayoutEnd) self.onLayoutEnd();
99
+ }
100
+
101
+ public run() {
102
+ const self = this;
103
+ const { nodes, edges, combos, comboEdges, center } = self;
104
+
105
+ const nodeMap: any = {};
106
+ nodes.forEach((node) => {
107
+ nodeMap[node.id] = node;
108
+ });
109
+ const comboMap: any = {};
110
+ combos.forEach((combo) => {
111
+ comboMap[combo.id] = combo;
112
+ });
113
+
114
+ const innerGraphs: any = self.getInnerGraphs(nodeMap);
115
+
116
+ // 每个 innerGraph 作为一个节点,带有大小,参与 force 计算
117
+ const outerNodeIds: string[] = [];
118
+ const outerNodes: Node[] = [];
119
+ const nodeAncestorIdMap: { [key: string]: string } = {};
120
+ let allHaveNoPosition = true;
121
+ this.comboTrees.forEach((cTree) => {
122
+ const innerNode = innerGraphs[cTree.id];
123
+ // 代表 combo 的节点
124
+ const oNode: Node = {
125
+ ...cTree,
126
+ x: innerNode.x || comboMap[cTree.id].x,
127
+ y: innerNode.y || comboMap[cTree.id].y,
128
+ fx: innerNode.fx || comboMap[cTree.id].fx,
129
+ fy: innerNode.fy || comboMap[cTree.id].fy,
130
+ mass: innerNode.mass || comboMap[cTree.id].mass,
131
+ size: innerNode.size
132
+ };
133
+ outerNodes.push(oNode);
134
+ if (!isNaN(oNode.x) && oNode.x !== 0 && !isNaN(oNode.y) && oNode.y !== 0) {
135
+ allHaveNoPosition = false;
136
+ } else {
137
+ oNode.x = Math.random() * 100;
138
+ oNode.y = Math.random() * 100;
139
+ }
140
+ outerNodeIds.push(cTree.id);
141
+ traverseTreeUp<ComboTree>(cTree, (child) => {
142
+ if (child.id !== cTree.id) nodeAncestorIdMap[child.id] = cTree.id;
143
+ return true;
144
+ });
145
+ });
146
+ nodes.forEach((node) => {
147
+ if (node.comboId && comboMap[node.comboId]) return;
148
+ // 代表节点的节点
149
+ const oNode: Node = { ...node };
150
+ outerNodes.push(oNode);
151
+ if (!isNaN(oNode.x) && oNode.x !== 0 && !isNaN(oNode.y) && oNode.y !== 0) {
152
+ allHaveNoPosition = false;
153
+ } else {
154
+ oNode.x = Math.random() * 100;
155
+ oNode.y = Math.random() * 100;
156
+ }
157
+ outerNodeIds.push(node.id);
158
+ });
159
+ const outerEdges: any = [];
160
+ edges.concat(comboEdges).forEach((edge) => {
161
+ const sourceAncestorId = nodeAncestorIdMap[edge.source] || edge.source;
162
+ const targetAncestorId = nodeAncestorIdMap[edge.target] || edge.target;
163
+ // 若两个点的祖先都在力导图的节点中,且是不同的节点,创建一条链接两个祖先的边到力导图的边中
164
+ if (sourceAncestorId !== targetAncestorId &&
165
+ outerNodeIds.includes(sourceAncestorId) &&
166
+ outerNodeIds.includes(targetAncestorId)) {
167
+ outerEdges.push({
168
+ source: sourceAncestorId,
169
+ target: targetAncestorId
170
+ });
171
+ }
172
+ });
173
+
174
+ // 若有需要最外层的 combo 或节点,则对最外层执行力导向
175
+ if (outerNodes?.length) {
176
+ if (outerNodes.length === 1) {
177
+ outerNodes[0].x = center[0];
178
+ outerNodes[0].y = center[1];
179
+ } else {
180
+ const outerData = {
181
+ nodes: outerNodes,
182
+ edges: outerEdges
183
+ };
184
+
185
+ // 需要使用一个同步的布局
186
+ // @ts-ignore
187
+ const outerLayout = this.outerLayout || new GForceLayout({
188
+ gravity: 1,
189
+ factor: 2,
190
+ linkDistance: (edge: any, source: any, target: any) => {
191
+ const nodeSize = ((source.size?.[0] || 30) + (target.size?.[0] || 30)) / 2;
192
+ return Math.min(nodeSize * 1.5, 700);
193
+ }
194
+ });
195
+ const outerLayoutType = outerLayout.getType?.();
196
+ outerLayout.updateCfg({
197
+ center,
198
+ kg: 5,
199
+ preventOverlap: true,
200
+ animate: false,
201
+ });
202
+ // 若所有 outerNodes 都没有位置,且 outerLayout 是力导家族的布局,则先执行 preset mds 或 grid
203
+ if (allHaveNoPosition && FORCE_LAYOUT_TYPE_MAP[outerLayoutType]) {
204
+ const outerLayoutPreset = outerNodes.length < 100 ? new MDSLayout() : new GridLayout();
205
+ outerLayoutPreset.layout(outerData);
206
+ }
207
+ outerLayout.layout(outerData);
208
+ }
209
+ // 根据外部布局结果,平移 innerGraphs 中的节点(第一层)
210
+ outerNodes.forEach((oNode) => {
211
+ const innerGraph = innerGraphs[oNode.id];
212
+ if (!innerGraph) {
213
+ const node = nodeMap[oNode.id];
214
+ if (node) {
215
+ node.x = oNode.x;
216
+ node.y = oNode.y;
217
+ }
218
+ return;
219
+ }
220
+ innerGraph.visited = true;
221
+ innerGraph.x = oNode.x;
222
+ innerGraph.y = oNode.y;
223
+ innerGraph.nodes.forEach((node: OutNode) => {
224
+ node.x += oNode.x;
225
+ node.y += oNode.y;
226
+ });
227
+ });
228
+ }
229
+
230
+ // 至上而下遍历树处理下面各层节点位置
231
+ const innerGraphIds = Object.keys(innerGraphs);
232
+ for (let i = innerGraphIds.length - 1; i >= 0; i--) {
233
+ const id = innerGraphIds[i];
234
+ const innerGraph = innerGraphs[id];
235
+ if (!innerGraph) continue;
236
+ innerGraph.nodes.forEach((node: OutNode) => {
237
+ if (!innerGraph.visited) {
238
+ node.x += (innerGraph.x || 0);
239
+ node.y += (innerGraph.y || 0);
240
+ }
241
+ if (nodeMap[node.id]) {
242
+ nodeMap[node.id].x = node.x;
243
+ nodeMap[node.id].y = node.y;
244
+ }
245
+ });
246
+ if (comboMap[id]) {
247
+ comboMap[id].x = innerGraph.x;
248
+ comboMap[id].y = innerGraph.y;
249
+ }
250
+ }
251
+ return { nodes, edges, combos, comboEdges };
252
+ }
253
+
254
+ private getInnerGraphs(nodeMap: any) {
255
+ const self = this;
256
+ const { comboTrees, nodeSize, edges, comboPadding, spacing } = self;
257
+ const innerGraphs: any = {};
258
+
259
+ // @ts-ignore
260
+ const innerGraphLayout: any = this.innerLayout || (new ConcentricLayout({ sortBy: 'id' }));
261
+ innerGraphLayout.center = [0, 0];
262
+ innerGraphLayout.preventOverlap = true;
263
+ innerGraphLayout.nodeSpacing = spacing;
264
+
265
+ (comboTrees || []).forEach((ctree: any) => {
266
+ traverseTreeUp<ComboTree>(ctree, (treeNode) => {
267
+ // @ts-ignore
268
+ let padding = comboPadding?.(treeNode) || 10; // 返回的最大值
269
+ if (isArray(padding)) padding = Math.max(...padding);
270
+ if (!treeNode.children?.length) {
271
+ // 空 combo
272
+ if (treeNode.itemType === 'combo') {
273
+ const treeNodeSize = padding ? [padding * 2, padding * 2] : [30, 30];
274
+ innerGraphs[treeNode.id] = {
275
+ id: treeNode.id,
276
+ nodes: [],
277
+ size: treeNodeSize
278
+ };
279
+ }
280
+ } else {
281
+ // 非空 combo
282
+ const innerGraphNodes = treeNode.children.map((child) => {
283
+ if (child.itemType === 'combo') return innerGraphs[child.id];
284
+ const oriNode = nodeMap[child.id] || {};
285
+ return { ...oriNode, ...child };
286
+ });
287
+ const innerGraphNodeIds = innerGraphNodes.map((node) => node.id);
288
+ const innerGraphData = {
289
+ nodes: innerGraphNodes,
290
+ edges: edges.filter((edge) => innerGraphNodeIds.includes(edge.source) && innerGraphNodeIds.includes(edge.target))
291
+ };
292
+ let minNodeSize = Infinity;
293
+ innerGraphNodes.forEach((node) => {
294
+ // @ts-ignore
295
+ if (!node.size) node.size = innerGraphs[node.id]?.size || nodeSize?.(node) || [30, 30];
296
+ if (isNumber(node.size)) node.size = [node.size, node.size];
297
+ if (minNodeSize > node.size[0]) minNodeSize = node.size[0];
298
+ if (minNodeSize > node.size[1]) minNodeSize = node.size[1];
299
+ });
300
+
301
+ // 根据节点数量、spacing,调整布局参数
302
+
303
+ innerGraphLayout.layout(innerGraphData);
304
+ const { minX, minY, maxX, maxY } = findMinMaxNodeXY(innerGraphNodes);
305
+ const innerGraphSize = Math.max(maxX - minX, maxY - minY, minNodeSize) + padding * 2;
306
+ innerGraphs[treeNode.id] = {
307
+ id: treeNode.id,
308
+ nodes: innerGraphNodes,
309
+ size: [innerGraphSize, innerGraphSize]
310
+ };
311
+ }
312
+ return true;
313
+ });
314
+ });
315
+ return innerGraphs;
316
+ }
317
+
318
+ private initVals() {
319
+ const self = this;
320
+
321
+ const nodeSize = self.nodeSize;
322
+ const spacing = self.spacing;
323
+ let nodeSizeFunc: (d: any) => number;
324
+ let spacingFunc: (d: any) => number;
325
+
326
+ // nodeSpacing to function
327
+ if (isNumber(spacing)) {
328
+ spacingFunc = () => spacing as any;
329
+ } else if (isFunction(spacing)) {
330
+ spacingFunc = spacing;
331
+ } else {
332
+ spacingFunc = () => 0;
333
+ }
334
+ this.spacing = spacingFunc;
335
+
336
+ // nodeSize to function
337
+ if (!nodeSize) {
338
+ nodeSizeFunc = (d) => {
339
+ const spacing = spacingFunc(d);
340
+ if (d.size) {
341
+ if (isArray(d.size)) {
342
+ const res = d.size[0] > d.size[1] ? d.size[0] : d.size[1];
343
+ return (res + spacing) / 2;
344
+ } if (isObject(d.size)) {
345
+ const res = d.size.width > d.size.height ? d.size.width : d.size.height;
346
+ return (res + spacing) / 2;
347
+ }
348
+ return (d.size + spacing) / 2;
349
+ }
350
+ return 10 + spacing / 2;
351
+ };
352
+ } else if (isFunction(nodeSize)) {
353
+ nodeSizeFunc = (d) => {
354
+ const size = nodeSize(d);
355
+ const spacing = spacingFunc(d);
356
+ if (isArray(d.size)) {
357
+ const res = d.size[0] > d.size[1] ? d.size[0] : d.size[1];
358
+ return (res + spacing) / 2;
359
+ }
360
+ return ((size || 10) + spacing) / 2;
361
+ };
362
+ } else if (isArray(nodeSize)) {
363
+ const larger = nodeSize[0] > nodeSize[1] ? nodeSize[0] : nodeSize[1];
364
+ const radius = larger / 2;
365
+ nodeSizeFunc = (d) => radius + spacingFunc(d) / 2;
366
+ } else {
367
+ // number type
368
+ const radius = nodeSize / 2;
369
+ nodeSizeFunc = (d) => radius + spacingFunc(d) / 2;
370
+ }
371
+ this.nodeSize = nodeSizeFunc;
372
+
373
+ // comboPadding to function
374
+ const comboPadding = self.comboPadding;
375
+ let comboPaddingFunc: (d: any) => number;
376
+ if (isNumber(comboPadding)) {
377
+ comboPaddingFunc = () => comboPadding as any;
378
+ } else if (isArray(comboPadding)) {
379
+ comboPaddingFunc = () => Math.max.apply(null, comboPadding);
380
+ } else if (isFunction(comboPadding)) {
381
+ comboPaddingFunc = comboPadding;
382
+ } else {
383
+ // null type
384
+ comboPaddingFunc = () => 0;
385
+ }
386
+ this.comboPadding = comboPaddingFunc;
387
+ }
388
+ public getType() {
389
+ return "comboCombined";
390
+ }
391
+ }