@@ -3043,13 +3043,79 @@ <h3>Phases</h3>
30433043 const fromPos = graphState . nodePositions . get ( rec . from ) ;
30443044 const toPos = graphState . nodePositions . get ( rec . to ) ;
30453045 if ( ! fromPos || ! toPos ) continue ;
3046+ // Anchor points: bottom-center of source -> top-center of destination
30463047 const startX = fromPos . x + GRAPH_NODE_WIDTH / 2 ;
3047- const startY = fromPos . y + GRAPH_NODE_HEIGHT / 2 ;
3048+ const startY = fromPos . y + GRAPH_NODE_HEIGHT ; // bottom of source
30483049 const endX = toPos . x + GRAPH_NODE_WIDTH / 2 ;
3049- const endY = toPos . y - 2 ; // small offset above
3050- const mx = ( startX + endX ) / 2 ;
3051- const my = ( startY + endY ) / 2 - 20 ; // tighter curve for compact layout
3052- const d = `M ${ startX } ${ startY } Q ${ mx } ${ my } ${ endX } ${ endY } ` ;
3050+ const endY = toPos . y ; // top of destination
3051+
3052+ // Compute how many rows apart they are using the level gap constant
3053+ const rowsApart = Math . max ( 1 , Math . round ( ( toPos . y - fromPos . y ) / GRAPH_LEVEL_GAP ) ) ;
3054+
3055+ // Horizontal column step between node centers
3056+ const colStep = GRAPH_COL_WIDTH + GRAPH_NODE_GAP ;
3057+
3058+ // Helper that emits a quadratic curve path segment from (sx,sy) to (ex,ey)
3059+ // with control points offset vertically from endpoints by 1/4 of vertical distance
3060+ function quadratic ( sx , sy , ex , ey ) {
3061+ const mx = ( sx + ex ) / 2 ;
3062+ const my = ( sy + ey ) / 2 ;
3063+ // Vertical offset magnitude for endpoint control points
3064+ const baseOffset = ( ey - sy ) / 4 ;
3065+ return `Q ${ sx } ${ sy + baseOffset } ${ mx } ${ my } ${ ex } ${ my + baseOffset } ${ ex } ${ ey } ` ;
3066+ }
3067+
3068+ let d = '' ;
3069+ if ( startX === endX && rowsApart === 1 ) {
3070+ // Direct vertical line from bottom of source to top of destination
3071+ d = `M ${ startX } ${ startY } L ${ endX } ${ endY } ` ;
3072+ } else if ( rowsApart === 1 ) {
3073+ // Exactly one row apart but different x: curve to exact midpoint then to dest
3074+ // We'll go from bottom of source -> midpoint -> top of dest using two cubic beziers
3075+ d = `M ${ startX } ${ startY } ` + quadratic ( startX , startY , endX , endY ) ;
3076+ } else {
3077+ // More than one row apart: build S-curve to an intermediate x (half-column shifted),
3078+ // vertical travel, then S-curve into destination.
3079+ const dir = Math . sign ( endX - startX ) || 1 ; // default to positive X if aligned
3080+ const halfColShift = colStep / 2 ;
3081+ // Prefer roughly halfway horizontally between source and destination
3082+ const candidateX = startX + ( endX - startX ) * 0.5 ;
3083+ // Determine number of column steps between the two nodes
3084+ const deltaCols = Math . max ( 1 , Math . round ( Math . abs ( endX - startX ) / colStep ) ) ;
3085+ // If the candidate lands too close to any column center (i.e. inside a node),
3086+ // nudge it by half a column into the gap between columns in the direction of travel.
3087+ let intermediateX = candidateX ;
3088+ let tooClose = false ;
3089+ for ( let k = 0 ; k <= deltaCols ; k ++ ) {
3090+ const center = startX + dir * k * colStep ;
3091+ if ( Math . abs ( candidateX - center ) < GRAPH_NODE_WIDTH / 2 + 2 ) {
3092+ tooClose = true ;
3093+ break ;
3094+ }
3095+ }
3096+ if ( tooClose ) intermediateX = candidateX + dir * halfColShift ;
3097+
3098+ // Y where we first arrive after the S-curve from the source:
3099+ // set to the top Y coordinate of the row immediately below the source.
3100+ // This equals origin row top (fromPos.y) + GRAPH_LEVEL_GAP.
3101+ const firstTargetY = fromPos . y + GRAPH_LEVEL_GAP ;
3102+
3103+ // Y at which we stop vertical travel: bottom of the row directly above destination
3104+ const bottomOfRowAboveDest = toPos . y - GRAPH_LEVEL_GAP + GRAPH_NODE_HEIGHT ;
3105+
3106+ // Ensure we don't compute inverted vertical ranges
3107+ const midY1 = firstTargetY ;
3108+ const midY2 = bottomOfRowAboveDest ;
3109+
3110+ // Build pieces:
3111+ // 1) S-curve from start -> (intermediateX, midY1)
3112+ d = `M ${ startX } ${ startY } ` + quadratic ( startX , startY , intermediateX , midY1 ) ;
3113+ // 2) Vertical line from midY1 -> midY2 at intermediateX
3114+ d += ` L ${ intermediateX } ${ midY2 } ` ;
3115+ // 3) S-curve from (intermediateX, midY2) -> destination top
3116+ d += ' ' + quadratic ( intermediateX , midY2 , endX , endY ) ;
3117+ }
3118+
30533119 rec . path . setAttribute ( 'd' , d ) ;
30543120 const depStatus = graphState . nodeStatus . get ( rec . to ) || 'Ready' ;
30553121 rec . path . setAttribute ( 'stroke' , statusColors [ depStatus ] || '#4b5563' ) ;
0 commit comments