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.
excellent work! thank you for the lessons and examples!
ReplyDeleteYou're welcome. I'm glad they're useful :-).
DeleteThanks for tutorial, helped me out alot!
ReplyDeletei added the button for transition but it doenst work. can you tell me why?
ReplyDeletei added this code:
button.on("click", function() {
mySquare.transition().attr("x",600);
})
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.
DeleteI don't know who are you. but It helped a lot , many many thanks
ReplyDeleteThank 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! :)
ReplyDeleteThanks 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.
ReplyDeleteGlad 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
DeleteHow do I implement the same functionality on change event of a dropdown?
ReplyDeleteGood 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)
DeleteHow can i update a chart using a column name selected from a dropdowm?
DeleteHopefully the original questioner can provide some guidance. This is not something that I have experience with I'm afraid
DeleteThank you very much for the tutorial!
ReplyDeleteIt'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)?
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.
DeleteThank you for your tutorial.
ReplyDeleteI 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.
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
DeleteThanks you so much for the tutorials. It was very helpful.
ReplyDeletei have a doubt.
if i have multiple data files,this procedure doesn't work.
is there any easy way to transit to all the data files on button clicks ?
The best way to illustrate this is to look at the code for multiple lines and toggling the data via clicking on the legend. Check it out here http://www.d3noob.org/2014/07/d3js-multi-line-graph-with-automatic.html
Deletei need to make a graph work as a button.I need to switch between 2 graphs each time i click on the button.Any idea how?
ReplyDeleteThere would be quite a few ways of accomplishing this. Aside from the technique shown in this post you could conceivably have two separate pages and switch between pages via a button
Delete