* Replicate the core spacing logic: compute push vectors for overlapping * community centroids and apply them to member nodes. * Returns the nodes with updated positions.
( nodes: SpacingNode[], radiusScale: number, gap: number, maxIterations: number, )
| 47 | * Returns the nodes with updated positions. |
| 48 | */ |
| 49 | function runSpacing( |
| 50 | nodes: SpacingNode[], |
| 51 | radiusScale: number, |
| 52 | gap: number, |
| 53 | maxIterations: number, |
| 54 | ): { nodes: SpacingNode[]; iterations: number; maxOverlap: number } { |
| 55 | if (nodes.length === 0) return { nodes, iterations: 0, maxOverlap: 0 }; |
| 56 | |
| 57 | // Group by community |
| 58 | const communityMap = new Map<number, number[]>(); |
| 59 | for (let i = 0; i < nodes.length; i++) { |
| 60 | const cid = nodes[i].communityId; |
| 61 | let list = communityMap.get(cid); |
| 62 | if (!list) { |
| 63 | list = []; |
| 64 | communityMap.set(cid, list); |
| 65 | } |
| 66 | list.push(i); |
| 67 | } |
| 68 | |
| 69 | // Build communities |
| 70 | const comms: Community[] = []; |
| 71 | for (const [id, indices] of communityMap) { |
| 72 | let cx = 0, |
| 73 | cy = 0; |
| 74 | for (const i of indices) { |
| 75 | cx += nodes[i].x; |
| 76 | cy += nodes[i].y; |
| 77 | } |
| 78 | cx /= indices.length; |
| 79 | cy /= indices.length; |
| 80 | comms.push({ |
| 81 | id, |
| 82 | nodeIndices: indices, |
| 83 | cx, |
| 84 | cy, |
| 85 | radius: Math.sqrt(indices.length) * radiusScale, |
| 86 | }); |
| 87 | } |
| 88 | |
| 89 | if (comms.length < 2) return { nodes, iterations: 0, maxOverlap: 0 }; |
| 90 | |
| 91 | let lastMaxOverlap = 0; |
| 92 | for (let iter = 0; iter < maxIterations; iter++) { |
| 93 | const pushX = new Float64Array(comms.length); |
| 94 | const pushY = new Float64Array(comms.length); |
| 95 | let maxOverlap = 0; |
| 96 | |
| 97 | for (let i = 0; i < comms.length; i++) { |
| 98 | for (let j = i + 1; j < comms.length; j++) { |
| 99 | const a = comms[i], |
| 100 | b = comms[j]; |
| 101 | const dx = b.cx - a.cx; |
| 102 | const dy = b.cy - a.cy; |
| 103 | const dist = Math.sqrt(dx * dx + dy * dy); |
| 104 | const minDist = a.radius + b.radius + gap; |
| 105 | |
| 106 | if (dist < minDist) { |
no test coverage detected