054 D3JS

25 Oct 2013

D3JS or data-driven documents is a powerful library to visualize data. In this episode we will learn how to display data using html and css, svg as well as external data in file formats such as json or csv. We will end off by plotting some charts to explore transition, scaling and axis with a tiny project on the growth of world population!

Download video: mp4

Sample code: Github

Version: 3.3.3

Similar episodes: 014 Local Web Server, 050 DOM, 051 SVG

Background on D3JS

  1. Main website - with API reference, plugins and components
  2. Tutorials
  3. Dashing D3JS
  4. Interactive Data Visualisation for the Web

Things to learn with D3JS

1. installing d3js

  1. why is data visualisation important?
  2. create a simple HTML file index.html with a javascript link to the d3 library from cdnjs

    <!doctype html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
      <title>D3JS</title>
      <script src="http://cdnjs.cloudflare.com/ajax/libs/d3/3.3.3/d3.min.js"></script>
    </head>
    <body>
    
        <p>Learning the Periodic Table!</p>
    
    </body>
    </html>
    
  3. open up the browser console

    d3
    d3.version
    

2. adding elements to the dom

  1. in the browser console, try these:

    d3.select('body').append('p');
    d3.select('body').append('p').style('color', 'blue');
    d3.select('body').append('p').style('color', 'blue').text('ohai');
    

3. working with various data types: json, csv or array

  1. Data with array

    add to the HTML file:

    <script>var d = [ "Hydrogen", "Helium", "Lithium" ];</script>
    

    try these in the browser console (look at both the Elements planel + the console after each line)

    d3.select('body');
    d3.select('body').selectAll('p');
    d3.select('body').selectAll('p').data(d); // joins an array of data with the current selection
    d3.select('body').selectAll('p').data(d).enter(); // return a reference to each data element that did not have a corresponding existing DOM Element
    d3.select('body').selectAll('p').data(d).enter().append('p');
    d3.select('body').selectAll('p').data(d).enter().append('p').text('Elements!!!');
    d3.select('body').selectAll('p').data(d).enter().append('p').text(function(e){ return e; });
    d3.select('body').selectAll('p').data(d).enter().append('p').text(function(e, i){ return 'Element ' + (i+1) + ': ' + e; });
    

    final

    <script>
    var d = [ "Hydrogen", "Helium", "Lithium" ];
    d3.select('body').selectAll('p').data(d).enter().append('p').text(function(e, i){
      return 'Element ' + (i+1) + ': ' + e;
    });
    </script>
    
  2. Data with CSV

    in file elements.csv

    Element,Symbol
    Hydrogen,H
    Helium,He
    Lithium,Li
    

    inside <script>

    d3.csv("elements.csv", function(data) {
    console.log(data);
      d3.select('body').selectAll('p').data(data).enter().append('p').text(function(e, i){
        return e.Element + ' [' + e.Symbol + ']';
      });
    });
    
  3. Data with JSON

    in file elements.json

    [
      {"element": "Hydrogen", "symbol": "H"},
      {"element": "Helium",   "symbol": "He"},
      {"element": "Lithium",  "symbol": "Li"}
    ]
    

    inside <script>

    d3.json("elements.json", function(data) {
    console.log(data);
      d3.select('body').selectAll('p').data(data).enter().append('p').text(function(e, i){
        return e.element + ' [' + e.symbol + ']';
      });
    });
    
  4. loading more data types

4. drawing with data

  1. intial display

    <style>
      .circle {
        display: inline-block;
        background-color: peru;
        height: 50px;
        width: 50px;
      }
    </style>
    
    …
    
    var dataset = [ 0.383, 0.949, 1, 0.532, 11.21, 9.45, 4.01, 3.88];
    
    d3.select('body')
      .selectAll('div')
      .data(dataset)
      .enter()
      .append('div')
      .attr('class', 'circle');
    
  2. add on variable height

    .circle {
        ...
        width: 10vw;
    }
    …
    d3.select('body')
      .selectAll('div')
      .data(dataset)
      .enter()
      .append('div')
      .attr('class', 'circle')
      .style('height', function(d) {
        return d*2.5 + 'vw';
      });
    
  3. with css and html

    <style>
    .circle {
        display: inline-block;
        border-radius: 50%;
        background-color: peru;
        margin-right: 1vw;
        vertical-align: bottom;
        position: relative;
    }
    </style>
    

    draw the chart/graph

    var dataset = [ 0.383, 0.949, 1, 0.532, 11.21, 9.45, 4.01, 3.88];
    
    d3.select("body").selectAll("div")
      .data(dataset)
      .enter()
      .append("div")
      .attr("class", "circle")
      .style("height", function(d) {
        var circleHeight = d * 2.5;
        return circleHeight + 'vw';
    })
    .style("width", function(d) {
        var circleWidth = d * 2.5;
        return circleWidth + 'vw';
    });
    
  4. with SVG

    <script>
    
    var dataset = [ // [diameter, astronomical unit, planet]
                    [0.383, 0.4, "Mercury"],
                    [0.949, 0.7, "Venus"],
                    [1, 1, "Earth"],
                    [0.532, 1.5, "Mars"],
                    [11.21, 5.2, "Jupiter"],
                    [9.45, 9.58, "Saturn"],
                    [4.01, 19.23, "Uranus"],
                    [3.88, 30.1, "Neptune"]
                  ];
    var w = 1240;
    var h = 700;
    
    var svg = d3.select("body")
      .append("svg")
      .attr("width", w)
      .attr("height", h);
    
    svg.selectAll("circle")
     .data(dataset)
     .enter()
     .append("circle")
     .attr("cx", function(d) {
        return d[1] * 30;
      })
      .attr("cy", function(d, i) {
        return 50 + i * 40;
      })
      .attr("r", function(d) {
        return d[0]*2;
      })
      .attr("fill", "grey");
    
    svg.selectAll("text")
     .data(dataset)
     .enter()
     .append("text")
     .text(function(d) {
       return d[2] + ': ' + d[0] + ' diameter with ' + d[1] + 'AU';
      })
      .attr("x", function(d) {
        return 25 + d[1]*30;
      })
      .attr("y", function(d, i) {
        return 55 + i * 40;
      })
      .attr("font-family", "sans-serif")
      .attr("font-size", "14px")
      .attr("fill", "black");
    
    </script>
    

5. transition

  1. click for action

    d3.selectAll("p").on("click", function() {
      svg.selectAll("text")
       .data(dataset)
       .transition()
       .delay(1000)
       .duration(1000)
       .ease("linear")
       .attr("x", function(d, i) {
          return w - 300;
       })
       .each("end", function() {
         d3.select(this)
          .text(function(d) {
             return d[2];
           });
       });
    
      svg.selectAll("circle")
        .data(dataset)
        .transition()
        .delay(1000)
        .duration(1000)
        .ease("linear")
        .attr("fill", "brown")
        .attr("r", function(d) {
          return d[0]*5;
        });
    
    });
    

6. Scaling values

  1. plot the data - but you will see that the scale is not correct

    var w = 2000;
    var h = 400;
    var padding = 50;
    
    var dataset = [
          [1, 2],
          [53, 3],
          [35, 4],
          [45, 5],
          [52, 6],
          [62, 7]
        ];
    
    var svg = d3.select('body')
      .append('svg')
      .attr('width', w)
      .attr('height', h);
    
    var lineFunction = d3.svg.line()
      .x(function(d) { return d[0]; })
      .y(function(d) { return d[1]; })
      .interpolate("basis");
      // linear, step-before, step-after, basis, basis-opened, basis-closed
    
    var lineGraph = svg.append("path")
      .attr("d", lineFunction(dataset))
      .attr("stroke", "lightblue")
      .attr("stroke-width", 5)
      .attr("fill", "none");
    
    svg.selectAll("text")
     .data(dataset)
     .enter()
     .append("text")
     .text(function(d) {
        return d[0] + ', ' + d[1];
     })
     .attr("x", function(d) {
        return d[0];
     })
     .attr("y", function(d) {
        return d[1];
     })
     .attr("font-family", "sans-serif")
     .attr("font-size", "14px")
     .attr("fill", "grey");
    
  2. add the scaling to the data

    var xScale = d3.scale.linear()
     .domain([d3.min(dataset, function(d) { return d[0]; }), d3.max(dataset, function(d) { return d[0]; })])
     .range([0, w]);
    
    var yScale = d3.scale.linear()
     .domain([d3.min(dataset, function(d) { return d[1]; }), d3.max(dataset, function(d) { return d[1]; })])
     .range([0, h]);
    

    amend the line to add xScale and yScale:

    var lineFunction = d3.svg.line()
      .x(function(d) { return xScale(d[0]); })
      .y(function(d) { return yScale(d[1]); })
      .interpolate("basis");
    

    try with various .interpolate('option') - linear, step-before, step-after, basis, basis-opened, basis-closed

  3. amend svg width and height to fit the graph

  4. amend the text label to add xScale and yScale

    svg.selectAll("text")
     .data(dataset)
     .enter()
     .append("text")
     .text(function(d) {
        return d[0] + ', ' + d[1];
     })
     .attr("x", function(d) {
        return xScale(d[0]);
     })
     .attr("y", function(d) {
        return yScale(d[1]);
     })
    
  5. play around with the height and width of the svg and all the values will scale!

  6. change the data

    // http://en.wikipedia.org/wiki/World_population#Milestones_by_the_billions
    var dataset = [
          [1927, 2],
          [1960, 3],
          [1974, 4],
          [1987, 5],
          [1999, 6],
          [2012, 7]
        ];
    

7. Adding the axis

  1. define the x-axis and y-axis

    var xAxis = d3.svg.axis()
      .scale(xScale)
      .orient("bottom")
      .ticks(5);
    
    var yAxis = d3.svg.axis()
      .scale(yScale)
      .orient("left")
      .ticks(5);
    
  2. insert the axis with padding so that it is shifted

    svg.append("g")
     .attr("class", "axis")
     .attr("transform", "translate(0," + (h - padding) + ")")
     .call(xAxis);
    
    svg.append("g")
     .attr("class", "axis")
     .attr("transform", "translate(" + padding + ",0)")
     .call(yAxis);
    
  3. fix the scale range

    var xScale = d3.scale.linear()
     …
     .range([padding, w - padding*2]);
    
    var yScale = d3.scale.linear()
     ...
     .range([padding, h-padding]);
    
  4. axis styling

    <style>
      .axis path,
      .axis line {
        fill: none;
        stroke: black;
        shape-rendering: crispEdges;
      }
      .axis text {
        font-family: sans-serif;
        font-size: 11px;
      }
    </style>
    
  5. label

    svg.selectAll("text")
    ...
     .text(function(d) {
        return 'Y' + d[0] + ', ' + d[1] + 'b';
     })
    
  6. scatter plot instead of line graph

    // comment off var lineFunction and var lineGraph
    
    …
    
    var rScale = d3.scale.linear()
      .domain([0, d3.max(dataset, function(d) { return d[1]; })])
      .range([2, 5]);
    
    …
    
    svg.selectAll("circle")
     .data(dataset)
     .enter()
     .append("circle")
     .attr("cx", function(d) {
        return xScale(d[0]);
     })
     .attr("cy", function(d) {
        return yScale(d[1]);
     })
     .attr("r", function(d) {
        return rScale(d[1])*2;
     })
     .attr('fill', 'lightblue');
    
  7. different data

    http://en.wikipedia.org/wiki/World_population#Population_growth_by_region
    var dataset = [
        [1500, 458],
        [1600, 580],
        [1700, 682],
        [1750, 791],
        [1800, 978],
        [1850, 1262],
        [1900, 1650],
        [1950, 2521],
        [1999, 5978],
        [2008, 6707],
        [2050, 8909],
        [2150, 9746]
    ];
    

More Resources on DOM

  1. Learning d3js for data visualisation
  2. Other charting tools
  3. 1000 d3js examples

Build Link of this episode

Free programming books