diff --git a/generate.js b/generate.js index bfa232b..5483614 100755 --- a/generate.js +++ b/generate.js @@ -442,6 +442,18 @@ const processGlobalDeaths = async () => { }), }; + stateItem.rollingAverageDaily = stateItem.timeSeriesDaily.map((item, i, arr) => { + const prevValues = arr.slice(Math.max(0, i - 6), i); + const valueAverage = (item.value + prevValues.reduce((sum, next) => sum + next.value, 0)) / (1 + prevValues.length); + const deltaAverage = (item.delta + prevValues.reduce((sum, next) => sum + next.delta, 0)) / (1 + prevValues.length); + + return { + key: item.key, + value: Math.round(valueAverage), + delta: Math.round(deltaAverage), + }; + }); + stateItem.deathGrowthRate = getGrowthRate(stateItem); // insert into states array for the country @@ -480,13 +492,25 @@ const processGlobalDeaths = async () => { }), }; + countryItem.rollingAverageDaily = countryItem.timeSeriesDaily.map((item, i, arr) => { + const prevValues = arr.slice(Math.max(0, i - 6), i); + const valueAverage = (item.value + prevValues.reduce((sum, next) => sum + next.value, 0)) / (1 + prevValues.length); + const deltaAverage = (item.delta + prevValues.reduce((sum, next) => sum + next.delta, 0)) / (1 + prevValues.length); + + return { + key: item.key, + value: Math.round(valueAverage), + delta: Math.round(deltaAverage), + }; + }); + countryItem.deathGrowthRate = getGrowthRate(countryItem); return countryItem; }); const worldData = { - name: 'The World', - safeName: 'the-world', + name: 'Worldwide', + safeName: 'worldwide', total: 0, countries: countryArr, timeSeriesDaily: {}, @@ -530,6 +554,18 @@ const processGlobalDeaths = async () => { }; }); + worldData.rollingAverageDaily = worldData.timeSeriesDaily.map((item, i) => { + const prevValues = worldData.timeSeriesDaily.slice(Math.max(0, i - 6), i); + const valueAverage = (item.value + prevValues.reduce((sum, next) => sum + next.value, 0)) / (1 + prevValues.length); + const deltaAverage = (item.delta + prevValues.reduce((sum, next) => sum + next.delta, 0)) / (1 + prevValues.length); + + return { + key: item.key, + value: Math.round(valueAverage), + delta: Math.round(deltaAverage), + }; + }); + worldData.deathGrowthRate = getGrowthRate(worldData); worldData.population = 7781841000; worldData.deathsPerMillion = worldData.total / worldData.population * 1000000; diff --git a/tmpl/master.pug b/tmpl/master.pug index 5bc59cb..3d5ef03 100644 --- a/tmpl/master.pug +++ b/tmpl/master.pug @@ -16,6 +16,10 @@ html .table-sm { font-size: 80%; } + .table-sm code { + font-size: 110%; + color: inherit; + } script. const charts = { @@ -30,12 +34,12 @@ html const chart = new Chart(canvas.getContext('2d'), { type: 'line', data: { - labels: [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14], + labels: new Array(data.length), datasets: [{ data: data, - borderColor: 'rgb(53,120,193)', + borderColor: 'rgb(53, 120, 193)', borderWidth: 1, - backgroundColor: 'rgba(148,193,250,0.51)', + backgroundColor: 'rgba(148, 193, 250, 0.50)', }], }, options: { @@ -55,9 +59,12 @@ html yAxes: [ { display: false, + type: 'logarithmic', ticks: { precision: 0, beginAtZero: true, + max: Math.pow(10, Math.ceil(Math.log10(maxValue))), + callback: value => Number(value.toString()), } }, ], @@ -103,7 +110,7 @@ html charts.trends.forEach((data) => { const axis = data.chart.options.scales.yAxes[0]; - if (axis.type === 'linear') { + if (type === 'logarithmic') { axis.type = 'logarithmic'; const maxLogPower = Math.ceil(Math.log10(data.maxValue)); axis.ticks.max = Math.pow(10, maxLogPower); @@ -118,7 +125,7 @@ html }); } - function makeHeroChart(id, title, labels, totalDeaths, newDeaths) { + function makeHeroChart(id, title, labels, totalDeaths, newDeaths, rollingAverage) { const canvas = document.getElementById(id); charts.heroMaxValue = totalDeaths[totalDeaths.length - 1]; @@ -129,20 +136,27 @@ html labels: labels, datasets: [ { - label: 'Total Deaths', + label: 'Cumulative', data: totalDeaths, - fill: true, + fill: '1', borderColor: 'rgb(196, 64, 64)', borderWidth: 1, backgroundColor: 'rgba(196, 128, 128, 0.25)', }, { - label: 'New Deaths', - data: newDeaths, - fill: true, - borderColor: 'rgb(64, 64, 64)', + label: 'New (rolling)', + data: rollingAverage, + fill: 'origin', + borderColor: 'rgb(20,24,59)', + borderWidth: 1, + backgroundColor: 'rgba(96, 96, 164, 0.25)', + }, + { + label: 'New', + data: newDeaths, + fill: false, + borderColor: 'rgb(96, 96, 96, 0.25)', borderWidth: 1, - backgroundColor: 'rgba(128, 128, 128, 0.75)', } ], }, @@ -163,9 +177,13 @@ html yAxes: [ { display: true, + type: 'logarithmic', ticks: { precision: 0, beginAtZero: true, + min: 0, + max: Math.pow(10, Math.ceil(Math.log10(charts.heroMaxValue))), + callback: value => Number(value.toString()) }, afterBuildTicks: (axis, ticks) => { if (axis.type === 'logarithmic') { @@ -212,19 +230,22 @@ html type="button" onclick="setAxisType('linear')" autocomplete="off" - disabled ) Linear button.btn.btn-secondary.btn-sm.set-axis-logarithmic( type="button" onclick="setAxisType('logarithmic')" autocomplete="off" + disabled ) Logarithmic canvas.mx-auto(id="main-chart" width="800" height="450") - const growthRate = '+' + (data.deathGrowthRate * 100).toFixed(2) + '%'; const population = 'pop. ' + data.population.toLocaleString(); - const deathsPerMillion = Math.round(data.deathsPerMillion) + '/million'; - const heroTitle = data.name + ` (${population}, ${deathsPerMillion}, ${growthRate})`; + const deathsPerMillion = Math.round(data.deathsPerMillion).toLocaleString() + '/MM'; + const heroTitle = [ + 'Covid-19 Deaths: ' + data.name, + `${population} | ${deathsPerMillion} | ${growthRate}` + ]; script. makeHeroChart( 'main-chart', @@ -232,6 +253,7 @@ html !{JSON.stringify(data.timeSeriesDaily.map(x => x.key))}, !{JSON.stringify(data.timeSeriesDaily.map(x => x.value))}, !{JSON.stringify(data.timeSeriesDaily.map(x => x.delta))}, + !{JSON.stringify(data.rollingAverageDaily.map(x => x.delta))}, ); mixin dataTable(items, label, type) @@ -246,9 +268,10 @@ html th.text-center(data-col="million"): +sortableLinks("million") per 1M th.text-center(data-col="total"): +sortableLinks("total") Total th.text-center.sorted(data-col="today"): +sortableLinks("today") Today - th.text-center(data-col="last7"): +sortableLinks("last7") Last 7 days - th.text-center(data-col="last30"): +sortableLinks("last30") Last 30 days - th.text-center(data-col="growth"): +sortableLinks("growth") Growth Rate + th.text-center(data-col="yesterday"): +sortableLinks("yesterday") Yesterday + th.text-center(data-col="last7"): +sortableLinks("last7") Last 7 + th.text-center(data-col="last30"): +sortableLinks("last30") Last 30 + th.text-center(data-col="growth"): +sortableLinks("growth") Growth th.text-center Trend - @@ -267,6 +290,7 @@ html const getValue = offset => (item.timeSeriesDaily[item.timeSeriesDaily.length - offset] || {}).value || 0; const getDelta = offset => (item.timeSeriesDaily[item.timeSeriesDaily.length - offset] || {}).delta || 0; const today = getDelta(1); + const yesterday = getDelta(2); const last7 = getValue(1) - getValue(7); const last30 = getValue(1) - getValue(30); tr( @@ -276,6 +300,7 @@ html data-total=item.total data-million=item.deathsPerMillion data-today=today + data-yesterday=yesterday data-last7=last7 data-last30=last30 data-growth=item.deathGrowthRate @@ -287,6 +312,7 @@ html td.text-right: code: +formatNumber(Math.round(item.deathsPerMillion)) td.text-right: code: +formatNumber(item.total) td.text-right.sorted: code: +formatNumber(today) + td.text-right: code: +formatNumber(yesterday) td.text-right: code: +formatNumber(last7) td.text-right: code: +formatNumber(last30) td.text-right: code= Number(item.deathGrowthRate * 100).toFixed(2) + '%' @@ -396,6 +422,7 @@ html break; case 'total': case 'today': + case 'yesterday': case 'last7': case 'last30': case 'population':