// 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 = `
MOCK DATA
Disconnected
Latency
-- ms
Messages
0
Uptime
0s
Drag to orbit | Scroll to zoom | Right-click to pan
`;
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);
}
}
}