275 lines
6.8 KiB
Plaintext
275 lines
6.8 KiB
Plaintext
doctype
|
|
|
|
html
|
|
head
|
|
title= $title + ' | Covid-19'
|
|
meta(charset="utf8")
|
|
link(rel="stylesheet" href="/bootstrap.css")
|
|
script(src="/Chart.bundle.js")
|
|
style.
|
|
table td {
|
|
vertical-align: middle !important;
|
|
}
|
|
th.sorted, td.sorted {
|
|
background-color: #e0eefd;
|
|
}
|
|
.table-sm {
|
|
font-size: 80%;
|
|
}
|
|
|
|
script.
|
|
function makeSparkline(id, data) {
|
|
const canvas = document.getElementById(id);
|
|
const chart = new Chart(canvas.getContext('2d'), {
|
|
type: 'line',
|
|
data: {
|
|
labels: [1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 13, 14],
|
|
datasets: [{ data: data }],
|
|
},
|
|
options: {
|
|
responsive: false,
|
|
legend: {
|
|
display: false,
|
|
},
|
|
elements: {
|
|
line: {
|
|
borderColor: '#000000',
|
|
borderWidth: 1,
|
|
},
|
|
point: {
|
|
radius: 0,
|
|
},
|
|
},
|
|
tooltips: {
|
|
enabled: false,
|
|
},
|
|
scales: {
|
|
yAxes: [
|
|
{
|
|
display: false,
|
|
ticks: {
|
|
precision: 0,
|
|
beginAtZero: true,
|
|
}
|
|
},
|
|
],
|
|
xAxes: [
|
|
{
|
|
display: false,
|
|
},
|
|
],
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function makeHeroChart(id, title, labels, totalDeaths, newDeaths) {
|
|
const canvas = document.getElementById(id);
|
|
const chart = new Chart(canvas.getContext('2d'), {
|
|
type: 'line',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [
|
|
{
|
|
label: 'Total Deaths',
|
|
data: totalDeaths,
|
|
fill: true,
|
|
borderColor: 'rgb(196, 64, 64)',
|
|
borderWidth: 1,
|
|
backgroundColor: 'rgba(196, 128, 128, 0.25)',
|
|
},
|
|
{
|
|
label: 'New Deaths',
|
|
data: newDeaths,
|
|
fill: true,
|
|
borderColor: 'rgb(64, 64, 64)',
|
|
borderWidth: 1,
|
|
backgroundColor: 'rgba(128, 128, 128, 0.75)',
|
|
}
|
|
],
|
|
},
|
|
options: {
|
|
responsive: false,
|
|
title: {
|
|
display: true,
|
|
position: 'top',
|
|
text: title,
|
|
fontSize: 18,
|
|
},
|
|
tooltips: {
|
|
intersect: false,
|
|
position: 'nearest',
|
|
axis: 'x',
|
|
},
|
|
scales: {
|
|
yAxes: [
|
|
{
|
|
display: true,
|
|
ticks: {
|
|
precision: 0,
|
|
beginAtZero: true,
|
|
}
|
|
},
|
|
],
|
|
xAxes: [
|
|
{
|
|
display: true,
|
|
},
|
|
],
|
|
}
|
|
}
|
|
});
|
|
}
|
|
body
|
|
|
|
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%")
|
|
a(href="#sort:" + col + ":asc") ▲
|
|
a(href="#sort:" + col + ":desc") ▼
|
|
span.d-inline-block.text-truncate
|
|
block
|
|
|
|
mixin heroChart(title)
|
|
div.card.mb-4
|
|
div.card-body
|
|
canvas.mx-auto(id="main-chart" width="800" height="450")
|
|
script.
|
|
makeHeroChart(
|
|
'main-chart',
|
|
!{JSON.stringify(data.name + ' (' + (data.deathGrowthRate < 0 ? '' : '+') + (data.deathGrowthRate * 100).toFixed(2) + '%)')},
|
|
!{JSON.stringify(data.timeSeriesDaily.map(x => x.key))},
|
|
!{JSON.stringify(data.timeSeriesDaily.map(x => x.value))},
|
|
!{JSON.stringify(data.timeSeriesDaily.map(x => x.delta))},
|
|
);
|
|
|
|
div.container.mt-2
|
|
h1.text-center Covid-19 Death Data
|
|
div.d-flex.justify-content-around.font-italic.small
|
|
div
|
|
- const generationDate = new Date().toISOString();
|
|
| Data from #[a(href="https://github.com/CSSEGISandData/COVID-19") Johns Hopkins CSSE]
|
|
div
|
|
| Generated: #[time.generation-date(datetime=generationDate title=generationDate)= generationDate]
|
|
div
|
|
- const lastUpdateISO = lastUpdate.toISOString();
|
|
| Data updated: #[time.update-date(datetime=lastUpdateISO title=lastUpdateISO)= lastUpdateISO]
|
|
|
|
hr
|
|
|
|
div.main-content.mt-4
|
|
block main
|
|
|
|
script.
|
|
(function() {
|
|
const table = document.getElementById('table');
|
|
const headerRow = table.querySelector('thead tr');
|
|
const headers = [].slice.call(headerRow.querySelectorAll('th'));
|
|
const tbody = table.querySelector('tbody');
|
|
const allRows = [].slice.call(tbody.querySelectorAll('tbody tr'));
|
|
|
|
const resortTable = (col) => {
|
|
let nextChild = null;
|
|
const highlightedIndex = headers.findIndex(cell => cell.getAttribute('data-col') === col);
|
|
console.log(col, highlightedIndex);
|
|
headers.forEach((cell, i) => {
|
|
if (i !== highlightedIndex) {
|
|
cell.classList.remove('sorted');
|
|
} else {
|
|
cell.classList.add('sorted');
|
|
}
|
|
});
|
|
|
|
for (let i = allRows.length - 1; i >= 0; i--) {
|
|
const row = allRows[i];
|
|
if (!row) {
|
|
continue;
|
|
}
|
|
const cells = [].slice.call(row.querySelectorAll('td'));
|
|
cells.forEach((cell, i) => {
|
|
if (i !== highlightedIndex) {
|
|
cell.classList.remove('sorted');
|
|
} else {
|
|
cell.classList.add('sorted');
|
|
}
|
|
});
|
|
if (row === nextChild) {
|
|
continue;
|
|
}
|
|
|
|
tbody.insertBefore(row, nextChild);
|
|
row.querySelector('.sort-order').textContent = (i + 1).toString();
|
|
nextChild = row;
|
|
}
|
|
};
|
|
|
|
const handleSort = (value, dir) => {
|
|
const newSortDir = dir === 'desc' ? 'desc' : 'asc';
|
|
const sortByNumberThenName = (attr) => {
|
|
allRows.sort((a, b) => {
|
|
const aValue = Number(a.getAttribute('data-' + attr));
|
|
const bValue = Number(b.getAttribute('data-' + attr));
|
|
if (aValue === bValue) {
|
|
const aName = a.getAttribute('data-name');
|
|
const bName = b.getAttribute('data-name');
|
|
return aName && bName ? aName.localeCompare(bName) : 0;
|
|
}
|
|
|
|
return aValue < bValue ?
|
|
(newSortDir === 'asc' ? -1 : 1) :
|
|
(newSortDir === 'asc' ? 1 : -1);
|
|
});
|
|
resortTable(value);
|
|
};
|
|
|
|
switch (value) {
|
|
case 'name':
|
|
allRows.sort((a, b) => {
|
|
const aName = a.getAttribute('data-name');
|
|
const bName = b.getAttribute('data-name');
|
|
if (!aName || !bName) {
|
|
return -1;
|
|
}
|
|
if (newSortDir === 'asc') {
|
|
return aName.localeCompare(bName);
|
|
}
|
|
|
|
return bName.localeCompare(aName);
|
|
});
|
|
resortTable('name');
|
|
break;
|
|
case 'total':
|
|
case 'yesterday':
|
|
case 'week':
|
|
case 'month':
|
|
case 'population':
|
|
case 'million':
|
|
case 'growth':
|
|
sortByNumberThenName(value);
|
|
break;
|
|
}
|
|
};
|
|
|
|
const handleHash = (hash) => {
|
|
const sortValue = hash.replace(/^#sort:/, '').split(':');
|
|
handleSort(sortValue[0], sortValue[1]);
|
|
};
|
|
|
|
window.addEventListener('hashchange', () => {
|
|
handleHash(window.location.hash);
|
|
});
|
|
|
|
handleHash(window.location.hash);
|
|
|
|
const setDate = (selector) => {
|
|
const node = document.querySelector(selector);
|
|
node.textContent = new Date(node.getAttribute('datetime')).toLocaleString();
|
|
};
|
|
|
|
setDate('.generation-date');
|
|
setDate('.update-date');
|
|
}());
|