85742ab165
Deploy Documentation / deploy (push) Has been cancelled
CPU Test / Test (Utilities, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (LLM proxy, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Others, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Store, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Utilities, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Weave, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (AgentOps, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (LLM proxy, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Others, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Weave, latest, Python 3.13) (push) Has been cancelled
Dashboard / Chromatic (push) Has been cancelled
CPU Test / Lint - fast (push) Has been cancelled
CPU Test / Lint - next (push) Has been cancelled
CPU Test / Lint - slow (push) Has been cancelled
CPU Test / Lint - JavaScript (push) Has been cancelled
CPU Test / Build documentation (push) Has been cancelled
CPU Test / Test (AgentOps, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (LLM proxy, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Others, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Store, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (Weave, legacy, Python 3.10) (push) Has been cancelled
CPU Test / Test (AgentOps, stable, Python 3.11) (push) Has been cancelled
CPU Test / Test (Store, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Utilities, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (Weave, stable, Python 3.12) (push) Has been cancelled
CPU Test / Test (AgentOps, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (LLM proxy, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Others, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Store, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (Utilities, latest, Python 3.13) (push) Has been cancelled
CPU Test / Test (JavaScript) (push) Has been cancelled
226 lines
7.1 KiB
JavaScript
226 lines
7.1 KiB
JavaScript
// Copyright (c) Microsoft. All rights reserved.
|
|
|
|
// ---- CSS helpers ---------------------------------------------------------
|
|
function matVar(name) {
|
|
return getComputedStyle(document.body).getPropertyValue(name).trim();
|
|
}
|
|
function toRGBA(color, a = 1) {
|
|
if (!color) return `rgba(0,0,0,${a})`;
|
|
const m = color.match(/^#?([\da-f]{3}|[\da-f]{6})$/i);
|
|
if (m) {
|
|
const hex = m[1].length === 3 ? m[1].split("").map((x) => x + x).join("") : m[1];
|
|
const r = parseInt(hex.slice(0, 2), 16);
|
|
const g = parseInt(hex.slice(2, 4), 16);
|
|
const b = parseInt(hex.slice(4, 6), 16);
|
|
return `rgba(${r}, ${g}, ${b}, ${a})`;
|
|
}
|
|
const nums = color.match(/[\d.]+/g) || [0, 0, 0, 1];
|
|
const [r, g, b] = nums.map(Number);
|
|
return `rgba(${r | 0}, ${g | 0}, ${b | 0}, ${a})`;
|
|
}
|
|
|
|
// ---- Theme defaults (pulled from MkDocs Material CSS vars) ---------------
|
|
function applyThemeDefaults() {
|
|
const font = matVar("--md-text-font").replace(/['"]/g, "") || "Roboto, sans-serif";
|
|
const text = matVar("--md-default-fg-color") || "#1f2937";
|
|
const border = "rgba(128, 128, 128, 0.1)"
|
|
const bg = "#777777";
|
|
|
|
Chart.defaults.font.family = font;
|
|
Chart.defaults.font.size = 16;
|
|
Chart.defaults.color = text;
|
|
Chart.defaults.borderColor = border;
|
|
Chart.defaults.backgroundColor = bg;
|
|
|
|
Chart.defaults.scale.grid.color = border;
|
|
Chart.defaults.scale.ticks.color = text;
|
|
Chart.defaults.scale.title.color = text;
|
|
|
|
Chart.defaults.plugins.legend.labels.color = text;
|
|
Chart.defaults.plugins.tooltip.titleColor = text;
|
|
Chart.defaults.plugins.tooltip.bodyColor = text;
|
|
Chart.defaults.plugins.tooltip.backgroundColor = toRGBA(bg, 0.5);
|
|
Chart.defaults.plugins.tooltip.borderColor = border;
|
|
Chart.defaults.plugins.tooltip.borderWidth = 1;
|
|
|
|
Chart.defaults.responsive = true;
|
|
Chart.defaults.maintainAspectRatio = false;
|
|
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
|
|
Chart.defaults.animation = false;
|
|
}
|
|
}
|
|
|
|
// ---- Dataset color defaults (Material primary/accent) --------------------
|
|
const colorScheme = ["#c45259", "#5276c4", "#f69047", "#7cc452", "#c2b00a"];
|
|
|
|
function applyDatasetDefaults(config) {
|
|
if (!config.data || !Array.isArray(config.data.datasets)) return;
|
|
config.data.datasets = config.data.datasets.map((ds, index) => {
|
|
const color = colorScheme[index % colorScheme.length];
|
|
return {
|
|
...ds,
|
|
borderColor: toRGBA(color, 0.8),
|
|
backgroundColor: toRGBA(color, 0.3),
|
|
pointBackgroundColor: color,
|
|
pointBorderColor: color,
|
|
};
|
|
});
|
|
}
|
|
|
|
// ---- Deep merge (config JSON + our defaults) ----------------------------
|
|
function deepMerge(target, src) {
|
|
if (!src || typeof src !== "object") return target;
|
|
for (const k of Object.keys(src)) {
|
|
const v = src[k];
|
|
if (v && typeof v === "object" && !Array.isArray(v)) {
|
|
target[k] = deepMerge(target[k] || {}, v);
|
|
} else {
|
|
target[k] = v;
|
|
}
|
|
}
|
|
return target;
|
|
}
|
|
|
|
// ---- Build final config for a canvas ------------------------------------
|
|
function buildConfig(baseCfg) {
|
|
const globalDefaults = {
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
interaction: { mode: "index", intersect: false },
|
|
plugins: {
|
|
legend: { position: "top" },
|
|
tooltip: { enabled: true },
|
|
},
|
|
layout: { padding: { top: 8, right: 8, bottom: 0, left: 0 } },
|
|
normalized: true,
|
|
alignToPixels: true,
|
|
animations: {
|
|
y: {
|
|
from: (ctx) => 300,
|
|
duration: 1500,
|
|
easing: "easeOutCubic",
|
|
},
|
|
radius: {
|
|
from: 0,
|
|
to: 3,
|
|
duration: 300,
|
|
delay: (ctx) => ctx.dataIndex * 30,
|
|
},
|
|
},
|
|
elements: { line: { tension: 0.3 } },
|
|
},
|
|
};
|
|
const merged = deepMerge({}, globalDefaults);
|
|
applyDatasetDefaults(baseCfg);
|
|
deepMerge(merged, baseCfg); // user config wins
|
|
return merged;
|
|
}
|
|
|
|
(function () {
|
|
// registry stores per-canvas state: { chart, cfg }
|
|
const registry = new WeakMap(); // canvas -> { chart, cfg }
|
|
|
|
// IntersectionObserver to (re)animate when visible
|
|
const io = new IntersectionObserver(
|
|
(entries) => {
|
|
entries.forEach((entry) => {
|
|
if (!entry.isIntersecting) return;
|
|
|
|
const canvas = entry.target;
|
|
const state = registry.get(canvas);
|
|
if (!state || !state.cfg) return;
|
|
|
|
// Respect reduced-motion: if disabled, just update without animation
|
|
const prefersReduced =
|
|
window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
|
|
// Destroy & rebuild to guarantee a fresh animation
|
|
if (state.chart) {
|
|
try {
|
|
state.chart.destroy();
|
|
} catch (_) {}
|
|
}
|
|
const ctx = canvas.getContext("2d");
|
|
const cfg = buildConfig(JSON.parse(JSON.stringify(state.cfg)));
|
|
|
|
// If reduced motion, skip animations
|
|
if (prefersReduced) {
|
|
cfg.options = cfg.options || {};
|
|
cfg.options.animation = false;
|
|
}
|
|
|
|
state.chart = new Chart(ctx, cfg);
|
|
registry.set(canvas, state);
|
|
});
|
|
},
|
|
{ threshold: 0.3 } // animate when ~30% visible
|
|
);
|
|
|
|
// ---- Render all canvases with data-chart JSON ---------------------------
|
|
function renderAll() {
|
|
document.querySelectorAll("canvas[data-chart]").forEach((canvas) => {
|
|
let parsedCfg;
|
|
try {
|
|
parsedCfg = JSON.parse(canvas.getAttribute("data-chart"));
|
|
} catch (e) {
|
|
console.error("Invalid data-chart JSON:", e, canvas);
|
|
return;
|
|
}
|
|
|
|
// store config; chart will be created by IntersectionObserver when visible
|
|
if (!registry.get(canvas)) {
|
|
registry.set(canvas, { chart: null, cfg: parsedCfg });
|
|
io.observe(canvas);
|
|
}
|
|
});
|
|
}
|
|
|
|
// ---- Retheme on scheme/primary/accent change ----------------------------
|
|
function retheme() {
|
|
applyThemeDefaults();
|
|
// Update visible charts without forcing animation
|
|
document.querySelectorAll("canvas[data-chart]").forEach((c) => {
|
|
const state = registry.get(c);
|
|
if (state?.chart) {
|
|
// Fix the issue that scale options color are not updated when theme changes
|
|
const scaleOptions = state.chart.options.scales;
|
|
for (const key of Object.keys(scaleOptions)) {
|
|
scaleOptions[key].ticks.color = Chart?.defaults?.scale?.ticks?.color;
|
|
scaleOptions[key].title.color = Chart?.defaults?.scale?.title?.color;
|
|
}
|
|
state.chart.update("none");
|
|
}
|
|
});
|
|
}
|
|
|
|
// Initial theme + render (works on hard refresh)
|
|
function boot() {
|
|
applyThemeDefaults();
|
|
renderAll();
|
|
}
|
|
|
|
if (document.readyState === "loading") {
|
|
document.addEventListener("DOMContentLoaded", boot);
|
|
} else {
|
|
boot();
|
|
}
|
|
|
|
// Observe theme flips. Attributes might be on <html> or <body>.
|
|
const attrs = ["data-md-color-scheme", "data-md-color-primary", "data-md-color-accent"];
|
|
const obs = new MutationObserver(retheme);
|
|
obs.observe(document.documentElement, {
|
|
attributes: true,
|
|
attributeFilter: attrs,
|
|
subtree: true,
|
|
});
|
|
|
|
// Re-scan on SPA navigations (Material)
|
|
if (typeof document$ !== "undefined" && document$.subscribe) {
|
|
document$.subscribe(() => {
|
|
renderAll(); // new canvases
|
|
retheme(); // keep colors in sync
|
|
});
|
|
}
|
|
})();
|