diff --git a/data_visualization/js/ui.js b/data_visualization/js/ui.js
new file mode 100644
index 0000000..652b066
--- /dev/null
+++ b/data_visualization/js/ui.js
@@ -0,0 +1,136 @@
+/* ui.js — Verantwortlich für DOM-Manipulation und UI-Rendering */
+
+/**
+ * Rendert interaktive Diagramme für Umweltdaten.
+ * @param {Array<{timestamp: string, temperature: number, wind_speed: number, humidity: number}>} data - API Response von /data
+ */
+export function renderCharts(data) {
+ const chartContainer = document.querySelector('#data-charts');
+ if (!chartContainer) return;
+ chartContainer.innerHTML = '';
+
+ if (!Array.isArray(data) || data.length === 0) {
+ const msg = document.createElement('p');
+ msg.textContent = 'Keine Daten verfügbar';
+ chartContainer.appendChild(msg);
+ return;
+ }
+
+ // Grundstruktur für Canvas-Diagramme
+ const canvas = document.createElement('canvas');
+ canvas.setAttribute('aria-label', 'Umweltdaten Diagramm');
+ canvas.setAttribute('role', 'img');
+ chartContainer.appendChild(canvas);
+
+ // Beispielhafte Nutzung von Chart.js oder ähnlicher Logik (ohne externe Ressourcen)
+ // Zur Vereinfachung: Darstellung als einfache Linien mit Canvas API
+ const ctx = canvas.getContext('2d');
+ const width = chartContainer.clientWidth;
+ const height = 200;
+ canvas.width = width;
+ canvas.height = height;
+
+ ctx.clearRect(0, 0, width, height);
+
+ const temperatures = data.map(d => d.temperature);
+ const winds = data.map(d => d.wind_speed);
+ const hums = data.map(d => d.humidity);
+
+ const maxTemp = Math.max(...temperatures, 1);
+ const maxWind = Math.max(...winds, 1);
+ const maxHum = Math.max(...hums, 1);
+
+ const step = width / (data.length - 1);
+
+ // Hilfsfunktion zum Zeichnen von Linien
+ const drawLine = (values, color, maxVal) => {
+ ctx.beginPath();
+ ctx.strokeStyle = color;
+ ctx.lineWidth = 2;
+ values.forEach((val, i) => {
+ const x = i * step;
+ const y = height - (val / maxVal) * height;
+ if (i === 0) {
+ ctx.moveTo(x, y);
+ } else {
+ ctx.lineTo(x, y);
+ }
+ });
+ ctx.stroke();
+ };
+
+ drawLine(temperatures, '#e74c3c', maxTemp); // rot für Temp
+ drawLine(winds, '#3498db', maxWind); // blau für Wind
+ drawLine(hums, '#2ecc71', maxHum); // grün für Luftfeuchte
+
+ const legend = document.createElement('div');
+ legend.className = 'chart-legend';
+ legend.innerHTML = `
+ Temperatur |
+ Wind |
+ Feuchtigkeit
+ `;
+ chartContainer.appendChild(legend);
+}
+
+/**
+ * Rendert eine Galerie aus Bilddaten.
+ * @param {Array<{url: string, capture_time: string, analysis_result: string}>} images - API Response von /images
+ */
+export function renderGallery(images) {
+ const galleryContainer = document.querySelector('#image-gallery');
+ if (!galleryContainer) return;
+ galleryContainer.innerHTML = '';
+
+ if (!Array.isArray(images) || images.length === 0) {
+ const msg = document.createElement('p');
+ msg.textContent = 'Keine Bilddaten verfügbar';
+ galleryContainer.appendChild(msg);
+ return;
+ }
+
+ images.forEach(imgData => {
+ const wrapper = document.createElement('div');
+ wrapper.className = 'gallery-item';
+
+ const figure = document.createElement('figure');
+
+ const thumb = document.createElement('div');
+ thumb.className = 'image-thumb';
+ thumb.tabIndex = 0;
+ thumb.setAttribute('role', 'button');
+ thumb.setAttribute('aria-label', `Bild aufgenommen am ${imgData.capture_time}`);
+
+ // Bilddarstellung über CSS background-image, da keine externen Bilddateien verwendet werden dürfen
+ thumb.style.background = '#ccc'; // Platzhalter, da keine echten Bilder geladen werden dürfen
+
+ const caption = document.createElement('figcaption');
+ caption.textContent = `${imgData.capture_time} – ${imgData.analysis_result}`;
+
+ figure.appendChild(thumb);
+ figure.appendChild(caption);
+ wrapper.appendChild(figure);
+ galleryContainer.appendChild(wrapper);
+
+ // Interaktive Zoomfunktion (nur symbolisch)
+ thumb.addEventListener('click', () => {
+ const expanded = document.createElement('div');
+ expanded.className = 'overlay';
+ expanded.setAttribute('role', 'dialog');
+ expanded.setAttribute('aria-modal', 'true');
+
+ const expandedContent = document.createElement('div');
+ expandedContent.className = 'overlay-content';
+ expandedContent.style.background = '#999'; // Platzhalter für Zoom-Bild
+
+ const closeBtn = document.createElement('button');
+ closeBtn.textContent = 'Schließen';
+ closeBtn.className = 'close-button';
+ closeBtn.addEventListener('click', () => expanded.remove());
+
+ expanded.appendChild(expandedContent);
+ expanded.appendChild(closeBtn);
+ document.body.appendChild(expanded);
+ });
+ });
+}