D3 notes
D3, for data driven documents, is a javascript library for data visualization. With it you can visualize your data using charts, graphs, maps and whatever else you can describe in code. I have wanted to learn D3 for quite a while so when an opportunity presented itself, I jumped at it. You can view the simple demo here.
Takeaways
I don’t know if mine is a unique case, but when learning, I usually have a torrid time before my code works. D3 was no different. First, the map…
var projection = d3.geoMercator()
.center([lng,lat])
.scale(150)
.translate([x, y]);
d3.json("./testing.geojson", function (json) {
svg.selectAll("circle")
.data(json.features)
.enter()
.append("circle")
.attr("r", 5)
.attr("transform", function (d) {
return "translate(" + projection(d.geometry.coordinates) + ")";
})
.attr("fill", "blue");
})
I was drawing point data, for which svg circles are a good fit. The problem was, my map only showed a single point. Or more precisely, all the points were ‘smeared’ together. We (I had help) spent the better part of an afternoon trying to figure out what was going on. Clearly the projection parameters were wrong, but changing them only gave us slight hope before the thing would disappear from the screen. Then, deep in the night, as so often happens, I found it:
projection.fitSize([width, height], json)
This is a new helper in D3 v4. What it does is compute for you the scaling and translation parameters that will fit the given geojson into an area of width by height pixels. My code then changes…
var projection = d3.geoMercator();
d3.json("./testing.geojson", function (json) {
projection.fitSize([width, height], json);
// ...
})
If you’re using D3 v3 or lower, please let me know how you get your parameters
First huddle cleared and I was ready to move to the chart.
D3 will take you back to school and remind you why you failed that question of “… using a suitable scale, plot a graph of….” D3 does a good job of breaking down the graph, you have the scale, axes and the meat (bars for a bar chart, line, points…). I found that this modularity makes it easier for me to understand what am doing.
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
D3 scales are initialized by providing a range and a domain. The range defines the scale’s extents in screen units, while the domain defines the extent of the data you want to chart.
Initializing the scale with just the range defined, allows you to specify your data(domain) when it becomes available; x.domain(d3.extent(data, callback))
var line = d3.line()
.x(function (d) { return x(d.properties.Section); })
.y(function (d) { return y(d.properties.Height); })
.curve(d3.curveCardinal);
d3.line()
is the function responsible for drawing your line. Passed a value in the scale’s domain, x()
and y()
return the corresponding value in the range.
d.properties.[]
extracts the value from the geojson point feature.
At this point the scales are defined but nothing is drawn yet.
xaxis.call(d3.axisBottom(x));
d3.select(".yaxis").call(d3.axisLeft(y));
linechart.datum(data.features)
.attr('d', line);
D3 provides several ready to use axes. The axis is initialized by passing in the scale and drawn using the .call()
function on a selected svg group.
xaxis, yaxis and linechart are SVG
I highly doubt the coherence of this note, the demo source is a bit better (I hope).