From 8bff273d8bdb35a0e4e37ceff85e23f129656e3a Mon Sep 17 00:00:00 2001 From: tmont Date: Wed, 29 Apr 2020 20:59:42 -0700 Subject: [PATCH] added doubling time, graph color tweaks --- generate.js | 100 +++++++++++++++++++++++++++--------------------- tmpl/master.pug | 28 +++++++++++--- 2 files changed, 80 insertions(+), 48 deletions(-) diff --git a/generate.js b/generate.js index 5483614..54fd622 100755 --- a/generate.js +++ b/generate.js @@ -154,23 +154,66 @@ const processGlobalDeaths = async () => { // return record['Country/Region'] === 'US'; // }); - const getGrowthRate = (record) => { - const ts = record.timeSeriesDaily; - const len = ts.length; + const getRollingAverage = (item) => { + return item.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), + }; + }); + + + }; + + const getDoublingTime = (item) => { + return item.timeSeriesDaily.map((item, i, arr) => { + const prevItems = arr.slice(Math.max(i - 7, 0), i); + const sum = prevItems.reduce((sum, next) => sum + next.value, 0); + let value; + if (sum < 10) { + value = 0; + } else { + const growthRate = calcGrowthRate(arr, i, 7); + if (growthRate <= 0 || !growthRate) { + value = 0; + } else { + value = (Math.log(2) / Math.log(1 + growthRate)); + } + } + + return { + key: item.key, + value: Math.round(value * 10) / 10, + }; + }); + }; + + const calcGrowthRate = (ts, i, range) => { + const items = ts.slice(Math.max(0, i - range), i + 1); + const len = items.length; if (len < 2) { return 0; } const latest = len - 1; - const earliest = Math.max(len - 14, 0); - const pow = 1/(latest - earliest + 1); - const hi = ts[latest].value; - const lo = Math.max(ts[earliest].value, 0.5); + const earliest = 0; + const pow = 1 / (latest - earliest + 1); + const hi = items[latest].value; + const lo = Math.max(items[earliest].value, 0.5); if (hi === 0 && lo < 1) { return 0; } return Math.pow((hi / lo), pow) - 1; }; + const getGrowthRate = (record) => { + return calcGrowthRate(record.timeSeriesDaily, record.timeSeriesDaily.length - 1, 7); + }; + // state/county data is separated for the US and doesn't need to be rolled up tsUSRecords.forEach((usRecord) => { const newRecord = { @@ -442,19 +485,9 @@ 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); + stateItem.rollingAverageDaily = getRollingAverage(stateItem); + stateItem.doublingDaily = getDoublingTime(stateItem); // insert into states array for the country perCountryTotals[item.country].states.push(stateItem); @@ -492,19 +525,9 @@ 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); + countryItem.rollingAverageDaily = getRollingAverage(countryItem); + countryItem.doublingDaily = getDoublingTime(countryItem); return countryItem; }); @@ -554,19 +577,10 @@ 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.rollingAverageDaily = getRollingAverage(worldData); + worldData.doublingDaily = getDoublingTime(worldData); + worldData.population = 7781841000; worldData.deathsPerMillion = worldData.total / worldData.population * 1000000; diff --git a/tmpl/master.pug b/tmpl/master.pug index 3d5ef03..ddf8ee1 100644 --- a/tmpl/master.pug +++ b/tmpl/master.pug @@ -22,6 +22,16 @@ html } script. + Chart.pluginService.register({ + beforeDraw: (chart) => { + const ctx = chart.chart.ctx; + ctx.save(); + ctx.fillStyle = 'white'; + ctx.fillRect(0, 0, chart.width, chart.height); + ctx.restore(); + } + }); + const charts = { logarithmic: false, heroChart: null, @@ -90,7 +100,7 @@ html axis.type = 'logarithmic'; const maxLogPower = Math.ceil(Math.log10(charts.heroMaxValue)); axis.ticks.max = Math.pow(10, maxLogPower); - axis.ticks.callback = value => Number(value.toString()); + axis.ticks.callback = value => Number(value.toString()).toLocaleString(); } else { axis.type = 'linear'; delete axis.ticks.max; @@ -114,7 +124,7 @@ html axis.type = 'logarithmic'; const maxLogPower = Math.ceil(Math.log10(data.maxValue)); axis.ticks.max = Math.pow(10, maxLogPower); - axis.ticks.callback = value => Number(value.toString()); + axis.ticks.callback = value => Number(value.toString()).toLocaleString(); } else { axis.type = 'linear'; delete axis.ticks.max; @@ -125,9 +135,8 @@ html }); } - function makeHeroChart(id, title, labels, totalDeaths, newDeaths, rollingAverage) { + function makeHeroChart(id, title, labels, totalDeaths, newDeaths, rollingAverage, doubling) { const canvas = document.getElementById(id); - charts.heroMaxValue = totalDeaths[totalDeaths.length - 1]; charts.heroChart = new Chart(canvas.getContext('2d'), { @@ -157,6 +166,14 @@ html fill: false, borderColor: 'rgb(96, 96, 96, 0.25)', borderWidth: 1, + }, + { + label: 'Days to 2x', + data: doubling, + fill: false, + borderColor: 'rgb(187,40,193, 0.5)', + borderWidth: 2, + pointRadius: 0, } ], }, @@ -183,7 +200,7 @@ html beginAtZero: true, min: 0, max: Math.pow(10, Math.ceil(Math.log10(charts.heroMaxValue))), - callback: value => Number(value.toString()) + callback: value => Number(value.toString()).toLocaleString(), }, afterBuildTicks: (axis, ticks) => { if (axis.type === 'logarithmic') { @@ -254,6 +271,7 @@ html !{JSON.stringify(data.timeSeriesDaily.map(x => x.value))}, !{JSON.stringify(data.timeSeriesDaily.map(x => x.delta))}, !{JSON.stringify(data.rollingAverageDaily.map(x => x.delta))}, + !{JSON.stringify(data.doublingDaily.map(x => x.value))}, ); mixin dataTable(items, label, type)