(function () { "use strict"; // Tabellen sortieren (Klick auf th[data-sort]); leere Werte stehen immer am Ende. document.querySelectorAll("table.sortable").forEach(function (table) { table.querySelectorAll("th[data-sort]").forEach(function (th) { th.addEventListener("click", function () { var index = Array.prototype.indexOf.call(th.parentNode.children, th); var numeric = th.dataset.sort === "num"; var dir = th.getAttribute("aria-sort") === "descending" ? "ascending" : "descending"; table.querySelectorAll("th[aria-sort]").forEach(function (other) { other.removeAttribute("aria-sort"); }); th.setAttribute("aria-sort", dir); var body = table.tBodies[0]; var rows = Array.prototype.slice.call(body.rows); function valueOf(row) { var cell = row.cells[index]; return cell.dataset.value !== undefined ? cell.dataset.value : cell.textContent.trim(); } rows.sort(function (a, b) { var va = valueOf(a), vb = valueOf(b); if (numeric) { var na = va === "" ? null : parseFloat(va); var nb = vb === "" ? null : parseFloat(vb); if (na === null && nb === null) return 0; if (na === null) return 1; if (nb === null) return -1; return dir === "ascending" ? na - nb : nb - na; } return dir === "ascending" ? va.localeCompare(vb, "de") : vb.localeCompare(va, "de"); }); rows.forEach(function (row) { body.appendChild(row); }); }); }); }); // Zeilen nach Reasoning-Modus filtern. Die Schalter stehen nur im HTML, wenn eine Tabelle mehrere Modi // zeigt; ohne JavaScript bleiben alle Zeilen sichtbar. document.querySelectorAll("[data-filter-table]").forEach(function (group) { var table = document.getElementById(group.dataset.filterTable); if (!table || !table.tBodies[0]) return; var buttons = Array.prototype.slice.call(group.querySelectorAll("[data-reasoning-filter]")); buttons.forEach(function (button) { button.addEventListener("click", function () { var wanted = button.dataset.reasoningFilter; buttons.forEach(function (other) { var picked = other === button; other.classList.toggle("active", picked); other.setAttribute("aria-pressed", picked ? "true" : "false"); }); Array.prototype.slice.call(table.tBodies[0].rows).forEach(function (row) { row.hidden = wanted !== "all" && (row.dataset.reasoning || "default") !== wanted; }); }); }); }); // Läufe für den Vergleich auswählen (mindestens 2). var picks = document.querySelectorAll(".compare-pick"); var button = document.getElementById("compare-button"); var hidden = document.getElementById("compare-runs"); picks.forEach(function (box) { box.addEventListener("change", function () { var ids = Array.prototype.filter.call(picks, function (p) { return p.checked; }).map(function (p) { return p.value; }); hidden.value = ids.join(","); button.disabled = ids.length < 2; }); }); // Bestätigung vor gefährlichen Aktionen (Formulare mit data-confirm). document.querySelectorAll("form[data-confirm]").forEach(function (form) { form.addEventListener("submit", function (event) { if (!window.confirm(form.dataset.confirm)) event.preventDefault(); }); }); if (typeof Chart === "undefined") return; var css = getComputedStyle(document.documentElement); function token(name) { return css.getPropertyValue(name).trim(); } var ink = token("--ink"), ink2 = token("--ink-2"), muted = token("--muted"); var grid = token("--grid"), axisColor = token("--axis"), surface = token("--surface"); var series = [token("--series-1"), token("--series-2"), token("--series-3")]; var fontFamily = 'system-ui, -apple-system, "Segoe UI", sans-serif'; Chart.defaults.font.family = fontFamily; Chart.defaults.color = ink2; function axis(title, extra) { var config = { grid: { color: grid, drawTicks: false }, border: { color: axisColor }, ticks: { color: muted, padding: 6 } }; if (title) config.title = { display: true, text: title, color: ink2 }; return Object.assign(config, extra || {}); } function readJson(id) { var el = document.getElementById(id); return el ? JSON.parse(el.textContent) : null; } // Wert direkt am Balkenende (nur für die einreihige Übersicht). var barValueLabels = { id: "barValueLabels", afterDatasetsDraw: function (chart) { var meta = chart.getDatasetMeta(0); var ctx = chart.ctx; ctx.save(); ctx.fillStyle = ink; ctx.font = "12px " + fontFamily; ctx.textBaseline = "middle"; meta.data.forEach(function (bar, i) { var value = chart.data.datasets[0].data[i]; if (value !== null && value !== undefined) ctx.fillText(value.toFixed(1), bar.x + 6, bar.y); }); ctx.restore(); } }; // Wunschbereich im Streudiagramm: rechts von min. Tokens/s und über min. Note, leicht grün hinterlegt. // Fehlt einer der Werte, ist der Bereich zu dieser Seite hin offen. var zone = token("--zone"); var zonePlugin = { id: "zone", beforeDatasetsDraw: function (chart, args, opts) { if (!opts || (opts.minX == null && opts.minY == null)) return; var area = chart.chartArea; var left = opts.minX == null ? area.left : Math.max(area.left, chart.scales.x.getPixelForValue(opts.minX)); var bottom = opts.minY == null ? area.bottom : Math.min(area.bottom, chart.scales.y.getPixelForValue(opts.minY)); if (left >= area.right || bottom <= area.top) return; var ctx = chart.ctx; ctx.save(); ctx.fillStyle = zone; ctx.fillRect(left, area.top, area.right - left, bottom - area.top); ctx.restore(); } }; // Gewählte Kategorie je Browser merken – nur Bequemlichkeit, ohne Speicher geht es mit „Gesamt“ los. var CATEGORY_KEY = "bench.chartCategory"; function rememberedCategory() { try { return window.localStorage.getItem(CATEGORY_KEY); } catch (e) { return null; } } function rememberCategory(key) { try { window.localStorage.setItem(CATEGORY_KEY, key); } catch (e) { /* ohne Speicher */ } } // Übersicht (Webseite und Space): je .chart-set ein Balken- und ein Streudiagramm, umschaltbar nach Kategorie. document.querySelectorAll(".chart-set").forEach(function (set) { var dataEl = set.querySelector("script.chart-data"); if (!dataEl) return; var data = JSON.parse(dataEl.textContent); var texts = data.texts; var limits = data.thresholds || {}; var buttons = Array.prototype.slice.call(set.querySelectorAll("[data-chart-category]")); var barBox = set.querySelector('[data-chart-box="bar"]'); var barTitle = set.querySelector('[data-chart-title="bar"]'); var scatterTitle = set.querySelector('[data-chart-title="scatter"]'); var barChart = null, scatterChart = null; function titleOf(key) { if (key === "overall") return texts.overall_title; var match = data.categories.filter(function (c) { return c.key === key; })[0]; return match ? match.label : key; } function show(key) { var title = titleOf(key); buttons.forEach(function (b) { var picked = b.dataset.chartCategory === key; b.classList.toggle("active", picked); b.setAttribute("aria-pressed", picked ? "true" : "false"); }); if (barTitle) barTitle.textContent = title; if (scatterTitle) scatterTitle.textContent = title + texts.vs; var bars = data.runs.filter(function (r) { return r.scores[key] !== null && r.scores[key] !== undefined; }) .sort(function (a, b) { return b.scores[key] - a.scores[key]; }); var points = bars.filter(function (r) { return r.tps !== null; }) .map(function (r) { return { label: r.label, x: r.tps, y: r.scores[key], source: r.tps_source }; }); // Hohl: Geschwindigkeit nur aus dem Speed-Test, der Lauf lieferte keine Werte. var fills = points.map(function (p) { return p.source === "test" ? surface : series[0]; }); var edges = points.map(function (p) { return p.source === "test" ? series[0] : surface; }); // Balken der Modelle im Wunschbereich grün – dieselbe Regel wie das Rechteck im Streudiagramm. var minTps = limits.min_gen_tps == null ? null : limits.min_gen_tps; var minScore = limits.min_score == null ? null : limits.min_score; function inZone(r) { if (minTps === null && minScore === null) return false; if (minTps !== null && (r.tps === null || r.tps < minTps)) return false; return minScore === null || r.scores[key] >= minScore; } if (barChart) barChart.destroy(); if (barBox) barBox.style.height = (70 + 30 * Math.max(bars.length, 1)) + "px"; barChart = new Chart(set.querySelector('[data-chart="bar"]'), { type: "bar", data: { labels: bars.map(function (r) { return r.label; }), datasets: [{ label: title, data: bars.map(function (r) { return r.scores[key]; }), backgroundColor: bars.map(function (r) { return inZone(r) ? series[2] : series[0]; }), borderRadius: 4, borderSkipped: "start", barPercentage: 0.8, categoryPercentage: 0.9, maxBarThickness: 18 }] }, options: { indexAxis: "y", maintainAspectRatio: false, animation: false, layout: { padding: { right: 40 } }, scales: { x: axis(texts.score_axis, { min: 0, max: 100 }), y: axis(null, { grid: { display: false } }) }, plugins: { legend: { display: false }, tooltip: { callbacks: { label: function (c) { return title + ": " + c.parsed.x.toFixed(1); } } } } }, plugins: [barValueLabels] }); if (scatterChart) scatterChart.destroy(); var minX = limits.min_gen_tps == null ? null : limits.min_gen_tps; var minY = limits.min_score == null ? null : limits.min_score; scatterChart = new Chart(set.querySelector('[data-chart="scatter"]'), { type: "scatter", data: { datasets: [{ label: title, data: points, backgroundColor: fills, borderColor: edges, borderWidth: 2, pointRadius: 5, pointHoverRadius: 7, pointHitRadius: 12 }] }, options: { maintainAspectRatio: false, animation: false, scales: { // Der Wunschbereich bleibt sichtbar, auch wenn alle Modelle langsamer sind. x: axis(texts.tps_axis, { beginAtZero: true, suggestedMax: minX === null ? undefined : minX * 1.1 }), y: axis(title, { min: 0, max: 100 }) }, plugins: { legend: { display: false }, zone: { minX: minX, minY: minY }, tooltip: { callbacks: { label: function (c) { var source = c.raw.source === "test" ? texts.source_test : texts.source_run; return c.raw.label + ": " + c.raw.y.toFixed(1) + " " + texts.points + ", " + c.raw.x.toFixed(1) + " t/s (" + source + ")"; } } } } }, plugins: [zonePlugin] }); } buttons.forEach(function (b) { b.addEventListener("click", function () { show(b.dataset.chartCategory); rememberCategory(b.dataset.chartCategory); }); }); var start = rememberedCategory(); var known = data.categories.some(function (c) { return c.key === start; }); show(known ? start : "overall"); }); var categories = readJson("category-data"); if (categories && categories.series.length) { new Chart(document.getElementById("category-chart"), { type: "bar", data: { labels: categories.labels, datasets: categories.series.map(function (s, i) { return { label: s.label, data: s.values, backgroundColor: series[i], borderColor: surface, borderWidth: { top: 1, bottom: 1, right: 2 }, borderRadius: 4, borderSkipped: "start", barPercentage: 0.9, categoryPercentage: 0.8, maxBarThickness: 16 }; }) }, options: { indexAxis: "y", maintainAspectRatio: false, animation: false, scales: { x: axis("Score (0–100)", { min: 0, max: 100 }), y: axis(null, { grid: { display: false } }) }, plugins: { legend: { display: true, position: "top", align: "start", labels: { usePointStyle: true, pointStyle: "rectRounded", color: ink2 } }, tooltip: { callbacks: { label: function (c) { return c.dataset.label + ": " + (c.parsed.x === null ? "–" : c.parsed.x.toFixed(1)); } } } } } }); } })();