1 /**
  2  * Returns a {@link pv.Flatten} operator for the specified map. This is a
  3  * convenience factory method, equivalent to <tt>new pv.Flatten(map)</tt>.
  4  *
  5  * @see pv.Flatten
  6  * @param map a map to flatten.
  7  * @returns {pv.Flatten} a flatten operator for the specified map.
  8  */
  9 pv.flatten = function(map) {
 10   return new pv.Flatten(map);
 11 };
 12 
 13 /**
 14  * Constructs a flatten operator for the specified map. This constructor should
 15  * not be invoked directly; use {@link pv.flatten} instead.
 16  *
 17  * @class Represents a flatten operator for the specified array. Flattening
 18  * allows hierarchical maps to be flattened into an array. The levels in the
 19  * input tree are specified by <i>key</i> functions.
 20  *
 21  * <p>For example, consider the following hierarchical data structure of Barley
 22  * yields, from various sites in Minnesota during 1931-2:
 23  *
 24  * <pre>{ 1931: {
 25  *     Manchuria: {
 26  *       "University Farm": 27.00,
 27  *       "Waseca": 48.87,
 28  *       "Morris": 27.43,
 29  *       ... },
 30  *     Glabron: {
 31  *       "University Farm": 43.07,
 32  *       "Waseca": 55.20,
 33  *       ... } },
 34  *   1932: {
 35  *     ... } }</pre>
 36  *
 37  * To facilitate visualization, it may be useful to flatten the tree into a
 38  * tabular array:
 39  *
 40  * <pre>var array = pv.flatten(yields)
 41  *     .key("year")
 42  *     .key("variety")
 43  *     .key("site")
 44  *     .key("yield")
 45  *     .array();</pre>
 46  *
 47  * This returns an array of object elements. Each element in the array has
 48  * attributes corresponding to this flatten operator's keys:
 49  *
 50  * <pre>{ site: "University Farm", variety: "Manchuria", year: 1931, yield: 27 },
 51  * { site: "Waseca", variety: "Manchuria", year: 1931, yield: 48.87 },
 52  * { site: "Morris", variety: "Manchuria", year: 1931, yield: 27.43 },
 53  * { site: "University Farm", variety: "Glabron", year: 1931, yield: 43.07 },
 54  * { site: "Waseca", variety: "Glabron", year: 1931, yield: 55.2 }, ...</pre>
 55  *
 56  * <p>The flatten operator is roughly the inverse of the {@link pv.Nest} and
 57  * {@link pv.Tree} operators.
 58  *
 59  * @param map a map to flatten.
 60  */
 61 pv.Flatten = function(map) {
 62   this.map = map;
 63   this.keys = [];
 64 };
 65 
 66 /**
 67  * Flattens using the specified key function. Multiple keys may be added to the
 68  * flatten; the tiers of the underlying tree must correspond to the specified
 69  * keys, in order. The order of the returned array is undefined; however, you
 70  * can easily sort it.
 71  *
 72  * @param {string} key the key name.
 73  * @param {function} [f] an optional value map function.
 74  * @return {pv.Nest} this.
 75  */
 76 pv.Flatten.prototype.key = function(key, f) {
 77   this.keys.push({name: key, value: f});
 78   return this;
 79 };
 80 
 81 /**
 82  * Returns the flattened array. Each entry in the array is an object; each
 83  * object has attributes corresponding to this flatten operator's keys.
 84  *
 85  * @returns an array of elements from the flattened map.
 86  */
 87 pv.Flatten.prototype.array = function() {
 88   var entries = [], stack = [], keys = this.keys;
 89 
 90   /* Recursively visits the specified value. */
 91   function visit(value, i) {
 92     if (i < keys.length - 1) {
 93       for (var key in value) {
 94         stack.push(key);
 95         visit(value[key], i + 1);
 96         stack.pop();
 97       }
 98     } else {
 99       entries.push(stack.concat(value));
100     }
101   }
102 
103   visit(this.map, 0);
104   return entries.map(function(stack) {
105       var m = {};
106       for (var i = 0; i < keys.length; i++) {
107         var k = keys[i], v = stack[i];
108         m[k.name] = k.value ? k.value.call(null, v) : v;
109       }
110       return m;
111     });
112 };
113