Preface

I should note here that I’m skipping over a lot here—there’s a ton of awesome stuff you can do with javascript, D3, svgs, and html/CSS more generally, and understanding how all of these play together can be very powerful. But, since we’re just playing around a little today, we’ll stick to just a few basics and I’ve linked a variety of resources at the bottom of the page to learn more!


Set up your html file

When we work in D3 and javascript, we’ll mostly be working in an html envrionment. So for that, we’ll need some very basic tags and formatting. Open a text file and paste the below code chunks into it, then save the file with a ‘.htm’ or ‘.html’ extension.

First, just set up the html and head:

<html>
<head>
<title>Epid 814 D3 Lab</title>

And, we also have to add the D3 library to our setup! We’re using the version at d3js.org here, but you could just reference a local copy too.

<script src="https://d3js.org/d3.v3.min.js"></script>

Last but not least, let’s close the head and start the main body of the html:

</head>
<body>

Everything we do below (unless we explicitly talk about editing the CSS or other stuff in the head), will go into the <body> section of the html.



SVGs

Next, before we dig in to D3, it’s useful to understand a little bit about Scalable Vector Graphics, or SVGs. As the name suggests, SVGs are a vector-based image format that you can use in html and a variety of other settings. One nice/interesting thing about them is that we can construct them by (basically) instructing the browser where and how to draw different elements, like circles, lines, and paths. SVGs are written using the usual xml/html-style tag formatting.


Circles

For example, here’s a circle:

<svg width="100" height="100">
<circle cx="50" cy="50" r="30" style="stroke:#000066; fill:#5555DD"/>
</svg>

In the above example, the initial tag, svg sets up the “paper” or space that we’ll be drawing in. Then cx and cy tell the browser the location for the center of the circle within the image, and r tells the browser the radius. The styling for our circle is done using CSS notation (we could also set this globally for all circles or for circles of a specific class using CSS in the html head or a separate CSS file).

One thing to note—SVGs work using the top-left corner as (0,0). So when we say cx="50" cy="50", that means go 50 pixels out from the top left (i.e. down 50 and to the right 50). You can change the units to other things if you want (em, mm, in, etc.) as well.

Try it out! Try changing the color too—remember that fills are done in hex, where the numbers each represent the amount of red, green, and blue as RRGGBB. Change the fill to be a different color and see how it looks!


Paths

An important piece of SVGs is the path element—paths are lines that can take on whatever shape you choose. It is highly versatile, but also means you have to tell it exactly what shapes you want to draw. One way to think about SVG paths is that you are moving a virtual ‘pen’, and you have to tell the browser where to put the pen down, then how to trace various paths around on the paper, where to pick it back up again, etc. It’s a little cumbersome, but you can basically draw anything you like (and as we’ll see, D3 makes it easier too).

So, to do this, you write paths where you move from one point to another with either straight lines or curves. The curves can be arcs of a circle, or a couple types of Bézier curves.

Here’s an example to draw a triangle—we start by moving the pen with the M command to point (15,5) (note the M command doesn’t draw things, it just moves the pen). Then we use the ‘line-to’ or L command to draw a line to two other points, and finally back to our original starting point to close the triangle:

<svg width="100" height="100">
<path d=" M 15 5 
          L 5 90 
          L 60 75 
          L 15 5" 
          stroke="DeepSkyBlue" stroke-width="2" fill="none" /> 
</svg>

Note that I used a named color here—a list of the named SVG colors can be found here.

You can also fill in shapes that you draw with paths—try filling in the triangle above with a color! There are a number of other things you can do with paths—they’re highly versatile and can basically draw anything. Here are two tutorials that give a nice overview of SVG paths and show you the different options for how to draw curves, lines, etc:



JavaScript and D3

Now, where do JavaScript and D3 come into all this? Well, you can imagine that if you had to draw a scatterplot by hand with SVGs it would be annoying—you’d have to make a circle for each point, lines for the axes, etc. Instead, we use JavaScript (and in our case, D3) to automate a lot of that drawing.

To start, note that javascript goes in a script tag, like this:

<script>
  alert("Hello, world!");
</script>

Add the above to your html document and re-open it in a browser (hopefully you get an alert box!). Note also that JavaScript lines end with a ;.

There’s a lot to learn with just JavaScript on its own, and your D3-ing will be stronger with some JavaScript background (see the resources at the end for some about this!), but for now, let’s dive right in to D3.


D3

Data-Driven Documents, or D3, is a JavaScript library for working with and visualizaing data. It has a slightly different way of working with variables, documents, and data than you might be used to—it operates on the html code, changing and adding tags (like the svg tags we saw before) in an automated way.

To do this, there are a couple of key concepts that we’ll need: select, append, enter, and exit.


D3 - Select

Selections in D3 are a way of grabbing all the html tags or other elements of a specific type (e.g. a CSS class or ID, or something else), so that you can modify them. This has some advantages in that it’s often a bit quicker than the equivalent straight javascript code (see examples at d3js.org for more info).

To illustrate how select works, let’s start by selecting the <body> tag from our html document and editing it. Before you add the code below, look at the html source code for your <body> in the browser. Now, add the following:

<script>
d3.select("body")
  .style("background-color", "AliceBlue");
</script>

You should see that when you refresh your html file in the browser, the background is now blue. Now, right-click/control-click on the background in your browser and choose ‘inspect element’—has the body tag changed?

The d3.select command pulls all tags with the name “body” and then changes their property “style” to have the attribute “background-color: AliceBlue”.

We have our two SVG’s as well—let’s try modifying those. Let’s change the location of the circle and make it a different color—add this code inside your <script> tags:

d3.select("circle")
  .attr("cx", 80)
  .style("fill", "#DD55DD");

the .attr command modifies attributes of the circle (i.e. things that go in the circle tag, like cx, cy, and r), while the .style command modifies attributes that go inside the CSS style part of the tag (i.e. things in the style="stroke: #000000; fill: #000000;" part of the tag).

Note that we’ve moved the center of the circle over a little to far, so it’s falling off the edge of the “paper”.


D3 - Append

Once, we’ve selected something, we can also append more elements to it—for example, we can select the <body and append other tags inside, like paragraphs <p>, or <svg>’s, or whatever else. This makes use of the chain notation that D3 uses:

var oursvg = d3.select("body")
    .append("svg")
    .attr("id","tempsvg")
    .attr("width",120)
    .attr("height",120);

Note that here we made our selection into a new javascript variable, oursvg. Now, we can work with oursvg and further append circles, lines, paths, and other elements into it:

oursvg.append("circle")
    .attr("cx",20)
    .attr("cy",20)
    .attr("r",10)
    .style("stroke","#000066")
    .style("fill","#999999");

//Note we could also have added the circle by selecting the ID of our SVG:
// d3.select("#tempsvg")
//   .append("circle")

Reload your html with the above additions—you should see that an svg has been added with a circle element included. Now, for just one circle, this is a lot longer code than just the usual <circle> tag. But if you want to make a bunch of circles (e.g. based on a data set), then D3 is very convenient—as we’ll see below.


D3 - Enter and Exit

Next, let’s see how to link D3 and data. We’ll use the following randomly generated data set as an example:

//generate 100 random datapoints
var data = [];
for(var i=0; i<100; i++){
    var datapoint = {};
    datapoint['x'] = Math.random() * 400;
    datapoint['y'] = datapoint['x']*(0.5+Math.random());
    datapoint['color'] = Math.random();
    datapoint['name'] = 'Name'+i;
    data.push(datapoint);
}

Open the console in your browser and type data to see what this looks like—you should see an array of objects, where each object has an x, y, and color value. You can also generate data as a CSV or JSON file and read it in to JavaScript/D3.

Now, we’re going to ‘bind’ the data to the html elements we’re working with, and then use D3 to automatically generate the new html elements for each data point (e.g. generating svg circles for each point on a graph).

First, adapt the oursvg code you made in the Append section above, to make an empty SVG that’s 500x500 pixels.

Next, let’s add a quick function to define a color scheme (just to make the dots look nice):

var color = d3.scale.linear()
    .domain([0,1])
    .range(["blue", "red"]);

Now, we’re ready to bind the data and draw our dots!

oursvg.selectAll("circle")
    .data(data)
    .enter()
    .append("circle")
    .attr("cy", function(d) { return d.y;  })
    .attr("cx", function(d) { return d.x;  })
    .attr("r", 6)
    .style("opacity", 0.6)
    .style("fill", function(d) { return color(d.color);});

Let’s break down what exactly we did here:

  • oursvg.selectAll("circle") - this selects all elements in oursvg that are circles. But wait—there aren’t any circles, it’s an empty SVG! This is part of the funny way things work in D3—the selection here will be empty but we’ll fill in the circles further in the chain of commands.

  • .data(data) - this parses and binds our data set. Each of the commands that follow will be run for each object in the data set.

  • .enter() - this creates new entries into the document, making a placeholder element for each new data point

  • .append("circle") - takes the placeholder element from enter() and adds a new circle element

  • the subsequent commands define the different attributes and styles for each new element we create—note that in some cases we use entries in each data point to define the circle’s properties (like the x- and y-positions and the circle color).

We can also use exit() to remove elements, but we’ll get to that later on.



D3 - Scatterplot

Now let’s pull it all together and make this into a scatterplot. One thing we need to do is transform the SVG—since it treats the top left corner as (0,0), our coordinates will be flipped from what we expect.

To make it interactive, let’s add a little tooltip that will appear when we hover over the text also (this part doesn’t work here but should work in your html file). Source code is below.

///// Generate simulated data /////
var data = [];
for(var i=0; i<100; i++){
    var datapoint = {};
    datapoint['x'] = Math.random() * 400;
    datapoint['y'] = datapoint['x']*(0.5+Math.random());
    datapoint['name'] = 'Name'+i;
    datapoint['color'] = Math.random();
    data.push(datapoint);
}


///// Generate Scatterplot /////

// Size and margins for the chart
var margin = {top: 20, right: 65, bottom: 60, left: 70}, 
  width = 550 - margin.left - margin.right,
  height = 500 - margin.top - margin.bottom;

// Set up color scheme
var color = d3.scale.linear()
    .domain([0,1])
    .range(["blue", "red"]);


// Main svg for plotting
var chart = d3.select("body")
    .append('svg')
    .attr('width', width + margin.right + margin.left)
    .attr('height', height + margin.top + margin.bottom)
    .attr('class', 'chart');

// Translate the location of the chart in the SVG to account for margins
var main = chart.append('g')
      .attr('transform','translate('+margin.left+','+margin.top+')')
      .attr('width', width)
      .attr('height', height)
      .attr('class', 'main');


// Set up x and y axis scales using D3
var x = d3.scale.linear()
      .domain([0, 400])
      .range([0, width]);

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


// Draw the x axis
var xAxis = d3.svg.axis()
      .scale(x)
      .orient('bottom');

var xAxisDraw = main.append('g')
      .attr('transform', 'translate(0,' + height + ')')
      .attr('class', 'axis')
      .style('fill', 'none')
      .style('stroke', '000')
      .style('stroke-width', '1.25px')
      .call(xAxis);

xAxisDraw.selectAll('text').style('fill','000').style('stroke','none');


// Draw the y axis
var yAxis = d3.svg.axis()
    .scale(y)
    .orient('left');

var yAxisDraw = main.append('g')
      .attr('transform', 'translate(0,0)')
      .attr('class', 'axis')
      .style('fill', 'none')
      .style('stroke', '000')
      .style('stroke-width', '1.25px')
      .call(yAxis);
      
yAxisDraw.selectAll('text').style('fill','000').style('stroke','none');

   
// Draw the scatterplot
var g = main.append("svg:g");

var dataDraw = g.selectAll("circle")
    .data(data)
    .enter()
    .append("circle")
    .attr("cy", function(d) { return y(d.y);  })
    .attr("cx", function(d) { return x(d.x);  })
    .attr("r", 6)
    .style("opacity", 0.6)
    .style("fill", function(d) { return color(d.color);});

    
// Add x axis label      
chart.append("text")
    .attr("text-anchor", "end")
    .attr("x", width+margin.left)
    .attr("y", height+margin.top+margin.bottom-15)
    .text("X-axis label");
    
// Add y axis label
chart.append("text")
    .attr("text-anchor", "end")
    .attr("y", margin.left/6)
    .attr("x", -margin.top)
    .attr("dy", ".75em")
    .attr("transform", "rotate(-90)")
    .text("Y-axis label");

    
// Add the hover tooltip

// Make a div to hold the tooltip contents
var tip = d3.select('body')
      .append('div')
      .attr('class', 'tip')
      .style('border', '1px solid grey')
      .style('background','rgba(255,255,255,0.8)')
      .style('padding', '5px')
      .style('position', 'absolute')
      .style('display', 'none')
      .on('mouseover', function(d, i) {
        tip.transition().duration(0);
      })
      .on('mouseout', function(d, i) {
        tip.style('display', 'none');
      });

// Add the tooltip
dataDraw.on('mouseover', function(d, i) {
        tip.transition().duration(0);
        tip.style('top', y(d.y) - 10 + 'px');
        tip.style('left', x(d.x) + 'px');
        tip.style('display', 'block');
        tip.html(d.name +"<br> Color Score: "+d.color);
      })
      .on('mouseout', function(d, i) {
        tip.transition()
        .delay(500)
        .style('display', 'none');
      });


Additional Resources

SVGs

D3 Tutorials

More Specific D3 Tutorials

Understanding how JavaScript and D3 work

Good collections of examples