/*
 *
 * 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(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
//---------------------------------------------------
    }));