import { ChartInput, ThemeColors, getTheme } from "../types.js";
function fmt(n: number): string {
if (Math.abs(n) >= 1_000_000) return (n / 1_000_000).toFixed(1) + "M";
if (Math.abs(n) >= 1_000) return (n / 1_000).toFixed(1) + "K";
return n.toFixed(n % 1 === 0 ? 0 : 1);
}
export function generateBarChart(input: ChartInput): string {
const theme = getTheme(input.style);
const W = input.style?.width ?? 600;
const H = input.style?.height ?? 400;
const font = input.style?.fontFamily ?? "Inter, system-ui, sans-serif";
const showValues = input.style?.showValues !== false;
const showGrid = input.style?.showGrid !== false;
const padTop = input.title ? 50 : 20;
const padBottom = 55;
const padLeft = 60;
const padRight = 20;
const chartW = W - padLeft - padRight;
const chartH = H - padTop - padBottom;
const values = input.data.map((d) => d.value ?? 0);
const maxVal = Math.max(...values, 0);
const minVal = Math.min(...values, 0);
const range = maxVal - minVal || 1;
// Nice axis
const niceMax = niceNumber(maxVal, minVal);
const niceMin = minVal < 0 ? -niceNumber(Math.abs(minVal), 0) : 0;
const niceRange = niceMax - niceMin;
const n = input.data.length;
const groupW = chartW / n;
const barPad = Math.max(4, groupW * 0.2);
const barW = groupW - barPad;
const yScale = (v: number) => chartH - ((v - niceMin) / niceRange) * chartH;
const zeroY = yScale(0);
const gridLines = 5;
let svg = ``;
return svg;
}
export function generateGroupedBarChart(input: ChartInput): string {
const theme = getTheme(input.style);
const W = input.style?.width ?? 640;
const H = input.style?.height ?? 420;
const font = input.style?.fontFamily ?? "Inter, system-ui, sans-serif";
const showValues = input.style?.showValues ?? false;
const showGrid = input.style?.showGrid !== false;
const showLegend = input.style?.showLegend !== false;
const legendH = showLegend ? 30 : 0;
const padTop = input.title ? 50 : 20;
const padBottom = 55 + legendH;
const padLeft = 60;
const padRight = 20;
const chartW = W - padLeft - padRight;
const chartH = H - padTop - padBottom;
// Determine number of series
const seriesCount = Math.max(...input.data.map((d) => d.values?.length ?? 0));
const allValues = input.data.flatMap((d) => d.values ?? []);
const maxVal = Math.max(...allValues, 0);
const minVal = Math.min(...allValues, 0);
const niceMax = niceNumber(maxVal, minVal);
const niceMin = minVal < 0 ? -niceNumber(Math.abs(minVal), 0) : 0;
const niceRange = niceMax - niceMin;
const n = input.data.length;
const groupW = chartW / n;
const groupPad = Math.max(4, groupW * 0.15);
const innerW = groupW - groupPad;
const barW = innerW / seriesCount - 2;
const yScale = (v: number) => chartH - ((v - niceMin) / niceRange) * chartH;
const zeroY = yScale(0);
let svg = ``;
return svg;
}
export function generateStackedBarChart(input: ChartInput): string {
const theme = getTheme(input.style);
const W = input.style?.width ?? 640;
const H = input.style?.height ?? 420;
const font = input.style?.fontFamily ?? "Inter, system-ui, sans-serif";
const showGrid = input.style?.showGrid !== false;
const showLegend = input.style?.showLegend !== false;
const legendH = showLegend ? 30 : 0;
const padTop = input.title ? 50 : 20;
const padBottom = 55 + legendH;
const padLeft = 60;
const padRight = 20;
const chartW = W - padLeft - padRight;
const chartH = H - padTop - padBottom;
const totals = input.data.map((d) => (d.values ?? []).reduce((a, b) => a + b, 0));
const maxVal = Math.max(...totals, 0);
const niceMax = niceNumber(maxVal, 0);
const n = input.data.length;
const groupW = chartW / n;
const barPad = Math.max(4, groupW * 0.2);
const barW = groupW - barPad;
const yScale = (v: number) => chartH * (1 - v / niceMax);
let svg = ``;
return svg;
}
function niceNumber(max: number, min: number): number {
if (max <= 0) return 10;
const range = max - Math.min(min, 0);
const magnitude = Math.pow(10, Math.floor(Math.log10(range)));
const fraction = range / magnitude;
let nice: number;
if (fraction <= 1) nice = 1;
else if (fraction <= 2) nice = 2;
else if (fraction <= 5) nice = 5;
else nice = 10;
return Math.ceil(max / (nice * magnitude)) * nice * magnitude;
}
function esc(s: string): string {
return s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """);
}
function truncate(s: string, n: number): string {
return s.length > n ? s.slice(0, n - 1) + "…" : s;
}