Add data_visualization/js/ui.js
This commit is contained in:
parent
6589c63f74
commit
9920c4d50a
1 changed files with 152 additions and 0 deletions
152
data_visualization/js/ui.js
Normal file
152
data_visualization/js/ui.js
Normal file
|
|
@ -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 = `<h2>Bild: ${imgData.filename}</h2><p>Aufgenommen: ${imgData.timestamp}</p>`;
|
||||
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;
|
||||
}
|
||||
Loading…
Reference in a new issue