UNPKG

10 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.FruchtermanLayout = void 0;
21var base_1 = require("./base");
22var util_1 = require("../util");
23var SPEED_DIVISOR = 800;
24/**
25 * fruchterman 布局
26 */
27var FruchtermanLayout = /** @class */ (function (_super) {
28 __extends(FruchtermanLayout, _super);
29 function FruchtermanLayout(options) {
30 var _this = _super.call(this) || this;
31 /** 停止迭代的最大迭代数 */
32 _this.maxIteration = 1000;
33 /** 重力大小,影响图的紧凑程度 */
34 _this.gravity = 10;
35 /** 速度 */
36 _this.speed = 1;
37 /** 是否产生聚类力 */
38 _this.clustering = false;
39 /** 聚类力大小 */
40 _this.clusterGravity = 10;
41 _this.nodes = [];
42 _this.edges = [];
43 _this.width = 300;
44 _this.height = 300;
45 _this.nodeMap = {};
46 _this.nodeIdxMap = {};
47 _this.updateCfg(options);
48 return _this;
49 }
50 FruchtermanLayout.prototype.getDefaultCfg = function () {
51 return {
52 maxIteration: 1000,
53 gravity: 10,
54 speed: 1,
55 clustering: false,
56 clusterGravity: 10,
57 };
58 };
59 /**
60 * 执行布局
61 */
62 FruchtermanLayout.prototype.execute = function () {
63 var _this = this;
64 var self = this;
65 var nodes = self.nodes;
66 if (!nodes || nodes.length === 0) {
67 return;
68 }
69 if (!self.width && typeof window !== 'undefined') {
70 self.width = window.innerWidth;
71 }
72 if (!self.height && typeof window !== 'undefined') {
73 self.height = window.innerHeight;
74 }
75 if (!self.center) {
76 self.center = [self.width / 2, self.height / 2];
77 }
78 var center = self.center;
79 if (nodes.length === 1) {
80 nodes[0].x = center[0];
81 nodes[0].y = center[1];
82 return;
83 }
84 var nodeMap = {};
85 var nodeIdxMap = {};
86 nodes.forEach(function (node, i) {
87 if (!util_1.isNumber(node.x))
88 node.x = Math.random() * _this.width;
89 if (!util_1.isNumber(node.y))
90 node.y = Math.random() * _this.height;
91 nodeMap[node.id] = node;
92 nodeIdxMap[node.id] = i;
93 });
94 self.nodeMap = nodeMap;
95 self.nodeIdxMap = nodeIdxMap;
96 // layout
97 return self.run();
98 };
99 FruchtermanLayout.prototype.run = function () {
100 var self = this;
101 var nodes = self.nodes;
102 var edges = self.edges;
103 var maxIteration = self.maxIteration;
104 var center = self.center;
105 var area = self.height * self.width;
106 var maxDisplace = Math.sqrt(area) / 10;
107 var k2 = area / (nodes.length + 1);
108 var k = Math.sqrt(k2);
109 var gravity = self.gravity;
110 var speed = self.speed;
111 var clustering = self.clustering;
112 var clusterMap = {};
113 if (clustering) {
114 nodes.forEach(function (n) {
115 if (clusterMap[n.cluster] === undefined) {
116 var cluster = {
117 name: n.cluster,
118 cx: 0,
119 cy: 0,
120 count: 0,
121 };
122 clusterMap[n.cluster] = cluster;
123 }
124 var c = clusterMap[n.cluster];
125 if (util_1.isNumber(n.x)) {
126 c.cx += n.x;
127 }
128 if (util_1.isNumber(n.y)) {
129 c.cy += n.y;
130 }
131 c.count++;
132 });
133 for (var key in clusterMap) {
134 clusterMap[key].cx /= clusterMap[key].count;
135 clusterMap[key].cy /= clusterMap[key].count;
136 }
137 }
138 var _loop_1 = function (i) {
139 var displacements = [];
140 nodes.forEach(function (_, j) {
141 displacements[j] = { x: 0, y: 0 };
142 });
143 self.applyCalculate(nodes, edges, displacements, k, k2);
144 // gravity for clusters
145 if (clustering) {
146 var clusterGravity_1 = self.clusterGravity || gravity;
147 nodes.forEach(function (n, j) {
148 if (!util_1.isNumber(n.x) || !util_1.isNumber(n.y))
149 return;
150 var c = clusterMap[n.cluster];
151 var distLength = Math.sqrt((n.x - c.cx) * (n.x - c.cx) + (n.y - c.cy) * (n.y - c.cy));
152 var gravityForce = k * clusterGravity_1;
153 displacements[j].x -= (gravityForce * (n.x - c.cx)) / distLength;
154 displacements[j].y -= (gravityForce * (n.y - c.cy)) / distLength;
155 });
156 for (var key in clusterMap) {
157 clusterMap[key].cx = 0;
158 clusterMap[key].cy = 0;
159 clusterMap[key].count = 0;
160 }
161 nodes.forEach(function (n) {
162 var c = clusterMap[n.cluster];
163 if (util_1.isNumber(n.x)) {
164 c.cx += n.x;
165 }
166 if (util_1.isNumber(n.y)) {
167 c.cy += n.y;
168 }
169 c.count++;
170 });
171 for (var key in clusterMap) {
172 clusterMap[key].cx /= clusterMap[key].count;
173 clusterMap[key].cy /= clusterMap[key].count;
174 }
175 }
176 // gravity
177 nodes.forEach(function (n, j) {
178 if (!util_1.isNumber(n.x) || !util_1.isNumber(n.y))
179 return;
180 var gravityForce = 0.01 * k * gravity;
181 displacements[j].x -= gravityForce * (n.x - center[0]);
182 displacements[j].y -= gravityForce * (n.y - center[1]);
183 });
184 // move
185 nodes.forEach(function (n, j) {
186 if (!util_1.isNumber(n.x) || !util_1.isNumber(n.y))
187 return;
188 var distLength = Math.sqrt(displacements[j].x * displacements[j].x + displacements[j].y * displacements[j].y);
189 if (distLength > 0) {
190 // && !n.isFixed()
191 var limitedDist = Math.min(maxDisplace * (speed / SPEED_DIVISOR), distLength);
192 n.x += (displacements[j].x / distLength) * limitedDist;
193 n.y += (displacements[j].y / distLength) * limitedDist;
194 }
195 });
196 };
197 for (var i = 0; i < maxIteration; i++) {
198 _loop_1(i);
199 }
200 return {
201 nodes: nodes,
202 edges: edges,
203 };
204 };
205 FruchtermanLayout.prototype.applyCalculate = function (nodes, edges, displacements, k, k2) {
206 var self = this;
207 self.calRepulsive(nodes, displacements, k2);
208 self.calAttractive(edges, displacements, k);
209 };
210 FruchtermanLayout.prototype.calRepulsive = function (nodes, displacements, k2) {
211 nodes.forEach(function (v, i) {
212 displacements[i] = { x: 0, y: 0 };
213 nodes.forEach(function (u, j) {
214 if (i === j) {
215 return;
216 }
217 if (!util_1.isNumber(v.x) || !util_1.isNumber(u.x) || !util_1.isNumber(v.y) || !util_1.isNumber(u.y))
218 return;
219 var vecX = v.x - u.x;
220 var vecY = v.y - u.y;
221 var vecLengthSqr = vecX * vecX + vecY * vecY;
222 if (vecLengthSqr === 0) {
223 vecLengthSqr = 1;
224 var sign = i > j ? 1 : -1;
225 vecX = 0.01 * sign;
226 vecY = 0.01 * sign;
227 }
228 var common = (k2) / vecLengthSqr;
229 displacements[i].x += vecX * common;
230 displacements[i].y += vecY * common;
231 });
232 });
233 };
234 FruchtermanLayout.prototype.calAttractive = function (edges, displacements, k) {
235 var _this = this;
236 edges.forEach(function (e) {
237 if (!e.source || !e.target)
238 return;
239 var uIndex = _this.nodeIdxMap[e.source];
240 var vIndex = _this.nodeIdxMap[e.target];
241 if (uIndex === vIndex) {
242 return;
243 }
244 var u = _this.nodeMap[e.source];
245 var v = _this.nodeMap[e.target];
246 if (!util_1.isNumber(v.x) || !util_1.isNumber(u.x) || !util_1.isNumber(v.y) || !util_1.isNumber(u.y))
247 return;
248 var vecX = v.x - u.x;
249 var vecY = v.y - u.y;
250 var vecLength = Math.sqrt(vecX * vecX + vecY * vecY);
251 var common = (vecLength * vecLength) / k;
252 displacements[vIndex].x -= (vecX / vecLength) * common;
253 displacements[vIndex].y -= (vecY / vecLength) * common;
254 displacements[uIndex].x += (vecX / vecLength) * common;
255 displacements[uIndex].y += (vecY / vecLength) * common;
256 });
257 };
258 FruchtermanLayout.prototype.getType = function () {
259 return 'fruchterman';
260 };
261 return FruchtermanLayout;
262}(base_1.Base));
263exports.FruchtermanLayout = FruchtermanLayout;
264//# sourceMappingURL=fruchterman.js.map
\No newline at end of file