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 zeroPad = value => value < 10 ? `0${value}` : value.toString();
|
||||||
const toSafeName = x => x.replace(/[^A-Za-z]/g, '-').toLowerCase();
|
const toSafeName = x => x.replace(/[^A-Za-z]/g, '-').toLowerCase();
|
||||||
|
|
||||||
const processGlobalDeaths = async () => {
|
const processRecords = async () => {
|
||||||
const globalStart = Date.now();
|
const globalStart = Date.now();
|
||||||
let start = Date.now();
|
let start = Date.now();
|
||||||
|
|
||||||
@ -81,6 +81,14 @@ const processGlobalDeaths = async () => {
|
|||||||
const populationCountriesRaw = fs.readFileSync(populationCountriesCsv, {encoding: 'utf8'});
|
const populationCountriesRaw = fs.readFileSync(populationCountriesCsv, {encoding: 'utf8'});
|
||||||
console.log(`read countries population CSV in ${Date.now() - start}ms`);
|
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();
|
start = Date.now();
|
||||||
let tsGlobalRecords = parseCsv(timeSeriesGlobalRaw, {
|
let tsGlobalRecords = parseCsv(timeSeriesGlobalRaw, {
|
||||||
cast: true,
|
cast: true,
|
||||||
@ -95,6 +103,20 @@ const processGlobalDeaths = async () => {
|
|||||||
});
|
});
|
||||||
console.log(`parsed US deaths CSV in ${Date.now() - start}ms`);
|
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();
|
start = Date.now();
|
||||||
let populationUSStateRecords = parseCsv(populationUSRaw, {
|
let populationUSStateRecords = parseCsv(populationUSRaw, {
|
||||||
cast: true,
|
cast: true,
|
||||||
@ -217,31 +239,7 @@ const processGlobalDeaths = async () => {
|
|||||||
return calcGrowthRate(record.timeSeriesDaily, record.timeSeriesDaily.length - 1, 7);
|
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
|
const normalizeRecord = (record) => {
|
||||||
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) => {
|
|
||||||
record.timeSeriesDaily = [];
|
record.timeSeriesDaily = [];
|
||||||
record.timeSeriesMonthly = [];
|
record.timeSeriesMonthly = [];
|
||||||
const dateColumns = Object.keys(record).filter(x => /^\d+\/\d+\/\d+$/.test(x))
|
const dateColumns = Object.keys(record).filter(x => /^\d+\/\d+\/\d+$/.test(x))
|
||||||
@ -335,6 +333,81 @@ const processGlobalDeaths = async () => {
|
|||||||
delete record.Long;
|
delete record.Long;
|
||||||
delete record.Admin2;
|
delete record.Admin2;
|
||||||
delete record.Population;
|
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) {
|
if (!record.population && !record.state && !record.county) {
|
||||||
const mappedPop = countryPopulationMap[record.country];
|
const mappedPop = countryPopulationMap[record.country];
|
||||||
@ -376,6 +449,10 @@ const processGlobalDeaths = async () => {
|
|||||||
timeSeriesMonthly: {},
|
timeSeriesMonthly: {},
|
||||||
states: [],
|
states: [],
|
||||||
safeName: record.countrySafeName,
|
safeName: record.countrySafeName,
|
||||||
|
cases: {
|
||||||
|
timeSeriesDaily: {},
|
||||||
|
timeSeriesMonthly: {},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const item = perCountryTotals[record.country];
|
const item = perCountryTotals[record.country];
|
||||||
@ -404,6 +481,10 @@ const processGlobalDeaths = async () => {
|
|||||||
deathsPerMillion: 0,
|
deathsPerMillion: 0,
|
||||||
timeSeriesDaily: {},
|
timeSeriesDaily: {},
|
||||||
timeSeriesMonthly: {},
|
timeSeriesMonthly: {},
|
||||||
|
cases: {
|
||||||
|
timeSeriesDaily: {},
|
||||||
|
timeSeriesMonthly: {},
|
||||||
|
},
|
||||||
counties: [],
|
counties: [],
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -436,6 +517,24 @@ const processGlobalDeaths = async () => {
|
|||||||
stateItem.timeSeriesMonthly[ts.key].delta += ts.delta;
|
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);
|
stateItem.counties.push(record);
|
||||||
} else {
|
} else {
|
||||||
item.states.push(record);
|
item.states.push(record);
|
||||||
@ -463,10 +562,31 @@ const processGlobalDeaths = async () => {
|
|||||||
item.timeSeriesMonthly[ts.key].value += ts.value;
|
item.timeSeriesMonthly[ts.key].value += ts.value;
|
||||||
item.timeSeriesMonthly[ts.key].delta += ts.delta;
|
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) => {
|
Object.keys(perStateTotals).forEach((stateName) => {
|
||||||
const item = perStateTotals[stateName];
|
const item = perStateTotals[stateName];
|
||||||
|
if (!item.cases) {
|
||||||
|
throw new Error('no cases');
|
||||||
|
}
|
||||||
const stateItem = {
|
const stateItem = {
|
||||||
name: stateName,
|
name: stateName,
|
||||||
safeName: item.safeName,
|
safeName: item.safeName,
|
||||||
@ -490,11 +610,28 @@ const processGlobalDeaths = async () => {
|
|||||||
delta: item.timeSeriesMonthly[date].delta,
|
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.deathGrowthRate = getGrowthRate(stateItem);
|
||||||
stateItem.rollingAverageDaily = getRollingAverage(stateItem);
|
stateItem.rollingAverageDaily = getRollingAverage(stateItem);
|
||||||
stateItem.doublingDaily = getDoublingTime(stateItem);
|
stateItem.doublingDaily = getDoublingTime(stateItem);
|
||||||
|
stateItem.cases.rollingAverageDaily = getRollingAverage(stateItem.cases);
|
||||||
|
|
||||||
// insert into states array for the country
|
// insert into states array for the country
|
||||||
perCountryTotals[item.country].states.push(stateItem);
|
perCountryTotals[item.country].states.push(stateItem);
|
||||||
@ -509,6 +646,10 @@ const processGlobalDeaths = async () => {
|
|||||||
item.population = countryPopulationMap[countryName];
|
item.population = countryPopulationMap[countryName];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!item.cases) {
|
||||||
|
throw new Error('no cases for country');
|
||||||
|
}
|
||||||
|
|
||||||
const countryItem = {
|
const countryItem = {
|
||||||
name: countryName,
|
name: countryName,
|
||||||
safeName: item.safeName,
|
safeName: item.safeName,
|
||||||
@ -530,11 +671,28 @@ const processGlobalDeaths = async () => {
|
|||||||
delta: item.timeSeriesMonthly[date].delta,
|
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.deathGrowthRate = getGrowthRate(countryItem);
|
||||||
countryItem.rollingAverageDaily = getRollingAverage(countryItem);
|
countryItem.rollingAverageDaily = getRollingAverage(countryItem);
|
||||||
countryItem.doublingDaily = getDoublingTime(countryItem);
|
countryItem.doublingDaily = getDoublingTime(countryItem);
|
||||||
|
countryItem.cases.rollingAverageDaily = getRollingAverage(countryItem.cases);
|
||||||
return countryItem;
|
return countryItem;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -545,6 +703,10 @@ const processGlobalDeaths = async () => {
|
|||||||
countries: countryArr,
|
countries: countryArr,
|
||||||
timeSeriesDaily: {},
|
timeSeriesDaily: {},
|
||||||
timeSeriesMonthly: {},
|
timeSeriesMonthly: {},
|
||||||
|
cases: {
|
||||||
|
timeSeriesDaily: {},
|
||||||
|
timeSeriesMonthly: {},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
countryArr.forEach((countryData) => {
|
countryArr.forEach((countryData) => {
|
||||||
@ -567,6 +729,24 @@ const processGlobalDeaths = async () => {
|
|||||||
worldData.timeSeriesMonthly[ts.key].value += ts.value;
|
worldData.timeSeriesMonthly[ts.key].value += ts.value;
|
||||||
worldData.timeSeriesMonthly[ts.key].delta += ts.delta;
|
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) => {
|
worldData.timeSeriesDaily = Object.keys(worldData.timeSeriesDaily).sort().map((date) => {
|
||||||
@ -583,10 +763,25 @@ const processGlobalDeaths = async () => {
|
|||||||
delta: worldData.timeSeriesMonthly[date].delta,
|
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.deathGrowthRate = getGrowthRate(worldData);
|
||||||
worldData.rollingAverageDaily = getRollingAverage(worldData);
|
worldData.rollingAverageDaily = getRollingAverage(worldData);
|
||||||
worldData.doublingDaily = getDoublingTime(worldData);
|
worldData.doublingDaily = getDoublingTime(worldData);
|
||||||
|
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;
|
||||||
@ -604,7 +799,6 @@ const processGlobalDeaths = async () => {
|
|||||||
const targetFile = path.join(publicDir, 'index.html');
|
const targetFile = path.join(publicDir, 'index.html');
|
||||||
fs.writeFileSync(targetFile, worldHtml);
|
fs.writeFileSync(targetFile, worldHtml);
|
||||||
console.log(`wrote to ${targetFile} in ${Date.now() - start}ms`);
|
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 singleCountryTmpl = path.join(templatesDir, 'country.pug');
|
||||||
const singleStateTmpl = path.join(templatesDir, 'state.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`);
|
console.log(`finished in ${((Date.now() - globalStart) / 1000).toFixed(2)}s`);
|
||||||
};
|
};
|
||||||
|
|
||||||
processGlobalDeaths()
|
processRecords()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log('all done');
|
console.log('all done');
|
||||||
})
|
})
|
||||||
|
142
tmpl/master.pug
142
tmpl/master.pug
@ -11,7 +11,28 @@ html
|
|||||||
vertical-align: middle !important;
|
vertical-align: middle !important;
|
||||||
}
|
}
|
||||||
th.sorted, td.sorted {
|
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 {
|
.table-sm {
|
||||||
font-size: 80%;
|
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);
|
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 firstNonZeroDeathIndex = totalDeaths.findIndex(value => value > 0);
|
||||||
const start = Math.max(0, firstNonZeroDeathIndex - 2);
|
const start = Math.max(0, firstNonZeroDeathIndex - 2);
|
||||||
@ -182,6 +213,8 @@ html
|
|||||||
const newData = newDeaths.slice(start, end);
|
const newData = newDeaths.slice(start, end);
|
||||||
const rollingData = rollingAverage.slice(start, end);
|
const rollingData = rollingAverage.slice(start, end);
|
||||||
const doublingData = doubling.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' ];
|
const months = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ];
|
||||||
|
|
||||||
@ -195,40 +228,59 @@ html
|
|||||||
labels: realLabels,
|
labels: realLabels,
|
||||||
datasets: [
|
datasets: [
|
||||||
{
|
{
|
||||||
label: 'Cumulative',
|
label: 'Cases',
|
||||||
data: totalData,
|
data: totalCaseData,
|
||||||
fill: '1',
|
fill: '2',
|
||||||
borderColor: 'rgb(196, 64, 64)',
|
borderColor: 'rgb(161,150,20)',
|
||||||
|
backgroundColor: 'rgba(161,150,20, 0.25)',
|
||||||
borderWidth: 1,
|
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,
|
data: rollingData,
|
||||||
fill: 'origin',
|
fill: 'origin',
|
||||||
borderColor: 'rgb(20,24,59)',
|
borderColor: 'rgb(20,24,59)',
|
||||||
borderWidth: 1,
|
|
||||||
backgroundColor: 'rgba(96, 96, 164, 0.25)',
|
backgroundColor: 'rgba(96, 96, 164, 0.25)',
|
||||||
|
borderWidth: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'New',
|
label: 'New Deaths',
|
||||||
data: newData,
|
data: newData,
|
||||||
fill: false,
|
fill: false,
|
||||||
borderColor: 'rgb(96, 96, 96, 0.25)',
|
borderColor: 'rgb(96, 96, 96, 0.25)',
|
||||||
|
backgroundColor: 'rgb(96, 96, 96, 0.25)',
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Days to 2x',
|
label: 'Days to 2x deaths',
|
||||||
data: doublingData,
|
data: doublingData,
|
||||||
fill: false,
|
fill: false,
|
||||||
borderColor: 'rgb(187,40,193, 0.5)',
|
borderColor: 'rgb(187,40,193, 0.5)',
|
||||||
|
backgroundColor: 'rgb(187,40,193, 0.5)',
|
||||||
borderWidth: 2,
|
borderWidth: 2,
|
||||||
pointRadius: 0,
|
pointRadius: 0,
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
responsive: false,
|
responsive: true,
|
||||||
title: {
|
title: {
|
||||||
display: true,
|
display: true,
|
||||||
position: 'top',
|
position: 'top',
|
||||||
@ -280,12 +332,13 @@ html
|
|||||||
mixin formatNumber(num)
|
mixin formatNumber(num)
|
||||||
= Number(num).toLocaleString()
|
= Number(num).toLocaleString()
|
||||||
|
|
||||||
mixin sortableLinks(col, label)
|
mixin sortableLinks(col, notCentered)
|
||||||
div.d-inline-flex
|
div.d-flex(class=(!notCentered ? "justify-content-center" : ""))
|
||||||
span.sortables.mr-2.d-inline-flex.flex-column(style="font-size: 50%")
|
div.sortables.mr-2(style="font-size: 50%")
|
||||||
a(href="#sort:" + col + ":asc") ▲
|
a(href="#sort:" + col + ":asc") ▲
|
||||||
|
br
|
||||||
a(href="#sort:" + col + ":desc") ▼
|
a(href="#sort:" + col + ":desc") ▼
|
||||||
span.d-inline-block.text-truncate
|
div
|
||||||
block
|
block
|
||||||
|
|
||||||
mixin heroChart()
|
mixin heroChart()
|
||||||
@ -304,13 +357,13 @@ html
|
|||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
disabled
|
disabled
|
||||||
) Logarithmic
|
) 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 growthRate = '+' + (data.deathGrowthRate * 100).toFixed(2) + '%';
|
||||||
const population = 'pop. ' + data.population.toLocaleString();
|
const population = 'pop. ' + data.population.toLocaleString();
|
||||||
const deathsPerMillion = Math.round(data.deathsPerMillion).toLocaleString() + '/MM';
|
const deathsPerMillion = Math.round(data.deathsPerMillion).toLocaleString() + '/MM';
|
||||||
const heroTitle = [
|
const heroTitle = [
|
||||||
'Covid-19 Deaths: ' + data.name,
|
'Covid-19: ' + data.name,
|
||||||
`${population} | ${deathsPerMillion} | ${growthRate}`
|
`${population} | ${deathsPerMillion} | ${growthRate}`
|
||||||
];
|
];
|
||||||
script.
|
script.
|
||||||
@ -322,6 +375,8 @@ html
|
|||||||
!{JSON.stringify(data.timeSeriesDaily.map(x => x.delta))},
|
!{JSON.stringify(data.timeSeriesDaily.map(x => x.delta))},
|
||||||
!{JSON.stringify(data.rollingAverageDaily.map(x => x.delta))},
|
!{JSON.stringify(data.rollingAverageDaily.map(x => x.delta))},
|
||||||
!{JSON.stringify(data.doublingDaily.map(x => x.value))},
|
!{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)
|
mixin dataTable(items, label, type)
|
||||||
@ -329,18 +384,27 @@ html
|
|||||||
|
|
||||||
div#table.table-responsive: table.table.table-sm.table-hover
|
div#table.table-responsive: table.table.table-sm.table-hover
|
||||||
thead: tr
|
thead: tr
|
||||||
th #
|
th.text-center.font-weight-bold.geo-bg-dark(colspan=(hasPopulation ? 3 : 2)) Geography
|
||||||
th(data-col="name"): +sortableLinks("name")= label
|
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
|
if hasPopulation
|
||||||
th.text-center(data-col="population"): +sortableLinks("population") Population
|
th.geo-bg(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.cases-bg(data-col="cases-total"): +sortableLinks("cases-total") Total
|
||||||
th.text-center.sorted(data-col="today"): +sortableLinks("today") Today
|
th.cases-bg(data-col="cases-today"): +sortableLinks("cases-today") Today
|
||||||
th.text-center(data-col="yesterday"): +sortableLinks("yesterday") Yesterday
|
|
||||||
th.text-center(data-col="last7"): +sortableLinks("last7") Last 7
|
if hasPopulation
|
||||||
th.text-center(data-col="last30"): +sortableLinks("last30") Last 30
|
th.deaths-bg(data-col="million"): +sortableLinks("million") per 1M
|
||||||
th.text-center(data-col="growth"): +sortableLinks("growth") Growth
|
th.deaths-bg(data-col="total"): +sortableLinks("total") Total
|
||||||
th.text-center Trend
|
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) => {
|
items.sort((a, b) => {
|
||||||
@ -357,13 +421,19 @@ html
|
|||||||
-
|
-
|
||||||
const getValue = offset => (item.timeSeriesDaily[item.timeSeriesDaily.length - offset] || {}).value || 0;
|
const getValue = offset => (item.timeSeriesDaily[item.timeSeriesDaily.length - offset] || {}).value || 0;
|
||||||
const getDelta = offset => (item.timeSeriesDaily[item.timeSeriesDaily.length - offset] || {}).delta || 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 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 last30 = getValue(1) - getValue(30);
|
||||||
|
const casesTotal = getValueCases(1);
|
||||||
|
const casesToday = 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-today=casesToday
|
||||||
data-population=item.population
|
data-population=item.population
|
||||||
data-total=item.total
|
data-total=item.total
|
||||||
data-million=item.deathsPerMillion
|
data-million=item.deathsPerMillion
|
||||||
@ -377,6 +447,9 @@ html
|
|||||||
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)
|
||||||
|
td.text-right: code: +formatNumber(casesToday)
|
||||||
|
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)
|
||||||
@ -385,7 +458,7 @@ html
|
|||||||
td.text-right: code: +formatNumber(last30)
|
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="200" height="50")
|
canvas.mx-auto(id="sparkline-" + i width="100" height="35")
|
||||||
script.
|
script.
|
||||||
makeSparkline(
|
makeSparkline(
|
||||||
"sparkline-#{i}",
|
"sparkline-#{i}",
|
||||||
@ -393,8 +466,9 @@ html
|
|||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
div.container.mt-2
|
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.d-flex.justify-content-around.font-italic.small
|
||||||
div
|
div
|
||||||
- const generationDate = new Date().toISOString();
|
- const generationDate = new Date().toISOString();
|
||||||
@ -413,7 +487,7 @@ html
|
|||||||
script.
|
script.
|
||||||
(function() {
|
(function() {
|
||||||
const table = document.getElementById('table');
|
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 headers = [].slice.call(headerRow.querySelectorAll('th'));
|
||||||
const tbody = table.querySelector('tbody');
|
const tbody = table.querySelector('tbody');
|
||||||
const allRows = [].slice.call(tbody.querySelectorAll('tbody tr'));
|
const allRows = [].slice.call(tbody.querySelectorAll('tbody tr'));
|
||||||
@ -485,6 +559,8 @@ html
|
|||||||
resortTable('name');
|
resortTable('name');
|
||||||
break;
|
break;
|
||||||
case 'total':
|
case 'total':
|
||||||
|
case 'cases-total':
|
||||||
|
case 'cases-today':
|
||||||
case 'today':
|
case 'today':
|
||||||
case 'yesterday':
|
case 'yesterday':
|
||||||
case 'last7':
|
case 'last7':
|
||||||
|
Loading…
Reference in New Issue
Block a user