Animation in D3

Thu, Oct 16, 2014

Readings

Downloads

Assignment

Create a D3 chart based on a CSV given by the instructor.

In this lesson, we will look at how to do animation in D3. To perform these exercises, create a boilerplate html page with the d3 library and an empty svg tag.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>D3 Exercises</title>
</head>
<body>
    <svg width="900" height="500"></svg>
    
    <script src="http://d3js.org/d3.v3.min.js"></script>
    <script>
        
        //your code will go here
        
    </script>
</body>
</html>

Transitions

Animation is a fairly simple process. You set a bunch of attributes, then you run the transition() command, and change those attributes in some way. D3 will take care of the rest by interpolating the differences.

d3.select("svg")
    .append("circle")
    .style({"fill":"red"})
    .attr("r", 20)
    .attr("cy", 50)
    .attr("cx", 10)
    .transition()   //make a transition, animate the cx from its previous value
    .attr("cx", 300);

Notice the transition() line. This function creates a breakpoint where everything after it will be interpolated — or “animated” — if those attributes are currently set. This works with all sorts of values. You can even animate colors:

d3.select("svg")
    .append("circle")
    .attr("r", 50)
    .attr("cx", 100)
    .attr("cy", 100)
    .style({"fill":"red"})
    .transition() //make a transition from the previously set red, to blue
    .style({"fill":"blue"});

This will make a red circle change to blue, but very quickly. You can control the duration of the animation in the next section

Duration

There are a few other functions that allow you to control the way the animate works. One of them is the duration() function. It accepts an argument in the form of milliseconds (1000ms = 1 second).

d3.select("svg")
    .append("circle")
    .attr("cx", 200)
    .attr("cy", 200)
    .style({"fill":"red"})
    .attr("r", 50)
    .transition()   //animate attributes after this
    .duration(1000) //duration of animation
    .attr("r", 100);

The above code will make a circle grow and get larger.

Delay

Sometimes you are animating multiple elements at the same time, and they animate simultaneously. In these situations, it might look better if one animates after another. The delay() function allows you to set a delay in milliseconds before the animation begins.

But how do you make it so that one element will be delayed after another? The secret is to use the anonymous function(d,i){} value where i is the index of each element being animated. The first time this runs, i will equal zero. The second time, i will be 1, then 2 and so on. We can multiply that by a number so that the delay gets greater and greater for each element.

d3.select("svg")
    .selectAll("circle")
    .data([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
    .enter()
    .append("circle")
    .style({"fill":"red"})
    .attr("r", 20)
    .attr("cx", function(d){ return d * 7; })
    .attr("cy", "10%")
    .transition()
    .duration(2000)
    .delay(function(d, i){ return i * 250; }) //each delay will get longer and longer for each circle
    .attr("cy", "90%");

Event Handling (Mouseover, Mouseout)

Sometimes you want something to change when you move the mouse over an element. In D3, events are triggers with the .on() function. The on function requires two arguments, the event “handler” and a callback function to be executed when the event occurs. One example would be .on("mouseover", function(){ }). The “mouseover” is the event handler which triggers the event, and the function will execute once it’s triggered.

Many times with event handlers, you want to do something to change the element that caused the trigger. To do this, you must use this keyword. this refers to the current element that triggered the function. It’s not a d3 specific convention, it’s part of JavaScript and most coding languages. But, you can convert this to d3 by selecting it like so: d3.select(this) which allows you to change the currently selected element in some way.

d3.select("svg")
    .selectAll("circle")
    .data([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
    .enter()
    .append("circle")
    .style({"fill":"red"})
    .attr("r", 20)
    .attr("cx", function(d){ return d * 7; })
    .attr("cy", 300)
    .on("mouseover", function(d){

        //this will execute when the mouse is over "this" particular circle
        d3.select(this)
            .transition()
            .duration(400)
            .style({"fill":"blue"})
            .attr("r", 30);
    })
    .on("mouseout", function(d){

        //return values back to the their defaults when mouse is off
        d3.select(this)
            .transition()
            .duration(400)
            .style({"fill":"red"})
            .attr("r", 20);
    });

Note: To use the hand cursor, so it looks like the circles are clickable, you can specify the pointer property in css:

circle{
    cursor: pointer;
}

Using CSS3 animations

Did you know you can animate elements just with CSS? You can specify certain CSS properties to animate over a period of time. The notation is as specified:

transition: property duration timing-function delay;
  • property — This is a CSS property, like background-color or a position like top or left.
  • duration — The duration as a number and unit, typically “s” for seconds. Ex: 3s for three seconds.
  • timing-function — Special timing functions, some options include linear, ease, ease-in, ease-out. This affects the motion of the animation. It is possible to do a custom one as well with cubic-bezier().
  • delay — Delay before the animation starts, typically in seconds. Written as 3s for three seconds.

The following CSS will animate the fill property to blue over 2 seconds, when applied to an element.

.mycircle{
    transition: fill 2s ease 0s;
    fill: blue;
}

The problem with CSS-only animation is that they are applied immediately when the page loads. But, combined with d3, we can apply a classname to an element only at a certain time (like when a button is pressed) which causes an animation to trigger. We do this with the d3 classed() function, which takes two arguments. The first is the classname to specify on the element, and the second argument is a true or false boolean.

An example of adding a class name to a circle element when clicked:

d3.select("svg")
    .append("circle")
    .attr("cx", 100)
    .attr("cy", 100)
    .attr("r", 50)
    .attr("fill","red")
    .on("click", function(d){

        d3.select(this)
          .classed("mycircle", true);//adds mycircle class to tag

    })
    
//Before: <circle cx="100" cy="100" r="50"></circle>
//After:  <circle class="mycircle" cx="100" cy="100" r="50"></circle>

Then, in our CSS, we can trigger a CSS animation.

Generally, native d3 animations are easier to do in code. But when you have lots of them happening at once, it can slow the browser down. CSS3 animations utilized more efficient browser processing, so they can sometimes produce smoother transitions.

Another way to do CSS3 animations

There is a second way to do CSS3 animations. It’s no improvment from the above method (in fact the above method is a shorthand of this method), but it does allow you a little more granularity over how the animation plays through. You can separately set @keyframes in our CSS. This allows us to specify different values for segments of an animation.

@keyframes someAnimationNameImadeUp {
    0%   {background: red; }
    25%  {background: yellow; }
    50%  {background: blue; }
    75%  {background: green; }
    100% {background: red; }
}

/* Then apply the keyframe to your class using the name reference */
.mycircle{
    animation: someAnimationNameImadeUp 5s;
}

You still have to use D3 to set the class name on the element in order for these animations to take effect. But the advantage to this method is that we can specify the different percentages of the animation. The above code is basically saying: start on red (0%), then a forth of the way through turn to yellow (25%), half-way through turn blue (50%), three-quarters of the way turn green (75%) and finally end with red (100%).

Browser prefixes

Most browsers understand CSS3 animations, but many people still use the -webkit- browser prefix, which ensures that older versions of webkit-based browser (mostly mobile devices) understand these rules. You basically just have to repeat the code, but include -webkit- before each rule. The code block from above would become:

@keyframes someAnimationNameImadeUp {
    0%   {background: red; }
    25%  {background: yellow; }
    50%  {background: blue; }
    75%  {background: green; }
    100% {background: red; }
}
/* Now include a version for older webkit browsers */
@-webkit-keyframes someAnimationNameImadeUp {
    0%   {background: red; }
    25%  {background: yellow; }
    50%  {background: blue; }
    75%  {background: green; }
    100% {background: red; }
}

.mycircle{
    animation: someAnimationNameImadeUp 5s;
    -webkit-animation: someAnimationNameImadeUp 5s; /* also need a webkit when applying CSS3 animations */
}

This requires lots of extra typing. Fortunately, there is a JavaScript tool available called Prefix Free which you include on your page, and it takes care of everything. No need to adding prefixes.