Enrico Bertini from Fell In Love With Data has been so kind to post an interview with me about Protovis and D3.

You can read the full interview here.

Thanks Enrico!

- TULP interactive
- +31 6 2455 1242
- janwillem@tulpinteractive.com
- @JanWillemTulp

October 14, 2011 – 14:43

Enrico Bertini from Fell In Love With Data has been so kind to post an interview with me about Protovis and D3.

You can read the full interview here.

Thanks Enrico!

April 1, 2011 – 11:15

One of the most common visualizations is a line chart. D3 is not a charting framework, but instead allows you to manipulate the document based on data. That’s what you’re actually doing with D3: adding elements to a document, removing them, updating them, etc. The advantage is that you are much more flexible in creating the visualization that you want. Some may consider it a slight disadvantage that you may do have to do some extra work to get things done.

For this tutorial we’re going to create a basic line chart with an x-axis and y-axis, tickmarks and labels.

First, we defined some variables:

var data = [3, 6, 2, 7, 5, 2, 1, 3, 8, 9, 2, 5, 7], w = 400, h = 200, margin = 20, y = d3.scale.linear().domain([0, d3.max(data)]).range([0 + margin, h - margin]), x = d3.scale.linear().domain([0, data.length]).range([0 + margin, w - margin])

The `data`

variable contains our dataset we want to display as a line chart. The `w`

and `h`

variables are used for the width and height of our chart and the `margin`

will be used to create some room to display our labels. The `y`

and `x`

variables are our linear scale functions, and remember that you can use `x`

and `y`

as functions. The scales are used to convert our data values (which are defined in the `domain`

of the scale) to x- and y-positions on the screen (which are defined in the `range`

of the scale). Note the use of `margin`

in the range.

Next we append an `svg`

element to our document with the proper width and height, and then we append a `g`

element to this `svg`

element so that all the elements that will be appended to this `g`

element will be grouped together. The transformation that we apply on the `g`

element makes sure that our coordinate grid moves down 200 pixels.

var vis = d3.select("body") .append("svg:svg") .attr("width", w) .attr("height", h) var g = vis.append("svg:g") .attr("transform", "translate(0, 200)");

In order to create a line chart, we’re going to add an `path`

element to our visualization. This `path`

element needs a `d`

attribute that contains the data for the path. Now, you could write that yourself, but that may be a little hard to do, so we use the helper function `d3.svg.line`

to do that for us (see Tutorial: Line interpolations in D3 for more details on this helper function). Now we add the `line`

variable which is, just like the scales earlier, a function that you can call. So when we bind our data to the path, this `d3.svg.line`

function will be called so that the value of the `d`

attribute will be created:

var line = d3.svg.line() .x(function(d,i) { return x(i); }) .y(function(d) { return -1 * y(d); })

The `i`

parameter is the index of the current item, and the `d`

is the current item itself. Note the usage of the scale functions (`x()`

and `y()`

to convert from `i`

and `d`

to x- and y-coordinates (note the -1. This is needed because right now the coordinate have a positive y-axis down, and since we have translated our `g`

down 200 pixels, we need to use the negative values of the y-axis now). To use our `line`

function we add a `path`

element to our `g`

element like this:

g.append("svg:path").attr("d", line(data));

Next up is adding the x-axis and y-axis. The following snippet adds them by appending a `line`

to our `g`

element.

g.append("svg:line") .attr("x1", x(0)) .attr("y1", -1 * y(0)) .attr("x2", x(w)) .attr("y2", -1 * y(0)) g.append("svg:line") .attr("x1", x(0)) .attr("y1", -1 * y(0)) .attr("x2", x(0)) .attr("y2", -1 * y(d3.max(data)))

Next are the labels. We can use a convenient function of the scales here: `x.ticks()`

and `y.ticks()`

. These functions will return the proper tickmarks, and you can provide how many you want. D3 does return nicely rounded numbers though. Also note the `.text(String)`

usage for obtaining the string value of the current data item. The labels are aligned in the center by using `.attr("text-anchor", "middle")`

. And for the y-labels I’ve used `dy`

in order to vertically position the labels correctly.

g.selectAll(".xLabel") .data(x.ticks(5)) .enter().append("svg:text") .attr("class", "xLabel") .text(String) .attr("x", function(d) { return x(d) }) .attr("y", 0) .attr("text-anchor", "middle") g.selectAll(".yLabel") .data(y.ticks(4)) .enter().append("svg:text") .attr("class", "yLabel") .text(String) .attr("x", 0) .attr("y", function(d) { return -1 * y(d) }) .attr("text-anchor", "right") .attr("dy", 4)

The last thing to do is to add the ticks themselves. These are actually just very short lines, so all you have to do is to provide the start x- and y position (`x1`

and `y1`

) and the end x- and y-position (`x2`

and `y2`

).

g.selectAll(".xTicks") .data(x.ticks(5)) .enter().append("svg:line") .attr("class", "xTicks") .attr("x1", function(d) { return x(d); }) .attr("y1", -1 * y(0)) .attr("x2", function(d) { return x(d); }) .attr("y2", -1 * y(-0.3)) g.selectAll(".yTicks") .data(y.ticks(4)) .enter().append("svg:line") .attr("class", "yTicks") .attr("y1", function(d) { return -1 * y(d); }) .attr("x1", x(-0.3)) .attr("y2", function(d) { return -1 * y(d); }) .attr("x2", x(0))

And that’s it. Finally, some of the styling of the objects was moved to a style section at the top of the page in order to get the final result:

path { stroke: steelblue; stroke-width: 2; fill: none; } line { stroke: black; } text { font-family: Arial; font-size: 9pt; }

View a live version here.

March 31, 2011 – 08:36

In this tutorial we’re going to cover some basic stuff in D3: ranges, min and max and merge. These are all pretty straightforward, but nevertheless very useful to have in your D3 toolbox!

Ranges are actually a pretty convenient way to generate a list of numbers. It is modeled after the built-in range method from Python. The full signature of the function looks like this:

d3.range(start, stop, step)

But the `stop`

and `step`

parameters are optional. So if you provide just the `start`

parameter, you would do something like this:

d3.range(5);

The result of this function call is an array of numbers, starting with 0, and the last number in the array is 4, so 5 is not included:

[0, 1, 2, 3, 4]

Now, if you provide a second argument, it will be interpreted as the `stop`

parameter. Note that the order of the parameters is: `start`

first, and then the `stop`

parameter. So, the result of this:

d3.range(3, 5);

is this:

[3, 4]

Note that again 5 is exclusive. At the moment it is not possible to create descending arrays directly with `d3.range()`

, so if the `start`

value is larger than the `stop`

value, you get an empty array. In order to create a descending array of numbers, you can just call the standard `reverse()`

Javascript function. So, to create the following result:

[4, 3, 2, 1]

you would write this:

d3.range(1, 5).reverse();

The last parameter you can use is the `step`

parameter. If you don’t provide a `step`

argument, it will default to 1. But if you do, then the result of this:

d3.range(5, 10, 2);

is this:

[5, 7, 9]

So, now you know how to generate a list of numbers in an easy way. Next up is the usage of `min`

and `max`

. It is really straightforward: to find the max number of an array of numbers, you use:

d3.max([4, 2, 6, 3, 8, 2]);

which will return 8 of course. Likewise, if you want to find the min number of an array, you use:

d3.min([4, 2, 6, 3, 8, 2]);

which will return 2 in this case.

The `d3.merge()`

can be used to merge an array of arrays so that it becomes one array. It’ll become clear when you see an example. If you have the following 2-dimensional array:

[[1, 2, 3], [4, 5, 6]]

you can turn this into a 1-dimensional array by calling the `d3.merge()`

function:

d3.merge([[1, 2, 3], [4, 5, 6]]);

This will result in the following 1-dimensional array:

[1, 2, 3, 4, 5, 6]

The `d3.merge()`

function will only merge one level. For example, the following 3-dimensional array will * not* result in a single 1-dimensional array:

d3.merge([[[1, 2], [3]], [[4, 5], [6]]]);

It will however, result in a 2-dimensional array:

[[1, 2], [3], [4, 5], [6]]

March 23, 2011 – 09:48

In this tutorial we’re going to explore line interpolations in D3.

First we start with 2 scales that we will use to convert values to x- and y-coordinates on the screen:

var x = d3.scale.linear().domain([0,10]).range([0,400]), y = d3.scale.linear().domain([0,1]).range([0,50]), groupHeight = 60, topMargin = 100

Next we generate some random data:

var data = [] d3.range(10).forEach(function(d) { data.push(Math.random()) })

We’re also creating an array which contains all the possible interpolations D3 supports. We’ll see the effects of every interpolation in a moment:

var interpolations = [ "linear", "step-before", "step-after", "basis", "basis-closed", "cardinal", "cardinal-closed"]

In SVG there is a difference between a `line`

and a `path`

. A `line`

is a straight line where you define the start and end position of the line: , whereas with a path you draw the outline of any arbitrary shape by specifying a series of connected lines, arcs, and curves. You do this by specifying the `d`

attribute of the `path`

. Every path must begin with a moveto command. The command letter is a capital `M`

followed by an x- and y-coordinate, separated by commas or whitespace. This command sets the current location of the “pen” that’s drawing the outline. This is followed by one or more lineto commands, denoted by a capital `L`

, also followed by x- and y-coordinates, and separated by commas or whitespace. You can see more of this specification here.

In our example we’re not actually creating an SVG line, but an SVG path, so we need to set the `d`

attribute of the paht. Luckily D3 has a helper function to ease the burden to create this data: `d3.svg.line`

. For this helper function you can set:

- an accessor function for obtaining x values
- an accessor function for obtaining y values
- an interpolation type, which defaults to
`linear`

- a tension value which affects the
`cardinal`

interpolations only

In our example, we want to show the different kinds of interpolations for the same data, so we create a function that takes the name of an interpolation as an argument, and then returns the `d3.svg.line`

function as a result. This is the code that does that (you can play with the out-commented `tension`

property to see the effect):

function getLine(interpolation) { return d3.svg.line().x(function(d,i) { return x(i) }).y(function(d) { return y(d) }).interpolate(interpolation) //.tension(0) }

Note the following: the function for x has 2 arguments: `d`

and `i`

. The `d`

is the current item in the dataset (which we will provide later), and `i`

is the index of the current item in the dataset. Also note that we’re using the x-scale to convert `i`

to an x-coordinate, and the y-scale to convert the data value to a y-coordinate.

Now we initialize the visualization:

var vis = d3.select("body") .append("svg:svg") .attr("class", "vis") .attr("width", window.width) .attr("height", window.height)

Next up is greating a group for each of the lines we want to show:

var lg = vis.selectAll(".lineGroup") .data(interpolations) .enter().append("svg:g") .attr("class", "lineGroup") .attr("transform", function(d,i) { return "translate(100," + (topMargin + i * groupHeight) + ")" }).each(drawLine)

We set the interpolations array as data for this group, so that `svg:g`

elements for each of the interpolations will be added to the visualization. The `svg:g`

element can be used to group other elements together, so that if you apply a transformation to the group for instance, it will be applied to all of its members. Note that we add the class `lineGroup`

in our selection to select all these elements. Next we set the `transform`

attribute, and we use the index to position the groups based on their position in the interpolation array. For each of the group, we want to draw a line. We do that by calling the `drawLine`

function in the `.each(drawLine)`

statement. The drawLine function itself looks like this:

function drawLine(p,j) { d3.select(this) .selectAll(".lineGroup") .data(data) .enter().append("svg:path") .attr("d", getLine(p)(data)) .attr("fill", "none") .attr("stroke", "steelblue") .attr("stroke-width", 3) //.attr("stroke-dasharray", "15 5") }

The drawLine function itself has to parameters: `p`

and `j`

where `p`

is the parent data item (the current interpolation name), and `j`

is the parent index. First we select the current element with `d3.select(this)`

, and next we select all the `.lineGroup`

elements. We assign the line data to the data property, and append a `path`

to each `lineGroup`

element. The `d`

attribute calls the `getLine`

function and provides the current interpolation name as an argument. The result of that is the `d3.svg.line`

function with the that uses the interpolation we just provided. Next we assign the line data to this function so that D3 will calculate the `data`

string that will be used by the `d`

attribute of the `svg:path`

element. Finally we set some basic properties. The final out-commented is one of the `stroke`

properties you can set, where 15 is the dash length and 5 is the gap length. Just play around with those properties to see what else is possible.

This concludes this tutorial. All the lines you see are using the same data, but they use different interpolations.

March 22, 2011 – 07:58

See the final version here.

This is an example of Conway’s Game of Life, built in D3. According to Wikipedia:

The Game of Life, also known simply as Life, is a cellular automaton devised by the British mathematician John Horton Conway in 1970.[1]The “game” is a zero-player game, meaning that its evolution is determined by its initial state, requiring no further input. One interacts with the Game of Life by creating an initial configuration and observing how it evolves.

There are only 4 rules in the Game of Life:

The universe of the Game of Life is an infinite two-dimensional orthogonal grid of square cells, each of which is in one of two possible states, live or dead. Every cell interacts with its eight neighbours, which are the cells that are horizontally, vertically, or diagonally adjacent. At each step in time, the following transitions occur:

- Any live cell with fewer than two live neighbours dies, as if caused by under-population.
- Any live cell with two or three live neighbours lives on to the next generation.
- Any live cell with more than three live neighbours dies, as if by overcrowding.
- Any dead cell with exactly three live neighbours becomes a live cell, as if by reproduction.

The initial pattern constitutes the seed of the system. The first generation is created by applying the above rules simultaneously to every cell in the seed—births and deaths occur simultaneously, and the discrete moment at which this happens is sometimes called a tick (in other words, each generation is a pure function of the preceding one). The rules continue to be applied repeatedly to create further generations.

I’m sure there are multiple ways of implementing Conway’s Game of Life, but this is just one of them. We start by declaring a few variables:

var ccx = 120, // cell count x ccy = 30, // cell count y cw = 5, // cellWidth ch = 5, // cellHeight del = 100, // delay xs = d3.scale.linear().domain([0,ccx]).range([0,ccx * cw]), ys = d3.scale.linear().domain([0,ccy]).range([0,ccy * ch]), states = new Array()

The states variable will be used to hold the states of each cell: `true`

for on and `false`

for off. Next up, we’re going to fill the states array with data:

d3.range(ccx).forEach(function(x) { states[x] = new Array() d3.range(ccy).forEach(function(y) { states[x][y] = Math.random() > .8 ? true : false }) })

This is a good example of the use of `d3.range([start], stop, [step])`

function which returns a range of number. We’re using only the stop argument, so in our case the first use of the `range()`

function will generate an array of 0 (which is the default start) to 120, and uses the default step of 1. As you can see we’re building a 2-dimensional array here, so that we can easily access each state, for example `states[0][0]`

to access the first state. We randomly set the state to either `true`

or `false`

.

I have created the `toGrid()`

function so that the 2-dimensional array is turned into an array of Objects, so that D3 can easily bind all the values to an SVG element (binding to the 2-dimensional array directly should also be possible, which I leave as an exercise for you at the moment…):

function toGrid(states) { var g = [] for (x = 0; x < ccx; x++) { for (y = 0; y < ccy; y++) { g.push({"x": x, "y": y, "state": states[x][y]}) } } return g }

Now we initialize the visualization:

var vis = d3.select("body") .append("svg:svg") .attr("class", "vis") .attr("width", window.width) .attr("height", window.height)

After that we create the initial state of the grid:

vis.selectAll("rect") .data(function() { return toGrid(states) }) .enter().append("svg:rect") .attr("stroke", "none") .attr("fill", function(d) { return d.state ? "green" : "white" }) .attr("x", function(d) { return xs(d.x) }) .attr("y", function(d) { return ys(d.y) }) .attr("width", cw) .attr("height", ch)

Note that the data that is bound to `svg:rect`

elements is the result from the `toGrid()`

function. Also, there are multiple ways to show the on and off state of a cell, for instance using the `visibility`

property. I chose to use the `fill`

property in this case. It is either colored green or white (which is the background color) based on the state. This is all that’s needed to create the initial grid. Now comes the fun part: creating the new generations:

function createNewGeneration() { var nextGen = new Array() for (x = 0; x < ccx; x++) { nextGen[x] = new Array() for (y = 0; y < ccy; y++) { var ti = y - 1 < 0 ? ccy - 1 : y - 1 // top index var ri = x + 1 == ccx ? 0 : x + 1 // right index var bi = y + 1 == ccy ? 0 : y + 1 // bottom index var li = x - 1 < 0 ? ccx - 1 : x - 1 // left index var thisState = states[x][y] var liveNeighbours = 0 liveNeighbours += states[li][ti] ? 1 : 0 liveNeighbours += states[x][ti] ? 1 : 0 liveNeighbours += states[ri][ti] ? 1 : 0 liveNeighbours += states[li][y] ? 1 : 0 liveNeighbours += states[ri][y] ? 1 : 0 liveNeighbours += states[li][bi] ? 1 : 0 liveNeighbours += states[x][bi] ? 1 : 0 liveNeighbours += states[ri][bi] ? 1 : 0 var newState = false if (thisState) { newState = liveNeighbours == 2 || liveNeighbours == 3 ? true : false } else { newState = liveNeighbours == 3 ? true : false } nextGen[x][y] = newState } } return nextGen }

This function implements the Game of Life rules mentioned earlier. We’re building a new 2-dimensional array here that will eventually be used to replace the value of the `states`

variable. We’re determining the top, right, bottom and left index to use for each cell. If one of the index numbers would fall out of the range (greater than the length, or smaller than 0), then the index of the opposite side is used. For example, if we are currently at cell(0,5) (which means x = 0 and y = 5), then to calculate `li`

(left index) we end up with index -1. This of course does not exist, so we use `ccx.length - 1`

instead, which is 199. This way all the cells will have a top, right bottom and left to work with. Next the number of `liveNeighbours`

is calculated by summing up the number of `true`

states of 8 neighbour cells. Finally the new state for this cell is calculate by actually applying the Game of Life rules. The new state is stored in the temporary array, which is being returned as the result of the function.

The last part we need to do is to create new generations repeatedly and animate the grid accordingly:

function animate() { states = createNewGeneration() d3.selectAll("rect") .data(toGrid(states)) .transition() .attr("fill", function(d) { return d.state ? "green" : "white" }) .delay(del) .duration(0) } setInterval("animate()", del)

This is done by the `setInterval()`

Javascript function. We call the animate function with a delay of 100 milliseconds (the value of the `del`

variable). The animate function itself is pretty straightforward. The value of the `states`

variable is replaced with a new generation. Then all the `rect`

elements are selected and the new generation is bound to these rectangles (note that again the 2-dimensional array is converted with the `toGrid()`

function. After that we define the `transition()`

we want to apply, and all we do is modify the `fill`

property of each cell. Setting the delay to the `del`

value as well seems to be working quite well. I guess this helps the browser to have enough time to calculate the new generation. We explicitly set the duration to 0 to override the default.

That’s all there’s to it. I thought it would be more complex to build the Game of Life in D3, but it appears to be fairly straightforward. This code of course does require some calculation power from your browser, so just play around with the delay or grid size to get an optimal setting that works for you. Also, you can play with various other attributes to create interesting effects, for instance, using this for creating the grid gives a nice blurry effect:

vis.selectAll("rect") .data(function() { return toGrid(states) }) .enter().append("svg:rect") .attr("stroke", "none") .attr("fill", function(d) { return d.state ? "green" : "white" }) .attr("fill-opacity", .3) .attr("x", function(d) { return xs(d.x) }) .attr("y", function(d) { return ys(d.y) }) .attr("width", function() { return 2 * cw }) .attr("height", function() { return 2 * ch })

Enjoy!

March 20, 2011 – 21:17

Check out the final result here!

D3 is a brand new visualization framework created by Mike Bostock. It is the successor of the successful great visualization framework Protovis. There are a few differences and similarities with Protovis. One of the most important differences is that in D3 you work more directly with SVG which gives you much greater flexibility than Protovis. Also, the performance of D3 is much better than Protovis, especially with animation, because in D3 only the properties that are changing are updated, instead of re-rendering the entire visualization. And once you dive into D3, it’s really easy to pick up, so let’s get started!

The example we’re going to work on is really simple and will show you some of the basic concepts of D3. Basically we just plot hidden circles randomly on the screen, and then transition them to a portion of the screen. Then we add some interaction to it so that the circles will move once you move your mouse over them.

The first thing you need to do is to make a reference to D3 from your HTML page. You can download D3, or make a link to a stable version on GitHub. The reference I used for this tutorial is: https://github.com/mbostock/d3/raw/v1.8.2/d3.js

To get started we generate some data that we want to bind the circles to. All we do here is just generate a bunch of x and y values and put them in an array:

var data = [] for (i=0; i < 1000; i++) { data.push({"x": Math.random(), "y": Math.random()}) }

Now we need to add an SVG element to the body of the page. This is how you do that:

var h = 1000 var vis = d3.select("body") .append("svg:svg") .attr("width", screen.width) .attr("height", screen.innerHeight)

First the body is selected using `d3.select("body")`

which is a similar selection you would do with jQuery selectors. The `h`

variable is the height I want to use. I refer to it later in the code. Next we’re going to plot the invisible circles:

var x = d3.scale.linear().domain([0,1]).range([screen.width / 2 - 400,screen.width / 2 + 400]), y = d3.scale.linear().domain([0,1]).range([0,h]), r = d3.scale.linear().domain([0,1]).range([5,10]), c = d3.scale.linear().domain([0,1]).range(["hsl(250, 50%, 50%)", "hsl(350, 100%, 50%)"]).interpolate(d3.interpolateHsl) vis.selectAll("circle") .data(data) .enter().append("svg:circle") .attr("cx", function(d) { return x(d.x) }) .attr("cy", function(d) { return y(d.y) }) .attr("stroke-width", "none") .attr("fill", function() { return c(Math.random()) }) .attr("fill-opacity", .5) .attr("visibility", "hidden") .attr("r", function() { return r(Math.random()) })

First we create some `d3.scale`

variables. Since we’re working with random data, we need to convert the output of `Math.random()`

outputs to screen positions, radii and colors. We use linear scales, and you see that the domain is `[0,1]`

for all the scales. That’s because the output of `Math.random()`

yields a number between 0 and 1. The range tells us to which range the domain should be converted. Each of these variables is actually a function, so you can use `r`

for example as a function to convert a number of the domain to a number of the range, so for example: `r(.5)`

will result in 7.5. The `c`

variable will turn any value between 0 and 1 to a color.

Next we select all the `circle`

elements, which is an empty collection at this time. We bind our data to this collection with the `.data(data)`

statement, and then use the `.enter().append("svg:circle")`

statement so that each element in our data array will be bound to a new `svg:circle`

element. Here we set various properties: `cx`

for the x-position of the circle (note that in SVG position `0,0`

is the top left corner). The `cy`

is used for the y-position of the circle, the `r`

is used for the radius of the circle. The other properties speak for themselves. Note that a difference with Protovis is that you cannot use the shorthand way for using a function: `function(d) x(d.x)`

, but in D3 you have to write out the `return`

keyword and the curly braces: `function(d) { return x(d.x) }. `

Now, when you view this page, you’ll see nothing, because we’ve set the `.attr("visibility", "hidden")`

. If you want to see the results so far, just remove this line. Next we want to move all those hidden circles to their new random position, so that together they will form the colorful bar. This is the code that does just that:

var y2 = d3.scale.linear().domain([0,1]).range([h/2 - 20, h/2 + 20]) var del = d3.scale.linear().domain([0,1]).range([0,1]) d3.selectAll("circle").transition() .attr("cx", function() { return x(Math.random()) }) .attr("cy", function() { return y2(Math.random()) }) .attr("visibility", "visible") .delay(function(d,i) { return i * del(Math.random()) }) .duration(1000) .ease("elastic", 10, .45)

First we need 2 extra scales to convert random numbers: `y2`

which is the scale that converts a random number to a new range. The `del`

scale will be used for the delay of the transition. We now first select all the circle elements and we start a `transition()`

. We set a few properties of the transition itself: delay, duration and ease, and also the properties of the selected circles elements that we want to animate. The `cx`

, `visibility`

and `cy`

properties of the circle will all be animated. All you have to do is provide the end state for the properties you want to animate. The animation will last 1000 milliseconds, there will be some delay for each circle before starting the transition, and we apply an elastic easing function to create the nice elastic bouncing effect. Go see what you’ve got so far! Fun, isn’t it?

The last part will be a little extension of the piece of code we used to add the circles initially, so change your code so that it looks like this:

vis.selectAll("circle") .data(data) .enter().append("svg:circle") .attr("cx", function(d) { return x(d.x) }) .attr("cy", function(d) { return y(d.y) }) .attr("stroke-width", "none") .attr("fill", function() { return c(Math.random()) }) .attr("fill-opacity", .5) .attr("visibility", "hidden") .attr("r", function() { return r(Math.random()) }) .on("mouseover", function() { d3.select(this).transition() .attr("cy", function() { return y2(Math.random()) }) .delay(0) .duration(2000) .ease("elastic", 10, .3) })

A `mouseover`

event has been added to each of the circles. And basically we’re doing something similar again: just add a transition, set some transition properties and the properties of the selected elements. In this case `d3.select(this)`

selects the current circle so that you apply the transition to the current selected circle.

That’s it!