diff --git a/data_visualization/js/ui.js b/data_visualization/js/ui.js new file mode 100644 index 0000000..c451e02 --- /dev/null +++ b/data_visualization/js/ui.js @@ -0,0 +1,152 @@ +/* UI Logic for temperature and image rendering */ + +/** + * Render a temperature line chart in the #temperature-chart container. + * @param {Array<{timestamp: string, value: number}>} temperatures + */ +export function renderChart(temperatures) { + const container = document.querySelector('#temperature-chart'); + if (!container) return; + container.innerHTML = ''; + + const canvas = document.createElement('canvas'); + canvas.setAttribute('role', 'img'); + canvas.setAttribute('aria-label', 'Temperaturverlauf'); + canvas.width = container.clientWidth; + canvas.height = 200; + container.appendChild(canvas); + + const ctx = canvas.getContext('2d'); + if (!ctx || temperatures.length === 0) return; + + const values = temperatures.map(t => t.value); + const minVal = Math.min(...values); + const maxVal = Math.max(...values); + const range = maxVal - minVal || 1; + + ctx.strokeStyle = '#2196f3'; + ctx.lineWidth = 2; + ctx.beginPath(); + + temperatures.forEach((t, i) => { + const x = (i / (temperatures.length - 1)) * canvas.width; + const y = canvas.height - ((t.value - minVal) / range) * canvas.height; + if (i === 0) { + ctx.moveTo(x, y); + } else { + ctx.lineTo(x, y); + } + }); + ctx.stroke(); +} + +/** + * Render an interactive image gallery. + * @param {Array<{filename: string, timestamp: string, metadata?: object}>} images + */ +export function renderGallery(images) { + const container = document.querySelector('#image-gallery'); + if (!container) return; + container.innerHTML = ''; + + const galleryGrid = document.createElement('div'); + galleryGrid.classList.add('gallery-grid'); + + images.forEach((imgData, index) => { + const imgEl = document.createElement('div'); + imgEl.classList.add('gallery-item'); + imgEl.tabIndex = 0; + imgEl.setAttribute('role', 'button'); + imgEl.setAttribute('aria-label', `Bild ${index + 1}`); + imgEl.style.backgroundColor = '#e0e0e0'; + imgEl.style.display = 'flex'; + imgEl.style.alignItems = 'center'; + imgEl.style.justifyContent = 'center'; + imgEl.style.height = '100px'; + imgEl.textContent = `Bild ${index + 1}`; + + imgEl.addEventListener('click', () => openImageModal(imgData)); + imgEl.addEventListener('keypress', e => { + if (e.key === 'Enter' || e.key === ' ') { + openImageModal(imgData); + } + }); + + galleryGrid.appendChild(imgEl); + }); + + container.appendChild(galleryGrid); +} + +/** + * Display a modal view for a selected image (textual substitute, no actual image). + * @param {{filename: string, timestamp: string, metadata?: object}} imgData + */ +function openImageModal(imgData) { + let modal = document.querySelector('#image-modal'); + if (!modal) { + modal = document.createElement('div'); + modal.id = 'image-modal'; + modal.setAttribute('role', 'dialog'); + modal.setAttribute('aria-modal', 'true'); + modal.style.position = 'fixed'; + modal.style.top = 0; + modal.style.left = 0; + modal.style.right = 0; + modal.style.bottom = 0; + modal.style.background = 'rgba(0,0,0,0.8)'; + modal.style.color = '#fff'; + modal.style.display = 'flex'; + modal.style.alignItems = 'center'; + modal.style.justifyContent = 'center'; + + const content = document.createElement('div'); + content.classList.add('modal-content'); + content.style.padding = '1rem'; + content.style.background = '#111'; + content.style.maxWidth = '90%'; + content.style.textAlign = 'center'; + + const closeBtn = document.createElement('button'); + closeBtn.textContent = 'Schließen'; + closeBtn.style.marginTop = '1rem'; + closeBtn.addEventListener('click', () => { + modal.remove(); + }); + + modal.appendChild(content); + content.appendChild(closeBtn); + document.body.appendChild(modal); + } + + const content = modal.querySelector('.modal-content'); + if (content) { + content.innerHTML = `

Bild: ${imgData.filename}

Aufgenommen: ${imgData.timestamp}

`; + const closeBtn = document.createElement('button'); + closeBtn.textContent = 'Schließen'; + closeBtn.style.marginTop = '1rem'; + closeBtn.addEventListener('click', () => modal.remove()); + content.appendChild(closeBtn); + } +} + +/** + * Update summary statistics for displayed data. + * @param {Array<{timestamp: string, value: number}>} temperatures + * @param {Array<{filename: string, timestamp: string}>} images + */ +export function updateStats(temperatures, images) { + const container = document.querySelector('#summary-stats'); + if (!container) return; + container.innerHTML = ''; + + const avgTemp = + temperatures.length > 0 + ? (temperatures.reduce((sum, t) => sum + t.value, 0) / temperatures.length).toFixed(2) + : 'N/A'; + + const imgCount = images.length; + + const statsText = `Durchschnittstemperatur: ${avgTemp}°C | Bilder: ${imgCount}`; + container.textContent = statsText; +}