@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,107 @@
1
+ import Body from './body';
2
+ import Quad from './quad';
3
+
4
+ /**
5
+ * @fileOverview quadTree
6
+ * @author shiwu.wyy@antfin.com
7
+ */
8
+
9
+ export default class QuadTree {
10
+ public body: Body | null;
11
+ public quad: Quad | null;
12
+ public theta: number;
13
+ public NW: QuadTree | null;
14
+ public NE: QuadTree | null;
15
+ public SW: QuadTree | null;
16
+ public SE: QuadTree | null;
17
+
18
+ // each quadtree represents a quadrant and an aggregate body
19
+ // that represents all bodies inside the quadrant
20
+ constructor(param: Quad | null) {
21
+ /**
22
+ * (aggregated) body in this quad
23
+ * @type {object}
24
+ */
25
+ this.body = null;
26
+ /**
27
+ * tree representing the northwest quadrant
28
+ * @type {object}
29
+ */
30
+ this.quad = null;
31
+ this.NW = null;
32
+ this.NE = null;
33
+ this.SW = null;
34
+ this.SE = null;
35
+ /**
36
+ * threshold
37
+ * @type {number}
38
+ */
39
+ this.theta = 0.5;
40
+ if (param != null) this.quad = param;
41
+ }
42
+ // insert a body(node) into the tree
43
+ insert(bo: Body) {
44
+ // if this node does not contain a body, put the new body bo here
45
+ if (this.body == null) {
46
+ this.body = bo;
47
+ return;
48
+ }
49
+ // internal node
50
+ if (!this._isExternal()) {
51
+ // update mass info
52
+ this.body = this.body.add(bo);
53
+ // insert body into quadrant
54
+ this._putBody(bo);
55
+ } else { // external node
56
+ // divide this region into four children
57
+ if (this.quad) {
58
+ this.NW = new QuadTree(this.quad.NW());
59
+ this.NE = new QuadTree(this.quad.NE());
60
+ this.SW = new QuadTree(this.quad.SW());
61
+ this.SE = new QuadTree(this.quad.SE());
62
+ }
63
+
64
+ // insert this body and bo
65
+ this._putBody(this.body);
66
+ this._putBody(bo);
67
+ // update the mass info
68
+ this.body = this.body.add(bo);
69
+
70
+ }
71
+ }
72
+ // inserts bo into a quad
73
+ // tslint:disable-next-line
74
+ _putBody(bo: Body) {
75
+ if (!this.quad) return;
76
+ if (bo.in(this.quad.NW()) && this.NW) this.NW.insert(bo);
77
+ else if (bo.in(this.quad.NE()) && this.NE) this.NE.insert(bo);
78
+ else if (bo.in(this.quad.SW()) && this.SW )this.SW.insert(bo);
79
+ else if (bo.in(this.quad.SE()) && this.SE) this.SE.insert(bo);
80
+ }
81
+ // tslint:disable-next-line
82
+ _isExternal() {
83
+ // four children are null
84
+ return (this.NW == null && this.NE == null && this.SW == null && this.SE == null);
85
+ }
86
+ // update the forces
87
+ updateForce(bo: Body) {
88
+ if (this.body == null || bo === this.body) {
89
+ return;
90
+ }
91
+ // if the current node is external
92
+ if (this._isExternal()) bo.addForce(this.body);
93
+ // internal nodes
94
+ else {
95
+ const s = this.quad ? this.quad.getLength() : 0;
96
+ const d = this.body.distanceTo(bo);
97
+ // b is far enough
98
+ if ((s / d) < this.theta) bo.addForce(this.body);
99
+ else {
100
+ this.NW && this.NW.updateForce(bo);
101
+ this.NE && this.NE.updateForce(bo);
102
+ this.SW && this.SW.updateForce(bo);
103
+ this.SE && this.SE.updateForce(bo);
104
+ }
105
+ }
106
+ }
107
+ }
@@ -0,0 +1,361 @@
1
+ /**
2
+ * @fileOverview fruchterman layout
3
+ * @author shiwu.wyy@antfin.com
4
+ */
5
+
6
+ import {
7
+ OutNode,
8
+ Edge,
9
+ PointTuple,
10
+ IndexMap,
11
+ Point,
12
+ FruchtermanLayoutOptions
13
+ } from "./types";
14
+ import { Base } from "./base";
15
+ import { getEdgeTerminal, isNumber } from "../util";
16
+
17
+ type NodeMap = {
18
+ [key: string]: INode;
19
+ };
20
+
21
+ type INode = OutNode & {
22
+ cluster: string;
23
+ };
24
+
25
+ const SPEED_DIVISOR = 800;
26
+
27
+ /**
28
+ * fruchterman 布局
29
+ */
30
+ export class FruchtermanLayout extends Base {
31
+ /** 布局中心 */
32
+ public center: PointTuple;
33
+
34
+ /** 停止迭代的最大迭代数 */
35
+ public maxIteration: number = 1000;
36
+
37
+ /** 是否启动 worker */
38
+ public workerEnabled: boolean = false;
39
+
40
+ /** 重力大小,影响图的紧凑程度 */
41
+ public gravity: number = 10;
42
+
43
+ /** 速度 */
44
+ public speed: number = 5;
45
+
46
+ /** 是否产生聚类力 */
47
+ public clustering: boolean = false;
48
+
49
+ /** 聚类力大小 */
50
+ public clusterGravity: number = 10;
51
+
52
+ public nodes: INode[] | null = [];
53
+
54
+ public edges: Edge[] | null = [];
55
+
56
+ public width: number = 300;
57
+
58
+ public height: number = 300;
59
+
60
+ public nodeMap: NodeMap = {};
61
+
62
+ public nodeIdxMap: IndexMap = {};
63
+
64
+ /** 迭代结束的回调函数 */
65
+ public onLayoutEnd: () => void = () => {};
66
+
67
+ /** 每次迭代结束的回调函数 */
68
+ public tick: (() => void) | null = () => {};
69
+
70
+ /** 是否使用 window.setInterval 运行迭代 */
71
+ public animate: boolean = true;
72
+
73
+ /** 迭代中的标识 */
74
+ private timeInterval: number;
75
+
76
+ constructor(options?: FruchtermanLayoutOptions) {
77
+ super();
78
+ this.updateCfg(options);
79
+ }
80
+
81
+ public getDefaultCfg() {
82
+ return {
83
+ maxIteration: 1000,
84
+ gravity: 10,
85
+ speed: 1,
86
+ clustering: false,
87
+ clusterGravity: 10,
88
+ animate: true
89
+ };
90
+ }
91
+
92
+ /**
93
+ * 执行布局
94
+ */
95
+ public execute() {
96
+ const self = this;
97
+ const nodes = self.nodes;
98
+
99
+ if (self.timeInterval !== undefined && typeof window !== "undefined") {
100
+ window.clearInterval(self.timeInterval);
101
+ }
102
+
103
+ if (!nodes || nodes.length === 0) {
104
+ self.onLayoutEnd?.();
105
+ return;
106
+ }
107
+
108
+ if (!self.width && typeof window !== "undefined") {
109
+ self.width = window.innerWidth;
110
+ }
111
+ if (!self.height && typeof window !== "undefined") {
112
+ self.height = window.innerHeight;
113
+ }
114
+ if (!self.center) {
115
+ self.center = [self.width / 2, self.height / 2];
116
+ }
117
+ const center = self.center;
118
+
119
+ if (nodes.length === 1) {
120
+ nodes[0].x = center[0];
121
+ nodes[0].y = center[1];
122
+ self.onLayoutEnd?.();
123
+ return;
124
+ }
125
+ const nodeMap: NodeMap = {};
126
+ const nodeIdxMap: IndexMap = {};
127
+ nodes.forEach((node, i) => {
128
+ if (!isNumber(node.x)) node.x = Math.random() * this.width;
129
+ if (!isNumber(node.y)) node.y = Math.random() * this.height;
130
+ nodeMap[node.id] = node;
131
+ nodeIdxMap[node.id] = i;
132
+ });
133
+ self.nodeMap = nodeMap;
134
+ self.nodeIdxMap = nodeIdxMap;
135
+ // layout
136
+ return self.run();
137
+ }
138
+
139
+ public run() {
140
+ const self = this;
141
+ const nodes = self.nodes;
142
+ if (!nodes) return;
143
+ const { edges, maxIteration, workerEnabled, clustering, animate } = self;
144
+ const clusterMap: {
145
+ [key: string]: {
146
+ name: string | number;
147
+ cx: number;
148
+ cy: number;
149
+ count: number;
150
+ };
151
+ } = {};
152
+ if (clustering) {
153
+ nodes.forEach((n) => {
154
+ if (clusterMap[n.cluster] === undefined) {
155
+ clusterMap[n.cluster] = {
156
+ name: n.cluster,
157
+ cx: 0,
158
+ cy: 0,
159
+ count: 0
160
+ };
161
+ }
162
+ });
163
+ }
164
+ if (workerEnabled || !animate) {
165
+ for (let i = 0; i < maxIteration; i++) {
166
+ self.runOneStep(clusterMap);
167
+ }
168
+ self.onLayoutEnd?.();
169
+ } else {
170
+ if (typeof window === "undefined") return;
171
+ let iter = 0;
172
+ // interval for render the result after each iteration
173
+ this.timeInterval = window.setInterval(() => {
174
+ self.runOneStep(clusterMap);
175
+ iter++;
176
+ if (iter >= maxIteration) {
177
+ self.onLayoutEnd?.();
178
+ window.clearInterval(self.timeInterval);
179
+ }
180
+ }, 0);
181
+ }
182
+ return {
183
+ nodes,
184
+ edges
185
+ };
186
+ }
187
+
188
+ private runOneStep(clusterMap: any) {
189
+ const self = this;
190
+ const nodes = self.nodes;
191
+ if (!nodes) return;
192
+ const { edges, center, gravity, speed, clustering } = self;
193
+ const area = self.height * self.width;
194
+ const maxDisplace = Math.sqrt(area) / 10;
195
+ const k2 = area / (nodes.length + 1);
196
+ const k = Math.sqrt(k2);
197
+ const displacements: Point[] = [];
198
+ nodes.forEach((_, j) => {
199
+ displacements[j] = { x: 0, y: 0 };
200
+ });
201
+ self.applyCalculate(nodes, edges, displacements, k, k2);
202
+
203
+ // gravity for clusters
204
+ if (clustering) {
205
+ // re-compute the clustering centers
206
+ for (const key in clusterMap) {
207
+ clusterMap[key].cx = 0;
208
+ clusterMap[key].cy = 0;
209
+ clusterMap[key].count = 0;
210
+ }
211
+ nodes.forEach((n) => {
212
+ const c = clusterMap[n.cluster];
213
+ if (isNumber(n.x)) {
214
+ c.cx += n.x;
215
+ }
216
+ if (isNumber(n.y)) {
217
+ c.cy += n.y;
218
+ }
219
+ c.count++;
220
+ });
221
+ for (const key in clusterMap) {
222
+ clusterMap[key].cx /= clusterMap[key].count;
223
+ clusterMap[key].cy /= clusterMap[key].count;
224
+ }
225
+
226
+ // compute the cluster gravity forces
227
+ const clusterGravity = self.clusterGravity || gravity;
228
+ nodes.forEach((n, j) => {
229
+ if (!isNumber(n.x) || !isNumber(n.y)) return;
230
+ const c = clusterMap[n.cluster];
231
+ const distLength = Math.sqrt(
232
+ (n.x - c.cx) * (n.x - c.cx) + (n.y - c.cy) * (n.y - c.cy)
233
+ );
234
+ const gravityForce = k * clusterGravity;
235
+ displacements[j].x -= (gravityForce * (n.x - c.cx)) / distLength;
236
+ displacements[j].y -= (gravityForce * (n.y - c.cy)) / distLength;
237
+ });
238
+ }
239
+
240
+ // gravity
241
+ nodes.forEach((n, j) => {
242
+ if (!isNumber(n.x) || !isNumber(n.y)) return;
243
+ const gravityForce = 0.01 * k * gravity;
244
+ displacements[j].x -= gravityForce * (n.x - center[0]);
245
+ displacements[j].y -= gravityForce * (n.y - center[1]);
246
+ });
247
+
248
+ // move
249
+ nodes.forEach((n: any, j) => {
250
+ if (isNumber(n.fx) && isNumber(n.fy)) {
251
+ n.x = n.fx;
252
+ n.y = n.fy;
253
+ return;
254
+ }
255
+ if (!isNumber(n.x) || !isNumber(n.y)) return;
256
+ const distLength = Math.sqrt(
257
+ displacements[j].x * displacements[j].x +
258
+ displacements[j].y * displacements[j].y
259
+ );
260
+ if (distLength > 0) {
261
+ // && !n.isFixed()
262
+ const limitedDist = Math.min(
263
+ maxDisplace * (speed / SPEED_DIVISOR),
264
+ distLength
265
+ );
266
+ n.x += (displacements[j].x / distLength) * limitedDist;
267
+ n.y += (displacements[j].y / distLength) * limitedDist;
268
+ }
269
+ });
270
+
271
+ self.tick?.();
272
+ }
273
+
274
+ private applyCalculate(
275
+ nodes: INode[],
276
+ edges: Edge[] | null,
277
+ displacements: Point[],
278
+ k: number,
279
+ k2: number
280
+ ) {
281
+ const self = this;
282
+ self.calRepulsive(nodes, displacements, k2);
283
+ if (edges) self.calAttractive(edges, displacements, k);
284
+ }
285
+
286
+ private calRepulsive(nodes: INode[], displacements: Point[], k2: number) {
287
+ nodes.forEach((v, i) => {
288
+ displacements[i] = { x: 0, y: 0 };
289
+ nodes.forEach((u, j) => {
290
+ if (i === j) {
291
+ return;
292
+ }
293
+ if (
294
+ !isNumber(v.x) ||
295
+ !isNumber(u.x) ||
296
+ !isNumber(v.y) ||
297
+ !isNumber(u.y)
298
+ ) {
299
+ return;
300
+ }
301
+ let vecX = v.x - u.x;
302
+ let vecY = v.y - u.y;
303
+ let vecLengthSqr = vecX * vecX + vecY * vecY;
304
+ if (vecLengthSqr === 0) {
305
+ vecLengthSqr = 1;
306
+ const sign = i > j ? 1 : -1;
307
+ vecX = 0.01 * sign;
308
+ vecY = 0.01 * sign;
309
+ }
310
+ const common = k2 / vecLengthSqr;
311
+ displacements[i].x += vecX * common;
312
+ displacements[i].y += vecY * common;
313
+ });
314
+ });
315
+ }
316
+
317
+ private calAttractive(edges: Edge[], displacements: Point[], k: number) {
318
+ edges.forEach((e) => {
319
+ const source = getEdgeTerminal(e, 'source');
320
+ const target = getEdgeTerminal(e, 'target');
321
+ if (!source || !target) return;
322
+ const uIndex = this.nodeIdxMap[source];
323
+ const vIndex = this.nodeIdxMap[target];
324
+ if (uIndex === vIndex) {
325
+ return;
326
+ }
327
+ const u = this.nodeMap[source];
328
+ const v = this.nodeMap[target];
329
+ if (!isNumber(v.x) || !isNumber(u.x) || !isNumber(v.y) || !isNumber(u.y)) {
330
+ return;
331
+ }
332
+ const vecX = v.x - u.x;
333
+ const vecY = v.y - u.y;
334
+ const vecLength = Math.sqrt(vecX * vecX + vecY * vecY);
335
+ const common = (vecLength * vecLength) / k;
336
+ displacements[vIndex].x -= (vecX / vecLength) * common;
337
+ displacements[vIndex].y -= (vecY / vecLength) * common;
338
+ displacements[uIndex].x += (vecX / vecLength) * common;
339
+ displacements[uIndex].y += (vecY / vecLength) * common;
340
+ });
341
+ }
342
+
343
+ public stop() {
344
+ if (this.timeInterval && typeof window !== "undefined") {
345
+ window.clearInterval(this.timeInterval);
346
+ }
347
+ }
348
+
349
+ public destroy() {
350
+ const self = this;
351
+ self.stop();
352
+ self.tick = null;
353
+ self.nodes = null;
354
+ self.edges = null;
355
+ self.destroyed = true;
356
+ }
357
+
358
+ public getType() {
359
+ return "fruchterman";
360
+ }
361
+ }