@antv/g6
Version:
graph visualization frame work
173 lines (167 loc) • 4.69 kB
JavaScript
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;