@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,391 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileOverview random layout
|
|
3
|
+
* @author shiwu.wyy@antfin.com
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Edge, Model, PointTuple, ForceLayoutOptions } from "../types";
|
|
7
|
+
import * as d3Force from "d3-force";
|
|
8
|
+
import forceInABox from "./force-in-a-box";
|
|
9
|
+
import { isArray, isFunction, isNumber, isObject } from "../../util";
|
|
10
|
+
import { Base } from "../base";
|
|
11
|
+
import { LAYOUT_MESSAGE } from "../constants";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 经典力导布局 force-directed
|
|
15
|
+
*/
|
|
16
|
+
export class ForceLayout extends Base {
|
|
17
|
+
/** 向心力作用点 */
|
|
18
|
+
public center: PointTuple = [0, 0];
|
|
19
|
+
|
|
20
|
+
/** 节点作用力 */
|
|
21
|
+
public nodeStrength: number | null = null;
|
|
22
|
+
|
|
23
|
+
/** 边的作用力, 默认为根据节点的入度出度自适应 */
|
|
24
|
+
public edgeStrength: number | null = null;
|
|
25
|
+
|
|
26
|
+
/** 是否防止节点相互覆盖 */
|
|
27
|
+
public preventOverlap: boolean = false;
|
|
28
|
+
|
|
29
|
+
/** 节点大小 / 直径,用于防止重叠时的碰撞检测 */
|
|
30
|
+
public nodeSize: number | number[] | ((d?: unknown) => number) | undefined;
|
|
31
|
+
|
|
32
|
+
/** 节点间距,防止节点重叠时节点之间的最小距离(两节点边缘最短距离) */
|
|
33
|
+
public nodeSpacing: ((d?: unknown) => number) | undefined;
|
|
34
|
+
|
|
35
|
+
/** 是否支持按类聚合 */
|
|
36
|
+
public clustering: boolean;
|
|
37
|
+
|
|
38
|
+
/** 聚类节点作用力 */
|
|
39
|
+
public clusterNodeStrength: number | null = null;
|
|
40
|
+
|
|
41
|
+
/** 聚类边作用力 */
|
|
42
|
+
public clusterEdgeStrength: number | null = null;
|
|
43
|
+
|
|
44
|
+
/** 聚类边长度 */
|
|
45
|
+
public clusterEdgeDistance: number | null = null;
|
|
46
|
+
|
|
47
|
+
/** 聚类节点大小 / 直径,直径越大,越分散 */
|
|
48
|
+
public clusterNodeSize: number | null = null;
|
|
49
|
+
|
|
50
|
+
/** 用于 foci 的力 */
|
|
51
|
+
public clusterFociStrength: number | null = null;
|
|
52
|
+
|
|
53
|
+
/** 默认边长度 */
|
|
54
|
+
public linkDistance: number = 50;
|
|
55
|
+
|
|
56
|
+
/** 自定义 force 方法 */
|
|
57
|
+
public forceSimulation: any;
|
|
58
|
+
|
|
59
|
+
/** 迭代阈值的衰减率 [0, 1],0.028 对应最大迭代数为 300 */
|
|
60
|
+
public alphaDecay: number = 0.028;
|
|
61
|
+
|
|
62
|
+
/** 停止迭代的阈值 */
|
|
63
|
+
public alphaMin: number = 0.001;
|
|
64
|
+
|
|
65
|
+
/** 当前阈值 */
|
|
66
|
+
public alpha: number = 0.3;
|
|
67
|
+
|
|
68
|
+
/** 防止重叠的力强度 */
|
|
69
|
+
public collideStrength: number = 1;
|
|
70
|
+
|
|
71
|
+
/** 是否启用web worker。前提是在web worker里执行布局,否则无效 */
|
|
72
|
+
public workerEnabled: boolean = false;
|
|
73
|
+
|
|
74
|
+
public tick: () => void = () => {};
|
|
75
|
+
|
|
76
|
+
/** 布局完成回调 */
|
|
77
|
+
public onLayoutEnd: () => void = () => {};
|
|
78
|
+
|
|
79
|
+
/** 是否正在布局 */
|
|
80
|
+
private ticking: boolean | undefined = undefined;
|
|
81
|
+
|
|
82
|
+
private edgeForce: any;
|
|
83
|
+
|
|
84
|
+
private clusterForce: any;
|
|
85
|
+
|
|
86
|
+
constructor(options?: ForceLayoutOptions) {
|
|
87
|
+
super();
|
|
88
|
+
if (options) {
|
|
89
|
+
this.updateCfg(options);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
public getDefaultCfg() {
|
|
94
|
+
return {
|
|
95
|
+
center: [0, 0],
|
|
96
|
+
nodeStrength: null,
|
|
97
|
+
edgeStrength: null,
|
|
98
|
+
preventOverlap: false,
|
|
99
|
+
nodeSize: undefined,
|
|
100
|
+
nodeSpacing: undefined,
|
|
101
|
+
linkDistance: 50,
|
|
102
|
+
forceSimulation: null,
|
|
103
|
+
alphaDecay: 0.028,
|
|
104
|
+
alphaMin: 0.001,
|
|
105
|
+
alpha: 0.3,
|
|
106
|
+
collideStrength: 1,
|
|
107
|
+
clustering: false,
|
|
108
|
+
clusterNodeStrength: -1,
|
|
109
|
+
clusterEdgeStrength: 0.1,
|
|
110
|
+
clusterEdgeDistance: 100,
|
|
111
|
+
clusterFociStrength: 0.8,
|
|
112
|
+
clusterNodeSize: 10,
|
|
113
|
+
tick() {},
|
|
114
|
+
onLayoutEnd() {}, // 布局完成回调
|
|
115
|
+
// 是否启用web worker。前提是在web worker里执行布局,否则无效
|
|
116
|
+
workerEnabled: false
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 初始化
|
|
122
|
+
* @param {object} data 数据
|
|
123
|
+
*/
|
|
124
|
+
public init(data: Model) {
|
|
125
|
+
const self = this;
|
|
126
|
+
self.nodes = data.nodes || [];
|
|
127
|
+
const edges = data.edges || [];
|
|
128
|
+
self.edges = edges.map((edge) => {
|
|
129
|
+
const res: any = {};
|
|
130
|
+
const expectKeys = ["targetNode", "sourceNode", "startPoint", "endPoint"];
|
|
131
|
+
Object.keys(edge).forEach((key: keyof Edge) => {
|
|
132
|
+
if (!(expectKeys.indexOf(key) > -1)) {
|
|
133
|
+
res[key] = edge[key];
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
return res;
|
|
137
|
+
});
|
|
138
|
+
self.ticking = false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 执行布局
|
|
143
|
+
*/
|
|
144
|
+
public execute(reloadData?: boolean) {
|
|
145
|
+
const self = this;
|
|
146
|
+
const nodes = self.nodes;
|
|
147
|
+
const edges = self.edges;
|
|
148
|
+
// 如果正在布局,忽略布局请求
|
|
149
|
+
if (self.ticking) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
let simulation = self.forceSimulation;
|
|
153
|
+
const alphaMin = self.alphaMin;
|
|
154
|
+
const alphaDecay = self.alphaDecay;
|
|
155
|
+
const alpha = self.alpha;
|
|
156
|
+
if (!simulation) {
|
|
157
|
+
try {
|
|
158
|
+
// 定义节点的力
|
|
159
|
+
const nodeForce = d3Force.forceManyBody();
|
|
160
|
+
if (self.nodeStrength) {
|
|
161
|
+
nodeForce.strength(self.nodeStrength);
|
|
162
|
+
}
|
|
163
|
+
simulation = d3Force.forceSimulation().nodes(nodes as any);
|
|
164
|
+
|
|
165
|
+
if (self.clustering) {
|
|
166
|
+
const clusterForce = forceInABox() as any;
|
|
167
|
+
clusterForce
|
|
168
|
+
.centerX(self.center[0])
|
|
169
|
+
.centerY(self.center[1])
|
|
170
|
+
.template("force")
|
|
171
|
+
.strength(self.clusterFociStrength);
|
|
172
|
+
if (edges) {
|
|
173
|
+
clusterForce.links(edges);
|
|
174
|
+
}
|
|
175
|
+
if (nodes) {
|
|
176
|
+
clusterForce.nodes(nodes);
|
|
177
|
+
}
|
|
178
|
+
clusterForce
|
|
179
|
+
.forceLinkDistance(self.clusterEdgeDistance)
|
|
180
|
+
.forceLinkStrength(self.clusterEdgeStrength)
|
|
181
|
+
.forceCharge(self.clusterNodeStrength)
|
|
182
|
+
.forceNodeSize(self.clusterNodeSize);
|
|
183
|
+
|
|
184
|
+
self.clusterForce = clusterForce;
|
|
185
|
+
simulation.force("group", clusterForce);
|
|
186
|
+
}
|
|
187
|
+
simulation
|
|
188
|
+
.force("center", d3Force.forceCenter(self.center[0], self.center[1]))
|
|
189
|
+
.force("charge", nodeForce)
|
|
190
|
+
.alpha(alpha)
|
|
191
|
+
.alphaDecay(alphaDecay)
|
|
192
|
+
.alphaMin(alphaMin);
|
|
193
|
+
|
|
194
|
+
if (self.preventOverlap) {
|
|
195
|
+
self.overlapProcess(simulation);
|
|
196
|
+
}
|
|
197
|
+
// 如果有边,定义边的力
|
|
198
|
+
if (edges) {
|
|
199
|
+
// d3 的 forceLayout 会重新生成边的数据模型,为了避免污染源数据
|
|
200
|
+
const edgeForce = d3Force
|
|
201
|
+
.forceLink()
|
|
202
|
+
.id((d: any) => d.id)
|
|
203
|
+
.links(edges);
|
|
204
|
+
if (self.edgeStrength) {
|
|
205
|
+
edgeForce.strength(self.edgeStrength);
|
|
206
|
+
}
|
|
207
|
+
if (self.linkDistance) {
|
|
208
|
+
edgeForce.distance(self.linkDistance);
|
|
209
|
+
}
|
|
210
|
+
self.edgeForce = edgeForce;
|
|
211
|
+
simulation.force("link", edgeForce);
|
|
212
|
+
}
|
|
213
|
+
if (self.workerEnabled && !isInWorker()) {
|
|
214
|
+
// 如果不是运行在web worker里,不用web worker布局
|
|
215
|
+
self.workerEnabled = false;
|
|
216
|
+
console.warn(
|
|
217
|
+
"workerEnabled option is only supported when running in web worker."
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
if (!self.workerEnabled) {
|
|
221
|
+
simulation
|
|
222
|
+
.on("tick", () => {
|
|
223
|
+
self.tick();
|
|
224
|
+
})
|
|
225
|
+
.on("end", () => {
|
|
226
|
+
self.ticking = false;
|
|
227
|
+
if (self.onLayoutEnd) self.onLayoutEnd();
|
|
228
|
+
});
|
|
229
|
+
self.ticking = true;
|
|
230
|
+
} else {
|
|
231
|
+
// worker is enabled
|
|
232
|
+
simulation.stop();
|
|
233
|
+
const totalTicks = getSimulationTicks(simulation);
|
|
234
|
+
for (let currentTick = 1; currentTick <= totalTicks; currentTick++) {
|
|
235
|
+
simulation.tick();
|
|
236
|
+
// currentTick starts from 1.
|
|
237
|
+
postMessage(
|
|
238
|
+
{
|
|
239
|
+
nodes,
|
|
240
|
+
currentTick,
|
|
241
|
+
totalTicks,
|
|
242
|
+
type: LAYOUT_MESSAGE.TICK
|
|
243
|
+
},
|
|
244
|
+
undefined as any
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
self.ticking = false;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
self.forceSimulation = simulation;
|
|
251
|
+
self.ticking = true;
|
|
252
|
+
} catch (e) {
|
|
253
|
+
self.ticking = false;
|
|
254
|
+
console.warn(e);
|
|
255
|
+
}
|
|
256
|
+
} else {
|
|
257
|
+
if (reloadData) {
|
|
258
|
+
if (self.clustering && self.clusterForce) {
|
|
259
|
+
self.clusterForce.nodes(nodes);
|
|
260
|
+
self.clusterForce.links(edges);
|
|
261
|
+
}
|
|
262
|
+
simulation.nodes(nodes);
|
|
263
|
+
if (edges && self.edgeForce) self.edgeForce.links(edges);
|
|
264
|
+
else if (edges && !self.edgeForce) {
|
|
265
|
+
// d3 的 forceLayout 会重新生成边的数据模型,为了避免污染源数据
|
|
266
|
+
const edgeForce = d3Force
|
|
267
|
+
.forceLink()
|
|
268
|
+
.id((d: any) => d.id)
|
|
269
|
+
.links(edges);
|
|
270
|
+
if (self.edgeStrength) {
|
|
271
|
+
edgeForce.strength(self.edgeStrength);
|
|
272
|
+
}
|
|
273
|
+
if (self.linkDistance) {
|
|
274
|
+
edgeForce.distance(self.linkDistance);
|
|
275
|
+
}
|
|
276
|
+
self.edgeForce = edgeForce;
|
|
277
|
+
simulation.force("link", edgeForce);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
if (self.preventOverlap) {
|
|
281
|
+
self.overlapProcess(simulation);
|
|
282
|
+
}
|
|
283
|
+
simulation.alpha(alpha).restart();
|
|
284
|
+
this.ticking = true;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* 防止重叠
|
|
290
|
+
* @param {object} simulation 力模拟模型
|
|
291
|
+
*/
|
|
292
|
+
public overlapProcess(simulation: any) {
|
|
293
|
+
const self = this;
|
|
294
|
+
const nodeSize = self.nodeSize;
|
|
295
|
+
const nodeSpacing = self.nodeSpacing;
|
|
296
|
+
let nodeSizeFunc: (d: any) => number;
|
|
297
|
+
let nodeSpacingFunc: any;
|
|
298
|
+
const collideStrength = self.collideStrength;
|
|
299
|
+
|
|
300
|
+
if (isNumber(nodeSpacing)) {
|
|
301
|
+
nodeSpacingFunc = () => nodeSpacing;
|
|
302
|
+
} else if (isFunction(nodeSpacing)) {
|
|
303
|
+
nodeSpacingFunc = nodeSpacing;
|
|
304
|
+
} else {
|
|
305
|
+
nodeSpacingFunc = () => 0;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (!nodeSize) {
|
|
309
|
+
nodeSizeFunc = (d) => {
|
|
310
|
+
if (d.size) {
|
|
311
|
+
if (isArray(d.size)) {
|
|
312
|
+
const res = d.size[0] > d.size[1] ? d.size[0] : d.size[1];
|
|
313
|
+
return res / 2 + nodeSpacingFunc(d);
|
|
314
|
+
} if (isObject(d.size)) {
|
|
315
|
+
const res = d.size.width > d.size.height ? d.size.width : d.size.height;
|
|
316
|
+
return res / 2 + nodeSpacingFunc(d);
|
|
317
|
+
}
|
|
318
|
+
return d.size / 2 + nodeSpacingFunc(d);
|
|
319
|
+
}
|
|
320
|
+
return 10 + nodeSpacingFunc(d);
|
|
321
|
+
};
|
|
322
|
+
} else if (isFunction(nodeSize)) {
|
|
323
|
+
nodeSizeFunc = (d) => {
|
|
324
|
+
const size = nodeSize(d);
|
|
325
|
+
return size + nodeSpacingFunc(d);
|
|
326
|
+
};
|
|
327
|
+
} else if (isArray(nodeSize)) {
|
|
328
|
+
const larger = nodeSize[0] > nodeSize[1] ? nodeSize[0] : nodeSize[1];
|
|
329
|
+
const radius = larger / 2;
|
|
330
|
+
nodeSizeFunc = (d) => radius + nodeSpacingFunc(d);
|
|
331
|
+
} else if (isNumber(nodeSize)) {
|
|
332
|
+
const radius = nodeSize / 2;
|
|
333
|
+
nodeSizeFunc = (d) => radius + nodeSpacingFunc(d);
|
|
334
|
+
} else {
|
|
335
|
+
nodeSizeFunc = () => 10;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// forceCollide's parameter is a radius
|
|
339
|
+
simulation.force(
|
|
340
|
+
"collisionForce",
|
|
341
|
+
d3Force.forceCollide(nodeSizeFunc).strength(collideStrength)
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* 更新布局配置,但不执行布局
|
|
347
|
+
* @param {object} cfg 需要更新的配置项
|
|
348
|
+
*/
|
|
349
|
+
public updateCfg(cfg: ForceLayoutOptions) {
|
|
350
|
+
const self = this;
|
|
351
|
+
if (self.ticking) {
|
|
352
|
+
self.forceSimulation.stop();
|
|
353
|
+
self.ticking = false;
|
|
354
|
+
}
|
|
355
|
+
self.forceSimulation = null;
|
|
356
|
+
Object.assign(self, cfg);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
public destroy() {
|
|
360
|
+
const self = this;
|
|
361
|
+
if (self.ticking) {
|
|
362
|
+
self.forceSimulation.stop();
|
|
363
|
+
self.ticking = false;
|
|
364
|
+
}
|
|
365
|
+
self.nodes = null;
|
|
366
|
+
self.edges = null;
|
|
367
|
+
self.destroyed = true;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Return total ticks of d3-force simulation
|
|
372
|
+
function getSimulationTicks(simulation: any): number {
|
|
373
|
+
const alphaMin = simulation.alphaMin();
|
|
374
|
+
const alphaTarget = simulation.alphaTarget();
|
|
375
|
+
const alpha = simulation.alpha();
|
|
376
|
+
const totalTicksFloat =
|
|
377
|
+
Math.log((alphaMin - alphaTarget) / (alpha - alphaTarget)) /
|
|
378
|
+
Math.log(1 - simulation.alphaDecay());
|
|
379
|
+
const totalTicks = Math.ceil(totalTicksFloat);
|
|
380
|
+
return totalTicks;
|
|
381
|
+
}
|
|
382
|
+
declare const WorkerGlobalScope: any;
|
|
383
|
+
|
|
384
|
+
// 判断是否运行在web worker里
|
|
385
|
+
function isInWorker(): boolean {
|
|
386
|
+
// eslint-disable-next-line no-undef
|
|
387
|
+
return (
|
|
388
|
+
typeof WorkerGlobalScope !== "undefined" &&
|
|
389
|
+
self instanceof WorkerGlobalScope
|
|
390
|
+
);
|
|
391
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './force';
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import Quad from './quad';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileOverview body
|
|
5
|
+
* @author shiwu.wyy@antfin.com
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
type BodyProps = {
|
|
9
|
+
id?: Number;
|
|
10
|
+
rx: number;
|
|
11
|
+
ry: number;
|
|
12
|
+
fx?: number;
|
|
13
|
+
fy?: number;
|
|
14
|
+
mass: number;
|
|
15
|
+
degree: number;
|
|
16
|
+
g?: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// represents a body(a point mass) and its position
|
|
20
|
+
export default class Body {
|
|
21
|
+
public id: Number;
|
|
22
|
+
public rx: number;
|
|
23
|
+
public ry: number;
|
|
24
|
+
public fx: number;
|
|
25
|
+
public fy: number;
|
|
26
|
+
public mass: number;
|
|
27
|
+
public degree: number;
|
|
28
|
+
public g: number;
|
|
29
|
+
|
|
30
|
+
constructor(params: BodyProps) {
|
|
31
|
+
/**
|
|
32
|
+
* the id of this body, the same with the node id
|
|
33
|
+
* @type {number}
|
|
34
|
+
*/
|
|
35
|
+
this.id = params.id || 0;
|
|
36
|
+
/**
|
|
37
|
+
* the position of this body
|
|
38
|
+
* @type {number}
|
|
39
|
+
*/
|
|
40
|
+
this.rx = params.rx;
|
|
41
|
+
/**
|
|
42
|
+
* the position of this body
|
|
43
|
+
* @type {number}
|
|
44
|
+
*/
|
|
45
|
+
this.ry = params.ry;
|
|
46
|
+
/**
|
|
47
|
+
* the force acting on this body
|
|
48
|
+
* @type {number}
|
|
49
|
+
*/
|
|
50
|
+
this.fx = 0;
|
|
51
|
+
/**
|
|
52
|
+
* the force acting on this body
|
|
53
|
+
* @type {number}
|
|
54
|
+
*/
|
|
55
|
+
this.fy = 0;
|
|
56
|
+
/**
|
|
57
|
+
* the mass of this body, =1 for a node
|
|
58
|
+
* @type {number}
|
|
59
|
+
*/
|
|
60
|
+
this.mass = params.mass;
|
|
61
|
+
/**
|
|
62
|
+
* the degree of the node represented by this body
|
|
63
|
+
* @type {number}
|
|
64
|
+
*/
|
|
65
|
+
this.degree = params.degree;
|
|
66
|
+
/**
|
|
67
|
+
* the parameter for repulsive force, = kr
|
|
68
|
+
* @type {number}
|
|
69
|
+
*/
|
|
70
|
+
this.g = params.g || 0;
|
|
71
|
+
}
|
|
72
|
+
// returns the euclidean distance
|
|
73
|
+
distanceTo(bo: Body) {
|
|
74
|
+
const dx = this.rx - bo.rx;
|
|
75
|
+
const dy = this.ry - bo.ry;
|
|
76
|
+
return Math.hypot(dx, dy);
|
|
77
|
+
}
|
|
78
|
+
setPos(x: number, y: number) {
|
|
79
|
+
this.rx = x;
|
|
80
|
+
this.ry = y;
|
|
81
|
+
}
|
|
82
|
+
// resets the forces
|
|
83
|
+
resetForce() {
|
|
84
|
+
this.fx = 0;
|
|
85
|
+
this.fy = 0;
|
|
86
|
+
}
|
|
87
|
+
addForce(b: Body) {
|
|
88
|
+
const dx = b.rx - this.rx;
|
|
89
|
+
const dy = b.ry - this.ry;
|
|
90
|
+
let dist = Math.hypot(dx, dy);
|
|
91
|
+
dist = dist < 0.0001 ? 0.0001 : dist;
|
|
92
|
+
// the repulsive defined by force atlas 2
|
|
93
|
+
const F = (this.g * (this.degree + 1) * (b.degree + 1)) / dist;
|
|
94
|
+
this.fx += F * dx / dist;
|
|
95
|
+
this.fy += F * dy / dist;
|
|
96
|
+
}
|
|
97
|
+
// if quad contains this body
|
|
98
|
+
in(quad: Quad) {
|
|
99
|
+
return quad.contains(this.rx, this.ry);
|
|
100
|
+
}
|
|
101
|
+
// returns a new body
|
|
102
|
+
add(bo: Body) {
|
|
103
|
+
const nenwMass = this.mass + bo.mass;
|
|
104
|
+
const x = (this.rx * this.mass + bo.rx * bo.mass) / nenwMass;
|
|
105
|
+
const y = (this.ry * this.mass + bo.ry * bo.mass) / nenwMass;
|
|
106
|
+
const dg = this.degree + bo.degree;
|
|
107
|
+
const params: BodyProps = {
|
|
108
|
+
rx: x,
|
|
109
|
+
ry: y,
|
|
110
|
+
mass: nenwMass,
|
|
111
|
+
degree: dg
|
|
112
|
+
};
|
|
113
|
+
return new Body(params);
|
|
114
|
+
}
|
|
115
|
+
}
|