// Shared SVG chart primitives — equity curve, sparkline, rolling sharpe, // allocation donut. No deps; pure SVG. Accept theme colors via props. window.ApexCharts = (function () { const { useMemo, useState, useRef, useEffect } = React; function pathFromPoints(pts, w, h, pad = 0) { if (!pts || !pts.length) return ''; const xs = pts.map(p => p.i); const ys = pts.map(p => p.v); const xmin = Math.min(...xs), xmax = Math.max(...xs); const ymin = Math.min(...ys), ymax = Math.max(...ys); const sx = (x) => pad + ((x - xmin) / Math.max(1e-6, xmax - xmin)) * (w - pad * 2); const sy = (y) => (h - pad) - ((y - ymin) / Math.max(1e-6, ymax - ymin)) * (h - pad * 2); let d = `M ${sx(pts[0].i).toFixed(2)} ${sy(pts[0].v).toFixed(2)}`; for (let k = 1; k < pts.length; k++) { d += ` L ${sx(pts[k].i).toFixed(2)} ${sy(pts[k].v).toFixed(2)}`; } return { d, sx, sy, xmin, xmax, ymin, ymax }; } // Equity curve with optional area fill, grid, hover tooltip. function EquityCurve({ data, width = 800, height = 280, color = '#00D4C8', fill = 'rgba(0,212,200,0.08)', grid = 'rgba(255,255,255,0.04)', axis = 'rgba(255,255,255,0.35)', label = 'Equity', mono = 'ui-monospace, SFMono-Regular, Menlo, monospace', showAxis = true }) { const svgRef = useRef(null); const [hover, setHover] = useState(null); const built = useMemo(() => pathFromPoints(data, width, height, 16), [data, width, height]); if (!built) return null; const { d, sx, sy, xmin, xmax, ymin, ymax } = built; const areaD = d + ` L ${sx(xmax).toFixed(2)} ${height - 16} L ${sx(xmin).toFixed(2)} ${height - 16} Z`; // Y gridlines (5) const ticks = 4; const gridlines = []; for (let k = 0; k <= ticks; k++) { const y = 16 + ((height - 32) * k) / ticks; gridlines.push(y); } function handleMove(e) { const rect = svgRef.current.getBoundingClientRect(); const x = ((e.clientX - rect.left) / rect.width) * width; // find nearest point let best = 0, bestDist = Infinity; for (let k = 0; k < data.length; k++) { const px = sx(data[k].i); const dx = Math.abs(px - x); if (dx < bestDist) { bestDist = dx; best = k; } } const p = data[best]; setHover({ x: sx(p.i), y: sy(p.v), v: p.v, i: p.i }); } const pctChange = ((data[data.length - 1].v / data[0].v) - 1) * 100; return ( setHover(null)}> {gridlines.map((y, k) => ( ))} {showAxis && ( <> {ymax.toFixed(1)} {ymin.toFixed(1)} )} {hover && ( D+{hover.i} {hover.v.toFixed(2)} ({((hover.v/100-1)*100).toFixed(2)}%) )} ); } function Sparkline({ data, width = 120, height = 32, color = '#00C389', strokeW = 1.2 }) { const built = useMemo(() => pathFromPoints(data, width, height, 2), [data, width, height]); if (!built) return null; const up = data[data.length - 1].v >= data[0].v; const stroke = color || (up ? '#00C389' : '#E5484D'); return ( ); } function RollingSharpe({ data, width = 420, height = 120, color = '#00D4C8', grid = 'rgba(255,255,255,0.05)', mono = 'ui-monospace, SFMono-Regular, Menlo, monospace' }) { const built = useMemo(() => pathFromPoints(data, width, height, 10), [data, width, height]); if (!built) return null; const lastVal = data[data.length - 1].v; // Horizontal reference lines at 1.0, 2.0 const refY1 = built.sy(1.0), refY2 = built.sy(2.0); return ( 2.0 1.0 ); } // Allocation donut function AllocationDonut({ segments, size = 180, stroke = 22, colors = ['#00D4C8','#3B82F6','#A78BFA','#F59E0B','#EC4899','#22C55E','#EAB308','#06B6D4','#F97316','#84CC16'], bg = 'rgba(255,255,255,0.05)' }) { const total = segments.reduce((s, x) => s + x.v, 0); const r = (size - stroke) / 2; const cx = size / 2, cy = size / 2; const C = 2 * Math.PI * r; let acc = 0; return ( {segments.map((seg, i) => { const frac = seg.v / total; const len = C * frac; const offset = C * (acc / total); acc += seg.v; return ( ); })} ); } return { EquityCurve, Sparkline, RollingSharpe, AllocationDonut, pathFromPoints }; })();