I spent an hour this weekend updating this site to Bootstrap 3, making the site responsive (and mobile first!) by default. That means the next step is to make visualizations that can handle a browser of any size, too.

Let's make maps and charts that resize automatically and work everywhere.

Part One: Maps

D3 actually makes this fairly easy.

  1. Start with a responsive framework.
  2. Size and scale SVG elements based on their containers.
  3. Resize when the window size changes.

Here's a working example, using data from the new CensusReporter. In this case, I'm comparing the percentage of adults over 25 years old with a bachelor's degree or higher.

1. Start with a responsive framework

I'm using Bootstrap, but Foundation or any other framework will do fine. Or roll your own. The point is that you want everything responding to changes in the viewport size. This makes the next step easier.

2. Size and scale SVG elements based on their containers.

I used to hard-code dimensions into my maps and charts. Often, this was already duplicating what I'd done in CSS, and it anchored the chart to a certain viewport size. Not on a 13-inch MacBook Pro? Tough.

Here's what I have at the top of my code now:

var margin = {top: 10, left: 10, bottom: 10, right: 10}
  , width = parseInt(d3.select('#map').style('width'))
  , width = width - margin.left - margin.right
  , mapRatio = .5
  , height = width * mapRatio;

The width variable is most important here. I set it based on the computed style of the container element, in this case #map. Now, use this to set the scale on your map projection:

var projection = d3.geo.albersUsa()
    .scale(width)
    .translate([width / 2, height / 2]);

var path = d3.geo.path()
    .projection(projection);

This gets us 90% of the way to responsiveness, because the map will be rendered at the right size on load. Pull this up on your phone, and it'll fit. Same for your giant desktop.

But it won't adjust if you rotate your phone (or tablet). As much as web developers are the only people who test the responsiveness of sites by resizing the browser, real users really do turn their devices sideways (even by accident). And it's no fun reloading a bunch of GIS data on your phone.

3. Resize when the window size changes.

This turns out to be simpler to solve than I expected. You need to catch window.onresize, and you need to resize the map. Do this by recalculating the container size, adjusting the projection's scale, then re-running the function that originally drew the map. Here's the code:

d3.select(window).on('resize', resize);

function resize() {
    // adjust things when the window size changes
    width = parseInt(d3.select('#map').style('width'));
    width = width - margin.left - margin.right;
    height = width * mapRatio;

    // update projection
    projection
        .translate([width / 2, height / 2])
        .scale(width);

    // resize the map container
    map
        .style('width', width + 'px')
        .style('height', height + 'px');

    // resize the map
    map.select('.land').attr('d', path);
    map.selectAll('.state').attr('d', path);
}

The reason the last two lines work is D3's data-binding. Remember, each selection is bound to an array of data, and each datum is stored on a corresponding element as __data__. This means you can access it later by simply reselecting the elements, and you can use it by re-setting the d attribute.

The map above is obviously missing things: There's no legend, first and foremost. I'll get to that in my next post. The interaction is also pretty sparse and totally mouse-driven (no hovering on mobile).

This also might not be the best way to present the data, so I'll circle back and do a responsive chart in post three of this series.

blog comments powered by Disqus