In my last post, I (deliberately) left off a legend. Let's fix that:
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
- Start with a scale
- Use HTML (instead of SVG)
- 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.