One of the things that makes New York a cool place to be is CitiBike, the bike-sharing system from our friends at Citi.
Credit where it’s due: they’ve blown this one out of the water. There’s even an open feed with up-to-the-minute data about docking station usage – which set my mind wandering in the way that these things tend to do.
I grabbed a static copy of the station feed and set to work with d3, the javascript data-driven documents library. There’s a little wizardry here and there but it’s super cool. Also it knows all about topoJson which is important. Let’s dive in!
Firstly, we find the SVG element on the page:
var svg = d3.select("svg") .attr("width", 1024) .attr("height", 768);
This does a DOM search for an SVG html element – so you’ll need one of those!
Then we create the map. Remember that in cartography, a “projection” is a mapping (ha!) of global coordinates (i.e. where in the world things are) to the cartesian coordinates we use to tell SVG where to put dots and lines on the page. The USGS uses the Albers projection, and if it’s good enough for them it’s good enough for me:
var projection = d3.geo.albers() .scale(300000) .rotate([73.969,0]) .center([0,40.759]) .translate([width/2,height/2]) .precision(.0001);
I got the values for Rotate
and Center
by pointing Google Maps at Manhattan and hacking the URL. I wish there were a nicer way to figure out the values for scale and precision, but it was really just trial and error.
var path = d3.geo.path().projection(projection);
Path is similar, in that it uses the projection to show geographic boundaries on screen. We’ll use this later on when we come to draw Manhattan.
Right now our screen shows a whole load of nothing, but if we load some map data and apply a little on-brand styling, we can see the massive horse’s cock that is Manhattan:
d3.json("nyc-boroughs.json", function(error, nyb) { svg.append("g") .attr("id", "boroughs") .selectAll(".state") .data(nyb.features) .enter().append("path") .attr("class", function(d){return d.properties.borough;}) .attr("d", path); });

Bingo! But what’s going on here? d3 works on a callback model, which means that the flow of control passes immediately from d3.json
whilst the file loading happens in the background. As soon as the file is read, the anonymous function is called with the data as an argument, allowing us to process it.
Firstly, we’re creating a g
element as a child of the top-level svg
element – this is a logical group we use to contain the borough paths. An SVG group doesn’t render anything to screen, it’s purely an organisational construct. attr
sets an html attribute – a name/value pair that, in this case, sets the id
of the newly-created g
element to the string “boroughs”. This allows us to style the borough elements using CSS.
Next, we iterate through the features
collection in the json data, creating an SVG path
for each one. I’m using d3’s selectAll
selector, which is slightly magical and explained in great detail here. Essentially we’re mapping feature data to .state
elements that don’t even exist yet – and setting the data (“d”) attribute using the path function we created earlier.
The syntax here is a little tricky – but if we expand out javascript’s anonymous function call syntax it should be clearer. That last line is equivalent to:
.attr("d", function(featureData){return path(featureData);});
…and because the featureData element is in the widely-used topoJson
format, the path
function knows how to interpret and render it.
Next, let’s load that bad boy bike data. Again we load the json file, but this time we need to interpret the latitude and longitude ourselves:
d3.json("citibike.json", function(error, data){ svg.append("g") .selectAll("circles.points") .data(data.stationBeanList) .enter() .append("circle") .attr("r", 5) .attr("transform", function(stationData){ return "translate(" + projection([stationData.longitude,stationData.latitude]) + ")" }) });
This time, we’re iterating over the stationBeanList
property in the resultant json object. For each station we create a circle
element with a radius of 5. Then, we apply the projection function to the latitude and longitude for each station – that ensures the dots line up to the right places on the map.
So, with a little styling and some tooltip magic from d3-tip, the beginnings of a fun Summer project. Take a look at the whole thing over at bl.ocks.org!
One thought on “Plotting CitiBike usage data with d3”