Using axes (d3.svg.axis)

We aren’t here to chop wood! Axes is the plural of axis, which we’ll say are those little lines that tell you what’s what on a graph.

Toolset checkin

First, we need to make sure you understand the following not-absolutely-beginner techniques:

  • How to use select to select smaller pieces of your page (or svg), using d3.select('.bar-graph').selectAll('rect') instead of d3.selectAll('rect').
  • Enter and append so you don’t have to type everything out a thousand times
  • Scales because axes are 150% bff with scales

Let’s get to it

Okay, so let’s say we have our nice chart from before. Wouldn’t it be nice if we knew what the heck those lengths meant?

Open example in new window

<svg class="bar"></svg>
<script>
  var states = [ {'name' : 'New York', 'total_area': 54555, 'land_area': 47126, 'p_water': 0.136}, {'name' : 'Texas', 'total_area': 268596, 'land_area': 261232, 'p_water': 0.027},  {'name' : 'Arizona', 'total_area': 113990, 'land_area': 113594, 'p_water': 0.003},  {'name' : 'California', 'total_area': 163695, 'land_area': 155779, 'p_water': 0.048},  {'name' : 'Mississippi', 'total_area': 48432, 'land_area': 46923, 'p_water': 0.031},  {'name' : 'New Jersey', 'total_area': 8723, 'land_area': 7354, 'p_water': 0.157} ];

  // How wide and tall do we want the svg?
  var width = 400;
  var height = 150;
  
  // Set the height and width of the svg through d3
  var svg = d3.select('.bar').attr('height', height).attr('width', width);
  
  // Create a scale
  var bar_scale = d3.scale.linear().domain([0, 300000]).range([0, 400]);
  
  // Draw the rectangles
  var rects = svg.selectAll('rect')
                  .data(states)   // bind our data
                  .enter()            // grab the 'new' data points
                  .append('rect')     // append the bars
                  .style('fill', '#FF0000') // make them red
                  .attr('x', 0)       // set the left hand side to 0
                  .attr('y', function(d, i) { return i * 15; }) // 15 pixels apart
                  .attr('height', 10) // each bar is 10 pixels tall
                  .attr('width', function(d) { 
                    return bar_scale(d['total_area']) // width = total area
                  });
</script>

Open example in new window

The answer is yes, and the answer is axes.

The first thing we need to do is create our axis using d3.svg.axis(). We chain it with two common methods - .orient to say which side the numbers should go on, and .scale(bar_scale) to tell it to listen to our bar scale.

var xAxis = d3.svg.axis()
                    .orient('bottom')
                    .scale(bar_scale);

After you create it, you need to append it to your svg. For god knows what reason you do this using a method called .call:

svg.append('g').call(xAxis);

That g we’re appending is the div of the SVG world - just a random container to hold stuff and keep it separate.

To see why we’d do that, use the Web Inspector to try to grab one of the numbers chart below, then poke around in the HTML. You’ll notice there’s a lot of stuff floating around in there.

Open example in new window

<svg class="bar"></svg>
<script>
  var states = [ {'name' : 'New York', 'total_area': 54555, 'land_area': 47126, 'p_water': 0.136}, {'name' : 'Texas', 'total_area': 268596, 'land_area': 261232, 'p_water': 0.027},  {'name' : 'Arizona', 'total_area': 113990, 'land_area': 113594, 'p_water': 0.003},  {'name' : 'California', 'total_area': 163695, 'land_area': 155779, 'p_water': 0.048},  {'name' : 'Mississippi', 'total_area': 48432, 'land_area': 46923, 'p_water': 0.031},  {'name' : 'New Jersey', 'total_area': 8723, 'land_area': 7354, 'p_water': 0.157} ];

  // How wide and tall do we want the svg?
  var width = 400;
  var height = 150;
  
  // Set the height and width of the svg through d3
  var svg = d3.select('.bar').attr('height', height).attr('width', width);
  
  // Create a scale
  var bar_scale = d3.scale.linear().domain([0, 300000]).range([0, 400]);
  
  // Draw the rectangles
  var rects = svg.selectAll('rect')
                  .data(states)   // bind our data
                  .enter()            // grab the 'new' data points
                  .append('rect')     // append the bars
                  .style('fill', '#FF0000') // make them red
                  .attr('x', 0)       // set the left hand side to 0
                  .attr('y', function(d, i) { return i * 15; }) // 15 pixels apart
                  .attr('height', 10) // each bar is 10 pixels tall
                  .attr('width', function(d) { 
                    return bar_scale(d['total_area']) // width = total area
                  });

  // CREATING THE AXIS
  var xAxis = d3.svg.axis()
                    .orient('bottom')
                    .scale(bar_scale);

  // ADDING A G ELEMENT, PUTTING THE AXIS IN THE G
  svg.append('g').call(xAxis);
</script>

Open example in new window

The big problem right now is that our axis is all up in our chart. Somehow we need to move the g element to be further down the page!

While you’d assume you’d use x and y to position it, it sure isn’t the case. This next thing is so important I’m putting it on a line by itself:

When positioning a g element, you use the transform attribute. Set it to translate(x, y), with x and y being the coordinates you’d like to position it at.

svg.append('g')
    .attr("transform", "translate(0, 90)");
    .call(xAxis)

They sound pretty similar, right? Don’t worry, I always forget which is which. Let’s try it out:

Open example in new window

<svg class="bar"></svg>
<script>
  var states = [ {'name' : 'New York', 'total_area': 54555, 'land_area': 47126, 'p_water': 0.136}, {'name' : 'Texas', 'total_area': 268596, 'land_area': 261232, 'p_water': 0.027},  {'name' : 'Arizona', 'total_area': 113990, 'land_area': 113594, 'p_water': 0.003},  {'name' : 'California', 'total_area': 163695, 'land_area': 155779, 'p_water': 0.048},  {'name' : 'Mississippi', 'total_area': 48432, 'land_area': 46923, 'p_water': 0.031},  {'name' : 'New Jersey', 'total_area': 8723, 'land_area': 7354, 'p_water': 0.157} ];

  // How wide and tall do we want the svg?
  var width = 400;
  var height = 150;
  
  // Set the height and width of the svg through d3
  var svg = d3.select('.bar').attr('height', height).attr('width', width);
  
  // Create a scale
  var bar_scale = d3.scale.linear().domain([0, 300000]).range([0, 400]);
  
  // Draw the rectangles
  var rects = svg.selectAll('rect')
                  .data(states)   // bind our data
                  .enter()            // grab the 'new' data points
                  .append('rect')     // append the bars
                  .style('fill', '#FF0000') // make them red
                  .attr('x', 0)       // set the left hand side to 0
                  .attr('y', function(d, i) { return i * 15; }) // 15 pixels apart
                  .attr('height', 10) // each bar is 10 pixels tall
                  .attr('width', function(d) { 
                    return bar_scale(d['total_area']) // width = total area
                  });

  // CREATING THE AXIS
  var xAxis = d3.svg.axis()
                    .orient('bottom')
                    .scale(bar_scale);

  // ADDING A G ELEMENT, PUTTING THE AXIS IN THE G
  svg.append('g')
      .attr("transform", "translate(0, 90)")
      .call(xAxis);
</script>

Open example in new window

The thing is, it’s really ugly. It’s really ugly, it’s running off the side of the page, and there are probably more things going on, too.

Styling the axis

First, we need to give the g a class of axis so we can style it.

  svg.append('g')
      .attr('class', 'axis')
      .attr("transform", "translate(0, 90)");
      .call(xAxis)

Then we need to create a <style> block and add some styling for the axis class.

<style>
.axis path,
.axis line {
    fill: none;
    stroke: black;
    shape-rendering: crispEdges;
}

.axis text {
    font-family: sans-serif;
    font-size: 11px;
}
</style>

Even though the styling looks a little bit better now, we need to push it in from the edges so the numbers don’t overlap so much.

Open example in new window

<style>
.axis path,
.axis line {
    fill: none;
    stroke: black;
    shape-rendering: crispEdges;
}

.axis text {
    font-family: sans-serif;
    font-size: 11px;
}
</style>
<svg class="bar"></svg>
<script>
  var states = [ {'name' : 'New York', 'total_area': 54555, 'land_area': 47126, 'p_water': 0.136}, {'name' : 'Texas', 'total_area': 268596, 'land_area': 261232, 'p_water': 0.027},  {'name' : 'Arizona', 'total_area': 113990, 'land_area': 113594, 'p_water': 0.003},  {'name' : 'California', 'total_area': 163695, 'land_area': 155779, 'p_water': 0.048},  {'name' : 'Mississippi', 'total_area': 48432, 'land_area': 46923, 'p_water': 0.031},  {'name' : 'New Jersey', 'total_area': 8723, 'land_area': 7354, 'p_water': 0.157} ];

  // How wide and tall do we want the svg?
  var width = 400;
  var height = 150;
  
  // Set the height and width of the svg through d3
  var svg = d3.select('.bar').attr('height', height).attr('width', width);
  
  // Create a scale
  var bar_scale = d3.scale.linear().domain([0, 300000]).range([0, 400]);
  
  // Draw the rectangles
  var rects = svg.selectAll('rect')
                  .data(states)   // bind our data
                  .enter()            // grab the 'new' data points
                  .append('rect')     // append the bars
                  .style('fill', '#FF0000') // make them red
                  .attr('x', 0)       // set the left hand side to 0
                  .attr('y', function(d, i) { return i * 15; }) // 15 pixels apart
                  .attr('height', 10) // each bar is 10 pixels tall
                  .attr('width', function(d) { 
                    return bar_scale(d['total_area']) // width = total area
                  });

  // CREATING THE AXIS
  var xAxis = d3.svg.axis()
                    .orient('bottom')
                    .scale(bar_scale);

  // ADDING A G ELEMENT, PUTTING THE AXIS IN THE G
  svg.append('g')
      .attr('class', 'axis')
      .attr("transform", "translate(0, 90)")
      .call(xAxis);
</script>

Open example in new window

In order to push it in from the edges and give it a little padding, we need to think about it in two steps

  1. Moving it away from the left-hand side
  2. Keeping it away from the right-hand side

We keep it away from the left-hand side by changing the x coordinates of everything - the rects can be positioned at x=20 instead of x=0

.attr('x', 20)       // set the left hand side to 0

And we’ll adjust translate() with the g to start 20 pixels in as well.

.attr("transform", "translate(0, 90)")

To keep it away from the right-hand side we’ll need to adjust the length of the bars and the length of the axis. Luckily, it’s all tied into the scale! Let’s just change the .range on the scale to be 0-360 instead of 0-400.

var bar_scale = d3.scale.linear().domain([0, 300000]).range([0, 360]);

Okay, let’s check in again and see our beautiful, beautiful axis!

Open example in new window

<style>
.axis path,
.axis line {
    fill: none;
    stroke: black;
    shape-rendering: crispEdges;
}

.axis text {
    font-family: sans-serif;
    font-size: 11px;
}
</style>
<svg class="bar"></svg>
<script>
  var states = [ {'name' : 'New York', 'total_area': 54555, 'land_area': 47126, 'p_water': 0.136}, {'name' : 'Texas', 'total_area': 268596, 'land_area': 261232, 'p_water': 0.027},  {'name' : 'Arizona', 'total_area': 113990, 'land_area': 113594, 'p_water': 0.003},  {'name' : 'California', 'total_area': 163695, 'land_area': 155779, 'p_water': 0.048},  {'name' : 'Mississippi', 'total_area': 48432, 'land_area': 46923, 'p_water': 0.031},  {'name' : 'New Jersey', 'total_area': 8723, 'land_area': 7354, 'p_water': 0.157} ];

  // How wide and tall do we want the svg?
  var width = 400;
  var height = 150;
  
  // Set the height and width of the svg through d3
  var svg = d3.select('.bar').attr('height', height).attr('width', width);
  
  // Create a scale
  // don't let it take up all of the svg's space
  var bar_scale = d3.scale.linear().domain([0, 300000]).range([0, 360]);
  
  // Draw the rectangles
  var rects = svg.selectAll('rect')
                  .data(states)   // bind our data
                  .enter()            // grab the 'new' data points
                  .append('rect')     // append the bars
                  .style('fill', '#FF0000') // make them red
                  .attr('x', 20)       // push the bars 20 pixels from the left
                  .attr('y', function(d, i) { return i * 15; }) // 15 pixels apart
                  .attr('height', 10) // each bar is 10 pixels tall
                  .attr('width', function(d) { 
                    return bar_scale(d['total_area']) // width = total area
                  });

  // CREATING THE AXIS
  var xAxis = d3.svg.axis()
                    .orient('bottom')
                    .scale(bar_scale);

  // ADDING A G ELEMENT, PUTTING THE AXIS IN THE G
  // push it 20 pixels to the left, and 90 pixels down
  svg.append('g')
      .attr('class', 'axis')
      .attr("transform", "translate(20, 90)")
      .call(xAxis);
</script>

Open example in new window

More editing

You can also do a lot more with an axis.

  1. Try changing .orient around a little bit - .orient('top'), .orient('left'), .orient('right').
  2. Chain something new on the axis - .ticks. What does .ticks(2) do?
  3. .tickValues is for when you really want to get picky. Try passing .tickValues(1000, 10000, 100000) and see what happens! (make sure you do it after .scale in the chain, though, otherwise it won’t work.)

Open example in new window

<style>
.axis path,
.axis line {
    fill: none;
    stroke: black;
    shape-rendering: crispEdges;
}

.axis text {
    font-family: sans-serif;
    font-size: 11px;
}
</style>
<svg class="bar"></svg>
<script>
  var states = [ {'name' : 'New York', 'total_area': 54555, 'land_area': 47126, 'p_water': 0.136}, {'name' : 'Texas', 'total_area': 268596, 'land_area': 261232, 'p_water': 0.027},  {'name' : 'Arizona', 'total_area': 113990, 'land_area': 113594, 'p_water': 0.003},  {'name' : 'California', 'total_area': 163695, 'land_area': 155779, 'p_water': 0.048},  {'name' : 'Mississippi', 'total_area': 48432, 'land_area': 46923, 'p_water': 0.031},  {'name' : 'New Jersey', 'total_area': 8723, 'land_area': 7354, 'p_water': 0.157} ];

  // How wide and tall do we want the svg?
  var width = 400;
  var height = 150;
  
  // Set the height and width of the svg through d3
  var svg = d3.select('.bar').attr('height', height).attr('width', width);
  
  // Create a scale
  // don't let it take up all of the svg's space
  var bar_scale = d3.scale.linear().domain([0, 300000]).range([0, 360]);
  
  // Draw the rectangles
  var rects = svg.selectAll('rect')
                  .data(states)   // bind our data
                  .enter()            // grab the 'new' data points
                  .append('rect')     // append the bars
                  .style('fill', '#FF0000') // make them red
                  .attr('x', 20)       // push the bars 20 pixels from the left
                  .attr('y', function(d, i) { return i * 15 + 25; }) // 15 pixels apart
                  .attr('height', 10) // each bar is 10 pixels tall
                  .attr('width', function(d) { 
                    return bar_scale(d['total_area']) // width = total area
                  });

  // CREATING THE AXIS
  var xAxis = d3.svg.axis()
                    .orient('top')
                    .scale(bar_scale)
                    .tickValues([0, 50000, 100000, 200000, 300000]);

  // ADDING A G ELEMENT, PUTTING THE AXIS IN THE G
  // push it 20 pixels to the left, and 90 pixels down
  svg.append('g')
      .attr('class', 'axis')
      .attr("transform", "translate(20, 20)")
      .call(xAxis);
</script>

Open example in new window

Cool, right? For more info on axes:

Want to hear when I release new things?
My infrequent and sporadic newsletter can help with that.