D3 Tips and Tricks v4

Wednesday, 6 February 2013

Update d3.js Data Dynamically (Button Click)


The following post is a portion of the D3 Tips and Tricks document which is 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:-)
-------------------------------------------------------

Update data dynamically – On Click

OK, you're going to enjoy this section. Mainly because it takes the traditional graph that we know, love and have been familiar with since childhood and adds in an aspect that that has been missing for most of your life.
Animation

Graphs are cool. Seeing information represented in a graphical way allows leaps of understanding that are difficult or impossible to achieve from raw data. But in this crazy ever-changing world, a static image is only as effective as the last update. The ability to being able to have the most recent data represented in you graph and to have it do it automatically provides a new dimension to traditional visualizations.

Interestingly enough, part of the reason for moving from D3's predecessor Protovis was the ability to provide greater control and scope to animating data.

Explanation of what's going to happen

So what are we going to do?

First we'll spend a bit of time setting the scene. We'll add a button to our basic graph file so that we can control when our animation occurs, we'll generate a new data set so that we can see how the data changes easily, then we'll shuffle the code about a bit to make it do it's magic. While we're shuffling the code we'll take a little bit of time to explain what's going on with various parts of it that are different to what we might have seen thus far. Then we'll change the graph to update automatically (on a schedule) when the data changes.

One of the problems with writing a manual about a moving object is that it's difficult to represent that movement on a written page, so where there is something animated occurring, I will provide all the code that I'm using so that you can try it at home and have an online version as well.

Adding a Button

It's all well and good animating your data, but if you don't know when it's supposed to happen or what should happen, it's a little difficult to evaluate how successful you've been.

To make life easy, we're going to take some of the mystery out of the equation (don't worry, we'll put it back later) and add a button to our graph that will give you control over when your graph should update it's data. When complete it should look like this;

To add a button, we will take our simple-graph.html example and just after the `<body>` tag we add the following code;
<div id="option">
    <input name="updateButton" 
                 type="button" 
                value="Update" 
                onclick="updateData()" />
</div>
The HTML `<div>` element (or HTML Document Division Element) is used to assign a division or section in an HTML document. We use it here as it's good practice to keep sections of your HTML document distinct so that it's easier to perform operations them at a later date.

In this case we have given the div the identifier “option” so that we can refer to it later if we need to (embarrassingly, we won't be referring to it at all, but it's good practice none the less.

The following line adds our button using the HTML `<input>` tag. The `<input>` tag has a wide range of attribute (options) for allowing user input. Check out the links to w3schools and Mozilla for a whole lot of reading.
In our `<input>` line we have four different attributes;
  • name
  • type
  • value
  • onclick

Each of these attributes modifies the `<input>` function in some way so that our button does what we want it to do.

name:
This is the name of the control (in this case a button) so that we can reference it in other parts of our HTML script.

type:
Probably the most important attribute for a button, this declares that our type of input will be a button! There are *heaps* of other options for `type` which would form a significant section in itself.

value:
For a `button` input type, this is the starting value for our button and forms the label that our button will have.

onclick:
This is not an attribute that is specific to the `<input>` function, but it allows the browser to capture a mouse clicking event when it occurs and in our case we tell it to run the `updateData()` function (which we'll be seeing more of soon).

Updating the data

To make our first start at demonstrating changing the data, we'll add another data file to our collection. We'll name it `data-alt.tsv` (you should be able to find it in the example file collection in the downloads page on d3noob.org). This file changes our normal data (only the values, not the structure) just enough to see a movement of the time period of the graph and the range of values on the y axis (this will become really oblivious in the transition).

We'll only use this file while we want to demonstrate that dynamic updating really does work. Ultimately we will just use the one file and rely on an external process updating that file to provide the changing data.


While going through the process of working out how to do this, the iterations of my code were mostly horrifying to behold. However, I think my understanding has improved sufficiently to allow only a slight amendment to our simple-graph.html JavaScript code to get this going.

What we should do is add the following block of code to our script towards the end of the file just before the `</style>` tag;
function updateData() {

    // Get the data again
    d3.tsv("data/data-alt.tsv", function(error, data) {
        data.forEach(function(d) {
            d.date = parseDate(d.date);
            d.close = +d.close;
        });

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

    // Select the section we want to apply our changes to
    var svg = d3.select("body").transition();

    // Make the changes
        svg.select(".line")   // change the line
            .duration(750)
            .attr("d", valueline(data));
        svg.select(".x.axis") // change the x axis
            .duration(750)
            .call(xAxis);
        svg.select(".y.axis") // change the y axis
            .duration(750)
            .call(yAxis);

    });
}

What's happening in the code?

There are several new concepts and techniques in this block of code for us to go through but we'll start with the overall wrapper for the block which is a function call.

The entirety of our JavaScript code that we're adding is a function called `updateData`. This is the subject of the first line in the code above (and the last closing curly bracket). It is called from the only other piece of code we've added to the file which is the button in the HTML section. So when that button is clicked, the `updateData` function is carried out.

It's worth noting that while our `updateData` function only appears to work the once when you first click the button, in fact every time the button is pushed the `updateData` function is carried out. It's just that since the data doesn't change after the first click, you never see any change.

Then we get our new data with the block that starts with `d3.tsv("data/data-alt.tsv"`. This is a replica of the block in the main part of the code with one glaring exception. It is getting the data from our new file called `data-alt.tsv`. However, one thing it's doing that bears explanation is that it's loading data into an array that we've already used to generate our line. At a point not to far from here (probably the next page) we're going to replace the data that make up our line on the page with the new data that's just been loaded.

We then set the scale and the range again using the `x.domain` and `y.domain` lines. We do this because it's more than possible that our data has exceeded or shrunk with respect to our original domains so we recalculate them using our new data. The consequence of not doing this would be a graph that could exceed it's available space or be cramped up.

Then we assign the variable svg to be our selection of the `"body"` div (which means the following actions will only be carried out on objects within the `"body"` div.

Selections are a very important and if reading Google Groups and Stack Overflow are anything to go by they are also a much misunderstood feature of D3. I won't claim to be in any better position to describe them, but I would direct readers to a description of nested selections by Mike Bostock (http://bost.ocks.org/mike/nest/) and a video tutorial by Ian Johnson (http://blog.visual.ly/using-selections-in-d3-to-make-data-driven-visualizations/).

The other part of that line is the transition command (`.transition()`). This command goes to the heart of animation dynamic data and visualizations and is a real treasure.

I will just be brushing the surface of the subject of transitions in d3.js, and I will certainly not do the topic the justice it deserves for in depth animations. I heartily recommend that you take an opportunity to read Mike Bostock's “Path Transitions” (http://bost.ocks.org/mike/path/), bar chart tutorial (http://mbostock.github.com/d3/tutorial/bar-2.html) and Jerome Cukier's “Creating Animations and Transitions with D3” (http://blog.visual.ly/creating-animations-and-transitions-with-d3-js/). Of course, one of the main resources for information on transitions is also the D3 wiki (https://github.com/mbostock/d3/wiki/Transitions).

So as the name suggests, a transition is a method for moving from one state to another. In it's simplest form for a d3.js visualisation, it could mean moving an object from one place to another, or changing an objects properties such as opacity or colour. In our case, we will take our data which is in the form of a line, and change some of that data. And when we change the data we will get d3 to manage the change via a transition. At the same time (because we're immensely clever) we will also make sure we change the axes is they need it.

So in short, we're going to change this...


… into this...


Obviously the line values have changed, and both axes have changed as well. And using a properly managed transition, it will all occur in a smooth ballet :-).

So, looking at the short block that manages the line transition;
        svg.select(".line")   // change the line
            .duration(750)
            .attr("d", valueline(data));

We select the `".line"` object and since we've already told the script that `svg` is all about the transition (`var svg = d3.select("body").transition();`) the attributes that follow specify how the transition for the line will proceed. In this case, the code describes the length of time that the transition will take as 750 milliseconds (`.duration(750)`) and uses the new data as transcribed by the `valueline` variable from the original part of the script (`.attr("d", valueline(data));`).

The same is true for both of the subsequent portions of the code that change the x and y axes. We've set both to a transition time of 750 milliseconds, although feel free to change those values (make each one different for an interesting effect).

Other attributes for the transition that we could have introduced would be a delay (`.delay(500)`, perhaps to stagger the movements) and more interestingly an easing attribute (`.ease(type[, arguments…])`) which will have the effect of changing how the movement of a transition appears (kind of like a fast-slow-fast vs linear, but with lots of variations).

But for us we'll survive with the defaults.

So, in theory, you've added in your new data file (`data-alt.tsv`) and made the two changes to the simple graph file (the HTML block for the button and the JavaScript one for the `updateData` function). And the result has been a new beginning in your wonderful d3 journey!

I have loaded the file for this into the d3noob downloads page with the general example files as data-load-button.html

If you fancied a quick test, consider what you would need to do to add another button that was labelled 'Revert' and when pressed changed the graph back to the original data (so that you could merrily press 'Update' and 'Revert' all day if you wanted.

I have loaded a simplistic version of the graph that will do this into the d3noob downloads page with the general example files as data-load-revert-button.html

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.

17 comments:

  1. excellent work! thank you for the lessons and examples!

    ReplyDelete
    Replies
    1. You're welcome. I'm glad they're useful :-).

      Delete
  2. Thanks for tutorial, helped me out alot!

    ReplyDelete
  3. i added the button for transition but it doenst work. can you tell me why?

    i added this code:
    button.on("click", function() {
    mySquare.transition().attr("x",600);
    })

    ReplyDelete
    Replies
    1. You would need to show all your code for someone to help out. The comments here aren't suited to code posting, so I recommend that you put a question on Stack Overflow. There are plenty of people there who would be happy to help.

      Delete
  4. I don't know who are you. but It helped a lot , many many thanks

    ReplyDelete
  5. Thank you again for taking the time to post this tutorial - I am going to try and apply this to a sunburst chart, when I get it figured out I will post it! :)

    ReplyDelete
  6. Thanks so much for the tutorial. It was super helpful. One thing that I'm struggling with is how to apply a scrubber that outputs data values when the data transitions on click. I can get the axis and path to transition, but the mousemove scrubber is still outputting data from the old data. Any help here would be appreciated.

    ReplyDelete
    Replies
    1. Glad the tutorial was useful. I'm sorry to say that I wouldn't know where to start to look at the problem of outputting data. I've not needed to use it as a feature, so I've never had a go. Try Stack Overflow if things get tough. They have a great community for D3 there. Good luck

      Delete
  7. How do I implement the same functionality on change event of a dropdown?

    ReplyDelete
    Replies
    1. Good question. This may require a bit of experimentation on your part, but there is a lot of guidance in the pages here (https://www.google.co.nz/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=html%20dropdown%20events)

      Delete
    2. How can i update a chart using a column name selected from a dropdowm?

      Delete
    3. Hopefully the original questioner can provide some guidance. This is not something that I have experience with I'm afraid

      Delete
  8. Thank you very much for the tutorial!
    It's amazing to see you are replying to questions in such article posted three years ago :)
    I wonder how dose this work for a scatter graph? Means that I have a lot of dots needed to update their position.
    The trend line I created updates very well as in your example, however the dots do not. Is it because all the lines/axis take only one "attr" statement but dots take two(for cx and cy)?

    ReplyDelete
    Replies
    1. Good question. It is a little difficult to tell without seeing your code, but the lost likely reason why you might have problems would be because you would have to have a new piece of code in the 'updateData' block that updates the cx,cy attributes. So yes, it s because of the cx/cy attributes, but you will need to add an additional 'svg.select......' portion for then in the updateData function for them to be updated.

      Delete
  9. Thank you for your tutorial.
    I have one important question and hopefully you have the answer.

    In your case, the button is created in a static manner. That's to say, you create the button before loading the data.
    Question: what if the creation of the button is related to the data to be loaded. For example, I want the button to be clicked to show part of the loaded data.
    To be more specific, say the input data is d = {["name": "a"], ["name":"b"]}; Two buttons will be created when the data is loaded using D3, now I want the visualization to load "a" when button for "a" is clicked, same for "b".
    I can't get this around when the buttons are created dynamically.

    ReplyDelete
    Replies
    1. Hmmmm... Interesting question. Unfortunately my HTML skills are mediocre at best. I might recommend a possible alternative in using the mouse functions to trigger the load in an adaption of the code that is used in the legend example here? https://leanpub.com/D3-Tips-and-Tricks/read#leanpub-auto-show--hide-an-element-by-clicking-on-another-element good luck

      Delete