Tutorial: Introduction to D3

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!

39 Comments

  • April 25, 2011 - 23:15 | Permalink

    Found a little mistake in your code, Jan. You begin the tutorial by defining the data variable (var data = []) but later refer to it as d (.attr(“cx”, function(d) { return x(d.x) })). I needed to change all references to “d” to “data” to get this to run.

    • April 26, 2011 - 11:15 | Permalink

      Are you sure your code is correct? If you look at the final result at: http://janwillemtulp.com/d3tut001/ it works just fine. The d in d.x refers to the d in function(d). For all the elements in my selection I want to apply this function, and to get a reference to each individual element, you need to provide a function with a parameter that has a name you can choose freely, and I chose d in this case.

      So this fragment: .attr("cx", function(d) { return x(d.x) }) sets the cx attribute for all the elements in my selection, and then applies the function to set the value. The value is obtained by getting the x value of each individual item from my dataset, and give that value to the x() function so that it will be converted to the proper scale.

      Does this make sense?

      Do you have a code example where it doesn’t work as expected?

    • March 5, 2012 - 22:21 | Permalink

      This “d” got me hung up a bit too. I had no problems with the code, but understanding it took a moment.

      Coming from AS3 here, In my head I wanted to do something like this…


      var i=0 (for i<50; i++) {
      var d:circle = circlesArray[i] as Circle;
      d.y = dataArray[i];
      }

      But this works better and because the data is binded it’s much cleaner.


      .attr("cy", function(d) {return y(d.y)})

      Only getting started, but is that the general idea?

  • Brandon
    May 11, 2011 - 23:43 | Permalink

    The code in the article is not sync’ed with the live vis. It has two error types that prevent it from working:

    (1) The var statements (var x … var y) are separated by commas. Either remove the trailing commas or remove the other var statements.

    (2) Attribute functions for cx and cy are broken because ‘d’ is not included as a function argument, so d.x is undefined.

    If you fix these two, it works.

  • May 16, 2011 - 21:37 | Permalink

    Fixed! Thanks!

  • June 24, 2011 - 18:16 | Permalink

    Jan, great tutorial, thanks very much – introductory material for d3 is in short supply.

    I notice that you have a redundant variable in the code:

    var del = d3.scale.linear().domain([0,1]).range([0,1])

    …is unnecessary – just mapping a value between 0 and 1 to a range of 0 and 1 will return the original value!

    I guess it’s useful if you wanted to use a longer delay than 1, but you could also just use a multiplier for this when setting the delay, e.g.:

    .delay(function(d,i) { return i * Math.random() * 2 })

    • June 24, 2011 - 21:12 | Permalink

      you’re right! I guess that must have stayed in there after trying different attempts. Good feedback, thanks!!

  • bruce
    July 1, 2011 - 09:24 | Permalink

    why the example not working in firefox?

  • July 9, 2011 - 23:47 | Permalink

    To be honest, I am not quite sure why. I would have to dive into it. So, if you have any suggestions, please share…

    • Stephan
      August 13, 2011 - 20:04 | Permalink

      In Firefox the height of the svg:svg is not correct. To fix that you need to use window.innerHeight instead of window.height

    • Dave
      August 19, 2011 - 14:45 | Permalink

      Hmm, the examples on the d3 website work, so it’s apparently something you are doing wrong. I’d have thought FF was a rather major target to have work right, but what do I know? There’s not much point in anybody trying to learn techniques from tutorials that are known-broken.

      Surprisingly, people who come to this page to learn aren’t really in the best position to analyse the cause of the problem. The people who write tutorials are the ones who are supposed to understand the subject best.

      FWIW, for anybody who hasn’t tried this, the error message is “d3 is not defined” on line 36.

      • August 21, 2011 - 09:11 | Permalink

        Hi Dave,

        I’m sorry to hear that the example didn’t work for you in FireFox. The actual solution has already been mentioned by Stephen, in the comment above, and that’s also implemented in the solution.

        Your problem seems to be a different one. Your error message actually means that it is unable to find the D3 library at all. In code I am referencing a specific version of D3 on GitHub, and although I am unable to reproduce your problem at this moment, I was able to reproduce it last Friday: for some odd reason I was not able to reach the GitHub site with FireFox, but I was able to reach it with other browsers. So, in order to prevent this problem the next time, I downloaded this library, and made a copy local on my server. This should prevent this problem.

        Does it work in FireFox for you too now?

        • Guillaume
          August 10, 2012 - 09:37 | Permalink

          This is because your script loaded before d3 loaded.

  • Mark A
    August 24, 2011 - 17:15 | Permalink

    How do you make a reference to D3 on an HTML page?

    • August 24, 2011 - 23:18 | Permalink

      With reference, I mean setting the location of the D3 library in the script tags, for example: <script type="text/javascript" src="d3-v1.8.2.js"></script>

      • Steve
        September 5, 2011 - 01:59 | Permalink

        Jan, using in the header still gives me an error that d3 is not defined. Do you need something else included?

  • Matt
    August 30, 2011 - 19:28 | Permalink

    Awesome tutorial, look forward to trying the rest! Thanks!

  • January 24, 2012 - 21:54 | Permalink

    Thanks for the tutorial – just worked through it and am beginning to get a feel for D3. A tip for other users – Chrome does a better job of rendering the result. The transitions were very choppy in firefox for me.

  • Sam Rodgers
    February 2, 2012 - 16:10 | Permalink

    Great tutorial – many thanks for sharing Jan. You have done a brilliant job of explaining several of the quirks of D3 that I had been struggling to get to grips with.

  • Pingback: D3 (Data-Driven Documents) | Craig's Musings

  • March 14, 2012 - 06:31 | Permalink

    I had heard how hard D3 was, so was expecting to be really confused — but this tutorial broke everything down nicely and was a great intro for me. Thanks so much!

  • March 21, 2012 - 13:21 | Permalink

    Hi,

    This is a great example. Thanks for publishing it.

    An idea for a future publication might be to walk through the creation of a simple chart, like a pie chart or a bar chart, that has features that become more complex, as the example progresses. For example…

    - start with drawing the pie and assigning colors to each arc.
    - adding labels to the canvas
    - adding color keys to the labels
    - adding a mouseover to the labels that cause the arcs to animate
    - adding a mouseclick to the labels that shows how to open another html page
    - etc.

    Anyhow, keep it up. Love the posts.

    Frank

  • anon
    April 15, 2012 - 03:13 | Permalink

    Hey there, nice tutorial, i was searching for a good easing example and i found your blog.

    Your blog style is great but i found it very hard to see links, i practically had to turn my lcd monitor upwards to get enough contrast to see them.
    Also the first thing I do when i see a tutorial is look for a link to the demo, took me forever to find it, even though its on the first line :|

    I was ready to leave the site but glad i diddnt.
    I like the way this site formats its tutorials, perhaps laying out your tutorial like this would help.

    http://net.tutsplus.com/tutorials/javascript-ajax/design-and-code-an-integrated-facebook-app/

    • April 16, 2012 - 10:09 | Permalink

      thanks for your feedback, and these are useful suggestions. I’ll look into it! Thanks!

  • Pingback: d3.js | Pearltrees

  • Pingback: D3 – another acronym to learn « Drunks&Lampposts

  • Pingback: Visualizing Swiss politicians on Twitter using D3.js | visurus

  • Pingback: Part 2: The essential collection of visualisation resources可视化编程语言和环境 @ dodo糯

  • Pingback: Webdesign and Development Resources - Page 3

  • Pingback: Frontend Development | espaco99

  • Pingback: 直接拿来用!最火的前端开源项目 | HTML5 CSS3 JavaScript

  • Pingback: 直接拿来用!最火前端开源项目(二) | 农夫庄园

  • Leave a Reply

    Your email address will not be published. Required fields are marked *

    *

    You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>