Layout

Tutorial

D3 is a visualization library, emphasis on library. It provides tools that help you put a visualization together. It is up to you to supply the data and put the parts together. A fundamental aspect of this is laying out the various graphical elements. Figuring out where to place all of the elements and how to make it all line up are critical to a final visualization that looks polished.

In this lab I'll deconstruct the chart below. I'll discuss the various components that make up the chart and explain a convention that makes layout easy with the added benefit of simplifying scales.

The first thing to notice about the sample chart is the grey area where the data is located. This region is called the data region and contains the actual data. The chart is made up of an SVG area of 500x300 pixels. Of that, the data region is just 291x191. The other components of the chart provide context and help you understand the data region. You'll notice that the non-data region is primarily made up of various types of labeling and empty space. We'll call the non-data region areas the margins.

D3 Margin Convention

Mike Bostock, one of D3's primary creators, suggests a helpful convention to use when creating margins. In a nutshell, you create the data region as an SVG group element and then place the margins around it. Often there is quite a bit of trial and error when creating a visualization to make it look good. This includes activities such as making sure none of the components overall and they are aligned in a way that looks good. To facilitate easy experimentation. You can create a margin object that keeps track of the width in pixels of the four margins: "top", "bottom", "left" and "right". By always refering to margin.top it is easy to add more room for the title later by just increasing margin.top instead of changing the code in several places.

var margin = { top: 50, bottom: 60, left: 120, right: 90 };

With the margins given and also the SVG view size, we can compute the size of the data region easily.

var width = svgWidth - margin.left - margin.right;
var height = svgHeight - margin.top - margin.bottom;
var svg = d3.select("#dataRegionExample svg");

// create the data region group
// and move it to the correct location (margin.left, margin.top)
var dataRegion = svg.append("g")
   .attr("transform", "translate(" + margin.left + ", " + margin.top + ")");

// add a backgound to the data region
dataRegion.append("rect")
          .classed("dataRegionBackground", true)
          .attr("width", width)
          .attr("height", height);

The dataRegionBackground CSS looks like:

.dataRegionBackground {
  fill: rgb(240,240,240);
}

Simplified Scales

In the example above, I added outlines to the SVG area to make it clear that the data region only occupies part of the area. One benefit of setting up the data region in this way is that it simplifies scale output ranges. Instead of leaving room for margins in the scales:

var xScale = d3.scale.linear()
               .range([120, svgWidth-90]);

I can just specify the range from 0 to width:

var xScale = d3.scale.linear()
               .range([0, width]);

Same goes for the yScale. Now any changes to the margins do not require changes to the scale code, it automatically adapts. Here is the actual scale code with data points used in the original chart.

var data = [40, 50, 60, 53, 45, 55];
var data2 = [56, 58, 52, 49, 42, 40];

var xScale = d3.scale.linear()
               .domain([0,data.length+1])
               .range([0,width]);

var yScale = d3.scale.linear()
               .domain([35,65])
               .range([height, 0]);

Centered Text

Another benefit is that it is easy to place centered text around the data region. Horizontal centering is as simple as width/2 + margin.left. Vertical centering is height/2 + margin.top. If you are using text be sure to use text-anchor: "middle" when centering horizontally. These two ideas were used to place the chart's title, subtitle and axis labels.

svg.append("text")
   .attr("x", margin.left + width/2)
   .attr("y", 20)
   .classed("chartTitle", true)
   .text("Chart Title");

svg.append("text")
   .attr("x", margin.left + width/2)
   .attr("y", 40)
   .classed("chartSubTitle", true)
   .text("Chart Sub-Title");

svg.append("text")
   .attr("x", margin.left + width/2)
   .attr("y", height + margin.top + margin.bottom - 20)
   .classed("xAxisLabel", true)
   .text("X-Axis Label");

svg.append("text")
   .attr("x", 0)
   .attr("y", margin.top + height/2)
   .classed("yAxisLabel", true)
   .text("Y-Axis Label");

As a quick aside, whenever you find yourself writing highly duplicated code like the above, take a moment and see if you can roll it into a simple function like the one below.

function addText(svg, x, y, cssClass, text) {
  svg.append("text")
     .attr("x", x)
     .attr("y", y)
     .classed(cssClass, true)
     .text(text);
}

var horizontalCenter = margin.left + width/2;
var verticalCenter = margin.top + height/2;
addText(svg, horizontalCenter, 20, "chartTitle", "Chart Title");
addText(svg, horizontalCenter, 40, "chartSubTitle", "Chart Sub-Title");
addText(svg, horizontalCenter, 300 - 20, "xAxisLabel", "X-Axis Label");
addText(svg, 0, verticalCenter, "yAxisLabel", "Y-Axis Label");

Legend

Finally, in this case the legend is added to the right of the chart. To facilitate easy movement of the legend I put it in it's own SVG group. I also used the same CSS classes used to style the actual data points. If the styles of the data points are updated, the legend is automatically updated as well.

var legend = svg.append("g")
   .attr("transform", "translate(" + (margin.left + width + 10) + ", " + (margin.top + height/2)+ ")");

legend.append("circle")
      .classed("dataPoint", true)
      .attr("r", 3)
      .attr("cx", 5)
      .attr("cy", 0);

legend.append("text")
      .attr("x", 15)
      .attr("y", 5)
      .text("Series 1");

legend.append("circle")
      .classed("dataPoint2", true)
      .attr("r", 3) 
      .attr("cx", 5)
      .attr("cy", 30);

legend.append("text")
      .attr("x", 15)
      .attr("y", 35)
      .text("Series 2");

But...

I'll talk about how to use D3 to create chart axes in the next lab, and I've alread covered the mechanics of putting the actual data in the data region. You should now have a good grasp of how to layout the data region and also how to add labels and a legend.

References

Bostock, Mike. Margin Convention. 2012. http://bl.ocks.org/mbostock/3019563

Quiz

Things to do

  1. The chart below

Extra Credit

Change the code on the left. Once you've made a change, the page will render on the right.