(function (global, factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('vega-dataflow'), require('vega-util'), require('d3-collection'), require('d3-hierarchy')) :
  typeof define === 'function' && define.amd ? define(['exports', 'vega-dataflow', 'vega-util', 'd3-collection', 'd3-hierarchy'], factory) :
  (factory((global.vega = global.vega || {}, global.vega.transforms = {}),global.vega,global.vega,global.d3,global.d3));
}(this, (function (exports,vegaDataflow,vegaUtil,d3Collection,d3Hierarchy) { 'use strict';

  // Build lookup table mapping tuple keys to tree node instances
  function lookup(tree, key, filter) {
    var map = {};
    tree.each(function(node) {
      var t = node.data;
      if (filter(t)) map[key(t)] = node;
    });
    tree.lookup = map;
    return tree;
  }

  /**
    * Nest tuples into a tree structure, grouped by key values.
    * @constructor
    * @param {object} params - The parameters for this operator.
    * @param {Array<function(object): *>} params.keys - The key fields to nest by, in order.
    * @param {boolean} [params.generate=false] - A boolean flag indicating if
    *   non-leaf nodes generated by this transform should be included in the
    *   output. The default (false) includes only the input data (leaf nodes)
    *   in the data stream.
    */
  function Nest(params) {
    vegaDataflow.Transform.call(this, null, params);
  }

  Nest.Definition = {
    "type": "Nest",
    "metadata": {"treesource": true, "changes": true},
    "params": [
      { "name": "keys", "type": "field", "array": true },
      { "name": "generate", "type": "boolean" }
    ]
  };

  var prototype = vegaUtil.inherits(Nest, vegaDataflow.Transform);

  function children(n) {
    return n.values;
  }

  prototype.transform = function(_, pulse) {
    if (!pulse.source) {
      vegaUtil.error('Nest transform requires an upstream data source.');
    }

    var gen = _.generate,
        mod = _.modified(),
        out = pulse.clone(),
        tree = this.value;

    if (!tree || mod || pulse.changed()) {
      // collect nodes to remove
      if (tree) {
        tree.each(function(node) {
          if (node.children && vegaDataflow.isTuple(node.data)) {
            out.rem.push(node.data);
          }
        });
      }

      // generate new tree structure
      this.value = tree = d3Hierarchy.hierarchy({
        values: vegaUtil.array(_.keys)
                  .reduce(function(n, k) { n.key(k); return n; }, d3Collection.nest())
                  .entries(out.source)
      }, children);

      // collect nodes to add
      if (gen) {
        tree.each(function(node) {
          if (node.children) {
            node = vegaDataflow.ingest(node.data);
            out.add.push(node);
            out.source.push(node);
          }
        });
      }

      // build lookup table
      lookup(tree, vegaDataflow.tupleid, vegaDataflow.tupleid);
    }

    out.source.root = tree;
    return out;
  };

  /**
   * Abstract class for tree layout.
   * @constructor
   * @param {object} params - The parameters for this operator.
   */
  function HierarchyLayout(params) {
    vegaDataflow.Transform.call(this, null, params);
  }

  var prototype$1 = vegaUtil.inherits(HierarchyLayout, vegaDataflow.Transform);

  prototype$1.transform = function(_, pulse) {
    if (!pulse.source || !pulse.source.root) {
      vegaUtil.error(this.constructor.name
        + ' transform requires a backing tree data source.');
    }

    var layout = this.layout(_.method),
        fields = this.fields,
        root = pulse.source.root,
        as = _.as || fields;

    if (_.field) root.sum(_.field);
    if (_.sort) root.sort(_.sort);

    setParams(layout, this.params, _);
    if (layout.separation) {
      layout.separation(_.separation !== false ? defaultSeparation : vegaUtil.one);
    }

    try {
      this.value = layout(root);
    } catch (err) {
      vegaUtil.error(err);
    }
    root.each(function(node) { setFields(node, fields, as); });

    return pulse.reflow(_.modified()).modifies(as).modifies('leaf');
  };

  function setParams(layout, params, _) {
    for (var p, i=0, n=params.length; i<n; ++i) {
      p = params[i];
      if (p in _) layout[p](_[p]);
    }
  }

  function setFields(node, fields, as) {
    var t = node.data;
    for (var i=0, n=fields.length-1; i<n; ++i) {
      t[as[i]] = node[fields[i]];
    }
    t[as[n]] = node.children ? node.children.length : 0;
  }

  function defaultSeparation(a, b) {
    return a.parent === b.parent ? 1 : 2;
  }

  var Output = ['x', 'y', 'r', 'depth', 'children'];

  /**
   * Packed circle tree layout.
   * @constructor
   * @param {object} params - The parameters for this operator.
   * @param {function(object): *} params.field - The value field to size nodes.
   */
  function Pack(params) {
    HierarchyLayout.call(this, params);
  }

  Pack.Definition = {
    "type": "Pack",
    "metadata": {"tree": true, "modifies": true},
    "params": [
      { "name": "field", "type": "field" },
      { "name": "sort", "type": "compare" },
      { "name": "padding", "type": "number", "default": 0 },
      { "name": "radius", "type": "field", "default": null },
      { "name": "size", "type": "number", "array": true, "length": 2 },
      { "name": "as", "type": "string", "array": true, "length": Output.length, "default": Output }
    ]
  };

  var prototype$2 = vegaUtil.inherits(Pack, HierarchyLayout);

  prototype$2.layout = d3Hierarchy.pack;

  prototype$2.params = ['size', 'padding'];

  prototype$2.fields = Output;

  var Output$1 = ['x0', 'y0', 'x1', 'y1', 'depth', 'children'];

  /**
   * Partition tree layout.
   * @constructor
   * @param {object} params - The parameters for this operator.
   * @param {function(object): *} params.field - The value field to size nodes.
   */
  function Partition(params) {
    HierarchyLayout.call(this, params);
  }

  Partition.Definition = {
    "type": "Partition",
    "metadata": {"tree": true, "modifies": true},
    "params": [
      { "name": "field", "type": "field" },
      { "name": "sort", "type": "compare" },
      { "name": "padding", "type": "number", "default": 0 },
      { "name": "round", "type": "boolean", "default": false },
      { "name": "size", "type": "number", "array": true, "length": 2 },
      { "name": "as", "type": "string", "array": true, "length": Output$1.length, "default": Output$1 }
    ]
  };

  var prototype$3 = vegaUtil.inherits(Partition, HierarchyLayout);

  prototype$3.layout = d3Hierarchy.partition;

  prototype$3.params = ['size', 'round', 'padding'];

  prototype$3.fields = Output$1;

  /**
    * Stratify a collection of tuples into a tree structure based on
    * id and parent id fields.
    * @constructor
    * @param {object} params - The parameters for this operator.
    * @param {function(object): *} params.key - Unique key field for each tuple.
    * @param {function(object): *} params.parentKey - Field with key for parent tuple.
    */
  function Stratify(params) {
    vegaDataflow.Transform.call(this, null, params);
  }

  Stratify.Definition = {
    "type": "Stratify",
    "metadata": {"treesource": true},
    "params": [
      { "name": "key", "type": "field", "required": true },
      { "name": "parentKey", "type": "field", "required": true  }
    ]
  };

  var prototype$4 = vegaUtil.inherits(Stratify, vegaDataflow.Transform);

  prototype$4.transform = function(_, pulse) {
    if (!pulse.source) {
      vegaUtil.error('Stratify transform requires an upstream data source.');
    }

    var tree = this.value,
        mod = _.modified(),
        out = pulse.fork(pulse.ALL).materialize(pulse.SOURCE),
        run = !this.value
           || mod
           || pulse.changed(pulse.ADD_REM)
           || pulse.modified(_.key.fields)
           || pulse.modified(_.parentKey.fields);

    // prevent upstream source pollution
    out.source = out.source.slice();

    if (run) {
      if (out.source.length) {
        tree = lookup(
          d3Hierarchy.stratify().id(_.key).parentId(_.parentKey)(out.source)
          , _.key, vegaUtil.truthy);
      } else {
        tree = lookup(d3Hierarchy.stratify()([{}]), _.key, _.key);
      }
    }

    out.source.root = this.value = tree;
    return out;
  };

  var Layouts = {
    tidy: d3Hierarchy.tree,
    cluster: d3Hierarchy.cluster
  };

  var Output$2 = ['x', 'y', 'depth', 'children'];

  /**
   * Tree layout. Depending on the method parameter, performs either
   * Reingold-Tilford 'tidy' layout or dendrogram 'cluster' layout.
   * @constructor
   * @param {object} params - The parameters for this operator.
   */
  function Tree(params) {
    HierarchyLayout.call(this, params);
  }

  Tree.Definition = {
    "type": "Tree",
    "metadata": {"tree": true, "modifies": true},
    "params": [
      { "name": "field", "type": "field" },
      { "name": "sort", "type": "compare" },
      { "name": "method", "type": "enum", "default": "tidy", "values": ["tidy", "cluster"] },
      { "name": "size", "type": "number", "array": true, "length": 2 },
      { "name": "nodeSize", "type": "number", "array": true, "length": 2 },
      { "name": "separation", "type": "boolean", "default": true },
      { "name": "as", "type": "string", "array": true, "length": Output$2.length, "default": Output$2 }
    ]
  };

  var prototype$5 = vegaUtil.inherits(Tree, HierarchyLayout);

  /**
   * Tree layout generator. Supports both 'tidy' and 'cluster' layouts.
   */
  prototype$5.layout = function(method) {
    var m = method || 'tidy';
    if (Layouts.hasOwnProperty(m)) return Layouts[m]();
    else vegaUtil.error('Unrecognized Tree layout method: ' + m);
  };

  prototype$5.params = ['size', 'nodeSize'];

  prototype$5.fields = Output$2;

  /**
    * Generate tuples representing links between tree nodes.
    * The resulting tuples will contain 'source' and 'target' fields,
    * which point to parent and child node tuples, respectively.
    * @constructor
    * @param {object} params - The parameters for this operator.
    */
  function TreeLinks(params) {
    vegaDataflow.Transform.call(this, [], params);
  }

  TreeLinks.Definition = {
    "type": "TreeLinks",
    "metadata": {"tree": true, "generates": true, "changes": true},
    "params": []
  };

  var prototype$6 = vegaUtil.inherits(TreeLinks, vegaDataflow.Transform);

  prototype$6.transform = function(_, pulse) {
    var links = this.value,
        tree = pulse.source && pulse.source.root,
        out = pulse.fork(pulse.NO_SOURCE),
        lut = {};

    if (!tree) vegaUtil.error('TreeLinks transform requires a tree data source.');

    if (pulse.changed(pulse.ADD_REM)) {
      // remove previous links
      out.rem = links;

      // build lookup table of valid tuples
      pulse.visit(pulse.SOURCE, function(t) { lut[vegaDataflow.tupleid(t)] = 1; });

      // generate links for all edges incident on valid tuples
      tree.each(function(node) {
        var t = node.data,
            p = node.parent && node.parent.data;
        if (p && lut[vegaDataflow.tupleid(t)] && lut[vegaDataflow.tupleid(p)]) {
          out.add.push(vegaDataflow.ingest({source: p, target: t}));
        }
      });
      this.value = out.add;
    }

    else if (pulse.changed(pulse.MOD)) {
      // build lookup table of modified tuples
      pulse.visit(pulse.MOD, function(t) { lut[vegaDataflow.tupleid(t)] = 1; });

      // gather links incident on modified tuples
      links.forEach(function(link) {
        if (lut[vegaDataflow.tupleid(link.source)] || lut[vegaDataflow.tupleid(link.target)]) {
          out.mod.push(link);
        }
      });
    }

    return out;
  };

  var Tiles = {
    binary: d3Hierarchy.treemapBinary,
    dice: d3Hierarchy.treemapDice,
    slice: d3Hierarchy.treemapSlice,
    slicedice: d3Hierarchy.treemapSliceDice,
    squarify: d3Hierarchy.treemapSquarify,
    resquarify: d3Hierarchy.treemapResquarify
  };

  var Output$3 = ['x0', 'y0', 'x1', 'y1', 'depth', 'children'];

  /**
   * Treemap layout.
   * @constructor
   * @param {object} params - The parameters for this operator.
   * @param {function(object): *} params.field - The value field to size nodes.
   */
  function Treemap(params) {
    HierarchyLayout.call(this, params);
  }

  Treemap.Definition = {
    "type": "Treemap",
    "metadata": {"tree": true, "modifies": true},
    "params": [
      { "name": "field", "type": "field" },
      { "name": "sort", "type": "compare" },
      { "name": "method", "type": "enum", "default": "squarify",
        "values": ["squarify", "resquarify", "binary", "dice", "slice", "slicedice"] },
      { "name": "padding", "type": "number", "default": 0 },
      { "name": "paddingInner", "type": "number", "default": 0 },
      { "name": "paddingOuter", "type": "number", "default": 0 },
      { "name": "paddingTop", "type": "number", "default": 0 },
      { "name": "paddingRight", "type": "number", "default": 0 },
      { "name": "paddingBottom", "type": "number", "default": 0 },
      { "name": "paddingLeft", "type": "number", "default": 0 },
      { "name": "ratio", "type": "number", "default": 1.618033988749895 },
      { "name": "round", "type": "boolean", "default": false },
      { "name": "size", "type": "number", "array": true, "length": 2 },
      { "name": "as", "type": "string", "array": true, "length": Output$3.length, "default": Output$3 }
    ]
  };

  var prototype$7 = vegaUtil.inherits(Treemap, HierarchyLayout);

  /**
   * Treemap layout generator. Adds 'method' and 'ratio' parameters
   * to configure the underlying tile method.
   */
  prototype$7.layout = function() {
    var x = d3Hierarchy.treemap();
    x.ratio = function(_) {
      var t = x.tile();
      if (t.ratio) x.tile(t.ratio(_));
    };
    x.method = function(_) {
      if (Tiles.hasOwnProperty(_)) x.tile(Tiles[_]);
      else vegaUtil.error('Unrecognized Treemap layout method: ' + _);
    };
    return x;
  };

  prototype$7.params = [
    'method', 'ratio', 'size', 'round',
    'padding', 'paddingInner', 'paddingOuter',
    'paddingTop', 'paddingRight', 'paddingBottom', 'paddingLeft'
  ];

  prototype$7.fields = Output$3;

  exports.nest = Nest;
  exports.pack = Pack;
  exports.partition = Partition;
  exports.stratify = Stratify;
  exports.tree = Tree;
  exports.treelinks = TreeLinks;
  exports.treemap = Treemap;

  Object.defineProperty(exports, '__esModule', { value: true });

})));
