diff src/DML/VendorAssetsBundle/Resources/assets/vega/2.2.6/vega.js @ 0:493bcb69166c

added public content
author Daniel Wolff
date Tue, 09 Feb 2016 20:54:02 +0100
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/DML/VendorAssetsBundle/Resources/assets/vega/2.2.6/vega.js	Tue Feb 09 20:54:02 2016 +0100
@@ -0,0 +1,19509 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.vg = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+module.exports = {
+  version: '2.2.6',
+  dataflow: require('vega-dataflow'),
+  parse: require('./src/parse/'),
+  scene: {
+    Bounder: require('./src/scene/Bounder'),
+    Builder: require('./src/scene/Builder'),
+    Encoder: require('./src/scene/Encoder'),
+    GroupBuilder: require('./src/scene/GroupBuilder'),
+  },
+  transforms: require('./src/transforms'),
+  schema: require('./src/core/schema'),
+  config: require('./src/core/config'),
+  util:  require('datalib'),
+  debug: require('vega-logging').debug
+};
+},{"./src/core/config":88,"./src/core/schema":89,"./src/parse/":95,"./src/scene/Bounder":107,"./src/scene/Builder":108,"./src/scene/Encoder":109,"./src/scene/GroupBuilder":110,"./src/transforms":139,"datalib":24,"vega-dataflow":39,"vega-logging":45}],2:[function(require,module,exports){
+
+},{}],3:[function(require,module,exports){
+(function (global, factory) {
+  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+  typeof define === 'function' && define.amd ? define(['exports'], factory) :
+  factory((global.dsv = {}));
+}(this, function (exports) { 'use strict';
+
+  var dsv = function(delimiter) {
+    var reFormat = new RegExp("[\"" + delimiter + "\n]"),
+        delimiterCode = delimiter.charCodeAt(0);
+
+    function parse(text, f) {
+      var o;
+      return parseRows(text, function(row, i) {
+        if (o) return o(row, i - 1);
+        var a = new Function("d", "return {" + row.map(function(name, i) {
+          return JSON.stringify(name) + ": d[" + i + "]";
+        }).join(",") + "}");
+        o = f ? function(row, i) { return f(a(row), i); } : a;
+      });
+    }
+
+    function parseRows(text, f) {
+      var EOL = {}, // sentinel value for end-of-line
+          EOF = {}, // sentinel value for end-of-file
+          rows = [], // output rows
+          N = text.length,
+          I = 0, // current character index
+          n = 0, // the current line number
+          t, // the current token
+          eol; // is the current token followed by EOL?
+
+      function token() {
+        if (I >= N) return EOF; // special case: end of file
+        if (eol) return eol = false, EOL; // special case: end of line
+
+        // special case: quotes
+        var j = I;
+        if (text.charCodeAt(j) === 34) {
+          var i = j;
+          while (i++ < N) {
+            if (text.charCodeAt(i) === 34) {
+              if (text.charCodeAt(i + 1) !== 34) break;
+              ++i;
+            }
+          }
+          I = i + 2;
+          var c = text.charCodeAt(i + 1);
+          if (c === 13) {
+            eol = true;
+            if (text.charCodeAt(i + 2) === 10) ++I;
+          } else if (c === 10) {
+            eol = true;
+          }
+          return text.slice(j + 1, i).replace(/""/g, "\"");
+        }
+
+        // common case: find next delimiter or newline
+        while (I < N) {
+          var c = text.charCodeAt(I++), k = 1;
+          if (c === 10) eol = true; // \n
+          else if (c === 13) { eol = true; if (text.charCodeAt(I) === 10) ++I, ++k; } // \r|\r\n
+          else if (c !== delimiterCode) continue;
+          return text.slice(j, I - k);
+        }
+
+        // special case: last token before EOF
+        return text.slice(j);
+      }
+
+      while ((t = token()) !== EOF) {
+        var a = [];
+        while (t !== EOL && t !== EOF) {
+          a.push(t);
+          t = token();
+        }
+        if (f && (a = f(a, n++)) == null) continue;
+        rows.push(a);
+      }
+
+      return rows;
+    }
+
+    function format(rows) {
+      if (Array.isArray(rows[0])) return formatRows(rows); // deprecated; use formatRows
+      var fieldSet = Object.create(null), fields = [];
+
+      // Compute unique fields in order of discovery.
+      rows.forEach(function(row) {
+        for (var field in row) {
+          if (!((field += "") in fieldSet)) {
+            fields.push(fieldSet[field] = field);
+          }
+        }
+      });
+
+      return [fields.map(formatValue).join(delimiter)].concat(rows.map(function(row) {
+        return fields.map(function(field) {
+          return formatValue(row[field]);
+        }).join(delimiter);
+      })).join("\n");
+    }
+
+    function formatRows(rows) {
+      return rows.map(formatRow).join("\n");
+    }
+
+    function formatRow(row) {
+      return row.map(formatValue).join(delimiter);
+    }
+
+    function formatValue(text) {
+      return reFormat.test(text) ? "\"" + text.replace(/\"/g, "\"\"") + "\"" : text;
+    }
+
+    return {
+      parse: parse,
+      parseRows: parseRows,
+      format: format,
+      formatRows: formatRows
+    };
+  }
+
+  exports.csv = dsv(",");
+  exports.tsv = dsv("\t");
+
+  exports.dsv = dsv;
+
+}));
+},{}],4:[function(require,module,exports){
+if (typeof Map === "undefined") {
+  Map = function() { this.clear(); };
+  Map.prototype = {
+    set: function(k, v) { this._[k] = v; return this; },
+    get: function(k) { return this._[k]; },
+    has: function(k) { return k in this._; },
+    delete: function(k) { return k in this._ && delete this._[k]; },
+    clear: function() { this._ = Object.create(null); },
+    get size() { var n = 0; for (var k in this._) ++n; return n; },
+    forEach: function(c) { for (var k in this._) c(this._[k], k, this); }
+  };
+} else (function() {
+  var m = new Map;
+  if (m.set(0, 0) !== m) {
+    m = m.set;
+    Map.prototype.set = function() { m.apply(this, arguments); return this; };
+  }
+})();
+
+(function (global, factory) {
+  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+  typeof define === 'function' && define.amd ? define(['exports'], factory) :
+  factory((global.format = {}));
+}(this, function (exports) { 'use strict';
+
+  var zhCn = {
+    decimal: ".",
+    thousands: ",",
+    grouping: [3],
+    currency: ["¥", ""]
+  };
+
+  var ruRu = {
+    decimal: ",",
+    thousands: "\xa0",
+    grouping: [3],
+    currency: ["", "\xa0руб."]
+  };
+
+  var ptBr = {
+    decimal: ",",
+    thousands: ".",
+    grouping: [3],
+    currency: ["R$", ""]
+  };
+
+  var plPl = {
+    decimal: ",",
+    thousands: ".",
+    grouping: [3],
+    currency: ["", "zł"]
+  };
+
+  var nlNl = {
+    decimal: ",",
+    thousands: ".",
+    grouping: [3],
+    currency: ["€\xa0", ""]
+  };
+
+  var mkMk = {
+    decimal: ",",
+    thousands: ".",
+    grouping: [3],
+    currency: ["", "\xa0ден."]
+  };
+
+  var jaJp = {
+    decimal: ".",
+    thousands: ",",
+    grouping: [3],
+    currency: ["", "円"]
+  };
+
+  var itIt = {
+    decimal: ",",
+    thousands: ".",
+    grouping: [3],
+    currency: ["€", ""]
+  };
+
+  var heIl = {
+    decimal: ".",
+    thousands: ",",
+    grouping: [3],
+    currency: ["₪", ""]
+  };
+
+  var frFr = {
+    decimal: ",",
+    thousands: ".",
+    grouping: [3],
+    currency: ["", "\xa0€"]
+  };
+
+  var frCa = {
+    decimal: ",",
+    thousands: "\xa0",
+    grouping: [3],
+    currency: ["", "$"]
+  };
+
+  var fiFi = {
+    decimal: ",",
+    thousands: "\xa0",
+    grouping: [3],
+    currency: ["", "\xa0€"]
+  };
+
+  var esEs = {
+    decimal: ",",
+    thousands: ".",
+    grouping: [3],
+    currency: ["", "\xa0€"]
+  };
+
+  var enUs = {
+    decimal: ".",
+    thousands: ",",
+    grouping: [3],
+    currency: ["$", ""]
+  };
+
+  var enGb = {
+    decimal: ".",
+    thousands: ",",
+    grouping: [3],
+    currency: ["£", ""]
+  };
+
+  var enCa = {
+    decimal: ".",
+    thousands: ",",
+    grouping: [3],
+    currency: ["$", ""]
+  };
+
+  var deDe = {
+    decimal: ",",
+    thousands: ".",
+    grouping: [3],
+    currency: ["", "\xa0€"]
+  };
+
+  var caEs = {
+    decimal: ",",
+    thousands: ".",
+    grouping: [3],
+    currency: ["", "\xa0€"]
+  };
+
+
+  // Computes the decimal coefficient and exponent of the specified number x with
+  // significant digits p, where x is positive and p is in [1, 21] or undefined.
+  // For example, formatDecimal(1.23) returns ["123", 0].
+  function formatDecimal(x, p) {
+    if ((i = (x = p ? x.toExponential(p - 1) : x.toExponential()).indexOf("e")) < 0) return null; // NaN, ±Infinity
+    var i, coefficient = x.slice(0, i);
+
+    // The string returned by toExponential either has the form \d\.\d+e[-+]\d+
+    // (e.g., 1.2e+3) or the form \de[-+]\d+ (e.g., 1e+3).
+    return [
+      coefficient.length > 1 ? coefficient[0] + coefficient.slice(2) : coefficient,
+      +x.slice(i + 1)
+    ];
+  }
+
+  function exponent(x) {
+    return x = formatDecimal(Math.abs(x)), x ? x[1] : NaN;
+  }
+
+  var prefixExponent;
+
+  function formatPrefixAuto(x, p) {
+    var d = formatDecimal(x, p);
+    if (!d) return x + "";
+    var coefficient = d[0],
+        exponent = d[1],
+        i = exponent - (prefixExponent = Math.max(-8, Math.min(8, Math.floor(exponent / 3))) * 3) + 1,
+        n = coefficient.length;
+    return i === n ? coefficient
+        : i > n ? coefficient + new Array(i - n + 1).join("0")
+        : i > 0 ? coefficient.slice(0, i) + "." + coefficient.slice(i)
+        : "0." + new Array(1 - i).join("0") + formatDecimal(x, p + i - 1)[0]; // less than 1y!
+  }
+
+  function formatRounded(x, p) {
+    var d = formatDecimal(x, p);
+    if (!d) return x + "";
+    var coefficient = d[0],
+        exponent = d[1];
+    return exponent < 0 ? "0." + new Array(-exponent).join("0") + coefficient
+        : coefficient.length > exponent + 1 ? coefficient.slice(0, exponent + 1) + "." + coefficient.slice(exponent + 1)
+        : coefficient + new Array(exponent - coefficient.length + 2).join("0");
+  }
+
+  function formatDefault(x, p) {
+    x = x.toPrecision(p);
+
+    out: for (var n = x.length, i = 1, i0 = -1, i1; i < n; ++i) {
+      switch (x[i]) {
+        case ".": i0 = i1 = i; break;
+        case "0": if (i0 === 0) i0 = i; i1 = i; break;
+        case "e": break out;
+        default: if (i0 > 0) i0 = 0; break;
+      }
+    }
+
+    return i0 > 0 ? x.slice(0, i0) + x.slice(i1 + 1) : x;
+  }
+
+  var formatTypes = {
+    "": formatDefault,
+    "%": function(x, p) { return (x * 100).toFixed(p); },
+    "b": function(x) { return Math.round(x).toString(2); },
+    "c": function(x) { return x + ""; },
+    "d": function(x) { return Math.round(x).toString(10); },
+    "e": function(x, p) { return x.toExponential(p); },
+    "f": function(x, p) { return x.toFixed(p); },
+    "g": function(x, p) { return x.toPrecision(p); },
+    "o": function(x) { return Math.round(x).toString(8); },
+    "p": function(x, p) { return formatRounded(x * 100, p); },
+    "r": formatRounded,
+    "s": formatPrefixAuto,
+    "X": function(x) { return Math.round(x).toString(16).toUpperCase(); },
+    "x": function(x) { return Math.round(x).toString(16); }
+  };
+
+
+  // [[fill]align][sign][symbol][0][width][,][.precision][type]
+  var re = /^(?:(.)?([<>=^]))?([+\-\( ])?([$#])?(0)?(\d+)?(,)?(\.\d+)?([a-z%])?$/i;
+
+  function formatSpecifier(specifier) {
+    return new FormatSpecifier(specifier);
+  }
+
+  function FormatSpecifier(specifier) {
+    if (!(match = re.exec(specifier))) throw new Error("invalid format: " + specifier);
+
+    var match,
+        fill = match[1] || " ",
+        align = match[2] || ">",
+        sign = match[3] || "-",
+        symbol = match[4] || "",
+        zero = !!match[5],
+        width = match[6] && +match[6],
+        comma = !!match[7],
+        precision = match[8] && +match[8].slice(1),
+        type = match[9] || "";
+
+    // The "n" type is an alias for ",g".
+    if (type === "n") comma = true, type = "g";
+
+    // Map invalid types to the default format.
+    else if (!formatTypes[type]) type = "";
+
+    // If zero fill is specified, padding goes after sign and before digits.
+    if (zero || (fill === "0" && align === "=")) zero = true, fill = "0", align = "=";
+
+    this.fill = fill;
+    this.align = align;
+    this.sign = sign;
+    this.symbol = symbol;
+    this.zero = zero;
+    this.width = width;
+    this.comma = comma;
+    this.precision = precision;
+    this.type = type;
+  }
+
+  FormatSpecifier.prototype.toString = function() {
+    return this.fill
+        + this.align
+        + this.sign
+        + this.symbol
+        + (this.zero ? "0" : "")
+        + (this.width == null ? "" : Math.max(1, this.width | 0))
+        + (this.comma ? "," : "")
+        + (this.precision == null ? "" : "." + Math.max(0, this.precision | 0))
+        + this.type;
+  };
+
+  function formatGroup(grouping, thousands) {
+    return function(value, width) {
+      var i = value.length,
+          t = [],
+          j = 0,
+          g = grouping[0],
+          length = 0;
+
+      while (i > 0 && g > 0) {
+        if (length + g + 1 > width) g = Math.max(1, width - length);
+        t.push(value.substring(i -= g, i + g));
+        if ((length += g + 1) > width) break;
+        g = grouping[j = (j + 1) % grouping.length];
+      }
+
+      return t.reverse().join(thousands);
+    };
+  }
+
+  var prefixes = ["y","z","a","f","p","n","µ","m","","k","M","G","T","P","E","Z","Y"];
+
+  function identity(x) {
+    return x;
+  }
+
+  function locale(locale) {
+    var group = locale.grouping && locale.thousands ? formatGroup(locale.grouping, locale.thousands) : identity,
+        currency = locale.currency,
+        decimal = locale.decimal;
+
+    function format(specifier) {
+      specifier = formatSpecifier(specifier);
+
+      var fill = specifier.fill,
+          align = specifier.align,
+          sign = specifier.sign,
+          symbol = specifier.symbol,
+          zero = specifier.zero,
+          width = specifier.width,
+          comma = specifier.comma,
+          precision = specifier.precision,
+          type = specifier.type;
+
+      // Compute the prefix and suffix.
+      // For SI-prefix, the suffix is lazily computed.
+      var prefix = symbol === "$" ? currency[0] : symbol === "#" && /[boxX]/.test(type) ? "0" + type.toLowerCase() : "",
+          suffix = symbol === "$" ? currency[1] : /[%p]/.test(type) ? "%" : "";
+
+      // What format function should we use?
+      // Is this an integer type?
+      // Can this type generate exponential notation?
+      var formatType = formatTypes[type],
+          maybeSuffix = !type || /[defgprs%]/.test(type);
+
+      // Set the default precision if not specified,
+      // or clamp the specified precision to the supported range.
+      // For significant precision, it must be in [1, 21].
+      // For fixed precision, it must be in [0, 20].
+      precision = precision == null ? (type ? 6 : 12)
+          : /[gprs]/.test(type) ? Math.max(1, Math.min(21, precision))
+          : Math.max(0, Math.min(20, precision));
+
+      return function(value) {
+        var valuePrefix = prefix,
+            valueSuffix = suffix;
+
+        if (type === "c") {
+          valueSuffix = formatType(value) + valueSuffix;
+          value = "";
+        } else {
+          value = +value;
+
+          // Convert negative to positive, and compute the prefix.
+          // Note that -0 is not less than 0, but 1 / -0 is!
+          var valueNegative = (value < 0 || 1 / value < 0) && (value *= -1, true);
+
+          // Perform the initial formatting.
+          value = formatType(value, precision);
+
+          // Compute the prefix and suffix.
+          valuePrefix = (valueNegative ? (sign === "(" ? sign : "-") : sign === "-" || sign === "(" ? "" : sign) + valuePrefix;
+          valueSuffix = valueSuffix + (type === "s" ? prefixes[8 + prefixExponent / 3] : "") + (valueNegative && sign === "(" ? ")" : "");
+
+          // Break the formatted value into the integer “value” part that can be
+          // grouped, and fractional or exponential “suffix” part that is not.
+          if (maybeSuffix) {
+            var i = -1, n = value.length, c;
+            while (++i < n) {
+              if (c = value.charCodeAt(i), 48 > c || c > 57) {
+                valueSuffix = (c === 46 ? decimal + value.slice(i + 1) : value.slice(i)) + valueSuffix;
+                value = value.slice(0, i);
+                break;
+              }
+            }
+          }
+        }
+
+        // If the fill character is not "0", grouping is applied before padding.
+        if (comma && !zero) value = group(value, Infinity);
+
+        // Compute the padding.
+        var length = valuePrefix.length + value.length + valueSuffix.length,
+            padding = length < width ? new Array(width - length + 1).join(fill) : "";
+
+        // If the fill character is "0", grouping is applied after padding.
+        if (comma && zero) value = group(padding + value, padding.length ? width - valueSuffix.length : Infinity), padding = "";
+
+        // Reconstruct the final output based on the desired alignment.
+        switch (align) {
+          case "<": return valuePrefix + value + valueSuffix + padding;
+          case "=": return valuePrefix + padding + value + valueSuffix;
+          case "^": return padding.slice(0, length = padding.length >> 1) + valuePrefix + value + valueSuffix + padding.slice(length);
+        }
+        return padding + valuePrefix + value + valueSuffix;
+      };
+    }
+
+    function formatPrefix(specifier, value) {
+      var f = format((specifier = formatSpecifier(specifier), specifier.type = "f", specifier)),
+          e = Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3,
+          k = Math.pow(10, -e),
+          prefix = prefixes[8 + e / 3];
+      return function(value) {
+        return f(k * value) + prefix;
+      };
+    }
+
+    return {
+      format: format,
+      formatPrefix: formatPrefix
+    };
+  }
+
+  function precisionRound(step, max) {
+    return Math.max(0, exponent(Math.abs(max)) - exponent(Math.abs(step))) + 1;
+  }
+
+  function precisionPrefix(step, value) {
+    return Math.max(0, Math.max(-8, Math.min(8, Math.floor(exponent(value) / 3))) * 3 - exponent(Math.abs(step)));
+  }
+
+  function precisionFixed(step) {
+    return Math.max(0, -exponent(Math.abs(step)));
+  }
+
+  var localeDefinitions = (new Map)
+      .set("ca-ES", caEs)
+      .set("de-DE", deDe)
+      .set("en-CA", enCa)
+      .set("en-GB", enGb)
+      .set("en-US", enUs)
+      .set("es-ES", esEs)
+      .set("fi-FI", fiFi)
+      .set("fr-CA", frCa)
+      .set("fr-FR", frFr)
+      .set("he-IL", heIl)
+      .set("it-IT", itIt)
+      .set("ja-JP", jaJp)
+      .set("mk-MK", mkMk)
+      .set("nl-NL", nlNl)
+      .set("pl-PL", plPl)
+      .set("pt-BR", ptBr)
+      .set("ru-RU", ruRu)
+      .set("zh-CN", zhCn);
+
+  var defaultLocale = locale(enUs);
+  exports.format = defaultLocale.format;
+  exports.formatPrefix = defaultLocale.formatPrefix;
+
+  function localeFormat(definition) {
+    if (typeof definition === "string") {
+      definition = localeDefinitions.get(definition);
+      if (!definition) return null;
+    }
+    return locale(definition);
+  }
+  ;
+
+  exports.localeFormat = localeFormat;
+  exports.formatSpecifier = formatSpecifier;
+  exports.precisionFixed = precisionFixed;
+  exports.precisionPrefix = precisionPrefix;
+  exports.precisionRound = precisionRound;
+
+}));
+},{}],5:[function(require,module,exports){
+if (typeof Map === "undefined") {
+  Map = function() { this.clear(); };
+  Map.prototype = {
+    set: function(k, v) { this._[k] = v; return this; },
+    get: function(k) { return this._[k]; },
+    has: function(k) { return k in this._; },
+    delete: function(k) { return k in this._ && delete this._[k]; },
+    clear: function() { this._ = Object.create(null); },
+    get size() { var n = 0; for (var k in this._) ++n; return n; },
+    forEach: function(c) { for (var k in this._) c(this._[k], k, this); }
+  };
+} else (function() {
+  var m = new Map;
+  if (m.set(0, 0) !== m) {
+    m = m.set;
+    Map.prototype.set = function() { m.apply(this, arguments); return this; };
+  }
+})();
+
+(function (global, factory) {
+  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+  typeof define === 'function' && define.amd ? define(['exports'], factory) :
+  factory((global.timeFormat = {}));
+}(this, function (exports) { 'use strict';
+
+  var zhCn = {
+    dateTime: "%a %b %e %X %Y",
+    date: "%Y/%-m/%-d",
+    time: "%H:%M:%S",
+    periods: ["上午", "下午"],
+    days: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"],
+    shortDays: ["星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六"],
+    months: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"],
+    shortMonths: ["一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"]
+  };
+
+  var ruRu = {
+    dateTime: "%A, %e %B %Y г. %X",
+    date: "%d.%m.%Y",
+    time: "%H:%M:%S",
+    periods: ["AM", "PM"],
+    days: ["воскресенье", "понедельник", "вторник", "среда", "четверг", "пятница", "суббота"],
+    shortDays: ["вс", "пн", "вт", "ср", "чт", "пт", "сб"],
+    months: ["января", "февраля", "марта", "апреля", "мая", "июня", "июля", "августа", "сентября", "октября", "ноября", "декабря"],
+    shortMonths: ["янв", "фев", "мар", "апр", "май", "июн", "июл", "авг", "сен", "окт", "ноя", "дек"]
+  };
+
+  var ptBr = {
+    dateTime: "%A, %e de %B de %Y. %X",
+    date: "%d/%m/%Y",
+    time: "%H:%M:%S",
+    periods: ["AM", "PM"],
+    days: ["Domingo", "Segunda", "Terça", "Quarta", "Quinta", "Sexta", "Sábado"],
+    shortDays: ["Dom", "Seg", "Ter", "Qua", "Qui", "Sex", "Sáb"],
+    months: ["Janeiro", "Fevereiro", "Março", "Abril", "Maio", "Junho", "Julho", "Agosto", "Setembro", "Outubro", "Novembro", "Dezembro"],
+    shortMonths: ["Jan", "Fev", "Mar", "Abr", "Mai", "Jun", "Jul", "Ago", "Set", "Out", "Nov", "Dez"]
+  };
+
+  var plPl = {
+    dateTime: "%A, %e %B %Y, %X",
+    date: "%d/%m/%Y",
+    time: "%H:%M:%S",
+    periods: ["AM", "PM"], // unused
+    days: ["Niedziela", "Poniedziałek", "Wtorek", "Środa", "Czwartek", "Piątek", "Sobota"],
+    shortDays: ["Niedz.", "Pon.", "Wt.", "Śr.", "Czw.", "Pt.", "Sob."],
+    months: ["Styczeń", "Luty", "Marzec", "Kwiecień", "Maj", "Czerwiec", "Lipiec", "Sierpień", "Wrzesień", "Październik", "Listopad", "Grudzień"],
+    shortMonths: ["Stycz.", "Luty", "Marz.", "Kwie.", "Maj", "Czerw.", "Lipc.", "Sierp.", "Wrz.", "Paźdz.", "Listop.", "Grudz."]/* In Polish language abbraviated months are not commonly used so there is a dispute about the proper abbraviations. */
+  };
+
+  var nlNl = {
+    dateTime: "%a %e %B %Y %T",
+    date: "%d-%m-%Y",
+    time: "%H:%M:%S",
+    periods: ["AM", "PM"], // unused
+    days: ["zondag", "maandag", "dinsdag", "woensdag", "donderdag", "vrijdag", "zaterdag"],
+    shortDays: ["zo", "ma", "di", "wo", "do", "vr", "za"],
+    months: ["januari", "februari", "maart", "april", "mei", "juni", "juli", "augustus", "september", "oktober", "november", "december"],
+    shortMonths: ["jan", "feb", "mrt", "apr", "mei", "jun", "jul", "aug", "sep", "okt", "nov", "dec"]
+  };
+
+  var mkMk = {
+    dateTime: "%A, %e %B %Y г. %X",
+    date: "%d.%m.%Y",
+    time: "%H:%M:%S",
+    periods: ["AM", "PM"],
+    days: ["недела", "понеделник", "вторник", "среда", "четврток", "петок", "сабота"],
+    shortDays: ["нед", "пон", "вто", "сре", "чет", "пет", "саб"],
+    months: ["јануари", "февруари", "март", "април", "мај", "јуни", "јули", "август", "септември", "октомври", "ноември", "декември"],
+    shortMonths: ["јан", "фев", "мар", "апр", "мај", "јун", "јул", "авг", "сеп", "окт", "ное", "дек"]
+  };
+
+  var jaJp = {
+    dateTime: "%Y %b %e %a %X",
+    date: "%Y/%m/%d",
+    time: "%H:%M:%S",
+    periods: ["AM", "PM"],
+    days: ["日曜日", "月曜日", "火曜日", "水曜日", "木曜日", "金曜日", "土曜日"],
+    shortDays: ["日", "月", "火", "水", "木", "金", "土"],
+    months: ["睦月", "如月", "弥生", "卯月", "皐月", "水無月", "文月", "葉月", "長月", "神無月", "霜月", "師走"],
+    shortMonths: ["1月", "2月", "3月", "4月", "5月", "6月", "7月", "8月", "9月", "10月", "11月", "12月"]
+  };
+
+  var itIt = {
+    dateTime: "%A %e %B %Y, %X",
+    date: "%d/%m/%Y",
+    time: "%H:%M:%S",
+    periods: ["AM", "PM"], // unused
+    days: ["Domenica", "Lunedì", "Martedì", "Mercoledì", "Giovedì", "Venerdì", "Sabato"],
+    shortDays: ["Dom", "Lun", "Mar", "Mer", "Gio", "Ven", "Sab"],
+    months: ["Gennaio", "Febbraio", "Marzo", "Aprile", "Maggio", "Giugno", "Luglio", "Agosto", "Settembre", "Ottobre", "Novembre", "Dicembre"],
+    shortMonths: ["Gen", "Feb", "Mar", "Apr", "Mag", "Giu", "Lug", "Ago", "Set", "Ott", "Nov", "Dic"]
+  };
+
+  var heIl = {
+    dateTime: "%A, %e ב%B %Y %X",
+    date: "%d.%m.%Y",
+    time: "%H:%M:%S",
+    periods: ["AM", "PM"],
+    days: ["ראשון", "שני", "שלישי", "רביעי", "חמישי", "שישי", "שבת"],
+    shortDays: ["א׳", "ב׳", "ג׳", "ד׳", "ה׳", "ו׳", "ש׳"],
+    months: ["ינואר", "פברואר", "מרץ", "אפריל", "מאי", "יוני", "יולי", "אוגוסט", "ספטמבר", "אוקטובר", "נובמבר", "דצמבר"],
+    shortMonths: ["ינו׳", "פבר׳", "מרץ", "אפר׳", "מאי", "יוני", "יולי", "אוג׳", "ספט׳", "אוק׳", "נוב׳", "דצמ׳"]
+  };
+
+  var frFr = {
+    dateTime: "%A, le %e %B %Y, %X",
+    date: "%d/%m/%Y",
+    time: "%H:%M:%S",
+    periods: ["AM", "PM"], // unused
+    days: ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"],
+    shortDays: ["dim.", "lun.", "mar.", "mer.", "jeu.", "ven.", "sam."],
+    months: ["janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"],
+    shortMonths: ["janv.", "févr.", "mars", "avr.", "mai", "juin", "juil.", "août", "sept.", "oct.", "nov.", "déc."]
+  };
+
+  var frCa = {
+    dateTime: "%a %e %b %Y %X",
+    date: "%Y-%m-%d",
+    time: "%H:%M:%S",
+    periods: ["", ""],
+    days: ["dimanche", "lundi", "mardi", "mercredi", "jeudi", "vendredi", "samedi"],
+    shortDays: ["dim", "lun", "mar", "mer", "jeu", "ven", "sam"],
+    months: ["janvier", "février", "mars", "avril", "mai", "juin", "juillet", "août", "septembre", "octobre", "novembre", "décembre"],
+    shortMonths: ["jan", "fév", "mar", "avr", "mai", "jui", "jul", "aoû", "sep", "oct", "nov", "déc"]
+  };
+
+  var fiFi = {
+    dateTime: "%A, %-d. %Bta %Y klo %X",
+    date: "%-d.%-m.%Y",
+    time: "%H:%M:%S",
+    periods: ["a.m.", "p.m."],
+    days: ["sunnuntai", "maanantai", "tiistai", "keskiviikko", "torstai", "perjantai", "lauantai"],
+    shortDays: ["Su", "Ma", "Ti", "Ke", "To", "Pe", "La"],
+    months: ["tammikuu", "helmikuu", "maaliskuu", "huhtikuu", "toukokuu", "kesäkuu", "heinäkuu", "elokuu", "syyskuu", "lokakuu", "marraskuu", "joulukuu"],
+    shortMonths: ["Tammi", "Helmi", "Maalis", "Huhti", "Touko", "Kesä", "Heinä", "Elo", "Syys", "Loka", "Marras", "Joulu"]
+  };
+
+  var esEs = {
+    dateTime: "%A, %e de %B de %Y, %X",
+    date: "%d/%m/%Y",
+    time: "%H:%M:%S",
+    periods: ["AM", "PM"],
+    days: ["domingo", "lunes", "martes", "miércoles", "jueves", "viernes", "sábado"],
+    shortDays: ["dom", "lun", "mar", "mié", "jue", "vie", "sáb"],
+    months: ["enero", "febrero", "marzo", "abril", "mayo", "junio", "julio", "agosto", "septiembre", "octubre", "noviembre", "diciembre"],
+    shortMonths: ["ene", "feb", "mar", "abr", "may", "jun", "jul", "ago", "sep", "oct", "nov", "dic"]
+  };
+
+  var enUs = {
+    dateTime: "%a %b %e %X %Y",
+    date: "%m/%d/%Y",
+    time: "%H:%M:%S",
+    periods: ["AM", "PM"],
+    days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
+    shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
+    months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
+    shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+  };
+
+  var enGb = {
+    dateTime: "%a %e %b %X %Y",
+    date: "%d/%m/%Y",
+    time: "%H:%M:%S",
+    periods: ["AM", "PM"],
+    days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
+    shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
+    months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
+    shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+  };
+
+  var enCa = {
+    dateTime: "%a %b %e %X %Y",
+    date: "%Y-%m-%d",
+    time: "%H:%M:%S",
+    periods: ["AM", "PM"],
+    days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"],
+    shortDays: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"],
+    months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
+    shortMonths: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
+  };
+
+  var deDe = {
+    dateTime: "%A, der %e. %B %Y, %X",
+    date: "%d.%m.%Y",
+    time: "%H:%M:%S",
+    periods: ["AM", "PM"], // unused
+    days: ["Sonntag", "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag"],
+    shortDays: ["So", "Mo", "Di", "Mi", "Do", "Fr", "Sa"],
+    months: ["Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember"],
+    shortMonths: ["Jan", "Feb", "Mrz", "Apr", "Mai", "Jun", "Jul", "Aug", "Sep", "Okt", "Nov", "Dez"]
+  };
+
+  var caEs = {
+    dateTime: "%A, %e de %B de %Y, %X",
+    date: "%d/%m/%Y",
+    time: "%H:%M:%S",
+    periods: ["AM", "PM"],
+    days: ["diumenge", "dilluns", "dimarts", "dimecres", "dijous", "divendres", "dissabte"],
+    shortDays: ["dg.", "dl.", "dt.", "dc.", "dj.", "dv.", "ds."],
+    months: ["gener", "febrer", "març", "abril", "maig", "juny", "juliol", "agost", "setembre", "octubre", "novembre", "desembre"],
+    shortMonths: ["gen.", "febr.", "març", "abr.", "maig", "juny", "jul.", "ag.", "set.", "oct.", "nov.", "des."]
+  };
+
+  var t0 = new Date;
+  var t1 = new Date;
+
+  function newInterval(floori, offseti, count) {
+
+    function interval(date) {
+      return floori(date = new Date(+date)), date;
+    }
+
+    interval.floor = interval;
+
+    interval.round = function(date) {
+      var d0 = new Date(+date),
+          d1 = new Date(date - 1);
+      floori(d0), floori(d1), offseti(d1, 1);
+      return date - d0 < d1 - date ? d0 : d1;
+    };
+
+    interval.ceil = function(date) {
+      return floori(date = new Date(date - 1)), offseti(date, 1), date;
+    };
+
+    interval.offset = function(date, step) {
+      return offseti(date = new Date(+date), step == null ? 1 : Math.floor(step)), date;
+    };
+
+    interval.range = function(start, stop, step) {
+      var range = [];
+      start = new Date(start - 1);
+      stop = new Date(+stop);
+      step = step == null ? 1 : Math.floor(step);
+      if (!(start < stop) || !(step > 0)) return range; // also handles Invalid Date
+      offseti(start, 1), floori(start);
+      if (start < stop) range.push(new Date(+start));
+      while (offseti(start, step), floori(start), start < stop) range.push(new Date(+start));
+      return range;
+    };
+
+    interval.filter = function(test) {
+      return newInterval(function(date) {
+        while (floori(date), !test(date)) date.setTime(date - 1);
+      }, function(date, step) {
+        while (--step >= 0) while (offseti(date, 1), !test(date));
+      });
+    };
+
+    if (count) interval.count = function(start, end) {
+      t0.setTime(+start), t1.setTime(+end);
+      floori(t0), floori(t1);
+      return Math.floor(count(t0, t1));
+    };
+
+    return interval;
+  }
+
+  var day = newInterval(function(date) {
+    date.setHours(0, 0, 0, 0);
+  }, function(date, step) {
+    date.setDate(date.getDate() + step);
+  }, function(start, end) {
+    return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * 6e4) / 864e5;
+  });
+
+  function weekday(i) {
+    return newInterval(function(date) {
+      date.setHours(0, 0, 0, 0);
+      date.setDate(date.getDate() - (date.getDay() + 7 - i) % 7);
+    }, function(date, step) {
+      date.setDate(date.getDate() + step * 7);
+    }, function(start, end) {
+      return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * 6e4) / 6048e5;
+    });
+  }
+
+  var sunday = weekday(0);
+  var monday = weekday(1);
+
+  var year = newInterval(function(date) {
+    date.setHours(0, 0, 0, 0);
+    date.setMonth(0, 1);
+  }, function(date, step) {
+    date.setFullYear(date.getFullYear() + step);
+  }, function(start, end) {
+    return end.getFullYear() - start.getFullYear();
+  });
+
+  var utcDay = newInterval(function(date) {
+    date.setUTCHours(0, 0, 0, 0);
+  }, function(date, step) {
+    date.setUTCDate(date.getUTCDate() + step);
+  }, function(start, end) {
+    return (end - start) / 864e5;
+  });
+
+  function utcWeekday(i) {
+    return newInterval(function(date) {
+      date.setUTCHours(0, 0, 0, 0);
+      date.setUTCDate(date.getUTCDate() - (date.getUTCDay() + 7 - i) % 7);
+    }, function(date, step) {
+      date.setUTCDate(date.getUTCDate() + step * 7);
+    }, function(start, end) {
+      return (end - start) / 6048e5;
+    });
+  }
+
+  var utcSunday = utcWeekday(0);
+  var utcMonday = utcWeekday(1);
+
+  var utcYear = newInterval(function(date) {
+    date.setUTCHours(0, 0, 0, 0);
+    date.setUTCMonth(0, 1);
+  }, function(date, step) {
+    date.setUTCFullYear(date.getUTCFullYear() + step);
+  }, function(start, end) {
+    return end.getUTCFullYear() - start.getUTCFullYear();
+  });
+
+  function localDate(d) {
+    if (0 <= d.y && d.y < 100) {
+      var date = new Date(-1, d.m, d.d, d.H, d.M, d.S, d.L);
+      date.setFullYear(d.y);
+      return date;
+    }
+    return new Date(d.y, d.m, d.d, d.H, d.M, d.S, d.L);
+  }
+
+  function utcDate(d) {
+    if (0 <= d.y && d.y < 100) {
+      var date = new Date(Date.UTC(-1, d.m, d.d, d.H, d.M, d.S, d.L));
+      date.setUTCFullYear(d.y);
+      return date;
+    }
+    return new Date(Date.UTC(d.y, d.m, d.d, d.H, d.M, d.S, d.L));
+  }
+
+  function newYear(y) {
+    return {y: y, m: 0, d: 1, H: 0, M: 0, S: 0, L: 0};
+  }
+
+  function locale(locale) {
+    var locale_dateTime = locale.dateTime,
+        locale_date = locale.date,
+        locale_time = locale.time,
+        locale_periods = locale.periods,
+        locale_weekdays = locale.days,
+        locale_shortWeekdays = locale.shortDays,
+        locale_months = locale.months,
+        locale_shortMonths = locale.shortMonths;
+
+    var periodLookup = formatLookup(locale_periods),
+        weekdayRe = formatRe(locale_weekdays),
+        weekdayLookup = formatLookup(locale_weekdays),
+        shortWeekdayRe = formatRe(locale_shortWeekdays),
+        shortWeekdayLookup = formatLookup(locale_shortWeekdays),
+        monthRe = formatRe(locale_months),
+        monthLookup = formatLookup(locale_months),
+        shortMonthRe = formatRe(locale_shortMonths),
+        shortMonthLookup = formatLookup(locale_shortMonths);
+
+    var formats = {
+      "a": formatShortWeekday,
+      "A": formatWeekday,
+      "b": formatShortMonth,
+      "B": formatMonth,
+      "c": null,
+      "d": formatDayOfMonth,
+      "e": formatDayOfMonth,
+      "H": formatHour24,
+      "I": formatHour12,
+      "j": formatDayOfYear,
+      "L": formatMilliseconds,
+      "m": formatMonthNumber,
+      "M": formatMinutes,
+      "p": formatPeriod,
+      "S": formatSeconds,
+      "U": formatWeekNumberSunday,
+      "w": formatWeekdayNumber,
+      "W": formatWeekNumberMonday,
+      "x": null,
+      "X": null,
+      "y": formatYear,
+      "Y": formatFullYear,
+      "Z": formatZone,
+      "%": formatLiteralPercent
+    };
+
+    var utcFormats = {
+      "a": formatUTCShortWeekday,
+      "A": formatUTCWeekday,
+      "b": formatUTCShortMonth,
+      "B": formatUTCMonth,
+      "c": null,
+      "d": formatUTCDayOfMonth,
+      "e": formatUTCDayOfMonth,
+      "H": formatUTCHour24,
+      "I": formatUTCHour12,
+      "j": formatUTCDayOfYear,
+      "L": formatUTCMilliseconds,
+      "m": formatUTCMonthNumber,
+      "M": formatUTCMinutes,
+      "p": formatUTCPeriod,
+      "S": formatUTCSeconds,
+      "U": formatUTCWeekNumberSunday,
+      "w": formatUTCWeekdayNumber,
+      "W": formatUTCWeekNumberMonday,
+      "x": null,
+      "X": null,
+      "y": formatUTCYear,
+      "Y": formatUTCFullYear,
+      "Z": formatUTCZone,
+      "%": formatLiteralPercent
+    };
+
+    var parses = {
+      "a": parseShortWeekday,
+      "A": parseWeekday,
+      "b": parseShortMonth,
+      "B": parseMonth,
+      "c": parseLocaleDateTime,
+      "d": parseDayOfMonth,
+      "e": parseDayOfMonth,
+      "H": parseHour24,
+      "I": parseHour24,
+      "j": parseDayOfYear,
+      "L": parseMilliseconds,
+      "m": parseMonthNumber,
+      "M": parseMinutes,
+      "p": parsePeriod,
+      "S": parseSeconds,
+      "U": parseWeekNumberSunday,
+      "w": parseWeekdayNumber,
+      "W": parseWeekNumberMonday,
+      "x": parseLocaleDate,
+      "X": parseLocaleTime,
+      "y": parseYear,
+      "Y": parseFullYear,
+      "Z": parseZone,
+      "%": parseLiteralPercent
+    };
+
+    // These recursive directive definitions must be deferred.
+    formats.x = newFormat(locale_date, formats);
+    formats.X = newFormat(locale_time, formats);
+    formats.c = newFormat(locale_dateTime, formats);
+    utcFormats.x = newFormat(locale_date, utcFormats);
+    utcFormats.X = newFormat(locale_time, utcFormats);
+    utcFormats.c = newFormat(locale_dateTime, utcFormats);
+
+    function newFormat(specifier, formats) {
+      return function(date) {
+        var string = [],
+            i = -1,
+            j = 0,
+            n = specifier.length,
+            c,
+            pad,
+            format;
+
+        while (++i < n) {
+          if (specifier.charCodeAt(i) === 37) {
+            string.push(specifier.slice(j, i));
+            if ((pad = pads[c = specifier.charAt(++i)]) != null) c = specifier.charAt(++i);
+            if (format = formats[c]) c = format(date, pad == null ? (c === "e" ? " " : "0") : pad);
+            string.push(c);
+            j = i + 1;
+          }
+        }
+
+        string.push(specifier.slice(j, i));
+        return string.join("");
+      };
+    }
+
+    function newParse(specifier, newDate) {
+      return function(string) {
+        var d = newYear(1900),
+            i = parseSpecifier(d, specifier, string, 0);
+        if (i != string.length) return null;
+
+        // The am-pm flag is 0 for AM, and 1 for PM.
+        if ("p" in d) d.H = d.H % 12 + d.p * 12;
+
+        // If a time zone is specified, all fields are interpreted as UTC and then
+        // offset according to the specified time zone.
+        if ("Z" in d) {
+          if ("w" in d && ("W" in d || "U" in d)) {
+            var day = utcDate(newYear(d.y)).getUTCDay();
+            if ("W" in d) d.U = d.W, d.w = (d.w + 6) % 7, --day;
+            d.m = 0;
+            d.d = d.w + d.U * 7 - (day + 6) % 7;
+          }
+          d.H += d.Z / 100 | 0;
+          d.M += d.Z % 100;
+          return utcDate(d);
+        }
+
+        // Otherwise, all fields are in local time.
+        if ("w" in d && ("W" in d || "U" in d)) {
+          var day = newDate(newYear(d.y)).getDay();
+          if ("W" in d) d.U = d.W, d.w = (d.w + 6) % 7, --day;
+          d.m = 0;
+          d.d = d.w + d.U * 7 - (day + 6) % 7;
+        }
+        return newDate(d);
+      };
+    }
+
+    function parseSpecifier(d, specifier, string, j) {
+      var i = 0,
+          n = specifier.length,
+          m = string.length,
+          c,
+          parse;
+
+      while (i < n) {
+        if (j >= m) return -1;
+        c = specifier.charCodeAt(i++);
+        if (c === 37) {
+          c = specifier.charAt(i++);
+          parse = parses[c in pads ? specifier.charAt(i++) : c];
+          if (!parse || ((j = parse(d, string, j)) < 0)) return -1;
+        } else if (c != string.charCodeAt(j++)) {
+          return -1;
+        }
+      }
+
+      return j;
+    }
+
+    function parseShortWeekday(d, string, i) {
+      var n = shortWeekdayRe.exec(string.slice(i));
+      return n ? (d.w = shortWeekdayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
+    }
+
+    function parseWeekday(d, string, i) {
+      var n = weekdayRe.exec(string.slice(i));
+      return n ? (d.w = weekdayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
+    }
+
+    function parseShortMonth(d, string, i) {
+      var n = shortMonthRe.exec(string.slice(i));
+      return n ? (d.m = shortMonthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
+    }
+
+    function parseMonth(d, string, i) {
+      var n = monthRe.exec(string.slice(i));
+      return n ? (d.m = monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
+    }
+
+    function parseLocaleDateTime(d, string, i) {
+      return parseSpecifier(d, locale_dateTime, string, i);
+    }
+
+    function parseLocaleDate(d, string, i) {
+      return parseSpecifier(d, locale_date, string, i);
+    }
+
+    function parseLocaleTime(d, string, i) {
+      return parseSpecifier(d, locale_time, string, i);
+    }
+
+    function parsePeriod(d, string, i) {
+      var n = periodLookup.get(string.slice(i, i += 2).toLowerCase());
+      return n == null ? -1 : (d.p = n, i);
+    }
+
+    function formatShortWeekday(d) {
+      return locale_shortWeekdays[d.getDay()];
+    }
+
+    function formatWeekday(d) {
+      return locale_weekdays[d.getDay()];
+    }
+
+    function formatShortMonth(d) {
+      return locale_shortMonths[d.getMonth()];
+    }
+
+    function formatMonth(d) {
+      return locale_months[d.getMonth()];
+    }
+
+    function formatPeriod(d) {
+      return locale_periods[+(d.getHours() >= 12)];
+    }
+
+    function formatUTCShortWeekday(d) {
+      return locale_shortWeekdays[d.getUTCDay()];
+    }
+
+    function formatUTCWeekday(d) {
+      return locale_weekdays[d.getUTCDay()];
+    }
+
+    function formatUTCShortMonth(d) {
+      return locale_shortMonths[d.getUTCMonth()];
+    }
+
+    function formatUTCMonth(d) {
+      return locale_months[d.getUTCMonth()];
+    }
+
+    function formatUTCPeriod(d) {
+      return locale_periods[+(d.getUTCHours() >= 12)];
+    }
+
+    return {
+      format: function(specifier) {
+        var f = newFormat(specifier += "", formats);
+        f.parse = newParse(specifier, localDate);
+        f.toString = function() { return specifier; };
+        return f;
+      },
+      utcFormat: function(specifier) {
+        var f = newFormat(specifier += "", utcFormats);
+        f.parse = newParse(specifier, utcDate);
+        f.toString = function() { return specifier; };
+        return f;
+      }
+    };
+  }
+
+  var pads = {"-": "", "_": " ", "0": "0"};
+  var numberRe = /^\s*\d+/;
+  var percentRe = /^%/;
+  var requoteRe = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
+
+  function pad(value, fill, width) {
+    var sign = value < 0 ? "-" : "",
+        string = (sign ? -value : value) + "",
+        length = string.length;
+    return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string);
+  }
+
+  function requote(s) {
+    return s.replace(requoteRe, "\\$&");
+  }
+
+  function formatRe(names) {
+    return new RegExp("^(?:" + names.map(requote).join("|") + ")", "i");
+  }
+
+  function formatLookup(names) {
+    var map = new Map, i = -1, n = names.length;
+    while (++i < n) map.set(names[i].toLowerCase(), i);
+    return map;
+  }
+
+  function parseWeekdayNumber(d, string, i) {
+    var n = numberRe.exec(string.slice(i, i + 1));
+    return n ? (d.w = +n[0], i + n[0].length) : -1;
+  }
+
+  function parseWeekNumberSunday(d, string, i) {
+    var n = numberRe.exec(string.slice(i));
+    return n ? (d.U = +n[0], i + n[0].length) : -1;
+  }
+
+  function parseWeekNumberMonday(d, string, i) {
+    var n = numberRe.exec(string.slice(i));
+    return n ? (d.W = +n[0], i + n[0].length) : -1;
+  }
+
+  function parseFullYear(d, string, i) {
+    var n = numberRe.exec(string.slice(i, i + 4));
+    return n ? (d.y = +n[0], i + n[0].length) : -1;
+  }
+
+  function parseYear(d, string, i) {
+    var n = numberRe.exec(string.slice(i, i + 2));
+    return n ? (d.y = +n[0] + (+n[0] > 68 ? 1900 : 2000), i + n[0].length) : -1;
+  }
+
+  function parseZone(d, string, i) {
+    return /^[+-]\d{4}$/.test(string = string.slice(i, i + 5))
+        ? (d.Z = -string, i + 5) // sign differs from getTimezoneOffset!
+        : -1;
+  }
+
+  function parseMonthNumber(d, string, i) {
+    var n = numberRe.exec(string.slice(i, i + 2));
+    return n ? (d.m = n[0] - 1, i + n[0].length) : -1;
+  }
+
+  function parseDayOfMonth(d, string, i) {
+    var n = numberRe.exec(string.slice(i, i + 2));
+    return n ? (d.d = +n[0], i + n[0].length) : -1;
+  }
+
+  function parseDayOfYear(d, string, i) {
+    var n = numberRe.exec(string.slice(i, i + 3));
+    return n ? (d.m = 0, d.d = +n[0], i + n[0].length) : -1;
+  }
+
+  function parseHour24(d, string, i) {
+    var n = numberRe.exec(string.slice(i, i + 2));
+    return n ? (d.H = +n[0], i + n[0].length) : -1;
+  }
+
+  function parseMinutes(d, string, i) {
+    var n = numberRe.exec(string.slice(i, i + 2));
+    return n ? (d.M = +n[0], i + n[0].length) : -1;
+  }
+
+  function parseSeconds(d, string, i) {
+    var n = numberRe.exec(string.slice(i, i + 2));
+    return n ? (d.S = +n[0], i + n[0].length) : -1;
+  }
+
+  function parseMilliseconds(d, string, i) {
+    var n = numberRe.exec(string.slice(i, i + 3));
+    return n ? (d.L = +n[0], i + n[0].length) : -1;
+  }
+
+  function parseLiteralPercent(d, string, i) {
+    var n = percentRe.exec(string.slice(i, i + 1));
+    return n ? i + n[0].length : -1;
+  }
+
+  function formatDayOfMonth(d, p) {
+    return pad(d.getDate(), p, 2);
+  }
+
+  function formatHour24(d, p) {
+    return pad(d.getHours(), p, 2);
+  }
+
+  function formatHour12(d, p) {
+    return pad(d.getHours() % 12 || 12, p, 2);
+  }
+
+  function formatDayOfYear(d, p) {
+    return pad(1 + day.count(year(d), d), p, 3);
+  }
+
+  function formatMilliseconds(d, p) {
+    return pad(d.getMilliseconds(), p, 3);
+  }
+
+  function formatMonthNumber(d, p) {
+    return pad(d.getMonth() + 1, p, 2);
+  }
+
+  function formatMinutes(d, p) {
+    return pad(d.getMinutes(), p, 2);
+  }
+
+  function formatSeconds(d, p) {
+    return pad(d.getSeconds(), p, 2);
+  }
+
+  function formatWeekNumberSunday(d, p) {
+    return pad(sunday.count(year(d), d), p, 2);
+  }
+
+  function formatWeekdayNumber(d) {
+    return d.getDay();
+  }
+
+  function formatWeekNumberMonday(d, p) {
+    return pad(monday.count(year(d), d), p, 2);
+  }
+
+  function formatYear(d, p) {
+    return pad(d.getFullYear() % 100, p, 2);
+  }
+
+  function formatFullYear(d, p) {
+    return pad(d.getFullYear() % 10000, p, 4);
+  }
+
+  function formatZone(d) {
+    var z = d.getTimezoneOffset();
+    return (z > 0 ? "-" : (z *= -1, "+"))
+        + pad(z / 60 | 0, "0", 2)
+        + pad(z % 60, "0", 2);
+  }
+
+  function formatUTCDayOfMonth(d, p) {
+    return pad(d.getUTCDate(), p, 2);
+  }
+
+  function formatUTCHour24(d, p) {
+    return pad(d.getUTCHours(), p, 2);
+  }
+
+  function formatUTCHour12(d, p) {
+    return pad(d.getUTCHours() % 12 || 12, p, 2);
+  }
+
+  function formatUTCDayOfYear(d, p) {
+    return pad(1 + utcDay.count(utcYear(d), d), p, 3);
+  }
+
+  function formatUTCMilliseconds(d, p) {
+    return pad(d.getUTCMilliseconds(), p, 3);
+  }
+
+  function formatUTCMonthNumber(d, p) {
+    return pad(d.getUTCMonth() + 1, p, 2);
+  }
+
+  function formatUTCMinutes(d, p) {
+    return pad(d.getUTCMinutes(), p, 2);
+  }
+
+  function formatUTCSeconds(d, p) {
+    return pad(d.getUTCSeconds(), p, 2);
+  }
+
+  function formatUTCWeekNumberSunday(d, p) {
+    return pad(utcSunday.count(utcYear(d), d), p, 2);
+  }
+
+  function formatUTCWeekdayNumber(d) {
+    return d.getUTCDay();
+  }
+
+  function formatUTCWeekNumberMonday(d, p) {
+    return pad(utcMonday.count(utcYear(d), d), p, 2);
+  }
+
+  function formatUTCYear(d, p) {
+    return pad(d.getUTCFullYear() % 100, p, 2);
+  }
+
+  function formatUTCFullYear(d, p) {
+    return pad(d.getUTCFullYear() % 10000, p, 4);
+  }
+
+  function formatUTCZone() {
+    return "+0000";
+  }
+
+  function formatLiteralPercent() {
+    return "%";
+  }
+
+  var isoSpecifier = "%Y-%m-%dT%H:%M:%S.%LZ";
+
+  function formatIsoNative(date) {
+    return date.toISOString();
+  }
+
+  formatIsoNative.parse = function(string) {
+    var date = new Date(string);
+    return isNaN(date) ? null : date;
+  };
+
+  formatIsoNative.toString = function() {
+    return isoSpecifier;
+  };
+
+  var formatIso = Date.prototype.toISOString && +new Date("2000-01-01T00:00:00.000Z")
+      ? formatIsoNative
+      : enUs.utcFormat(isoSpecifier);
+
+  var isoFormat = formatIso;
+
+  var localeDefinitions = (new Map)
+      .set("ca-ES", caEs)
+      .set("de-DE", deDe)
+      .set("en-CA", enCa)
+      .set("en-GB", enGb)
+      .set("en-US", enUs)
+      .set("es-ES", esEs)
+      .set("fi-FI", fiFi)
+      .set("fr-CA", frCa)
+      .set("fr-FR", frFr)
+      .set("he-IL", heIl)
+      .set("it-IT", itIt)
+      .set("ja-JP", jaJp)
+      .set("mk-MK", mkMk)
+      .set("nl-NL", nlNl)
+      .set("pl-PL", plPl)
+      .set("pt-BR", ptBr)
+      .set("ru-RU", ruRu)
+      .set("zh-CN", zhCn);
+
+  var defaultLocale = locale(enUs);
+  exports.format = defaultLocale.format;
+  exports.utcFormat = defaultLocale.utcFormat;
+
+  function localeFormat(definition) {
+    if (typeof definition === "string") {
+      definition = localeDefinitions.get(definition);
+      if (!definition) return null;
+    }
+    return locale(definition);
+  }
+  ;
+
+  exports.localeFormat = localeFormat;
+  exports.isoFormat = isoFormat;
+
+}));
+},{}],6:[function(require,module,exports){
+(function (global, factory) {
+  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
+  typeof define === 'function' && define.amd ? define(['exports'], factory) :
+  factory((global.time = {}));
+}(this, function (exports) { 'use strict';
+
+  var t1 = new Date;
+
+  var t0 = new Date;
+
+  function newInterval(floori, offseti, count) {
+
+    function interval(date) {
+      return floori(date = new Date(+date)), date;
+    }
+
+    interval.floor = interval;
+
+    interval.round = function(date) {
+      var d0 = new Date(+date),
+          d1 = new Date(date - 1);
+      floori(d0), floori(d1), offseti(d1, 1);
+      return date - d0 < d1 - date ? d0 : d1;
+    };
+
+    interval.ceil = function(date) {
+      return floori(date = new Date(date - 1)), offseti(date, 1), date;
+    };
+
+    interval.offset = function(date, step) {
+      return offseti(date = new Date(+date), step == null ? 1 : Math.floor(step)), date;
+    };
+
+    interval.range = function(start, stop, step) {
+      var range = [];
+      start = new Date(start - 1);
+      stop = new Date(+stop);
+      step = step == null ? 1 : Math.floor(step);
+      if (!(start < stop) || !(step > 0)) return range; // also handles Invalid Date
+      offseti(start, 1), floori(start);
+      if (start < stop) range.push(new Date(+start));
+      while (offseti(start, step), floori(start), start < stop) range.push(new Date(+start));
+      return range;
+    };
+
+    interval.filter = function(test) {
+      return newInterval(function(date) {
+        while (floori(date), !test(date)) date.setTime(date - 1);
+      }, function(date, step) {
+        while (--step >= 0) while (offseti(date, 1), !test(date));
+      });
+    };
+
+    if (count) interval.count = function(start, end) {
+      t0.setTime(+start), t1.setTime(+end);
+      floori(t0), floori(t1);
+      return Math.floor(count(t0, t1));
+    };
+
+    return interval;
+  }
+
+  var second = newInterval(function(date) {
+    date.setMilliseconds(0);
+  }, function(date, step) {
+    date.setTime(+date + step * 1e3);
+  }, function(start, end) {
+    return (end - start) / 1e3;
+  });
+
+  exports.seconds = second.range;
+
+  var minute = newInterval(function(date) {
+    date.setSeconds(0, 0);
+  }, function(date, step) {
+    date.setTime(+date + step * 6e4);
+  }, function(start, end) {
+    return (end - start) / 6e4;
+  });
+
+  exports.minutes = minute.range;
+
+  var hour = newInterval(function(date) {
+    date.setMinutes(0, 0, 0);
+  }, function(date, step) {
+    date.setTime(+date + step * 36e5);
+  }, function(start, end) {
+    return (end - start) / 36e5;
+  });
+
+  exports.hours = hour.range;
+
+  var day = newInterval(function(date) {
+    date.setHours(0, 0, 0, 0);
+  }, function(date, step) {
+    date.setDate(date.getDate() + step);
+  }, function(start, end) {
+    return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * 6e4) / 864e5;
+  });
+
+  exports.days = day.range;
+
+  function weekday(i) {
+    return newInterval(function(date) {
+      date.setHours(0, 0, 0, 0);
+      date.setDate(date.getDate() - (date.getDay() + 7 - i) % 7);
+    }, function(date, step) {
+      date.setDate(date.getDate() + step * 7);
+    }, function(start, end) {
+      return (end - start - (end.getTimezoneOffset() - start.getTimezoneOffset()) * 6e4) / 6048e5;
+    });
+  }
+
+  exports.sunday = weekday(0);
+
+  exports.sundays = exports.sunday.range;
+
+  exports.monday = weekday(1);
+
+  exports.mondays = exports.monday.range;
+
+  exports.tuesday = weekday(2);
+
+  exports.tuesdays = exports.tuesday.range;
+
+  exports.wednesday = weekday(3);
+
+  exports.wednesdays = exports.wednesday.range;
+
+  exports.thursday = weekday(4);
+
+  exports.thursdays = exports.thursday.range;
+
+  exports.friday = weekday(5);
+
+  exports.fridays = exports.friday.range;
+
+  exports.saturday = weekday(6);
+
+  exports.saturdays = exports.saturday.range;
+
+  var week = exports.sunday;
+
+  exports.weeks = week.range;
+
+  var month = newInterval(function(date) {
+    date.setHours(0, 0, 0, 0);
+    date.setDate(1);
+  }, function(date, step) {
+    date.setMonth(date.getMonth() + step);
+  }, function(start, end) {
+    return end.getMonth() - start.getMonth() + (end.getFullYear() - start.getFullYear()) * 12;
+  });
+
+  exports.months = month.range;
+
+  var year = newInterval(function(date) {
+    date.setHours(0, 0, 0, 0);
+    date.setMonth(0, 1);
+  }, function(date, step) {
+    date.setFullYear(date.getFullYear() + step);
+  }, function(start, end) {
+    return end.getFullYear() - start.getFullYear();
+  });
+
+  exports.years = year.range;
+
+  var utcSecond = newInterval(function(date) {
+    date.setUTCMilliseconds(0);
+  }, function(date, step) {
+    date.setTime(+date + step * 1e3);
+  }, function(start, end) {
+    return (end - start) / 1e3;
+  });
+
+  exports.utcSeconds = utcSecond.range;
+
+  var utcMinute = newInterval(function(date) {
+    date.setUTCSeconds(0, 0);
+  }, function(date, step) {
+    date.setTime(+date + step * 6e4);
+  }, function(start, end) {
+    return (end - start) / 6e4;
+  });
+
+  exports.utcMinutes = utcMinute.range;
+
+  var utcHour = newInterval(function(date) {
+    date.setUTCMinutes(0, 0, 0);
+  }, function(date, step) {
+    date.setTime(+date + step * 36e5);
+  }, function(start, end) {
+    return (end - start) / 36e5;
+  });
+
+  exports.utcHours = utcHour.range;
+
+  var utcDay = newInterval(function(date) {
+    date.setUTCHours(0, 0, 0, 0);
+  }, function(date, step) {
+    date.setUTCDate(date.getUTCDate() + step);
+  }, function(start, end) {
+    return (end - start) / 864e5;
+  });
+
+  exports.utcDays = utcDay.range;
+
+  function utcWeekday(i) {
+    return newInterval(function(date) {
+      date.setUTCHours(0, 0, 0, 0);
+      date.setUTCDate(date.getUTCDate() - (date.getUTCDay() + 7 - i) % 7);
+    }, function(date, step) {
+      date.setUTCDate(date.getUTCDate() + step * 7);
+    }, function(start, end) {
+      return (end - start) / 6048e5;
+    });
+  }
+
+  exports.utcSunday = utcWeekday(0);
+
+  exports.utcSundays = exports.utcSunday.range;
+
+  exports.utcMonday = utcWeekday(1);
+
+  exports.utcMondays = exports.utcMonday.range;
+
+  exports.utcTuesday = utcWeekday(2);
+
+  exports.utcTuesdays = exports.utcTuesday.range;
+
+  exports.utcWednesday = utcWeekday(3);
+
+  exports.utcWednesdays = exports.utcWednesday.range;
+
+  exports.utcThursday = utcWeekday(4);
+
+  exports.utcThursdays = exports.utcThursday.range;
+
+  exports.utcFriday = utcWeekday(5);
+
+  exports.utcFridays = exports.utcFriday.range;
+
+  exports.utcSaturday = utcWeekday(6);
+
+  exports.utcSaturdays = exports.utcSaturday.range;
+
+  var utcWeek = exports.utcSunday;
+
+  exports.utcWeeks = utcWeek.range;
+
+  var utcMonth = newInterval(function(date) {
+    date.setUTCHours(0, 0, 0, 0);
+    date.setUTCDate(1);
+  }, function(date, step) {
+    date.setUTCMonth(date.getUTCMonth() + step);
+  }, function(start, end) {
+    return end.getUTCMonth() - start.getUTCMonth() + (end.getUTCFullYear() - start.getUTCFullYear()) * 12;
+  });
+
+  exports.utcMonths = utcMonth.range;
+
+  var utcYear = newInterval(function(date) {
+    date.setUTCHours(0, 0, 0, 0);
+    date.setUTCMonth(0, 1);
+  }, function(date, step) {
+    date.setUTCFullYear(date.getUTCFullYear() + step);
+  }, function(start, end) {
+    return end.getUTCFullYear() - start.getUTCFullYear();
+  });
+
+  exports.utcYears = utcYear.range;
+
+  exports.interval = newInterval;
+  exports.second = second;
+  exports.minute = minute;
+  exports.hour = hour;
+  exports.day = day;
+  exports.week = week;
+  exports.month = month;
+  exports.year = year;
+  exports.utcSecond = utcSecond;
+  exports.utcMinute = utcMinute;
+  exports.utcHour = utcHour;
+  exports.utcDay = utcDay;
+  exports.utcWeek = utcWeek;
+  exports.utcMonth = utcMonth;
+  exports.utcYear = utcYear;
+
+}));
+},{}],7:[function(require,module,exports){
+var util = require('../util'),
+    Measures = require('./measures'),
+    Collector = require('./collector');
+
+function Aggregator() {
+  this._cells = {};
+  this._aggr = [];
+  this._stream = false;
+}
+
+var Flags = Aggregator.Flags = {
+  ADD_CELL: 1,
+  MOD_CELL: 2
+};
+
+var proto = Aggregator.prototype;
+
+// Parameters
+
+proto.stream = function(v) {
+  if (v == null) return this._stream;
+  this._stream = !!v;
+  this._aggr = [];
+  return this;
+};
+
+// key accessor to use for streaming removes
+proto.key = function(key) {
+  if (key == null) return this._key;
+  this._key = util.$(key);
+  return this;
+};
+
+// Input: array of objects of the form
+// {name: string, get: function}
+proto.groupby = function(dims) {
+  this._dims = util.array(dims).map(function(d, i) {
+    d = util.isString(d) ? {name: d, get: util.$(d)}
+      : util.isFunction(d) ? {name: util.name(d) || d.name || ('_' + i), get: d}
+      : (d.name && util.isFunction(d.get)) ? d : null;
+    if (d == null) throw 'Invalid groupby argument: ' + d;
+    return d;
+  });
+  return this.clear();
+};
+
+// Input: array of objects of the form
+// {name: string, ops: [string, ...]}
+proto.summarize = function(fields) {
+  fields = summarize_args(fields);
+  this._count = true;
+  var aggr = (this._aggr = []),
+      m, f, i, j, op, as, get;
+
+  for (i=0; i<fields.length; ++i) {
+    for (j=0, m=[], f=fields[i]; j<f.ops.length; ++j) {
+      op = f.ops[j];
+      if (op !== 'count') this._count = false;
+      as = (f.as && f.as[j]) || (op + (f.name==='*' ? '' : '_'+f.name));
+      m.push(Measures[op](as));
+    }
+    get = f.get && util.$(f.get) ||
+      (f.name === '*' ? util.identity : util.$(f.name));
+    aggr.push({
+      name: f.name,
+      measures: Measures.create(
+        m,
+        this._stream, // streaming remove flag
+        get,          // input tuple getter
+        this._assign) // output tuple setter
+    });
+  }
+  return this.clear();
+};
+
+// Convenience method to summarize by count
+proto.count = function() {
+  return this.summarize({'*':'count'});
+};
+
+// Override to perform custom tuple value assignment
+proto._assign = function(object, name, value) {
+  object[name] = value;
+};
+
+function summarize_args(fields) {
+  if (util.isArray(fields)) { return fields; }
+  if (fields == null) { return []; }
+  var a = [], name, ops;
+  for (name in fields) {
+    ops = util.array(fields[name]);
+    a.push({name: name, ops: ops});
+  }
+  return a;
+}
+
+// Cell Management
+
+proto.clear = function() {
+  return (this._cells = {}, this);
+};
+
+proto._cellkey = function(x) {
+  var d = this._dims,
+      n = d.length, i,
+      k = String(d[0].get(x));
+  for (i=1; i<n; ++i) {
+    k += '|' + d[i].get(x);
+  }
+  return k;
+};
+
+proto._cell = function(x) {
+  var key = this._dims.length ? this._cellkey(x) : '';
+  return this._cells[key] || (this._cells[key] = this._newcell(x, key));
+};
+
+proto._newcell = function(x, key) {
+  var cell = {
+    num:   0,
+    tuple: this._newtuple(x, key),
+    flag:  Flags.ADD_CELL,
+    aggs:  {}
+  };
+
+  var aggr = this._aggr, i;
+  for (i=0; i<aggr.length; ++i) {
+    cell.aggs[aggr[i].name] = new aggr[i].measures(cell, cell.tuple);
+  }
+  if (cell.collect) {
+    cell.data = new Collector(this._key);
+  }
+  return cell;
+};
+
+proto._newtuple = function(x) {
+  var dims = this._dims,
+      t = {}, i, n;
+  for (i=0, n=dims.length; i<n; ++i) {
+    t[dims[i].name] = dims[i].get(x);
+  }
+  return this._ingest(t);
+};
+
+// Override to perform custom tuple ingestion
+proto._ingest = util.identity;
+
+// Process Tuples
+
+proto._add = function(x) {
+  var cell = this._cell(x),
+      aggr = this._aggr, i;
+
+  cell.num += 1;
+  if (!this._count) { // skip if count-only
+    if (cell.collect) cell.data.add(x);
+    for (i=0; i<aggr.length; ++i) {
+      cell.aggs[aggr[i].name].add(x);
+    }
+  }
+  cell.flag |= Flags.MOD_CELL;
+  if (this._on_add) this._on_add(x, cell);
+};
+
+proto._rem = function(x) {
+  var cell = this._cell(x),
+      aggr = this._aggr, i;
+
+  cell.num -= 1;
+  if (!this._count) { // skip if count-only
+    if (cell.collect) cell.data.rem(x);
+    for (i=0; i<aggr.length; ++i) {
+      cell.aggs[aggr[i].name].rem(x);
+    }
+  }
+  cell.flag |= Flags.MOD_CELL;
+  if (this._on_rem) this._on_rem(x, cell);
+};
+
+proto._mod = function(curr, prev) {
+  var cell0 = this._cell(prev),
+      cell1 = this._cell(curr),
+      aggr = this._aggr, i;
+
+  if (cell0 !== cell1) {
+    cell0.num -= 1;
+    cell1.num += 1;
+    if (cell0.collect) cell0.data.rem(prev);
+    if (cell1.collect) cell1.data.add(curr);
+  } else if (cell0.collect && !util.isObject(curr)) {
+    cell0.data.rem(prev);
+    cell0.data.add(curr);
+  }
+
+  for (i=0; i<aggr.length; ++i) {
+    cell0.aggs[aggr[i].name].rem(prev);
+    cell1.aggs[aggr[i].name].add(curr);
+  }
+  cell0.flag |= Flags.MOD_CELL;
+  cell1.flag |= Flags.MOD_CELL;
+  if (this._on_mod) this._on_mod(curr, prev, cell0, cell1);
+};
+
+proto.result = function() {
+  var result = [],
+      aggr = this._aggr,
+      cell, i, k;
+
+  for (k in this._cells) {
+    cell = this._cells[k];
+    if (cell.num > 0) {
+      // consolidate collector values
+      if (cell.collect) {
+        cell.data.values();
+      }
+      // update tuple properties
+      for (i=0; i<aggr.length; ++i) {
+        cell.aggs[aggr[i].name].set();
+      }
+      // add output tuple
+      result.push(cell.tuple);
+    } else {
+      delete this._cells[k];
+    }
+    cell.flag = 0;
+  }
+
+  this._rems = false;
+  return result;
+};
+
+proto.changes = function(output) {
+  var changes = output || {add:[], rem:[], mod:[]},
+      aggr = this._aggr,
+      cell, flag, i, k;
+
+  for (k in this._cells) {
+    cell = this._cells[k];
+    flag = cell.flag;
+
+    // consolidate collector values
+    if (cell.collect) {
+      cell.data.values();
+    }
+
+    // update tuple properties
+    for (i=0; i<aggr.length; ++i) {
+      cell.aggs[aggr[i].name].set();
+    }
+
+    // organize output tuples
+    if (cell.num <= 0) {
+      changes.rem.push(cell.tuple); // if (flag === Flags.MOD_CELL) { ??
+      delete this._cells[k];
+      if (this._on_drop) this._on_drop(cell);
+    } else {
+      if (this._on_keep) this._on_keep(cell);
+      if (flag & Flags.ADD_CELL) {
+        changes.add.push(cell.tuple);
+      } else if (flag & Flags.MOD_CELL) {
+        changes.mod.push(cell.tuple);
+      }
+    }
+
+    cell.flag = 0;
+  }
+
+  this._rems = false;
+  return changes;
+};
+
+proto.execute = function(input) {
+  return this.clear().insert(input).result();
+};
+
+proto.insert = function(input) {
+  this._consolidate();
+  for (var i=0; i<input.length; ++i) {
+    this._add(input[i]);
+  }
+  return this;
+};
+
+proto.remove = function(input) {
+  if (!this._stream) {
+    throw 'Aggregator not configured for streaming removes.' +
+      ' Call stream(true) prior to calling summarize.';
+  }
+  for (var i=0; i<input.length; ++i) {
+    this._rem(input[i]);
+  }
+  this._rems = true;
+  return this;
+};
+
+// consolidate removals
+proto._consolidate = function() {
+  if (!this._rems) return;
+  for (var k in this._cells) {
+    if (this._cells[k].collect) {
+      this._cells[k].data.values();
+    }
+  }
+  this._rems = false;
+};
+
+module.exports = Aggregator;
+},{"../util":29,"./collector":8,"./measures":10}],8:[function(require,module,exports){
+var util = require('../util');
+var stats = require('../stats');
+
+var REM = '__dl_rem__';
+
+function Collector(key) {
+  this._add = [];
+  this._rem = [];
+  this._key = key || null;
+  this._last = null;
+}
+
+var proto = Collector.prototype;
+
+proto.add = function(v) {
+  this._add.push(v);
+};
+
+proto.rem = function(v) {
+  this._rem.push(v);
+};
+
+proto.values = function() {
+  this._get = null;
+  if (this._rem.length === 0) return this._add;
+
+  var a = this._add,
+      r = this._rem,
+      k = this._key,
+      x = Array(a.length - r.length),
+      i, j, n, m;
+
+  if (!util.isObject(r[0])) {
+    // processing raw values
+    m = stats.count.map(r);
+    for (i=0, j=0, n=a.length; i<n; ++i) {
+      if (m[a[i]] > 0) {
+        m[a[i]] -= 1;
+      } else {
+        x[j++] = a[i];
+      }
+    }
+  } else if (k) {
+    // has unique key field, so use that
+    m = util.toMap(r, k);
+    for (i=0, j=0, n=a.length; i<n; ++i) {
+      if (!m.hasOwnProperty(k(a[i]))) { x[j++] = a[i]; }
+    }
+  } else {
+    // no unique key, mark tuples directly
+    for (i=0, n=r.length; i<n; ++i) {
+      r[i][REM] = 1;
+    }
+    for (i=0, j=0, n=a.length; i<n; ++i) {
+      if (!a[i][REM]) { x[j++] = a[i]; }
+    }
+    for (i=0, n=r.length; i<n; ++i) {
+      delete r[i][REM];
+    }
+  }
+
+  this._rem = [];
+  return (this._add = x);
+};
+
+// memoizing statistics methods
+
+proto.extent = function(get) {
+  if (this._get !== get || !this._ext) {
+    var v = this.values(),
+        i = stats.extent.index(v, get);
+    this._ext = [v[i[0]], v[i[1]]];
+    this._get = get;    
+  }
+  return this._ext;
+};
+
+proto.argmin = function(get) {
+  return this.extent(get)[0];
+};
+
+proto.argmax = function(get) {
+  return this.extent(get)[1];
+};
+
+proto.min = function(get) {
+  var m = this.extent(get)[0];
+  return m ? get(m) : +Infinity;
+};
+
+proto.max = function(get) {
+  var m = this.extent(get)[1];
+  return m ? get(m) : -Infinity;
+};
+
+proto.quartile = function(get) {
+  if (this._get !== get || !this._q) {
+    this._q = stats.quartile(this.values(), get);
+    this._get = get;    
+  }
+  return this._q;
+};
+
+proto.q1 = function(get) {
+  return this.quartile(get)[0];
+};
+
+proto.q2 = function(get) {
+  return this.quartile(get)[1];
+};
+
+proto.q3 = function(get) {
+  return this.quartile(get)[2];
+};
+
+module.exports = Collector;
+
+},{"../stats":26,"../util":29}],9:[function(require,module,exports){
+var util = require('../util');
+var Aggregator = require('./aggregator');
+
+module.exports = function() {
+  // flatten arguments into a single array
+  var args = [].reduce.call(arguments, function(a, x) {
+    return a.concat(util.array(x));
+  }, []);
+  // create and return an aggregator
+  return new Aggregator()
+    .groupby(args)
+    .summarize({'*':'values'});
+};
+
+},{"../util":29,"./aggregator":7}],10:[function(require,module,exports){
+var util = require('../util');
+
+var types = {
+  'values': measure({
+    name: 'values',
+    init: 'cell.collect = true;',
+    set:  'cell.data.values()', idx: -1
+  }),
+  'count': measure({
+    name: 'count',
+    set:  'cell.num'
+  }),
+  'missing': measure({
+    name: 'missing',
+    set:  'this.missing'
+  }),
+  'valid': measure({
+    name: 'valid',
+    set:  'this.valid'
+  }),
+  'sum': measure({
+    name: 'sum',
+    init: 'this.sum = 0;',
+    add:  'this.sum += v;',
+    rem:  'this.sum -= v;',
+    set:  'this.sum'
+  }),
+  'mean': measure({
+    name: 'mean',
+    init: 'this.mean = 0;',
+    add:  'var d = v - this.mean; this.mean += d / this.valid;',
+    rem:  'var d = v - this.mean; this.mean -= this.valid ? d / this.valid : this.mean;',
+    set:  'this.mean'
+  }),
+  'average': measure({
+    name: 'average',
+    set:  'this.mean',
+    req:  ['mean'], idx: 1
+  }),
+  'variance': measure({
+    name: 'variance',
+    init: 'this.dev = 0;',
+    add:  'this.dev += d * (v - this.mean);',
+    rem:  'this.dev -= d * (v - this.mean);',
+    set:  'this.valid > 1 ? this.dev / (this.valid-1) : 0',
+    req:  ['mean'], idx: 1
+  }),
+  'variancep': measure({
+    name: 'variancep',
+    set:  'this.valid > 1 ? this.dev / this.valid : 0',
+    req:  ['variance'], idx: 2
+  }),
+  'stdev': measure({
+    name: 'stdev',
+    set:  'this.valid > 1 ? Math.sqrt(this.dev / (this.valid-1)) : 0',
+    req:  ['variance'], idx: 2
+  }),
+  'stdevp': measure({
+    name: 'stdevp',
+    set:  'this.valid > 1 ? Math.sqrt(this.dev / this.valid) : 0',
+    req:  ['variance'], idx: 2
+  }),
+  'median': measure({
+    name: 'median',
+    set:  'cell.data.q2(this.get)',
+    req:  ['values'], idx: 3
+  }),
+  'q1': measure({
+    name: 'q1',
+    set:  'cell.data.q1(this.get)',
+    req:  ['values'], idx: 3
+  }),
+  'q3': measure({
+    name: 'q3',
+    set:  'cell.data.q3(this.get)',
+    req:  ['values'], idx: 3
+  }),
+  'distinct': measure({
+    name: 'distinct',
+    set:  'this.distinct(cell.data.values(), this.get)',
+    req:  ['values'], idx: 3
+  }),
+  'argmin': measure({
+    name: 'argmin',
+    add:  'if (v < this.min) this.argmin = t;',
+    rem:  'if (v <= this.min) this.argmin = null;',
+    set:  'this.argmin = this.argmin || cell.data.argmin(this.get)',
+    req:  ['min'], str: ['values'], idx: 3
+  }),
+  'argmax': measure({
+    name: 'argmax',
+    add:  'if (v > this.max) this.argmax = t;',
+    rem:  'if (v >= this.max) this.argmax = null;',
+    set:  'this.argmax = this.argmax || cell.data.argmax(this.get)',
+    req:  ['max'], str: ['values'], idx: 3
+  }),
+  'min': measure({
+    name: 'min',
+    init: 'this.min = +Infinity;',
+    add:  'if (v < this.min) this.min = v;',
+    rem:  'if (v <= this.min) this.min = NaN;',
+    set:  'this.min = (isNaN(this.min) ? cell.data.min(this.get) : this.min)',
+    str:  ['values'], idx: 4
+  }),
+  'max': measure({
+    name: 'max',
+    init: 'this.max = -Infinity;',
+    add:  'if (v > this.max) this.max = v;',
+    rem:  'if (v >= this.max) this.max = NaN;',
+    set:  'this.max = (isNaN(this.max) ? cell.data.max(this.get) : this.max)',
+    str:  ['values'], idx: 4
+  }),
+  'modeskew': measure({
+    name: 'modeskew',
+    set:  'this.dev===0 ? 0 : (this.mean - cell.data.q2(this.get)) / Math.sqrt(this.dev/(this.valid-1))',
+    req:  ['mean', 'stdev', 'median'], idx: 5
+  })
+};
+
+function measure(base) {
+  return function(out) {
+    var m = util.extend({init:'', add:'', rem:'', idx:0}, base);
+    m.out = out || base.name;
+    return m;
+  };
+}
+
+function resolve(agg, stream) {
+  function collect(m, a) {
+    function helper(r) { if (!m[r]) collect(m, m[r] = types[r]()); }
+    if (a.req) a.req.forEach(helper);
+    if (stream && a.str) a.str.forEach(helper);
+    return m;
+  }
+  var map = agg.reduce(
+    collect,
+    agg.reduce(function(m, a) { return (m[a.name] = a, m); }, {})
+  );
+  return util.vals(map).sort(function(a, b) { return a.idx - b.idx; });
+}
+
+function create(agg, stream, accessor, mutator) {
+  var all = resolve(agg, stream),
+      ctr = 'this.cell = cell; this.tuple = t; this.valid = 0; this.missing = 0;',
+      add = 'if (v==null) this.missing++; if (!this.isValid(v)) return; ++this.valid;',
+      rem = 'if (v==null) this.missing--; if (!this.isValid(v)) return; --this.valid;',
+      set = 'var t = this.tuple; var cell = this.cell;';
+
+  all.forEach(function(a) {
+    if (a.idx < 0) {
+      ctr = a.init + ctr;
+      add = a.add + add;
+      rem = a.rem + rem;
+    } else {
+      ctr += a.init;
+      add += a.add;
+      rem += a.rem;
+    }
+  });
+  agg.slice()
+    .sort(function(a, b) { return a.idx - b.idx; })
+    .forEach(function(a) {
+      set += 'this.assign(t,\''+a.out+'\','+a.set+');';
+    });
+  set += 'return t;';
+
+  /* jshint evil: true */
+  ctr = Function('cell', 't', ctr);
+  ctr.prototype.assign = mutator;
+  ctr.prototype.add = Function('t', 'var v = this.get(t);' + add);
+  ctr.prototype.rem = Function('t', 'var v = this.get(t);' + rem);
+  ctr.prototype.set = Function(set);
+  ctr.prototype.get = accessor;
+  ctr.prototype.distinct = require('../stats').count.distinct;
+  ctr.prototype.isValid = util.isValid;
+  ctr.fields = agg.map(util.$('out'));
+  return ctr;
+}
+
+types.create = create;
+module.exports = types;
+},{"../stats":26,"../util":29}],11:[function(require,module,exports){
+var util = require('../util'),
+    time = require('../time'),
+    EPSILON = 1e-15;
+
+function bins(opt) {
+  if (!opt) { throw Error("Missing binning options."); }
+
+  // determine range
+  var maxb = opt.maxbins || 15,
+      base = opt.base || 10,
+      logb = Math.log(base),
+      div = opt.div || [5, 2],      
+      min = opt.min,
+      max = opt.max,
+      span = max - min,
+      step, level, minstep, precision, v, i, eps;
+
+  if (opt.step) {
+    // if step size is explicitly given, use that
+    step = opt.step;
+  } else if (opt.steps) {
+    // if provided, limit choice to acceptable step sizes
+    step = opt.steps[Math.min(
+      opt.steps.length - 1,
+      bisect(opt.steps, span/maxb, 0, opt.steps.length)
+    )];
+  } else {
+    // else use span to determine step size
+    level = Math.ceil(Math.log(maxb) / logb);
+    minstep = opt.minstep || 0;
+    step = Math.max(
+      minstep,
+      Math.pow(base, Math.round(Math.log(span) / logb) - level)
+    );
+    
+    // increase step size if too many bins
+    do { step *= base; } while (Math.ceil(span/step) > maxb);
+
+    // decrease step size if allowed
+    for (i=0; i<div.length; ++i) {
+      v = step / div[i];
+      if (v >= minstep && span / v <= maxb) step = v;
+    }
+  }
+
+  // update precision, min and max
+  v = Math.log(step);
+  precision = v >= 0 ? 0 : ~~(-v / logb) + 1;
+  eps = Math.pow(base, -precision - 1);
+  min = Math.min(min, Math.floor(min / step + eps) * step);
+  max = Math.ceil(max / step) * step;
+
+  return {
+    start: min,
+    stop:  max,
+    step:  step,
+    unit:  {precision: precision},
+    value: value,
+    index: index
+  };
+}
+
+function bisect(a, x, lo, hi) {
+  while (lo < hi) {
+    var mid = lo + hi >>> 1;
+    if (util.cmp(a[mid], x) < 0) { lo = mid + 1; }
+    else { hi = mid; }
+  }
+  return lo;
+}
+
+function value(v) {
+  return this.step * Math.floor(v / this.step + EPSILON);
+}
+
+function index(v) {
+  return Math.floor((v - this.start) / this.step + EPSILON);
+}
+
+function date_value(v) {
+  return this.unit.date(value.call(this, v));
+}
+
+function date_index(v) {
+  return index.call(this, this.unit.unit(v));
+}
+
+bins.date = function(opt) {
+  if (!opt) { throw Error("Missing date binning options."); }
+
+  // find time step, then bin
+  var units = opt.utc ? time.utc : time,
+      dmin = opt.min,
+      dmax = opt.max,
+      maxb = opt.maxbins || 20,
+      minb = opt.minbins || 4,
+      span = (+dmax) - (+dmin),
+      unit = opt.unit ? units[opt.unit] : units.find(span, minb, maxb),
+      spec = bins({
+        min:     unit.min != null ? unit.min : unit.unit(dmin),
+        max:     unit.max != null ? unit.max : unit.unit(dmax),
+        maxbins: maxb,
+        minstep: unit.minstep,
+        steps:   unit.step
+      });
+
+  spec.unit = unit;
+  spec.index = date_index;
+  if (!opt.raw) spec.value = date_value;
+  return spec;
+};
+
+module.exports = bins;
+
+},{"../time":28,"../util":29}],12:[function(require,module,exports){
+var bins = require('./bins'),
+    gen  = require('../generate'),
+    type = require('../import/type'),
+    util = require('../util'),
+    stats = require('../stats');
+
+var qtype = {
+  'integer': 1,
+  'number': 1,
+  'date': 1
+};
+
+function $bin(values, f, opt) {
+  opt = options(values, f, opt);
+  var b = spec(opt);
+  return !b ? (opt.accessor || util.identity) :
+    util.$func('bin', b.unit.unit ?
+      function(x) { return b.value(b.unit.unit(x)); } :
+      function(x) { return b.value(x); }
+    )(opt.accessor);
+}
+
+function histogram(values, f, opt) {
+  opt = options(values, f, opt);
+  var b = spec(opt);
+  return b ?
+    numerical(values, opt.accessor, b) :
+    categorical(values, opt.accessor, opt && opt.sort);
+}
+
+function spec(opt) {
+  var t = opt.type, b = null;
+  if (t == null || qtype[t]) {
+    if (t === 'integer' && opt.minstep == null) opt.minstep = 1;
+    b = (t === 'date') ? bins.date(opt) : bins(opt);
+  }
+  return b;
+}
+
+function options() {
+  var a = arguments,
+      i = 0,
+      values = util.isArray(a[i]) ? a[i++] : null,
+      f = util.isFunction(a[i]) || util.isString(a[i]) ? util.$(a[i++]) : null,
+      opt = util.extend({}, a[i]);
+  
+  if (values) {
+    opt.type = opt.type || type(values, f);
+    if (qtype[opt.type]) {
+      var ext = stats.extent(values, f);
+      opt = util.extend({min: ext[0], max: ext[1]}, opt);
+    }
+  }
+  if (f) { opt.accessor = f; }
+  return opt;
+}
+
+function numerical(values, f, b) {
+  var h = gen.range(b.start, b.stop + b.step/2, b.step)
+    .map(function(v) { return {value: b.value(v), count: 0}; });
+
+  for (var i=0, v, j; i<values.length; ++i) {
+    v = f ? f(values[i]) : values[i];
+    if (util.isValid(v)) {
+      j = b.index(v);
+      if (j < 0 || j >= h.length || !isFinite(j)) continue;
+      h[j].count += 1;
+    }
+  }
+  h.bins = b;
+  return h;
+}
+
+function categorical(values, f, sort) {
+  var u = stats.unique(values, f),
+      c = stats.count.map(values, f);
+  return u.map(function(k) { return {value: k, count: c[k]}; })
+    .sort(util.comparator(sort ? '-count' : '+value'));
+}
+
+module.exports = {
+  $bin: $bin,
+  histogram: histogram
+};
+},{"../generate":14,"../import/type":23,"../stats":26,"../util":29,"./bins":11}],13:[function(require,module,exports){
+var d3_time = require('d3-time'),
+    d3_timeF = require('d3-time-format'),
+    d3_numberF = require('d3-format'),
+    numberF = d3_numberF, // defaults to EN-US
+    timeF = d3_timeF;     // defaults to EN-US
+
+function numberLocale(l) {
+  var f = d3_numberF.localeFormat(l);
+  if (f == null) throw Error('Unrecognized locale: ' + l);
+  numberF = f;
+}
+
+function timeLocale(l) {
+  var f = d3_timeF.localeFormat(l);
+  if (f == null) throw Error('Unrecognized locale: ' + l);
+  timeF = f;
+}
+
+module.exports = {
+  // Update number formatter to use provided locale configuration.
+  // For more see https://github.com/d3/d3-format
+  numberLocale: numberLocale,
+  number:       function(f) { return numberF.format(f); },
+  numberPrefix: function(f, v) { return numberF.formatPrefix(f, v); },
+
+  // Update time formatter to use provided locale configuration.
+  // For more see https://github.com/d3/d3-time-format
+  timeLocale:   timeLocale,
+  time:         function(f) { return timeF.format(f); },  
+  utc:          function(f) { return timeF.utcFormat(f); },
+
+  // Set number and time locale simultaneously.
+  locale:       function(l) { numberLocale(l); timeLocale(l); },
+
+  // automatic formatting functions
+  auto: {
+    number:   numberAutoFormat,
+    time:     function() { return timeAutoFormat(); },
+    utc:      function() { return utcAutoFormat(); }
+  }
+};
+
+var e10 = Math.sqrt(50),
+    e5 = Math.sqrt(10),
+    e2 = Math.sqrt(2);
+
+function intervals(domain, count) {
+  if (!domain.length) domain = [0];
+  if (count == null) count = 10;
+
+  var start = domain[0],
+      stop = domain[domain.length - 1];
+
+  if (stop < start) { error = stop; stop = start; start = error; }
+
+  var span = (stop - start) || (count = 1, start || stop || 1),
+      step = Math.pow(10, Math.floor(Math.log(span / count) / Math.LN10)),
+      error = span / count / step;
+
+  // Filter ticks to get closer to the desired count.
+  if (error >= e10) step *= 10;
+  else if (error >= e5) step *= 5;
+  else if (error >= e2) step *= 2;
+
+  // Round start and stop values to step interval.
+  return [
+    Math.ceil(start / step) * step,
+    Math.floor(stop / step) * step + step / 2, // inclusive
+    step
+  ];
+}
+
+function numberAutoFormat(domain, count, f) {
+  var range = intervals(domain, count);
+  if (f == null) {
+    f = ',.' + d3_numberF.precisionFixed(range[2]) + 'f';
+  } else {
+    switch (f = d3_numberF.formatSpecifier(f), f.type) {
+      case 's': {
+        var value = Math.max(Math.abs(range[0]), Math.abs(range[1]));
+        if (f.precision == null) f.precision = d3_numberF.precisionPrefix(range[2], value);
+        return numberF.formatPrefix(f, value);
+      }
+      case '':
+      case 'e':
+      case 'g':
+      case 'p':
+      case 'r': {
+        if (f.precision == null) f.precision = d3_numberF.precisionRound(range[2], Math.max(Math.abs(range[0]), Math.abs(range[1]))) - (f.type === 'e');
+        break;
+      }
+      case 'f':
+      case '%': {
+        if (f.precision == null) f.precision = d3_numberF.precisionFixed(range[2]) - (f.type === '%') * 2;
+        break;
+      }
+    }
+  }
+  return numberF.format(f);
+}
+
+function timeAutoFormat() {
+  var f = timeF.format,
+      formatMillisecond = f('.%L'),
+      formatSecond = f(':%S'),
+      formatMinute = f('%I:%M'),
+      formatHour = f('%I %p'),
+      formatDay = f('%a %d'),
+      formatWeek = f('%b %d'),
+      formatMonth = f('%B'),
+      formatYear = f('%Y');
+
+  return function(date) {
+    var d = +date;
+    return (d3_time.second(date) < d ? formatMillisecond
+        : d3_time.minute(date) < d ? formatSecond
+        : d3_time.hour(date) < d ? formatMinute
+        : d3_time.day(date) < d ? formatHour
+        : d3_time.month(date) < d ?
+          (d3_time.week(date) < d ? formatDay : formatWeek)
+        : d3_time.year(date) < d ? formatMonth
+        : formatYear)(date);
+  };
+}
+
+function utcAutoFormat() {
+  var f = timeF.utcFormat,
+      formatMillisecond = f('.%L'),
+      formatSecond = f(':%S'),
+      formatMinute = f('%I:%M'),
+      formatHour = f('%I %p'),
+      formatDay = f('%a %d'),
+      formatWeek = f('%b %d'),
+      formatMonth = f('%B'),
+      formatYear = f('%Y');
+
+  return function(date) {
+    var d = +date;
+    return (d3_time.utcSecond(date) < d ? formatMillisecond
+        : d3_time.utcMinute(date) < d ? formatSecond
+        : d3_time.utcHour(date) < d ? formatMinute
+        : d3_time.utcDay(date) < d ? formatHour
+        : d3_time.utcMonth(date) < d ?
+          (d3_time.utcWeek(date) < d ? formatDay : formatWeek)
+        : d3_time.utcYear(date) < d ? formatMonth
+        : formatYear)(date);
+  };
+}
+
+},{"d3-format":4,"d3-time":6,"d3-time-format":5}],14:[function(require,module,exports){
+var gen = module.exports = {};
+
+gen.repeat = function(val, n) {
+  var a = Array(n), i;
+  for (i=0; i<n; ++i) a[i] = val;
+  return a;
+};
+
+gen.zeros = function(n) {
+  return gen.repeat(0, n);
+};
+
+gen.range = function(start, stop, step) {
+  if (arguments.length < 3) {
+    step = 1;
+    if (arguments.length < 2) {
+      stop = start;
+      start = 0;
+    }
+  }
+  if ((stop - start) / step == Infinity) throw new Error('Infinite range');
+  var range = [], i = -1, j;
+  if (step < 0) while ((j = start + step * ++i) > stop) range.push(j);
+  else while ((j = start + step * ++i) < stop) range.push(j);
+  return range;
+};
+
+gen.random = {};
+
+gen.random.uniform = function(min, max) {
+  if (max === undefined) {
+    max = min === undefined ? 1 : min;
+    min = 0;
+  }
+  var d = max - min;
+  var f = function() {
+    return min + d * Math.random();
+  };
+  f.samples = function(n) { return gen.zeros(n).map(f); };
+  return f;
+};
+
+gen.random.integer = function(a, b) {
+  if (b === undefined) {
+    b = a;
+    a = 0;
+  }
+  var d = b - a;
+  var f = function() {
+    return a + Math.floor(d * Math.random());
+  };
+  f.samples = function(n) { return gen.zeros(n).map(f); };
+  return f;
+};
+
+gen.random.normal = function(mean, stdev) {
+  mean = mean || 0;
+  stdev = stdev || 1;
+  var next;
+  var f = function() {
+    var x = 0, y = 0, rds, c;
+    if (next !== undefined) {
+      x = next;
+      next = undefined;
+      return x;
+    }
+    do {
+      x = Math.random()*2-1;
+      y = Math.random()*2-1;
+      rds = x*x + y*y;
+    } while (rds === 0 || rds > 1);
+    c = Math.sqrt(-2*Math.log(rds)/rds); // Box-Muller transform
+    next = mean + y*c*stdev;
+    return mean + x*c*stdev;
+  };
+  f.samples = function(n) { return gen.zeros(n).map(f); };
+  return f;
+};
+},{}],15:[function(require,module,exports){
+var util = require('../../util');
+var d3_dsv = require('d3-dsv');
+
+function dsv(data, format) {
+  if (data) {
+    var h = format.header;
+    data = (h ? h.join(format.delimiter) + '\n' : '') + data;
+  }
+  return d3_dsv.dsv(format.delimiter).parse(data);
+}
+
+dsv.delimiter = function(delim) {
+  var fmt = {delimiter: delim};
+  return function(data, format) {
+    return dsv(data, format ? util.extend(format, fmt) : fmt);
+  };
+};
+
+module.exports = dsv;
+},{"../../util":29,"d3-dsv":3}],16:[function(require,module,exports){
+var dsv = require('./dsv');
+
+module.exports = {
+  json: require('./json'),
+  topojson: require('./topojson'),
+  treejson: require('./treejson'),
+  dsv: dsv,
+  csv: dsv.delimiter(','),
+  tsv: dsv.delimiter('\t')
+};
+},{"./dsv":15,"./json":17,"./topojson":18,"./treejson":19}],17:[function(require,module,exports){
+var util = require('../../util');
+
+module.exports = function(data, format) {
+  var d = util.isObject(data) && !util.isBuffer(data) ?
+    data : JSON.parse(data);
+  if (format && format.property) {
+    d = util.accessor(format.property)(d);
+  }
+  return d;
+};
+
+},{"../../util":29}],18:[function(require,module,exports){
+(function (global){
+var json = require('./json');
+
+var reader = function(data, format) {
+  var topojson = reader.topojson;
+  if (topojson == null) { throw Error('TopoJSON library not loaded.'); }
+
+  var t = json(data, format), obj;
+
+  if (format && format.feature) {
+    if ((obj = t.objects[format.feature])) {
+      return topojson.feature(t, obj).features;
+    } else {
+      throw Error('Invalid TopoJSON object: ' + format.feature);
+    }
+  } else if (format && format.mesh) {
+    if ((obj = t.objects[format.mesh])) {
+      return [topojson.mesh(t, t.objects[format.mesh])];
+    } else {
+      throw Error('Invalid TopoJSON object: ' + format.mesh);
+    }
+  } else {
+    throw Error('Missing TopoJSON feature or mesh parameter.');
+  }
+};
+
+reader.topojson = (typeof window !== "undefined" ? window['topojson'] : typeof global !== "undefined" ? global['topojson'] : null);
+module.exports = reader;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"./json":17}],19:[function(require,module,exports){
+var json = require('./json');
+
+module.exports = function(data, format) {
+  data = json(data, format);
+  return toTable(data, (format && format.children));
+};
+
+function toTable(root, childrenField) {
+  childrenField = childrenField || 'children';
+  var table = [];
+  
+  function visit(node) {
+    table.push(node);
+    var children = node[childrenField];
+    if (children) {
+      for (var i=0; i<children.length; ++i) {
+        visit(children[i], node);
+      }
+    }
+  }
+  
+  visit(root, null);
+  return (table.root = root, table);
+}
+},{"./json":17}],20:[function(require,module,exports){
+// Matches absolute URLs with optional protocol
+//   https://...    file://...    //...
+var protocol_re = /^([A-Za-z]+:)?\/\//;
+
+// Special treatment in node.js for the file: protocol
+var fileProtocol = 'file://';
+
+// Validate and cleanup URL to ensure that it is allowed to be accessed
+// Returns cleaned up URL, or false if access is not allowed
+function sanitizeUrl(opt) {
+  var url = opt.url;
+  if (!url && opt.file) { return fileProtocol + opt.file; }
+
+  // In case this is a relative url (has no host), prepend opt.baseURL
+  if (opt.baseURL && !protocol_re.test(url)) {
+    if (!startsWith(url, '/') && opt.baseURL[opt.baseURL.length-1] !== '/') {
+      url = '/' + url; // Ensure that there is a slash between the baseURL (e.g. hostname) and url
+    }
+    url = opt.baseURL + url;
+  }
+  // relative protocol, starts with '//'
+  if (!load.useXHR && startsWith(url, '//')) {
+    url = (opt.defaultProtocol || 'http') + ':' + url;
+  }
+  // If opt.domainWhiteList is set, only allows url, whose hostname
+  // * Is the same as the origin (window.location.hostname)
+  // * Equals one of the values in the whitelist
+  // * Is a proper subdomain of one of the values in the whitelist
+  if (opt.domainWhiteList) {
+    var domain, origin;
+    if (load.useXHR) {
+      var a = document.createElement('a');
+      a.href = url;
+      // From http://stackoverflow.com/questions/736513/how-do-i-parse-a-url-into-hostname-and-path-in-javascript
+      // IE doesn't populate all link properties when setting .href with a relative URL,
+      // however .href will return an absolute URL which then can be used on itself
+      // to populate these additional fields.
+      if (a.host === '') {
+        a.href = a.href;
+      }
+      domain = a.hostname.toLowerCase();
+      origin = window.location.hostname;
+    } else {
+      // relative protocol is broken: https://github.com/defunctzombie/node-url/issues/5
+      var parts = require('url').parse(url);
+      domain = parts.hostname;
+      origin = null;
+    }
+
+    if (origin !== domain) {
+      var whiteListed = opt.domainWhiteList.some(function(d) {
+        var idx = domain.length - d.length;
+        return d === domain ||
+          (idx > 1 && domain[idx-1] === '.' && domain.lastIndexOf(d) === idx);
+      });
+      if (!whiteListed) {
+        throw 'URL is not whitelisted: ' + url;
+      }
+    }
+  }
+  return url;
+}
+
+function load(opt, callback) {
+  var error = callback || function(e) { throw e; }, url;
+
+  try {
+    url = load.sanitizeUrl(opt); // enable override
+  } catch (err) {
+    error(err);
+    return;
+  }
+
+  if (!url) {
+    error('Invalid URL: ' + opt.url);
+  } else if (load.useXHR) {
+    // on client, use xhr
+    return xhr(url, callback);
+  } else if (startsWith(url, fileProtocol)) {
+    // on server, if url starts with 'file://', strip it and load from file
+    return file(url.slice(fileProtocol.length), callback);
+  } else if (url.indexOf('://') < 0) { // TODO better protocol check?
+    // on server, if no protocol assume file
+    return file(url, callback);
+  } else {
+    // for regular URLs on server
+    return http(url, callback);
+  }
+}
+
+function xhrHasResponse(request) {
+  var type = request.responseType;
+  return type && type !== 'text' ?
+    request.response : // null on error
+    request.responseText; // '' on error
+}
+
+function xhr(url, callback) {
+  var async = !!callback;
+  var request = new XMLHttpRequest();
+  // If IE does not support CORS, use XDomainRequest (copied from d3.xhr)
+  if (this.XDomainRequest &&
+      !('withCredentials' in request) &&
+      /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest();
+
+  function respond() {
+    var status = request.status;
+    if (!status && xhrHasResponse(request) || status >= 200 && status < 300 || status === 304) {
+      callback(null, request.responseText);
+    } else {
+      callback(request, null);
+    }
+  }
+
+  if (async) {
+    if ('onload' in request) {
+      request.onload = request.onerror = respond;
+    } else {
+      request.onreadystatechange = function() {
+        if (request.readyState > 3) respond();
+      };
+    }
+  }
+  
+  request.open('GET', url, async);
+  request.send();
+  
+  if (!async && xhrHasResponse(request)) {
+    return request.responseText;
+  }
+}
+
+function file(filename, callback) {
+  var fs = require('fs');
+  if (!callback) {
+    return fs.readFileSync(filename, 'utf8');
+  }
+  fs.readFile(filename, callback);
+}
+
+function http(url, callback) {
+  if (!callback) {
+    return require('sync-request')('GET', url).getBody();
+  }
+  
+  var options = {url: url, encoding: null, gzip: true};
+  require('request')(options, function(error, response, body) {
+    if (!error && response.statusCode === 200) {
+      callback(null, body);
+    } else {
+      error = error ||
+        'Load failed with response code ' + response.statusCode + '.';
+      callback(error, null);
+    }
+  });
+}
+
+function startsWith(string, searchString) {
+  return string == null ? false : string.lastIndexOf(searchString, 0) === 0;
+}
+
+load.sanitizeUrl = sanitizeUrl;
+
+load.useXHR = (typeof XMLHttpRequest !== 'undefined');
+
+module.exports = load;
+
+},{"fs":2,"request":2,"sync-request":2,"url":2}],21:[function(require,module,exports){
+var util = require('../util');
+var type = require('./type');
+var formats = require('./formats');
+
+function read(data, format) {
+  var type = (format && format.type) || 'json';
+  data = formats[type](data, format);
+  if (format && format.parse) parse(data, format.parse);
+  return data;
+}
+
+function parse(data, types) {
+  var cols, parsers, d, i, j, clen, len = data.length;
+
+  types = (types==='auto') ? type.inferAll(data) : util.duplicate(types);
+  cols = util.keys(types);
+  parsers = cols.map(function(c) { return type.parsers[types[c]]; });
+
+  for (i=0, clen=cols.length; i<len; ++i) {
+    d = data[i];
+    for (j=0; j<clen; ++j) {
+      d[cols[j]] = parsers[j](d[cols[j]]);
+    }
+  }
+  type.annotation(data, types);
+}
+
+read.formats = formats;
+module.exports = read;
+
+},{"../util":29,"./formats":16,"./type":23}],22:[function(require,module,exports){
+var util = require('../util');
+var load = require('./load');
+var read = require('./read');
+
+module.exports = util
+  .keys(read.formats)
+  .reduce(function(out, type) {
+    out[type] = function(opt, format, callback) {
+      // process arguments
+      if (util.isString(opt)) { opt = {url: opt}; }
+      if (arguments.length === 2 && util.isFunction(format)) {
+        callback = format;
+        format = undefined;
+      }
+
+      // set up read format
+      format = util.extend({parse: 'auto'}, format);
+      format.type = type;
+
+      // load data
+      var data = load(opt, callback ? function(error, data) {
+        if (error) { callback(error, null); return; }
+        try {
+          // data loaded, now parse it (async)
+          data = read(data, format);
+          callback(null, data);
+        } catch (e) {
+          callback(e, null);
+        }
+      } : undefined);
+      
+      // data loaded, now parse it (sync)
+      if (!callback) return read(data, format);
+    };
+    return out;
+  }, {});
+
+},{"../util":29,"./load":20,"./read":21}],23:[function(require,module,exports){
+var util = require('../util');
+
+var TYPES = '__types__';
+
+var PARSERS = {
+  boolean: util.boolean,
+  integer: util.number,
+  number:  util.number,
+  date:    util.date,
+  string:  function(x) { return x==='' ? null : x; }
+};
+
+var TESTS = {
+  boolean: function(x) { return x==='true' || x==='false' || util.isBoolean(x); },
+  integer: function(x) { return TESTS.number(x) && (x=+x) === ~~x; },
+  number: function(x) { return !isNaN(+x) && !util.isDate(x); },
+  date: function(x) { return !isNaN(Date.parse(x)); }
+};
+
+function annotation(data, types) {
+  if (!types) return data && data[TYPES] || null;
+  data[TYPES] = types;
+}
+
+function type(values, f) {
+  f = util.$(f);
+  var v, i, n;
+
+  // if data array has type annotations, use them
+  if (values[TYPES]) {
+    v = f(values[TYPES]);
+    if (util.isString(v)) return v;
+  }
+
+  for (i=0, n=values.length; !util.isValid(v) && i<n; ++i) {
+    v = f ? f(values[i]) : values[i];
+  }
+
+  return util.isDate(v) ? 'date' :
+    util.isNumber(v)    ? 'number' :
+    util.isBoolean(v)   ? 'boolean' :
+    util.isString(v)    ? 'string' : null;
+}
+
+function typeAll(data, fields) {
+  if (!data.length) return;
+  fields = fields || util.keys(data[0]);
+  return fields.reduce(function(types, f) {
+    return (types[f] = type(data, f), types);
+  }, {});
+}
+
+function infer(values, f) {
+  f = util.$(f);
+  var i, j, v;
+
+  // types to test for, in precedence order
+  var types = ['boolean', 'integer', 'number', 'date'];
+
+  for (i=0; i<values.length; ++i) {
+    // get next value to test
+    v = f ? f(values[i]) : values[i];
+    // test value against remaining types
+    for (j=0; j<types.length; ++j) {
+      if (util.isValid(v) && !TESTS[types[j]](v)) {
+        types.splice(j, 1);
+        j -= 1;
+      }
+    }
+    // if no types left, return 'string'
+    if (types.length === 0) return 'string';
+  }
+
+  return types[0];
+}
+
+function inferAll(data, fields) {
+  fields = fields || util.keys(data[0]);
+  return fields.reduce(function(types, f) {
+    types[f] = infer(data, f);
+    return types;
+  }, {});
+}
+
+type.annotation = annotation;
+type.all = typeAll;
+type.infer = infer;
+type.inferAll = inferAll;
+type.parsers = PARSERS;
+module.exports = type;
+},{"../util":29}],24:[function(require,module,exports){
+var util = require('./util');
+
+var dl = {
+  version:    '1.4.6',
+  load:       require('./import/load'),
+  read:       require('./import/read'),
+  type:       require('./import/type'),
+  Aggregator: require('./aggregate/aggregator'),
+  groupby:    require('./aggregate/groupby'),
+  bins:       require('./bins/bins'),
+  $bin:       require('./bins/histogram').$bin,
+  histogram:  require('./bins/histogram').histogram,
+  format:     require('./format'),
+  print:      require('./print'),
+  template:   require('./template'),
+  time:       require('./time')
+};
+
+util.extend(dl, util);
+util.extend(dl, require('./generate'));
+util.extend(dl, require('./stats'));
+util.extend(dl, require('./import/readers'));
+
+module.exports = dl;
+},{"./aggregate/aggregator":7,"./aggregate/groupby":9,"./bins/bins":11,"./bins/histogram":12,"./format":13,"./generate":14,"./import/load":20,"./import/read":21,"./import/readers":22,"./import/type":23,"./print":25,"./stats":26,"./template":27,"./time":28,"./util":29}],25:[function(require,module,exports){
+var util = require('./util');
+var type = require('./import/type');
+var stats = require('./stats');
+var template = require('./template');
+
+var FMT = {
+  'date':    '|time:"%m/%d/%Y %H:%M:%S"',
+  'number':  '|number:".4f"',
+  'integer': '|number:"d"'
+};
+
+var POS = {
+  'number':  'left',
+  'integer': 'left'
+};
+
+module.exports.table = function(data, opt) {
+  opt = util.extend({separator:' ', minwidth: 8, maxwidth: 15}, opt);
+  var fields = opt.fields || util.keys(data[0]),
+      types = type.all(data);
+
+  if (opt.start || opt.limit) {
+    var a = opt.start || 0,
+        b = opt.limit ? a + opt.limit : data.length;
+    data = data.slice(a, b);
+  }
+
+  // determine char width of fields
+  var lens = fields.map(function(name) {
+    var format = FMT[types[name]] || '',
+        t = template('{{' + name + format + '}}'),
+        l = stats.max(data, function(x) { return t(x).length; });
+    l = Math.max(Math.min(name.length, opt.minwidth), l);
+    return opt.maxwidth > 0 ? Math.min(l, opt.maxwidth) : l;
+  });
+
+  // print header row
+  var head = fields.map(function(name, i) {
+    return util.truncate(util.pad(name, lens[i], 'center'), lens[i]);
+  }).join(opt.separator);
+
+  // build template function for each row
+  var tmpl = template(fields.map(function(name, i) {
+    return '{{' +
+      name +
+      (FMT[types[name]] || '') +
+      ('|pad:' + lens[i] + ',' + (POS[types[name]] || 'right')) +
+      ('|truncate:' + lens[i]) +
+    '}}';
+  }).join(opt.separator));
+
+  // print table
+  return head + "\n" + data.map(tmpl).join('\n');
+};
+
+module.exports.summary = function(s) {
+  s = s ? s.__summary__ ? s : stats.summary(s) : this;
+  var str = [], i, n;
+  for (i=0, n=s.length; i<n; ++i) {
+    str.push('-- ' + s[i].field + ' --');
+    if (s[i].type === 'string' || s[i].distinct < 10) {
+      str.push(printCategoricalProfile(s[i]));
+    } else {
+      str.push(printQuantitativeProfile(s[i]));
+    }
+    str.push('');
+  }
+  return str.join('\n');
+};
+
+function printQuantitativeProfile(p) {
+  return [
+    'valid:    ' + p.valid,
+    'missing:  ' + p.missing,
+    'distinct: ' + p.distinct,
+    'min:      ' + p.min,
+    'max:      ' + p.max,
+    'median:   ' + p.median,
+    'mean:     ' + p.mean,
+    'stdev:    ' + p.stdev,
+    'modeskew: ' + p.modeskew
+  ].join('\n');
+}
+
+function printCategoricalProfile(p) {
+  var list = [
+    'valid:    ' + p.valid,
+    'missing:  ' + p.missing,
+    'distinct: ' + p.distinct,
+    'top values: '
+  ];
+  var u = p.unique;
+  var top = util.keys(u)
+    .sort(function(a,b) { return u[b] - u[a]; })
+    .slice(0, 6)
+    .map(function(v) { return ' \'' + v + '\' (' + u[v] + ')'; });
+  return list.concat(top).join('\n');
+}
+},{"./import/type":23,"./stats":26,"./template":27,"./util":29}],26:[function(require,module,exports){
+var util = require('./util');
+var type = require('./import/type');
+var gen = require('./generate');
+var stats = {};
+
+// Collect unique values.
+// Output: an array of unique values, in first-observed order
+stats.unique = function(values, f, results) {
+  f = util.$(f);
+  results = results || [];
+  var u = {}, v, i, n;
+  for (i=0, n=values.length; i<n; ++i) {
+    v = f ? f(values[i]) : values[i];
+    if (v in u) continue;
+    u[v] = 1;
+    results.push(v);
+  }
+  return results;
+};
+
+// Return the length of the input array.
+stats.count = function(values) {
+  return values && values.length || 0;
+};
+
+// Count the number of non-null, non-undefined, non-NaN values.
+stats.count.valid = function(values, f) {
+  f = util.$(f);
+  var v, i, n, valid = 0;
+  for (i=0, n=values.length; i<n; ++i) {
+    v = f ? f(values[i]) : values[i];
+    if (util.isValid(v)) valid += 1;
+  }
+  return valid;
+};
+
+// Count the number of null or undefined values.
+stats.count.missing = function(values, f) {
+  f = util.$(f);
+  var v, i, n, count = 0;
+  for (i=0, n=values.length; i<n; ++i) {
+    v = f ? f(values[i]) : values[i];
+    if (v == null) count += 1;
+  }
+  return count;
+};
+
+// Count the number of distinct values.
+// Null, undefined and NaN are each considered distinct values.
+stats.count.distinct = function(values, f) {
+  f = util.$(f);
+  var u = {}, v, i, n, count = 0;
+  for (i=0, n=values.length; i<n; ++i) {
+    v = f ? f(values[i]) : values[i];
+    if (v in u) continue;
+    u[v] = 1;
+    count += 1;
+  }
+  return count;
+};
+
+// Construct a map from distinct values to occurrence counts.
+stats.count.map = function(values, f) {
+  f = util.$(f);
+  var map = {}, v, i, n;
+  for (i=0, n=values.length; i<n; ++i) {
+    v = f ? f(values[i]) : values[i];
+    map[v] = (v in map) ? map[v] + 1 : 1;
+  }
+  return map;
+};
+
+// Compute the median of an array of numbers.
+stats.median = function(values, f) {
+  if (f) values = values.map(util.$(f));
+  values = values.filter(util.isValid).sort(util.cmp);
+  return stats.quantile(values, 0.5);
+};
+
+// Computes the quartile boundaries of an array of numbers.
+stats.quartile = function(values, f) {
+  if (f) values = values.map(util.$(f));
+  values = values.filter(util.isValid).sort(util.cmp);
+  var q = stats.quantile;
+  return [q(values, 0.25), q(values, 0.50), q(values, 0.75)];
+};
+
+// Compute the quantile of a sorted array of numbers.
+// Adapted from the D3.js implementation.
+stats.quantile = function(values, f, p) {
+  if (p === undefined) { p = f; f = util.identity; }
+  f = util.$(f);
+  var H = (values.length - 1) * p + 1,
+      h = Math.floor(H),
+      v = +f(values[h - 1]),
+      e = H - h;
+  return e ? v + e * (f(values[h]) - v) : v;
+};
+
+// Compute the sum of an array of numbers.
+stats.sum = function(values, f) {
+  f = util.$(f);
+  for (var sum=0, i=0, n=values.length, v; i<n; ++i) {
+    v = f ? f(values[i]) : values[i];
+    if (util.isValid(v)) sum += v;
+  }
+  return sum;
+};
+
+// Compute the mean (average) of an array of numbers.
+stats.mean = function(values, f) {
+  f = util.$(f);
+  var mean = 0, delta, i, n, c, v;
+  for (i=0, c=0, n=values.length; i<n; ++i) {
+    v = f ? f(values[i]) : values[i];
+    if (util.isValid(v)) {
+      delta = v - mean;
+      mean = mean + delta / (++c);
+    }
+  }
+  return mean;
+};
+
+// Compute the sample variance of an array of numbers.
+stats.variance = function(values, f) {
+  f = util.$(f);
+  if (!util.isArray(values) || values.length < 2) return 0;
+  var mean = 0, M2 = 0, delta, i, c, v;
+  for (i=0, c=0; i<values.length; ++i) {
+    v = f ? f(values[i]) : values[i];
+    if (util.isValid(v)) {
+      delta = v - mean;
+      mean = mean + delta / (++c);
+      M2 = M2 + delta * (v - mean);
+    }
+  }
+  M2 = M2 / (c - 1);
+  return M2;
+};
+
+// Compute the sample standard deviation of an array of numbers.
+stats.stdev = function(values, f) {
+  return Math.sqrt(stats.variance(values, f));
+};
+
+// Compute the Pearson mode skewness ((median-mean)/stdev) of an array of numbers.
+stats.modeskew = function(values, f) {
+  var avg = stats.mean(values, f),
+      med = stats.median(values, f),
+      std = stats.stdev(values, f);
+  return std === 0 ? 0 : (avg - med) / std;
+};
+
+// Find the minimum value in an array.
+stats.min = function(values, f) {
+  return stats.extent(values, f)[0];
+};
+
+// Find the maximum value in an array.
+stats.max = function(values, f) {
+  return stats.extent(values, f)[1];
+};
+
+// Find the minimum and maximum of an array of values.
+stats.extent = function(values, f) {
+  f = util.$(f);
+  var a, b, v, i, n = values.length;
+  for (i=0; i<n; ++i) {
+    v = f ? f(values[i]) : values[i];
+    if (util.isValid(v)) { a = b = v; break; }
+  }
+  for (; i<n; ++i) {
+    v = f ? f(values[i]) : values[i];
+    if (util.isValid(v)) {
+      if (v < a) a = v;
+      if (v > b) b = v;
+    }
+  }
+  return [a, b];
+};
+
+// Find the integer indices of the minimum and maximum values.
+stats.extent.index = function(values, f) {
+  f = util.$(f);
+  var x = -1, y = -1, a, b, v, i, n = values.length;
+  for (i=0; i<n; ++i) {
+    v = f ? f(values[i]) : values[i];
+    if (util.isValid(v)) { a = b = v; x = y = i; break; }
+  }
+  for (; i<n; ++i) {
+    v = f ? f(values[i]) : values[i];
+    if (util.isValid(v)) {
+      if (v < a) { a = v; x = i; }
+      if (v > b) { b = v; y = i; }
+    }
+  }
+  return [x, y];
+};
+
+// Compute the dot product of two arrays of numbers.
+stats.dot = function(values, a, b) {
+  var sum = 0, i, v;
+  if (!b) {
+    if (values.length !== a.length) {
+      throw Error('Array lengths must match.');
+    }
+    for (i=0; i<values.length; ++i) {
+      v = values[i] * a[i];
+      if (v === v) sum += v;
+    }
+  } else {
+    a = util.$(a);
+    b = util.$(b);
+    for (i=0; i<values.length; ++i) {
+      v = a(values[i]) * b(values[i]);
+      if (v === v) sum += v;
+    }
+  }
+  return sum;
+};
+
+// Compute ascending rank scores for an array of values.
+// Ties are assigned their collective mean rank.
+stats.rank = function(values, f) {
+  f = util.$(f) || util.identity;
+  var a = values.map(function(v, i) {
+      return {idx: i, val: f(v)};
+    })
+    .sort(util.comparator('val'));
+
+  var n = values.length,
+      r = Array(n),
+      tie = -1, p = {}, i, v, mu;
+
+  for (i=0; i<n; ++i) {
+    v = a[i].val;
+    if (tie < 0 && p === v) {
+      tie = i - 1;
+    } else if (tie > -1 && p !== v) {
+      mu = 1 + (i-1 + tie) / 2;
+      for (; tie<i; ++tie) r[a[tie].idx] = mu;
+      tie = -1;
+    }
+    r[a[i].idx] = i + 1;
+    p = v;
+  }
+
+  if (tie > -1) {
+    mu = 1 + (n-1 + tie) / 2;
+    for (; tie<n; ++tie) r[a[tie].idx] = mu;
+  }
+
+  return r;
+};
+
+// Compute the sample Pearson product-moment correlation of two arrays of numbers.
+stats.cor = function(values, a, b) {
+  var fn = b;
+  b = fn ? values.map(util.$(b)) : a;
+  a = fn ? values.map(util.$(a)) : values;
+
+  var dot = stats.dot(a, b),
+      mua = stats.mean(a),
+      mub = stats.mean(b),
+      sda = stats.stdev(a),
+      sdb = stats.stdev(b),
+      n = values.length;
+
+  return (dot - n*mua*mub) / ((n-1) * sda * sdb);
+};
+
+// Compute the Spearman rank correlation of two arrays of values.
+stats.cor.rank = function(values, a, b) {
+  var ra = b ? stats.rank(values, util.$(a)) : stats.rank(values),
+      rb = b ? stats.rank(values, util.$(b)) : stats.rank(a),
+      n = values.length, i, s, d;
+
+  for (i=0, s=0; i<n; ++i) {
+    d = ra[i] - rb[i];
+    s += d * d;
+  }
+
+  return 1 - 6*s / (n * (n*n-1));
+};
+
+// Compute the distance correlation of two arrays of numbers.
+// http://en.wikipedia.org/wiki/Distance_correlation
+stats.cor.dist = function(values, a, b) {
+  var X = b ? values.map(util.$(a)) : values,
+      Y = b ? values.map(util.$(b)) : a;
+
+  var A = stats.dist.mat(X),
+      B = stats.dist.mat(Y),
+      n = A.length,
+      i, aa, bb, ab;
+
+  for (i=0, aa=0, bb=0, ab=0; i<n; ++i) {
+    aa += A[i]*A[i];
+    bb += B[i]*B[i];
+    ab += A[i]*B[i];
+  }
+
+  return Math.sqrt(ab / Math.sqrt(aa*bb));
+};
+
+// Compute the vector distance between two arrays of numbers.
+// Default is Euclidean (exp=2) distance, configurable via exp argument.
+stats.dist = function(values, a, b, exp) {
+  var f = util.isFunction(b) || util.isString(b),
+      X = values,
+      Y = f ? values : a,
+      e = f ? exp : b,
+      L2 = e === 2 || e == null,
+      n = values.length, s = 0, d, i;
+  if (f) {
+    a = util.$(a);
+    b = util.$(b);
+  }
+  for (i=0; i<n; ++i) {
+    d = f ? (a(X[i])-b(Y[i])) : (X[i]-Y[i]);
+    s += L2 ? d*d : Math.pow(Math.abs(d), e);
+  }
+  return L2 ? Math.sqrt(s) : Math.pow(s, 1/e);
+};
+
+// Construct a mean-centered distance matrix for an array of numbers.
+stats.dist.mat = function(X) {
+  var n = X.length,
+      m = n*n,
+      A = Array(m),
+      R = gen.zeros(n),
+      M = 0, v, i, j;
+
+  for (i=0; i<n; ++i) {
+    A[i*n+i] = 0;
+    for (j=i+1; j<n; ++j) {
+      A[i*n+j] = (v = Math.abs(X[i] - X[j]));
+      A[j*n+i] = v;
+      R[i] += v;
+      R[j] += v;
+    }
+  }
+
+  for (i=0; i<n; ++i) {
+    M += R[i];
+    R[i] /= n;
+  }
+  M /= m;
+
+  for (i=0; i<n; ++i) {
+    for (j=i; j<n; ++j) {
+      A[i*n+j] += M - R[i] - R[j];
+      A[j*n+i] = A[i*n+j];
+    }
+  }
+
+  return A;
+};
+
+// Compute the Shannon entropy (log base 2) of an array of counts.
+stats.entropy = function(counts, f) {
+  f = util.$(f);
+  var i, p, s = 0, H = 0, n = counts.length;
+  for (i=0; i<n; ++i) {
+    s += (f ? f(counts[i]) : counts[i]);
+  }
+  if (s === 0) return 0;
+  for (i=0; i<n; ++i) {
+    p = (f ? f(counts[i]) : counts[i]) / s;
+    if (p) H += p * Math.log(p);
+  }
+  return -H / Math.LN2;
+};
+
+// Compute the mutual information between two discrete variables.
+// Returns an array of the form [MI, MI_distance] 
+// MI_distance is defined as 1 - I(a,b) / H(a,b).
+// http://en.wikipedia.org/wiki/Mutual_information
+stats.mutual = function(values, a, b, counts) {
+  var x = counts ? values.map(util.$(a)) : values,
+      y = counts ? values.map(util.$(b)) : a,
+      z = counts ? values.map(util.$(counts)) : b;
+
+  var px = {},
+      py = {},
+      n = z.length,
+      s = 0, I = 0, H = 0, p, t, i;
+
+  for (i=0; i<n; ++i) {
+    px[x[i]] = 0;
+    py[y[i]] = 0;
+  }
+
+  for (i=0; i<n; ++i) {
+    px[x[i]] += z[i];
+    py[y[i]] += z[i];
+    s += z[i];
+  }
+
+  t = 1 / (s * Math.LN2);
+  for (i=0; i<n; ++i) {
+    if (z[i] === 0) continue;
+    p = (s * z[i]) / (px[x[i]] * py[y[i]]);
+    I += z[i] * t * Math.log(p);
+    H += z[i] * t * Math.log(z[i]/s);
+  }
+
+  return [I, 1 + I/H];
+};
+
+// Compute the mutual information between two discrete variables.
+stats.mutual.info = function(values, a, b, counts) {
+  return stats.mutual(values, a, b, counts)[0];
+};
+
+// Compute the mutual information distance between two discrete variables.
+// MI_distance is defined as 1 - I(a,b) / H(a,b).
+stats.mutual.dist = function(values, a, b, counts) {
+  return stats.mutual(values, a, b, counts)[1];
+};
+
+// Compute a profile of summary statistics for a variable.
+stats.profile = function(values, f) {
+  var mean = 0,
+      valid = 0,
+      missing = 0,
+      distinct = 0,
+      min = null,
+      max = null,
+      M2 = 0,
+      vals = [],
+      u = {}, delta, sd, i, v, x;
+
+  // compute summary stats
+  for (i=0; i<values.length; ++i) {
+    v = f ? f(values[i]) : values[i];
+
+    // update unique values
+    u[v] = (v in u) ? u[v] + 1 : (distinct += 1, 1);
+
+    if (v == null) {
+      ++missing;
+    } else if (util.isValid(v)) {
+      // update stats
+      x = (typeof v === 'string') ? v.length : v;
+      if (min===null || x < min) min = x;
+      if (max===null || x > max) max = x;
+      delta = x - mean;
+      mean = mean + delta / (++valid);
+      M2 = M2 + delta * (x - mean);
+      vals.push(x);
+    }
+  }
+  M2 = M2 / (valid - 1);
+  sd = Math.sqrt(M2);
+
+  // sort values for median and iqr
+  vals.sort(util.cmp);
+
+  return {
+    type:     type(values, f),
+    unique:   u,
+    count:    values.length,
+    valid:    valid,
+    missing:  missing,
+    distinct: distinct,
+    min:      min,
+    max:      max,
+    mean:     mean,
+    stdev:    sd,
+    median:   (v = stats.quantile(vals, 0.5)),
+    q1:       stats.quantile(vals, 0.25),
+    q3:       stats.quantile(vals, 0.75),
+    modeskew: sd === 0 ? 0 : (mean - v) / sd
+  };
+};
+
+// Compute profiles for all variables in a data set.
+stats.summary = function(data, fields) {
+  fields = fields || util.keys(data[0]);
+  var s = fields.map(function(f) {
+    var p = stats.profile(data, util.$(f));
+    return (p.field = f, p);
+  });
+  return (s.__summary__ = true, s);
+};
+
+module.exports = stats;
+},{"./generate":14,"./import/type":23,"./util":29}],27:[function(require,module,exports){
+var util = require('./util'),
+    format = require('./format');
+
+var context = {
+  formats:    [],
+  format_map: {},
+  truncate:   util.truncate,
+  pad:        util.pad
+};
+
+function template(text) {
+  var src = source(text, 'd');
+  src = 'var __t; return ' + src + ';';
+
+  /* jshint evil: true */
+  return (new Function('d', src)).bind(context);
+}
+
+template.source = source;
+template.context = context;
+module.exports = template;
+
+// Clear cache of format objects.
+// This can *break* prior template functions, so invoke with care!
+template.clearFormatCache = function() {
+  context.formats = [];
+  context.format_map = {};
+};
+
+// Generate property access code for use within template source.
+// object: the name of the object (variable) containing template data
+// property: the property access string, verbatim from template tag
+template.property = function(object, property) {
+  var src = util.field(property).map(util.str).join('][');
+  return object + '[' + src + ']';
+};
+
+// Generate source code for a template function.
+// text: the template text
+// variable: the name of the data object variable ('obj' by default)
+// properties: optional hash for collecting all accessed properties
+function source(text, variable, properties) {
+  variable = variable || 'obj';
+  var index = 0;
+  var src = '\'';
+  var regex = template_re;
+
+  // Compile the template source, escaping string literals appropriately.
+  text.replace(regex, function(match, interpolate, offset) {
+    src += text
+      .slice(index, offset)
+      .replace(template_escaper, template_escapeChar);
+    index = offset + match.length;
+
+    if (interpolate) {
+      src += '\'\n+((__t=(' +
+        template_var(interpolate, variable, properties) +
+        '))==null?\'\':__t)+\n\'';
+    }
+
+    // Adobe VMs need the match returned to produce the correct offest.
+    return match;
+  });
+  return src + '\'';
+}
+
+function template_var(text, variable, properties) {
+  var filters = text.match(filter_re);
+  var prop = filters.shift().trim();
+  var stringCast = true;
+
+  function strcall(fn) {
+    fn = fn || '';
+    if (stringCast) {
+      stringCast = false;
+      src = 'String(' + src + ')' + fn;
+    } else {
+      src += fn;
+    }
+    return src;
+  }
+
+  function date() {
+    return '(typeof ' + src + '==="number"?new Date('+src+'):'+src+')';
+  }
+
+  function number_format(fmt, key) {
+    a = template_format(args[0], key, fmt);
+    stringCast = false;
+    src = 'this.formats['+a+']('+src+')';
+  }
+  
+  function time_format(fmt, key) {
+    a = template_format(args[0], key, fmt);
+    stringCast = false;
+    src = 'this.formats['+a+']('+date()+')';
+  }
+
+  if (properties) properties[prop] = 1;
+  var src = template.property(variable, prop);
+
+  for (var i=0; i<filters.length; ++i) {
+    var f = filters[i], args = null, pidx, a, b;
+
+    if ((pidx=f.indexOf(':')) > 0) {
+      f = f.slice(0, pidx);
+      args = filters[i].slice(pidx+1)
+        .match(args_re)
+        .map(function(s) { return s.trim(); });
+    }
+    f = f.trim();
+
+    switch (f) {
+      case 'length':
+        strcall('.length');
+        break;
+      case 'lower':
+        strcall('.toLowerCase()');
+        break;
+      case 'upper':
+        strcall('.toUpperCase()');
+        break;
+      case 'lower-locale':
+        strcall('.toLocaleLowerCase()');
+        break;
+      case 'upper-locale':
+        strcall('.toLocaleUpperCase()');
+        break;
+      case 'trim':
+        strcall('.trim()');
+        break;
+      case 'left':
+        a = util.number(args[0]);
+        strcall('.slice(0,' + a + ')');
+        break;
+      case 'right':
+        a = util.number(args[0]);
+        strcall('.slice(-' + a +')');
+        break;
+      case 'mid':
+        a = util.number(args[0]);
+        b = a + util.number(args[1]);
+        strcall('.slice(+'+a+','+b+')');
+        break;
+      case 'slice':
+        a = util.number(args[0]);
+        strcall('.slice('+ a +
+          (args.length > 1 ? ',' + util.number(args[1]) : '') +
+          ')');
+        break;
+      case 'truncate':
+        a = util.number(args[0]);
+        b = args[1];
+        b = (b!=='left' && b!=='middle' && b!=='center') ? 'right' : b;
+        src = 'this.truncate(' + strcall() + ',' + a + ',\'' + b + '\')';
+        break;
+      case 'pad':
+        a = util.number(args[0]);
+        b = args[1];
+        b = (b!=='left' && b!=='middle' && b!=='center') ? 'right' : b;
+        src = 'this.pad(' + strcall() + ',' + a + ',\'' + b + '\')';
+        break;
+      case 'number':
+        number_format(format.number, 'number');
+        break;
+      case 'time':
+        time_format(format.time, 'time');
+        break;
+      case 'time-utc':
+        time_format(format.utc, 'time-utc');
+        break;
+      default:
+        throw Error('Unrecognized template filter: ' + f);
+    }
+  }
+
+  return src;
+}
+
+var template_re = /\{\{(.+?)\}\}|$/g,
+    filter_re = /(?:"[^"]*"|\'[^\']*\'|[^\|"]+|[^\|\']+)+/g,
+    args_re = /(?:"[^"]*"|\'[^\']*\'|[^,"]+|[^,\']+)+/g;
+
+// Certain characters need to be escaped so that they can be put into a
+// string literal.
+var template_escapes = {
+  '\'':     '\'',
+  '\\':     '\\',
+  '\r':     'r',
+  '\n':     'n',
+  '\u2028': 'u2028',
+  '\u2029': 'u2029'
+};
+
+var template_escaper = /\\|'|\r|\n|\u2028|\u2029/g;
+
+function template_escapeChar(match) {
+  return '\\' + template_escapes[match];
+}
+
+function template_format(pattern, key, fmt) {
+  if ((pattern[0] === '\'' && pattern[pattern.length-1] === '\'') ||
+      (pattern[0] === '"'  && pattern[pattern.length-1] === '"')) {
+    pattern = pattern.slice(1, -1);
+  } else {
+    throw Error('Format pattern must be quoted: ' + pattern);
+  }
+  key = key + ':' + pattern;
+  if (!context.format_map[key]) {
+    var f = fmt(pattern);
+    var i = context.formats.length;
+    context.formats.push(f);
+    context.format_map[key] = i;
+  }
+  return context.format_map[key];
+}
+
+},{"./format":13,"./util":29}],28:[function(require,module,exports){
+var d3_time = require('d3-time');
+
+var tempDate = new Date(),
+    baseDate = new Date(0, 0, 1).setFullYear(0), // Jan 1, 0 AD
+    utcBaseDate = new Date(Date.UTC(0, 0, 1)).setUTCFullYear(0);
+
+function date(d) {
+  return (tempDate.setTime(+d), tempDate);
+}
+
+// create a time unit entry
+function entry(type, date, unit, step, min, max) {
+  var e = {
+    type: type,
+    date: date,
+    unit: unit
+  };
+  if (step) {
+    e.step = step;
+  } else {
+    e.minstep = 1;
+  }
+  if (min != null) e.min = min;
+  if (max != null) e.max = max;
+  return e;
+}
+
+function create(type, unit, base, step, min, max) {
+  return entry(type,
+    function(d) { return unit.offset(base, d); },
+    function(d) { return unit.count(base, d); },
+    step, min, max);
+}
+
+var locale = [
+  create('second', d3_time.second, baseDate),
+  create('minute', d3_time.minute, baseDate),
+  create('hour',   d3_time.hour,   baseDate),
+  create('day',    d3_time.day,    baseDate, [1, 7]),
+  create('month',  d3_time.month,  baseDate, [1, 3, 6]),
+  create('year',   d3_time.year,   baseDate),
+
+  // periodic units
+  entry('seconds',
+    function(d) { return new Date(1970, 0, 1, 0, 0, d); },
+    function(d) { return date(d).getSeconds(); },
+    null, 0, 59
+  ),
+  entry('minutes',
+    function(d) { return new Date(1970, 0, 1, 0, d); },
+    function(d) { return date(d).getMinutes(); },
+    null, 0, 59
+  ),
+  entry('hours',
+    function(d) { return new Date(1970, 0, 1, d); },
+    function(d) { return date(d).getHours(); },
+    null, 0, 23
+  ),
+  entry('weekdays',
+    function(d) { return new Date(1970, 0, 4+d); },
+    function(d) { return date(d).getDay(); },
+    [1], 0, 6
+  ),
+  entry('dates',
+    function(d) { return new Date(1970, 0, d); },
+    function(d) { return date(d).getDate(); },
+    [1], 1, 31
+  ),
+  entry('months',
+    function(d) { return new Date(1970, d % 12, 1); },
+    function(d) { return date(d).getMonth(); },
+    [1], 0, 11
+  )
+];
+
+var utc = [
+  create('second', d3_time.utcSecond, utcBaseDate),
+  create('minute', d3_time.utcMinute, utcBaseDate),
+  create('hour',   d3_time.utcHour,   utcBaseDate),
+  create('day',    d3_time.utcDay,    utcBaseDate, [1, 7]),
+  create('month',  d3_time.utcMonth,  utcBaseDate, [1, 3, 6]),
+  create('year',   d3_time.utcYear,   utcBaseDate),
+
+  // periodic units
+  entry('seconds',
+    function(d) { return new Date(Date.UTC(1970, 0, 1, 0, 0, d)); },
+    function(d) { return date(d).getUTCSeconds(); },
+    null, 0, 59
+  ),
+  entry('minutes',
+    function(d) { return new Date(Date.UTC(1970, 0, 1, 0, d)); },
+    function(d) { return date(d).getUTCMinutes(); },
+    null, 0, 59
+  ),
+  entry('hours',
+    function(d) { return new Date(Date.UTC(1970, 0, 1, d)); },
+    function(d) { return date(d).getUTCHours(); },
+    null, 0, 23
+  ),
+  entry('weekdays',
+    function(d) { return new Date(Date.UTC(1970, 0, 4+d)); },
+    function(d) { return date(d).getUTCDay(); },
+    [1], 0, 6
+  ),
+  entry('dates',
+    function(d) { return new Date(Date.UTC(1970, 0, d)); },
+    function(d) { return date(d).getUTCDate(); },
+    [1], 1, 31
+  ),
+  entry('months',
+    function(d) { return new Date(Date.UTC(1970, d % 12, 1)); },
+    function(d) { return date(d).getUTCMonth(); },
+    [1], 0, 11
+  )
+];
+
+var STEPS = [
+  [31536e6, 5],  // 1-year
+  [7776e6, 4],   // 3-month
+  [2592e6, 4],   // 1-month
+  [12096e5, 3],  // 2-week
+  [6048e5, 3],   // 1-week
+  [1728e5, 3],   // 2-day
+  [864e5, 3],    // 1-day
+  [432e5, 2],    // 12-hour
+  [216e5, 2],    // 6-hour
+  [108e5, 2],    // 3-hour
+  [36e5, 2],     // 1-hour
+  [18e5, 1],     // 30-minute
+  [9e5, 1],      // 15-minute
+  [3e5, 1],      // 5-minute
+  [6e4, 1],      // 1-minute
+  [3e4, 0],      // 30-second
+  [15e3, 0],     // 15-second
+  [5e3, 0],      // 5-second
+  [1e3, 0]       // 1-second
+];
+
+function find(units, span, minb, maxb) {
+  var step = STEPS[0], i, n, bins;
+
+  for (i=1, n=STEPS.length; i<n; ++i) {
+    step = STEPS[i];
+    if (span > step[0]) {
+      bins = span / step[0];
+      if (bins > maxb) {
+        return units[STEPS[i-1][1]];
+      }
+      if (bins >= minb) {
+        return units[step[1]];
+      }
+    }
+  }
+  return units[STEPS[n-1][1]];
+}
+
+function toUnitMap(units) {
+  var map = {}, i, n;
+  for (i=0, n=units.length; i<n; ++i) {
+    map[units[i].type] = units[i];
+  }
+  map.find = function(span, minb, maxb) {
+    return find(units, span, minb, maxb);
+  };
+  return map;
+}
+
+module.exports = toUnitMap(locale);
+module.exports.utc = toUnitMap(utc);
+
+},{"d3-time":6}],29:[function(require,module,exports){
+var buffer = require('buffer'),
+    time = require('./time'),
+    utc = time.utc;
+
+var u = module.exports = {};
+
+// utility functions
+
+var FNAME = '__name__';
+
+u.namedfunc = function(name, f) { return (f[FNAME] = name, f); };
+
+u.name = function(f) { return f==null ? null : f[FNAME]; };
+
+u.identity = function(x) { return x; };
+
+u.true = u.namedfunc('true', function() { return true; });
+
+u.false = u.namedfunc('false', function() { return false; });
+
+u.duplicate = function(obj) {
+  return JSON.parse(JSON.stringify(obj));
+};
+
+u.equal = function(a, b) {
+  return JSON.stringify(a) === JSON.stringify(b);
+};
+
+u.extend = function(obj) {
+  for (var x, name, i=1, len=arguments.length; i<len; ++i) {
+    x = arguments[i];
+    for (name in x) { obj[name] = x[name]; }
+  }
+  return obj;
+};
+
+u.length = function(x) {
+  return x != null && x.length != null ? x.length : null;
+};
+
+u.keys = function(x) {
+  var keys = [], k;
+  for (k in x) keys.push(k);
+  return keys;
+};
+
+u.vals = function(x) {
+  var vals = [], k;
+  for (k in x) vals.push(x[k]);
+  return vals;
+};
+
+u.toMap = function(list, f) {
+  return (f = u.$(f)) ?
+    list.reduce(function(obj, x) { return (obj[f(x)] = 1, obj); }, {}) :
+    list.reduce(function(obj, x) { return (obj[x] = 1, obj); }, {});
+};
+
+u.keystr = function(values) {
+  // use to ensure consistent key generation across modules
+  var n = values.length;
+  if (!n) return '';
+  for (var s=String(values[0]), i=1; i<n; ++i) {
+    s += '|' + String(values[i]);
+  }
+  return s;
+};
+
+// type checking functions
+
+var toString = Object.prototype.toString;
+
+u.isObject = function(obj) {
+  return obj === Object(obj);
+};
+
+u.isFunction = function(obj) {
+  return toString.call(obj) === '[object Function]';
+};
+
+u.isString = function(obj) {
+  return typeof value === 'string' || toString.call(obj) === '[object String]';
+};
+
+u.isArray = Array.isArray || function(obj) {
+  return toString.call(obj) === '[object Array]';
+};
+
+u.isNumber = function(obj) {
+  return typeof obj === 'number' || toString.call(obj) === '[object Number]';
+};
+
+u.isBoolean = function(obj) {
+  return obj === true || obj === false || toString.call(obj) == '[object Boolean]';
+};
+
+u.isDate = function(obj) {
+  return toString.call(obj) === '[object Date]';
+};
+
+u.isValid = function(obj) {
+  return obj != null && obj === obj;
+};
+
+u.isBuffer = (buffer.Buffer && buffer.Buffer.isBuffer) || u.false;
+
+// type coercion functions
+
+u.number = function(s) {
+  return s == null || s === '' ? null : +s;
+};
+
+u.boolean = function(s) {
+  return s == null || s === '' ? null : s==='false' ? false : !!s;
+};
+
+u.date = function(s) {
+  return s == null || s === '' ? null : Date.parse(s);
+};
+
+u.array = function(x) {
+  return x != null ? (u.isArray(x) ? x : [x]) : [];
+};
+
+u.str = function(x) {
+  return u.isArray(x) ? '[' + x.map(u.str) + ']'
+    : u.isObject(x) ? JSON.stringify(x)
+    : u.isString(x) ? ('\''+util_escape_str(x)+'\'') : x;
+};
+
+var escape_str_re = /(^|[^\\])'/g;
+
+function util_escape_str(x) {
+  return x.replace(escape_str_re, '$1\\\'');
+}
+
+// data access functions
+
+var field_re = /\[(.*?)\]|[^.\[]+/g;
+
+u.field = function(f) {
+  return String(f).match(field_re).map(function(d) {
+    return d[0] !== '[' ? d :
+      d[1] !== "'" && d[1] !== '"' ? d.slice(1, -1) :
+      d.slice(2, -2).replace(/\\(["'])/g, '$1');
+  });
+};
+
+u.accessor = function(f) {
+  var s;
+  return f==null || u.isFunction(f) ? f :
+    u.namedfunc(f, (s = u.field(f)).length > 1 ?
+      function(x) { return s.reduce(function(x,f) { return x[f]; }, x); } :
+      function(x) { return x[f]; }
+    );
+};
+
+// short-cut for accessor
+u.$ = u.accessor;
+
+u.mutator = function(f) {
+  var s;
+  return u.isString(f) && (s=u.field(f)).length > 1 ?
+    function(x, v) {
+      for (var i=0; i<s.length-1; ++i) x = x[s[i]];
+      x[s[i]] = v;
+    } :
+    function(x, v) { x[f] = v; };
+};
+
+
+u.$func = function(name, op) {
+  return function(f) {
+    f = u.$(f) || u.identity;
+    var n = name + (u.name(f) ? '_'+u.name(f) : '');
+    return u.namedfunc(n, function(d) { return op(f(d)); });
+  };
+};
+
+u.$valid  = u.$func('valid', u.isValid);
+u.$length = u.$func('length', u.length);
+
+u.$in = function(f, values) {
+  f = u.$(f);
+  var map = u.isArray(values) ? u.toMap(values) : values;
+  return function(d) { return !!map[f(d)]; };
+};
+
+u.$year   = u.$func('year', time.year.unit);
+u.$month  = u.$func('month', time.months.unit);
+u.$date   = u.$func('date', time.dates.unit);
+u.$day    = u.$func('day', time.weekdays.unit);
+u.$hour   = u.$func('hour', time.hours.unit);
+u.$minute = u.$func('minute', time.minutes.unit);
+u.$second = u.$func('second', time.seconds.unit);
+
+u.$utcYear   = u.$func('utcYear', utc.year.unit);
+u.$utcMonth  = u.$func('utcMonth', utc.months.unit);
+u.$utcDate   = u.$func('utcDate', utc.dates.unit);
+u.$utcDay    = u.$func('utcDay', utc.weekdays.unit);
+u.$utcHour   = u.$func('utcHour', utc.hours.unit);
+u.$utcMinute = u.$func('utcMinute', utc.minutes.unit);
+u.$utcSecond = u.$func('utcSecond', utc.seconds.unit);
+
+// comparison / sorting functions
+
+u.comparator = function(sort) {
+  var sign = [];
+  if (sort === undefined) sort = [];
+  sort = u.array(sort).map(function(f) {
+    var s = 1;
+    if      (f[0] === '-') { s = -1; f = f.slice(1); }
+    else if (f[0] === '+') { s = +1; f = f.slice(1); }
+    sign.push(s);
+    return u.accessor(f);
+  });
+  return function(a,b) {
+    var i, n, f, x, y;
+    for (i=0, n=sort.length; i<n; ++i) {
+      f = sort[i]; x = f(a); y = f(b);
+      if (x < y) return -1 * sign[i];
+      if (x > y) return sign[i];
+    }
+    return 0;
+  };
+};
+
+u.cmp = function(a, b) {
+  if (a < b) {
+    return -1;
+  } else if (a > b) {
+    return 1;
+  } else if (a >= b) {
+    return 0;
+  } else if (a === null) {
+    return -1;
+  } else if (b === null) {
+    return 1;
+  }
+  return NaN;
+};
+
+u.numcmp = function(a, b) { return a - b; };
+
+u.stablesort = function(array, sortBy, keyFn) {
+  var indices = array.reduce(function(idx, v, i) {
+    return (idx[keyFn(v)] = i, idx);
+  }, {});
+
+  array.sort(function(a, b) {
+    var sa = sortBy(a),
+        sb = sortBy(b);
+    return sa < sb ? -1 : sa > sb ? 1
+         : (indices[keyFn(a)] - indices[keyFn(b)]);
+  });
+
+  return array;
+};
+
+
+// string functions
+
+u.pad = function(s, length, pos, padchar) {
+  padchar = padchar || " ";
+  var d = length - s.length;
+  if (d <= 0) return s;
+  switch (pos) {
+    case 'left':
+      return strrep(d, padchar) + s;
+    case 'middle':
+    case 'center':
+      return strrep(Math.floor(d/2), padchar) +
+         s + strrep(Math.ceil(d/2), padchar);
+    default:
+      return s + strrep(d, padchar);
+  }
+};
+
+function strrep(n, str) {
+  var s = "", i;
+  for (i=0; i<n; ++i) s += str;
+  return s;
+}
+
+u.truncate = function(s, length, pos, word, ellipsis) {
+  var len = s.length;
+  if (len <= length) return s;
+  ellipsis = ellipsis !== undefined ? String(ellipsis) : '\u2026';
+  var l = Math.max(0, length - ellipsis.length);
+
+  switch (pos) {
+    case 'left':
+      return ellipsis + (word ? truncateOnWord(s,l,1) : s.slice(len-l));
+    case 'middle':
+    case 'center':
+      var l1 = Math.ceil(l/2), l2 = Math.floor(l/2);
+      return (word ? truncateOnWord(s,l1) : s.slice(0,l1)) +
+        ellipsis + (word ? truncateOnWord(s,l2,1) : s.slice(len-l2));
+    default:
+      return (word ? truncateOnWord(s,l) : s.slice(0,l)) + ellipsis;
+  }
+};
+
+function truncateOnWord(s, len, rev) {
+  var cnt = 0, tok = s.split(truncate_word_re);
+  if (rev) {
+    s = (tok = tok.reverse())
+      .filter(function(w) { cnt += w.length; return cnt <= len; })
+      .reverse();
+  } else {
+    s = tok.filter(function(w) { cnt += w.length; return cnt <= len; });
+  }
+  return s.length ? s.join('').trim() : tok[0].slice(0, len);
+}
+
+var truncate_word_re = /([\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u202F\u205F\u2028\u2029\u3000\uFEFF])/;
+
+},{"./time":28,"buffer":2}],30:[function(require,module,exports){
+var DEPS = require('./Dependencies').ALL;
+
+function create(cs, reflow) {
+  var out = {};
+  copy(cs, out);
+
+  out.add = [];
+  out.mod = [];
+  out.rem = [];
+
+  out.reflow = reflow;
+
+  return out;
+}
+
+function copy(a, b) {
+  b.stamp = a ? a.stamp : 0;
+  b.sort  = a ? a.sort  : null;
+  b.facet = a ? a.facet : null;
+  b.trans = a ? a.trans : null;
+  b.dirty = a ? a.dirty : [];
+  b.request = a ? a.request : null;
+  for (var d, i=0, n=DEPS.length; i<n; ++i) {
+    b[d=DEPS[i]] = a ? a[d] : {};
+  }
+}
+
+module.exports = {
+  create: create,
+  copy: copy
+};
+},{"./Dependencies":33}],31:[function(require,module,exports){
+var log = require('vega-logging'),
+    Tuple = require('./Tuple'),
+    Base = require('./Node').prototype;
+
+function Collector(graph) {
+  Base.init.call(this, graph);
+  this._data = [];
+  this.router(true).collector(true);
+}
+
+var prototype = (Collector.prototype = Object.create(Base));
+prototype.constructor = Collector;
+
+prototype.data = function() {
+  return this._data;
+};
+
+prototype.evaluate = function(input) {
+  log.debug(input, ["collecting"]);
+
+  if (input.rem.length) {
+    this._data = Tuple.idFilter(this._data, input.rem);
+  }
+
+  if (input.add.length) {
+    this._data = this._data.length ? this._data.concat(input.add) : input.add;
+  }
+
+  if (input.sort) {
+    this._data.sort(input.sort);
+  }
+
+  if (input.reflow) {
+    input.mod = input.mod.concat(
+      Tuple.idFilter(this._data, input.add, input.mod, input.rem));
+    input.reflow = false;
+  }
+
+  return input;
+};
+
+module.exports = Collector;
+},{"./Node":36,"./Tuple":38,"vega-logging":45}],32:[function(require,module,exports){
+var log = require('vega-logging'),
+    ChangeSet = require('./ChangeSet'), 
+    Collector = require('./Collector'),
+    Tuple = require('./Tuple'),
+    Node = require('./Node'); // jshint ignore:line
+
+function DataSource(graph, name, facet) {
+  this._graph = graph;
+  this._name = name;
+  this._data = [];
+  this._source = null;
+  this._facet  = facet;
+  this._input  = ChangeSet.create();
+  this._output = null; // Output changeset
+
+  this._inputNode  = null;
+  this._outputNode = null;
+  this._pipeline  = null; // Pipeline of transformations.
+  this._collector = null; // Collector to materialize output of pipeline.
+  this._mutates = false;  // Does any pipeline operator mutate tuples?
+}
+
+var prototype = DataSource.prototype;
+
+prototype.name = function(name) {
+  if (!arguments.length) return this._name;
+  return (this._name = name, this);
+};
+
+prototype.source = function(src) {
+  if (!arguments.length) return this._source;
+  return (this._source = this._graph.data(src));
+};
+
+prototype.insert = function(tuples) {
+  this._input.add = this._input.add.concat(tuples.map(Tuple.ingest));
+  return this;
+};
+
+prototype.remove = function(where) {
+  var remove = this._data.filter(where);
+  this._input.rem = this._input.rem.concat(remove);
+  return this;
+};
+
+prototype.update = function(where, field, func) {
+  var mod = this._input.mod,
+      ids = Tuple.idMap(mod);
+
+  this._input.fields[field] = 1;
+
+  this._data.filter(where).forEach(function(x) {
+    var prev = x[field],
+        next = func(x);
+    if (prev !== next) {
+      Tuple.set(x, field, next);
+      if (ids[x._id] !== 1) {
+        mod.push(x);
+        ids[x._id] = 1;
+      }
+    }
+  });
+
+  return this;
+};
+
+prototype.values = function(data) {
+  if (!arguments.length) return this._collector.data();
+
+  // Replace backing data
+  this._input.rem = this._data.slice();
+  if (data) { this.insert(data); }
+  return this;
+};
+
+prototype.mutates = function(m) {
+  if (!arguments.length) return this._mutates;
+  this._mutates = this._mutates || m;
+  return this;
+};
+
+prototype.last = function() {
+  return this._output;
+};
+
+prototype.fire = function(input) {
+  if (input) this._input = input;
+  this._graph.propagate(this._input, this._pipeline[0]);
+  return this;
+};
+
+prototype.pipeline = function(pipeline) {
+  if (!arguments.length) return this._pipeline;
+
+  var graph = this._graph,
+      status;
+
+  pipeline.unshift(this._inputNode = DataSourceInput(this));
+  status = graph.preprocess(pipeline);
+
+  if (status.router) {
+    pipeline.push(status.collector = new Collector(graph));
+  }
+
+  pipeline.push(this._outputNode = DataSourceOutput(this));
+  this._collector = status.collector;
+  this._mutates = !!status.mutates;
+  graph.connect(this._pipeline = pipeline);
+
+  return this;
+};
+
+prototype.synchronize = function() {
+  this._graph.synchronize(this._pipeline);
+  return this;
+};
+
+prototype.listener = function() { 
+  return DataSourceListener(this).addListener(this._inputNode);
+};
+
+prototype.addListener = function(l) {
+  if (l instanceof DataSource) {
+    this._collector.addListener(l.listener());
+  } else {
+    this._outputNode.addListener(l);      
+  }
+  return this;
+};
+
+prototype.removeListener = function(l) {
+  this._outputNode.removeListener(l);
+};
+
+prototype.listeners = function(ds) {
+  return (ds ? this._collector : this._outputNode).listeners();
+};
+
+// Input node applies the datasource's delta, and propagates it to 
+// the rest of the pipeline. It receives touches to reflow data.
+function DataSourceInput(ds) {
+  var input = new Node(ds._graph)
+    .router(true)
+    .collector(true);
+
+  input.data = function() {
+    return ds._data;
+  };
+
+  input.evaluate = function(input) {
+    log.debug(input, ['input', ds._name]);
+
+    var delta = ds._input, 
+        out = ChangeSet.create(input), f;
+
+    // Delta might contain fields updated through API
+    for (f in delta.fields) {
+      out.fields[f] = 1;
+    }
+
+    // update data
+    if (delta.rem.length) {
+      ds._data = Tuple.idFilter(ds._data, delta.rem);
+    }
+
+    if (delta.add.length) {
+      ds._data = ds._data.concat(delta.add);
+    }
+
+    // if reflowing, add any other tuples not currently in changeset
+    if (input.reflow) {
+      delta.mod = delta.mod.concat(
+        Tuple.idFilter(ds._data, delta.add, delta.mod, delta.rem));
+    }
+
+    // reset change list
+    ds._input = ChangeSet.create();
+
+    out.add = delta.add; 
+    out.mod = delta.mod;
+    out.rem = delta.rem;
+    out.facet = ds._facet;
+    return out;
+  };
+
+  return input;
+}
+
+// Output node captures the last changeset seen by this datasource
+// (needed for joins and builds) and materializes any nested data.
+// If this datasource is faceted, materializes the values in the facet.
+function DataSourceOutput(ds) {
+  var output = new Node(ds._graph)
+    .router(true)
+    .reflows(true)
+    .collector(true);
+
+  output.data = function() {
+    return ds._collector ? ds._collector.data() : ds._data;
+  };
+
+  output.evaluate = function(input) {
+    log.debug(input, ['output', ds._name]);
+
+    var out = ChangeSet.create(input, true);
+
+    if (ds._facet) {
+      ds._facet.values = ds.values();
+      input.facet = null;
+    }
+
+    ds._output = input;
+    out.data[ds._name] = 1;
+    return out;
+  };
+
+  return output;
+}
+
+function DataSourceListener(ds) {
+  var l = new Node(ds._graph).router(true);
+
+  l.evaluate = function(input) {
+    // Tuple derivation carries a cost. So only derive if the pipeline has
+    // operators that mutate, and thus would override the source data.
+    if (ds.mutates()) {  
+      var map = ds._srcMap || (ds._srcMap = {}), // to propagate tuples correctly
+          output = ChangeSet.create(input);
+
+      output.add = input.add.map(function(t) {
+        return (map[t._id] = Tuple.derive(t));
+      });
+
+      output.mod = input.mod.map(function(t) {
+        return Tuple.rederive(t, map[t._id]);
+      });
+
+      output.rem = input.rem.map(function(t) { 
+        var o = map[t._id];
+        return (map[t._id] = null, o);
+      });
+
+      return (ds._input = output);
+    } else {
+      return (ds._input = input);
+    }
+  };
+
+  return l;
+}
+
+module.exports = DataSource;
+
+},{"./ChangeSet":30,"./Collector":31,"./Node":36,"./Tuple":38,"vega-logging":45}],33:[function(require,module,exports){
+var deps = module.exports = {
+  ALL: ['data', 'fields', 'scales', 'signals']
+};
+deps.ALL.forEach(function(k) { deps[k.toUpperCase()] = k; });
+
+},{}],34:[function(require,module,exports){
+var dl = require('datalib'),
+    Heap = require('./Heap'),
+    ChangeSet = require('./ChangeSet'),
+    DataSource = require('./DataSource'),
+    Collector = require('./Collector'),
+    Tuple = require('./Tuple'),
+    Signal = require('./Signal'),
+    Deps = require('./Dependencies');
+
+function Graph() {
+}
+
+var prototype = Graph.prototype;
+
+prototype.init = function() {
+  this._stamp = 0;
+  this._rank  = 0;
+
+  this._data = {};
+  this._signals = {};
+
+  this.doNotPropagate = {};
+};
+
+prototype.rank = function() {
+  return ++this._rank;
+};
+
+prototype.values = function(type, names, hash) {
+  var data = (type === Deps.SIGNALS ? this._signals : this._data),
+      n = (names !== undefined ? names : dl.keys(data)),
+      vals, i;
+
+  if (Array.isArray(n)) {
+    vals = hash || {};
+    for (i=0; i<n.length; ++i) {
+      vals[n[i]] = data[n[i]].values();
+    }
+    return vals;
+  } else {
+    return data[n].values();
+  }
+};
+
+// Retain for backwards-compatibility
+prototype.dataValues = function(names) {
+  return this.values(Deps.DATA, names);
+};
+
+// Retain for backwards-compatibility
+prototype.signalValues = function(names) {
+  return this.values(Deps.SIGNALS, names);
+};
+
+prototype.data = function(name, pipeline, facet) {
+  var db = this._data;
+  if (!arguments.length) {
+    var all = [], key;
+    for (key in db) { all.push(db[key]); }
+    return all;
+  } else if (arguments.length === 1) {
+    return db[name];
+  } else {
+    return (db[name] = new DataSource(this, name, facet).pipeline(pipeline));
+  }
+};
+
+prototype.signal = function(name, init) {
+  if (arguments.length === 1) {
+    var m = this;
+    return Array.isArray(name) ?
+      name.map(function(n) { return m._signals[n]; }) :
+      this._signals[name];
+  } else {
+    return (this._signals[name] = new Signal(this, name, init));
+  }
+};
+
+prototype.signalRef = function(ref) {
+  if (!Array.isArray(ref)) {
+    ref = dl.field(ref);
+  }
+
+  var value = this.signal(ref[0]).value();
+  if (ref.length > 1) {
+    for (var i=1, n=ref.length; i<n; ++i) {
+      value = value[ref[i]];
+    }
+  }
+  return value;
+};
+
+// Stamp should be specified with caution. It is necessary for inline datasources,
+// which need to be populated during the same cycle even though propagation has
+// passed that part of the dataflow graph.  
+prototype.propagate = function(pulse, node, stamp) {
+  var pulses = {},
+      listeners, next, nplse, tpls, ntpls, i, len;
+
+  // new PQ with each propagation cycle so that we can pulse branches
+  // of the dataflow graph during a propagation (e.g., when creating
+  // a new inline datasource).
+  var pq = new Heap(function(a, b) {
+    // Sort on qrank (queue-rank).
+    // Rank can change during propagation due to rewiring.
+    return a._qrank - b._qrank;
+  });
+
+  if (pulse.stamp) throw Error('Pulse already has a non-zero stamp.');
+
+  pulse.stamp = stamp || ++this._stamp;
+  pulses[node._id] = pulse;
+  pq.push(node.qrank(true));
+
+  while (pq.size() > 0) {
+    node  = pq.peek();
+    pulse = pulses[node._id];
+
+    if (node.rank() !== node.qrank()) {
+      // A node's rank might change during a propagation. Re-queue if so.
+      pq.replace(node.qrank(true));
+    } else {
+      // Evaluate node and propagate pulse.
+      pq.pop();
+      pulses[node._id] = null;
+      listeners = node._listeners;
+      pulse = this.evaluate(pulse, node);
+
+      // Propagate the pulse. 
+      if (pulse !== this.doNotPropagate) {
+        // Ensure reflow pulses always send reflow pulses even if skipped.
+        if (!pulse.reflow && node.reflows()) {
+          pulse = ChangeSet.create(pulse, true);
+        }
+
+        for (i=0, len=listeners.length; i<len; ++i) {
+          next = listeners[i];
+
+          if ((nplse = pulses[next._id]) !== undefined) {
+            if (nplse === null) throw Error('Already propagated to node.');
+            if (nplse === pulse) continue;  // Re-queueing the same pulse.
+
+            // We've already queued this node. Ensure there should be at most one
+            // pulse with tuples (add/mod/rem), and the remainder will be reflows. 
+            tpls  = pulse.add.length || pulse.mod.length || pulse.rem.length;
+            ntpls = nplse.add.length || nplse.mod.length || nplse.rem.length;
+
+            if (tpls && ntpls) throw Error('Multiple changeset pulses to same node');
+
+            // Combine reflow and tuples into a single pulse. 
+            pulses[next._id] = tpls ? pulse : nplse;
+            pulses[next._id].reflow = pulse.reflow || nplse.reflow;
+          } else {
+            // First time we're seeing this node, queue it for propagation.
+            pq.push(next.qrank(true));
+            pulses[next._id] = pulse;
+          }
+        }
+      }
+    }
+  }
+};
+
+// Process a new branch of the dataflow graph prior to connection:
+// (1) Insert new Collector nodes as needed. 
+// (2) Track + return mutation/routing status of the branch.
+prototype.preprocess = function(branch) {
+  var graph = this,
+      mutates = 0,
+      node, router, collector, collects;
+
+  for (var i=0; i<branch.length; ++i) {
+    node = branch[i];
+
+    // Batch nodes need access to a materialized dataset. 
+    if (node.batch() && !node._collector) {
+      if (router || !collector) {
+        node = new Collector(graph);
+        branch.splice(i, 0, node);
+        router = false;
+      } else {
+        node._collector = collector;
+      }
+    }
+
+    if ((collects = node.collector())) collector = node;
+    router  = router  || node.router() && !collects;
+    mutates = mutates || node.mutates();
+
+    // A collector needs to be inserted after tuple-producing
+    // nodes for correct previous value tracking.
+    if (node.produces()) {
+      branch.splice(i+1, 0, new Collector(graph));
+      router = false;
+    }
+  }
+
+  return {router: router, collector: collector, mutates: mutates};
+};
+
+prototype.connect = function(branch) {
+  var collector, node, data, signals, i, n, j, m;
+
+  // connect the pipeline
+  for (i=0, n=branch.length; i<n; ++i) {
+    node = branch[i];
+    if (node.collector()) collector = node;
+
+    data = node.dependency(Deps.DATA);
+    for (j=0, m=data.length; j<m; ++j) {
+      this.data(data[j]).addListener(collector);
+    }
+
+    signals = node.dependency(Deps.SIGNALS);
+    for (j=0, m=signals.length; j<m; ++j) {
+      this.signal(signals[j]).addListener(collector);
+    }
+
+    if (i > 0) branch[i-1].addListener(node);
+  }
+
+  return branch;
+};
+
+prototype.disconnect = function(branch) {
+  var collector, node, data, signals, i, n, j, m;
+
+  for (i=0, n=branch.length; i<n; ++i) {
+    node = branch[i];
+    if (node.collector()) collector = node;
+
+    data = node.dependency(Deps.DATA);
+    for (j=0, m=data.length; j<m; ++j) {
+      this.data(data[j]).removeListener(collector);
+    }
+
+    signals = node.dependency(Deps.SIGNALS);
+    for (j=0, m=signals.length; j<m; ++j) {
+      this.signal(signals[j]).removeListener(collector);
+    }
+
+    node.disconnect();
+  }
+
+  return branch;
+};
+
+prototype.synchronize = function(branch) {
+  var ids = {},
+      node, data, i, n, j, m, d, id;
+
+  for (i=0, n=branch.length; i<n; ++i) {
+    node = branch[i];
+    if (!node.collector()) continue;
+
+    for (j=0, data=node.data(), m=data.length; j<m; ++j) {
+      id = (d = data[j])._id;
+      if (ids[id]) continue; 
+      Tuple.prev_update(d);
+      ids[id] = 1; 
+    }
+  }
+
+  return this;
+};
+
+prototype.reevaluate = function(pulse, node) {
+  var reflowed = pulse.reflow && node.last() >= pulse.stamp,
+      run = node.router() || pulse.add.length || pulse.rem.length;
+
+  return run || !reflowed || node.reevaluate(pulse);
+};
+
+prototype.evaluate = function(pulse, node) {
+  if (!this.reevaluate(pulse, node)) return pulse;
+  pulse = node.evaluate(pulse);
+  node.last(pulse.stamp);
+  return pulse;
+};
+
+module.exports = Graph;
+
+},{"./ChangeSet":30,"./Collector":31,"./DataSource":32,"./Dependencies":33,"./Heap":35,"./Signal":37,"./Tuple":38,"datalib":24}],35:[function(require,module,exports){
+function Heap(comparator) {
+  this.cmp = comparator;
+  this.nodes = [];
+}
+
+var prototype = Heap.prototype;
+
+prototype.size = function() {
+  return this.nodes.length;
+};
+
+prototype.clear = function() {
+  return (this.nodes = [], this);
+};
+
+prototype.peek = function() {
+  return this.nodes[0];
+};
+
+prototype.push = function(x) {
+  var array = this.nodes;
+  array.push(x);
+  return _siftdown(array, 0, array.length-1, this.cmp);
+};
+
+prototype.pop = function() {
+  var array = this.nodes,
+      last = array.pop(),
+      item;
+
+  if (array.length) {
+    item = array[0];
+    array[0] = last;
+    _siftup(array, 0, this.cmp);
+  } else {
+    item = last;
+  }
+  return item;
+};
+
+prototype.replace = function(item) {
+  var array = this.nodes,
+      retval = array[0];
+  array[0] = item;
+  _siftup(array, 0, this.cmp);
+  return retval;
+};
+
+prototype.pushpop = function(item) {
+  var array = this.nodes, ref = array[0];
+  if (array.length && this.cmp(ref, item) < 0) {
+    array[0] = item;
+    item = ref;
+    _siftup(array, 0, this.cmp);
+  }
+  return item;
+};
+
+function _siftdown(array, start, idx, cmp) {
+  var item, parent, pidx;
+
+  item = array[idx];
+  while (idx > start) {
+    pidx = (idx - 1) >> 1;
+    parent = array[pidx];
+    if (cmp(item, parent) < 0) {
+      array[idx] = parent;
+      idx = pidx;
+      continue;
+    }
+    break;
+  }
+  return (array[idx] = item);
+}
+
+function _siftup(array, idx, cmp) {
+  var start = idx,
+      end = array.length,
+      item = array[idx],
+      cidx = 2 * idx + 1, ridx;
+
+  while (cidx < end) {
+    ridx = cidx + 1;
+    if (ridx < end && cmp(array[cidx], array[ridx]) >= 0) {
+      cidx = ridx;
+    }
+    array[idx] = array[cidx];
+    idx = cidx;
+    cidx = 2 * idx + 1;
+  }
+  array[idx] = item;
+  return _siftdown(array, start, idx, cmp);
+}
+
+module.exports = Heap;
+
+},{}],36:[function(require,module,exports){
+var DEPS = require('./Dependencies').ALL,
+    nodeID = 0;
+
+function Node(graph) {
+  if (graph) this.init(graph);
+}
+
+var Flags = Node.Flags = {
+  Router:     0x01, // Responsible for propagating tuples, cannot be skipped.
+  Collector:  0x02, // Holds a materialized dataset, pulse node to reflow.
+  Produces:   0x04, // Produces new tuples. 
+  Mutates:    0x08, // Sets properties of incoming tuples.
+  Reflows:    0x10, // Forwards a reflow pulse.
+  Batch:      0x20  // Performs batch data processing, needs collector.
+};
+
+var prototype = Node.prototype;
+
+prototype.init = function(graph) {
+  this._id = ++nodeID;
+  this._graph = graph;
+  this._rank  = graph.rank(); // Topological sort by rank
+  this._qrank = null; // Rank when enqueued for propagation
+  this._stamp = 0;    // Last stamp seen
+
+  this._listeners = [];
+  this._listeners._ids = {}; // To prevent duplicate listeners
+
+  // Initialize dependencies.
+  this._deps = {};
+  for (var i=0, n=DEPS.length; i<n; ++i) {
+    this._deps[DEPS[i]] = [];
+  }
+
+  // Initialize status flags.
+  this._flags = 0;
+
+  return this;
+};
+
+prototype.rank = function() {
+  return this._rank;
+};
+
+prototype.qrank = function(/* set */) {
+  if (!arguments.length) return this._qrank;
+  return (this._qrank = this._rank, this);
+};
+
+prototype.last = function(stamp) { 
+  if (!arguments.length) return this._stamp;
+  return (this._stamp = stamp, this);
+};
+
+// -- status flags ---
+
+prototype._setf = function(v, b) {
+  if (b) { this._flags |= v; } else { this._flags &= ~v; }
+  return this;
+};
+
+prototype.router = function(state) {
+  if (!arguments.length) return (this._flags & Flags.Router);
+  return this._setf(Flags.Router, state);
+};
+
+prototype.collector = function(state) {
+  if (!arguments.length) return (this._flags & Flags.Collector);
+  return this._setf(Flags.Collector, state);
+};
+
+prototype.produces = function(state) {
+  if (!arguments.length) return (this._flags & Flags.Produces);
+  return this._setf(Flags.Produces, state);
+};
+
+prototype.mutates = function(state) {
+  if (!arguments.length) return (this._flags & Flags.Mutates);
+  return this._setf(Flags.Mutates, state);
+};
+
+prototype.reflows = function(state) {
+  if (!arguments.length) return (this._flags & Flags.Reflows);
+  return this._setf(Flags.Reflows, state);
+};
+
+prototype.batch = function(state) {
+  if (!arguments.length) return (this._flags & Flags.Batch);
+  return this._setf(Flags.Batch, state);
+};
+
+prototype.dependency = function(type, deps) {
+  var d = this._deps[type],
+      n = d._names || (d._names = {});  // To prevent dupe deps
+
+  // Get dependencies of the given type
+  if (arguments.length === 1) {
+    return d;
+  }
+
+  if (deps === null) {
+    // Clear dependencies of the given type
+    d.splice(0, d.length);
+    d._names = {};
+  } else if (!Array.isArray(deps)) {
+    // Separate this case to avoid cost of array creation
+    if (n[deps]) return this;
+    d.push(deps);
+    n[deps] = 1;
+  } else {
+    for (var i=0, len=deps.length, dep; i<len; ++i) {
+      dep = deps[i];
+      if (n[dep]) continue;
+      d.push(dep);
+      n[dep] = 1;
+    }
+  }
+
+  return this;
+};
+
+prototype.listeners = function() {
+  return this._listeners;
+};
+
+prototype.addListener = function(l) {
+  if (!(l instanceof Node)) {
+    throw Error('Listener is not a Node');
+  }
+  if (this._listeners._ids[l._id]) return this;
+
+  this._listeners.push(l);
+  this._listeners._ids[l._id] = 1;
+  if (this._rank > l._rank) {
+    var q = [l],
+        g = this._graph, cur;
+    while (q.length) {
+      cur = q.shift();
+      cur._rank = g.rank();
+      q.unshift.apply(q, cur.listeners());
+    }
+  }
+
+  return this;
+};
+
+prototype.removeListener = function(l) {
+  if (!this._listeners._ids[l._id]) return false;
+  
+  var idx = this._listeners.indexOf(l),
+      b = idx >= 0;
+
+  if (b) {
+    this._listeners.splice(idx, 1);
+    this._listeners._ids[l._id] = null;
+  }
+  return b;
+};
+
+prototype.disconnect = function() {
+  this._listeners = [];
+  this._listeners._ids = {};
+};
+
+// Evaluate this dataflow node for the current pulse.
+// Subclasses should override to perform custom processing.
+prototype.evaluate = function(pulse) {
+  return pulse;
+};
+
+// Should this node be re-evaluated for the current pulse?
+// Searches pulse to see if any dependencies have updated.
+prototype.reevaluate = function(pulse) {
+  var prop, dep, i, n, j, m;
+
+  for (i=0, n=DEPS.length; i<n; ++i) {
+    prop = DEPS[i];
+    dep = this._deps[prop];
+    for (j=0, m=dep.length; j<m; ++j) {
+      if (pulse[prop][dep[j]]) return true;
+    }
+  }
+
+  return false;
+};
+
+Node.reset = function() { nodeID = 0; };
+
+module.exports = Node;
+
+},{"./Dependencies":33}],37:[function(require,module,exports){
+var ChangeSet = require('./ChangeSet'),
+    Node = require('./Node'), // jshint ignore:line
+    Base = Node.prototype;
+
+function Signal(graph, name, initialValue) {
+  Base.init.call(this, graph);
+  this._name  = name;
+  this._value = initialValue;
+  this._verbose = false; // Verbose signals re-pulse the graph even if prev === val.
+  this._handlers = [];
+  return this;
+}
+
+var prototype = (Signal.prototype = Object.create(Base));
+prototype.constructor = Signal;
+
+prototype.name = function() {
+  return this._name;
+};
+
+prototype.value = function(val) {
+  if (!arguments.length) return this._value;
+  return (this._value = val, this);
+};
+
+// Alias to value, for shared API with DataSource
+prototype.values = prototype.value;
+
+prototype.verbose = function(v) {
+  if (!arguments.length) return this._verbose;
+  return (this._verbose = !!v, this);
+};
+
+prototype.evaluate = function(input) {
+  return input.signals[this._name] ? input : this._graph.doNotPropagate;
+};
+
+prototype.fire = function(cs) {
+  if (!cs) cs = ChangeSet.create(null, true);
+  cs.signals[this._name] = 1;
+  this._graph.propagate(cs, this);
+};
+
+prototype.on = function(handler) {
+  var signal = this,
+      node = new Node(this._graph);
+
+  node.evaluate = function(input) {
+    handler(signal.name(), signal.value());
+    return input;
+  };
+
+  this._handlers.push({
+    handler: handler,
+    node: node
+  });
+
+  return this.addListener(node);
+};
+
+prototype.off = function(handler) {
+  var h = this._handlers, i, x;
+
+  for (i=h.length; --i>=0;) {
+    if (!handler || h[i].handler === handler) {
+      x = h.splice(i, 1)[0];
+      this.removeListener(x.node);
+    }
+  }
+
+  return this;
+};
+
+module.exports = Signal;
+
+},{"./ChangeSet":30,"./Node":36}],38:[function(require,module,exports){
+var tupleID = 0;
+
+function ingest(datum) {
+  datum = (datum === Object(datum)) ? datum : {data: datum};
+  datum._id = ++tupleID;
+  if (datum._prev) datum._prev = null;
+  return datum;
+}
+
+function idMap(a, ids) {
+  ids = ids || {};
+  for (var i=0, n=a.length; i<n; ++i) {
+    ids[a[i]._id] = 1;
+  }
+  return ids;
+}
+
+function copy(t, c) {
+  c = c || {};
+  for (var k in t) {
+    if (k !== '_prev' && k !== '_id') c[k] = t[k];
+  }
+  return c;
+}
+
+module.exports = {
+  ingest: ingest,
+  idMap: idMap,
+
+  derive: function(d) {
+    return ingest(copy(d));
+  },
+
+  rederive: function(d, t) {
+    return copy(d, t);
+  },
+
+  set: function(t, k, v) {
+    return t[k] === v ? 0 : (t[k] = v, 1);
+  },
+
+  prev: function(t) {
+    return t._prev || t;
+  },
+
+  prev_init: function(t) {
+    if (!t._prev) { t._prev = {_id: t._id}; }
+  },
+
+  prev_update: function(t) {
+    var p = t._prev, k, v;
+    if (p) for (k in t) {
+      if (k !== '_prev' && k !== '_id') {
+        p[k] = ((v=t[k]) instanceof Object && v._prev) ? v._prev : v;
+      }
+    }
+  },
+
+  reset: function() { tupleID = 0; },
+
+  idFilter: function(data) {
+    var ids = {};
+    for (var i=arguments.length; --i>0;) {
+      idMap(arguments[i], ids);
+    }
+    return data.filter(function(x) { return !ids[x._id]; });
+  }
+};
+
+},{}],39:[function(require,module,exports){
+module.exports = {
+  ChangeSet:    require('./ChangeSet'),
+  Collector:    require('./Collector'),
+  DataSource:   require('./DataSource'),
+  Dependencies: require('./Dependencies'),
+  Graph:        require('./Graph'),
+  Node:         require('./Node'),
+  Signal:       require('./Signal'),
+  Tuple:        require('./Tuple'),
+  debug:        require('vega-logging').debug
+};
+
+},{"./ChangeSet":30,"./Collector":31,"./DataSource":32,"./Dependencies":33,"./Graph":34,"./Node":36,"./Signal":37,"./Tuple":38,"vega-logging":45}],40:[function(require,module,exports){
+function toMap(list) {
+  var map = {}, i, n;
+  for (i=0, n=list.length; i<n; ++i) map[list[i]] = 1;
+  return map;
+}
+
+function keys(object) {
+  var list = [], k;
+  for (k in object) list.push(k);
+  return list;
+}
+
+module.exports = function(opt) {
+  opt = opt || {};
+  var constants = opt.constants || require('./constants'),
+      functions = (opt.functions || require('./functions'))(codegen),
+      idWhiteList = opt.idWhiteList ? toMap(opt.idWhiteList) : null,
+      idBlackList = opt.idBlackList ? toMap(opt.idBlackList) : null,
+      memberDepth = 0,
+      FIELD_VAR = opt.fieldVar || 'datum',
+      GLOBAL_VAR = opt.globalVar || 'signals',
+      globals = {},
+      fields = {};
+
+  function codegen_wrap(ast) {    
+    var retval = {
+      code: codegen(ast),
+      globals: keys(globals),
+      fields: keys(fields)
+    };
+    globals = {};
+    fields = {};
+    return retval;
+  }
+
+  function lookupGlobal(id) {
+    return GLOBAL_VAR + '["' + id + '"]';
+  }
+
+  function codegen(ast) {
+    if (typeof ast === 'string') return ast;
+    var generator = CODEGEN_TYPES[ast.type];
+    if (generator == null) {
+      throw new Error('Unsupported type: ' + ast.type);
+    }
+    return generator(ast);
+  }
+
+  var CODEGEN_TYPES = {
+    'Literal': function(n) {
+        return n.raw;
+      },
+    'Identifier': function(n) {
+        var id = n.name;
+        if (memberDepth > 0) {
+          return id;
+        }
+        if (constants.hasOwnProperty(id)) {
+          return constants[id];
+        }
+        if (idWhiteList) {
+          if (idWhiteList.hasOwnProperty(id)) {
+            return id;
+          } else {
+            globals[id] = 1;
+            return lookupGlobal(id);
+          }
+        }
+        if (idBlackList && idBlackList.hasOwnProperty(id)) {
+          throw new Error('Illegal identifier: ' + id);
+        }
+        return id;
+      },
+    'Program': function(n) {
+        return n.body.map(codegen).join('\n');
+      },
+    'MemberExpression': function(n) {
+        var d = !n.computed;
+        var o = codegen(n.object);
+        if (d) memberDepth += 1;
+        var p = codegen(n.property);
+        if (o === FIELD_VAR) { fields[p] = 1; } // HACKish...
+        if (d) memberDepth -= 1;
+        return o + (d ? '.'+p : '['+p+']');
+      },
+    'CallExpression': function(n) {
+        if (n.callee.type !== 'Identifier') {
+          throw new Error('Illegal callee type: ' + n.callee.type);
+        }
+        var callee = n.callee.name;
+        var args = n.arguments;
+        var fn = functions.hasOwnProperty(callee) && functions[callee];
+        if (!fn) throw new Error('Unrecognized function: ' + callee);
+        return fn instanceof Function ?
+          fn(args) :
+          fn + '(' + args.map(codegen).join(',') + ')';
+      },
+    'ArrayExpression': function(n) {
+        return '[' + n.elements.map(codegen).join(',') + ']';
+      },
+    'BinaryExpression': function(n) {
+        return '(' + codegen(n.left) + n.operator + codegen(n.right) + ')';
+      },
+    'UnaryExpression': function(n) {
+        return '(' + n.operator + codegen(n.argument) + ')';
+      },
+    'ConditionalExpression': function(n) {
+        return '(' + codegen(n.test) +
+          '?' + codegen(n.consequent) +
+          ':' + codegen(n.alternate) +
+          ')';
+      },
+    'LogicalExpression': function(n) {
+        return '(' + codegen(n.left) + n.operator + codegen(n.right) + ')';
+      },
+    'ObjectExpression': function(n) {
+        return '{' + n.properties.map(codegen).join(',') + '}';
+      },
+    'Property': function(n) {
+        memberDepth += 1;
+        var k = codegen(n.key);
+        memberDepth -= 1;
+        return k + ':' + codegen(n.value);
+      },
+    'ExpressionStatement': function(n) {
+        return codegen(n.expression);
+      }
+  };
+
+  codegen_wrap.functions = functions;
+  codegen_wrap.constants = constants;
+  return codegen_wrap;
+};
+
+},{"./constants":41,"./functions":42}],41:[function(require,module,exports){
+module.exports = {
+  'NaN':     'NaN',
+  'E':       'Math.E',
+  'LN2':     'Math.LN2',
+  'LN10':    'Math.LN10',
+  'LOG2E':   'Math.LOG2E',
+  'LOG10E':  'Math.LOG10E',
+  'PI':      'Math.PI',
+  'SQRT1_2': 'Math.SQRT1_2',
+  'SQRT2':   'Math.SQRT2'
+};
+},{}],42:[function(require,module,exports){
+module.exports = function(codegen) {
+
+  function fncall(name, args, cast, type) {
+    var obj = codegen(args[0]);
+    if (cast) {
+      obj = cast + '(' + obj + ')';
+      if (cast.lastIndexOf('new ', 0) === 0) obj = '(' + obj + ')';
+    }
+    return obj + '.' + name + (type < 0 ? '' : type === 0 ?
+      '()' :
+      '(' + args.slice(1).map(codegen).join(',') + ')');
+  }
+
+  function fn(name, cast, type) {
+    return function(args) {
+      return fncall(name, args, cast, type);
+    };
+  }
+
+  var DATE = 'new Date',
+      STRING = 'String',
+      REGEXP = 'RegExp';
+
+  return {
+    // MATH functions
+    'isNaN':    'isNaN',
+    'isFinite': 'isFinite',
+    'abs':      'Math.abs',
+    'acos':     'Math.acos',
+    'asin':     'Math.asin',
+    'atan':     'Math.atan',
+    'atan2':    'Math.atan2',
+    'ceil':     'Math.ceil',
+    'cos':      'Math.cos',
+    'exp':      'Math.exp',
+    'floor':    'Math.floor',
+    'log':      'Math.log',
+    'max':      'Math.max',
+    'min':      'Math.min',
+    'pow':      'Math.pow',
+    'random':   'Math.random',
+    'round':    'Math.round',
+    'sin':      'Math.sin',
+    'sqrt':     'Math.sqrt',
+    'tan':      'Math.tan',
+
+    'clamp': function(args) {
+      if (args.length < 3)
+        throw new Error('Missing arguments to clamp function.');
+      if (args.length > 3)
+      throw new Error('Too many arguments to clamp function.');
+      var a = args.map(codegen);
+      return 'Math.max('+a[1]+', Math.min('+a[2]+','+a[0]+'))';
+    },
+
+    // DATE functions
+    'now':             'Date.now',
+    'datetime':        DATE,
+    'date':            fn('getDate', DATE, 0),
+    'day':             fn('getDay', DATE, 0),
+    'year':            fn('getFullYear', DATE, 0),
+    'month':           fn('getMonth', DATE, 0),
+    'hours':           fn('getHours', DATE, 0),
+    'minutes':         fn('getMinutes', DATE, 0),
+    'seconds':         fn('getSeconds', DATE, 0),
+    'milliseconds':    fn('getMilliseconds', DATE, 0),
+    'time':            fn('getTime', DATE, 0),
+    'timezoneoffset':  fn('getTimezoneOffset', DATE, 0),
+    'utcdate':         fn('getUTCDate', DATE, 0),
+    'utcday':          fn('getUTCDay', DATE, 0),
+    'utcyear':         fn('getUTCFullYear', DATE, 0),
+    'utcmonth':        fn('getUTCMonth', DATE, 0),
+    'utchours':        fn('getUTCHours', DATE, 0),
+    'utcminutes':      fn('getUTCMinutes', DATE, 0),
+    'utcseconds':      fn('getUTCSeconds', DATE, 0),
+    'utcmilliseconds': fn('getUTCMilliseconds', DATE, 0),
+
+    // shared sequence functions
+    'length':      fn('length', null, -1),
+    'indexof':     fn('indexOf', null),
+    'lastindexof': fn('lastIndexOf', null),
+
+    // STRING functions
+    'parseFloat':  'parseFloat',
+    'parseInt':    'parseInt',
+    'upper':       fn('toUpperCase', STRING, 0),
+    'lower':       fn('toLowerCase', STRING, 0),
+    'slice':       fn('slice', STRING),
+    'substring':   fn('substring', STRING),
+
+    // REGEXP functions
+    'regexp':  REGEXP,
+    'test':    fn('test', REGEXP),
+
+    // Control Flow functions
+    'if': function(args) {
+        if (args.length < 3)
+          throw new Error('Missing arguments to if function.');
+        if (args.length > 3)
+        throw new Error('Too many arguments to if function.');
+        var a = args.map(codegen);
+        return a[0]+'?'+a[1]+':'+a[2];
+      }
+  };
+};
+},{}],43:[function(require,module,exports){
+var parser = require('./parser'),
+    codegen = require('./codegen');
+    
+var expr = module.exports = {
+  parse: function(input, opt) {
+      return parser.parse('('+input+')', opt);
+    },
+  code: function(opt) {
+      return codegen(opt);
+    },
+  compiler: function(args, opt) {
+      args = args.slice();
+      var generator = codegen(opt),
+          len = args.length,
+          compile = function(str) {
+            var value = generator(expr.parse(str));
+            args[len] = '"use strict"; return (' + value.code + ');';
+            value.fn = Function.apply(null, args);
+            return value;
+          };
+      compile.codegen = generator;
+      return compile;
+    },
+  functions: require('./functions'),
+  constants: require('./constants')
+};
+
+},{"./codegen":40,"./constants":41,"./functions":42,"./parser":44}],44:[function(require,module,exports){
+/*
+  The following expression parser is based on Esprima (http://esprima.org/).
+  Original header comment and license for Esprima is included here:
+
+  Copyright (C) 2013 Ariya Hidayat <ariya.hidayat@gmail.com>
+  Copyright (C) 2013 Thaddee Tyl <thaddee.tyl@gmail.com>
+  Copyright (C) 2013 Mathias Bynens <mathias@qiwi.be>
+  Copyright (C) 2012 Ariya Hidayat <ariya.hidayat@gmail.com>
+  Copyright (C) 2012 Mathias Bynens <mathias@qiwi.be>
+  Copyright (C) 2012 Joost-Wim Boekesteijn <joost-wim@boekesteijn.nl>
+  Copyright (C) 2012 Kris Kowal <kris.kowal@cixar.com>
+  Copyright (C) 2012 Yusuke Suzuki <utatane.tea@gmail.com>
+  Copyright (C) 2012 Arpad Borsos <arpad.borsos@googlemail.com>
+  Copyright (C) 2011 Ariya Hidayat <ariya.hidayat@gmail.com>
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are met:
+
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+      notice, this list of conditions and the following disclaimer in the
+      documentation and/or other materials provided with the distribution.
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+  ARE DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY
+  DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+/* istanbul ignore next */
+module.exports = (function() {
+  'use strict';
+
+  var Token,
+      TokenName,
+      Syntax,
+      PropertyKind,
+      Messages,
+      Regex,
+      source,
+      strict,
+      index,
+      lineNumber,
+      lineStart,
+      length,
+      lookahead,
+      state,
+      extra;
+
+  Token = {
+      BooleanLiteral: 1,
+      EOF: 2,
+      Identifier: 3,
+      Keyword: 4,
+      NullLiteral: 5,
+      NumericLiteral: 6,
+      Punctuator: 7,
+      StringLiteral: 8,
+      RegularExpression: 9
+  };
+
+  TokenName = {};
+  TokenName[Token.BooleanLiteral] = 'Boolean';
+  TokenName[Token.EOF] = '<end>';
+  TokenName[Token.Identifier] = 'Identifier';
+  TokenName[Token.Keyword] = 'Keyword';
+  TokenName[Token.NullLiteral] = 'Null';
+  TokenName[Token.NumericLiteral] = 'Numeric';
+  TokenName[Token.Punctuator] = 'Punctuator';
+  TokenName[Token.StringLiteral] = 'String';
+  TokenName[Token.RegularExpression] = 'RegularExpression';
+
+  Syntax = {
+      AssignmentExpression: 'AssignmentExpression',
+      ArrayExpression: 'ArrayExpression',
+      BinaryExpression: 'BinaryExpression',
+      CallExpression: 'CallExpression',
+      ConditionalExpression: 'ConditionalExpression',
+      ExpressionStatement: 'ExpressionStatement',
+      Identifier: 'Identifier',
+      Literal: 'Literal',
+      LogicalExpression: 'LogicalExpression',
+      MemberExpression: 'MemberExpression',
+      ObjectExpression: 'ObjectExpression',
+      Program: 'Program',
+      Property: 'Property',
+      UnaryExpression: 'UnaryExpression'
+  };
+
+  PropertyKind = {
+      Data: 1,
+      Get: 2,
+      Set: 4
+  };
+
+  // Error messages should be identical to V8.
+  Messages = {
+      UnexpectedToken:  'Unexpected token %0',
+      UnexpectedNumber:  'Unexpected number',
+      UnexpectedString:  'Unexpected string',
+      UnexpectedIdentifier:  'Unexpected identifier',
+      UnexpectedReserved:  'Unexpected reserved word',
+      UnexpectedEOS:  'Unexpected end of input',
+      NewlineAfterThrow:  'Illegal newline after throw',
+      InvalidRegExp: 'Invalid regular expression',
+      UnterminatedRegExp:  'Invalid regular expression: missing /',
+      InvalidLHSInAssignment:  'Invalid left-hand side in assignment',
+      InvalidLHSInForIn:  'Invalid left-hand side in for-in',
+      MultipleDefaultsInSwitch: 'More than one default clause in switch statement',
+      NoCatchOrFinally:  'Missing catch or finally after try',
+      UnknownLabel: 'Undefined label \'%0\'',
+      Redeclaration: '%0 \'%1\' has already been declared',
+      IllegalContinue: 'Illegal continue statement',
+      IllegalBreak: 'Illegal break statement',
+      IllegalReturn: 'Illegal return statement',
+      StrictModeWith:  'Strict mode code may not include a with statement',
+      StrictCatchVariable:  'Catch variable may not be eval or arguments in strict mode',
+      StrictVarName:  'Variable name may not be eval or arguments in strict mode',
+      StrictParamName:  'Parameter name eval or arguments is not allowed in strict mode',
+      StrictParamDupe: 'Strict mode function may not have duplicate parameter names',
+      StrictFunctionName:  'Function name may not be eval or arguments in strict mode',
+      StrictOctalLiteral:  'Octal literals are not allowed in strict mode.',
+      StrictDelete:  'Delete of an unqualified identifier in strict mode.',
+      StrictDuplicateProperty:  'Duplicate data property in object literal not allowed in strict mode',
+      AccessorDataProperty:  'Object literal may not have data and accessor property with the same name',
+      AccessorGetSet:  'Object literal may not have multiple get/set accessors with the same name',
+      StrictLHSAssignment:  'Assignment to eval or arguments is not allowed in strict mode',
+      StrictLHSPostfix:  'Postfix increment/decrement may not have eval or arguments operand in strict mode',
+      StrictLHSPrefix:  'Prefix increment/decrement may not have eval or arguments operand in strict mode',
+      StrictReservedWord:  'Use of future reserved word in strict mode'
+  };
+
+  // See also tools/generate-unicode-regex.py.
+  Regex = {
+      NonAsciiIdentifierStart: new RegExp('[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B2\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]'),
+      NonAsciiIdentifierPart: new RegExp('[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u08A0-\u08B2\u08E4-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58\u0C59\u0C60-\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D01-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D57\u0D60-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1CD0-\u1CD2\u1CD4-\u1CF6\u1CF8\u1CF9\u1D00-\u1DF5\u1DFC-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200C\u200D\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099\u309A\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA69D\uA69F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2D\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]')
+  };
+
+  // Ensure the condition is true, otherwise throw an error.
+  // This is only to have a better contract semantic, i.e. another safety net
+  // to catch a logic error. The condition shall be fulfilled in normal case.
+  // Do NOT use this to enforce a certain condition on any user input.
+
+  function assert(condition, message) {
+      if (!condition) {
+          throw new Error('ASSERT: ' + message);
+      }
+  }
+
+  function isDecimalDigit(ch) {
+      return (ch >= 0x30 && ch <= 0x39);   // 0..9
+  }
+
+  function isHexDigit(ch) {
+      return '0123456789abcdefABCDEF'.indexOf(ch) >= 0;
+  }
+
+  function isOctalDigit(ch) {
+      return '01234567'.indexOf(ch) >= 0;
+  }
+
+  // 7.2 White Space
+
+  function isWhiteSpace(ch) {
+      return (ch === 0x20) || (ch === 0x09) || (ch === 0x0B) || (ch === 0x0C) || (ch === 0xA0) ||
+          (ch >= 0x1680 && [0x1680, 0x180E, 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200A, 0x202F, 0x205F, 0x3000, 0xFEFF].indexOf(ch) >= 0);
+  }
+
+  // 7.3 Line Terminators
+
+  function isLineTerminator(ch) {
+      return (ch === 0x0A) || (ch === 0x0D) || (ch === 0x2028) || (ch === 0x2029);
+  }
+
+  // 7.6 Identifier Names and Identifiers
+
+  function isIdentifierStart(ch) {
+      return (ch === 0x24) || (ch === 0x5F) ||  // $ (dollar) and _ (underscore)
+          (ch >= 0x41 && ch <= 0x5A) ||         // A..Z
+          (ch >= 0x61 && ch <= 0x7A) ||         // a..z
+          (ch === 0x5C) ||                      // \ (backslash)
+          ((ch >= 0x80) && Regex.NonAsciiIdentifierStart.test(String.fromCharCode(ch)));
+  }
+
+  function isIdentifierPart(ch) {
+      return (ch === 0x24) || (ch === 0x5F) ||  // $ (dollar) and _ (underscore)
+          (ch >= 0x41 && ch <= 0x5A) ||         // A..Z
+          (ch >= 0x61 && ch <= 0x7A) ||         // a..z
+          (ch >= 0x30 && ch <= 0x39) ||         // 0..9
+          (ch === 0x5C) ||                      // \ (backslash)
+          ((ch >= 0x80) && Regex.NonAsciiIdentifierPart.test(String.fromCharCode(ch)));
+  }
+
+  // 7.6.1.2 Future Reserved Words
+
+  function isFutureReservedWord(id) {
+      switch (id) {
+      case 'class':
+      case 'enum':
+      case 'export':
+      case 'extends':
+      case 'import':
+      case 'super':
+          return true;
+      default:
+          return false;
+      }
+  }
+
+  function isStrictModeReservedWord(id) {
+      switch (id) {
+      case 'implements':
+      case 'interface':
+      case 'package':
+      case 'private':
+      case 'protected':
+      case 'public':
+      case 'static':
+      case 'yield':
+      case 'let':
+          return true;
+      default:
+          return false;
+      }
+  }
+
+  // 7.6.1.1 Keywords
+
+  function isKeyword(id) {
+      if (strict && isStrictModeReservedWord(id)) {
+          return true;
+      }
+
+      // 'const' is specialized as Keyword in V8.
+      // 'yield' and 'let' are for compatiblity with SpiderMonkey and ES.next.
+      // Some others are from future reserved words.
+
+      switch (id.length) {
+      case 2:
+          return (id === 'if') || (id === 'in') || (id === 'do');
+      case 3:
+          return (id === 'var') || (id === 'for') || (id === 'new') ||
+              (id === 'try') || (id === 'let');
+      case 4:
+          return (id === 'this') || (id === 'else') || (id === 'case') ||
+              (id === 'void') || (id === 'with') || (id === 'enum');
+      case 5:
+          return (id === 'while') || (id === 'break') || (id === 'catch') ||
+              (id === 'throw') || (id === 'const') || (id === 'yield') ||
+              (id === 'class') || (id === 'super');
+      case 6:
+          return (id === 'return') || (id === 'typeof') || (id === 'delete') ||
+              (id === 'switch') || (id === 'export') || (id === 'import');
+      case 7:
+          return (id === 'default') || (id === 'finally') || (id === 'extends');
+      case 8:
+          return (id === 'function') || (id === 'continue') || (id === 'debugger');
+      case 10:
+          return (id === 'instanceof');
+      default:
+          return false;
+      }
+  }
+
+  function skipComment() {
+      var ch, start;
+
+      start = (index === 0);
+      while (index < length) {
+          ch = source.charCodeAt(index);
+
+          if (isWhiteSpace(ch)) {
+              ++index;
+          } else if (isLineTerminator(ch)) {
+              ++index;
+              if (ch === 0x0D && source.charCodeAt(index) === 0x0A) {
+                  ++index;
+              }
+              ++lineNumber;
+              lineStart = index;
+              start = true;
+          } else {
+              break;
+          }
+      }
+  }
+
+  function scanHexEscape(prefix) {
+      var i, len, ch, code = 0;
+
+      len = (prefix === 'u') ? 4 : 2;
+      for (i = 0; i < len; ++i) {
+          if (index < length && isHexDigit(source[index])) {
+              ch = source[index++];
+              code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase());
+          } else {
+              return '';
+          }
+      }
+      return String.fromCharCode(code);
+  }
+
+  function scanUnicodeCodePointEscape() {
+      var ch, code, cu1, cu2;
+
+      ch = source[index];
+      code = 0;
+
+      // At least, one hex digit is required.
+      if (ch === '}') {
+          throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+      }
+
+      while (index < length) {
+          ch = source[index++];
+          if (!isHexDigit(ch)) {
+              break;
+          }
+          code = code * 16 + '0123456789abcdef'.indexOf(ch.toLowerCase());
+      }
+
+      if (code > 0x10FFFF || ch !== '}') {
+          throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+      }
+
+      // UTF-16 Encoding
+      if (code <= 0xFFFF) {
+          return String.fromCharCode(code);
+      }
+      cu1 = ((code - 0x10000) >> 10) + 0xD800;
+      cu2 = ((code - 0x10000) & 1023) + 0xDC00;
+      return String.fromCharCode(cu1, cu2);
+  }
+
+  function getEscapedIdentifier() {
+      var ch, id;
+
+      ch = source.charCodeAt(index++);
+      id = String.fromCharCode(ch);
+
+      // '\u' (U+005C, U+0075) denotes an escaped character.
+      if (ch === 0x5C) {
+          if (source.charCodeAt(index) !== 0x75) {
+              throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+          }
+          ++index;
+          ch = scanHexEscape('u');
+          if (!ch || ch === '\\' || !isIdentifierStart(ch.charCodeAt(0))) {
+              throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+          }
+          id = ch;
+      }
+
+      while (index < length) {
+          ch = source.charCodeAt(index);
+          if (!isIdentifierPart(ch)) {
+              break;
+          }
+          ++index;
+          id += String.fromCharCode(ch);
+
+          // '\u' (U+005C, U+0075) denotes an escaped character.
+          if (ch === 0x5C) {
+              id = id.substr(0, id.length - 1);
+              if (source.charCodeAt(index) !== 0x75) {
+                  throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+              }
+              ++index;
+              ch = scanHexEscape('u');
+              if (!ch || ch === '\\' || !isIdentifierPart(ch.charCodeAt(0))) {
+                  throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+              }
+              id += ch;
+          }
+      }
+
+      return id;
+  }
+
+  function getIdentifier() {
+      var start, ch;
+
+      start = index++;
+      while (index < length) {
+          ch = source.charCodeAt(index);
+          if (ch === 0x5C) {
+              // Blackslash (U+005C) marks Unicode escape sequence.
+              index = start;
+              return getEscapedIdentifier();
+          }
+          if (isIdentifierPart(ch)) {
+              ++index;
+          } else {
+              break;
+          }
+      }
+
+      return source.slice(start, index);
+  }
+
+  function scanIdentifier() {
+      var start, id, type;
+
+      start = index;
+
+      // Backslash (U+005C) starts an escaped character.
+      id = (source.charCodeAt(index) === 0x5C) ? getEscapedIdentifier() : getIdentifier();
+
+      // There is no keyword or literal with only one character.
+      // Thus, it must be an identifier.
+      if (id.length === 1) {
+          type = Token.Identifier;
+      } else if (isKeyword(id)) {
+          type = Token.Keyword;
+      } else if (id === 'null') {
+          type = Token.NullLiteral;
+      } else if (id === 'true' || id === 'false') {
+          type = Token.BooleanLiteral;
+      } else {
+          type = Token.Identifier;
+      }
+
+      return {
+          type: type,
+          value: id,
+          lineNumber: lineNumber,
+          lineStart: lineStart,
+          start: start,
+          end: index
+      };
+  }
+
+  // 7.7 Punctuators
+
+  function scanPunctuator() {
+      var start = index,
+          code = source.charCodeAt(index),
+          code2,
+          ch1 = source[index],
+          ch2,
+          ch3,
+          ch4;
+
+      switch (code) {
+
+      // Check for most common single-character punctuators.
+      case 0x2E:  // . dot
+      case 0x28:  // ( open bracket
+      case 0x29:  // ) close bracket
+      case 0x3B:  // ; semicolon
+      case 0x2C:  // , comma
+      case 0x7B:  // { open curly brace
+      case 0x7D:  // } close curly brace
+      case 0x5B:  // [
+      case 0x5D:  // ]
+      case 0x3A:  // :
+      case 0x3F:  // ?
+      case 0x7E:  // ~
+          ++index;
+          if (extra.tokenize) {
+              if (code === 0x28) {
+                  extra.openParenToken = extra.tokens.length;
+              } else if (code === 0x7B) {
+                  extra.openCurlyToken = extra.tokens.length;
+              }
+          }
+          return {
+              type: Token.Punctuator,
+              value: String.fromCharCode(code),
+              lineNumber: lineNumber,
+              lineStart: lineStart,
+              start: start,
+              end: index
+          };
+
+      default:
+          code2 = source.charCodeAt(index + 1);
+
+          // '=' (U+003D) marks an assignment or comparison operator.
+          if (code2 === 0x3D) {
+              switch (code) {
+              case 0x2B:  // +
+              case 0x2D:  // -
+              case 0x2F:  // /
+              case 0x3C:  // <
+              case 0x3E:  // >
+              case 0x5E:  // ^
+              case 0x7C:  // |
+              case 0x25:  // %
+              case 0x26:  // &
+              case 0x2A:  // *
+                  index += 2;
+                  return {
+                      type: Token.Punctuator,
+                      value: String.fromCharCode(code) + String.fromCharCode(code2),
+                      lineNumber: lineNumber,
+                      lineStart: lineStart,
+                      start: start,
+                      end: index
+                  };
+
+              case 0x21: // !
+              case 0x3D: // =
+                  index += 2;
+
+                  // !== and ===
+                  if (source.charCodeAt(index) === 0x3D) {
+                      ++index;
+                  }
+                  return {
+                      type: Token.Punctuator,
+                      value: source.slice(start, index),
+                      lineNumber: lineNumber,
+                      lineStart: lineStart,
+                      start: start,
+                      end: index
+                  };
+              }
+          }
+      }
+
+      // 4-character punctuator: >>>=
+
+      ch4 = source.substr(index, 4);
+
+      if (ch4 === '>>>=') {
+          index += 4;
+          return {
+              type: Token.Punctuator,
+              value: ch4,
+              lineNumber: lineNumber,
+              lineStart: lineStart,
+              start: start,
+              end: index
+          };
+      }
+
+      // 3-character punctuators: === !== >>> <<= >>=
+
+      ch3 = ch4.substr(0, 3);
+
+      if (ch3 === '>>>' || ch3 === '<<=' || ch3 === '>>=') {
+          index += 3;
+          return {
+              type: Token.Punctuator,
+              value: ch3,
+              lineNumber: lineNumber,
+              lineStart: lineStart,
+              start: start,
+              end: index
+          };
+      }
+
+      // Other 2-character punctuators: ++ -- << >> && ||
+      ch2 = ch3.substr(0, 2);
+
+      if ((ch1 === ch2[1] && ('+-<>&|'.indexOf(ch1) >= 0)) || ch2 === '=>') {
+          index += 2;
+          return {
+              type: Token.Punctuator,
+              value: ch2,
+              lineNumber: lineNumber,
+              lineStart: lineStart,
+              start: start,
+              end: index
+          };
+      }
+
+      // 1-character punctuators: < > = ! + - * % & | ^ /
+
+      if ('<>=!+-*%&|^/'.indexOf(ch1) >= 0) {
+          ++index;
+          return {
+              type: Token.Punctuator,
+              value: ch1,
+              lineNumber: lineNumber,
+              lineStart: lineStart,
+              start: start,
+              end: index
+          };
+      }
+
+      throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+  }
+
+  // 7.8.3 Numeric Literals
+
+  function scanHexLiteral(start) {
+      var number = '';
+
+      while (index < length) {
+          if (!isHexDigit(source[index])) {
+              break;
+          }
+          number += source[index++];
+      }
+
+      if (number.length === 0) {
+          throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+      }
+
+      if (isIdentifierStart(source.charCodeAt(index))) {
+          throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+      }
+
+      return {
+          type: Token.NumericLiteral,
+          value: parseInt('0x' + number, 16),
+          lineNumber: lineNumber,
+          lineStart: lineStart,
+          start: start,
+          end: index
+      };
+  }
+
+  function scanOctalLiteral(start) {
+      var number = '0' + source[index++];
+      while (index < length) {
+          if (!isOctalDigit(source[index])) {
+              break;
+          }
+          number += source[index++];
+      }
+
+      if (isIdentifierStart(source.charCodeAt(index)) || isDecimalDigit(source.charCodeAt(index))) {
+          throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+      }
+
+      return {
+          type: Token.NumericLiteral,
+          value: parseInt(number, 8),
+          octal: true,
+          lineNumber: lineNumber,
+          lineStart: lineStart,
+          start: start,
+          end: index
+      };
+  }
+
+  function scanNumericLiteral() {
+      var number, start, ch;
+
+      ch = source[index];
+      assert(isDecimalDigit(ch.charCodeAt(0)) || (ch === '.'),
+          'Numeric literal must start with a decimal digit or a decimal point');
+
+      start = index;
+      number = '';
+      if (ch !== '.') {
+          number = source[index++];
+          ch = source[index];
+
+          // Hex number starts with '0x'.
+          // Octal number starts with '0'.
+          if (number === '0') {
+              if (ch === 'x' || ch === 'X') {
+                  ++index;
+                  return scanHexLiteral(start);
+              }
+              if (isOctalDigit(ch)) {
+                  return scanOctalLiteral(start);
+              }
+
+              // decimal number starts with '0' such as '09' is illegal.
+              if (ch && isDecimalDigit(ch.charCodeAt(0))) {
+                  throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+              }
+          }
+
+          while (isDecimalDigit(source.charCodeAt(index))) {
+              number += source[index++];
+          }
+          ch = source[index];
+      }
+
+      if (ch === '.') {
+          number += source[index++];
+          while (isDecimalDigit(source.charCodeAt(index))) {
+              number += source[index++];
+          }
+          ch = source[index];
+      }
+
+      if (ch === 'e' || ch === 'E') {
+          number += source[index++];
+
+          ch = source[index];
+          if (ch === '+' || ch === '-') {
+              number += source[index++];
+          }
+          if (isDecimalDigit(source.charCodeAt(index))) {
+              while (isDecimalDigit(source.charCodeAt(index))) {
+                  number += source[index++];
+              }
+          } else {
+              throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+          }
+      }
+
+      if (isIdentifierStart(source.charCodeAt(index))) {
+          throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+      }
+
+      return {
+          type: Token.NumericLiteral,
+          value: parseFloat(number),
+          lineNumber: lineNumber,
+          lineStart: lineStart,
+          start: start,
+          end: index
+      };
+  }
+
+  // 7.8.4 String Literals
+
+  function scanStringLiteral() {
+      var str = '', quote, start, ch, code, unescaped, restore, octal = false, startLineNumber, startLineStart;
+      startLineNumber = lineNumber;
+      startLineStart = lineStart;
+
+      quote = source[index];
+      assert((quote === '\'' || quote === '"'),
+          'String literal must starts with a quote');
+
+      start = index;
+      ++index;
+
+      while (index < length) {
+          ch = source[index++];
+
+          if (ch === quote) {
+              quote = '';
+              break;
+          } else if (ch === '\\') {
+              ch = source[index++];
+              if (!ch || !isLineTerminator(ch.charCodeAt(0))) {
+                  switch (ch) {
+                  case 'u':
+                  case 'x':
+                      if (source[index] === '{') {
+                          ++index;
+                          str += scanUnicodeCodePointEscape();
+                      } else {
+                          restore = index;
+                          unescaped = scanHexEscape(ch);
+                          if (unescaped) {
+                              str += unescaped;
+                          } else {
+                              index = restore;
+                              str += ch;
+                          }
+                      }
+                      break;
+                  case 'n':
+                      str += '\n';
+                      break;
+                  case 'r':
+                      str += '\r';
+                      break;
+                  case 't':
+                      str += '\t';
+                      break;
+                  case 'b':
+                      str += '\b';
+                      break;
+                  case 'f':
+                      str += '\f';
+                      break;
+                  case 'v':
+                      str += '\x0B';
+                      break;
+
+                  default:
+                      if (isOctalDigit(ch)) {
+                          code = '01234567'.indexOf(ch);
+
+                          // \0 is not octal escape sequence
+                          if (code !== 0) {
+                              octal = true;
+                          }
+
+                          if (index < length && isOctalDigit(source[index])) {
+                              octal = true;
+                              code = code * 8 + '01234567'.indexOf(source[index++]);
+
+                              // 3 digits are only allowed when string starts
+                              // with 0, 1, 2, 3
+                              if ('0123'.indexOf(ch) >= 0 &&
+                                      index < length &&
+                                      isOctalDigit(source[index])) {
+                                  code = code * 8 + '01234567'.indexOf(source[index++]);
+                              }
+                          }
+                          str += String.fromCharCode(code);
+                      } else {
+                          str += ch;
+                      }
+                      break;
+                  }
+              } else {
+                  ++lineNumber;
+                  if (ch ===  '\r' && source[index] === '\n') {
+                      ++index;
+                  }
+                  lineStart = index;
+              }
+          } else if (isLineTerminator(ch.charCodeAt(0))) {
+              break;
+          } else {
+              str += ch;
+          }
+      }
+
+      if (quote !== '') {
+          throwError({}, Messages.UnexpectedToken, 'ILLEGAL');
+      }
+
+      return {
+          type: Token.StringLiteral,
+          value: str,
+          octal: octal,
+          startLineNumber: startLineNumber,
+          startLineStart: startLineStart,
+          lineNumber: lineNumber,
+          lineStart: lineStart,
+          start: start,
+          end: index
+      };
+  }
+
+  function testRegExp(pattern, flags) {
+      var tmp = pattern,
+          value;
+
+      if (flags.indexOf('u') >= 0) {
+          // Replace each astral symbol and every Unicode code point
+          // escape sequence with a single ASCII symbol to avoid throwing on
+          // regular expressions that are only valid in combination with the
+          // `/u` flag.
+          // Note: replacing with the ASCII symbol `x` might cause false
+          // negatives in unlikely scenarios. For example, `[\u{61}-b]` is a
+          // perfectly valid pattern that is equivalent to `[a-b]`, but it
+          // would be replaced by `[x-b]` which throws an error.
+          tmp = tmp
+              .replace(/\\u\{([0-9a-fA-F]+)\}/g, function ($0, $1) {
+                  if (parseInt($1, 16) <= 0x10FFFF) {
+                      return 'x';
+                  }
+                  throwError({}, Messages.InvalidRegExp);
+              })
+              .replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, 'x');
+      }
+
+      // First, detect invalid regular expressions.
+      try {
+          value = new RegExp(tmp);
+      } catch (e) {
+          throwError({}, Messages.InvalidRegExp);
+      }
+
+      // Return a regular expression object for this pattern-flag pair, or
+      // `null` in case the current environment doesn't support the flags it
+      // uses.
+      try {
+          return new RegExp(pattern, flags);
+      } catch (exception) {
+          return null;
+      }
+  }
+
+  function scanRegExpBody() {
+      var ch, str, classMarker, terminated, body;
+
+      ch = source[index];
+      assert(ch === '/', 'Regular expression literal must start with a slash');
+      str = source[index++];
+
+      classMarker = false;
+      terminated = false;
+      while (index < length) {
+          ch = source[index++];
+          str += ch;
+          if (ch === '\\') {
+              ch = source[index++];
+              // ECMA-262 7.8.5
+              if (isLineTerminator(ch.charCodeAt(0))) {
+                  throwError({}, Messages.UnterminatedRegExp);
+              }
+              str += ch;
+          } else if (isLineTerminator(ch.charCodeAt(0))) {
+              throwError({}, Messages.UnterminatedRegExp);
+          } else if (classMarker) {
+              if (ch === ']') {
+                  classMarker = false;
+              }
+          } else {
+              if (ch === '/') {
+                  terminated = true;
+                  break;
+              } else if (ch === '[') {
+                  classMarker = true;
+              }
+          }
+      }
+
+      if (!terminated) {
+          throwError({}, Messages.UnterminatedRegExp);
+      }
+
+      // Exclude leading and trailing slash.
+      body = str.substr(1, str.length - 2);
+      return {
+          value: body,
+          literal: str
+      };
+  }
+
+  function scanRegExpFlags() {
+      var ch, str, flags, restore;
+
+      str = '';
+      flags = '';
+      while (index < length) {
+          ch = source[index];
+          if (!isIdentifierPart(ch.charCodeAt(0))) {
+              break;
+          }
+
+          ++index;
+          if (ch === '\\' && index < length) {
+              ch = source[index];
+              if (ch === 'u') {
+                  ++index;
+                  restore = index;
+                  ch = scanHexEscape('u');
+                  if (ch) {
+                      flags += ch;
+                      for (str += '\\u'; restore < index; ++restore) {
+                          str += source[restore];
+                      }
+                  } else {
+                      index = restore;
+                      flags += 'u';
+                      str += '\\u';
+                  }
+                  throwErrorTolerant({}, Messages.UnexpectedToken, 'ILLEGAL');
+              } else {
+                  str += '\\';
+                  throwErrorTolerant({}, Messages.UnexpectedToken, 'ILLEGAL');
+              }
+          } else {
+              flags += ch;
+              str += ch;
+          }
+      }
+
+      return {
+          value: flags,
+          literal: str
+      };
+  }
+
+  function scanRegExp() {
+      var start, body, flags, value;
+
+      lookahead = null;
+      skipComment();
+      start = index;
+
+      body = scanRegExpBody();
+      flags = scanRegExpFlags();
+      value = testRegExp(body.value, flags.value);
+
+      if (extra.tokenize) {
+          return {
+              type: Token.RegularExpression,
+              value: value,
+              regex: {
+                  pattern: body.value,
+                  flags: flags.value
+              },
+              lineNumber: lineNumber,
+              lineStart: lineStart,
+              start: start,
+              end: index
+          };
+      }
+
+      return {
+          literal: body.literal + flags.literal,
+          value: value,
+          regex: {
+              pattern: body.value,
+              flags: flags.value
+          },
+          start: start,
+          end: index
+      };
+  }
+
+  function collectRegex() {
+      var pos, loc, regex, token;
+
+      skipComment();
+
+      pos = index;
+      loc = {
+          start: {
+              line: lineNumber,
+              column: index - lineStart
+          }
+      };
+
+      regex = scanRegExp();
+
+      loc.end = {
+          line: lineNumber,
+          column: index - lineStart
+      };
+
+      if (!extra.tokenize) {
+          // Pop the previous token, which is likely '/' or '/='
+          if (extra.tokens.length > 0) {
+              token = extra.tokens[extra.tokens.length - 1];
+              if (token.range[0] === pos && token.type === 'Punctuator') {
+                  if (token.value === '/' || token.value === '/=') {
+                      extra.tokens.pop();
+                  }
+              }
+          }
+
+          extra.tokens.push({
+              type: 'RegularExpression',
+              value: regex.literal,
+              regex: regex.regex,
+              range: [pos, index],
+              loc: loc
+          });
+      }
+
+      return regex;
+  }
+
+  function isIdentifierName(token) {
+      return token.type === Token.Identifier ||
+          token.type === Token.Keyword ||
+          token.type === Token.BooleanLiteral ||
+          token.type === Token.NullLiteral;
+  }
+
+  function advanceSlash() {
+      var prevToken,
+          checkToken;
+      // Using the following algorithm:
+      // https://github.com/mozilla/sweet.js/wiki/design
+      prevToken = extra.tokens[extra.tokens.length - 1];
+      if (!prevToken) {
+          // Nothing before that: it cannot be a division.
+          return collectRegex();
+      }
+      if (prevToken.type === 'Punctuator') {
+          if (prevToken.value === ']') {
+              return scanPunctuator();
+          }
+          if (prevToken.value === ')') {
+              checkToken = extra.tokens[extra.openParenToken - 1];
+              if (checkToken &&
+                      checkToken.type === 'Keyword' &&
+                      (checkToken.value === 'if' ||
+                       checkToken.value === 'while' ||
+                       checkToken.value === 'for' ||
+                       checkToken.value === 'with')) {
+                  return collectRegex();
+              }
+              return scanPunctuator();
+          }
+          if (prevToken.value === '}') {
+              // Dividing a function by anything makes little sense,
+              // but we have to check for that.
+              if (extra.tokens[extra.openCurlyToken - 3] &&
+                      extra.tokens[extra.openCurlyToken - 3].type === 'Keyword') {
+                  // Anonymous function.
+                  checkToken = extra.tokens[extra.openCurlyToken - 4];
+                  if (!checkToken) {
+                      return scanPunctuator();
+                  }
+              } else if (extra.tokens[extra.openCurlyToken - 4] &&
+                      extra.tokens[extra.openCurlyToken - 4].type === 'Keyword') {
+                  // Named function.
+                  checkToken = extra.tokens[extra.openCurlyToken - 5];
+                  if (!checkToken) {
+                      return collectRegex();
+                  }
+              } else {
+                  return scanPunctuator();
+              }
+              return scanPunctuator();
+          }
+          return collectRegex();
+      }
+      if (prevToken.type === 'Keyword' && prevToken.value !== 'this') {
+          return collectRegex();
+      }
+      return scanPunctuator();
+  }
+
+  function advance() {
+      var ch;
+
+      skipComment();
+
+      if (index >= length) {
+          return {
+              type: Token.EOF,
+              lineNumber: lineNumber,
+              lineStart: lineStart,
+              start: index,
+              end: index
+          };
+      }
+
+      ch = source.charCodeAt(index);
+
+      if (isIdentifierStart(ch)) {
+          return scanIdentifier();
+      }
+
+      // Very common: ( and ) and ;
+      if (ch === 0x28 || ch === 0x29 || ch === 0x3B) {
+          return scanPunctuator();
+      }
+
+      // String literal starts with single quote (U+0027) or double quote (U+0022).
+      if (ch === 0x27 || ch === 0x22) {
+          return scanStringLiteral();
+      }
+
+
+      // Dot (.) U+002E can also start a floating-point number, hence the need
+      // to check the next character.
+      if (ch === 0x2E) {
+          if (isDecimalDigit(source.charCodeAt(index + 1))) {
+              return scanNumericLiteral();
+          }
+          return scanPunctuator();
+      }
+
+      if (isDecimalDigit(ch)) {
+          return scanNumericLiteral();
+      }
+
+      // Slash (/) U+002F can also start a regex.
+      if (extra.tokenize && ch === 0x2F) {
+          return advanceSlash();
+      }
+
+      return scanPunctuator();
+  }
+
+  function collectToken() {
+      var loc, token, value, entry;
+
+      skipComment();
+      loc = {
+          start: {
+              line: lineNumber,
+              column: index - lineStart
+          }
+      };
+
+      token = advance();
+      loc.end = {
+          line: lineNumber,
+          column: index - lineStart
+      };
+
+      if (token.type !== Token.EOF) {
+          value = source.slice(token.start, token.end);
+          entry = {
+              type: TokenName[token.type],
+              value: value,
+              range: [token.start, token.end],
+              loc: loc
+          };
+          if (token.regex) {
+              entry.regex = {
+                  pattern: token.regex.pattern,
+                  flags: token.regex.flags
+              };
+          }
+          extra.tokens.push(entry);
+      }
+
+      return token;
+  }
+
+  function lex() {
+      var token;
+
+      token = lookahead;
+      index = token.end;
+      lineNumber = token.lineNumber;
+      lineStart = token.lineStart;
+
+      lookahead = (typeof extra.tokens !== 'undefined') ? collectToken() : advance();
+
+      index = token.end;
+      lineNumber = token.lineNumber;
+      lineStart = token.lineStart;
+
+      return token;
+  }
+
+  function peek() {
+      var pos, line, start;
+
+      pos = index;
+      line = lineNumber;
+      start = lineStart;
+      lookahead = (typeof extra.tokens !== 'undefined') ? collectToken() : advance();
+      index = pos;
+      lineNumber = line;
+      lineStart = start;
+  }
+
+  function Position() {
+      this.line = lineNumber;
+      this.column = index - lineStart;
+  }
+
+  function SourceLocation() {
+      this.start = new Position();
+      this.end = null;
+  }
+
+  function WrappingSourceLocation(startToken) {
+      if (startToken.type === Token.StringLiteral) {
+          this.start = {
+              line: startToken.startLineNumber,
+              column: startToken.start - startToken.startLineStart
+          };
+      } else {
+          this.start = {
+              line: startToken.lineNumber,
+              column: startToken.start - startToken.lineStart
+          };
+      }
+      this.end = null;
+  }
+
+  function Node() {
+      // Skip comment.
+      index = lookahead.start;
+      if (lookahead.type === Token.StringLiteral) {
+          lineNumber = lookahead.startLineNumber;
+          lineStart = lookahead.startLineStart;
+      } else {
+          lineNumber = lookahead.lineNumber;
+          lineStart = lookahead.lineStart;
+      }
+      if (extra.range) {
+          this.range = [index, 0];
+      }
+      if (extra.loc) {
+          this.loc = new SourceLocation();
+      }
+  }
+
+  function WrappingNode(startToken) {
+      if (extra.range) {
+          this.range = [startToken.start, 0];
+      }
+      if (extra.loc) {
+          this.loc = new WrappingSourceLocation(startToken);
+      }
+  }
+
+  WrappingNode.prototype = Node.prototype = {
+
+      finish: function () {
+          if (extra.range) {
+              this.range[1] = index;
+          }
+          if (extra.loc) {
+              this.loc.end = new Position();
+              if (extra.source) {
+                  this.loc.source = extra.source;
+              }
+          }
+      },
+
+      finishArrayExpression: function (elements) {
+          this.type = Syntax.ArrayExpression;
+          this.elements = elements;
+          this.finish();
+          return this;
+      },
+
+      finishAssignmentExpression: function (operator, left, right) {
+          this.type = Syntax.AssignmentExpression;
+          this.operator = operator;
+          this.left = left;
+          this.right = right;
+          this.finish();
+          return this;
+      },
+
+      finishBinaryExpression: function (operator, left, right) {
+          this.type = (operator === '||' || operator === '&&') ? Syntax.LogicalExpression : Syntax.BinaryExpression;
+          this.operator = operator;
+          this.left = left;
+          this.right = right;
+          this.finish();
+          return this;
+      },
+
+      finishCallExpression: function (callee, args) {
+          this.type = Syntax.CallExpression;
+          this.callee = callee;
+          this.arguments = args;
+          this.finish();
+          return this;
+      },
+
+      finishConditionalExpression: function (test, consequent, alternate) {
+          this.type = Syntax.ConditionalExpression;
+          this.test = test;
+          this.consequent = consequent;
+          this.alternate = alternate;
+          this.finish();
+          return this;
+      },
+
+      finishExpressionStatement: function (expression) {
+          this.type = Syntax.ExpressionStatement;
+          this.expression = expression;
+          this.finish();
+          return this;
+      },
+
+      finishIdentifier: function (name) {
+          this.type = Syntax.Identifier;
+          this.name = name;
+          this.finish();
+          return this;
+      },
+
+      finishLiteral: function (token) {
+          this.type = Syntax.Literal;
+          this.value = token.value;
+          this.raw = source.slice(token.start, token.end);
+          if (token.regex) {
+              if (this.raw == '//') {
+                this.raw = '/(?:)/';
+              }
+              this.regex = token.regex;
+          }
+          this.finish();
+          return this;
+      },
+
+      finishMemberExpression: function (accessor, object, property) {
+          this.type = Syntax.MemberExpression;
+          this.computed = accessor === '[';
+          this.object = object;
+          this.property = property;
+          this.finish();
+          return this;
+      },
+
+      finishObjectExpression: function (properties) {
+          this.type = Syntax.ObjectExpression;
+          this.properties = properties;
+          this.finish();
+          return this;
+      },
+
+      finishProgram: function (body) {
+          this.type = Syntax.Program;
+          this.body = body;
+          this.finish();
+          return this;
+      },
+
+      finishProperty: function (kind, key, value) {
+          this.type = Syntax.Property;
+          this.key = key;
+          this.value = value;
+          this.kind = kind;
+          this.finish();
+          return this;
+      },
+
+      finishUnaryExpression: function (operator, argument) {
+          this.type = Syntax.UnaryExpression;
+          this.operator = operator;
+          this.argument = argument;
+          this.prefix = true;
+          this.finish();
+          return this;
+      }
+  };
+
+  // Return true if there is a line terminator before the next token.
+
+  function peekLineTerminator() {
+      var pos, line, start, found;
+
+      pos = index;
+      line = lineNumber;
+      start = lineStart;
+      skipComment();
+      found = lineNumber !== line;
+      index = pos;
+      lineNumber = line;
+      lineStart = start;
+
+      return found;
+  }
+
+  // Throw an exception
+
+  function throwError(token, messageFormat) {
+      var error,
+          args = Array.prototype.slice.call(arguments, 2),
+          msg = messageFormat.replace(
+              /%(\d)/g,
+              function (whole, index) {
+                  assert(index < args.length, 'Message reference must be in range');
+                  return args[index];
+              }
+          );
+
+      if (typeof token.lineNumber === 'number') {
+          error = new Error('Line ' + token.lineNumber + ': ' + msg);
+          error.index = token.start;
+          error.lineNumber = token.lineNumber;
+          error.column = token.start - lineStart + 1;
+      } else {
+          error = new Error('Line ' + lineNumber + ': ' + msg);
+          error.index = index;
+          error.lineNumber = lineNumber;
+          error.column = index - lineStart + 1;
+      }
+
+      error.description = msg;
+      throw error;
+  }
+
+  function throwErrorTolerant() {
+      try {
+          throwError.apply(null, arguments);
+      } catch (e) {
+          if (extra.errors) {
+              extra.errors.push(e);
+          } else {
+              throw e;
+          }
+      }
+  }
+
+
+  // Throw an exception because of the token.
+
+  function throwUnexpected(token) {
+      if (token.type === Token.EOF) {
+          throwError(token, Messages.UnexpectedEOS);
+      }
+
+      if (token.type === Token.NumericLiteral) {
+          throwError(token, Messages.UnexpectedNumber);
+      }
+
+      if (token.type === Token.StringLiteral) {
+          throwError(token, Messages.UnexpectedString);
+      }
+
+      if (token.type === Token.Identifier) {
+          throwError(token, Messages.UnexpectedIdentifier);
+      }
+
+      if (token.type === Token.Keyword) {
+          if (isFutureReservedWord(token.value)) {
+              throwError(token, Messages.UnexpectedReserved);
+          } else if (strict && isStrictModeReservedWord(token.value)) {
+              throwErrorTolerant(token, Messages.StrictReservedWord);
+              return;
+          }
+          throwError(token, Messages.UnexpectedToken, token.value);
+      }
+
+      // BooleanLiteral, NullLiteral, or Punctuator.
+      throwError(token, Messages.UnexpectedToken, token.value);
+  }
+
+  // Expect the next token to match the specified punctuator.
+  // If not, an exception will be thrown.
+
+  function expect(value) {
+      var token = lex();
+      if (token.type !== Token.Punctuator || token.value !== value) {
+          throwUnexpected(token);
+      }
+  }
+
+  /**
+   * @name expectTolerant
+   * @description Quietly expect the given token value when in tolerant mode, otherwise delegates
+   * to <code>expect(value)</code>
+   * @param {String} value The value we are expecting the lookahead token to have
+   * @since 2.0
+   */
+  function expectTolerant(value) {
+      if (extra.errors) {
+          var token = lookahead;
+          if (token.type !== Token.Punctuator && token.value !== value) {
+              throwErrorTolerant(token, Messages.UnexpectedToken, token.value);
+          } else {
+              lex();
+          }
+      } else {
+          expect(value);
+      }
+  }
+
+  // Return true if the next token matches the specified punctuator.
+
+  function match(value) {
+      return lookahead.type === Token.Punctuator && lookahead.value === value;
+  }
+
+  // Return true if the next token matches the specified keyword
+
+  function matchKeyword(keyword) {
+      return lookahead.type === Token.Keyword && lookahead.value === keyword;
+  }
+
+  function consumeSemicolon() {
+      var line;
+
+      // Catch the very common case first: immediately a semicolon (U+003B).
+      if (source.charCodeAt(index) === 0x3B || match(';')) {
+          lex();
+          return;
+      }
+
+      line = lineNumber;
+      skipComment();
+      if (lineNumber !== line) {
+          return;
+      }
+
+      if (lookahead.type !== Token.EOF && !match('}')) {
+          throwUnexpected(lookahead);
+      }
+  }
+
+  // 11.1.4 Array Initialiser
+
+  function parseArrayInitialiser() {
+      var elements = [], node = new Node();
+
+      expect('[');
+
+      while (!match(']')) {
+          if (match(',')) {
+              lex();
+              elements.push(null);
+          } else {
+              elements.push(parseAssignmentExpression());
+
+              if (!match(']')) {
+                  expect(',');
+              }
+          }
+      }
+
+      lex();
+
+      return node.finishArrayExpression(elements);
+  }
+
+  // 11.1.5 Object Initialiser
+
+  function parseObjectPropertyKey() {
+      var token, node = new Node();
+
+      token = lex();
+
+      // Note: This function is called only from parseObjectProperty(), where
+      // EOF and Punctuator tokens are already filtered out.
+
+      if (token.type === Token.StringLiteral || token.type === Token.NumericLiteral) {
+          if (strict && token.octal) {
+              throwErrorTolerant(token, Messages.StrictOctalLiteral);
+          }
+          return node.finishLiteral(token);
+      }
+
+      return node.finishIdentifier(token.value);
+  }
+
+  function parseObjectProperty() {
+      var token, key, id, value, node = new Node();
+
+      token = lookahead;
+
+      if (token.type === Token.Identifier) {
+          id = parseObjectPropertyKey();
+          expect(':');
+          value = parseAssignmentExpression();
+          return node.finishProperty('init', id, value);
+      }
+      if (token.type === Token.EOF || token.type === Token.Punctuator) {
+          throwUnexpected(token);
+      } else {
+          key = parseObjectPropertyKey();
+          expect(':');
+          value = parseAssignmentExpression();
+          return node.finishProperty('init', key, value);
+      }
+  }
+
+  function parseObjectInitialiser() {
+      var properties = [], property, name, key, kind, map = {}, toString = String, node = new Node();
+
+      expect('{');
+
+      while (!match('}')) {
+          property = parseObjectProperty();
+
+          if (property.key.type === Syntax.Identifier) {
+              name = property.key.name;
+          } else {
+              name = toString(property.key.value);
+          }
+          kind = (property.kind === 'init') ? PropertyKind.Data : (property.kind === 'get') ? PropertyKind.Get : PropertyKind.Set;
+
+          key = '$' + name;
+          if (Object.prototype.hasOwnProperty.call(map, key)) {
+              if (map[key] === PropertyKind.Data) {
+                  if (strict && kind === PropertyKind.Data) {
+                      throwErrorTolerant({}, Messages.StrictDuplicateProperty);
+                  } else if (kind !== PropertyKind.Data) {
+                      throwErrorTolerant({}, Messages.AccessorDataProperty);
+                  }
+              } else {
+                  if (kind === PropertyKind.Data) {
+                      throwErrorTolerant({}, Messages.AccessorDataProperty);
+                  } else if (map[key] & kind) {
+                      throwErrorTolerant({}, Messages.AccessorGetSet);
+                  }
+              }
+              map[key] |= kind;
+          } else {
+              map[key] = kind;
+          }
+
+          properties.push(property);
+
+          if (!match('}')) {
+              expectTolerant(',');
+          }
+      }
+
+      expect('}');
+
+      return node.finishObjectExpression(properties);
+  }
+
+  // 11.1.6 The Grouping Operator
+
+  function parseGroupExpression() {
+      var expr;
+
+      expect('(');
+
+      ++state.parenthesisCount;
+
+      expr = parseExpression();
+
+      expect(')');
+
+      return expr;
+  }
+
+
+  // 11.1 Primary Expressions
+
+  var legalKeywords = {"if":1, "this":1};
+
+  function parsePrimaryExpression() {
+      var type, token, expr, node;
+
+      if (match('(')) {
+          return parseGroupExpression();
+      }
+
+      if (match('[')) {
+          return parseArrayInitialiser();
+      }
+
+      if (match('{')) {
+          return parseObjectInitialiser();
+      }
+
+      type = lookahead.type;
+      node = new Node();
+
+      if (type === Token.Identifier || legalKeywords[lookahead.value]) {
+          expr = node.finishIdentifier(lex().value);
+      } else if (type === Token.StringLiteral || type === Token.NumericLiteral) {
+          if (strict && lookahead.octal) {
+              throwErrorTolerant(lookahead, Messages.StrictOctalLiteral);
+          }
+          expr = node.finishLiteral(lex());
+      } else if (type === Token.Keyword) {
+          throw new Error("Disabled.");
+      } else if (type === Token.BooleanLiteral) {
+          token = lex();
+          token.value = (token.value === 'true');
+          expr = node.finishLiteral(token);
+      } else if (type === Token.NullLiteral) {
+          token = lex();
+          token.value = null;
+          expr = node.finishLiteral(token);
+      } else if (match('/') || match('/=')) {
+          if (typeof extra.tokens !== 'undefined') {
+              expr = node.finishLiteral(collectRegex());
+          } else {
+              expr = node.finishLiteral(scanRegExp());
+          }
+          peek();
+      } else {
+          throwUnexpected(lex());
+      }
+
+      return expr;
+  }
+
+  // 11.2 Left-Hand-Side Expressions
+
+  function parseArguments() {
+      var args = [];
+
+      expect('(');
+
+      if (!match(')')) {
+          while (index < length) {
+              args.push(parseAssignmentExpression());
+              if (match(')')) {
+                  break;
+              }
+              expectTolerant(',');
+          }
+      }
+
+      expect(')');
+
+      return args;
+  }
+
+  function parseNonComputedProperty() {
+      var token, node = new Node();
+
+      token = lex();
+
+      if (!isIdentifierName(token)) {
+          throwUnexpected(token);
+      }
+
+      return node.finishIdentifier(token.value);
+  }
+
+  function parseNonComputedMember() {
+      expect('.');
+
+      return parseNonComputedProperty();
+  }
+
+  function parseComputedMember() {
+      var expr;
+
+      expect('[');
+
+      expr = parseExpression();
+
+      expect(']');
+
+      return expr;
+  }
+
+  function parseLeftHandSideExpressionAllowCall() {
+      var expr, args, property, startToken, previousAllowIn = state.allowIn;
+
+      startToken = lookahead;
+      state.allowIn = true;
+      expr = parsePrimaryExpression();
+
+      for (;;) {
+          if (match('.')) {
+              property = parseNonComputedMember();
+              expr = new WrappingNode(startToken).finishMemberExpression('.', expr, property);
+          } else if (match('(')) {
+              args = parseArguments();
+              expr = new WrappingNode(startToken).finishCallExpression(expr, args);
+          } else if (match('[')) {
+              property = parseComputedMember();
+              expr = new WrappingNode(startToken).finishMemberExpression('[', expr, property);
+          } else {
+              break;
+          }
+      }
+      state.allowIn = previousAllowIn;
+
+      return expr;
+  }
+
+  // 11.3 Postfix Expressions
+
+  function parsePostfixExpression() {
+      var expr = parseLeftHandSideExpressionAllowCall();
+
+      if (lookahead.type === Token.Punctuator) {
+          if ((match('++') || match('--')) && !peekLineTerminator()) {
+              throw new Error("Disabled.");
+          }
+      }
+
+      return expr;
+  }
+
+  // 11.4 Unary Operators
+
+  function parseUnaryExpression() {
+      var token, expr, startToken;
+
+      if (lookahead.type !== Token.Punctuator && lookahead.type !== Token.Keyword) {
+          expr = parsePostfixExpression();
+      } else if (match('++') || match('--')) {
+          throw new Error("Disabled.");
+      } else if (match('+') || match('-') || match('~') || match('!')) {
+          startToken = lookahead;
+          token = lex();
+          expr = parseUnaryExpression();
+          expr = new WrappingNode(startToken).finishUnaryExpression(token.value, expr);
+      } else if (matchKeyword('delete') || matchKeyword('void') || matchKeyword('typeof')) {
+          throw new Error("Disabled.");
+      } else {
+          expr = parsePostfixExpression();
+      }
+
+      return expr;
+  }
+
+  function binaryPrecedence(token, allowIn) {
+      var prec = 0;
+
+      if (token.type !== Token.Punctuator && token.type !== Token.Keyword) {
+          return 0;
+      }
+
+      switch (token.value) {
+      case '||':
+          prec = 1;
+          break;
+
+      case '&&':
+          prec = 2;
+          break;
+
+      case '|':
+          prec = 3;
+          break;
+
+      case '^':
+          prec = 4;
+          break;
+
+      case '&':
+          prec = 5;
+          break;
+
+      case '==':
+      case '!=':
+      case '===':
+      case '!==':
+          prec = 6;
+          break;
+
+      case '<':
+      case '>':
+      case '<=':
+      case '>=':
+      case 'instanceof':
+          prec = 7;
+          break;
+
+      case 'in':
+          prec = allowIn ? 7 : 0;
+          break;
+
+      case '<<':
+      case '>>':
+      case '>>>':
+          prec = 8;
+          break;
+
+      case '+':
+      case '-':
+          prec = 9;
+          break;
+
+      case '*':
+      case '/':
+      case '%':
+          prec = 11;
+          break;
+
+      default:
+          break;
+      }
+
+      return prec;
+  }
+
+  // 11.5 Multiplicative Operators
+  // 11.6 Additive Operators
+  // 11.7 Bitwise Shift Operators
+  // 11.8 Relational Operators
+  // 11.9 Equality Operators
+  // 11.10 Binary Bitwise Operators
+  // 11.11 Binary Logical Operators
+
+  function parseBinaryExpression() {
+      var marker, markers, expr, token, prec, stack, right, operator, left, i;
+
+      marker = lookahead;
+      left = parseUnaryExpression();
+
+      token = lookahead;
+      prec = binaryPrecedence(token, state.allowIn);
+      if (prec === 0) {
+          return left;
+      }
+      token.prec = prec;
+      lex();
+
+      markers = [marker, lookahead];
+      right = parseUnaryExpression();
+
+      stack = [left, token, right];
+
+      while ((prec = binaryPrecedence(lookahead, state.allowIn)) > 0) {
+
+          // Reduce: make a binary expression from the three topmost entries.
+          while ((stack.length > 2) && (prec <= stack[stack.length - 2].prec)) {
+              right = stack.pop();
+              operator = stack.pop().value;
+              left = stack.pop();
+              markers.pop();
+              expr = new WrappingNode(markers[markers.length - 1]).finishBinaryExpression(operator, left, right);
+              stack.push(expr);
+          }
+
+          // Shift.
+          token = lex();
+          token.prec = prec;
+          stack.push(token);
+          markers.push(lookahead);
+          expr = parseUnaryExpression();
+          stack.push(expr);
+      }
+
+      // Final reduce to clean-up the stack.
+      i = stack.length - 1;
+      expr = stack[i];
+      markers.pop();
+      while (i > 1) {
+          expr = new WrappingNode(markers.pop()).finishBinaryExpression(stack[i - 1].value, stack[i - 2], expr);
+          i -= 2;
+      }
+
+      return expr;
+  }
+
+  // 11.12 Conditional Operator
+
+  function parseConditionalExpression() {
+      var expr, previousAllowIn, consequent, alternate, startToken;
+
+      startToken = lookahead;
+
+      expr = parseBinaryExpression();
+
+      if (match('?')) {
+          lex();
+          previousAllowIn = state.allowIn;
+          state.allowIn = true;
+          consequent = parseAssignmentExpression();
+          state.allowIn = previousAllowIn;
+          expect(':');
+          alternate = parseAssignmentExpression();
+
+          expr = new WrappingNode(startToken).finishConditionalExpression(expr, consequent, alternate);
+      }
+
+      return expr;
+  }
+
+  // 11.13 Assignment Operators
+
+  function parseAssignmentExpression() {
+      var oldParenthesisCount, token, expr, startToken;
+
+      oldParenthesisCount = state.parenthesisCount;
+
+      startToken = lookahead;
+      token = lookahead;
+
+      expr = parseConditionalExpression();
+
+      return expr;
+  }
+
+  // 11.14 Comma Operator
+
+  function parseExpression() {
+      var expr = parseAssignmentExpression();
+
+      if (match(',')) {
+          throw new Error("Disabled."); // no sequence expressions
+      }
+
+      return expr;
+  }
+
+  // 12.4 Expression Statement
+
+  function parseExpressionStatement(node) {
+      var expr = parseExpression();
+      consumeSemicolon();
+      return node.finishExpressionStatement(expr);
+  }
+
+  // 12 Statements
+
+  function parseStatement() {
+      var type = lookahead.type,
+          expr,
+          node;
+
+      if (type === Token.EOF) {
+          throwUnexpected(lookahead);
+      }
+
+      if (type === Token.Punctuator && lookahead.value === '{') {
+          throw new Error("Disabled."); // block statement
+      }
+
+      node = new Node();
+
+      if (type === Token.Punctuator) {
+          switch (lookahead.value) {
+          case ';':
+              throw new Error("Disabled."); // empty statement
+          case '(':
+              return parseExpressionStatement(node);
+          default:
+              break;
+          }
+      } else if (type === Token.Keyword) {
+          throw new Error("Disabled."); // keyword
+      }
+
+      expr = parseExpression();
+      consumeSemicolon();
+      return node.finishExpressionStatement(expr);
+  }
+
+  // 14 Program
+
+  function parseSourceElement() {
+      if (lookahead.type === Token.Keyword) {
+          switch (lookahead.value) {
+          case 'const':
+          case 'let':
+              throw new Error("Disabled.");
+          case 'function':
+              throw new Error("Disabled.");
+          default:
+              return parseStatement();
+          }
+      }
+
+      if (lookahead.type !== Token.EOF) {
+          return parseStatement();
+      }
+  }
+
+  function parseSourceElements() {
+      var sourceElement, sourceElements = [], token, directive, firstRestricted;
+
+      while (index < length) {
+          token = lookahead;
+          if (token.type !== Token.StringLiteral) {
+              break;
+          }
+
+          sourceElement = parseSourceElement();
+          sourceElements.push(sourceElement);
+          if (sourceElement.expression.type !== Syntax.Literal) {
+              // this is not directive
+              break;
+          }
+          directive = source.slice(token.start + 1, token.end - 1);
+          if (directive === 'use strict') {
+              strict = true;
+              if (firstRestricted) {
+                  throwErrorTolerant(firstRestricted, Messages.StrictOctalLiteral);
+              }
+          } else {
+              if (!firstRestricted && token.octal) {
+                  firstRestricted = token;
+              }
+          }
+      }
+
+      while (index < length) {
+          sourceElement = parseSourceElement();
+          if (typeof sourceElement === 'undefined') {
+              break;
+          }
+          sourceElements.push(sourceElement);
+      }
+      return sourceElements;
+  }
+
+  function parseProgram() {
+      var body, node;
+
+      skipComment();
+      peek();
+      node = new Node();
+      strict = true; // assume strict
+
+      body = parseSourceElements();
+      return node.finishProgram(body);
+  }
+
+  function filterTokenLocation() {
+      var i, entry, token, tokens = [];
+
+      for (i = 0; i < extra.tokens.length; ++i) {
+          entry = extra.tokens[i];
+          token = {
+              type: entry.type,
+              value: entry.value
+          };
+          if (entry.regex) {
+              token.regex = {
+                  pattern: entry.regex.pattern,
+                  flags: entry.regex.flags
+              };
+          }
+          if (extra.range) {
+              token.range = entry.range;
+          }
+          if (extra.loc) {
+              token.loc = entry.loc;
+          }
+          tokens.push(token);
+      }
+
+      extra.tokens = tokens;
+  }
+
+  function tokenize(code, options) {
+      var toString,
+          tokens;
+
+      toString = String;
+      if (typeof code !== 'string' && !(code instanceof String)) {
+          code = toString(code);
+      }
+
+      source = code;
+      index = 0;
+      lineNumber = (source.length > 0) ? 1 : 0;
+      lineStart = 0;
+      length = source.length;
+      lookahead = null;
+      state = {
+          allowIn: true,
+          labelSet: {},
+          inFunctionBody: false,
+          inIteration: false,
+          inSwitch: false,
+          lastCommentStart: -1
+      };
+
+      extra = {};
+
+      // Options matching.
+      options = options || {};
+
+      // Of course we collect tokens here.
+      options.tokens = true;
+      extra.tokens = [];
+      extra.tokenize = true;
+      // The following two fields are necessary to compute the Regex tokens.
+      extra.openParenToken = -1;
+      extra.openCurlyToken = -1;
+
+      extra.range = (typeof options.range === 'boolean') && options.range;
+      extra.loc = (typeof options.loc === 'boolean') && options.loc;
+
+      if (typeof options.tolerant === 'boolean' && options.tolerant) {
+          extra.errors = [];
+      }
+
+      try {
+          peek();
+          if (lookahead.type === Token.EOF) {
+              return extra.tokens;
+          }
+
+          lex();
+          while (lookahead.type !== Token.EOF) {
+              try {
+                  lex();
+              } catch (lexError) {
+                  if (extra.errors) {
+                      extra.errors.push(lexError);
+                      // We have to break on the first error
+                      // to avoid infinite loops.
+                      break;
+                  } else {
+                      throw lexError;
+                  }
+              }
+          }
+
+          filterTokenLocation();
+          tokens = extra.tokens;
+          if (typeof extra.errors !== 'undefined') {
+              tokens.errors = extra.errors;
+          }
+      } catch (e) {
+          throw e;
+      } finally {
+          extra = {};
+      }
+      return tokens;
+  }
+
+  function parse(code, options) {
+      var program, toString;
+
+      toString = String;
+      if (typeof code !== 'string' && !(code instanceof String)) {
+          code = toString(code);
+      }
+
+      source = code;
+      index = 0;
+      lineNumber = (source.length > 0) ? 1 : 0;
+      lineStart = 0;
+      length = source.length;
+      lookahead = null;
+      state = {
+          allowIn: true,
+          labelSet: {},
+          parenthesisCount: 0,
+          inFunctionBody: false,
+          inIteration: false,
+          inSwitch: false,
+          lastCommentStart: -1
+      };
+
+      extra = {};
+      if (typeof options !== 'undefined') {
+          extra.range = (typeof options.range === 'boolean') && options.range;
+          extra.loc = (typeof options.loc === 'boolean') && options.loc;
+
+          if (extra.loc && options.source !== null && options.source !== undefined) {
+              extra.source = toString(options.source);
+          }
+
+          if (typeof options.tokens === 'boolean' && options.tokens) {
+              extra.tokens = [];
+          }
+          if (typeof options.tolerant === 'boolean' && options.tolerant) {
+              extra.errors = [];
+          }
+      }
+
+      try {
+          program = parseProgram();
+          if (typeof extra.tokens !== 'undefined') {
+              filterTokenLocation();
+              program.tokens = extra.tokens;
+          }
+          if (typeof extra.errors !== 'undefined') {
+              program.errors = extra.errors;
+          }
+      } catch (e) {
+          throw e;
+      } finally {
+          extra = {};
+      }
+
+      return program;
+  }
+
+  return {
+    tokenize: tokenize,
+    parse: parse
+  };
+
+})();
+},{}],45:[function(require,module,exports){
+var ts = Date.now();
+
+function write(msg) {
+  msg = '[Vega Log] ' + msg;
+  console.log(msg);
+}
+
+function error(msg) {
+  msg = '[Vega Err] ' + msg;
+  console.error(msg);
+}
+
+function debug(input, args) {
+  if (!debug.enable) return;
+  var log = Function.prototype.bind.call(console.log, console);
+  var state = {
+    prevTime:  Date.now() - ts,
+    stamp: input.stamp
+  };
+
+  if (input.add) {
+    state.add = input.add.length;
+    state.mod = input.mod.length;
+    state.rem = input.rem.length;
+    state.reflow = !!input.reflow;
+  }
+
+  log.apply(console, (args.push(JSON.stringify(state)), args));
+  ts = Date.now();
+}
+
+module.exports = {
+  log:   write,
+  error: error,
+  debug: (debug.enable = false, debug)
+};
+
+},{}],46:[function(require,module,exports){
+module.exports = {
+  path:       require('./path'),
+  render:     require('./render'),
+  Item:       require('./util/Item'),
+  bound:      require('./util/bound'),
+  Bounds:     require('./util/Bounds'),
+  Gradient:   require('./util/Gradient'),
+  toJSON:     require('./util/scene').toJSON,
+  fromJSON:   require('./util/scene').fromJSON
+};
+},{"./path":48,"./render":68,"./util/Bounds":74,"./util/Gradient":76,"./util/Item":78,"./util/bound":79,"./util/scene":82}],47:[function(require,module,exports){
+var segmentCache = {},
+    bezierCache = {},
+    join = [].join;
+
+// Copied from Inkscape svgtopdf, thanks!
+function segments(x, y, rx, ry, large, sweep, rotateX, ox, oy) {
+  var key = join.call(arguments);
+  if (segmentCache[key]) {
+    return segmentCache[key];
+  }
+
+  var th = rotateX * (Math.PI/180);
+  var sin_th = Math.sin(th);
+  var cos_th = Math.cos(th);
+  rx = Math.abs(rx);
+  ry = Math.abs(ry);
+  var px = cos_th * (ox - x) * 0.5 + sin_th * (oy - y) * 0.5;
+  var py = cos_th * (oy - y) * 0.5 - sin_th * (ox - x) * 0.5;
+  var pl = (px*px) / (rx*rx) + (py*py) / (ry*ry);
+  if (pl > 1) {
+    pl = Math.sqrt(pl);
+    rx *= pl;
+    ry *= pl;
+  }
+
+  var a00 = cos_th / rx;
+  var a01 = sin_th / rx;
+  var a10 = (-sin_th) / ry;
+  var a11 = (cos_th) / ry;
+  var x0 = a00 * ox + a01 * oy;
+  var y0 = a10 * ox + a11 * oy;
+  var x1 = a00 * x + a01 * y;
+  var y1 = a10 * x + a11 * y;
+
+  var d = (x1-x0) * (x1-x0) + (y1-y0) * (y1-y0);
+  var sfactor_sq = 1 / d - 0.25;
+  if (sfactor_sq < 0) sfactor_sq = 0;
+  var sfactor = Math.sqrt(sfactor_sq);
+  if (sweep == large) sfactor = -sfactor;
+  var xc = 0.5 * (x0 + x1) - sfactor * (y1-y0);
+  var yc = 0.5 * (y0 + y1) + sfactor * (x1-x0);
+
+  var th0 = Math.atan2(y0-yc, x0-xc);
+  var th1 = Math.atan2(y1-yc, x1-xc);
+
+  var th_arc = th1-th0;
+  if (th_arc < 0 && sweep === 1){
+    th_arc += 2 * Math.PI;
+  } else if (th_arc > 0 && sweep === 0) {
+    th_arc -= 2 * Math.PI;
+  }
+
+  var segs = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001)));
+  var result = [];
+  for (var i=0; i<segs; ++i) {
+    var th2 = th0 + i * th_arc / segs;
+    var th3 = th0 + (i+1) * th_arc / segs;
+    result[i] = [xc, yc, th2, th3, rx, ry, sin_th, cos_th];
+  }
+
+  return (segmentCache[key] = result);
+}
+
+function bezier(params) {
+  var key = join.call(params);
+  if (bezierCache[key]) {
+    return bezierCache[key];
+  }
+  
+  var cx = params[0],
+      cy = params[1],
+      th0 = params[2],
+      th1 = params[3],
+      rx = params[4],
+      ry = params[5],
+      sin_th = params[6],
+      cos_th = params[7];
+
+  var a00 = cos_th * rx;
+  var a01 = -sin_th * ry;
+  var a10 = sin_th * rx;
+  var a11 = cos_th * ry;
+
+  var cos_th0 = Math.cos(th0);
+  var sin_th0 = Math.sin(th0);
+  var cos_th1 = Math.cos(th1);
+  var sin_th1 = Math.sin(th1);
+
+  var th_half = 0.5 * (th1 - th0);
+  var sin_th_h2 = Math.sin(th_half * 0.5);
+  var t = (8/3) * sin_th_h2 * sin_th_h2 / Math.sin(th_half);
+  var x1 = cx + cos_th0 - t * sin_th0;
+  var y1 = cy + sin_th0 + t * cos_th0;
+  var x3 = cx + cos_th1;
+  var y3 = cy + sin_th1;
+  var x2 = x3 + t * sin_th1;
+  var y2 = y3 - t * cos_th1;
+
+  return (bezierCache[key] = [
+    a00 * x1 + a01 * y1,  a10 * x1 + a11 * y1,
+    a00 * x2 + a01 * y2,  a10 * x2 + a11 * y2,
+    a00 * x3 + a01 * y3,  a10 * x3 + a11 * y3
+  ]);
+}
+
+module.exports = {
+  segments: segments,
+  bezier: bezier,
+  cache: {
+    segments: segmentCache,
+    bezier: bezierCache
+  }
+};
+
+},{}],48:[function(require,module,exports){
+module.exports = {
+  parse:  require('./parse'),
+  render: require('./render')
+};
+
+},{"./parse":49,"./render":50}],49:[function(require,module,exports){
+// Path parsing and rendering code adapted from fabric.js -- Thanks!
+var cmdlen = { m:2, l:2, h:1, v:1, c:6, s:4, q:4, t:2, a:7 },
+    regexp = [/([MLHVCSQTAZmlhvcsqtaz])/g, /###/, /(\d)([-+])/g, /\s|,|###/];
+
+module.exports = function(pathstr) {
+  var result = [],
+      path,
+      curr,
+      chunks,
+      parsed, param,
+      cmd, len, i, j, n, m;
+
+  // First, break path into command sequence
+  path = pathstr
+    .slice()
+    .replace(regexp[0], '###$1')
+    .split(regexp[1])
+    .slice(1);
+
+  // Next, parse each command in turn
+  for (i=0, n=path.length; i<n; ++i) {
+    curr = path[i];
+    chunks = curr
+      .slice(1)
+      .trim()
+      .replace(regexp[2],'$1###$2')
+      .split(regexp[3]);
+    cmd = curr.charAt(0);
+
+    parsed = [cmd];
+    for (j=0, m=chunks.length; j<m; ++j) {
+      if ((param = +chunks[j]) === param) { // not NaN
+        parsed.push(param);
+      }
+    }
+
+    len = cmdlen[cmd.toLowerCase()];
+    if (parsed.length-1 > len) {
+      for (j=1, m=parsed.length; j<m; j+=len) {
+        result.push([cmd].concat(parsed.slice(j, j+len)));
+      }
+    }
+    else {
+      result.push(parsed);
+    }
+  }
+
+  return result;
+};
+
+},{}],50:[function(require,module,exports){
+var arc = require('./arc');
+
+module.exports = function(g, path, l, t) {
+  var current, // current instruction
+      previous = null,
+      x = 0, // current x
+      y = 0, // current y
+      controlX = 0, // current control point x
+      controlY = 0, // current control point y
+      tempX,
+      tempY,
+      tempControlX,
+      tempControlY;
+
+  if (l == null) l = 0;
+  if (t == null) t = 0;
+
+  g.beginPath();
+
+  for (var i=0, len=path.length; i<len; ++i) {
+    current = path[i];
+
+    switch (current[0]) { // first letter
+
+      case 'l': // lineto, relative
+        x += current[1];
+        y += current[2];
+        g.lineTo(x + l, y + t);
+        break;
+
+      case 'L': // lineto, absolute
+        x = current[1];
+        y = current[2];
+        g.lineTo(x + l, y + t);
+        break;
+
+      case 'h': // horizontal lineto, relative
+        x += current[1];
+        g.lineTo(x + l, y + t);
+        break;
+
+      case 'H': // horizontal lineto, absolute
+        x = current[1];
+        g.lineTo(x + l, y + t);
+        break;
+
+      case 'v': // vertical lineto, relative
+        y += current[1];
+        g.lineTo(x + l, y + t);
+        break;
+
+      case 'V': // verical lineto, absolute
+        y = current[1];
+        g.lineTo(x + l, y + t);
+        break;
+
+      case 'm': // moveTo, relative
+        x += current[1];
+        y += current[2];
+        g.moveTo(x + l, y + t);
+        break;
+
+      case 'M': // moveTo, absolute
+        x = current[1];
+        y = current[2];
+        g.moveTo(x + l, y + t);
+        break;
+
+      case 'c': // bezierCurveTo, relative
+        tempX = x + current[5];
+        tempY = y + current[6];
+        controlX = x + current[3];
+        controlY = y + current[4];
+        g.bezierCurveTo(
+          x + current[1] + l, // x1
+          y + current[2] + t, // y1
+          controlX + l, // x2
+          controlY + t, // y2
+          tempX + l,
+          tempY + t
+        );
+        x = tempX;
+        y = tempY;
+        break;
+
+      case 'C': // bezierCurveTo, absolute
+        x = current[5];
+        y = current[6];
+        controlX = current[3];
+        controlY = current[4];
+        g.bezierCurveTo(
+          current[1] + l,
+          current[2] + t,
+          controlX + l,
+          controlY + t,
+          x + l,
+          y + t
+        );
+        break;
+
+      case 's': // shorthand cubic bezierCurveTo, relative
+        // transform to absolute x,y
+        tempX = x + current[3];
+        tempY = y + current[4];
+        // calculate reflection of previous control points
+        controlX = 2 * x - controlX;
+        controlY = 2 * y - controlY;
+        g.bezierCurveTo(
+          controlX + l,
+          controlY + t,
+          x + current[1] + l,
+          y + current[2] + t,
+          tempX + l,
+          tempY + t
+        );
+
+        // set control point to 2nd one of this command
+        // the first control point is assumed to be the reflection of
+        // the second control point on the previous command relative
+        // to the current point.
+        controlX = x + current[1];
+        controlY = y + current[2];
+
+        x = tempX;
+        y = tempY;
+        break;
+
+      case 'S': // shorthand cubic bezierCurveTo, absolute
+        tempX = current[3];
+        tempY = current[4];
+        // calculate reflection of previous control points
+        controlX = 2*x - controlX;
+        controlY = 2*y - controlY;
+        g.bezierCurveTo(
+          controlX + l,
+          controlY + t,
+          current[1] + l,
+          current[2] + t,
+          tempX + l,
+          tempY + t
+        );
+        x = tempX;
+        y = tempY;
+        // set control point to 2nd one of this command
+        // the first control point is assumed to be the reflection of
+        // the second control point on the previous command relative
+        // to the current point.
+        controlX = current[1];
+        controlY = current[2];
+
+        break;
+
+      case 'q': // quadraticCurveTo, relative
+        // transform to absolute x,y
+        tempX = x + current[3];
+        tempY = y + current[4];
+
+        controlX = x + current[1];
+        controlY = y + current[2];
+
+        g.quadraticCurveTo(
+          controlX + l,
+          controlY + t,
+          tempX + l,
+          tempY + t
+        );
+        x = tempX;
+        y = tempY;
+        break;
+
+      case 'Q': // quadraticCurveTo, absolute
+        tempX = current[3];
+        tempY = current[4];
+
+        g.quadraticCurveTo(
+          current[1] + l,
+          current[2] + t,
+          tempX + l,
+          tempY + t
+        );
+        x = tempX;
+        y = tempY;
+        controlX = current[1];
+        controlY = current[2];
+        break;
+
+      case 't': // shorthand quadraticCurveTo, relative
+
+        // transform to absolute x,y
+        tempX = x + current[1];
+        tempY = y + current[2];
+
+        if (previous[0].match(/[QqTt]/) === null) {
+          // If there is no previous command or if the previous command was not a Q, q, T or t,
+          // assume the control point is coincident with the current point
+          controlX = x;
+          controlY = y;
+        }
+        else if (previous[0] === 't') {
+          // calculate reflection of previous control points for t
+          controlX = 2 * x - tempControlX;
+          controlY = 2 * y - tempControlY;
+        }
+        else if (previous[0] === 'q') {
+          // calculate reflection of previous control points for q
+          controlX = 2 * x - controlX;
+          controlY = 2 * y - controlY;
+        }
+
+        tempControlX = controlX;
+        tempControlY = controlY;
+
+        g.quadraticCurveTo(
+          controlX + l,
+          controlY + t,
+          tempX + l,
+          tempY + t
+        );
+        x = tempX;
+        y = tempY;
+        controlX = x + current[1];
+        controlY = y + current[2];
+        break;
+
+      case 'T':
+        tempX = current[1];
+        tempY = current[2];
+
+        // calculate reflection of previous control points
+        controlX = 2 * x - controlX;
+        controlY = 2 * y - controlY;
+        g.quadraticCurveTo(
+          controlX + l,
+          controlY + t,
+          tempX + l,
+          tempY + t
+        );
+        x = tempX;
+        y = tempY;
+        break;
+
+      case 'a':
+        drawArc(g, x + l, y + t, [
+          current[1],
+          current[2],
+          current[3],
+          current[4],
+          current[5],
+          current[6] + x + l,
+          current[7] + y + t
+        ]);
+        x += current[6];
+        y += current[7];
+        break;
+
+      case 'A':
+        drawArc(g, x + l, y + t, [
+          current[1],
+          current[2],
+          current[3],
+          current[4],
+          current[5],
+          current[6] + l,
+          current[7] + t
+        ]);
+        x = current[6];
+        y = current[7];
+        break;
+
+      case 'z':
+      case 'Z':
+        g.closePath();
+        break;
+    }
+    previous = current;
+  }
+};
+
+function drawArc(g, x, y, coords) {
+  var seg = arc.segments(
+    coords[5], // end x
+    coords[6], // end y
+    coords[0], // radius x
+    coords[1], // radius y
+    coords[3], // large flag
+    coords[4], // sweep flag
+    coords[2], // rotation
+    x, y
+  );
+  for (var i=0; i<seg.length; ++i) {
+    var bez = arc.bezier(seg[i]);
+    g.bezierCurveTo.apply(g, bez);
+  }
+}
+
+},{"./arc":47}],51:[function(require,module,exports){
+function Handler() {
+  this._active = null;
+  this._handlers = {};
+}
+
+var prototype = Handler.prototype;
+
+prototype.initialize = function(el, pad, obj) {
+  this._el = el;
+  this._obj = obj || null;
+  return this.padding(pad);
+};
+
+prototype.element = function() {
+  return this._el;
+};
+
+prototype.padding = function(pad) {
+  this._padding = pad || {top:0, left:0, bottom:0, right:0};
+  return this;
+};
+
+prototype.scene = function(scene) {
+  if (!arguments.length) return this._scene;
+  this._scene = scene;
+  return this;
+};
+
+// add an event handler
+// subclasses should override
+prototype.on = function(/*type, handler*/) {};
+
+// remove an event handler
+// subclasses should override
+prototype.off = function(/*type, handler*/) {};
+
+// return an array with all registered event handlers
+prototype.handlers = function() {
+  var h = this._handlers, a = [], k;
+  for (k in h) { a.push.apply(a, h[k]); }
+  return a;
+};
+
+prototype.eventName = function(name) {
+  var i = name.indexOf('.');
+  return i < 0 ? name : name.slice(0,i);
+};
+
+module.exports = Handler;
+},{}],52:[function(require,module,exports){
+function Renderer() {
+  this._el = null;
+  this._bgcolor = null;
+}
+
+var prototype = Renderer.prototype;
+
+prototype.initialize = function(el, width, height, padding) {
+  this._el = el;
+  return this.resize(width, height, padding);
+};
+
+// Returns the parent container element for a visualization
+prototype.element = function() {
+  return this._el;
+};
+
+// Returns the scene element (e.g., canvas or SVG) of the visualization
+// Subclasses must override if the first child is not the scene element
+prototype.scene = function() {
+  return this._el && this._el.firstChild;
+};
+
+prototype.background = function(bgcolor) {
+  if (arguments.length === 0) return this._bgcolor;
+  this._bgcolor = bgcolor;
+  return this;
+};
+
+prototype.resize = function(width, height, padding) {
+  this._width = width;
+  this._height = height;
+  this._padding = padding || {top:0, left:0, bottom:0, right:0};
+  return this;
+};
+
+prototype.render = function(/*scene, items*/) {
+  return this;
+};
+
+module.exports = Renderer;
+},{}],53:[function(require,module,exports){
+var DOM = require('../../util/dom'),
+    Handler = require('../Handler'),
+    marks = require('./marks');
+
+function CanvasHandler() {
+  Handler.call(this);
+  this._down = null;
+  this._touch = null;
+  this._first = true;
+}
+
+var base = Handler.prototype;
+var prototype = (CanvasHandler.prototype = Object.create(base));
+prototype.constructor = CanvasHandler;
+
+prototype.initialize = function(el, pad, obj) {
+  // add event listeners
+  var canvas = this._canvas = DOM.find(el, 'canvas'),
+      that = this;
+  this.events.forEach(function(type) {
+    canvas.addEventListener(type, function(evt) {
+      if (prototype[type]) {
+        prototype[type].call(that, evt);
+      } else {
+        that.fire(type, evt);
+      }
+    });
+  });
+
+  return base.initialize.call(this, el, pad, obj);
+};
+
+prototype.canvas = function() {
+  return this._canvas;
+};
+
+// retrieve the current canvas context
+prototype.context = function() {
+  return this._canvas.getContext('2d');
+};
+
+// supported events
+prototype.events = [
+  'keydown',
+  'keypress',
+  'keyup',
+  'mousedown',
+  'mouseup',
+  'mousemove',
+  'mouseout',
+  'mouseover',
+  'click',
+  'dblclick',
+  'wheel',
+  'mousewheel',
+  'touchstart',
+  'touchmove',
+  'touchend'
+];
+
+// to keep firefox happy
+prototype.DOMMouseScroll = function(evt) {
+  this.fire('mousewheel', evt);
+};
+
+prototype.mousemove = function(evt) {
+  var a = this._active,
+      p = this.pickEvent(evt);
+
+  if (p === a) {
+    // active item and picked item are the same
+    this.fire('mousemove', evt); // fire move
+  } else {
+    // active item and picked item are different
+    this.fire('mouseout', evt);  // fire out for prior active item
+    this._active = p;            // set new active item
+    this.fire('mouseover', evt); // fire over for new active item
+    this.fire('mousemove', evt); // fire move for new active item
+  }
+};
+
+prototype.mouseout = function(evt) {
+  this.fire('mouseout', evt);
+  this._active = null;
+};
+
+prototype.mousedown = function(evt) {
+  this._down = this._active;
+  this.fire('mousedown', evt);
+};
+
+prototype.click = function(evt) {
+  if (this._down === this._active) {
+    this.fire('click', evt);
+    this._down = null;
+  }
+};
+
+prototype.touchstart = function(evt) {
+  this._touch = this.pickEvent(evt.changedTouches[0]);
+
+  if (this._first) {
+    this._active = this._touch;
+    this._first = false;
+  }
+
+  this.fire('touchstart', evt, true);
+};
+
+prototype.touchmove = function(evt) {
+  this.fire('touchmove', evt, true);
+};
+
+prototype.touchend = function(evt) {
+  this.fire('touchend', evt, true);
+  this._touch = null;
+};
+
+// fire an event
+prototype.fire = function(type, evt, touch) {
+  var a = touch ? this._touch : this._active,
+      h = this._handlers[type], i, len;
+  if (h) {
+    evt.vegaType = type;
+    for (i=0, len=h.length; i<len; ++i) {
+      h[i].handler.call(this._obj, evt, a);
+    }
+  }
+};
+
+// add an event handler
+prototype.on = function(type, handler) {
+  var name = this.eventName(type),
+      h = this._handlers;
+  (h[name] || (h[name] = [])).push({
+    type: type,
+    handler: handler
+  });
+  return this;
+};
+
+// remove an event handler
+prototype.off = function(type, handler) {
+  var name = this.eventName(type),
+      h = this._handlers[name], i;
+  if (!h) return;
+  for (i=h.length; --i>=0;) {
+    if (h[i].type !== type) continue;
+    if (!handler || h[i].handler === handler) h.splice(i, 1);
+  }
+  return this;
+};
+
+prototype.pickEvent = function(evt) {
+  var rect = this._canvas.getBoundingClientRect(),
+      pad = this._padding, x, y;
+  return this.pick(this._scene,
+    x = (evt.clientX - rect.left),
+    y = (evt.clientY - rect.top),
+    x - pad.left, y - pad.top);
+};
+
+// find the scenegraph item at the current mouse position
+// x, y -- the absolute x, y mouse coordinates on the canvas element
+// gx, gy -- the relative coordinates within the current group
+prototype.pick = function(scene, x, y, gx, gy) {
+  var g = this.context(),
+      mark = marks[scene.marktype];
+  return mark.pick.call(this, g, scene, x, y, gx, gy);
+};
+
+module.exports = CanvasHandler;
+
+},{"../../util/dom":81,"../Handler":51,"./marks":60}],54:[function(require,module,exports){
+var DOM = require('../../util/dom'),
+    Bounds = require('../../util/Bounds'),
+    ImageLoader = require('../../util/ImageLoader'),
+    Canvas = require('../../util/canvas'),
+    Renderer = require('../Renderer'),
+    marks = require('./marks');
+
+function CanvasRenderer(loadConfig) {
+  Renderer.call(this);
+  this._loader = new ImageLoader(loadConfig);
+}
+
+CanvasRenderer.RETINA = true;
+
+var base = Renderer.prototype;
+var prototype = (CanvasRenderer.prototype = Object.create(base));
+prototype.constructor = CanvasRenderer;
+
+prototype.initialize = function(el, width, height, padding) {
+  this._canvas = Canvas.instance(width, height);
+  if (el) {
+    DOM.clear(el, 0).appendChild(this._canvas);
+    this._canvas.setAttribute('class', 'marks');
+  }
+  return base.initialize.call(this, el, width, height, padding);
+};
+
+prototype.resize = function(width, height, padding) {
+  base.resize.call(this, width, height, padding);
+  Canvas.resize(this._canvas, this._width, this._height,
+    this._padding, CanvasRenderer.RETINA);
+  return this;
+};
+
+prototype.canvas = function() {
+  return this._canvas;
+};
+
+prototype.context = function() {
+  return this._canvas ? this._canvas.getContext('2d') : null;
+};
+
+prototype.pendingImages = function() {
+  return this._loader.pending();
+};
+
+function clipToBounds(g, items) {
+  if (!items) return null;
+
+  var b = new Bounds(), i, n, item, mark, group;
+  for (i=0, n=items.length; i<n; ++i) {
+    item = items[i];
+    mark = item.mark;
+    group = mark.group;
+    item = marks[mark.marktype].nested ? mark : item;
+    b.union(translate(item.bounds, group));
+    if (item['bounds:prev']) {
+      b.union(translate(item['bounds:prev'], group));
+    }
+  }
+  b.round();
+
+  g.beginPath();
+  g.rect(b.x1, b.y1, b.width(), b.height());
+  g.clip();
+
+  return b;
+}
+
+function translate(bounds, group) {
+  if (group == null) return bounds;
+  var b = bounds.clone();
+  for (; group != null; group = group.mark.group) {
+    b.translate(group.x || 0, group.y || 0);
+  }
+  return b;
+}
+
+prototype.render = function(scene, items) {
+  var g = this.context(),
+      p = this._padding,
+      w = this._width + p.left + p.right,
+      h = this._height + p.top + p.bottom,
+      b;
+
+  // setup
+  this._scene = scene; // cache scene for async redraw
+  g.save();
+  b = clipToBounds(g, items);
+  this.clear(-p.left, -p.top, w, h);
+
+  // render
+  this.draw(g, scene, b);
+  
+  // takedown
+  g.restore();
+  this._scene = null; // clear scene cache
+
+  return this;
+};
+
+prototype.draw = function(ctx, scene, bounds) {
+  var mark = marks[scene.marktype];
+  mark.draw.call(this, ctx, scene, bounds);
+};
+
+prototype.clear = function(x, y, w, h) {
+  var g = this.context();
+  g.clearRect(x, y, w, h);
+  if (this._bgcolor != null) {
+    g.fillStyle = this._bgcolor;
+    g.fillRect(x, y, w, h); 
+  }
+};
+
+prototype.loadImage = function(uri) {
+  var renderer = this,
+      scene = this._scene;
+  return this._loader.loadImage(uri, function() {
+    renderer.renderAsync(scene);
+  });
+};
+
+prototype.renderAsync = function(scene) {
+  // TODO make safe for multiple scene rendering?
+  var renderer = this;
+  if (renderer._async_id) {
+    clearTimeout(renderer._async_id);
+  }
+  renderer._async_id = setTimeout(function() {
+    renderer.render(scene);
+    delete renderer._async_id;
+  }, 10);
+};
+
+module.exports = CanvasRenderer;
+
+},{"../../util/Bounds":74,"../../util/ImageLoader":77,"../../util/canvas":80,"../../util/dom":81,"../Renderer":52,"./marks":60}],55:[function(require,module,exports){
+module.exports = {
+  Handler:  require('./CanvasHandler'),
+  Renderer: require('./CanvasRenderer')
+};
+},{"./CanvasHandler":53,"./CanvasRenderer":54}],56:[function(require,module,exports){
+var util = require('./util');
+var halfpi = Math.PI / 2;
+
+function path(g, o) {
+  var x = o.x || 0,
+      y = o.y || 0,
+      ir = o.innerRadius || 0,
+      or = o.outerRadius || 0,
+      sa = (o.startAngle || 0) - halfpi,
+      ea = (o.endAngle || 0) - halfpi;
+  g.beginPath();
+  if (ir === 0) g.moveTo(x, y);
+  else g.arc(x, y, ir, sa, ea, 0);
+  g.arc(x, y, or, ea, sa, 1);
+  g.closePath();
+}
+
+module.exports = {
+  draw: util.drawAll(path),
+  pick: util.pickPath(path)
+};
+},{"./util":67}],57:[function(require,module,exports){
+var util = require('./util'),
+    parse = require('../../../path/parse'),
+    render = require('../../../path/render'),
+    areaPath = require('../../../util/svg').path.area;
+
+function path(g, items) {
+  var o = items[0],
+      p = o.pathCache || (o.pathCache = parse(areaPath(items)));
+  render(g, p);
+}
+
+function pick(g, scene, x, y, gx, gy) {
+  var items = scene.items,
+      b = scene.bounds;
+
+  if (!items || !items.length || b && !b.contains(gx, gy)) {
+    return null;
+  }
+
+  if (g.pixelratio != null && g.pixelratio !== 1) {
+    x *= g.pixelratio;
+    y *= g.pixelratio;
+  }
+  return hit(g, items, x, y) ? items[0] : null;
+}
+
+var hit = util.testPath(path);
+
+module.exports = {
+  draw: util.drawOne(path),
+  pick: pick,
+  nested: true
+};
+
+},{"../../../path/parse":49,"../../../path/render":50,"../../../util/svg":83,"./util":67}],58:[function(require,module,exports){
+var util = require('./util'),
+    rect = require('./rect');
+
+function draw(g, scene, bounds) {
+  if (!scene.items || !scene.items.length) return;
+
+  var groups = scene.items,
+      renderer = this,
+      group, items, axes, legends, gx, gy, i, n, j, m;
+
+  rect.draw.call(renderer, g, scene, bounds);
+
+  for (i=0, n=groups.length; i<n; ++i) {
+    group = groups[i];
+    axes = group.axisItems || [];
+    items = group.items || [];
+    legends = group.legendItems || [];
+    gx = group.x || 0;
+    gy = group.y || 0;
+
+    // render group contents
+    g.save();
+    g.translate(gx, gy);
+    if (group.clip) {
+      g.beginPath();
+      g.rect(0, 0, group.width || 0, group.height || 0);
+      g.clip();
+    }
+
+    if (bounds) bounds.translate(-gx, -gy);
+
+    for (j=0, m=axes.length; j<m; ++j) {
+      if (axes[j].layer === 'back') {
+        renderer.draw(g, axes[j], bounds);
+      }
+    }
+    for (j=0, m=items.length; j<m; ++j) {
+      renderer.draw(g, items[j], bounds);
+    }
+    for (j=0, m=axes.length; j<m; ++j) {
+      if (axes[j].layer !== 'back') {
+        renderer.draw(g, axes[j], bounds);
+      }
+    }
+    for (j=0, m=legends.length; j<m; ++j) {
+      renderer.draw(g, legends[j], bounds);
+    }
+    
+    if (bounds) bounds.translate(gx, gy);
+    g.restore();
+  }    
+}
+
+function hit(g, o) {
+  return o.fill || o.stroke;
+}
+
+function pick(g, scene, x, y, gx, gy) {
+  if (scene.bounds && !scene.bounds.contains(gx, gy)) {
+    return null;
+  }
+  var items = scene.items || [],
+      subscene, group, hits, dx, dy, i, j;
+
+  for (i=items.length; --i>=0;) {
+    group = items[i];
+    dx = group.x || 0;
+    dy = group.y || 0;
+
+    g.save();
+    g.translate(dx, dy);
+    for (j=group.items.length; --j >= 0;) {
+      subscene = group.items[j];
+      if (subscene.interactive === false) continue;
+      hits = this.pick(subscene, x, y, gx-dx, gy-dy);
+      if (hits) {
+        g.restore();
+        return hits;
+      }
+    }
+    g.restore();
+  }
+
+  return scene.interactive !== false ? pickSelf(g, scene, x, y, gx, gy) : null;
+}
+
+var pickSelf = util.pick(hit);
+
+module.exports = {
+  draw: draw,
+  pick: pick
+};
+
+},{"./rect":63,"./util":67}],59:[function(require,module,exports){
+var util = require('./util');
+
+function draw(g, scene, bounds) {
+  if (!scene.items || !scene.items.length) return;
+
+  var renderer = this,
+      items = scene.items, o;
+
+  for (var i=0, len=items.length; i<len; ++i) {
+    o = items[i];
+    if (bounds && !bounds.intersects(o.bounds))
+      continue; // bounds check
+
+    if (!(o.image && o.image.url === o.url)) {
+      o.image = renderer.loadImage(o.url);
+      o.image.url = o.url;
+    }
+
+    var x = o.x || 0,
+        y = o.y || 0,
+        w = o.width || (o.image && o.image.width) || 0,
+        h = o.height || (o.image && o.image.height) || 0,
+        opac;
+    x = x - (o.align==='center' ? w/2 : o.align==='right' ? w : 0);
+    y = y - (o.baseline==='middle' ? h/2 : o.baseline==='bottom' ? h : 0);
+
+    if (o.image.loaded) {
+      g.globalAlpha = (opac = o.opacity) != null ? opac : 1;
+      g.drawImage(o.image, x, y, w, h);
+    }
+  }
+}
+
+module.exports = {
+  draw: draw,
+  pick: util.pick()
+};
+},{"./util":67}],60:[function(require,module,exports){
+module.exports = {
+  arc:    require('./arc'),
+  area:   require('./area'),
+  group:  require('./group'),
+  image:  require('./image'),
+  line:   require('./line'),
+  path:   require('./path'),
+  rect:   require('./rect'),
+  rule:   require('./rule'),
+  symbol: require('./symbol'),
+  text:   require('./text')
+};
+
+},{"./arc":56,"./area":57,"./group":58,"./image":59,"./line":61,"./path":62,"./rect":63,"./rule":64,"./symbol":65,"./text":66}],61:[function(require,module,exports){
+var util = require('./util'),
+    parse = require('../../../path/parse'),
+    render = require('../../../path/render'),
+    linePath = require('../../../util/svg').path.line;
+    
+function path(g, items) {
+  var o = items[0],
+      p = o.pathCache || (o.pathCache = parse(linePath(items)));
+  render(g, p);
+}
+
+function pick(g, scene, x, y, gx, gy) {
+  var items = scene.items,
+      b = scene.bounds;
+
+  if (!items || !items.length || b && !b.contains(gx, gy)) {
+    return null;
+  }
+
+  if (g.pixelratio != null && g.pixelratio !== 1) {
+    x *= g.pixelratio;
+    y *= g.pixelratio;
+  }
+  return hit(g, items, x, y) ? items[0] : null;
+}
+
+var hit = util.testPath(path, false);
+
+module.exports = {
+  draw: util.drawOne(path),
+  pick: pick,
+  nested: true
+};
+
+},{"../../../path/parse":49,"../../../path/render":50,"../../../util/svg":83,"./util":67}],62:[function(require,module,exports){
+var util = require('./util'),
+    parse = require('../../../path/parse'),
+    render = require('../../../path/render');
+
+function path(g, o) {
+  if (o.path == null) return true;
+  var p = o.pathCache || (o.pathCache = parse(o.path));
+  render(g, p, o.x, o.y);
+}
+
+module.exports = {
+  draw: util.drawAll(path),
+  pick: util.pickPath(path)
+};
+
+},{"../../../path/parse":49,"../../../path/render":50,"./util":67}],63:[function(require,module,exports){
+var util = require('./util');
+
+function draw(g, scene, bounds) {
+  if (!scene.items || !scene.items.length) return;
+
+  var items = scene.items,
+      o, opac, x, y, w, h;
+
+  for (var i=0, len=items.length; i<len; ++i) {
+    o = items[i];
+    if (bounds && !bounds.intersects(o.bounds))
+      continue; // bounds check
+
+    opac = o.opacity == null ? 1 : o.opacity;
+    if (opac === 0) continue;
+
+    x = o.x || 0;
+    y = o.y || 0;
+    w = o.width || 0;
+    h = o.height || 0;
+
+    if (o.fill && util.fill(g, o, opac)) {
+      g.fillRect(x, y, w, h);
+    }
+    if (o.stroke && util.stroke(g, o, opac)) {
+      g.strokeRect(x, y, w, h);
+    }
+  }
+}
+
+module.exports = {
+  draw: draw,
+  pick: util.pick()
+};
+},{"./util":67}],64:[function(require,module,exports){
+var util = require('./util');
+
+function draw(g, scene, bounds) {
+  if (!scene.items || !scene.items.length) return;
+
+  var items = scene.items,
+      o, opac, x1, y1, x2, y2;
+
+  for (var i=0, len=items.length; i<len; ++i) {
+    o = items[i];
+    if (bounds && !bounds.intersects(o.bounds))
+      continue; // bounds check
+
+    opac = o.opacity == null ? 1 : o.opacity;
+    if (opac === 0) continue;
+      
+    x1 = o.x || 0;
+    y1 = o.y || 0;
+    x2 = o.x2 != null ? o.x2 : x1;
+    y2 = o.y2 != null ? o.y2 : y1;
+
+    if (o.stroke && util.stroke(g, o, opac)) {
+      g.beginPath();
+      g.moveTo(x1, y1);
+      g.lineTo(x2, y2);
+      g.stroke();
+    }
+  }
+}
+
+function stroke(g, o) {
+  var x1 = o.x || 0,
+      y1 = o.y || 0,
+      x2 = o.x2 != null ? o.x2 : x1,
+      y2 = o.y2 != null ? o.y2 : y1,
+      lw = o.strokeWidth,
+      lc = o.strokeCap;
+
+  g.lineWidth = lw != null ? lw : 1;
+  g.lineCap   = lc != null ? lc : 'butt';
+  g.beginPath();
+  g.moveTo(x1, y1);
+  g.lineTo(x2, y2);
+}
+
+function hit(g, o, x, y) {
+  if (!g.isPointInStroke) return false;
+  stroke(g, o);
+  return g.isPointInStroke(x, y);
+}
+
+module.exports = {
+  draw: draw,
+  pick: util.pick(hit)
+};
+
+},{"./util":67}],65:[function(require,module,exports){
+var util = require('./util');
+
+var sqrt3 = Math.sqrt(3),
+    tan30 = Math.tan(30 * Math.PI / 180);
+
+function path(g, o) {
+  var size = o.size != null ? o.size : 100,
+      x = o.x, y = o.y, r, t, rx, ry;
+
+  g.beginPath();
+
+  if (o.shape == null || o.shape === 'circle') {
+    r = Math.sqrt(size / Math.PI);
+    g.arc(x, y, r, 0, 2*Math.PI, 0);
+    g.closePath();
+    return;
+  }
+
+  switch (o.shape) {
+    case 'cross':
+      r = Math.sqrt(size / 5) / 2;
+      t = 3*r;
+      g.moveTo(x-t, y-r);
+      g.lineTo(x-r, y-r);
+      g.lineTo(x-r, y-t);
+      g.lineTo(x+r, y-t);
+      g.lineTo(x+r, y-r);
+      g.lineTo(x+t, y-r);
+      g.lineTo(x+t, y+r);
+      g.lineTo(x+r, y+r);
+      g.lineTo(x+r, y+t);
+      g.lineTo(x-r, y+t);
+      g.lineTo(x-r, y+r);
+      g.lineTo(x-t, y+r);
+      break;
+
+    case 'diamond':
+      ry = Math.sqrt(size / (2 * tan30));
+      rx = ry * tan30;
+      g.moveTo(x, y-ry);
+      g.lineTo(x+rx, y);
+      g.lineTo(x, y+ry);
+      g.lineTo(x-rx, y);
+      break;
+
+    case 'square':
+      t = Math.sqrt(size);
+      r = t / 2;
+      g.rect(x-r, y-r, t, t);
+      break;
+
+    case 'triangle-down':
+      rx = Math.sqrt(size / sqrt3);
+      ry = rx * sqrt3 / 2;
+      g.moveTo(x, y+ry);
+      g.lineTo(x+rx, y-ry);
+      g.lineTo(x-rx, y-ry);
+      break;
+
+    case 'triangle-up':
+      rx = Math.sqrt(size / sqrt3);
+      ry = rx * sqrt3 / 2;
+      g.moveTo(x, y-ry);
+      g.lineTo(x+rx, y+ry);
+      g.lineTo(x-rx, y+ry);
+  }
+  g.closePath();
+}
+
+module.exports = {
+  draw: util.drawAll(path),
+  pick: util.pickPath(path)
+};
+},{"./util":67}],66:[function(require,module,exports){
+var Bounds = require('../../../util/Bounds'),
+    textBounds = require('../../../util/bound').text,
+    text = require('../../../util/text'),
+    util = require('./util'),
+    tempBounds = new Bounds();
+
+function draw(g, scene, bounds) {
+  if (!scene.items || !scene.items.length) return;
+
+  var items = scene.items,
+      o, opac, x, y, r, t, str;
+
+  for (var i=0, len=items.length; i<len; ++i) {
+    o = items[i];
+    if (bounds && !bounds.intersects(o.bounds))
+      continue; // bounds check
+
+    str = text.value(o.text);
+    if (!str) continue;
+    opac = o.opacity == null ? 1 : o.opacity;
+    if (opac === 0) continue;
+
+    g.font = text.font(o);
+    g.textAlign = o.align || 'left';
+
+    x = (o.x || 0);
+    y = (o.y || 0);
+    if ((r = o.radius)) {
+      t = (o.theta || 0) - Math.PI/2;
+      x += r * Math.cos(t);
+      y += r * Math.sin(t);
+    }
+
+    if (o.angle) {
+      g.save();
+      g.translate(x, y);
+      g.rotate(o.angle * Math.PI/180);
+      x = y = 0; // reset x, y
+    }
+    x += (o.dx || 0);
+    y += (o.dy || 0) + text.offset(o);
+
+    if (o.fill && util.fill(g, o, opac)) {
+      g.fillText(str, x, y);
+    }
+    if (o.stroke && util.stroke(g, o, opac)) {
+      g.strokeText(str, x, y);
+    }
+    if (o.angle) g.restore();
+  }
+}
+
+function hit(g, o, x, y, gx, gy) {
+  if (o.fontSize <= 0) return false;
+  if (!o.angle) return true; // bounds sufficient if no rotation
+
+  // project point into space of unrotated bounds
+  var b = textBounds(o, tempBounds, true),
+      a = -o.angle * Math.PI / 180,
+      cos = Math.cos(a),
+      sin = Math.sin(a),
+      ox = o.x,
+      oy = o.y,
+      px = cos*gx - sin*gy + (ox - ox*cos + oy*sin),
+      py = sin*gx + cos*gy + (oy - ox*sin - oy*cos);
+
+  return b.contains(px, py);
+}
+
+module.exports = {
+  draw: draw,
+  pick: util.pick(hit)
+};
+
+},{"../../../util/Bounds":74,"../../../util/bound":79,"../../../util/text":84,"./util":67}],67:[function(require,module,exports){
+function drawPathOne(path, g, o, items) {
+  if (path(g, items)) return;
+
+  var opac = o.opacity == null ? 1 : o.opacity;
+  if (opac===0) return;
+
+  if (o.fill && fill(g, o, opac)) { g.fill(); }
+  if (o.stroke && stroke(g, o, opac)) { g.stroke(); }
+}
+
+function drawPathAll(path, g, scene, bounds) {
+  var i, len, item;
+  for (i=0, len=scene.items.length; i<len; ++i) {
+    item = scene.items[i];
+    if (!bounds || bounds.intersects(item.bounds)) {
+      drawPathOne(path, g, item, item);
+    }
+  }
+}
+
+function drawAll(pathFunc) {
+  return function(g, scene, bounds) {
+    drawPathAll(pathFunc, g, scene, bounds);
+  };
+}
+
+function drawOne(pathFunc) {
+  return function(g, scene, bounds) {
+    if (!scene.items.length) return;
+    if (!bounds || bounds.intersects(scene.bounds)) {
+      drawPathOne(pathFunc, g, scene.items[0], scene.items);
+    }
+  };
+}
+
+var trueFunc = function() { return true; };
+
+function pick(test) {
+  if (!test) test = trueFunc;
+
+  return function(g, scene, x, y, gx, gy) {
+    if (!scene.items.length) return null;
+
+    var o, b, i;
+
+    if (g.pixelratio != null && g.pixelratio !== 1) {
+      x *= g.pixelratio;
+      y *= g.pixelratio;
+    }
+
+    for (i=scene.items.length; --i >= 0;) {
+      o = scene.items[i]; b = o.bounds;
+      // first hit test against bounding box
+      if ((b && !b.contains(gx, gy)) || !b) continue;
+      // if in bounding box, perform more careful test
+      if (test(g, o, x, y, gx, gy)) return o;
+    }
+    return null;
+  };
+}
+
+function testPath(path, filled) {
+  return function(g, o, x, y) {
+    var item = Array.isArray(o) ? o[0] : o,
+        fill = (filled == null) ? item.fill : filled,
+        stroke = item.stroke && g.isPointInStroke, lw, lc;
+
+    if (stroke) {
+      lw = item.strokeWidth;
+      lc = item.strokeCap;
+      g.lineWidth = lw != null ? lw : 1;
+      g.lineCap   = lc != null ? lc : 'butt';
+    }
+
+    return path(g, o) ? false :
+      (fill && g.isPointInPath(x, y)) ||
+      (stroke && g.isPointInStroke(x, y));
+  };
+}
+
+function pickPath(path) {
+  return pick(testPath(path));
+}
+
+function fill(g, o, opacity) {
+  opacity *= (o.fillOpacity==null ? 1 : o.fillOpacity);
+  if (opacity > 0) {
+    g.globalAlpha = opacity;
+    g.fillStyle = color(g, o, o.fill);
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function stroke(g, o, opacity) {
+  var lw = (lw = o.strokeWidth) != null ? lw : 1, lc;
+  if (lw <= 0) return false;
+
+  opacity *= (o.strokeOpacity==null ? 1 : o.strokeOpacity);
+  if (opacity > 0) {
+    g.globalAlpha = opacity;
+    g.strokeStyle = color(g, o, o.stroke);
+    g.lineWidth = lw;
+    g.lineCap = (lc = o.strokeCap) != null ? lc : 'butt';
+    g.vgLineDash(o.strokeDash || null);
+    g.vgLineDashOffset(o.strokeDashOffset || 0);
+    return true;
+  } else {
+    return false;
+  }
+}
+
+function color(g, o, value) {
+  return (value.id) ?
+    gradient(g, value, o.bounds) :
+    value;
+}
+
+function gradient(g, p, b) {
+  var w = b.width(),
+      h = b.height(),
+      x1 = b.x1 + p.x1 * w,
+      y1 = b.y1 + p.y1 * h,
+      x2 = b.x1 + p.x2 * w,
+      y2 = b.y1 + p.y2 * h,
+      grad = g.createLinearGradient(x1, y1, x2, y2),
+      stop = p.stops,
+      i, n;
+
+  for (i=0, n=stop.length; i<n; ++i) {
+    grad.addColorStop(stop[i].offset, stop[i].color);
+  }
+  return grad;
+}
+
+module.exports = {
+  drawOne:  drawOne,
+  drawAll:  drawAll,
+  pick:     pick,
+  pickPath: pickPath,
+  testPath: testPath,
+  stroke:   stroke,
+  fill:     fill,
+  color:    color,
+  gradient: gradient
+};
+
+},{}],68:[function(require,module,exports){
+module.exports = {
+  'canvas': require('./canvas'),
+  'svg':    require('./svg')
+};
+
+},{"./canvas":55,"./svg":72}],69:[function(require,module,exports){
+var DOM = require('../../util/dom'),
+    Handler = require('../Handler');
+
+function SVGHandler() {
+  Handler.call(this);
+}
+
+var base = Handler.prototype;
+var prototype = (SVGHandler.prototype = Object.create(base));
+prototype.constructor = SVGHandler;
+
+prototype.initialize = function(el, pad, obj) {
+  this._svg = DOM.find(el, 'svg');
+  return base.initialize.call(this, el, pad, obj);
+};
+
+prototype.svg = function() {
+  return this._svg;
+};
+
+// wrap an event listener for the SVG DOM
+prototype.listener = function(handler) {
+  var that = this;
+  return function(evt) {
+    var target = evt.target,
+        item = target.__data__;
+    evt.vegaType = evt.type;
+    item = Array.isArray(item) ? item[0] : item;
+    handler.call(that._obj, evt, item);
+  };
+};
+
+// add an event handler
+prototype.on = function(type, handler) {
+  var name = this.eventName(type),
+      svg = this._svg,
+      h = this._handlers,
+      x = {
+        type:     type,
+        handler:  handler,
+        listener: this.listener(handler)
+      };
+
+  (h[name] || (h[name] = [])).push(x);
+  svg.addEventListener(name, x.listener);
+  return this;
+};
+
+// remove an event handler
+prototype.off = function(type, handler) {
+  var name = this.eventName(type),
+      svg = this._svg,
+      h = this._handlers[name], i;
+  if (!h) return;
+  for (i=h.length; --i>=0;) {
+    if (h[i].type === type && !handler || h[i].handler === handler) {
+      svg.removeEventListener(name, h[i].listener);
+      h.splice(i, 1);
+    }
+  }
+  return this;
+};
+
+module.exports = SVGHandler;
+
+},{"../../util/dom":81,"../Handler":51}],70:[function(require,module,exports){
+var ImageLoader = require('../../util/ImageLoader'),
+    Renderer = require('../Renderer'),
+    text = require('../../util/text'),
+    DOM = require('../../util/dom'),
+    SVG = require('../../util/svg'),
+    ns = SVG.metadata.xmlns,
+    marks = require('./marks');
+
+function SVGRenderer(loadConfig) {
+  Renderer.call(this);
+  this._loader = new ImageLoader(loadConfig);
+  this._dirtyID = 0;
+}
+
+var base = Renderer.prototype;
+var prototype = (SVGRenderer.prototype = Object.create(base));
+prototype.constructor = SVGRenderer;
+
+prototype.initialize = function(el, width, height, padding) {
+  if (el) {
+    this._svg = DOM.child(el, 0, 'svg', ns, 'marks');
+    DOM.clear(el, 1);
+    // set the svg root group
+    this._root = DOM.child(this._svg, 0, 'g', ns);
+    DOM.clear(this._svg, 1);
+  }
+
+  // create the svg definitions cache
+  this._defs = {
+    clip_id:  1,
+    gradient: {},
+    clipping: {}
+  };
+
+  // set background color if defined
+  this.background(this._bgcolor);
+
+  return base.initialize.call(this, el, width, height, padding);
+};
+
+prototype.background = function(bgcolor) {
+  if (arguments.length && this._svg) {
+    this._svg.style.setProperty('background-color', bgcolor);
+  }
+  return base.background.apply(this, arguments);
+};
+
+prototype.resize = function(width, height, padding) {
+  base.resize.call(this, width, height, padding);
+  
+  if (this._svg) {
+    var w = this._width,
+        h = this._height,
+        p = this._padding;
+  
+    this._svg.setAttribute('width', w + p.left + p.right);
+    this._svg.setAttribute('height', h + p.top + p.bottom);
+    
+    this._root.setAttribute('transform', 'translate('+p.left+','+p.top+')');
+  }
+
+  return this;
+};
+
+prototype.svg = function() {
+  if (!this._svg) return null;
+
+  var attr = {
+    'class':  'marks',
+    'width':  this._width + this._padding.left + this._padding.right,
+    'height': this._height + this._padding.top + this._padding.bottom,
+  };
+  for (var key in SVG.metadata) {
+    attr[key] = SVG.metadata[key];
+  }
+
+  return DOM.openTag('svg', attr) + this._svg.innerHTML + DOM.closeTag('svg');
+};
+
+prototype.imageURL = function(url) {
+  return this._loader.imageURL(url);
+};
+
+
+// -- Render entry point --
+
+prototype.render = function(scene, items) {
+  if (this._dirtyCheck(items)) {
+    if (this._dirtyAll) this._resetDefs();
+    this.draw(this._root, scene, -1);
+    DOM.clear(this._root, 1);
+  }
+  this.updateDefs();
+  return this;
+};
+
+prototype.draw = function(el, scene, index) {
+  this.drawMark(el, scene, index, marks[scene.marktype]);
+};
+
+
+// -- Manage SVG definitions ('defs') block --
+
+prototype.updateDefs = function() {
+  var svg = this._svg,
+      defs = this._defs,
+      el = defs.el,
+      index = 0, id;
+
+  for (id in defs.gradient) {
+    if (!el) el = (defs.el = DOM.child(svg, 0, 'defs', ns));
+    updateGradient(el, defs.gradient[id], index++);
+  }
+
+  for (id in defs.clipping) {
+    if (!el) el = (defs.el = DOM.child(svg, 0, 'defs', ns));
+    updateClipping(el, defs.clipping[id], index++);
+  }
+
+  // clean-up
+  if (el) {
+    if (index === 0) {
+      svg.removeChild(el);
+      defs.el = null;
+    } else {
+      DOM.clear(el, index);      
+    }
+  }
+};
+
+function updateGradient(el, grad, index) {
+  var i, n, stop;
+
+  el = DOM.child(el, index, 'linearGradient', ns);
+  el.setAttribute('id', grad.id);
+  el.setAttribute('x1', grad.x1);
+  el.setAttribute('x2', grad.x2);
+  el.setAttribute('y1', grad.y1);
+  el.setAttribute('y2', grad.y2);
+  
+  for (i=0, n=grad.stops.length; i<n; ++i) {
+    stop = DOM.child(el, i, 'stop', ns);
+    stop.setAttribute('offset', grad.stops[i].offset);
+    stop.setAttribute('stop-color', grad.stops[i].color);
+  }
+  DOM.clear(el, i);
+}
+
+function updateClipping(el, clip, index) {
+  var rect;
+
+  el = DOM.child(el, index, 'clipPath', ns);
+  el.setAttribute('id', clip.id);
+  rect = DOM.child(el, 0, 'rect', ns);
+  rect.setAttribute('x', 0);
+  rect.setAttribute('y', 0);
+  rect.setAttribute('width', clip.width);
+  rect.setAttribute('height', clip.height);
+}
+
+prototype._resetDefs = function() {
+  var def = this._defs;
+  def.clip_id = 1;
+  def.gradient = {};
+  def.clipping = {};
+};
+
+
+// -- Manage rendering of items marked as dirty --
+
+prototype.isDirty = function(item) {
+  return this._dirtyAll || item.dirty === this._dirtyID;
+};
+
+prototype._dirtyCheck = function(items) {
+  this._dirtyAll = true;
+  if (!items) return true;
+
+  var id = ++this._dirtyID,
+      item, mark, type, mdef, i, n, o;
+
+  for (i=0, n=items.length; i<n; ++i) {
+    item = items[i];
+    mark = item.mark;
+    if (mark.marktype !== type) {
+      // memoize mark instance lookup
+      type = mark.marktype;
+      mdef = marks[type];
+    }
+
+    if (item.status === 'exit') { // EXIT
+      if (item._svg) {
+        if (mdef.nest && item.mark.items.length) {
+          // if nested mark with remaining points, update instead
+          this._update(mdef, item._svg, item.mark.items[0]);
+          o = item.mark.items[0];
+          o._svg = item._svg;
+          o._update = id;
+        } else {
+          // otherwise remove from DOM
+          DOM.remove(item._svg);
+        }
+        item._svg = null;
+      }
+      continue;
+    }
+
+    item = (mdef.nest ? mark.items[0] : item);
+    if (item._update === id) { // Already processed
+      continue;
+    } else if (item._svg) { // UPDATE
+      this._update(mdef, item._svg, item);
+    } else { // ENTER
+      this._dirtyAll = false;
+      dirtyParents(item, id);
+    }
+    item._update = id;
+  }
+  return !this._dirtyAll;
+};
+
+function dirtyParents(item, id) {
+  for (; item && item.dirty !== id; item=item.mark.group) {
+    item.dirty = id;
+    if (item.mark && item.mark.dirty !== id) {
+      item.mark.dirty = id;
+    } else return;
+  }
+}
+
+
+// -- Construct & maintain scenegraph to SVG mapping ---
+
+// Draw a mark container.
+prototype.drawMark = function(el, scene, index, mdef) {
+  if (!this.isDirty(scene)) return;
+
+  var items = mdef.nest ?
+        (scene.items && scene.items.length ? [scene.items[0]] : []) :
+        scene.items || [],
+      events = scene.interactive === false ? 'none' : null,
+      isGroup = (mdef.tag === 'g'),
+      className = DOM.cssClass(scene),
+      p, i, n, c, d, insert;
+
+  p = DOM.child(el, index+1, 'g', ns, className);
+  p.setAttribute('class', className);
+  scene._svg = p;
+  if (!isGroup && events) {
+    p.style.setProperty('pointer-events', events);
+  }
+
+  for (i=0, n=items.length; i<n; ++i) {
+    if (this.isDirty(d = items[i])) {
+      insert = !(this._dirtyAll || d._svg);
+      c = insert ? bind(p, mdef, d, i, true)
+        : (p.childNodes[i] || bind(p, mdef, d, i));
+      this._update(mdef, c, d);
+      if (isGroup) {
+        if (insert) this._dirtyAll = true;
+        this._recurse(c, d);
+        if (insert) this._dirtyAll = false;
+      }
+    }
+  }
+  DOM.clear(p, i);
+  return p;
+};
+
+// Recursively process group contents.
+prototype._recurse = function(el, group) {
+  var items = group.items || [],
+      legends = group.legendItems || [],
+      axes = group.axisItems || [],
+      idx = 0, j, m;
+
+  for (j=0, m=axes.length; j<m; ++j) {
+    if (axes[j].layer === 'back') {
+      this.drawMark(el, axes[j], idx++, marks.group);
+    }
+  }
+  for (j=0, m=items.length; j<m; ++j) {
+    this.draw(el, items[j], idx++);
+  }
+  for (j=0, m=axes.length; j<m; ++j) {
+    if (axes[j].layer !== 'back') {
+      this.drawMark(el, axes[j], idx++, marks.group);
+    }
+  }
+  for (j=0, m=legends.length; j<m; ++j) {
+    this.drawMark(el, legends[j], idx++, marks.group);
+  }
+
+  // remove any extraneous DOM elements
+  DOM.clear(el, 1 + idx);
+};
+
+// Bind a scenegraph item to an SVG DOM element.
+// Create new SVG elements as needed.
+function bind(el, mdef, item, index, insert) {
+  // create svg element, bind item data for D3 compatibility
+  var node = DOM.child(el, index, mdef.tag, ns, null, insert);
+  node.__data__ = item;
+  node.__values__ = {fill: 'default'};
+
+  // create background rect
+  if (mdef.tag === 'g') {
+    var bg = DOM.child(node, 0, 'rect', ns, 'background');
+    bg.__data__ = item;
+  }
+
+  // add pointer from scenegraph item to svg element
+  return (item._svg = node);
+}
+
+
+// -- Set attributes & styles on SVG elements ---
+
+var href = (typeof window !== 'undefined' ? window.location.href : ''),
+    element = null, // temp var for current SVG element
+    values = null;  // temp var for current values hash
+
+// Extra configuration for certain mark types
+var mark_extras = {
+  group: function(mdef, el, item) {
+    element = el.childNodes[0];
+    values = el.__values__; // use parent's values hash
+    mdef.background(emit, item, this);
+
+    var value = item.mark.interactive === false ? 'none' : null;
+    if (value !== values.events) {
+      element.style.setProperty('pointer-events', value);
+      values.events = value;
+    }
+  },
+  text: function(mdef, el, item) {
+    var str = text.value(item.text);
+    if (str !== values.text) {
+      el.textContent = str;
+      values.text = str;
+    }
+    str = text.font(item);
+    if (str !== values.font) {
+      el.style.setProperty('font', str);
+      values.font = str;
+    }
+  }
+};
+
+prototype._update = function(mdef, el, item) {
+  // set dom element and values cache
+  // provides access to emit method
+  element = el;
+  values = el.__values__;
+
+  // apply svg attributes
+  mdef.attr(emit, item, this);
+
+  // some marks need special treatment
+  var extra = mark_extras[mdef.type];
+  if (extra) extra(mdef, el, item);
+
+  // apply svg css styles
+  // note: element may be modified by 'extra' method
+  this.style(element, item);
+};
+
+function emit(name, value, ns) {
+  // early exit if value is unchanged
+  if (value === values[name]) return;
+
+  if (value != null) {
+    // if value is provided, update DOM attribute
+    if (ns) {
+      element.setAttributeNS(ns, name, value);
+    } else {
+      element.setAttribute(name, value);
+    }
+  } else {
+    // else remove DOM attribute
+    if (ns) {
+      element.removeAttributeNS(ns, name);
+    } else {
+      element.removeAttribute(name);
+    }
+  }
+
+  // note current value for future comparison
+  values[name] = value;
+}
+
+prototype.style = function(el, o) {
+  if (o == null) return;
+  var i, n, prop, name, value;
+
+  for (i=0, n=SVG.styleProperties.length; i<n; ++i) {
+    prop = SVG.styleProperties[i];
+    value = o[prop];
+    if (value === values[prop]) continue;
+
+    name = SVG.styles[prop];
+    if (value == null) {
+      if (name === 'fill') {
+        el.style.setProperty(name, 'none');
+      } else {
+        el.style.removeProperty(name);
+      }
+    } else {
+      if (value.id) {
+        // ensure definition is included
+        this._defs.gradient[value.id] = value;
+        value = 'url(' + href + '#' + value.id + ')';
+      }
+      el.style.setProperty(name, value+'');
+    }
+
+    values[prop] = value;
+  }
+};
+
+module.exports = SVGRenderer;
+
+},{"../../util/ImageLoader":77,"../../util/dom":81,"../../util/svg":83,"../../util/text":84,"../Renderer":52,"./marks":73}],71:[function(require,module,exports){
+var Renderer = require('../Renderer'),
+    ImageLoader = require('../../util/ImageLoader'),
+    SVG = require('../../util/svg'),
+    text = require('../../util/text'),
+    DOM = require('../../util/dom'),
+    openTag = DOM.openTag,
+    closeTag = DOM.closeTag,
+    MARKS = require('./marks');
+
+function SVGStringRenderer(loadConfig) {
+  Renderer.call(this);
+
+  this._loader = new ImageLoader(loadConfig);
+
+  this._text = {
+    head: '',
+    root: '',
+    foot: '',
+    defs: '',
+    body: ''
+  };
+
+  this._defs = {
+    clip_id:  1,
+    gradient: {},
+    clipping: {}
+  };
+}
+
+var base = Renderer.prototype;
+var prototype = (SVGStringRenderer.prototype = Object.create(base));
+prototype.constructor = SVGStringRenderer;
+
+prototype.resize = function(width, height, padding) {
+  base.resize.call(this, width, height, padding);
+  var p = this._padding,
+      t = this._text;
+
+  var attr = {
+    'class':  'marks',
+    'width':  this._width + p.left + p.right,
+    'height': this._height + p.top + p.bottom,
+  };
+  for (var key in SVG.metadata) {
+    attr[key] = SVG.metadata[key];
+  }
+
+  t.head = openTag('svg', attr);
+  t.root = openTag('g', {
+    transform: 'translate(' + p.left + ',' + p.top + ')'
+  });
+  t.foot = closeTag('g') + closeTag('svg');
+
+  return this;
+};
+
+prototype.svg = function() {
+  var t = this._text;
+  return t.head + t.defs + t.root + t.body + t.foot;
+};
+
+prototype.render = function(scene) {
+  this._text.body = this.mark(scene);
+  this._text.defs = this.buildDefs();
+  return this;
+};
+
+prototype.reset = function() {
+  this._defs.clip_id = 0;
+  return this;
+};
+
+prototype.buildDefs = function() {
+  var all = this._defs,
+      defs = '',
+      i, id, def, stops;
+
+  for (id in all.gradient) {
+    def = all.gradient[id];
+    stops = def.stops;
+
+    defs += openTag('linearGradient', {
+      id: id,
+      x1: def.x1,
+      x2: def.x2,
+      y1: def.y1,
+      y2: def.y2
+    });
+    
+    for (i=0; i<stops.length; ++i) {
+      defs += openTag('stop', {
+        offset: stops[i].offset,
+        'stop-color': stops[i].color
+      }) + closeTag('stop');
+    }
+    
+    defs += closeTag('linearGradient');
+  }
+  
+  for (id in all.clipping) {
+    def = all.clipping[id];
+
+    defs += openTag('clipPath', {id: id});
+
+    defs += openTag('rect', {
+      x: 0,
+      y: 0,
+      width: def.width,
+      height: def.height
+    }) + closeTag('rect');
+
+    defs += closeTag('clipPath');
+  }
+  
+  return (defs.length > 0) ? openTag('defs') + defs + closeTag('defs') : '';
+};
+
+prototype.imageURL = function(url) {
+  return this._loader.imageURL(url);
+};
+
+var object;
+
+function emit(name, value, ns, prefixed) {
+  object[prefixed || name] = value;
+}
+
+prototype.attributes = function(attr, item) {
+  object = {};
+  attr(emit, item, this);
+  return object;
+};
+
+prototype.mark = function(scene) {
+  var mdef = MARKS[scene.marktype],
+      tag  = mdef.tag,
+      attr = mdef.attr,
+      nest = mdef.nest || false,
+      data = nest ?
+          (scene.items && scene.items.length ? [scene.items[0]] : []) :
+          (scene.items || []),
+      defs = this._defs,
+      str = '',
+      style, i, item;
+
+  if (tag !== 'g' && scene.interactive === false) {
+    style = 'style="pointer-events: none;"';
+  }
+
+  // render opening group tag
+  str += openTag('g', {
+    'class': DOM.cssClass(scene)
+  }, style);
+
+  // render contained elements
+  for (i=0; i<data.length; ++i) {
+    item = data[i];
+    style = (tag !== 'g') ? styles(item, scene, tag, defs) : null;
+    str += openTag(tag, this.attributes(attr, item), style);
+    if (tag === 'text') {
+      str += escape_text(text.value(item.text));
+    } else if (tag === 'g') {
+      str += openTag('rect',
+        this.attributes(mdef.background, item),
+        styles(item, scene, 'bgrect', defs)) + closeTag('rect');
+      str += this.markGroup(item);
+    }
+    str += closeTag(tag);
+  }
+
+  // render closing group tag
+  return str + closeTag('g');
+};
+
+prototype.markGroup = function(scene) {
+  var str = '',
+      axes = scene.axisItems || [],
+      items = scene.items || [],
+      legends = scene.legendItems || [],
+      j, m;
+
+  for (j=0, m=axes.length; j<m; ++j) {
+    if (axes[j].layer === 'back') {
+      str += this.mark(axes[j]);
+    }
+  }
+  for (j=0, m=items.length; j<m; ++j) {
+    str += this.mark(items[j]);
+  }
+  for (j=0, m=axes.length; j<m; ++j) {
+    if (axes[j].layer !== 'back') {
+      str += this.mark(axes[j]);
+    }
+  }
+  for (j=0, m=legends.length; j<m; ++j) {
+    str += this.mark(legends[j]);
+  }
+
+  return str;
+};
+
+function styles(o, mark, tag, defs) {
+  if (o == null) return '';
+  var i, n, prop, name, value, s = '';
+
+  if (tag === 'bgrect' && mark.interactive === false) {
+    s += 'pointer-events: none;';
+  }
+
+  if (tag === 'text') {
+    s += 'font: ' + text.font(o) + ';';
+  }
+
+  for (i=0, n=SVG.styleProperties.length; i<n; ++i) {
+    prop = SVG.styleProperties[i];
+    name = SVG.styles[prop];
+    value = o[prop];
+
+    if (value == null) {
+      if (name === 'fill') {
+        s += (s.length ? ' ' : '') + 'fill: none;';
+      }
+    } else {
+      if (value.id) {
+        // ensure definition is included
+        defs.gradient[value.id] = value;
+        value = 'url(#' + value.id + ')';
+      }
+      s += (s.length ? ' ' : '') + name + ': ' + value + ';';
+    }
+  }
+
+  return s ? 'style="' + s + '"' : null;
+}
+
+function escape_text(s) {
+  return s.replace(/&/g, '&amp;')
+          .replace(/</g, '&lt;')
+          .replace(/>/g, '&gt;');
+}
+
+module.exports = SVGStringRenderer;
+
+},{"../../util/ImageLoader":77,"../../util/dom":81,"../../util/svg":83,"../../util/text":84,"../Renderer":52,"./marks":73}],72:[function(require,module,exports){
+module.exports = {
+  Handler:  require('./SVGHandler'),
+  Renderer: require('./SVGRenderer'),
+  string: {
+    Renderer : require('./SVGStringRenderer')
+  }
+};
+},{"./SVGHandler":69,"./SVGRenderer":70,"./SVGStringRenderer":71}],73:[function(require,module,exports){
+var text = require('../../util/text'),
+    SVG = require('../../util/svg'),
+    textAlign = SVG.textAlign,
+    path = SVG.path;
+
+function translateItem(o) {
+  return translate(o.x || 0, o.y || 0);
+}
+
+function translate(x, y) {
+  return 'translate(' + x + ',' + y + ')';
+}
+
+module.exports = {
+  arc: {
+    tag:  'path',
+    type: 'arc',
+    attr: function(emit, o) {
+      emit('transform', translateItem(o));
+      emit('d', path.arc(o));
+    }
+  },
+  area: {
+    tag:  'path',
+    type: 'area',
+    nest: true,
+    attr: function(emit, o) {
+      var items = o.mark.items;
+      if (items.length) emit('d', path.area(items));
+    }
+  },
+  group: {
+    tag:  'g',
+    type: 'group',
+    attr: function(emit, o, renderer) {
+      var id = null, defs, c;
+      emit('transform', translateItem(o));
+      if (o.clip) {
+        defs = renderer._defs;
+        id = o.clip_id || (o.clip_id = 'clip' + defs.clip_id++);
+        c = defs.clipping[id] || (defs.clipping[id] = {id: id});
+        c.width = o.width || 0;
+        c.height = o.height || 0;
+      }
+      emit('clip-path', id ? ('url(#' + id + ')') : null);
+    },
+    background: function(emit, o) {
+      emit('class', 'background');
+      emit('width', o.width || 0);
+      emit('height', o.height || 0);
+    }
+  },
+  image: {
+    tag:  'image',
+    type: 'image',
+    attr: function(emit, o, renderer) {
+      var x = o.x || 0,
+          y = o.y || 0,
+          w = o.width || 0,
+          h = o.height || 0,
+          url = renderer.imageURL(o.url);
+
+      x = x - (o.align === 'center' ? w/2 : o.align === 'right' ? w : 0);
+      y = y - (o.baseline === 'middle' ? h/2 : o.baseline === 'bottom' ? h : 0);
+
+      emit('href', url, 'http://www.w3.org/1999/xlink', 'xlink:href');
+      emit('transform', translate(x, y));
+      emit('width', w);
+      emit('height', h);
+    }
+  },
+  line: {
+    tag:  'path',
+    type: 'line',
+    nest: true,
+    attr: function(emit, o) {
+      var items = o.mark.items;
+      if (items.length) emit('d', path.line(items));
+    }
+  },
+  path: {
+    tag:  'path',
+    type: 'path',
+    attr: function(emit, o) {
+      emit('transform', translateItem(o));
+      emit('d', o.path);
+    }
+  },
+  rect: {
+    tag:  'rect',
+    type: 'rect',
+    nest: false,
+    attr: function(emit, o) {
+      emit('transform', translateItem(o));
+      emit('width', o.width || 0);
+      emit('height', o.height || 0);
+    }
+  },
+  rule: {
+    tag:  'line',
+    type: 'rule',
+    attr: function(emit, o) {
+      emit('transform', translateItem(o));
+      emit('x2', o.x2 != null ? o.x2 - (o.x||0) : 0);
+      emit('y2', o.y2 != null ? o.y2 - (o.y||0) : 0);
+    }
+  },
+  symbol: {
+    tag:  'path',
+    type: 'symbol',
+    attr: function(emit, o) {
+      emit('transform', translateItem(o));
+      emit('d', path.symbol(o));
+    }
+  },
+  text: {
+    tag:  'text',
+    type: 'text',
+    nest: false,
+    attr: function(emit, o) {
+      var dx = (o.dx || 0),
+          dy = (o.dy || 0) + text.offset(o),
+          x = (o.x || 0),
+          y = (o.y || 0),
+          a = o.angle || 0,
+          r = o.radius || 0, t;
+
+      if (r) {
+        t = (o.theta || 0) - Math.PI/2;
+        x += r * Math.cos(t);
+        y += r * Math.sin(t);
+      }
+
+      emit('text-anchor', textAlign[o.align] || 'start');
+      
+      if (a) {
+        t = translate(x, y) + ' rotate('+a+')';
+        if (dx || dy) t += ' ' + translate(dx, dy);
+      } else {
+        t = translate(x+dx, y+dy);
+      }
+      emit('transform', t);
+    }
+  }
+};
+
+},{"../../util/svg":83,"../../util/text":84}],74:[function(require,module,exports){
+function Bounds(b) {
+  this.clear();
+  if (b) this.union(b);
+}
+
+var prototype = Bounds.prototype;
+
+prototype.clone = function() {
+  return new Bounds(this);
+};
+
+prototype.clear = function() {
+  this.x1 = +Number.MAX_VALUE;
+  this.y1 = +Number.MAX_VALUE;
+  this.x2 = -Number.MAX_VALUE;
+  this.y2 = -Number.MAX_VALUE;
+  return this;
+};
+
+prototype.set = function(x1, y1, x2, y2) {
+  this.x1 = x1;
+  this.y1 = y1;
+  this.x2 = x2;
+  this.y2 = y2;
+  return this;
+};
+
+prototype.add = function(x, y) {
+  if (x < this.x1) this.x1 = x;
+  if (y < this.y1) this.y1 = y;
+  if (x > this.x2) this.x2 = x;
+  if (y > this.y2) this.y2 = y;
+  return this;
+};
+
+prototype.expand = function(d) {
+  this.x1 -= d;
+  this.y1 -= d;
+  this.x2 += d;
+  this.y2 += d;
+  return this;
+};
+
+prototype.round = function() {
+  this.x1 = Math.floor(this.x1);
+  this.y1 = Math.floor(this.y1);
+  this.x2 = Math.ceil(this.x2);
+  this.y2 = Math.ceil(this.y2);
+  return this;
+};
+
+prototype.translate = function(dx, dy) {
+  this.x1 += dx;
+  this.x2 += dx;
+  this.y1 += dy;
+  this.y2 += dy;
+  return this;
+};
+
+prototype.rotate = function(angle, x, y) {
+  var cos = Math.cos(angle),
+      sin = Math.sin(angle),
+      cx = x - x*cos + y*sin,
+      cy = y - x*sin - y*cos,
+      x1 = this.x1, x2 = this.x2,
+      y1 = this.y1, y2 = this.y2;
+
+  return this.clear()
+    .add(cos*x1 - sin*y1 + cx,  sin*x1 + cos*y1 + cy)
+    .add(cos*x1 - sin*y2 + cx,  sin*x1 + cos*y2 + cy)
+    .add(cos*x2 - sin*y1 + cx,  sin*x2 + cos*y1 + cy)
+    .add(cos*x2 - sin*y2 + cx,  sin*x2 + cos*y2 + cy);
+};
+
+prototype.union = function(b) {
+  if (b.x1 < this.x1) this.x1 = b.x1;
+  if (b.y1 < this.y1) this.y1 = b.y1;
+  if (b.x2 > this.x2) this.x2 = b.x2;
+  if (b.y2 > this.y2) this.y2 = b.y2;
+  return this;
+};
+
+prototype.encloses = function(b) {
+  return b && (
+    this.x1 <= b.x1 &&
+    this.x2 >= b.x2 &&
+    this.y1 <= b.y1 &&
+    this.y2 >= b.y2
+  );
+};
+
+prototype.intersects = function(b) {
+  return b && !(
+    this.x2 < b.x1 ||
+    this.x1 > b.x2 ||
+    this.y2 < b.y1 ||
+    this.y1 > b.y2
+  );
+};
+
+prototype.contains = function(x, y) {
+  return !(
+    x < this.x1 ||
+    x > this.x2 ||
+    y < this.y1 ||
+    y > this.y2
+  );
+};
+
+prototype.width = function() {
+  return this.x2 - this.x1;
+};
+
+prototype.height = function() {
+  return this.y2 - this.y1;
+};
+
+module.exports = Bounds;
+
+},{}],75:[function(require,module,exports){
+module.exports = function(b) {
+  function noop() { }
+  function add(x,y) { b.add(x, y); }
+
+  return {
+    bounds: function(_) {
+      if (!arguments.length) return b;
+      return (b = _, this);
+    },
+    beginPath: noop,
+    closePath: noop,
+    moveTo: add,
+    lineTo: add,
+    quadraticCurveTo: function(x1, y1, x2, y2) {
+      b.add(x1, y1);
+      b.add(x2, y2);
+    },
+    bezierCurveTo: function(x1, y1, x2, y2, x3, y3) {
+      b.add(x1, y1);
+      b.add(x2, y2);
+      b.add(x3, y3);
+    }
+  };
+};
+
+},{}],76:[function(require,module,exports){
+var gradient_id = 0;
+
+function Gradient(type) {
+  this.id = 'gradient_' + (gradient_id++);
+  this.type = type || 'linear';
+  this.stops = [];
+  this.x1 = 0;
+  this.x2 = 1;
+  this.y1 = 0;
+  this.y2 = 0;
+}
+
+var prototype = Gradient.prototype;
+
+prototype.stop = function(offset, color) {
+  this.stops.push({
+    offset: offset,
+    color: color
+  });
+  return this;
+};
+
+module.exports = Gradient;
+},{}],77:[function(require,module,exports){
+(function (global){
+var load = require('datalib/src/import/load');
+
+function ImageLoader(loadConfig) {
+  this._pending = 0;
+  this._config = loadConfig || ImageLoader.Config; 
+}
+
+// Overridable global default load configuration
+ImageLoader.Config = null;
+
+var prototype = ImageLoader.prototype;
+
+prototype.pending = function() {
+  return this._pending;
+};
+
+prototype.params = function(uri) {
+  var p = {url: uri}, k;
+  for (k in this._config) { p[k] = this._config[k]; }
+  return p;
+};
+
+prototype.imageURL = function(uri) {
+  return load.sanitizeUrl(this.params(uri));
+};
+
+function browser(uri, callback) {
+  var url = load.sanitizeUrl(this.params(uri));
+  if (!url) { // error
+    if (callback) callback(uri, null);
+    return null;
+  }
+
+  var loader = this,
+      image = new Image();
+
+  loader._pending += 1;
+
+  image.onload = function() {
+    loader._pending -= 1;
+    image.loaded = true;
+    if (callback) callback(null, image);
+  };
+  image.src = url;
+
+  return image;
+}
+
+function server(uri, callback) {
+  var loader = this,
+      image = new ((typeof window !== "undefined" ? window['canvas'] : typeof global !== "undefined" ? global['canvas'] : null).Image)();
+
+  loader._pending += 1;
+
+  load(this.params(uri), function(err, data) {
+    loader._pending -= 1;
+    if (err) {
+      if (callback) callback(err, null);
+      return null;
+    }
+    image.src = data;
+    image.loaded = true;
+    if (callback) callback(null, image);
+  });
+
+  return image;
+}
+
+prototype.loadImage = function(uri, callback) {
+  return load.useXHR ?
+    browser.call(this, uri, callback) :
+    server.call(this, uri, callback);
+};
+
+module.exports = ImageLoader;
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"datalib/src/import/load":20}],78:[function(require,module,exports){
+function Item(mark) {
+  this.mark = mark;
+}
+
+var prototype = Item.prototype;
+
+prototype.hasPropertySet = function(name) {
+  var props = this.mark.def.properties;
+  return props && props[name] != null;
+};
+
+prototype.cousin = function(offset, index) {
+  if (offset === 0) return this;
+  offset = offset || -1;
+  var mark = this.mark,
+      group = mark.group,
+      iidx = index==null ? mark.items.indexOf(this) : index,
+      midx = group.items.indexOf(mark) + offset;
+  return group.items[midx].items[iidx];
+};
+
+prototype.sibling = function(offset) {
+  if (offset === 0) return this;
+  offset = offset || -1;
+  var mark = this.mark,
+      iidx = mark.items.indexOf(this) + offset;
+  return mark.items[iidx];
+};
+
+prototype.remove = function() {
+  var item = this,
+      list = item.mark.items,
+      i = list.indexOf(item);
+  if (i >= 0) {
+    if (i===list.length-1) {
+      list.pop();
+    } else {
+      list.splice(i, 1);
+    }
+  }
+  return item;
+};
+
+prototype.touch = function() {
+  if (this.pathCache) this.pathCache = null;
+};
+
+module.exports = Item;
+},{}],79:[function(require,module,exports){
+var BoundsContext = require('./BoundsContext'),
+    Bounds = require('./Bounds'),
+    canvas = require('./canvas'),
+    svg = require('./svg'),
+    text = require('./text'),
+    paths = require('../path'),
+    parse = paths.parse,
+    drawPath = paths.render,
+    areaPath = svg.path.area,
+    linePath = svg.path.line,
+    halfpi = Math.PI / 2,
+    sqrt3 = Math.sqrt(3),
+    tan30 = Math.tan(30 * Math.PI / 180),
+    g2D = null,
+    bc = BoundsContext();
+
+function context() {
+  return g2D || (g2D = canvas.instance(1,1).getContext('2d'));
+}
+
+function strokeBounds(o, bounds) {
+  if (o.stroke && o.opacity !== 0 && o.stokeOpacity !== 0) {
+    bounds.expand(o.strokeWidth != null ? o.strokeWidth : 1);
+  }
+  return bounds;
+}
+
+function pathBounds(o, path, bounds, x, y) {
+  if (path == null) {
+    bounds.set(0, 0, 0, 0);
+  } else {
+    drawPath(bc.bounds(bounds), path, x, y);
+    strokeBounds(o, bounds);
+  }
+  return bounds;
+}
+
+function path(o, bounds) {
+  var p = o.path ? o.pathCache || (o.pathCache = parse(o.path)) : null;
+  return pathBounds(o, p, bounds, o.x, o.y);
+}
+
+function area(mark, bounds) {
+  if (mark.items.length === 0) return bounds;
+  var items = mark.items,
+      item = items[0],
+      p = item.pathCache || (item.pathCache = parse(areaPath(items)));
+  return pathBounds(item, p, bounds);
+}
+
+function line(mark, bounds) {
+  if (mark.items.length === 0) return bounds;
+  var items = mark.items,
+      item = items[0],
+      p = item.pathCache || (item.pathCache = parse(linePath(items)));
+  return pathBounds(item, p, bounds);
+}
+
+function rect(o, bounds) {
+  var x, y;
+  return strokeBounds(o, bounds.set(
+    x = o.x || 0,
+    y = o.y || 0,
+    (x + o.width) || 0,
+    (y + o.height) || 0
+  ));
+}
+
+function image(o, bounds) {
+  var x = o.x || 0,
+      y = o.y || 0,
+      w = o.width || 0,
+      h = o.height || 0;
+  x = x - (o.align === 'center' ? w/2 : (o.align === 'right' ? w : 0));
+  y = y - (o.baseline === 'middle' ? h/2 : (o.baseline === 'bottom' ? h : 0));
+  return bounds.set(x, y, x+w, y+h);
+}
+
+function rule(o, bounds) {
+  var x1, y1;
+  return strokeBounds(o, bounds.set(
+    x1 = o.x || 0,
+    y1 = o.y || 0,
+    o.x2 != null ? o.x2 : x1,
+    o.y2 != null ? o.y2 : y1
+  ));
+}
+
+function arc(o, bounds) {
+  var cx = o.x || 0,
+      cy = o.y || 0,
+      ir = o.innerRadius || 0,
+      or = o.outerRadius || 0,
+      sa = (o.startAngle || 0) - halfpi,
+      ea = (o.endAngle || 0) - halfpi,
+      xmin = Infinity, xmax = -Infinity,
+      ymin = Infinity, ymax = -Infinity,
+      a, i, n, x, y, ix, iy, ox, oy;
+
+  var angles = [sa, ea],
+      s = sa - (sa % halfpi);
+  for (i=0; i<4 && s<ea; ++i, s+=halfpi) {
+    angles.push(s);
+  }
+
+  for (i=0, n=angles.length; i<n; ++i) {
+    a = angles[i];
+    x = Math.cos(a); ix = ir*x; ox = or*x;
+    y = Math.sin(a); iy = ir*y; oy = or*y;
+    xmin = Math.min(xmin, ix, ox);
+    xmax = Math.max(xmax, ix, ox);
+    ymin = Math.min(ymin, iy, oy);
+    ymax = Math.max(ymax, iy, oy);
+  }
+
+  return strokeBounds(o, bounds.set(
+    cx + xmin,
+    cy + ymin,
+    cx + xmax,
+    cy + ymax
+  ));
+}
+
+function symbol(o, bounds) {
+  var size = o.size != null ? o.size : 100,
+      x = o.x || 0,
+      y = o.y || 0,
+      r, t, rx, ry;
+
+  switch (o.shape) {
+    case 'cross':
+      t = 3 * Math.sqrt(size / 5) / 2;
+      bounds.set(x-t, y-t, x+t, y+t);
+      break;
+
+    case 'diamond':
+      ry = Math.sqrt(size / (2 * tan30));
+      rx = ry * tan30;
+      bounds.set(x-rx, y-ry, x+rx, y+ry);
+      break;
+
+    case 'square':
+      t = Math.sqrt(size);
+      r = t / 2;
+      bounds.set(x-r, y-r, x+r, y+r);
+      break;
+
+    case 'triangle-down':
+      rx = Math.sqrt(size / sqrt3);
+      ry = rx * sqrt3 / 2;
+      bounds.set(x-rx, y-ry, x+rx, y+ry);
+      break;
+
+    case 'triangle-up':
+      rx = Math.sqrt(size / sqrt3);
+      ry = rx * sqrt3 / 2;
+      bounds.set(x-rx, y-ry, x+rx, y+ry);
+      break;
+
+    default:
+      r = Math.sqrt(size/Math.PI);
+      bounds.set(x-r, y-r, x+r, y+r);
+  }
+
+  return strokeBounds(o, bounds);
+}
+
+function textMark(o, bounds, noRotate) {
+  var g = context(),
+      h = text.size(o),
+      a = o.align,
+      r = o.radius || 0,
+      x = (o.x || 0),
+      y = (o.y || 0),
+      dx = (o.dx || 0),
+      dy = (o.dy || 0) + text.offset(o) - Math.round(0.8*h), // use 4/5 offset
+      w, t;
+
+  if (r) {
+    t = (o.theta || 0) - Math.PI/2;
+    x += r * Math.cos(t);
+    y += r * Math.sin(t);
+  }
+
+  // horizontal alignment
+  g.font = text.font(o);
+  w = g.measureText(text.value(o.text)).width;
+  if (a === 'center') {
+    dx -= (w / 2);
+  } else if (a === 'right') {
+    dx -= w;
+  } else {
+    // left by default, do nothing
+  }
+
+  bounds.set(dx+=x, dy+=y, dx+w, dy+h);
+  if (o.angle && !noRotate) {
+    bounds.rotate(o.angle*Math.PI/180, x, y);
+  }
+  return bounds.expand(noRotate ? 0 : 1);
+}
+
+function group(g, bounds, includeLegends) {
+  var axes = g.axisItems || [],
+      items = g.items || [],
+      legends = g.legendItems || [],
+      j, m;
+
+  for (j=0, m=axes.length; j<m; ++j) {
+    bounds.union(axes[j].bounds);
+  }
+  for (j=0, m=items.length; j<m; ++j) {
+    bounds.union(items[j].bounds);
+  }
+  if (includeLegends) {
+    for (j=0, m=legends.length; j<m; ++j) {
+      bounds.union(legends[j].bounds);
+    }
+  }
+  if (g.width || g.height) {
+    strokeBounds(g, bounds
+      .add(0, 0)
+      .add(g.width || 0, g.height || 0));
+  }
+  return bounds.translate(g.x || 0, g.y || 0);
+}
+
+var methods = {
+  group:  group,
+  symbol: symbol,
+  image:  image,
+  rect:   rect,
+  rule:   rule,
+  arc:    arc,
+  text:   textMark,
+  path:   path,
+  area:   area,
+  line:   line
+};
+methods.area.nest = true;
+methods.line.nest = true;
+
+function itemBounds(item, func, opt) {
+  var type = item.mark.marktype;
+  func = func || methods[type];
+  if (func.nest) item = item.mark;
+
+  var curr = item.bounds,
+      prev = item['bounds:prev'] || (item['bounds:prev'] = new Bounds());
+
+  if (curr) {
+    prev.clear().union(curr);
+    curr.clear();
+  } else {
+    item.bounds = new Bounds();
+  }
+  func(item, item.bounds, opt);
+  if (!curr) prev.clear().union(item.bounds);
+  return item.bounds;
+}
+
+var DUMMY_ITEM = {mark: null};
+
+function markBounds(mark, bounds, opt) {
+  var type  = mark.marktype,
+      func  = methods[type],
+      items = mark.items,
+      hasi  = items && items.length,
+      i, n, o, b;
+
+  if (func.nest) {
+    o = hasi ? items[0]
+      : (DUMMY_ITEM.mark = mark, DUMMY_ITEM); // no items, so fake it
+    b = itemBounds(o, func, opt);
+    bounds = bounds && bounds.union(b) || b;
+    return bounds;
+  }
+
+  bounds = bounds || mark.bounds && mark.bounds.clear() || new Bounds();
+  if (hasi) {  
+    for (i=0, n=items.length; i<n; ++i) {
+      bounds.union(itemBounds(items[i], func, opt));
+    }
+  }
+  return (mark.bounds = bounds);
+}
+
+module.exports = {
+  mark:  markBounds,
+  item:  itemBounds,
+  text:  textMark,
+  group: group
+};
+
+},{"../path":48,"./Bounds":74,"./BoundsContext":75,"./canvas":80,"./svg":83,"./text":84}],80:[function(require,module,exports){
+(function (global){
+function instance(w, h) {
+  w = w || 1;
+  h = h || 1;
+  var canvas;
+
+  if (typeof document !== 'undefined' && document.createElement) {
+    canvas = document.createElement('canvas');
+    canvas.width = w;
+    canvas.height = h;
+  } else {
+    var Canvas = (typeof window !== "undefined" ? window['canvas'] : typeof global !== "undefined" ? global['canvas'] : null);
+    if (!Canvas.prototype) return null;
+    canvas = new Canvas(w, h);
+  }
+  return lineDash(canvas);
+}
+
+function resize(canvas, w, h, p, retina) {
+  var g = this._ctx = canvas.getContext('2d'), 
+      s = 1;
+
+  canvas.width = w + p.left + p.right;
+  canvas.height = h + p.top + p.bottom;
+
+  // if browser canvas, attempt to modify for retina display
+  if (retina && typeof HTMLElement !== 'undefined' &&
+      canvas instanceof HTMLElement)
+  {
+    g.pixelratio = (s = pixelRatio(canvas) || 1);
+  }
+
+  g.setTransform(s, 0, 0, s, s*p.left, s*p.top);
+  return canvas;
+}
+
+function pixelRatio(canvas) {
+  var g = canvas.getContext('2d');
+
+  // get canvas pixel data
+  var devicePixelRatio = window && window.devicePixelRatio || 1,
+      backingStoreRatio = (
+        g.webkitBackingStorePixelRatio ||
+        g.mozBackingStorePixelRatio ||
+        g.msBackingStorePixelRatio ||
+        g.oBackingStorePixelRatio ||
+        g.backingStorePixelRatio) || 1,
+      ratio = devicePixelRatio / backingStoreRatio;
+
+  if (devicePixelRatio !== backingStoreRatio) {
+    // set actual and visible canvas size
+    var w = canvas.width,
+        h = canvas.height;
+    canvas.width = w * ratio;
+    canvas.height = h * ratio;
+    canvas.style.width = w + 'px';
+    canvas.style.height = h + 'px';
+  }
+
+  return ratio;
+}
+
+function lineDash(canvas) {
+  var g = canvas.getContext('2d');
+  if (g.vgLineDash) return; // already initialized!
+
+  var NOOP = function() {},
+      NODASH = [];
+  
+  if (g.setLineDash) {
+    g.vgLineDash = function(dash) { this.setLineDash(dash || NODASH); };
+    g.vgLineDashOffset = function(off) { this.lineDashOffset = off; };
+  } else if (g.webkitLineDash !== undefined) {
+  	g.vgLineDash = function(dash) { this.webkitLineDash = dash || NODASH; };
+    g.vgLineDashOffset = function(off) { this.webkitLineDashOffset = off; };
+  } else if (g.mozDash !== undefined) {
+    g.vgLineDash = function(dash) { this.mozDash = dash; };
+    g.vgLineDashOffset = NOOP;
+  } else {
+    g.vgLineDash = NOOP;
+    g.vgLineDashOffset = NOOP;
+  }
+  return canvas;
+}
+
+module.exports = {
+  instance:   instance,
+  resize:     resize,
+  lineDash:   lineDash
+};
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{}],81:[function(require,module,exports){
+// create a new DOM element
+function create(doc, tag, ns) {
+  return ns ? doc.createElementNS(ns, tag) : doc.createElement(tag);
+}
+
+// remove element from DOM
+// recursively remove parent elements if empty
+function remove(el) {
+  if (!el) return;
+  var p = el.parentNode;
+  if (p) {
+    p.removeChild(el);
+    if (!p.childNodes || !p.childNodes.length) remove(p);
+  }
+}
+
+module.exports = {
+  // find first child element with matching tag
+  find: function(el, tag) {
+    tag = tag.toLowerCase();
+    for (var i=0, n=el.childNodes.length; i<n; ++i) {
+      if (el.childNodes[i].tagName.toLowerCase() === tag) {
+        return el.childNodes[i];
+      }
+    }
+  },
+  // retrieve child element at given index
+  // create & insert if doesn't exist or if tag/className do not match
+  child: function(el, index, tag, ns, className, insert) {
+    var a, b;
+    a = b = el.childNodes[index];
+    if (!a || insert ||
+        a.tagName.toLowerCase() !== tag.toLowerCase() ||
+        className && a.getAttribute('class') != className) {
+      a = create(el.ownerDocument, tag, ns);
+      el.insertBefore(a, b);
+      if (className) a.setAttribute('class', className);
+    }
+    return a;
+  },
+  // remove all child elements at or above the given index
+  clear: function(el, index) {
+    var curr = el.childNodes.length;
+    while (curr > index) {
+      el.removeChild(el.childNodes[--curr]);
+    }
+    return el;
+  },
+  remove: remove,
+  // generate css class name for mark
+  cssClass: function(mark) {
+    return 'mark-' + mark.marktype + (mark.name ? ' '+mark.name : '');
+  },
+  // generate string for an opening xml tag
+  // tag: the name of the xml tag
+  // attr: hash of attribute name-value pairs to include
+  // raw: additional raw string to include in tag markup
+  openTag: function(tag, attr, raw) {
+    var s = '<' + tag, key, val;
+    if (attr) {
+      for (key in attr) {
+        val = attr[key];
+        if (val != null) {
+          s += ' ' + key + '="' + val + '"';
+        }
+      }
+    }
+    if (raw) s += ' ' + raw;
+    return s + '>';
+  },
+  // generate string for closing xml tag
+  // tag: the name of the xml tag
+  closeTag: function(tag) {
+    return '</' + tag + '>';
+  }
+};
+
+},{}],82:[function(require,module,exports){
+var bound = require('../util/bound');
+
+var sets = [
+  'items',
+  'axisItems',
+  'legendItems'
+];
+
+var keys = [
+  'marktype', 'name', 'interactive', 'clip',
+  'items', 'axisItems', 'legendItems', 'layer',
+  'x', 'y', 'width', 'height', 'align', 'baseline',             // layout
+  'fill', 'fillOpacity', 'opacity',                             // fill
+  'stroke', 'strokeOpacity', 'strokeWidth', 'strokeCap',        // stroke
+  'strokeDash', 'strokeDashOffset',                             // stroke dash
+  'startAngle', 'endAngle', 'innerRadius', 'outerRadius',       // arc
+  'interpolate', 'tension', 'orient',                           // area, line
+  'url',                                                        // image
+  'path',                                                       // path
+  'x2', 'y2',                                                   // rule
+  'size', 'shape',                                              // symbol
+  'text', 'angle', 'theta', 'radius', 'dx', 'dy',               // text
+  'font', 'fontSize', 'fontWeight', 'fontStyle', 'fontVariant'  // font
+];
+
+function toJSON(scene, indent) {
+  return JSON.stringify(scene, keys, indent);
+}
+
+function fromJSON(json) {
+  var scene = (typeof json === 'string' ? JSON.parse(json) : json);
+  return initialize(scene);
+}
+
+function initialize(scene) {
+  var type = scene.marktype,
+      i, n, s, m, items;
+
+  for (s=0, m=sets.length; s<m; ++s) {
+    if ((items = scene[sets[s]])) {
+      for (i=0, n=items.length; i<n; ++i) {
+        items[i][type ? 'mark' : 'group'] = scene;
+        if (!type || type === 'group') {
+          initialize(items[i]);
+        }
+      }
+    }
+  }
+
+  if (type) bound.mark(scene);
+  return scene;
+}
+
+module.exports = {
+  toJSON:   toJSON,
+  fromJSON: fromJSON
+};
+},{"../util/bound":79}],83:[function(require,module,exports){
+(function (global){
+var d3_svg = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null).svg;
+
+function x(o)     { return o.x || 0; }
+function y(o)     { return o.y || 0; }
+function xw(o)    { return (o.x || 0) + (o.width || 0); }
+function yh(o)    { return (o.y || 0) + (o.height || 0); }
+function size(o)  { return o.size == null ? 100 : o.size; }
+function shape(o) { return o.shape || 'circle'; }
+
+var areav = d3_svg.area().x(x).y1(y).y0(yh),
+    areah = d3_svg.area().y(y).x1(x).x0(xw),
+    line  = d3_svg.line().x(x).y(y);
+
+module.exports = {
+  metadata: {
+    'version': '1.1',
+    'xmlns': 'http://www.w3.org/2000/svg',
+    'xmlns:xlink': 'http://www.w3.org/1999/xlink'
+  },
+  path: {
+    arc: d3_svg.arc(),
+    symbol: d3_svg.symbol().type(shape).size(size),
+    area: function(items) {
+      var o = items[0];
+      return (o.orient === 'horizontal' ? areah : areav)
+        .interpolate(o.interpolate || 'linear')
+        .tension(o.tension || 0.7)
+        (items);
+    },
+    line: function(items) {
+      var o = items[0];
+      return line
+        .interpolate(o.interpolate || 'linear')
+        .tension(o.tension || 0.7)
+        (items);
+    }
+  },
+  textAlign: {
+    'left':   'start',
+    'center': 'middle',
+    'right':  'end'
+  },
+  textBaseline: {
+    'top':    'before-edge',
+    'bottom': 'after-edge',
+    'middle': 'central'
+  },
+  styles: {
+    'fill':             'fill',
+    'fillOpacity':      'fill-opacity',
+    'stroke':           'stroke',
+    'strokeWidth':      'stroke-width',
+    'strokeOpacity':    'stroke-opacity',
+    'strokeCap':        'stroke-linecap',
+    'strokeDash':       'stroke-dasharray',
+    'strokeDashOffset': 'stroke-dashoffset',
+    'opacity':          'opacity'
+  },
+  styleProperties: [
+    'fill',
+    'fillOpacity',
+    'stroke',
+    'strokeWidth',
+    'strokeOpacity',
+    'strokeCap',
+    'strokeDash',
+    'strokeDashOffset',
+    'opacity'
+  ]
+};
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{}],84:[function(require,module,exports){
+function size(item) {
+  return item.fontSize != null ? item.fontSize : 11;
+}
+
+module.exports = {
+  size: size,
+  value: function(s) {
+    return s != null ? String(s) : '';
+  },
+  font: function(item, quote) {
+    var font = item.font;
+    if (quote && font) {
+      font = String(font).replace(/\"/g, '\'');
+    }
+    return '' +
+      (item.fontStyle ? item.fontStyle + ' ' : '') +
+      (item.fontVariant ? item.fontVariant + ' ' : '') +
+      (item.fontWeight ? item.fontWeight + ' ' : '') +
+      size(item) + 'px ' +
+      (font || 'sans-serif');
+  },
+  offset: function(item) {
+    // perform our own font baseline calculation
+    // why? not all browsers support SVG 1.1 'alignment-baseline' :(
+    var baseline = item.baseline,
+        h = size(item);
+    return Math.round(
+      baseline === 'top'    ?  0.93*h :
+      baseline === 'middle' ?  0.30*h :
+      baseline === 'bottom' ? -0.21*h : 0
+    );
+  }
+};
+
+},{}],85:[function(require,module,exports){
+var sg = require('vega-scenegraph').render,
+    canvas = sg.canvas,
+    svg = sg.svg.string,
+    View = require('./View');
+
+function HeadlessView(width, height, model) {
+  View.call(null, width, height, model);
+  this._type = 'canvas';
+  this._renderers = {canvas: canvas, svg: svg};
+}
+
+var prototype = (HeadlessView.prototype = new View());
+
+prototype.renderer = function(type) {
+  if(type) this._type = type;
+  return View.prototype.renderer.apply(this, arguments);
+};
+
+prototype.canvas = function() {
+  return (this._type === 'canvas') ? this._renderer.canvas() : null;
+};
+
+prototype.canvasAsync = function(callback) {
+  var r = this._renderer, view = this;
+  
+  function wait() {
+    if (r.pendingImages() === 0) {
+      view.render(); // re-render with all images
+      callback(view.canvas());
+    } else {
+      setTimeout(wait, 10);
+    }
+  }
+
+  // if images loading, poll until ready
+  if (this._type !== 'canvas') return null;
+  if (r.pendingImages() > 0) { wait(); } else { callback(this.canvas()); }
+};
+
+prototype.svg = function() {
+  return (this._type === 'svg') ? this._renderer.svg() : null;
+};
+
+prototype.initialize = function() {    
+  var w = this._width,
+      h = this._height,
+      bg  = this._bgcolor,
+      pad = this._padding,
+      config = this.model().config();
+
+  if (this._viewport) {
+    w = this._viewport[0] - (pad ? pad.left + pad.right : 0);
+    h = this._viewport[1] - (pad ? pad.top + pad.bottom : 0);
+  }
+
+  this._renderer = (this._renderer || new this._io.Renderer(config.load))
+    .initialize(null, w, h, pad)
+    .background(bg);
+  
+  return this;
+};
+
+module.exports = HeadlessView;
+},{"./View":87,"vega-scenegraph":46}],86:[function(require,module,exports){
+var dl = require('datalib'),
+    df = require('vega-dataflow'),
+    ChangeSet = df.ChangeSet,
+    Base = df.Graph.prototype,
+    Node  = df.Node, // jshint ignore:line
+    GroupBuilder = require('../scene/GroupBuilder'),
+    visit = require('../scene/visit'),
+    config = require('./config');
+
+function Model(cfg) {
+  this._defs = {};
+  this._predicates = {};
+  this._scene = null;
+
+  this._node = null;
+  this._builder = null; // Top-level scenegraph builder
+
+  this._reset = {axes: false, legends: false};
+
+  this.config(cfg);
+  Base.init.call(this);
+}
+
+var prototype = (Model.prototype = Object.create(Base));
+prototype.constructor = Model;
+
+prototype.defs = function(defs) {
+  if (!arguments.length) return this._defs;
+  this._defs = defs;
+  return this;
+};
+
+prototype.config = function(cfg) {
+  if (!arguments.length) return this._config;
+  this._config = Object.create(config);
+  for (var name in cfg) {
+    var x = cfg[name], y = this._config[name];
+    if (dl.isObject(x) && dl.isObject(y)) {
+      dl.extend(y, x);
+    } else {
+      this._config[name] = x;
+    }
+  }
+
+  return this;
+};
+
+prototype.width = function(width) {
+  if (this._defs) this._defs.width = width;
+  if (this._defs && this._defs.marks) this._defs.marks.width = width;
+  if (this._scene) {
+    this._scene.items[0].width = width;
+    this._scene.items[0]._dirty = true;
+  }
+  this._reset.axes = true;
+  return this;
+};
+
+prototype.height = function(height) {
+  if (this._defs) this._defs.height = height;
+  if (this._defs && this._defs.marks) this._defs.marks.height = height;
+  if (this._scene) {
+    this._scene.items[0].height = height;
+    this._scene.items[0]._dirty = true;
+  }
+  this._reset.axes = true;
+  return this;
+};
+
+prototype.node = function() {
+  return this._node || (this._node = new Node(this));
+};
+
+prototype.data = function() {
+  var data = Base.data.apply(this, arguments);
+  if (arguments.length > 1) {  // new Datasource
+    this.node().addListener(data.pipeline()[0]);
+  }
+  return data;
+};
+
+function predicates(name) {
+  var m = this, pred = {};
+  if (!dl.isArray(name)) return this._predicates[name];
+  name.forEach(function(n) { pred[n] = m._predicates[n]; });
+  return pred;
+}
+
+prototype.predicate = function(name, predicate) {
+  if (arguments.length === 1) return predicates.call(this, name);
+  return (this._predicates[name] = predicate);
+};
+
+prototype.predicates = function() { return this._predicates; };
+
+prototype.scene = function(renderer) {
+  if (!arguments.length) return this._scene;
+  if (this._builder) this.node().removeListener(this._builder.disconnect());
+  this._builder = new GroupBuilder(this, this._defs.marks, this._scene={});
+  this.node().addListener(this._builder.connect());
+  var p = this._builder.pipeline();
+  p[p.length-1].addListener(renderer);
+  return this;
+};
+
+prototype.reset = function() {
+  if (this._scene && this._reset.axes) {
+    visit(this._scene, function(item) {
+      if (item.axes) item.axes.forEach(function(axis) { axis.reset(); });
+    });
+    this._reset.axes = false;
+  }
+  if (this._scene && this._reset.legends) {
+    visit(this._scene, function(item) {
+      if (item.legends) item.legends.forEach(function(l) { l.reset(); });
+    });
+    this._reset.legends = false;
+  }
+  return this;
+};
+
+prototype.addListener = function(l) {
+  this.node().addListener(l);
+};
+
+prototype.removeListener = function(l) {
+  this.node().removeListener(l); 
+};
+
+prototype.fire = function(cs) {
+  if (!cs) cs = ChangeSet.create();
+  this.propagate(cs, this.node());
+};
+
+module.exports = Model;
+},{"../scene/GroupBuilder":110,"../scene/visit":115,"./config":88,"datalib":24,"vega-dataflow":39}],87:[function(require,module,exports){
+(function (global){
+var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null),
+    dl = require('datalib'),
+    df = require('vega-dataflow'),
+    sg = require('vega-scenegraph').render,
+    log = require('vega-logging'),
+    Deps = df.Dependencies,
+    parseStreams = require('../parse/streams'),
+    Encoder = require('../scene/Encoder'),
+    Transition = require('../scene/Transition');
+
+function View(el, width, height) {
+  this._el    = null;
+  this._model = null;
+  this._width = this.__width = width || 500;
+  this._height  = this.__height = height || 300;
+  this._bgcolor = null;
+  this._autopad = 1;
+  this._padding = {top:0, left:0, bottom:0, right:0};
+  this._viewport = null;
+  this._renderer = null;
+  this._handler  = null;
+  this._streamer = null; // Targeted update for streaming changes
+  this._changeset = null;
+  this._repaint = true; // Full re-render on every re-init
+  this._renderers = sg;
+  this._io  = null;
+  this._api = {}; // Stash streaming data API sandboxes.
+}
+
+var prototype = View.prototype;
+
+prototype.model = function(model) {
+  if (!arguments.length) return this._model;
+  if (this._model !== model) {
+    this._model = model;
+    this._streamer = new df.Node(model);
+    this._streamer._rank = -1;  // HACK: To reduce re-ranking churn.
+    this._changeset = df.ChangeSet.create();
+    if (this._handler) this._handler.model(model);
+  }
+  return this;
+};
+
+// Sandboxed streaming data API
+function streaming(src) {
+  var view = this,
+      ds = this._model.data(src),
+      name = ds.name(),
+      listener = ds.pipeline()[0],
+      streamer = this._streamer,
+      api = {};
+
+  // If we have it stashed, don't create a new closure. 
+  if (this._api[src]) return this._api[src];
+
+  api.insert = function(vals) {
+    ds.insert(dl.duplicate(vals));  // Don't pollute the environment
+    streamer.addListener(listener);
+    view._changeset.data[name] = 1;
+    return api;
+  };
+
+  api.update = function() {
+    streamer.addListener(listener);
+    view._changeset.data[name] = 1;
+    return (ds.update.apply(ds, arguments), api);
+  };
+
+  api.remove = function() {
+    streamer.addListener(listener);
+    view._changeset.data[name] = 1;
+    return (ds.remove.apply(ds, arguments), api);
+  };
+
+  api.values = function() { return ds.values(); };    
+
+  return (this._api[src] = api);
+}
+
+prototype.data = function(data) {
+  var v = this;
+  if (!arguments.length) return v._model.values();
+  else if (dl.isString(data)) return streaming.call(v, data);
+  else if (dl.isObject(data)) {
+    dl.keys(data).forEach(function(k) {
+      var api = streaming.call(v, k);
+      data[k](api);
+    });
+  }
+  return this;
+};
+
+prototype.signal = function(name, value) {
+  var m  = this._model,
+      cs = this._changeset,
+      streamer = this._streamer,
+      setter = name; 
+
+  if (!arguments.length) {
+    return m.values(Deps.SIGNALS);
+  } else if (arguments.length == 1 && dl.isString(name)) {
+    return m.values(Deps.SIGNALS, name);
+  }
+
+  if (arguments.length == 2) {
+    setter = {};
+    setter[name] = value;
+  }
+
+  dl.keys(setter).forEach(function(k) {
+    streamer.addListener(m.signal(k).value(setter[k]));
+    cs.signals[k] = 1;
+    cs.reflow = true;
+  });
+
+  return this;
+};
+
+prototype.width = function(width) {
+  if (!arguments.length) return this.__width;
+  if (this.__width !== width) {
+    this._width = this.__width = width;
+    this.model().width(width);
+    this.initialize();
+    if (this._strict) this._autopad = 1;
+  }
+  return this;
+};
+
+prototype.height = function(height) {
+  if (!arguments.length) return this.__height;
+  if (this.__height !== height) {
+    this._height = this.__height = height;
+    this.model().height(height);
+    this.initialize();
+    if (this._strict) this._autopad = 1;
+  }
+  return this;
+};
+
+prototype.background = function(bgcolor) {
+  if (!arguments.length) return this._bgcolor;
+  if (this._bgcolor !== bgcolor) {
+    this._bgcolor = bgcolor;
+    this.initialize();
+  }
+  return this;
+};
+
+prototype.padding = function(pad) {
+  if (!arguments.length) return this._padding;
+  if (this._padding !== pad) {
+    if (dl.isString(pad)) {
+      this._autopad = 1;
+      this._padding = {top:0, left:0, bottom:0, right:0};
+      this._strict = (pad === 'strict');
+    } else {
+      this._autopad = 0;
+      this._padding = pad;
+      this._strict = false;
+    }
+    if (this._renderer) this._renderer.resize(this._width, this._height, pad);
+    if (this._handler)  this._handler.padding(pad);
+  }
+  return (this._repaint = true, this);
+};
+
+prototype.autopad = function(opt) {
+  if (this._autopad < 1) return this;
+  else this._autopad = 0;
+
+  var b = this.model().scene().bounds,
+      pad = this._padding,
+      config = this.model().config(),
+      inset = config.autopadInset,
+      l = b.x1 < 0 ? Math.ceil(-b.x1) + inset : 0,
+      t = b.y1 < 0 ? Math.ceil(-b.y1) + inset : 0,
+      r = b.x2 > this._width  ? Math.ceil(+b.x2 - this._width) + inset : 0;
+  b = b.y2 > this._height ? Math.ceil(+b.y2 - this._height) + inset : 0;
+  pad = {left:l, top:t, right:r, bottom:b};
+
+  if (this._strict) {
+    this._autopad = 0;
+    this._padding = pad;
+    this._width = Math.max(0, this.__width - (l+r));
+    this._height = Math.max(0, this.__height - (t+b));
+
+    this._model.width(this._width)
+      .height(this._height).reset();
+
+    this.initialize()
+      .update({props:'enter'}).update({props:'update'});
+  } else {
+    this.padding(pad).update(opt);
+  }
+  return this;
+};
+
+prototype.viewport = function(size) {
+  if (!arguments.length) return this._viewport;
+  if (this._viewport !== size) {
+    this._viewport = size;
+    this.initialize();
+  }
+  return this;
+};
+
+prototype.renderer = function(type) {
+  if (!arguments.length) return this._renderer;
+  if (this._renderers[type]) type = this._renderers[type];
+  else if (dl.isString(type)) throw new Error('Unknown renderer: ' + type);
+  else if (!type) throw new Error('No renderer specified');
+
+  if (this._io !== type) {
+    this._io = type;
+    this._renderer = null;
+    this.initialize();
+    if (this._build) this.render();
+  }
+  return this;
+};
+
+prototype.initialize = function(el) {
+  var v = this, prevHandler,
+      w = v._width, h = v._height, pad = v._padding, bg = v._bgcolor,
+      config = this.model().config();
+
+  if (!arguments.length || el === null) {
+    el = this._el ? this._el.parentNode : null;
+    if (!el) return this;  // This View cannot init w/o an
+  }
+
+  // clear pre-existing container
+  d3.select(el).select('div.vega').remove();
+  
+  // add div container
+  this._el = el = d3.select(el)
+    .append('div')
+    .attr('class', 'vega')
+    .style('position', 'relative')
+    .node();
+  if (v._viewport) {
+    d3.select(el)
+      .style('width',  (v._viewport[0] || w)+'px')
+      .style('height', (v._viewport[1] || h)+'px')
+      .style('overflow', 'auto');
+  }
+
+  // renderer
+  sg.canvas.Renderer.RETINA = config.render.retina;
+  v._renderer = (v._renderer || new this._io.Renderer(config.load))
+    .initialize(el, w, h, pad)
+    .background(bg);
+  
+  // input handler
+  prevHandler = v._handler;
+  v._handler = new this._io.Handler()
+    .initialize(el, pad, v);
+
+  if (prevHandler) {
+    prevHandler.handlers().forEach(function(h) {
+      v._handler.on(h.type, h.handler);
+    });
+  } else {
+    // Register event listeners for signal stream definitions.
+    v._detach = parseStreams(this);
+  }
+  
+  return (this._repaint = true, this);
+};
+
+prototype.destroy = function() {
+  if (this._detach) this._detach();
+};
+
+function build() {
+  var v = this;
+  v._renderNode = new df.Node(v._model)
+    .router(true);
+
+  v._renderNode.evaluate = function(input) {
+    log.debug(input, ['rendering']);
+
+    var s = v._model.scene(),
+        h = v._handler;
+
+    if (h && h.scene) h.scene(s);
+
+    if (input.trans) {
+      input.trans.start(function(items) { v._renderer.render(s, items); });
+    } else if (v._repaint) {
+      v._renderer.render(s);
+      v._repaint = false;
+    } else if (input.dirty.length) {
+      v._renderer.render(s, input.dirty);
+    }
+
+    if (input.dirty.length) {
+      input.dirty.forEach(function(i) { i._dirty = false; });
+      s.items[0]._dirty = false;
+    }
+
+    // For all updated datasources, clear their previous values.
+    for (var d in input.data) { v._model.data(d).synchronize(); }
+    return input;
+  };
+
+  return (v._model.scene(v._renderNode), true);  
+}
+
+prototype.update = function(opt) {
+  opt = opt || {};
+  var v = this,
+      trans = opt.duration ? new Transition(opt.duration, opt.ease) : null;
+
+  var cs = v._changeset;
+  if (trans) cs.trans = trans;
+  if (opt.props !== undefined) {
+    if (dl.keys(cs.data).length > 0) {
+      throw Error(
+        'New data values are not reflected in the visualization.' +
+        ' Please call view.update() before updating a specified property set.'
+      );
+    }
+
+    cs.reflow  = true;
+    cs.request = opt.props;
+  }
+
+  var built = v._build;
+  v._build = v._build || build.call(this);
+
+  // If specific items are specified, short-circuit dataflow graph.
+  // Else-If there are streaming updates, perform a targeted propagation.
+  // Otherwise, reevaluate the entire model (datasources + scene).
+  if (opt.items && built) { 
+    Encoder.update(this._model, opt.trans, opt.props, opt.items, cs.dirty);
+    v._renderNode.evaluate(cs);
+  } else if (v._streamer.listeners().length && built) {
+    v._model.propagate(cs, v._streamer);
+    v._streamer.disconnect();
+  } else {
+    v._model.fire(cs);
+  }
+
+  v._changeset = df.ChangeSet.create();
+
+  return v.autopad(opt);
+};
+
+prototype.toImageURL = function(type) {
+  var v = this, Renderer;
+
+  // lookup appropriate renderer
+  switch (type || 'png') {
+    case 'canvas':
+    case 'png':
+      Renderer = sg.canvas.Renderer; break;
+    case 'svg':
+      Renderer = sg.svg.string.Renderer; break;
+    default: throw Error('Unrecognized renderer type: ' + type);
+  }
+
+  var retina = sg.canvas.Renderer.RETINA;
+  sg.canvas.Renderer.RETINA = false; // ignore retina screen
+
+  // render the scenegraph
+  var ren = new Renderer(v._model.config.load)
+    .initialize(null, v._width, v._height, v._padding)
+    .render(v._model.scene());
+
+  sg.canvas.Renderer.RETINA = retina; // restore retina settings
+
+  // return data url
+  if (type === 'svg') {
+    var blob = new Blob([ren.svg()], {type: 'image/svg+xml'});
+    return window.URL.createObjectURL(blob);
+  } else {
+    return ren.canvas().toDataURL('image/png');
+  }
+};
+
+prototype.render = function(items) {
+  this._renderer.render(this._model.scene(), items);
+  return this;
+};
+
+prototype.on = function() {
+  this._handler.on.apply(this._handler, arguments);
+  return this;
+};
+
+prototype.onSignal = function(name, handler) {
+  this._model.signal(name).on(handler);
+  return this;
+};
+
+prototype.off = function() {
+  this._handler.off.apply(this._handler, arguments);
+  return this;
+};
+
+prototype.offSignal = function(name, handler) {
+  this._model.signal(name).off(handler);
+  return this;
+};
+
+View.factory = function(model) {
+  var HeadlessView = require('./HeadlessView');
+  return function(opt) {
+    opt = opt || {};
+    var defs = model.defs();
+    var v = (opt.el ? new View() : new HeadlessView())
+      .model(model)
+      .renderer(opt.renderer || 'canvas')
+      .width(defs.width)
+      .height(defs.height)
+      .background(defs.background)
+      .padding(defs.padding)
+      .viewport(defs.viewport)
+      .initialize(opt.el);
+
+    if (opt.data) v.data(opt.data);
+
+    if (opt.hover !== false && opt.el) {
+      v.on('mouseover', function(evt, item) {
+        if (item && item.hasPropertySet('hover')) {
+          this.update({props:'hover', items:item});
+        }
+      })
+      .on('mouseout', function(evt, item) {
+        if (item && item.hasPropertySet('hover')) {
+          this.update({props:'update', items:item});
+        }
+      });
+    }
+  
+    return v;
+  };    
+};
+
+module.exports = View;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"../parse/streams":105,"../scene/Encoder":109,"../scene/Transition":112,"./HeadlessView":85,"datalib":24,"vega-dataflow":39,"vega-logging":45,"vega-scenegraph":46}],88:[function(require,module,exports){
+(function (global){
+var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null),
+    config = {};
+
+config.load = {
+  // base url for loading external data files
+  // used only for server-side operation
+  baseURL: '',
+  // Allows domain restriction when using data loading via XHR.
+  // To enable, set it to a list of allowed domains
+  // e.g., ['wikipedia.org', 'eff.org']
+  domainWhiteList: false
+};
+
+// inset padding for automatic padding calculation
+config.autopadInset = 5;
+
+// extensible scale lookup table
+// all d3.scale.* instances also supported
+config.scale = {
+  time: d3.time.scale,
+  utc:  d3.time.scale.utc
+};
+
+// default rendering settings
+config.render = {
+  retina: true
+};
+
+// default axis properties
+config.axis = {
+  orient: 'bottom',
+  ticks: 10,
+  padding: 3,
+  axisColor: '#000',
+  gridColor: '#000',
+  gridOpacity: 0.15,
+  tickColor: '#000',
+  tickLabelColor: '#000',
+  axisWidth: 1,
+  tickWidth: 1,
+  tickSize: 6,
+  tickLabelFontSize: 11,
+  tickLabelFont: 'sans-serif',
+  titleColor: '#000',
+  titleFont: 'sans-serif',
+  titleFontSize: 11,
+  titleFontWeight: 'bold',
+  titleOffset: 35
+};
+
+// default legend properties
+config.legend = {
+  orient: 'right',
+  offset: 20,
+  padding: 3,
+  gradientStrokeColor: '#888',
+  gradientStrokeWidth: 1,
+  gradientHeight: 16,
+  gradientWidth: 100,
+  labelColor: '#000',
+  labelFontSize: 10,
+  labelFont: 'sans-serif',
+  labelAlign: 'left',
+  labelBaseline: 'middle',
+  labelOffset: 8,
+  symbolShape: 'circle',
+  symbolSize: 50,
+  symbolColor: '#888',
+  symbolStrokeWidth: 1,
+  titleColor: '#000',
+  titleFont: 'sans-serif',
+  titleFontSize: 11,
+  titleFontWeight: 'bold'
+};
+
+// default color values
+config.color = {
+  rgb: [128, 128, 128],
+  lab: [50, 0, 0],
+  hcl: [0, 0, 50],
+  hsl: [0, 0, 0.5]
+};
+
+// default scale ranges
+config.range = {
+  category10:  d3.scale.category10().range(),
+  category20:  d3.scale.category20().range(),
+  category20b: d3.scale.category20b().range(),
+  category20c: d3.scale.category20c().range(),
+  shapes: [
+    'circle',
+    'cross',
+    'diamond',
+    'square',
+    'triangle-down',
+    'triangle-up'
+  ]
+};
+
+module.exports = config;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{}],89:[function(require,module,exports){
+var dl = require('datalib'),
+    parse = require('../parse'),
+    Scale = require('../scene/Scale'),
+    config = require('./config');
+
+function compile(module, opt, schema) {
+  var s = module.schema;
+  if (!s) return;
+  if (s.refs) dl.extend(schema.refs, s.refs);
+  if (s.defs) dl.extend(schema.defs, s.defs);
+}
+
+module.exports = function(opt) {
+  var schema = null;
+  opt = opt || {};
+
+  // Compile if we're not loading the schema from a URL. 
+  // Load from a URL to extend the existing base schema.
+  if (opt.url) {
+    schema = dl.json(dl.extend({url: opt.url}, config.load));
+  } else {
+    schema = {
+      "$schema": "http://json-schema.org/draft-04/schema#",
+      "title": "Vega Visualization Specification Language",
+      "defs": {}, 
+      "refs": {}, 
+      "$ref": "#/defs/spec"
+    };
+
+    dl.keys(parse).forEach(function(k) { compile(parse[k], opt, schema); });
+
+    // Scales aren't in the parser, add schema manually
+    compile(Scale, opt, schema);
+  }
+
+  // Extend schema to support custom mark properties or property sets.
+  if (opt.properties) dl.keys(opt.properties).forEach(function(k) {
+    schema.defs.propset.properties[k] = {"$ref": "#/refs/"+opt.properties[k]+"Value"};
+  });
+
+  if (opt.propertySets) dl.keys(opt.propertySets).forEach(function(k) {
+    schema.defs.mark.properties.properties.properties[k] = {"$ref": "#/defs/propset"};
+  });
+
+  return schema;
+};
+},{"../parse":95,"../scene/Scale":111,"./config":88,"datalib":24}],90:[function(require,module,exports){
+var dl = require('datalib'),
+    axs = require('../scene/axis');
+
+var ORIENT = {
+  "x":      "bottom",
+  "y":      "left",
+  "top":    "top",
+  "bottom": "bottom",
+  "left":   "left",
+  "right":  "right"
+};
+
+function parseAxes(model, spec, axes, group) {
+  var config = model.config();
+  (spec || []).forEach(function(def, index) {
+    axes[index] = axes[index] || axs(model);
+    parseAxis(config, def, index, axes[index], group);
+  });
+}
+
+function parseAxis(config, def, index, axis, group) {
+  // axis scale
+  if (def.scale !== undefined) {
+    axis.scale(group.scale(def.scale));
+  }
+
+  // axis orientation
+  axis.orient(def.orient || ORIENT[def.type]);
+  // axis offset
+  axis.offset(def.offset || 0);
+  // axis layer
+  axis.layer(def.layer || "front");
+  // axis grid lines
+  axis.grid(def.grid || false);
+  // axis title
+  axis.title(def.title || null);
+  // axis title offset
+  axis.titleOffset(def.titleOffset != null ?
+    def.titleOffset : config.axis.titleOffset);
+  // axis values
+  axis.tickValues(def.values || null);
+  // axis label formatting
+  axis.tickFormat(def.format || null);
+  axis.tickFormatType(def.formatType || null);
+  // axis tick subdivision
+  axis.tickSubdivide(def.subdivide || 0);
+  // axis tick padding
+  axis.tickPadding(def.tickPadding || config.axis.padding);
+
+  // axis tick size(s)
+  var size = [];
+  if (def.tickSize !== undefined) {
+    for (var i=0; i<3; ++i) size.push(def.tickSize);
+  } else {
+    var ts = config.axis.tickSize;
+    size = [ts, ts, ts];
+  }
+  if (def.tickSizeMajor != null) size[0] = def.tickSizeMajor;
+  if (def.tickSizeMinor != null) size[1] = def.tickSizeMinor;
+  if (def.tickSizeEnd   != null) size[2] = def.tickSizeEnd;
+  if (size.length) {
+    axis.tickSize.apply(axis, size);
+  }
+
+  // axis tick count
+  axis.tickCount(def.ticks || config.axis.ticks);
+
+  // style properties
+  var p = def.properties;
+  if (p && p.ticks) {
+    axis.majorTickProperties(p.majorTicks ?
+      dl.extend({}, p.ticks, p.majorTicks) : p.ticks);
+    axis.minorTickProperties(p.minorTicks ?
+      dl.extend({}, p.ticks, p.minorTicks) : p.ticks);
+  } else {
+    axis.majorTickProperties(p && p.majorTicks || {});
+    axis.minorTickProperties(p && p.minorTicks || {});
+  }
+  axis.tickLabelProperties(p && p.labels || {});
+  axis.titleProperties(p && p.title || {});
+  axis.gridLineProperties(p && p.grid || {});
+  axis.domainProperties(p && p.axis || {});
+}
+
+module.exports = parseAxes;
+},{"../scene/axis":113,"datalib":24}],91:[function(require,module,exports){
+(function (global){
+var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null);
+
+function parseBg(bg) {
+  // return null if input is null or undefined
+  if (bg == null) return null;
+  // run through d3 rgb to sanity check
+  return d3.rgb(bg) + "";  
+}
+
+module.exports = parseBg;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{}],92:[function(require,module,exports){
+var dl = require('datalib'),
+    log = require('vega-logging'),
+    parseTransforms = require('./transforms'),
+    parseModify = require('./modify');
+
+function parseData(model, spec, callback) {
+  var config = model.config(),
+      count = 0;
+
+  function loaded(d) {
+    return function(error, data) {
+      if (error) {
+        log.error('LOADING FAILED: ' + d.url + ' ' + error);
+      } else {
+        model.data(d.name).values(dl.read(data, d.format));
+      }
+      if (--count === 0) callback();
+    };
+  }
+
+  // process each data set definition
+  (spec || []).forEach(function(d) {
+    if (d.url) {
+      count += 1;
+      dl.load(dl.extend({url: d.url}, config.load), loaded(d));
+    }
+    parseData.datasource(model, d);
+  });
+
+  if (count === 0) setTimeout(callback, 1);
+  return spec;
+}
+
+parseData.datasource = function(model, d) {
+  var transform = (d.transform || []).map(function(t) {
+        return parseTransforms(model, t); 
+      }),
+      mod = (d.modify || []).map(function(m) {
+        return parseModify(model, m, d);
+      }),
+      ds = model.data(d.name, mod.concat(transform));
+
+  if (d.values) {
+    ds.values(dl.read(d.values, d.format));
+  } else if (d.source) {
+    // Derived ds will be pulsed by its src rather than the model.
+    ds.source(d.source).addListener(ds);  
+    model.removeListener(ds.pipeline()[0]); 
+  }
+
+  return ds;    
+};
+
+module.exports = parseData;
+},{"./modify":99,"./transforms":106,"datalib":24,"vega-logging":45}],93:[function(require,module,exports){
+module.exports = (function() {
+  /*
+   * Generated by PEG.js 0.8.0.
+   *
+   * http://pegjs.majda.cz/
+   */
+
+  function peg$subclass(child, parent) {
+    function ctor() { this.constructor = child; }
+    ctor.prototype = parent.prototype;
+    child.prototype = new ctor();
+  }
+
+  function SyntaxError(message, expected, found, offset, line, column) {
+    this.message  = message;
+    this.expected = expected;
+    this.found    = found;
+    this.offset   = offset;
+    this.line     = line;
+    this.column   = column;
+
+    this.name     = "SyntaxError";
+  }
+
+  peg$subclass(SyntaxError, Error);
+
+  function parse(input) {
+    var options = arguments.length > 1 ? arguments[1] : {},
+
+        peg$FAILED = {},
+
+        peg$startRuleFunctions = { start: peg$parsestart },
+        peg$startRuleFunction  = peg$parsestart,
+
+        peg$c0 = peg$FAILED,
+        peg$c1 = ",",
+        peg$c2 = { type: "literal", value: ",", description: "\",\"" },
+        peg$c3 = function(o, m) { return [o].concat(m); },
+        peg$c4 = function(o) { return [o]; },
+        peg$c5 = "[",
+        peg$c6 = { type: "literal", value: "[", description: "\"[\"" },
+        peg$c7 = "]",
+        peg$c8 = { type: "literal", value: "]", description: "\"]\"" },
+        peg$c9 = ">",
+        peg$c10 = { type: "literal", value: ">", description: "\">\"" },
+        peg$c11 = function(f1, f2, o) { return {start: f1, end: f2, middle: o}; },
+        peg$c12 = [],
+        peg$c13 = function(s, f) { return (s.filters = f, s); },
+        peg$c14 = function(s) { return s; },
+        peg$c15 = "(",
+        peg$c16 = { type: "literal", value: "(", description: "\"(\"" },
+        peg$c17 = ")",
+        peg$c18 = { type: "literal", value: ")", description: "\")\"" },
+        peg$c19 = function(m) { return {stream: m}; },
+        peg$c20 = "@",
+        peg$c21 = { type: "literal", value: "@", description: "\"@\"" },
+        peg$c22 = ":",
+        peg$c23 = { type: "literal", value: ":", description: "\":\"" },
+        peg$c24 = function(n, e) { return {event: e, name: n}; },
+        peg$c25 = function(m, e) { return {event: e, mark: m}; },
+        peg$c26 = function(t, e) { return {event: e, target: t}; },
+        peg$c27 = function(e) { return {event: e}; },
+        peg$c28 = function(s) { return {signal: s}; },
+        peg$c29 = "rect",
+        peg$c30 = { type: "literal", value: "rect", description: "\"rect\"" },
+        peg$c31 = "symbol",
+        peg$c32 = { type: "literal", value: "symbol", description: "\"symbol\"" },
+        peg$c33 = "path",
+        peg$c34 = { type: "literal", value: "path", description: "\"path\"" },
+        peg$c35 = "arc",
+        peg$c36 = { type: "literal", value: "arc", description: "\"arc\"" },
+        peg$c37 = "area",
+        peg$c38 = { type: "literal", value: "area", description: "\"area\"" },
+        peg$c39 = "line",
+        peg$c40 = { type: "literal", value: "line", description: "\"line\"" },
+        peg$c41 = "rule",
+        peg$c42 = { type: "literal", value: "rule", description: "\"rule\"" },
+        peg$c43 = "image",
+        peg$c44 = { type: "literal", value: "image", description: "\"image\"" },
+        peg$c45 = "text",
+        peg$c46 = { type: "literal", value: "text", description: "\"text\"" },
+        peg$c47 = "group",
+        peg$c48 = { type: "literal", value: "group", description: "\"group\"" },
+        peg$c49 = "mousedown",
+        peg$c50 = { type: "literal", value: "mousedown", description: "\"mousedown\"" },
+        peg$c51 = "mouseup",
+        peg$c52 = { type: "literal", value: "mouseup", description: "\"mouseup\"" },
+        peg$c53 = "click",
+        peg$c54 = { type: "literal", value: "click", description: "\"click\"" },
+        peg$c55 = "dblclick",
+        peg$c56 = { type: "literal", value: "dblclick", description: "\"dblclick\"" },
+        peg$c57 = "wheel",
+        peg$c58 = { type: "literal", value: "wheel", description: "\"wheel\"" },
+        peg$c59 = "keydown",
+        peg$c60 = { type: "literal", value: "keydown", description: "\"keydown\"" },
+        peg$c61 = "keypress",
+        peg$c62 = { type: "literal", value: "keypress", description: "\"keypress\"" },
+        peg$c63 = "keyup",
+        peg$c64 = { type: "literal", value: "keyup", description: "\"keyup\"" },
+        peg$c65 = "mousewheel",
+        peg$c66 = { type: "literal", value: "mousewheel", description: "\"mousewheel\"" },
+        peg$c67 = "mousemove",
+        peg$c68 = { type: "literal", value: "mousemove", description: "\"mousemove\"" },
+        peg$c69 = "mouseout",
+        peg$c70 = { type: "literal", value: "mouseout", description: "\"mouseout\"" },
+        peg$c71 = "mouseover",
+        peg$c72 = { type: "literal", value: "mouseover", description: "\"mouseover\"" },
+        peg$c73 = "mouseenter",
+        peg$c74 = { type: "literal", value: "mouseenter", description: "\"mouseenter\"" },
+        peg$c75 = "touchstart",
+        peg$c76 = { type: "literal", value: "touchstart", description: "\"touchstart\"" },
+        peg$c77 = "touchmove",
+        peg$c78 = { type: "literal", value: "touchmove", description: "\"touchmove\"" },
+        peg$c79 = "touchend",
+        peg$c80 = { type: "literal", value: "touchend", description: "\"touchend\"" },
+        peg$c81 = function(e) { return e; },
+        peg$c82 = /^[a-zA-Z0-9_\-]/,
+        peg$c83 = { type: "class", value: "[a-zA-Z0-9_\\-]", description: "[a-zA-Z0-9_\\-]" },
+        peg$c84 = function(n) { return n.join(""); },
+        peg$c85 = /^[a-zA-Z0-9\-_  #.>+~[\]=|\^$*]/,
+        peg$c86 = { type: "class", value: "[a-zA-Z0-9\\-_  #.>+~[\\]=|\\^$*]", description: "[a-zA-Z0-9\\-_  #.>+~[\\]=|\\^$*]" },
+        peg$c87 = function(c) { return c.join(""); },
+        peg$c88 = /^['"a-zA-Z0-9_().><=! \t-&|~]/,
+        peg$c89 = { type: "class", value: "['\"a-zA-Z0-9_().><=! \\t-&|~]", description: "['\"a-zA-Z0-9_().><=! \\t-&|~]" },
+        peg$c90 = function(v) { return v.join(""); },
+        peg$c91 = /^[ \t\r\n]/,
+        peg$c92 = { type: "class", value: "[ \\t\\r\\n]", description: "[ \\t\\r\\n]" },
+
+        peg$currPos          = 0,
+        peg$reportedPos      = 0,
+        peg$cachedPos        = 0,
+        peg$cachedPosDetails = { line: 1, column: 1, seenCR: false },
+        peg$maxFailPos       = 0,
+        peg$maxFailExpected  = [],
+        peg$silentFails      = 0,
+
+        peg$result;
+
+    if ("startRule" in options) {
+      if (!(options.startRule in peg$startRuleFunctions)) {
+        throw new Error("Can't start parsing from rule \"" + options.startRule + "\".");
+      }
+
+      peg$startRuleFunction = peg$startRuleFunctions[options.startRule];
+    }
+
+    function text() {
+      return input.substring(peg$reportedPos, peg$currPos);
+    }
+
+    function offset() {
+      return peg$reportedPos;
+    }
+
+    function line() {
+      return peg$computePosDetails(peg$reportedPos).line;
+    }
+
+    function column() {
+      return peg$computePosDetails(peg$reportedPos).column;
+    }
+
+    function expected(description) {
+      throw peg$buildException(
+        null,
+        [{ type: "other", description: description }],
+        peg$reportedPos
+      );
+    }
+
+    function error(message) {
+      throw peg$buildException(message, null, peg$reportedPos);
+    }
+
+    function peg$computePosDetails(pos) {
+      function advance(details, startPos, endPos) {
+        var p, ch;
+
+        for (p = startPos; p < endPos; p++) {
+          ch = input.charAt(p);
+          if (ch === "\n") {
+            if (!details.seenCR) { details.line++; }
+            details.column = 1;
+            details.seenCR = false;
+          } else if (ch === "\r" || ch === "\u2028" || ch === "\u2029") {
+            details.line++;
+            details.column = 1;
+            details.seenCR = true;
+          } else {
+            details.column++;
+            details.seenCR = false;
+          }
+        }
+      }
+
+      if (peg$cachedPos !== pos) {
+        if (peg$cachedPos > pos) {
+          peg$cachedPos = 0;
+          peg$cachedPosDetails = { line: 1, column: 1, seenCR: false };
+        }
+        advance(peg$cachedPosDetails, peg$cachedPos, pos);
+        peg$cachedPos = pos;
+      }
+
+      return peg$cachedPosDetails;
+    }
+
+    function peg$fail(expected) {
+      if (peg$currPos < peg$maxFailPos) { return; }
+
+      if (peg$currPos > peg$maxFailPos) {
+        peg$maxFailPos = peg$currPos;
+        peg$maxFailExpected = [];
+      }
+
+      peg$maxFailExpected.push(expected);
+    }
+
+    function peg$buildException(message, expected, pos) {
+      function cleanupExpected(expected) {
+        var i = 1;
+
+        expected.sort(function(a, b) {
+          if (a.description < b.description) {
+            return -1;
+          } else if (a.description > b.description) {
+            return 1;
+          } else {
+            return 0;
+          }
+        });
+
+        while (i < expected.length) {
+          if (expected[i - 1] === expected[i]) {
+            expected.splice(i, 1);
+          } else {
+            i++;
+          }
+        }
+      }
+
+      function buildMessage(expected, found) {
+        function stringEscape(s) {
+          function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); }
+
+          return s
+            .replace(/\\/g,   '\\\\')
+            .replace(/"/g,    '\\"')
+            .replace(/\x08/g, '\\b')
+            .replace(/\t/g,   '\\t')
+            .replace(/\n/g,   '\\n')
+            .replace(/\f/g,   '\\f')
+            .replace(/\r/g,   '\\r')
+            .replace(/[\x00-\x07\x0B\x0E\x0F]/g, function(ch) { return '\\x0' + hex(ch); })
+            .replace(/[\x10-\x1F\x80-\xFF]/g,    function(ch) { return '\\x'  + hex(ch); })
+            .replace(/[\u0180-\u0FFF]/g,         function(ch) { return '\\u0' + hex(ch); })
+            .replace(/[\u1080-\uFFFF]/g,         function(ch) { return '\\u'  + hex(ch); });
+        }
+
+        var expectedDescs = new Array(expected.length),
+            expectedDesc, foundDesc, i;
+
+        for (i = 0; i < expected.length; i++) {
+          expectedDescs[i] = expected[i].description;
+        }
+
+        expectedDesc = expected.length > 1
+          ? expectedDescs.slice(0, -1).join(", ")
+              + " or "
+              + expectedDescs[expected.length - 1]
+          : expectedDescs[0];
+
+        foundDesc = found ? "\"" + stringEscape(found) + "\"" : "end of input";
+
+        return "Expected " + expectedDesc + " but " + foundDesc + " found.";
+      }
+
+      var posDetails = peg$computePosDetails(pos),
+          found      = pos < input.length ? input.charAt(pos) : null;
+
+      if (expected !== null) {
+        cleanupExpected(expected);
+      }
+
+      return new SyntaxError(
+        message !== null ? message : buildMessage(expected, found),
+        expected,
+        found,
+        pos,
+        posDetails.line,
+        posDetails.column
+      );
+    }
+
+    function peg$parsestart() {
+      var s0;
+
+      s0 = peg$parsemerged();
+
+      return s0;
+    }
+
+    function peg$parsemerged() {
+      var s0, s1, s2, s3, s4, s5;
+
+      s0 = peg$currPos;
+      s1 = peg$parseordered();
+      if (s1 !== peg$FAILED) {
+        s2 = peg$parsesep();
+        if (s2 !== peg$FAILED) {
+          if (input.charCodeAt(peg$currPos) === 44) {
+            s3 = peg$c1;
+            peg$currPos++;
+          } else {
+            s3 = peg$FAILED;
+            if (peg$silentFails === 0) { peg$fail(peg$c2); }
+          }
+          if (s3 !== peg$FAILED) {
+            s4 = peg$parsesep();
+            if (s4 !== peg$FAILED) {
+              s5 = peg$parsemerged();
+              if (s5 !== peg$FAILED) {
+                peg$reportedPos = s0;
+                s1 = peg$c3(s1, s5);
+                s0 = s1;
+              } else {
+                peg$currPos = s0;
+                s0 = peg$c0;
+              }
+            } else {
+              peg$currPos = s0;
+              s0 = peg$c0;
+            }
+          } else {
+            peg$currPos = s0;
+            s0 = peg$c0;
+          }
+        } else {
+          peg$currPos = s0;
+          s0 = peg$c0;
+        }
+      } else {
+        peg$currPos = s0;
+        s0 = peg$c0;
+      }
+      if (s0 === peg$FAILED) {
+        s0 = peg$currPos;
+        s1 = peg$parseordered();
+        if (s1 !== peg$FAILED) {
+          peg$reportedPos = s0;
+          s1 = peg$c4(s1);
+        }
+        s0 = s1;
+      }
+
+      return s0;
+    }
+
+    function peg$parseordered() {
+      var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11, s12, s13;
+
+      s0 = peg$currPos;
+      if (input.charCodeAt(peg$currPos) === 91) {
+        s1 = peg$c5;
+        peg$currPos++;
+      } else {
+        s1 = peg$FAILED;
+        if (peg$silentFails === 0) { peg$fail(peg$c6); }
+      }
+      if (s1 !== peg$FAILED) {
+        s2 = peg$parsesep();
+        if (s2 !== peg$FAILED) {
+          s3 = peg$parsefiltered();
+          if (s3 !== peg$FAILED) {
+            s4 = peg$parsesep();
+            if (s4 !== peg$FAILED) {
+              if (input.charCodeAt(peg$currPos) === 44) {
+                s5 = peg$c1;
+                peg$currPos++;
+              } else {
+                s5 = peg$FAILED;
+                if (peg$silentFails === 0) { peg$fail(peg$c2); }
+              }
+              if (s5 !== peg$FAILED) {
+                s6 = peg$parsesep();
+                if (s6 !== peg$FAILED) {
+                  s7 = peg$parsefiltered();
+                  if (s7 !== peg$FAILED) {
+                    s8 = peg$parsesep();
+                    if (s8 !== peg$FAILED) {
+                      if (input.charCodeAt(peg$currPos) === 93) {
+                        s9 = peg$c7;
+                        peg$currPos++;
+                      } else {
+                        s9 = peg$FAILED;
+                        if (peg$silentFails === 0) { peg$fail(peg$c8); }
+                      }
+                      if (s9 !== peg$FAILED) {
+                        s10 = peg$parsesep();
+                        if (s10 !== peg$FAILED) {
+                          if (input.charCodeAt(peg$currPos) === 62) {
+                            s11 = peg$c9;
+                            peg$currPos++;
+                          } else {
+                            s11 = peg$FAILED;
+                            if (peg$silentFails === 0) { peg$fail(peg$c10); }
+                          }
+                          if (s11 !== peg$FAILED) {
+                            s12 = peg$parsesep();
+                            if (s12 !== peg$FAILED) {
+                              s13 = peg$parseordered();
+                              if (s13 !== peg$FAILED) {
+                                peg$reportedPos = s0;
+                                s1 = peg$c11(s3, s7, s13);
+                                s0 = s1;
+                              } else {
+                                peg$currPos = s0;
+                                s0 = peg$c0;
+                              }
+                            } else {
+                              peg$currPos = s0;
+                              s0 = peg$c0;
+                            }
+                          } else {
+                            peg$currPos = s0;
+                            s0 = peg$c0;
+                          }
+                        } else {
+                          peg$currPos = s0;
+                          s0 = peg$c0;
+                        }
+                      } else {
+                        peg$currPos = s0;
+                        s0 = peg$c0;
+                      }
+                    } else {
+                      peg$currPos = s0;
+                      s0 = peg$c0;
+                    }
+                  } else {
+                    peg$currPos = s0;
+                    s0 = peg$c0;
+                  }
+                } else {
+                  peg$currPos = s0;
+                  s0 = peg$c0;
+                }
+              } else {
+                peg$currPos = s0;
+                s0 = peg$c0;
+              }
+            } else {
+              peg$currPos = s0;
+              s0 = peg$c0;
+            }
+          } else {
+            peg$currPos = s0;
+            s0 = peg$c0;
+          }
+        } else {
+          peg$currPos = s0;
+          s0 = peg$c0;
+        }
+      } else {
+        peg$currPos = s0;
+        s0 = peg$c0;
+      }
+      if (s0 === peg$FAILED) {
+        s0 = peg$parsefiltered();
+      }
+
+      return s0;
+    }
+
+    function peg$parsefiltered() {
+      var s0, s1, s2, s3;
+
+      s0 = peg$currPos;
+      s1 = peg$parsestream();
+      if (s1 !== peg$FAILED) {
+        s2 = [];
+        s3 = peg$parsefilter();
+        if (s3 !== peg$FAILED) {
+          while (s3 !== peg$FAILED) {
+            s2.push(s3);
+            s3 = peg$parsefilter();
+          }
+        } else {
+          s2 = peg$c0;
+        }
+        if (s2 !== peg$FAILED) {
+          peg$reportedPos = s0;
+          s1 = peg$c13(s1, s2);
+          s0 = s1;
+        } else {
+          peg$currPos = s0;
+          s0 = peg$c0;
+        }
+      } else {
+        peg$currPos = s0;
+        s0 = peg$c0;
+      }
+      if (s0 === peg$FAILED) {
+        s0 = peg$currPos;
+        s1 = peg$parsestream();
+        if (s1 !== peg$FAILED) {
+          peg$reportedPos = s0;
+          s1 = peg$c14(s1);
+        }
+        s0 = s1;
+      }
+
+      return s0;
+    }
+
+    function peg$parsestream() {
+      var s0, s1, s2, s3, s4;
+
+      s0 = peg$currPos;
+      if (input.charCodeAt(peg$currPos) === 40) {
+        s1 = peg$c15;
+        peg$currPos++;
+      } else {
+        s1 = peg$FAILED;
+        if (peg$silentFails === 0) { peg$fail(peg$c16); }
+      }
+      if (s1 !== peg$FAILED) {
+        s2 = peg$parsemerged();
+        if (s2 !== peg$FAILED) {
+          if (input.charCodeAt(peg$currPos) === 41) {
+            s3 = peg$c17;
+            peg$currPos++;
+          } else {
+            s3 = peg$FAILED;
+            if (peg$silentFails === 0) { peg$fail(peg$c18); }
+          }
+          if (s3 !== peg$FAILED) {
+            peg$reportedPos = s0;
+            s1 = peg$c19(s2);
+            s0 = s1;
+          } else {
+            peg$currPos = s0;
+            s0 = peg$c0;
+          }
+        } else {
+          peg$currPos = s0;
+          s0 = peg$c0;
+        }
+      } else {
+        peg$currPos = s0;
+        s0 = peg$c0;
+      }
+      if (s0 === peg$FAILED) {
+        s0 = peg$currPos;
+        if (input.charCodeAt(peg$currPos) === 64) {
+          s1 = peg$c20;
+          peg$currPos++;
+        } else {
+          s1 = peg$FAILED;
+          if (peg$silentFails === 0) { peg$fail(peg$c21); }
+        }
+        if (s1 !== peg$FAILED) {
+          s2 = peg$parsename();
+          if (s2 !== peg$FAILED) {
+            if (input.charCodeAt(peg$currPos) === 58) {
+              s3 = peg$c22;
+              peg$currPos++;
+            } else {
+              s3 = peg$FAILED;
+              if (peg$silentFails === 0) { peg$fail(peg$c23); }
+            }
+            if (s3 !== peg$FAILED) {
+              s4 = peg$parseeventType();
+              if (s4 !== peg$FAILED) {
+                peg$reportedPos = s0;
+                s1 = peg$c24(s2, s4);
+                s0 = s1;
+              } else {
+                peg$currPos = s0;
+                s0 = peg$c0;
+              }
+            } else {
+              peg$currPos = s0;
+              s0 = peg$c0;
+            }
+          } else {
+            peg$currPos = s0;
+            s0 = peg$c0;
+          }
+        } else {
+          peg$currPos = s0;
+          s0 = peg$c0;
+        }
+        if (s0 === peg$FAILED) {
+          s0 = peg$currPos;
+          s1 = peg$parsemarkType();
+          if (s1 !== peg$FAILED) {
+            if (input.charCodeAt(peg$currPos) === 58) {
+              s2 = peg$c22;
+              peg$currPos++;
+            } else {
+              s2 = peg$FAILED;
+              if (peg$silentFails === 0) { peg$fail(peg$c23); }
+            }
+            if (s2 !== peg$FAILED) {
+              s3 = peg$parseeventType();
+              if (s3 !== peg$FAILED) {
+                peg$reportedPos = s0;
+                s1 = peg$c25(s1, s3);
+                s0 = s1;
+              } else {
+                peg$currPos = s0;
+                s0 = peg$c0;
+              }
+            } else {
+              peg$currPos = s0;
+              s0 = peg$c0;
+            }
+          } else {
+            peg$currPos = s0;
+            s0 = peg$c0;
+          }
+          if (s0 === peg$FAILED) {
+            s0 = peg$currPos;
+            s1 = peg$parsecss();
+            if (s1 !== peg$FAILED) {
+              if (input.charCodeAt(peg$currPos) === 58) {
+                s2 = peg$c22;
+                peg$currPos++;
+              } else {
+                s2 = peg$FAILED;
+                if (peg$silentFails === 0) { peg$fail(peg$c23); }
+              }
+              if (s2 !== peg$FAILED) {
+                s3 = peg$parseeventType();
+                if (s3 !== peg$FAILED) {
+                  peg$reportedPos = s0;
+                  s1 = peg$c26(s1, s3);
+                  s0 = s1;
+                } else {
+                  peg$currPos = s0;
+                  s0 = peg$c0;
+                }
+              } else {
+                peg$currPos = s0;
+                s0 = peg$c0;
+              }
+            } else {
+              peg$currPos = s0;
+              s0 = peg$c0;
+            }
+            if (s0 === peg$FAILED) {
+              s0 = peg$currPos;
+              s1 = peg$parseeventType();
+              if (s1 !== peg$FAILED) {
+                peg$reportedPos = s0;
+                s1 = peg$c27(s1);
+              }
+              s0 = s1;
+              if (s0 === peg$FAILED) {
+                s0 = peg$currPos;
+                s1 = peg$parsename();
+                if (s1 !== peg$FAILED) {
+                  peg$reportedPos = s0;
+                  s1 = peg$c28(s1);
+                }
+                s0 = s1;
+              }
+            }
+          }
+        }
+      }
+
+      return s0;
+    }
+
+    function peg$parsemarkType() {
+      var s0;
+
+      if (input.substr(peg$currPos, 4) === peg$c29) {
+        s0 = peg$c29;
+        peg$currPos += 4;
+      } else {
+        s0 = peg$FAILED;
+        if (peg$silentFails === 0) { peg$fail(peg$c30); }
+      }
+      if (s0 === peg$FAILED) {
+        if (input.substr(peg$currPos, 6) === peg$c31) {
+          s0 = peg$c31;
+          peg$currPos += 6;
+        } else {
+          s0 = peg$FAILED;
+          if (peg$silentFails === 0) { peg$fail(peg$c32); }
+        }
+        if (s0 === peg$FAILED) {
+          if (input.substr(peg$currPos, 4) === peg$c33) {
+            s0 = peg$c33;
+            peg$currPos += 4;
+          } else {
+            s0 = peg$FAILED;
+            if (peg$silentFails === 0) { peg$fail(peg$c34); }
+          }
+          if (s0 === peg$FAILED) {
+            if (input.substr(peg$currPos, 3) === peg$c35) {
+              s0 = peg$c35;
+              peg$currPos += 3;
+            } else {
+              s0 = peg$FAILED;
+              if (peg$silentFails === 0) { peg$fail(peg$c36); }
+            }
+            if (s0 === peg$FAILED) {
+              if (input.substr(peg$currPos, 4) === peg$c37) {
+                s0 = peg$c37;
+                peg$currPos += 4;
+              } else {
+                s0 = peg$FAILED;
+                if (peg$silentFails === 0) { peg$fail(peg$c38); }
+              }
+              if (s0 === peg$FAILED) {
+                if (input.substr(peg$currPos, 4) === peg$c39) {
+                  s0 = peg$c39;
+                  peg$currPos += 4;
+                } else {
+                  s0 = peg$FAILED;
+                  if (peg$silentFails === 0) { peg$fail(peg$c40); }
+                }
+                if (s0 === peg$FAILED) {
+                  if (input.substr(peg$currPos, 4) === peg$c41) {
+                    s0 = peg$c41;
+                    peg$currPos += 4;
+                  } else {
+                    s0 = peg$FAILED;
+                    if (peg$silentFails === 0) { peg$fail(peg$c42); }
+                  }
+                  if (s0 === peg$FAILED) {
+                    if (input.substr(peg$currPos, 5) === peg$c43) {
+                      s0 = peg$c43;
+                      peg$currPos += 5;
+                    } else {
+                      s0 = peg$FAILED;
+                      if (peg$silentFails === 0) { peg$fail(peg$c44); }
+                    }
+                    if (s0 === peg$FAILED) {
+                      if (input.substr(peg$currPos, 4) === peg$c45) {
+                        s0 = peg$c45;
+                        peg$currPos += 4;
+                      } else {
+                        s0 = peg$FAILED;
+                        if (peg$silentFails === 0) { peg$fail(peg$c46); }
+                      }
+                      if (s0 === peg$FAILED) {
+                        if (input.substr(peg$currPos, 5) === peg$c47) {
+                          s0 = peg$c47;
+                          peg$currPos += 5;
+                        } else {
+                          s0 = peg$FAILED;
+                          if (peg$silentFails === 0) { peg$fail(peg$c48); }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+
+      return s0;
+    }
+
+    function peg$parseeventType() {
+      var s0;
+
+      if (input.substr(peg$currPos, 9) === peg$c49) {
+        s0 = peg$c49;
+        peg$currPos += 9;
+      } else {
+        s0 = peg$FAILED;
+        if (peg$silentFails === 0) { peg$fail(peg$c50); }
+      }
+      if (s0 === peg$FAILED) {
+        if (input.substr(peg$currPos, 7) === peg$c51) {
+          s0 = peg$c51;
+          peg$currPos += 7;
+        } else {
+          s0 = peg$FAILED;
+          if (peg$silentFails === 0) { peg$fail(peg$c52); }
+        }
+        if (s0 === peg$FAILED) {
+          if (input.substr(peg$currPos, 5) === peg$c53) {
+            s0 = peg$c53;
+            peg$currPos += 5;
+          } else {
+            s0 = peg$FAILED;
+            if (peg$silentFails === 0) { peg$fail(peg$c54); }
+          }
+          if (s0 === peg$FAILED) {
+            if (input.substr(peg$currPos, 8) === peg$c55) {
+              s0 = peg$c55;
+              peg$currPos += 8;
+            } else {
+              s0 = peg$FAILED;
+              if (peg$silentFails === 0) { peg$fail(peg$c56); }
+            }
+            if (s0 === peg$FAILED) {
+              if (input.substr(peg$currPos, 5) === peg$c57) {
+                s0 = peg$c57;
+                peg$currPos += 5;
+              } else {
+                s0 = peg$FAILED;
+                if (peg$silentFails === 0) { peg$fail(peg$c58); }
+              }
+              if (s0 === peg$FAILED) {
+                if (input.substr(peg$currPos, 7) === peg$c59) {
+                  s0 = peg$c59;
+                  peg$currPos += 7;
+                } else {
+                  s0 = peg$FAILED;
+                  if (peg$silentFails === 0) { peg$fail(peg$c60); }
+                }
+                if (s0 === peg$FAILED) {
+                  if (input.substr(peg$currPos, 8) === peg$c61) {
+                    s0 = peg$c61;
+                    peg$currPos += 8;
+                  } else {
+                    s0 = peg$FAILED;
+                    if (peg$silentFails === 0) { peg$fail(peg$c62); }
+                  }
+                  if (s0 === peg$FAILED) {
+                    if (input.substr(peg$currPos, 5) === peg$c63) {
+                      s0 = peg$c63;
+                      peg$currPos += 5;
+                    } else {
+                      s0 = peg$FAILED;
+                      if (peg$silentFails === 0) { peg$fail(peg$c64); }
+                    }
+                    if (s0 === peg$FAILED) {
+                      if (input.substr(peg$currPos, 10) === peg$c65) {
+                        s0 = peg$c65;
+                        peg$currPos += 10;
+                      } else {
+                        s0 = peg$FAILED;
+                        if (peg$silentFails === 0) { peg$fail(peg$c66); }
+                      }
+                      if (s0 === peg$FAILED) {
+                        if (input.substr(peg$currPos, 9) === peg$c67) {
+                          s0 = peg$c67;
+                          peg$currPos += 9;
+                        } else {
+                          s0 = peg$FAILED;
+                          if (peg$silentFails === 0) { peg$fail(peg$c68); }
+                        }
+                        if (s0 === peg$FAILED) {
+                          if (input.substr(peg$currPos, 8) === peg$c69) {
+                            s0 = peg$c69;
+                            peg$currPos += 8;
+                          } else {
+                            s0 = peg$FAILED;
+                            if (peg$silentFails === 0) { peg$fail(peg$c70); }
+                          }
+                          if (s0 === peg$FAILED) {
+                            if (input.substr(peg$currPos, 9) === peg$c71) {
+                              s0 = peg$c71;
+                              peg$currPos += 9;
+                            } else {
+                              s0 = peg$FAILED;
+                              if (peg$silentFails === 0) { peg$fail(peg$c72); }
+                            }
+                            if (s0 === peg$FAILED) {
+                              if (input.substr(peg$currPos, 10) === peg$c73) {
+                                s0 = peg$c73;
+                                peg$currPos += 10;
+                              } else {
+                                s0 = peg$FAILED;
+                                if (peg$silentFails === 0) { peg$fail(peg$c74); }
+                              }
+                              if (s0 === peg$FAILED) {
+                                if (input.substr(peg$currPos, 10) === peg$c75) {
+                                  s0 = peg$c75;
+                                  peg$currPos += 10;
+                                } else {
+                                  s0 = peg$FAILED;
+                                  if (peg$silentFails === 0) { peg$fail(peg$c76); }
+                                }
+                                if (s0 === peg$FAILED) {
+                                  if (input.substr(peg$currPos, 9) === peg$c77) {
+                                    s0 = peg$c77;
+                                    peg$currPos += 9;
+                                  } else {
+                                    s0 = peg$FAILED;
+                                    if (peg$silentFails === 0) { peg$fail(peg$c78); }
+                                  }
+                                  if (s0 === peg$FAILED) {
+                                    if (input.substr(peg$currPos, 8) === peg$c79) {
+                                      s0 = peg$c79;
+                                      peg$currPos += 8;
+                                    } else {
+                                      s0 = peg$FAILED;
+                                      if (peg$silentFails === 0) { peg$fail(peg$c80); }
+                                    }
+                                  }
+                                }
+                              }
+                            }
+                          }
+                        }
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+
+      return s0;
+    }
+
+    function peg$parsefilter() {
+      var s0, s1, s2, s3;
+
+      s0 = peg$currPos;
+      if (input.charCodeAt(peg$currPos) === 91) {
+        s1 = peg$c5;
+        peg$currPos++;
+      } else {
+        s1 = peg$FAILED;
+        if (peg$silentFails === 0) { peg$fail(peg$c6); }
+      }
+      if (s1 !== peg$FAILED) {
+        s2 = peg$parseexpr();
+        if (s2 !== peg$FAILED) {
+          if (input.charCodeAt(peg$currPos) === 93) {
+            s3 = peg$c7;
+            peg$currPos++;
+          } else {
+            s3 = peg$FAILED;
+            if (peg$silentFails === 0) { peg$fail(peg$c8); }
+          }
+          if (s3 !== peg$FAILED) {
+            peg$reportedPos = s0;
+            s1 = peg$c81(s2);
+            s0 = s1;
+          } else {
+            peg$currPos = s0;
+            s0 = peg$c0;
+          }
+        } else {
+          peg$currPos = s0;
+          s0 = peg$c0;
+        }
+      } else {
+        peg$currPos = s0;
+        s0 = peg$c0;
+      }
+
+      return s0;
+    }
+
+    function peg$parsename() {
+      var s0, s1, s2;
+
+      s0 = peg$currPos;
+      s1 = [];
+      if (peg$c82.test(input.charAt(peg$currPos))) {
+        s2 = input.charAt(peg$currPos);
+        peg$currPos++;
+      } else {
+        s2 = peg$FAILED;
+        if (peg$silentFails === 0) { peg$fail(peg$c83); }
+      }
+      if (s2 !== peg$FAILED) {
+        while (s2 !== peg$FAILED) {
+          s1.push(s2);
+          if (peg$c82.test(input.charAt(peg$currPos))) {
+            s2 = input.charAt(peg$currPos);
+            peg$currPos++;
+          } else {
+            s2 = peg$FAILED;
+            if (peg$silentFails === 0) { peg$fail(peg$c83); }
+          }
+        }
+      } else {
+        s1 = peg$c0;
+      }
+      if (s1 !== peg$FAILED) {
+        peg$reportedPos = s0;
+        s1 = peg$c84(s1);
+      }
+      s0 = s1;
+
+      return s0;
+    }
+
+    function peg$parsecss() {
+      var s0, s1, s2;
+
+      s0 = peg$currPos;
+      s1 = [];
+      if (peg$c85.test(input.charAt(peg$currPos))) {
+        s2 = input.charAt(peg$currPos);
+        peg$currPos++;
+      } else {
+        s2 = peg$FAILED;
+        if (peg$silentFails === 0) { peg$fail(peg$c86); }
+      }
+      if (s2 !== peg$FAILED) {
+        while (s2 !== peg$FAILED) {
+          s1.push(s2);
+          if (peg$c85.test(input.charAt(peg$currPos))) {
+            s2 = input.charAt(peg$currPos);
+            peg$currPos++;
+          } else {
+            s2 = peg$FAILED;
+            if (peg$silentFails === 0) { peg$fail(peg$c86); }
+          }
+        }
+      } else {
+        s1 = peg$c0;
+      }
+      if (s1 !== peg$FAILED) {
+        peg$reportedPos = s0;
+        s1 = peg$c87(s1);
+      }
+      s0 = s1;
+
+      return s0;
+    }
+
+    function peg$parseexpr() {
+      var s0, s1, s2;
+
+      s0 = peg$currPos;
+      s1 = [];
+      if (peg$c88.test(input.charAt(peg$currPos))) {
+        s2 = input.charAt(peg$currPos);
+        peg$currPos++;
+      } else {
+        s2 = peg$FAILED;
+        if (peg$silentFails === 0) { peg$fail(peg$c89); }
+      }
+      if (s2 !== peg$FAILED) {
+        while (s2 !== peg$FAILED) {
+          s1.push(s2);
+          if (peg$c88.test(input.charAt(peg$currPos))) {
+            s2 = input.charAt(peg$currPos);
+            peg$currPos++;
+          } else {
+            s2 = peg$FAILED;
+            if (peg$silentFails === 0) { peg$fail(peg$c89); }
+          }
+        }
+      } else {
+        s1 = peg$c0;
+      }
+      if (s1 !== peg$FAILED) {
+        peg$reportedPos = s0;
+        s1 = peg$c90(s1);
+      }
+      s0 = s1;
+
+      return s0;
+    }
+
+    function peg$parsesep() {
+      var s0, s1;
+
+      s0 = [];
+      if (peg$c91.test(input.charAt(peg$currPos))) {
+        s1 = input.charAt(peg$currPos);
+        peg$currPos++;
+      } else {
+        s1 = peg$FAILED;
+        if (peg$silentFails === 0) { peg$fail(peg$c92); }
+      }
+      while (s1 !== peg$FAILED) {
+        s0.push(s1);
+        if (peg$c91.test(input.charAt(peg$currPos))) {
+          s1 = input.charAt(peg$currPos);
+          peg$currPos++;
+        } else {
+          s1 = peg$FAILED;
+          if (peg$silentFails === 0) { peg$fail(peg$c92); }
+        }
+      }
+
+      return s0;
+    }
+
+    peg$result = peg$startRuleFunction();
+
+    if (peg$result !== peg$FAILED && peg$currPos === input.length) {
+      return peg$result;
+    } else {
+      if (peg$result !== peg$FAILED && peg$currPos < input.length) {
+        peg$fail({ type: "end", description: "end of input" });
+      }
+
+      throw peg$buildException(null, peg$maxFailExpected, peg$maxFailPos);
+    }
+  }
+
+  return {
+    SyntaxError: SyntaxError,
+    parse:       parse
+  };
+})();
+},{}],94:[function(require,module,exports){
+var expr = require('vega-expression'),
+    args = ['datum', 'event', 'signals'];
+
+module.exports = expr.compiler(args, {
+  idWhiteList: args,
+  fieldVar:    args[0],
+  globalVar:   args[2],
+  functions:   function(codegen) {
+    var fn = expr.functions(codegen);
+    fn.eventItem = function() { return 'event.vg.item'; };
+    fn.eventGroup = 'event.vg.getGroup';
+    fn.eventX = 'event.vg.getX';
+    fn.eventY = 'event.vg.getY';
+    fn.open = 'window.open';
+    return fn;
+  }
+});
+},{"vega-expression":43}],95:[function(require,module,exports){
+module.exports = {
+  axes: require('./axes'),
+  background: require('./background'),
+  data: require('./data'),
+  events: require('./events'),
+  expr: require('./expr'),
+  legends: require('./legends'),
+  mark: require('./mark'),
+  marks: require('./marks'),
+  modify: require('./modify'),
+  padding: require('./padding'),
+  predicates: require('./predicates'),
+  properties: require('./properties'),
+  signals: require('./signals'),
+  spec: require('./spec'),
+  streams: require('./streams'),
+  transforms: require('./transforms')
+};
+},{"./axes":90,"./background":91,"./data":92,"./events":93,"./expr":94,"./legends":96,"./mark":97,"./marks":98,"./modify":99,"./padding":100,"./predicates":101,"./properties":102,"./signals":103,"./spec":104,"./streams":105,"./transforms":106}],96:[function(require,module,exports){
+var lgnd = require('../scene/legend');
+
+function parseLegends(model, spec, legends, group) {
+  (spec || []).forEach(function(def, index) {
+    legends[index] = legends[index] || lgnd(model);
+    parseLegend(def, index, legends[index], group);
+  });
+}
+
+function parseLegend(def, index, legend, group) {
+  // legend scales
+  legend.size  (def.size   ? group.scale(def.size)   : null);
+  legend.shape (def.shape  ? group.scale(def.shape)  : null);
+  legend.fill  (def.fill   ? group.scale(def.fill)   : null);
+  legend.stroke(def.stroke ? group.scale(def.stroke) : null);
+
+  // legend orientation
+  if (def.orient) legend.orient(def.orient);
+
+  // legend offset
+  if (def.offset != null) legend.offset(def.offset);
+
+  // legend title
+  legend.title(def.title || null);
+
+  // legend values
+  legend.values(def.values || null);
+
+  // legend label formatting
+  legend.format(def.format !== undefined ? def.format : null);
+
+  // style properties
+  var p = def.properties;
+  legend.titleProperties(p && p.title || {});
+  legend.labelProperties(p && p.labels || {});
+  legend.legendProperties(p && p.legend || {});
+  legend.symbolProperties(p && p.symbols || {});
+  legend.gradientProperties(p && p.gradient || {});
+}
+
+module.exports = parseLegends;
+},{"../scene/legend":114}],97:[function(require,module,exports){
+var dl = require('datalib'),
+    parseProperties = require('./properties');
+
+function parseMark(model, mark) {
+  var props = mark.properties,
+      group = mark.marks;
+
+  // parse mark property definitions
+  dl.keys(props).forEach(function(k) {
+    props[k] = parseProperties(model, mark.type, props[k]);
+  });
+
+  // parse delay function
+  if (mark.delay) {
+    mark.delay = parseProperties(model, mark.type, {delay: mark.delay});
+  }
+
+  // recurse if group type
+  if (group) {
+    mark.marks = group.map(function(g) { return parseMark(model, g); });
+  }
+    
+  return mark;
+}
+
+module.exports = parseMark;
+},{"./properties":102,"datalib":24}],98:[function(require,module,exports){
+var parseMark = require('./mark');
+
+function parseRootMark(model, spec, width, height) {
+  return {
+    type: "group",
+    width: width,
+    height: height,
+    scales: spec.scales || [],
+    axes: spec.axes || [],
+    legends: spec.legends || [],
+    marks: (spec.marks || []).map(function(m) { return parseMark(model, m); })
+  };
+}
+
+module.exports = parseRootMark;
+},{"./mark":97}],99:[function(require,module,exports){
+var dl = require('datalib'),
+    log = require('vega-logging'),
+    df = require('vega-dataflow'),
+    Node = df.Node, // jshint ignore:line
+    Tuple = df.Tuple,
+    Deps = df.Dependencies;
+
+var Types = {
+  INSERT: "insert",
+  REMOVE: "remove",
+  TOGGLE: "toggle",
+  CLEAR:  "clear"
+};
+
+var EMPTY = [];
+
+var filter = function(field, value, src, dest) {
+  for(var i = src.length-1; i >= 0; --i) {
+    if (src[i][field] == value)
+      dest.push.apply(dest, src.splice(i, 1));
+  }
+};
+
+function parseModify(model, def, ds) {
+  var signal = def.signal ? dl.field(def.signal) : null, 
+      signalName = signal ? signal[0] : null,
+      predicate = def.predicate ? model.predicate(def.predicate.name || def.predicate) : null,
+      reeval = (predicate === null),
+      node = new Node(model).router(def.type === Types.CLEAR);
+
+  node.evaluate = function(input) {
+    if (predicate !== null) {  // TODO: predicate args
+      var db = model.values(Deps.DATA, predicate.data || EMPTY),
+          sg = model.values(Deps.SIGNALS, predicate.signals || EMPTY);
+      reeval = predicate.call(predicate, {}, db, sg, model._predicates);
+    }
+
+    log.debug(input, [def.type+"ing", reeval]);
+    if (!reeval) return input;
+
+    var datum = {}, 
+        value = signal ? model.signalRef(def.signal) : null,
+        d = model.data(ds.name),
+        t = null;
+
+    datum[def.field] = value;
+
+    // We have to modify ds._data so that subsequent pulses contain
+    // our dynamic data. W/o modifying ds._data, only the output
+    // collector will contain dynamic tuples. 
+    if (def.type === Types.INSERT) {
+      t = Tuple.ingest(datum);
+      input.add.push(t);
+      d._data.push(t);
+    } else if (def.type === Types.REMOVE) {
+      filter(def.field, value, input.add, input.rem);
+      filter(def.field, value, input.mod, input.rem);
+      d._data = d._data.filter(function(x) { return x[def.field] !== value; });
+    } else if (def.type === Types.TOGGLE) {
+      var add = [], rem = [];
+      filter(def.field, value, input.rem, add);
+      filter(def.field, value, input.add, rem);
+      filter(def.field, value, input.mod, rem);
+      if (!(add.length || rem.length)) add.push(Tuple.ingest(datum));
+
+      input.add.push.apply(input.add, add);
+      d._data.push.apply(d._data, add);
+      input.rem.push.apply(input.rem, rem);
+      d._data = d._data.filter(function(x) { return rem.indexOf(x) === -1; });
+    } else if (def.type === Types.CLEAR) {
+      input.rem.push.apply(input.rem, input.add);
+      input.rem.push.apply(input.rem, input.mod);
+      input.add = [];
+      input.mod = [];
+      d._data  = [];
+    } 
+
+    input.fields[def.field] = 1;
+    return input;
+  };
+
+  if (signalName) node.dependency(Deps.SIGNALS, signalName);
+  
+  if (predicate) {
+    node.dependency(Deps.DATA, predicate.data);
+    node.dependency(Deps.SIGNALS, predicate.signals);
+  }
+  
+  return node;
+}
+
+module.exports = parseModify;
+},{"datalib":24,"vega-dataflow":39,"vega-logging":45}],100:[function(require,module,exports){
+var dl = require('datalib');
+
+function parsePadding(pad) {
+  if (pad == null) return "auto";
+  else if (dl.isString(pad)) return pad==="strict" ? "strict" : "auto";
+  else if (dl.isObject(pad)) return pad;
+  var p = dl.isNumber(pad) ? pad : 20;
+  return {top:p, left:p, right:p, bottom:p};
+}
+
+module.exports = parsePadding;
+},{"datalib":24}],101:[function(require,module,exports){
+var dl = require('datalib');
+
+var types = {
+  '=':   parseComparator,
+  '==':  parseComparator,
+  '!=':  parseComparator,
+  '>':   parseComparator,
+  '>=':  parseComparator,
+  '<':   parseComparator,
+  '<=':  parseComparator,
+  'and': parseLogical,
+  '&&':  parseLogical,
+  'or':  parseLogical,
+  '||':  parseLogical,
+  'in':  parseIn
+};
+
+var nullScale = function() { return 0; };
+nullScale.invert = nullScale;
+
+function parsePredicates(model, spec) {
+  (spec || []).forEach(function(s) {
+    var parse = types[s.type](model, s);
+    
+    /* jshint evil:true */
+    var pred  = Function("args", "db", "signals", "predicates", parse.code);
+    pred.root = function() { return model.scene().items[0]; }; // For global scales
+    pred.nullScale = nullScale;
+    pred.isFunction = dl.isFunction;
+    pred.signals = parse.signals;
+    pred.data = parse.data;
+
+    model.predicate(s.name, pred);
+  });
+
+  return spec;
+}
+
+function parseSignal(signal, signals) {
+  var s = dl.field(signal),
+      code = "signals["+s.map(dl.str).join("][")+"]";
+  signals[s[0]] = 1;
+  return code;
+}
+
+function parseOperands(model, operands) {
+  var decl = [], defs = [],
+      signals = {}, db = {};
+
+  function setSignal(s) { signals[s] = 1; }
+  function setData(d) { db[d] = 1; }
+
+  dl.array(operands).forEach(function(o, i) {
+    var name = "o" + i,
+        def = "";
+
+    if (o.value !== undefined) {
+      def = dl.str(o.value);
+    } else if (o.arg) {
+      def = "args["+dl.str(o.arg)+"]";
+    } else if (o.signal) {
+      def = parseSignal(o.signal, signals);
+    } else if (o.predicate) {
+      var ref = o.predicate,
+          predName = ref && (ref.name || ref),
+          pred = model.predicate(predName),
+          p = "predicates["+dl.str(predName)+"]";
+
+      pred.signals.forEach(setSignal);
+      pred.data.forEach(setData);
+
+      if (dl.isObject(ref)) {
+        dl.keys(ref).forEach(function(k) {
+          if (k === "name") return;
+          var i = ref[k];
+          def += "args["+dl.str(k)+"] = ";
+          if (i.signal) {
+            def += parseSignal(i.signal, signals);
+          } else if (i.arg) {
+            def += "args["+dl.str(i.arg)+"]";
+          }
+          def += ", ";
+        });  
+      } 
+
+      def += p+".call("+p+", args, db, signals, predicates)";
+    }
+
+    decl.push(name);
+    defs.push(name+"=("+def+")");
+  });
+
+  return {
+    code: "var " + decl.join(", ") + ";\n" + defs.join(";\n") + ";\n",
+    signals: dl.keys(signals),
+    data: dl.keys(db)
+  };
+}
+
+function parseComparator(model, spec) {
+  var ops = parseOperands(model, spec.operands);
+  if (spec.type === '=') spec.type = '==';
+
+  ops.code += "o0 = o0 instanceof Date ? o0.getTime() : o0;\n" +
+    "o1 = o1 instanceof Date ? o1.getTime() : o1;\n";
+
+  return {
+    code: ops.code + "return " + ["o0", "o1"].join(spec.type) + ";",
+    signals: ops.signals,
+    data: ops.data
+  };
+}
+
+function parseLogical(model, spec) {
+  var ops = parseOperands(model, spec.operands),
+      o = [], i = 0, len = spec.operands.length;
+
+  while (o.push("o"+i++) < len);
+  if (spec.type === 'and') spec.type = '&&';
+  else if (spec.type === 'or') spec.type = '||';
+
+  return {
+    code: ops.code + "return " + o.join(spec.type) + ";",
+    signals: ops.signals,
+    data: ops.data
+  };
+}
+
+function parseIn(model, spec) {
+  var o = [spec.item], code = "";
+  if (spec.range) o.push.apply(o, spec.range);
+  if (spec.scale) {
+    code = parseScale(spec.scale, o);
+  }
+
+  var ops = parseOperands(model, o);
+  code = ops.code + code + "\n  var ordSet = null;\n";
+
+  if (spec.data) {
+    var field = dl.field(spec.field).map(dl.str);
+    code += "var where = function(d) { return d["+field.join("][")+"] == o0 };\n";
+    code += "return db["+dl.str(spec.data)+"].filter(where).length > 0;";
+  } else if (spec.range) {
+    // TODO: inclusive/exclusive range?
+    if (spec.scale) {
+      code += "if (scale.length == 2) {\n" + // inverting ordinal scales
+        "  ordSet = scale(o1, o2);\n" +
+        "} else {\n" +
+        "  o1 = scale(o1);\no2 = scale(o2);\n" +
+        "}";
+    }
+
+    code += "return ordSet !== null ? ordSet.indexOf(o0) !== -1 :\n" + 
+      "  o1 < o2 ? o1 <= o0 && o0 <= o2 : o2 <= o0 && o0 <= o1;";
+  }
+
+  return {
+    code: code, 
+    signals: ops.signals, 
+    data: ops.data.concat(spec.data ? [spec.data] : [])
+  };
+}
+
+// Populate ops such that ultimate scale/inversion function will be in `scale` var. 
+function parseScale(spec, ops) {
+  var code = "var scale = ", 
+      idx  = ops.length;
+
+  if (dl.isString(spec)) {
+    ops.push({ value: spec });
+    code += "this.root().scale(o"+idx+")";
+  } else if (spec.arg) {  // Scale function is being passed as an arg
+    ops.push(spec);
+    code += "o"+idx;
+  } else if (spec.name) { // Full scale parameter {name: ..}
+    ops.push(dl.isString(spec.name) ? {value: spec.name} : spec.name);
+    code += "(this.isFunction(o"+idx+") ? o"+idx+" : ";
+    if (spec.scope) {
+      ops.push(spec.scope);
+      code += "((o"+(idx+1)+".scale || this.root().scale)(o"+idx+") || this.nullScale)";
+    } else {
+      code += "this.root().scale(o"+idx+")";
+    }
+    code += ")";
+  }
+
+  if (spec.invert === true) {  // Allow spec.invert.arg?
+    code += ".invert";
+  }
+
+  return code+";\n";
+}
+
+module.exports = parsePredicates;
+},{"datalib":24}],102:[function(require,module,exports){
+(function (global){
+var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null),
+    dl = require('datalib'),
+    log = require('vega-logging'),
+    Tuple = require('vega-dataflow').Tuple;
+
+var DEPS = ["signals", "scales", "data", "fields"];
+
+function properties(model, mark, spec) {
+  var config = model.config(),
+      code = "",
+      names = dl.keys(spec),
+      i, len, name, ref, vars = {}, 
+      deps = {
+        signals: {},
+        scales:  {},
+        data:    {},
+        fields:  {},
+        nested:  [],
+        _nRefs:  {},  // Temp stash to de-dupe nested refs.
+        reflow:  false
+      };
+      
+  code += "var o = trans ? {} : item, d=0, set=this.tpl.set, tmpl=signals||{}, t;\n" +
+          // Stash for dl.template
+          "tmpl.datum  = item.datum;\n" + 
+          "tmpl.group  = group;\n" + 
+          "tmpl.parent = group.datum;\n";
+
+  function handleDep(p) {
+    if (ref[p] == null) return;
+    var k = dl.array(ref[p]), i, n;
+    for (i=0, n=k.length; i<n; ++i) {
+      deps[p][k[i]] = 1;
+    }
+  }
+
+  function handleNestedRefs(r) {
+    var k = (r.parent ? "parent_" : "group_")+r.level;
+    deps._nRefs[k] = r;
+  }
+
+  for (i=0, len=names.length; i<len; ++i) {
+    ref = spec[name = names[i]];
+    code += (i > 0) ? "\n  " : "  ";
+    if (ref.rule) {
+      ref = rule(model, name, ref.rule);
+      code += "\n  " + ref.code;
+    } else {
+      ref = valueRef(config, name, ref);
+      code += "d += set(o, "+dl.str(name)+", "+ref.val+");";
+    }
+
+    vars[name] = true;
+    DEPS.forEach(handleDep);
+    deps.reflow = deps.reflow || ref.reflow;
+    if (ref.nested.length) ref.nested.forEach(handleNestedRefs);
+  }
+
+  // If nested references are present, sort them based on their level
+  // to speed up determination of whether encoders should be reeval'd.
+  dl.keys(deps._nRefs).forEach(function(k) { deps.nested.push(deps._nRefs[k]); });
+  deps.nested.sort(function(a, b) { 
+    a = a.level;
+    b = b.level;
+    return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; 
+  });
+
+  if (vars.x2) {
+    if (vars.x) {
+      code += "\n  if (o.x > o.x2) { " +
+              "\n    t = o.x;" +
+              "\n    d += set(o, 'x', o.x2);" +
+              "\n    d += set(o, 'x2', t); " +
+              "\n  };";
+      code += "\n  d += set(o, 'width', (o.x2 - o.x));";
+    } else if (vars.width) {
+      code += "\n  d += set(o, 'x', (o.x2 - o.width));";
+    } else {
+      code += "\n  d += set(o, 'x', o.x2);";
+    }
+  }
+
+  if (vars.xc) {
+    if (vars.width) {
+      code += "\n  d += set(o, 'x', (o.xc - o.width/2));" ;
+    } else {
+      code += "\n  d += set(o, 'x', o.xc);" ;
+    }
+  }
+
+  if (vars.y2) {
+    if (vars.y) {
+      code += "\n  if (o.y > o.y2) { " +
+              "\n    t = o.y;" +
+              "\n    d += set(o, 'y', o.y2);" +
+              "\n    d += set(o, 'y2', t);" +
+              "\n  };";
+      code += "\n  d += set(o, 'height', (o.y2 - o.y));";
+    } else if (vars.height) {
+      code += "\n  d += set(o, 'y', (o.y2 - o.height));";
+    } else {
+      code += "\n  d += set(o, 'y', o.y2);";
+    }
+  }
+
+  if (vars.yc) {
+    if (vars.height) {
+      code += "\n  d += set(o, 'y', (o.yc - o.height/2));" ;
+    } else {
+      code += "\n  d += set(o, 'y', o.yc);" ;
+    }
+  }
+  
+  if (hasPath(mark, vars)) code += "\n  d += (item.touch(), 1);";
+  code += "\n  if (trans) trans.interpolate(item, o);";
+  code += "\n  return d > 0;";
+
+  try {
+    /* jshint evil:true */
+    var encoder = Function('item', 'group', 'trans', 'db', 
+      'signals', 'predicates', code);
+    encoder.tpl  = Tuple;
+    encoder.util = dl;
+    encoder.d3   = d3; // For color spaces
+    dl.extend(encoder, dl.template.context);
+    return {
+      encode:  encoder,
+      signals: dl.keys(deps.signals),
+      scales:  dl.keys(deps.scales),
+      data:    dl.keys(deps.data),
+      fields:  dl.keys(deps.fields),
+      nested:  deps.nested,
+      reflow:  deps.reflow
+    };
+  } catch (e) {
+    log.error(e);
+    log.log(code);
+  }
+}
+
+function dependencies(a, b) {
+  if (!dl.isObject(a)) {
+    a = {reflow: false, nested: []};
+    DEPS.forEach(function(d) { a[d] = []; });
+  }
+
+  if (dl.isObject(b)) {
+    a.reflow = a.reflow || b.reflow;
+    a.nested.push.apply(a.nested, b.nested);
+    DEPS.forEach(function(d) { a[d].push.apply(a[d], b[d]); });
+  }
+
+  return a;
+}
+
+function hasPath(mark, vars) {
+  return vars.path ||
+    ((mark==='area' || mark==='line') &&
+      (vars.x || vars.x2 || vars.width ||
+       vars.y || vars.y2 || vars.height ||
+       vars.tension || vars.interpolate));
+}
+
+function rule(model, name, rules) {
+  var config  = model.config(),
+      deps = dependencies(),
+      inputs  = [], code = '';
+
+  (rules||[]).forEach(function(r, i) {
+    var def = r.predicate,
+        predName = def && (def.name || def),
+        pred = model.predicate(predName),
+        p = 'predicates['+dl.str(predName)+']',
+        input = [], args = name+'_arg'+i,
+        ref;
+
+    if (dl.isObject(def)) {
+      dl.keys(def).forEach(function(k) {
+        if (k === 'name') return;
+        var ref = valueRef(config, i, def[k]);
+        input.push(dl.str(k)+': '+ref.val);
+        dependencies(deps, ref);
+      });
+    }
+
+    ref = valueRef(config, name, r);
+    dependencies(deps, ref);
+
+    if (predName) {
+      deps.signals.push.apply(deps.signals, pred.signals);
+      deps.data.push.apply(deps.data, pred.data);
+      inputs.push(args+" = {\n    "+input.join(",\n    ")+"\n  }");
+      code += "if ("+p+".call("+p+","+args+", db, signals, predicates)) {" +
+        "\n    d += set(o, "+dl.str(name)+", "+ref.val+");";
+      code += rules[i+1] ? "\n  } else " : "  }";
+    } else {
+      code += "{" + 
+        "\n    d += set(o, "+dl.str(name)+", "+ref.val+");"+
+        "\n  }\n";
+    }
+  });
+
+  code = "var " + inputs.join(",\n      ") + ";\n  " + code;
+  return (deps.code = code, deps);
+}
+
+function valueRef(config, name, ref) {
+  if (ref == null) return null;
+
+  if (name==='fill' || name==='stroke') {
+    if (ref.c) {
+      return colorRef(config, 'hcl', ref.h, ref.c, ref.l);
+    } else if (ref.h || ref.s) {
+      return colorRef(config, 'hsl', ref.h, ref.s, ref.l);
+    } else if (ref.l || ref.a) {
+      return colorRef(config, 'lab', ref.l, ref.a, ref.b);
+    } else if (ref.r || ref.g || ref.b) {
+      return colorRef(config, 'rgb', ref.r, ref.g, ref.b);
+    }
+  }
+
+  // initialize value
+  var val = null, scale = null, 
+      deps = dependencies(),
+      sgRef = null, fRef = null, sRef = null, tmpl = {};
+
+  if (ref.template !== undefined) {
+    val = dl.template.source(ref.template, 'tmpl', tmpl);
+    dl.keys(tmpl).forEach(function(k) {
+      var f = dl.field(k),
+          a = f.shift();
+      if (a === 'parent' || a === 'group') {
+        deps.nested.push({ 
+          parent: a === 'parent',
+          group:  a === 'group', 
+          level:  1
+        });
+      } else if (a === 'datum') {
+        deps.fields.push(f[0]);
+      } else {
+        deps.signals.push(a);
+      }
+    });
+  }
+
+  if (ref.value !== undefined) {
+    val = dl.str(ref.value);
+  }
+
+  if (ref.signal !== undefined) {
+    sgRef = dl.field(ref.signal);
+    val = 'signals['+sgRef.map(dl.str).join('][')+']'; 
+    deps.signals.push(sgRef.shift());
+  }
+
+  if (ref.field !== undefined) {
+    ref.field = dl.isString(ref.field) ? {datum: ref.field} : ref.field;
+    fRef = fieldRef(ref.field);
+    val  = fRef.val;
+    dependencies(deps, fRef);
+  }
+
+  if (ref.scale !== undefined) {
+    sRef  = scaleRef(ref.scale);
+    scale = sRef.val;
+    dependencies(deps, sRef);
+    deps.scales.push(ref.scale.name || ref.scale);
+
+    // run through scale function if val specified.
+    // if no val, scale function is predicate arg.
+    if (val !== null || ref.band || ref.mult || ref.offset) {
+      val = scale + (ref.band ? '.rangeBand()' : 
+        '('+(val !== null ? val : 'item.datum.data')+')');
+    } else {
+      val = scale;
+    }
+  }
+  
+  // multiply, offset, return value
+  val = '(' + (ref.mult?(dl.number(ref.mult)+' * '):'') + val + ')' +
+        (ref.offset ? ' + ' + dl.number(ref.offset) : '');
+
+  // Collate dependencies
+  return (deps.val = val, deps);
+}
+
+function colorRef(config, type, x, y, z) {
+  var xx = x ? valueRef(config, '', x) : config.color[type][0],
+      yy = y ? valueRef(config, '', y) : config.color[type][1],
+      zz = z ? valueRef(config, '', z) : config.color[type][2],
+      deps = dependencies();
+
+  [xx, yy, zz].forEach(function(v) {
+    if (dl.isArray) return;
+    dependencies(deps, v);
+  });
+
+  var val = '(this.d3.' + type + '(' + [xx.val, yy.val, zz.val].join(',') + ') + "")';
+  return (deps.val = val, deps);
+}
+
+// {field: {datum: "foo"} }  -> item.datum.foo
+// {field: {group: "foo"} }  -> group.foo
+// {field: {parent: "foo"} } -> group.datum.foo
+function fieldRef(ref) {
+  if (dl.isString(ref)) {
+    return {val: dl.field(ref).map(dl.str).join('][')};
+  } 
+
+  // Resolve nesting/parent lookups
+  var l = ref.level || 1,
+      nested = (ref.group || ref.parent) && l,
+      scope = nested ? Array(l).join('group.mark.') : '',
+      r = fieldRef(ref.datum || ref.group || ref.parent || ref.signal),
+      val = r.val,
+      deps = dependencies(null, r);
+
+  if (ref.datum) {
+    val = 'item.datum['+val+']';
+    deps.fields.push(ref.datum);
+  } else if (ref.group) {
+    val = scope+'group['+val+']';
+    deps.nested.push({ level: l, group: true });
+  } else if (ref.parent) {
+    val = scope+'group.datum['+val+']';
+    deps.nested.push({ level: l, parent: true });
+  } else if (ref.signal) {
+    val = 'signals['+val+']';
+    deps.signals.push(dl.field(ref.signal)[0]);
+    deps.reflow = true;
+  }
+
+  return (deps.val = val, deps);
+}
+
+// {scale: "x"}
+// {scale: {name: "x"}},
+// {scale: fieldRef}
+function scaleRef(ref) {
+  var scale = null,
+      fr = null,
+      deps = dependencies();
+
+  if (dl.isString(ref)) {
+    scale = dl.str(ref);
+  } else if (ref.name) {
+    scale = dl.isString(ref.name) ? dl.str(ref.name) : (fr = fieldRef(ref.name)).val;
+  } else {
+    scale = (fr = fieldRef(ref)).val;
+  }
+
+  scale = '(item.mark._scaleRefs['+scale+'] = 1, group.scale('+scale+'))';
+  if (ref.invert) scale += '.invert';
+
+  // Mark scale refs as they're dealt with separately in mark._scaleRefs.
+  if (fr) fr.nested.forEach(function(g) { g.scale = true; });
+  return fr ? (fr.val = scale, fr) : (deps.val = scale, deps);
+}
+
+module.exports = properties;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"datalib":24,"vega-dataflow":39,"vega-logging":45}],103:[function(require,module,exports){
+var dl = require('datalib'),
+    SIGNALS = require('vega-dataflow').Dependencies.SIGNALS,
+    expr = require('./expr');
+
+var RESERVED = ['datum', 'event', 'signals']
+  .concat(dl.keys(expr.codegen.functions));
+
+function parseSignals(model, spec) {
+  // process each signal definition
+  (spec || []).forEach(function(s) {
+    if (RESERVED.indexOf(s.name) !== -1) {
+      throw Error('Signal name "'+s.name+'" is a '+
+        'reserved keyword ('+RESERVED.join(', ')+').');
+    }
+
+    var signal = model.signal(s.name, s.init)
+      .verbose(s.verbose);
+
+    if (s.init && s.init.expr) {
+      s.init.expr = expr(s.init.expr);
+      signal.value(exprVal(model, s.init));
+    }
+
+    if (s.expr) {
+      s.expr = expr(s.expr);
+      signal.evaluate = function(input) {
+        var val = exprVal(model, s);
+        if (val !== signal.value() || signal.verbose()) {
+          signal.value(val);
+          input.signals[s.name] = 1;
+          return input;
+        }
+        return model.doNotPropagate;        
+      };
+      signal.dependency(SIGNALS, s.expr.globals);
+      s.expr.globals.forEach(function(dep) {
+        model.signal(dep).addListener(signal);
+      });
+    }
+  });
+
+  return spec;
+}
+
+function exprVal(model, spec) {
+  var e = spec.expr,
+      val = e.fn(null, null, model.values(SIGNALS, e.globals));
+  return spec.scale ? parseSignals.scale(model, spec, val) : val;
+}
+
+parseSignals.scale = function scale(model, spec, value, datum, evt) {
+  var def = spec.scale,
+      name  = def.name || def.signal || def,
+      scope = def.scope, e;
+
+  if (scope) {
+    if (scope.signal) {
+      scope = model.signalRef(scope.signal);
+    } else if (dl.isString(scope)) { // Scope is an expression
+      e = def._expr = (def._expr || expr(scope));
+      scope = e.fn(datum, evt, model.values(SIGNALS, e.globals));
+    }
+  }
+
+  if (!scope || !scope.scale) {
+    scope = (scope && scope.mark) ? scope.mark.group : model.scene().items[0];
+  }
+
+  var s = scope.scale(name);
+  return !s ? value : (def.invert ? s.invert(value) : s(value));
+};
+
+module.exports = parseSignals;
+},{"./expr":94,"datalib":24,"vega-dataflow":39}],104:[function(require,module,exports){
+var dl = require('datalib'),
+    log = require('vega-logging'),
+    Model = require('../core/Model'),
+    View = require('../core/View');
+
+function parseSpec(spec, callback) {
+  var vf = arguments[arguments.length-1],
+      viewFactory = arguments.length > 2 && dl.isFunction(vf) ? vf : View.factory,
+      config = arguments[2] !== viewFactory ? arguments[2] : {},
+      model = new Model(config);
+
+  function parse(spec) {
+    // protect against subsequent spec modification
+    spec = dl.duplicate(spec);
+
+    var parsers = require('./'),
+        width = spec.width || 500,
+        height = spec.height || 500,
+        viewport = spec.viewport || null;
+
+    model.defs({
+      width: width,
+      height: height,
+      viewport: viewport,
+      background: parsers.background(spec.background),
+      padding: parsers.padding(spec.padding),
+      signals: parsers.signals(model, spec.signals),
+      predicates: parsers.predicates(model, spec.predicates),
+      marks: parsers.marks(model, spec, width, height),
+      data: parsers.data(model, spec.data, function() {
+        callback(viewFactory(model));
+      })
+    });    
+  }
+
+  if (dl.isObject(spec)) {
+    parse(spec);
+  } else if (dl.isString(spec)) {
+    var opts = dl.extend({url: spec}, model.config().load);
+    dl.load(opts, function(err, data) {
+      if (err) {
+        log.error('LOADING SPECIFICATION FAILED: ' + err.statusText);
+      } else {
+        try { 
+          parse(JSON.parse(data)); 
+        } catch (e) { 
+          log.error('INVALID SPECIFICATION: Must be a valid JSON object. '+e); 
+        }
+      }
+    });
+  } else {
+    log.error('INVALID SPECIFICATION: Must be a valid JSON object or URL.');
+  }
+}
+
+module.exports = parseSpec;
+},{"../core/Model":86,"../core/View":87,"./":95,"datalib":24,"vega-logging":45}],105:[function(require,module,exports){
+(function (global){
+var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null),
+    dl = require('datalib'),
+    df = require('vega-dataflow'),
+    SIGNALS = df.Dependencies.SIGNALS,
+    parseSignals = require('./signals'),
+    selector = require('./events'),
+    expr = require('./expr');
+
+var GATEKEEPER = '_vgGATEKEEPER';
+
+var vgEvent = {
+  getGroup: function(name) { return name ? this.name[name] : this.group; },
+  getXY: function(item) {
+      var p = {x: this.x, y: this.y};
+      if (typeof item === 'string') {
+        item = this.name[item];
+      }
+      for (; item; item = item.mark && item.mark.group) {
+        p.x -= item.x || 0;
+        p.y -= item.y || 0;
+      }
+      return p;
+    },
+  getX: function(item) { return this.getXY(item).x; },
+  getY: function(item) { return this.getXY(item).y; }
+};
+
+function parseStreams(view) {
+  var model = view.model(),
+      spec  = model.defs().signals,
+      registry = {handlers: {}, nodes: {}},
+      internal = dl.duplicate(registry),  // Internal event processing
+      external = dl.duplicate(registry);  // External event processing
+
+  (spec || []).forEach(function(sig) {
+    var signal = model.signal(sig.name);
+    if (sig.expr) return;  // Cannot have an expr and stream definition.
+
+    (sig.streams || []).forEach(function(stream) {
+      var sel = selector.parse(stream.type),
+          exp = expr(stream.expr);
+      mergedStream(signal, sel, exp, stream);
+    });
+  });
+
+  // We register the event listeners all together so that if multiple
+  // signals are registered on the same event, they will receive the
+  // new value on the same pulse. 
+  dl.keys(internal.handlers).forEach(function(type) {
+    view.on(type, function(evt, item) {
+      evt.preventDefault(); // stop text selection
+      extendEvent(evt, item);
+      fire(internal, type, (item && item.datum) || {}, evt);
+    });
+  });
+
+  // add external event listeners
+  dl.keys(external.handlers).forEach(function(type) {
+    if (typeof window === 'undefined') return; // No external support
+
+    var h = external.handlers[type],
+        t = type.split(':'), // --> no element pseudo-selectors
+        elt = (t[0] === 'window') ? [window] :
+              window.document.querySelectorAll(t[0]);
+
+    function handler(evt) {
+      extendEvent(evt);
+      fire(external, type, d3.select(this).datum(), evt);
+    }
+
+    for (var i=0; i<elt.length; ++i) {
+      elt[i].addEventListener(t[1], handler);
+    }
+
+    h.elements = elt;
+    h.listener = handler;
+  });
+
+  // remove external event listeners
+  external.detach = function() {
+    dl.keys(external.handlers).forEach(function(type) {
+      var h = external.handlers[type],
+          t = type.split(':'),
+          elt = h.elements || [];
+
+      for (var i=0; i<elt.length; ++i) {
+        elt[i].removeEventListener(t[1], h.listener);
+      }
+    });
+  };
+
+  // export detach method
+  return external.detach;
+
+  // -- helper functions -----
+
+  function extendEvent(evt, item) {
+    var mouse = d3.mouse((d3.event=evt, view.renderer().scene())),
+        pad = view.padding(),
+        names = {}, mark, group, i;
+
+    if (item) {
+      mark = item.mark;
+      group = mark.marktype === 'group' ? item : mark.group;
+      for (i=item; i!=null; i=i.mark.group) {
+        if (i.mark.def.name) {
+          names[i.mark.def.name] = i;
+        }
+      }
+    }
+    names.root = view.model().scene().items[0];
+
+    evt.vg = Object.create(vgEvent);
+    evt.vg.group = group;
+    evt.vg.item = item || {};
+    evt.vg.name = names;
+    evt.vg.x = mouse[0] - pad.left;
+    evt.vg.y = mouse[1] - pad.top;
+  }
+
+  function fire(registry, type, datum, evt) {
+    var handlers = registry.handlers[type],
+        node = registry.nodes[type],
+        cs = df.ChangeSet.create(null, true),
+        filtered = false,
+        val, i, n, h;
+
+    function invoke(f) {
+      return !f.fn(datum, evt, model.values(SIGNALS, f.globals));
+    }
+
+    for (i=0, n=handlers.length; i<n; ++i) {
+      h = handlers[i];
+      filtered = h.filters.some(invoke);
+      if (filtered) continue;
+      
+      val = h.exp.fn(datum, evt, model.values(SIGNALS, h.exp.globals));
+      if (h.spec.scale) {
+        val = parseSignals.scale(model, h.spec, val, datum, evt);
+      }
+
+      if (val !== h.signal.value() || h.signal.verbose()) {
+        h.signal.value(val);
+        cs.signals[h.signal.name()] = 1;
+      }
+    }
+
+    model.propagate(cs, node);
+  }
+
+  function mergedStream(sig, selector, exp, spec) {
+    selector.forEach(function(s) {
+      if (s.event)       domEvent(sig, s, exp, spec);
+      else if (s.signal) signal(sig, s, exp, spec);
+      else if (s.start)  orderedStream(sig, s, exp, spec);
+      else if (s.stream) mergedStream(sig, s.stream, exp, spec);
+    });
+  }
+
+  function domEvent(sig, selector, exp, spec) {
+    var evt = selector.event,
+        name = selector.name,
+        mark = selector.mark,
+        target   = selector.target,
+        filters  = selector.filters || [],
+        registry = target ? external : internal,
+        type = target ? target+':'+evt : evt,
+        node = registry.nodes[type] || (registry.nodes[type] = new df.Node(model)),
+        handlers = registry.handlers[type] || (registry.handlers[type] = []);
+
+    if (name) {
+      filters.push('!!event.vg.name["' + name + '"]'); // Mimic event bubbling
+    } else if (mark) {
+      filters.push('event.vg.item.mark && event.vg.item.mark.marktype==='+dl.str(mark));
+    }
+
+    handlers.push({
+      signal: sig,
+      exp: exp,
+      spec: spec,
+      filters: filters.map(function(f) { return expr(f); })
+    });
+
+    node.addListener(sig);
+  }
+
+  function signal(sig, selector, exp, spec) {
+    var n = new df.Node(model);
+    n.evaluate = function(input) {
+      if (!input.signals[selector.signal]) return model.doNotPropagate;
+      var val = exp.fn(null, null, model.values(SIGNALS, exp.globals));
+      if (spec.scale) {
+        val = parseSignals.scale(model, spec, val);
+      }
+
+      if (val !== sig.value() || sig.verbose()) {
+        sig.value(val);
+        input.signals[sig.name()] = 1;
+        input.reflow = true;        
+      }
+
+      return input;  
+    };
+    n.dependency(df.Dependencies.SIGNALS, selector.signal);
+    n.addListener(sig);
+    model.signal(selector.signal).addListener(n);
+  }
+
+  function orderedStream(sig, selector, exp, spec) {
+    var name = sig.name(), 
+        gk = name + GATEKEEPER, 
+        trueFn  = expr('true'), 
+        falseFn = expr('false'),
+        middle  = selector.middle,
+        filters = middle.filters || (middle.filters = []),
+        gatekeeper = model.signal(gk) || model.signal(gk, false);
+
+    // Register an anonymous signal to act as a gatekeeper. Its value is
+    // true or false depending on whether the start or end streams occur. 
+    // The middle signal then simply filters for the gatekeeper's value. 
+    mergedStream(gatekeeper, [selector.start], trueFn, {});
+    mergedStream(gatekeeper, [selector.end], falseFn, {});
+
+    filters.push(gatekeeper.name());
+    mergedStream(sig, [selector.middle], exp, spec);
+  }
+}
+
+module.exports = parseStreams;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"./events":93,"./expr":94,"./signals":103,"datalib":24,"vega-dataflow":39}],106:[function(require,module,exports){
+var dl = require('datalib'),
+    transforms = require('../transforms/index');
+
+function parseTransforms(model, def) {
+  var tx = new transforms[def.type](model);
+  
+  // We want to rename output fields before setting any other properties,
+  // as subsequent properties may require output to be set (e.g. group by).
+  if(def.output) tx.output(def.output);
+
+  dl.keys(def).forEach(function(k) {
+    if(k === 'type' || k === 'output') return;
+    tx.param(k, def[k]);
+  });
+
+  return tx;
+}
+
+module.exports = parseTransforms;
+},{"../transforms/index":139,"datalib":24}],107:[function(require,module,exports){
+var dl = require('datalib'),
+    df = require('vega-dataflow'),
+    Node = df.Node, // jshint ignore:line
+    log = require('vega-logging'),
+    bound = require('vega-scenegraph').bound,
+    Encoder = require('./Encoder');
+
+function Bounder(graph, mark) {
+  this._mark = mark;
+  return Node.prototype.init.call(this, graph)
+    .router(true)
+    .reflows(true)
+    .mutates(true);
+}
+
+var proto = (Bounder.prototype = new Node());
+
+proto.evaluate = function(input) {
+  log.debug(input, ['bounds', this._mark.marktype]);
+
+  var type  = this._mark.marktype,
+      isGrp = type === 'group',
+      items = this._mark.items,
+      hasLegends = dl.array(this._mark.def.legends).length > 0,
+      i, ilen, j, jlen, group, legend;
+
+  if (input.add.length || input.rem.length || !items.length || 
+      input.mod.length === items.length ||
+      type === 'area' || type === 'line') {
+    bound.mark(this._mark, null, isGrp && !hasLegends);
+  } else {
+    input.mod.forEach(function(item) { bound.item(item); });
+  }
+
+  if (isGrp && hasLegends) {
+    for (i=0, ilen=items.length; i<ilen; ++i) {
+      group = items[i];
+      group._legendPositions = null;
+      for (j=0, jlen=group.legendItems.length; j<jlen; ++j) {
+        legend = group.legendItems[j];
+        Encoder.update(this._graph, input.trans, 'vg_legendPosition', legend.items, input.dirty);
+        bound.mark(legend, null, false);
+      }
+    }
+
+    bound.mark(this._mark, null, true);
+  }
+
+  return df.ChangeSet.create(input, true);
+};
+
+module.exports = Bounder;
+},{"./Encoder":109,"datalib":24,"vega-dataflow":39,"vega-logging":45,"vega-scenegraph":46}],108:[function(require,module,exports){
+var dl = require('datalib'),
+    log = require('vega-logging'),
+    Item = require('vega-scenegraph').Item,
+    df = require('vega-dataflow'),
+    Node = df.Node, // jshint ignore:line
+    Deps = df.Dependencies,
+    Tuple = df.Tuple,
+    ChangeSet = df.ChangeSet,
+    Sentinel = {},
+    Encoder  = require('./Encoder'),
+    Bounder  = require('./Bounder'),
+    parseData = require('../parse/data');
+
+function Builder() {    
+  return arguments.length ? this.init.apply(this, arguments) : this;
+}
+
+var Status = Builder.STATUS = {
+  ENTER:  'enter',
+  UPDATE: 'update',
+  EXIT:   'exit'
+};
+
+var CONNECTED = 1, DISCONNECTED = 2;
+
+var proto = (Builder.prototype = new Node());
+
+proto.init = function(graph, def, mark, parent, parent_id, inheritFrom) {
+  Node.prototype.init.call(this, graph)
+    .router(true)
+    .collector(true);
+
+  this._def   = def;
+  this._mark  = mark;
+  this._from  = (def.from ? def.from.data : null) || inheritFrom;
+  this._ds    = dl.isString(this._from) ? graph.data(this._from) : null;
+  this._map   = {};
+  this._status = null; // Connected or disconnected?
+
+  mark.def = def;
+  mark.marktype = def.type;
+  mark.interactive = (def.interactive !== false);
+  mark.items = [];
+  if (dl.isValid(def.name)) mark.name = def.name;
+
+  this._parent = parent;
+  this._parent_id = parent_id;
+
+  if (def.from && (def.from.mark || def.from.transform || def.from.modify)) {
+    inlineDs.call(this);
+  }
+
+  // Non-group mark builders are super nodes. Encoder and Bounder remain 
+  // separate operators but are embedded and called by Builder.evaluate.
+  this._isSuper = (this._def.type !== 'group'); 
+  this._encoder = new Encoder(this._graph, this._mark, this);
+  this._bounder = new Bounder(this._graph, this._mark);
+  this._output  = null; // Output changeset for reactive geom as Bounder reflows
+
+  if (this._ds) { this._encoder.dependency(Deps.DATA, this._from); }
+
+  // Since Builders are super nodes, copy over encoder dependencies
+  // (bounder has no registered dependencies).
+  this.dependency(Deps.DATA, this._encoder.dependency(Deps.DATA));
+  this.dependency(Deps.SCALES, this._encoder.dependency(Deps.SCALES));
+  this.dependency(Deps.SIGNALS, this._encoder.dependency(Deps.SIGNALS));
+
+  return this;
+};
+
+// Reactive geometry and mark-level transformations are handled here 
+// because they need their group's data-joined context. 
+function inlineDs() {
+  var from = this._def.from,
+      geom = from.mark,
+      src, name, spec, sibling, output, input;
+
+  if (geom) {
+    name = ['vg', this._parent_id, geom].join('_');
+    spec = {
+      name: name,
+      transform: from.transform, 
+      modify: from.modify
+    };
+  } else {
+    src = this._graph.data(this._from);
+    name = ['vg', this._from, this._def.type, src.listeners(true).length].join('_');
+    spec = {
+      name: name,
+      source: this._from,
+      transform: from.transform,
+      modify: from.modify
+    };
+  }
+
+  this._from = name;
+  this._ds = parseData.datasource(this._graph, spec);
+  var node;
+
+  if (geom) {
+    sibling = this.sibling(geom);
+
+    // Bounder reflows, so we need an intermediary node to propagate
+    // the output constructed by the Builder.
+    node = new Node(this._graph).addListener(this._ds.listener());
+    node.evaluate = function() { return sibling._output; };
+
+    if (sibling._isSuper) {
+      sibling.addListener(node);
+    } else {
+      sibling._bounder.addListener(node);
+    }
+  } else {
+    // At this point, we have a new datasource but it is empty as
+    // the propagation cycle has already crossed the datasources. 
+    // So, we repulse just this datasource. This should be safe
+    // as the ds isn't connected to the scenegraph yet.
+    output = this._ds.source().last();
+    input  = ChangeSet.create(output);
+
+    input.add = output.add;
+    input.mod = output.mod;
+    input.rem = output.rem;
+    input.stamp = null;
+    this._graph.propagate(input, this._ds.listener(), output.stamp);
+  }
+}
+
+proto.ds = function() { return this._ds; };
+proto.parent   = function() { return this._parent; };
+proto.encoder  = function() { return this._encoder; };
+proto.pipeline = function() { return [this]; };
+
+proto.connect = function() {
+  var builder = this;
+
+  this._graph.connect(this.pipeline());
+  this._encoder._scales.forEach(function(s) {
+    if (!(s = builder._parent.scale(s))) return;
+    s.addListener(builder);
+  });
+
+  if (this._parent) {
+    if (this._isSuper) this.addListener(this._parent._collector);
+    else this._bounder.addListener(this._parent._collector);
+  }
+
+  return (this._status = CONNECTED, this);
+};
+
+proto.disconnect = function() {
+  var builder = this;
+  if (!this._listeners.length) return this;
+
+  function disconnectScales(scales) {
+    for(var i=0, len=scales.length, s; i<len; ++i) {
+      if (!(s = builder._parent.scale(scales[i]))) continue;
+      s.removeListener(builder);
+    }
+  }
+
+  Node.prototype.disconnect.call(this);
+  this._graph.disconnect(this.pipeline());
+  disconnectScales(this._encoder._scales);
+  disconnectScales(dl.keys(this._mark._scaleRefs));
+  
+  return (this._status = DISCONNECTED, this);
+};
+
+proto.sibling = function(name) {
+  return this._parent.child(name, this._parent_id);
+};
+
+proto.evaluate = function(input) {
+  log.debug(input, ['building', (this._from || this._def.from), this._def.type]);
+
+  var self = this,
+      def = this._mark.def,
+      props  = def.properties || {},
+      update = props.update   || {},
+      output, fullUpdate, fcs, data, name;
+
+  if (this._ds) {
+    output = ChangeSet.create(input);
+
+    // We need to determine if any encoder dependencies have been updated.
+    // However, the encoder's data source will likely be updated, and shouldn't
+    // trigger all items to mod.
+    data = output.data[(name=this._ds.name())];
+    delete output.data[name];
+    fullUpdate = this._encoder.reevaluate(output);
+    output.data[name] = data;
+
+    // If a scale or signal in the update propset has been updated, 
+    // send forward all items for reencoding if we do an early return.
+    if (fullUpdate) output.mod = this._mark.items.slice();
+
+    fcs = this._ds.last();
+    if (!fcs) throw Error('Builder evaluated before backing DataSource.');
+    if (fcs.stamp > this._stamp) {
+      output = join.call(this, fcs, this._ds.values(), true, fullUpdate);
+    }
+  } else {
+    data = dl.isFunction(this._def.from) ? this._def.from() : [Sentinel];
+    output = join.call(this, input, data);
+  }
+
+  // Stash output before Bounder for downstream reactive geometry.
+  this._output = output = this._graph.evaluate(output, this._encoder);
+
+  // Add any new scale references to the dependency list, and ensure
+  // they're connected.
+  if (update.nested && update.nested.length && this._status === CONNECTED) {
+    dl.keys(this._mark._scaleRefs).forEach(function(s) {
+      var scale = self._parent.scale(s);
+      if (!scale) return;
+
+      scale.addListener(self);
+      self.dependency(Deps.SCALES, s);
+      self._encoder.dependency(Deps.SCALES, s);
+    });
+  }
+
+  // Supernodes calculate bounds too, but only on items marked dirty.
+  if (this._isSuper) {
+    output.mod = output.mod.filter(function(x) { return x._dirty; });
+    output = this._graph.evaluate(output, this._bounder);
+  }
+
+  return output;
+};
+
+function newItem() {
+  var item = Tuple.ingest(new Item(this._mark));
+
+  // For the root node's item
+  if (this._def.width)  Tuple.set(item, 'width',  this._def.width);
+  if (this._def.height) Tuple.set(item, 'height', this._def.height);
+  return item;
+}
+
+function join(input, data, ds, fullUpdate) {
+  var output = ChangeSet.create(input),
+      keyf = keyFunction(this._def.key || (ds ? '_id' : null)),
+      prev = this._mark.items || [],
+      rem  = ds ? input.rem : prev,
+      mod  = Tuple.idMap((!ds || fullUpdate) ? data : input.mod),
+      next = [],
+      i, key, len, item, datum, enter, diff;
+
+  // Only mark rems as exiting. Due to keyf, there may be an add/mod 
+  // tuple that replaces it.
+  for (i=0, len=rem.length; i<len; ++i) {
+    item = (rem[i] === prev[i]) ? prev[i] :
+      keyf ? this._map[keyf(rem[i])] : rem[i];
+    item.status = Status.EXIT;
+  }
+
+  for(i=0, len=data.length; i<len; ++i) {
+    datum = data[i];
+    item  = keyf ? this._map[key = keyf(datum)] : prev[i];
+    enter = item ? false : (item = newItem.call(this), true);
+    item.status = enter ? Status.ENTER : Status.UPDATE;
+    diff = !enter && item.datum !== datum;
+    item.datum = datum;
+
+    if (keyf) {
+      Tuple.set(item, 'key', key);
+      this._map[key] = item;
+    }
+
+    if (enter) {
+      output.add.push(item);
+    } else if (diff || mod[datum._id]) {
+      output.mod.push(item);
+    }
+
+    next.push(item);
+  }
+
+  for (i=0, len=rem.length; i<len; ++i) {
+    item = (rem[i] === prev[i]) ? prev[i] :
+      keyf ? this._map[key = keyf(rem[i])] : rem[i];
+    if (item.status === Status.EXIT) {
+      item._dirty = true;
+      input.dirty.push(item);
+      next.push(item);
+      output.rem.push(item);
+      if (keyf) this._map[key] = null;
+    }
+  }
+
+  return (this._mark.items = next, output);
+}
+
+function keyFunction(key) {
+  if (key == null) return null;
+  var f = dl.array(key).map(dl.accessor);
+  return function(d) {
+    for (var s='', i=0, n=f.length; i<n; ++i) {
+      if (i>0) s += '|';
+      s += String(f[i](d));
+    }
+    return s;
+  };
+}
+
+module.exports = Builder;
+},{"../parse/data":92,"./Bounder":107,"./Encoder":109,"datalib":24,"vega-dataflow":39,"vega-logging":45,"vega-scenegraph":46}],109:[function(require,module,exports){
+var dl = require('datalib'),
+    log = require('vega-logging'),
+    df = require('vega-dataflow'),
+    Node = df.Node, // jshint ignore:line
+    Deps = df.Dependencies,
+    bound = require('vega-scenegraph').bound;
+
+var EMPTY = {};
+
+function Encoder(graph, mark, builder) {
+  var props  = mark.def.properties || {},
+      enter  = props.enter,
+      update = props.update,
+      exit   = props.exit;
+
+  Node.prototype.init.call(this, graph);
+
+  this._mark = mark;
+  this._builder = builder;
+  var s = this._scales = [];
+
+  // Only scales used in the 'update' property set are set as
+  // encoder depedencies to have targeted reevaluations. However,
+  // we still want scales in 'enter' and 'exit' to be evaluated
+  // before the encoder. 
+  if (enter) s.push.apply(s, enter.scales);
+
+  if (update) {
+    this.dependency(Deps.DATA, update.data);
+    this.dependency(Deps.SIGNALS, update.signals);
+    this.dependency(Deps.FIELDS, update.fields);
+    this.dependency(Deps.SCALES, update.scales);
+    s.push.apply(s, update.scales);
+  }
+
+  if (exit) s.push.apply(s, exit.scales);
+
+  return this.mutates(true);
+}
+
+var proto = (Encoder.prototype = new Node());
+
+proto.evaluate = function(input) {
+  log.debug(input, ['encoding', this._mark.def.type]);
+  var graph = this._graph,
+      props = this._mark.def.properties || {},
+      items = this._mark.items,
+      enter  = props.enter,
+      update = props.update,
+      exit   = props.exit,
+      dirty  = input.dirty,
+      preds  = graph.predicates(),
+      req = input.request,
+      group = this._mark.group,
+      guide = group && (group.mark.axis || group.mark.legend),
+      db = EMPTY, sg = EMPTY, i, len, item, prop;
+
+  if (req && !guide) {
+    if ((prop = props[req]) && input.mod.length) {
+      db = prop.data ? graph.values(Deps.DATA, prop.data) : null;
+      sg = prop.signals ? graph.values(Deps.SIGNALS, prop.signals) : null;
+
+      for (i=0, len=input.mod.length; i<len; ++i) {
+        item = input.mod[i];
+        encode.call(this, prop, item, input.trans, db, sg, preds, dirty);
+      }
+    }
+
+    return input; // exit early if given request
+  }
+
+  db = values(Deps.DATA, graph, input, props);
+  sg = values(Deps.SIGNALS, graph, input, props);
+
+  // Items marked for removal are at the tail of items. Process them first.
+  for (i=0, len=input.rem.length; i<len; ++i) {
+    item = input.rem[i];
+    if (exit) encode.call(this, exit, item, input.trans, db, sg, preds, dirty); 
+    if (input.trans && !exit) input.trans.interpolate(item, EMPTY);
+    else if (!input.trans) items.pop();
+  }
+
+  var update_status = require('./Builder').STATUS.UPDATE;
+  for (i=0, len=input.add.length; i<len; ++i) {
+    item = input.add[i];
+    if (enter)  encode.call(this, enter,  item, input.trans, db, sg, preds, dirty);
+    if (update) encode.call(this, update, item, input.trans, db, sg, preds, dirty);
+    item.status = update_status;
+  }
+
+  if (update) {
+    for (i=0, len=input.mod.length; i<len; ++i) {
+      item = input.mod[i];
+      encode.call(this, update, item, input.trans, db, sg, preds, dirty);
+    }
+  }
+
+  return input;
+};
+
+// Only marshal necessary data and signal values
+function values(type, graph, input, props) {
+  var p, x, o, add = input.add.length;
+  if ((p=props.enter) && (x=p[type]).length && add) {
+    o = graph.values(type, x, (o=o||{}));
+  }
+  if ((p=props.exit) && (x=p[type]).length && input.rem.length) {
+    o = graph.values(type, x, (o=o||{})); 
+  }
+  if ((p=props.update) && (x=p[type]).length && (add || input.mod.length)) {
+    o = graph.values(type, x, (o=o||{}));
+  }
+  return o || EMPTY;
+}
+
+function encode(prop, item, trans, db, sg, preds, dirty) {
+  var enc = prop.encode,
+      wasDirty = item._dirty,
+      isDirty  = enc.call(enc, item, item.mark.group||item, trans, db, sg, preds);
+
+  item._dirty = isDirty || wasDirty;
+  if (isDirty && !wasDirty) dirty.push(item);
+}
+
+// If a specified property set called, or update property set 
+// uses nested fieldrefs, reevaluate all items.
+proto.reevaluate = function(pulse) {
+  var def = this._mark.def,
+      props = def.properties || {},
+      reeval = dl.isFunction(def.from) || def.orient || pulse.request || 
+        Node.prototype.reevaluate.call(this, pulse);
+
+  return reeval || (props.update ? nestedRefs.call(this) : false);
+};
+
+// Test if any nested refs trigger a reflow of mark items.
+function nestedRefs() {
+  var refs = this._mark.def.properties.update.nested,
+      parent = this._builder,
+      level = 0,
+      i = 0, len = refs.length,
+      ref, ds, stamp;
+
+  for (; i<len; ++i) {
+    ref = refs[i];
+
+    // Scale references are resolved via this._mark._scaleRefs which are
+    // added to dependency lists + connected in Builder.evaluate.
+    if (ref.scale) continue;
+
+    for (; level<ref.level; ++level) {
+      parent = parent.parent();
+      ds = parent.ds();
+    }
+
+    // Compare stamps to determine if a change in a group's properties
+    // or data should trigger a reeval. We cannot check anything fancier
+    // (e.g., pulse.fields) as the ref may use item.datum.
+    stamp = (ref.group ? parent.encoder() : ds.last())._stamp;
+    if (stamp > this._stamp) return true;
+  }
+
+  return false;
+}
+
+// Short-circuit encoder if user specifies items
+Encoder.update = function(graph, trans, request, items, dirty) {
+  items = dl.array(items);
+  var preds = graph.predicates(), 
+      db = graph.values(Deps.DATA),
+      sg = graph.values(Deps.SIGNALS),
+      i, len, item, props, prop;
+
+  for (i=0, len=items.length; i<len; ++i) {
+    item = items[i];
+    props = item.mark.def.properties;
+    prop = props && props[request];
+    if (prop) {
+      encode.call(null, prop, item, trans, db, sg, preds, dirty);
+      bound.item(item);
+    }
+  }
+
+};
+
+module.exports = Encoder;
+},{"./Builder":108,"datalib":24,"vega-dataflow":39,"vega-logging":45,"vega-scenegraph":46}],110:[function(require,module,exports){
+var dl = require('datalib'),
+    df = require('vega-dataflow'),
+    Node = df.Node, // jshint ignore:line
+    Deps = df.Dependencies,
+    Collector = df.Collector,
+    log = require('vega-logging'),
+    Builder = require('./Builder'),
+    Scale = require('./Scale'),
+    parseAxes = require('../parse/axes'),
+    parseLegends = require('../parse/legends');
+
+function GroupBuilder() {
+  this._children = {};
+  this._scaler = null;
+  this._recursor = null;
+
+  this._scales = {};
+  this.scale = scale.bind(this);
+  return arguments.length ? this.init.apply(this, arguments) : this;
+}
+
+var Types = GroupBuilder.TYPES = {
+  GROUP:  "group",
+  MARK:   "mark",
+  AXIS:   "axis",
+  LEGEND: "legend"
+};
+
+var proto = (GroupBuilder.prototype = new Builder());
+
+proto.init = function(graph, def) {
+  var builder = this, name;
+
+  this._scaler = new Node(graph);
+
+  (def.scales||[]).forEach(function(s) {
+    s = builder.scale((name=s.name), new Scale(graph, s, builder));
+    builder.scale(name+":prev", s);
+    builder._scaler.addListener(s);  // Scales should be computed after group is encoded
+  });
+
+  this._recursor = new Node(graph);
+  this._recursor.evaluate = recurse.bind(this);
+
+  var scales = (def.axes||[]).reduce(function(acc, x) {
+    return (acc[x.scale] = 1, acc);
+  }, {});
+
+  scales = (def.legends||[]).reduce(function(acc, x) {
+    return (acc[x.size || x.shape || x.fill || x.stroke], acc);
+  }, scales);
+
+  this._recursor.dependency(Deps.SCALES, dl.keys(scales));
+
+  // We only need a collector for up-propagation of bounds calculation,
+  // so only GroupBuilders, and not regular Builders, have collectors.
+  this._collector = new Collector(graph);
+
+  return Builder.prototype.init.apply(this, arguments);
+};
+
+proto.evaluate = function() {
+  var output = Builder.prototype.evaluate.apply(this, arguments),
+      builder = this;
+
+  output.add.forEach(function(group) { buildGroup.call(builder, output, group); });
+  return output;
+};
+
+proto.pipeline = function() {
+  return [this, this._scaler, this._recursor, this._collector, this._bounder];
+};
+
+proto.disconnect = function() {
+  var builder = this;
+  dl.keys(builder._children).forEach(function(group_id) {
+    builder._children[group_id].forEach(function(c) {
+      builder._recursor.removeListener(c.builder);
+      c.builder.disconnect();
+    });
+  });
+
+  builder._children = {};
+  return Builder.prototype.disconnect.call(this);
+};
+
+proto.child = function(name, group_id) {
+  var children = this._children[group_id],
+      i = 0, len = children.length,
+      child;
+
+  for (; i<len; ++i) {
+    child = children[i];
+    if (child.type == Types.MARK && child.builder._def.name == name) break;
+  }
+
+  return child.builder;
+};
+
+function recurse(input) {
+  var builder = this,
+      hasMarks = dl.array(this._def.marks).length > 0,
+      hasAxes = dl.array(this._def.axes).length > 0,
+      hasLegends = dl.array(this._def.legends).length > 0,
+      i, j, c, len, group, pipeline, def, inline = false;
+
+  for (i=0, len=input.add.length; i<len; ++i) {
+    group = input.add[i];
+    if (hasMarks) buildMarks.call(this, input, group);
+    if (hasAxes)  buildAxes.call(this, input, group);
+    if (hasLegends) buildLegends.call(this, input, group);
+  }
+
+  // Wire up new children builders in reverse to minimize graph rewrites.
+  for (i=input.add.length-1; i>=0; --i) {
+    group = input.add[i];
+    for (j=this._children[group._id].length-1; j>=0; --j) {
+      c = this._children[group._id][j];
+      c.builder.connect();
+      pipeline = c.builder.pipeline();
+      def = c.builder._def;
+
+      // This new child needs to be built during this propagation cycle.
+      // We could add its builder as a listener off the _recursor node, 
+      // but try to inline it if we can to minimize graph dispatches.
+      inline = (def.type !== Types.GROUP);
+      inline = inline && (this._graph.data(c.from) !== undefined); 
+      inline = inline && (pipeline[pipeline.length-1].listeners().length === 1); // Reactive geom source
+      inline = inline && (def.from && !def.from.mark); // Reactive geom target
+      c.inline = inline;
+
+      if (inline) this._graph.evaluate(input, c.builder);
+      else this._recursor.addListener(c.builder);
+    }
+  }
+
+  function removeTemp(c) {
+    if (c.type == Types.MARK && !c.inline &&
+        builder._graph.data(c.from) !== undefined) {
+      builder._recursor.removeListener(c.builder);
+    }
+  }
+
+  function updateAxis(a) { 
+    var scale = a.scale();
+    if (!input.scales[scale.scaleName]) return;
+    a.reset().def();
+  }
+  
+  function updateLegend(l) { 
+    var scale = l.size() || l.shape() || l.fill() || l.stroke();
+    if (!input.scales[scale.scaleName]) return;
+    l.reset().def();
+  }
+
+  for (i=0, len=input.mod.length; i<len; ++i) {
+    group = input.mod[i];
+
+    // Remove temporary connection for marks that draw from a source
+    if (hasMarks) builder._children[group._id].forEach(removeTemp);
+
+    // Update axis data defs
+    if (hasAxes) group.axes.forEach(updateAxis);
+
+    // Update legend data defs
+    if (hasLegends) group.legends.forEach(updateLegend);
+  }
+
+  function disconnectChildren(c) { 
+    builder._recursor.removeListener(c.builder);
+    c.builder.disconnect(); 
+  }
+
+  for (i=0, len=input.rem.length; i<len; ++i) {
+    group = input.rem[i];
+    // For deleted groups, disconnect their children
+    builder._children[group._id].forEach(disconnectChildren);
+    delete builder._children[group._id];
+  }
+
+  return input;
+}
+
+function scale(name, x) {
+  var group = this, s = null;
+  if (arguments.length === 2) return (group._scales[name] = x, x);
+  while (s == null) {
+    s = group._scales[name];
+    group = group.mark ? group.mark.group : group._parent;
+    if (!group) break;
+  }
+  return s;
+}
+
+function buildGroup(input, group) {
+  log.debug(input, ["building group", group._id]);
+
+  group._scales = group._scales || {};    
+  group.scale = scale.bind(group);
+
+  group.items = group.items || [];
+  this._children[group._id] = this._children[group._id] || [];
+
+  group.axes = group.axes || [];
+  group.axisItems = group.axisItems || [];
+
+  group.legends = group.legends || [];
+  group.legendItems = group.legendItems || [];
+}
+
+function buildMarks(input, group) {
+  log.debug(input, ["building children marks #"+group._id]);
+  var marks = this._def.marks,
+      mark, from, inherit, i, len, b;
+
+  for (i=0, len=marks.length; i<len; ++i) {
+    mark = marks[i];
+    from = mark.from || {};
+    inherit = group.datum._facetID;
+    group.items[i] = {group: group, _scaleRefs: {}};
+    b = (mark.type === Types.GROUP) ? new GroupBuilder() : new Builder();
+    b.init(this._graph, mark, group.items[i], this, group._id, inherit);
+    this._children[group._id].push({ 
+      builder: b, 
+      from: from.data || (from.mark ? ("vg_" + group._id + "_" + from.mark) : inherit), 
+      type: Types.MARK 
+    });
+  }
+}
+
+function buildAxes(input, group) {
+  var axes = group.axes,
+      axisItems = group.axisItems,
+      builder = this;
+
+  parseAxes(this._graph, this._def.axes, axes, group);
+  axes.forEach(function(a, i) {
+    var scale = builder._def.axes[i].scale,
+        def = a.def(),
+        b = null;
+
+    axisItems[i] = {group: group, axis: true, layer: def.layer};
+    b = (def.type === Types.GROUP) ? new GroupBuilder() : new Builder();
+    b.init(builder._graph, def, axisItems[i], builder)
+      .dependency(Deps.SCALES, scale);
+    builder._children[group._id].push({ builder: b, type: Types.AXIS, scale: scale });
+  });
+}
+
+function buildLegends(input, group) {
+  var legends = group.legends,
+      legendItems = group.legendItems,
+      builder = this;
+
+  parseLegends(this._graph, this._def.legends, legends, group);
+  legends.forEach(function(l, i) {
+    var scale = l.size() || l.shape() || l.fill() || l.stroke(),
+        def = l.def(),
+        b = null;
+
+    legendItems[i] = {group: group, legend: true};
+    b = (def.type === Types.GROUP) ? new GroupBuilder() : new Builder();
+    b.init(builder._graph, def, legendItems[i], builder)
+      .dependency(Deps.SCALES, scale);
+    builder._children[group._id].push({ builder: b, type: Types.LEGEND, scale: scale });
+  });
+}
+
+module.exports = GroupBuilder;
+},{"../parse/axes":90,"../parse/legends":96,"./Builder":108,"./Scale":111,"datalib":24,"vega-dataflow":39,"vega-logging":45}],111:[function(require,module,exports){
+(function (global){
+var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null),
+    dl = require('datalib'),
+    df = require('vega-dataflow'),
+    log = require('vega-logging'),
+    Node = df.Node, // jshint ignore:line
+    Deps = df.Dependencies,
+    Aggregate = require('../transforms/Aggregate');
+
+var Properties = {
+  width: 1,
+  height: 1
+};
+
+var Types = {
+  LINEAR: 'linear',
+  ORDINAL: 'ordinal',
+  LOG: 'log',
+  POWER: 'pow',
+  SQRT: 'sqrt',
+  TIME: 'time',
+  TIME_UTC: 'utc',
+  QUANTILE: 'quantile',
+  QUANTIZE: 'quantize',
+  THRESHOLD: 'threshold'
+};
+
+var DataRef = {
+  DOMAIN: 'domain',
+  RANGE: 'range',
+
+  COUNT: 'count',
+  GROUPBY: 'groupby',
+  MIN: 'min',
+  MAX: 'max',
+  VALUE: 'value',
+
+  ASC: 'asc',
+  DESC: 'desc'
+};
+
+function Scale(graph, def, parent) {
+  this._def     = def;
+  this._parent  = parent;
+  this._updated = false;
+  return Node.prototype.init.call(this, graph).reflows(true);
+}
+
+var proto = (Scale.prototype = new Node());
+
+proto.evaluate = function(input) {
+  var self = this,
+      fn = function(group) { scale.call(self, group); };
+
+  this._updated = false;
+  input.add.forEach(fn);
+  input.mod.forEach(fn);
+
+  // Scales are at the end of an encoding pipeline, so they should forward a
+  // reflow pulse. Thus, if multiple scales update in the parent group, we don't
+  // reevaluate child marks multiple times. 
+  if (this._updated) {
+    input.scales[this._def.name] = 1;
+    log.debug(input, ["scale", this._def.name]);  
+  } 
+  return df.ChangeSet.create(input, true);
+};
+
+// All of a scale's dependencies are registered during propagation as we parse
+// dataRefs. So a scale must be responsible for connecting itself to dependents.
+proto.dependency = function(type, deps) {
+  if (arguments.length == 2) {
+    var method = (type === Deps.DATA ? 'data' : 'signal');
+    deps = dl.array(deps);
+    for (var i=0, len=deps.length; i<len; ++i) {
+      this._graph[method](deps[i]).addListener(this._parent);
+    }
+  }
+
+  return Node.prototype.dependency.call(this, type, deps);
+};
+
+function scale(group) {
+  var name = this._def.name,
+      prev = name + ':prev',
+      s = instance.call(this, group.scale(name)),
+      m = s.type===Types.ORDINAL ? ordinal : quantitative,
+      rng = range.call(this, group);
+
+  m.call(this, s, rng, group);
+
+  group.scale(name, s);
+  group.scale(prev, group.scale(prev) || s);
+
+  return s;
+}
+
+function instance(scale) {
+  var config = this._graph.config(),
+      type = this._def.type || Types.LINEAR;
+  if (!scale || type !== scale.type) {
+    var ctor = config.scale[type] || d3.scale[type];
+    if (!ctor) throw Error('Unrecognized scale type: ' + type);
+    (scale = ctor()).type = scale.type || type;
+    scale.scaleName = this._def.name;
+    scale._prev = {};
+  }
+  return scale;
+}
+
+function ordinal(scale, rng, group) {
+  var def = this._def,
+      prev = scale._prev,
+      dataDrivenRange = false,
+      pad = signal.call(this, def.padding) || 0,
+      outer = def.outerPadding == null ? pad : signal.call(this, def.outerPadding),
+      points = def.points && signal.call(this, def.points),
+      round = signal.call(this, def.round) || def.round == null,
+      domain, str;
+  
+  // range pre-processing for data-driven ranges
+  if (dl.isObject(def.range) && !dl.isArray(def.range)) {
+    dataDrivenRange = true;
+    rng = dataRef.call(this, DataRef.RANGE, def.range, scale, group);
+  }
+  
+  // domain
+  domain = dataRef.call(this, DataRef.DOMAIN, def.domain, scale, group);
+  if (domain && !dl.equal(prev.domain, domain)) {
+    scale.domain(domain);
+    prev.domain = domain;
+    this._updated = true;
+  } 
+
+  // range
+  if (dl.equal(prev.range, rng)) return;
+
+  // width-defined range
+  if (def.bandWidth) {
+    var bw = signal.call(this, def.bandWidth),
+        len = domain.length,
+        space = def.points ? (pad*bw) : (pad*bw*(len-1) + 2*outer),
+        start;
+    if (rng[0] > rng[1]) {
+      start = rng[1] || 0;
+      rng = [start + (bw * len + space), start];
+    } else {
+      start = rng[0] || 0;
+      rng = [start, start + (bw * len + space)];
+    }
+  }
+
+  str = typeof rng[0] === 'string';
+  if (str || rng.length > 2 || rng.length===1 || dataDrivenRange) {
+    scale.range(rng); // color or shape values
+  } else if (points && round) {
+    scale.rangeRoundPoints(rng, pad);
+  } else if (points) {
+    scale.rangePoints(rng, pad);
+  } else if (round) {
+    scale.rangeRoundBands(rng, pad, outer);
+  } else {
+    scale.rangeBands(rng, pad, outer);
+  }
+
+  if (!scale.invert) {
+    scale.invert = function(x, y) {
+      if (arguments.length === 1) {
+        return scale.domain()[d3.bisect(scale.range(), x) - 1];
+      } else if (arguments.length === 2) {  // Invert extents
+        if (!dl.isNumber(x) || !dl.isNumber(y)) {
+          throw Error('Extents to ordinal invert are not numbers ('+x+', '+y+').');
+        }
+
+        var points = [],
+            rng = scale.range(),
+            i = 0, len = rng.length, r;
+
+        for(; i<len; ++i) {
+          r = rng[i];
+          if (x < y ? x <= r && r <= y : y <= r && r <= x) {
+            points.push(r);
+          }
+        }
+
+        return points.map(function(p) { return scale.invert(p); });
+      }
+    };
+  }
+
+  prev.range = rng;
+  this._updated = true;
+}
+
+function quantitative(scale, rng, group) {
+  var def = this._def,
+      prev = scale._prev,
+      round = signal.call(this, def.round),
+      exponent = signal.call(this, def.exponent),
+      clamp = signal.call(this, def.clamp),
+      nice = signal.call(this, def.nice),
+      domain, interval;
+
+  // domain
+  domain = (def.type === Types.QUANTILE) ?
+    dataRef.call(this, DataRef.DOMAIN, def.domain, scale, group) :
+    domainMinMax.call(this, scale, group);
+  if (domain && !dl.equal(prev.domain, domain)) {
+    scale.domain(domain);
+    prev.domain = domain;
+    this._updated = true;
+  } 
+
+  // range
+  // vertical scales should flip by default, so use XOR here
+  if (signal.call(this, def.range) === 'height') rng = rng.reverse();
+  if (dl.equal(prev.range, rng)) return;
+  scale[round && scale.rangeRound ? 'rangeRound' : 'range'](rng);
+  prev.range = rng;
+  this._updated = true;
+
+  // TODO: Support signals for these properties. Until then, only eval
+  // them once.
+  if (this._stamp > 0) return;
+  if (exponent && def.type===Types.POWER) scale.exponent(exponent);
+  if (clamp) scale.clamp(true);
+  if (nice) {
+    if (def.type === Types.TIME) {
+      interval = d3.time[nice];
+      if (!interval) log.error('Unrecognized interval: ' + interval);
+      scale.nice(interval);
+    } else {
+      scale.nice();
+    }
+  }
+}
+
+function isUniques(scale) { 
+  return scale.type === Types.ORDINAL || scale.type === Types.QUANTILE; 
+}
+
+function getRefs(def) { 
+  return def.fields || dl.array(def);
+}
+
+function inherits(refs) {
+  return refs.some(function(r) {
+    if (!r.data) return true;
+    return r.data && dl.array(r.field).some(function(f) {
+      return f.parent;
+    });
+  });
+}
+
+function getFields(ref, group) {
+  return dl.array(ref.field).map(function(f) {
+    return f.parent ?
+      dl.accessor(f.parent)(group.datum) :
+      f; // String or {'signal'}
+  });
+}
+
+// Scale datarefs can be computed over multiple schema types. 
+// This function determines the type of aggregator created, and
+// what data is sent to it: values, tuples, or multi-tuples that must
+// be standardized into a consistent schema. 
+function aggrType(def, scale) {
+  var refs = getRefs(def);
+
+  // If we're operating over only a single domain, send full tuples
+  // through for efficiency (fewer accessor creations/calls)
+  if (refs.length == 1 && dl.array(refs[0].field).length == 1) {
+    return Aggregate.TYPES.TUPLE;
+  }
+
+  // With quantitative scales, we only care about min/max.
+  if (!isUniques(scale)) return Aggregate.TYPES.VALUE;
+
+  // If we don't sort, then we can send values directly to aggrs as well
+  if (!dl.isObject(def.sort)) return Aggregate.TYPES.VALUE;
+
+  return Aggregate.TYPES.MULTI;
+}
+
+function getCache(which, def, scale, group) {
+  var refs = getRefs(def),
+      inherit = inherits(refs),
+      atype = aggrType(def, scale),
+      uniques = isUniques(scale),
+      sort = def.sort,
+      ck = '_'+which,
+      fields = getFields(refs[0], group);
+
+  if (scale[ck] || this[ck]) return scale[ck] || this[ck];
+
+  var cache = new Aggregate(this._graph).type(atype),
+      groupby, summarize;
+
+  // If a scale's dataref doesn't inherit data from the group, we can
+  // store the dataref aggregator at the Scale (dataflow node) level. 
+  if (inherit) {
+    scale[ck] = cache;
+  } else {
+    this[ck]  = cache;
+  }
+
+  if (uniques) {
+    if (atype === Aggregate.TYPES.VALUE) {
+      groupby = [{ name: DataRef.GROUPBY, get: dl.identity }];
+      summarize = {'*': DataRef.COUNT};
+    } else if (atype === Aggregate.TYPES.TUPLE) {
+      groupby = [{ name: DataRef.GROUPBY, get: dl.$(fields[0]) }];
+      summarize = dl.isObject(sort) ? [{
+        field: DataRef.VALUE,
+        get:  dl.$(sort.field),
+        ops: [sort.op]
+      }] : {'*': DataRef.COUNT};
+    } else {  // atype === Aggregate.TYPES.MULTI
+      groupby   = DataRef.GROUPBY;
+      summarize = [{ field: DataRef.VALUE, ops: [sort.op] }]; 
+    }
+  } else {
+    groupby = [];
+    summarize = [{
+      field: DataRef.VALUE,
+      get: (atype == Aggregate.TYPES.TUPLE) ? dl.$(fields[0]) : dl.identity,
+      ops: [DataRef.MIN, DataRef.MAX],
+      as:  [DataRef.MIN, DataRef.MAX]
+    }];
+  }
+
+  cache.param('groupby', groupby)
+    .param('summarize', summarize);
+
+  return (cache._lastUpdate = -1, cache);
+}
+
+function dataRef(which, def, scale, group) {
+  if (def == null) { return []; }
+  if (dl.isArray(def)) return def.map(signal.bind(this));
+
+  var self = this, graph = this._graph,
+      refs = getRefs(def),
+      inherit = inherits(refs),
+      atype = aggrType(def, scale),
+      cache = getCache.apply(this, arguments),
+      sort  = def.sort,
+      uniques = isUniques(scale),
+      i, rlen, j, flen, ref, fields, field, data, from, so, cmp;
+
+  function addDep(s) {
+    self.dependency(Deps.SIGNALS, s);
+  }
+
+  if (inherit || (!inherit && cache._lastUpdate < this._stamp)) {
+    for (i=0, rlen=refs.length; i<rlen; ++i) {
+      ref = refs[i];
+      from = ref.data || group.datum._facetID;
+      data = graph.data(from).last();
+
+      if (data.stamp <= this._stamp) continue;
+
+      fields = getFields(ref, group);
+      for (j=0, flen=fields.length; j<flen; ++j) {
+        field = fields[j];
+
+        if (atype === Aggregate.TYPES.VALUE) {
+          cache.accessors(null, field);
+        } else if (atype === Aggregate.TYPES.MULTI) {
+          cache.accessors(field, ref.sort || sort.field);
+        } // Else (Tuple-case) is handled by the aggregator accessors by default
+
+        cache.evaluate(data);
+      }
+
+      this.dependency(Deps.DATA, from);
+      cache.dependency(Deps.SIGNALS).forEach(addDep);
+    }
+
+    cache._lastUpdate = this._stamp;
+
+    data = cache.aggr().result();
+    if (uniques) {
+      if (dl.isObject(sort)) {
+        cmp = (so = sort.order) && so.signal ? graph.signalRef(so.signal) : so;
+        cmp = (cmp == DataRef.DESC ? '-' : '+') + sort.op + '_' + DataRef.VALUE;
+        cmp = dl.comparator(cmp);
+      } else if (sort === true) {
+        cmp = dl.comparator(DataRef.GROUPBY);
+      }
+
+      if (cmp) data = data.sort(cmp);
+      cache._values = data.map(function(d) { return d[DataRef.GROUPBY]; });
+    } else {
+      data = data[0];
+      cache._values = !dl.isValid(data) ? [] : [data[DataRef.MIN], data[DataRef.MAX]];
+    }
+  }
+
+  return cache._values;
+}
+
+function signal(v) {
+  if (!v || !v.signal) return v;
+  var s = v.signal, ref;
+  this.dependency(Deps.SIGNALS, (ref = dl.field(s))[0]);
+  return this._graph.signalRef(ref);
+}
+
+function domainMinMax(scale, group) {
+  var def = this._def,
+      domain = [null, null], s, z;
+
+  if (def.domain !== undefined) {
+    domain = (!dl.isObject(def.domain)) ? domain :
+      dataRef.call(this, DataRef.DOMAIN, def.domain, scale, group);
+  }
+
+  z = domain.length - 1;
+  if (def.domainMin !== undefined) {
+    if (dl.isObject(def.domainMin)) {
+      if (def.domainMin.signal) {
+        domain[0] = dl.isValid(s=signal.call(this, def.domainMin)) ? s : domain[0];
+      } else {
+        domain[0] = dataRef.call(this, DataRef.DOMAIN+DataRef.MIN, def.domainMin, scale, group)[0];
+      }
+    } else {
+      domain[0] = def.domainMin;
+    }
+  }
+  if (def.domainMax !== undefined) {
+    if (dl.isObject(def.domainMax)) {
+      if (def.domainMax.signal) {
+        domain[z] = dl.isValid(s=signal.call(this, def.domainMax)) ? s : domain[z];
+      } else {
+        domain[z] = dataRef.call(this, DataRef.DOMAIN+DataRef.MAX, def.domainMax, scale, group)[1];
+      }
+    } else {
+      domain[z] = def.domainMax;
+    }
+  }
+  if (def.type !== Types.LOG && def.type !== Types.TIME && (def.zero || def.zero===undefined)) {
+    domain[0] = Math.min(0, domain[0]);
+    domain[z] = Math.max(0, domain[z]);
+  }
+  return domain;
+}
+
+function range(group) {
+  var def = this._def,
+      config = this._graph.config(),
+      rangeVal = signal.call(this, def.range),
+      rng = [null, null];
+
+  if (rangeVal !== undefined) {
+    if (typeof rangeVal === 'string') {
+      if (Properties[rangeVal]) {
+        rng = [0, group[rangeVal]];
+      } else if (config.range[rangeVal]) {
+        rng = config.range[rangeVal];
+      } else {
+        log.error('Unrecogized range: ' + rangeVal);
+        return rng;
+      }
+    } else if (dl.isArray(rangeVal)) {
+      rng = dl.duplicate(rangeVal).map(signal.bind(this));
+    } else if (dl.isObject(rangeVal)) {
+      return null; // early exit
+    } else {
+      rng = [0, rangeVal];
+    }
+  }
+  if (def.rangeMin !== undefined) {
+    rng[0] = def.rangeMin.signal ?
+      signal.call(this, def.rangeMin) :
+      def.rangeMin;
+  }
+  if (def.rangeMax !== undefined) {
+    rng[rng.length-1] = def.rangeMax.signal ?
+      signal.call(this, def.rangeMax) :
+      def.rangeMax;
+  }
+  
+  if (def.reverse !== undefined) {
+    var rev = signal.call(this, def.reverse);
+    if (dl.isObject(rev)) {
+      rev = dl.accessor(rev.field)(group.datum);
+    }
+    if (rev) rng = rng.reverse();
+  }
+  
+  return rng;
+}
+
+module.exports = Scale;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"../transforms/Aggregate":116,"datalib":24,"vega-dataflow":39,"vega-logging":45}],112:[function(require,module,exports){
+(function (global){
+var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null),
+    bound = require('vega-scenegraph').bound,
+    Tuple = require('vega-dataflow').Tuple,
+    Status = require('./Builder').STATUS;
+
+function Transition(duration, ease) {
+  this.duration = duration || 500;
+  this.ease = ease && d3.ease(ease) || d3.ease('cubic-in-out');
+  this.updates = {next: null};
+}
+
+var prototype = Transition.prototype;
+
+var skip = {
+  'text': 1,
+  'url':  1
+};
+
+prototype.interpolate = function(item, values) {
+  var key, curr, next, interp, list = null;
+
+  for (key in values) {
+    curr = item[key];
+    next = values[key];      
+    if (curr !== next) {
+      if (skip[key] || curr === undefined) {
+        // skip interpolation for specific keys or undefined start values
+        Tuple.set(item, key, next);
+      } else if (typeof curr === 'number' && !isFinite(curr)) {
+        // for NaN or infinite numeric values, skip to final value
+        Tuple.set(item, key, next);
+      } else {
+        // otherwise lookup interpolator
+        interp = d3.interpolate(curr, next);
+        interp.property = key;
+        (list || (list=[])).push(interp);
+      }
+    }
+  }
+
+  if (list === null && item.status === Status.EXIT) {
+    list = []; // ensure exiting items are included
+  }
+
+  if (list != null) {
+    list.item = item;
+    list.ease = item.mark.ease || this.ease;
+    list.next = this.updates.next;
+    this.updates.next = list;
+  }
+  return this;
+};
+
+prototype.start = function(callback) {
+  var t = this, prev = t.updates, curr = prev.next;
+  for (; curr!=null; prev=curr, curr=prev.next) {
+    if (curr.item.status === Status.EXIT) {
+      // Only mark item as exited when it is removed.
+      curr.item.status = Status.UPDATE;
+      curr.remove = true;
+    }
+  }
+  t.callback = callback;
+  d3.timer(function(elapsed) { return step.call(t, elapsed); });
+};
+
+function step(elapsed) {
+  var list = this.updates, prev = list, curr = prev.next,
+      duration = this.duration,
+      item, delay, f, e, i, n, stop = true;
+
+  for (; curr!=null; prev=curr, curr=prev.next) {
+    item = curr.item;
+    delay = item.delay || 0;
+
+    f = (elapsed - delay) / duration;
+    if (f < 0) { stop = false; continue; }
+    if (f > 1) f = 1;
+    e = curr.ease(f);
+
+    for (i=0, n=curr.length; i<n; ++i) {
+      item[curr[i].property] = curr[i](e);
+    }
+    item.touch();
+    bound.item(item);
+
+    if (f === 1) {
+      if (curr.remove) {
+        item.status = Status.EXIT;
+        item.remove();
+      }
+      prev.next = curr.next;
+      curr = prev;
+    } else {
+      stop = false;
+    }
+  }
+
+  this.callback();
+  return stop;
+}
+
+module.exports = Transition;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"./Builder":108,"vega-dataflow":39,"vega-scenegraph":46}],113:[function(require,module,exports){
+var dl = require('datalib'),
+    Tuple = require('vega-dataflow').Tuple,
+    parseMark = require('../parse/mark');
+
+var TIME    = 'time',
+    UTC     = 'utc',
+    STRING  = 'string',
+    ORDINAL = 'ordinal',
+    NUMBER  = 'number';
+
+function axs(model) {
+  var scale,
+      config = model.config(),
+      orient = config.axis.orient,
+      offset = 0,
+      titleOffset = config.axis.titleOffset,
+      axisDef = {},
+      layer = 'front',
+      grid = false,
+      title = null,
+      tickMajorSize = config.axis.tickSize,
+      tickMinorSize = config.axis.tickSize,
+      tickEndSize = config.axis.tickSize,
+      tickPadding = config.axis.padding,
+      tickValues = null,
+      tickFormatString = null,
+      tickFormatType = null,
+      tickSubdivide = 0,
+      tickCount = config.axis.ticks,
+      gridLineStyle = {},
+      tickLabelStyle = {},
+      majorTickStyle = {},
+      minorTickStyle = {},
+      titleStyle = {},
+      domainStyle = {},
+      m = { // Axis marks as references for updates
+        gridLines:  {},
+        majorTicks: {},
+        minorTicks: {},
+        tickLabels: {},
+        domain: {},
+        title:  {}
+      };
+
+  var axis = {};
+
+  function reset() {
+    axisDef.type = null;
+  }
+
+  function ingest(d) {
+    return {data: d};
+  }
+
+  function getTickFormat() {
+    var formatType = tickFormatType || inferFormatType();
+    return getFormatter(formatType, tickFormatString);
+  }
+
+  function inferFormatType() {
+    switch (scale.type) {
+      case TIME:    return TIME;
+      case UTC:     return UTC;
+      case ORDINAL: return STRING;
+      default:      return NUMBER;
+    }
+  }
+
+  // Adapted from d3 log scale
+  // TODO customize? replace with range-size-aware filtering?
+  function logFilter(domain, count, f) {
+    if (count == null) return f;
+    var base = scale.base(),
+        k = Math.min(base, scale.ticks().length / count),
+        v = domain[0] > 0 ? (e = 1e-12, Math.ceil) : (e = -1e-12, Math.floor),
+        e;
+    function log(x) {
+      return (domain[0] < 0 ?
+        -Math.log(x > 0 ? 0 : -x) :
+        Math.log(x < 0 ? 0 : x)) / Math.log(base);
+    }
+    function pow(x) {
+      return domain[0] < 0 ? -Math.pow(base, -x) : Math.pow(base, x);
+    }
+    return function(d) {
+      return pow(v(log(d) + e)) / d >= k ? f(d) : '';
+    };
+  }
+
+  function getFormatter(formatType, str) {
+    var fmt = dl.format,
+        log = scale.type === 'log',
+        domain, f;
+
+    switch (formatType) {
+      case NUMBER:
+         domain = scale.domain();
+         f = fmt.auto.number(domain, tickCount, str || (log ? '.1r' : null));
+         return log ? logFilter(domain, tickCount, f) : f;
+      case TIME: return (str ? fmt : fmt.auto).time(str);
+      case UTC:  return (str ? fmt : fmt.auto).utc(str);
+      default:   return String;
+    }
+  }
+  
+  function getTicks(format) {
+    var major = tickValues || (scale.ticks ? scale.ticks(tickCount) : scale.domain()),
+        minor = axisSubdivide(scale, major, tickSubdivide).map(ingest);
+    major = major.map(function(d) { return (d = ingest(d), d.label = format(d.data), d); });
+    return [major, minor];
+  }
+
+  axis.def = function() {
+    if (!axisDef.type) axis_def(scale);
+
+    var ticks = getTicks(getTickFormat());
+    var tdata = title ? [title].map(ingest) : [];
+
+    axisDef.marks[0].from = function() { return grid ? ticks[0] : []; };
+    axisDef.marks[1].from = function() { return ticks[0]; };
+    axisDef.marks[2].from = function() { return ticks[1]; };
+    axisDef.marks[3].from = axisDef.marks[1].from;
+    axisDef.marks[4].from = function() { return [1]; };
+    axisDef.marks[5].from = function() { return tdata; };
+    axisDef.offset = offset;
+    axisDef.orient = orient;
+    axisDef.layer = layer;
+
+    return axisDef;
+  };
+
+  function axis_def(scale) {
+    // setup scale mapping
+    var newScale, oldScale, range;
+    if (scale.type === ORDINAL) {
+      newScale = {scale: scale.scaleName, offset: 0.5 + scale.rangeBand()/2};
+      oldScale = newScale;
+    } else {
+      newScale = {scale: scale.scaleName, offset: 0.5};
+      oldScale = {scale: scale.scaleName+':prev', offset: 0.5};
+    }
+    range = axisScaleRange(scale);
+
+    // setup axis marks
+    dl.extend(m.gridLines, axisTicks(config));
+    dl.extend(m.majorTicks, axisTicks(config));
+    dl.extend(m.minorTicks, axisTicks(config));
+    dl.extend(m.tickLabels, axisTickLabels(config));
+    dl.extend(m.domain, axisDomain(config));
+    dl.extend(m.title, axisTitle(config));
+    m.gridLines.properties.enter.stroke = {value: config.axis.gridColor};
+    m.gridLines.properties.enter.strokeOpacity = {value: config.axis.gridOpacity};
+
+    // extend axis marks based on axis orientation
+    axisTicksExtend(orient, m.gridLines, oldScale, newScale, Infinity);
+    axisTicksExtend(orient, m.majorTicks, oldScale, newScale, tickMajorSize);
+    axisTicksExtend(orient, m.minorTicks, oldScale, newScale, tickMinorSize);
+    axisLabelExtend(orient, m.tickLabels, oldScale, newScale, tickMajorSize, tickPadding);
+
+    axisDomainExtend(orient, m.domain, range, tickEndSize);
+    axisTitleExtend(orient, m.title, range, titleOffset); // TODO get offset
+    
+    // add / override custom style properties
+    dl.extend(m.gridLines.properties.update, gridLineStyle);
+    dl.extend(m.majorTicks.properties.update, majorTickStyle);
+    dl.extend(m.minorTicks.properties.update, minorTickStyle);
+    dl.extend(m.tickLabels.properties.update, tickLabelStyle);
+    dl.extend(m.domain.properties.update, domainStyle);
+    dl.extend(m.title.properties.update, titleStyle);
+
+    var marks = [m.gridLines, m.majorTicks, m.minorTicks, m.tickLabels, m.domain, m.title];
+    dl.extend(axisDef, {
+      type: 'group',
+      interactive: false,
+      properties: { 
+        enter: {
+          encode: axisUpdate,
+          scales: [scale.scaleName],
+          signals: [], data: []
+        },
+        update: {
+          encode: axisUpdate,
+          scales: [scale.scaleName],
+          signals: [], data: []
+        }
+      }
+    });
+
+    axisDef.marks = marks.map(function(m) { return parseMark(model, m); });
+  }
+
+  axis.scale = function(x) {
+    if (!arguments.length) return scale;
+    if (scale !== x) { scale = x; reset(); }
+    return axis;
+  };
+
+  axis.orient = function(x) {
+    if (!arguments.length) return orient;
+    if (orient !== x) {
+      orient = x in axisOrients ? x + '' : config.axis.orient;
+      reset();
+    }
+    return axis;
+  };
+
+  axis.title = function(x) {
+    if (!arguments.length) return title;
+    if (title !== x) { title = x; reset(); }
+    return axis;
+  };
+
+  axis.tickCount = function(x) {
+    if (!arguments.length) return tickCount;
+    tickCount = x;
+    return axis;
+  };
+
+  axis.tickValues = function(x) {
+    if (!arguments.length) return tickValues;
+    tickValues = x;
+    return axis;
+  };
+
+  axis.tickFormat = function(x) {
+    if (!arguments.length) return tickFormatString;
+    if (tickFormatString !== x) {
+      tickFormatString = x;
+      reset();
+    }
+    return axis;
+  };
+
+  axis.tickFormatType = function(x) {
+    if (!arguments.length) return tickFormatType;
+    if (tickFormatType !== x) {
+      tickFormatType = x;
+      reset();
+    }
+    return axis;
+  };
+
+  axis.tickSize = function(x, y) {
+    if (!arguments.length) return tickMajorSize;
+    var n = arguments.length - 1,
+        major = +x,
+        minor = n > 1 ? +y : tickMajorSize,
+        end   = n > 0 ? +arguments[n] : tickMajorSize;
+
+    if (tickMajorSize !== major ||
+        tickMinorSize !== minor ||
+        tickEndSize !== end) {
+      reset();
+    }
+
+    tickMajorSize = major;
+    tickMinorSize = minor;
+    tickEndSize = end;
+    return axis;
+  };
+
+  axis.tickSubdivide = function(x) {
+    if (!arguments.length) return tickSubdivide;
+    tickSubdivide = +x;
+    return axis;
+  };
+  
+  axis.offset = function(x) {
+    if (!arguments.length) return offset;
+    offset = dl.isObject(x) ? x : +x;
+    return axis;
+  };
+
+  axis.tickPadding = function(x) {
+    if (!arguments.length) return tickPadding;
+    if (tickPadding !== +x) { tickPadding = +x; reset(); }
+    return axis;
+  };
+
+  axis.titleOffset = function(x) {
+    if (!arguments.length) return titleOffset;
+    if (titleOffset !== +x) { titleOffset = +x; reset(); }
+    return axis;
+  };
+
+  axis.layer = function(x) {
+    if (!arguments.length) return layer;
+    if (layer !== x) { layer = x; reset(); }
+    return axis;
+  };
+
+  axis.grid = function(x) {
+    if (!arguments.length) return grid;
+    if (grid !== x) { grid = x; reset(); }
+    return axis;
+  };
+
+  axis.gridLineProperties = function(x) {
+    if (!arguments.length) return gridLineStyle;
+    if (gridLineStyle !== x) { gridLineStyle = x; }
+    return axis;
+  };
+
+  axis.majorTickProperties = function(x) {
+    if (!arguments.length) return majorTickStyle;
+    if (majorTickStyle !== x) { majorTickStyle = x; }
+    return axis;
+  };
+
+  axis.minorTickProperties = function(x) {
+    if (!arguments.length) return minorTickStyle;
+    if (minorTickStyle !== x) { minorTickStyle = x; }
+    return axis;
+  };
+
+  axis.tickLabelProperties = function(x) {
+    if (!arguments.length) return tickLabelStyle;
+    if (tickLabelStyle !== x) { tickLabelStyle = x; }
+    return axis;
+  };
+
+  axis.titleProperties = function(x) {
+    if (!arguments.length) return titleStyle;
+    if (titleStyle !== x) { titleStyle = x; }
+    return axis;
+  };
+
+  axis.domainProperties = function(x) {
+    if (!arguments.length) return domainStyle;
+    if (domainStyle !== x) { domainStyle = x; }
+    return axis;
+  };
+  
+  axis.reset = function() { 
+    reset(); 
+    return axis; 
+  };
+
+  return axis;
+}
+
+var axisOrients = {top: 1, right: 1, bottom: 1, left: 1};
+
+function axisSubdivide(scale, ticks, m) {
+  var subticks = [];
+  if (m && ticks.length > 1) {
+    var extent = axisScaleExtent(scale.domain()),
+        i = -1,
+        n = ticks.length,
+        d = (ticks[1] - ticks[0]) / ++m,
+        j,
+        v;
+    while (++i < n) {
+      for (j = m; --j > 0;) {
+        if ((v = +ticks[i] - j * d) >= extent[0]) {
+          subticks.push(v);
+        }
+      }
+    }
+    for (--i, j = 0; ++j < m && (v = +ticks[i] + j * d) < extent[1];) {
+      subticks.push(v);
+    }
+  }
+  return subticks;
+}
+
+function axisScaleExtent(domain) {
+  var start = domain[0], stop = domain[domain.length - 1];
+  return start < stop ? [start, stop] : [stop, start];
+}
+
+function axisScaleRange(scale) {
+  return scale.rangeExtent ?
+    scale.rangeExtent() :
+    axisScaleExtent(scale.range());
+}
+
+var axisAlign = {
+  bottom: 'center',
+  top: 'center',
+  left: 'right',
+  right: 'left'
+};
+
+var axisBaseline = {
+  bottom: 'top',
+  top: 'bottom',
+  left: 'middle',
+  right: 'middle'
+};
+
+function axisLabelExtend(orient, labels, oldScale, newScale, size, pad) {
+  size = Math.max(size, 0) + pad;
+  if (orient === 'left' || orient === 'top') {
+    size *= -1;
+  }  
+  if (orient === 'top' || orient === 'bottom') {
+    dl.extend(labels.properties.enter, {
+      x: oldScale,
+      y: {value: size},
+    });
+    dl.extend(labels.properties.update, {
+      x: newScale,
+      y: {value: size},
+      align: {value: 'center'},
+      baseline: {value: axisBaseline[orient]}
+    });
+  } else {
+    dl.extend(labels.properties.enter, {
+      x: {value: size},
+      y: oldScale,
+    });
+    dl.extend(labels.properties.update, {
+      x: {value: size},
+      y: newScale,
+      align: {value: axisAlign[orient]},
+      baseline: {value: 'middle'}
+    });
+  }
+}
+
+function axisTicksExtend(orient, ticks, oldScale, newScale, size) {
+  var sign = (orient === 'left' || orient === 'top') ? -1 : 1;
+  if (size === Infinity) {
+    size = (orient === 'top' || orient === 'bottom') ?
+      {field: {group: 'height', level: 2}, mult: -sign} :
+      {field: {group: 'width',  level: 2}, mult: -sign};
+  } else {
+    size = {value: sign * size};
+  }
+  if (orient === 'top' || orient === 'bottom') {
+    dl.extend(ticks.properties.enter, {
+      x:  oldScale,
+      y:  {value: 0},
+      y2: size
+    });
+    dl.extend(ticks.properties.update, {
+      x:  newScale,
+      y:  {value: 0},
+      y2: size
+    });
+    dl.extend(ticks.properties.exit, {
+      x:  newScale,
+    });        
+  } else {
+    dl.extend(ticks.properties.enter, {
+      x:  {value: 0},
+      x2: size,
+      y:  oldScale
+    });
+    dl.extend(ticks.properties.update, {
+      x:  {value: 0},
+      x2: size,
+      y:  newScale
+    });
+    dl.extend(ticks.properties.exit, {
+      y:  newScale,
+    });
+  }
+}
+
+function axisTitleExtend(orient, title, range, offset) {
+  var mid = ~~((range[0] + range[1]) / 2),
+      sign = (orient === 'top' || orient === 'left') ? -1 : 1;
+  
+  if (orient === 'bottom' || orient === 'top') {
+    dl.extend(title.properties.update, {
+      x: {value: mid},
+      y: {value: sign*offset},
+      angle: {value: 0}
+    });
+  } else {
+    dl.extend(title.properties.update, {
+      x: {value: sign*offset},
+      y: {value: mid},
+      angle: {value: orient === 'left' ? -90 : 90}
+    });
+  }
+}
+
+function axisDomainExtend(orient, domain, range, size) {
+  var path;
+  if (orient === 'top' || orient === 'left') {
+    size = -1 * size;
+  }
+  if (orient === 'bottom' || orient === 'top') {
+    path = 'M' + range[0] + ',' + size + 'V0H' + range[1] + 'V' + size;
+  } else {
+    path = 'M' + size + ',' + range[0] + 'H0V' + range[1] + 'H' + size;
+  }
+  domain.properties.update.path = {value: path};
+}
+
+function axisUpdate(item, group, trans) {
+  var o = trans ? {} : item,
+      offset = item.mark.def.offset,
+      orient = item.mark.def.orient,
+      width  = group.width,
+      height = group.height; // TODO fallback to global w,h?
+
+  if (dl.isArray(offset)) {
+    var ofx = offset[0],
+        ofy = offset[1];
+
+    switch (orient) {
+      case 'left':   { Tuple.set(o, 'x', -ofx); Tuple.set(o, 'y', ofy); break; }
+      case 'right':  { Tuple.set(o, 'x', width + ofx); Tuple.set(o, 'y', ofy); break; }
+      case 'bottom': { Tuple.set(o, 'x', ofx); Tuple.set(o, 'y', height + ofy); break; }
+      case 'top':    { Tuple.set(o, 'x', ofx); Tuple.set(o, 'y', -ofy); break; }
+      default:       { Tuple.set(o, 'x', ofx); Tuple.set(o, 'y', ofy); }
+    }
+  } else {
+    if (dl.isObject(offset)) {
+      offset = -group.scale(offset.scale)(offset.value);
+    }
+
+    switch (orient) {
+      case 'left':   { Tuple.set(o, 'x', -offset); Tuple.set(o, 'y', 0); break; }
+      case 'right':  { Tuple.set(o, 'x', width + offset); Tuple.set(o, 'y', 0); break; }
+      case 'bottom': { Tuple.set(o, 'x', 0); Tuple.set(o, 'y', height + offset); break; }
+      case 'top':    { Tuple.set(o, 'x', 0); Tuple.set(o, 'y', -offset); break; }
+      default:       { Tuple.set(o, 'x', 0); Tuple.set(o, 'y', 0); }
+    }
+  }
+
+  if (trans) trans.interpolate(item, o);
+  return true;
+}
+
+function axisTicks(config) {
+  return {
+    type: 'rule',
+    interactive: false,
+    key: 'data',
+    properties: {
+      enter: {
+        stroke: {value: config.axis.tickColor},
+        strokeWidth: {value: config.axis.tickWidth},
+        opacity: {value: 1e-6}
+      },
+      exit: { opacity: {value: 1e-6} },
+      update: { opacity: {value: 1} }
+    }
+  };
+}
+
+function axisTickLabels(config) {
+  return {
+    type: 'text',
+    interactive: true,
+    key: 'data',
+    properties: {
+      enter: {
+        fill: {value: config.axis.tickLabelColor},
+        font: {value: config.axis.tickLabelFont},
+        fontSize: {value: config.axis.tickLabelFontSize},
+        opacity: {value: 1e-6},
+        text: {field: 'label'}
+      },
+      exit: { opacity: {value: 1e-6} },
+      update: { opacity: {value: 1} }
+    }
+  };
+}
+
+function axisTitle(config) {
+  return {
+    type: 'text',
+    interactive: true,
+    properties: {
+      enter: {
+        font: {value: config.axis.titleFont},
+        fontSize: {value: config.axis.titleFontSize},
+        fontWeight: {value: config.axis.titleFontWeight},
+        fill: {value: config.axis.titleColor},
+        align: {value: 'center'},
+        baseline: {value: 'middle'},
+        text: {field: 'data'}
+      },
+      update: {}
+    }
+  };
+}
+
+function axisDomain(config) {
+  return {
+    type: 'path',
+    interactive: false,
+    properties: {
+      enter: {
+        x: {value: 0.5},
+        y: {value: 0.5},
+        stroke: {value: config.axis.axisColor},
+        strokeWidth: {value: config.axis.axisWidth}
+      },
+      update: {}
+    }
+  };
+}
+
+module.exports = axs;
+},{"../parse/mark":97,"datalib":24,"vega-dataflow":39}],114:[function(require,module,exports){
+(function (global){
+var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null),
+    dl = require('datalib'),
+    Gradient = require('vega-scenegraph').Gradient,
+    parseProperties = require('../parse/properties'),
+    parseMark = require('../parse/mark');
+
+function lgnd(model) {
+  var size = null,
+      shape = null,
+      fill = null,
+      stroke = null,
+      spacing = null,
+      values = null,
+      format = null,
+      formatString = null,
+      config = model.config(),
+      title,
+      orient = 'right',
+      offset = config.legend.offset,
+      padding = config.legend.padding,
+      tickArguments = [5],
+      legendStyle = {},
+      symbolStyle = {},
+      gradientStyle = {},
+      titleStyle = {},
+      labelStyle = {},
+      m = { // Legend marks as references for updates
+        titles:  {},
+        symbols: {},
+        labels:  {},
+        gradient: {}
+      };
+
+  var legend = {},
+      legendDef = {};
+
+  function reset() { legendDef.type = null; }
+  function ingest(d, i) { return {data: d, index: i}; }
+
+  legend.def = function() {
+    var scale = size || shape || fill || stroke;
+    
+    format = !formatString ? null : ((scale.type === 'time') ?
+      dl.format.time(formatString) : dl.format.number(formatString));
+    
+    if (!legendDef.type) {
+      legendDef = (scale===fill || scale===stroke) && !discrete(scale.type) ?
+        quantDef(scale) : ordinalDef(scale);      
+    }
+    legendDef.orient = orient;
+    legendDef.offset = offset;
+    legendDef.padding = padding;
+    return legendDef;
+  };
+
+  function discrete(type) {
+    return type==='ordinal' || type==='quantize' ||
+           type==='quantile' || type==='threshold';
+  }
+
+  function ordinalDef(scale) {
+    var def = o_legend_def(size, shape, fill, stroke);
+
+    // generate data
+    var data = (values == null ?
+      (scale.ticks ? scale.ticks.apply(scale, tickArguments) : scale.domain()) :
+      values).map(ingest);
+    var fmt = format==null ? (scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments) : String) : format;
+    
+    // determine spacing between legend entries
+    var fs, range, offset, pad=5, domain = d3.range(data.length);
+    if (size) {
+      range = data.map(function(x) { return Math.sqrt(size(x.data)); });
+      offset = d3.max(range);
+      range = range.reduce(function(a,b,i,z) {
+          if (i > 0) a[i] = a[i-1] + z[i-1]/2 + pad;
+          return (a[i] += b/2, a); }, [0]).map(Math.round);
+    } else {
+      offset = Math.round(Math.sqrt(config.legend.symbolSize));
+      range = spacing ||
+        (fs = labelStyle.fontSize) && (fs.value + pad) ||
+        (config.legend.labelFontSize + pad);
+      range = domain.map(function(d,i) {
+        return Math.round(offset/2 + i*range);
+      });
+    }
+
+    // account for padding and title size
+    var sz = padding, ts;
+    if (title) {
+      ts = titleStyle.fontSize;
+      sz += 5 + ((ts && ts.value) || config.legend.titleFontSize);
+    }
+    for (var i=0, n=range.length; i<n; ++i) range[i] += sz;
+    
+    // build scale for label layout
+    var scaleSpec = {
+      name: 'legend',
+      type: 'ordinal',
+      points: true,
+      domain: domain,
+      range: range
+    };
+    
+    // update legend def
+    var tdata = (title ? [title] : []).map(ingest);
+    data.forEach(function(d) {
+      d.label = fmt(d.data);
+      d.offset = offset;
+    });
+    def.scales = [ scaleSpec ];
+    def.marks[0].from = function() { return tdata; };
+    def.marks[1].from = function() { return data; };
+    def.marks[2].from = def.marks[1].from;
+
+    return def;
+  }
+
+  function o_legend_def(size, shape, fill, stroke) {
+    // setup legend marks
+    var titles  = dl.extend(m.titles, vg_legendTitle(config)),
+        symbols = dl.extend(m.symbols, vg_legendSymbols(config)),
+        labels  = dl.extend(m.labels, vg_vLegendLabels(config));
+
+    // extend legend marks
+    vg_legendSymbolExtend(symbols, size, shape, fill, stroke);
+    
+    // add / override custom style properties
+    dl.extend(titles.properties.update,  titleStyle);
+    dl.extend(symbols.properties.update, symbolStyle);
+    dl.extend(labels.properties.update,  labelStyle);
+
+    // padding from legend border
+    titles.properties.enter.x.value += padding;
+    titles.properties.enter.y.value += padding;
+    labels.properties.enter.x.offset += padding + 1;
+    symbols.properties.enter.x.offset = padding + 1;
+    labels.properties.update.x.offset += padding + 1;
+    symbols.properties.update.x.offset = padding + 1;
+
+    dl.extend(legendDef, {
+      type: 'group',
+      interactive: false,
+      properties: {
+        enter: parseProperties(model, 'group', legendStyle),
+        vg_legendPosition: {
+          encode: vg_legendPosition,
+          signals: [], scales:[], data: [], fields: []
+        }
+      }
+    });
+
+    legendDef.marks = [titles, symbols, labels].map(function(m) { return parseMark(model, m); });
+    return legendDef;
+  }
+
+  function quantDef(scale) {
+    var def = q_legend_def(scale),
+        dom = scale.domain(),
+        data = (values == null ?
+          (scale.ticks ? scale.ticks.apply(scale, tickArguments) : scale.domain()) :
+          values).map(ingest),
+        width = (gradientStyle.width && gradientStyle.width.value) || config.legend.gradientWidth,
+        fmt = format==null ? (scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments) : String) : format;
+
+    // build scale for label layout
+    var layoutSpec = {
+      name: 'legend',
+      type: scale.type,
+      round: true,
+      zero: false,
+      domain: [dom[0], dom[dom.length-1]],
+      range: [padding, width+padding]
+    };
+    if (scale.type==='pow') layoutSpec.exponent = scale.exponent();
+    
+    // update legend def
+    var tdata = (title ? [title] : []).map(ingest);
+    data.forEach(function(d,i) {
+      d.label = fmt(d.data);
+      d.align = i==(data.length-1) ? 'right' : i===0 ? 'left' : 'center';
+    });
+    def.scales = [ layoutSpec ];
+    def.marks[0].from = function() { return tdata; };
+    def.marks[1].from = function() { return [1]; };
+    def.marks[2].from = function() { return data; };
+    return def;
+  }
+  
+  function q_legend_def(scale) {
+    // setup legend marks
+    var titles = dl.extend(m.titles, vg_legendTitle(config)),
+        gradient = dl.extend(m.gradient, vg_legendGradient(config)),
+        labels = dl.extend(m.labels, vg_hLegendLabels(config)),
+        grad = new Gradient();
+
+    // setup color gradient
+    var dom = scale.domain(),
+        min = dom[0],
+        max = dom[dom.length-1],
+        f = scale.copy().domain([min, max]).range([0,1]);
+        
+    var stops = (scale.type !== 'linear' && scale.ticks) ?
+      scale.ticks.call(scale, 15) : dom;
+    if (min !== stops[0]) stops.unshift(min);
+    if (max !== stops[stops.length-1]) stops.push(max);
+
+    for (var i=0, n=stops.length; i<n; ++i) {
+      grad.stop(f(stops[i]), scale(stops[i]));
+    }
+    gradient.properties.enter.fill = {value: grad};
+
+    // add / override custom style properties
+    dl.extend(titles.properties.update, titleStyle);
+    dl.extend(gradient.properties.update, gradientStyle);
+    dl.extend(labels.properties.update, labelStyle);
+
+    // account for gradient size
+    var gp = gradient.properties, gh = gradientStyle.height,
+        hh = (gh && gh.value) || gp.enter.height.value;
+    labels.properties.enter.y.value = hh;
+    labels.properties.update.y.value = hh;
+
+    // account for title size as needed
+    if (title) {
+      var tp = titles.properties, fs = titleStyle.fontSize,
+          sz = 4 + ((fs && fs.value) || tp.enter.fontSize.value);
+      gradient.properties.enter.y.value += sz;
+      labels.properties.enter.y.value += sz;
+      gradient.properties.update.y.value += sz;
+      labels.properties.update.y.value += sz;
+    }
+    
+    // padding from legend border
+    titles.properties.enter.x.value += padding;
+    titles.properties.enter.y.value += padding;
+    gradient.properties.enter.x.value += padding;
+    gradient.properties.enter.y.value += padding;
+    labels.properties.enter.y.value += padding;
+    gradient.properties.update.x.value += padding;
+    gradient.properties.update.y.value += padding;
+    labels.properties.update.y.value += padding;
+
+    dl.extend(legendDef, {
+      type: 'group',
+      interactive: false,
+      properties: {
+        enter: parseProperties(model, 'group', legendStyle),
+        vg_legendPosition: {
+          encode: vg_legendPosition,
+          signals: [], scales: [], data: [], fields: []
+        }
+      }
+    });
+
+    legendDef.marks = [titles, gradient, labels].map(function(m) { return parseMark(model, m); });
+    return legendDef;
+  }
+
+  legend.size = function(x) {
+    if (!arguments.length) return size;
+    if (size !== x) { size = x; reset(); }
+    return legend;
+  };
+
+  legend.shape = function(x) {
+    if (!arguments.length) return shape;
+    if (shape !== x) { shape = x; reset(); }
+    return legend;
+  };
+
+  legend.fill = function(x) {
+    if (!arguments.length) return fill;
+    if (fill !== x) { fill = x; reset(); }
+    return legend;
+  };
+  
+  legend.stroke = function(x) {
+    if (!arguments.length) return stroke;
+    if (stroke !== x) { stroke = x; reset(); }
+    return legend;
+  };
+
+  legend.title = function(x) {
+    if (!arguments.length) return title;
+    if (title !== x) { title = x; reset(); }
+    return legend;
+  };
+
+  legend.format = function(x) {
+    if (!arguments.length) return formatString;
+    if (formatString !== x) {
+      formatString = x;
+      reset();
+    }
+    return legend;
+  };
+
+  legend.spacing = function(x) {
+    if (!arguments.length) return spacing;
+    if (spacing !== +x) { spacing = +x; reset(); }
+    return legend;
+  };
+
+  legend.orient = function(x) {
+    if (!arguments.length) return orient;
+    orient = x in vg_legendOrients ? x + '' : config.legend.orient;
+    return legend;
+  };
+
+  legend.offset = function(x) {
+    if (!arguments.length) return offset;
+    offset = +x;
+    return legend;
+  };
+
+  legend.values = function(x) {
+    if (!arguments.length) return values;
+    values = x;
+    return legend;
+  };
+
+  legend.legendProperties = function(x) {
+    if (!arguments.length) return legendStyle;
+    legendStyle = x;
+    return legend;
+  };
+
+  legend.symbolProperties = function(x) {
+    if (!arguments.length) return symbolStyle;
+    symbolStyle = x;
+    return legend;
+  };
+
+  legend.gradientProperties = function(x) {
+    if (!arguments.length) return gradientStyle;
+    gradientStyle = x;
+    return legend;
+  };
+
+  legend.labelProperties = function(x) {
+    if (!arguments.length) return labelStyle;
+    labelStyle = x;
+    return legend;
+  };
+  
+  legend.titleProperties = function(x) {
+    if (!arguments.length) return titleStyle;
+    titleStyle = x;
+    return legend;
+  };
+
+  legend.reset = function() { 
+    reset(); 
+    return legend;
+  };
+
+  return legend;
+}
+
+var vg_legendOrients = {right: 1, left: 1};
+
+function vg_legendPosition(item, group, trans, db, signals, predicates) {
+  var o = trans ? {} : item, gx,
+      offset = item.mark.def.offset,
+      orient = item.mark.def.orient,
+      pad    = item.mark.def.padding * 2,
+      lw     = ~~item.bounds.width() + (item.width ? 0 : pad),
+      lh     = ~~item.bounds.height() + (item.height ? 0 : pad),
+      pos = group._legendPositions || 
+        (group._legendPositions = {right: 0.5, left: 0.5});
+
+  o.x = 0.5;
+  o.width = lw;
+  o.y = pos[orient];
+  pos[orient] += (o.height = lh);
+
+  // HACK: use to estimate group bounds during animated transition
+  if (!trans && group.bounds) {
+    group.bounds.delta = group.bounds.x2 - group.width;
+  }
+
+  switch (orient) {
+    case 'left':  {
+      gx = group.bounds ? group.bounds.x1 : 0;
+      o.x += gx - offset - lw;
+      break;
+    }
+    case 'right': {
+      gx = group.width + (group.bounds && trans ? group.bounds.delta : 0);
+      o.x += gx + offset;
+      break;
+    }
+  }
+  
+  if (trans) trans.interpolate(item, o);
+  var enc = item.mark.def.properties.enter.encode;
+  enc.call(enc, item, group, trans, db, signals, predicates);
+  return true;
+}
+
+function vg_legendSymbolExtend(mark, size, shape, fill, stroke) {
+  var e = mark.properties.enter,
+      u = mark.properties.update;
+  if (size)   e.size   = u.size   = {scale: size.scaleName,   field: 'data'};
+  if (shape)  e.shape  = u.shape  = {scale: shape.scaleName,  field: 'data'};
+  if (fill)   e.fill   = u.fill   = {scale: fill.scaleName,   field: 'data'};
+  if (stroke) e.stroke = u.stroke = {scale: stroke.scaleName, field: 'data'};
+}
+
+function vg_legendTitle(config) {
+  var cfg = config.legend;
+  return {
+    type: 'text',
+    interactive: false,
+    key: 'data',
+    properties: {
+      enter: {
+        x: {value: 0},
+        y: {value: 0},
+        fill: {value: cfg.titleColor},
+        font: {value: cfg.titleFont},
+        fontSize: {value: cfg.titleFontSize},
+        fontWeight: {value: cfg.titleFontWeight},
+        baseline: {value: 'top'},
+        text: {field: 'data'},
+        opacity: {value: 1e-6}
+      },
+      exit: { opacity: {value: 1e-6} },
+      update: { opacity: {value: 1} }
+    }
+  };
+}
+
+function vg_legendSymbols(config) {
+  var cfg = config.legend;
+  return {
+    type: 'symbol',
+    interactive: false,
+    key: 'data',
+    properties: {
+      enter: {
+        x: {field: 'offset', mult: 0.5},
+        y: {scale: 'legend', field: 'index'},
+        shape: {value: cfg.symbolShape},
+        size: {value: cfg.symbolSize},
+        stroke: {value: cfg.symbolColor},
+        strokeWidth: {value: cfg.symbolStrokeWidth},
+        opacity: {value: 1e-6}
+      },
+      exit: { opacity: {value: 1e-6} },
+      update: {
+        x: {field: 'offset', mult: 0.5},
+        y: {scale: 'legend', field: 'index'},
+        opacity: {value: 1}
+      }
+    }
+  };
+}
+
+function vg_vLegendLabels(config) {
+  var cfg = config.legend;
+  return {
+    type: 'text',
+    interactive: false,
+    key: 'data',
+    properties: {
+      enter: {
+        x: {field: 'offset', offset: 5},
+        y: {scale: 'legend', field: 'index'},
+        fill: {value: cfg.labelColor},
+        font: {value: cfg.labelFont},
+        fontSize: {value: cfg.labelFontSize},
+        align: {value: cfg.labelAlign},
+        baseline: {value: cfg.labelBaseline},
+        text: {field: 'label'},
+        opacity: {value: 1e-6}
+      },
+      exit: { opacity: {value: 1e-6} },
+      update: {
+        opacity: {value: 1},
+        x: {field: 'offset', offset: 5},
+        y: {scale: 'legend', field: 'index'},
+      }
+    }
+  };
+}
+
+function vg_legendGradient(config) {
+  var cfg = config.legend;
+  return {
+    type: 'rect',
+    interactive: false,
+    properties: {
+      enter: {
+        x: {value: 0},
+        y: {value: 0},
+        width: {value: cfg.gradientWidth},
+        height: {value: cfg.gradientHeight},
+        stroke: {value: cfg.gradientStrokeColor},
+        strokeWidth: {value: cfg.gradientStrokeWidth},
+        opacity: {value: 1e-6}
+      },
+      exit: { opacity: {value: 1e-6} },
+      update: {
+        x: {value: 0},
+        y: {value: 0},
+        opacity: {value: 1}
+      }
+    }
+  };
+}
+
+function vg_hLegendLabels(config) {
+  var cfg = config.legend;
+  return {
+    type: 'text',
+    interactive: false,
+    key: 'data',
+    properties: {
+      enter: {
+        x: {scale: 'legend', field: 'data'},
+        y: {value: 20},
+        dy: {value: 2},
+        fill: {value: cfg.labelColor},
+        font: {value: cfg.labelFont},
+        fontSize: {value: cfg.labelFontSize},
+        align: {field: 'align'},
+        baseline: {value: 'top'},
+        text: {field: 'label'},
+        opacity: {value: 1e-6}
+      },
+      exit: { opacity: {value: 1e-6} },
+      update: {
+        x: {scale: 'legend', field: 'data'},
+        y: {value: 20},
+        opacity: {value: 1}
+      }
+    }
+  };
+}
+
+module.exports = lgnd;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"../parse/mark":97,"../parse/properties":102,"datalib":24,"vega-scenegraph":46}],115:[function(require,module,exports){
+module.exports = function visit(node, func) {
+  var i, n, s, m, items;
+  if (func(node)) return true;
+
+  var sets = ['items', 'axisItems', 'legendItems'];
+  for (s=0, m=sets.length; s<m; ++s) {
+    if ((items = node[sets[s]])) {
+      for (i=0, n=items.length; i<n; ++i) {
+        if (visit(items[i], func)) return true;
+      }
+    }
+  }
+};
+},{}],116:[function(require,module,exports){
+var dl = require('datalib'),
+    df = require('vega-dataflow'),
+    log = require('vega-logging'),
+    ChangeSet = df.ChangeSet,
+    Tuple = df.Tuple,
+    Deps = df.Dependencies,
+    Transform = require('./Transform'),
+    Facetor = require('./Facetor');
+
+function Aggregate(graph) {
+  Transform.prototype.init.call(this, graph);
+
+  Transform.addParameters(this, {
+    groupby: {type: 'array<field>'},
+    summarize: {
+      type: 'custom', 
+      set: function(summarize) {
+        var signalDeps = {},
+            tx = this._transform,
+            i, len, f, fields, name, ops;
+
+        if (!dl.isArray(fields = summarize)) { // Object syntax from dl
+          fields = [];
+          for (name in summarize) {
+            ops = dl.array(summarize[name]);
+            fields.push({field: name, ops: ops});
+          }
+        }
+
+        function sg(x) { if (x.signal) signalDeps[x.signal] = 1; }
+
+        for (i=0, len=fields.length; i<len; ++i) {
+          f = fields[i];
+          if (f.field.signal) { signalDeps[f.field.signal] = 1; }
+          dl.array(f.ops).forEach(sg);
+          dl.array(f.as).forEach(sg);
+        }
+
+        tx._fields = fields;
+        tx._aggr = null;
+        tx.dependency(Deps.SIGNALS, dl.keys(signalDeps));
+        return tx;
+      }
+    }
+  });
+
+  this._aggr  = null; // dl.Aggregator
+  this._input = null; // Used by Facetor._on_keep.
+  this._args  = null; // To cull re-computation.
+  this._fields = [];
+  this._out = [];
+
+  this._type = TYPES.TUPLE; 
+  this._acc = {groupby: dl.true, value: dl.true};
+
+  return this.router(true).produces(true);
+}
+
+var prototype = (Aggregate.prototype = Object.create(Transform.prototype));
+prototype.constructor = Aggregate;
+
+var TYPES = Aggregate.TYPES = {
+  VALUE: 1, 
+  TUPLE: 2, 
+  MULTI: 3
+};
+
+Aggregate.VALID_OPS = [
+  'values', 'count', 'valid', 'missing', 'distinct', 
+  'sum', 'mean', 'average', 'variance', 'variancep', 'stdev', 
+  'stdevp', 'median', 'q1', 'q3', 'modeskew', 'min', 'max', 
+  'argmin', 'argmax'
+];
+
+prototype.type = function(type) { 
+  return (this._type = type, this); 
+};
+
+prototype.accessors = function(groupby, value) {
+  var acc = this._acc;
+  acc.groupby = dl.$(groupby) || dl.true;
+  acc.value = dl.$(value) || dl.true;
+};
+
+prototype.aggr = function() {
+  if (this._aggr) return this._aggr;
+
+  var g = this._graph,
+      hasGetter = false,
+      args = [],
+      groupby = this.param('groupby').field,
+      value = function(x) { return x.signal ? g.signalRef(x.signal) : x; };
+
+  // Prepare summarize fields.
+  var fields = this._fields.map(function(f) {
+    var field = {
+      name: value(f.field),
+      as:   dl.array(f.as),
+      ops:  dl.array(value(f.ops)).map(value),
+      get:  f.get
+    };
+    hasGetter = hasGetter || field.get != null;
+    args.push(field.name);
+    return field;
+  });
+
+  // If there is an arbitrary getter, all bets are off.
+  // Otherwise, we can check argument fields to cull re-computation.
+  groupby.forEach(function(g) {
+    if (g.get) hasGetter = true;
+    args.push(g.name || g);
+  });
+  this._args = hasGetter || !fields.length ? null : args;
+
+  if (!fields.length) fields = {'*': 'values'};
+
+  // Instatiate our aggregator instance.
+  // Facetor is a special subclass that can facet into data pipelines.
+  var aggr = this._aggr = new Facetor()
+    .groupby(groupby)
+    .stream(true)
+    .summarize(fields);
+
+  // Collect output fields sets by this aggregate.
+  this._out = getFields(aggr);
+
+  // If we are processing tuples, key them by '_id'.
+  if (this._type !== TYPES.VALUE) { aggr.key('_id'); }
+
+  return aggr;
+};
+
+function getFields(aggr) {
+  // Collect the output fields set by this aggregate.
+  var f = [], i, n, j, m, dims, vals, meas;
+
+  dims = aggr._dims;
+  for (i=0, n=dims.length; i<n; ++i) {
+    f.push(dims[i].name);
+  }
+
+  vals = aggr._aggr;
+  for (i=0, n=vals.length; i<n; ++i) {
+    meas = vals[i].measures.fields;
+    for (j=0, m=meas.length; j<m; ++j) {
+      f.push(meas[j]);
+    }
+  }
+
+  return f;
+}
+
+prototype.transform = function(input, reset) {
+  log.debug(input, ['aggregate']);
+  this._input = input; // Used by Facetor._on_keep.
+
+  var output = ChangeSet.create(input),
+      aggr = this.aggr(),
+      out = this._out,
+      args = this._args,
+      reeval = true,
+      p = Tuple.prev,
+      add, rem, mod, i;
+
+  // Upon reset, retract prior tuples and re-initialize.
+  if (reset) {
+    output.rem.push.apply(output.rem, aggr.result());
+    aggr.clear();
+    this._aggr = null;
+    aggr = this.aggr();
+  }
+
+  // Get update methods according to input type.
+  if (this._type === TYPES.TUPLE) {
+    add = function(x) { aggr._add(x); Tuple.prev_init(x); };
+    rem = function(x) { aggr._rem(p(x)); };
+    mod = function(x) { aggr._mod(x, p(x)); };
+  } else {
+    var gby = this._acc.groupby,
+        val = this._acc.value,
+        get = this._type === TYPES.VALUE ? val : function(x) {
+          return { _id: x._id, groupby: gby(x), value: val(x) };
+        };
+    add = function(x) { aggr._add(get(x)); Tuple.prev_init(x); };
+    rem = function(x) { aggr._rem(get(p(x))); };
+    mod = function(x) { aggr._mod(get(x), get(p(x))); };
+  }
+
+  input.add.forEach(add);
+  if (reset) {
+    // A signal change triggered reflow. Add everything.
+    // No need for rem, we cleared the aggregator.
+    input.mod.forEach(add);
+  } else {
+    input.rem.forEach(rem);
+
+    // If possible, check argument fields to see if we need to re-process mods.
+    if (args) for (i=0, reeval=false; i<args.length; ++i) {
+      if (input.fields[args[i]]) { reeval = true; break; }
+    }
+    if (reeval) input.mod.forEach(mod);
+  }
+
+  // Indicate output fields and return aggregate tuples.
+  for (i=0; i<out.length; ++i) {
+    output.fields[out[i]] = 1;
+  }
+  return aggr.changes(output);
+};
+
+module.exports = Aggregate;
+},{"./Facetor":122,"./Transform":135,"datalib":24,"vega-dataflow":39,"vega-logging":45}],117:[function(require,module,exports){
+var Base = require('./Transform').prototype;
+
+function BatchTransform() {
+  // Nearest appropriate collector. 
+  // Set by the dataflow Graph during connection.
+  this._collector = null; 
+}
+
+var prototype = (BatchTransform.prototype = Object.create(Base));
+prototype.constructor = BatchTransform;
+
+prototype.init = function(graph) {
+  Base.init.call(this, graph);
+  return this.batch(true);
+};
+
+prototype.transform = function(input) {
+  return this.batchTransform(input, this._collector.data());
+};
+
+prototype.batchTransform = function(/* input, data */) {
+};
+
+module.exports = BatchTransform;
+},{"./Transform":135}],118:[function(require,module,exports){
+var bins = require('datalib').bins,
+    Tuple = require('vega-dataflow').Tuple,
+    log = require('vega-logging'),
+    Transform = require('./Transform');
+
+function Bin(graph) {
+  Transform.prototype.init.call(this, graph);
+  Transform.addParameters(this, {
+    field: {type: 'field'},
+    min: {type: 'value'},
+    max: {type: 'value'},
+    base: {type: 'value', default: 10},
+    maxbins: {type: 'value', default: 20},
+    step: {type: 'value'},
+    steps: {type: 'value'},
+    minstep: {type: 'value'},
+    div: {type: 'array<value>', default: [5, 2]}
+  });
+
+  this._output = {bin: 'bin'};
+  return this.mutates(true);
+}
+
+var prototype = (Bin.prototype = Object.create(Transform.prototype));
+prototype.constructor = Bin;
+
+prototype.transform = function(input) {
+  log.debug(input, ['binning']);
+
+  var output  = this._output.bin,
+      step    = this.param('step'),
+      steps   = this.param('steps'),
+      minstep = this.param('minstep'),
+      get     = this.param('field').accessor,
+      opt = {
+        min: this.param('min'),
+        max: this.param('max'),
+        base: this.param('base'),
+        maxbins: this.param('maxbins'),
+        div: this.param('div')
+      };
+
+  if (step) opt.step = step;
+  if (steps) opt.steps = steps;
+  if (minstep) opt.minstep = minstep;
+  var b = bins(opt);
+
+  function update(d) {
+    var v = get(d);
+    v = v == null ? null
+      : b.start + b.step * ~~((v - b.start) / b.step);
+    Tuple.set(d, output, v);
+  }
+  input.add.forEach(update);
+  input.mod.forEach(update);
+  input.rem.forEach(update);
+
+  input.fields[output] = 1;
+  return input;
+};
+
+module.exports = Bin;
+},{"./Transform":135,"datalib":24,"vega-dataflow":39,"vega-logging":45}],119:[function(require,module,exports){
+var df = require('vega-dataflow'),
+    Tuple = df.Tuple,
+    log = require('vega-logging'),
+    Transform = require('./Transform');
+
+function CountPattern(graph) {
+  Transform.prototype.init.call(this, graph);
+  Transform.addParameters(this, {
+    field:     {type: 'field', default: 'data'},
+    pattern:   {type: 'value', default: '[\\w\']+'},
+    case:      {type: 'value', default: 'lower'},
+    stopwords: {type: 'value', default: ''}
+  });
+
+  this._output = {text: 'text', count: 'count'};
+
+  return this.router(true).produces(true);
+}
+
+var prototype = (CountPattern.prototype = Object.create(Transform.prototype));
+prototype.constructor = CountPattern;
+
+prototype.transform = function(input, reset) {
+  log.debug(input, ['countpattern']);
+
+  var get = this.param('field').accessor,
+      pattern = this.param('pattern'),
+      stop = this.param('stopwords'),
+      rem = false;
+
+  // update parameters
+  if (this._stop !== stop) {
+    this._stop = stop;
+    this._stop_re = new RegExp('^' + stop + '$', 'i');
+    reset = true;
+  }
+
+  if (this._pattern !== pattern) {
+    this._pattern = pattern;
+    this._match = new RegExp(this._pattern, 'g');
+    reset = true;
+  }
+
+  if (reset) this._counts = {};
+
+  function curr(t) { return (Tuple.prev_init(t), get(t)); }
+  function prev(t) { return get(Tuple.prev(t)); }
+
+  this._add(input.add, curr);
+  if (!reset) this._rem(input.rem, prev);
+  if (reset || (rem = input.fields[get.field])) {
+    if (rem) this._rem(input.mod, prev);
+    this._add(input.mod, curr);
+  }
+
+  // generate output tuples
+  return this._changeset(input);
+};
+
+prototype._changeset = function(input) {
+  var counts = this._counts,
+      tuples = this._tuples || (this._tuples = {}),
+      change = df.ChangeSet.create(input),
+      out = this._output, w, t, c;
+
+  for (w in counts) {
+    t = tuples[w];
+    c = counts[w] || 0;
+    if (!t && c) {
+      tuples[w] = (t = Tuple.ingest({}));
+      t[out.text] = w;
+      t[out.count] = c;
+      change.add.push(t);
+    } else if (c === 0) {
+      if (t) change.rem.push(t);
+      delete counts[w];
+      delete tuples[w];
+    } else if (t[out.count] !== c) {
+      Tuple.set(t, out.count, c);
+      change.mod.push(t);
+    }
+  }
+  return change;
+};
+
+prototype._tokenize = function(text) {
+  switch (this.param('case')) {
+    case 'upper': text = text.toUpperCase(); break;
+    case 'lower': text = text.toLowerCase(); break;
+  }
+  return text.match(this._match);
+};
+
+prototype._add = function(tuples, get) {
+  var counts = this._counts,
+      stop = this._stop_re,
+      tok, i, j, t;
+
+  for (j=0; j<tuples.length; ++j) {
+    tok = this._tokenize(get(tuples[j]));
+    for (i=0; i<tok.length; ++i) {
+      if (!stop.test(t=tok[i])) {
+        counts[t] = 1 + (counts[t] || 0);
+      }
+    }
+  }
+};
+
+prototype._rem = function(tuples, get) {
+  var counts = this._counts,
+      stop = this._stop_re,
+      tok, i, j, t;
+
+  for (j=0; j<tuples.length; ++j) {
+    tok = this._tokenize(get(tuples[j]));
+    for (i=0; i<tok.length; ++i) {
+      if (!stop.test(t=tok[i])) {
+        counts[t] -= 1;
+      }
+    }
+  }
+};
+
+module.exports = CountPattern;
+},{"./Transform":135,"vega-dataflow":39,"vega-logging":45}],120:[function(require,module,exports){
+var df = require('vega-dataflow'),
+    ChangeSet = df.ChangeSet,
+    Tuple = df.Tuple,
+    SIGNALS = df.Dependencies.SIGNALS,
+    log = require('vega-logging'),
+    Transform = require('./Transform'),
+    BatchTransform = require('./BatchTransform');
+
+function Cross(graph) {
+  BatchTransform.prototype.init.call(this, graph);
+  Transform.addParameters(this, {
+    with: {type: 'data'},
+    diagonal: {type: 'value', default: 'true'},
+    filter: {type: 'expr'}
+  });
+
+  this._output = {'left': 'a', 'right': 'b'};
+  this._lastRem  = null; // Most recent stamp that rem occured. 
+  this._lastWith = null; // Last time we crossed w/withds.
+  this._ids   = {};
+  this._cache = {};
+
+  return this.router(true).produces(true);
+}
+
+var prototype = (Cross.prototype = Object.create(BatchTransform.prototype));
+prototype.constructor = Cross;
+
+// Each cached incoming tuple also has a stamp to track if we need to do
+// lazy filtering of removed tuples.
+function cache(x, t) {
+  var c = this._cache[x._id] = this._cache[x._id] || {c: [], s: this._stamp};
+  c.c.push(t);
+}
+
+function add(output, left, data, diag, test, x) {
+  var i = 0, len = data.length, t = {}, y, id;
+
+  for (; i<len; ++i) {
+    y = data[i];
+    id = left ? x._id+'_'+y._id : y._id+'_'+x._id;
+    if (this._ids[id]) continue;
+    if (x._id == y._id && !diag) continue;
+
+    t[this._output.left]  = left ? x : y;
+    t[this._output.right] = left ? y : x;
+
+    // Only ingest a tuple if we keep it around.
+    if (!test || test(t)) {
+      output.add.push(t=Tuple.ingest(t));
+      cache.call(this, x, t);
+      cache.call(this, y, t);
+      this._ids[id] = 1;
+      t = {};
+    }    
+  }
+}
+
+function mod(output, left, x) {
+  var cross = this,
+      c = this._cache[x._id];
+
+  if (this._lastRem > c.s) {  // Removed tuples haven't been filtered yet
+    c.c = c.c.filter(function(y) {
+      var t = y[cross._output[left ? 'right' : 'left']];
+      return cross._cache[t._id] !== null;
+    });
+    c.s = this._lastRem;
+  }
+
+  output.mod.push.apply(output.mod, c.c);
+}
+
+function rem(output, x) {
+  output.rem.push.apply(output.rem, this._cache[x._id].c);
+  this._cache[x._id] = null;
+  this._lastRem = this._stamp;
+}
+
+function upFields(input, output) {
+  if (input.add.length || input.rem.length) {
+    output.fields[this._output.left]  = 1; 
+    output.fields[this._output.right] = 1;
+  }
+}
+
+prototype.batchTransform = function(input, data) {
+  log.debug(input, ['crossing']);
+
+  var w = this.param('with'),
+      f = this.param('filter'),
+      diag = this.param('diagonal'),
+      graph = this._graph,
+      signals = graph.values(SIGNALS, this.dependency(SIGNALS)),
+      test = f ? function(x) {return f(x, null, signals); } : null,
+      selfCross = (!w.name),
+      woutput = selfCross ? input : w.source.last(),
+      wdata   = selfCross ? data : w.source.values(),
+      output  = ChangeSet.create(input),
+      r = rem.bind(this, output);
+
+  input.rem.forEach(r);
+  input.add.forEach(add.bind(this, output, true, wdata, diag, test));
+
+  if (!selfCross && woutput.stamp > this._lastWith) {
+    woutput.rem.forEach(r);
+    woutput.add.forEach(add.bind(this, output, false, data, diag, test));
+    woutput.mod.forEach(mod.bind(this, output, false));
+    upFields.call(this, woutput, output);
+    this._lastWith = woutput.stamp;
+  }
+
+  // Mods need to come after all removals have been run.
+  input.mod.forEach(mod.bind(this, output, true));
+  upFields.call(this, input, output);
+
+  return output;
+};
+
+module.exports = Cross;
+},{"./BatchTransform":117,"./Transform":135,"vega-dataflow":39,"vega-logging":45}],121:[function(require,module,exports){
+var Transform = require('./Transform'),
+    Aggregate = require('./Aggregate');
+
+function Facet(graph) {
+  Transform.addParameters(this, {
+    transform: {
+      type: "custom",
+      set: function(pipeline) {
+        return (this._transform._pipeline = pipeline, this._transform);
+      },
+      get: function() {
+        var parse = require('../parse/transforms'),
+            facet = this._transform;
+        return facet._pipeline.map(function(t) {
+          return parse(facet._graph, t);
+        });
+      }      
+    }
+  });
+
+  this._pipeline = [];
+  return Aggregate.call(this, graph);
+}
+
+var prototype = (Facet.prototype = Object.create(Aggregate.prototype));
+prototype.constructor = Facet;
+
+prototype.aggr = function() {
+  return Aggregate.prototype.aggr.call(this).facet(this);
+};
+
+module.exports = Facet;
+},{"../parse/transforms":106,"./Aggregate":116,"./Transform":135}],122:[function(require,module,exports){
+var dl = require('datalib'),
+    Aggregator = dl.Aggregator,
+    Base = Aggregator.prototype,
+    df = require('vega-dataflow'),
+    Tuple = df.Tuple,
+    log = require('vega-logging'),
+    facetID = 0;
+
+function Facetor() {
+  Aggregator.call(this);
+  this._facet = null;
+  this._facetID = ++facetID;
+}
+
+var prototype = (Facetor.prototype = Object.create(Base));
+prototype.constructor = Facetor;
+
+prototype.facet = function(f) {
+  return arguments.length ? (this._facet = f, this) : this._facet;
+};
+
+prototype._ingest = function(t) { 
+  return Tuple.ingest(t, null);
+};
+
+prototype._assign = Tuple.set;
+
+function disconnect_cell(facet) {
+  log.debug({}, ['disconnecting cell', this.tuple._id]);
+  var pipeline = this.ds.pipeline();
+  facet.removeListener(pipeline[0]);
+  facet._graph.removeListener(pipeline[0]);
+  facet._graph.disconnect(pipeline);
+}
+
+prototype._newcell = function(x, key) {
+  var cell  = Base._newcell.call(this, x, key),
+      facet = this._facet;
+
+  if (facet) {
+    var graph = facet._graph,
+        tuple = cell.tuple,
+        pipeline = facet.param('transform');
+    cell.ds = graph.data(tuple._facetID, pipeline, tuple);
+    cell.disconnect = disconnect_cell;
+    facet.addListener(pipeline[0]);
+  }
+
+  return cell;
+};
+
+prototype._newtuple = function(x, key) {
+  var t = Base._newtuple.call(this, x);
+  if (this._facet) {
+    Tuple.set(t, 'key', key);
+    Tuple.set(t, '_facetID', this._facetID + '_' + key);
+  }
+  return t;
+};
+
+prototype.clear = function() {
+  if (this._facet) {
+    for (var k in this._cells) {
+      this._cells[k].disconnect(this._facet);
+    }
+  }
+  return Base.clear.call(this);
+};
+
+prototype._on_add = function(x, cell) {
+  if (this._facet) cell.ds._input.add.push(x);
+};
+
+prototype._on_rem = function(x, cell) {
+  if (this._facet) cell.ds._input.rem.push(x);
+};
+
+prototype._on_mod = function(x, prev, cell0, cell1) {
+  if (this._facet) { // Propagate tuples
+    if (cell0 === cell1) {
+      cell0.ds._input.mod.push(x);
+    } else {
+      cell0.ds._input.rem.push(x);
+      cell1.ds._input.add.push(x);
+    }
+  }
+};
+
+prototype._on_drop = function(cell) {
+  if (this._facet) cell.disconnect(this._facet);
+};
+
+prototype._on_keep = function(cell) {
+  // propagate sort, signals, fields, etc.
+  if (this._facet) df.ChangeSet.copy(this._input, cell.ds._input);
+};
+
+module.exports = Facetor;
+},{"datalib":24,"vega-dataflow":39,"vega-logging":45}],123:[function(require,module,exports){
+var df = require('vega-dataflow'),
+    SIGNALS = df.Dependencies.SIGNALS,
+    log = require('vega-logging'),
+    Transform = require('./Transform');
+
+function Filter(graph) {
+  Transform.prototype.init.call(this, graph);
+  Transform.addParameters(this, {test: {type: 'expr'}});
+
+  this._skip = {};
+  return this.router(true);
+}
+
+var prototype = (Filter.prototype = Object.create(Transform.prototype));
+prototype.constructor = Filter;
+
+prototype.transform = function(input) {
+  log.debug(input, ['filtering']);
+
+  var output = df.ChangeSet.create(input),
+      graph = this._graph,
+      skip = this._skip,
+      test = this.param('test'),
+      signals = graph.values(SIGNALS, this.dependency(SIGNALS));
+
+  input.rem.forEach(function(x) {
+    if (skip[x._id] !== 1) output.rem.push(x);
+    else skip[x._id] = 0;
+  });
+
+  input.add.forEach(function(x) {
+    if (test(x, null, signals)) output.add.push(x);
+    else skip[x._id] = 1;
+  });
+
+  input.mod.forEach(function(x) {
+    var b = test(x, null, signals),
+        s = (skip[x._id] === 1);
+    if (b && s) {
+      skip[x._id] = 0;
+      output.add.push(x);
+    } else if (b && !s) {
+      output.mod.push(x);
+    } else if (!b && s) {
+      // do nothing, keep skip true
+    } else { // !b && !s
+      output.rem.push(x);
+      skip[x._id] = 1;
+    }
+  });
+
+  return output;
+};
+
+module.exports = Filter;
+},{"./Transform":135,"vega-dataflow":39,"vega-logging":45}],124:[function(require,module,exports){
+var df = require('vega-dataflow'),
+    Tuple = df.Tuple,
+    log = require('vega-logging'),
+    Transform = require('./Transform');
+
+function Fold(graph) {
+  Transform.prototype.init.call(this, graph);
+  Transform.addParameters(this, {
+    fields: {type: 'array<field>'} 
+  });
+
+  this._output = {key: 'key', value: 'value'};
+  this._cache = {};
+
+  return this.router(true).produces(true);
+}
+
+var prototype = (Fold.prototype = Object.create(Transform.prototype));
+prototype.constructor = Fold;
+
+prototype._reset = function(input, output) { 
+  for (var id in this._cache) {
+    output.rem.push.apply(output.rem, this._cache[id]);
+  }
+  this._cache = {};
+};
+
+prototype._tuple = function(x, i, len) {
+  var list = this._cache[x._id] || (this._cache[x._id] = Array(len));
+  return list[i] ? Tuple.rederive(x, list[i]) : (list[i] = Tuple.derive(x));
+};
+
+prototype._fn = function(data, on, out) {
+  var i, j, n, m, d, t;
+  for (i=0, n=data.length; i<n; ++i) {
+    d = data[i];
+    for (j=0, m=on.field.length; j<m; ++j) {
+      t = this._tuple(d, j, m);  
+      Tuple.set(t, this._output.key, on.field[j]);
+      Tuple.set(t, this._output.value, on.accessor[j](d));
+      out.push(t);
+    }      
+  }
+};
+
+prototype.transform = function(input, reset) {
+  log.debug(input, ['folding']);
+
+  var fold = this,
+      on = this.param('fields'),
+      output = df.ChangeSet.create(input);
+
+  if (reset) this._reset(input, output);
+
+  this._fn(input.add, on, output.add);
+  this._fn(input.mod, on, reset ? output.add : output.mod);
+  input.rem.forEach(function(x) {
+    output.rem.push.apply(output.rem, fold._cache[x._id]);
+    fold._cache[x._id] = null;
+  });
+
+  // If we're only propagating values, don't mark key/value as updated.
+  if (input.add.length || input.rem.length || 
+      on.field.some(function(f) { return !!input.fields[f]; })) {
+    output.fields[this._output.key] = 1;
+    output.fields[this._output.value] = 1;
+  }
+  return output;
+};
+
+module.exports = Fold;
+},{"./Transform":135,"vega-dataflow":39,"vega-logging":45}],125:[function(require,module,exports){
+(function (global){
+var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null),
+    df = require('vega-dataflow'),
+    Tuple = df.Tuple,
+    ChangeSet = df.ChangeSet,
+    log = require('vega-logging'),
+    Transform = require('./Transform');
+
+function Force(graph) {
+  Transform.prototype.init.call(this, graph);
+
+  this._prev = null;
+  this._interactive = false;
+  this._setup = true;
+  this._nodes  = [];
+  this._links = [];
+  this._layout = d3.layout.force();
+
+  Transform.addParameters(this, {
+    size: {type: 'array<value>', default: [500, 500]},
+    bound: {type: 'value', default: true},
+    links: {type: 'data'},
+
+    // TODO: for now force these to be value params only (pun-intended)
+    // Can update to include fields after Parameter refactoring.
+    linkStrength: {type: 'value', default: 1},
+    linkDistance: {type: 'value', default: 20},
+    charge: {type: 'value', default: -30},
+
+    chargeDistance: {type: 'value', default: Infinity},
+    friction: {type: 'value', default: 0.9},
+    theta: {type: 'value', default: 0.8},
+    gravity: {type: 'value', default: 0.1},
+    alpha: {type: 'value', default: 0.1},
+    iterations: {type: 'value', default: 500},
+
+    interactive: {type: 'value', default: this._interactive},    
+    active: {type: 'value', default: this._prev},
+    fixed: {type: 'data'}
+  });
+
+  this._output = {
+    'x': 'layout_x',
+    'y': 'layout_y'
+  };
+
+  return this.mutates(true);
+}
+
+var prototype = (Force.prototype = Object.create(Transform.prototype));
+prototype.constructor = Force;
+
+prototype.transform = function(nodeInput, reset) {
+  log.debug(nodeInput, ['force']);
+  reset = reset - (nodeInput.signals.active ? 1 : 0);
+
+  // get variables
+  var interactive = this.param('interactive'),
+      linkSource = this.param('links').source,
+      linkInput = linkSource.last(),
+      active = this.param('active'),
+      output = this._output,
+      layout = this._layout,
+      nodes = this._nodes,
+      links = this._links;
+
+  // configure nodes, links and layout
+  if (linkInput.stamp < nodeInput.stamp) linkInput = null;
+  this.configure(nodeInput, linkInput, interactive, reset);
+  
+  // run batch layout
+  if (!interactive) {
+    var iterations = this.param('iterations');
+    for (var i=0; i<iterations; ++i) layout.tick();
+    layout.stop();
+  }
+
+  // update node positions
+  this.update(active);
+
+  // re-up alpha on parameter change
+  if (reset || active !== this._prev && active && active.update) {
+    layout.alpha(this.param('alpha')); // re-start layout
+  }
+
+  // update active node status, 
+  if (active !== this._prev) {
+    this._prev = active;
+  }
+
+  // process removed nodes or edges
+  if (nodeInput.rem.length) {
+    layout.nodes(this._nodes = Tuple.idFilter(nodes, nodeInput.rem));
+  }
+  if (linkInput && linkInput.rem.length) {
+    layout.links(this._links = Tuple.idFilter(links, linkInput.rem));
+  }
+
+  // return changeset
+  nodeInput.fields[output.x] = 1;
+  nodeInput.fields[output.y] = 1;
+  return nodeInput;
+};
+
+prototype.configure = function(nodeInput, linkInput, interactive, reset) {
+  // check if we need to run configuration
+  var layout = this._layout,
+      update = this._setup || nodeInput.add.length ||
+            linkInput && linkInput.add.length ||
+            interactive !== this._interactive ||
+            this.param('charge') !== layout.charge() ||
+            this.param('linkStrength') !== layout.linkStrength() ||
+            this.param('linkDistance') !== layout.linkDistance();
+
+  if (update || reset) {
+    // a parameter changed, so update tick-only parameters
+    layout
+      .size(this.param('size'))
+      .chargeDistance(this.param('chargeDistance'))
+      .theta(this.param('theta'))
+      .gravity(this.param('gravity'))
+      .friction(this.param('friction'));
+  }
+
+  if (!update) return; // if no more updates needed, return now
+
+  this._setup = false;
+  this._interactive = interactive;
+
+  var force = this,
+      graph = this._graph,
+      nodes = this._nodes,
+      links = this._links, a, i;
+
+  // process added nodes
+  for (a=nodeInput.add, i=0; i<a.length; ++i) {
+    nodes.push({tuple: a[i]});
+  }
+
+  // process added edges
+  if (linkInput) for (a=linkInput.add, i=0; i<a.length; ++i) {
+    // TODO add configurable source/target accessors
+    // TODO support lookup by node id
+    // TODO process 'mod' of edge source or target?
+    links.push({
+      tuple:  a[i],
+      source: nodes[a[i].source],
+      target: nodes[a[i].target]
+    });
+  }
+
+  // setup handler for force layout tick events
+  var tickHandler = !interactive ? null : function() {
+    // re-schedule the transform, force reflow
+    graph.propagate(ChangeSet.create(null, true), force);
+  };
+
+  // configure the rest of the layout
+  layout
+    .linkStrength(this.param('linkStrength'))
+    .linkDistance(this.param('linkDistance'))
+    .charge(this.param('charge'))
+    .nodes(nodes)
+    .links(links)
+    .on('tick', tickHandler)
+    .start().alpha(this.param('alpha'));
+};
+
+prototype.update = function(active) {
+  var output = this._output,
+      bound = this.param('bound'),
+      fixed = this.param('fixed'),
+      size = this.param('size'),
+      nodes = this._nodes,
+      lut = {}, id, i, n, t, x, y;
+
+  if (fixed && fixed.source) {
+    // TODO: could cache and update as needed?
+    fixed = fixed.source.values();
+    for (i=0, n=fixed.length; i<n; ++i) {
+      lut[fixed[i].id] = 1;
+    }
+  }
+
+  for (i=0; i<nodes.length; ++i) {
+    n = nodes[i];
+    t = n.tuple;
+    id = t._id;
+
+    if (active && active.id === id) {
+      n.fixed = 1;
+      if (active.update) {
+        n.x = n.px = active.x;
+        n.y = n.py = active.y;
+      }
+    } else {
+      n.fixed = lut[id] || 0;
+    }
+
+    x = bound ? Math.max(0, Math.min(n.x, size[0])) : n.x;
+    y = bound ? Math.max(0, Math.min(n.y, size[1])) : n.y;
+    Tuple.set(t, output.x, x);
+    Tuple.set(t, output.y, y);
+  }
+};
+
+module.exports = Force;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"./Transform":135,"vega-dataflow":39,"vega-logging":45}],126:[function(require,module,exports){
+var df = require('vega-dataflow'),
+    Tuple = df.Tuple,
+    SIGNALS = df.Dependencies.SIGNALS,
+    log = require('vega-logging'),
+    Transform = require('./Transform');
+
+function Formula(graph) {
+  Transform.prototype.init.call(this, graph);
+  Transform.addParameters(this, {
+    field: {type: 'value'},
+    expr:  {type: 'expr'}
+  });
+
+  return this.mutates(true);
+}
+
+var prototype = (Formula.prototype = Object.create(Transform.prototype));
+prototype.constructor = Formula;
+
+prototype.transform = function(input) {
+  log.debug(input, ['formulating']);
+
+  var g = this._graph,
+      field = this.param('field'),
+      expr = this.param('expr'),
+      signals = g.values(SIGNALS, this.dependency(SIGNALS));
+
+  function set(x) {
+    Tuple.set(x, field, expr(x, null, signals));
+  }
+
+  input.add.forEach(set);
+  
+  if (this.reevaluate(input)) {
+    input.mod.forEach(set);
+  }
+
+  input.fields[field] = 1;
+  return input;
+};
+
+module.exports = Formula;
+},{"./Transform":135,"vega-dataflow":39,"vega-logging":45}],127:[function(require,module,exports){
+(function (global){
+var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null),
+    dl = require('datalib'),
+    Tuple = require('vega-dataflow').Tuple,
+    log = require('vega-logging'),
+    Transform = require('./Transform');
+
+function Geo(graph) {
+  Transform.prototype.init.call(this, graph);
+  Transform.addParameters(this, Geo.Parameters);
+  Transform.addParameters(this, {
+    lon: {type: 'field'},
+    lat: {type: 'field'}
+  });
+
+  this._output = {
+    'x': 'layout_x',
+    'y': 'layout_y'
+  };
+  return this.mutates(true);
+}
+
+Geo.Parameters = {
+  projection: {type: 'value', default: 'mercator'},
+  center:     {type: 'array<value>'},
+  translate:  {type: 'array<value>'},
+  rotate:     {type: 'array<value>'},
+  scale:      {type: 'value'},
+  precision:  {type: 'value'},
+  clipAngle:  {type: 'value'},
+  clipExtent: {type: 'value'}
+};
+
+Geo.d3Projection = function() {
+  var p = this.param('projection'),
+      param = Geo.Parameters,
+      proj, name, value;
+
+  if (p !== this._mode) {
+    this._mode = p;
+    this._projection = d3.geo[p]();
+  }
+  proj = this._projection;
+
+  for (name in param) {
+    if (name === 'projection' || !proj[name]) continue;
+    value = this.param(name);
+    if (value === undefined || (dl.isArray(value) && value.length === 0)) {
+      continue;
+    }
+    if (value !== proj[name]()) {
+      proj[name](value);
+    }
+  }
+
+  return proj;
+};
+
+var prototype = (Geo.prototype = Object.create(Transform.prototype));
+prototype.constructor = Geo;
+
+prototype.transform = function(input) {
+  log.debug(input, ['geo']);
+
+  var output = this._output,
+      lon = this.param('lon').accessor,
+      lat = this.param('lat').accessor,
+      proj = Geo.d3Projection.call(this);
+
+  function set(t) {
+    var ll = [lon(t), lat(t)];
+    var xy = proj(ll) || [null, null];
+    Tuple.set(t, output.x, xy[0]);
+    Tuple.set(t, output.y, xy[1]);
+  }
+
+  input.add.forEach(set);
+  if (this.reevaluate(input)) {
+    input.mod.forEach(set);
+    input.rem.forEach(set);
+  }
+
+  input.fields[output.x] = 1;
+  input.fields[output.y] = 1;
+  return input;
+};
+
+module.exports = Geo;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"./Transform":135,"datalib":24,"vega-dataflow":39,"vega-logging":45}],128:[function(require,module,exports){
+(function (global){
+var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null),
+    dl = require('datalib'),
+    Tuple = require('vega-dataflow').Tuple,
+    log = require('vega-logging'),
+    Geo = require('./Geo'),
+    Transform = require('./Transform');
+
+function GeoPath(graph) {
+  Transform.prototype.init.call(this, graph);
+  Transform.addParameters(this, Geo.Parameters);
+  Transform.addParameters(this, {
+    field: {type: 'field', default: null},
+  });
+
+  this._output = {
+    'path': 'layout_path'
+  };
+  return this.mutates(true);
+}
+
+var prototype = (GeoPath.prototype = Object.create(Transform.prototype));
+prototype.constructor = GeoPath;
+
+prototype.transform = function(input) {
+  log.debug(input, ['geopath']);
+
+  var output = this._output,
+      geojson = this.param('field').accessor || dl.identity,
+      proj = Geo.d3Projection.call(this),
+      path = d3.geo.path().projection(proj);
+
+  function set(t) {
+    Tuple.set(t, output.path, path(geojson(t)));
+  }
+
+  input.add.forEach(set);
+  if (this.reevaluate(input)) {
+    input.mod.forEach(set);
+    input.rem.forEach(set);
+  }
+
+  input.fields[output.path] = 1;
+  return input;
+};
+
+module.exports = GeoPath;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"./Geo":127,"./Transform":135,"datalib":24,"vega-dataflow":39,"vega-logging":45}],129:[function(require,module,exports){
+var Tuple = require('vega-dataflow').Tuple,
+    log = require('vega-logging'),
+    Transform = require('./Transform');
+
+function LinkPath(graph) {
+  Transform.prototype.init.call(this, graph);
+  Transform.addParameters(this, {
+    sourceX:  {type: 'field', default: '_source.layout_x'},
+    sourceY:  {type: 'field', default: '_source.layout_y'},
+    targetX:  {type: 'field', default: '_target.layout_x'},
+    targetY:  {type: 'field', default: '_target.layout_y'},
+    tension:  {type: 'value', default: 0.2},
+    shape:    {type: 'value', default: 'line'}
+  });
+
+  this._output = {'path': 'layout_path'};
+  return this.mutates(true);
+}
+
+var prototype = (LinkPath.prototype = Object.create(Transform.prototype));
+prototype.constructor = LinkPath;
+
+function line(sx, sy, tx, ty) {
+  return 'M' + sx + ',' + sy +
+         'L' + tx + ',' + ty;
+}
+
+function curve(sx, sy, tx, ty, tension) {
+  var dx = tx - sx,
+      dy = ty - sy,
+      ix = tension * (dx + dy),
+      iy = tension * (dy - dx);
+  return 'M' + sx + ',' + sy +
+         'C' + (sx+ix) + ',' + (sy+iy) +
+         ' ' + (tx+iy) + ',' + (ty-ix) +
+         ' ' + tx + ',' + ty;
+}
+
+function diagonalX(sx, sy, tx, ty) {
+  var m = (sx + tx) / 2;
+  return 'M' + sx + ',' + sy +
+         'C' + m  + ',' + sy +
+         ' ' + m  + ',' + ty +
+         ' ' + tx + ',' + ty;
+}
+
+function diagonalY(sx, sy, tx, ty) {
+  var m = (sy + ty) / 2;
+  return 'M' + sx + ',' + sy +
+         'C' + sx + ',' + m +
+         ' ' + tx + ',' + m +
+         ' ' + tx + ',' + ty;
+}
+
+var shapes = {
+  line:      line,
+  curve:     curve,
+  diagonal:  diagonalX,
+  diagonalX: diagonalX,
+  diagonalY: diagonalY
+};
+
+prototype.transform = function(input) {
+  log.debug(input, ['linkpath']);
+
+  var output = this._output,
+      shape = shapes[this.param('shape')] || shapes.line,
+      sourceX = this.param('sourceX').accessor,
+      sourceY = this.param('sourceY').accessor,
+      targetX = this.param('targetX').accessor,
+      targetY = this.param('targetY').accessor,
+      tension = this.param('tension');
+
+  function set(t) {
+    var path = shape(sourceX(t), sourceY(t), targetX(t), targetY(t), tension);
+    Tuple.set(t, output.path, path);
+  }
+
+  input.add.forEach(set);
+  if (this.reevaluate(input)) {
+    input.mod.forEach(set);
+    input.rem.forEach(set);
+  }
+
+  input.fields[output.path] = 1;
+  return input;
+};
+
+module.exports = LinkPath;
+},{"./Transform":135,"vega-dataflow":39,"vega-logging":45}],130:[function(require,module,exports){
+var Tuple = require('vega-dataflow').Tuple,
+    log = require('vega-logging'),
+    Transform = require('./Transform');
+
+function Lookup(graph) {
+  Transform.prototype.init.call(this, graph);
+  Transform.addParameters(this, {
+    on:      {type: 'data'},
+    onKey:   {type: 'field', default: null},
+    as:      {type: 'array<value>'},
+    keys:    {type: 'array<field>', default: ['data']},
+    default: {type: 'value'}
+  });
+
+  return this.mutates(true);
+}
+
+var prototype = (Lookup.prototype = Object.create(Transform.prototype));
+prototype.constructor = Lookup;
+
+prototype.transform = function(input, reset) {
+  log.debug(input, ['lookup']);
+
+  var on = this.param('on'),
+      onLast = on.source.last(),
+      onData = on.source.values(),
+      onKey = this.param('onKey'),
+      onF = onKey.field,
+      keys = this.param('keys'),
+      get = keys.accessor,
+      as = this.param('as'),
+      defaultValue = this.param('default'),
+      lut = this._lut,
+      i, v;
+
+  // build lookup table on init, withKey modified, or tuple add/rem
+  if (lut == null || this._on !== onF || onF && onLast.fields[onF] ||
+      onLast.add.length || onLast.rem.length)
+  {
+    if (onF) { // build hash from withKey field
+      onKey = onKey.accessor;
+      for (lut={}, i=0; i<onData.length; ++i) {
+        lut[onKey(v = onData[i])] = v;
+      }
+    } else { // otherwise, use index-based lookup
+      lut = onData;
+    }
+    this._lut = lut;
+    this._on = onF;
+    reset = true;
+  }
+
+  function set(t) {
+    for (var i=0; i<get.length; ++i) {
+      var v = lut[get[i](t)] || defaultValue;
+      Tuple.set(t, as[i], v);
+    }
+  }
+
+  input.add.forEach(set);
+  var run = keys.field.some(function(f) { return input.fields[f]; });
+  if (run || reset) {
+    input.mod.forEach(set);
+    input.rem.forEach(set); 
+  }
+
+  as.forEach(function(k) { input.fields[k] = 1; });
+  return input;
+};
+
+module.exports = Lookup;
+},{"./Transform":135,"vega-dataflow":39,"vega-logging":45}],131:[function(require,module,exports){
+var dl = require('datalib'),
+    Deps = require('vega-dataflow').Dependencies,
+    expr = require('../parse/expr');
+
+var arrayType = /array/i,
+    dataType  = /data/i,
+    fieldType = /field/i,
+    exprType  = /expr/i,
+    valType   = /value/i;
+
+function Parameter(name, type, transform) {
+  this._name = name;
+  this._type = type;
+  this._transform = transform;
+
+  // If parameter is defined w/signals, it must be resolved
+  // on every pulse.
+  this._value = [];
+  this._accessors = [];
+  this._resolution = false;
+  this._signals = {};
+}
+
+var prototype = Parameter.prototype;
+
+function get() {
+  var isArray = arrayType.test(this._type),
+      isData  = dataType.test(this._type),
+      isField = fieldType.test(this._type);
+
+  var val = isArray ? this._value : this._value[0],
+      acc = isArray ? this._accessors : this._accessors[0];
+
+  if (!dl.isValid(acc) && valType.test(this._type)) {
+    return val;
+  } else {
+    return isData ? { name: val, source: acc } :
+    isField ? { field: val, accessor: acc } : val;
+  }
+}
+
+prototype.get = function() {
+  var graph = this._transform._graph, 
+      isData  = dataType.test(this._type),
+      isField = fieldType.test(this._type),
+      s, idx, val;
+
+  // If we don't require resolution, return the value immediately.
+  if (!this._resolution) return get.call(this);
+
+  if (isData) {
+    this._accessors = this._value.map(function(v) { return graph.data(v); });
+    return get.call(this); // TODO: support signal as dataTypes
+  }
+
+  for (s in this._signals) {
+    idx = this._signals[s];
+    val = graph.signalRef(s);
+
+    if (isField) {
+      this._accessors[idx] = this._value[idx] != val ? 
+        dl.accessor(val) : this._accessors[idx];
+    }
+
+    this._value[idx] = val;
+  }
+
+  return get.call(this);
+};
+
+prototype.set = function(value) {
+  var p = this,
+      isExpr = exprType.test(this._type),
+      isData  = dataType.test(this._type),
+      isField = fieldType.test(this._type);
+
+  this._value = dl.array(value).map(function(v, i) {
+    if (dl.isString(v)) {
+      if (isExpr) {
+        var e = expr(v);
+        p._transform.dependency(Deps.FIELDS,  e.fields);
+        p._transform.dependency(Deps.SIGNALS, e.globals);
+        return e.fn;
+      } else if (isField) {  // Backwards compatibility
+        p._accessors[i] = dl.accessor(v);
+        p._transform.dependency(Deps.FIELDS, dl.field(v));
+      } else if (isData) {
+        p._resolution = true;
+        p._transform.dependency(Deps.DATA, v);
+      }
+      return v;
+    } else if (v.value !== undefined) {
+      return v.value;
+    } else if (v.field !== undefined) {
+      p._accessors[i] = dl.accessor(v.field);
+      p._transform.dependency(Deps.FIELDS, dl.field(v.field));
+      return v.field;
+    } else if (v.signal !== undefined) {
+      p._resolution = true;
+      p._signals[v.signal] = i;
+      p._transform.dependency(Deps.SIGNALS, v.signal);
+      return v.signal;
+    }
+
+    return v;
+  });
+
+  return p._transform;
+};
+
+module.exports = Parameter;
+},{"../parse/expr":94,"datalib":24,"vega-dataflow":39}],132:[function(require,module,exports){
+var dl = require('datalib'),
+    Tuple = require('vega-dataflow').Tuple,
+    log = require('vega-logging'),
+    Transform = require('./Transform'),
+    BatchTransform = require('./BatchTransform');
+
+function Pie(graph) {
+  BatchTransform.prototype.init.call(this, graph);
+  Transform.addParameters(this, {
+    field:      {type: 'field', default: null},
+    startAngle: {type: 'value', default: 0},
+    endAngle:   {type: 'value', default: 2 * Math.PI},
+    sort:       {type: 'value', default: false}
+  });
+
+  this._output = {
+    'start': 'layout_start',
+    'end':   'layout_end',
+    'mid':   'layout_mid'
+  };
+
+  return this.mutates(true);
+}
+
+var prototype = (Pie.prototype = Object.create(BatchTransform.prototype));
+prototype.constructor = Pie;
+
+function ones() { return 1; }
+
+prototype.batchTransform = function(input, data) {
+  log.debug(input, ['pie']);
+
+  var output = this._output,
+      field = this.param('field').accessor || ones,
+      start = this.param('startAngle'),
+      stop = this.param('endAngle'),
+      sort = this.param('sort');
+
+  var values = data.map(field),
+      a = start,
+      k = (stop - start) / dl.sum(values),
+      index = dl.range(data.length),
+      i, t, v;
+
+  if (sort) {
+    index.sort(function(a, b) {
+      return values[a] - values[b];
+    });
+  }
+
+  for (i=0; i<index.length; ++i) {
+    t = data[index[i]];
+    v = values[index[i]];
+    Tuple.set(t, output.start, a);
+    Tuple.set(t, output.mid, (a + 0.5 * v * k));
+    Tuple.set(t, output.end, (a += v * k));
+  }
+
+  input.fields[output.start] = 1;
+  input.fields[output.end] = 1;
+  input.fields[output.mid] = 1;
+  return input;
+};
+
+module.exports = Pie;
+},{"./BatchTransform":117,"./Transform":135,"datalib":24,"vega-dataflow":39,"vega-logging":45}],133:[function(require,module,exports){
+var dl = require('datalib'),
+    log  = require('vega-logging'),
+    Transform = require('./Transform');
+
+function Sort(graph) {
+  Transform.prototype.init.call(this, graph);
+  Transform.addParameters(this, {by: {type: 'array<field>'} });
+  this.router(true);
+}
+
+var prototype = (Sort.prototype = Object.create(Transform.prototype));
+prototype.constructor = Sort;
+
+prototype.transform = function(input) {
+  log.debug(input, ['sorting']);
+
+  if (input.add.length || input.mod.length || input.rem.length) {
+    input.sort = dl.comparator(this.param('by').field);
+  }
+  return input;
+};
+
+module.exports = Sort;
+},{"./Transform":135,"datalib":24,"vega-logging":45}],134:[function(require,module,exports){
+var dl = require('datalib'),
+    Tuple = require('vega-dataflow').Tuple,
+    log = require('vega-logging'),
+    Transform = require('./Transform'),
+    BatchTransform = require('./BatchTransform');
+
+function Stack(graph) {
+  BatchTransform.prototype.init.call(this, graph);
+  Transform.addParameters(this, {
+    groupby: {type: 'array<field>'},
+    sortby: {type: 'array<field>'},
+    field: {type: 'field'},
+    offset: {type: 'value', default: 'zero'}
+  });
+
+  this._output = {
+    'start': 'layout_start',
+    'end':   'layout_end',
+    'mid':   'layout_mid'
+  };
+  return this.mutates(true);
+}
+
+var prototype = (Stack.prototype = Object.create(BatchTransform.prototype));
+prototype.constructor = Stack;
+
+prototype.batchTransform = function(input, data) {
+  log.debug(input, ['stacking']);
+
+  var groupby = this.param('groupby').accessor,
+      sortby = dl.comparator(this.param('sortby').field),
+      field = this.param('field').accessor,
+      offset = this.param('offset'),
+      output = this._output;
+
+  // partition, sum, and sort the stack groups
+  var groups = partition(data, groupby, sortby, field);
+
+  // compute stack layouts per group
+  for (var i=0, max=groups.max; i<groups.length; ++i) {
+    var group = groups[i],
+        sum = group.sum,
+        off = offset==='center' ? (max - sum)/2 : 0,
+        scale = offset==='normalize' ? (1/sum) : 1,
+        j, x, a, b = off, v = 0;
+
+    // set stack coordinates for each datum in group
+    for (j=0; j<group.length; ++j) {
+      x = group[j];
+      a = b; // use previous value for start point
+      v += field(x);
+      b = scale * v + off; // compute end point
+      Tuple.set(x, output.start, a);
+      Tuple.set(x, output.end, b);
+      Tuple.set(x, output.mid, 0.5 * (a + b));
+    }
+  }
+
+  input.fields[output.start] = 1;
+  input.fields[output.end] = 1;
+  input.fields[output.mid] = 1;
+  return input;
+};
+
+function partition(data, groupby, sortby, field) {
+  var groups = [],
+      get = function(f) { return f(x); },
+      map, i, x, k, g, s, max;
+
+  // partition data points into stack groups
+  if (groupby == null) {
+    groups.push(data.slice());
+  } else {
+    for (map={}, i=0; i<data.length; ++i) {
+      x = data[i];
+      k = groupby.map(get);
+      g = map[k] || (groups.push(map[k] = []), map[k]);
+      g.push(x);
+    }
+  }
+
+  // compute sums of groups, sort groups as needed
+  for (k=0, max=0; k<groups.length; ++k) {
+    g = groups[k];
+    for (i=0, s=0; i<g.length; ++i) {
+      s += field(g[i]);
+    }
+    g.sum = s;
+    if (s > max) max = s;
+    if (sortby != null) g.sort(sortby);
+  }
+  groups.max = max;
+
+  return groups;
+}
+
+module.exports = Stack;
+},{"./BatchTransform":117,"./Transform":135,"datalib":24,"vega-dataflow":39,"vega-logging":45}],135:[function(require,module,exports){
+var df = require('vega-dataflow'),
+    Base = df.Node.prototype, // jshint ignore:line
+    Deps = df.Dependencies,
+    Parameter = require('./Parameter');
+
+function Transform(graph) {
+  if (graph) Base.init.call(this, graph);
+}
+
+Transform.addParameters = function(proto, params) {
+  proto._parameters = proto._parameters || {};
+  for (var name in params) {
+    var p = params[name],
+        param = new Parameter(name, p.type, proto);
+
+    proto._parameters[name] = param;
+
+    if (p.type === 'custom') {
+      if (p.set) param.set = p.set.bind(param);
+      if (p.get) param.get = p.get.bind(param);
+    }
+
+    if (p.hasOwnProperty('default')) param.set(p.default);
+  }
+};
+
+var prototype = (Transform.prototype = Object.create(Base));
+prototype.constructor = Transform;
+
+prototype.param = function(name, value) {
+  var param = this._parameters[name];
+  return (param === undefined) ? this :
+    (arguments.length === 1) ? param.get() : param.set(value);
+};
+
+// Perform transformation. Subclasses should override.
+prototype.transform = function(input/*, reset */) {
+  return input;
+};
+
+prototype.evaluate = function(input) {
+  // Many transforms store caches that must be invalidated if
+  // a signal value has changed. 
+  var reset = this._stamp < input.stamp &&
+    this.dependency(Deps.SIGNALS).reduce(function(c, s) {
+      return c += input.signals[s] ? 1 : 0;
+    }, 0);
+  return this.transform(input, reset);
+};
+
+prototype.output = function(map) {
+  for (var key in this._output) {
+    if (map[key] !== undefined) {
+      this._output[key] = map[key];
+    }
+  }
+  return this;
+};
+
+module.exports = Transform;
+},{"./Parameter":131,"vega-dataflow":39}],136:[function(require,module,exports){
+(function (global){
+var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null),
+    dl = require('datalib'),
+    Tuple = require('vega-dataflow').Tuple,
+    log = require('vega-logging'),
+    Transform = require('./Transform'),
+    BatchTransform = require('./BatchTransform');
+
+var defaultRatio = 0.5 * (1 + Math.sqrt(5));
+
+function Treemap(graph) {
+  BatchTransform.prototype.init.call(this, graph);
+  Transform.addParameters(this, {
+    // hierarchy parameters
+    sort: {type: 'array<field>', default: ['-value']},
+    children: {type: 'field', default: 'children'},
+    field: {type: 'field', default: 'value'},
+    // treemap parameters
+    size: {type: 'array<value>', default: [500, 500]},
+    round: {type: 'value', default: true},
+    sticky: {type: 'value', default: false},
+    ratio: {type: 'value', default: defaultRatio},
+    padding: {type: 'value', default: null},
+    mode: {type: 'value', default: 'squarify'}
+  });
+
+  this._layout = d3.layout.treemap();
+
+  this._output = {
+    'x':      'layout_x',
+    'y':      'layout_y',
+    'width':  'layout_width',
+    'height': 'layout_height',
+    'depth':  'layout_depth',
+  };
+  return this.mutates(true);
+}
+
+var prototype = (Treemap.prototype = Object.create(BatchTransform.prototype));
+prototype.constructor = Treemap;
+
+prototype.batchTransform = function(input, data) {
+  log.debug(input, ['treemap']);
+
+  // get variables
+  var layout = this._layout,
+      output = this._output;
+
+  // configure layout
+  layout
+    .sort(dl.comparator(this.param('sort').field))
+    .children(this.param('children').accessor)
+    .value(this.param('field').accessor)
+    .size(this.param('size'))
+    .round(this.param('round'))
+    .sticky(this.param('sticky'))
+    .ratio(this.param('ratio'))
+    .padding(this.param('padding'))
+    .mode(this.param('mode'))
+    .nodes(data[0]);
+
+  // copy layout values to nodes
+  data.forEach(function(n) {
+    Tuple.set(n, output.x, n.x);
+    Tuple.set(n, output.y, n.y);
+    Tuple.set(n, output.width, n.dx);
+    Tuple.set(n, output.height, n.dy);
+    Tuple.set(n, output.depth, n.depth);
+  });
+
+  // return changeset
+  input.fields[output.x] = 1;
+  input.fields[output.y] = 1;
+  input.fields[output.width] = 1;
+  input.fields[output.height] = 1;
+  return input;
+};
+
+module.exports = Treemap;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"./BatchTransform":117,"./Transform":135,"datalib":24,"vega-dataflow":39,"vega-logging":45}],137:[function(require,module,exports){
+(function (global){
+var d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null),
+    Tuple = require('vega-dataflow/src/Tuple'),
+    log = require('vega-logging'),
+    Transform = require('./Transform'),
+    BatchTransform = require('./BatchTransform');
+
+function Voronoi(graph) {
+  BatchTransform.prototype.init.call(this, graph);
+  Transform.addParameters(this, {
+    clipExtent: {type: 'array<value>', default: [[-1e5,-1e5],[1e5,1e5]]},
+    x: {type: 'field', default: 'layout_x'},
+    y: {type: 'field', default: 'layout_y'}
+  });
+
+  this._layout = d3.geom.voronoi();
+  this._output = {'path': 'layout_path'};
+
+  return this.mutates(true);
+}
+
+var prototype = (Voronoi.prototype = Object.create(BatchTransform.prototype));
+prototype.constructor = Voronoi;
+
+prototype.batchTransform = function(input, data) {
+  log.debug(input, ['voronoi']);
+
+  // get variables
+  var pathname = this._output.path;
+
+  // configure layout
+  var polygons = this._layout
+    .clipExtent(this.param('clipExtent'))
+    .x(this.param('x').accessor)
+    .y(this.param('y').accessor)
+    (data);
+
+  // build and assign path strings
+  for (var i=0; i<data.length; ++i) {
+    Tuple.set(data[i], pathname, 'M' + polygons[i].join('L') + 'Z');
+  }
+
+  // return changeset
+  input.fields[pathname] = 1;
+  return input;
+};
+
+module.exports = Voronoi;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"./BatchTransform":117,"./Transform":135,"vega-dataflow/src/Tuple":38,"vega-logging":45}],138:[function(require,module,exports){
+(function (global){
+var dl = require('datalib'),
+    d3 = (typeof window !== "undefined" ? window['d3'] : typeof global !== "undefined" ? global['d3'] : null),
+    d3_cloud = (typeof window !== "undefined" ? window['d3']['layout']['cloud'] : typeof global !== "undefined" ? global['d3']['layout']['cloud'] : null),
+    Tuple = require('vega-dataflow/src/Tuple'),
+    log = require('vega-logging'),
+    Transform = require('./Transform'),
+    BatchTransform = require('./BatchTransform');
+
+function Wordcloud(graph) {
+  BatchTransform.prototype.init.call(this, graph);
+  Transform.addParameters(this, {
+    size: {type: 'array<value>', default: [900, 500]},
+    text: {type: 'field', default: 'data'},
+    rotate: {type: 'field|value', default: 0},
+    font: {type: 'field|value', default: {value: 'sans-serif'}},
+    fontSize: {type: 'field|value', default: 14},
+    fontStyle: {type: 'field|value', default: {value: 'normal'}},
+    fontWeight: {type: 'field|value', default: {value: 'normal'}},
+    fontScale: {type: 'array<value>', default: [10, 50]},
+    padding: {type: 'value', default: 1},
+    spiral: {type: 'value', default: 'archimedean'}
+  });
+
+  this._layout = d3_cloud();
+
+  this._output = {
+    'x':          'layout_x',
+    'y':          'layout_y',
+    'font':       'layout_font',
+    'fontSize':   'layout_fontSize',
+    'fontStyle':  'layout_fontStyle',
+    'fontWeight': 'layout_fontWeight',
+    'rotate':     'layout_rotate',
+  };
+
+  return this.mutates(true);
+}
+
+var prototype = (Wordcloud.prototype = Object.create(BatchTransform.prototype));
+prototype.constructor = Wordcloud;
+
+function get(p) {
+  return (p && p.accessor) || p;
+}
+
+function wrap(tuple) {
+  var x = Object.create(tuple);
+  x._tuple = tuple;
+  return x;
+}
+
+prototype.batchTransform = function(input, data) {
+  log.debug(input, ['wordcloud']);
+
+  // get variables
+  var layout = this._layout,
+      output = this._output,
+      fontSize = this.param('fontSize'),
+      range = fontSize.accessor && this.param('fontScale'),
+      size, scale;
+  fontSize = fontSize.accessor || d3.functor(fontSize);
+  
+  // create font size scaling function as needed
+  if (range.length) {
+    scale = d3.scale.sqrt()
+      .domain(dl.extent(data, size=fontSize))
+      .range(range);
+    fontSize = function(x) { return scale(size(x)); };
+  }
+
+  // configure layout
+  layout
+    .size(this.param('size'))
+    .text(get(this.param('text')))
+    .padding(this.param('padding'))
+    .spiral(this.param('spiral'))
+    .rotate(get(this.param('rotate')))
+    .font(get(this.param('font')))
+    .fontStyle(get(this.param('fontStyle')))
+    .fontWeight(get(this.param('fontWeight')))
+    .fontSize(fontSize)
+    .words(data.map(wrap)) // wrap to avoid tuple writes
+    .on('end', function(words) {
+      var size = layout.size(),
+          dx = size[0] >> 1,
+          dy = size[1] >> 1,
+          w, t, i, len;
+
+      for (i=0, len=words.length; i<len; ++i) {
+        w = words[i];
+        t = w._tuple;
+        Tuple.set(t, output.x, w.x + dx);
+        Tuple.set(t, output.y, w.y + dy);
+        Tuple.set(t, output.font, w.font);
+        Tuple.set(t, output.fontSize, w.size);
+        Tuple.set(t, output.fontStyle, w.style);
+        Tuple.set(t, output.fontWeight, w.weight);
+        Tuple.set(t, output.rotate, w.rotate);
+      }
+    })
+    .start();
+
+  // return changeset
+  for (var key in output) input.fields[output[key]] = 1;
+  return input;
+};
+
+module.exports = Wordcloud;
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+
+},{"./BatchTransform":117,"./Transform":135,"datalib":24,"vega-dataflow/src/Tuple":38,"vega-logging":45}],139:[function(require,module,exports){
+module.exports = {
+  aggregate:    require('./Aggregate'),
+  bin:          require('./Bin'),
+  cross:        require('./Cross'),
+  countpattern: require('./CountPattern'),
+  linkpath:     require('./LinkPath'),
+  facet:        require('./Facet'),
+  filter:       require('./Filter'),
+  fold:         require('./Fold'),
+  force:        require('./Force'),
+  formula:      require('./Formula'),
+  geo:          require('./Geo'),
+  geopath:      require('./GeoPath'),
+  lookup:       require('./Lookup'),
+  pie:          require('./Pie'),
+  sort:         require('./Sort'),
+  stack:        require('./Stack'),
+  treemap:      require('./Treemap'),
+  voronoi:      require('./Voronoi'),
+  wordcloud:    require('./Wordcloud')
+};
+},{"./Aggregate":116,"./Bin":118,"./CountPattern":119,"./Cross":120,"./Facet":121,"./Filter":123,"./Fold":124,"./Force":125,"./Formula":126,"./Geo":127,"./GeoPath":128,"./LinkPath":129,"./Lookup":130,"./Pie":132,"./Sort":133,"./Stack":134,"./Treemap":136,"./Voronoi":137,"./Wordcloud":138}]},{},[1])(1)
+});
+//# sourceMappingURL=vega.js.map