diff --git a/results_visualization/js/visualization.js b/results_visualization/js/visualization.js new file mode 100644 index 0000000..c197071 --- /dev/null +++ b/results_visualization/js/visualization.js @@ -0,0 +1,121 @@ +"use strict"; + +/** + * @module visualization + * @description Verarbeitung und Darstellung aggregierter Zeitmessungsdaten. + * Verantwortlich für Rendering und Update von Charts und Summary Cards. + */ + +/** + * Rendert interaktive Diagramme auf Basis der geladenen Daten. + * @param {Array} data - Aggregierte Ergebnisse von der /results API. + * @param {HTMLElement} container - Container-Element zur Anzeige des Diagramms. + * @returns {void} + */ +export function renderCharts(data, container) { + if (!Array.isArray(data) || !container) return; + + container.innerHTML = ""; + + const canvas = document.createElement("canvas"); + canvas.id = "chart-results"; + canvas.setAttribute("aria-label", "Zeitmessungsdiagramm"); + canvas.setAttribute("role", "img"); + container.appendChild(canvas); + + const ctx = canvas.getContext("2d"); + + const runTypes = [...new Set(data.map(d => d.run_type))]; + const labels = data.map(d => d.run_id); + + const datasets = runTypes.map((type) => { + const subset = data.filter(d => d.run_type === type); + return { + label: type, + data: subset.map(r => r.metrics?.latency_mean ?? 0), + borderColor: type === "pinned" ? "#2a9d8f" : "#e76f51", + backgroundColor: type === "pinned" ? "rgba(42,157,143,0.4)" : "rgba(231,111,81,0.4)", + borderWidth: 2, + tension: 0.3, + }; + }); + + if (window.currentChart) { + window.currentChart.destroy(); + } + + window.currentChart = new Chart(ctx, { + type: "line", + data: { labels, datasets }, + options: { + responsive: true, + interaction: { mode: "index", intersect: false }, + scales: { + x: { title: { display: true, text: "Run ID" } }, + y: { title: { display: true, text: "Latenz (ms)" }, beginAtZero: true } + }, + plugins: { + legend: { position: "top" }, + tooltip: { + callbacks: { + label: ctx => `${ctx.dataset.label}: ${ctx.parsed.y.toFixed(2)} ms` + } + } + } + } + }); + + renderSummary(data); +} + +/** + * Aktualisiert Diagramme und Statistiken basierend auf Filtern. + * @param {Object} filters - Nutzerdefinierte Filterparameter. + * @param {Array} data - Aktuelle Ergebnisdaten. + * @returns {void} + */ +export function updateVisualization(filters, data) { + if (typeof filters !== "object" || !Array.isArray(data)) return; + + const filteredData = data.filter(item => { + const matchRunType = !filters.runType || item.run_type === filters.runType; + const matchMetric = !filters.metric || Object.keys(item.metrics || {}).includes(filters.metric); + return matchRunType && matchMetric; + }); + + const container = document.querySelector("#chart-container"); + renderCharts(filteredData, container); +} + +/** + * Rendert aggregierte Kennzahlen als Summary Cards unterhalb des Diagramms. + * @param {Array} data + */ +function renderSummary(data) { + const summaryContainer = document.querySelector("#summary-container"); + if (!summaryContainer) return; + + const total = data.length; + const avgLatency = (data.reduce((acc, val) => acc + (val.metrics?.latency_mean ?? 0), 0) / Math.max(total, 1)).toFixed(2); + const avgVariance = (data.reduce((acc, val) => acc + (val.metrics?.variance ?? 0), 0) / Math.max(total, 1)).toFixed(3); + const avgStability = (data.reduce((acc, val) => acc + (val.metrics?.stability_index ?? 0), 0) / Math.max(total, 1)).toFixed(3); + + summaryContainer.innerHTML = ` +
+

Runs insgesamt

+

${total}

+
+
+

Ø Latenz

+

${avgLatency} ms

+
+
+

Ø Varianz

+

${avgVariance}

+
+
+

Ø Stabilität

+

${avgStability}

+
+ `; +}