// average helper function average(arr) { return arr.reduce((a, b) => a + b, 0) / arr.length; } // period definitions const periodKeys = ['hourly','daily','weekly','monthly','yearly']; const periodLabels = ['Hourly','Daily','Weekly','Monthly','Yearly']; const periodConfig = { hourly: { keyFn: r => r.timestamp.slice(0,13), labelFn: k => k.replace('T',' ') }, daily: { keyFn: r => r.timestamp.slice(0,10), labelFn: k => k }, weekly: { keyFn: r => { const d = new Date(r.timestamp), y = d.getFullYear(); const w = Math.ceil((((d - new Date(y,0,1))/864e5) + new Date(y,0,1).getDay()+1)/7); return `${y}-W${w}`; }, labelFn: k => k }, monthly: { keyFn: r => r.timestamp.slice(0,7), labelFn: k => k }, yearly: { keyFn: r => r.timestamp.slice(0,4), labelFn: k => k } }; // global Chart.js instance let trendChart; // fetch all readings async function fetchReadings() { const res = await fetch('/api/readings'); return res.ok ? res.json() : []; } // build or update chart async function drawTrend() { const all = await fetchReadings(); const slider = document.getElementById('periodSlider'); const periodKey = periodKeys[slider.value]; const cfg = periodConfig[periodKey]; // group and compute stats const groups = {}; all.forEach(r => { const key = cfg.keyFn(r); groups[key] = groups[key] || { temps:[], hums:[], his:[] }; groups[key].temps.push(r.temperature); groups[key].hums.push(r.humidity); groups[key].his.push(r.heatIndex); }); const labels = Object.keys(groups).sort(); const stats = labels.map(k => { const g = groups[k]; return { temp: { avg: average(g.temps).toFixed(2), min: Math.min(...g.temps), max: Math.max(...g.temps) }, hum: { avg: average(g.hums).toFixed(2), min: Math.min(...g.hums), max: Math.max(...g.hums) }, hi: { avg: average(g.his).toFixed(2), min: Math.min(...g.his), max: Math.max(...g.his) } }; }); // selected toggles const checks = Array.from(document.querySelectorAll('#metricToggles input:checked')); const datasets = checks.map(chk => { const m = chk.dataset.metric, s = chk.dataset.stat; return { label: `${m.toUpperCase()} ${s.toUpperCase()}`, data: stats.map(x => x[m][s]), fill: false, tension: 0.1 }; }); const ctx = document.getElementById('trendChart').getContext('2d'); if (trendChart) { trendChart.data.labels = labels.map(cfg.labelFn); trendChart.data.datasets = datasets; trendChart.update(); } else { trendChart = new Chart(ctx, { type: 'line', data: { labels: labels.map(cfg.labelFn), datasets }, options: { responsive: true, maintainAspectRatio: false, // <<— ensure the canvas fills its container plugins: { legend: { display: true }, tooltip: { mode:'index', intersect:false }, zoom: { pan: { enabled:true, mode:'x', modifierKey:'ctrl' }, zoom: { wheel:{enabled:true}, pinch:{enabled:true}, mode:'x' } } }, scales: { x: { display:true }, y: { display:true } } } }); } } document.addEventListener('DOMContentLoaded', () => { drawTrend(); // slider document.getElementById('periodSlider') .addEventListener('input', e => { document.getElementById('periodLabel').textContent = periodLabels[e.target.value]; drawTrend(); }); // toggles document.querySelectorAll('#metricToggles input') .forEach(chk => chk.addEventListener('change', drawTrend)); });