diff --git a/steps/index.html b/steps/index.html index 7d74705..9dd12de 100644 --- a/steps/index.html +++ b/steps/index.html @@ -149,7 +149,7 @@ diff --git a/steps/src/drawUtils.js b/steps/src/drawUtils.js index c715064..536ffcf 100644 --- a/steps/src/drawUtils.js +++ b/steps/src/drawUtils.js @@ -18,6 +18,7 @@ import { toggleVelocity, toggleStationaryColor, toggleConfirmedOnly, + togglePredictedPos } from "./dom.js"; // Defines a set of SNR (Signal-to-Noise Ratio) colors. @@ -516,123 +517,170 @@ export function drawTrackMarkers(p, plotScales) { } } +// In src/drawUtils.js + +// (Make sure the necessary imports are at the top) + /** - * Handles the display of detailed info for points under the mouse cursor. + * Handles the display of a comprehensive info tooltip for all elements under the mouse. * @param {p5} p - The p5 instance. * @param {object} plotScales - The calculated scales for plotting. */ -// Function to handle the display of detailed information for points under the mouse cursor in close-up mode. export function handleCloseUpDisplay(p, plotScales) { - // Get current frame data. const frameData = appState.vizData.radarFrames[appState.currentFrame]; - if (!frameData || !frameData.pointCloud) return; - - const hoveredPoints = []; - const radius = 15; // hover radius - - // Iterate through point cloud to find hovered points. - for (const pt of frameData.pointCloud) { - // Skip if point coordinates are null. - if (pt.x === null || pt.y === null) continue; - // Calculate screen coordinates for the point. - // Convert radar coordinates to screen coordinates. - const screenX = pt.x * plotScales.plotScaleX + p.width / 2; - const screenY = p.height * 0.95 - pt.y * plotScales.plotScaleY; // Y-axis is inverted for drawing. - const d = p.dist(p.mouseX, p.mouseY, screenX, screenY); - if (d < radius) { - hoveredPoints.push({ - point: pt, - screenX: screenX, - screenY: screenY, - }); + if (!frameData) return; + + const hoveredItems = []; + const radius = 10; + const localClusterColors = clusterColors(p); // <-- Get the color palette once + + // ... (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 }); + } } } - // If points are hovered, display detailed info. - if (hoveredPoints.length > 0) { - // Sort points by Y-coordinate for consistent display. - hoveredPoints.sort((a, b) => a.screenY - b.screenY); + if (toggleClusterColor.checked && frameData.clusters) { + const clusters = Array.isArray(frameData.clusters) ? frameData.clusters : [frameData.clusters]; + for (const cluster of clusters) { + if (cluster.x === null || cluster.y === null) continue; + const screenX = cluster.x * plotScales.plotScaleX + p.width / 2; + const screenY = p.height * 0.95 - (cluster.y * plotScales.plotScaleY); + const d = p.dist(p.mouseX, p.mouseY, screenX, screenY); + if (d < radius) { + // ======================= CHANGE START ======================= + // Get the cluster's color and pass it in the hovered item object + const color = cluster.id > 0 + ? localClusterColors[(cluster.id - 1) % localClusterColors.length] + : p.color(128); + hoveredItems.push({ type: 'cluster', data: cluster, screenX, screenY, color: color }); + // ======================== CHANGE END ======================== + } + } + } - p.push(); - p.textSize(12); - // Line height for text in the info box. - const lineHeight = 15; - const boxPadding = 8; - let boxWidth = 0; - const infoStrings = []; - - // Generate info strings for each hovered point and determine max box width. - for (const hovered of hoveredPoints) { - const pt = hovered.point; - const vel = pt.velocity !== null ? pt.velocity.toFixed(2) : "N/A"; - const snr = pt.snr !== null ? pt.snr.toFixed(1) : "N/A"; - const clusterNumber = pt.clusterNumber !== null ? pt.clusterNumber : "N/A"; - const infoText = `X:${pt.x.toFixed(2)}, Y:${pt.y.toFixed(2)} | V:${vel}, SNR:${snr} | Clstr:${clusterNumber}`; - infoStrings.push(infoText); - boxWidth = Math.max(boxWidth, p.textWidth(infoText)); - } // Calculate box dimensions. - // Calculate total height and width for the info box. - const boxHeight = infoStrings.length * lineHeight + boxPadding * 2; - boxWidth += boxPadding * 2; - - // Position the info box relative to the mouse. - // Offset from mouse cursor. - const xOffset = 20; - let boxX = p.mouseX + xOffset; - let boxY = p.mouseY - boxHeight / 2; - - // Adjust box position to stay within canvas bounds. - if (boxX + boxWidth > p.width) { - boxX = p.mouseX - boxWidth - xOffset; + // ... (Step 1c: Find hovered tracks - no changes here) ... + if (appState.vizData.tracks) { + for (const track of appState.vizData.tracks) { + const log = track.historyLog.find(log => log.frameIdx === appState.currentFrame + 1); + if (log) { + if (log.correctedPosition && log.correctedPosition[0] !== null) { + const pos = log.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, trackId: track.id, screenX, screenY }); + } + } + if (togglePredictedPos.checked && log.predictedPosition && log.predictedPosition[0] !== null) { + const pos = log.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, trackId: track.id, screenX, screenY }); + } + } + } } - boxY = p.constrain(boxY, 0, p.height - boxHeight); - - // Highlight hovered points and draw connecting lines to the info box. - const highlightColor = p.color(46, 204, 113); - - // Draw highlight circles around hovered points and lines connecting them to the info box. - for (let i = 0; i < hoveredPoints.length; i++) { - const hovered = hoveredPoints[i]; - p.noFill(); - p.stroke(highlightColor); - p.strokeWeight(2); - p.ellipse(hovered.screenX, hovered.screenY, 15, 15); - p.strokeWeight(1); - p.line( - boxX + boxPadding, - boxY + boxPadding + i * lineHeight + lineHeight / 2, - hovered.screenX, - hovered.screenY - ); + } + + if (hoveredItems.length === 0) return; + + // Generate display text (no changes needed in this part) + const infoStrings = []; + // ... (The text generation logic remains the same) ... + for (const item of hoveredItems) { + let infoText = ''; + const data = item.data; + switch (item.type) { + 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(2)} | V:${vel}, SNR:${snr}`; + break; + case 'cluster': + const rs = data.radialSpeed !== null ? data.radialSpeed.toFixed(2) : 'N/A'; + const vx = data.vx !== null ? data.vx.toFixed(2) : 'N/A'; + const vy = data.vy !== null ? data.vy.toFixed(2) : 'N/A'; + infoText = `Cluster ${data.id} | X:${data.x.toFixed(2)}, Y:${data.y.toFixed(2)} | rSpeed:${rs}, vX:${vx}, vY:${vy}`; + break; + case 'track': + infoText = `Track ${item.trackId} | X:${data.correctedPosition[0].toFixed(2)}, Y:${data.correctedPosition[1].toFixed(2)}`; + break; + case 'prediction': + const p_vx = data.predictedVelocity[0] !== null ? data.predictedVelocity[0].toFixed(2) : 'N/A'; + const p_vy = data.predictedVelocity[1] !== null ? data.predictedVelocity[1].toFixed(2) : 'N/A'; + infoText = `Pred. for ${item.trackId} | X:${data.predictedPosition[0].toFixed(2)}, Y:${data.predictedPosition[1].toFixed(2)} | vX:${p_vx}, vY:${p_vy}`; + break; + } + if (infoText) { + infoStrings.push({text: infoText, color: item.color || null}); } + } + + // Render the unified tooltip + p.push(); + p.textSize(12); + const lineHeight = 15; + const boxPadding = 8; + let boxWidth = 0; + + for (const strInfo of infoStrings) { + boxWidth = Math.max(boxWidth, p.textWidth(strInfo.text)); + } + const boxHeight = (infoStrings.length * lineHeight) + (boxPadding * 2); + boxWidth += (boxPadding * 2); - // Draw the info box background and border. - const bgColor = document.documentElement.classList.contains("dark") - ? p.color(20, 20, 30, 255) - : p.color(245, 245, 245, 255); - p.fill(bgColor); + const xOffset = 20; + let boxX = p.mouseX + xOffset; + let boxY = p.mouseY - (boxHeight / 2); + + if (boxX + boxWidth > p.width) { + boxX = p.mouseX - boxWidth - xOffset; + } + boxY = p.constrain(boxY, 0, p.height - boxHeight); + + // ... (Highlighting logic remains the same) ... + const highlightColor = p.color(46, 204, 113); + for (let i = 0; i < hoveredItems.length; i++) { + const item = hoveredItems[i]; + p.noFill(); p.stroke(highlightColor); + p.strokeWeight(2); + p.ellipse(item.screenX, item.screenY, 15, 15); p.strokeWeight(1); - // Draw rounded rectangle for the info box. - p.rect(boxX, boxY, boxWidth, boxHeight, 4); - // Draw the text content inside the info box. - const textColor = document.documentElement.classList.contains("dark") - ? p.color(230) - : p.color(20); - p.fill(textColor); - p.noStroke(); - // Set text alignment. - p.textAlign(p.LEFT, p.TOP); - for (let i = 0; i < infoStrings.length; i++) { - p.text( - infoStrings[i], - boxX + boxPadding, - boxY + boxPadding + i * lineHeight - ); - } + p.line(boxX + boxPadding, boxY + boxPadding + (i * lineHeight) + (lineHeight / 2), item.screenX, item.screenY); + } - p.pop(); + 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); + p.rect(boxX, boxY, boxWidth, boxHeight, 4); + + // ======================= CHANGE START ======================= + // Draw the text inside the box, applying colors where needed + const defaultTextColor = document.documentElement.classList.contains('dark') ? p.color(230) : p.color(20); + p.noStroke(); + p.textAlign(p.LEFT, p.TOP); + for (let i = 0; i < infoStrings.length; i++) { + const info = infoStrings[i]; + // If a color is specified for this line, use it. Otherwise, use the default. + p.fill(info.color || defaultTextColor); + p.text(info.text, boxX + boxPadding, boxY + boxPadding + (i * lineHeight)); } + // ======================== CHANGE END ======================== + + p.pop(); } export function drawCovarianceEllipse(