From 99bcf4be8798ef26fe43f18c53ae0da359770a66 Mon Sep 17 00:00:00 2001 From: rakadu1 Date: Mon, 25 Aug 2025 15:29:46 +0530 Subject: [PATCH] feat: Move p5 sketches to separate modules --- steps/index.html | 524 ++----------------------------- steps/src/p5/radarSketch.js | 349 ++++++++++++++++++++ steps/src/p5/speedGraphSketch.js | 170 ++++++++++ 3 files changed, 540 insertions(+), 503 deletions(-) create mode 100644 steps/src/p5/radarSketch.js create mode 100644 steps/src/p5/speedGraphSketch.js diff --git a/steps/index.html b/steps/index.html index bc4d324..d79d5dc 100644 --- a/steps/index.html +++ b/steps/index.html @@ -263,6 +263,20 @@ // =========================================================================================================== + // import radar sketch from './src/p5/radarSketch.js'; + + import { + radarSketch + + } from './src/p5/radarSketch.js'; + + + // import speed graph sketch from './src/p5/speedGraphSketch.js'; + + import { + speedGraphSketch + + } from './src/p5/speedGraphSketch.js'; // import JSON parser, can log procesor from './src/fileParsers.js'; @@ -328,501 +342,6 @@ initDB, saveFileToDB, loadFileFromDB } from './src/db.js'; - // import file parsers from './src/fileParsers.js';' - - // --- p5.js Sketch Definitions --- - let sketch = function (p) { - - let plotScaleX, plotScaleY, staticBackgroundBuffer, snrLegendBuffer, snrColors, clusterColors; - // ADD COLOR DEFINITIONS FOR STATIONARY/MOVING OBJECTS - let stationaryColor, movingColor; - - p.setup = function () { - let canvas = p.createCanvas(canvasContainer.offsetWidth, canvasContainer.offsetHeight); - canvas.parent('canvas-container'); - staticBackgroundBuffer = p.createGraphics(p.width, p.height); - snrLegendBuffer = p.createGraphics(100, 450); - snrColors = { c1: p.color(0, 0, 255), c2: p.color(0, 255, 255), c3: p.color(0, 255, 0), c4: p.color(255, 255, 0), c5: p.color(255, 0, 0) }; - clusterColors = [p.color(230, 25, 75), p.color(60, 180, 75), p.color(0, 130, 200), p.color(245, 130, 48), p.color(145, 30, 180), p.color(70, 240, 240), p.color(240, 50, 230), p.color(210, 245, 60), p.color(128, 0, 0), p.color(0, 128, 128)]; - - // INITIALIZE STATIONARY/MOVING COLORS - stationaryColor = p.color(218, 165, 32); // Golden ROD Yellow - movingColor = p.color(255, 0, 255); // Magenta - - calculatePlotScales(); - p.drawSnrLegendToBuffer(appState.globalMinSnr, appState.globalMaxSnr); - drawStaticRegionsToBuffer(); - p.noLoop(); - }; - function calculatePlotScales() { const hPad = 0.05, vPad = 0.05, bOff = 0.05; const aW = p.width * (1 - 2 * hPad); const aH = p.height * (1 - bOff - vPad); plotScaleX = aW / (RADAR_X_MAX - RADAR_X_MIN); plotScaleY = aH / (RADAR_Y_MAX - RADAR_Y_MIN); } - p.draw = function () { - if (document.documentElement.classList.contains('dark')) { - p.background(55, 65, 81); - } else { - p.background(255); - } - if (!appState.vizData) return; - p.image(staticBackgroundBuffer, 0, 0); - p.push(); - p.translate(p.width / 2, p.height * 0.95); - p.scale(1, -1); - calculatePlotScales(); - drawAxes(); - if (toggleTracks.checked) { - drawTrajectories(); - drawTrackMarkers(); - } - const frameData = appState.vizData.radarFrames[appState.currentFrame]; - if (frameData) drawPointCloud(frameData.pointCloud); - p.pop(); - - if (appState.isCloseUpMode) { - handleCloseUpDisplay(); - } - - if (toggleSnrColor.checked) p.image(snrLegendBuffer, 10, p.height - snrLegendBuffer.height - 10); - }; - - - function drawStaticRegionsToBuffer() { const b = staticBackgroundBuffer; b.clear(); b.push(); b.translate(b.width / 2, b.height * 0.95); b.scale(1, -1); const hPad = 0.05, vPad = 0.05, bOff = 0.05; const bAW = b.width * (1 - 2 * hPad); const bAH = b.height * (1 - bOff - vPad); const bPSX = bAW / (RADAR_X_MAX - RADAR_X_MIN); const bPSY = bAH / (RADAR_Y_MAX - RADAR_Y_MIN); b.stroke(100, 100, 100, 150); b.strokeWeight(1); b.drawingContext.setLineDash([8, 8]); const a1 = p.radians(30), a2 = p.radians(150); const len = 70; b.line(0, 0, len * p.cos(a1) * bPSX, len * p.sin(a1) * bPSY); b.line(0, 0, len * p.cos(a2) * bPSX, len * p.sin(a2) * bPSY); b.drawingContext.setLineDash([]); b.pop(); } - function drawAxes() { - p.push(); - const axisColor = document.documentElement.classList.contains('dark') ? p.color(100) : p.color(220); - const mainAxisColor = document.documentElement.classList.contains('dark') ? p.color(150) : p.color(180); - const textColor = document.documentElement.classList.contains('dark') ? p.color(200) : p.color(150); - p.stroke(axisColor); - p.strokeWeight(1); - for (let y = 5; y <= RADAR_Y_MAX; y += 5) p.line(RADAR_X_MIN * plotScaleX, y * plotScaleY, RADAR_X_MAX * plotScaleX, y * plotScaleY); - for (let x = -15; x <= 15; x += 5) { if (x === 0) continue; p.line(x * plotScaleX, RADAR_Y_MIN * plotScaleY, x * plotScaleX, RADAR_Y_MAX * plotScaleY); } - p.stroke(mainAxisColor); - p.line(RADAR_X_MIN * plotScaleX, 0, RADAR_X_MAX * plotScaleX, 0); - p.line(0, RADAR_Y_MIN * plotScaleY, 0, RADAR_Y_MAX * plotScaleY); - p.fill(textColor); - p.noStroke(); - p.textSize(10); - for (let y = 5; y <= RADAR_Y_MAX; y += 5) { p.push(); p.translate(5, y * plotScaleY); p.scale(1, -1); p.text(y, 0, 4); p.pop(); } - for (let x = -15; x <= 15; x += 5) { if (x === 0) continue; p.push(); p.translate(x * plotScaleX, -10); p.scale(1, -1); p.textAlign(p.CENTER); p.text(x, 0, 0); p.pop(); } - p.pop(); - } - function drawPointCloud(points) { - p.strokeWeight(4); - const useSnr = toggleSnrColor.checked; - const useCluster = toggleClusterColor.checked; - const useInlier = toggleInlierColor.checked; - const useFrameNorm = toggleFrameNorm.checked; - let minSnr = appState.globalMinSnr, maxSnr = appState.globalMaxSnr; - - if (useSnr && useFrameNorm && points.length > 0) { - const snrVals = points.map(p => p.snr).filter(snr => snr !== null); - if (snrVals.length > 1) { - minSnr = Math.min(...snrVals); - maxSnr = Math.max(...snrVals); - } else if (snrVals.length === 1) { - minSnr = snrVals[0] - 1; - maxSnr = snrVals[0] + 1; - } - } - if (useSnr) p.drawSnrLegendToBuffer(minSnr, maxSnr); - - for (const pt of points) { - if (pt && pt.x !== null && pt.y !== null) { - if (useCluster && pt.clusterNumber !== null) { - if (pt.clusterNumber > 0) { - p.stroke(clusterColors[(pt.clusterNumber - 1) % clusterColors.length]); - } else { - p.stroke(128); - } - } else if (useInlier) { - if (pt.isOutlier === false) { - p.stroke(0, 255, 0); - } else if (pt.isOutlier === true) { - p.stroke(255, 0, 0); - } else { - p.stroke(128); - } - } else if (useSnr && pt.snr !== null) { - const amt = p.map(pt.snr, minSnr, maxSnr, 0, 1, true); - let c; - if (amt < 0.25) c = p.lerpColor(snrColors.c1, snrColors.c2, amt / 0.25); - else if (amt < 0.5) c = p.lerpColor(snrColors.c2, snrColors.c3, (amt - 0.25) / 0.25); - else if (amt < 0.75) c = p.lerpColor(snrColors.c3, snrColors.c4, (amt - 0.5) / 0.25); - else c = p.lerpColor(snrColors.c4, snrColors.c5, (amt - 0.75) / 0.25); - p.stroke(c); - } else { - p.stroke(0, 150, 255); - } - p.point(pt.x * plotScaleX, pt.y * plotScaleY); - } - } - } - function drawTrajectories() { - for (const track of appState.vizData.tracks) { - const logs = track.historyLog.filter(log => log.frameIdx <= appState.currentFrame + 1); - if (logs.length < 2) continue; - - const lastLog = logs[logs.length - 1]; - if (appState.currentFrame + 1 - lastLog.frameIdx > MAX_TRAJECTORY_LENGTH) continue; - - // Determine the state from the most recent log entry - const isCurrentlyStationary = lastLog.isStationary; - - // Set max trajectory length based on state - let maxLen = MAX_TRAJECTORY_LENGTH; - if (isCurrentlyStationary) { - maxLen = Math.floor(MAX_TRAJECTORY_LENGTH / 4); - } - - let trajPts = logs.filter(log => log.correctedPosition && log.correctedPosition[0] !== null).map(log => log.correctedPosition); - if (trajPts.length > maxLen) { - trajPts = trajPts.slice(trajPts.length - maxLen); - } - - p.push(); - p.noFill(); - - // Apply different styles based on the stationary state - if (isCurrentlyStationary) { - // Style for STATIONARY tracks: thin, dashed, green - p.stroke(34, 139, 34, 220); // A darker, forest green - p.strokeWeight(1); - p.drawingContext.setLineDash([3, 3]); // Small dashes - } else { - // Style for MOVING tracks: default blue - const isDark = document.documentElement.classList.contains('dark'); - if (isDark) { - p.stroke(10, 170, 255, 250); - } else { - p.stroke(0, 50, 255, 250); - } - p.strokeWeight(1.5); - // No dash for solid line - } - - p.beginShape(); - for (const pos of trajPts) p.vertex(pos[0] * plotScaleX, pos[1] * plotScaleY); - p.endShape(); - - // IMPORTANT: Reset the drawing context to avoid affecting other elements - p.drawingContext.setLineDash([]); - p.pop(); - } - } - function drawTrackMarkers() { - const showDetails = toggleVelocity.checked; - const useStationary = toggleStationaryColor.checked; - const textColor = document.documentElement.classList.contains('dark') ? p.color(255) : p.color(0); - - for (const track of appState.vizData.tracks) { - const log = track.historyLog.find(log => log.frameIdx === appState.currentFrame + 1); - if (log) { - const pos = (log.correctedPosition && log.correctedPosition[0] !== null) ? log.correctedPosition : log.predictedPosition; - if (pos && pos.length === 2 && pos[0] !== null && pos[1] !== null) { - - // --- START OF CORRECTED LOGIC --- - - const size = 5, x = pos[0] * plotScaleX, y = pos[1] * plotScaleY; - let velocityColor = p.color(255, 0, 255, 200); // Default velocity color - - p.push(); - p.strokeWeight(2); - - // Check conditions and draw the correct marker - if (useStationary && log.isStationary === true) { - // 1. Stationary object with toggle ON - p.stroke(stationaryColor); // Use yellow - p.noFill(); - p.rectMode(p.CENTER); - p.square(x, y, size * 1.5); - velocityColor = stationaryColor; - } else { - // 2. Moving object OR toggle OFF - let markerColor = p.color(0, 0, 255); // Default blue - if (useStationary && log.isStationary === false) { - markerColor = movingColor; // Magenta if toggle is on - velocityColor = movingColor; - } - p.stroke(markerColor); - p.line(x - size, y, x + size, y); - p.line(x, y - size, x, y + size); - } - p.pop(); - - // --- END OF CORRECTED LOGIC --- - - if (showDetails && log.predictedVelocity && log.predictedVelocity[0] !== null) { - const [vx, vy] = log.predictedVelocity; - - // Only draw velocity line if object is NOT stationary - if (log.isStationary === false) { - p.push(); - p.stroke(velocityColor); - p.strokeWeight(2); - p.line(pos[0] * plotScaleX, pos[1] * plotScaleY, (pos[0] + vx) * plotScaleX, (pos[1] + vy) * plotScaleY); - p.pop(); - } - - const speed = (p.sqrt(vx * vx + vy * vy) * 3.6).toFixed(1); - const ttc = (log.ttc !== null && isFinite(log.ttc) && log.ttc < 100) ? `TTC: ${log.ttc.toFixed(1)}s` : ''; - const text = `ID: ${track.id} | ${speed} km/h\n${ttc}`; - p.push(); - p.fill(textColor); - p.noStroke(); - p.scale(1, -1); - p.textSize(12); - p.text(text, pos[0] * plotScaleX + 10, -pos[1] * plotScaleY); - p.pop(); - } - } - } - } - } - - function handleCloseUpDisplay() { - const frameData = appState.vizData.radarFrames[appState.currentFrame]; - if (!frameData || !frameData.pointCloud) return; - - const hoveredPoints = []; - const radius = 10; - - for (const pt of frameData.pointCloud) { - if (pt.x === null || pt.y === null) continue; - const screenX = (pt.x * plotScaleX) + p.width / 2; - const screenY = p.height * 0.95 - (pt.y * plotScaleY); - const d = p.dist(p.mouseX, p.mouseY, screenX, screenY); - if (d < radius) { - hoveredPoints.push({ point: pt, screenX: screenX, screenY: screenY }); - } - } - - if (hoveredPoints.length > 0) { - hoveredPoints.sort((a, b) => a.screenY - b.screenY); - - p.push(); - p.textSize(12); - const lineHeight = 15; - const boxPadding = 8; - let boxWidth = 0; - const infoStrings = []; - - 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 infoText = `X:${pt.x.toFixed(2)}, Y:${pt.y.toFixed(2)} | V:${vel}, SNR:${snr}`; - infoStrings.push(infoText); - boxWidth = Math.max(boxWidth, p.textWidth(infoText)); - } - - const boxHeight = (infoStrings.length * lineHeight) + (boxPadding * 2); - boxWidth += (boxPadding * 2); - - 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); - - const highlightColor = p.color(46, 204, 113); - - 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); - } - - const bgColor = document.documentElement.classList.contains('dark') ? p.color(20, 20, 30, 255) : p.color(245, 245, 245, 255); - p.fill(bgColor); - p.stroke(highlightColor); - p.strokeWeight(1); - p.rect(boxX, boxY, boxWidth, boxHeight, 4); - - const textColor = document.documentElement.classList.contains('dark') ? p.color(230) : p.color(20); - p.fill(textColor); - p.noStroke(); - 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.pop(); - } - } - - p.drawSnrLegendToBuffer = function (minV, maxV) { const b = snrLegendBuffer; b.clear(); b.push(); const lx = 10, ly = 20, lw = 15, lh = 400; for (let i = 0; i < lh; i++) { const amt = b.map(i, 0, lh, 1, 0); let c; if (amt < 0.25) c = b.lerpColor(snrColors.c1, snrColors.c2, amt / 0.25); else if (amt < 0.5) c = b.lerpColor(snrColors.c2, snrColors.c3, (amt - 0.25) / 0.25); else if (amt < 0.75) c = b.lerpColor(snrColors.c3, snrColors.c4, (amt - 0.5) / 0.25); else c = b.lerpColor(snrColors.c4, snrColors.c5, (amt - 0.75) / 0.25); b.stroke(c); b.line(lx, ly + i, lx + lw, ly + i); } b.fill(0); b.noStroke(); b.textSize(10); b.textAlign(b.LEFT, b.CENTER); b.text(maxV.toFixed(1), lx + lw + 5, ly); b.text(minV.toFixed(1), lx + lw + 5, ly + lh); b.text("SNR", lx, ly - 10); b.pop(); }; - p.windowResized = function () { - p.resizeCanvas(canvasContainer.offsetWidth, canvasContainer.offsetHeight); - // Instead of resizing the buffer, we re-create it - staticBackgroundBuffer = p.createGraphics(p.width, p.height); - // And we must re-draw the static content to the new buffer - calculatePlotScales(); - drawStaticRegionsToBuffer(); - if (appState.vizData) p.redraw(); - }; - }; - let speedGraphSketch = function (p) { - let staticBuffer, minSpeed, maxSpeed, videoDuration; - const pad = { top: 20, right: 130, bottom: 30, left: 50 }; - - // This function is now attached to the p5 instance, making it public - // It's responsible for drawing the static background and data lines - p.drawStaticGraphToBuffer = function (canSpeedData, radarData) { - const b = staticBuffer; - b.clear(); - const isDark = document.documentElement.classList.contains('dark'); - b.background(isDark ? [55, 65, 81] : 255); - const gridColor = isDark ? 100 : 200; - const textColor = isDark ? 200 : 100; - - b.push(); - b.stroke(gridColor); - b.strokeWeight(1); - b.line(pad.left, pad.top, pad.left, b.height - pad.bottom); - b.line(pad.left, b.height - pad.bottom, b.width - pad.right, b.height - pad.bottom); - b.textAlign(b.RIGHT, b.CENTER); - b.noStroke(); - b.fill(textColor); - b.textSize(10); - for (let s = minSpeed; s <= maxSpeed; s += 10) { - const y = b.map(s, minSpeed, maxSpeed, b.height - pad.bottom, pad.top); - b.text(s, pad.left - 8, y); - if (s === 0) { - b.strokeWeight(1.5); - b.stroke(isDark ? 150 : 180); - } else { - b.strokeWeight(1); - b.stroke(isDark ? 80 : 230); - } - b.line(pad.left + 1, y, b.width - pad.right, y); - b.noStroke(); - } - - b.fill(textColor); - b.text("km/h", pad.left - 8, pad.top - 8); - b.textAlign(b.CENTER, b.TOP); - b.noStroke(); - b.fill(isDark ? 180 : 150); - const tInt = Math.max(1, Math.floor(videoDuration / 10)); - for (let t = 0; t <= videoDuration; t += tInt) { const x = b.map(t, 0, videoDuration, pad.left, b.width - pad.right); b.text(Math.round(t), x, b.height - pad.bottom + 5); } - b.fill(textColor); - b.text("Time (s)", b.width / 2, b.height - pad.bottom + 18); - b.pop(); - - if (canSpeedData && canSpeedData.length > 0) { - b.noFill(); - b.stroke(0, 150, 255); - b.strokeWeight(1.5); - b.beginShape(); - for (const d of canSpeedData) { const relTime = (d.time - appState.videoStartDate.getTime()) / 1000; if (relTime >= 0 && relTime <= videoDuration) { const x = b.map(relTime, 0, videoDuration, pad.left, b.width - pad.right); const y = b.map(d.speed, minSpeed, maxSpeed, b.height - pad.bottom, pad.top); b.vertex(x, y); } } - b.endShape(); - } - - if (radarData && radarData.radarFrames) { - b.stroke(0, 200, 100); - b.drawingContext.setLineDash([5, 5]); - b.beginShape(); - for (const frame of radarData.radarFrames) { - const relTime = frame.timestamp / 1000; - if (relTime >= 0 && relTime <= videoDuration) { - const x = b.map(relTime, 0, videoDuration, pad.left, b.width - pad.right); - const egoSpeedKmh = frame.egoVelocity[1] * 3.6; - const y = b.map(egoSpeedKmh, minSpeed, maxSpeed, b.height - pad.bottom, pad.top); - b.vertex(x, y); - } - } - b.endShape(); - b.drawingContext.setLineDash([]); - } - - b.push(); - b.strokeWeight(2); - b.noStroke(); - b.fill(textColor); - b.textAlign(b.LEFT, b.CENTER); - b.stroke(0, 150, 255); - b.line(b.width - 120, pad.top + 10, b.width - 100, pad.top + 10); - b.noStroke(); - b.text("CAN Speed", b.width - 95, pad.top + 10); - b.stroke(0, 200, 100); - b.drawingContext.setLineDash([3, 3]); - b.line(b.width - 120, pad.top + 30, b.width - 100, pad.top + 30); - b.drawingContext.setLineDash([]); - b.noStroke(); - b.text("Ego Speed", b.width - 95, pad.top + 30); - b.pop(); - }; - - p.setup = function () { - let canvas = p.createCanvas(speedGraphContainer.offsetWidth, speedGraphContainer.offsetHeight); - canvas.parent('speed-graph-container'); - staticBuffer = p.createGraphics(p.width, p.height); - p.noLoop(); - }; - - p.setData = function (canSpeedData, radarData, duration) { - if ((!canSpeedData || canSpeedData.length === 0) && !radarData) return; - videoDuration = duration; - - let speeds = []; - if (canSpeedData) { - speeds.push(...canSpeedData.map(d => parseFloat(d.speed))); - } - if (radarData && radarData.radarFrames) { - const egoSpeeds = radarData.radarFrames.map(frame => frame.egoVelocity[1] * 3.6); - speeds.push(...egoSpeeds); - } - - minSpeed = speeds.length > 0 ? Math.floor(Math.min(...speeds) / 10) * 10 : 0; - maxSpeed = speeds.length > 0 ? Math.ceil(Math.max(...speeds) / 10) * 10 : 10; - if (maxSpeed <= 0) maxSpeed = 10; - if (minSpeed >= 0) minSpeed = 0; - - p.drawStaticGraphToBuffer(canSpeedData, radarData); - p.redraw(); - }; - - p.draw = function () { - if (!videoDuration) return; - p.image(staticBuffer, 0, 0); - drawTimeIndicator(); - }; - - function drawTimeIndicator() { - const currentTime = videoPlayer.currentTime; - const x = p.map(currentTime, 0, videoDuration, pad.left, p.width - pad.right); - p.stroke(255, 0, 0, 150); - p.strokeWeight(1.5); - p.line(x, pad.top, x, p.height - pad.bottom); - const videoAbsTimeMs = appState.videoStartDate.getTime() + (currentTime * 1000); - const canIndex = findLastCanIndexBefore(videoAbsTimeMs, appState.canData); - if (canIndex !== -1) { - const canMsg = appState.canData[canIndex]; - const y = p.map(canMsg.speed, minSpeed, maxSpeed, p.height - pad.bottom, pad.top); - p.fill(255, 0, 0); - p.noStroke(); - p.ellipse(x, y, 8, 8); - } - } - - p.windowResized = function () { - p.resizeCanvas(speedGraphContainer.offsetWidth, speedGraphContainer.offsetHeight); - // Instead of resizing the buffer, we re-create it - staticBuffer = p.createGraphics(p.width, p.height); - // And we must re-draw the static content to the new buffer - if ((appState.canData.length > 0 || appState.vizData) && videoDuration) { - p.drawStaticGraphToBuffer(appState.canData, appState.vizData); - } - p.redraw(); - }; - }; - - function setupVideoPlayer(fileURL) { videoPlayer.src = fileURL; videoPlayer.classList.remove('hidden'); videoPlaceholder.classList.add('hidden'); videoPlayer.playbackRate = parseFloat(speedSlider.value); } loadJsonBtn.addEventListener('click', () => jsonFileInput.click()); loadVideoBtn.addEventListener('click', () => videoFileInput.click()); loadCanBtn.addEventListener('click', () => canFileInput.click()); @@ -861,7 +380,7 @@ featureToggles.classList.remove('hidden'); if (!appState.p5_instance) { - appState.p5_instance = new p5(sketch); + appState.p5_instance = new p5(radarSketch); } if (appState.speedGraphInstance) { @@ -1076,7 +595,7 @@ canvasPlaceholder.style.display = 'none'; featureToggles.classList.remove('hidden'); if (!appState.p5_instance) { - appState.p5_instance = new p5(sketch); + appState.p5_instance = new p5(radarSketch); } } if (appState.canData.length > 0 || appState.vizData) { @@ -1089,16 +608,15 @@ }; // This is the main controller + // --- THIS IS THE CORRECTED CODE --- if (videoBlob) { const fileURL = URL.createObjectURL(videoBlob); setupVideoPlayer(fileURL); - videoPlayer.onloadedmetadata = processAllData; // Process data ONLY when video is ready - if (videoPlayer.readyState >= 1) { - processAllData(); // Or if it was ready immediately - } + // This ensures we ONLY process data once the video's duration is known. + videoPlayer.onloadedmetadata = processAllData; } else { - // If there's no video, there's nothing to process - console.log("DEBUG: No video blob found. Awaiting user input."); + // If there's no video, we can go ahead and process the other data. + processAllData(); } }).catch(error => { console.error("DEBUG: Error during Promise.all data loading:", error); diff --git a/steps/src/p5/radarSketch.js b/steps/src/p5/radarSketch.js new file mode 100644 index 0000000..069d9d3 --- /dev/null +++ b/steps/src/p5/radarSketch.js @@ -0,0 +1,349 @@ +//---Import APPSTATE, Constatns, DOM---// + +import { appState + +} from '../state.js'; + +import { RADAR_X_MAX, RADAR_X_MIN, RADAR_Y_MAX, RADAR_Y_MIN, MAX_TRAJECTORY_LENGTH + +} from '../constants.js'; + +import { canvasContainer, toggleFrameNorm, toggleSnrColor, toggleClusterColor, toggleInlierColor, toggleStationaryColor, toggleTracks, toggleVelocity +} from '../dom.js'; + + +export const radarSketch = function (p) { + + let plotScaleX, plotScaleY, staticBackgroundBuffer, snrLegendBuffer, snrColors, clusterColors; + // ADD COLOR DEFINITIONS FOR STATIONARY/MOVING OBJECTS + let stationaryColor, movingColor; + + p.setup = function () { + let canvas = p.createCanvas(canvasContainer.offsetWidth, canvasContainer.offsetHeight); + canvas.parent('canvas-container'); + staticBackgroundBuffer = p.createGraphics(p.width, p.height); + snrLegendBuffer = p.createGraphics(100, 450); + snrColors = { c1: p.color(0, 0, 255), c2: p.color(0, 255, 255), c3: p.color(0, 255, 0), c4: p.color(255, 255, 0), c5: p.color(255, 0, 0) }; + clusterColors = [p.color(230, 25, 75), p.color(60, 180, 75), p.color(0, 130, 200), p.color(245, 130, 48), p.color(145, 30, 180), p.color(70, 240, 240), p.color(240, 50, 230), p.color(210, 245, 60), p.color(128, 0, 0), p.color(0, 128, 128)]; + + // INITIALIZE STATIONARY/MOVING COLORS + stationaryColor = p.color(218, 165, 32); // Golden ROD Yellow + movingColor = p.color(255, 0, 255); // Magenta + + calculatePlotScales(); + p.drawSnrLegendToBuffer(appState.globalMinSnr, appState.globalMaxSnr); + drawStaticRegionsToBuffer(); + p.noLoop(); + }; + function calculatePlotScales() { const hPad = 0.05, vPad = 0.05, bOff = 0.05; const aW = p.width * (1 - 2 * hPad); const aH = p.height * (1 - bOff - vPad); plotScaleX = aW / (RADAR_X_MAX - RADAR_X_MIN); plotScaleY = aH / (RADAR_Y_MAX - RADAR_Y_MIN); } + p.draw = function () { + if (document.documentElement.classList.contains('dark')) { + p.background(55, 65, 81); + } else { + p.background(255); + } + if (!appState.vizData) return; + p.image(staticBackgroundBuffer, 0, 0); + p.push(); + p.translate(p.width / 2, p.height * 0.95); + p.scale(1, -1); + calculatePlotScales(); + drawAxes(); + if (toggleTracks.checked) { + drawTrajectories(); + drawTrackMarkers(); + } + const frameData = appState.vizData.radarFrames[appState.currentFrame]; + if (frameData) drawPointCloud(frameData.pointCloud); + p.pop(); + + if (appState.isCloseUpMode) { + handleCloseUpDisplay(); + } + + if (toggleSnrColor.checked) p.image(snrLegendBuffer, 10, p.height - snrLegendBuffer.height - 10); + }; + + + function drawStaticRegionsToBuffer() { const b = staticBackgroundBuffer; b.clear(); b.push(); b.translate(b.width / 2, b.height * 0.95); b.scale(1, -1); const hPad = 0.05, vPad = 0.05, bOff = 0.05; const bAW = b.width * (1 - 2 * hPad); const bAH = b.height * (1 - bOff - vPad); const bPSX = bAW / (RADAR_X_MAX - RADAR_X_MIN); const bPSY = bAH / (RADAR_Y_MAX - RADAR_Y_MIN); b.stroke(100, 100, 100, 150); b.strokeWeight(1); b.drawingContext.setLineDash([8, 8]); const a1 = p.radians(30), a2 = p.radians(150); const len = 70; b.line(0, 0, len * p.cos(a1) * bPSX, len * p.sin(a1) * bPSY); b.line(0, 0, len * p.cos(a2) * bPSX, len * p.sin(a2) * bPSY); b.drawingContext.setLineDash([]); b.pop(); } + function drawAxes() { + p.push(); + const axisColor = document.documentElement.classList.contains('dark') ? p.color(100) : p.color(220); + const mainAxisColor = document.documentElement.classList.contains('dark') ? p.color(150) : p.color(180); + const textColor = document.documentElement.classList.contains('dark') ? p.color(200) : p.color(150); + p.stroke(axisColor); + p.strokeWeight(1); + for (let y = 5; y <= RADAR_Y_MAX; y += 5) p.line(RADAR_X_MIN * plotScaleX, y * plotScaleY, RADAR_X_MAX * plotScaleX, y * plotScaleY); + for (let x = -15; x <= 15; x += 5) { if (x === 0) continue; p.line(x * plotScaleX, RADAR_Y_MIN * plotScaleY, x * plotScaleX, RADAR_Y_MAX * plotScaleY); } + p.stroke(mainAxisColor); + p.line(RADAR_X_MIN * plotScaleX, 0, RADAR_X_MAX * plotScaleX, 0); + p.line(0, RADAR_Y_MIN * plotScaleY, 0, RADAR_Y_MAX * plotScaleY); + p.fill(textColor); + p.noStroke(); + p.textSize(10); + for (let y = 5; y <= RADAR_Y_MAX; y += 5) { p.push(); p.translate(5, y * plotScaleY); p.scale(1, -1); p.text(y, 0, 4); p.pop(); } + for (let x = -15; x <= 15; x += 5) { if (x === 0) continue; p.push(); p.translate(x * plotScaleX, -10); p.scale(1, -1); p.textAlign(p.CENTER); p.text(x, 0, 0); p.pop(); } + p.pop(); + } + function drawPointCloud(points) { + p.strokeWeight(4); + const useSnr = toggleSnrColor.checked; + const useCluster = toggleClusterColor.checked; + const useInlier = toggleInlierColor.checked; + const useFrameNorm = toggleFrameNorm.checked; + let minSnr = appState.globalMinSnr, maxSnr = appState.globalMaxSnr; + + if (useSnr && useFrameNorm && points.length > 0) { + const snrVals = points.map(p => p.snr).filter(snr => snr !== null); + if (snrVals.length > 1) { + minSnr = Math.min(...snrVals); + maxSnr = Math.max(...snrVals); + } else if (snrVals.length === 1) { + minSnr = snrVals[0] - 1; + maxSnr = snrVals[0] + 1; + } + } + if (useSnr) p.drawSnrLegendToBuffer(minSnr, maxSnr); + + for (const pt of points) { + if (pt && pt.x !== null && pt.y !== null) { + if (useCluster && pt.clusterNumber !== null) { + if (pt.clusterNumber > 0) { + p.stroke(clusterColors[(pt.clusterNumber - 1) % clusterColors.length]); + } else { + p.stroke(128); + } + } else if (useInlier) { + if (pt.isOutlier === false) { + p.stroke(0, 255, 0); + } else if (pt.isOutlier === true) { + p.stroke(255, 0, 0); + } else { + p.stroke(128); + } + } else if (useSnr && pt.snr !== null) { + const amt = p.map(pt.snr, minSnr, maxSnr, 0, 1, true); + let c; + if (amt < 0.25) c = p.lerpColor(snrColors.c1, snrColors.c2, amt / 0.25); + else if (amt < 0.5) c = p.lerpColor(snrColors.c2, snrColors.c3, (amt - 0.25) / 0.25); + else if (amt < 0.75) c = p.lerpColor(snrColors.c3, snrColors.c4, (amt - 0.5) / 0.25); + else c = p.lerpColor(snrColors.c4, snrColors.c5, (amt - 0.75) / 0.25); + p.stroke(c); + } else { + p.stroke(0, 150, 255); + } + p.point(pt.x * plotScaleX, pt.y * plotScaleY); + } + } + } + function drawTrajectories() { + for (const track of appState.vizData.tracks) { + const logs = track.historyLog.filter(log => log.frameIdx <= appState.currentFrame + 1); + if (logs.length < 2) continue; + + const lastLog = logs[logs.length - 1]; + if (appState.currentFrame + 1 - lastLog.frameIdx > MAX_TRAJECTORY_LENGTH) continue; + + // Determine the state from the most recent log entry + const isCurrentlyStationary = lastLog.isStationary; + + // Set max trajectory length based on state + let maxLen = MAX_TRAJECTORY_LENGTH; + if (isCurrentlyStationary) { + maxLen = Math.floor(MAX_TRAJECTORY_LENGTH / 4); + } + + let trajPts = logs.filter(log => log.correctedPosition && log.correctedPosition[0] !== null).map(log => log.correctedPosition); + if (trajPts.length > maxLen) { + trajPts = trajPts.slice(trajPts.length - maxLen); + } + + p.push(); + p.noFill(); + + // Apply different styles based on the stationary state + if (isCurrentlyStationary) { + // Style for STATIONARY tracks: thin, dashed, green + p.stroke(34, 139, 34, 220); // A darker, forest green + p.strokeWeight(1); + p.drawingContext.setLineDash([3, 3]); // Small dashes + } else { + // Style for MOVING tracks: default blue + const isDark = document.documentElement.classList.contains('dark'); + if (isDark) { + p.stroke(10, 170, 255, 250); + } else { + p.stroke(0, 50, 255, 250); + } + p.strokeWeight(1.5); + // No dash for solid line + } + + p.beginShape(); + for (const pos of trajPts) p.vertex(pos[0] * plotScaleX, pos[1] * plotScaleY); + p.endShape(); + + // IMPORTANT: Reset the drawing context to avoid affecting other elements + p.drawingContext.setLineDash([]); + p.pop(); + } + } + function drawTrackMarkers() { + const showDetails = toggleVelocity.checked; + const useStationary = toggleStationaryColor.checked; + const textColor = document.documentElement.classList.contains('dark') ? p.color(255) : p.color(0); + + for (const track of appState.vizData.tracks) { + const log = track.historyLog.find(log => log.frameIdx === appState.currentFrame + 1); + if (log) { + const pos = (log.correctedPosition && log.correctedPosition[0] !== null) ? log.correctedPosition : log.predictedPosition; + if (pos && pos.length === 2 && pos[0] !== null && pos[1] !== null) { + + // --- START OF CORRECTED LOGIC --- + + const size = 5, x = pos[0] * plotScaleX, y = pos[1] * plotScaleY; + let velocityColor = p.color(255, 0, 255, 200); // Default velocity color + + p.push(); + p.strokeWeight(2); + + // Check conditions and draw the correct marker + if (useStationary && log.isStationary === true) { + // 1. Stationary object with toggle ON + p.stroke(stationaryColor); // Use yellow + p.noFill(); + p.rectMode(p.CENTER); + p.square(x, y, size * 1.5); + velocityColor = stationaryColor; + } else { + // 2. Moving object OR toggle OFF + let markerColor = p.color(0, 0, 255); // Default blue + if (useStationary && log.isStationary === false) { + markerColor = movingColor; // Magenta if toggle is on + velocityColor = movingColor; + } + p.stroke(markerColor); + p.line(x - size, y, x + size, y); + p.line(x, y - size, x, y + size); + } + p.pop(); + + // --- END OF CORRECTED LOGIC --- + + if (showDetails && log.predictedVelocity && log.predictedVelocity[0] !== null) { + const [vx, vy] = log.predictedVelocity; + + // Only draw velocity line if object is NOT stationary + if (log.isStationary === false) { + p.push(); + p.stroke(velocityColor); + p.strokeWeight(2); + p.line(pos[0] * plotScaleX, pos[1] * plotScaleY, (pos[0] + vx) * plotScaleX, (pos[1] + vy) * plotScaleY); + p.pop(); + } + + const speed = (p.sqrt(vx * vx + vy * vy) * 3.6).toFixed(1); + const ttc = (log.ttc !== null && isFinite(log.ttc) && log.ttc < 100) ? `TTC: ${log.ttc.toFixed(1)}s` : ''; + const text = `ID: ${track.id} | ${speed} km/h\n${ttc}`; + p.push(); + p.fill(textColor); + p.noStroke(); + p.scale(1, -1); + p.textSize(12); + p.text(text, pos[0] * plotScaleX + 10, -pos[1] * plotScaleY); + p.pop(); + } + } + } + } + } + + function handleCloseUpDisplay() { + const frameData = appState.vizData.radarFrames[appState.currentFrame]; + if (!frameData || !frameData.pointCloud) return; + + const hoveredPoints = []; + const radius = 10; + + for (const pt of frameData.pointCloud) { + if (pt.x === null || pt.y === null) continue; + const screenX = (pt.x * plotScaleX) + p.width / 2; + const screenY = p.height * 0.95 - (pt.y * plotScaleY); + const d = p.dist(p.mouseX, p.mouseY, screenX, screenY); + if (d < radius) { + hoveredPoints.push({ point: pt, screenX: screenX, screenY: screenY }); + } + } + + if (hoveredPoints.length > 0) { + hoveredPoints.sort((a, b) => a.screenY - b.screenY); + + p.push(); + p.textSize(12); + const lineHeight = 15; + const boxPadding = 8; + let boxWidth = 0; + const infoStrings = []; + + 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 infoText = `X:${pt.x.toFixed(2)}, Y:${pt.y.toFixed(2)} | V:${vel}, SNR:${snr}`; + infoStrings.push(infoText); + boxWidth = Math.max(boxWidth, p.textWidth(infoText)); + } + + const boxHeight = (infoStrings.length * lineHeight) + (boxPadding * 2); + boxWidth += (boxPadding * 2); + + 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); + + const highlightColor = p.color(46, 204, 113); + + 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); + } + + const bgColor = document.documentElement.classList.contains('dark') ? p.color(20, 20, 30, 255) : p.color(245, 245, 245, 255); + p.fill(bgColor); + p.stroke(highlightColor); + p.strokeWeight(1); + p.rect(boxX, boxY, boxWidth, boxHeight, 4); + + const textColor = document.documentElement.classList.contains('dark') ? p.color(230) : p.color(20); + p.fill(textColor); + p.noStroke(); + 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.pop(); + } + } + + p.drawSnrLegendToBuffer = function (minV, maxV) { const b = snrLegendBuffer; b.clear(); b.push(); const lx = 10, ly = 20, lw = 15, lh = 400; for (let i = 0; i < lh; i++) { const amt = b.map(i, 0, lh, 1, 0); let c; if (amt < 0.25) c = b.lerpColor(snrColors.c1, snrColors.c2, amt / 0.25); else if (amt < 0.5) c = b.lerpColor(snrColors.c2, snrColors.c3, (amt - 0.25) / 0.25); else if (amt < 0.75) c = b.lerpColor(snrColors.c3, snrColors.c4, (amt - 0.5) / 0.25); else c = b.lerpColor(snrColors.c4, snrColors.c5, (amt - 0.75) / 0.25); b.stroke(c); b.line(lx, ly + i, lx + lw, ly + i); } b.fill(0); b.noStroke(); b.textSize(10); b.textAlign(b.LEFT, b.CENTER); b.text(maxV.toFixed(1), lx + lw + 5, ly); b.text(minV.toFixed(1), lx + lw + 5, ly + lh); b.text("SNR", lx, ly - 10); b.pop(); }; + p.windowResized = function () { + p.resizeCanvas(canvasContainer.offsetWidth, canvasContainer.offsetHeight); + // Instead of resizing the buffer, we re-create it + staticBackgroundBuffer = p.createGraphics(p.width, p.height); + // And we must re-draw the static content to the new buffer + calculatePlotScales(); + drawStaticRegionsToBuffer(); + if (appState.vizData) p.redraw(); + }; + }; \ No newline at end of file diff --git a/steps/src/p5/speedGraphSketch.js b/steps/src/p5/speedGraphSketch.js new file mode 100644 index 0000000..6ed8ce3 --- /dev/null +++ b/steps/src/p5/speedGraphSketch.js @@ -0,0 +1,170 @@ +//---Import APPSTATE VIDEOPLAYER and FindLastCanIndex---// + +import { appState + +} from '../state.js'; + +import { videoPlayer, speedGraphContainer + +} from '../dom.js'; + +import { findLastCanIndexBefore + +} from '../utils.js'; + + +export const speedGraphSketch = function (p) { + let staticBuffer, minSpeed, maxSpeed, videoDuration; + const pad = { top: 20, right: 130, bottom: 30, left: 50 }; + + // This function is now attached to the p5 instance, making it public + // It's responsible for drawing the static background and data lines + p.drawStaticGraphToBuffer = function (canSpeedData, radarData) { + const b = staticBuffer; + b.clear(); + const isDark = document.documentElement.classList.contains('dark'); + b.background(isDark ? [55, 65, 81] : 255); + const gridColor = isDark ? 100 : 200; + const textColor = isDark ? 200 : 100; + + b.push(); + b.stroke(gridColor); + b.strokeWeight(1); + b.line(pad.left, pad.top, pad.left, b.height - pad.bottom); + b.line(pad.left, b.height - pad.bottom, b.width - pad.right, b.height - pad.bottom); + b.textAlign(b.RIGHT, b.CENTER); + b.noStroke(); + b.fill(textColor); + b.textSize(10); + for (let s = minSpeed; s <= maxSpeed; s += 10) { + const y = b.map(s, minSpeed, maxSpeed, b.height - pad.bottom, pad.top); + b.text(s, pad.left - 8, y); + if (s === 0) { + b.strokeWeight(1.5); + b.stroke(isDark ? 150 : 180); + } else { + b.strokeWeight(1); + b.stroke(isDark ? 80 : 230); + } + b.line(pad.left + 1, y, b.width - pad.right, y); + b.noStroke(); + } + + b.fill(textColor); + b.text("km/h", pad.left - 8, pad.top - 8); + b.textAlign(b.CENTER, b.TOP); + b.noStroke(); + b.fill(isDark ? 180 : 150); + const tInt = Math.max(1, Math.floor(videoDuration / 10)); + for (let t = 0; t <= videoDuration; t += tInt) { const x = b.map(t, 0, videoDuration, pad.left, b.width - pad.right); b.text(Math.round(t), x, b.height - pad.bottom + 5); } + b.fill(textColor); + b.text("Time (s)", b.width / 2, b.height - pad.bottom + 18); + b.pop(); + + if (canSpeedData && canSpeedData.length > 0) { + b.noFill(); + b.stroke(0, 150, 255); + b.strokeWeight(1.5); + b.beginShape(); + for (const d of canSpeedData) { const relTime = (d.time - appState.videoStartDate.getTime()) / 1000; if (relTime >= 0 && relTime <= videoDuration) { const x = b.map(relTime, 0, videoDuration, pad.left, b.width - pad.right); const y = b.map(d.speed, minSpeed, maxSpeed, b.height - pad.bottom, pad.top); b.vertex(x, y); } } + b.endShape(); + } + + if (radarData && radarData.radarFrames) { + b.stroke(0, 200, 100); + b.drawingContext.setLineDash([5, 5]); + b.beginShape(); + for (const frame of radarData.radarFrames) { + const relTime = frame.timestamp / 1000; + if (relTime >= 0 && relTime <= videoDuration) { + const x = b.map(relTime, 0, videoDuration, pad.left, b.width - pad.right); + const egoSpeedKmh = frame.egoVelocity[1] * 3.6; + const y = b.map(egoSpeedKmh, minSpeed, maxSpeed, b.height - pad.bottom, pad.top); + b.vertex(x, y); + } + } + b.endShape(); + b.drawingContext.setLineDash([]); + } + + b.push(); + b.strokeWeight(2); + b.noStroke(); + b.fill(textColor); + b.textAlign(b.LEFT, b.CENTER); + b.stroke(0, 150, 255); + b.line(b.width - 120, pad.top + 10, b.width - 100, pad.top + 10); + b.noStroke(); + b.text("CAN Speed", b.width - 95, pad.top + 10); + b.stroke(0, 200, 100); + b.drawingContext.setLineDash([3, 3]); + b.line(b.width - 120, pad.top + 30, b.width - 100, pad.top + 30); + b.drawingContext.setLineDash([]); + b.noStroke(); + b.text("Ego Speed", b.width - 95, pad.top + 30); + b.pop(); + }; + + p.setup = function () { + let canvas = p.createCanvas(speedGraphContainer.offsetWidth, speedGraphContainer.offsetHeight); + canvas.parent('speed-graph-container'); + staticBuffer = p.createGraphics(p.width, p.height); + p.noLoop(); + }; + + p.setData = function (canSpeedData, radarData, duration) { + if ((!canSpeedData || canSpeedData.length === 0) && !radarData) return; + videoDuration = duration; + + let speeds = []; + if (canSpeedData) { + speeds.push(...canSpeedData.map(d => parseFloat(d.speed))); + } + if (radarData && radarData.radarFrames) { + const egoSpeeds = radarData.radarFrames.map(frame => frame.egoVelocity[1] * 3.6); + speeds.push(...egoSpeeds); + } + + minSpeed = speeds.length > 0 ? Math.floor(Math.min(...speeds) / 10) * 10 : 0; + maxSpeed = speeds.length > 0 ? Math.ceil(Math.max(...speeds) / 10) * 10 : 10; + if (maxSpeed <= 0) maxSpeed = 10; + if (minSpeed >= 0) minSpeed = 0; + + p.drawStaticGraphToBuffer(canSpeedData, radarData); + p.redraw(); + }; + + p.draw = function () { + if (!videoDuration) return; + p.image(staticBuffer, 0, 0); + drawTimeIndicator(); + }; + + function drawTimeIndicator() { + const currentTime = videoPlayer.currentTime; + const x = p.map(currentTime, 0, videoDuration, pad.left, p.width - pad.right); + p.stroke(255, 0, 0, 150); + p.strokeWeight(1.5); + p.line(x, pad.top, x, p.height - pad.bottom); + const videoAbsTimeMs = appState.videoStartDate.getTime() + (currentTime * 1000); + const canIndex = findLastCanIndexBefore(videoAbsTimeMs, appState.canData); + if (canIndex !== -1) { + const canMsg = appState.canData[canIndex]; + const y = p.map(canMsg.speed, minSpeed, maxSpeed, p.height - pad.bottom, pad.top); + p.fill(255, 0, 0); + p.noStroke(); + p.ellipse(x, y, 8, 8); + } + } + + p.windowResized = function () { + p.resizeCanvas(speedGraphContainer.offsetWidth, speedGraphContainer.offsetHeight); + // Instead of resizing the buffer, we re-create it + staticBuffer = p.createGraphics(p.width, p.height); + // And we must re-draw the static content to the new buffer + if ((appState.canData.length > 0 || appState.vizData) && videoDuration) { + p.drawStaticGraphToBuffer(appState.canData, appState.vizData); + } + p.redraw(); + }; + }; \ No newline at end of file