import type { ChartSpec } from './types'; import { COLORS, GRID, INK, Label, SERIES, SOFT, bidi, fa, mathText, niceStep, ticksBetween } from './svg'; const W = 330; /** نمودارِ ستونی، خط‌شکسته و دایره‌ای. درصدِ دایره‌ای را کد از روی مقدارها حساب می‌کند. */ export function Chart({ spec }: { spec: ChartSpec }) { const n = Math.min(spec.labels.length, spec.values.length); if (!n) return null; const labels = spec.labels.slice(0, n), values = spec.values.slice(0, n); return spec.kind === 'pie' ? : ; } function Bars({ kind, labels, values, unit }: { kind: 'bar' | 'linechart'; labels: string[]; values: number[]; unit?: string }) { const H = 230, left = 38, right = 12, top = 22, bottom = 44; const lo = Math.min(0, ...values), hi = Math.max(0, ...values); const step = niceStep(hi - lo || 1, 5); const y0 = Math.floor(lo / step) * step, y1 = Math.ceil(hi / step) * step || step; const plotW = W - left - right, plotH = H - top - bottom; const Y = (v: number) => top + ((y1 - v) / (y1 - y0)) * plotH; const slot = plotW / labels.length; const X = (i: number) => left + slot * (i + 0.5); const barW = Math.min(38, slot * 0.62); const long = labels.some((l) => l.length > 6); return ( {ticksBetween(y0, y1, step).map((t) => ( {fa(t)} ))} {unit && {mathText(unit)}} {kind === 'bar' ? values.map((v, i) => { const c = COLORS[SERIES[i % SERIES.length]]; return ( ); }) : ( <> `${i ? 'L' : 'M'}${X(i)},${Y(v)}`).join(' ')} fill="none" stroke={COLORS.blue} strokeWidth={2.6} strokeLinejoin="round" /> {values.map((v, i) => ( ))} )} {labels.map((l, i) => ( {mathText(l)} ))} ); } function Pie({ labels, values }: { labels: string[]; values: number[] }) { const total = values.reduce((a, v) => a + Math.max(0, v), 0); if (total <= 0) return null; const cx = 105, cy = 105, r = 88; let a = -Math.PI / 2; const slices = values.map((v, i) => { const frac = Math.max(0, v) / total, a0 = a, a1 = a + frac * 2 * Math.PI; a = a1; return { i, frac, a0, a1, c: COLORS[SERIES[i % SERIES.length]] }; }); const H = Math.max(215, 24 + labels.length * 22); return ( {slices.map((s) => { if (s.frac >= 0.9999) return ; const large = s.a1 - s.a0 > Math.PI ? 1 : 0; const p0 = [cx + r * Math.cos(s.a0), cy + r * Math.sin(s.a0)], p1 = [cx + r * Math.cos(s.a1), cy + r * Math.sin(s.a1)]; return ; })} {slices.filter((s) => s.frac >= 0.06).map((s) => { const m = (s.a0 + s.a1) / 2; return ( {fa(s.frac * 100, 1)}٪ ); })} {labels.map((l, i) => ( {`${l} (${fa(values[i])})`} ))} ); }