diff --git a/README.md b/README.md index 81c982de68dfadbbe5b2b80b5bd934979eae80a8..f14fb7b1752017cb3b1192eaa97faa3d7778a786 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ This project is developed in the [PMF methodology](http://programming-motherfuck Therefore, there are **no** external dependencies to pull, you **do not** need a local webserver, and you **do not** need `node` to use `npm` to get `yarn` to get `bower` to install `webpack`, because this techie happens to know how to left-pad without [`leftpad`](https://www.theregister.co.uk/2016/03/23/npm_left_pad_chaos/). -**Just clone this repository locally and navigate in your browser to the `index.html` file (for example, `file:///home/user/Projects/covid/index.html`). It should just work. That's it.** +**Just clone this repository locally and navigate your browser to the `index.html` file (for example, `file:///home/user/Projects/covid/index.html`). It should just work. That's it.** ## Deployment @@ -18,7 +18,7 @@ Copy the files to the location they're going to be served from. That's it. It's ## Privacy -There are no trackers, and no third-party content. This is completely self-contained. +There are no trackers, and [no third-party content](https://barnacles.online/). This is completely self-contained. And it is self-contained because there is absolutely zero need for it not to be. @@ -33,6 +33,9 @@ Remember this when somebody tells you again that they need Google Fonts ([nope]( > > WHO mission to China issued a statement saying that there was evidence of human-to-human transmission in Wuhan but more investigation was needed to understand the full extent of transmission. + - ***There are errors in the data / some data is missing!*** + As mentioned above, I am using external source of data that I do not control. That being said there is a reasonably large chance that the error is in how I process/display the data. If you think that might be the case, let me know. + - ***Some things do not work in Safari. Why?*** Because [Safari is broken](https://bugs.webkit.org/show_bug.cgi?id=119175), that's why. @@ -41,6 +44,11 @@ Remember this when somebody tells you again that they need Google Fonts ([nope]( ## Changelog +### 14.12.2020 + + - Added `incidence` chart for cases, recoveries, and deaths. + Fixes #3. + ### 06.12.2020 - Changed the way rolling average is calculated: a rolling average over `n` days for the current day is now calculated from data from the day and `n-1` proceeding days, instead of centering the rolling average on the selected day. diff --git a/covid.js b/covid.js index ab2c088a96bd8d3e38c8ddd711a3e8e7b04a71df..3f8b7947aee62ef157200eba7cbc5bfa43265f0e 100644 --- a/covid.js +++ b/covid.js @@ -1349,6 +1349,19 @@ let updateChartData = (siteSelect) => { // which data are we looking at var dataset = document.querySelector('input[type=radio][name=chart-data]:checked').value + // make sure the interface choices make sense + // NOTICE: fixing things here should be the last resort! + // settings that do not make sense should be made impossible + // in the interface by means of HTML+CSS first and foremost + // and by checks in the right places (ignoring settings that + // do not make sense in JS) + // + // only when this is not enough and adding such checks is too complicated + // should the blunt instrument of fixing them explicitly here be used + if ( (document.querySelector('input[type=radio][name=chart-cases]:checked').value === "incidence") && ( (dataset === "active") || (dataset === "cfr") ) ) { + document.querySelector('input[type=radio]#chart-cases-delta').checked = true + } + // population_ratio is also used as an indicator that we're doing a per-million chart var population_ratio = false if (dataset !== 'cfr') { @@ -1408,35 +1421,64 @@ let updateChartData = (siteSelect) => { to_chart.data = to_chart.data.map(row => Math.round(row / population_ratio)) } - // are we doing the moving average thing? + // are we doing the incidence / rolling average thing? var avg_over = document.querySelector('input[type=number][name=chart-average]').value - if ( (document.querySelector('input[type=radio][name=chart-cases]:checked').value === 'new') && (avg_over > 1) ) { + + if (avg_over > 1) { + + // average + if (document.querySelector('input[type=radio][name=chart-cases]:checked').value === 'new') { - to_chart.data = to_chart.data.reduce((acc, cur, idx, arr) => { + to_chart.data = to_chart.data.reduce((acc, cur, idx, arr) => { - var mean = 0; - var avg_start = idx - avg_over + 1 - var avg_end = idx + var mean = 0; + var avg_start = idx - avg_over + 1 + var avg_end = idx - // if we don't have enough previous data to average over - // use whatever we have - if (idx < avg_over) { - avg_start = 0 - } + // if we don't have enough previous data to average over + // use whatever we have + if (idx < avg_over) { + avg_start = 0 + } - // calculate the mean - for (var j=avg_start; j<=avg_end; j++) { - mean += arr[j]; - } - // if we're talking case fatality rate, we should not round - if (dataset === 'cfr') { - acc.push(mean / (avg_end - avg_start + 1)) - // everything else nicely rounded - } else { - acc.push(Math.round(mean / (avg_end - avg_start + 1))) - } - return acc - }, []) + // calculate the mean + for (var j=avg_start; j<=avg_end; j++) { + mean += arr[j]; + } + // if we're talking case fatality rate, we should not round + if (dataset === 'cfr') { + acc.push(mean / (avg_end - avg_start + 1)) + // everything else nicely rounded + } else { + acc.push(Math.round(mean / (avg_end - avg_start + 1))) + } + return acc + }, []) + + // incidence + } else if (document.querySelector('input[type=radio][name=chart-cases]:checked').value === 'incidence') { + + to_chart.data = to_chart.data.reduce((acc, cur, idx, arr) => { + + var incidence = 0; + var icdc_start = idx - avg_over + 1 + var icdc_end = idx + + // if we don't have enough previous data to calculate incidence within + // use whatever we have + if (idx < avg_over) { + icdc_start = 0 + } + + // calculate the incidence + for (var j=icdc_start; j<=icdc_end; j++) { + incidence += arr[j]; + } + + acc.push(incidence) + return acc + }, []) + } } // assign the data to the chart @@ -1567,6 +1609,12 @@ let updateChartSettings = () => { theChart.options.scales.yAxes[0].ticks.min = 0 } + // incidence numbers + // these *only* make sense for confirmed / recovered / deaths + } else if ( (document.querySelector('input[type=radio][name=chart-cases]:checked').value === 'incidence') && ( (chart_dataset === 'confirmed') || (chart_dataset === 'recovered') || (chart_dataset === 'deaths') ) ) { + theChart.options.scales.yAxes[0].scaleLabel.labelString += ", incidence" + theChart.options.title.text[0] = `incidence of ${theChart.options.title.text[0]}` + // showing new confirmed cases / recoveries / deaths / active cases / cfr // notice: *new active cases* can be *negative!* } else { @@ -2157,7 +2205,7 @@ document.addEventListener('DOMContentLoaded', (e)=>{ if (setting === '') { return false; } - // perhaps it' a "valued" setting? + // perhaps it's a "valued" setting? setting = setting.split(':') // get the node var node = document.querySelector(`.chart-config-container input[id$=${setting[0]}]`) diff --git a/index.html b/index.html index 4e98aa383383d252ed05ff2c9fd2eec6959372fd..6b37b193b5635b7013c0894531c801bd7f093dc3 100644 --- a/index.html +++ b/index.html @@ -327,6 +327,9 @@ .chart-config-container input#chart-data-cfr:checked ~ .chart-config-group label[for=chart-data-cfr], .chart-config-container input#chart-cases-cumulative:checked ~ .chart-config-group label[for=chart-cases-cumulative], .chart-config-container input#chart-cases-delta:checked ~ .chart-config-group label[for=chart-cases-delta], + .chart-config-container input#chart-data-active:not(:checked) ~ input#chart-data-cfr:not(:checked) ~ input#chart-cases-incidence:checked ~ .chart-config-group label[for=chart-cases-incidence], + .chart-config-container input#chart-data-active:checked ~ input#chart-cases-incidence:checked ~ .chart-config-group label[for=chart-cases-delta], + .chart-config-container input#chart-data-cfr:checked ~ input#chart-cases-incidence:checked ~ .chart-config-group label[for=chart-cases-delta], .chart-config-container input#chart-data-active:not(:checked) ~ input#chart-data-cfr:not(:checked) ~ input#chart-type-logarithmic:checked ~ .chart-config-group label[for=chart-type-logarithmic], .chart-config-container input#chart-data-active:not(:checked) ~ input#chart-cases-delta:not(:checked) ~ input#chart-type-logarithmic:checked ~ .chart-config-group label[for=chart-type-logarithmic], .chart-config-container input#chart-data-cfr:not(:checked) ~ input#chart-cases-delta:not(:checked) ~ input#chart-type-logarithmic:checked ~ .chart-config-group label[for=chart-type-logarithmic], @@ -346,6 +349,8 @@ color:black; box-shadow: 0px 0px 2px springgreen; } + .chart-config-container input#chart-data-active:checked ~ .chart-config-group label[for=chart-cases-incidence], + .chart-config-container input#chart-data-cfr:checked ~ .chart-config-group label[for=chart-cases-incidence], .chart-config-container input#chart-data-active:checked ~ input#chart-cases-delta:checked ~ .chart-config-group label[for=chart-type-logarithmic], .chart-config-container input#chart-data-cfr:checked ~ input#chart-cases-delta:checked ~ .chart-config-group label[for=chart-type-logarithmic], .chart-config-container input#chart-data-cfr:checked ~ .chart-config-group label[for=chart-values-per100k], @@ -408,6 +413,14 @@ .chart-config-container input#chart-data-cfr:checked ~ .chart-config-group .delta-change { display:inline; } + .chart-config-group .chart-average-average, + .chart-config-group .chart-average-incidence { + display:none; + } + .chart-config-container input#chart-cases-delta:checked ~ .chart-config-group .chart-average-average, + .chart-config-container input#chart-cases-incidence:checked ~ .chart-config-group .chart-average-incidence { + display:inline; + } .chart-config-group .per100k, .chart-config-group .permillion, .chart-config-group .data-confirmed, @@ -432,7 +445,8 @@ .chart-config-group.chart-average { display:none; } - .chart-config-container input#chart-cases-delta:checked ~ .chart-config-group.chart-average { + .chart-config-container input#chart-cases-delta:checked ~ .chart-config-group.chart-average, + .chart-config-container input#chart-cases-incidence:checked ~ .chart-config-group.chart-average { display:flex; } .chart-config-container #chart-average { @@ -505,6 +519,7 @@ + @@ -533,11 +548,12 @@
CasesRecoveriesDeathsChart:
+Average over:
+Average over:Incidence within:
-datapoints
+days
Values: