import { useEffect, useRef } from 'react'; // Canvas hook: resizes to element width at devicePixelRatio and calls draw. export function useCanvas( draw: (ctx: CanvasRenderingContext2D, w: number, h: number) => void, deps: unknown[], height: number, ) { const ref = useRef(null); useEffect(() => { const canvas = ref.current; if (!canvas) return; const render = () => { const dpr = window.devicePixelRatio || 1; const w = canvas.clientWidth; canvas.width = Math.max(1, Math.round(w * dpr)); canvas.height = Math.max(1, Math.round(height * dpr)); canvas.style.height = `${height}px`; const ctx = canvas.getContext('2d')!; ctx.setTransform(dpr, 0, 0, dpr, 0, 0); ctx.clearRect(0, 0, w, height); draw(ctx, w, height); }; render(); const ro = new ResizeObserver(render); ro.observe(canvas); return () => ro.disconnect(); // eslint-disable-next-line react-hooks/exhaustive-deps }, deps); return ref; } export const PLOT_COLORS = { bg: '#181b20', grid: '#2a2f38', axis: '#8b93a1', trace: '#4dd0e1', accent: '#e8a33d', good: '#6fbf73', warn: '#e57373', }; export function drawFrame(ctx: CanvasRenderingContext2D, w: number, h: number) { ctx.fillStyle = PLOT_COLORS.bg; ctx.fillRect(0, 0, w, h); } // Viridis-like colormap for the spectrogram, t in [0,1]. export function colormap(t: number): [number, number, number] { const stops: [number, number, number][] = [ [13, 8, 60], [84, 39, 143], [140, 41, 129], [196, 66, 90], [237, 105, 37], [251, 155, 6], [247, 209, 61], [252, 255, 164], ]; const x = Math.min(0.9999, Math.max(0, t)) * (stops.length - 1); const i = Math.floor(x); const f = x - i; const a = stops[i]; const b = stops[i + 1]; return [ Math.round(a[0] + f * (b[0] - a[0])), Math.round(a[1] + f * (b[1] - a[1])), Math.round(a[2] + f * (b[2] - a[2])), ]; } // Color a mode by its number of nodal diameters: cyan at n = 0 through amber // at the highest order retained. Used by every plot that shows modes, so the // orders read the same everywhere. export function modeColor(n: number, maxN: number, alpha = 1): string { const t = Math.min(1, Math.max(0, n / Math.max(1, maxN))); const r = Math.round(77 + t * (232 - 77)); const g = Math.round(208 + t * (163 - 208)); const b = Math.round(225 + t * (61 - 225)); return `rgba(${r}, ${g}, ${b}, ${alpha})`; }