UNPKG

14.3 kBJavaScriptView Raw
1"use strict";
2/**
3 * @fileOverview fruchterman layout
4 * @author shiwu.wyy@antfin.com
5 */
6var __extends = (this && this.__extends) || (function () {
7 var extendStatics = function (d, b) {
8 extendStatics = Object.setPrototypeOf ||
9 ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
10 function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
11 return extendStatics(d, b);
12 };
13 return function (d, b) {
14 extendStatics(d, b);
15 function __() { this.constructor = d; }
16 d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
17 };
18})();
19Object.defineProperty(exports, "__esModule", { value: true });
20exports.GForceLayout = void 0;
21var base_1 = require("./base");
22var util_1 = require("../util");
23var proccessToFunc = function (value, defaultV) {
24 var func;
25 if (!value) {
26 func = function (d) {
27 return defaultV || 1;
28 };
29 }
30 else if (util_1.isNumber(value)) {
31 func = function (d) {
32 return value;
33 };
34 }
35 else {
36 func = value;
37 }
38 return func;
39};
40/**
41 * graphin 中的 force 布局
42 */
43var GForceLayout = /** @class */ (function (_super) {
44 __extends(GForceLayout, _super);
45 function GForceLayout(options) {
46 var _this = _super.call(this) || this;
47 /** 停止迭代的最大迭代数 */
48 _this.maxIteration = 1000;
49 /** 弹簧引力系数 */
50 _this.edgeStrength = 200;
51 /** 斥力系数 */
52 _this.nodeStrength = 1000;
53 /** 库伦系数 */
54 _this.coulombDisScale = 0.005;
55 /** 阻尼系数 */
56 _this.damping = 0.9;
57 /** 最大速度 */
58 _this.maxSpeed = 1000;
59 /** 一次迭代的平均移动距离小于该值时停止迭代 */
60 _this.minMovement = 0.5;
61 /** 迭代中衰减 */
62 _this.interval = 0.02;
63 /** 斥力的一个系数 */
64 _this.factor = 1;
65 /** 理想边长 */
66 _this.linkDistance = 1;
67 /** 重力大小 */
68 _this.gravity = 10;
69 /** 是否防止重叠 */
70 _this.preventOverlap = true;
71 /** 每次迭代结束的回调函数 */
72 _this.tick = function () { };
73 _this.nodes = [];
74 _this.edges = [];
75 _this.width = 300;
76 _this.height = 300;
77 _this.nodeMap = {};
78 _this.nodeIdxMap = {};
79 _this.updateCfg(options);
80 return _this;
81 }
82 GForceLayout.prototype.getDefaultCfg = function () {
83 return {
84 maxIteration: 500,
85 gravity: 10,
86 enableTick: true,
87 };
88 };
89 /**
90 * 执行布局
91 */
92 GForceLayout.prototype.execute = function () {
93 var self = this;
94 var nodes = self.nodes;
95 if (self.timeInterval !== undefined && typeof window !== 'undefined') {
96 window.clearInterval(self.timeInterval);
97 }
98 if (!nodes || nodes.length === 0) {
99 return;
100 }
101 if (!self.width && typeof window !== 'undefined') {
102 self.width = window.innerWidth;
103 }
104 if (!self.height && typeof window !== 'undefined') {
105 self.height = window.innerHeight;
106 }
107 if (!self.center) {
108 self.center = [self.width / 2, self.height / 2];
109 }
110 var center = self.center;
111 if (nodes.length === 1) {
112 nodes[0].x = center[0];
113 nodes[0].y = center[1];
114 return;
115 }
116 var nodeMap = {};
117 var nodeIdxMap = {};
118 nodes.forEach(function (node, i) {
119 if (!util_1.isNumber(node.x))
120 node.x = Math.random() * self.width;
121 if (!util_1.isNumber(node.y))
122 node.y = Math.random() * self.height;
123 nodeMap[node.id] = node;
124 nodeIdxMap[node.id] = i;
125 });
126 self.nodeMap = nodeMap;
127 self.nodeIdxMap = nodeIdxMap;
128 self.linkDistance = proccessToFunc(self.linkDistance, 1);
129 self.nodeStrength = proccessToFunc(self.nodeStrength, 1);
130 self.edgeStrength = proccessToFunc(self.edgeStrength, 1);
131 // node size function
132 var nodeSize = self.nodeSize;
133 var nodeSizeFunc;
134 if (self.preventOverlap) {
135 var nodeSpacing_1 = self.nodeSpacing;
136 var nodeSpacingFunc_1;
137 if (util_1.isNumber(nodeSpacing_1)) {
138 nodeSpacingFunc_1 = function () { return nodeSpacing_1; };
139 }
140 else if (util_1.isFunction(nodeSpacing_1)) {
141 nodeSpacingFunc_1 = nodeSpacing_1;
142 }
143 else {
144 nodeSpacingFunc_1 = function () { return 0; };
145 }
146 if (!nodeSize) {
147 nodeSizeFunc = function (d) {
148 if (d.size) {
149 if (util_1.isArray(d.size)) {
150 var res = d.size[0] > d.size[1] ? d.size[0] : d.size[1];
151 return res + nodeSpacingFunc_1(d);
152 }
153 return d.size + nodeSpacingFunc_1(d);
154 }
155 return 10 + nodeSpacingFunc_1(d);
156 };
157 }
158 else if (util_1.isArray(nodeSize)) {
159 nodeSizeFunc = function (d) {
160 var res = nodeSize[0] > nodeSize[1] ? nodeSize[0] : nodeSize[1];
161 return res + nodeSpacingFunc_1(d);
162 };
163 }
164 else {
165 nodeSizeFunc = function (d) { return nodeSize + nodeSpacingFunc_1(d); };
166 }
167 }
168 self.nodeSize = nodeSizeFunc;
169 var edges = self.edges;
170 self.degrees = util_1.getDegree(nodes.length, self.nodeIdxMap, edges);
171 if (!self.getMass) {
172 self.getMass = function (d) {
173 return self.degrees[self.nodeIdxMap[d.id]];
174 };
175 }
176 // layout
177 self.run();
178 };
179 GForceLayout.prototype.run = function () {
180 var self = this;
181 var nodes = self.nodes;
182 var edges = self.edges;
183 var maxIteration = self.maxIteration;
184 if (typeof window === 'undefined')
185 return;
186 var iter = 0;
187 // interval for render the result after each iteration
188 this.timeInterval = window.setInterval(function () {
189 var accArray = [];
190 var velArray = [];
191 if (!nodes)
192 return;
193 nodes.forEach(function (_, i) {
194 accArray[2 * i] = 0;
195 accArray[2 * i + 1] = 0;
196 velArray[2 * i] = 0;
197 velArray[2 * i + 1] = 0;
198 });
199 self.calRepulsive(accArray, nodes);
200 if (edges)
201 self.calAttractive(accArray, edges);
202 self.calGravity(accArray, nodes);
203 var stepInterval = Math.max(0.02, self.interval - iter * 0.002);
204 self.updateVelocity(accArray, velArray, stepInterval, nodes);
205 var previousPos = [];
206 nodes.forEach(function (node) {
207 previousPos.push({
208 x: node.x,
209 y: node.y,
210 });
211 });
212 self.updatePosition(velArray, stepInterval, nodes);
213 if (self.tick)
214 self.tick();
215 // whether to stop the iteration
216 var movement = 0;
217 nodes.forEach(function (node, j) {
218 var vx = node.x - previousPos[j].x;
219 var vy = node.y - previousPos[j].y;
220 movement += Math.sqrt(vx * vx + vy * vy);
221 });
222 movement /= nodes.length;
223 if (movement < self.minMovement) {
224 window.clearInterval(self.timeInterval);
225 if (self.onLayoutEnd)
226 self.onLayoutEnd();
227 }
228 iter++;
229 if (iter > maxIteration) {
230 window.clearInterval(self.timeInterval);
231 if (self.onLayoutEnd)
232 self.onLayoutEnd();
233 }
234 }, 0);
235 };
236 GForceLayout.prototype.calRepulsive = function (accArray, nodes) {
237 var self = this;
238 // const nodes = self.nodes;
239 var getMass = self.getMass;
240 var nodeStrength = self.nodeStrength;
241 var factor = self.factor;
242 var coulombDisScale = self.coulombDisScale;
243 var preventOverlap = self.preventOverlap;
244 var nodeSize = self.nodeSize;
245 nodes.forEach(function (ni, i) {
246 var massi = getMass ? getMass(ni) : 1;
247 nodes.forEach(function (nj, j) {
248 if (i >= j)
249 return;
250 // if (!accArray[j]) accArray[j] = 0;
251 var vecX = ni.x - nj.x;
252 var vecY = ni.y - nj.y;
253 var vecLength = Math.sqrt(vecX * vecX + vecY * vecY) + 0.01;
254 var nVecLength = (vecLength + 0.1) * coulombDisScale;
255 var direX = vecX / vecLength;
256 var direY = vecY / vecLength;
257 var param = (((nodeStrength(ni) + nodeStrength(nj)) / 2) * factor) /
258 (nVecLength * nVecLength);
259 var massj = getMass ? getMass(nj) : 1;
260 accArray[2 * i] += (direX * param) / massi;
261 accArray[2 * i + 1] += (direY * param) / massi;
262 accArray[2 * j] -= (direX * param) / massj;
263 accArray[2 * j + 1] -= (direY * param) / massj;
264 if (preventOverlap && vecLength < (nodeSize(ni) + nodeSize(nj)) / 2) {
265 var paramOverlap = (nodeStrength(ni) + nodeStrength(nj)) / 2 / (vecLength * vecLength);
266 accArray[2 * i] += (direX * paramOverlap) / massi;
267 accArray[2 * i + 1] += (direY * paramOverlap) / massi;
268 accArray[2 * j] -= (direX * paramOverlap) / massj;
269 accArray[2 * j + 1] -= (direY * paramOverlap) / massj;
270 }
271 });
272 });
273 };
274 GForceLayout.prototype.calAttractive = function (accArray, edges) {
275 var self = this;
276 // const edges = self.edges;
277 var nodeMap = self.nodeMap;
278 var nodeIdxMap = self.nodeIdxMap;
279 var linkDistance = self.linkDistance;
280 var edgeStrength = self.edgeStrength;
281 var getMass = self.getMass;
282 edges.forEach(function (edge, i) {
283 var sourceNode = nodeMap[edge.source];
284 var targetNode = nodeMap[edge.target];
285 var vecX = targetNode.x - sourceNode.x;
286 var vecY = targetNode.y - sourceNode.y;
287 var vecLength = Math.sqrt(vecX * vecX + vecY * vecY) + 0.01;
288 var direX = vecX / vecLength;
289 var direY = vecY / vecLength;
290 var length = linkDistance(edge) || 1;
291 var diff = length - vecLength;
292 var param = diff * edgeStrength(edge);
293 var sourceIdx = nodeIdxMap[edge.source];
294 var targetIdx = nodeIdxMap[edge.target];
295 var massSource = getMass ? getMass(sourceNode) : 1;
296 var massTarget = getMass ? getMass(targetNode) : 1;
297 accArray[2 * sourceIdx] -= (direX * param) / massSource;
298 accArray[2 * sourceIdx + 1] -= (direY * param) / massSource;
299 accArray[2 * targetIdx] += (direX * param) / massTarget;
300 accArray[2 * targetIdx + 1] += (direY * param) / massTarget;
301 });
302 };
303 GForceLayout.prototype.calGravity = function (accArray, nodes) {
304 var self = this;
305 // const nodes = self.nodes;
306 var center = self.center;
307 var defaultGravity = self.gravity;
308 var degrees = self.degrees;
309 var nodeLength = nodes.length;
310 for (var i = 0; i < nodeLength; i++) {
311 var node = nodes[i];
312 var vecX = node.x - center[0];
313 var vecY = node.y - center[1];
314 var gravity = defaultGravity;
315 if (self.getCenter) {
316 var customCenterOpt = self.getCenter(node, degrees[i]);
317 if (customCenterOpt &&
318 util_1.isNumber(customCenterOpt[0]) &&
319 util_1.isNumber(customCenterOpt[1]) &&
320 util_1.isNumber(customCenterOpt[2])) {
321 vecX = node.x - customCenterOpt[0];
322 vecY = node.y - customCenterOpt[1];
323 gravity = customCenterOpt[2];
324 }
325 }
326 if (!gravity)
327 continue;
328 accArray[2 * i] -= gravity * vecX;
329 accArray[2 * i + 1] -= gravity * vecY;
330 }
331 };
332 GForceLayout.prototype.updateVelocity = function (accArray, velArray, stepInterval, nodes) {
333 var self = this;
334 var param = stepInterval * self.damping;
335 // const nodes = self.nodes;
336 nodes.forEach(function (node, i) {
337 var vx = accArray[2 * i] * param || 0.01;
338 var vy = accArray[2 * i + 1] * param || 0.01;
339 var vLength = Math.sqrt(vx * vx + vy * vy);
340 if (vLength > self.maxSpeed) {
341 var param2 = self.maxSpeed / vLength;
342 vx = param2 * vx;
343 vy = param2 * vy;
344 }
345 velArray[2 * i] = vx;
346 velArray[2 * i + 1] = vy;
347 });
348 };
349 GForceLayout.prototype.updatePosition = function (velArray, stepInterval, nodes) {
350 nodes.forEach(function (node, i) {
351 var distX = velArray[2 * i] * stepInterval;
352 var distY = velArray[2 * i + 1] * stepInterval;
353 node.x += distX;
354 node.y += distY;
355 });
356 };
357 GForceLayout.prototype.stop = function () {
358 if (this.timeInterval && typeof window !== 'undefined') {
359 window.clearInterval(this.timeInterval);
360 }
361 };
362 GForceLayout.prototype.destroy = function () {
363 var self = this;
364 self.stop();
365 self.tick = null;
366 self.nodes = null;
367 self.edges = null;
368 self.destroyed = true;
369 };
370 GForceLayout.prototype.getType = function () {
371 return 'gForce';
372 };
373 return GForceLayout;
374}(base_1.Base));
375exports.GForceLayout = GForceLayout;
376//# sourceMappingURL=gForce.js.map
\No newline at end of file