Skip to content

Commit 1b5e8e6

Browse files
committed
Fix graph view responsiveness and zoom to fit functionality
- Remove scroll bars by eliminating minHeight constraint from graph container - Remove node position bounds constraints to allow infinite panning - Improve empty space click detection with larger background area (20000x20000) - Fix button positioning: Zen Mode at top-4 right-6, Data Health at top-20 right-6 - Align Back button position with Zen Mode button for consistency - Completely rewrite zoom to fit algorithm for proper node detection - Zoom out much more aggressively (max 0.2x scale) with generous margins - Improve node creation stability by removing disruptive refetch() call - Add detailed logging for zoom to fit debugging - Preserve all existing functionality including context menus
1 parent 649e7e0 commit 1b5e8e6

2 files changed

Lines changed: 57 additions & 58 deletions

File tree

packages/web/src/components/InteractiveGraphVisualization.tsx

Lines changed: 56 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -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

packages/web/src/index.css

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@
7373

7474
/* Graph visualization styles */
7575
.graph-container {
76-
@apply w-full h-full overflow-hidden;
76+
@apply w-full h-full;
7777
}
7878

7979
.node {

0 commit comments

Comments
 (0)