This is a problem that I have previously solved using php or (what I thought was) needlessly complex MySQL queries, but which I suddenly thought there must be a more elegant
I've not greatly improved on your overall approach, but if you use some more built-in methods and add underscore/lodash you can make the data transformation a lot shorter:
x.domain(d3.extent(data, function(d) { return d.date; })).ticks(d3.time.month);
y.domain([0, d3.max(data, function(d) { return d.value; })]);
var newData = x.ticks().map(function(monthBucket) {
return _.find(data, {date: monthBucket}) || {date: monthBucket, value: 0};
});
If we tell it that it should use monthly ticks, then we can just get the ticks array back out again rather than constructing a separate buckets array.
And then from that point we just use .map
rather than for
loop and lodash (or underscore) _.find
method to match up to our original data.
Updated fiddle here: http://jsfiddle.net/a5jUz/3/
Original answer below... in case you want to use D3 scales to spread out the values on bar graph:
1 - You have to use a time scale rather than an ordinal scale:
var x = d3.time.scale().range([0, width]);
2 - You need to set the domain of that scale based on the min/max of the date range:
x.domain(d3.extent(data, function(d) { return d.date; })).nice();
3 - [the ugly part] now that you're not using ordinal scale, you don't have the rangeBand
function for the bar positioning:
// TODO: calculate based on overall width & number of data points
.attr("x", function(d) { return x(d.date); })
.attr("width", 16)
Updated fiddle here: http://jsfiddle.net/LWyjf/
Here is another option for padding zeros without using lodash/underscore by using the d3.get()
as opposed to _.find()
. Not sure how this impacts performance though.
var date_range = d3.time.hours(startDate, endDate, 1);
var m = d3.map(data, function(d) { return d.date });
var newData = date_range.map(function(bucket) {
return m.get(bucket) || {date: bucket, value: 0};
});
To improve on @explunit 's answer I prefer padding the zeros prior to mapping the data to the domain range so that you get the full dataset which won't affected by changes in scale to the domain:
var date_range = d3.time.days(minX, maxX, 1);
var newData = date_range.map(function(dayBucket) {
return _.find(data, function(d) {
return d.date = dayBucket;
} || {date: dayBucket, value: 0};
});
and then
x.domain(d3.extent(newData, function(d) { return d.date; })).ticks(d3.time.day);
y.domain([0, d3.max(newData, function(d) { return d.value; })]);
etc.
I'll update the JSFiddle and post here soon.