@@ -738,7 +738,8 @@ export function InteractiveGraphVisualization() {
738738
739739 if ( result . data ) {
740740 setNodeCounter ( prev => prev + 1 ) ;
741- refetch ( ) ;
741+ // Let Apollo's cache update handle the UI update instead of refetch to avoid camera jumping
742+ // The refetchQueries in the mutation config will handle the data update
742743 }
743744 } catch ( _error ) {
744745 // Error handled by mutation error state
@@ -777,11 +778,13 @@ export function InteractiveGraphVisualization() {
777778
778779 const g = svg . append ( 'g' ) ;
779780
780- // Add background for capturing clicks to show context menu
781+ // Add background for capturing clicks to show context menu (make it very large for better detection)
781782 const background = g . append ( 'rect' )
782783 . attr ( 'class' , 'background' )
783- . attr ( 'width' , width )
784- . attr ( 'height' , height )
784+ . attr ( 'x' , - 10000 )
785+ . attr ( 'y' , - 10000 )
786+ . attr ( 'width' , 20000 )
787+ . attr ( 'height' , 20000 )
785788 . attr ( 'fill' , 'transparent' )
786789 . style ( 'cursor' , 'default' ) ;
787790
@@ -1000,11 +1003,9 @@ export function InteractiveGraphVisualization() {
10001003 d . fy = d . y ;
10011004 } )
10021005 . on ( 'drag' , ( event , d : any ) => {
1003- // Get node radius
1004- const nodeRadius = 30 ;
1005- // Constrain to container bounds
1006- d . fx = Math . max ( nodeRadius , Math . min ( width - nodeRadius , event . x ) ) ;
1007- d . fy = Math . max ( nodeRadius , Math . min ( height - nodeRadius , event . y ) ) ;
1006+ // Allow free dragging without bounds constraints
1007+ d . fx = event . x ;
1008+ d . fy = event . y ;
10081009 d . x = d . fx ;
10091010 d . y = d . fy ;
10101011 } )
@@ -1843,12 +1844,7 @@ export function InteractiveGraphVisualization() {
18431844
18441845 // Simulation tick
18451846 simulation . on ( 'tick' , ( ) => {
1846- // Constrain nodes to container bounds
1847- nodes . forEach ( ( d : any ) => {
1848- const nodeRadius = 30 ;
1849- d . x = Math . max ( nodeRadius , Math . min ( width - nodeRadius , d . x || centerX ) ) ;
1850- d . y = Math . max ( nodeRadius , Math . min ( height - nodeRadius , d . y || centerY ) ) ;
1851- } ) ;
1847+ // Allow nodes to move freely without bounds constraints
18521848
18531849 // Update node positions
18541850 nodeElements
@@ -2184,7 +2180,7 @@ export function InteractiveGraphVisualization() {
21842180
21852181
21862182 return (
2187- < div ref = { containerRef } className = "graph-container relative w-full bg-gray-900" style = { { height : '100vh' , minHeight : '900px' } } >
2183+ < div ref = { containerRef } className = "graph-container relative w-full bg-gray-900" style = { { height : '100vh' } } >
21882184 < svg ref = { svgRef } className = "w-full h-full" style = { { background : 'radial-gradient(circle at center, #1f2937 0%, #111827 100%)' } } />
21892185
21902186
@@ -2776,58 +2772,61 @@ export function InteractiveGraphVisualization() {
27762772
27772773 < button
27782774 onClick = { ( ) => {
2779- // Zoom to fit all nodes
2775+ console . log ( 'Zoom to fit button clicked!' ) ;
2776+ // Zoom to fit all nodes - completely rewritten for proper detection
27802777 const svg = d3 . select ( svgRef . current ) ;
27812778 const containerRect = containerRef . current ?. getBoundingClientRect ( ) ;
27822779 if ( svg . node ( ) && containerRect && nodes . length > 0 ) {
2783- // Find bounds of all nodes using actual simulation positions
2784- const margin = 150 ;
2785- const nodePositions = nodes . map ( node => ( {
2786- x : node . x || node . positionX || 0 ,
2787- y : node . y || node . positionY || 0
2788- } ) ) ;
2780+ // Get all node positions (both from simulation and stored positions)
2781+ const allPositions = nodes . map ( node => ( {
2782+ x : node . x !== undefined ? node . x : ( node . positionX || 0 ) ,
2783+ y : node . y !== undefined ? node . y : ( node . positionY || 0 )
2784+ } ) ) . filter ( pos => pos . x !== undefined && pos . y !== undefined ) ;
2785+
2786+ if ( allPositions . length === 0 ) {
2787+ console . log ( 'No valid node positions found' ) ;
2788+ setContextMenuPosition ( null ) ;
2789+ return ;
2790+ }
27892791
2790- const xExtent = d3 . extent ( nodePositions , d => d . x ) as [ number , number ] ;
2791- const yExtent = d3 . extent ( nodePositions , d => d . y ) as [ number , number ] ;
2792+ // Find the absolute bounds of all nodes
2793+ const minX = Math . min ( ...allPositions . map ( p => p . x ) ) ;
2794+ const maxX = Math . max ( ...allPositions . map ( p => p . x ) ) ;
2795+ const minY = Math . min ( ...allPositions . map ( p => p . y ) ) ;
2796+ const maxY = Math . max ( ...allPositions . map ( p => p . y ) ) ;
27922797
2793- const width = containerRect . width ;
2794- const height = containerRect . height ;
2798+ // Add generous padding for node sizes and breathing room
2799+ const nodePadding = 150 ; // Account for node size
2800+ const extraMargin = 300 ; // Extra breathing room
2801+ const totalPadding = nodePadding + extraMargin ;
27952802
2796- // Calculate the bounding box of all nodes (add padding for node radius)
2797- const nodeRadius = 50 ; // Account for node size
2798- const nodeWidth = Math . max ( xExtent [ 1 ] - xExtent [ 0 ] + 2 * nodeRadius , 300 ) ;
2799- const nodeHeight = Math . max ( yExtent [ 1 ] - yExtent [ 0 ] + 2 * nodeRadius , 300 ) ;
2803+ const boundsWidth = ( maxX - minX ) + ( 2 * totalPadding ) ;
2804+ const boundsHeight = ( maxY - minY ) + ( 2 * totalPadding ) ;
28002805
2801- // Calculate scale to fit all nodes with margin (be more conservative)
2802- const scaleX = ( width - 2 * margin ) / nodeWidth ;
2803- const scaleY = ( height - 2 * margin ) / nodeHeight ;
2804- const scale = Math . min ( scaleX , scaleY , 0.8 ) ; // Max scale of 0.8x to zoom out more
2806+ // Calculate how much we need to scale to fit everything
2807+ const containerWidth = containerRect . width ;
2808+ const containerHeight = containerRect . height ;
28052809
2806- // Calculate center position of all nodes
2807- const nodesCenterX = ( xExtent [ 0 ] + xExtent [ 1 ] ) / 2 ;
2808- const nodesCenterY = ( yExtent [ 0 ] + yExtent [ 1 ] ) / 2 ;
2810+ const scaleX = containerWidth / boundsWidth ;
2811+ const scaleY = containerHeight / boundsHeight ;
2812+ const scale = Math . min ( scaleX , scaleY , 0.2 ) ; // Very conservative max scale for wide zoom out
28092813
2810- // Calculate screen center
2811- const screenCenterX = width / 2 ;
2812- const screenCenterY = height / 2 ;
2814+ // Find the center of all nodes
2815+ const centerX = ( minX + maxX ) / 2 ;
2816+ const centerY = ( minY + maxY ) / 2 ;
28132817
2814- // Calculate translation to put the center of nodes at the center of the screen
2815- // Formula: translate = screenCenter - (nodeCenter * scale)
2816- const translateX = screenCenterX - nodesCenterX * scale ;
2817- const translateY = screenCenterY - nodesCenterY * scale ;
2818+ // Calculate translation to center the nodes
2819+ const translateX = ( containerWidth / 2 ) - ( centerX * scale ) ;
2820+ const translateY = ( containerHeight / 2 ) - ( centerY * scale ) ;
28182821
2819- console . log ( 'Zoom to fit:' , {
2820- scale,
2821- translateX,
2822- translateY,
2823- nodesCenterX,
2824- nodesCenterY,
2825- screenCenterX,
2826- screenCenterY,
2827- width,
2828- height,
2829- xExtent,
2830- yExtent
2822+ console . log ( 'Zoom to fit (improved):' , {
2823+ nodeCount : allPositions . length ,
2824+ bounds : { minX, maxX, minY, maxY } ,
2825+ boundsSize : { width : boundsWidth , height : boundsHeight } ,
2826+ container : { width : containerWidth , height : containerHeight } ,
2827+ scale,
2828+ center : { x : centerX , y : centerY } ,
2829+ translate : { x : translateX , y : translateY }
28312830 } ) ;
28322831
28332832 // Create the transform and update D3's zoom behavior state properly
0 commit comments