D3 Tips and Tricks v4

Wednesday, 9 January 2013

How to rotate the text labels for the x Axis of a d3.js graph


The following post is a portion of the D3 Tips and Tricks document which it free to download. To use this post in context, consider it with the others in the blog or just download the pdf  and / or the examples from the downloads page:-)
--------------------------------------------------------

The observant reader will recall the problem we had observed earlier when increasing the number of ticks on our x axis to 10. The effect had been to produce a large number of x axis ticks (actually 19) but they had run together and become unreadable.
We postulated at the time that an answer to the problem might be to rotate the text to provide more space. Well, it's about time we solved that problem.

The answer I found most usable was provided by Aaron Ward on Google Groups
(https://groups.google.com/forum/#!msg/d3-js/CRlW0ISbOy4/1sgrE5uS5ysJ)

Now, I'll put a bit of a caveat on this solution to the rotating axis label problem. It looks like it's worked well, but I've only carried out this investigation to the point where I've got something that looks like it's a solution. There may be better or more elegant ways of carrying out the same task, so let Google be your friend if it doesn't appear to be working out for you.

So, starting out with our simple graph example, we should increase the number of ticks on the x axis to 10 to get it to highlight our problem as it appears in the previous image.

The first substantive change would be a little housekeeping. Because we are going to be rotating the text at the bottom of the graph, we are going to need some extra space to fit in our labels. So we should change our bottom margin appropriately.
var margin = {top: 30, right: 40, bottom: 50, left: 50},
I found that 50 pixels was sufficient.

The remainder of our changes occur in the block that draws the x axis.
svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis)
        .selectAll("text")  
            .style("text-anchor", "end")
            .attr("dx", "-.8em")
            .attr("dy", ".15em")
            .attr("transform", "rotate(-65)" );
It's pretty standard until the .call(xAxis) portion of the code. Here we remove the semicolon that was there so that the block continues with its function.

Then we select all the text elements that comprise the x avis with the .selectAll("text"). From this point we are operating on the text elements associated with the x axis. So in effect the following 4 'actions' taken are applied to the text labels.

The .style("text-anchor", "end") line ensures that the text label has the end of the label 'attached to the axis tick. This has the effect of making sure that the text rotates about the end of the date. This makes sure that the text all ends up a uniform distance from the axis ticks.

The 'dx' and 'dy' attribute lines move the end of the text just far enough away from the axis tick so that they don't crowd it and not too far away so that it appears disassociated. This took a little bit of fiddling to get right and you will notice that I've used the 'em' units to get an adjustment if the size of the font differs.
The final action is kind of the money shot.

The transform attribute applies itself to each text label and rotates each line by -65 degrees. I selected -65 degrees just because it looked OK. There was no deeper reason.

The end result then looks like the following;
This was a surprisingly difficult problem to find a solution to that I could easily understand (well done Aaron). So that makes me think that there are some far deeper mysteries to it that I don't fully appreciate that could trip this solution up. But in lieu of that, enjoy!

The above description (and heaps of other stuff) is in the D3 Tips and Tricks document that can be accessed from the downloads page of d3noob.org.

24 comments:

  1. I had a similar experience back in 2012. Here's my stackoverflow post:

    http://stackoverflow.com/questions/11814399/need-help-lining-up-x-axis-ticks-with-bars-in-d3-js-bar-chart

    And javascript fiddle:

    http://jsfiddle.net/MmEjF/

    ReplyDelete
    Replies
    1. Thanks Edward. That's good information to have!

      Delete
  2. Good article, but I have a very weird problem, I'm using nvd3 to show a graph with two sets of data, the user can switch from a set to another, triggering the graph update. Something like this: http://nvd3.org/ghpages/multiBar.html

    After the selection (or any other update of the graph, such as window resizing), the labels are drawn back horizontally, how can I force d3 to rotate labels even after the update?

    Thank you

    ReplyDelete
    Replies
    1. That is a really good question!
      I'm not very familiar with nvd3, so I'm not going to be able to give you a straight answer, but If I was troubleshooting the problem, I would look for the point in the initial drawing of the graph when you set up the labels vertically, you will need to set their attributes specifically (rotation wise (it sounds like you've already done this)). So what you will need to do is perform the same transformation with the appropriate rotation attribute when you update the graph. So if you were taking the update example from here http://www.d3noob.org/2013/02/update-d3js-data-dynamically-button.html, at the point where we call the function updateData(), you would want to select the axis element and make sure that the same rotation transformation is applied. I hope that makes sense.
      I don't know if it will work with your example, because of the differences with nvd3, but if you're still having trouble, post your code onto StackOverflow and ask there. It's an interesting question and I would like to think that there will be someone much smarter than me who will know what to do straight off the bat.
      Good luck.

      Delete
  3. This saved me so much refactoring time. Great solution

    ReplyDelete
    Replies
    1. Great to hear! Thanks for the feedback.

      Delete
  4. I'd like to see the complete code for this but when I download the code library I get errors:

    > XMLHttpRequest cannot load file:///home/dev/temp/d3noob/data/data.tsv. Cross origin requests are only supported for HTTP.

    > Uncaught TypeError: Cannot call method 'forEach' of undefined @ simple-graph-commented.html:63

    > Uncaught NetworkError: A network error occurred. @ d3.v3.js:444

    Chrome 32. Your zip should have a readme that explains how to get the samples running.

    ReplyDelete
    Replies
    1. Hi Justin, Sorry you're having difficulty. The downloads section is a significant weakness with the information I have on d3.js. Deepest apologies. It sounds like you aren't running a web server that will allow you to access the additional file. I kind on rely on folks reading the book in some parts to help them along. I explain a bit here (https://leanpub.com/D3-Tips-and-Tricks/read#leanpub-auto-web-servers). see if that helps any.

      Delete
  5. Hi, Is it possible, rotate the text labels of y-axis in d3.js?

    ReplyDelete
    Replies
    1. Sure is. It's as simple as getting the portion of code that does the rotating in the 'add x axis' part and duplicate it in the 'add y axis part'. Yow will then need to work out the anchor points, spacing and amount of rotation to suit your graph, but this is pretty simple.

      Delete
  6. Good article. I am trying to add a horizontal text below the x axis but it is not being rendered.

    I have added the following code for the xaxis
    svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis)
    .selectAll("text") // added to display tick label at an angle
    .style("text-anchor", "end") // added to display tick label at an angle
    .attr("dx", "-.8em") // added to display tick label at an angle
    .attr("dy", ".15em") // added to display tick label at an angle
    .attr("transform", function(d) {return "rotate(-65)"})
    .append("text") // added to display the label for axis
    .attr("x", width/2) // added to display the label for axis
    .attr("y", 40) // added to display the label for axis
    .style("text-anchor","middle") // added to display the label for axis
    .text("Date"); // added to display the label for axis


    When I remove the code that renders the tick labels at an angle, the x axis label text will be rendered. When I add the code that renders the tick labels at an angle, the x axis label text will not be rendered.

    How will I be able to achieve both (display the tick label at an angle AND display the x axis label horizontally below the slanted tick labels).

    Thanks

    ReplyDelete
    Replies
    1. Apologies for the really late reply. I'm sure that you've figured this out long before now, but I'll answer just in case and if others are interested.
      Just looking at your section of the code it looks like you have almost combined both the addition of the x axis and the label into a single append.
      What you would want to do is to separate these into two distinct blocks similar to that demonstrated here; http://bl.ocks.org/d3noob/e1aa61856118edfa624d

      Delete
  7. Thank you so much for this post...my headache is gone from trying to figure this out!

    ReplyDelete
  8. I know this is an old example but the line...

    .attr("transform", function(d) { return "rotate(-65)" });

    is rather odd.

    You are not accessing the datum or returning a variable to the transform attribute so you could simplify it to.

    .attr("transform", "rotate(-65)");

    ReplyDelete
    Replies
    1. Wow! It sure is odd. That's been there for a couple of years! Not just here, but in the book and probably in the example code samples. Great spotting. I feel suitably humbled that my noobishness has been reinforced. Many thanks I will be correcting directly.

      Delete
    2. And sure enough, it was everywhere. All now changed in the text above, the book and the code samples. Many thanks again.

      Delete
  9. .x.axis text {
    text-anchor: end !important;
    transform: rotate(-45deg);
    }

    ReplyDelete
    Replies
    1. Absolutely! Adjusting the style would be a neater way to go and I would encourage folks to experiment with the code above (Thanks!). For the purposes of learning and experimenting with the rotate transform in d3 (especially as it relates to text), try both.

      Delete