Skip to content

Instantly share code, notes, and snippets.

@joyrexus
Created November 7, 2013 19:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joyrexus/7360353 to your computer and use it in GitHub Desktop.
Save joyrexus/7360353 to your computer and use it in GitHub Desktop.
Nesting demo

Quick demo of how you can use D3's Nest in your node apps, sans D3.

nest = require 'nest'
assert = require 'assert'
isEqual = assert.deepEqual

Suppose we have an array of records:

data = [
  type: "apple"
  color: "green"
  quantity: 1
 , 
  type: "apple"
  color: "red"
  quantity: 2
 , 
  type: "grape"
  color: "green"
  quantity: 3
 ,
  type: "grape"
  color: "red"
  quantity: 4
]

Grouping

Let's group our entries by color:

groups = nest()
  .key((d) -> d.color)  # group entries by color
  .entries(data)

expected = [
  key: 'green'
  values: 
    [ { type: 'apple', color: 'green', quantity: 1 },
      { type: 'grape', color: 'green', quantity: 3 } ]
 ,
  key: 'red'
  values: 
    [ { type: 'apple', color: 'red', quantity: 2 },
      { type: 'grape', color: 'red', quantity: 4 } ]
]
isEqual groups, expected, 'group by color'

Rollups

Let's define an aggregator function for a rollup. Here the total method sums up the quantity attribute of each entry:

sum = (arr, acc) -> 
  x = 0
  x += (if acc then acc(i) else i) for i in arr
  x

total = (d) -> sum(d, (x) -> x.quantity)

So we can now get totals by color:

totals = nest()
  .key((d) -> d.color)    # group by color
  .rollup(total)          # sum entries by quantity
  .entries(data)

expected = [ { key: 'green', values: 4 }, { key: 'red', values: 6 } ]
isEqual totals, expected, 'total by color'

Similarly by type:

totals = nest()
  .key((d) -> d.type)     # group by type
  .rollup(total)          # sum entries by quantity
  .entries(data)

expected = [ { key: 'apple', values: 3 }, { key: 'grape', values: 7 } ]
isEqual totals, expected, 'total by type'
(function() {
function _class(ctor, properties) {
try {
for (var key in properties) {
Object.defineProperty(ctor.prototype, key, {
value: properties[key],
enumerable: false
});
}
} catch (e) {
ctor.prototype = properties;
}
}
var Map = function(object) {
var map = new _Map;
if (object instanceof _Map) object.forEach(function(key, value) { map.set(key, value); });
else for (var key in object) map.set(key, object[key]);
return map;
};
function _Map() {}
_class(_Map, {
has: function(key) {
return _map_prefix + key in this;
},
get: function(key) {
return this[_map_prefix + key];
},
set: function(key, value) {
return this[_map_prefix + key] = value;
},
remove: function(key) {
key = _map_prefix + key;
return key in this && delete this[key];
},
keys: function() {
var keys = [];
this.forEach(function(key) { keys.push(key); });
return keys;
},
values: function() {
var values = [];
this.forEach(function(key, value) { values.push(value); });
return values;
},
entries: function() {
var entries = [];
this.forEach(function(key, value) { entries.push({key: key, value: value}); });
return entries;
},
forEach: function(f) {
for (var key in this) {
if (key.charCodeAt(0) === _map_prefixCode) {
f.call(this, key.substring(1), this[key]);
}
}
}
});
var _map_prefix = "\0", // prevent collision with built-ins
_map_prefixCode = _map_prefix.charCodeAt(0);
var nest = function() {
var nest = {},
keys = [],
sortKeys = [],
sortValues,
rollup;
function map(mapType, array, depth) {
if (depth >= keys.length) return rollup
? rollup.call(nest, array) : (sortValues
? array.sort(sortValues)
: array);
var i = -1,
n = array.length,
key = keys[depth++],
keyValue,
object,
setter,
valuesByKey = new _Map,
values;
while (++i < n) {
if (values = valuesByKey.get(keyValue = key(object = array[i]))) {
values.push(object);
} else {
valuesByKey.set(keyValue, [object]);
}
}
if (mapType) {
object = mapType();
setter = function(keyValue, values) {
object.set(keyValue, map(mapType, values, depth));
};
} else {
object = {};
setter = function(keyValue, values) {
object[keyValue] = map(mapType, values, depth);
};
}
valuesByKey.forEach(setter);
return object;
}
function entries(map, depth) {
if (depth >= keys.length) return map;
var array = [],
sortKey = sortKeys[depth++];
map.forEach(function(key, keyMap) {
array.push({key: key, values: entries(keyMap, depth)});
});
return sortKey
? array.sort(function(a, b) { return sortKey(a.key, b.key); })
: array;
}
nest.map = function(array, mapType) {
return map(mapType, array, 0);
};
nest.entries = function(array) {
return entries(map(Map, array, 0), 0);
};
nest.key = function(d) {
keys.push(d);
return nest;
};
// Specifies the order for the most-recently specified key.
// Note: only applies to entries. Map keys are unordered!
nest.sortKeys = function(order) {
sortKeys[keys.length - 1] = order;
return nest;
};
// Specifies the order for leaf values.
// Applies to both maps and entries array.
nest.sortValues = function(order) {
sortValues = order;
return nest;
};
nest.rollup = function(f) {
rollup = f;
return nest;
};
return nest;
};
if (typeof module !== "undefined" && module !== null) {
module.exports = nest;
} else {
this.nest = nest;
}
}).call(this);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment