Chennzi's picture
Deploy the website
7c3d1a4 verified
Raw History Blame Contribute Delete
8.54 kB
// DOM, formatting and color helpers, and components several pages share.
// Data strings only ever enter the DOM as text nodes.
export function h(tag, attrs = {}, ...children) {
const el = document.createElement(tag);
for (const [key, value] of Object.entries(attrs || {})) {
if (value == null || value === false) continue;
if (key === "class") el.className = value;
else if (key === "style" && typeof value === "object") Object.assign(el.style, value);
else if (key.startsWith("on")) el.addEventListener(key.slice(2), value);
else el.setAttribute(key, value === true ? "" : value);
}
for (const child of children.flat(Infinity)) {
if (child == null || child === false) continue;
el.append(child instanceof Node ? child : document.createTextNode(String(child)));
}
return el;
}
// Chart renderers re-run on resize and theme changes; pages register them here.
export const redraws = [];
export const parseDay = d3.utcParse("%Y-%m-%d");
export const fmt = {
int: d3.format(",.0f"),
ratio: (v) => (v == null ? "β€”" : d3.format(".3f")(v)),
pct: (v) => (v == null ? "β€”" : d3.format(".0%")(v)),
// whole numbers for large targets (MW), two decimals for small ones (river stage, AQHI)
value: (v) => (v == null ? "β€”" : d3.format(Math.abs(v) < 100 ? ",.2f" : ",.0f")(v)),
compact: (v) => (v == null ? "β€”" : d3.format(".3~s")(v).replace("G", "B")),
bytes: (v) => (v == null ? "β€”" : `${d3.format(".2~s")(v)}B`),
usd: (v) => (v == null ? "β€”" : `$${d3.format(".2f")(v)}`),
day: d3.utcFormat("%b %-d"),
dow: d3.utcFormat("%a %b %-d"),
long: d3.utcFormat("%a, %b %-d, %Y"),
};
export const dayLabel = (iso, style = "day") => (iso ? fmt[style](parseDay(iso)) : "β€”");
export function timeIn(iso, timeZone, withDate = false) {
if (!iso) return "β€”";
const options = { timeZone, hour: "2-digit", minute: "2-digit", hour12: false, timeZoneName: "short" };
if (withDate) Object.assign(options, { weekday: "short", month: "short", day: "numeric" });
return new Intl.DateTimeFormat("en-US", options).format(new Date(iso));
}
export function duration(seconds) {
if (seconds == null) return "β€”";
const m = Math.round(seconds / 60);
return m < 60 ? `${m} min` : `${Math.floor(m / 60)} h ${m % 60} min`;
}
export function host(url) {
try { return new URL(url).hostname.replace(/^www\./, ""); } catch { return null; }
}
// Color follows the system, never its rank: fixed slots for known systems, stable spill-over
// slots for new ones, and recessive gray for the seasonal-naive reference.
const SLOTS = {
"codex-cli-gpt-6.1-sol": 1,
"claude-code-claude-sonnet-5": 2,
"chronos-2": 3,
"chronos-2-univariate": 4,
"timesfm-3": 5,
"chronos-2-expert": 6,
"timesfm-3-expert": 8,
};
const SPARE = [6, 8];
let systemIndex = new Map();
export function setSystems(systems) {
systemIndex = new Map(systems.map((s) => [s.id, s]));
let spare = 0;
for (const s of systems) {
if (s.id in SLOTS || s.kind === "reference" || s.kind === "expert") continue;
// a continual agent takes its fresh twin's slot: the same agent, carrying on
const twin = s.id.replace(/-continual$/, "");
SLOTS[s.id] = twin in SLOTS ? SLOTS[twin] : SPARE[spare++ % SPARE.length];
}
}
export const system = (id) => systemIndex.get(id) || { id, kind: "agent", label: id, description: "" };
// every task's expert forecast shares one slot; a task page shows only its own
export const color = (id) => {
const kind = system(id).kind;
if (kind === "reference") return "var(--muted)";
return `var(--series-${kind === "expert" ? 7 : SLOTS[id] || 8})`;
};
export const KINDS = ["agent", "expert", "tsfm", "reference"];
export const KIND_GROUP = { agent: "Agents", expert: "Expert forecast", tsfm: "TSFM baselines", reference: "Reference" };
export const KIND_LABEL = { agent: "Agent", expert: "Expert", tsfm: "TSFM", reference: "Reference" };
export const bySystem = (a, b) =>
KINDS.indexOf(system(a).kind) - KINDS.indexOf(system(b).kind) || system(a).label.localeCompare(system(b).label);
export const keyLine = (id) => h("span", { class: "key-line", style: { background: color(id) } });
export const systemCell = (id) => h("span", { class: "system-cell", title: system(id).description }, keyLine(id), system(id).label);
export function segmented(options, current, onChange, label) {
return h("div", { class: "segmented", role: "group", "aria-label": label },
options.map(([value, text]) =>
h("button", { type: "button", "aria-pressed": String(value === current), onclick: () => onChange(value) }, text)));
}
export function sectionHead(id, title, text, ...extra) {
return h("div", { class: "section-head", id }, h("div", {}, h("h2", {}, title), text ? h("p", {}, text) : null), ...extra);
}
export const empty = (text) => h("div", { class: "empty" }, text);
/** Text whose $…$ spans are inline TeX, e.g. md("frozen as $X_d$"); for h() children. */
export const md = (text) => text.split(/\$([^$]+)\$/).map((part, i) => (i % 2 ? tex(part) : part));
/** TeX typeset by KaTeX when it loaded, else the source in a code span. */
export function tex(source, display = false) {
const el = h(display ? "div" : "span", { class: display ? "tex-display" : "tex" });
if (window.katex) window.katex.render(source, el, { displayMode: display, throwOnError: false, output: "html" });
else el.append(h("code", {}, source));
return el;
}
export function more(label, ...body) {
return h("details", { class: "more" }, h("summary", {}, label), h("div", { class: "body" }, ...body));
}
export function sourceLink(source) {
return source?.url
? h("a", { href: source.url, target: "_blank", rel: "noopener" }, source.name || host(source.url))
: source?.name || "β€”";
}
export function scoreBar(value, id, max) {
if (value == null) return h("span", { class: "muted" }, "β€”");
return h("div", { class: "bar-cell" },
h("div", { class: "bar-track" },
h("div", { class: "bar-fill", style: { width: `${(Math.min(value, max) / max) * 100}%`, background: color(id) } }),
max >= 1 ? h("div", { class: "bar-tick", style: { left: `${(1 / max) * 100}%` }, title: "seasonal naive = 1" }) : null),
h("span", { class: "bar-value" }, fmt.ratio(value)));
}
function coverage(value) {
if (value == null) return h("span", { class: "muted small" }, "point");
return h("span", { class: "cov", title: "share of actual values inside the 10–90% interval; 80% is calibrated" },
h("span", { class: "cov-track" },
h("span", { class: "bar-tick", style: { left: "80%" } }),
h("span", { class: "cov-dot", style: { left: `${value * 100}%` } })),
fmt.pct(value));
}
/** Leaderboard rows grouped by kind: agents, expert forecast, TSFM baselines, reference. */
export function leaderboard(rows, unit) {
if (!rows.length) return empty("No forecast has been scored yet.");
const max = Math.max(1.05, d3.max(rows, (r) => r.scaled_crps) || 1);
const groups = d3.group([...rows].sort((a, b) => (a.scaled_crps ?? 9) - (b.scaled_crps ?? 9)), (r) => system(r.system).kind);
return h("div", { class: "table-wrap" }, h("table", {},
h("thead", {}, h("tr", {},
h("th", {}, "System"), h("th", {}, "Scaled CRPS ↓"), h("th", { class: "num" }, "Scaled MAE"),
h("th", {}, "80% coverage"), h("th", { class: "num" }, `CRPS (${unit})`), h("th", { class: "num" }, "Pairs"))),
h("tbody", {}, KINDS.filter((k) => groups.has(k)).map((kind) => [
h("tr", { class: "group" }, h("td", { colspan: 6 }, KIND_GROUP[kind])),
groups.get(kind).map((r) => h("tr", {},
h("td", {}, systemCell(r.system)),
h("td", {}, scoreBar(r.scaled_crps, r.system, max)),
h("td", { class: "num" }, fmt.ratio(r.scaled_mae)),
h("td", {}, coverage(r.coverage80)),
h("td", { class: "num" }, fmt.value(r.crps)),
h("td", { class: "num" }, String(r.n)))),
]))));
}
const tooltip = () => document.getElementById("tooltip");
export function showTooltip(event, content) {
const el = tooltip();
el.replaceChildren(content);
el.hidden = false;
const { innerWidth, innerHeight } = window;
const rect = el.getBoundingClientRect();
let x = event.clientX + 16, y = event.clientY + 16;
if (x + rect.width > innerWidth - 8) x = event.clientX - rect.width - 16;
if (y + rect.height > innerHeight - 8) y = event.clientY - rect.height - 16;
el.style.left = `${Math.max(8, x)}px`;
el.style.top = `${Math.max(8, y)}px`;
}
export const hideTooltip = () => { tooltip().hidden = true; };