@@ -104,24 +104,48 @@ export default function GraphCanvas({
104104 stateRef . current . camera . y = - selNode . y ;
105105 stateRef . current . camera . targetX = - selNode . x ;
106106 stateRef . current . camera . targetY = - selNode . y ;
107- stateRef . current . camera . scale = 1.35 ;
108- stateRef . current . camera . targetScale = 1.35 ;
107+ stateRef . current . camera . scale = 0.95 ;
108+ stateRef . current . camera . targetScale = 0.95 ;
109109 }
110110 }
111111 } , [ graphData ] ) ;
112112
113113 // Center on selected node when selection changes or panel opens/closes
114- // When sidebar opens, offset the camera so the node remains centered in the visible left viewport
114+ // Calculates optimal zoom scale so the selected node AND all its connected neighbor nodes fit comfortably in the visible viewport
115115 useEffect ( ( ) => {
116116 if ( ! selectedNodeId ) return ;
117117 const node = stateRef . current . nodeMap ?. get ( selectedNodeId ) ;
118118 if ( node ) {
119- const targetScale = 1.35 ;
120-
119+ const links = stateRef . current . links || [ ] ;
120+ const neighborNodes = [ ] ;
121+ links . forEach ( l => {
122+ if ( l . source . id === node . id && l . target ) neighborNodes . push ( l . target ) ;
123+ if ( l . target . id === node . id && l . source ) neighborNodes . push ( l . source ) ;
124+ } ) ;
125+
126+ let maxDist = 0 ;
127+ neighborNodes . forEach ( n => {
128+ const d = Math . hypot ( n . x - node . x , n . y - node . y ) ;
129+ if ( d > maxDist ) maxDist = d ;
130+ } ) ;
131+
132+ const winW = typeof window !== 'undefined' ? window . innerWidth : 1280 ;
133+ const winH = typeof window !== 'undefined' ? window . innerHeight : 800 ;
134+ const sidebarWidth = ( isPanelOpen && winW >= 640 ) ? ( winW >= 1024 ? 560 : 500 ) : 0 ;
135+ const visibleWidth = winW - sidebarWidth ;
136+ const visibleHeight = winH ;
137+
138+ // Ensure all neighbors fit with breathing room for node badges and labels
139+ const safeRadius = Math . max ( 160 , maxDist + 80 ) ;
140+ const idealScaleX = ( visibleWidth * 0.82 ) / ( safeRadius * 2 ) ;
141+ const idealScaleY = ( visibleHeight * 0.82 ) / ( safeRadius * 2 ) ;
142+ const idealScale = Math . min ( idealScaleX , idealScaleY ) ;
143+
144+ // Clamp between 0.65 and 0.95 (never over-zoom, never excessively shrink)
145+ const targetScale = Math . max ( 0.65 , Math . min ( 0.95 , idealScale ) ) ;
146+
121147 let offsetX = 0 ;
122- if ( isPanelOpen && typeof window !== 'undefined' && window . innerWidth >= 640 ) {
123- const sidebarWidth = window . innerWidth >= 1024 ? 560 : 500 ;
124- // Shift camera so the node is centered in (window.innerWidth - sidebarWidth)
148+ if ( sidebarWidth > 0 ) {
125149 offsetX = ( sidebarWidth / 2 ) / targetScale ;
126150 }
127151
0 commit comments