import { useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; import { ScatterChart, Scatter, Cell, XAxis, YAxis, ZAxis, CartesianGrid, Tooltip, ResponsiveContainer, ReferenceLine, ReferenceArea, } from 'recharts'; import { getRegimeHistory, getRegimeMonitor } from '../../api/regime'; import { Callout } from '../ui/Callout'; import { SkeletonCard } from '../ui/Skeleton'; // Lazy-loaded (see RegimePage) so recharts stays in the regime-tab chunk. // Quadrant boundaries come from the backend v2 methodology response. const TRAIL = 60; // sessions shown interface QPoint { x: number; y: number; date: string; } /** Centered moving average to de-noise the path; today (last) kept exact. */ function smoothTrail(points: QPoint[], half = 2): QPoint[] { const n = points.length; return points.map((p, i) => { if (i === n - 1) return { ...p }; let sx = 0; let sy = 0; let c = 0; for (let j = Math.max(0, i - half); j <= Math.min(n - 1, i + half); j++) { sx += points[j].x; sy += points[j].y; c += 1; } return { x: sx / c, y: sy / c, date: p.date }; }); } /** Recency gradient: 0 = oldest (muted slate), 1 = newest (bright blue). */ function recencyColor(t: number): string { const lerp = (a: number, b: number) => Math.round(a + (b - a) * t); const r = lerp(71, 96); const g = lerp(85, 165); const b = lerp(105, 250); const alpha = (0.3 + 0.7 * t).toFixed(2); return `rgba(${r}, ${g}, ${b}, ${alpha})`; } function QuadrantTip({ active, payload }: { active?: boolean; payload?: { payload: QPoint }[] }) { if (!active || !payload?.length) return null; const p = payload[0].payload; return (
White dot = today; the trail fades from muted (older) to bright blue (newer) over the last {TRAIL}{' '} sessions, smoothed. The path matters more than a single point. Risk thermometer — not an entry, exit, or sizing signal.
> )}