UNPKG

@antv/g6

Version:

graph visualization frame work

173 lines (167 loc) 4.69 kB
const SPEED_DIVISOR = 800; class RadialNonoverlapForce { constructor(params) { /** * node positions * @type {array} */ this.positions = params.positions; /** * adjacency matrix * @type {array} */ this.adjMatrix = params.adjMatrix; /** * focus node * @type {array} */ this.focusID = params.focusID; /** * radii * @type {number} */ this.radii = params.radii; /** * the number of iterations * @type {number} */ this.iterations = params.iterations || 10; /** * the height of the canvas * @type {number} */ this.height = params.height || 10; /** * the width of the canvas * @type {number} */ this.width = params.width || 10; /** * the moving speed * @type {number} */ this.speed = params.speed || 100; /** * the gravity * @type {number} */ this.gravity = params.gravity || 10; /** * the node size * @type {number} */ this.nodeSizeFunc = params.nodeSizeFunc; /** * the strength of forces * @type {number} */ this.k = params.k || 5; /** * if each circle can be separated into subcircles to avoid overlappings * @type {number} */ this.strictRadial = params.strictRadial; /** * the nodes data * @type {array} */ this.nodes = params.nodes; } layout() { const self = this; const positions = self.positions; const disp = []; const iterations = self.iterations; const maxDisplace = self.width / 10; self.maxDisplace = maxDisplace; self.disp = disp; for (let i = 0; i < iterations; i++) { positions.forEach((p, k) => { disp[k] = { x: 0, y: 0 }; }); // 给重叠的节点增加斥力 self.getRepulsion(); self.updatePositions(); } return positions; } getRepulsion() { const self = this; const positions = self.positions; const nodes = self.nodes; const disp = self.disp; const k = self.k; const radii = self.radii; positions.forEach((v, i) => { disp[i] = { x: 0, y: 0 }; positions.forEach((u, j) => { if (i === j) return; // v and u are not on the same circle, return if (radii[i] !== radii[j]) return; const vecx = v[0] - u[0]; const vecy = v[1] - u[1]; let vecLength = Math.sqrt(vecx * vecx + vecy * vecy); if (vecLength === 0) vecLength = 1; // these two nodes overlap if (vecLength < (self.nodeSizeFunc(nodes[i]) / 2 + self.nodeSizeFunc(nodes[j]) / 2)) { const common = k * k / (vecLength); disp[i].x += vecx / vecLength * common; disp[i].y += vecy / vecLength * common; } }); }); } updatePositions() { const self = this; const positions = self.positions; const disp = self.disp; const speed = self.speed; const strictRadial = self.strictRadial; const f = self.focusID; if (strictRadial) { disp.forEach((di, i) => { const vx = positions[i][0] - positions[f][0]; const vy = positions[i][1] - positions[f][1]; const vLength = Math.sqrt(vx * vx + vy * vy); let vpx = vy / vLength; let vpy = -vx / vLength; const diLength = Math.sqrt(di.x * di.x + di.y * di.y); let alpha = Math.acos((vpx * di.x + vpy * di.y) / diLength); if (alpha > Math.PI / 2) { alpha -= Math.PI / 2; vpx *= -1; vpy *= -1; } const tdispLength = Math.cos(alpha) * diLength; di.x = vpx * tdispLength; di.y = vpy * tdispLength; }); } // speed positions.forEach((n, i) => { disp[i].dx *= speed / SPEED_DIVISOR; disp[i].dy *= speed / SPEED_DIVISOR; }); // move const radii = self.radii; positions.forEach((n, i) => { if (i === f) return; const distLength = Math.sqrt(disp[i].x * disp[i].x + disp[i].y * disp[i].y); if (distLength > 0 && i !== f) { const limitedDist = Math.min(self.maxDisplace * (speed / SPEED_DIVISOR), distLength); n[0] += disp[i].x / distLength * limitedDist; n[1] += disp[i].y / distLength * limitedDist; if (strictRadial) { let vx = n[0] - positions[f][0]; let vy = n[1] - positions[f][1]; const nfDis = Math.sqrt(vx * vx + vy * vy); vx = vx / nfDis * radii[i]; vy = vy / nfDis * radii[i]; n[0] = positions[f][0] + vx; n[1] = positions[f][1] + vy; } } }); } } module.exports = RadialNonoverlapForce;