diff --git a/data_visualization/js/visualization.js b/data_visualization/js/visualization.js new file mode 100644 index 0000000..a57c092 --- /dev/null +++ b/data_visualization/js/visualization.js @@ -0,0 +1,136 @@ +/** + * visualization.js + * Verantwortlich für Rendering der Diagramme und Aktualisierung bei neuen Daten. + * Part of experiment: resonanzband_analysis (artifact: data_visualization) + */ + +/** + * Aktualisiert die aggregierten Kennzahlen im summary-section. + * @param {Array} data - Analyseergebnisse mit Feldern { run_id, band_center, band_width_p10_p90, max_outlier, overhead_ms } + */ +export function updateSummary(data) { + if (!Array.isArray(data) || data.length === 0) { + const summaryEl = document.getElementById('summary-section'); + if (summaryEl) summaryEl.textContent = 'Keine Daten verfügbar.'; + return; + } + + const avg = (arr, key) => arr.reduce((sum, d) => sum + (d[key] ?? 0), 0) / arr.length; + const maxOutlier = Math.max(...data.map(d => d.max_outlier ?? 0)); + + const summary = { + avgBandCenter: avg(data, 'band_center').toFixed(2), + avgWidth: avg(data, 'band_width_p10_p90').toFixed(2), + maxOutlier, + avgOverhead: avg(data, 'overhead_ms').toFixed(2) + }; + + const summaryEl = document.getElementById('summary-section'); + if (!summaryEl) return; + summaryEl.innerHTML = ` +
Durchschnitt Bandcenter: ${summary.avgBandCenter}
+
Durchschnitt Bandbreite (P10–P90): ${summary.avgWidth}
+
Maximaler Ausreißer: ${summary.maxOutlier}
+
Mittlerer Overhead (ms): ${summary.avgOverhead}
+ `; +} + +/** + * Zeichnet Diagramme und Statistiken im charts-container basierend auf Analyseergebnissen. + * @param {Array} results - Analyseergebnisse + */ +export function renderResults(results) { + const container = document.getElementById('charts-container'); + if (!container) return; + container.innerHTML = ''; + + if (!Array.isArray(results) || results.length === 0) { + container.textContent = 'Keine Ergebnisse gefunden.'; + updateSummary([]); + return; + } + + // Diagramm mit Canvas-Element zeichnen (native Lösung ohne externe Lib) + const canvas = document.createElement('canvas'); + canvas.width = container.clientWidth; + canvas.height = 300; + canvas.setAttribute('role', 'img'); + canvas.setAttribute('aria-label', 'Diagramm der Resonanzband-Metriken'); + container.appendChild(canvas); + + const ctx = canvas.getContext('2d'); + const padding = 40; + const width = canvas.width - 2 * padding; + const height = canvas.height - 2 * padding; + + const maxBand = Math.max(...results.map(r => r.band_center + r.band_width_p10_p90)); + const minBand = Math.min(...results.map(r => r.band_center - r.band_width_p10_p90)); + + const scaleY = val => height - ((val - minBand) / (maxBand - minBand)) * height + padding; + const scaleX = (i) => padding + (i / (results.length - 1)) * width; + + // Achsen zeichnen + ctx.strokeStyle = '#333'; + ctx.beginPath(); + ctx.moveTo(padding, padding); + ctx.lineTo(padding, height + padding); + ctx.lineTo(width + padding, height + padding); + ctx.stroke(); + + // Linien für Bandcenter zeichnen + ctx.strokeStyle = '#0057b7'; + ctx.lineWidth = 2; + ctx.beginPath(); + results.forEach((r, i) => { + const x = scaleX(i); + const y = scaleY(r.band_center); + if (i === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); + }); + ctx.stroke(); + + // Outlier Punkte zeichnen + ctx.fillStyle = '#b70000'; + results.forEach((r, i) => { + const x = scaleX(i); + const y = scaleY(r.band_center + r.max_outlier); + ctx.beginPath(); + ctx.arc(x, y, 3, 0, Math.PI * 2); + ctx.fill(); + }); + + // Tooltips: einfache textuelle Version + canvas.addEventListener('mousemove', e => { + const rect = canvas.getBoundingClientRect(); + const x = e.clientX - rect.left; + const y = e.clientY - rect.top; + + const idx = Math.round((x - padding) / width * (results.length - 1)); + const item = results[idx]; + if (!item) return; + + const tooltip = document.getElementById('chart-tooltip') ?? document.createElement('div'); + tooltip.id = 'chart-tooltip'; + tooltip.className = 'chart__tooltip'; + tooltip.style.position = 'absolute'; + tooltip.style.left = `${x + 10}px`; + tooltip.style.top = `${y + 10}px`; + tooltip.style.background = '#222'; + tooltip.style.color = '#fff'; + tooltip.style.padding = '4px 8px'; + tooltip.style.borderRadius = '4px'; + tooltip.style.fontSize = '0.8rem'; + tooltip.style.pointerEvents = 'none'; + tooltip.textContent = `Run ${item.run_id}: Center=${item.band_center}, Width=${item.band_width_p10_p90}, Outlier=${item.max_outlier}`; + + document.body.appendChild(tooltip); + }); + + canvas.addEventListener('mouseleave', () => { + const tooltip = document.getElementById('chart-tooltip'); + if (tooltip) tooltip.remove(); + }); + + // Zusammenfassung aktualisieren + updateSummary(results); +}