Faceting a Choropleth


I’m a big fan of Plot but one thing I can’t figure out is how to facet a choropleth. For instance, take the Choropleth Plot example: Plot: Choropleth / Observable | Observable and imagine that you have a yearly time series of unemployment data. Is there an obvious way to facet on year?

The only way I can figure out how to do it would be to create a geometry object for each county-year combination, which seems excessive because it would mean making many copies of the same geometries.

Currently, I work around this by just creating my own faceted layout using something like this approach: Facet wrap / Toph Tucker | Observable.

Thanks for any help!

Have you taken a look at the Plot geo documentation page? There is an example halfway down the page.

You don’t really duplicate the features, as JavaScript does a good job of using light references to the same object.

Here’s a light way to do a faceted choropleth:

(I made random time series, so…)


Excellent, @Fil. I had not realized you could specify a Geo mark that way. I’d suggest showing this way of constructing a choropleth (faceted or not) in the docs or as an example (or maybe I’ve just missed this). This seems like a more natural way of making a choropleth than embedding the data in the feature properties or constructing a Map as a fips based data lookup.

Good point too about the objects not really being copied.

@Cobus I saw that example, but it appears to assume that your geodata and your “other” data are already merged, which I think is an odd assumption for the docs given that the tutorials and templates assume you are starting from a “blank” set of geodata (like the U.S. Map template) to which you will bringing, say, a csv. Unless I’m missing something, the method suggested by that example would require a user to either pull in another library to merge their data to the feature properties or to construct a new geoJSON object by hand.

Also I would suggest that the example is confusing in that it references a walmart dataset that is different from the one in other examples. For example, I might expect to be able to copy and paste that example into this notebook and have it work: Plot: Map small multiples / Observable | Observable but I’m pretty sure it doesn’t.

1 Like

FWIW - here is a version of that example that might have made more sense to me, and used the geometry option.

  margin: 0,
  padding: 0,
  projection: "albers",
  fy: {interval: "10 years"},
  marks: [
    Plot.geo(statemesh, {strokeOpacity: 0.2}),
    Plot.geo(walmarts, {
      geometry: d=>  ({
        type: "Point",
        coordinates:[d.longitude, d.latitude]
      fy: "date", r: 1.5, fill: "blue"}),
    Plot.axisFy({frameAnchor: "top", dy: 30, tickFormat: (d) => `${d.getUTCFullYear()}’s`})

Thanks for sharing this. I do agree that it would be useful to be a bit more explicit about what the walmarts data looks like.

For point data you can use Plot.dot directly — no need to construct a geometry for Plot.geo :slight_smile:

Do you by any chance have an interesting dataset that could be used to demonstrate a faceted choropleth? We could use a good example.

I like this approach as it results in simpler and easier to manage code than squeezing attributes into the geoJSON properties.

However, I’ve come across one problem: In the normal approach of supplying a geoJSON to Plot.geo(), one can encase its options object inside Plot.centroid to get tooltips or point-based symbols. But this doesn’t appear to work if one uses your suggested geometry element approach.

Is there way to use Plot.centroid (which is nice because it is so concise), or would you have to generate centroid coordinates explicitly with, say, d3.polygonCentroid()?

The centroid transform supports the geometry option. Can you share an example of it not working?

Reassuring to know it should be possible. Most likely a misunderstanding somewhere on my part.

Here is a (non) working example illustrating the problem.

(You can skip directly to the problem at the bottom of the document, but I’ve tried to explain my workings in the content above it).

Thanks for the example, Jo. You can fix temporarily by re-adding it to the options.

  width: 900,
  height: 320,
  projection: londonProj,
  fx: { label: null },
  marks: [
    Plot.geo(carAccessTidy, {
        fx: "year",
        geometry: "geometry",
        fill: "carAccess",
        opacity: 0.7,
        tip: true
      geometry: "geometry" // ✨

A more generic fix in fix centroid/geocentroid with tips by Fil · Pull Request #2086 · observablehq/plot · GitHub


Thanks Fil, both for the workaround and fix. Glad I wasn’t doing something stupid.

1 Like

Hi Fil,

This isn’t the dataset that generated this question, but it is one I had on hand and that is publicly available: Plot Faceted Choropleth / Evan Galloway | Observable.

Thanks again for your always excellent help.


Thanks! There is a small difference between the two approaches that we have to be aware of, wrt non-available data. When you map “from the features” you can style N/A as grey with, say, specifying the unknown option of the color scale to be gray. When you map from the data, every feature must be present in the dataset (and in each facet), otherwise you get holes in the map. (The problem can be solved by different means: by laying out a gray background on the whole map, or by making a full join of some sort.)

1 Like

Thanks! Good point. I added your note to the notebook.