diff --git a/data_visualization/js/visualization.js b/data_visualization/js/visualization.js
new file mode 100644
index 0000000..9bdb295
--- /dev/null
+++ b/data_visualization/js/visualization.js
@@ -0,0 +1,82 @@
+/* eslint-env browser */
+
+/**
+ * Modul: visualization.js
+ * Rolle: render_logic
+ * Beschreibung: Rendering der Heatmap und Statistikaktualisierung basierend auf WLAN-Messdaten.
+ */
+
+/**
+ * Aktualisiert die Statistik-Seitenleiste basierend auf den aktuellen Heatmap-Daten.
+ * @param {Array<{location: {lat: number, lng: number}, intensity: number, rssi?: number}>} heatmap_data - Messdaten
+ */
+export function updateStatsPanel(heatmap_data = []) {
+ const statsPanel = document.getElementById('stats-panel');
+ if (!statsPanel || heatmap_data.length === 0) return;
+
+ // Extrahiere Signalstärken (RSSI oder intensity)
+ const values = heatmap_data.map(p => p.rssi ?? p.intensity).filter(v => typeof v === 'number');
+ if (values.length === 0) return;
+
+ const sorted = [...values].sort((a, b) => a - b);
+ const min = sorted[0];
+ const max = sorted[sorted.length - 1];
+ const median = sorted[Math.floor(sorted.length / 2)];
+
+ statsPanel.innerHTML = `
+
Signalstatistik
+
+ - Messpunkte: ${values.length}
+ - Minimum RSSI: ${min.toFixed(1)} dBm
+ - Maximum RSSI: ${max.toFixed(1)} dBm
+ - Median RSSI: ${median.toFixed(1)} dBm
+
+ `;
+}
+
+/**
+ * Rendert die Heatmap über der Karte.
+ * @param {Array<{location: {lat: number, lng: number}, intensity: number}>} heatmap_data - WLAN-Daten
+ */
+export function render_heatmap(heatmap_data = []) {
+ // Defensive: Canvas- oder Map-Element prüfen
+ const canvas = document.getElementById('heatmap-canvas');
+ if (!canvas) return;
+ const ctx = canvas.getContext('2d');
+ const width = canvas.width;
+ const height = canvas.height;
+ ctx.clearRect(0, 0, width, height);
+
+ // Einfache farbliche Darstellung basierend auf Intensität
+ heatmap_data.forEach(point => {
+ const { lat, lng } = point.location || {};
+ if (typeof lat !== 'number' || typeof lng !== 'number') return;
+
+ // Beispielkonversion: fiktives Mapping lat/lng -> Canvas-Koordinaten
+ const x = ((lng + 180) / 360) * width;
+ const y = ((90 - lat) / 180) * height;
+
+ const intensity = Math.max(0, Math.min(1, point.intensity ?? 0));
+ const radius = 20 + intensity * 30;
+
+ const gradient = ctx.createRadialGradient(x, y, 0, x, y, radius);
+ gradient.addColorStop(0, `rgba(255, 0, 0, ${0.4 * intensity})`);
+ gradient.addColorStop(1, 'rgba(255, 0, 0, 0)');
+
+ ctx.beginPath();
+ ctx.fillStyle = gradient;
+ ctx.arc(x, y, radius, 0, 2 * Math.PI);
+ ctx.fill();
+ });
+
+ // Statistiken nach Rendering aktualisieren
+ updateStatsPanel(heatmap_data);
+
+ // Accessibility-Update, z. B. für Screenreader
+ const liveRegion = document.getElementById('heatmap-live');
+ if (liveRegion) {
+ liveRegion.textContent = `Heatmap aktualisiert: ${heatmap_data.length} Punkte.`;
+ }
+}
+
+// Hinweis: Dieses Modul wird von app.refreshData aufgerufen.
\ No newline at end of file