import type { ChartSpec } from "../lib/chartspec";
// ChartView:把 chart spec 自绘成 SVG(bar / line / pie),无第三方图表依赖。
const PALETTE = ["#60a5fa", "#34d399", "#fbbf24", "#f87171", "#a78bfa", "#22d3ee", "#fb923c", "#4ade80"];
export function ChartView({ spec }: { spec: ChartSpec }) {
return (
{spec.title && {spec.title}}
{spec.type === "pie" ? : }
{spec.type !== "pie" && spec.series.length > 1 &&
);
}
function Legend({ names }: { names: string[] }) {
return (
{names.map((n, i) => (
{n}
))}
);
}
// BarLine:柱状图 / 折线图(共用坐标系)。
function BarLine({ spec }: { spec: ChartSpec }) {
const W = 480, H = 220, padL = 40, padB = 28, padT = 8, padR = 8;
const plotW = W - padL - padR, plotH = H - padT - padB;
const all = spec.series.flatMap((s) => s.data);
const max = Math.max(1, ...all);
const min = Math.min(0, ...all);
const span = max - min || 1;
const y = (v: number) => padT + plotH - ((v - min) / span) * plotH;
const n = spec.labels.length;
const slot = plotW / n;
return (
);
}
// Pie:饼图(用第一条系列)。
function Pie({ spec }: { spec: ChartSpec }) {
const data = spec.series[0]?.data ?? [];
const total = data.reduce((a, b) => a + Math.max(0, b), 0) || 1;
const cx = 110, cy = 110, r = 90;
let acc = -Math.PI / 2; // 从 12 点方向起
const arcs = data.map((v, i) => {
const frac = Math.max(0, v) / total;
const a0 = acc;
const a1 = acc + frac * Math.PI * 2;
acc = a1;
const large = a1 - a0 > Math.PI ? 1 : 0;
const x0 = cx + r * Math.cos(a0), y0 = cy + r * Math.sin(a0);
const x1 = cx + r * Math.cos(a1), y1 = cy + r * Math.sin(a1);
const d = `M${cx},${cy} L${x0.toFixed(2)},${y0.toFixed(2)} A${r},${r} 0 ${large} 1 ${x1.toFixed(2)},${y1.toFixed(2)} Z`;
return { d, color: PALETTE[i % PALETTE.length], label: spec.labels[i], pct: Math.round(frac * 100) };
});
return (
{arcs.map((a, i) => (
{a.label} · {a.pct}%
))}
);
}
function fmt(v: number): string {
if (Math.abs(v) >= 1000) return (v / 1000).toFixed(1) + "k";
return String(Math.round(v * 100) / 100);
}