Raspberry Pi Pico Tips and Tricks

Thursday, 3 January 2013

Adding grid lines to 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:-)
--------------------------------------------------------

Grid lines are an important feature for some graphs as they allow the eye to associate three analogue scales (the x and y axis and the displayed line).

There is currently a tendency to use graphs without grid lines online as it gives the appearance of a 'cleaner' interface, but they are still widely used and a necessary component for graphing.

This is what we're going to draw;
Like pretty much everything in this document, the clever parts of this are not my work. I've simply used other peoples cleverness to solve my problems. In this case I think the source of this solution came from the good work of Justin Palmer in his excellent description of the design of a line graph here 

http://dealloc.me/2011/06/24/d3-is-not-a-graphing-library.html. However, in retrospect when I've looked back, I'm not sure if I got this right (as I did this quite a while ago when I was less fastidious about noting my sources). In any case, Justin's work is excellent and I heartily recommend it, and here is my implementation of what I think is his work :-)

So, how to build grid lines?

Well what we're going to do is to use the axis function to generate two more axis elements (one for x and one for y) but for these ones instead of drawing the main lines and the labels, we're just going to draw the tick lines. Really long ticklines.

To create then we have to add in 3 separate blocks of code.
  1. One in the CSS section to define what style the grid lines will have.
  2. One to define the functions that generate the grid lines. And...
  3. One to draw the lines.

The grid line CSS

This is the total styling that we need to add for the tick lines;
.grid .tick {
    stroke: lightgrey;
    opacity: 0.7;
}
.grid path {
      stroke-width: 0;
}
.grid .tick {
    stroke: lightgrey;
    opacity: 0.7;
}
.grid path {
      stroke-width: 0;
}
Just add this block of code at the end of the current CSS that is in the simple graph template (just before the </style> tag).

The CSS here is done in two parts.

The first portion sets the line colour (stroke) and the opacity (transparency) of the lines.
stroke: lightgrey;
    opacity: 0.7;
The colour is pretty standard, but in using the opacity style we give ourselves the opportunity to use a good shade of colour (if grey actually is a colour) and to juggle the degree to which it stands out a little better.
stroke-width: 0;
Now it might seem a little weird to be setting the stroke width to zero, but if you don't (and we remove the style) this is what happens;
If you look closely (compare with the previous picture if necessary) the main lines for the axis have turned thicker. The stroke width style is obviously adding in new (thicker) axis lines and we're not interested in them at the moment. Therefore, if we set the stroke width to zero, we get rid of the problem.

Define the grid line functions

We will need to define two functions to generate the grid lines and they look a little like this;
function make_x_axis() {        
    return d3.svg.axis()
        .scale(x)
         .orient("bottom")
         .ticks(5)
}

function make_y_axis() {        
    return d3.svg.axis()
        .scale(y)
        .orient("left")
        .ticks(5)
}
Each function will carry out it's configuration when called from the later part of the script (the drawing part).
A good spot to place the code is just before we load the data with the d3.tsv  
//  <== Put the functions here!
// Get the data
d3.tsv("data/data.tsv", function(error, data) {
    data.forEach(function(d) {
        d.date = parseDate(d.date);
        d.close = +d.close;
    });
Both functions are almost identical. They give the function a name (make_x_axis and make_y_axis) which will be used later when the piece of code that draws the lines calls out to them.

Both functions also show which parameters each will be fed back to the drawing process when called. Both make sure that is uses the d3.svg.axis function and then they set individual attributes which make sense.
The make sure they've got the right axis (.scale(x) and .scale(y)).

They set the orientation of the axes to match the incumbent axes (.orient("bottom") and .orient("left")).

And they set the number of ticks to match the number of ticks in the main axis (.ticks(5) and .ticks(5)). You have the opportunity here to do something slightly different if you want. For instance, if we think back to when we were setting up the axis for the basic graph and we messes about with seeing how many we could get to appear. If we increase the number of ticks that appear in the grid (lets say to .ticks(30) and .ticks(10))) we get the following;
So the grid lines can now show divisions of 50 on the y axis and per day on the x axis :-)

Draw the lines

The final block of code we need is the bit that draws the lines.
svg.append("g")         
        .attr("class", "grid")
        .attr("transform", "translate(0," + height + ")")
        .call(make_x_axis()
            .tickSize(-height, 0, 0)
            .tickFormat("")
        )

    svg.append("g")         
        .attr("class", "grid")
        .call(make_y_axis()
            .tickSize(-width, 0, 0)
            .tickFormat("")
        )
The first two lines of both the x and y axis grid lines code above should be pretty familiar by now. The first one appends the element to be drawn to the group "g". the second line (.attr("class", "grid")) makes sure that the style information set out in the CSS is applied.

The x axis grid lines portion makes a slight deviation from conformity here to adjust its positioning to take into account the coordinates system .attr("transform", "translate(0," + height + ")").

Then both portions call their respective make axis functions (.call(make_x_axis() and .call(make_y_axis()).
Now comes the really interesting bit.

What you will see if you go to the D3 API wiki (https://github.com/mbostock/d3/wiki/SVG-Axes#wiki-tickSizeis that for the .tickSize function, the following is the format.
  • axis.tickSize([major[​[, minor], end]])
That tells us that you get to specify the size of the ticks on the axes, by the major ticks, the minor ticks and the end ticks (that is to say the lines on the very end of the graph which in the case of the example we are looking at aren't there!).

So in our example we are setting our major ticks to a length that corresponds to the full height or width of the graph. Which of course means that they extend across the graph and have the appearance of grid lines! What a neat trick.

Something I haven't done before is to see what would happen if I included the tick lines for the minor and end ticks. So here we go :-).
Darn! Disappointment. We can see a minor tick line for the y axis, but nothing for the x axis and nothing on the ends. Clearly I will have to run some experiments to see what's going on there (later).

The last thing that is included in the code to draw the grid lines is the instruction to suppress printing any label for the ticks;
.tickFormat("")

After all, that would become a bit confusing to have two sets of labels. Even if one was on top of the other. They do tend to become obvious if that occurs (they kind of bulk out a bit like bold text).

And that's it. Grid lines!

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.  

23 comments:

  1. I am interested in major / minor gridlines. I Think you have to use: # axis.tickSubdivide([count]) to see minor ticks (ie gridlines). I am a noob so still investigating.

    ReplyDelete
  2. Hi, thanks for the useful tutorial.
    I would like to translate the vertical gridlines along the x axis. I've tried to use the transition on the element returned from the call to svg.append, but after the first update the gridline disappears.
    It would be great if you could add a section to achieve this behavior.

    Thanks!

    ReplyDelete
    Replies
    1. That's an interesting problem. Are you able to post the details and perhaps sample code on Stack Overflow?

      Delete
  3. very useful tutorial.

    I have created one bubble chart. Added grid lines using this tutorial. But the grid lines are appearing infront of bubbles. Any guess what could be the problem or you have any solution to this?
    please help.

    ReplyDelete
    Replies
    1. Ahh yes, the order that you put your 'append' blocks of code for the grids are after the one for the lines. Just change the order so that the appends for the grid lines come before the lines and you should be fine. If that doesn't work post your code onto Stack Overflow or github and I can take a look. But I think this will solve the problem. Good luck.

      Delete
  4. Thanks! This was perfect.

    ReplyDelete
  5. I'm quite certain this is the wrong place to ask this, but I'm not sure where the right place is. Feel free to point me elsewhere:

    How can I label the x-axis origin and right-hand extreme in this example? (So that it explicitly says March 25 and the ending date on those tick marks)

    ReplyDelete
    Replies
    1. No, I think this is a good place to ask :-).
      Unfortunately I don't know the answer off the top of my head. Have a search on stack overflow and the google groups for d3js and if you come up with nothing drop me a reminder here and I'll dig into it in a week or so (little busy just at the moment sorry). Good question.

      Delete
    2. I couldn't help myself and I had a quick look. This would be a start... http://stackoverflow.com/questions/14708802/d3js-axis-dates-start-and-end-value-only It's not the answer you REALLY want, but it would do what you want in conjunction with the current code. However, there will be a better way.

      Delete
    3. Right, I was aware of those methods, but I wanted to preserve the automagic of the axis generator while simply adding the endpoints. And I asked only because my searches have come up nil so far :)

      I'm dealing with diverse data sets and I didn't want to limit or explicitly specify the number of ticks or write my own function to replicate and extend the axis generator.

      Delete
    4. OK, that's interesting. Looking at .tickValues a little more closely, it should work in conjunction with .ticks (https://github.com/mbostock/d3/wiki/SVG-Axes#wiki-tickValues. I would have therefore have expected to be able to use the tickValues([max/min]) settings for the outer values on the x axis and normal .ticks for the inner. That should preserve the automagic and everyone should go home happy. However, I've run a couple of examples and I can't get the desired effect. Hmm.... This will require more thought.

      Delete
    5. Here's something I've found: when the domains begin and end with values that land exactly where a tick would be, labels are produced for the endpoints of the axis.

      So I wrote a function to find the closest tick values preceding and following my dataset. This means the line won't be butted up against the edges of the graph, which works for me as I wanted to pad my data anyhow.

      Delete
    6. Clever! Good work around.

      Delete
  6. This don't tell me how to add x and y axis

    ReplyDelete
    Replies
    1. No. This post tells you how to add grid lines to a graph. They are similar, but I think you might be looking for something along the lines of https://leanpub.com/D3-Tips-and-Tricks/read#leanpub-auto-setting-up-the-axes. I recommend that you download a copy of the book (https://leanpub.com/D3-Tips-and-Tricks, It's free) and follow along the section on setting up a simple graph which will provide some additional context. Good luck.

      Delete
  7. Hi, how can I dynamically add grid lines for every tick value? My ticks are generated dynamically based on the domain function I wrote.
    Thanks!

    ReplyDelete
    Replies
    1. This technique should still work for adding dynamic ticks. Since it uses the ticks themselves to draw the grid lines you should be good to go. The one thing that might be tripping you up is if the instructions to draw the ticks aren't included in the re-rendered part of your code. Just make sure that they're refreshed on any change and they should work.

      Delete
  8. Hi all i want to add only positive number and 0 on y axis like 0,1,2,3,..n ,

    i am using

    lineChart.yAxis
    .axisLabel('Users').tickFormat(d3.format('d'));


    But did not get what i want can any one helps ?

    Thanks

    ReplyDelete
    Replies
    1. It looks like you're on the right track. Certainly looking at the formatting instructions here https://github.com/mbostock/d3/wiki/Formatting#d3_format it looks right. perhaps the only thing that might be tripping you up could be the use of single speaeh marks ' rather than double speechmarks " surrounding the d.

      Delete
  9. tHAnK yOu vERy mUCh !!! foR tHIs pOsT aTlAsT i hAvE aDDeD gRId lINEs tO mY gRAPh !! ;-() aFTEr sO MUcH oF sTRUggLIng

    ReplyDelete