import { ChartInput, getTheme } from "../types.js";
function esc(s: string): string {
return s.replace(/&/g, "&").replace(//g, ">");
}
function truncate(s: string, n: number): string {
return s.length > n ? s.slice(0, n - 1) + "…" : s;
}
// ─── GANTT ───────────────────────────────────────────────────────────────────
export function generateGanttChart(input: ChartInput): string {
const theme = getTheme(input.style);
const W = input.style?.width ?? 640;
const font = input.style?.fontFamily ?? "Inter, system-ui, sans-serif";
const rowH = 36;
const padTop = input.title ? 50 : 20;
const padBottom = 40;
const padLeft = 110;
const padRight = 20;
const n = input.data.length;
const H = Math.max(input.style?.height ?? 0, padTop + padBottom + n * rowH + 10);
const allStarts = input.data.map((d) => d.start ?? 0);
const allEnds = input.data.map((d) => d.end ?? 0);
const axisMin = Math.min(...allStarts, 0);
const axisMax = Math.max(...allEnds, 100);
const axisRange = axisMax - axisMin || 1;
const chartW = W - padLeft - padRight;
const xPx = (v: number) => padLeft + ((v - axisMin) / axisRange) * chartW;
let svg = ``;
return svg;
}
// ─── MEKKO ───────────────────────────────────────────────────────────────────
export function generateMekkoChart(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 padTop = input.title ? 50 : 20;
const padBottom = 40;
const padLeft = 20;
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 grandTotal = totals.reduce((a, b) => a + b, 0);
let svg = ``;
return svg;
}
// ─── RADAR ───────────────────────────────────────────────────────────────────
export function generateRadarChart(input: ChartInput): string {
const theme = getTheme(input.style);
const W = input.style?.width ?? 520;
const H = input.style?.height ?? 460;
const font = input.style?.fontFamily ?? "Inter, system-ui, sans-serif";
const padTop = input.title ? 40 : 20;
const padBottom = 20;
const padSide = 60;
const cx = W / 2;
const cy = padTop + (H - padTop - padBottom) / 2;
const maxR = Math.min((W - 2 * padSide) / 2, (H - padTop - padBottom) / 2);
const n = input.data.length;
const color = theme.palette[0];
const levels = 5;
function angleFor(i: number) {
return ((i / n) * 2 * Math.PI) - Math.PI / 2;
}
function point(i: number, r: number) {
const a = angleFor(i);
return { x: cx + r * Math.cos(a), y: cy + r * Math.sin(a) };
}
let svg = ``;
return svg;
}
// ─── HEATMAP ─────────────────────────────────────────────────────────────────
export function generateHeatmapChart(input: ChartInput): string {
const theme = getTheme(input.style);
const font = input.style?.fontFamily ?? "Inter, system-ui, sans-serif";
// Extract unique rows and cols
const rows = [...new Set(input.data.map((d) => d.row ?? ""))];
const cols = [...new Set(input.data.map((d) => d.col ?? ""))];
const padTop = input.title ? 50 : 20;
const padBottom = 40;
const padLeft = 80;
const padRight = 30;
const cellW = Math.max(40, Math.min(80, 500 / cols.length));
const cellH = Math.max(28, Math.min(60, 400 / rows.length));
const W = input.style?.width ?? padLeft + cols.length * cellW + padRight;
const H = input.style?.height ?? padTop + rows.length * cellH + padBottom;
const values = input.data.map((d) => d.value ?? 0);
const minVal = Math.min(...values);
const maxVal = Math.max(...values);
const valRange = maxVal - minVal || 1;
function cellColor(v: number): string {
const t = (v - minVal) / valRange;
// Interpolate: low = dim blue → high = accent
const accent = theme.accent;
// Parse hex
const r = parseInt(accent.slice(1, 3), 16);
const g = parseInt(accent.slice(3, 5), 16);
const b = parseInt(accent.slice(5, 7), 16);
const bgR = theme.background === "#0a0a0a" ? 20 : 220;
const bgG = bgR;
const bgB = bgR + 30;
const cr = Math.round(bgR + t * (r - bgR));
const cg = Math.round(bgG + t * (g - bgG));
const cb = Math.round(bgB + t * (b - bgB));
return `rgb(${cr},${cg},${cb})`;
}
function textColor(v: number): string {
const t = (v - minVal) / valRange;
return t > 0.5 ? "white" : theme.subtext;
}
let svg = ``;
return svg;
}