Search code examples
javascriptreactjsd3.jstopojson

Issue rendering correct visual for Albers US Choropleth Map with d3.js and React


I am trying to figure out what went wrong with my code since I have not been able to generate anything else than a big square of a single color while I try to get a colorScale for each element with the class "county" of this Albers US map.

The project should look like this: https://choropleth-map.freecodecamp.rocks/

The weirdest thing is that I validate the test.

I am using d3.js and put it in a React 18 component and I use Babel for rendering through another HTML file.

The snippet which might be blocking:

  const createChoroplethMap = (counties, educationData, width, height, margins) => {
    // create SVG element
    const svg = d3.select("#chomap")
      .append("svg")
      .attr("width", width + margins.left + margins.right)
      .attr("height", height + margins.top + margins.bottom);
  
    // create projection and path generator
    const projection = d3.geoAlbersUsa();
    const pathGenerator = d3.geoPath().projection(projection);
  
    // create color scale
    const colorScale = d3.scaleQuantize()
      .domain([0, d3.max(educationData, d => d.bachelorsOrHigher)])
      .range(d3.schemeBlues[9]);
  
    // draw counties
    svg.selectAll(".county")
      .data(topojson.feature(counties, counties.objects.counties).features)
      .enter().append("path")
      .attr("class", "county")
      .attr("d", pathGenerator)
      .attr("fill", d => {
        const county = educationData.find(e => e.fips === d.id);
        console.log(county.bachelorsOrHigher)
        return colorScale(county.bachelorsOrHigher);
      })
      .attr("data-fips", d => d.id)
      .attr("data-education", d => {
        const county = educationData.find(e => e.fips === d.id);
        return county.bachelorsOrHigher;
      });
  
    // draw states
    svg.selectAll(".state")
      .data(topojson.feature(counties, counties.objects.states).features)
      .enter().append("path")
      .attr("class", "state")
      .attr("d", pathGenerator);
};

Link to the project on codepen: https://codepen.io/reggr0y/pen/QWVoBOR


Solution

  • You can fix your codepen in 2 ways:

    Based on the freecodecamp script it is implied that this topojson is already projected. So you can simply comment out the projection call below.

    const pathGenerator = d3.geoPath() //.projection(projection);
    

    Perhaps this was called out in the tutorial/ course you are following, and if not, it is common to pre-project for Albers USA maps because it is a constructed map that does not follow real geography (e.g. Alaska and Hawaii are not really where they are).

    There is a simpler pre-projected Albers USA in this example for a reference without the choropleth mechanics. Whilst the projection is given for data overlays, note in the rendering code Mike Bostock does not use the projection and path is simply defined as d3.geoPath().

    Perhaps, it is also worth reading through the problems/ answers here and here from Andrew Reid.

    Second, you draw the states over the top of the counties, so make the fill none and the stroke black:

    // draw states
    svg.selectAll(".state")
      .data(topojson.feature(counties, counties.objects.states).features)
      .enter().append("path")
      .attr("class", "state")
      .attr("d", pathGenerator)
      .attr("fill", "none") // <-- add 
      .attr("stroke", "black"); // <-- add  
    

    Here's the outcome from removing the projection - note you just see the black states because they overlay the counties:

    enter image description here

    Here's the outcome from making the adjustments to the state fill and stroke (means that your application of colorScale for the fill was correct):

    enter image description here