covid-19/sim.js

245 lines
5.2 KiB
JavaScript
Raw Normal View History

2020-03-31 19:02:59 +00:00
window.$ = (query,el=document)=>{
return el.querySelector(query);
};
window.$all = (query,el=document)=>{
return [...el.querySelectorAll(query)];
};
let canvas = $('#graphCanvas');
let context = canvas.getContext('2d');
canvas.width = 1000;
canvas.height = 1000;
canvas.style.width = (canvas.width/2)+"px";
canvas.style.height = (canvas.height/2)+"px";
let r_0 = $('#r_0');
let slider_susceptible = $('#slider_susceptible');
let r_susceptible = $('#r_susceptible');
let slider_distancing = $('#slider_distancing');
let r_distancing = $('#r_distancing');
let slider_cases = $('#slider_cases');
let r_cases = $('#r_cases');
let slider_contacts = $('#slider_contacts');
let r_contacts = $('#r_contacts');
let slider_vax = $('#slider_vax');
let r_vax = $('#r_vax');
let r_end = $('#r_end');
let r_now = $('#r_now');
let N = 100;
let S,I,R;
let I_FLOOR = 0.013;
//let R0 = [0.8,0.9,0.1,0.2];
let R0 = [0.8*1.25, 0.9*1.25, 0.1*1.25, 0.2*1.25];
let Rt = R0.slice();
let update = (delta)=>{
let R_total = Rt.reduce(add);
let newlyInfected = R_total * I * delta;
S -= newlyInfected;
I += newlyInfected;
let newlyRecovered = 1 * I * delta;
I -= newlyRecovered;
R += newlyRecovered;
if(I<I_FLOOR) I=I_FLOOR; // Floor
//////////////////////////////////////////
// DOMs //////////////////////////////////
//////////////////////////////////////////
// R0
updateBar(r_0, R0);
Rt = R0;
// Susceptible
let change = S/N;
Rt = Rt.map(r => r * change);
slider_susceptible.value = change;
updateBar(r_susceptible, Rt);
// Distancing
change = 1 - parseFloat(slider_distancing.value);
Rt = Rt.map(r => r * change);
updateBar(r_distancing, Rt);
// Cases
change = 1 - parseFloat(slider_cases.value);
Rt[0] = Rt[0] * change;
updateBar(r_cases, Rt);
// Contacts
change = 1 - parseFloat(slider_contacts.value);
Rt[1] = Rt[1] * change;
Rt[2] = Rt[2] * change;
updateBar(r_contacts, Rt);
// Vax
change = 1 - parseFloat(slider_vax.value);
Rt = Rt.map(r => r * change);
updateBar(r_vax, Rt);
// R, current growth
updateBar(r_end, Rt);
// What's the current R?
R_total = Rt.reduce(add);
r_now.innerHTML = R_total.toFixed(2) + "<br>" + ((R_total>1) ? "uncontained" : "contained");
};
let BAR_WIDTH_R = 2.5;
let updateBar = (bar, Rt)=>{
[...bar.children].forEach((sub_bar,i)=>{
let r = Rt[i];
sub_bar.style.width = (r/BAR_WIDTH_R)*150 + "px";
});
};
$('#containment_line').style.left = 150 + (150*(1/BAR_WIDTH_R)) + 5 + "px";
const add = (a,b)=>a+b;
//let x = 0;
let SIM_TIME = 0;
let X_SPEED = 0.5;
let UPDATE_PER_PIXEL = 0.025;
const INTERVENTION_COLORS = [
[slider_distancing, "#71c0ff", 0.5],
[slider_cases, "#70ff70", 0.25],
[slider_contacts, "#b8ff70", 0.25],
[slider_vax, "#ffe770", 0.5],
];
let PHASE = 0;
let draw = ()=>{
// Redraw
requestAnimationFrame(draw);
// Caps
[slider_distancing, slider_cases, slider_contacts, slider_vax].forEach((slider)=>{
let cap = parseFloat(slider.getAttribute("CAP"));
if(parseFloat(slider.value) > cap){
slider.value = cap;
}
});
// SIM TIME: Phase unlock or stop
if(SIM_TIME>canvas.width) return;
if(SIM_TIME>canvas.width * 1/8 && PHASE<1){
PHASE = 1;
$all('.phase_i').forEach((control)=>{
control.removeAttribute('locked');
});
}
if(SIM_TIME>canvas.width * 2/8 && PHASE<2){
PHASE = 2;
$all('.phase_ii').forEach((control)=>{
control.removeAttribute('locked');
});
}
if(SIM_TIME>canvas.width * 7/8 && PHASE<3){
PHASE = 3;
$all('.phase_iii').forEach((control)=>{
control.removeAttribute('locked');
});
}
// Update!
update(UPDATE_PER_PIXEL*X_SPEED);
////////////////////////////////////////
// DRAWWWWWWW //////////////////////////
////////////////////////////////////////
let ctx = context;
let y=0, h;
// S
h = (S/N)*canvas.height;
ctx.fillStyle = "#eeeeee";
ctx.fillRect(0,y,1,h);
// R
y += h;
h = (R/N)*canvas.height;
ctx.fillStyle = "#cccccc";
ctx.fillRect(0,y,1,h);
// I
y += h;
h = (I/N)*canvas.height;
ctx.fillStyle = "#ff4040";
ctx.fillRect(0,y,1,h);
// ICU bed capacity
y = (1-0.02)*canvas.height;
h = 2;
ctx.fillStyle = "#000000";
ctx.fillRect(0,y,1,h);
// Interventions
INTERVENTION_COLORS.forEach((ic)=>{
let [slider,color,alpha] = ic;
ctx.globalAlpha = parseFloat(slider.value) * alpha;
ctx.fillStyle = color;
ctx.fillRect(0,0,1,canvas.height);
ctx.globalAlpha = 1;
});
// Next!
ctx.translate(X_SPEED,0);
SIM_TIME += X_SPEED;
};
requestAnimationFrame(draw);
// Caps
[slider_distancing, slider_cases, slider_contacts, slider_vax].forEach((slider)=>{
let cap = parseFloat(slider.getAttribute("CAP"));
let capDOM = document.createElement('div');
capDOM.className = 'cap';
capDOM.style.left = (cap*133)+"px";
capDOM.style.width = ((1-cap)*133)+"px";
slider.parentNode.appendChild(capDOM);
});
///////////////
// RESTART ////
///////////////
const restart = ()=>{
let ctx = context;
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.fillStyle = "#ffffff";
ctx.fillRect(0,0,canvas.width,canvas.height);
SIM_TIME = 0;
PHASE = 0;
$all('.phase').forEach((control)=>{
control.setAttribute('locked','yup');
});
S = N;
I = I_FLOOR;
R = 0;
Rt = R0.slice();
slider_susceptible.value = 1;
slider_distancing.value = 0;
slider_cases.value = 0;
slider_contacts.value = 0;
slider_vax.value = 0;
};
$('#restart').onclick = restart;
restart();