William Dickerson
New York, NY, USA
Computer Science, Engineering
& Other Adventures

Responsive Figures with D3 and the SVG viewBox

June 21st, 2016 | by William Dickerson

The SVG viewBox attribute is a great way to make D3 charts responsive to window resizing. Let's look at an example of a bar chart about customers' dessert preference. Here's the data:

var desserts = ["Cookies","Cupcakes","Ice Cream Cones","Muffins"];
var customers = [34,41,52,18];

Let's start with a non-responsive chart. First, we set up our margins and prepare an SVG element to draw on:

var margin = {top: 10, right: 10, bottom: 120, left: 30};
var width = 300 - margin.left - margin.right;
var height = 300 - margin.top - margin.bottom;

//A non-responsive chart area
var chart = d3.select("#chart-div")
                .append("svg")
                .attr("width",width+margin.left+margin.right)
                .attr("height",height+margin.top+margin.bottom)
                .append("g")
                .attr("transform","translate("+
                    margin.left+","+margin.top+")");

Now, we set up the scales and axes:

//Define the x and y scales
var x = d3.scale.ordinal()
          .domain(desserts)
          .rangePoints([0,width],1);         
var y = d3.scale.linear()
          .domain([0,60])
          .range([height,0]); 

//Define the x and y axes
var xAxis = d3.svg.axis()
              .scale(x)
              .orient("bottom");
var yAxis = d3.svg.axis()
              .scale(y)
              .orient("left");

Finally, add the axes and bars to our chart area:

//Add the x axis, rotate the labels
chart.append("g")
     .attr("transform", "translate(0,"+height+")")
     .call(xAxis)
     .selectAll("text")
     .attr("transform", "rotate(-65)")
     .style("text-anchor", "end");

//Add the y axis
chart.append("g")
     .call(yAxis);

//Add the bars
chart.selectAll("bars")
     .data(desserts).enter()
     .append("rect")
     .attr("x",function (d) { return x(d)-20; })
     .attr("y",function (d,i) { return y(customers[i]); })
     .attr("height",function (d,i) { return height-y(customers[i]); })
     .attr("width",50);

Here's our non-responsive chart in a JSFiddle. Notice the blue border around the div element, and notice that the chart doesn't change if you resize the window.

Responding to width resizes

Now the fun part! To make our chart responsive, we just need to change a few lines in our definition of the chart area:

//A responsive chart area
var chart = d3.select("#chart-div")
                .append("svg")
                .attr("width","100%")
                .attr("viewBox","0 0 "+
                    (width+margin.left+margin.right)+
                    " "+
                    (height+margin.top+margin.bottom) )
                .append("g")
                .attr("transform","translate("+
                    margin.left+","+margin.top+")");

Now we're defining the chart area to be the width of our div. Sometimes that width might be 100 pixels, and sometimes it might be 800 pixels. The viewBox attribute tells the browser that--no matter the actual width of the chart area--we want to work with a single coordinate system. Our coordinate system goes from 0 to width + margin.left + margin.right in the x direction, and from 0 to height + margin.top + margin.bottom in the y direction.

Here it is in a JSFiddle. Notice how the chart responds to width resizes, but how it will still outgrow the height of our div.

Responding to width AND height resizes

What happens when we set both the width and height of chart to be 100% or our div? By default, the browser will preserve the aspect ratio of the drawing, so you will get the largest possible chart that fits into both the width and height of the div. See it in this fiddle:

Sometimes we may want our chart to skew in order to fill the entire div. In that case, we just add .attr("preserveAspectRatio","none") to our definition of chart. Here it is in a fiddle:

Although our example looked at a bar chart, the viewBox technique works for many types of figures generated with D3.

Here's a great tutorial on the SVG viewBox: http://jonibologna.com/svg-viewbox-and-viewport/