From 8d986d55645eb68b6a9e4dc767fa8cb5e1a2c68d Mon Sep 17 00:00:00 2001 From: Mika Date: Sun, 10 May 2026 02:07:41 +0000 Subject: [PATCH] Add data_visualization/js/visualizations.js --- data_visualization/js/visualizations.js | 145 ++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 data_visualization/js/visualizations.js diff --git a/data_visualization/js/visualizations.js b/data_visualization/js/visualizations.js new file mode 100644 index 0000000..069ec9b --- /dev/null +++ b/data_visualization/js/visualizations.js @@ -0,0 +1,145 @@ +/** + * @module visualizations + * Zeichnet Diagramme und analysiert Ereignisse basierend auf Sensor- und Zustandsdaten. + * Dunkles Design für Nachtbetrieb, responsiv ausgelegt. + */ + +/** + * Erstellt Diagramme für IR-Gain, Spannung und Drift. + * @param {Array} telemetryData - Array von Telemetrie-Datenpunkten {timestamp, vbat, ir_gain, drift, event_flag} + */ +export function renderCharts(telemetryData = []) { + const container = document.querySelector('.chart-container'); + if (!container) return; + + container.innerHTML = ''; + if (!Array.isArray(telemetryData) || telemetryData.length === 0) { + container.textContent = 'Keine Telemetrie-Daten verfügbar.'; + return; + } + + // Zeitachse vorbereiten + const timestamps = telemetryData.map(d => new Date(d.timestamp)); + const irGainValues = telemetryData.map(d => d.ir_gain); + const vbatValues = telemetryData.map(d => d.vbat); + const driftValues = telemetryData.map(d => d.drift); + + // Canvas-Elemente erzeugen + const chartSpecs = [ + { key: 'ir_gain', label: 'IR-Gain', values: irGainValues, color: '#ff9800' }, + { key: 'vbat', label: 'Batteriespannung (V)', values: vbatValues, color: '#03a9f4' }, + { key: 'drift', label: 'Drift', values: driftValues, color: '#8bc34a' } + ]; + + chartSpecs.forEach(spec => { + const chartBlock = document.createElement('section'); + chartBlock.className = `chart-block chart-block--${spec.key}`; + + const h3 = document.createElement('h3'); + h3.textContent = spec.label; + chartBlock.appendChild(h3); + + const canvas = document.createElement('canvas'); + canvas.width = container.offsetWidth; + canvas.height = 180; + chartBlock.appendChild(canvas); + + container.appendChild(chartBlock); + + drawLineChart(canvas, timestamps, spec.values, spec.color); + }); + + // Ereignis-Log aktualisieren + const events = telemetryData.filter(d => d.event_flag); + renderEventLog(events); +} + +/** + * Zeichnet ein einfaches Liniendiagramm auf Canvas. + * @param {HTMLCanvasElement} canvas + * @param {Array} labels Zeitwerte (X-Achse) + * @param {Array} data Werte für Y-Achse + * @param {string} color Linienfarbe + */ +function drawLineChart(canvas, labels, data, color) { + const ctx = canvas.getContext('2d'); + const width = canvas.width; + const height = canvas.height; + + // Hintergrund dunkel + ctx.fillStyle = '#111'; + ctx.fillRect(0, 0, width, height); + + if (data.length === 0) return; + + const min = Math.min(...data); + const max = Math.max(...data); + const xStep = width / (data.length - 1); + + ctx.strokeStyle = color; + ctx.lineWidth = 2; + ctx.beginPath(); + + data.forEach((val, i) => { + const x = i * xStep; + const y = height - ((val - min) / (max - min)) * (height - 20) - 10; + if (i === 0) ctx.moveTo(x, y); + else ctx.lineTo(x, y); + }); + ctx.stroke(); + + ctx.strokeStyle = '#333'; + ctx.lineWidth = 1; + ctx.beginPath(); + ctx.moveTo(0, height - 1); + ctx.lineTo(width, height - 1); + ctx.stroke(); +} + +/** + * Befüllt Tabelleneinträge für Ereignislog. + * @param {Array} events - Array von Objekten mit event_flag TRUE und zusätzlichen Daten + */ +export function renderEventLog(events = []) { + const table = document.querySelector('.event-log'); + if (!table) return; + + table.innerHTML = ''; + const headerRow = document.createElement('tr'); + ['Zeit', 'Typ', 'Details'].forEach(label => { + const th = document.createElement('th'); + th.textContent = label; + headerRow.appendChild(th); + }); + table.appendChild(headerRow); + + if (events.length === 0) { + const row = document.createElement('tr'); + const cell = document.createElement('td'); + cell.colSpan = 3; + cell.textContent = 'Keine Ereignisse erkannt.'; + row.appendChild(cell); + table.appendChild(row); + return; + } + + events.forEach(evt => { + const row = document.createElement('tr'); + + const timeCell = document.createElement('td'); + timeCell.textContent = new Date(evt.timestamp).toLocaleTimeString(); + row.appendChild(timeCell); + + const typeCell = document.createElement('td'); + if (evt.vbat < 6.0) typeCell.textContent = 'Spannungseinbruch'; + else if (evt.event_flag === 'reinit') typeCell.textContent = 'Reinitialisierung'; + else typeCell.textContent = 'Unbekanntes Ereignis'; + row.appendChild(typeCell); + + const detailsCell = document.createElement('td'); + detailsCell.textContent = `IR-Gain: ${evt.ir_gain}, Drift: ${evt.drift.toFixed(2)}`; + row.appendChild(detailsCell); + + table.appendChild(row); + }); +}