Browse Source

Added user manual to make things clear.

refactor/modularize
RUSHIL AMBARISH KADU 7 months ago
parent
commit
59893ba65b
  1. 489
      steps/User_Manual.html
  2. 88
      steps/index.html
  3. 419
      steps/src/drawUtils.js

489
steps/User_Manual.html

@ -0,0 +1,489 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Radar & Video Synchronizer - Infographic Manual</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;700;800&family=Roboto+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
body {
font-family: 'Inter', sans-serif;
background-color: #f8fafc; /* slate-50 */
color: #1e293b; /* slate-800 */
}
.font-mono { font-family: 'Roboto Mono', monospace; }
/* Infographic Palette */
:root {
--c-primary: #0284c7; /* sky-600 */
--c-primary-light: #f0f9ff; /* sky-50 */
--c-secondary: #059669; /* emerald-600 */
--c-accent: #db2777; /* pink-600 */
--c-dark: #334155; /* slate-700 */
--c-light: #f1f5f9; /* slate-100 */
--c-text: #374151; /* gray-700 */
--c-text-light: #64748b; /* slate-500 */
}
h1, h2, h3 { font-weight: 700; letter-spacing: -0.025em; color: var(--c-dark); }
h1 { font-size: 2.25rem; line-height: 2.5rem; font-weight: 800; }
h2 { font-size: 1.875rem; line-height: 2.25rem; border-bottom: 2px solid var(--c-light); padding-bottom: 0.5rem; }
h3 { font-size: 1.25rem; line-height: 1.75rem; color: var(--c-primary); }
.section-icon {
font-size: 2rem;
line-height: 1;
background-color: var(--c-primary-light);
color: var(--c-primary);
padding: 0.75rem;
border-radius: 0.5rem;
display: inline-block;
margin-bottom: 0.5rem;
}
.feature-card {
background-color: white;
border: 1px solid #e2e8f0; /* slate-200 */
border-radius: 0.75rem;
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05), 0 2px 4px -2px rgba(0,0,0,0.05);
transition: all 0.2s ease-in-out;
}
.feature-card:hover {
transform: translateY(-4px);
box-shadow: 0 10px 15px -3px rgba(0,0,0,0.07), 0 4px 6px -2px rgba(0,0,0,0.05);
border-color: var(--c-primary);
}
.feature-card-icon {
font-size: 1.875rem;
line-height: 2.25rem;
margin-right: 0.75rem;
}
.flow-step {
background-color: white;
border: 2px solid var(--c-primary);
color: var(--c-dark);
border-radius: 0.5rem;
padding: 1rem;
text-align: center;
font-weight: 500;
box-shadow: 0 4px 6px -1px rgba(0,0,0,0.05);
}
.flow-arrow {
color: var(--c-primary);
font-size: 2.25rem;
line-height: 2.5rem;
font-weight: 200;
margin: 0.5rem 0;
}
/* Keyboard Shortcut Styling */
kbd {
background-color: #e2e8f0; /* slate-200 */
border: 1px solid #cbd5e1; /* slate-300 */
border-bottom-width: 2px;
color: #334155; /* slate-700 */
border-radius: 0.375rem;
padding: 0.125rem 0.5rem;
font-family: 'Roboto Mono', monospace;
font-size: 0.875rem;
line-height: 1.25rem;
font-weight: 500;
display: inline-block;
margin: 0 0.125rem;
}
/* File Tree Styling */
.file-tree { list-style: none; padding-left: 0; }
.file-tree li { margin-bottom: 0.25rem; padding-left: 1.75rem; position: relative; font-family: 'Roboto Mono', monospace; color: var(--c-text); }
.file-tree .folder::before { content: '📁'; position: absolute; left: 0; color: #facc15; } /* yellow-400 */
.file-tree .file::before { content: '📄'; position: absolute; left: 0; color: #60a5fa; } /* blue-400 */
/* Wireframe Annotation Styling */
.wireframe-box {
background-color: var(--c-light);
border: 2px dashed #94a3b8; /* slate-400 */
border-radius: 0.5rem;
display: flex;
align-items: center;
justify-content: center;
color: #64748b; /* slate-500 */
font-weight: 500;
}
.annotation {
position: absolute;
background-color: white;
border: 1px solid var(--c-dark);
border-radius: 0.375rem;
padding: 0.5rem 0.75rem;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
z-index: 10;
}
.annotation p {
font-size: 0.875rem;
line-height: 1.25rem;
color: var(--c-text);
}
.annotation strong {
color: var(--c-dark);
}
.annotation code {
font-size: 0.75rem;
line-height: 1rem;
background-color: var(--c-light);
padding: 0.125rem 0.375rem;
border-radius: 0.25rem;
color: var(--c-accent);
}
@media (max-width: 768px) {
.flow-diagram { flex-direction: column; }
.flow-arrow { transform: rotate(90deg); }
}
</style>
</head>
<body class="antialiased">
<div class="container mx-auto px-4 py-12 max-w-6xl space-y-16">
<!-- 1. Hero Section -->
<section class="text-center">
<div class="section-icon">📡️ 🎬</div>
<h1 class="text-4xl md:text-5xl">Radar & Video Synchronizer</h1>
<p class="text-xl text-slate-600 max-w-3xl mx-auto mt-4">
An infographic user manual for the high-precision visualization tool.
</p>
</section>
<!-- 2. How to Use (30-Second Guide) -->
<section>
<h2 class="text-center mb-8">How to Use (The 30-Second Guide)</h2>
<div class="flow-diagram flex flex-col md:flex-row items-stretch justify-center gap-4 md:gap-8">
<div class="flow-step flex-1">
<span class="text-3xl">1️⃣</span>
<h3 class="mt-2">Load Files</h3>
<p class="text-sm text-slate-600">Drag & drop your <kbd>.json</kbd> and <kbd>.mp4</kbd> files onto the window, or use the <kbd>Load JSON</kbd> / <kbd>Load Video</kbd> buttons.</p>
</div>
<div class="flow-arrow self-center"></div>
<div class="flow-step flex-1">
<span class="text-3xl">2️⃣</span>
<h3 class="mt-2">Play & Sync</h3>
<p class="text-sm text-slate-600">Press <kbd>Play</kbd> (or <kbd>Spacebar</kbd>). The app automatically syncs the radar data to the video based on their timestamps.</p>
</div>
<div class="flow-arrow self-center"></div>
<div class="flow-step flex-1">
<span class="text-3xl">3️⃣</span>
<h3 class="mt-2">Explore & Analyze</h3>
<p class="text-sm text-slate-600">Use the timeline, sidebar (<kbd>M</kbd>), zoom (<kbd>G</kbd>), and Data Explorer (<kbd>I</kbd>) to analyze the synchronized data.</p>
</div>
</div>
</section>
<!-- 3. The Main Interface (VISUAL) -->
<section>
<h2 class="text-center mb-8">The Main Interface</h2>
<div class="relative p-8 bg-white rounded-lg shadow-lg border border-slate-200">
<!-- Main Layout Wireframe -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 h-[600px]">
<div class="wireframe-box h-[500px] lg:h-full">Main Radar Canvas</div>
<div class="flex flex-col gap-6">
<div class="wireframe-box h-[300px]">Video Player</div>
<div class="wireframe-box flex-1">Speed Graph</div>
</div>
</div>
<div class="wireframe-box h-32 mt-6">Control Bar & Timeline</div>
<!-- Annotations -->
<!-- Radar Canvas -->
<div class="annotation" style="top: 8rem; left: 10rem;">
<strong>1. Radar Canvas</strong>
<p>Main p5.js visualization. Shows:<br>
- Point Clouds (Points)<br>
- Object Trajectories (Lines)<br>
- Track Markers (+)<br>
- Ego Vehicle (Box at bottom)
</p>
<p class="mt-1">Overlays (top-left):<br>
<code>Frame: 123</code> <code>Time: 12:30:01.456</code><br>
<code>Mode: Color by SNR</code> <code>Drift: -15ms</code>
</p>
</div>
<!-- Video Player -->
<div class="annotation" style="top: 8rem; right: 8rem;">
<strong>2. Video Player</strong>
<p>Shows the synchronized video feed.<br>
Overlays (top-left):<br>
<code>Frame: 110</code> <code>Time: 12:30:01.440</code>
</p>
</div>
<!-- Speed Graph -->
<div class="annotation" style="bottom: 17rem; right: 9rem;">
<strong>3. Speed Graph</strong>
<p>Shows Ego Speed vs. CAN Speed over time.<br>
A red line indicates the current playback position.
</p>
</div>
<!-- Control Bar -->
<div class="annotation" style="bottom: 5rem; left: 25%;">
<strong>4. Control Bar & Timeline</strong>
<p>Main controls for playback.<br>
- <kbd>Play</kbd> / <kbd>Pause</kbd> (<kbd>Space</kbd>), <kbd>Stop</kbd><br>
- Timeline slider (Click, drag, or <kbd>Scroll Wheel</kbd> to seek)<br>
- Playback <kbd>Speed</kbd> slider
</p>
</div>
<!-- Sidebar Toggle -->
<div class="annotation" style="top: 2rem; left: 1rem;">
<strong>Sidebar</strong>
<p>Click ☰ (or <kbd>M</kbd>)<br>to open all toggles.</p>
</div>
<!-- Data Explorer Toggle -->
<div class="annotation" style="top: 2rem; right: 1rem;">
<strong>Data Explorer</strong>
<p>Click ⛶ (or <kbd>I</kbd>)<br>to open data inspector.</p>
</div>
</div>
</section>
<!-- 4. Key Features & Controls -->
<section>
<h2 class="text-center mb-8">Key Features & Controls</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<!-- Feature: Loading -->
<div class="feature-card p-6">
<div class="flex items-center">
<span class="feature-card-icon">🖱️</span>
<h3>File Loading & Caching</h3>
</div>
<p class="text-sm text-slate-600 mt-2">
Load <kbd>.json</kbd> and <kbd>.mp4</kbd> files via <strong>Drag & Drop</strong> anywhere on the page or by using the <kbd>Load</kbd> buttons. Files are cached in `IndexedDB` for instant reloads.
</p>
</div>
<!-- Feature: Playback -->
<div class="feature-card p-6">
<div class="flex items-center">
<span class="feature-card-icon">⏱️</span>
<h3>High-Precision Sync</h3>
</div>
<p class="text-sm text-slate-600 mt-2">
The app uses a `performance.now()` master clock to drive playback, correcting any video drift. The <strong>time offset</strong> is automatically calculated from filenames (e.g., `fHist_...` and `WIN_...`).
</p>
</div>
<!-- Feature: Timeline -->
<div class="feature-card p-6">
<div class="flex items-center">
<span class="feature-card-icon">slider</span>
<h3>Advanced Timeline Control</h3>
</div>
<p class="text-sm text-slate-600 mt-2">
Click or drag the timeline to seek. You can also hover your <strong>mouse wheel</strong> over the slider to seek frame-by-frame (slow scroll) or scrub quickly (fast scroll).
</p>
</div>
<!-- Feature: Sidebar -->
<div class="feature-card p-6">
<div class="flex items-center">
<span class="feature-card-icon">🎨</span>
<h3>Display Settings Sidebar (<kbd>M</kbd>)</h3>
</div>
<p class="text-sm text-slate-600 mt-2">
Toggle visibility of: <strong>Tracks</strong> (<kbd>T</kbd>), <strong>Details</strong> (<kbd>D</kbd>), <strong>Predicted Pos</strong> (<kbd>P</kbd>), <strong>Covariance</strong>, and <strong>Confirmed Tracks</strong>.
<br><strong>Color Modes:</strong> Switch between SNR (<kbd>1</kbd>), Cluster (<kbd>2</kbd>), Inlier (<kbd>3</kbd>), or Stationary (<kbd>4</kbd>).
<br><strong>TTC Colors:</strong> Customize the Time-to-Collision (TTC) risk colors.
</p>
</div>
<!-- Feature: GOD Mode -->
<div class="feature-card p-6">
<div class="flex items-center">
<span class="feature-card-icon">🚀</span>
<h3>"GOD MODE" Zoom (<kbd>G</kbd>)</h3>
</div>
<p class="text-sm text-slate-600 mt-2">
Activates a separate, high-fidelity p5.js canvas that follows your mouse, showing a <strong>magnified view</strong>. Hover over points/tracks to see a detailed tooltip. Use the <strong>mouse wheel</strong> while in this mode to zoom in/out.
</p>
</div>
<!-- Feature: Data Explorer -->
<div class="feature-card p-6">
<div class="flex items-center">
<span class="feature-card-icon">📈</span>
<h3>Data Explorer (<kbd>I</kbd>)</h3>
</div>
<p class="text-sm text-slate-600 mt-2">
Opens a resizable panel to inspect the raw data of the *current frame*.
<br><strong>Tree View:</strong> Shows the full JSON structure.
<br><strong>Grid View:</strong> Sortable/filterable AG-Grid of point clouds or tracks.
<br><strong>Plot View:</strong> Click a grid header to plot that data with Chart.js.
</p>
</div>
<!-- Feature: CAN Data -->
<div class="feature-card p-6" hidden>
<div class="flex items-center">
<span class="feature-card-icon">🚗</span>
<h3>CAN Speed Integration</h3>
</div>
<p class="text-sm text-slate-600 mt-2">
The <kbd>Load CAN</kbd> button has been removed. The visualizer now expects CAN speed data (<code>canVehSpeed_kmph</code>) to be included <strong>directly within the main JSON file</strong>, associated with each radar frame.
</p>
</div>
<!-- Feature: Session -->
<div class="feature-card p-6" hidden>
<div class="flex items-center">
<span class="feature-card-icon">💾</span>
<h3>Session Management</h3>
</div>
<p class="text-sm text-slate-600 mt-2">
<kbd>Save Session</kbd> downloads a <kbd>.json</kbd> file with your current file names, offset, and all UI toggle states. <kbd>Load Session</kbd> restores this state (requires files to be cached).
</p>
</div>
<!-- Feature: Parsing -->
<div class="feature-card p-6">
<div class="flex items-center">
<span class="feature-card-icon">⚙️</span>
<h3>Web Worker Parsing</h3>
</div>
<p class="text-sm text-slate-600 mt-2">
Large JSON files are parsed in a background <strong>Web Worker</strong> using a streaming parser. This prevents the UI from freezing and shows a real-time progress bar.
</p>
</div>
</div>
</section>
<!-- 5. Full Keyboard Shortcuts -->
<section>
<h2 class="text-center mb-16">Full Keyboard Shortcuts</h2>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<!-- Playback -->
<div class="feature-card p-6">
<h3 class="mb-3">Playback & Navigation</h3>
<ul class="space-y-2 text-sm">
<li><kbd>Spacebar</kbd> - Play / Pause</li>
<li><kbd>ArrowRight</kbd> - Next Frame (pauses)</li>
<li><kbd>ArrowLeft</kbd> - Previous Frame (pauses)</li>
<li><kbd>R</kbd> - Reset to Frame 0</li>
</ul>
</div>
<!-- View & Toggles -->
<div class="feature-card p-6">
<h3 class="mb-3">View & UI Toggles</h3>
<ul class="space-y-2 text-sm">
<li><kbd>M</kbd> - Toggle Sidebar Menu</li>
<li><kbd>I</kbd> - Toggle Data Explorer</li>
<li><kbd>G</kbd> - Toggle "GOD MODE" Zoom</li>
<li><kbd>Q</kbd> - Toggle Dark/Light Theme</li>
<li><kbd>A</kbd> - Toggle Advanced Debug Info</li>
<li><kbd>F11</kbd> - Toggle Fullscreen</li>
</ul>
</div>
<!-- Data Display -->
<div class="feature-card p-6">
<h3 class="mb-3">Data Display Toggles</h3>
<ul class="space-y-2 text-sm">
<li><kbd>T</kbd> - Toggle Tracks</li>
<li><kbd>D</kbd> - Toggle Object Details</li>
<li><kbd>P</kbd> - Toggle Predicted Position</li>
<li><kbd>C</kbd> - Toggle Confirmed Tracks Only</li>
<li><kbd>1</kbd> / <kbd>S</kbd> - Color by SNR</li>
<li><kbd>2</kbd> - Color by Cluster</li>
<li><kbd>3</kbd> - Color by Inlier</li>
<li><kbd>4</kbd> - Color by Stationary</li>
</ul>
</div>
</div>
</section>
<!-- 6. Data & Setup -->
<section>
<h2 class="text-center mb-8">Data & Setup</h2>
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
<!-- File Structure -->
<div class="bg-white rounded-lg shadow-md p-6">
<h3 class="mb-4">Project File Structure</h3>
<ul class="file-tree text-sm">
<li class="file">index.html (Main Page)</li>
<li class="file">readme.md (Docs)</li>
<li class="file">Visualizer_quick_start_Guide.html (This file)</li>
<li class="file">Visualization_Start.bat</li>
<li class="file">python_check.bat</li>
<li class="folder">src/
<ul class="file-tree pl-4 border-l border-slate-200 ml-1 mt-1">
<li class="file">main.js (App Entry)</li>
<li class="file">state.js (Global State)</li>
<li class="file">sync.js (Animation Loop)</li>
<li class="file">dom.js (UI Functions)</li>
<li class="file">drawUtils.js (P5 Helpers)</li>
<li class="file">fileParsers.js (Data Processing)</li>
<li class="file">parser.worker.js (JSON Parsing)</li>
<li class="file">dataExplorer.js (Grid/Plot)</li>
<li class="file">db.js (Caching)</li>
<li class="file">modal.js (Popups)</li>
<li class="file">theme.js (Dark/Light)</li>
<li class="file">utils.js (Helpers)</li>
<li class="file">constants.js (Config)</li>
<li class="folder">p5/
<ul class="file-tree pl-4 border-l border-slate-200 ml-1 mt-1">
<li class="file">radarSketch.js</li>
<li class="file">speedGraphSketch.js</li>
<li class="file">zoomSketch.js</li>
</ul>
</li>
</ul>
</li>
</ul>
</div>
<!-- Setup Guide -->
<div class="bg-white rounded-lg shadow-md p-6">
<h3 class="mb-4">Local Setup Guide</h3>
<p class="text-sm text-slate-600 mb-4">The app must be run from a local server due to browser security rules for modules.</p>
<ol class="space-y-3">
<li class="flex items-start">
<span class="flow-step !p-2 !rounded-full w-8 h-8 mr-3 text-sm">1</span>
<span class="text-sm text-slate-600">Run <kbd>Visualization_Start.bat</kbd>. This script checks for Python 3 and starts the server for you.</span>
</li>
<li class="flex items-start">
<span class="flow-step !p-2 !rounded-full w-8 h-8 mr-3 text-sm">2</span>
<span class="text-sm text-slate-600">Your browser should open to `http://localhost:8000` automatically.</span>
</li>
<li class="flex items-start">
<span class="flow-step !p-2 !rounded-full w-8 h-8 mr-3 text-sm">3</span>
<span class="text-sm text-slate-600"><strong>Keep the black terminal window open</strong> while you use the app. Close it to stop the server.</span>
</li>
<li class="flex items-start">
<span class="flow-step !p-2 !rounded-full w-8 h-8 mr-3 text-sm">4</span>
<span class="text-sm text-slate-600">If the `.bat` fails, run <kbd>python_check.bat</kbd> to diagnose, or manually run `python -m http.server 8000` in the `steps` folder.</span>
</li>
</ol>
</div>
</div>
</section>
<!-- Footer -->
<footer class="text-center mt-12 text-slate-500 text-sm">
<p>This infographic was generated based on the project's readme.md and context.md files.</p>
</footer>
</div>
</body>
</html>

88
steps/index.html

@ -78,19 +78,70 @@
background: transparent;
z-index: 10;
}
.resizer-t { top: -5px; left: 5px; right: 5px; height: 10px; cursor: ns-resize; }
.resizer-r { top: 5px; right: -5px; bottom: 5px; width: 10px; cursor: ew-resize; }
.resizer-b { bottom: -5px; left: 5px; right: 5px; height: 10px; cursor: ns-resize; }
.resizer-l { top: 5px; left: -5px; bottom: 5px; width: 10px; cursor: ew-resize; }
.resizer-tl { top: -5px; left: -5px; cursor: nwse-resize; }
.resizer-tr { top: -5px; right: -5px; cursor: nesw-resize; }
.resizer-br { bottom: -5px; right: -5px; cursor: nwse-resize; }
.resizer-bl { bottom: -5px; left: -5px; cursor: nesw-resize; }
.resizer-t {
top: -5px;
left: 5px;
right: 5px;
height: 10px;
cursor: ns-resize;
}
.resizer-r {
top: 5px;
right: -5px;
bottom: 5px;
width: 10px;
cursor: ew-resize;
}
.resizer-b {
bottom: -5px;
left: 5px;
right: 5px;
height: 10px;
cursor: ns-resize;
}
.resizer-l {
top: 5px;
left: -5px;
bottom: 5px;
width: 10px;
cursor: ew-resize;
}
.resizer-tl {
top: -5px;
left: -5px;
cursor: nwse-resize;
}
.resizer-tr {
top: -5px;
right: -5px;
cursor: nesw-resize;
}
.resizer-br {
bottom: -5px;
right: -5px;
cursor: nwse-resize;
}
.resizer-bl {
bottom: -5px;
left: -5px;
cursor: nesw-resize;
}
/* When dragging/resizing, prevent text selection on the page */
body.resizing, body.dragging {
body.resizing,
body.dragging {
user-select: none;
-webkit-user-select: none;
}
/* --- END: CSS for Resizable/Draggable Panel --- */
</style>
</head>
@ -105,7 +156,7 @@
</p>
<div class="absolute top-4 right-4 flex items-center gap-2">
<button id="fullscreen-btn" type="button"
class="text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 font-medium rounded-lg text-xs px-4 py-2 transition-colors">
class="bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 shadow-sm hover:bg-gray-200 dark:hover:bg-gray-600 active:scale-95 active:shadow-inner font-medium rounded-lg text-xs px-4 py-2 transition-all">
FULLSCREEN
<span
class="text-xs font-mono bg-gray-200 dark:bg-gray-600 text-gray-700 dark:text-gray-200 px-1.5 py-0.5 rounded">
@ -113,15 +164,18 @@
</span>
</button>
<button id="explorer-btn" type="button"
class="text-gray-600 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700 font-medium rounded-lg text-xs px-4 py-2 transition-colors">
class="bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 shadow-sm hover:bg-gray-200 dark:hover:bg-gray-600 active:scale-95 active:shadow-inner font-medium rounded-lg text-xs px-4 py-2 transition-all">
Data-Explorer<br>(MATLAB-style)
<span
class="text-xs font-mono bg-gray-200 dark:bg-gray-600 text-gray-700 dark:text-gray-200 px-1.5 py-0.5 rounded">
(i)
</span>
</button>
<button id="theme-toggle" type="button"
class=" flex flex-row items-center gap-2 dark:text-gray-400 hover:bg-gray-100 dark:hover:bg-gray-700 focus:outline-none focus:ring-4 focus:ring-gray-200 dark:focus:ring-gray-700 rounded-lg text-sm p-2.5">
<a href="User_Manual.html" target="_blank" id="user-manual-btn"
class="bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 shadow-sm hover:bg-gray-200 dark:hover:bg-gray-600 active:scale-95 active:shadow-inner font-medium rounded-lg text-xs px-4 py-2 transition-all text-center">
Quick-Start<br>Guide
</a>
<button id="theme-toggle" type="button" class="bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300 border border-gray-300 dark:border-gray-600 shadow-sm hover:bg-gray-200 dark:hover:bg-gray-600 active:scale-95 active:shadow-inner flex flex-row items-center gap-2 rounded-lg text-sm p-2.5 transition-all">
<svg id="theme-toggle-dark-icon" class="hidden w-5 h-5" fill="currentColor" viewBox="0 0 20 20">
<path d="M17.293 13.293A8 8 0 016.707 2.707a8.001 8.001 0 1010.586 10.586z"></path>
</svg>
@ -299,7 +353,8 @@
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">
<!-- START: Add Draggable Header and Resizer Handles -->
<div id="data-explorer-header" class="flex items-center justify-between p-2 border-b dark:border-gray-700 cursor-move">
<div id="data-explorer-header"
class="flex items-center justify-between p-2 border-b dark:border-gray-700 cursor-move">
<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>
@ -319,7 +374,8 @@
<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-track-grid" class="px-4 py-2 text-sm font-medium text-gray-500 dark:text-gray-400">Track Grid</button>
<button id="tab-btn-track-grid" class="px-4 py-2 text-sm font-medium text-gray-500 dark:text-gray-400">Track
Grid</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>
@ -330,7 +386,7 @@
<div id="data-grid" class="ag-theme-alpine-dark h-full w-full"></div>
</div>
<div id="tab-panel-track-grid" class="hidden h-full p-2">
<div id="track-data-grid" class="ag-theme-alpine-dark h-full w-full"></div>
<div id="track-data-grid" class="ag-theme-alpine-dark h-full w-full"></div>
</div>
<div id="tab-panel-plot" class="hidden p-2">
<canvas id="data-chart"></canvas>

419
steps/src/drawUtils.js

@ -815,48 +815,6 @@ export function drawCovarianceEllipse(
radiusB * 2 * plotScales.plotScaleY // in p5 library expect
);
p.pop();
//---old ellipse logic using covariance from data directly//
// const pPos = [
// [covarianceP[0][0], covarianceP[0][1]],
// [covarianceP[1][0], covarianceP[1][1]],
// ];
// const a = pPos[0][0];
// const b = pPos[0][1];
// const d = pPos[1][1];
// const trace = a + d;
// const determinant = a * d - b * b;
//const lambda1 = trace / 2 + Math.sqrt(Math.pow(trace, 2) / 4 - determinant);
//const lambda2 = trace / 2 - Math.sqrt(Math.pow(trace, 2) / 4 - determinant);
// --- START: New robust calculation with logging ---
// let sqrtTermVal = Math.pow(trace, 2) / 4 - determinant;
// Check for a negative value, which causes NaN errors
// if (sqrtTermVal < 0) {
// // Log a warning so we know it happened, as you suggested
// console.warn(
// `Clamping negative sqrtTermVal in frame ${appState.currentFrame} to prevent NaN. Original value: ${sqrtTermVal}`
// );
// // Clamp the value to 0. This allows drawing to continue instead of breaking.
// sqrtTermVal = 0;
// }
// const sqrtTerm = Math.sqrt(sqrtTermVal);
// const lambda1 = trace / 2 + sqrtTerm;
// const lambda2 = trace / 2 - sqrtTerm;
// // --- END: New robust calculation with logging ---
// const chi2 = 5.991;
// const majorAxis = Math.sqrt(chi2 * lambda1);
// const minorAxis = Math.sqrt(chi2 * lambda2);
// let eigenvector = [1, 0];
// if (b !== 0) {
// eigenvector = [lambda1 - d, b];
// }
// const angle = Math.atan2(eigenvector[1], eigenvector[0]);
//---old ellipse logic using covariance from data directly//
}
// In src/drawUtils.js
@ -1010,380 +968,3 @@ export function drawClusterCentroids(p, clustersInput, plotScales) {
}
}
}
//--- drawClusterCentroids function---//
// old trial functions to replace the close up display.
// In src/drawUtils.js
// Replace the ENTIRE 'handleCloseUpDisplay' function with these TWO new functions:
// /**
// * Finds all radar elements (points, tracks, etc.) under the mouse cursor.
// * @param {p5} p - The p5 instance (for mouse coordinates and distance checks).
// * @param {object} plotScales - The calculated scales for plotting.
// * @returns {Array} An array of hovered item objects.
// */
// export function findHoveredItems(p, plotScales) {
// const frameData = appState.vizData.radarFrames[appState.currentFrame];
// if (!frameData) return [];
// const hoveredItems = [];
// const radius = 10;
// const localClusterColors = clusterColors(p);
// // Find hovered points
// 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);
// if (p.dist(p.mouseX, p.mouseY, screenX, screenY) < radius) {
// hoveredItems.push({ type: 'point', data: pt, screenX, screenY });
// }
// }
// }
// // Find hovered cluster centroids
// 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);
// if (p.dist(p.mouseX, p.mouseY, screenX, screenY) < radius) {
// const color = cluster.id > 0 ? localClusterColors[(cluster.id - 1) % localClusterColors.length] : p.color(128);
// hoveredItems.push({ type: 'cluster', data: cluster, screenX, screenY, color });
// }
// }
// }
// // Find hovered tracks and predictions
// 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);
// if (p.dist(p.mouseX, p.mouseY, screenX, screenY) < 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);
// if (p.dist(p.mouseX, p.mouseY, screenX, screenY) < radius) {
// hoveredItems.push({ type: 'prediction', data: log, trackId: track.id, screenX, screenY });
// }
// }
// }
// }
// }
// hoveredItems.sort((a, b) => a.screenY - b.screenY);
// return hoveredItems;
// }
// /**
// * Draws the visual tooltip and connectors for a given list of hovered items.
// * @param {p5} p - The p5 instance to draw with.
// * @param {Array} hoveredItems - An array of items from findHoveredItems.
// */
// export function drawTooltip(p, hoveredItems) {
// if (hoveredItems.length === 0) return;
// const infoStrings = [];
// // Generate display text
// for (const item of hoveredItems) {
// let infoText = '';
// const data = item.data;
// switch (item.type) {
// case 'point':
// infoText = `Point | X:${data.x.toFixed(2)}, Y:${data.y.toFixed(2)} | V:${data.velocity?.toFixed(2)}, SNR:${data.snr?.toFixed(1)}`;
// break;
// case 'cluster':
// infoText = `Cluster ${data.id} | X:${data.x.toFixed(2)}, Y:${data.y.toFixed(2)} | rSpeed:${data.radialSpeed?.toFixed(2)}`;
// break;
// case 'track':
// infoText = `Track ${item.trackId} | X:${data.correctedPosition[0].toFixed(2)}, Y:${data.correctedPosition[1].toFixed(2)}`;
// break;
// case 'prediction':
// infoText = `Pred. for ${item.trackId} | X:${data.predictedPosition[0].toFixed(2)}, Y:${data.predictedPosition[1].toFixed(2)}`;
// break;
// }
// if (infoText) {
// infoStrings.push({ text: infoText, color: item.color || null });
// }
// }
// p.push();
// p.textSize(12);
// const lineHeight = 15;
// const boxPadding = 8;
// let boxWidth = 0;
// infoStrings.forEach(info => {
// boxWidth = Math.max(boxWidth, p.textWidth(info.text));
// });
// const boxHeight = (infoStrings.length * lineHeight) + (boxPadding * 2);
// boxWidth += (boxPadding * 2);
// const xOffset = 20;
// let boxX = p.mouseX + xOffset;
// if (boxX + boxWidth > p.width) {
// boxX = p.mouseX - boxWidth - xOffset;
// }
// let boxY = p.mouseY - (boxHeight / 2);
// boxY = p.constrain(boxY, 0, p.height - boxHeight);
// // Draw highlights and connectors
// const highlightColor = p.color(46, 204, 113);
// hoveredItems.forEach((item, i) => {
// p.noFill();
// p.stroke(highlightColor);
// p.strokeWeight(2);
// p.ellipse(item.screenX, item.screenY, 15, 15);
// p.strokeWeight(1);
// const lineAnchorX = boxX < p.mouseX ? boxX + boxWidth : boxX;
// p.line(lineAnchorX, boxY + boxPadding + (i * lineHeight) + (lineHeight / 2), item.screenX, item.screenY);
// });
// // Draw the box and text
// 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);
// const defaultTextColor = document.documentElement.classList.contains('dark') ? p.color(230) : p.color(20);
// p.noStroke();
// p.textAlign(p.LEFT, p.TOP);
// infoStrings.forEach((info, i) => {
// p.fill(info.color || defaultTextColor);
// p.text(info.text, boxX + boxPadding, boxY + boxPadding + (i * lineHeight));
// });
// p.pop();
// }
// // /**
// // * Renders a high-fidelity, zoomed-in view of the scene around the mouse cursor.
// // * @param {p5} p - The p5 instance.
// // * @param {object} plotScales - The calculated scales for plotting.
// // * @param {Array} hoveredItems - The array of items currently under the mouse.
// // */
// // export function drawZoomWindow(p, plotScales, hoveredItems) {
// // // --- Zoom Window Configuration (easily modifiable) ---
// // // The magnification level. 4.0 means 4x zoom.
// // const zoomFactor = 4.0;
// // // The output size of the zoom window on the screen, in pixels.
// // const zoomWindowWidth = 250;
// // const zoomWindowHeight = 250;
// // // Position the zoom window in the bottom-right of the canvas.
// // const boxX = p.width - zoomWindowWidth - 20;
// // const boxY = p.height - zoomWindowHeight - 20;
// // p.push(); // Save the current global drawing state.
// // // --- Create a "Portal" to the Zoomed View ---
// // // We use a clipping mask to ensure the zoomed content doesn't spill out.
// // p.drawingContext.save();
// // p.drawingContext.rect(boxX, boxY, zoomWindowWidth, zoomWindowHeight);
// // p.drawingContext.clip();
// // // We now transform the entire canvas coordinate system for the redraw.
// // p.translate(boxX, boxY); // 1. Move origin to the zoom box's corner.
// // p.scale(zoomFactor); // 2. Scale everything up.
// // // 3. Translate so the mouse position is in the center of the box.
// // p.translate(-p.mouseX + zoomWindowWidth / (2 * zoomFactor), -p.mouseY + zoomWindowHeight / (2 * zoomFactor));
// // // --- Redraw the Entire Scene in the New Zoomed Coordinate System ---
// // // This provides a high-fidelity, not just pixelated, zoom.
// // p.background(document.documentElement.classList.contains('dark') ? p.color(55, 65, 81) : 255);
// // p.image(p.get(), 0, 0); // A trick to redraw the static background buffer
// // p.push(); // Nested push for the main radar transformations.
// // p.translate(p.width / 2, p.height * 0.95);
// // p.scale(1, -1);
// // const frameData = appState.vizData.radarFrames[appState.currentFrame];
// // drawAxes(p, plotScales);
// // drawEgoVehicle(p, plotScales);
// // if (frameData) {
// // drawRegionsOfInterest(p, frameData, plotScales);
// // if (toggleTracks.checked) {
// // drawTrajectories(p, plotScales);
// // drawTrackMarkers(p, plotScales);
// // }
// // drawPointCloud(p, frameData.pointCloud, plotScales);
// // if (toggleClusterColor.checked) {
// // drawClusterCentroids(p, frameData.clusters, plotScales);
// // }
// // // Redraw predicted positions if toggled
// // if (togglePredictedPos.checked) {
// // for (const track of appState.vizData.tracks) {
// // const log = track.historyLog.find(log => log.frameIdx === appState.currentFrame + 1);
// // if (log && log.predictedPosition && log.predictedPosition[0] !== null) {
// // const pos = log.predictedPosition;
// // const x = pos[0] * plotScales.plotScaleX;
// // const y = pos[1] * plotScales.plotScaleY;
// // p.push();
// // p.stroke(255, 0, 0); p.strokeWeight(2);
// // p.line(x - 4, y - 4, x + 4, y + 4);
// // p.line(x + 4, y - 4, x - 4, y + 4);
// // p.pop();
// // }
// // }
// // }
// // }
// // p.pop(); // End of radar transformations.
// // // --- Redraw Tooltip and Connectors ---
// // // We re-run the original tooltip function, which will now draw inside our zoomed view,
// // // making the connector lines perfectly accurate.
// // handleCloseUpDisplay(p, plotScales);
// // // Clean up the clipping mask.
// // p.drawingContext.restore();
// // // --- Draw Border and Crosshairs on Top of Everything ---
// // p.noFill();
// // p.stroke(46, 204, 113); // Highlight green border.
// // p.strokeWeight(2);
// // p.rect(boxX, boxY, zoomWindowWidth, zoomWindowHeight);
// // // Red crosshairs to mark the exact mouse position.
// // const crosshairSize = 10;
// // p.stroke(255, 0, 0, 150);
// // p.strokeWeight(1);
// // p.line(boxX + zoomWindowWidth/2 - crosshairSize, boxY + zoomWindowHeight/2, boxX + zoomWindowWidth/2 + crosshairSize, boxY + zoomWindowHeight/2);
// // p.line(boxX + zoomWindowWidth/2, boxY + zoomWindowHeight/2 - crosshairSize, boxX + zoomWindowWidth/2, boxY + zoomWindowHeight/2 + crosshairSize);
// // p.pop(); // Restore the original global drawing state.
// // }
// OLD HATCH FILL logic
// /**
// * Draws a hatched pattern inside a rectangle defined by corner points.
// * This is a new helper function.
// * @param {p5.Graphics} b The p5.Graphics buffer to draw on.
// * @param {number} x1 The x-coordinate of the first corner.
// * @param {number} y1 The y-coordinate of the first corner.
// * @param {number} x2 The x-coordinate of the second corner.
// * @param {number} y2 The y-coordinate of the second corner.
// * @param {p5.Color} hatchColor The color of the hatches.
// * @param {number} spacing The distance between hatch lines.
// */
// function drawHatchedRect(b, x1, y1, x2, y2, hatchColor, spacing) {
// const minX = Math.min(x1, x2);
// const maxX = Math.max(x1, x2);
// const minY = Math.min(y1, y2);
// const maxY = Math.max(y1, y2);
// b.push();
// b.stroke(hatchColor);
// b.strokeWeight(2);
// b.noFill();
// // To draw lines like '/', we use the equation y = x + c (positive slope).
// // The constant 'c' is equal to y - x.
// // We iterate 'c' through its possible range for the given rectangle.
// // The minimum value for c is minY - maxX, and the maximum is maxY - minX.
// for (let c = minY - maxX; c < maxY - minX; c += spacing) {
// let points = [];
// // For a line y = x + c:
// // Intersection with top edge (y = maxY) -> x = maxY - c
// let ix_top = maxY - c;
// if (ix_top >= minX && ix_top <= maxX) points.push({ x: ix_top, y: maxY });
// // Intersection with bottom edge (y = minY) -> x = minY - c
// let ix_bottom = minY - c;
// if (ix_bottom >= minX && ix_bottom <= maxX) {
// points.push({ x: ix_bottom, y: minY });
// }
// // Intersection with left edge (x = minX) -> y = minX + c
// let iy_left = minX + c;
// if (iy_left >= minY && iy_left <= maxY) {
// points.push({ x: minX, y: iy_left });
// }
// // Intersection with right edge (x = maxX) -> y = maxX + c
// let iy_right = maxX + c;
// if (iy_right >= minY && iy_right <= maxY) {
// points.push({ x: maxX, y: iy_right });
// }
// // A line can only intersect a convex shape (like a rectangle) at two points.
// // If it passes through a corner, we might get duplicates.
// if (points.length >= 2) {
// // Remove duplicate points
// const uniquePoints = [];
// const seen = new Set();
// for (const p of points) {
// const key = `${p.x},${p.y}`;
// if (!seen.has(key)) {
// uniquePoints.push(p);
// seen.add(key);
// }
// }
// if (uniquePoints.length >= 2) {
// b.line(
// uniquePoints[0].x,
// uniquePoints[0].y,
// uniquePoints[1].x,
// uniquePoints[1].y
// );
// }
// }
// }
// b.pop();
// }
// /**
// * Draws the defined regions of interest (ROI) onto the canvas buffer.
// * @param {p5} p - The p5 instance.
// * @param {p5.Graphics} b - The p5.Graphics buffer to draw on.
// * @param {object} plotScales - The calculated scales for plotting.
// */
// export function drawRegionsOfInterest(p, b, plotScales) {
// const isDark = document.documentElement.classList.contains("dark");
// // Define semi-transparent colors for the hatching. Opacity is higher for lines.
// const tracksRegionColor = isDark
// ? p.color(137, 207, 240, 50)
// : p.color(173, 216, 230, 100); // Light blue
// const closeRegionColor = isDark
// ? p.color(255, 182, 193, 60)
// : p.color(255, 182, 193, 120); // Light pink
// b.push();
// b.translate(b.width / 2, b.height * 0.95); // Translate to the bottom center of the buffer, same as other static elements
// b.scale(1, -1); // Flip Y-axis
// // --- Draw Tracks Region ---
// drawHatchedRect(
// b,
// ROI_TRACKS_X_MIN * plotScales.plotScaleX,
// ROI_TRACKS_Y_MIN * plotScales.plotScaleY,
// ROI_TRACKS_X_MAX * plotScales.plotScaleX,
// ROI_TRACKS_Y_MAX * plotScales.plotScaleY,
// tracksRegionColor,
// 15 // Spacing for the hatch lines
// );
// // --- Draw Close Region ---
// drawHatchedRect(
// b,
// ROI_CLOSE_X_MIN * plotScales.plotScaleX,
// ROI_CLOSE_Y_MIN * plotScales.plotScaleY,
// ROI_CLOSE_X_MAX * plotScales.plotScaleX,
// ROI_CLOSE_Y_MAX * plotScales.plotScaleY,
// closeRegionColor,
// 15 // Spacing for the hatch lines
// );
// b.pop();
// }
Loading…
Cancel
Save