D3 Tips and Tricks v4

Friday, 21 December 2012

Setting up axes in d3.js

The following post is a portion of the D3 Tips and Tricks document which it free to download from the main page.
--------------------------------------------------------------------------

So we come to our next piece of code;
var xAxis = d3.svg.axis().scale(x)
    .orient("bottom").ticks(5);

var yAxis = d3.svg.axis().scale(y)
    .orient("left").ticks(5);
I've included both the x and y axes because they carry out the formatting in very similar ways. And it's worth noting that this is not the point where the axes get drawn. That occurs later in the piece where the data.tsv file has been loaded as 'data'.

D3 has it's own axis component that aims to take the fuss out of setting up and displaying the axes. So it includes a number of configurable options.

Looking first at the x axis;
var xAxis = d3.svg.axis().scale(x)
    .orient("bottom").ticks(5);
The axis function is called with d3.svg.axis() and then the scale is set using the x values that we setup in the scales, ranges and domains section using .scale(x). Then a curious thing happens, we tell the graph to orientate itself to the bottom of the graph .orient("bottom"). Now if I tell you that "bottom" is the default setting, then you could be forgiven for thinking that technically, we don't need to specify this since it will go there anyway, but it does give us an opportunity to change it to to “top” to see what happens;
Well, I hope you didn't see that coming, because I didn't. So, it would transpire that what we're talking about there is the orientation of the values and ticks about the axis line itself. Ahh... Ok. So, useful if your x axis is at the top of your graph, but for this one, not so useful.

All right, he next part (.ticks(5)) sets the number of ticks on the axis. Hopefully you just did a quick count across the bottom of the previous graph and went “Yep, five ticks. Spot on”. Well done if you did, but there's a little bit of a sneaky trick up D3's sleeve with the number of ticks on a graph axis.

For instance, here's what the graph looks like when the .ticks(5) value is changed to .ticks(4).
Eh? Hang on. Isn't that some kind of mistake? There's still five ticks. Yep sure is. But wait... we can keep dropping the ticks value till we get to two and it will still be the same. At .ticks(2) though, we finally see a change.
How about that? At first glance that just doesn't seem right, then you have a bit of a think about it and you go “Hmm... When there were 5 ticks, they were separated by a week each, and that stayed that way till we got to a point where it could show a separation of a month.”.

So, what's going on here is that D3 is making a command decision for you as to how your ticks should be best displayed. This is great for simple graphs and indeed for the vast majority of graphs. And like all things D3, if you really need to do something bespoke and fancy, it'll let you if you know enough.

The following is the list of time intervals that D3 will consider when setting automatic ticks on a time based axis;
  • 1-, 5-, 15- and 30-second.
  • 1-, 5-, 15- and 30-minute.
  • 1-, 3-, 6- and 12-hour.
  • 1- and 2-day.
  • 1-week.
  • 1- and 3-month.
  • 1-year.
Just for giggles have a think about what value of ticks you will need to increase to until you get D3 to show more than five ticks.
Hopefully you won't sneak a glance at the following graph before you come up with the right answer.
Yikes! The answer is 10! And then when it does, the number of ticks is so great that they jumble all over each other. Not looking to good there. However, you could rotate the text (or perhaps slant it) and it could fit in (that must be the topic of a future how-to). You could also make the graph longer if you wanted, but of course that is probably going to create other layout problems. So think about your data and presentation as a single entity.

The code that formats the y axis is pretty similar;
var yAxis = d3.svg.axis().scale(y)
    .orient("left").ticks(5);

We can change the orientation to "right" if we want, but it won't be winning any beauty prizes.
Nope. Not a pretty sight.

So what about the umber of ticks? Well this scale is quite different to the x axis. Formatting the dates using logical separators (weeks, months) was tricky, but with standard numbers, it should be a little easier. In fact, there's a better than even chance that you've already had a look at the y axis and seen that there are 6 ticks there already when the script is asking for 5 :-)

And without too much fussing around, we can lower the tick number to 4 and we get a logical result.
But we need to raise the count to 10 before we get more than 6.



12 comments:

  1. What are x,y the parameters to the scale call?

    ReplyDelete
  2. If I understand your question correctly, I think it's because they represent the already scaled range that was defined earlier in the code (http://www.d3noob.org/2012/12/setting-scales-domains-and-ranges-in.html) That way, the axes will scale to the correct extent. Hopefully that makes sense.

    ReplyDelete
  3. Exactly what i want. thank u...

    ReplyDelete
  4. Thank you very-very much!

    ReplyDelete
  5. Thanks for your great posts! In the 4th picture down, where the labels crash into each other, is there anyway to fix that? I'm using a responsive design where the number of ticks is calculated as a division of browser width. But as soon as it goes wider than about 1300px it puts in fully spelled out month names (like October) and the labels crash. Your beautiful tooltip code of course works perfectly :) Thanks for any suggestions! URL below:
    http://airwwwave.com/inter/argentina-reserves-v2/index.html

    ReplyDelete
  6. Actually, I figured out a solution, maybe not the best solution, but it works:

    var margin = {top: 60, right: 60, bottom: 120, left: 60};
    var width = parseInt(d3.select("#graph").style("width")) - (margin.left + margin.right);
    var numberOfXTicks = Math.max(width / 50, 2);

    if (numberOfXTicks > 24.18) {
    xAxis.ticks(d3.time.month, 6).tickFormat(d3.time.format("%m" + "/" +"%y"))
    } else if (numberOfXTicks <= 9) {
    xAxis.ticks(d3.time.year, 2).tickFormat(d3.time.format("%Y"))
    } else {
    xAxis.ticks(d3.time.year, 1).tickFormat(d3.time.format("%Y"))
    }

    ReplyDelete
  7. I don't understand. Why does the range has to be changed? Range is set on what the height/width of the chart is for specific axis, and the charts heigh/width stays the same. Is it possible to see a working example, where you set how many ticks you want?

    ReplyDelete
    Replies
    1. If I understand your question properly, the range doesn't have to be changed. The examples above are just for illustration. Your suggestion for an example where we could set ticks on a sliding scale or something similar is genius! use the following information and you should be able to mash up a simple graph with an HTML input to illustrate what you're talking about http://www.d3noob.org/2014/04/using-html-inputs-with-d3js.html. Great suggestion

      Delete
  8. Could it be possible to show values for the first and the last ticks?

    ReplyDelete
    Replies
    1. That's a really good question that when I first read it I thought it would be a simple answer, but I have to say that I don't know what it is! A quick googling (which I assume you have done as well) did not give an answer, but I can see that it's not as straight forward as the question of removing the final tick (which is the common thread). I therefore think that this would be a great question to ask on Stack Overflow where there is a great range of people who would be interested in breaking new ground on an interesting d3.js question. Thanks for the great thought.

      Delete
  9. Is there a way to set distance (or gap) between ticks? Please help.

    ReplyDelete
    Replies
    1. Hmm... This kind of depends on what you're trying to achieve. d3 does an automatic selection of what it considers to be the best interval and from there the spacing is determined by the width of the graph. I suggest that if you have a specific need that d3 isn't satisfying, you could either set ordinal values for the axis possibly. The first thing I would suggest is having a read over the official documentation (https://github.com/mbostock/d3/wiki/SVG-Axes) and you may also get a different perspective from Dashing D3.js (https://www.dashingd3js.com/d3js-axes) or Scott Murray's excellent blogs (http://alignedleft.com/tutorials/d3/axes). Have a good read, experiment a bit and see how you get on. Good luck.

      Delete