0 && staticBackgroundBuffer.height > 0) {
+ p.image(staticBackgroundBuffer, 0, 0);
+ }
// Apply transformations for radar coordinate system (origin at bottom-center, Y-axis inverted)
p.push();
@@ -365,7 +367,7 @@ export const radarSketch = function (p) {
// 4. Draw the new legend buffer onto the main canvas
// This is placed at the bottom-right corner.
- if (toggleTracks.checked && !appState.isRawOnlyMode) {
+ if (toggleTracks.checked && !appState.isRawOnlyMode && trackLegendBuffer && trackLegendBuffer.width > 0) {
p.image(
trackLegendBuffer,
p.width - trackLegendBuffer.width - 10,
@@ -537,8 +539,8 @@ export const radarSketch = function (p) {
isFirstFrame = true; // Reset for the next time zoom mode is enabled
}
// --- Legend Drawing ---
- // Draw the SNR legend if enabled
- if (toggleSnrColor.checked) {
+ // Draw the legend buffer if requested
+ if (toggleSnrColor.checked && snrLegendBuffer && snrLegendBuffer.width > 0) {
p.image(snrLegendBuffer, 10, p.height - snrLegendBuffer.height - 10);
}
};
diff --git a/steps/src/p5/speedGraphSketch.js b/steps/src/p5/speedGraphSketch.js
index 7a71fa8..f5fb3da 100644
--- a/steps/src/p5/speedGraphSketch.js
+++ b/steps/src/p5/speedGraphSketch.js
@@ -459,7 +459,9 @@ export const speedGraphSketch = function (p) {
p.text("No data to display", p.width / 2, p.height / 2);
return;
}
- p.image(staticBuffer, 0, 0);
+ if (staticBuffer && staticBuffer.width > 0 && staticBuffer.height > 0) {
+ p.image(staticBuffer, 0, 0);
+ }
drawTimeIndicator();
// draw hover vertical line and tooltip if applicable
diff --git a/steps/src/p5/zoomSketch.js b/steps/src/p5/zoomSketch.js
index 032aefe..cc6205c 100644
--- a/steps/src/p5/zoomSketch.js
+++ b/steps/src/p5/zoomSketch.js
@@ -325,7 +325,8 @@ export const zoomSketch = function (p) {
p.scale(appState.zoomFactor);
// --- Redraw the scene from scratch ---
- if (appState.p5_instance && appState.p5_instance.getStaticBackground) {
+ // Performance fix: Check if source has valid dimensions before drawing
+ if (appState.p5_instance && appState.p5_instance.width > 0 && appState.p5_instance.height > 0 && appState.p5_instance.getStaticBackground) {
const bg = appState.p5_instance.getStaticBackground();
// Optimization: Only draw the visible slice of the background
// Drawing the full 1920x1080 texture every frame is expensive if we only see a tiny part.
@@ -345,7 +346,7 @@ export const zoomSketch = function (p) {
const dW = Math.min(imgW, sX + visibleW) - dX;
const dH = Math.min(imgH, sY + visibleH) - dY;
- if (dW > 0 && dH > 0) {
+ if (dW > 0 && dH > 0 && bg.width > 0 && bg.height > 0) {
// Draw only the visible sub-rectangle
// Since we are transformed to World Space, destination (dx,dy) matches source (dx,dy)
p.image(bg, dX, dY, dW, dH, dX, dY, dW, dH);
diff --git a/steps/src/ui.js b/steps/src/ui.js
index 4e9d570..20cf1dc 100644
--- a/steps/src/ui.js
+++ b/steps/src/ui.js
@@ -62,6 +62,50 @@ export function makeDraggableAndResizable(panel, header, minWidth = 400, minHeig
let original_mouse_x = 0;
let original_mouse_y = 0;
+ // --- Persistence Logic ---
+ const storageKey = `panel_pos_${panel.id}`;
+
+ function savePosition() {
+ if (!panel.id) return;
+ const state = {
+ left: panel.style.left,
+ top: panel.style.top,
+ width: panel.style.width,
+ height: panel.style.height
+ };
+ console.log(`Saving position for ${panel.id}`, state);
+ localStorage.setItem(storageKey, JSON.stringify(state));
+ }
+
+ function loadPosition() {
+ if (!panel.id) return;
+ const saved = localStorage.getItem(storageKey);
+ if (saved) {
+ try {
+ const state = JSON.parse(saved);
+ console.log(`Loading position for ${panel.id}`, state);
+ if (state.left) panel.style.left = state.left;
+ if (state.top) panel.style.top = state.top;
+ if (state.width) panel.style.width = state.width;
+ if (state.height) panel.style.height = state.height;
+ // Ensure it's still in view
+ requestAnimationFrame(() => constrainToViewport());
+ } catch (e) { console.error(`Failed to load position for ${panel.id}`, e); }
+ } else {
+ console.log(`No saved position found for ${panel.id}`);
+ }
+ }
+
+ // --- Auto-Focus (Bring to Front) ---
+ panel.addEventListener('mousedown', () => {
+ document.querySelectorAll('#zoom-panel, #data-explorer-panel').forEach(p => {
+ p.style.zIndex = "30";
+ });
+ panel.style.zIndex = "40";
+ });
+
+ loadPosition();
+
// --- Dragging Logic ---
header.addEventListener('mousedown', (e) => {
// Prevent drag if clicking buttons
@@ -91,6 +135,7 @@ export function makeDraggableAndResizable(panel, header, minWidth = 400, minHeig
document.body.classList.remove('dragging');
window.removeEventListener('mousemove', dragPanel);
window.removeEventListener('mouseup', stopDrag);
+ savePosition();
}
// --- Resizing Logic ---
@@ -112,6 +157,7 @@ export function makeDraggableAndResizable(panel, header, minWidth = 400, minHeig
window.addEventListener('mouseup', () => {
document.body.classList.remove('resizing');
window.removeEventListener('mousemove', resizeFunc);
+ savePosition();
if (panel.id === 'zoom-panel' && appState.zoomSketchInstance) {
appState.zoomSketchInstance.handleContainerResize();
}
@@ -249,6 +295,39 @@ export function initUIEventListeners() {
animate: true,
handle: '.grid-stack-item-content > .cursor-grab',
});
+
+ // Load saved layout with a small delay to ensure DOM is ready
+ let isInitialLoad = true;
+ setTimeout(() => {
+ const savedLayout = localStorage.getItem('gridstack_layout');
+ if (savedLayout) {
+ try {
+ const layout = JSON.parse(savedLayout);
+ console.log("Restoring GridStack positions", layout);
+ // Use "soft load" to updates positions by id without replacing DOM
+ layout.forEach(item => {
+ const id = item.id || item.gsId;
+ if (id) {
+ const el = document.querySelector(`.grid-stack-item[gs-id="${id}"]`);
+ if (el) appState.gridStackInstance.update(el, { x: item.x, y: item.y, w: item.w, h: item.h });
+ }
+ });
+ } catch (e) { }
+ }
+ isInitialLoad = false;
+ }, 100);
+
+ // Save layout on changes
+ const saveGrid = () => {
+ if (isInitialLoad) return; // Don't save while loading
+ // save(true, false) saves all items with their current positions/sizes
+ const layout = appState.gridStackInstance.save(true, false);
+ console.log("Saving GridStack layout", layout);
+ localStorage.setItem('gridstack_layout', JSON.stringify(layout));
+ };
+ appState.gridStackInstance.on('change', saveGrid);
+ appState.gridStackInstance.on('dragstop', saveGrid);
+ appState.gridStackInstance.on('resizestop', saveGrid);
}
// --- Initialize Floating Zoom Panel ---