diff options
| author | Christian Cleberg <[email protected]> | 2026-04-17 19:41:31 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-04-17 19:41:31 -0500 |
| commit | 623ae07ebacb9d596b225179727dc87641ad1cca (patch) | |
| tree | f1045282b3369bf3f4b0249cd649b47a5d010dad /assets/js/ui/controls.js | |
| parent | fd412c5197286518ff9491a1b51e6319c9a22904 (diff) | |
| download | world-incarceration-623ae07ebacb9d596b225179727dc87641ad1cca.tar.gz world-incarceration-623ae07ebacb9d596b225179727dc87641ad1cca.tar.bz2 world-incarceration-623ae07ebacb9d596b225179727dc87641ad1cca.zip | |
phase 2-6: complete D3/TopoJSON migration
Replace ZingChart + jQuery with D3 v7 + TopoJSON. Implements all
planned phases: choropleth map (NaturalEarth1 projection, YlOrRd
gradient), hover tooltip, zoom/pan/reset, country detail modal,
side-by-side compare, metric selector, label toggle, donut pie chart
with per-country drilldown bar chart. Upgrades Bootstrap JS bundle
from v4.5.3 (jQuery-dependent) to v5.0.0, fixes modal API calls
to use getInstance/constructor pattern compatible with BS 5.0.0+.
Removes jquery, zingchart ×3 library files.
Diffstat (limited to 'assets/js/ui/controls.js')
| -rw-r--r-- | assets/js/ui/controls.js | 59 |
1 files changed, 56 insertions, 3 deletions
diff --git a/assets/js/ui/controls.js b/assets/js/ui/controls.js index 15c1a3b..4c229d4 100644 --- a/assets/js/ui/controls.js +++ b/assets/js/ui/controls.js @@ -1,3 +1,56 @@ -// Phase 5: metric selector, label toggle, reset button bindings -export function initControls(onMetricChange, onLabelToggle, onReset) {} -export function getTop5(countries, metricKey) {} +const BTN_IDS = ["totPop", "perCapita", "women", "juveniles", "occupancy"]; +const METRIC_KEYS = ["pop", "rate", "females", "juveniles", "occupancy"]; + +export function initControls(onMetricChange, onLabelToggle, onReset) { + BTN_IDS.forEach((id, i) => { + const btn = document.getElementById(id); + if (!btn) return; + btn.addEventListener("click", () => { + BTN_IDS.forEach(b => document.getElementById(b)?.classList.remove("active-metric")); + btn.classList.add("active-metric"); + onMetricChange(METRIC_KEYS[i]); + }); + }); + + document.querySelector("[data-action='labels']")?.addEventListener("click", e => { + e.preventDefault(); + onLabelToggle(); + }); + + document.querySelector("[data-action='reset']")?.addEventListener("click", e => { + e.preventDefault(); + onReset(); + }); +} + +// Returns [{label, value, alpha3}] top-5 plus a rest-of-world entry. +export function getTop5(countries, metricKey) { + const valid = Object.entries(countries) + .filter(([, c]) => typeof c[metricKey] === "number" && !isNaN(c[metricKey])) + .sort((a, b) => b[1][metricKey] - a[1][metricKey]); + + const top5 = valid.slice(0, 5).map(([alpha3, c]) => ({ + label: c.country, + value: c[metricKey], + alpha3, + })); + + const restValue = valid.slice(5).reduce((sum, [, c]) => sum + c[metricKey], 0); + if (restValue > 0) { + top5.push({ label: "Rest of World", value: restValue, alpha3: null }); + } + + return top5; +} + +export function formatMetricValue(val, key) { + if (val === "--" || val === null || val === undefined || (typeof val === "number" && isNaN(val))) { + return "N/A"; + } + if (key === "pop") return Number(val).toLocaleString("en"); + if (key === "rate") return String(val); + if (key === "females" || key === "juveniles" || key === "occupancy") { + return (val * 100).toFixed(1) + "%"; + } + return String(val); +} |
