From 253468f0f9973c453d05d84a0f1333258a89d142 Mon Sep 17 00:00:00 2001 From: rakadu1 Date: Mon, 3 Nov 2025 15:34:10 +0530 Subject: [PATCH] feat(zoom): Enhance zoom interaction and fix data display bugs This commit introduces significant improvements to the "Zoom Mode" (Close-Up Display) functionality and resolves critical data synchronization bugs related to tooltips and marker rendering. #### 1. Interactive Zoom Enhancements To improve user experience and provide better visual feedback during zoom operations, the following features have been added: - **Dynamic Hover Radius:** The hover detection radius is now inversely proportional to the zoom factor (`appState.zoomFactor`). This provides a larger, more forgiving selection area when zoomed out and a smaller, more precise area when zoomed in. The formula `constrain(80 / zoomFactor, 5, 25)` is used to keep the radius within a usable range. - **Visual Zoom Area Rectangle:** A semi-transparent, dashed red rectangle is now drawn on the main radar canvas around the mouse cursor. This rectangle visually represents the exact area being displayed in the zoom window, making it clear what is being magnified. - **Debug Hover Circle:** For tuning and visualization purposes, a temporary purple circle is drawn around the cursor, matching the dynamic hover radius. This makes it easy to see the current size of the selection area as the user zooms in and out with the scroll wheel. #### 2. Tooltip and Data Display Fixes A core issue was identified where tooltips and rendered markers were using data from different frames, causing a disconnect between the visualization and the information displayed. - **Corrected Data Source:** The logic has been unified across `radarSketch.js`, `zoomSketch.js`, and `drawUtils.js` to ensure that both the track markers (`correctedPosition`) and the predicted position markers (`predictedPosition`) are drawn using data exclusively from the `appState.currentFrame`. - **Aligned Tooltip Information:** The `handleCloseUpDisplay` function was corrected to fetch tooltip data for all markers from the current frame's log. This ensures the connecting lines from the tooltip point to the correct markers on the screen and that the displayed coordinate data matches what is being rendered. #### 3. Bug Fix: Console Warning - **Resolved `CanvasTextAlign` Error:** Fixed a persistent console warning (`The provided value 'NaN' is not a valid enum value of type CanvasTextAlign`). The error was caused by an incorrect `textAlign(p.LEFT - 2)` call in `zoomSketch.js`. This has been corrected to the valid `textAlign(p.LEFT, p.TOP)`, eliminating the console spam. These changes result in a more intuitive, accurate, and bug-free user experience when using the close-up display and inspecting track data. **Modified Files:** - `src/p5/radarSketch.js` - `src/p5/zoomSketch.js` - `src/drawUtils.js` --- steps/src/drawUtils.js | 32 +++++++++++++++++++++----------- steps/src/p5/radarSketch.js | 34 ++++++++++++++++++++++++++++++++++ steps/src/p5/zoomSketch.js | 4 ++-- 3 files changed, 57 insertions(+), 13 deletions(-) diff --git a/steps/src/drawUtils.js b/steps/src/drawUtils.js index 10d189f..3ff0d6c 100644 --- a/steps/src/drawUtils.js +++ b/steps/src/drawUtils.js @@ -527,7 +527,10 @@ export function handleCloseUpDisplay(p, plotScales) { const frameData = appState.vizData.radarFrames[appState.currentFrame]; if (!frameData) return []; // Return empty array if no data const hoveredItems = []; - const radius = 10; + // --- START: Dynamic Radius Logic --- + // The hover radius is now inversely proportional to the zoom factor. + const radius = p.constrain(80 / appState.zoomFactor, 5, 25); + // --- END: Dynamic Radius Logic --- const localClusterColors = clusterColors(p); // <-- Get the color palette once // ... (Step 1a: Find hovered points - no changes here) ... @@ -585,38 +588,45 @@ export function handleCloseUpDisplay(p, plotScales) { // Find hovered track markers and predicted positions if (appState.vizData.tracks) { for (const track of appState.vizData.tracks) { - const log = track.historyLog.find( - (log) => log.frameIdx === appState.currentFrame + 1 + // --- FIX START: Fetch log for the CURRENT frame for the track marker --- + const currentLog = track.historyLog.find( + (log) => log.frameIdx === appState.currentFrame ); - if (log) { - if (log.correctedPosition && log.correctedPosition[0] !== null) { - const pos = log.correctedPosition; + // --- FIX END --- + + if (currentLog) { + if (currentLog.correctedPosition && currentLog.correctedPosition[0] !== null) { + const pos = currentLog.correctedPosition; const screenX = pos[0] * plotScales.plotScaleX + p.width / 2; const screenY = p.height * 0.95 - pos[1] * plotScales.plotScaleY; const d = p.dist(p.mouseX, p.mouseY, screenX, screenY); if (d < radius) { hoveredItems.push({ type: "track", - data: log, + data: currentLog, // Use the log for the current frame trackId: track.id, screenX, screenY, }); } } + } + + // For predicted position, we now also use the current frame's log. + if (currentLog) { if ( togglePredictedPos.checked && - log.predictedPosition && - log.predictedPosition[0] !== null + currentLog.predictedPosition && + currentLog.predictedPosition[0] !== null ) { - const pos = log.predictedPosition; + const pos = currentLog.predictedPosition; const screenX = pos[0] * plotScales.plotScaleX + p.width / 2; const screenY = p.height * 0.95 - pos[1] * plotScales.plotScaleY; const d = p.dist(p.mouseX, p.mouseY, screenX, screenY); if (d < radius) { hoveredItems.push({ type: "prediction", - data: log, + data: currentLog, trackId: track.id, screenX, screenY, diff --git a/steps/src/p5/radarSketch.js b/steps/src/p5/radarSketch.js index 0114e34..dcf22d4 100644 --- a/steps/src/p5/radarSketch.js +++ b/steps/src/p5/radarSketch.js @@ -225,6 +225,40 @@ export const radarSketch = function (p) { const zoomPanel = document.getElementById("zoom-panel"); if (appState.isCloseUpMode) { const hoveredItems = handleCloseUpDisplay(p, plotScales); + + // --- START: Draw Zoom Area Rectangle & Debug Circle --- + const zoomWindow = document.getElementById("zoom-canvas-container"); + if (zoomWindow) { + const zoomWindowWidth = zoomWindow.offsetWidth; + const zoomWindowHeight = zoomWindow.offsetHeight; + + // Calculate the dimensions of the source rectangle on the main canvas. + const sourceWidth = zoomWindowWidth / appState.zoomFactor; + const sourceHeight = zoomWindowHeight / appState.zoomFactor; + + p.push(); + p.noFill(); + p.stroke(255, 0, 0, 150); // Semi-transparent red, visible in both themes. + p.strokeWeight(1); // Reduced thickness. + p.drawingContext.setLineDash([5, 3]); // Dashed line. + p.rectMode(p.CENTER); + p.rect(p.mouseX, p.mouseY, sourceWidth, sourceHeight); + p.drawingContext.setLineDash([]); // Reset line dash + p.pop(); + } + + // Draw a temporary debug circle representing the hover radius. + // This formula must match the one in drawUtils.js for accurate visualization. + const hoverRadius = p.constrain(80 / appState.zoomFactor, 5, 25); + p.push(); + p.noFill(); + p.stroke(148, 0, 211, 150); // Deep purple, semi-transparent. + p.strokeWeight(1); + p.drawingContext.setLineDash([5,3]) + p.ellipse(p.mouseX, p.mouseY, hoverRadius * 2, hoverRadius * 2); + p.pop(); + // --- END: Draw Zoom Area Rectangle & Debug Circle --- + if (hoveredItems.length > 0) { clearTimeout(appState.zoomHoverTimeout); // Cancel the timer appState.zoomHoverTimeout = null; diff --git a/steps/src/p5/zoomSketch.js b/steps/src/p5/zoomSketch.js index 8fbe899..43fc1bd 100644 --- a/steps/src/p5/zoomSketch.js +++ b/steps/src/p5/zoomSketch.js @@ -275,7 +275,7 @@ export const zoomSketch = function (p) { if (togglePredictedPos.checked) { for (const track of appState.vizData.tracks) { const log = track.historyLog.find( - (log) => log.frameIdx === appState.currentFrame + 1 + (log) => log.frameIdx === appState.currentFrame ); if ( log && @@ -314,7 +314,7 @@ export const zoomSketch = function (p) { p.fill(textColor); p.noStroke(); p.textSize(16); - p.textAlign(p.LEFT - 2, p.TOP); + p.textAlign(p.LEFT, p.TOP); p.textStyle(p.BOLD); p.text(titleText, 10, 10); p.pop();