diff --git a/generate.js b/generate.js index 54fd622..7743f64 100755 --- a/generate.js +++ b/generate.js @@ -157,25 +157,28 @@ const processGlobalDeaths = async () => { 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); + let valueAverage = (item.value + prevValues.reduce((sum, next) => sum + next.value, 0)) / (1 + prevValues.length); + let deltaAverage = (item.delta + prevValues.reduce((sum, next) => sum + next.delta, 0)) / (1 + prevValues.length); + + if (valueAverage < 1) { + valueAverage = Math.round(valueAverage); + } + if (deltaAverage < 1) { + deltaAverage = Math.round(deltaAverage); + } return { key: item.key, - value: Math.round(valueAverage), - delta: Math.round(deltaAverage), + value: Math.round(valueAverage * 10) / 10, + delta: Math.round(deltaAverage * 10) / 10, }; }); - - }; 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) { + if (item.value < 10) { value = 0; } else { const growthRate = calcGrowthRate(arr, i, 7); diff --git a/tmpl/master.pug b/tmpl/master.pug index ddf8ee1..52eb4a8 100644 --- a/tmpl/master.pug +++ b/tmpl/master.pug @@ -32,6 +32,31 @@ html } }); + // https://stackoverflow.com/a/45172506 + Chart.defaults.lineVerticalTooltip = Chart.defaults.line; + Chart.controllers.lineVerticalTooltip = Chart.controllers.line.extend({ + draw: function(ease) { + Chart.controllers.line.prototype.draw.call(this, ease); + + if (this.chart.tooltip._active && this.chart.tooltip._active.length) { + const activePoint = this.chart.tooltip._active[0]; + const ctx = this.chart.ctx; + const x = activePoint.tooltipPosition().x; + const topY = this.chart.legend.bottom; + const bottomY = this.chart.chartArea.bottom; + + ctx.save(); + ctx.beginPath(); + ctx.moveTo(x, topY); + ctx.lineTo(x, bottomY); + ctx.lineWidth = 1; + ctx.strokeStyle = 'rgba(96, 96, 96, 0.75)'; + ctx.stroke(); + ctx.restore(); + } + } + }); + const charts = { logarithmic: false, heroChart: null, @@ -40,7 +65,7 @@ html function makeSparkline(id, data) { const canvas = document.getElementById(id); - const maxValue = data[data.length - 1]; + const maxValue = data.reduce((max, value) => Math.max(max, value), 0); const chart = new Chart(canvas.getContext('2d'), { type: 'line', data: { @@ -73,7 +98,8 @@ html ticks: { precision: 0, beginAtZero: true, - max: Math.pow(10, Math.ceil(Math.log10(maxValue))), + min: 0, + max: maxValue > 0 ? Math.pow(10, Math.ceil(Math.log10(maxValue))) : 0, callback: value => Number(value.toString()), } }, @@ -139,14 +165,29 @@ html const canvas = document.getElementById(id); charts.heroMaxValue = totalDeaths[totalDeaths.length - 1]; + const firstNonZeroDeathIndex = totalDeaths.findIndex(value => value > 0); + const start = Math.max(0, firstNonZeroDeathIndex - 2); + const end = totalDeaths.length; + + const totalData = totalDeaths.slice(start, end); + const newData = newDeaths.slice(start, end); + const rollingData = rollingAverage.slice(start, end); + const doublingData = doubling.slice(start, end); + + const months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ]; + + const realLabels = labels + .slice(start, end) + .map((date) => date.replace(/\d{4}-(\d\d?)-(\d\d?)/, (_, m, d) => `${months[Number(m) - 1]} ${Number(d)}`)); + charts.heroChart = new Chart(canvas.getContext('2d'), { - type: 'line', + type: 'lineVerticalTooltip', data: { - labels: labels, + labels: realLabels, datasets: [ { label: 'Cumulative', - data: totalDeaths, + data: totalData, fill: '1', borderColor: 'rgb(196, 64, 64)', borderWidth: 1, @@ -154,7 +195,7 @@ html }, { label: 'New (rolling)', - data: rollingAverage, + data: rollingData, fill: 'origin', borderColor: 'rgb(20,24,59)', borderWidth: 1, @@ -162,14 +203,14 @@ html }, { label: 'New', - data: newDeaths, + data: newData, fill: false, borderColor: 'rgb(96, 96, 96, 0.25)', borderWidth: 1, }, { label: 'Days to 2x', - data: doubling, + data: doublingData, fill: false, borderColor: 'rgb(187,40,193, 0.5)', borderWidth: 2, @@ -339,7 +380,7 @@ html script. makeSparkline( "sparkline-#{i}", - #{JSON.stringify(item.timeSeriesDaily.slice(-14).map(x => x.value))}, + #{JSON.stringify(item.timeSeriesDaily.slice(-14).map(x => x.delta))}, ); @@ -371,7 +412,6 @@ html const resortTable = (col) => { let nextChild = null; const highlightedIndex = headers.findIndex(cell => cell.getAttribute('data-col') === col); - console.log(col, highlightedIndex); headers.forEach((cell, i) => { if (i !== highlightedIndex) { cell.classList.remove('sorted'); @@ -383,8 +423,11 @@ html for (let i = allRows.length - 1; i >= 0; i--) { const row = allRows[i]; if (!row) { + console.log(i + ' no row!'); continue; } + const name = row.getAttribute('data-name'); + console.log(row.id, name, !name && row); const cells = [].slice.call(row.querySelectorAll('td')); cells.forEach((cell, i) => { if (i !== highlightedIndex) { @@ -401,6 +444,7 @@ html row.querySelector('.sort-order').textContent = (i + 1).toString(); nextChild = row; } + console.log('---'); }; const handleSort = (value, dir) => { @@ -412,7 +456,7 @@ html if (aValue === bValue) { const aName = a.getAttribute('data-name'); const bName = b.getAttribute('data-name'); - return aName && bName ? aName.localeCompare(bName) : 0; + return aName && bName ? aName.localeCompare(bName) : -1; } return aValue < bValue ?