Prompt utilisé pour régénérer cette page :
Page: Virus Propagation - Agent-Based SEIR Epidemic Simulation
Title: "Virus Propagation"
Description: "Visualize how epidemics spread through populations"
Icon: virus
Tags: simulation, epidemiology, agent-based
Status: validated
Category: computer-science
Front matter: no js/scss keys (uses default convention)
HTML structure in index.md:
<section class="container visual size-800 ratio-1-1 canvas-contain">
<canvas id="virus-canvas"></canvas>
</section>
Widget files:
_stats.right.md (weight: 10, title: "Statistics"):
div.virus-stats with 5 stat-row elements, each having:
<span class="swatch {state}"> (colored circle) + <span class="label">{Name}</span> + <span class="value" id="stat-{state}">0</span>
States: susceptible, exposed, infected, recovered, dead
_controls.right.md (weight: 20, title: "Controls"):
div.virus-controls > div.virus-actions:
{{< button id="btn-start" label="Play" class="is-start primary" >}}
{{< button id="btn-pause" label="Pause" class="is-pause primary" >}}
{{< button id="btn-reset" label="Reset" >}}
Play/Pause visibility toggled via CSS .is-running on .virus-actions
_options.right.md (weight: 30, title: "Options"):
##### Preset
div.virus-options:
1. Preset selector: <select id="virus-preset-select"> with 3 optgroups:
Respiratory: COVID-19 (selected), Seasonal Flu, Measles, Common Cold
Hemorrhagic (Ebola): Ebola Zaire (90%), Ebola Sudan (50%), Ebola Bundibugyo (25%)
Historical: Black Plague, Smallpox
2. Transmission mode: <select id="virus-transmission-mode">: Airborne, Contact, Vector
3. Parameter sliders (each in div.option-group):
virus-population: Population, range 50-500, step 10, default 200
virus-initial-infected: Initial Infected, range 1-20, step 1, default 3
virus-infection-radius: Infection Radius, range 5-50, step 1, default 15
virus-infection-rate: Infection Rate, range 5-100, step 5, default 30 (displayed as %)
virus-incubation-time: Incubation Time, range 0-500, step 10, default 100
virus-asymptomatic-rate: Asymptomatic Rate, range 0-100, step 5, default 0 (displayed as %)
virus-recovery-time: Recovery Time, range 100-1000, step 50, default 300
virus-mortality-rate: Mortality Rate, range 0-100, step 1, default 2 (displayed as %)
virus-immunity-duration: Immunity Duration, range 0-3000, step 100, default 0 (0 = infinity display)
virus-vaccination-rate: Vaccination Rate, range 0-90, step 5, default 0 (displayed as %)
4. Checkbox: <input type="checkbox" id="virus-social-distancing"> Social Distancing
Each slider has display <span class="value" id="virus-{name}-value">
_chart.after.md (weight: 5, title: "SEIR Chart"):
div.virus-chart:
div.chart-container > <canvas id="virus-chart">
div.chart-legend with 5 items: S (susceptible), E (exposed), I (infected), R (recovered), D (dead)
Each with <span class="swatch {state}"> colored circle
_algorithm.after.md (title: "Algorithm"):
Markdown explaining SIR/SEIR model: States, Parameters, Interventions, R0 explanation
Preset JS files (9 files, _preset-*.js):
Each registers on window.VIRUS_PRESETS global object. Pattern:
window.VIRUS_PRESETS = window.VIRUS_PRESETS || {};
window.VIRUS_PRESETS['key'] = { name, population, infectionRadius, infectionRate,
recoveryTime, mortalityRate, initialInfected, incubationTime, asymptomaticRate,
immunityDuration, transmissionMode };
_preset-covid.js: COVID-19
population=200, infectionRadius=15, infectionRate=0.30, recoveryTime=400, mortalityRate=0.02,
initialInfected=3, incubationTime=120, asymptomaticRate=0.35, immunityDuration=1500, transmissionMode='airborne'
_preset-flu.js: Seasonal Flu
population=200, infectionRadius=12, infectionRate=0.15, recoveryTime=200, mortalityRate=0.001,
initialInfected=3, incubationTime=60, asymptomaticRate=0.25, immunityDuration=2000, transmissionMode='airborne'
_preset-measles.js: Measles
population=200, infectionRadius=25, infectionRate=0.90, recoveryTime=300, mortalityRate=0.002,
initialInfected=1, incubationTime=250, asymptomaticRate=0.05, immunityDuration=0, transmissionMode='airborne'
_preset-cold.js: Common Cold
population=200, infectionRadius=12, infectionRate=0.25, recoveryTime=150, mortalityRate=0.0,
initialInfected=5, incubationTime=40, asymptomaticRate=0.10, immunityDuration=500, transmissionMode='airborne'
_preset-ebola-zaire.js: Ebola Zaire
population=150, infectionRadius=8, infectionRate=0.25, recoveryTime=600, mortalityRate=0.90,
initialInfected=1, incubationTime=180, asymptomaticRate=0.0, immunityDuration=0, transmissionMode='contact'
_preset-ebola-sudan.js: Ebola Sudan
population=150, infectionRadius=8, infectionRate=0.22, recoveryTime=550, mortalityRate=0.50,
initialInfected=1, incubationTime=180, asymptomaticRate=0.0, immunityDuration=0, transmissionMode='contact'
_preset-ebola-bundibugyo.js: Ebola Bundibugyo
population=150, infectionRadius=8, infectionRate=0.20, recoveryTime=500, mortalityRate=0.25,
initialInfected=1, incubationTime=180, asymptomaticRate=0.0, immunityDuration=0, transmissionMode='contact'
_preset-plague.js: Black Plague
population=200, infectionRadius=10, infectionRate=0.20, recoveryTime=400, mortalityRate=0.40,
initialInfected=2, incubationTime=80, asymptomaticRate=0.0, immunityDuration=0, transmissionMode='vector'
_preset-smallpox.js: Smallpox
population=200, infectionRadius=15, infectionRate=0.50, recoveryTime=500, mortalityRate=0.30,
initialInfected=2, incubationTime=280, asymptomaticRate=0.0, immunityDuration=0, transmissionMode='contact'
Architecture (1 JS file + 9 preset files):
default.js — IIFE, 'use strict', no external imports (~984 lines):
Enums: STATE { SUSCEPTIBLE, EXPOSED, INFECTED, RECOVERED, DEAD }, TRANSMISSION { AIRBORNE, CONTACT, VECTOR }
Speed constants: NORMAL_SPEED=2, DISTANCING_SPEED=0.8
CONFIG (mutable): population=200, infectionRadius=15, infectionRate=0.3, recoveryTime=300, mortalityRate=0.02,
vaccinationRate=0, socialDistancing=false, initialInfected=3, incubationTime=100, asymptomaticRate=0.3,
immunityDuration=0, transmissionMode=AIRBORNE
State: canvas, ctx, chartCanvas, chartCtx, dpr, agents[], isRunning, animationId, resizeTimeout, tick=0
History: { susceptible[], exposed[], infected[], recovered[], dead[] }, MAX_HISTORY=500
cachedColors: { surface, susceptible, exposed, infected, recovered, dead, chart{bg, grid} }
PRESETS: loaded from window.VIRUS_PRESETS at init, currentPreset='covid'
Agent class:
constructor(x, y, state=SUSCEPTIBLE): position, state, exposedTime=0, infectedTime=0, recoveredTime=0,
isAsymptomatic=false, radius=5. Random velocity direction, speed from CONFIG (NORMAL or DISTANCING)
update(width, height): Dead agents skip. Adjusts speed for social distancing. Moves, bounces off walls.
EXPOSED: increments exposedTime, transitions to INFECTED at >= incubationTime (randomly sets isAsymptomatic)
INFECTED: increments infectedTime, at >= recoveryTime: dies (mortalityRate, asymptomatic immune to death) or recovers
RECOVERED: if immunityDuration > 0, increments recoveredTime, loses immunity when expired → SUSCEPTIBLE
tryInfect(infectedAgent): Only SUSCEPTIBLE. Calculates distance. Adjusts effective radius by transmission mode
(contact=0.5x, vector=0.3x, airborne=1x). If within radius and random < infectionRate → EXPOSED (SEIR)
getColor(): Returns cached color for current state
draw(ctx): Filled circle. INFECTED: draws infection radius circle (faint, alpha 0.1 or 0.05 if asymptomatic).
Asymptomatic: additional green inner ring at radius+2
Functions:
cacheColors(): Reads CSS vars --virus-color-{state}, --background-color-surface, --draw-color-surface
initCanvas(): Fixed 800px square, DPR scaling (canvas.width = 800*dpr, ctx.setTransform(dpr,...))
resizeChartCanvas(): Sizes chart canvas to parent container dimensions
handleResize(): Debounced (100ms) chart resize
getWidth()/getHeight(): canvas.width/height / dpr (logical dimensions)
countStates(): Counts agents per state, returns object
applyPreset(presetName): Copies all 11 preset values to CONFIG, calls updateControlsUI
updateControlsUI(): Syncs all 13 UI elements (2 selects, 10 sliders, 1 checkbox) with CONFIG.
Rates converted to percentage display. Immunity 0 shown as infinity symbol
initControls(): Binds all UI elements:
virus-preset-select → applyPreset + reset
virus-transmission-mode → CONFIG.transmissionMode
10 range inputs (population, initial-infected, infection-radius, infection-rate/100, incubation-time,
asymptomatic-rate/100, recovery-time, mortality-rate/100, immunity-duration, vaccination-rate/100)
virus-social-distancing checkbox
btn-start click (only if !isRunning), btn-pause click (only if isRunning), btn-reset click
init(): Loads PRESETS from window.VIRUS_PRESETS, gets both canvases, caches colors, listens for
prefers-color-scheme changes, inits canvas + chart, inits controls, applies default preset ('covid'), resets
reset(): Stops running, removes .is-running from .virus-actions. Clears history, tick=0. Creates
CONFIG.population agents at random positions. Applies vaccination (state=RECOVERED). Infects
CONFIG.initialInfected agents. Updates stats + draws
toggleRun(): Toggles isRunning, toggles .is-running on .virus-actions. Starts run() loop or cancels RAF
run(): Exits if !isRunning. Updates all agents. Filters infected, each tries to infect all others.
Every 5 ticks: records all 5 state counts to history, limits to MAX_HISTORY=500.
Updates stats, draws, schedules next RAF
updateStats(): Counts states, updates 5 stat DOM elements (#stat-{state})
draw(): Fills surface background, draws all agents, calls drawChart
drawChart(): Renders SEIR time-series on chartCanvas. 2px padding, 4 horizontal grid lines.
5 curves: susceptible (green), exposed (orange, only if non-zero), infected (red),
recovered (blue), dead (gray, only if non-zero). lineWidth=2. Y-scale = CONFIG.population
Auto-init: readyState check + DOMContentLoaded
default.scss:
:root CSS vars: --virus-color-susceptible: var(--color-green), --virus-color-exposed: var(--color-orange),
--virus-color-infected: var(--color-red), --virus-color-recovered: var(--color-blue),
--virus-color-dead: var(--color-gray)
$breakpoint-mobile: 768px
layout-main main: flex column center, 2rem padding, 1.5rem gap
#virus-canvas: 100% w/h, 1px border surface, 4px radius, themed bg
.virus-controls: flex column, 1rem gap
.control-group select: 100% width, 0.5rem padding, themed border, 4px radius
.virus-actions: flex row, center, 0.5rem gap. .button flex:1, no-wrap, text-overflow ellipsis
Play/Pause toggle: .is-start display:block + .is-pause display:none. Reversed when .is-running
.virus-stats: flex column, 0.75rem gap
.stat-row: flex row, center, 0.5rem gap
.swatch: 0.75rem circle, colored by state class (susceptible/exposed/infected/recovered/dead)
.label: flex:1, weight 300. .value: weight 600, tabular-nums
.virus-options: flex column, 1rem gap
.option-group: flex column, 0.25rem gap. label: flex row between, 0.85rem weight 300
.value span: weight 600, tabular-nums
input[type="range"]: 0.5rem height, accent-color --draw-color-primary
input[type="checkbox"]: 1rem square, accent-color --draw-color-primary
.checkbox-group: flex row, center, 0.5rem gap, cursor pointer
.virus-chart: 100% width
.chart-container: 120px height, 1px border, 4px radius, themed bg
#virus-chart: 100% w/h
.chart-legend: flex row wrap center, 1rem gap, 0.5rem margin-top
.legend-item: inline-flex, center, 0.25rem gap, 0.75rem, weight 300
.swatch: 0.5rem circle, colored by state class
@media max-width $breakpoint-mobile:
.virus-stats: flex row wrap center, 1rem gap
.chart-container: 100px height
Important implementation notes:
- Main canvas 800px with DPR scaling (unlike solar-system/three-body at 1000px without DPR)
- SEIR model: Susceptible → Exposed → Infected → Recovered/Dead, with optional immunity loss
- Chart canvas separate from main canvas, resized independently to parent container
- Presets loaded from window.VIRUS_PRESETS global (set by _preset-*.js files loaded before default.js)
- Infection check: O(infected * total) each frame — infected agents try to infect all others
- History recorded every 5 ticks (not every frame) to reduce data points
- Transmission modes affect effective infection radius: airborne=1x, contact=0.5x, vector=0.3x
- Asymptomatic agents: cannot die from disease, have green inner ring, faint infection radius
- Vaccination sets agents to RECOVERED state at creation time
- Social distancing reduces movement speed from 2 to 0.8
- No auto-start — resets and waits for Play click
Page entièrement générée et maintenue par IA, sans intervention humaine.