import { Component, createEffect } from 'solid-js'; import styles from './GradientFlow.module.css'; export type GradientStatus = 'vanishing' | 'exploding' | 'converged' | 'healthy'; export interface GradientFlowProps { /** Per-layer L2 norms of weight delta. */ layerNorms: () => ReadonlyArray; /** Per-layer status. Length must match layerNorms(). */ status: () => ReadonlyArray; width?: number; height?: number; ariaLabel?: string; } const STATUS_COLOR: Record = { vanishing: '#5b9eef', exploding: '#ef5b5b', converged: '#9a9a9a', healthy: '#6bc26b', }; export const GradientFlow: Component = (props) => { let canvasEl: HTMLCanvasElement | undefined; createEffect(() => { if (!canvasEl) return; const dpr = window.devicePixelRatio || 1; const w = (props.width ?? 240) * dpr; const h = (props.height ?? 80) * dpr; if (canvasEl.width !== w || canvasEl.height !== h) { canvasEl.width = w; canvasEl.height = h; } const ctx = canvasEl.getContext('2d'); if (!ctx) return; ctx.clearRect(0, 0, w, h); const norms = props.layerNorms(); const status = props.status(); if (norms.length === 0) return; let max = 0; for (const n of norms) if (n > max) max = n; if (max <= 0) max = 1; const barW = w / norms.length; for (let i = 0; i < norms.length; i++) { const bh = (norms[i]! / max) * (h - 14 * dpr); const s = status[i] ?? 'healthy'; ctx.fillStyle = STATUS_COLOR[s]; ctx.fillRect(i * barW + 2, h - bh - 2, barW - 4, bh); ctx.fillStyle = '#5a5a5a'; ctx.font = `${10 * dpr}px var(--font-mono)`; ctx.textAlign = 'center'; ctx.fillText(`L${i}`, i * barW + barW / 2, h - 2); } }); return ( ); };