diff --git a/steps/index.html b/steps/index.html index 26c3930..39e9e61 100644 --- a/steps/index.html +++ b/steps/index.html @@ -147,7 +147,7 @@ id="toggle-debug2-overlay" class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500" /> Show Advanced Debug (A) + class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500" /> GOD MODE diff --git a/steps/src/drawUtils.js b/steps/src/drawUtils.js index 0551faf..e6fa1eb 100644 --- a/steps/src/drawUtils.js +++ b/steps/src/drawUtils.js @@ -532,13 +532,26 @@ export function handleCloseUpDisplay(p, plotScales) { // ... (Step 1a: Find hovered points - no changes here) ... if (frameData.pointCloud) { - for (const pt of frameData.pointCloud) { - if (pt.x === null || pt.y === null) continue; - const screenX = pt.x * plotScales.plotScaleX + p.width / 2; - const screenY = p.height * 0.95 - pt.y * plotScales.plotScaleY; - const d = p.dist(p.mouseX, p.mouseY, screenX, screenY); - if (d < radius) { - hoveredItems.push({ type: "point", data: pt, screenX, screenY }); + // In steps/src/drawUtils.js + + // Find hovered points + if (frameData.pointCloud) { + for (let i = 0; i < frameData.pointCloud.length; i++) { + const pt = frameData.pointCloud[i]; + if (pt.x === null || pt.y === null) continue; + const screenX = pt.x * plotScales.plotScaleX + p.width / 2; + const screenY = p.height * 0.95 - pt.y * plotScales.plotScaleY; + const d = p.dist(p.mouseX, p.mouseY, screenX, screenY); + if (d < radius) { + // Add the index 'i' to the object we push + hoveredItems.push({ + type: "point", + data: pt, + screenX, + screenY, + index: i, + }); + } } } } @@ -631,7 +644,7 @@ export function handleCloseUpDisplay(p, plotScales) { case "point": const vel = data.velocity !== null ? data.velocity.toFixed(2) : "N/A"; const snr = data.snr !== null ? data.snr.toFixed(1) : "N/A"; - infoText = `Point | X:${data.x.toFixed(2)}, Y:${data.y.toFixed( + infoText = `Point ${item.index} | X:${data.x.toFixed(2)}, Y:${data.y.toFixed( 2 )} | V:${vel}, SNR:${snr}, Cluster: ${data.clusterNumber}`; break; @@ -843,8 +856,6 @@ export function drawEgoVehicle(p, plotScales) { p.pop(); } - - //OLD_Solid Fill Logic /** @@ -1225,8 +1236,7 @@ export function drawClusterCentroids(p, clustersInput, plotScales) { // // p.pop(); // Restore the original global drawing state. // // } - -// OLD HATCH FILL logic +// OLD HATCH FILL logic // /** // * Draws a hatched pattern inside a rectangle defined by corner points. // * This is a new helper function. @@ -1348,4 +1358,4 @@ export function drawClusterCentroids(p, clustersInput, plotScales) { // ); // b.pop(); -// } \ No newline at end of file +// } diff --git a/steps/src/p5/radarSketch.js b/steps/src/p5/radarSketch.js index caf925e..00b1bd6 100644 --- a/steps/src/p5/radarSketch.js +++ b/steps/src/p5/radarSketch.js @@ -221,11 +221,16 @@ export const radarSketch = function (p) { // BUG FIX 1: Call the close-up handler if the mode is active // --- Zoom and Tooltip Logic --- + const COOLING_PERIOD_MS = 2000; const zoomPanel = document.getElementById("zoom-panel"); if (appState.isCloseUpMode) { const hoveredItems = handleCloseUpDisplay(p, plotScales); if (hoveredItems.length > 0) { - zoomPanel.style.display = "block"; // show the panel + clearTimeout(appState.zoomHoverTimeout); // Cancel the timer + appState.zoomHoverTimeout = null; + if (zoomPanel.style.display !== "block") { + zoomPanel.style.display = "block"; + } if ( appState.zoomSketchInstance && appState.zoomSketchInstance.updateAndDraw @@ -237,9 +242,30 @@ export const radarSketch = function (p) { plotScales ); } - } else { - zoomPanel.style.display = "none"; - } + } else if (zoomPanel.style.display === "block") { + // --- THIS BLOCK IS THE FIX --- + // If NOT hovering, but the panel is still visible: + + // 1. Continue to update the zoom sketch's position to follow the mouse. + // We pass an empty array for hoveredItems, so no tooltip is drawn. + if (appState.zoomSketchInstance && appState.zoomSketchInstance.updateAndDraw) { + appState.zoomSketchInstance.updateAndDraw( + p.mouseX, + p.mouseY, + [], // Pass empty array + plotScales + ); + } + + // 2. If a "hide" timer isn't already running, start one. + if (!appState.zoomHoverTimeout) { + appState.zoomHoverTimeout = setTimeout(() => { + console.log("Cooling period ended. Hiding zoom panel."); + zoomPanel.style.display = "none"; + appState.zoomHoverTimeout = null; + }, COOLING_PERIOD_MS); + } +} } else { zoomPanel.style.display = "none"; } @@ -324,29 +350,31 @@ export const radarSketch = function (p) { // In src/p5/radarSketch.js -p.windowResized = function () { - console.log("radarSketch: windowResized triggered!"); - - // Immediately resize the elements that we know are stable. - p.resizeCanvas(canvasContainer.offsetWidth, canvasContainer.offsetHeight); - staticBackgroundBuffer = p.createGraphics(p.width, p.height); - trackLegendBuffer = p.createGraphics(120, 120); - p.drawTrackLegendToBuffer(); - calculatePlotScales(); - drawStaticRegionsToBuffer(p, staticBackgroundBuffer, plotScales); - - // Defer the call to destroy the zoom canvas. - if (appState.zoomSketchInstance && appState.isCloseUpMode) { - setTimeout(() => { - console.log("radarSketch: Executing deferred call to zoomSketch.handleResize()."); - appState.zoomSketchInstance.handleResize(); - }, 10); // A 10ms delay is slightly more robust than 0. - } + p.windowResized = function () { + console.log("radarSketch: windowResized triggered!"); - if (appState.vizData) { - p.redraw(); - } -}; + // Immediately resize the elements that we know are stable. + p.resizeCanvas(canvasContainer.offsetWidth, canvasContainer.offsetHeight); + staticBackgroundBuffer = p.createGraphics(p.width, p.height); + trackLegendBuffer = p.createGraphics(120, 120); + p.drawTrackLegendToBuffer(); + calculatePlotScales(); + drawStaticRegionsToBuffer(p, staticBackgroundBuffer, plotScales); + + // Defer the call to destroy the zoom canvas. + if (appState.zoomSketchInstance && appState.isCloseUpMode) { + setTimeout(() => { + console.log( + "radarSketch: Executing deferred call to zoomSketch.handleResize()." + ); + appState.zoomSketchInstance.handleResize(); + }, 10); // A 10ms delay is slightly more robust than 0. + } + + if (appState.vizData) { + p.redraw(); + } + }; // Function to draw the SNR legend to its buffer p.drawSnrLegendToBuffer = function (minV, maxV) { diff --git a/steps/src/p5/zoomSketch.js b/steps/src/p5/zoomSketch.js index 19bd317..d2ff8ed 100644 --- a/steps/src/p5/zoomSketch.js +++ b/steps/src/p5/zoomSketch.js @@ -17,133 +17,7 @@ import { toggleCovariance, } from "../dom.js"; -/** - * A dedicated tooltip function for the zoom sketch. - * It draws the tooltip relative to the hovered items and compensates for the zoom factor. - */ -/** - * A dedicated tooltip function for the zoom sketch with full features. - * It draws the tooltip in the least cluttered quadrant, has dynamic connectors, - * highlights items, and compensates for the zoom factor. - */ -/** - * A dedicated tooltip function for the zoom sketch with smart quadrant positioning. - */ -/** - * A dedicated tooltip function for the zoom sketch that "pushes" the tooltip - * 100 pixels away from the hovered items towards the least cluttered corner. - */ -// function drawZoomTooltip(p, hoveredItems) { -// if (!hoveredItems || hoveredItems.length === 0) return; -// // 1. Generate text content (this is unchanged) -// const infoStrings = []; -// for (const item of hoveredItems) { -// let infoText = ''; -// const data = item.data; -// switch (item.type) { -// case 'point': infoText = `Point | X:${data.x.toFixed(2)}, Y:${data.y.toFixed(2)} | V:${data.velocity?.toFixed(2)}, SNR:${data.snr?.toFixed(1)}`; break; -// case 'cluster': infoText = `Cluster ${data.id} | X:${data.x.toFixed(2)}, Y:${data.y.toFixed(2)} | rSpeed:${data.radialSpeed?.toFixed(2)}`; break; -// case 'track': infoText = `Track ${item.trackId} | X:${data.correctedPosition[0].toFixed(2)}, Y:${data.correctedPosition[1].toFixed(2)}`; break; -// case 'prediction': infoText = `Pred. for ${item.trackId} | X:${data.predictedPosition[0].toFixed(2)}, Y:${data.predictedPosition[1].toFixed(2)}`; break; -// } -// if (infoText) infoStrings.push({ text: infoText, color: item.color || null }); -// } - -// // 2. Find the average screen position of hovered items. This is our anchor point. -// const avgX = hoveredItems.reduce((acc, item) => acc + item.screenX, 0) / hoveredItems.length; -// const avgY = hoveredItems.reduce((acc, item) => acc + item.screenY, 0) / hoveredItems.length; - -// p.push(); - -// // 3. Compensate for zoom factor for all drawing operations (unchanged) -// const zoomFactor = appState.zoomFactor || 6.0; -// p.textSize(12 / zoomFactor); -// p.strokeWeight(1 / zoomFactor); - -// const lineHeight = 15 / zoomFactor; -// const boxPadding = 8 / zoomFactor; - -// let boxWidth = 0; -// infoStrings.forEach(info => { -// boxWidth = Math.max(boxWidth, p.textWidth(info.text)); -// }); -// const boxHeight = (infoStrings.length * lineHeight) + (boxPadding * 2); -// boxWidth += (boxPadding * 2); - -// // --- START: New "Push" Positioning Logic --- - -// // Line 1: Define the push distance. We scale it by the zoomFactor so it's a consistent -// // visual distance on the screen, regardless of zoom level. -// const pushDistance = 100 / zoomFactor; - -// // Line 2: Determine the horizontal direction. If the items are on the right, our direction is left (-1). -// // If they are on the left, our direction is right (1). -// const dirX = (avgX > appState.p5_instance.width / 2) ? -1 : 1; - -// // Line 3: Determine the vertical direction. If the items are on the bottom, our direction is up (-1). -// // If they are on the top, our direction is down (1). -// const dirY = (avgY > appState.p5_instance.height / 2) ? -1 : 1; - -// // Line 4: Create a p5.Vector object. This is like an arrow representing our direction (e.g., up and to the right). -// const pushVector = p.createVector(dirX, dirY); - -// // Line 5: Normalize the vector. This makes its length exactly 1, so it only represents a pure direction. -// pushVector.normalize(); - -// // Line 6: Scale the vector. Now it's an arrow that is exactly `pushDistance` pixels long. -// pushVector.mult(pushDistance); - -// // Line 7: Calculate the tooltip's corner position by adding our push vector to the anchor point. -// let boxX = avgX + pushVector.x; -// let boxY = avgY + pushVector.y; - -// // Line 8: Define where the connector line should attach to the box. -// // If we pushed right, the connector attaches to the left side of the box. -// let connectorAnchorX = (dirX > 0) ? boxX : boxX + boxWidth; - -// // Line 9: If we pushed down, the connector attaches to the top side of the box. -// let connectorAnchorY = (dirY > 0) ? boxY : boxY + boxHeight; - -// // Line 10: Adjust the box's final position to account for its own size, so the *corner* -// // of the box is at our calculated position, not its top-left. -// if (dirX < 0) boxX -= boxWidth; // If we pushed left, shift the box left by its own width. -// if (dirY < 0) boxY -= boxHeight; // If we pushed up, shift the box up by its own height. - -// // --- END: New "Push" Positioning Logic --- - -// // 4. Draw highlights, box, text, and connectors (this logic is now restored and complete) -// const highlightColor = p.color(46, 204, 113); -// hoveredItems.forEach(item => { -// p.noFill(); p.stroke(highlightColor); p.strokeWeight(2 / zoomFactor); -// p.ellipse(item.screenX, item.screenY, 15 / zoomFactor, 15 / zoomFactor); -// }); - -// const bgColor = document.documentElement.classList.contains('dark') ? p.color(20, 20, 30, 220) : p.color(245, 245, 245, 220); -// p.fill(bgColor); p.stroke(highlightColor); p.strokeWeight(1 / zoomFactor); -// p.rect(boxX, boxY, boxWidth, boxHeight, 4 / zoomFactor); - -// const defaultTextColor = document.documentElement.classList.contains('dark') ? p.color(230) : p.color(20); -// p.noStroke(); p.textAlign(p.LEFT, p.TOP); -// infoStrings.forEach((info, i) => { -// p.fill(info.color || defaultTextColor); -// p.text(info.text, boxX + boxPadding, boxY + boxPadding + (i * lineHeight)); -// }); - -// hoveredItems.forEach((item, i) => { -// p.stroke(highlightColor); p.strokeWeight(1 / zoomFactor); -// p.line(connectorAnchorX, connectorAnchorY, item.screenX, item.screenY); -// }); - -// p.pop(); -// } -/** - * A dedicated tooltip function for the zoom sketch with smart quadrant positioning, - * individual dynamic connectors, and item highlighting. - */ -/** - * A dedicated tooltip function for the zoom sketch with full features and customizations. - */ function drawZoomTooltip(p, hoveredItems, mainMouseX) { if (!hoveredItems || hoveredItems.length === 0) return; @@ -154,7 +28,7 @@ function drawZoomTooltip(p, hoveredItems, mainMouseX) { const data = item.data; switch (item.type) { case "point": - infoText = `Point | X:${data.x.toFixed(2)}, Y:${data.y.toFixed( + infoText = `Point${item.index} | X:${data.x.toFixed(2)}, Y:${data.y.toFixed( 2 )} | V:${data.velocity?.toFixed(2)}, SNR:${data.snr?.toFixed(1)}`; break; @@ -295,13 +169,12 @@ export const zoomSketch = function (p) { if (container && container.offsetWidth > 0) { canvas = p.createCanvas(container.offsetWidth, container.offsetHeight); canvas.parent(containerId); - console.log(`zoomSketch: Canvas CREATED with dimensions ${p.width}x${p.height}`); // debug + //console.log(`zoomSketch: Canvas CREATED with dimensions ${p.width}x${p.height}`); // debug } else { console.warn("zoomSketch: updateAndDraw called, but container is not ready. Aborting draw."); //debug return; } } - console.log(`zoomSketch: updateAndDraw is running. Canvas dimensions are ${p.width}x${p.height}. Hovered items: ${hoveredItems.length}`); //debug p.redraw(); }; @@ -316,8 +189,6 @@ export const zoomSketch = function (p) { } p.draw = function () { if (!appState.vizData || !canvas) return; - console.log("zoomSketch: Draw function is executing."); //debug - p.background( document.documentElement.classList.contains("dark") ? p.color(55, 65, 81) @@ -406,7 +277,7 @@ export const zoomSketch = function (p) { p.fill(textColor); p.noStroke(); p.textSize(16); - p.textAlign(p.LEFT, p.TOP); + p.textAlign(p.LEFT -2, p.TOP); p.textStyle(p.BOLD); p.text(titleText, 10, 10); p.pop(); diff --git a/steps/src/state.js b/steps/src/state.js index 92016db..13c9801 100644 --- a/steps/src/state.js +++ b/steps/src/state.js @@ -1,4 +1,5 @@ export const appState = { + zoomHoverTimeout: null, // timeout for hovering over the GOD MODE isRawOnlyMode: false, // <-- ADD THIS LINE // Stores the parsed visualization data (radar frames, tracks, etc.)