-
Notifications
You must be signed in to change notification settings - Fork 2
/
vac.html
282 lines (238 loc) · 8.53 KB
/
vac.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
<html>
<head>
<title>Calculate when countries are done vaccinating.</title>
<meta charset=utf-8 />
<meta http-equiv=“Pragma” content=”no-cache”>
<meta http-equiv=“Expires” content=”-1″>
<meta http-equiv=“CACHE-CONTROL” content=”NO-CACHE”>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="keywords" content="covid,vaccination,estimation">
<meta name="author" content="Willem Hengeveld, itsme@xs4all.nl">
<meta name="description" content="Calculate when the various countries would be done with their COVID vaccination program.">
<style>
#controlsdiv {
position:fixed;
right:0px;
top:100px;
width:120px;
z-index:5;
}
#tablediv {
position:absolute;
top:140px;
left:0px;
right:140px;
}
</style>
<script src="countries.js" ></script>
<script src="jsutils.js" ></script>
<script language=javascript><!--
var g_vacpp = 1.7;
var g_days = 7;
const VACURL = "https://raw.githubusercontent.com/owid/covid-19-data/master/public/data/vaccinations/vaccinations.csv";
function makegraph(values)
{
// create a graph for the list of (t, y) pairs.
var td = el('td');
td.style.textAlign = 'right';
var canvas = el('canvas');
td.append(canvas);
var ymax = Math.max(...values.map(pair=>pair[1]));
var ymin = Math.min(...values.map(pair=>pair[1]));
var tmin = values[0][0].getTime();
var tmax = values[values.length-1][0].getTime();
var tnow = new Date().getTime();
values = values.map( ([t,y]) => [t, (y-ymin)/(ymax-ymin)] );
canvas.width = (tnow-tmin)/(86400*1000)*5;
canvas.height = 40;
var cvy = (y) => (canvas.height-5-y*(canvas.height-10));
var cvx = (t) => ((t.getTime()-tmin)/(86400*1000)*5);
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(cvx(values[0][0]), cvy(values[0][1]));
for (var x=1 ; x<values.length ; x++) {
ctx.lineTo(cvx(values[x][0]), cvy(values[x][1]));
}
ctx.stroke();
return td;
}
function calcdifferences(table)
{
// calculate the difference between entries in the table.
var res = [];
var prev;
for (var ent of table)
{
if (prev && ent[1]-prev[1]) {
// note: ignoring entries which show 0 progress.
res.push([
ent[0], // the time
(ent[1]-prev[1])/(ent[0]-prev[0])*86400*1000, // the value
]);
prev = ent;
}
else if (!prev) {
prev = ent;
}
}
return res;
}
function makerow(name, data, ci)
{
// create a row in the table
var tr = el('tr');
tr.append(el('td', name));
var inwoners = ci ? ci.population : 0;
tr.append(makenumtd(inwoners));
var vdata = Object.entries(data).filter(([k,v])=> v.total_vaccinations>0).map(([k,v])=>[new Date(k), Number(v.total_vaccinations)]);
var mostrecent = vdata[vdata.length-1];
var weekago = vdata[0];
for (var i=vdata.length-2 ; i>=0 ; i--)
if (mostrecent[0]-vdata[i][0] >= g_days*86400*1000) {
weekago = vdata[i];
break;
}
if (!weekago)
return;
tr.append(makedatetd(weekago[0]));
tr.append(makenumtd(weekago[1]));
tr.append(makedatetd(mostrecent[0]));
tr.append(makenumtd(mostrecent[1]));
tr.append(makenumtd(mostrecent[1]/(g_vacpp*inwoners)*100, 1));
if ((mostrecent[0]-weekago[0])==0)
return;
var vacrate = (mostrecent[1]-weekago[1]) / (mostrecent[0]-weekago[0]);
var vactodo = g_vacpp*inwoners-mostrecent[1];
var timetodo = vactodo/vacrate;
var timefinished = new Date(mostrecent[0].getTime()+timetodo);
tr.append(makenumtd(vacrate*86400*1000, 0));
tr.append(makenumtd(vacrate*86400*1000/(inwoners/100000), 0));
tr.append(makedatetd(timefinished));
var diff = calcdifferences(vdata);
tr.append(makegraph(diff));
return tr;
}
function createheader(th)
{
// create the table header row
var headers = [ "Country", "Population", "a week before", "nr vac", "most recent", "nr vac", "pct vac", "vac/day", "vac/day<br>/100k", "estimated date done", "shape" ];
var tr = th.insertRow(0);
for (var h of headers) {
var c = el('th');
c.innerHTML = h;
tr.appendChild(c);
}
}
function makesliders()
{
// create the sliders for the 'controlsdiv'
return [
makeslider("vacpp:", "vacpp", 1, 30, 17, t => g_vacpp = Number(t)/10, () => g_vacpp.toFixed(1)),
makeslider("days:", "days", 1, 30, 7, t => g_days = Number(t), () => g_days.toFixed(0)),
];
}
function bycountrybydate(table)
{
// reorganize the source data by country and date
var dict = {};
for (rec of table)
{
if (!dict[rec.location])
dict[rec.location] = {};
dict[rec.location][rec.date] = rec;
}
return dict;
}
function start()
{
// This function is called from body.onLoad, starts the fetch,
// and creates the controls and tables.
fetch(VACURL).then(r => r.text()).then( r => { g_table = bycountrybydate(decodecsv(r)); update(); });
var div = document.getElementById("controlsdiv");
div.append(...makesliders());
var div = document.getElementById("tablediv");
var tab = el('table');
tab.id = "table";
createheader(tab.createTHead());
div.appendChild(tab);
add_sorter(tab);
}
function update()
{
// populate the main table with data from g_table.
var tab = document.getElementById('table');
tab.cellSpacing = 0;
tab.cellPadding = 5;
tab.border = true;
var oldtbody = tab.tBodies[0];
if (oldtbody)
tab.removeChild(oldtbody);
var tbody = tab.createTBody();
Object.entries(g_table).forEach( ([name, data]) => { var row = makerow(name, data, cinfo[name]); if (row) tbody.append(row); } );
if (tab.currentsortcolumn)
sortbycolumn(tab, tab.currentsortcolumn);
var infor = document.getElementById("infodiv").getBoundingClientRect();
var tabler = document.getElementById("tablediv").getBoundingClientRect();
if (infor.bottom > tabler.top) {
var tdiv = document.getElementById("tablediv")
tdiv.style.top = 20;
var adiv = document.getElementById("authordiv")
adiv.style.position = "absolute";
adiv.style.top = 0;
var idiv = document.getElementById("infodiv");
idiv.style.position = "absolute";
idiv.style.top = 30;
idiv.style.zIndex = 0;
idiv.style.opacity= 0.1;
}
}
function sortbycolumn(table, th)
{
// sort each section by the specified column.
// the 'th' object has extra properties: asc and colnr
const getCellValue = (tr, idx) => tr.children[idx].innerText || tr.children[idx].textContent;
const comparer = (idx, asc) => (a, b) => ((v1, v2) =>
v1 !== '' && v2 !== '' && !isNaN(v1) && !isNaN(v2) ? v1 - v2 : v1.toString().localeCompare(v2)
)(getCellValue(asc ? a : b, idx), getCellValue(asc ? b : a, idx));
var oldtbody = table.tBodies[0];
var newtbody = table.createTBody();
Array.from(oldtbody.children)
.sort(comparer(th.colnr, th.asc))
.forEach(tr => newtbody.append(tr) );
if (oldtbody);
table.removeChild(oldtbody);
table.currentsortcolumn = th;
}
function add_sorter(table)
{
// add column sorters to the specified table.
table.currentsortcolumn = null;
table.querySelectorAll('th').forEach(th => {
th.colnr = Array.from(th.parentNode.children).indexOf(th);
th.asc = true;
th.addEventListener('click', (() => { th.asc = !th.asc; sortbycolumn(table, th); } ))
});
}
--></script>
</head>
<body onLoad="start()">
<div id="infodiv">
Data from <a href="https://github.com/owid/covid-19-data/blob/master/public/data/vaccinations/">OurWorldInData</a>.
More tables:
<a href="owid.html">country info</a>,
<a href="rivm.html">Dutch municipalities</a>,
<a href="vac.html">Worldwide vaccinations</a>,
<a href="usvac.html">US vaccinations</a>.<br>
<br>
This table shows the estimated date when each country's vaccination program will have completed.
Use the 'vacpp' slider to select how many vaccinations are required per person.
Use the 'days' slider to select how many days to average for the calculation.
The graph shows how many vaccinations are delivered per day. Click table headers to sort.<br><p>
</div>
<div id="authordiv">
Created by Willem Hengeveld <<a href="mailto:itsme@xs4all.nl">itsme@xs4all.nl</a>>, <a href="https://itsme.home.xs4all.nl/">homepage</a>, <a href="https://github.com/nlitsme/covidcalculations">source code</a>.
</div>
<div id="controlsdiv"></div>
<div id="tablediv"></div>
</body>
</html>