Reputation: 2306
Where I struggle the most with D3 is proper data manipulation prior to plugging it into existing models. I am trying to figure out the least amount of data manipulation required to create a basic D3 area chart with a time-based x-axis. I am starting with a JSON array of objects with a date
key and Date object property, along with several other key-value pairs to populate the chart.
For example:
[{date: Wed Jul 20 2016 00:00:00 GMT-1000 (HST), a: 5, b: 1, c: 9, nothankyou: 90},
{date: Wed Jul 21 2016 00:00:00 GMT-1000 (HST), a: 7, b: 2, c: 10, nothankyou: 70},
{date: Wed Jul 22 2016 00:00:00 GMT-1000 (HST), a: 6, b: 5, c: 3, nothankyou: 50},
...etc...]
The goal is to create an area chart with the values for a
, b
, and c
along the y-axis and time (date
) along the x-axis. Need to filter out unwanted data points, such as nothankyou
.
I have previously used d3.nest
to similarly manipulate data in another project, but isn't it overkill where the data is already organized by date?
The D3 area chart itself is fairly straightforward (it assumes the data has been d3.nest()-ed already):
var format = d3.time.format("%Y-%m-%d");
var margin = {top: 20, right: 30, bottom: 30, left: 40},
width = 450 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom,
delay = 500,
duration = 750;
var x = d3.time.scale()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var z = d3.scale.category20c();
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.ticks(d3.time.months);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var stack = d3.layout.stack()
.offset("zero")
.values(function(d) { return d.values; })
.x(function(d) { return d.date; })
.y(function(d) { return d.value; });
var nest = d3.nest()
.key(function(d) { return d.key; });
var area = d3.svg.area()
.interpolate("cardinal")
.x(function(d) { return x(d.date); })
.y0(function(d) { return y(d.y0); })
.y1(function(d) { return y(d.y0 + d.y); });
var svg = d3.select(".areaChart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
totalCrimeData.forEach(function(d) {
d.date = format.parse(d.date);
d.value = +d.value;
});
var layers = stack(nest.entries(data));
console.log("layers",layers);
x.domain(d3.extent(data, function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.y0 + d.y; })]);
svg.selectAll(".layer")
.data(layers)
.enter().append("path")
.attr("class", "layer")
.attr("d", function(d) { return area(d.values); })
.style("fill", function(d, i) { return z(i); });
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
Upvotes: 0
Views: 309
Reputation: 2306
Here is an interim solution (non D3) that I've come up with to structure the data the way that the D3 set up wants it. Comments welcome.
var data = [ {}, {}, {}, ... ] // see above
var dataSets = ["a","b","c"]
var newArr = [];
data.map(function(a) {
var prop;
var _this = this;
dataSets.map(function(k) {
prop = a.date+"|"+k;
if (!_this[prop]) {
_this[prop] = {
key: k,
value: 0,
date: a.date
};
newArr.push(_this[prop]);
}
if (prop === a.date+"|"+k) {
_this[prop].value += parseInt(a[k]);
}
});
}, Object.create(null));
console.log("reduced data", newArr);
Upvotes: 0