added more case data, table styling

This commit is contained in:
tmont 2020-05-02 18:57:59 -07:00
parent a4afdeb8a6
commit c08e7373e2
2 changed files with 93 additions and 34 deletions

View File

@ -235,8 +235,8 @@ const processRecords = async () => {
return Math.pow((hi / lo), pow) - 1; return Math.pow((hi / lo), pow) - 1;
}; };
const getGrowthRate = (record) => { const getGrowthRate = (ts) => {
return calcGrowthRate(record.timeSeriesDaily, record.timeSeriesDaily.length - 1, 7); return calcGrowthRate(ts, ts.length - 1, 7);
}; };
const normalizeRecord = (record) => { const normalizeRecord = (record) => {
@ -404,6 +404,7 @@ const processRecords = async () => {
} }
record.cases = { record.cases = {
total: confirmedCases.timeSeriesDaily[confirmedCases.timeSeriesDaily.length - 1].value,
timeSeriesDaily: confirmedCases.timeSeriesDaily, timeSeriesDaily: confirmedCases.timeSeriesDaily,
timeSeriesMonthly: confirmedCases.timeSeriesMonthly, timeSeriesMonthly: confirmedCases.timeSeriesMonthly,
rollingAverageDaily: confirmedCases.rollingAverageDaily, rollingAverageDaily: confirmedCases.rollingAverageDaily,
@ -428,7 +429,9 @@ const processRecords = async () => {
} }
record.deathsPerMillion = !!record.population ? record.total / record.population * 1000000 : 0; record.deathsPerMillion = !!record.population ? record.total / record.population * 1000000 : 0;
record.deathGrowthRate = getGrowthRate(record); record.casesPerMillion = !!record.population ? record.cases.total / record.population * 1000000 : 0;
record.deathGrowthRate = getGrowthRate(record.timeSeriesDaily);
record.caseGrowthRate = getGrowthRate(record.cases.timeSeriesDaily);
}); });
tsGlobalRecords.sort((a, b) => { tsGlobalRecords.sort((a, b) => {
@ -447,9 +450,12 @@ const processRecords = async () => {
population: 0, population: 0,
timeSeriesDaily: {}, timeSeriesDaily: {},
timeSeriesMonthly: {}, timeSeriesMonthly: {},
deathsPerMillion: 0,
casesPerMillion: 0,
states: [], states: [],
safeName: record.countrySafeName, safeName: record.countrySafeName,
cases: { cases: {
total: 0,
timeSeriesDaily: {}, timeSeriesDaily: {},
timeSeriesMonthly: {}, timeSeriesMonthly: {},
}, },
@ -479,9 +485,11 @@ const processRecords = async () => {
total: 0, total: 0,
population: 0, population: 0,
deathsPerMillion: 0, deathsPerMillion: 0,
casesPerMillion: 0,
timeSeriesDaily: {}, timeSeriesDaily: {},
timeSeriesMonthly: {}, timeSeriesMonthly: {},
cases: { cases: {
total: 0,
timeSeriesDaily: {}, timeSeriesDaily: {},
timeSeriesMonthly: {}, timeSeriesMonthly: {},
}, },
@ -499,6 +507,7 @@ const processRecords = async () => {
} }
stateItem.total += record.total; stateItem.total += record.total;
stateItem.cases.total += record.cases.total;
record.timeSeriesDaily.forEach((ts) => { record.timeSeriesDaily.forEach((ts) => {
stateItem.timeSeriesDaily[ts.key] = stateItem.timeSeriesDaily[ts.key] || { stateItem.timeSeriesDaily[ts.key] = stateItem.timeSeriesDaily[ts.key] || {
value: 0, value: 0,
@ -545,6 +554,8 @@ const processRecords = async () => {
} }
item.total += record.total; item.total += record.total;
item.cases.total += record.cases.total;
record.timeSeriesDaily.forEach((ts) => { record.timeSeriesDaily.forEach((ts) => {
item.timeSeriesDaily[ts.key] = item.timeSeriesDaily[ts.key] || { item.timeSeriesDaily[ts.key] = item.timeSeriesDaily[ts.key] || {
value: 0, value: 0,
@ -596,6 +607,7 @@ const processRecords = async () => {
counties: item.counties, counties: item.counties,
population: item.population, population: item.population,
deathsPerMillion: item.population > 0 ? item.total / item.population * 1000000 : 0, deathsPerMillion: item.population > 0 ? item.total / item.population * 1000000 : 0,
casesPerMillion: item.population > 0 ? item.cases.total / item.population * 1000000 : 0,
timeSeriesDaily: Object.keys(item.timeSeriesDaily).sort().map((date) => { timeSeriesDaily: Object.keys(item.timeSeriesDaily).sort().map((date) => {
return { return {
key: date, key: date,
@ -611,6 +623,7 @@ const processRecords = async () => {
}; };
}), }),
cases: { cases: {
total: item.cases.total,
timeSeriesDaily: Object.keys(item.cases.timeSeriesDaily).sort().map((date) => { timeSeriesDaily: Object.keys(item.cases.timeSeriesDaily).sort().map((date) => {
return { return {
key: date, key: date,
@ -628,7 +641,8 @@ const processRecords = async () => {
}, },
}; };
stateItem.deathGrowthRate = getGrowthRate(stateItem); stateItem.deathGrowthRate = getGrowthRate(stateItem.timeSeriesDaily);
stateItem.caseGrowthRate = getGrowthRate(stateItem.cases.timeSeriesDaily);
stateItem.rollingAverageDaily = getRollingAverage(stateItem); stateItem.rollingAverageDaily = getRollingAverage(stateItem);
stateItem.doublingDaily = getDoublingTime(stateItem); stateItem.doublingDaily = getDoublingTime(stateItem);
stateItem.cases.rollingAverageDaily = getRollingAverage(stateItem.cases); stateItem.cases.rollingAverageDaily = getRollingAverage(stateItem.cases);
@ -657,6 +671,7 @@ const processRecords = async () => {
states: item.states, states: item.states,
population: item.population, population: item.population,
deathsPerMillion: item.population > 0 ? item.total / item.population * 1000000 : 0, deathsPerMillion: item.population > 0 ? item.total / item.population * 1000000 : 0,
casesPerMillion: item.population > 0 ? item.cases.total / item.population * 1000000 : 0,
timeSeriesDaily: Object.keys(item.timeSeriesDaily).sort().map((date) => { timeSeriesDaily: Object.keys(item.timeSeriesDaily).sort().map((date) => {
return { return {
key: date, key: date,
@ -672,6 +687,7 @@ const processRecords = async () => {
}; };
}), }),
cases: { cases: {
total: item.cases.total,
timeSeriesDaily: Object.keys(item.cases.timeSeriesDaily).sort().map((date) => { timeSeriesDaily: Object.keys(item.cases.timeSeriesDaily).sort().map((date) => {
return { return {
key: date, key: date,
@ -689,7 +705,8 @@ const processRecords = async () => {
}, },
}; };
countryItem.deathGrowthRate = getGrowthRate(countryItem); countryItem.deathGrowthRate = getGrowthRate(countryItem.timeSeriesDaily);
countryItem.caseGrowthRate = getGrowthRate(countryItem.cases.timeSeriesDaily);
countryItem.rollingAverageDaily = getRollingAverage(countryItem); countryItem.rollingAverageDaily = getRollingAverage(countryItem);
countryItem.doublingDaily = getDoublingTime(countryItem); countryItem.doublingDaily = getDoublingTime(countryItem);
countryItem.cases.rollingAverageDaily = getRollingAverage(countryItem.cases); countryItem.cases.rollingAverageDaily = getRollingAverage(countryItem.cases);
@ -704,6 +721,7 @@ const processRecords = async () => {
timeSeriesDaily: {}, timeSeriesDaily: {},
timeSeriesMonthly: {}, timeSeriesMonthly: {},
cases: { cases: {
total: 0,
timeSeriesDaily: {}, timeSeriesDaily: {},
timeSeriesMonthly: {}, timeSeriesMonthly: {},
}, },
@ -711,6 +729,7 @@ const processRecords = async () => {
countryArr.forEach((countryData) => { countryArr.forEach((countryData) => {
worldData.total += countryData.total; worldData.total += countryData.total;
worldData.cases.total += countryData.cases.total;
countryData.timeSeriesDaily.forEach((ts) => { countryData.timeSeriesDaily.forEach((ts) => {
worldData.timeSeriesDaily[ts.key] = worldData.timeSeriesDaily[ts.key] || { worldData.timeSeriesDaily[ts.key] = worldData.timeSeriesDaily[ts.key] || {
@ -778,13 +797,15 @@ const processRecords = async () => {
}; };
}); });
worldData.deathGrowthRate = getGrowthRate(worldData); worldData.deathGrowthRate = getGrowthRate(worldData.timeSeriesDaily);
worldData.casesGrowthRate = getGrowthRate(worldData.cases.timeSeriesDaily);
worldData.rollingAverageDaily = getRollingAverage(worldData); worldData.rollingAverageDaily = getRollingAverage(worldData);
worldData.doublingDaily = getDoublingTime(worldData); worldData.doublingDaily = getDoublingTime(worldData);
worldData.cases.rollingAverageDaily = getRollingAverage(worldData.cases); worldData.cases.rollingAverageDaily = getRollingAverage(worldData.cases);
worldData.population = 7781841000; worldData.population = 7781841000;
worldData.deathsPerMillion = worldData.total / worldData.population * 1000000; worldData.deathsPerMillion = worldData.total / worldData.population * 1000000;
worldData.casesPerMillion = worldData.cases.total / worldData.population * 1000000;
console.log(`transformed data in ${Date.now() - start}ms`); console.log(`transformed data in ${Date.now() - start}ms`);
@ -792,7 +813,7 @@ const processRecords = async () => {
const worldTmpl = path.join(templatesDir, 'world.pug'); const worldTmpl = path.join(templatesDir, 'world.pug');
const worldHtml = pug.renderFile(worldTmpl, { const worldHtml = pug.renderFile(worldTmpl, {
data: worldData, data: worldData,
$title: 'The World', $title: 'Worldwide',
lastUpdate, lastUpdate,
}); });

View File

@ -13,6 +13,18 @@ html
th.sorted, td.sorted { th.sorted, td.sorted {
background-color: #e0eaf7; background-color: #e0eaf7;
} }
thead.headers th {
position: sticky;
top: 0;
}
thead.headers th:after {
content: '';
position: absolute;
left: 0;
width: 100%;
bottom: -1px;
border-bottom: 2px solid #b5b5b5;
}
.geo-bg-dark { .geo-bg-dark {
background-color: #8e8e8e; background-color: #8e8e8e;
color: white; color: white;
@ -92,20 +104,30 @@ html
trends: [], trends: [],
}; };
function makeSparkline(id, data) { function makeSparkline(id, deathData, caseData) {
const canvas = document.getElementById(id); const canvas = document.getElementById(id);
const maxValue = data.reduce((max, value) => Math.max(max, value), 0); const maxValue = caseData.reduce((max, value) => Math.max(max, value), 0);
const max = maxValue > 0 ? Math.pow(10, Math.ceil(Math.log10(maxValue))) : 0 const max = maxValue > 0 ? Math.pow(10, Math.ceil(Math.log10(maxValue))) : 0
const chart = new Chart(canvas.getContext('2d'), { const chart = new Chart(canvas.getContext('2d'), {
type: 'line', type: 'line',
data: { data: {
labels: new Array(data.length), labels: new Array(deathData.length),
datasets: [{ datasets: [
data: data, {
borderColor: 'rgb(53, 120, 193)', data: caseData,
borderWidth: 1, borderColor: 'rgb(161,150,20)',
backgroundColor: 'rgba(148, 193, 250, 0.50)', borderWidth: 1,
}], backgroundColor: 'rgba(161,150,20, 0.25)',
fill: '1',
},
{
data: deathData,
borderColor: 'rgb(196, 64, 64)',
borderWidth: 1,
backgroundColor: 'rgba(196, 64, 64, 0.25)',
fill: 'origin',
},
],
}, },
options: { options: {
responsive: false, responsive: false,
@ -344,7 +366,7 @@ html
mixin heroChart() mixin heroChart()
div.card.mb-4 div.card.mb-4
div.card-body.position-relative div.card-body.position-relative
div.position-absolute(style="top: 10px; right: 10px") div.position-absolute(style="top: 10px; right: 10px; z-index: 2;")
div.btn-group div.btn-group
button.btn.btn-secondary.btn-sm.set-axis-linear( button.btn.btn-secondary.btn-sm.set-axis-linear(
type="button" type="button"
@ -357,7 +379,8 @@ html
autocomplete="off" autocomplete="off"
disabled disabled
) Logarithmic ) Logarithmic
canvas.mx-auto(id="main-chart" width="1024" height="576") div.mx-auto.position-relative(style="max-width: 1024px; z-index: 1")
canvas.mx-auto(id="main-chart" width="1024" height="576")
- -
const growthRate = '+' + (data.deathGrowthRate * 100).toFixed(2) + '%'; const growthRate = '+' + (data.deathGrowthRate * 100).toFixed(2) + '%';
const population = 'pop. ' + data.population.toLocaleString(); const population = 'pop. ' + data.population.toLocaleString();
@ -382,19 +405,25 @@ html
mixin dataTable(items, label, type) mixin dataTable(items, label, type)
- const hasPopulation = type !== 'state' || data.name === 'United States'; - const hasPopulation = type !== 'state' || data.name === 'United States';
div#table.table-responsive: table.table.table-sm.table-hover div#table: table.table.table-sm.table-hover
thead: tr thead: tr
th.text-center.font-weight-bold.geo-bg-dark(colspan=(hasPopulation ? 3 : 2)) Geography th.text-center.font-weight-bold.geo-bg-dark(colspan=(hasPopulation ? 3 : 2)) Geography
th.text-center.font-weight-bold.cases-bg-dark(colspan="2") Cases th.text-center.font-weight-bold.cases-bg-dark(colspan=(hasPopulation ? 6 : 5)) Cases
th.text-center.font-weight-bold.deaths-bg-dark(colspan="100") Deaths th.text-center.font-weight-bold.deaths-bg-dark(colspan=(hasPopulation ? 6 : 5)) Deaths
th.text-center.font-weight-bold.geo-bg-dark Trends
thead.headers: tr thead.headers: tr
th.geo-bg # th.geo-bg #
th.geo-bg(data-col="name"): +sortableLinks("name", true)= label th.geo-bg(data-col="name"): +sortableLinks("name", true)= label
if hasPopulation if hasPopulation
th.geo-bg(data-col="population"): +sortableLinks("population") Population th.geo-bg(data-col="population"): +sortableLinks("population") Population
if hasPopulation
th.cases-bg(data-col="cases-million"): +sortableLinks("cases-million") per 1M
th.cases-bg(data-col="cases-total"): +sortableLinks("cases-total") Total th.cases-bg(data-col="cases-total"): +sortableLinks("cases-total") Total
th.cases-bg(data-col="cases-today"): +sortableLinks("cases-today") Today th.cases-bg(data-col="cases-today"): +sortableLinks("cases-today") Today
th.cases-bg(data-col="cases-yesterday"): +sortableLinks("cases-yesterday") Yesterday
th.cases-bg(data-col="cases-last7"): +sortableLinks("cases-last7") Last 7
th.cases-bg(data-col="cases-growth"): +sortableLinks("cases-growth") Growth
if hasPopulation if hasPopulation
th.deaths-bg(data-col="million"): +sortableLinks("million") per 1M th.deaths-bg(data-col="million"): +sortableLinks("million") per 1M
@ -402,9 +431,8 @@ html
th.sorted.deaths-bg(data-col="today"): +sortableLinks("today") Today th.sorted.deaths-bg(data-col="today"): +sortableLinks("today") Today
th.deaths-bg(data-col="yesterday"): +sortableLinks("yesterday") Yesterday th.deaths-bg(data-col="yesterday"): +sortableLinks("yesterday") Yesterday
th.deaths-bg(data-col="last7"): +sortableLinks("last7") Last 7 th.deaths-bg(data-col="last7"): +sortableLinks("last7") Last 7
th.deaths-bg(data-col="last30"): +sortableLinks("last30") Last 30
th.deaths-bg(data-col="growth"): +sortableLinks("growth") Growth th.deaths-bg(data-col="growth"): +sortableLinks("growth") Growth
th.text-center.deaths-bg Trend th.text-center.geo-bg Last 14 days
- -
items.sort((a, b) => { items.sort((a, b) => {
@ -426,48 +454,55 @@ html
const today = getDelta(1); const today = getDelta(1);
const yesterday = getDelta(2); const yesterday = getDelta(2);
const last7 = getValue(1) - getValue(7); const last7 = getValue(1) - getValue(7);
const last30 = getValue(1) - getValue(30);
const casesTotal = getValueCases(1);
const casesToday = getDeltaCases(1); const casesToday = getDeltaCases(1);
const casesLast7 = getValueCases(1) - getValueCases(7);
const casesYesterday = getDeltaCases(1);
tr( tr(
id=("row-" + (item.safeName || '_')) id=("row-" + (item.safeName || '_'))
data-name=(item.name || '_') data-name=(item.name || '_')
data-cases-total=casesTotal data-cases-total=item.cases.total
data-cases-today=casesToday data-cases-today=casesToday
data-cases-last7=casesLast7
data-cases-million=item.casesPerMillion
data-cases-yesterday=casesYesterday
data-cases-growth=item.caseGrowthRate
data-population=item.population data-population=item.population
data-total=item.total data-total=item.total
data-million=item.deathsPerMillion data-million=item.deathsPerMillion
data-today=today data-today=today
data-yesterday=yesterday data-yesterday=yesterday
data-last7=last7 data-last7=last7
data-last30=last30
data-growth=item.deathGrowthRate data-growth=item.deathGrowthRate
) )
td.sort-order= i + 1 td.sort-order= i + 1
td: +renderItemName(item) td: +renderItemName(item)
if hasPopulation if hasPopulation
td.text-right: code: +formatNumber(item.population) td.text-right: code: +formatNumber(item.population)
td.text-right: code: +formatNumber(casesTotal)
if hasPopulation
td.text-right: code: +formatNumber(Math.round(item.casesPerMillion))
td.text-right: code: +formatNumber(item.cases.total)
td.text-right: code: +formatNumber(casesToday) td.text-right: code: +formatNumber(casesToday)
td.text-right: code: +formatNumber(casesYesterday)
td.text-right: code: +formatNumber(casesLast7)
td.text-right: code= Number(item.caseGrowthRate * 100).toFixed(2) + '%'
if hasPopulation if hasPopulation
td.text-right: code: +formatNumber(Math.round(item.deathsPerMillion)) td.text-right: code: +formatNumber(Math.round(item.deathsPerMillion))
td.text-right: code: +formatNumber(item.total) td.text-right: code: +formatNumber(item.total)
td.text-right.sorted: code: +formatNumber(today) td.text-right.sorted: code: +formatNumber(today)
td.text-right: code: +formatNumber(yesterday) td.text-right: code: +formatNumber(yesterday)
td.text-right: code: +formatNumber(last7) td.text-right: code: +formatNumber(last7)
td.text-right: code: +formatNumber(last30)
td.text-right: code= Number(item.deathGrowthRate * 100).toFixed(2) + '%' td.text-right: code= Number(item.deathGrowthRate * 100).toFixed(2) + '%'
td td
canvas.mx-auto(id="sparkline-" + i width="100" height="35") canvas.mx-auto(id="sparkline-" + i width="100" height="35")
script. script.
makeSparkline( makeSparkline(
"sparkline-#{i}", "sparkline-#{i}",
#{JSON.stringify(item.rollingAverageDaily.slice(-14).map(x => x.delta))} #{JSON.stringify(item.rollingAverageDaily.slice(-14).map(x => x.delta))},
#{JSON.stringify(item.cases.rollingAverageDaily.slice(-14).map(x => x.delta))},
); );
div.container-fluid.mt-2(style="max-width: 1600px")
div.container.mt-2
h1.text-center Covid-19 Data h1.text-center Covid-19 Data
div.d-flex.justify-content-around.font-italic.small div.d-flex.justify-content-around.font-italic.small
div div
@ -561,10 +596,13 @@ html
case 'total': case 'total':
case 'cases-total': case 'cases-total':
case 'cases-today': case 'cases-today':
case 'cases-yesterday':
case 'cases-million':
case 'cases-last7':
case 'cases-growth':
case 'today': case 'today':
case 'yesterday': case 'yesterday':
case 'last7': case 'last7':
case 'last30':
case 'population': case 'population':
case 'million': case 'million':
case 'growth': case 'growth':