krz/world-incarceration

An interactive map of world incarceration statistics.

clone: git clone https://gitbay.org/krz/world-incarceration.git

v2: assets/js/app.js · raw

  1import * as d3 from "https://cdn.jsdelivr.net/npm/d3@7/+esm";
  2import { countries } from "../data/countries.js";
  3import { isoNumericToAlpha3 } from "../data/iso-numeric-to-alpha3.js";
  4import {
  5	initMap, applyGradient, resetGradient,
  6	toggleLabels, setClickHandler, getCountryPaths,
  7} from "./map/choropleth.js";
  8import { initTooltip, attachTooltip } from "./map/tooltip.js";
  9import { initZoom, resetZoom } from "./map/zoom.js";
 10import { renderBarChart } from "./charts/bar.js";
 11import { renderPieChart } from "./charts/pie.js";
 12import { showCountryModal } from "./ui/modal.js";
 13import { initCompare } from "./ui/compare.js";
 14import { initControls, getTop5, formatMetricValue } from "./ui/controls.js";
 15
 16// Tracks which metric is currently active for the graph modal
 17let currentMetric = "pop";
 18
 19async function main() {
 20	const { svg, mapGroup } = await initMap(
 21		"#map-container", countries, isoNumericToAlpha3
 22	);
 23
 24	// --- Phase 3: tooltip ------------------------------------------------
 25	initTooltip();
 26	attachTooltip(getCountryPaths(), (event, d) => {
 27		const alpha3 = isoNumericToAlpha3[+d.id];
 28		const name = alpha3 && countries[alpha3] ? countries[alpha3].country : null;
 29		return name ? `<strong>${name}</strong>` : null;
 30	});
 31
 32	// --- Phase 3: zoom ---------------------------------------------------
 33	initZoom(svg, mapGroup);
 34
 35	// --- Phase 3: country click → detail modal ---------------------------
 36	setClickHandler((event, d) => {
 37		const alpha3 = isoNumericToAlpha3[+d.id];
 38		if (alpha3 && countries[alpha3]) showCountryModal(countries[alpha3]);
 39	});
 40
 41	// --- Phase 5: metric buttons, label toggle, reset --------------------
 42	initControls(
 43		(metricKey) => {
 44			currentMetric = metricKey;
 45			applyGradient(metricKey);
 46			// Refresh pie if graph modal is already open
 47			if (document.getElementById("graphModal").classList.contains("show")) {
 48				_renderPie();
 49			} else {
 50				// Clear stale chart so it re-renders fresh on next open
 51				document.getElementById("myChart2").innerHTML = "";
 52			}
 53		},
 54		() => toggleLabels(),
 55		() => { resetGradient(); resetZoom(); }
 56	);
 57
 58	// --- Phase 5: compare modal ------------------------------------------
 59	initCompare(countries);
 60
 61	// --- Phase 4: graph modal — render pie on first open -----------------
 62	document.getElementById("graphModal").addEventListener("show.bs.modal", () => {
 63		if (!document.querySelector("#myChart2 svg")) {
 64			_renderPie();
 65		}
 66	});
 67}
 68
 69// Renders (or re-renders) the pie chart for the current metric
 70function _renderPie() {
 71	document.getElementById("myChart2").innerHTML = "";
 72	document.getElementById("myNextChart").style.display = "none";
 73	document.getElementById("myNextChart").innerHTML = "";
 74	document.getElementById("myChart2").style.display = "";
 75	renderPieChart("#myChart2", getTop5(countries, currentMetric), _showDrilldown);
 76}
 77
 78// Replaces pie with a per-country metrics bar chart
 79function _showDrilldown(alpha3) {
 80	const c = countries[alpha3];
 81	const metricKeys   = ["pop",        "rate",       "females",  "juveniles",  "occupancy"];
 82	const metricLabels = ["Population", "Rate/100k",  "% Female", "% Juvenile", "Occupancy"];
 83
 84	const data = metricKeys.map((key, i) => {
 85		const val = c[key];
 86		if (typeof val !== "number" || isNaN(val)) return null;
 87
 88		const allVals = Object.values(countries)
 89			.map(co => co[key])
 90			.filter(v => typeof v === "number" && !isNaN(v));
 91		const max = Math.max(...allVals);
 92
 93		return {
 94			label:        metricLabels[i],
 95			value:        (val / max) * 100,
 96			displayValue: formatMetricValue(val, key),
 97		};
 98	}).filter(Boolean);
 99
100	document.getElementById("myChart2").style.display = "none";
101	document.getElementById("myNextChart").style.display = "block";
102
103	renderBarChart("#myNextChart", data, {
104		title: c.country,
105		onBack: () => _renderPie(),
106	});
107}
108
109main();