In my last post, I (deliberately) left off a legend. Let's fix that:

Percent of adults over 25 with at least a bachelor's degree

Legends are tricky.

For Your Warming World, we used a static image and matched colors to it. In this treemap of drug findings for WBUR's Bad Chemistry, I used a flattened list, with span tags in each list item colored to match the chart. It worked, but I don't love it.

For a choropleth, where I'm showing a progression of values, I want a legend that shows that continuity, and I want to make the colors easy to see and compare. And I want to make it responsive, like the map.

How to make a responsive legend

  1. Start with a scale
  2. Use HTML (instead of SVG)
  3. Use preset, relative sizes

Start with a scale

For the map, I used D3's quantize scale and colorbrewer.

var colors = d3.scale.quantize()
    .range(colorbrewer.Greens[7]);

Simple enough. This breaks the input domain (which I'll set after loading my data) into discrete segments, based on the size of the output range. For a choropleth, it makes for clear distinctions in color.

D3's scales have an added bonus: They can be inverted. In the case of quantize scales, calling scale.invertExtent returns an array of [min, max].

Now you have data for a legend. Mike Bostock shows us the way.

Use HTML

Mike's example uses SVG. I like SVG. But in this case, HTML is a little easier (opinions may differ here).

I decided to stick with a flattened list, but instead of using nested span tags, I figured out a trick (or an ugly hack; you decide). I use borders.

var legend = d3.select('#legend')
  .append('ul')
    .attr('class', 'list-inline');

var keys = legend.selectAll('li.key')
    .data(colors.range());

keys.enter().append('li')
    .attr('class', 'key')
    .style('border-top-color', String)
    .text(function(d) {
        var r = colors.invertExtent(d);
        return formats.percent(r[0]);
    });

This requires a little extra CSS:

#legend {
    padding: 1.5em 0 0 1.5em;
}

li.key {
    border-top-width: 15px;
    border-top-style: solid;
    font-size: .75em;
    width: 10%;
    padding-left: 0;
    padding-right: 0;
}

Each li gets a 15-pixel top border, colored according to the scale. This alleviates the problem of sizing multiple elements at different browser widths, and it keeps colors and values together.

Use preset, relative sizes

There's one problem with this approach. HTML elements will set their own widths based on their content. In this case, by default, legend items with more digits end up wider. That might give the impression of a larger range. I don't want your browser lying on my behalf. (Note that if you're using a threshold scale, widths should be different.)

The fix for this is to set a width in CSS, as I did above.

I also used percents (in this case, 10%, but adjust based on cardinality), so the legend and each item adjusts with the browser width.

Now we have a responsive legend.

blog comments powered by Disqus