/* AAU CRM — lightweight SVG charts. Exposes on window. */ (function () { const GREEN = '#008060', INK = '#303030', GRID = '#ebebeb', SUB = '#8a8a8a'; function LineChart({ data, height = 220, color = INK, markers = [], fmt }) { const w = 640, h = height, pad = { l: 44, r: 16, t: 16, b: 28 }; const max = Math.max(...data.map(d => d.v)) * 1.1 || 1; const min = 0; const iw = w - pad.l - pad.r, ih = h - pad.t - pad.b; const x = i => pad.l + (i / (data.length - 1)) * iw; const y = v => pad.t + ih - ((v - min) / (max - min)) * ih; const path = data.map((d, i) => (i ? 'L' : 'M') + x(i) + ',' + y(d.v)).join(' '); const area = path + ` L${x(data.length - 1)},${pad.t + ih} L${x(0)},${pad.t + ih} Z`; const ticks = 4; return ( {Array.from({ length: ticks + 1 }).map((_, i) => { const v = min + (max - min) * (i / ticks); const yy = y(v); return {fmt ? fmt(v) : Math.round(v)}; })} {data.map((d, i) => i % Math.ceil(data.length / 8) === 0 && {d.l})} {markers.map((m, i) => {m.note})} ); } function BarChart({ data, height = 220, color = GREEN, fmt, target }) { const w = 640, h = height, pad = { l: 44, r: 16, t: 16, b: 28 }; const max = Math.max(...data.map(d => d.v), target || 0) * 1.15 || 1; const iw = w - pad.l - pad.r, ih = h - pad.t - pad.b; const bw = iw / data.length * 0.6; const x = i => pad.l + (i + 0.5) / data.length * iw; const y = v => pad.t + ih - (v / max) * ih; return ( {[0, 0.25, 0.5, 0.75, 1].map((t, i) => { const yy = pad.t + ih - t * ih; return {fmt ? fmt(max * t) : Math.round(max * t)}; })} {target != null && } {data.map((d, i) => {d.l})} ); } function StackedBar({ data, keys, height = 220, fmt }) { const w = 640, h = height, pad = { l: 44, r: 16, t: 16, b: 28 }; const max = Math.max(...data.map(d => keys.reduce((s, k) => s + (d[k.key] || 0), 0))) * 1.15 || 1; const iw = w - pad.l - pad.r, ih = h - pad.t - pad.b, bw = iw / data.length * 0.6; const x = i => pad.l + (i + 0.5) / data.length * iw; return ( {[0, 0.5, 1].map((t, i) => { const yy = pad.t + ih - t * ih; return ; })} {data.map((d, i) => { let acc = 0; return {keys.map(k => { const v = d[k.key] || 0; const hh = (v / max) * ih; const yy = pad.t + ih - acc - hh; acc += hh; return ; })}{d.l}; })} ); } function Donut({ data, size = 180, thickness = 28, center }) { const r = size / 2, ir = r - thickness, total = data.reduce((s, d) => s + d.v, 0) || 1; let a0 = -Math.PI / 2; const arc = (a0, a1) => { const x0 = r + r * Math.cos(a0), y0 = r + r * Math.sin(a0), x1 = r + r * Math.cos(a1), y1 = r + r * Math.sin(a1); const xi0 = r + ir * Math.cos(a1), yi0 = r + ir * Math.sin(a1), xi1 = r + ir * Math.cos(a0), yi1 = r + ir * Math.sin(a0); const large = a1 - a0 > Math.PI ? 1 : 0; return `M${x0},${y0} A${r},${r} 0 ${large} 1 ${x1},${y1} L${xi0},${yi0} A${ir},${ir} 0 ${large} 0 ${xi1},${yi1} Z`; }; return ( {data.map((d, i) => { const a1 = a0 + (d.v / total) * Math.PI * 2; const p = arc(a0, a1); a0 = a1; return {d.l}: {d.v}; })} {center && {center.v}} {center && {center.l}} ); } function Funnel({ steps, height }) { // steps: [{l, v, color}] const max = steps[0].v || 1; return (
{steps.map((s, i) => { const pct = Math.round((s.v / max) * 100); const conv = i > 0 ? Math.round((s.v / steps[i - 1].v) * 100) : 100; return (
{s.l}{AAU.fmtNum(s.v)} {i > 0 && · {conv}% từ bước trước}
{pct}%
); })}
); } function Radar({ data, size = 240 }) { // data: [{l, v}] v 0-10 const r = size / 2 - 30, cx = size / 2, cy = size / 2, n = data.length, maxV = 10; const pt = (i, val) => { const a = -Math.PI / 2 + (i / n) * Math.PI * 2; const rr = (val / maxV) * r; return [cx + rr * Math.cos(a), cy + rr * Math.sin(a)]; }; const poly = data.map((d, i) => pt(i, d.v).join(',')).join(' '); return ( {[0.25, 0.5, 0.75, 1].map((t, i) => pt(j, maxV * t).join(',')).join(' ')} fill="none" stroke={GRID} />)} {data.map((d, i) => { const [x, y] = pt(i, maxV); return ; })} {data.map((d, i) => { const [x, y] = pt(i, maxV + 0.1); const a = -Math.PI / 2 + (i / n) * Math.PI * 2; return {d.l}; })} {data.map((d, i) => { const [x, y] = pt(i, d.v); return ; })} ); } function Legend({ items }) { return
{items.map((it, i) => {it.l})}
; } Object.assign(window, { LineChart, BarChart, StackedBar, Donut, Funnel, Radar, Legend }); })();