/** * Returns a {@link pv.Flatten} operator for the specified map. This is a * convenience factory method, equivalent to new pv.Flatten(map). * * @see pv.Flatten * @param map a map to flatten. * @returns {pv.Flatten} a flatten operator for the specified map. */ pv.flatten = function(map) { return new pv.Flatten(map); }; /** * Constructs a flatten operator for the specified map. This constructor should * not be invoked directly; use {@link pv.flatten} instead. * * @class Represents a flatten operator for the specified array. Flattening * allows hierarchical maps to be flattened into an array. The levels in the * input tree are specified by key functions. * *

For example, consider the following hierarchical data structure of Barley * yields, from various sites in Minnesota during 1931-2: * *

{ 1931: {
 *     Manchuria: {
 *       "University Farm": 27.00,
 *       "Waseca": 48.87,
 *       "Morris": 27.43,
 *       ... },
 *     Glabron: {
 *       "University Farm": 43.07,
 *       "Waseca": 55.20,
 *       ... } },
 *   1932: {
 *     ... } }
* * To facilitate visualization, it may be useful to flatten the tree into a * tabular array: * *
var array = pv.flatten(yields)
 *     .key("year")
 *     .key("variety")
 *     .key("site")
 *     .key("yield")
 *     .array();
* * This returns an array of object elements. Each element in the array has * attributes corresponding to this flatten operator's keys: * *
{ site: "University Farm", variety: "Manchuria", year: 1931, yield: 27 },
 * { site: "Waseca", variety: "Manchuria", year: 1931, yield: 48.87 },
 * { site: "Morris", variety: "Manchuria", year: 1931, yield: 27.43 },
 * { site: "University Farm", variety: "Glabron", year: 1931, yield: 43.07 },
 * { site: "Waseca", variety: "Glabron", year: 1931, yield: 55.2 }, ...
* *

The flatten operator is roughly the inverse of the {@link pv.Nest} and * {@link pv.Tree} operators. * * @param map a map to flatten. */ pv.Flatten = function(map) { this.map = map; this.keys = []; }; /** * Flattens using the specified key function. Multiple keys may be added to the * flatten; the tiers of the underlying tree must correspond to the specified * keys, in order. The order of the returned array is undefined; however, you * can easily sort it. * * @param {string} key the key name. * @param {function} [f] an optional value map function. * @returns {pv.Nest} this. */ pv.Flatten.prototype.key = function(key, f) { this.keys.push({name: key, value: f}); delete this.$leaf; return this; }; /** * Flattens using the specified leaf function. This is an alternative to * specifying an explicit set of keys; the tiers of the underlying tree will be * determined dynamically by recursing on the values, and the resulting keys * will be stored in the entries keys attribute. The leaf function must * return true for leaves, and false for internal nodes. * * @param {function} f a leaf function. * @returns {pv.Nest} this. */ pv.Flatten.prototype.leaf = function(f) { this.keys.length = 0; this.$leaf = f; return this; }; /** * Returns the flattened array. Each entry in the array is an object; each * object has attributes corresponding to this flatten operator's keys. * * @returns an array of elements from the flattened map. */ pv.Flatten.prototype.array = function() { var entries = [], stack = [], keys = this.keys, leaf = this.$leaf; /* Recursively visit using the leaf function. */ if (leaf) { function recurse(value, i) { if (leaf(value)) { entries.push({keys: stack.slice(), value: value}); } else { for (var key in value) { stack.push(key); recurse(value[key], i + 1); stack.pop(); } } } recurse(this.map, 0); return entries; } /* Recursively visits the specified value. */ function visit(value, i) { if (i < keys.length - 1) { for (var key in value) { stack.push(key); visit(value[key], i + 1); stack.pop(); } } else { entries.push(stack.concat(value)); } } visit(this.map, 0); return entries.map(function(stack) { var m = {}; for (var i = 0; i < keys.length; i++) { var k = keys[i], v = stack[i]; m[k.name] = k.value ? k.value.call(null, v) : v; } return m; }); };