* Resolves vertical overlaps between nodes in the same layer. * X overlaps are prevented by construction via cumulative width-based positioning.
(nodes: GraphNode[], verticalSpacing: number)
| 204 | * X overlaps are prevented by construction via cumulative width-based positioning. |
| 205 | */ |
| 206 | function resolveVerticalOverlaps(nodes: GraphNode[], verticalSpacing: number): void { |
| 207 | let iteration = 0 |
| 208 | let hasOverlap = true |
| 209 | |
| 210 | while (hasOverlap && iteration < MAX_OVERLAP_ITERATIONS) { |
| 211 | hasOverlap = false |
| 212 | iteration++ |
| 213 | |
| 214 | const nodesByLayer = new Map<number, GraphNode[]>() |
| 215 | for (const node of nodes) { |
| 216 | if (!nodesByLayer.has(node.layer)) { |
| 217 | nodesByLayer.set(node.layer, []) |
| 218 | } |
| 219 | nodesByLayer.get(node.layer)!.push(node) |
| 220 | } |
| 221 | |
| 222 | for (const [layer, layerNodes] of nodesByLayer) { |
| 223 | if (layerNodes.length < 2) continue |
| 224 | |
| 225 | layerNodes.sort((a, b) => a.position.y - b.position.y) |
| 226 | |
| 227 | for (let i = 0; i < layerNodes.length - 1; i++) { |
| 228 | const node1 = layerNodes[i] |
| 229 | const node2 = layerNodes[i + 1] |
| 230 | |
| 231 | const node1Bottom = node1.position.y + node1.metrics.height |
| 232 | const requiredY = node1Bottom + verticalSpacing |
| 233 | |
| 234 | if (node2.position.y < requiredY) { |
| 235 | hasOverlap = true |
| 236 | node2.position.y = requiredY |
| 237 | |
| 238 | logger.debug('Resolved vertical overlap in layer', { |
| 239 | layer, |
| 240 | block1: node1.id, |
| 241 | block2: node2.id, |
| 242 | iteration, |
| 243 | }) |
| 244 | } |
| 245 | } |
| 246 | } |
| 247 | } |
| 248 | |
| 249 | if (hasOverlap) { |
| 250 | logger.warn('Could not fully resolve all vertical overlaps after max iterations', { |
| 251 | iterations: MAX_OVERLAP_ITERATIONS, |
| 252 | }) |
| 253 | } |
| 254 | } |
| 255 | |
| 256 | /** |
| 257 | * Checks if a block is a container type (loop or parallel) |