commit 368bd06733da23630dc08bf0c66c4e412dcbfa79 Author: Nicky Case Date: Tue Mar 31 15:02:59 2020 -0400 barebones sim done diff --git a/sim.css b/sim.css new file mode 100644 index 0000000..6c352a5 --- /dev/null +++ b/sim.css @@ -0,0 +1,115 @@ +body{ + font-family: Helvetica, Arial, sans-serif; +} + + +#controls{ + width:300px; + /*height:500px;*/ + float:left; + position: relative; +} +#controls > div{ + overflow: hidden; + margin-bottom: 1em; + position: relative; + + border-left: 5px solid #ffffff; + padding-left: 10px; +} +#controls > div > div{ + width: 150px; + float: left; + position: relative; +} +#controls > div > div.bar{ + height:10px; + background: #eeeeee; + position: absolute; + bottom:0; left:150px; + overflow: hidden; +} +#controls > div > div.bar > div{ + height: 100%; + float: left; +} +#controls > div > div.bar > div:nth-child(1){ + background: #ff4040; +} +#controls > div > div.bar > div:nth-child(2){ + background: #cc4040; +} +#controls > div > div.bar > div:nth-child(3){ + background: #994040; +} +#controls > div > div.bar > div:nth-child(4){ + background: #664040; +} +#controls > div > #r_end{ + height: 30px; +} +#controls > #containment_line{ + display: block; + width:1px; + height: 100%; + background: #000; + position: absolute; + top: 0; +} +#controls .cap{ + background: #fff; + opacity: 0.8; + position: absolute; + bottom: 0; + right: 0; + width: 50px; + height: 20px; +} + +#controls > div[locked]{ + opacity: 0.2; + pointer-events: none; +} + + +#graph{ + float:left; + margin-left: 1em; + width: 500px; +} +#graphCanvas{ + width:500px; + height:500px; + border: 1px solid #eeeeee; +} +#graph > #months{ + color: #bbb; + font-size: 10px; + width: 562.5px; + height: 50px; + text-align: right; + position: relative; + left: -70.5px; + top: 2px; +} +#graph > #months > div{ + color: #bbb; + font-size: 10px; + width: 62.5px; + float: left; + transform-origin: top right; + transform: rotate(-30deg); +} +#graph > #month_ticks{ + width: 562.5px; + height: 5px; + position: relative; + left: -61.5px; + top: -50px; +} +#graph > #month_ticks > div{ + width: 61.5px; + height: 100%; + border-right: 1px solid #bbb; + float: left; +} \ No newline at end of file diff --git a/sim.js b/sim.js new file mode 100644 index 0000000..e5e1096 --- /dev/null +++ b/sim.js @@ -0,0 +1,244 @@ +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 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) + "
" + ((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(); + diff --git a/wip.html b/wip.html new file mode 100644 index 0000000..980fc4a --- /dev/null +++ b/wip.html @@ -0,0 +1,112 @@ + + +
+ +
+
+ Growth in the beginning: +
+ R0 = 2.50 +
+
+
+
+
+ +
+
+ Ratio of folks still Susceptible: + +
+
+
+
+
+ +
+
+ Social Distancing: + +
+
+
+
+
+ +
+
+ Cases isolated: + +
+
+
+
+
+ +
+
+ Contacts quarantined: + +
+
+
+
+
+ +
+
+ Folks vaccinated: + +
+
+
+
+
+ +
+
+ Growth right now: +
+ + R = 0.1 + +
+
+
+
+
+ + + + + +
+ +
+ +
+
dec 2019
+
mar 2020
+
jun 2020
+
sep 2020
+
dec 2020
+
mar 2021
+
jun 2021
+
sep 2021
+
dec 2021
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + + \ No newline at end of file