How to Make d3 Charts Responsive

  1. Size and scale SVG elements based on their containers.
  2. Reset scales when the window sizes.
  3. Re-size all the things.

Size and scale SVG elements based on their containers.

var margin = {top: 30, right: 10, bottom: 30, left: 10}
 , width = parseInt(d3.select('#chart').style('width'), 10)
 , width = width - margin.left - margin.right
 , percent = d3.format('%');

// scales and axes
var x = d3.scale.linear()
 .range([0, width])
 .domain([0, .4]); // hard-coding this because I know the data

// ordinal scales are easier for uniform bar heights
// I'll set domain and rangeBands after data loads
var y = d3.scale.ordinal();

var xAxis = d3.svg.axis()
 .scale(x)
 .tickFormat(percent);

Reset scales when the window sizes

We can catch the resize event on window and running a function:

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

function resize() {
    // update width
    width = parseInt(d3.select('#chart').style('width'), 10);
    width = width - margin.left - margin.right;

    // reset x range
    x.range([0, width]);

    // do the actual resize...
}

Resize All Things

Here’s the list again:

  • top axis
  • bottom axis
  • background bars
  • foreground (data) bars
  • labels (maybe)
  • median ticks
d3.select(window).on('resize', resize);

function resize() {
    // update width
    width = parseInt(d3.select('#chart').style('width'), 10);
    width = width - margin.left - margin.right;

    // resize the chart
    x.range([0, width]);
    d3.select(chart.node().parentNode)
        .style('height', (y.rangeExtent()[1] + margin.top + margin.bottom) + 'px')
        .style('width', (width + margin.left + margin.right) + 'px');

    chart.selectAll('rect.background')
        .attr('width', width);

    chart.selectAll('rect.percent')
        .attr('width', function(d) { return x(d.percent); });

    // update median ticks
    var median = d3.median(chart.selectAll('.bar').data(),
        function(d) { return d.percent; });

    chart.selectAll('line.median')
        .attr('x1', x(median))
        .attr('x2', x(median));


    // update axes
    chart.select('.x.axis.top').call(xAxis.orient('top'));
    chart.select('.x.axis.bottom').call(xAxis.orient('bottom'));

}