Well, it seems that there is no obvious answer that jumps out, so I will answer my question and post the technique that I used to solve this problem.
This example assumes that I have already created a dimension and grouping, which is passed as groupDim . Since I want to be able to summarize any arbitrary numeric field, I also pass fieldName so that it is available in the closing area of ββmy reduction functions.
One of the important characteristics of this method is that it relies on the fact that there is a way to uniquely identify which group each row belongs to. Thinking about OLAP terms, this is essentially a βtupleβ that defines the specific context of aggregation. But it can be anything if it deterministically returns the same value for all rows of data belonging to this group.
The end result is that empty groups will have a cumulative null value that can be easily detected and filtered out after the fact. Any group with at least one line will have a numerical value (even if it turns out to be zero).
Clarifications or suggestions to this are more welcome. Here is the code with inline comments:
function configureAggregateSum(groupDim, fieldName) { function getGroupKey(datum) { // Given datum return key corresponding to the group to which the datum belongs } // This object will keep track of the number of times each group had reduceAdd // versus reduceRemove called. It is used to revert the running aggregate value // back to "null" if the count hits zero. This is unfortunately necessary because // Crossfilter filters as it is aggregating so reduceAdd can be called even if, in // the end, all records in a group end up being filtered out. // var groupCount = {}; function reduceAdd(p, v) { // Here the code that keeps track of the invocation count per group var groupKey = getGroupKey(v); if (groupCount[groupKey] === undefined) { groupCount[groupKey] = 0; } groupCount[groupKey]++; // And here the implementation of the add reduction (sum in my case) // Note the check for null (our initial value) var value = +v[fieldName]; return p === null ? value : p + value; } function reduceRemove(p, v) { // This code keeps track of invocations of invocation count per group and, importantly, // reverts value back to "null" if it hits 0 for the group. Essentially, if we detect // that group has no records again we revert to the initial value. var groupKey = getGroupKey(v); groupCount[groupKey]--; if (groupCount[groupKey] === 0) { return null; } // And here the code for the remove reduction (sum in my case) var value = +v[fieldName]; return p - value; } function reduceInitial() { return null; } // Once returned, can invoke all() or top() to get the values, which can then be filtered // using a native Array.filter to remove the groups with null value. return groupedDim.reduce(reduceAdd, reduceRemove, reduceInitial); }
source share