added case data
This commit is contained in:
parent
b86ac75ffd
commit
a4afdeb8a6
250
generate.js
250
generate.js
@ -62,7 +62,7 @@ const lastUpdate = new Date(lastGlobalDeathsUpdate > lastUSDeathsUpdate ?
|
||||
const zeroPad = value => value < 10 ? `0${value}` : value.toString();
|
||||
const toSafeName = x => x.replace(/[^A-Za-z]/g, '-').toLowerCase();
|
||||
|
||||
const processGlobalDeaths = async () => {
|
||||
const processRecords = async () => {
|
||||
const globalStart = Date.now();
|
||||
let start = Date.now();
|
||||
|
||||
@ -81,6 +81,14 @@ const processGlobalDeaths = async () => {
|
||||
const populationCountriesRaw = fs.readFileSync(populationCountriesCsv, {encoding: 'utf8'});
|
||||
console.log(`read countries population CSV in ${Date.now() - start}ms`);
|
||||
|
||||
start = Date.now();
|
||||
const casesGlobalRaw = fs.readFileSync(confirmedGlobalCsv, {encoding: 'utf8'});
|
||||
console.log(`read global confirmed CSV in ${Date.now() - start}ms`);
|
||||
|
||||
start = Date.now();
|
||||
const casesUSRaw = fs.readFileSync(confirmedUSCsv, {encoding: 'utf8'});
|
||||
console.log(`read US confirmed CSV in ${Date.now() - start}ms`);
|
||||
|
||||
start = Date.now();
|
||||
let tsGlobalRecords = parseCsv(timeSeriesGlobalRaw, {
|
||||
cast: true,
|
||||
@ -95,6 +103,20 @@ const processGlobalDeaths = async () => {
|
||||
});
|
||||
console.log(`parsed US deaths CSV in ${Date.now() - start}ms`);
|
||||
|
||||
start = Date.now();
|
||||
let tsCasesGlobal = parseCsv(casesGlobalRaw, {
|
||||
cast: true,
|
||||
columns: true,
|
||||
});
|
||||
console.log(`parsed global cases CSV in ${Date.now() - start}ms`);
|
||||
|
||||
start = Date.now();
|
||||
let tsCasesUS = parseCsv(casesUSRaw, {
|
||||
cast: true,
|
||||
columns: true,
|
||||
});
|
||||
console.log(`parsed US cases CSV in ${Date.now() - start}ms`);
|
||||
|
||||
start = Date.now();
|
||||
let populationUSStateRecords = parseCsv(populationUSRaw, {
|
||||
cast: true,
|
||||
@ -217,31 +239,7 @@ const processGlobalDeaths = async () => {
|
||||
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 = {
|
||||
...usRecord,
|
||||
needsRollup: false,
|
||||
Long: usRecord.Long_,
|
||||
'Province/State': usRecord.Province_State,
|
||||
'Country/Region': usRecord.Country_Region,
|
||||
};
|
||||
|
||||
delete newRecord.UID;
|
||||
delete newRecord.iso2;
|
||||
delete newRecord.iso3;
|
||||
delete newRecord.code3;
|
||||
delete newRecord.FIPS;
|
||||
delete newRecord.Combined_Key;
|
||||
delete newRecord.Long_;
|
||||
delete newRecord.Province_State;
|
||||
delete newRecord.Country_Region;
|
||||
|
||||
tsGlobalRecords.push(newRecord);
|
||||
});
|
||||
|
||||
start = Date.now();
|
||||
tsGlobalRecords.forEach((record) => {
|
||||
const normalizeRecord = (record) => {
|
||||
record.timeSeriesDaily = [];
|
||||
record.timeSeriesMonthly = [];
|
||||
const dateColumns = Object.keys(record).filter(x => /^\d+\/\d+\/\d+$/.test(x))
|
||||
@ -335,6 +333,81 @@ const processGlobalDeaths = async () => {
|
||||
delete record.Long;
|
||||
delete record.Admin2;
|
||||
delete record.Population;
|
||||
};
|
||||
|
||||
const getRecordKey = record => `${record.country || ''}:${record.state || ''}:${record.county || ''}`;
|
||||
|
||||
// pre-process confirmed case data for later lookup
|
||||
tsCasesUS.forEach((usRecord) => {
|
||||
const newRecord = {
|
||||
...usRecord,
|
||||
needsRollup: false,
|
||||
Long: usRecord.Long_,
|
||||
'Province/State': usRecord.Province_State,
|
||||
'Country/Region': usRecord.Country_Region,
|
||||
}
|
||||
|
||||
delete newRecord.UID;
|
||||
delete newRecord.iso2;
|
||||
delete newRecord.iso3;
|
||||
delete newRecord.code3;
|
||||
delete newRecord.FIPS;
|
||||
delete newRecord.Combined_Key;
|
||||
delete newRecord.Long_;
|
||||
delete newRecord.Province_State;
|
||||
delete newRecord.Country_Region;
|
||||
|
||||
tsCasesGlobal.push(newRecord);
|
||||
});
|
||||
|
||||
const confirmedCasesLookup = tsCasesGlobal.reduce((lookup, record) => {
|
||||
normalizeRecord(record);
|
||||
const key = getRecordKey(record);
|
||||
if (lookup[key]) {
|
||||
throw new Error(`key "${key}" already exists in confirmed case lookup table`);
|
||||
}
|
||||
lookup[key] = record;
|
||||
return lookup;
|
||||
}, {});
|
||||
|
||||
// state/county data is separated for the US and doesn't need to be rolled up
|
||||
tsUSRecords.forEach((usRecord) => {
|
||||
const newRecord = {
|
||||
...usRecord,
|
||||
needsRollup: false,
|
||||
Long: usRecord.Long_,
|
||||
'Province/State': usRecord.Province_State,
|
||||
'Country/Region': usRecord.Country_Region,
|
||||
};
|
||||
|
||||
delete newRecord.UID;
|
||||
delete newRecord.iso2;
|
||||
delete newRecord.iso3;
|
||||
delete newRecord.code3;
|
||||
delete newRecord.FIPS;
|
||||
delete newRecord.Combined_Key;
|
||||
delete newRecord.Long_;
|
||||
delete newRecord.Province_State;
|
||||
delete newRecord.Country_Region;
|
||||
|
||||
tsGlobalRecords.push(newRecord);
|
||||
});
|
||||
|
||||
start = Date.now();
|
||||
tsGlobalRecords.forEach((record) => {
|
||||
normalizeRecord(record);
|
||||
|
||||
const recordKey = getRecordKey(record);
|
||||
const confirmedCases = confirmedCasesLookup[recordKey];
|
||||
if (!confirmedCases) {
|
||||
throw new Error(`no cases found in lookup for key "${recordKey}"`);
|
||||
}
|
||||
|
||||
record.cases = {
|
||||
timeSeriesDaily: confirmedCases.timeSeriesDaily,
|
||||
timeSeriesMonthly: confirmedCases.timeSeriesMonthly,
|
||||
rollingAverageDaily: confirmedCases.rollingAverageDaily,
|
||||
};
|
||||
|
||||
if (!record.population && !record.state && !record.county) {
|
||||
const mappedPop = countryPopulationMap[record.country];
|
||||
@ -376,6 +449,10 @@ const processGlobalDeaths = async () => {
|
||||
timeSeriesMonthly: {},
|
||||
states: [],
|
||||
safeName: record.countrySafeName,
|
||||
cases: {
|
||||
timeSeriesDaily: {},
|
||||
timeSeriesMonthly: {},
|
||||
},
|
||||
};
|
||||
|
||||
const item = perCountryTotals[record.country];
|
||||
@ -404,6 +481,10 @@ const processGlobalDeaths = async () => {
|
||||
deathsPerMillion: 0,
|
||||
timeSeriesDaily: {},
|
||||
timeSeriesMonthly: {},
|
||||
cases: {
|
||||
timeSeriesDaily: {},
|
||||
timeSeriesMonthly: {},
|
||||
},
|
||||
counties: [],
|
||||
};
|
||||
|
||||
@ -436,6 +517,24 @@ const processGlobalDeaths = async () => {
|
||||
stateItem.timeSeriesMonthly[ts.key].delta += ts.delta;
|
||||
});
|
||||
|
||||
record.cases.timeSeriesDaily.forEach((ts) => {
|
||||
stateItem.cases.timeSeriesDaily[ts.key] = stateItem.cases.timeSeriesDaily[ts.key] || {
|
||||
value: 0,
|
||||
delta: 0,
|
||||
};
|
||||
stateItem.cases.timeSeriesDaily[ts.key].value += ts.value;
|
||||
stateItem.cases.timeSeriesDaily[ts.key].delta += ts.delta;
|
||||
});
|
||||
|
||||
record.cases.timeSeriesMonthly.forEach((ts) => {
|
||||
stateItem.cases.timeSeriesMonthly[ts.key] = stateItem.cases.timeSeriesMonthly[ts.key] || {
|
||||
value: 0,
|
||||
delta: 0,
|
||||
};
|
||||
stateItem.cases.timeSeriesMonthly[ts.key].value += ts.value;
|
||||
stateItem.cases.timeSeriesMonthly[ts.key].delta += ts.delta;
|
||||
});
|
||||
|
||||
stateItem.counties.push(record);
|
||||
} else {
|
||||
item.states.push(record);
|
||||
@ -463,10 +562,31 @@ const processGlobalDeaths = async () => {
|
||||
item.timeSeriesMonthly[ts.key].value += ts.value;
|
||||
item.timeSeriesMonthly[ts.key].delta += ts.delta;
|
||||
});
|
||||
|
||||
record.cases.timeSeriesDaily.forEach((ts) => {
|
||||
item.cases.timeSeriesDaily[ts.key] = item.cases.timeSeriesDaily[ts.key] || {
|
||||
value: 0,
|
||||
delta: 0,
|
||||
};
|
||||
item.cases.timeSeriesDaily[ts.key].value += ts.value;
|
||||
item.cases.timeSeriesDaily[ts.key].delta += ts.delta;
|
||||
});
|
||||
|
||||
record.cases.timeSeriesMonthly.forEach((ts) => {
|
||||
item.cases.timeSeriesMonthly[ts.key] = item.cases.timeSeriesMonthly[ts.key] || {
|
||||
value: 0,
|
||||
delta: 0,
|
||||
};
|
||||
item.cases.timeSeriesMonthly[ts.key].value += ts.value;
|
||||
item.cases.timeSeriesMonthly[ts.key].delta += ts.delta;
|
||||
});
|
||||
});
|
||||
|
||||
Object.keys(perStateTotals).forEach((stateName) => {
|
||||
const item = perStateTotals[stateName];
|
||||
if (!item.cases) {
|
||||
throw new Error('no cases');
|
||||
}
|
||||
const stateItem = {
|
||||
name: stateName,
|
||||
safeName: item.safeName,
|
||||
@ -490,11 +610,28 @@ const processGlobalDeaths = async () => {
|
||||
delta: item.timeSeriesMonthly[date].delta,
|
||||
};
|
||||
}),
|
||||
cases: {
|
||||
timeSeriesDaily: Object.keys(item.cases.timeSeriesDaily).sort().map((date) => {
|
||||
return {
|
||||
key: date,
|
||||
value: item.cases.timeSeriesDaily[date].value,
|
||||
delta: item.cases.timeSeriesDaily[date].delta,
|
||||
};
|
||||
}),
|
||||
timeSeriesMonthly: Object.keys(item.cases.timeSeriesMonthly).sort().map((date) => {
|
||||
return {
|
||||
key: date,
|
||||
value: item.cases.timeSeriesMonthly[date].value,
|
||||
delta: item.cases.timeSeriesMonthly[date].delta,
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
stateItem.deathGrowthRate = getGrowthRate(stateItem);
|
||||
stateItem.rollingAverageDaily = getRollingAverage(stateItem);
|
||||
stateItem.doublingDaily = getDoublingTime(stateItem);
|
||||
stateItem.cases.rollingAverageDaily = getRollingAverage(stateItem.cases);
|
||||
|
||||
// insert into states array for the country
|
||||
perCountryTotals[item.country].states.push(stateItem);
|
||||
@ -509,6 +646,10 @@ const processGlobalDeaths = async () => {
|
||||
item.population = countryPopulationMap[countryName];
|
||||
}
|
||||
|
||||
if (!item.cases) {
|
||||
throw new Error('no cases for country');
|
||||
}
|
||||
|
||||
const countryItem = {
|
||||
name: countryName,
|
||||
safeName: item.safeName,
|
||||
@ -530,11 +671,28 @@ const processGlobalDeaths = async () => {
|
||||
delta: item.timeSeriesMonthly[date].delta,
|
||||
};
|
||||
}),
|
||||
cases: {
|
||||
timeSeriesDaily: Object.keys(item.cases.timeSeriesDaily).sort().map((date) => {
|
||||
return {
|
||||
key: date,
|
||||
value: item.cases.timeSeriesDaily[date].value,
|
||||
delta: item.cases.timeSeriesDaily[date].delta,
|
||||
};
|
||||
}),
|
||||
timeSeriesMonthly: Object.keys(item.cases.timeSeriesMonthly).sort().map((date) => {
|
||||
return {
|
||||
key: date,
|
||||
value: item.cases.timeSeriesMonthly[date].value,
|
||||
delta: item.cases.timeSeriesMonthly[date].delta,
|
||||
};
|
||||
}),
|
||||
},
|
||||
};
|
||||
|
||||
countryItem.deathGrowthRate = getGrowthRate(countryItem);
|
||||
countryItem.rollingAverageDaily = getRollingAverage(countryItem);
|
||||
countryItem.doublingDaily = getDoublingTime(countryItem);
|
||||
countryItem.cases.rollingAverageDaily = getRollingAverage(countryItem.cases);
|
||||
return countryItem;
|
||||
});
|
||||
|
||||
@ -545,6 +703,10 @@ const processGlobalDeaths = async () => {
|
||||
countries: countryArr,
|
||||
timeSeriesDaily: {},
|
||||
timeSeriesMonthly: {},
|
||||
cases: {
|
||||
timeSeriesDaily: {},
|
||||
timeSeriesMonthly: {},
|
||||
},
|
||||
};
|
||||
|
||||
countryArr.forEach((countryData) => {
|
||||
@ -567,6 +729,24 @@ const processGlobalDeaths = async () => {
|
||||
worldData.timeSeriesMonthly[ts.key].value += ts.value;
|
||||
worldData.timeSeriesMonthly[ts.key].delta += ts.delta;
|
||||
});
|
||||
|
||||
countryData.cases.timeSeriesDaily.forEach((ts) => {
|
||||
worldData.cases.timeSeriesDaily[ts.key] = worldData.cases.timeSeriesDaily[ts.key] || {
|
||||
value: 0,
|
||||
delta: 0,
|
||||
};
|
||||
worldData.cases.timeSeriesDaily[ts.key].value += ts.value;
|
||||
worldData.cases.timeSeriesDaily[ts.key].delta += ts.delta;
|
||||
});
|
||||
|
||||
countryData.cases.timeSeriesMonthly.forEach((ts) => {
|
||||
worldData.cases.timeSeriesMonthly[ts.key] = worldData.cases.timeSeriesMonthly[ts.key] || {
|
||||
value: 0,
|
||||
delta: 0,
|
||||
};
|
||||
worldData.cases.timeSeriesMonthly[ts.key].value += ts.value;
|
||||
worldData.cases.timeSeriesMonthly[ts.key].delta += ts.delta;
|
||||
});
|
||||
});
|
||||
|
||||
worldData.timeSeriesDaily = Object.keys(worldData.timeSeriesDaily).sort().map((date) => {
|
||||
@ -583,10 +763,25 @@ const processGlobalDeaths = async () => {
|
||||
delta: worldData.timeSeriesMonthly[date].delta,
|
||||
};
|
||||
});
|
||||
worldData.cases.timeSeriesDaily = Object.keys(worldData.cases.timeSeriesDaily).sort().map((date) => {
|
||||
return {
|
||||
key: date,
|
||||
value: worldData.cases.timeSeriesDaily[date].value,
|
||||
delta: worldData.cases.timeSeriesDaily[date].delta,
|
||||
};
|
||||
});
|
||||
worldData.cases.timeSeriesMonthly = Object.keys(worldData.cases.timeSeriesMonthly).sort().map((date) => {
|
||||
return {
|
||||
key: date,
|
||||
value: worldData.cases.timeSeriesMonthly[date].value,
|
||||
delta: worldData.cases.timeSeriesMonthly[date].delta,
|
||||
};
|
||||
});
|
||||
|
||||
worldData.deathGrowthRate = getGrowthRate(worldData);
|
||||
worldData.rollingAverageDaily = getRollingAverage(worldData);
|
||||
worldData.doublingDaily = getDoublingTime(worldData);
|
||||
worldData.cases.rollingAverageDaily = getRollingAverage(worldData.cases);
|
||||
|
||||
worldData.population = 7781841000;
|
||||
worldData.deathsPerMillion = worldData.total / worldData.population * 1000000;
|
||||
@ -604,7 +799,6 @@ const processGlobalDeaths = async () => {
|
||||
const targetFile = path.join(publicDir, 'index.html');
|
||||
fs.writeFileSync(targetFile, worldHtml);
|
||||
console.log(`wrote to ${targetFile} in ${Date.now() - start}ms`);
|
||||
// fs.writeFileSync(path.join(publicDir, 'countries.json'), JSON.stringify(countryArr, null, ' '));
|
||||
|
||||
const singleCountryTmpl = path.join(templatesDir, 'country.pug');
|
||||
const singleStateTmpl = path.join(templatesDir, 'state.pug');
|
||||
@ -645,7 +839,7 @@ const processGlobalDeaths = async () => {
|
||||
console.log(`finished in ${((Date.now() - globalStart) / 1000).toFixed(2)}s`);
|
||||
};
|
||||
|
||||
processGlobalDeaths()
|
||||
processRecords()
|
||||
.then(() => {
|
||||
console.log('all done');
|
||||
})
|
||||
|
142
tmpl/master.pug
142
tmpl/master.pug
@ -11,7 +11,28 @@ html
|
||||
vertical-align: middle !important;
|
||||
}
|
||||
th.sorted, td.sorted {
|
||||
background-color: #e0eefd;
|
||||
background-color: #e0eaf7;
|
||||
}
|
||||
.geo-bg-dark {
|
||||
background-color: #8e8e8e;
|
||||
color: white;
|
||||
}
|
||||
.cases-bg-dark {
|
||||
background-color: #aaa55e;
|
||||
color: white;
|
||||
}
|
||||
.deaths-bg-dark {
|
||||
background-color: #a65353;
|
||||
color: white;
|
||||
}
|
||||
.geo-bg {
|
||||
background-color: #eeeeee;
|
||||
}
|
||||
.cases-bg {
|
||||
background-color: #f9f6d5;
|
||||
}
|
||||
.deaths-bg {
|
||||
background-color: #eac8c8;
|
||||
}
|
||||
.table-sm {
|
||||
font-size: 80%;
|
||||
@ -170,9 +191,19 @@ html
|
||||
});
|
||||
}
|
||||
|
||||
function makeHeroChart(id, title, labels, totalDeaths, newDeaths, rollingAverage, doubling) {
|
||||
function makeHeroChart(
|
||||
id,
|
||||
title,
|
||||
labels,
|
||||
totalDeaths,
|
||||
newDeaths,
|
||||
rollingAverage,
|
||||
doubling,
|
||||
totalCases,
|
||||
newCases,
|
||||
) {
|
||||
const canvas = document.getElementById(id);
|
||||
charts.heroMaxValue = totalDeaths[totalDeaths.length - 1];
|
||||
charts.heroMaxValue = totalCases.reduce((max, value) => Math.max(max, value), 0);
|
||||
|
||||
const firstNonZeroDeathIndex = totalDeaths.findIndex(value => value > 0);
|
||||
const start = Math.max(0, firstNonZeroDeathIndex - 2);
|
||||
@ -182,6 +213,8 @@ html
|
||||
const newData = newDeaths.slice(start, end);
|
||||
const rollingData = rollingAverage.slice(start, end);
|
||||
const doublingData = doubling.slice(start, end);
|
||||
const totalCaseData = totalCases.slice(start, end);
|
||||
const newCaseData = newCases.slice(start, end);
|
||||
|
||||
const months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];
|
||||
|
||||
@ -195,40 +228,59 @@ html
|
||||
labels: realLabels,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Cumulative',
|
||||
data: totalData,
|
||||
fill: '1',
|
||||
borderColor: 'rgb(196, 64, 64)',
|
||||
label: 'Cases',
|
||||
data: totalCaseData,
|
||||
fill: '2',
|
||||
borderColor: 'rgb(161,150,20)',
|
||||
backgroundColor: 'rgba(161,150,20, 0.25)',
|
||||
borderWidth: 1,
|
||||
backgroundColor: 'rgba(196, 128, 128, 0.25)',
|
||||
},
|
||||
{
|
||||
label: 'New (rolling)',
|
||||
label: 'New Cases',
|
||||
data: newCaseData,
|
||||
fill: false,
|
||||
borderColor: 'rgba(206,108,46,0.5)',
|
||||
backgroundColor: 'rgba(206,108,46,0.5)',
|
||||
borderWidth: 1,
|
||||
pointRadius: 0,
|
||||
},
|
||||
{
|
||||
label: 'Deaths',
|
||||
data: totalData,
|
||||
fill: '3',
|
||||
borderColor: 'rgb(196, 64, 64)',
|
||||
backgroundColor: 'rgba(196, 64, 64, 0.25)',
|
||||
borderWidth: 1,
|
||||
},
|
||||
{
|
||||
label: 'New Deaths (rolling)',
|
||||
data: rollingData,
|
||||
fill: 'origin',
|
||||
borderColor: 'rgb(20,24,59)',
|
||||
borderWidth: 1,
|
||||
backgroundColor: 'rgba(96, 96, 164, 0.25)',
|
||||
borderWidth: 1,
|
||||
},
|
||||
{
|
||||
label: 'New',
|
||||
label: 'New Deaths',
|
||||
data: newData,
|
||||
fill: false,
|
||||
borderColor: 'rgb(96, 96, 96, 0.25)',
|
||||
backgroundColor: 'rgb(96, 96, 96, 0.25)',
|
||||
borderWidth: 1,
|
||||
},
|
||||
{
|
||||
label: 'Days to 2x',
|
||||
label: 'Days to 2x deaths',
|
||||
data: doublingData,
|
||||
fill: false,
|
||||
borderColor: 'rgb(187,40,193, 0.5)',
|
||||
backgroundColor: 'rgb(187,40,193, 0.5)',
|
||||
borderWidth: 2,
|
||||
pointRadius: 0,
|
||||
}
|
||||
],
|
||||
},
|
||||
options: {
|
||||
responsive: false,
|
||||
responsive: true,
|
||||
title: {
|
||||
display: true,
|
||||
position: 'top',
|
||||
@ -280,12 +332,13 @@ html
|
||||
mixin formatNumber(num)
|
||||
= Number(num).toLocaleString()
|
||||
|
||||
mixin sortableLinks(col, label)
|
||||
div.d-inline-flex
|
||||
span.sortables.mr-2.d-inline-flex.flex-column(style="font-size: 50%")
|
||||
mixin sortableLinks(col, notCentered)
|
||||
div.d-flex(class=(!notCentered ? "justify-content-center" : ""))
|
||||
div.sortables.mr-2(style="font-size: 50%")
|
||||
a(href="#sort:" + col + ":asc") ▲
|
||||
br
|
||||
a(href="#sort:" + col + ":desc") ▼
|
||||
span.d-inline-block.text-truncate
|
||||
div
|
||||
block
|
||||
|
||||
mixin heroChart()
|
||||
@ -304,13 +357,13 @@ html
|
||||
autocomplete="off"
|
||||
disabled
|
||||
) Logarithmic
|
||||
canvas.mx-auto(id="main-chart" width="800" height="450")
|
||||
canvas.mx-auto(id="main-chart" width="1024" height="576")
|
||||
-
|
||||
const growthRate = '+' + (data.deathGrowthRate * 100).toFixed(2) + '%';
|
||||
const population = 'pop. ' + data.population.toLocaleString();
|
||||
const deathsPerMillion = Math.round(data.deathsPerMillion).toLocaleString() + '/MM';
|
||||
const heroTitle = [
|
||||
'Covid-19 Deaths: ' + data.name,
|
||||
'Covid-19: ' + data.name,
|
||||
`${population} | ${deathsPerMillion} | ${growthRate}`
|
||||
];
|
||||
script.
|
||||
@ -322,6 +375,8 @@ html
|
||||
!{JSON.stringify(data.timeSeriesDaily.map(x => x.delta))},
|
||||
!{JSON.stringify(data.rollingAverageDaily.map(x => x.delta))},
|
||||
!{JSON.stringify(data.doublingDaily.map(x => x.value))},
|
||||
!{JSON.stringify(data.cases.timeSeriesDaily.map(x => x.value))},
|
||||
!{JSON.stringify(data.cases.timeSeriesDaily.map(x => x.delta))},
|
||||
);
|
||||
|
||||
mixin dataTable(items, label, type)
|
||||
@ -329,18 +384,27 @@ html
|
||||
|
||||
div#table.table-responsive: table.table.table-sm.table-hover
|
||||
thead: tr
|
||||
th #
|
||||
th(data-col="name"): +sortableLinks("name")= label
|
||||
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.deaths-bg-dark(colspan="100") Deaths
|
||||
thead.headers: tr
|
||||
th.geo-bg #
|
||||
th.geo-bg(data-col="name"): +sortableLinks("name", true)= label
|
||||
if hasPopulation
|
||||
th.text-center(data-col="population"): +sortableLinks("population") Population
|
||||
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="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
|
||||
th.geo-bg(data-col="population"): +sortableLinks("population") Population
|
||||
|
||||
th.cases-bg(data-col="cases-total"): +sortableLinks("cases-total") Total
|
||||
th.cases-bg(data-col="cases-today"): +sortableLinks("cases-today") Today
|
||||
|
||||
if hasPopulation
|
||||
th.deaths-bg(data-col="million"): +sortableLinks("million") per 1M
|
||||
th.deaths-bg(data-col="total"): +sortableLinks("total") Total
|
||||
th.sorted.deaths-bg(data-col="today"): +sortableLinks("today") Today
|
||||
th.deaths-bg(data-col="yesterday"): +sortableLinks("yesterday") Yesterday
|
||||
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.text-center.deaths-bg Trend
|
||||
|
||||
-
|
||||
items.sort((a, b) => {
|
||||
@ -357,13 +421,19 @@ html
|
||||
-
|
||||
const getValue = offset => (item.timeSeriesDaily[item.timeSeriesDaily.length - offset] || {}).value || 0;
|
||||
const getDelta = offset => (item.timeSeriesDaily[item.timeSeriesDaily.length - offset] || {}).delta || 0;
|
||||
const getValueCases = offset => (item.cases.timeSeriesDaily[item.cases.timeSeriesDaily.length - offset] || {}).value || 0;
|
||||
const getDeltaCases = offset => (item.cases.timeSeriesDaily[item.cases.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);
|
||||
const casesTotal = getValueCases(1);
|
||||
const casesToday = getDeltaCases(1);
|
||||
tr(
|
||||
id=("row-" + (item.safeName || '_'))
|
||||
data-name=(item.name || '_')
|
||||
data-cases-total=casesTotal
|
||||
data-cases-today=casesToday
|
||||
data-population=item.population
|
||||
data-total=item.total
|
||||
data-million=item.deathsPerMillion
|
||||
@ -377,6 +447,9 @@ html
|
||||
td: +renderItemName(item)
|
||||
if hasPopulation
|
||||
td.text-right: code: +formatNumber(item.population)
|
||||
td.text-right: code: +formatNumber(casesTotal)
|
||||
td.text-right: code: +formatNumber(casesToday)
|
||||
if hasPopulation
|
||||
td.text-right: code: +formatNumber(Math.round(item.deathsPerMillion))
|
||||
td.text-right: code: +formatNumber(item.total)
|
||||
td.text-right.sorted: code: +formatNumber(today)
|
||||
@ -385,7 +458,7 @@ html
|
||||
td.text-right: code: +formatNumber(last30)
|
||||
td.text-right: code= Number(item.deathGrowthRate * 100).toFixed(2) + '%'
|
||||
td
|
||||
canvas.mx-auto(id="sparkline-" + i width="200" height="50")
|
||||
canvas.mx-auto(id="sparkline-" + i width="100" height="35")
|
||||
script.
|
||||
makeSparkline(
|
||||
"sparkline-#{i}",
|
||||
@ -393,8 +466,9 @@ html
|
||||
);
|
||||
|
||||
|
||||
|
||||
div.container.mt-2
|
||||
h1.text-center Covid-19 Death Data
|
||||
h1.text-center Covid-19 Data
|
||||
div.d-flex.justify-content-around.font-italic.small
|
||||
div
|
||||
- const generationDate = new Date().toISOString();
|
||||
@ -413,7 +487,7 @@ html
|
||||
script.
|
||||
(function() {
|
||||
const table = document.getElementById('table');
|
||||
const headerRow = table.querySelector('thead tr');
|
||||
const headerRow = table.querySelector('thead.headers tr');
|
||||
const headers = [].slice.call(headerRow.querySelectorAll('th'));
|
||||
const tbody = table.querySelector('tbody');
|
||||
const allRows = [].slice.call(tbody.querySelectorAll('tbody tr'));
|
||||
@ -485,6 +559,8 @@ html
|
||||
resortTable('name');
|
||||
break;
|
||||
case 'total':
|
||||
case 'cases-total':
|
||||
case 'cases-today':
|
||||
case 'today':
|
||||
case 'yesterday':
|
||||
case 'last7':
|
||||
|
Loading…
Reference in New Issue
Block a user