D3 Transition Basics
FREE     Duration: 21:33
Part of Course: Intermediate D3 Course
 

Takeaways:

  • D3 Transitions take a selection of elements and for each element it applies a transition to a part of the current definition of the element
  • The D3 Transition method is used to animate the transition of a DOM element from a current state to an end state
  • The d3.selection.transition method allows you to define what properties are changed, how the properties change, the duration of time it takes for the transition to occur, when the transition starts after it is triggered, and what the end state will look like
  • D3 Transitions are constructed as key-frame animations
  • D3 Transitions allow you to use pre-defined 'tweening as well as use your own definitions
  • To use the d3.selection.transition method in a useful manner, the values that are changed must be interpolatable
  • A D3 Transition has a life-cycle that consists of "transition scheduled", "transition start", "transition running", and "transition end"
  • D3 Transitions can use JavaScript and D3 functions to specify how all of the definitional parts are defined for each DOM element using the "d" for data, the "i" for index, and the "this" for each specific element

Resources:
Transcript:

D3 Transition Basics

D3 Transition Motivating Example


Let's start with some motivating examples.


As you can see, these videos use the Chrome web browser and chrome developer tools.


This is a static file that lives in a folder on the desktop of my computer.


It is not being hosted on a server because we do not need a server yet.


Alright - so you can see the elements in the top window

We have the HTML doctype, character set, the script using the D3.js library hosted at the d3js.org website and an empty HTML body.

We also have open the JavaScript Console.


To start, let's create 5 paragraph elements that say hello and tell us their internal data.

d3.select("body").selectAll("p").data([5,4,3,2,1]).enter().append("p").text(function(d,i) { return " Hello - " + d; });

BROWSER Open the Body Element in the ELEMENTS Section of the Chrome Browser Web Tools

We use D3 to select all the non-existent paragraph elements.

Then we pass an array of numbers to the data operator.

Then we select the enter selection.

Then we append paragraph elements to the enter selection thus merging the placeholder elements to create paragraphs.

Lastly, we use an anonymous function inside of the text operator to return the string "Hello" plus the data element for that particular paragraph element.

You can see the 5 paragraph elements.


Now that we have our 5 paragraph elements, let's do a simple transition.

d3.selectAll("p").transition().style("color","green");

On the screen you should have seen the paragraph text go from black to green.

In the window that shows the HTML elements you should have also seen the HTML paragraph tags change.

This command added a style attribute and value to each HTML paragraph tag.

Each HTML paragraph tag now has a style="color..." attribute, value pair.


Let's try it again and this time go from green to blue.

d3.selectAll("p").transition().style("color","blue");

The text inside of each HTML paragraph tag now went from green to blue.


This is the most basic example of a transition.

D3 takes a selection of elements and for each element it applies a transition to a part of the current definition of the element.

This transition creates an animation that shows what changes happened from the previous state to the new defined state.


Let's now take a look at the basics of what a transition is and how it works.



D3 Transitions are Animations


The D3 transition method is used to animate the transition of a DOM element from a current state to an end state.

d3.selection.transition


The D3 transition method allows property changes to occur smoothly over a specified duration rather than instantaneously.

It is this smooth transition of properties over a specified period of time that creates the look and feel of an animation.


Breaking down the sentence of what a D3 transition is, we can see four main areas to explore.

d3.selection.transition

  1. property change
  2. occur smoothly
  3. over a specified duration
  4. from start start to end state

What properties can be changed

How do the properties change smoothly

How do we specify a duration

and How can we specify the start and end state.


[ Image : Screenshot of what changed ]

When we look at a Screenshot of the example we saw earlier, we can talk about the four areas:

The property that changes is the color of the text.

How did the color change?

It was smooth and we didn't really notice it.

The duration was long enough that we saw it change though not so long that it didn't seem to take long.

The starting state was the color black while the ending state was the color green.

The second time we did a transition the starting color was green while the ending state was the color blue.


[ Image: Animated Example Of Ball Bouncing ]

Wikipedia has this image as an example of what Animation is.

Here we see six frames of a ball bouncing on a green surface.

If we were to cycle through the frames at 10 frames per second, the ball would look like it was in motion.

It is this rapid display of a sequence of images that creates an illusion of movement.

Done correctly, the animation is not jarring and it looks like natural movement.


Transitions in D3 are constructed as a key frame animation.

d3.selection.transition

  • start state
  • 'tweening
  • end state

A key frame animation is a type of animation where only the starting and end points are defined.

The frames in between the starting and end points are generated by the software to be a smooth transition.

This is called 'tweening taken from "inbetween"

In D3, unless otherwise specified the start state is the current state of the DOM Element.

In the motivating example, the start state of the text was the color black.

The end state was the color green.

The second time we ran the transition the start state was the current state of the DOM Element, which was green.

The end state was the color blue.


D3 created a smooth animation where the color transitioned from black to green.

// Black = #000000 = rgb(0,   0,   0) 
// Green = #008000 = rgb(0, 128,   0) 
// Blue  = #0000FF = rgb(0,   0, 255)

Then we created another smooth animation where the color transitioned from green to blue.

In HTML we can define the colors of DOM elements using CSS properties.

We can define colors using English language words like Black, Green and Blue.

Or we can define colors using their Hexadecimal code.

Or we can define colors in a RGB Color Model.

The RGB Color Model is an additive color model in which Red, Green and Blue lights are added together in various ways to reproduce a color.

The RGB Model is named after the colors, Red, Green and Blue.

The D3 transition smoothly created "inbetween" frames from the starting frame of the black to the ending frame of the blue.

This made the animation look natural and not jarring.

To make this animation go smoothly, D3 had to do a value interpolation over time from one color to the other.

We cover this in the next section.



Value Interpolation Over Time.


Let's go back to our example and slow down time.


First, let's look at the 5 paragraphs elements

BROWSER Look at the elements the first paragraph

Each paragraph has an attribute called style and a value called color semicolon and the RGB code for the color blue.

The first number is for the color red and it is zero.

The second number is for the color green and it is zero.

The third number is for the color blue and it is 255.


When we tell D3 to make a transition to change the style of the elements to a color of red, we should expect the first number to go to 255 and the last number to go to 0.


Ignoring for now the duration command and how it works, let's take a look what happens when we tell D3 to make the transition take 10 seconds.

d3.selectAll("p").transition().style("color","red").duration(10000);

You can see that slowly the third number (the blue number) goes to zero.

You can see that slowly the first number (the red number) goes to 255.


Let's try this again this time changing the color to black.

d3.selectAll("p").transition().style("color","black").duration(10000);

The color black in the RGB Color Model is 0 comma 0 comma 0.

So in this instance, we see the first number (the red number) go from 255 to 0.

It did it smoothly and evenly.


Let's do it one last time, this time specifying the color directly in the RGB code.

d3.selectAll("p").transition().style("color","rgb(128,128,128)").duration(10000);

As you can see, this works the same way.

Each color number slowly, smoothly and evenly went from 0 to 128.

As you can imagine, this points to D3 using some type of interpolator method to accurately figure out how to get from 0 to 128 in the span of 10 seconds.


To get the accurate blending of in-between frames from the start frame value to the end frame value, D3 uses an internal interpolator.

d3.interpolate(a, b)

  • interpolateNumber
  • interpolateRgb
  • interpolateString

Depending on what type of values a and b are, the d3.interpolate method uses different interpolator functions.

If b is a number, a is coerced to a number and the interpolateNumber function is used.

If b is a color, a is coerced to an RGB color and the interpolateRgb function is used.

If b is a string, a check is made to see if it's a CSS named color, otherwise the interpolateString function is used.


When working with transitions, it is important to think about whether you can interpolate from the start frame to the end frame.

d3.interpolate(a,b)

can you actually interpolate it?

For instance, you cannot interpolate the creation or destruction of an element.

You can certainly make it too small to see or change the color to match the background, however, the element must already exist.


With the earlier example we saw the transition of colors and by way of the RGB Color Mode, the transition of numbers.

d3.interpolate(a,b)
// numbers
// colors
// geometrics transforms
// strings with embedded numbers

D3 also has geometric transforms based on the transform translate attribute that can be applied to SVG elements.

The last common transition you'll see is that of strings with embedded numbers.


A common example of an embedded number in a string is that of font-size.

style="font-size:12px;"

This could also apply to the stroke width of SVG lines or paths generated by D3.

D3 is sophisticated enough to be able to figure out what is the number part of the string and do an interpolation.

Let's take a look at two examples in the JavaScript console.


Let's go back to our example and in slow motion change the font size.


First, notice that the style does not currently include a font.


D3 figures this out for us through the use of CSS slash DOM getComputerStyle or getAttribute.


Alright, let's transition the paragraph font-size from what it currently is to 6px over 10 seconds.

d3.selectAll("p").transition().style("font-size","6px").duration(10000);

Note that as soon as we pressed enter, the font-size style appeared in the HTML paragraph tags.

Then the number started decreasing from 12 pixels to 6 pixels.

Also note that when we typed in the command, we typed in 6 p x as a string.

D3 was sophisticated enough to figure out what part was the number and do an interpolation on that part.


Let's bring back the font-size without specifying a duration.

d3.selectAll("p").transition().style("font-size","12px");

In this case, you saw a flash of numbers as the interpolation happened quickly.

Just how quickly, we'll cover later in the Transition Duration section.


Now that that we have a better understanding of how a D3 transition is an animation of interpolate-able values between key-frames, let's take a look at the different stages of transitions.



Stages of Transition


The life cycle of a D3 transition is broken down into four separate phases.

d3.selection.transition
// Transition Scheduled
// Transition Starts
// Transition Runs
// Transition Ends

Because transitions happen over time, there is a sequence of recurring callbacks.

Each phase starts when the previous phase has ended.

This is very important to pay attention to when running multiple transitions on one element

or one transition on multiple elements

or multiple transitions on multiple elements.


Mike Bostock shares a useful mnemonic in that transitions are conceived, are born, live and then die.

d3.selection.transition
// Transition Scheduled : conceived
// Transition Starts : born
// Transition Runs : lives
// Transition Ends : dies


A transition is scheduled when it is created by the code running.

d3.selection.transition
// Transition Scheduled : conceived
// Transition Starts : born  ** GRAYED OUT **
// Transition Runs : lives   ** GRAYED OUT **
// Transition Ends : dies    ** GRAYED OUT **

To schedule a transition, the element to which the transition is applied must exist.

In D3 version 3.0 and above, the definition of the ending key frame occurs when the transition is scheduled.

This can be the style, the attribute or the other transition methods available.

In our case, when we defined the new font-size or new color, we were scheduling and defining the transition as soon as we pressed enter in the JavaScript console.


Transitions starts when they are scheduled.

d3.selection.transition
// Transition Scheduled : conceived ** GRAYED OUT **
// Transition Starts : born  
// Transition Runs : lives   ** GRAYED OUT **
// Transition Ends : dies    ** GRAYED OUT **

If a delay has been specified and defined, then they will start after the delay in milliseconds has occurred.

Otherwise, if no delay has been specified and defined, then the transition will start immediately.

When the transition starts, the start event is dispatched.


When the start event is dispatched the transition starts to generate in-between frames.

d3.selection.transition
// Transition Scheduled : conceived ** GRAYED OUT **
// Transition Starts : born  ** GRAYED OUT **
// Transition Runs : lives   
// Transition Ends : dies    ** GRAYED OUT **

These frames are generated until the transition ends.

The way the frames are drawn on the screen is controlled by the easing control timing.

The default easing function D3 uses is "cubic-in-out"

This specific type of easing functions has a slow start and end and then does a cubic-bezier interpolation function in between.

So while the interpolation is linear, the time steps are controlled by the easing function to make the transition look better.

Which makes it look like time slows down at the start and end of the transition and runs at normal speed during the life of the transition.


When the transition has covered its duration and delayed start time, the transition ends.

d3.selection.transition
// Transition Scheduled : conceived ** GRAYED OUT **
// Transition Starts : born  ** GRAYED OUT **
// Transition Runs : lives   ** GRAYED OUT **
// Transition Ends : dies

A final frame is invoked and then the end event is dispatched.

The end key frame value is set exactly when the Transition Ends.


Now that we know the life cycle of how the D3 transition works and behaves, let's next take a look at how to actually change the scheduled start time of the transition and how to change the duration of the transition.

d3.selection.transition
// Transition Scheduled : conceived
// Transition Starts : born  
// Transition Runs : lives
// Transition Ends : dies 



Delaying Transition Start


As we covered before, the transition will start after the delay is run.

d3.selection.transition
// Transition Scheduled
// Transition Starts ** Highlight **
// Transition Runs
// Transition Ends

If there is no delay, then the transition will start immediately.

Otherwise, if there is a delay, then the transition starts when the delay is over.


The transition delay is calculated in milliseconds.

d3.selection.transition.delay(delay)
// delay = constant
// delay = function

The transition delay can be a constant value or it can be a function.


If the delay is a constant value, then all the elements within the selection are given the same delay.

d3.selection.transition.delay(delay)
// delay = constant ** HIGHLIGHT **
// delay = function

The transition will then start for all the elements at the same time.


If the delay is a function, depending on the function, different elements within the selection will be given different delays.

d3.selection.transition.delay(delay)
// delay = constant 
// delay = function ** HIGHLIGHT **

The transition will then start at different times for different elements.


This is the first example we are going to run in the JavaScript Console.

d3.selectAll("p").transition().delay(2000).style("font-size","8px");

Note that in milliseconds, two thousand is 2 seconds.

Here we will change the font size of the paragraph elements to be 8 pixels.

The transition will occur for all the paragraph elements in the selection at the same time because the delay is a constant value.


This is the second example we are going to run in the JavaScript Console.

d3.selectAll("p").transition().style("font-size","17px").delay(function(d,i){
    if (i % 2 === 0) {
        return 2000;
    } else {
        return 4000;
    }
});

The first thing to notice is that we can define the delay of the transition after defining the end frame.

It works perfectly either way.

The second thing to notice is that we are going to use an anonymous JavaScript function to given different elements different delays.

Within the delay method, if we have a function, then the d for the current element datum, the i for the current index and the "this" context are all passed to it.

Which means that in this instances we are going to set different delays to each element based on whether the index of the element is even or odd.

If the element index is even, then it will be delayed 2 seconds before it starts it's transition.

If the element index is odd, then it will be delayed 4 seconds before it starts it's transition.

It's worth remembering that D3 selections are 0-indexed.


This is the third example we are going to run in the JavaScript Console.

d3.selectAll("p").transition().style("font-size","7px").delay(function(d,i){
    return d * 1000;
});

In the previous example, we used the index of the current DOM element.

This time we are going to use the data attached to the current DOM element to specify how long the delay will be.

If you recall, the data attached to the elements were the numbers 5, 4, 3, 2 and 1.

So the last element will transition after 1 second,

the second to last element will transition after 2 seconds,

and so on and so forth until the first element transitions after 5 seconds.


Alright - let's go to the JavaScript console to test out the transition delays.


We continue with our examples where we left off.


First, let's run the transition delay example where all the elements transition after 2 seconds.

d3.selectAll("p").transition().delay(2000).style("font-size","8px");

Notice that nothing happened for 2 seconds and then all the elements transitions at the same time.


Next, let's run the transition delay example where the elements transition after 2 seconds or 4 seconds depending on whether their selection index is even or odd.

d3.selectAll("p").transition().style("font-size","17px").delay(function(d,i){
    if (i % 2 === 0) {
        return 2000;
    } else {
        return 4000;
    }
});

As we just spoke about, D3 selections are 0-indexed.

Which means that the first element - the paragraph "Hello - 5" has an index of 0.

0 modulus two in JavaScript is zero, so that's why three elements transitioned after 2 seconds.

DOM Elements with the index of 2 and 4 then transitioned after 4 seconds.


Finally, let's run the the transition delay example where the elements transition based on the data attached to the DOM elements.

d3.selectAll("p").transition().style("font-size","7px").delay(function(d,i){ return d * 1000;});

The D3 API calls this a "data driven animation"


If we take a look at the data that is encapsulated in the d3.selectAll("p") selection

d3.selectAll("p").data();

We can see that the data is 5, 4, 3, 2 and 1.

Which as we covered earlier meant that the last element was going to wait 1 second to transition while the 1st element was going to take 5 seconds to transition.


Now that we have looked at how to delay the transition, let's next look at how to change the duration of the transition.



Modifying Transition Duration


The transition runs according to the duration specified.

d3.selection.transition
// Transition Scheduled
// Transition Starts
// Transition Runs   ** Highlight **
// Transition Ends

The D3 transition default duration is 250ms.

If no duration is specified, then the default duration is used.


The transition duration is calculated in milliseconds.

d3.selection.transition.duration(duration)
// duration = constant
// duration = function

The transition can be a constant value or it can be a function.


If the duration is a constant value, then all of the elements within the selection are given the same duration.

d3.selection.transition.duration(duration)
// duration = constant ** HIGHLIGHT **
// duration = function

The transition will take the same amount of time for all the elements.


If the duration is a function, depending on the function, different elements within the selection will be given different duration times.

d3.selection.transition.duration(duration)
// duration = constant 
// duration = function ** HIGHLIGHT **

The transition will then end at different times for different elements.


This is the first example we are going to run in the JavaScript Console.

d3.selectAll("p").transition().duration(2000).style("font-size","8px");

Note that in milliseconds, two thousand is 2 seconds.

Here we will change the font size of the paragraph elements to the 8 pixels.

The duration of the transition will be 2 seconds for all the paragraph elements in the selection because the duration is a constant value.


This is the second example we are going to run in the JavaScript Console.

d3.selectAll("p").transition().style("font-size","17px").duration(function(d,i){
    if (i % 2 === 0) {
       return 2000;
    } else {
       return 4000;
    }
});

The first thing to notice is that we can define the duration of the transition after defining the end frame.

It works perfectly either way.

The second thing to notice is that we are going to use an anonymous JavaScript function to given different elements different durations.

Within the duration method, if we have a function, then the d for the current element datum, the i for the current index and the "this" context are all passed to it.

Which means that in this instances we are going to set different durations for each element based on whether the index of the element is even or odd.

If the element index is even, then the transition will take a duration of 2 seconds.

If the element index is odd, then the transition will take a duration of 4 seconds.

It's worth remembering that D3 selections are 0-indexed.


This is the third example we are going to run in the JavaScript Console.

d3.selectAll("p").transition().style("font-size","7px").duration(function(d,i){
    return d * 1000;
});

In the previous example, we used the index of the current DOM element.

This time we are going to use the data attached to the current DOM element to specify how long the transition duration will be.

If you recall, the data attached to the elements were the numbers 5, 4, 3, 2 and 1.

So the last element will have a duration of 1 second,

the second to last element will have a duration of 2 seconds,

and so on and so forth until the first element has a duration of 5 seconds.

Alright - let's go to the JavaScript console to test out the transition durations.


We continue with our examples where we left off.


First, let's run the transition duration example where all of the elements transitions have a duration of 2 seconds.

d3.selectAll("p").transition().duration(2000).style("font-size","8px");

Notice that the duration for the transition took 2 seconds for all of the elements.


Next, let's run the transition duration example where the element's transition duration is either 2 seconds or 4 seconds depending on whether their selection index is odd or even.

d3.selectAll("p").transition().style("font-size","17px").duration(function(d,i){
    if (i % 2 === 0) {
        return 2000;
    } else {
        return 4000;
    }
});

As we just spoke about, D3 selections are 0-indexed.

Which means that the first element - the paragraph "Hello - 5" has an index of 0.

0 modulus two in JavaScript is zero, so that's why three elements had a transition duration of 2 seconds.

DOM Elements with the index of 2 and 4 had a transition duration of 4 seconds.


Finally, let's run the the transition duration example where the elements transition duration is based on the data attached to the DOM elements.

d3.selectAll("p").transition().style("font-size","7px").duration(function(d,i){ return d * 1000; });

The D3 API calls this a "data driven animation"


If we take a look at the data that is encapsulated in the d3.selectAll("p") selection

d3.selectAll("p").data();

We can see that the data is 5, 4, 3, 2 and 1.

Which as we covered earlier meant that the last element was going to have a transition duration of 1 second while the 1st element was going to take 5 seconds to do the full transition.


And with that we have covered how D3 transitions work, as well as basic examples and functionality.

<< Back To D3 Screencast and Written Tutorials Index