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!
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.
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.
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!
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:
Tutorial on SVG paths (part of a larger nice tutorial on SVGs in general)
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.
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.
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”.
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.
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.
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');
});
SVGs
Tutorial on SVG paths (part of a larger nice tutorial on SVGs in general)
D3 Tutorials
More Specific D3 Tutorials
Visual Cinnamon has some nice tutorials on specific D3 topics–e.g. text placement, adapting chord diagrams, etc.
Understanding how JavaScript and D3 work
Good collections of examples
Just browse through bl.ocks.org! Bl.ocks is also a really nice way of showing Github gists in general