summaryrefslogtreecommitdiff
path: root/assets/js/map/choropleth.js
diff options
context:
space:
mode:
authorChristian Cleberg <[email protected]>2026-04-17 19:41:31 -0500
committerChristian Cleberg <[email protected]>2026-04-17 19:41:31 -0500
commit623ae07ebacb9d596b225179727dc87641ad1cca (patch)
treef1045282b3369bf3f4b0249cd649b47a5d010dad /assets/js/map/choropleth.js
parentfd412c5197286518ff9491a1b51e6319c9a22904 (diff)
downloadworld-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/map/choropleth.js')
-rw-r--r--assets/js/map/choropleth.js31
1 files changed, 31 insertions, 0 deletions
diff --git a/assets/js/map/choropleth.js b/assets/js/map/choropleth.js
index 9316f29..5262764 100644
--- a/assets/js/map/choropleth.js
+++ b/assets/js/map/choropleth.js
@@ -10,6 +10,7 @@ const NO_DATA_FILL = "#444";
let svg, mapGroup, projection, pathGen, features;
let _countries, _isoLookup;
let _clickHandler = null;
+let _labelsVisible = false;
export async function initMap(container, countries, isoLookup) {
_countries = countries;
@@ -43,9 +44,33 @@ export async function initMap(container, countries, isoLookup) {
if (_clickHandler) _clickHandler(event, d);
});
+ // Country name labels, hidden by default
+ mapGroup.selectAll("text.country-label")
+ .data(features)
+ .join("text")
+ .attr("class", "country-label")
+ .attr("transform", d => {
+ const [x, y] = pathGen.centroid(d);
+ return isNaN(x) || isNaN(y) ? "translate(-9999,-9999)" : `translate(${x},${y})`;
+ })
+ .attr("text-anchor", "middle")
+ .attr("dy", "0.35em")
+ .style("font-size", "7px")
+ .style("fill", "#fff")
+ .style("pointer-events", "none")
+ .style("display", "none")
+ .text(d => {
+ const alpha3 = _isoLookup[+d.id];
+ return alpha3 && _countries[alpha3] ? _countries[alpha3].country : "";
+ });
+
return { svg, mapGroup, projection };
}
+export function getCountryPaths() {
+ return mapGroup.selectAll("path.country");
+}
+
export function applyGradient(metricKey) {
const values = Object.values(_countries)
.map(c => c[metricKey])
@@ -69,6 +94,12 @@ export function resetGradient() {
.attr("fill", d => _fillForFeature(d, null, null));
}
+export function toggleLabels() {
+ _labelsVisible = !_labelsVisible;
+ mapGroup.selectAll("text.country-label")
+ .style("display", _labelsVisible ? "block" : "none");
+}
+
export function setClickHandler(fn) {
_clickHandler = fn;
}