D3 Tips and Tricks v4

Monday, 7 January 2013

Adding more than one line to a graph in d3.js


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:-)
--------------------------------------------------------

All right, we're starting to get serious now. Two lines on a graph is a bit of a step into a different world in one respect. I mean that in the sense that there's more than one way to carry out the task, and I tend to do it one way and not the other mainly because I don't fully understand the other way :-(.

I should stress that that's not because it's more complex, or that it's a bad way, it's just that once I started doing things one way, I haven't come across a need to do things another way. There's a good chance I will have to revisit this decision in the future, but for now I'll keep moving.

So, how are we going to do this? I think that the best way will be to make the executive decision that we have suddenly come across more data and that it is also in our data.tsv file. In fact it looks a little like this (apologies in advance for the big ugly block of data);
date        close    open
1-May-12    58.13    34.12
30-Apr-12   53.98    45.56
27-Apr-12   67.00    67.89
26-Apr-12   89.70    78.54
25-Apr-12   99.00    89.23
24-Apr-12   130.28   99.23
23-Apr-12   166.70   101.34
20-Apr-12   234.98   122.34
19-Apr-12   345.44   134.56
18-Apr-12   443.34   160.45
17-Apr-12   543.70   180.34
16-Apr-12   580.13   210.23
13-Apr-12   605.23   223.45
12-Apr-12   622.77   201.56
11-Apr-12   626.20   212.67
10-Apr-12   628.44   310.45
9-Apr-12    636.23   350.45
5-Apr-12    633.68   410.23
4-Apr-12    624.31   430.56
3-Apr-12    629.32   460.34
2-Apr-12    618.63   510.34
30-Mar-12   599.55   534.23
29-Mar-12   609.86   578.23
28-Mar-12   617.62   590.12
27-Mar-12   614.48   560.34
26-Mar-12   606.98   580.12
Three columns, date open and close. The first two are exactly what we have been dealing with all along and the last (open) is our new made up data. Each column is separated by a tab (hence .tsv (Tab Separated Values)), which is the format we're currently using to import data.

We should save this as a new file so we don't mess up our previous data, so let's call it data2.tsv.

We will be using our simple graph template to start with, so the immediate consequence of this is that we need to edit the line that was looking for 'data.tsv. To reflect the new name.
d3.tsv("data/data2.tsv", function(error, data) {
So when you browse to our new graph html file, we don't see any changes. It still happily loads the new data, but because it hasn't been told to do anything with it, nothing new happens.

What we need to do now it to essentially duplicate the code blocks that drew the first line for the second line.

The good news is that in the simplest way possible that's just two code blocks.

The first sets up the function that defines the new line;
var valueline2 = d3.svg.line()
    .x(function(d) { return x(d.date); })
    .y(function(d) { return y(d.open); });
You should notice that this block is identical to the block that sets up the function for the first line, except this one is called (imaginatively) valueline2. We should put it directly after the block that sets up the function for valueline.

The second block draws our new line;
svg.append("path")      // Add the valueline2 path.
        .attr("class", "line")
        .attr("d", valueline2(data));
Again, this is identical to the block that draws the first line, except this one is called valueline2. We should put it directly after the block that draws valueline.

After those three small changes, check out your new graph;
Hey! Two lines! Hmm.... Both being the same colour is a bit confusing. Good news. We can change the colour of the second line by inserting a line that adjusts it's stroke (colour) very simply.

So here's what our new drawing block looks like;
svg.append("path")      // Add the valueline2 path.
        .attr("class", "line")
        .style("stroke", "red")
        .attr("d", valueline2(data));
And as if by magic, here's our new graph;
Wow. Right about now, we're thinking ourselves pretty clever. But there's two places where we're not doing things right. We took a simple way, but we took some short cuts that might bite us in the posterior.

The first mistake we made was not ensuring that our variable “d.open” is being treated as a number or a string. We're fortunate in this case that it is, but this can't always be assumed. So, this is an easy fix and we just need to put the following (indicated line) in our code;
// Get the data
d3.tsv("data/data.tsv", function(error, data) {
    data.forEach(function(d) {
        d.date = parseDate(d.date);
        d.close = +d.close;
        d.open = +d.open;       //  <=== Add this line in!
    });
The second and potentially more fatal flaw is that nowhere in our code do we make allowance for our second set of data (the second line's values) exceeding our first lines values.

Now that might not sound too normal straight away, but consider this. What if when we made up our data earlier, some of the new data exceeded our maximum value in our original data? As a means of demonstration, here's what happens when our second line of data has values higher than the first lines;
Ahh.... We're not too clever now.

Good news though, we can fix it!

The problem comes about because when we set the domain for the y axis this is what we put in the code;
y.domain([0, d3.max(data, function(d) { return d.close; })]);
So that only considers d.close when establishing the domain. With d.open exceeding our domain, it just keeps drawing off the graph!

The good news is that 'Bill' has provided a solution for just this problem here; http://stackoverflow.com/questions/12732487/d3-js-dataset-array-w-multiple-y-axis-values

So all you need to replace the y.domain line with is this;
y.domain([0, d3.max(data, function(d) { return Math.max(d.close, d.open); })]);

It does much the same thing, but this time it returns the maximum of d.close and d.open (whichever is largest). Good work Bill.

If we put that code into the graph with the higher values for our second line we are now presented with this;
And it doesn't matter which of the two sets of data is largest, the graph will always adjust :-)

You will also have noticed that our y axis has auto adjusted again to cope. Clever eh?

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.

16 comments:

  1. This is helpful, what if you were importing a new csv file? How do you first find the max value fore both and then find the appropriate scale to fit them in?

    ReplyDelete
    Replies
    1. Good news! D3 will do it for you. when you use the piece of code 'return Math.max(d.close, d.open)' this will look over all the values in the 'close' and 'open' columns and adjust it automagically to the maximum value. If you're using a csv with different column names, the code will have to reflect those names, but the determination of the maximum values will happen just as easily :-).

      Delete
    2. Very helpful, sorry I missed it in your notes above.

      One other question - once I add additional lines I lose the reference to d outside of the .csv parser. I would like to access each line individually and get the extent() or max/min values of each line and then increment them every 15 seconds. I think its just a syntax hangup but I can't figure out how to access those separate datasets?

      I've tried csv[i] with no luck, I've tried the path selectors, they all have unique id's, no luck.

      Thx!

      Delete
    3. Hmm.. I'm sorry, but I can't quite understand your question properly. Would you be able to post your code on stackoverflow and we can walk through it there (just post a note in reply to this comment with a link to your question on stackoverflow).
      Cheers.

      Delete
    4. http://stackoverflow.com/questions/16746151/how-do-i-reference-the-values-for-specific-paths-in-a-multiline-d3-graph

      thank you for your time D3noob!

      Delete
  2. This comment has been removed by the author.

    ReplyDelete
    Replies
    1. Hi there, I recommend that you ask the question on Stack Overflow. It's a great place to get help with d3 and there are heaps of people way smarter than me itching to give answers :-).
      However, before you ask the question there, There are three things I will get you to do.
      1. Make sure that you have a working version of the example from the blog post, including the sample data in tsv (or csv if you like) format.
      2. Update the data with your own data (still in tsv/csv format) and get it to work.
      3. Amend the data into a json format and see how it comes out.
      Looking at your json data, it is definitely not in a format that will give a result as in the example. It will need to be something like;
      [
      {"date":"1-May-12","close":600.13,"open":60.13},
      {"date":"30-Apr-12","close":610.98,"open":58.13},
      {"date":"27-Apr-12","close":620.00,"open":55.13}
      ]
      Try those ways forward then ask on Stack Overflow. I'm sure it will be solved.

      Delete
  3. This has just saved me sooooo much time!! Thank you so much for your documentation! This is making learning d3 so much more enjoyable! :)

    ReplyDelete
    Replies
    1. You're welcome! I'm glad it helps. d3.js does have a bit of a learning curve, but the results are cool and the stuff you learn is also useful!

      Delete
  4. Hello sir thanks for the wonderful tutorial....Can you please guide me how to how to include a text dynamically at the end of each line in the graph

    ReplyDelete
    Replies
    1. That is a really cool question! I don't know is my immediate answer, but I'm going to have a look and see what I can find out :-). Thanks for the question.

      Delete
    2. Well, that was a learning experience for me!
      I've made what I think is an example of an answer to your question here http://bl.ocks.org/d3noob/8603837.
      I shall now have to include it in the book!

      Delete
  5. I have a question. For displaying the date on the graph, how do I make it so it shows the actual date instead of just the month for the first day of the month. Instead of April, I want it to show Apr 01.

    ReplyDelete
    Replies
    1. Ahh... You're in luck. I have a section on this in the book (https://leanpub.com/D3-Tips-and-Tricks). Check out the section titled 'Format a date / time axis with specified values' in the 'Things you can do with the basic graph' chapter. The book is free from Leanpub so grab a copy :-).

      Delete
  6. I think I do everything the same as it is written, but the graph is not updated with the 'open' column. Is any tricks with the .tsv format which I don"t know? I use the files downloaded from here, I paste the code from here, but it doesn't works. All the other stuff before worked for me. I am new to this D3 and programming thing. BTW your book is great, but I'm stuck here on page 77. :(

    ReplyDelete
    Replies
    1. It sounds like you're doing everything right, so I can only imagine that either there might be a small syntax error that has occurred in the code (in which case have a read about using firebug for firefox or the google developer console for chrome to track it down). Or it might be something strange with the tsv file (I am regretting using tsv in the book and I will be re-writing it with csv at some stage) So you could change the file to a csv file (search and replace the tags for commas, rename the file and replace d3.tsv with d3.csv). Give those a crack. and if that all fails, put you code into jsfiddle or put a question onto stack overflow. Good luck!

      Delete