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:
- Task 1: Render the County Shapes in a Map Visualization (~30 min.) – use the provided TopoJSON dataset to visualize the selected counties.
- Task 2: Add the Population Data to your Map Visualization (~30 min.) – use the provided population data to create a complete map visualization!
You have a choice of visualization tool for this exercise:
- You can use Vega-Lite, building directly off the methods in the Cartographic Visualization notebook reading, or
- You can use D3, building on any prior experience or initial readings in the D3 course textbook.
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?!
- Write your explanation here.
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:
- Interactive US State Map Example
- CA State Choropleth Map Example
- CA Plane Zone 3 Chrolopleth Map Example
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?!
- Write your explanation here.
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:
- Add a
lookup
transform to add population estimates to the data. You want to use theid
property of the county GeoJSON data to match against theGEOID_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 thelookup
transform documentation. - Add a
color
encoding to visualize looked-up population estimates. Add acolor
property within theencoding
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:
- Refine the color encoding, for example by using a different color scheme. Next, try changing from a continuous scale to a discretizing scale, for example using the
quantile
scale type with either 5 or 7 quantiles. (Hint: for discretizing color schemes, use a scalescheme
such as{name: 'blues', count: 7}
). - Try adjusting the legend orientation (
orient
) to the top-left corner of the map. - Add tooltips, for example using a
tooltip
channel within theencoding
block. Can you update your lookup function to also retrieve theCountry
name field and include it in the tooltip alongside a population estimate?
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:
- https://observablehq.com/@jeantimex/us-state-county-map
- https://observablehq.com/@parkerziegler/choropleths-four-ways
- https://observablehq.com/d/9d20c5be5e71a8e4
For color scales/legends:
- [https://vega.github.io/vega/docs/schemes/](Vega Color Schemes)
- https://observablehq.com/@d3/color-schemes
- https://observablehq.com/@d3/quantile-quantize-and-threshold-scales
- https://observablehq.com/@d3/color-legend
Don’t forget to add
, commit
, and push
your exercises to your GitLab repo!