From 8f80a64b2120638b35d7b9ceeeb5d2c1b16271a1 Mon Sep 17 00:00:00 2001 From: Mika Date: Sun, 29 Mar 2026 03:07:25 +0000 Subject: [PATCH] Add data_visualization/js/ui.js --- data_visualization/js/ui.js | 111 ++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 data_visualization/js/ui.js diff --git a/data_visualization/js/ui.js b/data_visualization/js/ui.js new file mode 100644 index 0000000..b41732f --- /dev/null +++ b/data_visualization/js/ui.js @@ -0,0 +1,111 @@ +"use strict"; + +/** + * UI Logic for Data Visualization + * Handles rendering charts, updating stats, and applying filters. + */ + +import { fetchData } from './api.js'; + +/** + * @typedef {Object} SensorData + * @property {string} timestamp - ISO time string + * @property {number} intensity - Measured light intensity + * @property {number} wavelength - Light wavelength in nm + * @property {string} sensor_id - Identifier of the sensor + */ + +/** + * Renders chart visualization inside the DOM element with id 'chart-container'. + * @param {SensorData[]} dataArray + */ +export function renderChart(dataArray) { + const container = document.getElementById('chart-container'); + if (!container) return; + + container.innerHTML = ''; + + // Create canvas for chart + const canvas = document.createElement('canvas'); + canvas.setAttribute('role', 'img'); + canvas.setAttribute('aria-label', 'Diagramm der Intensitätswerte über Zeit'); + container.appendChild(canvas); + + const ctx = canvas.getContext('2d'); + const timestamps = dataArray.map(d => new Date(d.timestamp).toLocaleTimeString()); + const intensities = dataArray.map(d => d.intensity); + + // Chart.js-like pseudo implementation (no external lib) + ctx.beginPath(); + const maxIntensity = Math.max(...intensities, 1); + const minIntensity = Math.min(...intensities, 0); + const width = canvas.width = container.offsetWidth; + const height = canvas.height = Math.min(400, container.offsetHeight || 300); + + ctx.strokeStyle = '#007acc'; + ctx.lineWidth = 2; + + intensities.forEach((value, i) => { + const x = (i / (intensities.length - 1)) * width; + const y = height - ((value - minIntensity) / (maxIntensity - minIntensity)) * height; + if (i === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); + }); + ctx.stroke(); + + updateSummary(dataArray); +} + +/** + * Updates statistics panel with calculated summary data. + * @param {SensorData[]} dataArray + */ +export function updateSummary(dataArray) { + const avg = dataArray.length ? dataArray.reduce((acc, d) => acc + d.intensity, 0) / dataArray.length : 0; + const max = dataArray.length ? Math.max(...dataArray.map(d => d.intensity)) : 0; + const min = dataArray.length ? Math.min(...dataArray.map(d => d.intensity)) : 0; + + const summaryEl = document.getElementById('summary-panel'); + if (!summaryEl) return; + + summaryEl.innerHTML = ` +
Durchschnitt: ${avg.toFixed(2)}
+
Max: ${max.toFixed(2)}
+
Min: ${min.toFixed(2)}
+ `; +} + +/** + * Reads filter form values and triggers data reload. + */ +export async function applyFilters() { + const sensorSelect = document.getElementById('filter-sensor'); + const timeStart = document.getElementById('filter-start'); + const timeEnd = document.getElementById('filter-end'); + + const params = {}; + if (sensorSelect?.value) params.sensor = sensorSelect.value; + if (timeStart?.value) params.start = timeStart.value; + if (timeEnd?.value) params.end = timeEnd.value; + + const data = await fetchData(params).catch(() => []); + renderChart(data); +} + +/** + * Binds UI event handlers. + */ +export function bindUIEvents() { + const form = document.getElementById('filter-form'); + if (form) { + form.addEventListener('submit', e => { + e.preventDefault(); + applyFilters(); + }); + } +} + +// Auto-bind when DOM is ready +document.addEventListener('DOMContentLoaded', () => { + bindUIEvents(); +}); \ No newline at end of file