An intro to transition with d3.js





This document displays several d3.js example illustration how to build transitions. Reproducible and editable code is provided, hopefully allowing you to get the key concept of transition.

Most basic transition


This is the most basic transition you can build with d3.js. It smoothly changes the width attribute of a rectangle.


Steps:

  • For code clarity, the rectangle is built in svg. Visit this page for more on svg shapes.

  • The rectangle is then modified using d3. It starts by selecting the rect element thanks to d3.select(). Then .transition() is called to initialize the transition. An optional .duration() is specified: the animation here lasts 2000 ms. Finally, the width attribute is modified to 400px.
<!DOCTYPE html>
<meta charset="utf-8">

<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>

<!-- Create a div with just a rect in svg -->
<div>
  <svg width="960px" height="400px">
      <rect
        id="my_rect" x="10" y="200"
        width="200" height="30" stroke="black"
        fill="#69b3a2" stroke-width="1"/>
  </svg>
</div>

<script>

d3.select("#my_rect")
  .transition()
  .duration(2000)
  .attr("width", "400")

</script>

Trigger transition with a button


It is important to understand that the animation instructions can be wrapped in a function. It allows to control when to trigger the animation. Here a button is used to trigger, but any other event could work as well.


Steps:

  • Here, a button is created in html, and a onclick attribute is added. Read more about buttons on the dedicated page.

  • When the button is pressed, a function called triggerTransition() is triggered. This function contains the instructions for the animation as described in the first example of the series.
<!DOCTYPE html>
<meta charset="utf-8">

<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>

<!-- Create a button to trigger the transition -->
<button onclick="triggerTransition()">Trigger transition</button>

<!-- Create a div with just a rect in svg -->
<div>
  <svg width="960px" height="400px">
      <rect
        id="rectArea" x="10" y="200"
        width="200" height="30" stroke="black"
        fill="#69b3a2" stroke-width="1"/>
  </svg>
</div>

<script>

function triggerTransition(){
  d3.select("#rectArea")
    .transition()
    .delay(100)
    .duration(2000)
    .attr("width", "400")
}

</script>

Piping transitions


It is possible to pipe transitions: make them happen one after each other instead of simultaneously. For this to happen, you have to call the transition() function at the beginning of each step of the pipeline.


<!DOCTYPE html>
<meta charset="utf-8">

<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>

<!-- Create a button to trigger the transition -->
<button onclick="triggerTransitionPiping()">Trigger transition</button>

<!-- Create a div with just a rect in svg -->
<div>
  <svg width="960px" height="400px">
      <rect
        id="my_rect2" x="10" y="200"
        width="200" height="30" stroke="black"
        fill="#69b3a2" stroke-width="1"/>
  </svg>
</div>

<script>

function triggerTransitionPiping(){

  d3.select("#my_rect2")

    // First, make the bar wider
    .transition()
    .duration(2000)
    .attr("width", "400")

    // Second, higher
    .transition()
    .attr("height", "100")

    // Change its color
    .transition()
    .style("fill", "red")

    // And now very small
    .transition()
    .duration(200)
    .attr("height", "10")
    .attr("width", "10")
}

</script>

Adding delay between elements


It is possible to add delay between the transition of each element. Sometimes it adds a nice flow impression to the transition.


Steps:

  • Here, several elements are created. It is thus possible to provide a different delay value for each circle.
<!DOCTYPE html>
<meta charset="utf-8">

<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>

<!-- Create a button to trigger the transition -->
<button onclick="triggerTransitionDelay()">Trigger transition</button>

<!-- Create a svg area-->
<svg id="dataviz_delay" width="400px" height="320px"></svg>

<script>

// Position of the circles on the X axis
var position = [50, 100, 150, 200, 250, 300, 350]

// Add circles at the top
d3.select("#dataviz_delay")
  .selectAll("mycircles")
  .data(position)
  .enter()
  .append("circle")
    .attr("cx", function(d){return d} )
    .attr("cy", 40)
    .attr("r", 10)

// Animation: put them down one by one:
function triggerTransitionDelay(){
  d3.selectAll("circle")
    .transition()
    .duration(2000)
    .attr("cy", 300)
    .delay(function(i){return(i*10)})
}

</script>

Axis Transition


Let's consider an X axis. We want to update the upper limit of the axis from 100 to 1000, with a smooth transition. D3.js makes it a breeze using the same transition() function.


Steps:

  • To read more about axis, visit the dedicated section.

  • Here, we just change the X scale first, and then recall the axisBottom() function with a previous transition().

  • Note: the position of any other element of the chart will not be updated, only this axis.
<!DOCTYPE html>
<meta charset="utf-8">

<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>

<!-- Create a button to trigger the transition -->
<button onclick="triggerTransitionAxis()">Trigger transition</button>

<!-- Create a div with just a rect in svg -->
<svg id="svgAxisTransition" width="960px" height="400px"></svg>

<script>

// Select the svg area
svg = d3.select("#svgAxisTransition")

// Create the scale
var x = d3.scaleLinear()
    .domain([0, 100])         // This is what is written on the Axis: from 0 to 100
    .range([10, 400]);       // This is where the axis is placed: from 100 px to 800px

// Draw the axis
svg.append("g")
  .attr("transform", "translate(0,200)")      // This controls the vertical position of the Axis
  .call(d3.axisBottom(x))
  .attr("class", "myAxis")                    // Give a class to this element: we'll have to call it later

function triggerTransitionAxis(){

  // Change the scale of the axis
  x.domain([0,1000])

  // Update the axis
  svg.select(".myAxis")
    .transition()
    .duration(3000)
    .call(d3.axisBottom(x))

}
// Note that this won't change your data points if you have some, you have to apply theme a transition() as well.

</script>

Change dataset with same index


Consider a dataset composed by 4 values. We map the X position of 4 circles to these values. We then switch to a second dataset that has exactly the same features.


Steps:

  • This is the simplest example: both datasets have the same number of entry. We also consider they are ordered the same way, so position of circle 1 is always the first in the list.

  • First step: creating the circles. enter() allows to start a loop on every elements of data. append() is used to add a circle at each iteration.

  • Second step: bind the circles to a new dataset using data(). Here the number of entry is the same: no need to enter() to add new circle or to exit() to delete circles

  • Try: add an element to the second list: this element won't appear.

  • Try: remove the last element of the second list: the 4th circle won't disappear during the transition.
<!DOCTYPE html>
<meta charset="utf-8">

<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>

<!-- Create a data1 to trigger the transition -->
<button onclick="triggerTransition()">Trigger transition</button>

<!-- Create a div with just a rect in svg -->
<svg id="areaData1" width="960px" height="200px"></svg>

<script>

// create 2 datasets:
var data1 = [50, 100, 200, 300]
var data2 = [70, 90, 230, 390]

// Select the SVG area
var svg = d3.select("#areaData1")

// Add circles at the top
svg
  .selectAll("mycircles")
  .data(data1)
  .enter()
  .append("circle")
    .attr("cx", function(d){return d} )
    .attr("cy", 100)
    .attr("r", 10)

// A function that switch to the second dataset:
function triggerTransition(){
  svg
    .selectAll("circle")
    .data(data2)
    .transition()
    .delay(100)
    .duration(2000)
    .attr("cx", function(d){return d} )
}

</script>

New dataset has more entries


This example is almost the same as the previous one. However, there is now one more circle in the second list than in the first list.


Steps:

  • This is the simplest example: both datasets have the same number of entry. We also consider they are ordered the same way, so position of circle 1 is always the first in the list.

  • First step: creating the circles. enter() allows to start a loop on every elements of data. append() is used to add a circle at each iteration.

  • Second step: bind the circles to a new dataset using data(). Here the number of entry is the same: no need to enter() to add new circle or to exit() to delete circles

  • Try: add an element to the second list: this element won't appear.

  • Try: remove the last element of the second list: the 4th circle won't disappear during the transition.
<!DOCTYPE html>
<meta charset="utf-8">

<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>

<!-- Create a data2 to trigger the transition -->
<button onclick="triggerTransitionData2()">Trigger transition</button>

<!-- Create a div with just a rect in svg -->
<svg id="areadata2" width="960px" height="200px"></svg>

<script>

// create 2 datasets:
var data1 = [50, 100, 200]
var data2 = [70, 90, 230, 390]

// Select the SVG area
var Svg = d3.select("#areadata2")

// Add circles at the top
Svg
  .selectAll("mycircles")
  .data(data1)
  .enter()
  .append("circle")
    .attr("cx", function(d){return d} )
    .attr("cy", 100)
    .attr("r", 10)

// A function that switch to the second dataset:
function triggerTransitionData2(){

  // Create the u variable
  var u = Svg.selectAll("circle")
    .data(data2)

  u
    .enter()
    .append("circle") // Add a new circle for each new elements
    .merge(u) // get the already existing elements as well
    .transition() // and apply changes to all of them
    .duration(1000)
      .attr("cx", function(d){return d} )
      .attr("cy", 100)
      .attr("r", 10)
}

</script>

New dataset has less entries


This example is almost the same as the previous one. However, there is now one less circle in the second list than in the first list.


Steps:

  • This is the simplest example: both datasets have the same number of entry. We also consider they are ordered the same way, so position of circle 1 is always the first in the list.

  • First step: creating the circles. enter() allows to start a loop on every elements of data. append() is used to add a circle at each iteration.

  • Second step: bind the circles to a new dataset using data(). Here the number of entry is the same: no need to enter() to add new circle or to exit() to delete circles

  • Try: add an element to the second list: this element won't appear.

  • Try: remove the last element of the second list: the 4th circle won't disappear during the transition.
<!DOCTYPE html>
<meta charset="utf-8">

<!-- Load d3.js -->
<script src="https://d3js.org/d3.v4.js"></script>

<!-- Create a data3 to trigger the transition -->
<button onclick="triggerTransitionData3()">Trigger transition</button>

<!-- Create a div with just a rect in svg -->
<svg id="areadata3" width="960px" height="200px"></svg>

<script>

// create 2 datasets:
var data1 = [50, 100, 200,300]
var data2 = [70, 90, 230]

// Select the SVG area
var SVG = d3.select("#areadata3")

// Add circles at the top
SVG
  .selectAll("mycircles")
  .data(data1)
  .enter()
  .append("circle")
    .attr("cx", function(d){return d} )
    .attr("cy", 100)
    .attr("r", 10)

// A function that switch to the second dataset:
function triggerTransitionData3(){

  // Create the u variable
  var u = SVG.selectAll("circle")
    .data(data2)

  u
    .transition() // and apply changes to all of them
    .duration(1000)
      .attr("cx", function(d){return d} )
      .attr("cy", 100)
      .attr("r", 10)

  // If less group in the new dataset, I delete the ones not in use anymore
  u
    .exit()
    .transition() // and apply changes to all of them
    .duration(1000)
    .style("opacity", 0)
    .remove()
}

</script>

Application on real charts