/* global React */
/* Charts: Sparkline, Line, Bar, Area, KPI metrics — all SVG, no deps */
const { useMemo, useState, useRef, useEffect } = React;
function useMeasure() {
const ref = useRef(null);
const [size, setSize] = useState({ w: 0, h: 0 });
useEffect(() => {
if (!ref.current) return;
const ro = new ResizeObserver((entries) => {
for (const e of entries) {
const cr = e.contentRect;
setSize({ w: Math.round(cr.width), h: Math.round(cr.height) });
}
});
ro.observe(ref.current);
return () => ro.disconnect();
}, []);
return [ref, size];
}
function _scale(values, w, h, pad = 0) {
const min = Math.min(...values);
const max = Math.max(...values);
const r = max - min || 1;
return values.map((v, i) => [
(i / (values.length - 1)) * (w - pad * 2) + pad,
h - pad - ((v - min) / r) * (h - pad * 2),
]);
}
function Sparkline({ data, w = 120, h = 36, color = "currentColor", area = true, stroke = 1.5 }) {
const pts = useMemo(() => _scale(data, w, h, 2), [data, w, h]);
const d = pts.map(([x, y], i) => `${i === 0 ? "M" : "L"}${x.toFixed(1)},${y.toFixed(1)}`).join(" ");
const areaD = `${d} L${w - 2},${h - 2} L2,${h - 2} Z`;
return (
);
}
/* ---------- LineChart ---------- */
function LineChart({ series = [], labels = [], w: wIn = 720, h: hIn = 280, gradient = true, yTicks = 4, fill = false }) {
const [ref, size] = useMeasure();
const w = fill && size.w ? size.w : wIn;
const h = fill && size.h ? size.h : hIn;
const pad = { t: 16, r: 16, b: 28, l: 40 };
const cw = w - pad.l - pad.r;
const ch = h - pad.t - pad.b;
const all = series.flatMap(s => s.data);
const min = Math.min(0, ...all);
const max = Math.max(...all);
const r = max - min || 1;
const x = (i, n) => pad.l + (i / (n - 1)) * cw;
const y = (v) => pad.t + ch - ((v - min) / r) * ch;
const ticks = useMemo(() => {
const arr = [];
for (let i = 0; i <= yTicks; i++) {
const v = min + (r * i / yTicks);
arr.push({ y: y(v), v });
}
return arr;
}, [min, max, ch, yTicks]);
const colors = ["var(--ink)", "var(--grad-4)", "var(--grad-1)", "var(--grad-2)"];
const svg = (
);
if (!fill) return svg;
return
{size.w ? svg : null}
;
}
/* ---------- BarChart ---------- */
function BarChart({ data = [], labels = [], w: wIn = 720, h: hIn = 240, color = "var(--ink)", stacked, fill = false }) {
const [ref, size] = useMeasure();
const w = fill && size.w ? size.w : wIn;
const h = fill && size.h ? size.h : hIn;
const pad = { t: 12, r: 12, b: 28, l: 36 };
const cw = w - pad.l - pad.r;
const ch = h - pad.t - pad.b;
const max = Math.max(...data.flat());
const groupW = cw / data.length;
const barW = Math.min(groupW * 0.55, 32);
const svg = (
);
if (!fill) return svg;
return {size.w ? svg : null}
;
}
/* ---------- AreaChart ---------- */
function AreaChart({ data = [], labels = [], w = 720, h = 200 }) {
const pad = { t: 8, r: 8, b: 24, l: 36 };
const cw = w - pad.l - pad.r;
const ch = h - pad.t - pad.b;
const max = Math.max(...data);
const min = Math.min(0, ...data);
const r = max - min || 1;
const pts = data.map((v, i) => [
pad.l + (i / (data.length - 1)) * cw,
pad.t + ch - ((v - min) / r) * ch,
]);
const d = pts.map(([x, y], i) => `${i === 0 ? "M" : "L"}${x.toFixed(1)},${y.toFixed(1)}`).join(" ");
const fillD = `${d} L${pad.l + cw},${pad.t + ch} L${pad.l},${pad.t + ch} Z`;
return (
);
}
/* ---------- KPI Card ---------- */
function KPICard({ label, value, delta, deltaLabel = "vs last 7d", spark, gradient, big }) {
const positive = delta != null && delta >= 0;
return (
{label}
{delta != null && (
{positive ? "▲" : "▼"} {Math.abs(delta).toFixed(1)}%
)}
{gradient ? {value} : {value}}
{(spark || deltaLabel) && (
{deltaLabel && {deltaLabel}}
{spark && }
)}
);
}
/* ---------- Stat Block (editorial, bordered top) ---------- */
function StatBlock({ label, value, sub, gradient }) {
return (
{gradient ? {value} : value}
{label}
{sub &&
{sub}
}
);
}
/* ---------- Donut ---------- */
function Donut({ value = 60, size = 120, stroke = 12, color = "var(--brand-grad)", label, sub }) {
const r = (size - stroke) / 2;
const c = 2 * Math.PI * r;
const off = c - (value / 100) * c;
return (
{label || `${value}%`}
{sub && {sub}}
);
}
Object.assign(window, { Sparkline, LineChart, BarChart, AreaChart, KPICard, StatBlock, Donut });