You might expect this to be simple, but actually keeping track of intervals is one of the classic tricky problems of computer science and it requires a specialized data structure called an interval tree to do it properly.
It's a pretty common request, so out of curiosity, I looked for a JavaScript library for interval trees and found one by Mikola Lysenko.
I've incorporated it into a new example here. (source)
The important parts of the example are, first, to use groupAll
to populate the interval tree:
projectsPerMonthTree = ndx.groupAll().reduce(
function(v, d) {
v.insert(d.interval);
return v;
},
function(v, d) {
v.remove(d.interval);
return v;
},
function() {
return lysenkoIntervalTree(null);
}
)
Next we populate a fake group using the start and end dates, counting all the intervals which intersect with each month:
function intervalTreeGroup(tree, firstDate, lastDate) {
return {
all: function() {
var begin = d3.time.month(firstDate), end = d3.time.month(lastDate);
var i = new Date(begin);
var ret = [], count;
do {
next = new Date(i);
next.setMonth(next.getMonth()+1);
count = 0;
tree.queryInterval(i.getTime(), next.getTime(), function() {
++count;
});
ret.push({key: i, value: count});
i = next;
}
while(i.getTime() <= end.getTime());
return ret;
}
};
}
projectsPerMonthGroup = intervalTreeGroup(projectsPerMonthTree.value(), firstDate, lastDate),
(This could probably be simpler and cheaper if we used lower-level access to the interval tree, or if it had a richer API that allowed walking the intervals in order. But this should be fast enough.)
Finally, we set a filterFunction
so that we choose the intervals which intersect with a given date range:
monthChart.filterHandler(function(dim, filters) {
if(filters && filters.length) {
if(filters.length !== 1)
throw new Error('not expecting more than one range filter');
var range = filters[0];
dim.filterFunction(function(i) {
return !(i[1] < range[0].getTime() || i[0] > range[1].getTime());
})
}
else dim.filterAll();
return filters;
});
I've set it up so that it filters the month chart to show all projects which intersect with its own date range. If this is not desired, the groupAll
can be put on the intervalDimension
instead.