Exercise: County Maps

In this exercise, you will create a map of the various counties in Washington state. You have already been provided with shape data and population data for each county. Your job is to create a single map visualization using both of these datasets.

Here are the tasks:

You have a choice of visualization tool for this exercise:


Datasets

We will use two datasets: county shape data and county population data. Later on you will need to link the two together. The id property in the shape data corresponds to the GEOID_TIGER property in the population data.

WA County Populations Data

The waPopulation variable is an array containing population data for each WA county.

Note: “TIGER” stands for the Topologically Integrated Geographic Encoding and Referencing system. For example, it provides standard identifiers for geographic regions.

WA County Shapes Data

The usData variable is a TopoJSON dataset containing the shapes for each US state and county.

const usData = await d3.json('https://cdn.jsdelivr.net/npm/vega-datasets@2.8.0/data/us-10m.json')

The waData variable contains shape data specifically for WA state, which has id 53. We use the topojson library to parse the TopoJSON data to GeoJSON data, then filter the resulting shapes (“features”) to retain WA state only.

const waData = topojson
  .feature(usData, usData.objects.states) // parse TopoJSON to GeoJSON features for all states
  .features // the features array has one entry per state
  .filter((d) => d.id === 53); // filter to WA state (id 53)

We use the countyIDs variable to get the unique identifiers for all WA state counties. We will use this to filter the usData to only the counties in WA state.

const waCountyIDs = waPopulation.map(c => c.GEOID_TIGER)

We now construct a countiesData variable that contains shape data specifically for the counties in WA state. We parse the county shapes from the source TopoJSON to GeoJSON, and then filter to just WA counties.

const countiesData = topojson
  .feature(usData, usData.objects.counties) // parse TopoJSON to GeoJSON for all counties
  .features // the features array has one entry per county
  .filter(d => waCountyIDs.indexOf(d.id) >= 0) // filter to only counties in WA state

Before we dive in, let’s also define width and height variables for sizing our maps:

const width = 500;
const height = 500;

Task 1: Render the County Shapes

In this activity, we will be using the GeoJSON data above (parsed from the TopoJSON source) to create a map of all WA state counties.


Tool Choice: Vega-Lite

(Skip this section if you’re using D3)

As a starter, here is a Vega-Lite map using state-level shape data only. For more guidance, revisit the examples in the Cartographic Visualization reading!

render({
  mark: { type: 'geoshape' },
  data: { values: waData },
  projection: { type: 'identity' },
  width: width,
  height: height
})

Why is the map initially upside down?!

Now try changing the projection type to explore further. Possible alternatives include 'mercator', 'albers', and 'equalEarth’. For a complete list, see the Vega projection documentation. For some projections, you may also want to adjust rotation or scale parameters in the projection. What is the default if you remove the projection entry altogether?

Your job is to now visualize the county-level data. Copy your map code above to the cell below and modify it to load WA county data instead.

// copy code from above and modify it to show WA county data here!

Once you can see the counties, try to further customize the display. For example, set the stroke and fill colors for the counties to custom values.


Tool Choice: D3

(Skip this section if you’re using Vega-Lite)

Please feel free to use existing map examples to help you here! We’ve listed a few examples on Observable that may be helpful for this activity:

We’ve included some starter code to help with the rendering.

The projection variable tells D3 how to turn lon/lat coordinates into pixel coordinates. Below we’ve started with an identity projection, which simply rescales the provided coordinates.

const projection = d3.geoIdentity().fitSize([width, height], waData[0]);

The path variable generates SVG paths we can include in an svg container.

const path = d3.geoPath(projection);

Here is an initial map rendering of state-level data only:

Why is the map initially upside down?!

Change the projection definition to explore further. Possible alternatives include d3.geoMercator() and d3.geoAlbers(). For a complete list, see the D3 projection documentation. For some projections, you may also want to adjust rotation or scale parameters in the projection.

Your job is to now visualize the county-level data. Adapt your map code above in the cell below to load WA county data instead.

(() => {
  // create the SVG container
  const svg = d3.create("svg")
    .attr("width", width)
    .attr("height", height)
    .attr("viewBox", [0, 0, width, height]);

  // NOTE: you will need to use the path variable to generate SVG objects from GeoJSON!

  // draw the counties using countiesData
  // svg
  //  .selectAll(".county")
  //  .data(...)
  //  .join(...)
  //  .attr(...)
  //  .style(...) // set fill, stroke properties
  //  ...
  //[FILL THIS IN WITH YOUR OWN CODE]

  display(svg.node());
})();

Task 2: Add the Population Data

In this activity, we will update our map visualization to color each county (or if you like, overlay a circle) according to population size.


Tool Choice: Vega-Lite

(Skip this section if you’re using D3)

Start by copying your county-level visualization from above. You will then need to add two more aspects:

  1. Add a lookup transform to add population estimates to the data. You want to use the id property of the county GeoJSON data to match against the GEOID_TIGER field of the population data table. You will then want to retrieve one or more fields containing population estimate data (e.g., Estimate2022). See the lookup transform documentation.
  2. Add a color encoding to visualize looked-up population estimates. Add a color property within the encoding object.

If at any point you’re feeling stuck, remember that you can look at the Cartographic Visualization notebook for guidance and inspiration.

// copy your county-level visualization from above here and update it

Once you’ve colored the counties by population, here are some stretch goals:


Tool Choice: D3

(Skip this section if you’re using Vega-Lite)

Paste a copy of your D3 chart cell here and update it to incorporate population data.

// paste a copy of the chart code here
// and modify the code to also visualize population data

// You will want to write your own "lookup" data structure or "lookup" function
// where for a given county, we can use "lookup" to retrieve the population count from waPopulation.
// Using your lookup function/data structure may look something like this in the final code:
//  .selectAll(".county")
//  .data(...)
//  .join(...)
//  .attr(...)
// .style("fill", f => color(lookup[f.id].Estimate2022))
// ...

Once your map is coming together, try adding a legend in a cell above the map, using the Legend function from the d3/color-legend library.


Acknowledgements

Thanks to the authors of the following pages, which informed these tasks!

For maps:

For color scales/legends:


Don’t forget to add, commit, and push your exercises to your GitLab repo!