Getting Started with D3.js

Photo Credit: Cliff Senkbeil

Getting Started with D3.js

What is D3.js

D3.js is a JavaScript library that makes it easy to present and interact with data in a web browser. Modern web browsers support a range of technologies (HTML5, SVG and CSS3) that can be brought together to help you tell your data’s story. D3.js builds upon these technologies and can be used in a wide range of cases, including simple graphing, animation, and mapping. For more examples visit the D3.js gallery.

In this post we will use D3.js to make a straight forward line graph showing the population of Australia from 1900 to 2008, just like the chart below.1

Setting up D3.js

To get started with charting in D3.js you’ll need some data, in our case a two column TSV (tab seperated value) file containing Australia’s population by year called AusPopulation.tsv, and a HTML file called index.html. To render our line graph we will use D3.js tocreate an SVG element, and then detect mouse movement to allow us to display a marker and text indicating the population for a given year.

Firstly, we will need to include the D3.js library, here we will use the latest online release. However, you can also download a verison of D3.js and host it as needed.

<script src="http://d3js.org/d3.v3.js" charset="utf-8"></script>

Once you have included the D3.js library you can start writing the JavaScript that will render your chart. Below is the source code used in this post to create the above graph.

// Set up the margins, width and height of the chart
var margin = {top: 40, right: 150, bottom: 70, left: 150},
    width = 720 - margin.left - margin.right,
    height = 400 - margin.top - margin.bottom;

// This chart will display years as integers, and populations with thousands separators
var formatYear = d3.format("d");
var formatPopulation = d3.format(",");

// Init a function to find the nest lowest year datum relevant to the current mouse location
var bisectYear = d3.bisector(function(d) { return d.year; }).left;

// Setup the x and y values as linear scale
var x = d3.scale.linear()
    .range([0, width]);

var y = d3.scale.linear()
    .range([height, 0]);

// Setup x and y axes
var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom")
    .tickFormat(formatYear);

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left");

// Setup gridlines for x and y
var xAxisGrid = d3.svg.axis()
    .scale(x)
    .orient("bottom")
    .ticks(20)
    .tickSize(-height, 0, 0)
    .tickFormat("");

var yAxisGrid = d3.svg.axis()
    .scale(y)
    .orient("left")
    .ticks(10)
    .tickSize(-width, 0, 0)
    .tickFormat("");
// Setup a line function to access the year and population columns
var line = d3.svg.line()
    .x(function(d) { return x(d.year); })
    .y(function(d) { return y(d.population); });

// Init the SVG inside the <div id="graph"></div> element
var svg = d3.select("#graph").append("svg")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + margin.top + margin.bottom)
  .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// Load and pass to an anonymous function the Australian Population data
d3.tsv("/datasets/AustPopulation.tsv", function(error, data) {
  data.forEach(function(d) {
    d.year = +d.year;
    d.population = +d.population;
});
  // Setup the domains of X and Y
  // X will range from min Year to max Year
  x.domain(d3.extent(data, function(d) { return d.year; }));

  // Y will range from 0 to max Population 
  y.domain([0, d3.max(data, function(d) { return d.population; })]);
  
  //Create Title 
  svg.append("text")
    .attr("x", width / 2 )
    .attr("y", -10)
    .attr("class", "title")
    .style("text-anchor", "middle")
    .style("font-size", "200%")
    .text("Australia's Population");

  // Add X Axis grid lines
  svg.append("g")         
    .attr("class", "grid")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxisGrid);

  // Add Y Axis grid lines
  svg.append("g")         
    .attr("class", "grid")
    .call(yAxisGrid);

  // Add X Axis title 'Year'
  svg.append("g")
      .attr("class", "x axis")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxis)
      .append("text")
      .attr("x", width / 2 )
      .attr("y", margin.bottom / 2)
      .style("text-anchor", "middle")
      .style("font-size", "150%")
      .text("Year");

  // Add Y Axis title 'Population'
  svg.append("g")
      .attr("class", "y axis")
      .call(yAxis)
      .append("text")
      .attr("transform", "rotate(-90)")
      .attr("y", 6)
      .attr("y", - margin.left / 2)
      .attr("x", - height / 2)
      .style("text-anchor", "middle")
      .style("font-size", "150%")
      .text("Population");

  // Draw the line graph
  svg.append("path")
      .datum(data)
      .attr("class", "line")
      .attr("d", line);

  // Create the interactive focus
  var focus = svg.append("g")
    .attr("class", "focus")
    .style("display", "none");

  // Add two circles, one inside the other to highlight the focus point
  focus.append("circle")
      .attr("r", 5.5);
  focus.append("circle")
      .attr("r", 2);

  // Add text to the right of the focus point showing year
  focus.append("text")
      .attr("class", "yearLabel")
      .attr("x", 10)
      .attr("dy", ".35em");
  
  // Add text below the highlight year showing the population
  focus.append("text")
      .attr("class", "populationLabel")
      .attr("x", 10)
      .attr("dy", "1.35em");

  // Add a Rect overlay to detect mouse events, control focus visibility and call the mousemove function.
  svg.append("rect")
      .attr("class", "overlay")
      .attr("width", width)
      .attr("height", height)
      .on("mouseover", function() { focus.style("display", null); })
      .on("mouseout", function() { focus.style("display", "none"); })
      .on("mousemove", mousemove);

  // Determine which year the mouse is over and highlight the corresponding datum.
  function mousemove() {
    var x0 = x.invert(d3.mouse(this)[0]),
        i = bisectYear(data, x0, 1),
        d0 = data[i - 1],
        d1 = data[i],
        d = x0 - d0.date > d1.date - x0 ? d1 : d0;
    focus.attr("transform", "translate(" + x(d.year) + "," + y(d.population) + ")");
    focus.select(".yearLabel").text("Year: " + formatYear(d.year));
    focus.select(".populationLabel").text("Population: " + formatPopulation(d.population));
  }
});

At this stage we will need to setup the, CSS styling for the line, grid and highlight data pointer.

<style type="text/css">
    /* Init Graph font size and height */
    #graph {
        font-size: 12px;
        height: 400px;
    }

    /* X and Y Axis styling*/
    #graph .axis path,
    #graph .axis line {
      fill: none;
      stroke: #000;
      shape-rendering: crispEdges;
    }

    /* Graph line styling */
    #graph .line {
      fill: none;
      stroke: steelBlue;
      stroke-width: 2.0px;
    }

    /* Gridline styling */
    #graph .grid .tick {
        stroke: #efefef;
        shape-rendering: crispEdges;
    }
    #graph .grid path {
          stroke-width: 0;
    }

    /* Overlay rect styling */
    #graph .overlay {
      fill: none;
      pointer-events: all;
    }

    /* Data highlight focus styling */
    #graph .focus circle {
      fill: none;
      stroke: steelBlue;
      stroke-width: 1.5px;
    }
</style>

Finally we will need to place a <div id="graph"></div> element in the body of the html where our JavaScript can render the D3.js SVG chart. And that’s it… You should have a working line chart that highlights and display point values! Feel free to play with the code and learn more about the D3 functions by checkout the API Reference. Below I’ve included the complete source code for index.html and AusPopulation.tsv for your convenience. Feel free to get in touch with my via Twitter.

index.html

<!DOCTYPE html>
<meta charset="utf-8">
  <head>
    <style type="text/css">
      /* Init Graph font size and height */
      #graph {
          font-size: 12px;
          height: 400px;
      }

      /* X and Y Axis styling*/
      #graph .axis path,
      #graph .axis line {
        fill: none;
        stroke: #000;
        shape-rendering: crispEdges;
      }

      /* Graph line styling */
      #graph .line {
        fill: none;
        stroke: steelBlue;
        stroke-width: 2.0px;
      }

      /* Gridline styling */
      #graph .grid .tick {
          stroke: #efefef;
          shape-rendering: crispEdges;
      }
      #graph .grid path {
            stroke-width: 0;
      }

      /* Overlay rect styling */
      #graph .overlay {
        fill: none;
        pointer-events: all;
      }

      /* Data highlight focus styling */
      #graph .focus circle {
        fill: none;
        stroke: steelBlue;
        stroke-width: 1.5px;
      }
  </style>
</head>
<body>
  <div id="graph"></div>
  <script src="http://d3js.org/d3.v3.js" charset="utf-8"></script>
  <script>
  // Set up the margins, width and height of the chart
  var margin = {top: 40, right: 150, bottom: 70, left: 150},
      width = 720 - margin.left - margin.right,
      height = 400 - margin.top - margin.bottom;

  // This chart will display years as integers, and populations with thousands separators
  var formatYear = d3.format("d");
  var formatPopulation = d3.format(",");

  // Init a function to find the nest lowest year datum relevant to the current mouse location
  var bisectYear = d3.bisector(function(d) { return d.year; }).left;

  // Setup the x and y values as linear scale
  var x = d3.scale.linear()
      .range([0, width]);

  var y = d3.scale.linear()
      .range([height, 0]);

  // Setup x and y axes
  var xAxis = d3.svg.axis()
      .scale(x)
      .orient("bottom")
      .tickFormat(formatYear);

  var yAxis = d3.svg.axis()
      .scale(y)
      .orient("left");

  // Setup gridlines for x and y
  var xAxisGrid = d3.svg.axis()
      .scale(x)
      .orient("bottom")
      .ticks(20)
      .tickSize(-height, 0, 0)
      .tickFormat("");

  var yAxisGrid = d3.svg.axis()
      .scale(y)
      .orient("left")
      .ticks(10)
      .tickSize(-width, 0, 0)
      .tickFormat("");
  // Setup a line function to access the year and population columns
  var line = d3.svg.line()
      .x(function(d) { return x(d.year); })
      .y(function(d) { return y(d.population); });

  // Init the SVG inside the <div id="graph"></div> element
  var svg = d3.select("#graph").append("svg")
      .attr("width", width + margin.left + margin.right)
      .attr("height", height + margin.top + margin.bottom)
    .append("g")
      .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

  // Load and pass to an anonymous function the Australian Population data
  d3.tsv("/datasets/AustPopulation.tsv", function(error, data) {
    data.forEach(function(d) {
      d.year = +d.year;
      d.population = +d.population;
  });
    // Setup the domains of X and Y
    // X will range from min Year to max Year
    x.domain(d3.extent(data, function(d) { return d.year; }));

    // Y will range from 0 to max Population 
    y.domain([0, d3.max(data, function(d) { return d.population; })]);
    
    //Create Title 
    svg.append("text")
      .attr("x", width / 2 )
      .attr("y", -10)
      .attr("class", "title")
      .style("text-anchor", "middle")
      .style("font-size", "200%")
      .text("Australia's Population");

    // Add X Axis grid lines
    svg.append("g")         
      .attr("class", "grid")
      .attr("transform", "translate(0," + height + ")")
      .call(xAxisGrid);

    // Add Y Axis grid lines
    svg.append("g")         
      .attr("class", "grid")
      .call(yAxisGrid);

    // Add X Axis title 'Year'
    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis)
        .append("text")
        .attr("x", width / 2 )
        .attr("y", margin.bottom / 2)
        .style("text-anchor", "middle")
        .style("font-size", "150%")
        .text("Year");

    // Add Y Axis title 'Population'
    svg.append("g")
        .attr("class", "y axis")
        .call(yAxis)
        .append("text")
        .attr("transform", "rotate(-90)")
        .attr("y", 6)
        .attr("y", - margin.left / 2)
        .attr("x", - height / 2)
        .style("text-anchor", "middle")
        .style("font-size", "150%")
        .text("Population");

    // Draw the line graph
    svg.append("path")
        .datum(data)
        .attr("class", "line")
        .attr("d", line);

    // Create the interactive focus
    var focus = svg.append("g")
      .attr("class", "focus")
      .style("display", "none");

    // Add two circles, one inside the other to highlight the focus point
    focus.append("circle")
        .attr("r", 5.5);
    focus.append("circle")
        .attr("r", 2);

    // Add text to the right of the focus point showing year
    focus.append("text")
        .attr("class", "yearLabel")
        .attr("x", 10)
        .attr("dy", ".35em");
    
    // Add text below the highlight year showing the population
    focus.append("text")
        .attr("class", "populationLabel")
        .attr("x", 10)
        .attr("dy", "1.35em");

    // Add a Rect overlay to detect mouse events, control focus visibility and call the mousemove function.
    svg.append("rect")
        .attr("class", "overlay")
        .attr("width", width)
        .attr("height", height)
        .on("mouseover", function() { focus.style("display", null); })
        .on("mouseout", function() { focus.style("display", "none"); })
        .on("mousemove", mousemove);

    // Determine which year the mouse is over and highlight the corresponding datum.
    function mousemove() {
      var x0 = x.invert(d3.mouse(this)[0]),
          i = bisectYear(data, x0, 1),
          d0 = data[i - 1],
          d1 = data[i],
          d = x0 - d0.date > d1.date - x0 ? d1 : d0;
      focus.attr("transform", "translate(" + x(d.year) + "," + y(d.population) + ")");
      focus.select(".yearLabel").text("Year: " + formatYear(d.year));
      focus.select(".populationLabel").text("Population: " + formatPopulation(d.population));
    }
  });

  </script>
</body>
</html>

/datasets/AustPopulation.tsv

year  population
1900  3715988
1901  3765339
1902  3824913
1903  3875318
1904  3916592
1905  3974150
1906  4032977
1907  4091485
1908  4161722
1909  4232278
1910  4323960
1911  4425083
1912  4573786
1913  4746589
1914  4893741
1915  4971778
1916  4969457
1917  4917949
1918  4982063
1919  5080912
1920  5303574
1921  5411297
1922  5510944
1923  5637286
1924  5755986
1925  5882002
1926  6003027
1927  6124020
1928  6251016
1929  6355770
1930  6436213
1931  6500751
1932  6552606
1933  6603785
1934  6656695
1935  6707247
1936  6755662
1937  6810413
1938  6871492
1939  6935909
1940  7004912
1941  7077586
1942  7143598
1943  7201096
1944  7269658
1945  7347024
1946  7430197
1947  7517981
1948  7637963
1949  7792465
1950  8045570
1951  8307481
1952  8527907
1953  8739569
1954  8902686
1955  9089936
1956  9311825
1957  9530871
1958  9744087
1959  9947358
1960  10160968
1961  10391920
1962  10642654
1963  10846059
1964  11055482
1965  11280429
1966  11505408
1967  11704843
1968  11912253
1969  12145582
1970  12407217
1971  12663469
1972  13067265
1973  13303664
1974  13504538
1975  13722571
1976  13892995
1977  14033083
1978  14192234
1979  14359255
1980  14515729
1981  14695356
1982  14923260
1983  15184247
1984  15393472
1985  15579391
1986  15788312
1987  16018350
1988  16263874
1989  16532164
1990  16814416
1991  17065128
1992  17284036
1993  17494664
1994  17667093
1995  17854738
1996  18071758
1997  18310714
1998  18517564
1999  18711271
2000  18925855
2001  19153380
2002  19413240
2003  19651438
2004  19895435
2005  20127363
2006  20394791
2007  20697880
2008  21015042