One of the many interesting things Github does are punchcards for repositories that can tell you when people work on their code. Unfortunately, they're only per-repository and I was interested in per-user Github punchcards.

So I made my own.

Collecting the data was fairly straightforward, finding a simple tutorial/example of a scatterplot in d3.js proved to be less than trivial.

## Scatterplotting

Drawing a scatterplot is nothing more than distributing data into buckets in a two dimensional space, then drawing a circle based on how many entities ended up in a particular bucket. Adding some colour gives us an extra dimension.

For starters, we're going to need some simple HTML and a bit of CSS to make things prettier.

<style>.axis path,.axis line {fill: none;stroke: #eee;shape-rendering: crispEdges;}.axis text {font-family: sans-serif;font-size: 11px;}.loading {font-family: sans-serif;font-size: 15px;}.circle {fill: #222;}</style><div id="punchcard"></div><script src="http://d3js.org/d3.v2.min.js"></script><script src="script.js"></script>

The *div* is where our scatterplot will end up. It doesn't need any styling since we'll do that with d3 directly, but splashing some CSS on stuff in the graph is going to make things look much better.

Going into *script.js* we start off by defining the width, height and padding for our graph. Due to d3's magic we'll later be able to change these and have the graph scale itself properly without further intervention.

var w = 940,h = 300,pad = 20,left_pad = 100,Data_url = "/data.json";

Next we define our scatterplot

var svg = d3.select("#punchcard").append("svg").attr("width", w).attr("height", h);

This tells d3 that we want to put some svg in the *punchcard* div and how big we want it.

The next step is defining our *x _and _y* scales. They're helpful for translating data into x,y positions on the graph since they're usually not the same.

var x = d3.scale.linear().domain([0, 23]).range([left_pad, w - pad]),y = d3.scale.linear().domain([0, 6]).range([pad, h - pad * 2]);

We specified that *x _coordinates map from values between 0 and 23 to coordinates between the left padding and however big the graph is. Similarly for _y* coordinates.

D3 will handle everything else for us.

Next we should define our axes since graphs without labeled axes are rather useless.

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

Essentially we've told the axes to use their corresponding scales and where we want the labels to end up.

Now it's finally time to draw something!

svg.append("g").attr("class", "axis").attr("transform", "translate(0, " + (h - pad) + ")").call(xAxis);svg.append("g").attr("class", "axis").attr("transform", "translate(" + (left_pad - pad) + ", 0)").call(yAxis);

As you can see, d3 was nice enough to figure out on its own how many ticks to draw and where to put them. This usually makes graphs more readable, but in our case we do want all the ticks.

All it takes is telling the axes how many ticks we want and while we're at it let's improve the labels as well.

var xAxis = d3.svg.axis().scale(x).orient("bottom").ticks(24).tickFormat(function (d, i) {var m = d > 12 ? "p" : "a";return d % 12 == 0 ? 12 + m : (d % 12) + m;}),yAxis = d3.svg.axis().scale(y).orient("left").ticks(7).tickFormat(function (d, i) {return ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday",][d];});

The *.tickFormat* method allows us to give d3 a function that calculates what a label should look like and we used *.ticks* to specify how many ticks we want.

Much better, but since we'll be loading the data asynchronously, let's tell the user what's going on by placing *"Loading ..."* roughly in the middle of the plot.

svg.append("text").attr("class", "loading").text("Loading ...").attr("x", function () {return w / 2;}).attr("y", function () {return h / 2 - 5;});

Loading the data can be done with one of d3's many data loading functions.

d3.json(Data_url, function (punchcard_data) {

Within that function we are now going to draw the actual scatterplot.

The data I loaded is organized into triplets *[day, hour, N]* where the combination of *day* and *hour* tells us where to draw a circle and *N* tells us how big it should be.

We should define another scale for the radius of the circles.

var max_r = d3.max(punchcard_data.map(function (d) {return d[2];})),r = d3.scale.linear().domain([0,d3.max(punchcard_data, function (d) {return d[2];}),]).range([0, 12]);

Everything from *zero* to *max_r* will be mapped to radiuses between *0* and *12* pixels.

To make d3 put some data on our graph we need to tell it to load up our data into the graph and give a transformation that results in circles.

svg.selectAll(".loading").remove();svg.selectAll("circle").data(punchcard_data).enter().append("circle").attr("class", "circle").attr("cx", function (d) {return x(d[1]);}).attr("cy", function (d) {return y(d[0]);}).attr("r", function (d) {return r(d[2]);});

After removing the *"Loading ..." _we gave d3 our data, then said that for each datum a _circle* should be appended to the graph and given attributes *cx, cy* and *r* that determine where this circle will be displayed and how big it's going to be.

Notice we're using functions to compute these values, but because we're lazy we just rely on d3 doing all of the actual calculations with the *x, y* and *r* scales we defined earlier.

On pictures everything looks great now, but the circles appear very suddenly. Wouldn't it be nice if there was a sexy transition going on?

To do that we shove a .*transition()* and a *.delay(800)* before defining the radius.

svg.selectAll("circle").data(punchcard_data).enter().append("circle").attr("class", "circle").attr("cx", function (d) {return x(d[1]);}).attr("cy", function (d) {return y(d[0]);}).transition().duration(800).attr("r", function (d) {return r(d[2]);});

Now the circles appear in a lovely 800 millisecond transition.

Here's the full javascript code:

var w = 940,h = 300,pad = 20,left_pad = 100,Data_url = "/data.json";var svg = d3.select("#punchcard").append("svg").attr("width", w).attr("height", h);var x = d3.scale.linear().domain([0, 23]).range([left_pad, w - pad]),y = d3.scale.linear().domain([0, 6]).range([pad, h - pad * 2]);var xAxis = d3.svg.axis().scale(x).orient("bottom").ticks(24).tickFormat(function (d, i) {var m = d > 12 ? "p" : "a";return d % 12 == 0 ? 12 + m : (d % 12) + m;}),yAxis = d3.svg.axis().scale(y).orient("left").ticks(7).tickFormat(function (d, i) {return ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday",][d];});svg.append("g").attr("class", "axis").attr("transform", "translate(0, " + (h - pad) + ")").call(xAxis);svg.append("g").attr("class", "axis").attr("transform", "translate(" + (left_pad - pad) + ", 0)").call(yAxis);svg.append("text").attr("class", "loading").text("Loading ...").attr("x", function () {return w / 2;}).attr("y", function () {return h / 2 - 5;});d3.json(Data_url, function (punchcard_data) {var max_r = d3.max(punchcard_data.map(function (d) {return d[2];})),r = d3.scale.linear().domain([0,d3.max(punchcard_data, function (d) {return d[2];}),]).range([0, 12]);svg.selectAll(".loading").remove();svg.selectAll("circle").data(punchcard_data).enter().append("circle").attr("class", "circle").attr("cx", function (d) {return x(d[1]);}).attr("cy", function (d) {return y(d[0]);}).transition().duration(800).attr("r", function (d) {return r(d[2]);});});

###### Related articles

- More on Exploring Correlations in R
- HTML5: Render Urban Population Growth on a 3D World Globe With Three.js and Canvas
- Striking Scatterplot: Unemployment and Investment
- Building and Testing a Backbone App

## Learned something new?

Want to become an expert?

Here's how it works 👇

Leave your email and I'll send you thoughtfully written emails every week about **React**, **JavaScript**, and **your career**. Lessons learned over 20 years in the industry working with companies ranging from tiny startups to Fortune5 behemoths.

### Join Swizec's Newsletter

And get thoughtful letters 💌 on **mindsets, tactics, and technical skills** for your career. Real lessons from building production software. No bullshit.

"Man, love your simple writing! Yours is the only newsletter I open and only blog that I give a fuck to read & scroll till the end. And wow always take away lessons with me. Inspiring! And very relatable. 👌"

**Have a burning question that you think I can answer?** I don't have all of the answers, but I have some! Hit me up on twitter or book a 30min ama for in-depth help.

**Ready to Stop copy pasting D3 examples and create data visualizations of your own?**
Learn how to build scalable dataviz components your whole team can understand
with React for Data Visualization

**Curious about Serverless and the modern backend?** Check out
Serverless Handbook, modern backend for the frontend engineer.

**Ready to learn how it all fits together and build a modern webapp from scratch?**
Learn how to launch a webapp and make your first 💰 on the side with ServerlessReact.Dev

**Want to brush up on your modern JavaScript syntax?** Check out my interactive cheatsheet: es6cheatsheet.com

**By the way, just in case no one has told you it yet today: I love and appreciate you for who you are ❤️**