// aerOS — real SVG charts. Exports to window. All responsive via viewBox. const { useState: _cS, useId: _cId } = React; function useUid() { return React.useId ? React.useId().replace(/:/g, '') : Math.random().toString(36).slice(2); } /* ----------------------------- LineChart (area) ----------------------------- */ function LineChart({ data, height = 220, color = 'var(--brand)', labels, valueFmt = v => v, showDots = true, area = true }) { const [hover, setHover] = _cS(null); const uid = useUid(); const W = 600, H = height, padL = 8, padR = 8, padT = 16, padB = 28; const xs = data.length; const max = Math.max(...data, 1) * 1.12; const min = Math.min(...data, 0); const range = max - min || 1; const px = i => padL + (i * (W - padL - padR)) / Math.max(1, xs - 1); const py = v => padT + (1 - (v - min) / range) * (H - padT - padB); const pts = data.map((v, i) => [px(i), py(v)]); const line = pts.map((p, i) => (i === 0 ? 'M' : 'L') + p[0].toFixed(1) + ' ' + p[1].toFixed(1)).join(' '); const areaPath = line + ` L${px(xs - 1).toFixed(1)} ${H - padB} L${padL} ${H - padB} Z`; const gy = [0, 0.25, 0.5, 0.75, 1].map(f => padT + f * (H - padT - padB)); return ( setHover(null)}> {gy.map((y, i) => )} {area && } {showDots && pts.map((p, i) => ( setHover(i)} /> ))} {labels && labels.map((l, i) => (i % Math.ceil(xs / 8) === 0 || i === xs - 1) && ( {l} ))} {hover != null && ( {valueFmt(data[hover])} )} ); } /* ----------------------------- BarChart ----------------------------- */ function BarChart({ data, height = 220, color = 'var(--brand)', labels, valueFmt = v => v, horizontal = false }) { const [hover, setHover] = _cS(null); const max = Math.max(...data.map(d => typeof d === 'object' ? d.value : d), 1) * 1.1; const vals = data.map(d => typeof d === 'object' ? d : { value: d }); if (horizontal) { return (
{vals.map((d, i) => (
{labels ? labels[i] : d.label}
{valueFmt(d.value)}
))}
); } const W = 600, H = height, padB = 28, padT = 12, gap = 0.34; const n = vals.length; const bw = (W / n) * (1 - gap); return ( setHover(null)}> {[0.25, 0.5, 0.75, 1].map((f, i) => )} {vals.map((d, i) => { const h = (d.value / max) * (H - padT - padB); const x = (i + 0.5) * (W / n) - bw / 2; const y = H - padB - h; return ( setHover(i)}> {hover === i && {valueFmt(d.value)}} {labels && {labels[i]}} ); })} ); } /* ----------------------------- DonutChart ----------------------------- */ function DonutChart({ data, size = 180, thickness = 26, centerLabel, centerValue }) { const [hover, setHover] = _cS(null); const total = data.reduce((s, d) => s + d.value, 0) || 1; const R = size / 2, r = R - thickness / 2; const C = 2 * Math.PI * r; let offset = 0; return (
{data.map((d, i) => { const frac = d.value / total; const dash = frac * C; const seg = setHover(i)} onMouseLeave={() => setHover(null)} style={{ transition: 'stroke-width 120ms', cursor: 'pointer' }} />; offset += dash; return seg; })}
{hover != null ? data[hover].value : (centerValue ?? total)}
{hover != null ? data[hover].label : centerLabel}
{data.map((d, i) => (
setHover(i)} onMouseLeave={() => setHover(null)} style={{ display: 'flex', alignItems: 'center', gap: 9, cursor: 'pointer', opacity: hover == null || hover === i ? 1 : 0.5, transition: 'opacity 120ms' }}> {d.label} {Math.round(d.value / total * 100)}%
))}
); } /* ----------------------------- Sparkline ----------------------------- */ function Sparkline({ data, width = 90, height = 30, color = 'var(--brand)' }) { const max = Math.max(...data, 1), min = Math.min(...data, 0), range = max - min || 1; const px = i => (i * width) / Math.max(1, data.length - 1); const py = v => height - ((v - min) / range) * (height - 4) - 2; const line = data.map((v, i) => (i === 0 ? 'M' : 'L') + px(i).toFixed(1) + ' ' + py(v).toFixed(1)).join(' '); return ( ); } Object.assign(window, { LineChart, BarChart, DonutChart, Sparkline });