separated case/death graphs, removed trend graph
This commit is contained in:
parent
6f762b0524
commit
8566acc2a6
@ -124,6 +124,10 @@ const processRecords = async () => {
|
||||
});
|
||||
console.log(`parsed US states population CSV in ${Date.now() - start}ms`);
|
||||
|
||||
// let tsCasesUS = [];
|
||||
// let populationUSStateRecords = [];
|
||||
// let tsUSRecords = [];
|
||||
|
||||
start = Date.now();
|
||||
let populationCountriesRecords = parseCsv(populationCountriesRaw, {
|
||||
cast: true,
|
||||
@ -176,7 +180,7 @@ const processRecords = async () => {
|
||||
}
|
||||
|
||||
// tsGlobalRecords = tsGlobalRecords.filter((record) => {
|
||||
// return record['Country/Region'] === 'US';
|
||||
// return record['Country/Region'] === 'Germany';
|
||||
// });
|
||||
|
||||
const getRollingAverage = (item) => {
|
||||
|
330
tmpl/master.pug
330
tmpl/master.pug
@ -29,13 +29,13 @@ html
|
||||
bottom: -1px;
|
||||
border-bottom: 2px solid #b5b5b5;
|
||||
}
|
||||
#hero-tooltip table {
|
||||
.hero-tooltip table {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
#hero-tooltip th, #hero-tooltip td {
|
||||
.hero-tooltip th, .hero-tooltip td {
|
||||
padding: 2px 4px;
|
||||
}
|
||||
#hero-tooltip {
|
||||
.hero-tooltip {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
@ -135,83 +135,84 @@ html
|
||||
|
||||
const charts = {
|
||||
logarithmic: false,
|
||||
heroChart: null,
|
||||
heroCharts: [],
|
||||
trends: [],
|
||||
heroMaxValues: [],
|
||||
};
|
||||
|
||||
function makeSparkline(id, deathData, caseData) {
|
||||
const canvas = document.getElementById(id);
|
||||
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 chart = new Chart(canvas.getContext('2d'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: new Array(deathData.length),
|
||||
datasets: [
|
||||
{
|
||||
data: caseData,
|
||||
borderColor: 'rgb(161,150,20)',
|
||||
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: {
|
||||
responsive: false,
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
elements: {
|
||||
point: {
|
||||
radius: 0,
|
||||
},
|
||||
},
|
||||
tooltips: {
|
||||
enabled: false,
|
||||
},
|
||||
scales: {
|
||||
yAxes: [
|
||||
{
|
||||
display: false,
|
||||
type: 'logarithmic',
|
||||
ticks: {
|
||||
precision: 0,
|
||||
beginAtZero: true,
|
||||
min: 0,
|
||||
max: Math.max(max, 2), // this is necessary for some reason
|
||||
callback: value => Number(value.toString()),
|
||||
}
|
||||
},
|
||||
],
|
||||
xAxes: [
|
||||
{
|
||||
display: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
}
|
||||
});
|
||||
// const canvas = document.getElementById(id);
|
||||
// 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 chart = new Chart(canvas.getContext('2d'), {
|
||||
// type: 'line',
|
||||
// data: {
|
||||
// labels: new Array(deathData.length),
|
||||
// datasets: [
|
||||
// {
|
||||
// data: caseData,
|
||||
// borderColor: 'rgb(161,150,20)',
|
||||
// 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: {
|
||||
// responsive: false,
|
||||
// legend: {
|
||||
// display: false,
|
||||
// },
|
||||
// elements: {
|
||||
// point: {
|
||||
// radius: 0,
|
||||
// },
|
||||
// },
|
||||
// tooltips: {
|
||||
// enabled: false,
|
||||
// },
|
||||
// scales: {
|
||||
// yAxes: [
|
||||
// {
|
||||
// display: false,
|
||||
// type: 'logarithmic',
|
||||
// ticks: {
|
||||
// precision: 0,
|
||||
// beginAtZero: true,
|
||||
// min: 0,
|
||||
// max: Math.max(max, 2), // this is necessary for some reason
|
||||
// callback: value => Number(value.toString()),
|
||||
// }
|
||||
// },
|
||||
// ],
|
||||
// xAxes: [
|
||||
// {
|
||||
// display: false,
|
||||
// },
|
||||
// ],
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
charts.trends.push({
|
||||
chart,
|
||||
maxValue,
|
||||
});
|
||||
// charts.trends.push({
|
||||
// chart,
|
||||
// maxValue,
|
||||
// });
|
||||
}
|
||||
|
||||
function setAxisType(type) {
|
||||
if (charts.heroChart) {
|
||||
const axis = charts.heroChart.options.scales.yAxes[0];
|
||||
charts.heroCharts.forEach((chart, i) => {
|
||||
const axis = chart.options.scales.yAxes[0];
|
||||
if (type === 'logarithmic') {
|
||||
axis.type = 'logarithmic';
|
||||
const maxLogPower = Math.ceil(Math.log10(charts.heroMaxValue));
|
||||
const maxLogPower = Math.ceil(Math.log10(charts.heroMaxValues[i]));
|
||||
axis.ticks.max = Math.pow(10, maxLogPower);
|
||||
axis.ticks.callback = value => Number(value.toString()).toLocaleString();
|
||||
} else {
|
||||
@ -220,7 +221,7 @@ html
|
||||
axis.ticks.callback = value => value;
|
||||
}
|
||||
|
||||
charts.heroChart.update();
|
||||
chart.update();
|
||||
|
||||
const selector = '.set-axis-' + type;
|
||||
const otherSelector = type === 'linear' ?
|
||||
@ -229,7 +230,7 @@ html
|
||||
|
||||
document.querySelector(selector).disabled = true;
|
||||
document.querySelector(otherSelector).disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
charts.trends.forEach((data) => {
|
||||
const axis = data.chart.options.scales.yAxes[0];
|
||||
@ -250,26 +251,24 @@ html
|
||||
|
||||
function makeHeroChart(
|
||||
id,
|
||||
type,
|
||||
title,
|
||||
labels,
|
||||
totalDeaths,
|
||||
newDeaths,
|
||||
cumulative,
|
||||
daily,
|
||||
rollingAverage,
|
||||
totalCases,
|
||||
newCases,
|
||||
) {
|
||||
const canvas = document.getElementById(id);
|
||||
charts.heroMaxValue = totalCases.reduce((max, value) => Math.max(max, value), 0);
|
||||
const maxValue = cumulative.reduce((max, value) => Math.max(max, value), 0);
|
||||
charts.heroMaxValues.push(maxValue);
|
||||
|
||||
const firstNonZeroDeathIndex = totalDeaths.findIndex(value => value > 0);
|
||||
const firstNonZeroDeathIndex = cumulative.findIndex(value => value > 0);
|
||||
const start = Math.max(0, firstNonZeroDeathIndex - 2);
|
||||
const end = totalDeaths.length;
|
||||
const end = cumulative.length;
|
||||
|
||||
const totalData = totalDeaths.slice(start, end);
|
||||
const newData = newDeaths.slice(start, end);
|
||||
const totalData = cumulative.slice(start, end);
|
||||
const newData = daily.slice(start, end);
|
||||
const rollingData = rollingAverage.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' ];
|
||||
|
||||
@ -277,52 +276,45 @@ html
|
||||
.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'), {
|
||||
const pointRadius = 1;
|
||||
|
||||
const totalColor = type === 'cases' ? [220, 126, 26 ] : [194, 57, 57];
|
||||
const dailyColor = type === 'cases' ? [83, 83, 83 ] : [83, 83, 83 ];
|
||||
const rollingColor = type === 'cases' ? [187, 178, 16] : [208, 105, 167];
|
||||
|
||||
charts.heroCharts.push(new Chart(canvas.getContext('2d'), {
|
||||
type: 'lineVerticalTooltip',
|
||||
data: {
|
||||
labels: realLabels,
|
||||
datasets: [
|
||||
{
|
||||
label: 'Cases',
|
||||
data: totalCaseData,
|
||||
fill: '2',
|
||||
borderColor: 'rgb(161,150,20)',
|
||||
backgroundColor: 'rgba(161,150,20, 0.25)',
|
||||
borderWidth: 1,
|
||||
},
|
||||
{
|
||||
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',
|
||||
label: 'Cumulative',
|
||||
data: totalData,
|
||||
fill: '3',
|
||||
borderColor: 'rgb(196, 64, 64)',
|
||||
backgroundColor: 'rgba(196, 64, 64, 0.25)',
|
||||
fill: '2',
|
||||
borderColor: 'rgba(' + totalColor.join(',') + ')',
|
||||
backgroundColor: 'rgba(' + totalColor.join(',') + ',0.25)',
|
||||
borderWidth: 1,
|
||||
pointRadius,
|
||||
},
|
||||
{
|
||||
label: 'New Deaths (rolling)',
|
||||
data: rollingData,
|
||||
fill: 'origin',
|
||||
borderColor: 'rgb(20,24,59)',
|
||||
backgroundColor: 'rgba(96, 96, 164, 0.25)',
|
||||
borderWidth: 1,
|
||||
},
|
||||
{
|
||||
label: 'New Deaths',
|
||||
label: 'New',
|
||||
data: newData,
|
||||
fill: false,
|
||||
borderColor: 'rgb(20,24,59, 0.15)',
|
||||
backgroundColor: 'rgb(20,24,59, 0.15)',
|
||||
borderColor: 'rgba(' + dailyColor.join(',') + ',0.15)',
|
||||
backgroundColor: 'rgba(' + dailyColor.join(',') + ',0.15)',
|
||||
borderWidth: 1,
|
||||
pointRadius,
|
||||
},
|
||||
{
|
||||
label: 'New (7 day avg.)',
|
||||
data: rollingData,
|
||||
fill: 'origin',
|
||||
borderColor: 'rgba(' + rollingColor.join(',') + ')',
|
||||
backgroundColor: 'rgba(' + rollingColor.join(',') + ',0.25)',
|
||||
borderWidth: 1,
|
||||
pointRadius,
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
options: {
|
||||
@ -338,7 +330,7 @@ html
|
||||
axis: 'x',
|
||||
enabled: false,
|
||||
custom: function(tooltipModel) {
|
||||
const tooltipEl = document.getElementById('hero-tooltip');
|
||||
const tooltipEl = document.getElementById('hero-tooltip-' + type);
|
||||
|
||||
if (tooltipModel.opacity === 0) {
|
||||
tooltipEl.style.opacity = '0';
|
||||
@ -367,10 +359,8 @@ html
|
||||
}
|
||||
};
|
||||
|
||||
setData('cases-total', 0);
|
||||
setData('cases-new', 1);
|
||||
setData('deaths-total', 2);
|
||||
setData('deaths-new', 4, 3);
|
||||
setData(type + '-total', 0);
|
||||
setData(type + '-new', 1, 2);
|
||||
}
|
||||
|
||||
const position = this._chart.canvas.getBoundingClientRect();
|
||||
@ -397,7 +387,7 @@ html
|
||||
precision: 0,
|
||||
beginAtZero: true,
|
||||
min: 0,
|
||||
max: Math.pow(10, Math.ceil(Math.log10(charts.heroMaxValue))),
|
||||
max: Math.pow(10, Math.ceil(Math.log10(maxValue))),
|
||||
callback: value => Number(value.toString()).toLocaleString(),
|
||||
},
|
||||
afterBuildTicks: (axis, ticks) => {
|
||||
@ -419,7 +409,7 @@ html
|
||||
],
|
||||
}
|
||||
}
|
||||
});
|
||||
}));
|
||||
}
|
||||
body
|
||||
mixin formatNumber(num)
|
||||
@ -437,21 +427,11 @@ html
|
||||
mixin heroChart()
|
||||
div.card.mb-4
|
||||
div.card-body.position-relative
|
||||
div.position-absolute(style="top: 10px; right: 10px; z-index: 2;")
|
||||
div.btn-group.btn-group-sm
|
||||
button.btn.btn-secondary.set-axis-linear(
|
||||
type="button"
|
||||
onclick="setAxisType('linear')"
|
||||
autocomplete="off"
|
||||
) Linear
|
||||
button.btn.btn-secondary.set-axis-logarithmic(
|
||||
type="button"
|
||||
onclick="setAxisType('logarithmic')"
|
||||
autocomplete="off"
|
||||
disabled
|
||||
) Logarithmic
|
||||
div.mx-auto.position-relative(style="max-width: 1024px; z-index: 1")
|
||||
canvas.mx-auto(id="main-chart" width="1024" height="576")
|
||||
div.row
|
||||
div.col-12.col-lg-6.position-relative
|
||||
canvas.mx-auto(id="main-chart-cases" width="512" height="288")
|
||||
div.col-12.col-lg-6.position-relative
|
||||
canvas.mx-auto(id="main-chart-deaths" width="512" height="288")
|
||||
-
|
||||
const population = 'pop. ' + data.population.toLocaleString();
|
||||
const deathsPerMillion = Math.round(data.deathsPerMillion).toLocaleString() + '/1M';
|
||||
@ -465,21 +445,32 @@ html
|
||||
if (data.country) {
|
||||
name += ', ' + data.country;
|
||||
}
|
||||
const heroTitle = [
|
||||
const heroCasesTitle = [
|
||||
`Covid-19: ${name} (${population})`,
|
||||
`${totalCases} cases (${casesPerMillion})`,
|
||||
];
|
||||
const heroDeathsTitle = [
|
||||
`Covid-19: ${name} (${population})`,
|
||||
`${totalDeaths} deaths (${deathsPerMillion})`,
|
||||
];
|
||||
script.
|
||||
makeHeroChart(
|
||||
'main-chart',
|
||||
!{JSON.stringify(heroTitle)},
|
||||
'main-chart-cases',
|
||||
'cases',
|
||||
!{JSON.stringify(heroCasesTitle)},
|
||||
!{JSON.stringify(data.cases.timeSeriesDaily.map(x => x.key))},
|
||||
!{JSON.stringify(data.cases.timeSeriesDaily.map(x => x.value))},
|
||||
!{JSON.stringify(data.cases.timeSeriesDaily.map(x => x.delta))},
|
||||
!{JSON.stringify(data.cases.rollingAverageDaily.map(x => x.delta))},
|
||||
);
|
||||
makeHeroChart(
|
||||
'main-chart-deaths',
|
||||
'deaths',
|
||||
!{JSON.stringify(heroDeathsTitle)},
|
||||
!{JSON.stringify(data.timeSeriesDaily.map(x => x.key))},
|
||||
!{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.cases.timeSeriesDaily.map(x => x.value))},
|
||||
!{JSON.stringify(data.cases.timeSeriesDaily.map(x => x.delta))},
|
||||
);
|
||||
|
||||
mixin dataTable(items, label, type)
|
||||
@ -519,17 +510,21 @@ html
|
||||
|
||||
th.other-bg(data-col="cfr"): +sortableLinks("cfr")
|
||||
acronym(title="Case Fatality Rate") CFR
|
||||
th.text-center.other-bg Trend
|
||||
//th.text-center.other-bg Trend
|
||||
|
||||
-
|
||||
items.sort((a, b) => {
|
||||
const yesterdayA = a.timeSeriesDaily[a.timeSeriesDaily.length - 1].delta;
|
||||
const yesterdayB = b.timeSeriesDaily[b.timeSeriesDaily.length - 1].delta;
|
||||
if (yesterdayA === yesterdayB) {
|
||||
return a.name && b.name ? a.name.localeCompare(b.name) : 0;
|
||||
}
|
||||
try {
|
||||
const yesterdayA = a.timeSeriesDaily[a.timeSeriesDaily.length - 1].delta;
|
||||
const yesterdayB = b.timeSeriesDaily[b.timeSeriesDaily.length - 1].delta;
|
||||
if (yesterdayA === yesterdayB) {
|
||||
return a.name && b.name ? a.name.localeCompare(b.name) : 0;
|
||||
}
|
||||
|
||||
return yesterdayA < yesterdayB ? 1 : -1;
|
||||
return yesterdayA < yesterdayB ? 1 : -1;
|
||||
} catch (e) {
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
|
||||
tbody: each item, i in items
|
||||
@ -592,14 +587,14 @@ html
|
||||
if hasPopulation
|
||||
td.text-right: code: +formatNumber(Math.round(last14Avg))
|
||||
td.text-right: code= Number(item.caseFatalityRate * 100).toFixed(2) + '%'
|
||||
td
|
||||
canvas.mx-auto(id="sparkline-" + i width="100" height="35")
|
||||
script.
|
||||
makeSparkline(
|
||||
"sparkline-#{i}",
|
||||
#{JSON.stringify(item.rollingAverageDaily.slice(-14).map(x => x.delta))},
|
||||
#{JSON.stringify(item.cases.rollingAverageDaily.slice(-14).map(x => x.delta))},
|
||||
);
|
||||
//td
|
||||
// canvas.mx-auto(id="sparkline-" + i width="100" height="35")
|
||||
// script.
|
||||
// makeSparkline(
|
||||
// "sparkline-#{i}",
|
||||
// #{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")
|
||||
h1.text-center Covid-19 Data
|
||||
@ -616,9 +611,22 @@ html
|
||||
hr
|
||||
|
||||
div.main-content.mt-4.position-relative
|
||||
div.position-absolute(style="z-index: 2; left: 50%; transform: translateX(-50%) translateY(12%);")
|
||||
div.btn-group.btn-group-sm
|
||||
button.btn.btn-secondary.set-axis-linear(
|
||||
type="button"
|
||||
onclick="setAxisType('linear')"
|
||||
autocomplete="off"
|
||||
) Linear
|
||||
button.btn.btn-secondary.set-axis-logarithmic(
|
||||
type="button"
|
||||
onclick="setAxisType('logarithmic')"
|
||||
autocomplete="off"
|
||||
disabled
|
||||
) Logarithmic
|
||||
block main
|
||||
|
||||
div#hero-tooltip
|
||||
div#hero-tooltip-cases.hero-tooltip
|
||||
div.text-center(style="font-size: 125%"): strong.tooltip-title
|
||||
table.tooltip-bordered
|
||||
tr
|
||||
@ -629,6 +637,10 @@ html
|
||||
td: span.tooltip-color-cases-new
|
||||
th New Cases
|
||||
td.tooltip-value-cases-new
|
||||
|
||||
div#hero-tooltip-deaths.hero-tooltip
|
||||
div.text-center(style="font-size: 125%"): strong.tooltip-title
|
||||
table.tooltip-bordered
|
||||
tr
|
||||
td: span.tooltip-color-deaths-total
|
||||
th Total Deaths
|
||||
|
Loading…
Reference in New Issue
Block a user