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'); }());