Basic D3 Graphs



Scales, Axes, and Scatterplots


In this section, we'll take what we've learned to create a scatterplot that displays 4 variables. Along the way we'll discuss .scales() and .axis()

<!DOCTYPE html>
  <html>
    <head>
    <script type="text/javascript" src="http://mbostock.github.com/d3/d3.js"></script>
	
    <style type="text/css">
      .axis path,
      .axis line {
        fill: none;
        stroke: black;
        shape-rendering: crispEdges;
      }
      .axis text {
        font-family: sans-serif;
        font-size: 11px;
      }
    </style>
    </head>
    
    <body>
      <div id="scatterplot">
      <h2 style = "text-align:center">M/F Life Expectancy & Median Income By Country</h2>
      </div>
	  
      <script type="text/javascript">
        var xMaleLE = [55, 70, 65, 60, 70, 67, 70, 80];
        var yFemaleLE = [57, 58, 55, 57, 62, 75, 83, 85];
        var rMedianIncome =[4800, 4900, 5200, 10000, 15000, 20000, 25000, 27000 ];
        var tCountry = ["North Korea", "Ethiopia", "Vietnam", 
          "South Africa", "Italy", "France","United Kingdom", "United States"];
        var cCountry = ["rgb(127, 201, 127)","rgb(190, 174, 212)","rgb(253, 192, 134)",
          "rgb(255, 255, 153)", "rgb(56, 108, 176)", "rgb(240, 2, 127)",
          "rgb(191, 91, 23)", "rgb(102, 102, 102)"]
        var margin = {top: 20, right: 15, bottom: 60, left: 60}
          , width = 730 - margin.left - margin.right
          , height = 730 - margin.top - margin.bottom;
        var x = d3.scale.linear()
          .domain([d3.min(xMaleLE) - 20, d3.max(xMaleLE) + 20 ]) 
          .range([ 0, width ]); 
        var y = d3.scale.linear()
          .domain([d3.min(xMaleLE) - 20, d3.max(yFemaleLE) + 20])
          .range([ height, 0 ]);
        var r = d3.scale.linear()
          .domain([d3.min(rMedianIncome), d3.max(rMedianIncome)])
          .range([5, 35]);
		  
        var chart = d3.select('#scatterplot')
          .append('svg:svg')
          .attr('width', width + margin.right + margin.left)
          .attr('height', height + margin.top + margin.bottom)
          .attr('class', 'chart');
		  
        var main = chart.append('g')
          .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
          .attr('width', width)
          .attr('height', height)
          .attr('class', 'main')
		var xAxis = d3.svg.axis()
          .scale(x)
          .orient('bottom');

        main.append('g')
          .attr('transform', 'translate(0,' + height + ')')
          .attr('class', 'main axis date')
          .call(xAxis);
        var yAxis = d3.svg.axis()
          .scale(y)
          .orient('left');
		  
        main.append('g')
          .attr('transform', 'translate(0,0)')
          .attr('class', 'main axis date')
          .call(yAxis);
		  
        var g = main.append("svg:g");
		
        g.selectAll('scatterplot')
          .data(yFemaleLE) // using the values in the yFemaleLE array
          .enter().append("svg:circle") 
          .attr("cy", function (d) { return y(d); } )
          .attr("cx", function (d,i) { return x(xMaleLE[i]); } )
          .attr("r", function(d,i){ return r(rMedianIncome[i]);})
          .style("fill", function(d, i){return cCountry[i];});
		  
        g.selectAll('scatterplot')
          .data(yFemaleLE)
          .enter().append("text") //Add a text element
          .attr("y", function (d) { return y(d); })
          .attr("x", function (d,i) { return x(xMaleLE[i]); })
          .attr("dx", function(d,i){ return -r(rMedianIncome[i]);})
          .text(function(d, i){return tCountry[i];});
      </script>
	  
      <h3 style="text-align:center">Male Life Expectancy</h3>
    </body>
  </html>
		

Explaining .scales() with a dataset

We'll use a fabricated dataset to help explain .scales(). The dataset contains the male / female life expectancy and median income for 8 different countries. There are 8 different data points, with each data point encoding 4 variables. The dataset is stored in 5 different arrays

  var xMaleLE = [55, 70, 65, 60, 70, 67, 70, 80];
  var yFemaleLE = [57, 58, 55, 57, 62, 75, 83, 85];
  var rMedianIncome =[4800, 4900, 5200, 10000, 15000, 20000, 25000, 27000 ];
  var tCountry = ["North Korea", "Ethiopia", "Vietnam", 
    "South Africa", "Italy", "France","United Kingdom", 
    "United States"];
  var cCountry = ["rgb(127, 201, 127)","rgb(190, 174, 212)","rgb(253, 192, 134)",
    "rgb(255, 255, 153)", "rgb(56, 108, 176)", "rgb(240, 2, 127)",
    "rgb(191, 91, 23)", "rgb(102, 102, 102)"]
			

Variable Encoding

Circles will be used for each data point.

We'll encode male life expectancy (xMaleLE) on the x-position.
Female life expectancy (yFemaleLE) will be encoded on the y-position.

Median Income will be encoded using circle size. Larger circles indicate higher median incomes.

Countries will have their own unique color and the name overlayed each data point.

Explaining .scales()

Now that we have a data set to work with, explaining .scales() is easier.

Imagine, if we only have a 730 x 730 pixel area in which to draw a graph, where should we position the data point for the "United States" in relations to other countries?

Since we're using male/female life expectancy as the (x,y) positions, how would we translate United States(80,85) into a (x,y) position within a 730 x 730 area?

To do this, we would need a function that takes the values (life expectancy) we want to plot, and converts (map) those values into pixel positions. The function would need to return meaninful values so that position could tell us something about the data.

In other words, the position a data point has to tell us something itself (male life expectancy, female life expectancy) and how it relates to the other data points. Data points with similar male & female life expectancy would need to be close together.

Data points with wide differences in life expectancy would need to be further apart.

This function would need to take the domain of the values (life expectancy) and the range of the pixel on which to plot, and create a meaninful map so that the information is preserved.

Luckily, d3.scale() does all this for us.

The d3.scale() handles the math involved with mapping data values onto a given range. It makes positioning data points on a graph, relatively painless.

With d3.scale() there's no need to code functions (technically map) our x, y variables into positions.

In order to use the d3.scale() it needs to be given the domain and range.
The domain is the set of values that will be mapped. In our example, this is male & female life expectancy.
The range is the set of values it will return -- which will be the (x,y) pixel position.

NOTE: In order to get the (x,y) position, we'll use two scales. One for each position (x,y).

We start by calling d3.scale.linear(). This creates a linear scale. D3 supports other scales: log scales, power scales, and square root scales.


The next method in the chain is .domain()
.domain() is given the minimum and maximum values we will use to map.
We want to create a graph that with an axis 20 years below the min life expectancy and 20 years above the max life expectancy.

Next we indicated the range the function should return. In this case we want it to range from 0 to the width of the graph.

This results in x which is a function that provide the x-position for any life expectancy value we pass.

NOTE: Keep in mind that x() is a function. If we call x(85) it will return the x-position for 85.

  var x = d3.scale.linear()
    .domain([d3.min(xMaleLE) - 20, d3.max(xMaleLE) + 20 ])
    .range([ 0, width ]); 
			

We follow the same process for mapping female life expectancy onto the y-position.
Since we want to maintain a 1:1 aspect ratio in our graph, we use male life expectancy to set the domain. Since male and female life expectancies are similar, we don't run the risk of having data being drawn outside the graph.

  var y = d3.scale.linear()
    .domain([d3.min(xMaleLE) - 20, d3.max(yFemaleLE) + 20])
    .range([ height, 0 ]);
			

We also need to create a scale for Median Income. We'll use this scale to set the radius for the data points (circles). The radius will range from 5 to 35 px. This results in the country with the highest median income having a radius = 35 and the country with the lowest median income being drawn with a circle having radius = 5.

  var r = d3.scale.linear()
    .domain([d3.min(rMedianIncome), d3.max(rMedianIncome)])
    .range([5, 35]);
			

Axis

Now that we have .scale() functions defined, we can use them to create the x and y axis for our graph.
D3 makes creating axis easy with d3.svg.axis()

To create an SVG axis, we call d3.svg.axis.scale() and pass the scale function we created -- x.

.orient() is used to specify the layout, such as whether the axis will be read at the top, bottom, left or right of the graph. The actual position is specified when the axis is drawn in later code.

  var xAxis = d3.svg.axis()
    .scale(x)
    .orient('bottom');
	
  var yAxis = d3.svg.axis()
    .scale(y)
    .orient('left');
			

With the svg axis elements created, they can be added to the chart. .call() is used to call outside selections. xAxis / yAxis are selections that exist outside the method chain, they can be called into the current selection using .call().

.call() lets us separate the code for generating the axis from code that adds the axis to the graph.

Here we've added the axis to the main drawing area.

  main.append('g')
    .attr('transform', 'translate(0,' + height + ')')
    .attr('class', 'main axis date')
    .call(xAxis);
	
  main.append('g')
    .attr('transform', 'translate(0,0)')
    .attr('class', 'main axis date')
    .call(yAxis);
			

Finishing the Scatterplot

  var g = main.append("svg:g");
  
  g.selectAll('scatterplot')
    .data(yFemaleLE)  // using the values in the yFemaleLE array
    .enter().append("svg:circle") 
    .attr("cy", function (d) { return y(d); } )
    .attr("cx", function (d,i) { return x(xMaleLE[i]); } )
    .attr("r", function(d,i){ return r(rMedianIncome[i]);})
    .style("fill", function(d, i){return cCountry[i];});
			

Now that we have the axis and scales set, we can begin to draw the data points. We begin by creating an svg <g> element to the drawing area.

  var g = main.append("svg:g");
			

Next we bind the female life expectancy data (yFemaleLE).

  g.selectAll('scatterplot')
    .data(yFemaleLE) // using the values in the yFemaleLE array
			

Each data point is drawn as a circle.

  .enter().append("svg:circle") 
			

The (cx,cy) positions are set using the scales we defined earlier.

The position for cy is set using the y scale() and yFemaleLE
Here yFemaleLE is d.

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

The cx position is set using the x scale() and xMaleLE.

  .attr("cx", function (d,i) { return x(xMaleLE[i]); } )
			

The radius is set using the r scale() and rMedianIncome.

  .attr("r", function(d,i){ return r(rMedianIncome[i]);})
			

Finally we will the circle with the country's assigned color.

  .style("fill", function(d, i){return cCountry[i];});
			

To overlay the countries name on the data points. We use similar code, except that we use an svg:text element.

  g.selectAll('scatterplot')
    .data(yFemaleLE)
    .enter().append("text") //Add a text element
    .attr("y", function (d) { return y(d); })
    .attr("x", function (d,i) { return x(xMaleLE[i]); })
    .attr("dx", function(d,i){ return -r(rMedianIncome[i]);})
    .text(function(d, i){return tCountry[i];});
	

The resulting scatter plot looks like

M/F Life Expectancy & Median Income By Country

Male Life Expectancy

horizontal line

Line Graph Tutorial


1) Import d3 library -

Make sure to include this text so that you can access the d3 library. This is typically placed in the main Head of the HTML file.

  <script type="text/javascript" src="http://mbostock.github.com/d3/d3.js"></script>
			

2) Insert the div container -

This code will specify where your d3 visualization will be placed in the HTML page

  <div id="viz"></div>
			

3) Declare Variables -

Here we first specify the data we will be using in our line graph as the arrays data1 and data2. The height and width of our graph will be determined by w and h. The margin will be the blank space between our x and y axis and the edge of our graph which we will use to display the number scale. Finally we have our x and y linear scale functions which we will need to convert our data values to x and y positions on the screen

  <script type="text/javascript">
    var data2 = [1, 3, 4, 3, 6, 1, 8, 2, 4, 1, 3, 4, 1]
    var data1 = [3, 6, 2, 7, 5, 2, 1, 3, 8, 9, 2, 5, 7],
	
    w = 500,
    h = 200,
    margin = 20,
	
    y = d3.scale.linear().domain([0, d3.max(data1)]).range([0 + margin, h - margin]),
    x = d3.scale.linear().domain([0, data1.length]).range([0 + margin, w – margin])
			

4) Create our SVG -

Here we first select our viz container and add an SVG element to it, and then specify the height and width. We then append a g element to our SVG element so that everything added to the g element will be grouped together. The transformation is used to move our coordinate grid down by 200 pixels.

  var vis = d3.select("#viz") 
    .append("svg:svg")
    .attr("width", w)
    .attr("height", h)

  var g = vis.append("svg:g") 
    .attr("transform", "translate(0, 200)") 
			

5) Draw the axis -

We start by appending a SVG line element to our g element from earlier, and to give it some visual appeal we add a transition operator which will make it look as if the line is drawn when the visualization is first loaded. We then specify the color of the line by declaring it's stroke as black. To actually draw the line we need to specify a starting point (x1, y1) and an end point (x2, y2). Remember that the 0,0 coordinate is in the top left corner, so that is why our y values are negative. Drawing the Y axis is very similar, the only difference is that we call d3.max(data1) so that we know what the maximum value is and draw the scale accordingly.

  //Lets draw the X axis
  g.append("svg:line")
    .transition()
    .duration(1000)
    .style("stroke", "black")
    .attr("x1", x(0))
    .attr("y1", -1 * y(0))
    .attr("x2", x(w))
    .attr("y2", -1 * y(0))
	
  //Lets draw the Y axis
  g.append("svg:line")
    .transition()
    .duration(1000)
    .style("stroke", "black")
    .attr("x1", x(0))
    .attr("y1", -1 * y(0))
    .attr("x2", x(0))
    .attr("y2", -1 * y(d3.max(data1)))
			

As you can see from the example below we are making progress and now have our axis displayed.


6) Add axis labels -

Here we will add in our numerical labels for both the x and y axis. First we start by selecting the x labels, then we use the scalar function x.ticks(5) which will return the proper tickmarks for where the numbers should go. Naturally we will add another transition here so that when the page is loaded our labels will smoothly slide into place. We then add the text element to our g element, and we define our x values by simply calling a function with the parameter I for index and just return it. Since this is for the x axis we leave the y value set to 0, and vice verse for the y labels. Finally we set the text-anchor so that the numbers will appear directly below the tick marks we will draw in the next step.

  //X Axis labels 
  g.selectAll(".xLabel")
    .data(x.ticks(5))
    .style("font-size","9pt")
    .enter()
    .append("svg:text")
    .transition()
    .duration(1000)
    .attr("class", "xLabel")
    .text(String)
    .attr("x", function(i) { return x(i) })
    .attr("y", 0)
    .attr("text-anchor", "middle")
	
  //Y axis labels
  g.selectAll(".yLabel")
    .data(y.ticks(4))
    .style("font-size","9pt")
    .enter().append("svg:text")
    .transition()
    .duration(1000)
    .attr("class", "yLabel")
    .text(String)
    .attr("x", 0)
    .attr("y", function(i) { return -1 * y(i) })
    .attr("text-anchor", "right")
    .attr("dy", 4)
			

7) Add tick marks -

This step is very similar to the last. We now will select the xTicks and add the data points using the scalar function x.ticks(5). However, this time instead of adding a text element we will just add a line element. Again we use another transition to help animate the visualization. Similar to when we where drawing the lines for the axis here we must also specify the start and end points for each tick mark. Remember that we are using SelectAll here, so this will evaluate for each individual tick mark.

  //X axis tick marks
  g.selectAll(".xTicks")
    .data(x.ticks(5))
    .enter().append("svg:line")
    .transition()
    .duration(1000)
    .style("stroke", "black")
    .attr("class", "xTicks")
    .attr("x1", function(i) { return x(i); })
    .attr("y1", -1 * y(0))
    .attr("x2", function(i) { return x(i); })
    .attr("y2", -1 * y(-0.3))
	
  //Y axis tick marks
  g.selectAll(".yTicks")
    .data(y.ticks(4))
    .enter().append("svg:line")
    .transition()
    .duration(1000)
    .style("stroke", "black")
    .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));
			

Now we can see that both our labels and tick marks have been added to the axis we drew earlier.


8) Draw the line -

Now that we have our axis down lets add a line to represent our values in data1. We begin by defining a variable/function line that will allow us to draw this line, we use the helper function d3.svg.line() to define our d attribute which we will need to actually store our datapoints. Note how we use the x and y functions from earlier to find exactly where the place these points. We then load the data to this line by appending the path to the g elecment and passing the d attribute our data1 values. We also add a transition operation to the line as well, mainly to add the delay of 1.1s so that the line graph will only appear once the axis and labels have moved into place.

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

  g.append("svg:path")
    .transition()
    .delay(1100)
    .attr("d", line(data1))
    .style("stroke", "indianred")
    .style("stroke-width", 3)
    .style("fill", "none")
			

You can now see our static graph loaded here:


9) Adding interactivity -

Now we will demonstrate how you can add a simple mouse event to this graph to allow us to load in the values in data2 by clicking anywhere on the graph. We start by declaring a boolean variable change which will be either true or false depending on which data set we are trying to load. We start with our vis element, which if you remember is the main parent element for our whole graph, and call the on operator and specify that on a mouse click (mousedown) we will launch a function. In this function we will use the change variable to determine which dataset we are currently displaying, and then through an if-else statement we will select the path element of our g element, use a transition of course, and then change the d attribute of this path element to the opposing dataset, and change the color of our line for added effect. When loading back in data1 we also specify the .ease function of back which will make our transition effect “simulate backing into a parking space.”

  var change=new Boolean()
    change = true
	
  vis.on("mousedown" , function(){
    if(change){
      g.select("path")
        .transition()
        .duration(2000)
        .attr("d", line(data2))
        .style("stroke", "steelblue")
        
        change = false
    }
    else {
      g.select("path")
        .transition()
        .ease("back")
        .duration(2000)
        .attr("d", line(data1))
        .style("stroke", "indianred")
    
        change = true
    }
  })
			

Here is the final product, a line graph with several transition effects.


Note: Here is an independent page for the line graph tutorial. We have noticed some browser issues on rendering D3 charts correctly. The line graphs are displayed correctly on the redirected page from major browsers like Firefox, Chrome, Safari, and Opera (with recently updated versions). But on updated IE 9 all the 4 line graphs are missing from the tutorial. We are not exactly sure why, but the same code might have the same browser issues or CSS conflicts on this page for showing squished ticks on the y-axis and the wrong animation. On IE 9 the x-axis extends all the way to the right-hand side.

horizontal line

Bar Chart


This section will guide you through the process of implementing a D3 bar chart in your HTML file.

Preparation

1) Use the D3 library -

Make sure you have this piece of information attached to the <head> section of your HTML file:

  <head>
  <script type="text/javascript" src="http://mbostock.github.com/d3/d3.js"></script>
  </head>
			

2) Insert a placeholder -

Insert a <div> tag to wherever you want to display the chart on your page. Assign an ID to it. Example:

  <div id="rect1"></div>
			

3) Select the div and use D3 code to draw -

Draw a simple shape (golden rectangle) with the following D3 code. This JavaScript snippet can go anywhere in your HTML file. Example:

  <div id="rect1"></div>
  
  <script type="text/javascript">
    var rectDemo = d3.select("#rect1").
      append("svg:svg").
      attr("width", 400).
      attr("height", 300);

    rectDemo.append("svg:rect").
      attr("x", 100).
      attr("y", 100).
      attr("height", 100).
      attr("width", 200).
      style("fill", "gold");
  </script>
			

Note: From this code snippet, we are informed of some basic D3 syntax. First we define a variable "rectDemo" and select a div named "rect1" to place the chart. Then we assign a 400*300 canvas to this visualization, and draw a 200*100 golden rectangle. This rectangle has the starting point (100, 100) at its upper-left corner. The point (0, 0) is at the farther upper-left side, so we are drawing in the 4th dimension on this plane.

Draw a bar chart

After knowing the basics of how to draw a rectangle with D3, we can move on to creating a bar chart.

4) The skeleton -

Bar chart is a graph consisting of parallel, generally vertical bars. Bar lengths are proportional to the quantities or frequencies specified in a set of data. So first we draw a bar for the chart:


  var data = [{year: 2012, deaths: 96}];
  
  var barWidth = 40; 
  var width = (barWidth + 10) * data.length;
  var height = 200;
  
  var x = d3.scale.linear().domain([0, data.length]).range([0, width]);
  var y = d3.scale.linear().domain([0, d3.max(data, function(datum) 
    {return datum.deaths;})]).rangeRound([0, height]);
  
  // add the canvas to the DOM
  
  var barBasic = d3.select("#bar-basic").
    append("svg:svg").
    attr("width", width).
    attr("height", height);

  barBasic.selectAll("rect").
    data(data).
    enter().
    append("svg:rect").
    attr("x", function(datum, index) { return x(index); }).
    attr("y", function(datum) { return height - y(datum.deaths); }).
    attr("height", function(datum) { return y(datum.deaths); }).
    attr("width", barWidth).
    attr("fill", "purple");
			

The code above demonstrates how to set up a skeleton for bar charts:
a) Define data variables in the form of an array;
b) Define bar width and the overall canvas width and height;
c) Define position variables x and y as the starting point;

var xScale = d3.scale.linear()  //.scale.linear() translates between the data 
                                //    input and the display (x, y) on the canvas
  .domain([0, 20])              //.domain() specifies your data maximum and minimum
  .range([0,100]);              //.range() specifies the width of the chart by 
                                //    pixels to the map
				

d) Select the div and append SVG shapes to the canvas;
e) Use anonymous functions for additional calculations to adjust the position;

  attr("x", function(datum, index){
    return xScale(datum.foobar) + whatever
  }) 
				

Note: If the data is complicated, you can pass a function to attr. It takes two arguments: datum (d) and index (i). Inside the function you can call the scale function, manipulate the results, and return them.



5) Data binding -

In D3, data is stored in the form of arrays. In the following example, we add more data to the variable "data" and display the parallel bars:

  var data = [{year: 2006, deaths: 55},
        {year: 2007, deaths: 63},
        {year: 2008, deaths: 69},
        {year: 2009, deaths: 81},
        {year: 2010, deaths: 74},
        {year: 2011, deaths: 79},
        {year: 2012, deaths: 93}];
			


6) Adding text -

Now the bar chart shows the amount of changes, but it is also important to add more texts to specify the quantitiy of each data point on x- and y- axes.



Here is the code for adding text to the bars:

  barFinal.selectAll("text").
    data(data).
    enter().
    append("svg:text").
    attr("x", function(datum, index) { return x(index) + barWidth; }).
    attr("y", function(datum) { return height - y(datum.deaths); }).
    attr("dx", -barWidth/2).
    attr("dy", "1.2em").
    attr("text-anchor", "middle").
    text(function(datum) { return datum.deaths;}).
    attr("fill", "white");
			  
  barFinal.selectAll("text.yAxis").
    data(data).
    enter().append("svg:text").
    attr("x", function(datum, index) { return x(index) + barWidth; }).
    attr("y", height).
    attr("dx", -barWidth/2).
    attr("text-anchor", "middle").
    attr("style", "font-size: 12; font-family: Helvetica, sans-serif").
    text(function(datum) { return datum.year;}).
    attr("transform", "translate(0, 18)").
    attr("class", "yAxis");
			

Note: If you add the above code snippet to where you have the JavaScript code about drawing the bars, you would see the datum and index/year information added to the chart.



To add numbers inside the bars, the elements must be positioned in the same place as the top of the bar. Then add padding to make it look right. First, selectAll() is used to get a selection of elements, and data() is bound to them. And then enter() is used to add the elements to the chart.

To add an x-axis, the height of the chart must be increased to make room for it. By keeping the old height variable and increasing the height of the svg:svg canvas by padding, the rest of the code does not need to change.