blob: 10859f2eb401895b68d476d79dc82b892d2063ae [file] [log] [blame]
/**
* @license
* Copyright 2023 The Go Authors. All rights reserved.
* Use of this source code is governed by a BSD-style
* license that can be found in the LICENSE file.
*/
import * as Plot from "@observablehq/plot";
import "../shared/base";
declare global {
interface Page {
Charts: ChartData;
}
interface ChartData {
Programs: Program[];
DateRange: [string, string];
}
interface Program {
ID: string;
Name: string;
Counters: Counter[];
Active: boolean;
}
interface Counter {
ID: string;
Name: string;
Data: Datum[];
}
interface Datum {
[key: string]: any;
Week: string;
Program: string;
Version: string;
GOARCH: string;
GOOS: string;
GoVersion: string;
Key: string;
Value: number;
}
const Page: Page;
}
window.onload = function () {
drawCharts();
configSelector();
breadcrumbController();
sectionController();
};
// sectionController adds event listeners to the section headers
// to toggle them open and closed.
function sectionController() {
const html = document.querySelector("html")!;
for (const e of document.querySelectorAll("h2")) {
e.addEventListener("click", function () {
let closed = localStorage.getItem("closed-sections")?.split(",");
if (closed?.includes(this.id)) {
closed = closed.filter((v) => v !== this.id);
const str = closed.join(",");
localStorage.setItem("closed-sections", str);
html.setAttribute("data-closed-sections", str);
} else {
closed = [this.id].concat(closed ?? []);
const str = closed.join(",");
localStorage.setItem("closed-sections", str);
html.setAttribute("data-closed-sections", str);
}
});
}
}
// drawCharts draws the charts using @observable/plot. It is called when
// the page is first rendered and when a facet is selected.
function drawCharts() {
for (const program of Page.Charts.Programs ?? []) {
for (const counter of program.Counters ?? []) {
const rectYOpts: Plot.BinXInputs<Plot.RectYOptions> = {
tip: true,
x: (d: Datum) => new Date(d.Week),
y: (d: Datum) => d.Value,
interval: "week",
fill: (d: Datum) => {
const n = Number(d.Key);
return isNaN(n) ? d.Key : n;
},
};
const chart = Plot.plot({
nice: true,
x: {
domain: Page.Charts.DateRange.map((d) => new Date(d)),
label: "Week",
},
y: {
label: "Value",
},
color: {
type: "ordinal",
legend: true,
scheme: "Spectral",
reverse: true,
label: "Counter",
},
height: 256,
style: "overflow:visible;width:100%;background:transparent",
marks: [
Plot.rectY(counter.Data, Plot.binX({ y: "sum" }, rectYOpts)),
Plot.ruleY([0]),
],
});
document
.querySelector(`[data-chart-id="${counter.ID}"]`)
?.replaceChildren(chart);
}
}
}
// configSelector adds an event listener that reloads the page when a config
// version is selected.
function configSelector() {
const el = document.querySelector<HTMLButtonElement>(".js-selectConfig");
el?.addEventListener("change", () => {
const params = new URLSearchParams(location.search);
params.set(el.name, el.value);
history.replaceState(null, "", "?" + params.toString());
location.reload();
});
}
// breadcrumbController updates the navigation header as the user scrolls
// that page displaying information about the content currently in the
// viewport.
function breadcrumbController() {
const headings =
document.querySelectorAll<HTMLHeadingElement>("h1, h2, h3, h4");
const callback = debounce(() => {
let above: HTMLHeadingElement[] = [];
for (const h of headings) {
const rect = h.getBoundingClientRect();
if (rect.height && rect.top < 80) {
above.unshift(h);
}
}
if (above.length < 2) {
above = [];
}
let threshold = Infinity;
const els: HTMLHeadingElement[] = [];
for (const h of above) {
const level = Number(h.tagName[1]);
if (level < threshold) {
threshold = level;
els.unshift(h);
}
}
const breadcrumb = document.querySelector(".js-breadcrumb ol");
const items = [];
for (const h of els) {
breadcrumb?.replaceChildren;
const li = document.createElement("li");
const a = document.createElement("a");
a.href = `#${h.id}`;
a.innerText = h.getAttribute("data-label") ?? h.innerText;
li.appendChild(a);
items.push(li);
}
breadcrumb?.replaceChildren(...items);
}, 100);
const observer = new IntersectionObserver(callback);
for (const h of headings) {
observer.observe(h);
}
}
function debounce<T extends (...args: unknown[]) => unknown>(
callback: T,
wait: number
) {
let timeout: number;
return (...args: unknown[]) => {
clearTimeout(timeout);
timeout = setTimeout(() => callback(...args), wait);
};
}
export {};