From 58b40eb8b4529bacc1c72e4c5d483fd3a49275db Mon Sep 17 00:00:00 2001 From: xenofem Date: Sat, 16 Apr 2022 12:42:46 -0400 Subject: [PATCH] add option to cut off omicron spike --- static/index.html | 5 ++ static/poopGraph.js | 182 +++++++++++++++++++++++++++++--------------- 2 files changed, 127 insertions(+), 60 deletions(-) diff --git a/static/index.html b/static/index.html index 4169f6e..0d3ab39 100644 --- a/static/index.html +++ b/static/index.html @@ -17,6 +17,7 @@ #controls { display: flex; justify-content: center; + align-items: center; gap: 20px 20px; } @@ -33,6 +34,10 @@ End date: +
diff --git a/static/poopGraph.js b/static/poopGraph.js index 9dd5dc3..6620d13 100644 --- a/static/poopGraph.js +++ b/static/poopGraph.js @@ -1,7 +1,14 @@ -const hash = window.location.hash.substring(1); -const [start, end] = (hash !== "") ? hash.split(":") : ["", ""]; -document.getElementById('startDate').value = start; -document.getElementById('endDate').value = end; +const startInput = document.getElementById('startDate'); +const endInput = document.getElementById('endDate'); +const cutOmicronInput = document.getElementById('cutOmicron'); + +const hashParams = new URLSearchParams(window.location.hash.substring(1)); +startInput.value = hashParams.get('start'); +endInput.value = hashParams.get('end'); +cutOmicronInput.checked = hashParams.get('cutOmicron') === 'true'; + +const OMICRON_START_DATE = '2021-11-01'; +const OMICRON_END_DATE = '2022-02-31'; fetch('/data.json') .then(resp => resp.json()) @@ -106,48 +113,91 @@ function extractWithErrorBars(data, region) { return extracted; } +function maxExcludingOmicron(data, start, end) { + let max = 0; + let secondMax = 0; + for (const row of data) { + if ( + (start !== "" && row.x < start) + || (end != "" && row.x > end) + || (OMICRON_START_DATE < row.x && row.x < OMICRON_END_DATE) + ) { + continue; + } + if (row.yMax > max) { + secondMax = max; + max = row.yMax; + } + } + // To exclude single weird outliers + return secondMax; +} + 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 = { - aspectRatio: 1, - interaction: { - intersect: false, - }, - scales: { - x: { - type: 'time', - min: document.getElementById('startDate').value, - max: document.getElementById('endDate').value, - time: { - tooltipFormat: 'MMM d, yyyy', + + const getOptions = (region) => { + return { + aspectRatio: 1, + interaction: { + intersect: false, + }, + scales: { + x: { + type: 'time', + min: startInput.value, + max: endInput.value, + time: { + tooltipFormat: 'MMM d, yyyy', + }, + }, + y: { + min: 0, + max: null, }, }, - y: { - min: 0, + plugins: { + title: { + display: true, + text: region + ' System Wastewater COVID RNA Signal (copies/mL)', + }, }, - }, - plugins: { - title: { - display: true, - text: 'Wastewater COVID RNA Signal (copies/mL)', + elements: { + pointWithErrorBar: { + errorBarWhiskerSize: 5, + }, }, - }, - elements: { - pointWithErrorBar: { - errorBarWhiskerSize: 5, - }, - }, + }; }; + const northOptions = getOptions("North"); + const southOptions = getOptions("South"); + + const updateYMax = () => { + if ( + cutOmicronInput.checked + && (startInput.value === "" || startInput.value < OMICRON_END_DATE) + && (endInput.value === "" || endInput.value > OMICRON_START_DATE) + ) { + northOptions.scales.y.max = maxExcludingOmicron(northData, startInput.value, endInput.value); + southOptions.scales.y.max = maxExcludingOmicron(southData, startInput.value, endInput.value); + } else { + northOptions.scales.y.max = null; + southOptions.scales.y.max = null; + } + }; + + updateYMax(); + const northChart = new Chart(northCtx, { data: { datasets: [ { type: 'scatterWithErrorBars', - label: 'North System', + label: 'Measured value', data: northData, backgroundColor: 'rgba(0,0,255,0.5)', color: 'rgba(0,0,255,0.5)', @@ -170,7 +220,7 @@ function plot(data) { },*/ { type: 'line', - label: 'North System 7-day average (low)', + label: '7-day average (low)', data: smooth(northData, 'yMin', 7, average), pointRadius: 0, borderColor: 'rgba(0,255,0,0.6)', @@ -179,7 +229,7 @@ function plot(data) { }, { type: 'line', - label: 'North System 7-day average', + label: '7-day average', data: smooth(northData, 'y', 7, average), pointRadius: 0, borderColor: 'rgba(0,128,0,0.9)', @@ -187,7 +237,7 @@ function plot(data) { }, { type: 'line', - label: 'North System 7-day average (high)', + label: '7-day average (high)', data: smooth(northData, 'yMax', 7, average), pointRadius: 0, borderColor: 'rgba(0,255,0,0.6)', @@ -196,14 +246,14 @@ function plot(data) { }, ] }, - options, + options: northOptions, }); const southChart = new Chart(southCtx, { data: { datasets: [ { type: 'scatterWithErrorBars', - label: 'South System', + label: 'Measured value', data: southData, backgroundColor: 'rgba(255,0,0,0.5)', color: 'rgba(255,0,0,0.5)', @@ -212,7 +262,7 @@ function plot(data) { }, { type: 'line', - label: 'South System 7-day average (low)', + label: '7-day average (low)', data: smooth(southData, 'yMin', 7, average), pointRadius: 0, borderColor: 'rgba(255,127,0,0.6)', @@ -221,7 +271,7 @@ function plot(data) { }, { type: 'line', - label: 'South System 7-day average', + label: '7-day average', data: smooth(southData, 'y', 7, average), pointRadius: 0, borderColor: 'rgba(127,63,0,0.9)', @@ -229,7 +279,7 @@ function plot(data) { }, { type: 'line', - label: 'South System 7-day average (high)', + label: '7-day average (high)', data: smooth(southData, 'yMax', 7, average), pointRadius: 0, borderColor: 'rgba(255,127,0,0.6)', @@ -238,28 +288,40 @@ function plot(data) { }, ], }, - options, + options: southOptions, }); - document.getElementById('startDate').addEventListener('input', (e) => { - options.scales.x.min = e.target.value; - northChart.update(); - southChart.update(); - updateHash(); - }); - document.getElementById('endDate').addEventListener('input', (e) => { - options.scales.x.max = e.target.value; - northChart.update(); - southChart.update(); - updateHash(); - }); -} -function updateHash() { - const start = document.getElementById('startDate').value; - const end = document.getElementById('endDate').value; - if (start !== "" || end !== "") { - window.location.hash = start + ":" + end; - } else { - window.location.hash = ""; - } + const update = () => { + updateYMax(); + northChart.update(); + southChart.update(); + + const params = new URLSearchParams(); + const start = startInput.value; + if (start !== '') { + params.set('start', start); + } + const end = endInput.value; + if (end !== '') { + params.set('end', end); + } + if (cutOmicronInput.checked) { + params.set('cutOmicron', 'true'); + } + window.location.hash = params.toString(); + }; + + startInput.addEventListener('input', (e) => { + northOptions.scales.x.min = e.target.value; + southOptions.scales.x.min = e.target.value; + update(); + }); + endInput.addEventListener('input', (e) => { + northOptions.scales.x.max = e.target.value; + southOptions.scales.x.max = e.target.value; + update(); + }); + cutOmicronInput.addEventListener('change', (e) => { + update(); + }); }