Pulse/frontend-modern/src/utils/canvasRenderQueue.ts
rcourtman 886368ec44 feat: add sparklines view mode for metrics visualization
Add comprehensive sparkline chart support as an alternative to progress bars
for CPU, Memory, and Disk metrics across all tables.

Features:
- Toggle between bars/trends view modes (persisted to localStorage)
- 30-second sampling with 2-hour retention window using ring buffer
- Canvas-based rendering with shared requestAnimationFrame for efficiency
- Hover tooltips showing exact values and timestamps
- Threshold reference lines (warning/critical) for context
- localStorage persistence survives page refreshes (12-hour max age)
- Dynamic width adaptation to column size
- Namespaced resource IDs prevent collisions
- Lifecycle cleanup prevents memory leaks

Performance optimizations:
- Decoupled sampling from WebSocket handler (6x reduction in recording)
- O(1) ring buffer insertions (no array cloning)
- Batched canvas rendering (single rAF for all sparklines)
- Debounced localStorage writes
- Automatic pruning of removed resources

UI improvements:
- Consistent radio toggle styling matching other filters
- Fixed column widths prevent layout shift during toggle
- Fixed row heights prevent vertical size changes
- Sparklines fill available column width proportionally
2025-11-09 22:31:35 +00:00

69 lines
1.4 KiB
TypeScript

/**
* Canvas Render Queue
*
* Shared requestAnimationFrame scheduler for batching canvas redraws.
* Prevents each sparkline from scheduling its own rAF cycle.
*/
import { logger } from './logger';
let rafId: number | null = null;
const pending = new Set<() => void>();
/**
* Flush all pending render callbacks in a single rAF cycle
*/
const flush = (): void => {
rafId = null;
const count = pending.size;
pending.forEach((draw) => {
try {
draw();
} catch (error) {
logger.error('[CanvasRenderQueue] Draw callback failed', { error });
}
});
pending.clear();
if (import.meta.env.DEV) {
logger.debug('[CanvasRenderQueue] Flushed render queue', { count });
}
};
/**
* Schedule a canvas redraw
* Returns an unregister callback for cleanup
*
* @param draw - The draw callback to execute on next animation frame
* @returns Cleanup function to unregister the callback
*/
export function scheduleSparkline(draw: () => void): () => void {
pending.add(draw);
if (rafId === null) {
rafId = requestAnimationFrame(flush);
}
return () => {
pending.delete(draw);
};
}
/**
* Get render queue stats for debugging
*/
export function getRenderQueueStats() {
return {
pendingCount: pending.size,
rafScheduled: rafId !== null,
};
}
if (import.meta.env.DEV) {
// Expose for debugging in dev mode
(window as any).__canvasRenderQueue = {
getStats: getRenderQueueStats,
};
}