- Circle size = P(≥10 cases, 60-day sim). Color = MMR vaccination rate. Select a city to explore its risk.
+ Circle size = relative simulated chance of observing ≥10 cases. Color = MMR vaccination rate. Select a city to compare outcomes.
- Outbreak probability estimated from the final day (day 60) of 200 independent simulations per city.
+ Risk labels summarize the probability of observing at least the listed number of cases by day 60.
@@ -732,9 +787,9 @@
City-Level Risk Summary
MMR coverage
Population
Seed cases †
-
Prob(≥10 cases)
-
Prob(≥20 cases)
-
Prob(≥50 cases)
+
Risk (≥10 cases)
+
Risk (≥20 cases)
+
Risk (≥50 cases)
@@ -811,15 +866,39 @@
How to get in touch
/* CITIES is defined in data.js – auto-generated by docs/generate_data.py
from scenarios/*_outbreak_size.csv, data/mmr.csv, data/population.csv */
+/* ── Risk labels for observing cases ────────────────────────── */
+function riskLevel(prob) {
+ if (prob < 0.10) return {
+ label: 'Low',
+ className: 'risk-low',
+ color: getComputedStyle(document.documentElement).getPropertyValue('--risk-low').trim()
+ };
+ if (prob <= 0.40) return {
+ label: 'Moderate',
+ className: 'risk-moderate',
+ color: getComputedStyle(document.documentElement).getPropertyValue('--risk-moderate').trim()
+ };
+ return {
+ label: 'High',
+ className: 'risk-high',
+ color: getComputedStyle(document.documentElement).getPropertyValue('--risk-high').trim()
+ };
+}
+
+function riskBadgeHtml(prob) {
+ const risk = riskLevel(prob);
+ return `${risk.label}`;
+}
+
/* ── Colour fill by vaccination rate ────────────────────────── */
/* ColorBrewer RdYlBu 4-class diverging scale (Brewer 1994).
- Centred at the ~95 % measles herd-immunity threshold (R₀ ≈12–18).
+ Centred at the ~95 % measles herd-immunity threshold (R0 approx. 12-18).
Red = low coverage / higher risk; Blue = high coverage / lower risk. */
function vaccColor(vacc) {
- if (vacc >= 95) return '#2c7bb6'; // blue — at/above herd immunity
- if (vacc >= 92) return '#abd9e9'; // lt blue — approaching threshold
- if (vacc >= 90) return '#fdae61'; // orange — below threshold
- return '#d7191c'; // red — well below threshold
+ if (vacc >= 95) return '#2c7bb6'; // blue
+ if (vacc >= 92) return '#abd9e9'; // light blue
+ if (vacc >= 90) return '#fdae61'; // orange
+ return '#d7191c'; // red
}
/* ── Radius scaled by prob_10 (min 6 px, max 35 px total) ────── */
@@ -945,12 +1024,11 @@
How to get in touch
new RecenterControl().addTo(map);
/* ── Theme-aware popup (WCAG 2.1: ≥4.5:1 contrast in both modes) ── */
-function buildPopupHtml(city, color) {
+function buildPopupHtml(city) {
const dark = document.documentElement.getAttribute('data-theme') !== 'light';
const tc = dark ? '#e6edf3' : '#1f2328'; // text colour
const mc = dark ? '#8b949e' : '#57606a'; // muted label
const hr = dark ? '#30363d' : '#d0d7de'; // divider
- const pct = v => (v * 100).toFixed(1) + '%';
return `
${city.name}
@@ -960,13 +1038,13 @@
How to get in touch
${city.vacc}%
Population
${city.pop.toLocaleString()}
-
Outbreak probability
+
Risk of observing cases
≥ 10 cases
-
${pct(city.prob_10)}
+
${riskBadgeHtml(city.prob_10)}
≥ 20 cases
-
${pct(city.prob_20)}
+
${riskBadgeHtml(city.prob_20)}
≥ 50 cases
-
${pct(city.prob_50)}
+
${riskBadgeHtml(city.prob_50)}
`;
}
@@ -980,14 +1058,11 @@
How to get in touch
function isPanelVisible() { return window.innerWidth >= PANEL_BP; }
function buildPanelHtml(city, color) {
- const pct = v => (v * 100).toFixed(1) + '%';
- function probBar(label, v) {
+ function riskRow(label, v) {
return `
-
-
${label}${pct(v)}
-
-
-
+
+ ${label}
+ ${riskBadgeHtml(v)}
`;
}
return `
@@ -1005,11 +1080,12 @@
${city.name}
${(city.pop / 1e6).toFixed(2)}M
-
Outbreak Probability
-
200 simulations · 60-day window · single seed case