separated case/death graphs, removed trend graph

This commit is contained in:
tmont 2020-11-15 16:39:38 -08:00
parent 6f762b0524
commit 8566acc2a6
2 changed files with 176 additions and 160 deletions

View File

@ -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) => {

View File

@ -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