fetch('/data.json') .then(resp => resp.json()) .then(plot) .catch(err => { console.log(err); }); function average(window) { let y_sum = 0; let count = 0; for (const sample of window) { if (sample['y'] !== undefined) { y_sum += sample['y']; count += 1; } } if (count === 0) { return undefined; } return y_sum / count; } function localLinearRegression(slopeWindowSize, averageWindowSize) { return (window) => { let x_sum = 0; let y_sum = 0; let count = 0; for (const sample of window.slice(-slopeWindowSize)) { if (sample['y'] !== undefined) { x_sum += sample['x']; y_sum += sample['y']; count += 1; } } if (count === 0) { return undefined; } const x_avg = x_sum / count; const y_avg = y_sum / count; let numerator = 0; let denominator = 0; for (const sample of window) { if (sample['y'] !== undefined) { const x_err = sample['x'] - x_avg; const y_err = sample['y'] - y_avg; numerator += x_err * y_err; denominator += x_err * x_err; } } if (denominator === 0) { return window.at(-1)['y']; } const slope = numerator / denominator; let short_x_sum = 0; let short_y_sum = 0; let short_count = 0; for (const sample of window.slice(-averageWindowSize)) { if (sample['y'] !== undefined) { short_x_sum += sample['x']; short_y_sum += sample['y']; short_count += 1; } } if (short_count === 0) { return y_avg + (window.at(-1)['x'] - x_avg) * slope; } const short_x_avg = short_x_sum / short_count; const short_y_avg = short_y_sum / short_count; return short_y_avg + (window.at(-1)['x'] - short_x_avg) * slope; }; } function smooth(data, column, windowSize, smoothFunc) { const smoothed = []; const window = []; for (const row of data) { window.push({ x: new Date(row['x']).getTime(), y: row[column] }); if (window.length > windowSize) { window.shift(); } smoothed.push({ x: row['x'], y: smoothFunc(window) }); } return smoothed; } function extractWithErrorBars(data, region) { const extracted = []; for (const row of data['rows']) { const y = row[region + ' (copies/mL)']; if (y !== undefined) { extracted.push({ x: row['Date'], y: y, yMin: y - (row[region + ' Low Confidence Interval'] || 0), yMax: y + (row[region + ' High Confidence Interval'] || 0), }); } } return extracted; } function plot(data) { const northData = extractWithErrorBars(data, 'Northern'); const southData = extractWithErrorBars(data, 'Southern'); const northCtx = document.getElementById('northCanvas'); const southCtx = document.getElementById('southCanvas'); let options = { maintainAspectRatio: false, interaction: { intersect: false, }, scales: { x: { type: 'timeseries', min: document.getElementById('startDate').value, max: document.getElementById('endDate').value, }, y: { min: 0, }, }, plugins: { title: { display: true, text: 'Wastewater COVID RNA Signal (copies/mL)', }, }, }; const northChart = new Chart(northCtx, { data: { datasets: [ { type: 'scatterWithErrorBars', label: 'North System', data: northData, backgroundColor: 'rgba(0,0,255,0.5)', color: 'rgba(0,0,255,0.5)', borderColor: 'rgba(0,0,255,0.5)', spanGaps: true, }, /* { type: 'line', label: 'smoothing 18/14', data: smooth(northData, 'y', 18, localLinearRegression(18, 14)), pointRadius: 0, borderColor: 'rgba(0,0,255,0.6)', }, { type: 'line', label: 'smoothing 14/14', data: smooth(northData, 'y', 14, localLinearRegression(14, 14)), pointRadius: 0, borderColor: 'rgba(255,0,255,0.6)', },*/ { type: 'line', label: 'North System 7-day average (low)', data: smooth(northData, 'yMin', 7, average), pointRadius: 0, borderColor: 'rgba(0,255,0,0.6)', fill: '+1', backgroundColor: 'rgba(0,255,0,0.2)', }, { type: 'line', label: 'North System 7-day average', data: smooth(northData, 'y', 7, average), pointRadius: 0, borderColor: 'rgba(0,128,0,0.9)', backgroundColor: 'rgba(0,128,0,0.9)', }, { type: 'line', label: 'North System 7-day average (high)', data: smooth(northData, 'yMax', 7, average), pointRadius: 0, borderColor: 'rgba(0,255,0,0.6)', fill: '-1', backgroundColor: 'rgba(0,255,0,0.2)', }, ] }, options, }); const southChart = new Chart(southCtx, { data: { datasets: [ { type: 'scatterWithErrorBars', label: 'South System', data: southData, backgroundColor: 'rgba(255,0,0,0.5)', color: 'rgba(255,0,0,0.5)', borderColor: 'rgba(255,0,0,0.5)', spanGaps: true, }, { type: 'line', label: 'South System 7-day average (low)', data: smooth(southData, 'yMin', 7, average), pointRadius: 0, borderColor: 'rgba(255,127,0,0.6)', fill: '+1', backgroundColor: 'rgba(255,127,0,0.2)', }, { type: 'line', label: 'South System 7-day average', data: smooth(southData, 'y', 7, average), pointRadius: 0, borderColor: 'rgba(127,63,0,0.9)', backgroundColor: 'rgba(127,63,0,0.9)', }, { type: 'line', label: 'South System 7-day average (high)', data: smooth(southData, 'yMax', 7, average), pointRadius: 0, borderColor: 'rgba(255,127,0,0.6)', fill: '-1', backgroundColor: 'rgba(255,127,0,0.2)', }, ], }, options, }); document.getElementById('startDate').addEventListener('input', (e) => { options.scales.x.min = e.target.value; northChart.update(); southChart.update(); }); document.getElementById('endDate').addEventListener('input', (e) => { options.scales.x.max = e.target.value; northChart.update(); southChart.update(); }); }