Browse Source

added a basic data explorer to the main working code. Use the Key (i) to launch it.

refactor/modularize
RUSHIL AMBARISH KADU 7 months ago
parent
commit
7407a9bf57
  1. 36
      steps/index.html
  2. 148
      steps/src/dataExplorer.js
  3. 28
      steps/src/main.js

36
steps/index.html

@ -8,6 +8,8 @@
<link rel="icon" type="image/png" sizes="32x32" href="favicon.png" />
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/oboe@2.1.5/dist/oboe-browser.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/ag-grid-community/dist/ag-grid-community.min.js"></script>
<script>
tailwind.config = {
darkMode: "class",
@ -147,7 +149,7 @@
id="toggle-debug2-overlay" class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500" /> Show
Advanced Debug (A)</label>
<label class="flex items-center gap-2 text-sm cursor-pointer"><input type="checkbox" id="toggle-close-up"
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500" /> GOD MODE </label>
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500" /> GOD MODE </label>
<label class="flex items-center gap-2 text-sm cursor-pointer"><input type="checkbox" id="toggle-predicted-pos"
class="form-checkbox h-4 w-4 text-blue-600 rounded focus:ring-blue-500" checked /> Show Predicted Position
(P)</label>
@ -262,6 +264,38 @@
</p>
</div>
</div>
<div id="data-explorer-panel"
class="hidden fixed bottom-24 right-4 bg-white dark:bg-gray-800 shadow-2xl rounded-lg z-30 flex flex-col w-full max-w-2xl h-1/2 border dark:border-gray-600">
<div class="flex items-center justify-between p-2 border-b dark:border-gray-700">
<h2 class="text-lg font-bold ml-2">Data Explorer</h2>
<button id="close-explorer-btn"
class="text-gray-500 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-lg p-1.5">&times;</button>
</div>
<div class="flex border-b dark:border-gray-700">
<button id="tab-btn-tree" class="px-4 py-2 text-sm font-medium border-b-2 border-blue-500">Tree View</button>
<button id="tab-btn-grid" class="px-4 py-2 text-sm font-medium text-gray-500 dark:text-gray-400">Grid
View</button>
<button id="tab-btn-plot" class="px-4 py-2 text-sm font-medium text-gray-500 dark:text-gray-400">Plot
View</button>
</div>
<div class="flex-grow p-2 overflow-auto">
<div id="tab-panel-tree" class="font-mono text-xs"></div>
<div id="tab-panel-grid" class="hidden h-full">
<div id="data-grid" class="ag-theme-alpine-dark h-full"></div>
</div>
<div id="tab-panel-plot" class="hidden p-2">
<canvas id="data-chart"></canvas>
</div>
</div>
<div id="explorer-footer" class="p-2 border-t dark:border-gray-700 text-right hidden">
<button id="plot-selected-btn"
class="bg-blue-600 text-white px-3 py-1 rounded-lg text-sm hover:bg-blue-700">Plot Selected Column</button>
</div>
</div>
</main>
<footer class="bg-white dark:bg-gray-800 shadow-up w-full p-4 mt-auto sticky bottom-0 z-20">

148
steps/src/dataExplorer.js

@ -0,0 +1,148 @@
// In src/dataExplorer.js
import { appState } from './state.js';
// --- DOM Elements ---
const panel = document.getElementById('data-explorer-panel');
const closeBtn = document.getElementById('close-explorer-btn');
const footer = document.getElementById('explorer-footer');
const plotBtn = document.getElementById('plot-selected-btn');
const tabs = {
tree: { btn: document.getElementById('tab-btn-tree'), panel: document.getElementById('tab-panel-tree') },
grid: { btn: document.getElementById('tab-btn-grid'), panel: document.getElementById('tab-panel-grid') },
plot: { btn: document.getElementById('tab-btn-plot'), panel: document.getElementById('tab-panel-plot') },
};
const gridDiv = document.getElementById('data-grid');
const chartCanvas = document.getElementById('data-chart');
let gridApi = null; // This will hold the grid's API
let chartInstance = null;
let currentGridData = null;
// --- AG Grid Configuration ---
const gridOptions = {
rowData: [],
columnDefs: [],
defaultColDef: {
sortable: true,
filter: true,
resizable: true,
},
// The onGridReady callback is no longer needed with the new createGrid method.
};
// --- Chart.js Configuration ---
function createChart(data, label) {
if (chartInstance) {
chartInstance.destroy();
}
chartInstance = new Chart(chartCanvas, {
type: 'line',
data: {
labels: data.map((_, i) => i),
datasets: [{
label: label,
data: data,
borderColor: 'rgba(75, 192, 192, 1)',
tension: 0.1,
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
}
});
}
// --- Core Functions ---
function switchTab(targetTab) {
Object.values(tabs).forEach(tab => {
tab.panel.classList.add('hidden');
tab.btn.classList.remove('border-blue-500');
tab.btn.classList.add('text-gray-500', 'dark:text-gray-400');
});
tabs[targetTab].panel.classList.remove('hidden');
tabs[targetTab].btn.classList.add('border-blue-500');
tabs[targetTab].btn.classList.remove('text-gray-500', 'dark:text-gray-400');
footer.classList.toggle('hidden', targetTab !== 'grid');
}
function createTreeView(data) {
const pre = document.createElement('pre');
pre.textContent = JSON.stringify(data, (key, value) => {
if (key.startsWith('_')) return undefined;
return value;
}, 2);
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];
if (!frame) return;
tabs.tree.panel.innerHTML = '';
tabs.tree.panel.appendChild(createTreeView({
currentFrame: appState.currentFrame,
frameData: frame
}));
}
export function displayInGrid(data, title) {
if (!data || data.length === 0 || !gridApi) return;
currentGridData = data;
const columns = Object.keys(data[0]).map(key => ({ field: key }));
gridApi.setGridOption('columnDefs', columns);
gridApi.setGridOption('rowData', data);
tabs.grid.btn.textContent = `Grid View: ${title}`;
switchTab('grid');
}
// --- Event Listeners ---
closeBtn.addEventListener('click', hideExplorer);
Object.keys(tabs).forEach(key => {
tabs[key].btn.addEventListener('click', () => switchTab(key));
});
plotBtn.addEventListener('click', () => {
// --- START: MODIFIED LOGIC ---
const focusedCell = gridApi.getFocusedCell();
if (!focusedCell) {
alert("Please click a column header to select it for plotting.");
return;
}
const colId = focusedCell.column.getColId();
// --- END: MODIFIED LOGIC ---
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.");
}
});
// --- 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 ---

28
steps/src/main.js

@ -17,6 +17,7 @@
// ===========================================================================================================
import { zoomSketch } from "./p5/zoomSketch.js";
import { showExplorer, hideExplorer, displayInGrid } from "./dataExplorer.js";
import {
showModal,
hideModal,
@ -336,7 +337,6 @@ function finalizeSetup(_parsedJsonData) {
if (!appState.speedGraphInstance) {
appState.speedGraphInstance = new p5(speedGraphSketch);
}
// The previous logic for setting the frame and redrawing was correct.
// It failed because the underlying timestamp data was wrong.
resetVisualization();
@ -945,6 +945,7 @@ document.addEventListener("keydown", (event) => {
"m",
"q",
"c",
"i",
];
if (!appState.vizData || !recognizedKeys.includes(key)) {
@ -1011,7 +1012,14 @@ document.addEventListener("keydown", (event) => {
appState.p5_instance.redraw();
}
}
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();
@ -1042,6 +1050,18 @@ document.addEventListener("keydown", (event) => {
}
});
canvasContainer.addEventListener('click', () => {
if (!appState.vizData) return;
// For this example, let's just send the pointCloud of the current frame to the grid.
// A more advanced version could detect if you clicked on a specific track.
const currentFrameData = appState.vizData.radarFrames[appState.currentFrame];
if (currentFrameData && currentFrameData.pointCloud) {
displayInGrid(currentFrameData.pointCloud, `Frame ${appState.currentFrame} - Point Cloud`);
}
});
function calculateAndSetOffset() {
const jsonTimestampInfo = extractTimestampInfo(appState.jsonFilename);
const videoTimestampInfo = extractTimestampInfo(appState.videoFilename);
@ -1050,8 +1070,8 @@ function calculateAndSetOffset() {
videoTimestampInfo.timestampStr,
videoTimestampInfo.format
);
if (appState.videoStartDate){
};
if (appState.videoStartDate) {
}
}
if (jsonTimestampInfo) {

Loading…
Cancel
Save