Data Driven Documents (D3)

Welcome to the D3 Workshop! This is an introductory tutorial to introduce some of the concepts and fundamentals behind D3. A familiarity with web programming and creating webpages using HTML, CSS, and JavaScript will be helpful in understanding what is going on, although not required. If you are a beginner, start by running through the DUSPVIZ session named "Code your First Website".

This tutorial will introduce D3 and walk through the creation of chart to introduce the fundamentals.


Session Objectives

  • Learn the fundamentals of D3 and Scaleable Vector Graphics (SVG).
  • Understand how D3 binds a data value to a page element.

Suggested Prerequisites: A basic understanding of webpages, HTML, CSS, and JavaScript will be very helpful in learning D3. Please look over basic JavaScript principles in Codecademy's JavaScript tutorial. and complete the Code your First Website session.

To download the materials for exercises, click here. This is d3_part1_makeAChart.


What is D3?

D3 stands for Data Driven Documents. It is a JavaScript library developed by Mike Bostock that is designed to manipulate documents based on data, allowing the user to create rich and dynamic visualizations using web standards of HTML, SVG, CSS, and JavaScript. The library is well developed, and very popular among Data Analysts and Data Visualization Specialists.

It is perhaps easier to describe D3 by detailing what it is not. D3 is not a graphical representation. D3 is not a magical tool that draws and styles charts, maps, and visualizations for you, but rather, it is a tool that provides a means for YOU to create and style documents based on your data. Think of it as a very fancy tool that you have to learn to operate, telling it what to draw, what data to base the drawing on, and which document to manipulate.

D3 is the toolbox, the rest is up to you!

How D3 Works

To describe how D3 works, first consider what we mean by the term Documents. A webpage is a collection of documents sitting on your web server. The web server has an address on the World Wide Web that other computers can navigate to. When a visitor navigates to your address, their browser will request one or more documents. These documents describe to their browser what to display, how to display it, where to get data from that aids in the display, and when to execute events such as user clicks. A basic webpage could contain a series of text documents that contain code written to structure the webpage (HTML), style that page(CSS), and add dynamic elements (JS). Webpages will often have many assets, including images, data, and other supplemental material that makes the page display and function propertly.

Web Page Documents and Assets

When working with D3, you are creating and manipulating the elements of these documents. For example, the D3 JavaScript library will find a div element in your page, bind a dataset to it, then set attributes of that element according to values in the dataset. It can dynamically update based on events such as a click or change the elements based on an update to the dataset. You 'bind' data to page elements, the Data Driven component of the name.

D3 and Page Elements

D3 can work with just about any element found on your webpage and modify by binding data to that element and setting attributes of that element accordingly. The data 'drives' the document. For example, D3 can be used to generate an HTML table from an array of numbers, or you can use the same data to create an interactive bar chart those same numbers. Events you connect to these elements allow for interaction with the data1.

Just what does this mean? Take a look at a sample dataset. D3 allows this data to be attached to elements in our document. this data to elements in our document.

City # of Rats
Brookline 40
Boston 90
Cambridge 30
Somerville 60

The primary element you will find yourself working with is SVG, or Scalable Vector Graphics. SVG is an XML-based vector image format that meets web standards and is an open standard developed for web friendly two-dimensional graphics and provides support for animation and interactivity. SVG is unique in that all of the behaviors and components of the SVG images can be accessed from JavaScript and CSS just like any other element in a webpage.

Binding Data to SVG Page Elements

Let's Get Started!

In this exercise, you will create a webpage and display a chart that will help explain what we mean by driving a document with data. The next steps will set up an environment, create a basic webpage with some graphic elements, and show how you can use the D3 JavaScript library to connect data to some SVG elements on your page.

This Tutorial...

A bit more about this tutorial. D3 is a widely used and well documented language, and a large and growing amount of material can found online. This tutorial, and the ones that follow, will contain many links to outside resources, supplemental documentation, and additional tutorials that can help you become a D3 master! To give you a better idea, we will begin to cover how D3 works, and in doing so we will make the following bar chart.

Dynamic Bar Chart with Tooltips and Axes (Click to view this example on its own.)
Hover over bars to see data

An Intro to D3: Make a Chart

Download Docs and Set Up your Development Environment

In order to work with D3, we have to serve the page your visualization is on to the web. To do so, open terminal, change directory to where you have saved the files, and start up a simple Python server using:

python -m http.server

Navigate in a browser to simple-bar-chart-completed.html, and you can view the completed code for the first example. We are going to start from scratch to show you how to draw SVG!

1. Set up an HTML Boilerplate

To get start, create an empty text document in your web folder. In that document, copy and paste the following. This is some basic boilerplate HTML, with a head and body which we can fill with content.

<!DOCTYPE html>
<html lang="en">
<head>
	<title>I'm Learning D3</title>
	<!-- You will load the D3 library here. -->
</head>
<body>
	<!-- Page elements and content go here. -->
	<script>
		<!-- Our D3 code will go here. -->
	</script>	
</body>
</html>

Save this document as chart.html in your web folder. There is no content in here yet, just the boilerplate template. We will work in this document for the exercise.

2. Hardcode a Simple Dataset into SVG Elements

As mentioned, D3 operates with a goal of binding data to elements on our page. The following step illustrates what this would look like without D3, and will give you a nice picture of what exactly we are going to be addressing by using the D3 JavaScript library to connect data to our page elements. For illustration, lets say we have a dataset with the following values.

(40, 90, 30, 60)

We want a bar chart, with the height of each bar as the respective value in the dataset. (i.e. The first bar has a height of 40 pixels, etc.) In the following the block of code, copy and paste the SVG elements highlighted in the block below into your code.

<!DOCTYPE html>
<html lang="en">
<head>
	<title>I'm Learning D3</title>
	<!-- You will load the D3 library here. -->
</head>
<body>
	<!-- Page elements and content go here. -->
	<svg width="160" height="180">
		<rect x="30" width="20" height="40" fill="steelblue"></rect>
		<rect x="55" width="20" height="90" fill="steelblue"></rect>
		<rect x="80" width="20" height="30" fill="steelblue"></rect>
		<rect x="105" width="20" height="60" fill="steelblue"></rect>
	</svg>
	<script>
	<!-- Our D3 code will go here. -->
	</script>
	
</body>
</html>

Save your document and open up your page in a web browser. You will see the elements on the page at the top, justified to the top, with height set accordingly to our hardcoded dataset.

Data Hardcoded into SVG Elements (Click to View this Example.)

This is very basic, but we have begun to locate items on our screen. This is the first step towards a nice visualization. We can build from here, and show how we can use D3 to make this a robust, dynamic chart in which we can load data.


Working with Scaleable Vector Graphics (SVG)

One of the primary element types you will manipulate using D3 are Scaleable Vector Graphics, or SVG. These are page elements that can sit right within the body of your page and can be manipulated like any other element in the Document Object Model. The following shows the layout of three circles in the DOM, and how they will appear in our browser.

Circles with SVG: diagram based on Three Little Circles, by Mike Bostock
Rectangles with SVG: diagram based on this exercise!

Note that 0,0 is in the upper left corner of the SVG element, and all child elements are located in relation to this parent element. For further reading and an excellent tutorial on SVG and D3, see Three Little Circles, by Mike Bostock, for a fantastic tutorial on SVG creation and manipulation2.

For more on SVG, its capabilities and available elements, check out the documentation.


Working with the Document Object Model

Working with D3, you will become very familiar with the Document Object Model, often shorted to just "The DOM". The DOM is highly functional, and as a model it provides the path for your browser to interact with a webpage. It is a fully object-oriented representation of the webpage, organized as a structured group of nodes, elements, and objects. Each element of the page, including the paragraphs, headings, divs, styles, etc. can be accessed and modified. Think of it as a structured breakdown the elements of a web page that can be traced for easy interaction and manipulation, and also as the space in which the elements exist when you interact with them.

The Document Object Model3

Tthe body of your page is an element, under it are paragraph elements and headers. Typically, an SVG element used in D3 is sits nested within the body element in the DOM.

For more on working with the DOM, and what exactly this means, check out the documentation.


3. Connect Data to the SVG Elements Using D3

There are limitations to hard coding the data to an element. It can be hard to create, tedious to update or modify, and is not expandable. This is where D3 steps in. D3 will allow us easy access to these elements, we can use D3 to give the height to the element.

Modify our code block to include the D3 library, and then embed the data into the JavaScript tag in the body. Observe the block of code below. Note that we remove the height attribute for the SVG rectangles, we will use D3 to create and define the height attribute.

<!DOCTYPE html>
<html lang="en">
<head>
	<title>I'm Learning D3</title>
	<!-- Load the D3 library here -->
	<script src="https://d3js.org/d3.v4.js" charset="utf-8"></script>
</head>
<body>
	<!-- Page elements and content go here. -->
	<svg width="160" height="180">
		<rect x="30" width="20" fill="steelblue"></rect>
		<rect x="55" width="20" fill="steelblue"></rect>
		<rect x="80" width="20" fill="steelblue"></rect>
		<rect x="105" width="20" fill="steelblue"></rect>
	</svg>
	<script>
		<!-- Our D3 code will go here. -->
		var ratData = [ 40, 90, 30, 60 ];

		d3.selectAll( "rect" )
			.data( ratData )
			.attr( "height", function(d){
				return d;
			});
	</script>
	
</body>
</html>

Save your document and open up your page in a web browser. You will see the elements on the page at the top, justified to the top.

Using D3 to Bind Data to the Elements (Looks the same, Huh?) (Click to view this example on its own.)

So, what happened here?

Look between the script tags in our code. In line 18, we created a simple array of data containing our dataset. These numbers are what we want to show using the height of each of the rectangle elements. This is measured in pixels.

<!-- Our D3 code will go here. -->
var ratData = [ 40, 90, 30, 60 ];

Next, we use a D3 selection to select all of the rectangle objects (rect) in the document. We do this using selectAll().


Working with Selections

A D3 selection is an array of elements from the current document and follows the same guidelines as CSS. The selection process lets D3 select elements from the document so that operators can be to the elements, telling them to do stuff. D3 has select() and selectAll() methods to find single or multiple DOM elements, respectively. This works much like jQuery.

d3.selectAll( "rect" ); // select all SVG rect elements
d3.select( "#boston" ); // select an element where id='boston'
d3.selectAll( ".bar" ); // select all elements with the class 'bar'

Since selectAll("rect") finds multiple elements, everything in the chain following this will be happening to each of those elements.4 Using this will iterate through each of the elements on the page that we are binding data to.

SelectAll Iteration

The selectAll method iterates through the data values one by one. Going one by one, data values are returned to the DOM. This ultimately allows for functions and operations to be performed on each data value. The order, unless specified in another way, will be from top to bottom down you page.

For more on working with selections and the options available, check out the documentation.

Additional recommended reading on selections from Mike Bostock How Selections Work


The selector, in our code looks like the following.

d3.selectAll( "rect" )
	.data( ratData )
	.attr( "height", function(d){
		return d;
	});

In pseudo code, this selects all rect elements, passes the variable ratData to the rect elements (ratData contains an array), then sets the attribute of height to the values in the ratData array. The elements will be selected and data from the array will be applied in document traversal order (top-to-bottom). When you run out of elements or data, the return is empty.

The data() method

The data() method is the very soul of D3. With it, an array of data is bound to page elements.4

The attr() method

The attr() method allows us to set attributes of the page elements. In this example, we set the height attribute.

Anonymous Functions

Something that might have happened here that could be a bit confusing, we set height to an anonymous function.

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

The argument d that is being passed to the function represents our dataset. The anonymous function has a parameter for the data values you just bound to your page elements in the data statement. This is built into D3. The name of this variable (d) is arbitrary, but d is usually used as it represents a data value.

You can use console.log(d); to see the values held by d printed to the console.

.attr( "height", function(d){
	console.log(d);
	return d;
});

This code prints the following to the console.

Console Log of d Variable

Change the variable value to a and view the result.

.attr( "height", function(a){
	console.log(a);
	return a;
});

You get the same.

Console Log of a Variable

The data you bind to your page elements is an object in itself. In this example, d is our object that can be operated on locally within this function. If our data object has properties, you can refer to these properties in this step. For example, if our dataset is a JSON, with two properties, number of rats (number) and city city), we can reference it as we would in any other JavaScript object. i.e. d.number. We will look at this more in a bit!

Further reading on declared versus anonymous functions


4. Use D3 to Read Data and Create Elements from Data

It's more often the case that you don't have your data-driven elements pre-baked into the page, but rather create them on the fly. Let's give that a try. Save your current document, and let's start with a new empty HTML page. Here is the boilerplate, with the D3 library loaded. Note we don't create any SVG elements by hand, we are going to use the capabilities of the D3 library to create these.

<!DOCTYPE html>
<html lang="en">
<head>
	<title>I'm Learning D3</title>
	<script src="https://d3js.org/d3.v4.js" charset="utf-8"></script>
</head>
<body>
	<!-- Location for page elements. -->
	<script>
		// Our D3 code will go here.
	</script>	
</body>
</html>

Working within the Script Tags... let's create our chart

Locate the script section of our page, here is where all of our D3 script will go.

1. First things first, add the data. We are going to use the same dataset.

<script>
	// Our D3 code will go here.
	var ratData = [ 40, 90, 30, 60 ]; // Rat data!
</script>

2. Next, we are going to create the SVG element. Decide on the width and height of the SVG, set those numbers as variables. For the chart, let's use a width of 150 pixels, and a height of 175 pixels. We'll carry this through our examples. After that, we want to create the SVG element. The append() method creates a new element as a child of each element in the current selection, then the attr() method will be used to set the height and width of the SVG element. The highlighed code below will create our SVG as a child of the body element, and give it height and weight attributes.

<script>
	// Our D3 code will go here.
	var ratData = [ 40, 90, 30, 60 ]; // Rat data!

	// Width and height of SVG
	var w = 150;
	var h = 175;

	//Create SVG element
	var svg = d3.select("body")
		.append("svg")
		.attr("width", w)
		.attr("height", h);
</script>

3. The next step is create the rect elements, bind the data to them, and place them on the screen. Do this with the following code.

<script>
	// Our D3 code will go here.
	var ratData = [ 40, 90, 30, 60 ]; // Rat data!

	// Width and height of SVG
	var w = 150;
	var h = 175;

	//Create SVG element
	var svg = d3.select("body")
		.append("svg")
		.attr("width", w)
		.attr("height", h);

	// Select and generate rectangle elements
	svg.selectAll( "rect" )
		.data( ratData )
		.enter()
		.append("rect")
		.attr( "x", 30 )
		.attr( "y", 0 )
		.attr( "width", 20 )
		.attr( "height", 100 )
		.attr( "fill", "steelblue");
</script>

Things just got a little weird. Did we just select a bunch of rectangle elements that don't exist? Well, yes... kinda. With D3, you always have to first select what you are going to be operating on4, even if it doesn't yet exist. This is a bit abstract, but hang with me, the next steps will explain this more.

What we did is select a bunch of rectangles that are not there, so we get an empty selection. The next few lines of code in our block above create these elements by binding data, using the enter(), and appending a new element.

  1. data() - We bind the data to our empty selection using the data() method, it will return the four data values in our dataset.
  2. enter() - When we load data, it will iterate through the dataset and apply all methods that follow to each of the values of our dataset. The enter() method creates placeholders for each data element for which no corresponding DOM element was found. Because it iterates, it will create four placeholders.
  3. append() - Finally, the append("rect") method will insert a rectangle into each of the placeholders that do not have a "rect" element, which is all of them4.
  4. attr() - Iteratively sets attributes, such as (x,y) location, width, and height for each of the rectangle elements. Right now, these are all in the same location (0,0) and have the same width (20) and height (100). We need to use functions to make this work properly, and will detail that next.

Save and refresh your document. You should see this... pretty boring, but if you see this it is working! You have four rectangles, but they are all in the same location.

Creating Elements with D3 (Click to view this example on its own.)

Working with the Enter and Exit methods

The enter() and exit() methods deal with new elements and unused elements, respectively, based on incoming data. For more reading on this, Mike Bostock explains this in his Three Little Circles tutorial, or you can visit the documentation.


4. Finally, we properly size and arrange the "rect" elements. To do this, we can modify the height attribute and x and y attributes for each of our rectangle elements. The attributes can read functions that allow us to dynamically change attributes based on the data value of the current iteration.

Update the attributes to properly display the "rect" elements by changing the x attribute and height attribute. This will look familiar! It is exactly how we assigned a height in the previous example.

<script>
	// Our D3 code will go here.
	var ratData = [ 40, 90, 30, 60 ]; // Rat data!

	// Width and height of SVG
	var w = 150;
	var h = 175;

	//Create SVG element
	var svg = d3.select("body")
		.append("svg")
		.attr("width", w)
		.attr("height", h);

	// Select and generate rectangle elements
	svg.selectAll( "rect" )
		.data( ratData )
		.enter()
		.append("rect")
		.attr( "x", function(d,i){
			return i*25 + 30; // Set x coordinate of rectangle to index of data value (i)*25.
			// Add 30 to account for our left margin.
		})
		.attr( "y", 0 )
		.attr( "width", 20 )
		.attr( "height", function(d){
			return d; // Set height of rectangle to data value
		})
		.attr( "fill", "steelblue");
</script>

Setting the x attribute - Space the bars our horizontally using the second i argument. The i argument is the index of each bar in the selection as the code iterates through. For our four rectangles, i here will be 0, 1, 2, and 3, giving us x positions of 0, 25, 50, and 75.

You notice here a second argument is provided to our anonymous function (i). This represets the index location of the data value refered to in the d argument.

Setting the height attribute - The d argument is the data value for each piece of data. The function will return the value for each rectangle based on the data value, setting the height attribute equal to the data value for each respective rectangle.

Setting the fill attribute - Let's color our chart while we are at it. Set the fill attribute to the color of your choice. I used "steelblue". This attribute will take web standard colors. Look up some Hex codes and pick your favorite color, or just use "steelblue".

Save and refresh your document, look familiar? We did it!

Creating SVG Elements using D3 (Click to view this example on its own.)

5. Design the Chart

As is, this chart is not very useful. In fact, it is just four rectangles. We need to add some context, rejustify the bars, and perhaps add some axes. We can do this right in our script by adjusting the attributes and properties of the SVG elements. In the following steps, we'll add those axes and label.

1. Bottom-justify the Bars - The chart is confusing. The higher the number the farther down the page the bar extends. We can change this quite easily by adjusting the value of the y attribute. See the adjustment to our code below in lines 24-26.

<script>
	// Our D3 code will go here.
	var ratData = [ 40, 90, 30, 60 ]; // Rat data!

	// Width and height of SVG
	var w = 150;
	var h = 175;

	//Create SVG element
	var svg = d3.select("body")
		.append("svg")
		.attr("width", w)
		.attr("height", h);

	// Select and generate rectangle elements
	svg.selectAll( "rect" )
		.data( ratData )
		.enter()
		.append("rect")
		.attr( "x", function(d,i){
			return i*25 + 30; // Set x coordinate of rectangle to index of data value (i)*25.
			// Add 30 to account for our left margin.
		})
		.attr( "y", function(d){
			return h - d; // Set y coordinate for each bar to height minus the data value
		})
		.attr( "width", 20 )
		.attr( "height", function(d){
			return d; // Set height of rectangle to data value
		})
		.attr( "fill", "steelblue");
</script>

This will bottom-justify our bars and will be more intuitive. Save and refresh, you should see the following.


2. Add Labels and Axes - Next, lets add some labels to our graph, provide a bit more context, and add axes to the map along the left side and bottom. When finished, our chart will look like this. Let's take a look.

Styled Bar Chart with Labels and Axes (Click to view this example on its own.)

a. Add the X and Y axes. A simple method of doing this is to add SVG line elements to our script. We can set the X and Y coordinates for the start and end of the line, along with the stroke and width. The following text will add our axes.

// Create y-axis
svg.append("line")
	.attr("x1", 30)
	.attr("y1", 75)
	.attr("x2", 30)
	.attr("y2", 175)
	.attr("stroke-width", 2)
	.attr("stroke", "black");

// Create x-axis
svg.append("line")
	.attr("x1", 30)
	.attr("y1", 175)
	.attr("x2", 130)
	.attr("y2", 175)
	.attr("stroke-width", 2)
	.attr("stroke", "black");

b. Add a Label There are ways when working with numbers to automate this, but for now, let's create one label by add text elements to our SVG element.

// Add a Label
// y-axis label
svg.append("text")
	.attr("class", "y label")
	.attr("text-anchor", "end")
	.text("No. of Rats")
	.attr("transform", "translate(20, 20) rotate(-90)");

c. Style Fonts In the head of your document, use some CSS to style your font

<style>
	text {
		font-family: "Open Sans", sans-serif;
		font-size: 12px;
	}
</style>

Our chart:

Styled Bar Chart with Labels and Axes (Click to view this example on its own.)

6. What happens if our array changes size?

Data can change. What if your number of data values changes? Or the value of the maximum data value changes? There are two different methods for this. One is to manually build in a scale, and the other is to use the D3 Domain and range methods. We will use a combo of the two in our example.

Bar Chart with Additional Data Elements (Click to view this example on its own.)

In all likelihood, you are not going to be working with static data. What happens then if our dataset changes size? In that case, the number of values might be different, or the minimum or maximum values might change. For example, let's add a few more elements to our data array

// New dataset with More Numbers
var ratData = [ 40, 70, 60, 20, 40, 100, 60 ]; // Rat data!

To accomplish this, we need to adjust the places in the code that sets the X location of where the bars are drawn, how wide the bar should be, and the max value of the dataset. Put this code right after where you define the width and height of the SVG.

// Get length of dataset
var arrayLength = ratData.length; // length of dataset
var maxValue = d3.max(ratData, function(d) { return +d;} ); // get max value of our dataset
var x_axisLength = 100; // length of x-axis in our layout
var y_axisLength = 100; // length of y-axis in our layout

Here we get the length of the array so we can we can properly layout the correct number of bars in the chart, and the maximum data value of our dataset so we can we can give correct relative heights to the bars. The length of the X and Y axes are needed for our calculations.

Doing it the D3 Way: Using Scales, Domains, and Ranges

The second method is to use a scale to adjust our visualization to match our data. This will be discussed in greater detail the next session on scatterplots, but let's display how this works quickly.

In D3, scales are represented using domain and range. Domain represents the input values, and range represents the possible output values on your screen. In short, a pixel is not always equal to a data point. If our maximum value is 120, but our visualization is only 100 pixels tall, we need to to set up a scale that will set the data value of 120 to draw an SVG shape that is 100 pixels tall. Scott Murray, in his tutorial on scales, represents domains and ranges as follows.

Scales: Domain and Range Scott Murray

To implement the scale in our visualization, create a variable, we'll call it yScale, and then use the scale.linear domain and range methods to set up our scale. We will set the domain to be 0 to the max value of our data array, and the range to be the range of our visualization. The code for our scale will look like the following, enter this after the variables we just set.

var yScale = d3.scaleLinear()
	.domain([0, maxValue])
	.range([0, y_axisLength]);

This takes in our data and scales it to our visualization.

Next, modify the generate rectangle elements method in our code to properly locate x, y, width, and height. We'll use the yScale for the y and height values, and some simple math for our x and width values.


// Select and generate rectangle elements
svg.selectAll( "rect" )
	.data( ratData )
	.enter()
	.append("rect")
	.attr( "x", function(d,i){
		return i * (x_axisLength/arrayLength) + 30; // Set x coord of rect using length of array
	})
	.attr( "y", function(d){
		return h - yScale(d) - 75; // Set y coordinate of rect using the y scale
	})
	.attr( "width", (x_axisLength/arrayLength) - 1) // Set bar width using length of array, leave gap of 1px between rect
	.attr( "height", function(d){
		return yScale(d); // Set height of using the scale
	})
	.attr( "fill", "steelblue");

This will change the location and dimensions of our bars to match the new dataset. Try adjusting the array and seeing how the visualization changes dynamically!

Bar Chart with Additional Data Elements (Click to view this example on its own.)

7. Load a Tabular Dataset

Most of the time, you will be working with datasets that are read from other files, such as a CSV file. D3 supports reading from CSV, TSV, and other tabular formats.

To do this, you have to do three main things

  1. Replace ratData array with call to CSV
  2. Put the code that creates your visualization within one function called createVisualization().
  3. Locate and correct the calls to your dataset in your function. (d -> d.rats)

We are going to read data that is being held in CSV. Up to now, our data has been in a simple array, but now our data is going to have multiple variable. D3 has a method that reads a CSV file and stores it as a JSON. The JSON can be conceptualized as the following.

Our Dataset

In your materials, locate rat-data.csv. It contains the exact same small dataset. It is saved in CSV format, with the first row as a column header.

city,rats
Brookline,40
Boston,90
Cambridge,30
Somerville,60

When the CSV method of D3 is called, it reads the delimited file and saves it as a JSON element.

[{"city": "Brookline", "rats": 40 },
{"city": "Boston", "rats": 90 },
{"city": "Cambridge", "rats": 30 },
{"city": "Somerville", "rats": 60 }]

Using d3.csv will read the CSV and store the data as a JSON in which you can parse the dataset.

1. Replace ratData array with call to CSV

Replace the dataset array that looks like this:

// New dataset with More Numbers
var ratData = [ 40, 70, 60, 20, 40, 100, 60 ]; // Rat data!

With the following:

// New dataset read from CSV
var ratData = [];

d3.csv("rat-data.csv", function(d) {
	return {
		city : d.city, // city name
		rats : +d.rats // force value of rats to be number (+)
	};
}, function(error, rows) { // catch error if error, read rows
	ratData = rows; // set ratData equal to rows
	console.log(ratData);
	createVisualization(); // call function to create chart
});

This is an empty array that is then populated with a call to a CSV. Within the function, call a new function named createVisualization()

2. Put the entire D3 chart code into one function called createVisualization()

Create the createVisualization() function by declaring a function, and then putting the D3 code that creates the chart within the function. In short, put the entirety of the code that creates your chart into one function that we can call when we load the page.

...

// Write the createVisualization function
// This will contain the script that creates the chart
function createVisualization(){
	// Width and height of SVG
	var w = 150;
	var h = 175;

	// Get length of dataset
	var arrayLength = ratData.length; // length of dataset
	var maxValue = d3.max(ratData, function(d) { return +d;} ); // get maximum
	var x_axisLength = 100; // length of x-axis in our layout
	var y_axisLength = 100; // length of y-axis in our layout

	// Use a scale for the height of the visualization
	var yScale = d3.scaleLinear()
	    .domain([0, maxValue])
	    .range([0, y_axisLength]);

	//Create SVG element
	var svg = d3.select("body")
		.append("svg")
		.attr("width", w)
		.attr("height", h);

	// Select and generate rectangle elements
	svg.selectAll( "rect" )
		.data( ratData )
		.enter()
		.append("rect")
		.attr( "x", function(d,i){
			return i * (x_axisLength/arrayLength) + 30; // Set x coord
		})
		.attr( "y", function(d){
			return h - d * (y_axisLength/maxValue); // Set y coord
		})
		.attr( "width", (x_axisLength/arrayLength) - 1) 
		.attr( "height", function(d){
			return d * (y_axisLength/maxValue); // Set height to data value
		})
		.attr( "fill", "steelblue");

	// Create y-axis
	svg.append("line")
		.attr("x1", 30)
		.attr("y1", 75)
		.attr("x2", 30)
		.attr("y2", 175)
		.attr("stroke-width", 2)
		.attr("stroke", "black");

	// Create x-axis
	svg.append("line")
		.attr("x1", 30)
		.attr("y1", 175)
		.attr("x2", 130)
		.attr("y2", 175)
		.attr("stroke-width", 2)
		.attr("stroke", "black");

	// y-axis label
	svg.append("text")
		.attr("class", "y label")
		.attr("text-anchor", "end")
		.text("No. of Rats")
		.attr("transform", "translate(20, 20) rotate(-90)")
		.attr("font-size", "14")
		.attr("font-family", "'Open Sans', sans-serif");
}; // end of function

...
This puts the creation of our chart into one function that we can call at any point.

3. Change data references from d to d.rats

Last, because our dataset is now from a CSV, not just a simple array, we have to tell D3 to use the city and number fields that represent our data. When we load the CSV, D3 creates a JSON in which we can access the objects and properties of each data item. Remember that JavaScript holds properties of objects within the object itself, and you access those by using object.property. To access the rats value in our dataset, we need to use d.rats when we pass data to a function.

// Write the createVisualization function
// This will contain the script that creates the chart
function createVisualization(){
	// Width and height of SVG
	var w = 150;
	var h = 175;

	// Get length of dataset
	var arrayLength = ratData.length; // length of dataset
	var maxValue = d3.max(ratData, function(d) { return +d.rats;} ); // get maximum
	var x_axisLength = 100; // length of x-axis in our layout
	var y_axisLength = 100; // length of y-axis in our layout

	// Use a scale for the height of the visualization
	var yScale = d3.scaleLinear()
	    .domain([0, maxValue])
	    .range([0, y_axisLength]);

	//Create SVG element
	var svg = d3.select("body")
		.append("svg")
		.attr("width", w)
		.attr("height", h);

	// Select and generate rectangle elements
	svg.selectAll( "rect" )
		.data( ratData )
		.enter()
		.append("rect")
		.attr( "x", function(d,i){
			return i * (x_axisLength/arrayLength) + 30; // Set x coordinate of rectangle to index of data value (i) *25
		})
		.attr( "y", function(d){
			return h - yScale(d.rats); // Set y coordinate of rect using the y scale
		})
		.attr( "width", (x_axisLength/arrayLength) - 1) 
		.attr( "height", function(d){
			return yScale(d.rats); // Set height of using the scale
		})
		.attr( "fill", "steelblue");

	// Create y-axis
	svg.append("line")
		.attr("x1", 30)
		.attr("y1", 75)
		.attr("x2", 30)
		.attr("y2", 175)
		.attr("stroke-width", 2)
		.attr("stroke", "black");

	// Create x-axis
	svg.append("line")
		.attr("x1", 30)
		.attr("y1", 175)
		.attr("x2", 130)
		.attr("y2", 175)
		.attr("stroke-width", 2)
		.attr("stroke", "black");

	// y-axis label
	svg.append("text")
		.attr("class", "y label")
		.attr("text-anchor", "end")
		.text("No. of Rats")
		.attr("transform", "translate(20, 20) rotate(-90)")
		.attr("font-size", "14")
		.attr("font-family", "'Open Sans', sans-serif");
}; // end of function

Reload your chart.

Bar Chart with Data Loaded from CSV (Click to view this example on its own.)

8. Add Simple Hovering

In the last step in this exercise, we are going to label the bars of our chart and show the number of rats in each city. Because our data is variable, lets do this by adding a hover tooltip.

Hover Over Data Bars to see Values (Click to view this example on its own.)

The hover tooltip is created in two steps.

  1. Using D3, create a DIV element for the tooltip and set it to be hidden
  2. Use mouse event listeners (mouseover, mousemove, and mouseout) to listen for hover and set properties of the tooltip DIV element

1. Create the Tooltip DIV element

At the end of your createVisualization() function, create a variable called tooltip and set some of the properties of that element. Essentially, we want it to be an empty element that we will populate when we hover over the data bars. In the variable, select the body, append a div element to it, and set some of the properties of the div, such as position, font family and size, z-index (the 'layering' of the page), and the visibility. The variable should look as follows.

	// Create Tooltip and set it to be hidden	
var tooltip = d3.select("body")
	.append("div")
	.style("position", "absolute")
	.style("font-family", "'Open Sans', sans-serif")
	.style("font-size", "12px")
	.style("z-index", "10")
	.style("visibility", "hidden");

2. Add Event Listeners for mouse action to the rectangle generating method

Locate the method in which we create the rectangles based on the data. Here we need to add three methods that listen for mouse events and modify the tooltip div based on the mouse event. We want to set the tooltip to display on mouseover, set it move with the mouse on mousemove, and hide it on mouseout. When we set it to the display, we want to set the text property to display the correct values of our data (d.city and d.rats. In the mousemove function, we will use the event.pageX and event.pageY methods to return the position of the mouse pointer.

Modify your code to include the highlighted lines below.

>// Select and generate rectangle elements
svg.selectAll( "rect" )
	.data( ratData )
	.enter()
	.append("rect")
	.attr( "x", function(d,i){
		return i * (x_axisLength/arrayLength) + 30; // Set x coord
	})
	.attr( "y", function(d){
		return h - d.rats * (y_axisLength/maxValue); // Set y coord
	})
	.attr( "width", (x_axisLength/arrayLength) - 1) 
	.attr( "height", function(d){
		return d.rats * (y_axisLength/maxValue); // Set height to data value
	})
	.attr( "fill", "steelblue")
	.on("mouseover", function(d){
		return tooltip.style("visibility", "visible").text(d.city + ": " + d.rats);
	})
	.on("mousemove", function(d){
		return tooltip.style("top", (event.pageY-10)+"px").style("left",(event.pageX+10)+"px").text(d.city + ": " + d.rats);
	})
	.on("mouseout", function(d){
		return tooltip.style("visibility", "hidden");
	});

Save and refresh.

Bar Chart with Interactive Hover (Click to view this example on its own.)

There we have it!

Exhausted? You should be, we just covered alot! These are some basics of D3, displaying how to work with a simple array dataset, bind that dataset to an SVG, and create SVG elements that are encoded with our data. We have merely scratched the tip of the iceberg, the list of references below is excellent, and can build off of what we talked about today.

Try adding the following rows of additional data to your CSV, and observing how it dynamically changes your chart!

Watertown,75
Medford,10
Chelsea,55

In the next session, we will create a scatterplot, look at scales and domains, and explore how you can customize code snippets created by others. For now, observe and modify our chart!

Dynamic Bar Chart with Interactive Hover (Click to view this example on its own.)

9. Stacked Bar Charts

Note, view separate example in the provided files called stacked-barchart-completed.html to see the completed code.

Stacking your bar chart is a common solution when displaying data in bar chart form. An example might be the following dataset, found in the materials as rat-squirrel-data.csv.

City # of Rats # of Squirrels
Brookline 40 30
Boston 90 50
Cambridge 30 10
Somerville 60 40

Charted as follows.

Stacked Bar Chart with Interactive Hover (Click to view this example on its own.)

When creating a stacked bar chart, the primary difference is that you need to group your data into "stacks". These are group of data for each column. For this, you can use the Stack Layout methods of D3 to group and stack our data. The stack method creates not just stacked bar charts, but also stream graphs and any other grouped data. When stacking the data, it takes Y values and splits them into an array for each bar. The values of the array provide base heights, and you can take the differences between them to get a height of the rectangle bars. Apply these numbers to a Y-axis domain and range, as we did in the example above, you can get a stacked axis for your bars.

// set up the properties for stack
var stack = d3.stack()
	.keys(["rats", "squirrels"]) // set the keys for the data
	.order(d3.stackOrderNone) // set ordering properties, try d3.stackOrderAscending
	.offset(d3.stackOffsetNone); // set offset properties

// transpose your data using stack
var series = stack(data); // use the stack function

// view the stack
console.log(series);

Your stacked dataset is just a nested array

// resulting array
[[[0,40],[0,90],[0,30],[0,60]],
[[40,70],[90,140],[30,40],[60,100]]]

View source on our example to see this in action. Ultimately, we then draw the bar chart in the same fashion as the simple one above. Notice that the mouseover, the tooltips, the axes are the same, we just use the values provided in the array to properly place the SVG in our canvas.


The next session goes into further depth on scales, domains, and ranges, and show the creation of a scatterplot. Take your knowledge here and keep building!


References and Bibliography

1 - D3 Homepage - d3js.com

2 - Three Little Circles, Mike Bostock - http://bost.ocks.org/mike/circles/

3 - Intro to D3.js - Youjin Shin - http://codekitchen.mit.edu/events/introduction-to-d3-js/

4 - D3 Tutorials (Aligned Left) - Scott Murray - http://alignedleft.com/tutorials/d3


Return to DUSPVIZ tutorials page