/* FinPulse charts — hand-rolled responsive SVG. Exports to window. */ const { useRef, useState, useEffect, useLayoutEffect } = React; // measure container width function useWidth() { const ref = useRef(null); const [w, setW] = useState(600); useLayoutEffect(() => { if (!ref.current) return; const ro = new ResizeObserver((entries) => { const cw = entries[0].contentRect.width; if (cw > 0) setW(cw); }); ro.observe(ref.current); return () => ro.disconnect(); }, []); return [ref, w]; } function buildPath(data, w, h, padTop, padBottom) { const min = Math.min(...data), max = Math.max(...data); const range = max - min || 1; const usableH = h - padTop - padBottom; const step = data.length > 1 ? w / (data.length - 1) : 0; const pts = data.map((v, i) => [i * step, padTop + usableH - ((v - min) / range) * usableH]); const line = pts.map((p, i) => (i === 0 ? `M${p[0]},${p[1]}` : `L${p[0]},${p[1]}`)).join(' '); const area = `${line} L${pts[pts.length - 1][0]},${h - padBottom} L0,${h - padBottom} Z`; return { line, area, pts, min, max }; } // ---------- Sparkline ---------- function Sparkline({ data, up, width = 80, height = 32, strokeWidth = 1.5 }) { const { line } = buildPath(data, width, height, 3, 3); const color = up ? 'var(--up)' : 'var(--down)'; return ( ); } // ---------- Area chart ---------- function AreaChart({ data, color = 'var(--accent)', height = 260, labels }) { const [ref, w] = useWidth(); const [hover, setHover] = useState(null); const padTop = 12, padBottom = 26, padRight = 0; const cw = Math.max(w - padRight, 10); const { line, area, pts, min, max } = buildPath(data, cw, height, padTop, padBottom); const gid = 'ag-' + Math.round(min); const gridVals = [max, (max + min) / 2, min]; const usableH = height - padTop - padBottom; function onMove(e) { const rect = ref.current.getBoundingClientRect(); const x = e.clientX - rect.left; const step = cw / (data.length - 1); let idx = Math.round(x / step); idx = Math.max(0, Math.min(data.length - 1, idx)); setHover(idx); } const last = data.length; const xLabels = labels || ['9:15', '11:00', '12:45', '14:30', '15:30']; return (
setHover(null)}> {gridVals.map((gv, i) => { const y = padTop + usableH - ((gv - min) / (max - min || 1)) * usableH; return ; })} {hover != null && ( )} {/* x axis labels */}
{xLabels.map((l, i) => {l})}
{hover != null && (
₹{data[hover].toLocaleString('en-IN')}
{xLabels[Math.min(Math.floor(hover / data.length * xLabels.length), xLabels.length - 1)] || ''}
)}
); } // ---------- Bar chart ---------- function BarChart({ data, height = 120, color = 'var(--accent)' }) { const [ref, w] = useWidth(); const [hover, setHover] = useState(null); const max = Math.max(...data) || 1; const n = data.length; const gap = 3; const bw = (w - gap * (n - 1)) / n; const r = Math.min(4, bw / 2); return (
{data.map((v, i) => { const bh = Math.max((v / max) * (height - 4), 2); const x = i * (bw + gap); const y = height - bh; return ( setHover(i)} onMouseLeave={() => setHover(null)} /> ); })}
); } Object.assign(window, { Sparkline, AreaChart, BarChart });