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