Choropleth attempt

Tried implementing choropleth while keeping filters and adding zoom function. Zoom & drag function currently active on ALL graph types. Looking to keep zoom & drag only on choropleth
This commit is contained in:
bennteaa 2025-06-02 17:01:51 +10:00
parent a49cc771b8
commit 3de4b68136
4 changed files with 113 additions and 2 deletions

BIN
.DS_Store vendored

Binary file not shown.

View file

@ -10,6 +10,9 @@
<!-- D3.js library --> <!-- D3.js library -->
<script src="https://d3js.org/d3.v7.min.js"></script> <script src="https://d3js.org/d3.v7.min.js"></script>
<!-- TopoJSON client library for map rendering -->
<script src="https://unpkg.com/topojson-client@3"></script>
<!-- Save SVG as PNG library --> <!-- Save SVG as PNG library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/save-svg-as-png/1.4.17/saveSvgAsPng.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/save-svg-as-png/1.4.17/saveSvgAsPng.min.js"></script>
</head> </head>

File diff suppressed because one or more lines are too long

View file

@ -83,8 +83,19 @@ darkModeToggle.addEventListener('click', () => {
}); });
// Load data // Add 'choropleth' to chart type selector on page load
d3.csv("PHSwithContinent.csv").then(function(data) { if (document.getElementById("chartTypeSelect") && !Array.from(document.getElementById("chartTypeSelect").options).some(opt => opt.value === "choropleth")) {
const option = document.createElement("option");
option.value = "choropleth";
option.text = "Choropleth Map";
document.getElementById("chartTypeSelect").appendChild(option);
}
// Load both CSV and TopoJSON data
Promise.all([
d3.csv("PHSwithContinent.csv"),
d3.json("script/countries-50m.json")
]).then(function([data, world]) {
// Normalize Socio-economic status values to match educationLevels // Normalize Socio-economic status values to match educationLevels
data.forEach(d => { data.forEach(d => {
if (d["Socio-economic status"] === "Pre-primary, primary and lower secondary education") { if (d["Socio-economic status"] === "Pre-primary, primary and lower secondary education") {
@ -96,6 +107,7 @@ d3.csv("PHSwithContinent.csv").then(function(data) {
} }
}); });
const allData = data; const allData = data;
const worldData = world;
// Populate continent and education dropdowns // Populate continent and education dropdowns
const continents = Array.from(new Set(data.map(d => d.CONTINENT))).sort(); const continents = Array.from(new Set(data.map(d => d.CONTINENT))).sort();
@ -166,6 +178,8 @@ d3.csv("PHSwithContinent.csv").then(function(data) {
drawDotPlot(filteredData); drawDotPlot(filteredData);
} else if (selectedChart === "heatmap") { } else if (selectedChart === "heatmap") {
drawHeatmap(filteredData); drawHeatmap(filteredData);
} else if (selectedChart === "choropleth") {
drawChoropleth(filteredData, worldData);
} }
} }
@ -525,3 +539,96 @@ function drawHeatmap(data) {
g.append("g") g.append("g")
.call(d3.axisLeft(y)); .call(d3.axisLeft(y));
} }
// Choropleth map drawing function
function drawChoropleth(data, worldData) {
// Remove any previous zoom behavior
svg.on("mousedown.zoom", null).on("touchstart.zoom", null).on("touchmove.zoom", null).on("touchend.zoom", null);
// Setup projection and path
const projection = d3.geoMercator()
.fitSize([width, height], topojson.feature(worldData, worldData.objects.countries));
const path = d3.geoPath().projection(projection);
// Prepare a map from country name to value
const valueByCountry = {};
data.forEach(d => {
valueByCountry[d["Reference area"]] = parseFloat(d.OBS_VALUE);
});
// Color scale for choropleth
const colorScale = d3.scaleSequential()
.interpolator(d3.interpolateYlGnBu)
.domain([0, 100]);
// Remove previous map if any
g.selectAll(".country").remove();
g.selectAll(".choropleth-legend").remove();
// Draw countries
const countries = topojson.feature(worldData, worldData.objects.countries).features;
const countryPaths = g.selectAll(".country")
.data(countries)
.join("path")
.attr("class", "country")
.attr("d", path)
.attr("fill", d => {
const name = d.properties.name;
const val = valueByCountry[name];
return val != null ? colorScale(val) : "#ccc";
})
.attr("stroke", darkMode ? "#222" : "#fff")
.attr("stroke-width", 0.5)
.on("mouseover", function(event, d) {
const name = d.properties.name;
const val = valueByCountry[name];
tooltip.html(`<strong>${name}</strong><br/>${val != null ? val + "%" : "No data"}`)
.style("left", (event.pageX + 10) + "px")
.style("top", (event.pageY - 40) + "px")
.transition().duration(300)
.style("opacity", 1)
.style("transform", "translateY(-10px)");
})
.on("mouseout", function() {
tooltip.transition().duration(500)
.style("opacity", 0)
.style("transform", "translateY(0px)");
});
// Add zoom and pan
svg.call(d3.zoom()
.scaleExtent([1, 8])
.on("zoom", (event) => {
g.attr("transform", event.transform);
})
);
// Add legend for color scale
const legendWidth = 200;
const legendHeight = 12;
const legendSvg = g.append("g")
.attr("class", "choropleth-legend")
.attr("transform", `translate(${width - legendWidth - 20},${height - 40})`);
const defs = svg.select("defs").empty() ? svg.append("defs") : svg.select("defs");
defs.selectAll("#choropleth-gradient").remove();
const linearGradient = defs.append("linearGradient")
.attr("id", "choropleth-gradient");
linearGradient.selectAll("stop")
.data(d3.range(0, 1.01, 0.01))
.enter().append("stop")
.attr("offset", d => d)
.attr("stop-color", d => colorScale(d * 100));
legendSvg.append("rect")
.attr("width", legendWidth)
.attr("height", legendHeight)
.style("fill", "url(#choropleth-gradient)");
// Legend axis
const legendScale = d3.scaleLinear().domain([0, 100]).range([0, legendWidth]);
const legendAxis = d3.axisBottom(legendScale).ticks(5).tickFormat(d => d + "%");
legendSvg.append("g")
.attr("transform", `translate(0,${legendHeight})`)
.call(legendAxis);
}