@@ -3,12 +3,9 @@ import {
33 CONTAINER_LAYOUT_OPTIONS ,
44 DEFAULT_LAYOUT_OPTIONS ,
55 MAX_OVERLAP_ITERATIONS ,
6- OVERLAP_MARGIN ,
76} from '@/lib/workflows/autolayout/constants'
87import type { Edge , GraphNode , LayoutOptions } from '@/lib/workflows/autolayout/types'
98import {
10- boxesOverlap ,
11- createBoundingBox ,
129 getBlockMetrics ,
1310 normalizePositions ,
1411 prepareBlockMetrics ,
@@ -172,54 +169,48 @@ export function groupByLayer(nodes: Map<string, GraphNode>): Map<number, GraphNo
172169}
173170
174171/**
175- * Resolves overlaps between all nodes, including across layers.
176- * Nodes in the same layer are shifted vertically to avoid overlap.
177- * Nodes in different layers that overlap are shifted down.
172+ * Resolves vertical overlaps between nodes in the same layer.
173+ * X overlaps are prevented by construction via cumulative width-based positioning.
178174 */
179- function resolveOverlaps ( nodes : GraphNode [ ] , verticalSpacing : number ) : void {
175+ function resolveVerticalOverlaps ( nodes : GraphNode [ ] , verticalSpacing : number ) : void {
180176 let iteration = 0
181177 let hasOverlap = true
182178
183179 while ( hasOverlap && iteration < MAX_OVERLAP_ITERATIONS ) {
184180 hasOverlap = false
185181 iteration ++
186182
187- // Sort nodes by layer then by Y position for consistent processing
188- const sortedNodes = [ ...nodes ] . sort ( ( a , b ) => {
189- if ( a . layer !== b . layer ) return a . layer - b . layer
190- return a . position . y - b . position . y
191- } )
183+ // Group nodes by layer for same-layer overlap resolution
184+ const nodesByLayer = new Map < number , GraphNode [ ] > ( )
185+ for ( const node of nodes ) {
186+ if ( ! nodesByLayer . has ( node . layer ) ) {
187+ nodesByLayer . set ( node . layer , [ ] )
188+ }
189+ nodesByLayer . get ( node . layer ) ! . push ( node )
190+ }
192191
193- for ( let i = 0 ; i < sortedNodes . length ; i ++ ) {
194- for ( let j = i + 1 ; j < sortedNodes . length ; j ++ ) {
195- const node1 = sortedNodes [ i ]
196- const node2 = sortedNodes [ j ]
192+ // Process each layer independently
193+ for ( const [ layer , layerNodes ] of nodesByLayer ) {
194+ if ( layerNodes . length < 2 ) continue
197195
198- const box1 = createBoundingBox ( node1 . position , node1 . metrics )
199- const box2 = createBoundingBox ( node2 . position , node2 . metrics )
196+ // Sort by Y position for consistent processing
197+ layerNodes . sort ( ( a , b ) => a . position . y - b . position . y )
200198
201- // Check for overlap with margin
202- if ( boxesOverlap ( box1 , box2 , OVERLAP_MARGIN ) ) {
203- hasOverlap = true
199+ for ( let i = 0 ; i < layerNodes . length - 1 ; i ++ ) {
200+ const node1 = layerNodes [ i ]
201+ const node2 = layerNodes [ i + 1 ]
204202
205- // If in same layer, shift vertically around midpoint
206- if ( node1 . layer === node2 . layer ) {
207- const midpoint = ( node1 . position . y + node2 . position . y ) / 2
208-
209- node1 . position . y = midpoint - node1 . metrics . height / 2 - verticalSpacing / 2
210- node2 . position . y = midpoint + node2 . metrics . height / 2 + verticalSpacing / 2
211- } else {
212- // Different layers - shift the later one down
213- const requiredSpace = box1 . y + box1 . height + verticalSpacing
214- if ( node2 . position . y < requiredSpace ) {
215- node2 . position . y = requiredSpace
216- }
217- }
203+ const node1Bottom = node1 . position . y + node1 . metrics . height
204+ const requiredY = node1Bottom + verticalSpacing
205+
206+ if ( node2 . position . y < requiredY ) {
207+ hasOverlap = true
208+ node2 . position . y = requiredY
218209
219- logger . debug ( 'Resolved overlap between blocks' , {
210+ logger . debug ( 'Resolved vertical overlap in layer' , {
211+ layer,
220212 block1 : node1 . id ,
221213 block2 : node2 . id ,
222- sameLayer : node1 . layer === node2 . layer ,
223214 iteration,
224215 } )
225216 }
@@ -228,14 +219,15 @@ function resolveOverlaps(nodes: GraphNode[], verticalSpacing: number): void {
228219 }
229220
230221 if ( hasOverlap ) {
231- logger . warn ( 'Could not fully resolve all overlaps after max iterations' , {
222+ logger . warn ( 'Could not fully resolve all vertical overlaps after max iterations' , {
232223 iterations : MAX_OVERLAP_ITERATIONS ,
233224 } )
234225 }
235226}
236227
237228/**
238- * Calculates positions for nodes organized by layer
229+ * Calculates positions for nodes organized by layer.
230+ * Uses cumulative width-based X positioning to properly handle containers of varying widths.
239231 */
240232export function calculatePositions (
241233 layers : Map < number , GraphNode [ ] > ,
@@ -248,9 +240,27 @@ export function calculatePositions(
248240
249241 const layerNumbers = Array . from ( layers . keys ( ) ) . sort ( ( a , b ) => a - b )
250242
243+ // Calculate max width for each layer
244+ const layerWidths = new Map < number , number > ( )
245+ for ( const layerNum of layerNumbers ) {
246+ const nodesInLayer = layers . get ( layerNum ) !
247+ const maxWidth = Math . max ( ...nodesInLayer . map ( ( n ) => n . metrics . width ) )
248+ layerWidths . set ( layerNum , maxWidth )
249+ }
250+
251+ // Calculate cumulative X positions for each layer based on actual widths
252+ const layerXPositions = new Map < number , number > ( )
253+ let cumulativeX = padding . x
254+
255+ for ( const layerNum of layerNumbers ) {
256+ layerXPositions . set ( layerNum , cumulativeX )
257+ cumulativeX += layerWidths . get ( layerNum ) ! + horizontalSpacing
258+ }
259+
260+ // Position nodes using cumulative X
251261 for ( const layerNum of layerNumbers ) {
252262 const nodesInLayer = layers . get ( layerNum ) !
253- const xPosition = padding . x + layerNum * horizontalSpacing
263+ const xPosition = layerXPositions . get ( layerNum ) !
254264
255265 // Calculate total height for this layer
256266 const totalHeight = nodesInLayer . reduce (
@@ -285,8 +295,8 @@ export function calculatePositions(
285295 }
286296 }
287297
288- // Resolve overlaps across all nodes
289- resolveOverlaps ( Array . from ( layers . values ( ) ) . flat ( ) , verticalSpacing )
298+ // Resolve vertical overlaps within layers (X overlaps prevented by cumulative positioning)
299+ resolveVerticalOverlaps ( Array . from ( layers . values ( ) ) . flat ( ) , verticalSpacing )
290300}
291301
292302/**
0 commit comments