@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.
- package/dist/layout.min.js +1 -1
- package/dist/layout.min.js.map +1 -1
- package/es/layout/comboCombined.js +6 -21
- package/es/layout/comboCombined.js.map +1 -1
- package/es/layout/grid.js +2 -0
- package/es/layout/grid.js.map +1 -1
- package/es/layout/types.d.ts +7 -3
- package/lib/layout/comboCombined.js +6 -21
- package/lib/layout/comboCombined.js.map +1 -1
- package/lib/layout/grid.js +2 -0
- package/lib/layout/grid.js.map +1 -1
- package/lib/layout/types.d.ts +7 -3
- package/package.json +2 -1
- package/src/index.ts +7 -0
- package/src/layout/base.ts +54 -0
- package/src/layout/circular.ts +369 -0
- package/src/layout/comboCombined.ts +391 -0
- package/src/layout/comboForce.ts +873 -0
- package/src/layout/concentric.ts +289 -0
- package/src/layout/constants.ts +21 -0
- package/src/layout/dagre/graph.ts +104 -0
- package/src/layout/dagre/index.ts +31 -0
- package/src/layout/dagre/src/acyclic.ts +58 -0
- package/src/layout/dagre/src/add-border-segments.ts +47 -0
- package/src/layout/dagre/src/coordinate-system.ts +77 -0
- package/src/layout/dagre/src/data/list.ts +60 -0
- package/src/layout/dagre/src/debug.ts +30 -0
- package/src/layout/dagre/src/greedy-fas.ts +144 -0
- package/src/layout/dagre/src/layout.ts +580 -0
- package/src/layout/dagre/src/nesting-graph.ts +143 -0
- package/src/layout/dagre/src/normalize.ts +96 -0
- package/src/layout/dagre/src/order/add-subgraph-constraints.ts +29 -0
- package/src/layout/dagre/src/order/barycenter.ts +26 -0
- package/src/layout/dagre/src/order/build-layer-graph.ts +82 -0
- package/src/layout/dagre/src/order/cross-count.ts +77 -0
- package/src/layout/dagre/src/order/index.ts +105 -0
- package/src/layout/dagre/src/order/init-data-order.ts +27 -0
- package/src/layout/dagre/src/order/init-order.ts +56 -0
- package/src/layout/dagre/src/order/resolve-conflicts.ts +152 -0
- package/src/layout/dagre/src/order/sort-subgraph.ts +105 -0
- package/src/layout/dagre/src/order/sort.ts +76 -0
- package/src/layout/dagre/src/parent-dummy-chains.ts +102 -0
- package/src/layout/dagre/src/position/bk.ts +494 -0
- package/src/layout/dagre/src/position/index.ts +82 -0
- package/src/layout/dagre/src/rank/feasible-tree.ts +165 -0
- package/src/layout/dagre/src/rank/index.ts +54 -0
- package/src/layout/dagre/src/rank/network-simplex.ts +225 -0
- package/src/layout/dagre/src/rank/util.ts +157 -0
- package/src/layout/dagre/src/util.ts +308 -0
- package/src/layout/dagre.ts +423 -0
- package/src/layout/dagreCompound.ts +518 -0
- package/src/layout/er/core.ts +117 -0
- package/src/layout/er/forceGrid.ts +95 -0
- package/src/layout/er/grid.ts +185 -0
- package/src/layout/er/index.ts +68 -0
- package/src/layout/er/mysqlWorkbench.ts +345 -0
- package/src/layout/er/type.ts +39 -0
- package/src/layout/force/force-in-a-box.ts +400 -0
- package/src/layout/force/force.ts +391 -0
- package/src/layout/force/index.ts +1 -0
- package/src/layout/forceAtlas2/body.ts +115 -0
- package/src/layout/forceAtlas2/index.ts +556 -0
- package/src/layout/forceAtlas2/quad.ts +115 -0
- package/src/layout/forceAtlas2/quadTree.ts +107 -0
- package/src/layout/fruchterman.ts +361 -0
- package/src/layout/gForce.ts +487 -0
- package/src/layout/gpu/fruchterman.ts +314 -0
- package/src/layout/gpu/fruchtermanShader.ts +204 -0
- package/src/layout/gpu/gForce.ts +406 -0
- package/src/layout/gpu/gForceShader.ts +221 -0
- package/src/layout/grid.ts +393 -0
- package/src/layout/index.ts +45 -0
- package/src/layout/layout.ts +75 -0
- package/src/layout/mds.ts +140 -0
- package/src/layout/radial/index.ts +1 -0
- package/src/layout/radial/mds.ts +51 -0
- package/src/layout/radial/radial.ts +500 -0
- package/src/layout/radial/radialNonoverlapForce.ts +189 -0
- package/src/layout/random.ts +75 -0
- package/src/layout/types.ts +425 -0
- package/src/registy/index.ts +43 -0
- package/src/util/array.ts +1 -0
- package/src/util/function.ts +64 -0
- package/src/util/gpu.ts +254 -0
- package/src/util/index.ts +6 -0
- package/src/util/math.ts +158 -0
- package/src/util/number.ts +8 -0
- package/src/util/object.ts +28 -0
- package/src/util/string.ts +18 -0
- package/CHANGELOG.md +0 -88
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileOverview force atlas 2
|
|
3
|
+
* @author shiwu.wyy@antfin.com
|
|
4
|
+
*/
|
|
5
|
+
import { PointTuple, OutNode, Edge, ForceAtlas2LayoutOptions } from "../types";
|
|
6
|
+
import { Base } from "../base";
|
|
7
|
+
import { getEdgeTerminal, isArray, isNumber, isObject } from "../../util";
|
|
8
|
+
import Body from './body';
|
|
9
|
+
import Quad from './quad';
|
|
10
|
+
import QuadTree from './quadTree';
|
|
11
|
+
|
|
12
|
+
export class ForceAtlas2Layout extends Base {
|
|
13
|
+
/** 布局中心 */
|
|
14
|
+
public center: PointTuple = [0, 0];
|
|
15
|
+
|
|
16
|
+
/** 宽度 */
|
|
17
|
+
public width: number = 300;
|
|
18
|
+
|
|
19
|
+
/** 高度 */
|
|
20
|
+
public height: number = 300;
|
|
21
|
+
|
|
22
|
+
public nodes: OutNode[] = [];
|
|
23
|
+
|
|
24
|
+
public edges: Edge[] = [];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* the parameter for repulsive forces,
|
|
28
|
+
* it will scale the layout but won't change the layout
|
|
29
|
+
* larger the kr, looser the layout
|
|
30
|
+
* @type {number}
|
|
31
|
+
*/
|
|
32
|
+
public kr: number = 5;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* the parameter for gravity forces
|
|
36
|
+
* @type {number}
|
|
37
|
+
*/
|
|
38
|
+
public kg: number = 1;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* modes:
|
|
42
|
+
* 'normal' for normal using
|
|
43
|
+
* 'linlog' for compact clusters.
|
|
44
|
+
* @type {string}
|
|
45
|
+
*/
|
|
46
|
+
public mode: 'normal' | 'linlog' = 'normal';
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* whether preventing the node overlapping
|
|
50
|
+
* @type {boolean}
|
|
51
|
+
*/
|
|
52
|
+
public preventOverlap: boolean = false;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* whether active the dissuade hub mode
|
|
56
|
+
* true: grant authorities (nodes with a high indegree)
|
|
57
|
+
* a more central position than hubs (nodes with a high outdegree)
|
|
58
|
+
* @type {boolean}
|
|
59
|
+
*/
|
|
60
|
+
public dissuadeHubs: boolean = false;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* whether active the barnes hut optimization on computing repulsive forces
|
|
64
|
+
* @type {boolean}
|
|
65
|
+
*/
|
|
66
|
+
public barnesHut: boolean | undefined = undefined;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* the max iteration number
|
|
70
|
+
* @type {number}
|
|
71
|
+
*/
|
|
72
|
+
public maxIteration: number = 0;
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* control the global velocity
|
|
76
|
+
* defualt: 0.1(gephi)
|
|
77
|
+
* @type {number}
|
|
78
|
+
*/
|
|
79
|
+
public ks: number = 0.1;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* the max global velocity
|
|
83
|
+
* @type {number}
|
|
84
|
+
*/
|
|
85
|
+
public ksmax: number = 10;
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* the tolerance for the global swinging
|
|
89
|
+
* @type {number}
|
|
90
|
+
*/
|
|
91
|
+
public tao: number = 0.1;
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* the function of layout complete listener, display the legend and minimap after layout
|
|
95
|
+
* @type {function}
|
|
96
|
+
*/
|
|
97
|
+
public onLayoutEnd: () => void = () => {};
|
|
98
|
+
|
|
99
|
+
public tick: () => void;
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* activate prune or not.
|
|
103
|
+
* prune the leaves during most iterations, layout the leaves in the last 50 iteraitons.
|
|
104
|
+
* if prune === '', it will be activated when the nodes number > 100
|
|
105
|
+
* note that it will reduce the quality of the layout
|
|
106
|
+
* @type {boolean}
|
|
107
|
+
*/
|
|
108
|
+
public prune: boolean | undefined = undefined;
|
|
109
|
+
|
|
110
|
+
public getWidth: (node: any) => number;
|
|
111
|
+
public getHeight: (node: any) => number;
|
|
112
|
+
|
|
113
|
+
constructor(options?: ForceAtlas2LayoutOptions) {
|
|
114
|
+
super();
|
|
115
|
+
this.updateCfg(options);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
public getDefaultCfg() {
|
|
119
|
+
return {};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// execute the layout
|
|
123
|
+
public execute() {
|
|
124
|
+
const self = this;
|
|
125
|
+
const {
|
|
126
|
+
nodes,
|
|
127
|
+
onLayoutEnd,
|
|
128
|
+
prune,
|
|
129
|
+
} = self;
|
|
130
|
+
let maxIteration = self.maxIteration;
|
|
131
|
+
|
|
132
|
+
if (!self.width && typeof window !== "undefined") {
|
|
133
|
+
self.width = window.innerWidth;
|
|
134
|
+
}
|
|
135
|
+
if (!self.height && typeof window !== "undefined") {
|
|
136
|
+
self.height = window.innerHeight;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// the whidth of each nodes
|
|
140
|
+
const sizes = [];
|
|
141
|
+
const nodeNum = nodes.length;
|
|
142
|
+
for (let i = 0; i < nodeNum; i += 1) {
|
|
143
|
+
const node = nodes[i] as any;
|
|
144
|
+
let nodeWidth = 10;
|
|
145
|
+
let nodeHeight = 10;
|
|
146
|
+
if (isNumber(node.size)) {
|
|
147
|
+
nodeWidth = node.size;
|
|
148
|
+
nodeHeight = node.size;
|
|
149
|
+
}
|
|
150
|
+
if (isArray(node.size)) {
|
|
151
|
+
if (!isNaN(node.size[0])) nodeWidth = node.size[0];
|
|
152
|
+
if (!isNaN(node.size[1])) nodeHeight = node.size[1];
|
|
153
|
+
} else if (isObject(node.size)) {
|
|
154
|
+
nodeWidth = node.size.width;
|
|
155
|
+
nodeHeight = node.size.height;
|
|
156
|
+
}
|
|
157
|
+
if (self.getWidth && !isNaN(self.getWidth(node))) nodeHeight = self.getWidth(node);
|
|
158
|
+
if (self.getHeight && !isNaN(self.getHeight(node))) nodeWidth = self.getHeight(node);
|
|
159
|
+
|
|
160
|
+
const maxSize = Math.max(nodeWidth, nodeHeight);
|
|
161
|
+
sizes.push(maxSize);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (self.barnesHut === undefined && nodeNum > 250) self.barnesHut = true;
|
|
165
|
+
if (self.prune === undefined && nodeNum > 100) self.prune = true;
|
|
166
|
+
if (this.maxIteration === 0 && !self.prune) {
|
|
167
|
+
maxIteration = 250;
|
|
168
|
+
if (nodeNum <= 200 && nodeNum > 100) maxIteration = 1000;
|
|
169
|
+
else if (nodeNum > 200) maxIteration = 1200;
|
|
170
|
+
this.maxIteration = maxIteration;
|
|
171
|
+
} else if (this.maxIteration === 0 && prune) {
|
|
172
|
+
maxIteration = 100;
|
|
173
|
+
if (nodeNum <= 200 && nodeNum > 100) maxIteration = 500;
|
|
174
|
+
else if (nodeNum > 200) maxIteration = 950;
|
|
175
|
+
this.maxIteration = maxIteration;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (!self.kr) {
|
|
179
|
+
self.kr = 50;
|
|
180
|
+
if (nodeNum > 100 && nodeNum <= 500) self.kr = 20;
|
|
181
|
+
else if (nodeNum > 500) self.kr = 1;
|
|
182
|
+
}
|
|
183
|
+
if (!self.kg) {
|
|
184
|
+
self.kg = 20;
|
|
185
|
+
if (nodeNum > 100 && nodeNum <= 500) self.kg = 10;
|
|
186
|
+
else if (nodeNum > 500) self.kg = 1;
|
|
187
|
+
}
|
|
188
|
+
this.nodes = self.updateNodesByForces(sizes);
|
|
189
|
+
onLayoutEnd();
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
updateNodesByForces(sizes: number[]) {
|
|
194
|
+
const self = this;
|
|
195
|
+
const { edges, maxIteration } = self;
|
|
196
|
+
let nodes = self.nodes;
|
|
197
|
+
|
|
198
|
+
const nonLoopEdges = edges.filter((edge: any) => {
|
|
199
|
+
const source = getEdgeTerminal(edge, 'source');
|
|
200
|
+
const target = getEdgeTerminal(edge, 'target');
|
|
201
|
+
return source !== target;
|
|
202
|
+
});
|
|
203
|
+
const size = nodes.length;
|
|
204
|
+
const esize = nonLoopEdges.length;
|
|
205
|
+
|
|
206
|
+
const degrees = [];
|
|
207
|
+
const idMap: {[key: string]: number} = {};
|
|
208
|
+
const edgeEndsIdMap: {[key: number]: {sourceIdx: number, targetIdx: number}} = {};
|
|
209
|
+
|
|
210
|
+
// tslint:disable-next-line
|
|
211
|
+
const Es = []
|
|
212
|
+
for (let i = 0; i < size; i += 1) {
|
|
213
|
+
idMap[nodes[i].id] = i;
|
|
214
|
+
degrees[i] = 0;
|
|
215
|
+
if (nodes[i].x === undefined || isNaN(nodes[i].x)) { nodes[i].x = Math.random() * 1000; }
|
|
216
|
+
if (nodes[i].y === undefined || isNaN(nodes[i].y)) { nodes[i].y = Math.random() * 1000; }
|
|
217
|
+
Es.push({ x: nodes[i].x, y: nodes[i].y });
|
|
218
|
+
}
|
|
219
|
+
for (let i = 0; i < esize; i += 1) {
|
|
220
|
+
let node1;
|
|
221
|
+
let node2;
|
|
222
|
+
let sIdx = 0;
|
|
223
|
+
let tIdx = 0;
|
|
224
|
+
|
|
225
|
+
for (let j = 0; j < size; j += 1) {
|
|
226
|
+
const source = getEdgeTerminal(nonLoopEdges[i], 'source');
|
|
227
|
+
const target = getEdgeTerminal(nonLoopEdges[i], 'target');
|
|
228
|
+
if (nodes[j].id === source) {
|
|
229
|
+
node1 = nodes[j];
|
|
230
|
+
sIdx = j;
|
|
231
|
+
} else if (nodes[j].id === target) {
|
|
232
|
+
node2 = nodes[j];
|
|
233
|
+
tIdx = j;
|
|
234
|
+
}
|
|
235
|
+
edgeEndsIdMap[i] = { sourceIdx: sIdx, targetIdx: tIdx };
|
|
236
|
+
}
|
|
237
|
+
if (node1) degrees[idMap[node1.id]] += 1;
|
|
238
|
+
if (node2) degrees[idMap[node2.id]] += 1;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let iteration = maxIteration;
|
|
242
|
+
nodes = this.iterate(iteration, idMap, edgeEndsIdMap, esize, degrees, sizes);
|
|
243
|
+
|
|
244
|
+
// if prune, place the leaves around their parents, and then re-layout for several iterations.
|
|
245
|
+
if (self.prune) {
|
|
246
|
+
for (let j = 0; j < esize; j += 1) {
|
|
247
|
+
if (degrees[edgeEndsIdMap[j].sourceIdx] <= 1) {
|
|
248
|
+
nodes[edgeEndsIdMap[j].sourceIdx].x = nodes[edgeEndsIdMap[j].targetIdx].x;
|
|
249
|
+
nodes[edgeEndsIdMap[j].sourceIdx].y = nodes[edgeEndsIdMap[j].targetIdx].y;
|
|
250
|
+
|
|
251
|
+
} else if (degrees[edgeEndsIdMap[j].targetIdx] <= 1) {
|
|
252
|
+
nodes[edgeEndsIdMap[j].targetIdx].x = nodes[edgeEndsIdMap[j].sourceIdx].x;
|
|
253
|
+
nodes[edgeEndsIdMap[j].targetIdx].y = nodes[edgeEndsIdMap[j].sourceIdx].y;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
self.prune = false;
|
|
257
|
+
self.barnesHut = false;
|
|
258
|
+
iteration = 100;
|
|
259
|
+
nodes = this.iterate(
|
|
260
|
+
iteration,
|
|
261
|
+
idMap,
|
|
262
|
+
edgeEndsIdMap,
|
|
263
|
+
esize,
|
|
264
|
+
degrees,
|
|
265
|
+
sizes
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
return nodes;
|
|
269
|
+
}
|
|
270
|
+
iterate(
|
|
271
|
+
iteration: number,
|
|
272
|
+
idMap: {[key: string]: number},
|
|
273
|
+
edgeEndsIdMap: {[key: number]: {sourceIdx: number, targetIdx: number}},
|
|
274
|
+
esize: number,
|
|
275
|
+
degrees: number[],
|
|
276
|
+
sizes: number[],
|
|
277
|
+
) {
|
|
278
|
+
|
|
279
|
+
const self = this;
|
|
280
|
+
let { nodes } = self;
|
|
281
|
+
const { kr, preventOverlap } = self;
|
|
282
|
+
const { barnesHut } = self;
|
|
283
|
+
|
|
284
|
+
const nodeNum = nodes.length;
|
|
285
|
+
let sg = 0;
|
|
286
|
+
const krPrime = 100;
|
|
287
|
+
let iter = iteration;
|
|
288
|
+
const prevoIter = 50;
|
|
289
|
+
let forces = [];
|
|
290
|
+
const preForces = [];
|
|
291
|
+
const bodies = [];
|
|
292
|
+
|
|
293
|
+
for (let i = 0; i < nodeNum; i += 1) {
|
|
294
|
+
forces[2 * i] = 0;
|
|
295
|
+
forces[2 * i + 1] = 0;
|
|
296
|
+
|
|
297
|
+
if (barnesHut) {
|
|
298
|
+
const params = {
|
|
299
|
+
id: i,
|
|
300
|
+
rx: nodes[i].x,
|
|
301
|
+
ry: nodes[i].y,
|
|
302
|
+
mass: 1,
|
|
303
|
+
g: kr,
|
|
304
|
+
degree: degrees[i]
|
|
305
|
+
};
|
|
306
|
+
bodies[i] = new Body(params);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
while (iter > 0) {
|
|
311
|
+
for (let i = 0; i < nodeNum; i += 1) {
|
|
312
|
+
preForces[2 * i] = forces[2 * i];
|
|
313
|
+
preForces[2 * i + 1] = forces[2 * i + 1];
|
|
314
|
+
forces[2 * i] = 0;
|
|
315
|
+
forces[2 * i + 1] = 0;
|
|
316
|
+
}
|
|
317
|
+
// attractive forces, existing on every actual edge
|
|
318
|
+
forces = this.getAttrForces(
|
|
319
|
+
iter,
|
|
320
|
+
prevoIter,
|
|
321
|
+
esize,
|
|
322
|
+
idMap,
|
|
323
|
+
edgeEndsIdMap,
|
|
324
|
+
degrees,
|
|
325
|
+
sizes,
|
|
326
|
+
forces
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
// repulsive forces and Gravity, existing on every node pair
|
|
330
|
+
// if preventOverlap, using the no-optimized method in the last prevoIter instead.
|
|
331
|
+
if (barnesHut && ((preventOverlap && iter > prevoIter) || !preventOverlap)) {
|
|
332
|
+
forces = this.getOptRepGraForces(forces, bodies, degrees);
|
|
333
|
+
} else {
|
|
334
|
+
forces = this.getRepGraForces(iter, prevoIter, forces, krPrime, sizes, degrees);
|
|
335
|
+
}
|
|
336
|
+
// update the positions
|
|
337
|
+
const res = this.updatePos(forces, preForces, sg, degrees);
|
|
338
|
+
nodes = res.nodes;
|
|
339
|
+
sg = res.sg;
|
|
340
|
+
iter --;
|
|
341
|
+
if (self.tick) self.tick();
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return nodes;
|
|
345
|
+
}
|
|
346
|
+
getAttrForces(
|
|
347
|
+
iter: number,
|
|
348
|
+
prevoIter: number,
|
|
349
|
+
esize: number,
|
|
350
|
+
idMap: {[key: string]: number},
|
|
351
|
+
edgeEndsIdMap: {[key: number]: {sourceIdx: number, targetIdx: number}},
|
|
352
|
+
degrees: number[],
|
|
353
|
+
sizes: number[],
|
|
354
|
+
forces: number[],
|
|
355
|
+
): number[] {
|
|
356
|
+
const self = this;
|
|
357
|
+
const { nodes, preventOverlap, dissuadeHubs, mode, prune } = self;
|
|
358
|
+
for (let i = 0; i < esize; i += 1) {
|
|
359
|
+
const sourceNode = nodes[edgeEndsIdMap[i].sourceIdx];
|
|
360
|
+
const sourceIdx = edgeEndsIdMap[i].sourceIdx;
|
|
361
|
+
const targetNode = nodes[edgeEndsIdMap[i].targetIdx];
|
|
362
|
+
const targetIdx = edgeEndsIdMap[i].targetIdx;
|
|
363
|
+
|
|
364
|
+
if (prune && (degrees[sourceIdx] <= 1 || degrees[targetIdx] <= 1)) continue;
|
|
365
|
+
|
|
366
|
+
const dir = [ targetNode.x - sourceNode.x, targetNode.y - sourceNode.y ];
|
|
367
|
+
let eucliDis = Math.hypot(dir[0], dir[1]);
|
|
368
|
+
eucliDis = eucliDis < 0.0001 ? 0.0001 : eucliDis;
|
|
369
|
+
dir[0] = dir[0] / eucliDis;
|
|
370
|
+
dir[1] = dir[1] / eucliDis;
|
|
371
|
+
|
|
372
|
+
if (preventOverlap && iter < prevoIter) eucliDis = eucliDis - sizes[sourceIdx] - sizes[targetIdx];
|
|
373
|
+
let Fa1 = eucliDis // tslint:disable-line
|
|
374
|
+
let Fa2 = Fa1 // tslint:disable-line
|
|
375
|
+
if (mode === 'linlog') {
|
|
376
|
+
Fa1 = Math.log(1 + eucliDis);
|
|
377
|
+
Fa2 = Fa1;
|
|
378
|
+
}
|
|
379
|
+
if (dissuadeHubs) {
|
|
380
|
+
Fa1 = eucliDis / degrees[sourceIdx];
|
|
381
|
+
Fa2 = eucliDis / degrees[targetIdx];
|
|
382
|
+
}
|
|
383
|
+
if (preventOverlap && iter < prevoIter && eucliDis <= 0) {
|
|
384
|
+
Fa1 = 0;
|
|
385
|
+
Fa2 = 0;
|
|
386
|
+
} else if (preventOverlap && iter < prevoIter && eucliDis > 0) {
|
|
387
|
+
Fa1 = eucliDis;
|
|
388
|
+
Fa2 = eucliDis;
|
|
389
|
+
}
|
|
390
|
+
forces[2 * idMap[sourceNode.id]] += Fa1 * dir[0];
|
|
391
|
+
forces[2 * idMap[targetNode.id]] -= Fa2 * dir[0];
|
|
392
|
+
forces[2 * idMap[sourceNode.id] + 1] += Fa1 * dir[1];
|
|
393
|
+
forces[2 * idMap[targetNode.id] + 1] -= Fa2 * dir[1];
|
|
394
|
+
}
|
|
395
|
+
return forces;
|
|
396
|
+
}
|
|
397
|
+
getRepGraForces(iter: number, prevoIter: number, forces: number[], krPrime: number, sizes: number[], degrees: number[]) {
|
|
398
|
+
const self = this;
|
|
399
|
+
const { nodes, preventOverlap, kr, kg, center, prune } = self;
|
|
400
|
+
const nodeNum = nodes.length;
|
|
401
|
+
for (let i = 0; i < nodeNum; i += 1) {
|
|
402
|
+
for (let j = i + 1; j < nodeNum; j += 1) {
|
|
403
|
+
|
|
404
|
+
if (prune && (degrees[i] <= 1 || degrees[j] <= 1)) continue;
|
|
405
|
+
|
|
406
|
+
const dir = [ nodes[j].x - nodes[i].x, nodes[j].y - nodes[i].y ];
|
|
407
|
+
let eucliDis = Math.hypot(dir[0], dir[1]);
|
|
408
|
+
eucliDis = eucliDis < 0.0001 ? 0.0001 : eucliDis;
|
|
409
|
+
dir[0] = dir[0] / eucliDis;
|
|
410
|
+
dir[1] = dir[1] / eucliDis;
|
|
411
|
+
|
|
412
|
+
if (preventOverlap && iter < prevoIter) eucliDis = eucliDis - sizes[i] - sizes[j];
|
|
413
|
+
|
|
414
|
+
let Fr = kr * (degrees[i] + 1) * (degrees[j] + 1) / eucliDis // tslint:disable-line
|
|
415
|
+
|
|
416
|
+
if (preventOverlap && iter < prevoIter && eucliDis < 0) {
|
|
417
|
+
Fr = krPrime * (degrees[i] + 1) * (degrees[j] + 1);
|
|
418
|
+
} else if (preventOverlap && iter < prevoIter && eucliDis === 0) {
|
|
419
|
+
Fr = 0;
|
|
420
|
+
} else if (preventOverlap && iter < prevoIter && eucliDis > 0) {
|
|
421
|
+
Fr = kr * (degrees[i] + 1) * (degrees[j] + 1) / eucliDis;
|
|
422
|
+
}
|
|
423
|
+
forces[2 * i] -= Fr * dir[0];
|
|
424
|
+
forces[2 * j] += Fr * dir[0];
|
|
425
|
+
forces[2 * i + 1] -= Fr * dir[1];
|
|
426
|
+
forces[2 * j + 1] += Fr * dir[1];
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// gravity
|
|
430
|
+
const dir = [ nodes[i].x - center[0], nodes[i].y - center[1] ];
|
|
431
|
+
const eucliDis = Math.hypot(dir[0], dir[1]);
|
|
432
|
+
dir[0] = dir[0] / eucliDis;
|
|
433
|
+
dir[1] = dir[1] / eucliDis;
|
|
434
|
+
const Fg = kg * (degrees[i] + 1) // tslint:disable-line
|
|
435
|
+
forces[2 * i] -= Fg * dir[0];
|
|
436
|
+
forces[2 * i + 1] -= Fg * dir[1];
|
|
437
|
+
}
|
|
438
|
+
return forces;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
getOptRepGraForces(forces: number[], bodies: any, degrees: number[]) {
|
|
442
|
+
const self = this;
|
|
443
|
+
const { nodes, kg, center, prune } = self;
|
|
444
|
+
const nodeNum = nodes.length;
|
|
445
|
+
let minx = 9e10;
|
|
446
|
+
let maxx = -9e10;
|
|
447
|
+
let miny = 9e10;
|
|
448
|
+
let maxy = -9e10;
|
|
449
|
+
for (let i = 0; i < nodeNum; i += 1) {
|
|
450
|
+
if (prune && (degrees[i] <= 1)) continue;
|
|
451
|
+
bodies[i].setPos(nodes[i].x, nodes[i].y);
|
|
452
|
+
if (nodes[i].x >= maxx) maxx = nodes[i].x;
|
|
453
|
+
if (nodes[i].x <= minx) minx = nodes[i].x;
|
|
454
|
+
if (nodes[i].y >= maxy) maxy = nodes[i].y;
|
|
455
|
+
if (nodes[i].y <= miny) miny = nodes[i].y;
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const width = Math.max(maxx - minx, maxy - miny);
|
|
459
|
+
|
|
460
|
+
const quadParams = {
|
|
461
|
+
xmid: (maxx + minx) / 2,
|
|
462
|
+
ymid: (maxy + miny) / 2,
|
|
463
|
+
length: width,
|
|
464
|
+
massCenter: center,
|
|
465
|
+
mass: nodeNum
|
|
466
|
+
};
|
|
467
|
+
const quad = new Quad(quadParams);
|
|
468
|
+
const quadTree = new QuadTree(quad);
|
|
469
|
+
|
|
470
|
+
// build the tree, insert the nodes(quads) into the tree
|
|
471
|
+
for (let i = 0; i < nodeNum; i += 1) {
|
|
472
|
+
|
|
473
|
+
if (prune && (degrees[i] <= 1)) continue;
|
|
474
|
+
|
|
475
|
+
if (bodies[i].in(quad)) quadTree.insert(bodies[i]);
|
|
476
|
+
}
|
|
477
|
+
// update the repulsive forces and the gravity.
|
|
478
|
+
for (let i = 0; i < nodeNum; i += 1) {
|
|
479
|
+
|
|
480
|
+
if (prune && (degrees[i] <= 1)) continue;
|
|
481
|
+
|
|
482
|
+
bodies[i].resetForce();
|
|
483
|
+
quadTree.updateForce(bodies[i]);
|
|
484
|
+
forces[2 * i] -= bodies[i].fx;
|
|
485
|
+
forces[2 * i + 1] -= bodies[i].fy;
|
|
486
|
+
|
|
487
|
+
// gravity
|
|
488
|
+
const dir = [ nodes[i].x - center[0], nodes[i].y - center[1] ];
|
|
489
|
+
let eucliDis = Math.hypot(dir[0], dir[1]);
|
|
490
|
+
eucliDis = eucliDis < 0.0001 ? 0.0001 : eucliDis;
|
|
491
|
+
dir[0] = dir[0] / eucliDis;
|
|
492
|
+
dir[1] = dir[1] / eucliDis;
|
|
493
|
+
const Fg = kg * (degrees[i] + 1) // tslint:disable-line
|
|
494
|
+
forces[2 * i] -= Fg * dir[0];
|
|
495
|
+
forces[2 * i + 1] -= Fg * dir[1];
|
|
496
|
+
}
|
|
497
|
+
return forces;
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
updatePos(
|
|
501
|
+
forces: number[],
|
|
502
|
+
preForces: number[],
|
|
503
|
+
sg: number,
|
|
504
|
+
degrees: number[]
|
|
505
|
+
): { nodes: any, sg: number } {
|
|
506
|
+
const self = this;
|
|
507
|
+
const { nodes, ks, tao, prune, ksmax } = self;
|
|
508
|
+
const nodeNum = nodes.length;
|
|
509
|
+
const swgns = [];
|
|
510
|
+
const trans = [];
|
|
511
|
+
// swg(G) and tra(G)
|
|
512
|
+
let swgG = 0;
|
|
513
|
+
let traG = 0;
|
|
514
|
+
for (let i = 0; i < nodeNum; i += 1) {
|
|
515
|
+
|
|
516
|
+
if (prune && (degrees[i] <= 1)) continue;
|
|
517
|
+
|
|
518
|
+
const minus = [ forces[2 * i] - preForces[2 * i],
|
|
519
|
+
forces[2 * i + 1] - preForces[2 * i + 1]
|
|
520
|
+
];
|
|
521
|
+
const minusNorm = Math.hypot(minus[0], minus[1]);
|
|
522
|
+
const add = [ forces[2 * i] + preForces[2 * i],
|
|
523
|
+
forces[2 * i + 1] + preForces[2 * i + 1]
|
|
524
|
+
];
|
|
525
|
+
const addNorm = Math.hypot(add[0], add[1]);
|
|
526
|
+
|
|
527
|
+
swgns[i] = minusNorm;
|
|
528
|
+
trans[i] = addNorm / 2;
|
|
529
|
+
|
|
530
|
+
swgG += (degrees[i] + 1) * swgns[i];
|
|
531
|
+
traG += (degrees[i] + 1) * trans[i];
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
const preSG = sg;
|
|
535
|
+
sg = tao * traG / swgG // tslint:disable-line
|
|
536
|
+
if (preSG !== 0) {
|
|
537
|
+
sg = sg > (1.5 * preSG) ? (1.5 * preSG) : sg // tslint:disable-line
|
|
538
|
+
}
|
|
539
|
+
// update the node positions
|
|
540
|
+
for (let i = 0; i < nodeNum; i += 1) {
|
|
541
|
+
|
|
542
|
+
if (prune && (degrees[i] <= 1)) continue;
|
|
543
|
+
|
|
544
|
+
let sn = ks * sg / (1 + sg * Math.sqrt(swgns[i]));
|
|
545
|
+
let absForce = Math.hypot(forces[2 * i], forces[2 * i + 1]);
|
|
546
|
+
absForce = absForce < 0.0001 ? 0.0001 : absForce;
|
|
547
|
+
const max = ksmax / absForce;
|
|
548
|
+
sn = sn > max ? max : sn;
|
|
549
|
+
const dnx = sn * forces[2 * i];
|
|
550
|
+
const dny = sn * forces[2 * i + 1];
|
|
551
|
+
nodes[i].x += dnx;
|
|
552
|
+
nodes[i].y += dny;
|
|
553
|
+
}
|
|
554
|
+
return { nodes, sg };
|
|
555
|
+
}
|
|
556
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { PointTuple } from "../types";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileOverview quad
|
|
5
|
+
* @author shiwu.wyy@antfin.com
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
type QuadProps = {
|
|
9
|
+
xmid: number;
|
|
10
|
+
ymid: number;
|
|
11
|
+
length: number;
|
|
12
|
+
massCenter?: PointTuple;
|
|
13
|
+
mass?: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export default class Quad {
|
|
17
|
+
public xmid: number;
|
|
18
|
+
public ymid: number;
|
|
19
|
+
public length: number;
|
|
20
|
+
public massCenter: PointTuple;
|
|
21
|
+
public mass: number;
|
|
22
|
+
constructor(params: QuadProps) {
|
|
23
|
+
/**
|
|
24
|
+
* the center position of this quad
|
|
25
|
+
* @type {number}
|
|
26
|
+
*/
|
|
27
|
+
this.xmid = params.xmid;
|
|
28
|
+
/**
|
|
29
|
+
* the center position of this quad
|
|
30
|
+
* @type {number}
|
|
31
|
+
*/
|
|
32
|
+
this.ymid = params.ymid;
|
|
33
|
+
/**
|
|
34
|
+
* the length of this quad
|
|
35
|
+
* @type {number}
|
|
36
|
+
*/
|
|
37
|
+
this.length = params.length;
|
|
38
|
+
/**
|
|
39
|
+
* the mass center of this quad
|
|
40
|
+
* @type {number}
|
|
41
|
+
*/
|
|
42
|
+
this.massCenter = params.massCenter || [0, 0];
|
|
43
|
+
/**
|
|
44
|
+
* the mass of this quad
|
|
45
|
+
* @type {number}
|
|
46
|
+
*/
|
|
47
|
+
this.mass = params.mass || 1;
|
|
48
|
+
}
|
|
49
|
+
getLength() {
|
|
50
|
+
return this.length;
|
|
51
|
+
}
|
|
52
|
+
contains(x: number, y: number) {
|
|
53
|
+
const halfLen = this.length / 2;
|
|
54
|
+
return (x <= this.xmid + halfLen &&
|
|
55
|
+
x >= this.xmid - halfLen &&
|
|
56
|
+
y <= this.ymid + halfLen &&
|
|
57
|
+
y >= this.ymid - halfLen);
|
|
58
|
+
}
|
|
59
|
+
// northwest quadrant
|
|
60
|
+
// tslint:disable-next-line
|
|
61
|
+
NW() {
|
|
62
|
+
const x = this.xmid - this.length / 4;
|
|
63
|
+
const y = this.ymid + this.length / 4;
|
|
64
|
+
const len = this.length / 2;
|
|
65
|
+
const params: QuadProps = {
|
|
66
|
+
xmid: x,
|
|
67
|
+
ymid: y,
|
|
68
|
+
length: len
|
|
69
|
+
};
|
|
70
|
+
const NW = new Quad(params);
|
|
71
|
+
return NW;
|
|
72
|
+
}
|
|
73
|
+
// northeast
|
|
74
|
+
// tslint:disable-next-line
|
|
75
|
+
NE() {
|
|
76
|
+
const x = this.xmid + this.length / 4;
|
|
77
|
+
const y = this.ymid + this.length / 4;
|
|
78
|
+
const len = this.length / 2;
|
|
79
|
+
const params = {
|
|
80
|
+
xmid: x,
|
|
81
|
+
ymid: y,
|
|
82
|
+
length: len
|
|
83
|
+
};
|
|
84
|
+
const NE = new Quad(params);
|
|
85
|
+
return NE;
|
|
86
|
+
}
|
|
87
|
+
// southwest
|
|
88
|
+
// tslint:disable-next-line
|
|
89
|
+
SW() {
|
|
90
|
+
const x = this.xmid - this.length / 4;
|
|
91
|
+
const y = this.ymid - this.length / 4;
|
|
92
|
+
const len = this.length / 2;
|
|
93
|
+
const params = {
|
|
94
|
+
xmid: x,
|
|
95
|
+
ymid: y,
|
|
96
|
+
length: len
|
|
97
|
+
};
|
|
98
|
+
const SW = new Quad(params);
|
|
99
|
+
return SW;
|
|
100
|
+
}
|
|
101
|
+
// southeast
|
|
102
|
+
// tslint:disable-next-line
|
|
103
|
+
SE() {
|
|
104
|
+
const x = this.xmid + this.length / 4;
|
|
105
|
+
const y = this.ymid - this.length / 4;
|
|
106
|
+
const len = this.length / 2;
|
|
107
|
+
const params = {
|
|
108
|
+
xmid: x,
|
|
109
|
+
ymid: y,
|
|
110
|
+
length: len
|
|
111
|
+
};
|
|
112
|
+
const SE = new Quad(params);
|
|
113
|
+
return SE;
|
|
114
|
+
}
|
|
115
|
+
}
|