Sing a Rainbow with D3.js

Red and yellow and pink and green
Purple and orange and blue
I can sing a rainbow
Sing a rainbow
Sing a rainbow too.

Play again!

This animation uses d3.js and a small amount of data. The idea is to mirror the lines of the nursery rhyme.

First a set of data is defined in Javascript arrays:

    var colours = ["Red", "Orange", "Yellow", "Green", "Blue", "Indigo", "Violet"];
    var order = [0, 2, 6, 3, 5, 1, 4];
    var delay = [100, 1000, 1000, 1000, 750, 1000, 1000]

These control the colours of the parts of the rainbow (colours), the order in which they will appear (order), and the delays between each one appearing (delay).

Next, we define some configuration variables to control how large the rainbow is, and how much space is left around it:

    var margin = { top: 30, right: 200, bottom: 50, left: 30, pad: 10 };
    var width = 800 - margin.left - margin.right;
    var height = 350 - margin.top - margin.bottom;

In order to draw the rainbow, we need to use SVG or Structured Vector Graphics. That means we need to add some elements to the HTML page. We do that with the following code:

    var svgContainer = d3
        .select(".rainbow")
        .append("svg")
        .attr("width", width)
        .attr("height", height)
        .append("g")
        .attr("transform", "translate(300,300)");
It asks D3 to select the <div> element which has the CSS class rainbow, and add a new <svg> element to it. Next, it sets the width and height so that our graphic is big enough. Inside that it add a new <g> element, which denotes graphics. It then moves in 300 pixels from the top and from the left before starting to draw. (Translate is the technical term for move.)

After that, we have to define an arc, which is the maths name for part of a circle. Arcs are defined by a starting angle, an ending angle and a radius. It easy to think of drawing a circle, but only putting the pen on the paper between the start and end angles.

var arc = d3
    .arc()
    .innerRadius(function (d) {
        return 300 - 20 * d;
    })
    .outerRadius(function (d) {
        return 300 - 20 * d - 20;
    })
    .startAngle(function (d) {
        return (-Math.PI / 2) * 0.7;
    })
    .endAngle(function (d) {
        return (Math.PI / 2) * 0.7;
    });
Computers do trigonometry slightly different to us, they don't use degrees for angles as the distance between each degree changes depending on how big the circle is. Instead they use something called radians. One radian is the same no matter how big the circle is, and is roughly equal to 57 degrees. A full circle goes from 0 to 2 x pi. We want to have a length the 70% of our circumference of our circle (so the length is 0.7 x pi) but centred on the line that points straight up and down. That's why the angles go from Math.PI / 2 to - Math.PI / 2.

The innerRadius is the length of the bottom (or in our case smaller) side of the rainbow lines, and the outerRadius is the larger side. The values are calculated from their place in the order of the rainbow. The value, or place is the value d in the functions.

Now we come to the graphics bit, actually drawing the lines. In SVG lines are called paths. We use D3's .data() function to draw one for each item in our array:

var path = svgContainer
    .selectAll("g.arc")
    .data(order)
    .enter()
    .append("g")
    .attr("class", "arc");
This creates a new <g> element for each item to hold our path. Lastly we actually draw the animation:
path
    .append("path")
    .transition()
    .delay(function (d, i) {
        thisDelay = 0;
        for (let index = 0; index < i + 1; index++) {
        thisDelay += delay[index];
        }
        return thisDelay;
    })
    .duration(150)
    .attr("fill", function (d, i) {
        return colours[d];
    })
    .attr("d", arc);
There's lots going on here, so we'll go from the top. First a path is added, and we need to use a transition() as it needs to be animated rather than all at once. The delay() function is used to draw the lines at different times. The delay array doesn't contain absolute values, so the loop adds up everything that happens before this line. The duration() instructs D3 to draw the line over 150 milliseconds. The attr("fill") call sets which colour will be used to draw the line itself, before using the arc generator from before to set the attr("d") or data for the path.