Tuesday, 16 July 2013

Arranging more than one d3.js graph on a web page

The following post is a portion of the D3 Tips and Tricks book which is free to download. To use this post in context, consider it with the others in the blog or just download the the book as a pdf / epub or mobi .
---------------------------------------------------------- 

Arranging more than one graph on a web page.

We'll start with the presumption that we want to be able to display two separate graphs on the same web page. The example we will use is clearly contrived, but we should remember that it's the process we're interested in in this case, not the content.

First make a page with two graphs

This is surprisingly easy. If you start with the simple graph that we initially used as our learning example at the start of the book, and duplicate the section that looks like the following, you are 99% of the way there.

// Adds the svg canvas
var chart2 = d3.select("body")
    .append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
    .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");
        
// Get the data
d3.csv("data2.csv", function(error, data) {
    data.forEach(function(d) {
        d.date = parseDate(d.date);
        d.close = +d.close;
    });

    // Scale the range of the data
    x.domain(d3.extent(data, function(d) { return d.date; }));
    y.domain([0, d3.max(data, function(d) { return d.close; })]);

    // Add the valueline path.
    chart2.append("path")
        .attr("class", "line")
        .attr("d", valueline(data));

    // Add the X Axis
    chart2.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

    // Add the Y Axis
    chart2.append("g")
        .attr("class", "y axis")
        .call(yAxis);

});

For simplicity, I have generated an example you can use as a starting point on bl.ocks.org here.
The differences from the original simple graph example are;

  • The graphs are slightly smaller (to make it easier to display the graphs as they move about).
  • I have used *.csv files for the data and there are two different data files so that they look different and we can differentiate between the graphs.
  • Most importantly, I have declared the two charts with different variable names (one as chart1 and the other as chart2).

The different variable names are important, because if you leave them with the same identifier, the web page decides that what you're trying to do is to put all your drawing data into the same space. The end result is two graphs trying to occupy the same space and looks a bit like this...


The example with the correct (different) variable labels should look a little like this...

The first thing I want to point out about how the graphs are presented is that they are both 'attached' to the same point on our web page. Both of the graphs select the body of the web page and then append a svg element to it;
var chart2 = d3.select("body")
    .append("svg")
This has the effect of appending the graphs to the same anchor point. Interestingly, if we narrow the window of our web browser to less that the width of both of our graphs side by side, the browser will automatically move one of the graphs to a position below the first in much the same way that text will wrap on a page.
For a very simple mechanism of putting two graphs (or any two d3.js generated images) on a single page, this will work, but we don't have a lot of control over the positioning.

Arrange the graphs with separate anchors

To gain a little more control over where the graphs are placed we will employ ID selectors.
An ID selector is a way of naming an anchor point on an HTML page. They can be defined as "*a unique identifier to an element*". This means that we can name a position on our web page and then we can assign our graphs to those positions.
This can be done simply by placing div tags in our html file in an appropriate place (here I've put them in between the<style> section and the <body>).
</style>

<div id="area1"></div>
<div id="area2"></div>

<body>
Remembering that the tag defines a division or a section in an HTML document. Therefore we are labelling specific sections in our web page .
Now all we need to do is to tell each graph to append itself to either of these ID selectors. We do this by replacing the selected section in our JavaScript code with the appropriate ID selector as follows;
var chart1 = d3.select("#area1")
    .append("svg")
.. .and...
var chart2 = d3.select("#area2")
    .append("svg")
A couple of points to note:
When we reference our ID selectors in the code (other than when we we set them with id="area1") we need to put a hash (#) in front of the selector for the HTML to recognise it.
We can only use a single ID selector in a single place. This might sound like common sense, but whatever the temptation, don't go trying to assign the same ID selector to more than one place (You can certainly assign more than one item to an ID selector (for instance, you could append chart2 to area1 (var chart2 = d3.select("#area1")) but an ID selector is a unique identifier of a position.).

With these divs added, when you browse to the file, you will find that it looks like this;
This looks the same as when the two graphs were wrapping when the browser was narrowed. However, this time the browser is wide enough to support the two side by side, but they won't position themselves that way. This is because each div *di*vides the web page. The top graph is in the div with the ID selector area1 and the bottom graph is in the div with the ID selector area2. These divs effectively extend for the width of the web page.
The situation that we now find ourselves in is that we have control over where the graphs will be anchored, but we don't have much flexibility for arranging those anchors. This is where Bootstrap comes in.
The description above (and heaps of other stuff) is in the D3 Tips and Tricks book that can be downloaded for free (or donate if you really want to :-)).

17 comments:

  1. Thanks for sharing these great information. Also the PDF is very useful for me.

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

      Delete
  2. Much appreciated, excellent tutorial!

    ReplyDelete
  3. Awesome tutorial. Keep up the great work.

    ReplyDelete
    Replies
    1. Thanks! I'm working on a new section at the moment on Elements, Attributes and Styles. Check out progress by downloading a copy of the book https://leanpub.com/D3-Tips-and-Tricks. (Remember, you can just pay $0 by sliding the payment bar to the left :-)).

      Delete
  4. I cant seem to get the forEach function to work. Any suggestions?

    ReplyDelete
    Replies
    1. It should be a fairly straightforward piece of code. My first thought is that it will be a simple syntax problem that you might be having problems seeing because you're looking at the code all the time. I would recommend asking a question on stack overflow and include the code listing so that folks can get a good look. You never know, it might be the trigger that helps you identify the problem yourself. Good luck.

      Delete
  5. I can't seem to do the same for sankey diagrams. I need to have two sankey diagrams showing different data in a single webpage. Any suggestions?

    ReplyDelete
    Replies
    1. Hmm... It should be directly translatable to a Sankey diagram. The best advice I would give is to put the question (and especially your code) into a question on Stack Overflow. The key will be in the code. Make it as simple an example as possible so that people answering can concentrate on the problem rather than the data. I'm sure this will get a result.

      Delete
    2. Anonymous, what I did for the sankey was to put the code within a function then call it from different buttons which read info from the database. It renders one below the other (tested only on cellphone app though).

      Delete
  6. Tip: Instead of giving each chart svg a different name one can also use closures to prevent the different chart codes to see each other variabales. Using closures may give you a cleaner code, especially if it is automatically generated from a template system. More info is available in this so discussion: http://stackoverflow.com/questions/16714271/trying-to-add-multiple-d3-graphs

    (btw, you will still need unique names for the div that holds the chart)

    ReplyDelete
    Replies
    1. This was a perfect solution for me actually, thank you! :)

      Delete
  7. Hi can I add CSS properties to these div like position , top right margin left etc to these two divs

    ReplyDelete
    Replies
    1. That's a really good question and something that I have never tried. This sounds like a good opportunity to carry out some experimentation to see what happens. My starting point would be something like this; http://www.w3schools.com/css/css_positioning.asp or this https://www.w3.org/wiki/CSS_absolute_and_fixed_positioning

      Delete
  8. Why is it that the anchor tag divs must be before the opening body tag? Does that mean that all html code for a page that includes d3 graphs must be before the opening body tag, with only the d3 JavaScript code being within the body tags?

    ReplyDelete
    Replies
    1. Actually I don't think that they do (I say this without testing). I think that they should work both ways, but the way that I've presented above is probably not the best way (on reflection). Try the alternative and see how you get on. As with all things D3, experimentation is a great teacher.

      Delete