UNPKG

@antv/layout

Version:
128 lines (126 loc) 5.02 kB
const SPEED_DIVISOR = 800; const DEFAULTS_LAYOUT_OPTIONS$1 = { maxIteration: 10, width: 10, speed: 100, gravity: 10, k: 5, }; const radialNonoverlapForce = (model, options) => { const mergedOptions = Object.assign(Object.assign({}, DEFAULTS_LAYOUT_OPTIONS$1), options); const { maxIteration, width, k, speed = DEFAULTS_LAYOUT_OPTIONS$1.speed, strictRadial, focusNode, radiiMap, nodeSizeFunc, } = mergedOptions; const earlyStopThreshold = k * 0.002; const displacements = new Map(); const maxDisplacement = width / 10; for (let i = 0; i < maxIteration; i++) { model.forEachNode((node) => { displacements.set(node.id, { x: 0, y: 0 }); }); // 给重叠的节点增加斥力 getRepulsion(model, displacements, k, radiiMap, nodeSizeFunc); const avgDisplacement = updatePositions(model, displacements, speed, strictRadial, focusNode, maxDisplacement, radiiMap); if (avgDisplacement < earlyStopThreshold) { break; } } }; const getRepulsion = (model, displacements, k, radiiMap, nodeSizeFunc) => { let i = 0; model.forEachNode((nodeU) => { let j = 0; model.forEachNode((nodeV) => { if (j <= i) { j++; return; } if (nodeU.id === nodeV.id) return; // nodeU and nodeV are not on the same circle, return if (radiiMap.get(nodeU.id) !== radiiMap.get(nodeV.id)) return; let vecx = nodeU.x - nodeV.x; let vecy = nodeU.y - nodeV.y; let vecLength = Math.sqrt(vecx * vecx + vecy * vecy); // If the two nodes are exactly at the same position, we set a small distance between them if (vecLength === 0) { vecLength = 1; const sign = i > j ? 1 : -1; vecx = 0.01 * sign; vecy = 0.01 * sign; } const nodeSizeU = Math.max(...nodeSizeFunc(nodeU._original)); const nodeSizeV = Math.max(...nodeSizeFunc(nodeV._original)); // these two nodes overlap if (vecLength < nodeSizeV / 2 + nodeSizeU / 2) { const common = (k * k) / vecLength; const dispU = displacements.get(nodeU.id); const dispV = displacements.get(nodeV.id); const deltaX = (vecx / vecLength) * common; const deltaY = (vecy / vecLength) * common; displacements.set(nodeU.id, { x: dispU.x + deltaX, y: dispU.y + deltaY, }); displacements.set(nodeV.id, { x: dispV.x - deltaX, y: dispV.y - deltaY, }); } j++; }); i++; }); }; const updatePositions = (model, displacements, speed, strictRadial, focusNode, maxDisplacement, radiiMap) => { if (strictRadial) { model.forEachNode((node) => { const vx = node.x - focusNode.x; const vy = node.y - focusNode.y; const vLength = Math.sqrt(vx * vx + vy * vy); let vpx = vy / vLength; let vpy = -vx / vLength; const disp = displacements.get(node.id); const diLength = Math.sqrt(disp.x * disp.x + disp.y * disp.y); let alpha = Math.acos((vpx * disp.x + vpy * disp.y) / diLength); if (alpha > Math.PI / 2) { alpha -= Math.PI / 2; vpx *= -1; vpy *= -1; } const tdispLength = Math.cos(alpha) * diLength; displacements.set(node.id, { x: vpx * tdispLength, y: vpy * tdispLength, }); }); } // move let totalDisplacement = 0; let nodeCount = 0; model.forEachNode((node) => { if (node.id === focusNode.id) { return; } const disp = displacements.get(node.id); const distLength = Math.sqrt(disp.x * disp.x + disp.y * disp.y); if (distLength > 0) { const limitedDist = Math.min(maxDisplacement * (speed / SPEED_DIVISOR), distLength); node.x += (disp.x / distLength) * limitedDist; node.y += (disp.y / distLength) * limitedDist; if (strictRadial) { let vx = node.x - focusNode.x; let vy = node.y - focusNode.y; const nfDis = Math.sqrt(vx * vx + vy * vy); vx = (vx / nfDis) * radiiMap.get(node.id); vy = (vy / nfDis) * radiiMap.get(node.id); node.x = focusNode.x + vx; node.y = focusNode.y + vy; } totalDisplacement += limitedDist; nodeCount++; } }); return nodeCount > 0 ? totalDisplacement / nodeCount : 0; }; export { radialNonoverlapForce }; //# sourceMappingURL=radial-nonoverlap-force.js.map