docs: function (d3, topojson) {
  //---------------------------------------------------
  // BEGIN code for this module
  //---------------------------------------------------
  
    var vg = {
      version:  "1.4.3", // semantic versioning
      d3:       d3,      // stash d3 for use in property functions
      topojson: topojson // stash topojson similarly
    };
  // type checking functions
  var toString = Object.prototype.toString;
  
  vg.isObject = function(obj) {
    return obj === Object(obj);
  };
  
  vg.isFunction = function(obj) {
    return toString.call(obj) == '[object Function]';
  };
  
  vg.isString = function(obj) {
    return toString.call(obj) == '[object String]';
  };
    
  vg.isArray = Array.isArray || function(obj) {
    return toString.call(obj) == '[object Array]';
  };
  
  vg.isNumber = function(obj) {
    return toString.call(obj) == '[object Number]';
  };
  
  vg.isBoolean = function(obj) {
    return toString.call(obj) == '[object Boolean]';
  };
  
  vg.isTree = function(obj) {
    return obj && obj.__vgtree__;
  };
  
  vg.tree = function(obj, children) {
    var d = [obj];
    d.__vgtree__ = true;
    d.children = children || "children";
    return d;
  };
  
  vg.number = function(s) { return +s; };
  
  vg.boolean = function(s) { return !!s; };
  
  // utility functions
  
  vg.identity = function(x) { return x; };
  
  vg.true = function() { return true; };
  
  vg.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;
  };
  
  vg.duplicate = function(obj) {
    return JSON.parse(JSON.stringify(obj));
  };
  
  vg.field = function(f) {
    return f.split("\\.")
      .map(function(d) { return d.split("."); })
      .reduce(function(a, b) {
        if (a.length) { a[a.length-1] += "." + b.shift(); }
        a.push.apply(a, b);
        return a;
      }, []);
  };
  
  vg.accessor = function(f) {
    var s;
    return (vg.isFunction(f) || f==null)
      ? f : vg.isString(f) && (s=vg.field(f)).length > 1
      ? function(x) { return s.reduce(function(x,f) {
            return x[f];
          }, x);
        }
      : function(x) { return x[f]; };
  };
  
  vg.mutator = function(f) {
    var s;
    return vg.isString(f) && (s=vg.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; };
  };
  
  vg.comparator = function(sort) {
    var sign = [];
    if (sort === undefined) sort = [];
    sort = vg.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 vg.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;
    };
  };
  
  vg.cmp = function(a, b) { return a<b ? -1 : a>b ? 1 : 0; };
  
  vg.numcmp = function(a, b) { return a - b; };
  
  vg.array = function(x) {
    return x != null ? (vg.isArray(x) ? x : [x]) : [];
  };
  
  vg.values = function(x) {
    return (vg.isObject(x) && !vg.isArray(x) && x.values) ? x.values : x;
  };
  
  vg.str = function(x) {
    return vg.isArray(x) ? "[" + x.map(vg.str) + "]"
      : vg.isObject(x) ? JSON.stringify(x)
      : vg.isString(x) ? ("'"+vg_escape_str(x)+"'") : x;
  };
  
  var escape_str_re = /(^|[^\\])'/g;
  
  function vg_escape_str(x) {
    return x.replace(escape_str_re, "$1\\'");
  }
  
  vg.keys = function(x) {
    var keys = [];
    for (var key in x) keys.push(key);
    return keys;
  };
  
  vg.unique = function(data, f, results) {
    if (!vg.isArray(data) || data.length==0) return [];
    f = f || vg.identity;
    results = results || [];
    for (var v, i=0, n=data.length; i<n; ++i) {
      v = f(data[i]);
      if (results.indexOf(v) < 0) results.push(v);
    }
    return results;
  };
  
  vg.minIndex = function(data, f) {
    if (!vg.isArray(data) || data.length==0) return -1;
    f = f || vg.identity;
    var idx = 0, min = f(data[0]), v = min;
    for (var i=1, n=data.length; i<n; ++i) {
      v = f(data[i]);
      if (v < min) { min = v; idx = i; }
    }
    return idx;
  };
  
  vg.maxIndex = function(data, f) {
    if (!vg.isArray(data) || data.length==0) return -1;
    f = f || vg.identity;
    var idx = 0, max = f(data[0]), v = max;
    for (var i=1, n=data.length; i<n; ++i) {
      v = f(data[i]);
      if (v > max) { max = v; idx = i; }
    }
    return idx;
  };
  
  vg.truncate = function(s, length, pos, word, ellipsis) {
    var len = s.length;
    if (len <= length) return s;
    ellipsis = ellipsis || "...";
    var l = Math.max(0, length - ellipsis.length);
  
    switch (pos) {
      case "left":
        return ellipsis + (word ? vg_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 ? vg_truncateOnWord(s,l1) : s.slice(0,l1)) + ellipsis
          + (word ? vg_truncateOnWord(s,l2,1) : s.slice(len-l2));
      default:
        return (word ? vg_truncateOnWord(s,l) : s.slice(0,l)) + ellipsis;
    }
  }
  
  function vg_truncateOnWord(s, len, rev) {
    var cnt = 0, tok = s.split(vg_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 vg_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])/;
  
  // Logging
  
  function vg_write(msg) {
    vg.config.isNode
      ? process.stderr.write(msg + "\n")
      : console.log(msg);
  }
  
  vg.log = function(msg) {
    vg_write("[Vega Log] " + msg);
  };
  
  vg.error = function(msg) {
    msg = "[Vega Err] " + msg;
    vg_write(msg);
    if (typeof alert !== "undefined") alert(msg);
  };vg.config = {};
  
  // are we running in node.js?
  // via timetler.com/2012/10/13/environment-detection-in-javascript/
  vg.config.isNode = typeof exports !== 'undefined' && this.exports !== exports;
  
  // 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']
  vg.config.domainWhiteList = false;
  
  // If true, disable potentially unsafe transforms (filter, formula)
  // involving possible JavaScript injection attacks.
  vg.config.safeMode = false;
  
  // base url for loading external data files
  // used only for server-side operation
  vg.config.baseURL = "";
  
  // version and namepsaces for exported svg
  vg.config.svgNamespace =
    'version="1.1" xmlns="http://www.w3.org/2000/svg" ' +
    'xmlns:xlink="http://www.w3.org/1999/xlink"';
  
  // inset padding for automatic padding calculation
  vg.config.autopadInset = 5;
  
  // extensible scale lookup table
  // all d3.scale.* instances also supported
  vg.config.scale = {
    time: d3.time.scale,
    utc:  d3.time.scale.utc
  };
  
  // default rendering settings
  vg.config.render = {
    lineWidth: 1,
    lineCap:   "butt",
    font:      "sans-serif",
    fontSize:  11
  };
  
  // default axis properties
  vg.config.axis = {
    orient: "bottom",
    ticks: 10,
    padding: 3,
    axisColor: "#000",
    gridColor: "#d8d8d8",
    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
  vg.config.legend = {
    orient: "right",
    offset: 10,
    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
  vg.config.color = {
    rgb: [128, 128, 128],
    lab: [50, 0, 0],
    hcl: [0, 0, 50],
    hsl: [0, 0, 0.5]
  };
  
  // default scale ranges
  vg.config.range = {
    category10: [
      "#1f77b4",
      "#ff7f0e",
      "#2ca02c",
      "#d62728",
      "#9467bd",
      "#8c564b",
      "#e377c2",
      "#7f7f7f",
      "#bcbd22",
      "#17becf"
    ],
    category20: [
      "#1f77b4",
      "#aec7e8",
      "#ff7f0e",
      "#ffbb78",
      "#2ca02c",
      "#98df8a",
      "#d62728",
      "#ff9896",
      "#9467bd",
      "#c5b0d5",
      "#8c564b",
      "#c49c94",
      "#e377c2",
      "#f7b6d2",
      "#7f7f7f",
      "#c7c7c7",
      "#bcbd22",
      "#dbdb8d",
      "#17becf",
      "#9edae5"
    ],
    shapes: [
      "circle",
      "cross",
      "diamond",
      "square",
      "triangle-down",
      "triangle-up"
    ]
  };vg.Bounds = (function() {
    var bounds = function(b) {
      this.clear();
      if (b) this.union(b);
    };
    
    var prototype = bounds.prototype;
    
    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;
    };
  
    return bounds;
  })();vg.Gradient = (function() {
  
    function gradient(type) {
      this.id = "grad_" + (vg_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;
    };
    
    return gradient;
  })();
  
  var vg_gradient_id = 0;vg.canvas = {};vg.canvas.path = (function() {
  
    // Path parsing and rendering code taken from fabric.js -- Thanks!
    var cmdLength = { m:2, l:2, h:1, v:1, c:6, s:4, q:4, t:2, a:7 },
        re = [/([MLHVCSQTAZmlhvcsqtaz])/g, /###/, /(\d)-/g, /\s|,|###/];
  
    function parse(path) {
      var result = [],
          currentPath,
          chunks,
          parsed;
  
      // First, break path into command sequence
      path = path.slice().replace(re[0], '###$1').split(re[1]).slice(1);
  
      // Next, parse each command in turn
      for (var i=0, j, chunksParsed, len=path.length; i<len; i++) {
        currentPath = path[i];
        chunks = currentPath.slice(1).trim().replace(re[2],'$1###-').split(re[3]);
        chunksParsed = [currentPath.charAt(0)];
  
        for (var j = 0, jlen = chunks.length; j < jlen; j++) {
          parsed = parseFloat(chunks[j]);
          if (!isNaN(parsed)) {
            chunksParsed.push(parsed);
          }
        }
  
        var command = chunksParsed[0].toLowerCase(),
            commandLength = cmdLength[command];
  
        if (chunksParsed.length - 1 > commandLength) {
          for (var k = 1, klen = chunksParsed.length; k < klen; k += commandLength) {
            result.push([ chunksParsed[0] ].concat(chunksParsed.slice(k, k + commandLength)));
          }
        }
        else {
          result.push(chunksParsed);
        }
      }
  
      return result;
    }
  
    function drawArc(g, x, y, coords, bounds, l, t) {
      var rx = coords[0];
      var ry = coords[1];
      var rot = coords[2];
      var large = coords[3];
      var sweep = coords[4];
      var ex = coords[5];
      var ey = coords[6];
      var segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y);
      for (var i=0; i<segs.length; i++) {
        var bez = segmentToBezier.apply(null, segs[i]);
        g.bezierCurveTo.apply(g, bez);
        bounds.add(bez[0]-l, bez[1]-t);
        bounds.add(bez[2]-l, bez[3]-t);
        bounds.add(bez[4]-l, bez[5]-t);
      }
    }
  
    function boundArc(x, y, coords, bounds) {
      var rx = coords[0];
      var ry = coords[1];
      var rot = coords[2];
      var large = coords[3];
      var sweep = coords[4];
      var ex = coords[5];
      var ey = coords[6];
      var segs = arcToSegments(ex, ey, rx, ry, large, sweep, rot, x, y);
      for (var i=0; i<segs.length; i++) {
        var bez = segmentToBezier.apply(null, segs[i]);
        bounds.add(bez[0], bez[1]);
        bounds.add(bez[2], bez[3]);
        bounds.add(bez[4], bez[5]);
      }
    }
  
    var arcToSegmentsCache = { },
        segmentToBezierCache = { },
        join = Array.prototype.join,
        argsStr;
  
    // Copied from Inkscape svgtopdf, thanks!
    function arcToSegments(x, y, rx, ry, large, sweep, rotateX, ox, oy) {
      argsStr = join.call(arguments);
      if (arcToSegmentsCache[argsStr]) {
        return arcToSegmentsCache[argsStr];
      }
  
      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 segments = Math.ceil(Math.abs(th_arc / (Math.PI * 0.5 + 0.001)));
      var result = [];
      for (var i=0; i<segments; i++) {
        var th2 = th0 + i * th_arc / segments;
        var th3 = th0 + (i+1) * th_arc / segments;
        result[i] = [xc, yc, th2, th3, rx, ry, sin_th, cos_th];
      }
  
      return (arcToSegmentsCache[argsStr] = result);
    }
  
    function segmentToBezier(cx, cy, th0, th1, rx, ry, sin_th, cos_th) {
      argsStr = join.call(arguments);
      if (segmentToBezierCache[argsStr]) {
        return segmentToBezierCache[argsStr];
      }
  
      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 (segmentToBezierCache[argsStr] = [
        a00 * x1 + a01 * y1,  a10 * x1 + a11 * y1,
        a00 * x2 + a01 * y2,  a10 * x2 + a11 * y2,
        a00 * x3 + a01 * y3,  a10 * x3 + a11 * y3
      ]);
    }
  
    function render(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,
          bounds = new vg.Bounds();
      if (l == undefined) l = 0;
      if (t == undefined) 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);
            bounds.add(x, y);
            break;
  
          case 'L': // lineto, absolute
            x = current[1];
            y = current[2];
            g.lineTo(x + l, y + t);
            bounds.add(x, y);
            break;
  
          case 'h': // horizontal lineto, relative
            x += current[1];
            g.lineTo(x + l, y + t);
            bounds.add(x, y);
            break;
  
          case 'H': // horizontal lineto, absolute
            x = current[1];
            g.lineTo(x + l, y + t);
            bounds.add(x, y);
            break;
  
          case 'v': // vertical lineto, relative
            y += current[1];
            g.lineTo(x + l, y + t);
            bounds.add(x, y);
            break;
  
          case 'V': // verical lineto, absolute
            y = current[1];
            g.lineTo(x + l, y + t);
            bounds.add(x, y);
            break;
  
          case 'm': // moveTo, relative
            x += current[1];
            y += current[2];
            g.moveTo(x + l, y + t);
            bounds.add(x, y);
            break;
  
          case 'M': // moveTo, absolute
            x = current[1];
            y = current[2];
            g.moveTo(x + l, y + t);
            bounds.add(x, y);
            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
            );
            bounds.add(x + current[1], y + current[2]);
            bounds.add(controlX, controlY);
            bounds.add(tempX, tempY);
            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
            );
            bounds.add(current[1], current[2]);
            bounds.add(controlX, controlY);
            bounds.add(x, y);
            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
            );
            bounds.add(controlX, controlY);
            bounds.add(x + current[1], y + current[2]);
            bounds.add(tempX, 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 = 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;
            bounds.add(current[1], current[2]);
            bounds.add(controlX, controlY);
            bounds.add(tempX, 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;
            bounds.add(controlX, controlY);
            bounds.add(tempX, 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];
            bounds.add(controlX, controlY);
            bounds.add(tempX, tempY);
            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];
            bounds.add(controlX, controlY);
            bounds.add(tempX, tempY);
            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;
            bounds.add(controlX, controlY);
            bounds.add(tempX, 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
            ], bounds, l, 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
            ], bounds, l, t);
            x = current[6];
            y = current[7];
            break;
  
          case 'z':
          case 'Z':
            g.closePath();
            break;
        }
        previous = current;
      }
      return bounds.translate(l, t);
    }
  
    function bounds(path, bounds) {
      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;
  
      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];
            bounds.add(x, y);
            break;
  
          case 'L': // lineto, absolute
            x = current[1];
            y = current[2];
            bounds.add(x, y);
            break;
  
          case 'h': // horizontal lineto, relative
            x += current[1];
            bounds.add(x, y);
            break;
  
          case 'H': // horizontal lineto, absolute
            x = current[1];
            bounds.add(x, y);
            break;
  
          case 'v': // vertical lineto, relative
            y += current[1];
            bounds.add(x, y);
            break;
  
          case 'V': // verical lineto, absolute
            y = current[1];
            bounds.add(x, y);
            break;
  
          case 'm': // moveTo, relative
            x += current[1];
            y += current[2];
            bounds.add(x, y);
            break;
  
          case 'M': // moveTo, absolute
            x = current[1];
            y = current[2];
            bounds.add(x, y);
            break;
  
          case 'c': // bezierCurveTo, relative
            tempX = x + current[5];
            tempY = y + current[6];
            controlX = x + current[3];
            controlY = y + current[4];
            bounds.add(x + current[1], y + current[2]);
            bounds.add(controlX, controlY);
            bounds.add(tempX, tempY);
            x = tempX;
            y = tempY;
            break;
  
          case 'C': // bezierCurveTo, absolute
            x = current[5];
            y = current[6];
            controlX = current[3];
            controlY = current[4];
            bounds.add(current[1], current[2]);
            bounds.add(controlX, controlY);
            bounds.add(x, y);
            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;
            bounds.add(controlX, controlY);
            bounds.add(x + current[1], y + current[2]);
            bounds.add(tempX, 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 = 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;
            x = tempX;
            y = tempY;
            bounds.add(current[1], current[2]);
            bounds.add(controlX, controlY);
            bounds.add(tempX, 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];
  
            x = tempX;
            y = tempY;
            bounds.add(controlX, controlY);
            bounds.add(tempX, tempY);
            break;
  
          case 'Q': // quadraticCurveTo, absolute
            tempX = current[3];
            tempY = current[4];
  
            x = tempX;
            y = tempY;
            controlX = current[1];
            controlY = current[2];
            bounds.add(controlX, controlY);
            bounds.add(tempX, tempY);
            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;
  
            x = tempX;
            y = tempY;
            controlX = x + current[1];
            controlY = y + current[2];
            bounds.add(controlX, controlY);
            bounds.add(tempX, tempY);
            break;
  
          case 'T':
            tempX = current[1];
            tempY = current[2];
  
            // calculate reflection of previous control points
            controlX = 2 * x - controlX;
            controlY = 2 * y - controlY;
  
            x = tempX;
            y = tempY;
            bounds.add(controlX, controlY);
            bounds.add(tempX, tempY);
            break;
  
          case 'a':
            boundArc(x, y, [
              current[1],
              current[2],
              current[3],
              current[4],
              current[5],
              current[6] + x,
              current[7] + y
            ], bounds);
            x += current[6];
            y += current[7];
            break;
  
          case 'A':
            boundArc(x, y, [
              current[1],
              current[2],
              current[3],
              current[4],
              current[5],
              current[6],
              current[7]
            ], bounds);
            x = current[6];
            y = current[7];
            break;
  
          case 'z':
          case 'Z':
            break;
        }
        previous = current;
      }
      return bounds;
    }
    
    function area(items) {
      var o = items[0];
      var area;
      
      if (o.orient === "horizontal") {
        area = d3.svg.area()
          .y(function(d) { return d.y; })
          .x0(function(d) { return d.x; })
          .x1(function(d) { return d.x + d.width; });
      } else {
        area = d3.svg.area()
          .x(function(d) { return d.x; })
          .y1(function(d) { return d.y; })
          .y0(function(d) { return d.y + d.height; });
      }
  
      if (o.interpolate) area.interpolate(o.interpolate);
      if (o.tension != null) area.tension(o.tension);
      return area(items);
    }
  
    function line(items) {
      var o = items[0];
      var line = d3.svg.line()
       .x(function(d) { return d.x; })
       .y(function(d) { return d.y; });
      if (o.interpolate) line.interpolate(o.interpolate);
      if (o.tension != null) line.tension(o.tension);
      return line(items);
    }
    
    return {
      parse:  parse,
      render: render,
      bounds: bounds,
      area:   area,
      line:   line
    };
    
  })();vg.canvas.marks = (function() {
  
    var parsePath = vg.canvas.path.parse,
        renderPath = vg.canvas.path.render,
        halfpi = Math.PI / 2,
        sqrt3 = Math.sqrt(3),
        tan30 = Math.tan(30 * Math.PI / 180),
        tmpBounds = new vg.Bounds();
  
    // path generators
  
    function arcPath(g, o) {
      var x = o.x || 0,
          y = o.y || 0,
          ir = o.innerRadius || 0,
          or = o.outerRadius || 0,
          sa = (o.startAngle || 0) - Math.PI/2,
          ea = (o.endAngle || 0) - Math.PI/2;
      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();
    }
  
    function areaPath(g, items) {
      var o = items[0],
          m = o.mark,
          p = m.pathCache || (m.pathCache = parsePath(vg.canvas.path.area(items)));
      renderPath(g, p);
    }
  
    function linePath(g, items) {
      var o = items[0],
          m = o.mark,
          p = m.pathCache || (m.pathCache = parsePath(vg.canvas.path.line(items)));
      renderPath(g, p);
    }
  
    function pathPath(g, o) {
      if (o.path == null) return;
      var p = o.pathCache || (o.pathCache = parsePath(o.path));
      return renderPath(g, p, o.x, o.y);
    }
  
    function symbolPath(g, o) {
      g.beginPath();
      var size = o.size != null ? o.size : 100,
          x = o.x, y = o.y, r, t, rx, ry;
  
      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();
    }
  
    function lineStroke(g, items) {
      var o = items[0],
          lw = o.strokeWidth,
          lc = o.strokeCap;
      g.lineWidth = lw != null ? lw : vg.config.render.lineWidth;
      g.lineCap   = lc != null ? lc : vg.config.render.lineCap;
      linePath(g, items);
    }
  
    function ruleStroke(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 : vg.config.render.lineWidth;
      g.lineCap   = lc != null ? lc : vg.config.render.lineCap;
      g.beginPath();
      g.moveTo(x1, y1);
      g.lineTo(x2, y2);
    }
  
    // drawing functions
  
    function drawPathOne(path, g, o, items) {
      var fill = o.fill, stroke = o.stroke, opac, lc, lw;
  
      path(g, items);
  
      opac = o.opacity == null ? 1 : o.opacity;
      if (opac == 0 || !fill && !stroke) return;
  
      if (fill) {
        g.globalAlpha = opac * (o.fillOpacity==null ? 1 : o.fillOpacity);
        g.fillStyle = color(g, o, fill);
        g.fill();
      }
  
      if (stroke) {
        lw = (lw = o.strokeWidth) != null ? lw : vg.config.render.lineWidth;
        if (lw > 0) {
          g.globalAlpha = opac * (o.strokeOpacity==null ? 1 : o.strokeOpacity);
          g.strokeStyle = color(g, o, stroke);
          g.lineWidth = lw;
          g.lineCap = (lc = o.strokeCap) != null ? lc : vg.config.render.lineCap;
          g.vgLineDash(o.strokeDash || null);
          g.vgLineDashOffset(o.strokeDashOffset || 0);
          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))
          continue; // bounds check
        drawPathOne(path, g, item, item);
      }
    }
  
    function drawRect(g, scene, bounds) {
      if (!scene.items.length) return;
      var items = scene.items,
          o, fill, stroke, opac, lc, lw, 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
  
        x = o.x || 0;
        y = o.y || 0;
        w = o.width || 0;
        h = o.height || 0;
  
        opac = o.opacity == null ? 1 : o.opacity;
        if (opac == 0) continue;
  
        if (fill = o.fill) {
          g.globalAlpha = opac * (o.fillOpacity==null ? 1 : o.fillOpacity);
          g.fillStyle = color(g, o, fill);
          g.fillRect(x, y, w, h);
        }
  
        if (stroke = o.stroke) {
          lw = (lw = o.strokeWidth) != null ? lw : vg.config.render.lineWidth;
          if (lw > 0) {
            g.globalAlpha = opac * (o.strokeOpacity==null ? 1 : o.strokeOpacity);
            g.strokeStyle = color(g, o, stroke);
            g.lineWidth = lw;
            g.lineCap = (lc = o.strokeCap) != null ? lc : vg.config.render.lineCap;
            g.vgLineDash(o.strokeDash || null);
            g.vgLineDashOffset(o.strokeDashOffset || 0);
            g.strokeRect(x, y, w, h);
          }
        }
      }
    }
  
    function drawRule(g, scene, bounds) {
      if (!scene.items.length) return;
      var items = scene.items,
          o, stroke, opac, lc, lw, 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
  
        x1 = o.x || 0;
        y1 = o.y || 0;
        x2 = o.x2 != null ? o.x2 : x1;
        y2 = o.y2 != null ? o.y2 : y1;
  
        opac = o.opacity == null ? 1 : o.opacity;
        if (opac == 0) continue;
        
        if (stroke = o.stroke) {
          lw = (lw = o.strokeWidth) != null ? lw : vg.config.render.lineWidth;
          if (lw > 0) {
            g.globalAlpha = opac * (o.strokeOpacity==null ? 1 : o.strokeOpacity);
            g.strokeStyle = color(g, o, stroke);
            g.lineWidth = lw;
            g.lineCap = (lc = o.strokeCap) != null ? lc : vg.config.render.lineCap;
            g.vgLineDash(o.strokeDash || null);
            g.vgLineDashOffset(o.strokeDashOffset || 0);
            g.beginPath();
            g.moveTo(x1, y1);
            g.lineTo(x2, y2);
            g.stroke();
          }
        }
      }
    }
  
    function drawImage(g, scene, bounds) {
      if (!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, y, w, h, opac;
        w = o.width || (o.image && o.image.width) || 0;
        h = o.height || (o.image && o.image.height) || 0;
        x = (o.x||0) - (o.align === "center"
          ? w/2 : (o.align === "right" ? w : 0));
        y = (o.y||0) - (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);
        }
      }
    }
  
    function drawText(g, scene, bounds) {
      if (!scene.items.length) return;
      var items = scene.items,
          o, fill, stroke, opac, lw, x, y, r, t;
  
      for (var i=0, len=items.length; i<len; ++i) {
        o = items[i];
        if (bounds && !bounds.intersects(o.bounds))
          continue; // bounds check
  
        g.font = vg.scene.fontString(o);
        g.textAlign = o.align || "left";
        g.textBaseline = o.baseline || "alphabetic";
  
        opac = o.opacity == null ? 1 : o.opacity;
        if (opac == 0) continue;
  
        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 = o.dx || 0;
          y = o.dy || 0;
        } else {
          x += (o.dx || 0);
          y += (o.dy || 0);
        }
  
        if (fill = o.fill) {
          g.globalAlpha = opac * (o.fillOpacity==null ? 1 : o.fillOpacity);
          g.fillStyle = color(g, o, fill);
          g.fillText(o.text, x, y);
        }
  
        if (stroke = o.stroke) {
          lw = (lw = o.strokeWidth) != null ? lw : 1;
          if (lw > 0) {
            g.globalAlpha = opac * (o.strokeOpacity==null ? 1 : o.strokeOpacity);
            g.strokeStyle = color(o, stroke);
            g.lineWidth = lw;
            g.strokeText(o.text, x, y);
          }
        }
  
        if (o.angle) g.restore();
      }
    }
  
    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.items[0].bounds))
          return; // bounds check
        drawPathOne(pathFunc, g, scene.items[0], scene.items);
      }
    }
  
    function drawGroup(g, scene, bounds) {
      if (!scene.items.length) return;
      var items = scene.items, group, axes, legends,
          renderer = this, gx, gy, gb, i, n, j, m;
  
      drawRect(g, scene, bounds);
  
      for (i=0, n=items.length; i<n; ++i) {
        group = items[i];
        axes = group.axisItems || [];
        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].def.layer === "back") {
            renderer.draw(g, axes[j], bounds);
          }
        }
        for (j=0, m=group.items.length; j<m; ++j) {
          renderer.draw(g, group.items[j], bounds);
        }
        for (j=0, m=axes.length; j<m; ++j) {
          if (axes[j].def.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 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;
    }
  
    // hit testing
  
    function pickGroup(g, scene, x, y, gx, gy) {
      if (scene.items.length === 0 ||
          scene.bounds && !scene.bounds.contains(gx, gy)) {
        return false;
      }
      var items = scene.items, subscene, group, hit, dx, dy,
          handler = this, 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;
          hit = handler.pick(subscene, x, y, gx-dx, gy-dy);
          if (hit) {
            g.restore();
            return hit;
          }
        }
        g.restore();
      }
  
      return scene.interactive
        ? pickAll(hitTests.group, g, scene, x, y, gx, gy)
        : false;
    }
  
    function pickAll(test, g, scene, x, y, gx, gy) {
      if (!scene.items.length) return false;
      var o, b, i;
  
      if (g._ratio !== 1) {
        x *= g._ratio;
        y *= g._ratio;
      }
  
      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 false;
    }
  
    function pickArea(g, scene, x, y, gx, gy) {
      if (!scene.items.length) return false;
      var items = scene.items,
          o, b, i, di, dd, od, dx, dy;
  
      b = items[0].bounds;
      if (b && !b.contains(gx, gy)) return false;
      if (g._ratio !== 1) {
        x *= g._ratio;
        y *= g._ratio;
      }
      if (!hitTests.area(g, items, x, y)) return false;
      return items[0];
    }
  
    function pickLine(g, scene, x, y, gx, gy) {
      if (!scene.items.length) return false;
      var items = scene.items,
          o, b, i, di, dd, od, dx, dy;
  
      b = items[0].bounds;
      if (b && !b.contains(gx, gy)) return false;
      if (g._ratio !== 1) {
        x *= g._ratio;
        y *= g._ratio;
      }
      if (!hitTests.line(g, items, x, y)) return false;
      return items[0];
    }
  
    function pick(test) {
      return function (g, scene, x, y, gx, gy) {
        return pickAll(test, g, scene, x, y, gx, gy);
      };
    }
  
    function textHit(g, o, x, y, gx, gy) {
      if (!o.fontSize) return false;
      if (!o.angle) return true; // bounds sufficient if no rotation
  
      var b = vg.scene.bounds.text(o, tmpBounds, true),
          a = -o.angle * Math.PI / 180,
          cos = Math.cos(a),
          sin = Math.sin(a),
          x = o.x,
          y = o.y,
          px = cos*gx - sin*gy + (x - x*cos + y*sin),
          py = sin*gx + cos*gy + (y - x*sin - y*cos);
  
      return b.contains(px, py);
    }
  
    var hitTests = {
      text:   textHit,
      rect:   function(g,o,x,y) { return true; }, // bounds test is sufficient
      image:  function(g,o,x,y) { return true; }, // bounds test is sufficient
      group:  function(g,o,x,y) { return o.fill || o.stroke; },
      rule:   function(g,o,x,y) {
                if (!g.isPointInStroke) return false;
                ruleStroke(g,o); return g.isPointInStroke(x,y);
              },
      line:   function(g,s,x,y) {
                if (!g.isPointInStroke) return false;
                lineStroke(g,s); return g.isPointInStroke(x,y);
              },
      arc:    function(g,o,x,y) { arcPath(g,o);  return g.isPointInPath(x,y); },
      area:   function(g,s,x,y) { areaPath(g,s); return g.isPointInPath(x,y); },
      path:   function(g,o,x,y) { pathPath(g,o); return g.isPointInPath(x,y); },
      symbol: function(g,o,x,y) { symbolPath(g,o); return g.isPointInPath(x,y); }
    };
  
    return {
      draw: {
        group:   drawGroup,
        area:    drawOne(areaPath),
        line:    drawOne(linePath),
        arc:     drawAll(arcPath),
        path:    drawAll(pathPath),
        symbol:  drawAll(symbolPath),
        rect:    drawRect,
        rule:    drawRule,
        text:    drawText,
        image:   drawImage,
        drawOne: drawOne, // expose for extensibility
        drawAll: drawAll  // expose for extensibility
      },
      pick: {
        group:   pickGroup,
        area:    pickArea,
        line:    pickLine,
        arc:     pick(hitTests.arc),
        path:    pick(hitTests.path),
        symbol:  pick(hitTests.symbol),
        rect:    pick(hitTests.rect),
        rule:    pick(hitTests.rule),
        text:    pick(hitTests.text),
        image:   pick(hitTests.image),
        pickAll: pickAll  // expose for extensibility
      }
    };
  
  })();vg.canvas.Renderer = (function() {
    var renderer = function() {
      this._ctx = null;
      this._el = null;
      this._imgload = 0;
    };
  
    var prototype = renderer.prototype;
  
    prototype.initialize = function(el, width, height, pad) {
      this._el = el;
    
      if (!el) return this; // early exit if no DOM element
  
      // select canvas element
      var canvas = d3.select(el)
        .selectAll("canvas.marks")
        .data([1]);
  
      // create new canvas element if needed
      canvas.enter()
        .append("canvas")
        .attr("class", "marks");
  
      // remove extraneous canvas if needed
      canvas.exit().remove();
  
      return this.resize(width, height, pad);
    };
  
    prototype.resize = function(width, height, pad) {
      this._width = width;
      this._height = height;
      this._padding = pad;
  
      if (this._el) {
        var canvas = d3.select(this._el).select("canvas.marks");
  
        // initialize canvas attributes
        canvas
          .attr("width", width + pad.left + pad.right)
          .attr("height", height + pad.top + pad.bottom);
  
        // get the canvas graphics context
        var s;
        this._ctx = canvas.node().getContext("2d");
        this._ctx._ratio = (s = scaleCanvas(canvas.node(), this._ctx) || 1);
        this._ctx.setTransform(s, 0, 0, s, s*pad.left, s*pad.top);
      }
  
      initializeLineDash(this._ctx);
      return this;
    };
  
    function scaleCanvas(canvas, ctx) {
      // get canvas pixel data
      var devicePixelRatio = window.devicePixelRatio || 1,
          backingStoreRatio = (
            ctx.webkitBackingStorePixelRatio ||
            ctx.mozBackingStorePixelRatio ||
            ctx.msBackingStorePixelRatio ||
            ctx.oBackingStorePixelRatio ||
            ctx.backingStorePixelRatio) || 1,
          ratio = devicePixelRatio / backingStoreRatio;
  
      if (devicePixelRatio !== backingStoreRatio) {
        var w = canvas.width, h = canvas.height;
        // set actual and visible canvas size
        canvas.setAttribute("width", w * ratio);
        canvas.setAttribute("height", h * ratio);
        canvas.style.width = w + 'px';
        canvas.style.height = h + 'px';
      }
      return ratio;
    }
  
    function initializeLineDash(ctx) {
      if (ctx.vgLineDash) return; // already set
  
      var NODASH = [];
      if (ctx.setLineDash) {
        ctx.vgLineDash = function(dash) { this.setLineDash(dash || NODASH); };
        ctx.vgLineDashOffset = function(off) { this.lineDashOffset = off; };
      } else if (ctx.webkitLineDash !== undefined) {
      	ctx.vgLineDash = function(dash) { this.webkitLineDash = dash || NODASH; };
        ctx.vgLineDashOffset = function(off) { this.webkitLineDashOffset = off; };
      } else if (ctx.mozDash !== undefined) {
        ctx.vgLineDash = function(dash) { this.mozDash = dash; };
        ctx.vgLineDashOffset = function(off) { /* unsupported */ };
      } else {
        ctx.vgLineDash = function(dash) { /* unsupported */ };
        ctx.vgLineDashOffset = function(off) { /* unsupported */ };
      }
    }
  
    prototype.context = function(ctx) {
      if (ctx) { this._ctx = ctx; return this; }
      else return this._ctx;
    };
  
    prototype.element = function() {
      return this._el;
    };
  
    prototype.pendingImages = function() {
      return this._imgload;
    };
  
    function translatedBounds(item, bounds) {
      var b = new vg.Bounds(bounds);
      while ((item = item.mark.group) != null) {
        b.translate(item.x || 0, item.y || 0);
      }
      return b;
    }
  
    function getBounds(items) {
      return !items ? null :
        vg.array(items).reduce(function(b, item) {
          return b.union(translatedBounds(item, item.bounds))
                  .union(translatedBounds(item, item['bounds:prev']));
        }, new vg.Bounds());
    }
  
    function setBounds(g, bounds) {
      var bbox = null;
      if (bounds) {
        bbox = (new vg.Bounds(bounds)).round();
        g.beginPath();
        g.rect(bbox.x1, bbox.y1, bbox.width(), bbox.height());
        g.clip();
      }
      return bbox;
    }
  
    prototype.render = function(scene, items) {
      var g = this._ctx,
          pad = this._padding,
          w = this._width + pad.left + pad.right,
          h = this._height + pad.top + pad.bottom,
          bb = null, bb2;
  
      // setup
      this._scene = scene;
      g.save();
      bb = setBounds(g, getBounds(items));
      g.clearRect(-pad.left, -pad.top, w, h);
  
      // render
      this.draw(g, scene, bb);
  
      // render again to handle possible bounds change
      if (items) {
        g.restore();
        g.save();
        bb2 = setBounds(g, getBounds(items));
        if (!bb.encloses(bb2)) {
          g.clearRect(-pad.left, -pad.top, w, h);
          this.draw(g, scene, bb2);
        }
      }
  
      // takedown
      g.restore();
      this._scene = null;
    };
  
    prototype.draw = function(ctx, scene, bounds) {
      var marktype = scene.marktype,
          renderer = vg.canvas.marks.draw[marktype];
      renderer.call(this, ctx, scene, bounds);
    };
  
    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;
      }, 50);
    };
  
    prototype.loadImage = function(uri) {
      var renderer = this,
          scene = renderer._scene,
          image = null, url;
  
      renderer._imgload += 1;
      if (vg.config.isNode) {
        image = new (require("canvas").Image)();
        vg.data.load(uri, function(err, data) {
          if (err) { vg.error(err); return; }
          image.src = data;
          image.loaded = true;
          renderer._imgload -= 1;
        });
      } else {
        image = new Image();
        url = vg.config.baseURL + uri;
        image.onload = function() {
          vg.log("LOAD IMAGE: "+url);
          image.loaded = true;
          renderer._imgload -= 1;
          renderer.renderAsync(scene);
        };
        image.src = url;
      }
  
      return image;
    };
  
    return renderer;
  })();vg.canvas.Handler = (function() {
    var handler = function(el, model) {
      this._active = null;
      this._handlers = {};
      if (el) this.initialize(el);
      if (model) this.model(model);
    };
    
    var prototype = handler.prototype;
  
    prototype.initialize = function(el, pad, obj) {
      this._el = d3.select(el).node();
      this._canvas = d3.select(el).select("canvas.marks").node();
      this._padding = pad;
      this._obj = obj || null;
      
      // add event listeners
      var canvas = this._canvas, that = this;
      events.forEach(function(type) {
        canvas.addEventListener(type, function(evt) {
          prototype[type].call(that, evt);
        });
      });
      
      return this;
    };
    
    prototype.padding = function(pad) {
      this._padding = pad;
      return this;
    };
    
    prototype.model = function(model) {
      if (!arguments.length) return this._model;
      this._model = model;
      return this;
    };
  
    prototype.handlers = function() {
      var h = this._handlers;
      return vg.keys(h).reduce(function(a, k) {
        return h[k].reduce(function(a, x) { return (a.push(x), a); }, a);
      }, []);
    };
  
    // setup events
    var events = [
      "mousedown",
      "mouseup",
      "click",
      "dblclick",
      "wheel",
      "keydown",
      "keypress",
      "keyup",
      "mousewheel"
    ];
    events.forEach(function(type) {
      prototype[type] = function(evt) {
        this.fire(type, evt);
      };
    });
    events.push("mousemove");
    events.push("mouseout");
  
    function eventName(name) {
      var i = name.indexOf(".");
      return i < 0 ? name : name.slice(0,i);
    }
  
    prototype.mousemove = function(evt) {
      var pad = this._padding,
          b = evt.target.getBoundingClientRect(),
          x = evt.clientX - b.left,
          y = evt.clientY - b.top,
          a = this._active,
          p = this.pick(this._model.scene(), x, y, x-pad.left, y-pad.top);
  
      if (p === a) {
        this.fire("mousemove", evt);
        return;
      } else if (a) {
        this.fire("mouseout", evt);
      }
      this._active = p;
      if (p) {
        this.fire("mouseover", evt);
      }
    };
    
    prototype.mouseout = function(evt) {
      if (this._active) {
        this.fire("mouseout", evt);
      }
      this._active = null;
    };
  
    // to keep firefox happy
    prototype.DOMMouseScroll = function(evt) {
      this.fire("mousewheel", evt);
    };
  
    // fire an event
    prototype.fire = function(type, evt) {
      var a = this._active,
          h = this._handlers[type];
      if (a && h) {
        for (var 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 = eventName(type),
          h = this._handlers;
      h = h[name] || (h[name] = []);
      h.push({
        type: type,
        handler: handler
      });
      return this;
    };
  
    // remove an event handler
    prototype.off = function(type, handler) {
      var name = eventName(type),
          h = this._handlers[name];
      if (!h) return;
      for (var i=h.length; --i>=0;) {
        if (h[i].type !== type) continue;
        if (!handler || h[i].handler === handler) h.splice(i, 1);
      }
      return this;
    };
    
    // retrieve the current canvas context
    prototype.context = function() {
      return this._canvas.getContext("2d");
    };
    
    // 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(),
          marktype = scene.marktype,
          picker = vg.canvas.marks.pick[marktype];
      return picker.call(this, g, scene, x, y, gx, gy);
    };
  
    return handler;
  })();vg.svg = {};vg.svg.marks = (function() {
  
    function x(o)     { return o.x || 0; }
    function y(o)     { return o.y || 0; }
    function xw(o)    { return o.x + o.width || 0; }
    function yh(o)    { return o.y + o.height || 0; }
    function key(o)   { return o.key; }
    function size(o)  { return o.size==null ? 100 : o.size; }
    function shape(o) { return o.shape || "circle"; }
        
    var arc_path    = d3.svg.arc(),
        area_path_v = d3.svg.area().x(x).y1(y).y0(yh),
        area_path_h = d3.svg.area().y(y).x0(xw).x1(x),
        line_path   = d3.svg.line().x(x).y(y),
        symbol_path = d3.svg.symbol().type(shape).size(size);
    
    var mark_id = 0,
        clip_id = 0;
    
    var textAlign = {
      "left":   "start",
      "center": "middle",
      "right":  "end"
    };
    
    var 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"
    };
    var styleProps = vg.keys(styles);
  
    function style(d) {
      var i, n, prop, name, value,
          o = d.mark ? d : d.length ? d[0] : null;
      if (o === null) return;
  
      for (i=0, n=styleProps.length; i<n; ++i) {
        prop = styleProps[i];
        name = styles[prop];
        value = o[prop];
  
        if (value == null) {
          if (name === "fill") this.style.setProperty(name, "none", null);
          else this.style.removeProperty(name);
        } else {
          if (value.id) {
            // ensure definition is included
            vg.svg._cur._defs.gradient[value.id] = value;
            value = "url(" + window.location.href + "#" + value.id + ")";
          }
          this.style.setProperty(name, value+"", null);
        }
      }
    }
    
    function arc(o) {
      var x = o.x || 0,
          y = o.y || 0;
      this.setAttribute("transform", "translate("+x+","+y+")");
      this.setAttribute("d", arc_path(o));
    }
    
    function area(items) {
      if (!items.length) return;
      var o = items[0],
          path = o.orient === "horizontal" ? area_path_h : area_path_v;
      path
        .interpolate(o.interpolate || "linear")
        .tension(o.tension == null ? 0.7 : o.tension);
      this.setAttribute("d", path(items));
    }
    
    function line(items) {
      if (!items.length) return;
      var o = items[0];
      line_path
        .interpolate(o.interpolate || "linear")
        .tension(o.tension == null ? 0.7 : o.tension);
      this.setAttribute("d", line_path(items));
    }
    
    function path(o) {
      var x = o.x || 0,
          y = o.y || 0;
      this.setAttribute("transform", "translate("+x+","+y+")");
      if (o.path != null) this.setAttribute("d", o.path);
    }
  
    function rect(o) {
      this.setAttribute("x", o.x || 0);
      this.setAttribute("y", o.y || 0);
      this.setAttribute("width", o.width || 0);
      this.setAttribute("height", o.height || 0);
    }
  
    function rule(o) {
      var x1 = o.x || 0,
          y1 = o.y || 0;
      this.setAttribute("x1", x1);
      this.setAttribute("y1", y1);
      this.setAttribute("x2", o.x2 != null ? o.x2 : x1);
      this.setAttribute("y2", o.y2 != null ? o.y2 : y1);
    }
    
    function symbol(o) {
      var x = o.x || 0,
          y = o.y || 0;
      this.setAttribute("transform", "translate("+x+","+y+")");
      this.setAttribute("d", symbol_path(o));
    }
    
    function image(o) {
      var w = o.width || (o.image && o.image.width) || 0,
          h = o.height || (o.image && o.image.height) || 0,
          x = o.x - (o.align === "center"
            ? w/2 : (o.align === "right" ? w : 0)),
          y = o.y - (o.baseline === "middle"
            ? h/2 : (o.baseline === "bottom" ? h : 0)),
          url = vg.config.baseURL + o.url;
      
      this.setAttributeNS("http://www.w3.org/1999/xlink", "href", url);
      this.setAttribute("x", x);
      this.setAttribute("y", y);
      this.setAttribute("width", w);
      this.setAttribute("height", h);
    }
      
    function fontString(o) {
      return (o.fontStyle ? o.fontStyle + " " : "")
        + (o.fontVariant ? o.fontVariant + " " : "")
        + (o.fontWeight ? o.fontWeight + " " : "")
        + (o.fontSize != null ? o.fontSize : vg.config.render.fontSize) + "px "
        + (o.font || vg.config.render.font);
    }
    
    function text(o) {
      var x = o.x || 0,
          y = o.y || 0,
          dx = o.dx || 0,
          dy = o.dy || 0,
          a = o.angle || 0,
          r = o.radius || 0,
          align = textAlign[o.align || "left"],
          base = o.baseline==="top" ? ".9em"
               : o.baseline==="middle" ? ".35em" : 0;
  
      if (r) {
        var t = (o.theta || 0) - Math.PI/2;
        x += r * Math.cos(t);
        y += r * Math.sin(t);
      }
  
      this.setAttribute("x", x + dx);
      this.setAttribute("y", y + dy);
      this.setAttribute("text-anchor", align);
      
      if (a) this.setAttribute("transform", "rotate("+a+" "+x+","+y+")");
      else this.removeAttribute("transform");
      
      if (base) this.setAttribute("dy", base);
      else this.removeAttribute("dy");
      
      this.textContent = o.text;
      this.style.setProperty("font", fontString(o), null);
    }
    
    function group(o) {
      var x = o.x || 0,
          y = o.y || 0;
      this.setAttribute("transform", "translate("+x+","+y+")");
  
      if (o.clip) {
        var c = {width: o.width || 0, height: o.height || 0},
            id = o.clip_id || (o.clip_id = "clip" + clip_id++);
        vg.svg._cur._defs.clipping[id] = c;
        this.setAttribute("clip-path", "url(#"+id+")");
      }
    }
  
    function group_bg(o) {
      var w = o.width || 0,
          h = o.height || 0;
      this.setAttribute("width", w);
      this.setAttribute("height", h);
    }
  
    function cssClass(def) {
      var cls = "type-" + def.type;
      if (def.name) cls += " " + def.name;
      return cls;
    }
  
    function draw(tag, attr, nest) {
      return function(g, scene, index) {
        drawMark(g, scene, index, "mark_", tag, attr, nest);
      };
    }
    
    function drawMark(g, scene, index, prefix, tag, attr, nest) {
      var data = nest ? [scene.items] : scene.items,
          evts = scene.interactive===false ? "none" : null,
          grps = g.node().childNodes,
          notG = (tag !== "g"),
          p = (p = grps[index+1]) // +1 to skip group background rect
            ? d3.select(p)
            : g.append("g")
               .attr("id", "g"+(++mark_id))
               .attr("class", cssClass(scene.def));
  
      var id = p.attr("id"),
          s = "#" + id + " > " + tag,
          m = p.selectAll(s).data(data),
          e = m.enter().append(tag);
  
      if (notG) {
        p.style("pointer-events", evts);
        e.each(function(d) {
          if (d.mark) d._svg = this;
          else if (d.length) d[0]._svg = this;
        });
      } else {
        e.append("rect").attr("class","background").style("pointer-events",evts);
      }
      
      m.exit().remove();
      m.each(attr);
      if (notG) m.each(style);
      else p.selectAll(s+" > rect.background").each(group_bg).each(style);
      
      return p;
    }
  
    function drawGroup(g, scene, index, prefix) {
      var p = drawMark(g, scene, index, prefix || "group_", "g", group),
          c = p.node().childNodes, n = c.length, i, j, m;
      
      for (i=0; i<n; ++i) {
        var items = c[i].__data__.items,
            legends = c[i].__data__.legendItems || [],
            axes = c[i].__data__.axisItems || [],
            sel = d3.select(c[i]),
            idx = 0;
  
        for (j=0, m=axes.length; j<m; ++j) {
          if (axes[j].def.layer === "back") {
            drawGroup.call(this, sel, axes[j], idx++, "axis_");
          }
        }
        for (j=0, m=items.length; j<m; ++j) {
          this.draw(sel, items[j], idx++);
        }
        for (j=0, m=axes.length; j<m; ++j) {
          if (axes[j].def.layer !== "back") {
            drawGroup.call(this, sel, axes[j], idx++, "axis_");
          }
        }
        for (j=0, m=legends.length; j<m; ++j) {
          drawGroup.call(this, sel, legends[j], idx++, "legend_");
        }
      }
    }
  
    return {
      update: {
        group:   rect,
        area:    area,
        line:    line,
        arc:     arc,
        path:    path,
        symbol:  symbol,
        rect:    rect,
        rule:    rule,
        text:    text,
        image:   image
      },
      nested: {
        "area": true,
        "line": true
      },
      style: style,
      draw: {
        group:   drawGroup,
        area:    draw("path", area, true),
        line:    draw("path", line, true),
        arc:     draw("path", arc),
        path:    draw("path", path),
        symbol:  draw("path", symbol),
        rect:    draw("rect", rect),
        rule:    draw("line", rule),
        text:    draw("text", text),
        image:   draw("image", image),
        draw:    draw // expose for extensibility
      }
    };
    
  })();vg.svg.Renderer = (function() {
    var renderer = function() {
      this._svg = null;
      this._ctx = null;
      this._el = null;
      this._defs = {
        gradient: {},
        clipping: {}
      };
    };
    
    var prototype = renderer.prototype;
    
    prototype.initialize = function(el, width, height, pad) {
      this._el = el;
  
      // remove any existing svg element
      d3.select(el).select("svg.marks").remove();
  
      // create svg element and initialize attributes
      this._svg = d3.select(el)
        .append("svg")
        .attr("class", "marks");
      
      // set the svg root group
      this._ctx = this._svg.append("g");
      
      return this.resize(width, height, pad);
    };
    
    prototype.resize = function(width, height, pad) {
      this._width = width;
      this._height = height;
      this._padding = pad;
      
      this._svg
        .attr("width", width + pad.left + pad.right)
        .attr("height", height + pad.top + pad.bottom);
        
      this._ctx
        .attr("transform", "translate("+pad.left+","+pad.top+")");
  
      return this;
    };
    
    prototype.context = function() {
      return this._ctx;
    };
    
    prototype.element = function() {
      return this._el;
    };
  
    prototype.updateDefs = function() {
      var svg = this._svg,
          all = this._defs,
          dgrad = vg.keys(all.gradient),
          dclip = vg.keys(all.clipping),
          defs = svg.select("defs"), grad, clip;
    
      // get or create svg defs block
      if (dgrad.length===0 && dclip.length==0) { defs.remove(); return; }
      if (defs.empty()) defs = svg.insert("defs", ":first-child");
      
      grad = defs.selectAll("linearGradient").data(dgrad, vg.identity);
      grad.enter().append("linearGradient").attr("id", vg.identity);
      grad.exit().remove();
      grad.each(function(id) {
        var def = all.gradient[id],
            grd = d3.select(this);
    
        // set gradient coordinates
        grd.attr({x1: def.x1, x2: def.x2, y1: def.y1, y2: def.y2});
    
        // set gradient stops
        stop = grd.selectAll("stop").data(def.stops);
        stop.enter().append("stop");
        stop.exit().remove();
        stop.attr("offset", function(d) { return d.offset; })
            .attr("stop-color", function(d) { return d.color; });
      });
      
      clip = defs.selectAll("clipPath").data(dclip, vg.identity);
      clip.enter().append("clipPath").attr("id", vg.identity);
      clip.exit().remove();
      clip.each(function(id) {
        var def = all.clipping[id],
            cr = d3.select(this).selectAll("rect").data([1]);
        cr.enter().append("rect");
        cr.attr("x", 0)
          .attr("y", 0)
          .attr("width", def.width)
          .attr("height", def.height);
      });
    };
    
    prototype.render = function(scene, items) {
      vg.svg._cur = this;
  
      if (items) {
        this.renderItems(vg.array(items));
      } else {
        this.draw(this._ctx, scene, -1);
      }
      this.updateDefs();
  
     delete vg.svg._cur;
    };
    
    prototype.renderItems = function(items) {
      var item, node, type, nest, i, n,
          marks = vg.svg.marks;
  
      for (i=0, n=items.length; i<n; ++i) {
        item = items[i];
        node = item._svg;
        type = item.mark.marktype;
  
        item = marks.nested[type] ? item.mark.items : item;
        marks.update[type].call(node, item);
        marks.style.call(node, item);
      }
    }
    
    prototype.draw = function(ctx, scene, index) {
      var marktype = scene.marktype,
          renderer = vg.svg.marks.draw[marktype];
      renderer.call(this, ctx, scene, index);
    };
    
    return renderer;
  })();vg.svg.Handler = (function() {
    var handler = function(el, model) {
      this._active = null;
      this._handlers = {};
      if (el) this.initialize(el);
      if (model) this.model(model);
    };
    
    function svgHandler(handler) {
      var that = this;
      return function(evt) {
        var target = evt.target,
            item = target.__data__;
        if (item) {
          item = item.mark ? item : item[0];
          handler.call(that._obj, evt, item);
        }
      };
    }
    
    function eventName(name) {
      var i = name.indexOf(".");
      return i < 0 ? name : name.slice(0,i);
    }
    
    var prototype = handler.prototype;
  
    prototype.initialize = function(el, pad, obj) {
      this._el = d3.select(el).node();
      this._svg = d3.select(el).select("svg.marks").node();
      this._padding = pad;
      this._obj = obj || null;
      return this;
    };
    
    prototype.padding = function(pad) {
      this._padding = pad;
      return this;
    };
    
    prototype.model = function(model) {
      if (!arguments.length) return this._model;
      this._model = model;
      return this;
    };
    
    prototype.handlers = function() {
      var h = this._handlers;
      return vg.keys(h).reduce(function(a, k) {
        return h[k].reduce(function(a, x) { return (a.push(x), a); }, a);
      }, []);
    };
  
    // add an event handler
    prototype.on = function(type, handler) {
      var name = eventName(type),
          h = this._handlers,
          dom = d3.select(this._svg).node();
          
      var x = {
        type: type,
        handler: handler,
        svg: svgHandler.call(this, handler)
      };
      h = h[name] || (h[name] = []);
      h.push(x);
  
      dom.addEventListener(name, x.svg);
      return this;
    };
  
    // remove an event handler
    prototype.off = function(type, handler) {
      var name = eventName(type),
          h = this._handlers[name],
          dom = d3.select(this._svg).node();
      if (!h) return;
      for (var i=h.length; --i>=0;) {
        if (h[i].type !== type) continue;
        if (!handler || h[i].handler === handler) {
          dom.removeEventListener(name, h[i].svg);
          h.splice(i, 1);
        }
      }
      return this;
    };
  
    return handler;
  })();vg.data = {};
  
  vg.data.ingestAll = function(data) {
    return vg.isTree(data)
      ? vg_make_tree(vg.data.ingestTree(data[0], data.children))
      : data.map(vg.data.ingest);
  };
  
  vg.data.ingest = function(datum, index) {
    return {
      data: datum,
      index: index
    };
  };
  
  vg.data.ingestTree = function(node, children, index) {
    var d = vg.data.ingest(node, index || 0),
        c = node[children], n, i;
    if (c && (n = c.length)) {
      d.values = Array(n);
      for (i=0; i<n; ++i) {
        d.values[i] = vg.data.ingestTree(c[i], children, i);
      }
    }
    return d;
  };
  
  function vg_make_tree(d) {
    d.__vgtree__ = true;
    d.nodes = function() { return vg_tree_nodes(this, []); };
    return d;
  }
  
  function vg_tree_nodes(root, nodes) {
    var c = root.values,
        n = c ? c.length : 0, i;
    nodes.push(root);
    for (i=0; i<n; ++i) { vg_tree_nodes(c[i], nodes); }
    return nodes;
  }
  
  function vg_data_duplicate(d) {
    var x=d, i, n;
    if (vg.isArray(d)) {
      x = [];
      for (i=0, n=d.length; i<n; ++i) {
        x.push(vg_data_duplicate(d[i]));
      }
    } else if (vg.isObject(d)) {
      x = {};
      for (i in d) {
        x[i] = vg_data_duplicate(d[i]);
      }
    }
    return x;
  }
  
  vg.data.mapper = function(func) {
    return function(data) {
      data.forEach(func);
      return data;
    }
  };
  
  vg.data.size = function(size, group) {
    size = vg.isArray(size) ? size : [0, size];
    size = size.map(function(d) {
      return (typeof d === 'string') ? group[d] : d;
    });
    return size;
  };vg.data.load = function(uri, callback) {
    var url = vg_load_hasProtocol(uri) ? uri : vg.config.baseURL + uri;
    if (vg.config.isNode) {
      // in node.js, consult url and select file or http
      var get = vg_load_isFile(url) ? vg_load_file : vg_load_http;
      get(url, callback);
    } else {
      // in browser, use xhr
      vg_load_xhr(url, callback);
    }  
  };
  
  var vg_load_protocolRE = /^[A-Za-z]+\:\/\//;
  var vg_load_fileProtocol = "file://";
  
  function vg_load_hasProtocol(url) {
    return vg_load_protocolRE.test(url);
  }
  
  function vg_load_isFile(url) {
    return url.indexOf(vg_load_fileProtocol) === 0;
  }
  
  function vg_load_xhr(url, callback) {
    vg.log("LOAD: " + url);
    if (!vg_url_check(url)) {
      vg.error("URL is not whitelisted: " + url);
      return;
    }
    d3.xhr(url, function(err, resp) {
      if (resp) resp = resp.responseText;
      callback(err, resp);
    });
  }
  
  function vg_url_check(url) {
    // If vg.config.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 (!vg.config.domainWhiteList)
      return true;
  
    var a = document.createElement("a");
    a.href = url;
    var domain = a.hostname.toLowerCase();
  
    return window.location.hostname === domain ||
      vg.config.domainWhiteList.some(function(d) {
        var ind = domain.length - d.length;
        return d === domain ||
          (ind > 1 && domain[ind-1] === '.' && domain.lastIndexOf(d) === ind);
      });
  }
  
  function vg_load_file(file, callback) {
    vg.log("LOAD FILE: " + file);
    var idx = file.indexOf(vg_load_fileProtocol);
    if (idx >= 0) file = file.slice(vg_load_fileProtocol.length);
    require("fs").readFile(file, callback);
  }
  
  function vg_load_http(url, callback) {
    vg.log("LOAD HTTP: " + url);
  	var req = require("http").request(url, function(res) {
      var pos=0, data = new Buffer(parseInt(res.headers['content-length'],10));
  		res.on("error", function(err) { callback(err, null); });
  		res.on("data", function(x) { x.copy(data, pos); pos += x.length; });
  		res.on("end", function() { callback(null, data); });
  	});
  	req.on("error", function(err) { callback(err); });
  	req.end();
  }vg.data.read = (function() {
    var formats = {},
        parsers = {
          "number": vg.number,
          "boolean": vg.boolean,
          "date": Date.parse
        };
  
    function read(data, format) {
      var type = (format && format.type) || "json";
      data = formats[type](data, format);
      if (format && format.parse) parseValues(data, format.parse);
      return data;
    }
  
    formats.json = function(data, format) {
      var d = vg.isObject(data) ? data : JSON.parse(data);
      if (format && format.property) {
        d = vg.accessor(format.property)(d);
      }
      return d;
    };
  
    formats.csv = function(data, format) {
      var d = d3.csv.parse(data);
      return d;
    };
  
    formats.tsv = function(data, format) {
      var d = d3.tsv.parse(data);
      return d;
    };
    
    formats.topojson = function(data, format) {
      if (topojson == null) {
        vg.error("TopoJSON library not loaded.");
        return [];
      }    
      var t = vg.isObject(data) ? data : JSON.parse(data),
          obj = [];
  
      if (format && format.feature) {
        obj = (obj = t.objects[format.feature])
          ? topojson.feature(t, obj).features
          : (vg.error("Invalid TopoJSON object: "+format.feature), []);
      } else if (format && format.mesh) {
        obj = (obj = t.objects[format.mesh])
          ? [topojson.mesh(t, t.objects[format.mesh])]
          : (vg.error("Invalid TopoJSON object: " + format.mesh), []);
      }
      else { vg.error("Missing TopoJSON feature or mesh parameter."); }
  
      return obj;
    };
    
    formats.treejson = function(data, format) {
      data = vg.isObject(data) ? data : JSON.parse(data);
      return vg.tree(data, format.children);
    };
    
    function parseValues(data, types) {
      var cols = vg.keys(types),
          p = cols.map(function(col) { return parsers[types[col]]; }),
          tree = vg.isTree(data);
      vg_parseArray(tree ? [data] : data, cols, p, tree);
    }
    
    function vg_parseArray(data, cols, p, tree) {
      var d, i, j, len, clen;
      for (i=0, len=data.length; i<len; ++i) {
        d = data[i];
        for (j=0, clen=cols.length; j<clen; ++j) {
          d[cols[j]] = p[j](d[cols[j]]);
        }
        if (tree && d.values) parseValues(d, cols, p, true);
      }
    }
  
    read.formats = formats;
    read.parse = parseValues;
    return read;
  })();vg.data.aggregate = function() {
    var groupby = [],
        fields = [],
        gaccess,
        faccess;
  
    var OPS = {
      "count": function() {},
  		"sum": function(c, s, x) { return s + x; },
  		"avg": function(c, s, x) { return s + (x-s)/c.count; },
  		"min": function(c, s, x) { return x < s ? x : s; },
  		"max": function(c, s, x) { return x > s ? x : s; }
  	};
  	OPS.min.init = function() { return +Infinity; }
  	OPS.max.init = function() { return -Infinity; }
  
  	function fkey(x) {
  		return x.op + "_" + x.field;
  	}
  
  	var cells = {};
  
    function cell(x) {
      // consider other key constructions...
      var k = gaccess.reduce(function(v,f) {
        return (v.push(f(x)), v);
      }, []).join("|");
      return cells[k] || (cells[k] = new_cell(x));
    }
  
    function new_cell(x) {
      var o = {};
      // dimensions
      for (var i=0, f; i<groupby.length; ++i) {
        o[groupby[i]] = gaccess[i](x);
      }
      // measures
      o.count = 0;
  		for (i=0; i<fields.length; ++i) {
  		  if (fields[i].op === "count") continue;
  		  var op = OPS[fields[i].op];
  			o[fkey(fields[i])] = op.init ? op.init() : 0;
  		}
      return o;
    }
  
    function aggregate(input) {
      var output = [], k;
  		var keys = fields.map(fkey);
  		var ops = fields.map(function(x) { return OPS[x.op]; });
  
      // compute aggregates
      input.forEach(function(x) {
        var c = cell(x);
  
  			// compute aggregates...
        c.count += 1;
  			for (var i=0; i<fields.length; ++i) {
  				c[keys[i]] = ops[i](c, c[keys[i]], faccess[i](x));
  			}
      });
      // collect output tuples
      var index = 0;
      for (k in cells) {
        output.push({index:index++, data:cells[k]});
      }
      cells = {}; // clear internal state
      return output;
    };
  
    aggregate.fields = function(f) {
      fields = vg.array(f);
      faccess = fields.map(function(x,i) {
        var xf = x.field;
        if (xf.indexOf("data.") === 0) {
          fields[i] = {op:x.op, field:xf.slice(5)};
        }
        return vg.accessor(xf);
      });
      return aggregate;
    };
  
    aggregate.groupby = function(f) {
      groupby = vg.array(f);
      gaccess = groupby.map(function(x,i) {
        if (x.indexOf("data.") === 0) {
          groupby[i] = x.slice(5);
        }
        return vg.accessor(x);
      });
      return aggregate;
    };
  
    return aggregate;
  };vg.data.array = function() {
    var fields = [];
     
    function array(data) {
      return data.map(function(d) {      
        var list = [];
        for (var i=0, len=fields.length; i<len; ++i) {
          list.push(fields[i](d));
        }
        return list;
      });
    }
    
    array.fields = function(fieldList) {
      fields = vg.array(fieldList).map(vg.accessor);
      return array;
    };
    
    return array;
  };vg.data.bin = function() {
  
    var field,
        accessor,
        setter,
        min = undefined,
        max = undefined,
        step = undefined,
        maxbins = 20,
        output = "bin";
  
    function compare(a, b) {
      return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
    }
  
    function bisectLeft(a, x, lo, hi) {
      if (arguments.length < 3) { lo = 0; }
      if (arguments.length < 4) { hi = a.length; }
      while (lo < hi) {
        var mid = lo + hi >>> 1;
        if (compare(a[mid], x) < 0) { lo = mid + 1; }
        else { hi = mid; }
      }
      return lo;
    }
  
    function bins(opt) {
      opt = opt || {};
  
      // determine range
      var maxb = opt.maxbins || 1024,
          base = opt.base || 10,
          div = opt.div || [5, 2],
          mins = opt.minstep || 0,
          logb = Math.log(base),
          level = Math.ceil(Math.log(maxb) / logb),
          min = opt.min,
          max = opt.max,
          span = max - min,
          step = Math.max(mins, Math.pow(base, Math.round(Math.log(span) / logb) - level)),
          nbins = Math.ceil(span / step),
          precision, v, i, eps;
  
      if (opt.step != null) {
        step = opt.step;
      } else if (opt.steps) {
        // if provided, limit choice to acceptable step sizes
        step = opt.steps[Math.min(
            opt.steps.length - 1,
            bisectLeft(opt.steps, span / maxb)
        )];
      } else {
        // increase step size if too many bins
        do {
          step *= base;
          nbins = Math.ceil(span / step);
        } while (nbins > maxb);
  
        // decrease step size if allowed
        for (i = 0; i < div.length; ++i) {
          v = step / div[i];
          if (v >= mins && span / v <= maxb) {
            step = v;
            nbins = Math.ceil(span / step);
          }
        }
      }
  
      // update precision, min and max
      v = Math.log(step);
      precision = v >= 0 ? 0 : ~~(-v / logb) + 1;
      eps = Math.pow(base, -precision - 1);
  
      // outer Math.min to remove some rounding errors:
      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
      };
    }
  
    function bin(input) {
      var opt = {
        min: min != null ? min : +Infinity,
        max: max != null ? max : -Infinity,
        step: step != null ? step : null,
        maxbins: maxbins
      };
      if (min == null || max == null) {
        input.forEach(function(d) {
          var v = accessor(d);
          if (min == null && v > opt.max) opt.max = v;
          if (max == null && v < opt.min) opt.min = v;
        });
      }
      var b = bins(opt);
      input.forEach(function(d) {
        var v = accessor(d);
        setter(d, b.start + b.step * ~~((v - b.start) / b.step));
      });
      return input;
    }
  
    bin.min = function(x) {
      min = x;
      return bin;
    };
  
    bin.max = function(x) {
      max = x;
      return bin;
    };
  
    bin.step = function(x) {
      step = x;
      return bin;
    };
  
    bin.maxbins = function(x) {
      maxbins = x;
      return bin;
    };
  
    bin.field = function(f) {
      field = f;
      accessor = vg.accessor(f);
      return bin;
    };
  
    bin.output = function(f) {
      output = f;
      setter = vg.mutator(f);
      return bin;
    };
  
    return bin;
  };vg.data.copy = function() {
    var from = vg.accessor("data"),
        fields = [],
        as = null;
    
    var copy = vg.data.mapper(function(d) {
      var src = from(d), i, len,
          source = fields,
          target = as || fields;
      for (i=0, len=fields.length; i<len; ++i) {
        d[target[i]] = src[fields[i]];
      }
      return d;
    });
  
    copy.from = function(field) {
      from = vg.accessor(field);
      return copy;
    };
    
    copy.fields = function(fieldList) {
      fields = vg.array(fieldList);
      return copy;
    };
    
    copy.as = function(fieldList) {
      as = vg.array(fieldList);
      return copy;
    };
  
    return copy;
  };vg.data.cross = function() {
    var other = null,
        nodiag = false,
        output = {left:"a", right:"b"};
  
    function cross(data) {
      var result = [],
          data2 = other || data,
          o, i, j, n = data.length;
  
      for (i=0; i<n; ++i) {
        for (j=0; j<n; ++j) {
          if (nodiag && i===j) continue;
          o = {};
          o[output.left] = data[i];
          o[output.right] = data2[j];
          result.push(o);
        }
      }
      return result;
    }
  
    cross["with"] = function(d) {
      other = d;
      return cross;
    };
    
    cross.diagonal = function(x) {
      nodiag = !x;
      return cross;
    };
  
    cross.output = function(map) {
      vg.keys(output).forEach(function(k) {
        if (map[k] !== undefined) { output[k] = map[k]; }
      });
      return cross;
    };
  
    return cross;
  };
  vg.data.facet = function() {
  
    var keys = [],
        sort = null;
  
    function facet(data) {
      var result = {
            key: "",
            keys: [],
            values: []
          },
          map = {},
          vals = result.values,
          obj, klist, kstr, len, i, j, k, kv, cmp;
  
      if (keys.length === 0) {
        // if no keys, skip collation step
        vals.push(obj = {
          key: "", keys: [], index: 0,
          values: sort ? data.slice() : data
        });
        if (sort) sort(obj.values);
        return result;
      }
  
      for (i=0, len=data.length; i<len; ++i) {
        for (k=0, klist=[], kstr=""; k<keys.length; ++k) {
          kv = keys[k](data[i]);
          klist.push(kv);
          kstr += (k>0 ? "|" : "") + String(kv);
        }
        obj = map[kstr];
        if (obj === undefined) {
          vals.push(obj = map[kstr] = {
            key: kstr,
            keys: klist,
            index: vals.length,
            values: []
          });
        }
        obj.values.push(data[i]);
      }
  
      if (sort) {
        for (i=0, len=vals.length; i<len; ++i) {
          sort(vals[i].values);
        }
      }
  
      return result;
    }
  
    facet.keys = function(k) {
      keys = vg.array(k).map(vg.accessor);
      return facet;
    };
  
    facet.sort = function(s) {
      sort = vg.data.sort().by(s);
      return facet;
    };
  
    return facet;
  };vg.data.filter = function() {
  
    var test = null;
  
    function filter(data) {
      return test ? data.filter(test) : data;
    }
    
    filter.test = function(func) {
      test = vg.isFunction(func) ? func : vg.parse.expr(func);
      return filter;
    };
  
    return filter;
  };vg.data.flatten = function() {
      
    function flatten(data) {
      return flat(data, []);
    }
    
    function flat(data, list) {
      if (data.values) {
        for (var i=0, n=data.values.length; i<n; ++i) {
          flat(data.values[i], list);
        }
      } else {
        list.push(data);
      }
      return list;
    }
    
    return flatten;
  };vg.data.fold = function() {
    var fields = [],
        accessors = [],
        output = {
          key: "key",
          value: "value"
        };
  
    function fold(data) {
      var values = [],
          item, i, j, n, m = fields.length;
  
      for (i=0, n=data.length; i<n; ++i) {
        item = data[i];
        for (j=0; j<m; ++j) {
          var o = {
            index: values.length,
            data: item.data
          };
          o[output.key] = fields[j];
          o[output.value] = accessors[j](item);
          values.push(o);
        }
      }
  
      return values;
    }  
  
    fold.fields = function(f) {
      fields = vg.array(f);
      accessors = fields.map(vg.accessor);
      return fold;
    };
  
    fold.output = function(map) {
      vg.keys(output).forEach(function(k) {
        if (map[k] !== undefined) {
          output[k] = map[k];
        }
      });
      return fold;
    };
  
    return fold;
  };vg.data.force = function() {
    var layout = d3.layout.force(),
        links = null,
        linkDistance = 20,
        linkStrength = 1,
        charge = -30,
        iterations = 500,
        size = ["width", "height"],
        params = [
          "friction",
          "theta",
          "gravity",
          "alpha"
        ];
  
    function force(data, db, group) {
      layout
        .size(vg.data.size(size, group))
        .nodes(data);
  
      if (links && db[links]) {
        layout.links(db[links]);
      }
  
      layout.start();
      for (var i=0; i<iterations; ++i) {
        layout.tick();
      }
      layout.stop();
    
      return data;
    }
  
    force.links = function(dataSetName) {
      links = dataSetName;
      return force;
    };
  
    force.size = function(sz) {
      size = sz;
      return force;
    };
  
    force.linkDistance = function(field) {
      linkDistance = typeof field === 'number'
        ? field
        : vg.accessor(field);
      layout.linkDistance(linkDistance);
      return force;
    };
  
    force.linkStrength = function(field) {
      linkStrength = typeof field === 'number'
        ? field
        : vg.accessor(field);
      layout.linkStrength(linkStrength);
      return force;
    };
  
    force.charge = function(field) {
      charge = typeof field === 'number'
        ? field
        : vg.accessor(field);
      layout.charge(charge);
      return force;
    };
  
    force.iterations = function(iter) {
      iterations = iter;
      return force;
    };
  
    params.forEach(function(name) {
      force[name] = function(x) {
        layout[name](x);
        return force;
      }
    });
  
    return force;
  };
  
  vg.data.force.dependencies = ["links"];vg.data.formula = (function() {
    
    return function() {
      var field = null,
          expr = vg.identity;
    
      var formula = vg.data.mapper(function(d, i, list) {
        if (field) d[field] = expr.call(null, d, i, list);
        return d;
      });
  
      formula.field = function(name) {
        field = name;
        return formula;
      };
    
      formula.expr = function(func) {
        expr = vg.isFunction(func) ? func : vg.parse.expr(func);
        return formula;
      };
  
      return formula;
    };
  })();vg.data.geo = (function() {
    var params = [
      "center",
      "scale",
      "translate",
      "rotate",
      "precision",
      "clipAngle"
    ];
  
    function geo() {
      var opt = {},
          projection = "mercator",
          func = d3.geo[projection](),
          lat = vg.identity,
          lon = vg.identity,
          output = {
            "x": "x",
            "y": "y"
          };
      
      var map = vg.data.mapper(function(d) {
        var ll = [lon(d), lat(d)],
            xy = func(ll);
        d[output.x] = xy[0];
        d[output.y] = xy[1];
        return d;
      });
  
      map.func = function() {
        return func;
      };
          
      map.projection = function(p) {
        if (projection !== p) {
          projection = p;
          func = d3.geo[projection]();
          for (var name in opt) {
            func[name](opt[name]);
          }
        }
        return map;
      };
  
      params.forEach(function(name) {
        map[name] = function(x) {
          opt[name] = x;
          func[name](x);
          return map;
        }
      });
      
      map.lon = function(field) {
        lon = vg.accessor(field);
        return map;
      };
  
      map.lat = function(field) {
        lat = vg.accessor(field);
        return map;
      };
      
      map.output = function(map) {
        vg.keys(output).forEach(function(k) {
          if (map[k] !== undefined) {
            output[k] = map[k];
          }
        });
        return map;
      };
      
      
      return map;
    };
    
    geo.params = params;
    return geo;
  })();vg.data.geopath = function() {
    var geopath = d3.geo.path().projection(d3.geo.mercator()),
        projection = "mercator",
        geojson = vg.identity,
        opt = {},
        output = {"path": "path"};
  
    var map = vg.data.mapper(function(d) {
      d[output.path] = geopath(geojson(d));
      return d;
    });
    
    map.projection = function(proj) {
      if (projection !== proj) {
        projection = proj;
        var p = d3.geo[projection]();
        for (var name in opt) {
          p[name](opt[name]);
        }
        geopath.projection(p);
      }
      return map;
    };
    
    vg.data.geo.params.forEach(function(name) {
      map[name] = function(x) {
        opt[name] = x;
        (geopath.projection())[name](x);
        return map;
      }
    });
     
    map.value = function(field) {
      geojson = vg.accessor(field);
      return map;
    };
  
    map.output = function(map) {
      vg.keys(output).forEach(function(k) {
        if (map[k] !== undefined) {
          output[k] = map[k];
        }
      });
      return map;
    };
  
    return map;
  };vg.data.link = function() {
    var shape = "line",
        source = vg.accessor("source"),
        target = vg.accessor("target"),
        tension = 0.2,
        output = {"path": "path"};
    
    function line(d) {
      var s = source(d),
          t = target(d);
      return "M" + s.x + "," + s.y 
           + "L" + t.x + "," + t.y;
    }
  
    function curve(d) {
      var s = source(d),
          t = target(d),
          dx = t.x - s.x,
          dy = t.y - s.y,
          ix = tension * (dx + dy),
          iy = tension * (dy - dx);
      return "M" + s.x + "," + s.y
           + "C" + (s.x+ix) + "," + (s.y+iy)
           + " " + (t.x+iy) + "," + (t.y-ix)
           + " " + t.x + "," + t.y;
    }
    
    function diagonalX(d) {
      var s = source(d),
          t = target(d),
          m = (s.x + t.x) / 2;
      return "M" + s.x + "," + s.y
           + "C" + m   + "," + s.y
           + " " + m   + "," + t.y
           + " " + t.x + "," + t.y;
    }
  
    function diagonalY(d) {
      var s = source(d),
          t = target(d),
          m = (s.y + t.y) / 2;
      return "M" + s.x + "," + s.y
           + "C" + s.x + "," + m
           + " " + t.x + "," + m
           + " " + t.x + "," + t.y;
    }
  
    var shapes = {
      line:      line,
      curve:     curve,
      diagonal:  diagonalX,
      diagonalX: diagonalX,
      diagonalY: diagonalY
    };
    
    function link(data) {
      var path = shapes[shape];
          
      data.forEach(function(d) {
        d[output.path] = path(d);
      });
      
      return data;
    }
  
    link.shape = function(val) {
      shape = val;
      return link;
    };
  
    link.tension = function(val) {
      tension = val;
      return link;
    };
    
    link.source = function(field) {
      source = vg.accessor(field);
      return link;
    };
    
    link.target = function(field) {
      target = vg.accessor(field);
      return link;
    };
    
    link.output = function(map) {
      vg.keys(output).forEach(function(k) {
        if (map[k] !== undefined) {
          output[k] = map[k];
        }
      });
      return link;
    };
    
    return link;
  };vg.data.pie = function() {
    var one = function() { return 1; },
        value = one,
        start = 0,
        end = 2 * Math.PI,
        sort = false,
        output = {
          "startAngle": "startAngle",
          "endAngle": "endAngle",
          "midAngle": "midAngle"
        };
  
    function pie(data) {
      var values = data.map(function(d, i) { return +value(d); }),
          a = start,
          k = (end - start) / d3.sum(values),
          index = d3.range(data.length);
      
      if (sort) {
        index.sort(function(a, b) {
          return values[a] - values[b];
        });
      }
      
      index.forEach(function(i) {
        var d;
        data[i].value = (d = values[i]);
        data[i][output.startAngle] = a;
        data[i][output.midAngle] = (a + 0.5 * d * k);
        data[i][output.endAngle] = (a += d * k);
      });
      
      return data;
    }
  
    pie.sort = function(b) {
      sort = b;
      return pie;
    };
         
    pie.value = function(field) {
      value = field ? vg.accessor(field) : one;
      return pie;
    };
    
    pie.startAngle = function(startAngle) {
      start = Math.PI * startAngle / 180;
      return pie;
    };
    
    pie.endAngle = function(endAngle) {
      end = Math.PI * endAngle / 180;
      return pie;
    };
  
    pie.output = function(map) {
      vg.keys(output).forEach(function(k) {
        if (map[k] !== undefined) {
          output[k] = map[k];
        }
      });
      return pie;
    };
  
    return pie;
  };vg.data.slice = function() {
    var by = null,
        field = vg.accessor("data");
  
    function slice(data) {
      data = vg.values(data);
      
      if (by === "min") {
        data = [data[vg.minIndex(data, field)]];
      } else if (by === "max") {
        data = [data[vg.maxIndex(data, field)]];
      } else if (by === "median") {
        var list = data.slice().sort(function(a,b) {
          a = field(a); b = field(b);
          return a < b ? -1 : a > b ? 1 : 0;
        });
        data = [data[~~(list.length/2)]];
      } else {
        var idx = vg.array(by);
        data = data.slice(idx[0], idx[1]);
      }
      return data;
    }
    
    slice.by = function(x) {
      by = x;
      return slice;
    };
    
    slice.field = function(f) {
      field = vg.accessor(f);
      return slice;
    };
  
    return slice;
  };vg.data.sort = function() {
    var by = null;
  
    function sort(data) {
      data = (vg.isArray(data) ? data : data.values || []);
      data.sort(by);
      for (var i=0, n=data.length; i<n; ++i) data[i].index = i; // re-index
      return data;
    }
    
    sort.by = function(s) {
      by = vg.comparator(s);
      return sort;
    };
  
    return sort;
  };vg.data.stack = function() {
    var layout = d3.layout.stack(),
        point = vg.accessor("index"),
        height = vg.accessor("data"),
        params = ["offset", "order"],
        output = {
          "y0": "y2",
          "y1": "y",
          "cy": "cy"
        };
  
    function stack(data) {
      var out_y0 = output["y0"],
          out_y1 = output["y1"],
          out_cy = output["cy"];
      
      var series = stacks(data);
      if (series.length === 0) return data;
      
      layout.out(function(d, y0, y) {
        if (d.datum) {
          d.datum[out_y0] = y0;
          d.datum[out_y1] = y + y0;
          d.datum[out_cy] = y0 + y/2;
        }
      })(series);
      
      return data;
    }
    
    function stacks(data) {
      var values = vg.values(data),
          points = [], series = [],
          a, i, n, j, m, k, p, v, x;
  
      // exit early if no data
      if (values.length === 0) return series;
  
      // collect and sort data points
      for (i=0, n=values.length; i<n; ++i) {
        a = vg.values(values[i]);
        for (j=0, m=a.length; j<m; ++j) {
          points.push({x:point(a[j]), y:height(a[j]), z:i, datum:a[j]});
        }
        series.push([]);
      }
      points.sort(function(a,b) {
        return a.x<b.x ? -1 : a.x>b.x ? 1 : (a.z<b.z ? -1 : a.z>b.z ? 1 : 0);
      });
  
      // emit data series for stack layout
      for (x=points[0].x, i=0, j=0, k=0, n=points.length; k<n; ++k) {
        p = points[k];    
        if (p.x !== x) {
          while (i < series.length) series[i++].push({x:j, y:0});
          x = p.x; i = 0; j += 1;
        }
        while (p.z > i) series[i++].push({x:j, y:0});
        p.x = j;
        series[i++].push(p);
      }
      while (i < series.length) series[i++].push({x:j, y:0});
  
      return series;
    }
         
    stack.point = function(field) {
      point = vg.accessor(field);
      return stack;
    };
    
    stack.height = function(field) {
      height = vg.accessor(field);
      return stack;
    };
  
    params.forEach(function(name) {
      stack[name] = function(x) {
        layout[name](x);
        return stack;
      }
    });
  
    stack.output = function(map) {
      d3.keys(output).forEach(function(k) {
        if (map[k] !== undefined) {
          output[k] = map[k];
        }
      });
      return stack;
    };
  
    return stack;
  };vg.data.stats = function() {
    var value = vg.accessor("data"),
        assign = false,
        median = false,
        output = {
          "count":    "count",
          "min":      "min",
          "max":      "max",
          "sum":      "sum",
          "mean":     "mean",
          "variance": "variance",
          "stdev":    "stdev",
          "median":   "median"
        };
  
    function reduce(data) {
      var min = +Infinity,
          max = -Infinity,
          sum = 0,
          mean = 0,
          M2 = 0,
          i, len, v, delta;
  
      var list = (vg.isArray(data) ? data : data.values || []).map(value);
  
      // compute aggregates
      for (i=0, len=list.length; i<len; ++i) {
        v = list[i];
        if (v < min) min = v;
        if (v > max) max = v;
        sum += v;
        delta = v - mean;
        mean = mean + delta / (i+1);
        M2 = M2 + delta * (v - mean);
      }
      M2 = M2 / (len - 1);
  
      var o = vg.isArray(data) ? {} : data;
      if (median) {
        list.sort(vg.numcmp);
        i = list.length >> 1;
        o[output.median] = list.length % 2
          ? list[i]
          : (list[i-1] + list[i])/2;
      }
      o[output.count] = len;
      o[output.min] = min;
      o[output.max] = max;
      o[output.sum] = sum;
      o[output.mean] = mean;
      o[output.variance] = M2;
      o[output.stdev] = Math.sqrt(M2);
  
      if (assign) {
        list = (vg.isArray(data) ? data : data.values);
        v = {};
        v[output.count] = len;
        v[output.min] = min;
        v[output.max] = max;
        v[output.sum] = sum;
        v[output.mean] = mean;
        v[output.variance] = M2;
        v[output.stdev] = Math.sqrt(M2);
        if (median) v[output.median] = o[output.median];
        for (i=0, len=list.length; i<len; ++i) {
          list[i].stats = v;
        }
        if (vg.isArray(data)) o = list;
      }
  
      return o;
    }
  
    function stats(data) {
      if (vg.isArray(data)) {
        return reduce(data);
      } else {
        return (data.values || []).map(reduce);
      }
    }
  
    stats.median = function(bool) {
      median = bool || false;
      return stats;
    };
  
    stats.value = function(field) {
      value = vg.accessor(field);
      return stats;
    };
  
    stats.assign = function(b) {
      assign = b;
      return stats;
    };
  
    stats.output = function(map) {
      vg.keys(output).forEach(function(k) {
        if (map[k] !== undefined) {
          output[k] = map[k];
        }
      });
      return stats;
    };
  
    return stats;
  };vg.data.treemap = function() {
    var layout = d3.layout.treemap()
                   .children(function(d) { return d.values; }),
        value = vg.accessor("data"),
        size = ["width", "height"],
        params = ["round", "sticky", "ratio", "padding"],
        output = {
          "x": "x",
          "y": "y",
          "dx": "width",
          "dy": "height"
        };
  
    function treemap(data, db, group) {
      data = layout
        .size(vg.data.size(size, group))
        .value(value)
        .nodes(vg.isTree(data) ? data : {values: data});
      
      var keys = vg.keys(output),
          len = keys.length;
      data.forEach(function(d) {
        var key, val;
        for (var i=0; i<len; ++i) {
          key = keys[i];
          if (key !== output[key]) {
            val = d[key];
            delete d[key];
            d[output[key]] = val;
          }
        }
      });
      
      return data;
    }
  
    treemap.size = function(sz) {
      size = sz;
      return treemap;
    };
  
    treemap.value = function(field) {
      value = vg.accessor(field);
      return treemap;
    };
  
    params.forEach(function(name) {
      treemap[name] = function(x) {
        layout[name](x);
        return treemap;
      }
    });
  
    treemap.output = function(map) {
      vg.keys(output).forEach(function(k) {
        if (map[k] !== undefined) {
          output[k] = map[k];
        }
      });
      return treemap;
    };
  
    return treemap;
  };vg.data.truncate = function() {
    var value = vg.accessor("data"),
        as = "truncate",
        position = "right",
        ellipsis = "...",
        wordBreak = true,
        limit = 100;
    
    var truncate = vg.data.mapper(function(d) {
      var text = vg.truncate(value(d), limit, position, wordBreak, ellipsis);
      return (d[as] = text, d);
    });
  
    truncate.value = function(field) {
      value = vg.accessor(field);
      return truncate;
    };
    
    truncate.output = function(field) {
      as = field;
      return truncate;
    };
  
    truncate.limit = function(len) {
      limit = +len;
      return truncate;
    };
    
    truncate.position = function(pos) {
      position = pos;
      return truncate;
    };
  
    truncate.ellipsis = function(str) {
      ellipsis = str+"";
      return truncate;
    };
  
    truncate.wordbreak = function(b) {
      wordBreak = !!b;
      return truncate;
    };
  
    return truncate;
  };vg.data.unique = function() {
  
    var field = null,
        as = "field";
  
    function unique(data) {
      return vg.unique(data, field)
        .map(function(x) {
          var o = {};
          o[as] = x;
          return o;
        });
    }
    
    unique.field = function(f) {
      field = vg.accessor(f);
      return unique;
    };
    
    unique.as = function(x) {
      as = x;
      return unique;
    };
  
    return unique;
  };vg.data.window = function() {
  
    var size = 2,
        step = 1;
    
    function win(data) {
      data = vg.isArray(data) ? data : data.values || [];
      var runs = [], i, j, n=data.length-size, curr;
      for (i=0; i<=n; i+=step) {
        for (j=0, curr=[]; j<size; ++j) curr.push(data[i+j]);
        runs.push({key: i, values: curr});
      }
      return {values: runs};
    }
    
    win.size = function(n) {
      size = n;
      return win;
    };
    
    win.step = function(n) {
      step = n;
      return win;
    };
  
    return win;
  };vg.data.wordcloud = function() {
    var layout = d3.layout.cloud().size([900, 500]),
        text = vg.accessor("data"),
        size = ["width", "height"],
        fontSize = function() { return 14; },
        rotate = function() { return 0; },
        params = ["font", "fontStyle", "fontWeight", "padding"];
    
    var output = {
      "x": "x",
      "y": "y",
      "size": "fontSize",
      "font": "font",
      "rotate": "angle"
    };
    
    function cloud(data, db, group) {
      function finish(tags, bounds) {
        var size = layout.size(),
            dx = size[0] / 2,
            dy = size[1] / 2,
            keys = vg.keys(output),
            key, d, i, n, k, m = keys.length;
  
        // sort data to match wordcloud order
        data.sort(function(a,b) {
          return fontSize(b) - fontSize(a);
        });
  
        for (i=0, n=tags.length; i<n; ++i) {
          d = data[i];
          for (k=0; k<m; ++k) {
            key = keys[k];
            d[output[key]] = tags[i][key];
            if (key === "x") d[output.x] += dx;
            if (key === "y") d[output.y] += dy;
          }
        }
      }
      
      layout
        .size(vg.data.size(size, group))
        .text(text)
        .fontSize(fontSize)
        .rotate(rotate)
        .words(data)
        .on("end", finish)
        .start();
      return data;
    }
  
    cloud.text = function(field) {
      text = vg.accessor(field);
      return cloud;
    };
    
    cloud.size = function(sz) {
      size = sz;
      return cloud;
    };
           
    cloud.fontSize = function(field) {
      fontSize = vg.accessor(field);
      return cloud;
    };
    
    cloud.rotate = function(x) {
      var v;
      if (vg.isObject(x) && !Array.isArray(x)) {
        if (x.random !== undefined) {
          v = (v = x.random) ? vg.array(v) : [0];
          rotate = function() {
            return v[~~(Math.random()*v.length-0.00001)];
          };
        } else if (x.alternate !== undefined) {
          v = (v = x.alternate) ? vg.array(v) : [0];
          rotate = function(d, i) {
            return v[i % v.length];
          };
        }
      } else {
        rotate = vg.accessor(field);
      }
      return cloud;
    };
  
    params.forEach(function(name) {
      cloud[name] = function(x) {
        layout[name](x);
        return cloud;
      }
    });
  
    cloud.output = function(map) {
      vg.keys(output).forEach(function(k) {
        if (map[k] !== undefined) {
          output[k] = map[k];
        }
      });
      return cloud;
    };
    
    return cloud;
  };vg.data.zip = function() {
    var z = null,
        as = "zip",
        key = vg.accessor("data"),
        defaultValue = undefined,
        withKey = null;
  
    function zip(data, db) {
      var zdata = db[z], zlen = zdata.length, v, d, i, len, map;
      
      if (withKey) {
        map = {};
        zdata.forEach(function(s) { map[withKey(s)] = s; });
      }
      
      for (i=0, len=data.length; i<len; ++i) {
        d = data[i];
        d[as] = map
          ? ((v=map[key(d)]) != null ? v : defaultValue)
          : zdata[i % zlen];
      }
      
      return data;
    }
  
    zip["with"] = function(d) {
      z = d;
      return zip;
    };
    
    zip["default"] = function(d) {
      defaultValue = d;
      return zip;
    };
  
    zip.as = function(name) {
      as = name;
      return zip;
    };
  
    zip.key = function(k) {
      key = vg.accessor(k);
      return zip;
    };
  
    zip.withKey = function(k) {
      withKey = vg.accessor(k);
      return zip;
    };
  
    return zip;
  };
  
  vg.data.zip.dependencies = ["with"];vg.parse = {};vg.parse.axes = (function() {
    var ORIENT = {
      "x":      "bottom",
      "y":      "left",
      "top":    "top",
      "bottom": "bottom",
      "left":   "left",
      "right":  "right"
    };
  
    function axes(spec, axes, scales) {
      (spec || []).forEach(function(def, index) {
        axes[index] = axes[index] || vg.scene.axis();
        axis(def, index, axes[index], scales);
      });
    };
  
    function axis(def, index, axis, scales) {
      // axis scale
      if (def.scale !== undefined) {
        axis.scale(scales[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 : vg.config.axis.titleOffset);
      // axis values
      axis.tickValues(def.values || null);
      // axis label formatting
      axis.tickFormat(def.format || null);
      // axis tick subdivision
      axis.tickSubdivide(def.subdivide || 0);
      // axis tick padding
      axis.tickPadding(def.tickPadding || vg.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 = vg.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);
      }
  
      // tick arguments
      if (def.ticks != null) {
        var ticks = vg.isArray(def.ticks) ? def.ticks : [def.ticks];
        axis.ticks.apply(axis, ticks);
      } else {
        axis.ticks(vg.config.axis.ticks);
      }
  
      // style properties
      var p = def.properties;
      if (p && p.ticks) {
        axis.majorTickProperties(p.majorTicks
          ? vg.extend({}, p.ticks, p.majorTicks) : p.ticks);
        axis.minorTickProperties(p.minorTicks
          ? vg.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 || {});
    }
  
    return axes;
  })();
  vg.parse.data = function(spec, callback) {
    var model = {
      defs: spec,
      load: {},
      flow: {},
      deps: {},
      source: {},
      sorted: null
    };
  
    var count = 0;
    
    function load(d) {
      return function(error, data) {
        if (error) {
          vg.error("LOADING FAILED: " + d.url);
        } else {
          model.load[d.name] = vg.data.read(data.toString(), d.format);
        }
        if (--count === 0) callback();
      }
    }
    
    // process each data set definition
    (spec || []).forEach(function(d) {
      if (d.url) {
        count += 1;
        vg.data.load(d.url, load(d)); 
      } else if (d.values) {
        model.load[d.name] = vg.data.read(d.values, d.format);
      } else if (d.source) {
        (model.source[d.source] || (model.source[d.source] = [])).push(d.name);
      }
      
      if (d.transform) {
        var flow = vg.parse.dataflow(d);
        model.flow[d.name] = flow;
        flow.dependencies.forEach(function(dep) {
          (model.deps[dep] || (model.deps[dep] = [])).push(d.name);
        });
      }
    });
    
    // topological sort by dependencies
    var names = (spec || []).map(vg.accessor("name")),
        order = [], v = {}, n;
    function visit(n) {
      if (v[n] === 1) return; // not a DAG!
      if (!v[n]) {
        v[n] = 1;
        (model.source[n] || []).forEach(visit);
        (model.deps[n] || []).forEach(visit);
        v[n] = 2;
        order.push(n);
      }
    }
    while (names.length) { if (v[n=names.pop()] !== 2) visit(n); }
    model.sorted = order.reverse();
    
    if (count === 0) setTimeout(callback, 1);
    return model;
  };vg.parse.dataflow = function(def) {
    var tx = (def.transform || []).map(vg.parse.transform),
        df = tx.length
          ? function(data, db, group) {
              return tx.reduce(function(d,t) { return t(d,db,group); }, data);
            }
          : vg.identity;
    df.transforms = tx;
    df.dependencies = vg.keys((def.transform || [])
      .reduce(function(map, tdef) {
        var deps = vg.data[tdef.type].dependencies;
        if (deps) deps.forEach(function(d) {
          if (tdef[d]) map[tdef[d]] = 1;
        });
        return map;
      }, {}));
    return df;
  };vg.parse.expr = (function() {
  
    var CONSTANT = {
    	"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"
    };
  
    var FUNCTION = {
    	"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"
    };
  
    var lexer = /([\"\']|[\=\<\>\~\&\|\?\:\+\-\/\*\%\!\^\,\;\[\]\{\}\(\) ]+)/;
  
    return function(x) {
      if (vg.config.safeMode) {
        vg.error("Safe mode: Expression parsing disabled.");
        return vg.true;
      }
  
      var tokens = x.split(lexer),
          t, v, i, n, sq, dq;
  
      for (sq=0, dq=0, i=0, n=tokens.length; i<n; ++i) {
        var t = tokens[i];
        if (t==="'") { if (!dq) sq = !sq; continue; }
        if (t==='"') { if (!sq) dq = !dq; continue; }
        if (dq || sq) continue;
        if (CONSTANT[t]) {
          tokens[i] = CONSTANT[t];
        }
        if (FUNCTION[t] && (v=tokens[i+1]) && v[0]==="(") {
          tokens[i] = FUNCTION[t];
        }
      }
  
      return Function("d", "index", "data", "return ("+tokens.join("")+");");
    };
  
  })();vg.parse.legends = (function() {
  
    function legends(spec, legends, scales) {
      (spec || []).forEach(function(def, index) {
        legends[index] = legends[index] || vg.scene.legend();
        legend(def, index, legends[index], scales);
      });
    };
  
    function legend(def, index, legend, scales) {
      // legend scales
      legend.size  (def.size   ? scales[def.size]   : null);
      legend.shape (def.shape  ? scales[def.shape]  : null);
      legend.fill  (def.fill   ? scales[def.fill]   : null);
      legend.stroke(def.stroke ? scales[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 || {});
    }
    
    return legends;
  })();vg.parse.mark = function(mark) {
    var props = mark.properties,
        group = mark.marks;
  
    // parse mark property definitions
    vg.keys(props).forEach(function(k) {
      props[k] = vg.parse.properties(mark.type, props[k]);
    });
  
    // parse delay function
    if (mark.delay) {
      mark.delay = vg.parse.properties(mark.type, {delay: mark.delay});
    }
  
    // parse mark data definition
    if (mark.from) {
      var name = mark.from.data,
          tx = vg.parse.dataflow(mark.from);
      mark.from = function(db, group, parentData) {
        var data = vg.scene.data(name ? db[name] : null, parentData);
        return tx(data, db, group);
      };
    }
  
    // recurse if group type
    if (group) {
      mark.marks = group.map(vg.parse.mark);
    }
      
    return mark;
  };vg.parse.marks = function(spec, width, height) {
    return {
      type: "group",
      width: width,
      height: height,
      scales: spec.scales || [],
      axes: spec.axes || [],
      legends: spec.legends || [],
      marks: (spec.marks || []).map(vg.parse.mark)
    };
  };vg.parse.padding = function(pad) {
    if (pad == null) return "auto";
    else if (vg.isString(pad)) return pad==="strict" ? "strict" : "auto";
    else if (vg.isObject(pad)) return pad;
    var p = vg.isNumber(pad) ? pad : 20;
    return {top:p, left:p, right:p, bottom:p};
  };
  vg.parse.properties = (function() {
    function compile(mark, spec) {
      var code = "",
          names = vg.keys(spec),
          i, len, name, ref, vars = {};
          
      code += "var o = trans ? {} : item;\n"
      
      for (i=0, len=names.length; i<len; ++i) {
        ref = spec[name = names[i]];
        code += (i > 0) ? "\n  " : "  ";
        code += "o."+name+" = "+valueRef(name, ref)+";";
        vars[name] = true;
      }
      
      if (vars.x2) {
        if (vars.x) {
          code += "\n  if (o.x > o.x2) { "
                + "var t = o.x; o.x = o.x2; o.x2 = t; };";
          code += "\n  o.width = (o.x2 - o.x);";
        } else if (vars.width) {
          code += "\n  o.x = (o.x2 - o.width);";
        } else {
          code += "\n  o.x = o.x2;"
        }
      }
      if (vars.xc) {
        if (vars.width) {
          code += "\n  o.x = (o.xc - o.width/2);";
        } else {
          code += "\n  o.x = o.xc;"
        }
      }
  
      if (vars.y2) {
        if (vars.y) {
          code += "\n  if (o.y > o.y2) { "
                + "var t = o.y; o.y = o.y2; o.y2 = t; };";
          code += "\n  o.height = (o.y2 - o.y);";
        } else if (vars.height) {
          code += "\n  o.y = (o.y2 - o.height);";
        } else {
          code += "\n  o.y = o.y2;"
        }
      }
      if (vars.yc) {
        if (vars.height) {
          code += "\n  o.y = (o.yc - o.height/2);";
        } else {
          code += "\n  o.y = o.yc;"
        }
      }
      
      if (hasPath(mark, vars)) code += "\n  item.touch();";
      code += "\n  if (trans) trans.interpolate(item, o);";
  
      try {
        return Function("item", "group", "trans", code);
      } catch (e) {
        vg.error(e);
        vg.log(code);
      }
    }
    
    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));
    }
    
    var GROUP_VARS = {
      "width": 1,
      "height": 1,
      "mark.group.width": 1,
      "mark.group.height": 1
    };
  
    function valueRef(name, ref) {
      if (ref == null) return null;
      var isColor = name==="fill" || name==="stroke";
  
      if (isColor) {
        if (ref.c) {
          return colorRef("hcl", ref.h, ref.c, ref.l);
        } else if (ref.h || ref.s) {
          return colorRef("hsl", ref.h, ref.s, ref.l);
        } else if (ref.l || ref.a) {
          return colorRef("lab", ref.l, ref.a, ref.b);
        } else if (ref.r || ref.g || ref.b) {
          return colorRef("rgb", ref.r, ref.g, ref.b);
        }
      }
  
      // initialize value
      var val = "item.datum.data";
      if (ref.value !== undefined) {
        val = vg.str(ref.value);
      }
  
      // get field reference for enclosing group
      if (ref.group != null) {
        var grp = "group.datum";
        if (vg.isString(ref.group)) {
          grp = GROUP_VARS[ref.group]
            ? "group." + ref.group
            : "group.datum["+vg.field(ref.group).map(vg.str).join("][")+"]";
        }
      }
  
      // get data field value
      if (ref.field != null) {
        if (vg.isString(ref.field)) {
          val = "item.datum["+vg.field(ref.field).map(vg.str).join("][")+"]";
          if (ref.group != null) { val = "this.accessor("+val+")("+grp+")"; }
        } else {
          val = "this.accessor(group.datum["
              + vg.field(ref.field.group).map(vg.str).join("][")
              + "])(item.datum.data)";
        }
      } else if (ref.group != null) {
        val = grp;
      }
  
      // run through scale function
      if (ref.scale != null) {
        var scale = vg.isString(ref.scale)
          ? vg.str(ref.scale)
          : (ref.scale.group ? "group" : "item")
            + ".datum[" + vg.str(ref.scale.group || ref.scale.field) + "]";
        scale = "group.scales[" + scale + "]";
        val = scale + (ref.band ? ".rangeBand()" : "("+val+")");
      }
      
      // multiply, offset, return value
      val = "(" + (ref.mult?(vg.number(ref.mult)+" * "):"") + val + ")"
        + (ref.offset ? " + " + vg.number(ref.offset) : "");
      return val;
    }
    
    function colorRef(type, x, y, z) {
      var xx = x ? valueRef("", x) : vg.config.color[type][0],
          yy = y ? valueRef("", y) : vg.config.color[type][1],
          zz = z ? valueRef("", z) : vg.config.color[type][2];
      return "(this.d3." + type + "(" + [xx,yy,zz].join(",") + ') + "")';
    }
    
    return compile;
  })();vg.parse.scales = (function() {
    var LINEAR = "linear",
        ORDINAL = "ordinal",
        LOG = "log",
        POWER = "pow",
        TIME = "time",
        QUANTILE = "quantile",
        GROUP_PROPERTY = {width: 1, height: 1};
  
    function scales(spec, scales, db, group) {
      return (spec || []).reduce(function(o, def) {
        var name = def.name, prev = name + ":prev";
        o[name] = scale(def, o[name], db, group);
        o[prev] = o[prev] || o[name];
        return o;
      }, scales || {});
    }
  
    function scale(def, scale, db, group) {
      var s = instance(def, scale),
          m = s.type===ORDINAL ? ordinal : quantitative,
          rng = range(def, group),
          data = vg.values(group.datum);
  
      m(def, s, rng, db, data);
      return s;
    }
  
    function instance(def, scale) {
      var type = def.type || LINEAR;
      if (!scale || type !== scale.type) {
        var ctor = vg.config.scale[type] || d3.scale[type];
        if (!ctor) vg.error("Unrecognized scale type: " + type);
        (scale = ctor()).type = scale.type || type;
        scale.scaleName = def.name;
      }
      return scale;
    }
  
    function ordinal(def, scale, rng, db, data) {
      var dataDrivenRange = false,
          pad = def.padding || 0,
          outer = def.outerPadding || 0,
          domain, sort, str, refs;
  
      // range pre-processing for data-driven ranges
      if (vg.isObject(def.range) && !vg.isArray(def.range)) {
        dataDrivenRange = true;
        refs = def.range.fields || vg.array(def.range);
        rng = extract(refs, db, data);
      }
  
      // domain
      sort = def.sort && !dataDrivenRange;
      domain = domainValues(def, db, data, sort);
      if (domain) scale.domain(domain);
  
      // width-defined range
      if (def.bandWidth) {
        var bw = def.bandWidth,
            len = domain.length,
            start = rng[0] || 0,
            space = def.points ? (pad*bw) : (pad*bw*(len-1) + 2*outer);
        rng = [start, start + (bw * len + space)];
      }
  
      // range
      str = typeof rng[0] === 'string';
      if (str || rng.length > 2 || rng.length===1 || dataDrivenRange) {
        scale.range(rng); // color or shape values
      } else if (def.points) {
        scale.rangePoints(rng, pad);
      } else if (def.round || def.round===undefined) {
        scale.rangeRoundBands(rng, pad, outer);
      } else {
        scale.rangeBands(rng, pad, outer);
      }
    }
  
    function quantitative(def, scale, rng, db, data) {
      var domain, interval;
  
      // domain
      domain = (def.type === QUANTILE)
        ? domainValues(def, db, data, false)
        : domainMinMax(def, db, data);
      scale.domain(domain);
  
      // range
      // vertical scales should flip by default, so use XOR here
      if (def.range === "height") rng = rng.reverse();
      scale[def.round && scale.rangeRound ? "rangeRound" : "range"](rng);
  
      if (def.exponent && def.type===POWER) scale.exponent(def.exponent);
      if (def.clamp) scale.clamp(true);
      if (def.nice) {
        if (def.type === TIME) {
          interval = d3.time[def.nice];
          if (!interval) vg.error("Unrecognized interval: " + interval);
          scale.nice(interval);
        } else {
          scale.nice();
        }
      }
    }
    
    function extract(refs, db, data) {
      return refs.reduce(function(values, r) {        
        var dat = vg.values(db[r.data] || data),
            get = vg.accessor(vg.isString(r.field)
                ? r.field : "data." + vg.accessor(r.field.group)(data));
        return vg.unique(dat, get, values);
      }, []);
    }
    
    function domainValues(def, db, data, sort) {
      var domain = def.domain, values, refs;
      if (vg.isArray(domain)) {
        values = sort ? domain.slice() : domain;
      } else if (vg.isObject(domain)) {
        refs = domain.fields || vg.array(domain);
        values = extract(refs, db, data);
      }
      if (values && sort) values.sort(vg.cmp);
      return values;
    }
    
    function domainMinMax(def, db, data) {
      var domain = [null, null], refs, z;
      
      function extract(ref, min, max, z) {
        var dat = vg.values(db[ref.data] || data);
        var fields = vg.array(ref.field).map(function(f) {
          return vg.isString(f) ? f
            : "data." + vg.accessor(f.group)(data);
        });
        
        fields.forEach(function(f,i) {
          f = vg.accessor(f);
          if (min) domain[0] = d3.min([domain[0], d3.min(dat, f)]);
          if (max) domain[z] = d3.max([domain[z], d3.max(dat, f)]);
        });
      }
  
      if (def.domain !== undefined) {
        if (vg.isArray(def.domain)) {
          domain = def.domain.slice();
        } else if (vg.isObject(def.domain)) {
          refs = def.domain.fields || vg.array(def.domain);
          refs.forEach(function(r) { extract(r,1,1,1); });
        } else {
          domain = def.domain;
        }
      }
      z = domain.length - 1;
      if (def.domainMin !== undefined) {
        if (vg.isObject(def.domainMin)) {
          domain[0] = null;
          refs = def.domainMin.fields || vg.array(def.domainMin);
          refs.forEach(function(r) { extract(r,1,0,z); });
        } else {
          domain[0] = def.domainMin;
        }
      }
      if (def.domainMax !== undefined) {
        if (vg.isObject(def.domainMax)) {
          domain[z] = null;
          refs = def.domainMax.fields || vg.array(def.domainMax);
          refs.forEach(function(r) { extract(r,0,1,z); });
        } else {
          domain[z] = def.domainMax;
        }
      }
      if (def.type !== LOG && def.type !== TIME && (def.zero || def.zero===undefined)) {
        domain[0] = Math.min(0, domain[0]);
        domain[z] = Math.max(0, domain[z]);
      }
      return domain;
    }
  
    function range(def, group) {
      var rng = [null, null];
  
      if (def.range !== undefined) {
        if (typeof def.range === 'string') {
          if (GROUP_PROPERTY[def.range]) {
            rng = [0, group[def.range]];
          } else if (vg.config.range[def.range]) {
            rng = vg.config.range[def.range];
          } else {
            vg.error("Unrecogized range: "+def.range);
            return rng;
          }
        } else if (vg.isArray(def.range)) {
          rng = def.range;
        } else if (vg.isObject(def.range)) {
          return null; // early exit
        } else {
          rng = [0, def.range];
        }
      }
      if (def.rangeMin !== undefined) {
        rng[0] = def.rangeMin;
      }
      if (def.rangeMax !== undefined) {
        rng[rng.length-1] = def.rangeMax;
      }
      
      if (def.reverse !== undefined) {
        var rev = def.reverse;
        if (vg.isObject(rev)) {
          rev = vg.accessor(rev.field)(group.datum);
        }
        if (rev) rng = rng.reverse();
      }
      
      return rng;
    }
  
    return scales;
  })();
  vg.parse.spec = function(spec, callback, viewFactory) {
    
    viewFactory = viewFactory || vg.ViewFactory;
    
    function parse(spec) {
      // protect against subsequent spec modification
      spec = vg.duplicate(spec);
      
      var width = spec.width || 500,
          height = spec.height || 500,
          viewport = spec.viewport || null;
      
      var defs = {
        width: width,
        height: height,
        viewport: viewport,
        padding: vg.parse.padding(spec.padding),
        marks: vg.parse.marks(spec, width, height),
        data: vg.parse.data(spec.data, function() { callback(viewConstructor); })
      };
      
      var viewConstructor = viewFactory(defs);
    }
    
    vg.isObject(spec) ? parse(spec) :
      d3.json(spec, function(error, json) {
        error ? vg.error(error) : parse(json);
      });
  };vg.parse.transform = function(def) {
    var tx = vg.data[def.type]();
        
    vg.keys(def).forEach(function(k) {
      if (k === 'type') return;
      (tx[k])(def[k]);
    });
    
    return tx;
  };vg.scene = {};
  
  vg.scene.GROUP  = "group",
  vg.scene.ENTER  = 0,
  vg.scene.UPDATE = 1,
  vg.scene.EXIT   = 2;
  
  vg.scene.DEFAULT_DATA = {"sentinel":1}
  
  vg.scene.data = function(data, parentData) {
    var DEFAULT = vg.scene.DEFAULT_DATA;
  
    // if data is undefined, inherit or use default
    data = vg.values(data || parentData || [DEFAULT]);
  
    // if inheriting default data, ensure its in an array
    if (data === DEFAULT) data = [DEFAULT];
    
    return data;
  };
  
  vg.scene.fontString = function(o) {
    return (o.fontStyle ? o.fontStyle + " " : "")
      + (o.fontVariant ? o.fontVariant + " " : "")
      + (o.fontWeight ? o.fontWeight + " " : "")
      + (o.fontSize != null ? o.fontSize : vg.config.render.fontSize) + "px "
      + (o.font || vg.config.render.font);
  };vg.scene.Item = (function() {
    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) (i===list.length-1) ? list.pop() : list.splice(i, 1);
      return item;
    };
    
    prototype.touch = function() {
      if (this.pathCache) this.pathCache = null;
      if (this.mark.pathCache) this.mark.pathCache = null;
    };
    
    return item;
  })();
  
  vg.scene.item = function(mark) {
    return new vg.scene.Item(mark);
  };vg.scene.visit = function(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 (vg.scene.visit(items[i], func)) return true;
        }
      }
    }
  };vg.scene.build = (function() {
    var GROUP  = vg.scene.GROUP,
        ENTER  = vg.scene.ENTER,
        UPDATE = vg.scene.UPDATE,
        EXIT   = vg.scene.EXIT,
        DEFAULT= {"sentinel":1};
    
    function build(def, db, node, parentData, reentrant) {
      var data = vg.scene.data(
        def.from ? def.from(db, node, parentData) : null,
        parentData);
      
      // build node and items
      node = buildNode(def, node);
      node.items = buildItems(def, data, node);
      buildTrans(def, node);
      
      // recurse if group
      if (def.type === GROUP) {
        buildGroup(def, db, node, reentrant);
      }
      
      return node;
    };
    
    function buildNode(def, node) {
      node = node || {};
      node.def = def;
      node.marktype = def.type;
      node.interactive = !(def.interactive === false);
      return node;
    }
    
    function buildItems(def, data, node) {
      var keyf = keyFunction(def.key),
          prev = node.items || [],
          next = [],
          map = {},
          i, key, len, item, datum, enter;
  
      for (i=0, len=prev.length; i<len; ++i) {
        item = prev[i];
        item.status = EXIT;
        if (keyf) map[item.key] = item;
      }
      
      for (i=0, len=data.length; i<len; ++i) {
        datum = data[i];
        key = i;
        item = keyf ? map[key = keyf(datum)] : prev[i];
        enter = item ? false : (item = vg.scene.item(node), true);
        item.status = enter ? ENTER : UPDATE;
        item.datum = datum;
        item.key = key;
        next.push(item);
      }
  
      for (i=0, len=prev.length; i<len; ++i) {
        item = prev[i];
        if (item.status === EXIT) {
          item.key = keyf ? item.key : next.length;
          next.splice(item.index, 0, item);
        }
      }
      
      return next;
    }
    
    function buildGroup(def, db, node, reentrant) {
      var groups = node.items,
          marks = def.marks,
          i, len, m, mlen, name, group;
  
      for (i=0, len=groups.length; i<len; ++i) {
        group = groups[i];
        
        // update scales
        if (!reentrant && group.scales) for (name in group.scales) {
          if (name.indexOf(":prev") < 0) {
            group.scales[name+":prev"] = group.scales[name].copy();
          }
        }
  
        // build items
        group.items = group.items || [];
        for (m=0, mlen=marks.length; m<mlen; ++m) {
          group.items[m] = build(marks[m], db, group.items[m], group.datum);
          group.items[m].group = group;
        }
      }
    }
  
    function buildTrans(def, node) {
      if (def.duration) node.duration = def.duration;
      if (def.ease) node.ease = d3.ease(def.ease)
      if (def.delay) {
        var items = node.items, group = node.group, n = items.length, i;
        for (i=0; i<n; ++i) def.delay.call(this, items[i], group);
      }
    }
    
    function keyFunction(key) {
      if (key == null) return null;
      var f = vg.array(key).map(vg.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;
      }
    }
    
    return build;
  })();vg.scene.bounds = (function() {
  
    var parse = vg.canvas.path.parse,
        boundPath = vg.canvas.path.bounds,
        areaPath = vg.canvas.path.area,
        linePath = vg.canvas.path.line,
        halfpi = Math.PI / 2,
        sqrt3 = Math.sqrt(3),
        tan30 = Math.tan(30 * Math.PI / 180),
        gfx = null;
  
    function context() {
      return gfx || (gfx = (vg.config.isNode
        ? new (require("canvas"))(1,1)
        : d3.select("body").append("canvas")
            .attr("class", "vega_hidden")
            .attr("width", 1)
            .attr("height", 1)
            .style("display", "none")
            .node())
        .getContext("2d"));
    }
  
    function pathBounds(o, path, bounds) {
      if (path == null) {
        bounds.set(0, 0, 0, 0);
      } else {
        boundPath(path, bounds);
        if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) {
          bounds.expand(o.strokeWidth);
        }
      }
      return bounds;
    }
  
    function path(o, bounds) {
      var p = o.path
        ? o.pathCache || (o.pathCache = parse(o.path))
        : null;
      return pathBounds(o, p, bounds);
    }
    
    function area(o, bounds) {
      var items = o.mark.items, o = items[0];
      var p = o.pathCache || (o.pathCache = parse(areaPath(items)));
      return pathBounds(items[0], p, bounds);
    }
  
    function line(o, bounds) {
      var items = o.mark.items, o = items[0];
      var p = o.pathCache || (o.pathCache = parse(linePath(items)));
      return pathBounds(items[0], p, bounds);
    }
  
    function rect(o, bounds) {
      var x = o.x || 0,
          y = o.y || 0,
          w = (x + o.width) || 0,
          h = (y + o.height) || 0;
      bounds.set(x, y, w, h);
      if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) {
        bounds.expand(o.strokeWidth);
      }
      return bounds;
    }
  
    function image(o, bounds) {
      var w = o.width || 0,
          h = o.height || 0,
          x = (o.x||0) - (o.align === "center"
              ? w/2 : (o.align === "right" ? w : 0)),
          y = (o.y||0) - (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;
      bounds.set(
        x1 = o.x || 0,
        y1 = o.y || 0,
        o.x2 != null ? o.x2 : x1,
        o.y2 != null ? o.y2 : y1
      );
      if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) {
        bounds.expand(o.strokeWidth);
      }
      return bounds;
    }
    
    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);
      }
  
      bounds.set(cx+xmin, cy+ymin, cx+xmax, cy+ymax);
      if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) {
        bounds.expand(o.strokeWidth);
      }
      return bounds;
    }
  
    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":
          r = Math.sqrt(size / 5) / 2;
          t = 3*r;
          bounds.set(x-t, y-r, x+t, y+r);
          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);
      }
      if (o.stroke && o.opacity !== 0 && o.strokeWidth > 0) {
        bounds.expand(o.strokeWidth);
      }
      return bounds;
    }
  
    function text(o, bounds, noRotate) {
      var x = (o.x || 0) + (o.dx || 0),
          y = (o.y || 0) + (o.dy || 0),
          h = o.fontSize || vg.config.render.fontSize,
          a = o.align,
          b = o.baseline,
          r = o.radius || 0,
          g = context(), w, t;
  
      g.font = vg.scene.fontString(o);
      g.textAlign = a || "left";
      g.textBaseline = b || "alphabetic";
      w = g.measureText(o.text || "").width;
  
      if (r) {
        t = (o.theta || 0) - Math.PI/2;
        x += r * Math.cos(t);
        y += r * Math.sin(t);
      }
  
      // horizontal
      if (a === "center") {
        x = x - (w / 2);
      } else if (a === "right") {
        x = x - w;
      } else {
        // left by default, do nothing
      }
  
      /// TODO find a robust solution for heights.
      /// These offsets work for some but not all fonts.
  
      // vertical
      if (b === "top") {
        y = y + (h/5);
      } else if (b === "bottom") {
        y = y - h;
      } else if (b === "middle") {
        y = y - (h/2) + (h/10);
      } else {
        y = y - 4*h/5; // alphabetic by default
      }
      
      bounds.set(x, y, x+w, y+h);
      if (o.angle && !noRotate) {
        bounds.rotate(o.angle*Math.PI/180, o.x||0, o.y||0);
      }
      return bounds.expand(noRotate ? 0 : 1);
    }
  
    function group(g, bounds, includeLegends) {
      var axes = g.axisItems || [],
          legends = g.legendItems || [], j, m;
  
      for (j=0, m=axes.length; j<m; ++j) {
        bounds.union(axes[j].bounds);
      }
      for (j=0, m=g.items.length; j<m; ++j) {
        bounds.union(g.items[j].bounds);
      }
      if (includeLegends) {
        for (j=0, m=legends.length; j<m; ++j) {
          bounds.union(legends[j].bounds);
        }
        if (g.width != null && g.height != null) {
          bounds.add(g.width, g.height);
        }
        if (g.x != null && g.y != null) {
          bounds.add(0, 0);
        }
      }
      bounds.translate(g.x||0, g.y||0);
      return bounds;
    }
  
    var methods = {
      group:  group,
      symbol: symbol,
      image:  image,
      rect:   rect,
      rule:   rule,
      arc:    arc,
      text:   text,
      path:   path,
      area:   area,
      line:   line
    };
  
    function itemBounds(item, func, opt) {
      func = func || methods[item.mark.marktype];
      if (!item.bounds_prev) item['bounds:prev'] = new vg.Bounds();
      var b = item.bounds, pb = item['bounds:prev'];
      if (b) pb.clear().union(b);
      item.bounds = func(item, b ? b.clear() : new vg.Bounds(), opt);
      if (!b) pb.clear().union(item.bounds);
      return item.bounds;
    }
  
    function markBounds(mark, bounds, opt) {
      bounds = bounds || mark.bounds && mark.bounds.clear() || new vg.Bounds();
      var type  = mark.marktype,
          func  = methods[type],
          items = mark.items,
          item, i, len;
          
      if (type==="area" || type==="line") {
        if (items.length) {
          items[0].bounds = func(items[0], bounds);
        }
      } else {
        for (i=0, len=items.length; i<len; ++i) {
          bounds.union(itemBounds(items[i], func, opt));
        }
      }
      mark.bounds = bounds;
    }
    
    return {
      mark:  markBounds,
      item:  itemBounds,
      text:  text,
      group: group
    };
  
  })();vg.scene.encode = (function() {
    var GROUP  = vg.scene.GROUP,
        ENTER  = vg.scene.ENTER,
        UPDATE = vg.scene.UPDATE,
        EXIT   = vg.scene.EXIT,
        EMPTY  = {};
  
    function main(scene, def, trans, request, items) {
      (request && items)
        ? update.call(this, scene, def, trans, request, items)
        : encode.call(this, scene, scene, def, trans, request);
      return scene;
    }
    
    function update(scene, def, trans, request, items) {
      items = vg.array(items);
      var i, len, item, group, props, prop;
      for (i=0, len=items.length; i<len; ++i) {
        item = items[i];
        group = item.mark.group || null;
        props = item.mark.def.properties;
        prop = props && props[request];
        if (prop) {
          prop.call(vg, item, group, trans);
          vg.scene.bounds.item(item);
        }
      }
    }
    
    function encode(group, scene, def, trans, request) {
      encodeItems.call(this, group, scene.items, def, trans, request);
      if (scene.marktype === GROUP) {
        encodeGroup.call(this, scene, def, group, trans, request);
      } else {
        vg.scene.bounds.mark(scene);
      }
    }
    
    function encodeLegend(group, scene, def, trans, request) {
      encodeGroup.call(this, scene, def, group, trans, request);
      encodeItems.call(this, group, scene.items, def, trans, request);
      vg.scene.bounds.mark(scene, null, true);
    }
    
    function encodeGroup(scene, def, parent, trans, request) {
      var i, len, m, mlen, group, scales,
          axes, axisItems, axisDef, leg, legItems, legDef;
  
      for (i=0, len=scene.items.length; i<len; ++i) {
        group = scene.items[i];
  
        // cascade scales recursively
        // use parent scales if there are no group-level scale defs
        scales = group.scales || (group.scales =
          def.scales ? vg.extend({}, parent.scales) : parent.scales);
        
        // update group-level scales
        if (def.scales) {
          vg.parse.scales(def.scales, scales, this._data, group);
        }
        
        // update group-level axes
        if (def.axes) {
          axes = group.axes || (group.axes = []);
          axisItems = group.axisItems || (group.axisItems = []);
          vg.parse.axes(def.axes, axes, group.scales);
          axes.forEach(function(a, i) {
            axisDef = a.def();
            axisItems[i] = vg.scene.build(axisDef, this._data, axisItems[i], null, 1);
            axisItems[i].group = group;
            encode.call(this, group, group.axisItems[i], axisDef, trans);
          });
        }
        
        // encode children marks
        for (m=0, mlen=group.items.length; m<mlen; ++m) {
          encode.call(this, group, group.items[m], def.marks[m], trans, request);
        }
      }
      
      // compute bounds (without legend)
      vg.scene.bounds.mark(scene, null, !def.legends);
      
      // update legends
      if (def.legends) {
        for (i=0, len=scene.items.length; i<len; ++i) {
          group = scene.items[i];
          leg = group.legends || (group.legends = []);
          legItems = group.legendItems || (group.legendItems = []);
          vg.parse.legends(def.legends, leg, group.scales);
          leg.forEach(function(l, i) {
            legDef = l.def();
            legItems[i] = vg.scene.build(legDef, this._data, legItems[i], null, 1);
            legItems[i].group = group;
            encodeLegend.call(this, group, group.legendItems[i], legDef, trans);
          });
        }
        vg.scene.bounds.mark(scene, null, true);
      }
    }
    
    function encodeItems(group, items, def, trans, request) {    
      var props  = def.properties || EMPTY,
          enter  = props.enter,
          update = props.update,
          exit   = props.exit,
          i, len, item, prop;
  
      if (request) {
        if (prop = props[request]) {
          for (i=0, len=items.length; i<len; ++i) {
            prop.call(vg, items[i], group, trans);
          }
        }
        return; // exit early if given request
      }
  
      for (i=0; i<items.length; ++i) {
        item = items[i];
  
        // enter set
        if (item.status === ENTER) {
          if (enter) enter.call(vg, item, group);
          item.status = UPDATE;
        }
  
        // update set      
        if (item.status !== EXIT && update) {
          update.call(vg, item, group, trans);
        }
        
        // exit set
        if (item.status === EXIT) {
          if (exit) exit.call(vg, item, group, trans);
          if (trans && !exit) trans.interpolate(item, EMPTY);
          else if (!trans) items[i--].remove();
        }
      }
    }
    
    return main;
  })();vg.scene.Transition = (function() {
    function trans(duration, ease) {
      this.duration = duration || 500;
      this.ease = ease && d3.ease(ease) || d3.ease("cubic-in-out");
      this.updates = {next: null};
    }
    
    var prototype = trans.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
            item[key] = next;
          } else if (typeof curr === "number" && !isFinite(curr)) {
            // for NaN or infinite numeric values, skip to final value
            item[key] = next;
          } else {
            // otherwise lookup interpolator
            interp = d3.interpolate(curr, next);
            interp.property = key;
            (list || (list=[])).push(interp);
          }
        }
      }
  
      if (list === null && item.status === vg.scene.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 === vg.scene.EXIT) 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();
        vg.scene.bounds.item(item);
  
        if (f === 1) {
          if (curr.remove) item.remove();
          prev.next = curr.next;
          curr = prev;
        } else {
          stop = false;
        }
      }
  
      this.callback();
      return stop;
    };
    
    return trans;
    
  })();
  
  vg.scene.transition = function(dur, ease) {
    return new vg.scene.Transition(dur, ease);
  };vg.scene.axis = function() {
    var scale,
        orient = vg.config.axis.orient,
        offset = 0,
        titleOffset = vg.config.axis.titleOffset,
        axisDef = null,
        layer = "front",
        grid = false,
        title = null,
        tickMajorSize = vg.config.axis.tickSize,
        tickMinorSize = vg.config.axis.tickSize,
        tickEndSize = vg.config.axis.tickSize,
        tickPadding = vg.config.axis.padding,
        tickValues = null,
        tickFormatString = null,
        tickFormat = null,
        tickSubdivide = 0,
        tickArguments = [vg.config.axis.ticks],
        gridLineStyle = {},
        tickLabelStyle = {},
        majorTickStyle = {},
        minorTickStyle = {},
        titleStyle = {},
        domainStyle = {};
  
    var axis = {};
  
    function reset() {
      axisDef = null;
    }
  
    axis.def = function() {
      var def = axisDef ? axisDef : (axisDef = axis_def(scale));
  
      // tick format
      tickFormat = !tickFormatString ? null : ((scale.type === 'time')
        ? d3.time.format(tickFormatString)
        : d3.format(tickFormatString));
  
      // generate data
      var major = tickValues == null
        ? (scale.ticks ? scale.ticks.apply(scale, tickArguments) : scale.domain())
        : tickValues;
      var minor = vg_axisSubdivide(scale, major, tickSubdivide).map(vg.data.ingest);
      major = major.map(vg.data.ingest);
      var fmt = tickFormat==null ? (scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments) : String) : tickFormat;
      major.forEach(function(d) { d.label = fmt(d.data); });
      var tdata = title ? [title].map(vg.data.ingest) : [];
      
      // update axis def
      def.marks[0].from = function() { return grid ? major : []; };
      def.marks[1].from = function() { return major; };
      def.marks[2].from = function() { return minor; };
      def.marks[3].from = def.marks[1].from;
      def.marks[4].from = function() { return [1]; };
      def.marks[5].from = function() { return tdata; };
      def.offset = offset;
      def.orient = orient;
      def.layer = layer;
      return def;
    };
  
    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 = vg_axisScaleRange(scale);
  
      // setup axis marks
      var gridLines = vg_axisTicks();
      var majorTicks = vg_axisTicks();
      var minorTicks = vg_axisTicks();
      var tickLabels = vg_axisTickLabels();
      var domain = vg_axisDomain();
      var title = vg_axisTitle();
      gridLines.properties.enter.stroke = {value: vg.config.axis.gridColor};
  
      // extend axis marks based on axis orientation
      vg_axisTicksExtend(orient, gridLines, oldScale, newScale, Infinity);
      vg_axisTicksExtend(orient, majorTicks, oldScale, newScale, tickMajorSize);
      vg_axisTicksExtend(orient, minorTicks, oldScale, newScale, tickMinorSize);
      vg_axisLabelExtend(orient, tickLabels, oldScale, newScale, tickMajorSize, tickPadding);
  
      vg_axisDomainExtend(orient, domain, range, tickEndSize);
      vg_axisTitleExtend(orient, title, range, titleOffset); // TODO get offset
      
      // add / override custom style properties
      vg.extend(gridLines.properties.update, gridLineStyle);
      vg.extend(majorTicks.properties.update, majorTickStyle);
      vg.extend(minorTicks.properties.update, minorTickStyle);
      vg.extend(tickLabels.properties.update, tickLabelStyle);
      vg.extend(domain.properties.update, domainStyle);
      vg.extend(title.properties.update, titleStyle);
  
      var marks = [gridLines, majorTicks, minorTicks, tickLabels, domain, title];
      return {
        type: "group",
        interactive: false,
        properties: { enter: vg_axisUpdate, update: vg_axisUpdate },
        marks: marks.map(vg.parse.mark)
      };
    }
  
    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 vg_axisOrients ? x + "" : vg.config.axis.orient;
        reset();
      }
      return axis;
    };
  
    axis.title = function(x) {
      if (!arguments.length) return title;
      if (title !== x) { title = x; reset(); }
      return axis;
    };
  
    axis.ticks = function() {
      if (!arguments.length) return tickArguments;
      tickArguments = arguments;
      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.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 = vg.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;
  };
  
  var vg_axisOrients = {top: 1, right: 1, bottom: 1, left: 1};
  
  function vg_axisSubdivide(scale, ticks, m) {
    subticks = [];
    if (m && ticks.length > 1) {
      var extent = vg_axisScaleExtent(scale.domain()),
          subticks,
          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 vg_axisScaleExtent(domain) {
    var start = domain[0], stop = domain[domain.length - 1];
    return start < stop ? [start, stop] : [stop, start];
  }
  
  function vg_axisScaleRange(scale) {
    return scale.rangeExtent
      ? scale.rangeExtent()
      : vg_axisScaleExtent(scale.range());
  }
  
  var vg_axisAlign = {
    bottom: "center",
    top: "center",
    left: "right",
    right: "left"
  };
  
  var vg_axisBaseline = {
    bottom: "top",
    top: "bottom",
    left: "middle",
    right: "middle"
  };
  
  function vg_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") {
      vg.extend(labels.properties.enter, {
        x: oldScale,
        y: {value: size},
      });
      vg.extend(labels.properties.update, {
        x: newScale,
        y: {value: size},
        align: {value: "center"},
        baseline: {value: vg_axisBaseline[orient]}
      });
    } else {
      vg.extend(labels.properties.enter, {
        x: {value: size},
        y: oldScale,
      });
      vg.extend(labels.properties.update, {
        x: {value: size},
        y: newScale,
        align: {value: vg_axisAlign[orient]},
        baseline: {value: "middle"}
      });
    }
  }
  
  function vg_axisTicksExtend(orient, ticks, oldScale, newScale, size) {
    var sign = (orient === "left" || orient === "top") ? -1 : 1;
    if (size === Infinity) {
      size = (orient === "top" || orient === "bottom")
        ? {group: "mark.group.height", mult: -sign}
        : {group: "mark.group.width", mult: -sign};
    } else {
      size = {value: sign * size};
    }
    if (orient === "top" || orient === "bottom") {
      vg.extend(ticks.properties.enter, {
        x:  oldScale,
        y:  {value: 0},
        y2: size
      });
      vg.extend(ticks.properties.update, {
        x:  newScale,
        y:  {value: 0},
        y2: size
      });
      vg.extend(ticks.properties.exit, {
        x:  newScale,
      });        
    } else {
      vg.extend(ticks.properties.enter, {
        x:  {value: 0},
        x2: size,
        y:  oldScale
      });
      vg.extend(ticks.properties.update, {
        x:  {value: 0},
        x2: size,
        y:  newScale
      });
      vg.extend(ticks.properties.exit, {
        y:  newScale,
      });
    }
  }
  
  function vg_axisTitleExtend(orient, title, range, offset) {
    var mid = ~~((range[0] + range[1]) / 2),
        sign = (orient === "top" || orient === "left") ? -1 : 1;
    
    if (orient === "bottom" || orient === "top") {
      vg.extend(title.properties.update, {
        x: {value: mid},
        y: {value: sign*offset},
        angle: {value: 0}
      });
    } else {
      vg.extend(title.properties.update, {
        x: {value: sign*offset},
        y: {value: mid},
        angle: {value: -90}
      });
    }
  }
  
  function vg_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 vg_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 (vg.isArray(offset)) {
        var ofx = offset[0],
            ofy = offset[1];
  
        switch (orient) {
          case "left":   { o.x = -ofx; o.y = ofy; break; }
          case "right":  { o.x = width + ofx; o.y = ofy; break; }
          case "bottom": { o.x = ofx; o.y = height + ofy; break; }
          case "top":    { o.x = ofx; o.y = -ofy; break; }
          default:       { o.x = ofx; o.y = ofy; }
        }
      } else {
        if (vg.isObject(offset)) {
          offset = -group.scales[offset.scale](offset.value);
        }
  
        switch (orient) {
          case "left":   { o.x = -offset; o.y = 0; break; }
          case "right":  { o.x = width + offset; o.y = 0; break; }
          case "bottom": { o.x = 0; o.y = height + offset; break; }
          case "top":    { o.x = 0; o.y = -offset; break; }
          default:       { o.x = 0; o.y = 0; }
        }
      }
  
    if (trans) trans.interpolate(item, o);
  }
  
  function vg_axisTicks() {
    return {
      type: "rule",
      interactive: false,
      key: "data",
      properties: {
        enter: {
          stroke: {value: vg.config.axis.tickColor},
          strokeWidth: {value: vg.config.axis.tickWidth},
          opacity: {value: 1e-6}
        },
        exit: { opacity: {value: 1e-6} },
        update: { opacity: {value: 1} }
      }
    };
  }
  
  function vg_axisTickLabels() {
    return {
      type: "text",
      interactive: true,
      key: "data",
      properties: {
        enter: {
          fill: {value: vg.config.axis.tickLabelColor},
          font: {value: vg.config.axis.tickLabelFont},
          fontSize: {value: vg.config.axis.tickLabelFontSize},
          opacity: {value: 1e-6},
          text: {field: "label"}
        },
        exit: { opacity: {value: 1e-6} },
        update: { opacity: {value: 1} }
      }
    };
  }
  
  function vg_axisTitle() {
    return {
      type: "text",
      interactive: true,
      properties: {
        enter: {
          font: {value: vg.config.axis.titleFont},
          fontSize: {value: vg.config.axis.titleFontSize},
          fontWeight: {value: vg.config.axis.titleFontWeight},
          fill: {value: vg.config.axis.titleColor},
          align: {value: "center"},
          baseline: {value: "middle"},
          text: {field: "data"}
        },
        update: {}
      }
    };
  }
  
  function vg_axisDomain() {
    return {
      type: "path",
      interactive: false,
      properties: {
        enter: {
          x: {value: 0.5},
          y: {value: 0.5},
          stroke: {value: vg.config.axis.axisColor},
          strokeWidth: {value: vg.config.axis.axisWidth}
        },
        update: {}
      }
    };
  }
  vg.scene.legend = function() {
    var size = null,
        shape = null,
        fill = null,
        stroke = null,
        spacing = null,
        values = null,
        format = null,
        formatString = null,
        title = undefined,
        orient = "right",
        offset = vg.config.legend.offset,
        padding = vg.config.legend.padding,
        legendDef,
        tickArguments = [5],
        legendStyle = {},
        symbolStyle = {},
        gradientStyle = {},
        titleStyle = {},
        labelStyle = {};
  
    var legend = {},
        legendDef = null;
  
    function reset() { legendDef = null; }
  
    legend.def = function() {
      var scale = size || shape || fill || stroke;
      
      format = !formatString ? null : ((scale.type === 'time')
        ? d3.time.format(formatString)
        : d3.format(formatString));
      
      if (!legendDef) {
        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(vg.data.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(vg.config.legend.symbolSize));
        range = spacing
          || (fs = labelStyle.fontSize) && (fs.value + pad)
          || (vg.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) || vg.config.legend.titleFontSize);
      }
      for (var i=0, n=range.length; i<n; ++i) range[i] += sz;
      
      // build scale for label layout
      var scale = {
        name: "legend",
        type: "ordinal",
        points: true,
        domain: domain,
        range: range
      };
      
      // update legend def
      var tdata = (title ? [title] : []).map(vg.data.ingest);
      data.forEach(function(d) {
        d.label = fmt(d.data);
        d.offset = offset;
      });
      def.scales = [ scale ];
      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 = vg_legendTitle(),
          symbols = vg_legendSymbols(),
          labels = vg_vLegendLabels();
  
      // extend legend marks
      vg_legendSymbolExtend(symbols, size, shape, fill, stroke);
      
      // add / override custom style properties
      vg.extend(titles.properties.update, titleStyle);
      vg.extend(symbols.properties.update, symbolStyle);
      vg.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;
  
      return {
        type: "group",
        interactive: false,
        properties: {
          enter: vg.parse.properties("group", legendStyle),
          update: vg_legendUpdate
        },
        marks: [titles, symbols, labels].map(vg.parse.mark)
      };
    }
  
    function quantDef(scale) {
      var def = q_legend_def(scale),
          dom = scale.domain(),
          data = dom.map(vg.data.ingest),
          width = (gradientStyle.width && gradientStyle.width.value) || vg.config.legend.gradientWidth,
          fmt = format==null ? (scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments) : String) : format;
  
      // build scale for label layout
      var layout = {
        name: "legend",
        type: scale.type,
        round: true,
        zero: false,
        domain: [dom[0], dom[dom.length-1]],
        range: [padding, width+padding]
      };
      if (scale.type==="pow") layout.exponent = scale.exponent();
      
      // update legend def
      var tdata = (title ? [title] : []).map(vg.data.ingest);
      data.forEach(function(d,i) {
        d.label = fmt(d.data);
        d.align = i==(data.length-1) ? "right" : i==0 ? "left" : "center";
      });
      def.scales = [ layout ];
      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 = vg_legendTitle(),
          gradient = vg_legendGradient(),
          labels = vg_hLegendLabels(),
          grad = new vg.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
      vg.extend(titles.properties.update, titleStyle);
      vg.extend(gradient.properties.update, gradientStyle);
      vg.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;
  
      return {
        type: "group",
        interactive: false,
        properties: {
          enter: vg.parse.properties("group", legendStyle),
          update: vg_legendUpdate
        },
        marks: [titles, gradient, labels].map(vg.parse.mark)
      };
    }
  
    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 + "" : vg.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;
  };
  
  var vg_legendOrients = {right: 1, left: 1};
  
  function vg_legendUpdate(item, group, trans) {
    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);
  
    o.x = 0.5;
    o.y = 0.5;
    o.width = lw;
    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;
        if (group.bounds) gx = trans
          ? group.width + group.bounds.delta
          : group.bounds.x2;
        o.x += gx + offset;
        break;
      };
    }
    
    if (trans) trans.interpolate(item, o);
    item.mark.def.properties.enter(item, group, trans);
  }
  
  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() {
    var cfg = vg.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() {
    var cfg = vg.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() {
    var cfg = vg.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() {
    var cfg = vg.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() {
    var cfg = vg.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}
        }
      }
    };
  }vg.Model = (function() {
    function model() {
      this._defs = null;
      this._data = {};
      this._scene = null;
      this._reset = {axes: false, legends: false};
    }
  
    var prototype = model.prototype;
  
    prototype.defs = function(defs) {
      if (!arguments.length) return this._defs;
      this._defs = defs;
      return this;
    };
  
    prototype.data = function(data) {
      if (!arguments.length) return this._data;
  
      var deps = {},
          defs = this._defs,
          src  = defs.data.source,
          tx   = defs.data.flow || {},
          keys = defs.data.sorted,
          len  = keys.length, i, k, x;
  
      // collect source data set dependencies
      function sources(k) {
        (src[k] || []).forEach(function(s) { deps[s] = k; sources(s); });
      }
      vg.keys(data).forEach(sources);
      
      // update data sets in dependency-aware order
      for (i=0; i<len; ++i) {
        if (data[k=keys[i]]) {
          x = data[k];
        } else if (deps[k]) {
          x = vg_data_duplicate(this._data[deps[k]]);
          if (vg.isTree(data)) vg_make_tree(x);
        } else continue;
        this._data[k] = tx[k] ? tx[k](x, this._data, defs.marks) : x;
      }
  
      this._reset.legends = true;
      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._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._reset.axes = true;
      return this;
    };
  
    prototype.scene = function(node) {
      if (!arguments.length) return this._scene;
      this._scene = node;
      return this;
    };
  
    prototype.build = function() {
      var m = this, data = m._data, marks = m._defs.marks;
      m._scene = vg.scene.build.call(m, marks, data, m._scene);
      m._scene.items[0].width = marks.width;
      m._scene.items[0].height = marks.height;
      m._scene.interactive = false;
      return this;
    };
  
    prototype.encode = function(trans, request, item) {
      this.reset();
      var m = this, scene = m._scene, defs = m._defs;
      vg.scene.encode.call(m, scene, defs.marks, trans, request, item);
      return this;
    };
  
    prototype.reset = function() {
      if (this._scene && this._reset.axes) {
        vg.scene.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) {
        vg.scene.visit(this._scene, function(item) {
          if (item.legends) item.legends.forEach(function(l) { l.reset(); });
        });
        this._reset.legends = false;
      }
      return this;
    };
  
    return model;
  })();vg.View = (function() {
    var view = function(el, width, height) {
      this._el = null;
      this._build = false;
      this._model = new vg.Model();
      this._width = this.__width = width || 500;
      this._height = this.__height = height || 500;
      this._autopad = 1;
      this._padding = {top:0, left:0, bottom:0, right:0};
      this._viewport = null;
      this._renderer = null;
      this._handler = null;
      this._io = vg.canvas;
      if (el) this.initialize(el);
    };
    
    var prototype = view.prototype;
    
    prototype.width = function(width) {
      if (!arguments.length) return this.__width;
      if (this.__width !== width) {
        this._width = this.__width = width;
        if (this._el) this.initialize(this._el.parentNode);
        this._model.width(width);
        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;
        if (this._el) this.initialize(this._el.parentNode);
        this._model.height(this._height);
        if (this._strict) this._autopad = 1;
      }
      return this;
    };
  
    prototype.padding = function(pad) {
      if (!arguments.length) return this._padding;
      if (this._padding !== pad) {
        if (vg.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._el) {
          this._renderer.resize(this._width, this._height, pad);
          this._handler.padding(pad);
        }
      }
      return this;
    };
    
    prototype.autopad = function(opt) {
      if (this._autopad < 1) return this;
      else this._autopad = 0;
  
      var pad = this._padding,
          b = this.model().scene().bounds,
          inset = vg.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);
        this._model.height(this._height);
        if (this._el) this.initialize(this._el.parentNode);
        this.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;
        if (this._el) this.initialize(this._el.parentNode);
      }
      return this;
    };
    
    prototype.renderer = function(type) {
      if (!arguments.length) return this._io;
      if (type === "canvas") type = vg.canvas;
      if (type === "svg") type = vg.svg;
      if (this._io !== type) {
        this._io = type;
        this._renderer = null;
        if (this._el) this.initialize(this._el.parentNode);
        if (this._build) this.render();
      }
      return this;
    };
  
    prototype.defs = function(defs) {
      if (!arguments.length) return this._model.defs();
      this._model.defs(defs);
      return this;
    };
  
    prototype.data = function(data) {
      if (!arguments.length) return this._model.data();
      var ingest = vg.keys(data).reduce(function(d, k) {
        return (d[k] = vg.data.ingestAll(data[k]), d);
      }, {});
      this._model.data(ingest);
      this._build = false;
      return this;
    };
  
    prototype.model = function(model) {
      if (!arguments.length) return this._model;
      if (this._model !== model) {
        this._model = model;
        if (this._handler) this._handler.model(model);
      }
      return this;
    };
  
    prototype.initialize = function(el) {
      var v = this, prevHandler,
          w = v._width, h = v._height, pad = v._padding;
      
      // 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
      v._renderer = (v._renderer || new this._io.Renderer())
        .initialize(el, w, h, pad);
      
      // input handler
      prevHandler = v._handler;
      v._handler = new this._io.Handler()
        .initialize(el, pad, v)
        .model(v._model);
  
      if (prevHandler) {
        prevHandler.handlers().forEach(function(h) {
          v._handler.on(h.type, h.handler);
        });
      }
      
      return this;
    };
    
    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.off = function() {
      this._handler.off.apply(this._handler, arguments);
      return this;
    };
    
    prototype.update = function(opt) {    
      opt = opt || {};
      var view = this,
          trans = opt.duration
            ? vg.scene.transition(opt.duration, opt.ease)
            : null;
  
      view._build = view._build || (view._model.build(), true);
      view._model.encode(trans, opt.props, opt.items);
      
      if (trans) {
        trans.start(function(items) {
          view._renderer.render(view._model.scene(), items);
        });
      }
      else view.render(opt.items);
  
      return view.autopad(opt);
    };
        
    return view;
  })();
  
  // view constructor factory
  // takes definitions from parsed specification as input
  // returns a view constructor
  vg.ViewFactory = function(defs) {
    return function(opt) {
      opt = opt || {};
      var v = new vg.View()
        .width(defs.width)
        .height(defs.height)
        .padding(defs.padding)
        .viewport(defs.viewport)
        .renderer(opt.renderer || "canvas")
        .defs(defs);
  
      if (defs.data.load) v.data(defs.data.load);
      if (opt.data) v.data(opt.data);
      if (opt.el) v.initialize(opt.el);
  
      if (opt.hover !== false) {
        v.on("mouseover", function(evt, item) {
          if (item.hasPropertySet("hover")) {
            this.update({props:"hover", items:item});
          }
        })
        .on("mouseout", function(evt, item) {
          if (item.hasPropertySet("hover")) {
            this.update({props:"update", items:item});
          }
        });
      }
    
      return v;
    };
  };
  vg.Spec = (function() {
    var spec = function(s) {
      this.spec = {
        width: 500,
        height: 500,
        padding: 0,
        data: [],
        scales: [],
        axes: [],
        marks: []
      };
      if (s) vg.extend(this.spec, s);
    };
    
    var prototype = spec.prototype;
  
    prototype.width = function(w) {
      this.spec.width = w;
      return this;
    };
    
    prototype.height = function(h) {
      this.spec.height = h;
      return this;
    };
    
    prototype.padding = function(p) {
      this.spec.padding = p;
      return this;
    };
    
    prototype.viewport = function(v) {
      this.spec.viewport = v;
      return this;
    };
  
    prototype.data = function(name, params) {
      if (!params) params = vg.isString(name) ? {name: name} : name;
      else params.name = name;
      this.spec.data.push(params);
      return this;
    };
    
    prototype.scale = function(name, params) {
      if (!params) params = vg.isString(name) ? {name: name} : name;
      else params.name = name;
      this.spec.scales.push(params);
      return this;
    };
    
    prototype.axis = function(params) {
      this.spec.axes.push(params);
      return this;
    };
    
    prototype.mark = function(type, mark) {
      if (!mark) mark = {type: type};
      else mark.type = type;
      mark.properties = {};
      this.spec.marks.push(mark);
      
      var that = this;
      return {
        from: function(name, obj) {
                mark.from = obj
                  ? (obj.data = name, obj)
                  : vg.isString(name) ? {data: name} : name;
                return this;
              },
        prop: function(name, obj) {
                mark.properties[name] = vg.keys(obj).reduce(function(o,k) {
                  var v = obj[k];
                  return (o[k] = vg.isObject(v) ? v : {value: v}, o);
                }, {});
                return this;
              },
        done: function() { return that; }
      };
    };
  
    prototype.parse = function(callback) {
      vg.parse.spec(this.spec, callback);
    };
  
    prototype.json = function() {
      return this.spec;
    };
  
    return spec;
  })();
  
  vg.spec = function(s) {
    return new vg.Spec(s);
  };
  vg.headless = {};vg.headless.View = (function() {
    
    var view = function(width, height, pad, type, vp) {
      this._canvas = null;
      this._type = type;
      this._el = "body";
      this._build = false;
      this._model = new vg.Model();
      this._width = this.__width = width || 500;
      this._height = this.__height = height || 500;
      this._padding = pad || {top:0, left:0, bottom:0, right:0};
      this._autopad = vg.isString(this._padding) ? 1 : 0;
      this._renderer = new vg[type].Renderer();
      this._viewport = vp || null;
      this.initialize();
    };
    
    var prototype = view.prototype;
  
    prototype.el = function(el) {
      if (!arguments.length) return this._el;
      if (this._el !== el) {
        this._el = el;
        this.initialize();
      }
      return this;
    };
  
    prototype.width = function(width) {
      if (!arguments.length) return this._width;
      if (this._width !== width) {
        this._width = width;
        this.initialize();
        this._model.width(width);
      }
      return this;
    };
  
    prototype.height = function(height) {
      if (!arguments.length) return this._height;
      if (this._height !== height) {
        this._height = height;
        this.initialize();
        this._model.height(this._height);
      }
      return this;
    };
  
    prototype.padding = function(pad) {
      if (!arguments.length) return this._padding;
      if (this._padding !== pad) {
        if (vg.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;
        }
        this.initialize();
      }
      return this;
    };
  
    prototype.autopad = function(opt) {
      if (this._autopad < 1) return this;
      else this._autopad = 0;
  
      var pad = this._padding,
          b = this._model.scene().bounds,
          inset = vg.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);
        this._model.height(this._height);
        if (this._el) this.initialize();
        this.update({props:"enter"}).update({props:"update"});
      } else {
        this.padding(pad).update(opt);
      }
      return this;
    };
  
    prototype.viewport = function(vp) {
      if (!arguments.length) return _viewport;
      this._viewport = vp;
      this.initialize();
      return this;
    };
  
    prototype.defs = function(defs) {
      if (!arguments.length) return this._model.defs();
      this._model.defs(defs);
      return this;
    };
  
    prototype.data = function(data) {
      if (!arguments.length) return this._model.data();
      var ingest = vg.keys(data).reduce(function(d, k) {
        return (d[k] = vg.data.ingestAll(data[k]), d);
      }, {});
      this._model.data(ingest);
      this._build = false;
      return this;
    };
  
    prototype.renderer = function() {
      return this._renderer;
    };
  
    prototype.canvas = function() {
      return this._canvas;
    };
    
    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
      (r.pendingImages() > 0) ? wait() : callback(this._canvas);
    };
    
    prototype.svg = function() {
      if (this._type !== "svg") return null;
  
      var p = this._padding,
          w = this._width  + (p ? p.left + p.right : 0),
          h = this._height + (p ? p.top + p.bottom : 0);
  
      if (this._viewport) {
        w = this._viewport[0] - (p ? p.left + p.right : 0);
        h = this._viewport[1] - (p ? p.top + p.bottom : 0);
      }
  
        // build svg text
      var svg = d3.select(this._el)
        .select("svg").node().innerHTML
        .replace(/ href=/g, " xlink:href="); // ns hack. sigh.
  
      return '<svg '
        + 'width="' + w + '" '
        + 'height="' + h + '" '
        + vg.config.svgNamespace + '>' + svg + '</svg>'
    };
  
    prototype.initialize = function() {    
      var w = this._width,
          h = this._height,
          pad = this._padding;
  
      if (this._viewport) {
        w = this._viewport[0] - (pad ? pad.left + pad.right : 0);
        h = this._viewport[1] - (pad ? pad.top + pad.bottom : 0);
      }
      
      if (this._type === "svg") {
        this.initSVG(w, h, pad);
      } else {
        this.initCanvas(w, h, pad);
      }
      
      return this;
    };
    
    prototype.initCanvas = function(w, h, pad) {
      var Canvas = require("canvas"),
          tw = w + pad.left + pad.right,
          th = h + pad.top + pad.bottom,
          canvas = this._canvas = new Canvas(tw, th),
          ctx = canvas.getContext("2d");
      
      // setup canvas context
      ctx.setTransform(1, 0, 0, 1, pad.left, pad.top);
  
      // configure renderer
      this._renderer.context(ctx);
      this._renderer.resize(w, h, pad);
    };
    
    prototype.initSVG = function(w, h, pad) {
      var tw = w + pad.left + pad.right,
          th = h + pad.top + pad.bottom;
  
      // configure renderer
      this._renderer.initialize(this._el, w, h, pad);
    }
    
    prototype.render = function(items) {
      this._renderer.render(this._model.scene(), items);
      return this;
    };
    
    prototype.update = function(opt) {
      opt = opt || {};
      var view = this;
      view._build = view._build || (view._model.build(), true);
      view._model.encode(null, opt.props, opt.items);
      view.render(opt.items);
      return view.autopad(opt);
    };
      
    return view;
  })();
  
  // headless view constructor factory
  // takes definitions from parsed specification as input
  // returns a view constructor
  vg.headless.View.Factory = function(defs) {
    return function(opt) {
      opt = opt || {};
      var w = defs.width,
          h = defs.height,
          p = defs.padding,
          vp = defs.viewport,
          r = opt.renderer || "canvas",
          v = new vg.headless.View(w, h, p, r, vp).defs(defs);
      if (defs.data.load) v.data(defs.data.load);
      if (opt.data) v.data(opt.data);
      return v;
    };
  };vg.headless.render = function(opt, callback) {
    function draw(chart) {
      try {
        // create and render view
        var view = chart({
          data: opt.data,
          renderer: opt.renderer
        }).update();
  
        if (opt.renderer === "svg") {
          // extract rendered svg
          callback(null, {svg: view.svg()});
        } else {
          // extract rendered canvas, waiting for any images to load
          view.canvasAsync(function(canvas) {
            callback(null, {canvas: canvas});
          });
        }
      } catch (err) {
        callback(err, null);
      }
    }
  
    vg.parse.spec(opt.spec, draw, vg.headless.View.Factory);
  };  // return module
    return vg;
  
  //---------------------------------------------------
  // END code for this module
  //---------------------------------------------------
  }