Drawing US Map in D3 (Chloropleth)

I’m on the 4th of the final projects in the Data Visualization course, where we have to plot a map of the US and education by county, and I have no idea where to even start with this one.

I’ve looked around the web for info on plotting maps in D3, but the explanations are all way, way above my head. I’ve seen references to geoPath and other things, but unfortunately the explanations I’ve found are not much help in understanding what they do or how to relate them to the data we’re given.

Any help is greatly appreciated!

Link to pen, which as of now only logs the map-building data to the console:

Hey! you can use this course to learn D3 : Let's learn D3.js - D3 for data visualization (full course) - YouTube
:slight_smile:

This section was definitely the least fun. The projects require knowledge you didn’t learn on FCC. And D3 is obviously not that popular because Googling doesn’t yield useful info. Look up topojson and geopath

1 Like

The choropleth is easily the hardest project, right up until it isn’t, and then it’s as easy as the bar graph. Crossing that gap is the hard part. The steps are basically the same on both projects. You need to fetch the data (you can do that already), you need to process it so that you’ve got a data set to iterate over (like the GDP data), and you need to draw it (just a bar, almost), color it (just like the heatmap with a color scale), and add the tooltips (just like every other project).

The hard part is getting started since there is nothing in the practice about mapping and it’s not obvious from anything what format the data is. The education data is just plain, self-explanatory JSON. It’s fips field is the US Census FIPS ID for the county and is the same as the id field in the counties data. The county data is the geography information in topoJSON format (the type: "Topology" field is the hint) which along with geoJSON is one of the map formats that D3 handles best. This data is JSON, with three different types of geography objects: counties (array of county boundaries), states, and nation. These three will be used to draw the counties, states, and nation boundaries. The catch is that topoJSON is a condensed format, and has to be converted to geoJSON before drawing. You can use the library topojson-client to do the conversion.

Once you have the geoJSON, you can draw the counties. The geoJSON data looks similar to topoJSON, but it has been converted into coordinates (either latitude/longitude or projected latitude/longitude; this is the latter). This data is in the field features, which is an array of objects containing information about the feature in the properties field and arrays of coordinates in the geometry field, something like

{
  ...
  'features': [
    {
      'type': 'Feature',
      'properties': {
        'id': 01127,
        'name': 'Walker County',
        ...
      },
      'geometry': {
        ...
      }
    },
    ...
  ]
}

So to draw the counties, all you have to know is the projection to tell D3 (it’s Albers USA), and then pass the feature to D3’s geoPath function for drawing (usually you do this by iterating over the features array, just like drawing rects by iterating over the GDP data array). Once you can draw counties, you can link the county and education data via the id/fips fields and do the the rest of the stuff that’s the same on all the projects.

To summarize what you’ll need to know:

  • the topojson-client function to convert an array of objects (in topoData.objects.counties, or whatever you name the fetched data) to geoJSON
  • how to set the D3 projection to Albers USA
  • how to use d3.geoPath() to draw a path with a set of coordinates (a feature)
  • how to iterate over your generated features in some variable like geoData.features like you did with the GDP data from the bar graph
  • how to do the rest of the stuff all these projects require
2 Likes

Thank you for the incredibly detailed response. I really appreciate it.

I’m still not following on some of the steps, though. If you wouldn’t mind shining some light on them, that would be amazing:

  1. What function from the topojson-client converts the topoJSON to the geoJSON? I’ve tried using the “topo2geo” one but the data doesn’t look any different.

  2. Is the data with the “features” array something that you get after the conversion? I’ve looked through both JSON files and can’t find that field anywhere.

  3. I’m just realizing now that we need to load two JSON files to do this project, but we’ve only ever had to load one before. I only know the “new XMLHttpRequest” way to load JSON data that we were taught by FCC, but then all the code has to be within its “onload” function. Can I just call the 2nd set of data within that function, or is there an easier way to load JSON data that we haven’t been taught?

Again, greatly appreciate your help with this!

Start here since you’re trying to generate features. The topology is the object that has the topoJSON data (from your fetch or whatever) and the object is where the objects are located in the topology object, so if your object is called data and you want to draw the counties the object is data.objects.counties since that’s where the county data is in the counties.json file. Technically, it’s looking for an object with a "type": "GeometryCollection" property in this case (or generally a topoJSON object).

Yes. It will be returned after you convert the initial topoJSON to geoJSON. console.log() the output once you can convert to geoJSON and you’ll see them.

There’s a bunch of ways. I usually use an axios.get() and await that. You can do two of those or you can fetch with d3 functions and nest a couple of callbacks or use a sequence of promises with .then()s. I haven’t actually used XMLHttpRequest directly, but I’m sure it’s possible. It’s entirely up to you; I’ve seen it done all these ways and more.

2 Likes

Thank you so much for the reply! I appreciate all of your help explaining this stuff.

Thanks to you I was finally able to finish the project. Greatly appreciated, couldn’t have don it without you! :slight_smile: