diff --git a/steps/src/dataExplorer.js b/steps/src/dataExplorer.js index 69e8f42..23bf8d9 100644 --- a/steps/src/dataExplorer.js +++ b/steps/src/dataExplorer.js @@ -1,8 +1,12 @@ // In src/dataExplorer.js import { appState } from './state.js'; +import { + canvasContainer, + explorerBtn +} from './dom.js'; // Import the DOM elements we need to listen to -// --- DOM Elements --- +// --- DOM Elements (Internal to this module) --- const panel = document.getElementById('data-explorer-panel'); const closeBtn = document.getElementById('close-explorer-btn'); const footer = document.getElementById('explorer-footer'); @@ -17,7 +21,8 @@ const tabs = { const gridDiv = document.getElementById('data-grid'); const chartCanvas = document.getElementById('data-chart'); -let gridApi = null; // This will hold the grid's API +// --- Module-Local State --- +let gridApi = null; let chartInstance = null; let currentGridData = null; @@ -30,7 +35,6 @@ const gridOptions = { filter: true, resizable: true, }, - // The onGridReady callback is no longer needed with the new createGrid method. }; // --- Chart.js Configuration --- @@ -56,7 +60,16 @@ function createChart(data, label) { }); } -// --- Core Functions --- +// --- Core Functions (Internal) --- + +function showExplorer() { + panel.classList.remove('hidden'); + updateExplorer(); +} + +function hideExplorer() { + panel.classList.add('hidden'); +} function switchTab(targetTab) { Object.values(tabs).forEach(tab => { @@ -81,19 +94,12 @@ function createTreeView(data) { return pre; } -export function showExplorer() { - panel.classList.remove('hidden'); - updateExplorer(); -} - -export function hideExplorer() { - panel.classList.add('hidden'); -} - -export function updateExplorer() { - if (panel.classList.contains('hidden')) return; - const frame = appState.vizData?.radarFrames[appState.currentFrame]; +function updateExplorer() { + if (panel.classList.contains('hidden') || !appState.vizData) return; + + const frame = appState.vizData.radarFrames[appState.currentFrame]; if (!frame) return; + tabs.tree.panel.innerHTML = ''; tabs.tree.panel.appendChild(createTreeView({ currentFrame: appState.currentFrame, @@ -101,11 +107,17 @@ export function updateExplorer() { })); } -export function displayInGrid(data, title) { +function displayInGrid(data, title) { if (!data || data.length === 0 || !gridApi) return; currentGridData = data; - const columns = Object.keys(data[0]).map(key => ({ field: key })); + // Auto-generate columns from the first data object + const columns = Object.keys(data[0]).map(key => ({ + field: key, + headerName: key, // Set header name + sortable: true, // Ensure all generated columns are sortable + filter: true, // Ensure all generated columns are filterable + })); gridApi.setGridOption('columnDefs', columns); gridApi.setGridOption('rowData', data); @@ -114,35 +126,84 @@ export function displayInGrid(data, title) { switchTab('grid'); } -// --- Event Listeners --- -closeBtn.addEventListener('click', hideExplorer); - -Object.keys(tabs).forEach(key => { - tabs[key].btn.addEventListener('click', () => switchTab(key)); -}); +// --- Initialization Function (The file's only export) --- -plotBtn.addEventListener('click', () => { - // --- START: MODIFIED LOGIC --- - const focusedCell = gridApi.getFocusedCell(); - if (!focusedCell) { - alert("Please click a column header to select it for plotting."); - return; +export function initializeDataExplorer() { + // Initialize the grid + if (!gridApi) { + gridApi = agGrid.createGrid(gridDiv, gridOptions); } - - const colId = focusedCell.column.getColId(); - // --- END: MODIFIED LOGIC --- - const plotData = currentGridData.map(row => row[colId]).filter(val => typeof val === 'number'); + // --- Wire up all event listeners --- - if (plotData.length > 0) { - createChart(plotData, colId); - switchTab('plot'); - } else { - alert("The selected column contains no numeric data to plot."); - } -}); + // Toggle panel visibility + explorerBtn.addEventListener('click', () => { + if (panel.classList.contains('hidden')) { + showExplorer(); + } else { + hideExplorer(); + } + }); + closeBtn.addEventListener('click', hideExplorer); -// --- START: THIS IS THE MAIN FIX --- -// Initialize the grid using the new createGrid method and store its API. -gridApi = agGrid.createGrid(gridDiv, gridOptions); -// --- END: THIS IS THE MAIN FIX --- \ No newline at end of file + // Tab switching + Object.keys(tabs).forEach(key => { + tabs[key].btn.addEventListener('click', () => switchTab(key)); + }); + + // Plot button + plotBtn.addEventListener('click', () => { + // Fix: Use onColumnHeaderClicked or get column from focused cell + const focusedCell = gridApi.getFocusedCell(); + if (!focusedCell) { + alert("Please click a cell in the column you wish to plot."); + return; + } + + const colId = focusedCell.column.getColId(); + const plotData = currentGridData.map(row => row[colId]).filter(val => typeof val === 'number'); + + if (plotData.length > 0) { + createChart(plotData, colId); + switchTab('plot'); + } else { + alert("The selected column contains no numeric data to plot."); + } + }); + + // Main canvas click listener + canvasContainer.addEventListener('click', () => { + if (!appState.vizData) return; + + const currentFrameData = appState.vizData.radarFrames[appState.currentFrame]; + if (currentFrameData && currentFrameData.pointCloud) { + // Send point cloud data to the grid + displayInGrid(currentFrameData.pointCloud, `Frame ${appState.currentFrame} - Point Cloud`); + // Show the explorer if it's hidden + if (panel.classList.contains('hidden')) { + showExplorer(); + } + } + }); + + // Keyboard shortcut listener + document.addEventListener("keydown", (event) => { + // Ignore if typing in an input + const isTextInputFocused = + event.target.tagName === "INPUT" && + (event.target.type === "text" || event.target.type === "number"); + if (isTextInputFocused) { + return; + } + + // Toggle explorer with 'i' key + if (event.key === "i") { + event.preventDefault(); + if (panel.classList.contains("hidden")) { + showExplorer(); + } else { + hideExplorer(); + } + } + }); +} \ No newline at end of file diff --git a/steps/src/main.js b/steps/src/main.js index eab1650..052cfd2 100644 --- a/steps/src/main.js +++ b/steps/src/main.js @@ -17,7 +17,8 @@ // =========================================================================================================== import { zoomSketch } from "./p5/zoomSketch.js"; -import { showExplorer, hideExplorer, displayInGrid } from "./dataExplorer.js"; +//import { showExplorer, hideExplorer, displayInGrid } from "./dataExplorer.js"; +import { initializeDataExplorer } from "./dataExplorer.js"; // <-- ADD THIS import { showModal, hideModal, @@ -103,7 +104,7 @@ import { menuScrim, toggleConfirmedOnly, resetUIForNewLoad, - explorerBtn, + //explorerBtn, } from "./dom.js"; import { initializeTheme } from "./theme.js"; @@ -946,7 +947,7 @@ document.addEventListener("keydown", (event) => { "m", "q", "c", - "i", + //"i", ]; if (!appState.vizData || !recognizedKeys.includes(key)) { @@ -1013,14 +1014,14 @@ document.addEventListener("keydown", (event) => { appState.p5_instance.redraw(); } } - if (key === "i") { + /* if (key === "i") { const panel = document.getElementById("data-explorer-panel"); if (panel.classList.contains("hidden")) { showExplorer(); } else { hideExplorer(); } - } + } */ if (key === "p") { togglePredictedPos.click(); appState.p5_instance.redraw(); @@ -1051,7 +1052,7 @@ document.addEventListener("keydown", (event) => { } }); -canvasContainer.addEventListener('click', () => { +/* canvasContainer.addEventListener('click', () => { if (!appState.vizData) return; // For this example, let's just send the pointCloud of the current frame to the grid. @@ -1061,8 +1062,8 @@ canvasContainer.addEventListener('click', () => { if (currentFrameData && currentFrameData.pointCloud) { displayInGrid(currentFrameData.pointCloud, `Frame ${appState.currentFrame} - Point Cloud`); } -}); -explorerBtn.addEventListener('click', () => { +}); */ +/* explorerBtn.addEventListener('click', () => { const panel = document.getElementById("data-explorer-panel"); if (panel.classList.contains("hidden")) { showExplorer(); @@ -1070,7 +1071,7 @@ explorerBtn.addEventListener('click', () => { hideExplorer(); } }); - + */ function calculateAndSetOffset() { const jsonTimestampInfo = extractTimestampInfo(appState.jsonFilename); const videoTimestampInfo = extractTimestampInfo(appState.videoFilename); @@ -1106,6 +1107,7 @@ function calculateAndSetOffset() { // --- [START] CORRECTED INITIALIZATION LOGIC --- document.addEventListener("DOMContentLoaded", () => { initializeTheme(); + initializeDataExplorer(); // <-- ADD THIS LINE initDB(async () => { console.log("Database initialized. Checking for cached session...");