module Rubyvis # Returns a {@link pv.Flatten} operator for the specified map. This is a # convenience factory method, equivalent to new pv.Flatten(map). def self.flatten(map) Flatten.new(map) end # 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. # class Flatten def initialize(map) @map=map @keys=[] @leaf=nil end # 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. def key(k, f=nil) @keys.push(OpenStruct.new({:name=>k, :value=>f})) @leaf=nil self end # 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. def leaf(f) @keys.clear @leaf=f self end def recurse(value,i) if @leaf.call(value) @entries.push({:keys=>@stack.dup, :value=>value}) else value.each {|key,v| @stack.push(key) recurse(v, i+1) @stack.pop } end end def visit(value,i) if (i < @keys.size - 1) value.each {|key,v| @stack.push(key) visit(v,i+1) @stack.pop } else @entries.push(@stack+[value]) end end # 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. def array @entries=[] @stack=[] if @leaf recurse(@map,0) return @entries end visit(@map,0) @entries.map {|stack| m={} @keys.each_with_index {|k,i| v=stack[i] m[k.name]=k.value ? k.value.js_call(self,v) : v } m } end alias :to_a :array end end