/* global React */ const { useMemo } = React; // Sparkline SVG function Spark({ data, color = "var(--accent)", height = 28, width = 120, fill = true }) { const { path, area, last } = useMemo(() => { if (!data || data.length === 0) return { path: "", area: "", last: 0 }; const min = Math.min(...data); const max = Math.max(...data); const range = max - min || 1; const w = width, h = height; const stepX = w / (data.length - 1); const pts = data.map((v, i) => [i * stepX, h - ((v - min) / range) * (h - 4) - 2]); const path = "M " + pts.map(p => p[0].toFixed(1) + " " + p[1].toFixed(1)).join(" L "); const area = path + ` L ${w} ${h} L 0 ${h} Z`; return { path, area, last: pts[pts.length - 1][1] }; }, [data, width, height]); const id = useMemo(() => "sg" + Math.random().toString(36).slice(2, 8), []); return ( {fill && ( <> )} ); } // Equity area chart function EquityChart({ data, height = 280 }) { const padding = { top: 16, right: 16, bottom: 24, left: 50 }; const w = 800, h = height; const innerW = w - padding.left - padding.right; const innerH = h - padding.top - padding.bottom; const { path, area, points, yTicks, xTicks, min, max } = useMemo(() => { if (!data?.length) return {}; const ys = data.map(d => d.y); const min = Math.min(...ys); const max = Math.max(...ys); const yRange = max - min || 1; const stepX = innerW / (data.length - 1); const pts = data.map((d, i) => [ padding.left + i * stepX, padding.top + innerH - ((d.y - min) / yRange) * innerH ]); const path = "M " + pts.map(p => p[0].toFixed(1) + " " + p[1].toFixed(1)).join(" L "); const area = path + ` L ${padding.left + innerW} ${padding.top + innerH} L ${padding.left} ${padding.top + innerH} Z`; const yTicks = [0, 0.25, 0.5, 0.75, 1].map(t => ({ y: padding.top + innerH - t * innerH, label: (min + t * yRange).toFixed(0) })); const xTicks = [0, 0.25, 0.5, 0.75, 1].map(t => ({ x: padding.left + t * innerW, label: `D-${Math.round((1 - t) * (data.length - 1))}` })); return { path, area, points: pts, yTicks, xTicks, min, max }; }, [data, w, h]); return ( {yTicks?.map((t, i) => ( ${t.label} ))} {xTicks?.map((t, i) => ( {t.label} ))} {area && } {path && } {points && points.length > 0 && ( )} ); } // Bar chart (daily P&L) function BarChart({ data, height = 200 }) { const padding = { top: 12, right: 12, bottom: 24, left: 36 }; const w = 520, h = height; const innerW = w - padding.left - padding.right; const innerH = h - padding.top - padding.bottom; const max = Math.max(...data.map(Math.abs)) || 1; const barW = innerW / data.length - 4; return ( {data.map((v, i) => { const x = padding.left + i * (barW + 4); const barH = (Math.abs(v) / max) * (innerH / 2); const y = v >= 0 ? padding.top + innerH / 2 - barH : padding.top + innerH / 2; return ( = 0 ? "var(--green)" : "var(--red)"} opacity="0.85" rx="1" /> ); })} +${max.toFixed(0)} -${max.toFixed(0)} ); } // Donut for win/loss function Donut({ wins, losses, size = 140 }) { const total = wins + losses; const winPct = wins / total; const radius = size / 2 - 12; const circ = 2 * Math.PI * radius; const winLen = circ * winPct; return (
{(winPct * 100).toFixed(1)}%
Win Rate
); } // Heatmap function Heatmap({ data }) { const days = ["Mon", "Tue", "Wed", "Thu", "Fri"]; const colorFor = (v) => { if (v < 0) return `rgba(255,93,108,${Math.min(1, Math.abs(v) * 1.5)})`; return `rgba(0,224,255,${Math.min(1, v * 0.9)})`; }; return (
{Array.from({ length: 24 }).map((_, h) => (
{h % 4 === 0 ? `${h}h` : ""}
))} {days.map((d) => (
{d}
{Array.from({ length: 24 }).map((_, h) => { const cell = data.find((c) => c.day === d && c.hour === h); return
; })} ))}
); } Object.assign(window, { Spark, EquityChart, BarChart, Donut, Heatmap });