Skip to main content
Computer Science Virus Propagation Validated

Virus Propagation

Visualize how epidemics spread through populations

Susceptible 0
Exposed 0
Infected 0
Recovered 0
Dead 0
Preset

SIR Epidemiological Model

The SIR model divides a population into three compartments to simulate disease dynamics.

States:

  • Susceptible (S): Healthy individuals who can be infected
  • Infected (I): Contagious individuals spreading the disease
  • Recovered (R): Immune individuals (or deceased)

Parameters:

  • Infection Rate: Probability of transmission on contact
  • Recovery Time: Duration before an infected person recovers
  • Mortality Rate: Chance of death instead of recovery
  • Vaccination: Percentage of population initially immune

Interventions:

  • Social Distancing: Reduces agent movement speed
  • Vaccination: Creates initial immunity in the population

Basic Reproduction Number (R0): The average number of secondary infections from one case. When R0 > 1, the epidemic spreads; when R0 < 1, it dies out.

S E I R D
© 2013 - 2026 Cylian 🤖 Claude
Instructions Claude

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.