// Volume-story overlay. // Static top bar + collapsible legend showing per-dataset corpus share. const VOLUME_DATA_URL = "./data/volume-datasets.json?v=volume-note-removed-1"; function escapeHtml(value) { return String(value ?? "") .replace(/&/g, "&") .replace(//g, ">") .replace(/"/g, """); } function fmtRows(n) { if (n >= 1_000_000) return (n / 1_000_000).toFixed(2).replace(/\.?0+$/, "") + "M"; if (n >= 1_000) return (n / 1_000).toFixed(n >= 100_000 ? 0 : 1).replace(/\.?0+$/, "") + "k"; return String(n); } function fmtRowsExact(n) { return Number(n ?? 0).toLocaleString("en-US"); } function stageClass(stage) { return String(stage ?? "other").replace(/[^A-Za-z0-9_-]+/g, "-"); } async function loadVolumeData() { const response = await fetch(VOLUME_DATA_URL); if (!response.ok) throw new Error(`volume-datasets.json: HTTP ${response.status}`); const data = await response.json(); const datasets = Array.isArray(data.datasets) ? data.datasets : []; if (datasets.length === 0) throw new Error("volume-datasets.json has no datasets"); const totals = data.totals ?? { datasets: datasets.length, rows: datasets.reduce((sum, dataset) => sum + Number(dataset.rows ?? 0), 0), sampled_rows: datasets.reduce((sum, dataset) => sum + Number(dataset.sampled_rows ?? 0), 0), }; return { datasets, totals }; } function buildBar(totals) { const rowsPerPoint = Math.round(Number(totals.rows ?? 0) / Math.max(Number(totals.sampled_rows ?? 0), 1)); const bar = document.createElement("div"); bar.id = "volume-bar"; bar.innerHTML = ` ${fmtRowsExact(totals.datasets)} datasets · ${fmtRows(totals.rows)} rows · ${fmtRows(totals.sampled_rows)} shown · 1 point ≈ ${fmtRowsExact(rowsPerPoint)} rows `; document.body.appendChild(bar); return bar; } function buildLegend(datasets, totals) { const panel = document.createElement("div"); panel.id = "volume-legend"; panel.setAttribute("data-state", "collapsed"); const sorted = [...datasets].sort((a, b) => Number(b.rows ?? 0) - Number(a.rows ?? 0)); const max = Math.max(...sorted.map((dataset) => Number(dataset.rows ?? 0)), 1); const totalRows = Math.max(Number(totals.rows ?? 0), 1); const items = sorted.map((dataset) => { const rows = Number(dataset.rows ?? 0); const pct = (rows / max) * 100; const share = ((rows / totalRows) * 100).toFixed(1); const name = dataset.dataset_full_name ?? dataset.dataset; const filterValue = dataset.dataset ?? name; const stage = dataset.stage_short ?? dataset.pipeline_stage ?? "other"; const source = dataset.source_group === "ultra" ? "Ultra" : "Original"; return `
${fmtRowsExact(totals.datasets)} datasets · ${fmtRowsExact(totals.rows)} rows · ${fmtRowsExact(totals.sampled_rows)} shown · click to filter