(async () => {
const width = 975;
const height = 620;
const scale = 5;
const us = await d3.json("https://d3js.org/us-10m.v1.json");
const evData = [
{ county: "53001", name: "Adams", ev_population: 83 },
{ county: "53003", name: "Asotin", ev_population: 97 },
{ county: "53005", name: "Benton", ev_population: 3102 },
{ county: "53007", name: "Chelan", ev_population: 1462 },
{ county: "53009", name: "Clallam", ev_population: 1439 },
{ county: "53011", name: "Clark", ev_population: 14418 },
{ county: "53013", name: "Columbia", ev_population: 20 },
{ county: "53015", name: "Cowlitz", ev_population: 1240 },
{ county: "53017", name: "Douglas", ev_population: 528 },
{ county: "53019", name: "Ferry", ev_population: 36 },
{ county: "53021", name: "Franklin", ev_population: 904 },
{ county: "53023", name: "Garfield", ev_population: 3 },
{ county: "53025", name: "Grant", ev_population: 881 },
{ county: "53027", name: "Grays Harbor", ev_population: 883 },
{ county: "53029", name: "Island", ev_population: 2587 },
{ county: "53031", name: "Jefferson", ev_population: 1260 },
{ county: "53033", name: "King", ev_population: 120383 },
{ county: "53035", name: "Kitsap", ev_population: 8071 },
{ county: "53037", name: "Kittitas", ev_population: 885 },
{ county: "53039", name: "Klickitat", ev_population: 418 },
{ county: "53041", name: "Lewis", ev_population: 1054 },
{ county: "53043", name: "Lincoln", ev_population: 70 },
{ county: "53045", name: "Mason", ev_population: 1159 },
{ county: "53047", name: "Okanogan", ev_population: 363 },
{ county: "53049", name: "Pacific", ev_population: 291 },
{ county: "53051", name: "Pend Oreille", ev_population: 76 },
{ county: "53053", name: "Pierce", ev_population: 19672 },
{ county: "53055", name: "San Juan", ev_population: 1140 },
{ county: "53057", name: "Skagit", ev_population: 2730 },
{ county: "53059", name: "Skamania", ev_population: 248 },
{ county: "53061", name: "Snohomish", ev_population: 29435 },
{ county: "53063", name: "Spokane", ev_population: 6654 },
{ county: "53065", name: "Stevens", ev_population: 287 },
{ county: "53067", name: "Thurston", ev_population: 8759 },
{ county: "53069", name: "Wahkiakum", ev_population: 84 },
{ county: "53071", name: "Walla Walla", ev_population: 645 },
{ county: "53073", name: "Whatcom", ev_population: 5824 },
{ county: "53075", name: "Whitman", ev_population: 477 },
{ county: "53077", name: "Yakima", ev_population: 1564 }
];
const evMap = new Map(evData.map(d => [d.county, d]));
// Use a logarithmic scale for better visualization of wide-ranging EV populations
const color = d3.scaleSequentialLog()
.domain([1, d3.max(evData, d => d.ev_population)])
.interpolator(d3.interpolateBlues)
.unknown("#444"); // Darker gray for unknown values to blend with black background
const path = d3.geoPath();
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height])
.attr("style", "max-width: 100%; height: auto; background: #000;");
// Add title
svg.append("text")
.attr("x", width / 2)
.attr("y", 30)
.attr("text-anchor", "middle")
.attr("font-size", "35px")
.attr("font-weight", "bold")
.attr("fill", "#fff") // White for contrast
.text("Choropleth Map: Washington State");
// Create a tooltip div
const tooltip = d3.select("body").append("div")
.attr("class", "tooltip")
.style("position", "absolute")
.style("visibility", "hidden")
.style("background", "rgba(255, 255, 255, 0.9)") // Light background for contrast
.style("color", "#000") // Black text for readability
.style("padding", "8px")
.style("border-radius", "4px")
.style("font-size", "12px");
const g = svg.append("g");
// Calculate the bounding box of the map to center it
const washington = topojson.feature(us, us.objects.counties).features.filter(d => d.id.startsWith("53"));
const bounds = path.bounds({ type: "FeatureCollection", features: washington });
const mapWidth = bounds[1][0] - bounds[0][0];
const mapHeight = bounds[1][1] - bounds[0][1];
const translateX = (width - mapWidth * scale) / 2 - bounds[0][0] * scale;
const translateY = (height - mapHeight * scale) / 2 - bounds[0][1] * scale;
// Apply fixed 5x scale and center
g.attr("transform", `translate(${translateX}, ${translateY}) scale(${scale})`);
// Create a group for labels
const labelGroup = g.append("g")
.attr("class", "labels")
.style("pointer-events", "none");
const counties = g.append("g")
.attr("cursor", "pointer")
.selectAll("path")
.data(washington)
.join("path")
.attr("fill", d => color(evMap.get(d.id)?.ev_population ?? 1))
.attr("stroke", "#fff")
.attr("stroke-width", 1 / scale)
.attr("d", path)
.on("click", clicked)
.on("mouseover", function(event, d) {
d3.select(this).attr("fill", "#4a90e2"); // Light blue for hover
const ev = evMap.get(d.id);
tooltip.style("visibility", "visible")
.html(`<strong>${ev?.name ?? "Unknown"} County</strong><br>EV Population: ${ev?.ev_population ?? "N/A"}`);
})
.on("mousemove", function(event) {
tooltip.style("top", (event.pageY - 10) + "px")
.style("left", (event.pageX + 10) + "px");
})
.on("mouseout", function(event, d) {
d3.select(this).attr("fill", d => color(evMap.get(d.id)?.ev_population ?? 1));
tooltip.style("visibility", "hidden");
});
g.append("path")
.attr("fill", "none")
.attr("stroke", "#fff")
.attr("stroke-linejoin", "round")
.attr("stroke-width", 1 / scale)
.attr("d", path(topojson.mesh(us, us.objects.counties, (a, b) => a !== b && a.id.startsWith("53") && b.id.startsWith("53"))));
// Enhanced Legend (Centered at Bottom)
const legendWidth = 300;
const legendHeight = 60;
const legendG = svg.append("g")
.attr("transform", `translate(${(width - legendWidth) / 2}, ${height - legendHeight - 20})`);
const legendScale = d3.scaleLog()
.domain([1, d3.max(evData, d => d.ev_population)])
.range([0, legendWidth]);
const legendAxis = d3.axisBottom(legendScale)
.ticks(5)
.tickFormat(d3.format(".0s"));
legendG.append("rect")
.attr("x", -10)
.attr("y", -15)
.attr("width", legendWidth + 20)
.attr("height", legendHeight)
.attr("fill", "rgba(255, 255, 255, 0.9)") // White background for contrast
.attr("stroke", "#666")
.attr("stroke-width", 1);
legendG.append("g")
.attr("transform", `translate(0, ${legendHeight - 20})`)
.call(legendAxis)
.selectAll("text")
.attr("fill", "#fff"); // White for contrast
const gradient = svg.append("defs")
.append("linearGradient")
.attr("id", "legend-gradient")
.attr("x1", "0%")
.attr("y1", "0%")
.attr("x2", "100%")
.attr("y2", "0%");
gradient.selectAll("stop")
.data(d3.range(0, 1.1, 0.1))
.enter()
.append("stop")
.attr("offset", d => `${d * 100}%`)
.attr("stop-color", d => color(d * d3.max(evData, d => d.ev_population)));
legendG.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", legendWidth)
.attr("height", 20)
.attr("fill", "url(#legend-gradient)");
legendG.append("text")
.attr("x", legendWidth / 2)
.attr("y", -5)
.attr("text-anchor", "middle")
.attr("font-size", "12px") // Larger font size
.attr("font-weight", "bold")
.attr("fill", color(d3.max(evData, d => d.ev_population))) // Match highest EV population color
.text("EV Population");
function clicked(event, d) {
event.stopPropagation();
// Reset all counties to their original color
counties.transition().attr("fill", d => color(evMap.get(d.id)?.ev_population ?? 1));
// Highlight the clicked county
d3.select(this)
.raise()
.transition()
.duration(300)
.attr("stroke-width", 3 / scale)
.attr("fill", "#08306b") // Dark blue for click
.transition()
.duration(300)
.attr("stroke-width", 1 / scale);
// Add county name label
labelGroup.selectAll("text").remove();
const centroid = path.centroid(d);
labelGroup.append("text")
.attr("x", centroid[0])
.attr("y", centroid[1])
.attr("text-anchor", "middle")
.attr("font-size", 12 / scale)
.attr("font-weight", "bold")
.attr("fill", "#fff") // White for contrast
.attr("stroke", "#000") // Black stroke for readability
.attr("stroke-width", 0.5 / scale)
.text(evMap.get(d.id)?.name ?? "Unknown");
}
return svg.node();
})();