window.rustProxyChartScale = (() => { const floors = { rate: 100, count: 10, ms: 100, bytes: 64 * 1024 * 1024, "bytes/s": 64 * 1024 }; const ceilings = new Map(); function peak(key, unit, series) { if (unit === "percent") return 100; const floor = floors[unit] ?? 1; let maximum = floor; for (const values of series) for (const value of values) { if (typeof value === "number" && Number.isFinite(value) && value >= 0) maximum = Math.max(maximum, value); } const magnitude = 10 ** Math.floor(Math.log10(maximum)); const tick = [1, 2, 5, 10].find(n => n * magnitude >= maximum) ?? 10; const result = Math.max(ceilings.get(key) ?? 0, tick * magnitude); ceilings.set(key, result); return result; } function liveRate(values) { const samples = (Array.isArray(values) ? values : []).map(value => typeof value === "number" && Number.isFinite(value) && value > 0 ? value : 0); const maximum = Math.max(0, ...samples); const recent = samples.slice(-24).sort((a, b) => a - b); const typical = recent.length ? recent[Math.floor((recent.length - 1) * 0.85)] : 0; const scale = Math.max(1, typical * 1.25); const points = samples.map((value, index) => { const x = index * 320 / Math.max(1, samples.length - 1); const y = 50 - Math.min(value / scale, 1) * 44; return `${x},${y}`; }).join(" "); return { points, peak: maximum, scale, clipped: maximum > scale }; } function reset(prefix) { for (const key of ceilings.keys()) if (key.startsWith(prefix)) ceilings.delete(key); } function axisValue(value, unit) { if (value === 0) return "0"; const n = Number(value); const format = v => v.toLocaleString(typeof locale === "function" ? locale() : undefined, { maximumFractionDigits: v < 1 ? 3 : v < 10 ? 2 : 1 }); if (unit === "bytes" || unit === "bytes/s") { let magnitude = n, suffix = "B"; for (const next of ["KiB", "MiB", "GiB", "TiB"]) { if (magnitude < 1024) break; magnitude /= 1024; suffix = next; } return `${format(magnitude)} ${suffix}${unit === "bytes/s" ? "/s" : ""}`; } return `${format(n)}${unit === "percent" ? "%" : unit === "ms" ? " ms" : unit === "rate" ? "/s" : ""}`; } function setAxisText(node, value) { if (node.textContent !== value) node.textContent = value; } function renderAxes(svg) { if (!svg?.isConnected || !svg.rustProxyAxisData) return; let host = svg.parentElement; if (!host?.classList.contains("rp-chart-axis-host")) { host = document.createElement("div"); host.className = "rp-chart-axis-host"; svg.before(host); host.append(svg); const overlay = document.createElement("div"); overlay.className = "rp-chart-axis-y"; overlay.setAttribute("aria-hidden", "true"); for (const name of ["top", "middle", "bottom"]) { const tick = document.createElement("span"); tick.className = `rp-chart-axis-${name}`; overlay.append(tick); } const perSeries = document.createElement("div"); perSeries.className = "rp-chart-axis-series"; perSeries.hidden = true; const x = document.createElement("div"); x.className = "rp-chart-axis-x"; x.setAttribute("aria-label", "X"); for (const name of ["start", "caption", "end"]) { const tick = document.createElement("span"); tick.className = `rp-chart-axis-${name}`; x.append(tick); } host.append(overlay, perSeries, x); } const { peak: maximum, unit, count, top, bottom, clipped, seriesScales } = svg.rustProxyAxisData; const compact = svg.closest(".stat-chart") !== null; host.classList.toggle("rp-chart-axis-compact", compact); const multi = Array.isArray(seriesScales) && seriesScales.length > 0; host.classList.toggle("rp-chart-axis-multiple", multi); const perSeries = host.querySelector(".rp-chart-axis-series"); perSeries.hidden = !multi; if (multi) { const values = seriesScales.map(([name, value]) => `${name}: 0–${axisValue(value, unit)}`); if (perSeries.dataset.values !== values.join("|")) { perSeries.replaceChildren(...values.map(value => { const label = document.createElement("span"); label.textContent = value; return label; })); perSeries.dataset.values = values.join("|"); } } host.style.setProperty("--rp-axis-top", `${Math.round(top * 1000) / 10}%`); host.style.setProperty("--rp-axis-bottom", `${Math.round(bottom * 1000) / 10}%`); setAxisText(host.querySelector(".rp-chart-axis-top"), `Y ${axisValue(maximum, unit)}${clipped ? " ↑" : ""}`); setAxisText(host.querySelector(".rp-chart-axis-middle"), axisValue(maximum / 2, unit)); setAxisText(host.querySelector(".rp-chart-axis-bottom"), "0"); setAxisText(host.querySelector(".rp-chart-axis-start"), compact && !multi ? `Y 0–${axisValue(maximum, unit)}` : count ? "1" : "–"); setAxisText(host.querySelector(".rp-chart-axis-caption"), `X · ${typeof t === "function" ? t("chartAxisSamples") : "samples"}`); setAxisText(host.querySelector(".rp-chart-axis-end"), count ? (compact ? `1–${count}` : String(count)) : "–"); host.querySelector(".rp-chart-axis-x").title = typeof t === "function" ? t("chartAxisSamplesHint") : "Sample indices, not elapsed time"; } function axes(target, peakValue, unit, count, { top = 0.08, bottom = 0.92, clipped = false, seriesScales = null } = {}) { const node = typeof target === "string" ? document.getElementById(target) : target; const svg = node?.tagName?.toLowerCase() === "svg" ? node : node?.ownerSVGElement; if (!svg || svg.classList.contains("ui-icon")) return; svg.rustProxyAxisData = { peak: Number.isFinite(peakValue) && peakValue > 0 ? peakValue : 1, unit, count: Number.isInteger(count) && count > 0 ? count : 0, top: Math.min(1, Math.max(0, top)), bottom: Math.min(1, Math.max(0, bottom)), clipped: clipped === true, seriesScales: Array.isArray(seriesScales) ? seriesScales.filter(row => Array.isArray(row) && typeof row[0] === "string" && Number.isFinite(row[1]) && row[1] >= 0) : null, }; if (document.documentElement.dataset.chartAxes === "true") renderAxes(svg); } function refreshAxes() { const show = document.documentElement.dataset.chartAxes === "true"; for (const svg of document.querySelectorAll("svg:not(.ui-icon)")) { if (show && svg.rustProxyAxisData) renderAxes(svg); else if (!show && svg.parentElement?.classList.contains("rp-chart-axis-host")) { const host = svg.parentElement; host.before(svg); host.remove(); } } } return { peak, liveRate, reset, axes, refreshAxes, axisValue }; })();