http://git-wip-us.apache.org/repos/asf/stratos/blob/19a021ae/extensions/das/modules/artifacts/monitoring-dashboard/capps/stratos-monitoring-service/GadgetScalingDetails/Scaling_Details/js/vega.js
----------------------------------------------------------------------
diff --git 
a/extensions/das/modules/artifacts/monitoring-dashboard/capps/stratos-monitoring-service/GadgetScalingDetails/Scaling_Details/js/vega.js
 
b/extensions/das/modules/artifacts/monitoring-dashboard/capps/stratos-monitoring-service/GadgetScalingDetails/Scaling_Details/js/vega.js
new file mode 100644
index 0000000..076e19b
--- /dev/null
+++ 
b/extensions/das/modules/artifacts/monitoring-dashboard/capps/stratos-monitoring-service/GadgetScalingDetails/Scaling_Details/js/vega.js
@@ -0,0 +1,7583 @@
+/*
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ *
+ */
+// Define module using Universal Module Definition pattern
+// https://github.com/umdjs/umd/blob/master/amdWeb.js
+
+(function (factory) {
+  if (typeof define === 'function' && define.amd) {
+    // Support AMD. Register as an anonymous module.
+    // NOTE: List all dependencies in AMD style
+    define(['d3', 'topojson'], factory);
+  } else {
+    // No AMD. Set module as a global variable
+    // NOTE: Pass dependencies to factory function
+    // (assume that both d3 and topojson are also global.)
+    var tj = (typeof topojson === 'undefined') ? null : topojson;
+    vg = factory(d3, tj);
+  }
+}(
+//NOTE: The dependencies are passed to this function
+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

<TRUNCATED>

Reply via email to