Files
wifi-densepose/ui/components/dashboard-hud.js
Claude dd382824fe feat: Add hardware requirement notice to README, additional Three.js viz components
Add prominent hardware requirements table at top of README documenting
the three paths to real CSI data (ESP32, research NIC, commodity WiFi).
Include remaining Three.js visualization components for dashboard.

https://claude.ai/code/session_01Ki7pvEZtJDvqJkmyn6B714
2026-02-28 06:26:10 +00:00

430 lines
13 KiB
JavaScript

// Dashboard HUD Overlay - WiFi DensePose 3D Visualization
// Connection status, FPS counter, detection confidence, person count, sensing mode
export class DashboardHUD {
constructor(container) {
this.container = typeof container === 'string'
? document.getElementById(container)
: container;
// State
this.state = {
connectionStatus: 'disconnected', // connected, disconnected, connecting, error
isRealData: false,
fps: 0,
confidence: 0,
personCount: 0,
sensingMode: 'Mock', // CSI, RSSI, Mock
latency: 0,
messageCount: 0,
uptime: 0
};
this._fpsFrames = [];
this._lastFpsUpdate = 0;
this._build();
}
_build() {
// Create HUD overlay container
this.hudElement = document.createElement('div');
this.hudElement.id = 'viz-hud';
this.hudElement.innerHTML = `
<style>
#viz-hud {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 100;
font-family: 'Courier New', 'Consolas', monospace;
color: #88ccff;
}
#viz-hud * {
pointer-events: none;
}
/* Data source banner */
.hud-banner {
position: absolute;
top: 0;
left: 0;
right: 0;
text-align: center;
padding: 6px 0;
font-size: 14px;
font-weight: bold;
letter-spacing: 3px;
text-transform: uppercase;
z-index: 110;
}
.hud-banner.mock {
background: linear-gradient(90deg, rgba(180,100,0,0.85) 0%, rgba(200,120,0,0.85) 50%, rgba(180,100,0,0.85) 100%);
color: #fff;
border-bottom: 2px solid #ff8800;
}
.hud-banner.real {
background: linear-gradient(90deg, rgba(0,120,60,0.85) 0%, rgba(0,160,80,0.85) 50%, rgba(0,120,60,0.85) 100%);
color: #fff;
border-bottom: 2px solid #00ff66;
animation: pulse-green 2s ease-in-out infinite;
}
@keyframes pulse-green {
0%, 100% { border-bottom-color: #00ff66; }
50% { border-bottom-color: #00cc44; }
}
/* Top-left: connection info */
.hud-top-left {
position: absolute;
top: 40px;
left: 12px;
display: flex;
flex-direction: column;
gap: 4px;
}
.hud-row {
display: flex;
align-items: center;
gap: 6px;
font-size: 11px;
line-height: 1.4;
}
.hud-label {
color: #5588aa;
min-width: 65px;
text-transform: uppercase;
font-size: 9px;
letter-spacing: 1px;
}
.hud-value {
color: #aaddff;
font-weight: bold;
font-size: 12px;
}
/* Status dot */
.hud-status-dot {
width: 8px;
height: 8px;
border-radius: 50%;
display: inline-block;
margin-right: 4px;
}
.hud-status-dot.connected {
background: #00ff66;
box-shadow: 0 0 6px #00ff66;
}
.hud-status-dot.disconnected {
background: #666;
}
.hud-status-dot.connecting {
background: #ffaa00;
box-shadow: 0 0 6px #ffaa00;
animation: blink 1s infinite;
}
.hud-status-dot.error {
background: #ff3344;
box-shadow: 0 0 6px #ff3344;
}
@keyframes blink {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
/* Top-right: performance */
.hud-top-right {
position: absolute;
top: 40px;
right: 12px;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
}
.hud-fps {
font-size: 22px;
font-weight: bold;
color: #00ff88;
line-height: 1;
}
.hud-fps.low { color: #ff4444; }
.hud-fps.mid { color: #ffaa00; }
.hud-fps.high { color: #00ff88; }
/* Bottom-left: detection info */
.hud-bottom-left {
position: absolute;
bottom: 12px;
left: 12px;
display: flex;
flex-direction: column;
gap: 4px;
}
.hud-person-count {
font-size: 28px;
font-weight: bold;
line-height: 1;
}
.hud-confidence-bar {
width: 120px;
height: 6px;
background: rgba(20, 30, 50, 0.8);
border: 1px solid #223344;
border-radius: 3px;
overflow: hidden;
}
.hud-confidence-fill {
height: 100%;
border-radius: 3px;
transition: width 0.3s ease, background 0.3s ease;
}
/* Bottom-right: sensing mode */
.hud-bottom-right {
position: absolute;
bottom: 12px;
right: 12px;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4px;
}
.hud-mode-badge {
padding: 3px 10px;
border-radius: 4px;
font-size: 11px;
font-weight: bold;
letter-spacing: 1px;
text-transform: uppercase;
}
.hud-mode-badge.csi {
background: rgba(0, 100, 200, 0.7);
border: 1px solid #0088ff;
color: #aaddff;
}
.hud-mode-badge.rssi {
background: rgba(100, 0, 200, 0.7);
border: 1px solid #8800ff;
color: #ddaaff;
}
.hud-mode-badge.mock {
background: rgba(120, 80, 0, 0.7);
border: 1px solid #ff8800;
color: #ffddaa;
}
/* Corner brackets decoration */
.hud-corner {
position: absolute;
width: 20px;
height: 20px;
border-color: rgba(100, 150, 200, 0.3);
border-style: solid;
}
.hud-corner.tl { top: 36px; left: 4px; border-width: 1px 0 0 1px; }
.hud-corner.tr { top: 36px; right: 4px; border-width: 1px 1px 0 0; }
.hud-corner.bl { bottom: 4px; left: 4px; border-width: 0 0 1px 1px; }
.hud-corner.br { bottom: 4px; right: 4px; border-width: 0 1px 1px 0; }
/* Controls hint */
.hud-controls-hint {
position: absolute;
bottom: 50px;
left: 50%;
transform: translateX(-50%);
font-size: 10px;
color: #445566;
text-align: center;
opacity: 0.6;
}
</style>
<!-- Data source banner -->
<div class="hud-banner mock" id="hud-banner">MOCK DATA</div>
<!-- Corner decorations -->
<div class="hud-corner tl"></div>
<div class="hud-corner tr"></div>
<div class="hud-corner bl"></div>
<div class="hud-corner br"></div>
<!-- Top-left: connection info -->
<div class="hud-top-left">
<div class="hud-row">
<span class="hud-status-dot disconnected" id="hud-status-dot"></span>
<span class="hud-value" id="hud-conn-status">Disconnected</span>
</div>
<div class="hud-row">
<span class="hud-label">Latency</span>
<span class="hud-value" id="hud-latency">-- ms</span>
</div>
<div class="hud-row">
<span class="hud-label">Messages</span>
<span class="hud-value" id="hud-msg-count">0</span>
</div>
<div class="hud-row">
<span class="hud-label">Uptime</span>
<span class="hud-value" id="hud-uptime">0s</span>
</div>
</div>
<!-- Top-right: FPS -->
<div class="hud-top-right">
<div class="hud-fps high" id="hud-fps">-- FPS</div>
<div class="hud-row">
<span class="hud-label">Frame</span>
<span class="hud-value" id="hud-frame-time">-- ms</span>
</div>
</div>
<!-- Bottom-left: detection info -->
<div class="hud-bottom-left">
<div class="hud-row">
<span class="hud-label">Persons</span>
<span class="hud-person-count hud-value" id="hud-person-count">0</span>
</div>
<div class="hud-row">
<span class="hud-label">Confidence</span>
<span class="hud-value" id="hud-confidence">0%</span>
</div>
<div class="hud-confidence-bar">
<div class="hud-confidence-fill" id="hud-confidence-fill" style="width: 0%; background: #334455;"></div>
</div>
</div>
<!-- Bottom-right: sensing mode -->
<div class="hud-bottom-right">
<div class="hud-mode-badge mock" id="hud-mode-badge">MOCK</div>
<div class="hud-row" style="margin-top: 4px;">
<span class="hud-label">WiFi DensePose</span>
</div>
</div>
<!-- Controls hint -->
<div class="hud-controls-hint">
Drag to orbit | Scroll to zoom | Right-click to pan
</div>
`;
this.container.style.position = 'relative';
this.container.appendChild(this.hudElement);
// Cache DOM references
this._els = {
banner: this.hudElement.querySelector('#hud-banner'),
statusDot: this.hudElement.querySelector('#hud-status-dot'),
connStatus: this.hudElement.querySelector('#hud-conn-status'),
latency: this.hudElement.querySelector('#hud-latency'),
msgCount: this.hudElement.querySelector('#hud-msg-count'),
uptime: this.hudElement.querySelector('#hud-uptime'),
fps: this.hudElement.querySelector('#hud-fps'),
frameTime: this.hudElement.querySelector('#hud-frame-time'),
personCount: this.hudElement.querySelector('#hud-person-count'),
confidence: this.hudElement.querySelector('#hud-confidence'),
confidenceFill: this.hudElement.querySelector('#hud-confidence-fill'),
modeBadge: this.hudElement.querySelector('#hud-mode-badge')
};
}
// Update state from external data
updateState(newState) {
Object.assign(this.state, newState);
this._render();
}
// Track FPS - call each frame
tickFPS() {
const now = performance.now();
this._fpsFrames.push(now);
// Keep only last second of frames
while (this._fpsFrames.length > 0 && this._fpsFrames[0] < now - 1000) {
this._fpsFrames.shift();
}
// Update FPS display at most 4 times per second
if (now - this._lastFpsUpdate > 250) {
this.state.fps = this._fpsFrames.length;
const frameTime = this._fpsFrames.length > 1
? (now - this._fpsFrames[0]) / (this._fpsFrames.length - 1)
: 0;
this._lastFpsUpdate = now;
// Update FPS elements
this._els.fps.textContent = `${this.state.fps} FPS`;
this._els.fps.className = 'hud-fps ' + (
this.state.fps >= 50 ? 'high' : this.state.fps >= 25 ? 'mid' : 'low'
);
this._els.frameTime.textContent = `${frameTime.toFixed(1)} ms`;
}
}
_render() {
const { state } = this;
// Banner
if (state.isRealData) {
this._els.banner.textContent = 'REAL DATA - LIVE STREAM';
this._els.banner.className = 'hud-banner real';
} else {
this._els.banner.textContent = 'MOCK DATA - DEMO MODE';
this._els.banner.className = 'hud-banner mock';
}
// Connection status
this._els.statusDot.className = `hud-status-dot ${state.connectionStatus}`;
const statusText = {
connected: 'Connected',
disconnected: 'Disconnected',
connecting: 'Connecting...',
error: 'Error'
};
this._els.connStatus.textContent = statusText[state.connectionStatus] || 'Unknown';
// Latency
this._els.latency.textContent = state.latency > 0 ? `${state.latency.toFixed(0)} ms` : '-- ms';
// Messages
this._els.msgCount.textContent = state.messageCount.toLocaleString();
// Uptime
const uptimeSec = Math.floor(state.uptime);
if (uptimeSec < 60) {
this._els.uptime.textContent = `${uptimeSec}s`;
} else if (uptimeSec < 3600) {
this._els.uptime.textContent = `${Math.floor(uptimeSec / 60)}m ${uptimeSec % 60}s`;
} else {
const h = Math.floor(uptimeSec / 3600);
const m = Math.floor((uptimeSec % 3600) / 60);
this._els.uptime.textContent = `${h}h ${m}m`;
}
// Person count
this._els.personCount.textContent = state.personCount;
this._els.personCount.style.color = state.personCount > 0 ? '#00ff88' : '#556677';
// Confidence
const confPct = (state.confidence * 100).toFixed(1);
this._els.confidence.textContent = `${confPct}%`;
this._els.confidenceFill.style.width = `${state.confidence * 100}%`;
// Color temperature: red (low) -> yellow (mid) -> green (high)
const confHue = state.confidence * 120; // 0=red, 60=yellow, 120=green
this._els.confidenceFill.style.background = `hsl(${confHue}, 100%, 45%)`;
// Sensing mode
const modeLower = (state.sensingMode || 'Mock').toLowerCase();
this._els.modeBadge.textContent = state.sensingMode.toUpperCase();
this._els.modeBadge.className = `hud-mode-badge ${modeLower}`;
}
dispose() {
if (this.hudElement && this.hudElement.parentNode) {
this.hudElement.parentNode.removeChild(this.hudElement);
}
}
}