// 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 (
);
}
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 (
);
}
// 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 (
);
}
return { EquityCurve, Sparkline, RollingSharpe, AllocationDonut, pathFromPoints };
})();