square/crossfilter — pull request #48 by chondl (2012-12-05 07:34:11+00:00)
Title: Support pivot groups for cross tabulation

This is a first cut at adding a "pivotGroup" to maintain cross tabulations across multiple dimensions.

This has tests and decent performance, but needs a bit more work to be generally useful (in particular it does not update correctly if data is added to the crossfilter after the pivotGroup is created). There is some duplication of code with the regular group object that could also be eliminated. Happy to spend more time on this if there is interest in merging it upstream.

The API is very simple. The crossfilter object has a new method pivotGroup that takes an array of group objects as the dimensions for cross tabulation. The pivotGroup has reduce(), reduceCount(), and reduceSum() methods to define the reduce operation in the same manner as regular group objects. The pivotGroup only has two methods for accessing results: size() and all() which also behave similarly to regular group objects, with the keys for the results returned in all() being an array of key values for each dimension. Results from all() are returned sorted.

Construction performance is enhanced by using FNV-1a hash function from bloomfilter.js by jasondavis.

```javascript

var data = [ { gender:'Female', handed:'Right-handed', score: 9},
{ gender:'Male', handed:'Left-handed', score: 2},
{ gender:'Female', handed:'Right-handed', score: 32},
{ gender:'Male', handed:'Right-handed', score: 22},
{ gender:'Male', handed:'Left-handed', score: 3},
{ gender:'Male', handed:'Right-handed', score: 21},
{ gender:'Female', handed:'Right-handed', score: 99},
{ gender:'Female', handed:'Left-handed', score: 12},
{ gender:'Male', handed:'Right-handed', score: 0},
{ gender:'Female', handed:'Right-handed', score: 1},
],
c = crossfilter(data),
dim = { gender: c.dimension(function(v) { return v.gender }),
handed: c.dimension(function(v) { return v.handed }) }
group = { gender: dim.gender.group(),
handed: dim.handed.group() },
pivotGroup = c.pivotGroup([group.gender, group.handed])

assert.deepEqual(pivotGroup.all(), [{key:['Female', 'Left-handed'], value:1},
{key:['Female', 'Right-handed'], value:4},
{key:['Male', 'Left-handed'], value:2},
{key:['Male', 'Right-handed'], value:3}])

dim.gender.filter('Female')
assert.deepEqual(pivotGroup.all(), [{key:['Female', 'Left-handed'], value:1},
{key:['Female', 'Right-handed'], value:4},
{key:['Male', 'Left-handed'], value:0},
{key:['Male', 'Right-handed'], value:0}])

```