Add data_visualization/js/visualization.js

This commit is contained in:
Mika 2026-03-16 13:59:09 +00:00
parent 083b4caa74
commit 8f146dadc3

View file

@ -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<Object>} 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 = `
<div class="summary__metric"><span class="summary__label">Durchschnitt Bandcenter:</span> <span class="summary__value">${summary.avgBandCenter}</span></div>
<div class="summary__metric"><span class="summary__label">Durchschnitt Bandbreite (P10P90):</span> <span class="summary__value">${summary.avgWidth}</span></div>
<div class="summary__metric"><span class="summary__label">Maximaler Ausreißer:</span> <span class="summary__value">${summary.maxOutlier}</span></div>
<div class="summary__metric"><span class="summary__label">Mittlerer Overhead (ms):</span> <span class="summary__value">${summary.avgOverhead}</span></div>
`;
}
/**
* Zeichnet Diagramme und Statistiken im charts-container basierend auf Analyseergebnissen.
* @param {Array<Object>} 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);
}