diff --git a/src/legacy/design-studio/js/chart.registry.js b/src/legacy/design-studio/js/chart.registry.js
new file mode 100644
index 0000000000000000000000000000000000000000..4fcea58dbf2a63b0131911d86139a4b32d5da402
--- /dev/null
+++ b/src/legacy/design-studio/js/chart.registry.js
@@ -0,0 +1,28 @@
+/*
+ This is an abstraction of dc.chartRegistry using d3.dispatch.
+
+ https://github.com/dc-js/dc.js/blob/develop/web/docs/api-latest.md#dc.chartRegistry
+
+ Will eventually be cleaned up and modularized, and dc.js will use it (while preserving its legacy interface).
+*/
+(function() {
+    var chart_registry = window.chart_registry || {};
+    window.chart_registry = chart_registry;
+
+    var types = {};
+
+    chart_registry.create_type = function(type, constructor) {
+        if(!types[type])
+            types[type] = {constructor: constructor, groups: {}};
+
+        return types[type];
+    };
+
+    chart_registry.create_group = function(type, groupname) {
+        if(!types[type])
+            throw new Error('chart registry type "' + type + '" not known');
+        if(!types[type][groupname])
+            types[type][groupname] = types[type].constructor();
+        return types[type][groupname];
+    };
+})();
diff --git a/src/legacy/design-studio/js/classlist-polyfill.js b/src/legacy/design-studio/js/classlist-polyfill.js
new file mode 100644
index 0000000000000000000000000000000000000000..4d608fb31020e2342131b5ad66cf2e8eb5e15e68
--- /dev/null
+++ b/src/legacy/design-studio/js/classlist-polyfill.js
@@ -0,0 +1,240 @@
+/*
+ * classList.js: Cross-browser full element.classList implementation.
+ * 1.1.20170427
+ *
+ * By Eli Grey, http://eligrey.com
+ * License: Dedicated to the public domain.
+ *   See https://github.com/eligrey/classList.js/blob/master/LICENSE.md
+ */
+
+/*global self, document, DOMException */
+
+/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js */
+
+if ("document" in window.self) {
+
+// Full polyfill for browsers with no classList support
+// Including IE < Edge missing SVGElement.classList
+if (!("classList" in document.createElement("_")) 
+	|| document.createElementNS && !("classList" in document.createElementNS("http://www.w3.org/2000/svg","g"))) {
+
+(function (view) {
+
+"use strict";
+
+if (!('Element' in view)) return;
+
+var
+	  classListProp = "classList"
+	, protoProp = "prototype"
+	, elemCtrProto = view.Element[protoProp]
+	, objCtr = Object
+	, strTrim = String[protoProp].trim || function () {
+		return this.replace(/^\s+|\s+$/g, "");
+	}
+	, arrIndexOf = Array[protoProp].indexOf || function (item) {
+		var
+			  i = 0
+			, len = this.length
+		;
+		for (; i < len; i++) {
+			if (i in this && this[i] === item) {
+				return i;
+			}
+		}
+		return -1;
+	}
+	// Vendors: please allow content code to instantiate DOMExceptions
+	, DOMEx = function (type, message) {
+		this.name = type;
+		this.code = DOMException[type];
+		this.message = message;
+	}
+	, checkTokenAndGetIndex = function (classList, token) {
+		if (token === "") {
+			throw new DOMEx(
+				  "SYNTAX_ERR"
+				, "An invalid or illegal string was specified"
+			);
+		}
+		if (/\s/.test(token)) {
+			throw new DOMEx(
+				  "INVALID_CHARACTER_ERR"
+				, "String contains an invalid character"
+			);
+		}
+		return arrIndexOf.call(classList, token);
+	}
+	, ClassList = function (elem) {
+		var
+			  trimmedClasses = strTrim.call(elem.getAttribute("class") || "")
+			, classes = trimmedClasses ? trimmedClasses.split(/\s+/) : []
+			, i = 0
+			, len = classes.length
+		;
+		for (; i < len; i++) {
+			this.push(classes[i]);
+		}
+		this._updateClassName = function () {
+			elem.setAttribute("class", this.toString());
+		};
+	}
+	, classListProto = ClassList[protoProp] = []
+	, classListGetter = function () {
+		return new ClassList(this);
+	}
+;
+// Most DOMException implementations don't allow calling DOMException's toString()
+// on non-DOMExceptions. Error's toString() is sufficient here.
+DOMEx[protoProp] = Error[protoProp];
+classListProto.item = function (i) {
+	return this[i] || null;
+};
+classListProto.contains = function (token) {
+	token += "";
+	return checkTokenAndGetIndex(this, token) !== -1;
+};
+classListProto.add = function () {
+	var
+		  tokens = arguments
+		, i = 0
+		, l = tokens.length
+		, token
+		, updated = false
+	;
+	do {
+		token = tokens[i] + "";
+		if (checkTokenAndGetIndex(this, token) === -1) {
+			this.push(token);
+			updated = true;
+		}
+	}
+	while (++i < l);
+
+	if (updated) {
+		this._updateClassName();
+	}
+};
+classListProto.remove = function () {
+	var
+		  tokens = arguments
+		, i = 0
+		, l = tokens.length
+		, token
+		, updated = false
+		, index
+	;
+	do {
+		token = tokens[i] + "";
+		index = checkTokenAndGetIndex(this, token);
+		while (index !== -1) {
+			this.splice(index, 1);
+			updated = true;
+			index = checkTokenAndGetIndex(this, token);
+		}
+	}
+	while (++i < l);
+
+	if (updated) {
+		this._updateClassName();
+	}
+};
+classListProto.toggle = function (token, force) {
+	token += "";
+
+	var
+		  result = this.contains(token)
+		, method = result ?
+			force !== true && "remove"
+		:
+			force !== false && "add"
+	;
+
+	if (method) {
+		this[method](token);
+	}
+
+	if (force === true || force === false) {
+		return force;
+	} else {
+		return !result;
+	}
+};
+classListProto.toString = function () {
+	return this.join(" ");
+};
+
+if (objCtr.defineProperty) {
+	var classListPropDesc = {
+		  get: classListGetter
+		, enumerable: true
+		, configurable: true
+	};
+	try {
+		objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
+	} catch (ex) { // IE 8 doesn't support enumerable:true
+		// adding undefined to fight this issue https://github.com/eligrey/classList.js/issues/36
+		// modernie IE8-MSW7 machine has IE8 8.0.6001.18702 and is affected
+		if (ex.number === undefined || ex.number === -0x7FF5EC54) {
+			classListPropDesc.enumerable = false;
+			objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
+		}
+	}
+} else if (objCtr[protoProp].__defineGetter__) {
+	elemCtrProto.__defineGetter__(classListProp, classListGetter);
+}
+
+}(window.self));
+
+}
+
+// There is full or partial native classList support, so just check if we need
+// to normalize the add/remove and toggle APIs.
+
+(function () {
+	"use strict";
+
+	var testElement = document.createElement("_");
+
+	testElement.classList.add("c1", "c2");
+
+	// Polyfill for IE 10/11 and Firefox <26, where classList.add and
+	// classList.remove exist but support only one argument at a time.
+	if (!testElement.classList.contains("c2")) {
+		var createMethod = function(method) {
+			var original = DOMTokenList.prototype[method];
+
+			DOMTokenList.prototype[method] = function(token) {
+				var i, len = arguments.length;
+
+				for (i = 0; i < len; i++) {
+					token = arguments[i];
+					original.call(this, token);
+				}
+			};
+		};
+		createMethod('add');
+		createMethod('remove');
+	}
+
+	testElement.classList.toggle("c3", false);
+
+	// Polyfill for IE 10 and Firefox <24, where classList.toggle does not
+	// support the second argument.
+	if (testElement.classList.contains("c3")) {
+		var _toggle = DOMTokenList.prototype.toggle;
+
+		DOMTokenList.prototype.toggle = function(token, force) {
+			if (1 in arguments && !this.contains(token) === !force) {
+				return force;
+			} else {
+				return _toggle.call(this, token);
+			}
+		};
+
+	}
+
+	testElement = null;
+}());
+
+}
\ No newline at end of file
diff --git a/src/legacy/design-studio/js/cola.js b/src/legacy/design-studio/js/cola.js
new file mode 100644
index 0000000000000000000000000000000000000000..f05d8815bc7a924fe6a0a811318190822120613f
--- /dev/null
+++ b/src/legacy/design-studio/js/cola.js
@@ -0,0 +1,4891 @@
+var cola;
+(function (cola) {
+    var packingOptions = {
+        PADDING: 10,
+        GOLDEN_SECTION: (1 + Math.sqrt(5)) / 2,
+        FLOAT_EPSILON: 0.0001,
+        MAX_INERATIONS: 100
+    };
+    // assign x, y to nodes while using box packing algorithm for disconnected graphs
+    function applyPacking(graphs, w, h, node_size, desired_ratio) {
+        if (desired_ratio === void 0) { desired_ratio = 1; }
+        var init_x = 0, init_y = 0, svg_width = w, svg_height = h, desired_ratio = typeof desired_ratio !== 'undefined' ? desired_ratio : 1, node_size = typeof node_size !== 'undefined' ? node_size : 0, real_width = 0, real_height = 0, min_width = 0, global_bottom = 0, line = [];
+        if (graphs.length == 0)
+            return;
+        /// that would take care of single nodes problem
+        // graphs.forEach(function (g) {
+        //     if (g.array.length == 1) {
+        //         g.array[0].x = 0;
+        //         g.array[0].y = 0;
+        //     }
+        // });
+        calculate_bb(graphs);
+        apply(graphs, desired_ratio);
+        put_nodes_to_right_positions(graphs);
+        // get bounding boxes for all separate graphs
+        function calculate_bb(graphs) {
+            graphs.forEach(function (g) {
+                calculate_single_bb(g);
+            });
+            function calculate_single_bb(graph) {
+                var min_x = Number.MAX_VALUE, min_y = Number.MAX_VALUE, max_x = 0, max_y = 0;
+                graph.array.forEach(function (v) {
+                    var w = typeof v.width !== 'undefined' ? v.width : node_size;
+                    var h = typeof v.height !== 'undefined' ? v.height : node_size;
+                    w /= 2;
+                    h /= 2;
+                    max_x = Math.max(v.x + w, max_x);
+                    min_x = Math.min(v.x - w, min_x);
+                    max_y = Math.max(v.y + h, max_y);
+                    min_y = Math.min(v.y - h, min_y);
+                });
+                graph.width = max_x - min_x;
+                graph.height = max_y - min_y;
+            }
+        }
+        //function plot(data, left, right, opt_x, opt_y) {
+        //    // plot the cost function
+        //    var plot_svg = d3.select("body").append("svg")
+        //        .attr("width", function () { return 2 * (right - left); })
+        //        .attr("height", 200);
+        //    var x = d3.time.scale().range([0, 2 * (right - left)]);
+        //    var xAxis = d3.svg.axis().scale(x).orient("bottom");
+        //    plot_svg.append("g").attr("class", "x axis")
+        //        .attr("transform", "translate(0, 199)")
+        //        .call(xAxis);
+        //    var lastX = 0;
+        //    var lastY = 0;
+        //    var value = 0;
+        //    for (var r = left; r < right; r += 1) {
+        //        value = step(data, r);
+        //        // value = 1;
+        //        plot_svg.append("line").attr("x1", 2 * (lastX - left))
+        //            .attr("y1", 200 - 30 * lastY)
+        //            .attr("x2", 2 * r - 2 * left)
+        //            .attr("y2", 200 - 30 * value)
+        //            .style("stroke", "rgb(6,120,155)");
+        //        lastX = r;
+        //        lastY = value;
+        //    }
+        //    plot_svg.append("circle").attr("cx", 2 * opt_x - 2 * left).attr("cy", 200 - 30 * opt_y)
+        //        .attr("r", 5).style('fill', "rgba(0,0,0,0.5)");
+        //}
+        // actual assigning of position to nodes
+        function put_nodes_to_right_positions(graphs) {
+            graphs.forEach(function (g) {
+                // calculate current graph center:
+                var center = { x: 0, y: 0 };
+                g.array.forEach(function (node) {
+                    center.x += node.x;
+                    center.y += node.y;
+                });
+                center.x /= g.array.length;
+                center.y /= g.array.length;
+                // calculate current top left corner:
+                var corner = { x: center.x - g.width / 2, y: center.y - g.height / 2 };
+                var offset = { x: g.x - corner.x + svg_width / 2 - real_width / 2, y: g.y - corner.y + svg_height / 2 - real_height / 2 };
+                // put nodes:
+                g.array.forEach(function (node) {
+                    node.x += offset.x;
+                    node.y += offset.y;
+                });
+            });
+        }
+        // starts box packing algorithm
+        // desired ratio is 1 by default
+        function apply(data, desired_ratio) {
+            var curr_best_f = Number.POSITIVE_INFINITY;
+            var curr_best = 0;
+            data.sort(function (a, b) { return b.height - a.height; });
+            min_width = data.reduce(function (a, b) {
+                return a.width < b.width ? a.width : b.width;
+            });
+            var left = x1 = min_width;
+            var right = x2 = get_entire_width(data);
+            var iterationCounter = 0;
+            var f_x1 = Number.MAX_VALUE;
+            var f_x2 = Number.MAX_VALUE;
+            var flag = -1; // determines which among f_x1 and f_x2 to recompute
+            var dx = Number.MAX_VALUE;
+            var df = Number.MAX_VALUE;
+            while ((dx > min_width) || df > packingOptions.FLOAT_EPSILON) {
+                if (flag != 1) {
+                    var x1 = right - (right - left) / packingOptions.GOLDEN_SECTION;
+                    var f_x1 = step(data, x1);
+                }
+                if (flag != 0) {
+                    var x2 = left + (right - left) / packingOptions.GOLDEN_SECTION;
+                    var f_x2 = step(data, x2);
+                }
+                dx = Math.abs(x1 - x2);
+                df = Math.abs(f_x1 - f_x2);
+                if (f_x1 < curr_best_f) {
+                    curr_best_f = f_x1;
+                    curr_best = x1;
+                }
+                if (f_x2 < curr_best_f) {
+                    curr_best_f = f_x2;
+                    curr_best = x2;
+                }
+                if (f_x1 > f_x2) {
+                    left = x1;
+                    x1 = x2;
+                    f_x1 = f_x2;
+                    flag = 1;
+                }
+                else {
+                    right = x2;
+                    x2 = x1;
+                    f_x2 = f_x1;
+                    flag = 0;
+                }
+                if (iterationCounter++ > 100) {
+                    break;
+                }
+            }
+            // plot(data, min_width, get_entire_width(data), curr_best, curr_best_f);
+            step(data, curr_best);
+        }
+        // one iteration of the optimization method
+        // (gives a proper, but not necessarily optimal packing)
+        function step(data, max_width) {
+            line = [];
+            real_width = 0;
+            real_height = 0;
+            global_bottom = init_y;
+            for (var i = 0; i < data.length; i++) {
+                var o = data[i];
+                put_rect(o, max_width);
+            }
+            return Math.abs(get_real_ratio() - desired_ratio);
+        }
+        // looking for a position to one box 
+        function put_rect(rect, max_width) {
+            var parent = undefined;
+            for (var i = 0; i < line.length; i++) {
+                if ((line[i].space_left >= rect.height) && (line[i].x + line[i].width + rect.width + packingOptions.PADDING - max_width) <= packingOptions.FLOAT_EPSILON) {
+                    parent = line[i];
+                    break;
+                }
+            }
+            line.push(rect);
+            if (parent !== undefined) {
+                rect.x = parent.x + parent.width + packingOptions.PADDING;
+                rect.y = parent.bottom;
+                rect.space_left = rect.height;
+                rect.bottom = rect.y;
+                parent.space_left -= rect.height + packingOptions.PADDING;
+                parent.bottom += rect.height + packingOptions.PADDING;
+            }
+            else {
+                rect.y = global_bottom;
+                global_bottom += rect.height + packingOptions.PADDING;
+                rect.x = init_x;
+                rect.bottom = rect.y;
+                rect.space_left = rect.height;
+            }
+            if (rect.y + rect.height - real_height > -packingOptions.FLOAT_EPSILON)
+                real_height = rect.y + rect.height - init_y;
+            if (rect.x + rect.width - real_width > -packingOptions.FLOAT_EPSILON)
+                real_width = rect.x + rect.width - init_x;
+        }
+        ;
+        function get_entire_width(data) {
+            var width = 0;
+            data.forEach(function (d) { return width += d.width + packingOptions.PADDING; });
+            return width;
+        }
+        function get_real_ratio() {
+            return (real_width / real_height);
+        }
+    }
+    cola.applyPacking = applyPacking;
+    /**
+     * connected components of graph
+     * returns an array of {}
+     */
+    function separateGraphs(nodes, links) {
+        var marks = {};
+        var ways = {};
+        var graphs = [];
+        var clusters = 0;
+        for (var i = 0; i < links.length; i++) {
+            var link = links[i];
+            var n1 = link.source;
+            var n2 = link.target;
+            if (ways[n1.index])
+                ways[n1.index].push(n2);
+            else
+                ways[n1.index] = [n2];
+            if (ways[n2.index])
+                ways[n2.index].push(n1);
+            else
+                ways[n2.index] = [n1];
+        }
+        for (var i = 0; i < nodes.length; i++) {
+            var node = nodes[i];
+            if (marks[node.index])
+                continue;
+            explore_node(node, true);
+        }
+        function explore_node(n, is_new) {
+            if (marks[n.index] !== undefined)
+                return;
+            if (is_new) {
+                clusters++;
+                graphs.push({ array: [] });
+            }
+            marks[n.index] = clusters;
+            graphs[clusters - 1].array.push(n);
+            var adjacent = ways[n.index];
+            if (!adjacent)
+                return;
+            for (var j = 0; j < adjacent.length; j++) {
+                explore_node(adjacent[j], false);
+            }
+        }
+        return graphs;
+    }
+    cola.separateGraphs = separateGraphs;
+})(cola || (cola = {}));
+var cola;
+(function (cola) {
+    var vpsc;
+    (function (vpsc) {
+        var PositionStats = (function () {
+            function PositionStats(scale) {
+                this.scale = scale;
+                this.AB = 0;
+                this.AD = 0;
+                this.A2 = 0;
+            }
+            PositionStats.prototype.addVariable = function (v) {
+                var ai = this.scale / v.scale;
+                var bi = v.offset / v.scale;
+                var wi = v.weight;
+                this.AB += wi * ai * bi;
+                this.AD += wi * ai * v.desiredPosition;
+                this.A2 += wi * ai * ai;
+            };
+            PositionStats.prototype.getPosn = function () {
+                return (this.AD - this.AB) / this.A2;
+            };
+            return PositionStats;
+        })();
+        vpsc.PositionStats = PositionStats;
+        var Constraint = (function () {
+            function Constraint(left, right, gap, equality) {
+                if (equality === void 0) { equality = false; }
+                this.left = left;
+                this.right = right;
+                this.gap = gap;
+                this.equality = equality;
+                this.active = false;
+                this.unsatisfiable = false;
+                this.left = left;
+                this.right = right;
+                this.gap = gap;
+                this.equality = equality;
+            }
+            Constraint.prototype.slack = function () {
+                return this.unsatisfiable ? Number.MAX_VALUE
+                    : this.right.scale * this.right.position() - this.gap
+                        - this.left.scale * this.left.position();
+            };
+            return Constraint;
+        })();
+        vpsc.Constraint = Constraint;
+        var Variable = (function () {
+            function Variable(desiredPosition, weight, scale) {
+                if (weight === void 0) { weight = 1; }
+                if (scale === void 0) { scale = 1; }
+                this.desiredPosition = desiredPosition;
+                this.weight = weight;
+                this.scale = scale;
+                this.offset = 0;
+            }
+            Variable.prototype.dfdv = function () {
+                return 2.0 * this.weight * (this.position() - this.desiredPosition);
+            };
+            Variable.prototype.position = function () {
+                return (this.block.ps.scale * this.block.posn + this.offset) / this.scale;
+            };
+            // visit neighbours by active constraints within the same block
+            Variable.prototype.visitNeighbours = function (prev, f) {
+                var ff = function (c, next) { return c.active && prev !== next && f(c, next); };
+                this.cOut.forEach(function (c) { return ff(c, c.right); });
+                this.cIn.forEach(function (c) { return ff(c, c.left); });
+            };
+            return Variable;
+        })();
+        vpsc.Variable = Variable;
+        var Block = (function () {
+            function Block(v) {
+                this.vars = [];
+                v.offset = 0;
+                this.ps = new PositionStats(v.scale);
+                this.addVariable(v);
+            }
+            Block.prototype.addVariable = function (v) {
+                v.block = this;
+                this.vars.push(v);
+                this.ps.addVariable(v);
+                this.posn = this.ps.getPosn();
+            };
+            // move the block where it needs to be to minimize cost
+            Block.prototype.updateWeightedPosition = function () {
+                this.ps.AB = this.ps.AD = this.ps.A2 = 0;
+                for (var i = 0, n = this.vars.length; i < n; ++i)
+                    this.ps.addVariable(this.vars[i]);
+                this.posn = this.ps.getPosn();
+            };
+            Block.prototype.compute_lm = function (v, u, postAction) {
+                var _this = this;
+                var dfdv = v.dfdv();
+                v.visitNeighbours(u, function (c, next) {
+                    var _dfdv = _this.compute_lm(next, v, postAction);
+                    if (next === c.right) {
+                        dfdv += _dfdv * c.left.scale;
+                        c.lm = _dfdv;
+                    }
+                    else {
+                        dfdv += _dfdv * c.right.scale;
+                        c.lm = -_dfdv;
+                    }
+                    postAction(c);
+                });
+                return dfdv / v.scale;
+            };
+            Block.prototype.populateSplitBlock = function (v, prev) {
+                var _this = this;
+                v.visitNeighbours(prev, function (c, next) {
+                    next.offset = v.offset + (next === c.right ? c.gap : -c.gap);
+                    _this.addVariable(next);
+                    _this.populateSplitBlock(next, v);
+                });
+            };
+            // traverse the active constraint tree applying visit to each active constraint
+            Block.prototype.traverse = function (visit, acc, v, prev) {
+                var _this = this;
+                if (v === void 0) { v = this.vars[0]; }
+                if (prev === void 0) { prev = null; }
+                v.visitNeighbours(prev, function (c, next) {
+                    acc.push(visit(c));
+                    _this.traverse(visit, acc, next, v);
+                });
+            };
+            // calculate lagrangian multipliers on constraints and
+            // find the active constraint in this block with the smallest lagrangian.
+            // if the lagrangian is negative, then the constraint is a split candidate.  
+            Block.prototype.findMinLM = function () {
+                var m = null;
+                this.compute_lm(this.vars[0], null, function (c) {
+                    if (!c.equality && (m === null || c.lm < m.lm))
+                        m = c;
+                });
+                return m;
+            };
+            Block.prototype.findMinLMBetween = function (lv, rv) {
+                this.compute_lm(lv, null, function () { });
+                var m = null;
+                this.findPath(lv, null, rv, function (c, next) {
+                    if (!c.equality && c.right === next && (m === null || c.lm < m.lm))
+                        m = c;
+                });
+                return m;
+            };
+            Block.prototype.findPath = function (v, prev, to, visit) {
+                var _this = this;
+                var endFound = false;
+                v.visitNeighbours(prev, function (c, next) {
+                    if (!endFound && (next === to || _this.findPath(next, v, to, visit))) {
+                        endFound = true;
+                        visit(c, next);
+                    }
+                });
+                return endFound;
+            };
+            // Search active constraint tree from u to see if there is a directed path to v.
+            // Returns true if path is found.
+            Block.prototype.isActiveDirectedPathBetween = function (u, v) {
+                if (u === v)
+                    return true;
+                var i = u.cOut.length;
+                while (i--) {
+                    var c = u.cOut[i];
+                    if (c.active && this.isActiveDirectedPathBetween(c.right, v))
+                        return true;
+                }
+                return false;
+            };
+            // split the block into two by deactivating the specified constraint
+            Block.split = function (c) {
+                /* DEBUG
+                            console.log("split on " + c);
+                            console.assert(c.active, "attempt to split on inactive constraint");
+                DEBUG */
+                c.active = false;
+                return [Block.createSplitBlock(c.left), Block.createSplitBlock(c.right)];
+            };
+            Block.createSplitBlock = function (startVar) {
+                var b = new Block(startVar);
+                b.populateSplitBlock(startVar, null);
+                return b;
+            };
+            // find a split point somewhere between the specified variables
+            Block.prototype.splitBetween = function (vl, vr) {
+                /* DEBUG
+                            console.assert(vl.block === this);
+                            console.assert(vr.block === this);
+                DEBUG */
+                var c = this.findMinLMBetween(vl, vr);
+                if (c !== null) {
+                    var bs = Block.split(c);
+                    return { constraint: c, lb: bs[0], rb: bs[1] };
+                }
+                // couldn't find a split point - for example the active path is all equality constraints
+                return null;
+            };
+            Block.prototype.mergeAcross = function (b, c, dist) {
+                c.active = true;
+                for (var i = 0, n = b.vars.length; i < n; ++i) {
+                    var v = b.vars[i];
+                    v.offset += dist;
+                    this.addVariable(v);
+                }
+                this.posn = this.ps.getPosn();
+            };
+            Block.prototype.cost = function () {
+                var sum = 0, i = this.vars.length;
+                while (i--) {
+                    var v = this.vars[i], d = v.position() - v.desiredPosition;
+                    sum += d * d * v.weight;
+                }
+                return sum;
+            };
+            return Block;
+        })();
+        vpsc.Block = Block;
+        var Blocks = (function () {
+            function Blocks(vs) {
+                this.vs = vs;
+                var n = vs.length;
+                this.list = new Array(n);
+                while (n--) {
+                    var b = new Block(vs[n]);
+                    this.list[n] = b;
+                    b.blockInd = n;
+                }
+            }
+            Blocks.prototype.cost = function () {
+                var sum = 0, i = this.list.length;
+                while (i--)
+                    sum += this.list[i].cost();
+                return sum;
+            };
+            Blocks.prototype.insert = function (b) {
+                /* DEBUG
+                            console.assert(!this.contains(b), "blocks error: tried to reinsert block " + b.blockInd)
+                DEBUG */
+                b.blockInd = this.list.length;
+                this.list.push(b);
+                /* DEBUG
+                            console.log("insert block: " + b.blockInd);
+                            this.contains(b);
+                DEBUG */
+            };
+            Blocks.prototype.remove = function (b) {
+                /* DEBUG
+                            console.log("remove block: " + b.blockInd);
+                            console.assert(this.contains(b));
+                DEBUG */
+                var last = this.list.length - 1;
+                var swapBlock = this.list[last];
+                this.list.length = last;
+                if (b !== swapBlock) {
+                    this.list[b.blockInd] = swapBlock;
+                    swapBlock.blockInd = b.blockInd;
+                }
+            };
+            // merge the blocks on either side of the specified constraint, by copying the smaller block into the larger
+            // and deleting the smaller.
+            Blocks.prototype.merge = function (c) {
+                var l = c.left.block, r = c.right.block;
+                /* DEBUG
+                            console.assert(l!==r, "attempt to merge within the same block");
+                DEBUG */
+                var dist = c.right.offset - c.left.offset - c.gap;
+                if (l.vars.length < r.vars.length) {
+                    r.mergeAcross(l, c, dist);
+                    this.remove(l);
+                }
+                else {
+                    l.mergeAcross(r, c, -dist);
+                    this.remove(r);
+                }
+                /* DEBUG
+                            console.assert(Math.abs(c.slack()) < 1e-6, "Error: Constraint should be at equality after merge!");
+                            console.log("merged on " + c);
+                DEBUG */
+            };
+            Blocks.prototype.forEach = function (f) {
+                this.list.forEach(f);
+            };
+            // useful, for example, after variable desired positions change.
+            Blocks.prototype.updateBlockPositions = function () {
+                this.list.forEach(function (b) { return b.updateWeightedPosition(); });
+            };
+            // split each block across its constraint with the minimum lagrangian 
+            Blocks.prototype.split = function (inactive) {
+                var _this = this;
+                this.updateBlockPositions();
+                this.list.forEach(function (b) {
+                    var v = b.findMinLM();
+                    if (v !== null && v.lm < Solver.LAGRANGIAN_TOLERANCE) {
+                        b = v.left.block;
+                        Block.split(v).forEach(function (nb) { return _this.insert(nb); });
+                        _this.remove(b);
+                        inactive.push(v);
+                    }
+                });
+            };
+            return Blocks;
+        })();
+        vpsc.Blocks = Blocks;
+        var Solver = (function () {
+            function Solver(vs, cs) {
+                this.vs = vs;
+                this.cs = cs;
+                this.vs = vs;
+                vs.forEach(function (v) {
+                    v.cIn = [], v.cOut = [];
+                    /* DEBUG
+                                    v.toString = () => "v" + vs.indexOf(v);
+                    DEBUG */
+                });
+                this.cs = cs;
+                cs.forEach(function (c) {
+                    c.left.cOut.push(c);
+                    c.right.cIn.push(c);
+                    /* DEBUG
+                                    c.toString = () => c.left + "+" + c.gap + "<=" + c.right + " slack=" + c.slack() + " active=" + c.active;
+                    DEBUG */
+                });
+                this.inactive = cs.map(function (c) { c.active = false; return c; });
+                this.bs = null;
+            }
+            Solver.prototype.cost = function () {
+                return this.bs.cost();
+            };
+            // set starting positions without changing desired positions.
+            // Note: it throws away any previous block structure.
+            Solver.prototype.setStartingPositions = function (ps) {
+                this.inactive = this.cs.map(function (c) { c.active = false; return c; });
+                this.bs = new Blocks(this.vs);
+                this.bs.forEach(function (b, i) { return b.posn = ps[i]; });
+            };
+            Solver.prototype.setDesiredPositions = function (ps) {
+                this.vs.forEach(function (v, i) { return v.desiredPosition = ps[i]; });
+            };
+            /* DEBUG
+                    private getId(v: Variable): number {
+                        return this.vs.indexOf(v);
+                    }
+            
+                    // sanity check of the index integrity of the inactive list
+                    checkInactive(): void {
+                        var inactiveCount = 0;
+                        this.cs.forEach(c=> {
+                            var i = this.inactive.indexOf(c);
+                            console.assert(!c.active && i >= 0 || c.active && i < 0, "constraint should be in the inactive list if it is not active: " + c);
+                            if (i >= 0) {
+                                inactiveCount++;
+                            } else {
+                                console.assert(c.active, "inactive constraint not found in inactive list: " + c);
+                            }
+                        });
+                        console.assert(inactiveCount === this.inactive.length, inactiveCount + " inactive constraints found, " + this.inactive.length + "in inactive list");
+                    }
+                    // after every call to satisfy the following should check should pass
+                    checkSatisfied(): void {
+                        this.cs.forEach(c=>console.assert(c.slack() >= vpsc.Solver.ZERO_UPPERBOUND, "Error: Unsatisfied constraint! "+c));
+                    }
+            DEBUG */
+            Solver.prototype.mostViolated = function () {
+                var minSlack = Number.MAX_VALUE, v = null, l = this.inactive, n = l.length, deletePoint = n;
+                for (var i = 0; i < n; ++i) {
+                    var c = l[i];
+                    if (c.unsatisfiable)
+                        continue;
+                    var slack = c.slack();
+                    if (c.equality || slack < minSlack) {
+                        minSlack = slack;
+                        v = c;
+                        deletePoint = i;
+                        if (c.equality)
+                            break;
+                    }
+                }
+                if (deletePoint !== n &&
+                    (minSlack < Solver.ZERO_UPPERBOUND && !v.active || v.equality)) {
+                    l[deletePoint] = l[n - 1];
+                    l.length = n - 1;
+                }
+                return v;
+            };
+            // satisfy constraints by building block structure over violated constraints
+            // and moving the blocks to their desired positions
+            Solver.prototype.satisfy = function () {
+                if (this.bs == null) {
+                    this.bs = new Blocks(this.vs);
+                }
+                /* DEBUG
+                            console.log("satisfy: " + this.bs);
+                DEBUG */
+                this.bs.split(this.inactive);
+                var v = null;
+                while ((v = this.mostViolated()) && (v.equality || v.slack() < Solver.ZERO_UPPERBOUND && !v.active)) {
+                    var lb = v.left.block, rb = v.right.block;
+                    /* DEBUG
+                                    console.log("most violated is: " + v);
+                                    this.bs.contains(lb);
+                                    this.bs.contains(rb);
+                    DEBUG */
+                    if (lb !== rb) {
+                        this.bs.merge(v);
+                    }
+                    else {
+                        if (lb.isActiveDirectedPathBetween(v.right, v.left)) {
+                            // cycle found!
+                            v.unsatisfiable = true;
+                            continue;
+                        }
+                        // constraint is within block, need to split first
+                        var split = lb.splitBetween(v.left, v.right);
+                        if (split !== null) {
+                            this.bs.insert(split.lb);
+                            this.bs.insert(split.rb);
+                            this.bs.remove(lb);
+                            this.inactive.push(split.constraint);
+                        }
+                        else {
+                            /* DEBUG
+                                                    console.log("unsatisfiable constraint found");
+                            DEBUG */
+                            v.unsatisfiable = true;
+                            continue;
+                        }
+                        if (v.slack() >= 0) {
+                            /* DEBUG
+                                                    console.log("violated constraint indirectly satisfied: " + v);
+                            DEBUG */
+                            // v was satisfied by the above split!
+                            this.inactive.push(v);
+                        }
+                        else {
+                            /* DEBUG
+                                                    console.log("merge after split:");
+                            DEBUG */
+                            this.bs.merge(v);
+                        }
+                    }
+                }
+                /* DEBUG
+                            this.checkSatisfied();
+                DEBUG */
+            };
+            // repeatedly build and split block structure until we converge to an optimal solution
+            Solver.prototype.solve = function () {
+                this.satisfy();
+                var lastcost = Number.MAX_VALUE, cost = this.bs.cost();
+                while (Math.abs(lastcost - cost) > 0.0001) {
+                    this.satisfy();
+                    lastcost = cost;
+                    cost = this.bs.cost();
+                }
+                return cost;
+            };
+            Solver.LAGRANGIAN_TOLERANCE = -1e-4;
+            Solver.ZERO_UPPERBOUND = -1e-10;
+            return Solver;
+        })();
+        vpsc.Solver = Solver;
+    })(vpsc = cola.vpsc || (cola.vpsc = {}));
+})(cola || (cola = {}));
+var __extends = (this && this.__extends) || function (d, b) {
+    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
+    function __() { this.constructor = d; }
+    __.prototype = b.prototype;
+    d.prototype = new __();
+};
+var cola;
+(function (cola) {
+    var vpsc;
+    (function (vpsc) {
+        //Based on js_es:
+        //
+        //https://github.com/vadimg/js_bintrees
+        //
+        //Copyright (C) 2011 by Vadim Graboys
+        //
+        //Permission is hereby granted, free of charge, to any person obtaining a copy
+        //of this software and associated documentation files (the "Software"), to deal
+        //in the Software without restriction, including without limitation the rights
+        //to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+        //copies of the Software, and to permit persons to whom the Software is
+        //furnished to do so, subject to the following conditions:
+        //
+        //The above copyright notice and this permission notice shall be included in
+        //all copies or substantial portions of the Software.
+        //
+        //THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+        //IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+        //FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+        //AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+        //LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+        //OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+        //THE SOFTWARE.
+        var TreeBase = (function () {
+            function TreeBase() {
+                // returns iterator to node if found, null otherwise
+                this.findIter = function (data) {
+                    var res = this._root;
+                    var iter = this.iterator();
+                    while (res !== null) {
+                        var c = this._comparator(data, res.data);
+                        if (c === 0) {
+                            iter._cursor = res;
+                            return iter;
+                        }
+                        else {
+                            iter._ancestors.push(res);
+                            res = res.get_child(c > 0);
+                        }
+                    }
+                    return null;
+                };
+            }
+            // removes all nodes from the tree
+            TreeBase.prototype.clear = function () {
+                this._root = null;
+                this.size = 0;
+            };
+            ;
+            // returns node data if found, null otherwise
+            TreeBase.prototype.find = function (data) {
+                var res = this._root;
+                while (res !== null) {
+                    var c = this._comparator(data, res.data);
+                    if (c === 0) {
+                        return res.data;
+                    }
+                    else {
+                        res = res.get_child(c > 0);
+                    }
+                }
+                return null;
+            };
+            ;
+            // Returns an interator to the tree node immediately before (or at) the element
+            TreeBase.prototype.lowerBound = function (data) {
+                return this._bound(data, this._comparator);
+            };
+            ;
+            // Returns an interator to the tree node immediately after (or at) the element
+            TreeBase.prototype.upperBound = function (data) {
+                var cmp = this._comparator;
+                function reverse_cmp(a, b) {
+                    return cmp(b, a);
+                }
+                return this._bound(data, reverse_cmp);
+            };
+            ;
+            // returns null if tree is empty
+            TreeBase.prototype.min = function () {
+                var res = this._root;
+                if (res === null) {
+                    return null;
+                }
+                while (res.left !== null) {
+                    res = res.left;
+                }
+                return res.data;
+            };
+            ;
+            // returns null if tree is empty
+            TreeBase.prototype.max = function () {
+                var res = this._root;
+                if (res === null) {
+                    return null;
+                }
+                while (res.right !== null) {
+                    res = res.right;
+                }
+                return res.data;
+            };
+            ;
+            // returns a null iterator
+            // call next() or prev() to point to an element
+            TreeBase.prototype.iterator = function () {
+                return new Iterator(this);
+            };
+            ;
+            // calls cb on each node's data, in order
+            TreeBase.prototype.each = function (cb) {
+                var it = this.iterator(), data;
+                while ((data = it.next()) !== null) {
+                    cb(data);
+                }
+            };
+            ;
+            // calls cb on each node's data, in reverse order
+            TreeBase.prototype.reach = function (cb) {
+                var it = this.iterator(), data;
+                while ((data = it.prev()) !== null) {
+                    cb(data);
+                }
+            };
+            ;
+            // used for lowerBound and upperBound
+            TreeBase.prototype._bound = function (data, cmp) {
+                var cur = this._root;
+                var iter = this.iterator();
+                while (cur !== null) {
+                    var c = this._comparator(data, cur.data);
+                    if (c === 0) {
+                        iter._cursor = cur;
+                        return iter;
+                    }
+                    iter._ancestors.push(cur);
+                    cur = cur.get_child(c > 0);
+                }
+                for (var i = iter._ancestors.length - 1; i >= 0; --i) {
+                    cur = iter._ancestors[i];
+                    if (cmp(data, cur.data) > 0) {
+                        iter._cursor = cur;
+                        iter._ancestors.length = i;
+                        return iter;
+                    }
+                }
+                iter._ancestors.length = 0;
+                return iter;
+            };
+            ;
+            return TreeBase;
+        })();
+        vpsc.TreeBase = TreeBase;
+        var Iterator = (function () {
+            function Iterator(tree) {
+                this._tree = tree;
+                this._ancestors = [];
+                this._cursor = null;
+            }
+            Iterator.prototype.data = function () {
+                return this._cursor !== null ? this._cursor.data : null;
+            };
+            ;
+            // if null-iterator, returns first node
+            // otherwise, returns next node
+            Iterator.prototype.next = function () {
+                if (this._cursor === null) {
+                    var root = this._tree._root;
+                    if (root !== null) {
+                        this._minNode(root);
+                    }
+                }
+                else {
+                    if (this._cursor.right === null) {
+                        // no greater node in subtree, go up to parent
+                        // if coming from a right child, continue up the stack
+                        var save;
+                        do {
+                            save = this._cursor;
+                            if (this._ancestors.length) {
+                                this._cursor = this._ancestors.pop();
+                            }
+                            else {
+                                this._cursor = null;
+                                break;
+                            }
+                        } while (this._cursor.right === save);
+                    }
+                    else {
+                        // get the next node from the subtree
+                        this._ancestors.push(this._cursor);
+                        this._minNode(this._cursor.right);
+                    }
+                }
+                return this._cursor !== null ? this._cursor.data : null;
+            };
+            ;
+            // if null-iterator, returns last node
+            // otherwise, returns previous node
+            Iterator.prototype.prev = function () {
+                if (this._cursor === null) {
+                    var root = this._tree._root;
+                    if (root !== null) {
+                        this._maxNode(root);
+                    }
+                }
+                else {
+                    if (this._cursor.left === null) {
+                        var save;
+                        do {
+                            save = this._cursor;
+                            if (this._ancestors.length) {
+                                this._cursor = this._ancestors.pop();
+                            }
+                            else {
+                                this._cursor = null;
+                                break;
+                            }
+                        } while (this._cursor.left === save);
+                    }
+                    else {
+                        this._ancestors.push(this._cursor);
+                        this._maxNode(this._cursor.left);
+                    }
+                }
+                return this._cursor !== null ? this._cursor.data : null;
+            };
+            ;
+            Iterator.prototype._minNode = function (start) {
+                while (start.left !== null) {
+                    this._ancestors.push(start);
+                    start = start.left;
+                }
+                this._cursor = start;
+            };
+            ;
+            Iterator.prototype._maxNode = function (start) {
+                while (start.right !== null) {
+                    this._ancestors.push(start);
+                    start = start.right;
+                }
+                this._cursor = start;
+            };
+            ;
+            return Iterator;
+        })();
+        vpsc.Iterator = Iterator;
+        var Node = (function () {
+            function Node(data) {
+                this.data = data;
+                this.left = null;
+                this.right = null;
+                this.red = true;
+            }
+            Node.prototype.get_child = function (dir) {
+                return dir ? this.right : this.left;
+            };
+            ;
+            Node.prototype.set_child = function (dir, val) {
+                if (dir) {
+                    this.right = val;
+                }
+                else {
+                    this.left = val;
+                }
+            };
+            ;
+            return Node;
+        })();
+        var RBTree = (function (_super) {
+            __extends(RBTree, _super);
+            function RBTree(comparator) {
+                _super.call(this);
+                this._root = null;
+                this._comparator = comparator;
+                this.size = 0;
+            }
+            // returns true if inserted, false if duplicate
+            RBTree.prototype.insert = function (data) {
+                var ret = false;
+                if (this._root === null) {
+                    // empty tree
+                    this._root = new Node(data);
+                    ret = true;
+                    this.size++;
+                }
+                else {
+                    var head = new Node(undefined); // fake tree root
+                    var dir = false;
+                    var last = false;
+                    // setup
+                    var gp = null; // grandparent
+                    var ggp = head; // grand-grand-parent
+                    var p = null; // parent
+                    var node = this._root;
+                    ggp.right = this._root;
+                    // search down
+                    while (true) {
+                        if (node === null) {
+                            // insert new node at the bottom
+                            node = new Node(data);
+                            p.set_child(dir, node);
+                            ret = true;
+                            this.size++;
+                        }
+                        else if (RBTree.is_red(node.left) && RBTree.is_red(node.right)) {
+                            // color flip
+                            node.red = true;
+                            node.left.red = false;
+                            node.right.red = false;
+                        }
+                        // fix red violation
+                        if (RBTree.is_red(node) && RBTree.is_red(p)) {
+                            var dir2 = ggp.right === gp;
+                            if (node === p.get_child(last)) {
+                                ggp.set_child(dir2, RBTree.single_rotate(gp, !last));
+                            }
+                            else {
+                                ggp.set_child(dir2, RBTree.double_rotate(gp, !last));
+                            }
+                        }
+                        var cmp = this._comparator(node.data, data);
+                        // stop if found
+                        if (cmp === 0) {
+                            break;
+                        }
+                        last = dir;
+                        dir = cmp < 0;
+                        // update helpers
+                        if (gp !== null) {
+                            ggp = gp;
+                        }
+                        gp = p;
+                        p = node;
+                        node = node.get_child(dir);
+                    }
+                    // update root
+                    this._root = head.right;
+                }
+                // make root black
+                this._root.red = false;
+                return ret;
+            };
+            ;
+            // returns true if removed, false if not found
+            RBTree.prototype.remove = function (data) {
+                if (this._root === null) {
+                    return false;
+                }
+                var head = new Node(undefined); // fake tree root
+                var node = head;
+                node.right = this._root;
+                var p = null; // parent
+                var gp = null; // grand parent
+                var found = null; // found item
+                var dir = true;
+                while (node.get_child(dir) !== null) {
+                    var last = dir;
+                    // update helpers
+                    gp = p;
+                    p = node;
+                    node = node.get_child(dir);
+                    var cmp = this._comparator(data, node.data);
+                    dir = cmp > 0;
+                    // save found node
+                    if (cmp === 0) {
+                        found = node;
+                    }
+                    // push the red node down
+                    if (!RBTree.is_red(node) && !RBTree.is_red(node.get_child(dir))) {
+                        if (RBTree.is_red(node.get_child(!dir))) {
+                            var sr = RBTree.single_rotate(node, dir);
+                            p.set_child(last, sr);
+                            p = sr;
+                        }
+                        else if (!RBTree.is_red(node.get_child(!dir))) {
+                            var sibling = p.get_child(!last);
+                            if (sibling !== null) {
+                                if (!RBTree.is_red(sibling.get_child(!last)) && !RBTree.is_red(sibling.get_child(last))) {
+                                    // color flip
+                                    p.red = false;
+                                    sibling.red = true;
+                                    node.red = true;
+                                }
+                                else {
+                                    var dir2 = gp.right === p;
+                                    if (RBTree.is_red(sibling.get_child(last))) {
+                                        gp.set_child(dir2, RBTree.double_rotate(p, last));
+                                    }
+                                    else if (RBTree.is_red(sibling.get_child(!last))) {
+                                        gp.set_child(dir2, RBTree.single_rotate(p, last));
+                                    }
+                                    // ensure correct coloring
+                                    var gpc = gp.get_child(dir2);
+                                    gpc.red = true;
+                                    node.red = true;
+                                    gpc.left.red = false;
+                                    gpc.right.red = false;
+                                }
+                            }
+                        }
+                    }
+                }
+                // replace and remove if found
+                if (found !== null) {
+                    found.data = node.data;
+                    p.set_child(p.right === node, node.get_child(node.left === null));
+                    this.size--;
+                }
+                // update root and make it black
+                this._root = head.right;
+                if (this._root !== null) {
+                    this._root.red = false;
+                }
+                return found !== null;
+            };
+            ;
+            RBTree.is_red = function (node) {
+                return node !== null && node.red;
+            };
+            RBTree.single_rotate = function (root, dir) {
+                var save = root.get_child(!dir);
+                root.set_child(!dir, save.get_child(dir));
+                save.set_child(dir, root);
+                root.red = true;
+                save.red = false;
+                return save;
+            };
+            RBTree.double_rotate = function (root, dir) {
+                root.set_child(!dir, RBTree.single_rotate(root.get_child(!dir), !dir));
+                return RBTree.single_rotate(root, dir);
+            };
+            return RBTree;
+        })(TreeBase);
+        vpsc.RBTree = RBTree;
+    })(vpsc = cola.vpsc || (cola.vpsc = {}));
+})(cola || (cola = {}));
+///<reference path="vpsc.ts"/>
+///<reference path="rbtree.ts"/>
+var cola;
+(function (cola) {
+    var vpsc;
+    (function (vpsc) {
+        function computeGroupBounds(g) {
+            g.bounds = typeof g.leaves !== "undefined" ?
+                g.leaves.reduce(function (r, c) { return c.bounds.union(r); }, Rectangle.empty()) :
+                Rectangle.empty();
+            if (typeof g.groups !== "undefined")
+                g.bounds = g.groups.reduce(function (r, c) { return computeGroupBounds(c).union(r); }, g.bounds);
+            g.bounds = g.bounds.inflate(g.padding);
+            return g.bounds;
+        }
+        vpsc.computeGroupBounds = computeGroupBounds;
+        var Rectangle = (function () {
+            function Rectangle(x, X, y, Y) {
+                this.x = x;
+                this.X = X;
+                this.y = y;
+                this.Y = Y;
+            }
+            Rectangle.empty = function () { return new Rectangle(Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY); };
+            Rectangle.prototype.cx = function () { return (this.x + this.X) / 2; };
+            Rectangle.prototype.cy = function () { return (this.y + this.Y) / 2; };
+            Rectangle.prototype.overlapX = function (r) {
+                var ux = this.cx(), vx = r.cx();
+                if (ux <= vx && r.x < this.X)
+                    return this.X - r.x;
+                if (vx <= ux && this.x < r.X)
+                    return r.X - this.x;
+                return 0;
+            };
+            Rectangle.prototype.overlapY = function (r) {
+                var uy = this.cy(), vy = r.cy();
+                if (uy <= vy && r.y < this.Y)
+                    return this.Y - r.y;
+                if (vy <= uy && this.y < r.Y)
+                    return r.Y - this.y;
+                return 0;
+            };
+            Rectangle.prototype.setXCentre = function (cx) {
+                var dx = cx - this.cx();
+                this.x += dx;
+                this.X += dx;
+            };
+            Rectangle.prototype.setYCentre = function (cy) {
+                var dy = cy - this.cy();
+                this.y += dy;
+                this.Y += dy;
+            };
+            Rectangle.prototype.width = function () {
+                return this.X - this.x;
+            };
+            Rectangle.prototype.height = function () {
+                return this.Y - this.y;
+            };
+            Rectangle.prototype.union = function (r) {
+                return new Rectangle(Math.min(this.x, r.x), Math.max(this.X, r.X), Math.min(this.y, r.y), Math.max(this.Y, r.Y));
+            };
+            /**
+             * return any intersection points between the given line and the sides of this rectangle
+             * @method lineIntersection
+             * @param x1 number first x coord of line
+             * @param y1 number first y coord of line
+             * @param x2 number second x coord of line
+             * @param y2 number second y coord of line
+             * @return any intersection points found
+             */
+            Rectangle.prototype.lineIntersections = function (x1, y1, x2, y2) {
+                var sides = [[this.x, this.y, this.X, this.y],
+                    [this.X, this.y, this.X, this.Y],
+                    [this.X, this.Y, this.x, this.Y],
+                    [this.x, this.Y, this.x, this.y]];
+                var intersections = [];
+                for (var i = 0; i < 4; ++i) {
+                    var r = Rectangle.lineIntersection(x1, y1, x2, y2, sides[i][0], sides[i][1], sides[i][2], sides[i][3]);
+                    if (r !== null)
+                        intersections.push({ x: r.x, y: r.y });
+                }
+                return intersections;
+            };
+            /**
+             * return any intersection points between a line extending from the centre of this rectangle to the given point,
+             *  and the sides of this rectangle
+             * @method lineIntersection
+             * @param x2 number second x coord of line
+             * @param y2 number second y coord of line
+             * @return any intersection points found
+             */
+            Rectangle.prototype.rayIntersection = function (x2, y2) {
+                var ints = this.lineIntersections(this.cx(), this.cy(), x2, y2);
+                return ints.length > 0 ? ints[0] : null;
+            };
+            Rectangle.prototype.vertices = function () {
+                return [
+                    { x: this.x, y: this.y },
+                    { x: this.X, y: this.y },
+                    { x: this.X, y: this.Y },
+                    { x: this.x, y: this.Y },
+                    { x: this.x, y: this.y }];
+            };
+            Rectangle.lineIntersection = function (x1, y1, x2, y2, x3, y3, x4, y4) {
+                var dx12 = x2 - x1, dx34 = x4 - x3, dy12 = y2 - y1, dy34 = y4 - y3, denominator = dy34 * dx12 - dx34 * dy12;
+                if (denominator == 0)
+                    return null;
+                var dx31 = x1 - x3, dy31 = y1 - y3, numa = dx34 * dy31 - dy34 * dx31, a = numa / denominator, numb = dx12 * dy31 - dy12 * dx31, b = numb / denominator;
+                if (a >= 0 && a <= 1 && b >= 0 && b <= 1) {
+                    return {
+                        x: x1 + a * dx12,
+                        y: y1 + a * dy12
+                    };
+                }
+                return null;
+            };
+            Rectangle.prototype.inflate = function (pad) {
+                return new Rectangle(this.x - pad, this.X + pad, this.y - pad, this.Y + pad);
+            };
+            return Rectangle;
+        })();
+        vpsc.Rectangle = Rectangle;
+        function makeEdgeBetween(source, target, ah) {
+            var si = source.rayIntersection(target.cx(), target.cy()) || { x: source.cx(), y: source.cy() }, ti = target.rayIntersection(source.cx(), source.cy()) || { x: target.cx(), y: target.cy() }, dx = ti.x - si.x, dy = ti.y - si.y, l = Math.sqrt(dx * dx + dy * dy), al = l - ah;
+            return {
+                sourceIntersection: si,
+                targetIntersection: ti,
+                arrowStart: { x: si.x + al * dx / l, y: si.y + al * dy / l }
+            };
+        }
+        vpsc.makeEdgeBetween = makeEdgeBetween;
+        function makeEdgeTo(s, target, ah) {
+            var ti = target.rayIntersection(s.x, s.y);
+            if (!ti)
+                ti = { x: target.cx(), y: target.cy() };
+            var dx = ti.x - s.x, dy = ti.y - s.y, l = Math.sqrt(dx * dx + dy * dy);
+            return { x: ti.x - ah * dx / l, y: ti.y - ah * dy / l };
+        }
+        vpsc.makeEdgeTo = makeEdgeTo;
+        var Node = (function () {
+            function Node(v, r, pos) {
+                this.v = v;
+                this.r = r;
+                this.pos = pos;
+                this.prev = makeRBTree();
+                this.next = makeRBTree();
+            }
+            return Node;
+        })();
+        var Event = (function () {
+            function Event(isOpen, v, pos) {
+                this.isOpen = isOpen;
+                this.v = v;
+                this.pos = pos;
+            }
+            return Event;
+        })();
+        function compareEvents(a, b) {
+            if (a.pos > b.pos) {
+                return 1;
+            }
+            if (a.pos < b.pos) {
+                return -1;
+            }
+            if (a.isOpen) {
+                // open must come before close
+                return -1;
+            }
+            if (b.isOpen) {
+                // open must come before close
+                return 1;
+            }
+            return 0;
+        }
+        function makeRBTree() {
+            return new vpsc.RBTree(function (a, b) { return a.pos - b.pos; });
+        }
+        var xRect = {
+            getCentre: function (r) { return r.cx(); },
+            getOpen: function (r) { return r.y; },
+            getClose: function (r) { return r.Y; },
+            getSize: function (r) { return r.width(); },
+            makeRect: function (open, close, center, size) { return new Rectangle(center - size / 2, center + size / 2, open, close); },
+            findNeighbours: findXNeighbours
+        };
+        var yRect = {
+            getCentre: function (r) { return r.cy(); },
+            getOpen: function (r) { return r.x; },
+            getClose: function (r) { return r.X; },
+            getSize: function (r) { return r.height(); },
+            makeRect: function (open, close, center, size) { return new Rectangle(open, close, center - size / 2, center + size / 2); },
+            findNeighbours: findYNeighbours
+        };
+        function generateGroupConstraints(root, f, minSep, isContained) {
+            if (isContained === void 0) { isContained = false; }
+            var padding = root.padding, gn = typeof root.groups !== 'undefined' ? root.groups.length : 0, ln = typeof root.leaves !== 'undefined' ? root.leaves.length : 0, childConstraints = !gn ? []
+                : root.groups.reduce(function (ccs, g) { return ccs.concat(generateGroupConstraints(g, f, minSep, true)); }, []), n = (isContained ? 2 : 0) + ln + gn, vs = new Array(n), rs = new Array(n), i = 0, add = function (r, v) { rs[i] = r; vs[i++] = v; };
+            if (isContained) {
+                // if this group is contained by another, then we add two dummy vars and rectangles for the borders
+                var b = root.bounds, c = f.getCentre(b), s = f.getSize(b) / 2, open = f.getOpen(b), close = f.getClose(b), min = c - s + padding / 2, max = c + s - padding / 2;
+                root.minVar.desiredPosition = min;
+                add(f.makeRect(open, close, min, padding), root.minVar);
+                root.maxVar.desiredPosition = max;
+                add(f.makeRect(open, close, max, padding), root.maxVar);
+            }
+            if (ln)
+                root.leaves.forEach(function (l) { return add(l.bounds, l.variable); });
+            if (gn)
+                root.groups.forEach(function (g) {
+                    var b = g.bounds;
+                    add(f.makeRect(f.getOpen(b), f.getClose(b), f.getCentre(b), f.getSize(b)), g.minVar);
+                });
+            var cs = generateConstraints(rs, vs, f, minSep);
+            if (gn) {
+                vs.forEach(function (v) { v.cOut = [], v.cIn = []; });
+                cs.forEach(function (c) { c.left.cOut.push(c), c.right.cIn.push(c); });
+                root.groups.forEach(function (g) {
+                    var gapAdjustment = (g.padding - f.getSize(g.bounds)) / 2;
+                    g.minVar.cIn.forEach(function (c) { return c.gap += gapAdjustment; });
+                    g.minVar.cOut.forEach(function (c) { c.left = g.maxVar; c.gap += gapAdjustment; });
+                });
+            }
+            return childConstraints.concat(cs);
+        }
+        function generateConstraints(rs, vars, rect, minSep) {
+            var i, n = rs.length;
+            var N = 2 * n;
+            console.assert(vars.length >= n);
+            var events = new Array(N);
+            for (i = 0; i < n; ++i) {
+                var r = rs[i];
+                var v = new Node(vars[i], r, rect.getCentre(r));
+                events[i] = new Event(true, v, rect.getOpen(r));
+                events[i + n] = new Event(false, v, rect.getClose(r));
+            }
+            events.sort(compareEvents);
+            var cs = new Array();
+            var scanline = makeRBTree();
+            for (i = 0; i < N; ++i) {
+                var e = events[i];
+                var v = e.v;
+                if (e.isOpen) {
+                    scanline.insert(v);
+                    rect.findNeighbours(v, scanline);
+                }
+                else {
+                    // close event
+                    scanline.remove(v);
+                    var makeConstraint = function (l, r) {
+                        var sep = (rect.getSize(l.r) + rect.getSize(r.r)) / 2 + minSep;
+                        cs.push(new vpsc.Constraint(l.v, r.v, sep));
+                    };
+                    var visitNeighbours = function (forward, reverse, mkcon) {
+                        var u, it = v[forward].iterator();
+                        while ((u = it[forward]()) !== null) {
+                            mkcon(u, v);
+                            u[reverse].remove(v);
+                        }
+                    };
+                    visitNeighbours("prev", "next", function (u, v) { return makeConstraint(u, v); });
+                    visitNeighbours("next", "prev", function (u, v) { return makeConstraint(v, u); });
+                }
+            }
+            console.assert(scanline.size === 0);
+            return cs;
+        }
+        function findXNeighbours(v, scanline) {
+            var f = function (forward, reverse) {
+                var it = scanline.findIter(v);
+                var u;
+                while ((u = it[forward]()) !== null) {
+                    var uovervX = u.r.overlapX(v.r);
+                    if (uovervX <= 0 || uovervX <= u.r.overlapY(v.r)) {
+                        v[forward].insert(u);
+                        u[reverse].insert(v);
+                    }
+                    if (uovervX <= 0) {
+                        break;
+                    }
+                }
+            };
+            f("next", "prev");
+            f("prev", "next");
+        }
+        function findYNeighbours(v, scanline) {
+            var f = function (forward, reverse) {
+                var u = scanline.findIter(v)[forward]();
+                if (u !== null && u.r.overlapX(v.r) > 0) {
+                    v[forward].insert(u);
+                    u[reverse].insert(v);
+                }
+            };
+            f("next", "prev");
+            f("prev", "next");
+        }
+        function generateXConstraints(rs, vars) {
+            return generateConstraints(rs, vars, xRect, 1e-6);
+        }
+        vpsc.generateXConstraints = generateXConstraints;
+        function generateYConstraints(rs, vars) {
+            return generateConstraints(rs, vars, yRect, 1e-6);
+        }
+        vpsc.generateYConstraints = generateYConstraints;
+        function generateXGroupConstraints(root) {
+            return generateGroupConstraints(root, xRect, 1e-6);
+        }
+        vpsc.generateXGroupConstraints = generateXGroupConstraints;
+        function generateYGroupConstraints(root) {
+            return generateGroupConstraints(root, yRect, 1e-6);
+        }
+        vpsc.generateYGroupConstraints = generateYGroupConstraints;
+        function removeOverlaps(rs) {
+            var vs = rs.map(function (r) { return new vpsc.Variable(r.cx()); });
+            var cs = vpsc.generateXConstraints(rs, vs);
+            var solver = new vpsc.Solver(vs, cs);
+            solver.solve();
+            vs.forEach(function (v, i) { return rs[i].setXCentre(v.position()); });
+            vs = rs.map(function (r) { return new vpsc.Variable(r.cy()); });
+            cs = vpsc.generateYConstraints(rs, vs);
+            solver = new vpsc.Solver(vs, cs);
+            solver.solve();
+            vs.forEach(function (v, i) { return rs[i].setYCentre(v.position()); });
+        }
+        vpsc.removeOverlaps = removeOverlaps;
+        var IndexedVariable = (function (_super) {
+            __extends(IndexedVariable, _super);
+            function IndexedVariable(index, w) {
+                _super.call(this, 0, w);
+                this.index = index;
+            }
+            return IndexedVariable;
+        })(vpsc.Variable);
+        vpsc.IndexedVariable = IndexedVariable;
+        var Projection = (function () {
+            function Projection(nodes, groups, rootGroup, constraints, avoidOverlaps) {
+                var _this = this;
+                if (rootGroup === void 0) { rootGroup = null; }
+                if (constraints === void 0) { constraints = null; }
+                if (avoidOverlaps === void 0) { avoidOverlaps = false; }
+                this.nodes = nodes;
+                this.groups = groups;
+                this.rootGroup = rootGroup;
+                this.avoidOverlaps = avoidOverlaps;
+                this.variables = nodes.map(function (v, i) {
+                    return v.variable = new IndexedVariable(i, 1);
+                });
+                if (constraints)
+                    this.createConstraints(constraints);
+                if (avoidOverlaps && rootGroup && typeof rootGroup.groups !== 'undefined') {
+                    nodes.forEach(function (v) {
+                        if (!v.width || !v.height) {
+                            //If undefined, default to nothing
+                            v.bounds = new vpsc.Rectangle(v.x, v.x, v.y, v.y);
+                            return;
+                        }
+                        var w2 = v.width / 2, h2 = v.height / 2;
+                        v.bounds = new vpsc.Rectangle(v.x - w2, v.x + w2, v.y - h2, v.y + h2);
+                    });
+                    computeGroupBounds(rootGroup);
+                    var i = nodes.length;
+                    groups.forEach(function (g) {
+                        _this.variables[i] = g.minVar = new IndexedVariable(i++, typeof g.stiffness !== "undefined" ? g.stiffness : 0.01);
+                        _this.variables[i] = g.maxVar = new IndexedVariable(i++, typeof g.stiffness !== "undefined" ? g.stiffness : 0.01);
+                    });
+                }
+            }
+            Projection.prototype.createSeparation = function (c) {
+                return new vpsc.Constraint(this.nodes[c.left].variable, this.nodes[c.right].variable, c.gap, typeof c.equality !== "undefined" ? c.equality : false);
+            };
+            Projection.prototype.makeFeasible = function (c) {
+                var _this = this;
+                if (!this.avoidOverlaps)
+                    return;
+                var axis = 'x', dim = 'width';
+                if (c.axis === 'x')
+                    axis = 'y', dim = 'height';
+                var vs = c.offsets.map(function (o) { return _this.nodes[o.node]; }).sort(function (a, b) { return a[axis] - b[axis]; });
+                var p = null;
+                vs.forEach(function (v) {
+                    if (p)
+                        v[axis] = p[axis] + p[dim] + 1;
+                    p = v;
+                });
+            };
+            Projection.prototype.createAlignment = function (c) {
+                var _this = this;
+                var u = this.nodes[c.offsets[0].node].variable;
+                this.makeFeasible(c);
+                var cs = c.axis === 'x' ? this.xConstraints : this.yConstraints;
+                c.offsets.slice(1).forEach(function (o) {
+                    var v = _this.nodes[o.node].variable;
+                    cs.push(new vpsc.Constraint(u, v, o.offset, true));
+                });
+            };
+            Projection.prototype.createConstraints = function (constraints) {
+                var _this = this;
+                var isSep = function (c) { return typeof c.type === 'undefined' || c.type === 'separation'; };
+                this.xConstraints = constraints
+                    .filter(function (c) { return c.axis === "x" && isSep(c); })
+                    .map(function (c) { return _this.createSeparation(c); });
+                this.yConstraints = constraints
+                    .filter(function (c) { return c.axis === "y" && isSep(c); })
+                    .map(function (c) { return _this.createSeparation(c); });
+                constraints
+                    .filter(function (c) { return c.type === 'alignment'; })
+                    .forEach(function (c) { return _this.createAlignment(c); });
+            };
+            Projection.prototype.setupVariablesAndBounds = function (x0, y0, desired, getDesired) {
+                this.nodes.forEach(function (v, i) {
+                    if (v.fixed) {
+                        v.variable.weight = v.fixedWeight ? v.fixedWeight : 1000;
+                        desired[i] = getDesired(v);
+                    }
+                    else {
+                        v.variable.weight = 1;
+                    }
+                    var w = (v.width || 0) / 2, h = (v.height || 0) / 2;
+                    var ix = x0[i], iy = y0[i];
+                    v.bounds = new Rectangle(ix - w, ix + w, iy - h, iy + h);
+                });
+            };
+            Projection.prototype.xProject = function (x0, y0, x) {
+                if (!this.rootGroup && !(this.avoidOverlaps || this.xConstraints))
+                    return;
+                this.project(x0, y0, x0, x, function (v) { return v.px; }, this.xConstraints, generateXGroupConstraints, function (v) { return v.bounds.setXCentre(x[v.variable.index] = v.variable.position()); }, function (g) {
+                    var xmin = x[g.minVar.index] = g.minVar.position();
+                    var xmax = x[g.maxVar.index] = g.maxVar.position();
+                    var p2 = g.padding / 2;
+                    g.bounds.x = xmin - p2;
+                    g.bounds.X = xmax + p2;
+                });
+            };
+            Projection.prototype.yProject = function (x0, y0, y) {
+                if (!this.rootGroup && !this.yConstraints)
+                    return;
+                this.project(x0, y0, y0, y, function (v) { return v.py; }, this.yConstraints, generateYGroupConstraints, function (v) { return v.bounds.setYCentre(y[v.variable.index] = v.variable.position()); }, function (g) {
+                    var ymin = y[g.minVar.index] = g.minVar.position();
+                    var ymax = y[g.maxVar.index] = g.maxVar.position();
+                    var p2 = g.padding / 2;
+                    g.bounds.y = ymin - p2;
+                    ;
+                    g.bounds.Y = ymax + p2;
+                });
+            };
+            Projection.prototype.projectFunctions = function () {
+                var _this = this;
+                return [
+                    function (x0, y0, x) { return _this.xProject(x0, y0, x); },
+                    function (x0, y0, y) { return _this.yProject(x0, y0, y); }
+                ];
+            };
+            Projection.prototype.project = function (x0, y0, start, desired, getDesired, cs, generateConstraints, updateNodeBounds, updateGroupBounds) {
+                this.setupVariablesAndBounds(x0, y0, desired, getDesired);
+                if (this.rootGroup && this.avoidOverlaps) {
+                    computeGroupBounds(this.rootGroup);
+                    cs = cs.concat(generateConstraints(this.rootGroup));
+                }
+                this.solve(this.variables, cs, start, desired);
+                this.nodes.forEach(updateNodeBounds);
+                if (this.rootGroup && this.avoidOverlaps) {
+                    this.groups.forEach(updateGroupBounds);
+                }
+            };
+            Projection.prototype.solve = function (vs, cs, starting, desired) {
+                var solver = new vpsc.Solver(vs, cs);
+                solver.setStartingPositions(starting);
+                solver.setDesiredPositions(desired);
+                solver.solve();
+            };
+            return Projection;
+        })();
+        vpsc.Projection = Projection;
+    })(vpsc = cola.vpsc || (cola.vpsc = {}));
+})(cola || (cola = {}));
+///<reference path="vpsc.ts"/>
+///<reference path="rectangle.ts"/>
+var cola;
+(function (cola) {
+    var geom;
+    (function (geom) {
+        var Point = (function () {
+            function Point() {
+            }
+            return Point;
+        })();
+        geom.Point = Point;
+        var LineSegment = (function () {
+            function LineSegment(x1, y1, x2, y2) {
+                this.x1 = x1;
+                this.y1 = y1;
+                this.x2 = x2;
+                this.y2 = y2;
+            }
+            return LineSegment;
+        })();
+        geom.LineSegment = LineSegment;
+        var PolyPoint = (function (_super) {
+            __extends(PolyPoint, _super);
+            function PolyPoint() {
+                _super.apply(this, arguments);
+            }
+            return PolyPoint;
+        })(Point);
+        geom.PolyPoint = PolyPoint;
+        /** tests if a point is Left|On|Right of an infinite line.
+         * @param points P0, P1, and P2
+         * @return >0 for P2 left of the line through P0 and P1
+         *            =0 for P2 on the line
+         *            <0 for P2 right of the line
+         */
+        function isLeft(P0, P1, P2) {
+            return (P1.x - P0.x) * (P2.y - P0.y) - (P2.x - P0.x) * (P1.y - P0.y);
+        }
+        geom.isLeft = isLeft;
+        function above(p, vi, vj) {
+            return isLeft(p, vi, vj) > 0;
+        }
+        function below(p, vi, vj) {
+            return isLeft(p, vi, vj) < 0;
+        }
+        /**
+         * returns the convex hull of a set of points using Andrew's monotone chain algorithm
+         * see: http://geomalgorithms.com/a10-_hull-1.html#Monotone%20Chain
+         * @param S array of points
+         * @return the convex hull as an array of points
+         */
+        function ConvexHull(S) {
+            var P = S.slice(0).sort(function (a, b) { return a.x !== b.x ? b.x - a.x : b.y - a.y; });
+            var n = S.length, i;
+            var minmin = 0;
+            var xmin = P[0].x;
+            for (i = 1; i < n; ++i) {
+                if (P[i].x !== xmin)
+                    break;
+            }
+            var minmax = i - 1;
+            var H = [];
+            H.push(P[minmin]); // push minmin point onto stack
+            if (minmax === n - 1) {
+                if (P[minmax].y !== P[minmin].y)
+                    H.push(P[minmax]);
+            }
+            else {
+                // Get the indices of points with max x-coord and min|max y-coord
+                var maxmin, maxmax = n - 1;
+                var xmax = P[n - 1].x;
+                for (i = n - 2; i >= 0; i--)
+                    if (P[i].x !== xmax)
+                        break;
+                maxmin = i + 1;
+                // Compute the lower hull on the stack H
+                i = minmax;
+                while (++i <= maxmin) {
+                    // the lower line joins P[minmin]  with P[maxmin]
+                    if (isLeft(P[minmin], P[maxmin], P[i]) >= 0 && i < maxmin)
+                        continue; // ignore P[i] above or on the lower line
+                    while (H.length > 1) {
+                        // test if  P[i] is left of the line at the stack top
+                        if (isLeft(H[H.length - 2], H[H.length - 1], P[i]) > 0)
+                            break; // P[i] is a new hull  vertex
+                        else
+                            H.length -= 1; // pop top point off  stack
+                    }
+                    if (i != minmin)
+                        H.push(P[i]);
+                }
+                // Next, compute the upper hull on the stack H above the bottom hull
+                if (maxmax != maxmin)
+                    H.push(P[maxmax]); // push maxmax point onto stack
+                var bot = H.length; // the bottom point of the upper hull stack
+                i = maxmin;
+                while (--i >= minmax) {
+                    // the upper line joins P[maxmax]  with P[minmax]
+                    if (isLeft(P[maxmax], P[minmax], P[i]) >= 0 && i > minmax)
+                        continue; // ignore P[i] below or on the upper line
+                    while (H.length > bot) {
+                        // test if  P[i] is left of the line at the stack top
+                        if (isLeft(H[H.length - 2], H[H.length - 1], P[i]) > 0)
+                            break; // P[i] is a new hull  vertex
+                        else
+                            H.length -= 1; // pop top point off  stack
+                    }
+                    if (i != minmin)
+                        H.push(P[i]); // push P[i] onto stack
+                }
+            }
+            return H;
+        }
+        geom.ConvexHull = ConvexHull;
+        // apply f to the points in P in clockwise order around the point p
+        function clockwiseRadialSweep(p, P, f) {
+            P.slice(0).sort(function (a, b) { return Math.atan2(a.y - p.y, a.x - p.x) - Math.atan2(b.y - p.y, b.x - p.x); }).forEach(f);
+        }
+        geom.clockwiseRadialSweep = clockwiseRadialSweep;
+        function nextPolyPoint(p, ps) {
+            if (p.polyIndex === ps.length - 1)
+                return ps[0];
+            return ps[p.polyIndex + 1];
+        }
+        function prevPolyPoint(p, ps) {
+            if (p.polyIndex === 0)
+                return ps[ps.length - 1];
+            return ps[p.polyIndex - 1];
+        }
+        // tangent_PointPolyC(): fast binary search for tangents to a convex polygon
+        //    Input:  P = a 2D point (exterior to the polygon)
+        //            n = number of polygon vertices
+        //            V = array of vertices for a 2D convex polygon with V[n] = V[0]
+        //    Output: rtan = index of rightmost tangent point V[rtan]
+        //            ltan = index of leftmost tangent point V[ltan]
+        function tangent_PointPolyC(P, V) {
+            return { rtan: Rtangent_PointPolyC(P, V), ltan: Ltangent_PointPolyC(P, V) };
+        }
+        // Rtangent_PointPolyC(): binary search for convex polygon right tangent
+        //    Input:  P = a 2D point (exterior to the polygon)
+        //            n = number of polygon vertices
+        //            V = array of vertices for a 2D convex polygon with V[n] = V[0]
+        //    Return: index "i" of rightmost tangent point V[i]
+        function Rtangent_PointPolyC(P, V) {
+            var n = V.length - 1;
+            // use binary search for large convex polygons
+            var a, b, c; // indices for edge chain endpoints
+            var upA, dnC; // test for up direction of edges a and c
+            // rightmost tangent = maximum for the isLeft() ordering
+            // test if V[0] is a local maximum
+            if (below(P, V[1], V[0]) && !above(P, V[n - 1], V[0]))
+                return 0; // V[0] is the maximum tangent point
+            for (a = 0, b = n;;) {
+                if (b - a === 1)
+                    if (above(P, V[a], V[b]))
+                        return a;
+                    else
+                        return b;
+                c = Math.floor((a + b) / 2); // midpoint of [a,b], and 0<c<n
+                dnC = below(P, V[c + 1], V[c]);
+                if (dnC && !above(P, V[c - 1], V[c]))
+                    return c; // V[c] is the maximum tangent point
+                // no max yet, so continue with the binary search
+                // pick one of the two subchains [a,c] or [c,b]
+                upA = above(P, V[a + 1], V[a]);
+                if (upA) {
+                    if (dnC)
+                        b = c; // select [a,c]
+                    else {
+                        if (above(P, V[a], V[c]))
+                            b = c; // select [a,c]
+                        else
+                            a = c; // select [c,b]
+                    }
+                }
+                else {
+                    if (!dnC)
+                        a = c; // select [c,b]
+                    else {
+                        if (below(P, V[a], V[c]))
+                            b = c; // select [a,c]
+                        else
+                            a = c; // select [c,b]
+                    }
+                }
+            }
+        }
+        // Ltangent_PointPolyC(): binary search for convex polygon left tangent
+        //    Input:  P = a 2D point (exterior to the polygon)
+        //            n = number of polygon vertices
+        //            V = array of vertices for a 2D convex polygon with V[n]=V[0]
+        //    Return: index "i" of leftmost tangent point V[i]
+        function Ltangent_PointPolyC(P, V) {
+            var n = V.length - 1;
+            // use binary search for large convex polygons
+            var a, b, c; // indices for edge chain endpoints
+            var dnA, dnC; // test for down direction of edges a and c
+            // leftmost tangent = minimum for the isLeft() ordering
+            // test if V[0] is a local minimum
+            if (above(P, V[n - 1], V[0]) && !below(P, V[1], V[0]))
+                return 0; // V[0] is the minimum tangent point
+            for (a = 0, b = n;;) {
+                if (b - a === 1)
+                    if (below(P, V[a], V[b]))
+                        return a;
+                    else
+                        return b;
+                c = Math.floor((a + b) / 2); // midpoint of [a,b], and 0<c<n
+                dnC = below(P, V[c + 1], V[c]);
+                if (above(P, V[c - 1], V[c]) && !dnC)
+                    return c; // V[c] is the minimum tangent point
+                // no min yet, so continue with the binary search
+                // pick one of the two subchains [a,c] or [c,b]
+                dnA = below(P, V[a + 1], V[a]);
+                if (dnA) {
+                    if (!dnC)
+                        b = c; // select [a,c]
+                    else {
+                        if (below(P, V[a], V[c]))
+                            b = c; // select [a,c]
+                        else
+                            a = c; // select [c,b]
+                    }
+                }
+                else {
+                    if (dnC)
+                        a = c; // select [c,b]
+                    else {
+                        if (above(P, V[a], V[c]))
+                            b = c; // select [a,c]
+                        else
+                            a = c; // select [c,b]
+                    }
+                }
+            }
+        }
+        // RLtangent_PolyPolyC(): get the RL tangent between two convex polygons
+        //    Input:  m = number of vertices in polygon 1
+        //            V = array of vertices for convex polygon 1 with V[m]=V[0]
+        //            n = number of vertices in polygon 2
+        //            W = array of vertices for convex polygon 2 with W[n]=W[0]
+        //    Output: *t1 = index of tangent point V[t1] for polygon 1
+        //            *t2 = index of tangent point W[t2] for polygon 2
+        function tangent_PolyPolyC(V, W, t1, t2, cmp1, cmp2) {
+            var ix1, ix2; // search indices for polygons 1 and 2
+            // first get the initial vertex on each polygon
+            ix1 = t1(W[0], V); // right tangent from W[0] to V
+            ix2 = t2(V[ix1], W); // left tangent from V[ix1] to W
+            // ping-pong linear search until it stabilizes
+            var done = false; // flag when done
+            while (!done) {
+                done = true; // assume done until...
+                while (true) {
+                    if (ix1 === V.length - 1)
+                        ix1 = 0;
+                    if (cmp1(W[ix2], V[ix1], V[ix1 + 1]))
+                        break;
+                    ++ix1; // get Rtangent from W[ix2] to V
+                }
+                while (true) {
+                    if (ix2 === 0)
+                        ix2 = W.length - 1;
+                    if (cmp2(V[ix1], W[ix2], W[ix2 - 1]))
+                        break;
+                    --ix2; // get Ltangent from V[ix1] to W
+                    done = false; // not done if had to adjust this
+                }
+            }
+            return { t1: ix1, t2: ix2 };
+        }
+        geom.tangent_PolyPolyC = tangent_PolyPolyC;
+        function LRtangent_PolyPolyC(V, W) {
+            var rl = RLtangent_PolyPolyC(W, V);
+            return { t1: rl.t2, t2: rl.t1 };
+        }
+        geom.LRtangent_PolyPolyC = LRtangent_PolyPolyC;
+        function RLtangent_PolyPolyC(V, W) {
+            return tangent_PolyPolyC(V, W, Rtangent_PointPolyC, Ltangent_PointPolyC, above, below);
+        }
+        geom.RLtangent_PolyPolyC = RLtangent_PolyPolyC;
+        function LLtangent_PolyPolyC(V, W) {
+            return tangent_PolyPolyC(V, W, Ltangent_PointPolyC, Ltangent_PointPolyC, below, below);
+        }
+        geom.LLtangent_PolyPolyC = LLtangent_PolyPolyC;
+        function RRtangent_PolyPolyC(V, W) {
+            return tangent_PolyPolyC(V, W, Rtangent_PointPolyC, Rtangent_PointPolyC, above, above);
+        }
+        geom.RRtangent_PolyPolyC = RRtangent_PolyPolyC;
+        var BiTangent = (function () {
+            function BiTangent(t1, t2) {
+                this.t1 = t1;
+                this.t2 = t2;
+            }
+            return BiTangent;
+        })();
+        geom.BiTangent = BiTangent;
+        var BiTangents = (function () {
+            function BiTangents() {
+            }
+            return BiTangents;
+        })();
+        geom.BiTangents = BiTangents;
+        var TVGPoint = (function (_super) {
+            __extends(TVGPoint, _super);
+            function TVGPoint() {
+                _super.apply(this, arguments);
+            }
+            return TVGPoint;
+        })(Point);
+        geom.TVGPoint = TVGPoint;
+        var VisibilityVertex = (function () {
+            function VisibilityVertex(id, polyid, polyvertid, p) {
+                this.id = id;
+                this.polyid = polyid;
+                this.polyvertid = polyvertid;
+                this.p = p;
+                p.vv = this;
+            }
+            return VisibilityVertex;
+        })();
+        geom.VisibilityVertex = VisibilityVertex;
+        var VisibilityEdge = (function () {
+            function VisibilityEdge(source, target) {
+                this.source = source;
+                this.target = target;
+            }
+            VisibilityEdge.prototype.length = function () {
+                var dx = this.source.p.x - this.target.p.x;
+                var dy = this.source.p.y - this.target.p.y;
+                return Math.sqrt(dx * dx + dy * dy);
+            };
+            return VisibilityEdge;
+        })();
+        geom.VisibilityEdge = VisibilityEdge;
+        var TangentVisibilityGraph = (function () {
+            function TangentVisibilityGraph(P, g0) {
+                this.P = P;
+                this.V = [];
+                this.E = [];
+                if (!g0) {
+                    var n = P.length;
+                    for (var i = 0; i < n; i++) {
+                        var p = P[i];
+                        for (var j = 0; j < p.length; ++j) {
+                            var pj = p[j], vv = new VisibilityVertex(this.V.length, i, j, pj);
+                            this.V.push(vv);
+                            if (j > 0)
+                                this.E.push(new VisibilityEdge(p[j - 1].vv, vv));
+                        }
+                    }
+                    for (var i = 0; i < n - 1; i++) {
+                        var Pi = P[i];
+                        for (var j = i + 1; j < n; j++) {
+                            var Pj = P[j], t = geom.tangents(Pi, Pj);
+                            for (var q in t) {
+                                var c = t[q], source = Pi[c.t1], target = Pj[c.t2];
+                                this.addEdgeIfVisible(source, target, i, j);
+                            }
+                        }
+                    }
+                }
+                else {
+                    this.V = g0.V.slice(0);
+                    this.E = g0.E.slice(0);
+                }
+            }
+            TangentVisibilityGraph.prototype.addEdgeIfVisible = function (u, v, i1, i2) {
+                if (!this.intersectsPolys(new LineSegment(u.x, u.y, v.x, v.y), i1, i2)) {
+                    this.E.push(new VisibilityEdge(u.vv, v.vv));
+                }
+            };
+            TangentVisibilityGraph.prototype.addPoint = function (p, i1) {
+                var n = this.P.length;
+                this.V.push(new VisibilityVertex(this.V.length, n, 0, p));
+                for (var i = 0; i < n; ++i) {
+                    if (i === i1)
+                        continue;
+                    var poly = this.P[i], t = tangent_PointPolyC(p, poly);
+                    this.addEdgeIfVisible(p, poly[t.ltan], i1, i);
+                    this.addEdgeIfVisible(p, poly[t.rtan], i1, i);
+                }
+                return p.vv;
+            };
+            TangentVisibilityGraph.prototype.intersectsPolys = function (l, i1, i2) {
+                for (var i = 0, n = this.P.length; i < n; ++i) {
+                    if (i != i1 && i != i2 && intersects(l, this.P[i]).length > 0) {
+                        return true;
+                    }
+                }
+                return false;
+            };
+            return TangentVisibilityGraph;
+        })();
+        geom.TangentVisibilityGraph = TangentVisibilityGraph;
+        function intersects(l, P) {
+            var ints = [];
+            for (var i = 1, n = P.length; i < n; ++i) {
+                var int = cola.vpsc.Rectangle.lineIntersection(l.x1, l.y1, l.x2, l.y2, P[i - 1].x, P[i - 1].y, P[i].x, P[i].y);
+                if (int)
+                    ints.push(int);
+            }
+            return ints;
+        }
+        function tangents(V, W) {
+            var m = V.length - 1, n = W.length - 1;
+            var bt = new BiTangents();
+            for (var i = 0; i < m; ++i) {
+                for (var j = 0; j < n; ++j) {
+                    var v1 = V[i == 0 ? m - 1 : i - 1];
+                    var v2 = V[i];
+                    var v3 = V[i + 1];
+                    var w1 = W[j == 0 ? n - 1 : j - 1];
+                    var w2 = W[j];
+                    var w3 = W[j + 1];
+                    var v1v2w2 = isLeft(v1, v2, w2);
+                    var v2w1w2 = isLeft(v2, w1, w2);
+                    var v2w2w3 = isLeft(v2, w2, w3);
+                    var w1w2v2 = isLeft(w1, w2, v2);
+                    var w2v1v2 = isLeft(w2, v1, v2);
+                    var w2v2v3 = isLeft(w2, v2, v3);
+                    if (v1v2w2 >= 0 && v2w1w2 >= 0 && v2w2w3 < 0
+                        && w1w2v2 >= 0 && w2v1v2 >= 0 && w2v2v3 < 0) {
+                        bt.ll = new BiTangent(i, j);
+                    }
+                    else if (v1v2w2 <= 0 && v2w1w2 <= 0 && v2w2w3 > 0
+                        && w1w2v2 <= 0 && w2v1v2 <= 0 && w2v2v3 > 0) {
+                        bt.rr = new BiTangent(i, j);
+                    }
+                    else if (v1v2w2 <= 0 && v2w1w2 > 0 && v2w2w3 <= 0
+                        && w1w2v2 >= 0 && w2v1v2 < 0 && w2v2v3 >= 0) {
+                        bt.rl = new BiTangent(i, j);
+                    }
+                    else if (v1v2w2 >= 0 && v2w1w2 < 0 && v2w2w3 >= 0
+                        && w1w2v2 <= 0 && w2v1v2 > 0 && w2v2v3 <= 0) {
+                        bt.lr = new BiTangent(i, j);
+                    }
+                }
+            }
+            return bt;
+        }
+        geom.tangents = tangents;
+        function isPointInsidePoly(p, poly) {
+            for (var i = 1, n = poly.length; i < n; ++i)
+                if (below(poly[i - 1], poly[i], p))
+                    return false;
+            return true;
+        }
+        function isAnyPInQ(p, q) {
+            return !p.every(function (v) { return !isPointInsidePoly(v, q); });
+        }
+        function polysOverlap(p, q) {
+            if (isAnyPInQ(p, q))
+                return true;
+            if (isAnyPInQ(q, p))
+                return true;
+            for (var i = 1, n = p.length; i < n; ++i) {
+                var v = p[i], u = p[i - 1];
+                if (intersects(new LineSegment(u.x, u.y, v.x, v.y), q).length > 0)
+                    return true;
+            }
+            return false;
+        }
+        geom.polysOverlap = polysOverlap;
+    })(geom = cola.geom || (cola.geom = {}));
+})(cola || (cola = {}));
+/**
+ * @module cola
+ */
+var cola;
+(function (cola) {
+    /**
+     * Descent respects a collection of locks over nodes that should not move
+     * @class Locks
+     */
+    var Locks = (function () {
+        function Locks() {
+            this.locks = {};
+        }
+        /**
+         * add a lock on the node at index id
+         * @method add
+         * @param id index of node to be locked
+         * @param x required position for node
+         */
+        Locks.prototype.add = function (id, x) {
+            /* DEBUG
+                        if (isNaN(x[0]) || isNaN(x[1])) debugger;
+            DEBUG */
+            this.locks[id] = x;
+        };
+        /**
+         * @method clear clear all locks
+         */
+        Locks.prototype.clear = function () {
+            this.locks = {};
+        };
+        /**
+         * @isEmpty
+         * @returns false if no locks exist
+         */
+        Locks.prototype.isEmpty = function () {
+            for (var l in this.locks)
+                return false;
+            return true;
+        };
+        /**
+         * perform an operation on each lock
+         * @apply
+         */
+        Locks.prototype.apply = function (f) {
+            for (var l in this.locks) {
+                f(l, this.locks[l]);
+            }
+        };
+        return Locks;
+    })();
+    cola.Locks = Locks;
+    /**
+     * Uses a gradient descent approach to reduce a stress or p-stress goal function over a graph with specified ideal edge lengths or a square matrix of dissimilarities.
+     * The standard stress function over a graph nodes with position vectors x,y,z is (mathematica input):
+     *   stress[x_,y_,z_,D_,w_]:=Sum[w[[i,j]] (length[x[[i]],y[[i]],z[[i]],x[[j]],y[[j]],z[[j]]]-d[[i,j]])^2,{i,Length[x]-1},{j,i+1,Length[x]}]
+     * where: D is a square matrix of ideal separations between nodes, w is matrix of weights for those separations
+     *        length[x1_, y1_, z1_, x2_, y2_, z2_] = Sqrt[(x1 - x2)^2 + (y1 - y2)^2 + (z1 - z2)^2]
+     * below, we use wij = 1/(Dij^2)
+     *
+     * @class Descent
+     */
+    var Descent = (function () {
+        /**
+         * @method constructor
+         * @param x {number[][]} initial coordinates for nodes
+         * @param D {number[][]} matrix of desired distances between pairs of nodes
+         * @param G {number[][]} [default=null] if specified, G is a matrix of weights for goal terms between pairs of nodes.
+         * If G[i][j] > 1 and the separation between nodes i and j is greater than their ideal distance, then there is no contribution for this pair to the goal
+         * If G[i][j] <= 1 then it is used as a weighting on the contribution of the variance between ideal and actual separation between i and j to the goal function
+         */
+        function Descent(x, D, G) {
+            if (G === void 0) { G = null; }
+            this.D = D;
+            this.G = G;
+            this.threshold = 0.0001;
+            // Parameters for grid snap stress.
+            // TODO: Make a pluggable "StressTerm" class instead of this
+            // mess.
+            this.numGridSnapNodes = 0;
+            this.snapGridSize = 100;
+            this.snapStrength = 1000;
+            this.scaleSnapByMaxH = false;
+            this.random = new PseudoRandom();
+            this.project = null;
+            this.x = x;
+            this.k = x.length; // dimensionality
+            var n = this.n = x[0].length; // number of nodes
+            this.H = new Array(this.k);
+            this.g = new Array(this.k);
+            this.Hd = new Array(this.k);
+            this.a = new Array(this.k);
+            this.b = new Array(this.k);
+            this.c = new Array(this.k);
+            this.d = new Array(this.k);
+            this.e = new Array(this.k);
+            this.ia = new Array(this.k);
+            this.ib = new Array(this.k);
+            this.xtmp = new Array(this.k);
+            this.locks = new Locks();
+            this.minD = Number.MAX_VALUE;
+            var i = n, j;
+            while (i--) {
+                j = n;
+                while (--j > i) {
+                    var d = D[i][j];
+                    if (d > 0 && d < this.minD) {
+                        this.minD = d;
+                    }
+                }
+            }
+            if (this.minD === Number.MAX_VALUE)
+                this.minD = 1;
+            i = this.k;
+            while (i--) {
+                this.g[i] = new Array(n);
+                this.H[i] = new Array(n);
+                j = n;
+                while (j--) {
+                    this.H[i][j] = new Array(n);
+                }
+                this.Hd[i] = new Array(n);
+                this.a[i] = new Array(n);
+                this.b[i] = new Array(n);
+                this.c[i] = new Array(n);
+                this.d[i] = new Array(n);
+                this.e[i] = new Array(n);
+                this.ia[i] = new Array(n);
+                this.ib[i] = new Array(n);
+                this.xtmp[i] = new Array(n);
+            }
+        }
+        Descent.createSquareMatrix = function (n, f) {
+            var M = new Array(n);
+            for (var i = 0; i < n; ++i) {
+                M[i] = new Array(n);
+                for (var j = 0; j < n; ++j) {
+                    M[i][j] = f(i, j);
+                }
+            }
+            return M;
+        };
+        Descent.prototype.offsetDir = function () {
+            var _this = this;
+            var u = new Array(this.k);
+            var l = 0;
+            for (var i = 0; i < this.k; ++i) {
+                var x = u[i] = this.random.getNextBetween(0.01, 1) - 0.5;
+                l += x * x;
+            }
+            l = Math.sqrt(l);
+            return u.map(function (x) { return x *= _this.minD / l; });
+        };
+        // compute first and second derivative information storing results in this.g and this.H
+        Descent.prototype.computeDerivatives = function (x) {
+            var _this = this;
+            var n = this.n;
+            if (n < 1)
+                return;
+            var i;
+            /* DEBUG
+                        for (var u: number = 0; u < n; ++u)
+                            for (i = 0; i < this.k; ++i)
+                                if (isNaN(x[i][u])) debugger;
+            DEBUG */
+            var d = new Array(this.k);
+            var d2 = new Array(this.k);
+            var Huu = new Array(this.k);
+            var maxH = 0;
+            for (var u = 0; u < n; ++u) {
+                for (i = 0; i < this.k; ++i)
+                    Huu[i] = this.g[i][u] = 0;
+                for (var v = 0; v < n; ++v) {
+                    if (u === v)
+                        continue;
+                    // The following loop randomly displaces nodes that are at identical positions
+                    var maxDisplaces = n; // avoid infinite loop in the case of numerical issues, such as huge values
+                    while (maxDisplaces--) {
+                        var sd2 = 0;
+                        for (i = 0; i < this.k; ++i) {
+                            var dx = d[i] = x[i][u] - x[i][v];
+                            sd2 += d2[i] = dx * dx;
+                        }
+                        if (sd2 > 1e-9)
+                            break;
+                        var rd = this.offsetDir();
+                        for (i = 0; i < this.k; ++i)
+                            x[i][v] += rd[i];
+                    }
+                    var l = Math.sqrt(sd2);
+                    var D = this.D[u][v];
+                    var weight = this.G != null ? this.G[u][v] : 1;
+                    if (weight > 1 && l > D || !isFinite(D)) {
+                        for (i = 0; i < this.k; ++i)
+                            this.H[i][u][v] = 0;
+                        continue;
+                    }
+                    if (weight > 1) {
+                        weight = 1;
+                    }
+                    var D2 = D * D;
+                    var gs = 2 * weight * (l - D) / (D2 * l);
+                    var l3 = l * l * l;
+                    var hs = 2 * -weight / (D2 * l3);
+                    if (!isFinite(gs))
+                        console.log(gs);
+                    for (i = 0; i < this.k; ++i) {
+                        this.g[i][u] += d[i] * gs;
+                        Huu[i] -= this.H[i][u][v] = hs * (l3 + D * (d2[i] - sd2) + l * sd2);
+                    }
+                }
+                for (i = 0; i < this.k; ++i)
+                    maxH = Math.max(maxH, this.H[i][u][u] = Huu[i]);
+            }
+            // Grid snap forces
+            var r = this.snapGridSize / 2;
+            var g = this.snapGridSize;
+            var w = this.snapStrength;
+            var k = w / (r * r);
+            var numNodes = this.numGridSnapNodes;
+            //var numNodes = n;
+            for (var u = 0; u < numNodes; ++u) {
+                for (i = 0; i < this.k; ++i) {
+                    var xiu = this.x[i][u];
+                    var m = xiu / g;
+                    var f = m % 1;
+                    var q = m - f;
+                    var a = Math.abs(f);
+                    var dx = (a <= 0.5) ? xiu - q * g :
+                        (xiu > 0) ? xiu - (q + 1) * g : xiu - (q - 1) * g;
+                    if (-r < dx && dx <= r) {
+                        if (this.scaleSnapByMaxH) {
+                            this.g[i][u] += maxH * k * dx;
+                            this.H[i][u][u] += maxH * k;
+                        }
+                        else {
+                            this.g[i][u] += k * dx;
+                            this.H[i][u][u] += k;
+                        }
+                    }
+                }
+            }
+            if (!this.locks.isEmpty()) {
+                this.locks.apply(function (u, p) {
+                    for (i = 0; i < _this.k; ++i) {
+                        _this.H[i][u][u] += maxH;
+                        _this.g[i][u] -= maxH * (p[i] - x[i][u]);
+                    }
+                });
+            }
+            /* DEBUG
+                        for (var u: number = 0; u < n; ++u)
+                            for (i = 0; i < this.k; ++i) {
+                                if (isNaN(this.g[i][u])) debugger;
+                                for (var v: number = 0; v < n; ++v)
+                                    if (isNaN(this.H[i][u][v])) debugger;
+                            }
+            DEBUG */
+        };
+        Descent.dotProd = function (a, b) {
+            var x = 0, i = a.length;
+            while (i--)
+                x += a[i] * b[i];
+            return x;
+        };
+        // result r = matrix m * vector v
+        Descent.rightMultiply = function (m, v, r) {
+            var i = m.length;
+            while (i--)
+                r[i] = Descent.dotProd(m[i], v);
+        };
+        // computes the optimal step size to take in direction d using the
+        // derivative information in this.g and this.H
+        // returns the scalar multiplier to apply to d to get the optimal step
+        Descent.prototype.computeStepSize = function (d) {
+            var numerator = 0, denominator = 0;
+            for (var i = 0; i < this.k; ++i) {
+                numerator += Descent.dotProd(this.g[i], d[i]);
+                Descent.rightMultiply(this.H[i], d[i], this.Hd[i]);
+                denominator += Descent.dotProd(d[i], this.Hd[i]);
+            }
+            if (denominator === 0 || !isFinite(denominator))
+                return 0;
+            return 1 * numerator / denominator;
+        };
+        Descent.prototype.reduceStress = function () {
+            this.computeDerivatives(this.x);
+            var alpha = this.computeStepSize(this.g);
+            for (var i = 0; i < this.k; ++i) {
+                this.takeDescentStep(this.x[i], this.g[i], alpha);
+            }
+            return this.computeStress();
+        };
+        Descent.copy = function (a, b) {
+            var m = a.length, n = b[0].length;
+            for (var i = 0; i < m; ++i) {
+                for (var j = 0; j < n; ++j) {
+                    b[i][j] = a[i][j];
+                }
+            }
+        };
+        // takes a step of stepSize * d from x0, and then project against any constraints.
+        // result is returned in r.
+        // x0: starting positions
+        // r: result positions will be returned here
+        // d: unconstrained descent vector
+        // stepSize: amount to step along d
+        Descent.prototype.stepAndProject = function (x0, r, d, stepSize) {
+            Descent.copy(x0, r);
+            this.takeDescentStep(r[0], d[0], stepSize);
+            if (this.project)
+                this.project[0](x0[0], x0[1], r[0]);
+            this.takeDescentStep(r[1], d[1], stepSize);
+            if (this.project)
+                this.project[1](r[0], x0[1], r[1]);
+            // todo: allow projection against constraints in higher dimensions
+            for (var i = 2; i < this.k; i++)
+                this.takeDescentStep(r[i], d[i], stepSize);
+            // the following makes locks extra sticky... but hides the result of the projection from the consumer
+            //if (!this.locks.isEmpty()) {
+            //    this.locks.apply((u, p) => {
+            //        for (var i = 0; i < this.k; i++) {
+            //            r[i][u] = p[i];
+            //        }
+            //    });
+            //}
+        };
+        Descent.mApply = function (m, n, f) {
+            var i = m;
+            while (i-- > 0) {
+                var j = n;
+                while (j-- > 0)
+                    f(i, j);
+            }
+        };
+        Descent.prototype.matrixApply = function (f) {
+            Descent.mApply(this.k, this.n, f);
+        };
+        Descent.prototype.computeNextPosition = function (x0, r) {
+            var _this = this;
+            this.computeDerivatives(x0);
+            var alpha = this.computeStepSize(this.g);
+            this.stepAndProject(x0, r, this.g, alpha);
+            /* DEBUG
+                        for (var u: number = 0; u < this.n; ++u)
+                            for (var i = 0; i < this.k; ++i)
+                                if (isNaN(r[i][u])) debugger;
+            DEBUG */
+            if (this.project) {
+                this.matrixApply(function (i, j) { return _this.e[i][j] = x0[i][j] - r[i][j]; });
+                var beta = this.computeStepSize(this.e);
+                beta = Math.max(0.2, Math.min(beta, 1));
+                this.stepAndProject(x0, r, this.e, beta);
+            }
+        };
+        Descent.prototype.run = function (iterations) {
+            var stress = Number.MAX_VALUE, converged = false;
+            while (!converged && iterations-- > 0) {
+                var s = this.rungeKutta();
+                converged = Math.abs(stress / s - 1) < this.threshold;
+                stress = s;
+            }
+            return stress;
+        };
+        Descent.prototype.rungeKutta = function () {
+            var _this = this;
+            this.computeNextPosition(this.x, this.a);
+            Descent.mid(this.x, this.a, this.ia);
+            this.computeNextPosition(this.ia, this.b);
+            Descent.mid(this.x, this.b, this.ib);
+            this.computeNextPosition(this.ib, this.c);
+            this.computeNextPosition(this.c, this.d);
+            var disp = 0;
+            this.matrixApply(function (i, j) {
+                var x = (_this.a[i][j] + 2.0 * _this.b[i][j] + 2.0 * _this.c[i][j] + _this.d[i][j]) / 6.0, d = _this.x[i][j] - x;
+                disp += d * d;
+                _this.x[i][j] = x;
+            });
+            return disp;
+        };
+        Descent.mid = function (a, b, m) {
+            Descent.mApply(a.length, a[0].length, function (i, j) {
+                return m[i][j] = a[i][j] + (b[i][j] - a[i][j]) / 2.0;
+            });
+        };
+        Descent.prototype.takeDescentStep = function (x, d, stepSize) {
+            for (var i = 0; i < this.n; ++i) {
+                x[i] = x[i] - stepSize * d[i];
+            }
+        };
+        Descent.prototype.computeStress = function () {
+            var stress = 0;
+            for (var u = 0, nMinus1 = this.n - 1; u < nMinus1; ++u) {
+                for (var v = u + 1, n = this.n; v < n; ++v) {
+                    var l = 0;
+                    for (var i = 0; i < this.k; ++i) {
+                        var dx = this.x[i][u] - this.x[i][v];
+                        l += dx * dx;
+                    }
+                    l = Math.sqrt(l);
+                    var d = this.D[u][v];
+                    if (!isFinite(d))
+                        continue;
+                    var rl = d - l;
+                    var d2 = d * d;
+                    stress += rl * rl / d2;
+                }
+            }
+            return stress;
+        };
+        Descent.zeroDistance = 1e-10;
+        return Descent;
+    })();
+    cola.Descent = Descent;
+    // Linear congruential pseudo random number generator
+    var PseudoRandom = (function () {
+        function PseudoRandom(seed) {
+            if (seed === void 0) { seed = 1; }
+            this.seed = seed;
+            this.a = 214013;
+            this.c = 2531011;
+            this.m = 2147483648;
+            this.range = 32767;
+        }
+        // random real between 0 and 1
+        PseudoRandom.prototype.getNext = function () {
+            this.seed = (this.seed * this.a + this.c) % this.m;
+            return (this.seed >> 16) / this.range;
+        };
+        // random real between min and max
+        PseudoRandom.prototype.getNextBetween = function (min, max) {
+            return min + this.getNext() * (max - min);
+        };
+        return PseudoRandom;
+    })();
+    cola.PseudoRandom = PseudoRandom;
+})(cola || (cola = {}));
+var cola;
+(function (cola) {
+    var powergraph;
+    (function (powergraph) {
+        var PowerEdge = (function () {
+            function PowerEdge(source, target, type) {
+                this.source = source;
+                this.target = target;
+                this.type = type;
+            }
+            return PowerEdge;
+        })();
+        powergraph.PowerEdge = PowerEdge;
+        var Configuration = (function () {
+            function Configuration(n, edges, linkAccessor, rootGroup) {
+                var _this = this;
+                this.linkAccessor = linkAccessor;
+                this.modules = new Array(n);
+                this.roots = [];
+                if (rootGroup) {
+                    this.initModulesFromGroup(rootGroup);
+                }
+                else {
+                    this.roots.push(new ModuleSet());
+                    for (var i = 0; i < n; ++i)
+                        this.roots[0].add(this.modules[i] = new Module(i));
+                }
+                this.R = edges.length;
+                edges.forEach(function (e) {
+                    var s = _this.modules[linkAccessor.getSourceIndex(e)], t = _this.modules[linkAccessor.getTargetIndex(e)], type = linkAccessor.getType(e);
+                    s.outgoing.add(type, t);
+                    t.incoming.add(type, s);
+                });
+            }
+            Configuration.prototype.initModulesFromGroup = function (group) {
+                var moduleSet = new ModuleSet();
+                this.roots.push(moduleSet);
+                for (var i = 0; i < group.leaves.length; ++i) {
+                    var node = group.leaves[i];
+                    var module = new Module(node.id);
+                    this.modules[node.id] = module;
+                    moduleSet.add(module);
+                }
+                if (group.groups) {
+                    for (var j = 0; j < group.groups.length; ++j) {
+                        var child = group.groups[j];
+                        // Propagate group properties (like padding, stiffness, ...) as module definition so that the generated power graph group will inherit it
+                        var definition = {};
+                        for (var prop in child)
+                            if (prop !== "leaves" && prop !== "groups" && child.hasOwnProperty(prop))
+                                definition[prop] = child[prop];
+                        // Use negative module id to avoid clashes between predefined and generated modules
+                        moduleSet.add(new Module(-1 - j, new LinkSets(), new LinkSets(), this.initModulesFromGroup(child), definition));
+                    }
+                }
+                return moduleSet;
+            };
+            // merge modules a and b keeping track of their power edges and removing the from roots
+            Configuration.prototype.merge = function (a, b, k) {
+                if (k === void 0) { k = 0; }
+                var inInt = a.incoming.intersection(b.incoming), outInt = a.outgoing.intersection(b.outgoing);
+                var children = new ModuleSet();
+                children.add(a);
+                children.add(b);
+                var m = new Module(this.modules.length, outInt, inInt, children);
+                this.modules.push(m);
+                var update = function (s, i, o) {
+                    s.forAll(function (ms, linktype) {
+                        ms.forAll(function (n) {
+                            var nls = n[i];
+                            nls.add(linktype, m);
+                            nls.remove(linktype, a);
+                            nls.remove(linktype, b);
+                            a[o].remove(linktype, n);
+                            b[o].remove(linktype, n);
+                        });
+                    });
+                };
+                update(outInt, "incoming", "outgoing");
+                update(inInt, "outgoing", "incoming");
+                this.R -= inInt.count() + outInt.count();
+                this.roots[k].remove(a);
+                this.roots[k].remove(b);
+                this.roots[k].add(m);
+                return m;
+            };
+            Configuration.prototype.rootMerges = function (k) {
+                if (k === void 0) { k = 0; }
+                var rs = this.roots[k].modules();
+                var n = rs.length;
+                var merges = new Array(n * (n - 1));
+                var ctr = 0;
+                for (var i = 0, i_ = n - 1; i < i_; ++i) {
+                    for (var j = i + 1; j < n; ++j) {
+                        var a = rs[i], b = rs[j];
+                        merges[ctr] = { id: ctr, nEdges: this.nEdges(a, b), a: a, b: b };
+                        ctr++;
+                    }
+                }
+                return merges;
+            };
+            Configuration.prototype.greedyMerge = function () {
+                for (var i = 0; i < this.roots.length; ++i) {
+                    // Handle single nested module case
+                    if (this.roots[i].modules().length < 2)
+                        continue;
+                    // find the merge that allows for the most edges to be removed.  secondary ordering based on arbitrary id (for predictability)
+                    var ms = this.rootMerges(i).sort(function (a, b) { return a.nEdges == b.nEdges ? a.id - b.id : a.nEdges - b.nEdges; });
+                    var m = ms[0];
+                    if (m.nEdges >= this.R)
+                        continue;
+                    this.merge(m.a, m.b, i);
+                    return true;
+                }
+            };
+            Configuration.prototype.nEdges = function (a, b) {
+                var inInt = a.incoming.intersection(b.incoming), outInt = a.outgoing.intersection(b.outgoing);
+                return this.R - inInt.count() - outInt.count();
+            };
+            Configuration.prototype.getGroupHierarchy = function (retargetedEdges) {
+                var _this = this;
+                var groups = [];
+                var root = {};
+                toGroups(this.roots[0], root, groups);
+                var es = this.allEdges();
+                es.forEach(function (e) {
+                    var a = _this.modules[e.source];
+                    var b = _this.modules[e.target];
+                    retargetedEdges.push(new PowerEdge(typeof a.gid === "undefined" ? e.source : groups[a.gid], typeof b.gid === "undefined" ? e.target : groups[b.gid], e.type));
+                });
+                return groups;
+            };
+            Configuration.prototype.allEdges = function () {
+                var es = [];
+                Configuration.getEdges(this.roots[0], es);
+                return es;
+            };
+            Configuration.getEdges = function (modules, es) {
+                modules.forAll(function (m) {
+                    m.getEdges(es);
+                    Configuration.getEdges(m.children, es);
+                });
+            };
+            return Configuration;
+        })();
+        powergraph.Configuration = Configuration;
+        function toGroups(modules, group, groups) {
+            modules.forAll(function (m) {
+                if (m.isLeaf()) {
+                    if (!group.leaves)
+                        group.leaves = [];
+                    group.leaves.push(m.id);
+                }
+                else {
+                    var g = group;
+                    m.gid = groups.length;
+                    if (!m.isIsland() || m.isPredefined()) {
+                        g = { id: m.gid };
+                        if (m.isPredefined())
+                            // Apply original group properties
+                            for (var prop in m.definition)
+                                g[prop] = m.definition[prop];
+                        if (!group.groups)
+                            group.groups = [];
+                        group.groups.push(m.gid);
+                        groups.push(g);
+                    }
+                    toGroups(m.children, g, groups);
+                }
+            });
+        }
+        var Module = (function () {
+            function Module(id, outgoing, incoming, children, definition) {
+                if (outgoing === void 0) { outgoing = new LinkSets(); }
+                if (incoming === void 0) { incoming = new LinkSets(); }
+                if (children === void 0) { children = new ModuleSet(); }
+                this.id = id;
+                this.outgoing = outgoing;
+                this.incoming = incoming;
+                this.children = children;
+                this.definition = definition;
+            }
+            Module.prototype.getEdges = function (es) {
+                var _this = this;
+                this.outgoing.forAll(function (ms, edgetype) {
+                    ms.forAll(function (target) {
+                        es.push(new PowerEdge(_this.id, target.id, edgetype));
+                    });
+                });
+            };
+            Module.prototype.isLeaf = function () {
+                return this.children.count() === 0;
+            };
+            Module.prototype.isIsland = function () {
+                return this.outgoing.count() === 0 && this.incoming.count() === 0;
+            };
+            Module.prototype.isPredefined = function () {
+                return typeof this.definition !== "undefined";
+            };
+            return Module;
+        })();
+        powergraph.Module = Module;
+        function intersection(m, n) {
+            var i = {};
+            for (var v in m)
+                if (v in n)
+                    i[v] = m[v];
+            return i;
+        }
+        var ModuleSet = (function () {
+            function ModuleSet() {
+                this.table = {};
+            }
+            ModuleSet.prototype.count = function () {
+                return Object.keys(this.table).length;
+            };
+            ModuleSet.prototype.intersection = function (other) {
+                var result = new ModuleSet();
+                result.table = intersection(this.table, other.table);
+                return result;
+            };
+            ModuleSet.prototype.intersectionCount = function (other) {
+                return this.intersection(other).count();
+            };
+            ModuleSet.prototype.contains = function (id) {
+                return id in this.table;
+            };
+            ModuleSet.prototype.add = function (m) {
+                this.table[m.id] = m;
+            };
+            ModuleSet.prototype.remove = function (m) {
+                delete this.table[m.id];
+            };
+            ModuleSet.prototype.forAll = function (f) {
+                for (var mid in this.table) {
+                    f(this.table[mid]);
+                }
+            };
+            ModuleSet.prototype.modules = function () {
+                var vs = [];
+                this.forAll(function (m) {
+                    if (!m.isPredefined())
+                        vs.push(m);
+                });
+                return vs;
+            };
+            return ModuleSet;
+        })();
+        powergraph.ModuleSet = ModuleSet;
+        var LinkSets = (function () {
+            function LinkSets() {
+                this.sets = {};
+                this.n = 0;
+            }
+            LinkSets.prototype.count = function () {
+                return this.n;
+            };
+            LinkSets.prototype.contains = function (id) {
+                var result = false;
+                this.forAllModules(function (m) {
+                    if (!result && m.id == id) {
+                        result = true;
+                    }
+                });
+                return result;
+            };
+            LinkSets.prototype.add = function (linktype, m) {
+                var s = linktype in this.sets ? this.sets[linktype] : this.sets[linktype] = new ModuleSet();
+                s.add(m);
+                ++this.n;
+            };
+            LinkSets.prototype.remove = function (linktype, m) {
+                var ms = this.sets[linktype];
+                ms.remove(m);
+                if (ms.count() === 0) {
+                    delete this.sets[linktype];
+                }
+                --this.n;
+            };
+            LinkSets.prototype.forAll = function (f) {
+                for (var linktype in this.sets) {
+                    f(this.sets[linktype], linktype);
+                }
+            };
+            LinkSets.prototype.forAllModules = function (f) {
+                this.forAll(function (ms, lt) { return ms.forAll(f); });
+            };
+            LinkSets.prototype.intersection = function (other) {
+                var result = new LinkSets();
+                this.forAll(function (ms, lt) {
+                    if (lt in other.sets) {
+                        var i = ms.intersection(other.sets[lt]), n = i.count();
+                        if (n > 0) {
+                            result.sets[lt] = i;
+                            result.n += n;
+                        }
+                    }
+                });
+                return result;
+            };
+            return LinkSets;
+        })();
+        powergraph.LinkSets = LinkSets;
+        function intersectionCount(m, n) {
+            return Object.keys(intersection(m, n)).length;
+        }
+        function getGroups(nodes, links, la, rootGroup) {
+            var n = nodes.length, c = new powergraph.Configuration(n, links, la, rootGroup);
+            while (c.greedyMerge())
+                ;
+            var powerEdges = [];
+            var g = c.getGroupHierarchy(powerEdges);
+            powerEdges.forEach(function (e) {
+                var f = function (end) {
+                    var g = e[end];
+                    if (typeof g == "number")
+                        e[end] = nodes[g];
+                };
+                f("source");
+                f("target");
+            });
+            return { groups: g, powerEdges: powerEdges };
+        }
+        powergraph.getGroups = getGroups;
+    })(powergraph = cola.powergraph || (cola.powergraph = {}));
+})(cola || (cola = {}));
+/**
+ * @module cola
+ */
+var cola;
+(function (cola) {
+    // compute the size of the union of two sets a and b
+    function unionCount(a, b) {
+        var u = {};
+        for (var i in a)
+            u[i] = {};
+        for (var i in b)
+            u[i] = {};
+        return Object.keys(u).length;
+    }
+    // compute the size of the intersection of two sets a and b
+    function intersectionCount(a, b) {
+        var n = 0;
+        for (var i in a)
+            if (typeof b[i] !== 'undefined')
+                ++n;
+        return n;
+    }
+    function getNeighbours(links, la) {
+        var neighbours = {};
+        var addNeighbours = function (u, v) {
+            if (typeof neighbours[u] === 'undefined')
+                neighbours[u] = {};
+            neighbours[u][v] = {};
+        };
+        links.forEach(function (e) {
+            var u = la.getSourceIndex(e), v = la.getTargetIndex(e);
+            addNeighbours(u, v);
+            addNeighbours(v, u);
+        });
+        return neighbours;
+    }
+    // modify the lengths of the specified links by the result of function f weighted by w
+    function computeLinkLengths(links, w, f, la) {
+        var neighbours = getNeighbours(links, la);
+        links.forEach(function (l) {
+            var a = neighbours[la.getSourceIndex(l)];
+            var b = neighbours[la.getTargetIndex(l)];
+            la.setLength(l, 1 + w * f(a, b));
+        });
+    }
+    /** modify the specified link lengths based on the symmetric difference of their neighbours
+     * @class symmetricDiffLinkLengths
+     */
+    function symmetricDiffLinkLengths(links, la, w) {
+        if (w === void 0) { w = 1; }
+        computeLinkLengths(links, w, function (a, b) { return Math.sqrt(unionCount(a, b) - intersectionCount(a, b)); }, la);
+    }
+    cola.symmetricDiffLinkLengths = symmetricDiffLinkLengths;
+    /** modify the specified links lengths based on the jaccard difference between their neighbours
+     * @class jaccardLinkLengths
+     */
+    function jaccardLinkLengths(links, la, w) {
+        if (w === void 0) { w = 1; }
+        computeLinkLengths(links, w, function (a, b) {
+            return Math.min(Object.keys(a).length, Object.keys(b).length) < 1.1 ? 0 : intersectionCount(a, b) / unionCount(a, b);
+        }, la);
+    }
+    cola.jaccardLinkLengths = jaccardLinkLengths;
+    /** generate separation constraints for all edges unless both their source and sink are in the same strongly connected component
+     * @class generateDirectedEdgeConstraints
+     */
+    function generateDirectedEdgeConstraints(n, links, axis, la) {
+        var components = stronglyConnectedComponents(n, links, la);
+        var nodes = {};
+        components.forEach(function (c, i) {
+            return c.forEach(function (v) { return nodes[v] = i; });
+        });
+        var constraints = [];
+        links.forEach(function (l) {
+            var ui = la.getSourceIndex(l), vi = la.getTargetIndex(l), u = nodes[ui], v = nodes[vi];
+            if (u !== v) {
+                constraints.push({
+                    axis: axis,
+                    left: ui,
+                    right: vi,
+                    gap: la.getMinSeparation(l)
+                });
+            }
+        });
+        return constraints;
+    }
+    cola.generateDirectedEdgeConstraints = generateDirectedEdgeConstraints;
+    /**
+     * Tarjan's strongly connected components algorithm for directed graphs
+     * returns an array of arrays of node indicies in each of the strongly connected components.
+     * a vertex not in a SCC of two or more nodes is it's own SCC.
+     * adaptation of https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
+     */
+    function stronglyConnectedComponents(numVertices, edges, la) {
+        var nodes = [];
+        var index = 0;
+        var stack = [];
+        var components = [];
+        function strongConnect(v) {
+            // Set the depth index for v to the smallest unused index
+            v.index = v.lowlink = index++;
+            stack.push(v);
+            v.onStack = true;
+            // Consider successors of v
+            for (var _i = 0, _a = v.out; _i < _a.length; _i++) {
+                var w = _a[_i];
+                if (typeof w.index === 'undefined') {
+                    // Successor w has not yet been visited; recurse on it
+                    strongConnect(w);
+                    v.lowlink = Math.min(v.lowlink, w.lowlink);
+                }
+                else if (w.onStack) {
+                    // Successor w is in stack S and hence in the current SCC
+                    v.lowlink = Math.min(v.lowlink, w.index);
+                }
+            }
+            // If v is a root node, pop the stack and generate an SCC
+            if (v.lowlink === v.index) {
+                // start a new strongly connected component
+                var component = [];
+                while (stack.length) {
+                    w = stack.pop();
+                    w.onStack = false;
+                    //add w to current strongly connected component
+                    component.push(w);
+                    if (w === v)
+                        break;
+                }
+                // output the current strongly connected component
+                components.push(component.map(function (v) { return v.id; }));
+            }
+        }
+        for (var i = 0; i < numVertices; i++) {
+            nodes.push({ id: i, out: [] });
+        }
+        for (var _i = 0; _i < edges.length; _i++) {
+            var e = edges[_i];
+            var v_1 = nodes[la.getSourceIndex(e)], w = nodes[la.getTargetIndex(e)];
+            v_1.out.push(w);
+        }
+        for (var _a = 0; _a < nodes.length; _a++) {
+            var v = nodes[_a];
+            if (typeof v.index === 'undefined')
+                strongConnect(v);
+        }
+        return components;
+    }
+    cola.stronglyConnectedComponents = stronglyConnectedComponents;
+})(cola || (cola = {}));
+var PairingHeap = (function () {
+    // from: https://gist.github.com/nervoussystem
+    //{elem:object, subheaps:[array of heaps]}
+    function PairingHeap(elem) {
+        this.elem = elem;
+        this.subheaps = [];
+    }
+    PairingHeap.prototype.toString = function (selector) {
+        var str = "", needComma = false;
+        for (var i = 0; i < this.subheaps.length; ++i) {
+            var subheap = this.subheaps[i];
+            if (!subheap.elem) {
+                needComma = false;
+                continue;
+            }
+            if (needComma) {
+                str = str + ",";
+            }
+            str = str + subheap.toString(selector);
+            needComma = true;
+        }
+        if (str !== "") {
+            str = "(" + str + ")";
+        }
+        return (this.elem ? selector(this.elem) : "") + str;
+    };
+    PairingHeap.prototype.forEach = function (f) {
+        if (!this.empty()) {
+            f(this.elem, this);
+            this.subheaps.forEach(function (s) { return s.forEach(f); });
+        }
+    };
+    PairingHeap.prototype.count = function () {
+        return this.empty() ? 0 : 1 + this.subheaps.reduce(function (n, h) {
+            return n + h.count();
+        }, 0);
+    };
+    PairingHeap.prototype.min = function () {
+        return this.elem;
+    };
+    PairingHeap.prototype.empty = function () {
+        return this.elem == null;
+    };
+    PairingHeap.prototype.contains = function (h) {
+        if (this === h)
+            return true;
+        for (var i = 0; i < this.subheaps.length; i++) {
+            if (this.subheaps[i].contains(h))
+                return true;
+        }
+        return false;
+    };
+    PairingHeap.prototype.isHeap = function (lessThan) {
+        var _this = this;
+        return this.subheaps.every(function (h) { return lessThan(_this.elem, h.elem) && h.isHeap(lessThan); });
+    };
+    PairingHeap.prototype.insert = function (obj, lessThan) {
+        return this.merge(new PairingHeap(obj), lessThan);
+    };
+    PairingHeap.prototype.merge = function (heap2, lessThan) {
+        if (this.empty())
+            return heap2;
+        else if (heap2.empty())
+            return this;
+        else if (lessThan(this.elem, heap2.elem)) {
+            this.subheaps.push(heap2);
+            return this;
+        }
+        else {
+            heap2.subheaps.push(this);
+            return heap2;
+        }
+    };
+    PairingHeap.prototype.removeMin = function (lessThan) {
+        if (this.empty())
+            return null;
+        else
+            return this.mergePairs(lessThan);
+    };
+    PairingHeap.prototype.mergePairs = function (lessThan) {
+        if (this.subheaps.length == 0)
+            return new PairingHeap(null);
+        else if (this.subheaps.length == 1) {
+            return this.subheaps[0];
+        }
+        else {
+            var firstPair = this.subheaps.pop().merge(this.subheaps.pop(), lessThan);
+            var remaining = this.mergePairs(lessThan);
+            return firstPair.merge(remaining, lessThan);
+        }
+    };
+    PairingHeap.prototype.decreaseKey = function (subheap, newValue, setHeapNode, lessThan) {
+        var newHeap = subheap.removeMin(lessThan);
+        //reassign subheap values to preserve tree
+        subheap.elem = newHeap.elem;
+        subheap.subheaps = newHeap.subheaps;
+        if (setHeapNode !== null && newHeap.elem !== null) {
+            setHeapNode(subheap.elem, subheap);
+        }
+        var pairingNode = new PairingHeap(newValue);
+        if (setHeapNode !== null) {
+            setHeapNode(newValue, pairingNode);
+        }
+        return this.merge(pairingNode, lessThan);
+    };
+    return PairingHeap;
+})();
+/**
+ * @class PriorityQueue a min priority queue backed by a pairing heap
+ */
+var PriorityQueue = (function () {
+    function PriorityQueue(lessThan) {
+        this.lessThan = lessThan;
+    }
+    /**
+     * @method top
+     * @return the top element (the min element as defined by lessThan)
+     */
+    PriorityQueue.prototype.top = function () {
+        if (this.empty()) {
+            return null;
+        }
+        return this.root.elem;
+    };
+    /**
+     * @method push
+     * put things on the heap
+     */
+    PriorityQueue.prototype.push = function () {
+        var args = [];
+        for (var _i = 0; _i < arguments.length; _i++) {
+            args[_i - 0] = arguments[_i];
+        }
+        var pairingNode;
+        for (var i = 0, arg; arg = args[i]; ++i) {
+            pairingNode = new PairingHeap(arg);
+            this.root = this.empty() ?
+                pairingNode : this.root.merge(pairingNode, this.lessThan);
+        }
+        return pairingNode;
+    };
+    /**
+     * @method empty
+     * @return true if no more elements in queue
+     */
+    PriorityQueue.prototype.empty = function () {
+        return !this.root || !this.root.elem;
+    };
+    /**
+     * @method isHeap check heap condition (for testing)
+     * @return true if queue is in valid state
+     */
+    PriorityQueue.prototype.isHeap = function () {
+        return this.root.isHeap(this.lessThan);
+    };
+    /**
+     * @method forEach apply f to each element of the queue
+     * @param f function to apply
+     */
+    PriorityQueue.prototype.forEach = function (f) {
+        this.root.forEach(f);
+    };
+    /**
+     * @method pop remove and return the min element from the queue
+     */
+    PriorityQueue.prototype.pop = function () {
+        if (this.empty()) {
+            return null;
+        }
+        var obj = this.root.min();
+        this.root = this.root.removeMin(this.lessThan);
+        return obj;
+    };
+    /**
+     * @method reduceKey reduce the key value of the specified heap node
+     */
+    PriorityQueue.prototype.reduceKey = function (heapNode, newKey, setHeapNode) {
+        if (setHeapNode === void 0) { setHeapNode = null; }
+        this.root = this.root.decreaseKey(heapNode, newKey, setHeapNode, this.lessThan);
+    };
+    PriorityQueue.prototype.toString = function (selector) {
+        return this.root.toString(selector);
+    };
+    /**
+     * @method count
+     * @return number of elements in queue
+     */
+    PriorityQueue.prototype.count = function () {
+        return this.root.count();
+    };
+    return PriorityQueue;
+})();
+///<reference path="pqueue.ts"/>
+/**
+ * @module shortestpaths
+ */
+var cola;
+(function (cola) {
+    var shortestpaths;
+    (function (shortestpaths) {
+        var Neighbour = (function () {
+            function Neighbour(id, distance) {
+                this.id = id;
+                this.distance = distance;
+            }
+            return Neighbour;
+        })();
+        var Node = (function () {
+            function Node(id) {
+                this.id = id;
+                this.neighbours = [];
+            }
+            return Node;
+        })();
+        var QueueEntry = (function () {
+            function QueueEntry(node, prev, d) {
+                this.node = node;
+                this.prev = prev;
+                this.d = d;
+            }
+            return QueueEntry;
+        })();
+        /**
+         * calculates all-pairs shortest paths or shortest paths from a single node
+         * @class Calculator
+         * @constructor
+         * @param n {number} number of nodes
+         * @param es {Edge[]} array of edges
+         */
+        var Calculator = (function () {
+            function Calculator(n, es, getSourceIndex, getTargetIndex, getLength) {
+                this.n = n;
+                this.es = es;
+                this.neighbours = new Array(this.n);
+                var i = this.n;
+                while (i--)
+                    this.neighbours[i] = new Node(i);
+                i = this.es.length;
+                while (i--) {
+                    var e = this.es[i];
+                    var u = getSourceIndex(e), v = getTargetIndex(e);
+                    var d = getLength(e);
+                    this.neighbours[u].neighbours.push(new Neighbour(v, d));
+                    this.neighbours[v].neighbours.push(new Neighbour(u, d));
+                }
+            }
+            /**
+             * compute shortest paths for graph over n nodes with edges an array of source/target pairs
+             * edges may optionally have a length attribute.  1 is the default.
+             * Uses Johnson's algorithm.
+             *
+             * @method DistanceMatrix
+             * @return the distance matrix
+             */
+            Calculator.prototype.DistanceMatrix = function () {
+                var D = new Array(this.n);
+                for (var i = 0; i < this.n; ++i) {
+                    D[i] = this.dijkstraNeighbours(i);
+                }
+                return D;
+            };
+            /**
+             * get shortest paths from a specified start node
+             * @method DistancesFromNode
+             * @param start node index
+             * @return array of path lengths
+             */
+            Calculator.prototype.DistancesFromNode = function (start) {
+                return this.dijkstraNeighbours(start);
+            };
+            Calculator.prototype.PathFromNodeToNode = function (start, end) {
+                return this.dijkstraNeighbours(start, end);
+            };
+            // find shortest path from start to end, with the opportunity at 
+            // each edge traversal to compute a custom cost based on the 
+            // previous edge.  For example, to penalise bends.
+            Calculator.prototype.PathFromNodeToNodeWithPrevCost = function (start, end, prevCost) {
+                var q = new PriorityQueue(function (a, b) { return a.d <= b.d; }), u = this.neighbours[start], qu = new QueueEntry(u, null, 0), visitedFrom = {};
+                q.push(qu);
+                while (!q.empty()) {
+                    qu = q.pop();
+                    u = qu.node;
+                    if (u.id === end) {
+                        break;
+                    }
+                    var i = u.neighbours.length;
+                    while (i--) {
+                        var neighbour = u.neighbours[i], v = this.neighbours[neighbour.id];
+                        // don't double back
+                        if (qu.prev && v.id === qu.prev.node.id)
+                            continue;
+                        // don't retraverse an edge if it has already been explored
+                        // from a lower cost route
+                        var viduid = v.id + ',' + u.id;
+                        if (viduid in visitedFrom && visitedFrom[viduid] <= qu.d)
+                            continue;
+                        var cc = qu.prev ? prevCost(qu.prev.node.id, u.id, v.id) : 0, t = qu.d + neighbour.distance + cc;
+                        // store cost of this traversal
+                        visitedFrom[viduid] = t;
+                        q.push(new QueueEntry(v, qu, t));
+                    }
+                }
+                var path = [];
+                while (qu.prev) {
+                    qu = qu.prev;
+                    path.push(qu.node.id);
+                }
+                return path;
+            };
+            Calculator.prototype.dijkstraNeighbours = function (start, dest) {
+                if (dest === void 0) { dest = -1; }
+                var q = new PriorityQueue(function (a, b) { return a.d <= b.d; }), i = this.neighbours.length, d = new Array(i);
+                while (i--) {
+                    var node = this.neighbours[i];
+                    node.d = i === start ? 0 : Number.POSITIVE_INFINITY;
+                    node.q = q.push(node);
+                }
+                while (!q.empty()) {
+                    // console.log(q.toString(function (u) { return u.id + "=" + (u.d === Number.POSITIVE_INFINITY ? "\u221E" : u.d.toFixed(2) )}));
+                    var u = q.pop();
+                    d[u.id] = u.d;
+                    if (u.id === dest) {
+                        var path = [];
+                        var v = u;
+                        while (typeof v.prev !== 'undefined') {
+                            path.push(v.prev.id);
+                            v = v.prev;
+                        }
+                        return path;
+                    }
+                    i = u.neighbours.length;
+                    while (i--) {
+                        var neighbour = u.neighbours[i];
+                        var v = this.neighbours[neighbour.id];
+                        var t = u.d + neighbour.distance;
+                        if (u.d !== Number.MAX_VALUE && v.d > t) {
+                            v.d = t;
+                            v.prev = u;
+                            q.reduceKey(v.q, v, function (e, q) { return e.q = q; });
+                        }
+                    }
+                }
+                return d;
+            };
+            return Calculator;
+        })();
+        shortestpaths.Calculator = Calculator;
+    })(shortestpaths = cola.shortestpaths || (cola.shortestpaths = {}));
+})(cola || (cola = {}));
+///<reference path="handledisconnected.ts"/>
+///<reference path="geom.ts"/>
+///<reference path="descent.ts"/>
+///<reference path="powergraph.ts"/>
+///<reference path="linklengths.ts"/>
+///<reference path="shortestpaths.ts"/>
+/**
+ * @module cola
+ */
+var cola;
+(function (cola) {
+    /**
+     * The layout process fires three events:
+     *  - start: layout iterations started
+     *  - tick: fired once per iteration, listen to this to animate
+     *  - end: layout converged, you might like to zoom-to-fit or something at notification of this event
+     */
+    (function (EventType) {
+        EventType[EventType["start"] = 0] = "start";
+        EventType[EventType["tick"] = 1] = "tick";
+        EventType[EventType["end"] = 2] = "end";
+    })(cola.EventType || (cola.EventType = {}));
+    var EventType = cola.EventType;
+    ;
+    /**
+     * Main interface to cola layout.
+     * @class Layout
+     */
+    var Layout = (function () {
+        function Layout() {
+            var _this = this;
+            this._canvasSize = [1, 1];
+            this._linkDistance = 20;
+            this._defaultNodeSize = 10;
+            this._linkLengthCalculator = null;
+            this._linkType = null;
+            this._avoidOverlaps = false;
+            this._handleDisconnected = true;
+            this._running = false;
+            this._nodes = [];
+            this._groups = [];
+            this._rootGroup = null;
+            this._links = [];
+            this._constraints = [];
+            this._distanceMatrix = null;
+            this._descent = null;
+            this._directedLinkConstraints = null;
+            this._threshold = 0.01;
+            this._visibilityGraph = null;
+            this._groupCompactness = 1e-6;
+            // sub-class and override this property to replace with a more sophisticated eventing mechanism
+            this.event = null;
+            this.linkAccessor = {
+                getSourceIndex: Layout.getSourceIndex,
+                getTargetIndex: Layout.getTargetIndex,
+                setLength: Layout.setLinkLength,
+                getType: function (l) { return typeof _this._linkType === "function" ? _this._linkType(l) : 0; }
+            };
+        }
+        // subscribe a listener to an event
+        // sub-class and override this method to replace with a more sophisticated eventing mechanism
+        Layout.prototype.on = function (e, listener) {
+            // override me!
+            if (!this.event)
+                this.event = {};
+            if (typeof e === 'string') {
+                this.event[EventType[e]] = listener;
+            }
+            else {
+                this.event[e] = listener;
+            }
+            return this;
+        };
+        // a function that is notified of events like "tick"
+        // sub-class and override this method to replace with a more sophisticated eventing mechanism
+        Layout.prototype.trigger = function (e) {
+            if (this.event && typeof this.event[e.type] !== 'undefined') {
+                this.event[e.type](e);
+            }
+        };
+        // a function that kicks off the iteration tick loop
+        // it calls tick() repeatedly until tick returns true (is converged)
+        // subclass and override it with something fancier (e.g. dispatch tick on a timer)
+        Layout.prototype.kick = function () {
+            while (!this.tick())
+                ;
+        };
+        /**
+         * iterate the layout.  Returns true when layout converged.
+         */
+        Layout.prototype.tick = function () {
+            if (this._alpha < this._threshold) {
+                this._running = false;
+                this.trigger({ type: EventType.end, alpha: this._alpha = 0, stress: this._lastStress });
+                return true;
+            }
+            var n = this._nodes.length, m = this._links.length;
+            var o, i;
+            this._descent.locks.clear();
+            for (i = 0; i < n; ++i) {
+                o = this._nodes[i];
+                if (o.fixed) {
+                    if (typeof o.px === 'undefined' || typeof o.py === 'undefined') {
+                        o.px = o.x;
+                        o.py = o.y;
+                    }
+                    var p = [o.px, o.py];
+                    this._descent.locks.add(i, p);
+                }
+            }
+            var s1 = this._descent.rungeKutta();
+            //var s1 = descent.reduceStress();
+            if (s1 === 0) {
+                this._alpha = 0;
+            }
+            else if (typeof this._lastStress !== 'undefined') {
+                this._alpha = s1; //Math.abs(Math.abs(this._lastStress / s1) - 1);
+            }
+            this._lastStress = s1;
+            this.updateNodePositions();
+            this.trigger({ type: EventType.tick, alpha: this._alpha, stress: this._lastStress });
+            return false;
+        };
+        // copy positions out of descent instance into each of the nodes' center coords
+        Layout.prototype.updateNodePositions = function () {
+            var x = this._descent.x[0], y = this._descent.x[1];
+            var o, i = this._nodes.length;
+            while (i--) {
+                o = this._nodes[i];
+                o.x = x[i];
+                o.y = y[i];
+            }
+        };
+        Layout.prototype.nodes = function (v) {
+            if (!v) {
+                if (this._nodes.length === 0 && this._links.length > 0) {
+                    // if we have links but no nodes, create the nodes array now with empty objects for the links to point at.
+                    // in this case the links are expected to be numeric indices for nodes in the range 0..n-1 where n is the number of nodes
+                    var n = 0;
+                    this._links.forEach(function (l) {
+                        n = Math.max(n, l.source, l.target);
+                    });
+                    this._nodes = new Array(++n);
+                    for (var i = 0; i < n; ++i) {
+                        this._nodes[i] = {};
+                    }
+                }
+                return this._nodes;
+            }
+            this._nodes = v;
+            return this;
+        };
+        Layout.prototype.groups = function (x) {
+            var _this = this;
+            if (!x)
+                return this._groups;
+            this._groups = x;
+            this._rootGroup = {};
+            this._groups.forEach(function (g) {
+                if (typeof g.padding === "undefined")
+                    g.padding = 1;
+                if (typeof g.leaves !== "undefined")
+                    g.leaves.forEach(function (v, i) { (g.leaves[i] = _this._nodes[v]).parent = g; });
+                if (typeof g.groups !== "undefined")
+                    g.groups.forEach(function (gi, i) { (g.groups[i] = _this._groups[gi]).parent = g; });
+            });
+            this._rootGroup.leaves = this._nodes.filter(function (v) { return typeof v.parent === 'undefined'; });
+            this._rootGroup.groups = this._groups.filter(function (g) { return typeof g.parent === 'undefined'; });
+            return this;
+        };
+        Layout.prototype.powerGraphGroups = function (f) {
+            var g = cola.powergraph.getGroups(this._nodes, this._links, this.linkAccessor, this._rootGroup);
+            this.groups(g.groups);
+            f(g);
+            return this;
+        };
+        Layout.prototype.avoidOverlaps = function (v) {
+            if (!arguments.length)
+                return this._avoidOverlaps;
+            this._avoidOverlaps = v;
+            return this;
+        };
+        Layout.prototype.handleDisconnected = function (v) {
+            if (!arguments.length)
+                return this._handleDisconnected;
+            this._handleDisconnected = v;
+            return this;
+        };
+        /**
+         * causes constraints to be generated such that directed graphs are laid out either from left-to-right or top-to-bottom.
+         * a separation constraint is generated in the selected axis for each edge that is not involved in a cycle (part of a strongly connected component)
+         * @param axis {string} 'x' for left-to-right, 'y' for top-to-bottom
+         * @param minSeparation {number|link=>number} either a number specifying a minimum spacing required across all links or a function to return the minimum spacing for each link
+         */
+        Layout.prototype.flowLayout = function (axis, minSeparation) {
+            if (!arguments.length)
+                axis = 'y';
+            this._directedLinkConstraints = {
+                axis: axis,
+                getMinSeparation: typeof minSeparation === 'number' ? function () { return minSeparation; } : minSeparation
+            };
+            return this;
+        };
+        Layout.prototype.links = function (x) {
+            if (!arguments.length)
+                return this._links;
+            this._links = x;
+            return this;
+        };
+        Layout.prototype.constraints = function (c) {
+            if (!arguments.length)
+                return this._constraints;
+            this._constraints = c;
+            return this;
+        };
+        Layout.prototype.distanceMatrix = function (d) {
+            if (!arguments.length)
+                return this._distanceMatrix;
+            this._distanceMatrix = d;
+            return this;
+        };
+        Layout.prototype.size = function (x) {
+            if (!x)
+                return this._canvasSize;
+            this._canvasSize = x;
+            return this;
+        };
+        Layout.prototype.defaultNodeSize = function (x) {
+            if (!x)
+                return this._defaultNodeSize;
+            this._defaultNodeSize = x;
+            return this;
+        };
+        Layout.prototype.groupCompactness = function (x) {
+            if (!x)
+                return this._groupCompactness;
+            this._groupCompactness = x;
+            return this;
+        };
+        Layout.prototype.linkDistance = function (x) {
+            if (!x) {
+                return this._linkDistance;
+            }
+            this._linkDistance = typeof x === "function" ? x : +x;
+            this._linkLengthCalculator = null;
+            return this;
+        };
+        Layout.prototype.linkType = function (f) {
+            this._linkType = f;
+            return this;
+        };
+        Layout.prototype.convergenceThreshold = function (x) {
+            if (!x)
+                return this._threshold;
+            this._threshold = typeof x === "function" ? x : +x;
+            return this;
+        };
+        Layout.prototype.alpha = function (x) {
+            if (!arguments.length)
+                return this._alpha;
+            else {
+                x = +x;
+                if (this._alpha) {
+                    if (x > 0)
+                        this._alpha = x; // we might keep it hot
+                    else
+                        this._alpha = 0; // or, next tick will dispatch "end"
+                }
+                else if (x > 0) {
+                    if (!this._running) {
+                        this._running = true;
+                        this.trigger({ type: EventType.start, alpha: this._alpha = x });
+                        this.kick();
+                    }
+                }
+                return this;
+            }
+        };
+        Layout.prototype.getLinkLength = function (link) {
+            return typeof this._linkDistance === "function" ? +(this._linkDistance(link)) : this._linkDistance;
+        };
+        Layout.setLinkLength = function (link, length) {
+            link.length = length;
+        };
+        Layout.prototype.getLinkType = function (link) {
+            return typeof this._linkType === "function" ? this._linkType(link) : 0;
+        };
+        /**
+         * compute an ideal length for each link based on the graph structure around that link.
+         * you can use this (for example) to create extra space around hub-nodes in dense graphs.
+         * In particular this calculation is based on the "symmetric difference" in the neighbour sets of the source and target:
+         * i.e. if neighbours of source is a and neighbours of target are b then calculation is: sqrt(|a union b| - |a intersection b|)
+         * Actual computation based on inspection of link structure occurs in start(), so links themselves
+         * don't have to have been assigned before invoking this function.
+         * @param {number} [idealLength] the base length for an edge when its source and start have no other common neighbours (e.g. 40)
+         * @param {number} [w] a multiplier for the effect of the length adjustment (e.g. 0.7)
+         */
+        Layout.prototype.symmetricDiffLinkLengths = function (idealLength, w) {
+            var _this = this;
+            if (w === void 0) { w = 1; }
+            this.linkDistance(function (l) { return idealLength * l.length; });
+            this._linkLengthCalculator = function () { return cola.symmetricDiffLinkLengths(_this._links, _this.linkAccessor, w); };
+            return this;
+        };
+        /**
+         * compute an ideal length for each link based on the graph structure around that link.
+         * you can use this (for example) to create extra space around hub-nodes in dense graphs.
+         * In particular this calculation is based on the "symmetric difference" in the neighbour sets of the source and target:
+         * i.e. if neighbours of source is a and neighbours of target are b then calculation is: |a intersection b|/|a union b|
+         * Actual computation based on inspection of link structure occurs in start(), so links themselves
+         * don't have to have been assigned before invoking this function.
+         * @param {number} [idealLength] the base length for an edge when its source and start have no other common neighbours (e.g. 40)
+         * @param {number} [w] a multiplier for the effect of the length adjustment (e.g. 0.7)
+         */
+        Layout.prototype.jaccardLinkLengths = function (idealLength, w) {
+            var _this = this;
+            if (w === void 0) { w = 1; }
+            this.linkDistance(function (l) { return idealLength * l.length; });
+            this._linkLengthCalculator = function () { return cola.jaccardLinkLengths(_this._links, _this.linkAccessor, w); };
+            return this;
+        };
+        /**
+         * start the layout process
+         * @method start
+         * @param {number} [initialUnconstrainedIterations=0] unconstrained initial layout iterations
+         * @param {number} [initialUserConstraintIterations=0] initial layout iterations with user-specified constraints
+         * @param {number} [initialAllConstraintsIterations=0] initial layout iterations with all constraints including non-overlap
+         * @param {number} [gridSnapIterations=0] iterations of "grid snap", which pulls nodes towards grid cell centers - grid of size node[0].width - only really makes sense if all nodes have the same width and height
+         * @param [keepRunning=true] keep iterating asynchronously via the tick method
+         */
+        Layout.prototype.start = function (initialUnconstrainedIterations, initialUserConstraintIterations, initialAllConstraintsIterations, gridSnapIterations, keepRunning) {
+            var _this = this;
+            if (initialUnconstrainedIterations === void 0) { initialUnconstrainedIterations = 0; }
+            if (initialUserConstraintIterations === void 0) { initialUserConstraintIterations = 0; }
+            if (initialAllConstraintsIterations === void 0) { initialAllConstraintsIterations = 0; }
+            if (gridSnapIterations === void 0) { gridSnapIterations = 0; }
+            if (keepRunning === void 0) { keepRunning = true; }
+            var i, j, n = this.nodes().length, N = n + 2 * this._groups.length, m = this._links.length, w = this._canvasSize[0], h = this._canvasSize[1];
+            if (this._linkLengthCalculator)
+                this._linkLengthCalculator();
+            var x = new Array(N), y = new Array(N);
+            var G = null;
+            var ao = this._avoidOverlaps;
+            this._nodes.forEach(function (v, i) {
+                v.index = i;
+                if (typeof v.x === 'undefined') {
+                    v.x = w / 2, v.y = h / 2;
+                }
+                x[i] = v.x, y[i] = v.y;
+            });
+            //should we do this to clearly label groups?
+            //this._groups.forEach((g, i) => g.groupIndex = i);
+            var distances;
+            if (this._distanceMatrix) {
+                // use the user specified distanceMatrix
+                distances = this._distanceMatrix;
+            }
+            else {
+                // construct an n X n distance matrix based on shortest paths through graph (with respect to edge.length).
+                distances = (new cola.shortestpaths.Calculator(N, this._links, Layout.getSourceIndex, Layout.getTargetIndex, function (l) { return _this.getLinkLength(l); })).DistanceMatrix();
+                // G is a square matrix with G[i][j] = 1 iff there exists an edge between node i and node j
+                // otherwise 2. (
+                G = cola.Descent.createSquareMatrix(N, function () { return 2; });
+                this._links.forEach(function (l) {
+                    if (typeof l.source == "number")
+                        l.source = _this._nodes[l.source];
+                    if (typeof l.target == "number")
+                        l.target = _this._nodes[l.target];
+                });
+                this._links.forEach(function (e) {
+                    var u = Layout.getSourceIndex(e), v = Layout.getTargetIndex(e);
+                    G[u][v] = G[v][u] = e.weight || 1;
+                });
+            }
+            var D = cola.Descent.createSquareMatrix(N, function (i, j) {
+                return distances[i][j];
+            });
+            if (this._rootGroup && typeof this._rootGroup.groups !== 'undefined') {
+                var i = n;
+                var addAttraction = function (i, j, strength, idealDistance) {
+                    G[i][j] = G[j][i] = strength;
+                    D[i][j] = D[j][i] = idealDistance;
+                };
+                this._groups.forEach(function (g) {
+                    addAttraction(i, i + 1, _this._groupCompactness, 0.1);
+                    // todo: add terms here attracting children of the group to the group dummy nodes
+                    //if (typeof g.leaves !== 'undefined')
+                    //    g.leaves.forEach(l => {
+                    //        addAttraction(l.index, i, 1e-4, 0.1);
+                    //        addAttraction(l.index, i + 1, 1e-4, 0.1);
+                    //    });
+                    //if (typeof g.groups !== 'undefined')
+                    //    g.groups.forEach(g => {
+                    //        var gid = n + g.groupIndex * 2;
+                    //        addAttraction(gid, i, 0.1, 0.1);
+                    //        addAttraction(gid + 1, i, 0.1, 0.1);
+                    //        addAttraction(gid, i + 1, 0.1, 0.1);
+                    //        addAttraction(gid + 1, i + 1, 0.1, 0.1);
+                    //    });
+                    x[i] = 0, y[i++] = 0;
+                    x[i] = 0, y[i++] = 0;
+                });
+            }
+            else
+                this._rootGroup = { leaves: this._nodes, groups: [] };
+            var curConstraints = this._constraints || [];
+            if (this._directedLinkConstraints) {
+                this.linkAccessor.getMinSeparation = this._directedLinkConstraints.getMinSeparation;
+                curConstraints = curConstraints.concat(cola.generateDirectedEdgeConstraints(n, this._links, this._directedLinkConstraints.axis, (this.linkAccessor)));
+            }
+            this.avoidOverlaps(false);
+            this._descent = new cola.Descent([x, y], D);
+            this._descent.locks.clear();
+            for (var i = 0; i < n; ++i) {
+                var o = this._nodes[i];
+                if (o.fixed) {
+                    o.px = o.x;
+                    o.py = o.y;
+                    var p = [o.x, o.y];
+                    this._descent.locks.add(i, p);
+                }
+            }
+            this._descent.threshold = this._threshold;
+            // apply initialIterations without user constraints or nonoverlap constraints
+            this._descent.run(initialUnconstrainedIterations);
+            // apply initialIterations with user constraints but no nonoverlap constraints
+            if (curConstraints.length > 0)
+                this._descent.project = new cola.vpsc.Projection(this._nodes, this._groups, this._rootGroup, curConstraints).projectFunctions();
+            this._descent.run(initialUserConstraintIterations);
+            this.separateOverlappingComponents(w, h);
+            // subsequent iterations will apply all constraints
+            this.avoidOverlaps(ao);
+            if (ao) {
+                this._nodes.forEach(function (v, i) { v.x = x[i], v.y = y[i]; });
+                this._descent.project = new cola.vpsc.Projection(this._nodes, this._groups, this._rootGroup, curConstraints, true).projectFunctions();
+                this._nodes.forEach(function (v, i) { x[i] = v.x, y[i] = v.y; });
+            }
+            // allow not immediately connected nodes to relax apart (p-stress)
+            this._descent.G = G;
+            this._descent.run(initialAllConstraintsIterations);
+            if (gridSnapIterations) {
+                this._descent.snapStrength = 1000;
+                this._descent.snapGridSize = this._nodes[0].width;
+                this._descent.numGridSnapNodes = n;
+                this._descent.scaleSnapByMaxH = n != N; // if we have groups then need to scale hessian so grid forces still apply
+                var G0 = cola.Descent.createSquareMatrix(N, function (i, j) {
+                    if (i >= n || j >= n)
+                        return G[i][j];
+                    return 0;
+                });
+                this._descent.G = G0;
+                this._descent.run(gridSnapIterations);
+            }
+            this.updateNodePositions();
+            this.separateOverlappingComponents(w, h);
+            return keepRunning ? this.resume() : this;
+        };
+        // recalculate nodes position for disconnected graphs
+        Layout.prototype.separateOverlappingComponents = function (width, height) {
+            var _this = this;
+            // recalculate nodes position for disconnected graphs
+            if (!this._distanceMatrix && this._handleDisconnected) {
+                var x = this._descent.x[0], y = this._descent.x[1];
+                this._nodes.forEach(function (v, i) { v.x = x[i], v.y = y[i]; });
+                var graphs = cola.separateGraphs(this._nodes, this._links);
+                cola.applyPacking(graphs, width, height, this._defaultNodeSize);
+                this._nodes.forEach(function (v, i) {
+                    _this._descent.x[0][i] = v.x, _this._descent.x[1][i] = v.y;
+                    if (v.bounds) {
+                        v.bounds.setXCentre(v.x);
+                        v.bounds.setYCentre(v.y);
+                    }
+                });
+            }
+        };
+        Layout.prototype.resume = function () {
+            return this.alpha(0.1);
+        };
+        Layout.prototype.stop = function () {
+            return this.alpha(0);
+        };
+        /// find a visibility graph over the set of nodes.  assumes all nodes have a
+        /// bounds property (a rectangle) and that no pair of bounds overlaps.
+        Layout.prototype.prepareEdgeRouting = function (nodeMargin) {
+            if (nodeMargin === void 0) { nodeMargin = 0; }
+            this._visibilityGraph = new cola.geom.TangentVisibilityGraph(this._nodes.map(function (v) {
+                return v.bounds.inflate(-nodeMargin).vertices();
+            }));
+        };
+        /// find a route avoiding node bounds for the given edge.
+        /// assumes the visibility graph has been created (by prepareEdgeRouting method)
+        /// and also assumes that nodes have an index property giving their position in the
+        /// node array.  This index property is created by the start() method.
+        Layout.prototype.routeEdge = function (edge, draw) {
+            var lineData = [];
+            //if (d.source.id === 10 && d.target.id === 11) {
+            //    debugger;
+            //}
+            var vg2 = new cola.geom.TangentVisibilityGraph(this._visibilityGraph.P, { V: this._visibilityGraph.V, E: this._visibilityGraph.E }), port1 = { x: edge.source.x, y: edge.source.y }, port2 = { x: edge.target.x, y: edge.target.y }, start = vg2.addPoint(port1, edge.source.index), end = vg2.addPoint(port2, edge.target.index);
+            vg2.addEdgeIfVisible(port1, port2, edge.source.index, edge.target.index);
+            if (typeof draw !== 'undefined') {
+                draw(vg2);
+            }
+            var sourceInd = function (e) { return e.source.id; }, targetInd = function (e) { return e.target.id; }, length = function (e) { return e.length(); }, spCalc = new cola.shortestpaths.Calculator(vg2.V.length, vg2.E, sourceInd, targetInd, length), shortestPath = spCalc.PathFromNodeToNode(start.id, end.id);
+            if (shortestPath.length === 1 || shortestPath.length === vg2.V.length) {
+                var route = cola.vpsc.makeEdgeBetween(edge.source.innerBounds, edge.target.innerBounds, 5);
+                lineData = [route.sourceIntersection, route.arrowStart];
+            }
+            else {
+                var n = shortestPath.length - 2, p = vg2.V[shortestPath[n]].p, q = vg2.V[shortestPath[0]].p, lineData = [edge.source.innerBounds.rayIntersection(p.x, p.y)];
+                for (var i = n; i >= 0; --i)
+                    lineData.push(vg2.V[shortestPath[i]].p);
+                lineData.push(cola.vpsc.makeEdgeTo(q, edge.target.innerBounds, 5));
+            }
+            //lineData.forEach((v, i) => {
+            //    if (i > 0) {
+            //        var u = lineData[i - 1];
+            //        this._nodes.forEach(function (node) {
+            //            if (node.id === getSourceIndex(d) || node.id === getTargetIndex(d)) return;
+            //            var ints = node.innerBounds.lineIntersections(u.x, u.y, v.x, v.y);
+            //            if (ints.length > 0) {
+            //                debugger;
+            //            }
+            //        })
+            //    }
+            //})
+            return lineData;
+        };
+        //The link source and target may be just a node index, or they may be references to nodes themselves.
+        Layout.getSourceIndex = function (e) {
+            return typeof e.source === 'number' ? e.source : e.source.index;
+        };
+        //The link source and target may be just a node index, or they may be references to nodes themselves.
+        Layout.getTargetIndex = function (e) {
+            return typeof e.target === 'number' ? e.target : e.target.index;
+        };
+        // Get a string ID for a given link.
+        Layout.linkId = function (e) {
+            return Layout.getSourceIndex(e) + "-" + Layout.getTargetIndex(e);
+        };
+        // The fixed property has three bits:
+        // Bit 1 can be set externally (e.g., d.fixed = true) and show persist.
+        // Bit 2 stores the dragging state, from mousedown to mouseup.
+        // Bit 3 stores the hover state, from mouseover to mouseout.
+        // Dragend is a special case: it also clears the hover state.
+        Layout.dragStart = function (d) {
+            d.fixed |= 2; // set bit 2
+            d.px = d.x, d.py = d.y; // set velocity to zero
+        };
+        Layout.dragEnd = function (d) {
+            d.fixed &= ~6; // unset bits 2 and 3
+            //d.fixed = 0;
+        };
+        Layout.mouseOver = function (d) {
+            d.fixed |= 4; // set bit 3
+            d.px = d.x, d.py = d.y; // set velocity to zero
+        };
+        Layout.mouseOut = function (d) {
+            d.fixed &= ~4; // unset bit 3
+        };
+        return Layout;
+    })();
+    cola.Layout = Layout;
+})(cola || (cola = {}));
+///<reference path="layout.ts"/>
+var cola;
+(function (cola) {
+    var LayoutAdaptor = (function (_super) {
+        __extends(LayoutAdaptor, _super);
+        function LayoutAdaptor(options) {
+            _super.call(this);
+            // take in implementation as defined by client
+            var self = this;
+            var o = options;
+            if (o.trigger) {
+                this.trigger = o.trigger;
+            }
+            if (o.kick) {
+                this.kick = o.kick;
+            }
+            if (o.drag) {
+                this.drag = o.drag;
+            }
+            if (o.on) {
+                this.on = o.on;
+            }
+            this.dragstart = this.dragStart = cola.Layout.dragStart;
+            this.dragend = this.dragEnd = cola.Layout.dragEnd;
+        }
+        // dummy functions in case not defined by client
+        LayoutAdaptor.prototype.trigger = function (e) { };
+        ;
+        LayoutAdaptor.prototype.kick = function () { };
+        ;
+        LayoutAdaptor.prototype.drag = function () { };
+        ;
+        LayoutAdaptor.prototype.on = function (eventType, listener) { return this; };
+        ;
+        return LayoutAdaptor;
+    })(cola.Layout);
+    cola.LayoutAdaptor = LayoutAdaptor;
+    /**
+     * provides an interface for use with any external graph system (e.g. Cytoscape.js):
+     */
+    function adaptor(options) {
+        return new LayoutAdaptor(options);
+    }
+    cola.adaptor = adaptor;
+})(cola || (cola = {}));
+var cola;
+(function (cola) {
+    function gridify(pgLayout, nudgeGap, margin, groupMargin) {
+        pgLayout.cola.start(0, 0, 0, 10, false);
+        var gridrouter = route(pgLayout.cola.nodes(), pgLayout.cola.groups(), margin, groupMargin);
+        return gridrouter.routeEdges(pgLayout.powerGraph.powerEdges, nudgeGap, function (e) { return e.source.routerNode.id; }, function (e) { return e.target.routerNode.id; });
+    }
+    cola.gridify = gridify;
+    function route(nodes, groups, margin, groupMargin) {
+        nodes.forEach(function (d) {
+            d.routerNode = {
+                name: d.name,
+                bounds: d.bounds.inflate(-margin)
+            };
+        });
+        groups.forEach(function (d) {
+            d.routerNode = {
+                bounds: d.bounds.inflate(-groupMargin),
+                children: (typeof d.groups !== 'undefined' ? d.groups.map(function (c) { return nodes.length + c.id; }) : [])
+                    .concat(typeof d.leaves !== 'undefined' ? d.leaves.map(function (c) { return c.index; }) : [])
+            };
+        });
+        var gridRouterNodes = nodes.concat(groups).map(function (d, i) {
+            d.routerNode.id = i;
+            return d.routerNode;
+        });
+        return new cola.GridRouter(gridRouterNodes, {
+            getChildren: function (v) { return v.children; },
+            getBounds: function (v) { return v.bounds; }
+        }, margin - groupMargin);
+    }
+    function powerGraphGridLayout(graph, size, grouppadding, margin, groupMargin) {
+        // compute power graph
+        var powerGraph;
+        graph.nodes.forEach(function (v, i) { return v.index = i; });
+        new cola.Layout()
+            .avoidOverlaps(false)
+            .nodes(graph.nodes)
+            .links(graph.links)
+            .powerGraphGroups(function (d) {
+            powerGraph = d;
+            powerGraph.groups.forEach(function (v) { return v.padding = grouppadding; });
+        });
+        // construct a flat graph with dummy nodes for the groups and edges connecting group dummy nodes to their children
+        // power edges attached to groups are replaced with edges connected to the corresponding group dummy node
+        var n = graph.nodes.length;
+        var edges = [];
+        var vs = graph.nodes.slice(0);
+        vs.forEach(function (v, i) { return v.index = i; });
+        powerGraph.groups.forEach(function (g) {
+            var sourceInd = g.index = g.id + n;
+            vs.push(g);
+            if (typeof g.leaves !== 'undefined')
+                g.leaves.forEach(function (v) { return edges.push({ source: sourceInd, target: v.index }); });
+            if (typeof g.groups !== 'undefined')
+                g.groups.forEach(function (gg) { return edges.push({ source: sourceInd, target: gg.id + n }); });
+        });
+        powerGraph.powerEdges.forEach(function (e) {
+            edges.push({ source: e.source.index, target: e.target.index });
+        });
+        // layout the flat graph with dummy nodes and edges
+        new cola.Layout()
+            .size(size)
+            .nodes(vs)
+            .links(edges)
+            .avoidOverlaps(false)
+            .linkDistance(30)
+            .symmetricDiffLinkLengths(5)
+            .convergenceThreshold(1e-4)
+            .start(100, 0, 0, 0, false);
+        // final layout taking node positions from above as starting positions
+        // subject to group containment constraints
+        // and then gridifying the layout
+        return {
+            cola: new cola.Layout()
+                .convergenceThreshold(1e-3)
+                .size(size)
+                .avoidOverlaps(true)
+                .nodes(graph.nodes)
+                .links(graph.links)
+                .groupCompactness(1e-4)
+                .linkDistance(30)
+                .symmetricDiffLinkLengths(5)
+                .powerGraphGroups(function (d) {
+                powerGraph = d;
+                powerGraph.groups.forEach(function (v) {
+                    v.padding = grouppadding;
+                });
+            }).start(50, 0, 100, 0, false),
+            powerGraph: powerGraph
+        };
+    }
+    cola.powerGraphGridLayout = powerGraphGridLayout;
+})(cola || (cola = {}));
+///<reference path="../extern/d3.d.ts"/>
+///<reference path="layout.ts"/>
+var cola;
+(function (cola) {
+    var D3StyleLayoutAdaptor = (function (_super) {
+        __extends(D3StyleLayoutAdaptor, _super);
+        function D3StyleLayoutAdaptor() {
+            _super.call(this);
+            this.event = d3.dispatch(cola.EventType[cola.EventType.start], cola.EventType[cola.EventType.tick], cola.EventType[cola.EventType.end]);
+            // bit of trickyness remapping 'this' so we can reference it in the function body.
+            var d3layout = this;
+            var drag;
+            this.drag = function () {
+                if (!drag) {
+                    var drag = d3.behavior.drag()
+                        .origin(function (d) { return d; })
+                        .on("dragstart.d3adaptor", cola.Layout.dragStart)
+                        .on("drag.d3adaptor", function (d) {
+                        d.px = d3.event.x, d.py = d3.event.y;
+                        d3layout.resume(); // restart annealing
+                    })
+                        .on("dragend.d3adaptor", cola.Layout.dragEnd);
+                }
+                if (!arguments.length)
+                    return drag;
+                // this is the context of the function, i.e. the d3 selection
+                this //.on("mouseover.adaptor", colaMouseover)
+                    .call(drag);
+            };
+        }
+        D3StyleLayoutAdaptor.prototype.trigger = function (e) {
+            var d3event = { type: cola.EventType[e.type], alpha: e.alpha, stress: e.stress };
+            this.event[d3event.type](d3event); // via d3 dispatcher, e.g. event.start(e);
+        };
+        // iterate layout using a d3.timer, which queues calls to tick repeatedly until tick returns true
+        D3StyleLayoutAdaptor.prototype.kick = function () {
+            var _this = this;
+            d3.timer(function () { return _super.prototype.tick.call(_this); });
+        };
+        // a function for binding to events on the adapter
+        D3StyleLayoutAdaptor.prototype.on = function (eventType, listener) {
+            if (typeof eventType === 'string') {
+                this.event.on(eventType, listener);
+            }
+            else {
+                this.event.on(cola.EventType[eventType], listener);
+            }
+            return this;
+        };
+        return D3StyleLayoutAdaptor;
+    })(cola.Layout);
+    cola.D3StyleLayoutAdaptor = D3StyleLayoutAdaptor;
+    /**
+     * provides an interface for use with d3:
+     * - uses the d3 event system to dispatch layout events such as:
+     *   o "start" (start layout process)
+     *   o "tick" (after each layout iteration)
+     *   o "end" (layout converged and complete).
+     * - uses the d3 timer to queue layout iterations.
+     * - sets up d3.behavior.drag to drag nodes
+     *   o use `node.call(<the returned instance of Layout>.drag)` to make nodes draggable
+     * returns an instance of the cola.Layout itself with which the user
+     * can interact directly.
+     */
+    function d3adaptor() {
+        return new D3StyleLayoutAdaptor();
+    }
+    cola.d3adaptor = d3adaptor;
+})(cola || (cola = {}));
+/// <reference path="rectangle.ts"/>
+/// <reference path="shortestpaths.ts"/>
+/// <reference path="geom.ts"/>
+/// <reference path="vpsc.ts"/>
+var cola;
+(function (cola) {
+    var NodeWrapper = (function () {
+        function NodeWrapper(id, rect, children) {
+            this.id = id;
+            this.rect = rect;
+            this.children = children;
+            this.leaf = typeof children === 'undefined' || children.length === 0;
+        }
+        return NodeWrapper;
+    })();
+    cola.NodeWrapper = NodeWrapper;
+    var Vert = (function () {
+        function Vert(id, x, y, node, line) {
+            if (node === void 0) { node = null; }
+            if (line === void 0) { line = null; }
+            this.id = id;
+            this.x = x;
+            this.y = y;
+            this.node = node;
+            this.line = line;
+        }
+        return Vert;
+    })();
+    cola.Vert = Vert;
+    var LongestCommonSubsequence = (function () {
+        function LongestCommonSubsequence(s, t) {
+            this.s = s;
+            this.t = t;
+            var mf = LongestCommonSubsequence.findMatch(s, t);
+            var tr = t.slice(0).reverse();
+            var mr = LongestCommonSubsequence.findMatch(s, tr);
+            if (mf.length >= mr.length) {
+                this.length = mf.length;
+                this.si = mf.si;
+                this.ti = mf.ti;
+                this.reversed = false;
+            }
+            else {
+                this.length = mr.length;
+                this.si = mr.si;
+                this.ti = t.length - mr.ti - mr.length;
+                this.reversed = true;
+            }
+        }
+        LongestCommonSubsequence.findMatch = function (s, t) {
+            var m = s.length;
+            var n = t.length;
+            var match = { length: 0, si: -1, ti: -1 };
+            var l = new Array(m);
+            for (var i = 0; i < m; i++) {
+                l[i] = new Array(n);
+                for (var j = 0; j < n; j++)
+                    if (s[i] === t[j]) {
+                        var v = l[i][j] = (i === 0 || j === 0) ? 1 : l[i - 1][j - 1] + 1;
+                        if (v > match.length) {
+                            match.length = v;
+                            match.si = i - v + 1;
+                            match.ti = j - v + 1;
+                        }
+                        ;
+                    }
+                    else
+                        l[i][j] = 0;
+            }
+            return match;
+        };
+        LongestCommonSubsequence.prototype.getSequence = function () {
+            return this.length >= 0 ? this.s.slice(this.si, this.si + this.length) : [];
+        };
+        return LongestCommonSubsequence;
+    })();
+    cola.LongestCommonSubsequence = LongestCommonSubsequence;
+    var GridRouter = (function () {
+        function GridRouter(originalnodes, accessor, groupPadding) {
+            var _this = this;
+            if (groupPadding === void 0) { groupPadding = 12; }
+            this.originalnodes = originalnodes;
+            this.groupPadding = groupPadding;
+            this.leaves = null;
+            this.nodes = originalnodes.map(function (v, i) { return new NodeWrapper(i, accessor.getBounds(v), accessor.getChildren(v)); });
+            this.leaves = this.nodes.filter(function (v) { return v.leaf; });
+            this.groups = this.nodes.filter(function (g) { return !g.leaf; });
+            this.cols = this.getGridLines('x');
+            this.rows = this.getGridLines('y');
+            // create parents for each node or group that is a member of another's children 
+            this.groups.forEach(function (v) {
+                return v.children.forEach(function (c) { return _this.nodes[c].parent = v; });
+            });
+            // root claims the remaining orphans
+            this.root = { children: [] };
+            this.nodes.forEach(function (v) {
+                if (typeof v.parent === 'undefined') {
+                    v.parent = _this.root;
+                    _this.root.children.push(v.id);
+                }
+                // each node will have grid vertices associated with it,
+                // some inside the node and some on the boundary
+                // leaf nodes will have exactly one internal node at the center
+                // and four boundary nodes
+                // groups will have potentially many of each
+                v.ports = [];
+            });
+            // nodes ordered by their position in the group hierarchy
+            this.backToFront = this.nodes.slice(0);
+            this.backToFront.sort(function (x, y) { return _this.getDepth(x) - _this.getDepth(y); });
+            // compute boundary rectangles for each group
+            // has to be done from front to back, i.e. inside groups to outside groups
+            // such that each can be made large enough to enclose its interior
+            var frontToBackGroups = this.backToFront.slice(0).reverse().filter(function (g) { return !g.leaf; });
+            frontToBackGroups.forEach(function (v) {
+                var r = cola.vpsc.Rectangle.empty();
+                v.children.forEach(function (c) { return r = r.union(_this.nodes[c].rect); });
+                v.rect = r.inflate(_this.groupPadding);
+            });
+            var colMids = this.midPoints(this.cols.map(function (r) { return r.pos; }));
+            var rowMids = this.midPoints(this.rows.map(function (r) { return r.pos; }));
+            // setup extents of lines
+            var rowx = colMids[0], rowX = colMids[colMids.length - 1];
+            var coly = rowMids[0], colY = rowMids[rowMids.length - 1];
+            // horizontal lines
+            var hlines = this.rows.map(function (r) { return { x1: rowx, x2: rowX, y1: r.pos, y2: r.pos }; })
+                .concat(rowMids.map(function (m) { return { x1: rowx, x2: rowX, y1: m, y2: m }; }));
+            // vertical lines
+            var vlines = this.cols.map(function (c) { return { x1: c.pos, x2: c.pos, y1: coly, y2: colY }; })
+                .concat(colMids.map(function (m) { return { x1: m, x2: m, y1: coly, y2: colY }; }));
+            // the full set of lines
+            var lines = hlines.concat(vlines);
+            // we record the vertices associated with each line
+            lines.forEach(function (l) { return l.verts = []; });
+            // the routing graph
+            this.verts = [];
+            this.edges = [];
+            // create vertices at the crossings of horizontal and vertical grid-lines
+            hlines.forEach(function (h) {
+                return vlines.forEach(function (v) {
+                    var p = new Vert(_this.verts.length, v.x1, h.y1);
+                    h.verts.push(p);
+                    v.verts.push(p);
+                    _this.verts.push(p);
+                    // assign vertices to the nodes immediately under them
+                    var i = _this.backToFront.length;
+                    while (i-- > 0) {
+                        var node = _this.backToFront[i], r = node.rect;
+                        var dx = Math.abs(p.x - r.cx()), dy = Math.abs(p.y - r.cy());
+                        if (dx < r.width() / 2 && dy < r.height() / 2) {
+                            p.node = node;
+                            break;
+                        }
+                    }
+                });
+            });
+            lines.forEach(function (l, li) {
+                // create vertices at the intersections of nodes and lines
+                _this.nodes.forEach(function (v, i) {
+                    v.rect.lineIntersections(l.x1, l.y1, l.x2, l.y2).forEach(function (intersect, j) {
+                        //console.log(li+','+i+','+j+':'+intersect.x + ',' + intersect.y);
+                        var p = new Vert(_this.verts.length, intersect.x, intersect.y, v, l);
+                        _this.verts.push(p);
+                        l.verts.push(p);
+                        v.ports.push(p);
+                    });
+                });
+                // split lines into edges joining vertices
+                var isHoriz = Math.abs(l.y1 - l.y2) < 0.1;
+                var delta = function (a, b) { return isHoriz ? b.x - a.x : b.y - a.y; };
+                l.verts.sort(delta);
+                for (var i = 1; i < l.verts.length; i++) {
+                    var u = l.verts[i - 1], v = l.verts[i];
+                    if (u.node && u.node === v.node && u.node.leaf)
+                        continue;
+                    _this.edges.push({ source: u.id, target: v.id, length: Math.abs(delta(u, v)) });
+                }
+            });
+        }
+        GridRouter.prototype.avg = function (a) { return a.reduce(function (x, y) { return x + y; }) / a.length; };
+        // in the given axis, find sets of leaves overlapping in that axis
+        // center of each GridLine is average of all nodes in column
+        GridRouter.prototype.getGridLines = function (axis) {
+            var columns = [];
+            var ls = this.leaves.slice(0, this.leaves.length);
+            while (ls.length > 0) {
+                // find a column of all leaves overlapping in axis with the first leaf
+                var overlapping = ls.filter(function (v) { return v.rect['overlap' + axis.toUpperCase()](ls[0].rect); });
+                var col = {
+                    nodes: overlapping,
+                    pos: this.avg(overlapping.map(function (v) { return v.rect['c' + axis](); }))
+                };
+                columns.push(col);
+                col.nodes.forEach(function (v) { return ls.splice(ls.indexOf(v), 1); });
+            }
+            columns.sort(function (a, b) { return a.pos - b.pos; });
+            return columns;
+        };
+        // get the depth of the given node in the group hierarchy
+        GridRouter.prototype.getDepth = function (v) {
+            var depth = 0;
+            while (v.parent !== this.root) {
+                depth++;
+                v = v.parent;
+            }
+            return depth;
+        };
+        // medial axes between node centres and also boundary lines for the grid
+        GridRouter.prototype.midPoints = function (a) {
+            var gap = a[1] - a[0];
+            var mids = [a[0] - gap / 2];
+            for (var i = 1; i < a.length; i++) {
+                mids.push((a[i] + a[i - 1]) / 2);
+            }
+            mids.push(a[a.length - 1] + gap / 2);
+            return mids;
+        };
+        // find path from v to root including both v and root
+        GridRouter.prototype.findLineage = function (v) {
+            var lineage = [v];
+            do {
+                v = v.parent;
+                lineage.push(v);
+            } while (v !== this.root);
+            return lineage.reverse();
+        };
+        // find path connecting a and b through their lowest common ancestor
+        GridRouter.prototype.findAncestorPathBetween = function (a, b) {
+            var aa = this.findLineage(a), ba = this.findLineage(b), i = 0;
+            while (aa[i] === ba[i])
+                i++;
+            // i-1 to include common ancestor only once (as first element)
+            return { commonAncestor: aa[i - 1], lineages: aa.slice(i).concat(ba.slice(i)) };
+        };
+        // when finding a path between two nodes a and b, siblings of a and b on the
+        // paths from a and b to their least common ancestor are obstacles
+        GridRouter.prototype.siblingObstacles = function (a, b) {
+            var _this = this;
+            var path = this.findAncestorPathBetween(a, b);
+            var lineageLookup = {};
+            path.lineages.forEach(function (v) { return lineageLookup[v.id] = {}; });
+            var obstacles = path.commonAncestor.children.filter(function (v) { return !(v in lineageLookup); });
+            path.lineages
+                .filter(function (v) { return v.parent !== path.commonAncestor; })
+                .forEach(function (v) { return obstacles = obstacles.concat(v.parent.children.filter(function (c) { return c !== v.id; })); });
+            return obstacles.map(function (v) { return _this.nodes[v]; });
+        };
+        // for the given routes, extract all the segments orthogonal to the axis x
+        // and return all them grouped by x position
+        GridRouter.getSegmentSets = function (routes, x, y) {
+            // vsegments is a list of vertical segments sorted by x position
+            var vsegments = [];
+            for (var ei = 0; ei < routes.length; ei++) {
+                var route = routes[ei];
+                for (var si = 0; si < route.length; si++) {
+                    var s = route[si];
+                    s.edgeid = ei;
+                    s.i = si;
+                    var sdx = s[1][x] - s[0][x];
+                    if (Math.abs(sdx) < 0.1) {
+                        vsegments.push(s);
+                    }
+                }
+            }
+            vsegments.sort(function (a, b) { return a[0][x] - b[0][x]; });
+            // vsegmentsets is a set of sets of segments grouped by x position
+            var vsegmentsets = [];
+            var segmentset = null;
+            for (var i = 0; i < vsegments.length; i++) {
+                var s = vsegments[i];
+                if (!segmentset || Math.abs(s[0][x] - segmentset.pos) > 0.1) {
+                    segmentset = { pos: s[0][x], segments: [] };
+                    vsegmentsets.push(segmentset);
+                }
+                segmentset.segments.push(s);
+            }
+            return vsegmentsets;
+        };
+        // for all segments in this bundle create a vpsc problem such that
+        // each segment's x position is a variable and separation constraints 
+        // are given by the partial order over the edges to which the segments belong
+        // for each pair s1,s2 of segments in the open set:
+        //   e1 = edge of s1, e2 = edge of s2
+        //   if leftOf(e1,e2) create constraint s1.x + gap <= s2.x
+        //   else if leftOf(e2,e1) create cons. s2.x + gap <= s1.x
+        GridRouter.nudgeSegs = function (x, y, routes, segments, leftOf, gap) {
+            var n = segments.length;
+            if (n <= 1)
+                return;
+            var vs = segments.map(function (s) { return new cola.vpsc.Variable(s[0][x]); });
+            var cs = [];
+            for (var i = 0; i < n; i++) {
+                for (var j = 0; j < n; j++) {
+                    if (i === j)
+                        continue;
+                    var s1 = segments[i], s2 = segments[j], e1 = s1.edgeid, e2 = s2.edgeid, lind = -1, rind = -1;
+                    // in page coordinates (not cartesian) the notion of 'leftof' is flipped in the horizontal axis from the vertical axis
+                    // that is, when nudging vertical segments, if they increase in the y(conj) direction the segment belonging to the
+                    // 'left' edge actually needs to be nudged to the right
+                    // when nudging horizontal segments, if the segments increase in the x direction
+                    // then the 'left' segment needs to go higher, i.e. to have y pos less than that of the right
+                    if (x == 'x') {
+                        if (leftOf(e1, e2)) {
+                            //console.log('s1: ' + s1[0][x] + ',' + s1[0][y] + '-' + s1[1][x] + ',' + s1[1][y]);
+                            if (s1[0][y] < s1[1][y]) {
+                                lind = j, rind = i;
+                            }
+                            else {
+                                lind = i, rind = j;
+                            }
+                        }
+                    }
+                    else {
+                        if (leftOf(e1, e2)) {
+                            if (s1[0][y] < s1[1][y]) {
+                                lind = i, rind = j;
+                            }
+                            else {
+                                lind = j, rind = i;
+                            }
+                        }
+                    }
+                    if (lind >= 0) {
+                        //console.log(x+' constraint: ' + lind + '<' + rind);
+                        cs.push(new cola.vpsc.Constraint(vs[lind], vs[rind], gap));
+                    }
+                }
+            }
+            var solver = new cola.vpsc.Solver(vs, cs);
+            solver.solve();
+            vs.forEach(function (v, i) {
+                var s = segments[i];
+                var pos = v.position();
+                s[0][x] = s[1][x] = pos;
+                var route = routes[s.edgeid];
+                if (s.i > 0)
+                    route[s.i - 1][1][x] = pos;
+                if (s.i < route.length - 1)
+                    route[s.i + 1][0][x] = pos;
+            });
+        };
+        GridRouter.nudgeSegments = function (routes, x, y, leftOf, gap) {
+            var vsegmentsets = GridRouter.getSegmentSets(routes, x, y);
+            // scan the grouped (by x) segment sets to find co-linear bundles
+            for (var i = 0; i < vsegmentsets.length; i++) {
+                var ss = vsegmentsets[i];
+                var events = [];
+                for (var j = 0; j < ss.segments.length; j++) {
+                    var s = ss.segments[j];
+                    events.push({ type: 0, s: s, pos: Math.min(s[0][y], s[1][y]) });
+                    events.push({ type: 1, s: s, pos: Math.max(s[0][y], s[1][y]) });
+                }
+                events.sort(function (a, b) { return a.pos - b.pos + a.type - b.type; });
+                var open = [];
+                var openCount = 0;
+                events.forEach(function (e) {
+                    if (e.type === 0) {
+                        open.push(e.s);
+                        openCount++;
+                    }
+                    else {
+                        openCount--;
+                    }
+                    if (openCount == 0) {
+                        GridRouter.nudgeSegs(x, y, routes, open, leftOf, gap);
+                        open = [];
+                    }
+                });
+            }
+        };
+        // obtain routes for the specified edges, nicely nudged apart
+        // warning: edge paths may be reversed such that common paths are ordered consistently within bundles!
+        // @param edges list of edges
+        // @param nudgeGap how much to space parallel edge segements
+        // @param source function to retrieve the index of the source node for a given edge
+        // @param target function to retrieve the index of the target node for a given edge
+        // @returns an array giving, for each edge, an array of segments, each segment a pair of points in an array
+        GridRouter.prototype.routeEdges = function (edges, nudgeGap, source, target) {
+            var _this = this;
+            var routePaths = edges.map(function (e) { return _this.route(source(e), target(e)); });
+            var order = cola.GridRouter.orderEdges(routePaths);
+            var routes = routePaths.map(function (e) { return cola.GridRouter.makeSegments(e); });
+            cola.GridRouter.nudgeSegments(routes, 'x', 'y', order, nudgeGap);
+            cola.GridRouter.nudgeSegments(routes, 'y', 'x', order, nudgeGap);
+            cola.GridRouter.unreverseEdges(routes, routePaths);
+            return routes;
+        };
+        // path may have been reversed by the subsequence processing in orderEdges
+        // so now we need to restore the original order
+        GridRouter.unreverseEdges = function (routes, routePaths) {
+            routes.forEach(function (segments, i) {
+                var path = routePaths[i];
+                if (path.reversed) {
+                    segments.reverse(); // reverse order of segments
+                    segments.forEach(function (segment) {
+                        segment.reverse(); // reverse each segment
+                    });
+                }
+            });
+        };
+        GridRouter.angleBetween2Lines = function (line1, line2) {
+            var angle1 = Math.atan2(line1[0].y - line1[1].y, line1[0].x - line1[1].x);
+            var angle2 = Math.atan2(line2[0].y - line2[1].y, line2[0].x - line2[1].x);
+            var diff = angle1 - angle2;
+            if (diff > Math.PI || diff < -Math.PI) {
+                diff = angle2 - angle1;
+            }
+            return diff;
+        };
+        // does the path a-b-c describe a left turn?
+        GridRouter.isLeft = function (a, b, c) {
+            return ((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)) <= 0;
+        };
+        // for the given list of ordered pairs, returns a function that (efficiently) looks-up a specific pair to
+        // see if it exists in the list
+        GridRouter.getOrder = function (pairs) {
+            var outgoing = {};
+            for (var i = 0; i < pairs.length; i++) {
+                var p = pairs[i];
+                if (typeof outgoing[p.l] === 'undefined')
+                    outgoing[p.l] = {};
+                outgoing[p.l][p.r] = true;
+            }
+            return function (l, r) { return typeof outgoing[l] !== 'undefined' && outgoing[l][r]; };
+        };
+        // returns an ordering (a lookup function) that determines the correct order to nudge the
+        // edge paths apart to minimize crossings
+        GridRouter.orderEdges = function (edges) {
+            var edgeOrder = [];
+            for (var i = 0; i < edges.length - 1; i++) {
+                for (var j = i + 1; j < edges.length; j++) {
+                    var e = edges[i], f = edges[j], lcs = new cola.LongestCommonSubsequence(e, f);
+                    var u, vi, vj;
+                    if (lcs.length === 0)
+                        continue; // no common subpath
+                    if (lcs.reversed) {
+                        // if we found a common subpath but one of the edges runs the wrong way, 
+                        // then reverse f.
+                        f.reverse();
+                        f.reversed = true;
+                        lcs = new cola.LongestCommonSubsequence(e, f);
+                    }
+                    if ((lcs.si <= 0 || lcs.ti <= 0) &&
+                        (lcs.si + lcs.length >= e.length || lcs.ti + lcs.length >= f.length)) {
+                        // the paths do not diverge, so make an arbitrary ordering decision
+                        edgeOrder.push({ l: i, r: j });
+                        continue;
+                    }
+                    if (lcs.si + lcs.length >= e.length || lcs.ti + lcs.length >= f.length) {
+                        // if the common subsequence of the
+                        // two edges being considered goes all the way to the
+                        // end of one (or both) of the lines then we have to 
+                        // base our ordering decision on the other end of the
+                        // common subsequence
+                        u = e[lcs.si + 1];
+                        vj = e[lcs.si - 1];
+                        vi = f[lcs.ti - 1];
+                    }
+                    else {
+                        u = e[lcs.si + lcs.length - 2];
+                        vi = e[lcs.si + lcs.length];
+                        vj = f[lcs.ti + lcs.length];
+                    }
+                    if (GridRouter.isLeft(u, vi, vj)) {
+                        edgeOrder.push({ l: j, r: i });
+                    }
+                    else {
+                        edgeOrder.push({ l: i, r: j });
+                    }
+                }
+            }
+            //edgeOrder.forEach(function (e) { console.log('l:' + e.l + ',r:' + e.r) });
+            return cola.GridRouter.getOrder(edgeOrder);
+        };
+        // for an orthogonal path described by a sequence of points, create a list of segments
+        // if consecutive segments would make a straight line they are merged into a single segment
+        // segments are over cloned points, not the original vertices
+        GridRouter.makeSegments = function (path) {
+            function copyPoint(p) {
+                return { x: p.x, y: p.y };
+            }
+            var isStraight = function (a, b, c) { return Math.abs((b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x)) < 0.001; };
+            var segments = [];
+            var a = copyPoint(path[0]);
+            for (var i = 1; i < path.length; i++) {
+                var b = copyPoint(path[i]), c = i < path.length - 1 ? path[i + 1] : null;
+                if (!c || !isStraight(a, b, c)) {
+                    segments.push([a, b]);
+                    a = b;
+                }
+            }
+            return segments;
+        };
+        // find a route between node s and node t
+        // returns an array of indices to verts
+        GridRouter.prototype.route = function (s, t) {
+            var _this = this;
+            var source = this.nodes[s], target = this.nodes[t];
+            this.obstacles = this.siblingObstacles(source, target);
+            var obstacleLookup = {};
+            this.obstacles.forEach(function (o) { return obstacleLookup[o.id] = o; });
+            this.passableEdges = this.edges.filter(function (e) {
+                var u = _this.verts[e.source], v = _this.verts[e.target];
+                return !(u.node && u.node.id in obstacleLookup
+                    || v.node && v.node.id in obstacleLookup);
+            });
+            // add dummy segments linking ports inside source and target
+            for (var i = 1; i < source.ports.length; i++) {
+                var u = source.ports[0].id;
+                var v = source.ports[i].id;
+                this.passableEdges.push({
+                    source: u,
+                    target: v,
+                    length: 0
+                });
+            }
+            for (var i = 1; i < target.ports.length; i++) {
+                var u = target.ports[0].id;
+                var v = target.ports[i].id;
+                this.passableEdges.push({
+                    source: u,
+                    target: v,
+                    length: 0
+                });
+            }
+            var getSource = function (e) { return e.source; }, getTarget = function (e) { return e.target; }, getLength = function (e) { return e.length; };
+            var shortestPathCalculator = new cola.shortestpaths.Calculator(this.verts.length, this.passableEdges, getSource, getTarget, getLength);
+            var bendPenalty = function (u, v, w) {
+                var a = _this.verts[u], b = _this.verts[v], c = _this.verts[w];
+                var dx = Math.abs(c.x - a.x), dy = Math.abs(c.y - a.y);
+                // don't count bends from internal node edges
+                if (a.node === source && a.node === b.node || b.node === target && b.node === c.node)
+                    return 0;
+                return dx > 1 && dy > 1 ? 1000 : 0;
+            };
+            // get shortest path
+            var shortestPath = shortestPathCalculator.PathFromNodeToNodeWithPrevCost(source.ports[0].id, target.ports[0].id, bendPenalty);
+            // shortest path is reversed and does not include the target port
+            var pathPoints = shortestPath.reverse().map(function (vi) { return _this.verts[vi]; });
+            pathPoints.push(this.nodes[target.id].ports[0]);
+            // filter out any extra end points that are inside the source or target (i.e. the dummy segments above)
+            return pathPoints.filter(function (v, i) {
+                return !(i < pathPoints.length - 1 && pathPoints[i + 1].node === source && v.node === source
+                    || i > 0 && v.node === target && pathPoints[i - 1].node === target);
+            });
+        };
+        GridRouter.getRoutePath = function (route, cornerradius, arrowwidth, arrowheight) {
+            var result = {
+                routepath: 'M ' + route[0][0].x + ' ' + route[0][0].y + ' ',
+                arrowpath: ''
+            };
+            if (route.length > 1) {
+                for (var i = 0; i < route.length; i++) {
+                    var li = route[i];
+                    var x = li[1].x, y = li[1].y;
+                    var dx = x - li[0].x;
+                    var dy = y - li[0].y;
+                    if (i < route.length - 1) {
+                        if (Math.abs(dx) > 0) {
+                            x -= dx / Math.abs(dx) * cornerradius;
+                        }
+                        else {
+                            y -= dy / Math.abs(dy) * cornerradius;
+                        }
+                        result.routepath += 'L ' + x + ' ' + y + ' ';
+                        var l = route[i + 1];
+                        var x0 = l[0].x, y0 = l[0].y;
+                        var x1 = l[1].x;
+                        var y1 = l[1].y;
+                        dx = x1 - x0;
+                        dy = y1 - y0;
+                        var angle = GridRouter.angleBetween2Lines(li, l) < 0 ? 1 : 0;
+                        //console.log(cola.GridRouter.angleBetween2Lines(li, l))
+                        var x2, y2;
+                        if (Math.abs(dx) > 0) {
+                            x2 = x0 + dx / Math.abs(dx) * cornerradius;
+                            y2 = y0;
+                        }
+                        else {
+                            x2 = x0;
+                            y2 = y0 + dy / Math.abs(dy) * cornerradius;
+                        }
+                        var cx = Math.abs(x2 - x);
+                        var cy = Math.abs(y2 - y);
+                        result.routepath += 'A ' + cx + ' ' + cy + ' 0 0 ' + angle + ' ' + x2 + ' ' + y2 + ' ';
+                    }
+                    else {
+                        var arrowtip = [x, y];
+                        var arrowcorner1, arrowcorner2;
+                        if (Math.abs(dx) > 0) {
+                            x -= dx / Math.abs(dx) * arrowheight;
+                            arrowcorner1 = [x, y + arrowwidth];
+                            arrowcorner2 = [x, y - arrowwidth];
+                        }
+                        else {
+                            y -= dy / Math.abs(dy) * arrowheight;
+                            arrowcorner1 = [x + arrowwidth, y];
+                            arrowcorner2 = [x - arrowwidth, y];
+                        }
+                        result.routepath += 'L ' + x + ' ' + y + ' ';
+                        if (arrowheight > 0) {
+                            result.arrowpath = 'M ' + arrowtip[0] + ' ' + arrowtip[1] + ' L ' + arrowcorner1[0] + ' ' + arrowcorner1[1]
+                                + ' L ' + arrowcorner2[0] + ' ' + arrowcorner2[1];
+                        }
+                    }
+                }
+            }
+            else {
+                var li = route[0];
+                var x = li[1].x, y = li[1].y;
+                var dx = x - li[0].x;
+                var dy = y - li[0].y;
+                var arrowtip = [x, y];
+                var arrowcorner1, arrowcorner2;
+                if (Math.abs(dx) > 0) {
+                    x -= dx / Math.abs(dx) * arrowheight;
+                    arrowcorner1 = [x, y + arrowwidth];
+                    arrowcorner2 = [x, y - arrowwidth];
+                }
+                else {
+                    y -= dy / Math.abs(dy) * arrowheight;
+                    arrowcorner1 = [x + arrowwidth, y];
+                    arrowcorner2 = [x - arrowwidth, y];
+                }
+                result.routepath += 'L ' + x + ' ' + y + ' ';
+                if (arrowheight > 0) {
+                    result.arrowpath = 'M ' + arrowtip[0] + ' ' + arrowtip[1] + ' L ' + arrowcorner1[0] + ' ' + arrowcorner1[1]
+                        + ' L ' + arrowcorner2[0] + ' ' + arrowcorner2[1];
+                }
+            }
+            return result;
+        };
+        return GridRouter;
+    })();
+    cola.GridRouter = GridRouter;
+})(cola || (cola = {}));
+/**
+ * Use cola to do a layout in 3D!! Yay.
+ * Pretty simple for the moment.
+ */
+var cola;
+(function (cola) {
+    var Link3D = (function () {
+        function Link3D(source, target) {
+            this.source = source;
+            this.target = target;
+        }
+        Link3D.prototype.actualLength = function (x) {
+            var _this = this;
+            return Math.sqrt(x.reduce(function (c, v) {
+                var dx = v[_this.target] - v[_this.source];
+                return c + dx * dx;
+            }, 0));
+        };
+        return Link3D;
+    })();
+    cola.Link3D = Link3D;
+    var Node3D = (function () {
+        function Node3D(x, y, z) {
+            if (x === void 0) { x = 0; }
+            if (y === void 0) { y = 0; }
+            if (z === void 0) { z = 0; }
+            this.x = x;
+            this.y = y;
+            this.z = z;
+        }
+        return Node3D;
+    })();
+    cola.Node3D = Node3D;
+    var Layout3D = (function () {
+        function Layout3D(nodes, links, idealLinkLength) {
+            var _this = this;
+            if (idealLinkLength === void 0) { idealLinkLength = 1; }
+            this.nodes = nodes;
+            this.links = links;
+            this.idealLinkLength = idealLinkLength;
+            this.constraints = null;
+            this.useJaccardLinkLengths = true;
+            this.result = new Array(Layout3D.k);
+            for (var i = 0; i < Layout3D.k; ++i) {
+                this.result[i] = new Array(nodes.length);
+            }
+            nodes.forEach(function (v, i) {
+                for (var _i = 0, _a = Layout3D.dims; _i < _a.length; _i++) {
+                    var dim = _a[_i];
+                    if (typeof v[dim] == 'undefined')
+                        v[dim] = Math.random();
+                }
+                _this.result[0][i] = v.x;
+                _this.result[1][i] = v.y;
+                _this.result[2][i] = v.z;
+            });
+        }
+        ;
+        Layout3D.prototype.linkLength = function (l) {
+            return l.actualLength(this.result);
+        };
+        Layout3D.prototype.start = function (iterations) {
+            var _this = this;
+            if (iterations === void 0) { iterations = 100; }
+            var n = this.nodes.length;
+            var linkAccessor = new LinkAccessor();
+            if (this.useJaccardLinkLengths)
+                cola.jaccardLinkLengths(this.links, linkAccessor, 1.5);
+            this.links.forEach(function (e) { return e.length *= _this.idealLinkLength; });
+            // Create the distance matrix that Cola needs
+            var distanceMatrix = (new cola.shortestpaths.Calculator(n, this.links, function (e) { return e.source; }, function (e) { return e.target; }, function (e) { return e.length; })).DistanceMatrix();
+            var D = cola.Descent.createSquareMatrix(n, function (i, j) { return distanceMatrix[i][j]; });
+            // G is a square matrix with G[i][j] = 1 iff there exists an edge between node i and node j
+            // otherwise 2.
+            var G = cola.Descent.createSquareMatrix(n, function () { return 2; });
+            this.links.forEach(function (_a) {
+                var source = _a.source, target = _a.target;
+                return G[source][target] = G[target][source] = 1;
+            });
+            this.descent = new cola.Descent(this.result, D);
+            this.descent.threshold = 1e-3;
+            this.descent.G = G;
+            //let constraints = this.links.map(e=> <any>{
+            //    axis: 'y', left: e.source, right: e.target, gap: e.length*1.5
+            //});
+            if (this.constraints)
+                this.descent.project = new cola.vpsc.Projection(this.nodes, null, null, this.constraints).projectFunctions();
+            for (var i = 0; i < this.nodes.length; i++) {
+                var v = this.nodes[i];
+                if (v.fixed) {
+                    this.descent.locks.add(i, [v.x, v.y, v.z]);
+                }
+            }
+            this.descent.run(iterations);
+            return this;
+        };
+        Layout3D.prototype.tick = function () {
+            this.descent.locks.clear();
+            for (var i = 0; i < this.nodes.length; i++) {
+                var v = this.nodes[i];
+                if (v.fixed) {
+                    this.descent.locks.add(i, [v.x, v.y, v.z]);
+                }
+            }
+            return this.descent.rungeKutta();
+        };
+        Layout3D.dims = ['x', 'y', 'z'];
+        Layout3D.k = Layout3D.dims.length;
+        return Layout3D;
+    })();
+    cola.Layout3D = Layout3D;
+    var LinkAccessor = (function () {
+        function LinkAccessor() {
+        }
+        LinkAccessor.prototype.getSourceIndex = function (e) { return e.source; };
+        LinkAccessor.prototype.getTargetIndex = function (e) { return e.target; };
+        LinkAccessor.prototype.getLength = function (e) { return e.length; };
+        LinkAccessor.prototype.setLength = function (e, l) { e.length = l; };
+        return LinkAccessor;
+    })();
+})(cola || (cola = {}));
+//# sourceMappingURL=cola.js.map
\ No newline at end of file
diff --git a/src/legacy/design-studio/js/crossfilter.js b/src/legacy/design-studio/js/crossfilter.js
new file mode 100644
index 0000000000000000000000000000000000000000..71577d395d25e59935d5eb24b9a7d05bda67c38a
--- /dev/null
+++ b/src/legacy/design-studio/js/crossfilter.js
@@ -0,0 +1,3077 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.crossfilter = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+module.exports = require("./src/crossfilter").crossfilter;
+
+},{"./src/crossfilter":6}],2:[function(require,module,exports){
+(function (global){
+/**
+ * lodash (Custom Build) <https://lodash.com/>
+ * Build: `lodash modularize exports="npm" -o ./`
+ * Copyright jQuery Foundation and other contributors <https://jquery.org/>
+ * Released under MIT license <https://lodash.com/license>
+ * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
+ * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ */
+
+/** Used as the `TypeError` message for "Functions" methods. */
+var FUNC_ERROR_TEXT = 'Expected a function';
+
+/** Used to stand-in for `undefined` hash values. */
+var HASH_UNDEFINED = '__lodash_hash_undefined__';
+
+/** Used as references for various `Number` constants. */
+var INFINITY = 1 / 0;
+
+/** `Object#toString` result references. */
+var funcTag = '[object Function]',
+    genTag = '[object GeneratorFunction]',
+    symbolTag = '[object Symbol]';
+
+/** Used to match property names within property paths. */
+var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
+    reIsPlainProp = /^\w*$/,
+    reLeadingDot = /^\./,
+    rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
+
+/**
+ * Used to match `RegExp`
+ * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
+ */
+var reRegExpChar = /[\\^$.*+?()[\]{}|]/g;
+
+/** Used to match backslashes in property paths. */
+var reEscapeChar = /\\(\\)?/g;
+
+/** Used to detect host constructors (Safari). */
+var reIsHostCtor = /^\[object .+?Constructor\]$/;
+
+/** Detect free variable `global` from Node.js. */
+var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
+
+/** Detect free variable `self`. */
+var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
+
+/** Used as a reference to the global object. */
+var root = freeGlobal || freeSelf || Function('return this')();
+
+/**
+ * Gets the value at `key` of `object`.
+ *
+ * @private
+ * @param {Object} [object] The object to query.
+ * @param {string} key The key of the property to get.
+ * @returns {*} Returns the property value.
+ */
+function getValue(object, key) {
+  return object == null ? undefined : object[key];
+}
+
+/**
+ * Checks if `value` is a host object in IE < 9.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a host object, else `false`.
+ */
+function isHostObject(value) {
+  // Many host objects are `Object` objects that can coerce to strings
+  // despite having improperly defined `toString` methods.
+  var result = false;
+  if (value != null && typeof value.toString != 'function') {
+    try {
+      result = !!(value + '');
+    } catch (e) {}
+  }
+  return result;
+}
+
+/** Used for built-in method references. */
+var arrayProto = Array.prototype,
+    funcProto = Function.prototype,
+    objectProto = Object.prototype;
+
+/** Used to detect overreaching core-js shims. */
+var coreJsData = root['__core-js_shared__'];
+
+/** Used to detect methods masquerading as native. */
+var maskSrcKey = (function() {
+  var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');
+  return uid ? ('Symbol(src)_1.' + uid) : '';
+}());
+
+/** Used to resolve the decompiled source of functions. */
+var funcToString = funcProto.toString;
+
+/** Used to check objects for own properties. */
+var hasOwnProperty = objectProto.hasOwnProperty;
+
+/**
+ * Used to resolve the
+ * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
+ * of values.
+ */
+var objectToString = objectProto.toString;
+
+/** Used to detect if a method is native. */
+var reIsNative = RegExp('^' +
+  funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&')
+  .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
+);
+
+/** Built-in value references. */
+var Symbol = root.Symbol,
+    splice = arrayProto.splice;
+
+/* Built-in method references that are verified to be native. */
+var Map = getNative(root, 'Map'),
+    nativeCreate = getNative(Object, 'create');
+
+/** Used to convert symbols to primitives and strings. */
+var symbolProto = Symbol ? Symbol.prototype : undefined,
+    symbolToString = symbolProto ? symbolProto.toString : undefined;
+
+/**
+ * Creates a hash object.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [entries] The key-value pairs to cache.
+ */
+function Hash(entries) {
+  var index = -1,
+      length = entries ? entries.length : 0;
+
+  this.clear();
+  while (++index < length) {
+    var entry = entries[index];
+    this.set(entry[0], entry[1]);
+  }
+}
+
+/**
+ * Removes all key-value entries from the hash.
+ *
+ * @private
+ * @name clear
+ * @memberOf Hash
+ */
+function hashClear() {
+  this.__data__ = nativeCreate ? nativeCreate(null) : {};
+}
+
+/**
+ * Removes `key` and its value from the hash.
+ *
+ * @private
+ * @name delete
+ * @memberOf Hash
+ * @param {Object} hash The hash to modify.
+ * @param {string} key The key of the value to remove.
+ * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+ */
+function hashDelete(key) {
+  return this.has(key) && delete this.__data__[key];
+}
+
+/**
+ * Gets the hash value for `key`.
+ *
+ * @private
+ * @name get
+ * @memberOf Hash
+ * @param {string} key The key of the value to get.
+ * @returns {*} Returns the entry value.
+ */
+function hashGet(key) {
+  var data = this.__data__;
+  if (nativeCreate) {
+    var result = data[key];
+    return result === HASH_UNDEFINED ? undefined : result;
+  }
+  return hasOwnProperty.call(data, key) ? data[key] : undefined;
+}
+
+/**
+ * Checks if a hash value for `key` exists.
+ *
+ * @private
+ * @name has
+ * @memberOf Hash
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
+function hashHas(key) {
+  var data = this.__data__;
+  return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key);
+}
+
+/**
+ * Sets the hash `key` to `value`.
+ *
+ * @private
+ * @name set
+ * @memberOf Hash
+ * @param {string} key The key of the value to set.
+ * @param {*} value The value to set.
+ * @returns {Object} Returns the hash instance.
+ */
+function hashSet(key, value) {
+  var data = this.__data__;
+  data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;
+  return this;
+}
+
+// Add methods to `Hash`.
+Hash.prototype.clear = hashClear;
+Hash.prototype['delete'] = hashDelete;
+Hash.prototype.get = hashGet;
+Hash.prototype.has = hashHas;
+Hash.prototype.set = hashSet;
+
+/**
+ * Creates an list cache object.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [entries] The key-value pairs to cache.
+ */
+function ListCache(entries) {
+  var index = -1,
+      length = entries ? entries.length : 0;
+
+  this.clear();
+  while (++index < length) {
+    var entry = entries[index];
+    this.set(entry[0], entry[1]);
+  }
+}
+
+/**
+ * Removes all key-value entries from the list cache.
+ *
+ * @private
+ * @name clear
+ * @memberOf ListCache
+ */
+function listCacheClear() {
+  this.__data__ = [];
+}
+
+/**
+ * Removes `key` and its value from the list cache.
+ *
+ * @private
+ * @name delete
+ * @memberOf ListCache
+ * @param {string} key The key of the value to remove.
+ * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+ */
+function listCacheDelete(key) {
+  var data = this.__data__,
+      index = assocIndexOf(data, key);
+
+  if (index < 0) {
+    return false;
+  }
+  var lastIndex = data.length - 1;
+  if (index == lastIndex) {
+    data.pop();
+  } else {
+    splice.call(data, index, 1);
+  }
+  return true;
+}
+
+/**
+ * Gets the list cache value for `key`.
+ *
+ * @private
+ * @name get
+ * @memberOf ListCache
+ * @param {string} key The key of the value to get.
+ * @returns {*} Returns the entry value.
+ */
+function listCacheGet(key) {
+  var data = this.__data__,
+      index = assocIndexOf(data, key);
+
+  return index < 0 ? undefined : data[index][1];
+}
+
+/**
+ * Checks if a list cache value for `key` exists.
+ *
+ * @private
+ * @name has
+ * @memberOf ListCache
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
+function listCacheHas(key) {
+  return assocIndexOf(this.__data__, key) > -1;
+}
+
+/**
+ * Sets the list cache `key` to `value`.
+ *
+ * @private
+ * @name set
+ * @memberOf ListCache
+ * @param {string} key The key of the value to set.
+ * @param {*} value The value to set.
+ * @returns {Object} Returns the list cache instance.
+ */
+function listCacheSet(key, value) {
+  var data = this.__data__,
+      index = assocIndexOf(data, key);
+
+  if (index < 0) {
+    data.push([key, value]);
+  } else {
+    data[index][1] = value;
+  }
+  return this;
+}
+
+// Add methods to `ListCache`.
+ListCache.prototype.clear = listCacheClear;
+ListCache.prototype['delete'] = listCacheDelete;
+ListCache.prototype.get = listCacheGet;
+ListCache.prototype.has = listCacheHas;
+ListCache.prototype.set = listCacheSet;
+
+/**
+ * Creates a map cache object to store key-value pairs.
+ *
+ * @private
+ * @constructor
+ * @param {Array} [entries] The key-value pairs to cache.
+ */
+function MapCache(entries) {
+  var index = -1,
+      length = entries ? entries.length : 0;
+
+  this.clear();
+  while (++index < length) {
+    var entry = entries[index];
+    this.set(entry[0], entry[1]);
+  }
+}
+
+/**
+ * Removes all key-value entries from the map.
+ *
+ * @private
+ * @name clear
+ * @memberOf MapCache
+ */
+function mapCacheClear() {
+  this.__data__ = {
+    'hash': new Hash,
+    'map': new (Map || ListCache),
+    'string': new Hash
+  };
+}
+
+/**
+ * Removes `key` and its value from the map.
+ *
+ * @private
+ * @name delete
+ * @memberOf MapCache
+ * @param {string} key The key of the value to remove.
+ * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+ */
+function mapCacheDelete(key) {
+  return getMapData(this, key)['delete'](key);
+}
+
+/**
+ * Gets the map value for `key`.
+ *
+ * @private
+ * @name get
+ * @memberOf MapCache
+ * @param {string} key The key of the value to get.
+ * @returns {*} Returns the entry value.
+ */
+function mapCacheGet(key) {
+  return getMapData(this, key).get(key);
+}
+
+/**
+ * Checks if a map value for `key` exists.
+ *
+ * @private
+ * @name has
+ * @memberOf MapCache
+ * @param {string} key The key of the entry to check.
+ * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+ */
+function mapCacheHas(key) {
+  return getMapData(this, key).has(key);
+}
+
+/**
+ * Sets the map `key` to `value`.
+ *
+ * @private
+ * @name set
+ * @memberOf MapCache
+ * @param {string} key The key of the value to set.
+ * @param {*} value The value to set.
+ * @returns {Object} Returns the map cache instance.
+ */
+function mapCacheSet(key, value) {
+  getMapData(this, key).set(key, value);
+  return this;
+}
+
+// Add methods to `MapCache`.
+MapCache.prototype.clear = mapCacheClear;
+MapCache.prototype['delete'] = mapCacheDelete;
+MapCache.prototype.get = mapCacheGet;
+MapCache.prototype.has = mapCacheHas;
+MapCache.prototype.set = mapCacheSet;
+
+/**
+ * Gets the index at which the `key` is found in `array` of key-value pairs.
+ *
+ * @private
+ * @param {Array} array The array to inspect.
+ * @param {*} key The key to search for.
+ * @returns {number} Returns the index of the matched value, else `-1`.
+ */
+function assocIndexOf(array, key) {
+  var length = array.length;
+  while (length--) {
+    if (eq(array[length][0], key)) {
+      return length;
+    }
+  }
+  return -1;
+}
+
+/**
+ * The base implementation of `_.isNative` without bad shim checks.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a native function,
+ *  else `false`.
+ */
+function baseIsNative(value) {
+  if (!isObject(value) || isMasked(value)) {
+    return false;
+  }
+  var pattern = (isFunction(value) || isHostObject(value)) ? reIsNative : reIsHostCtor;
+  return pattern.test(toSource(value));
+}
+
+/**
+ * The base implementation of `_.toString` which doesn't convert nullish
+ * values to empty strings.
+ *
+ * @private
+ * @param {*} value The value to process.
+ * @returns {string} Returns the string.
+ */
+function baseToString(value) {
+  // Exit early for strings to avoid a performance hit in some environments.
+  if (typeof value == 'string') {
+    return value;
+  }
+  if (isSymbol(value)) {
+    return symbolToString ? symbolToString.call(value) : '';
+  }
+  var result = (value + '');
+  return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
+}
+
+/**
+ * Casts `value` to a path array if it's not one.
+ *
+ * @private
+ * @param {*} value The value to inspect.
+ * @returns {Array} Returns the cast property path array.
+ */
+function castPath(value) {
+  return isArray(value) ? value : stringToPath(value);
+}
+
+/**
+ * Gets the data for `map`.
+ *
+ * @private
+ * @param {Object} map The map to query.
+ * @param {string} key The reference key.
+ * @returns {*} Returns the map data.
+ */
+function getMapData(map, key) {
+  var data = map.__data__;
+  return isKeyable(key)
+    ? data[typeof key == 'string' ? 'string' : 'hash']
+    : data.map;
+}
+
+/**
+ * Gets the native function at `key` of `object`.
+ *
+ * @private
+ * @param {Object} object The object to query.
+ * @param {string} key The key of the method to get.
+ * @returns {*} Returns the function if it's native, else `undefined`.
+ */
+function getNative(object, key) {
+  var value = getValue(object, key);
+  return baseIsNative(value) ? value : undefined;
+}
+
+/**
+ * Checks if `value` is a property name and not a property path.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @param {Object} [object] The object to query keys on.
+ * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
+ */
+function isKey(value, object) {
+  if (isArray(value)) {
+    return false;
+  }
+  var type = typeof value;
+  if (type == 'number' || type == 'symbol' || type == 'boolean' ||
+      value == null || isSymbol(value)) {
+    return true;
+  }
+  return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
+    (object != null && value in Object(object));
+}
+
+/**
+ * Checks if `value` is suitable for use as unique object key.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is suitable, else `false`.
+ */
+function isKeyable(value) {
+  var type = typeof value;
+  return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')
+    ? (value !== '__proto__')
+    : (value === null);
+}
+
+/**
+ * Checks if `func` has its source masked.
+ *
+ * @private
+ * @param {Function} func The function to check.
+ * @returns {boolean} Returns `true` if `func` is masked, else `false`.
+ */
+function isMasked(func) {
+  return !!maskSrcKey && (maskSrcKey in func);
+}
+
+/**
+ * Converts `string` to a property path array.
+ *
+ * @private
+ * @param {string} string The string to convert.
+ * @returns {Array} Returns the property path array.
+ */
+var stringToPath = memoize(function(string) {
+  string = toString(string);
+
+  var result = [];
+  if (reLeadingDot.test(string)) {
+    result.push('');
+  }
+  string.replace(rePropName, function(match, number, quote, string) {
+    result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
+  });
+  return result;
+});
+
+/**
+ * Converts `value` to a string key if it's not a string or symbol.
+ *
+ * @private
+ * @param {*} value The value to inspect.
+ * @returns {string|symbol} Returns the key.
+ */
+function toKey(value) {
+  if (typeof value == 'string' || isSymbol(value)) {
+    return value;
+  }
+  var result = (value + '');
+  return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
+}
+
+/**
+ * Converts `func` to its source code.
+ *
+ * @private
+ * @param {Function} func The function to process.
+ * @returns {string} Returns the source code.
+ */
+function toSource(func) {
+  if (func != null) {
+    try {
+      return funcToString.call(func);
+    } catch (e) {}
+    try {
+      return (func + '');
+    } catch (e) {}
+  }
+  return '';
+}
+
+/**
+ * Creates a function that memoizes the result of `func`. If `resolver` is
+ * provided, it determines the cache key for storing the result based on the
+ * arguments provided to the memoized function. By default, the first argument
+ * provided to the memoized function is used as the map cache key. The `func`
+ * is invoked with the `this` binding of the memoized function.
+ *
+ * **Note:** The cache is exposed as the `cache` property on the memoized
+ * function. Its creation may be customized by replacing the `_.memoize.Cache`
+ * constructor with one whose instances implement the
+ * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object)
+ * method interface of `delete`, `get`, `has`, and `set`.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Function
+ * @param {Function} func The function to have its output memoized.
+ * @param {Function} [resolver] The function to resolve the cache key.
+ * @returns {Function} Returns the new memoized function.
+ * @example
+ *
+ * var object = { 'a': 1, 'b': 2 };
+ * var other = { 'c': 3, 'd': 4 };
+ *
+ * var values = _.memoize(_.values);
+ * values(object);
+ * // => [1, 2]
+ *
+ * values(other);
+ * // => [3, 4]
+ *
+ * object.a = 2;
+ * values(object);
+ * // => [1, 2]
+ *
+ * // Modify the result cache.
+ * values.cache.set(object, ['a', 'b']);
+ * values(object);
+ * // => ['a', 'b']
+ *
+ * // Replace `_.memoize.Cache`.
+ * _.memoize.Cache = WeakMap;
+ */
+function memoize(func, resolver) {
+  if (typeof func != 'function' || (resolver && typeof resolver != 'function')) {
+    throw new TypeError(FUNC_ERROR_TEXT);
+  }
+  var memoized = function() {
+    var args = arguments,
+        key = resolver ? resolver.apply(this, args) : args[0],
+        cache = memoized.cache;
+
+    if (cache.has(key)) {
+      return cache.get(key);
+    }
+    var result = func.apply(this, args);
+    memoized.cache = cache.set(key, result);
+    return result;
+  };
+  memoized.cache = new (memoize.Cache || MapCache);
+  return memoized;
+}
+
+// Assign cache to `_.memoize`.
+memoize.Cache = MapCache;
+
+/**
+ * Performs a
+ * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+ * comparison between two values to determine if they are equivalent.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to compare.
+ * @param {*} other The other value to compare.
+ * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+ * @example
+ *
+ * var object = { 'a': 1 };
+ * var other = { 'a': 1 };
+ *
+ * _.eq(object, object);
+ * // => true
+ *
+ * _.eq(object, other);
+ * // => false
+ *
+ * _.eq('a', 'a');
+ * // => true
+ *
+ * _.eq('a', Object('a'));
+ * // => false
+ *
+ * _.eq(NaN, NaN);
+ * // => true
+ */
+function eq(value, other) {
+  return value === other || (value !== value && other !== other);
+}
+
+/**
+ * Checks if `value` is classified as an `Array` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an array, else `false`.
+ * @example
+ *
+ * _.isArray([1, 2, 3]);
+ * // => true
+ *
+ * _.isArray(document.body.children);
+ * // => false
+ *
+ * _.isArray('abc');
+ * // => false
+ *
+ * _.isArray(_.noop);
+ * // => false
+ */
+var isArray = Array.isArray;
+
+/**
+ * Checks if `value` is classified as a `Function` object.
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a function, else `false`.
+ * @example
+ *
+ * _.isFunction(_);
+ * // => true
+ *
+ * _.isFunction(/abc/);
+ * // => false
+ */
+function isFunction(value) {
+  // The use of `Object#toString` avoids issues with the `typeof` operator
+  // in Safari 8-9 which returns 'object' for typed array and other constructors.
+  var tag = isObject(value) ? objectToString.call(value) : '';
+  return tag == funcTag || tag == genTag;
+}
+
+/**
+ * Checks if `value` is the
+ * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
+ * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+ *
+ * @static
+ * @memberOf _
+ * @since 0.1.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is an object, else `false`.
+ * @example
+ *
+ * _.isObject({});
+ * // => true
+ *
+ * _.isObject([1, 2, 3]);
+ * // => true
+ *
+ * _.isObject(_.noop);
+ * // => true
+ *
+ * _.isObject(null);
+ * // => false
+ */
+function isObject(value) {
+  var type = typeof value;
+  return !!value && (type == 'object' || type == 'function');
+}
+
+/**
+ * Checks if `value` is object-like. A value is object-like if it's not `null`
+ * and has a `typeof` result of "object".
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
+ * @example
+ *
+ * _.isObjectLike({});
+ * // => true
+ *
+ * _.isObjectLike([1, 2, 3]);
+ * // => true
+ *
+ * _.isObjectLike(_.noop);
+ * // => false
+ *
+ * _.isObjectLike(null);
+ * // => false
+ */
+function isObjectLike(value) {
+  return !!value && typeof value == 'object';
+}
+
+/**
+ * Checks if `value` is classified as a `Symbol` primitive or object.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
+ * @example
+ *
+ * _.isSymbol(Symbol.iterator);
+ * // => true
+ *
+ * _.isSymbol('abc');
+ * // => false
+ */
+function isSymbol(value) {
+  return typeof value == 'symbol' ||
+    (isObjectLike(value) && objectToString.call(value) == symbolTag);
+}
+
+/**
+ * Converts `value` to a string. An empty string is returned for `null`
+ * and `undefined` values. The sign of `-0` is preserved.
+ *
+ * @static
+ * @memberOf _
+ * @since 4.0.0
+ * @category Lang
+ * @param {*} value The value to process.
+ * @returns {string} Returns the string.
+ * @example
+ *
+ * _.toString(null);
+ * // => ''
+ *
+ * _.toString(-0);
+ * // => '-0'
+ *
+ * _.toString([1, 2, 3]);
+ * // => '1,2,3'
+ */
+function toString(value) {
+  return value == null ? '' : baseToString(value);
+}
+
+/**
+ * This method is like `_.get` except that if the resolved value is a
+ * function it's invoked with the `this` binding of its parent object and
+ * its result is returned.
+ *
+ * @static
+ * @since 0.1.0
+ * @memberOf _
+ * @category Object
+ * @param {Object} object The object to query.
+ * @param {Array|string} path The path of the property to resolve.
+ * @param {*} [defaultValue] The value returned for `undefined` resolved values.
+ * @returns {*} Returns the resolved value.
+ * @example
+ *
+ * var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] };
+ *
+ * _.result(object, 'a[0].b.c1');
+ * // => 3
+ *
+ * _.result(object, 'a[0].b.c2');
+ * // => 4
+ *
+ * _.result(object, 'a[0].b.c3', 'default');
+ * // => 'default'
+ *
+ * _.result(object, 'a[0].b.c3', _.constant('default'));
+ * // => 'default'
+ */
+function result(object, path, defaultValue) {
+  path = isKey(path, object) ? [path] : castPath(path);
+
+  var index = -1,
+      length = path.length;
+
+  // Ensure the loop is entered when path is empty.
+  if (!length) {
+    object = undefined;
+    length = 1;
+  }
+  while (++index < length) {
+    var value = object == null ? undefined : object[toKey(path[index])];
+    if (value === undefined) {
+      index = length;
+      value = defaultValue;
+    }
+    object = isFunction(value) ? value.call(object) : value;
+  }
+  return object;
+}
+
+module.exports = result;
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}],3:[function(require,module,exports){
+module.exports={"version":"1.4.0"}
+},{}],4:[function(require,module,exports){
+if (typeof Uint8Array !== "undefined") {
+  var crossfilter_array8 = function(n) { return new Uint8Array(n); };
+  var crossfilter_array16 = function(n) { return new Uint16Array(n); };
+  var crossfilter_array32 = function(n) { return new Uint32Array(n); };
+
+  var crossfilter_arrayLengthen = function(array, length) {
+    if (array.length >= length) return array;
+    var copy = new array.constructor(length);
+    copy.set(array);
+    return copy;
+  };
+
+  var crossfilter_arrayWiden = function(array, width) {
+    var copy;
+    switch (width) {
+      case 16: copy = crossfilter_array16(array.length); break;
+      case 32: copy = crossfilter_array32(array.length); break;
+      default: throw new Error("invalid array width!");
+    }
+    copy.set(array);
+    return copy;
+  };
+}
+
+function crossfilter_arrayUntyped(n) {
+  var array = new Array(n), i = -1;
+  while (++i < n) array[i] = 0;
+  return array;
+}
+
+function crossfilter_arrayLengthenUntyped(array, length) {
+  var n = array.length;
+  while (n < length) array[n++] = 0;
+  return array;
+}
+
+function crossfilter_arrayWidenUntyped(array, width) {
+  if (width > 32) throw new Error("invalid array width!");
+  return array;
+}
+
+// An arbitrarily-wide array of bitmasks
+function crossfilter_bitarray(n) {
+  this.length = n;
+  this.subarrays = 1;
+  this.width = 8;
+  this.masks = {
+    0: 0
+  }
+
+  this[0] = crossfilter_array8(n);
+}
+
+crossfilter_bitarray.prototype.lengthen = function(n) {
+  var i, len;
+  for (i = 0, len = this.subarrays; i < len; ++i) {
+    this[i] = crossfilter_arrayLengthen(this[i], n);
+  }
+  this.length = n;
+};
+
+// Reserve a new bit index in the array, returns {offset, one}
+crossfilter_bitarray.prototype.add = function() {
+  var m, w, one, i, len;
+
+  for (i = 0, len = this.subarrays; i < len; ++i) {
+    m = this.masks[i];
+    w = this.width - (32 * i);
+    one = ~m & -~m;
+
+    if (w >= 32 && !one) {
+      continue;
+    }
+
+    if (w < 32 && (one & (1 << w))) {
+      // widen this subarray
+      this[i] = crossfilter_arrayWiden(this[i], w <<= 1);
+      this.width = 32 * i + w;
+    }
+
+    this.masks[i] |= one;
+
+    return {
+      offset: i,
+      one: one
+    };
+  }
+
+  // add a new subarray
+  this[this.subarrays] = crossfilter_array8(this.length);
+  this.masks[this.subarrays] = 1;
+  this.width += 8;
+  return {
+    offset: this.subarrays++,
+    one: 1
+  };
+};
+
+// Copy record from index src to index dest
+crossfilter_bitarray.prototype.copy = function(dest, src) {
+  var i, len;
+  for (i = 0, len = this.subarrays; i < len; ++i) {
+    this[i][dest] = this[i][src];
+  }
+};
+
+// Truncate the array to the given length
+crossfilter_bitarray.prototype.truncate = function(n) {
+  var i, len;
+  for (i = 0, len = this.subarrays; i < len; ++i) {
+    for (var j = this.length - 1; j >= n; j--) {
+      this[i][j] = 0;
+    }
+    this[i].length = n;
+  }
+  this.length = n;
+};
+
+// Checks that all bits for the given index are 0
+crossfilter_bitarray.prototype.zero = function(n) {
+  var i, len;
+  for (i = 0, len = this.subarrays; i < len; ++i) {
+    if (this[i][n]) {
+      return false;
+    }
+  }
+  return true;
+};
+
+// Checks that all bits for the given index are 0 except for possibly one
+crossfilter_bitarray.prototype.zeroExcept = function(n, offset, zero) {
+  var i, len;
+  for (i = 0, len = this.subarrays; i < len; ++i) {
+    if (i === offset ? this[i][n] & zero : this[i][n]) {
+      return false;
+    }
+  }
+  return true;
+};
+
+// Checks that all bits for the given indez are 0 except for the specified mask.
+// The mask should be an array of the same size as the filter subarrays width.
+crossfilter_bitarray.prototype.zeroExceptMask = function(n, mask) {
+  var i, len;
+  for (i = 0, len = this.subarrays; i < len; ++i) {
+    if (this[i][n] & mask[i]) {
+      return false;
+    }
+  }
+  return true;
+}
+
+// Checks that only the specified bit is set for the given index
+crossfilter_bitarray.prototype.only = function(n, offset, one) {
+  var i, len;
+  for (i = 0, len = this.subarrays; i < len; ++i) {
+    if (this[i][n] != (i === offset ? one : 0)) {
+      return false;
+    }
+  }
+  return true;
+};
+
+// Checks that only the specified bit is set for the given index except for possibly one other
+crossfilter_bitarray.prototype.onlyExcept = function(n, offset, zero, onlyOffset, onlyOne) {
+  var mask;
+  var i, len;
+  for (i = 0, len = this.subarrays; i < len; ++i) {
+    mask = this[i][n];
+    if (i === offset)
+      mask &= zero;
+    if (mask != (i === onlyOffset ? onlyOne : 0)) {
+      return false;
+    }
+  }
+  return true;
+};
+
+module.exports = {
+  array8: crossfilter_arrayUntyped,
+  array16: crossfilter_arrayUntyped,
+  array32: crossfilter_arrayUntyped,
+  arrayLengthen: crossfilter_arrayLengthenUntyped,
+  arrayWiden: crossfilter_arrayWidenUntyped,
+  bitarray: crossfilter_bitarray
+};
+
+},{}],5:[function(require,module,exports){
+'use strict';
+
+var crossfilter_identity = require('./identity');
+
+function bisect_by(f) {
+
+  // Locate the insertion point for x in a to maintain sorted order. The
+  // arguments lo and hi may be used to specify a subset of the array which
+  // should be considered; by default the entire array is used. If x is already
+  // present in a, the insertion point will be before (to the left of) any
+  // existing entries. The return value is suitable for use as the first
+  // argument to `array.splice` assuming that a is already sorted.
+  //
+  // The returned insertion point i partitions the array a into two halves so
+  // that all v < x for v in a[lo:i] for the left side and all v >= x for v in
+  // a[i:hi] for the right side.
+  function bisectLeft(a, x, lo, hi) {
+    while (lo < hi) {
+      var mid = lo + hi >>> 1;
+      if (f(a[mid]) < x) lo = mid + 1;
+      else hi = mid;
+    }
+    return lo;
+  }
+
+  // Similar to bisectLeft, but returns an insertion point which comes after (to
+  // the right of) any existing entries of x in a.
+  //
+  // The returned insertion point i partitions the array into two halves so that
+  // all v <= x for v in a[lo:i] for the left side and all v > x for v in
+  // a[i:hi] for the right side.
+  function bisectRight(a, x, lo, hi) {
+    while (lo < hi) {
+      var mid = lo + hi >>> 1;
+      if (x < f(a[mid])) hi = mid;
+      else lo = mid + 1;
+    }
+    return lo;
+  }
+
+  bisectRight.right = bisectRight;
+  bisectRight.left = bisectLeft;
+  return bisectRight;
+}
+
+module.exports = bisect_by(crossfilter_identity);
+module.exports.by = bisect_by; // assign the raw function to the export as well
+
+},{"./identity":10}],6:[function(require,module,exports){
+'use strict';
+
+var xfilterArray = require('./array');
+var xfilterFilter = require('./filter');
+var crossfilter_identity = require('./identity');
+var crossfilter_null = require('./null');
+var crossfilter_zero = require('./zero');
+var xfilterHeapselect = require('./heapselect');
+var xfilterHeap = require('./heap');
+var bisect = require('./bisect');
+var insertionsort = require('./insertionsort');
+var permute = require('./permute');
+var quicksort = require('./quicksort');
+var xfilterReduce = require('./reduce');
+var packageJson = require('./../package.json'); // require own package.json for the version field
+var result = require('lodash.result');
+// expose API exports
+exports.crossfilter = crossfilter;
+exports.crossfilter.heap = xfilterHeap;
+exports.crossfilter.heapselect = xfilterHeapselect;
+exports.crossfilter.bisect = bisect;
+exports.crossfilter.insertionsort = insertionsort;
+exports.crossfilter.permute = permute;
+exports.crossfilter.quicksort = quicksort;
+exports.crossfilter.version = packageJson.version; // please note use of "package-json-versionify" transform
+
+function crossfilter() {
+  var crossfilter = {
+    add: add,
+    remove: removeData,
+    dimension: dimension,
+    groupAll: groupAll,
+    size: size,
+    all: all,
+    allFiltered: allFiltered,
+    onChange: onChange,
+    isElementFiltered: isElementFiltered
+  };
+
+  var data = [], // the records
+      n = 0, // the number of records; data.length
+      filters, // 1 is filtered out
+      filterListeners = [], // when the filters change
+      dataListeners = [], // when data is added
+      removeDataListeners = [], // when data is removed
+      callbacks = [];
+
+  filters = new xfilterArray.bitarray(0);
+
+  // Adds the specified new records to this crossfilter.
+  function add(newData) {
+    var n0 = n,
+        n1 = newData.length;
+
+    // If there's actually new data to add…
+    // Merge the new data into the existing data.
+    // Lengthen the filter bitset to handle the new records.
+    // Notify listeners (dimensions and groups) that new data is available.
+    if (n1) {
+      data = data.concat(newData);
+      filters.lengthen(n += n1);
+      dataListeners.forEach(function(l) { l(newData, n0, n1); });
+      triggerOnChange('dataAdded');
+    }
+
+    return crossfilter;
+  }
+
+  // Removes all records that match the current filters.
+  function removeData() {
+    var newIndex = crossfilter_index(n, n),
+        removed = [];
+
+    for (var index1 = 0, index2 = 0; index1 < n; ++index1) {
+      if (!filters.zero(index1)) newIndex[index1] = index2++;
+      else removed.push(index1);
+    }
+
+    // Remove all matching records from groups.
+    filterListeners.forEach(function(l) { l(-1, -1, [], removed, true); });
+
+    // Update indexes.
+    removeDataListeners.forEach(function(l) { l(newIndex); });
+
+    // Remove old filters and data by overwriting.
+    for (var index3 = 0, index4 = 0; index3 < n; ++index3) {
+      if (!filters.zero(index3)) {
+        if (index3 !== index4) filters.copy(index4, index3), data[index4] = data[index3];
+        ++index4;
+      }
+    }
+
+    data.length = n = index4;
+    filters.truncate(index4);
+    triggerOnChange('dataRemoved');
+  }
+
+  // Return true if the data element at index i is filtered IN.
+  // Optionally, ignore the filters of any dimensions in the ignore_dimensions list.
+  function isElementFiltered(i, ignore_dimensions) {
+    var n,
+        d,
+        id,
+        len,
+        mask = Array(filters.subarrays);
+    for (n = 0; n < filters.subarrays; n++) { mask[n] = ~0; }
+    if (ignore_dimensions) {
+      for (d = 0, len = ignore_dimensions.length; d < len; d++) {
+        // The top bits of the ID are the subarray offset and the lower bits are the bit
+        // offset of the "one" mask.
+        id = ignore_dimensions[d].id();
+        mask[id >> 7] &= ~(0x1 << (id & 0x3f));
+      }
+    }
+    return filters.zeroExceptMask(i,mask);
+  }
+
+  // Adds a new dimension with the specified value accessor function.
+  function dimension(value, iterable) {
+
+    if (typeof value === 'string') {
+      var accessorPath = value;
+      value = function(d) { return result(d, accessorPath); };
+    }
+
+    var dimension = {
+      filter: filter,
+      filterExact: filterExact,
+      filterRange: filterRange,
+      filterFunction: filterFunction,
+      filterAll: filterAll,
+      top: top,
+      bottom: bottom,
+      group: group,
+      groupAll: groupAll,
+      dispose: dispose,
+      remove: dispose, // for backwards-compatibility
+      accessor: value,
+      id: function() { return id; }
+    };
+
+    var one, // lowest unset bit as mask, e.g., 00001000
+        zero, // inverted one, e.g., 11110111
+        offset, // offset into the filters arrays
+        id, // unique ID for this dimension (reused when dimensions are disposed)
+        values, // sorted, cached array
+        index, // value rank ↦ object id
+        newValues, // temporary array storing newly-added values
+        newIndex, // temporary array storing newly-added index
+        iterablesIndexCount,
+        newIterablesIndexCount,
+        iterablesIndexFilterStatus,
+        newIterablesIndexFilterStatus,
+        iterablesEmptyRows,
+        sort = quicksort.by(function(i) { return newValues[i]; }),
+        refilter = xfilterFilter.filterAll, // for recomputing filter
+        refilterFunction, // the custom filter function in use
+        indexListeners = [], // when data is added
+        dimensionGroups = [],
+        lo0 = 0,
+        hi0 = 0,
+        t = 0,
+        k;
+
+    // Updating a dimension is a two-stage process. First, we must update the
+    // associated filters for the newly-added records. Once all dimensions have
+    // updated their filters, the groups are notified to update.
+    dataListeners.unshift(preAdd);
+    dataListeners.push(postAdd);
+
+    removeDataListeners.push(removeData);
+
+    // Add a new dimension in the filter bitmap and store the offset and bitmask.
+    var tmp = filters.add();
+    offset = tmp.offset;
+    one = tmp.one;
+    zero = ~one;
+
+    // Create a unique ID for the dimension
+    // IDs will be re-used if dimensions are disposed.
+    // For internal use the ID is the subarray offset shifted left 7 bits or'd with the
+    // bit offset of the set bit in the dimension's "one" mask.
+    id = (offset << 7) | (Math.log(one) / Math.log(2));
+
+    preAdd(data, 0, n);
+    postAdd(data, 0, n);
+
+    // Incorporates the specified new records into this dimension.
+    // This function is responsible for updating filters, values, and index.
+    function preAdd(newData, n0, n1) {
+
+      if (iterable){
+        // Count all the values
+        t = 0;
+        j = 0;
+        k = [];
+
+        for (var i0 = 0; i0 < newData.length; i0++) {
+          for(j = 0, k = value(newData[i0]); j < k.length; j++) {
+            t++;
+          }
+        }
+
+        newValues = [];
+        newIterablesIndexCount = crossfilter_range(newData.length);
+        newIterablesIndexFilterStatus = crossfilter_index(t,1);
+        iterablesEmptyRows = [];
+        var unsortedIndex = crossfilter_range(t);
+
+        for (var l = 0, index1 = 0; index1 < newData.length; index1++) {
+          k = value(newData[index1])
+          //
+          if(!k.length){
+            newIterablesIndexCount[index1] = 0;
+            iterablesEmptyRows.push(index1);
+            continue;
+          }
+          newIterablesIndexCount[index1] = k.length
+          for (j = 0; j < k.length; j++) {
+            newValues.push(k[j]);
+            unsortedIndex[l] = index1;
+            l++;
+          }
+        }
+
+        // Create the Sort map used to sort both the values and the valueToData indices
+        var sortMap = sort(crossfilter_range(t), 0, t);
+
+        // Use the sortMap to sort the newValues
+        newValues = permute(newValues, sortMap);
+
+
+        // Use the sortMap to sort the unsortedIndex map
+        // newIndex should be a map of sortedValue -> crossfilterData
+        newIndex = permute(unsortedIndex, sortMap)
+
+      } else{
+        // Permute new values into natural order using a standard sorted index.
+        newValues = newData.map(value);
+        newIndex = sort(crossfilter_range(n1), 0, n1);
+        newValues = permute(newValues, newIndex);
+      }
+
+      if(iterable) {
+        n1 = t;
+      }
+
+      // Bisect newValues to determine which new records are selected.
+      var bounds = refilter(newValues), lo1 = bounds[0], hi1 = bounds[1];
+      if (refilterFunction) {
+        for (var index2 = 0; index2 < n1; ++index2) {
+          if (!refilterFunction(newValues[index2], index2)) {
+            filters[offset][newIndex[index2] + n0] |= one;
+            if(iterable) newIterablesIndexFilterStatus[index2] = 1;
+          }
+        }
+      } else {
+        for (var index3 = 0; index3 < lo1; ++index3) {
+          filters[offset][newIndex[index3] + n0] |= one;
+          if(iterable) newIterablesIndexFilterStatus[index3] = 1;
+        }
+        for (var index4 = hi1; index4 < n1; ++index4) {
+          filters[offset][newIndex[index4] + n0] |= one;
+          if(iterable) newIterablesIndexFilterStatus[index4] = 1;
+        }
+      }
+
+      // If this dimension previously had no data, then we don't need to do the
+      // more expensive merge operation; use the new values and index as-is.
+      if (!n0) {
+        values = newValues;
+        index = newIndex;
+        iterablesIndexCount = newIterablesIndexCount;
+        iterablesIndexFilterStatus = newIterablesIndexFilterStatus;
+        lo0 = lo1;
+        hi0 = hi1;
+        return;
+      }
+
+
+
+      var oldValues = values,
+        oldIndex = index,
+        oldIterablesIndexFilterStatus = iterablesIndexFilterStatus,
+        old_n0,
+        i1 = 0;
+
+      i0 = 0;
+
+      if(iterable){
+        old_n0 = n0
+        n0 = oldValues.length;
+        n1 = t
+      }
+
+      // Otherwise, create new arrays into which to merge new and old.
+      values = iterable ? new Array(n0 + n1) : new Array(n);
+      index = iterable ? new Array(n0 + n1) : crossfilter_index(n, n);
+      if(iterable) iterablesIndexFilterStatus = crossfilter_index(n0 + n1, 1);
+
+      // Concatenate the newIterablesIndexCount onto the old one.
+      if(iterable) {
+        var oldiiclength = iterablesIndexCount.length;
+        iterablesIndexCount = xfilterArray.arrayLengthen(iterablesIndexCount, n);
+        for(var j=0; j+oldiiclength < n; j++) {
+          iterablesIndexCount[j+oldiiclength] = newIterablesIndexCount[j];
+        }
+      }
+
+      // Merge the old and new sorted values, and old and new index.
+      var index5 = 0;
+      for (; i0 < n0 && i1 < n1; ++index5) {
+        if (oldValues[i0] < newValues[i1]) {
+          values[index5] = oldValues[i0];
+          if(iterable) iterablesIndexFilterStatus[index5] = oldIterablesIndexFilterStatus[i0];
+          index[index5] = oldIndex[i0++];
+        } else {
+          values[index5] = newValues[i1];
+          if(iterable) iterablesIndexFilterStatus[index5] = oldIterablesIndexFilterStatus[i1];
+          index[index5] = newIndex[i1++] + (iterable ? old_n0 : n0);
+        }
+      }
+
+      // Add any remaining old values.
+      for (; i0 < n0; ++i0, ++index5) {
+        values[index5] = oldValues[i0];
+        if(iterable) iterablesIndexFilterStatus[index5] = oldIterablesIndexFilterStatus[i0];
+        index[index5] = oldIndex[i0];
+      }
+
+      // Add any remaining new values.
+      for (; i1 < n1; ++i1, ++index5) {
+        values[index5] = newValues[i1];
+        if(iterable) iterablesIndexFilterStatus[index5] = oldIterablesIndexFilterStatus[i1];
+        index[index5] = newIndex[i1] + (iterable ? old_n0 : n0);
+      }
+
+      // Bisect again to recompute lo0 and hi0.
+      bounds = refilter(values), lo0 = bounds[0], hi0 = bounds[1];
+    }
+
+    // When all filters have updated, notify index listeners of the new values.
+    function postAdd(newData, n0, n1) {
+      indexListeners.forEach(function(l) { l(newValues, newIndex, n0, n1); });
+      newValues = newIndex = null;
+    }
+
+    function removeData(reIndex) {
+      for (var i = 0, j = 0, k; i < n; ++i) {
+        if (!filters.zero(k = index[i])) {
+          if (i !== j) values[j] = values[i];
+          index[j] = reIndex[k];
+          ++j;
+        }
+      }
+      values.length = j;
+      while (j < n) index[j++] = 0;
+
+      // Bisect again to recompute lo0 and hi0.
+      var bounds = refilter(values);
+      lo0 = bounds[0], hi0 = bounds[1];
+    }
+
+    // Updates the selected values based on the specified bounds [lo, hi].
+    // This implementation is used by all the public filter methods.
+    function filterIndexBounds(bounds) {
+
+      var lo1 = bounds[0],
+          hi1 = bounds[1];
+
+      if (refilterFunction) {
+        refilterFunction = null;
+        filterIndexFunction(function(d, i) { return lo1 <= i && i < hi1; }, bounds[0] === 0 && bounds[1] === index.length);
+        lo0 = lo1;
+        hi0 = hi1;
+        return dimension;
+      }
+
+      var i,
+          j,
+          k,
+          added = [],
+          removed = [],
+          valueIndexAdded = [],
+          valueIndexRemoved = [];
+
+
+      // Fast incremental update based on previous lo index.
+      if (lo1 < lo0) {
+        for (i = lo1, j = Math.min(lo0, hi1); i < j; ++i) {
+          added.push(index[i]);
+          valueIndexAdded.push(i);
+        }
+      } else if (lo1 > lo0) {
+        for (i = lo0, j = Math.min(lo1, hi0); i < j; ++i) {
+          removed.push(index[i]);
+          valueIndexRemoved.push(i);
+        }
+      }
+
+      // Fast incremental update based on previous hi index.
+      if (hi1 > hi0) {
+        for (i = Math.max(lo1, hi0), j = hi1; i < j; ++i) {
+          added.push(index[i]);
+          valueIndexAdded.push(i);
+        }
+      } else if (hi1 < hi0) {
+        for (i = Math.max(lo0, hi1), j = hi0; i < j; ++i) {
+          removed.push(index[i]);
+          valueIndexRemoved.push(i);
+        }
+      }
+
+      if(!iterable) {
+        // Flip filters normally.
+
+        for(i=0; i<added.length; i++) {
+          filters[offset][added[i]] ^= one;
+        }
+
+        for(i=0; i<removed.length; i++) {
+          filters[offset][removed[i]] ^= one;
+        }
+
+      } else {
+        // For iterables, we need to figure out if the row has been completely removed vs partially included
+        // Only count a row as added if it is not already being aggregated. Only count a row
+        // as removed if the last element being aggregated is removed.
+
+        var newAdded = [];
+        var newRemoved = [];
+        for (i = 0; i < added.length; i++) {
+          iterablesIndexCount[added[i]]++
+          iterablesIndexFilterStatus[valueIndexAdded[i]] = 0;
+          if(iterablesIndexCount[added[i]] === 1) {
+            filters[offset][added[i]] ^= one;
+            newAdded.push(added[i]);
+          }
+        }
+        for (i = 0; i < removed.length; i++) {
+          iterablesIndexCount[removed[i]]--
+          iterablesIndexFilterStatus[valueIndexRemoved[i]] = 1;
+          if(iterablesIndexCount[removed[i]] === 0) {
+            filters[offset][removed[i]] ^= one;
+            newRemoved.push(removed[i]);
+          }
+        }
+
+        added = newAdded;
+        removed = newRemoved;
+
+        // Now handle empty rows.
+        if(bounds[0] === 0 && bounds[1] === index.length) {
+          for(i = 0; i < iterablesEmptyRows.length; i++) {
+            if((filters[offset][k = iterablesEmptyRows[i]] & one)) {
+              // Was not in the filter, so set the filter and add
+              filters[offset][k] ^= one;
+              added.push(k);
+            }
+          }
+        } else {
+          // filter in place - remove empty rows if necessary
+          for(i = 0; i < iterablesEmptyRows.length; i++) {
+            if(!(filters[offset][k = iterablesEmptyRows[i]] & one)) {
+              // Was in the filter, so set the filter and remove
+              filters[offset][k] ^= one;
+              removed.push(k);
+            }
+          }
+        }
+      }
+
+      lo0 = lo1;
+      hi0 = hi1;
+      filterListeners.forEach(function(l) { l(one, offset, added, removed); });
+      triggerOnChange('filtered');
+      return dimension;
+    }
+
+    // Filters this dimension using the specified range, value, or null.
+    // If the range is null, this is equivalent to filterAll.
+    // If the range is an array, this is equivalent to filterRange.
+    // Otherwise, this is equivalent to filterExact.
+    function filter(range) {
+      return range == null
+          ? filterAll() : Array.isArray(range)
+          ? filterRange(range) : typeof range === "function"
+          ? filterFunction(range)
+          : filterExact(range);
+    }
+
+    // Filters this dimension to select the exact value.
+    function filterExact(value) {
+      return filterIndexBounds((refilter = xfilterFilter.filterExact(bisect, value))(values));
+    }
+
+    // Filters this dimension to select the specified range [lo, hi].
+    // The lower bound is inclusive, and the upper bound is exclusive.
+    function filterRange(range) {
+      return filterIndexBounds((refilter = xfilterFilter.filterRange(bisect, range))(values));
+    }
+
+    // Clears any filters on this dimension.
+    function filterAll() {
+      return filterIndexBounds((refilter = xfilterFilter.filterAll)(values));
+    }
+
+    // Filters this dimension using an arbitrary function.
+    function filterFunction(f) {
+      refilterFunction = f;
+      refilter = xfilterFilter.filterAll;
+
+      filterIndexFunction(f, false);
+
+      lo0 = 0;
+      hi0 = n;
+
+      return dimension;
+    }
+
+    function filterIndexFunction(f, filterAll) {
+      var i,
+          k,
+          x,
+          added = [],
+          removed = [],
+          valueIndexAdded = [],
+          valueIndexRemoved = [],
+          indexLength = index.length;
+
+      if(!iterable) {
+        for (i = 0; i < indexLength; ++i) {
+          if (!(filters[offset][k = index[i]] & one) ^ !!(x = f(values[i], i))) {
+            if (x) added.push(k);
+            else removed.push(k);
+          }
+        }
+      }
+
+      if(iterable) {
+        for(i=0; i < indexLength; ++i) {
+          if(f(values[i], i)) {
+            added.push(index[i]);
+            valueIndexAdded.push(i);
+          } else {
+            removed.push(index[i]);
+            valueIndexRemoved.push(i);
+          }
+        }
+      }
+
+      if(!iterable) {
+        for(i=0; i<added.length; i++) {
+          if(filters[offset][added[i]] & one) filters[offset][added[i]] &= zero;
+        }
+
+        for(i=0; i<removed.length; i++) {
+          if(!(filters[offset][removed[i]] & one)) filters[offset][removed[i]] |= one;
+        }
+      } else {
+
+        var newAdded = [];
+        var newRemoved = [];
+        for (i = 0; i < added.length; i++) {
+          // First check this particular value needs to be added
+          if(iterablesIndexFilterStatus[valueIndexAdded[i]] === 1) {
+            iterablesIndexCount[added[i]]++
+            iterablesIndexFilterStatus[valueIndexAdded[i]] = 0;
+            if(iterablesIndexCount[added[i]] === 1) {
+              filters[offset][added[i]] ^= one;
+              newAdded.push(added[i]);
+            }
+          }
+        }
+        for (i = 0; i < removed.length; i++) {
+          // First check this particular value needs to be removed
+          if(iterablesIndexFilterStatus[valueIndexRemoved[i]] === 0) {
+            iterablesIndexCount[removed[i]]--
+            iterablesIndexFilterStatus[valueIndexRemoved[i]] = 1;
+            if(iterablesIndexCount[removed[i]] === 0) {
+              filters[offset][removed[i]] ^= one;
+              newRemoved.push(removed[i]);
+            }
+          }
+        }
+
+        added = newAdded;
+        removed = newRemoved;
+
+        // Now handle empty rows.
+        if(filterAll) {
+          for(i = 0; i < iterablesEmptyRows.length; i++) {
+            if((filters[offset][k = iterablesEmptyRows[i]] & one)) {
+              // Was not in the filter, so set the filter and add
+              filters[offset][k] ^= one;
+              added.push(k);
+            }
+          }
+        } else {
+          // filter in place - remove empty rows if necessary
+          for(i = 0; i < iterablesEmptyRows.length; i++) {
+            if(!(filters[offset][k = iterablesEmptyRows[i]] & one)) {
+              // Was in the filter, so set the filter and remove
+              filters[offset][k] ^= one;
+              removed.push(k);
+            }
+          }
+        }
+      }
+
+      filterListeners.forEach(function(l) { l(one, offset, added, removed); });
+      triggerOnChange('filtered');
+    }
+
+    // Returns the top K selected records based on this dimension's order.
+    // Note: observes this dimension's filter, unlike group and groupAll.
+    function top(k, top_offset) {
+      var array = [],
+          i = hi0,
+          j,
+          toSkip = 0;
+
+      if(top_offset && top_offset > 0) toSkip = top_offset;
+
+      while (--i >= lo0 && k > 0) {
+        if (filters.zero(j = index[i])) {
+          if(toSkip > 0) {
+            //skip matching row
+            --toSkip;
+          } else {
+            array.push(data[j]);
+            --k;
+          }
+        }
+      }
+
+      if(iterable){
+        for(i = 0; i < iterablesEmptyRows.length && k > 0; i++) {
+          // Add row with empty iterable column at the end
+          if(filters.zero(j = iterablesEmptyRows[i])) {
+            if(toSkip > 0) {
+              //skip matching row
+              --toSkip;
+            } else {
+              array.push(data[j]);
+              --k;
+            }
+          }
+        }
+      }
+
+      return array;
+    }
+
+    // Returns the bottom K selected records based on this dimension's order.
+    // Note: observes this dimension's filter, unlike group and groupAll.
+    function bottom(k, bottom_offset) {
+      var array = [],
+          i,
+          j,
+          toSkip = 0;
+
+      if(bottom_offset && bottom_offset > 0) toSkip = bottom_offset;
+
+      if(iterable) {
+        // Add row with empty iterable column at the top
+        for(i = 0; i < iterablesEmptyRows.length && k > 0; i++) {
+          if(filters.zero(j = iterablesEmptyRows[i])) {
+            if(toSkip > 0) {
+              //skip matching row
+              --toSkip;
+            } else {
+              array.push(data[j]);
+              --k;
+            }
+          }
+        }
+      }
+
+      i = lo0;
+
+      while (i < hi0 && k > 0) {
+        if (filters.zero(j = index[i])) {
+          if(toSkip > 0) {
+            //skip matching row
+            --toSkip;
+          } else {
+            array.push(data[j]);
+            --k;
+          }
+        }
+        i++;
+      }
+
+      return array;
+    }
+
+    // Adds a new group to this dimension, using the specified key function.
+    function group(key) {
+      var group = {
+        top: top,
+        all: all,
+        reduce: reduce,
+        reduceCount: reduceCount,
+        reduceSum: reduceSum,
+        order: order,
+        orderNatural: orderNatural,
+        size: size,
+        dispose: dispose,
+        remove: dispose // for backwards-compatibility
+      };
+
+      // Ensure that this group will be removed when the dimension is removed.
+      dimensionGroups.push(group);
+
+      var groups, // array of {key, value}
+          groupIndex, // object id ↦ group id
+          groupWidth = 8,
+          groupCapacity = crossfilter_capacity(groupWidth),
+          k = 0, // cardinality
+          select,
+          heap,
+          reduceAdd,
+          reduceRemove,
+          reduceInitial,
+          update = crossfilter_null,
+          reset = crossfilter_null,
+          resetNeeded = true,
+          groupAll = key === crossfilter_null,
+          n0old;
+
+      if (arguments.length < 1) key = crossfilter_identity;
+
+      // The group listens to the crossfilter for when any dimension changes, so
+      // that it can update the associated reduce values. It must also listen to
+      // the parent dimension for when data is added, and compute new keys.
+      filterListeners.push(update);
+      indexListeners.push(add);
+      removeDataListeners.push(removeData);
+
+      // Incorporate any existing data into the grouping.
+      add(values, index, 0, n);
+
+      // Incorporates the specified new values into this group.
+      // This function is responsible for updating groups and groupIndex.
+      function add(newValues, newIndex, n0, n1) {
+
+        if(iterable) {
+          n0old = n0
+          n0 = values.length - newValues.length
+          n1 = newValues.length;
+        }
+
+        var oldGroups = groups,
+            reIndex = iterable ? [] : crossfilter_index(k, groupCapacity),
+            add = reduceAdd,
+            remove = reduceRemove,
+            initial = reduceInitial,
+            k0 = k, // old cardinality
+            i0 = 0, // index of old group
+            i1 = 0, // index of new record
+            j, // object id
+            g0, // old group
+            x0, // old key
+            x1, // new key
+            g, // group to add
+            x; // key of group to add
+
+        // If a reset is needed, we don't need to update the reduce values.
+        if (resetNeeded) add = initial = crossfilter_null;
+        if (resetNeeded) remove = initial = crossfilter_null;
+
+        // Reset the new groups (k is a lower bound).
+        // Also, make sure that groupIndex exists and is long enough.
+        groups = new Array(k), k = 0;
+        if(iterable){
+          groupIndex = k0 > 1 ? groupIndex : [];
+        }
+        else{
+          groupIndex = k0 > 1 ? xfilterArray.arrayLengthen(groupIndex, n) : crossfilter_index(n, groupCapacity);
+        }
+
+
+        // Get the first old key (x0 of g0), if it exists.
+        if (k0) x0 = (g0 = oldGroups[0]).key;
+
+        // Find the first new key (x1), skipping NaN keys.
+        while (i1 < n1 && !((x1 = key(newValues[i1])) >= x1)) ++i1;
+
+        // While new keys remain…
+        while (i1 < n1) {
+
+          // Determine the lesser of the two current keys; new and old.
+          // If there are no old keys remaining, then always add the new key.
+          if (g0 && x0 <= x1) {
+            g = g0, x = x0;
+
+            // Record the new index of the old group.
+            reIndex[i0] = k;
+
+            // Retrieve the next old key.
+            g0 = oldGroups[++i0];
+            if (g0) x0 = g0.key;
+          } else {
+            g = {key: x1, value: initial()}, x = x1;
+          }
+
+          // Add the lesser group.
+          groups[k] = g;
+
+          // Add any selected records belonging to the added group, while
+          // advancing the new key and populating the associated group index.
+
+          while (x1 <= x) {
+            j = newIndex[i1] + (iterable ? n0old : n0)
+
+
+            if(iterable){
+              if(groupIndex[j]){
+                groupIndex[j].push(k)
+              }
+              else{
+                groupIndex[j] = [k]
+              }
+            }
+            else{
+              groupIndex[j] = k;
+            }
+
+            // Always add new values to groups. Only remove when not in filter.
+            // This gives groups full information on data life-cycle.
+            g.value = add(g.value, data[j], true);
+            if (!filters.zeroExcept(j, offset, zero)) g.value = remove(g.value, data[j], false);
+            if (++i1 >= n1) break;
+            x1 = key(newValues[i1]);
+          }
+
+          groupIncrement();
+        }
+
+        // Add any remaining old groups that were greater th1an all new keys.
+        // No incremental reduce is needed; these groups have no new records.
+        // Also record the new index of the old group.
+        while (i0 < k0) {
+          groups[reIndex[i0] = k] = oldGroups[i0++];
+          groupIncrement();
+        }
+
+
+        // Fill in gaps with empty arrays where there may have been rows with empty iterables
+        if(iterable){
+          for (var index1 = 0; index1 < n; index1++) {
+            if(!groupIndex[index1]){
+              groupIndex[index1] = [];
+            }
+          }
+        }
+
+        // If we added any new groups before any old groups,
+        // update the group index of all the old records.
+        if(k > i0){
+          if(iterable){
+            groupIndex = permute(groupIndex, reIndex, true)
+          }
+          else{
+            for (i0 = 0; i0 < n0; ++i0) {
+              groupIndex[i0] = reIndex[groupIndex[i0]];
+            }
+          }
+        }
+
+        // Modify the update and reset behavior based on the cardinality.
+        // If the cardinality is less than or equal to one, then the groupIndex
+        // is not needed. If the cardinality is zero, then there are no records
+        // and therefore no groups to update or reset. Note that we also must
+        // change the registered listener to point to the new method.
+        j = filterListeners.indexOf(update);
+        if (k > 1) {
+          update = updateMany;
+          reset = resetMany;
+        } else {
+          if (!k && groupAll) {
+            k = 1;
+            groups = [{key: null, value: initial()}];
+          }
+          if (k === 1) {
+            update = updateOne;
+            reset = resetOne;
+          } else {
+            update = crossfilter_null;
+            reset = crossfilter_null;
+          }
+          groupIndex = null;
+        }
+        filterListeners[j] = update;
+
+        // Count the number of added groups,
+        // and widen the group index as needed.
+        function groupIncrement() {
+          if(iterable){
+            k++
+            return
+          }
+          if (++k === groupCapacity) {
+            reIndex = xfilterArray.arrayWiden(reIndex, groupWidth <<= 1);
+            groupIndex = xfilterArray.arrayWiden(groupIndex, groupWidth);
+            groupCapacity = crossfilter_capacity(groupWidth);
+          }
+        }
+      }
+
+      function removeData() {
+        if (k > 1) {
+          var oldK = k,
+              oldGroups = groups,
+              seenGroups = crossfilter_index(oldK, oldK);
+
+          // Filter out non-matches by copying matching group index entries to
+          // the beginning of the array.
+          for (var i = 0, j = 0; i < n; ++i) {
+            if (!filters.zero(i)) {
+              seenGroups[groupIndex[j] = groupIndex[i]] = 1;
+              ++j;
+            }
+          }
+
+          // Reassemble groups including only those groups that were referred
+          // to by matching group index entries.  Note the new group index in
+          // seenGroups.
+          groups = [], k = 0;
+          for (i = 0; i < oldK; ++i) {
+            if (seenGroups[i]) {
+              seenGroups[i] = k++;
+              groups.push(oldGroups[i]);
+            }
+          }
+
+          if (k > 1) {
+            // Reindex the group index using seenGroups to find the new index.
+            for (var index2 = 0; index2 < j; ++index2) groupIndex[index2] = seenGroups[groupIndex[index2]];
+          } else {
+            groupIndex = null;
+          }
+          filterListeners[filterListeners.indexOf(update)] = k > 1
+              ? (reset = resetMany, update = updateMany)
+              : k === 1 ? (reset = resetOne, update = updateOne)
+              : reset = update = crossfilter_null;
+        } else if (k === 1) {
+          if (groupAll) return;
+          for (var index3 = 0; index3 < n; ++index3) if (!filters.zero(index3)) return;
+          groups = [], k = 0;
+          filterListeners[filterListeners.indexOf(update)] =
+          update = reset = crossfilter_null;
+        }
+      }
+
+      // Reduces the specified selected or deselected records.
+      // This function is only used when the cardinality is greater than 1.
+      // notFilter indicates a crossfilter.add/remove operation.
+      function updateMany(filterOne, filterOffset, added, removed, notFilter) {
+
+        if ((filterOne === one && filterOffset === offset) || resetNeeded) return;
+
+        var i,
+            j,
+            k,
+            n,
+            g;
+
+        if(iterable){
+          // Add the added values.
+          for (i = 0, n = added.length; i < n; ++i) {
+            if (filters.zeroExcept(k = added[i], offset, zero)) {
+              for (j = 0; j < groupIndex[k].length; j++) {
+                g = groups[groupIndex[k][j]];
+                g.value = reduceAdd(g.value, data[k], false, j);
+              }
+            }
+          }
+
+          // Remove the removed values.
+          for (i = 0, n = removed.length; i < n; ++i) {
+            if (filters.onlyExcept(k = removed[i], offset, zero, filterOffset, filterOne)) {
+              for (j = 0; j < groupIndex[k].length; j++) {
+                g = groups[groupIndex[k][j]];
+                g.value = reduceRemove(g.value, data[k], notFilter, j);
+              }
+            }
+          }
+          return;
+        }
+
+        // Add the added values.
+        for (i = 0, n = added.length; i < n; ++i) {
+          if (filters.zeroExcept(k = added[i], offset, zero)) {
+            g = groups[groupIndex[k]];
+            g.value = reduceAdd(g.value, data[k], false);
+          }
+        }
+
+        // Remove the removed values.
+        for (i = 0, n = removed.length; i < n; ++i) {
+          if (filters.onlyExcept(k = removed[i], offset, zero, filterOffset, filterOne)) {
+            g = groups[groupIndex[k]];
+            g.value = reduceRemove(g.value, data[k], notFilter);
+          }
+        }
+      }
+
+      // Reduces the specified selected or deselected records.
+      // This function is only used when the cardinality is 1.
+      // notFilter indicates a crossfilter.add/remove operation.
+      function updateOne(filterOne, filterOffset, added, removed, notFilter) {
+        if ((filterOne === one && filterOffset === offset) || resetNeeded) return;
+
+        var i,
+            k,
+            n,
+            g = groups[0];
+
+        // Add the added values.
+        for (i = 0, n = added.length; i < n; ++i) {
+          if (filters.zeroExcept(k = added[i], offset, zero)) {
+            g.value = reduceAdd(g.value, data[k], false);
+          }
+        }
+
+        // Remove the removed values.
+        for (i = 0, n = removed.length; i < n; ++i) {
+          if (filters.onlyExcept(k = removed[i], offset, zero, filterOffset, filterOne)) {
+            g.value = reduceRemove(g.value, data[k], notFilter);
+          }
+        }
+      }
+
+      // Recomputes the group reduce values from scratch.
+      // This function is only used when the cardinality is greater than 1.
+      function resetMany() {
+        var i,
+            j,
+            g;
+
+        // Reset all group values.
+        for (i = 0; i < k; ++i) {
+          groups[i].value = reduceInitial();
+        }
+
+        // We add all records and then remove filtered records so that reducers
+        // can build an 'unfiltered' view even if there are already filters in
+        // place on other dimensions.
+        if(iterable){
+          for (i = 0; i < n; ++i) {
+            for (j = 0; j < groupIndex[i].length; j++) {
+              g = groups[groupIndex[i][j]];
+              g.value = reduceAdd(g.value, data[i], true, j);
+            }
+          }
+          for (i = 0; i < n; ++i) {
+            if (!filters.zeroExcept(i, offset, zero)) {
+              for (j = 0; j < groupIndex[i].length; j++) {
+                g = groups[groupIndex[i][j]];
+                g.value = reduceRemove(g.value, data[i], false, j);
+              }
+            }
+          }
+          return;
+        }
+
+        for (i = 0; i < n; ++i) {
+          g = groups[groupIndex[i]];
+          g.value = reduceAdd(g.value, data[i], true);
+        }
+        for (i = 0; i < n; ++i) {
+          if (!filters.zeroExcept(i, offset, zero)) {
+            g = groups[groupIndex[i]];
+            g.value = reduceRemove(g.value, data[i], false);
+          }
+        }
+      }
+
+      // Recomputes the group reduce values from scratch.
+      // This function is only used when the cardinality is 1.
+      function resetOne() {
+        var i,
+            g = groups[0];
+
+        // Reset the singleton group values.
+        g.value = reduceInitial();
+
+        // We add all records and then remove filtered records so that reducers
+        // can build an 'unfiltered' view even if there are already filters in
+        // place on other dimensions.
+        for (i = 0; i < n; ++i) {
+          g.value = reduceAdd(g.value, data[i], true);
+        }
+
+        for (i = 0; i < n; ++i) {
+          if (!filters.zeroExcept(i, offset, zero)) {
+            g.value = reduceRemove(g.value, data[i], false);
+          }
+        }
+      }
+
+      // Returns the array of group values, in the dimension's natural order.
+      function all() {
+        if (resetNeeded) reset(), resetNeeded = false;
+        return groups;
+      }
+
+      // Returns a new array containing the top K group values, in reduce order.
+      function top(k) {
+        var top = select(all(), 0, groups.length, k);
+        return heap.sort(top, 0, top.length);
+      }
+
+      // Sets the reduce behavior for this group to use the specified functions.
+      // This method lazily recomputes the reduce values, waiting until needed.
+      function reduce(add, remove, initial) {
+        reduceAdd = add;
+        reduceRemove = remove;
+        reduceInitial = initial;
+        resetNeeded = true;
+        return group;
+      }
+
+      // A convenience method for reducing by count.
+      function reduceCount() {
+        return reduce(xfilterReduce.reduceIncrement, xfilterReduce.reduceDecrement, crossfilter_zero);
+      }
+
+      // A convenience method for reducing by sum(value).
+      function reduceSum(value) {
+        return reduce(xfilterReduce.reduceAdd(value), xfilterReduce.reduceSubtract(value), crossfilter_zero);
+      }
+
+      // Sets the reduce order, using the specified accessor.
+      function order(value) {
+        select = xfilterHeapselect.by(valueOf);
+        heap = xfilterHeap.by(valueOf);
+        function valueOf(d) { return value(d.value); }
+        return group;
+      }
+
+      // A convenience method for natural ordering by reduce value.
+      function orderNatural() {
+        return order(crossfilter_identity);
+      }
+
+      // Returns the cardinality of this group, irrespective of any filters.
+      function size() {
+        return k;
+      }
+
+      // Removes this group and associated event listeners.
+      function dispose() {
+        var i = filterListeners.indexOf(update);
+        if (i >= 0) filterListeners.splice(i, 1);
+        i = indexListeners.indexOf(add);
+        if (i >= 0) indexListeners.splice(i, 1);
+        i = removeDataListeners.indexOf(removeData);
+        if (i >= 0) removeDataListeners.splice(i, 1);
+        return group;
+      }
+
+      return reduceCount().orderNatural();
+    }
+
+    // A convenience function for generating a singleton group.
+    function groupAll() {
+      var g = group(crossfilter_null), all = g.all;
+      delete g.all;
+      delete g.top;
+      delete g.order;
+      delete g.orderNatural;
+      delete g.size;
+      g.value = function() { return all()[0].value; };
+      return g;
+    }
+
+    // Removes this dimension and associated groups and event listeners.
+    function dispose() {
+      dimensionGroups.forEach(function(group) { group.dispose(); });
+      var i = dataListeners.indexOf(preAdd);
+      if (i >= 0) dataListeners.splice(i, 1);
+      i = dataListeners.indexOf(postAdd);
+      if (i >= 0) dataListeners.splice(i, 1);
+      i = removeDataListeners.indexOf(removeData);
+      if (i >= 0) removeDataListeners.splice(i, 1);
+      filters.masks[offset] &= zero;
+      return filterAll();
+    }
+
+    return dimension;
+  }
+
+  // A convenience method for groupAll on a dummy dimension.
+  // This implementation can be optimized since it always has cardinality 1.
+  function groupAll() {
+    var group = {
+      reduce: reduce,
+      reduceCount: reduceCount,
+      reduceSum: reduceSum,
+      value: value,
+      dispose: dispose,
+      remove: dispose // for backwards-compatibility
+    };
+
+    var reduceValue,
+        reduceAdd,
+        reduceRemove,
+        reduceInitial,
+        resetNeeded = true;
+
+    // The group listens to the crossfilter for when any dimension changes, so
+    // that it can update the reduce value. It must also listen to the parent
+    // dimension for when data is added.
+    filterListeners.push(update);
+    dataListeners.push(add);
+
+    // For consistency; actually a no-op since resetNeeded is true.
+    add(data, 0, n);
+
+    // Incorporates the specified new values into this group.
+    function add(newData, n0) {
+      var i;
+
+      if (resetNeeded) return;
+
+      // Cycle through all the values.
+      for (i = n0; i < n; ++i) {
+
+        // Add all values all the time.
+        reduceValue = reduceAdd(reduceValue, data[i], true);
+
+        // Remove the value if filtered.
+        if (!filters.zero(i)) {
+          reduceValue = reduceRemove(reduceValue, data[i], false);
+        }
+      }
+    }
+
+    // Reduces the specified selected or deselected records.
+    function update(filterOne, filterOffset, added, removed, notFilter) {
+      var i,
+          k,
+          n;
+
+      if (resetNeeded) return;
+
+      // Add the added values.
+      for (i = 0, n = added.length; i < n; ++i) {
+        if (filters.zero(k = added[i])) {
+          reduceValue = reduceAdd(reduceValue, data[k], notFilter);
+        }
+      }
+
+      // Remove the removed values.
+      for (i = 0, n = removed.length; i < n; ++i) {
+        if (filters.only(k = removed[i], filterOffset, filterOne)) {
+          reduceValue = reduceRemove(reduceValue, data[k], notFilter);
+        }
+      }
+    }
+
+    // Recomputes the group reduce value from scratch.
+    function reset() {
+      var i;
+
+      reduceValue = reduceInitial();
+
+      // Cycle through all the values.
+      for (i = 0; i < n; ++i) {
+
+        // Add all values all the time.
+        reduceValue = reduceAdd(reduceValue, data[i], true);
+
+        // Remove the value if it is filtered.
+        if (!filters.zero(i)) {
+          reduceValue = reduceRemove(reduceValue, data[i], false);
+        }
+      }
+    }
+
+    // Sets the reduce behavior for this group to use the specified functions.
+    // This method lazily recomputes the reduce value, waiting until needed.
+    function reduce(add, remove, initial) {
+      reduceAdd = add;
+      reduceRemove = remove;
+      reduceInitial = initial;
+      resetNeeded = true;
+      return group;
+    }
+
+    // A convenience method for reducing by count.
+    function reduceCount() {
+      return reduce(xfilterReduce.reduceIncrement, xfilterReduce.reduceDecrement, crossfilter_zero);
+    }
+
+    // A convenience method for reducing by sum(value).
+    function reduceSum(value) {
+      return reduce(xfilterReduce.reduceAdd(value), xfilterReduce.reduceSubtract(value), crossfilter_zero);
+    }
+
+    // Returns the computed reduce value.
+    function value() {
+      if (resetNeeded) reset(), resetNeeded = false;
+      return reduceValue;
+    }
+
+    // Removes this group and associated event listeners.
+    function dispose() {
+      var i = filterListeners.indexOf(update);
+      if (i >= 0) filterListeners.splice(i);
+      i = dataListeners.indexOf(add);
+      if (i >= 0) dataListeners.splice(i);
+      return group;
+    }
+
+    return reduceCount();
+  }
+
+  // Returns the number of records in this crossfilter, irrespective of any filters.
+  function size() {
+    return n;
+  }
+
+  // Returns the raw row data contained in this crossfilter
+  function all(){
+    return data;
+  }
+
+  // Returns row data with all dimension filters applied
+  function allFiltered() {
+    var array = [],
+        i = 0;
+
+      for (i = 0; i < n; i++) {
+        if (filters.zero(i)) {
+          array.push(data[i]);
+        }
+      }
+
+      return array;
+  }
+
+  function onChange(cb){
+    if(typeof cb !== 'function'){
+      /* eslint no-console: 0 */
+      console.warn('onChange callback parameter must be a function!');
+      return;
+    }
+    callbacks.push(cb);
+    return function(){
+      callbacks.splice(callbacks.indexOf(cb), 1);
+    };
+  }
+
+  function triggerOnChange(eventName){
+    for (var i = 0; i < callbacks.length; i++) {
+      callbacks[i](eventName);
+    }
+  }
+
+  return arguments.length
+      ? add(arguments[0])
+      : crossfilter;
+}
+
+// Returns an array of size n, big enough to store ids up to m.
+function crossfilter_index(n, m) {
+  return (m < 0x101
+      ? xfilterArray.array8 : m < 0x10001
+      ? xfilterArray.array16
+      : xfilterArray.array32)(n);
+}
+
+// Constructs a new array of size n, with sequential values from 0 to n - 1.
+function crossfilter_range(n) {
+  var range = crossfilter_index(n, n);
+  for (var i = -1; ++i < n;) range[i] = i;
+  return range;
+}
+
+function crossfilter_capacity(w) {
+  return w === 8
+      ? 0x100 : w === 16
+      ? 0x10000
+      : 0x100000000;
+}
+
+},{"./../package.json":3,"./array":4,"./bisect":5,"./filter":7,"./heap":8,"./heapselect":9,"./identity":10,"./insertionsort":11,"./null":12,"./permute":13,"./quicksort":14,"./reduce":15,"./zero":16,"lodash.result":2}],7:[function(require,module,exports){
+'use strict';
+
+function crossfilter_filterExact(bisect, value) {
+  return function(values) {
+    var n = values.length;
+    return [bisect.left(values, value, 0, n), bisect.right(values, value, 0, n)];
+  };
+}
+
+function crossfilter_filterRange(bisect, range) {
+  var min = range[0],
+      max = range[1];
+  return function(values) {
+    var n = values.length;
+    return [bisect.left(values, min, 0, n), bisect.left(values, max, 0, n)];
+  };
+}
+
+function crossfilter_filterAll(values) {
+  return [0, values.length];
+}
+
+module.exports = {
+  filterExact: crossfilter_filterExact,
+  filterRange: crossfilter_filterRange,
+  filterAll: crossfilter_filterAll
+};
+
+},{}],8:[function(require,module,exports){
+'use strict';
+
+var crossfilter_identity = require('./identity');
+
+function heap_by(f) {
+
+  // Builds a binary heap within the specified array a[lo:hi]. The heap has the
+  // property such that the parent a[lo+i] is always less than or equal to its
+  // two children: a[lo+2*i+1] and a[lo+2*i+2].
+  function heap(a, lo, hi) {
+    var n = hi - lo,
+        i = (n >>> 1) + 1;
+    while (--i > 0) sift(a, i, n, lo);
+    return a;
+  }
+
+  // Sorts the specified array a[lo:hi] in descending order, assuming it is
+  // already a heap.
+  function sort(a, lo, hi) {
+    var n = hi - lo,
+        t;
+    while (--n > 0) t = a[lo], a[lo] = a[lo + n], a[lo + n] = t, sift(a, 1, n, lo);
+    return a;
+  }
+
+  // Sifts the element a[lo+i-1] down the heap, where the heap is the contiguous
+  // slice of array a[lo:lo+n]. This method can also be used to update the heap
+  // incrementally, without incurring the full cost of reconstructing the heap.
+  function sift(a, i, n, lo) {
+    var d = a[--lo + i],
+        x = f(d),
+        child;
+    while ((child = i << 1) <= n) {
+      if (child < n && f(a[lo + child]) > f(a[lo + child + 1])) child++;
+      if (x <= f(a[lo + child])) break;
+      a[lo + i] = a[lo + child];
+      i = child;
+    }
+    a[lo + i] = d;
+  }
+
+  heap.sort = sort;
+  return heap;
+}
+
+module.exports = heap_by(crossfilter_identity);
+module.exports.by = heap_by;
+
+},{"./identity":10}],9:[function(require,module,exports){
+'use strict';
+
+var crossfilter_identity = require('./identity');
+var xFilterHeap = require('./heap');
+
+function heapselect_by(f) {
+  var heap = xFilterHeap.by(f);
+
+  // Returns a new array containing the top k elements in the array a[lo:hi].
+  // The returned array is not sorted, but maintains the heap property. If k is
+  // greater than hi - lo, then fewer than k elements will be returned. The
+  // order of elements in a is unchanged by this operation.
+  function heapselect(a, lo, hi, k) {
+    var queue = new Array(k = Math.min(hi - lo, k)),
+        min,
+        i,
+        d;
+
+    for (i = 0; i < k; ++i) queue[i] = a[lo++];
+    heap(queue, 0, k);
+
+    if (lo < hi) {
+      min = f(queue[0]);
+      do {
+        if (f(d = a[lo]) > min) {
+          queue[0] = d;
+          min = f(heap(queue, 0, k)[0]);
+        }
+      } while (++lo < hi);
+    }
+
+    return queue;
+  }
+
+  return heapselect;
+}
+
+module.exports = heapselect_by(crossfilter_identity);
+module.exports.by = heapselect_by; // assign the raw function to the export as well
+
+},{"./heap":8,"./identity":10}],10:[function(require,module,exports){
+'use strict';
+
+function crossfilter_identity(d) {
+  return d;
+}
+
+module.exports = crossfilter_identity;
+
+},{}],11:[function(require,module,exports){
+'use strict';
+
+var crossfilter_identity = require('./identity');
+
+function insertionsort_by(f) {
+
+  function insertionsort(a, lo, hi) {
+    for (var i = lo + 1; i < hi; ++i) {
+      for (var j = i, t = a[i], x = f(t); j > lo && f(a[j - 1]) > x; --j) {
+        a[j] = a[j - 1];
+      }
+      a[j] = t;
+    }
+    return a;
+  }
+
+  return insertionsort;
+}
+
+module.exports = insertionsort_by(crossfilter_identity);
+module.exports.by = insertionsort_by;
+
+},{"./identity":10}],12:[function(require,module,exports){
+'use strict';
+
+function crossfilter_null() {
+  return null;
+}
+
+module.exports = crossfilter_null;
+
+},{}],13:[function(require,module,exports){
+'use strict';
+
+function permute(array, index, deep) {
+  for (var i = 0, n = index.length, copy = deep ? JSON.parse(JSON.stringify(array)) : new Array(n); i < n; ++i) {
+    copy[i] = array[index[i]];
+  }
+  return copy;
+}
+
+module.exports = permute;
+
+},{}],14:[function(require,module,exports){
+var crossfilter_identity = require('./identity');
+var xFilterInsertionsort = require('./insertionsort');
+
+// Algorithm designed by Vladimir Yaroslavskiy.
+// Implementation based on the Dart project; see NOTICE and AUTHORS for details.
+
+function quicksort_by(f) {
+  var insertionsort = xFilterInsertionsort.by(f);
+
+  function sort(a, lo, hi) {
+    return (hi - lo < quicksort_sizeThreshold
+        ? insertionsort
+        : quicksort)(a, lo, hi);
+  }
+
+  function quicksort(a, lo, hi) {
+    // Compute the two pivots by looking at 5 elements.
+    var sixth = (hi - lo) / 6 | 0,
+        i1 = lo + sixth,
+        i5 = hi - 1 - sixth,
+        i3 = lo + hi - 1 >> 1,  // The midpoint.
+        i2 = i3 - sixth,
+        i4 = i3 + sixth;
+
+    var e1 = a[i1], x1 = f(e1),
+        e2 = a[i2], x2 = f(e2),
+        e3 = a[i3], x3 = f(e3),
+        e4 = a[i4], x4 = f(e4),
+        e5 = a[i5], x5 = f(e5);
+
+    var t;
+
+    // Sort the selected 5 elements using a sorting network.
+    if (x1 > x2) t = e1, e1 = e2, e2 = t, t = x1, x1 = x2, x2 = t;
+    if (x4 > x5) t = e4, e4 = e5, e5 = t, t = x4, x4 = x5, x5 = t;
+    if (x1 > x3) t = e1, e1 = e3, e3 = t, t = x1, x1 = x3, x3 = t;
+    if (x2 > x3) t = e2, e2 = e3, e3 = t, t = x2, x2 = x3, x3 = t;
+    if (x1 > x4) t = e1, e1 = e4, e4 = t, t = x1, x1 = x4, x4 = t;
+    if (x3 > x4) t = e3, e3 = e4, e4 = t, t = x3, x3 = x4, x4 = t;
+    if (x2 > x5) t = e2, e2 = e5, e5 = t, t = x2, x2 = x5, x5 = t;
+    if (x2 > x3) t = e2, e2 = e3, e3 = t, t = x2, x2 = x3, x3 = t;
+    if (x4 > x5) t = e4, e4 = e5, e5 = t, t = x4, x4 = x5, x5 = t;
+
+    var pivot1 = e2, pivotValue1 = x2,
+        pivot2 = e4, pivotValue2 = x4;
+
+    // e2 and e4 have been saved in the pivot variables. They will be written
+    // back, once the partitioning is finished.
+    a[i1] = e1;
+    a[i2] = a[lo];
+    a[i3] = e3;
+    a[i4] = a[hi - 1];
+    a[i5] = e5;
+
+    var less = lo + 1,   // First element in the middle partition.
+        great = hi - 2;  // Last element in the middle partition.
+
+    // Note that for value comparison, <, <=, >= and > coerce to a primitive via
+    // Object.prototype.valueOf; == and === do not, so in order to be consistent
+    // with natural order (such as for Date objects), we must do two compares.
+    var pivotsEqual = pivotValue1 <= pivotValue2 && pivotValue1 >= pivotValue2;
+    if (pivotsEqual) {
+
+      // Degenerated case where the partitioning becomes a dutch national flag
+      // problem.
+      //
+      // [ |  < pivot  | == pivot | unpartitioned | > pivot  | ]
+      //  ^             ^          ^             ^            ^
+      // left         less         k           great         right
+      //
+      // a[left] and a[right] are undefined and are filled after the
+      // partitioning.
+      //
+      // Invariants:
+      //   1) for x in ]left, less[ : x < pivot.
+      //   2) for x in [less, k[ : x == pivot.
+      //   3) for x in ]great, right[ : x > pivot.
+      for (var k = less; k <= great; ++k) {
+        var ek = a[k], xk = f(ek);
+        if (xk < pivotValue1) {
+          if (k !== less) {
+            a[k] = a[less];
+            a[less] = ek;
+          }
+          ++less;
+        } else if (xk > pivotValue1) {
+
+          // Find the first element <= pivot in the range [k - 1, great] and
+          // put [:ek:] there. We know that such an element must exist:
+          // When k == less, then el3 (which is equal to pivot) lies in the
+          // interval. Otherwise a[k - 1] == pivot and the search stops at k-1.
+          // Note that in the latter case invariant 2 will be violated for a
+          // short amount of time. The invariant will be restored when the
+          // pivots are put into their final positions.
+          /* eslint no-constant-condition: 0 */
+          while (true) {
+            var greatValue = f(a[great]);
+            if (greatValue > pivotValue1) {
+              great--;
+              // This is the only location in the while-loop where a new
+              // iteration is started.
+              continue;
+            } else if (greatValue < pivotValue1) {
+              // Triple exchange.
+              a[k] = a[less];
+              a[less++] = a[great];
+              a[great--] = ek;
+              break;
+            } else {
+              a[k] = a[great];
+              a[great--] = ek;
+              // Note: if great < k then we will exit the outer loop and fix
+              // invariant 2 (which we just violated).
+              break;
+            }
+          }
+        }
+      }
+    } else {
+
+      // We partition the list into three parts:
+      //  1. < pivot1
+      //  2. >= pivot1 && <= pivot2
+      //  3. > pivot2
+      //
+      // During the loop we have:
+      // [ | < pivot1 | >= pivot1 && <= pivot2 | unpartitioned  | > pivot2  | ]
+      //  ^            ^                        ^              ^             ^
+      // left         less                     k              great        right
+      //
+      // a[left] and a[right] are undefined and are filled after the
+      // partitioning.
+      //
+      // Invariants:
+      //   1. for x in ]left, less[ : x < pivot1
+      //   2. for x in [less, k[ : pivot1 <= x && x <= pivot2
+      //   3. for x in ]great, right[ : x > pivot2
+      (function () { // isolate scope
+      for (var k = less; k <= great; k++) {
+        var ek = a[k], xk = f(ek);
+        if (xk < pivotValue1) {
+          if (k !== less) {
+            a[k] = a[less];
+            a[less] = ek;
+          }
+          ++less;
+        } else {
+          if (xk > pivotValue2) {
+            while (true) {
+              var greatValue = f(a[great]);
+              if (greatValue > pivotValue2) {
+                great--;
+                if (great < k) break;
+                // This is the only location inside the loop where a new
+                // iteration is started.
+                continue;
+              } else {
+                // a[great] <= pivot2.
+                if (greatValue < pivotValue1) {
+                  // Triple exchange.
+                  a[k] = a[less];
+                  a[less++] = a[great];
+                  a[great--] = ek;
+                } else {
+                  // a[great] >= pivot1.
+                  a[k] = a[great];
+                  a[great--] = ek;
+                }
+                break;
+              }
+            }
+          }
+        }
+      }
+      })(); // isolate scope
+    }
+
+    // Move pivots into their final positions.
+    // We shrunk the list from both sides (a[left] and a[right] have
+    // meaningless values in them) and now we move elements from the first
+    // and third partition into these locations so that we can store the
+    // pivots.
+    a[lo] = a[less - 1];
+    a[less - 1] = pivot1;
+    a[hi - 1] = a[great + 1];
+    a[great + 1] = pivot2;
+
+    // The list is now partitioned into three partitions:
+    // [ < pivot1   | >= pivot1 && <= pivot2   |  > pivot2   ]
+    //  ^            ^                        ^             ^
+    // left         less                     great        right
+
+    // Recursive descent. (Don't include the pivot values.)
+    sort(a, lo, less - 1);
+    sort(a, great + 2, hi);
+
+    if (pivotsEqual) {
+      // All elements in the second partition are equal to the pivot. No
+      // need to sort them.
+      return a;
+    }
+
+    // In theory it should be enough to call _doSort recursively on the second
+    // partition.
+    // The Android source however removes the pivot elements from the recursive
+    // call if the second partition is too large (more than 2/3 of the list).
+    if (less < i1 && great > i5) {
+
+      (function () { // isolate scope
+      var lessValue, greatValue;
+      while ((lessValue = f(a[less])) <= pivotValue1 && lessValue >= pivotValue1) ++less;
+      while ((greatValue = f(a[great])) <= pivotValue2 && greatValue >= pivotValue2) --great;
+
+      // Copy paste of the previous 3-way partitioning with adaptions.
+      //
+      // We partition the list into three parts:
+      //  1. == pivot1
+      //  2. > pivot1 && < pivot2
+      //  3. == pivot2
+      //
+      // During the loop we have:
+      // [ == pivot1 | > pivot1 && < pivot2 | unpartitioned  | == pivot2 ]
+      //              ^                      ^              ^
+      //            less                     k              great
+      //
+      // Invariants:
+      //   1. for x in [ *, less[ : x == pivot1
+      //   2. for x in [less, k[ : pivot1 < x && x < pivot2
+      //   3. for x in ]great, * ] : x == pivot2
+      for (var k = less; k <= great; k++) {
+        var ek = a[k], xk = f(ek);
+        if (xk <= pivotValue1 && xk >= pivotValue1) {
+          if (k !== less) {
+            a[k] = a[less];
+            a[less] = ek;
+          }
+          less++;
+        } else {
+          if (xk <= pivotValue2 && xk >= pivotValue2) {
+            /* eslint no-constant-condition: 0 */
+            while (true) {
+              greatValue = f(a[great]);
+              if (greatValue <= pivotValue2 && greatValue >= pivotValue2) {
+                great--;
+                if (great < k) break;
+                // This is the only location inside the loop where a new
+                // iteration is started.
+                continue;
+              } else {
+                // a[great] < pivot2.
+                if (greatValue < pivotValue1) {
+                  // Triple exchange.
+                  a[k] = a[less];
+                  a[less++] = a[great];
+                  a[great--] = ek;
+                } else {
+                  // a[great] == pivot1.
+                  a[k] = a[great];
+                  a[great--] = ek;
+                }
+                break;
+              }
+            }
+          }
+        }
+      }
+      })(); // isolate scope
+
+    }
+
+    // The second partition has now been cleared of pivot elements and looks
+    // as follows:
+    // [  *  |  > pivot1 && < pivot2  | * ]
+    //        ^                      ^
+    //       less                  great
+    // Sort the second partition using recursive descent.
+
+    // The second partition looks as follows:
+    // [  *  |  >= pivot1 && <= pivot2  | * ]
+    //        ^                        ^
+    //       less                    great
+    // Simply sort it by recursive descent.
+
+    return sort(a, less, great + 1);
+  }
+
+  return sort;
+}
+
+var quicksort_sizeThreshold = 32;
+
+module.exports = quicksort_by(crossfilter_identity);
+module.exports.by = quicksort_by;
+
+},{"./identity":10,"./insertionsort":11}],15:[function(require,module,exports){
+'use strict';
+
+function crossfilter_reduceIncrement(p) {
+  return p + 1;
+}
+
+function crossfilter_reduceDecrement(p) {
+  return p - 1;
+}
+
+function crossfilter_reduceAdd(f) {
+  return function(p, v) {
+    return p + +f(v);
+  };
+}
+
+function crossfilter_reduceSubtract(f) {
+  return function(p, v) {
+    return p - f(v);
+  };
+}
+
+module.exports = {
+  reduceIncrement: crossfilter_reduceIncrement,
+  reduceDecrement: crossfilter_reduceDecrement,
+  reduceAdd: crossfilter_reduceAdd,
+  reduceSubtract: crossfilter_reduceSubtract
+};
+
+},{}],16:[function(require,module,exports){
+'use strict';
+
+function crossfilter_zero() {
+  return 0;
+}
+
+module.exports = crossfilter_zero;
+
+},{}]},{},[1])(1)
+});
\ No newline at end of file
diff --git a/src/legacy/design-studio/js/d3.js b/src/legacy/design-studio/js/d3.js
new file mode 100644
index 0000000000000000000000000000000000000000..aded45c440cecdc01cfc94ca364f4102e870a1b7
--- /dev/null
+++ b/src/legacy/design-studio/js/d3.js
@@ -0,0 +1,9554 @@
+!function() {
+  var d3 = {
+    version: "3.5.17"
+  };
+  var d3_arraySlice = [].slice, d3_array = function(list) {
+    return d3_arraySlice.call(list);
+  };
+  var d3_document = this.document;
+  function d3_documentElement(node) {
+    return node && (node.ownerDocument || node.document || node).documentElement;
+  }
+  function d3_window(node) {
+    return node && (node.ownerDocument && node.ownerDocument.defaultView || node.document && node || node.defaultView);
+  }
+  if (d3_document) {
+    try {
+      d3_array(d3_document.documentElement.childNodes)[0].nodeType;
+    } catch (e) {
+      d3_array = function(list) {
+        var i = list.length, array = new Array(i);
+        while (i--) array[i] = list[i];
+        return array;
+      };
+    }
+  }
+  if (!Date.now) Date.now = function() {
+    return +new Date();
+  };
+  if (d3_document) {
+    try {
+      d3_document.createElement("DIV").style.setProperty("opacity", 0, "");
+    } catch (error) {
+      var d3_element_prototype = this.Element.prototype, d3_element_setAttribute = d3_element_prototype.setAttribute, d3_element_setAttributeNS = d3_element_prototype.setAttributeNS, d3_style_prototype = this.CSSStyleDeclaration.prototype, d3_style_setProperty = d3_style_prototype.setProperty;
+      d3_element_prototype.setAttribute = function(name, value) {
+        d3_element_setAttribute.call(this, name, value + "");
+      };
+      d3_element_prototype.setAttributeNS = function(space, local, value) {
+        d3_element_setAttributeNS.call(this, space, local, value + "");
+      };
+      d3_style_prototype.setProperty = function(name, value, priority) {
+        d3_style_setProperty.call(this, name, value + "", priority);
+      };
+    }
+  }
+  d3.ascending = d3_ascending;
+  function d3_ascending(a, b) {
+    return a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN;
+  }
+  d3.descending = function(a, b) {
+    return b < a ? -1 : b > a ? 1 : b >= a ? 0 : NaN;
+  };
+  d3.min = function(array, f) {
+    var i = -1, n = array.length, a, b;
+    if (arguments.length === 1) {
+      while (++i < n) if ((b = array[i]) != null && b >= b) {
+        a = b;
+        break;
+      }
+      while (++i < n) if ((b = array[i]) != null && a > b) a = b;
+    } else {
+      while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) {
+        a = b;
+        break;
+      }
+      while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b;
+    }
+    return a;
+  };
+  d3.max = function(array, f) {
+    var i = -1, n = array.length, a, b;
+    if (arguments.length === 1) {
+      while (++i < n) if ((b = array[i]) != null && b >= b) {
+        a = b;
+        break;
+      }
+      while (++i < n) if ((b = array[i]) != null && b > a) a = b;
+    } else {
+      while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) {
+        a = b;
+        break;
+      }
+      while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b;
+    }
+    return a;
+  };
+  d3.extent = function(array, f) {
+    var i = -1, n = array.length, a, b, c;
+    if (arguments.length === 1) {
+      while (++i < n) if ((b = array[i]) != null && b >= b) {
+        a = c = b;
+        break;
+      }
+      while (++i < n) if ((b = array[i]) != null) {
+        if (a > b) a = b;
+        if (c < b) c = b;
+      }
+    } else {
+      while (++i < n) if ((b = f.call(array, array[i], i)) != null && b >= b) {
+        a = c = b;
+        break;
+      }
+      while (++i < n) if ((b = f.call(array, array[i], i)) != null) {
+        if (a > b) a = b;
+        if (c < b) c = b;
+      }
+    }
+    return [ a, c ];
+  };
+  function d3_number(x) {
+    return x === null ? NaN : +x;
+  }
+  function d3_numeric(x) {
+    return !isNaN(x);
+  }
+  d3.sum = function(array, f) {
+    var s = 0, n = array.length, a, i = -1;
+    if (arguments.length === 1) {
+      while (++i < n) if (d3_numeric(a = +array[i])) s += a;
+    } else {
+      while (++i < n) if (d3_numeric(a = +f.call(array, array[i], i))) s += a;
+    }
+    return s;
+  };
+  d3.mean = function(array, f) {
+    var s = 0, n = array.length, a, i = -1, j = n;
+    if (arguments.length === 1) {
+      while (++i < n) if (d3_numeric(a = d3_number(array[i]))) s += a; else --j;
+    } else {
+      while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) s += a; else --j;
+    }
+    if (j) return s / j;
+  };
+  d3.quantile = function(values, p) {
+    var H = (values.length - 1) * p + 1, h = Math.floor(H), v = +values[h - 1], e = H - h;
+    return e ? v + e * (values[h] - v) : v;
+  };
+  d3.median = function(array, f) {
+    var numbers = [], n = array.length, a, i = -1;
+    if (arguments.length === 1) {
+      while (++i < n) if (d3_numeric(a = d3_number(array[i]))) numbers.push(a);
+    } else {
+      while (++i < n) if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) numbers.push(a);
+    }
+    if (numbers.length) return d3.quantile(numbers.sort(d3_ascending), .5);
+  };
+  d3.variance = function(array, f) {
+    var n = array.length, m = 0, a, d, s = 0, i = -1, j = 0;
+    if (arguments.length === 1) {
+      while (++i < n) {
+        if (d3_numeric(a = d3_number(array[i]))) {
+          d = a - m;
+          m += d / ++j;
+          s += d * (a - m);
+        }
+      }
+    } else {
+      while (++i < n) {
+        if (d3_numeric(a = d3_number(f.call(array, array[i], i)))) {
+          d = a - m;
+          m += d / ++j;
+          s += d * (a - m);
+        }
+      }
+    }
+    if (j > 1) return s / (j - 1);
+  };
+  d3.deviation = function() {
+    var v = d3.variance.apply(this, arguments);
+    return v ? Math.sqrt(v) : v;
+  };
+  function d3_bisector(compare) {
+    return {
+      left: function(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;
+      },
+      right: function(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) hi = mid; else lo = mid + 1;
+        }
+        return lo;
+      }
+    };
+  }
+  var d3_bisect = d3_bisector(d3_ascending);
+  d3.bisectLeft = d3_bisect.left;
+  d3.bisect = d3.bisectRight = d3_bisect.right;
+  d3.bisector = function(f) {
+    return d3_bisector(f.length === 1 ? function(d, x) {
+      return d3_ascending(f(d), x);
+    } : f);
+  };
+  d3.shuffle = function(array, i0, i1) {
+    if ((m = arguments.length) < 3) {
+      i1 = array.length;
+      if (m < 2) i0 = 0;
+    }
+    var m = i1 - i0, t, i;
+    while (m) {
+      i = Math.random() * m-- | 0;
+      t = array[m + i0], array[m + i0] = array[i + i0], array[i + i0] = t;
+    }
+    return array;
+  };
+  d3.permute = function(array, indexes) {
+    var i = indexes.length, permutes = new Array(i);
+    while (i--) permutes[i] = array[indexes[i]];
+    return permutes;
+  };
+  d3.pairs = function(array) {
+    var i = 0, n = array.length - 1, p0, p1 = array[0], pairs = new Array(n < 0 ? 0 : n);
+    while (i < n) pairs[i] = [ p0 = p1, p1 = array[++i] ];
+    return pairs;
+  };
+  d3.transpose = function(matrix) {
+    if (!(n = matrix.length)) return [];
+    for (var i = -1, m = d3.min(matrix, d3_transposeLength), transpose = new Array(m); ++i < m; ) {
+      for (var j = -1, n, row = transpose[i] = new Array(n); ++j < n; ) {
+        row[j] = matrix[j][i];
+      }
+    }
+    return transpose;
+  };
+  function d3_transposeLength(d) {
+    return d.length;
+  }
+  d3.zip = function() {
+    return d3.transpose(arguments);
+  };
+  d3.keys = function(map) {
+    var keys = [];
+    for (var key in map) keys.push(key);
+    return keys;
+  };
+  d3.values = function(map) {
+    var values = [];
+    for (var key in map) values.push(map[key]);
+    return values;
+  };
+  d3.entries = function(map) {
+    var entries = [];
+    for (var key in map) entries.push({
+      key: key,
+      value: map[key]
+    });
+    return entries;
+  };
+  d3.merge = function(arrays) {
+    var n = arrays.length, m, i = -1, j = 0, merged, array;
+    while (++i < n) j += arrays[i].length;
+    merged = new Array(j);
+    while (--n >= 0) {
+      array = arrays[n];
+      m = array.length;
+      while (--m >= 0) {
+        merged[--j] = array[m];
+      }
+    }
+    return merged;
+  };
+  var abs = Math.abs;
+  d3.range = function(start, stop, step) {
+    if (arguments.length < 3) {
+      step = 1;
+      if (arguments.length < 2) {
+        stop = start;
+        start = 0;
+      }
+    }
+    if ((stop - start) / step === Infinity) throw new Error("infinite range");
+    var range = [], k = d3_range_integerScale(abs(step)), i = -1, j;
+    start *= k, stop *= k, step *= k;
+    if (step < 0) while ((j = start + step * ++i) > stop) range.push(j / k); else while ((j = start + step * ++i) < stop) range.push(j / k);
+    return range;
+  };
+  function d3_range_integerScale(x) {
+    var k = 1;
+    while (x * k % 1) k *= 10;
+    return k;
+  }
+  function d3_class(ctor, properties) {
+    for (var key in properties) {
+      Object.defineProperty(ctor.prototype, key, {
+        value: properties[key],
+        enumerable: false
+      });
+    }
+  }
+  d3.map = function(object, f) {
+    var map = new d3_Map();
+    if (object instanceof d3_Map) {
+      object.forEach(function(key, value) {
+        map.set(key, value);
+      });
+    } else if (Array.isArray(object)) {
+      var i = -1, n = object.length, o;
+      if (arguments.length === 1) while (++i < n) map.set(i, object[i]); else while (++i < n) map.set(f.call(object, o = object[i], i), o);
+    } else {
+      for (var key in object) map.set(key, object[key]);
+    }
+    return map;
+  };
+  function d3_Map() {
+    this._ = Object.create(null);
+  }
+  var d3_map_proto = "__proto__", d3_map_zero = "\x00";
+  d3_class(d3_Map, {
+    has: d3_map_has,
+    get: function(key) {
+      return this._[d3_map_escape(key)];
+    },
+    set: function(key, value) {
+      return this._[d3_map_escape(key)] = value;
+    },
+    remove: d3_map_remove,
+    keys: d3_map_keys,
+    values: function() {
+      var values = [];
+      for (var key in this._) values.push(this._[key]);
+      return values;
+    },
+    entries: function() {
+      var entries = [];
+      for (var key in this._) entries.push({
+        key: d3_map_unescape(key),
+        value: this._[key]
+      });
+      return entries;
+    },
+    size: d3_map_size,
+    empty: d3_map_empty,
+    forEach: function(f) {
+      for (var key in this._) f.call(this, d3_map_unescape(key), this._[key]);
+    }
+  });
+  function d3_map_escape(key) {
+    return (key += "") === d3_map_proto || key[0] === d3_map_zero ? d3_map_zero + key : key;
+  }
+  function d3_map_unescape(key) {
+    return (key += "")[0] === d3_map_zero ? key.slice(1) : key;
+  }
+  function d3_map_has(key) {
+    return d3_map_escape(key) in this._;
+  }
+  function d3_map_remove(key) {
+    return (key = d3_map_escape(key)) in this._ && delete this._[key];
+  }
+  function d3_map_keys() {
+    var keys = [];
+    for (var key in this._) keys.push(d3_map_unescape(key));
+    return keys;
+  }
+  function d3_map_size() {
+    var size = 0;
+    for (var key in this._) ++size;
+    return size;
+  }
+  function d3_map_empty() {
+    for (var key in this._) return false;
+    return true;
+  }
+  d3.nest = function() {
+    var nest = {}, keys = [], sortKeys = [], sortValues, rollup;
+    function map(mapType, array, depth) {
+      if (depth >= keys.length) return rollup ? rollup.call(nest, array) : sortValues ? array.sort(sortValues) : array;
+      var i = -1, n = array.length, key = keys[depth++], keyValue, object, setter, valuesByKey = new d3_Map(), values;
+      while (++i < n) {
+        if (values = valuesByKey.get(keyValue = key(object = array[i]))) {
+          values.push(object);
+        } else {
+          valuesByKey.set(keyValue, [ object ]);
+        }
+      }
+      if (mapType) {
+        object = mapType();
+        setter = function(keyValue, values) {
+          object.set(keyValue, map(mapType, values, depth));
+        };
+      } else {
+        object = {};
+        setter = function(keyValue, values) {
+          object[keyValue] = map(mapType, values, depth);
+        };
+      }
+      valuesByKey.forEach(setter);
+      return object;
+    }
+    function entries(map, depth) {
+      if (depth >= keys.length) return map;
+      var array = [], sortKey = sortKeys[depth++];
+      map.forEach(function(key, keyMap) {
+        array.push({
+          key: key,
+          values: entries(keyMap, depth)
+        });
+      });
+      return sortKey ? array.sort(function(a, b) {
+        return sortKey(a.key, b.key);
+      }) : array;
+    }
+    nest.map = function(array, mapType) {
+      return map(mapType, array, 0);
+    };
+    nest.entries = function(array) {
+      return entries(map(d3.map, array, 0), 0);
+    };
+    nest.key = function(d) {
+      keys.push(d);
+      return nest;
+    };
+    nest.sortKeys = function(order) {
+      sortKeys[keys.length - 1] = order;
+      return nest;
+    };
+    nest.sortValues = function(order) {
+      sortValues = order;
+      return nest;
+    };
+    nest.rollup = function(f) {
+      rollup = f;
+      return nest;
+    };
+    return nest;
+  };
+  d3.set = function(array) {
+    var set = new d3_Set();
+    if (array) for (var i = 0, n = array.length; i < n; ++i) set.add(array[i]);
+    return set;
+  };
+  function d3_Set() {
+    this._ = Object.create(null);
+  }
+  d3_class(d3_Set, {
+    has: d3_map_has,
+    add: function(key) {
+      this._[d3_map_escape(key += "")] = true;
+      return key;
+    },
+    remove: d3_map_remove,
+    values: d3_map_keys,
+    size: d3_map_size,
+    empty: d3_map_empty,
+    forEach: function(f) {
+      for (var key in this._) f.call(this, d3_map_unescape(key));
+    }
+  });
+  d3.behavior = {};
+  function d3_identity(d) {
+    return d;
+  }
+  d3.rebind = function(target, source) {
+    var i = 1, n = arguments.length, method;
+    while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]);
+    return target;
+  };
+  function d3_rebind(target, source, method) {
+    return function() {
+      var value = method.apply(source, arguments);
+      return value === source ? target : value;
+    };
+  }
+  function d3_vendorSymbol(object, name) {
+    if (name in object) return name;
+    name = name.charAt(0).toUpperCase() + name.slice(1);
+    for (var i = 0, n = d3_vendorPrefixes.length; i < n; ++i) {
+      var prefixName = d3_vendorPrefixes[i] + name;
+      if (prefixName in object) return prefixName;
+    }
+  }
+  var d3_vendorPrefixes = [ "webkit", "ms", "moz", "Moz", "o", "O" ];
+  function d3_noop() {}
+  d3.dispatch = function() {
+    var dispatch = new d3_dispatch(), i = -1, n = arguments.length;
+    while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
+    return dispatch;
+  };
+  function d3_dispatch() {}
+  d3_dispatch.prototype.on = function(type, listener) {
+    var i = type.indexOf("."), name = "";
+    if (i >= 0) {
+      name = type.slice(i + 1);
+      type = type.slice(0, i);
+    }
+    if (type) return arguments.length < 2 ? this[type].on(name) : this[type].on(name, listener);
+    if (arguments.length === 2) {
+      if (listener == null) for (type in this) {
+        if (this.hasOwnProperty(type)) this[type].on(name, null);
+      }
+      return this;
+    }
+  };
+  function d3_dispatch_event(dispatch) {
+    var listeners = [], listenerByName = new d3_Map();
+    function event() {
+      var z = listeners, i = -1, n = z.length, l;
+      while (++i < n) if (l = z[i].on) l.apply(this, arguments);
+      return dispatch;
+    }
+    event.on = function(name, listener) {
+      var l = listenerByName.get(name), i;
+      if (arguments.length < 2) return l && l.on;
+      if (l) {
+        l.on = null;
+        listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1));
+        listenerByName.remove(name);
+      }
+      if (listener) listeners.push(listenerByName.set(name, {
+        on: listener
+      }));
+      return dispatch;
+    };
+    return event;
+  }
+  d3.event = null;
+  function d3_eventPreventDefault() {
+    d3.event.preventDefault();
+  }
+  function d3_eventSource() {
+    var e = d3.event, s;
+    while (s = e.sourceEvent) e = s;
+    return e;
+  }
+  function d3_eventDispatch(target) {
+    var dispatch = new d3_dispatch(), i = 0, n = arguments.length;
+    while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch);
+    dispatch.of = function(thiz, argumentz) {
+      return function(e1) {
+        try {
+          var e0 = e1.sourceEvent = d3.event;
+          e1.target = target;
+          d3.event = e1;
+          dispatch[e1.type].apply(thiz, argumentz);
+        } finally {
+          d3.event = e0;
+        }
+      };
+    };
+    return dispatch;
+  }
+  d3.requote = function(s) {
+    return s.replace(d3_requote_re, "\\$&");
+  };
+  var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g;
+  var d3_subclass = {}.__proto__ ? function(object, prototype) {
+    object.__proto__ = prototype;
+  } : function(object, prototype) {
+    for (var property in prototype) object[property] = prototype[property];
+  };
+  function d3_selection(groups) {
+    d3_subclass(groups, d3_selectionPrototype);
+    return groups;
+  }
+  var d3_select = function(s, n) {
+    return n.querySelector(s);
+  }, d3_selectAll = function(s, n) {
+    return n.querySelectorAll(s);
+  }, d3_selectMatches = function(n, s) {
+    var d3_selectMatcher = n.matches || n[d3_vendorSymbol(n, "matchesSelector")];
+    d3_selectMatches = function(n, s) {
+      return d3_selectMatcher.call(n, s);
+    };
+    return d3_selectMatches(n, s);
+  };
+  if (typeof Sizzle === "function") {
+    d3_select = function(s, n) {
+      return Sizzle(s, n)[0] || null;
+    };
+    d3_selectAll = Sizzle;
+    d3_selectMatches = Sizzle.matchesSelector;
+  }
+  d3.selection = function() {
+    return d3.select(d3_document.documentElement);
+  };
+  var d3_selectionPrototype = d3.selection.prototype = [];
+  d3_selectionPrototype.select = function(selector) {
+    var subgroups = [], subgroup, subnode, group, node;
+    selector = d3_selection_selector(selector);
+    for (var j = -1, m = this.length; ++j < m; ) {
+      subgroups.push(subgroup = []);
+      subgroup.parentNode = (group = this[j]).parentNode;
+      for (var i = -1, n = group.length; ++i < n; ) {
+        if (node = group[i]) {
+          subgroup.push(subnode = selector.call(node, node.__data__, i, j));
+          if (subnode && "__data__" in node) subnode.__data__ = node.__data__;
+        } else {
+          subgroup.push(null);
+        }
+      }
+    }
+    return d3_selection(subgroups);
+  };
+  function d3_selection_selector(selector) {
+    return typeof selector === "function" ? selector : function() {
+      return d3_select(selector, this);
+    };
+  }
+  d3_selectionPrototype.selectAll = function(selector) {
+    var subgroups = [], subgroup, node;
+    selector = d3_selection_selectorAll(selector);
+    for (var j = -1, m = this.length; ++j < m; ) {
+      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
+        if (node = group[i]) {
+          subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i, j)));
+          subgroup.parentNode = node;
+        }
+      }
+    }
+    return d3_selection(subgroups);
+  };
+  function d3_selection_selectorAll(selector) {
+    return typeof selector === "function" ? selector : function() {
+      return d3_selectAll(selector, this);
+    };
+  }
+  var d3_nsXhtml = "http://www.w3.org/1999/xhtml";
+  var d3_nsPrefix = {
+    svg: "http://www.w3.org/2000/svg",
+    xhtml: d3_nsXhtml,
+    xlink: "http://www.w3.org/1999/xlink",
+    xml: "http://www.w3.org/XML/1998/namespace",
+    xmlns: "http://www.w3.org/2000/xmlns/"
+  };
+  d3.ns = {
+    prefix: d3_nsPrefix,
+    qualify: function(name) {
+      var i = name.indexOf(":"), prefix = name;
+      if (i >= 0 && (prefix = name.slice(0, i)) !== "xmlns") name = name.slice(i + 1);
+      return d3_nsPrefix.hasOwnProperty(prefix) ? {
+        space: d3_nsPrefix[prefix],
+        local: name
+      } : name;
+    }
+  };
+  d3_selectionPrototype.attr = function(name, value) {
+    if (arguments.length < 2) {
+      if (typeof name === "string") {
+        var node = this.node();
+        name = d3.ns.qualify(name);
+        return name.local ? node.getAttributeNS(name.space, name.local) : node.getAttribute(name);
+      }
+      for (value in name) this.each(d3_selection_attr(value, name[value]));
+      return this;
+    }
+    return this.each(d3_selection_attr(name, value));
+  };
+  function d3_selection_attr(name, value) {
+    name = d3.ns.qualify(name);
+    function attrNull() {
+      this.removeAttribute(name);
+    }
+    function attrNullNS() {
+      this.removeAttributeNS(name.space, name.local);
+    }
+    function attrConstant() {
+      this.setAttribute(name, value);
+    }
+    function attrConstantNS() {
+      this.setAttributeNS(name.space, name.local, value);
+    }
+    function attrFunction() {
+      var x = value.apply(this, arguments);
+      if (x == null) this.removeAttribute(name); else this.setAttribute(name, x);
+    }
+    function attrFunctionNS() {
+      var x = value.apply(this, arguments);
+      if (x == null) this.removeAttributeNS(name.space, name.local); else this.setAttributeNS(name.space, name.local, x);
+    }
+    return value == null ? name.local ? attrNullNS : attrNull : typeof value === "function" ? name.local ? attrFunctionNS : attrFunction : name.local ? attrConstantNS : attrConstant;
+  }
+  function d3_collapse(s) {
+    return s.trim().replace(/\s+/g, " ");
+  }
+  d3_selectionPrototype.classed = function(name, value) {
+    if (arguments.length < 2) {
+      if (typeof name === "string") {
+        var node = this.node(), n = (name = d3_selection_classes(name)).length, i = -1;
+        if (value = node.classList) {
+          while (++i < n) if (!value.contains(name[i])) return false;
+        } else {
+          value = node.getAttribute("class");
+          while (++i < n) if (!d3_selection_classedRe(name[i]).test(value)) return false;
+        }
+        return true;
+      }
+      for (value in name) this.each(d3_selection_classed(value, name[value]));
+      return this;
+    }
+    return this.each(d3_selection_classed(name, value));
+  };
+  function d3_selection_classedRe(name) {
+    return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g");
+  }
+  function d3_selection_classes(name) {
+    return (name + "").trim().split(/^|\s+/);
+  }
+  function d3_selection_classed(name, value) {
+    name = d3_selection_classes(name).map(d3_selection_classedName);
+    var n = name.length;
+    function classedConstant() {
+      var i = -1;
+      while (++i < n) name[i](this, value);
+    }
+    function classedFunction() {
+      var i = -1, x = value.apply(this, arguments);
+      while (++i < n) name[i](this, x);
+    }
+    return typeof value === "function" ? classedFunction : classedConstant;
+  }
+  function d3_selection_classedName(name) {
+    var re = d3_selection_classedRe(name);
+    return function(node, value) {
+      if (c = node.classList) return value ? c.add(name) : c.remove(name);
+      var c = node.getAttribute("class") || "";
+      if (value) {
+        re.lastIndex = 0;
+        if (!re.test(c)) node.setAttribute("class", d3_collapse(c + " " + name));
+      } else {
+        node.setAttribute("class", d3_collapse(c.replace(re, " ")));
+      }
+    };
+  }
+  d3_selectionPrototype.style = function(name, value, priority) {
+    var n = arguments.length;
+    if (n < 3) {
+      if (typeof name !== "string") {
+        if (n < 2) value = "";
+        for (priority in name) this.each(d3_selection_style(priority, name[priority], value));
+        return this;
+      }
+      if (n < 2) {
+        var node = this.node();
+        return d3_window(node).getComputedStyle(node, null).getPropertyValue(name);
+      }
+      priority = "";
+    }
+    return this.each(d3_selection_style(name, value, priority));
+  };
+  function d3_selection_style(name, value, priority) {
+    function styleNull() {
+      this.style.removeProperty(name);
+    }
+    function styleConstant() {
+      this.style.setProperty(name, value, priority);
+    }
+    function styleFunction() {
+      var x = value.apply(this, arguments);
+      if (x == null) this.style.removeProperty(name); else this.style.setProperty(name, x, priority);
+    }
+    return value == null ? styleNull : typeof value === "function" ? styleFunction : styleConstant;
+  }
+  d3_selectionPrototype.property = function(name, value) {
+    if (arguments.length < 2) {
+      if (typeof name === "string") return this.node()[name];
+      for (value in name) this.each(d3_selection_property(value, name[value]));
+      return this;
+    }
+    return this.each(d3_selection_property(name, value));
+  };
+  function d3_selection_property(name, value) {
+    function propertyNull() {
+      delete this[name];
+    }
+    function propertyConstant() {
+      this[name] = value;
+    }
+    function propertyFunction() {
+      var x = value.apply(this, arguments);
+      if (x == null) delete this[name]; else this[name] = x;
+    }
+    return value == null ? propertyNull : typeof value === "function" ? propertyFunction : propertyConstant;
+  }
+  d3_selectionPrototype.text = function(value) {
+    return arguments.length ? this.each(typeof value === "function" ? function() {
+      var v = value.apply(this, arguments);
+      this.textContent = v == null ? "" : v;
+    } : value == null ? function() {
+      this.textContent = "";
+    } : function() {
+      this.textContent = value;
+    }) : this.node().textContent;
+  };
+  d3_selectionPrototype.html = function(value) {
+    return arguments.length ? this.each(typeof value === "function" ? function() {
+      var v = value.apply(this, arguments);
+      this.innerHTML = v == null ? "" : v;
+    } : value == null ? function() {
+      this.innerHTML = "";
+    } : function() {
+      this.innerHTML = value;
+    }) : this.node().innerHTML;
+  };
+  d3_selectionPrototype.append = function(name) {
+    name = d3_selection_creator(name);
+    return this.select(function() {
+      return this.appendChild(name.apply(this, arguments));
+    });
+  };
+  function d3_selection_creator(name) {
+    function create() {
+      var document = this.ownerDocument, namespace = this.namespaceURI;
+      return namespace === d3_nsXhtml && document.documentElement.namespaceURI === d3_nsXhtml ? document.createElement(name) : document.createElementNS(namespace, name);
+    }
+    function createNS() {
+      return this.ownerDocument.createElementNS(name.space, name.local);
+    }
+    return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? createNS : create;
+  }
+  d3_selectionPrototype.insert = function(name, before) {
+    name = d3_selection_creator(name);
+    before = d3_selection_selector(before);
+    return this.select(function() {
+      return this.insertBefore(name.apply(this, arguments), before.apply(this, arguments) || null);
+    });
+  };
+  d3_selectionPrototype.remove = function() {
+    return this.each(d3_selectionRemove);
+  };
+  function d3_selectionRemove() {
+    var parent = this.parentNode;
+    if (parent) parent.removeChild(this);
+  }
+  d3_selectionPrototype.data = function(value, key) {
+    var i = -1, n = this.length, group, node;
+    if (!arguments.length) {
+      value = new Array(n = (group = this[0]).length);
+      while (++i < n) {
+        if (node = group[i]) {
+          value[i] = node.__data__;
+        }
+      }
+      return value;
+    }
+    function bind(group, groupData) {
+      var i, n = group.length, m = groupData.length, n0 = Math.min(n, m), updateNodes = new Array(m), enterNodes = new Array(m), exitNodes = new Array(n), node, nodeData;
+      if (key) {
+        var nodeByKeyValue = new d3_Map(), keyValues = new Array(n), keyValue;
+        for (i = -1; ++i < n; ) {
+          if (node = group[i]) {
+            if (nodeByKeyValue.has(keyValue = key.call(node, node.__data__, i))) {
+              exitNodes[i] = node;
+            } else {
+              nodeByKeyValue.set(keyValue, node);
+            }
+            keyValues[i] = keyValue;
+          }
+        }
+        for (i = -1; ++i < m; ) {
+          if (!(node = nodeByKeyValue.get(keyValue = key.call(groupData, nodeData = groupData[i], i)))) {
+            enterNodes[i] = d3_selection_dataNode(nodeData);
+          } else if (node !== true) {
+            updateNodes[i] = node;
+            node.__data__ = nodeData;
+          }
+          nodeByKeyValue.set(keyValue, true);
+        }
+        for (i = -1; ++i < n; ) {
+          if (i in keyValues && nodeByKeyValue.get(keyValues[i]) !== true) {
+            exitNodes[i] = group[i];
+          }
+        }
+      } else {
+        for (i = -1; ++i < n0; ) {
+          node = group[i];
+          nodeData = groupData[i];
+          if (node) {
+            node.__data__ = nodeData;
+            updateNodes[i] = node;
+          } else {
+            enterNodes[i] = d3_selection_dataNode(nodeData);
+          }
+        }
+        for (;i < m; ++i) {
+          enterNodes[i] = d3_selection_dataNode(groupData[i]);
+        }
+        for (;i < n; ++i) {
+          exitNodes[i] = group[i];
+        }
+      }
+      enterNodes.update = updateNodes;
+      enterNodes.parentNode = updateNodes.parentNode = exitNodes.parentNode = group.parentNode;
+      enter.push(enterNodes);
+      update.push(updateNodes);
+      exit.push(exitNodes);
+    }
+    var enter = d3_selection_enter([]), update = d3_selection([]), exit = d3_selection([]);
+    if (typeof value === "function") {
+      while (++i < n) {
+        bind(group = this[i], value.call(group, group.parentNode.__data__, i));
+      }
+    } else {
+      while (++i < n) {
+        bind(group = this[i], value);
+      }
+    }
+    update.enter = function() {
+      return enter;
+    };
+    update.exit = function() {
+      return exit;
+    };
+    return update;
+  };
+  function d3_selection_dataNode(data) {
+    return {
+      __data__: data
+    };
+  }
+  d3_selectionPrototype.datum = function(value) {
+    return arguments.length ? this.property("__data__", value) : this.property("__data__");
+  };
+  d3_selectionPrototype.filter = function(filter) {
+    var subgroups = [], subgroup, group, node;
+    if (typeof filter !== "function") filter = d3_selection_filter(filter);
+    for (var j = 0, m = this.length; j < m; j++) {
+      subgroups.push(subgroup = []);
+      subgroup.parentNode = (group = this[j]).parentNode;
+      for (var i = 0, n = group.length; i < n; i++) {
+        if ((node = group[i]) && filter.call(node, node.__data__, i, j)) {
+          subgroup.push(node);
+        }
+      }
+    }
+    return d3_selection(subgroups);
+  };
+  function d3_selection_filter(selector) {
+    return function() {
+      return d3_selectMatches(this, selector);
+    };
+  }
+  d3_selectionPrototype.order = function() {
+    for (var j = -1, m = this.length; ++j < m; ) {
+      for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0; ) {
+        if (node = group[i]) {
+          if (next && next !== node.nextSibling) next.parentNode.insertBefore(node, next);
+          next = node;
+        }
+      }
+    }
+    return this;
+  };
+  d3_selectionPrototype.sort = function(comparator) {
+    comparator = d3_selection_sortComparator.apply(this, arguments);
+    for (var j = -1, m = this.length; ++j < m; ) this[j].sort(comparator);
+    return this.order();
+  };
+  function d3_selection_sortComparator(comparator) {
+    if (!arguments.length) comparator = d3_ascending;
+    return function(a, b) {
+      return a && b ? comparator(a.__data__, b.__data__) : !a - !b;
+    };
+  }
+  d3_selectionPrototype.each = function(callback) {
+    return d3_selection_each(this, function(node, i, j) {
+      callback.call(node, node.__data__, i, j);
+    });
+  };
+  function d3_selection_each(groups, callback) {
+    for (var j = 0, m = groups.length; j < m; j++) {
+      for (var group = groups[j], i = 0, n = group.length, node; i < n; i++) {
+        if (node = group[i]) callback(node, i, j);
+      }
+    }
+    return groups;
+  }
+  d3_selectionPrototype.call = function(callback) {
+    var args = d3_array(arguments);
+    callback.apply(args[0] = this, args);
+    return this;
+  };
+  d3_selectionPrototype.empty = function() {
+    return !this.node();
+  };
+  d3_selectionPrototype.node = function() {
+    for (var j = 0, m = this.length; j < m; j++) {
+      for (var group = this[j], i = 0, n = group.length; i < n; i++) {
+        var node = group[i];
+        if (node) return node;
+      }
+    }
+    return null;
+  };
+  d3_selectionPrototype.size = function() {
+    var n = 0;
+    d3_selection_each(this, function() {
+      ++n;
+    });
+    return n;
+  };
+  function d3_selection_enter(selection) {
+    d3_subclass(selection, d3_selection_enterPrototype);
+    return selection;
+  }
+  var d3_selection_enterPrototype = [];
+  d3.selection.enter = d3_selection_enter;
+  d3.selection.enter.prototype = d3_selection_enterPrototype;
+  d3_selection_enterPrototype.append = d3_selectionPrototype.append;
+  d3_selection_enterPrototype.empty = d3_selectionPrototype.empty;
+  d3_selection_enterPrototype.node = d3_selectionPrototype.node;
+  d3_selection_enterPrototype.call = d3_selectionPrototype.call;
+  d3_selection_enterPrototype.size = d3_selectionPrototype.size;
+  d3_selection_enterPrototype.select = function(selector) {
+    var subgroups = [], subgroup, subnode, upgroup, group, node;
+    for (var j = -1, m = this.length; ++j < m; ) {
+      upgroup = (group = this[j]).update;
+      subgroups.push(subgroup = []);
+      subgroup.parentNode = group.parentNode;
+      for (var i = -1, n = group.length; ++i < n; ) {
+        if (node = group[i]) {
+          subgroup.push(upgroup[i] = subnode = selector.call(group.parentNode, node.__data__, i, j));
+          subnode.__data__ = node.__data__;
+        } else {
+          subgroup.push(null);
+        }
+      }
+    }
+    return d3_selection(subgroups);
+  };
+  d3_selection_enterPrototype.insert = function(name, before) {
+    if (arguments.length < 2) before = d3_selection_enterInsertBefore(this);
+    return d3_selectionPrototype.insert.call(this, name, before);
+  };
+  function d3_selection_enterInsertBefore(enter) {
+    var i0, j0;
+    return function(d, i, j) {
+      var group = enter[j].update, n = group.length, node;
+      if (j != j0) j0 = j, i0 = 0;
+      if (i >= i0) i0 = i + 1;
+      while (!(node = group[i0]) && ++i0 < n) ;
+      return node;
+    };
+  }
+  d3.select = function(node) {
+    var group;
+    if (typeof node === "string") {
+      group = [ d3_select(node, d3_document) ];
+      group.parentNode = d3_document.documentElement;
+    } else {
+      group = [ node ];
+      group.parentNode = d3_documentElement(node);
+    }
+    return d3_selection([ group ]);
+  };
+  d3.selectAll = function(nodes) {
+    var group;
+    if (typeof nodes === "string") {
+      group = d3_array(d3_selectAll(nodes, d3_document));
+      group.parentNode = d3_document.documentElement;
+    } else {
+      group = d3_array(nodes);
+      group.parentNode = null;
+    }
+    return d3_selection([ group ]);
+  };
+  d3_selectionPrototype.on = function(type, listener, capture) {
+    var n = arguments.length;
+    if (n < 3) {
+      if (typeof type !== "string") {
+        if (n < 2) listener = false;
+        for (capture in type) this.each(d3_selection_on(capture, type[capture], listener));
+        return this;
+      }
+      if (n < 2) return (n = this.node()["__on" + type]) && n._;
+      capture = false;
+    }
+    return this.each(d3_selection_on(type, listener, capture));
+  };
+  function d3_selection_on(type, listener, capture) {
+    var name = "__on" + type, i = type.indexOf("."), wrap = d3_selection_onListener;
+    if (i > 0) type = type.slice(0, i);
+    var filter = d3_selection_onFilters.get(type);
+    if (filter) type = filter, wrap = d3_selection_onFilter;
+    function onRemove() {
+      var l = this[name];
+      if (l) {
+        this.removeEventListener(type, l, l.$);
+        delete this[name];
+      }
+    }
+    function onAdd() {
+      var l = wrap(listener, d3_array(arguments));
+      onRemove.call(this);
+      this.addEventListener(type, this[name] = l, l.$ = capture);
+      l._ = listener;
+    }
+    function removeAll() {
+      var re = new RegExp("^__on([^.]+)" + d3.requote(type) + "$"), match;
+      for (var name in this) {
+        if (match = name.match(re)) {
+          var l = this[name];
+          this.removeEventListener(match[1], l, l.$);
+          delete this[name];
+        }
+      }
+    }
+    return i ? listener ? onAdd : onRemove : listener ? d3_noop : removeAll;
+  }
+  var d3_selection_onFilters = d3.map({
+    mouseenter: "mouseover",
+    mouseleave: "mouseout"
+  });
+  if (d3_document) {
+    d3_selection_onFilters.forEach(function(k) {
+      if ("on" + k in d3_document) d3_selection_onFilters.remove(k);
+    });
+  }
+  function d3_selection_onListener(listener, argumentz) {
+    return function(e) {
+      var o = d3.event;
+      d3.event = e;
+      argumentz[0] = this.__data__;
+      try {
+        listener.apply(this, argumentz);
+      } finally {
+        d3.event = o;
+      }
+    };
+  }
+  function d3_selection_onFilter(listener, argumentz) {
+    var l = d3_selection_onListener(listener, argumentz);
+    return function(e) {
+      var target = this, related = e.relatedTarget;
+      if (!related || related !== target && !(related.compareDocumentPosition(target) & 8)) {
+        l.call(target, e);
+      }
+    };
+  }
+  var d3_event_dragSelect, d3_event_dragId = 0;
+  function d3_event_dragSuppress(node) {
+    var name = ".dragsuppress-" + ++d3_event_dragId, click = "click" + name, w = d3.select(d3_window(node)).on("touchmove" + name, d3_eventPreventDefault).on("dragstart" + name, d3_eventPreventDefault).on("selectstart" + name, d3_eventPreventDefault);
+    if (d3_event_dragSelect == null) {
+      d3_event_dragSelect = "onselectstart" in node ? false : d3_vendorSymbol(node.style, "userSelect");
+    }
+    if (d3_event_dragSelect) {
+      var style = d3_documentElement(node).style, select = style[d3_event_dragSelect];
+      style[d3_event_dragSelect] = "none";
+    }
+    return function(suppressClick) {
+      w.on(name, null);
+      if (d3_event_dragSelect) style[d3_event_dragSelect] = select;
+      if (suppressClick) {
+        var off = function() {
+          w.on(click, null);
+        };
+        w.on(click, function() {
+          d3_eventPreventDefault();
+          off();
+        }, true);
+        setTimeout(off, 0);
+      }
+    };
+  }
+  d3.mouse = function(container) {
+    return d3_mousePoint(container, d3_eventSource());
+  };
+  var d3_mouse_bug44083 = this.navigator && /WebKit/.test(this.navigator.userAgent) ? -1 : 0;
+  function d3_mousePoint(container, e) {
+    if (e.changedTouches) e = e.changedTouches[0];
+    var svg = container.ownerSVGElement || container;
+    if (svg.createSVGPoint) {
+      var point = svg.createSVGPoint();
+      if (d3_mouse_bug44083 < 0) {
+        var window = d3_window(container);
+        if (window.scrollX || window.scrollY) {
+          svg = d3.select("body").append("svg").style({
+            position: "absolute",
+            top: 0,
+            left: 0,
+            margin: 0,
+            padding: 0,
+            border: "none"
+          }, "important");
+          var ctm = svg[0][0].getScreenCTM();
+          d3_mouse_bug44083 = !(ctm.f || ctm.e);
+          svg.remove();
+        }
+      }
+      if (d3_mouse_bug44083) point.x = e.pageX, point.y = e.pageY; else point.x = e.clientX, 
+      point.y = e.clientY;
+      point = point.matrixTransform(container.getScreenCTM().inverse());
+      return [ point.x, point.y ];
+    }
+    var rect = container.getBoundingClientRect();
+    return [ e.clientX - rect.left - container.clientLeft, e.clientY - rect.top - container.clientTop ];
+  }
+  d3.touch = function(container, touches, identifier) {
+    if (arguments.length < 3) identifier = touches, touches = d3_eventSource().changedTouches;
+    if (touches) for (var i = 0, n = touches.length, touch; i < n; ++i) {
+      if ((touch = touches[i]).identifier === identifier) {
+        return d3_mousePoint(container, touch);
+      }
+    }
+  };
+  d3.behavior.drag = function() {
+    var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null, mousedown = dragstart(d3_noop, d3.mouse, d3_window, "mousemove", "mouseup"), touchstart = dragstart(d3_behavior_dragTouchId, d3.touch, d3_identity, "touchmove", "touchend");
+    function drag() {
+      this.on("mousedown.drag", mousedown).on("touchstart.drag", touchstart);
+    }
+    function dragstart(id, position, subject, move, end) {
+      return function() {
+        var that = this, target = d3.event.target.correspondingElement || d3.event.target, parent = that.parentNode, dispatch = event.of(that, arguments), dragged = 0, dragId = id(), dragName = ".drag" + (dragId == null ? "" : "-" + dragId), dragOffset, dragSubject = d3.select(subject(target)).on(move + dragName, moved).on(end + dragName, ended), dragRestore = d3_event_dragSuppress(target), position0 = position(parent, dragId);
+        if (origin) {
+          dragOffset = origin.apply(that, arguments);
+          dragOffset = [ dragOffset.x - position0[0], dragOffset.y - position0[1] ];
+        } else {
+          dragOffset = [ 0, 0 ];
+        }
+        dispatch({
+          type: "dragstart"
+        });
+        function moved() {
+          var position1 = position(parent, dragId), dx, dy;
+          if (!position1) return;
+          dx = position1[0] - position0[0];
+          dy = position1[1] - position0[1];
+          dragged |= dx | dy;
+          position0 = position1;
+          dispatch({
+            type: "drag",
+            x: position1[0] + dragOffset[0],
+            y: position1[1] + dragOffset[1],
+            dx: dx,
+            dy: dy
+          });
+        }
+        function ended() {
+          if (!position(parent, dragId)) return;
+          dragSubject.on(move + dragName, null).on(end + dragName, null);
+          dragRestore(dragged);
+          dispatch({
+            type: "dragend"
+          });
+        }
+      };
+    }
+    drag.origin = function(x) {
+      if (!arguments.length) return origin;
+      origin = x;
+      return drag;
+    };
+    return d3.rebind(drag, event, "on");
+  };
+  function d3_behavior_dragTouchId() {
+    return d3.event.changedTouches[0].identifier;
+  }
+  d3.touches = function(container, touches) {
+    if (arguments.length < 2) touches = d3_eventSource().touches;
+    return touches ? d3_array(touches).map(function(touch) {
+      var point = d3_mousePoint(container, touch);
+      point.identifier = touch.identifier;
+      return point;
+    }) : [];
+  };
+  var ε = 1e-6, ε2 = ε * ε, π = Math.PI, τ = 2 * π, τε = τ - ε, halfπ = π / 2, d3_radians = π / 180, d3_degrees = 180 / π;
+  function d3_sgn(x) {
+    return x > 0 ? 1 : x < 0 ? -1 : 0;
+  }
+  function d3_cross2d(a, b, c) {
+    return (b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]);
+  }
+  function d3_acos(x) {
+    return x > 1 ? 0 : x < -1 ? π : Math.acos(x);
+  }
+  function d3_asin(x) {
+    return x > 1 ? halfπ : x < -1 ? -halfπ : Math.asin(x);
+  }
+  function d3_sinh(x) {
+    return ((x = Math.exp(x)) - 1 / x) / 2;
+  }
+  function d3_cosh(x) {
+    return ((x = Math.exp(x)) + 1 / x) / 2;
+  }
+  function d3_tanh(x) {
+    return ((x = Math.exp(2 * x)) - 1) / (x + 1);
+  }
+  function d3_haversin(x) {
+    return (x = Math.sin(x / 2)) * x;
+  }
+  var ρ = Math.SQRT2, ρ2 = 2, ρ4 = 4;
+  d3.interpolateZoom = function(p0, p1) {
+    var ux0 = p0[0], uy0 = p0[1], w0 = p0[2], ux1 = p1[0], uy1 = p1[1], w1 = p1[2], dx = ux1 - ux0, dy = uy1 - uy0, d2 = dx * dx + dy * dy, i, S;
+    if (d2 < ε2) {
+      S = Math.log(w1 / w0) / ρ;
+      i = function(t) {
+        return [ ux0 + t * dx, uy0 + t * dy, w0 * Math.exp(ρ * t * S) ];
+      };
+    } else {
+      var d1 = Math.sqrt(d2), b0 = (w1 * w1 - w0 * w0 + ρ4 * d2) / (2 * w0 * ρ2 * d1), b1 = (w1 * w1 - w0 * w0 - ρ4 * d2) / (2 * w1 * ρ2 * d1), r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0), r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1);
+      S = (r1 - r0) / ρ;
+      i = function(t) {
+        var s = t * S, coshr0 = d3_cosh(r0), u = w0 / (ρ2 * d1) * (coshr0 * d3_tanh(ρ * s + r0) - d3_sinh(r0));
+        return [ ux0 + u * dx, uy0 + u * dy, w0 * coshr0 / d3_cosh(ρ * s + r0) ];
+      };
+    }
+    i.duration = S * 1e3;
+    return i;
+  };
+  d3.behavior.zoom = function() {
+    var view = {
+      x: 0,
+      y: 0,
+      k: 1
+    }, translate0, center0, center, size = [ 960, 500 ], scaleExtent = d3_behavior_zoomInfinity, duration = 250, zooming = 0, mousedown = "mousedown.zoom", mousemove = "mousemove.zoom", mouseup = "mouseup.zoom", mousewheelTimer, touchstart = "touchstart.zoom", touchtime, event = d3_eventDispatch(zoom, "zoomstart", "zoom", "zoomend"), x0, x1, y0, y1;
+    if (!d3_behavior_zoomWheel) {
+      d3_behavior_zoomWheel = "onwheel" in d3_document ? (d3_behavior_zoomDelta = function() {
+        return -d3.event.deltaY * (d3.event.deltaMode ? 120 : 1);
+      }, "wheel") : "onmousewheel" in d3_document ? (d3_behavior_zoomDelta = function() {
+        return d3.event.wheelDelta;
+      }, "mousewheel") : (d3_behavior_zoomDelta = function() {
+        return -d3.event.detail;
+      }, "MozMousePixelScroll");
+    }
+    function zoom(g) {
+      g.on(mousedown, mousedowned).on(d3_behavior_zoomWheel + ".zoom", mousewheeled).on("dblclick.zoom", dblclicked).on(touchstart, touchstarted);
+    }
+    zoom.event = function(g) {
+      g.each(function() {
+        var dispatch = event.of(this, arguments), view1 = view;
+        if (d3_transitionInheritId) {
+          d3.select(this).transition().each("start.zoom", function() {
+            view = this.__chart__ || {
+              x: 0,
+              y: 0,
+              k: 1
+            };
+            zoomstarted(dispatch);
+          }).tween("zoom:zoom", function() {
+            var dx = size[0], dy = size[1], cx = center0 ? center0[0] : dx / 2, cy = center0 ? center0[1] : dy / 2, i = d3.interpolateZoom([ (cx - view.x) / view.k, (cy - view.y) / view.k, dx / view.k ], [ (cx - view1.x) / view1.k, (cy - view1.y) / view1.k, dx / view1.k ]);
+            return function(t) {
+              var l = i(t), k = dx / l[2];
+              this.__chart__ = view = {
+                x: cx - l[0] * k,
+                y: cy - l[1] * k,
+                k: k
+              };
+              zoomed(dispatch);
+            };
+          }).each("interrupt.zoom", function() {
+            zoomended(dispatch);
+          }).each("end.zoom", function() {
+            zoomended(dispatch);
+          });
+        } else {
+          this.__chart__ = view;
+          zoomstarted(dispatch);
+          zoomed(dispatch);
+          zoomended(dispatch);
+        }
+      });
+    };
+    zoom.translate = function(_) {
+      if (!arguments.length) return [ view.x, view.y ];
+      view = {
+        x: +_[0],
+        y: +_[1],
+        k: view.k
+      };
+      rescale();
+      return zoom;
+    };
+    zoom.scale = function(_) {
+      if (!arguments.length) return view.k;
+      view = {
+        x: view.x,
+        y: view.y,
+        k: null
+      };
+      scaleTo(+_);
+      rescale();
+      return zoom;
+    };
+    zoom.scaleExtent = function(_) {
+      if (!arguments.length) return scaleExtent;
+      scaleExtent = _ == null ? d3_behavior_zoomInfinity : [ +_[0], +_[1] ];
+      return zoom;
+    };
+    zoom.center = function(_) {
+      if (!arguments.length) return center;
+      center = _ && [ +_[0], +_[1] ];
+      return zoom;
+    };
+    zoom.size = function(_) {
+      if (!arguments.length) return size;
+      size = _ && [ +_[0], +_[1] ];
+      return zoom;
+    };
+    zoom.duration = function(_) {
+      if (!arguments.length) return duration;
+      duration = +_;
+      return zoom;
+    };
+    zoom.x = function(z) {
+      if (!arguments.length) return x1;
+      x1 = z;
+      x0 = z.copy();
+      view = {
+        x: 0,
+        y: 0,
+        k: 1
+      };
+      return zoom;
+    };
+    zoom.y = function(z) {
+      if (!arguments.length) return y1;
+      y1 = z;
+      y0 = z.copy();
+      view = {
+        x: 0,
+        y: 0,
+        k: 1
+      };
+      return zoom;
+    };
+    function location(p) {
+      return [ (p[0] - view.x) / view.k, (p[1] - view.y) / view.k ];
+    }
+    function point(l) {
+      return [ l[0] * view.k + view.x, l[1] * view.k + view.y ];
+    }
+    function scaleTo(s) {
+      view.k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s));
+    }
+    function translateTo(p, l) {
+      l = point(l);
+      view.x += p[0] - l[0];
+      view.y += p[1] - l[1];
+    }
+    function zoomTo(that, p, l, k) {
+      that.__chart__ = {
+        x: view.x,
+        y: view.y,
+        k: view.k
+      };
+      scaleTo(Math.pow(2, k));
+      translateTo(center0 = p, l);
+      that = d3.select(that);
+      if (duration > 0) that = that.transition().duration(duration);
+      that.call(zoom.event);
+    }
+    function rescale() {
+      if (x1) x1.domain(x0.range().map(function(x) {
+        return (x - view.x) / view.k;
+      }).map(x0.invert));
+      if (y1) y1.domain(y0.range().map(function(y) {
+        return (y - view.y) / view.k;
+      }).map(y0.invert));
+    }
+    function zoomstarted(dispatch) {
+      if (!zooming++) dispatch({
+        type: "zoomstart"
+      });
+    }
+    function zoomed(dispatch) {
+      rescale();
+      dispatch({
+        type: "zoom",
+        scale: view.k,
+        translate: [ view.x, view.y ]
+      });
+    }
+    function zoomended(dispatch) {
+      if (!--zooming) dispatch({
+        type: "zoomend"
+      }), center0 = null;
+    }
+    function mousedowned() {
+      var that = this, dispatch = event.of(that, arguments), dragged = 0, subject = d3.select(d3_window(that)).on(mousemove, moved).on(mouseup, ended), location0 = location(d3.mouse(that)), dragRestore = d3_event_dragSuppress(that);
+      d3_selection_interrupt.call(that);
+      zoomstarted(dispatch);
+      function moved() {
+        dragged = 1;
+        translateTo(d3.mouse(that), location0);
+        zoomed(dispatch);
+      }
+      function ended() {
+        subject.on(mousemove, null).on(mouseup, null);
+        dragRestore(dragged);
+        zoomended(dispatch);
+      }
+    }
+    function touchstarted() {
+      var that = this, dispatch = event.of(that, arguments), locations0 = {}, distance0 = 0, scale0, zoomName = ".zoom-" + d3.event.changedTouches[0].identifier, touchmove = "touchmove" + zoomName, touchend = "touchend" + zoomName, targets = [], subject = d3.select(that), dragRestore = d3_event_dragSuppress(that);
+      started();
+      zoomstarted(dispatch);
+      subject.on(mousedown, null).on(touchstart, started);
+      function relocate() {
+        var touches = d3.touches(that);
+        scale0 = view.k;
+        touches.forEach(function(t) {
+          if (t.identifier in locations0) locations0[t.identifier] = location(t);
+        });
+        return touches;
+      }
+      function started() {
+        var target = d3.event.target;
+        d3.select(target).on(touchmove, moved).on(touchend, ended);
+        targets.push(target);
+        var changed = d3.event.changedTouches;
+        for (var i = 0, n = changed.length; i < n; ++i) {
+          locations0[changed[i].identifier] = null;
+        }
+        var touches = relocate(), now = Date.now();
+        if (touches.length === 1) {
+          if (now - touchtime < 500) {
+            var p = touches[0];
+            zoomTo(that, p, locations0[p.identifier], Math.floor(Math.log(view.k) / Math.LN2) + 1);
+            d3_eventPreventDefault();
+          }
+          touchtime = now;
+        } else if (touches.length > 1) {
+          var p = touches[0], q = touches[1], dx = p[0] - q[0], dy = p[1] - q[1];
+          distance0 = dx * dx + dy * dy;
+        }
+      }
+      function moved() {
+        var touches = d3.touches(that), p0, l0, p1, l1;
+        d3_selection_interrupt.call(that);
+        for (var i = 0, n = touches.length; i < n; ++i, l1 = null) {
+          p1 = touches[i];
+          if (l1 = locations0[p1.identifier]) {
+            if (l0) break;
+            p0 = p1, l0 = l1;
+          }
+        }
+        if (l1) {
+          var distance1 = (distance1 = p1[0] - p0[0]) * distance1 + (distance1 = p1[1] - p0[1]) * distance1, scale1 = distance0 && Math.sqrt(distance1 / distance0);
+          p0 = [ (p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2 ];
+          l0 = [ (l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2 ];
+          scaleTo(scale1 * scale0);
+        }
+        touchtime = null;
+        translateTo(p0, l0);
+        zoomed(dispatch);
+      }
+      function ended() {
+        if (d3.event.touches.length) {
+          var changed = d3.event.changedTouches;
+          for (var i = 0, n = changed.length; i < n; ++i) {
+            delete locations0[changed[i].identifier];
+          }
+          for (var identifier in locations0) {
+            return void relocate();
+          }
+        }
+        d3.selectAll(targets).on(zoomName, null);
+        subject.on(mousedown, mousedowned).on(touchstart, touchstarted);
+        dragRestore();
+        zoomended(dispatch);
+      }
+    }
+    function mousewheeled() {
+      var dispatch = event.of(this, arguments);
+      if (mousewheelTimer) clearTimeout(mousewheelTimer); else d3_selection_interrupt.call(this), 
+      translate0 = location(center0 = center || d3.mouse(this)), zoomstarted(dispatch);
+      mousewheelTimer = setTimeout(function() {
+        mousewheelTimer = null;
+        zoomended(dispatch);
+      }, 50);
+      d3_eventPreventDefault();
+      scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * view.k);
+      translateTo(center0, translate0);
+      zoomed(dispatch);
+    }
+    function dblclicked() {
+      var p = d3.mouse(this), k = Math.log(view.k) / Math.LN2;
+      zoomTo(this, p, location(p), d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1);
+    }
+    return d3.rebind(zoom, event, "on");
+  };
+  var d3_behavior_zoomInfinity = [ 0, Infinity ], d3_behavior_zoomDelta, d3_behavior_zoomWheel;
+  d3.color = d3_color;
+  function d3_color() {}
+  d3_color.prototype.toString = function() {
+    return this.rgb() + "";
+  };
+  d3.hsl = d3_hsl;
+  function d3_hsl(h, s, l) {
+    return this instanceof d3_hsl ? void (this.h = +h, this.s = +s, this.l = +l) : arguments.length < 2 ? h instanceof d3_hsl ? new d3_hsl(h.h, h.s, h.l) : d3_rgb_parse("" + h, d3_rgb_hsl, d3_hsl) : new d3_hsl(h, s, l);
+  }
+  var d3_hslPrototype = d3_hsl.prototype = new d3_color();
+  d3_hslPrototype.brighter = function(k) {
+    k = Math.pow(.7, arguments.length ? k : 1);
+    return new d3_hsl(this.h, this.s, this.l / k);
+  };
+  d3_hslPrototype.darker = function(k) {
+    k = Math.pow(.7, arguments.length ? k : 1);
+    return new d3_hsl(this.h, this.s, k * this.l);
+  };
+  d3_hslPrototype.rgb = function() {
+    return d3_hsl_rgb(this.h, this.s, this.l);
+  };
+  function d3_hsl_rgb(h, s, l) {
+    var m1, m2;
+    h = isNaN(h) ? 0 : (h %= 360) < 0 ? h + 360 : h;
+    s = isNaN(s) ? 0 : s < 0 ? 0 : s > 1 ? 1 : s;
+    l = l < 0 ? 0 : l > 1 ? 1 : l;
+    m2 = l <= .5 ? l * (1 + s) : l + s - l * s;
+    m1 = 2 * l - m2;
+    function v(h) {
+      if (h > 360) h -= 360; else if (h < 0) h += 360;
+      if (h < 60) return m1 + (m2 - m1) * h / 60;
+      if (h < 180) return m2;
+      if (h < 240) return m1 + (m2 - m1) * (240 - h) / 60;
+      return m1;
+    }
+    function vv(h) {
+      return Math.round(v(h) * 255);
+    }
+    return new d3_rgb(vv(h + 120), vv(h), vv(h - 120));
+  }
+  d3.hcl = d3_hcl;
+  function d3_hcl(h, c, l) {
+    return this instanceof d3_hcl ? void (this.h = +h, this.c = +c, this.l = +l) : arguments.length < 2 ? h instanceof d3_hcl ? new d3_hcl(h.h, h.c, h.l) : h instanceof d3_lab ? d3_lab_hcl(h.l, h.a, h.b) : d3_lab_hcl((h = d3_rgb_lab((h = d3.rgb(h)).r, h.g, h.b)).l, h.a, h.b) : new d3_hcl(h, c, l);
+  }
+  var d3_hclPrototype = d3_hcl.prototype = new d3_color();
+  d3_hclPrototype.brighter = function(k) {
+    return new d3_hcl(this.h, this.c, Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)));
+  };
+  d3_hclPrototype.darker = function(k) {
+    return new d3_hcl(this.h, this.c, Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)));
+  };
+  d3_hclPrototype.rgb = function() {
+    return d3_hcl_lab(this.h, this.c, this.l).rgb();
+  };
+  function d3_hcl_lab(h, c, l) {
+    if (isNaN(h)) h = 0;
+    if (isNaN(c)) c = 0;
+    return new d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c);
+  }
+  d3.lab = d3_lab;
+  function d3_lab(l, a, b) {
+    return this instanceof d3_lab ? void (this.l = +l, this.a = +a, this.b = +b) : arguments.length < 2 ? l instanceof d3_lab ? new d3_lab(l.l, l.a, l.b) : l instanceof d3_hcl ? d3_hcl_lab(l.h, l.c, l.l) : d3_rgb_lab((l = d3_rgb(l)).r, l.g, l.b) : new d3_lab(l, a, b);
+  }
+  var d3_lab_K = 18;
+  var d3_lab_X = .95047, d3_lab_Y = 1, d3_lab_Z = 1.08883;
+  var d3_labPrototype = d3_lab.prototype = new d3_color();
+  d3_labPrototype.brighter = function(k) {
+    return new d3_lab(Math.min(100, this.l + d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
+  };
+  d3_labPrototype.darker = function(k) {
+    return new d3_lab(Math.max(0, this.l - d3_lab_K * (arguments.length ? k : 1)), this.a, this.b);
+  };
+  d3_labPrototype.rgb = function() {
+    return d3_lab_rgb(this.l, this.a, this.b);
+  };
+  function d3_lab_rgb(l, a, b) {
+    var y = (l + 16) / 116, x = y + a / 500, z = y - b / 200;
+    x = d3_lab_xyz(x) * d3_lab_X;
+    y = d3_lab_xyz(y) * d3_lab_Y;
+    z = d3_lab_xyz(z) * d3_lab_Z;
+    return new d3_rgb(d3_xyz_rgb(3.2404542 * x - 1.5371385 * y - .4985314 * z), d3_xyz_rgb(-.969266 * x + 1.8760108 * y + .041556 * z), d3_xyz_rgb(.0556434 * x - .2040259 * y + 1.0572252 * z));
+  }
+  function d3_lab_hcl(l, a, b) {
+    return l > 0 ? new d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l) : new d3_hcl(NaN, NaN, l);
+  }
+  function d3_lab_xyz(x) {
+    return x > .206893034 ? x * x * x : (x - 4 / 29) / 7.787037;
+  }
+  function d3_xyz_lab(x) {
+    return x > .008856 ? Math.pow(x, 1 / 3) : 7.787037 * x + 4 / 29;
+  }
+  function d3_xyz_rgb(r) {
+    return Math.round(255 * (r <= .00304 ? 12.92 * r : 1.055 * Math.pow(r, 1 / 2.4) - .055));
+  }
+  d3.rgb = d3_rgb;
+  function d3_rgb(r, g, b) {
+    return this instanceof d3_rgb ? void (this.r = ~~r, this.g = ~~g, this.b = ~~b) : arguments.length < 2 ? r instanceof d3_rgb ? new d3_rgb(r.r, r.g, r.b) : d3_rgb_parse("" + r, d3_rgb, d3_hsl_rgb) : new d3_rgb(r, g, b);
+  }
+  function d3_rgbNumber(value) {
+    return new d3_rgb(value >> 16, value >> 8 & 255, value & 255);
+  }
+  function d3_rgbString(value) {
+    return d3_rgbNumber(value) + "";
+  }
+  var d3_rgbPrototype = d3_rgb.prototype = new d3_color();
+  d3_rgbPrototype.brighter = function(k) {
+    k = Math.pow(.7, arguments.length ? k : 1);
+    var r = this.r, g = this.g, b = this.b, i = 30;
+    if (!r && !g && !b) return new d3_rgb(i, i, i);
+    if (r && r < i) r = i;
+    if (g && g < i) g = i;
+    if (b && b < i) b = i;
+    return new d3_rgb(Math.min(255, r / k), Math.min(255, g / k), Math.min(255, b / k));
+  };
+  d3_rgbPrototype.darker = function(k) {
+    k = Math.pow(.7, arguments.length ? k : 1);
+    return new d3_rgb(k * this.r, k * this.g, k * this.b);
+  };
+  d3_rgbPrototype.hsl = function() {
+    return d3_rgb_hsl(this.r, this.g, this.b);
+  };
+  d3_rgbPrototype.toString = function() {
+    return "#" + d3_rgb_hex(this.r) + d3_rgb_hex(this.g) + d3_rgb_hex(this.b);
+  };
+  function d3_rgb_hex(v) {
+    return v < 16 ? "0" + Math.max(0, v).toString(16) : Math.min(255, v).toString(16);
+  }
+  function d3_rgb_parse(format, rgb, hsl) {
+    var r = 0, g = 0, b = 0, m1, m2, color;
+    m1 = /([a-z]+)\((.*)\)/.exec(format = format.toLowerCase());
+    if (m1) {
+      m2 = m1[2].split(",");
+      switch (m1[1]) {
+       case "hsl":
+        {
+          return hsl(parseFloat(m2[0]), parseFloat(m2[1]) / 100, parseFloat(m2[2]) / 100);
+        }
+
+       case "rgb":
+        {
+          return rgb(d3_rgb_parseNumber(m2[0]), d3_rgb_parseNumber(m2[1]), d3_rgb_parseNumber(m2[2]));
+        }
+      }
+    }
+    if (color = d3_rgb_names.get(format)) {
+      return rgb(color.r, color.g, color.b);
+    }
+    if (format != null && format.charAt(0) === "#" && !isNaN(color = parseInt(format.slice(1), 16))) {
+      if (format.length === 4) {
+        r = (color & 3840) >> 4;
+        r = r >> 4 | r;
+        g = color & 240;
+        g = g >> 4 | g;
+        b = color & 15;
+        b = b << 4 | b;
+      } else if (format.length === 7) {
+        r = (color & 16711680) >> 16;
+        g = (color & 65280) >> 8;
+        b = color & 255;
+      }
+    }
+    return rgb(r, g, b);
+  }
+  function d3_rgb_hsl(r, g, b) {
+    var min = Math.min(r /= 255, g /= 255, b /= 255), max = Math.max(r, g, b), d = max - min, h, s, l = (max + min) / 2;
+    if (d) {
+      s = l < .5 ? d / (max + min) : d / (2 - max - min);
+      if (r == max) h = (g - b) / d + (g < b ? 6 : 0); else if (g == max) h = (b - r) / d + 2; else h = (r - g) / d + 4;
+      h *= 60;
+    } else {
+      h = NaN;
+      s = l > 0 && l < 1 ? 0 : h;
+    }
+    return new d3_hsl(h, s, l);
+  }
+  function d3_rgb_lab(r, g, b) {
+    r = d3_rgb_xyz(r);
+    g = d3_rgb_xyz(g);
+    b = d3_rgb_xyz(b);
+    var x = d3_xyz_lab((.4124564 * r + .3575761 * g + .1804375 * b) / d3_lab_X), y = d3_xyz_lab((.2126729 * r + .7151522 * g + .072175 * b) / d3_lab_Y), z = d3_xyz_lab((.0193339 * r + .119192 * g + .9503041 * b) / d3_lab_Z);
+    return d3_lab(116 * y - 16, 500 * (x - y), 200 * (y - z));
+  }
+  function d3_rgb_xyz(r) {
+    return (r /= 255) <= .04045 ? r / 12.92 : Math.pow((r + .055) / 1.055, 2.4);
+  }
+  function d3_rgb_parseNumber(c) {
+    var f = parseFloat(c);
+    return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f;
+  }
+  var d3_rgb_names = d3.map({
+    aliceblue: 15792383,
+    antiquewhite: 16444375,
+    aqua: 65535,
+    aquamarine: 8388564,
+    azure: 15794175,
+    beige: 16119260,
+    bisque: 16770244,
+    black: 0,
+    blanchedalmond: 16772045,
+    blue: 255,
+    blueviolet: 9055202,
+    brown: 10824234,
+    burlywood: 14596231,
+    cadetblue: 6266528,
+    chartreuse: 8388352,
+    chocolate: 13789470,
+    coral: 16744272,
+    cornflowerblue: 6591981,
+    cornsilk: 16775388,
+    crimson: 14423100,
+    cyan: 65535,
+    darkblue: 139,
+    darkcyan: 35723,
+    darkgoldenrod: 12092939,
+    darkgray: 11119017,
+    darkgreen: 25600,
+    darkgrey: 11119017,
+    darkkhaki: 12433259,
+    darkmagenta: 9109643,
+    darkolivegreen: 5597999,
+    darkorange: 16747520,
+    darkorchid: 10040012,
+    darkred: 9109504,
+    darksalmon: 15308410,
+    darkseagreen: 9419919,
+    darkslateblue: 4734347,
+    darkslategray: 3100495,
+    darkslategrey: 3100495,
+    darkturquoise: 52945,
+    darkviolet: 9699539,
+    deeppink: 16716947,
+    deepskyblue: 49151,
+    dimgray: 6908265,
+    dimgrey: 6908265,
+    dodgerblue: 2003199,
+    firebrick: 11674146,
+    floralwhite: 16775920,
+    forestgreen: 2263842,
+    fuchsia: 16711935,
+    gainsboro: 14474460,
+    ghostwhite: 16316671,
+    gold: 16766720,
+    goldenrod: 14329120,
+    gray: 8421504,
+    green: 32768,
+    greenyellow: 11403055,
+    grey: 8421504,
+    honeydew: 15794160,
+    hotpink: 16738740,
+    indianred: 13458524,
+    indigo: 4915330,
+    ivory: 16777200,
+    khaki: 15787660,
+    lavender: 15132410,
+    lavenderblush: 16773365,
+    lawngreen: 8190976,
+    lemonchiffon: 16775885,
+    lightblue: 11393254,
+    lightcoral: 15761536,
+    lightcyan: 14745599,
+    lightgoldenrodyellow: 16448210,
+    lightgray: 13882323,
+    lightgreen: 9498256,
+    lightgrey: 13882323,
+    lightpink: 16758465,
+    lightsalmon: 16752762,
+    lightseagreen: 2142890,
+    lightskyblue: 8900346,
+    lightslategray: 7833753,
+    lightslategrey: 7833753,
+    lightsteelblue: 11584734,
+    lightyellow: 16777184,
+    lime: 65280,
+    limegreen: 3329330,
+    linen: 16445670,
+    magenta: 16711935,
+    maroon: 8388608,
+    mediumaquamarine: 6737322,
+    mediumblue: 205,
+    mediumorchid: 12211667,
+    mediumpurple: 9662683,
+    mediumseagreen: 3978097,
+    mediumslateblue: 8087790,
+    mediumspringgreen: 64154,
+    mediumturquoise: 4772300,
+    mediumvioletred: 13047173,
+    midnightblue: 1644912,
+    mintcream: 16121850,
+    mistyrose: 16770273,
+    moccasin: 16770229,
+    navajowhite: 16768685,
+    navy: 128,
+    oldlace: 16643558,
+    olive: 8421376,
+    olivedrab: 7048739,
+    orange: 16753920,
+    orangered: 16729344,
+    orchid: 14315734,
+    palegoldenrod: 15657130,
+    palegreen: 10025880,
+    paleturquoise: 11529966,
+    palevioletred: 14381203,
+    papayawhip: 16773077,
+    peachpuff: 16767673,
+    peru: 13468991,
+    pink: 16761035,
+    plum: 14524637,
+    powderblue: 11591910,
+    purple: 8388736,
+    rebeccapurple: 6697881,
+    red: 16711680,
+    rosybrown: 12357519,
+    royalblue: 4286945,
+    saddlebrown: 9127187,
+    salmon: 16416882,
+    sandybrown: 16032864,
+    seagreen: 3050327,
+    seashell: 16774638,
+    sienna: 10506797,
+    silver: 12632256,
+    skyblue: 8900331,
+    slateblue: 6970061,
+    slategray: 7372944,
+    slategrey: 7372944,
+    snow: 16775930,
+    springgreen: 65407,
+    steelblue: 4620980,
+    tan: 13808780,
+    teal: 32896,
+    thistle: 14204888,
+    tomato: 16737095,
+    turquoise: 4251856,
+    violet: 15631086,
+    wheat: 16113331,
+    white: 16777215,
+    whitesmoke: 16119285,
+    yellow: 16776960,
+    yellowgreen: 10145074
+  });
+  d3_rgb_names.forEach(function(key, value) {
+    d3_rgb_names.set(key, d3_rgbNumber(value));
+  });
+  function d3_functor(v) {
+    return typeof v === "function" ? v : function() {
+      return v;
+    };
+  }
+  d3.functor = d3_functor;
+  d3.xhr = d3_xhrType(d3_identity);
+  function d3_xhrType(response) {
+    return function(url, mimeType, callback) {
+      if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType, 
+      mimeType = null;
+      return d3_xhr(url, mimeType, response, callback);
+    };
+  }
+  function d3_xhr(url, mimeType, response, callback) {
+    var xhr = {}, dispatch = d3.dispatch("beforesend", "progress", "load", "error"), headers = {}, request = new XMLHttpRequest(), responseType = null;
+    if (this.XDomainRequest && !("withCredentials" in request) && /^(http(s)?:)?\/\//.test(url)) request = new XDomainRequest();
+    "onload" in request ? request.onload = request.onerror = respond : request.onreadystatechange = function() {
+      request.readyState > 3 && respond();
+    };
+    function respond() {
+      var status = request.status, result;
+      if (!status && d3_xhrHasResponse(request) || status >= 200 && status < 300 || status === 304) {
+        try {
+          result = response.call(xhr, request);
+        } catch (e) {
+          dispatch.error.call(xhr, e);
+          return;
+        }
+        dispatch.load.call(xhr, result);
+      } else {
+        dispatch.error.call(xhr, request);
+      }
+    }
+    request.onprogress = function(event) {
+      var o = d3.event;
+      d3.event = event;
+      try {
+        dispatch.progress.call(xhr, request);
+      } finally {
+        d3.event = o;
+      }
+    };
+    xhr.header = function(name, value) {
+      name = (name + "").toLowerCase();
+      if (arguments.length < 2) return headers[name];
+      if (value == null) delete headers[name]; else headers[name] = value + "";
+      return xhr;
+    };
+    xhr.mimeType = function(value) {
+      if (!arguments.length) return mimeType;
+      mimeType = value == null ? null : value + "";
+      return xhr;
+    };
+    xhr.responseType = function(value) {
+      if (!arguments.length) return responseType;
+      responseType = value;
+      return xhr;
+    };
+    xhr.response = function(value) {
+      response = value;
+      return xhr;
+    };
+    [ "get", "post" ].forEach(function(method) {
+      xhr[method] = function() {
+        return xhr.send.apply(xhr, [ method ].concat(d3_array(arguments)));
+      };
+    });
+    xhr.send = function(method, data, callback) {
+      if (arguments.length === 2 && typeof data === "function") callback = data, data = null;
+      request.open(method, url, true);
+      if (mimeType != null && !("accept" in headers)) headers["accept"] = mimeType + ",*/*";
+      if (request.setRequestHeader) for (var name in headers) request.setRequestHeader(name, headers[name]);
+      if (mimeType != null && request.overrideMimeType) request.overrideMimeType(mimeType);
+      if (responseType != null) request.responseType = responseType;
+      if (callback != null) xhr.on("error", callback).on("load", function(request) {
+        callback(null, request);
+      });
+      dispatch.beforesend.call(xhr, request);
+      request.send(data == null ? null : data);
+      return xhr;
+    };
+    xhr.abort = function() {
+      request.abort();
+      return xhr;
+    };
+    d3.rebind(xhr, dispatch, "on");
+    return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback));
+  }
+  function d3_xhr_fixCallback(callback) {
+    return callback.length === 1 ? function(error, request) {
+      callback(error == null ? request : null);
+    } : callback;
+  }
+  function d3_xhrHasResponse(request) {
+    var type = request.responseType;
+    return type && type !== "text" ? request.response : request.responseText;
+  }
+  d3.dsv = function(delimiter, mimeType) {
+    var reFormat = new RegExp('["' + delimiter + "\n]"), delimiterCode = delimiter.charCodeAt(0);
+    function dsv(url, row, callback) {
+      if (arguments.length < 3) callback = row, row = null;
+      var xhr = d3_xhr(url, mimeType, row == null ? response : typedResponse(row), callback);
+      xhr.row = function(_) {
+        return arguments.length ? xhr.response((row = _) == null ? response : typedResponse(_)) : row;
+      };
+      return xhr;
+    }
+    function response(request) {
+      return dsv.parse(request.responseText);
+    }
+    function typedResponse(f) {
+      return function(request) {
+        return dsv.parse(request.responseText, f);
+      };
+    }
+    dsv.parse = function(text, f) {
+      var o;
+      return dsv.parseRows(text, function(row, i) {
+        if (o) return o(row, i - 1);
+        var a = new Function("d", "return {" + row.map(function(name, i) {
+          return JSON.stringify(name) + ": d[" + i + "]";
+        }).join(",") + "}");
+        o = f ? function(row, i) {
+          return f(a(row), i);
+        } : a;
+      });
+    };
+    dsv.parseRows = function(text, f) {
+      var EOL = {}, EOF = {}, rows = [], N = text.length, I = 0, n = 0, t, eol;
+      function token() {
+        if (I >= N) return EOF;
+        if (eol) return eol = false, EOL;
+        var j = I;
+        if (text.charCodeAt(j) === 34) {
+          var i = j;
+          while (i++ < N) {
+            if (text.charCodeAt(i) === 34) {
+              if (text.charCodeAt(i + 1) !== 34) break;
+              ++i;
+            }
+          }
+          I = i + 2;
+          var c = text.charCodeAt(i + 1);
+          if (c === 13) {
+            eol = true;
+            if (text.charCodeAt(i + 2) === 10) ++I;
+          } else if (c === 10) {
+            eol = true;
+          }
+          return text.slice(j + 1, i).replace(/""/g, '"');
+        }
+        while (I < N) {
+          var c = text.charCodeAt(I++), k = 1;
+          if (c === 10) eol = true; else if (c === 13) {
+            eol = true;
+            if (text.charCodeAt(I) === 10) ++I, ++k;
+          } else if (c !== delimiterCode) continue;
+          return text.slice(j, I - k);
+        }
+        return text.slice(j);
+      }
+      while ((t = token()) !== EOF) {
+        var a = [];
+        while (t !== EOL && t !== EOF) {
+          a.push(t);
+          t = token();
+        }
+        if (f && (a = f(a, n++)) == null) continue;
+        rows.push(a);
+      }
+      return rows;
+    };
+    dsv.format = function(rows) {
+      if (Array.isArray(rows[0])) return dsv.formatRows(rows);
+      var fieldSet = new d3_Set(), fields = [];
+      rows.forEach(function(row) {
+        for (var field in row) {
+          if (!fieldSet.has(field)) {
+            fields.push(fieldSet.add(field));
+          }
+        }
+      });
+      return [ fields.map(formatValue).join(delimiter) ].concat(rows.map(function(row) {
+        return fields.map(function(field) {
+          return formatValue(row[field]);
+        }).join(delimiter);
+      })).join("\n");
+    };
+    dsv.formatRows = function(rows) {
+      return rows.map(formatRow).join("\n");
+    };
+    function formatRow(row) {
+      return row.map(formatValue).join(delimiter);
+    }
+    function formatValue(text) {
+      return reFormat.test(text) ? '"' + text.replace(/\"/g, '""') + '"' : text;
+    }
+    return dsv;
+  };
+  d3.csv = d3.dsv(",", "text/csv");
+  d3.tsv = d3.dsv("	", "text/tab-separated-values");
+  var d3_timer_queueHead, d3_timer_queueTail, d3_timer_interval, d3_timer_timeout, d3_timer_frame = this[d3_vendorSymbol(this, "requestAnimationFrame")] || function(callback) {
+    setTimeout(callback, 17);
+  };
+  d3.timer = function() {
+    d3_timer.apply(this, arguments);
+  };
+  function d3_timer(callback, delay, then) {
+    var n = arguments.length;
+    if (n < 2) delay = 0;
+    if (n < 3) then = Date.now();
+    var time = then + delay, timer = {
+      c: callback,
+      t: time,
+      n: null
+    };
+    if (d3_timer_queueTail) d3_timer_queueTail.n = timer; else d3_timer_queueHead = timer;
+    d3_timer_queueTail = timer;
+    if (!d3_timer_interval) {
+      d3_timer_timeout = clearTimeout(d3_timer_timeout);
+      d3_timer_interval = 1;
+      d3_timer_frame(d3_timer_step);
+    }
+    return timer;
+  }
+  function d3_timer_step() {
+    var now = d3_timer_mark(), delay = d3_timer_sweep() - now;
+    if (delay > 24) {
+      if (isFinite(delay)) {
+        clearTimeout(d3_timer_timeout);
+        d3_timer_timeout = setTimeout(d3_timer_step, delay);
+      }
+      d3_timer_interval = 0;
+    } else {
+      d3_timer_interval = 1;
+      d3_timer_frame(d3_timer_step);
+    }
+  }
+  d3.timer.flush = function() {
+    d3_timer_mark();
+    d3_timer_sweep();
+  };
+  function d3_timer_mark() {
+    var now = Date.now(), timer = d3_timer_queueHead;
+    while (timer) {
+      if (now >= timer.t && timer.c(now - timer.t)) timer.c = null;
+      timer = timer.n;
+    }
+    return now;
+  }
+  function d3_timer_sweep() {
+    var t0, t1 = d3_timer_queueHead, time = Infinity;
+    while (t1) {
+      if (t1.c) {
+        if (t1.t < time) time = t1.t;
+        t1 = (t0 = t1).n;
+      } else {
+        t1 = t0 ? t0.n = t1.n : d3_timer_queueHead = t1.n;
+      }
+    }
+    d3_timer_queueTail = t0;
+    return time;
+  }
+  function d3_format_precision(x, p) {
+    return p - (x ? Math.ceil(Math.log(x) / Math.LN10) : 1);
+  }
+  d3.round = function(x, n) {
+    return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x);
+  };
+  var d3_formatPrefixes = [ "y", "z", "a", "f", "p", "n", "µ", "m", "", "k", "M", "G", "T", "P", "E", "Z", "Y" ].map(d3_formatPrefix);
+  d3.formatPrefix = function(value, precision) {
+    var i = 0;
+    if (value = +value) {
+      if (value < 0) value *= -1;
+      if (precision) value = d3.round(value, d3_format_precision(value, precision));
+      i = 1 + Math.floor(1e-12 + Math.log(value) / Math.LN10);
+      i = Math.max(-24, Math.min(24, Math.floor((i - 1) / 3) * 3));
+    }
+    return d3_formatPrefixes[8 + i / 3];
+  };
+  function d3_formatPrefix(d, i) {
+    var k = Math.pow(10, abs(8 - i) * 3);
+    return {
+      scale: i > 8 ? function(d) {
+        return d / k;
+      } : function(d) {
+        return d * k;
+      },
+      symbol: d
+    };
+  }
+  function d3_locale_numberFormat(locale) {
+    var locale_decimal = locale.decimal, locale_thousands = locale.thousands, locale_grouping = locale.grouping, locale_currency = locale.currency, formatGroup = locale_grouping && locale_thousands ? function(value, width) {
+      var i = value.length, t = [], j = 0, g = locale_grouping[0], length = 0;
+      while (i > 0 && g > 0) {
+        if (length + g + 1 > width) g = Math.max(1, width - length);
+        t.push(value.substring(i -= g, i + g));
+        if ((length += g + 1) > width) break;
+        g = locale_grouping[j = (j + 1) % locale_grouping.length];
+      }
+      return t.reverse().join(locale_thousands);
+    } : d3_identity;
+    return function(specifier) {
+      var match = d3_format_re.exec(specifier), fill = match[1] || " ", align = match[2] || ">", sign = match[3] || "-", symbol = match[4] || "", zfill = match[5], width = +match[6], comma = match[7], precision = match[8], type = match[9], scale = 1, prefix = "", suffix = "", integer = false, exponent = true;
+      if (precision) precision = +precision.substring(1);
+      if (zfill || fill === "0" && align === "=") {
+        zfill = fill = "0";
+        align = "=";
+      }
+      switch (type) {
+       case "n":
+        comma = true;
+        type = "g";
+        break;
+
+       case "%":
+        scale = 100;
+        suffix = "%";
+        type = "f";
+        break;
+
+       case "p":
+        scale = 100;
+        suffix = "%";
+        type = "r";
+        break;
+
+       case "b":
+       case "o":
+       case "x":
+       case "X":
+        if (symbol === "#") prefix = "0" + type.toLowerCase();
+
+       case "c":
+        exponent = false;
+
+       case "d":
+        integer = true;
+        precision = 0;
+        break;
+
+       case "s":
+        scale = -1;
+        type = "r";
+        break;
+      }
+      if (symbol === "$") prefix = locale_currency[0], suffix = locale_currency[1];
+      if (type == "r" && !precision) type = "g";
+      if (precision != null) {
+        if (type == "g") precision = Math.max(1, Math.min(21, precision)); else if (type == "e" || type == "f") precision = Math.max(0, Math.min(20, precision));
+      }
+      type = d3_format_types.get(type) || d3_format_typeDefault;
+      var zcomma = zfill && comma;
+      return function(value) {
+        var fullSuffix = suffix;
+        if (integer && value % 1) return "";
+        var negative = value < 0 || value === 0 && 1 / value < 0 ? (value = -value, "-") : sign === "-" ? "" : sign;
+        if (scale < 0) {
+          var unit = d3.formatPrefix(value, precision);
+          value = unit.scale(value);
+          fullSuffix = unit.symbol + suffix;
+        } else {
+          value *= scale;
+        }
+        value = type(value, precision);
+        var i = value.lastIndexOf("."), before, after;
+        if (i < 0) {
+          var j = exponent ? value.lastIndexOf("e") : -1;
+          if (j < 0) before = value, after = ""; else before = value.substring(0, j), after = value.substring(j);
+        } else {
+          before = value.substring(0, i);
+          after = locale_decimal + value.substring(i + 1);
+        }
+        if (!zfill && comma) before = formatGroup(before, Infinity);
+        var length = prefix.length + before.length + after.length + (zcomma ? 0 : negative.length), padding = length < width ? new Array(length = width - length + 1).join(fill) : "";
+        if (zcomma) before = formatGroup(padding + before, padding.length ? width - after.length : Infinity);
+        negative += prefix;
+        value = before + after;
+        return (align === "<" ? negative + value + padding : align === ">" ? padding + negative + value : align === "^" ? padding.substring(0, length >>= 1) + negative + value + padding.substring(length) : negative + (zcomma ? value : padding + value)) + fullSuffix;
+      };
+    };
+  }
+  var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i;
+  var d3_format_types = d3.map({
+    b: function(x) {
+      return x.toString(2);
+    },
+    c: function(x) {
+      return String.fromCharCode(x);
+    },
+    o: function(x) {
+      return x.toString(8);
+    },
+    x: function(x) {
+      return x.toString(16);
+    },
+    X: function(x) {
+      return x.toString(16).toUpperCase();
+    },
+    g: function(x, p) {
+      return x.toPrecision(p);
+    },
+    e: function(x, p) {
+      return x.toExponential(p);
+    },
+    f: function(x, p) {
+      return x.toFixed(p);
+    },
+    r: function(x, p) {
+      return (x = d3.round(x, d3_format_precision(x, p))).toFixed(Math.max(0, Math.min(20, d3_format_precision(x * (1 + 1e-15), p))));
+    }
+  });
+  function d3_format_typeDefault(x) {
+    return x + "";
+  }
+  var d3_time = d3.time = {}, d3_date = Date;
+  function d3_date_utc() {
+    this._ = new Date(arguments.length > 1 ? Date.UTC.apply(this, arguments) : arguments[0]);
+  }
+  d3_date_utc.prototype = {
+    getDate: function() {
+      return this._.getUTCDate();
+    },
+    getDay: function() {
+      return this._.getUTCDay();
+    },
+    getFullYear: function() {
+      return this._.getUTCFullYear();
+    },
+    getHours: function() {
+      return this._.getUTCHours();
+    },
+    getMilliseconds: function() {
+      return this._.getUTCMilliseconds();
+    },
+    getMinutes: function() {
+      return this._.getUTCMinutes();
+    },
+    getMonth: function() {
+      return this._.getUTCMonth();
+    },
+    getSeconds: function() {
+      return this._.getUTCSeconds();
+    },
+    getTime: function() {
+      return this._.getTime();
+    },
+    getTimezoneOffset: function() {
+      return 0;
+    },
+    valueOf: function() {
+      return this._.valueOf();
+    },
+    setDate: function() {
+      d3_time_prototype.setUTCDate.apply(this._, arguments);
+    },
+    setDay: function() {
+      d3_time_prototype.setUTCDay.apply(this._, arguments);
+    },
+    setFullYear: function() {
+      d3_time_prototype.setUTCFullYear.apply(this._, arguments);
+    },
+    setHours: function() {
+      d3_time_prototype.setUTCHours.apply(this._, arguments);
+    },
+    setMilliseconds: function() {
+      d3_time_prototype.setUTCMilliseconds.apply(this._, arguments);
+    },
+    setMinutes: function() {
+      d3_time_prototype.setUTCMinutes.apply(this._, arguments);
+    },
+    setMonth: function() {
+      d3_time_prototype.setUTCMonth.apply(this._, arguments);
+    },
+    setSeconds: function() {
+      d3_time_prototype.setUTCSeconds.apply(this._, arguments);
+    },
+    setTime: function() {
+      d3_time_prototype.setTime.apply(this._, arguments);
+    }
+  };
+  var d3_time_prototype = Date.prototype;
+  function d3_time_interval(local, step, number) {
+    function round(date) {
+      var d0 = local(date), d1 = offset(d0, 1);
+      return date - d0 < d1 - date ? d0 : d1;
+    }
+    function ceil(date) {
+      step(date = local(new d3_date(date - 1)), 1);
+      return date;
+    }
+    function offset(date, k) {
+      step(date = new d3_date(+date), k);
+      return date;
+    }
+    function range(t0, t1, dt) {
+      var time = ceil(t0), times = [];
+      if (dt > 1) {
+        while (time < t1) {
+          if (!(number(time) % dt)) times.push(new Date(+time));
+          step(time, 1);
+        }
+      } else {
+        while (time < t1) times.push(new Date(+time)), step(time, 1);
+      }
+      return times;
+    }
+    function range_utc(t0, t1, dt) {
+      try {
+        d3_date = d3_date_utc;
+        var utc = new d3_date_utc();
+        utc._ = t0;
+        return range(utc, t1, dt);
+      } finally {
+        d3_date = Date;
+      }
+    }
+    local.floor = local;
+    local.round = round;
+    local.ceil = ceil;
+    local.offset = offset;
+    local.range = range;
+    var utc = local.utc = d3_time_interval_utc(local);
+    utc.floor = utc;
+    utc.round = d3_time_interval_utc(round);
+    utc.ceil = d3_time_interval_utc(ceil);
+    utc.offset = d3_time_interval_utc(offset);
+    utc.range = range_utc;
+    return local;
+  }
+  function d3_time_interval_utc(method) {
+    return function(date, k) {
+      try {
+        d3_date = d3_date_utc;
+        var utc = new d3_date_utc();
+        utc._ = date;
+        return method(utc, k)._;
+      } finally {
+        d3_date = Date;
+      }
+    };
+  }
+  d3_time.year = d3_time_interval(function(date) {
+    date = d3_time.day(date);
+    date.setMonth(0, 1);
+    return date;
+  }, function(date, offset) {
+    date.setFullYear(date.getFullYear() + offset);
+  }, function(date) {
+    return date.getFullYear();
+  });
+  d3_time.years = d3_time.year.range;
+  d3_time.years.utc = d3_time.year.utc.range;
+  d3_time.day = d3_time_interval(function(date) {
+    var day = new d3_date(2e3, 0);
+    day.setFullYear(date.getFullYear(), date.getMonth(), date.getDate());
+    return day;
+  }, function(date, offset) {
+    date.setDate(date.getDate() + offset);
+  }, function(date) {
+    return date.getDate() - 1;
+  });
+  d3_time.days = d3_time.day.range;
+  d3_time.days.utc = d3_time.day.utc.range;
+  d3_time.dayOfYear = function(date) {
+    var year = d3_time.year(date);
+    return Math.floor((date - year - (date.getTimezoneOffset() - year.getTimezoneOffset()) * 6e4) / 864e5);
+  };
+  [ "sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday" ].forEach(function(day, i) {
+    i = 7 - i;
+    var interval = d3_time[day] = d3_time_interval(function(date) {
+      (date = d3_time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7);
+      return date;
+    }, function(date, offset) {
+      date.setDate(date.getDate() + Math.floor(offset) * 7);
+    }, function(date) {
+      var day = d3_time.year(date).getDay();
+      return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i);
+    });
+    d3_time[day + "s"] = interval.range;
+    d3_time[day + "s"].utc = interval.utc.range;
+    d3_time[day + "OfYear"] = function(date) {
+      var day = d3_time.year(date).getDay();
+      return Math.floor((d3_time.dayOfYear(date) + (day + i) % 7) / 7);
+    };
+  });
+  d3_time.week = d3_time.sunday;
+  d3_time.weeks = d3_time.sunday.range;
+  d3_time.weeks.utc = d3_time.sunday.utc.range;
+  d3_time.weekOfYear = d3_time.sundayOfYear;
+  function d3_locale_timeFormat(locale) {
+    var locale_dateTime = locale.dateTime, locale_date = locale.date, locale_time = locale.time, locale_periods = locale.periods, locale_days = locale.days, locale_shortDays = locale.shortDays, locale_months = locale.months, locale_shortMonths = locale.shortMonths;
+    function d3_time_format(template) {
+      var n = template.length;
+      function format(date) {
+        var string = [], i = -1, j = 0, c, p, f;
+        while (++i < n) {
+          if (template.charCodeAt(i) === 37) {
+            string.push(template.slice(j, i));
+            if ((p = d3_time_formatPads[c = template.charAt(++i)]) != null) c = template.charAt(++i);
+            if (f = d3_time_formats[c]) c = f(date, p == null ? c === "e" ? " " : "0" : p);
+            string.push(c);
+            j = i + 1;
+          }
+        }
+        string.push(template.slice(j, i));
+        return string.join("");
+      }
+      format.parse = function(string) {
+        var d = {
+          y: 1900,
+          m: 0,
+          d: 1,
+          H: 0,
+          M: 0,
+          S: 0,
+          L: 0,
+          Z: null
+        }, i = d3_time_parse(d, template, string, 0);
+        if (i != string.length) return null;
+        if ("p" in d) d.H = d.H % 12 + d.p * 12;
+        var localZ = d.Z != null && d3_date !== d3_date_utc, date = new (localZ ? d3_date_utc : d3_date)();
+        if ("j" in d) date.setFullYear(d.y, 0, d.j); else if ("W" in d || "U" in d) {
+          if (!("w" in d)) d.w = "W" in d ? 1 : 0;
+          date.setFullYear(d.y, 0, 1);
+          date.setFullYear(d.y, 0, "W" in d ? (d.w + 6) % 7 + d.W * 7 - (date.getDay() + 5) % 7 : d.w + d.U * 7 - (date.getDay() + 6) % 7);
+        } else date.setFullYear(d.y, d.m, d.d);
+        date.setHours(d.H + (d.Z / 100 | 0), d.M + d.Z % 100, d.S, d.L);
+        return localZ ? date._ : date;
+      };
+      format.toString = function() {
+        return template;
+      };
+      return format;
+    }
+    function d3_time_parse(date, template, string, j) {
+      var c, p, t, i = 0, n = template.length, m = string.length;
+      while (i < n) {
+        if (j >= m) return -1;
+        c = template.charCodeAt(i++);
+        if (c === 37) {
+          t = template.charAt(i++);
+          p = d3_time_parsers[t in d3_time_formatPads ? template.charAt(i++) : t];
+          if (!p || (j = p(date, string, j)) < 0) return -1;
+        } else if (c != string.charCodeAt(j++)) {
+          return -1;
+        }
+      }
+      return j;
+    }
+    d3_time_format.utc = function(template) {
+      var local = d3_time_format(template);
+      function format(date) {
+        try {
+          d3_date = d3_date_utc;
+          var utc = new d3_date();
+          utc._ = date;
+          return local(utc);
+        } finally {
+          d3_date = Date;
+        }
+      }
+      format.parse = function(string) {
+        try {
+          d3_date = d3_date_utc;
+          var date = local.parse(string);
+          return date && date._;
+        } finally {
+          d3_date = Date;
+        }
+      };
+      format.toString = local.toString;
+      return format;
+    };
+    d3_time_format.multi = d3_time_format.utc.multi = d3_time_formatMulti;
+    var d3_time_periodLookup = d3.map(), d3_time_dayRe = d3_time_formatRe(locale_days), d3_time_dayLookup = d3_time_formatLookup(locale_days), d3_time_dayAbbrevRe = d3_time_formatRe(locale_shortDays), d3_time_dayAbbrevLookup = d3_time_formatLookup(locale_shortDays), d3_time_monthRe = d3_time_formatRe(locale_months), d3_time_monthLookup = d3_time_formatLookup(locale_months), d3_time_monthAbbrevRe = d3_time_formatRe(locale_shortMonths), d3_time_monthAbbrevLookup = d3_time_formatLookup(locale_shortMonths);
+    locale_periods.forEach(function(p, i) {
+      d3_time_periodLookup.set(p.toLowerCase(), i);
+    });
+    var d3_time_formats = {
+      a: function(d) {
+        return locale_shortDays[d.getDay()];
+      },
+      A: function(d) {
+        return locale_days[d.getDay()];
+      },
+      b: function(d) {
+        return locale_shortMonths[d.getMonth()];
+      },
+      B: function(d) {
+        return locale_months[d.getMonth()];
+      },
+      c: d3_time_format(locale_dateTime),
+      d: function(d, p) {
+        return d3_time_formatPad(d.getDate(), p, 2);
+      },
+      e: function(d, p) {
+        return d3_time_formatPad(d.getDate(), p, 2);
+      },
+      H: function(d, p) {
+        return d3_time_formatPad(d.getHours(), p, 2);
+      },
+      I: function(d, p) {
+        return d3_time_formatPad(d.getHours() % 12 || 12, p, 2);
+      },
+      j: function(d, p) {
+        return d3_time_formatPad(1 + d3_time.dayOfYear(d), p, 3);
+      },
+      L: function(d, p) {
+        return d3_time_formatPad(d.getMilliseconds(), p, 3);
+      },
+      m: function(d, p) {
+        return d3_time_formatPad(d.getMonth() + 1, p, 2);
+      },
+      M: function(d, p) {
+        return d3_time_formatPad(d.getMinutes(), p, 2);
+      },
+      p: function(d) {
+        return locale_periods[+(d.getHours() >= 12)];
+      },
+      S: function(d, p) {
+        return d3_time_formatPad(d.getSeconds(), p, 2);
+      },
+      U: function(d, p) {
+        return d3_time_formatPad(d3_time.sundayOfYear(d), p, 2);
+      },
+      w: function(d) {
+        return d.getDay();
+      },
+      W: function(d, p) {
+        return d3_time_formatPad(d3_time.mondayOfYear(d), p, 2);
+      },
+      x: d3_time_format(locale_date),
+      X: d3_time_format(locale_time),
+      y: function(d, p) {
+        return d3_time_formatPad(d.getFullYear() % 100, p, 2);
+      },
+      Y: function(d, p) {
+        return d3_time_formatPad(d.getFullYear() % 1e4, p, 4);
+      },
+      Z: d3_time_zone,
+      "%": function() {
+        return "%";
+      }
+    };
+    var d3_time_parsers = {
+      a: d3_time_parseWeekdayAbbrev,
+      A: d3_time_parseWeekday,
+      b: d3_time_parseMonthAbbrev,
+      B: d3_time_parseMonth,
+      c: d3_time_parseLocaleFull,
+      d: d3_time_parseDay,
+      e: d3_time_parseDay,
+      H: d3_time_parseHour24,
+      I: d3_time_parseHour24,
+      j: d3_time_parseDayOfYear,
+      L: d3_time_parseMilliseconds,
+      m: d3_time_parseMonthNumber,
+      M: d3_time_parseMinutes,
+      p: d3_time_parseAmPm,
+      S: d3_time_parseSeconds,
+      U: d3_time_parseWeekNumberSunday,
+      w: d3_time_parseWeekdayNumber,
+      W: d3_time_parseWeekNumberMonday,
+      x: d3_time_parseLocaleDate,
+      X: d3_time_parseLocaleTime,
+      y: d3_time_parseYear,
+      Y: d3_time_parseFullYear,
+      Z: d3_time_parseZone,
+      "%": d3_time_parseLiteralPercent
+    };
+    function d3_time_parseWeekdayAbbrev(date, string, i) {
+      d3_time_dayAbbrevRe.lastIndex = 0;
+      var n = d3_time_dayAbbrevRe.exec(string.slice(i));
+      return n ? (date.w = d3_time_dayAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
+    }
+    function d3_time_parseWeekday(date, string, i) {
+      d3_time_dayRe.lastIndex = 0;
+      var n = d3_time_dayRe.exec(string.slice(i));
+      return n ? (date.w = d3_time_dayLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
+    }
+    function d3_time_parseMonthAbbrev(date, string, i) {
+      d3_time_monthAbbrevRe.lastIndex = 0;
+      var n = d3_time_monthAbbrevRe.exec(string.slice(i));
+      return n ? (date.m = d3_time_monthAbbrevLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
+    }
+    function d3_time_parseMonth(date, string, i) {
+      d3_time_monthRe.lastIndex = 0;
+      var n = d3_time_monthRe.exec(string.slice(i));
+      return n ? (date.m = d3_time_monthLookup.get(n[0].toLowerCase()), i + n[0].length) : -1;
+    }
+    function d3_time_parseLocaleFull(date, string, i) {
+      return d3_time_parse(date, d3_time_formats.c.toString(), string, i);
+    }
+    function d3_time_parseLocaleDate(date, string, i) {
+      return d3_time_parse(date, d3_time_formats.x.toString(), string, i);
+    }
+    function d3_time_parseLocaleTime(date, string, i) {
+      return d3_time_parse(date, d3_time_formats.X.toString(), string, i);
+    }
+    function d3_time_parseAmPm(date, string, i) {
+      var n = d3_time_periodLookup.get(string.slice(i, i += 2).toLowerCase());
+      return n == null ? -1 : (date.p = n, i);
+    }
+    return d3_time_format;
+  }
+  var d3_time_formatPads = {
+    "-": "",
+    _: " ",
+    "0": "0"
+  }, d3_time_numberRe = /^\s*\d+/, d3_time_percentRe = /^%/;
+  function d3_time_formatPad(value, fill, width) {
+    var sign = value < 0 ? "-" : "", string = (sign ? -value : value) + "", length = string.length;
+    return sign + (length < width ? new Array(width - length + 1).join(fill) + string : string);
+  }
+  function d3_time_formatRe(names) {
+    return new RegExp("^(?:" + names.map(d3.requote).join("|") + ")", "i");
+  }
+  function d3_time_formatLookup(names) {
+    var map = new d3_Map(), i = -1, n = names.length;
+    while (++i < n) map.set(names[i].toLowerCase(), i);
+    return map;
+  }
+  function d3_time_parseWeekdayNumber(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.slice(i, i + 1));
+    return n ? (date.w = +n[0], i + n[0].length) : -1;
+  }
+  function d3_time_parseWeekNumberSunday(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.slice(i));
+    return n ? (date.U = +n[0], i + n[0].length) : -1;
+  }
+  function d3_time_parseWeekNumberMonday(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.slice(i));
+    return n ? (date.W = +n[0], i + n[0].length) : -1;
+  }
+  function d3_time_parseFullYear(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.slice(i, i + 4));
+    return n ? (date.y = +n[0], i + n[0].length) : -1;
+  }
+  function d3_time_parseYear(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
+    return n ? (date.y = d3_time_expandYear(+n[0]), i + n[0].length) : -1;
+  }
+  function d3_time_parseZone(date, string, i) {
+    return /^[+-]\d{4}$/.test(string = string.slice(i, i + 5)) ? (date.Z = -string, 
+    i + 5) : -1;
+  }
+  function d3_time_expandYear(d) {
+    return d + (d > 68 ? 1900 : 2e3);
+  }
+  function d3_time_parseMonthNumber(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
+    return n ? (date.m = n[0] - 1, i + n[0].length) : -1;
+  }
+  function d3_time_parseDay(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
+    return n ? (date.d = +n[0], i + n[0].length) : -1;
+  }
+  function d3_time_parseDayOfYear(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.slice(i, i + 3));
+    return n ? (date.j = +n[0], i + n[0].length) : -1;
+  }
+  function d3_time_parseHour24(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
+    return n ? (date.H = +n[0], i + n[0].length) : -1;
+  }
+  function d3_time_parseMinutes(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
+    return n ? (date.M = +n[0], i + n[0].length) : -1;
+  }
+  function d3_time_parseSeconds(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.slice(i, i + 2));
+    return n ? (date.S = +n[0], i + n[0].length) : -1;
+  }
+  function d3_time_parseMilliseconds(date, string, i) {
+    d3_time_numberRe.lastIndex = 0;
+    var n = d3_time_numberRe.exec(string.slice(i, i + 3));
+    return n ? (date.L = +n[0], i + n[0].length) : -1;
+  }
+  function d3_time_zone(d) {
+    var z = d.getTimezoneOffset(), zs = z > 0 ? "-" : "+", zh = abs(z) / 60 | 0, zm = abs(z) % 60;
+    return zs + d3_time_formatPad(zh, "0", 2) + d3_time_formatPad(zm, "0", 2);
+  }
+  function d3_time_parseLiteralPercent(date, string, i) {
+    d3_time_percentRe.lastIndex = 0;
+    var n = d3_time_percentRe.exec(string.slice(i, i + 1));
+    return n ? i + n[0].length : -1;
+  }
+  function d3_time_formatMulti(formats) {
+    var n = formats.length, i = -1;
+    while (++i < n) formats[i][0] = this(formats[i][0]);
+    return function(date) {
+      var i = 0, f = formats[i];
+      while (!f[1](date)) f = formats[++i];
+      return f[0](date);
+    };
+  }
+  d3.locale = function(locale) {
+    return {
+      numberFormat: d3_locale_numberFormat(locale),
+      timeFormat: d3_locale_timeFormat(locale)
+    };
+  };
+  var d3_locale_enUS = d3.locale({
+    decimal: ".",
+    thousands: ",",
+    grouping: [ 3 ],
+    currency: [ "$", "" ],
+    dateTime: "%a %b %e %X %Y",
+    date: "%m/%d/%Y",
+    time: "%H:%M:%S",
+    periods: [ "AM", "PM" ],
+    days: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ],
+    shortDays: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ],
+    months: [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ],
+    shortMonths: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ]
+  });
+  d3.format = d3_locale_enUS.numberFormat;
+  d3.geo = {};
+  function d3_adder() {}
+  d3_adder.prototype = {
+    s: 0,
+    t: 0,
+    add: function(y) {
+      d3_adderSum(y, this.t, d3_adderTemp);
+      d3_adderSum(d3_adderTemp.s, this.s, this);
+      if (this.s) this.t += d3_adderTemp.t; else this.s = d3_adderTemp.t;
+    },
+    reset: function() {
+      this.s = this.t = 0;
+    },
+    valueOf: function() {
+      return this.s;
+    }
+  };
+  var d3_adderTemp = new d3_adder();
+  function d3_adderSum(a, b, o) {
+    var x = o.s = a + b, bv = x - a, av = x - bv;
+    o.t = a - av + (b - bv);
+  }
+  d3.geo.stream = function(object, listener) {
+    if (object && d3_geo_streamObjectType.hasOwnProperty(object.type)) {
+      d3_geo_streamObjectType[object.type](object, listener);
+    } else {
+      d3_geo_streamGeometry(object, listener);
+    }
+  };
+  function d3_geo_streamGeometry(geometry, listener) {
+    if (geometry && d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) {
+      d3_geo_streamGeometryType[geometry.type](geometry, listener);
+    }
+  }
+  var d3_geo_streamObjectType = {
+    Feature: function(feature, listener) {
+      d3_geo_streamGeometry(feature.geometry, listener);
+    },
+    FeatureCollection: function(object, listener) {
+      var features = object.features, i = -1, n = features.length;
+      while (++i < n) d3_geo_streamGeometry(features[i].geometry, listener);
+    }
+  };
+  var d3_geo_streamGeometryType = {
+    Sphere: function(object, listener) {
+      listener.sphere();
+    },
+    Point: function(object, listener) {
+      object = object.coordinates;
+      listener.point(object[0], object[1], object[2]);
+    },
+    MultiPoint: function(object, listener) {
+      var coordinates = object.coordinates, i = -1, n = coordinates.length;
+      while (++i < n) object = coordinates[i], listener.point(object[0], object[1], object[2]);
+    },
+    LineString: function(object, listener) {
+      d3_geo_streamLine(object.coordinates, listener, 0);
+    },
+    MultiLineString: function(object, listener) {
+      var coordinates = object.coordinates, i = -1, n = coordinates.length;
+      while (++i < n) d3_geo_streamLine(coordinates[i], listener, 0);
+    },
+    Polygon: function(object, listener) {
+      d3_geo_streamPolygon(object.coordinates, listener);
+    },
+    MultiPolygon: function(object, listener) {
+      var coordinates = object.coordinates, i = -1, n = coordinates.length;
+      while (++i < n) d3_geo_streamPolygon(coordinates[i], listener);
+    },
+    GeometryCollection: function(object, listener) {
+      var geometries = object.geometries, i = -1, n = geometries.length;
+      while (++i < n) d3_geo_streamGeometry(geometries[i], listener);
+    }
+  };
+  function d3_geo_streamLine(coordinates, listener, closed) {
+    var i = -1, n = coordinates.length - closed, coordinate;
+    listener.lineStart();
+    while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1], coordinate[2]);
+    listener.lineEnd();
+  }
+  function d3_geo_streamPolygon(coordinates, listener) {
+    var i = -1, n = coordinates.length;
+    listener.polygonStart();
+    while (++i < n) d3_geo_streamLine(coordinates[i], listener, 1);
+    listener.polygonEnd();
+  }
+  d3.geo.area = function(object) {
+    d3_geo_areaSum = 0;
+    d3.geo.stream(object, d3_geo_area);
+    return d3_geo_areaSum;
+  };
+  var d3_geo_areaSum, d3_geo_areaRingSum = new d3_adder();
+  var d3_geo_area = {
+    sphere: function() {
+      d3_geo_areaSum += 4 * π;
+    },
+    point: d3_noop,
+    lineStart: d3_noop,
+    lineEnd: d3_noop,
+    polygonStart: function() {
+      d3_geo_areaRingSum.reset();
+      d3_geo_area.lineStart = d3_geo_areaRingStart;
+    },
+    polygonEnd: function() {
+      var area = 2 * d3_geo_areaRingSum;
+      d3_geo_areaSum += area < 0 ? 4 * π + area : area;
+      d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop;
+    }
+  };
+  function d3_geo_areaRingStart() {
+    var λ00, φ00, λ0, cosφ0, sinφ0;
+    d3_geo_area.point = function(λ, φ) {
+      d3_geo_area.point = nextPoint;
+      λ0 = (λ00 = λ) * d3_radians, cosφ0 = Math.cos(φ = (φ00 = φ) * d3_radians / 2 + π / 4), 
+      sinφ0 = Math.sin(φ);
+    };
+    function nextPoint(λ, φ) {
+      λ *= d3_radians;
+      φ = φ * d3_radians / 2 + π / 4;
+      var dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, cosφ = Math.cos(φ), sinφ = Math.sin(φ), k = sinφ0 * sinφ, u = cosφ0 * cosφ + k * Math.cos(adλ), v = k * sdλ * Math.sin(adλ);
+      d3_geo_areaRingSum.add(Math.atan2(v, u));
+      λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ;
+    }
+    d3_geo_area.lineEnd = function() {
+      nextPoint(λ00, φ00);
+    };
+  }
+  function d3_geo_cartesian(spherical) {
+    var λ = spherical[0], φ = spherical[1], cosφ = Math.cos(φ);
+    return [ cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ) ];
+  }
+  function d3_geo_cartesianDot(a, b) {
+    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
+  }
+  function d3_geo_cartesianCross(a, b) {
+    return [ a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0] ];
+  }
+  function d3_geo_cartesianAdd(a, b) {
+    a[0] += b[0];
+    a[1] += b[1];
+    a[2] += b[2];
+  }
+  function d3_geo_cartesianScale(vector, k) {
+    return [ vector[0] * k, vector[1] * k, vector[2] * k ];
+  }
+  function d3_geo_cartesianNormalize(d) {
+    var l = Math.sqrt(d[0] * d[0] + d[1] * d[1] + d[2] * d[2]);
+    d[0] /= l;
+    d[1] /= l;
+    d[2] /= l;
+  }
+  function d3_geo_spherical(cartesian) {
+    return [ Math.atan2(cartesian[1], cartesian[0]), d3_asin(cartesian[2]) ];
+  }
+  function d3_geo_sphericalEqual(a, b) {
+    return abs(a[0] - b[0]) < ε && abs(a[1] - b[1]) < ε;
+  }
+  d3.geo.bounds = function() {
+    var λ0, φ0, λ1, φ1, λ_, λ__, φ__, p0, dλSum, ranges, range;
+    var bound = {
+      point: point,
+      lineStart: lineStart,
+      lineEnd: lineEnd,
+      polygonStart: function() {
+        bound.point = ringPoint;
+        bound.lineStart = ringStart;
+        bound.lineEnd = ringEnd;
+        dλSum = 0;
+        d3_geo_area.polygonStart();
+      },
+      polygonEnd: function() {
+        d3_geo_area.polygonEnd();
+        bound.point = point;
+        bound.lineStart = lineStart;
+        bound.lineEnd = lineEnd;
+        if (d3_geo_areaRingSum < 0) λ0 = -(λ1 = 180), φ0 = -(φ1 = 90); else if (dλSum > ε) φ1 = 90; else if (dλSum < -ε) φ0 = -90;
+        range[0] = λ0, range[1] = λ1;
+      }
+    };
+    function point(λ, φ) {
+      ranges.push(range = [ λ0 = λ, λ1 = λ ]);
+      if (φ < φ0) φ0 = φ;
+      if (φ > φ1) φ1 = φ;
+    }
+    function linePoint(λ, φ) {
+      var p = d3_geo_cartesian([ λ * d3_radians, φ * d3_radians ]);
+      if (p0) {
+        var normal = d3_geo_cartesianCross(p0, p), equatorial = [ normal[1], -normal[0], 0 ], inflection = d3_geo_cartesianCross(equatorial, normal);
+        d3_geo_cartesianNormalize(inflection);
+        inflection = d3_geo_spherical(inflection);
+        var dλ = λ - λ_, s = dλ > 0 ? 1 : -1, λi = inflection[0] * d3_degrees * s, antimeridian = abs(dλ) > 180;
+        if (antimeridian ^ (s * λ_ < λi && λi < s * λ)) {
+          var φi = inflection[1] * d3_degrees;
+          if (φi > φ1) φ1 = φi;
+        } else if (λi = (λi + 360) % 360 - 180, antimeridian ^ (s * λ_ < λi && λi < s * λ)) {
+          var φi = -inflection[1] * d3_degrees;
+          if (φi < φ0) φ0 = φi;
+        } else {
+          if (φ < φ0) φ0 = φ;
+          if (φ > φ1) φ1 = φ;
+        }
+        if (antimeridian) {
+          if (λ < λ_) {
+            if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ;
+          } else {
+            if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ;
+          }
+        } else {
+          if (λ1 >= λ0) {
+            if (λ < λ0) λ0 = λ;
+            if (λ > λ1) λ1 = λ;
+          } else {
+            if (λ > λ_) {
+              if (angle(λ0, λ) > angle(λ0, λ1)) λ1 = λ;
+            } else {
+              if (angle(λ, λ1) > angle(λ0, λ1)) λ0 = λ;
+            }
+          }
+        }
+      } else {
+        point(λ, φ);
+      }
+      p0 = p, λ_ = λ;
+    }
+    function lineStart() {
+      bound.point = linePoint;
+    }
+    function lineEnd() {
+      range[0] = λ0, range[1] = λ1;
+      bound.point = point;
+      p0 = null;
+    }
+    function ringPoint(λ, φ) {
+      if (p0) {
+        var dλ = λ - λ_;
+        dλSum += abs(dλ) > 180 ? dλ + (dλ > 0 ? 360 : -360) : dλ;
+      } else λ__ = λ, φ__ = φ;
+      d3_geo_area.point(λ, φ);
+      linePoint(λ, φ);
+    }
+    function ringStart() {
+      d3_geo_area.lineStart();
+    }
+    function ringEnd() {
+      ringPoint(λ__, φ__);
+      d3_geo_area.lineEnd();
+      if (abs(dλSum) > ε) λ0 = -(λ1 = 180);
+      range[0] = λ0, range[1] = λ1;
+      p0 = null;
+    }
+    function angle(λ0, λ1) {
+      return (λ1 -= λ0) < 0 ? λ1 + 360 : λ1;
+    }
+    function compareRanges(a, b) {
+      return a[0] - b[0];
+    }
+    function withinRange(x, range) {
+      return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x;
+    }
+    return function(feature) {
+      φ1 = λ1 = -(λ0 = φ0 = Infinity);
+      ranges = [];
+      d3.geo.stream(feature, bound);
+      var n = ranges.length;
+      if (n) {
+        ranges.sort(compareRanges);
+        for (var i = 1, a = ranges[0], b, merged = [ a ]; i < n; ++i) {
+          b = ranges[i];
+          if (withinRange(b[0], a) || withinRange(b[1], a)) {
+            if (angle(a[0], b[1]) > angle(a[0], a[1])) a[1] = b[1];
+            if (angle(b[0], a[1]) > angle(a[0], a[1])) a[0] = b[0];
+          } else {
+            merged.push(a = b);
+          }
+        }
+        var best = -Infinity, dλ;
+        for (var n = merged.length - 1, i = 0, a = merged[n], b; i <= n; a = b, ++i) {
+          b = merged[i];
+          if ((dλ = angle(a[1], b[0])) > best) best = dλ, λ0 = b[0], λ1 = a[1];
+        }
+      }
+      ranges = range = null;
+      return λ0 === Infinity || φ0 === Infinity ? [ [ NaN, NaN ], [ NaN, NaN ] ] : [ [ λ0, φ0 ], [ λ1, φ1 ] ];
+    };
+  }();
+  d3.geo.centroid = function(object) {
+    d3_geo_centroidW0 = d3_geo_centroidW1 = d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0;
+    d3.geo.stream(object, d3_geo_centroid);
+    var x = d3_geo_centroidX2, y = d3_geo_centroidY2, z = d3_geo_centroidZ2, m = x * x + y * y + z * z;
+    if (m < ε2) {
+      x = d3_geo_centroidX1, y = d3_geo_centroidY1, z = d3_geo_centroidZ1;
+      if (d3_geo_centroidW1 < ε) x = d3_geo_centroidX0, y = d3_geo_centroidY0, z = d3_geo_centroidZ0;
+      m = x * x + y * y + z * z;
+      if (m < ε2) return [ NaN, NaN ];
+    }
+    return [ Math.atan2(y, x) * d3_degrees, d3_asin(z / Math.sqrt(m)) * d3_degrees ];
+  };
+  var d3_geo_centroidW0, d3_geo_centroidW1, d3_geo_centroidX0, d3_geo_centroidY0, d3_geo_centroidZ0, d3_geo_centroidX1, d3_geo_centroidY1, d3_geo_centroidZ1, d3_geo_centroidX2, d3_geo_centroidY2, d3_geo_centroidZ2;
+  var d3_geo_centroid = {
+    sphere: d3_noop,
+    point: d3_geo_centroidPoint,
+    lineStart: d3_geo_centroidLineStart,
+    lineEnd: d3_geo_centroidLineEnd,
+    polygonStart: function() {
+      d3_geo_centroid.lineStart = d3_geo_centroidRingStart;
+    },
+    polygonEnd: function() {
+      d3_geo_centroid.lineStart = d3_geo_centroidLineStart;
+    }
+  };
+  function d3_geo_centroidPoint(λ, φ) {
+    λ *= d3_radians;
+    var cosφ = Math.cos(φ *= d3_radians);
+    d3_geo_centroidPointXYZ(cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ));
+  }
+  function d3_geo_centroidPointXYZ(x, y, z) {
+    ++d3_geo_centroidW0;
+    d3_geo_centroidX0 += (x - d3_geo_centroidX0) / d3_geo_centroidW0;
+    d3_geo_centroidY0 += (y - d3_geo_centroidY0) / d3_geo_centroidW0;
+    d3_geo_centroidZ0 += (z - d3_geo_centroidZ0) / d3_geo_centroidW0;
+  }
+  function d3_geo_centroidLineStart() {
+    var x0, y0, z0;
+    d3_geo_centroid.point = function(λ, φ) {
+      λ *= d3_radians;
+      var cosφ = Math.cos(φ *= d3_radians);
+      x0 = cosφ * Math.cos(λ);
+      y0 = cosφ * Math.sin(λ);
+      z0 = Math.sin(φ);
+      d3_geo_centroid.point = nextPoint;
+      d3_geo_centroidPointXYZ(x0, y0, z0);
+    };
+    function nextPoint(λ, φ) {
+      λ *= d3_radians;
+      var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), w = Math.atan2(Math.sqrt((w = y0 * z - z0 * y) * w + (w = z0 * x - x0 * z) * w + (w = x0 * y - y0 * x) * w), x0 * x + y0 * y + z0 * z);
+      d3_geo_centroidW1 += w;
+      d3_geo_centroidX1 += w * (x0 + (x0 = x));
+      d3_geo_centroidY1 += w * (y0 + (y0 = y));
+      d3_geo_centroidZ1 += w * (z0 + (z0 = z));
+      d3_geo_centroidPointXYZ(x0, y0, z0);
+    }
+  }
+  function d3_geo_centroidLineEnd() {
+    d3_geo_centroid.point = d3_geo_centroidPoint;
+  }
+  function d3_geo_centroidRingStart() {
+    var λ00, φ00, x0, y0, z0;
+    d3_geo_centroid.point = function(λ, φ) {
+      λ00 = λ, φ00 = φ;
+      d3_geo_centroid.point = nextPoint;
+      λ *= d3_radians;
+      var cosφ = Math.cos(φ *= d3_radians);
+      x0 = cosφ * Math.cos(λ);
+      y0 = cosφ * Math.sin(λ);
+      z0 = Math.sin(φ);
+      d3_geo_centroidPointXYZ(x0, y0, z0);
+    };
+    d3_geo_centroid.lineEnd = function() {
+      nextPoint(λ00, φ00);
+      d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd;
+      d3_geo_centroid.point = d3_geo_centroidPoint;
+    };
+    function nextPoint(λ, φ) {
+      λ *= d3_radians;
+      var cosφ = Math.cos(φ *= d3_radians), x = cosφ * Math.cos(λ), y = cosφ * Math.sin(λ), z = Math.sin(φ), cx = y0 * z - z0 * y, cy = z0 * x - x0 * z, cz = x0 * y - y0 * x, m = Math.sqrt(cx * cx + cy * cy + cz * cz), u = x0 * x + y0 * y + z0 * z, v = m && -d3_acos(u) / m, w = Math.atan2(m, u);
+      d3_geo_centroidX2 += v * cx;
+      d3_geo_centroidY2 += v * cy;
+      d3_geo_centroidZ2 += v * cz;
+      d3_geo_centroidW1 += w;
+      d3_geo_centroidX1 += w * (x0 + (x0 = x));
+      d3_geo_centroidY1 += w * (y0 + (y0 = y));
+      d3_geo_centroidZ1 += w * (z0 + (z0 = z));
+      d3_geo_centroidPointXYZ(x0, y0, z0);
+    }
+  }
+  function d3_geo_compose(a, b) {
+    function compose(x, y) {
+      return x = a(x, y), b(x[0], x[1]);
+    }
+    if (a.invert && b.invert) compose.invert = function(x, y) {
+      return x = b.invert(x, y), x && a.invert(x[0], x[1]);
+    };
+    return compose;
+  }
+  function d3_true() {
+    return true;
+  }
+  function d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener) {
+    var subject = [], clip = [];
+    segments.forEach(function(segment) {
+      if ((n = segment.length - 1) <= 0) return;
+      var n, p0 = segment[0], p1 = segment[n];
+      if (d3_geo_sphericalEqual(p0, p1)) {
+        listener.lineStart();
+        for (var i = 0; i < n; ++i) listener.point((p0 = segment[i])[0], p0[1]);
+        listener.lineEnd();
+        return;
+      }
+      var a = new d3_geo_clipPolygonIntersection(p0, segment, null, true), b = new d3_geo_clipPolygonIntersection(p0, null, a, false);
+      a.o = b;
+      subject.push(a);
+      clip.push(b);
+      a = new d3_geo_clipPolygonIntersection(p1, segment, null, false);
+      b = new d3_geo_clipPolygonIntersection(p1, null, a, true);
+      a.o = b;
+      subject.push(a);
+      clip.push(b);
+    });
+    clip.sort(compare);
+    d3_geo_clipPolygonLinkCircular(subject);
+    d3_geo_clipPolygonLinkCircular(clip);
+    if (!subject.length) return;
+    for (var i = 0, entry = clipStartInside, n = clip.length; i < n; ++i) {
+      clip[i].e = entry = !entry;
+    }
+    var start = subject[0], points, point;
+    while (1) {
+      var current = start, isSubject = true;
+      while (current.v) if ((current = current.n) === start) return;
+      points = current.z;
+      listener.lineStart();
+      do {
+        current.v = current.o.v = true;
+        if (current.e) {
+          if (isSubject) {
+            for (var i = 0, n = points.length; i < n; ++i) listener.point((point = points[i])[0], point[1]);
+          } else {
+            interpolate(current.x, current.n.x, 1, listener);
+          }
+          current = current.n;
+        } else {
+          if (isSubject) {
+            points = current.p.z;
+            for (var i = points.length - 1; i >= 0; --i) listener.point((point = points[i])[0], point[1]);
+          } else {
+            interpolate(current.x, current.p.x, -1, listener);
+          }
+          current = current.p;
+        }
+        current = current.o;
+        points = current.z;
+        isSubject = !isSubject;
+      } while (!current.v);
+      listener.lineEnd();
+    }
+  }
+  function d3_geo_clipPolygonLinkCircular(array) {
+    if (!(n = array.length)) return;
+    var n, i = 0, a = array[0], b;
+    while (++i < n) {
+      a.n = b = array[i];
+      b.p = a;
+      a = b;
+    }
+    a.n = b = array[0];
+    b.p = a;
+  }
+  function d3_geo_clipPolygonIntersection(point, points, other, entry) {
+    this.x = point;
+    this.z = points;
+    this.o = other;
+    this.e = entry;
+    this.v = false;
+    this.n = this.p = null;
+  }
+  function d3_geo_clip(pointVisible, clipLine, interpolate, clipStart) {
+    return function(rotate, listener) {
+      var line = clipLine(listener), rotatedClipStart = rotate.invert(clipStart[0], clipStart[1]);
+      var clip = {
+        point: point,
+        lineStart: lineStart,
+        lineEnd: lineEnd,
+        polygonStart: function() {
+          clip.point = pointRing;
+          clip.lineStart = ringStart;
+          clip.lineEnd = ringEnd;
+          segments = [];
+          polygon = [];
+        },
+        polygonEnd: function() {
+          clip.point = point;
+          clip.lineStart = lineStart;
+          clip.lineEnd = lineEnd;
+          segments = d3.merge(segments);
+          var clipStartInside = d3_geo_pointInPolygon(rotatedClipStart, polygon);
+          if (segments.length) {
+            if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
+            d3_geo_clipPolygon(segments, d3_geo_clipSort, clipStartInside, interpolate, listener);
+          } else if (clipStartInside) {
+            if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
+            listener.lineStart();
+            interpolate(null, null, 1, listener);
+            listener.lineEnd();
+          }
+          if (polygonStarted) listener.polygonEnd(), polygonStarted = false;
+          segments = polygon = null;
+        },
+        sphere: function() {
+          listener.polygonStart();
+          listener.lineStart();
+          interpolate(null, null, 1, listener);
+          listener.lineEnd();
+          listener.polygonEnd();
+        }
+      };
+      function point(λ, φ) {
+        var point = rotate(λ, φ);
+        if (pointVisible(λ = point[0], φ = point[1])) listener.point(λ, φ);
+      }
+      function pointLine(λ, φ) {
+        var point = rotate(λ, φ);
+        line.point(point[0], point[1]);
+      }
+      function lineStart() {
+        clip.point = pointLine;
+        line.lineStart();
+      }
+      function lineEnd() {
+        clip.point = point;
+        line.lineEnd();
+      }
+      var segments;
+      var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), polygonStarted = false, polygon, ring;
+      function pointRing(λ, φ) {
+        ring.push([ λ, φ ]);
+        var point = rotate(λ, φ);
+        ringListener.point(point[0], point[1]);
+      }
+      function ringStart() {
+        ringListener.lineStart();
+        ring = [];
+      }
+      function ringEnd() {
+        pointRing(ring[0][0], ring[0][1]);
+        ringListener.lineEnd();
+        var clean = ringListener.clean(), ringSegments = buffer.buffer(), segment, n = ringSegments.length;
+        ring.pop();
+        polygon.push(ring);
+        ring = null;
+        if (!n) return;
+        if (clean & 1) {
+          segment = ringSegments[0];
+          var n = segment.length - 1, i = -1, point;
+          if (n > 0) {
+            if (!polygonStarted) listener.polygonStart(), polygonStarted = true;
+            listener.lineStart();
+            while (++i < n) listener.point((point = segment[i])[0], point[1]);
+            listener.lineEnd();
+          }
+          return;
+        }
+        if (n > 1 && clean & 2) ringSegments.push(ringSegments.pop().concat(ringSegments.shift()));
+        segments.push(ringSegments.filter(d3_geo_clipSegmentLength1));
+      }
+      return clip;
+    };
+  }
+  function d3_geo_clipSegmentLength1(segment) {
+    return segment.length > 1;
+  }
+  function d3_geo_clipBufferListener() {
+    var lines = [], line;
+    return {
+      lineStart: function() {
+        lines.push(line = []);
+      },
+      point: function(λ, φ) {
+        line.push([ λ, φ ]);
+      },
+      lineEnd: d3_noop,
+      buffer: function() {
+        var buffer = lines;
+        lines = [];
+        line = null;
+        return buffer;
+      },
+      rejoin: function() {
+        if (lines.length > 1) lines.push(lines.pop().concat(lines.shift()));
+      }
+    };
+  }
+  function d3_geo_clipSort(a, b) {
+    return ((a = a.x)[0] < 0 ? a[1] - halfπ - ε : halfπ - a[1]) - ((b = b.x)[0] < 0 ? b[1] - halfπ - ε : halfπ - b[1]);
+  }
+  var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate, [ -Ï€, -Ï€ / 2 ]);
+  function d3_geo_clipAntimeridianLine(listener) {
+    var λ0 = NaN, φ0 = NaN, sλ0 = NaN, clean;
+    return {
+      lineStart: function() {
+        listener.lineStart();
+        clean = 1;
+      },
+      point: function(λ1, φ1) {
+        var sλ1 = λ1 > 0 ? π : -π, dλ = abs(λ1 - λ0);
+        if (abs(dλ - π) < ε) {
+          listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? halfπ : -halfπ);
+          listener.point(sλ0, φ0);
+          listener.lineEnd();
+          listener.lineStart();
+          listener.point(sλ1, φ0);
+          listener.point(λ1, φ0);
+          clean = 0;
+        } else if (sλ0 !== sλ1 && dλ >= π) {
+          if (abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε;
+          if (abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε;
+          φ0 = d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1);
+          listener.point(sλ0, φ0);
+          listener.lineEnd();
+          listener.lineStart();
+          listener.point(sλ1, φ0);
+          clean = 0;
+        }
+        listener.point(λ0 = λ1, φ0 = φ1);
+        sλ0 = sλ1;
+      },
+      lineEnd: function() {
+        listener.lineEnd();
+        λ0 = φ0 = NaN;
+      },
+      clean: function() {
+        return 2 - clean;
+      }
+    };
+  }
+  function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) {
+    var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1);
+    return abs(sinλ0_λ1) > ε ? Math.atan((Math.sin(φ0) * (cosφ1 = Math.cos(φ1)) * Math.sin(λ1) - Math.sin(φ1) * (cosφ0 = Math.cos(φ0)) * Math.sin(λ0)) / (cosφ0 * cosφ1 * sinλ0_λ1)) : (φ0 + φ1) / 2;
+  }
+  function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) {
+    var φ;
+    if (from == null) {
+      φ = direction * halfπ;
+      listener.point(-π, φ);
+      listener.point(0, φ);
+      listener.point(π, φ);
+      listener.point(Ï€, 0);
+      listener.point(π, -φ);
+      listener.point(0, -φ);
+      listener.point(-π, -φ);
+      listener.point(-Ï€, 0);
+      listener.point(-π, φ);
+    } else if (abs(from[0] - to[0]) > ε) {
+      var s = from[0] < to[0] ? π : -π;
+      φ = direction * s / 2;
+      listener.point(-s, φ);
+      listener.point(0, φ);
+      listener.point(s, φ);
+    } else {
+      listener.point(to[0], to[1]);
+    }
+  }
+  function d3_geo_pointInPolygon(point, polygon) {
+    var meridian = point[0], parallel = point[1], meridianNormal = [ Math.sin(meridian), -Math.cos(meridian), 0 ], polarAngle = 0, winding = 0;
+    d3_geo_areaRingSum.reset();
+    for (var i = 0, n = polygon.length; i < n; ++i) {
+      var ring = polygon[i], m = ring.length;
+      if (!m) continue;
+      var point0 = ring[0], λ0 = point0[0], φ0 = point0[1] / 2 + π / 4, sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), j = 1;
+      while (true) {
+        if (j === m) j = 0;
+        point = ring[j];
+        var λ = point[0], φ = point[1] / 2 + π / 4, sinφ = Math.sin(φ), cosφ = Math.cos(φ), dλ = λ - λ0, sdλ = dλ >= 0 ? 1 : -1, adλ = sdλ * dλ, antimeridian = adλ > π, k = sinφ0 * sinφ;
+        d3_geo_areaRingSum.add(Math.atan2(k * sdλ * Math.sin(adλ), cosφ0 * cosφ + k * Math.cos(adλ)));
+        polarAngle += antimeridian ? dλ + sdλ * τ : dλ;
+        if (antimeridian ^ λ0 >= meridian ^ λ >= meridian) {
+          var arc = d3_geo_cartesianCross(d3_geo_cartesian(point0), d3_geo_cartesian(point));
+          d3_geo_cartesianNormalize(arc);
+          var intersection = d3_geo_cartesianCross(meridianNormal, arc);
+          d3_geo_cartesianNormalize(intersection);
+          var φarc = (antimeridian ^ dλ >= 0 ? -1 : 1) * d3_asin(intersection[2]);
+          if (parallel > φarc || parallel === φarc && (arc[0] || arc[1])) {
+            winding += antimeridian ^ dλ >= 0 ? 1 : -1;
+          }
+        }
+        if (!j++) break;
+        λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ, point0 = point;
+      }
+    }
+    return (polarAngle < -ε || polarAngle < ε && d3_geo_areaRingSum < -ε) ^ winding & 1;
+  }
+  function d3_geo_clipCircle(radius) {
+    var cr = Math.cos(radius), smallRadius = cr > 0, notHemisphere = abs(cr) > ε, interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians);
+    return d3_geo_clip(visible, clipLine, interpolate, smallRadius ? [ 0, -radius ] : [ -π, radius - π ]);
+    function visible(λ, φ) {
+      return Math.cos(λ) * Math.cos(φ) > cr;
+    }
+    function clipLine(listener) {
+      var point0, c0, v0, v00, clean;
+      return {
+        lineStart: function() {
+          v00 = v0 = false;
+          clean = 1;
+        },
+        point: function(λ, φ) {
+          var point1 = [ λ, φ ], point2, v = visible(λ, φ), c = smallRadius ? v ? 0 : code(λ, φ) : v ? code(λ + (λ < 0 ? π : -π), φ) : 0;
+          if (!point0 && (v00 = v0 = v)) listener.lineStart();
+          if (v !== v0) {
+            point2 = intersect(point0, point1);
+            if (d3_geo_sphericalEqual(point0, point2) || d3_geo_sphericalEqual(point1, point2)) {
+              point1[0] += ε;
+              point1[1] += ε;
+              v = visible(point1[0], point1[1]);
+            }
+          }
+          if (v !== v0) {
+            clean = 0;
+            if (v) {
+              listener.lineStart();
+              point2 = intersect(point1, point0);
+              listener.point(point2[0], point2[1]);
+            } else {
+              point2 = intersect(point0, point1);
+              listener.point(point2[0], point2[1]);
+              listener.lineEnd();
+            }
+            point0 = point2;
+          } else if (notHemisphere && point0 && smallRadius ^ v) {
+            var t;
+            if (!(c & c0) && (t = intersect(point1, point0, true))) {
+              clean = 0;
+              if (smallRadius) {
+                listener.lineStart();
+                listener.point(t[0][0], t[0][1]);
+                listener.point(t[1][0], t[1][1]);
+                listener.lineEnd();
+              } else {
+                listener.point(t[1][0], t[1][1]);
+                listener.lineEnd();
+                listener.lineStart();
+                listener.point(t[0][0], t[0][1]);
+              }
+            }
+          }
+          if (v && (!point0 || !d3_geo_sphericalEqual(point0, point1))) {
+            listener.point(point1[0], point1[1]);
+          }
+          point0 = point1, v0 = v, c0 = c;
+        },
+        lineEnd: function() {
+          if (v0) listener.lineEnd();
+          point0 = null;
+        },
+        clean: function() {
+          return clean | (v00 && v0) << 1;
+        }
+      };
+    }
+    function intersect(a, b, two) {
+      var pa = d3_geo_cartesian(a), pb = d3_geo_cartesian(b);
+      var n1 = [ 1, 0, 0 ], n2 = d3_geo_cartesianCross(pa, pb), n2n2 = d3_geo_cartesianDot(n2, n2), n1n2 = n2[0], determinant = n2n2 - n1n2 * n1n2;
+      if (!determinant) return !two && a;
+      var c1 = cr * n2n2 / determinant, c2 = -cr * n1n2 / determinant, n1xn2 = d3_geo_cartesianCross(n1, n2), A = d3_geo_cartesianScale(n1, c1), B = d3_geo_cartesianScale(n2, c2);
+      d3_geo_cartesianAdd(A, B);
+      var u = n1xn2, w = d3_geo_cartesianDot(A, u), uu = d3_geo_cartesianDot(u, u), t2 = w * w - uu * (d3_geo_cartesianDot(A, A) - 1);
+      if (t2 < 0) return;
+      var t = Math.sqrt(t2), q = d3_geo_cartesianScale(u, (-w - t) / uu);
+      d3_geo_cartesianAdd(q, A);
+      q = d3_geo_spherical(q);
+      if (!two) return q;
+      var λ0 = a[0], λ1 = b[0], φ0 = a[1], φ1 = b[1], z;
+      if (λ1 < λ0) z = λ0, λ0 = λ1, λ1 = z;
+      var δλ = λ1 - λ0, polar = abs(δλ - π) < ε, meridian = polar || δλ < ε;
+      if (!polar && φ1 < φ0) z = φ0, φ0 = φ1, φ1 = z;
+      if (meridian ? polar ? φ0 + φ1 > 0 ^ q[1] < (abs(q[0] - λ0) < ε ? φ0 : φ1) : φ0 <= q[1] && q[1] <= φ1 : δλ > π ^ (λ0 <= q[0] && q[0] <= λ1)) {
+        var q1 = d3_geo_cartesianScale(u, (-w + t) / uu);
+        d3_geo_cartesianAdd(q1, A);
+        return [ q, d3_geo_spherical(q1) ];
+      }
+    }
+    function code(λ, φ) {
+      var r = smallRadius ? radius : π - radius, code = 0;
+      if (λ < -r) code |= 1; else if (λ > r) code |= 2;
+      if (φ < -r) code |= 4; else if (φ > r) code |= 8;
+      return code;
+    }
+  }
+  function d3_geom_clipLine(x0, y0, x1, y1) {
+    return function(line) {
+      var a = line.a, b = line.b, ax = a.x, ay = a.y, bx = b.x, by = b.y, t0 = 0, t1 = 1, dx = bx - ax, dy = by - ay, r;
+      r = x0 - ax;
+      if (!dx && r > 0) return;
+      r /= dx;
+      if (dx < 0) {
+        if (r < t0) return;
+        if (r < t1) t1 = r;
+      } else if (dx > 0) {
+        if (r > t1) return;
+        if (r > t0) t0 = r;
+      }
+      r = x1 - ax;
+      if (!dx && r < 0) return;
+      r /= dx;
+      if (dx < 0) {
+        if (r > t1) return;
+        if (r > t0) t0 = r;
+      } else if (dx > 0) {
+        if (r < t0) return;
+        if (r < t1) t1 = r;
+      }
+      r = y0 - ay;
+      if (!dy && r > 0) return;
+      r /= dy;
+      if (dy < 0) {
+        if (r < t0) return;
+        if (r < t1) t1 = r;
+      } else if (dy > 0) {
+        if (r > t1) return;
+        if (r > t0) t0 = r;
+      }
+      r = y1 - ay;
+      if (!dy && r < 0) return;
+      r /= dy;
+      if (dy < 0) {
+        if (r > t1) return;
+        if (r > t0) t0 = r;
+      } else if (dy > 0) {
+        if (r < t0) return;
+        if (r < t1) t1 = r;
+      }
+      if (t0 > 0) line.a = {
+        x: ax + t0 * dx,
+        y: ay + t0 * dy
+      };
+      if (t1 < 1) line.b = {
+        x: ax + t1 * dx,
+        y: ay + t1 * dy
+      };
+      return line;
+    };
+  }
+  var d3_geo_clipExtentMAX = 1e9;
+  d3.geo.clipExtent = function() {
+    var x0, y0, x1, y1, stream, clip, clipExtent = {
+      stream: function(output) {
+        if (stream) stream.valid = false;
+        stream = clip(output);
+        stream.valid = true;
+        return stream;
+      },
+      extent: function(_) {
+        if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ];
+        clip = d3_geo_clipExtent(x0 = +_[0][0], y0 = +_[0][1], x1 = +_[1][0], y1 = +_[1][1]);
+        if (stream) stream.valid = false, stream = null;
+        return clipExtent;
+      }
+    };
+    return clipExtent.extent([ [ 0, 0 ], [ 960, 500 ] ]);
+  };
+  function d3_geo_clipExtent(x0, y0, x1, y1) {
+    return function(listener) {
+      var listener_ = listener, bufferListener = d3_geo_clipBufferListener(), clipLine = d3_geom_clipLine(x0, y0, x1, y1), segments, polygon, ring;
+      var clip = {
+        point: point,
+        lineStart: lineStart,
+        lineEnd: lineEnd,
+        polygonStart: function() {
+          listener = bufferListener;
+          segments = [];
+          polygon = [];
+          clean = true;
+        },
+        polygonEnd: function() {
+          listener = listener_;
+          segments = d3.merge(segments);
+          var clipStartInside = insidePolygon([ x0, y1 ]), inside = clean && clipStartInside, visible = segments.length;
+          if (inside || visible) {
+            listener.polygonStart();
+            if (inside) {
+              listener.lineStart();
+              interpolate(null, null, 1, listener);
+              listener.lineEnd();
+            }
+            if (visible) {
+              d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener);
+            }
+            listener.polygonEnd();
+          }
+          segments = polygon = ring = null;
+        }
+      };
+      function insidePolygon(p) {
+        var wn = 0, n = polygon.length, y = p[1];
+        for (var i = 0; i < n; ++i) {
+          for (var j = 1, v = polygon[i], m = v.length, a = v[0], b; j < m; ++j) {
+            b = v[j];
+            if (a[1] <= y) {
+              if (b[1] > y && d3_cross2d(a, b, p) > 0) ++wn;
+            } else {
+              if (b[1] <= y && d3_cross2d(a, b, p) < 0) --wn;
+            }
+            a = b;
+          }
+        }
+        return wn !== 0;
+      }
+      function interpolate(from, to, direction, listener) {
+        var a = 0, a1 = 0;
+        if (from == null || (a = corner(from, direction)) !== (a1 = corner(to, direction)) || comparePoints(from, to) < 0 ^ direction > 0) {
+          do {
+            listener.point(a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0);
+          } while ((a = (a + direction + 4) % 4) !== a1);
+        } else {
+          listener.point(to[0], to[1]);
+        }
+      }
+      function pointVisible(x, y) {
+        return x0 <= x && x <= x1 && y0 <= y && y <= y1;
+      }
+      function point(x, y) {
+        if (pointVisible(x, y)) listener.point(x, y);
+      }
+      var x__, y__, v__, x_, y_, v_, first, clean;
+      function lineStart() {
+        clip.point = linePoint;
+        if (polygon) polygon.push(ring = []);
+        first = true;
+        v_ = false;
+        x_ = y_ = NaN;
+      }
+      function lineEnd() {
+        if (segments) {
+          linePoint(x__, y__);
+          if (v__ && v_) bufferListener.rejoin();
+          segments.push(bufferListener.buffer());
+        }
+        clip.point = point;
+        if (v_) listener.lineEnd();
+      }
+      function linePoint(x, y) {
+        x = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, x));
+        y = Math.max(-d3_geo_clipExtentMAX, Math.min(d3_geo_clipExtentMAX, y));
+        var v = pointVisible(x, y);
+        if (polygon) ring.push([ x, y ]);
+        if (first) {
+          x__ = x, y__ = y, v__ = v;
+          first = false;
+          if (v) {
+            listener.lineStart();
+            listener.point(x, y);
+          }
+        } else {
+          if (v && v_) listener.point(x, y); else {
+            var l = {
+              a: {
+                x: x_,
+                y: y_
+              },
+              b: {
+                x: x,
+                y: y
+              }
+            };
+            if (clipLine(l)) {
+              if (!v_) {
+                listener.lineStart();
+                listener.point(l.a.x, l.a.y);
+              }
+              listener.point(l.b.x, l.b.y);
+              if (!v) listener.lineEnd();
+              clean = false;
+            } else if (v) {
+              listener.lineStart();
+              listener.point(x, y);
+              clean = false;
+            }
+          }
+        }
+        x_ = x, y_ = y, v_ = v;
+      }
+      return clip;
+    };
+    function corner(p, direction) {
+      return abs(p[0] - x0) < ε ? direction > 0 ? 0 : 3 : abs(p[0] - x1) < ε ? direction > 0 ? 2 : 1 : abs(p[1] - y0) < ε ? direction > 0 ? 1 : 0 : direction > 0 ? 3 : 2;
+    }
+    function compare(a, b) {
+      return comparePoints(a.x, b.x);
+    }
+    function comparePoints(a, b) {
+      var ca = corner(a, 1), cb = corner(b, 1);
+      return ca !== cb ? ca - cb : ca === 0 ? b[1] - a[1] : ca === 1 ? a[0] - b[0] : ca === 2 ? a[1] - b[1] : b[0] - a[0];
+    }
+  }
+  function d3_geo_conic(projectAt) {
+    var φ0 = 0, φ1 = π / 3, m = d3_geo_projectionMutator(projectAt), p = m(φ0, φ1);
+    p.parallels = function(_) {
+      if (!arguments.length) return [ φ0 / π * 180, φ1 / π * 180 ];
+      return m(φ0 = _[0] * π / 180, φ1 = _[1] * π / 180);
+    };
+    return p;
+  }
+  function d3_geo_conicEqualArea(φ0, φ1) {
+    var sinφ0 = Math.sin(φ0), n = (sinφ0 + Math.sin(φ1)) / 2, C = 1 + sinφ0 * (2 * n - sinφ0), ρ0 = Math.sqrt(C) / n;
+    function forward(λ, φ) {
+      var ρ = Math.sqrt(C - 2 * n * Math.sin(φ)) / n;
+      return [ ρ * Math.sin(λ *= n), ρ0 - ρ * Math.cos(λ) ];
+    }
+    forward.invert = function(x, y) {
+      var ρ0_y = ρ0 - y;
+      return [ Math.atan2(x, ρ0_y) / n, d3_asin((C - (x * x + ρ0_y * ρ0_y) * n * n) / (2 * n)) ];
+    };
+    return forward;
+  }
+  (d3.geo.conicEqualArea = function() {
+    return d3_geo_conic(d3_geo_conicEqualArea);
+  }).raw = d3_geo_conicEqualArea;
+  d3.geo.albers = function() {
+    return d3.geo.conicEqualArea().rotate([ 96, 0 ]).center([ -.6, 38.7 ]).parallels([ 29.5, 45.5 ]).scale(1070);
+  };
+  d3.geo.albersUsa = function() {
+    var lower48 = d3.geo.albers();
+    var alaska = d3.geo.conicEqualArea().rotate([ 154, 0 ]).center([ -2, 58.5 ]).parallels([ 55, 65 ]);
+    var hawaii = d3.geo.conicEqualArea().rotate([ 157, 0 ]).center([ -3, 19.9 ]).parallels([ 8, 18 ]);
+    var point, pointStream = {
+      point: function(x, y) {
+        point = [ x, y ];
+      }
+    }, lower48Point, alaskaPoint, hawaiiPoint;
+    function albersUsa(coordinates) {
+      var x = coordinates[0], y = coordinates[1];
+      point = null;
+      (lower48Point(x, y), point) || (alaskaPoint(x, y), point) || hawaiiPoint(x, y);
+      return point;
+    }
+    albersUsa.invert = function(coordinates) {
+      var k = lower48.scale(), t = lower48.translate(), x = (coordinates[0] - t[0]) / k, y = (coordinates[1] - t[1]) / k;
+      return (y >= .12 && y < .234 && x >= -.425 && x < -.214 ? alaska : y >= .166 && y < .234 && x >= -.214 && x < -.115 ? hawaii : lower48).invert(coordinates);
+    };
+    albersUsa.stream = function(stream) {
+      var lower48Stream = lower48.stream(stream), alaskaStream = alaska.stream(stream), hawaiiStream = hawaii.stream(stream);
+      return {
+        point: function(x, y) {
+          lower48Stream.point(x, y);
+          alaskaStream.point(x, y);
+          hawaiiStream.point(x, y);
+        },
+        sphere: function() {
+          lower48Stream.sphere();
+          alaskaStream.sphere();
+          hawaiiStream.sphere();
+        },
+        lineStart: function() {
+          lower48Stream.lineStart();
+          alaskaStream.lineStart();
+          hawaiiStream.lineStart();
+        },
+        lineEnd: function() {
+          lower48Stream.lineEnd();
+          alaskaStream.lineEnd();
+          hawaiiStream.lineEnd();
+        },
+        polygonStart: function() {
+          lower48Stream.polygonStart();
+          alaskaStream.polygonStart();
+          hawaiiStream.polygonStart();
+        },
+        polygonEnd: function() {
+          lower48Stream.polygonEnd();
+          alaskaStream.polygonEnd();
+          hawaiiStream.polygonEnd();
+        }
+      };
+    };
+    albersUsa.precision = function(_) {
+      if (!arguments.length) return lower48.precision();
+      lower48.precision(_);
+      alaska.precision(_);
+      hawaii.precision(_);
+      return albersUsa;
+    };
+    albersUsa.scale = function(_) {
+      if (!arguments.length) return lower48.scale();
+      lower48.scale(_);
+      alaska.scale(_ * .35);
+      hawaii.scale(_);
+      return albersUsa.translate(lower48.translate());
+    };
+    albersUsa.translate = function(_) {
+      if (!arguments.length) return lower48.translate();
+      var k = lower48.scale(), x = +_[0], y = +_[1];
+      lower48Point = lower48.translate(_).clipExtent([ [ x - .455 * k, y - .238 * k ], [ x + .455 * k, y + .238 * k ] ]).stream(pointStream).point;
+      alaskaPoint = alaska.translate([ x - .307 * k, y + .201 * k ]).clipExtent([ [ x - .425 * k + ε, y + .12 * k + ε ], [ x - .214 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point;
+      hawaiiPoint = hawaii.translate([ x - .205 * k, y + .212 * k ]).clipExtent([ [ x - .214 * k + ε, y + .166 * k + ε ], [ x - .115 * k - ε, y + .234 * k - ε ] ]).stream(pointStream).point;
+      return albersUsa;
+    };
+    return albersUsa.scale(1070);
+  };
+  var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = {
+    point: d3_noop,
+    lineStart: d3_noop,
+    lineEnd: d3_noop,
+    polygonStart: function() {
+      d3_geo_pathAreaPolygon = 0;
+      d3_geo_pathArea.lineStart = d3_geo_pathAreaRingStart;
+    },
+    polygonEnd: function() {
+      d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop;
+      d3_geo_pathAreaSum += abs(d3_geo_pathAreaPolygon / 2);
+    }
+  };
+  function d3_geo_pathAreaRingStart() {
+    var x00, y00, x0, y0;
+    d3_geo_pathArea.point = function(x, y) {
+      d3_geo_pathArea.point = nextPoint;
+      x00 = x0 = x, y00 = y0 = y;
+    };
+    function nextPoint(x, y) {
+      d3_geo_pathAreaPolygon += y0 * x - x0 * y;
+      x0 = x, y0 = y;
+    }
+    d3_geo_pathArea.lineEnd = function() {
+      nextPoint(x00, y00);
+    };
+  }
+  var d3_geo_pathBoundsX0, d3_geo_pathBoundsY0, d3_geo_pathBoundsX1, d3_geo_pathBoundsY1;
+  var d3_geo_pathBounds = {
+    point: d3_geo_pathBoundsPoint,
+    lineStart: d3_noop,
+    lineEnd: d3_noop,
+    polygonStart: d3_noop,
+    polygonEnd: d3_noop
+  };
+  function d3_geo_pathBoundsPoint(x, y) {
+    if (x < d3_geo_pathBoundsX0) d3_geo_pathBoundsX0 = x;
+    if (x > d3_geo_pathBoundsX1) d3_geo_pathBoundsX1 = x;
+    if (y < d3_geo_pathBoundsY0) d3_geo_pathBoundsY0 = y;
+    if (y > d3_geo_pathBoundsY1) d3_geo_pathBoundsY1 = y;
+  }
+  function d3_geo_pathBuffer() {
+    var pointCircle = d3_geo_pathBufferCircle(4.5), buffer = [];
+    var stream = {
+      point: point,
+      lineStart: function() {
+        stream.point = pointLineStart;
+      },
+      lineEnd: lineEnd,
+      polygonStart: function() {
+        stream.lineEnd = lineEndPolygon;
+      },
+      polygonEnd: function() {
+        stream.lineEnd = lineEnd;
+        stream.point = point;
+      },
+      pointRadius: function(_) {
+        pointCircle = d3_geo_pathBufferCircle(_);
+        return stream;
+      },
+      result: function() {
+        if (buffer.length) {
+          var result = buffer.join("");
+          buffer = [];
+          return result;
+        }
+      }
+    };
+    function point(x, y) {
+      buffer.push("M", x, ",", y, pointCircle);
+    }
+    function pointLineStart(x, y) {
+      buffer.push("M", x, ",", y);
+      stream.point = pointLine;
+    }
+    function pointLine(x, y) {
+      buffer.push("L", x, ",", y);
+    }
+    function lineEnd() {
+      stream.point = point;
+    }
+    function lineEndPolygon() {
+      buffer.push("Z");
+    }
+    return stream;
+  }
+  function d3_geo_pathBufferCircle(radius) {
+    return "m0," + radius + "a" + radius + "," + radius + " 0 1,1 0," + -2 * radius + "a" + radius + "," + radius + " 0 1,1 0," + 2 * radius + "z";
+  }
+  var d3_geo_pathCentroid = {
+    point: d3_geo_pathCentroidPoint,
+    lineStart: d3_geo_pathCentroidLineStart,
+    lineEnd: d3_geo_pathCentroidLineEnd,
+    polygonStart: function() {
+      d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidRingStart;
+    },
+    polygonEnd: function() {
+      d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint;
+      d3_geo_pathCentroid.lineStart = d3_geo_pathCentroidLineStart;
+      d3_geo_pathCentroid.lineEnd = d3_geo_pathCentroidLineEnd;
+    }
+  };
+  function d3_geo_pathCentroidPoint(x, y) {
+    d3_geo_centroidX0 += x;
+    d3_geo_centroidY0 += y;
+    ++d3_geo_centroidZ0;
+  }
+  function d3_geo_pathCentroidLineStart() {
+    var x0, y0;
+    d3_geo_pathCentroid.point = function(x, y) {
+      d3_geo_pathCentroid.point = nextPoint;
+      d3_geo_pathCentroidPoint(x0 = x, y0 = y);
+    };
+    function nextPoint(x, y) {
+      var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy);
+      d3_geo_centroidX1 += z * (x0 + x) / 2;
+      d3_geo_centroidY1 += z * (y0 + y) / 2;
+      d3_geo_centroidZ1 += z;
+      d3_geo_pathCentroidPoint(x0 = x, y0 = y);
+    }
+  }
+  function d3_geo_pathCentroidLineEnd() {
+    d3_geo_pathCentroid.point = d3_geo_pathCentroidPoint;
+  }
+  function d3_geo_pathCentroidRingStart() {
+    var x00, y00, x0, y0;
+    d3_geo_pathCentroid.point = function(x, y) {
+      d3_geo_pathCentroid.point = nextPoint;
+      d3_geo_pathCentroidPoint(x00 = x0 = x, y00 = y0 = y);
+    };
+    function nextPoint(x, y) {
+      var dx = x - x0, dy = y - y0, z = Math.sqrt(dx * dx + dy * dy);
+      d3_geo_centroidX1 += z * (x0 + x) / 2;
+      d3_geo_centroidY1 += z * (y0 + y) / 2;
+      d3_geo_centroidZ1 += z;
+      z = y0 * x - x0 * y;
+      d3_geo_centroidX2 += z * (x0 + x);
+      d3_geo_centroidY2 += z * (y0 + y);
+      d3_geo_centroidZ2 += z * 3;
+      d3_geo_pathCentroidPoint(x0 = x, y0 = y);
+    }
+    d3_geo_pathCentroid.lineEnd = function() {
+      nextPoint(x00, y00);
+    };
+  }
+  function d3_geo_pathContext(context) {
+    var pointRadius = 4.5;
+    var stream = {
+      point: point,
+      lineStart: function() {
+        stream.point = pointLineStart;
+      },
+      lineEnd: lineEnd,
+      polygonStart: function() {
+        stream.lineEnd = lineEndPolygon;
+      },
+      polygonEnd: function() {
+        stream.lineEnd = lineEnd;
+        stream.point = point;
+      },
+      pointRadius: function(_) {
+        pointRadius = _;
+        return stream;
+      },
+      result: d3_noop
+    };
+    function point(x, y) {
+      context.moveTo(x + pointRadius, y);
+      context.arc(x, y, pointRadius, 0, Ï„);
+    }
+    function pointLineStart(x, y) {
+      context.moveTo(x, y);
+      stream.point = pointLine;
+    }
+    function pointLine(x, y) {
+      context.lineTo(x, y);
+    }
+    function lineEnd() {
+      stream.point = point;
+    }
+    function lineEndPolygon() {
+      context.closePath();
+    }
+    return stream;
+  }
+  function d3_geo_resample(project) {
+    var δ2 = .5, cosMinDistance = Math.cos(30 * d3_radians), maxDepth = 16;
+    function resample(stream) {
+      return (maxDepth ? resampleRecursive : resampleNone)(stream);
+    }
+    function resampleNone(stream) {
+      return d3_geo_transformPoint(stream, function(x, y) {
+        x = project(x, y);
+        stream.point(x[0], x[1]);
+      });
+    }
+    function resampleRecursive(stream) {
+      var λ00, φ00, x00, y00, a00, b00, c00, λ0, x0, y0, a0, b0, c0;
+      var resample = {
+        point: point,
+        lineStart: lineStart,
+        lineEnd: lineEnd,
+        polygonStart: function() {
+          stream.polygonStart();
+          resample.lineStart = ringStart;
+        },
+        polygonEnd: function() {
+          stream.polygonEnd();
+          resample.lineStart = lineStart;
+        }
+      };
+      function point(x, y) {
+        x = project(x, y);
+        stream.point(x[0], x[1]);
+      }
+      function lineStart() {
+        x0 = NaN;
+        resample.point = linePoint;
+        stream.lineStart();
+      }
+      function linePoint(λ, φ) {
+        var c = d3_geo_cartesian([ λ, φ ]), p = project(λ, φ);
+        resampleLineTo(x0, y0, λ0, a0, b0, c0, x0 = p[0], y0 = p[1], λ0 = λ, a0 = c[0], b0 = c[1], c0 = c[2], maxDepth, stream);
+        stream.point(x0, y0);
+      }
+      function lineEnd() {
+        resample.point = point;
+        stream.lineEnd();
+      }
+      function ringStart() {
+        lineStart();
+        resample.point = ringPoint;
+        resample.lineEnd = ringEnd;
+      }
+      function ringPoint(λ, φ) {
+        linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0;
+        resample.point = linePoint;
+      }
+      function ringEnd() {
+        resampleLineTo(x0, y0, λ0, a0, b0, c0, x00, y00, λ00, a00, b00, c00, maxDepth, stream);
+        resample.lineEnd = lineEnd;
+        lineEnd();
+      }
+      return resample;
+    }
+    function resampleLineTo(x0, y0, λ0, a0, b0, c0, x1, y1, λ1, a1, b1, c1, depth, stream) {
+      var dx = x1 - x0, dy = y1 - y0, d2 = dx * dx + dy * dy;
+      if (d2 > 4 * δ2 && depth--) {
+        var a = a0 + a1, b = b0 + b1, c = c0 + c1, m = Math.sqrt(a * a + b * b + c * c), φ2 = Math.asin(c /= m), λ2 = abs(abs(c) - 1) < ε || abs(λ0 - λ1) < ε ? (λ0 + λ1) / 2 : Math.atan2(b, a), p = project(λ2, φ2), x2 = p[0], y2 = p[1], dx2 = x2 - x0, dy2 = y2 - y0, dz = dy * dx2 - dx * dy2;
+        if (dz * dz / d2 > δ2 || abs((dx * dx2 + dy * dy2) / d2 - .5) > .3 || a0 * a1 + b0 * b1 + c0 * c1 < cosMinDistance) {
+          resampleLineTo(x0, y0, λ0, a0, b0, c0, x2, y2, λ2, a /= m, b /= m, c, depth, stream);
+          stream.point(x2, y2);
+          resampleLineTo(x2, y2, λ2, a, b, c, x1, y1, λ1, a1, b1, c1, depth, stream);
+        }
+      }
+    }
+    resample.precision = function(_) {
+      if (!arguments.length) return Math.sqrt(δ2);
+      maxDepth = (δ2 = _ * _) > 0 && 16;
+      return resample;
+    };
+    return resample;
+  }
+  d3.geo.path = function() {
+    var pointRadius = 4.5, projection, context, projectStream, contextStream, cacheStream;
+    function path(object) {
+      if (object) {
+        if (typeof pointRadius === "function") contextStream.pointRadius(+pointRadius.apply(this, arguments));
+        if (!cacheStream || !cacheStream.valid) cacheStream = projectStream(contextStream);
+        d3.geo.stream(object, cacheStream);
+      }
+      return contextStream.result();
+    }
+    path.area = function(object) {
+      d3_geo_pathAreaSum = 0;
+      d3.geo.stream(object, projectStream(d3_geo_pathArea));
+      return d3_geo_pathAreaSum;
+    };
+    path.centroid = function(object) {
+      d3_geo_centroidX0 = d3_geo_centroidY0 = d3_geo_centroidZ0 = d3_geo_centroidX1 = d3_geo_centroidY1 = d3_geo_centroidZ1 = d3_geo_centroidX2 = d3_geo_centroidY2 = d3_geo_centroidZ2 = 0;
+      d3.geo.stream(object, projectStream(d3_geo_pathCentroid));
+      return d3_geo_centroidZ2 ? [ d3_geo_centroidX2 / d3_geo_centroidZ2, d3_geo_centroidY2 / d3_geo_centroidZ2 ] : d3_geo_centroidZ1 ? [ d3_geo_centroidX1 / d3_geo_centroidZ1, d3_geo_centroidY1 / d3_geo_centroidZ1 ] : d3_geo_centroidZ0 ? [ d3_geo_centroidX0 / d3_geo_centroidZ0, d3_geo_centroidY0 / d3_geo_centroidZ0 ] : [ NaN, NaN ];
+    };
+    path.bounds = function(object) {
+      d3_geo_pathBoundsX1 = d3_geo_pathBoundsY1 = -(d3_geo_pathBoundsX0 = d3_geo_pathBoundsY0 = Infinity);
+      d3.geo.stream(object, projectStream(d3_geo_pathBounds));
+      return [ [ d3_geo_pathBoundsX0, d3_geo_pathBoundsY0 ], [ d3_geo_pathBoundsX1, d3_geo_pathBoundsY1 ] ];
+    };
+    path.projection = function(_) {
+      if (!arguments.length) return projection;
+      projectStream = (projection = _) ? _.stream || d3_geo_pathProjectStream(_) : d3_identity;
+      return reset();
+    };
+    path.context = function(_) {
+      if (!arguments.length) return context;
+      contextStream = (context = _) == null ? new d3_geo_pathBuffer() : new d3_geo_pathContext(_);
+      if (typeof pointRadius !== "function") contextStream.pointRadius(pointRadius);
+      return reset();
+    };
+    path.pointRadius = function(_) {
+      if (!arguments.length) return pointRadius;
+      pointRadius = typeof _ === "function" ? _ : (contextStream.pointRadius(+_), +_);
+      return path;
+    };
+    function reset() {
+      cacheStream = null;
+      return path;
+    }
+    return path.projection(d3.geo.albersUsa()).context(null);
+  };
+  function d3_geo_pathProjectStream(project) {
+    var resample = d3_geo_resample(function(x, y) {
+      return project([ x * d3_degrees, y * d3_degrees ]);
+    });
+    return function(stream) {
+      return d3_geo_projectionRadians(resample(stream));
+    };
+  }
+  d3.geo.transform = function(methods) {
+    return {
+      stream: function(stream) {
+        var transform = new d3_geo_transform(stream);
+        for (var k in methods) transform[k] = methods[k];
+        return transform;
+      }
+    };
+  };
+  function d3_geo_transform(stream) {
+    this.stream = stream;
+  }
+  d3_geo_transform.prototype = {
+    point: function(x, y) {
+      this.stream.point(x, y);
+    },
+    sphere: function() {
+      this.stream.sphere();
+    },
+    lineStart: function() {
+      this.stream.lineStart();
+    },
+    lineEnd: function() {
+      this.stream.lineEnd();
+    },
+    polygonStart: function() {
+      this.stream.polygonStart();
+    },
+    polygonEnd: function() {
+      this.stream.polygonEnd();
+    }
+  };
+  function d3_geo_transformPoint(stream, point) {
+    return {
+      point: point,
+      sphere: function() {
+        stream.sphere();
+      },
+      lineStart: function() {
+        stream.lineStart();
+      },
+      lineEnd: function() {
+        stream.lineEnd();
+      },
+      polygonStart: function() {
+        stream.polygonStart();
+      },
+      polygonEnd: function() {
+        stream.polygonEnd();
+      }
+    };
+  }
+  d3.geo.projection = d3_geo_projection;
+  d3.geo.projectionMutator = d3_geo_projectionMutator;
+  function d3_geo_projection(project) {
+    return d3_geo_projectionMutator(function() {
+      return project;
+    })();
+  }
+  function d3_geo_projectionMutator(projectAt) {
+    var project, rotate, projectRotate, projectResample = d3_geo_resample(function(x, y) {
+      x = project(x, y);
+      return [ x[0] * k + δx, δy - x[1] * k ];
+    }), k = 150, x = 480, y = 250, λ = 0, φ = 0, δλ = 0, δφ = 0, δγ = 0, δx, δy, preclip = d3_geo_clipAntimeridian, postclip = d3_identity, clipAngle = null, clipExtent = null, stream;
+    function projection(point) {
+      point = projectRotate(point[0] * d3_radians, point[1] * d3_radians);
+      return [ point[0] * k + δx, δy - point[1] * k ];
+    }
+    function invert(point) {
+      point = projectRotate.invert((point[0] - δx) / k, (δy - point[1]) / k);
+      return point && [ point[0] * d3_degrees, point[1] * d3_degrees ];
+    }
+    projection.stream = function(output) {
+      if (stream) stream.valid = false;
+      stream = d3_geo_projectionRadians(preclip(rotate, projectResample(postclip(output))));
+      stream.valid = true;
+      return stream;
+    };
+    projection.clipAngle = function(_) {
+      if (!arguments.length) return clipAngle;
+      preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians);
+      return invalidate();
+    };
+    projection.clipExtent = function(_) {
+      if (!arguments.length) return clipExtent;
+      clipExtent = _;
+      postclip = _ ? d3_geo_clipExtent(_[0][0], _[0][1], _[1][0], _[1][1]) : d3_identity;
+      return invalidate();
+    };
+    projection.scale = function(_) {
+      if (!arguments.length) return k;
+      k = +_;
+      return reset();
+    };
+    projection.translate = function(_) {
+      if (!arguments.length) return [ x, y ];
+      x = +_[0];
+      y = +_[1];
+      return reset();
+    };
+    projection.center = function(_) {
+      if (!arguments.length) return [ λ * d3_degrees, φ * d3_degrees ];
+      λ = _[0] % 360 * d3_radians;
+      φ = _[1] % 360 * d3_radians;
+      return reset();
+    };
+    projection.rotate = function(_) {
+      if (!arguments.length) return [ δλ * d3_degrees, δφ * d3_degrees, δγ * d3_degrees ];
+      δλ = _[0] % 360 * d3_radians;
+      δφ = _[1] % 360 * d3_radians;
+      δγ = _.length > 2 ? _[2] % 360 * d3_radians : 0;
+      return reset();
+    };
+    d3.rebind(projection, projectResample, "precision");
+    function reset() {
+      projectRotate = d3_geo_compose(rotate = d3_geo_rotation(δλ, δφ, δγ), project);
+      var center = project(λ, φ);
+      δx = x - center[0] * k;
+      δy = y + center[1] * k;
+      return invalidate();
+    }
+    function invalidate() {
+      if (stream) stream.valid = false, stream = null;
+      return projection;
+    }
+    return function() {
+      project = projectAt.apply(this, arguments);
+      projection.invert = project.invert && invert;
+      return reset();
+    };
+  }
+  function d3_geo_projectionRadians(stream) {
+    return d3_geo_transformPoint(stream, function(x, y) {
+      stream.point(x * d3_radians, y * d3_radians);
+    });
+  }
+  function d3_geo_equirectangular(λ, φ) {
+    return [ λ, φ ];
+  }
+  (d3.geo.equirectangular = function() {
+    return d3_geo_projection(d3_geo_equirectangular);
+  }).raw = d3_geo_equirectangular.invert = d3_geo_equirectangular;
+  d3.geo.rotation = function(rotate) {
+    rotate = d3_geo_rotation(rotate[0] % 360 * d3_radians, rotate[1] * d3_radians, rotate.length > 2 ? rotate[2] * d3_radians : 0);
+    function forward(coordinates) {
+      coordinates = rotate(coordinates[0] * d3_radians, coordinates[1] * d3_radians);
+      return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates;
+    }
+    forward.invert = function(coordinates) {
+      coordinates = rotate.invert(coordinates[0] * d3_radians, coordinates[1] * d3_radians);
+      return coordinates[0] *= d3_degrees, coordinates[1] *= d3_degrees, coordinates;
+    };
+    return forward;
+  };
+  function d3_geo_identityRotation(λ, φ) {
+    return [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ];
+  }
+  d3_geo_identityRotation.invert = d3_geo_equirectangular;
+  function d3_geo_rotation(δλ, δφ, δγ) {
+    return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_identityRotation;
+  }
+  function d3_geo_forwardRotationλ(δλ) {
+    return function(λ, φ) {
+      return λ += δλ, [ λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ ];
+    };
+  }
+  function d3_geo_rotationλ(δλ) {
+    var rotation = d3_geo_forwardRotationλ(δλ);
+    rotation.invert = d3_geo_forwardRotationλ(-δλ);
+    return rotation;
+  }
+  function d3_geo_rotationφγ(δφ, δγ) {
+    var cosδφ = Math.cos(δφ), sinδφ = Math.sin(δφ), cosδγ = Math.cos(δγ), sinδγ = Math.sin(δγ);
+    function rotation(λ, φ) {
+      var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδφ + x * sinδφ;
+      return [ Math.atan2(y * cosδγ - k * sinδγ, x * cosδφ - z * sinδφ), d3_asin(k * cosδγ + y * sinδγ) ];
+    }
+    rotation.invert = function(λ, φ) {
+      var cosφ = Math.cos(φ), x = Math.cos(λ) * cosφ, y = Math.sin(λ) * cosφ, z = Math.sin(φ), k = z * cosδγ - y * sinδγ;
+      return [ Math.atan2(y * cosδγ + z * sinδγ, x * cosδφ + k * sinδφ), d3_asin(k * cosδφ - x * sinδφ) ];
+    };
+    return rotation;
+  }
+  d3.geo.circle = function() {
+    var origin = [ 0, 0 ], angle, precision = 6, interpolate;
+    function circle() {
+      var center = typeof origin === "function" ? origin.apply(this, arguments) : origin, rotate = d3_geo_rotation(-center[0] * d3_radians, -center[1] * d3_radians, 0).invert, ring = [];
+      interpolate(null, null, 1, {
+        point: function(x, y) {
+          ring.push(x = rotate(x, y));
+          x[0] *= d3_degrees, x[1] *= d3_degrees;
+        }
+      });
+      return {
+        type: "Polygon",
+        coordinates: [ ring ]
+      };
+    }
+    circle.origin = function(x) {
+      if (!arguments.length) return origin;
+      origin = x;
+      return circle;
+    };
+    circle.angle = function(x) {
+      if (!arguments.length) return angle;
+      interpolate = d3_geo_circleInterpolate((angle = +x) * d3_radians, precision * d3_radians);
+      return circle;
+    };
+    circle.precision = function(_) {
+      if (!arguments.length) return precision;
+      interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians);
+      return circle;
+    };
+    return circle.angle(90);
+  };
+  function d3_geo_circleInterpolate(radius, precision) {
+    var cr = Math.cos(radius), sr = Math.sin(radius);
+    return function(from, to, direction, listener) {
+      var step = direction * precision;
+      if (from != null) {
+        from = d3_geo_circleAngle(cr, from);
+        to = d3_geo_circleAngle(cr, to);
+        if (direction > 0 ? from < to : from > to) from += direction * Ï„;
+      } else {
+        from = radius + direction * Ï„;
+        to = radius - .5 * step;
+      }
+      for (var point, t = from; direction > 0 ? t > to : t < to; t -= step) {
+        listener.point((point = d3_geo_spherical([ cr, -sr * Math.cos(t), -sr * Math.sin(t) ]))[0], point[1]);
+      }
+    };
+  }
+  function d3_geo_circleAngle(cr, point) {
+    var a = d3_geo_cartesian(point);
+    a[0] -= cr;
+    d3_geo_cartesianNormalize(a);
+    var angle = d3_acos(-a[1]);
+    return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI);
+  }
+  d3.geo.distance = function(a, b) {
+    var Δλ = (b[0] - a[0]) * d3_radians, φ0 = a[1] * d3_radians, φ1 = b[1] * d3_radians, sinΔλ = Math.sin(Δλ), cosΔλ = Math.cos(Δλ), sinφ0 = Math.sin(φ0), cosφ0 = Math.cos(φ0), sinφ1 = Math.sin(φ1), cosφ1 = Math.cos(φ1), t;
+    return Math.atan2(Math.sqrt((t = cosφ1 * sinΔλ) * t + (t = cosφ0 * sinφ1 - sinφ0 * cosφ1 * cosΔλ) * t), sinφ0 * sinφ1 + cosφ0 * cosφ1 * cosΔλ);
+  };
+  d3.geo.graticule = function() {
+    var x1, x0, X1, X0, y1, y0, Y1, Y0, dx = 10, dy = dx, DX = 90, DY = 360, x, y, X, Y, precision = 2.5;
+    function graticule() {
+      return {
+        type: "MultiLineString",
+        coordinates: lines()
+      };
+    }
+    function lines() {
+      return d3.range(Math.ceil(X0 / DX) * DX, X1, DX).map(X).concat(d3.range(Math.ceil(Y0 / DY) * DY, Y1, DY).map(Y)).concat(d3.range(Math.ceil(x0 / dx) * dx, x1, dx).filter(function(x) {
+        return abs(x % DX) > ε;
+      }).map(x)).concat(d3.range(Math.ceil(y0 / dy) * dy, y1, dy).filter(function(y) {
+        return abs(y % DY) > ε;
+      }).map(y));
+    }
+    graticule.lines = function() {
+      return lines().map(function(coordinates) {
+        return {
+          type: "LineString",
+          coordinates: coordinates
+        };
+      });
+    };
+    graticule.outline = function() {
+      return {
+        type: "Polygon",
+        coordinates: [ X(X0).concat(Y(Y1).slice(1), X(X1).reverse().slice(1), Y(Y0).reverse().slice(1)) ]
+      };
+    };
+    graticule.extent = function(_) {
+      if (!arguments.length) return graticule.minorExtent();
+      return graticule.majorExtent(_).minorExtent(_);
+    };
+    graticule.majorExtent = function(_) {
+      if (!arguments.length) return [ [ X0, Y0 ], [ X1, Y1 ] ];
+      X0 = +_[0][0], X1 = +_[1][0];
+      Y0 = +_[0][1], Y1 = +_[1][1];
+      if (X0 > X1) _ = X0, X0 = X1, X1 = _;
+      if (Y0 > Y1) _ = Y0, Y0 = Y1, Y1 = _;
+      return graticule.precision(precision);
+    };
+    graticule.minorExtent = function(_) {
+      if (!arguments.length) return [ [ x0, y0 ], [ x1, y1 ] ];
+      x0 = +_[0][0], x1 = +_[1][0];
+      y0 = +_[0][1], y1 = +_[1][1];
+      if (x0 > x1) _ = x0, x0 = x1, x1 = _;
+      if (y0 > y1) _ = y0, y0 = y1, y1 = _;
+      return graticule.precision(precision);
+    };
+    graticule.step = function(_) {
+      if (!arguments.length) return graticule.minorStep();
+      return graticule.majorStep(_).minorStep(_);
+    };
+    graticule.majorStep = function(_) {
+      if (!arguments.length) return [ DX, DY ];
+      DX = +_[0], DY = +_[1];
+      return graticule;
+    };
+    graticule.minorStep = function(_) {
+      if (!arguments.length) return [ dx, dy ];
+      dx = +_[0], dy = +_[1];
+      return graticule;
+    };
+    graticule.precision = function(_) {
+      if (!arguments.length) return precision;
+      precision = +_;
+      x = d3_geo_graticuleX(y0, y1, 90);
+      y = d3_geo_graticuleY(x0, x1, precision);
+      X = d3_geo_graticuleX(Y0, Y1, 90);
+      Y = d3_geo_graticuleY(X0, X1, precision);
+      return graticule;
+    };
+    return graticule.majorExtent([ [ -180, -90 + ε ], [ 180, 90 - ε ] ]).minorExtent([ [ -180, -80 - ε ], [ 180, 80 + ε ] ]);
+  };
+  function d3_geo_graticuleX(y0, y1, dy) {
+    var y = d3.range(y0, y1 - ε, dy).concat(y1);
+    return function(x) {
+      return y.map(function(y) {
+        return [ x, y ];
+      });
+    };
+  }
+  function d3_geo_graticuleY(x0, x1, dx) {
+    var x = d3.range(x0, x1 - ε, dx).concat(x1);
+    return function(y) {
+      return x.map(function(x) {
+        return [ x, y ];
+      });
+    };
+  }
+  function d3_source(d) {
+    return d.source;
+  }
+  function d3_target(d) {
+    return d.target;
+  }
+  d3.geo.greatArc = function() {
+    var source = d3_source, source_, target = d3_target, target_;
+    function greatArc() {
+      return {
+        type: "LineString",
+        coordinates: [ source_ || source.apply(this, arguments), target_ || target.apply(this, arguments) ]
+      };
+    }
+    greatArc.distance = function() {
+      return d3.geo.distance(source_ || source.apply(this, arguments), target_ || target.apply(this, arguments));
+    };
+    greatArc.source = function(_) {
+      if (!arguments.length) return source;
+      source = _, source_ = typeof _ === "function" ? null : _;
+      return greatArc;
+    };
+    greatArc.target = function(_) {
+      if (!arguments.length) return target;
+      target = _, target_ = typeof _ === "function" ? null : _;
+      return greatArc;
+    };
+    greatArc.precision = function() {
+      return arguments.length ? greatArc : 0;
+    };
+    return greatArc;
+  };
+  d3.geo.interpolate = function(source, target) {
+    return d3_geo_interpolate(source[0] * d3_radians, source[1] * d3_radians, target[0] * d3_radians, target[1] * d3_radians);
+  };
+  function d3_geo_interpolate(x0, y0, x1, y1) {
+    var cy0 = Math.cos(y0), sy0 = Math.sin(y0), cy1 = Math.cos(y1), sy1 = Math.sin(y1), kx0 = cy0 * Math.cos(x0), ky0 = cy0 * Math.sin(x0), kx1 = cy1 * Math.cos(x1), ky1 = cy1 * Math.sin(x1), d = 2 * Math.asin(Math.sqrt(d3_haversin(y1 - y0) + cy0 * cy1 * d3_haversin(x1 - x0))), k = 1 / Math.sin(d);
+    var interpolate = d ? function(t) {
+      var B = Math.sin(t *= d) * k, A = Math.sin(d - t) * k, x = A * kx0 + B * kx1, y = A * ky0 + B * ky1, z = A * sy0 + B * sy1;
+      return [ Math.atan2(y, x) * d3_degrees, Math.atan2(z, Math.sqrt(x * x + y * y)) * d3_degrees ];
+    } : function() {
+      return [ x0 * d3_degrees, y0 * d3_degrees ];
+    };
+    interpolate.distance = d;
+    return interpolate;
+  }
+  d3.geo.length = function(object) {
+    d3_geo_lengthSum = 0;
+    d3.geo.stream(object, d3_geo_length);
+    return d3_geo_lengthSum;
+  };
+  var d3_geo_lengthSum;
+  var d3_geo_length = {
+    sphere: d3_noop,
+    point: d3_noop,
+    lineStart: d3_geo_lengthLineStart,
+    lineEnd: d3_noop,
+    polygonStart: d3_noop,
+    polygonEnd: d3_noop
+  };
+  function d3_geo_lengthLineStart() {
+    var λ0, sinφ0, cosφ0;
+    d3_geo_length.point = function(λ, φ) {
+      λ0 = λ * d3_radians, sinφ0 = Math.sin(φ *= d3_radians), cosφ0 = Math.cos(φ);
+      d3_geo_length.point = nextPoint;
+    };
+    d3_geo_length.lineEnd = function() {
+      d3_geo_length.point = d3_geo_length.lineEnd = d3_noop;
+    };
+    function nextPoint(λ, φ) {
+      var sinφ = Math.sin(φ *= d3_radians), cosφ = Math.cos(φ), t = abs((λ *= d3_radians) - λ0), cosΔλ = Math.cos(t);
+      d3_geo_lengthSum += Math.atan2(Math.sqrt((t = cosφ * Math.sin(t)) * t + (t = cosφ0 * sinφ - sinφ0 * cosφ * cosΔλ) * t), sinφ0 * sinφ + cosφ0 * cosφ * cosΔλ);
+      λ0 = λ, sinφ0 = sinφ, cosφ0 = cosφ;
+    }
+  }
+  function d3_geo_azimuthal(scale, angle) {
+    function azimuthal(λ, φ) {
+      var cosλ = Math.cos(λ), cosφ = Math.cos(φ), k = scale(cosλ * cosφ);
+      return [ k * cosφ * Math.sin(λ), k * Math.sin(φ) ];
+    }
+    azimuthal.invert = function(x, y) {
+      var ρ = Math.sqrt(x * x + y * y), c = angle(ρ), sinc = Math.sin(c), cosc = Math.cos(c);
+      return [ Math.atan2(x * sinc, ρ * cosc), Math.asin(ρ && y * sinc / ρ) ];
+    };
+    return azimuthal;
+  }
+  var d3_geo_azimuthalEqualArea = d3_geo_azimuthal(function(cosλcosφ) {
+    return Math.sqrt(2 / (1 + cosλcosφ));
+  }, function(ρ) {
+    return 2 * Math.asin(ρ / 2);
+  });
+  (d3.geo.azimuthalEqualArea = function() {
+    return d3_geo_projection(d3_geo_azimuthalEqualArea);
+  }).raw = d3_geo_azimuthalEqualArea;
+  var d3_geo_azimuthalEquidistant = d3_geo_azimuthal(function(cosλcosφ) {
+    var c = Math.acos(cosλcosφ);
+    return c && c / Math.sin(c);
+  }, d3_identity);
+  (d3.geo.azimuthalEquidistant = function() {
+    return d3_geo_projection(d3_geo_azimuthalEquidistant);
+  }).raw = d3_geo_azimuthalEquidistant;
+  function d3_geo_conicConformal(φ0, φ1) {
+    var cosφ0 = Math.cos(φ0), t = function(φ) {
+      return Math.tan(π / 4 + φ / 2);
+    }, n = φ0 === φ1 ? Math.sin(φ0) : Math.log(cosφ0 / Math.cos(φ1)) / Math.log(t(φ1) / t(φ0)), F = cosφ0 * Math.pow(t(φ0), n) / n;
+    if (!n) return d3_geo_mercator;
+    function forward(λ, φ) {
+      if (F > 0) {
+        if (φ < -halfπ + ε) φ = -halfπ + ε;
+      } else {
+        if (φ > halfπ - ε) φ = halfπ - ε;
+      }
+      var ρ = F / Math.pow(t(φ), n);
+      return [ ρ * Math.sin(n * λ), F - ρ * Math.cos(n * λ) ];
+    }
+    forward.invert = function(x, y) {
+      var ρ0_y = F - y, ρ = d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y);
+      return [ Math.atan2(x, ρ0_y) / n, 2 * Math.atan(Math.pow(F / ρ, 1 / n)) - halfπ ];
+    };
+    return forward;
+  }
+  (d3.geo.conicConformal = function() {
+    return d3_geo_conic(d3_geo_conicConformal);
+  }).raw = d3_geo_conicConformal;
+  function d3_geo_conicEquidistant(φ0, φ1) {
+    var cosφ0 = Math.cos(φ0), n = φ0 === φ1 ? Math.sin(φ0) : (cosφ0 - Math.cos(φ1)) / (φ1 - φ0), G = cosφ0 / n + φ0;
+    if (abs(n) < ε) return d3_geo_equirectangular;
+    function forward(λ, φ) {
+      var ρ = G - φ;
+      return [ ρ * Math.sin(n * λ), G - ρ * Math.cos(n * λ) ];
+    }
+    forward.invert = function(x, y) {
+      var ρ0_y = G - y;
+      return [ Math.atan2(x, ρ0_y) / n, G - d3_sgn(n) * Math.sqrt(x * x + ρ0_y * ρ0_y) ];
+    };
+    return forward;
+  }
+  (d3.geo.conicEquidistant = function() {
+    return d3_geo_conic(d3_geo_conicEquidistant);
+  }).raw = d3_geo_conicEquidistant;
+  var d3_geo_gnomonic = d3_geo_azimuthal(function(cosλcosφ) {
+    return 1 / cosλcosφ;
+  }, Math.atan);
+  (d3.geo.gnomonic = function() {
+    return d3_geo_projection(d3_geo_gnomonic);
+  }).raw = d3_geo_gnomonic;
+  function d3_geo_mercator(λ, φ) {
+    return [ λ, Math.log(Math.tan(π / 4 + φ / 2)) ];
+  }
+  d3_geo_mercator.invert = function(x, y) {
+    return [ x, 2 * Math.atan(Math.exp(y)) - halfπ ];
+  };
+  function d3_geo_mercatorProjection(project) {
+    var m = d3_geo_projection(project), scale = m.scale, translate = m.translate, clipExtent = m.clipExtent, clipAuto;
+    m.scale = function() {
+      var v = scale.apply(m, arguments);
+      return v === m ? clipAuto ? m.clipExtent(null) : m : v;
+    };
+    m.translate = function() {
+      var v = translate.apply(m, arguments);
+      return v === m ? clipAuto ? m.clipExtent(null) : m : v;
+    };
+    m.clipExtent = function(_) {
+      var v = clipExtent.apply(m, arguments);
+      if (v === m) {
+        if (clipAuto = _ == null) {
+          var k = π * scale(), t = translate();
+          clipExtent([ [ t[0] - k, t[1] - k ], [ t[0] + k, t[1] + k ] ]);
+        }
+      } else if (clipAuto) {
+        v = null;
+      }
+      return v;
+    };
+    return m.clipExtent(null);
+  }
+  (d3.geo.mercator = function() {
+    return d3_geo_mercatorProjection(d3_geo_mercator);
+  }).raw = d3_geo_mercator;
+  var d3_geo_orthographic = d3_geo_azimuthal(function() {
+    return 1;
+  }, Math.asin);
+  (d3.geo.orthographic = function() {
+    return d3_geo_projection(d3_geo_orthographic);
+  }).raw = d3_geo_orthographic;
+  var d3_geo_stereographic = d3_geo_azimuthal(function(cosλcosφ) {
+    return 1 / (1 + cosλcosφ);
+  }, function(ρ) {
+    return 2 * Math.atan(ρ);
+  });
+  (d3.geo.stereographic = function() {
+    return d3_geo_projection(d3_geo_stereographic);
+  }).raw = d3_geo_stereographic;
+  function d3_geo_transverseMercator(λ, φ) {
+    return [ Math.log(Math.tan(π / 4 + φ / 2)), -λ ];
+  }
+  d3_geo_transverseMercator.invert = function(x, y) {
+    return [ -y, 2 * Math.atan(Math.exp(x)) - halfπ ];
+  };
+  (d3.geo.transverseMercator = function() {
+    var projection = d3_geo_mercatorProjection(d3_geo_transverseMercator), center = projection.center, rotate = projection.rotate;
+    projection.center = function(_) {
+      return _ ? center([ -_[1], _[0] ]) : (_ = center(), [ _[1], -_[0] ]);
+    };
+    projection.rotate = function(_) {
+      return _ ? rotate([ _[0], _[1], _.length > 2 ? _[2] + 90 : 90 ]) : (_ = rotate(), 
+      [ _[0], _[1], _[2] - 90 ]);
+    };
+    return rotate([ 0, 0, 90 ]);
+  }).raw = d3_geo_transverseMercator;
+  d3.geom = {};
+  function d3_geom_pointX(d) {
+    return d[0];
+  }
+  function d3_geom_pointY(d) {
+    return d[1];
+  }
+  d3.geom.hull = function(vertices) {
+    var x = d3_geom_pointX, y = d3_geom_pointY;
+    if (arguments.length) return hull(vertices);
+    function hull(data) {
+      if (data.length < 3) return [];
+      var fx = d3_functor(x), fy = d3_functor(y), i, n = data.length, points = [], flippedPoints = [];
+      for (i = 0; i < n; i++) {
+        points.push([ +fx.call(this, data[i], i), +fy.call(this, data[i], i), i ]);
+      }
+      points.sort(d3_geom_hullOrder);
+      for (i = 0; i < n; i++) flippedPoints.push([ points[i][0], -points[i][1] ]);
+      var upper = d3_geom_hullUpper(points), lower = d3_geom_hullUpper(flippedPoints);
+      var skipLeft = lower[0] === upper[0], skipRight = lower[lower.length - 1] === upper[upper.length - 1], polygon = [];
+      for (i = upper.length - 1; i >= 0; --i) polygon.push(data[points[upper[i]][2]]);
+      for (i = +skipLeft; i < lower.length - skipRight; ++i) polygon.push(data[points[lower[i]][2]]);
+      return polygon;
+    }
+    hull.x = function(_) {
+      return arguments.length ? (x = _, hull) : x;
+    };
+    hull.y = function(_) {
+      return arguments.length ? (y = _, hull) : y;
+    };
+    return hull;
+  };
+  function d3_geom_hullUpper(points) {
+    var n = points.length, hull = [ 0, 1 ], hs = 2;
+    for (var i = 2; i < n; i++) {
+      while (hs > 1 && d3_cross2d(points[hull[hs - 2]], points[hull[hs - 1]], points[i]) <= 0) --hs;
+      hull[hs++] = i;
+    }
+    return hull.slice(0, hs);
+  }
+  function d3_geom_hullOrder(a, b) {
+    return a[0] - b[0] || a[1] - b[1];
+  }
+  d3.geom.polygon = function(coordinates) {
+    d3_subclass(coordinates, d3_geom_polygonPrototype);
+    return coordinates;
+  };
+  var d3_geom_polygonPrototype = d3.geom.polygon.prototype = [];
+  d3_geom_polygonPrototype.area = function() {
+    var i = -1, n = this.length, a, b = this[n - 1], area = 0;
+    while (++i < n) {
+      a = b;
+      b = this[i];
+      area += a[1] * b[0] - a[0] * b[1];
+    }
+    return area * .5;
+  };
+  d3_geom_polygonPrototype.centroid = function(k) {
+    var i = -1, n = this.length, x = 0, y = 0, a, b = this[n - 1], c;
+    if (!arguments.length) k = -1 / (6 * this.area());
+    while (++i < n) {
+      a = b;
+      b = this[i];
+      c = a[0] * b[1] - b[0] * a[1];
+      x += (a[0] + b[0]) * c;
+      y += (a[1] + b[1]) * c;
+    }
+    return [ x * k, y * k ];
+  };
+  d3_geom_polygonPrototype.clip = function(subject) {
+    var input, closed = d3_geom_polygonClosed(subject), i = -1, n = this.length - d3_geom_polygonClosed(this), j, m, a = this[n - 1], b, c, d;
+    while (++i < n) {
+      input = subject.slice();
+      subject.length = 0;
+      b = this[i];
+      c = input[(m = input.length - closed) - 1];
+      j = -1;
+      while (++j < m) {
+        d = input[j];
+        if (d3_geom_polygonInside(d, a, b)) {
+          if (!d3_geom_polygonInside(c, a, b)) {
+            subject.push(d3_geom_polygonIntersect(c, d, a, b));
+          }
+          subject.push(d);
+        } else if (d3_geom_polygonInside(c, a, b)) {
+          subject.push(d3_geom_polygonIntersect(c, d, a, b));
+        }
+        c = d;
+      }
+      if (closed) subject.push(subject[0]);
+      a = b;
+    }
+    return subject;
+  };
+  function d3_geom_polygonInside(p, a, b) {
+    return (b[0] - a[0]) * (p[1] - a[1]) < (b[1] - a[1]) * (p[0] - a[0]);
+  }
+  function d3_geom_polygonIntersect(c, d, a, b) {
+    var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3, y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3, ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21);
+    return [ x1 + ua * x21, y1 + ua * y21 ];
+  }
+  function d3_geom_polygonClosed(coordinates) {
+    var a = coordinates[0], b = coordinates[coordinates.length - 1];
+    return !(a[0] - b[0] || a[1] - b[1]);
+  }
+  var d3_geom_voronoiEdges, d3_geom_voronoiCells, d3_geom_voronoiBeaches, d3_geom_voronoiBeachPool = [], d3_geom_voronoiFirstCircle, d3_geom_voronoiCircles, d3_geom_voronoiCirclePool = [];
+  function d3_geom_voronoiBeach() {
+    d3_geom_voronoiRedBlackNode(this);
+    this.edge = this.site = this.circle = null;
+  }
+  function d3_geom_voronoiCreateBeach(site) {
+    var beach = d3_geom_voronoiBeachPool.pop() || new d3_geom_voronoiBeach();
+    beach.site = site;
+    return beach;
+  }
+  function d3_geom_voronoiDetachBeach(beach) {
+    d3_geom_voronoiDetachCircle(beach);
+    d3_geom_voronoiBeaches.remove(beach);
+    d3_geom_voronoiBeachPool.push(beach);
+    d3_geom_voronoiRedBlackNode(beach);
+  }
+  function d3_geom_voronoiRemoveBeach(beach) {
+    var circle = beach.circle, x = circle.x, y = circle.cy, vertex = {
+      x: x,
+      y: y
+    }, previous = beach.P, next = beach.N, disappearing = [ beach ];
+    d3_geom_voronoiDetachBeach(beach);
+    var lArc = previous;
+    while (lArc.circle && abs(x - lArc.circle.x) < ε && abs(y - lArc.circle.cy) < ε) {
+      previous = lArc.P;
+      disappearing.unshift(lArc);
+      d3_geom_voronoiDetachBeach(lArc);
+      lArc = previous;
+    }
+    disappearing.unshift(lArc);
+    d3_geom_voronoiDetachCircle(lArc);
+    var rArc = next;
+    while (rArc.circle && abs(x - rArc.circle.x) < ε && abs(y - rArc.circle.cy) < ε) {
+      next = rArc.N;
+      disappearing.push(rArc);
+      d3_geom_voronoiDetachBeach(rArc);
+      rArc = next;
+    }
+    disappearing.push(rArc);
+    d3_geom_voronoiDetachCircle(rArc);
+    var nArcs = disappearing.length, iArc;
+    for (iArc = 1; iArc < nArcs; ++iArc) {
+      rArc = disappearing[iArc];
+      lArc = disappearing[iArc - 1];
+      d3_geom_voronoiSetEdgeEnd(rArc.edge, lArc.site, rArc.site, vertex);
+    }
+    lArc = disappearing[0];
+    rArc = disappearing[nArcs - 1];
+    rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, rArc.site, null, vertex);
+    d3_geom_voronoiAttachCircle(lArc);
+    d3_geom_voronoiAttachCircle(rArc);
+  }
+  function d3_geom_voronoiAddBeach(site) {
+    var x = site.x, directrix = site.y, lArc, rArc, dxl, dxr, node = d3_geom_voronoiBeaches._;
+    while (node) {
+      dxl = d3_geom_voronoiLeftBreakPoint(node, directrix) - x;
+      if (dxl > ε) node = node.L; else {
+        dxr = x - d3_geom_voronoiRightBreakPoint(node, directrix);
+        if (dxr > ε) {
+          if (!node.R) {
+            lArc = node;
+            break;
+          }
+          node = node.R;
+        } else {
+          if (dxl > -ε) {
+            lArc = node.P;
+            rArc = node;
+          } else if (dxr > -ε) {
+            lArc = node;
+            rArc = node.N;
+          } else {
+            lArc = rArc = node;
+          }
+          break;
+        }
+      }
+    }
+    var newArc = d3_geom_voronoiCreateBeach(site);
+    d3_geom_voronoiBeaches.insert(lArc, newArc);
+    if (!lArc && !rArc) return;
+    if (lArc === rArc) {
+      d3_geom_voronoiDetachCircle(lArc);
+      rArc = d3_geom_voronoiCreateBeach(lArc.site);
+      d3_geom_voronoiBeaches.insert(newArc, rArc);
+      newArc.edge = rArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site);
+      d3_geom_voronoiAttachCircle(lArc);
+      d3_geom_voronoiAttachCircle(rArc);
+      return;
+    }
+    if (!rArc) {
+      newArc.edge = d3_geom_voronoiCreateEdge(lArc.site, newArc.site);
+      return;
+    }
+    d3_geom_voronoiDetachCircle(lArc);
+    d3_geom_voronoiDetachCircle(rArc);
+    var lSite = lArc.site, ax = lSite.x, ay = lSite.y, bx = site.x - ax, by = site.y - ay, rSite = rArc.site, cx = rSite.x - ax, cy = rSite.y - ay, d = 2 * (bx * cy - by * cx), hb = bx * bx + by * by, hc = cx * cx + cy * cy, vertex = {
+      x: (cy * hb - by * hc) / d + ax,
+      y: (bx * hc - cx * hb) / d + ay
+    };
+    d3_geom_voronoiSetEdgeEnd(rArc.edge, lSite, rSite, vertex);
+    newArc.edge = d3_geom_voronoiCreateEdge(lSite, site, null, vertex);
+    rArc.edge = d3_geom_voronoiCreateEdge(site, rSite, null, vertex);
+    d3_geom_voronoiAttachCircle(lArc);
+    d3_geom_voronoiAttachCircle(rArc);
+  }
+  function d3_geom_voronoiLeftBreakPoint(arc, directrix) {
+    var site = arc.site, rfocx = site.x, rfocy = site.y, pby2 = rfocy - directrix;
+    if (!pby2) return rfocx;
+    var lArc = arc.P;
+    if (!lArc) return -Infinity;
+    site = lArc.site;
+    var lfocx = site.x, lfocy = site.y, plby2 = lfocy - directrix;
+    if (!plby2) return lfocx;
+    var hl = lfocx - rfocx, aby2 = 1 / pby2 - 1 / plby2, b = hl / plby2;
+    if (aby2) return (-b + Math.sqrt(b * b - 2 * aby2 * (hl * hl / (-2 * plby2) - lfocy + plby2 / 2 + rfocy - pby2 / 2))) / aby2 + rfocx;
+    return (rfocx + lfocx) / 2;
+  }
+  function d3_geom_voronoiRightBreakPoint(arc, directrix) {
+    var rArc = arc.N;
+    if (rArc) return d3_geom_voronoiLeftBreakPoint(rArc, directrix);
+    var site = arc.site;
+    return site.y === directrix ? site.x : Infinity;
+  }
+  function d3_geom_voronoiCell(site) {
+    this.site = site;
+    this.edges = [];
+  }
+  d3_geom_voronoiCell.prototype.prepare = function() {
+    var halfEdges = this.edges, iHalfEdge = halfEdges.length, edge;
+    while (iHalfEdge--) {
+      edge = halfEdges[iHalfEdge].edge;
+      if (!edge.b || !edge.a) halfEdges.splice(iHalfEdge, 1);
+    }
+    halfEdges.sort(d3_geom_voronoiHalfEdgeOrder);
+    return halfEdges.length;
+  };
+  function d3_geom_voronoiCloseCells(extent) {
+    var x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], x2, y2, x3, y3, cells = d3_geom_voronoiCells, iCell = cells.length, cell, iHalfEdge, halfEdges, nHalfEdges, start, end;
+    while (iCell--) {
+      cell = cells[iCell];
+      if (!cell || !cell.prepare()) continue;
+      halfEdges = cell.edges;
+      nHalfEdges = halfEdges.length;
+      iHalfEdge = 0;
+      while (iHalfEdge < nHalfEdges) {
+        end = halfEdges[iHalfEdge].end(), x3 = end.x, y3 = end.y;
+        start = halfEdges[++iHalfEdge % nHalfEdges].start(), x2 = start.x, y2 = start.y;
+        if (abs(x3 - x2) > ε || abs(y3 - y2) > ε) {
+          halfEdges.splice(iHalfEdge, 0, new d3_geom_voronoiHalfEdge(d3_geom_voronoiCreateBorderEdge(cell.site, end, abs(x3 - x0) < ε && y1 - y3 > ε ? {
+            x: x0,
+            y: abs(x2 - x0) < ε ? y2 : y1
+          } : abs(y3 - y1) < ε && x1 - x3 > ε ? {
+            x: abs(y2 - y1) < ε ? x2 : x1,
+            y: y1
+          } : abs(x3 - x1) < ε && y3 - y0 > ε ? {
+            x: x1,
+            y: abs(x2 - x1) < ε ? y2 : y0
+          } : abs(y3 - y0) < ε && x3 - x0 > ε ? {
+            x: abs(y2 - y0) < ε ? x2 : x0,
+            y: y0
+          } : null), cell.site, null));
+          ++nHalfEdges;
+        }
+      }
+    }
+  }
+  function d3_geom_voronoiHalfEdgeOrder(a, b) {
+    return b.angle - a.angle;
+  }
+  function d3_geom_voronoiCircle() {
+    d3_geom_voronoiRedBlackNode(this);
+    this.x = this.y = this.arc = this.site = this.cy = null;
+  }
+  function d3_geom_voronoiAttachCircle(arc) {
+    var lArc = arc.P, rArc = arc.N;
+    if (!lArc || !rArc) return;
+    var lSite = lArc.site, cSite = arc.site, rSite = rArc.site;
+    if (lSite === rSite) return;
+    var bx = cSite.x, by = cSite.y, ax = lSite.x - bx, ay = lSite.y - by, cx = rSite.x - bx, cy = rSite.y - by;
+    var d = 2 * (ax * cy - ay * cx);
+    if (d >= -ε2) return;
+    var ha = ax * ax + ay * ay, hc = cx * cx + cy * cy, x = (cy * ha - ay * hc) / d, y = (ax * hc - cx * ha) / d, cy = y + by;
+    var circle = d3_geom_voronoiCirclePool.pop() || new d3_geom_voronoiCircle();
+    circle.arc = arc;
+    circle.site = cSite;
+    circle.x = x + bx;
+    circle.y = cy + Math.sqrt(x * x + y * y);
+    circle.cy = cy;
+    arc.circle = circle;
+    var before = null, node = d3_geom_voronoiCircles._;
+    while (node) {
+      if (circle.y < node.y || circle.y === node.y && circle.x <= node.x) {
+        if (node.L) node = node.L; else {
+          before = node.P;
+          break;
+        }
+      } else {
+        if (node.R) node = node.R; else {
+          before = node;
+          break;
+        }
+      }
+    }
+    d3_geom_voronoiCircles.insert(before, circle);
+    if (!before) d3_geom_voronoiFirstCircle = circle;
+  }
+  function d3_geom_voronoiDetachCircle(arc) {
+    var circle = arc.circle;
+    if (circle) {
+      if (!circle.P) d3_geom_voronoiFirstCircle = circle.N;
+      d3_geom_voronoiCircles.remove(circle);
+      d3_geom_voronoiCirclePool.push(circle);
+      d3_geom_voronoiRedBlackNode(circle);
+      arc.circle = null;
+    }
+  }
+  function d3_geom_voronoiClipEdges(extent) {
+    var edges = d3_geom_voronoiEdges, clip = d3_geom_clipLine(extent[0][0], extent[0][1], extent[1][0], extent[1][1]), i = edges.length, e;
+    while (i--) {
+      e = edges[i];
+      if (!d3_geom_voronoiConnectEdge(e, extent) || !clip(e) || abs(e.a.x - e.b.x) < ε && abs(e.a.y - e.b.y) < ε) {
+        e.a = e.b = null;
+        edges.splice(i, 1);
+      }
+    }
+  }
+  function d3_geom_voronoiConnectEdge(edge, extent) {
+    var vb = edge.b;
+    if (vb) return true;
+    var va = edge.a, x0 = extent[0][0], x1 = extent[1][0], y0 = extent[0][1], y1 = extent[1][1], lSite = edge.l, rSite = edge.r, lx = lSite.x, ly = lSite.y, rx = rSite.x, ry = rSite.y, fx = (lx + rx) / 2, fy = (ly + ry) / 2, fm, fb;
+    if (ry === ly) {
+      if (fx < x0 || fx >= x1) return;
+      if (lx > rx) {
+        if (!va) va = {
+          x: fx,
+          y: y0
+        }; else if (va.y >= y1) return;
+        vb = {
+          x: fx,
+          y: y1
+        };
+      } else {
+        if (!va) va = {
+          x: fx,
+          y: y1
+        }; else if (va.y < y0) return;
+        vb = {
+          x: fx,
+          y: y0
+        };
+      }
+    } else {
+      fm = (lx - rx) / (ry - ly);
+      fb = fy - fm * fx;
+      if (fm < -1 || fm > 1) {
+        if (lx > rx) {
+          if (!va) va = {
+            x: (y0 - fb) / fm,
+            y: y0
+          }; else if (va.y >= y1) return;
+          vb = {
+            x: (y1 - fb) / fm,
+            y: y1
+          };
+        } else {
+          if (!va) va = {
+            x: (y1 - fb) / fm,
+            y: y1
+          }; else if (va.y < y0) return;
+          vb = {
+            x: (y0 - fb) / fm,
+            y: y0
+          };
+        }
+      } else {
+        if (ly < ry) {
+          if (!va) va = {
+            x: x0,
+            y: fm * x0 + fb
+          }; else if (va.x >= x1) return;
+          vb = {
+            x: x1,
+            y: fm * x1 + fb
+          };
+        } else {
+          if (!va) va = {
+            x: x1,
+            y: fm * x1 + fb
+          }; else if (va.x < x0) return;
+          vb = {
+            x: x0,
+            y: fm * x0 + fb
+          };
+        }
+      }
+    }
+    edge.a = va;
+    edge.b = vb;
+    return true;
+  }
+  function d3_geom_voronoiEdge(lSite, rSite) {
+    this.l = lSite;
+    this.r = rSite;
+    this.a = this.b = null;
+  }
+  function d3_geom_voronoiCreateEdge(lSite, rSite, va, vb) {
+    var edge = new d3_geom_voronoiEdge(lSite, rSite);
+    d3_geom_voronoiEdges.push(edge);
+    if (va) d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, va);
+    if (vb) d3_geom_voronoiSetEdgeEnd(edge, rSite, lSite, vb);
+    d3_geom_voronoiCells[lSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, lSite, rSite));
+    d3_geom_voronoiCells[rSite.i].edges.push(new d3_geom_voronoiHalfEdge(edge, rSite, lSite));
+    return edge;
+  }
+  function d3_geom_voronoiCreateBorderEdge(lSite, va, vb) {
+    var edge = new d3_geom_voronoiEdge(lSite, null);
+    edge.a = va;
+    edge.b = vb;
+    d3_geom_voronoiEdges.push(edge);
+    return edge;
+  }
+  function d3_geom_voronoiSetEdgeEnd(edge, lSite, rSite, vertex) {
+    if (!edge.a && !edge.b) {
+      edge.a = vertex;
+      edge.l = lSite;
+      edge.r = rSite;
+    } else if (edge.l === rSite) {
+      edge.b = vertex;
+    } else {
+      edge.a = vertex;
+    }
+  }
+  function d3_geom_voronoiHalfEdge(edge, lSite, rSite) {
+    var va = edge.a, vb = edge.b;
+    this.edge = edge;
+    this.site = lSite;
+    this.angle = rSite ? Math.atan2(rSite.y - lSite.y, rSite.x - lSite.x) : edge.l === lSite ? Math.atan2(vb.x - va.x, va.y - vb.y) : Math.atan2(va.x - vb.x, vb.y - va.y);
+  }
+  d3_geom_voronoiHalfEdge.prototype = {
+    start: function() {
+      return this.edge.l === this.site ? this.edge.a : this.edge.b;
+    },
+    end: function() {
+      return this.edge.l === this.site ? this.edge.b : this.edge.a;
+    }
+  };
+  function d3_geom_voronoiRedBlackTree() {
+    this._ = null;
+  }
+  function d3_geom_voronoiRedBlackNode(node) {
+    node.U = node.C = node.L = node.R = node.P = node.N = null;
+  }
+  d3_geom_voronoiRedBlackTree.prototype = {
+    insert: function(after, node) {
+      var parent, grandpa, uncle;
+      if (after) {
+        node.P = after;
+        node.N = after.N;
+        if (after.N) after.N.P = node;
+        after.N = node;
+        if (after.R) {
+          after = after.R;
+          while (after.L) after = after.L;
+          after.L = node;
+        } else {
+          after.R = node;
+        }
+        parent = after;
+      } else if (this._) {
+        after = d3_geom_voronoiRedBlackFirst(this._);
+        node.P = null;
+        node.N = after;
+        after.P = after.L = node;
+        parent = after;
+      } else {
+        node.P = node.N = null;
+        this._ = node;
+        parent = null;
+      }
+      node.L = node.R = null;
+      node.U = parent;
+      node.C = true;
+      after = node;
+      while (parent && parent.C) {
+        grandpa = parent.U;
+        if (parent === grandpa.L) {
+          uncle = grandpa.R;
+          if (uncle && uncle.C) {
+            parent.C = uncle.C = false;
+            grandpa.C = true;
+            after = grandpa;
+          } else {
+            if (after === parent.R) {
+              d3_geom_voronoiRedBlackRotateLeft(this, parent);
+              after = parent;
+              parent = after.U;
+            }
+            parent.C = false;
+            grandpa.C = true;
+            d3_geom_voronoiRedBlackRotateRight(this, grandpa);
+          }
+        } else {
+          uncle = grandpa.L;
+          if (uncle && uncle.C) {
+            parent.C = uncle.C = false;
+            grandpa.C = true;
+            after = grandpa;
+          } else {
+            if (after === parent.L) {
+              d3_geom_voronoiRedBlackRotateRight(this, parent);
+              after = parent;
+              parent = after.U;
+            }
+            parent.C = false;
+            grandpa.C = true;
+            d3_geom_voronoiRedBlackRotateLeft(this, grandpa);
+          }
+        }
+        parent = after.U;
+      }
+      this._.C = false;
+    },
+    remove: function(node) {
+      if (node.N) node.N.P = node.P;
+      if (node.P) node.P.N = node.N;
+      node.N = node.P = null;
+      var parent = node.U, sibling, left = node.L, right = node.R, next, red;
+      if (!left) next = right; else if (!right) next = left; else next = d3_geom_voronoiRedBlackFirst(right);
+      if (parent) {
+        if (parent.L === node) parent.L = next; else parent.R = next;
+      } else {
+        this._ = next;
+      }
+      if (left && right) {
+        red = next.C;
+        next.C = node.C;
+        next.L = left;
+        left.U = next;
+        if (next !== right) {
+          parent = next.U;
+          next.U = node.U;
+          node = next.R;
+          parent.L = node;
+          next.R = right;
+          right.U = next;
+        } else {
+          next.U = parent;
+          parent = next;
+          node = next.R;
+        }
+      } else {
+        red = node.C;
+        node = next;
+      }
+      if (node) node.U = parent;
+      if (red) return;
+      if (node && node.C) {
+        node.C = false;
+        return;
+      }
+      do {
+        if (node === this._) break;
+        if (node === parent.L) {
+          sibling = parent.R;
+          if (sibling.C) {
+            sibling.C = false;
+            parent.C = true;
+            d3_geom_voronoiRedBlackRotateLeft(this, parent);
+            sibling = parent.R;
+          }
+          if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) {
+            if (!sibling.R || !sibling.R.C) {
+              sibling.L.C = false;
+              sibling.C = true;
+              d3_geom_voronoiRedBlackRotateRight(this, sibling);
+              sibling = parent.R;
+            }
+            sibling.C = parent.C;
+            parent.C = sibling.R.C = false;
+            d3_geom_voronoiRedBlackRotateLeft(this, parent);
+            node = this._;
+            break;
+          }
+        } else {
+          sibling = parent.L;
+          if (sibling.C) {
+            sibling.C = false;
+            parent.C = true;
+            d3_geom_voronoiRedBlackRotateRight(this, parent);
+            sibling = parent.L;
+          }
+          if (sibling.L && sibling.L.C || sibling.R && sibling.R.C) {
+            if (!sibling.L || !sibling.L.C) {
+              sibling.R.C = false;
+              sibling.C = true;
+              d3_geom_voronoiRedBlackRotateLeft(this, sibling);
+              sibling = parent.L;
+            }
+            sibling.C = parent.C;
+            parent.C = sibling.L.C = false;
+            d3_geom_voronoiRedBlackRotateRight(this, parent);
+            node = this._;
+            break;
+          }
+        }
+        sibling.C = true;
+        node = parent;
+        parent = parent.U;
+      } while (!node.C);
+      if (node) node.C = false;
+    }
+  };
+  function d3_geom_voronoiRedBlackRotateLeft(tree, node) {
+    var p = node, q = node.R, parent = p.U;
+    if (parent) {
+      if (parent.L === p) parent.L = q; else parent.R = q;
+    } else {
+      tree._ = q;
+    }
+    q.U = parent;
+    p.U = q;
+    p.R = q.L;
+    if (p.R) p.R.U = p;
+    q.L = p;
+  }
+  function d3_geom_voronoiRedBlackRotateRight(tree, node) {
+    var p = node, q = node.L, parent = p.U;
+    if (parent) {
+      if (parent.L === p) parent.L = q; else parent.R = q;
+    } else {
+      tree._ = q;
+    }
+    q.U = parent;
+    p.U = q;
+    p.L = q.R;
+    if (p.L) p.L.U = p;
+    q.R = p;
+  }
+  function d3_geom_voronoiRedBlackFirst(node) {
+    while (node.L) node = node.L;
+    return node;
+  }
+  function d3_geom_voronoi(sites, bbox) {
+    var site = sites.sort(d3_geom_voronoiVertexOrder).pop(), x0, y0, circle;
+    d3_geom_voronoiEdges = [];
+    d3_geom_voronoiCells = new Array(sites.length);
+    d3_geom_voronoiBeaches = new d3_geom_voronoiRedBlackTree();
+    d3_geom_voronoiCircles = new d3_geom_voronoiRedBlackTree();
+    while (true) {
+      circle = d3_geom_voronoiFirstCircle;
+      if (site && (!circle || site.y < circle.y || site.y === circle.y && site.x < circle.x)) {
+        if (site.x !== x0 || site.y !== y0) {
+          d3_geom_voronoiCells[site.i] = new d3_geom_voronoiCell(site);
+          d3_geom_voronoiAddBeach(site);
+          x0 = site.x, y0 = site.y;
+        }
+        site = sites.pop();
+      } else if (circle) {
+        d3_geom_voronoiRemoveBeach(circle.arc);
+      } else {
+        break;
+      }
+    }
+    if (bbox) d3_geom_voronoiClipEdges(bbox), d3_geom_voronoiCloseCells(bbox);
+    var diagram = {
+      cells: d3_geom_voronoiCells,
+      edges: d3_geom_voronoiEdges
+    };
+    d3_geom_voronoiBeaches = d3_geom_voronoiCircles = d3_geom_voronoiEdges = d3_geom_voronoiCells = null;
+    return diagram;
+  }
+  function d3_geom_voronoiVertexOrder(a, b) {
+    return b.y - a.y || b.x - a.x;
+  }
+  d3.geom.voronoi = function(points) {
+    var x = d3_geom_pointX, y = d3_geom_pointY, fx = x, fy = y, clipExtent = d3_geom_voronoiClipExtent;
+    if (points) return voronoi(points);
+    function voronoi(data) {
+      var polygons = new Array(data.length), x0 = clipExtent[0][0], y0 = clipExtent[0][1], x1 = clipExtent[1][0], y1 = clipExtent[1][1];
+      d3_geom_voronoi(sites(data), clipExtent).cells.forEach(function(cell, i) {
+        var edges = cell.edges, site = cell.site, polygon = polygons[i] = edges.length ? edges.map(function(e) {
+          var s = e.start();
+          return [ s.x, s.y ];
+        }) : site.x >= x0 && site.x <= x1 && site.y >= y0 && site.y <= y1 ? [ [ x0, y1 ], [ x1, y1 ], [ x1, y0 ], [ x0, y0 ] ] : [];
+        polygon.point = data[i];
+      });
+      return polygons;
+    }
+    function sites(data) {
+      return data.map(function(d, i) {
+        return {
+          x: Math.round(fx(d, i) / ε) * ε,
+          y: Math.round(fy(d, i) / ε) * ε,
+          i: i
+        };
+      });
+    }
+    voronoi.links = function(data) {
+      return d3_geom_voronoi(sites(data)).edges.filter(function(edge) {
+        return edge.l && edge.r;
+      }).map(function(edge) {
+        return {
+          source: data[edge.l.i],
+          target: data[edge.r.i]
+        };
+      });
+    };
+    voronoi.triangles = function(data) {
+      var triangles = [];
+      d3_geom_voronoi(sites(data)).cells.forEach(function(cell, i) {
+        var site = cell.site, edges = cell.edges.sort(d3_geom_voronoiHalfEdgeOrder), j = -1, m = edges.length, e0, s0, e1 = edges[m - 1].edge, s1 = e1.l === site ? e1.r : e1.l;
+        while (++j < m) {
+          e0 = e1;
+          s0 = s1;
+          e1 = edges[j].edge;
+          s1 = e1.l === site ? e1.r : e1.l;
+          if (i < s0.i && i < s1.i && d3_geom_voronoiTriangleArea(site, s0, s1) < 0) {
+            triangles.push([ data[i], data[s0.i], data[s1.i] ]);
+          }
+        }
+      });
+      return triangles;
+    };
+    voronoi.x = function(_) {
+      return arguments.length ? (fx = d3_functor(x = _), voronoi) : x;
+    };
+    voronoi.y = function(_) {
+      return arguments.length ? (fy = d3_functor(y = _), voronoi) : y;
+    };
+    voronoi.clipExtent = function(_) {
+      if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent;
+      clipExtent = _ == null ? d3_geom_voronoiClipExtent : _;
+      return voronoi;
+    };
+    voronoi.size = function(_) {
+      if (!arguments.length) return clipExtent === d3_geom_voronoiClipExtent ? null : clipExtent && clipExtent[1];
+      return voronoi.clipExtent(_ && [ [ 0, 0 ], _ ]);
+    };
+    return voronoi;
+  };
+  var d3_geom_voronoiClipExtent = [ [ -1e6, -1e6 ], [ 1e6, 1e6 ] ];
+  function d3_geom_voronoiTriangleArea(a, b, c) {
+    return (a.x - c.x) * (b.y - a.y) - (a.x - b.x) * (c.y - a.y);
+  }
+  d3.geom.delaunay = function(vertices) {
+    return d3.geom.voronoi().triangles(vertices);
+  };
+  d3.geom.quadtree = function(points, x1, y1, x2, y2) {
+    var x = d3_geom_pointX, y = d3_geom_pointY, compat;
+    if (compat = arguments.length) {
+      x = d3_geom_quadtreeCompatX;
+      y = d3_geom_quadtreeCompatY;
+      if (compat === 3) {
+        y2 = y1;
+        x2 = x1;
+        y1 = x1 = 0;
+      }
+      return quadtree(points);
+    }
+    function quadtree(data) {
+      var d, fx = d3_functor(x), fy = d3_functor(y), xs, ys, i, n, x1_, y1_, x2_, y2_;
+      if (x1 != null) {
+        x1_ = x1, y1_ = y1, x2_ = x2, y2_ = y2;
+      } else {
+        x2_ = y2_ = -(x1_ = y1_ = Infinity);
+        xs = [], ys = [];
+        n = data.length;
+        if (compat) for (i = 0; i < n; ++i) {
+          d = data[i];
+          if (d.x < x1_) x1_ = d.x;
+          if (d.y < y1_) y1_ = d.y;
+          if (d.x > x2_) x2_ = d.x;
+          if (d.y > y2_) y2_ = d.y;
+          xs.push(d.x);
+          ys.push(d.y);
+        } else for (i = 0; i < n; ++i) {
+          var x_ = +fx(d = data[i], i), y_ = +fy(d, i);
+          if (x_ < x1_) x1_ = x_;
+          if (y_ < y1_) y1_ = y_;
+          if (x_ > x2_) x2_ = x_;
+          if (y_ > y2_) y2_ = y_;
+          xs.push(x_);
+          ys.push(y_);
+        }
+      }
+      var dx = x2_ - x1_, dy = y2_ - y1_;
+      if (dx > dy) y2_ = y1_ + dx; else x2_ = x1_ + dy;
+      function insert(n, d, x, y, x1, y1, x2, y2) {
+        if (isNaN(x) || isNaN(y)) return;
+        if (n.leaf) {
+          var nx = n.x, ny = n.y;
+          if (nx != null) {
+            if (abs(nx - x) + abs(ny - y) < .01) {
+              insertChild(n, d, x, y, x1, y1, x2, y2);
+            } else {
+              var nPoint = n.point;
+              n.x = n.y = n.point = null;
+              insertChild(n, nPoint, nx, ny, x1, y1, x2, y2);
+              insertChild(n, d, x, y, x1, y1, x2, y2);
+            }
+          } else {
+            n.x = x, n.y = y, n.point = d;
+          }
+        } else {
+          insertChild(n, d, x, y, x1, y1, x2, y2);
+        }
+      }
+      function insertChild(n, d, x, y, x1, y1, x2, y2) {
+        var xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym, i = below << 1 | right;
+        n.leaf = false;
+        n = n.nodes[i] || (n.nodes[i] = d3_geom_quadtreeNode());
+        if (right) x1 = xm; else x2 = xm;
+        if (below) y1 = ym; else y2 = ym;
+        insert(n, d, x, y, x1, y1, x2, y2);
+      }
+      var root = d3_geom_quadtreeNode();
+      root.add = function(d) {
+        insert(root, d, +fx(d, ++i), +fy(d, i), x1_, y1_, x2_, y2_);
+      };
+      root.visit = function(f) {
+        d3_geom_quadtreeVisit(f, root, x1_, y1_, x2_, y2_);
+      };
+      root.find = function(point) {
+        return d3_geom_quadtreeFind(root, point[0], point[1], x1_, y1_, x2_, y2_);
+      };
+      i = -1;
+      if (x1 == null) {
+        while (++i < n) {
+          insert(root, data[i], xs[i], ys[i], x1_, y1_, x2_, y2_);
+        }
+        --i;
+      } else data.forEach(root.add);
+      xs = ys = data = d = null;
+      return root;
+    }
+    quadtree.x = function(_) {
+      return arguments.length ? (x = _, quadtree) : x;
+    };
+    quadtree.y = function(_) {
+      return arguments.length ? (y = _, quadtree) : y;
+    };
+    quadtree.extent = function(_) {
+      if (!arguments.length) return x1 == null ? null : [ [ x1, y1 ], [ x2, y2 ] ];
+      if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = +_[0][0], y1 = +_[0][1], x2 = +_[1][0], 
+      y2 = +_[1][1];
+      return quadtree;
+    };
+    quadtree.size = function(_) {
+      if (!arguments.length) return x1 == null ? null : [ x2 - x1, y2 - y1 ];
+      if (_ == null) x1 = y1 = x2 = y2 = null; else x1 = y1 = 0, x2 = +_[0], y2 = +_[1];
+      return quadtree;
+    };
+    return quadtree;
+  };
+  function d3_geom_quadtreeCompatX(d) {
+    return d.x;
+  }
+  function d3_geom_quadtreeCompatY(d) {
+    return d.y;
+  }
+  function d3_geom_quadtreeNode() {
+    return {
+      leaf: true,
+      nodes: [],
+      point: null,
+      x: null,
+      y: null
+    };
+  }
+  function d3_geom_quadtreeVisit(f, node, x1, y1, x2, y2) {
+    if (!f(node, x1, y1, x2, y2)) {
+      var sx = (x1 + x2) * .5, sy = (y1 + y2) * .5, children = node.nodes;
+      if (children[0]) d3_geom_quadtreeVisit(f, children[0], x1, y1, sx, sy);
+      if (children[1]) d3_geom_quadtreeVisit(f, children[1], sx, y1, x2, sy);
+      if (children[2]) d3_geom_quadtreeVisit(f, children[2], x1, sy, sx, y2);
+      if (children[3]) d3_geom_quadtreeVisit(f, children[3], sx, sy, x2, y2);
+    }
+  }
+  function d3_geom_quadtreeFind(root, x, y, x0, y0, x3, y3) {
+    var minDistance2 = Infinity, closestPoint;
+    (function find(node, x1, y1, x2, y2) {
+      if (x1 > x3 || y1 > y3 || x2 < x0 || y2 < y0) return;
+      if (point = node.point) {
+        var point, dx = x - node.x, dy = y - node.y, distance2 = dx * dx + dy * dy;
+        if (distance2 < minDistance2) {
+          var distance = Math.sqrt(minDistance2 = distance2);
+          x0 = x - distance, y0 = y - distance;
+          x3 = x + distance, y3 = y + distance;
+          closestPoint = point;
+        }
+      }
+      var children = node.nodes, xm = (x1 + x2) * .5, ym = (y1 + y2) * .5, right = x >= xm, below = y >= ym;
+      for (var i = below << 1 | right, j = i + 4; i < j; ++i) {
+        if (node = children[i & 3]) switch (i & 3) {
+         case 0:
+          find(node, x1, y1, xm, ym);
+          break;
+
+         case 1:
+          find(node, xm, y1, x2, ym);
+          break;
+
+         case 2:
+          find(node, x1, ym, xm, y2);
+          break;
+
+         case 3:
+          find(node, xm, ym, x2, y2);
+          break;
+        }
+      }
+    })(root, x0, y0, x3, y3);
+    return closestPoint;
+  }
+  d3.interpolateRgb = d3_interpolateRgb;
+  function d3_interpolateRgb(a, b) {
+    a = d3.rgb(a);
+    b = d3.rgb(b);
+    var ar = a.r, ag = a.g, ab = a.b, br = b.r - ar, bg = b.g - ag, bb = b.b - ab;
+    return function(t) {
+      return "#" + d3_rgb_hex(Math.round(ar + br * t)) + d3_rgb_hex(Math.round(ag + bg * t)) + d3_rgb_hex(Math.round(ab + bb * t));
+    };
+  }
+  d3.interpolateObject = d3_interpolateObject;
+  function d3_interpolateObject(a, b) {
+    var i = {}, c = {}, k;
+    for (k in a) {
+      if (k in b) {
+        i[k] = d3_interpolate(a[k], b[k]);
+      } else {
+        c[k] = a[k];
+      }
+    }
+    for (k in b) {
+      if (!(k in a)) {
+        c[k] = b[k];
+      }
+    }
+    return function(t) {
+      for (k in i) c[k] = i[k](t);
+      return c;
+    };
+  }
+  d3.interpolateNumber = d3_interpolateNumber;
+  function d3_interpolateNumber(a, b) {
+    a = +a, b = +b;
+    return function(t) {
+      return a * (1 - t) + b * t;
+    };
+  }
+  d3.interpolateString = d3_interpolateString;
+  function d3_interpolateString(a, b) {
+    var bi = d3_interpolate_numberA.lastIndex = d3_interpolate_numberB.lastIndex = 0, am, bm, bs, i = -1, s = [], q = [];
+    a = a + "", b = b + "";
+    while ((am = d3_interpolate_numberA.exec(a)) && (bm = d3_interpolate_numberB.exec(b))) {
+      if ((bs = bm.index) > bi) {
+        bs = b.slice(bi, bs);
+        if (s[i]) s[i] += bs; else s[++i] = bs;
+      }
+      if ((am = am[0]) === (bm = bm[0])) {
+        if (s[i]) s[i] += bm; else s[++i] = bm;
+      } else {
+        s[++i] = null;
+        q.push({
+          i: i,
+          x: d3_interpolateNumber(am, bm)
+        });
+      }
+      bi = d3_interpolate_numberB.lastIndex;
+    }
+    if (bi < b.length) {
+      bs = b.slice(bi);
+      if (s[i]) s[i] += bs; else s[++i] = bs;
+    }
+    return s.length < 2 ? q[0] ? (b = q[0].x, function(t) {
+      return b(t) + "";
+    }) : function() {
+      return b;
+    } : (b = q.length, function(t) {
+      for (var i = 0, o; i < b; ++i) s[(o = q[i]).i] = o.x(t);
+      return s.join("");
+    });
+  }
+  var d3_interpolate_numberA = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/g, d3_interpolate_numberB = new RegExp(d3_interpolate_numberA.source, "g");
+  d3.interpolate = d3_interpolate;
+  function d3_interpolate(a, b) {
+    var i = d3.interpolators.length, f;
+    while (--i >= 0 && !(f = d3.interpolators[i](a, b))) ;
+    return f;
+  }
+  d3.interpolators = [ function(a, b) {
+    var t = typeof b;
+    return (t === "string" ? d3_rgb_names.has(b.toLowerCase()) || /^(#|rgb\(|hsl\()/i.test(b) ? d3_interpolateRgb : d3_interpolateString : b instanceof d3_color ? d3_interpolateRgb : Array.isArray(b) ? d3_interpolateArray : t === "object" && isNaN(b) ? d3_interpolateObject : d3_interpolateNumber)(a, b);
+  } ];
+  d3.interpolateArray = d3_interpolateArray;
+  function d3_interpolateArray(a, b) {
+    var x = [], c = [], na = a.length, nb = b.length, n0 = Math.min(a.length, b.length), i;
+    for (i = 0; i < n0; ++i) x.push(d3_interpolate(a[i], b[i]));
+    for (;i < na; ++i) c[i] = a[i];
+    for (;i < nb; ++i) c[i] = b[i];
+    return function(t) {
+      for (i = 0; i < n0; ++i) c[i] = x[i](t);
+      return c;
+    };
+  }
+  var d3_ease_default = function() {
+    return d3_identity;
+  };
+  var d3_ease = d3.map({
+    linear: d3_ease_default,
+    poly: d3_ease_poly,
+    quad: function() {
+      return d3_ease_quad;
+    },
+    cubic: function() {
+      return d3_ease_cubic;
+    },
+    sin: function() {
+      return d3_ease_sin;
+    },
+    exp: function() {
+      return d3_ease_exp;
+    },
+    circle: function() {
+      return d3_ease_circle;
+    },
+    elastic: d3_ease_elastic,
+    back: d3_ease_back,
+    bounce: function() {
+      return d3_ease_bounce;
+    }
+  });
+  var d3_ease_mode = d3.map({
+    "in": d3_identity,
+    out: d3_ease_reverse,
+    "in-out": d3_ease_reflect,
+    "out-in": function(f) {
+      return d3_ease_reflect(d3_ease_reverse(f));
+    }
+  });
+  d3.ease = function(name) {
+    var i = name.indexOf("-"), t = i >= 0 ? name.slice(0, i) : name, m = i >= 0 ? name.slice(i + 1) : "in";
+    t = d3_ease.get(t) || d3_ease_default;
+    m = d3_ease_mode.get(m) || d3_identity;
+    return d3_ease_clamp(m(t.apply(null, d3_arraySlice.call(arguments, 1))));
+  };
+  function d3_ease_clamp(f) {
+    return function(t) {
+      return t <= 0 ? 0 : t >= 1 ? 1 : f(t);
+    };
+  }
+  function d3_ease_reverse(f) {
+    return function(t) {
+      return 1 - f(1 - t);
+    };
+  }
+  function d3_ease_reflect(f) {
+    return function(t) {
+      return .5 * (t < .5 ? f(2 * t) : 2 - f(2 - 2 * t));
+    };
+  }
+  function d3_ease_quad(t) {
+    return t * t;
+  }
+  function d3_ease_cubic(t) {
+    return t * t * t;
+  }
+  function d3_ease_cubicInOut(t) {
+    if (t <= 0) return 0;
+    if (t >= 1) return 1;
+    var t2 = t * t, t3 = t2 * t;
+    return 4 * (t < .5 ? t3 : 3 * (t - t2) + t3 - .75);
+  }
+  function d3_ease_poly(e) {
+    return function(t) {
+      return Math.pow(t, e);
+    };
+  }
+  function d3_ease_sin(t) {
+    return 1 - Math.cos(t * halfπ);
+  }
+  function d3_ease_exp(t) {
+    return Math.pow(2, 10 * (t - 1));
+  }
+  function d3_ease_circle(t) {
+    return 1 - Math.sqrt(1 - t * t);
+  }
+  function d3_ease_elastic(a, p) {
+    var s;
+    if (arguments.length < 2) p = .45;
+    if (arguments.length) s = p / Ï„ * Math.asin(1 / a); else a = 1, s = p / 4;
+    return function(t) {
+      return 1 + a * Math.pow(2, -10 * t) * Math.sin((t - s) * Ï„ / p);
+    };
+  }
+  function d3_ease_back(s) {
+    if (!s) s = 1.70158;
+    return function(t) {
+      return t * t * ((s + 1) * t - s);
+    };
+  }
+  function d3_ease_bounce(t) {
+    return t < 1 / 2.75 ? 7.5625 * t * t : t < 2 / 2.75 ? 7.5625 * (t -= 1.5 / 2.75) * t + .75 : t < 2.5 / 2.75 ? 7.5625 * (t -= 2.25 / 2.75) * t + .9375 : 7.5625 * (t -= 2.625 / 2.75) * t + .984375;
+  }
+  d3.interpolateHcl = d3_interpolateHcl;
+  function d3_interpolateHcl(a, b) {
+    a = d3.hcl(a);
+    b = d3.hcl(b);
+    var ah = a.h, ac = a.c, al = a.l, bh = b.h - ah, bc = b.c - ac, bl = b.l - al;
+    if (isNaN(bc)) bc = 0, ac = isNaN(ac) ? b.c : ac;
+    if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360;
+    return function(t) {
+      return d3_hcl_lab(ah + bh * t, ac + bc * t, al + bl * t) + "";
+    };
+  }
+  d3.interpolateHsl = d3_interpolateHsl;
+  function d3_interpolateHsl(a, b) {
+    a = d3.hsl(a);
+    b = d3.hsl(b);
+    var ah = a.h, as = a.s, al = a.l, bh = b.h - ah, bs = b.s - as, bl = b.l - al;
+    if (isNaN(bs)) bs = 0, as = isNaN(as) ? b.s : as;
+    if (isNaN(bh)) bh = 0, ah = isNaN(ah) ? b.h : ah; else if (bh > 180) bh -= 360; else if (bh < -180) bh += 360;
+    return function(t) {
+      return d3_hsl_rgb(ah + bh * t, as + bs * t, al + bl * t) + "";
+    };
+  }
+  d3.interpolateLab = d3_interpolateLab;
+  function d3_interpolateLab(a, b) {
+    a = d3.lab(a);
+    b = d3.lab(b);
+    var al = a.l, aa = a.a, ab = a.b, bl = b.l - al, ba = b.a - aa, bb = b.b - ab;
+    return function(t) {
+      return d3_lab_rgb(al + bl * t, aa + ba * t, ab + bb * t) + "";
+    };
+  }
+  d3.interpolateRound = d3_interpolateRound;
+  function d3_interpolateRound(a, b) {
+    b -= a;
+    return function(t) {
+      return Math.round(a + b * t);
+    };
+  }
+  d3.transform = function(string) {
+    var g = d3_document.createElementNS(d3.ns.prefix.svg, "g");
+    return (d3.transform = function(string) {
+      if (string != null) {
+        g.setAttribute("transform", string);
+        var t = g.transform.baseVal.consolidate();
+      }
+      return new d3_transform(t ? t.matrix : d3_transformIdentity);
+    })(string);
+  };
+  function d3_transform(m) {
+    var r0 = [ m.a, m.b ], r1 = [ m.c, m.d ], kx = d3_transformNormalize(r0), kz = d3_transformDot(r0, r1), ky = d3_transformNormalize(d3_transformCombine(r1, r0, -kz)) || 0;
+    if (r0[0] * r1[1] < r1[0] * r0[1]) {
+      r0[0] *= -1;
+      r0[1] *= -1;
+      kx *= -1;
+      kz *= -1;
+    }
+    this.rotate = (kx ? Math.atan2(r0[1], r0[0]) : Math.atan2(-r1[0], r1[1])) * d3_degrees;
+    this.translate = [ m.e, m.f ];
+    this.scale = [ kx, ky ];
+    this.skew = ky ? Math.atan2(kz, ky) * d3_degrees : 0;
+  }
+  d3_transform.prototype.toString = function() {
+    return "translate(" + this.translate + ")rotate(" + this.rotate + ")skewX(" + this.skew + ")scale(" + this.scale + ")";
+  };
+  function d3_transformDot(a, b) {
+    return a[0] * b[0] + a[1] * b[1];
+  }
+  function d3_transformNormalize(a) {
+    var k = Math.sqrt(d3_transformDot(a, a));
+    if (k) {
+      a[0] /= k;
+      a[1] /= k;
+    }
+    return k;
+  }
+  function d3_transformCombine(a, b, k) {
+    a[0] += k * b[0];
+    a[1] += k * b[1];
+    return a;
+  }
+  var d3_transformIdentity = {
+    a: 1,
+    b: 0,
+    c: 0,
+    d: 1,
+    e: 0,
+    f: 0
+  };
+  d3.interpolateTransform = d3_interpolateTransform;
+  function d3_interpolateTransformPop(s) {
+    return s.length ? s.pop() + "," : "";
+  }
+  function d3_interpolateTranslate(ta, tb, s, q) {
+    if (ta[0] !== tb[0] || ta[1] !== tb[1]) {
+      var i = s.push("translate(", null, ",", null, ")");
+      q.push({
+        i: i - 4,
+        x: d3_interpolateNumber(ta[0], tb[0])
+      }, {
+        i: i - 2,
+        x: d3_interpolateNumber(ta[1], tb[1])
+      });
+    } else if (tb[0] || tb[1]) {
+      s.push("translate(" + tb + ")");
+    }
+  }
+  function d3_interpolateRotate(ra, rb, s, q) {
+    if (ra !== rb) {
+      if (ra - rb > 180) rb += 360; else if (rb - ra > 180) ra += 360;
+      q.push({
+        i: s.push(d3_interpolateTransformPop(s) + "rotate(", null, ")") - 2,
+        x: d3_interpolateNumber(ra, rb)
+      });
+    } else if (rb) {
+      s.push(d3_interpolateTransformPop(s) + "rotate(" + rb + ")");
+    }
+  }
+  function d3_interpolateSkew(wa, wb, s, q) {
+    if (wa !== wb) {
+      q.push({
+        i: s.push(d3_interpolateTransformPop(s) + "skewX(", null, ")") - 2,
+        x: d3_interpolateNumber(wa, wb)
+      });
+    } else if (wb) {
+      s.push(d3_interpolateTransformPop(s) + "skewX(" + wb + ")");
+    }
+  }
+  function d3_interpolateScale(ka, kb, s, q) {
+    if (ka[0] !== kb[0] || ka[1] !== kb[1]) {
+      var i = s.push(d3_interpolateTransformPop(s) + "scale(", null, ",", null, ")");
+      q.push({
+        i: i - 4,
+        x: d3_interpolateNumber(ka[0], kb[0])
+      }, {
+        i: i - 2,
+        x: d3_interpolateNumber(ka[1], kb[1])
+      });
+    } else if (kb[0] !== 1 || kb[1] !== 1) {
+      s.push(d3_interpolateTransformPop(s) + "scale(" + kb + ")");
+    }
+  }
+  function d3_interpolateTransform(a, b) {
+    var s = [], q = [];
+    a = d3.transform(a), b = d3.transform(b);
+    d3_interpolateTranslate(a.translate, b.translate, s, q);
+    d3_interpolateRotate(a.rotate, b.rotate, s, q);
+    d3_interpolateSkew(a.skew, b.skew, s, q);
+    d3_interpolateScale(a.scale, b.scale, s, q);
+    a = b = null;
+    return function(t) {
+      var i = -1, n = q.length, o;
+      while (++i < n) s[(o = q[i]).i] = o.x(t);
+      return s.join("");
+    };
+  }
+  function d3_uninterpolateNumber(a, b) {
+    b = (b -= a = +a) || 1 / b;
+    return function(x) {
+      return (x - a) / b;
+    };
+  }
+  function d3_uninterpolateClamp(a, b) {
+    b = (b -= a = +a) || 1 / b;
+    return function(x) {
+      return Math.max(0, Math.min(1, (x - a) / b));
+    };
+  }
+  d3.layout = {};
+  d3.layout.bundle = function() {
+    return function(links) {
+      var paths = [], i = -1, n = links.length;
+      while (++i < n) paths.push(d3_layout_bundlePath(links[i]));
+      return paths;
+    };
+  };
+  function d3_layout_bundlePath(link) {
+    var start = link.source, end = link.target, lca = d3_layout_bundleLeastCommonAncestor(start, end), points = [ start ];
+    while (start !== lca) {
+      start = start.parent;
+      points.push(start);
+    }
+    var k = points.length;
+    while (end !== lca) {
+      points.splice(k, 0, end);
+      end = end.parent;
+    }
+    return points;
+  }
+  function d3_layout_bundleAncestors(node) {
+    var ancestors = [], parent = node.parent;
+    while (parent != null) {
+      ancestors.push(node);
+      node = parent;
+      parent = parent.parent;
+    }
+    ancestors.push(node);
+    return ancestors;
+  }
+  function d3_layout_bundleLeastCommonAncestor(a, b) {
+    if (a === b) return a;
+    var aNodes = d3_layout_bundleAncestors(a), bNodes = d3_layout_bundleAncestors(b), aNode = aNodes.pop(), bNode = bNodes.pop(), sharedNode = null;
+    while (aNode === bNode) {
+      sharedNode = aNode;
+      aNode = aNodes.pop();
+      bNode = bNodes.pop();
+    }
+    return sharedNode;
+  }
+  d3.layout.chord = function() {
+    var chord = {}, chords, groups, matrix, n, padding = 0, sortGroups, sortSubgroups, sortChords;
+    function relayout() {
+      var subgroups = {}, groupSums = [], groupIndex = d3.range(n), subgroupIndex = [], k, x, x0, i, j;
+      chords = [];
+      groups = [];
+      k = 0, i = -1;
+      while (++i < n) {
+        x = 0, j = -1;
+        while (++j < n) {
+          x += matrix[i][j];
+        }
+        groupSums.push(x);
+        subgroupIndex.push(d3.range(n));
+        k += x;
+      }
+      if (sortGroups) {
+        groupIndex.sort(function(a, b) {
+          return sortGroups(groupSums[a], groupSums[b]);
+        });
+      }
+      if (sortSubgroups) {
+        subgroupIndex.forEach(function(d, i) {
+          d.sort(function(a, b) {
+            return sortSubgroups(matrix[i][a], matrix[i][b]);
+          });
+        });
+      }
+      k = (Ï„ - padding * n) / k;
+      x = 0, i = -1;
+      while (++i < n) {
+        x0 = x, j = -1;
+        while (++j < n) {
+          var di = groupIndex[i], dj = subgroupIndex[di][j], v = matrix[di][dj], a0 = x, a1 = x += v * k;
+          subgroups[di + "-" + dj] = {
+            index: di,
+            subindex: dj,
+            startAngle: a0,
+            endAngle: a1,
+            value: v
+          };
+        }
+        groups[di] = {
+          index: di,
+          startAngle: x0,
+          endAngle: x,
+          value: groupSums[di]
+        };
+        x += padding;
+      }
+      i = -1;
+      while (++i < n) {
+        j = i - 1;
+        while (++j < n) {
+          var source = subgroups[i + "-" + j], target = subgroups[j + "-" + i];
+          if (source.value || target.value) {
+            chords.push(source.value < target.value ? {
+              source: target,
+              target: source
+            } : {
+              source: source,
+              target: target
+            });
+          }
+        }
+      }
+      if (sortChords) resort();
+    }
+    function resort() {
+      chords.sort(function(a, b) {
+        return sortChords((a.source.value + a.target.value) / 2, (b.source.value + b.target.value) / 2);
+      });
+    }
+    chord.matrix = function(x) {
+      if (!arguments.length) return matrix;
+      n = (matrix = x) && matrix.length;
+      chords = groups = null;
+      return chord;
+    };
+    chord.padding = function(x) {
+      if (!arguments.length) return padding;
+      padding = x;
+      chords = groups = null;
+      return chord;
+    };
+    chord.sortGroups = function(x) {
+      if (!arguments.length) return sortGroups;
+      sortGroups = x;
+      chords = groups = null;
+      return chord;
+    };
+    chord.sortSubgroups = function(x) {
+      if (!arguments.length) return sortSubgroups;
+      sortSubgroups = x;
+      chords = null;
+      return chord;
+    };
+    chord.sortChords = function(x) {
+      if (!arguments.length) return sortChords;
+      sortChords = x;
+      if (chords) resort();
+      return chord;
+    };
+    chord.chords = function() {
+      if (!chords) relayout();
+      return chords;
+    };
+    chord.groups = function() {
+      if (!groups) relayout();
+      return groups;
+    };
+    return chord;
+  };
+  d3.layout.force = function() {
+    var force = {}, event = d3.dispatch("start", "tick", "end"), timer, size = [ 1, 1 ], drag, alpha, friction = .9, linkDistance = d3_layout_forceLinkDistance, linkStrength = d3_layout_forceLinkStrength, charge = -30, chargeDistance2 = d3_layout_forceChargeDistance2, gravity = .1, theta2 = .64, nodes = [], links = [], distances, strengths, charges;
+    function repulse(node) {
+      return function(quad, x1, _, x2) {
+        if (quad.point !== node) {
+          var dx = quad.cx - node.x, dy = quad.cy - node.y, dw = x2 - x1, dn = dx * dx + dy * dy;
+          if (dw * dw / theta2 < dn) {
+            if (dn < chargeDistance2) {
+              var k = quad.charge / dn;
+              node.px -= dx * k;
+              node.py -= dy * k;
+            }
+            return true;
+          }
+          if (quad.point && dn && dn < chargeDistance2) {
+            var k = quad.pointCharge / dn;
+            node.px -= dx * k;
+            node.py -= dy * k;
+          }
+        }
+        return !quad.charge;
+      };
+    }
+    force.tick = function() {
+      if ((alpha *= .99) < .005) {
+        timer = null;
+        event.end({
+          type: "end",
+          alpha: alpha = 0
+        });
+        return true;
+      }
+      var n = nodes.length, m = links.length, q, i, o, s, t, l, k, x, y;
+      for (i = 0; i < m; ++i) {
+        o = links[i];
+        s = o.source;
+        t = o.target;
+        x = t.x - s.x;
+        y = t.y - s.y;
+        if (l = x * x + y * y) {
+          l = alpha * strengths[i] * ((l = Math.sqrt(l)) - distances[i]) / l;
+          x *= l;
+          y *= l;
+          t.x -= x * (k = s.weight + t.weight ? s.weight / (s.weight + t.weight) : .5);
+          t.y -= y * k;
+          s.x += x * (k = 1 - k);
+          s.y += y * k;
+        }
+      }
+      if (k = alpha * gravity) {
+        x = size[0] / 2;
+        y = size[1] / 2;
+        i = -1;
+        if (k) while (++i < n) {
+          o = nodes[i];
+          o.x += (x - o.x) * k;
+          o.y += (y - o.y) * k;
+        }
+      }
+      if (charge) {
+        d3_layout_forceAccumulate(q = d3.geom.quadtree(nodes), alpha, charges);
+        i = -1;
+        while (++i < n) {
+          if (!(o = nodes[i]).fixed) {
+            q.visit(repulse(o));
+          }
+        }
+      }
+      i = -1;
+      while (++i < n) {
+        o = nodes[i];
+        if (o.fixed) {
+          o.x = o.px;
+          o.y = o.py;
+        } else {
+          o.x -= (o.px - (o.px = o.x)) * friction;
+          o.y -= (o.py - (o.py = o.y)) * friction;
+        }
+      }
+      event.tick({
+        type: "tick",
+        alpha: alpha
+      });
+    };
+    force.nodes = function(x) {
+      if (!arguments.length) return nodes;
+      nodes = x;
+      return force;
+    };
+    force.links = function(x) {
+      if (!arguments.length) return links;
+      links = x;
+      return force;
+    };
+    force.size = function(x) {
+      if (!arguments.length) return size;
+      size = x;
+      return force;
+    };
+    force.linkDistance = function(x) {
+      if (!arguments.length) return linkDistance;
+      linkDistance = typeof x === "function" ? x : +x;
+      return force;
+    };
+    force.distance = force.linkDistance;
+    force.linkStrength = function(x) {
+      if (!arguments.length) return linkStrength;
+      linkStrength = typeof x === "function" ? x : +x;
+      return force;
+    };
+    force.friction = function(x) {
+      if (!arguments.length) return friction;
+      friction = +x;
+      return force;
+    };
+    force.charge = function(x) {
+      if (!arguments.length) return charge;
+      charge = typeof x === "function" ? x : +x;
+      return force;
+    };
+    force.chargeDistance = function(x) {
+      if (!arguments.length) return Math.sqrt(chargeDistance2);
+      chargeDistance2 = x * x;
+      return force;
+    };
+    force.gravity = function(x) {
+      if (!arguments.length) return gravity;
+      gravity = +x;
+      return force;
+    };
+    force.theta = function(x) {
+      if (!arguments.length) return Math.sqrt(theta2);
+      theta2 = x * x;
+      return force;
+    };
+    force.alpha = function(x) {
+      if (!arguments.length) return alpha;
+      x = +x;
+      if (alpha) {
+        if (x > 0) {
+          alpha = x;
+        } else {
+          timer.c = null, timer.t = NaN, timer = null;
+          event.end({
+            type: "end",
+            alpha: alpha = 0
+          });
+        }
+      } else if (x > 0) {
+        event.start({
+          type: "start",
+          alpha: alpha = x
+        });
+        timer = d3_timer(force.tick);
+      }
+      return force;
+    };
+    force.start = function() {
+      var i, n = nodes.length, m = links.length, w = size[0], h = size[1], neighbors, o;
+      for (i = 0; i < n; ++i) {
+        (o = nodes[i]).index = i;
+        o.weight = 0;
+      }
+      for (i = 0; i < m; ++i) {
+        o = links[i];
+        if (typeof o.source == "number") o.source = nodes[o.source];
+        if (typeof o.target == "number") o.target = nodes[o.target];
+        ++o.source.weight;
+        ++o.target.weight;
+      }
+      for (i = 0; i < n; ++i) {
+        o = nodes[i];
+        if (isNaN(o.x)) o.x = position("x", w);
+        if (isNaN(o.y)) o.y = position("y", h);
+        if (isNaN(o.px)) o.px = o.x;
+        if (isNaN(o.py)) o.py = o.y;
+      }
+      distances = [];
+      if (typeof linkDistance === "function") for (i = 0; i < m; ++i) distances[i] = +linkDistance.call(this, links[i], i); else for (i = 0; i < m; ++i) distances[i] = linkDistance;
+      strengths = [];
+      if (typeof linkStrength === "function") for (i = 0; i < m; ++i) strengths[i] = +linkStrength.call(this, links[i], i); else for (i = 0; i < m; ++i) strengths[i] = linkStrength;
+      charges = [];
+      if (typeof charge === "function") for (i = 0; i < n; ++i) charges[i] = +charge.call(this, nodes[i], i); else for (i = 0; i < n; ++i) charges[i] = charge;
+      function position(dimension, size) {
+        if (!neighbors) {
+          neighbors = new Array(n);
+          for (j = 0; j < n; ++j) {
+            neighbors[j] = [];
+          }
+          for (j = 0; j < m; ++j) {
+            var o = links[j];
+            neighbors[o.source.index].push(o.target);
+            neighbors[o.target.index].push(o.source);
+          }
+        }
+        var candidates = neighbors[i], j = -1, l = candidates.length, x;
+        while (++j < l) if (!isNaN(x = candidates[j][dimension])) return x;
+        return Math.random() * size;
+      }
+      return force.resume();
+    };
+    force.resume = function() {
+      return force.alpha(.1);
+    };
+    force.stop = function() {
+      return force.alpha(0);
+    };
+    force.drag = function() {
+      if (!drag) drag = d3.behavior.drag().origin(d3_identity).on("dragstart.force", d3_layout_forceDragstart).on("drag.force", dragmove).on("dragend.force", d3_layout_forceDragend);
+      if (!arguments.length) return drag;
+      this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag);
+    };
+    function dragmove(d) {
+      d.px = d3.event.x, d.py = d3.event.y;
+      force.resume();
+    }
+    return d3.rebind(force, event, "on");
+  };
+  function d3_layout_forceDragstart(d) {
+    d.fixed |= 2;
+  }
+  function d3_layout_forceDragend(d) {
+    d.fixed &= ~6;
+  }
+  function d3_layout_forceMouseover(d) {
+    d.fixed |= 4;
+    d.px = d.x, d.py = d.y;
+  }
+  function d3_layout_forceMouseout(d) {
+    d.fixed &= ~4;
+  }
+  function d3_layout_forceAccumulate(quad, alpha, charges) {
+    var cx = 0, cy = 0;
+    quad.charge = 0;
+    if (!quad.leaf) {
+      var nodes = quad.nodes, n = nodes.length, i = -1, c;
+      while (++i < n) {
+        c = nodes[i];
+        if (c == null) continue;
+        d3_layout_forceAccumulate(c, alpha, charges);
+        quad.charge += c.charge;
+        cx += c.charge * c.cx;
+        cy += c.charge * c.cy;
+      }
+    }
+    if (quad.point) {
+      if (!quad.leaf) {
+        quad.point.x += Math.random() - .5;
+        quad.point.y += Math.random() - .5;
+      }
+      var k = alpha * charges[quad.point.index];
+      quad.charge += quad.pointCharge = k;
+      cx += k * quad.point.x;
+      cy += k * quad.point.y;
+    }
+    quad.cx = cx / quad.charge;
+    quad.cy = cy / quad.charge;
+  }
+  var d3_layout_forceLinkDistance = 20, d3_layout_forceLinkStrength = 1, d3_layout_forceChargeDistance2 = Infinity;
+  d3.layout.hierarchy = function() {
+    var sort = d3_layout_hierarchySort, children = d3_layout_hierarchyChildren, value = d3_layout_hierarchyValue;
+    function hierarchy(root) {
+      var stack = [ root ], nodes = [], node;
+      root.depth = 0;
+      while ((node = stack.pop()) != null) {
+        nodes.push(node);
+        if ((childs = children.call(hierarchy, node, node.depth)) && (n = childs.length)) {
+          var n, childs, child;
+          while (--n >= 0) {
+            stack.push(child = childs[n]);
+            child.parent = node;
+            child.depth = node.depth + 1;
+          }
+          if (value) node.value = 0;
+          node.children = childs;
+        } else {
+          if (value) node.value = +value.call(hierarchy, node, node.depth) || 0;
+          delete node.children;
+        }
+      }
+      d3_layout_hierarchyVisitAfter(root, function(node) {
+        var childs, parent;
+        if (sort && (childs = node.children)) childs.sort(sort);
+        if (value && (parent = node.parent)) parent.value += node.value;
+      });
+      return nodes;
+    }
+    hierarchy.sort = function(x) {
+      if (!arguments.length) return sort;
+      sort = x;
+      return hierarchy;
+    };
+    hierarchy.children = function(x) {
+      if (!arguments.length) return children;
+      children = x;
+      return hierarchy;
+    };
+    hierarchy.value = function(x) {
+      if (!arguments.length) return value;
+      value = x;
+      return hierarchy;
+    };
+    hierarchy.revalue = function(root) {
+      if (value) {
+        d3_layout_hierarchyVisitBefore(root, function(node) {
+          if (node.children) node.value = 0;
+        });
+        d3_layout_hierarchyVisitAfter(root, function(node) {
+          var parent;
+          if (!node.children) node.value = +value.call(hierarchy, node, node.depth) || 0;
+          if (parent = node.parent) parent.value += node.value;
+        });
+      }
+      return root;
+    };
+    return hierarchy;
+  };
+  function d3_layout_hierarchyRebind(object, hierarchy) {
+    d3.rebind(object, hierarchy, "sort", "children", "value");
+    object.nodes = object;
+    object.links = d3_layout_hierarchyLinks;
+    return object;
+  }
+  function d3_layout_hierarchyVisitBefore(node, callback) {
+    var nodes = [ node ];
+    while ((node = nodes.pop()) != null) {
+      callback(node);
+      if ((children = node.children) && (n = children.length)) {
+        var n, children;
+        while (--n >= 0) nodes.push(children[n]);
+      }
+    }
+  }
+  function d3_layout_hierarchyVisitAfter(node, callback) {
+    var nodes = [ node ], nodes2 = [];
+    while ((node = nodes.pop()) != null) {
+      nodes2.push(node);
+      if ((children = node.children) && (n = children.length)) {
+        var i = -1, n, children;
+        while (++i < n) nodes.push(children[i]);
+      }
+    }
+    while ((node = nodes2.pop()) != null) {
+      callback(node);
+    }
+  }
+  function d3_layout_hierarchyChildren(d) {
+    return d.children;
+  }
+  function d3_layout_hierarchyValue(d) {
+    return d.value;
+  }
+  function d3_layout_hierarchySort(a, b) {
+    return b.value - a.value;
+  }
+  function d3_layout_hierarchyLinks(nodes) {
+    return d3.merge(nodes.map(function(parent) {
+      return (parent.children || []).map(function(child) {
+        return {
+          source: parent,
+          target: child
+        };
+      });
+    }));
+  }
+  d3.layout.partition = function() {
+    var hierarchy = d3.layout.hierarchy(), size = [ 1, 1 ];
+    function position(node, x, dx, dy) {
+      var children = node.children;
+      node.x = x;
+      node.y = node.depth * dy;
+      node.dx = dx;
+      node.dy = dy;
+      if (children && (n = children.length)) {
+        var i = -1, n, c, d;
+        dx = node.value ? dx / node.value : 0;
+        while (++i < n) {
+          position(c = children[i], x, d = c.value * dx, dy);
+          x += d;
+        }
+      }
+    }
+    function depth(node) {
+      var children = node.children, d = 0;
+      if (children && (n = children.length)) {
+        var i = -1, n;
+        while (++i < n) d = Math.max(d, depth(children[i]));
+      }
+      return 1 + d;
+    }
+    function partition(d, i) {
+      var nodes = hierarchy.call(this, d, i);
+      position(nodes[0], 0, size[0], size[1] / depth(nodes[0]));
+      return nodes;
+    }
+    partition.size = function(x) {
+      if (!arguments.length) return size;
+      size = x;
+      return partition;
+    };
+    return d3_layout_hierarchyRebind(partition, hierarchy);
+  };
+  d3.layout.pie = function() {
+    var value = Number, sort = d3_layout_pieSortByValue, startAngle = 0, endAngle = Ï„, padAngle = 0;
+    function pie(data) {
+      var n = data.length, values = data.map(function(d, i) {
+        return +value.call(pie, d, i);
+      }), a = +(typeof startAngle === "function" ? startAngle.apply(this, arguments) : startAngle), da = (typeof endAngle === "function" ? endAngle.apply(this, arguments) : endAngle) - a, p = Math.min(Math.abs(da) / n, +(typeof padAngle === "function" ? padAngle.apply(this, arguments) : padAngle)), pa = p * (da < 0 ? -1 : 1), sum = d3.sum(values), k = sum ? (da - n * pa) / sum : 0, index = d3.range(n), arcs = [], v;
+      if (sort != null) index.sort(sort === d3_layout_pieSortByValue ? function(i, j) {
+        return values[j] - values[i];
+      } : function(i, j) {
+        return sort(data[i], data[j]);
+      });
+      index.forEach(function(i) {
+        arcs[i] = {
+          data: data[i],
+          value: v = values[i],
+          startAngle: a,
+          endAngle: a += v * k + pa,
+          padAngle: p
+        };
+      });
+      return arcs;
+    }
+    pie.value = function(_) {
+      if (!arguments.length) return value;
+      value = _;
+      return pie;
+    };
+    pie.sort = function(_) {
+      if (!arguments.length) return sort;
+      sort = _;
+      return pie;
+    };
+    pie.startAngle = function(_) {
+      if (!arguments.length) return startAngle;
+      startAngle = _;
+      return pie;
+    };
+    pie.endAngle = function(_) {
+      if (!arguments.length) return endAngle;
+      endAngle = _;
+      return pie;
+    };
+    pie.padAngle = function(_) {
+      if (!arguments.length) return padAngle;
+      padAngle = _;
+      return pie;
+    };
+    return pie;
+  };
+  var d3_layout_pieSortByValue = {};
+  d3.layout.stack = function() {
+    var values = d3_identity, order = d3_layout_stackOrderDefault, offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY;
+    function stack(data, index) {
+      if (!(n = data.length)) return data;
+      var series = data.map(function(d, i) {
+        return values.call(stack, d, i);
+      });
+      var points = series.map(function(d) {
+        return d.map(function(v, i) {
+          return [ x.call(stack, v, i), y.call(stack, v, i) ];
+        });
+      });
+      var orders = order.call(stack, points, index);
+      series = d3.permute(series, orders);
+      points = d3.permute(points, orders);
+      var offsets = offset.call(stack, points, index);
+      var m = series[0].length, n, i, j, o;
+      for (j = 0; j < m; ++j) {
+        out.call(stack, series[0][j], o = offsets[j], points[0][j][1]);
+        for (i = 1; i < n; ++i) {
+          out.call(stack, series[i][j], o += points[i - 1][j][1], points[i][j][1]);
+        }
+      }
+      return data;
+    }
+    stack.values = function(x) {
+      if (!arguments.length) return values;
+      values = x;
+      return stack;
+    };
+    stack.order = function(x) {
+      if (!arguments.length) return order;
+      order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault;
+      return stack;
+    };
+    stack.offset = function(x) {
+      if (!arguments.length) return offset;
+      offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero;
+      return stack;
+    };
+    stack.x = function(z) {
+      if (!arguments.length) return x;
+      x = z;
+      return stack;
+    };
+    stack.y = function(z) {
+      if (!arguments.length) return y;
+      y = z;
+      return stack;
+    };
+    stack.out = function(z) {
+      if (!arguments.length) return out;
+      out = z;
+      return stack;
+    };
+    return stack;
+  };
+  function d3_layout_stackX(d) {
+    return d.x;
+  }
+  function d3_layout_stackY(d) {
+    return d.y;
+  }
+  function d3_layout_stackOut(d, y0, y) {
+    d.y0 = y0;
+    d.y = y;
+  }
+  var d3_layout_stackOrders = d3.map({
+    "inside-out": function(data) {
+      var n = data.length, i, j, max = data.map(d3_layout_stackMaxIndex), sums = data.map(d3_layout_stackReduceSum), index = d3.range(n).sort(function(a, b) {
+        return max[a] - max[b];
+      }), top = 0, bottom = 0, tops = [], bottoms = [];
+      for (i = 0; i < n; ++i) {
+        j = index[i];
+        if (top < bottom) {
+          top += sums[j];
+          tops.push(j);
+        } else {
+          bottom += sums[j];
+          bottoms.push(j);
+        }
+      }
+      return bottoms.reverse().concat(tops);
+    },
+    reverse: function(data) {
+      return d3.range(data.length).reverse();
+    },
+    "default": d3_layout_stackOrderDefault
+  });
+  var d3_layout_stackOffsets = d3.map({
+    silhouette: function(data) {
+      var n = data.length, m = data[0].length, sums = [], max = 0, i, j, o, y0 = [];
+      for (j = 0; j < m; ++j) {
+        for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
+        if (o > max) max = o;
+        sums.push(o);
+      }
+      for (j = 0; j < m; ++j) {
+        y0[j] = (max - sums[j]) / 2;
+      }
+      return y0;
+    },
+    wiggle: function(data) {
+      var n = data.length, x = data[0], m = x.length, i, j, k, s1, s2, s3, dx, o, o0, y0 = [];
+      y0[0] = o = o0 = 0;
+      for (j = 1; j < m; ++j) {
+        for (i = 0, s1 = 0; i < n; ++i) s1 += data[i][j][1];
+        for (i = 0, s2 = 0, dx = x[j][0] - x[j - 1][0]; i < n; ++i) {
+          for (k = 0, s3 = (data[i][j][1] - data[i][j - 1][1]) / (2 * dx); k < i; ++k) {
+            s3 += (data[k][j][1] - data[k][j - 1][1]) / dx;
+          }
+          s2 += s3 * data[i][j][1];
+        }
+        y0[j] = o -= s1 ? s2 / s1 * dx : 0;
+        if (o < o0) o0 = o;
+      }
+      for (j = 0; j < m; ++j) y0[j] -= o0;
+      return y0;
+    },
+    expand: function(data) {
+      var n = data.length, m = data[0].length, k = 1 / n, i, j, o, y0 = [];
+      for (j = 0; j < m; ++j) {
+        for (i = 0, o = 0; i < n; i++) o += data[i][j][1];
+        if (o) for (i = 0; i < n; i++) data[i][j][1] /= o; else for (i = 0; i < n; i++) data[i][j][1] = k;
+      }
+      for (j = 0; j < m; ++j) y0[j] = 0;
+      return y0;
+    },
+    zero: d3_layout_stackOffsetZero
+  });
+  function d3_layout_stackOrderDefault(data) {
+    return d3.range(data.length);
+  }
+  function d3_layout_stackOffsetZero(data) {
+    var j = -1, m = data[0].length, y0 = [];
+    while (++j < m) y0[j] = 0;
+    return y0;
+  }
+  function d3_layout_stackMaxIndex(array) {
+    var i = 1, j = 0, v = array[0][1], k, n = array.length;
+    for (;i < n; ++i) {
+      if ((k = array[i][1]) > v) {
+        j = i;
+        v = k;
+      }
+    }
+    return j;
+  }
+  function d3_layout_stackReduceSum(d) {
+    return d.reduce(d3_layout_stackSum, 0);
+  }
+  function d3_layout_stackSum(p, d) {
+    return p + d[1];
+  }
+  d3.layout.histogram = function() {
+    var frequency = true, valuer = Number, ranger = d3_layout_histogramRange, binner = d3_layout_histogramBinSturges;
+    function histogram(data, i) {
+      var bins = [], values = data.map(valuer, this), range = ranger.call(this, values, i), thresholds = binner.call(this, range, values, i), bin, i = -1, n = values.length, m = thresholds.length - 1, k = frequency ? 1 : 1 / n, x;
+      while (++i < m) {
+        bin = bins[i] = [];
+        bin.dx = thresholds[i + 1] - (bin.x = thresholds[i]);
+        bin.y = 0;
+      }
+      if (m > 0) {
+        i = -1;
+        while (++i < n) {
+          x = values[i];
+          if (x >= range[0] && x <= range[1]) {
+            bin = bins[d3.bisect(thresholds, x, 1, m) - 1];
+            bin.y += k;
+            bin.push(data[i]);
+          }
+        }
+      }
+      return bins;
+    }
+    histogram.value = function(x) {
+      if (!arguments.length) return valuer;
+      valuer = x;
+      return histogram;
+    };
+    histogram.range = function(x) {
+      if (!arguments.length) return ranger;
+      ranger = d3_functor(x);
+      return histogram;
+    };
+    histogram.bins = function(x) {
+      if (!arguments.length) return binner;
+      binner = typeof x === "number" ? function(range) {
+        return d3_layout_histogramBinFixed(range, x);
+      } : d3_functor(x);
+      return histogram;
+    };
+    histogram.frequency = function(x) {
+      if (!arguments.length) return frequency;
+      frequency = !!x;
+      return histogram;
+    };
+    return histogram;
+  };
+  function d3_layout_histogramBinSturges(range, values) {
+    return d3_layout_histogramBinFixed(range, Math.ceil(Math.log(values.length) / Math.LN2 + 1));
+  }
+  function d3_layout_histogramBinFixed(range, n) {
+    var x = -1, b = +range[0], m = (range[1] - b) / n, f = [];
+    while (++x <= n) f[x] = m * x + b;
+    return f;
+  }
+  function d3_layout_histogramRange(values) {
+    return [ d3.min(values), d3.max(values) ];
+  }
+  d3.layout.pack = function() {
+    var hierarchy = d3.layout.hierarchy().sort(d3_layout_packSort), padding = 0, size = [ 1, 1 ], radius;
+    function pack(d, i) {
+      var nodes = hierarchy.call(this, d, i), root = nodes[0], w = size[0], h = size[1], r = radius == null ? Math.sqrt : typeof radius === "function" ? radius : function() {
+        return radius;
+      };
+      root.x = root.y = 0;
+      d3_layout_hierarchyVisitAfter(root, function(d) {
+        d.r = +r(d.value);
+      });
+      d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings);
+      if (padding) {
+        var dr = padding * (radius ? 1 : Math.max(2 * root.r / w, 2 * root.r / h)) / 2;
+        d3_layout_hierarchyVisitAfter(root, function(d) {
+          d.r += dr;
+        });
+        d3_layout_hierarchyVisitAfter(root, d3_layout_packSiblings);
+        d3_layout_hierarchyVisitAfter(root, function(d) {
+          d.r -= dr;
+        });
+      }
+      d3_layout_packTransform(root, w / 2, h / 2, radius ? 1 : 1 / Math.max(2 * root.r / w, 2 * root.r / h));
+      return nodes;
+    }
+    pack.size = function(_) {
+      if (!arguments.length) return size;
+      size = _;
+      return pack;
+    };
+    pack.radius = function(_) {
+      if (!arguments.length) return radius;
+      radius = _ == null || typeof _ === "function" ? _ : +_;
+      return pack;
+    };
+    pack.padding = function(_) {
+      if (!arguments.length) return padding;
+      padding = +_;
+      return pack;
+    };
+    return d3_layout_hierarchyRebind(pack, hierarchy);
+  };
+  function d3_layout_packSort(a, b) {
+    return a.value - b.value;
+  }
+  function d3_layout_packInsert(a, b) {
+    var c = a._pack_next;
+    a._pack_next = b;
+    b._pack_prev = a;
+    b._pack_next = c;
+    c._pack_prev = b;
+  }
+  function d3_layout_packSplice(a, b) {
+    a._pack_next = b;
+    b._pack_prev = a;
+  }
+  function d3_layout_packIntersects(a, b) {
+    var dx = b.x - a.x, dy = b.y - a.y, dr = a.r + b.r;
+    return .999 * dr * dr > dx * dx + dy * dy;
+  }
+  function d3_layout_packSiblings(node) {
+    if (!(nodes = node.children) || !(n = nodes.length)) return;
+    var nodes, xMin = Infinity, xMax = -Infinity, yMin = Infinity, yMax = -Infinity, a, b, c, i, j, k, n;
+    function bound(node) {
+      xMin = Math.min(node.x - node.r, xMin);
+      xMax = Math.max(node.x + node.r, xMax);
+      yMin = Math.min(node.y - node.r, yMin);
+      yMax = Math.max(node.y + node.r, yMax);
+    }
+    nodes.forEach(d3_layout_packLink);
+    a = nodes[0];
+    a.x = -a.r;
+    a.y = 0;
+    bound(a);
+    if (n > 1) {
+      b = nodes[1];
+      b.x = b.r;
+      b.y = 0;
+      bound(b);
+      if (n > 2) {
+        c = nodes[2];
+        d3_layout_packPlace(a, b, c);
+        bound(c);
+        d3_layout_packInsert(a, c);
+        a._pack_prev = c;
+        d3_layout_packInsert(c, b);
+        b = a._pack_next;
+        for (i = 3; i < n; i++) {
+          d3_layout_packPlace(a, b, c = nodes[i]);
+          var isect = 0, s1 = 1, s2 = 1;
+          for (j = b._pack_next; j !== b; j = j._pack_next, s1++) {
+            if (d3_layout_packIntersects(j, c)) {
+              isect = 1;
+              break;
+            }
+          }
+          if (isect == 1) {
+            for (k = a._pack_prev; k !== j._pack_prev; k = k._pack_prev, s2++) {
+              if (d3_layout_packIntersects(k, c)) {
+                break;
+              }
+            }
+          }
+          if (isect) {
+            if (s1 < s2 || s1 == s2 && b.r < a.r) d3_layout_packSplice(a, b = j); else d3_layout_packSplice(a = k, b);
+            i--;
+          } else {
+            d3_layout_packInsert(a, c);
+            b = c;
+            bound(c);
+          }
+        }
+      }
+    }
+    var cx = (xMin + xMax) / 2, cy = (yMin + yMax) / 2, cr = 0;
+    for (i = 0; i < n; i++) {
+      c = nodes[i];
+      c.x -= cx;
+      c.y -= cy;
+      cr = Math.max(cr, c.r + Math.sqrt(c.x * c.x + c.y * c.y));
+    }
+    node.r = cr;
+    nodes.forEach(d3_layout_packUnlink);
+  }
+  function d3_layout_packLink(node) {
+    node._pack_next = node._pack_prev = node;
+  }
+  function d3_layout_packUnlink(node) {
+    delete node._pack_next;
+    delete node._pack_prev;
+  }
+  function d3_layout_packTransform(node, x, y, k) {
+    var children = node.children;
+    node.x = x += k * node.x;
+    node.y = y += k * node.y;
+    node.r *= k;
+    if (children) {
+      var i = -1, n = children.length;
+      while (++i < n) d3_layout_packTransform(children[i], x, y, k);
+    }
+  }
+  function d3_layout_packPlace(a, b, c) {
+    var db = a.r + c.r, dx = b.x - a.x, dy = b.y - a.y;
+    if (db && (dx || dy)) {
+      var da = b.r + c.r, dc = dx * dx + dy * dy;
+      da *= da;
+      db *= db;
+      var x = .5 + (db - da) / (2 * dc), y = Math.sqrt(Math.max(0, 2 * da * (db + dc) - (db -= dc) * db - da * da)) / (2 * dc);
+      c.x = a.x + x * dx + y * dy;
+      c.y = a.y + x * dy - y * dx;
+    } else {
+      c.x = a.x + db;
+      c.y = a.y;
+    }
+  }
+  d3.layout.tree = function() {
+    var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = null;
+    function tree(d, i) {
+      var nodes = hierarchy.call(this, d, i), root0 = nodes[0], root1 = wrapTree(root0);
+      d3_layout_hierarchyVisitAfter(root1, firstWalk), root1.parent.m = -root1.z;
+      d3_layout_hierarchyVisitBefore(root1, secondWalk);
+      if (nodeSize) d3_layout_hierarchyVisitBefore(root0, sizeNode); else {
+        var left = root0, right = root0, bottom = root0;
+        d3_layout_hierarchyVisitBefore(root0, function(node) {
+          if (node.x < left.x) left = node;
+          if (node.x > right.x) right = node;
+          if (node.depth > bottom.depth) bottom = node;
+        });
+        var tx = separation(left, right) / 2 - left.x, kx = size[0] / (right.x + separation(right, left) / 2 + tx), ky = size[1] / (bottom.depth || 1);
+        d3_layout_hierarchyVisitBefore(root0, function(node) {
+          node.x = (node.x + tx) * kx;
+          node.y = node.depth * ky;
+        });
+      }
+      return nodes;
+    }
+    function wrapTree(root0) {
+      var root1 = {
+        A: null,
+        children: [ root0 ]
+      }, queue = [ root1 ], node1;
+      while ((node1 = queue.pop()) != null) {
+        for (var children = node1.children, child, i = 0, n = children.length; i < n; ++i) {
+          queue.push((children[i] = child = {
+            _: children[i],
+            parent: node1,
+            children: (child = children[i].children) && child.slice() || [],
+            A: null,
+            a: null,
+            z: 0,
+            m: 0,
+            c: 0,
+            s: 0,
+            t: null,
+            i: i
+          }).a = child);
+        }
+      }
+      return root1.children[0];
+    }
+    function firstWalk(v) {
+      var children = v.children, siblings = v.parent.children, w = v.i ? siblings[v.i - 1] : null;
+      if (children.length) {
+        d3_layout_treeShift(v);
+        var midpoint = (children[0].z + children[children.length - 1].z) / 2;
+        if (w) {
+          v.z = w.z + separation(v._, w._);
+          v.m = v.z - midpoint;
+        } else {
+          v.z = midpoint;
+        }
+      } else if (w) {
+        v.z = w.z + separation(v._, w._);
+      }
+      v.parent.A = apportion(v, w, v.parent.A || siblings[0]);
+    }
+    function secondWalk(v) {
+      v._.x = v.z + v.parent.m;
+      v.m += v.parent.m;
+    }
+    function apportion(v, w, ancestor) {
+      if (w) {
+        var vip = v, vop = v, vim = w, vom = vip.parent.children[0], sip = vip.m, sop = vop.m, sim = vim.m, som = vom.m, shift;
+        while (vim = d3_layout_treeRight(vim), vip = d3_layout_treeLeft(vip), vim && vip) {
+          vom = d3_layout_treeLeft(vom);
+          vop = d3_layout_treeRight(vop);
+          vop.a = v;
+          shift = vim.z + sim - vip.z - sip + separation(vim._, vip._);
+          if (shift > 0) {
+            d3_layout_treeMove(d3_layout_treeAncestor(vim, v, ancestor), v, shift);
+            sip += shift;
+            sop += shift;
+          }
+          sim += vim.m;
+          sip += vip.m;
+          som += vom.m;
+          sop += vop.m;
+        }
+        if (vim && !d3_layout_treeRight(vop)) {
+          vop.t = vim;
+          vop.m += sim - sop;
+        }
+        if (vip && !d3_layout_treeLeft(vom)) {
+          vom.t = vip;
+          vom.m += sip - som;
+          ancestor = v;
+        }
+      }
+      return ancestor;
+    }
+    function sizeNode(node) {
+      node.x *= size[0];
+      node.y = node.depth * size[1];
+    }
+    tree.separation = function(x) {
+      if (!arguments.length) return separation;
+      separation = x;
+      return tree;
+    };
+    tree.size = function(x) {
+      if (!arguments.length) return nodeSize ? null : size;
+      nodeSize = (size = x) == null ? sizeNode : null;
+      return tree;
+    };
+    tree.nodeSize = function(x) {
+      if (!arguments.length) return nodeSize ? size : null;
+      nodeSize = (size = x) == null ? null : sizeNode;
+      return tree;
+    };
+    return d3_layout_hierarchyRebind(tree, hierarchy);
+  };
+  function d3_layout_treeSeparation(a, b) {
+    return a.parent == b.parent ? 1 : 2;
+  }
+  function d3_layout_treeLeft(v) {
+    var children = v.children;
+    return children.length ? children[0] : v.t;
+  }
+  function d3_layout_treeRight(v) {
+    var children = v.children, n;
+    return (n = children.length) ? children[n - 1] : v.t;
+  }
+  function d3_layout_treeMove(wm, wp, shift) {
+    var change = shift / (wp.i - wm.i);
+    wp.c -= change;
+    wp.s += shift;
+    wm.c += change;
+    wp.z += shift;
+    wp.m += shift;
+  }
+  function d3_layout_treeShift(v) {
+    var shift = 0, change = 0, children = v.children, i = children.length, w;
+    while (--i >= 0) {
+      w = children[i];
+      w.z += shift;
+      w.m += shift;
+      shift += w.s + (change += w.c);
+    }
+  }
+  function d3_layout_treeAncestor(vim, v, ancestor) {
+    return vim.a.parent === v.parent ? vim.a : ancestor;
+  }
+  d3.layout.cluster = function() {
+    var hierarchy = d3.layout.hierarchy().sort(null).value(null), separation = d3_layout_treeSeparation, size = [ 1, 1 ], nodeSize = false;
+    function cluster(d, i) {
+      var nodes = hierarchy.call(this, d, i), root = nodes[0], previousNode, x = 0;
+      d3_layout_hierarchyVisitAfter(root, function(node) {
+        var children = node.children;
+        if (children && children.length) {
+          node.x = d3_layout_clusterX(children);
+          node.y = d3_layout_clusterY(children);
+        } else {
+          node.x = previousNode ? x += separation(node, previousNode) : 0;
+          node.y = 0;
+          previousNode = node;
+        }
+      });
+      var left = d3_layout_clusterLeft(root), right = d3_layout_clusterRight(root), x0 = left.x - separation(left, right) / 2, x1 = right.x + separation(right, left) / 2;
+      d3_layout_hierarchyVisitAfter(root, nodeSize ? function(node) {
+        node.x = (node.x - root.x) * size[0];
+        node.y = (root.y - node.y) * size[1];
+      } : function(node) {
+        node.x = (node.x - x0) / (x1 - x0) * size[0];
+        node.y = (1 - (root.y ? node.y / root.y : 1)) * size[1];
+      });
+      return nodes;
+    }
+    cluster.separation = function(x) {
+      if (!arguments.length) return separation;
+      separation = x;
+      return cluster;
+    };
+    cluster.size = function(x) {
+      if (!arguments.length) return nodeSize ? null : size;
+      nodeSize = (size = x) == null;
+      return cluster;
+    };
+    cluster.nodeSize = function(x) {
+      if (!arguments.length) return nodeSize ? size : null;
+      nodeSize = (size = x) != null;
+      return cluster;
+    };
+    return d3_layout_hierarchyRebind(cluster, hierarchy);
+  };
+  function d3_layout_clusterY(children) {
+    return 1 + d3.max(children, function(child) {
+      return child.y;
+    });
+  }
+  function d3_layout_clusterX(children) {
+    return children.reduce(function(x, child) {
+      return x + child.x;
+    }, 0) / children.length;
+  }
+  function d3_layout_clusterLeft(node) {
+    var children = node.children;
+    return children && children.length ? d3_layout_clusterLeft(children[0]) : node;
+  }
+  function d3_layout_clusterRight(node) {
+    var children = node.children, n;
+    return children && (n = children.length) ? d3_layout_clusterRight(children[n - 1]) : node;
+  }
+  d3.layout.treemap = function() {
+    var hierarchy = d3.layout.hierarchy(), round = Math.round, size = [ 1, 1 ], padding = null, pad = d3_layout_treemapPadNull, sticky = false, stickies, mode = "squarify", ratio = .5 * (1 + Math.sqrt(5));
+    function scale(children, k) {
+      var i = -1, n = children.length, child, area;
+      while (++i < n) {
+        area = (child = children[i]).value * (k < 0 ? 0 : k);
+        child.area = isNaN(area) || area <= 0 ? 0 : area;
+      }
+    }
+    function squarify(node) {
+      var children = node.children;
+      if (children && children.length) {
+        var rect = pad(node), row = [], remaining = children.slice(), child, best = Infinity, score, u = mode === "slice" ? rect.dx : mode === "dice" ? rect.dy : mode === "slice-dice" ? node.depth & 1 ? rect.dy : rect.dx : Math.min(rect.dx, rect.dy), n;
+        scale(remaining, rect.dx * rect.dy / node.value);
+        row.area = 0;
+        while ((n = remaining.length) > 0) {
+          row.push(child = remaining[n - 1]);
+          row.area += child.area;
+          if (mode !== "squarify" || (score = worst(row, u)) <= best) {
+            remaining.pop();
+            best = score;
+          } else {
+            row.area -= row.pop().area;
+            position(row, u, rect, false);
+            u = Math.min(rect.dx, rect.dy);
+            row.length = row.area = 0;
+            best = Infinity;
+          }
+        }
+        if (row.length) {
+          position(row, u, rect, true);
+          row.length = row.area = 0;
+        }
+        children.forEach(squarify);
+      }
+    }
+    function stickify(node) {
+      var children = node.children;
+      if (children && children.length) {
+        var rect = pad(node), remaining = children.slice(), child, row = [];
+        scale(remaining, rect.dx * rect.dy / node.value);
+        row.area = 0;
+        while (child = remaining.pop()) {
+          row.push(child);
+          row.area += child.area;
+          if (child.z != null) {
+            position(row, child.z ? rect.dx : rect.dy, rect, !remaining.length);
+            row.length = row.area = 0;
+          }
+        }
+        children.forEach(stickify);
+      }
+    }
+    function worst(row, u) {
+      var s = row.area, r, rmax = 0, rmin = Infinity, i = -1, n = row.length;
+      while (++i < n) {
+        if (!(r = row[i].area)) continue;
+        if (r < rmin) rmin = r;
+        if (r > rmax) rmax = r;
+      }
+      s *= s;
+      u *= u;
+      return s ? Math.max(u * rmax * ratio / s, s / (u * rmin * ratio)) : Infinity;
+    }
+    function position(row, u, rect, flush) {
+      var i = -1, n = row.length, x = rect.x, y = rect.y, v = u ? round(row.area / u) : 0, o;
+      if (u == rect.dx) {
+        if (flush || v > rect.dy) v = rect.dy;
+        while (++i < n) {
+          o = row[i];
+          o.x = x;
+          o.y = y;
+          o.dy = v;
+          x += o.dx = Math.min(rect.x + rect.dx - x, v ? round(o.area / v) : 0);
+        }
+        o.z = true;
+        o.dx += rect.x + rect.dx - x;
+        rect.y += v;
+        rect.dy -= v;
+      } else {
+        if (flush || v > rect.dx) v = rect.dx;
+        while (++i < n) {
+          o = row[i];
+          o.x = x;
+          o.y = y;
+          o.dx = v;
+          y += o.dy = Math.min(rect.y + rect.dy - y, v ? round(o.area / v) : 0);
+        }
+        o.z = false;
+        o.dy += rect.y + rect.dy - y;
+        rect.x += v;
+        rect.dx -= v;
+      }
+    }
+    function treemap(d) {
+      var nodes = stickies || hierarchy(d), root = nodes[0];
+      root.x = root.y = 0;
+      if (root.value) root.dx = size[0], root.dy = size[1]; else root.dx = root.dy = 0;
+      if (stickies) hierarchy.revalue(root);
+      scale([ root ], root.dx * root.dy / root.value);
+      (stickies ? stickify : squarify)(root);
+      if (sticky) stickies = nodes;
+      return nodes;
+    }
+    treemap.size = function(x) {
+      if (!arguments.length) return size;
+      size = x;
+      return treemap;
+    };
+    treemap.padding = function(x) {
+      if (!arguments.length) return padding;
+      function padFunction(node) {
+        var p = x.call(treemap, node, node.depth);
+        return p == null ? d3_layout_treemapPadNull(node) : d3_layout_treemapPad(node, typeof p === "number" ? [ p, p, p, p ] : p);
+      }
+      function padConstant(node) {
+        return d3_layout_treemapPad(node, x);
+      }
+      var type;
+      pad = (padding = x) == null ? d3_layout_treemapPadNull : (type = typeof x) === "function" ? padFunction : type === "number" ? (x = [ x, x, x, x ], 
+      padConstant) : padConstant;
+      return treemap;
+    };
+    treemap.round = function(x) {
+      if (!arguments.length) return round != Number;
+      round = x ? Math.round : Number;
+      return treemap;
+    };
+    treemap.sticky = function(x) {
+      if (!arguments.length) return sticky;
+      sticky = x;
+      stickies = null;
+      return treemap;
+    };
+    treemap.ratio = function(x) {
+      if (!arguments.length) return ratio;
+      ratio = x;
+      return treemap;
+    };
+    treemap.mode = function(x) {
+      if (!arguments.length) return mode;
+      mode = x + "";
+      return treemap;
+    };
+    return d3_layout_hierarchyRebind(treemap, hierarchy);
+  };
+  function d3_layout_treemapPadNull(node) {
+    return {
+      x: node.x,
+      y: node.y,
+      dx: node.dx,
+      dy: node.dy
+    };
+  }
+  function d3_layout_treemapPad(node, padding) {
+    var x = node.x + padding[3], y = node.y + padding[0], dx = node.dx - padding[1] - padding[3], dy = node.dy - padding[0] - padding[2];
+    if (dx < 0) {
+      x += dx / 2;
+      dx = 0;
+    }
+    if (dy < 0) {
+      y += dy / 2;
+      dy = 0;
+    }
+    return {
+      x: x,
+      y: y,
+      dx: dx,
+      dy: dy
+    };
+  }
+  d3.random = {
+    normal: function(µ, σ) {
+      var n = arguments.length;
+      if (n < 2) σ = 1;
+      if (n < 1) µ = 0;
+      return function() {
+        var x, y, r;
+        do {
+          x = Math.random() * 2 - 1;
+          y = Math.random() * 2 - 1;
+          r = x * x + y * y;
+        } while (!r || r > 1);
+        return µ + σ * x * Math.sqrt(-2 * Math.log(r) / r);
+      };
+    },
+    logNormal: function() {
+      var random = d3.random.normal.apply(d3, arguments);
+      return function() {
+        return Math.exp(random());
+      };
+    },
+    bates: function(m) {
+      var random = d3.random.irwinHall(m);
+      return function() {
+        return random() / m;
+      };
+    },
+    irwinHall: function(m) {
+      return function() {
+        for (var s = 0, j = 0; j < m; j++) s += Math.random();
+        return s;
+      };
+    }
+  };
+  d3.scale = {};
+  function d3_scaleExtent(domain) {
+    var start = domain[0], stop = domain[domain.length - 1];
+    return start < stop ? [ start, stop ] : [ stop, start ];
+  }
+  function d3_scaleRange(scale) {
+    return scale.rangeExtent ? scale.rangeExtent() : d3_scaleExtent(scale.range());
+  }
+  function d3_scale_bilinear(domain, range, uninterpolate, interpolate) {
+    var u = uninterpolate(domain[0], domain[1]), i = interpolate(range[0], range[1]);
+    return function(x) {
+      return i(u(x));
+    };
+  }
+  function d3_scale_nice(domain, nice) {
+    var i0 = 0, i1 = domain.length - 1, x0 = domain[i0], x1 = domain[i1], dx;
+    if (x1 < x0) {
+      dx = i0, i0 = i1, i1 = dx;
+      dx = x0, x0 = x1, x1 = dx;
+    }
+    domain[i0] = nice.floor(x0);
+    domain[i1] = nice.ceil(x1);
+    return domain;
+  }
+  function d3_scale_niceStep(step) {
+    return step ? {
+      floor: function(x) {
+        return Math.floor(x / step) * step;
+      },
+      ceil: function(x) {
+        return Math.ceil(x / step) * step;
+      }
+    } : d3_scale_niceIdentity;
+  }
+  var d3_scale_niceIdentity = {
+    floor: d3_identity,
+    ceil: d3_identity
+  };
+  function d3_scale_polylinear(domain, range, uninterpolate, interpolate) {
+    var u = [], i = [], j = 0, k = Math.min(domain.length, range.length) - 1;
+    if (domain[k] < domain[0]) {
+      domain = domain.slice().reverse();
+      range = range.slice().reverse();
+    }
+    while (++j <= k) {
+      u.push(uninterpolate(domain[j - 1], domain[j]));
+      i.push(interpolate(range[j - 1], range[j]));
+    }
+    return function(x) {
+      var j = d3.bisect(domain, x, 1, k) - 1;
+      return i[j](u[j](x));
+    };
+  }
+  d3.scale.linear = function() {
+    return d3_scale_linear([ 0, 1 ], [ 0, 1 ], d3_interpolate, false);
+  };
+  function d3_scale_linear(domain, range, interpolate, clamp) {
+    var output, input;
+    function rescale() {
+      var linear = Math.min(domain.length, range.length) > 2 ? d3_scale_polylinear : d3_scale_bilinear, uninterpolate = clamp ? d3_uninterpolateClamp : d3_uninterpolateNumber;
+      output = linear(domain, range, uninterpolate, interpolate);
+      input = linear(range, domain, uninterpolate, d3_interpolate);
+      return scale;
+    }
+    function scale(x) {
+      return output(x);
+    }
+    scale.invert = function(y) {
+      return input(y);
+    };
+    scale.domain = function(x) {
+      if (!arguments.length) return domain;
+      domain = x.map(Number);
+      return rescale();
+    };
+    scale.range = function(x) {
+      if (!arguments.length) return range;
+      range = x;
+      return rescale();
+    };
+    scale.rangeRound = function(x) {
+      return scale.range(x).interpolate(d3_interpolateRound);
+    };
+    scale.clamp = function(x) {
+      if (!arguments.length) return clamp;
+      clamp = x;
+      return rescale();
+    };
+    scale.interpolate = function(x) {
+      if (!arguments.length) return interpolate;
+      interpolate = x;
+      return rescale();
+    };
+    scale.ticks = function(m) {
+      return d3_scale_linearTicks(domain, m);
+    };
+    scale.tickFormat = function(m, format) {
+      return d3_scale_linearTickFormat(domain, m, format);
+    };
+    scale.nice = function(m) {
+      d3_scale_linearNice(domain, m);
+      return rescale();
+    };
+    scale.copy = function() {
+      return d3_scale_linear(domain, range, interpolate, clamp);
+    };
+    return rescale();
+  }
+  function d3_scale_linearRebind(scale, linear) {
+    return d3.rebind(scale, linear, "range", "rangeRound", "interpolate", "clamp");
+  }
+  function d3_scale_linearNice(domain, m) {
+    d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2]));
+    d3_scale_nice(domain, d3_scale_niceStep(d3_scale_linearTickRange(domain, m)[2]));
+    return domain;
+  }
+  function d3_scale_linearTickRange(domain, m) {
+    if (m == null) m = 10;
+    var extent = d3_scaleExtent(domain), span = extent[1] - extent[0], step = Math.pow(10, Math.floor(Math.log(span / m) / Math.LN10)), err = m / span * step;
+    if (err <= .15) step *= 10; else if (err <= .35) step *= 5; else if (err <= .75) step *= 2;
+    extent[0] = Math.ceil(extent[0] / step) * step;
+    extent[1] = Math.floor(extent[1] / step) * step + step * .5;
+    extent[2] = step;
+    return extent;
+  }
+  function d3_scale_linearTicks(domain, m) {
+    return d3.range.apply(d3, d3_scale_linearTickRange(domain, m));
+  }
+  function d3_scale_linearTickFormat(domain, m, format) {
+    var range = d3_scale_linearTickRange(domain, m);
+    if (format) {
+      var match = d3_format_re.exec(format);
+      match.shift();
+      if (match[8] === "s") {
+        var prefix = d3.formatPrefix(Math.max(abs(range[0]), abs(range[1])));
+        if (!match[7]) match[7] = "." + d3_scale_linearPrecision(prefix.scale(range[2]));
+        match[8] = "f";
+        format = d3.format(match.join(""));
+        return function(d) {
+          return format(prefix.scale(d)) + prefix.symbol;
+        };
+      }
+      if (!match[7]) match[7] = "." + d3_scale_linearFormatPrecision(match[8], range);
+      format = match.join("");
+    } else {
+      format = ",." + d3_scale_linearPrecision(range[2]) + "f";
+    }
+    return d3.format(format);
+  }
+  var d3_scale_linearFormatSignificant = {
+    s: 1,
+    g: 1,
+    p: 1,
+    r: 1,
+    e: 1
+  };
+  function d3_scale_linearPrecision(value) {
+    return -Math.floor(Math.log(value) / Math.LN10 + .01);
+  }
+  function d3_scale_linearFormatPrecision(type, range) {
+    var p = d3_scale_linearPrecision(range[2]);
+    return type in d3_scale_linearFormatSignificant ? Math.abs(p - d3_scale_linearPrecision(Math.max(abs(range[0]), abs(range[1])))) + +(type !== "e") : p - (type === "%") * 2;
+  }
+  d3.scale.log = function() {
+    return d3_scale_log(d3.scale.linear().domain([ 0, 1 ]), 10, true, [ 1, 10 ]);
+  };
+  function d3_scale_log(linear, base, positive, domain) {
+    function log(x) {
+      return (positive ? Math.log(x < 0 ? 0 : x) : -Math.log(x > 0 ? 0 : -x)) / Math.log(base);
+    }
+    function pow(x) {
+      return positive ? Math.pow(base, x) : -Math.pow(base, -x);
+    }
+    function scale(x) {
+      return linear(log(x));
+    }
+    scale.invert = function(x) {
+      return pow(linear.invert(x));
+    };
+    scale.domain = function(x) {
+      if (!arguments.length) return domain;
+      positive = x[0] >= 0;
+      linear.domain((domain = x.map(Number)).map(log));
+      return scale;
+    };
+    scale.base = function(_) {
+      if (!arguments.length) return base;
+      base = +_;
+      linear.domain(domain.map(log));
+      return scale;
+    };
+    scale.nice = function() {
+      var niced = d3_scale_nice(domain.map(log), positive ? Math : d3_scale_logNiceNegative);
+      linear.domain(niced);
+      domain = niced.map(pow);
+      return scale;
+    };
+    scale.ticks = function() {
+      var extent = d3_scaleExtent(domain), ticks = [], u = extent[0], v = extent[1], i = Math.floor(log(u)), j = Math.ceil(log(v)), n = base % 1 ? 2 : base;
+      if (isFinite(j - i)) {
+        if (positive) {
+          for (;i < j; i++) for (var k = 1; k < n; k++) ticks.push(pow(i) * k);
+          ticks.push(pow(i));
+        } else {
+          ticks.push(pow(i));
+          for (;i++ < j; ) for (var k = n - 1; k > 0; k--) ticks.push(pow(i) * k);
+        }
+        for (i = 0; ticks[i] < u; i++) {}
+        for (j = ticks.length; ticks[j - 1] > v; j--) {}
+        ticks = ticks.slice(i, j);
+      }
+      return ticks;
+    };
+    scale.tickFormat = function(n, format) {
+      if (!arguments.length) return d3_scale_logFormat;
+      if (arguments.length < 2) format = d3_scale_logFormat; else if (typeof format !== "function") format = d3.format(format);
+      var k = Math.max(1, base * n / scale.ticks().length);
+      return function(d) {
+        var i = d / pow(Math.round(log(d)));
+        if (i * base < base - .5) i *= base;
+        return i <= k ? format(d) : "";
+      };
+    };
+    scale.copy = function() {
+      return d3_scale_log(linear.copy(), base, positive, domain);
+    };
+    return d3_scale_linearRebind(scale, linear);
+  }
+  var d3_scale_logFormat = d3.format(".0e"), d3_scale_logNiceNegative = {
+    floor: function(x) {
+      return -Math.ceil(-x);
+    },
+    ceil: function(x) {
+      return -Math.floor(-x);
+    }
+  };
+  d3.scale.pow = function() {
+    return d3_scale_pow(d3.scale.linear(), 1, [ 0, 1 ]);
+  };
+  function d3_scale_pow(linear, exponent, domain) {
+    var powp = d3_scale_powPow(exponent), powb = d3_scale_powPow(1 / exponent);
+    function scale(x) {
+      return linear(powp(x));
+    }
+    scale.invert = function(x) {
+      return powb(linear.invert(x));
+    };
+    scale.domain = function(x) {
+      if (!arguments.length) return domain;
+      linear.domain((domain = x.map(Number)).map(powp));
+      return scale;
+    };
+    scale.ticks = function(m) {
+      return d3_scale_linearTicks(domain, m);
+    };
+    scale.tickFormat = function(m, format) {
+      return d3_scale_linearTickFormat(domain, m, format);
+    };
+    scale.nice = function(m) {
+      return scale.domain(d3_scale_linearNice(domain, m));
+    };
+    scale.exponent = function(x) {
+      if (!arguments.length) return exponent;
+      powp = d3_scale_powPow(exponent = x);
+      powb = d3_scale_powPow(1 / exponent);
+      linear.domain(domain.map(powp));
+      return scale;
+    };
+    scale.copy = function() {
+      return d3_scale_pow(linear.copy(), exponent, domain);
+    };
+    return d3_scale_linearRebind(scale, linear);
+  }
+  function d3_scale_powPow(e) {
+    return function(x) {
+      return x < 0 ? -Math.pow(-x, e) : Math.pow(x, e);
+    };
+  }
+  d3.scale.sqrt = function() {
+    return d3.scale.pow().exponent(.5);
+  };
+  d3.scale.ordinal = function() {
+    return d3_scale_ordinal([], {
+      t: "range",
+      a: [ [] ]
+    });
+  };
+  function d3_scale_ordinal(domain, ranger) {
+    var index, range, rangeBand;
+    function scale(x) {
+      return range[((index.get(x) || (ranger.t === "range" ? index.set(x, domain.push(x)) : NaN)) - 1) % range.length];
+    }
+    function steps(start, step) {
+      return d3.range(domain.length).map(function(i) {
+        return start + step * i;
+      });
+    }
+    scale.domain = function(x) {
+      if (!arguments.length) return domain;
+      domain = [];
+      index = new d3_Map();
+      var i = -1, n = x.length, xi;
+      while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi));
+      return scale[ranger.t].apply(scale, ranger.a);
+    };
+    scale.range = function(x) {
+      if (!arguments.length) return range;
+      range = x;
+      rangeBand = 0;
+      ranger = {
+        t: "range",
+        a: arguments
+      };
+      return scale;
+    };
+    scale.rangePoints = function(x, padding) {
+      if (arguments.length < 2) padding = 0;
+      var start = x[0], stop = x[1], step = domain.length < 2 ? (start = (start + stop) / 2, 
+      0) : (stop - start) / (domain.length - 1 + padding);
+      range = steps(start + step * padding / 2, step);
+      rangeBand = 0;
+      ranger = {
+        t: "rangePoints",
+        a: arguments
+      };
+      return scale;
+    };
+    scale.rangeRoundPoints = function(x, padding) {
+      if (arguments.length < 2) padding = 0;
+      var start = x[0], stop = x[1], step = domain.length < 2 ? (start = stop = Math.round((start + stop) / 2), 
+      0) : (stop - start) / (domain.length - 1 + padding) | 0;
+      range = steps(start + Math.round(step * padding / 2 + (stop - start - (domain.length - 1 + padding) * step) / 2), step);
+      rangeBand = 0;
+      ranger = {
+        t: "rangeRoundPoints",
+        a: arguments
+      };
+      return scale;
+    };
+    scale.rangeBands = function(x, padding, outerPadding) {
+      if (arguments.length < 2) padding = 0;
+      if (arguments.length < 3) outerPadding = padding;
+      var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = (stop - start) / (domain.length - padding + 2 * outerPadding);
+      range = steps(start + step * outerPadding, step);
+      if (reverse) range.reverse();
+      rangeBand = step * (1 - padding);
+      ranger = {
+        t: "rangeBands",
+        a: arguments
+      };
+      return scale;
+    };
+    scale.rangeRoundBands = function(x, padding, outerPadding) {
+      if (arguments.length < 2) padding = 0;
+      if (arguments.length < 3) outerPadding = padding;
+      var reverse = x[1] < x[0], start = x[reverse - 0], stop = x[1 - reverse], step = Math.floor((stop - start) / (domain.length - padding + 2 * outerPadding));
+      range = steps(start + Math.round((stop - start - (domain.length - padding) * step) / 2), step);
+      if (reverse) range.reverse();
+      rangeBand = Math.round(step * (1 - padding));
+      ranger = {
+        t: "rangeRoundBands",
+        a: arguments
+      };
+      return scale;
+    };
+    scale.rangeBand = function() {
+      return rangeBand;
+    };
+    scale.rangeExtent = function() {
+      return d3_scaleExtent(ranger.a[0]);
+    };
+    scale.copy = function() {
+      return d3_scale_ordinal(domain, ranger);
+    };
+    return scale.domain(domain);
+  }
+  d3.scale.category10 = function() {
+    return d3.scale.ordinal().range(d3_category10);
+  };
+  d3.scale.category20 = function() {
+    return d3.scale.ordinal().range(d3_category20);
+  };
+  d3.scale.category20b = function() {
+    return d3.scale.ordinal().range(d3_category20b);
+  };
+  d3.scale.category20c = function() {
+    return d3.scale.ordinal().range(d3_category20c);
+  };
+  var d3_category10 = [ 2062260, 16744206, 2924588, 14034728, 9725885, 9197131, 14907330, 8355711, 12369186, 1556175 ].map(d3_rgbString);
+  var d3_category20 = [ 2062260, 11454440, 16744206, 16759672, 2924588, 10018698, 14034728, 16750742, 9725885, 12955861, 9197131, 12885140, 14907330, 16234194, 8355711, 13092807, 12369186, 14408589, 1556175, 10410725 ].map(d3_rgbString);
+  var d3_category20b = [ 3750777, 5395619, 7040719, 10264286, 6519097, 9216594, 11915115, 13556636, 9202993, 12426809, 15186514, 15190932, 8666169, 11356490, 14049643, 15177372, 8077683, 10834324, 13528509, 14589654 ].map(d3_rgbString);
+  var d3_category20c = [ 3244733, 7057110, 10406625, 13032431, 15095053, 16616764, 16625259, 16634018, 3253076, 7652470, 10607003, 13101504, 7695281, 10394312, 12369372, 14342891, 6513507, 9868950, 12434877, 14277081 ].map(d3_rgbString);
+  d3.scale.quantile = function() {
+    return d3_scale_quantile([], []);
+  };
+  function d3_scale_quantile(domain, range) {
+    var thresholds;
+    function rescale() {
+      var k = 0, q = range.length;
+      thresholds = [];
+      while (++k < q) thresholds[k - 1] = d3.quantile(domain, k / q);
+      return scale;
+    }
+    function scale(x) {
+      if (!isNaN(x = +x)) return range[d3.bisect(thresholds, x)];
+    }
+    scale.domain = function(x) {
+      if (!arguments.length) return domain;
+      domain = x.map(d3_number).filter(d3_numeric).sort(d3_ascending);
+      return rescale();
+    };
+    scale.range = function(x) {
+      if (!arguments.length) return range;
+      range = x;
+      return rescale();
+    };
+    scale.quantiles = function() {
+      return thresholds;
+    };
+    scale.invertExtent = function(y) {
+      y = range.indexOf(y);
+      return y < 0 ? [ NaN, NaN ] : [ y > 0 ? thresholds[y - 1] : domain[0], y < thresholds.length ? thresholds[y] : domain[domain.length - 1] ];
+    };
+    scale.copy = function() {
+      return d3_scale_quantile(domain, range);
+    };
+    return rescale();
+  }
+  d3.scale.quantize = function() {
+    return d3_scale_quantize(0, 1, [ 0, 1 ]);
+  };
+  function d3_scale_quantize(x0, x1, range) {
+    var kx, i;
+    function scale(x) {
+      return range[Math.max(0, Math.min(i, Math.floor(kx * (x - x0))))];
+    }
+    function rescale() {
+      kx = range.length / (x1 - x0);
+      i = range.length - 1;
+      return scale;
+    }
+    scale.domain = function(x) {
+      if (!arguments.length) return [ x0, x1 ];
+      x0 = +x[0];
+      x1 = +x[x.length - 1];
+      return rescale();
+    };
+    scale.range = function(x) {
+      if (!arguments.length) return range;
+      range = x;
+      return rescale();
+    };
+    scale.invertExtent = function(y) {
+      y = range.indexOf(y);
+      y = y < 0 ? NaN : y / kx + x0;
+      return [ y, y + 1 / kx ];
+    };
+    scale.copy = function() {
+      return d3_scale_quantize(x0, x1, range);
+    };
+    return rescale();
+  }
+  d3.scale.threshold = function() {
+    return d3_scale_threshold([ .5 ], [ 0, 1 ]);
+  };
+  function d3_scale_threshold(domain, range) {
+    function scale(x) {
+      if (x <= x) return range[d3.bisect(domain, x)];
+    }
+    scale.domain = function(_) {
+      if (!arguments.length) return domain;
+      domain = _;
+      return scale;
+    };
+    scale.range = function(_) {
+      if (!arguments.length) return range;
+      range = _;
+      return scale;
+    };
+    scale.invertExtent = function(y) {
+      y = range.indexOf(y);
+      return [ domain[y - 1], domain[y] ];
+    };
+    scale.copy = function() {
+      return d3_scale_threshold(domain, range);
+    };
+    return scale;
+  }
+  d3.scale.identity = function() {
+    return d3_scale_identity([ 0, 1 ]);
+  };
+  function d3_scale_identity(domain) {
+    function identity(x) {
+      return +x;
+    }
+    identity.invert = identity;
+    identity.domain = identity.range = function(x) {
+      if (!arguments.length) return domain;
+      domain = x.map(identity);
+      return identity;
+    };
+    identity.ticks = function(m) {
+      return d3_scale_linearTicks(domain, m);
+    };
+    identity.tickFormat = function(m, format) {
+      return d3_scale_linearTickFormat(domain, m, format);
+    };
+    identity.copy = function() {
+      return d3_scale_identity(domain);
+    };
+    return identity;
+  }
+  d3.svg = {};
+  function d3_zero() {
+    return 0;
+  }
+  d3.svg.arc = function() {
+    var innerRadius = d3_svg_arcInnerRadius, outerRadius = d3_svg_arcOuterRadius, cornerRadius = d3_zero, padRadius = d3_svg_arcAuto, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle, padAngle = d3_svg_arcPadAngle;
+    function arc() {
+      var r0 = Math.max(0, +innerRadius.apply(this, arguments)), r1 = Math.max(0, +outerRadius.apply(this, arguments)), a0 = startAngle.apply(this, arguments) - halfπ, a1 = endAngle.apply(this, arguments) - halfπ, da = Math.abs(a1 - a0), cw = a0 > a1 ? 0 : 1;
+      if (r1 < r0) rc = r1, r1 = r0, r0 = rc;
+      if (da >= τε) return circleSegment(r1, cw) + (r0 ? circleSegment(r0, 1 - cw) : "") + "Z";
+      var rc, cr, rp, ap, p0 = 0, p1 = 0, x0, y0, x1, y1, x2, y2, x3, y3, path = [];
+      if (ap = (+padAngle.apply(this, arguments) || 0) / 2) {
+        rp = padRadius === d3_svg_arcAuto ? Math.sqrt(r0 * r0 + r1 * r1) : +padRadius.apply(this, arguments);
+        if (!cw) p1 *= -1;
+        if (r1) p1 = d3_asin(rp / r1 * Math.sin(ap));
+        if (r0) p0 = d3_asin(rp / r0 * Math.sin(ap));
+      }
+      if (r1) {
+        x0 = r1 * Math.cos(a0 + p1);
+        y0 = r1 * Math.sin(a0 + p1);
+        x1 = r1 * Math.cos(a1 - p1);
+        y1 = r1 * Math.sin(a1 - p1);
+        var l1 = Math.abs(a1 - a0 - 2 * p1) <= π ? 0 : 1;
+        if (p1 && d3_svg_arcSweep(x0, y0, x1, y1) === cw ^ l1) {
+          var h1 = (a0 + a1) / 2;
+          x0 = r1 * Math.cos(h1);
+          y0 = r1 * Math.sin(h1);
+          x1 = y1 = null;
+        }
+      } else {
+        x0 = y0 = 0;
+      }
+      if (r0) {
+        x2 = r0 * Math.cos(a1 - p0);
+        y2 = r0 * Math.sin(a1 - p0);
+        x3 = r0 * Math.cos(a0 + p0);
+        y3 = r0 * Math.sin(a0 + p0);
+        var l0 = Math.abs(a0 - a1 + 2 * p0) <= π ? 0 : 1;
+        if (p0 && d3_svg_arcSweep(x2, y2, x3, y3) === 1 - cw ^ l0) {
+          var h0 = (a0 + a1) / 2;
+          x2 = r0 * Math.cos(h0);
+          y2 = r0 * Math.sin(h0);
+          x3 = y3 = null;
+        }
+      } else {
+        x2 = y2 = 0;
+      }
+      if (da > ε && (rc = Math.min(Math.abs(r1 - r0) / 2, +cornerRadius.apply(this, arguments))) > .001) {
+        cr = r0 < r1 ^ cw ? 0 : 1;
+        var rc1 = rc, rc0 = rc;
+        if (da < π) {
+          var oc = x3 == null ? [ x2, y2 ] : x1 == null ? [ x0, y0 ] : d3_geom_polygonIntersect([ x0, y0 ], [ x3, y3 ], [ x1, y1 ], [ x2, y2 ]), ax = x0 - oc[0], ay = y0 - oc[1], bx = x1 - oc[0], by = y1 - oc[1], kc = 1 / Math.sin(Math.acos((ax * bx + ay * by) / (Math.sqrt(ax * ax + ay * ay) * Math.sqrt(bx * bx + by * by))) / 2), lc = Math.sqrt(oc[0] * oc[0] + oc[1] * oc[1]);
+          rc0 = Math.min(rc, (r0 - lc) / (kc - 1));
+          rc1 = Math.min(rc, (r1 - lc) / (kc + 1));
+        }
+        if (x1 != null) {
+          var t30 = d3_svg_arcCornerTangents(x3 == null ? [ x2, y2 ] : [ x3, y3 ], [ x0, y0 ], r1, rc1, cw), t12 = d3_svg_arcCornerTangents([ x1, y1 ], [ x2, y2 ], r1, rc1, cw);
+          if (rc === rc1) {
+            path.push("M", t30[0], "A", rc1, ",", rc1, " 0 0,", cr, " ", t30[1], "A", r1, ",", r1, " 0 ", 1 - cw ^ d3_svg_arcSweep(t30[1][0], t30[1][1], t12[1][0], t12[1][1]), ",", cw, " ", t12[1], "A", rc1, ",", rc1, " 0 0,", cr, " ", t12[0]);
+          } else {
+            path.push("M", t30[0], "A", rc1, ",", rc1, " 0 1,", cr, " ", t12[0]);
+          }
+        } else {
+          path.push("M", x0, ",", y0);
+        }
+        if (x3 != null) {
+          var t03 = d3_svg_arcCornerTangents([ x0, y0 ], [ x3, y3 ], r0, -rc0, cw), t21 = d3_svg_arcCornerTangents([ x2, y2 ], x1 == null ? [ x0, y0 ] : [ x1, y1 ], r0, -rc0, cw);
+          if (rc === rc0) {
+            path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t21[1], "A", r0, ",", r0, " 0 ", cw ^ d3_svg_arcSweep(t21[1][0], t21[1][1], t03[1][0], t03[1][1]), ",", 1 - cw, " ", t03[1], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]);
+          } else {
+            path.push("L", t21[0], "A", rc0, ",", rc0, " 0 0,", cr, " ", t03[0]);
+          }
+        } else {
+          path.push("L", x2, ",", y2);
+        }
+      } else {
+        path.push("M", x0, ",", y0);
+        if (x1 != null) path.push("A", r1, ",", r1, " 0 ", l1, ",", cw, " ", x1, ",", y1);
+        path.push("L", x2, ",", y2);
+        if (x3 != null) path.push("A", r0, ",", r0, " 0 ", l0, ",", 1 - cw, " ", x3, ",", y3);
+      }
+      path.push("Z");
+      return path.join("");
+    }
+    function circleSegment(r1, cw) {
+      return "M0," + r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + -r1 + "A" + r1 + "," + r1 + " 0 1," + cw + " 0," + r1;
+    }
+    arc.innerRadius = function(v) {
+      if (!arguments.length) return innerRadius;
+      innerRadius = d3_functor(v);
+      return arc;
+    };
+    arc.outerRadius = function(v) {
+      if (!arguments.length) return outerRadius;
+      outerRadius = d3_functor(v);
+      return arc;
+    };
+    arc.cornerRadius = function(v) {
+      if (!arguments.length) return cornerRadius;
+      cornerRadius = d3_functor(v);
+      return arc;
+    };
+    arc.padRadius = function(v) {
+      if (!arguments.length) return padRadius;
+      padRadius = v == d3_svg_arcAuto ? d3_svg_arcAuto : d3_functor(v);
+      return arc;
+    };
+    arc.startAngle = function(v) {
+      if (!arguments.length) return startAngle;
+      startAngle = d3_functor(v);
+      return arc;
+    };
+    arc.endAngle = function(v) {
+      if (!arguments.length) return endAngle;
+      endAngle = d3_functor(v);
+      return arc;
+    };
+    arc.padAngle = function(v) {
+      if (!arguments.length) return padAngle;
+      padAngle = d3_functor(v);
+      return arc;
+    };
+    arc.centroid = function() {
+      var r = (+innerRadius.apply(this, arguments) + +outerRadius.apply(this, arguments)) / 2, a = (+startAngle.apply(this, arguments) + +endAngle.apply(this, arguments)) / 2 - halfπ;
+      return [ Math.cos(a) * r, Math.sin(a) * r ];
+    };
+    return arc;
+  };
+  var d3_svg_arcAuto = "auto";
+  function d3_svg_arcInnerRadius(d) {
+    return d.innerRadius;
+  }
+  function d3_svg_arcOuterRadius(d) {
+    return d.outerRadius;
+  }
+  function d3_svg_arcStartAngle(d) {
+    return d.startAngle;
+  }
+  function d3_svg_arcEndAngle(d) {
+    return d.endAngle;
+  }
+  function d3_svg_arcPadAngle(d) {
+    return d && d.padAngle;
+  }
+  function d3_svg_arcSweep(x0, y0, x1, y1) {
+    return (x0 - x1) * y0 - (y0 - y1) * x0 > 0 ? 0 : 1;
+  }
+  function d3_svg_arcCornerTangents(p0, p1, r1, rc, cw) {
+    var x01 = p0[0] - p1[0], y01 = p0[1] - p1[1], lo = (cw ? rc : -rc) / Math.sqrt(x01 * x01 + y01 * y01), ox = lo * y01, oy = -lo * x01, x1 = p0[0] + ox, y1 = p0[1] + oy, x2 = p1[0] + ox, y2 = p1[1] + oy, x3 = (x1 + x2) / 2, y3 = (y1 + y2) / 2, dx = x2 - x1, dy = y2 - y1, d2 = dx * dx + dy * dy, r = r1 - rc, D = x1 * y2 - x2 * y1, d = (dy < 0 ? -1 : 1) * Math.sqrt(Math.max(0, r * r * d2 - D * D)), cx0 = (D * dy - dx * d) / d2, cy0 = (-D * dx - dy * d) / d2, cx1 = (D * dy + dx * d) / d2, cy1 = (-D * dx + dy * d) / d2, dx0 = cx0 - x3, dy0 = cy0 - y3, dx1 = cx1 - x3, dy1 = cy1 - y3;
+    if (dx0 * dx0 + dy0 * dy0 > dx1 * dx1 + dy1 * dy1) cx0 = cx1, cy0 = cy1;
+    return [ [ cx0 - ox, cy0 - oy ], [ cx0 * r1 / r, cy0 * r1 / r ] ];
+  }
+  function d3_svg_line(projection) {
+    var x = d3_geom_pointX, y = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = .7;
+    function line(data) {
+      var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y);
+      function segment() {
+        segments.push("M", interpolate(projection(points), tension));
+      }
+      while (++i < n) {
+        if (defined.call(this, d = data[i], i)) {
+          points.push([ +fx.call(this, d, i), +fy.call(this, d, i) ]);
+        } else if (points.length) {
+          segment();
+          points = [];
+        }
+      }
+      if (points.length) segment();
+      return segments.length ? segments.join("") : null;
+    }
+    line.x = function(_) {
+      if (!arguments.length) return x;
+      x = _;
+      return line;
+    };
+    line.y = function(_) {
+      if (!arguments.length) return y;
+      y = _;
+      return line;
+    };
+    line.defined = function(_) {
+      if (!arguments.length) return defined;
+      defined = _;
+      return line;
+    };
+    line.interpolate = function(_) {
+      if (!arguments.length) return interpolateKey;
+      if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key;
+      return line;
+    };
+    line.tension = function(_) {
+      if (!arguments.length) return tension;
+      tension = _;
+      return line;
+    };
+    return line;
+  }
+  d3.svg.line = function() {
+    return d3_svg_line(d3_identity);
+  };
+  var d3_svg_lineInterpolators = d3.map({
+    linear: d3_svg_lineLinear,
+    "linear-closed": d3_svg_lineLinearClosed,
+    step: d3_svg_lineStep,
+    "step-before": d3_svg_lineStepBefore,
+    "step-after": d3_svg_lineStepAfter,
+    basis: d3_svg_lineBasis,
+    "basis-open": d3_svg_lineBasisOpen,
+    "basis-closed": d3_svg_lineBasisClosed,
+    bundle: d3_svg_lineBundle,
+    cardinal: d3_svg_lineCardinal,
+    "cardinal-open": d3_svg_lineCardinalOpen,
+    "cardinal-closed": d3_svg_lineCardinalClosed,
+    monotone: d3_svg_lineMonotone
+  });
+  d3_svg_lineInterpolators.forEach(function(key, value) {
+    value.key = key;
+    value.closed = /-closed$/.test(key);
+  });
+  function d3_svg_lineLinear(points) {
+    return points.length > 1 ? points.join("L") : points + "Z";
+  }
+  function d3_svg_lineLinearClosed(points) {
+    return points.join("L") + "Z";
+  }
+  function d3_svg_lineStep(points) {
+    var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
+    while (++i < n) path.push("H", (p[0] + (p = points[i])[0]) / 2, "V", p[1]);
+    if (n > 1) path.push("H", p[0]);
+    return path.join("");
+  }
+  function d3_svg_lineStepBefore(points) {
+    var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
+    while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]);
+    return path.join("");
+  }
+  function d3_svg_lineStepAfter(points) {
+    var i = 0, n = points.length, p = points[0], path = [ p[0], ",", p[1] ];
+    while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]);
+    return path.join("");
+  }
+  function d3_svg_lineCardinalOpen(points, tension) {
+    return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, -1), d3_svg_lineCardinalTangents(points, tension));
+  }
+  function d3_svg_lineCardinalClosed(points, tension) {
+    return points.length < 3 ? d3_svg_lineLinearClosed(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), 
+    points), d3_svg_lineCardinalTangents([ points[points.length - 2] ].concat(points, [ points[1] ]), tension));
+  }
+  function d3_svg_lineCardinal(points, tension) {
+    return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension));
+  }
+  function d3_svg_lineHermite(points, tangents) {
+    if (tangents.length < 1 || points.length != tangents.length && points.length != tangents.length + 2) {
+      return d3_svg_lineLinear(points);
+    }
+    var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1;
+    if (quad) {
+      path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) + "," + p[0] + "," + p[1];
+      p0 = points[1];
+      pi = 2;
+    }
+    if (tangents.length > 1) {
+      t = tangents[1];
+      p = points[pi];
+      pi++;
+      path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1];
+      for (var i = 2; i < tangents.length; i++, pi++) {
+        p = points[pi];
+        t = tangents[i];
+        path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1];
+      }
+    }
+    if (quad) {
+      var lp = points[pi];
+      path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1];
+    }
+    return path;
+  }
+  function d3_svg_lineCardinalTangents(points, tension) {
+    var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length;
+    while (++i < n) {
+      p0 = p1;
+      p1 = p2;
+      p2 = points[i];
+      tangents.push([ a * (p2[0] - p0[0]), a * (p2[1] - p0[1]) ]);
+    }
+    return tangents;
+  }
+  function d3_svg_lineBasis(points) {
+    if (points.length < 3) return d3_svg_lineLinear(points);
+    var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [ x0, x0, x0, (pi = points[1])[0] ], py = [ y0, y0, y0, pi[1] ], path = [ x0, ",", y0, "L", d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ];
+    points.push(points[n - 1]);
+    while (++i <= n) {
+      pi = points[i];
+      px.shift();
+      px.push(pi[0]);
+      py.shift();
+      py.push(pi[1]);
+      d3_svg_lineBasisBezier(path, px, py);
+    }
+    points.pop();
+    path.push("L", pi);
+    return path.join("");
+  }
+  function d3_svg_lineBasisOpen(points) {
+    if (points.length < 4) return d3_svg_lineLinear(points);
+    var path = [], i = -1, n = points.length, pi, px = [ 0 ], py = [ 0 ];
+    while (++i < 3) {
+      pi = points[i];
+      px.push(pi[0]);
+      py.push(pi[1]);
+    }
+    path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py));
+    --i;
+    while (++i < n) {
+      pi = points[i];
+      px.shift();
+      px.push(pi[0]);
+      py.shift();
+      py.push(pi[1]);
+      d3_svg_lineBasisBezier(path, px, py);
+    }
+    return path.join("");
+  }
+  function d3_svg_lineBasisClosed(points) {
+    var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = [];
+    while (++i < 4) {
+      pi = points[i % n];
+      px.push(pi[0]);
+      py.push(pi[1]);
+    }
+    path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ];
+    --i;
+    while (++i < m) {
+      pi = points[i % n];
+      px.shift();
+      px.push(pi[0]);
+      py.shift();
+      py.push(pi[1]);
+      d3_svg_lineBasisBezier(path, px, py);
+    }
+    return path.join("");
+  }
+  function d3_svg_lineBundle(points, tension) {
+    var n = points.length - 1;
+    if (n) {
+      var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t;
+      while (++i <= n) {
+        p = points[i];
+        t = i / n;
+        p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx);
+        p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy);
+      }
+    }
+    return d3_svg_lineBasis(points);
+  }
+  function d3_svg_lineDot4(a, b) {
+    return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3];
+  }
+  var d3_svg_lineBasisBezier1 = [ 0, 2 / 3, 1 / 3, 0 ], d3_svg_lineBasisBezier2 = [ 0, 1 / 3, 2 / 3, 0 ], d3_svg_lineBasisBezier3 = [ 0, 1 / 6, 2 / 3, 1 / 6 ];
+  function d3_svg_lineBasisBezier(path, x, y) {
+    path.push("C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y));
+  }
+  function d3_svg_lineSlope(p0, p1) {
+    return (p1[1] - p0[1]) / (p1[0] - p0[0]);
+  }
+  function d3_svg_lineFiniteDifferences(points) {
+    var i = 0, j = points.length - 1, m = [], p0 = points[0], p1 = points[1], d = m[0] = d3_svg_lineSlope(p0, p1);
+    while (++i < j) {
+      m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2;
+    }
+    m[i] = d;
+    return m;
+  }
+  function d3_svg_lineMonotoneTangents(points) {
+    var tangents = [], d, a, b, s, m = d3_svg_lineFiniteDifferences(points), i = -1, j = points.length - 1;
+    while (++i < j) {
+      d = d3_svg_lineSlope(points[i], points[i + 1]);
+      if (abs(d) < ε) {
+        m[i] = m[i + 1] = 0;
+      } else {
+        a = m[i] / d;
+        b = m[i + 1] / d;
+        s = a * a + b * b;
+        if (s > 9) {
+          s = d * 3 / Math.sqrt(s);
+          m[i] = s * a;
+          m[i + 1] = s * b;
+        }
+      }
+    }
+    i = -1;
+    while (++i <= j) {
+      s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i]));
+      tangents.push([ s || 0, m[i] * s || 0 ]);
+    }
+    return tangents;
+  }
+  function d3_svg_lineMonotone(points) {
+    return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points));
+  }
+  d3.svg.line.radial = function() {
+    var line = d3_svg_line(d3_svg_lineRadial);
+    line.radius = line.x, delete line.x;
+    line.angle = line.y, delete line.y;
+    return line;
+  };
+  function d3_svg_lineRadial(points) {
+    var point, i = -1, n = points.length, r, a;
+    while (++i < n) {
+      point = points[i];
+      r = point[0];
+      a = point[1] - halfπ;
+      point[0] = r * Math.cos(a);
+      point[1] = r * Math.sin(a);
+    }
+    return points;
+  }
+  function d3_svg_area(projection) {
+    var x0 = d3_geom_pointX, x1 = d3_geom_pointX, y0 = 0, y1 = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, interpolateReverse = interpolate, L = "L", tension = .7;
+    function area(data) {
+      var segments = [], points0 = [], points1 = [], i = -1, n = data.length, d, fx0 = d3_functor(x0), fy0 = d3_functor(y0), fx1 = x0 === x1 ? function() {
+        return x;
+      } : d3_functor(x1), fy1 = y0 === y1 ? function() {
+        return y;
+      } : d3_functor(y1), x, y;
+      function segment() {
+        segments.push("M", interpolate(projection(points1), tension), L, interpolateReverse(projection(points0.reverse()), tension), "Z");
+      }
+      while (++i < n) {
+        if (defined.call(this, d = data[i], i)) {
+          points0.push([ x = +fx0.call(this, d, i), y = +fy0.call(this, d, i) ]);
+          points1.push([ +fx1.call(this, d, i), +fy1.call(this, d, i) ]);
+        } else if (points0.length) {
+          segment();
+          points0 = [];
+          points1 = [];
+        }
+      }
+      if (points0.length) segment();
+      return segments.length ? segments.join("") : null;
+    }
+    area.x = function(_) {
+      if (!arguments.length) return x1;
+      x0 = x1 = _;
+      return area;
+    };
+    area.x0 = function(_) {
+      if (!arguments.length) return x0;
+      x0 = _;
+      return area;
+    };
+    area.x1 = function(_) {
+      if (!arguments.length) return x1;
+      x1 = _;
+      return area;
+    };
+    area.y = function(_) {
+      if (!arguments.length) return y1;
+      y0 = y1 = _;
+      return area;
+    };
+    area.y0 = function(_) {
+      if (!arguments.length) return y0;
+      y0 = _;
+      return area;
+    };
+    area.y1 = function(_) {
+      if (!arguments.length) return y1;
+      y1 = _;
+      return area;
+    };
+    area.defined = function(_) {
+      if (!arguments.length) return defined;
+      defined = _;
+      return area;
+    };
+    area.interpolate = function(_) {
+      if (!arguments.length) return interpolateKey;
+      if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key;
+      interpolateReverse = interpolate.reverse || interpolate;
+      L = interpolate.closed ? "M" : "L";
+      return area;
+    };
+    area.tension = function(_) {
+      if (!arguments.length) return tension;
+      tension = _;
+      return area;
+    };
+    return area;
+  }
+  d3_svg_lineStepBefore.reverse = d3_svg_lineStepAfter;
+  d3_svg_lineStepAfter.reverse = d3_svg_lineStepBefore;
+  d3.svg.area = function() {
+    return d3_svg_area(d3_identity);
+  };
+  d3.svg.area.radial = function() {
+    var area = d3_svg_area(d3_svg_lineRadial);
+    area.radius = area.x, delete area.x;
+    area.innerRadius = area.x0, delete area.x0;
+    area.outerRadius = area.x1, delete area.x1;
+    area.angle = area.y, delete area.y;
+    area.startAngle = area.y0, delete area.y0;
+    area.endAngle = area.y1, delete area.y1;
+    return area;
+  };
+  d3.svg.chord = function() {
+    var source = d3_source, target = d3_target, radius = d3_svg_chordRadius, startAngle = d3_svg_arcStartAngle, endAngle = d3_svg_arcEndAngle;
+    function chord(d, i) {
+      var s = subgroup(this, source, d, i), t = subgroup(this, target, d, i);
+      return "M" + s.p0 + arc(s.r, s.p1, s.a1 - s.a0) + (equals(s, t) ? curve(s.r, s.p1, s.r, s.p0) : curve(s.r, s.p1, t.r, t.p0) + arc(t.r, t.p1, t.a1 - t.a0) + curve(t.r, t.p1, s.r, s.p0)) + "Z";
+    }
+    function subgroup(self, f, d, i) {
+      var subgroup = f.call(self, d, i), r = radius.call(self, subgroup, i), a0 = startAngle.call(self, subgroup, i) - halfπ, a1 = endAngle.call(self, subgroup, i) - halfπ;
+      return {
+        r: r,
+        a0: a0,
+        a1: a1,
+        p0: [ r * Math.cos(a0), r * Math.sin(a0) ],
+        p1: [ r * Math.cos(a1), r * Math.sin(a1) ]
+      };
+    }
+    function equals(a, b) {
+      return a.a0 == b.a0 && a.a1 == b.a1;
+    }
+    function arc(r, p, a) {
+      return "A" + r + "," + r + " 0 " + +(a > π) + ",1 " + p;
+    }
+    function curve(r0, p0, r1, p1) {
+      return "Q 0,0 " + p1;
+    }
+    chord.radius = function(v) {
+      if (!arguments.length) return radius;
+      radius = d3_functor(v);
+      return chord;
+    };
+    chord.source = function(v) {
+      if (!arguments.length) return source;
+      source = d3_functor(v);
+      return chord;
+    };
+    chord.target = function(v) {
+      if (!arguments.length) return target;
+      target = d3_functor(v);
+      return chord;
+    };
+    chord.startAngle = function(v) {
+      if (!arguments.length) return startAngle;
+      startAngle = d3_functor(v);
+      return chord;
+    };
+    chord.endAngle = function(v) {
+      if (!arguments.length) return endAngle;
+      endAngle = d3_functor(v);
+      return chord;
+    };
+    return chord;
+  };
+  function d3_svg_chordRadius(d) {
+    return d.radius;
+  }
+  d3.svg.diagonal = function() {
+    var source = d3_source, target = d3_target, projection = d3_svg_diagonalProjection;
+    function diagonal(d, i) {
+      var p0 = source.call(this, d, i), p3 = target.call(this, d, i), m = (p0.y + p3.y) / 2, p = [ p0, {
+        x: p0.x,
+        y: m
+      }, {
+        x: p3.x,
+        y: m
+      }, p3 ];
+      p = p.map(projection);
+      return "M" + p[0] + "C" + p[1] + " " + p[2] + " " + p[3];
+    }
+    diagonal.source = function(x) {
+      if (!arguments.length) return source;
+      source = d3_functor(x);
+      return diagonal;
+    };
+    diagonal.target = function(x) {
+      if (!arguments.length) return target;
+      target = d3_functor(x);
+      return diagonal;
+    };
+    diagonal.projection = function(x) {
+      if (!arguments.length) return projection;
+      projection = x;
+      return diagonal;
+    };
+    return diagonal;
+  };
+  function d3_svg_diagonalProjection(d) {
+    return [ d.x, d.y ];
+  }
+  d3.svg.diagonal.radial = function() {
+    var diagonal = d3.svg.diagonal(), projection = d3_svg_diagonalProjection, projection_ = diagonal.projection;
+    diagonal.projection = function(x) {
+      return arguments.length ? projection_(d3_svg_diagonalRadialProjection(projection = x)) : projection;
+    };
+    return diagonal;
+  };
+  function d3_svg_diagonalRadialProjection(projection) {
+    return function() {
+      var d = projection.apply(this, arguments), r = d[0], a = d[1] - halfπ;
+      return [ r * Math.cos(a), r * Math.sin(a) ];
+    };
+  }
+  d3.svg.symbol = function() {
+    var type = d3_svg_symbolType, size = d3_svg_symbolSize;
+    function symbol(d, i) {
+      return (d3_svg_symbols.get(type.call(this, d, i)) || d3_svg_symbolCircle)(size.call(this, d, i));
+    }
+    symbol.type = function(x) {
+      if (!arguments.length) return type;
+      type = d3_functor(x);
+      return symbol;
+    };
+    symbol.size = function(x) {
+      if (!arguments.length) return size;
+      size = d3_functor(x);
+      return symbol;
+    };
+    return symbol;
+  };
+  function d3_svg_symbolSize() {
+    return 64;
+  }
+  function d3_svg_symbolType() {
+    return "circle";
+  }
+  function d3_svg_symbolCircle(size) {
+    var r = Math.sqrt(size / π);
+    return "M0," + r + "A" + r + "," + r + " 0 1,1 0," + -r + "A" + r + "," + r + " 0 1,1 0," + r + "Z";
+  }
+  var d3_svg_symbols = d3.map({
+    circle: d3_svg_symbolCircle,
+    cross: function(size) {
+      var r = Math.sqrt(size / 5) / 2;
+      return "M" + -3 * r + "," + -r + "H" + -r + "V" + -3 * r + "H" + r + "V" + -r + "H" + 3 * r + "V" + r + "H" + r + "V" + 3 * r + "H" + -r + "V" + r + "H" + -3 * r + "Z";
+    },
+    diamond: function(size) {
+      var ry = Math.sqrt(size / (2 * d3_svg_symbolTan30)), rx = ry * d3_svg_symbolTan30;
+      return "M0," + -ry + "L" + rx + ",0" + " 0," + ry + " " + -rx + ",0" + "Z";
+    },
+    square: function(size) {
+      var r = Math.sqrt(size) / 2;
+      return "M" + -r + "," + -r + "L" + r + "," + -r + " " + r + "," + r + " " + -r + "," + r + "Z";
+    },
+    "triangle-down": function(size) {
+      var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2;
+      return "M0," + ry + "L" + rx + "," + -ry + " " + -rx + "," + -ry + "Z";
+    },
+    "triangle-up": function(size) {
+      var rx = Math.sqrt(size / d3_svg_symbolSqrt3), ry = rx * d3_svg_symbolSqrt3 / 2;
+      return "M0," + -ry + "L" + rx + "," + ry + " " + -rx + "," + ry + "Z";
+    }
+  });
+  d3.svg.symbolTypes = d3_svg_symbols.keys();
+  var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * d3_radians);
+  d3_selectionPrototype.transition = function(name) {
+    var id = d3_transitionInheritId || ++d3_transitionId, ns = d3_transitionNamespace(name), subgroups = [], subgroup, node, transition = d3_transitionInherit || {
+      time: Date.now(),
+      ease: d3_ease_cubicInOut,
+      delay: 0,
+      duration: 250
+    };
+    for (var j = -1, m = this.length; ++j < m; ) {
+      subgroups.push(subgroup = []);
+      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
+        if (node = group[i]) d3_transitionNode(node, i, ns, id, transition);
+        subgroup.push(node);
+      }
+    }
+    return d3_transition(subgroups, ns, id);
+  };
+  d3_selectionPrototype.interrupt = function(name) {
+    return this.each(name == null ? d3_selection_interrupt : d3_selection_interruptNS(d3_transitionNamespace(name)));
+  };
+  var d3_selection_interrupt = d3_selection_interruptNS(d3_transitionNamespace());
+  function d3_selection_interruptNS(ns) {
+    return function() {
+      var lock, activeId, active;
+      if ((lock = this[ns]) && (active = lock[activeId = lock.active])) {
+        active.timer.c = null;
+        active.timer.t = NaN;
+        if (--lock.count) delete lock[activeId]; else delete this[ns];
+        lock.active += .5;
+        active.event && active.event.interrupt.call(this, this.__data__, active.index);
+      }
+    };
+  }
+  function d3_transition(groups, ns, id) {
+    d3_subclass(groups, d3_transitionPrototype);
+    groups.namespace = ns;
+    groups.id = id;
+    return groups;
+  }
+  var d3_transitionPrototype = [], d3_transitionId = 0, d3_transitionInheritId, d3_transitionInherit;
+  d3_transitionPrototype.call = d3_selectionPrototype.call;
+  d3_transitionPrototype.empty = d3_selectionPrototype.empty;
+  d3_transitionPrototype.node = d3_selectionPrototype.node;
+  d3_transitionPrototype.size = d3_selectionPrototype.size;
+  d3.transition = function(selection, name) {
+    return selection && selection.transition ? d3_transitionInheritId ? selection.transition(name) : selection : d3.selection().transition(selection);
+  };
+  d3.transition.prototype = d3_transitionPrototype;
+  d3_transitionPrototype.select = function(selector) {
+    var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnode, node;
+    selector = d3_selection_selector(selector);
+    for (var j = -1, m = this.length; ++j < m; ) {
+      subgroups.push(subgroup = []);
+      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
+        if ((node = group[i]) && (subnode = selector.call(node, node.__data__, i, j))) {
+          if ("__data__" in node) subnode.__data__ = node.__data__;
+          d3_transitionNode(subnode, i, ns, id, node[ns][id]);
+          subgroup.push(subnode);
+        } else {
+          subgroup.push(null);
+        }
+      }
+    }
+    return d3_transition(subgroups, ns, id);
+  };
+  d3_transitionPrototype.selectAll = function(selector) {
+    var id = this.id, ns = this.namespace, subgroups = [], subgroup, subnodes, node, subnode, transition;
+    selector = d3_selection_selectorAll(selector);
+    for (var j = -1, m = this.length; ++j < m; ) {
+      for (var group = this[j], i = -1, n = group.length; ++i < n; ) {
+        if (node = group[i]) {
+          transition = node[ns][id];
+          subnodes = selector.call(node, node.__data__, i, j);
+          subgroups.push(subgroup = []);
+          for (var k = -1, o = subnodes.length; ++k < o; ) {
+            if (subnode = subnodes[k]) d3_transitionNode(subnode, k, ns, id, transition);
+            subgroup.push(subnode);
+          }
+        }
+      }
+    }
+    return d3_transition(subgroups, ns, id);
+  };
+  d3_transitionPrototype.filter = function(filter) {
+    var subgroups = [], subgroup, group, node;
+    if (typeof filter !== "function") filter = d3_selection_filter(filter);
+    for (var j = 0, m = this.length; j < m; j++) {
+      subgroups.push(subgroup = []);
+      for (var group = this[j], i = 0, n = group.length; i < n; i++) {
+        if ((node = group[i]) && filter.call(node, node.__data__, i, j)) {
+          subgroup.push(node);
+        }
+      }
+    }
+    return d3_transition(subgroups, this.namespace, this.id);
+  };
+  d3_transitionPrototype.tween = function(name, tween) {
+    var id = this.id, ns = this.namespace;
+    if (arguments.length < 2) return this.node()[ns][id].tween.get(name);
+    return d3_selection_each(this, tween == null ? function(node) {
+      node[ns][id].tween.remove(name);
+    } : function(node) {
+      node[ns][id].tween.set(name, tween);
+    });
+  };
+  function d3_transition_tween(groups, name, value, tween) {
+    var id = groups.id, ns = groups.namespace;
+    return d3_selection_each(groups, typeof value === "function" ? function(node, i, j) {
+      node[ns][id].tween.set(name, tween(value.call(node, node.__data__, i, j)));
+    } : (value = tween(value), function(node) {
+      node[ns][id].tween.set(name, value);
+    }));
+  }
+  d3_transitionPrototype.attr = function(nameNS, value) {
+    if (arguments.length < 2) {
+      for (value in nameNS) this.attr(value, nameNS[value]);
+      return this;
+    }
+    var interpolate = nameNS == "transform" ? d3_interpolateTransform : d3_interpolate, name = d3.ns.qualify(nameNS);
+    function attrNull() {
+      this.removeAttribute(name);
+    }
+    function attrNullNS() {
+      this.removeAttributeNS(name.space, name.local);
+    }
+    function attrTween(b) {
+      return b == null ? attrNull : (b += "", function() {
+        var a = this.getAttribute(name), i;
+        return a !== b && (i = interpolate(a, b), function(t) {
+          this.setAttribute(name, i(t));
+        });
+      });
+    }
+    function attrTweenNS(b) {
+      return b == null ? attrNullNS : (b += "", function() {
+        var a = this.getAttributeNS(name.space, name.local), i;
+        return a !== b && (i = interpolate(a, b), function(t) {
+          this.setAttributeNS(name.space, name.local, i(t));
+        });
+      });
+    }
+    return d3_transition_tween(this, "attr." + nameNS, value, name.local ? attrTweenNS : attrTween);
+  };
+  d3_transitionPrototype.attrTween = function(nameNS, tween) {
+    var name = d3.ns.qualify(nameNS);
+    function attrTween(d, i) {
+      var f = tween.call(this, d, i, this.getAttribute(name));
+      return f && function(t) {
+        this.setAttribute(name, f(t));
+      };
+    }
+    function attrTweenNS(d, i) {
+      var f = tween.call(this, d, i, this.getAttributeNS(name.space, name.local));
+      return f && function(t) {
+        this.setAttributeNS(name.space, name.local, f(t));
+      };
+    }
+    return this.tween("attr." + nameNS, name.local ? attrTweenNS : attrTween);
+  };
+  d3_transitionPrototype.style = function(name, value, priority) {
+    var n = arguments.length;
+    if (n < 3) {
+      if (typeof name !== "string") {
+        if (n < 2) value = "";
+        for (priority in name) this.style(priority, name[priority], value);
+        return this;
+      }
+      priority = "";
+    }
+    function styleNull() {
+      this.style.removeProperty(name);
+    }
+    function styleString(b) {
+      return b == null ? styleNull : (b += "", function() {
+        var a = d3_window(this).getComputedStyle(this, null).getPropertyValue(name), i;
+        return a !== b && (i = d3_interpolate(a, b), function(t) {
+          this.style.setProperty(name, i(t), priority);
+        });
+      });
+    }
+    return d3_transition_tween(this, "style." + name, value, styleString);
+  };
+  d3_transitionPrototype.styleTween = function(name, tween, priority) {
+    if (arguments.length < 3) priority = "";
+    function styleTween(d, i) {
+      var f = tween.call(this, d, i, d3_window(this).getComputedStyle(this, null).getPropertyValue(name));
+      return f && function(t) {
+        this.style.setProperty(name, f(t), priority);
+      };
+    }
+    return this.tween("style." + name, styleTween);
+  };
+  d3_transitionPrototype.text = function(value) {
+    return d3_transition_tween(this, "text", value, d3_transition_text);
+  };
+  function d3_transition_text(b) {
+    if (b == null) b = "";
+    return function() {
+      this.textContent = b;
+    };
+  }
+  d3_transitionPrototype.remove = function() {
+    var ns = this.namespace;
+    return this.each("end.transition", function() {
+      var p;
+      if (this[ns].count < 2 && (p = this.parentNode)) p.removeChild(this);
+    });
+  };
+  d3_transitionPrototype.ease = function(value) {
+    var id = this.id, ns = this.namespace;
+    if (arguments.length < 1) return this.node()[ns][id].ease;
+    if (typeof value !== "function") value = d3.ease.apply(d3, arguments);
+    return d3_selection_each(this, function(node) {
+      node[ns][id].ease = value;
+    });
+  };
+  d3_transitionPrototype.delay = function(value) {
+    var id = this.id, ns = this.namespace;
+    if (arguments.length < 1) return this.node()[ns][id].delay;
+    return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
+      node[ns][id].delay = +value.call(node, node.__data__, i, j);
+    } : (value = +value, function(node) {
+      node[ns][id].delay = value;
+    }));
+  };
+  d3_transitionPrototype.duration = function(value) {
+    var id = this.id, ns = this.namespace;
+    if (arguments.length < 1) return this.node()[ns][id].duration;
+    return d3_selection_each(this, typeof value === "function" ? function(node, i, j) {
+      node[ns][id].duration = Math.max(1, value.call(node, node.__data__, i, j));
+    } : (value = Math.max(1, value), function(node) {
+      node[ns][id].duration = value;
+    }));
+  };
+  d3_transitionPrototype.each = function(type, listener) {
+    var id = this.id, ns = this.namespace;
+    if (arguments.length < 2) {
+      var inherit = d3_transitionInherit, inheritId = d3_transitionInheritId;
+      try {
+        d3_transitionInheritId = id;
+        d3_selection_each(this, function(node, i, j) {
+          d3_transitionInherit = node[ns][id];
+          type.call(node, node.__data__, i, j);
+        });
+      } finally {
+        d3_transitionInherit = inherit;
+        d3_transitionInheritId = inheritId;
+      }
+    } else {
+      d3_selection_each(this, function(node) {
+        var transition = node[ns][id];
+        (transition.event || (transition.event = d3.dispatch("start", "end", "interrupt"))).on(type, listener);
+      });
+    }
+    return this;
+  };
+  d3_transitionPrototype.transition = function() {
+    var id0 = this.id, id1 = ++d3_transitionId, ns = this.namespace, subgroups = [], subgroup, group, node, transition;
+    for (var j = 0, m = this.length; j < m; j++) {
+      subgroups.push(subgroup = []);
+      for (var group = this[j], i = 0, n = group.length; i < n; i++) {
+        if (node = group[i]) {
+          transition = node[ns][id0];
+          d3_transitionNode(node, i, ns, id1, {
+            time: transition.time,
+            ease: transition.ease,
+            delay: transition.delay + transition.duration,
+            duration: transition.duration
+          });
+        }
+        subgroup.push(node);
+      }
+    }
+    return d3_transition(subgroups, ns, id1);
+  };
+  function d3_transitionNamespace(name) {
+    return name == null ? "__transition__" : "__transition_" + name + "__";
+  }
+  function d3_transitionNode(node, i, ns, id, inherit) {
+    var lock = node[ns] || (node[ns] = {
+      active: 0,
+      count: 0
+    }), transition = lock[id], time, timer, duration, ease, tweens;
+    function schedule(elapsed) {
+      var delay = transition.delay;
+      timer.t = delay + time;
+      if (delay <= elapsed) return start(elapsed - delay);
+      timer.c = start;
+    }
+    function start(elapsed) {
+      var activeId = lock.active, active = lock[activeId];
+      if (active) {
+        active.timer.c = null;
+        active.timer.t = NaN;
+        --lock.count;
+        delete lock[activeId];
+        active.event && active.event.interrupt.call(node, node.__data__, active.index);
+      }
+      for (var cancelId in lock) {
+        if (+cancelId < id) {
+          var cancel = lock[cancelId];
+          cancel.timer.c = null;
+          cancel.timer.t = NaN;
+          --lock.count;
+          delete lock[cancelId];
+        }
+      }
+      timer.c = tick;
+      d3_timer(function() {
+        if (timer.c && tick(elapsed || 1)) {
+          timer.c = null;
+          timer.t = NaN;
+        }
+        return 1;
+      }, 0, time);
+      lock.active = id;
+      transition.event && transition.event.start.call(node, node.__data__, i);
+      tweens = [];
+      transition.tween.forEach(function(key, value) {
+        if (value = value.call(node, node.__data__, i)) {
+          tweens.push(value);
+        }
+      });
+      ease = transition.ease;
+      duration = transition.duration;
+    }
+    function tick(elapsed) {
+      var t = elapsed / duration, e = ease(t), n = tweens.length;
+      while (n > 0) {
+        tweens[--n].call(node, e);
+      }
+      if (t >= 1) {
+        transition.event && transition.event.end.call(node, node.__data__, i);
+        if (--lock.count) delete lock[id]; else delete node[ns];
+        return 1;
+      }
+    }
+    if (!transition) {
+      time = inherit.time;
+      timer = d3_timer(schedule, 0, time);
+      transition = lock[id] = {
+        tween: new d3_Map(),
+        time: time,
+        timer: timer,
+        delay: inherit.delay,
+        duration: inherit.duration,
+        ease: inherit.ease,
+        index: i
+      };
+      inherit = null;
+      ++lock.count;
+    }
+  }
+  d3.svg.axis = function() {
+    var scale = d3.scale.linear(), orient = d3_svg_axisDefaultOrient, innerTickSize = 6, outerTickSize = 6, tickPadding = 3, tickArguments_ = [ 10 ], tickValues = null, tickFormat_;
+    function axis(g) {
+      g.each(function() {
+        var g = d3.select(this);
+        var scale0 = this.__chart__ || scale, scale1 = this.__chart__ = scale.copy();
+        var ticks = tickValues == null ? scale1.ticks ? scale1.ticks.apply(scale1, tickArguments_) : scale1.domain() : tickValues, tickFormat = tickFormat_ == null ? scale1.tickFormat ? scale1.tickFormat.apply(scale1, tickArguments_) : d3_identity : tickFormat_, tick = g.selectAll(".tick").data(ticks, scale1), tickEnter = tick.enter().insert("g", ".domain").attr("class", "tick").style("opacity", ε), tickExit = d3.transition(tick.exit()).style("opacity", ε).remove(), tickUpdate = d3.transition(tick.order()).style("opacity", 1), tickSpacing = Math.max(innerTickSize, 0) + tickPadding, tickTransform;
+        var range = d3_scaleRange(scale1), path = g.selectAll(".domain").data([ 0 ]), pathUpdate = (path.enter().append("path").attr("class", "domain"), 
+        d3.transition(path));
+        tickEnter.append("line");
+        tickEnter.append("text");
+        var lineEnter = tickEnter.select("line"), lineUpdate = tickUpdate.select("line"), text = tick.select("text").text(tickFormat), textEnter = tickEnter.select("text"), textUpdate = tickUpdate.select("text"), sign = orient === "top" || orient === "left" ? -1 : 1, x1, x2, y1, y2;
+        if (orient === "bottom" || orient === "top") {
+          tickTransform = d3_svg_axisX, x1 = "x", y1 = "y", x2 = "x2", y2 = "y2";
+          text.attr("dy", sign < 0 ? "0em" : ".71em").style("text-anchor", "middle");
+          pathUpdate.attr("d", "M" + range[0] + "," + sign * outerTickSize + "V0H" + range[1] + "V" + sign * outerTickSize);
+        } else {
+          tickTransform = d3_svg_axisY, x1 = "y", y1 = "x", x2 = "y2", y2 = "x2";
+          text.attr("dy", ".32em").style("text-anchor", sign < 0 ? "end" : "start");
+          pathUpdate.attr("d", "M" + sign * outerTickSize + "," + range[0] + "H0V" + range[1] + "H" + sign * outerTickSize);
+        }
+        lineEnter.attr(y2, sign * innerTickSize);
+        textEnter.attr(y1, sign * tickSpacing);
+        lineUpdate.attr(x2, 0).attr(y2, sign * innerTickSize);
+        textUpdate.attr(x1, 0).attr(y1, sign * tickSpacing);
+        if (scale1.rangeBand) {
+          var x = scale1, dx = x.rangeBand() / 2;
+          scale0 = scale1 = function(d) {
+            return x(d) + dx;
+          };
+        } else if (scale0.rangeBand) {
+          scale0 = scale1;
+        } else {
+          tickExit.call(tickTransform, scale1, scale0);
+        }
+        tickEnter.call(tickTransform, scale0, scale1);
+        tickUpdate.call(tickTransform, scale1, scale1);
+      });
+    }
+    axis.scale = function(x) {
+      if (!arguments.length) return scale;
+      scale = x;
+      return axis;
+    };
+    axis.orient = function(x) {
+      if (!arguments.length) return orient;
+      orient = x in d3_svg_axisOrients ? x + "" : d3_svg_axisDefaultOrient;
+      return axis;
+    };
+    axis.ticks = function() {
+      if (!arguments.length) return tickArguments_;
+      tickArguments_ = d3_array(arguments);
+      return axis;
+    };
+    axis.tickValues = function(x) {
+      if (!arguments.length) return tickValues;
+      tickValues = x;
+      return axis;
+    };
+    axis.tickFormat = function(x) {
+      if (!arguments.length) return tickFormat_;
+      tickFormat_ = x;
+      return axis;
+    };
+    axis.tickSize = function(x) {
+      var n = arguments.length;
+      if (!n) return innerTickSize;
+      innerTickSize = +x;
+      outerTickSize = +arguments[n - 1];
+      return axis;
+    };
+    axis.innerTickSize = function(x) {
+      if (!arguments.length) return innerTickSize;
+      innerTickSize = +x;
+      return axis;
+    };
+    axis.outerTickSize = function(x) {
+      if (!arguments.length) return outerTickSize;
+      outerTickSize = +x;
+      return axis;
+    };
+    axis.tickPadding = function(x) {
+      if (!arguments.length) return tickPadding;
+      tickPadding = +x;
+      return axis;
+    };
+    axis.tickSubdivide = function() {
+      return arguments.length && axis;
+    };
+    return axis;
+  };
+  var d3_svg_axisDefaultOrient = "bottom", d3_svg_axisOrients = {
+    top: 1,
+    right: 1,
+    bottom: 1,
+    left: 1
+  };
+  function d3_svg_axisX(selection, x0, x1) {
+    selection.attr("transform", function(d) {
+      var v0 = x0(d);
+      return "translate(" + (isFinite(v0) ? v0 : x1(d)) + ",0)";
+    });
+  }
+  function d3_svg_axisY(selection, y0, y1) {
+    selection.attr("transform", function(d) {
+      var v0 = y0(d);
+      return "translate(0," + (isFinite(v0) ? v0 : y1(d)) + ")";
+    });
+  }
+  d3.svg.brush = function() {
+    var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x = null, y = null, xExtent = [ 0, 0 ], yExtent = [ 0, 0 ], xExtentDomain, yExtentDomain, xClamp = true, yClamp = true, resizes = d3_svg_brushResizes[0];
+    function brush(g) {
+      g.each(function() {
+        var g = d3.select(this).style("pointer-events", "all").style("-webkit-tap-highlight-color", "rgba(0,0,0,0)").on("mousedown.brush", brushstart).on("touchstart.brush", brushstart);
+        var background = g.selectAll(".background").data([ 0 ]);
+        background.enter().append("rect").attr("class", "background").style("visibility", "hidden").style("cursor", "crosshair");
+        g.selectAll(".extent").data([ 0 ]).enter().append("rect").attr("class", "extent").style("cursor", "move");
+        var resize = g.selectAll(".resize").data(resizes, d3_identity);
+        resize.exit().remove();
+        resize.enter().append("g").attr("class", function(d) {
+          return "resize " + d;
+        }).style("cursor", function(d) {
+          return d3_svg_brushCursor[d];
+        }).append("rect").attr("x", function(d) {
+          return /[ew]$/.test(d) ? -3 : null;
+        }).attr("y", function(d) {
+          return /^[ns]/.test(d) ? -3 : null;
+        }).attr("width", 6).attr("height", 6).style("visibility", "hidden");
+        resize.style("display", brush.empty() ? "none" : null);
+        var gUpdate = d3.transition(g), backgroundUpdate = d3.transition(background), range;
+        if (x) {
+          range = d3_scaleRange(x);
+          backgroundUpdate.attr("x", range[0]).attr("width", range[1] - range[0]);
+          redrawX(gUpdate);
+        }
+        if (y) {
+          range = d3_scaleRange(y);
+          backgroundUpdate.attr("y", range[0]).attr("height", range[1] - range[0]);
+          redrawY(gUpdate);
+        }
+        redraw(gUpdate);
+      });
+    }
+    brush.event = function(g) {
+      g.each(function() {
+        var event_ = event.of(this, arguments), extent1 = {
+          x: xExtent,
+          y: yExtent,
+          i: xExtentDomain,
+          j: yExtentDomain
+        }, extent0 = this.__chart__ || extent1;
+        this.__chart__ = extent1;
+        if (d3_transitionInheritId) {
+          d3.select(this).transition().each("start.brush", function() {
+            xExtentDomain = extent0.i;
+            yExtentDomain = extent0.j;
+            xExtent = extent0.x;
+            yExtent = extent0.y;
+            event_({
+              type: "brushstart"
+            });
+          }).tween("brush:brush", function() {
+            var xi = d3_interpolateArray(xExtent, extent1.x), yi = d3_interpolateArray(yExtent, extent1.y);
+            xExtentDomain = yExtentDomain = null;
+            return function(t) {
+              xExtent = extent1.x = xi(t);
+              yExtent = extent1.y = yi(t);
+              event_({
+                type: "brush",
+                mode: "resize"
+              });
+            };
+          }).each("end.brush", function() {
+            xExtentDomain = extent1.i;
+            yExtentDomain = extent1.j;
+            event_({
+              type: "brush",
+              mode: "resize"
+            });
+            event_({
+              type: "brushend"
+            });
+          });
+        } else {
+          event_({
+            type: "brushstart"
+          });
+          event_({
+            type: "brush",
+            mode: "resize"
+          });
+          event_({
+            type: "brushend"
+          });
+        }
+      });
+    };
+    function redraw(g) {
+      g.selectAll(".resize").attr("transform", function(d) {
+        return "translate(" + xExtent[+/e$/.test(d)] + "," + yExtent[+/^s/.test(d)] + ")";
+      });
+    }
+    function redrawX(g) {
+      g.select(".extent").attr("x", xExtent[0]);
+      g.selectAll(".extent,.n>rect,.s>rect").attr("width", xExtent[1] - xExtent[0]);
+    }
+    function redrawY(g) {
+      g.select(".extent").attr("y", yExtent[0]);
+      g.selectAll(".extent,.e>rect,.w>rect").attr("height", yExtent[1] - yExtent[0]);
+    }
+    function brushstart() {
+      var target = this, eventTarget = d3.select(d3.event.target), event_ = event.of(target, arguments), g = d3.select(target), resizing = eventTarget.datum(), resizingX = !/^(n|s)$/.test(resizing) && x, resizingY = !/^(e|w)$/.test(resizing) && y, dragging = eventTarget.classed("extent"), dragRestore = d3_event_dragSuppress(target), center, origin = d3.mouse(target), offset;
+      var w = d3.select(d3_window(target)).on("keydown.brush", keydown).on("keyup.brush", keyup);
+      if (d3.event.changedTouches) {
+        w.on("touchmove.brush", brushmove).on("touchend.brush", brushend);
+      } else {
+        w.on("mousemove.brush", brushmove).on("mouseup.brush", brushend);
+      }
+      g.interrupt().selectAll("*").interrupt();
+      if (dragging) {
+        origin[0] = xExtent[0] - origin[0];
+        origin[1] = yExtent[0] - origin[1];
+      } else if (resizing) {
+        var ex = +/w$/.test(resizing), ey = +/^n/.test(resizing);
+        offset = [ xExtent[1 - ex] - origin[0], yExtent[1 - ey] - origin[1] ];
+        origin[0] = xExtent[ex];
+        origin[1] = yExtent[ey];
+      } else if (d3.event.altKey) center = origin.slice();
+      g.style("pointer-events", "none").selectAll(".resize").style("display", null);
+      d3.select("body").style("cursor", eventTarget.style("cursor"));
+      event_({
+        type: "brushstart"
+      });
+      brushmove();
+      function keydown() {
+        if (d3.event.keyCode == 32) {
+          if (!dragging) {
+            center = null;
+            origin[0] -= xExtent[1];
+            origin[1] -= yExtent[1];
+            dragging = 2;
+          }
+          d3_eventPreventDefault();
+        }
+      }
+      function keyup() {
+        if (d3.event.keyCode == 32 && dragging == 2) {
+          origin[0] += xExtent[1];
+          origin[1] += yExtent[1];
+          dragging = 0;
+          d3_eventPreventDefault();
+        }
+      }
+      function brushmove() {
+        var point = d3.mouse(target), moved = false;
+        if (offset) {
+          point[0] += offset[0];
+          point[1] += offset[1];
+        }
+        if (!dragging) {
+          if (d3.event.altKey) {
+            if (!center) center = [ (xExtent[0] + xExtent[1]) / 2, (yExtent[0] + yExtent[1]) / 2 ];
+            origin[0] = xExtent[+(point[0] < center[0])];
+            origin[1] = yExtent[+(point[1] < center[1])];
+          } else center = null;
+        }
+        if (resizingX && move1(point, x, 0)) {
+          redrawX(g);
+          moved = true;
+        }
+        if (resizingY && move1(point, y, 1)) {
+          redrawY(g);
+          moved = true;
+        }
+        if (moved) {
+          redraw(g);
+          event_({
+            type: "brush",
+            mode: dragging ? "move" : "resize"
+          });
+        }
+      }
+      function move1(point, scale, i) {
+        var range = d3_scaleRange(scale), r0 = range[0], r1 = range[1], position = origin[i], extent = i ? yExtent : xExtent, size = extent[1] - extent[0], min, max;
+        if (dragging) {
+          r0 -= position;
+          r1 -= size + position;
+        }
+        min = (i ? yClamp : xClamp) ? Math.max(r0, Math.min(r1, point[i])) : point[i];
+        if (dragging) {
+          max = (min += position) + size;
+        } else {
+          if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min));
+          if (position < min) {
+            max = min;
+            min = position;
+          } else {
+            max = position;
+          }
+        }
+        if (extent[0] != min || extent[1] != max) {
+          if (i) yExtentDomain = null; else xExtentDomain = null;
+          extent[0] = min;
+          extent[1] = max;
+          return true;
+        }
+      }
+      function brushend() {
+        brushmove();
+        g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null);
+        d3.select("body").style("cursor", null);
+        w.on("mousemove.brush", null).on("mouseup.brush", null).on("touchmove.brush", null).on("touchend.brush", null).on("keydown.brush", null).on("keyup.brush", null);
+        dragRestore();
+        event_({
+          type: "brushend"
+        });
+      }
+    }
+    brush.x = function(z) {
+      if (!arguments.length) return x;
+      x = z;
+      resizes = d3_svg_brushResizes[!x << 1 | !y];
+      return brush;
+    };
+    brush.y = function(z) {
+      if (!arguments.length) return y;
+      y = z;
+      resizes = d3_svg_brushResizes[!x << 1 | !y];
+      return brush;
+    };
+    brush.clamp = function(z) {
+      if (!arguments.length) return x && y ? [ xClamp, yClamp ] : x ? xClamp : y ? yClamp : null;
+      if (x && y) xClamp = !!z[0], yClamp = !!z[1]; else if (x) xClamp = !!z; else if (y) yClamp = !!z;
+      return brush;
+    };
+    brush.extent = function(z) {
+      var x0, x1, y0, y1, t;
+      if (!arguments.length) {
+        if (x) {
+          if (xExtentDomain) {
+            x0 = xExtentDomain[0], x1 = xExtentDomain[1];
+          } else {
+            x0 = xExtent[0], x1 = xExtent[1];
+            if (x.invert) x0 = x.invert(x0), x1 = x.invert(x1);
+            if (x1 < x0) t = x0, x0 = x1, x1 = t;
+          }
+        }
+        if (y) {
+          if (yExtentDomain) {
+            y0 = yExtentDomain[0], y1 = yExtentDomain[1];
+          } else {
+            y0 = yExtent[0], y1 = yExtent[1];
+            if (y.invert) y0 = y.invert(y0), y1 = y.invert(y1);
+            if (y1 < y0) t = y0, y0 = y1, y1 = t;
+          }
+        }
+        return x && y ? [ [ x0, y0 ], [ x1, y1 ] ] : x ? [ x0, x1 ] : y && [ y0, y1 ];
+      }
+      if (x) {
+        x0 = z[0], x1 = z[1];
+        if (y) x0 = x0[0], x1 = x1[0];
+        xExtentDomain = [ x0, x1 ];
+        if (x.invert) x0 = x(x0), x1 = x(x1);
+        if (x1 < x0) t = x0, x0 = x1, x1 = t;
+        if (x0 != xExtent[0] || x1 != xExtent[1]) xExtent = [ x0, x1 ];
+      }
+      if (y) {
+        y0 = z[0], y1 = z[1];
+        if (x) y0 = y0[1], y1 = y1[1];
+        yExtentDomain = [ y0, y1 ];
+        if (y.invert) y0 = y(y0), y1 = y(y1);
+        if (y1 < y0) t = y0, y0 = y1, y1 = t;
+        if (y0 != yExtent[0] || y1 != yExtent[1]) yExtent = [ y0, y1 ];
+      }
+      return brush;
+    };
+    brush.clear = function() {
+      if (!brush.empty()) {
+        xExtent = [ 0, 0 ], yExtent = [ 0, 0 ];
+        xExtentDomain = yExtentDomain = null;
+      }
+      return brush;
+    };
+    brush.empty = function() {
+      return !!x && xExtent[0] == xExtent[1] || !!y && yExtent[0] == yExtent[1];
+    };
+    return d3.rebind(brush, event, "on");
+  };
+  var d3_svg_brushCursor = {
+    n: "ns-resize",
+    e: "ew-resize",
+    s: "ns-resize",
+    w: "ew-resize",
+    nw: "nwse-resize",
+    ne: "nesw-resize",
+    se: "nwse-resize",
+    sw: "nesw-resize"
+  };
+  var d3_svg_brushResizes = [ [ "n", "e", "s", "w", "nw", "ne", "se", "sw" ], [ "e", "w" ], [ "n", "s" ], [] ];
+  var d3_time_format = d3_time.format = d3_locale_enUS.timeFormat;
+  var d3_time_formatUtc = d3_time_format.utc;
+  var d3_time_formatIso = d3_time_formatUtc("%Y-%m-%dT%H:%M:%S.%LZ");
+  d3_time_format.iso = Date.prototype.toISOString && +new Date("2000-01-01T00:00:00.000Z") ? d3_time_formatIsoNative : d3_time_formatIso;
+  function d3_time_formatIsoNative(date) {
+    return date.toISOString();
+  }
+  d3_time_formatIsoNative.parse = function(string) {
+    var date = new Date(string);
+    return isNaN(date) ? null : date;
+  };
+  d3_time_formatIsoNative.toString = d3_time_formatIso.toString;
+  d3_time.second = d3_time_interval(function(date) {
+    return new d3_date(Math.floor(date / 1e3) * 1e3);
+  }, function(date, offset) {
+    date.setTime(date.getTime() + Math.floor(offset) * 1e3);
+  }, function(date) {
+    return date.getSeconds();
+  });
+  d3_time.seconds = d3_time.second.range;
+  d3_time.seconds.utc = d3_time.second.utc.range;
+  d3_time.minute = d3_time_interval(function(date) {
+    return new d3_date(Math.floor(date / 6e4) * 6e4);
+  }, function(date, offset) {
+    date.setTime(date.getTime() + Math.floor(offset) * 6e4);
+  }, function(date) {
+    return date.getMinutes();
+  });
+  d3_time.minutes = d3_time.minute.range;
+  d3_time.minutes.utc = d3_time.minute.utc.range;
+  d3_time.hour = d3_time_interval(function(date) {
+    var timezone = date.getTimezoneOffset() / 60;
+    return new d3_date((Math.floor(date / 36e5 - timezone) + timezone) * 36e5);
+  }, function(date, offset) {
+    date.setTime(date.getTime() + Math.floor(offset) * 36e5);
+  }, function(date) {
+    return date.getHours();
+  });
+  d3_time.hours = d3_time.hour.range;
+  d3_time.hours.utc = d3_time.hour.utc.range;
+  d3_time.month = d3_time_interval(function(date) {
+    date = d3_time.day(date);
+    date.setDate(1);
+    return date;
+  }, function(date, offset) {
+    date.setMonth(date.getMonth() + offset);
+  }, function(date) {
+    return date.getMonth();
+  });
+  d3_time.months = d3_time.month.range;
+  d3_time.months.utc = d3_time.month.utc.range;
+  function d3_time_scale(linear, methods, format) {
+    function scale(x) {
+      return linear(x);
+    }
+    scale.invert = function(x) {
+      return d3_time_scaleDate(linear.invert(x));
+    };
+    scale.domain = function(x) {
+      if (!arguments.length) return linear.domain().map(d3_time_scaleDate);
+      linear.domain(x);
+      return scale;
+    };
+    function tickMethod(extent, count) {
+      var span = extent[1] - extent[0], target = span / count, i = d3.bisect(d3_time_scaleSteps, target);
+      return i == d3_time_scaleSteps.length ? [ methods.year, d3_scale_linearTickRange(extent.map(function(d) {
+        return d / 31536e6;
+      }), count)[2] ] : !i ? [ d3_time_scaleMilliseconds, d3_scale_linearTickRange(extent, count)[2] ] : methods[target / d3_time_scaleSteps[i - 1] < d3_time_scaleSteps[i] / target ? i - 1 : i];
+    }
+    scale.nice = function(interval, skip) {
+      var domain = scale.domain(), extent = d3_scaleExtent(domain), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" && tickMethod(extent, interval);
+      if (method) interval = method[0], skip = method[1];
+      function skipped(date) {
+        return !isNaN(date) && !interval.range(date, d3_time_scaleDate(+date + 1), skip).length;
+      }
+      return scale.domain(d3_scale_nice(domain, skip > 1 ? {
+        floor: function(date) {
+          while (skipped(date = interval.floor(date))) date = d3_time_scaleDate(date - 1);
+          return date;
+        },
+        ceil: function(date) {
+          while (skipped(date = interval.ceil(date))) date = d3_time_scaleDate(+date + 1);
+          return date;
+        }
+      } : interval));
+    };
+    scale.ticks = function(interval, skip) {
+      var extent = d3_scaleExtent(scale.domain()), method = interval == null ? tickMethod(extent, 10) : typeof interval === "number" ? tickMethod(extent, interval) : !interval.range && [ {
+        range: interval
+      }, skip ];
+      if (method) interval = method[0], skip = method[1];
+      return interval.range(extent[0], d3_time_scaleDate(+extent[1] + 1), skip < 1 ? 1 : skip);
+    };
+    scale.tickFormat = function() {
+      return format;
+    };
+    scale.copy = function() {
+      return d3_time_scale(linear.copy(), methods, format);
+    };
+    return d3_scale_linearRebind(scale, linear);
+  }
+  function d3_time_scaleDate(t) {
+    return new Date(t);
+  }
+  var d3_time_scaleSteps = [ 1e3, 5e3, 15e3, 3e4, 6e4, 3e5, 9e5, 18e5, 36e5, 108e5, 216e5, 432e5, 864e5, 1728e5, 6048e5, 2592e6, 7776e6, 31536e6 ];
+  var d3_time_scaleLocalMethods = [ [ d3_time.second, 1 ], [ d3_time.second, 5 ], [ d3_time.second, 15 ], [ d3_time.second, 30 ], [ d3_time.minute, 1 ], [ d3_time.minute, 5 ], [ d3_time.minute, 15 ], [ d3_time.minute, 30 ], [ d3_time.hour, 1 ], [ d3_time.hour, 3 ], [ d3_time.hour, 6 ], [ d3_time.hour, 12 ], [ d3_time.day, 1 ], [ d3_time.day, 2 ], [ d3_time.week, 1 ], [ d3_time.month, 1 ], [ d3_time.month, 3 ], [ d3_time.year, 1 ] ];
+  var d3_time_scaleLocalFormat = d3_time_format.multi([ [ ".%L", function(d) {
+    return d.getMilliseconds();
+  } ], [ ":%S", function(d) {
+    return d.getSeconds();
+  } ], [ "%I:%M", function(d) {
+    return d.getMinutes();
+  } ], [ "%I %p", function(d) {
+    return d.getHours();
+  } ], [ "%a %d", function(d) {
+    return d.getDay() && d.getDate() != 1;
+  } ], [ "%b %d", function(d) {
+    return d.getDate() != 1;
+  } ], [ "%B", function(d) {
+    return d.getMonth();
+  } ], [ "%Y", d3_true ] ]);
+  var d3_time_scaleMilliseconds = {
+    range: function(start, stop, step) {
+      return d3.range(Math.ceil(start / step) * step, +stop, step).map(d3_time_scaleDate);
+    },
+    floor: d3_identity,
+    ceil: d3_identity
+  };
+  d3_time_scaleLocalMethods.year = d3_time.year;
+  d3_time.scale = function() {
+    return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat);
+  };
+  var d3_time_scaleUtcMethods = d3_time_scaleLocalMethods.map(function(m) {
+    return [ m[0].utc, m[1] ];
+  });
+  var d3_time_scaleUtcFormat = d3_time_formatUtc.multi([ [ ".%L", function(d) {
+    return d.getUTCMilliseconds();
+  } ], [ ":%S", function(d) {
+    return d.getUTCSeconds();
+  } ], [ "%I:%M", function(d) {
+    return d.getUTCMinutes();
+  } ], [ "%I %p", function(d) {
+    return d.getUTCHours();
+  } ], [ "%a %d", function(d) {
+    return d.getUTCDay() && d.getUTCDate() != 1;
+  } ], [ "%b %d", function(d) {
+    return d.getUTCDate() != 1;
+  } ], [ "%B", function(d) {
+    return d.getUTCMonth();
+  } ], [ "%Y", d3_true ] ]);
+  d3_time_scaleUtcMethods.year = d3_time.year.utc;
+  d3_time.scale.utc = function() {
+    return d3_time_scale(d3.scale.linear(), d3_time_scaleUtcMethods, d3_time_scaleUtcFormat);
+  };
+  d3.text = d3_xhrType(function(request) {
+    return request.responseText;
+  });
+  d3.json = function(url, callback) {
+    return d3_xhr(url, "application/json", d3_json, callback);
+  };
+  function d3_json(request) {
+    return JSON.parse(request.responseText);
+  }
+  d3.html = function(url, callback) {
+    return d3_xhr(url, "text/html", d3_html, callback);
+  };
+  function d3_html(request) {
+    var range = d3_document.createRange();
+    range.selectNode(d3_document.body);
+    return range.createContextualFragment(request.responseText);
+  }
+  d3.xml = d3_xhrType(function(request) {
+    return request.responseXML;
+  });
+  if (typeof define === "function" && define.amd) this.d3 = d3, define(d3); else if (typeof module === "object" && module.exports) module.exports = d3; else this.d3 = d3;
+}();
\ No newline at end of file
diff --git a/src/legacy/design-studio/js/dagre.js b/src/legacy/design-studio/js/dagre.js
new file mode 100644
index 0000000000000000000000000000000000000000..830997be17eefb8d385f37d88b5508976dbb0a71
--- /dev/null
+++ b/src/legacy/design-studio/js/dagre.js
@@ -0,0 +1,16396 @@
+!function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.dagre=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+/*
+Copyright (c) 2012-2014 Chris Pettitt
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+*/
+
+module.exports = {
+  graphlib: require("./lib/graphlib"),
+
+  layout: require("./lib/layout"),
+  debug: require("./lib/debug"),
+  util: {
+    time: require("./lib/util").time,
+    notime: require("./lib/util").notime
+  },
+  version: require("./lib/version")
+};
+
+},{"./lib/debug":6,"./lib/graphlib":7,"./lib/layout":9,"./lib/util":29,"./lib/version":30}],2:[function(require,module,exports){
+"use strict";
+
+var _ = require("./lodash"),
+    greedyFAS = require("./greedy-fas");
+
+module.exports = {
+  run: run,
+  undo: undo
+};
+
+function run(g) {
+  var fas = (g.graph().acyclicer === "greedy"
+                ? greedyFAS(g, weightFn(g))
+                : dfsFAS(g));
+  _.each(fas, function(e) {
+    var label = g.edge(e);
+    g.removeEdge(e);
+    label.forwardName = e.name;
+    label.reversed = true;
+    g.setEdge(e.w, e.v, label, _.uniqueId("rev"));
+  });
+
+  function weightFn(g) {
+    return function(e) {
+      return g.edge(e).weight;
+    };
+  }
+}
+
+function dfsFAS(g) {
+  var fas = [],
+      stack = {},
+      visited = {};
+
+  function dfs(v) {
+    if (_.has(visited, v)) {
+      return;
+    }
+    visited[v] = true;
+    stack[v] = true;
+    _.each(g.outEdges(v), function(e) {
+      if (_.has(stack, e.w)) {
+        fas.push(e);
+      } else {
+        dfs(e.w);
+      }
+    });
+    delete stack[v];
+  }
+
+  _.each(g.nodes(), dfs);
+  return fas;
+}
+
+function undo(g) {
+  _.each(g.edges(), function(e) {
+    var label = g.edge(e);
+    if (label.reversed) {
+      g.removeEdge(e);
+
+      var forwardName = label.forwardName;
+      delete label.reversed;
+      delete label.forwardName;
+      g.setEdge(e.w, e.v, label, forwardName);
+    }
+  });
+}
+
+},{"./greedy-fas":8,"./lodash":10}],3:[function(require,module,exports){
+var _ = require("./lodash"),
+    util = require("./util");
+
+module.exports = addBorderSegments;
+
+function addBorderSegments(g) {
+  function dfs(v) {
+    var children = g.children(v),
+        node = g.node(v);
+    if (children.length) {
+      _.each(children, dfs);
+    }
+
+    if (_.has(node, "minRank")) {
+      node.borderLeft = [];
+      node.borderRight = [];
+      for (var rank = node.minRank, maxRank = node.maxRank + 1;
+           rank < maxRank;
+           ++rank) {
+        addBorderNode(g, "borderLeft", "_bl", v, node, rank);
+        addBorderNode(g, "borderRight", "_br", v, node, rank);
+      }
+    }
+  }
+
+  _.each(g.children(), dfs);
+}
+
+function addBorderNode(g, prop, prefix, sg, sgNode, rank) {
+  var label = { width: 0, height: 0, rank: rank, borderType: prop },
+      prev = sgNode[prop][rank - 1],
+      curr = util.addDummyNode(g, "border", label, prefix);
+  sgNode[prop][rank] = curr;
+  g.setParent(curr, sg);
+  if (prev) {
+    g.setEdge(prev, curr, { weight: 1 });
+  }
+}
+
+},{"./lodash":10,"./util":29}],4:[function(require,module,exports){
+"use strict";
+
+var _ = require("./lodash");
+
+module.exports = {
+  adjust: adjust,
+  undo: undo
+};
+
+function adjust(g) {
+  var rankDir = g.graph().rankdir.toLowerCase();
+  if (rankDir === "lr" || rankDir === "rl") {
+    swapWidthHeight(g);
+  }
+}
+
+function undo(g) {
+  var rankDir = g.graph().rankdir.toLowerCase();
+  if (rankDir === "bt" || rankDir === "rl") {
+    reverseY(g);
+  }
+
+  if (rankDir === "lr" || rankDir === "rl") {
+    swapXY(g);
+    swapWidthHeight(g);
+  }
+}
+
+function swapWidthHeight(g) {
+  _.each(g.nodes(), function(v) { swapWidthHeightOne(g.node(v)); });
+  _.each(g.edges(), function(e) { swapWidthHeightOne(g.edge(e)); });
+}
+
+function swapWidthHeightOne(attrs) {
+  var w = attrs.width;
+  attrs.width = attrs.height;
+  attrs.height = w;
+}
+
+function reverseY(g) {
+  _.each(g.nodes(), function(v) { reverseYOne(g.node(v)); });
+
+  _.each(g.edges(), function(e) {
+    var edge = g.edge(e);
+    _.each(edge.points, reverseYOne);
+    if (_.has(edge, "y")) {
+      reverseYOne(edge);
+    }
+  });
+}
+
+function reverseYOne(attrs) {
+  attrs.y = -attrs.y;
+}
+
+function swapXY(g) {
+  _.each(g.nodes(), function(v) { swapXYOne(g.node(v)); });
+
+  _.each(g.edges(), function(e) {
+    var edge = g.edge(e);
+    _.each(edge.points, swapXYOne);
+    if (_.has(edge, "x")) {
+      swapXYOne(edge);
+    }
+  });
+}
+
+function swapXYOne(attrs) {
+  var x = attrs.x;
+  attrs.x = attrs.y;
+  attrs.y = x;
+}
+
+},{"./lodash":10}],5:[function(require,module,exports){
+/*
+ * Simple doubly linked list implementation derived from Cormen, et al.,
+ * "Introduction to Algorithms".
+ */
+
+module.exports = List;
+
+function List() {
+  var sentinel = {};
+  sentinel._next = sentinel._prev = sentinel;
+  this._sentinel = sentinel;
+}
+
+List.prototype.dequeue = function() {
+  var sentinel = this._sentinel,
+      entry = sentinel._prev;
+  if (entry !== sentinel) {
+    unlink(entry);
+    return entry;
+  }
+};
+
+List.prototype.enqueue = function(entry) {
+  var sentinel = this._sentinel;
+  if (entry._prev && entry._next) {
+    unlink(entry);
+  }
+  entry._next = sentinel._next;
+  sentinel._next._prev = entry;
+  sentinel._next = entry;
+  entry._prev = sentinel;
+};
+
+List.prototype.toString = function() {
+  var strs = [],
+      sentinel = this._sentinel,
+      curr = sentinel._prev;
+  while (curr !== sentinel) {
+    strs.push(JSON.stringify(curr, filterOutLinks));
+    curr = curr._prev;
+  }
+  return "[" + strs.join(", ") + "]";
+};
+
+function unlink(entry) {
+  entry._prev._next = entry._next;
+  entry._next._prev = entry._prev;
+  delete entry._next;
+  delete entry._prev;
+}
+
+function filterOutLinks(k, v) {
+  if (k !== "_next" && k !== "_prev") {
+    return v;
+  }
+}
+
+},{}],6:[function(require,module,exports){
+var _ = require("./lodash"),
+    util = require("./util"),
+    Graph = require("./graphlib").Graph;
+
+module.exports = {
+  debugOrdering: debugOrdering
+};
+
+/* istanbul ignore next */
+function debugOrdering(g) {
+  var layerMatrix = util.buildLayerMatrix(g);
+
+  var h = new Graph({ compound: true, multigraph: true }).setGraph({});
+
+  _.each(g.nodes(), function(v) {
+    h.setNode(v, { label: v });
+    h.setParent(v, "layer" + g.node(v).rank);
+  });
+
+  _.each(g.edges(), function(e) {
+    h.setEdge(e.v, e.w, {}, e.name);
+  });
+
+  _.each(layerMatrix, function(layer, i) {
+    var layerV = "layer" + i;
+    h.setNode(layerV, { rank: "same" });
+    _.reduce(layer, function(u, v) {
+      h.setEdge(u, v, { style: "invis" });
+      return v;
+    });
+  });
+
+  return h;
+}
+
+},{"./graphlib":7,"./lodash":10,"./util":29}],7:[function(require,module,exports){
+/* global window */
+
+var graphlib;
+
+if (typeof require === "function") {
+  try {
+    graphlib = require("graphlib");
+  } catch (e) {}
+}
+
+if (!graphlib) {
+  graphlib = window.graphlib;
+}
+
+module.exports = graphlib;
+
+},{"graphlib":31}],8:[function(require,module,exports){
+var _ = require("./lodash"),
+    Graph = require("./graphlib").Graph,
+    List = require("./data/list");
+
+/*
+ * A greedy heuristic for finding a feedback arc set for a graph. A feedback
+ * arc set is a set of edges that can be removed to make a graph acyclic.
+ * The algorithm comes from: P. Eades, X. Lin, and W. F. Smyth, "A fast and
+ * effective heuristic for the feedback arc set problem." This implementation
+ * adjusts that from the paper to allow for weighted edges.
+ */
+module.exports = greedyFAS;
+
+var DEFAULT_WEIGHT_FN = _.constant(1);
+
+function greedyFAS(g, weightFn) {
+  if (g.nodeCount() <= 1) {
+    return [];
+  }
+  var state = buildState(g, weightFn || DEFAULT_WEIGHT_FN);
+  var results = doGreedyFAS(state.graph, state.buckets, state.zeroIdx);
+
+  // Expand multi-edges
+  return _.flatten(_.map(results, function(e) {
+    return g.outEdges(e.v, e.w);
+  }), true);
+}
+
+function doGreedyFAS(g, buckets, zeroIdx) {
+  var results = [],
+      sources = buckets[buckets.length - 1],
+      sinks = buckets[0];
+
+  var entry;
+  while (g.nodeCount()) {
+    while ((entry = sinks.dequeue()))   { removeNode(g, buckets, zeroIdx, entry); }
+    while ((entry = sources.dequeue())) { removeNode(g, buckets, zeroIdx, entry); }
+    if (g.nodeCount()) {
+      for (var i = buckets.length - 2; i > 0; --i) {
+        entry = buckets[i].dequeue();
+        if (entry) {
+          results = results.concat(removeNode(g, buckets, zeroIdx, entry, true));
+          break;
+        }
+      }
+    }
+  }
+
+  return results;
+}
+
+function removeNode(g, buckets, zeroIdx, entry, collectPredecessors) {
+  var results = collectPredecessors ? [] : undefined;
+
+  _.each(g.inEdges(entry.v), function(edge) {
+    var weight = g.edge(edge),
+        uEntry = g.node(edge.v);
+
+    if (collectPredecessors) {
+      results.push({ v: edge.v, w: edge.w });
+    }
+
+    uEntry.out -= weight;
+    assignBucket(buckets, zeroIdx, uEntry);
+  });
+
+  _.each(g.outEdges(entry.v), function(edge) {
+    var weight = g.edge(edge),
+        w = edge.w,
+        wEntry = g.node(w);
+    wEntry["in"] -= weight;
+    assignBucket(buckets, zeroIdx, wEntry);
+  });
+
+  g.removeNode(entry.v);
+
+  return results;
+}
+
+function buildState(g, weightFn) {
+  var fasGraph = new Graph(),
+      maxIn = 0,
+      maxOut = 0;
+
+  _.each(g.nodes(), function(v) {
+    fasGraph.setNode(v, { v: v, "in": 0, out: 0 });
+  });
+
+  // Aggregate weights on nodes, but also sum the weights across multi-edges
+  // into a single edge for the fasGraph.
+  _.each(g.edges(), function(e) {
+    var prevWeight = fasGraph.edge(e.v, e.w) || 0,
+        weight = weightFn(e),
+        edgeWeight = prevWeight + weight;
+    fasGraph.setEdge(e.v, e.w, edgeWeight);
+    maxOut = Math.max(maxOut, fasGraph.node(e.v).out += weight);
+    maxIn  = Math.max(maxIn,  fasGraph.node(e.w)["in"]  += weight);
+  });
+
+  var buckets = _.range(maxOut + maxIn + 3).map(function() { return new List(); });
+  var zeroIdx = maxIn + 1;
+
+  _.each(fasGraph.nodes(), function(v) {
+    assignBucket(buckets, zeroIdx, fasGraph.node(v));
+  });
+
+  return { graph: fasGraph, buckets: buckets, zeroIdx: zeroIdx };
+}
+
+function assignBucket(buckets, zeroIdx, entry) {
+  if (!entry.out) {
+    buckets[0].enqueue(entry);
+  } else if (!entry["in"]) {
+    buckets[buckets.length - 1].enqueue(entry);
+  } else {
+    buckets[entry.out - entry["in"] + zeroIdx].enqueue(entry);
+  }
+}
+
+},{"./data/list":5,"./graphlib":7,"./lodash":10}],9:[function(require,module,exports){
+"use strict";
+
+var _ = require("./lodash"),
+    acyclic = require("./acyclic"),
+    normalize = require("./normalize"),
+    rank = require("./rank"),
+    normalizeRanks = require("./util").normalizeRanks,
+    parentDummyChains = require("./parent-dummy-chains"),
+    removeEmptyRanks = require("./util").removeEmptyRanks,
+    nestingGraph = require("./nesting-graph"),
+    addBorderSegments = require("./add-border-segments"),
+    coordinateSystem = require("./coordinate-system"),
+    order = require("./order"),
+    position = require("./position"),
+    util = require("./util"),
+    Graph = require("./graphlib").Graph;
+
+module.exports = layout;
+
+function layout(g, opts) {
+  var time = opts && opts.debugTiming ? util.time : util.notime;
+  time("layout", function() {
+    var layoutGraph = time("  buildLayoutGraph",
+                               function() { return buildLayoutGraph(g); });
+    time("  runLayout",        function() { runLayout(layoutGraph, time); });
+    time("  updateInputGraph", function() { updateInputGraph(g, layoutGraph); });
+  });
+}
+
+function runLayout(g, time) {
+  time("    makeSpaceForEdgeLabels", function() { makeSpaceForEdgeLabels(g); });
+  time("    removeSelfEdges",        function() { removeSelfEdges(g); });
+  time("    acyclic",                function() { acyclic.run(g); });
+  time("    nestingGraph.run",       function() { nestingGraph.run(g); });
+  time("    rank",                   function() { rank(util.asNonCompoundGraph(g)); });
+  time("    injectEdgeLabelProxies", function() { injectEdgeLabelProxies(g); });
+  time("    removeEmptyRanks",       function() { removeEmptyRanks(g); });
+  time("    nestingGraph.cleanup",   function() { nestingGraph.cleanup(g); });
+  time("    normalizeRanks",         function() { normalizeRanks(g); });
+  time("    assignRankMinMax",       function() { assignRankMinMax(g); });
+  time("    removeEdgeLabelProxies", function() { removeEdgeLabelProxies(g); });
+  time("    normalize.run",          function() { normalize.run(g); });
+  time("    parentDummyChains",      function() { parentDummyChains(g); });
+  time("    addBorderSegments",      function() { addBorderSegments(g); });
+  time("    order",                  function() { order(g); });
+  time("    insertSelfEdges",        function() { insertSelfEdges(g); });
+  time("    adjustCoordinateSystem", function() { coordinateSystem.adjust(g); });
+  time("    position",               function() { position(g); });
+  time("    positionSelfEdges",      function() { positionSelfEdges(g); });
+  time("    removeBorderNodes",      function() { removeBorderNodes(g); });
+  time("    normalize.undo",         function() { normalize.undo(g); });
+  time("    fixupEdgeLabelCoords",   function() { fixupEdgeLabelCoords(g); });
+  time("    undoCoordinateSystem",   function() { coordinateSystem.undo(g); });
+  time("    translateGraph",         function() { translateGraph(g); });
+  time("    assignNodeIntersects",   function() { assignNodeIntersects(g); });
+  time("    reversePoints",          function() { reversePointsForReversedEdges(g); });
+  time("    acyclic.undo",           function() { acyclic.undo(g); });
+}
+
+/*
+ * Copies final layout information from the layout graph back to the input
+ * graph. This process only copies whitelisted attributes from the layout graph
+ * to the input graph, so it serves as a good place to determine what
+ * attributes can influence layout.
+ */
+function updateInputGraph(inputGraph, layoutGraph) {
+  _.each(inputGraph.nodes(), function(v) {
+    var inputLabel = inputGraph.node(v),
+        layoutLabel = layoutGraph.node(v);
+
+    if (inputLabel) {
+      inputLabel.x = layoutLabel.x;
+      inputLabel.y = layoutLabel.y;
+
+      if (layoutGraph.children(v).length) {
+        inputLabel.width = layoutLabel.width;
+        inputLabel.height = layoutLabel.height;
+      }
+    }
+  });
+
+  _.each(inputGraph.edges(), function(e) {
+    var inputLabel = inputGraph.edge(e),
+        layoutLabel = layoutGraph.edge(e);
+
+    inputLabel.points = layoutLabel.points;
+    if (_.has(layoutLabel, "x")) {
+      inputLabel.x = layoutLabel.x;
+      inputLabel.y = layoutLabel.y;
+    }
+  });
+
+  inputGraph.graph().width = layoutGraph.graph().width;
+  inputGraph.graph().height = layoutGraph.graph().height;
+}
+
+var graphNumAttrs = ["nodesep", "edgesep", "ranksep", "marginx", "marginy"],
+    graphDefaults = { ranksep: 50, edgesep: 20, nodesep: 50, rankdir: "tb" },
+    graphAttrs = ["acyclicer", "ranker", "rankdir", "align"],
+    nodeNumAttrs = ["width", "height"],
+    nodeDefaults = { width: 0, height: 0 },
+    edgeNumAttrs = ["minlen", "weight", "width", "height", "labeloffset"],
+    edgeDefaults = {
+      minlen: 1, weight: 1, width: 0, height: 0,
+      labeloffset: 10, labelpos: "r"
+    },
+    edgeAttrs = ["labelpos"];
+
+/*
+ * Constructs a new graph from the input graph, which can be used for layout.
+ * This process copies only whitelisted attributes from the input graph to the
+ * layout graph. Thus this function serves as a good place to determine what
+ * attributes can influence layout.
+ */
+function buildLayoutGraph(inputGraph) {
+  var g = new Graph({ multigraph: true, compound: true }),
+      graph = canonicalize(inputGraph.graph());
+
+  g.setGraph(_.merge({},
+    graphDefaults,
+    selectNumberAttrs(graph, graphNumAttrs),
+    _.pick(graph, graphAttrs)));
+
+  _.each(inputGraph.nodes(), function(v) {
+    var node = canonicalize(inputGraph.node(v));
+    g.setNode(v, _.defaults(selectNumberAttrs(node, nodeNumAttrs), nodeDefaults));
+    g.setParent(v, inputGraph.parent(v));
+  });
+
+  _.each(inputGraph.edges(), function(e) {
+    var edge = canonicalize(inputGraph.edge(e));
+    g.setEdge(e, _.merge({},
+      edgeDefaults,
+      selectNumberAttrs(edge, edgeNumAttrs),
+      _.pick(edge, edgeAttrs)));
+  });
+
+  return g;
+}
+
+/*
+ * This idea comes from the Gansner paper: to account for edge labels in our
+ * layout we split each rank in half by doubling minlen and halving ranksep.
+ * Then we can place labels at these mid-points between nodes.
+ *
+ * We also add some minimal padding to the width to push the label for the edge
+ * away from the edge itself a bit.
+ */
+function makeSpaceForEdgeLabels(g) {
+  var graph = g.graph();
+  graph.ranksep /= 2;
+  _.each(g.edges(), function(e) {
+    var edge = g.edge(e);
+    edge.minlen *= 2;
+    if (edge.labelpos.toLowerCase() !== "c") {
+      if (graph.rankdir === "TB" || graph.rankdir === "BT") {
+        edge.width += edge.labeloffset;
+      } else {
+        edge.height += edge.labeloffset;
+      }
+    }
+  });
+}
+
+/*
+ * Creates temporary dummy nodes that capture the rank in which each edge's
+ * label is going to, if it has one of non-zero width and height. We do this
+ * so that we can safely remove empty ranks while preserving balance for the
+ * label's position.
+ */
+function injectEdgeLabelProxies(g) {
+  _.each(g.edges(), function(e) {
+    var edge = g.edge(e);
+    if (edge.width && edge.height) {
+      var v = g.node(e.v),
+          w = g.node(e.w),
+          label = { rank: (w.rank - v.rank) / 2 + v.rank, e: e };
+      util.addDummyNode(g, "edge-proxy", label, "_ep");
+    }
+  });
+}
+
+function assignRankMinMax(g) {
+  var maxRank = 0;
+  _.each(g.nodes(), function(v) {
+    var node = g.node(v);
+    if (node.borderTop) {
+      node.minRank = g.node(node.borderTop).rank;
+      node.maxRank = g.node(node.borderBottom).rank;
+      maxRank = _.max(maxRank, node.maxRank);
+    }
+  });
+  g.graph().maxRank = maxRank;
+}
+
+function removeEdgeLabelProxies(g) {
+  _.each(g.nodes(), function(v) {
+    var node = g.node(v);
+    if (node.dummy === "edge-proxy") {
+      g.edge(node.e).labelRank = node.rank;
+      g.removeNode(v);
+    }
+  });
+}
+
+function translateGraph(g) {
+  var minX = Number.POSITIVE_INFINITY,
+      maxX = 0,
+      minY = Number.POSITIVE_INFINITY,
+      maxY = 0,
+      graphLabel = g.graph(),
+      marginX = graphLabel.marginx || 0,
+      marginY = graphLabel.marginy || 0;
+
+  function getExtremes(attrs) {
+    var x = attrs.x,
+        y = attrs.y,
+        w = attrs.width,
+        h = attrs.height;
+    minX = Math.min(minX, x - w / 2);
+    maxX = Math.max(maxX, x + w / 2);
+    minY = Math.min(minY, y - h / 2);
+    maxY = Math.max(maxY, y + h / 2);
+  }
+
+  _.each(g.nodes(), function(v) { getExtremes(g.node(v)); });
+  _.each(g.edges(), function(e) {
+    var edge = g.edge(e);
+    if (_.has(edge, "x")) {
+      getExtremes(edge);
+    }
+  });
+
+  minX -= marginX;
+  minY -= marginY;
+
+  _.each(g.nodes(), function(v) {
+    var node = g.node(v);
+    node.x -= minX;
+    node.y -= minY;
+  });
+
+  _.each(g.edges(), function(e) {
+    var edge = g.edge(e);
+    _.each(edge.points, function(p) {
+      p.x -= minX;
+      p.y -= minY;
+    });
+    if (_.has(edge, "x")) { edge.x -= minX; }
+    if (_.has(edge, "y")) { edge.y -= minY; }
+  });
+
+  graphLabel.width = maxX - minX + marginX;
+  graphLabel.height = maxY - minY + marginY;
+}
+
+function assignNodeIntersects(g) {
+  _.each(g.edges(), function(e) {
+    var edge = g.edge(e),
+        nodeV = g.node(e.v),
+        nodeW = g.node(e.w),
+        p1, p2;
+    if (!edge.points) {
+      edge.points = [];
+      p1 = nodeW;
+      p2 = nodeV;
+    } else {
+      p1 = edge.points[0];
+      p2 = edge.points[edge.points.length - 1];
+    }
+    edge.points.unshift(util.intersectRect(nodeV, p1));
+    edge.points.push(util.intersectRect(nodeW, p2));
+  });
+}
+
+function fixupEdgeLabelCoords(g) {
+  _.each(g.edges(), function(e) {
+    var edge = g.edge(e);
+    if (_.has(edge, "x")) {
+      if (edge.labelpos === "l" || edge.labelpos === "r") {
+        edge.width -= edge.labeloffset;
+      }
+      switch (edge.labelpos) {
+        case "l": edge.x -= edge.width / 2 + edge.labeloffset; break;
+        case "r": edge.x += edge.width / 2 + edge.labeloffset; break;
+      }
+    }
+  });
+}
+
+function reversePointsForReversedEdges(g) {
+  _.each(g.edges(), function(e) {
+    var edge = g.edge(e);
+    if (edge.reversed) {
+      edge.points.reverse();
+    }
+  });
+}
+
+function removeBorderNodes(g) {
+  _.each(g.nodes(), function(v) {
+    if (g.children(v).length) {
+      var node = g.node(v),
+          t = g.node(node.borderTop),
+          b = g.node(node.borderBottom),
+          l = g.node(_.last(node.borderLeft)),
+          r = g.node(_.last(node.borderRight));
+
+      node.width = Math.abs(r.x - l.x);
+      node.height = Math.abs(b.y - t.y);
+      node.x = l.x + node.width / 2;
+      node.y = t.y + node.height / 2;
+    }
+  });
+
+  _.each(g.nodes(), function(v) {
+    if (g.node(v).dummy === "border") {
+      g.removeNode(v);
+    }
+  });
+}
+
+function removeSelfEdges(g) {
+  _.each(g.edges(), function(e) {
+    if (e.v === e.w) {
+      var node = g.node(e.v);
+      if (!node.selfEdges) {
+        node.selfEdges = [];
+      }
+      node.selfEdges.push({ e: e, label: g.edge(e) });
+      g.removeEdge(e);
+    }
+  });
+}
+
+function insertSelfEdges(g) {
+  var layers = util.buildLayerMatrix(g);
+  _.each(layers, function(layer) {
+    var orderShift = 0;
+    _.each(layer, function(v, i) {
+      var node = g.node(v);
+      node.order = i + orderShift;
+      _.each(node.selfEdges, function(selfEdge) {
+        util.addDummyNode(g, "selfedge", {
+          width: selfEdge.label.width,
+          height: selfEdge.label.height,
+          rank: node.rank,
+          order: i + (++orderShift),
+          e: selfEdge.e,
+          label: selfEdge.label
+        }, "_se");
+      });
+      delete node.selfEdges;
+    });
+  });
+}
+
+function positionSelfEdges(g) {
+  _.each(g.nodes(), function(v) {
+    var node = g.node(v);
+    if (node.dummy === "selfedge") {
+      var selfNode = g.node(node.e.v),
+          x = selfNode.x + selfNode.width / 2,
+          y = selfNode.y,
+          dx = node.x - x,
+          dy = selfNode.height / 2;
+      g.setEdge(node.e, node.label);
+      g.removeNode(v);
+      node.label.points = [
+        { x: x + 2 * dx / 3, y: y - dy },
+        { x: x + 5 * dx / 6, y: y - dy },
+        { x: x +     dx    , y: y },
+        { x: x + 5 * dx / 6, y: y + dy },
+        { x: x + 2 * dx / 3, y: y + dy },
+      ];
+      node.label.x = node.x;
+      node.label.y = node.y;
+    }
+  });
+}
+
+function selectNumberAttrs(obj, attrs) {
+  return _.mapValues(_.pick(obj, attrs), Number);
+}
+
+function canonicalize(attrs) {
+  var newAttrs = {};
+  _.each(attrs, function(v, k) {
+    newAttrs[k.toLowerCase()] = v;
+  });
+  return newAttrs;
+}
+
+},{"./acyclic":2,"./add-border-segments":3,"./coordinate-system":4,"./graphlib":7,"./lodash":10,"./nesting-graph":11,"./normalize":12,"./order":17,"./parent-dummy-chains":22,"./position":24,"./rank":26,"./util":29}],10:[function(require,module,exports){
+/* global window */
+
+var lodash;
+
+if (typeof require === "function") {
+  try {
+    lodash = require("lodash");
+  } catch (e) {}
+}
+
+if (!lodash) {
+  lodash = window._;
+}
+
+module.exports = lodash;
+
+},{"lodash":51}],11:[function(require,module,exports){
+var _ = require("./lodash"),
+    util = require("./util");
+
+module.exports = {
+  run: run,
+  cleanup: cleanup
+};
+
+/*
+ * A nesting graph creates dummy nodes for the tops and bottoms of subgraphs,
+ * adds appropriate edges to ensure that all cluster nodes are placed between
+ * these boundries, and ensures that the graph is connected.
+ *
+ * In addition we ensure, through the use of the minlen property, that nodes
+ * and subgraph border nodes to not end up on the same rank.
+ *
+ * Preconditions:
+ *
+ *    1. Input graph is a DAG
+ *    2. Nodes in the input graph has a minlen attribute
+ *
+ * Postconditions:
+ *
+ *    1. Input graph is connected.
+ *    2. Dummy nodes are added for the tops and bottoms of subgraphs.
+ *    3. The minlen attribute for nodes is adjusted to ensure nodes do not
+ *       get placed on the same rank as subgraph border nodes.
+ *
+ * The nesting graph idea comes from Sander, "Layout of Compound Directed
+ * Graphs."
+ */
+function run(g) {
+  var root = util.addDummyNode(g, "root", {}, "_root"),
+      depths = treeDepths(g),
+      height = _.max(depths) - 1,
+      nodeSep = 2 * height + 1;
+
+  g.graph().nestingRoot = root;
+
+  // Multiply minlen by nodeSep to align nodes on non-border ranks.
+  _.each(g.edges(), function(e) { g.edge(e).minlen *= nodeSep; });
+
+  // Calculate a weight that is sufficient to keep subgraphs vertically compact
+  var weight = sumWeights(g) + 1;
+
+  // Create border nodes and link them up
+  _.each(g.children(), function(child) {
+    dfs(g, root, nodeSep, weight, height, depths, child);
+  });
+
+  // Save the multiplier for node layers for later removal of empty border
+  // layers.
+  g.graph().nodeRankFactor = nodeSep;
+}
+
+function dfs(g, root, nodeSep, weight, height, depths, v) {
+  var children = g.children(v);
+  if (!children.length) {
+    if (v !== root) {
+      g.setEdge(root, v, { weight: 0, minlen: nodeSep });
+    }
+    return;
+  }
+
+  var top = util.addBorderNode(g, "_bt"),
+      bottom = util.addBorderNode(g, "_bb"),
+      label = g.node(v);
+
+  g.setParent(top, v);
+  label.borderTop = top;
+  g.setParent(bottom, v);
+  label.borderBottom = bottom;
+
+  _.each(children, function(child) {
+    dfs(g, root, nodeSep, weight, height, depths, child);
+
+    var childNode = g.node(child),
+        childTop = childNode.borderTop ? childNode.borderTop : child,
+        childBottom = childNode.borderBottom ? childNode.borderBottom : child,
+        thisWeight = childNode.borderTop ? weight : 2 * weight,
+        minlen = childTop !== childBottom ? 1 : height - depths[v] + 1;
+
+    g.setEdge(top, childTop, {
+      weight: thisWeight,
+      minlen: minlen,
+      nestingEdge: true
+    });
+
+    g.setEdge(childBottom, bottom, {
+      weight: thisWeight,
+      minlen: minlen,
+      nestingEdge: true
+    });
+  });
+
+  if (!g.parent(v)) {
+    g.setEdge(root, top, { weight: 0, minlen: height + depths[v] });
+  }
+}
+
+function treeDepths(g) {
+  var depths = {};
+  function dfs(v, depth) {
+    var children = g.children(v);
+    if (children && children.length) {
+      _.each(children, function(child) {
+        dfs(child, depth + 1);
+      });
+    }
+    depths[v] = depth;
+  }
+  _.each(g.children(), function(v) { dfs(v, 1); });
+  return depths;
+}
+
+function sumWeights(g) {
+  return _.reduce(g.edges(), function(acc, e) {
+    return acc + g.edge(e).weight;
+  }, 0);
+}
+
+function cleanup(g) {
+  var graphLabel = g.graph();
+  g.removeNode(graphLabel.nestingRoot);
+  delete graphLabel.nestingRoot;
+  _.each(g.edges(), function(e) {
+    var edge = g.edge(e);
+    if (edge.nestingEdge) {
+      g.removeEdge(e);
+    }
+  });
+}
+
+},{"./lodash":10,"./util":29}],12:[function(require,module,exports){
+"use strict";
+
+var _ = require("./lodash"),
+    util = require("./util");
+
+module.exports = {
+  run: run,
+  undo: undo
+};
+
+/*
+ * Breaks any long edges in the graph into short segments that span 1 layer
+ * each. This operation is undoable with the denormalize function.
+ *
+ * Pre-conditions:
+ *
+ *    1. The input graph is a DAG.
+ *    2. Each node in the graph has a "rank" property.
+ *
+ * Post-condition:
+ *
+ *    1. All edges in the graph have a length of 1.
+ *    2. Dummy nodes are added where edges have been split into segments.
+ *    3. The graph is augmented with a "dummyChains" attribute which contains
+ *       the first dummy in each chain of dummy nodes produced.
+ */
+function run(g) {
+  g.graph().dummyChains = [];
+  _.each(g.edges(), function(edge) { normalizeEdge(g, edge); });
+}
+
+function normalizeEdge(g, e) {
+  var v = e.v,
+      vRank = g.node(v).rank,
+      w = e.w,
+      wRank = g.node(w).rank,
+      name = e.name,
+      edgeLabel = g.edge(e),
+      labelRank = edgeLabel.labelRank;
+
+  if (wRank === vRank + 1) return;
+
+  g.removeEdge(e);
+
+  var dummy, attrs, i;
+  for (i = 0, ++vRank; vRank < wRank; ++i, ++vRank) {
+    edgeLabel.points = [];
+    attrs = {
+      width: 0, height: 0,
+      edgeLabel: edgeLabel, edgeObj: e,
+      rank: vRank
+    };
+    dummy = util.addDummyNode(g, "edge", attrs, "_d");
+    if (vRank === labelRank) {
+      attrs.width = edgeLabel.width;
+      attrs.height = edgeLabel.height;
+      attrs.dummy = "edge-label";
+      attrs.labelpos = edgeLabel.labelpos;
+    }
+    g.setEdge(v, dummy, { weight: edgeLabel.weight }, name);
+    if (i === 0) {
+      g.graph().dummyChains.push(dummy);
+    }
+    v = dummy;
+  }
+
+  g.setEdge(v, w, { weight: edgeLabel.weight }, name);
+}
+
+function undo(g) {
+  _.each(g.graph().dummyChains, function(v) {
+    var node = g.node(v),
+        origLabel = node.edgeLabel,
+        w;
+    g.setEdge(node.edgeObj, origLabel);
+    while (node.dummy) {
+      w = g.successors(v)[0];
+      g.removeNode(v);
+      origLabel.points.push({ x: node.x, y: node.y });
+      if (node.dummy === "edge-label") {
+        origLabel.x = node.x;
+        origLabel.y = node.y;
+        origLabel.width = node.width;
+        origLabel.height = node.height;
+      }
+      v = w;
+      node = g.node(v);
+    }
+  });
+}
+
+},{"./lodash":10,"./util":29}],13:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = addSubgraphConstraints;
+
+function addSubgraphConstraints(g, cg, vs) {
+  var prev = {},
+      rootPrev;
+
+  _.each(vs, function(v) {
+    var child = g.parent(v),
+        parent,
+        prevChild;
+    while (child) {
+      parent = g.parent(child);
+      if (parent) {
+        prevChild = prev[parent];
+        prev[parent] = child;
+      } else {
+        prevChild = rootPrev;
+        rootPrev = child;
+      }
+      if (prevChild && prevChild !== child) {
+        cg.setEdge(prevChild, child);
+        return;
+      }
+      child = parent;
+    }
+  });
+
+  /*
+  function dfs(v) {
+    var children = v ? g.children(v) : g.children();
+    if (children.length) {
+      var min = Number.POSITIVE_INFINITY,
+          subgraphs = [];
+      _.each(children, function(child) {
+        var childMin = dfs(child);
+        if (g.children(child).length) {
+          subgraphs.push({ v: child, order: childMin });
+        }
+        min = Math.min(min, childMin);
+      });
+      _.reduce(_.sortBy(subgraphs, "order"), function(prev, curr) {
+        cg.setEdge(prev.v, curr.v);
+        return curr;
+      });
+      return min;
+    }
+    return g.node(v).order;
+  }
+  dfs(undefined);
+  */
+}
+
+},{"../lodash":10}],14:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = barycenter;
+
+function barycenter(g, movable) {
+  return _.map(movable, function(v) {
+    var inV = g.inEdges(v);
+    if (!inV.length) {
+      return { v: v };
+    } else {
+      var result = _.reduce(inV, function(acc, e) {
+        var edge = g.edge(e),
+            nodeU = g.node(e.v);
+        return {
+          sum: acc.sum + (edge.weight * nodeU.order),
+          weight: acc.weight + edge.weight
+        };
+      }, { sum: 0, weight: 0 });
+
+      return {
+        v: v,
+        barycenter: result.sum / result.weight,
+        weight: result.weight
+      };
+    }
+  });
+}
+
+
+},{"../lodash":10}],15:[function(require,module,exports){
+var _ = require("../lodash"),
+    Graph = require("../graphlib").Graph;
+
+module.exports = buildLayerGraph;
+
+/*
+ * Constructs a graph that can be used to sort a layer of nodes. The graph will
+ * contain all base and subgraph nodes from the request layer in their original
+ * hierarchy and any edges that are incident on these nodes and are of the type
+ * requested by the "relationship" parameter.
+ *
+ * Nodes from the requested rank that do not have parents are assigned a root
+ * node in the output graph, which is set in the root graph attribute. This
+ * makes it easy to walk the hierarchy of movable nodes during ordering.
+ *
+ * Pre-conditions:
+ *
+ *    1. Input graph is a DAG
+ *    2. Base nodes in the input graph have a rank attribute
+ *    3. Subgraph nodes in the input graph has minRank and maxRank attributes
+ *    4. Edges have an assigned weight
+ *
+ * Post-conditions:
+ *
+ *    1. Output graph has all nodes in the movable rank with preserved
+ *       hierarchy.
+ *    2. Root nodes in the movable layer are made children of the node
+ *       indicated by the root attribute of the graph.
+ *    3. Non-movable nodes incident on movable nodes, selected by the
+ *       relationship parameter, are included in the graph (without hierarchy).
+ *    4. Edges incident on movable nodes, selected by the relationship
+ *       parameter, are added to the output graph.
+ *    5. The weights for copied edges are aggregated as need, since the output
+ *       graph is not a multi-graph.
+ */
+function buildLayerGraph(g, rank, relationship) {
+  var root = createRootNode(g),
+      result = new Graph({ compound: true }).setGraph({ root: root })
+                  .setDefaultNodeLabel(function(v) { return g.node(v); });
+
+  _.each(g.nodes(), function(v) {
+    var node = g.node(v),
+        parent = g.parent(v);
+
+    if (node.rank === rank || node.minRank <= rank && rank <= node.maxRank) {
+      result.setNode(v);
+      result.setParent(v, parent || root);
+
+      // This assumes we have only short edges!
+      _.each(g[relationship](v), function(e) {
+        var u = e.v === v ? e.w : e.v,
+            edge = result.edge(u, v),
+            weight = !_.isUndefined(edge) ? edge.weight : 0;
+        result.setEdge(u, v, { weight: g.edge(e).weight + weight });
+      });
+
+      if (_.has(node, "minRank")) {
+        result.setNode(v, {
+          borderLeft: node.borderLeft[rank],
+          borderRight: node.borderRight[rank]
+        });
+      }
+    }
+  });
+
+  return result;
+}
+
+function createRootNode(g) {
+  var v;
+  while (g.hasNode((v = _.uniqueId("_root"))));
+  return v;
+}
+
+},{"../graphlib":7,"../lodash":10}],16:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash");
+
+module.exports = crossCount;
+
+/*
+ * A function that takes a layering (an array of layers, each with an array of
+ * ordererd nodes) and a graph and returns a weighted crossing count.
+ *
+ * Pre-conditions:
+ *
+ *    1. Input graph must be simple (not a multigraph), directed, and include
+ *       only simple edges.
+ *    2. Edges in the input graph must have assigned weights.
+ *
+ * Post-conditions:
+ *
+ *    1. The graph and layering matrix are left unchanged.
+ *
+ * This algorithm is derived from Barth, et al., "Bilayer Cross Counting."
+ */
+function crossCount(g, layering) {
+  var cc = 0;
+  for (var i = 1; i < layering.length; ++i) {
+    cc += twoLayerCrossCount(g, layering[i-1], layering[i]);
+  }
+  return cc;
+}
+
+function twoLayerCrossCount(g, northLayer, southLayer) {
+  // Sort all of the edges between the north and south layers by their position
+  // in the north layer and then the south. Map these edges to the position of
+  // their head in the south layer.
+  var southPos = _.zipObject(southLayer,
+                             _.map(southLayer, function (v, i) { return i; }));
+  var southEntries = _.flatten(_.map(northLayer, function(v) {
+    return _.chain(g.outEdges(v))
+            .map(function(e) {
+              return { pos: southPos[e.w], weight: g.edge(e).weight };
+            })
+            .sortBy("pos")
+            .value();
+  }), true);
+
+  // Build the accumulator tree
+  var firstIndex = 1;
+  while (firstIndex < southLayer.length) firstIndex <<= 1;
+  var treeSize = 2 * firstIndex - 1;
+  firstIndex -= 1;
+  var tree = _.map(new Array(treeSize), function() { return 0; });
+
+  // Calculate the weighted crossings
+  var cc = 0;
+  _.each(southEntries.forEach(function(entry) {
+    var index = entry.pos + firstIndex;
+    tree[index] += entry.weight;
+    var weightSum = 0;
+    while (index > 0) {
+      if (index % 2) {
+        weightSum += tree[index + 1];
+      }
+      index = (index - 1) >> 1;
+      tree[index] += entry.weight;
+    }
+    cc += entry.weight * weightSum;
+  }));
+
+  return cc;
+}
+
+},{"../lodash":10}],17:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash"),
+    initOrder = require("./init-order"),
+    crossCount = require("./cross-count"),
+    sortSubgraph = require("./sort-subgraph"),
+    buildLayerGraph = require("./build-layer-graph"),
+    addSubgraphConstraints = require("./add-subgraph-constraints"),
+    Graph = require("../graphlib").Graph,
+    util = require("../util");
+
+module.exports = order;
+
+/*
+ * Applies heuristics to minimize edge crossings in the graph and sets the best
+ * order solution as an order attribute on each node.
+ *
+ * Pre-conditions:
+ *
+ *    1. Graph must be DAG
+ *    2. Graph nodes must be objects with a "rank" attribute
+ *    3. Graph edges must have the "weight" attribute
+ *
+ * Post-conditions:
+ *
+ *    1. Graph nodes will have an "order" attribute based on the results of the
+ *       algorithm.
+ */
+function order(g) {
+  var maxRank = util.maxRank(g),
+      downLayerGraphs = buildLayerGraphs(g, _.range(1, maxRank + 1), "inEdges"),
+      upLayerGraphs = buildLayerGraphs(g, _.range(maxRank - 1, -1, -1), "outEdges");
+
+  var layering = initOrder(g);
+  assignOrder(g, layering);
+
+  var bestCC = Number.POSITIVE_INFINITY,
+      best;
+
+  for (var i = 0, lastBest = 0; lastBest < 4; ++i, ++lastBest) {
+    sweepLayerGraphs(i % 2 ? downLayerGraphs : upLayerGraphs, i % 4 >= 2);
+
+    layering = util.buildLayerMatrix(g);
+    var cc = crossCount(g, layering);
+    if (cc < bestCC) {
+      lastBest = 0;
+      best = _.cloneDeep(layering);
+      bestCC = cc;
+    }
+  }
+
+  assignOrder(g, best);
+}
+
+function buildLayerGraphs(g, ranks, relationship) {
+  return _.map(ranks, function(rank) {
+    return buildLayerGraph(g, rank, relationship);
+  });
+}
+
+function sweepLayerGraphs(layerGraphs, biasRight) {
+  var cg = new Graph();
+  _.each(layerGraphs, function(lg) {
+    var root = lg.graph().root;
+    var sorted = sortSubgraph(lg, root, cg, biasRight);
+    _.each(sorted.vs, function(v, i) {
+      lg.node(v).order = i;
+    });
+    addSubgraphConstraints(lg, cg, sorted.vs);
+  });
+}
+
+function assignOrder(g, layering) {
+  _.each(layering, function(layer) {
+    _.each(layer, function(v, i) {
+      g.node(v).order = i;
+    });
+  });
+}
+
+},{"../graphlib":7,"../lodash":10,"../util":29,"./add-subgraph-constraints":13,"./build-layer-graph":15,"./cross-count":16,"./init-order":18,"./sort-subgraph":20}],18:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash");
+
+module.exports = initOrder;
+
+/*
+ * Assigns an initial order value for each node by performing a DFS search
+ * starting from nodes in the first rank. Nodes are assigned an order in their
+ * rank as they are first visited.
+ *
+ * This approach comes from Gansner, et al., "A Technique for Drawing Directed
+ * Graphs."
+ *
+ * Returns a layering matrix with an array per layer and each layer sorted by
+ * the order of its nodes.
+ */
+function initOrder(g) {
+  var visited = {},
+      simpleNodes = _.filter(g.nodes(), function(v) {
+        return !g.children(v).length;
+      }),
+      maxRank = _.max(_.map(simpleNodes, function(v) { return g.node(v).rank; })),
+      layers = _.map(_.range(maxRank + 1), function() { return []; });
+
+  function dfs(v) {
+    if (_.has(visited, v)) return;
+    visited[v] = true;
+    var node = g.node(v);
+    layers[node.rank].push(v);
+    _.each(g.successors(v), dfs);
+  }
+
+  var orderedVs = _.sortBy(simpleNodes, function(v) { return g.node(v).rank; });
+  _.each(orderedVs, dfs);
+
+  return layers;
+}
+
+},{"../lodash":10}],19:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash");
+
+module.exports = resolveConflicts;
+
+/*
+ * Given a list of entries of the form {v, barycenter, weight} and a
+ * constraint graph this function will resolve any conflicts between the
+ * constraint graph and the barycenters for the entries. If the barycenters for
+ * an entry would violate a constraint in the constraint graph then we coalesce
+ * the nodes in the conflict into a new node that respects the contraint and
+ * aggregates barycenter and weight information.
+ *
+ * This implementation is based on the description in Forster, "A Fast and
+ * Simple Hueristic for Constrained Two-Level Crossing Reduction," thought it
+ * differs in some specific details.
+ *
+ * Pre-conditions:
+ *
+ *    1. Each entry has the form {v, barycenter, weight}, or if the node has
+ *       no barycenter, then {v}.
+ *
+ * Returns:
+ *
+ *    A new list of entries of the form {vs, i, barycenter, weight}. The list
+ *    `vs` may either be a singleton or it may be an aggregation of nodes
+ *    ordered such that they do not violate constraints from the constraint
+ *    graph. The property `i` is the lowest original index of any of the
+ *    elements in `vs`.
+ */
+function resolveConflicts(entries, cg) {
+  var mappedEntries = {};
+  _.each(entries, function(entry, i) {
+    var tmp = mappedEntries[entry.v] = {
+      indegree: 0,
+      "in": [],
+      out: [],
+      vs: [entry.v],
+      i: i
+    };
+    if (!_.isUndefined(entry.barycenter)) {
+      tmp.barycenter = entry.barycenter;
+      tmp.weight = entry.weight;
+    }
+  });
+
+  _.each(cg.edges(), function(e) {
+    var entryV = mappedEntries[e.v],
+        entryW = mappedEntries[e.w];
+    if (!_.isUndefined(entryV) && !_.isUndefined(entryW)) {
+      entryW.indegree++;
+      entryV.out.push(mappedEntries[e.w]);
+    }
+  });
+
+  var sourceSet = _.filter(mappedEntries, function(entry) {
+    return !entry.indegree;
+  });
+
+  return doResolveConflicts(sourceSet);
+}
+
+function doResolveConflicts(sourceSet) {
+  var entries = [];
+
+  function handleIn(vEntry) {
+    return function(uEntry) {
+      if (uEntry.merged) {
+        return;
+      }
+      if (_.isUndefined(uEntry.barycenter) ||
+          _.isUndefined(vEntry.barycenter) ||
+          uEntry.barycenter >= vEntry.barycenter) {
+        mergeEntries(vEntry, uEntry);
+      }
+    };
+  }
+
+  function handleOut(vEntry) {
+    return function(wEntry) {
+      wEntry["in"].push(vEntry);
+      if (--wEntry.indegree === 0) {
+        sourceSet.push(wEntry);
+      }
+    };
+  }
+
+  while (sourceSet.length) {
+    var entry = sourceSet.pop();
+    entries.push(entry);
+    _.each(entry["in"].reverse(), handleIn(entry));
+    _.each(entry.out, handleOut(entry));
+  }
+
+  return _.chain(entries)
+          .filter(function(entry) { return !entry.merged; })
+          .map(function(entry) {
+            return _.pick(entry, ["vs", "i", "barycenter", "weight"]);
+          })
+          .value();
+}
+
+function mergeEntries(target, source) {
+  var sum = 0,
+      weight = 0;
+
+  if (target.weight) {
+    sum += target.barycenter * target.weight;
+    weight += target.weight;
+  }
+
+  if (source.weight) {
+    sum += source.barycenter * source.weight;
+    weight += source.weight;
+  }
+
+  target.vs = source.vs.concat(target.vs);
+  target.barycenter = sum / weight;
+  target.weight = weight;
+  target.i = Math.min(source.i, target.i);
+  source.merged = true;
+}
+
+},{"../lodash":10}],20:[function(require,module,exports){
+var _ = require("../lodash"),
+    barycenter = require("./barycenter"),
+    resolveConflicts = require("./resolve-conflicts"),
+    sort = require("./sort");
+
+module.exports = sortSubgraph;
+
+function sortSubgraph(g, v, cg, biasRight) {
+  var movable = g.children(v),
+      node = g.node(v),
+      bl = node ? node.borderLeft : undefined,
+      br = node ? node.borderRight: undefined,
+      subgraphs = {};
+
+  if (bl) {
+    movable = _.filter(movable, function(w) {
+      return w !== bl && w !== br;
+    });
+  }
+
+  var barycenters = barycenter(g, movable);
+  _.each(barycenters, function(entry) {
+    if (g.children(entry.v).length) {
+      var subgraphResult = sortSubgraph(g, entry.v, cg, biasRight);
+      subgraphs[entry.v] = subgraphResult;
+      if (_.has(subgraphResult, "barycenter")) {
+        mergeBarycenters(entry, subgraphResult);
+      }
+    }
+  });
+
+  var entries = resolveConflicts(barycenters, cg);
+  expandSubgraphs(entries, subgraphs);
+
+  var result = sort(entries, biasRight);
+
+  if (bl) {
+    result.vs = _.flatten([bl, result.vs, br], true);
+    if (g.predecessors(bl).length) {
+      var blPred = g.node(g.predecessors(bl)[0]),
+          brPred = g.node(g.predecessors(br)[0]);
+      if (!_.has(result, "barycenter")) {
+        result.barycenter = 0;
+        result.weight = 0;
+      }
+      result.barycenter = (result.barycenter * result.weight +
+                           blPred.order + brPred.order) / (result.weight + 2);
+      result.weight += 2;
+    }
+  }
+
+  return result;
+}
+
+function expandSubgraphs(entries, subgraphs) {
+  _.each(entries, function(entry) {
+    entry.vs = _.flatten(entry.vs.map(function(v) {
+      if (subgraphs[v]) {
+        return subgraphs[v].vs;
+      }
+      return v;
+    }), true);
+  });
+}
+
+function mergeBarycenters(target, other) {
+  if (!_.isUndefined(target.barycenter)) {
+    target.barycenter = (target.barycenter * target.weight +
+                         other.barycenter * other.weight) /
+                        (target.weight + other.weight);
+    target.weight += other.weight;
+  } else {
+    target.barycenter = other.barycenter;
+    target.weight = other.weight;
+  }
+}
+
+},{"../lodash":10,"./barycenter":14,"./resolve-conflicts":19,"./sort":21}],21:[function(require,module,exports){
+var _ = require("../lodash"),
+    util = require("../util");
+
+module.exports = sort;
+
+function sort(entries, biasRight) {
+  var parts = util.partition(entries, function(entry) {
+    return _.has(entry, "barycenter");
+  });
+  var sortable = parts.lhs,
+      unsortable = _.sortBy(parts.rhs, function(entry) { return -entry.i; }),
+      vs = [],
+      sum = 0,
+      weight = 0,
+      vsIndex = 0;
+
+  sortable.sort(compareWithBias(!!biasRight));
+
+  vsIndex = consumeUnsortable(vs, unsortable, vsIndex);
+
+  _.each(sortable, function (entry) {
+    vsIndex += entry.vs.length;
+    vs.push(entry.vs);
+    sum += entry.barycenter * entry.weight;
+    weight += entry.weight;
+    vsIndex = consumeUnsortable(vs, unsortable, vsIndex);
+  });
+
+  var result = { vs: _.flatten(vs, true) };
+  if (weight) {
+    result.barycenter = sum / weight;
+    result.weight = weight;
+  }
+  return result;
+}
+
+function consumeUnsortable(vs, unsortable, index) {
+  var last;
+  while (unsortable.length && (last = _.last(unsortable)).i <= index) {
+    unsortable.pop();
+    vs.push(last.vs);
+    index++;
+  }
+  return index;
+}
+
+function compareWithBias(bias) {
+  return function(entryV, entryW) {
+    if (entryV.barycenter < entryW.barycenter) {
+      return -1;
+    } else if (entryV.barycenter > entryW.barycenter) {
+      return 1;
+    }
+
+    return !bias ? entryV.i - entryW.i : entryW.i - entryV.i;
+  };
+}
+
+},{"../lodash":10,"../util":29}],22:[function(require,module,exports){
+var _ = require("./lodash");
+
+module.exports = parentDummyChains;
+
+function parentDummyChains(g) {
+  var postorderNums = postorder(g);
+
+  _.each(g.graph().dummyChains, function(v) {
+    var node = g.node(v),
+        edgeObj = node.edgeObj,
+        pathData = findPath(g, postorderNums, edgeObj.v, edgeObj.w),
+        path = pathData.path,
+        lca = pathData.lca,
+        pathIdx = 0,
+        pathV = path[pathIdx],
+        ascending = true;
+
+    while (v !== edgeObj.w) {
+      node = g.node(v);
+
+      if (ascending) {
+        while ((pathV = path[pathIdx]) !== lca &&
+               g.node(pathV).maxRank < node.rank) {
+          pathIdx++;
+        }
+
+        if (pathV === lca) {
+          ascending = false;
+        }
+      }
+
+      if (!ascending) {
+        while (pathIdx < path.length - 1 &&
+               g.node(pathV = path[pathIdx + 1]).minRank <= node.rank) {
+          pathIdx++;
+        }
+        pathV = path[pathIdx];
+      }
+
+      g.setParent(v, pathV);
+      v = g.successors(v)[0];
+    }
+  });
+}
+
+// Find a path from v to w through the lowest common ancestor (LCA). Return the
+// full path and the LCA.
+function findPath(g, postorderNums, v, w) {
+  var vPath = [],
+      wPath = [],
+      low = Math.min(postorderNums[v].low, postorderNums[w].low),
+      lim = Math.max(postorderNums[v].lim, postorderNums[w].lim),
+      parent,
+      lca;
+
+  // Traverse up from v to find the LCA
+  parent = v;
+  do {
+    parent = g.parent(parent);
+    vPath.push(parent);
+  } while (parent &&
+           (postorderNums[parent].low > low || lim > postorderNums[parent].lim));
+  lca = parent;
+
+  // Traverse from w to LCA
+  parent = w;
+  while ((parent = g.parent(parent)) !== lca) {
+    wPath.push(parent);
+  }
+
+  return { path: vPath.concat(wPath.reverse()), lca: lca };
+}
+
+function postorder(g) {
+  var result = {},
+      lim = 0;
+
+  function dfs(v) {
+    var low = lim;
+    _.each(g.children(v), dfs);
+    result[v] = { low: low, lim: lim++ };
+  }
+  _.each(g.children(), dfs);
+
+  return result;
+}
+
+},{"./lodash":10}],23:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash"),
+    Graph = require("../graphlib").Graph,
+    util = require("../util");
+
+/*
+ * This module provides coordinate assignment based on Brandes and Köpf, "Fast
+ * and Simple Horizontal Coordinate Assignment."
+ */
+
+module.exports = {
+  positionX: positionX,
+  findType1Conflicts: findType1Conflicts,
+  findType2Conflicts: findType2Conflicts,
+  addConflict: addConflict,
+  hasConflict: hasConflict,
+  verticalAlignment: verticalAlignment,
+  horizontalCompaction: horizontalCompaction,
+  alignCoordinates: alignCoordinates,
+  findSmallestWidthAlignment: findSmallestWidthAlignment,
+  balance: balance
+};
+
+/*
+ * Marks all edges in the graph with a type-1 conflict with the "type1Conflict"
+ * property. A type-1 conflict is one where a non-inner segment crosses an
+ * inner segment. An inner segment is an edge with both incident nodes marked
+ * with the "dummy" property.
+ *
+ * This algorithm scans layer by layer, starting with the second, for type-1
+ * conflicts between the current layer and the previous layer. For each layer
+ * it scans the nodes from left to right until it reaches one that is incident
+ * on an inner segment. It then scans predecessors to determine if they have
+ * edges that cross that inner segment. At the end a final scan is done for all
+ * nodes on the current rank to see if they cross the last visited inner
+ * segment.
+ *
+ * This algorithm (safely) assumes that a dummy node will only be incident on a
+ * single node in the layers being scanned.
+ */
+function findType1Conflicts(g, layering) {
+  var conflicts = {};
+
+  function visitLayer(prevLayer, layer) {
+    var
+      // last visited node in the previous layer that is incident on an inner
+      // segment.
+      k0 = 0,
+      // Tracks the last node in this layer scanned for crossings with a type-1
+      // segment.
+      scanPos = 0,
+      prevLayerLength = prevLayer.length,
+      lastNode = _.last(layer);
+
+    _.each(layer, function(v, i) {
+      var w = findOtherInnerSegmentNode(g, v),
+          k1 = w ? g.node(w).order : prevLayerLength;
+
+      if (w || v === lastNode) {
+        _.each(layer.slice(scanPos, i +1), function(scanNode) {
+          _.each(g.predecessors(scanNode), function(u) {
+            var uLabel = g.node(u),
+                uPos = uLabel.order;
+            if ((uPos < k0 || k1 < uPos) &&
+                !(uLabel.dummy && g.node(scanNode).dummy)) {
+              addConflict(conflicts, u, scanNode);
+            }
+          });
+        });
+        scanPos = i + 1;
+        k0 = k1;
+      }
+    });
+
+    return layer;
+  }
+
+  _.reduce(layering, visitLayer);
+  return conflicts;
+}
+
+function findType2Conflicts(g, layering) {
+  var conflicts = {};
+
+  function scan(south, southPos, southEnd, prevNorthBorder, nextNorthBorder) {
+    var v;
+    _.each(_.range(southPos, southEnd), function(i) {
+      v = south[i];
+      if (g.node(v).dummy) {
+        _.each(g.predecessors(v), function(u) {
+          var uNode = g.node(u);
+          if (uNode.dummy &&
+              (uNode.order < prevNorthBorder || uNode.order > nextNorthBorder)) {
+            addConflict(conflicts, u, v);
+          }
+        });
+      }
+    });
+  }
+
+
+  function visitLayer(north, south) {
+    var prevNorthPos = -1,
+        nextNorthPos,
+        southPos = 0;
+
+    _.each(south, function(v, southLookahead) {
+      if (g.node(v).dummy === "border") {
+        var predecessors = g.predecessors(v);
+        if (predecessors.length) {
+          nextNorthPos = g.node(predecessors[0]).order;
+          scan(south, southPos, southLookahead, prevNorthPos, nextNorthPos);
+          southPos = southLookahead;
+          prevNorthPos = nextNorthPos;
+        }
+      }
+      scan(south, southPos, south.length, nextNorthPos, north.length);
+    });
+
+    return south;
+  }
+
+  _.reduce(layering, visitLayer);
+  return conflicts;
+}
+
+function findOtherInnerSegmentNode(g, v) {
+  if (g.node(v).dummy) {
+    return _.find(g.predecessors(v), function(u) {
+      return g.node(u).dummy;
+    });
+  }
+}
+
+function addConflict(conflicts, v, w) {
+  if (v > w) {
+    var tmp = v;
+    v = w;
+    w = tmp;
+  }
+
+  var conflictsV = conflicts[v];
+  if (!conflictsV) {
+    conflicts[v] = conflictsV = {};
+  }
+  conflictsV[w] = true;
+}
+
+function hasConflict(conflicts, v, w) {
+  if (v > w) {
+    var tmp = v;
+    v = w;
+    w = tmp;
+  }
+  return _.has(conflicts[v], w);
+}
+
+/*
+ * Try to align nodes into vertical "blocks" where possible. This algorithm
+ * attempts to align a node with one of its median neighbors. If the edge
+ * connecting a neighbor is a type-1 conflict then we ignore that possibility.
+ * If a previous node has already formed a block with a node after the node
+ * we're trying to form a block with, we also ignore that possibility - our
+ * blocks would be split in that scenario.
+ */
+function verticalAlignment(g, layering, conflicts, neighborFn) {
+  var root = {},
+      align = {},
+      pos = {};
+
+  // We cache the position here based on the layering because the graph and
+  // layering may be out of sync. The layering matrix is manipulated to
+  // generate different extreme alignments.
+  _.each(layering, function(layer) {
+    _.each(layer, function(v, order) {
+      root[v] = v;
+      align[v] = v;
+      pos[v] = order;
+    });
+  });
+
+  _.each(layering, function(layer) {
+    var prevIdx = -1;
+    _.each(layer, function(v) {
+      var ws = neighborFn(v);
+      if (ws.length) {
+        ws = _.sortBy(ws, function(w) { return pos[w]; });
+        var mp = (ws.length - 1) / 2;
+        for (var i = Math.floor(mp), il = Math.ceil(mp); i <= il; ++i) {
+          var w = ws[i];
+          if (align[v] === v &&
+              prevIdx < pos[w] &&
+              !hasConflict(conflicts, v, w)) {
+            align[w] = v;
+            align[v] = root[v] = root[w];
+            prevIdx = pos[w];
+          }
+        }
+      }
+    });
+  });
+
+  return { root: root, align: align };
+}
+
+function horizontalCompaction(g, layering, root, align, reverseSep) {
+  // This portion of the algorithm differs from BK due to a number of problems.
+  // Instead of their algorithm we construct a new block graph and do two
+  // sweeps. The first sweep places blocks with the smallest possible
+  // coordinates. The second sweep removes unused space by moving blocks to the
+  // greatest coordinates without violating separation.
+  var xs = {},
+      blockG = buildBlockGraph(g, layering, root, reverseSep);
+
+  // First pass, assign smallest coordinates via DFS
+  var visited = {};
+  function pass1(v) {
+    if (!_.has(visited, v)) {
+      visited[v] = true;
+      xs[v] = _.reduce(blockG.inEdges(v), function(max, e) {
+        pass1(e.v);
+        return Math.max(max, xs[e.v] + blockG.edge(e));
+      }, 0);
+    }
+  }
+  _.each(blockG.nodes(), pass1);
+
+  var borderType = reverseSep ? "borderLeft" : "borderRight";
+  function pass2(v) {
+    if (visited[v] !== 2) {
+      visited[v]++;
+      var node = g.node(v);
+      var min = _.reduce(blockG.outEdges(v), function(min, e) {
+        pass2(e.w);
+        return Math.min(min, xs[e.w] - blockG.edge(e));
+      }, Number.POSITIVE_INFINITY);
+      if (min !== Number.POSITIVE_INFINITY && node.borderType !== borderType) {
+        xs[v] = Math.max(xs[v], min);
+      }
+    }
+  }
+  _.each(blockG.nodes(), pass2);
+
+  // Assign x coordinates to all nodes
+  _.each(align, function(v) {
+    xs[v] = xs[root[v]];
+  });
+
+  return xs;
+}
+
+
+function buildBlockGraph(g, layering, root, reverseSep) {
+  var blockGraph = new Graph(),
+      graphLabel = g.graph(),
+      sepFn = sep(graphLabel.nodesep, graphLabel.edgesep, reverseSep);
+
+  _.each(layering, function(layer) {
+    var u;
+    _.each(layer, function(v) {
+      var vRoot = root[v];
+      blockGraph.setNode(vRoot);
+      if (u) {
+        var uRoot = root[u],
+            prevMax = blockGraph.edge(uRoot, vRoot);
+        blockGraph.setEdge(uRoot, vRoot, Math.max(sepFn(g, v, u), prevMax || 0));
+      }
+      u = v;
+    });
+  });
+
+  return blockGraph;
+}
+
+/*
+ * Returns the alignment that has the smallest width of the given alignments.
+ */
+function findSmallestWidthAlignment(g, xss) {
+  return _.min(xss, function(xs) {
+    var min = _.min(xs, function(x, v) { return x - width(g, v) / 2; }),
+        max = _.max(xs, function(x, v) { return x + width(g, v) / 2; });
+    return max - min;
+  });
+}
+
+/*
+ * Align the coordinates of each of the layout alignments such that
+ * left-biased alignments have their minimum coordinate at the same point as
+ * the minimum coordinate of the smallest width alignment and right-biased
+ * alignments have their maximum coordinate at the same point as the maximum
+ * coordinate of the smallest width alignment.
+ */
+function alignCoordinates(xss, alignTo) {
+  var alignToMin = _.min(alignTo),
+      alignToMax = _.max(alignTo);
+
+  _.each(["u", "d"], function(vert) {
+    _.each(["l", "r"], function(horiz) {
+      var alignment = vert + horiz,
+          xs = xss[alignment],
+          delta;
+      if (xs === alignTo) return;
+
+      delta = horiz === "l" ? alignToMin - _.min(xs) : alignToMax - _.max(xs);
+
+      if (delta) {
+        xss[alignment] = _.mapValues(xs, function(x) { return x + delta; });
+      }
+    });
+  });
+}
+
+function balance(xss, align) {
+  return _.mapValues(xss.ul, function(ignore, v) {
+    if (align) {
+      return xss[align.toLowerCase()][v];
+    } else {
+      var xs = _.sortBy(_.pluck(xss, v));
+      return (xs[1] + xs[2]) / 2;
+    }
+  });
+}
+
+function positionX(g) {
+  var layering = util.buildLayerMatrix(g),
+      conflicts = _.merge(findType1Conflicts(g, layering),
+                          findType2Conflicts(g, layering));
+
+  var xss = {},
+      adjustedLayering;
+  _.each(["u", "d"], function(vert) {
+    adjustedLayering = vert === "u" ? layering : _.values(layering).reverse();
+    _.each(["l", "r"], function(horiz) {
+      if (horiz === "r") {
+        adjustedLayering = _.map(adjustedLayering, function(inner) {
+          return _.values(inner).reverse();
+        });
+      }
+
+      var neighborFn = _.bind(vert === "u" ? g.predecessors : g.successors, g);
+      var align = verticalAlignment(g, adjustedLayering, conflicts, neighborFn);
+      var xs = horizontalCompaction(g, adjustedLayering,
+                                    align.root, align.align,
+                                    horiz === "r");
+      if (horiz === "r") {
+        xs = _.mapValues(xs, function(x) { return -x; });
+      }
+      xss[vert + horiz] = xs;
+    });
+  });
+
+  var smallestWidth = findSmallestWidthAlignment(g, xss);
+  alignCoordinates(xss, smallestWidth);
+  return balance(xss, g.graph().align);
+}
+
+function sep(nodeSep, edgeSep, reverseSep) {
+  return function(g, v, w) {
+    var vLabel = g.node(v),
+        wLabel = g.node(w),
+        sum = 0,
+        delta;
+
+    sum += vLabel.width / 2;
+    if (_.has(vLabel, "labelpos")) {
+      switch (vLabel.labelpos.toLowerCase()) {
+        case "l": delta = -vLabel.width / 2; break;
+        case "r": delta = vLabel.width / 2; break;
+      }
+    }
+    if (delta) {
+      sum += reverseSep ? delta : -delta;
+    }
+    delta = 0;
+
+    sum += (vLabel.dummy ? edgeSep : nodeSep) / 2;
+    sum += (wLabel.dummy ? edgeSep : nodeSep) / 2;
+
+    sum += wLabel.width / 2;
+    if (_.has(wLabel, "labelpos")) {
+      switch (wLabel.labelpos.toLowerCase()) {
+        case "l": delta = wLabel.width / 2; break;
+        case "r": delta = -wLabel.width / 2; break;
+      }
+    }
+    if (delta) {
+      sum += reverseSep ? delta : -delta;
+    }
+    delta = 0;
+
+    return sum;
+  };
+}
+
+function width(g, v) {
+  return g.node(v).width;
+}
+
+},{"../graphlib":7,"../lodash":10,"../util":29}],24:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash"),
+    util = require("../util"),
+    positionX = require("./bk").positionX;
+
+module.exports = position;
+
+function position(g) {
+  g = util.asNonCompoundGraph(g);
+
+  positionY(g);
+  _.each(positionX(g), function(x, v) {
+    g.node(v).x = x;
+  });
+}
+
+function positionY(g) {
+  var layering = util.buildLayerMatrix(g),
+      rankSep = g.graph().ranksep,
+      prevY = 0;
+  _.each(layering, function(layer) {
+    var maxHeight = _.max(_.map(layer, function(v) { return g.node(v).height; }));
+    _.each(layer, function(v) {
+      g.node(v).y = prevY + maxHeight / 2;
+    });
+    prevY += maxHeight + rankSep;
+  });
+}
+
+
+},{"../lodash":10,"../util":29,"./bk":23}],25:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash"),
+    Graph = require("../graphlib").Graph,
+    slack = require("./util").slack;
+
+module.exports = feasibleTree;
+
+/*
+ * Constructs a spanning tree with tight edges and adjusted the input node's
+ * ranks to achieve this. A tight edge is one that is has a length that matches
+ * its "minlen" attribute.
+ *
+ * The basic structure for this function is derived from Gansner, et al., "A
+ * Technique for Drawing Directed Graphs."
+ *
+ * Pre-conditions:
+ *
+ *    1. Graph must be a DAG.
+ *    2. Graph must be connected.
+ *    3. Graph must have at least one node.
+ *    5. Graph nodes must have been previously assigned a "rank" property that
+ *       respects the "minlen" property of incident edges.
+ *    6. Graph edges must have a "minlen" property.
+ *
+ * Post-conditions:
+ *
+ *    - Graph nodes will have their rank adjusted to ensure that all edges are
+ *      tight.
+ *
+ * Returns a tree (undirected graph) that is constructed using only "tight"
+ * edges.
+ */
+function feasibleTree(g) {
+  var t = new Graph({ directed: false });
+
+  // Choose arbitrary node from which to start our tree
+  var start = g.nodes()[0],
+      size = g.nodeCount();
+  t.setNode(start, {});
+
+  var edge, delta;
+  while (tightTree(t, g) < size) {
+    edge = findMinSlackEdge(t, g);
+    delta = t.hasNode(edge.v) ? slack(g, edge) : -slack(g, edge);
+    shiftRanks(t, g, delta);
+  }
+
+  return t;
+}
+
+/*
+ * Finds a maximal tree of tight edges and returns the number of nodes in the
+ * tree.
+ */
+function tightTree(t, g) {
+  function dfs(v) {
+    _.each(g.nodeEdges(v), function(e) {
+      var edgeV = e.v,
+          w = (v === edgeV) ? e.w : edgeV;
+      if (!t.hasNode(w) && !slack(g, e)) {
+        t.setNode(w, {});
+        t.setEdge(v, w, {});
+        dfs(w);
+      }
+    });
+  }
+
+  _.each(t.nodes(), dfs);
+  return t.nodeCount();
+}
+
+/*
+ * Finds the edge with the smallest slack that is incident on tree and returns
+ * it.
+ */
+function findMinSlackEdge(t, g) {
+  return _.min(g.edges(), function(e) {
+    if (t.hasNode(e.v) !== t.hasNode(e.w)) {
+      return slack(g, e);
+    }
+  });
+}
+
+function shiftRanks(t, g, delta) {
+  _.each(t.nodes(), function(v) {
+    g.node(v).rank += delta;
+  });
+}
+
+},{"../graphlib":7,"../lodash":10,"./util":28}],26:[function(require,module,exports){
+"use strict";
+
+var rankUtil = require("./util"),
+    longestPath = rankUtil.longestPath,
+    feasibleTree = require("./feasible-tree"),
+    networkSimplex = require("./network-simplex");
+
+module.exports = rank;
+
+/*
+ * Assigns a rank to each node in the input graph that respects the "minlen"
+ * constraint specified on edges between nodes.
+ *
+ * This basic structure is derived from Gansner, et al., "A Technique for
+ * Drawing Directed Graphs."
+ *
+ * Pre-conditions:
+ *
+ *    1. Graph must be a connected DAG
+ *    2. Graph nodes must be objects
+ *    3. Graph edges must have "weight" and "minlen" attributes
+ *
+ * Post-conditions:
+ *
+ *    1. Graph nodes will have a "rank" attribute based on the results of the
+ *       algorithm. Ranks can start at any index (including negative), we'll
+ *       fix them up later.
+ */
+function rank(g) {
+  switch(g.graph().ranker) {
+    case "network-simplex": networkSimplexRanker(g); break;
+    case "tight-tree": tightTreeRanker(g); break;
+    case "longest-path": longestPathRanker(g); break;
+    default: networkSimplexRanker(g);
+  }
+}
+
+// A fast and simple ranker, but results are far from optimal.
+var longestPathRanker = longestPath;
+
+function tightTreeRanker(g) {
+  longestPath(g);
+  feasibleTree(g);
+}
+
+function networkSimplexRanker(g) {
+  networkSimplex(g);
+}
+
+},{"./feasible-tree":25,"./network-simplex":27,"./util":28}],27:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash"),
+    feasibleTree = require("./feasible-tree"),
+    slack = require("./util").slack,
+    initRank = require("./util").longestPath,
+    preorder = require("../graphlib").alg.preorder,
+    postorder = require("../graphlib").alg.postorder,
+    simplify = require("../util").simplify;
+
+module.exports = networkSimplex;
+
+// Expose some internals for testing purposes
+networkSimplex.initLowLimValues = initLowLimValues;
+networkSimplex.initCutValues = initCutValues;
+networkSimplex.calcCutValue = calcCutValue;
+networkSimplex.leaveEdge = leaveEdge;
+networkSimplex.enterEdge = enterEdge;
+networkSimplex.exchangeEdges = exchangeEdges;
+
+/*
+ * The network simplex algorithm assigns ranks to each node in the input graph
+ * and iteratively improves the ranking to reduce the length of edges.
+ *
+ * Preconditions:
+ *
+ *    1. The input graph must be a DAG.
+ *    2. All nodes in the graph must have an object value.
+ *    3. All edges in the graph must have "minlen" and "weight" attributes.
+ *
+ * Postconditions:
+ *
+ *    1. All nodes in the graph will have an assigned "rank" attribute that has
+ *       been optimized by the network simplex algorithm. Ranks start at 0.
+ *
+ *
+ * A rough sketch of the algorithm is as follows:
+ *
+ *    1. Assign initial ranks to each node. We use the longest path algorithm,
+ *       which assigns ranks to the lowest position possible. In general this
+ *       leads to very wide bottom ranks and unnecessarily long edges.
+ *    2. Construct a feasible tight tree. A tight tree is one such that all
+ *       edges in the tree have no slack (difference between length of edge
+ *       and minlen for the edge). This by itself greatly improves the assigned
+ *       rankings by shorting edges.
+ *    3. Iteratively find edges that have negative cut values. Generally a
+ *       negative cut value indicates that the edge could be removed and a new
+ *       tree edge could be added to produce a more compact graph.
+ *
+ * Much of the algorithms here are derived from Gansner, et al., "A Technique
+ * for Drawing Directed Graphs." The structure of the file roughly follows the
+ * structure of the overall algorithm.
+ */
+function networkSimplex(g) {
+  g = simplify(g);
+  initRank(g);
+  var t = feasibleTree(g);
+  initLowLimValues(t);
+  initCutValues(t, g);
+
+  var e, f;
+  while ((e = leaveEdge(t))) {
+    f = enterEdge(t, g, e);
+    exchangeEdges(t, g, e, f);
+  }
+}
+
+/*
+ * Initializes cut values for all edges in the tree.
+ */
+function initCutValues(t, g) {
+  var vs = postorder(t, t.nodes());
+  vs = vs.slice(0, vs.length - 1);
+  _.each(vs, function(v) {
+    assignCutValue(t, g, v);
+  });
+}
+
+function assignCutValue(t, g, child) {
+  var childLab = t.node(child),
+      parent = childLab.parent;
+  t.edge(child, parent).cutvalue = calcCutValue(t, g, child);
+}
+
+/*
+ * Given the tight tree, its graph, and a child in the graph calculate and
+ * return the cut value for the edge between the child and its parent.
+ */
+function calcCutValue(t, g, child) {
+  var childLab = t.node(child),
+      parent = childLab.parent,
+      // True if the child is on the tail end of the edge in the directed graph
+      childIsTail = true,
+      // The graph's view of the tree edge we're inspecting
+      graphEdge = g.edge(child, parent),
+      // The accumulated cut value for the edge between this node and its parent
+      cutValue = 0;
+
+  if (!graphEdge) {
+    childIsTail = false;
+    graphEdge = g.edge(parent, child);
+  }
+
+  cutValue = graphEdge.weight;
+
+  _.each(g.nodeEdges(child), function(e) {
+    var isOutEdge = e.v === child,
+        other = isOutEdge ? e.w : e.v;
+
+    if (other !== parent) {
+      var pointsToHead = isOutEdge === childIsTail,
+          otherWeight = g.edge(e).weight;
+
+      cutValue += pointsToHead ? otherWeight : -otherWeight;
+      if (isTreeEdge(t, child, other)) {
+        var otherCutValue = t.edge(child, other).cutvalue;
+        cutValue += pointsToHead ? -otherCutValue : otherCutValue;
+      }
+    }
+  });
+
+  return cutValue;
+}
+
+function initLowLimValues(tree, root) {
+  if (arguments.length < 2) {
+    root = tree.nodes()[0];
+  }
+  dfsAssignLowLim(tree, {}, 1, root);
+}
+
+function dfsAssignLowLim(tree, visited, nextLim, v, parent) {
+  var low = nextLim,
+      label = tree.node(v);
+
+  visited[v] = true;
+  _.each(tree.neighbors(v), function(w) {
+    if (!_.has(visited, w)) {
+      nextLim = dfsAssignLowLim(tree, visited, nextLim, w, v);
+    }
+  });
+
+  label.low = low;
+  label.lim = nextLim++;
+  if (parent) {
+    label.parent = parent;
+  } else {
+    // TODO should be able to remove this when we incrementally update low lim
+    delete label.parent;
+  }
+
+  return nextLim;
+}
+
+function leaveEdge(tree) {
+  return _.find(tree.edges(), function(e) {
+    return tree.edge(e).cutvalue < 0;
+  });
+}
+
+function enterEdge(t, g, edge) {
+  var v = edge.v,
+      w = edge.w;
+
+  // For the rest of this function we assume that v is the tail and w is the
+  // head, so if we don't have this edge in the graph we should flip it to
+  // match the correct orientation.
+  if (!g.hasEdge(v, w)) {
+    v = edge.w;
+    w = edge.v;
+  }
+
+  var vLabel = t.node(v),
+      wLabel = t.node(w),
+      tailLabel = vLabel,
+      flip = false;
+
+  // If the root is in the tail of the edge then we need to flip the logic that
+  // checks for the head and tail nodes in the candidates function below.
+  if (vLabel.lim > wLabel.lim) {
+    tailLabel = wLabel;
+    flip = true;
+  }
+
+  var candidates = _.filter(g.edges(), function(edge) {
+    return flip === isDescendant(t, t.node(edge.v), tailLabel) &&
+           flip !== isDescendant(t, t.node(edge.w), tailLabel);
+  });
+
+  return _.min(candidates, function(edge) { return slack(g, edge); });
+}
+
+function exchangeEdges(t, g, e, f) {
+  var v = e.v,
+      w = e.w;
+  t.removeEdge(v, w);
+  t.setEdge(f.v, f.w, {});
+  initLowLimValues(t);
+  initCutValues(t, g);
+  updateRanks(t, g);
+}
+
+function updateRanks(t, g) {
+  var root = _.find(t.nodes(), function(v) { return !g.node(v).parent; }),
+      vs = preorder(t, root);
+  vs = vs.slice(1);
+  _.each(vs, function(v) {
+    var parent = t.node(v).parent,
+        edge = g.edge(v, parent),
+        flipped = false;
+
+    if (!edge) {
+      edge = g.edge(parent, v);
+      flipped = true;
+    }
+
+    g.node(v).rank = g.node(parent).rank + (flipped ? edge.minlen : -edge.minlen);
+  });
+}
+
+/*
+ * Returns true if the edge is in the tree.
+ */
+function isTreeEdge(tree, u, v) {
+  return tree.hasEdge(u, v);
+}
+
+/*
+ * Returns true if the specified node is descendant of the root node per the
+ * assigned low and lim attributes in the tree.
+ */
+function isDescendant(tree, vLabel, rootLabel) {
+  return rootLabel.low <= vLabel.lim && vLabel.lim <= rootLabel.lim;
+}
+
+},{"../graphlib":7,"../lodash":10,"../util":29,"./feasible-tree":25,"./util":28}],28:[function(require,module,exports){
+"use strict";
+
+var _ = require("../lodash");
+
+module.exports = {
+  longestPath: longestPath,
+  slack: slack
+};
+
+/*
+ * Initializes ranks for the input graph using the longest path algorithm. This
+ * algorithm scales well and is fast in practice, it yields rather poor
+ * solutions. Nodes are pushed to the lowest layer possible, leaving the bottom
+ * ranks wide and leaving edges longer than necessary. However, due to its
+ * speed, this algorithm is good for getting an initial ranking that can be fed
+ * into other algorithms.
+ *
+ * This algorithm does not normalize layers because it will be used by other
+ * algorithms in most cases. If using this algorithm directly, be sure to
+ * run normalize at the end.
+ *
+ * Pre-conditions:
+ *
+ *    1. Input graph is a DAG.
+ *    2. Input graph node labels can be assigned properties.
+ *
+ * Post-conditions:
+ *
+ *    1. Each node will be assign an (unnormalized) "rank" property.
+ */
+function longestPath(g) {
+  var visited = {};
+
+  function dfs(v) {
+    var label = g.node(v);
+    if (_.has(visited, v)) {
+      return label.rank;
+    }
+    visited[v] = true;
+
+    var rank = _.min(_.map(g.outEdges(v), function(e) {
+      return dfs(e.w) - g.edge(e).minlen;
+    }));
+
+    if (rank === Number.POSITIVE_INFINITY) {
+      rank = 0;
+    }
+
+    return (label.rank = rank);
+  }
+
+  _.each(g.sources(), dfs);
+}
+
+/*
+ * Returns the amount of slack for the given edge. The slack is defined as the
+ * difference between the length of the edge and its minimum length.
+ */
+function slack(g, e) {
+  return g.node(e.w).rank - g.node(e.v).rank - g.edge(e).minlen;
+}
+
+},{"../lodash":10}],29:[function(require,module,exports){
+"use strict";
+
+var _ = require("./lodash"),
+    Graph = require("./graphlib").Graph;
+
+module.exports = {
+  addDummyNode: addDummyNode,
+  simplify: simplify,
+  asNonCompoundGraph: asNonCompoundGraph,
+  successorWeights: successorWeights,
+  predecessorWeights: predecessorWeights,
+  intersectRect: intersectRect,
+  buildLayerMatrix: buildLayerMatrix,
+  normalizeRanks: normalizeRanks,
+  removeEmptyRanks: removeEmptyRanks,
+  addBorderNode: addBorderNode,
+  maxRank: maxRank,
+  partition: partition,
+  time: time,
+  notime: notime
+};
+
+/*
+ * Adds a dummy node to the graph and return v.
+ */
+function addDummyNode(g, type, attrs, name) {
+  var v;
+  do {
+    v = _.uniqueId(name);
+  } while (g.hasNode(v));
+
+  attrs.dummy = type;
+  g.setNode(v, attrs);
+  return v;
+}
+
+/*
+ * Returns a new graph with only simple edges. Handles aggregation of data
+ * associated with multi-edges.
+ */
+function simplify(g) {
+  var simplified = new Graph().setGraph(g.graph());
+  _.each(g.nodes(), function(v) { simplified.setNode(v, g.node(v)); });
+  _.each(g.edges(), function(e) {
+    var simpleLabel = simplified.edge(e.v, e.w) || { weight: 0, minlen: 1 },
+        label = g.edge(e);
+    simplified.setEdge(e.v, e.w, {
+      weight: simpleLabel.weight + label.weight,
+      minlen: Math.max(simpleLabel.minlen, label.minlen)
+    });
+  });
+  return simplified;
+}
+
+function asNonCompoundGraph(g) {
+  var simplified = new Graph({ multigraph: g.isMultigraph() }).setGraph(g.graph());
+  _.each(g.nodes(), function(v) {
+    if (!g.children(v).length) {
+      simplified.setNode(v, g.node(v));
+    }
+  });
+  _.each(g.edges(), function(e) {
+    simplified.setEdge(e, g.edge(e));
+  });
+  return simplified;
+}
+
+function successorWeights(g) {
+  var weightMap = _.map(g.nodes(), function(v) {
+    var sucs = {};
+    _.each(g.outEdges(v), function(e) {
+      sucs[e.w] = (sucs[e.w] || 0) + g.edge(e).weight;
+    });
+    return sucs;
+  });
+  return _.zipObject(g.nodes(), weightMap);
+}
+
+function predecessorWeights(g) {
+  var weightMap = _.map(g.nodes(), function(v) {
+    var preds = {};
+    _.each(g.inEdges(v), function(e) {
+      preds[e.v] = (preds[e.v] || 0) + g.edge(e).weight;
+    });
+    return preds;
+  });
+  return _.zipObject(g.nodes(), weightMap);
+}
+
+/*
+ * Finds where a line starting at point ({x, y}) would intersect a rectangle
+ * ({x, y, width, height}) if it were pointing at the rectangle's center.
+ */
+function intersectRect(rect, point) {
+  var x = rect.x;
+  var y = rect.y;
+
+  // Rectangle intersection algorithm from:
+  // http://math.stackexchange.com/questions/108113/find-edge-between-two-boxes
+  var dx = point.x - x;
+  var dy = point.y - y;
+  var w = rect.width / 2;
+  var h = rect.height / 2;
+
+  if (!dx && !dy) {
+    throw new Error("Not possible to find intersection inside of the rectangle");
+  }
+
+  var sx, sy;
+  if (Math.abs(dy) * w > Math.abs(dx) * h) {
+    // Intersection is top or bottom of rect.
+    if (dy < 0) {
+      h = -h;
+    }
+    sx = h * dx / dy;
+    sy = h;
+  } else {
+    // Intersection is left or right of rect.
+    if (dx < 0) {
+      w = -w;
+    }
+    sx = w;
+    sy = w * dy / dx;
+  }
+
+  return { x: x + sx, y: y + sy };
+}
+
+/*
+ * Given a DAG with each node assigned "rank" and "order" properties, this
+ * function will produce a matrix with the ids of each node.
+ */
+function buildLayerMatrix(g) {
+  var layering = _.map(_.range(maxRank(g) + 1), function() { return []; });
+  _.each(g.nodes(), function(v) {
+    var node = g.node(v),
+        rank = node.rank;
+    if (!_.isUndefined(rank)) {
+      layering[rank][node.order] = v;
+    }
+  });
+  return layering;
+}
+
+/*
+ * Adjusts the ranks for all nodes in the graph such that all nodes v have
+ * rank(v) >= 0 and at least one node w has rank(w) = 0.
+ */
+function normalizeRanks(g) {
+  var min = _.min(_.map(g.nodes(), function(v) { return g.node(v).rank; }));
+  _.each(g.nodes(), function(v) {
+    var node = g.node(v);
+    if (_.has(node, "rank")) {
+      node.rank -= min;
+    }
+  });
+}
+
+function removeEmptyRanks(g) {
+  // Ranks may not start at 0, so we need to offset them
+  var offset = _.min(_.map(g.nodes(), function(v) { return g.node(v).rank; }));
+
+  var layers = [];
+  _.each(g.nodes(), function(v) {
+    var rank = g.node(v).rank - offset;
+    if (!layers[rank]) {
+      layers[rank] = [];
+    }
+    layers[rank].push(v);
+  });
+
+  var delta = 0,
+      nodeRankFactor = g.graph().nodeRankFactor;
+  _.each(layers, function(vs, i) {
+    if (_.isUndefined(vs) && i % nodeRankFactor !== 0) {
+      --delta;
+    } else if (delta) {
+      _.each(vs, function(v) { g.node(v).rank += delta; });
+    }
+  });
+}
+
+function addBorderNode(g, prefix, rank, order) {
+  var node = {
+    width: 0,
+    height: 0
+  };
+  if (arguments.length >= 4) {
+    node.rank = rank;
+    node.order = order;
+  }
+  return addDummyNode(g, "border", node, prefix);
+}
+
+function maxRank(g) {
+  return _.max(_.map(g.nodes(), function(v) {
+    var rank = g.node(v).rank;
+    if (!_.isUndefined(rank)) {
+      return rank;
+    }
+  }));
+}
+
+/*
+ * Partition a collection into two groups: `lhs` and `rhs`. If the supplied
+ * function returns true for an entry it goes into `lhs`. Otherwise it goes
+ * into `rhs.
+ */
+function partition(collection, fn) {
+  var result = { lhs: [], rhs: [] };
+  _.each(collection, function(value) {
+    if (fn(value)) {
+      result.lhs.push(value);
+    } else {
+      result.rhs.push(value);
+    }
+  });
+  return result;
+}
+
+/*
+ * Returns a new function that wraps `fn` with a timer. The wrapper logs the
+ * time it takes to execute the function.
+ */
+function time(name, fn) {
+  var start = _.now();
+  try {
+    return fn();
+  } finally {
+    console.log(name + " time: " + (_.now() - start) + "ms");
+  }
+}
+
+function notime(name, fn) {
+  return fn();
+}
+
+},{"./graphlib":7,"./lodash":10}],30:[function(require,module,exports){
+module.exports = "0.7.4";
+
+},{}],31:[function(require,module,exports){
+/**
+ * Copyright (c) 2014, Chris Pettitt
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * 3. Neither the name of the copyright holder nor the names of its contributors
+ * may be used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+var lib = require("./lib");
+
+module.exports = {
+  Graph: lib.Graph,
+  json: require("./lib/json"),
+  alg: require("./lib/alg"),
+  version: lib.version
+};
+
+},{"./lib":47,"./lib/alg":38,"./lib/json":48}],32:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = components;
+
+function components(g) {
+  var visited = {},
+      cmpts = [],
+      cmpt;
+
+  function dfs(v) {
+    if (_.has(visited, v)) return;
+    visited[v] = true;
+    cmpt.push(v);
+    _.each(g.successors(v), dfs);
+    _.each(g.predecessors(v), dfs);
+  }
+
+  _.each(g.nodes(), function(v) {
+    cmpt = [];
+    dfs(v);
+    if (cmpt.length) {
+      cmpts.push(cmpt);
+    }
+  });
+
+  return cmpts;
+}
+
+},{"../lodash":49}],33:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = dfs;
+
+/*
+ * A helper that preforms a pre- or post-order traversal on the input graph
+ * and returns the nodes in the order they were visited. This algorithm treats
+ * the input as undirected.
+ *
+ * Order must be one of "pre" or "post".
+ */
+function dfs(g, vs, order) {
+  if (!_.isArray(vs)) {
+    vs = [vs];
+  }
+
+  var acc = [],
+      visited = {};
+  _.each(vs, function(v) {
+    if (!g.hasNode(v)) {
+      throw new Error("Graph does not have node: " + v);
+    }
+
+    doDfs(g, v, order === "post", visited, acc);
+  });
+  return acc;
+}
+
+function doDfs(g, v, postorder, visited, acc) {
+  if (!_.has(visited, v)) {
+    visited[v] = true;
+
+    if (!postorder) { acc.push(v); }
+    _.each(g.neighbors(v), function(w) {
+      doDfs(g, w, postorder, visited, acc);
+    });
+    if (postorder) { acc.push(v); }
+  }
+}
+
+},{"../lodash":49}],34:[function(require,module,exports){
+var dijkstra = require("./dijkstra"),
+    _ = require("../lodash");
+
+module.exports = dijkstraAll;
+
+function dijkstraAll(g, weightFunc, edgeFunc) {
+  return _.transform(g.nodes(), function(acc, v) {
+    acc[v] = dijkstra(g, v, weightFunc, edgeFunc);
+  }, {});
+}
+
+},{"../lodash":49,"./dijkstra":35}],35:[function(require,module,exports){
+var _ = require("../lodash"),
+    PriorityQueue = require("../data/priority-queue");
+
+module.exports = dijkstra;
+
+var DEFAULT_WEIGHT_FUNC = _.constant(1);
+
+function dijkstra(g, source, weightFn, edgeFn) {
+  return runDijkstra(g, String(source),
+                     weightFn || DEFAULT_WEIGHT_FUNC,
+                     edgeFn || function(v) { return g.outEdges(v); });
+}
+
+function runDijkstra(g, source, weightFn, edgeFn) {
+  var results = {},
+      pq = new PriorityQueue(),
+      v, vEntry;
+
+  var updateNeighbors = function(edge) {
+    var w = edge.v !== v ? edge.v : edge.w,
+        wEntry = results[w],
+        weight = weightFn(edge),
+        distance = vEntry.distance + weight;
+
+    if (weight < 0) {
+      throw new Error("dijkstra does not allow negative edge weights. " +
+                      "Bad edge: " + edge + " Weight: " + weight);
+    }
+
+    if (distance < wEntry.distance) {
+      wEntry.distance = distance;
+      wEntry.predecessor = v;
+      pq.decrease(w, distance);
+    }
+  };
+
+  g.nodes().forEach(function(v) {
+    var distance = v === source ? 0 : Number.POSITIVE_INFINITY;
+    results[v] = { distance: distance };
+    pq.add(v, distance);
+  });
+
+  while (pq.size() > 0) {
+    v = pq.removeMin();
+    vEntry = results[v];
+    if (vEntry.distance === Number.POSITIVE_INFINITY) {
+      break;
+    }
+
+    edgeFn(v).forEach(updateNeighbors);
+  }
+
+  return results;
+}
+
+},{"../data/priority-queue":45,"../lodash":49}],36:[function(require,module,exports){
+var _ = require("../lodash"),
+    tarjan = require("./tarjan");
+
+module.exports = findCycles;
+
+function findCycles(g) {
+  return _.filter(tarjan(g), function(cmpt) {
+    return cmpt.length > 1 || (cmpt.length === 1 && g.hasEdge(cmpt[0], cmpt[0]));
+  });
+}
+
+},{"../lodash":49,"./tarjan":43}],37:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = floydWarshall;
+
+var DEFAULT_WEIGHT_FUNC = _.constant(1);
+
+function floydWarshall(g, weightFn, edgeFn) {
+  return runFloydWarshall(g,
+                          weightFn || DEFAULT_WEIGHT_FUNC,
+                          edgeFn || function(v) { return g.outEdges(v); });
+}
+
+function runFloydWarshall(g, weightFn, edgeFn) {
+  var results = {},
+      nodes = g.nodes();
+
+  nodes.forEach(function(v) {
+    results[v] = {};
+    results[v][v] = { distance: 0 };
+    nodes.forEach(function(w) {
+      if (v !== w) {
+        results[v][w] = { distance: Number.POSITIVE_INFINITY };
+      }
+    });
+    edgeFn(v).forEach(function(edge) {
+      var w = edge.v === v ? edge.w : edge.v,
+          d = weightFn(edge);
+      results[v][w] = { distance: d, predecessor: v };
+    });
+  });
+
+  nodes.forEach(function(k) {
+    var rowK = results[k];
+    nodes.forEach(function(i) {
+      var rowI = results[i];
+      nodes.forEach(function(j) {
+        var ik = rowI[k];
+        var kj = rowK[j];
+        var ij = rowI[j];
+        var altDistance = ik.distance + kj.distance;
+        if (altDistance < ij.distance) {
+          ij.distance = altDistance;
+          ij.predecessor = kj.predecessor;
+        }
+      });
+    });
+  });
+
+  return results;
+}
+
+},{"../lodash":49}],38:[function(require,module,exports){
+module.exports = {
+  components: require("./components"),
+  dijkstra: require("./dijkstra"),
+  dijkstraAll: require("./dijkstra-all"),
+  findCycles: require("./find-cycles"),
+  floydWarshall: require("./floyd-warshall"),
+  isAcyclic: require("./is-acyclic"),
+  postorder: require("./postorder"),
+  preorder: require("./preorder"),
+  prim: require("./prim"),
+  tarjan: require("./tarjan"),
+  topsort: require("./topsort")
+};
+
+},{"./components":32,"./dijkstra":35,"./dijkstra-all":34,"./find-cycles":36,"./floyd-warshall":37,"./is-acyclic":39,"./postorder":40,"./preorder":41,"./prim":42,"./tarjan":43,"./topsort":44}],39:[function(require,module,exports){
+var topsort = require("./topsort");
+
+module.exports = isAcyclic;
+
+function isAcyclic(g) {
+  try {
+    topsort(g);
+  } catch (e) {
+    if (e instanceof topsort.CycleException) {
+      return false;
+    }
+    throw e;
+  }
+  return true;
+}
+
+},{"./topsort":44}],40:[function(require,module,exports){
+var dfs = require("./dfs");
+
+module.exports = postorder;
+
+function postorder(g, vs) {
+  return dfs(g, vs, "post");
+}
+
+},{"./dfs":33}],41:[function(require,module,exports){
+var dfs = require("./dfs");
+
+module.exports = preorder;
+
+function preorder(g, vs) {
+  return dfs(g, vs, "pre");
+}
+
+},{"./dfs":33}],42:[function(require,module,exports){
+var _ = require("../lodash"),
+    Graph = require("../graph"),
+    PriorityQueue = require("../data/priority-queue");
+
+module.exports = prim;
+
+function prim(g, weightFunc) {
+  var result = new Graph(),
+      parents = {},
+      pq = new PriorityQueue(),
+      v;
+
+  function updateNeighbors(edge) {
+    var w = edge.v === v ? edge.w : edge.v,
+        pri = pq.priority(w);
+    if (pri !== undefined) {
+      var edgeWeight = weightFunc(edge);
+      if (edgeWeight < pri) {
+        parents[w] = v;
+        pq.decrease(w, edgeWeight);
+      }
+    }
+  }
+
+  if (g.nodeCount() === 0) {
+    return result;
+  }
+
+  _.each(g.nodes(), function(v) {
+    pq.add(v, Number.POSITIVE_INFINITY);
+    result.setNode(v);
+  });
+
+  // Start from an arbitrary node
+  pq.decrease(g.nodes()[0], 0);
+
+  var init = false;
+  while (pq.size() > 0) {
+    v = pq.removeMin();
+    if (_.has(parents, v)) {
+      result.setEdge(v, parents[v]);
+    } else if (init) {
+      throw new Error("Input graph is not connected: " + g);
+    } else {
+      init = true;
+    }
+
+    g.nodeEdges(v).forEach(updateNeighbors);
+  }
+
+  return result;
+}
+
+},{"../data/priority-queue":45,"../graph":46,"../lodash":49}],43:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = tarjan;
+
+function tarjan(g) {
+  var index = 0,
+      stack = [],
+      visited = {}, // node id -> { onStack, lowlink, index }
+      results = [];
+
+  function dfs(v) {
+    var entry = visited[v] = {
+      onStack: true,
+      lowlink: index,
+      index: index++
+    };
+    stack.push(v);
+
+    g.successors(v).forEach(function(w) {
+      if (!_.has(visited, w)) {
+        dfs(w);
+        entry.lowlink = Math.min(entry.lowlink, visited[w].lowlink);
+      } else if (visited[w].onStack) {
+        entry.lowlink = Math.min(entry.lowlink, visited[w].index);
+      }
+    });
+
+    if (entry.lowlink === entry.index) {
+      var cmpt = [],
+          w;
+      do {
+        w = stack.pop();
+        visited[w].onStack = false;
+        cmpt.push(w);
+      } while (v !== w);
+      results.push(cmpt);
+    }
+  }
+
+  g.nodes().forEach(function(v) {
+    if (!_.has(visited, v)) {
+      dfs(v);
+    }
+  });
+
+  return results;
+}
+
+},{"../lodash":49}],44:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = topsort;
+topsort.CycleException = CycleException;
+
+function topsort(g) {
+  var visited = {},
+      stack = {},
+      results = [];
+
+  function visit(node) {
+    if (_.has(stack, node)) {
+      throw new CycleException();
+    }
+
+    if (!_.has(visited, node)) {
+      stack[node] = true;
+      visited[node] = true;
+      _.each(g.predecessors(node), visit);
+      delete stack[node];
+      results.push(node);
+    }
+  }
+
+  _.each(g.sinks(), visit);
+
+  if (_.size(visited) !== g.nodeCount()) {
+    throw new CycleException();
+  }
+
+  return results;
+}
+
+function CycleException() {}
+
+},{"../lodash":49}],45:[function(require,module,exports){
+var _ = require("../lodash");
+
+module.exports = PriorityQueue;
+
+/**
+ * A min-priority queue data structure. This algorithm is derived from Cormen,
+ * et al., "Introduction to Algorithms". The basic idea of a min-priority
+ * queue is that you can efficiently (in O(1) time) get the smallest key in
+ * the queue. Adding and removing elements takes O(log n) time. A key can
+ * have its priority decreased in O(log n) time.
+ */
+function PriorityQueue() {
+  this._arr = [];
+  this._keyIndices = {};
+}
+
+/**
+ * Returns the number of elements in the queue. Takes `O(1)` time.
+ */
+PriorityQueue.prototype.size = function() {
+  return this._arr.length;
+};
+
+/**
+ * Returns the keys that are in the queue. Takes `O(n)` time.
+ */
+PriorityQueue.prototype.keys = function() {
+  return this._arr.map(function(x) { return x.key; });
+};
+
+/**
+ * Returns `true` if **key** is in the queue and `false` if not.
+ */
+PriorityQueue.prototype.has = function(key) {
+  return _.has(this._keyIndices, key);
+};
+
+/**
+ * Returns the priority for **key**. If **key** is not present in the queue
+ * then this function returns `undefined`. Takes `O(1)` time.
+ *
+ * @param {Object} key
+ */
+PriorityQueue.prototype.priority = function(key) {
+  var index = this._keyIndices[key];
+  if (index !== undefined) {
+    return this._arr[index].priority;
+  }
+};
+
+/**
+ * Returns the key for the minimum element in this queue. If the queue is
+ * empty this function throws an Error. Takes `O(1)` time.
+ */
+PriorityQueue.prototype.min = function() {
+  if (this.size() === 0) {
+    throw new Error("Queue underflow");
+  }
+  return this._arr[0].key;
+};
+
+/**
+ * Inserts a new key into the priority queue. If the key already exists in
+ * the queue this function returns `false`; otherwise it will return `true`.
+ * Takes `O(n)` time.
+ *
+ * @param {Object} key the key to add
+ * @param {Number} priority the initial priority for the key
+ */
+PriorityQueue.prototype.add = function(key, priority) {
+  var keyIndices = this._keyIndices;
+  key = String(key);
+  if (!_.has(keyIndices, key)) {
+    var arr = this._arr;
+    var index = arr.length;
+    keyIndices[key] = index;
+    arr.push({key: key, priority: priority});
+    this._decrease(index);
+    return true;
+  }
+  return false;
+};
+
+/**
+ * Removes and returns the smallest key in the queue. Takes `O(log n)` time.
+ */
+PriorityQueue.prototype.removeMin = function() {
+  this._swap(0, this._arr.length - 1);
+  var min = this._arr.pop();
+  delete this._keyIndices[min.key];
+  this._heapify(0);
+  return min.key;
+};
+
+/**
+ * Decreases the priority for **key** to **priority**. If the new priority is
+ * greater than the previous priority, this function will throw an Error.
+ *
+ * @param {Object} key the key for which to raise priority
+ * @param {Number} priority the new priority for the key
+ */
+PriorityQueue.prototype.decrease = function(key, priority) {
+  var index = this._keyIndices[key];
+  if (priority > this._arr[index].priority) {
+    throw new Error("New priority is greater than current priority. " +
+        "Key: " + key + " Old: " + this._arr[index].priority + " New: " + priority);
+  }
+  this._arr[index].priority = priority;
+  this._decrease(index);
+};
+
+PriorityQueue.prototype._heapify = function(i) {
+  var arr = this._arr;
+  var l = 2 * i,
+      r = l + 1,
+      largest = i;
+  if (l < arr.length) {
+    largest = arr[l].priority < arr[largest].priority ? l : largest;
+    if (r < arr.length) {
+      largest = arr[r].priority < arr[largest].priority ? r : largest;
+    }
+    if (largest !== i) {
+      this._swap(i, largest);
+      this._heapify(largest);
+    }
+  }
+};
+
+PriorityQueue.prototype._decrease = function(index) {
+  var arr = this._arr;
+  var priority = arr[index].priority;
+  var parent;
+  while (index !== 0) {
+    parent = index >> 1;
+    if (arr[parent].priority < priority) {
+      break;
+    }
+    this._swap(index, parent);
+    index = parent;
+  }
+};
+
+PriorityQueue.prototype._swap = function(i, j) {
+  var arr = this._arr;
+  var keyIndices = this._keyIndices;
+  var origArrI = arr[i];
+  var origArrJ = arr[j];
+  arr[i] = origArrJ;
+  arr[j] = origArrI;
+  keyIndices[origArrJ.key] = i;
+  keyIndices[origArrI.key] = j;
+};
+
+},{"../lodash":49}],46:[function(require,module,exports){
+"use strict";
+
+var _ = require("./lodash");
+
+module.exports = Graph;
+
+var DEFAULT_EDGE_NAME = "\x00",
+    GRAPH_NODE = "\x00",
+    EDGE_KEY_DELIM = "\x01";
+
+// Implementation notes:
+//
+//  * Node id query functions should return string ids for the nodes
+//  * Edge id query functions should return an "edgeObj", edge object, that is
+//    composed of enough information to uniquely identify an edge: {v, w, name}.
+//  * Internally we use an "edgeId", a stringified form of the edgeObj, to
+//    reference edges. This is because we need a performant way to look these
+//    edges up and, object properties, which have string keys, are the closest
+//    we're going to get to a performant hashtable in JavaScript.
+
+function Graph(opts) {
+  this._isDirected = _.has(opts, "directed") ? opts.directed : true;
+  this._isMultigraph = _.has(opts, "multigraph") ? opts.multigraph : false;
+  this._isCompound = _.has(opts, "compound") ? opts.compound : false;
+
+  // Label for the graph itself
+  this._label = undefined;
+
+  // Defaults to be set when creating a new node
+  this._defaultNodeLabelFn = _.constant(undefined);
+
+  // Defaults to be set when creating a new edge
+  this._defaultEdgeLabelFn = _.constant(undefined);
+
+  // v -> label
+  this._nodes = {};
+
+  if (this._isCompound) {
+    // v -> parent
+    this._parent = {};
+
+    // v -> children
+    this._children = {};
+    this._children[GRAPH_NODE] = {};
+  }
+
+  // v -> edgeObj
+  this._in = {};
+
+  // u -> v -> Number
+  this._preds = {};
+
+  // v -> edgeObj
+  this._out = {};
+
+  // v -> w -> Number
+  this._sucs = {};
+
+  // e -> edgeObj
+  this._edgeObjs = {};
+
+  // e -> label
+  this._edgeLabels = {};
+}
+
+/* Number of nodes in the graph. Should only be changed by the implementation. */
+Graph.prototype._nodeCount = 0;
+
+/* Number of edges in the graph. Should only be changed by the implementation. */
+Graph.prototype._edgeCount = 0;
+
+
+/* === Graph functions ========= */
+
+Graph.prototype.isDirected = function() {
+  return this._isDirected;
+};
+
+Graph.prototype.isMultigraph = function() {
+  return this._isMultigraph;
+};
+
+Graph.prototype.isCompound = function() {
+  return this._isCompound;
+};
+
+Graph.prototype.setGraph = function(label) {
+  this._label = label;
+  return this;
+};
+
+Graph.prototype.graph = function() {
+  return this._label;
+};
+
+
+/* === Node functions ========== */
+
+Graph.prototype.setDefaultNodeLabel = function(newDefault) {
+  if (!_.isFunction(newDefault)) {
+    newDefault = _.constant(newDefault);
+  }
+  this._defaultNodeLabelFn = newDefault;
+  return this;
+};
+
+Graph.prototype.nodeCount = function() {
+  return this._nodeCount;
+};
+
+Graph.prototype.nodes = function() {
+  return _.keys(this._nodes);
+};
+
+Graph.prototype.sources = function() {
+  return _.filter(this.nodes(), function(v) {
+    return _.isEmpty(this._in[v]);
+  }, this);
+};
+
+Graph.prototype.sinks = function() {
+  return _.filter(this.nodes(), function(v) {
+    return _.isEmpty(this._out[v]);
+  }, this);
+};
+
+Graph.prototype.setNodes = function(vs, value) {
+  var args = arguments;
+  _.each(vs, function(v) {
+    if (args.length > 1) {
+      this.setNode(v, value);
+    } else {
+      this.setNode(v);
+    }
+  }, this);
+  return this;
+};
+
+Graph.prototype.setNode = function(v, value) {
+  if (_.has(this._nodes, v)) {
+    if (arguments.length > 1) {
+      this._nodes[v] = value;
+    }
+    return this;
+  }
+
+  this._nodes[v] = arguments.length > 1 ? value : this._defaultNodeLabelFn(v);
+  if (this._isCompound) {
+    this._parent[v] = GRAPH_NODE;
+    this._children[v] = {};
+    this._children[GRAPH_NODE][v] = true;
+  }
+  this._in[v] = {};
+  this._preds[v] = {};
+  this._out[v] = {};
+  this._sucs[v] = {};
+  ++this._nodeCount;
+  return this;
+};
+
+Graph.prototype.node = function(v) {
+  return this._nodes[v];
+};
+
+Graph.prototype.hasNode = function(v) {
+  return _.has(this._nodes, v);
+};
+
+Graph.prototype.removeNode =  function(v) {
+  var self = this;
+  if (_.has(this._nodes, v)) {
+    var removeEdge = function(e) { self.removeEdge(self._edgeObjs[e]); };
+    delete this._nodes[v];
+    if (this._isCompound) {
+      this._removeFromParentsChildList(v);
+      delete this._parent[v];
+      _.each(this.children(v), function(child) {
+        this.setParent(child);
+      }, this);
+      delete this._children[v];
+    }
+    _.each(_.keys(this._in[v]), removeEdge);
+    delete this._in[v];
+    delete this._preds[v];
+    _.each(_.keys(this._out[v]), removeEdge);
+    delete this._out[v];
+    delete this._sucs[v];
+    --this._nodeCount;
+  }
+  return this;
+};
+
+Graph.prototype.setParent = function(v, parent) {
+  if (!this._isCompound) {
+    throw new Error("Cannot set parent in a non-compound graph");
+  }
+
+  if (_.isUndefined(parent)) {
+    parent = GRAPH_NODE;
+  } else {
+    // Coerce parent to string
+    parent += "";
+    for (var ancestor = parent;
+         !_.isUndefined(ancestor);
+         ancestor = this.parent(ancestor)) {
+      if (ancestor === v) {
+        throw new Error("Setting " + parent+ " as parent of " + v +
+                        " would create create a cycle");
+      }
+    }
+
+    this.setNode(parent);
+  }
+
+  this.setNode(v);
+  this._removeFromParentsChildList(v);
+  this._parent[v] = parent;
+  this._children[parent][v] = true;
+  return this;
+};
+
+Graph.prototype._removeFromParentsChildList = function(v) {
+  delete this._children[this._parent[v]][v];
+};
+
+Graph.prototype.parent = function(v) {
+  if (this._isCompound) {
+    var parent = this._parent[v];
+    if (parent !== GRAPH_NODE) {
+      return parent;
+    }
+  }
+};
+
+Graph.prototype.children = function(v) {
+  if (_.isUndefined(v)) {
+    v = GRAPH_NODE;
+  }
+
+  if (this._isCompound) {
+    var children = this._children[v];
+    if (children) {
+      return _.keys(children);
+    }
+  } else if (v === GRAPH_NODE) {
+    return this.nodes();
+  } else if (this.hasNode(v)) {
+    return [];
+  }
+};
+
+Graph.prototype.predecessors = function(v) {
+  var predsV = this._preds[v];
+  if (predsV) {
+    return _.keys(predsV);
+  }
+};
+
+Graph.prototype.successors = function(v) {
+  var sucsV = this._sucs[v];
+  if (sucsV) {
+    return _.keys(sucsV);
+  }
+};
+
+Graph.prototype.neighbors = function(v) {
+  var preds = this.predecessors(v);
+  if (preds) {
+    return _.union(preds, this.successors(v));
+  }
+};
+
+/* === Edge functions ========== */
+
+Graph.prototype.setDefaultEdgeLabel = function(newDefault) {
+  if (!_.isFunction(newDefault)) {
+    newDefault = _.constant(newDefault);
+  }
+  this._defaultEdgeLabelFn = newDefault;
+  return this;
+};
+
+Graph.prototype.edgeCount = function() {
+  return this._edgeCount;
+};
+
+Graph.prototype.edges = function() {
+  return _.values(this._edgeObjs);
+};
+
+Graph.prototype.setPath = function(vs, value) {
+  var self = this,
+      args = arguments;
+  _.reduce(vs, function(v, w) {
+    if (args.length > 1) {
+      self.setEdge(v, w, value);
+    } else {
+      self.setEdge(v, w);
+    }
+    return w;
+  });
+  return this;
+};
+
+/*
+ * setEdge(v, w, [value, [name]])
+ * setEdge({ v, w, [name] }, [value])
+ */
+Graph.prototype.setEdge = function() {
+  var v, w, name, value,
+      valueSpecified = false;
+
+  if (_.isPlainObject(arguments[0])) {
+    v = arguments[0].v;
+    w = arguments[0].w;
+    name = arguments[0].name;
+    if (arguments.length === 2) {
+      value = arguments[1];
+      valueSpecified = true;
+    }
+  } else {
+    v = arguments[0];
+    w = arguments[1];
+    name = arguments[3];
+    if (arguments.length > 2) {
+      value = arguments[2];
+      valueSpecified = true;
+    }
+  }
+
+  v = "" + v;
+  w = "" + w;
+  if (!_.isUndefined(name)) {
+    name = "" + name;
+  }
+
+  var e = edgeArgsToId(this._isDirected, v, w, name);
+  if (_.has(this._edgeLabels, e)) {
+    if (valueSpecified) {
+      this._edgeLabels[e] = value;
+    }
+    return this;
+  }
+
+  if (!_.isUndefined(name) && !this._isMultigraph) {
+    throw new Error("Cannot set a named edge when isMultigraph = false");
+  }
+
+  // It didn't exist, so we need to create it.
+  // First ensure the nodes exist.
+  this.setNode(v);
+  this.setNode(w);
+
+  this._edgeLabels[e] = valueSpecified ? value : this._defaultEdgeLabelFn(v, w, name);
+
+  var edgeObj = edgeArgsToObj(this._isDirected, v, w, name);
+  // Ensure we add undirected edges in a consistent way.
+  v = edgeObj.v;
+  w = edgeObj.w;
+
+  Object.freeze(edgeObj);
+  this._edgeObjs[e] = edgeObj;
+  incrementOrInitEntry(this._preds[w], v);
+  incrementOrInitEntry(this._sucs[v], w);
+  this._in[w][e] = edgeObj;
+  this._out[v][e] = edgeObj;
+  this._edgeCount++;
+  return this;
+};
+
+Graph.prototype.edge = function(v, w, name) {
+  var e = (arguments.length === 1
+            ? edgeObjToId(this._isDirected, arguments[0])
+            : edgeArgsToId(this._isDirected, v, w, name));
+  return this._edgeLabels[e];
+};
+
+Graph.prototype.hasEdge = function(v, w, name) {
+  var e = (arguments.length === 1
+            ? edgeObjToId(this._isDirected, arguments[0])
+            : edgeArgsToId(this._isDirected, v, w, name));
+  return _.has(this._edgeLabels, e);
+};
+
+Graph.prototype.removeEdge = function(v, w, name) {
+  var e = (arguments.length === 1
+            ? edgeObjToId(this._isDirected, arguments[0])
+            : edgeArgsToId(this._isDirected, v, w, name)),
+      edge = this._edgeObjs[e];
+  if (edge) {
+    v = edge.v;
+    w = edge.w;
+    delete this._edgeLabels[e];
+    delete this._edgeObjs[e];
+    decrementOrRemoveEntry(this._preds[w], v);
+    decrementOrRemoveEntry(this._sucs[v], w);
+    delete this._in[w][e];
+    delete this._out[v][e];
+    this._edgeCount--;
+  }
+  return this;
+};
+
+Graph.prototype.inEdges = function(v, u) {
+  var inV = this._in[v];
+  if (inV) {
+    var edges = _.values(inV);
+    if (!u) {
+      return edges;
+    }
+    return _.filter(edges, function(edge) { return edge.v === u; });
+  }
+};
+
+Graph.prototype.outEdges = function(v, w) {
+  var outV = this._out[v];
+  if (outV) {
+    var edges = _.values(outV);
+    if (!w) {
+      return edges;
+    }
+    return _.filter(edges, function(edge) { return edge.w === w; });
+  }
+};
+
+Graph.prototype.nodeEdges = function(v, w) {
+  var inEdges = this.inEdges(v, w);
+  if (inEdges) {
+    return inEdges.concat(this.outEdges(v, w));
+  }
+};
+
+function incrementOrInitEntry(map, k) {
+  if (_.has(map, k)) {
+    map[k]++;
+  } else {
+    map[k] = 1;
+  }
+}
+
+function decrementOrRemoveEntry(map, k) {
+  if (!--map[k]) { delete map[k]; }
+}
+
+function edgeArgsToId(isDirected, v, w, name) {
+  if (!isDirected && v > w) {
+    var tmp = v;
+    v = w;
+    w = tmp;
+  }
+  return v + EDGE_KEY_DELIM + w + EDGE_KEY_DELIM +
+             (_.isUndefined(name) ? DEFAULT_EDGE_NAME : name);
+}
+
+function edgeArgsToObj(isDirected, v, w, name) {
+  if (!isDirected && v > w) {
+    var tmp = v;
+    v = w;
+    w = tmp;
+  }
+  var edgeObj =  { v: v, w: w };
+  if (name) {
+    edgeObj.name = name;
+  }
+  return edgeObj;
+}
+
+function edgeObjToId(isDirected, edgeObj) {
+  return edgeArgsToId(isDirected, edgeObj.v, edgeObj.w, edgeObj.name);
+}
+
+},{"./lodash":49}],47:[function(require,module,exports){
+// Includes only the "core" of graphlib
+module.exports = {
+  Graph: require("./graph"),
+  version: require("./version")
+};
+
+},{"./graph":46,"./version":50}],48:[function(require,module,exports){
+var _ = require("./lodash"),
+    Graph = require("./graph");
+
+module.exports = {
+  write: write,
+  read: read
+};
+
+function write(g) {
+  var json = {
+    options: {
+      directed: g.isDirected(),
+      multigraph: g.isMultigraph(),
+      compound: g.isCompound()
+    },
+    nodes: writeNodes(g),
+    edges: writeEdges(g)
+  };
+  if (!_.isUndefined(g.graph())) {
+    json.value = _.clone(g.graph());
+  }
+  return json;
+}
+
+function writeNodes(g) {
+  return _.map(g.nodes(), function(v) {
+    var nodeValue = g.node(v),
+        parent = g.parent(v),
+        node = { v: v };
+    if (!_.isUndefined(nodeValue)) {
+      node.value = nodeValue;
+    }
+    if (!_.isUndefined(parent)) {
+      node.parent = parent;
+    }
+    return node;
+  });
+}
+
+function writeEdges(g) {
+  return _.map(g.edges(), function(e) {
+    var edgeValue = g.edge(e),
+        edge = { v: e.v, w: e.w };
+    if (!_.isUndefined(e.name)) {
+      edge.name = e.name;
+    }
+    if (!_.isUndefined(edgeValue)) {
+      edge.value = edgeValue;
+    }
+    return edge;
+  });
+}
+
+function read(json) {
+  var g = new Graph(json.options).setGraph(json.value);
+  _.each(json.nodes, function(entry) {
+    g.setNode(entry.v, entry.value);
+    if (entry.parent) {
+      g.setParent(entry.v, entry.parent);
+    }
+  });
+  _.each(json.edges, function(entry) {
+    g.setEdge({ v: entry.v, w: entry.w, name: entry.name }, entry.value);
+  });
+  return g;
+}
+
+},{"./graph":46,"./lodash":49}],49:[function(require,module,exports){
+module.exports=require(10)
+},{"/Users/cpettitt/projects/dagre/lib/lodash.js":10,"lodash":51}],50:[function(require,module,exports){
+module.exports = '1.0.5';
+
+},{}],51:[function(require,module,exports){
+(function (global){
+/**
+ * @license
+ * lodash 3.10.0 (Custom Build) <https://lodash.com/>
+ * Build: `lodash modern -d -o ./index.js`
+ * Copyright 2012-2015 The Dojo Foundation <http://dojofoundation.org/>
+ * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
+ * Copyright 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ * Available under MIT license <https://lodash.com/license>
+ */
+;(function() {
+
+  /** Used as a safe reference for `undefined` in pre-ES5 environments. */
+  var undefined;
+
+  /** Used as the semantic version number. */
+  var VERSION = '3.10.0';
+
+  /** Used to compose bitmasks for wrapper metadata. */
+  var BIND_FLAG = 1,
+      BIND_KEY_FLAG = 2,
+      CURRY_BOUND_FLAG = 4,
+      CURRY_FLAG = 8,
+      CURRY_RIGHT_FLAG = 16,
+      PARTIAL_FLAG = 32,
+      PARTIAL_RIGHT_FLAG = 64,
+      ARY_FLAG = 128,
+      REARG_FLAG = 256;
+
+  /** Used as default options for `_.trunc`. */
+  var DEFAULT_TRUNC_LENGTH = 30,
+      DEFAULT_TRUNC_OMISSION = '...';
+
+  /** Used to detect when a function becomes hot. */
+  var HOT_COUNT = 150,
+      HOT_SPAN = 16;
+
+  /** Used as the size to enable large array optimizations. */
+  var LARGE_ARRAY_SIZE = 200;
+
+  /** Used to indicate the type of lazy iteratees. */
+  var LAZY_FILTER_FLAG = 1,
+      LAZY_MAP_FLAG = 2;
+
+  /** Used as the `TypeError` message for "Functions" methods. */
+  var FUNC_ERROR_TEXT = 'Expected a function';
+
+  /** Used as the internal argument placeholder. */
+  var PLACEHOLDER = '__lodash_placeholder__';
+
+  /** `Object#toString` result references. */
+  var argsTag = '[object Arguments]',
+      arrayTag = '[object Array]',
+      boolTag = '[object Boolean]',
+      dateTag = '[object Date]',
+      errorTag = '[object Error]',
+      funcTag = '[object Function]',
+      mapTag = '[object Map]',
+      numberTag = '[object Number]',
+      objectTag = '[object Object]',
+      regexpTag = '[object RegExp]',
+      setTag = '[object Set]',
+      stringTag = '[object String]',
+      weakMapTag = '[object WeakMap]';
+
+  var arrayBufferTag = '[object ArrayBuffer]',
+      float32Tag = '[object Float32Array]',
+      float64Tag = '[object Float64Array]',
+      int8Tag = '[object Int8Array]',
+      int16Tag = '[object Int16Array]',
+      int32Tag = '[object Int32Array]',
+      uint8Tag = '[object Uint8Array]',
+      uint8ClampedTag = '[object Uint8ClampedArray]',
+      uint16Tag = '[object Uint16Array]',
+      uint32Tag = '[object Uint32Array]';
+
+  /** Used to match empty string literals in compiled template source. */
+  var reEmptyStringLeading = /\b__p \+= '';/g,
+      reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
+      reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
+
+  /** Used to match HTML entities and HTML characters. */
+  var reEscapedHtml = /&(?:amp|lt|gt|quot|#39|#96);/g,
+      reUnescapedHtml = /[&<>"'`]/g,
+      reHasEscapedHtml = RegExp(reEscapedHtml.source),
+      reHasUnescapedHtml = RegExp(reUnescapedHtml.source);
+
+  /** Used to match template delimiters. */
+  var reEscape = /<%-([\s\S]+?)%>/g,
+      reEvaluate = /<%([\s\S]+?)%>/g,
+      reInterpolate = /<%=([\s\S]+?)%>/g;
+
+  /** Used to match property names within property paths. */
+  var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\n\\]|\\.)*?\1)\]/,
+      reIsPlainProp = /^\w*$/,
+      rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\n\\]|\\.)*?)\2)\]/g;
+
+  /**
+   * Used to match `RegExp` [syntax characters](http://ecma-international.org/ecma-262/6.0/#sec-patterns)
+   * and those outlined by [`EscapeRegExpPattern`](http://ecma-international.org/ecma-262/6.0/#sec-escaperegexppattern).
+   */
+  var reRegExpChars = /^[:!,]|[\\^$.*+?()[\]{}|\/]|(^[0-9a-fA-Fnrtuvx])|([\n\r\u2028\u2029])/g,
+      reHasRegExpChars = RegExp(reRegExpChars.source);
+
+  /** Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks). */
+  var reComboMark = /[\u0300-\u036f\ufe20-\ufe23]/g;
+
+  /** Used to match backslashes in property paths. */
+  var reEscapeChar = /\\(\\)?/g;
+
+  /** Used to match [ES template delimiters](http://ecma-international.org/ecma-262/6.0/#sec-template-literal-lexical-components). */
+  var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
+
+  /** Used to match `RegExp` flags from their coerced string values. */
+  var reFlags = /\w*$/;
+
+  /** Used to detect hexadecimal string values. */
+  var reHasHexPrefix = /^0[xX]/;
+
+  /** Used to detect host constructors (Safari > 5). */
+  var reIsHostCtor = /^\[object .+?Constructor\]$/;
+
+  /** Used to detect unsigned integer values. */
+  var reIsUint = /^\d+$/;
+
+  /** Used to match latin-1 supplementary letters (excluding mathematical operators). */
+  var reLatin1 = /[\xc0-\xd6\xd8-\xde\xdf-\xf6\xf8-\xff]/g;
+
+  /** Used to ensure capturing order of template delimiters. */
+  var reNoMatch = /($^)/;
+
+  /** Used to match unescaped characters in compiled string literals. */
+  var reUnescapedString = /['\n\r\u2028\u2029\\]/g;
+
+  /** Used to match words to create compound words. */
+  var reWords = (function() {
+    var upper = '[A-Z\\xc0-\\xd6\\xd8-\\xde]',
+        lower = '[a-z\\xdf-\\xf6\\xf8-\\xff]+';
+
+    return RegExp(upper + '+(?=' + upper + lower + ')|' + upper + '?' + lower + '|' + upper + '+|[0-9]+', 'g');
+  }());
+
+  /** Used to assign default `context` object properties. */
+  var contextProps = [
+    'Array', 'ArrayBuffer', 'Date', 'Error', 'Float32Array', 'Float64Array',
+    'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Math', 'Number',
+    'Object', 'RegExp', 'Set', 'String', '_', 'clearTimeout', 'isFinite',
+    'parseFloat', 'parseInt', 'setTimeout', 'TypeError', 'Uint8Array',
+    'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap'
+  ];
+
+  /** Used to make template sourceURLs easier to identify. */
+  var templateCounter = -1;
+
+  /** Used to identify `toStringTag` values of typed arrays. */
+  var typedArrayTags = {};
+  typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
+  typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
+  typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
+  typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
+  typedArrayTags[uint32Tag] = true;
+  typedArrayTags[argsTag] = typedArrayTags[arrayTag] =
+  typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
+  typedArrayTags[dateTag] = typedArrayTags[errorTag] =
+  typedArrayTags[funcTag] = typedArrayTags[mapTag] =
+  typedArrayTags[numberTag] = typedArrayTags[objectTag] =
+  typedArrayTags[regexpTag] = typedArrayTags[setTag] =
+  typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false;
+
+  /** Used to identify `toStringTag` values supported by `_.clone`. */
+  var cloneableTags = {};
+  cloneableTags[argsTag] = cloneableTags[arrayTag] =
+  cloneableTags[arrayBufferTag] = cloneableTags[boolTag] =
+  cloneableTags[dateTag] = cloneableTags[float32Tag] =
+  cloneableTags[float64Tag] = cloneableTags[int8Tag] =
+  cloneableTags[int16Tag] = cloneableTags[int32Tag] =
+  cloneableTags[numberTag] = cloneableTags[objectTag] =
+  cloneableTags[regexpTag] = cloneableTags[stringTag] =
+  cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] =
+  cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;
+  cloneableTags[errorTag] = cloneableTags[funcTag] =
+  cloneableTags[mapTag] = cloneableTags[setTag] =
+  cloneableTags[weakMapTag] = false;
+
+  /** Used to map latin-1 supplementary letters to basic latin letters. */
+  var deburredLetters = {
+    '\xc0': 'A',  '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A',
+    '\xe0': 'a',  '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a',
+    '\xc7': 'C',  '\xe7': 'c',
+    '\xd0': 'D',  '\xf0': 'd',
+    '\xc8': 'E',  '\xc9': 'E', '\xca': 'E', '\xcb': 'E',
+    '\xe8': 'e',  '\xe9': 'e', '\xea': 'e', '\xeb': 'e',
+    '\xcC': 'I',  '\xcd': 'I', '\xce': 'I', '\xcf': 'I',
+    '\xeC': 'i',  '\xed': 'i', '\xee': 'i', '\xef': 'i',
+    '\xd1': 'N',  '\xf1': 'n',
+    '\xd2': 'O',  '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O',
+    '\xf2': 'o',  '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o',
+    '\xd9': 'U',  '\xda': 'U', '\xdb': 'U', '\xdc': 'U',
+    '\xf9': 'u',  '\xfa': 'u', '\xfb': 'u', '\xfc': 'u',
+    '\xdd': 'Y',  '\xfd': 'y', '\xff': 'y',
+    '\xc6': 'Ae', '\xe6': 'ae',
+    '\xde': 'Th', '\xfe': 'th',
+    '\xdf': 'ss'
+  };
+
+  /** Used to map characters to HTML entities. */
+  var htmlEscapes = {
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;',
+    '"': '&quot;',
+    "'": '&#39;',
+    '`': '&#96;'
+  };
+
+  /** Used to map HTML entities to characters. */
+  var htmlUnescapes = {
+    '&amp;': '&',
+    '&lt;': '<',
+    '&gt;': '>',
+    '&quot;': '"',
+    '&#39;': "'",
+    '&#96;': '`'
+  };
+
+  /** Used to determine if values are of the language type `Object`. */
+  var objectTypes = {
+    'function': true,
+    'object': true
+  };
+
+  /** Used to escape characters for inclusion in compiled regexes. */
+  var regexpEscapes = {
+    '0': 'x30', '1': 'x31', '2': 'x32', '3': 'x33', '4': 'x34',
+    '5': 'x35', '6': 'x36', '7': 'x37', '8': 'x38', '9': 'x39',
+    'A': 'x41', 'B': 'x42', 'C': 'x43', 'D': 'x44', 'E': 'x45', 'F': 'x46',
+    'a': 'x61', 'b': 'x62', 'c': 'x63', 'd': 'x64', 'e': 'x65', 'f': 'x66',
+    'n': 'x6e', 'r': 'x72', 't': 'x74', 'u': 'x75', 'v': 'x76', 'x': 'x78'
+  };
+
+  /** Used to escape characters for inclusion in compiled string literals. */
+  var stringEscapes = {
+    '\\': '\\',
+    "'": "'",
+    '\n': 'n',
+    '\r': 'r',
+    '\u2028': 'u2028',
+    '\u2029': 'u2029'
+  };
+
+  /** Detect free variable `exports`. */
+  var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports;
+
+  /** Detect free variable `module`. */
+  var freeModule = objectTypes[typeof module] && module && !module.nodeType && module;
+
+  /** Detect free variable `global` from Node.js. */
+  var freeGlobal = freeExports && freeModule && typeof global == 'object' && global && global.Object && global;
+
+  /** Detect free variable `self`. */
+  var freeSelf = objectTypes[typeof self] && self && self.Object && self;
+
+  /** Detect free variable `window`. */
+  var freeWindow = objectTypes[typeof window] && window && window.Object && window;
+
+  /** Detect the popular CommonJS extension `module.exports`. */
+  var moduleExports = freeModule && freeModule.exports === freeExports && freeExports;
+
+  /**
+   * Used as a reference to the global object.
+   *
+   * The `this` value is used if it's the global object to avoid Greasemonkey's
+   * restricted `window` object, otherwise the `window` object is used.
+   */
+  var root = freeGlobal || ((freeWindow !== (this && this.window)) && freeWindow) || freeSelf || this;
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * The base implementation of `compareAscending` which compares values and
+   * sorts them in ascending order without guaranteeing a stable sort.
+   *
+   * @private
+   * @param {*} value The value to compare.
+   * @param {*} other The other value to compare.
+   * @returns {number} Returns the sort order indicator for `value`.
+   */
+  function baseCompareAscending(value, other) {
+    if (value !== other) {
+      var valIsNull = value === null,
+          valIsUndef = value === undefined,
+          valIsReflexive = value === value;
+
+      var othIsNull = other === null,
+          othIsUndef = other === undefined,
+          othIsReflexive = other === other;
+
+      if ((value > other && !othIsNull) || !valIsReflexive ||
+          (valIsNull && !othIsUndef && othIsReflexive) ||
+          (valIsUndef && othIsReflexive)) {
+        return 1;
+      }
+      if ((value < other && !valIsNull) || !othIsReflexive ||
+          (othIsNull && !valIsUndef && valIsReflexive) ||
+          (othIsUndef && valIsReflexive)) {
+        return -1;
+      }
+    }
+    return 0;
+  }
+
+  /**
+   * The base implementation of `_.findIndex` and `_.findLastIndex` without
+   * support for callback shorthands and `this` binding.
+   *
+   * @private
+   * @param {Array} array The array to search.
+   * @param {Function} predicate The function invoked per iteration.
+   * @param {boolean} [fromRight] Specify iterating from right to left.
+   * @returns {number} Returns the index of the matched value, else `-1`.
+   */
+  function baseFindIndex(array, predicate, fromRight) {
+    var length = array.length,
+        index = fromRight ? length : -1;
+
+    while ((fromRight ? index-- : ++index < length)) {
+      if (predicate(array[index], index, array)) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * The base implementation of `_.indexOf` without support for binary searches.
+   *
+   * @private
+   * @param {Array} array The array to search.
+   * @param {*} value The value to search for.
+   * @param {number} fromIndex The index to search from.
+   * @returns {number} Returns the index of the matched value, else `-1`.
+   */
+  function baseIndexOf(array, value, fromIndex) {
+    if (value !== value) {
+      return indexOfNaN(array, fromIndex);
+    }
+    var index = fromIndex - 1,
+        length = array.length;
+
+    while (++index < length) {
+      if (array[index] === value) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * The base implementation of `_.isFunction` without support for environments
+   * with incorrect `typeof` results.
+   *
+   * @private
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+   */
+  function baseIsFunction(value) {
+    // Avoid a Chakra JIT bug in compatibility modes of IE 11.
+    // See https://github.com/jashkenas/underscore/issues/1621 for more details.
+    return typeof value == 'function' || false;
+  }
+
+  /**
+   * Converts `value` to a string if it's not one. An empty string is returned
+   * for `null` or `undefined` values.
+   *
+   * @private
+   * @param {*} value The value to process.
+   * @returns {string} Returns the string.
+   */
+  function baseToString(value) {
+    return value == null ? '' : (value + '');
+  }
+
+  /**
+   * Used by `_.trim` and `_.trimLeft` to get the index of the first character
+   * of `string` that is not found in `chars`.
+   *
+   * @private
+   * @param {string} string The string to inspect.
+   * @param {string} chars The characters to find.
+   * @returns {number} Returns the index of the first character not found in `chars`.
+   */
+  function charsLeftIndex(string, chars) {
+    var index = -1,
+        length = string.length;
+
+    while (++index < length && chars.indexOf(string.charAt(index)) > -1) {}
+    return index;
+  }
+
+  /**
+   * Used by `_.trim` and `_.trimRight` to get the index of the last character
+   * of `string` that is not found in `chars`.
+   *
+   * @private
+   * @param {string} string The string to inspect.
+   * @param {string} chars The characters to find.
+   * @returns {number} Returns the index of the last character not found in `chars`.
+   */
+  function charsRightIndex(string, chars) {
+    var index = string.length;
+
+    while (index-- && chars.indexOf(string.charAt(index)) > -1) {}
+    return index;
+  }
+
+  /**
+   * Used by `_.sortBy` to compare transformed elements of a collection and stable
+   * sort them in ascending order.
+   *
+   * @private
+   * @param {Object} object The object to compare.
+   * @param {Object} other The other object to compare.
+   * @returns {number} Returns the sort order indicator for `object`.
+   */
+  function compareAscending(object, other) {
+    return baseCompareAscending(object.criteria, other.criteria) || (object.index - other.index);
+  }
+
+  /**
+   * Used by `_.sortByOrder` to compare multiple properties of a value to another
+   * and stable sort them.
+   *
+   * If `orders` is unspecified, all valuess are sorted in ascending order. Otherwise,
+   * a value is sorted in ascending order if its corresponding order is "asc", and
+   * descending if "desc".
+   *
+   * @private
+   * @param {Object} object The object to compare.
+   * @param {Object} other The other object to compare.
+   * @param {boolean[]} orders The order to sort by for each property.
+   * @returns {number} Returns the sort order indicator for `object`.
+   */
+  function compareMultiple(object, other, orders) {
+    var index = -1,
+        objCriteria = object.criteria,
+        othCriteria = other.criteria,
+        length = objCriteria.length,
+        ordersLength = orders.length;
+
+    while (++index < length) {
+      var result = baseCompareAscending(objCriteria[index], othCriteria[index]);
+      if (result) {
+        if (index >= ordersLength) {
+          return result;
+        }
+        var order = orders[index];
+        return result * ((order === 'asc' || order === true) ? 1 : -1);
+      }
+    }
+    // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
+    // that causes it, under certain circumstances, to provide the same value for
+    // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247
+    // for more details.
+    //
+    // This also ensures a stable sort in V8 and other engines.
+    // See https://code.google.com/p/v8/issues/detail?id=90 for more details.
+    return object.index - other.index;
+  }
+
+  /**
+   * Used by `_.deburr` to convert latin-1 supplementary letters to basic latin letters.
+   *
+   * @private
+   * @param {string} letter The matched letter to deburr.
+   * @returns {string} Returns the deburred letter.
+   */
+  function deburrLetter(letter) {
+    return deburredLetters[letter];
+  }
+
+  /**
+   * Used by `_.escape` to convert characters to HTML entities.
+   *
+   * @private
+   * @param {string} chr The matched character to escape.
+   * @returns {string} Returns the escaped character.
+   */
+  function escapeHtmlChar(chr) {
+    return htmlEscapes[chr];
+  }
+
+  /**
+   * Used by `_.escapeRegExp` to escape characters for inclusion in compiled regexes.
+   *
+   * @private
+   * @param {string} chr The matched character to escape.
+   * @param {string} leadingChar The capture group for a leading character.
+   * @param {string} whitespaceChar The capture group for a whitespace character.
+   * @returns {string} Returns the escaped character.
+   */
+  function escapeRegExpChar(chr, leadingChar, whitespaceChar) {
+    if (leadingChar) {
+      chr = regexpEscapes[chr];
+    } else if (whitespaceChar) {
+      chr = stringEscapes[chr];
+    }
+    return '\\' + chr;
+  }
+
+  /**
+   * Used by `_.template` to escape characters for inclusion in compiled string literals.
+   *
+   * @private
+   * @param {string} chr The matched character to escape.
+   * @returns {string} Returns the escaped character.
+   */
+  function escapeStringChar(chr) {
+    return '\\' + stringEscapes[chr];
+  }
+
+  /**
+   * Gets the index at which the first occurrence of `NaN` is found in `array`.
+   *
+   * @private
+   * @param {Array} array The array to search.
+   * @param {number} fromIndex The index to search from.
+   * @param {boolean} [fromRight] Specify iterating from right to left.
+   * @returns {number} Returns the index of the matched `NaN`, else `-1`.
+   */
+  function indexOfNaN(array, fromIndex, fromRight) {
+    var length = array.length,
+        index = fromIndex + (fromRight ? 0 : -1);
+
+    while ((fromRight ? index-- : ++index < length)) {
+      var other = array[index];
+      if (other !== other) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * Checks if `value` is object-like.
+   *
+   * @private
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
+   */
+  function isObjectLike(value) {
+    return !!value && typeof value == 'object';
+  }
+
+  /**
+   * Used by `trimmedLeftIndex` and `trimmedRightIndex` to determine if a
+   * character code is whitespace.
+   *
+   * @private
+   * @param {number} charCode The character code to inspect.
+   * @returns {boolean} Returns `true` if `charCode` is whitespace, else `false`.
+   */
+  function isSpace(charCode) {
+    return ((charCode <= 160 && (charCode >= 9 && charCode <= 13) || charCode == 32 || charCode == 160) || charCode == 5760 || charCode == 6158 ||
+      (charCode >= 8192 && (charCode <= 8202 || charCode == 8232 || charCode == 8233 || charCode == 8239 || charCode == 8287 || charCode == 12288 || charCode == 65279)));
+  }
+
+  /**
+   * Replaces all `placeholder` elements in `array` with an internal placeholder
+   * and returns an array of their indexes.
+   *
+   * @private
+   * @param {Array} array The array to modify.
+   * @param {*} placeholder The placeholder to replace.
+   * @returns {Array} Returns the new array of placeholder indexes.
+   */
+  function replaceHolders(array, placeholder) {
+    var index = -1,
+        length = array.length,
+        resIndex = -1,
+        result = [];
+
+    while (++index < length) {
+      if (array[index] === placeholder) {
+        array[index] = PLACEHOLDER;
+        result[++resIndex] = index;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * An implementation of `_.uniq` optimized for sorted arrays without support
+   * for callback shorthands and `this` binding.
+   *
+   * @private
+   * @param {Array} array The array to inspect.
+   * @param {Function} [iteratee] The function invoked per iteration.
+   * @returns {Array} Returns the new duplicate-value-free array.
+   */
+  function sortedUniq(array, iteratee) {
+    var seen,
+        index = -1,
+        length = array.length,
+        resIndex = -1,
+        result = [];
+
+    while (++index < length) {
+      var value = array[index],
+          computed = iteratee ? iteratee(value, index, array) : value;
+
+      if (!index || seen !== computed) {
+        seen = computed;
+        result[++resIndex] = value;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Used by `_.trim` and `_.trimLeft` to get the index of the first non-whitespace
+   * character of `string`.
+   *
+   * @private
+   * @param {string} string The string to inspect.
+   * @returns {number} Returns the index of the first non-whitespace character.
+   */
+  function trimmedLeftIndex(string) {
+    var index = -1,
+        length = string.length;
+
+    while (++index < length && isSpace(string.charCodeAt(index))) {}
+    return index;
+  }
+
+  /**
+   * Used by `_.trim` and `_.trimRight` to get the index of the last non-whitespace
+   * character of `string`.
+   *
+   * @private
+   * @param {string} string The string to inspect.
+   * @returns {number} Returns the index of the last non-whitespace character.
+   */
+  function trimmedRightIndex(string) {
+    var index = string.length;
+
+    while (index-- && isSpace(string.charCodeAt(index))) {}
+    return index;
+  }
+
+  /**
+   * Used by `_.unescape` to convert HTML entities to characters.
+   *
+   * @private
+   * @param {string} chr The matched character to unescape.
+   * @returns {string} Returns the unescaped character.
+   */
+  function unescapeHtmlChar(chr) {
+    return htmlUnescapes[chr];
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Create a new pristine `lodash` function using the given `context` object.
+   *
+   * @static
+   * @memberOf _
+   * @category Utility
+   * @param {Object} [context=root] The context object.
+   * @returns {Function} Returns a new `lodash` function.
+   * @example
+   *
+   * _.mixin({ 'foo': _.constant('foo') });
+   *
+   * var lodash = _.runInContext();
+   * lodash.mixin({ 'bar': lodash.constant('bar') });
+   *
+   * _.isFunction(_.foo);
+   * // => true
+   * _.isFunction(_.bar);
+   * // => false
+   *
+   * lodash.isFunction(lodash.foo);
+   * // => false
+   * lodash.isFunction(lodash.bar);
+   * // => true
+   *
+   * // using `context` to mock `Date#getTime` use in `_.now`
+   * var mock = _.runInContext({
+   *   'Date': function() {
+   *     return { 'getTime': getTimeMock };
+   *   }
+   * });
+   *
+   * // or creating a suped-up `defer` in Node.js
+   * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer;
+   */
+  function runInContext(context) {
+    // Avoid issues with some ES3 environments that attempt to use values, named
+    // after built-in constructors like `Object`, for the creation of literals.
+    // ES5 clears this up by stating that literals must use built-in constructors.
+    // See https://es5.github.io/#x11.1.5 for more details.
+    context = context ? _.defaults(root.Object(), context, _.pick(root, contextProps)) : root;
+
+    /** Native constructor references. */
+    var Array = context.Array,
+        Date = context.Date,
+        Error = context.Error,
+        Function = context.Function,
+        Math = context.Math,
+        Number = context.Number,
+        Object = context.Object,
+        RegExp = context.RegExp,
+        String = context.String,
+        TypeError = context.TypeError;
+
+    /** Used for native method references. */
+    var arrayProto = Array.prototype,
+        objectProto = Object.prototype,
+        stringProto = String.prototype;
+
+    /** Used to resolve the decompiled source of functions. */
+    var fnToString = Function.prototype.toString;
+
+    /** Used to check objects for own properties. */
+    var hasOwnProperty = objectProto.hasOwnProperty;
+
+    /** Used to generate unique IDs. */
+    var idCounter = 0;
+
+    /**
+     * Used to resolve the [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+     * of values.
+     */
+    var objToString = objectProto.toString;
+
+    /** Used to restore the original `_` reference in `_.noConflict`. */
+    var oldDash = root._;
+
+    /** Used to detect if a method is native. */
+    var reIsNative = RegExp('^' +
+      fnToString.call(hasOwnProperty).replace(/[\\^$.*+?()[\]{}|]/g, '\\$&')
+      .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
+    );
+
+    /** Native method references. */
+    var ArrayBuffer = context.ArrayBuffer,
+        clearTimeout = context.clearTimeout,
+        parseFloat = context.parseFloat,
+        pow = Math.pow,
+        propertyIsEnumerable = objectProto.propertyIsEnumerable,
+        Set = getNative(context, 'Set'),
+        setTimeout = context.setTimeout,
+        splice = arrayProto.splice,
+        Uint8Array = context.Uint8Array,
+        WeakMap = getNative(context, 'WeakMap');
+
+    /* Native method references for those with the same name as other `lodash` methods. */
+    var nativeCeil = Math.ceil,
+        nativeCreate = getNative(Object, 'create'),
+        nativeFloor = Math.floor,
+        nativeIsArray = getNative(Array, 'isArray'),
+        nativeIsFinite = context.isFinite,
+        nativeKeys = getNative(Object, 'keys'),
+        nativeMax = Math.max,
+        nativeMin = Math.min,
+        nativeNow = getNative(Date, 'now'),
+        nativeParseInt = context.parseInt,
+        nativeRandom = Math.random;
+
+    /** Used as references for `-Infinity` and `Infinity`. */
+    var NEGATIVE_INFINITY = Number.NEGATIVE_INFINITY,
+        POSITIVE_INFINITY = Number.POSITIVE_INFINITY;
+
+    /** Used as references for the maximum length and index of an array. */
+    var MAX_ARRAY_LENGTH = 4294967295,
+        MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1,
+        HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1;
+
+    /**
+     * Used as the [maximum length](http://ecma-international.org/ecma-262/6.0/#sec-number.max_safe_integer)
+     * of an array-like value.
+     */
+    var MAX_SAFE_INTEGER = 9007199254740991;
+
+    /** Used to store function metadata. */
+    var metaMap = WeakMap && new WeakMap;
+
+    /** Used to lookup unminified function names. */
+    var realNames = {};
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a `lodash` object which wraps `value` to enable implicit chaining.
+     * Methods that operate on and return arrays, collections, and functions can
+     * be chained together. Methods that retrieve a single value or may return a
+     * primitive value will automatically end the chain returning the unwrapped
+     * value. Explicit chaining may be enabled using `_.chain`. The execution of
+     * chained methods is lazy, that is, execution is deferred until `_#value`
+     * is implicitly or explicitly called.
+     *
+     * Lazy evaluation allows several methods to support shortcut fusion. Shortcut
+     * fusion is an optimization strategy which merge iteratee calls; this can help
+     * to avoid the creation of intermediate data structures and greatly reduce the
+     * number of iteratee executions.
+     *
+     * Chaining is supported in custom builds as long as the `_#value` method is
+     * directly or indirectly included in the build.
+     *
+     * In addition to lodash methods, wrappers have `Array` and `String` methods.
+     *
+     * The wrapper `Array` methods are:
+     * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`,
+     * `splice`, and `unshift`
+     *
+     * The wrapper `String` methods are:
+     * `replace` and `split`
+     *
+     * The wrapper methods that support shortcut fusion are:
+     * `compact`, `drop`, `dropRight`, `dropRightWhile`, `dropWhile`, `filter`,
+     * `first`, `initial`, `last`, `map`, `pluck`, `reject`, `rest`, `reverse`,
+     * `slice`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, `toArray`,
+     * and `where`
+     *
+     * The chainable wrapper methods are:
+     * `after`, `ary`, `assign`, `at`, `before`, `bind`, `bindAll`, `bindKey`,
+     * `callback`, `chain`, `chunk`, `commit`, `compact`, `concat`, `constant`,
+     * `countBy`, `create`, `curry`, `debounce`, `defaults`, `defaultsDeep`,
+     * `defer`, `delay`, `difference`, `drop`, `dropRight`, `dropRightWhile`,
+     * `dropWhile`, `fill`, `filter`, `flatten`, `flattenDeep`, `flow`, `flowRight`,
+     * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`,
+     * `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`,
+     * `invoke`, `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`,
+     * `matchesProperty`, `memoize`, `merge`, `method`, `methodOf`, `mixin`,
+     * `modArgs`, `negate`, `omit`, `once`, `pairs`, `partial`, `partialRight`,
+     * `partition`, `pick`, `plant`, `pluck`, `property`, `propertyOf`, `pull`,
+     * `pullAt`, `push`, `range`, `rearg`, `reject`, `remove`, `rest`, `restParam`,
+     * `reverse`, `set`, `shuffle`, `slice`, `sort`, `sortBy`, `sortByAll`,
+     * `sortByOrder`, `splice`, `spread`, `take`, `takeRight`, `takeRightWhile`,
+     * `takeWhile`, `tap`, `throttle`, `thru`, `times`, `toArray`, `toPlainObject`,
+     * `transform`, `union`, `uniq`, `unshift`, `unzip`, `unzipWith`, `values`,
+     * `valuesIn`, `where`, `without`, `wrap`, `xor`, `zip`, `zipObject`, `zipWith`
+     *
+     * The wrapper methods that are **not** chainable by default are:
+     * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clone`, `cloneDeep`,
+     * `deburr`, `endsWith`, `escape`, `escapeRegExp`, `every`, `find`, `findIndex`,
+     * `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `findWhere`, `first`,
+     * `floor`, `get`, `gt`, `gte`, `has`, `identity`, `includes`, `indexOf`,
+     * `inRange`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`,
+     * `isEmpty`, `isEqual`, `isError`, `isFinite` `isFunction`, `isMatch`,
+     * `isNative`, `isNaN`, `isNull`, `isNumber`, `isObject`, `isPlainObject`,
+     * `isRegExp`, `isString`, `isUndefined`, `isTypedArray`, `join`, `kebabCase`,
+     * `last`, `lastIndexOf`, `lt`, `lte`, `max`, `min`, `noConflict`, `noop`,
+     * `now`, `pad`, `padLeft`, `padRight`, `parseInt`, `pop`, `random`, `reduce`,
+     * `reduceRight`, `repeat`, `result`, `round`, `runInContext`, `shift`, `size`,
+     * `snakeCase`, `some`, `sortedIndex`, `sortedLastIndex`, `startCase`,
+     * `startsWith`, `sum`, `template`, `trim`, `trimLeft`, `trimRight`, `trunc`,
+     * `unescape`, `uniqueId`, `value`, and `words`
+     *
+     * The wrapper method `sample` will return a wrapped value when `n` is provided,
+     * otherwise an unwrapped value is returned.
+     *
+     * @name _
+     * @constructor
+     * @category Chain
+     * @param {*} value The value to wrap in a `lodash` instance.
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var wrapped = _([1, 2, 3]);
+     *
+     * // returns an unwrapped value
+     * wrapped.reduce(function(total, n) {
+     *   return total + n;
+     * });
+     * // => 6
+     *
+     * // returns a wrapped value
+     * var squares = wrapped.map(function(n) {
+     *   return n * n;
+     * });
+     *
+     * _.isArray(squares);
+     * // => false
+     *
+     * _.isArray(squares.value());
+     * // => true
+     */
+    function lodash(value) {
+      if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) {
+        if (value instanceof LodashWrapper) {
+          return value;
+        }
+        if (hasOwnProperty.call(value, '__chain__') && hasOwnProperty.call(value, '__wrapped__')) {
+          return wrapperClone(value);
+        }
+      }
+      return new LodashWrapper(value);
+    }
+
+    /**
+     * The function whose prototype all chaining wrappers inherit from.
+     *
+     * @private
+     */
+    function baseLodash() {
+      // No operation performed.
+    }
+
+    /**
+     * The base constructor for creating `lodash` wrapper objects.
+     *
+     * @private
+     * @param {*} value The value to wrap.
+     * @param {boolean} [chainAll] Enable chaining for all wrapper methods.
+     * @param {Array} [actions=[]] Actions to peform to resolve the unwrapped value.
+     */
+    function LodashWrapper(value, chainAll, actions) {
+      this.__wrapped__ = value;
+      this.__actions__ = actions || [];
+      this.__chain__ = !!chainAll;
+    }
+
+    /**
+     * An object environment feature flags.
+     *
+     * @static
+     * @memberOf _
+     * @type Object
+     */
+    var support = lodash.support = {};
+
+    /**
+     * By default, the template delimiters used by lodash are like those in
+     * embedded Ruby (ERB). Change the following template settings to use
+     * alternative delimiters.
+     *
+     * @static
+     * @memberOf _
+     * @type Object
+     */
+    lodash.templateSettings = {
+
+      /**
+       * Used to detect `data` property values to be HTML-escaped.
+       *
+       * @memberOf _.templateSettings
+       * @type RegExp
+       */
+      'escape': reEscape,
+
+      /**
+       * Used to detect code to be evaluated.
+       *
+       * @memberOf _.templateSettings
+       * @type RegExp
+       */
+      'evaluate': reEvaluate,
+
+      /**
+       * Used to detect `data` property values to inject.
+       *
+       * @memberOf _.templateSettings
+       * @type RegExp
+       */
+      'interpolate': reInterpolate,
+
+      /**
+       * Used to reference the data object in the template text.
+       *
+       * @memberOf _.templateSettings
+       * @type string
+       */
+      'variable': '',
+
+      /**
+       * Used to import variables into the compiled template.
+       *
+       * @memberOf _.templateSettings
+       * @type Object
+       */
+      'imports': {
+
+        /**
+         * A reference to the `lodash` function.
+         *
+         * @memberOf _.templateSettings.imports
+         * @type Function
+         */
+        '_': lodash
+      }
+    };
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation.
+     *
+     * @private
+     * @param {*} value The value to wrap.
+     */
+    function LazyWrapper(value) {
+      this.__wrapped__ = value;
+      this.__actions__ = [];
+      this.__dir__ = 1;
+      this.__filtered__ = false;
+      this.__iteratees__ = [];
+      this.__takeCount__ = POSITIVE_INFINITY;
+      this.__views__ = [];
+    }
+
+    /**
+     * Creates a clone of the lazy wrapper object.
+     *
+     * @private
+     * @name clone
+     * @memberOf LazyWrapper
+     * @returns {Object} Returns the cloned `LazyWrapper` object.
+     */
+    function lazyClone() {
+      var result = new LazyWrapper(this.__wrapped__);
+      result.__actions__ = arrayCopy(this.__actions__);
+      result.__dir__ = this.__dir__;
+      result.__filtered__ = this.__filtered__;
+      result.__iteratees__ = arrayCopy(this.__iteratees__);
+      result.__takeCount__ = this.__takeCount__;
+      result.__views__ = arrayCopy(this.__views__);
+      return result;
+    }
+
+    /**
+     * Reverses the direction of lazy iteration.
+     *
+     * @private
+     * @name reverse
+     * @memberOf LazyWrapper
+     * @returns {Object} Returns the new reversed `LazyWrapper` object.
+     */
+    function lazyReverse() {
+      if (this.__filtered__) {
+        var result = new LazyWrapper(this);
+        result.__dir__ = -1;
+        result.__filtered__ = true;
+      } else {
+        result = this.clone();
+        result.__dir__ *= -1;
+      }
+      return result;
+    }
+
+    /**
+     * Extracts the unwrapped value from its lazy wrapper.
+     *
+     * @private
+     * @name value
+     * @memberOf LazyWrapper
+     * @returns {*} Returns the unwrapped value.
+     */
+    function lazyValue() {
+      var array = this.__wrapped__.value(),
+          dir = this.__dir__,
+          isArr = isArray(array),
+          isRight = dir < 0,
+          arrLength = isArr ? array.length : 0,
+          view = getView(0, arrLength, this.__views__),
+          start = view.start,
+          end = view.end,
+          length = end - start,
+          index = isRight ? end : (start - 1),
+          iteratees = this.__iteratees__,
+          iterLength = iteratees.length,
+          resIndex = 0,
+          takeCount = nativeMin(length, this.__takeCount__);
+
+      if (!isArr || arrLength < LARGE_ARRAY_SIZE || (arrLength == length && takeCount == length)) {
+        return baseWrapperValue((isRight && isArr) ? array.reverse() : array, this.__actions__);
+      }
+      var result = [];
+
+      outer:
+      while (length-- && resIndex < takeCount) {
+        index += dir;
+
+        var iterIndex = -1,
+            value = array[index];
+
+        while (++iterIndex < iterLength) {
+          var data = iteratees[iterIndex],
+              iteratee = data.iteratee,
+              type = data.type,
+              computed = iteratee(value);
+
+          if (type == LAZY_MAP_FLAG) {
+            value = computed;
+          } else if (!computed) {
+            if (type == LAZY_FILTER_FLAG) {
+              continue outer;
+            } else {
+              break outer;
+            }
+          }
+        }
+        result[resIndex++] = value;
+      }
+      return result;
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a cache object to store key/value pairs.
+     *
+     * @private
+     * @static
+     * @name Cache
+     * @memberOf _.memoize
+     */
+    function MapCache() {
+      this.__data__ = {};
+    }
+
+    /**
+     * Removes `key` and its value from the cache.
+     *
+     * @private
+     * @name delete
+     * @memberOf _.memoize.Cache
+     * @param {string} key The key of the value to remove.
+     * @returns {boolean} Returns `true` if the entry was removed successfully, else `false`.
+     */
+    function mapDelete(key) {
+      return this.has(key) && delete this.__data__[key];
+    }
+
+    /**
+     * Gets the cached value for `key`.
+     *
+     * @private
+     * @name get
+     * @memberOf _.memoize.Cache
+     * @param {string} key The key of the value to get.
+     * @returns {*} Returns the cached value.
+     */
+    function mapGet(key) {
+      return key == '__proto__' ? undefined : this.__data__[key];
+    }
+
+    /**
+     * Checks if a cached value for `key` exists.
+     *
+     * @private
+     * @name has
+     * @memberOf _.memoize.Cache
+     * @param {string} key The key of the entry to check.
+     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+     */
+    function mapHas(key) {
+      return key != '__proto__' && hasOwnProperty.call(this.__data__, key);
+    }
+
+    /**
+     * Sets `value` to `key` of the cache.
+     *
+     * @private
+     * @name set
+     * @memberOf _.memoize.Cache
+     * @param {string} key The key of the value to cache.
+     * @param {*} value The value to cache.
+     * @returns {Object} Returns the cache object.
+     */
+    function mapSet(key, value) {
+      if (key != '__proto__') {
+        this.__data__[key] = value;
+      }
+      return this;
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     *
+     * Creates a cache object to store unique values.
+     *
+     * @private
+     * @param {Array} [values] The values to cache.
+     */
+    function SetCache(values) {
+      var length = values ? values.length : 0;
+
+      this.data = { 'hash': nativeCreate(null), 'set': new Set };
+      while (length--) {
+        this.push(values[length]);
+      }
+    }
+
+    /**
+     * Checks if `value` is in `cache` mimicking the return signature of
+     * `_.indexOf` by returning `0` if the value is found, else `-1`.
+     *
+     * @private
+     * @param {Object} cache The cache to search.
+     * @param {*} value The value to search for.
+     * @returns {number} Returns `0` if `value` is found, else `-1`.
+     */
+    function cacheIndexOf(cache, value) {
+      var data = cache.data,
+          result = (typeof value == 'string' || isObject(value)) ? data.set.has(value) : data.hash[value];
+
+      return result ? 0 : -1;
+    }
+
+    /**
+     * Adds `value` to the cache.
+     *
+     * @private
+     * @name push
+     * @memberOf SetCache
+     * @param {*} value The value to cache.
+     */
+    function cachePush(value) {
+      var data = this.data;
+      if (typeof value == 'string' || isObject(value)) {
+        data.set.add(value);
+      } else {
+        data.hash[value] = true;
+      }
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a new array joining `array` with `other`.
+     *
+     * @private
+     * @param {Array} array The array to join.
+     * @param {Array} other The other array to join.
+     * @returns {Array} Returns the new concatenated array.
+     */
+    function arrayConcat(array, other) {
+      var index = -1,
+          length = array.length,
+          othIndex = -1,
+          othLength = other.length,
+          result = Array(length + othLength);
+
+      while (++index < length) {
+        result[index] = array[index];
+      }
+      while (++othIndex < othLength) {
+        result[index++] = other[othIndex];
+      }
+      return result;
+    }
+
+    /**
+     * Copies the values of `source` to `array`.
+     *
+     * @private
+     * @param {Array} source The array to copy values from.
+     * @param {Array} [array=[]] The array to copy values to.
+     * @returns {Array} Returns `array`.
+     */
+    function arrayCopy(source, array) {
+      var index = -1,
+          length = source.length;
+
+      array || (array = Array(length));
+      while (++index < length) {
+        array[index] = source[index];
+      }
+      return array;
+    }
+
+    /**
+     * A specialized version of `_.forEach` for arrays without support for callback
+     * shorthands and `this` binding.
+     *
+     * @private
+     * @param {Array} array The array to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Array} Returns `array`.
+     */
+    function arrayEach(array, iteratee) {
+      var index = -1,
+          length = array.length;
+
+      while (++index < length) {
+        if (iteratee(array[index], index, array) === false) {
+          break;
+        }
+      }
+      return array;
+    }
+
+    /**
+     * A specialized version of `_.forEachRight` for arrays without support for
+     * callback shorthands and `this` binding.
+     *
+     * @private
+     * @param {Array} array The array to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Array} Returns `array`.
+     */
+    function arrayEachRight(array, iteratee) {
+      var length = array.length;
+
+      while (length--) {
+        if (iteratee(array[length], length, array) === false) {
+          break;
+        }
+      }
+      return array;
+    }
+
+    /**
+     * A specialized version of `_.every` for arrays without support for callback
+     * shorthands and `this` binding.
+     *
+     * @private
+     * @param {Array} array The array to iterate over.
+     * @param {Function} predicate The function invoked per iteration.
+     * @returns {boolean} Returns `true` if all elements pass the predicate check,
+     *  else `false`.
+     */
+    function arrayEvery(array, predicate) {
+      var index = -1,
+          length = array.length;
+
+      while (++index < length) {
+        if (!predicate(array[index], index, array)) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    /**
+     * A specialized version of `baseExtremum` for arrays which invokes `iteratee`
+     * with one argument: (value).
+     *
+     * @private
+     * @param {Array} array The array to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @param {Function} comparator The function used to compare values.
+     * @param {*} exValue The initial extremum value.
+     * @returns {*} Returns the extremum value.
+     */
+    function arrayExtremum(array, iteratee, comparator, exValue) {
+      var index = -1,
+          length = array.length,
+          computed = exValue,
+          result = computed;
+
+      while (++index < length) {
+        var value = array[index],
+            current = +iteratee(value);
+
+        if (comparator(current, computed)) {
+          computed = current;
+          result = value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * A specialized version of `_.filter` for arrays without support for callback
+     * shorthands and `this` binding.
+     *
+     * @private
+     * @param {Array} array The array to iterate over.
+     * @param {Function} predicate The function invoked per iteration.
+     * @returns {Array} Returns the new filtered array.
+     */
+    function arrayFilter(array, predicate) {
+      var index = -1,
+          length = array.length,
+          resIndex = -1,
+          result = [];
+
+      while (++index < length) {
+        var value = array[index];
+        if (predicate(value, index, array)) {
+          result[++resIndex] = value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * A specialized version of `_.map` for arrays without support for callback
+     * shorthands and `this` binding.
+     *
+     * @private
+     * @param {Array} array The array to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Array} Returns the new mapped array.
+     */
+    function arrayMap(array, iteratee) {
+      var index = -1,
+          length = array.length,
+          result = Array(length);
+
+      while (++index < length) {
+        result[index] = iteratee(array[index], index, array);
+      }
+      return result;
+    }
+
+    /**
+     * Appends the elements of `values` to `array`.
+     *
+     * @private
+     * @param {Array} array The array to modify.
+     * @param {Array} values The values to append.
+     * @returns {Array} Returns `array`.
+     */
+    function arrayPush(array, values) {
+      var index = -1,
+          length = values.length,
+          offset = array.length;
+
+      while (++index < length) {
+        array[offset + index] = values[index];
+      }
+      return array;
+    }
+
+    /**
+     * A specialized version of `_.reduce` for arrays without support for callback
+     * shorthands and `this` binding.
+     *
+     * @private
+     * @param {Array} array The array to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @param {*} [accumulator] The initial value.
+     * @param {boolean} [initFromArray] Specify using the first element of `array`
+     *  as the initial value.
+     * @returns {*} Returns the accumulated value.
+     */
+    function arrayReduce(array, iteratee, accumulator, initFromArray) {
+      var index = -1,
+          length = array.length;
+
+      if (initFromArray && length) {
+        accumulator = array[++index];
+      }
+      while (++index < length) {
+        accumulator = iteratee(accumulator, array[index], index, array);
+      }
+      return accumulator;
+    }
+
+    /**
+     * A specialized version of `_.reduceRight` for arrays without support for
+     * callback shorthands and `this` binding.
+     *
+     * @private
+     * @param {Array} array The array to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @param {*} [accumulator] The initial value.
+     * @param {boolean} [initFromArray] Specify using the last element of `array`
+     *  as the initial value.
+     * @returns {*} Returns the accumulated value.
+     */
+    function arrayReduceRight(array, iteratee, accumulator, initFromArray) {
+      var length = array.length;
+      if (initFromArray && length) {
+        accumulator = array[--length];
+      }
+      while (length--) {
+        accumulator = iteratee(accumulator, array[length], length, array);
+      }
+      return accumulator;
+    }
+
+    /**
+     * A specialized version of `_.some` for arrays without support for callback
+     * shorthands and `this` binding.
+     *
+     * @private
+     * @param {Array} array The array to iterate over.
+     * @param {Function} predicate The function invoked per iteration.
+     * @returns {boolean} Returns `true` if any element passes the predicate check,
+     *  else `false`.
+     */
+    function arraySome(array, predicate) {
+      var index = -1,
+          length = array.length;
+
+      while (++index < length) {
+        if (predicate(array[index], index, array)) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    /**
+     * A specialized version of `_.sum` for arrays without support for callback
+     * shorthands and `this` binding..
+     *
+     * @private
+     * @param {Array} array The array to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {number} Returns the sum.
+     */
+    function arraySum(array, iteratee) {
+      var length = array.length,
+          result = 0;
+
+      while (length--) {
+        result += +iteratee(array[length]) || 0;
+      }
+      return result;
+    }
+
+    /**
+     * Used by `_.defaults` to customize its `_.assign` use.
+     *
+     * @private
+     * @param {*} objectValue The destination object property value.
+     * @param {*} sourceValue The source object property value.
+     * @returns {*} Returns the value to assign to the destination object.
+     */
+    function assignDefaults(objectValue, sourceValue) {
+      return objectValue === undefined ? sourceValue : objectValue;
+    }
+
+    /**
+     * Used by `_.template` to customize its `_.assign` use.
+     *
+     * **Note:** This function is like `assignDefaults` except that it ignores
+     * inherited property values when checking if a property is `undefined`.
+     *
+     * @private
+     * @param {*} objectValue The destination object property value.
+     * @param {*} sourceValue The source object property value.
+     * @param {string} key The key associated with the object and source values.
+     * @param {Object} object The destination object.
+     * @returns {*} Returns the value to assign to the destination object.
+     */
+    function assignOwnDefaults(objectValue, sourceValue, key, object) {
+      return (objectValue === undefined || !hasOwnProperty.call(object, key))
+        ? sourceValue
+        : objectValue;
+    }
+
+    /**
+     * A specialized version of `_.assign` for customizing assigned values without
+     * support for argument juggling, multiple sources, and `this` binding `customizer`
+     * functions.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @param {Function} customizer The function to customize assigned values.
+     * @returns {Object} Returns `object`.
+     */
+    function assignWith(object, source, customizer) {
+      var index = -1,
+          props = keys(source),
+          length = props.length;
+
+      while (++index < length) {
+        var key = props[index],
+            value = object[key],
+            result = customizer(value, source[key], key, object, source);
+
+        if ((result === result ? (result !== value) : (value === value)) ||
+            (value === undefined && !(key in object))) {
+          object[key] = result;
+        }
+      }
+      return object;
+    }
+
+    /**
+     * The base implementation of `_.assign` without support for argument juggling,
+     * multiple sources, and `customizer` functions.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @returns {Object} Returns `object`.
+     */
+    function baseAssign(object, source) {
+      return source == null
+        ? object
+        : baseCopy(source, keys(source), object);
+    }
+
+    /**
+     * The base implementation of `_.at` without support for string collections
+     * and individual key arguments.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {number[]|string[]} props The property names or indexes of elements to pick.
+     * @returns {Array} Returns the new array of picked elements.
+     */
+    function baseAt(collection, props) {
+      var index = -1,
+          isNil = collection == null,
+          isArr = !isNil && isArrayLike(collection),
+          length = isArr ? collection.length : 0,
+          propsLength = props.length,
+          result = Array(propsLength);
+
+      while(++index < propsLength) {
+        var key = props[index];
+        if (isArr) {
+          result[index] = isIndex(key, length) ? collection[key] : undefined;
+        } else {
+          result[index] = isNil ? undefined : collection[key];
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Copies properties of `source` to `object`.
+     *
+     * @private
+     * @param {Object} source The object to copy properties from.
+     * @param {Array} props The property names to copy.
+     * @param {Object} [object={}] The object to copy properties to.
+     * @returns {Object} Returns `object`.
+     */
+    function baseCopy(source, props, object) {
+      object || (object = {});
+
+      var index = -1,
+          length = props.length;
+
+      while (++index < length) {
+        var key = props[index];
+        object[key] = source[key];
+      }
+      return object;
+    }
+
+    /**
+     * The base implementation of `_.callback` which supports specifying the
+     * number of arguments to provide to `func`.
+     *
+     * @private
+     * @param {*} [func=_.identity] The value to convert to a callback.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {number} [argCount] The number of arguments to provide to `func`.
+     * @returns {Function} Returns the callback.
+     */
+    function baseCallback(func, thisArg, argCount) {
+      var type = typeof func;
+      if (type == 'function') {
+        return thisArg === undefined
+          ? func
+          : bindCallback(func, thisArg, argCount);
+      }
+      if (func == null) {
+        return identity;
+      }
+      if (type == 'object') {
+        return baseMatches(func);
+      }
+      return thisArg === undefined
+        ? property(func)
+        : baseMatchesProperty(func, thisArg);
+    }
+
+    /**
+     * The base implementation of `_.clone` without support for argument juggling
+     * and `this` binding `customizer` functions.
+     *
+     * @private
+     * @param {*} value The value to clone.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @param {Function} [customizer] The function to customize cloning values.
+     * @param {string} [key] The key of `value`.
+     * @param {Object} [object] The object `value` belongs to.
+     * @param {Array} [stackA=[]] Tracks traversed source objects.
+     * @param {Array} [stackB=[]] Associates clones with source counterparts.
+     * @returns {*} Returns the cloned value.
+     */
+    function baseClone(value, isDeep, customizer, key, object, stackA, stackB) {
+      var result;
+      if (customizer) {
+        result = object ? customizer(value, key, object) : customizer(value);
+      }
+      if (result !== undefined) {
+        return result;
+      }
+      if (!isObject(value)) {
+        return value;
+      }
+      var isArr = isArray(value);
+      if (isArr) {
+        result = initCloneArray(value);
+        if (!isDeep) {
+          return arrayCopy(value, result);
+        }
+      } else {
+        var tag = objToString.call(value),
+            isFunc = tag == funcTag;
+
+        if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
+          result = initCloneObject(isFunc ? {} : value);
+          if (!isDeep) {
+            return baseAssign(result, value);
+          }
+        } else {
+          return cloneableTags[tag]
+            ? initCloneByTag(value, tag, isDeep)
+            : (object ? value : {});
+        }
+      }
+      // Check for circular references and return its corresponding clone.
+      stackA || (stackA = []);
+      stackB || (stackB = []);
+
+      var length = stackA.length;
+      while (length--) {
+        if (stackA[length] == value) {
+          return stackB[length];
+        }
+      }
+      // Add the source value to the stack of traversed objects and associate it with its clone.
+      stackA.push(value);
+      stackB.push(result);
+
+      // Recursively populate clone (susceptible to call stack limits).
+      (isArr ? arrayEach : baseForOwn)(value, function(subValue, key) {
+        result[key] = baseClone(subValue, isDeep, customizer, key, value, stackA, stackB);
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.create` without support for assigning
+     * properties to the created object.
+     *
+     * @private
+     * @param {Object} prototype The object to inherit from.
+     * @returns {Object} Returns the new object.
+     */
+    var baseCreate = (function() {
+      function object() {}
+      return function(prototype) {
+        if (isObject(prototype)) {
+          object.prototype = prototype;
+          var result = new object;
+          object.prototype = undefined;
+        }
+        return result || {};
+      };
+    }());
+
+    /**
+     * The base implementation of `_.delay` and `_.defer` which accepts an index
+     * of where to slice the arguments to provide to `func`.
+     *
+     * @private
+     * @param {Function} func The function to delay.
+     * @param {number} wait The number of milliseconds to delay invocation.
+     * @param {Object} args The arguments provide to `func`.
+     * @returns {number} Returns the timer id.
+     */
+    function baseDelay(func, wait, args) {
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      return setTimeout(function() { func.apply(undefined, args); }, wait);
+    }
+
+    /**
+     * The base implementation of `_.difference` which accepts a single array
+     * of values to exclude.
+     *
+     * @private
+     * @param {Array} array The array to inspect.
+     * @param {Array} values The values to exclude.
+     * @returns {Array} Returns the new array of filtered values.
+     */
+    function baseDifference(array, values) {
+      var length = array ? array.length : 0,
+          result = [];
+
+      if (!length) {
+        return result;
+      }
+      var index = -1,
+          indexOf = getIndexOf(),
+          isCommon = indexOf == baseIndexOf,
+          cache = (isCommon && values.length >= LARGE_ARRAY_SIZE) ? createCache(values) : null,
+          valuesLength = values.length;
+
+      if (cache) {
+        indexOf = cacheIndexOf;
+        isCommon = false;
+        values = cache;
+      }
+      outer:
+      while (++index < length) {
+        var value = array[index];
+
+        if (isCommon && value === value) {
+          var valuesIndex = valuesLength;
+          while (valuesIndex--) {
+            if (values[valuesIndex] === value) {
+              continue outer;
+            }
+          }
+          result.push(value);
+        }
+        else if (indexOf(values, value, 0) < 0) {
+          result.push(value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.forEach` without support for callback
+     * shorthands and `this` binding.
+     *
+     * @private
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Array|Object|string} Returns `collection`.
+     */
+    var baseEach = createBaseEach(baseForOwn);
+
+    /**
+     * The base implementation of `_.forEachRight` without support for callback
+     * shorthands and `this` binding.
+     *
+     * @private
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Array|Object|string} Returns `collection`.
+     */
+    var baseEachRight = createBaseEach(baseForOwnRight, true);
+
+    /**
+     * The base implementation of `_.every` without support for callback
+     * shorthands and `this` binding.
+     *
+     * @private
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} predicate The function invoked per iteration.
+     * @returns {boolean} Returns `true` if all elements pass the predicate check,
+     *  else `false`
+     */
+    function baseEvery(collection, predicate) {
+      var result = true;
+      baseEach(collection, function(value, index, collection) {
+        result = !!predicate(value, index, collection);
+        return result;
+      });
+      return result;
+    }
+
+    /**
+     * Gets the extremum value of `collection` invoking `iteratee` for each value
+     * in `collection` to generate the criterion by which the value is ranked.
+     * The `iteratee` is invoked with three arguments: (value, index|key, collection).
+     *
+     * @private
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @param {Function} comparator The function used to compare values.
+     * @param {*} exValue The initial extremum value.
+     * @returns {*} Returns the extremum value.
+     */
+    function baseExtremum(collection, iteratee, comparator, exValue) {
+      var computed = exValue,
+          result = computed;
+
+      baseEach(collection, function(value, index, collection) {
+        var current = +iteratee(value, index, collection);
+        if (comparator(current, computed) || (current === exValue && current === result)) {
+          computed = current;
+          result = value;
+        }
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.fill` without an iteratee call guard.
+     *
+     * @private
+     * @param {Array} array The array to fill.
+     * @param {*} value The value to fill `array` with.
+     * @param {number} [start=0] The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns `array`.
+     */
+    function baseFill(array, value, start, end) {
+      var length = array.length;
+
+      start = start == null ? 0 : (+start || 0);
+      if (start < 0) {
+        start = -start > length ? 0 : (length + start);
+      }
+      end = (end === undefined || end > length) ? length : (+end || 0);
+      if (end < 0) {
+        end += length;
+      }
+      length = start > end ? 0 : (end >>> 0);
+      start >>>= 0;
+
+      while (start < length) {
+        array[start++] = value;
+      }
+      return array;
+    }
+
+    /**
+     * The base implementation of `_.filter` without support for callback
+     * shorthands and `this` binding.
+     *
+     * @private
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} predicate The function invoked per iteration.
+     * @returns {Array} Returns the new filtered array.
+     */
+    function baseFilter(collection, predicate) {
+      var result = [];
+      baseEach(collection, function(value, index, collection) {
+        if (predicate(value, index, collection)) {
+          result.push(value);
+        }
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.find`, `_.findLast`, `_.findKey`, and `_.findLastKey`,
+     * without support for callback shorthands and `this` binding, which iterates
+     * over `collection` using the provided `eachFunc`.
+     *
+     * @private
+     * @param {Array|Object|string} collection The collection to search.
+     * @param {Function} predicate The function invoked per iteration.
+     * @param {Function} eachFunc The function to iterate over `collection`.
+     * @param {boolean} [retKey] Specify returning the key of the found element
+     *  instead of the element itself.
+     * @returns {*} Returns the found element or its key, else `undefined`.
+     */
+    function baseFind(collection, predicate, eachFunc, retKey) {
+      var result;
+      eachFunc(collection, function(value, key, collection) {
+        if (predicate(value, key, collection)) {
+          result = retKey ? key : value;
+          return false;
+        }
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.flatten` with added support for restricting
+     * flattening and specifying the start index.
+     *
+     * @private
+     * @param {Array} array The array to flatten.
+     * @param {boolean} [isDeep] Specify a deep flatten.
+     * @param {boolean} [isStrict] Restrict flattening to arrays-like objects.
+     * @param {Array} [result=[]] The initial result value.
+     * @returns {Array} Returns the new flattened array.
+     */
+    function baseFlatten(array, isDeep, isStrict, result) {
+      result || (result = []);
+
+      var index = -1,
+          length = array.length;
+
+      while (++index < length) {
+        var value = array[index];
+        if (isObjectLike(value) && isArrayLike(value) &&
+            (isStrict || isArray(value) || isArguments(value))) {
+          if (isDeep) {
+            // Recursively flatten arrays (susceptible to call stack limits).
+            baseFlatten(value, isDeep, isStrict, result);
+          } else {
+            arrayPush(result, value);
+          }
+        } else if (!isStrict) {
+          result[result.length] = value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `baseForIn` and `baseForOwn` which iterates
+     * over `object` properties returned by `keysFunc` invoking `iteratee` for
+     * each property. Iteratee functions may exit iteration early by explicitly
+     * returning `false`.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @param {Function} keysFunc The function to get the keys of `object`.
+     * @returns {Object} Returns `object`.
+     */
+    var baseFor = createBaseFor();
+
+    /**
+     * This function is like `baseFor` except that it iterates over properties
+     * in the opposite order.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @param {Function} keysFunc The function to get the keys of `object`.
+     * @returns {Object} Returns `object`.
+     */
+    var baseForRight = createBaseFor(true);
+
+    /**
+     * The base implementation of `_.forIn` without support for callback
+     * shorthands and `this` binding.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     */
+    function baseForIn(object, iteratee) {
+      return baseFor(object, iteratee, keysIn);
+    }
+
+    /**
+     * The base implementation of `_.forOwn` without support for callback
+     * shorthands and `this` binding.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     */
+    function baseForOwn(object, iteratee) {
+      return baseFor(object, iteratee, keys);
+    }
+
+    /**
+     * The base implementation of `_.forOwnRight` without support for callback
+     * shorthands and `this` binding.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     */
+    function baseForOwnRight(object, iteratee) {
+      return baseForRight(object, iteratee, keys);
+    }
+
+    /**
+     * The base implementation of `_.functions` which creates an array of
+     * `object` function property names filtered from those provided.
+     *
+     * @private
+     * @param {Object} object The object to inspect.
+     * @param {Array} props The property names to filter.
+     * @returns {Array} Returns the new array of filtered property names.
+     */
+    function baseFunctions(object, props) {
+      var index = -1,
+          length = props.length,
+          resIndex = -1,
+          result = [];
+
+      while (++index < length) {
+        var key = props[index];
+        if (isFunction(object[key])) {
+          result[++resIndex] = key;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `get` without support for string paths
+     * and default values.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array} path The path of the property to get.
+     * @param {string} [pathKey] The key representation of path.
+     * @returns {*} Returns the resolved value.
+     */
+    function baseGet(object, path, pathKey) {
+      if (object == null) {
+        return;
+      }
+      if (pathKey !== undefined && pathKey in toObject(object)) {
+        path = [pathKey];
+      }
+      var index = 0,
+          length = path.length;
+
+      while (object != null && index < length) {
+        object = object[path[index++]];
+      }
+      return (index && index == length) ? object : undefined;
+    }
+
+    /**
+     * The base implementation of `_.isEqual` without support for `this` binding
+     * `customizer` functions.
+     *
+     * @private
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @param {Function} [customizer] The function to customize comparing values.
+     * @param {boolean} [isLoose] Specify performing partial comparisons.
+     * @param {Array} [stackA] Tracks traversed `value` objects.
+     * @param {Array} [stackB] Tracks traversed `other` objects.
+     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+     */
+    function baseIsEqual(value, other, customizer, isLoose, stackA, stackB) {
+      if (value === other) {
+        return true;
+      }
+      if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) {
+        return value !== value && other !== other;
+      }
+      return baseIsEqualDeep(value, other, baseIsEqual, customizer, isLoose, stackA, stackB);
+    }
+
+    /**
+     * A specialized version of `baseIsEqual` for arrays and objects which performs
+     * deep comparisons and tracks traversed objects enabling objects with circular
+     * references to be compared.
+     *
+     * @private
+     * @param {Object} object The object to compare.
+     * @param {Object} other The other object to compare.
+     * @param {Function} equalFunc The function to determine equivalents of values.
+     * @param {Function} [customizer] The function to customize comparing objects.
+     * @param {boolean} [isLoose] Specify performing partial comparisons.
+     * @param {Array} [stackA=[]] Tracks traversed `value` objects.
+     * @param {Array} [stackB=[]] Tracks traversed `other` objects.
+     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+     */
+    function baseIsEqualDeep(object, other, equalFunc, customizer, isLoose, stackA, stackB) {
+      var objIsArr = isArray(object),
+          othIsArr = isArray(other),
+          objTag = arrayTag,
+          othTag = arrayTag;
+
+      if (!objIsArr) {
+        objTag = objToString.call(object);
+        if (objTag == argsTag) {
+          objTag = objectTag;
+        } else if (objTag != objectTag) {
+          objIsArr = isTypedArray(object);
+        }
+      }
+      if (!othIsArr) {
+        othTag = objToString.call(other);
+        if (othTag == argsTag) {
+          othTag = objectTag;
+        } else if (othTag != objectTag) {
+          othIsArr = isTypedArray(other);
+        }
+      }
+      var objIsObj = objTag == objectTag,
+          othIsObj = othTag == objectTag,
+          isSameTag = objTag == othTag;
+
+      if (isSameTag && !(objIsArr || objIsObj)) {
+        return equalByTag(object, other, objTag);
+      }
+      if (!isLoose) {
+        var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
+            othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
+
+        if (objIsWrapped || othIsWrapped) {
+          return equalFunc(objIsWrapped ? object.value() : object, othIsWrapped ? other.value() : other, customizer, isLoose, stackA, stackB);
+        }
+      }
+      if (!isSameTag) {
+        return false;
+      }
+      // Assume cyclic values are equal.
+      // For more information on detecting circular references see https://es5.github.io/#JO.
+      stackA || (stackA = []);
+      stackB || (stackB = []);
+
+      var length = stackA.length;
+      while (length--) {
+        if (stackA[length] == object) {
+          return stackB[length] == other;
+        }
+      }
+      // Add `object` and `other` to the stack of traversed objects.
+      stackA.push(object);
+      stackB.push(other);
+
+      var result = (objIsArr ? equalArrays : equalObjects)(object, other, equalFunc, customizer, isLoose, stackA, stackB);
+
+      stackA.pop();
+      stackB.pop();
+
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.isMatch` without support for callback
+     * shorthands and `this` binding.
+     *
+     * @private
+     * @param {Object} object The object to inspect.
+     * @param {Array} matchData The propery names, values, and compare flags to match.
+     * @param {Function} [customizer] The function to customize comparing objects.
+     * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+     */
+    function baseIsMatch(object, matchData, customizer) {
+      var index = matchData.length,
+          length = index,
+          noCustomizer = !customizer;
+
+      if (object == null) {
+        return !length;
+      }
+      object = toObject(object);
+      while (index--) {
+        var data = matchData[index];
+        if ((noCustomizer && data[2])
+              ? data[1] !== object[data[0]]
+              : !(data[0] in object)
+            ) {
+          return false;
+        }
+      }
+      while (++index < length) {
+        data = matchData[index];
+        var key = data[0],
+            objValue = object[key],
+            srcValue = data[1];
+
+        if (noCustomizer && data[2]) {
+          if (objValue === undefined && !(key in object)) {
+            return false;
+          }
+        } else {
+          var result = customizer ? customizer(objValue, srcValue, key) : undefined;
+          if (!(result === undefined ? baseIsEqual(srcValue, objValue, customizer, true) : result)) {
+            return false;
+          }
+        }
+      }
+      return true;
+    }
+
+    /**
+     * The base implementation of `_.map` without support for callback shorthands
+     * and `this` binding.
+     *
+     * @private
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Array} Returns the new mapped array.
+     */
+    function baseMap(collection, iteratee) {
+      var index = -1,
+          result = isArrayLike(collection) ? Array(collection.length) : [];
+
+      baseEach(collection, function(value, key, collection) {
+        result[++index] = iteratee(value, key, collection);
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.matches` which does not clone `source`.
+     *
+     * @private
+     * @param {Object} source The object of property values to match.
+     * @returns {Function} Returns the new function.
+     */
+    function baseMatches(source) {
+      var matchData = getMatchData(source);
+      if (matchData.length == 1 && matchData[0][2]) {
+        var key = matchData[0][0],
+            value = matchData[0][1];
+
+        return function(object) {
+          if (object == null) {
+            return false;
+          }
+          return object[key] === value && (value !== undefined || (key in toObject(object)));
+        };
+      }
+      return function(object) {
+        return baseIsMatch(object, matchData);
+      };
+    }
+
+    /**
+     * The base implementation of `_.matchesProperty` which does not clone `srcValue`.
+     *
+     * @private
+     * @param {string} path The path of the property to get.
+     * @param {*} srcValue The value to compare.
+     * @returns {Function} Returns the new function.
+     */
+    function baseMatchesProperty(path, srcValue) {
+      var isArr = isArray(path),
+          isCommon = isKey(path) && isStrictComparable(srcValue),
+          pathKey = (path + '');
+
+      path = toPath(path);
+      return function(object) {
+        if (object == null) {
+          return false;
+        }
+        var key = pathKey;
+        object = toObject(object);
+        if ((isArr || !isCommon) && !(key in object)) {
+          object = path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1));
+          if (object == null) {
+            return false;
+          }
+          key = last(path);
+          object = toObject(object);
+        }
+        return object[key] === srcValue
+          ? (srcValue !== undefined || (key in object))
+          : baseIsEqual(srcValue, object[key], undefined, true);
+      };
+    }
+
+    /**
+     * The base implementation of `_.merge` without support for argument juggling,
+     * multiple sources, and `this` binding `customizer` functions.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @param {Function} [customizer] The function to customize merged values.
+     * @param {Array} [stackA=[]] Tracks traversed source objects.
+     * @param {Array} [stackB=[]] Associates values with source counterparts.
+     * @returns {Object} Returns `object`.
+     */
+    function baseMerge(object, source, customizer, stackA, stackB) {
+      if (!isObject(object)) {
+        return object;
+      }
+      var isSrcArr = isArrayLike(source) && (isArray(source) || isTypedArray(source)),
+          props = isSrcArr ? undefined : keys(source);
+
+      arrayEach(props || source, function(srcValue, key) {
+        if (props) {
+          key = srcValue;
+          srcValue = source[key];
+        }
+        if (isObjectLike(srcValue)) {
+          stackA || (stackA = []);
+          stackB || (stackB = []);
+          baseMergeDeep(object, source, key, baseMerge, customizer, stackA, stackB);
+        }
+        else {
+          var value = object[key],
+              result = customizer ? customizer(value, srcValue, key, object, source) : undefined,
+              isCommon = result === undefined;
+
+          if (isCommon) {
+            result = srcValue;
+          }
+          if ((result !== undefined || (isSrcArr && !(key in object))) &&
+              (isCommon || (result === result ? (result !== value) : (value === value)))) {
+            object[key] = result;
+          }
+        }
+      });
+      return object;
+    }
+
+    /**
+     * A specialized version of `baseMerge` for arrays and objects which performs
+     * deep merges and tracks traversed objects enabling objects with circular
+     * references to be merged.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @param {string} key The key of the value to merge.
+     * @param {Function} mergeFunc The function to merge values.
+     * @param {Function} [customizer] The function to customize merged values.
+     * @param {Array} [stackA=[]] Tracks traversed source objects.
+     * @param {Array} [stackB=[]] Associates values with source counterparts.
+     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+     */
+    function baseMergeDeep(object, source, key, mergeFunc, customizer, stackA, stackB) {
+      var length = stackA.length,
+          srcValue = source[key];
+
+      while (length--) {
+        if (stackA[length] == srcValue) {
+          object[key] = stackB[length];
+          return;
+        }
+      }
+      var value = object[key],
+          result = customizer ? customizer(value, srcValue, key, object, source) : undefined,
+          isCommon = result === undefined;
+
+      if (isCommon) {
+        result = srcValue;
+        if (isArrayLike(srcValue) && (isArray(srcValue) || isTypedArray(srcValue))) {
+          result = isArray(value)
+            ? value
+            : (isArrayLike(value) ? arrayCopy(value) : []);
+        }
+        else if (isPlainObject(srcValue) || isArguments(srcValue)) {
+          result = isArguments(value)
+            ? toPlainObject(value)
+            : (isPlainObject(value) ? value : {});
+        }
+        else {
+          isCommon = false;
+        }
+      }
+      // Add the source value to the stack of traversed objects and associate
+      // it with its merged value.
+      stackA.push(srcValue);
+      stackB.push(result);
+
+      if (isCommon) {
+        // Recursively merge objects and arrays (susceptible to call stack limits).
+        object[key] = mergeFunc(result, srcValue, customizer, stackA, stackB);
+      } else if (result === result ? (result !== value) : (value === value)) {
+        object[key] = result;
+      }
+    }
+
+    /**
+     * The base implementation of `_.property` without support for deep paths.
+     *
+     * @private
+     * @param {string} key The key of the property to get.
+     * @returns {Function} Returns the new function.
+     */
+    function baseProperty(key) {
+      return function(object) {
+        return object == null ? undefined : object[key];
+      };
+    }
+
+    /**
+     * A specialized version of `baseProperty` which supports deep paths.
+     *
+     * @private
+     * @param {Array|string} path The path of the property to get.
+     * @returns {Function} Returns the new function.
+     */
+    function basePropertyDeep(path) {
+      var pathKey = (path + '');
+      path = toPath(path);
+      return function(object) {
+        return baseGet(object, path, pathKey);
+      };
+    }
+
+    /**
+     * The base implementation of `_.pullAt` without support for individual
+     * index arguments and capturing the removed elements.
+     *
+     * @private
+     * @param {Array} array The array to modify.
+     * @param {number[]} indexes The indexes of elements to remove.
+     * @returns {Array} Returns `array`.
+     */
+    function basePullAt(array, indexes) {
+      var length = array ? indexes.length : 0;
+      while (length--) {
+        var index = indexes[length];
+        if (index != previous && isIndex(index)) {
+          var previous = index;
+          splice.call(array, index, 1);
+        }
+      }
+      return array;
+    }
+
+    /**
+     * The base implementation of `_.random` without support for argument juggling
+     * and returning floating-point numbers.
+     *
+     * @private
+     * @param {number} min The minimum possible value.
+     * @param {number} max The maximum possible value.
+     * @returns {number} Returns the random number.
+     */
+    function baseRandom(min, max) {
+      return min + nativeFloor(nativeRandom() * (max - min + 1));
+    }
+
+    /**
+     * The base implementation of `_.reduce` and `_.reduceRight` without support
+     * for callback shorthands and `this` binding, which iterates over `collection`
+     * using the provided `eachFunc`.
+     *
+     * @private
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @param {*} accumulator The initial value.
+     * @param {boolean} initFromCollection Specify using the first or last element
+     *  of `collection` as the initial value.
+     * @param {Function} eachFunc The function to iterate over `collection`.
+     * @returns {*} Returns the accumulated value.
+     */
+    function baseReduce(collection, iteratee, accumulator, initFromCollection, eachFunc) {
+      eachFunc(collection, function(value, index, collection) {
+        accumulator = initFromCollection
+          ? (initFromCollection = false, value)
+          : iteratee(accumulator, value, index, collection);
+      });
+      return accumulator;
+    }
+
+    /**
+     * The base implementation of `setData` without support for hot loop detection.
+     *
+     * @private
+     * @param {Function} func The function to associate metadata with.
+     * @param {*} data The metadata.
+     * @returns {Function} Returns `func`.
+     */
+    var baseSetData = !metaMap ? identity : function(func, data) {
+      metaMap.set(func, data);
+      return func;
+    };
+
+    /**
+     * The base implementation of `_.slice` without an iteratee call guard.
+     *
+     * @private
+     * @param {Array} array The array to slice.
+     * @param {number} [start=0] The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns the slice of `array`.
+     */
+    function baseSlice(array, start, end) {
+      var index = -1,
+          length = array.length;
+
+      start = start == null ? 0 : (+start || 0);
+      if (start < 0) {
+        start = -start > length ? 0 : (length + start);
+      }
+      end = (end === undefined || end > length) ? length : (+end || 0);
+      if (end < 0) {
+        end += length;
+      }
+      length = start > end ? 0 : ((end - start) >>> 0);
+      start >>>= 0;
+
+      var result = Array(length);
+      while (++index < length) {
+        result[index] = array[index + start];
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.some` without support for callback shorthands
+     * and `this` binding.
+     *
+     * @private
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} predicate The function invoked per iteration.
+     * @returns {boolean} Returns `true` if any element passes the predicate check,
+     *  else `false`.
+     */
+    function baseSome(collection, predicate) {
+      var result;
+
+      baseEach(collection, function(value, index, collection) {
+        result = predicate(value, index, collection);
+        return !result;
+      });
+      return !!result;
+    }
+
+    /**
+     * The base implementation of `_.sortBy` which uses `comparer` to define
+     * the sort order of `array` and replaces criteria objects with their
+     * corresponding values.
+     *
+     * @private
+     * @param {Array} array The array to sort.
+     * @param {Function} comparer The function to define sort order.
+     * @returns {Array} Returns `array`.
+     */
+    function baseSortBy(array, comparer) {
+      var length = array.length;
+
+      array.sort(comparer);
+      while (length--) {
+        array[length] = array[length].value;
+      }
+      return array;
+    }
+
+    /**
+     * The base implementation of `_.sortByOrder` without param guards.
+     *
+     * @private
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.
+     * @param {boolean[]} orders The sort orders of `iteratees`.
+     * @returns {Array} Returns the new sorted array.
+     */
+    function baseSortByOrder(collection, iteratees, orders) {
+      var callback = getCallback(),
+          index = -1;
+
+      iteratees = arrayMap(iteratees, function(iteratee) { return callback(iteratee); });
+
+      var result = baseMap(collection, function(value) {
+        var criteria = arrayMap(iteratees, function(iteratee) { return iteratee(value); });
+        return { 'criteria': criteria, 'index': ++index, 'value': value };
+      });
+
+      return baseSortBy(result, function(object, other) {
+        return compareMultiple(object, other, orders);
+      });
+    }
+
+    /**
+     * The base implementation of `_.sum` without support for callback shorthands
+     * and `this` binding.
+     *
+     * @private
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {number} Returns the sum.
+     */
+    function baseSum(collection, iteratee) {
+      var result = 0;
+      baseEach(collection, function(value, index, collection) {
+        result += +iteratee(value, index, collection) || 0;
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.uniq` without support for callback shorthands
+     * and `this` binding.
+     *
+     * @private
+     * @param {Array} array The array to inspect.
+     * @param {Function} [iteratee] The function invoked per iteration.
+     * @returns {Array} Returns the new duplicate-value-free array.
+     */
+    function baseUniq(array, iteratee) {
+      var index = -1,
+          indexOf = getIndexOf(),
+          length = array.length,
+          isCommon = indexOf == baseIndexOf,
+          isLarge = isCommon && length >= LARGE_ARRAY_SIZE,
+          seen = isLarge ? createCache() : null,
+          result = [];
+
+      if (seen) {
+        indexOf = cacheIndexOf;
+        isCommon = false;
+      } else {
+        isLarge = false;
+        seen = iteratee ? [] : result;
+      }
+      outer:
+      while (++index < length) {
+        var value = array[index],
+            computed = iteratee ? iteratee(value, index, array) : value;
+
+        if (isCommon && value === value) {
+          var seenIndex = seen.length;
+          while (seenIndex--) {
+            if (seen[seenIndex] === computed) {
+              continue outer;
+            }
+          }
+          if (iteratee) {
+            seen.push(computed);
+          }
+          result.push(value);
+        }
+        else if (indexOf(seen, computed, 0) < 0) {
+          if (iteratee || isLarge) {
+            seen.push(computed);
+          }
+          result.push(value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.values` and `_.valuesIn` which creates an
+     * array of `object` property values corresponding to the property names
+     * of `props`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array} props The property names to get values for.
+     * @returns {Object} Returns the array of property values.
+     */
+    function baseValues(object, props) {
+      var index = -1,
+          length = props.length,
+          result = Array(length);
+
+      while (++index < length) {
+        result[index] = object[props[index]];
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.dropRightWhile`, `_.dropWhile`, `_.takeRightWhile`,
+     * and `_.takeWhile` without support for callback shorthands and `this` binding.
+     *
+     * @private
+     * @param {Array} array The array to query.
+     * @param {Function} predicate The function invoked per iteration.
+     * @param {boolean} [isDrop] Specify dropping elements instead of taking them.
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Array} Returns the slice of `array`.
+     */
+    function baseWhile(array, predicate, isDrop, fromRight) {
+      var length = array.length,
+          index = fromRight ? length : -1;
+
+      while ((fromRight ? index-- : ++index < length) && predicate(array[index], index, array)) {}
+      return isDrop
+        ? baseSlice(array, (fromRight ? 0 : index), (fromRight ? index + 1 : length))
+        : baseSlice(array, (fromRight ? index + 1 : 0), (fromRight ? length : index));
+    }
+
+    /**
+     * The base implementation of `wrapperValue` which returns the result of
+     * performing a sequence of actions on the unwrapped `value`, where each
+     * successive action is supplied the return value of the previous.
+     *
+     * @private
+     * @param {*} value The unwrapped value.
+     * @param {Array} actions Actions to peform to resolve the unwrapped value.
+     * @returns {*} Returns the resolved value.
+     */
+    function baseWrapperValue(value, actions) {
+      var result = value;
+      if (result instanceof LazyWrapper) {
+        result = result.value();
+      }
+      var index = -1,
+          length = actions.length;
+
+      while (++index < length) {
+        var action = actions[index];
+        result = action.func.apply(action.thisArg, arrayPush([result], action.args));
+      }
+      return result;
+    }
+
+    /**
+     * Performs a binary search of `array` to determine the index at which `value`
+     * should be inserted into `array` in order to maintain its sort order.
+     *
+     * @private
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {boolean} [retHighest] Specify returning the highest qualified index.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     */
+    function binaryIndex(array, value, retHighest) {
+      var low = 0,
+          high = array ? array.length : low;
+
+      if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) {
+        while (low < high) {
+          var mid = (low + high) >>> 1,
+              computed = array[mid];
+
+          if ((retHighest ? (computed <= value) : (computed < value)) && computed !== null) {
+            low = mid + 1;
+          } else {
+            high = mid;
+          }
+        }
+        return high;
+      }
+      return binaryIndexBy(array, value, identity, retHighest);
+    }
+
+    /**
+     * This function is like `binaryIndex` except that it invokes `iteratee` for
+     * `value` and each element of `array` to compute their sort ranking. The
+     * iteratee is invoked with one argument; (value).
+     *
+     * @private
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @param {boolean} [retHighest] Specify returning the highest qualified index.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     */
+    function binaryIndexBy(array, value, iteratee, retHighest) {
+      value = iteratee(value);
+
+      var low = 0,
+          high = array ? array.length : 0,
+          valIsNaN = value !== value,
+          valIsNull = value === null,
+          valIsUndef = value === undefined;
+
+      while (low < high) {
+        var mid = nativeFloor((low + high) / 2),
+            computed = iteratee(array[mid]),
+            isDef = computed !== undefined,
+            isReflexive = computed === computed;
+
+        if (valIsNaN) {
+          var setLow = isReflexive || retHighest;
+        } else if (valIsNull) {
+          setLow = isReflexive && isDef && (retHighest || computed != null);
+        } else if (valIsUndef) {
+          setLow = isReflexive && (retHighest || isDef);
+        } else if (computed == null) {
+          setLow = false;
+        } else {
+          setLow = retHighest ? (computed <= value) : (computed < value);
+        }
+        if (setLow) {
+          low = mid + 1;
+        } else {
+          high = mid;
+        }
+      }
+      return nativeMin(high, MAX_ARRAY_INDEX);
+    }
+
+    /**
+     * A specialized version of `baseCallback` which only supports `this` binding
+     * and specifying the number of arguments to provide to `func`.
+     *
+     * @private
+     * @param {Function} func The function to bind.
+     * @param {*} thisArg The `this` binding of `func`.
+     * @param {number} [argCount] The number of arguments to provide to `func`.
+     * @returns {Function} Returns the callback.
+     */
+    function bindCallback(func, thisArg, argCount) {
+      if (typeof func != 'function') {
+        return identity;
+      }
+      if (thisArg === undefined) {
+        return func;
+      }
+      switch (argCount) {
+        case 1: return function(value) {
+          return func.call(thisArg, value);
+        };
+        case 3: return function(value, index, collection) {
+          return func.call(thisArg, value, index, collection);
+        };
+        case 4: return function(accumulator, value, index, collection) {
+          return func.call(thisArg, accumulator, value, index, collection);
+        };
+        case 5: return function(value, other, key, object, source) {
+          return func.call(thisArg, value, other, key, object, source);
+        };
+      }
+      return function() {
+        return func.apply(thisArg, arguments);
+      };
+    }
+
+    /**
+     * Creates a clone of the given array buffer.
+     *
+     * @private
+     * @param {ArrayBuffer} buffer The array buffer to clone.
+     * @returns {ArrayBuffer} Returns the cloned array buffer.
+     */
+    function bufferClone(buffer) {
+      var result = new ArrayBuffer(buffer.byteLength),
+          view = new Uint8Array(result);
+
+      view.set(new Uint8Array(buffer));
+      return result;
+    }
+
+    /**
+     * Creates an array that is the composition of partially applied arguments,
+     * placeholders, and provided arguments into a single array of arguments.
+     *
+     * @private
+     * @param {Array|Object} args The provided arguments.
+     * @param {Array} partials The arguments to prepend to those provided.
+     * @param {Array} holders The `partials` placeholder indexes.
+     * @returns {Array} Returns the new array of composed arguments.
+     */
+    function composeArgs(args, partials, holders) {
+      var holdersLength = holders.length,
+          argsIndex = -1,
+          argsLength = nativeMax(args.length - holdersLength, 0),
+          leftIndex = -1,
+          leftLength = partials.length,
+          result = Array(leftLength + argsLength);
+
+      while (++leftIndex < leftLength) {
+        result[leftIndex] = partials[leftIndex];
+      }
+      while (++argsIndex < holdersLength) {
+        result[holders[argsIndex]] = args[argsIndex];
+      }
+      while (argsLength--) {
+        result[leftIndex++] = args[argsIndex++];
+      }
+      return result;
+    }
+
+    /**
+     * This function is like `composeArgs` except that the arguments composition
+     * is tailored for `_.partialRight`.
+     *
+     * @private
+     * @param {Array|Object} args The provided arguments.
+     * @param {Array} partials The arguments to append to those provided.
+     * @param {Array} holders The `partials` placeholder indexes.
+     * @returns {Array} Returns the new array of composed arguments.
+     */
+    function composeArgsRight(args, partials, holders) {
+      var holdersIndex = -1,
+          holdersLength = holders.length,
+          argsIndex = -1,
+          argsLength = nativeMax(args.length - holdersLength, 0),
+          rightIndex = -1,
+          rightLength = partials.length,
+          result = Array(argsLength + rightLength);
+
+      while (++argsIndex < argsLength) {
+        result[argsIndex] = args[argsIndex];
+      }
+      var offset = argsIndex;
+      while (++rightIndex < rightLength) {
+        result[offset + rightIndex] = partials[rightIndex];
+      }
+      while (++holdersIndex < holdersLength) {
+        result[offset + holders[holdersIndex]] = args[argsIndex++];
+      }
+      return result;
+    }
+
+    /**
+     * Creates a `_.countBy`, `_.groupBy`, `_.indexBy`, or `_.partition` function.
+     *
+     * @private
+     * @param {Function} setter The function to set keys and values of the accumulator object.
+     * @param {Function} [initializer] The function to initialize the accumulator object.
+     * @returns {Function} Returns the new aggregator function.
+     */
+    function createAggregator(setter, initializer) {
+      return function(collection, iteratee, thisArg) {
+        var result = initializer ? initializer() : {};
+        iteratee = getCallback(iteratee, thisArg, 3);
+
+        if (isArray(collection)) {
+          var index = -1,
+              length = collection.length;
+
+          while (++index < length) {
+            var value = collection[index];
+            setter(result, value, iteratee(value, index, collection), collection);
+          }
+        } else {
+          baseEach(collection, function(value, key, collection) {
+            setter(result, value, iteratee(value, key, collection), collection);
+          });
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Creates a `_.assign`, `_.defaults`, or `_.merge` function.
+     *
+     * @private
+     * @param {Function} assigner The function to assign values.
+     * @returns {Function} Returns the new assigner function.
+     */
+    function createAssigner(assigner) {
+      return restParam(function(object, sources) {
+        var index = -1,
+            length = object == null ? 0 : sources.length,
+            customizer = length > 2 ? sources[length - 2] : undefined,
+            guard = length > 2 ? sources[2] : undefined,
+            thisArg = length > 1 ? sources[length - 1] : undefined;
+
+        if (typeof customizer == 'function') {
+          customizer = bindCallback(customizer, thisArg, 5);
+          length -= 2;
+        } else {
+          customizer = typeof thisArg == 'function' ? thisArg : undefined;
+          length -= (customizer ? 1 : 0);
+        }
+        if (guard && isIterateeCall(sources[0], sources[1], guard)) {
+          customizer = length < 3 ? undefined : customizer;
+          length = 1;
+        }
+        while (++index < length) {
+          var source = sources[index];
+          if (source) {
+            assigner(object, source, customizer);
+          }
+        }
+        return object;
+      });
+    }
+
+    /**
+     * Creates a `baseEach` or `baseEachRight` function.
+     *
+     * @private
+     * @param {Function} eachFunc The function to iterate over a collection.
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new base function.
+     */
+    function createBaseEach(eachFunc, fromRight) {
+      return function(collection, iteratee) {
+        var length = collection ? getLength(collection) : 0;
+        if (!isLength(length)) {
+          return eachFunc(collection, iteratee);
+        }
+        var index = fromRight ? length : -1,
+            iterable = toObject(collection);
+
+        while ((fromRight ? index-- : ++index < length)) {
+          if (iteratee(iterable[index], index, iterable) === false) {
+            break;
+          }
+        }
+        return collection;
+      };
+    }
+
+    /**
+     * Creates a base function for `_.forIn` or `_.forInRight`.
+     *
+     * @private
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new base function.
+     */
+    function createBaseFor(fromRight) {
+      return function(object, iteratee, keysFunc) {
+        var iterable = toObject(object),
+            props = keysFunc(object),
+            length = props.length,
+            index = fromRight ? length : -1;
+
+        while ((fromRight ? index-- : ++index < length)) {
+          var key = props[index];
+          if (iteratee(iterable[key], key, iterable) === false) {
+            break;
+          }
+        }
+        return object;
+      };
+    }
+
+    /**
+     * Creates a function that wraps `func` and invokes it with the `this`
+     * binding of `thisArg`.
+     *
+     * @private
+     * @param {Function} func The function to bind.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @returns {Function} Returns the new bound function.
+     */
+    function createBindWrapper(func, thisArg) {
+      var Ctor = createCtorWrapper(func);
+
+      function wrapper() {
+        var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+        return fn.apply(thisArg, arguments);
+      }
+      return wrapper;
+    }
+
+    /**
+     * Creates a `Set` cache object to optimize linear searches of large arrays.
+     *
+     * @private
+     * @param {Array} [values] The values to cache.
+     * @returns {null|Object} Returns the new cache object if `Set` is supported, else `null`.
+     */
+    function createCache(values) {
+      return (nativeCreate && Set) ? new SetCache(values) : null;
+    }
+
+    /**
+     * Creates a function that produces compound words out of the words in a
+     * given string.
+     *
+     * @private
+     * @param {Function} callback The function to combine each word.
+     * @returns {Function} Returns the new compounder function.
+     */
+    function createCompounder(callback) {
+      return function(string) {
+        var index = -1,
+            array = words(deburr(string)),
+            length = array.length,
+            result = '';
+
+        while (++index < length) {
+          result = callback(result, array[index], index);
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Creates a function that produces an instance of `Ctor` regardless of
+     * whether it was invoked as part of a `new` expression or by `call` or `apply`.
+     *
+     * @private
+     * @param {Function} Ctor The constructor to wrap.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createCtorWrapper(Ctor) {
+      return function() {
+        // Use a `switch` statement to work with class constructors.
+        // See http://ecma-international.org/ecma-262/6.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist
+        // for more details.
+        var args = arguments;
+        switch (args.length) {
+          case 0: return new Ctor;
+          case 1: return new Ctor(args[0]);
+          case 2: return new Ctor(args[0], args[1]);
+          case 3: return new Ctor(args[0], args[1], args[2]);
+          case 4: return new Ctor(args[0], args[1], args[2], args[3]);
+          case 5: return new Ctor(args[0], args[1], args[2], args[3], args[4]);
+          case 6: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]);
+          case 7: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
+        }
+        var thisBinding = baseCreate(Ctor.prototype),
+            result = Ctor.apply(thisBinding, args);
+
+        // Mimic the constructor's `return` behavior.
+        // See https://es5.github.io/#x13.2.2 for more details.
+        return isObject(result) ? result : thisBinding;
+      };
+    }
+
+    /**
+     * Creates a `_.curry` or `_.curryRight` function.
+     *
+     * @private
+     * @param {boolean} flag The curry bit flag.
+     * @returns {Function} Returns the new curry function.
+     */
+    function createCurry(flag) {
+      function curryFunc(func, arity, guard) {
+        if (guard && isIterateeCall(func, arity, guard)) {
+          arity = undefined;
+        }
+        var result = createWrapper(func, flag, undefined, undefined, undefined, undefined, undefined, arity);
+        result.placeholder = curryFunc.placeholder;
+        return result;
+      }
+      return curryFunc;
+    }
+
+    /**
+     * Creates a `_.defaults` or `_.defaultsDeep` function.
+     *
+     * @private
+     * @param {Function} assigner The function to assign values.
+     * @param {Function} customizer The function to customize assigned values.
+     * @returns {Function} Returns the new defaults function.
+     */
+    function createDefaults(assigner, customizer) {
+      return restParam(function(args) {
+        var object = args[0];
+        if (object == null) {
+          return object;
+        }
+        args.push(customizer);
+        return assigner.apply(undefined, args);
+      });
+    }
+
+    /**
+     * Creates a `_.max` or `_.min` function.
+     *
+     * @private
+     * @param {Function} comparator The function used to compare values.
+     * @param {*} exValue The initial extremum value.
+     * @returns {Function} Returns the new extremum function.
+     */
+    function createExtremum(comparator, exValue) {
+      return function(collection, iteratee, thisArg) {
+        if (thisArg && isIterateeCall(collection, iteratee, thisArg)) {
+          iteratee = undefined;
+        }
+        iteratee = getCallback(iteratee, thisArg, 3);
+        if (iteratee.length == 1) {
+          collection = isArray(collection) ? collection : toIterable(collection);
+          var result = arrayExtremum(collection, iteratee, comparator, exValue);
+          if (!(collection.length && result === exValue)) {
+            return result;
+          }
+        }
+        return baseExtremum(collection, iteratee, comparator, exValue);
+      };
+    }
+
+    /**
+     * Creates a `_.find` or `_.findLast` function.
+     *
+     * @private
+     * @param {Function} eachFunc The function to iterate over a collection.
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new find function.
+     */
+    function createFind(eachFunc, fromRight) {
+      return function(collection, predicate, thisArg) {
+        predicate = getCallback(predicate, thisArg, 3);
+        if (isArray(collection)) {
+          var index = baseFindIndex(collection, predicate, fromRight);
+          return index > -1 ? collection[index] : undefined;
+        }
+        return baseFind(collection, predicate, eachFunc);
+      };
+    }
+
+    /**
+     * Creates a `_.findIndex` or `_.findLastIndex` function.
+     *
+     * @private
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new find function.
+     */
+    function createFindIndex(fromRight) {
+      return function(array, predicate, thisArg) {
+        if (!(array && array.length)) {
+          return -1;
+        }
+        predicate = getCallback(predicate, thisArg, 3);
+        return baseFindIndex(array, predicate, fromRight);
+      };
+    }
+
+    /**
+     * Creates a `_.findKey` or `_.findLastKey` function.
+     *
+     * @private
+     * @param {Function} objectFunc The function to iterate over an object.
+     * @returns {Function} Returns the new find function.
+     */
+    function createFindKey(objectFunc) {
+      return function(object, predicate, thisArg) {
+        predicate = getCallback(predicate, thisArg, 3);
+        return baseFind(object, predicate, objectFunc, true);
+      };
+    }
+
+    /**
+     * Creates a `_.flow` or `_.flowRight` function.
+     *
+     * @private
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new flow function.
+     */
+    function createFlow(fromRight) {
+      return function() {
+        var wrapper,
+            length = arguments.length,
+            index = fromRight ? length : -1,
+            leftIndex = 0,
+            funcs = Array(length);
+
+        while ((fromRight ? index-- : ++index < length)) {
+          var func = funcs[leftIndex++] = arguments[index];
+          if (typeof func != 'function') {
+            throw new TypeError(FUNC_ERROR_TEXT);
+          }
+          if (!wrapper && LodashWrapper.prototype.thru && getFuncName(func) == 'wrapper') {
+            wrapper = new LodashWrapper([], true);
+          }
+        }
+        index = wrapper ? -1 : length;
+        while (++index < length) {
+          func = funcs[index];
+
+          var funcName = getFuncName(func),
+              data = funcName == 'wrapper' ? getData(func) : undefined;
+
+          if (data && isLaziable(data[0]) && data[1] == (ARY_FLAG | CURRY_FLAG | PARTIAL_FLAG | REARG_FLAG) && !data[4].length && data[9] == 1) {
+            wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]);
+          } else {
+            wrapper = (func.length == 1 && isLaziable(func)) ? wrapper[funcName]() : wrapper.thru(func);
+          }
+        }
+        return function() {
+          var args = arguments,
+              value = args[0];
+
+          if (wrapper && args.length == 1 && isArray(value) && value.length >= LARGE_ARRAY_SIZE) {
+            return wrapper.plant(value).value();
+          }
+          var index = 0,
+              result = length ? funcs[index].apply(this, args) : value;
+
+          while (++index < length) {
+            result = funcs[index].call(this, result);
+          }
+          return result;
+        };
+      };
+    }
+
+    /**
+     * Creates a function for `_.forEach` or `_.forEachRight`.
+     *
+     * @private
+     * @param {Function} arrayFunc The function to iterate over an array.
+     * @param {Function} eachFunc The function to iterate over a collection.
+     * @returns {Function} Returns the new each function.
+     */
+    function createForEach(arrayFunc, eachFunc) {
+      return function(collection, iteratee, thisArg) {
+        return (typeof iteratee == 'function' && thisArg === undefined && isArray(collection))
+          ? arrayFunc(collection, iteratee)
+          : eachFunc(collection, bindCallback(iteratee, thisArg, 3));
+      };
+    }
+
+    /**
+     * Creates a function for `_.forIn` or `_.forInRight`.
+     *
+     * @private
+     * @param {Function} objectFunc The function to iterate over an object.
+     * @returns {Function} Returns the new each function.
+     */
+    function createForIn(objectFunc) {
+      return function(object, iteratee, thisArg) {
+        if (typeof iteratee != 'function' || thisArg !== undefined) {
+          iteratee = bindCallback(iteratee, thisArg, 3);
+        }
+        return objectFunc(object, iteratee, keysIn);
+      };
+    }
+
+    /**
+     * Creates a function for `_.forOwn` or `_.forOwnRight`.
+     *
+     * @private
+     * @param {Function} objectFunc The function to iterate over an object.
+     * @returns {Function} Returns the new each function.
+     */
+    function createForOwn(objectFunc) {
+      return function(object, iteratee, thisArg) {
+        if (typeof iteratee != 'function' || thisArg !== undefined) {
+          iteratee = bindCallback(iteratee, thisArg, 3);
+        }
+        return objectFunc(object, iteratee);
+      };
+    }
+
+    /**
+     * Creates a function for `_.mapKeys` or `_.mapValues`.
+     *
+     * @private
+     * @param {boolean} [isMapKeys] Specify mapping keys instead of values.
+     * @returns {Function} Returns the new map function.
+     */
+    function createObjectMapper(isMapKeys) {
+      return function(object, iteratee, thisArg) {
+        var result = {};
+        iteratee = getCallback(iteratee, thisArg, 3);
+
+        baseForOwn(object, function(value, key, object) {
+          var mapped = iteratee(value, key, object);
+          key = isMapKeys ? mapped : key;
+          value = isMapKeys ? value : mapped;
+          result[key] = value;
+        });
+        return result;
+      };
+    }
+
+    /**
+     * Creates a function for `_.padLeft` or `_.padRight`.
+     *
+     * @private
+     * @param {boolean} [fromRight] Specify padding from the right.
+     * @returns {Function} Returns the new pad function.
+     */
+    function createPadDir(fromRight) {
+      return function(string, length, chars) {
+        string = baseToString(string);
+        return (fromRight ? string : '') + createPadding(string, length, chars) + (fromRight ? '' : string);
+      };
+    }
+
+    /**
+     * Creates a `_.partial` or `_.partialRight` function.
+     *
+     * @private
+     * @param {boolean} flag The partial bit flag.
+     * @returns {Function} Returns the new partial function.
+     */
+    function createPartial(flag) {
+      var partialFunc = restParam(function(func, partials) {
+        var holders = replaceHolders(partials, partialFunc.placeholder);
+        return createWrapper(func, flag, undefined, partials, holders);
+      });
+      return partialFunc;
+    }
+
+    /**
+     * Creates a function for `_.reduce` or `_.reduceRight`.
+     *
+     * @private
+     * @param {Function} arrayFunc The function to iterate over an array.
+     * @param {Function} eachFunc The function to iterate over a collection.
+     * @returns {Function} Returns the new each function.
+     */
+    function createReduce(arrayFunc, eachFunc) {
+      return function(collection, iteratee, accumulator, thisArg) {
+        var initFromArray = arguments.length < 3;
+        return (typeof iteratee == 'function' && thisArg === undefined && isArray(collection))
+          ? arrayFunc(collection, iteratee, accumulator, initFromArray)
+          : baseReduce(collection, getCallback(iteratee, thisArg, 4), accumulator, initFromArray, eachFunc);
+      };
+    }
+
+    /**
+     * Creates a function that wraps `func` and invokes it with optional `this`
+     * binding of, partial application, and currying.
+     *
+     * @private
+     * @param {Function|string} func The function or method name to reference.
+     * @param {number} bitmask The bitmask of flags. See `createWrapper` for more details.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {Array} [partials] The arguments to prepend to those provided to the new function.
+     * @param {Array} [holders] The `partials` placeholder indexes.
+     * @param {Array} [partialsRight] The arguments to append to those provided to the new function.
+     * @param {Array} [holdersRight] The `partialsRight` placeholder indexes.
+     * @param {Array} [argPos] The argument positions of the new function.
+     * @param {number} [ary] The arity cap of `func`.
+     * @param {number} [arity] The arity of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createHybridWrapper(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) {
+      var isAry = bitmask & ARY_FLAG,
+          isBind = bitmask & BIND_FLAG,
+          isBindKey = bitmask & BIND_KEY_FLAG,
+          isCurry = bitmask & CURRY_FLAG,
+          isCurryBound = bitmask & CURRY_BOUND_FLAG,
+          isCurryRight = bitmask & CURRY_RIGHT_FLAG,
+          Ctor = isBindKey ? undefined : createCtorWrapper(func);
+
+      function wrapper() {
+        // Avoid `arguments` object use disqualifying optimizations by
+        // converting it to an array before providing it to other functions.
+        var length = arguments.length,
+            index = length,
+            args = Array(length);
+
+        while (index--) {
+          args[index] = arguments[index];
+        }
+        if (partials) {
+          args = composeArgs(args, partials, holders);
+        }
+        if (partialsRight) {
+          args = composeArgsRight(args, partialsRight, holdersRight);
+        }
+        if (isCurry || isCurryRight) {
+          var placeholder = wrapper.placeholder,
+              argsHolders = replaceHolders(args, placeholder);
+
+          length -= argsHolders.length;
+          if (length < arity) {
+            var newArgPos = argPos ? arrayCopy(argPos) : undefined,
+                newArity = nativeMax(arity - length, 0),
+                newsHolders = isCurry ? argsHolders : undefined,
+                newHoldersRight = isCurry ? undefined : argsHolders,
+                newPartials = isCurry ? args : undefined,
+                newPartialsRight = isCurry ? undefined : args;
+
+            bitmask |= (isCurry ? PARTIAL_FLAG : PARTIAL_RIGHT_FLAG);
+            bitmask &= ~(isCurry ? PARTIAL_RIGHT_FLAG : PARTIAL_FLAG);
+
+            if (!isCurryBound) {
+              bitmask &= ~(BIND_FLAG | BIND_KEY_FLAG);
+            }
+            var newData = [func, bitmask, thisArg, newPartials, newsHolders, newPartialsRight, newHoldersRight, newArgPos, ary, newArity],
+                result = createHybridWrapper.apply(undefined, newData);
+
+            if (isLaziable(func)) {
+              setData(result, newData);
+            }
+            result.placeholder = placeholder;
+            return result;
+          }
+        }
+        var thisBinding = isBind ? thisArg : this,
+            fn = isBindKey ? thisBinding[func] : func;
+
+        if (argPos) {
+          args = reorder(args, argPos);
+        }
+        if (isAry && ary < args.length) {
+          args.length = ary;
+        }
+        if (this && this !== root && this instanceof wrapper) {
+          fn = Ctor || createCtorWrapper(func);
+        }
+        return fn.apply(thisBinding, args);
+      }
+      return wrapper;
+    }
+
+    /**
+     * Creates the padding required for `string` based on the given `length`.
+     * The `chars` string is truncated if the number of characters exceeds `length`.
+     *
+     * @private
+     * @param {string} string The string to create padding for.
+     * @param {number} [length=0] The padding length.
+     * @param {string} [chars=' '] The string used as padding.
+     * @returns {string} Returns the pad for `string`.
+     */
+    function createPadding(string, length, chars) {
+      var strLength = string.length;
+      length = +length;
+
+      if (strLength >= length || !nativeIsFinite(length)) {
+        return '';
+      }
+      var padLength = length - strLength;
+      chars = chars == null ? ' ' : (chars + '');
+      return repeat(chars, nativeCeil(padLength / chars.length)).slice(0, padLength);
+    }
+
+    /**
+     * Creates a function that wraps `func` and invokes it with the optional `this`
+     * binding of `thisArg` and the `partials` prepended to those provided to
+     * the wrapper.
+     *
+     * @private
+     * @param {Function} func The function to partially apply arguments to.
+     * @param {number} bitmask The bitmask of flags. See `createWrapper` for more details.
+     * @param {*} thisArg The `this` binding of `func`.
+     * @param {Array} partials The arguments to prepend to those provided to the new function.
+     * @returns {Function} Returns the new bound function.
+     */
+    function createPartialWrapper(func, bitmask, thisArg, partials) {
+      var isBind = bitmask & BIND_FLAG,
+          Ctor = createCtorWrapper(func);
+
+      function wrapper() {
+        // Avoid `arguments` object use disqualifying optimizations by
+        // converting it to an array before providing it `func`.
+        var argsIndex = -1,
+            argsLength = arguments.length,
+            leftIndex = -1,
+            leftLength = partials.length,
+            args = Array(leftLength + argsLength);
+
+        while (++leftIndex < leftLength) {
+          args[leftIndex] = partials[leftIndex];
+        }
+        while (argsLength--) {
+          args[leftIndex++] = arguments[++argsIndex];
+        }
+        var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+        return fn.apply(isBind ? thisArg : this, args);
+      }
+      return wrapper;
+    }
+
+    /**
+     * Creates a `_.ceil`, `_.floor`, or `_.round` function.
+     *
+     * @private
+     * @param {string} methodName The name of the `Math` method to use when rounding.
+     * @returns {Function} Returns the new round function.
+     */
+    function createRound(methodName) {
+      var func = Math[methodName];
+      return function(number, precision) {
+        precision = precision === undefined ? 0 : (+precision || 0);
+        if (precision) {
+          precision = pow(10, precision);
+          return func(number * precision) / precision;
+        }
+        return func(number);
+      };
+    }
+
+    /**
+     * Creates a `_.sortedIndex` or `_.sortedLastIndex` function.
+     *
+     * @private
+     * @param {boolean} [retHighest] Specify returning the highest qualified index.
+     * @returns {Function} Returns the new index function.
+     */
+    function createSortedIndex(retHighest) {
+      return function(array, value, iteratee, thisArg) {
+        var callback = getCallback(iteratee);
+        return (iteratee == null && callback === baseCallback)
+          ? binaryIndex(array, value, retHighest)
+          : binaryIndexBy(array, value, callback(iteratee, thisArg, 1), retHighest);
+      };
+    }
+
+    /**
+     * Creates a function that either curries or invokes `func` with optional
+     * `this` binding and partially applied arguments.
+     *
+     * @private
+     * @param {Function|string} func The function or method name to reference.
+     * @param {number} bitmask The bitmask of flags.
+     *  The bitmask may be composed of the following flags:
+     *     1 - `_.bind`
+     *     2 - `_.bindKey`
+     *     4 - `_.curry` or `_.curryRight` of a bound function
+     *     8 - `_.curry`
+     *    16 - `_.curryRight`
+     *    32 - `_.partial`
+     *    64 - `_.partialRight`
+     *   128 - `_.rearg`
+     *   256 - `_.ary`
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {Array} [partials] The arguments to be partially applied.
+     * @param {Array} [holders] The `partials` placeholder indexes.
+     * @param {Array} [argPos] The argument positions of the new function.
+     * @param {number} [ary] The arity cap of `func`.
+     * @param {number} [arity] The arity of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createWrapper(func, bitmask, thisArg, partials, holders, argPos, ary, arity) {
+      var isBindKey = bitmask & BIND_KEY_FLAG;
+      if (!isBindKey && typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      var length = partials ? partials.length : 0;
+      if (!length) {
+        bitmask &= ~(PARTIAL_FLAG | PARTIAL_RIGHT_FLAG);
+        partials = holders = undefined;
+      }
+      length -= (holders ? holders.length : 0);
+      if (bitmask & PARTIAL_RIGHT_FLAG) {
+        var partialsRight = partials,
+            holdersRight = holders;
+
+        partials = holders = undefined;
+      }
+      var data = isBindKey ? undefined : getData(func),
+          newData = [func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity];
+
+      if (data) {
+        mergeData(newData, data);
+        bitmask = newData[1];
+        arity = newData[9];
+      }
+      newData[9] = arity == null
+        ? (isBindKey ? 0 : func.length)
+        : (nativeMax(arity - length, 0) || 0);
+
+      if (bitmask == BIND_FLAG) {
+        var result = createBindWrapper(newData[0], newData[2]);
+      } else if ((bitmask == PARTIAL_FLAG || bitmask == (BIND_FLAG | PARTIAL_FLAG)) && !newData[4].length) {
+        result = createPartialWrapper.apply(undefined, newData);
+      } else {
+        result = createHybridWrapper.apply(undefined, newData);
+      }
+      var setter = data ? baseSetData : setData;
+      return setter(result, newData);
+    }
+
+    /**
+     * A specialized version of `baseIsEqualDeep` for arrays with support for
+     * partial deep comparisons.
+     *
+     * @private
+     * @param {Array} array The array to compare.
+     * @param {Array} other The other array to compare.
+     * @param {Function} equalFunc The function to determine equivalents of values.
+     * @param {Function} [customizer] The function to customize comparing arrays.
+     * @param {boolean} [isLoose] Specify performing partial comparisons.
+     * @param {Array} [stackA] Tracks traversed `value` objects.
+     * @param {Array} [stackB] Tracks traversed `other` objects.
+     * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
+     */
+    function equalArrays(array, other, equalFunc, customizer, isLoose, stackA, stackB) {
+      var index = -1,
+          arrLength = array.length,
+          othLength = other.length;
+
+      if (arrLength != othLength && !(isLoose && othLength > arrLength)) {
+        return false;
+      }
+      // Ignore non-index properties.
+      while (++index < arrLength) {
+        var arrValue = array[index],
+            othValue = other[index],
+            result = customizer ? customizer(isLoose ? othValue : arrValue, isLoose ? arrValue : othValue, index) : undefined;
+
+        if (result !== undefined) {
+          if (result) {
+            continue;
+          }
+          return false;
+        }
+        // Recursively compare arrays (susceptible to call stack limits).
+        if (isLoose) {
+          if (!arraySome(other, function(othValue) {
+                return arrValue === othValue || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB);
+              })) {
+            return false;
+          }
+        } else if (!(arrValue === othValue || equalFunc(arrValue, othValue, customizer, isLoose, stackA, stackB))) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    /**
+     * A specialized version of `baseIsEqualDeep` for comparing objects of
+     * the same `toStringTag`.
+     *
+     * **Note:** This function only supports comparing values with tags of
+     * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
+     *
+     * @private
+     * @param {Object} object The object to compare.
+     * @param {Object} other The other object to compare.
+     * @param {string} tag The `toStringTag` of the objects to compare.
+     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+     */
+    function equalByTag(object, other, tag) {
+      switch (tag) {
+        case boolTag:
+        case dateTag:
+          // Coerce dates and booleans to numbers, dates to milliseconds and booleans
+          // to `1` or `0` treating invalid dates coerced to `NaN` as not equal.
+          return +object == +other;
+
+        case errorTag:
+          return object.name == other.name && object.message == other.message;
+
+        case numberTag:
+          // Treat `NaN` vs. `NaN` as equal.
+          return (object != +object)
+            ? other != +other
+            : object == +other;
+
+        case regexpTag:
+        case stringTag:
+          // Coerce regexes to strings and treat strings primitives and string
+          // objects as equal. See https://es5.github.io/#x15.10.6.4 for more details.
+          return object == (other + '');
+      }
+      return false;
+    }
+
+    /**
+     * A specialized version of `baseIsEqualDeep` for objects with support for
+     * partial deep comparisons.
+     *
+     * @private
+     * @param {Object} object The object to compare.
+     * @param {Object} other The other object to compare.
+     * @param {Function} equalFunc The function to determine equivalents of values.
+     * @param {Function} [customizer] The function to customize comparing values.
+     * @param {boolean} [isLoose] Specify performing partial comparisons.
+     * @param {Array} [stackA] Tracks traversed `value` objects.
+     * @param {Array} [stackB] Tracks traversed `other` objects.
+     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+     */
+    function equalObjects(object, other, equalFunc, customizer, isLoose, stackA, stackB) {
+      var objProps = keys(object),
+          objLength = objProps.length,
+          othProps = keys(other),
+          othLength = othProps.length;
+
+      if (objLength != othLength && !isLoose) {
+        return false;
+      }
+      var index = objLength;
+      while (index--) {
+        var key = objProps[index];
+        if (!(isLoose ? key in other : hasOwnProperty.call(other, key))) {
+          return false;
+        }
+      }
+      var skipCtor = isLoose;
+      while (++index < objLength) {
+        key = objProps[index];
+        var objValue = object[key],
+            othValue = other[key],
+            result = customizer ? customizer(isLoose ? othValue : objValue, isLoose? objValue : othValue, key) : undefined;
+
+        // Recursively compare objects (susceptible to call stack limits).
+        if (!(result === undefined ? equalFunc(objValue, othValue, customizer, isLoose, stackA, stackB) : result)) {
+          return false;
+        }
+        skipCtor || (skipCtor = key == 'constructor');
+      }
+      if (!skipCtor) {
+        var objCtor = object.constructor,
+            othCtor = other.constructor;
+
+        // Non `Object` object instances with different constructors are not equal.
+        if (objCtor != othCtor &&
+            ('constructor' in object && 'constructor' in other) &&
+            !(typeof objCtor == 'function' && objCtor instanceof objCtor &&
+              typeof othCtor == 'function' && othCtor instanceof othCtor)) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    /**
+     * Gets the appropriate "callback" function. If the `_.callback` method is
+     * customized this function returns the custom method, otherwise it returns
+     * the `baseCallback` function. If arguments are provided the chosen function
+     * is invoked with them and its result is returned.
+     *
+     * @private
+     * @returns {Function} Returns the chosen function or its result.
+     */
+    function getCallback(func, thisArg, argCount) {
+      var result = lodash.callback || callback;
+      result = result === callback ? baseCallback : result;
+      return argCount ? result(func, thisArg, argCount) : result;
+    }
+
+    /**
+     * Gets metadata for `func`.
+     *
+     * @private
+     * @param {Function} func The function to query.
+     * @returns {*} Returns the metadata for `func`.
+     */
+    var getData = !metaMap ? noop : function(func) {
+      return metaMap.get(func);
+    };
+
+    /**
+     * Gets the name of `func`.
+     *
+     * @private
+     * @param {Function} func The function to query.
+     * @returns {string} Returns the function name.
+     */
+    function getFuncName(func) {
+      var result = func.name,
+          array = realNames[result],
+          length = array ? array.length : 0;
+
+      while (length--) {
+        var data = array[length],
+            otherFunc = data.func;
+        if (otherFunc == null || otherFunc == func) {
+          return data.name;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Gets the appropriate "indexOf" function. If the `_.indexOf` method is
+     * customized this function returns the custom method, otherwise it returns
+     * the `baseIndexOf` function. If arguments are provided the chosen function
+     * is invoked with them and its result is returned.
+     *
+     * @private
+     * @returns {Function|number} Returns the chosen function or its result.
+     */
+    function getIndexOf(collection, target, fromIndex) {
+      var result = lodash.indexOf || indexOf;
+      result = result === indexOf ? baseIndexOf : result;
+      return collection ? result(collection, target, fromIndex) : result;
+    }
+
+    /**
+     * Gets the "length" property value of `object`.
+     *
+     * **Note:** This function is used to avoid a [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792)
+     * that affects Safari on at least iOS 8.1-8.3 ARM64.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {*} Returns the "length" value.
+     */
+    var getLength = baseProperty('length');
+
+    /**
+     * Gets the propery names, values, and compare flags of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the match data of `object`.
+     */
+    function getMatchData(object) {
+      var result = pairs(object),
+          length = result.length;
+
+      while (length--) {
+        result[length][2] = isStrictComparable(result[length][1]);
+      }
+      return result;
+    }
+
+    /**
+     * Gets the native function at `key` of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {string} key The key of the method to get.
+     * @returns {*} Returns the function if it's native, else `undefined`.
+     */
+    function getNative(object, key) {
+      var value = object == null ? undefined : object[key];
+      return isNative(value) ? value : undefined;
+    }
+
+    /**
+     * Gets the view, applying any `transforms` to the `start` and `end` positions.
+     *
+     * @private
+     * @param {number} start The start of the view.
+     * @param {number} end The end of the view.
+     * @param {Array} transforms The transformations to apply to the view.
+     * @returns {Object} Returns an object containing the `start` and `end`
+     *  positions of the view.
+     */
+    function getView(start, end, transforms) {
+      var index = -1,
+          length = transforms.length;
+
+      while (++index < length) {
+        var data = transforms[index],
+            size = data.size;
+
+        switch (data.type) {
+          case 'drop':      start += size; break;
+          case 'dropRight': end -= size; break;
+          case 'take':      end = nativeMin(end, start + size); break;
+          case 'takeRight': start = nativeMax(start, end - size); break;
+        }
+      }
+      return { 'start': start, 'end': end };
+    }
+
+    /**
+     * Initializes an array clone.
+     *
+     * @private
+     * @param {Array} array The array to clone.
+     * @returns {Array} Returns the initialized clone.
+     */
+    function initCloneArray(array) {
+      var length = array.length,
+          result = new array.constructor(length);
+
+      // Add array properties assigned by `RegExp#exec`.
+      if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
+        result.index = array.index;
+        result.input = array.input;
+      }
+      return result;
+    }
+
+    /**
+     * Initializes an object clone.
+     *
+     * @private
+     * @param {Object} object The object to clone.
+     * @returns {Object} Returns the initialized clone.
+     */
+    function initCloneObject(object) {
+      var Ctor = object.constructor;
+      if (!(typeof Ctor == 'function' && Ctor instanceof Ctor)) {
+        Ctor = Object;
+      }
+      return new Ctor;
+    }
+
+    /**
+     * Initializes an object clone based on its `toStringTag`.
+     *
+     * **Note:** This function only supports cloning values with tags of
+     * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
+     *
+     * @private
+     * @param {Object} object The object to clone.
+     * @param {string} tag The `toStringTag` of the object to clone.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the initialized clone.
+     */
+    function initCloneByTag(object, tag, isDeep) {
+      var Ctor = object.constructor;
+      switch (tag) {
+        case arrayBufferTag:
+          return bufferClone(object);
+
+        case boolTag:
+        case dateTag:
+          return new Ctor(+object);
+
+        case float32Tag: case float64Tag:
+        case int8Tag: case int16Tag: case int32Tag:
+        case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:
+          var buffer = object.buffer;
+          return new Ctor(isDeep ? bufferClone(buffer) : buffer, object.byteOffset, object.length);
+
+        case numberTag:
+        case stringTag:
+          return new Ctor(object);
+
+        case regexpTag:
+          var result = new Ctor(object.source, reFlags.exec(object));
+          result.lastIndex = object.lastIndex;
+      }
+      return result;
+    }
+
+    /**
+     * Invokes the method at `path` on `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the method to invoke.
+     * @param {Array} args The arguments to invoke the method with.
+     * @returns {*} Returns the result of the invoked method.
+     */
+    function invokePath(object, path, args) {
+      if (object != null && !isKey(path, object)) {
+        path = toPath(path);
+        object = path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1));
+        path = last(path);
+      }
+      var func = object == null ? object : object[path];
+      return func == null ? undefined : func.apply(object, args);
+    }
+
+    /**
+     * Checks if `value` is array-like.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
+     */
+    function isArrayLike(value) {
+      return value != null && isLength(getLength(value));
+    }
+
+    /**
+     * Checks if `value` is a valid array-like index.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
+     * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
+     */
+    function isIndex(value, length) {
+      value = (typeof value == 'number' || reIsUint.test(value)) ? +value : -1;
+      length = length == null ? MAX_SAFE_INTEGER : length;
+      return value > -1 && value % 1 == 0 && value < length;
+    }
+
+    /**
+     * Checks if the provided arguments are from an iteratee call.
+     *
+     * @private
+     * @param {*} value The potential iteratee value argument.
+     * @param {*} index The potential iteratee index or key argument.
+     * @param {*} object The potential iteratee object argument.
+     * @returns {boolean} Returns `true` if the arguments are from an iteratee call, else `false`.
+     */
+    function isIterateeCall(value, index, object) {
+      if (!isObject(object)) {
+        return false;
+      }
+      var type = typeof index;
+      if (type == 'number'
+          ? (isArrayLike(object) && isIndex(index, object.length))
+          : (type == 'string' && index in object)) {
+        var other = object[index];
+        return value === value ? (value === other) : (other !== other);
+      }
+      return false;
+    }
+
+    /**
+     * Checks if `value` is a property name and not a property path.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @param {Object} [object] The object to query keys on.
+     * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
+     */
+    function isKey(value, object) {
+      var type = typeof value;
+      if ((type == 'string' && reIsPlainProp.test(value)) || type == 'number') {
+        return true;
+      }
+      if (isArray(value)) {
+        return false;
+      }
+      var result = !reIsDeepProp.test(value);
+      return result || (object != null && value in toObject(object));
+    }
+
+    /**
+     * Checks if `func` has a lazy counterpart.
+     *
+     * @private
+     * @param {Function} func The function to check.
+     * @returns {boolean} Returns `true` if `func` has a lazy counterpart, else `false`.
+     */
+    function isLaziable(func) {
+      var funcName = getFuncName(func);
+      if (!(funcName in LazyWrapper.prototype)) {
+        return false;
+      }
+      var other = lodash[funcName];
+      if (func === other) {
+        return true;
+      }
+      var data = getData(other);
+      return !!data && func === data[0];
+    }
+
+    /**
+     * Checks if `value` is a valid array-like length.
+     *
+     * **Note:** This function is based on [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
+     */
+    function isLength(value) {
+      return typeof value == 'number' && value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
+    }
+
+    /**
+     * Checks if `value` is suitable for strict equality comparisons, i.e. `===`.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` if suitable for strict
+     *  equality comparisons, else `false`.
+     */
+    function isStrictComparable(value) {
+      return value === value && !isObject(value);
+    }
+
+    /**
+     * Merges the function metadata of `source` into `data`.
+     *
+     * Merging metadata reduces the number of wrappers required to invoke a function.
+     * This is possible because methods like `_.bind`, `_.curry`, and `_.partial`
+     * may be applied regardless of execution order. Methods like `_.ary` and `_.rearg`
+     * augment function arguments, making the order in which they are executed important,
+     * preventing the merging of metadata. However, we make an exception for a safe
+     * common case where curried functions have `_.ary` and or `_.rearg` applied.
+     *
+     * @private
+     * @param {Array} data The destination metadata.
+     * @param {Array} source The source metadata.
+     * @returns {Array} Returns `data`.
+     */
+    function mergeData(data, source) {
+      var bitmask = data[1],
+          srcBitmask = source[1],
+          newBitmask = bitmask | srcBitmask,
+          isCommon = newBitmask < ARY_FLAG;
+
+      var isCombo =
+        (srcBitmask == ARY_FLAG && bitmask == CURRY_FLAG) ||
+        (srcBitmask == ARY_FLAG && bitmask == REARG_FLAG && data[7].length <= source[8]) ||
+        (srcBitmask == (ARY_FLAG | REARG_FLAG) && bitmask == CURRY_FLAG);
+
+      // Exit early if metadata can't be merged.
+      if (!(isCommon || isCombo)) {
+        return data;
+      }
+      // Use source `thisArg` if available.
+      if (srcBitmask & BIND_FLAG) {
+        data[2] = source[2];
+        // Set when currying a bound function.
+        newBitmask |= (bitmask & BIND_FLAG) ? 0 : CURRY_BOUND_FLAG;
+      }
+      // Compose partial arguments.
+      var value = source[3];
+      if (value) {
+        var partials = data[3];
+        data[3] = partials ? composeArgs(partials, value, source[4]) : arrayCopy(value);
+        data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : arrayCopy(source[4]);
+      }
+      // Compose partial right arguments.
+      value = source[5];
+      if (value) {
+        partials = data[5];
+        data[5] = partials ? composeArgsRight(partials, value, source[6]) : arrayCopy(value);
+        data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : arrayCopy(source[6]);
+      }
+      // Use source `argPos` if available.
+      value = source[7];
+      if (value) {
+        data[7] = arrayCopy(value);
+      }
+      // Use source `ary` if it's smaller.
+      if (srcBitmask & ARY_FLAG) {
+        data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]);
+      }
+      // Use source `arity` if one is not provided.
+      if (data[9] == null) {
+        data[9] = source[9];
+      }
+      // Use source `func` and merge bitmasks.
+      data[0] = source[0];
+      data[1] = newBitmask;
+
+      return data;
+    }
+
+    /**
+     * Used by `_.defaultsDeep` to customize its `_.merge` use.
+     *
+     * @private
+     * @param {*} objectValue The destination object property value.
+     * @param {*} sourceValue The source object property value.
+     * @returns {*} Returns the value to assign to the destination object.
+     */
+    function mergeDefaults(objectValue, sourceValue) {
+      return objectValue === undefined ? sourceValue : merge(objectValue, sourceValue, mergeDefaults);
+    }
+
+    /**
+     * A specialized version of `_.pick` which picks `object` properties specified
+     * by `props`.
+     *
+     * @private
+     * @param {Object} object The source object.
+     * @param {string[]} props The property names to pick.
+     * @returns {Object} Returns the new object.
+     */
+    function pickByArray(object, props) {
+      object = toObject(object);
+
+      var index = -1,
+          length = props.length,
+          result = {};
+
+      while (++index < length) {
+        var key = props[index];
+        if (key in object) {
+          result[key] = object[key];
+        }
+      }
+      return result;
+    }
+
+    /**
+     * A specialized version of `_.pick` which picks `object` properties `predicate`
+     * returns truthy for.
+     *
+     * @private
+     * @param {Object} object The source object.
+     * @param {Function} predicate The function invoked per iteration.
+     * @returns {Object} Returns the new object.
+     */
+    function pickByCallback(object, predicate) {
+      var result = {};
+      baseForIn(object, function(value, key, object) {
+        if (predicate(value, key, object)) {
+          result[key] = value;
+        }
+      });
+      return result;
+    }
+
+    /**
+     * Reorder `array` according to the specified indexes where the element at
+     * the first index is assigned as the first element, the element at
+     * the second index is assigned as the second element, and so on.
+     *
+     * @private
+     * @param {Array} array The array to reorder.
+     * @param {Array} indexes The arranged array indexes.
+     * @returns {Array} Returns `array`.
+     */
+    function reorder(array, indexes) {
+      var arrLength = array.length,
+          length = nativeMin(indexes.length, arrLength),
+          oldArray = arrayCopy(array);
+
+      while (length--) {
+        var index = indexes[length];
+        array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined;
+      }
+      return array;
+    }
+
+    /**
+     * Sets metadata for `func`.
+     *
+     * **Note:** If this function becomes hot, i.e. is invoked a lot in a short
+     * period of time, it will trip its breaker and transition to an identity function
+     * to avoid garbage collection pauses in V8. See [V8 issue 2070](https://code.google.com/p/v8/issues/detail?id=2070)
+     * for more details.
+     *
+     * @private
+     * @param {Function} func The function to associate metadata with.
+     * @param {*} data The metadata.
+     * @returns {Function} Returns `func`.
+     */
+    var setData = (function() {
+      var count = 0,
+          lastCalled = 0;
+
+      return function(key, value) {
+        var stamp = now(),
+            remaining = HOT_SPAN - (stamp - lastCalled);
+
+        lastCalled = stamp;
+        if (remaining > 0) {
+          if (++count >= HOT_COUNT) {
+            return key;
+          }
+        } else {
+          count = 0;
+        }
+        return baseSetData(key, value);
+      };
+    }());
+
+    /**
+     * A fallback implementation of `Object.keys` which creates an array of the
+     * own enumerable property names of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names.
+     */
+    function shimKeys(object) {
+      var props = keysIn(object),
+          propsLength = props.length,
+          length = propsLength && object.length;
+
+      var allowIndexes = !!length && isLength(length) &&
+        (isArray(object) || isArguments(object));
+
+      var index = -1,
+          result = [];
+
+      while (++index < propsLength) {
+        var key = props[index];
+        if ((allowIndexes && isIndex(key, length)) || hasOwnProperty.call(object, key)) {
+          result.push(key);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Converts `value` to an array-like object if it's not one.
+     *
+     * @private
+     * @param {*} value The value to process.
+     * @returns {Array|Object} Returns the array-like object.
+     */
+    function toIterable(value) {
+      if (value == null) {
+        return [];
+      }
+      if (!isArrayLike(value)) {
+        return values(value);
+      }
+      return isObject(value) ? value : Object(value);
+    }
+
+    /**
+     * Converts `value` to an object if it's not one.
+     *
+     * @private
+     * @param {*} value The value to process.
+     * @returns {Object} Returns the object.
+     */
+    function toObject(value) {
+      return isObject(value) ? value : Object(value);
+    }
+
+    /**
+     * Converts `value` to property path array if it's not one.
+     *
+     * @private
+     * @param {*} value The value to process.
+     * @returns {Array} Returns the property path array.
+     */
+    function toPath(value) {
+      if (isArray(value)) {
+        return value;
+      }
+      var result = [];
+      baseToString(value).replace(rePropName, function(match, number, quote, string) {
+        result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
+      });
+      return result;
+    }
+
+    /**
+     * Creates a clone of `wrapper`.
+     *
+     * @private
+     * @param {Object} wrapper The wrapper to clone.
+     * @returns {Object} Returns the cloned wrapper.
+     */
+    function wrapperClone(wrapper) {
+      return wrapper instanceof LazyWrapper
+        ? wrapper.clone()
+        : new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__, arrayCopy(wrapper.__actions__));
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates an array of elements split into groups the length of `size`.
+     * If `collection` can't be split evenly, the final chunk will be the remaining
+     * elements.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to process.
+     * @param {number} [size=1] The length of each chunk.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
+     * @returns {Array} Returns the new array containing chunks.
+     * @example
+     *
+     * _.chunk(['a', 'b', 'c', 'd'], 2);
+     * // => [['a', 'b'], ['c', 'd']]
+     *
+     * _.chunk(['a', 'b', 'c', 'd'], 3);
+     * // => [['a', 'b', 'c'], ['d']]
+     */
+    function chunk(array, size, guard) {
+      if (guard ? isIterateeCall(array, size, guard) : size == null) {
+        size = 1;
+      } else {
+        size = nativeMax(nativeFloor(size) || 1, 1);
+      }
+      var index = 0,
+          length = array ? array.length : 0,
+          resIndex = -1,
+          result = Array(nativeCeil(length / size));
+
+      while (index < length) {
+        result[++resIndex] = baseSlice(array, index, (index += size));
+      }
+      return result;
+    }
+
+    /**
+     * Creates an array with all falsey values removed. The values `false`, `null`,
+     * `0`, `""`, `undefined`, and `NaN` are falsey.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to compact.
+     * @returns {Array} Returns the new array of filtered values.
+     * @example
+     *
+     * _.compact([0, 1, false, 2, '', 3]);
+     * // => [1, 2, 3]
+     */
+    function compact(array) {
+      var index = -1,
+          length = array ? array.length : 0,
+          resIndex = -1,
+          result = [];
+
+      while (++index < length) {
+        var value = array[index];
+        if (value) {
+          result[++resIndex] = value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Creates an array of unique `array` values not included in the other
+     * provided arrays using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {...Array} [values] The arrays of values to exclude.
+     * @returns {Array} Returns the new array of filtered values.
+     * @example
+     *
+     * _.difference([1, 2, 3], [4, 2]);
+     * // => [1, 3]
+     */
+    var difference = restParam(function(array, values) {
+      return (isObjectLike(array) && isArrayLike(array))
+        ? baseDifference(array, baseFlatten(values, false, true))
+        : [];
+    });
+
+    /**
+     * Creates a slice of `array` with `n` elements dropped from the beginning.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=1] The number of elements to drop.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.drop([1, 2, 3]);
+     * // => [2, 3]
+     *
+     * _.drop([1, 2, 3], 2);
+     * // => [3]
+     *
+     * _.drop([1, 2, 3], 5);
+     * // => []
+     *
+     * _.drop([1, 2, 3], 0);
+     * // => [1, 2, 3]
+     */
+    function drop(array, n, guard) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return [];
+      }
+      if (guard ? isIterateeCall(array, n, guard) : n == null) {
+        n = 1;
+      }
+      return baseSlice(array, n < 0 ? 0 : n);
+    }
+
+    /**
+     * Creates a slice of `array` with `n` elements dropped from the end.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=1] The number of elements to drop.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.dropRight([1, 2, 3]);
+     * // => [1, 2]
+     *
+     * _.dropRight([1, 2, 3], 2);
+     * // => [1]
+     *
+     * _.dropRight([1, 2, 3], 5);
+     * // => []
+     *
+     * _.dropRight([1, 2, 3], 0);
+     * // => [1, 2, 3]
+     */
+    function dropRight(array, n, guard) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return [];
+      }
+      if (guard ? isIterateeCall(array, n, guard) : n == null) {
+        n = 1;
+      }
+      n = length - (+n || 0);
+      return baseSlice(array, 0, n < 0 ? 0 : n);
+    }
+
+    /**
+     * Creates a slice of `array` excluding elements dropped from the end.
+     * Elements are dropped until `predicate` returns falsey. The predicate is
+     * bound to `thisArg` and invoked with three arguments: (value, index, array).
+     *
+     * If a property name is provided for `predicate` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `predicate` the created `_.matches` style
+     * callback returns `true` for elements that match the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {Function|Object|string} [predicate=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `predicate`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.dropRightWhile([1, 2, 3], function(n) {
+     *   return n > 1;
+     * });
+     * // => [1]
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': true },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': false }
+     * ];
+     *
+     * // using the `_.matches` callback shorthand
+     * _.pluck(_.dropRightWhile(users, { 'user': 'pebbles', 'active': false }), 'user');
+     * // => ['barney', 'fred']
+     *
+     * // using the `_.matchesProperty` callback shorthand
+     * _.pluck(_.dropRightWhile(users, 'active', false), 'user');
+     * // => ['barney']
+     *
+     * // using the `_.property` callback shorthand
+     * _.pluck(_.dropRightWhile(users, 'active'), 'user');
+     * // => ['barney', 'fred', 'pebbles']
+     */
+    function dropRightWhile(array, predicate, thisArg) {
+      return (array && array.length)
+        ? baseWhile(array, getCallback(predicate, thisArg, 3), true, true)
+        : [];
+    }
+
+    /**
+     * Creates a slice of `array` excluding elements dropped from the beginning.
+     * Elements are dropped until `predicate` returns falsey. The predicate is
+     * bound to `thisArg` and invoked with three arguments: (value, index, array).
+     *
+     * If a property name is provided for `predicate` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `predicate` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {Function|Object|string} [predicate=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `predicate`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.dropWhile([1, 2, 3], function(n) {
+     *   return n < 3;
+     * });
+     * // => [3]
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': false },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': true }
+     * ];
+     *
+     * // using the `_.matches` callback shorthand
+     * _.pluck(_.dropWhile(users, { 'user': 'barney', 'active': false }), 'user');
+     * // => ['fred', 'pebbles']
+     *
+     * // using the `_.matchesProperty` callback shorthand
+     * _.pluck(_.dropWhile(users, 'active', false), 'user');
+     * // => ['pebbles']
+     *
+     * // using the `_.property` callback shorthand
+     * _.pluck(_.dropWhile(users, 'active'), 'user');
+     * // => ['barney', 'fred', 'pebbles']
+     */
+    function dropWhile(array, predicate, thisArg) {
+      return (array && array.length)
+        ? baseWhile(array, getCallback(predicate, thisArg, 3), true)
+        : [];
+    }
+
+    /**
+     * Fills elements of `array` with `value` from `start` up to, but not
+     * including, `end`.
+     *
+     * **Note:** This method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to fill.
+     * @param {*} value The value to fill `array` with.
+     * @param {number} [start=0] The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [1, 2, 3];
+     *
+     * _.fill(array, 'a');
+     * console.log(array);
+     * // => ['a', 'a', 'a']
+     *
+     * _.fill(Array(3), 2);
+     * // => [2, 2, 2]
+     *
+     * _.fill([4, 6, 8], '*', 1, 2);
+     * // => [4, '*', 8]
+     */
+    function fill(array, value, start, end) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return [];
+      }
+      if (start && typeof start != 'number' && isIterateeCall(array, value, start)) {
+        start = 0;
+        end = length;
+      }
+      return baseFill(array, value, start, end);
+    }
+
+    /**
+     * This method is like `_.find` except that it returns the index of the first
+     * element `predicate` returns truthy for instead of the element itself.
+     *
+     * If a property name is provided for `predicate` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `predicate` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to search.
+     * @param {Function|Object|string} [predicate=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `predicate`.
+     * @returns {number} Returns the index of the found element, else `-1`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': false },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': true }
+     * ];
+     *
+     * _.findIndex(users, function(chr) {
+     *   return chr.user == 'barney';
+     * });
+     * // => 0
+     *
+     * // using the `_.matches` callback shorthand
+     * _.findIndex(users, { 'user': 'fred', 'active': false });
+     * // => 1
+     *
+     * // using the `_.matchesProperty` callback shorthand
+     * _.findIndex(users, 'active', false);
+     * // => 0
+     *
+     * // using the `_.property` callback shorthand
+     * _.findIndex(users, 'active');
+     * // => 2
+     */
+    var findIndex = createFindIndex();
+
+    /**
+     * This method is like `_.findIndex` except that it iterates over elements
+     * of `collection` from right to left.
+     *
+     * If a property name is provided for `predicate` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `predicate` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to search.
+     * @param {Function|Object|string} [predicate=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `predicate`.
+     * @returns {number} Returns the index of the found element, else `-1`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': true },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': false }
+     * ];
+     *
+     * _.findLastIndex(users, function(chr) {
+     *   return chr.user == 'pebbles';
+     * });
+     * // => 2
+     *
+     * // using the `_.matches` callback shorthand
+     * _.findLastIndex(users, { 'user': 'barney', 'active': true });
+     * // => 0
+     *
+     * // using the `_.matchesProperty` callback shorthand
+     * _.findLastIndex(users, 'active', false);
+     * // => 2
+     *
+     * // using the `_.property` callback shorthand
+     * _.findLastIndex(users, 'active');
+     * // => 0
+     */
+    var findLastIndex = createFindIndex(true);
+
+    /**
+     * Gets the first element of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @alias head
+     * @category Array
+     * @param {Array} array The array to query.
+     * @returns {*} Returns the first element of `array`.
+     * @example
+     *
+     * _.first([1, 2, 3]);
+     * // => 1
+     *
+     * _.first([]);
+     * // => undefined
+     */
+    function first(array) {
+      return array ? array[0] : undefined;
+    }
+
+    /**
+     * Flattens a nested array. If `isDeep` is `true` the array is recursively
+     * flattened, otherwise it is only flattened a single level.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to flatten.
+     * @param {boolean} [isDeep] Specify a deep flatten.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * _.flatten([1, [2, 3, [4]]]);
+     * // => [1, 2, 3, [4]]
+     *
+     * // using `isDeep`
+     * _.flatten([1, [2, 3, [4]]], true);
+     * // => [1, 2, 3, 4]
+     */
+    function flatten(array, isDeep, guard) {
+      var length = array ? array.length : 0;
+      if (guard && isIterateeCall(array, isDeep, guard)) {
+        isDeep = false;
+      }
+      return length ? baseFlatten(array, isDeep) : [];
+    }
+
+    /**
+     * Recursively flattens a nested array.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to recursively flatten.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * _.flattenDeep([1, [2, 3, [4]]]);
+     * // => [1, 2, 3, 4]
+     */
+    function flattenDeep(array) {
+      var length = array ? array.length : 0;
+      return length ? baseFlatten(array, true) : [];
+    }
+
+    /**
+     * Gets the index at which the first occurrence of `value` is found in `array`
+     * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons. If `fromIndex` is negative, it is used as the offset
+     * from the end of `array`. If `array` is sorted providing `true` for `fromIndex`
+     * performs a faster binary search.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to search.
+     * @param {*} value The value to search for.
+     * @param {boolean|number} [fromIndex=0] The index to search from or `true`
+     *  to perform a binary search on a sorted array.
+     * @returns {number} Returns the index of the matched value, else `-1`.
+     * @example
+     *
+     * _.indexOf([1, 2, 1, 2], 2);
+     * // => 1
+     *
+     * // using `fromIndex`
+     * _.indexOf([1, 2, 1, 2], 2, 2);
+     * // => 3
+     *
+     * // performing a binary search
+     * _.indexOf([1, 1, 2, 2], 2, true);
+     * // => 2
+     */
+    function indexOf(array, value, fromIndex) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return -1;
+      }
+      if (typeof fromIndex == 'number') {
+        fromIndex = fromIndex < 0 ? nativeMax(length + fromIndex, 0) : fromIndex;
+      } else if (fromIndex) {
+        var index = binaryIndex(array, value);
+        if (index < length &&
+            (value === value ? (value === array[index]) : (array[index] !== array[index]))) {
+          return index;
+        }
+        return -1;
+      }
+      return baseIndexOf(array, value, fromIndex || 0);
+    }
+
+    /**
+     * Gets all but the last element of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to query.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.initial([1, 2, 3]);
+     * // => [1, 2]
+     */
+    function initial(array) {
+      return dropRight(array, 1);
+    }
+
+    /**
+     * Creates an array of unique values that are included in all of the provided
+     * arrays using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @returns {Array} Returns the new array of shared values.
+     * @example
+     * _.intersection([1, 2], [4, 2], [2, 1]);
+     * // => [2]
+     */
+    var intersection = restParam(function(arrays) {
+      var othLength = arrays.length,
+          othIndex = othLength,
+          caches = Array(length),
+          indexOf = getIndexOf(),
+          isCommon = indexOf == baseIndexOf,
+          result = [];
+
+      while (othIndex--) {
+        var value = arrays[othIndex] = isArrayLike(value = arrays[othIndex]) ? value : [];
+        caches[othIndex] = (isCommon && value.length >= 120) ? createCache(othIndex && value) : null;
+      }
+      var array = arrays[0],
+          index = -1,
+          length = array ? array.length : 0,
+          seen = caches[0];
+
+      outer:
+      while (++index < length) {
+        value = array[index];
+        if ((seen ? cacheIndexOf(seen, value) : indexOf(result, value, 0)) < 0) {
+          var othIndex = othLength;
+          while (--othIndex) {
+            var cache = caches[othIndex];
+            if ((cache ? cacheIndexOf(cache, value) : indexOf(arrays[othIndex], value, 0)) < 0) {
+              continue outer;
+            }
+          }
+          if (seen) {
+            seen.push(value);
+          }
+          result.push(value);
+        }
+      }
+      return result;
+    });
+
+    /**
+     * Gets the last element of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to query.
+     * @returns {*} Returns the last element of `array`.
+     * @example
+     *
+     * _.last([1, 2, 3]);
+     * // => 3
+     */
+    function last(array) {
+      var length = array ? array.length : 0;
+      return length ? array[length - 1] : undefined;
+    }
+
+    /**
+     * This method is like `_.indexOf` except that it iterates over elements of
+     * `array` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to search.
+     * @param {*} value The value to search for.
+     * @param {boolean|number} [fromIndex=array.length-1] The index to search from
+     *  or `true` to perform a binary search on a sorted array.
+     * @returns {number} Returns the index of the matched value, else `-1`.
+     * @example
+     *
+     * _.lastIndexOf([1, 2, 1, 2], 2);
+     * // => 3
+     *
+     * // using `fromIndex`
+     * _.lastIndexOf([1, 2, 1, 2], 2, 2);
+     * // => 1
+     *
+     * // performing a binary search
+     * _.lastIndexOf([1, 1, 2, 2], 2, true);
+     * // => 3
+     */
+    function lastIndexOf(array, value, fromIndex) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return -1;
+      }
+      var index = length;
+      if (typeof fromIndex == 'number') {
+        index = (fromIndex < 0 ? nativeMax(length + fromIndex, 0) : nativeMin(fromIndex || 0, length - 1)) + 1;
+      } else if (fromIndex) {
+        index = binaryIndex(array, value, true) - 1;
+        var other = array[index];
+        if (value === value ? (value === other) : (other !== other)) {
+          return index;
+        }
+        return -1;
+      }
+      if (value !== value) {
+        return indexOfNaN(array, index, true);
+      }
+      while (index--) {
+        if (array[index] === value) {
+          return index;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * Removes all provided values from `array` using
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * **Note:** Unlike `_.without`, this method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {...*} [values] The values to remove.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [1, 2, 3, 1, 2, 3];
+     *
+     * _.pull(array, 2, 3);
+     * console.log(array);
+     * // => [1, 1]
+     */
+    function pull() {
+      var args = arguments,
+          array = args[0];
+
+      if (!(array && array.length)) {
+        return array;
+      }
+      var index = 0,
+          indexOf = getIndexOf(),
+          length = args.length;
+
+      while (++index < length) {
+        var fromIndex = 0,
+            value = args[index];
+
+        while ((fromIndex = indexOf(array, value, fromIndex)) > -1) {
+          splice.call(array, fromIndex, 1);
+        }
+      }
+      return array;
+    }
+
+    /**
+     * Removes elements from `array` corresponding to the given indexes and returns
+     * an array of the removed elements. Indexes may be specified as an array of
+     * indexes or as individual arguments.
+     *
+     * **Note:** Unlike `_.at`, this method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {...(number|number[])} [indexes] The indexes of elements to remove,
+     *  specified as individual indexes or arrays of indexes.
+     * @returns {Array} Returns the new array of removed elements.
+     * @example
+     *
+     * var array = [5, 10, 15, 20];
+     * var evens = _.pullAt(array, 1, 3);
+     *
+     * console.log(array);
+     * // => [5, 15]
+     *
+     * console.log(evens);
+     * // => [10, 20]
+     */
+    var pullAt = restParam(function(array, indexes) {
+      indexes = baseFlatten(indexes);
+
+      var result = baseAt(array, indexes);
+      basePullAt(array, indexes.sort(baseCompareAscending));
+      return result;
+    });
+
+    /**
+     * Removes all elements from `array` that `predicate` returns truthy for
+     * and returns an array of the removed elements. The predicate is bound to
+     * `thisArg` and invoked with three arguments: (value, index, array).
+     *
+     * If a property name is provided for `predicate` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `predicate` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * **Note:** Unlike `_.filter`, this method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {Function|Object|string} [predicate=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `predicate`.
+     * @returns {Array} Returns the new array of removed elements.
+     * @example
+     *
+     * var array = [1, 2, 3, 4];
+     * var evens = _.remove(array, function(n) {
+     *   return n % 2 == 0;
+     * });
+     *
+     * console.log(array);
+     * // => [1, 3]
+     *
+     * console.log(evens);
+     * // => [2, 4]
+     */
+    function remove(array, predicate, thisArg) {
+      var result = [];
+      if (!(array && array.length)) {
+        return result;
+      }
+      var index = -1,
+          indexes = [],
+          length = array.length;
+
+      predicate = getCallback(predicate, thisArg, 3);
+      while (++index < length) {
+        var value = array[index];
+        if (predicate(value, index, array)) {
+          result.push(value);
+          indexes.push(index);
+        }
+      }
+      basePullAt(array, indexes);
+      return result;
+    }
+
+    /**
+     * Gets all but the first element of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @alias tail
+     * @category Array
+     * @param {Array} array The array to query.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.rest([1, 2, 3]);
+     * // => [2, 3]
+     */
+    function rest(array) {
+      return drop(array, 1);
+    }
+
+    /**
+     * Creates a slice of `array` from `start` up to, but not including, `end`.
+     *
+     * **Note:** This method is used instead of `Array#slice` to support node
+     * lists in IE < 9 and to ensure dense arrays are returned.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to slice.
+     * @param {number} [start=0] The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns the slice of `array`.
+     */
+    function slice(array, start, end) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return [];
+      }
+      if (end && typeof end != 'number' && isIterateeCall(array, start, end)) {
+        start = 0;
+        end = length;
+      }
+      return baseSlice(array, start, end);
+    }
+
+    /**
+     * Uses a binary search to determine the lowest index at which `value` should
+     * be inserted into `array` in order to maintain its sort order. If an iteratee
+     * function is provided it is invoked for `value` and each element of `array`
+     * to compute their sort ranking. The iteratee is bound to `thisArg` and
+     * invoked with one argument; (value).
+     *
+     * If a property name is provided for `iteratee` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `iteratee` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {Function|Object|string} [iteratee=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * _.sortedIndex([30, 50], 40);
+     * // => 1
+     *
+     * _.sortedIndex([4, 4, 5, 5], 5);
+     * // => 2
+     *
+     * var dict = { 'data': { 'thirty': 30, 'forty': 40, 'fifty': 50 } };
+     *
+     * // using an iteratee function
+     * _.sortedIndex(['thirty', 'fifty'], 'forty', function(word) {
+     *   return this.data[word];
+     * }, dict);
+     * // => 1
+     *
+     * // using the `_.property` callback shorthand
+     * _.sortedIndex([{ 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x');
+     * // => 1
+     */
+    var sortedIndex = createSortedIndex();
+
+    /**
+     * This method is like `_.sortedIndex` except that it returns the highest
+     * index at which `value` should be inserted into `array` in order to
+     * maintain its sort order.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {Function|Object|string} [iteratee=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * _.sortedLastIndex([4, 4, 5, 5], 5);
+     * // => 4
+     */
+    var sortedLastIndex = createSortedIndex(true);
+
+    /**
+     * Creates a slice of `array` with `n` elements taken from the beginning.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=1] The number of elements to take.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.take([1, 2, 3]);
+     * // => [1]
+     *
+     * _.take([1, 2, 3], 2);
+     * // => [1, 2]
+     *
+     * _.take([1, 2, 3], 5);
+     * // => [1, 2, 3]
+     *
+     * _.take([1, 2, 3], 0);
+     * // => []
+     */
+    function take(array, n, guard) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return [];
+      }
+      if (guard ? isIterateeCall(array, n, guard) : n == null) {
+        n = 1;
+      }
+      return baseSlice(array, 0, n < 0 ? 0 : n);
+    }
+
+    /**
+     * Creates a slice of `array` with `n` elements taken from the end.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=1] The number of elements to take.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.takeRight([1, 2, 3]);
+     * // => [3]
+     *
+     * _.takeRight([1, 2, 3], 2);
+     * // => [2, 3]
+     *
+     * _.takeRight([1, 2, 3], 5);
+     * // => [1, 2, 3]
+     *
+     * _.takeRight([1, 2, 3], 0);
+     * // => []
+     */
+    function takeRight(array, n, guard) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return [];
+      }
+      if (guard ? isIterateeCall(array, n, guard) : n == null) {
+        n = 1;
+      }
+      n = length - (+n || 0);
+      return baseSlice(array, n < 0 ? 0 : n);
+    }
+
+    /**
+     * Creates a slice of `array` with elements taken from the end. Elements are
+     * taken until `predicate` returns falsey. The predicate is bound to `thisArg`
+     * and invoked with three arguments: (value, index, array).
+     *
+     * If a property name is provided for `predicate` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `predicate` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {Function|Object|string} [predicate=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `predicate`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.takeRightWhile([1, 2, 3], function(n) {
+     *   return n > 1;
+     * });
+     * // => [2, 3]
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': true },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': false }
+     * ];
+     *
+     * // using the `_.matches` callback shorthand
+     * _.pluck(_.takeRightWhile(users, { 'user': 'pebbles', 'active': false }), 'user');
+     * // => ['pebbles']
+     *
+     * // using the `_.matchesProperty` callback shorthand
+     * _.pluck(_.takeRightWhile(users, 'active', false), 'user');
+     * // => ['fred', 'pebbles']
+     *
+     * // using the `_.property` callback shorthand
+     * _.pluck(_.takeRightWhile(users, 'active'), 'user');
+     * // => []
+     */
+    function takeRightWhile(array, predicate, thisArg) {
+      return (array && array.length)
+        ? baseWhile(array, getCallback(predicate, thisArg, 3), false, true)
+        : [];
+    }
+
+    /**
+     * Creates a slice of `array` with elements taken from the beginning. Elements
+     * are taken until `predicate` returns falsey. The predicate is bound to
+     * `thisArg` and invoked with three arguments: (value, index, array).
+     *
+     * If a property name is provided for `predicate` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `predicate` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {Function|Object|string} [predicate=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `predicate`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.takeWhile([1, 2, 3], function(n) {
+     *   return n < 3;
+     * });
+     * // => [1, 2]
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': false },
+     *   { 'user': 'fred',    'active': false},
+     *   { 'user': 'pebbles', 'active': true }
+     * ];
+     *
+     * // using the `_.matches` callback shorthand
+     * _.pluck(_.takeWhile(users, { 'user': 'barney', 'active': false }), 'user');
+     * // => ['barney']
+     *
+     * // using the `_.matchesProperty` callback shorthand
+     * _.pluck(_.takeWhile(users, 'active', false), 'user');
+     * // => ['barney', 'fred']
+     *
+     * // using the `_.property` callback shorthand
+     * _.pluck(_.takeWhile(users, 'active'), 'user');
+     * // => []
+     */
+    function takeWhile(array, predicate, thisArg) {
+      return (array && array.length)
+        ? baseWhile(array, getCallback(predicate, thisArg, 3))
+        : [];
+    }
+
+    /**
+     * Creates an array of unique values, in order, from all of the provided arrays
+     * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @returns {Array} Returns the new array of combined values.
+     * @example
+     *
+     * _.union([1, 2], [4, 2], [2, 1]);
+     * // => [1, 2, 4]
+     */
+    var union = restParam(function(arrays) {
+      return baseUniq(baseFlatten(arrays, false, true));
+    });
+
+    /**
+     * Creates a duplicate-free version of an array, using
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons, in which only the first occurence of each element
+     * is kept. Providing `true` for `isSorted` performs a faster search algorithm
+     * for sorted arrays. If an iteratee function is provided it is invoked for
+     * each element in the array to generate the criterion by which uniqueness
+     * is computed. The `iteratee` is bound to `thisArg` and invoked with three
+     * arguments: (value, index, array).
+     *
+     * If a property name is provided for `iteratee` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `iteratee` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias unique
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {boolean} [isSorted] Specify the array is sorted.
+     * @param {Function|Object|string} [iteratee] The function invoked per iteration.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {Array} Returns the new duplicate-value-free array.
+     * @example
+     *
+     * _.uniq([2, 1, 2]);
+     * // => [2, 1]
+     *
+     * // using `isSorted`
+     * _.uniq([1, 1, 2], true);
+     * // => [1, 2]
+     *
+     * // using an iteratee function
+     * _.uniq([1, 2.5, 1.5, 2], function(n) {
+     *   return this.floor(n);
+     * }, Math);
+     * // => [1, 2.5]
+     *
+     * // using the `_.property` callback shorthand
+     * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
+     * // => [{ 'x': 1 }, { 'x': 2 }]
+     */
+    function uniq(array, isSorted, iteratee, thisArg) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return [];
+      }
+      if (isSorted != null && typeof isSorted != 'boolean') {
+        thisArg = iteratee;
+        iteratee = isIterateeCall(array, isSorted, thisArg) ? undefined : isSorted;
+        isSorted = false;
+      }
+      var callback = getCallback();
+      if (!(iteratee == null && callback === baseCallback)) {
+        iteratee = callback(iteratee, thisArg, 3);
+      }
+      return (isSorted && getIndexOf() == baseIndexOf)
+        ? sortedUniq(array, iteratee)
+        : baseUniq(array, iteratee);
+    }
+
+    /**
+     * This method is like `_.zip` except that it accepts an array of grouped
+     * elements and creates an array regrouping the elements to their pre-zip
+     * configuration.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array of grouped elements to process.
+     * @returns {Array} Returns the new array of regrouped elements.
+     * @example
+     *
+     * var zipped = _.zip(['fred', 'barney'], [30, 40], [true, false]);
+     * // => [['fred', 30, true], ['barney', 40, false]]
+     *
+     * _.unzip(zipped);
+     * // => [['fred', 'barney'], [30, 40], [true, false]]
+     */
+    function unzip(array) {
+      if (!(array && array.length)) {
+        return [];
+      }
+      var index = -1,
+          length = 0;
+
+      array = arrayFilter(array, function(group) {
+        if (isArrayLike(group)) {
+          length = nativeMax(group.length, length);
+          return true;
+        }
+      });
+      var result = Array(length);
+      while (++index < length) {
+        result[index] = arrayMap(array, baseProperty(index));
+      }
+      return result;
+    }
+
+    /**
+     * This method is like `_.unzip` except that it accepts an iteratee to specify
+     * how regrouped values should be combined. The `iteratee` is bound to `thisArg`
+     * and invoked with four arguments: (accumulator, value, index, group).
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array of grouped elements to process.
+     * @param {Function} [iteratee] The function to combine regrouped values.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {Array} Returns the new array of regrouped elements.
+     * @example
+     *
+     * var zipped = _.zip([1, 2], [10, 20], [100, 200]);
+     * // => [[1, 10, 100], [2, 20, 200]]
+     *
+     * _.unzipWith(zipped, _.add);
+     * // => [3, 30, 300]
+     */
+    function unzipWith(array, iteratee, thisArg) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return [];
+      }
+      var result = unzip(array);
+      if (iteratee == null) {
+        return result;
+      }
+      iteratee = bindCallback(iteratee, thisArg, 4);
+      return arrayMap(result, function(group) {
+        return arrayReduce(group, iteratee, undefined, true);
+      });
+    }
+
+    /**
+     * Creates an array excluding all provided values using
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {Array} array The array to filter.
+     * @param {...*} [values] The values to exclude.
+     * @returns {Array} Returns the new array of filtered values.
+     * @example
+     *
+     * _.without([1, 2, 1, 3], 1, 2);
+     * // => [3]
+     */
+    var without = restParam(function(array, values) {
+      return isArrayLike(array)
+        ? baseDifference(array, values)
+        : [];
+    });
+
+    /**
+     * Creates an array of unique values that is the [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference)
+     * of the provided arrays.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @returns {Array} Returns the new array of values.
+     * @example
+     *
+     * _.xor([1, 2], [4, 2]);
+     * // => [1, 4]
+     */
+    function xor() {
+      var index = -1,
+          length = arguments.length;
+
+      while (++index < length) {
+        var array = arguments[index];
+        if (isArrayLike(array)) {
+          var result = result
+            ? arrayPush(baseDifference(result, array), baseDifference(array, result))
+            : array;
+        }
+      }
+      return result ? baseUniq(result) : [];
+    }
+
+    /**
+     * Creates an array of grouped elements, the first of which contains the first
+     * elements of the given arrays, the second of which contains the second elements
+     * of the given arrays, and so on.
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {...Array} [arrays] The arrays to process.
+     * @returns {Array} Returns the new array of grouped elements.
+     * @example
+     *
+     * _.zip(['fred', 'barney'], [30, 40], [true, false]);
+     * // => [['fred', 30, true], ['barney', 40, false]]
+     */
+    var zip = restParam(unzip);
+
+    /**
+     * The inverse of `_.pairs`; this method returns an object composed from arrays
+     * of property names and values. Provide either a single two dimensional array,
+     * e.g. `[[key1, value1], [key2, value2]]` or two arrays, one of property names
+     * and one of corresponding values.
+     *
+     * @static
+     * @memberOf _
+     * @alias object
+     * @category Array
+     * @param {Array} props The property names.
+     * @param {Array} [values=[]] The property values.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * _.zipObject([['fred', 30], ['barney', 40]]);
+     * // => { 'fred': 30, 'barney': 40 }
+     *
+     * _.zipObject(['fred', 'barney'], [30, 40]);
+     * // => { 'fred': 30, 'barney': 40 }
+     */
+    function zipObject(props, values) {
+      var index = -1,
+          length = props ? props.length : 0,
+          result = {};
+
+      if (length && !values && !isArray(props[0])) {
+        values = [];
+      }
+      while (++index < length) {
+        var key = props[index];
+        if (values) {
+          result[key] = values[index];
+        } else if (key) {
+          result[key[0]] = key[1];
+        }
+      }
+      return result;
+    }
+
+    /**
+     * This method is like `_.zip` except that it accepts an iteratee to specify
+     * how grouped values should be combined. The `iteratee` is bound to `thisArg`
+     * and invoked with four arguments: (accumulator, value, index, group).
+     *
+     * @static
+     * @memberOf _
+     * @category Array
+     * @param {...Array} [arrays] The arrays to process.
+     * @param {Function} [iteratee] The function to combine grouped values.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {Array} Returns the new array of grouped elements.
+     * @example
+     *
+     * _.zipWith([1, 2], [10, 20], [100, 200], _.add);
+     * // => [111, 222]
+     */
+    var zipWith = restParam(function(arrays) {
+      var length = arrays.length,
+          iteratee = length > 2 ? arrays[length - 2] : undefined,
+          thisArg = length > 1 ? arrays[length - 1] : undefined;
+
+      if (length > 2 && typeof iteratee == 'function') {
+        length -= 2;
+      } else {
+        iteratee = (length > 1 && typeof thisArg == 'function') ? (--length, thisArg) : undefined;
+        thisArg = undefined;
+      }
+      arrays.length = length;
+      return unzipWith(arrays, iteratee, thisArg);
+    });
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a `lodash` object that wraps `value` with explicit method
+     * chaining enabled.
+     *
+     * @static
+     * @memberOf _
+     * @category Chain
+     * @param {*} value The value to wrap.
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'age': 36 },
+     *   { 'user': 'fred',    'age': 40 },
+     *   { 'user': 'pebbles', 'age': 1 }
+     * ];
+     *
+     * var youngest = _.chain(users)
+     *   .sortBy('age')
+     *   .map(function(chr) {
+     *     return chr.user + ' is ' + chr.age;
+     *   })
+     *   .first()
+     *   .value();
+     * // => 'pebbles is 1'
+     */
+    function chain(value) {
+      var result = lodash(value);
+      result.__chain__ = true;
+      return result;
+    }
+
+    /**
+     * This method invokes `interceptor` and returns `value`. The interceptor is
+     * bound to `thisArg` and invoked with one argument; (value). The purpose of
+     * this method is to "tap into" a method chain in order to perform operations
+     * on intermediate results within the chain.
+     *
+     * @static
+     * @memberOf _
+     * @category Chain
+     * @param {*} value The value to provide to `interceptor`.
+     * @param {Function} interceptor The function to invoke.
+     * @param {*} [thisArg] The `this` binding of `interceptor`.
+     * @returns {*} Returns `value`.
+     * @example
+     *
+     * _([1, 2, 3])
+     *  .tap(function(array) {
+     *    array.pop();
+     *  })
+     *  .reverse()
+     *  .value();
+     * // => [2, 1]
+     */
+    function tap(value, interceptor, thisArg) {
+      interceptor.call(thisArg, value);
+      return value;
+    }
+
+    /**
+     * This method is like `_.tap` except that it returns the result of `interceptor`.
+     *
+     * @static
+     * @memberOf _
+     * @category Chain
+     * @param {*} value The value to provide to `interceptor`.
+     * @param {Function} interceptor The function to invoke.
+     * @param {*} [thisArg] The `this` binding of `interceptor`.
+     * @returns {*} Returns the result of `interceptor`.
+     * @example
+     *
+     * _('  abc  ')
+     *  .chain()
+     *  .trim()
+     *  .thru(function(value) {
+     *    return [value];
+     *  })
+     *  .value();
+     * // => ['abc']
+     */
+    function thru(value, interceptor, thisArg) {
+      return interceptor.call(thisArg, value);
+    }
+
+    /**
+     * Enables explicit method chaining on the wrapper object.
+     *
+     * @name chain
+     * @memberOf _
+     * @category Chain
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36 },
+     *   { 'user': 'fred',   'age': 40 }
+     * ];
+     *
+     * // without explicit chaining
+     * _(users).first();
+     * // => { 'user': 'barney', 'age': 36 }
+     *
+     * // with explicit chaining
+     * _(users).chain()
+     *   .first()
+     *   .pick('user')
+     *   .value();
+     * // => { 'user': 'barney' }
+     */
+    function wrapperChain() {
+      return chain(this);
+    }
+
+    /**
+     * Executes the chained sequence and returns the wrapped result.
+     *
+     * @name commit
+     * @memberOf _
+     * @category Chain
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var array = [1, 2];
+     * var wrapped = _(array).push(3);
+     *
+     * console.log(array);
+     * // => [1, 2]
+     *
+     * wrapped = wrapped.commit();
+     * console.log(array);
+     * // => [1, 2, 3]
+     *
+     * wrapped.last();
+     * // => 3
+     *
+     * console.log(array);
+     * // => [1, 2, 3]
+     */
+    function wrapperCommit() {
+      return new LodashWrapper(this.value(), this.__chain__);
+    }
+
+    /**
+     * Creates a new array joining a wrapped array with any additional arrays
+     * and/or values.
+     *
+     * @name concat
+     * @memberOf _
+     * @category Chain
+     * @param {...*} [values] The values to concatenate.
+     * @returns {Array} Returns the new concatenated array.
+     * @example
+     *
+     * var array = [1];
+     * var wrapped = _(array).concat(2, [3], [[4]]);
+     *
+     * console.log(wrapped.value());
+     * // => [1, 2, 3, [4]]
+     *
+     * console.log(array);
+     * // => [1]
+     */
+    var wrapperConcat = restParam(function(values) {
+      values = baseFlatten(values);
+      return this.thru(function(array) {
+        return arrayConcat(isArray(array) ? array : [toObject(array)], values);
+      });
+    });
+
+    /**
+     * Creates a clone of the chained sequence planting `value` as the wrapped value.
+     *
+     * @name plant
+     * @memberOf _
+     * @category Chain
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var array = [1, 2];
+     * var wrapped = _(array).map(function(value) {
+     *   return Math.pow(value, 2);
+     * });
+     *
+     * var other = [3, 4];
+     * var otherWrapped = wrapped.plant(other);
+     *
+     * otherWrapped.value();
+     * // => [9, 16]
+     *
+     * wrapped.value();
+     * // => [1, 4]
+     */
+    function wrapperPlant(value) {
+      var result,
+          parent = this;
+
+      while (parent instanceof baseLodash) {
+        var clone = wrapperClone(parent);
+        if (result) {
+          previous.__wrapped__ = clone;
+        } else {
+          result = clone;
+        }
+        var previous = clone;
+        parent = parent.__wrapped__;
+      }
+      previous.__wrapped__ = value;
+      return result;
+    }
+
+    /**
+     * Reverses the wrapped array so the first element becomes the last, the
+     * second element becomes the second to last, and so on.
+     *
+     * **Note:** This method mutates the wrapped array.
+     *
+     * @name reverse
+     * @memberOf _
+     * @category Chain
+     * @returns {Object} Returns the new reversed `lodash` wrapper instance.
+     * @example
+     *
+     * var array = [1, 2, 3];
+     *
+     * _(array).reverse().value()
+     * // => [3, 2, 1]
+     *
+     * console.log(array);
+     * // => [3, 2, 1]
+     */
+    function wrapperReverse() {
+      var value = this.__wrapped__;
+
+      var interceptor = function(value) {
+        return (wrapped && wrapped.__dir__ < 0) ? value : value.reverse();
+      };
+      if (value instanceof LazyWrapper) {
+        var wrapped = value;
+        if (this.__actions__.length) {
+          wrapped = new LazyWrapper(this);
+        }
+        wrapped = wrapped.reverse();
+        wrapped.__actions__.push({ 'func': thru, 'args': [interceptor], 'thisArg': undefined });
+        return new LodashWrapper(wrapped, this.__chain__);
+      }
+      return this.thru(interceptor);
+    }
+
+    /**
+     * Produces the result of coercing the unwrapped value to a string.
+     *
+     * @name toString
+     * @memberOf _
+     * @category Chain
+     * @returns {string} Returns the coerced string value.
+     * @example
+     *
+     * _([1, 2, 3]).toString();
+     * // => '1,2,3'
+     */
+    function wrapperToString() {
+      return (this.value() + '');
+    }
+
+    /**
+     * Executes the chained sequence to extract the unwrapped value.
+     *
+     * @name value
+     * @memberOf _
+     * @alias run, toJSON, valueOf
+     * @category Chain
+     * @returns {*} Returns the resolved unwrapped value.
+     * @example
+     *
+     * _([1, 2, 3]).value();
+     * // => [1, 2, 3]
+     */
+    function wrapperValue() {
+      return baseWrapperValue(this.__wrapped__, this.__actions__);
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates an array of elements corresponding to the given keys, or indexes,
+     * of `collection`. Keys may be specified as individual arguments or as arrays
+     * of keys.
+     *
+     * @static
+     * @memberOf _
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {...(number|number[]|string|string[])} [props] The property names
+     *  or indexes of elements to pick, specified individually or in arrays.
+     * @returns {Array} Returns the new array of picked elements.
+     * @example
+     *
+     * _.at(['a', 'b', 'c'], [0, 2]);
+     * // => ['a', 'c']
+     *
+     * _.at(['barney', 'fred', 'pebbles'], 0, 2);
+     * // => ['barney', 'pebbles']
+     */
+    var at = restParam(function(collection, props) {
+      return baseAt(collection, baseFlatten(props));
+    });
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of `collection` through `iteratee`. The corresponding value
+     * of each key is the number of times the key was returned by `iteratee`.
+     * The `iteratee` is bound to `thisArg` and invoked with three arguments:
+     * (value, index|key, collection).
+     *
+     * If a property name is provided for `iteratee` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `iteratee` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [iteratee=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * _.countBy([4.3, 6.1, 6.4], function(n) {
+     *   return Math.floor(n);
+     * });
+     * // => { '4': 1, '6': 2 }
+     *
+     * _.countBy([4.3, 6.1, 6.4], function(n) {
+     *   return this.floor(n);
+     * }, Math);
+     * // => { '4': 1, '6': 2 }
+     *
+     * _.countBy(['one', 'two', 'three'], 'length');
+     * // => { '3': 2, '5': 1 }
+     */
+    var countBy = createAggregator(function(result, value, key) {
+      hasOwnProperty.call(result, key) ? ++result[key] : (result[key] = 1);
+    });
+
+    /**
+     * Checks if `predicate` returns truthy for **all** elements of `collection`.
+     * The predicate is bound to `thisArg` and invoked with three arguments:
+     * (value, index|key, collection).
+     *
+     * If a property name is provided for `predicate` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `predicate` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias all
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [predicate=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `predicate`.
+     * @returns {boolean} Returns `true` if all elements pass the predicate check,
+     *  else `false`.
+     * @example
+     *
+     * _.every([true, 1, null, 'yes'], Boolean);
+     * // => false
+     *
+     * var users = [
+     *   { 'user': 'barney', 'active': false },
+     *   { 'user': 'fred',   'active': false }
+     * ];
+     *
+     * // using the `_.matches` callback shorthand
+     * _.every(users, { 'user': 'barney', 'active': false });
+     * // => false
+     *
+     * // using the `_.matchesProperty` callback shorthand
+     * _.every(users, 'active', false);
+     * // => true
+     *
+     * // using the `_.property` callback shorthand
+     * _.every(users, 'active');
+     * // => false
+     */
+    function every(collection, predicate, thisArg) {
+      var func = isArray(collection) ? arrayEvery : baseEvery;
+      if (thisArg && isIterateeCall(collection, predicate, thisArg)) {
+        predicate = undefined;
+      }
+      if (typeof predicate != 'function' || thisArg !== undefined) {
+        predicate = getCallback(predicate, thisArg, 3);
+      }
+      return func(collection, predicate);
+    }
+
+    /**
+     * Iterates over elements of `collection`, returning an array of all elements
+     * `predicate` returns truthy for. The predicate is bound to `thisArg` and
+     * invoked with three arguments: (value, index|key, collection).
+     *
+     * If a property name is provided for `predicate` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `predicate` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias select
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [predicate=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `predicate`.
+     * @returns {Array} Returns the new filtered array.
+     * @example
+     *
+     * _.filter([4, 5, 6], function(n) {
+     *   return n % 2 == 0;
+     * });
+     * // => [4, 6]
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': true },
+     *   { 'user': 'fred',   'age': 40, 'active': false }
+     * ];
+     *
+     * // using the `_.matches` callback shorthand
+     * _.pluck(_.filter(users, { 'age': 36, 'active': true }), 'user');
+     * // => ['barney']
+     *
+     * // using the `_.matchesProperty` callback shorthand
+     * _.pluck(_.filter(users, 'active', false), 'user');
+     * // => ['fred']
+     *
+     * // using the `_.property` callback shorthand
+     * _.pluck(_.filter(users, 'active'), 'user');
+     * // => ['barney']
+     */
+    function filter(collection, predicate, thisArg) {
+      var func = isArray(collection) ? arrayFilter : baseFilter;
+      predicate = getCallback(predicate, thisArg, 3);
+      return func(collection, predicate);
+    }
+
+    /**
+     * Iterates over elements of `collection`, returning the first element
+     * `predicate` returns truthy for. The predicate is bound to `thisArg` and
+     * invoked with three arguments: (value, index|key, collection).
+     *
+     * If a property name is provided for `predicate` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `predicate` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias detect
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to search.
+     * @param {Function|Object|string} [predicate=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `predicate`.
+     * @returns {*} Returns the matched element, else `undefined`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'age': 36, 'active': true },
+     *   { 'user': 'fred',    'age': 40, 'active': false },
+     *   { 'user': 'pebbles', 'age': 1,  'active': true }
+     * ];
+     *
+     * _.result(_.find(users, function(chr) {
+     *   return chr.age < 40;
+     * }), 'user');
+     * // => 'barney'
+     *
+     * // using the `_.matches` callback shorthand
+     * _.result(_.find(users, { 'age': 1, 'active': true }), 'user');
+     * // => 'pebbles'
+     *
+     * // using the `_.matchesProperty` callback shorthand
+     * _.result(_.find(users, 'active', false), 'user');
+     * // => 'fred'
+     *
+     * // using the `_.property` callback shorthand
+     * _.result(_.find(users, 'active'), 'user');
+     * // => 'barney'
+     */
+    var find = createFind(baseEach);
+
+    /**
+     * This method is like `_.find` except that it iterates over elements of
+     * `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to search.
+     * @param {Function|Object|string} [predicate=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `predicate`.
+     * @returns {*} Returns the matched element, else `undefined`.
+     * @example
+     *
+     * _.findLast([1, 2, 3, 4], function(n) {
+     *   return n % 2 == 1;
+     * });
+     * // => 3
+     */
+    var findLast = createFind(baseEachRight, true);
+
+    /**
+     * Performs a deep comparison between each element in `collection` and the
+     * source object, returning the first element that has equivalent property
+     * values.
+     *
+     * **Note:** This method supports comparing arrays, booleans, `Date` objects,
+     * numbers, `Object` objects, regexes, and strings. Objects are compared by
+     * their own, not inherited, enumerable properties. For comparing a single
+     * own or inherited property value see `_.matchesProperty`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to search.
+     * @param {Object} source The object of property values to match.
+     * @returns {*} Returns the matched element, else `undefined`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': true },
+     *   { 'user': 'fred',   'age': 40, 'active': false }
+     * ];
+     *
+     * _.result(_.findWhere(users, { 'age': 36, 'active': true }), 'user');
+     * // => 'barney'
+     *
+     * _.result(_.findWhere(users, { 'age': 40, 'active': false }), 'user');
+     * // => 'fred'
+     */
+    function findWhere(collection, source) {
+      return find(collection, baseMatches(source));
+    }
+
+    /**
+     * Iterates over elements of `collection` invoking `iteratee` for each element.
+     * The `iteratee` is bound to `thisArg` and invoked with three arguments:
+     * (value, index|key, collection). Iteratee functions may exit iteration early
+     * by explicitly returning `false`.
+     *
+     * **Note:** As with other "Collections" methods, objects with a "length" property
+     * are iterated like arrays. To avoid this behavior `_.forIn` or `_.forOwn`
+     * may be used for object iteration.
+     *
+     * @static
+     * @memberOf _
+     * @alias each
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {Array|Object|string} Returns `collection`.
+     * @example
+     *
+     * _([1, 2]).forEach(function(n) {
+     *   console.log(n);
+     * }).value();
+     * // => logs each value from left to right and returns the array
+     *
+     * _.forEach({ 'a': 1, 'b': 2 }, function(n, key) {
+     *   console.log(n, key);
+     * });
+     * // => logs each value-key pair and returns the object (iteration order is not guaranteed)
+     */
+    var forEach = createForEach(arrayEach, baseEach);
+
+    /**
+     * This method is like `_.forEach` except that it iterates over elements of
+     * `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @alias eachRight
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {Array|Object|string} Returns `collection`.
+     * @example
+     *
+     * _([1, 2]).forEachRight(function(n) {
+     *   console.log(n);
+     * }).value();
+     * // => logs each value from right to left and returns the array
+     */
+    var forEachRight = createForEach(arrayEachRight, baseEachRight);
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of `collection` through `iteratee`. The corresponding value
+     * of each key is an array of the elements responsible for generating the key.
+     * The `iteratee` is bound to `thisArg` and invoked with three arguments:
+     * (value, index|key, collection).
+     *
+     * If a property name is provided for `iteratee` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `iteratee` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [iteratee=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * _.groupBy([4.2, 6.1, 6.4], function(n) {
+     *   return Math.floor(n);
+     * });
+     * // => { '4': [4.2], '6': [6.1, 6.4] }
+     *
+     * _.groupBy([4.2, 6.1, 6.4], function(n) {
+     *   return this.floor(n);
+     * }, Math);
+     * // => { '4': [4.2], '6': [6.1, 6.4] }
+     *
+     * // using the `_.property` callback shorthand
+     * _.groupBy(['one', 'two', 'three'], 'length');
+     * // => { '3': ['one', 'two'], '5': ['three'] }
+     */
+    var groupBy = createAggregator(function(result, value, key) {
+      if (hasOwnProperty.call(result, key)) {
+        result[key].push(value);
+      } else {
+        result[key] = [value];
+      }
+    });
+
+    /**
+     * Checks if `value` is in `collection` using
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons. If `fromIndex` is negative, it is used as the offset
+     * from the end of `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @alias contains, include
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to search.
+     * @param {*} target The value to search for.
+     * @param {number} [fromIndex=0] The index to search from.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.reduce`.
+     * @returns {boolean} Returns `true` if a matching element is found, else `false`.
+     * @example
+     *
+     * _.includes([1, 2, 3], 1);
+     * // => true
+     *
+     * _.includes([1, 2, 3], 1, 2);
+     * // => false
+     *
+     * _.includes({ 'user': 'fred', 'age': 40 }, 'fred');
+     * // => true
+     *
+     * _.includes('pebbles', 'eb');
+     * // => true
+     */
+    function includes(collection, target, fromIndex, guard) {
+      var length = collection ? getLength(collection) : 0;
+      if (!isLength(length)) {
+        collection = values(collection);
+        length = collection.length;
+      }
+      if (typeof fromIndex != 'number' || (guard && isIterateeCall(target, fromIndex, guard))) {
+        fromIndex = 0;
+      } else {
+        fromIndex = fromIndex < 0 ? nativeMax(length + fromIndex, 0) : (fromIndex || 0);
+      }
+      return (typeof collection == 'string' || !isArray(collection) && isString(collection))
+        ? (fromIndex <= length && collection.indexOf(target, fromIndex) > -1)
+        : (!!length && getIndexOf(collection, target, fromIndex) > -1);
+    }
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of `collection` through `iteratee`. The corresponding value
+     * of each key is the last element responsible for generating the key. The
+     * iteratee function is bound to `thisArg` and invoked with three arguments:
+     * (value, index|key, collection).
+     *
+     * If a property name is provided for `iteratee` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `iteratee` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [iteratee=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * var keyData = [
+     *   { 'dir': 'left', 'code': 97 },
+     *   { 'dir': 'right', 'code': 100 }
+     * ];
+     *
+     * _.indexBy(keyData, 'dir');
+     * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
+     *
+     * _.indexBy(keyData, function(object) {
+     *   return String.fromCharCode(object.code);
+     * });
+     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+     *
+     * _.indexBy(keyData, function(object) {
+     *   return this.fromCharCode(object.code);
+     * }, String);
+     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+     */
+    var indexBy = createAggregator(function(result, value, key) {
+      result[key] = value;
+    });
+
+    /**
+     * Invokes the method at `path` of each element in `collection`, returning
+     * an array of the results of each invoked method. Any additional arguments
+     * are provided to each invoked method. If `methodName` is a function it is
+     * invoked for, and `this` bound to, each element in `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Array|Function|string} path The path of the method to invoke or
+     *  the function invoked per iteration.
+     * @param {...*} [args] The arguments to invoke the method with.
+     * @returns {Array} Returns the array of results.
+     * @example
+     *
+     * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort');
+     * // => [[1, 5, 7], [1, 2, 3]]
+     *
+     * _.invoke([123, 456], String.prototype.split, '');
+     * // => [['1', '2', '3'], ['4', '5', '6']]
+     */
+    var invoke = restParam(function(collection, path, args) {
+      var index = -1,
+          isFunc = typeof path == 'function',
+          isProp = isKey(path),
+          result = isArrayLike(collection) ? Array(collection.length) : [];
+
+      baseEach(collection, function(value) {
+        var func = isFunc ? path : ((isProp && value != null) ? value[path] : undefined);
+        result[++index] = func ? func.apply(value, args) : invokePath(value, path, args);
+      });
+      return result;
+    });
+
+    /**
+     * Creates an array of values by running each element in `collection` through
+     * `iteratee`. The `iteratee` is bound to `thisArg` and invoked with three
+     * arguments: (value, index|key, collection).
+     *
+     * If a property name is provided for `iteratee` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `iteratee` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * Many lodash methods are guarded to work as iteratees for methods like
+     * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`.
+     *
+     * The guarded methods are:
+     * `ary`, `callback`, `chunk`, `clone`, `create`, `curry`, `curryRight`,
+     * `drop`, `dropRight`, `every`, `fill`, `flatten`, `invert`, `max`, `min`,
+     * `parseInt`, `slice`, `sortBy`, `take`, `takeRight`, `template`, `trim`,
+     * `trimLeft`, `trimRight`, `trunc`, `random`, `range`, `sample`, `some`,
+     * `sum`, `uniq`, and `words`
+     *
+     * @static
+     * @memberOf _
+     * @alias collect
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [iteratee=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {Array} Returns the new mapped array.
+     * @example
+     *
+     * function timesThree(n) {
+     *   return n * 3;
+     * }
+     *
+     * _.map([1, 2], timesThree);
+     * // => [3, 6]
+     *
+     * _.map({ 'a': 1, 'b': 2 }, timesThree);
+     * // => [3, 6] (iteration order is not guaranteed)
+     *
+     * var users = [
+     *   { 'user': 'barney' },
+     *   { 'user': 'fred' }
+     * ];
+     *
+     * // using the `_.property` callback shorthand
+     * _.map(users, 'user');
+     * // => ['barney', 'fred']
+     */
+    function map(collection, iteratee, thisArg) {
+      var func = isArray(collection) ? arrayMap : baseMap;
+      iteratee = getCallback(iteratee, thisArg, 3);
+      return func(collection, iteratee);
+    }
+
+    /**
+     * Creates an array of elements split into two groups, the first of which
+     * contains elements `predicate` returns truthy for, while the second of which
+     * contains elements `predicate` returns falsey for. The predicate is bound
+     * to `thisArg` and invoked with three arguments: (value, index|key, collection).
+     *
+     * If a property name is provided for `predicate` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `predicate` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [predicate=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `predicate`.
+     * @returns {Array} Returns the array of grouped elements.
+     * @example
+     *
+     * _.partition([1, 2, 3], function(n) {
+     *   return n % 2;
+     * });
+     * // => [[1, 3], [2]]
+     *
+     * _.partition([1.2, 2.3, 3.4], function(n) {
+     *   return this.floor(n) % 2;
+     * }, Math);
+     * // => [[1.2, 3.4], [2.3]]
+     *
+     * var users = [
+     *   { 'user': 'barney',  'age': 36, 'active': false },
+     *   { 'user': 'fred',    'age': 40, 'active': true },
+     *   { 'user': 'pebbles', 'age': 1,  'active': false }
+     * ];
+     *
+     * var mapper = function(array) {
+     *   return _.pluck(array, 'user');
+     * };
+     *
+     * // using the `_.matches` callback shorthand
+     * _.map(_.partition(users, { 'age': 1, 'active': false }), mapper);
+     * // => [['pebbles'], ['barney', 'fred']]
+     *
+     * // using the `_.matchesProperty` callback shorthand
+     * _.map(_.partition(users, 'active', false), mapper);
+     * // => [['barney', 'pebbles'], ['fred']]
+     *
+     * // using the `_.property` callback shorthand
+     * _.map(_.partition(users, 'active'), mapper);
+     * // => [['fred'], ['barney', 'pebbles']]
+     */
+    var partition = createAggregator(function(result, value, key) {
+      result[key ? 0 : 1].push(value);
+    }, function() { return [[], []]; });
+
+    /**
+     * Gets the property value of `path` from all elements in `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Array|string} path The path of the property to pluck.
+     * @returns {Array} Returns the property values.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36 },
+     *   { 'user': 'fred',   'age': 40 }
+     * ];
+     *
+     * _.pluck(users, 'user');
+     * // => ['barney', 'fred']
+     *
+     * var userIndex = _.indexBy(users, 'user');
+     * _.pluck(userIndex, 'age');
+     * // => [36, 40] (iteration order is not guaranteed)
+     */
+    function pluck(collection, path) {
+      return map(collection, property(path));
+    }
+
+    /**
+     * Reduces `collection` to a value which is the accumulated result of running
+     * each element in `collection` through `iteratee`, where each successive
+     * invocation is supplied the return value of the previous. If `accumulator`
+     * is not provided the first element of `collection` is used as the initial
+     * value. The `iteratee` is bound to `thisArg` and invoked with four arguments:
+     * (accumulator, value, index|key, collection).
+     *
+     * Many lodash methods are guarded to work as iteratees for methods like
+     * `_.reduce`, `_.reduceRight`, and `_.transform`.
+     *
+     * The guarded methods are:
+     * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `sortByAll`,
+     * and `sortByOrder`
+     *
+     * @static
+     * @memberOf _
+     * @alias foldl, inject
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [accumulator] The initial value.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {*} Returns the accumulated value.
+     * @example
+     *
+     * _.reduce([1, 2], function(total, n) {
+     *   return total + n;
+     * });
+     * // => 3
+     *
+     * _.reduce({ 'a': 1, 'b': 2 }, function(result, n, key) {
+     *   result[key] = n * 3;
+     *   return result;
+     * }, {});
+     * // => { 'a': 3, 'b': 6 } (iteration order is not guaranteed)
+     */
+    var reduce = createReduce(arrayReduce, baseEach);
+
+    /**
+     * This method is like `_.reduce` except that it iterates over elements of
+     * `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @alias foldr
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [accumulator] The initial value.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {*} Returns the accumulated value.
+     * @example
+     *
+     * var array = [[0, 1], [2, 3], [4, 5]];
+     *
+     * _.reduceRight(array, function(flattened, other) {
+     *   return flattened.concat(other);
+     * }, []);
+     * // => [4, 5, 2, 3, 0, 1]
+     */
+    var reduceRight = createReduce(arrayReduceRight, baseEachRight);
+
+    /**
+     * The opposite of `_.filter`; this method returns the elements of `collection`
+     * that `predicate` does **not** return truthy for.
+     *
+     * @static
+     * @memberOf _
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [predicate=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `predicate`.
+     * @returns {Array} Returns the new filtered array.
+     * @example
+     *
+     * _.reject([1, 2, 3, 4], function(n) {
+     *   return n % 2 == 0;
+     * });
+     * // => [1, 3]
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': false },
+     *   { 'user': 'fred',   'age': 40, 'active': true }
+     * ];
+     *
+     * // using the `_.matches` callback shorthand
+     * _.pluck(_.reject(users, { 'age': 40, 'active': true }), 'user');
+     * // => ['barney']
+     *
+     * // using the `_.matchesProperty` callback shorthand
+     * _.pluck(_.reject(users, 'active', false), 'user');
+     * // => ['fred']
+     *
+     * // using the `_.property` callback shorthand
+     * _.pluck(_.reject(users, 'active'), 'user');
+     * // => ['barney']
+     */
+    function reject(collection, predicate, thisArg) {
+      var func = isArray(collection) ? arrayFilter : baseFilter;
+      predicate = getCallback(predicate, thisArg, 3);
+      return func(collection, function(value, index, collection) {
+        return !predicate(value, index, collection);
+      });
+    }
+
+    /**
+     * Gets a random element or `n` random elements from a collection.
+     *
+     * @static
+     * @memberOf _
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to sample.
+     * @param {number} [n] The number of elements to sample.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
+     * @returns {*} Returns the random sample(s).
+     * @example
+     *
+     * _.sample([1, 2, 3, 4]);
+     * // => 2
+     *
+     * _.sample([1, 2, 3, 4], 2);
+     * // => [3, 1]
+     */
+    function sample(collection, n, guard) {
+      if (guard ? isIterateeCall(collection, n, guard) : n == null) {
+        collection = toIterable(collection);
+        var length = collection.length;
+        return length > 0 ? collection[baseRandom(0, length - 1)] : undefined;
+      }
+      var index = -1,
+          result = toArray(collection),
+          length = result.length,
+          lastIndex = length - 1;
+
+      n = nativeMin(n < 0 ? 0 : (+n || 0), length);
+      while (++index < n) {
+        var rand = baseRandom(index, lastIndex),
+            value = result[rand];
+
+        result[rand] = result[index];
+        result[index] = value;
+      }
+      result.length = n;
+      return result;
+    }
+
+    /**
+     * Creates an array of shuffled values, using a version of the
+     * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle).
+     *
+     * @static
+     * @memberOf _
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to shuffle.
+     * @returns {Array} Returns the new shuffled array.
+     * @example
+     *
+     * _.shuffle([1, 2, 3, 4]);
+     * // => [4, 1, 3, 2]
+     */
+    function shuffle(collection) {
+      return sample(collection, POSITIVE_INFINITY);
+    }
+
+    /**
+     * Gets the size of `collection` by returning its length for array-like
+     * values or the number of own enumerable properties for objects.
+     *
+     * @static
+     * @memberOf _
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to inspect.
+     * @returns {number} Returns the size of `collection`.
+     * @example
+     *
+     * _.size([1, 2, 3]);
+     * // => 3
+     *
+     * _.size({ 'a': 1, 'b': 2 });
+     * // => 2
+     *
+     * _.size('pebbles');
+     * // => 7
+     */
+    function size(collection) {
+      var length = collection ? getLength(collection) : 0;
+      return isLength(length) ? length : keys(collection).length;
+    }
+
+    /**
+     * Checks if `predicate` returns truthy for **any** element of `collection`.
+     * The function returns as soon as it finds a passing value and does not iterate
+     * over the entire collection. The predicate is bound to `thisArg` and invoked
+     * with three arguments: (value, index|key, collection).
+     *
+     * If a property name is provided for `predicate` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `predicate` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias any
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [predicate=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `predicate`.
+     * @returns {boolean} Returns `true` if any element passes the predicate check,
+     *  else `false`.
+     * @example
+     *
+     * _.some([null, 0, 'yes', false], Boolean);
+     * // => true
+     *
+     * var users = [
+     *   { 'user': 'barney', 'active': true },
+     *   { 'user': 'fred',   'active': false }
+     * ];
+     *
+     * // using the `_.matches` callback shorthand
+     * _.some(users, { 'user': 'barney', 'active': false });
+     * // => false
+     *
+     * // using the `_.matchesProperty` callback shorthand
+     * _.some(users, 'active', false);
+     * // => true
+     *
+     * // using the `_.property` callback shorthand
+     * _.some(users, 'active');
+     * // => true
+     */
+    function some(collection, predicate, thisArg) {
+      var func = isArray(collection) ? arraySome : baseSome;
+      if (thisArg && isIterateeCall(collection, predicate, thisArg)) {
+        predicate = undefined;
+      }
+      if (typeof predicate != 'function' || thisArg !== undefined) {
+        predicate = getCallback(predicate, thisArg, 3);
+      }
+      return func(collection, predicate);
+    }
+
+    /**
+     * Creates an array of elements, sorted in ascending order by the results of
+     * running each element in a collection through `iteratee`. This method performs
+     * a stable sort, that is, it preserves the original sort order of equal elements.
+     * The `iteratee` is bound to `thisArg` and invoked with three arguments:
+     * (value, index|key, collection).
+     *
+     * If a property name is provided for `iteratee` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `iteratee` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [iteratee=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {Array} Returns the new sorted array.
+     * @example
+     *
+     * _.sortBy([1, 2, 3], function(n) {
+     *   return Math.sin(n);
+     * });
+     * // => [3, 1, 2]
+     *
+     * _.sortBy([1, 2, 3], function(n) {
+     *   return this.sin(n);
+     * }, Math);
+     * // => [3, 1, 2]
+     *
+     * var users = [
+     *   { 'user': 'fred' },
+     *   { 'user': 'pebbles' },
+     *   { 'user': 'barney' }
+     * ];
+     *
+     * // using the `_.property` callback shorthand
+     * _.pluck(_.sortBy(users, 'user'), 'user');
+     * // => ['barney', 'fred', 'pebbles']
+     */
+    function sortBy(collection, iteratee, thisArg) {
+      if (collection == null) {
+        return [];
+      }
+      if (thisArg && isIterateeCall(collection, iteratee, thisArg)) {
+        iteratee = undefined;
+      }
+      var index = -1;
+      iteratee = getCallback(iteratee, thisArg, 3);
+
+      var result = baseMap(collection, function(value, key, collection) {
+        return { 'criteria': iteratee(value, key, collection), 'index': ++index, 'value': value };
+      });
+      return baseSortBy(result, compareAscending);
+    }
+
+    /**
+     * This method is like `_.sortBy` except that it can sort by multiple iteratees
+     * or property names.
+     *
+     * If a property name is provided for an iteratee the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If an object is provided for an iteratee the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {...(Function|Function[]|Object|Object[]|string|string[])} iteratees
+     *  The iteratees to sort by, specified as individual values or arrays of values.
+     * @returns {Array} Returns the new sorted array.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'fred',   'age': 48 },
+     *   { 'user': 'barney', 'age': 36 },
+     *   { 'user': 'fred',   'age': 42 },
+     *   { 'user': 'barney', 'age': 34 }
+     * ];
+     *
+     * _.map(_.sortByAll(users, ['user', 'age']), _.values);
+     * // => [['barney', 34], ['barney', 36], ['fred', 42], ['fred', 48]]
+     *
+     * _.map(_.sortByAll(users, 'user', function(chr) {
+     *   return Math.floor(chr.age / 10);
+     * }), _.values);
+     * // => [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 42]]
+     */
+    var sortByAll = restParam(function(collection, iteratees) {
+      if (collection == null) {
+        return [];
+      }
+      var guard = iteratees[2];
+      if (guard && isIterateeCall(iteratees[0], iteratees[1], guard)) {
+        iteratees.length = 1;
+      }
+      return baseSortByOrder(collection, baseFlatten(iteratees), []);
+    });
+
+    /**
+     * This method is like `_.sortByAll` except that it allows specifying the
+     * sort orders of the iteratees to sort by. If `orders` is unspecified, all
+     * values are sorted in ascending order. Otherwise, a value is sorted in
+     * ascending order if its corresponding order is "asc", and descending if "desc".
+     *
+     * If a property name is provided for an iteratee the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If an object is provided for an iteratee the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.
+     * @param {boolean[]} [orders] The sort orders of `iteratees`.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.reduce`.
+     * @returns {Array} Returns the new sorted array.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'fred',   'age': 48 },
+     *   { 'user': 'barney', 'age': 34 },
+     *   { 'user': 'fred',   'age': 42 },
+     *   { 'user': 'barney', 'age': 36 }
+     * ];
+     *
+     * // sort by `user` in ascending order and by `age` in descending order
+     * _.map(_.sortByOrder(users, ['user', 'age'], ['asc', 'desc']), _.values);
+     * // => [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 42]]
+     */
+    function sortByOrder(collection, iteratees, orders, guard) {
+      if (collection == null) {
+        return [];
+      }
+      if (guard && isIterateeCall(iteratees, orders, guard)) {
+        orders = undefined;
+      }
+      if (!isArray(iteratees)) {
+        iteratees = iteratees == null ? [] : [iteratees];
+      }
+      if (!isArray(orders)) {
+        orders = orders == null ? [] : [orders];
+      }
+      return baseSortByOrder(collection, iteratees, orders);
+    }
+
+    /**
+     * Performs a deep comparison between each element in `collection` and the
+     * source object, returning an array of all elements that have equivalent
+     * property values.
+     *
+     * **Note:** This method supports comparing arrays, booleans, `Date` objects,
+     * numbers, `Object` objects, regexes, and strings. Objects are compared by
+     * their own, not inherited, enumerable properties. For comparing a single
+     * own or inherited property value see `_.matchesProperty`.
+     *
+     * @static
+     * @memberOf _
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to search.
+     * @param {Object} source The object of property values to match.
+     * @returns {Array} Returns the new filtered array.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': false, 'pets': ['hoppy'] },
+     *   { 'user': 'fred',   'age': 40, 'active': true, 'pets': ['baby puss', 'dino'] }
+     * ];
+     *
+     * _.pluck(_.where(users, { 'age': 36, 'active': false }), 'user');
+     * // => ['barney']
+     *
+     * _.pluck(_.where(users, { 'pets': ['dino'] }), 'user');
+     * // => ['fred']
+     */
+    function where(collection, source) {
+      return filter(collection, baseMatches(source));
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Gets the number of milliseconds that have elapsed since the Unix epoch
+     * (1 January 1970 00:00:00 UTC).
+     *
+     * @static
+     * @memberOf _
+     * @category Date
+     * @example
+     *
+     * _.defer(function(stamp) {
+     *   console.log(_.now() - stamp);
+     * }, _.now());
+     * // => logs the number of milliseconds it took for the deferred function to be invoked
+     */
+    var now = nativeNow || function() {
+      return new Date().getTime();
+    };
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * The opposite of `_.before`; this method creates a function that invokes
+     * `func` once it is called `n` or more times.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {number} n The number of calls before `func` is invoked.
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * var saves = ['profile', 'settings'];
+     *
+     * var done = _.after(saves.length, function() {
+     *   console.log('done saving!');
+     * });
+     *
+     * _.forEach(saves, function(type) {
+     *   asyncSave({ 'type': type, 'complete': done });
+     * });
+     * // => logs 'done saving!' after the two async saves have completed
+     */
+    function after(n, func) {
+      if (typeof func != 'function') {
+        if (typeof n == 'function') {
+          var temp = n;
+          n = func;
+          func = temp;
+        } else {
+          throw new TypeError(FUNC_ERROR_TEXT);
+        }
+      }
+      n = nativeIsFinite(n = +n) ? n : 0;
+      return function() {
+        if (--n < 1) {
+          return func.apply(this, arguments);
+        }
+      };
+    }
+
+    /**
+     * Creates a function that accepts up to `n` arguments ignoring any
+     * additional arguments.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {Function} func The function to cap arguments for.
+     * @param {number} [n=func.length] The arity cap.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * _.map(['6', '8', '10'], _.ary(parseInt, 1));
+     * // => [6, 8, 10]
+     */
+    function ary(func, n, guard) {
+      if (guard && isIterateeCall(func, n, guard)) {
+        n = undefined;
+      }
+      n = (func && n == null) ? func.length : nativeMax(+n || 0, 0);
+      return createWrapper(func, ARY_FLAG, undefined, undefined, undefined, undefined, n);
+    }
+
+    /**
+     * Creates a function that invokes `func`, with the `this` binding and arguments
+     * of the created function, while it is called less than `n` times. Subsequent
+     * calls to the created function return the result of the last `func` invocation.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {number} n The number of calls at which `func` is no longer invoked.
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * jQuery('#add').on('click', _.before(5, addContactToList));
+     * // => allows adding up to 4 contacts to the list
+     */
+    function before(n, func) {
+      var result;
+      if (typeof func != 'function') {
+        if (typeof n == 'function') {
+          var temp = n;
+          n = func;
+          func = temp;
+        } else {
+          throw new TypeError(FUNC_ERROR_TEXT);
+        }
+      }
+      return function() {
+        if (--n > 0) {
+          result = func.apply(this, arguments);
+        }
+        if (n <= 1) {
+          func = undefined;
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Creates a function that invokes `func` with the `this` binding of `thisArg`
+     * and prepends any additional `_.bind` arguments to those provided to the
+     * bound function.
+     *
+     * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds,
+     * may be used as a placeholder for partially applied arguments.
+     *
+     * **Note:** Unlike native `Function#bind` this method does not set the "length"
+     * property of bound functions.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {Function} func The function to bind.
+     * @param {*} thisArg The `this` binding of `func`.
+     * @param {...*} [partials] The arguments to be partially applied.
+     * @returns {Function} Returns the new bound function.
+     * @example
+     *
+     * var greet = function(greeting, punctuation) {
+     *   return greeting + ' ' + this.user + punctuation;
+     * };
+     *
+     * var object = { 'user': 'fred' };
+     *
+     * var bound = _.bind(greet, object, 'hi');
+     * bound('!');
+     * // => 'hi fred!'
+     *
+     * // using placeholders
+     * var bound = _.bind(greet, object, _, '!');
+     * bound('hi');
+     * // => 'hi fred!'
+     */
+    var bind = restParam(function(func, thisArg, partials) {
+      var bitmask = BIND_FLAG;
+      if (partials.length) {
+        var holders = replaceHolders(partials, bind.placeholder);
+        bitmask |= PARTIAL_FLAG;
+      }
+      return createWrapper(func, bitmask, thisArg, partials, holders);
+    });
+
+    /**
+     * Binds methods of an object to the object itself, overwriting the existing
+     * method. Method names may be specified as individual arguments or as arrays
+     * of method names. If no method names are provided all enumerable function
+     * properties, own and inherited, of `object` are bound.
+     *
+     * **Note:** This method does not set the "length" property of bound functions.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {Object} object The object to bind and assign the bound methods to.
+     * @param {...(string|string[])} [methodNames] The object method names to bind,
+     *  specified as individual method names or arrays of method names.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var view = {
+     *   'label': 'docs',
+     *   'onClick': function() {
+     *     console.log('clicked ' + this.label);
+     *   }
+     * };
+     *
+     * _.bindAll(view);
+     * jQuery('#docs').on('click', view.onClick);
+     * // => logs 'clicked docs' when the element is clicked
+     */
+    var bindAll = restParam(function(object, methodNames) {
+      methodNames = methodNames.length ? baseFlatten(methodNames) : functions(object);
+
+      var index = -1,
+          length = methodNames.length;
+
+      while (++index < length) {
+        var key = methodNames[index];
+        object[key] = createWrapper(object[key], BIND_FLAG, object);
+      }
+      return object;
+    });
+
+    /**
+     * Creates a function that invokes the method at `object[key]` and prepends
+     * any additional `_.bindKey` arguments to those provided to the bound function.
+     *
+     * This method differs from `_.bind` by allowing bound functions to reference
+     * methods that may be redefined or don't yet exist.
+     * See [Peter Michaux's article](http://peter.michaux.ca/articles/lazy-function-definition-pattern)
+     * for more details.
+     *
+     * The `_.bindKey.placeholder` value, which defaults to `_` in monolithic
+     * builds, may be used as a placeholder for partially applied arguments.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {Object} object The object the method belongs to.
+     * @param {string} key The key of the method.
+     * @param {...*} [partials] The arguments to be partially applied.
+     * @returns {Function} Returns the new bound function.
+     * @example
+     *
+     * var object = {
+     *   'user': 'fred',
+     *   'greet': function(greeting, punctuation) {
+     *     return greeting + ' ' + this.user + punctuation;
+     *   }
+     * };
+     *
+     * var bound = _.bindKey(object, 'greet', 'hi');
+     * bound('!');
+     * // => 'hi fred!'
+     *
+     * object.greet = function(greeting, punctuation) {
+     *   return greeting + 'ya ' + this.user + punctuation;
+     * };
+     *
+     * bound('!');
+     * // => 'hiya fred!'
+     *
+     * // using placeholders
+     * var bound = _.bindKey(object, 'greet', _, '!');
+     * bound('hi');
+     * // => 'hiya fred!'
+     */
+    var bindKey = restParam(function(object, key, partials) {
+      var bitmask = BIND_FLAG | BIND_KEY_FLAG;
+      if (partials.length) {
+        var holders = replaceHolders(partials, bindKey.placeholder);
+        bitmask |= PARTIAL_FLAG;
+      }
+      return createWrapper(key, bitmask, object, partials, holders);
+    });
+
+    /**
+     * Creates a function that accepts one or more arguments of `func` that when
+     * called either invokes `func` returning its result, if all `func` arguments
+     * have been provided, or returns a function that accepts one or more of the
+     * remaining `func` arguments, and so on. The arity of `func` may be specified
+     * if `func.length` is not sufficient.
+     *
+     * The `_.curry.placeholder` value, which defaults to `_` in monolithic builds,
+     * may be used as a placeholder for provided arguments.
+     *
+     * **Note:** This method does not set the "length" property of curried functions.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {Function} func The function to curry.
+     * @param {number} [arity=func.length] The arity of `func`.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
+     * @returns {Function} Returns the new curried function.
+     * @example
+     *
+     * var abc = function(a, b, c) {
+     *   return [a, b, c];
+     * };
+     *
+     * var curried = _.curry(abc);
+     *
+     * curried(1)(2)(3);
+     * // => [1, 2, 3]
+     *
+     * curried(1, 2)(3);
+     * // => [1, 2, 3]
+     *
+     * curried(1, 2, 3);
+     * // => [1, 2, 3]
+     *
+     * // using placeholders
+     * curried(1)(_, 3)(2);
+     * // => [1, 2, 3]
+     */
+    var curry = createCurry(CURRY_FLAG);
+
+    /**
+     * This method is like `_.curry` except that arguments are applied to `func`
+     * in the manner of `_.partialRight` instead of `_.partial`.
+     *
+     * The `_.curryRight.placeholder` value, which defaults to `_` in monolithic
+     * builds, may be used as a placeholder for provided arguments.
+     *
+     * **Note:** This method does not set the "length" property of curried functions.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {Function} func The function to curry.
+     * @param {number} [arity=func.length] The arity of `func`.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
+     * @returns {Function} Returns the new curried function.
+     * @example
+     *
+     * var abc = function(a, b, c) {
+     *   return [a, b, c];
+     * };
+     *
+     * var curried = _.curryRight(abc);
+     *
+     * curried(3)(2)(1);
+     * // => [1, 2, 3]
+     *
+     * curried(2, 3)(1);
+     * // => [1, 2, 3]
+     *
+     * curried(1, 2, 3);
+     * // => [1, 2, 3]
+     *
+     * // using placeholders
+     * curried(3)(1, _)(2);
+     * // => [1, 2, 3]
+     */
+    var curryRight = createCurry(CURRY_RIGHT_FLAG);
+
+    /**
+     * Creates a debounced function that delays invoking `func` until after `wait`
+     * milliseconds have elapsed since the last time the debounced function was
+     * invoked. The debounced function comes with a `cancel` method to cancel
+     * delayed invocations. Provide an options object to indicate that `func`
+     * should be invoked on the leading and/or trailing edge of the `wait` timeout.
+     * Subsequent calls to the debounced function return the result of the last
+     * `func` invocation.
+     *
+     * **Note:** If `leading` and `trailing` options are `true`, `func` is invoked
+     * on the trailing edge of the timeout only if the the debounced function is
+     * invoked more than once during the `wait` timeout.
+     *
+     * See [David Corbacho's article](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation)
+     * for details over the differences between `_.debounce` and `_.throttle`.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {Function} func The function to debounce.
+     * @param {number} [wait=0] The number of milliseconds to delay.
+     * @param {Object} [options] The options object.
+     * @param {boolean} [options.leading=false] Specify invoking on the leading
+     *  edge of the timeout.
+     * @param {number} [options.maxWait] The maximum time `func` is allowed to be
+     *  delayed before it is invoked.
+     * @param {boolean} [options.trailing=true] Specify invoking on the trailing
+     *  edge of the timeout.
+     * @returns {Function} Returns the new debounced function.
+     * @example
+     *
+     * // avoid costly calculations while the window size is in flux
+     * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
+     *
+     * // invoke `sendMail` when the click event is fired, debouncing subsequent calls
+     * jQuery('#postbox').on('click', _.debounce(sendMail, 300, {
+     *   'leading': true,
+     *   'trailing': false
+     * }));
+     *
+     * // ensure `batchLog` is invoked once after 1 second of debounced calls
+     * var source = new EventSource('/stream');
+     * jQuery(source).on('message', _.debounce(batchLog, 250, {
+     *   'maxWait': 1000
+     * }));
+     *
+     * // cancel a debounced call
+     * var todoChanges = _.debounce(batchLog, 1000);
+     * Object.observe(models.todo, todoChanges);
+     *
+     * Object.observe(models, function(changes) {
+     *   if (_.find(changes, { 'user': 'todo', 'type': 'delete'})) {
+     *     todoChanges.cancel();
+     *   }
+     * }, ['delete']);
+     *
+     * // ...at some point `models.todo` is changed
+     * models.todo.completed = true;
+     *
+     * // ...before 1 second has passed `models.todo` is deleted
+     * // which cancels the debounced `todoChanges` call
+     * delete models.todo;
+     */
+    function debounce(func, wait, options) {
+      var args,
+          maxTimeoutId,
+          result,
+          stamp,
+          thisArg,
+          timeoutId,
+          trailingCall,
+          lastCalled = 0,
+          maxWait = false,
+          trailing = true;
+
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      wait = wait < 0 ? 0 : (+wait || 0);
+      if (options === true) {
+        var leading = true;
+        trailing = false;
+      } else if (isObject(options)) {
+        leading = !!options.leading;
+        maxWait = 'maxWait' in options && nativeMax(+options.maxWait || 0, wait);
+        trailing = 'trailing' in options ? !!options.trailing : trailing;
+      }
+
+      function cancel() {
+        if (timeoutId) {
+          clearTimeout(timeoutId);
+        }
+        if (maxTimeoutId) {
+          clearTimeout(maxTimeoutId);
+        }
+        lastCalled = 0;
+        maxTimeoutId = timeoutId = trailingCall = undefined;
+      }
+
+      function complete(isCalled, id) {
+        if (id) {
+          clearTimeout(id);
+        }
+        maxTimeoutId = timeoutId = trailingCall = undefined;
+        if (isCalled) {
+          lastCalled = now();
+          result = func.apply(thisArg, args);
+          if (!timeoutId && !maxTimeoutId) {
+            args = thisArg = undefined;
+          }
+        }
+      }
+
+      function delayed() {
+        var remaining = wait - (now() - stamp);
+        if (remaining <= 0 || remaining > wait) {
+          complete(trailingCall, maxTimeoutId);
+        } else {
+          timeoutId = setTimeout(delayed, remaining);
+        }
+      }
+
+      function maxDelayed() {
+        complete(trailing, timeoutId);
+      }
+
+      function debounced() {
+        args = arguments;
+        stamp = now();
+        thisArg = this;
+        trailingCall = trailing && (timeoutId || !leading);
+
+        if (maxWait === false) {
+          var leadingCall = leading && !timeoutId;
+        } else {
+          if (!maxTimeoutId && !leading) {
+            lastCalled = stamp;
+          }
+          var remaining = maxWait - (stamp - lastCalled),
+              isCalled = remaining <= 0 || remaining > maxWait;
+
+          if (isCalled) {
+            if (maxTimeoutId) {
+              maxTimeoutId = clearTimeout(maxTimeoutId);
+            }
+            lastCalled = stamp;
+            result = func.apply(thisArg, args);
+          }
+          else if (!maxTimeoutId) {
+            maxTimeoutId = setTimeout(maxDelayed, remaining);
+          }
+        }
+        if (isCalled && timeoutId) {
+          timeoutId = clearTimeout(timeoutId);
+        }
+        else if (!timeoutId && wait !== maxWait) {
+          timeoutId = setTimeout(delayed, wait);
+        }
+        if (leadingCall) {
+          isCalled = true;
+          result = func.apply(thisArg, args);
+        }
+        if (isCalled && !timeoutId && !maxTimeoutId) {
+          args = thisArg = undefined;
+        }
+        return result;
+      }
+      debounced.cancel = cancel;
+      return debounced;
+    }
+
+    /**
+     * Defers invoking the `func` until the current call stack has cleared. Any
+     * additional arguments are provided to `func` when it is invoked.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {Function} func The function to defer.
+     * @param {...*} [args] The arguments to invoke the function with.
+     * @returns {number} Returns the timer id.
+     * @example
+     *
+     * _.defer(function(text) {
+     *   console.log(text);
+     * }, 'deferred');
+     * // logs 'deferred' after one or more milliseconds
+     */
+    var defer = restParam(function(func, args) {
+      return baseDelay(func, 1, args);
+    });
+
+    /**
+     * Invokes `func` after `wait` milliseconds. Any additional arguments are
+     * provided to `func` when it is invoked.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {Function} func The function to delay.
+     * @param {number} wait The number of milliseconds to delay invocation.
+     * @param {...*} [args] The arguments to invoke the function with.
+     * @returns {number} Returns the timer id.
+     * @example
+     *
+     * _.delay(function(text) {
+     *   console.log(text);
+     * }, 1000, 'later');
+     * // => logs 'later' after one second
+     */
+    var delay = restParam(function(func, wait, args) {
+      return baseDelay(func, wait, args);
+    });
+
+    /**
+     * Creates a function that returns the result of invoking the provided
+     * functions with the `this` binding of the created function, where each
+     * successive invocation is supplied the return value of the previous.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {...Function} [funcs] Functions to invoke.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var addSquare = _.flow(_.add, square);
+     * addSquare(1, 2);
+     * // => 9
+     */
+    var flow = createFlow();
+
+    /**
+     * This method is like `_.flow` except that it creates a function that
+     * invokes the provided functions from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @alias backflow, compose
+     * @category Function
+     * @param {...Function} [funcs] Functions to invoke.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var addSquare = _.flowRight(square, _.add);
+     * addSquare(1, 2);
+     * // => 9
+     */
+    var flowRight = createFlow(true);
+
+    /**
+     * Creates a function that memoizes the result of `func`. If `resolver` is
+     * provided it determines the cache key for storing the result based on the
+     * arguments provided to the memoized function. By default, the first argument
+     * provided to the memoized function is coerced to a string and used as the
+     * cache key. The `func` is invoked with the `this` binding of the memoized
+     * function.
+     *
+     * **Note:** The cache is exposed as the `cache` property on the memoized
+     * function. Its creation may be customized by replacing the `_.memoize.Cache`
+     * constructor with one whose instances implement the [`Map`](http://ecma-international.org/ecma-262/6.0/#sec-properties-of-the-map-prototype-object)
+     * method interface of `get`, `has`, and `set`.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {Function} func The function to have its output memoized.
+     * @param {Function} [resolver] The function to resolve the cache key.
+     * @returns {Function} Returns the new memoizing function.
+     * @example
+     *
+     * var upperCase = _.memoize(function(string) {
+     *   return string.toUpperCase();
+     * });
+     *
+     * upperCase('fred');
+     * // => 'FRED'
+     *
+     * // modifying the result cache
+     * upperCase.cache.set('fred', 'BARNEY');
+     * upperCase('fred');
+     * // => 'BARNEY'
+     *
+     * // replacing `_.memoize.Cache`
+     * var object = { 'user': 'fred' };
+     * var other = { 'user': 'barney' };
+     * var identity = _.memoize(_.identity);
+     *
+     * identity(object);
+     * // => { 'user': 'fred' }
+     * identity(other);
+     * // => { 'user': 'fred' }
+     *
+     * _.memoize.Cache = WeakMap;
+     * var identity = _.memoize(_.identity);
+     *
+     * identity(object);
+     * // => { 'user': 'fred' }
+     * identity(other);
+     * // => { 'user': 'barney' }
+     */
+    function memoize(func, resolver) {
+      if (typeof func != 'function' || (resolver && typeof resolver != 'function')) {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      var memoized = function() {
+        var args = arguments,
+            key = resolver ? resolver.apply(this, args) : args[0],
+            cache = memoized.cache;
+
+        if (cache.has(key)) {
+          return cache.get(key);
+        }
+        var result = func.apply(this, args);
+        memoized.cache = cache.set(key, result);
+        return result;
+      };
+      memoized.cache = new memoize.Cache;
+      return memoized;
+    }
+
+    /**
+     * Creates a function that runs each argument through a corresponding
+     * transform function.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {Function} func The function to wrap.
+     * @param {...(Function|Function[])} [transforms] The functions to transform
+     * arguments, specified as individual functions or arrays of functions.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * function doubled(n) {
+     *   return n * 2;
+     * }
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var modded = _.modArgs(function(x, y) {
+     *   return [x, y];
+     * }, square, doubled);
+     *
+     * modded(1, 2);
+     * // => [1, 4]
+     *
+     * modded(5, 10);
+     * // => [25, 20]
+     */
+    var modArgs = restParam(function(func, transforms) {
+      transforms = baseFlatten(transforms);
+      if (typeof func != 'function' || !arrayEvery(transforms, baseIsFunction)) {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      var length = transforms.length;
+      return restParam(function(args) {
+        var index = nativeMin(args.length, length);
+        while (index--) {
+          args[index] = transforms[index](args[index]);
+        }
+        return func.apply(this, args);
+      });
+    });
+
+    /**
+     * Creates a function that negates the result of the predicate `func`. The
+     * `func` predicate is invoked with the `this` binding and arguments of the
+     * created function.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {Function} predicate The predicate to negate.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * function isEven(n) {
+     *   return n % 2 == 0;
+     * }
+     *
+     * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));
+     * // => [1, 3, 5]
+     */
+    function negate(predicate) {
+      if (typeof predicate != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      return function() {
+        return !predicate.apply(this, arguments);
+      };
+    }
+
+    /**
+     * Creates a function that is restricted to invoking `func` once. Repeat calls
+     * to the function return the value of the first call. The `func` is invoked
+     * with the `this` binding and arguments of the created function.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * var initialize = _.once(createApplication);
+     * initialize();
+     * initialize();
+     * // `initialize` invokes `createApplication` once
+     */
+    function once(func) {
+      return before(2, func);
+    }
+
+    /**
+     * Creates a function that invokes `func` with `partial` arguments prepended
+     * to those provided to the new function. This method is like `_.bind` except
+     * it does **not** alter the `this` binding.
+     *
+     * The `_.partial.placeholder` value, which defaults to `_` in monolithic
+     * builds, may be used as a placeholder for partially applied arguments.
+     *
+     * **Note:** This method does not set the "length" property of partially
+     * applied functions.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {Function} func The function to partially apply arguments to.
+     * @param {...*} [partials] The arguments to be partially applied.
+     * @returns {Function} Returns the new partially applied function.
+     * @example
+     *
+     * var greet = function(greeting, name) {
+     *   return greeting + ' ' + name;
+     * };
+     *
+     * var sayHelloTo = _.partial(greet, 'hello');
+     * sayHelloTo('fred');
+     * // => 'hello fred'
+     *
+     * // using placeholders
+     * var greetFred = _.partial(greet, _, 'fred');
+     * greetFred('hi');
+     * // => 'hi fred'
+     */
+    var partial = createPartial(PARTIAL_FLAG);
+
+    /**
+     * This method is like `_.partial` except that partially applied arguments
+     * are appended to those provided to the new function.
+     *
+     * The `_.partialRight.placeholder` value, which defaults to `_` in monolithic
+     * builds, may be used as a placeholder for partially applied arguments.
+     *
+     * **Note:** This method does not set the "length" property of partially
+     * applied functions.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {Function} func The function to partially apply arguments to.
+     * @param {...*} [partials] The arguments to be partially applied.
+     * @returns {Function} Returns the new partially applied function.
+     * @example
+     *
+     * var greet = function(greeting, name) {
+     *   return greeting + ' ' + name;
+     * };
+     *
+     * var greetFred = _.partialRight(greet, 'fred');
+     * greetFred('hi');
+     * // => 'hi fred'
+     *
+     * // using placeholders
+     * var sayHelloTo = _.partialRight(greet, 'hello', _);
+     * sayHelloTo('fred');
+     * // => 'hello fred'
+     */
+    var partialRight = createPartial(PARTIAL_RIGHT_FLAG);
+
+    /**
+     * Creates a function that invokes `func` with arguments arranged according
+     * to the specified indexes where the argument value at the first index is
+     * provided as the first argument, the argument value at the second index is
+     * provided as the second argument, and so on.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {Function} func The function to rearrange arguments for.
+     * @param {...(number|number[])} indexes The arranged argument indexes,
+     *  specified as individual indexes or arrays of indexes.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var rearged = _.rearg(function(a, b, c) {
+     *   return [a, b, c];
+     * }, 2, 0, 1);
+     *
+     * rearged('b', 'c', 'a')
+     * // => ['a', 'b', 'c']
+     *
+     * var map = _.rearg(_.map, [1, 0]);
+     * map(function(n) {
+     *   return n * 3;
+     * }, [1, 2, 3]);
+     * // => [3, 6, 9]
+     */
+    var rearg = restParam(function(func, indexes) {
+      return createWrapper(func, REARG_FLAG, undefined, undefined, undefined, baseFlatten(indexes));
+    });
+
+    /**
+     * Creates a function that invokes `func` with the `this` binding of the
+     * created function and arguments from `start` and beyond provided as an array.
+     *
+     * **Note:** This method is based on the [rest parameter](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/rest_parameters).
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {Function} func The function to apply a rest parameter to.
+     * @param {number} [start=func.length-1] The start position of the rest parameter.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var say = _.restParam(function(what, names) {
+     *   return what + ' ' + _.initial(names).join(', ') +
+     *     (_.size(names) > 1 ? ', & ' : '') + _.last(names);
+     * });
+     *
+     * say('hello', 'fred', 'barney', 'pebbles');
+     * // => 'hello fred, barney, & pebbles'
+     */
+    function restParam(func, start) {
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      start = nativeMax(start === undefined ? (func.length - 1) : (+start || 0), 0);
+      return function() {
+        var args = arguments,
+            index = -1,
+            length = nativeMax(args.length - start, 0),
+            rest = Array(length);
+
+        while (++index < length) {
+          rest[index] = args[start + index];
+        }
+        switch (start) {
+          case 0: return func.call(this, rest);
+          case 1: return func.call(this, args[0], rest);
+          case 2: return func.call(this, args[0], args[1], rest);
+        }
+        var otherArgs = Array(start + 1);
+        index = -1;
+        while (++index < start) {
+          otherArgs[index] = args[index];
+        }
+        otherArgs[start] = rest;
+        return func.apply(this, otherArgs);
+      };
+    }
+
+    /**
+     * Creates a function that invokes `func` with the `this` binding of the created
+     * function and an array of arguments much like [`Function#apply`](https://es5.github.io/#x15.3.4.3).
+     *
+     * **Note:** This method is based on the [spread operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_operator).
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {Function} func The function to spread arguments over.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var say = _.spread(function(who, what) {
+     *   return who + ' says ' + what;
+     * });
+     *
+     * say(['fred', 'hello']);
+     * // => 'fred says hello'
+     *
+     * // with a Promise
+     * var numbers = Promise.all([
+     *   Promise.resolve(40),
+     *   Promise.resolve(36)
+     * ]);
+     *
+     * numbers.then(_.spread(function(x, y) {
+     *   return x + y;
+     * }));
+     * // => a Promise of 76
+     */
+    function spread(func) {
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      return function(array) {
+        return func.apply(this, array);
+      };
+    }
+
+    /**
+     * Creates a throttled function that only invokes `func` at most once per
+     * every `wait` milliseconds. The throttled function comes with a `cancel`
+     * method to cancel delayed invocations. Provide an options object to indicate
+     * that `func` should be invoked on the leading and/or trailing edge of the
+     * `wait` timeout. Subsequent calls to the throttled function return the
+     * result of the last `func` call.
+     *
+     * **Note:** If `leading` and `trailing` options are `true`, `func` is invoked
+     * on the trailing edge of the timeout only if the the throttled function is
+     * invoked more than once during the `wait` timeout.
+     *
+     * See [David Corbacho's article](http://drupalmotion.com/article/debounce-and-throttle-visual-explanation)
+     * for details over the differences between `_.throttle` and `_.debounce`.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {Function} func The function to throttle.
+     * @param {number} [wait=0] The number of milliseconds to throttle invocations to.
+     * @param {Object} [options] The options object.
+     * @param {boolean} [options.leading=true] Specify invoking on the leading
+     *  edge of the timeout.
+     * @param {boolean} [options.trailing=true] Specify invoking on the trailing
+     *  edge of the timeout.
+     * @returns {Function} Returns the new throttled function.
+     * @example
+     *
+     * // avoid excessively updating the position while scrolling
+     * jQuery(window).on('scroll', _.throttle(updatePosition, 100));
+     *
+     * // invoke `renewToken` when the click event is fired, but not more than once every 5 minutes
+     * jQuery('.interactive').on('click', _.throttle(renewToken, 300000, {
+     *   'trailing': false
+     * }));
+     *
+     * // cancel a trailing throttled call
+     * jQuery(window).on('popstate', throttled.cancel);
+     */
+    function throttle(func, wait, options) {
+      var leading = true,
+          trailing = true;
+
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      if (options === false) {
+        leading = false;
+      } else if (isObject(options)) {
+        leading = 'leading' in options ? !!options.leading : leading;
+        trailing = 'trailing' in options ? !!options.trailing : trailing;
+      }
+      return debounce(func, wait, { 'leading': leading, 'maxWait': +wait, 'trailing': trailing });
+    }
+
+    /**
+     * Creates a function that provides `value` to the wrapper function as its
+     * first argument. Any additional arguments provided to the function are
+     * appended to those provided to the wrapper function. The wrapper is invoked
+     * with the `this` binding of the created function.
+     *
+     * @static
+     * @memberOf _
+     * @category Function
+     * @param {*} value The value to wrap.
+     * @param {Function} wrapper The wrapper function.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var p = _.wrap(_.escape, function(func, text) {
+     *   return '<p>' + func(text) + '</p>';
+     * });
+     *
+     * p('fred, barney, & pebbles');
+     * // => '<p>fred, barney, &amp; pebbles</p>'
+     */
+    function wrap(value, wrapper) {
+      wrapper = wrapper == null ? identity : wrapper;
+      return createWrapper(wrapper, PARTIAL_FLAG, undefined, [value], []);
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a clone of `value`. If `isDeep` is `true` nested objects are cloned,
+     * otherwise they are assigned by reference. If `customizer` is provided it is
+     * invoked to produce the cloned values. If `customizer` returns `undefined`
+     * cloning is handled by the method instead. The `customizer` is bound to
+     * `thisArg` and invoked with two argument; (value [, index|key, object]).
+     *
+     * **Note:** This method is loosely based on the
+     * [structured clone algorithm](http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm).
+     * The enumerable properties of `arguments` objects and objects created by
+     * constructors other than `Object` are cloned to plain `Object` objects. An
+     * empty object is returned for uncloneable values such as functions, DOM nodes,
+     * Maps, Sets, and WeakMaps.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to clone.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @param {Function} [customizer] The function to customize cloning values.
+     * @param {*} [thisArg] The `this` binding of `customizer`.
+     * @returns {*} Returns the cloned value.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney' },
+     *   { 'user': 'fred' }
+     * ];
+     *
+     * var shallow = _.clone(users);
+     * shallow[0] === users[0];
+     * // => true
+     *
+     * var deep = _.clone(users, true);
+     * deep[0] === users[0];
+     * // => false
+     *
+     * // using a customizer callback
+     * var el = _.clone(document.body, function(value) {
+     *   if (_.isElement(value)) {
+     *     return value.cloneNode(false);
+     *   }
+     * });
+     *
+     * el === document.body
+     * // => false
+     * el.nodeName
+     * // => BODY
+     * el.childNodes.length;
+     * // => 0
+     */
+    function clone(value, isDeep, customizer, thisArg) {
+      if (isDeep && typeof isDeep != 'boolean' && isIterateeCall(value, isDeep, customizer)) {
+        isDeep = false;
+      }
+      else if (typeof isDeep == 'function') {
+        thisArg = customizer;
+        customizer = isDeep;
+        isDeep = false;
+      }
+      return typeof customizer == 'function'
+        ? baseClone(value, isDeep, bindCallback(customizer, thisArg, 1))
+        : baseClone(value, isDeep);
+    }
+
+    /**
+     * Creates a deep clone of `value`. If `customizer` is provided it is invoked
+     * to produce the cloned values. If `customizer` returns `undefined` cloning
+     * is handled by the method instead. The `customizer` is bound to `thisArg`
+     * and invoked with two argument; (value [, index|key, object]).
+     *
+     * **Note:** This method is loosely based on the
+     * [structured clone algorithm](http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm).
+     * The enumerable properties of `arguments` objects and objects created by
+     * constructors other than `Object` are cloned to plain `Object` objects. An
+     * empty object is returned for uncloneable values such as functions, DOM nodes,
+     * Maps, Sets, and WeakMaps.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to deep clone.
+     * @param {Function} [customizer] The function to customize cloning values.
+     * @param {*} [thisArg] The `this` binding of `customizer`.
+     * @returns {*} Returns the deep cloned value.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney' },
+     *   { 'user': 'fred' }
+     * ];
+     *
+     * var deep = _.cloneDeep(users);
+     * deep[0] === users[0];
+     * // => false
+     *
+     * // using a customizer callback
+     * var el = _.cloneDeep(document.body, function(value) {
+     *   if (_.isElement(value)) {
+     *     return value.cloneNode(true);
+     *   }
+     * });
+     *
+     * el === document.body
+     * // => false
+     * el.nodeName
+     * // => BODY
+     * el.childNodes.length;
+     * // => 20
+     */
+    function cloneDeep(value, customizer, thisArg) {
+      return typeof customizer == 'function'
+        ? baseClone(value, true, bindCallback(customizer, thisArg, 1))
+        : baseClone(value, true);
+    }
+
+    /**
+     * Checks if `value` is greater than `other`.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is greater than `other`, else `false`.
+     * @example
+     *
+     * _.gt(3, 1);
+     * // => true
+     *
+     * _.gt(3, 3);
+     * // => false
+     *
+     * _.gt(1, 3);
+     * // => false
+     */
+    function gt(value, other) {
+      return value > other;
+    }
+
+    /**
+     * Checks if `value` is greater than or equal to `other`.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is greater than or equal to `other`, else `false`.
+     * @example
+     *
+     * _.gte(3, 1);
+     * // => true
+     *
+     * _.gte(3, 3);
+     * // => true
+     *
+     * _.gte(1, 3);
+     * // => false
+     */
+    function gte(value, other) {
+      return value >= other;
+    }
+
+    /**
+     * Checks if `value` is classified as an `arguments` object.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+     * @example
+     *
+     * _.isArguments(function() { return arguments; }());
+     * // => true
+     *
+     * _.isArguments([1, 2, 3]);
+     * // => false
+     */
+    function isArguments(value) {
+      return isObjectLike(value) && isArrayLike(value) &&
+        hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee');
+    }
+
+    /**
+     * Checks if `value` is classified as an `Array` object.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+     * @example
+     *
+     * _.isArray([1, 2, 3]);
+     * // => true
+     *
+     * _.isArray(function() { return arguments; }());
+     * // => false
+     */
+    var isArray = nativeIsArray || function(value) {
+      return isObjectLike(value) && isLength(value.length) && objToString.call(value) == arrayTag;
+    };
+
+    /**
+     * Checks if `value` is classified as a boolean primitive or object.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+     * @example
+     *
+     * _.isBoolean(false);
+     * // => true
+     *
+     * _.isBoolean(null);
+     * // => false
+     */
+    function isBoolean(value) {
+      return value === true || value === false || (isObjectLike(value) && objToString.call(value) == boolTag);
+    }
+
+    /**
+     * Checks if `value` is classified as a `Date` object.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+     * @example
+     *
+     * _.isDate(new Date);
+     * // => true
+     *
+     * _.isDate('Mon April 23 2012');
+     * // => false
+     */
+    function isDate(value) {
+      return isObjectLike(value) && objToString.call(value) == dateTag;
+    }
+
+    /**
+     * Checks if `value` is a DOM element.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a DOM element, else `false`.
+     * @example
+     *
+     * _.isElement(document.body);
+     * // => true
+     *
+     * _.isElement('<body>');
+     * // => false
+     */
+    function isElement(value) {
+      return !!value && value.nodeType === 1 && isObjectLike(value) && !isPlainObject(value);
+    }
+
+    /**
+     * Checks if `value` is empty. A value is considered empty unless it is an
+     * `arguments` object, array, string, or jQuery-like collection with a length
+     * greater than `0` or an object with own enumerable properties.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {Array|Object|string} value The value to inspect.
+     * @returns {boolean} Returns `true` if `value` is empty, else `false`.
+     * @example
+     *
+     * _.isEmpty(null);
+     * // => true
+     *
+     * _.isEmpty(true);
+     * // => true
+     *
+     * _.isEmpty(1);
+     * // => true
+     *
+     * _.isEmpty([1, 2, 3]);
+     * // => false
+     *
+     * _.isEmpty({ 'a': 1 });
+     * // => false
+     */
+    function isEmpty(value) {
+      if (value == null) {
+        return true;
+      }
+      if (isArrayLike(value) && (isArray(value) || isString(value) || isArguments(value) ||
+          (isObjectLike(value) && isFunction(value.splice)))) {
+        return !value.length;
+      }
+      return !keys(value).length;
+    }
+
+    /**
+     * Performs a deep comparison between two values to determine if they are
+     * equivalent. If `customizer` is provided it is invoked to compare values.
+     * If `customizer` returns `undefined` comparisons are handled by the method
+     * instead. The `customizer` is bound to `thisArg` and invoked with three
+     * arguments: (value, other [, index|key]).
+     *
+     * **Note:** This method supports comparing arrays, booleans, `Date` objects,
+     * numbers, `Object` objects, regexes, and strings. Objects are compared by
+     * their own, not inherited, enumerable properties. Functions and DOM nodes
+     * are **not** supported. Provide a customizer function to extend support
+     * for comparing other values.
+     *
+     * @static
+     * @memberOf _
+     * @alias eq
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @param {Function} [customizer] The function to customize value comparisons.
+     * @param {*} [thisArg] The `this` binding of `customizer`.
+     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+     * @example
+     *
+     * var object = { 'user': 'fred' };
+     * var other = { 'user': 'fred' };
+     *
+     * object == other;
+     * // => false
+     *
+     * _.isEqual(object, other);
+     * // => true
+     *
+     * // using a customizer callback
+     * var array = ['hello', 'goodbye'];
+     * var other = ['hi', 'goodbye'];
+     *
+     * _.isEqual(array, other, function(value, other) {
+     *   if (_.every([value, other], RegExp.prototype.test, /^h(?:i|ello)$/)) {
+     *     return true;
+     *   }
+     * });
+     * // => true
+     */
+    function isEqual(value, other, customizer, thisArg) {
+      customizer = typeof customizer == 'function' ? bindCallback(customizer, thisArg, 3) : undefined;
+      var result = customizer ? customizer(value, other) : undefined;
+      return  result === undefined ? baseIsEqual(value, other, customizer) : !!result;
+    }
+
+    /**
+     * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`,
+     * `SyntaxError`, `TypeError`, or `URIError` object.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an error object, else `false`.
+     * @example
+     *
+     * _.isError(new Error);
+     * // => true
+     *
+     * _.isError(Error);
+     * // => false
+     */
+    function isError(value) {
+      return isObjectLike(value) && typeof value.message == 'string' && objToString.call(value) == errorTag;
+    }
+
+    /**
+     * Checks if `value` is a finite primitive number.
+     *
+     * **Note:** This method is based on [`Number.isFinite`](http://ecma-international.org/ecma-262/6.0/#sec-number.isfinite).
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a finite number, else `false`.
+     * @example
+     *
+     * _.isFinite(10);
+     * // => true
+     *
+     * _.isFinite('10');
+     * // => false
+     *
+     * _.isFinite(true);
+     * // => false
+     *
+     * _.isFinite(Object(10));
+     * // => false
+     *
+     * _.isFinite(Infinity);
+     * // => false
+     */
+    function isFinite(value) {
+      return typeof value == 'number' && nativeIsFinite(value);
+    }
+
+    /**
+     * Checks if `value` is classified as a `Function` object.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+     * @example
+     *
+     * _.isFunction(_);
+     * // => true
+     *
+     * _.isFunction(/abc/);
+     * // => false
+     */
+    function isFunction(value) {
+      // The use of `Object#toString` avoids issues with the `typeof` operator
+      // in older versions of Chrome and Safari which return 'function' for regexes
+      // and Safari 8 equivalents which return 'object' for typed array constructors.
+      return isObject(value) && objToString.call(value) == funcTag;
+    }
+
+    /**
+     * Checks if `value` is the [language type](https://es5.github.io/#x8) of `Object`.
+     * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an object, else `false`.
+     * @example
+     *
+     * _.isObject({});
+     * // => true
+     *
+     * _.isObject([1, 2, 3]);
+     * // => true
+     *
+     * _.isObject(1);
+     * // => false
+     */
+    function isObject(value) {
+      // Avoid a V8 JIT bug in Chrome 19-20.
+      // See https://code.google.com/p/v8/issues/detail?id=2291 for more details.
+      var type = typeof value;
+      return !!value && (type == 'object' || type == 'function');
+    }
+
+    /**
+     * Performs a deep comparison between `object` and `source` to determine if
+     * `object` contains equivalent property values. If `customizer` is provided
+     * it is invoked to compare values. If `customizer` returns `undefined`
+     * comparisons are handled by the method instead. The `customizer` is bound
+     * to `thisArg` and invoked with three arguments: (value, other, index|key).
+     *
+     * **Note:** This method supports comparing properties of arrays, booleans,
+     * `Date` objects, numbers, `Object` objects, regexes, and strings. Functions
+     * and DOM nodes are **not** supported. Provide a customizer function to extend
+     * support for comparing other values.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {Object} object The object to inspect.
+     * @param {Object} source The object of property values to match.
+     * @param {Function} [customizer] The function to customize value comparisons.
+     * @param {*} [thisArg] The `this` binding of `customizer`.
+     * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+     * @example
+     *
+     * var object = { 'user': 'fred', 'age': 40 };
+     *
+     * _.isMatch(object, { 'age': 40 });
+     * // => true
+     *
+     * _.isMatch(object, { 'age': 36 });
+     * // => false
+     *
+     * // using a customizer callback
+     * var object = { 'greeting': 'hello' };
+     * var source = { 'greeting': 'hi' };
+     *
+     * _.isMatch(object, source, function(value, other) {
+     *   return _.every([value, other], RegExp.prototype.test, /^h(?:i|ello)$/) || undefined;
+     * });
+     * // => true
+     */
+    function isMatch(object, source, customizer, thisArg) {
+      customizer = typeof customizer == 'function' ? bindCallback(customizer, thisArg, 3) : undefined;
+      return baseIsMatch(object, getMatchData(source), customizer);
+    }
+
+    /**
+     * Checks if `value` is `NaN`.
+     *
+     * **Note:** This method is not the same as [`isNaN`](https://es5.github.io/#x15.1.2.4)
+     * which returns `true` for `undefined` and other non-numeric values.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
+     * @example
+     *
+     * _.isNaN(NaN);
+     * // => true
+     *
+     * _.isNaN(new Number(NaN));
+     * // => true
+     *
+     * isNaN(undefined);
+     * // => true
+     *
+     * _.isNaN(undefined);
+     * // => false
+     */
+    function isNaN(value) {
+      // An `NaN` primitive is the only value that is not equal to itself.
+      // Perform the `toStringTag` check first to avoid errors with some host objects in IE.
+      return isNumber(value) && value != +value;
+    }
+
+    /**
+     * Checks if `value` is a native function.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a native function, else `false`.
+     * @example
+     *
+     * _.isNative(Array.prototype.push);
+     * // => true
+     *
+     * _.isNative(_);
+     * // => false
+     */
+    function isNative(value) {
+      if (value == null) {
+        return false;
+      }
+      if (isFunction(value)) {
+        return reIsNative.test(fnToString.call(value));
+      }
+      return isObjectLike(value) && reIsHostCtor.test(value);
+    }
+
+    /**
+     * Checks if `value` is `null`.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is `null`, else `false`.
+     * @example
+     *
+     * _.isNull(null);
+     * // => true
+     *
+     * _.isNull(void 0);
+     * // => false
+     */
+    function isNull(value) {
+      return value === null;
+    }
+
+    /**
+     * Checks if `value` is classified as a `Number` primitive or object.
+     *
+     * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are classified
+     * as numbers, use the `_.isFinite` method.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+     * @example
+     *
+     * _.isNumber(8.4);
+     * // => true
+     *
+     * _.isNumber(NaN);
+     * // => true
+     *
+     * _.isNumber('8.4');
+     * // => false
+     */
+    function isNumber(value) {
+      return typeof value == 'number' || (isObjectLike(value) && objToString.call(value) == numberTag);
+    }
+
+    /**
+     * Checks if `value` is a plain object, that is, an object created by the
+     * `Object` constructor or one with a `[[Prototype]]` of `null`.
+     *
+     * **Note:** This method assumes objects created by the `Object` constructor
+     * have no inherited enumerable properties.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     * }
+     *
+     * _.isPlainObject(new Foo);
+     * // => false
+     *
+     * _.isPlainObject([1, 2, 3]);
+     * // => false
+     *
+     * _.isPlainObject({ 'x': 0, 'y': 0 });
+     * // => true
+     *
+     * _.isPlainObject(Object.create(null));
+     * // => true
+     */
+    function isPlainObject(value) {
+      var Ctor;
+
+      // Exit early for non `Object` objects.
+      if (!(isObjectLike(value) && objToString.call(value) == objectTag && !isArguments(value)) ||
+          (!hasOwnProperty.call(value, 'constructor') && (Ctor = value.constructor, typeof Ctor == 'function' && !(Ctor instanceof Ctor)))) {
+        return false;
+      }
+      // IE < 9 iterates inherited properties before own properties. If the first
+      // iterated property is an object's own property then there are no inherited
+      // enumerable properties.
+      var result;
+      // In most environments an object's own properties are iterated before
+      // its inherited properties. If the last iterated property is an object's
+      // own property then there are no inherited enumerable properties.
+      baseForIn(value, function(subValue, key) {
+        result = key;
+      });
+      return result === undefined || hasOwnProperty.call(value, result);
+    }
+
+    /**
+     * Checks if `value` is classified as a `RegExp` object.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+     * @example
+     *
+     * _.isRegExp(/abc/);
+     * // => true
+     *
+     * _.isRegExp('/abc/');
+     * // => false
+     */
+    function isRegExp(value) {
+      return isObject(value) && objToString.call(value) == regexpTag;
+    }
+
+    /**
+     * Checks if `value` is classified as a `String` primitive or object.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+     * @example
+     *
+     * _.isString('abc');
+     * // => true
+     *
+     * _.isString(1);
+     * // => false
+     */
+    function isString(value) {
+      return typeof value == 'string' || (isObjectLike(value) && objToString.call(value) == stringTag);
+    }
+
+    /**
+     * Checks if `value` is classified as a typed array.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified, else `false`.
+     * @example
+     *
+     * _.isTypedArray(new Uint8Array);
+     * // => true
+     *
+     * _.isTypedArray([]);
+     * // => false
+     */
+    function isTypedArray(value) {
+      return isObjectLike(value) && isLength(value.length) && !!typedArrayTags[objToString.call(value)];
+    }
+
+    /**
+     * Checks if `value` is `undefined`.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
+     * @example
+     *
+     * _.isUndefined(void 0);
+     * // => true
+     *
+     * _.isUndefined(null);
+     * // => false
+     */
+    function isUndefined(value) {
+      return value === undefined;
+    }
+
+    /**
+     * Checks if `value` is less than `other`.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is less than `other`, else `false`.
+     * @example
+     *
+     * _.lt(1, 3);
+     * // => true
+     *
+     * _.lt(3, 3);
+     * // => false
+     *
+     * _.lt(3, 1);
+     * // => false
+     */
+    function lt(value, other) {
+      return value < other;
+    }
+
+    /**
+     * Checks if `value` is less than or equal to `other`.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is less than or equal to `other`, else `false`.
+     * @example
+     *
+     * _.lte(1, 3);
+     * // => true
+     *
+     * _.lte(3, 3);
+     * // => true
+     *
+     * _.lte(3, 1);
+     * // => false
+     */
+    function lte(value, other) {
+      return value <= other;
+    }
+
+    /**
+     * Converts `value` to an array.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to convert.
+     * @returns {Array} Returns the converted array.
+     * @example
+     *
+     * (function() {
+     *   return _.toArray(arguments).slice(1);
+     * }(1, 2, 3));
+     * // => [2, 3]
+     */
+    function toArray(value) {
+      var length = value ? getLength(value) : 0;
+      if (!isLength(length)) {
+        return values(value);
+      }
+      if (!length) {
+        return [];
+      }
+      return arrayCopy(value);
+    }
+
+    /**
+     * Converts `value` to a plain object flattening inherited enumerable
+     * properties of `value` to own properties of the plain object.
+     *
+     * @static
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to convert.
+     * @returns {Object} Returns the converted plain object.
+     * @example
+     *
+     * function Foo() {
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.assign({ 'a': 1 }, new Foo);
+     * // => { 'a': 1, 'b': 2 }
+     *
+     * _.assign({ 'a': 1 }, _.toPlainObject(new Foo));
+     * // => { 'a': 1, 'b': 2, 'c': 3 }
+     */
+    function toPlainObject(value) {
+      return baseCopy(value, keysIn(value));
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Recursively merges own enumerable properties of the source object(s), that
+     * don't resolve to `undefined` into the destination object. Subsequent sources
+     * overwrite property assignments of previous sources. If `customizer` is
+     * provided it is invoked to produce the merged values of the destination and
+     * source properties. If `customizer` returns `undefined` merging is handled
+     * by the method instead. The `customizer` is bound to `thisArg` and invoked
+     * with five arguments: (objectValue, sourceValue, key, object, source).
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} [sources] The source objects.
+     * @param {Function} [customizer] The function to customize assigned values.
+     * @param {*} [thisArg] The `this` binding of `customizer`.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var users = {
+     *   'data': [{ 'user': 'barney' }, { 'user': 'fred' }]
+     * };
+     *
+     * var ages = {
+     *   'data': [{ 'age': 36 }, { 'age': 40 }]
+     * };
+     *
+     * _.merge(users, ages);
+     * // => { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] }
+     *
+     * // using a customizer callback
+     * var object = {
+     *   'fruits': ['apple'],
+     *   'vegetables': ['beet']
+     * };
+     *
+     * var other = {
+     *   'fruits': ['banana'],
+     *   'vegetables': ['carrot']
+     * };
+     *
+     * _.merge(object, other, function(a, b) {
+     *   if (_.isArray(a)) {
+     *     return a.concat(b);
+     *   }
+     * });
+     * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot'] }
+     */
+    var merge = createAssigner(baseMerge);
+
+    /**
+     * Assigns own enumerable properties of source object(s) to the destination
+     * object. Subsequent sources overwrite property assignments of previous sources.
+     * If `customizer` is provided it is invoked to produce the assigned values.
+     * The `customizer` is bound to `thisArg` and invoked with five arguments:
+     * (objectValue, sourceValue, key, object, source).
+     *
+     * **Note:** This method mutates `object` and is based on
+     * [`Object.assign`](http://ecma-international.org/ecma-262/6.0/#sec-object.assign).
+     *
+     * @static
+     * @memberOf _
+     * @alias extend
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} [sources] The source objects.
+     * @param {Function} [customizer] The function to customize assigned values.
+     * @param {*} [thisArg] The `this` binding of `customizer`.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * _.assign({ 'user': 'barney' }, { 'age': 40 }, { 'user': 'fred' });
+     * // => { 'user': 'fred', 'age': 40 }
+     *
+     * // using a customizer callback
+     * var defaults = _.partialRight(_.assign, function(value, other) {
+     *   return _.isUndefined(value) ? other : value;
+     * });
+     *
+     * defaults({ 'user': 'barney' }, { 'age': 36 }, { 'user': 'fred' });
+     * // => { 'user': 'barney', 'age': 36 }
+     */
+    var assign = createAssigner(function(object, source, customizer) {
+      return customizer
+        ? assignWith(object, source, customizer)
+        : baseAssign(object, source);
+    });
+
+    /**
+     * Creates an object that inherits from the given `prototype` object. If a
+     * `properties` object is provided its own enumerable properties are assigned
+     * to the created object.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} prototype The object to inherit from.
+     * @param {Object} [properties] The properties to assign to the object.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * function Shape() {
+     *   this.x = 0;
+     *   this.y = 0;
+     * }
+     *
+     * function Circle() {
+     *   Shape.call(this);
+     * }
+     *
+     * Circle.prototype = _.create(Shape.prototype, {
+     *   'constructor': Circle
+     * });
+     *
+     * var circle = new Circle;
+     * circle instanceof Circle;
+     * // => true
+     *
+     * circle instanceof Shape;
+     * // => true
+     */
+    function create(prototype, properties, guard) {
+      var result = baseCreate(prototype);
+      if (guard && isIterateeCall(prototype, properties, guard)) {
+        properties = undefined;
+      }
+      return properties ? baseAssign(result, properties) : result;
+    }
+
+    /**
+     * Assigns own enumerable properties of source object(s) to the destination
+     * object for all destination properties that resolve to `undefined`. Once a
+     * property is set, additional values of the same property are ignored.
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} [sources] The source objects.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * _.defaults({ 'user': 'barney' }, { 'age': 36 }, { 'user': 'fred' });
+     * // => { 'user': 'barney', 'age': 36 }
+     */
+    var defaults = createDefaults(assign, assignDefaults);
+
+    /**
+     * This method is like `_.defaults` except that it recursively assigns
+     * default properties.
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} [sources] The source objects.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * _.defaultsDeep({ 'user': { 'name': 'barney' } }, { 'user': { 'name': 'fred', 'age': 36 } });
+     * // => { 'user': { 'name': 'barney', 'age': 36 } }
+     *
+     */
+    var defaultsDeep = createDefaults(merge, mergeDefaults);
+
+    /**
+     * This method is like `_.find` except that it returns the key of the first
+     * element `predicate` returns truthy for instead of the element itself.
+     *
+     * If a property name is provided for `predicate` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `predicate` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to search.
+     * @param {Function|Object|string} [predicate=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `predicate`.
+     * @returns {string|undefined} Returns the key of the matched element, else `undefined`.
+     * @example
+     *
+     * var users = {
+     *   'barney':  { 'age': 36, 'active': true },
+     *   'fred':    { 'age': 40, 'active': false },
+     *   'pebbles': { 'age': 1,  'active': true }
+     * };
+     *
+     * _.findKey(users, function(chr) {
+     *   return chr.age < 40;
+     * });
+     * // => 'barney' (iteration order is not guaranteed)
+     *
+     * // using the `_.matches` callback shorthand
+     * _.findKey(users, { 'age': 1, 'active': true });
+     * // => 'pebbles'
+     *
+     * // using the `_.matchesProperty` callback shorthand
+     * _.findKey(users, 'active', false);
+     * // => 'fred'
+     *
+     * // using the `_.property` callback shorthand
+     * _.findKey(users, 'active');
+     * // => 'barney'
+     */
+    var findKey = createFindKey(baseForOwn);
+
+    /**
+     * This method is like `_.findKey` except that it iterates over elements of
+     * a collection in the opposite order.
+     *
+     * If a property name is provided for `predicate` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `predicate` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to search.
+     * @param {Function|Object|string} [predicate=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `predicate`.
+     * @returns {string|undefined} Returns the key of the matched element, else `undefined`.
+     * @example
+     *
+     * var users = {
+     *   'barney':  { 'age': 36, 'active': true },
+     *   'fred':    { 'age': 40, 'active': false },
+     *   'pebbles': { 'age': 1,  'active': true }
+     * };
+     *
+     * _.findLastKey(users, function(chr) {
+     *   return chr.age < 40;
+     * });
+     * // => returns `pebbles` assuming `_.findKey` returns `barney`
+     *
+     * // using the `_.matches` callback shorthand
+     * _.findLastKey(users, { 'age': 36, 'active': true });
+     * // => 'barney'
+     *
+     * // using the `_.matchesProperty` callback shorthand
+     * _.findLastKey(users, 'active', false);
+     * // => 'fred'
+     *
+     * // using the `_.property` callback shorthand
+     * _.findLastKey(users, 'active');
+     * // => 'pebbles'
+     */
+    var findLastKey = createFindKey(baseForOwnRight);
+
+    /**
+     * Iterates over own and inherited enumerable properties of an object invoking
+     * `iteratee` for each property. The `iteratee` is bound to `thisArg` and invoked
+     * with three arguments: (value, key, object). Iteratee functions may exit
+     * iteration early by explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.forIn(new Foo, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => logs 'a', 'b', and 'c' (iteration order is not guaranteed)
+     */
+    var forIn = createForIn(baseFor);
+
+    /**
+     * This method is like `_.forIn` except that it iterates over properties of
+     * `object` in the opposite order.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.forInRight(new Foo, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => logs 'c', 'b', and 'a' assuming `_.forIn ` logs 'a', 'b', and 'c'
+     */
+    var forInRight = createForIn(baseForRight);
+
+    /**
+     * Iterates over own enumerable properties of an object invoking `iteratee`
+     * for each property. The `iteratee` is bound to `thisArg` and invoked with
+     * three arguments: (value, key, object). Iteratee functions may exit iteration
+     * early by explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.forOwn(new Foo, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => logs 'a' and 'b' (iteration order is not guaranteed)
+     */
+    var forOwn = createForOwn(baseForOwn);
+
+    /**
+     * This method is like `_.forOwn` except that it iterates over properties of
+     * `object` in the opposite order.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.forOwnRight(new Foo, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => logs 'b' and 'a' assuming `_.forOwn` logs 'a' and 'b'
+     */
+    var forOwnRight = createForOwn(baseForOwnRight);
+
+    /**
+     * Creates an array of function property names from all enumerable properties,
+     * own and inherited, of `object`.
+     *
+     * @static
+     * @memberOf _
+     * @alias methods
+     * @category Object
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns the new array of property names.
+     * @example
+     *
+     * _.functions(_);
+     * // => ['after', 'ary', 'assign', ...]
+     */
+    function functions(object) {
+      return baseFunctions(object, keysIn(object));
+    }
+
+    /**
+     * Gets the property value at `path` of `object`. If the resolved value is
+     * `undefined` the `defaultValue` is used in its place.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the property to get.
+     * @param {*} [defaultValue] The value returned if the resolved value is `undefined`.
+     * @returns {*} Returns the resolved value.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+     *
+     * _.get(object, 'a[0].b.c');
+     * // => 3
+     *
+     * _.get(object, ['a', '0', 'b', 'c']);
+     * // => 3
+     *
+     * _.get(object, 'a.b.c', 'default');
+     * // => 'default'
+     */
+    function get(object, path, defaultValue) {
+      var result = object == null ? undefined : baseGet(object, toPath(path), path + '');
+      return result === undefined ? defaultValue : result;
+    }
+
+    /**
+     * Checks if `path` is a direct property.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path to check.
+     * @returns {boolean} Returns `true` if `path` is a direct property, else `false`.
+     * @example
+     *
+     * var object = { 'a': { 'b': { 'c': 3 } } };
+     *
+     * _.has(object, 'a');
+     * // => true
+     *
+     * _.has(object, 'a.b.c');
+     * // => true
+     *
+     * _.has(object, ['a', 'b', 'c']);
+     * // => true
+     */
+    function has(object, path) {
+      if (object == null) {
+        return false;
+      }
+      var result = hasOwnProperty.call(object, path);
+      if (!result && !isKey(path)) {
+        path = toPath(path);
+        object = path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1));
+        if (object == null) {
+          return false;
+        }
+        path = last(path);
+        result = hasOwnProperty.call(object, path);
+      }
+      return result || (isLength(object.length) && isIndex(path, object.length) &&
+        (isArray(object) || isArguments(object)));
+    }
+
+    /**
+     * Creates an object composed of the inverted keys and values of `object`.
+     * If `object` contains duplicate values, subsequent values overwrite property
+     * assignments of previous values unless `multiValue` is `true`.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to invert.
+     * @param {boolean} [multiValue] Allow multiple values per key.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
+     * @returns {Object} Returns the new inverted object.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': 2, 'c': 1 };
+     *
+     * _.invert(object);
+     * // => { '1': 'c', '2': 'b' }
+     *
+     * // with `multiValue`
+     * _.invert(object, true);
+     * // => { '1': ['a', 'c'], '2': ['b'] }
+     */
+    function invert(object, multiValue, guard) {
+      if (guard && isIterateeCall(object, multiValue, guard)) {
+        multiValue = undefined;
+      }
+      var index = -1,
+          props = keys(object),
+          length = props.length,
+          result = {};
+
+      while (++index < length) {
+        var key = props[index],
+            value = object[key];
+
+        if (multiValue) {
+          if (hasOwnProperty.call(result, value)) {
+            result[value].push(key);
+          } else {
+            result[value] = [key];
+          }
+        }
+        else {
+          result[value] = key;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Creates an array of the own enumerable property names of `object`.
+     *
+     * **Note:** Non-object values are coerced to objects. See the
+     * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys)
+     * for more details.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.keys(new Foo);
+     * // => ['a', 'b'] (iteration order is not guaranteed)
+     *
+     * _.keys('hi');
+     * // => ['0', '1']
+     */
+    var keys = !nativeKeys ? shimKeys : function(object) {
+      var Ctor = object == null ? undefined : object.constructor;
+      if ((typeof Ctor == 'function' && Ctor.prototype === object) ||
+          (typeof object != 'function' && isArrayLike(object))) {
+        return shimKeys(object);
+      }
+      return isObject(object) ? nativeKeys(object) : [];
+    };
+
+    /**
+     * Creates an array of the own and inherited enumerable property names of `object`.
+     *
+     * **Note:** Non-object values are coerced to objects.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.keysIn(new Foo);
+     * // => ['a', 'b', 'c'] (iteration order is not guaranteed)
+     */
+    function keysIn(object) {
+      if (object == null) {
+        return [];
+      }
+      if (!isObject(object)) {
+        object = Object(object);
+      }
+      var length = object.length;
+      length = (length && isLength(length) &&
+        (isArray(object) || isArguments(object)) && length) || 0;
+
+      var Ctor = object.constructor,
+          index = -1,
+          isProto = typeof Ctor == 'function' && Ctor.prototype === object,
+          result = Array(length),
+          skipIndexes = length > 0;
+
+      while (++index < length) {
+        result[index] = (index + '');
+      }
+      for (var key in object) {
+        if (!(skipIndexes && isIndex(key, length)) &&
+            !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
+          result.push(key);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The opposite of `_.mapValues`; this method creates an object with the
+     * same values as `object` and keys generated by running each own enumerable
+     * property of `object` through `iteratee`.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function|Object|string} [iteratee=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {Object} Returns the new mapped object.
+     * @example
+     *
+     * _.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) {
+     *   return key + value;
+     * });
+     * // => { 'a1': 1, 'b2': 2 }
+     */
+    var mapKeys = createObjectMapper(true);
+
+    /**
+     * Creates an object with the same keys as `object` and values generated by
+     * running each own enumerable property of `object` through `iteratee`. The
+     * iteratee function is bound to `thisArg` and invoked with three arguments:
+     * (value, key, object).
+     *
+     * If a property name is provided for `iteratee` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `iteratee` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function|Object|string} [iteratee=_.identity] The function invoked
+     *  per iteration.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {Object} Returns the new mapped object.
+     * @example
+     *
+     * _.mapValues({ 'a': 1, 'b': 2 }, function(n) {
+     *   return n * 3;
+     * });
+     * // => { 'a': 3, 'b': 6 }
+     *
+     * var users = {
+     *   'fred':    { 'user': 'fred',    'age': 40 },
+     *   'pebbles': { 'user': 'pebbles', 'age': 1 }
+     * };
+     *
+     * // using the `_.property` callback shorthand
+     * _.mapValues(users, 'age');
+     * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
+     */
+    var mapValues = createObjectMapper();
+
+    /**
+     * The opposite of `_.pick`; this method creates an object composed of the
+     * own and inherited enumerable properties of `object` that are not omitted.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The source object.
+     * @param {Function|...(string|string[])} [predicate] The function invoked per
+     *  iteration or property names to omit, specified as individual property
+     *  names or arrays of property names.
+     * @param {*} [thisArg] The `this` binding of `predicate`.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * var object = { 'user': 'fred', 'age': 40 };
+     *
+     * _.omit(object, 'age');
+     * // => { 'user': 'fred' }
+     *
+     * _.omit(object, _.isNumber);
+     * // => { 'user': 'fred' }
+     */
+    var omit = restParam(function(object, props) {
+      if (object == null) {
+        return {};
+      }
+      if (typeof props[0] != 'function') {
+        var props = arrayMap(baseFlatten(props), String);
+        return pickByArray(object, baseDifference(keysIn(object), props));
+      }
+      var predicate = bindCallback(props[0], props[1], 3);
+      return pickByCallback(object, function(value, key, object) {
+        return !predicate(value, key, object);
+      });
+    });
+
+    /**
+     * Creates a two dimensional array of the key-value pairs for `object`,
+     * e.g. `[[key1, value1], [key2, value2]]`.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the new array of key-value pairs.
+     * @example
+     *
+     * _.pairs({ 'barney': 36, 'fred': 40 });
+     * // => [['barney', 36], ['fred', 40]] (iteration order is not guaranteed)
+     */
+    function pairs(object) {
+      object = toObject(object);
+
+      var index = -1,
+          props = keys(object),
+          length = props.length,
+          result = Array(length);
+
+      while (++index < length) {
+        var key = props[index];
+        result[index] = [key, object[key]];
+      }
+      return result;
+    }
+
+    /**
+     * Creates an object composed of the picked `object` properties. Property
+     * names may be specified as individual arguments or as arrays of property
+     * names. If `predicate` is provided it is invoked for each property of `object`
+     * picking the properties `predicate` returns truthy for. The predicate is
+     * bound to `thisArg` and invoked with three arguments: (value, key, object).
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The source object.
+     * @param {Function|...(string|string[])} [predicate] The function invoked per
+     *  iteration or property names to pick, specified as individual property
+     *  names or arrays of property names.
+     * @param {*} [thisArg] The `this` binding of `predicate`.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * var object = { 'user': 'fred', 'age': 40 };
+     *
+     * _.pick(object, 'user');
+     * // => { 'user': 'fred' }
+     *
+     * _.pick(object, _.isString);
+     * // => { 'user': 'fred' }
+     */
+    var pick = restParam(function(object, props) {
+      if (object == null) {
+        return {};
+      }
+      return typeof props[0] == 'function'
+        ? pickByCallback(object, bindCallback(props[0], props[1], 3))
+        : pickByArray(object, baseFlatten(props));
+    });
+
+    /**
+     * This method is like `_.get` except that if the resolved value is a function
+     * it is invoked with the `this` binding of its parent object and its result
+     * is returned.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the property to resolve.
+     * @param {*} [defaultValue] The value returned if the resolved value is `undefined`.
+     * @returns {*} Returns the resolved value.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] };
+     *
+     * _.result(object, 'a[0].b.c1');
+     * // => 3
+     *
+     * _.result(object, 'a[0].b.c2');
+     * // => 4
+     *
+     * _.result(object, 'a.b.c', 'default');
+     * // => 'default'
+     *
+     * _.result(object, 'a.b.c', _.constant('default'));
+     * // => 'default'
+     */
+    function result(object, path, defaultValue) {
+      var result = object == null ? undefined : object[path];
+      if (result === undefined) {
+        if (object != null && !isKey(path, object)) {
+          path = toPath(path);
+          object = path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1));
+          result = object == null ? undefined : object[last(path)];
+        }
+        result = result === undefined ? defaultValue : result;
+      }
+      return isFunction(result) ? result.call(object) : result;
+    }
+
+    /**
+     * Sets the property value of `path` on `object`. If a portion of `path`
+     * does not exist it is created.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to augment.
+     * @param {Array|string} path The path of the property to set.
+     * @param {*} value The value to set.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+     *
+     * _.set(object, 'a[0].b.c', 4);
+     * console.log(object.a[0].b.c);
+     * // => 4
+     *
+     * _.set(object, 'x[0].y.z', 5);
+     * console.log(object.x[0].y.z);
+     * // => 5
+     */
+    function set(object, path, value) {
+      if (object == null) {
+        return object;
+      }
+      var pathKey = (path + '');
+      path = (object[pathKey] != null || isKey(path, object)) ? [pathKey] : toPath(path);
+
+      var index = -1,
+          length = path.length,
+          lastIndex = length - 1,
+          nested = object;
+
+      while (nested != null && ++index < length) {
+        var key = path[index];
+        if (isObject(nested)) {
+          if (index == lastIndex) {
+            nested[key] = value;
+          } else if (nested[key] == null) {
+            nested[key] = isIndex(path[index + 1]) ? [] : {};
+          }
+        }
+        nested = nested[key];
+      }
+      return object;
+    }
+
+    /**
+     * An alternative to `_.reduce`; this method transforms `object` to a new
+     * `accumulator` object which is the result of running each of its own enumerable
+     * properties through `iteratee`, with each invocation potentially mutating
+     * the `accumulator` object. The `iteratee` is bound to `thisArg` and invoked
+     * with four arguments: (accumulator, value, key, object). Iteratee functions
+     * may exit iteration early by explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Array|Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [accumulator] The custom accumulator value.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {*} Returns the accumulated value.
+     * @example
+     *
+     * _.transform([2, 3, 4], function(result, n) {
+     *   result.push(n *= n);
+     *   return n % 2 == 0;
+     * });
+     * // => [4, 9]
+     *
+     * _.transform({ 'a': 1, 'b': 2 }, function(result, n, key) {
+     *   result[key] = n * 3;
+     * });
+     * // => { 'a': 3, 'b': 6 }
+     */
+    function transform(object, iteratee, accumulator, thisArg) {
+      var isArr = isArray(object) || isTypedArray(object);
+      iteratee = getCallback(iteratee, thisArg, 4);
+
+      if (accumulator == null) {
+        if (isArr || isObject(object)) {
+          var Ctor = object.constructor;
+          if (isArr) {
+            accumulator = isArray(object) ? new Ctor : [];
+          } else {
+            accumulator = baseCreate(isFunction(Ctor) ? Ctor.prototype : undefined);
+          }
+        } else {
+          accumulator = {};
+        }
+      }
+      (isArr ? arrayEach : baseForOwn)(object, function(value, index, object) {
+        return iteratee(accumulator, value, index, object);
+      });
+      return accumulator;
+    }
+
+    /**
+     * Creates an array of the own enumerable property values of `object`.
+     *
+     * **Note:** Non-object values are coerced to objects.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property values.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.values(new Foo);
+     * // => [1, 2] (iteration order is not guaranteed)
+     *
+     * _.values('hi');
+     * // => ['h', 'i']
+     */
+    function values(object) {
+      return baseValues(object, keys(object));
+    }
+
+    /**
+     * Creates an array of the own and inherited enumerable property values
+     * of `object`.
+     *
+     * **Note:** Non-object values are coerced to objects.
+     *
+     * @static
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property values.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.valuesIn(new Foo);
+     * // => [1, 2, 3] (iteration order is not guaranteed)
+     */
+    function valuesIn(object) {
+      return baseValues(object, keysIn(object));
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Checks if `n` is between `start` and up to but not including, `end`. If
+     * `end` is not specified it is set to `start` with `start` then set to `0`.
+     *
+     * @static
+     * @memberOf _
+     * @category Number
+     * @param {number} n The number to check.
+     * @param {number} [start=0] The start of the range.
+     * @param {number} end The end of the range.
+     * @returns {boolean} Returns `true` if `n` is in the range, else `false`.
+     * @example
+     *
+     * _.inRange(3, 2, 4);
+     * // => true
+     *
+     * _.inRange(4, 8);
+     * // => true
+     *
+     * _.inRange(4, 2);
+     * // => false
+     *
+     * _.inRange(2, 2);
+     * // => false
+     *
+     * _.inRange(1.2, 2);
+     * // => true
+     *
+     * _.inRange(5.2, 4);
+     * // => false
+     */
+    function inRange(value, start, end) {
+      start = +start || 0;
+      if (end === undefined) {
+        end = start;
+        start = 0;
+      } else {
+        end = +end || 0;
+      }
+      return value >= nativeMin(start, end) && value < nativeMax(start, end);
+    }
+
+    /**
+     * Produces a random number between `min` and `max` (inclusive). If only one
+     * argument is provided a number between `0` and the given number is returned.
+     * If `floating` is `true`, or either `min` or `max` are floats, a floating-point
+     * number is returned instead of an integer.
+     *
+     * @static
+     * @memberOf _
+     * @category Number
+     * @param {number} [min=0] The minimum possible value.
+     * @param {number} [max=1] The maximum possible value.
+     * @param {boolean} [floating] Specify returning a floating-point number.
+     * @returns {number} Returns the random number.
+     * @example
+     *
+     * _.random(0, 5);
+     * // => an integer between 0 and 5
+     *
+     * _.random(5);
+     * // => also an integer between 0 and 5
+     *
+     * _.random(5, true);
+     * // => a floating-point number between 0 and 5
+     *
+     * _.random(1.2, 5.2);
+     * // => a floating-point number between 1.2 and 5.2
+     */
+    function random(min, max, floating) {
+      if (floating && isIterateeCall(min, max, floating)) {
+        max = floating = undefined;
+      }
+      var noMin = min == null,
+          noMax = max == null;
+
+      if (floating == null) {
+        if (noMax && typeof min == 'boolean') {
+          floating = min;
+          min = 1;
+        }
+        else if (typeof max == 'boolean') {
+          floating = max;
+          noMax = true;
+        }
+      }
+      if (noMin && noMax) {
+        max = 1;
+        noMax = false;
+      }
+      min = +min || 0;
+      if (noMax) {
+        max = min;
+        min = 0;
+      } else {
+        max = +max || 0;
+      }
+      if (floating || min % 1 || max % 1) {
+        var rand = nativeRandom();
+        return nativeMin(min + (rand * (max - min + parseFloat('1e-' + ((rand + '').length - 1)))), max);
+      }
+      return baseRandom(min, max);
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase).
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the camel cased string.
+     * @example
+     *
+     * _.camelCase('Foo Bar');
+     * // => 'fooBar'
+     *
+     * _.camelCase('--foo-bar');
+     * // => 'fooBar'
+     *
+     * _.camelCase('__foo_bar__');
+     * // => 'fooBar'
+     */
+    var camelCase = createCompounder(function(result, word, index) {
+      word = word.toLowerCase();
+      return result + (index ? (word.charAt(0).toUpperCase() + word.slice(1)) : word);
+    });
+
+    /**
+     * Capitalizes the first character of `string`.
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to capitalize.
+     * @returns {string} Returns the capitalized string.
+     * @example
+     *
+     * _.capitalize('fred');
+     * // => 'Fred'
+     */
+    function capitalize(string) {
+      string = baseToString(string);
+      return string && (string.charAt(0).toUpperCase() + string.slice(1));
+    }
+
+    /**
+     * Deburrs `string` by converting [latin-1 supplementary letters](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table)
+     * to basic latin letters and removing [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks).
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to deburr.
+     * @returns {string} Returns the deburred string.
+     * @example
+     *
+     * _.deburr('déjà vu');
+     * // => 'deja vu'
+     */
+    function deburr(string) {
+      string = baseToString(string);
+      return string && string.replace(reLatin1, deburrLetter).replace(reComboMark, '');
+    }
+
+    /**
+     * Checks if `string` ends with the given target string.
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to search.
+     * @param {string} [target] The string to search for.
+     * @param {number} [position=string.length] The position to search from.
+     * @returns {boolean} Returns `true` if `string` ends with `target`, else `false`.
+     * @example
+     *
+     * _.endsWith('abc', 'c');
+     * // => true
+     *
+     * _.endsWith('abc', 'b');
+     * // => false
+     *
+     * _.endsWith('abc', 'b', 2);
+     * // => true
+     */
+    function endsWith(string, target, position) {
+      string = baseToString(string);
+      target = (target + '');
+
+      var length = string.length;
+      position = position === undefined
+        ? length
+        : nativeMin(position < 0 ? 0 : (+position || 0), length);
+
+      position -= target.length;
+      return position >= 0 && string.indexOf(target, position) == position;
+    }
+
+    /**
+     * Converts the characters "&", "<", ">", '"', "'", and "\`", in `string` to
+     * their corresponding HTML entities.
+     *
+     * **Note:** No other characters are escaped. To escape additional characters
+     * use a third-party library like [_he_](https://mths.be/he).
+     *
+     * Though the ">" character is escaped for symmetry, characters like
+     * ">" and "/" don't need escaping in HTML and have no special meaning
+     * unless they're part of a tag or unquoted attribute value.
+     * See [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands)
+     * (under "semi-related fun fact") for more details.
+     *
+     * Backticks are escaped because in Internet Explorer < 9, they can break out
+     * of attribute values or HTML comments. See [#59](https://html5sec.org/#59),
+     * [#102](https://html5sec.org/#102), [#108](https://html5sec.org/#108), and
+     * [#133](https://html5sec.org/#133) of the [HTML5 Security Cheatsheet](https://html5sec.org/)
+     * for more details.
+     *
+     * When working with HTML you should always [quote attribute values](http://wonko.com/post/html-escaping)
+     * to reduce XSS vectors.
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to escape.
+     * @returns {string} Returns the escaped string.
+     * @example
+     *
+     * _.escape('fred, barney, & pebbles');
+     * // => 'fred, barney, &amp; pebbles'
+     */
+    function escape(string) {
+      // Reset `lastIndex` because in IE < 9 `String#replace` does not.
+      string = baseToString(string);
+      return (string && reHasUnescapedHtml.test(string))
+        ? string.replace(reUnescapedHtml, escapeHtmlChar)
+        : string;
+    }
+
+    /**
+     * Escapes the `RegExp` special characters "\", "/", "^", "$", ".", "|", "?",
+     * "*", "+", "(", ")", "[", "]", "{" and "}" in `string`.
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to escape.
+     * @returns {string} Returns the escaped string.
+     * @example
+     *
+     * _.escapeRegExp('[lodash](https://lodash.com/)');
+     * // => '\[lodash\]\(https:\/\/lodash\.com\/\)'
+     */
+    function escapeRegExp(string) {
+      string = baseToString(string);
+      return (string && reHasRegExpChars.test(string))
+        ? string.replace(reRegExpChars, escapeRegExpChar)
+        : (string || '(?:)');
+    }
+
+    /**
+     * Converts `string` to [kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles).
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the kebab cased string.
+     * @example
+     *
+     * _.kebabCase('Foo Bar');
+     * // => 'foo-bar'
+     *
+     * _.kebabCase('fooBar');
+     * // => 'foo-bar'
+     *
+     * _.kebabCase('__foo_bar__');
+     * // => 'foo-bar'
+     */
+    var kebabCase = createCompounder(function(result, word, index) {
+      return result + (index ? '-' : '') + word.toLowerCase();
+    });
+
+    /**
+     * Pads `string` on the left and right sides if it's shorter than `length`.
+     * Padding characters are truncated if they can't be evenly divided by `length`.
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to pad.
+     * @param {number} [length=0] The padding length.
+     * @param {string} [chars=' '] The string used as padding.
+     * @returns {string} Returns the padded string.
+     * @example
+     *
+     * _.pad('abc', 8);
+     * // => '  abc   '
+     *
+     * _.pad('abc', 8, '_-');
+     * // => '_-abc_-_'
+     *
+     * _.pad('abc', 3);
+     * // => 'abc'
+     */
+    function pad(string, length, chars) {
+      string = baseToString(string);
+      length = +length;
+
+      var strLength = string.length;
+      if (strLength >= length || !nativeIsFinite(length)) {
+        return string;
+      }
+      var mid = (length - strLength) / 2,
+          leftLength = nativeFloor(mid),
+          rightLength = nativeCeil(mid);
+
+      chars = createPadding('', rightLength, chars);
+      return chars.slice(0, leftLength) + string + chars;
+    }
+
+    /**
+     * Pads `string` on the left side if it's shorter than `length`. Padding
+     * characters are truncated if they exceed `length`.
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to pad.
+     * @param {number} [length=0] The padding length.
+     * @param {string} [chars=' '] The string used as padding.
+     * @returns {string} Returns the padded string.
+     * @example
+     *
+     * _.padLeft('abc', 6);
+     * // => '   abc'
+     *
+     * _.padLeft('abc', 6, '_-');
+     * // => '_-_abc'
+     *
+     * _.padLeft('abc', 3);
+     * // => 'abc'
+     */
+    var padLeft = createPadDir();
+
+    /**
+     * Pads `string` on the right side if it's shorter than `length`. Padding
+     * characters are truncated if they exceed `length`.
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to pad.
+     * @param {number} [length=0] The padding length.
+     * @param {string} [chars=' '] The string used as padding.
+     * @returns {string} Returns the padded string.
+     * @example
+     *
+     * _.padRight('abc', 6);
+     * // => 'abc   '
+     *
+     * _.padRight('abc', 6, '_-');
+     * // => 'abc_-_'
+     *
+     * _.padRight('abc', 3);
+     * // => 'abc'
+     */
+    var padRight = createPadDir(true);
+
+    /**
+     * Converts `string` to an integer of the specified radix. If `radix` is
+     * `undefined` or `0`, a `radix` of `10` is used unless `value` is a hexadecimal,
+     * in which case a `radix` of `16` is used.
+     *
+     * **Note:** This method aligns with the [ES5 implementation](https://es5.github.io/#E)
+     * of `parseInt`.
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} string The string to convert.
+     * @param {number} [radix] The radix to interpret `value` by.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
+     * @returns {number} Returns the converted integer.
+     * @example
+     *
+     * _.parseInt('08');
+     * // => 8
+     *
+     * _.map(['6', '08', '10'], _.parseInt);
+     * // => [6, 8, 10]
+     */
+    function parseInt(string, radix, guard) {
+      // Firefox < 21 and Opera < 15 follow ES3 for `parseInt`.
+      // Chrome fails to trim leading <BOM> whitespace characters.
+      // See https://code.google.com/p/v8/issues/detail?id=3109 for more details.
+      if (guard ? isIterateeCall(string, radix, guard) : radix == null) {
+        radix = 0;
+      } else if (radix) {
+        radix = +radix;
+      }
+      string = trim(string);
+      return nativeParseInt(string, radix || (reHasHexPrefix.test(string) ? 16 : 10));
+    }
+
+    /**
+     * Repeats the given string `n` times.
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to repeat.
+     * @param {number} [n=0] The number of times to repeat the string.
+     * @returns {string} Returns the repeated string.
+     * @example
+     *
+     * _.repeat('*', 3);
+     * // => '***'
+     *
+     * _.repeat('abc', 2);
+     * // => 'abcabc'
+     *
+     * _.repeat('abc', 0);
+     * // => ''
+     */
+    function repeat(string, n) {
+      var result = '';
+      string = baseToString(string);
+      n = +n;
+      if (n < 1 || !string || !nativeIsFinite(n)) {
+        return result;
+      }
+      // Leverage the exponentiation by squaring algorithm for a faster repeat.
+      // See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more details.
+      do {
+        if (n % 2) {
+          result += string;
+        }
+        n = nativeFloor(n / 2);
+        string += string;
+      } while (n);
+
+      return result;
+    }
+
+    /**
+     * Converts `string` to [snake case](https://en.wikipedia.org/wiki/Snake_case).
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the snake cased string.
+     * @example
+     *
+     * _.snakeCase('Foo Bar');
+     * // => 'foo_bar'
+     *
+     * _.snakeCase('fooBar');
+     * // => 'foo_bar'
+     *
+     * _.snakeCase('--foo-bar');
+     * // => 'foo_bar'
+     */
+    var snakeCase = createCompounder(function(result, word, index) {
+      return result + (index ? '_' : '') + word.toLowerCase();
+    });
+
+    /**
+     * Converts `string` to [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage).
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the start cased string.
+     * @example
+     *
+     * _.startCase('--foo-bar');
+     * // => 'Foo Bar'
+     *
+     * _.startCase('fooBar');
+     * // => 'Foo Bar'
+     *
+     * _.startCase('__foo_bar__');
+     * // => 'Foo Bar'
+     */
+    var startCase = createCompounder(function(result, word, index) {
+      return result + (index ? ' ' : '') + (word.charAt(0).toUpperCase() + word.slice(1));
+    });
+
+    /**
+     * Checks if `string` starts with the given target string.
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to search.
+     * @param {string} [target] The string to search for.
+     * @param {number} [position=0] The position to search from.
+     * @returns {boolean} Returns `true` if `string` starts with `target`, else `false`.
+     * @example
+     *
+     * _.startsWith('abc', 'a');
+     * // => true
+     *
+     * _.startsWith('abc', 'b');
+     * // => false
+     *
+     * _.startsWith('abc', 'b', 1);
+     * // => true
+     */
+    function startsWith(string, target, position) {
+      string = baseToString(string);
+      position = position == null
+        ? 0
+        : nativeMin(position < 0 ? 0 : (+position || 0), string.length);
+
+      return string.lastIndexOf(target, position) == position;
+    }
+
+    /**
+     * Creates a compiled template function that can interpolate data properties
+     * in "interpolate" delimiters, HTML-escape interpolated data properties in
+     * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data
+     * properties may be accessed as free variables in the template. If a setting
+     * object is provided it takes precedence over `_.templateSettings` values.
+     *
+     * **Note:** In the development build `_.template` utilizes
+     * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl)
+     * for easier debugging.
+     *
+     * For more information on precompiling templates see
+     * [lodash's custom builds documentation](https://lodash.com/custom-builds).
+     *
+     * For more information on Chrome extension sandboxes see
+     * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval).
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The template string.
+     * @param {Object} [options] The options object.
+     * @param {RegExp} [options.escape] The HTML "escape" delimiter.
+     * @param {RegExp} [options.evaluate] The "evaluate" delimiter.
+     * @param {Object} [options.imports] An object to import into the template as free variables.
+     * @param {RegExp} [options.interpolate] The "interpolate" delimiter.
+     * @param {string} [options.sourceURL] The sourceURL of the template's compiled source.
+     * @param {string} [options.variable] The data object variable name.
+     * @param- {Object} [otherOptions] Enables the legacy `options` param signature.
+     * @returns {Function} Returns the compiled template function.
+     * @example
+     *
+     * // using the "interpolate" delimiter to create a compiled template
+     * var compiled = _.template('hello <%= user %>!');
+     * compiled({ 'user': 'fred' });
+     * // => 'hello fred!'
+     *
+     * // using the HTML "escape" delimiter to escape data property values
+     * var compiled = _.template('<b><%- value %></b>');
+     * compiled({ 'value': '<script>' });
+     * // => '<b>&lt;script&gt;</b>'
+     *
+     * // using the "evaluate" delimiter to execute JavaScript and generate HTML
+     * var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>');
+     * compiled({ 'users': ['fred', 'barney'] });
+     * // => '<li>fred</li><li>barney</li>'
+     *
+     * // using the internal `print` function in "evaluate" delimiters
+     * var compiled = _.template('<% print("hello " + user); %>!');
+     * compiled({ 'user': 'barney' });
+     * // => 'hello barney!'
+     *
+     * // using the ES delimiter as an alternative to the default "interpolate" delimiter
+     * var compiled = _.template('hello ${ user }!');
+     * compiled({ 'user': 'pebbles' });
+     * // => 'hello pebbles!'
+     *
+     * // using custom template delimiters
+     * _.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
+     * var compiled = _.template('hello {{ user }}!');
+     * compiled({ 'user': 'mustache' });
+     * // => 'hello mustache!'
+     *
+     * // using backslashes to treat delimiters as plain text
+     * var compiled = _.template('<%= "\\<%- value %\\>" %>');
+     * compiled({ 'value': 'ignored' });
+     * // => '<%- value %>'
+     *
+     * // using the `imports` option to import `jQuery` as `jq`
+     * var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>';
+     * var compiled = _.template(text, { 'imports': { 'jq': jQuery } });
+     * compiled({ 'users': ['fred', 'barney'] });
+     * // => '<li>fred</li><li>barney</li>'
+     *
+     * // using the `sourceURL` option to specify a custom sourceURL for the template
+     * var compiled = _.template('hello <%= user %>!', { 'sourceURL': '/basic/greeting.jst' });
+     * compiled(data);
+     * // => find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector
+     *
+     * // using the `variable` option to ensure a with-statement isn't used in the compiled template
+     * var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' });
+     * compiled.source;
+     * // => function(data) {
+     * //   var __t, __p = '';
+     * //   __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!';
+     * //   return __p;
+     * // }
+     *
+     * // using the `source` property to inline compiled templates for meaningful
+     * // line numbers in error messages and a stack trace
+     * fs.writeFileSync(path.join(cwd, 'jst.js'), '\
+     *   var JST = {\
+     *     "main": ' + _.template(mainText).source + '\
+     *   };\
+     * ');
+     */
+    function template(string, options, otherOptions) {
+      // Based on John Resig's `tmpl` implementation (http://ejohn.org/blog/javascript-micro-templating/)
+      // and Laura Doktorova's doT.js (https://github.com/olado/doT).
+      var settings = lodash.templateSettings;
+
+      if (otherOptions && isIterateeCall(string, options, otherOptions)) {
+        options = otherOptions = undefined;
+      }
+      string = baseToString(string);
+      options = assignWith(baseAssign({}, otherOptions || options), settings, assignOwnDefaults);
+
+      var imports = assignWith(baseAssign({}, options.imports), settings.imports, assignOwnDefaults),
+          importsKeys = keys(imports),
+          importsValues = baseValues(imports, importsKeys);
+
+      var isEscaping,
+          isEvaluating,
+          index = 0,
+          interpolate = options.interpolate || reNoMatch,
+          source = "__p += '";
+
+      // Compile the regexp to match each delimiter.
+      var reDelimiters = RegExp(
+        (options.escape || reNoMatch).source + '|' +
+        interpolate.source + '|' +
+        (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' +
+        (options.evaluate || reNoMatch).source + '|$'
+      , 'g');
+
+      // Use a sourceURL for easier debugging.
+      var sourceURL = '//# sourceURL=' +
+        ('sourceURL' in options
+          ? options.sourceURL
+          : ('lodash.templateSources[' + (++templateCounter) + ']')
+        ) + '\n';
+
+      string.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
+        interpolateValue || (interpolateValue = esTemplateValue);
+
+        // Escape characters that can't be included in string literals.
+        source += string.slice(index, offset).replace(reUnescapedString, escapeStringChar);
+
+        // Replace delimiters with snippets.
+        if (escapeValue) {
+          isEscaping = true;
+          source += "' +\n__e(" + escapeValue + ") +\n'";
+        }
+        if (evaluateValue) {
+          isEvaluating = true;
+          source += "';\n" + evaluateValue + ";\n__p += '";
+        }
+        if (interpolateValue) {
+          source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
+        }
+        index = offset + match.length;
+
+        // The JS engine embedded in Adobe products requires returning the `match`
+        // string in order to produce the correct `offset` value.
+        return match;
+      });
+
+      source += "';\n";
+
+      // If `variable` is not specified wrap a with-statement around the generated
+      // code to add the data object to the top of the scope chain.
+      var variable = options.variable;
+      if (!variable) {
+        source = 'with (obj) {\n' + source + '\n}\n';
+      }
+      // Cleanup code by stripping empty strings.
+      source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)
+        .replace(reEmptyStringMiddle, '$1')
+        .replace(reEmptyStringTrailing, '$1;');
+
+      // Frame code as the function body.
+      source = 'function(' + (variable || 'obj') + ') {\n' +
+        (variable
+          ? ''
+          : 'obj || (obj = {});\n'
+        ) +
+        "var __t, __p = ''" +
+        (isEscaping
+           ? ', __e = _.escape'
+           : ''
+        ) +
+        (isEvaluating
+          ? ', __j = Array.prototype.join;\n' +
+            "function print() { __p += __j.call(arguments, '') }\n"
+          : ';\n'
+        ) +
+        source +
+        'return __p\n}';
+
+      var result = attempt(function() {
+        return Function(importsKeys, sourceURL + 'return ' + source).apply(undefined, importsValues);
+      });
+
+      // Provide the compiled function's source by its `toString` method or
+      // the `source` property as a convenience for inlining compiled templates.
+      result.source = source;
+      if (isError(result)) {
+        throw result;
+      }
+      return result;
+    }
+
+    /**
+     * Removes leading and trailing whitespace or specified characters from `string`.
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to trim.
+     * @param {string} [chars=whitespace] The characters to trim.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
+     * @returns {string} Returns the trimmed string.
+     * @example
+     *
+     * _.trim('  abc  ');
+     * // => 'abc'
+     *
+     * _.trim('-_-abc-_-', '_-');
+     * // => 'abc'
+     *
+     * _.map(['  foo  ', '  bar  '], _.trim);
+     * // => ['foo', 'bar']
+     */
+    function trim(string, chars, guard) {
+      var value = string;
+      string = baseToString(string);
+      if (!string) {
+        return string;
+      }
+      if (guard ? isIterateeCall(value, chars, guard) : chars == null) {
+        return string.slice(trimmedLeftIndex(string), trimmedRightIndex(string) + 1);
+      }
+      chars = (chars + '');
+      return string.slice(charsLeftIndex(string, chars), charsRightIndex(string, chars) + 1);
+    }
+
+    /**
+     * Removes leading whitespace or specified characters from `string`.
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to trim.
+     * @param {string} [chars=whitespace] The characters to trim.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
+     * @returns {string} Returns the trimmed string.
+     * @example
+     *
+     * _.trimLeft('  abc  ');
+     * // => 'abc  '
+     *
+     * _.trimLeft('-_-abc-_-', '_-');
+     * // => 'abc-_-'
+     */
+    function trimLeft(string, chars, guard) {
+      var value = string;
+      string = baseToString(string);
+      if (!string) {
+        return string;
+      }
+      if (guard ? isIterateeCall(value, chars, guard) : chars == null) {
+        return string.slice(trimmedLeftIndex(string));
+      }
+      return string.slice(charsLeftIndex(string, (chars + '')));
+    }
+
+    /**
+     * Removes trailing whitespace or specified characters from `string`.
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to trim.
+     * @param {string} [chars=whitespace] The characters to trim.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
+     * @returns {string} Returns the trimmed string.
+     * @example
+     *
+     * _.trimRight('  abc  ');
+     * // => '  abc'
+     *
+     * _.trimRight('-_-abc-_-', '_-');
+     * // => '-_-abc'
+     */
+    function trimRight(string, chars, guard) {
+      var value = string;
+      string = baseToString(string);
+      if (!string) {
+        return string;
+      }
+      if (guard ? isIterateeCall(value, chars, guard) : chars == null) {
+        return string.slice(0, trimmedRightIndex(string) + 1);
+      }
+      return string.slice(0, charsRightIndex(string, (chars + '')) + 1);
+    }
+
+    /**
+     * Truncates `string` if it's longer than the given maximum string length.
+     * The last characters of the truncated string are replaced with the omission
+     * string which defaults to "...".
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to truncate.
+     * @param {Object|number} [options] The options object or maximum string length.
+     * @param {number} [options.length=30] The maximum string length.
+     * @param {string} [options.omission='...'] The string to indicate text is omitted.
+     * @param {RegExp|string} [options.separator] The separator pattern to truncate to.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
+     * @returns {string} Returns the truncated string.
+     * @example
+     *
+     * _.trunc('hi-diddly-ho there, neighborino');
+     * // => 'hi-diddly-ho there, neighbo...'
+     *
+     * _.trunc('hi-diddly-ho there, neighborino', 24);
+     * // => 'hi-diddly-ho there, n...'
+     *
+     * _.trunc('hi-diddly-ho there, neighborino', {
+     *   'length': 24,
+     *   'separator': ' '
+     * });
+     * // => 'hi-diddly-ho there,...'
+     *
+     * _.trunc('hi-diddly-ho there, neighborino', {
+     *   'length': 24,
+     *   'separator': /,? +/
+     * });
+     * // => 'hi-diddly-ho there...'
+     *
+     * _.trunc('hi-diddly-ho there, neighborino', {
+     *   'omission': ' [...]'
+     * });
+     * // => 'hi-diddly-ho there, neig [...]'
+     */
+    function trunc(string, options, guard) {
+      if (guard && isIterateeCall(string, options, guard)) {
+        options = undefined;
+      }
+      var length = DEFAULT_TRUNC_LENGTH,
+          omission = DEFAULT_TRUNC_OMISSION;
+
+      if (options != null) {
+        if (isObject(options)) {
+          var separator = 'separator' in options ? options.separator : separator;
+          length = 'length' in options ? (+options.length || 0) : length;
+          omission = 'omission' in options ? baseToString(options.omission) : omission;
+        } else {
+          length = +options || 0;
+        }
+      }
+      string = baseToString(string);
+      if (length >= string.length) {
+        return string;
+      }
+      var end = length - omission.length;
+      if (end < 1) {
+        return omission;
+      }
+      var result = string.slice(0, end);
+      if (separator == null) {
+        return result + omission;
+      }
+      if (isRegExp(separator)) {
+        if (string.slice(end).search(separator)) {
+          var match,
+              newEnd,
+              substring = string.slice(0, end);
+
+          if (!separator.global) {
+            separator = RegExp(separator.source, (reFlags.exec(separator) || '') + 'g');
+          }
+          separator.lastIndex = 0;
+          while ((match = separator.exec(substring))) {
+            newEnd = match.index;
+          }
+          result = result.slice(0, newEnd == null ? end : newEnd);
+        }
+      } else if (string.indexOf(separator, end) != end) {
+        var index = result.lastIndexOf(separator);
+        if (index > -1) {
+          result = result.slice(0, index);
+        }
+      }
+      return result + omission;
+    }
+
+    /**
+     * The inverse of `_.escape`; this method converts the HTML entities
+     * `&amp;`, `&lt;`, `&gt;`, `&quot;`, `&#39;`, and `&#96;` in `string` to their
+     * corresponding characters.
+     *
+     * **Note:** No other HTML entities are unescaped. To unescape additional HTML
+     * entities use a third-party library like [_he_](https://mths.be/he).
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to unescape.
+     * @returns {string} Returns the unescaped string.
+     * @example
+     *
+     * _.unescape('fred, barney, &amp; pebbles');
+     * // => 'fred, barney, & pebbles'
+     */
+    function unescape(string) {
+      string = baseToString(string);
+      return (string && reHasEscapedHtml.test(string))
+        ? string.replace(reEscapedHtml, unescapeHtmlChar)
+        : string;
+    }
+
+    /**
+     * Splits `string` into an array of its words.
+     *
+     * @static
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to inspect.
+     * @param {RegExp|string} [pattern] The pattern to match words.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
+     * @returns {Array} Returns the words of `string`.
+     * @example
+     *
+     * _.words('fred, barney, & pebbles');
+     * // => ['fred', 'barney', 'pebbles']
+     *
+     * _.words('fred, barney, & pebbles', /[^, ]+/g);
+     * // => ['fred', 'barney', '&', 'pebbles']
+     */
+    function words(string, pattern, guard) {
+      if (guard && isIterateeCall(string, pattern, guard)) {
+        pattern = undefined;
+      }
+      string = baseToString(string);
+      return string.match(pattern || reWords) || [];
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Attempts to invoke `func`, returning either the result or the caught error
+     * object. Any additional arguments are provided to `func` when it is invoked.
+     *
+     * @static
+     * @memberOf _
+     * @category Utility
+     * @param {Function} func The function to attempt.
+     * @returns {*} Returns the `func` result or error object.
+     * @example
+     *
+     * // avoid throwing errors for invalid selectors
+     * var elements = _.attempt(function(selector) {
+     *   return document.querySelectorAll(selector);
+     * }, '>_>');
+     *
+     * if (_.isError(elements)) {
+     *   elements = [];
+     * }
+     */
+    var attempt = restParam(function(func, args) {
+      try {
+        return func.apply(undefined, args);
+      } catch(e) {
+        return isError(e) ? e : new Error(e);
+      }
+    });
+
+    /**
+     * Creates a function that invokes `func` with the `this` binding of `thisArg`
+     * and arguments of the created function. If `func` is a property name the
+     * created callback returns the property value for a given element. If `func`
+     * is an object the created callback returns `true` for elements that contain
+     * the equivalent object properties, otherwise it returns `false`.
+     *
+     * @static
+     * @memberOf _
+     * @alias iteratee
+     * @category Utility
+     * @param {*} [func=_.identity] The value to convert to a callback.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param- {Object} [guard] Enables use as a callback for functions like `_.map`.
+     * @returns {Function} Returns the callback.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36 },
+     *   { 'user': 'fred',   'age': 40 }
+     * ];
+     *
+     * // wrap to create custom callback shorthands
+     * _.callback = _.wrap(_.callback, function(callback, func, thisArg) {
+     *   var match = /^(.+?)__([gl]t)(.+)$/.exec(func);
+     *   if (!match) {
+     *     return callback(func, thisArg);
+     *   }
+     *   return function(object) {
+     *     return match[2] == 'gt'
+     *       ? object[match[1]] > match[3]
+     *       : object[match[1]] < match[3];
+     *   };
+     * });
+     *
+     * _.filter(users, 'age__gt36');
+     * // => [{ 'user': 'fred', 'age': 40 }]
+     */
+    function callback(func, thisArg, guard) {
+      if (guard && isIterateeCall(func, thisArg, guard)) {
+        thisArg = undefined;
+      }
+      return isObjectLike(func)
+        ? matches(func)
+        : baseCallback(func, thisArg);
+    }
+
+    /**
+     * Creates a function that returns `value`.
+     *
+     * @static
+     * @memberOf _
+     * @category Utility
+     * @param {*} value The value to return from the new function.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var object = { 'user': 'fred' };
+     * var getter = _.constant(object);
+     *
+     * getter() === object;
+     * // => true
+     */
+    function constant(value) {
+      return function() {
+        return value;
+      };
+    }
+
+    /**
+     * This method returns the first argument provided to it.
+     *
+     * @static
+     * @memberOf _
+     * @category Utility
+     * @param {*} value Any value.
+     * @returns {*} Returns `value`.
+     * @example
+     *
+     * var object = { 'user': 'fred' };
+     *
+     * _.identity(object) === object;
+     * // => true
+     */
+    function identity(value) {
+      return value;
+    }
+
+    /**
+     * Creates a function that performs a deep comparison between a given object
+     * and `source`, returning `true` if the given object has equivalent property
+     * values, else `false`.
+     *
+     * **Note:** This method supports comparing arrays, booleans, `Date` objects,
+     * numbers, `Object` objects, regexes, and strings. Objects are compared by
+     * their own, not inherited, enumerable properties. For comparing a single
+     * own or inherited property value see `_.matchesProperty`.
+     *
+     * @static
+     * @memberOf _
+     * @category Utility
+     * @param {Object} source The object of property values to match.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': true },
+     *   { 'user': 'fred',   'age': 40, 'active': false }
+     * ];
+     *
+     * _.filter(users, _.matches({ 'age': 40, 'active': false }));
+     * // => [{ 'user': 'fred', 'age': 40, 'active': false }]
+     */
+    function matches(source) {
+      return baseMatches(baseClone(source, true));
+    }
+
+    /**
+     * Creates a function that compares the property value of `path` on a given
+     * object to `value`.
+     *
+     * **Note:** This method supports comparing arrays, booleans, `Date` objects,
+     * numbers, `Object` objects, regexes, and strings. Objects are compared by
+     * their own, not inherited, enumerable properties.
+     *
+     * @static
+     * @memberOf _
+     * @category Utility
+     * @param {Array|string} path The path of the property to get.
+     * @param {*} srcValue The value to match.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney' },
+     *   { 'user': 'fred' }
+     * ];
+     *
+     * _.find(users, _.matchesProperty('user', 'fred'));
+     * // => { 'user': 'fred' }
+     */
+    function matchesProperty(path, srcValue) {
+      return baseMatchesProperty(path, baseClone(srcValue, true));
+    }
+
+    /**
+     * Creates a function that invokes the method at `path` on a given object.
+     * Any additional arguments are provided to the invoked method.
+     *
+     * @static
+     * @memberOf _
+     * @category Utility
+     * @param {Array|string} path The path of the method to invoke.
+     * @param {...*} [args] The arguments to invoke the method with.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var objects = [
+     *   { 'a': { 'b': { 'c': _.constant(2) } } },
+     *   { 'a': { 'b': { 'c': _.constant(1) } } }
+     * ];
+     *
+     * _.map(objects, _.method('a.b.c'));
+     * // => [2, 1]
+     *
+     * _.invoke(_.sortBy(objects, _.method(['a', 'b', 'c'])), 'a.b.c');
+     * // => [1, 2]
+     */
+    var method = restParam(function(path, args) {
+      return function(object) {
+        return invokePath(object, path, args);
+      };
+    });
+
+    /**
+     * The opposite of `_.method`; this method creates a function that invokes
+     * the method at a given path on `object`. Any additional arguments are
+     * provided to the invoked method.
+     *
+     * @static
+     * @memberOf _
+     * @category Utility
+     * @param {Object} object The object to query.
+     * @param {...*} [args] The arguments to invoke the method with.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var array = _.times(3, _.constant),
+     *     object = { 'a': array, 'b': array, 'c': array };
+     *
+     * _.map(['a[2]', 'c[0]'], _.methodOf(object));
+     * // => [2, 0]
+     *
+     * _.map([['a', '2'], ['c', '0']], _.methodOf(object));
+     * // => [2, 0]
+     */
+    var methodOf = restParam(function(object, args) {
+      return function(path) {
+        return invokePath(object, path, args);
+      };
+    });
+
+    /**
+     * Adds all own enumerable function properties of a source object to the
+     * destination object. If `object` is a function then methods are added to
+     * its prototype as well.
+     *
+     * **Note:** Use `_.runInContext` to create a pristine `lodash` function to
+     * avoid conflicts caused by modifying the original.
+     *
+     * @static
+     * @memberOf _
+     * @category Utility
+     * @param {Function|Object} [object=lodash] The destination object.
+     * @param {Object} source The object of functions to add.
+     * @param {Object} [options] The options object.
+     * @param {boolean} [options.chain=true] Specify whether the functions added
+     *  are chainable.
+     * @returns {Function|Object} Returns `object`.
+     * @example
+     *
+     * function vowels(string) {
+     *   return _.filter(string, function(v) {
+     *     return /[aeiou]/i.test(v);
+     *   });
+     * }
+     *
+     * _.mixin({ 'vowels': vowels });
+     * _.vowels('fred');
+     * // => ['e']
+     *
+     * _('fred').vowels().value();
+     * // => ['e']
+     *
+     * _.mixin({ 'vowels': vowels }, { 'chain': false });
+     * _('fred').vowels();
+     * // => ['e']
+     */
+    function mixin(object, source, options) {
+      if (options == null) {
+        var isObj = isObject(source),
+            props = isObj ? keys(source) : undefined,
+            methodNames = (props && props.length) ? baseFunctions(source, props) : undefined;
+
+        if (!(methodNames ? methodNames.length : isObj)) {
+          methodNames = false;
+          options = source;
+          source = object;
+          object = this;
+        }
+      }
+      if (!methodNames) {
+        methodNames = baseFunctions(source, keys(source));
+      }
+      var chain = true,
+          index = -1,
+          isFunc = isFunction(object),
+          length = methodNames.length;
+
+      if (options === false) {
+        chain = false;
+      } else if (isObject(options) && 'chain' in options) {
+        chain = options.chain;
+      }
+      while (++index < length) {
+        var methodName = methodNames[index],
+            func = source[methodName];
+
+        object[methodName] = func;
+        if (isFunc) {
+          object.prototype[methodName] = (function(func) {
+            return function() {
+              var chainAll = this.__chain__;
+              if (chain || chainAll) {
+                var result = object(this.__wrapped__),
+                    actions = result.__actions__ = arrayCopy(this.__actions__);
+
+                actions.push({ 'func': func, 'args': arguments, 'thisArg': object });
+                result.__chain__ = chainAll;
+                return result;
+              }
+              return func.apply(object, arrayPush([this.value()], arguments));
+            };
+          }(func));
+        }
+      }
+      return object;
+    }
+
+    /**
+     * Reverts the `_` variable to its previous value and returns a reference to
+     * the `lodash` function.
+     *
+     * @static
+     * @memberOf _
+     * @category Utility
+     * @returns {Function} Returns the `lodash` function.
+     * @example
+     *
+     * var lodash = _.noConflict();
+     */
+    function noConflict() {
+      root._ = oldDash;
+      return this;
+    }
+
+    /**
+     * A no-operation function that returns `undefined` regardless of the
+     * arguments it receives.
+     *
+     * @static
+     * @memberOf _
+     * @category Utility
+     * @example
+     *
+     * var object = { 'user': 'fred' };
+     *
+     * _.noop(object) === undefined;
+     * // => true
+     */
+    function noop() {
+      // No operation performed.
+    }
+
+    /**
+     * Creates a function that returns the property value at `path` on a
+     * given object.
+     *
+     * @static
+     * @memberOf _
+     * @category Utility
+     * @param {Array|string} path The path of the property to get.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var objects = [
+     *   { 'a': { 'b': { 'c': 2 } } },
+     *   { 'a': { 'b': { 'c': 1 } } }
+     * ];
+     *
+     * _.map(objects, _.property('a.b.c'));
+     * // => [2, 1]
+     *
+     * _.pluck(_.sortBy(objects, _.property(['a', 'b', 'c'])), 'a.b.c');
+     * // => [1, 2]
+     */
+    function property(path) {
+      return isKey(path) ? baseProperty(path) : basePropertyDeep(path);
+    }
+
+    /**
+     * The opposite of `_.property`; this method creates a function that returns
+     * the property value at a given path on `object`.
+     *
+     * @static
+     * @memberOf _
+     * @category Utility
+     * @param {Object} object The object to query.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var array = [0, 1, 2],
+     *     object = { 'a': array, 'b': array, 'c': array };
+     *
+     * _.map(['a[2]', 'c[0]'], _.propertyOf(object));
+     * // => [2, 0]
+     *
+     * _.map([['a', '2'], ['c', '0']], _.propertyOf(object));
+     * // => [2, 0]
+     */
+    function propertyOf(object) {
+      return function(path) {
+        return baseGet(object, toPath(path), path + '');
+      };
+    }
+
+    /**
+     * Creates an array of numbers (positive and/or negative) progressing from
+     * `start` up to, but not including, `end`. If `end` is not specified it is
+     * set to `start` with `start` then set to `0`. If `end` is less than `start`
+     * a zero-length range is created unless a negative `step` is specified.
+     *
+     * @static
+     * @memberOf _
+     * @category Utility
+     * @param {number} [start=0] The start of the range.
+     * @param {number} end The end of the range.
+     * @param {number} [step=1] The value to increment or decrement by.
+     * @returns {Array} Returns the new array of numbers.
+     * @example
+     *
+     * _.range(4);
+     * // => [0, 1, 2, 3]
+     *
+     * _.range(1, 5);
+     * // => [1, 2, 3, 4]
+     *
+     * _.range(0, 20, 5);
+     * // => [0, 5, 10, 15]
+     *
+     * _.range(0, -4, -1);
+     * // => [0, -1, -2, -3]
+     *
+     * _.range(1, 4, 0);
+     * // => [1, 1, 1]
+     *
+     * _.range(0);
+     * // => []
+     */
+    function range(start, end, step) {
+      if (step && isIterateeCall(start, end, step)) {
+        end = step = undefined;
+      }
+      start = +start || 0;
+      step = step == null ? 1 : (+step || 0);
+
+      if (end == null) {
+        end = start;
+        start = 0;
+      } else {
+        end = +end || 0;
+      }
+      // Use `Array(length)` so engines like Chakra and V8 avoid slower modes.
+      // See https://youtu.be/XAqIpGU8ZZk#t=17m25s for more details.
+      var index = -1,
+          length = nativeMax(nativeCeil((end - start) / (step || 1)), 0),
+          result = Array(length);
+
+      while (++index < length) {
+        result[index] = start;
+        start += step;
+      }
+      return result;
+    }
+
+    /**
+     * Invokes the iteratee function `n` times, returning an array of the results
+     * of each invocation. The `iteratee` is bound to `thisArg` and invoked with
+     * one argument; (index).
+     *
+     * @static
+     * @memberOf _
+     * @category Utility
+     * @param {number} n The number of times to invoke `iteratee`.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {Array} Returns the array of results.
+     * @example
+     *
+     * var diceRolls = _.times(3, _.partial(_.random, 1, 6, false));
+     * // => [3, 6, 4]
+     *
+     * _.times(3, function(n) {
+     *   mage.castSpell(n);
+     * });
+     * // => invokes `mage.castSpell(n)` three times with `n` of `0`, `1`, and `2`
+     *
+     * _.times(3, function(n) {
+     *   this.cast(n);
+     * }, mage);
+     * // => also invokes `mage.castSpell(n)` three times
+     */
+    function times(n, iteratee, thisArg) {
+      n = nativeFloor(n);
+
+      // Exit early to avoid a JSC JIT bug in Safari 8
+      // where `Array(0)` is treated as `Array(1)`.
+      if (n < 1 || !nativeIsFinite(n)) {
+        return [];
+      }
+      var index = -1,
+          result = Array(nativeMin(n, MAX_ARRAY_LENGTH));
+
+      iteratee = bindCallback(iteratee, thisArg, 1);
+      while (++index < n) {
+        if (index < MAX_ARRAY_LENGTH) {
+          result[index] = iteratee(index);
+        } else {
+          iteratee(index);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Generates a unique ID. If `prefix` is provided the ID is appended to it.
+     *
+     * @static
+     * @memberOf _
+     * @category Utility
+     * @param {string} [prefix] The value to prefix the ID with.
+     * @returns {string} Returns the unique ID.
+     * @example
+     *
+     * _.uniqueId('contact_');
+     * // => 'contact_104'
+     *
+     * _.uniqueId();
+     * // => '105'
+     */
+    function uniqueId(prefix) {
+      var id = ++idCounter;
+      return baseToString(prefix) + id;
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Adds two numbers.
+     *
+     * @static
+     * @memberOf _
+     * @category Math
+     * @param {number} augend The first number to add.
+     * @param {number} addend The second number to add.
+     * @returns {number} Returns the sum.
+     * @example
+     *
+     * _.add(6, 4);
+     * // => 10
+     */
+    function add(augend, addend) {
+      return (+augend || 0) + (+addend || 0);
+    }
+
+    /**
+     * Calculates `n` rounded up to `precision`.
+     *
+     * @static
+     * @memberOf _
+     * @category Math
+     * @param {number} n The number to round up.
+     * @param {number} [precision=0] The precision to round up to.
+     * @returns {number} Returns the rounded up number.
+     * @example
+     *
+     * _.ceil(4.006);
+     * // => 5
+     *
+     * _.ceil(6.004, 2);
+     * // => 6.01
+     *
+     * _.ceil(6040, -2);
+     * // => 6100
+     */
+    var ceil = createRound('ceil');
+
+    /**
+     * Calculates `n` rounded down to `precision`.
+     *
+     * @static
+     * @memberOf _
+     * @category Math
+     * @param {number} n The number to round down.
+     * @param {number} [precision=0] The precision to round down to.
+     * @returns {number} Returns the rounded down number.
+     * @example
+     *
+     * _.floor(4.006);
+     * // => 4
+     *
+     * _.floor(0.046, 2);
+     * // => 0.04
+     *
+     * _.floor(4060, -2);
+     * // => 4000
+     */
+    var floor = createRound('floor');
+
+    /**
+     * Gets the maximum value of `collection`. If `collection` is empty or falsey
+     * `-Infinity` is returned. If an iteratee function is provided it is invoked
+     * for each value in `collection` to generate the criterion by which the value
+     * is ranked. The `iteratee` is bound to `thisArg` and invoked with three
+     * arguments: (value, index, collection).
+     *
+     * If a property name is provided for `iteratee` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `iteratee` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Math
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [iteratee] The function invoked per iteration.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {*} Returns the maximum value.
+     * @example
+     *
+     * _.max([4, 2, 8, 6]);
+     * // => 8
+     *
+     * _.max([]);
+     * // => -Infinity
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36 },
+     *   { 'user': 'fred',   'age': 40 }
+     * ];
+     *
+     * _.max(users, function(chr) {
+     *   return chr.age;
+     * });
+     * // => { 'user': 'fred', 'age': 40 }
+     *
+     * // using the `_.property` callback shorthand
+     * _.max(users, 'age');
+     * // => { 'user': 'fred', 'age': 40 }
+     */
+    var max = createExtremum(gt, NEGATIVE_INFINITY);
+
+    /**
+     * Gets the minimum value of `collection`. If `collection` is empty or falsey
+     * `Infinity` is returned. If an iteratee function is provided it is invoked
+     * for each value in `collection` to generate the criterion by which the value
+     * is ranked. The `iteratee` is bound to `thisArg` and invoked with three
+     * arguments: (value, index, collection).
+     *
+     * If a property name is provided for `iteratee` the created `_.property`
+     * style callback returns the property value of the given element.
+     *
+     * If a value is also provided for `thisArg` the created `_.matchesProperty`
+     * style callback returns `true` for elements that have a matching property
+     * value, else `false`.
+     *
+     * If an object is provided for `iteratee` the created `_.matches` style
+     * callback returns `true` for elements that have the properties of the given
+     * object, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @category Math
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [iteratee] The function invoked per iteration.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {*} Returns the minimum value.
+     * @example
+     *
+     * _.min([4, 2, 8, 6]);
+     * // => 2
+     *
+     * _.min([]);
+     * // => Infinity
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36 },
+     *   { 'user': 'fred',   'age': 40 }
+     * ];
+     *
+     * _.min(users, function(chr) {
+     *   return chr.age;
+     * });
+     * // => { 'user': 'barney', 'age': 36 }
+     *
+     * // using the `_.property` callback shorthand
+     * _.min(users, 'age');
+     * // => { 'user': 'barney', 'age': 36 }
+     */
+    var min = createExtremum(lt, POSITIVE_INFINITY);
+
+    /**
+     * Calculates `n` rounded to `precision`.
+     *
+     * @static
+     * @memberOf _
+     * @category Math
+     * @param {number} n The number to round.
+     * @param {number} [precision=0] The precision to round to.
+     * @returns {number} Returns the rounded number.
+     * @example
+     *
+     * _.round(4.006);
+     * // => 4
+     *
+     * _.round(4.006, 2);
+     * // => 4.01
+     *
+     * _.round(4060, -2);
+     * // => 4100
+     */
+    var round = createRound('round');
+
+    /**
+     * Gets the sum of the values in `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @category Math
+     * @param {Array|Object|string} collection The collection to iterate over.
+     * @param {Function|Object|string} [iteratee] The function invoked per iteration.
+     * @param {*} [thisArg] The `this` binding of `iteratee`.
+     * @returns {number} Returns the sum.
+     * @example
+     *
+     * _.sum([4, 6]);
+     * // => 10
+     *
+     * _.sum({ 'a': 4, 'b': 6 });
+     * // => 10
+     *
+     * var objects = [
+     *   { 'n': 4 },
+     *   { 'n': 6 }
+     * ];
+     *
+     * _.sum(objects, function(object) {
+     *   return object.n;
+     * });
+     * // => 10
+     *
+     * // using the `_.property` callback shorthand
+     * _.sum(objects, 'n');
+     * // => 10
+     */
+    function sum(collection, iteratee, thisArg) {
+      if (thisArg && isIterateeCall(collection, iteratee, thisArg)) {
+        iteratee = undefined;
+      }
+      iteratee = getCallback(iteratee, thisArg, 3);
+      return iteratee.length == 1
+        ? arraySum(isArray(collection) ? collection : toIterable(collection), iteratee)
+        : baseSum(collection, iteratee);
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    // Ensure wrappers are instances of `baseLodash`.
+    lodash.prototype = baseLodash.prototype;
+
+    LodashWrapper.prototype = baseCreate(baseLodash.prototype);
+    LodashWrapper.prototype.constructor = LodashWrapper;
+
+    LazyWrapper.prototype = baseCreate(baseLodash.prototype);
+    LazyWrapper.prototype.constructor = LazyWrapper;
+
+    // Add functions to the `Map` cache.
+    MapCache.prototype['delete'] = mapDelete;
+    MapCache.prototype.get = mapGet;
+    MapCache.prototype.has = mapHas;
+    MapCache.prototype.set = mapSet;
+
+    // Add functions to the `Set` cache.
+    SetCache.prototype.push = cachePush;
+
+    // Assign cache to `_.memoize`.
+    memoize.Cache = MapCache;
+
+    // Add functions that return wrapped values when chaining.
+    lodash.after = after;
+    lodash.ary = ary;
+    lodash.assign = assign;
+    lodash.at = at;
+    lodash.before = before;
+    lodash.bind = bind;
+    lodash.bindAll = bindAll;
+    lodash.bindKey = bindKey;
+    lodash.callback = callback;
+    lodash.chain = chain;
+    lodash.chunk = chunk;
+    lodash.compact = compact;
+    lodash.constant = constant;
+    lodash.countBy = countBy;
+    lodash.create = create;
+    lodash.curry = curry;
+    lodash.curryRight = curryRight;
+    lodash.debounce = debounce;
+    lodash.defaults = defaults;
+    lodash.defaultsDeep = defaultsDeep;
+    lodash.defer = defer;
+    lodash.delay = delay;
+    lodash.difference = difference;
+    lodash.drop = drop;
+    lodash.dropRight = dropRight;
+    lodash.dropRightWhile = dropRightWhile;
+    lodash.dropWhile = dropWhile;
+    lodash.fill = fill;
+    lodash.filter = filter;
+    lodash.flatten = flatten;
+    lodash.flattenDeep = flattenDeep;
+    lodash.flow = flow;
+    lodash.flowRight = flowRight;
+    lodash.forEach = forEach;
+    lodash.forEachRight = forEachRight;
+    lodash.forIn = forIn;
+    lodash.forInRight = forInRight;
+    lodash.forOwn = forOwn;
+    lodash.forOwnRight = forOwnRight;
+    lodash.functions = functions;
+    lodash.groupBy = groupBy;
+    lodash.indexBy = indexBy;
+    lodash.initial = initial;
+    lodash.intersection = intersection;
+    lodash.invert = invert;
+    lodash.invoke = invoke;
+    lodash.keys = keys;
+    lodash.keysIn = keysIn;
+    lodash.map = map;
+    lodash.mapKeys = mapKeys;
+    lodash.mapValues = mapValues;
+    lodash.matches = matches;
+    lodash.matchesProperty = matchesProperty;
+    lodash.memoize = memoize;
+    lodash.merge = merge;
+    lodash.method = method;
+    lodash.methodOf = methodOf;
+    lodash.mixin = mixin;
+    lodash.modArgs = modArgs;
+    lodash.negate = negate;
+    lodash.omit = omit;
+    lodash.once = once;
+    lodash.pairs = pairs;
+    lodash.partial = partial;
+    lodash.partialRight = partialRight;
+    lodash.partition = partition;
+    lodash.pick = pick;
+    lodash.pluck = pluck;
+    lodash.property = property;
+    lodash.propertyOf = propertyOf;
+    lodash.pull = pull;
+    lodash.pullAt = pullAt;
+    lodash.range = range;
+    lodash.rearg = rearg;
+    lodash.reject = reject;
+    lodash.remove = remove;
+    lodash.rest = rest;
+    lodash.restParam = restParam;
+    lodash.set = set;
+    lodash.shuffle = shuffle;
+    lodash.slice = slice;
+    lodash.sortBy = sortBy;
+    lodash.sortByAll = sortByAll;
+    lodash.sortByOrder = sortByOrder;
+    lodash.spread = spread;
+    lodash.take = take;
+    lodash.takeRight = takeRight;
+    lodash.takeRightWhile = takeRightWhile;
+    lodash.takeWhile = takeWhile;
+    lodash.tap = tap;
+    lodash.throttle = throttle;
+    lodash.thru = thru;
+    lodash.times = times;
+    lodash.toArray = toArray;
+    lodash.toPlainObject = toPlainObject;
+    lodash.transform = transform;
+    lodash.union = union;
+    lodash.uniq = uniq;
+    lodash.unzip = unzip;
+    lodash.unzipWith = unzipWith;
+    lodash.values = values;
+    lodash.valuesIn = valuesIn;
+    lodash.where = where;
+    lodash.without = without;
+    lodash.wrap = wrap;
+    lodash.xor = xor;
+    lodash.zip = zip;
+    lodash.zipObject = zipObject;
+    lodash.zipWith = zipWith;
+
+    // Add aliases.
+    lodash.backflow = flowRight;
+    lodash.collect = map;
+    lodash.compose = flowRight;
+    lodash.each = forEach;
+    lodash.eachRight = forEachRight;
+    lodash.extend = assign;
+    lodash.iteratee = callback;
+    lodash.methods = functions;
+    lodash.object = zipObject;
+    lodash.select = filter;
+    lodash.tail = rest;
+    lodash.unique = uniq;
+
+    // Add functions to `lodash.prototype`.
+    mixin(lodash, lodash);
+
+    /*------------------------------------------------------------------------*/
+
+    // Add functions that return unwrapped values when chaining.
+    lodash.add = add;
+    lodash.attempt = attempt;
+    lodash.camelCase = camelCase;
+    lodash.capitalize = capitalize;
+    lodash.ceil = ceil;
+    lodash.clone = clone;
+    lodash.cloneDeep = cloneDeep;
+    lodash.deburr = deburr;
+    lodash.endsWith = endsWith;
+    lodash.escape = escape;
+    lodash.escapeRegExp = escapeRegExp;
+    lodash.every = every;
+    lodash.find = find;
+    lodash.findIndex = findIndex;
+    lodash.findKey = findKey;
+    lodash.findLast = findLast;
+    lodash.findLastIndex = findLastIndex;
+    lodash.findLastKey = findLastKey;
+    lodash.findWhere = findWhere;
+    lodash.first = first;
+    lodash.floor = floor;
+    lodash.get = get;
+    lodash.gt = gt;
+    lodash.gte = gte;
+    lodash.has = has;
+    lodash.identity = identity;
+    lodash.includes = includes;
+    lodash.indexOf = indexOf;
+    lodash.inRange = inRange;
+    lodash.isArguments = isArguments;
+    lodash.isArray = isArray;
+    lodash.isBoolean = isBoolean;
+    lodash.isDate = isDate;
+    lodash.isElement = isElement;
+    lodash.isEmpty = isEmpty;
+    lodash.isEqual = isEqual;
+    lodash.isError = isError;
+    lodash.isFinite = isFinite;
+    lodash.isFunction = isFunction;
+    lodash.isMatch = isMatch;
+    lodash.isNaN = isNaN;
+    lodash.isNative = isNative;
+    lodash.isNull = isNull;
+    lodash.isNumber = isNumber;
+    lodash.isObject = isObject;
+    lodash.isPlainObject = isPlainObject;
+    lodash.isRegExp = isRegExp;
+    lodash.isString = isString;
+    lodash.isTypedArray = isTypedArray;
+    lodash.isUndefined = isUndefined;
+    lodash.kebabCase = kebabCase;
+    lodash.last = last;
+    lodash.lastIndexOf = lastIndexOf;
+    lodash.lt = lt;
+    lodash.lte = lte;
+    lodash.max = max;
+    lodash.min = min;
+    lodash.noConflict = noConflict;
+    lodash.noop = noop;
+    lodash.now = now;
+    lodash.pad = pad;
+    lodash.padLeft = padLeft;
+    lodash.padRight = padRight;
+    lodash.parseInt = parseInt;
+    lodash.random = random;
+    lodash.reduce = reduce;
+    lodash.reduceRight = reduceRight;
+    lodash.repeat = repeat;
+    lodash.result = result;
+    lodash.round = round;
+    lodash.runInContext = runInContext;
+    lodash.size = size;
+    lodash.snakeCase = snakeCase;
+    lodash.some = some;
+    lodash.sortedIndex = sortedIndex;
+    lodash.sortedLastIndex = sortedLastIndex;
+    lodash.startCase = startCase;
+    lodash.startsWith = startsWith;
+    lodash.sum = sum;
+    lodash.template = template;
+    lodash.trim = trim;
+    lodash.trimLeft = trimLeft;
+    lodash.trimRight = trimRight;
+    lodash.trunc = trunc;
+    lodash.unescape = unescape;
+    lodash.uniqueId = uniqueId;
+    lodash.words = words;
+
+    // Add aliases.
+    lodash.all = every;
+    lodash.any = some;
+    lodash.contains = includes;
+    lodash.eq = isEqual;
+    lodash.detect = find;
+    lodash.foldl = reduce;
+    lodash.foldr = reduceRight;
+    lodash.head = first;
+    lodash.include = includes;
+    lodash.inject = reduce;
+
+    mixin(lodash, (function() {
+      var source = {};
+      baseForOwn(lodash, function(func, methodName) {
+        if (!lodash.prototype[methodName]) {
+          source[methodName] = func;
+        }
+      });
+      return source;
+    }()), false);
+
+    /*------------------------------------------------------------------------*/
+
+    // Add functions capable of returning wrapped and unwrapped values when chaining.
+    lodash.sample = sample;
+
+    lodash.prototype.sample = function(n) {
+      if (!this.__chain__ && n == null) {
+        return sample(this.value());
+      }
+      return this.thru(function(value) {
+        return sample(value, n);
+      });
+    };
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * The semantic version number.
+     *
+     * @static
+     * @memberOf _
+     * @type string
+     */
+    lodash.VERSION = VERSION;
+
+    // Assign default placeholders.
+    arrayEach(['bind', 'bindKey', 'curry', 'curryRight', 'partial', 'partialRight'], function(methodName) {
+      lodash[methodName].placeholder = lodash;
+    });
+
+    // Add `LazyWrapper` methods for `_.drop` and `_.take` variants.
+    arrayEach(['drop', 'take'], function(methodName, index) {
+      LazyWrapper.prototype[methodName] = function(n) {
+        var filtered = this.__filtered__;
+        if (filtered && !index) {
+          return new LazyWrapper(this);
+        }
+        n = n == null ? 1 : nativeMax(nativeFloor(n) || 0, 0);
+
+        var result = this.clone();
+        if (filtered) {
+          result.__takeCount__ = nativeMin(result.__takeCount__, n);
+        } else {
+          result.__views__.push({ 'size': n, 'type': methodName + (result.__dir__ < 0 ? 'Right' : '') });
+        }
+        return result;
+      };
+
+      LazyWrapper.prototype[methodName + 'Right'] = function(n) {
+        return this.reverse()[methodName](n).reverse();
+      };
+    });
+
+    // Add `LazyWrapper` methods that accept an `iteratee` value.
+    arrayEach(['filter', 'map', 'takeWhile'], function(methodName, index) {
+      var type = index + 1,
+          isFilter = type != LAZY_MAP_FLAG;
+
+      LazyWrapper.prototype[methodName] = function(iteratee, thisArg) {
+        var result = this.clone();
+        result.__iteratees__.push({ 'iteratee': getCallback(iteratee, thisArg, 1), 'type': type });
+        result.__filtered__ = result.__filtered__ || isFilter;
+        return result;
+      };
+    });
+
+    // Add `LazyWrapper` methods for `_.first` and `_.last`.
+    arrayEach(['first', 'last'], function(methodName, index) {
+      var takeName = 'take' + (index ? 'Right' : '');
+
+      LazyWrapper.prototype[methodName] = function() {
+        return this[takeName](1).value()[0];
+      };
+    });
+
+    // Add `LazyWrapper` methods for `_.initial` and `_.rest`.
+    arrayEach(['initial', 'rest'], function(methodName, index) {
+      var dropName = 'drop' + (index ? '' : 'Right');
+
+      LazyWrapper.prototype[methodName] = function() {
+        return this.__filtered__ ? new LazyWrapper(this) : this[dropName](1);
+      };
+    });
+
+    // Add `LazyWrapper` methods for `_.pluck` and `_.where`.
+    arrayEach(['pluck', 'where'], function(methodName, index) {
+      var operationName = index ? 'filter' : 'map',
+          createCallback = index ? baseMatches : property;
+
+      LazyWrapper.prototype[methodName] = function(value) {
+        return this[operationName](createCallback(value));
+      };
+    });
+
+    LazyWrapper.prototype.compact = function() {
+      return this.filter(identity);
+    };
+
+    LazyWrapper.prototype.reject = function(predicate, thisArg) {
+      predicate = getCallback(predicate, thisArg, 1);
+      return this.filter(function(value) {
+        return !predicate(value);
+      });
+    };
+
+    LazyWrapper.prototype.slice = function(start, end) {
+      start = start == null ? 0 : (+start || 0);
+
+      var result = this;
+      if (result.__filtered__ && (start > 0 || end < 0)) {
+        return new LazyWrapper(result);
+      }
+      if (start < 0) {
+        result = result.takeRight(-start);
+      } else if (start) {
+        result = result.drop(start);
+      }
+      if (end !== undefined) {
+        end = (+end || 0);
+        result = end < 0 ? result.dropRight(-end) : result.take(end - start);
+      }
+      return result;
+    };
+
+    LazyWrapper.prototype.takeRightWhile = function(predicate, thisArg) {
+      return this.reverse().takeWhile(predicate, thisArg).reverse();
+    };
+
+    LazyWrapper.prototype.toArray = function() {
+      return this.take(POSITIVE_INFINITY);
+    };
+
+    // Add `LazyWrapper` methods to `lodash.prototype`.
+    baseForOwn(LazyWrapper.prototype, function(func, methodName) {
+      var checkIteratee = /^(?:filter|map|reject)|While$/.test(methodName),
+          retUnwrapped = /^(?:first|last)$/.test(methodName),
+          lodashFunc = lodash[retUnwrapped ? ('take' + (methodName == 'last' ? 'Right' : '')) : methodName];
+
+      if (!lodashFunc) {
+        return;
+      }
+      lodash.prototype[methodName] = function() {
+        var args = retUnwrapped ? [1] : arguments,
+            chainAll = this.__chain__,
+            value = this.__wrapped__,
+            isHybrid = !!this.__actions__.length,
+            isLazy = value instanceof LazyWrapper,
+            iteratee = args[0],
+            useLazy = isLazy || isArray(value);
+
+        if (useLazy && checkIteratee && typeof iteratee == 'function' && iteratee.length != 1) {
+          // Avoid lazy use if the iteratee has a "length" value other than `1`.
+          isLazy = useLazy = false;
+        }
+        var interceptor = function(value) {
+          return (retUnwrapped && chainAll)
+            ? lodashFunc(value, 1)[0]
+            : lodashFunc.apply(undefined, arrayPush([value], args));
+        };
+
+        var action = { 'func': thru, 'args': [interceptor], 'thisArg': undefined },
+            onlyLazy = isLazy && !isHybrid;
+
+        if (retUnwrapped && !chainAll) {
+          if (onlyLazy) {
+            value = value.clone();
+            value.__actions__.push(action);
+            return func.call(value);
+          }
+          return lodashFunc.call(undefined, this.value())[0];
+        }
+        if (!retUnwrapped && useLazy) {
+          value = onlyLazy ? value : new LazyWrapper(this);
+          var result = func.apply(value, args);
+          result.__actions__.push(action);
+          return new LodashWrapper(result, chainAll);
+        }
+        return this.thru(interceptor);
+      };
+    });
+
+    // Add `Array` and `String` methods to `lodash.prototype`.
+    arrayEach(['join', 'pop', 'push', 'replace', 'shift', 'sort', 'splice', 'split', 'unshift'], function(methodName) {
+      var func = (/^(?:replace|split)$/.test(methodName) ? stringProto : arrayProto)[methodName],
+          chainName = /^(?:push|sort|unshift)$/.test(methodName) ? 'tap' : 'thru',
+          retUnwrapped = /^(?:join|pop|replace|shift)$/.test(methodName);
+
+      lodash.prototype[methodName] = function() {
+        var args = arguments;
+        if (retUnwrapped && !this.__chain__) {
+          return func.apply(this.value(), args);
+        }
+        return this[chainName](function(value) {
+          return func.apply(value, args);
+        });
+      };
+    });
+
+    // Map minified function names to their real names.
+    baseForOwn(LazyWrapper.prototype, function(func, methodName) {
+      var lodashFunc = lodash[methodName];
+      if (lodashFunc) {
+        var key = lodashFunc.name,
+            names = realNames[key] || (realNames[key] = []);
+
+        names.push({ 'name': methodName, 'func': lodashFunc });
+      }
+    });
+
+    realNames[createHybridWrapper(undefined, BIND_KEY_FLAG).name] = [{ 'name': 'wrapper', 'func': undefined }];
+
+    // Add functions to the lazy wrapper.
+    LazyWrapper.prototype.clone = lazyClone;
+    LazyWrapper.prototype.reverse = lazyReverse;
+    LazyWrapper.prototype.value = lazyValue;
+
+    // Add chaining functions to the `lodash` wrapper.
+    lodash.prototype.chain = wrapperChain;
+    lodash.prototype.commit = wrapperCommit;
+    lodash.prototype.concat = wrapperConcat;
+    lodash.prototype.plant = wrapperPlant;
+    lodash.prototype.reverse = wrapperReverse;
+    lodash.prototype.toString = wrapperToString;
+    lodash.prototype.run = lodash.prototype.toJSON = lodash.prototype.valueOf = lodash.prototype.value = wrapperValue;
+
+    // Add function aliases to the `lodash` wrapper.
+    lodash.prototype.collect = lodash.prototype.map;
+    lodash.prototype.head = lodash.prototype.first;
+    lodash.prototype.select = lodash.prototype.filter;
+    lodash.prototype.tail = lodash.prototype.rest;
+
+    return lodash;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  // Export lodash.
+  var _ = runInContext();
+
+  // Some AMD build optimizers like r.js check for condition patterns like the following:
+  if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
+    // Expose lodash to the global object when an AMD loader is present to avoid
+    // errors in cases where lodash is loaded by a script tag and not intended
+    // as an AMD module. See http://requirejs.org/docs/errors.html#mismatch for
+    // more details.
+    root._ = _;
+
+    // Define as an anonymous module so, through path mapping, it can be
+    // referenced as the "underscore" module.
+    define(function() {
+      return _;
+    });
+  }
+  // Check for `exports` after `define` in case a build optimizer adds an `exports` object.
+  else if (freeExports && freeModule) {
+    // Export for Node.js or RingoJS.
+    if (moduleExports) {
+      (freeModule.exports = _)._ = _;
+    }
+    // Export for Rhino with CommonJS support.
+    else {
+      freeExports._ = _;
+    }
+  }
+  else {
+    // Export for a browser or Rhino.
+    root._ = _;
+  }
+}.call(this));
+
+}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
+},{}]},{},[1])(1)
+});
\ No newline at end of file
diff --git a/src/legacy/design-studio/js/dc.graph.cola.worker.js b/src/legacy/design-studio/js/dc.graph.cola.worker.js
new file mode 100644
index 0000000000000000000000000000000000000000..3cc6899ce6fee35b46215924673b894fe0caa65e
--- /dev/null
+++ b/src/legacy/design-studio/js/dc.graph.cola.worker.js
@@ -0,0 +1,496 @@
+/*!
+ *  dc.graph 0.6.0
+ *  http://dc-js.github.io/dc.graph.js/
+ *  Copyright 2015-2016 AT&T Intellectual Property & the dc.graph.js Developers
+ *  https://github.com/dc-js/dc.graph.js/blob/master/AUTHORS
+ *
+ *  Licensed 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.
+ *
+ */
+/**
+ * The entire dc.graph.js library is scoped under the **dc_graph** name space. It does not introduce
+ * anything else into the global name space.
+ *
+ * Like in dc.js and most libraries built on d3, most `dc_graph` functions are designed to allow function chaining, meaning they return the current chart
+ * instance whenever it is appropriate.  The getter forms of functions do not participate in function
+ * chaining because they return values that are not the chart.
+ * @namespace dc_graph
+ * @version 0.6.0
+ * @example
+ * // Example chaining
+ * chart.width(600)
+ *      .height(400)
+ *      .nodeDimension(nodeDim)
+ *      .nodeGroup(nodeGroup);
+ */
+
+var dc_graph = {
+    version: '0.6.0',
+    constants: {
+        CHART_CLASS: 'dc-graph'
+    }
+};
+
+function get_original(x) {
+    return x.orig;
+}
+
+function identity(x) {
+    return x;
+};
+
+var property = function (defaultValue, unwrap) {
+    if(unwrap === undefined)
+        unwrap = get_original;
+    else if(unwrap === false)
+        unwrap = identity;
+    var value = defaultValue, react = null;
+    var cascade = [];
+    var ret = function (_) {
+        if (!arguments.length) {
+            return value;
+        }
+        if(react)
+            react(_);
+        value = _;
+        return this;
+    };
+    ret.cascade = function (n, f) {
+        for(var i = 0; i<cascade.length; ++i) {
+            if(cascade[i].n === n) {
+                if(f)
+                    cascade[i].f = f;
+                else delete cascade[i];
+                return ret;
+            } else if(cascade[i].n > n) {
+                cascade.splice(i, 0, {n: n, f: f});
+                return ret;
+            }
+        }
+        cascade.push({n: n, f: f});
+        return ret;
+    };
+    ret._eval = function(o, n) {
+        if(n===0 || !cascade.length)
+            return dc_graph.functor_wrap(ret(), unwrap)(o);
+        else {
+            var last = cascade[n-1];
+            return last.f(o, function() {
+                return ret._eval(o, n-1);
+            });
+        }
+    };
+    ret.eval = function(o) {
+        return ret._eval(o, cascade.length);
+    };
+    ret.react = function(_) {
+        if (!arguments.length) {
+            return react;
+        }
+        react = _;
+        return this;
+    };
+    return ret;
+};
+
+function deprecated_property(message, defaultValue) {
+    var prop = property(defaultValue);
+    var ret = function() {
+        if(arguments.length) {
+            console.warn(message);
+            prop.apply(property, arguments);
+            return this;
+        }
+        return prop();
+    };
+    ['cascade', '_eval', 'eval', 'react'].forEach(function(method) {
+        ret[method] = prop[method];
+    });
+    return ret;
+}
+
+// http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
+function uuid() {
+    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+        var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+        return v.toString(16);
+    });
+}
+
+// create or re-use objects in a map, delete the ones that were not reused
+function regenerate_objects(preserved, list, need, key, assign, create, destroy) {
+    if(!create) create = function(k, o) { };
+    if(!destroy) destroy = function(k) { };
+    var keep = {};
+    function wrap(o) {
+        var k = key(o);
+        if(!preserved[k])
+            create(k, preserved[k] = {}, o);
+        var o1 = preserved[k];
+        assign(o1, o);
+        keep[k] = true;
+        return o1;
+    }
+    var wlist = list.map(wrap);
+    if(need)
+        need.forEach(function(k) {
+            if(!preserved[k])
+                create(k, preserved[k] = {}, null);
+            keep[k] = true;
+            wlist.push(preserved[k]);
+        });
+    // delete any objects from last round that are no longer used
+    for(var k in preserved)
+        if(!keep[k]) {
+            destroy(k, preserved[k]);
+            delete preserved[k];
+        }
+    return wlist;
+}
+
+/**
+ * `dc_graph.graphviz_attrs defines a basic set of attributes which layout engines should
+ * implement - although these are not required, they make it easier for clients and
+ * behaviors (like expand_collapse) to work with multiple layout engines.
+ *
+ * these attributes are {@link http://www.graphviz.org/doc/info/attrs.html from graphviz}
+ * @class graphviz_attrs
+ * @memberof dc_graph
+ * @return {Object}
+ **/
+dc_graph.graphviz_attrs = function() {
+    return {
+        /**
+         * Direction to draw ranks.
+         * @method rankdir
+         * @memberof dc_graph.graphviz_attrs
+         * @instance
+         * @param {String} [rankdir='TB'] 'TB', 'LR', 'BT', or 'RL'
+         **/
+        rankdir: property('TB'),
+        /**
+         * Spacing in between nodes in the same rank.
+         * @method nodesep
+         * @memberof dc_graph.graphviz_attrs
+         * @instance
+         * @param {String} [nodesep=40]
+         **/
+        nodesep: property(40),
+        /**
+         * Spacing in between ranks.
+         * @method ranksep
+         * @memberof dc_graph.graphviz_attrs
+         * @instance
+         * @param {String} [ranksep=40]
+         **/
+        ranksep: property(40)
+    };
+};
+
+/**
+ * `dc_graph.cola_layout` is an adaptor for cola.js layouts in dc.graph.js
+ * @class cola_layout
+ * @memberof dc_graph
+ * @param {String} [id=uuid()] - Unique identifier
+ * @return {dc_graph.cola_layout}
+ **/
+dc_graph.cola_layout = function(id) {
+    var _layoutId = id || uuid();
+    var _d3cola = null;
+    var _dispatch = d3.dispatch('tick', 'start', 'end');
+    var _flowLayout;
+    // node and edge objects shared with cola.js, preserved from one iteration
+    // to the next (as long as the object is still in the layout)
+    var _nodes = {}, _edges = {};
+
+    function init(options) {
+        // width, height, handleDisconnected, lengthStrategy, baseLength, flowLayout, tickSize
+        _d3cola = cola.d3adaptor()
+            .avoidOverlaps(true)
+            .size([options.width, options.height])
+            .handleDisconnected(options.handleDisconnected);
+        if(_d3cola.tickSize) // non-standard
+            _d3cola.tickSize(options.tickSize);
+
+        switch(options.lengthStrategy) {
+        case 'symmetric':
+            _d3cola.symmetricDiffLinkLengths(options.baseLength);
+            break;
+        case 'jaccard':
+            _d3cola.jaccardLinkLengths(options.baseLength);
+            break;
+        case 'individual':
+            _d3cola.linkDistance(function(e) {
+                return e.dcg_edgeLength || options.baseLength;
+            });
+            break;
+        case 'none':
+        default:
+        }
+        if(options.flowLayout) {
+            _d3cola.flowLayout(options.flowLayout.axis, options.flowLayout.minSeparation);
+        }
+    }
+
+    function data(nodes, edges, constraints, options) {
+        var wnodes = regenerate_objects(_nodes, nodes, null, function(v) {
+            return v.dcg_nodeKey;
+        }, function(v1, v) {
+            v1.dcg_nodeKey = v.dcg_nodeKey;
+            v1.width = v.width;
+            v1.height = v.height;
+            v1.fixed = !!v.dcg_nodeFixed;
+
+            if(v1.fixed && typeof v.dcg_nodeFixed === 'object') {
+                v1.x = v.dcg_nodeFixed.x;
+                v1.y = v.dcg_nodeFixed.y;
+            }
+            else {
+                // should we support e.g. null to unset x,y?
+                if(v.x !== undefined)
+                    v1.x = v.x;
+                if(v.y !== undefined)
+                    v1.y = v.y;
+            }
+        });
+        var wedges = regenerate_objects(_edges, edges, null, function(e) {
+            return e.dcg_edgeKey;
+        }, function(e1, e) {
+            e1.dcg_edgeKey = e.dcg_edgeKey;
+            // cola edges can work with indices or with object references
+            // but it will replace indices with object references
+            e1.source = _nodes[e.dcg_edgeSource];
+            e1.target = _nodes[e.dcg_edgeTarget];
+            e1.dcg_edgeLength = e.dcg_edgeLength;
+        });
+
+        // cola needs each node object to have an index property
+        wnodes.forEach(function(v, i) {
+            v.index = i;
+        });
+
+        var groups = null;
+        if(options.groupConnected) {
+            var components = cola.separateGraphs(wnodes, wedges);
+            groups = components.map(function(g) {
+                return {leaves: g.array.map(function(n) { return n.index; })};
+            });
+        }
+
+        function dispatchState(event) {
+            _dispatch[event](
+                wnodes,
+                wedges.map(function(e) {
+                    return {dcg_edgeKey: e.dcg_edgeKey};
+                })
+            );
+        }
+        _d3cola.on('tick', /* _tick = */ function() {
+            dispatchState('tick');
+        }).on('start', function() {
+            _dispatch.start();
+        }).on('end', /* _done = */ function() {
+            dispatchState('end');
+        });
+        _d3cola.nodes(wnodes)
+            .links(wedges)
+            .constraints(constraints)
+            .groups(groups);
+    }
+
+    function start(options) {
+        _d3cola.start(options.initialUnconstrainedIterations,
+                      options.initialUserConstraintIterations,
+                      options.initialAllConstraintsIterations,
+                      options.gridSnapIterations);
+    }
+
+    function stop() {
+        _d3cola.stop();
+    }
+
+    var graphviz = dc_graph.graphviz_attrs(), graphviz_keys = Object.keys(graphviz);
+    graphviz.rankdir(null);
+
+    var engine = Object.assign(graphviz, {
+        layoutAlgorithm: function() {
+            return 'cola';
+        },
+        layoutId: function() {
+            return _layoutId;
+        },
+        supportsWebworker: function() {
+            return true;
+        },
+        needsStage: function(stage) { // stopgap until we have engine chaining
+            return stage === 'ports' || stage === 'edgepos';
+        },
+        parent: property(null),
+        on: function(event, f) {
+            _dispatch.on(event, f);
+            return this;
+        },
+        init: function(options) {
+            this.optionNames().forEach(function(option) {
+                options[option] = options[option] || this[option]();
+            }.bind(this));
+            init(options);
+            return this;
+        },
+        data: function(nodes, edges, constraints, options) {
+            data(nodes, edges, constraints, options);
+        },
+        start: function(options) {
+            start(options);
+        },
+        stop: function() {
+            stop();
+        },
+        optionNames: function() {
+            return ['handleDisconnected', 'lengthStrategy', 'baseLength', 'flowLayout', 'tickSize']
+                .concat(graphviz_keys);
+        },
+        populateLayoutNode: function() {},
+        populateLayoutEdge: function() {},
+        /**
+         * Instructs cola.js to fit the connected components.
+         * @method handleDisconnected
+         * @memberof dc_graph.cola_layout
+         * @instance
+         * @param {Boolean} [handleDisconnected=true]
+         * @return {Boolean}
+         * @return {dc_graph.cola_layout}
+         **/
+        handleDisconnected: property(true),
+        /**
+         * Currently, three strategies are supported for specifying the lengths of edges:
+         * * 'individual' - uses the `edgeLength` for each edge. If it returns falsy, uses the
+         * `baseLength`
+         * * 'symmetric', 'jaccard' - compute the edge length based on the graph structure around
+         * the edge. See
+         * {@link https://github.com/tgdwyer/WebCola/wiki/link-lengths the cola.js wiki}
+         * for more details.
+         * 'none' - no edge lengths will be specified
+         * @method lengthStrategy
+         * @memberof dc_graph.cola_layout
+         * @instance
+         * @param {Function|String} [lengthStrategy='symmetric']
+         * @return {Function|String}
+         * @return {dc_graph.cola_layout}
+         **/
+        lengthStrategy: property('symmetric'),
+        /**
+         * Gets or sets the default edge length (in pixels) when the `.lengthStrategy` is
+         * 'individual', and the base value to be multiplied for 'symmetric' and 'jaccard' edge
+         * lengths.
+         * @method baseLength
+         * @memberof dc_graph.cola_layout
+         * @instance
+         * @param {Number} [baseLength=30]
+         * @return {Number}
+         * @return {dc_graph.cola_layout}
+         **/
+        baseLength: property(30),
+        /**
+         * If `flowLayout` is set, it determines the axis and separation for
+         * {@link http://marvl.infotech.monash.edu/webcola/doc/classes/cola.layout.html#flowlayout cola flow layout}.
+         * If it is not set, `flowLayout` will be calculated from the {@link dc_graph.graphviz_attrs#rankdir rankdir}
+         * and {@link dc_graph.graphviz_attrs#ranksep ranksep}; if `rankdir` is also null (the
+         * default for cola layout), then there will be no flow.
+         * @method flowLayout
+         * @memberof dc_graph.cola_layout
+         * @instance
+         * @param {Object} [flowLayout=null]
+         * @example
+         * // No flow (default)
+         * chart.flowLayout(null)
+         * // flow in x with min separation 200
+         * chart.flowLayout({axis: 'x', minSeparation: 200})
+         **/
+        flowLayout: function(flow) {
+            if(!arguments.length) {
+                if(_flowLayout)
+                    return _flowLayout;
+                var dir = engine.rankdir();
+                switch(dir) {
+                case 'LR': return {axis: 'x', minSeparation: engine.ranksep() + engine.parent().nodeRadius()*2};
+                case 'TB': return {axis: 'y', minSeparation: engine.ranksep() + engine.parent().nodeRadius()*2};
+                default: return null; // RL, BT do not appear to be possible (negative separation) (?)
+                }
+            }
+            _flowLayout = flow;
+            return this;
+        },
+        tickSize: property(1)
+    });
+    return engine;
+};
+
+dc_graph.cola_layout.scripts = ['d3.js', 'cola.js'];
+
+var _layouts;
+
+function postResponse(event, layoutId) {
+    return function() {
+        var message = {
+            response: event,
+            layoutId: layoutId
+        };
+        message.args = Array.prototype.slice.call(arguments);
+        postMessage(message);
+    };
+}
+
+onmessage = function(e) {
+    var args = e.data.args;
+    switch(e.data.command) {
+    case 'init':
+        // find a function under dc_graph that has `scripts`
+        var layout_name;
+        for(var name in dc_graph) {
+            if(typeof dc_graph[name] === 'function' && dc_graph[name].scripts)
+                layout_name = name;
+        }
+        if(!_layouts) {
+            _layouts = {};
+            importScripts.apply(null, dc_graph[layout_name].scripts);
+        }
+
+        _layouts[args.layoutId] = dc_graph[layout_name]()
+            .on('tick', postResponse('tick', args.layoutId))
+            .on('start', postResponse('start', args.layoutId))
+            .on('end', postResponse('end', args.layoutId))
+            .init(args.options);
+        break;
+    case 'data':
+        if(_layouts)
+            _layouts[args.layoutId].data(args.nodes, args.edges, args.constraints, args.options);
+        break;
+    case 'start':
+        // if(args.initialOnly) {
+        //     if(args.showLayoutSteps)
+        //         _tick();
+        //     _done();
+        // }
+        // else
+        _layouts[args.layoutId].start(args.options);
+        break;
+    case 'stop':
+        if(_layouts)
+            _layouts[args.layoutId].stop();
+        break;
+    }
+};
+
+
+//# sourceMappingURL=dc.graph.cola.worker.js.map
\ No newline at end of file
diff --git a/src/legacy/design-studio/js/dc.graph.dagre.worker.js b/src/legacy/design-studio/js/dc.graph.dagre.worker.js
new file mode 100644
index 0000000000000000000000000000000000000000..199ffcf9abb0a2e43e9b3e8722386ea60e18f8b4
--- /dev/null
+++ b/src/legacy/design-studio/js/dc.graph.dagre.worker.js
@@ -0,0 +1,384 @@
+/*!
+ *  dc.graph 0.6.0
+ *  http://dc-js.github.io/dc.graph.js/
+ *  Copyright 2015-2016 AT&T Intellectual Property & the dc.graph.js Developers
+ *  https://github.com/dc-js/dc.graph.js/blob/master/AUTHORS
+ *
+ *  Licensed 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.
+ *
+ */
+/**
+ * The entire dc.graph.js library is scoped under the **dc_graph** name space. It does not introduce
+ * anything else into the global name space.
+ *
+ * Like in dc.js and most libraries built on d3, most `dc_graph` functions are designed to allow function chaining, meaning they return the current chart
+ * instance whenever it is appropriate.  The getter forms of functions do not participate in function
+ * chaining because they return values that are not the chart.
+ * @namespace dc_graph
+ * @version 0.6.0
+ * @example
+ * // Example chaining
+ * chart.width(600)
+ *      .height(400)
+ *      .nodeDimension(nodeDim)
+ *      .nodeGroup(nodeGroup);
+ */
+
+var dc_graph = {
+    version: '0.6.0',
+    constants: {
+        CHART_CLASS: 'dc-graph'
+    }
+};
+
+function get_original(x) {
+    return x.orig;
+}
+
+function identity(x) {
+    return x;
+};
+
+var property = function (defaultValue, unwrap) {
+    if(unwrap === undefined)
+        unwrap = get_original;
+    else if(unwrap === false)
+        unwrap = identity;
+    var value = defaultValue, react = null;
+    var cascade = [];
+    var ret = function (_) {
+        if (!arguments.length) {
+            return value;
+        }
+        if(react)
+            react(_);
+        value = _;
+        return this;
+    };
+    ret.cascade = function (n, f) {
+        for(var i = 0; i<cascade.length; ++i) {
+            if(cascade[i].n === n) {
+                if(f)
+                    cascade[i].f = f;
+                else delete cascade[i];
+                return ret;
+            } else if(cascade[i].n > n) {
+                cascade.splice(i, 0, {n: n, f: f});
+                return ret;
+            }
+        }
+        cascade.push({n: n, f: f});
+        return ret;
+    };
+    ret._eval = function(o, n) {
+        if(n===0 || !cascade.length)
+            return dc_graph.functor_wrap(ret(), unwrap)(o);
+        else {
+            var last = cascade[n-1];
+            return last.f(o, function() {
+                return ret._eval(o, n-1);
+            });
+        }
+    };
+    ret.eval = function(o) {
+        return ret._eval(o, cascade.length);
+    };
+    ret.react = function(_) {
+        if (!arguments.length) {
+            return react;
+        }
+        react = _;
+        return this;
+    };
+    return ret;
+};
+
+function deprecated_property(message, defaultValue) {
+    var prop = property(defaultValue);
+    var ret = function() {
+        if(arguments.length) {
+            console.warn(message);
+            prop.apply(property, arguments);
+            return this;
+        }
+        return prop();
+    };
+    ['cascade', '_eval', 'eval', 'react'].forEach(function(method) {
+        ret[method] = prop[method];
+    });
+    return ret;
+}
+
+// http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
+function uuid() {
+    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+        var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+        return v.toString(16);
+    });
+}
+
+// create or re-use objects in a map, delete the ones that were not reused
+function regenerate_objects(preserved, list, need, key, assign, create, destroy) {
+    if(!create) create = function(k, o) { };
+    if(!destroy) destroy = function(k) { };
+    var keep = {};
+    function wrap(o) {
+        var k = key(o);
+        if(!preserved[k])
+            create(k, preserved[k] = {}, o);
+        var o1 = preserved[k];
+        assign(o1, o);
+        keep[k] = true;
+        return o1;
+    }
+    var wlist = list.map(wrap);
+    if(need)
+        need.forEach(function(k) {
+            if(!preserved[k])
+                create(k, preserved[k] = {}, null);
+            keep[k] = true;
+            wlist.push(preserved[k]);
+        });
+    // delete any objects from last round that are no longer used
+    for(var k in preserved)
+        if(!keep[k]) {
+            destroy(k, preserved[k]);
+            delete preserved[k];
+        }
+    return wlist;
+}
+
+/**
+ * `dc_graph.graphviz_attrs defines a basic set of attributes which layout engines should
+ * implement - although these are not required, they make it easier for clients and
+ * behaviors (like expand_collapse) to work with multiple layout engines.
+ *
+ * these attributes are {@link http://www.graphviz.org/doc/info/attrs.html from graphviz}
+ * @class graphviz_attrs
+ * @memberof dc_graph
+ * @return {Object}
+ **/
+dc_graph.graphviz_attrs = function() {
+    return {
+        /**
+         * Direction to draw ranks.
+         * @method rankdir
+         * @memberof dc_graph.graphviz_attrs
+         * @instance
+         * @param {String} [rankdir='TB'] 'TB', 'LR', 'BT', or 'RL'
+         **/
+        rankdir: property('TB'),
+        /**
+         * Spacing in between nodes in the same rank.
+         * @method nodesep
+         * @memberof dc_graph.graphviz_attrs
+         * @instance
+         * @param {String} [nodesep=40]
+         **/
+        nodesep: property(40),
+        /**
+         * Spacing in between ranks.
+         * @method ranksep
+         * @memberof dc_graph.graphviz_attrs
+         * @instance
+         * @param {String} [ranksep=40]
+         **/
+        ranksep: property(40)
+    };
+};
+
+/**
+ * `dc_graph.dagre_layout` is an adaptor for dagre.js layouts in dc.graph.js
+ *
+ * In addition to the below layout attributes, `dagre_layout` also implements the attributes from
+ * {@link dc_graph.graphviz_attrs graphviz_attrs}
+ * @class dagre_layout
+ * @memberof dc_graph
+ * @param {String} [id=uuid()] - Unique identifier
+ * @return {dc_graph.dagre_layout}
+ **/
+dc_graph.dagre_layout = function(id) {
+    var _layoutId = id || uuid();
+    var _dagreGraph = null, _tick, _done;
+    var _dispatch = d3.dispatch('tick', 'start', 'end');
+    // node and edge objects preserved from one iteration
+    // to the next (as long as the object is still in the layout)
+    var _nodes = {}, _edges = {};
+
+    function init(options) {
+        // Create a new directed graph
+        _dagreGraph = new dagre.graphlib.Graph({multigraph: true});
+
+        // Set an object for the graph label
+        _dagreGraph.setGraph({rankdir: options.rankdir, nodesep: options.nodesep, ranksep: options.ranksep});
+
+        // Default to assigning a new object as a label for each new edge.
+        _dagreGraph.setDefaultEdgeLabel(function() { return {}; });
+    }
+
+    function data(nodes, edges, constraints, options) {
+        var wnodes = regenerate_objects(_nodes, nodes, null, function(v) {
+            return v.dcg_nodeKey;
+        }, function(v1, v) {
+            v1.dcg_nodeKey = v.dcg_nodeKey;
+            v1.width = v.width;
+            v1.height = v.height;
+            /*
+              dagre does not seem to accept input positions
+              if(v.dcg_nodeFixed) {
+                v1.x = v.dcg_nodeFixed.x;
+                v1.y = v.dcg_nodeFixed.y;
+              }
+             */
+        }, function(k, o) {
+            _dagreGraph.setNode(k, o);
+        }, function(k) {
+            _dagreGraph.removeNode(k);
+        });
+        var wedges = regenerate_objects(_edges, edges, null, function(e) {
+            return e.dcg_edgeKey;
+        }, function(e1, e) {
+            e1.dcg_edgeKey = e.dcg_edgeKey;
+            e1.dcg_edgeSource = e.dcg_edgeSource;
+            e1.dcg_edgeTarget = e.dcg_edgeTarget;
+        }, function(k, o, e) {
+            _dagreGraph.setEdge(e.dcg_edgeSource, e.dcg_edgeTarget, o);
+        }, function(k, e) {
+            _dagreGraph.removeEdge(e.dcg_edgeSource, e.dcg_edgeTarget, e.dcg_edgeKey);
+        });
+
+        function dispatchState(event) {
+            _dispatch[event](
+                wnodes,
+                wedges.map(function(e) {
+                    return {dcg_edgeKey: e.dcg_edgeKey};
+                })
+            );
+        }
+        _tick = function() {
+            dispatchState('tick');
+        };
+        _done = function() {
+            dispatchState('end');
+        };
+    }
+
+    function start(options) {
+        _dispatch.start();
+        dagre.layout(_dagreGraph);
+        _done();
+    }
+
+    function stop() {
+    }
+
+    var graphviz = dc_graph.graphviz_attrs(), graphviz_keys = Object.keys(graphviz);
+    return Object.assign(graphviz, {
+        layoutAlgorithm: function() {
+            return 'dagre';
+        },
+        layoutId: function() {
+            return _layoutId;
+        },
+        supportsWebworker: function() {
+            return true;
+        },
+        needsStage: function(stage) { // stopgap until we have engine chaining
+            return stage === 'ports' || stage === 'edgepos';
+        },
+        on: function(event, f) {
+            _dispatch.on(event, f);
+            return this;
+        },
+        init: function(options) {
+            this.optionNames().forEach(function(option) {
+                options[option] = options[option] || this[option]();
+            }.bind(this));
+            init(options);
+            return this;
+        },
+        data: function(nodes, edges, constraints, options) {
+            data(nodes, edges, constraints, options);
+        },
+        start: function(options) {
+            start(options);
+        },
+        stop: function() {
+            stop();
+        },
+        optionNames: function() {
+            return graphviz_keys;
+        },
+        populateLayoutNode: function() {},
+        populateLayoutEdge: function() {}
+    });
+};
+
+dc_graph.dagre_layout.scripts = ['d3.js', 'dagre.js'];
+
+var _layouts;
+
+function postResponse(event, layoutId) {
+    return function() {
+        var message = {
+            response: event,
+            layoutId: layoutId
+        };
+        message.args = Array.prototype.slice.call(arguments);
+        postMessage(message);
+    };
+}
+
+onmessage = function(e) {
+    var args = e.data.args;
+    switch(e.data.command) {
+    case 'init':
+        // find a function under dc_graph that has `scripts`
+        var layout_name;
+        for(var name in dc_graph) {
+            if(typeof dc_graph[name] === 'function' && dc_graph[name].scripts)
+                layout_name = name;
+        }
+        if(!_layouts) {
+            _layouts = {};
+            importScripts.apply(null, dc_graph[layout_name].scripts);
+        }
+
+        _layouts[args.layoutId] = dc_graph[layout_name]()
+            .on('tick', postResponse('tick', args.layoutId))
+            .on('start', postResponse('start', args.layoutId))
+            .on('end', postResponse('end', args.layoutId))
+            .init(args.options);
+        break;
+    case 'data':
+        if(_layouts)
+            _layouts[args.layoutId].data(args.nodes, args.edges, args.constraints, args.options);
+        break;
+    case 'start':
+        // if(args.initialOnly) {
+        //     if(args.showLayoutSteps)
+        //         _tick();
+        //     _done();
+        // }
+        // else
+        _layouts[args.layoutId].start(args.options);
+        break;
+    case 'stop':
+        if(_layouts)
+            _layouts[args.layoutId].stop();
+        break;
+    }
+};
+
+
+//# sourceMappingURL=dc.graph.dagre.worker.js.map
\ No newline at end of file
diff --git a/src/legacy/design-studio/js/dc.graph.js b/src/legacy/design-studio/js/dc.graph.js
new file mode 100644
index 0000000000000000000000000000000000000000..6de9384693419c641f120e2a0e8a07bf2d7c36fa
--- /dev/null
+++ b/src/legacy/design-studio/js/dc.graph.js
@@ -0,0 +1,15617 @@
+/*!
+ *  dc.graph 0.9.5
+ *  http://dc-js.github.io/dc.graph.js/
+ *  Copyright 2015-2019 AT&T Intellectual Property & the dc.graph.js Developers
+ *  https://github.com/dc-js/dc.graph.js/blob/master/AUTHORS
+ *
+ *  Licensed 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.
+ *
+ */
+(function() { function _dc_graph(d3, crossfilter, dc) {
+'use strict';
+
+/**
+ * The entire dc.graph.js library is scoped under the **dc_graph** name space. It does not introduce
+ * anything else into the global name space.
+ *
+ * Like in dc.js and most libraries built on d3, most `dc_graph` functions are designed to allow function chaining, meaning they return the current diagram
+ * instance whenever it is appropriate.  The getter forms of functions do not participate in function
+ * chaining because they return values that are not the diagram.
+ * @namespace dc_graph
+ * @version 0.9.5
+ * @example
+ * // Example chaining
+ * diagram.width(600)
+ *      .height(400)
+ *      .nodeDimension(nodeDim)
+ *      .nodeGroup(nodeGroup);
+ */
+
+var dc_graph = {
+    version: '0.9.5',
+    constants: {
+        CHART_CLASS: 'dc-graph'
+    }
+};
+
+function get_original(x) {
+    return x.orig;
+}
+
+function identity(x) {
+    return x;
+};
+
+var property = function (defaultValue, unwrap) {
+    if(unwrap === undefined)
+        unwrap = get_original;
+    else if(unwrap === false)
+        unwrap = identity;
+    var value = defaultValue, react = null;
+    var cascade = [];
+    var ret = function (_) {
+        if (!arguments.length) {
+            return value;
+        }
+        if(react)
+            react(_);
+        value = _;
+        return this;
+    };
+    ret.cascade = function (n, f) {
+        for(var i = 0; i<cascade.length; ++i) {
+            if(cascade[i].n === n) {
+                if(f)
+                    cascade[i].f = f;
+                else cascade.splice(i, 1);
+                return ret;
+            } else if(cascade[i].n > n) {
+                cascade.splice(i, 0, {n: n, f: f});
+                return ret;
+            }
+        }
+        cascade.push({n: n, f: f});
+        return ret;
+    };
+    ret._eval = function(o, n) {
+        if(n===0 || !cascade.length)
+            return dc_graph.functor_wrap(ret(), unwrap)(o);
+        else {
+            var last = cascade[n-1];
+            return last.f(o, function() {
+                return ret._eval(o, n-1);
+            });
+        }
+    };
+    ret.eval = function(o) {
+        return ret._eval(o, cascade.length);
+    };
+    ret.react = function(_) {
+        if (!arguments.length) {
+            return react;
+        }
+        react = _;
+        return this;
+    };
+    return ret;
+};
+
+function named_children() {
+    var _children = {};
+    var f = function(id, object) {
+        if(arguments.length === 1)
+            return _children[id];
+        if(f.reject) {
+            var reject = f.reject(id, object);
+            if(reject) {
+                console.groupCollapsed(reject);
+                console.trace();
+                console.groupEnd();
+                return this;
+            }
+        }
+        // do not notify unnecessarily
+        if(_children[id] === object)
+            return this;
+        if(_children[id])
+            _children[id].parent(null);
+        _children[id] = object;
+        if(object)
+            object.parent(this);
+        return this;
+    };
+    f.enum = function() {
+        return Object.keys(_children);
+    };
+    f.nameOf = function(o) {
+        var found = Object.entries(_children).find(function(kv) {
+            return kv[1] == o;
+        });
+        return found ? found[0] : null;
+    };
+    return f;
+}
+
+function deprecated_property(message, defaultValue) {
+    var prop = property(defaultValue);
+    var ret = function() {
+        if(arguments.length) {
+            console.warn(message);
+            prop.apply(property, arguments);
+            return this;
+        }
+        return prop();
+    };
+    ['cascade', '_eval', 'eval', 'react'].forEach(function(method) {
+        ret[method] = prop[method];
+    });
+    return ret;
+}
+
+function onetime_trace(level, message) {
+    var said = false;
+    return function() {
+        if(said)
+            return;
+        if(level === 'trace') {
+            console.groupCollapsed(message);
+            console.trace();
+            console.groupEnd();
+        }
+        else
+            console[level](message);
+        said = true;
+    };
+}
+
+function deprecation_warning(message) {
+    return onetime_trace('warn', message);
+}
+
+function trace_function(level, message, f) {
+    var dep = onetime_trace(level, message);
+    return function() {
+        dep();
+        return f.apply(this, arguments);
+    };
+}
+function deprecate_function(message, f) {
+    return trace_function('warn', message, f);
+}
+
+// http://stackoverflow.com/questions/105034/create-guid-uuid-in-javascript
+function uuid() {
+    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+        var r = Math.random()*16|0, v = c == 'x' ? r : (r&0x3|0x8);
+        return v.toString(16);
+    });
+}
+
+function is_ie() {
+    var ua = window.navigator.userAgent;
+
+    return(ua.indexOf('MSIE ') > 0 ||
+           ua.indexOf('Trident/') > 0 ||
+           ua.indexOf('Edge/') > 0);
+}
+
+function is_safari() {
+    return /^((?!chrome|android).)*safari/i.test(navigator.userAgent);
+}
+
+// polyfill Object.assign for IE
+// it's just too useful to do without
+if (typeof Object.assign != 'function') {
+  // Must be writable: true, enumerable: false, configurable: true
+  Object.defineProperty(Object, "assign", {
+    value: function assign(target, varArgs) { // .length of function is 2
+      'use strict';
+      if (target == null) { // TypeError if undefined or null
+        throw new TypeError('Cannot convert undefined or null to object');
+      }
+
+      var to = Object(target);
+
+      for (var index = 1; index < arguments.length; index++) {
+        var nextSource = arguments[index];
+
+        if (nextSource != null) { // Skip over if undefined or null
+          for (var nextKey in nextSource) {
+            // Avoid bugs when hasOwnProperty is shadowed
+            if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
+              to[nextKey] = nextSource[nextKey];
+            }
+          }
+        }
+      }
+      return to;
+    },
+    writable: true,
+    configurable: true
+  });
+}
+
+
+// https://tc39.github.io/ecma262/#sec-array.prototype.includes
+if (!Array.prototype.includes) {
+  Object.defineProperty(Array.prototype, 'includes', {
+    value: function(valueToFind, fromIndex) {
+
+      if (this == null) {
+        throw new TypeError('"this" is null or not defined');
+      }
+
+      // 1. Let O be ? ToObject(this value).
+      var o = Object(this);
+
+      // 2. Let len be ? ToLength(? Get(O, "length")).
+      var len = o.length >>> 0;
+
+      // 3. If len is 0, return false.
+      if (len === 0) {
+        return false;
+      }
+
+      // 4. Let n be ? ToInteger(fromIndex).
+      //    (If fromIndex is undefined, this step produces the value 0.)
+      var n = fromIndex | 0;
+
+      // 5. If n >= 0, then
+      //  a. Let k be n.
+      // 6. Else n < 0,
+      //  a. Let k be len + n.
+      //  b. If k < 0, let k be 0.
+      var k = Math.max(n >= 0 ? n : len - Math.abs(n), 0);
+
+      function sameValueZero(x, y) {
+        return x === y || (typeof x === 'number' && typeof y === 'number' && isNaN(x) && isNaN(y));
+      }
+
+      // 7. Repeat, while k < len
+      while (k < len) {
+        // a. Let elementK be the result of ? Get(O, ! ToString(k)).
+        // b. If SameValueZero(valueToFind, elementK) is true, return true.
+        if (sameValueZero(o[k], valueToFind)) {
+          return true;
+        }
+        // c. Increase k by 1.
+        k++;
+      }
+
+      // 8. Return false
+      return false;
+    }
+  });
+}
+
+if (!Object.entries) {
+  Object.entries = function( obj ){
+    var ownProps = Object.keys( obj ),
+        i = ownProps.length,
+        resArray = new Array(i); // preallocate the Array
+    while (i--)
+      resArray[i] = [ownProps[i], obj[ownProps[i]]];
+    return resArray;
+  };
+}
+
+// https://github.com/KhaledElAnsari/Object.values
+Object.values = Object.values ? Object.values : function(obj) {
+    var allowedTypes = ["[object String]", "[object Object]", "[object Array]", "[object Function]"];
+    var objType = Object.prototype.toString.call(obj);
+
+    if(obj === null || typeof obj === "undefined") {
+	throw new TypeError("Cannot convert undefined or null to object");
+    } else if(!~allowedTypes.indexOf(objType)) {
+	return [];
+    } else {
+	// if ES6 is supported
+	if (Object.keys) {
+	    return Object.keys(obj).map(function (key) {
+		return obj[key];
+	    });
+	}
+
+	var result = [];
+	for (var prop in obj) {
+	    if (obj.hasOwnProperty(prop)) {
+		result.push(obj[prop]);
+	    }
+	}
+
+	return result;
+    }
+};
+
+function getBBoxNoThrow(elem) {
+    // firefox seems to have issues with some of my texts
+    // just catch for now
+    try {
+        return elem.getBBox();
+    } catch(xep) {
+        return {x: 0, y: 0, width:0, height: 0};
+    }
+}
+
+function property_if(pred, curr) {
+    return function(o, last) {
+        return pred(o) ? curr(o) : last();
+    };
+}
+
+function property_interpolate(value, curr) {
+    return function(o, last) {
+        return d3.interpolate(last(o), curr(o))(value(o));
+    };
+}
+
+function multiply_properties(pred, props, blend) {
+    var props2 = {};
+    for(var p in props)
+        props2[p] = blend(pred, param(props[p]));
+    return props2;
+}
+
+function conditional_properties(pred, props) {
+    return multiply_properties(pred, props, property_if);
+}
+
+function node_edge_conditions(npred, epred, props) {
+    var nprops = {}, eprops = {}, badprops = [];
+    for(var p in props) {
+        if(/^node/.test(p))
+            nprops[p] = props[p];
+        else if(/^edge/.test(p))
+            eprops[p] = props[p];
+        else badprops.push(p);
+    }
+    if(badprops.length)
+        console.error('only know how to deal with properties that start with "node" or "edge"', badprops);
+    var props2 = npred ? conditional_properties(npred, nprops) : {};
+    if(epred)
+        Object.assign(props2, conditional_properties(epred, eprops));
+    return props2;
+}
+
+function cascade(parent) {
+    return function(level, add, props) {
+        for(var p in props) {
+            if(!parent[p])
+                throw new Error('unknown attribute ' + p);
+            parent[p].cascade(level, add ? props[p] : null);
+        }
+        return parent;
+    };
+}
+
+function compose(f, g) {
+    return function() {
+        return f(g.apply(null, arguments));
+    };
+}
+
+// version of d3.functor that optionally wraps the function with another
+// one, if the parameter is a function
+dc_graph.functor_wrap = function (v, wrap) {
+    if(typeof v === "function") {
+        return wrap ? function(x) {
+            return v(wrap(x));
+        } : v;
+    }
+    else return function() {
+        return v;
+    };
+};
+
+// we want to allow either values or functions to be passed to specify parameters.
+// if a function, the function needs a preprocessor to extract the original key/value
+// pair from the wrapper object we put it in.
+function param(v) {
+    return dc_graph.functor_wrap(v, get_original);
+}
+
+// http://jsperf.com/cloning-an-object/101
+function clone(obj) {
+    var target = {};
+    for(var i in obj) {
+        if(obj.hasOwnProperty(i)) {
+            target[i] = obj[i];
+        }
+    }
+    return target;
+}
+
+// because i don't think we need to bind edge point data (yet!)
+var bez_cmds = {
+    1: 'L', 2: 'Q', 3: 'C'
+};
+
+function generate_path(pts, bezDegree, close) {
+    var cats = ['M', pts[0].x, ',', pts[0].y], remain = bezDegree;
+    var hasNaN = false;
+    for(var i = 1; i < pts.length; ++i) {
+        if(isNaN(pts[i].x) || isNaN(pts[i].y))
+            hasNaN = true;
+        cats.push(remain===bezDegree ? bez_cmds[bezDegree] : ' ', pts[i].x, ',', pts[i].y);
+        if(--remain===0)
+            remain = bezDegree;
+    }
+    if(remain!=bezDegree)
+        console.log("warning: pts.length didn't match bezian degree", pts, bezDegree);
+    if(close)
+        cats.push('Z');
+    return cats.join('');
+}
+
+// for IE (do we care really?)
+Math.hypot = Math.hypot || function() {
+  var y = 0;
+  var length = arguments.length;
+
+  for (var i = 0; i < length; i++) {
+    if (arguments[i] === Infinity || arguments[i] === -Infinity) {
+      return Infinity;
+    }
+    y += arguments[i] * arguments[i];
+  }
+  return Math.sqrt(y);
+};
+
+// outputs the array with adjacent identical lines collapsed to one
+function uniq(a) {
+    var ret = [];
+    a.forEach(function(x, i) {
+        if(i === 0 || x !== a[i-1])
+            ret.push(x);
+    });
+    return ret;
+}
+
+// https://tc39.github.io/ecma262/#sec-array.prototype.find
+if (!Array.prototype.find) {
+  Object.defineProperty(Array.prototype, 'find', {
+    value: function(predicate) {
+     // 1. Let O be ? ToObject(this value).
+      if (this == null) {
+        throw new TypeError('"this" is null or not defined');
+      }
+
+      var o = Object(this);
+
+      // 2. Let len be ? ToLength(? Get(O, "length")).
+      var len = o.length >>> 0;
+
+      // 3. If IsCallable(predicate) is false, throw a TypeError exception.
+      if (typeof predicate !== 'function') {
+        throw new TypeError('predicate must be a function');
+      }
+
+      // 4. If thisArg was supplied, let T be thisArg; else let T be undefined.
+      var thisArg = arguments[1];
+
+      // 5. Let k be 0.
+      var k = 0;
+
+      // 6. Repeat, while k < len
+      while (k < len) {
+        // a. Let Pk be ! ToString(k).
+        // b. Let kValue be ? Get(O, Pk).
+        // c. Let testResult be ToBoolean(? Call(predicate, T, << kValue, k, O >>)).
+        // d. If testResult is true, return kValue.
+        var kValue = o[k];
+        if (predicate.call(thisArg, kValue, k, o)) {
+          return kValue;
+        }
+        // e. Increase k by 1.
+        k++;
+      }
+
+      // 7. Return undefined.
+      return undefined;
+    }
+  });
+}
+
+var script_path = function() {
+    var _path;
+    return function() {
+        if(_path === undefined) {
+            // adapted from http://stackoverflow.com/a/18283141/676195
+            _path = null; // only try once
+            var filename = 'dc.graph.js';
+            var scripts = document.getElementsByTagName('script');
+            if (scripts && scripts.length > 0) {
+                for (var i in scripts) {
+                    if (scripts[i].src && scripts[i].src.match(new RegExp(filename+'$'))) {
+                        _path = scripts[i].src.replace(new RegExp('(.*)'+filename+'$'), '$1');
+                        break;
+                    }
+                }
+            }
+        }
+        return _path;
+    };
+}();
+
+dc_graph.event_coords = function(diagram) {
+    var bound = diagram.root().node().getBoundingClientRect();
+    return diagram.invertCoord([d3.event.clientX - bound.left,
+                              d3.event.clientY - bound.top]);
+};
+
+function promise_identity(x) {
+    return Promise.resolve(x);
+}
+
+// http://stackoverflow.com/questions/7044944/jquery-javascript-to-detect-os-without-a-plugin
+var is_a_mac = navigator.platform.toUpperCase().indexOf('MAC')!==-1;
+
+// https://stackoverflow.com/questions/16863917/check-if-class-exists-somewhere-in-parent-vanilla-js
+function ancestor_has_class(element, classname) {
+    if(d3.select(element).classed(classname))
+        return true;
+    return element.parentElement && ancestor_has_class(element.parentElement, classname);
+}
+
+if (typeof SVGElement.prototype.contains == 'undefined') {
+    SVGElement.prototype.contains = HTMLDivElement.prototype.contains;
+}
+
+// arguably depth first search is a stupid algorithm to modularize -
+// there are many, many interesting moments to insert a behavior
+// and those end up being almost bigger than the function itself
+
+// this is an argument for providing a graph API which could make it
+// easy to just write a recursive function instead of using this
+dc_graph.depth_first_traversal = function(callbacks) { // {[init, root, row, tree, place, sib, push, pop, skip,] finish, nodeid, sourceid, targetid}
+    return function(nodes, edges) {
+        callbacks.init && callbacks.init();
+        if(callbacks.tree)
+            edges = edges.filter(function(e) { return callbacks.tree(e); });
+        var indegree = {};
+        var outmap = edges.reduce(function(m, e) {
+            var tail = callbacks.sourceid(e),
+                head = callbacks.targetid(e);
+            if(!m[tail]) m[tail] = [];
+            m[tail].push(e);
+            indegree[head] = (indegree[head] || 0) + 1;
+            return m;
+        }, {});
+        var nmap = nodes.reduce(function(m, n) {
+            var key = callbacks.nodeid(n);
+            m[key] = n;
+            return m;
+        }, {});
+
+        var rows = [];
+        var placed = {};
+        function place_tree(n, r) {
+            var key = callbacks.nodeid(n);
+            if(placed[key]) {
+                callbacks.skip && callbacks.skip(n, indegree[key]);
+                return;
+            }
+            if(!rows[r])
+                rows[r] = [];
+            callbacks.place && callbacks.place(n, r, rows[r]);
+            rows[r].push(n);
+            placed[key] = true;
+            if(outmap[key])
+                outmap[key].forEach(function(e, ei) {
+                    var target = nmap[callbacks.targetid(e)];
+                    if(ei && callbacks.sib)
+                        callbacks.sib(false, nmap[callbacks.targetid(outmap[key][ei-1])], target);
+                    callbacks.push && callbacks.push();
+                    place_tree(target, r+1);
+                });
+            callbacks.pop && callbacks.pop(n);
+        }
+
+        var roots;
+        if(callbacks.root)
+            roots = nodes.filter(function(n) { return callbacks.root(n); });
+        else {
+            roots = nodes.filter(function(n) { return !indegree[callbacks.nodeid(n)]; });
+            if(nodes.length && !roots.length) // all nodes are in a cycle
+                roots = [nodes[0]];
+        }
+        roots.forEach(function(n, ni) {
+            if(ni && callbacks.sib)
+                callbacks.sib(true, roots[ni-1], n);
+            callbacks.push && callbacks.push();
+            place_tree(n, callbacks.row && callbacks.row(n) || 0);
+        });
+        callbacks.finish(rows);
+    };
+};
+
+// basically, see if it's any simpler if we start from scratch
+// (well, of course it's simpler because we have less callbacks)
+// same caveats as above
+dc_graph.undirected_dfs = function(callbacks) { // {[comp, node], nodeid, sourceid, targetid}
+    return function(nodes, edges) {
+        var adjacencies = edges.reduce(function(m, e) {
+            var tail = callbacks.sourceid(e),
+                head = callbacks.targetid(e);
+            if(!m[tail]) m[tail] = [];
+            if(!m[head]) m[head] = [];
+            m[tail].push(head);
+            m[head].push(tail);
+            return m;
+        }, {});
+        var nmap = nodes.reduce(function(m, n) {
+            var key = callbacks.nodeid(n);
+            m[key] = n;
+            return m;
+        }, {});
+        var found = {};
+        function recurse(n) {
+            var nid = callbacks.nodeid(n);
+            callbacks.node(compid, n);
+            found[nid] = true;
+            if(adjacencies[nid])
+                adjacencies[nid].forEach(function(adj) {
+                    if(!found[adj])
+                        recurse(nmap[adj]);
+                });
+        }
+        var compid = 0;
+        nodes.forEach(function(n) {
+            if(!found[callbacks.nodeid(n)]) {
+                callbacks.comp && callbacks.comp(compid);
+                recurse(n);
+                ++compid;
+            }
+        });
+    };
+};
+
+// create or re-use objects in a map, delete the ones that were not reused
+function regenerate_objects(preserved, list, need, key, assign, create, destroy) {
+    if(!create) create = function(k, o) { };
+    if(!destroy) destroy = function(k) { };
+    var keep = {};
+    function wrap(o) {
+        var k = key(o);
+        if(!preserved[k])
+            create(k, preserved[k] = {}, o);
+        var o1 = preserved[k];
+        assign(o1, o);
+        keep[k] = true;
+        return o1;
+    }
+    var wlist = list.map(wrap);
+    if(need)
+        need.forEach(function(k) {
+            if(!preserved[k]) { // hasn't been created, needs to be
+                create(k, preserved[k] = {}, null);
+                assign(preserved[k], null);
+            }
+            if(!keep[k]) { // wasn't in list, should be
+                wlist.push(preserved[k]);
+                keep[k] = true;
+            }
+        });
+    // delete any objects from last round that are no longer used
+    for(var k in preserved)
+        if(!keep[k]) {
+            destroy(k, preserved[k]);
+            delete preserved[k];
+        }
+    return wlist;
+}
+
+function point_on_ellipse(A, B, dx, dy) {
+    var tansq = Math.tan(Math.atan2(dy, dx));
+    tansq = tansq*tansq; // why is this not just dy*dy/dx*dx ? ?
+    var ret = {x: A*B/Math.sqrt(B*B + A*A*tansq), y: A*B/Math.sqrt(A*A + B*B/tansq)};
+    if(dx<0)
+        ret.x = -ret.x;
+    if(dy<0)
+        ret.y = -ret.y;
+    return ret;
+}
+
+var eps = 0.0000001;
+function between(a, b, c) {
+    return a-eps <= b && b <= c+eps;
+}
+
+// Adapted from http://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect/1968345#1968345
+function segment_intersection(x1,y1,x2,y2, x3,y3,x4,y4) {
+    var x=((x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4)) /
+            ((x1-x2)*(y3-y4)-(y1-y2)*(x3-x4));
+    var y=((x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4)) /
+            ((x1-x2)*(y3-y4)-(y1-y2)*(x3-x4));
+    if (isNaN(x)||isNaN(y)) {
+        return false;
+    } else {
+        if (x1>=x2) {
+            if (!between(x2, x, x1)) {return false;}
+        } else {
+            if (!between(x1, x, x2)) {return false;}
+        }
+        if (y1>=y2) {
+            if (!between(y2, y, y1)) {return false;}
+        } else {
+            if (!between(y1, y, y2)) {return false;}
+        }
+        if (x3>=x4) {
+            if (!between(x4, x, x3)) {return false;}
+        } else {
+            if (!between(x3, x, x4)) {return false;}
+        }
+        if (y3>=y4) {
+            if (!between(y4, y, y3)) {return false;}
+        } else {
+            if (!between(y3, y, y4)) {return false;}
+        }
+    }
+    return {x: x, y: y};
+}
+
+
+function point_on_polygon(points, x0, y0, x1, y1) {
+    for(var i = 0; i < points.length; ++i) {
+        var next = i===points.length-1 ? 0 : i+1;
+        var isect = segment_intersection(points[i].x, points[i].y, points[next].x, points[next].y,
+                                         x0, y0, x1, y1);
+        if(isect)
+            return isect;
+    }
+    return null;
+}
+
+// as many as we can get from
+// http://www.graphviz.org/doc/info/shapes.html
+dc_graph.shape_presets = {
+    egg: {
+        // not really: an ovoid should be two half-ellipses stuck together
+        // https://en.wikipedia.org/wiki/Oval
+        generator: 'polygon',
+        preset: function() {
+            return {sides: 100, distortion: -0.25};
+        }
+    },
+    triangle: {
+        generator: 'polygon',
+        preset: function() {
+            return {sides: 3};
+        }
+    },
+    rectangle: {
+        generator: 'polygon',
+        preset: function() {
+            return {sides: 4};
+        }
+    },
+    diamond: {
+        generator: 'polygon',
+        preset: function() {
+            return {sides: 4, rotation: 45};
+        }
+    },
+    trapezium: {
+        generator: 'polygon',
+        preset: function() {
+            return {sides: 4, distortion: -0.5};
+        }
+    },
+    parallelogram: {
+        generator: 'polygon',
+        preset: function() {
+            return {sides: 4, skew: 0.5};
+        }
+    },
+    pentagon: {
+        generator: 'polygon',
+        preset: function() {
+            return {sides: 5};
+        }
+    },
+    hexagon: {
+        generator: 'polygon',
+        preset: function() {
+            return {sides: 6};
+        }
+    },
+    septagon: {
+        generator: 'polygon',
+        preset: function() {
+            return {sides: 7};
+        }
+    },
+    octagon: {
+        generator: 'polygon',
+        preset: function() {
+            return {sides: 8};
+        }
+    },
+    invtriangle: {
+        generator: 'polygon',
+        preset: function() {
+            return {sides: 3, rotation: 180};
+        }
+    },
+    invtrapezium: {
+        generator: 'polygon',
+        preset: function() {
+            return {sides: 4, distortion: 0.5};
+        }
+    },
+    square: {
+        generator: 'polygon',
+        preset: function() {
+            return {
+                sides: 4,
+                regular: true
+            };
+        }
+    },
+    plain: {
+        generator: 'rounded-rect',
+        preset: function() {
+            return {
+                noshape: true
+            };
+        }
+    },
+    house: {
+        generator: 'elaborated-rect',
+        preset: function() {
+            return {
+                get_points: function(rx, ry) {
+                    return [
+                        {x: rx, y: ry*2/3},
+                        {x: rx, y: -ry/2},
+                        {x: 0, y: -ry},
+                        {x: -rx, y: -ry/2},
+                        {x: -rx, y: ry*2/3}
+                    ];
+                },
+                minrx: 30
+            };
+        }
+    },
+    invhouse: {
+        generator: 'elaborated-rect',
+        preset: function() {
+            return {
+                get_points: function(rx, ry) {
+                    return [
+                        {x: rx, y: ry/2},
+                        {x: rx, y: -ry*2/3},
+                        {x: -rx, y: -ry*2/3},
+                        {x: -rx, y: ry/2},
+                        {x: 0, y: ry}
+                    ];
+                },
+                minrx: 30
+            };
+        }
+    },
+    rarrow: {
+        generator: 'elaborated-rect',
+        preset: function() {
+            return {
+                get_points: function(rx, ry) {
+                    return [
+                        {x: rx, y: ry},
+                        {x: rx, y: ry*1.5},
+                        {x: rx + ry*1.5, y: 0},
+                        {x: rx, y: -ry*1.5},
+                        {x: rx, y: -ry},
+                        {x: -rx, y: -ry},
+                        {x: -rx, y: ry}
+                    ];
+                },
+                minrx: 30
+            };
+        }
+    },
+    larrow: {
+        generator: 'elaborated-rect',
+        preset: function() {
+            return {
+                get_points: function(rx, ry) {
+                    return [
+                        {x: -rx, y: ry},
+                        {x: -rx, y: ry*1.5},
+                        {x: -rx - ry*1.5, y: 0},
+                        {x: -rx, y: -ry*1.5},
+                        {x: -rx, y: -ry},
+                        {x: rx, y: -ry},
+                        {x: rx, y: ry}
+                    ];
+                },
+                minrx: 30
+            };
+        }
+    },
+    rpromoter: {
+        generator: 'elaborated-rect',
+        preset: function() {
+            return {
+                get_points: function(rx, ry) {
+                    return [
+                        {x: rx, y: ry},
+                        {x: rx, y: ry*1.5},
+                        {x: rx + ry*1.5, y: 0},
+                        {x: rx, y: -ry*1.5},
+                        {x: rx, y: -ry},
+                        {x: -rx, y: -ry},
+                        {x: -rx, y: ry*1.5},
+                        {x: 0, y: ry*1.5},
+                        {x: 0, y: ry},
+                    ];
+                },
+                minrx: 30
+            };
+        }
+    },
+    lpromoter: {
+        generator: 'elaborated-rect',
+        preset: function() {
+            return {
+                get_points: function(rx, ry) {
+                    return [
+                        {x: -rx, y: ry},
+                        {x: -rx, y: ry*1.5},
+                        {x: -rx - ry*1.5, y: 0},
+                        {x: -rx, y: -ry*1.5},
+                        {x: -rx, y: -ry},
+                        {x: rx, y: -ry},
+                        {x: rx, y: ry*1.5},
+                        {x: 0, y: ry*1.5},
+                        {x: 0, y: ry}
+                    ];
+                },
+                minrx: 30
+            };
+        }
+    },
+    cds: {
+        generator: 'elaborated-rect',
+        preset: function() {
+            return {
+                get_points: function(rx, ry) {
+                    return [
+                        {x: rx, y: ry},
+                        {x: rx + ry, y: 0},
+                        {x: rx, y: -ry},
+                        {x: -rx, y: -ry},
+                        {x: -rx, y: ry}
+                    ];
+                },
+                minrx: 30
+            };
+        }
+    },
+};
+
+dc_graph.shape_presets.box = dc_graph.shape_presets.rect = dc_graph.shape_presets.rectangle;
+
+dc_graph.available_shapes = function() {
+    var shapes = Object.keys(dc_graph.shape_presets);
+    return shapes.slice(0, shapes.length-1); // not including polygon
+};
+
+var default_shape = {shape: 'ellipse'};
+
+function normalize_shape_def(diagram, n) {
+    var def = diagram.nodeShape.eval(n);
+    if(!def)
+        return default_shape;
+    if(typeof def === 'string')
+        return {shape: def};
+    return def;
+}
+
+function elaborate_shape(diagram, def) {
+    var shape = def.shape, def2 = Object.assign({}, def);
+    delete def2.shape;
+    if(shape === 'random') {
+        var available = dc_graph.available_shapes(); // could include diagram.shape !== ellipse, polygon
+        shape = available[Math.floor(Math.random()*available.length)];
+    }
+    else if(diagram.shape.enum().indexOf(shape) !== -1)
+        return diagram.shape(shape).elaborate({shape: shape}, def2);
+    if(!dc_graph.shape_presets[shape]) {
+        console.warn('unknown shape ', shape);
+        return default_shape;
+    }
+    var preset = dc_graph.shape_presets[shape].preset(def2);
+    preset.shape = dc_graph.shape_presets[shape].generator;
+    return diagram.shape(preset.shape).elaborate(preset, def2);
+}
+
+function infer_shape(diagram) {
+    return function(n) {
+        var def = normalize_shape_def(diagram, n);
+        n.dcg_shape = elaborate_shape(diagram, def);
+        n.dcg_shape.abstract = def;
+    };
+}
+
+function shape_changed(diagram) {
+    return function(n) {
+        var def = normalize_shape_def(diagram, n);
+        var old = n.dcg_shape.abstract;
+        if(def.shape !== old.shape)
+            return true;
+        else if(def.shape === 'polygon') {
+            return def.shape.sides !== old.sides || def.shape.skew !== old.skew ||
+                def.shape.distortion !== old.distortion || def.shape.rotation !== old.rotation;
+        }
+        else return false;
+    };
+}
+
+function node_label_padding(diagram, n) {
+    var nlp = diagram.nodeLabelPadding.eval(n);
+    if(typeof nlp === 'number' || typeof nlp === 'string')
+        return {x: +nlp, y: +nlp};
+    else return nlp;
+}
+
+function fit_shape(shape, diagram) {
+    return function(content) {
+        content.each(function(n) {
+            var bbox = null;
+            if((!shape.useTextSize || shape.useTextSize(n.dcg_shape)) && diagram.nodeFitLabel.eval(n)) {
+                bbox = getBBoxNoThrow(this);
+                bbox = {x: bbox.x, y: bbox.y, width: bbox.width, height: bbox.height};
+                var padding;
+                var content = diagram.nodeContent.eval(n);
+                if(content && diagram.content(content).padding)
+                    padding = diagram.content(content).padding(n);
+                else {
+                    var padding2 = node_label_padding(diagram, n);
+                    padding = {
+                        x: padding2.x*2,
+                        y: padding2.y*2
+                    };
+                }
+                bbox.width += padding.x;
+                bbox.height += padding.y;
+                n.bbox = bbox;
+            }
+            var r = 0, radii;
+            if(!shape.useRadius || shape.useRadius(n.dcg_shape))
+                r = diagram.nodeRadius.eval(n);
+            if(bbox && bbox.width && bbox.height || shape.useTextSize && !shape.useTextSize(n.dcg_shape))
+                radii = shape.calc_radii(n, r, bbox);
+            else
+                radii = {rx: r, ry: r};
+            n.dcg_rx = radii.rx;
+            n.dcg_ry = radii.ry;
+
+            var w = radii.rx*2, h = radii.ry*2;
+            // fixme: this is only consistent if regular || !squeeze
+            // but we'd need to calculate polygon first in order to find out
+            // (not a bad idea, just no time right now)
+            if(w<h) w = h;
+
+            if(!shape.usePaddingAndStroke || shape.usePaddingAndStroke(n.dcg_shape)) {
+                var pands = diagram.nodePadding.eval(n) + diagram.nodeStrokeWidth.eval(n);
+                w += pands;
+                h += pands;
+            }
+            n.cola.width = w;
+            n.cola.height = h;
+        });
+    };
+}
+
+function ellipse_attrs(diagram) {
+    return {
+        rx: function(n) { return n.dcg_rx; },
+        ry: function(n) { return n.dcg_ry; }
+    };
+}
+
+function polygon_attrs(diagram, n) {
+    return {
+        d: function(n) {
+            var rx = n.dcg_rx, ry = n.dcg_ry,
+                def = n.dcg_shape,
+                sides = def.sides || 4,
+                skew = def.skew || 0,
+                distortion = def.distortion || 0,
+                rotation = def.rotation || 0,
+                align = (sides%2 ? 0 : 0.5), // even-sided horizontal top, odd pointy top
+                angles = [];
+            rotation = rotation/360 + 0.25; // start at y axis not x
+            for(var i = 0; i<sides; ++i) {
+                var theta = -((i+align)/sides + rotation)*Math.PI*2; // svg is up-negative
+                angles.push({x: Math.cos(theta), y: Math.sin(theta)});
+            }
+            var yext = d3.extent(angles, function(theta) { return theta.y; });
+            if(def.regular)
+                rx = ry = Math.max(rx, ry);
+            else if(rx < ry && !def.squeeze)
+                rx = ry;
+            else
+                ry = ry / Math.min(-yext[0], yext[1]);
+            n.dcg_points = angles.map(function(theta) {
+                var x = rx*theta.x,
+                    y = ry*theta.y;
+                x *= 1 + distortion*((ry-y)/ry - 1);
+                x -= skew*y/2;
+                return {x: x, y: y};
+            });
+            return generate_path(n.dcg_points, 1, true);
+        }
+    };
+}
+
+function binary_search(f, a, b) {
+    var patience = 100;
+    if(f(a).val >= 0)
+        throw new Error("f(a) must be less than 0");
+    if(f(b).val <= 0)
+        throw new Error("f(b) must be greater than 0");
+    while(true) {
+        if(!--patience)
+            throw new Error("patience ran out");
+        var c = (a+b)/2,
+            f_c = f(c), fv = f_c.val;
+        if(Math.abs(fv) < 0.5)
+            return f_c;
+        if(fv > 0)
+            b = c;
+        else
+            a = c;
+    }
+}
+
+function draw_edge_to_shapes(diagram, e, sx, sy, tx, ty,
+                             neighbor, dir, offset, source_padding, target_padding) {
+    var deltaX, deltaY,
+        sp, tp, points, bezDegree,
+        headAng, retPath;
+    if(!neighbor) {
+        sp = e.sourcePort.pos;
+        tp = e.targetPort.pos;
+        if(!sp) sp = {x: 0, y: 0};
+        if(!tp) tp = {x: 0, y: 0};
+        points = [{
+            x: sx + sp.x,
+            y: sy + sp.y
+        }, {
+            x: tx + tp.x,
+            y: ty + tp.y
+        }];
+        bezDegree = 1;
+    }
+    else {
+        var p_on_s = function(node, ang) {
+            return diagram.shape(node.dcg_shape.shape).intersect_vec(node, Math.cos(ang)*1000, Math.sin(ang)*1000);
+        };
+        var compare_dist = function(node, port0, goal) {
+            return function(ang) {
+                var port = p_on_s(node, ang);
+                if(!port)
+                    return {
+                        port: {x: 0, y: 0},
+                        val: 0,
+                        ang: ang
+                    };
+                else
+                    return {
+                        port: port,
+                        val: Math.hypot(port.x - port0.x, port.y - port0.y) - goal,
+                        ang: ang
+                    };
+            };
+        };
+        var srcang = Math.atan2(neighbor.sourcePort.y, neighbor.sourcePort.x),
+            tarang = Math.atan2(neighbor.targetPort.y, neighbor.targetPort.x);
+        var bss, bst;
+
+        // don't like this but throwing is unacceptable
+        try {
+            bss = binary_search(compare_dist(e.source, neighbor.sourcePort, offset),
+                                srcang, srcang + 2 * dir * offset / source_padding);
+        }
+        catch(x) {
+            bss = {ang: srcang, port: neighbor.sourcePort};
+        }
+        try {
+            bst = binary_search(compare_dist(e.target, neighbor.targetPort, offset),
+                                tarang, tarang - 2 * dir * offset / source_padding);
+        }
+        catch(x) {
+            bst = {ang: tarang, port: neighbor.targetPort};
+        }
+
+        sp = bss.port;
+        tp = bst.port;
+        var sdist = Math.hypot(sp.x, sp.y),
+            tdist = Math.hypot(tp.x, tp.y),
+            c1dist = sdist+source_padding/2,
+            c2dist = tdist+target_padding/2;
+        var c1X = sx + c1dist * Math.cos(bss.ang),
+            c1Y = sy + c1dist * Math.sin(bss.ang),
+            c2X = tx + c2dist * Math.cos(bst.ang),
+            c2Y = ty + c2dist * Math.sin(bst.ang);
+        points = [
+            {x: sx + sp.x, y: sy + sp.y},
+            {x: c1X, y: c1Y},
+            {x: c2X, y: c2Y},
+            {x: tx + tp.x, y: ty + tp.y}
+        ];
+        bezDegree = 3;
+    }
+    return {
+        sourcePort: sp,
+        targetPort: tp,
+        points: points,
+        bezDegree: bezDegree
+    };
+}
+
+function is_one_segment(path) {
+    return path.bezDegree === 1 && path.points.length === 2 ||
+        path.bezDegree === 3 && path.points.length === 4;
+}
+
+function as_bezier3(path) {
+    var p = path.points;
+    if(path.bezDegree === 3) return p;
+    else if(path.bezDegree === 1)
+        return [
+            {
+                x: p[0].x,
+                y: p[0].y
+            },
+            {
+                x: p[0].x + (p[1].x - p[0].x)/3,
+                y: p[0].y + (p[1].y - p[0].y)/3
+            },
+            {
+                x: p[0].x + 2*(p[1].x - p[0].x)/3,
+                y: p[0].y + 2*(p[1].y - p[0].y)/3
+            },
+            {
+                x: p[1].x,
+                y: p[1].y
+            }
+        ];
+    else throw new Error('unknown bezDegree ' + path.bezDegree);
+}
+
+// from https://www.jasondavies.com/animated-bezier/
+function interpolate(d, p) {
+    var r = [];
+    for (var i=1; i<d.length; i++) {
+        var d0 = d[i-1], d1 = d[i];
+        r.push({x: d0.x + (d1.x - d0.x) * p, y: d0.y + (d1.y - d0.y) * p});
+    }
+    return r;
+}
+
+function getLevels(points, t_) {
+    var x = [points];
+    for (var i=1; i<points.length; i++) {
+        x.push(interpolate(x[x.length-1], t_));
+    }
+    return x;
+}
+
+// get a point on a bezier segment, where 0 <= t <= 1
+function bezier_point(points, t_) {
+    var q = getLevels(points, t_);
+    return q[q.length-1][0];
+}
+
+// from https://stackoverflow.com/questions/8369488/splitting-a-bezier-curve#8405756
+// somewhat redundant with the above but different objective
+function split_bezier(p, t) {
+    var x1 = p[0].x, y1 = p[0].y,
+        x2 = p[1].x, y2 = p[1].y,
+        x3 = p[2].x, y3 = p[2].y,
+        x4 = p[3].x, y4 = p[3].y,
+
+        x12 = (x2-x1)*t+x1,
+        y12 = (y2-y1)*t+y1,
+
+        x23 = (x3-x2)*t+x2,
+        y23 = (y3-y2)*t+y2,
+
+        x34 = (x4-x3)*t+x3,
+        y34 = (y4-y3)*t+y3,
+
+        x123 = (x23-x12)*t+x12,
+        y123 = (y23-y12)*t+y12,
+
+        x234 = (x34-x23)*t+x23,
+        y234 = (y34-y23)*t+y23,
+
+        x1234 = (x234-x123)*t+x123,
+        y1234 = (y234-y123)*t+y123;
+
+    return [
+        [{x: x1, y: y1}, {x: x12, y: y12}, {x: x123, y: y123}, {x: x1234, y: y1234}],
+        [{x: x1234, y: y1234}, {x: x234, y: y234}, {x: x34, y: y34}, {x: x4, y: y4}]
+    ];
+}
+function split_bezier_n(p, n) {
+    var ret = [];
+    while(n > 1) {
+        var parts = split_bezier(p, 1/n);
+        ret.push(parts[0][0], parts[0][1], parts[0][2]);
+        p = parts[1];
+        --n;
+    }
+    ret.push.apply(ret, p);
+    return ret;
+}
+
+// binary search for a point along a bezier that is a certain distance from one of the end points
+// return the bezier cut at that point.
+function chop_bezier(points, end, dist) {
+    var EPS = 0.1, dist2 = dist*dist;
+    var ref, dir, segment;
+    if(end === 'head') {
+        ref = points[points.length-1];
+        segment = points.slice(points.length-4);
+        dir = -1;
+    } else {
+        ref = points[0];
+        segment = points.slice(0, 4);
+        dir = 1;
+    }
+    var parts, d2, t = 0.5, dt = 0.5, dx, dy;
+    do {
+        parts = split_bezier(segment, t);
+        dx = ref.x - parts[1][0].x;
+        dy = ref.y - parts[1][0].y;
+        d2 = dx*dx + dy*dy;
+        dt /= 2;
+        if(d2 > dist2)
+            t -= dt*dir;
+        else
+            t += dt*dir;
+        //console.log('dist', dist, 'dir', dir, 'd', d, 't', t, 'dt', dt);
+    }
+    while(dt > 0.0000001 && Math.abs(d2 - dist2) > EPS);
+    points = points.slice();
+    if(end === 'head')
+        return points.slice(0, points.length-4).concat(parts[0]);
+    else
+        return parts[1].concat(points.slice(4));
+}
+
+function angle_between_points(p0, p1) {
+    return Math.atan2(p1.y - p0.y, p1.x - p0.x);
+}
+
+dc_graph.no_shape = function() {
+    var _shape = {
+        parent: property(null),
+        elaborate: function(preset, def) {
+            return Object.assign(preset, def);
+        },
+        useTextSize: function() { return false; },
+        useRadius: function() { return false; },
+        usePaddingAndStroke: function() { return false; },
+        intersect_vec: function(n, deltaX, deltaY) {
+            return {x: 0, y: 0};
+        },
+        calc_radii: function(n, ry, bbox) {
+            return {rx: 0, ry: 0};
+        },
+        create: function(nodeEnter) {
+        },
+        replace: function(nodeChanged) {
+        },
+        update: function(node) {
+        }
+    };
+    return _shape;
+};
+
+dc_graph.ellipse_shape = function() {
+    var _shape = {
+        parent: property(null),
+        elaborate: function(preset, def) {
+            return Object.assign(preset, def);
+        },
+        intersect_vec: function(n, deltaX, deltaY) {
+            return point_on_ellipse(n.dcg_rx, n.dcg_ry, deltaX, deltaY);
+        },
+        calc_radii: function(n, ry, bbox) {
+            // make sure we can fit height in r
+            ry = Math.max(ry, bbox.height/2 + 5);
+            var rx = bbox.width/2;
+
+            // solve (x/A)^2 + (y/B)^2) = 1 for A, with B=r, to fit text in ellipse
+            // http://stackoverflow.com/a/433438/676195
+            var y_over_B = bbox.height/2/ry;
+            rx = rx/Math.sqrt(1 - y_over_B*y_over_B);
+            rx = Math.max(rx, ry);
+
+            return {rx: rx, ry: ry};
+        },
+        create: function(nodeEnter) {
+            nodeEnter.insert('ellipse', ':first-child')
+                .attr('class', 'node-shape');
+        },
+        update: function(node) {
+            node.select('ellipse.node-shape')
+                .attr(ellipse_attrs(_shape.parent()));
+        }
+    };
+    return _shape;
+};
+
+dc_graph.polygon_shape = function() {
+    var _shape = {
+        parent: property(null),
+        elaborate: function(preset, def) {
+            return Object.assign(preset, def);
+        },
+        intersect_vec: function(n, deltaX, deltaY) {
+            return point_on_polygon(n.dcg_points, 0, 0, deltaX, deltaY);
+        },
+        calc_radii: function(n, ry, bbox) {
+            // make sure we can fit height in r
+            ry = Math.max(ry, bbox.height/2 + 5);
+            var rx = bbox.width/2;
+
+            // this is cribbed from graphviz but there is much i don't understand
+            // and any errors are mine
+            // https://github.com/ellson/graphviz/blob/6acd566eab716c899ef3c4ddc87eceb9b428b627/lib/common/shapes.c#L1996
+            rx = rx*Math.sqrt(2)/Math.cos(Math.PI/(n.dcg_shape.sides||4));
+
+            return {rx: rx, ry: ry};
+        },
+        create: function(nodeEnter) {
+            nodeEnter.insert('path', ':first-child')
+                .attr('class', 'node-shape');
+        },
+        update: function(node) {
+            node.select('path.node-shape')
+                .attr(polygon_attrs(_shape.parent()));
+        }
+    };
+    return _shape;
+};
+
+dc_graph.rounded_rectangle_shape = function() {
+    var _shape = {
+        parent: property(null),
+        elaborate: function(preset, def) {
+            preset = Object.assign({rx: 10, ry: 10}, preset);
+            return Object.assign(preset, def);
+        },
+        intersect_vec: function(n, deltaX, deltaY) {
+            var points = [
+                {x:  n.dcg_rx, y:  n.dcg_ry},
+                {x:  n.dcg_rx, y: -n.dcg_ry},
+                {x: -n.dcg_rx, y: -n.dcg_ry},
+                {x: -n.dcg_rx, y:  n.dcg_ry}
+            ];
+            return point_on_polygon(points, 0, 0, deltaX, deltaY); // not rounded
+        },
+        useRadius: function(shape) {
+            return !shape.noshape;
+        },
+        calc_radii: function(n, ry, bbox) {
+            var fity = bbox.height/2;
+            // fixme: fudge to make sure text is not too tall for node
+            if(!n.dcg_shape.noshape)
+                fity += 5;
+            return {
+                rx: bbox.width / 2,
+                ry: Math.max(ry, fity)
+            };
+        },
+        create: function(nodeEnter) {
+            nodeEnter.filter(function(n) {
+                return !n.dcg_shape.noshape;
+            }).insert('rect', ':first-child')
+                .attr('class', 'node-shape');
+        },
+        update: function(node) {
+            node.select('rect.node-shape')
+                .attr({
+                    x: function(n) {
+                        return -n.dcg_rx;
+                    },
+                    y: function(n) {
+                        return -n.dcg_ry;
+                    },
+                    width: function(n) {
+                        return 2*n.dcg_rx;
+                    },
+                    height: function(n) {
+                        return 2*n.dcg_ry;
+                    },
+                    rx: function(n) {
+                        return n.dcg_shape.rx + 'px';
+                    },
+                    ry: function(n) {
+                        return n.dcg_shape.ry + 'px';
+                    }
+                });
+        }
+    };
+    return _shape;
+};
+
+// this is not all that accurate - idea is that arrows, houses, etc, are rectangles
+// in terms of sizing, but elaborated drawing & clipping. refine until done.
+dc_graph.elaborated_rectangle_shape = function() {
+    var _shape = dc_graph.rounded_rectangle_shape();
+    _shape.intersect_vec = function(n, deltaX, deltaY) {
+        var points = n.dcg_shape.get_points(n.dcg_rx, n.dcg_ry);
+        return point_on_polygon(points, 0, 0, deltaX, deltaY);
+    };
+    delete _shape.useRadius;
+    var orig_radii = _shape.calc_radii;
+    _shape.calc_radii = function(n, ry, bbox) {
+        var ret = orig_radii(n, ry, bbox);
+        return {
+            rx: Math.max(ret.rx, n.dcg_shape.minrx),
+            ry: ret.ry
+        };
+    };
+    _shape.create = function(nodeEnter) {
+        nodeEnter.insert('path', ':first-child')
+            .attr('class', 'node-shape');
+    };
+    _shape.update = function(node) {
+        node.select('path.node-shape')
+            .attr('d', function(n) {
+                return generate_path(n.dcg_shape.get_points(n.dcg_rx, n.dcg_ry), 1, true);
+            });
+    };
+    return _shape;
+};
+
+
+function offsetx(ofsx) {
+    return function(p) {
+        return {x: p.x + ofsx, y: p.y};
+    };
+}
+
+dc_graph.builtin_arrows = {
+    box: function(open, side) {
+        if(!open) return {
+            frontRef: [8,0],
+            drawFunction: function(marker, ofs, stemWidth) {
+                marker.append('rect')
+                    .attr({
+                        x: ofs[0],
+                        y: side==='right' ? -stemWidth/2 : -4,
+                        width: 8,
+                        height: side ? 4+stemWidth/2 : 8,
+                        'stroke-width': 0
+                    });
+            }
+        };
+        else return {
+            frontRef: [8,0],
+            drawFunction: function(marker, ofs, stemWidth) {
+                marker.append('rect')
+                    .attr({
+                        x: ofs[0] + 0.5,
+                        y: side==='right' ? 0 : -3.5,
+                        width: 7,
+                        height: side ? 3.5 : 7,
+                        'stroke-width': 1,
+                        fill: 'none'
+                    });
+                if(side)
+                marker.append('svg:path')
+                    .attr({
+                        d: ['M', ofs[0], 0, 'h',8].join(' '),
+                        'stroke-width': stemWidth,
+                        fill: 'none'
+                    });
+            }
+        };
+    },
+    curve: function(open, side) {
+        return {
+            stems: [true,false],
+            kernstems: [0, 0.25],
+            frontRef: [8,0],
+            drawFunction: function(marker, ofs, stemWidth) {
+                var instrs = [];
+                instrs.push('M', (side==='left' ? 7.5 : 4) + ofs[0], side==='left' ? stemWidth/2 : 3.5);
+                if(side==='left')
+                    instrs.push('v', -stemWidth/2);
+                instrs.push('A', 3.5, 3.5, 0, 0, 0,
+                            (side==='right' ? 7.5 : 4) + ofs[0], side==='right' ? 0 : -3.5);
+                if(side==='right')
+                    instrs.push('v', -stemWidth/2);
+                marker.append('svg:path')
+                    .attr({
+                        d: instrs.join(' '),
+                        'stroke-width': 1,
+                        fill: 'none'
+                    });
+                marker.append('svg:path')
+                    .attr({
+                        d: ['M', 7 + ofs[0],  0,
+                            'h  -7'].join(' '),
+                        'stroke-width': stemWidth,
+                        fill: 'none'
+                    });
+            }
+        };
+    },
+    icurve: function(open, side) {
+        return {
+            stems: [false,true],
+            kernstems: [0.25,0],
+            frontRef: [8,0],
+            drawFunction: function(marker, ofs, stemWidth) {
+                var instrs = [];
+                instrs.push('M', (side==='left' ? 0.5 : 4) + ofs[0], side==='left' ? stemWidth/2 : 3.5);
+                if(side==='left')
+                    instrs.push('v', -stemWidth/2);
+                instrs.push('A', 3.5, 3.5, 0, 0, 1,
+                            (side==='right' ? 0.5 : 4) + ofs[0], side==='right' ? 0 : -3.5);
+                if(side==='right')
+                    instrs.push('v', -stemWidth/2);
+                marker.append('svg:path')
+                    .attr({
+                        d: instrs.join(' '),
+                        'stroke-width': 1,
+                        fill: 'none'
+                    });
+                marker.append('svg:path')
+                    .attr({
+                        d: ['M', 1 + ofs[0],  0,
+                            'h 7'].join(' '),
+                        'stroke-width': stemWidth,
+                        fill: 'none'
+                    });
+            }
+        };
+    },
+    diamond: function(open, side) {
+        if(!open) return {
+            frontRef: [side ? 11.25 : 12, 0],
+            backRef: [side ? 0.75 : 0, 0],
+            viewBox: [0, -4, 12, 8],
+            stems: [!!side, !!side],
+            kernstems: function(stemWidth) {
+                return [side ? 0 : .75*stemWidth, side ? 0 : .75*stemWidth];
+            },
+            drawFunction: function(marker, ofs, stemWidth) {
+                var upoints = [{x: 0, y: 0}];
+                if(side !== 'left')
+                    upoints.push({x: 6, y: 4});
+                else
+                    upoints.push({x: 6, y: -4});
+                upoints.push({x: 12, y: 0});
+                if(!side)
+                    upoints.push({x: 6, y: -4});
+                var points = upoints.map(offsetx(ofs[0]));
+                marker.append('svg:path')
+                    .attr({
+                        d: generate_path(points, 1, true),
+                        'stroke-width': 0
+                    });
+                if(side) {
+                    marker.append('svg:path')
+                        .attr({
+                            d: ['M', 0.75 + ofs[0],  0,
+                                'h 10.5'].join(' '),
+                            'stroke-width': stemWidth,
+                            fill: 'none'
+                        });
+                }
+            }
+        };
+        else return {
+            frontRef: [side ? 11.25 : 12, 0],
+            backRef: [side ? 0.75 : 0, 0],
+            viewBox: [0, -4, 12, 8],
+            stems: [!!side, !!side],
+            kernstems: function(stemWidth) {
+                return [side ? 0 : .75*stemWidth, side ? 0 : .75*stemWidth];
+            },
+            drawFunction: function(marker, ofs, stemWidth) {
+                var upoints = [{x: 0.9, y: 0}];
+                if(side !== 'left')
+                    upoints.push({x: 6, y: 3.4});
+                else
+                    upoints.push({x: 6, y: -3.4});
+                upoints.push({x: 11.1, y: 0});
+                if(!side)
+                    upoints.push({x: 6, y: -3.4});
+                var points = upoints.map(offsetx(ofs[0]));
+                marker.append('svg:path')
+                    .attr({
+                        d: generate_path(points, 1, !side),
+                        'stroke-width': 1,
+                        fill: 'none'
+                    });
+                if(side) {
+                    marker.append('svg:path')
+                        .attr({
+                            d: ['M', 0.75 + ofs[0],  0,
+                                'h 10.5'].join(' '),
+                            'stroke-width': stemWidth,
+                            fill: 'none'
+                        });
+                }
+            }
+        };
+    },
+    dot: function(open, side) {
+        if(!open) return {
+            frontRef: [8,0],
+            stems: [!!side, !!side],
+            drawFunction: function(marker, ofs, stemWidth) {
+                if(side) {
+                    marker.append('svg:path')
+                        .attr({
+                            d: ['M', ofs[0], 0,
+                                'A', 4, 4, 0, 0, side==='left'?1:0, 8 + ofs[0], 0].join(' '),
+                            'stroke-width': 0
+                        });
+                    marker.append('svg:path')
+                        .attr({
+                            d: ['M', ofs[0],  0,
+                                'h 8'].join(' '),
+                            'stroke-width': stemWidth,
+                            fill: 'none'
+                        });
+                }
+                else {
+                    marker.append('svg:circle')
+                        .attr('r', 4)
+                        .attr('cx', 4 + ofs[0])
+                        .attr('cy', 0)
+                        .attr('stroke-width', '0px');
+                }
+            }
+        };
+        else return {
+            frontRef: [8,0],
+            stems: [!!side, !!side],
+            drawFunction: function(marker, ofs, stemWidth) {
+                if(side) {
+                    marker.append('svg:path')
+                        .attr({
+                            d: ['M', 0.5 + ofs[0], 0,
+                                'A', 3.5, 3.5, 0, 0, side==='left'?1:0, 7.5 + ofs[0], 0].join(' '),
+                            'stroke-width': 1,
+                            fill: 'none'
+                        });
+                    marker.append('svg:path')
+                        .attr({
+                            d: ['M', ofs[0],  0,
+                                'h 8'].join(' '),
+                            'stroke-width': stemWidth,
+                            fill: 'none'
+                        });
+                } else {
+                    marker.append('svg:circle')
+                        .attr('r', 3.5)
+                        .attr('cx', 4 + ofs[0])
+                        .attr('cy', 0)
+                        .attr('fill', 'none')
+                        .attr('stroke-width', '1px');
+                }
+            }
+        };
+    },
+    normal: function(open, side) {
+        if(!open) return {
+            frontRef: [side ? 8-4/3 : 8, 0],
+            viewBox: [0, -3, 8, 6],
+            kernstems: function(stemWidth) {
+                return [0,stemWidth*4/3];
+            },
+            drawFunction: function(marker, ofs, stemWidth) {
+                var upoints = [];
+                if(side === 'left')
+                    upoints.push({x: 0, y: 0});
+                else
+                    upoints.push({x: 0, y: 3});
+                switch(side) {
+                case 'left':
+                    upoints.push({x: 8 - stemWidth*4/3, y: -stemWidth/2});
+                    break;
+                case 'right':
+                    upoints.push({x: 8 - stemWidth*4/3, y: stemWidth/2});
+                    break;
+                default:
+                    upoints.push({x: 8, y: 0});
+                }
+                if(side === 'right')
+                    upoints.push({x: 0, y: 0});
+                else
+                    upoints.push({x: 0, y: -3});
+                var points = upoints.map(offsetx(ofs[0]));
+                marker.append('svg:path')
+                    .attr('d', generate_path(points, 1, true))
+                    .attr('stroke-width', '0px');
+                if(side) {
+                    marker.append('svg:path')
+                        .attr({
+                            d: ['M', ofs[0],  0,
+                                'h', 8-4*stemWidth/3].join(' '),
+                            'stroke-width': stemWidth,
+                            fill: 'none'
+                        });
+                }
+            }
+        };
+        else return {
+            frontRef: [side ? 8-4/3 : 8, 0],
+            viewBox: [0, -3, 8, 6],
+            kernstems: function(stemWidth) {
+                return [0,stemWidth*4/3];
+            },
+            drawFunction: function(marker, ofs, stemWidth) {
+                var upoints = [];
+                if(!side) {
+                    upoints = [
+                        {x: 0.5, y: 2.28},
+                        {x: 6.57, y: 0},
+                        {x: 0.5, y: -2.28}
+                    ];
+                } else {
+                    upoints = [
+                        {x: 0.5, y: 0},
+                        {x: 0.5, y: side === 'left' ? -2.28 : 2.28},
+                        {x: 8-4/3, y: 0}
+                    ];
+                }
+                var points = upoints.map(offsetx(ofs[0]));
+                marker.append('svg:path')
+                    .attr({
+                        d: generate_path(points, 1, !side),
+                        'stroke-width': 1,
+                        fill: 'none'
+                    });
+                if(side) {
+                    marker.append('svg:path')
+                        .attr({
+                            d: ['M', ofs[0],  0,
+                                'h', 8-4/3].join(' '),
+                            'stroke-width': stemWidth,
+                            fill: 'none'
+                        });
+                }
+            }
+        };
+    },
+    inv: function(open, side) {
+        if(!open) return {
+            frontRef: [8,0],
+            backRef: [side ? 4/3 : 0, 0],
+            viewBox: [0, -3, 8, 6],
+            kernstems: function(stemWidth) {
+                return [stemWidth*4/3,0];
+            },
+            drawFunction: function(marker, ofs, stemWidth) {
+                var upoints = [];
+                if(side === 'left')
+                    upoints.push({x: 8, y: 0});
+                else
+                    upoints.push({x: 8, y: 3});
+                switch(side) {
+                case 'left':
+                    upoints.push({x: stemWidth*4/3, y: -stemWidth/2});
+                    break;
+                case 'right':
+                    upoints.push({x: stemWidth*4/3, y: stemWidth/2});
+                    break;
+                default:
+                    upoints.push({x: 0, y: 0});
+                }
+                if(side === 'right')
+                    upoints.push({x: 8, y: 0});
+                else
+                    upoints.push({x: 8, y: -3});
+                var points = upoints.map(offsetx(ofs[0]));
+                marker.append('svg:path')
+                    .attr('d', generate_path(points, 1, true))
+                    .attr('stroke-width', '0px');
+                if(side) {
+                    marker.append('svg:path')
+                        .attr({
+                            d: ['M', 4*stemWidth/3 + ofs[0],  0,
+                                'h', 8-4*stemWidth/3].join(' '),
+                            'stroke-width': stemWidth,
+                            fill: 'none'
+                        });
+                }
+            }
+        };
+        else return {
+            frontRef: [8,0],
+            backRef: [side ? 4/3 : 0, 0],
+            viewBox: [0, -3, 8, 6],
+            kernstems: function(stemWidth) {
+                return [stemWidth*4/3,0];
+            },
+            drawFunction: function(marker, ofs, stemWidth) {
+                var upoints = [];
+                if(!side) {
+                    upoints = [
+                        {x: 7.5, y: 2.28},
+                        {x: 1.43, y: 0},
+                        {x: 7.5, y: -2.28}
+                    ];
+                } else {
+                    upoints = [
+                        {x: 7.5, y: 0},
+                        {x: 7.5, y: side === 'left' ? -2.28 : 2.28},
+                        {x: 1.43, y: 0}
+                    ];
+                }
+                var points = upoints.map(offsetx(ofs[0]));
+                marker.append('svg:path')
+                    .attr({
+                        d: generate_path(points, 1, !side),
+                        'stroke-width': 1,
+                        fill: 'none'
+                    });
+                if(side) {
+                    marker.append('svg:path')
+                        .attr({
+                            d: ['M', 4*stemWidth/3 + ofs[0],  0,
+                                'h', 8-4/3].join(' '),
+                            'stroke-width': stemWidth,
+                            fill: 'none'
+                        });
+                }
+            }
+        };
+    },
+    tee: function(open, side) {
+        return {
+            frontRef: [5,0],
+            viewBox: [0, -5, 5, 10],
+            stems: [true,false],
+            drawFunction: function(marker, ofs, stemWidth) {
+                var b = side === 'right' ? 0 : -5,
+                    t = side === 'left' ? 0 : 5;
+                var points = [
+                    {x: 2, y: t},
+                    {x: 5, y: t},
+                    {x: 5, y: b},
+                    {x: 2, y: b}
+                ].map(offsetx(ofs[0]));
+                marker.append('svg:path')
+                    .attr('d', generate_path(points, 1, true))
+                    .attr('stroke-width', '0px');
+                marker.append('svg:path')
+                    .attr('d', ['M', ofs[0], 0, 'h', 5].join(' '))
+                    .attr('stroke-width', stemWidth)
+                    .attr('fill', 'none');
+            }
+        };
+    },
+    vee: function(open, side) {
+        return {
+            stems: [true,false],
+            kernstems: function(stemWidth) {
+                return [0,stemWidth];
+            },
+            drawFunction: function(marker, ofs, stemWidth) {
+                var upoints = [
+                    {x: 0, y: -5},
+                    {x: 10, y: 0},
+                    {x: 0, y: 5},
+                    {x: 5, y: 0}
+                ];
+                if(side==='right')
+                    upoints.splice(0, 1,
+                                  {x: 5, y: -stemWidth/2},
+                                  {x: 10, y: -stemWidth/2});
+                else if(side==='left')
+                    upoints.splice(2, 1,
+                                  {x: 10, y: stemWidth/2},
+                                  {x: 5, y: stemWidth/2});
+                var points = upoints.map(offsetx(ofs[0]));
+                marker.append('svg:path')
+                    .attr('d', generate_path(points, 1, true))
+                    .attr('stroke-width', '0px');
+                marker.append('svg:path')
+                    .attr('d', ['M', ofs[0]+5, 0, 'h',-5].join(' '))
+                    .attr('stroke-width', stemWidth);
+            }
+        };
+    },
+    crow: function(open, side) {
+        return {
+            stems: [false,true],
+            kernstems: function(stemWidth) {
+                return [stemWidth,0];
+            },
+            drawFunction: function(marker, ofs, stemWidth) {
+                var upoints = [
+                    {x: 10, y: -5},
+                    {x: 0, y: 0},
+                    {x: 10, y: 5},
+                    {x: 5, y: 0}
+                ];
+                if(side==='right')
+                    upoints.splice(0, 1,
+                                  {x: 5, y: -stemWidth/2},
+                                  {x: 0, y: -stemWidth/2});
+                else if(side==='left')
+                    upoints.splice(2, 1,
+                                  {x: 0, y: stemWidth/2},
+                                  {x: 5, y: stemWidth/2});
+                var points = upoints.map(offsetx(ofs[0]));
+                marker.append('svg:path')
+                    .attr('d', generate_path(points, 1, true))
+                    .attr('stroke-width', '0px');
+                marker.append('svg:path')
+                    .attr('d', ['M', ofs[0]+5, 0, 'h',5].join(' '))
+                    .attr('stroke-width', stemWidth);
+            }
+        };
+    }
+};
+
+function arrow_def(arrdefs, shape, open, side) {
+    return arrdefs[shape](open, side);
+}
+
+function arrow_parts(arrdefs, desc) {
+    // graphviz appears to use a real parser for this
+    var parts = [];
+    while(desc && desc.length) {
+        var mods = /^o?(?:l|r)?/.exec(desc);
+        var open = false, side = null;
+        if(mods[0]) {
+            mods = mods[0];
+            desc = desc.slice(mods.length);
+            open = mods[0] === 'o';
+            switch(mods[mods.length-1]) {
+            case 'l':
+                side='left';
+                break;
+            case 'r':
+                side='right';
+            }
+        }
+        var ok = false;
+        for(var aname in arrdefs)
+            if(desc.substring(0, aname.length) === aname) {
+                ok = true;
+                parts.push(arrow_def(arrdefs, aname, open, side));
+                desc = desc.slice(aname.length);
+                break;
+            }
+        if(!ok) {
+            console.warn("couldn't find arrow name in " + desc);
+            break;
+        }
+    }
+    return parts;
+}
+
+function union_viewbox(vb1, vb2) {
+    var left = Math.min(vb1[0], vb2[0]),
+        bottom = Math.min(vb1[1], vb2[1]),
+        right = Math.max(vb1[0] + vb1[2], vb2[0] + vb2[2]),
+        top = Math.max(vb1[1] + vb1[3], vb2[1] + vb2[3]);
+    return [left, bottom, right - left, top - bottom];
+}
+
+function subtract_points(p1, p2) {
+    return [p1[0] - p2[0], p1[1] - p2[1]];
+}
+
+function add_points(p1, p2) {
+    return [p1[0] + p2[0], p1[1] + p2[1]];
+}
+
+function mult_point(p, s) {
+    return p.map(function(x) { return x*s; });
+}
+
+function defaulted(def) {
+    return function(x) {
+        return x || def;
+    };
+}
+
+var view_box = defaulted([0, -5, 10, 10]),
+    front_ref = defaulted([10, 0]),
+    back_ref = defaulted([0, 0]);
+
+function arrow_offsets(parts, stemWidth) {
+    var frontRef = null, backRef = null;
+    return parts.map(function(p, i) {
+        var fr = front_ref(p.frontRef).slice(),
+            br = back_ref(p.backRef).slice();
+        if(p.kernstems) {
+            var kernstems = p.kernstems;
+            if(typeof kernstems === 'function')
+                kernstems = kernstems(stemWidth);
+            if(i !== 0 && kernstems[1]) {
+                var last = parts[i-1];
+                if(last.stems && last.stems[0])
+                    fr[0] -= kernstems[1];
+            }
+            if(kernstems[0]) {
+                var kern = false;
+                if(i === parts.length-1)
+                    kern = true;
+                else {
+                    var next = parts[i+1];
+                    if(next.stems && next.stems[1])
+                        kern = true;
+                }
+                if(kern)
+                    br[0] += kernstems[0];
+            }
+        }
+        if(i === 0) {
+            frontRef = fr;
+            backRef = br;
+            return {backRef: backRef, offset: [0, 0]};
+        } else {
+            var ofs = subtract_points(backRef, fr);
+            backRef = add_points(br, ofs);
+            return {backRef: backRef, offset: ofs};
+        }
+    });
+}
+
+function arrow_bounds(parts, stemWidth) {
+    var viewBox = null, offsets = arrow_offsets(parts, stemWidth);
+    parts.forEach(function(p, i) {
+        var vb = view_box(p.viewBox);
+        var ofs = offsets[i].offset;
+        if(!viewBox)
+            viewBox = vb.slice();
+        else
+            viewBox = union_viewbox(viewBox, [vb[0] + ofs[0], vb[1] + ofs[1], vb[2], vb[3]]);
+    });
+    return {offsets: offsets, viewBox: viewBox};
+}
+
+function arrow_length(parts, stemWidth) {
+    if(!parts.length)
+        return 0;
+    var offsets = arrow_offsets(parts, stemWidth);
+    return front_ref(parts[0].frontRef)[0] - offsets[parts.length-1].backRef[0];
+}
+
+
+function scaled_arrow_lengths(diagram, e) {
+    var arrowSize = diagram.edgeArrowSize.eval(e),
+        stemWidth = diagram.edgeStrokeWidth.eval(e) / arrowSize;
+    var headLength = arrowSize *
+        (arrow_length(arrow_parts(diagram.arrows(), diagram.edgeArrowhead.eval(e)), stemWidth) +
+         diagram.nodeStrokeWidth.eval(e.target) / 2),
+        tailLength = arrowSize *
+        (arrow_length(arrow_parts(diagram.arrows(), diagram.edgeArrowtail.eval(e)), stemWidth) +
+         diagram.nodeStrokeWidth.eval(e.source) / 2);
+    return {headLength: headLength, tailLength: tailLength};
+}
+
+function clip_path_to_arrows(headLength, tailLength, path) {
+    var points0 = as_bezier3(path),
+        points = chop_bezier(points0, 'head', headLength);
+    return {
+        bezDegree: 3,
+        points: chop_bezier(points, 'tail', tailLength),
+        sourcePort: path.sourcePort,
+        targetPort: path.targetPort
+    };
+}
+
+function place_arrows_on_spline(diagram, e, points) {
+    var alengths = scaled_arrow_lengths(diagram, e);
+    var path0 = {
+        points: points,
+        bezDegree: 3
+    };
+    var path = clip_path_to_arrows(alengths.headLength, alengths.tailLength, path0);
+    return {
+        path: path,
+        full: path0,
+        orienthead: angle_between_points(path.points[path.points.length-1], path0.points[path0.points.length-1]) + 'rad', //calculate_arrowhead_orientation(e.cola.points, 'head'),
+        orienttail: angle_between_points(path.points[0], path0.points[0]) + 'rad' //calculate_arrowhead_orientation(e.cola.points, 'tail')
+    };
+}
+
+
+// determine pre-transition orientation that won't spin a lot going to new orientation
+function unsurprising_orient(oldorient, neworient) {
+    var oldang = +oldorient.slice(0, -3),
+        newang = +neworient.slice(0, -3);
+    if(Math.abs(oldang - newang) > Math.PI) {
+        if(newang > oldang)
+            oldang += 2*Math.PI;
+        else oldang -= 2*Math.PI;
+    }
+    return oldang;
+}
+
+
+function edgeArrow(diagram, arrdefs, e, kind, desc) {
+    var id = diagram.arrowId(e, kind);
+    var strokeOfs, edgeStroke;
+    function arrow_sig() {
+        return desc + '-' + strokeOfs + '-' + edgeStroke;
+    }
+    if(desc) {
+        strokeOfs = diagram.nodeStrokeWidth.eval(kind==='tail' ? e.source : e.target)/2;
+        edgeStroke = diagram.edgeStroke.eval(e);
+        if(e[kind + 'ArrowLast'] === arrow_sig())
+            return id;
+    }
+    var parts = arrow_parts(arrdefs, desc),
+        marker = diagram.addOrRemoveDef(id, !!parts.length, 'svg:marker');
+
+    if(parts.length) {
+        var arrowSize = diagram.edgeArrowSize.eval(e),
+            stemWidth = diagram.edgeStrokeWidth.eval(e) / arrowSize,
+            bounds = arrow_bounds(parts, stemWidth),
+            frontRef = front_ref(parts[0].frontRef);
+        bounds.viewBox[0] -= strokeOfs/arrowSize;
+        bounds.viewBox[3] += strokeOfs/arrowSize;
+        marker
+            .attr('viewBox', bounds.viewBox.join(' '))
+            .attr('refX', frontRef[0])
+            .attr('refY', frontRef[1])
+            .attr('markerUnits', 'userSpaceOnUse')
+            .attr('markerWidth', bounds.viewBox[2]*arrowSize)
+            .attr('markerHeight', bounds.viewBox[3]*arrowSize)
+            .attr('stroke', edgeStroke)
+            .attr('fill', edgeStroke);
+        marker.html(null);
+        parts.forEach(function(p, i) {
+            marker
+                .call(p.drawFunction,
+                      add_points([-strokeOfs/arrowSize,0], bounds.offsets[i].offset),
+                      stemWidth);
+        });
+    }
+    e[kind + 'ArrowLast'] = arrow_sig();
+    return desc ? id : null;
+}
+
+dc_graph.text_contents = function() {
+    var _contents = {
+        parent: property(null),
+        update: function(container) {
+            var text = container.selectAll('text.node-label')
+                    .data(function(n) { return [n]; });
+            text.enter().append('text')
+                .attr('class', 'node-label');
+            var tspan = text.selectAll('tspan').data(function(n) {
+                var lines = _contents.parent().nodeLabel.eval(n);
+                if(!lines)
+                    return [];
+                else if(typeof lines === 'string')
+                    lines = [lines];
+                var lineHeight = _contents.parent().nodeLineHeight();
+                var first = 0.5 - ((lines.length - 1) * lineHeight + 1)/2;
+                // IE, Edge, and Safari do not seem to support
+                // dominant-baseline: central although they say they do
+                if(is_ie() || is_safari())
+                    first += 0.3;
+                return lines.map(function(line, i) { return {node: n, line: line, yofs: (i==0 ? first : lineHeight) + 'em'}; });
+            });
+            tspan.enter().append('tspan');
+            tspan.attr({
+                'text-anchor': 'start',
+                'text-decoration': function(line) {
+                    return _contents.parent().nodeLabelDecoration.eval(line.node);
+                },
+                x: 0
+            }).html(function(s) { return s.line; });
+            text
+                .each(function(n) {
+                    n.xofs = 0;
+                })
+                .filter(function(n) {
+                    return _contents.parent().nodeLabelAlignment.eval(n) !== 'center';
+                })
+                .each(function(n) {
+                    var bbox = getBBoxNoThrow(this);
+                    n.bbox = {x: bbox.x, y: bbox.y, width: bbox.width, height: bbox.height};
+                    switch(_contents.parent().nodeLabelAlignment.eval(n)) {
+                    case 'left': n.xofs = -n.bbox.width/2;
+                        break;
+                    case 'right': n.xofs = n.bbox.width/2;
+                        break;
+                    }
+                })
+                .selectAll('tspan');
+            tspan.attr({
+                'text-anchor': function(s) {
+                    switch(_contents.parent().nodeLabelAlignment.eval(s.node)) {
+                    case 'left': return 'start';
+                    case 'center': return 'middle';
+                    case 'right': return 'end';
+                    }
+                    return null;
+                },
+                x: function(s) {
+                    return s.node.xofs;
+                },
+                dy: function(d) { return d.yofs; }
+            });
+
+            tspan.exit().remove();
+            text
+                .attr('fill', _contents.parent().nodeLabelFill.eval);
+        },
+        textbox: function(container) {
+            var bbox = getBBoxNoThrow(this.selectContent(container).node());
+            return {x: bbox.x, y: bbox.y, width: bbox.width, height: bbox.height};
+        },
+        selectContent: function(container) {
+            return container.select('text.node-label');
+        },
+        selectText: function(container) {
+            return this.selectContent(container);
+        }
+    };
+    return _contents;
+};
+
+dc_graph.with_icon_contents = function(contents, width, height) {
+    var _contents = {
+        parent: property(null).react(function(parent) {
+            contents.parent(parent);
+        }),
+        padding: function(n) {
+            var padding = node_label_padding(_contents.parent(), n);
+            return {
+                x: padding.x * 3,
+                y: padding.y * 3
+            };
+        },
+        update: function(container) {
+            var g = container.selectAll('g.with-icon')
+                    .data(function(n) { return [n]; });
+            var gEnter = g.enter();
+            gEnter.append('g')
+                .attr('class', 'with-icon')
+              .append('image').attr({
+                class: 'icon',
+                width: width + 'px',
+                height: height + 'px'
+            });
+            g.call(contents.update);
+            contents.selectContent(g)
+                .attr('transform',  'translate(' + width/2 + ')');
+            g.selectAll('image.icon').attr({
+                href: _contents.parent().nodeIcon.eval,
+                x: function(n) {
+                    var totwid = width + contents.textbox(d3.select(this.parentNode)).width;
+                    return -totwid/2 - node_label_padding(_contents.parent(), n).x;
+                },
+                y: -height/2
+            });
+        },
+        textbox: function(container) {
+            var box = contents.textbox(container);
+            box.x += width/2;
+            return box;
+        },
+        selectContent: function(container) {
+            return container.select('g.with-icon');
+        },
+        selectText: function(container) {
+            return this.selectContent(container).select('text.node-label');
+        }
+    };
+    return _contents;
+};
+
+
+/**
+ * `dc_graph.diagram` is a dc.js-compatible network visualization component. It registers in
+ * the dc.js chart registry and its nodes and edges are generated from crossfilter groups. It
+ * logically derives from the dc.js
+ * {@link https://github.com/dc-js/dc.js/blob/develop/web/docs/api-latest.md#dc.baseMixin baseMixin},
+ * but it does not physically derive from it since so much is different about network
+ * visualization versus conventional charts.
+ * @class diagram
+ * @memberof dc_graph
+ * @param {String|node} parent - Any valid
+ * {@link https://github.com/mbostock/d3/wiki/Selections#selecting-elements d3 single selector}
+ * specifying a dom block element such as a div; or a dom element.
+ * @param {String} [chartGroup] - The name of the dc.js chart group this diagram instance
+ * should be placed in. Filter interaction with a diagram will only trigger events and redraws
+ * within the diagram's group.
+ * @return {dc_graph.diagram}
+ **/
+dc_graph.diagram = function (parent, chartGroup) {
+    // different enough from regular dc charts that we don't use dc.baseMixin
+    // but attempt to implement most of that interface, copying some of the most basic stuff
+    var _diagram = dc.marginMixin({});
+    _diagram.__dcFlag__ = dc.utils.uniqueId();
+    _diagram.margins({left: 10, top: 10, right: 10, bottom: 10});
+    var _dispatch = d3.dispatch('preDraw', 'data', 'end', 'start', 'render', 'drawn', 'receivedLayout', 'transitionsStarted', 'zoomed', 'reset');
+    var _nodes = {}, _edges = {}; // hold state between runs
+    var _ports = {}; // id = node|edge/id/name
+    var _clusters = {};
+    var _nodePorts; // ports sorted by node id
+    var _stats = {};
+    var _nodes_snapshot, _edges_snapshot;
+    var _arrows = {};
+    var _running = false; // for detecting concurrency issues
+    var _anchor, _chartGroup;
+    var _animateZoom;
+
+    var _minWidth = 200;
+    var _defaultWidthCalc = function (element) {
+        var width = element && element.getBoundingClientRect && element.getBoundingClientRect().width;
+        return (width && width > _minWidth) ? width : _minWidth;
+    };
+    var _widthCalc = _defaultWidthCalc;
+
+    var _minHeight = 200;
+    var _defaultHeightCalc = function (element) {
+        var height = element && element.getBoundingClientRect && element.getBoundingClientRect().height;
+        return (height && height > _minHeight) ? height : _minHeight;
+    };
+    var _heightCalc = _defaultHeightCalc;
+    var _width, _height, _lastWidth, _lastHeight;
+
+    function deprecate_layout_algo_parameter(name) {
+        return function(value) {
+            if(!_diagram.layoutEngine())
+                _diagram.layoutAlgorithm('cola', true);
+            var engine = _diagram.layoutEngine();
+            if(engine.getEngine)
+                engine = engine.getEngine();
+            if(engine[name]) {
+                console.warn('property is deprecated, call on layout engine instead: dc_graph.diagram.%c' + name,
+                             'font-weight: bold');
+                if(!arguments.length)
+                    return engine[name]();
+                engine[name](value);
+            } else {
+                console.warn('property is deprecated, and is not supported for Warning: dc_graph.diagram.<b>' + name + '</b> is deprecated, and it is not supported for the "' + engine.layoutAlgorithm() + '" layout algorithm: ignored.');
+                if(!arguments.length)
+                    return null;
+            }
+            return this;
+        };
+    }
+
+    /**
+     * Set or get the height attribute of the diagram. If a value is given, then the diagram is
+     * returned for method chaining. If no value is given, then the current value of the height
+     * attribute will be returned.
+     *
+     * The width and height are applied to the SVG element generated by the diagram on render, or
+     * when `resizeSvg` is called.
+     *
+     * If the value is falsy or a function, the height will be calculated the first time it is
+     * needed, using the provided function or default height calculator, and then cached. The
+     * default calculator uses the client rect of the element specified when constructing the chart,
+     * with a minimum of `minHeight`. A custom calculator will be passed the element.
+     *
+     * If the value is `'auto'`, the height will be calculated every time the diagram is drawn, and
+     * it will not be set on the `<svg>` element. Instead, the element will be pinned to the same
+     * rectangle as its containing div using CSS.
+     *
+     * @method height
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Number} [height=200]
+     * @return {Number}
+     * @return {dc_graph.diagram}
+      **/
+    _diagram.height = function (height) {
+        if (!arguments.length) {
+            if (!dc.utils.isNumber(_height)) {
+                _lastHeight = _heightCalc(_diagram.root().node());
+                if(_height === 'auto') // 'auto' => calculate every time
+                    return _lastHeight;
+                // null/undefined => calculate once only
+                _height = _lastHeight;
+            }
+            return _height;
+        }
+        if(dc.utils.isNumber(height) || !height || height === 'auto')
+            _height = height;
+        else if(typeof height === 'function') {
+            _heightCalc = height;
+            _height = undefined;
+        }
+        else throw new Error("don't know what to do with height type " + typeof height + " value " + height);
+        return _diagram;
+    };
+    _diagram.minHeight = function(height) {
+        if(!arguments.length)
+            return _minHeight;
+        _minHeight = height;
+        return _diagram;
+    };
+    /**
+     * Set or get the width attribute of the diagram. If a value is given, then the diagram is
+     * returned for method chaining. If no value is given, then the current value of the width
+     * attribute will be returned.
+     *
+     * The width and height are applied to the SVG element generated by the diagram on render, or
+     * when `resizeSvg` is called.
+     *
+     * If the value is falsy or a function, the width will be calculated the first time it is
+     * needed, using the provided function or default width calculator, and then cached. The default
+     * calculator uses the client rect of the element specified when constructing the chart, with a
+     * minimum of `minWidth`. A custom calculator will be passed the element.
+     *
+     * If the value is `'auto'`, the width will be calculated every time the diagram is drawn, and
+     * it will not be set on the `<svg>` element. Instead, the element will be pinned to the same
+     * rectangle as its containing div using CSS.
+     *
+     * @method width
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Number} [width=200]
+     * @return {Number}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.width = function (width) {
+        if (!arguments.length) {
+            if (!dc.utils.isNumber(_width)) {
+                _lastWidth = _widthCalc(_diagram.root().node());
+                if(_width === 'auto') // 'auto' => calculate every time
+                    return _lastWidth;
+                // null/undefined => calculate once only
+                _width = _lastWidth;
+            }
+            return _width;
+        }
+        if(dc.utils.isNumber(width) || !width || width === 'auto')
+            _width = width;
+        else if(typeof width === 'function') {
+            _widthCalc = width;
+            _width = undefined;
+        }
+        else throw new Error("don't know what to do with width type " + typeof width + " value " + width);
+        return _diagram;
+    };
+    _diagram.minWidth = function(width) {
+        if(!arguments.length)
+            return _minWidth;
+        _minWidth = width;
+        return _diagram;
+    };
+
+    /**
+     * Get or set the root element, which is usually the parent div. Normally the root is set
+     * when the diagram is constructed; setting it later may have unexpected consequences.
+     * @method root
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {node} [root=null]
+     * @return {node}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.root = property(null).react(function(e) {
+        if(e.empty())
+            console.log('Warning: parent selector ' + parent + " doesn't seem to exist");
+    });
+
+    /**
+     * Get or set whether mouse wheel rotation or touchpad gestures will zoom the diagram, and
+     * whether dragging on the background pans the diagram.
+     * @method mouseZoomable
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Boolean} [mouseZoomable=true]
+     * @return {Boolean}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.mouseZoomable = property(true);
+
+    _diagram.zoomExtent = property([.1, 2]);
+
+    /**
+     * Whether zooming should only be enabled when the alt key is pressed.
+     * @method altKeyZoom
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Boolean} [altKeyZoom=true]
+     * @return {Boolean}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.modKeyZoom = _diagram.altKeyZoom = property(false);
+
+    /**
+     * Set or get the fitting strategy for the canvas, which affects how the translate
+     * and scale get calculated when `autoZoom` is triggered.
+     *
+     * * `'default'` - simulates the preserveAspectRatio behavior of `xMidYMid meet`, but
+     *   with margins - the content is stretched or squished in the more constrained
+     *   direction, and centered in the other direction
+     * * `'vertical'` - fits the canvas vertically (with vertical margins) and centers
+     *   it horizontally. If the canvas is taller than the viewport, it will meet
+     *   vertically and there will be blank areas to the left and right. If the canvas
+     *   is wider than the viewport, it will be sliced.
+     * * `'horizontal'` - fits the canvas horizontally (with horizontal margins) and
+     *   centers it vertically. If the canvas is wider than the viewport, it will meet
+     *   horizontally and there will be blank areas above and below. If the canvas is
+     *   taller than the viewport, it will be sliced.
+     *
+     * Other options
+     * * `null` - no attempt is made to fit the content in the viewport
+     * * `'zoom'` - does not scale the content, but attempts to bring as much content
+     *   into view as possible, using using the same algorithm as `restrictPan`
+     * * `'align_{tlbrc}[2]'` - does not scale; aligns up to two sides or centers them
+     * @method fitStrategy
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {String} [fitStrategy='default']
+     * @return {String}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.fitStrategy = property('default');
+
+    /**
+     * Do not allow panning (scrolling) to push the diagram out of the viewable area, if there
+     * is space for it to be shown. */
+    _diagram.restrictPan = property(false);
+
+    /**
+     * Auto-zoom behavior.
+     * * `'always'` - zoom every time layout happens
+     * * `'once'` - zoom the next time layout happens
+     * * `null` - manual, call `zoomToFit` to fit
+     * @method autoZoom
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {String} [autoZoom=null]
+     * @return {String}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.autoZoom = property(null);
+    _diagram.zoomToFit = function(animate) {
+        // if(!(_nodeLayer && _edgeLayer))
+        //     return;
+        auto_zoom(animate);
+    };
+    _diagram.zoomDuration = property(500);
+
+    /**
+     * Set or get the crossfilter dimension which represents the nodes (vertices) in the
+     * diagram. Typically there will be a crossfilter instance for the nodes, and another for
+     * the edges.
+     *
+     * *Dimensions are included on the diagram for similarity to dc.js, however the diagram
+     * itself does not use them - but {@link dc_graph.filter_selection filter_selection} will.*
+     * @method nodeDimension
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {crossfilter.dimension} [nodeDimension]
+     * @return {crossfilter.dimension}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.nodeDimension = property();
+
+    /**
+     * Set or get the crossfilter group which is the data source for the nodes in the
+     * diagram. The diagram will use the group's `.all()` method to get an array of `{key,
+     * value}` pairs, where the key is a unique identifier, and the value is usually an object
+     * containing the node's attributes. All accessors work with these key/value pairs.
+     *
+     * If the group is changed or returns different values, the next call to `.redraw()` will
+     * reflect the changes incrementally.
+     *
+     * It is possible to pass another object with the same `.all()` interface instead of a
+     * crossfilter group.
+     * @method nodeGroup
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {crossfilter.group} [nodeGroup]
+     * @return {crossfilter.group}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.nodeGroup = property();
+
+    /**
+     * Set or get the crossfilter dimension which represents the edges in the
+     * diagram. Typically there will be a crossfilter instance for the nodes, and another for
+     * the edges.
+     *
+     * *Dimensions are included on the diagram for similarity to dc.js, however the diagram
+     * itself does not use them - but {@link dc_graph.filter_selection filter_selection} will.*
+     * @method edgeDimension
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {crossfilter.dimension} [edgeDimension]
+     * @return {crossfilter.dimension}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.edgeDimension = property();
+
+    /**
+     * Set or get the crossfilter group which is the data source for the edges in the
+     * diagram. See `.nodeGroup` above for the way data is loaded from a crossfilter group.
+     *
+     * The values in the key/value pairs returned by `diagram.edgeGroup().all()` need to
+     * support, at a minimum, the {@link dc_graph.diagram#nodeSource nodeSource} and
+     * {@link dc_graph.diagram#nodeTarget nodeTarget}, which should return the same
+     * keys as the {@link dc_graph.diagram#nodeKey nodeKey}
+     *
+     * @method edgeGroup
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {crossfilter.group} [edgeGroup]
+     * @return {crossfilter.group}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.edgeGroup = property();
+
+    _diagram.edgesInFront = property(false);
+
+    /**
+     * Set or get the function which will be used to retrieve the unique key for each node. By
+     * default, this accesses the `key` field of the object passed to it. The keys should match
+     * the keys returned by the {@link dc_graph.diagram#edgeSource edgeSource} and
+     * {@link dc_graph.diagram#edgeTarget edgeTarget}.
+     *
+     * @method nodeKey
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function} [nodeKey=function(kv) { return kv.key }]
+     * @return {Function}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.nodeKey = _diagram.nodeKeyAccessor = property(function(kv) {
+        return kv.key;
+    });
+
+    /**
+     * Set or get the function which will be used to retrieve the unique key for each edge. By
+     * default, this accesses the `key` field of the object passed to it.
+     *
+     * @method edgeKey
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function} [edgeKey=function(kv) { return kv.key }]
+     * @return {Function}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.edgeKey = _diagram.edgeKeyAccessor = property(function(kv) {
+        return kv.key;
+    });
+
+    /**
+     * Set or get the function which will be used to retrieve the source (origin/tail) key of
+     * the edge objects.  The key must equal the key returned by the `.nodeKey` for one of the
+     * nodes; if it does not, or if the node is currently filtered out, the edge will not be
+     * displayed. By default, looks for `.value.sourcename`.
+     *
+     * @method edgeSource
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function} [edgeSource=function(kv) { return kv.value.sourcename; }]
+     * @return {Function}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.edgeSource = _diagram.sourceAccessor = property(function(kv) {
+        return kv.value.sourcename;
+    });
+
+    /**
+     * Set or get the function which will be used to retrieve the target (destination/head) key
+     * of the edge objects.  The key must equal the key returned by the
+     * {@link dc_graph.diagram#nodeKey nodeKey} for one of the nodes; if it does not, or if the node
+     * is currently filtered out, the edge will not be displayed. By default, looks for
+     * `.value.targetname`.
+     * @method edgeTarget
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function} [edgeTarget=function(kv) { return kv.value.targetname; }]
+     * @return {Function}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.edgeTarget = _diagram.targetAccessor = property(function(kv) {
+        return kv.value.targetname;
+    });
+
+    _diagram.portDimension = property(null);
+    _diagram.portGroup = property(null);
+    _diagram.portNodeKey = property(null);
+    _diagram.portEdgeKey = property(null);
+    _diagram.portName = property(null);
+    _diagram.portStyleName = property(null);
+    _diagram.portElastic = property(true);
+
+    _diagram.portStyle = named_children();
+
+    _diagram.portBounds = property(null); // position limits, in radians
+
+    _diagram.edgeSourcePortName = property(null);
+    _diagram.edgeTargetPortName = property(null);
+
+    /**
+     * Set or get the crossfilter dimension which represents the edges in the
+     * diagram. Typically there will be a crossfilter instance for the nodes, and another for
+     * the edges.
+     *
+     * *As with node and edge dimensions, the diagram will itself not filter on cluster dimensions;
+     * this is included for symmetry, and for modes which may want to filter clusters.*
+     * @method clusterDimension
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {crossfilter.dimension} [clusterDimension]
+     * @return {crossfilter.dimension}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.clusterDimension = property(null);
+
+    /**
+     * Set or get the crossfilter group which is the data source for clusters in the
+     * diagram.
+     *
+     * The key/value pairs returned by `diagram.clusterGroup().all()` need to support, at a minimum,
+     * the {@link dc_graph.diagram#clusterKey clusterKey} and {@link dc_graph.diagram#clusterParent clusterParent}
+     * accessors, which should return keys in this group.
+     *
+     * @method clusterGroup
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {crossfilter.group} [clusterGroup]
+     * @return {crossfilter.group}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.clusterGroup = property(null);
+
+    // cluster accessors
+    /**
+     * Set or get the function which will be used to retrieve the unique key for each cluster. By
+     * default, this accesses the `key` field of the object passed to it.
+     *
+     * @method clusterKey
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function} [clusterKey=function(kv) { return kv.key }]
+     * @return {Function}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.clusterKey = property(dc.pluck('key'));
+
+    /**
+     * Set or get the function which will be used to retrieve the key of the parent of a cluster,
+     * which is another cluster.
+     *
+     * @method clusterParent
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function} [clusterParent=function(kv) { return kv.key }]
+     * @return {Function}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.clusterParent = property(null);
+
+    /**
+     * Set or get the function which will be used to retrieve the padding, in pixels, around a cluster.
+     *
+     * **To be implemented.** If a single value is returned, it will be used on all sides; if two
+     * values are returned they will be interpreted as the vertical and horizontal padding.
+     *
+     * @method clusterPadding
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function} [clusterPadding=function(kv) { return kv.key }]
+     * @return {Function}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.clusterPadding = property(8);
+
+    // node accessor
+    /**
+     * Set or get the function which will be used to retrieve the parent cluster of a node, or
+     * `null` if the node is not in a cluster.
+     *
+     * @method nodeParentCluster
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function} [nodeParentCluster=function(kv) { return kv.key }]
+     * @return {Function}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.nodeParentCluster = property(null);
+
+    /**
+     * Set or get the function which will be used to retrieve the radius, in pixels, for each
+     * node. This determines the height of nodes,and if `nodeFitLabel` is false, the width too.
+     * @method nodeRadius
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|Number} [nodeRadius=25]
+     * @return {Function|Number}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.nodeRadius = _diagram.nodeRadiusAccessor = property(25);
+
+    /**
+     * Set or get the function which will be used to retrieve the stroke width, in pixels, for
+     * drawing the outline of each node. According to the SVG specification, the outline will
+     * be drawn half on top of the fill, and half outside. Default: 1
+     * @method nodeStrokeWidth
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|Number} [nodeStrokeWidth=1]
+     * @return {Function|Number}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.nodeStrokeWidth = _diagram.nodeStrokeWidthAccessor = property(1);
+
+    /**
+     * Set or get the function which will be used to retrieve the stroke color for the outline
+     * of each node.
+     * @method nodeStroke
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|String} [nodeStroke='black']
+     * @return {Function|String}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.nodeStroke = _diagram.nodeStrokeAccessor = property('black');
+
+    _diagram.nodeStrokeDashArray = property(null);
+
+    /**
+     * If set, the value returned from `nodeFill` will be processed through this
+     * {@link https://github.com/mbostock/d3/wiki/Scales d3.scale}
+     * to return the fill color. If falsy, uses the identity function (no scale).
+     * @method nodeFillScale
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|d3.scale} [nodeFillScale]
+     * @return {Function|d3.scale}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.nodeFillScale = property(null);
+
+    /**
+     * Set or get the function which will be used to retrieve the fill color for the body of each
+     * node.
+     * @method nodeFill
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|String} [nodeFill='white']
+     * @return {Function|String}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.nodeFill = _diagram.nodeFillAccessor = property('white');
+
+    /**
+     * Set or get the function which will be used to retrieve the opacity of each node.
+     * @method nodeOpacity
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|Number} [nodeOpacity=1]
+     * @return {Function|Number}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.nodeOpacity = property(1);
+
+    /**
+     * Set or get the padding or minimum distance, in pixels, for a node. (Will be distributed
+     * to both sides of the node.)
+     * @method nodePadding
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|Number} [nodePadding=6]
+     * @return {Function|Number}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.nodePadding = property(6);
+
+
+    /**
+     * Set or get the padding, in pixels, for a node's label. If an object, should contain fields
+     * `x` and `y`. If a number, will be applied to both x and y.
+     * @method nodeLabelPadding
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|Number|Object} [nodeLabelPadding=0]
+     * @return {Function|Number}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.nodeLabelPadding = property(0);
+
+    /**
+     * Set or get the line height for nodes with multiple lines of text, in ems.
+     * @method nodeLineHeight
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|Number} [nodeLineHeight=1]
+     * @return {Function|Number}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.nodeLineHeight = property(1);
+
+    /**
+     * Set or get the function which will be used to retrieve the label text to display in each
+     * node. By default, looks for a field `label` or `name` inside the `value` field.
+     * @method nodeLabel
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|String} [nodeLabel]
+     * @return {Function|String}
+     * @example
+     * // Default behavior
+     * diagram.nodeLabel(function(kv) {
+     *   return kv.value.label || kv.value.name;
+     * });
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.nodeLabel = _diagram.nodeLabelAccessor = property(function(kv) {
+        return kv.value.label || kv.value.name;
+    });
+
+    _diagram.nodeLabelAlignment = property('center');
+    _diagram.nodeLabelDecoration = property(null);
+
+    /**
+     * Set or get the function which will be used to retrieve the label fill color. Default: null
+     * @method nodeLabelFill
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|String} [nodeLabelFill=null]
+     * @return {Function|String}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.nodeLabelFill = _diagram.nodeLabelFillAccessor = property(null);
+
+    /**
+     * Whether to fit the node shape around the label
+     * @method nodeFitLabel
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|Boolean} [nodeFitLabel=true]
+     * @return {Function|Boolean}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.nodeFitLabel = _diagram.nodeFitLabelAccessor = property(true);
+
+    /**
+     * The shape to use for drawing each node, specified as an object with at least the field
+     * `shape`. The names of shapes are mostly taken
+     * [from graphviz](http://www.graphviz.org/doc/info/shapes.html); currently ellipse, egg,
+     * triangle, rectangle, diamond, trapezium, parallelogram, pentagon, hexagon, septagon, octagon,
+     * invtriangle, invtrapezium, square, polygon are supported.
+     *
+     * If `shape = polygon`:
+     * * `sides`: number of sides for a polygon
+     * @method nodeShape
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|Object} [nodeShape={shape: 'ellipse'}]
+     * @return {Function|Object}
+     * @return {dc_graph.diagram}
+     * @example
+     * // set shape to diamond or parallelogram based on flag
+     * diagram.nodeShape(function(kv) {
+     *   return {shape: kv.value.flag ? 'diamond' : 'parallelogram'};
+     * });
+     **/
+    _diagram.nodeShape = property(default_shape);
+
+    // for defining custom (and standard) shapes
+    _diagram.shape = named_children();
+
+    _diagram.shape('nothing', dc_graph.no_shape());
+    _diagram.shape('ellipse', dc_graph.ellipse_shape());
+    _diagram.shape('polygon', dc_graph.polygon_shape());
+    _diagram.shape('rounded-rect', dc_graph.rounded_rectangle_shape());
+    _diagram.shape('elaborated-rect', dc_graph.elaborated_rectangle_shape());
+
+    _diagram.nodeContent = property('text');
+    _diagram.content = named_children();
+    _diagram.content('text', dc_graph.text_contents());
+
+    // really looks like these should reside in an open namespace - this used only by an extension
+    // but it's no less real than any other computed property
+    _diagram.nodeIcon = property(null);
+
+    /**
+     * Set or get the function which will be used to retrieve the node title, usually rendered
+     * as a tooltip. By default, uses the key of the node.
+     * @method nodeTitle
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|String} [nodeTitle]
+     * @return {Function|String}
+     * @example
+     * // Default behavior
+     * diagram.nodeTitle(function(kv) {
+     *   return _diagram.nodeKey()(kv);
+     * });
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.nodeTitle = _diagram.nodeTitleAccessor = property(function(kv) {
+        return _diagram.nodeKey()(kv);
+    });
+
+    /**
+     * By default, nodes are added to the layout in the order that `.nodeGroup().all()` returns
+     * them. If specified, `.nodeOrdering` provides an accessor that returns a key to sort the
+     * nodes on.  *It would be better not to rely on ordering to affect layout, but it may
+     * affect the layout in some cases.*
+     * @method nodeOrdering
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function} [nodeOrdering=null]
+     * @return {Function}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.nodeOrdering = property(null);
+
+    /**
+     * Specify an accessor that returns an {x,y} coordinate for a node that should be
+     * {@link https://github.com/tgdwyer/WebCola/wiki/Fixed-Node-Positions fixed in place},
+     * and returns falsy for other nodes.
+     * @method nodeFixed
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|Object} [nodeFixed=null]
+     * @return {Function|Object}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.nodeFixed = _diagram.nodeFixedAccessor = property(null);
+
+
+    /**
+     * Set or get the function which will be used to retrieve the stroke color for the edges.
+     * @method edgeStroke
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|String} [edgeStroke='black']
+     * @return {Function|String}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.edgeStroke = _diagram.edgeStrokeAccessor = property('black');
+
+    /**
+     * Set or get the function which will be used to retrieve the stroke width for the edges.
+     * @method edgeStrokeWidth
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|Number} [edgeStrokeWidth=1]
+     * @return {Function|Number}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.edgeStrokeWidth = _diagram.edgeStrokeWidthAccessor = property(1);
+
+    _diagram.edgeStrokeDashArray = property(null);
+
+    /**
+     * Set or get the function which will be used to retrieve the edge opacity, a number from 0
+     * to 1.
+     * @method edgeOpacity
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|Number} [edgeOpacity=1]
+     * @return {Function|Number}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.edgeOpacity = _diagram.edgeOpacityAccessor = property(1);
+
+    /**
+     * Set or get the function which will be used to retrieve the edge label text. The label is
+     * displayed when an edge is hovered over. By default, uses the `edgeKey`.
+     * @method edgeLabel
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|String} [edgeLabel]
+     * @example
+     * // Default behavior
+     * diagram.edgeLabel(function(e) {
+     *   return _diagram.edgeKey()(e);
+     * });
+     * @return {Function|String}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.edgeLabel = _diagram.edgeLabelAccessor = property(function(e) {
+        return _diagram.edgeKey()(e);
+    });
+    // vertical spacing when there are multiple lines of edge label
+    _diagram.edgeLabelSpacing = property(12);
+
+    /**
+     * Set or get the function which will be used to retrieve the name of the arrowhead to use
+     * for the target/ head/destination of the edge. Arrow symbols can be specified with
+     * `.defineArrow()`. Return null to display no arrowhead.
+     * @method edgeArrowhead
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|String} [edgeArrowhead='vee']
+     * @return {Function|String}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.edgeArrowhead = _diagram.edgeArrowheadAccessor = property('vee');
+
+    /**
+     * Set or get the function which will be used to retrieve the name of the arrow tail to use
+     * for the tail/source of the edge. Arrow symbols can be specified with
+     * `.defineArrow()`. Return null to display no arrowtail.
+     * @method edgeArrowtail
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|String} [edgeArrowtail=null]
+     * @return {Function|String}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.edgeArrowtail = _diagram.edgeArrowtailAccessor = property(null);
+
+    /**
+     * Multiplier for arrow size.
+     * @method edgeArrowSize
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|Number} [edgeArrowSize=1]
+     * @return {Function|Number}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.edgeArrowSize = property(1);
+
+    /**
+     * To draw an edge but not have it affect the layout, specify a function which returns
+     * false for that edge.  By default, will return false if the `notLayout` field of the edge
+     * value is truthy, true otherwise.
+     * @method edgeIsLayout
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|Boolean} [edgeIsLayout]
+     * @example
+     * // Default behavior
+     * diagram.edgeIsLayout(function(kv) {
+     *   return !kv.value.notLayout;
+     * });
+     * @return {Function|Boolean}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.edgeIsLayout = _diagram.edgeIsLayoutAccessor = property(function(kv) {
+        return !kv.value.notLayout;
+    });
+
+    // if false, don't draw or layout the edge. this is not documented because it seems like
+    // the interface could be better and this combined with edgeIsLayout. (currently there is
+    // no way to layout but not draw an edge.)
+    _diagram.edgeIsShown = property(true);
+
+    /**
+     * Currently, three strategies are supported for specifying the lengths of edges:
+     * * 'individual' - uses the `edgeLength` for each edge. If it returns falsy, uses the
+     * `baseLength`
+     * * 'symmetric', 'jaccard' - compute the edge length based on the graph structure around
+     * the edge. See
+     * {@link https://github.com/tgdwyer/WebCola/wiki/link-lengths the cola.js wiki}
+     * for more details.
+     * 'none' - no edge lengths will be specified
+     *
+     * **Deprecated**: Use {@link dc_graph.cola_layout#lengthStrategy cola_layout.lengthStrategy} instead.
+     * @method lengthStrategy
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|String} [lengthStrategy='symmetric']
+     * @return {Function|String}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.lengthStrategy = deprecate_layout_algo_parameter('lengthStrategy');
+
+    /**
+     * When the `.lengthStrategy` is 'individual', this accessor will be used to read the
+     * length of each edge.  By default, reads the `distance` field of the edge. If the
+     * distance is falsy, uses the `baseLength`.
+     * @method edgeLength
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|Number} [edgeLength]
+     * @example
+     * // Default behavior
+     * diagram.edgeLength(function(kv) {
+     *   return kv.value.distance;
+     * });
+     * @return {Function|Number}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.edgeLength = _diagram.edgeDistanceAccessor = property(function(kv) {
+        return kv.value.distance;
+    });
+
+    /**
+     * This should be equivalent to rankdir and ranksep in the dagre/graphviz nomenclature, but for
+     * now it is separate.
+     *
+     * **Deprecated**: use {@link dc_graph.cola_layout#flowLayout cola_layout.flowLayout} instead.
+     * @method flowLayout
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Object} [flowLayout]
+     * @example
+     * // No flow (default)
+     * diagram.flowLayout(null)
+     * // flow in x with min separation 200
+     * diagram.flowLayout({axis: 'x', minSeparation: 200})
+     **/
+    _diagram.flowLayout = deprecate_layout_algo_parameter('flowLayout');
+
+    /**
+     * Direction to draw ranks. Currently for dagre and expand_collapse, but I think cola could be
+     * generated from graphviz-style since it is more general.
+     *
+     * **Deprecated**: use {@link dc_graph.dagre_layout#rankdir dagre_layout.rankdir} instead.
+     * @method rankdir
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {String} [rankdir]
+     **/
+    _diagram.rankdir = deprecate_layout_algo_parameter('rankdir');
+
+    /**
+     * Gets or sets the default edge length (in pixels) when the `.lengthStrategy` is
+     * 'individual', and the base value to be multiplied for 'symmetric' and 'jaccard' edge
+     * lengths.
+     *
+     * **Deprecated**: use {@link dc_graph.cola_layout#baseLength cola_layout.baseLength} instead.
+     * @method baseLength
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Number} [baseLength]
+     * @return {Number}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.baseLength = deprecate_layout_algo_parameter('baseLength');
+
+    /**
+     * Gets or sets the transition duration, the length of time each change to the diagram will
+     * be animated.
+     * @method transitionDuration
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Number} [transitionDuration=500]
+     * @return {Number}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.transitionDuration = property(500);
+
+    /**
+     * How transitions should be split into separate animations to emphasize
+     * the delete, modify, and insert operations:
+     * * `none`: modify and insert operations animate at the same time
+     * * `modins`: modify operations happen before inserts
+     * * `insmod`: insert operations happen before modifies
+     *
+     * Deletions always happen before/during layout computation.
+     * @method stageTransitions
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {String} [stageTransitions='none']
+     * @return {String}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.stageTransitions = property('none');
+
+    /**
+     * The delete transition happens simultaneously with layout, which can take longer
+     * than the transition duration. Delaying it can bring it closer to the other
+     * staged transitions.
+     * @method deleteDelay
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Number} [deleteDelay=0]
+     * @return {Number}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.deleteDelay = property(0);
+
+    /**
+     * Whether to put connected components each in their own group, to stabilize layout.
+     * @method groupConnected
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {String} [groupConnected=false]
+     * @return {String}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.groupConnected = deprecate_layout_algo_parameter('groupConnected');
+
+    /**
+     * Gets or sets the maximum time spent doing layout for a render or redraw. Set to 0 for no
+     * limit.
+     * @method timeLimit
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function|Number} [timeLimit=0]
+     * @return {Function|Number}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.timeLimit = property(0);
+
+    /**
+     * Gets or sets a function which will be called with the current nodes and edges on each
+     * redraw in order to derive new layout constraints. The constraints are built from scratch
+     * on each redraw.
+     *
+     * This can be used to generate alignment (rank) or axis constraints. By default, no
+     * constraints will be added, although cola.js uses constraints internally to implement
+     * flow and overlap prevention. See
+     * {@link https://github.com/tgdwyer/WebCola/wiki/Constraints the cola.js wiki}
+     * for more details.
+     *
+     * For convenience, dc.graph.js implements a other constraints on top of those implemented
+     * by cola.js:
+     * * 'ordering' - the nodes will be ordered on the specified `axis` according to the keys
+     * returned by the `ordering` function, by creating separation constraints using the
+     * specified `gap`.
+     * * 'circle' - (experimental) the nodes will be placed in a circle using "wheel"
+     * edge lengths similar to those described in
+     * {@link http://www.csse.monash.edu.au/~tdwyer/Dwyer2009FastConstraints.pdf Scalable, Versatile, and Simple Constrained Graph Layout}
+     * *Although this is not as performant or stable as might be desired, it may work for
+     * simple cases. In particular, it should use edge length *constraints*, which don't yet
+     * exist in cola.js.*
+     *
+     * Because it is tedious to write code to generate constraints for a graph, **dc.graph.js**
+     * also includes a {@link #dc_graph+constraint_pattern constraint generator} to produce
+     * this constrain function, specifying the constraints themselves in a graph.
+     * @method constrain
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function} [constrain]
+     * @return {Function}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.constrain = property(function(nodes, edges) {
+        return [];
+    });
+
+    /**
+     * If there are multiple edges between the same two nodes, start them this many pixels away
+     * from the original so they don't overlap.
+     * @method parallelEdgeOffset
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Number} [parallelEdgeOffset=10]
+     * @return {Number}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.parallelEdgeOffset = property(10);
+
+    /**
+     * By default, edges are added to the layout in the order that `.edgeGroup().all()` returns
+     * them. If specified, `.edgeOrdering` provides an accessor that returns a key to sort the
+     * edges on.
+     *
+     * *It would be better not to rely on ordering to affect layout, but it may affect the
+     * layout in some cases. (Probably less than node ordering, but it does affect which
+     * parallel edge is which.)*
+     * @method edgeOrdering
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function} [edgeOrdering=null]
+     * @return {Function}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.edgeOrdering = property(null);
+
+    _diagram.edgeSort = property(null);
+
+    _diagram.cascade = cascade(_diagram);
+
+    /**
+     * Currently there are some bugs when the same instance of cola.js is used multiple
+     * times. (In particular, overlaps between nodes may not be eliminated
+     * {@link https://github.com/tgdwyer/WebCola/issues/118 if cola is not reinitialized}
+     * This flag can be set true to construct a new cola layout object on each redraw. However,
+     * layout seems to be more stable if this is set false, so hopefully this will be fixed
+     * soon.
+     * @method initLayoutOnRedraw
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Boolean} [initLayoutOnRedraw=false]
+     * @return {Boolean}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.initLayoutOnRedraw = property(false);
+
+    /**
+     * Whether to perform layout when the data is unchanged from the last redraw.
+     * @method layoutUnchanged
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Boolean} [layoutUnchanged=false]
+     * @return {Boolean}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.layoutUnchanged = property(false);
+
+    /**
+     * When `layoutUnchanged` is false, this will force layout to happen again. This may be needed
+     * when changing a parameter but not changing the topology of the graph. (Yes, probably should
+     * not be necessary.)
+     * @method relayout
+     * @memberof dc_graph.diagram
+     * @instance
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.relayout = function() {
+        _nodes_snapshot = _edges_snapshot = null;
+        return this;
+    };
+
+    /**
+     * Function to call to generate an initial layout. Takes (diagram, nodes, edges)
+     *
+     * **Deprecated**: The only layout that was using this was `tree_positions` and it never
+     * worked as an initialization step for cola, as was originally intended. Now that
+     * `tree_layout` is a layout algorithm, this should go away.
+     *
+     * In the future, there will be support for chaining layout algorithms. But that will be a
+     * matter of composing them into a super-algorithm, not a special step like this was.
+     * @method initialLayout
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Function} [initialLayout=null]
+     * @return {Function}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.initialLayout = deprecated_property('initialLayout is deprecated - use layout algorithms instead', null);
+
+    _diagram.initialOnly = deprecated_property('initialOnly is deprecated - see the initialLayout deprecation notice in the documentation', false);
+
+    /**
+     * By default, all nodes are included, and edges are only included if both end-nodes are
+     * visible.  If `.induceNodes` is set, then only nodes which have at least one edge will be
+     * shown.
+     * @method induceNodes
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Boolean} [induceNodes=false]
+     * @return {Boolean}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.induceNodes = property(false);
+
+    /**
+     * If this flag is true, the positions of nodes and will be updated while layout is
+     * iterating. If false, the positions will only be updated once layout has
+     * stabilized. Note: this may not be compatible with transitionDuration.
+     * @method showLayoutSteps
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Boolean} [showLayoutSteps=false]
+     * @return {Boolean}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.showLayoutSteps = property(false);
+
+    /**
+     * Assigns a legend object which will be displayed within the same SVG element and
+     * according to the visual encoding of this diagram.
+     * @method legend
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Object} [legend=null]
+     * @return {Object}
+     * @return {dc_graph.diagram}
+     **/
+    // (pre-deprecated; see below)
+
+    /**
+     * Specifies another kind of child layer or interface. For example, this can
+     * be used to display tooltips on nodes using `dc_graph.tip`.
+
+     * The child needs to support a `parent` method, the diagram to modify.
+     * @method child
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {String} [id] - the name of the child to modify or add
+     * @param {Object} [object] - the child object to add, or null to remove
+     * @example
+     * // Display tooltips on node hover, via the d3-tip library
+     * var tip = dc_graph.tip()
+     * tip.content(function(n, k) {
+     *   // you can do an asynchronous call here, e.g. d3.json, if you need
+     *   // to fetch data to show the tooltip - just call k() with the content
+     *   k("This is <em>" + n.orig.value.name + "</em>");
+     * });
+     * diagram.child('tip', tip);
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.mode = _diagram.child = named_children();
+
+    _diagram.mode.reject = function(id, object) {
+        var rtype = _diagram.renderer().rendererType();
+        if(!object)
+            return false; // null is always a valid mode for any renderer
+        if(!object.supportsRenderer)
+            console.log('could not check if "' + id + '" is compatible with ' + rtype);
+        else if(!object.supportsRenderer(rtype))
+            return 'not installing "' + id + '" because it is not compatible with renderer ' + rtype;
+        return false;
+    };
+
+    _diagram.legend = deprecate_function(".legend() is deprecated; use .child() for more control & multiple legends", function(_) {
+        if(!arguments.length)
+            return _diagram.child('node-legend');
+        _diagram.child('node-legend', _);
+        return _diagram;
+    });
+
+    /**
+     * Specify 'cola' (the default) or 'dagre' as the Layout Algorithm and it will replace the
+     * back-end.
+     *
+     * **Deprecated**: use {@link dc_graph.diagram#layoutEngine diagram.layoutEngine} with the engine
+     * object instead
+     * @method layoutAlgorithm
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {String} [algo='cola'] - the name of the layout algorithm to use
+     * @example
+     * // use dagre for layout
+     * diagram.layoutAlgorithm('dagre');
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.layoutAlgorithm = function(value, skipWarning) {
+        if(!arguments.length)
+            return _diagram.layoutEngine() ? _diagram.layoutEngine().layoutAlgorithm() : 'cola';
+        if(!skipWarning)
+            console.warn('dc.graph.diagram.layoutAlgorithm is deprecated - pass the layout engine object to dc_graph.diagram.layoutEngine instead');
+
+        var engine;
+        switch(value) {
+        case 'cola':
+            engine = dc_graph.cola_layout();
+            break;
+        case 'dagre':
+            engine = dc_graph.dagre_layout();
+        }
+        engine = dc_graph.webworker_layout(engine);
+        _diagram.layoutEngine(engine);
+        return this;
+    };
+
+    /**
+     * The layout engine determines positions of nodes and edges.
+     * @method layoutEngine
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Object} [engine=null] - the layout engine to use
+     * @example
+     * // use cola with no webworker
+     * diagram.layoutEngine(dc_graph.cola_layout());
+     * // use dagre with a webworker
+     * diagram.layoutEngine(dc_graph.webworker_layout(dc_graph.dagre_layout()));
+     **/
+    _diagram.layoutEngine = property(null).react(function(val) {
+        if(val && val.parent)
+            val.parent(_diagram);
+        if(_diagram.renderer().isRendered()) {
+            // remove any calculated points, if engine did that
+            Object.keys(_edges).forEach(function(k) {
+                _edges[k].cola.points = null;
+            });
+            // initialize engine
+            initLayout(val);
+        }
+    });
+
+    _diagram.renderer = property(dc_graph.render_svg().parent(_diagram)).react(function(r) {
+        if(_diagram.renderer())
+            _diagram.renderer().parent(null);
+        r.parent(_diagram);
+    });
+
+    // S-spline any edges that are not going in this direction
+    _diagram.enforceEdgeDirection = property(null);
+
+    _diagram.tickSize = deprecate_layout_algo_parameter('tickSize');
+
+
+    _diagram.uniqueId = function() {
+        return _diagram.anchorName().replace(/[ .#=\[\]"]/g, '-');
+    };
+
+    _diagram.edgeId = function(e) {
+        return 'edge-' + _diagram.edgeKey.eval(e).replace(/[^\w-_]/g, '-');
+    };
+
+    _diagram.arrowId = function(e, kind) {
+        return 'arrow-' + kind + '-' + _diagram.uniqueId() + '-'  + _diagram.edgeId(e);
+    };
+    _diagram.textpathId = function(e) {
+        return 'textpath-' + _diagram.uniqueId() + '-' + _diagram.edgeId(e);
+    };
+
+    // this kind of begs a (meta)graph ADT
+    // instead of munging this into the diagram
+    _diagram.getNode = function(id) {
+        return _nodes[id] ? _nodes[id].orig : null;
+    };
+
+    _diagram.getWholeNode = function(id) {
+        return _nodes[id] ? _nodes[id] : null;
+    };
+
+    _diagram.getEdge = function(id) {
+        return _edges[id] ? _edges[id].orig : null;
+    };
+
+    _diagram.getWholeEdge = function(id) {
+        return _edges[id] ? _edges[id] : null;
+    };
+
+    // again, awful, we need an ADT
+    _diagram.getPort = function(nid, eid, name) {
+        return _ports[port_name(nid, eid, name)];
+    };
+
+    _diagram.nodePorts = function() {
+        return _nodePorts;
+    };
+
+    _diagram.getWholeCluster = function(id) {
+        return _clusters[id] || null;
+    };
+
+    /**
+     * Instructs cola.js to fit the connected components.
+     *
+     * **Deprecated**: Use
+     * {@link dc_graph.cola_layout#handleDisconnected cola_layout.handleDisconnected} instead.
+     * @method handleDisconnected
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {Boolean} [handleDisconnected=true]
+     * @return {Boolean}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.handleDisconnected = deprecate_layout_algo_parameter('handleDisconnected');
+
+    function initLayout(engine) {
+        if(!_diagram.layoutEngine())
+            _diagram.layoutAlgorithm('cola', true);
+        (engine || _diagram.layoutEngine()).init({
+            width: _diagram.width(),
+            height: _diagram.height()
+        });
+    }
+
+    _diagram.forEachChild = function(node, children, idf, f) {
+        children.enum().forEach(function(key) {
+            f(children(key),
+              node.filter(function(n) { return idf(n) === key; }));
+        });
+    };
+    _diagram.forEachShape = function(node, f) {
+        _diagram.forEachChild(node, _diagram.shape, function(n) { return n.dcg_shape.shape; }, f);
+    };
+    _diagram.forEachContent = function(node, f) {
+        _diagram.forEachChild(node, _diagram.content, _diagram.nodeContent.eval, f);
+    };
+
+    function has_source_and_target(e) {
+        return !!e.source && !!e.target;
+    }
+
+    // three stages: delete before layout, and modify & insert split the transitionDuration
+    _diagram.stagedDuration = function() {
+        return (_diagram.stageTransitions() !== 'none') ?
+            _diagram.transitionDuration() / 2 :
+            _diagram.transitionDuration();
+    };
+
+    _diagram.stagedDelay = function(is_enter) {
+        return _diagram.stageTransitions() === 'none' ||
+            _diagram.stageTransitions() === 'modins' === !is_enter ?
+            0 :
+            _diagram.transitionDuration() / 2;
+    };
+
+    _diagram.isRunning = function() {
+        return _running;
+    };
+
+    function svg_specific(name) {
+        return trace_function('trace', name + '() is specific to the SVG renderer', function() {
+            return _diagram.renderer()[name].apply(this, arguments);
+        });
+    }
+
+    function call_on_renderer(name) {
+        return trace_function('trace', 'calling ' + name + '() on renderer', function() {
+            return _diagram.renderer()[name].apply(this, arguments);
+        });
+    }
+
+    _diagram.svg = svg_specific('svg');
+    _diagram.g = svg_specific('g');
+    _diagram.select = svg_specific('select');
+    _diagram.selectAll = svg_specific('selectAll');
+    _diagram.addOrRemoveDef = svg_specific('addOrRemoveDef');
+    _diagram.selectAllNodes = svg_specific('selectAllNodes');
+    _diagram.selectAllEdges = svg_specific('selectAllEdges');
+    _diagram.selectNodePortsOfStyle = svg_specific('selectNodePortsOfStyle');
+    _diagram.zoom = svg_specific('zoom');
+    _diagram.translate = svg_specific('translate');
+    _diagram.scale = svg_specific('scale');
+
+    function renderer_specific(name) {
+        return trace_function('trace', name + '() will have renderer-specific arguments', function() {
+            return _diagram.renderer()[name].apply(this, arguments);
+        });
+    }
+    _diagram.renderNode = svg_specific('renderNode');
+    _diagram.renderEdge = svg_specific('renderEdge');
+    _diagram.redrawNode = svg_specific('redrawNode');
+    _diagram.redrawEdge = svg_specific('redrawEdge');
+    _diagram.reposition = call_on_renderer('reposition');
+
+
+    /**
+     * Standard dc.js
+     * {@link https://github.com/dc-js/dc.js/blob/develop/web/docs/api-latest.md#dc.baseMixin baseMixin}
+     * method. Computes a new layout based on the nodes and edges in the edge groups, and
+     * displays the diagram.  To the extent possible, the diagram will minimize changes in
+     * positions from the previous layout.  `.render()` must be called the first time, and
+     * `.redraw()` can be called after that.
+     *
+     * `.redraw()` will be triggered by changes to the filters in any other charts in the same
+     * dc.js chart group.
+     *
+     * Unlike in dc.js, `redraw` executes asynchronously, because drawing can be computationally
+     * intensive, and the diagram will be drawn multiple times if
+     * {@link #dc_graph.diagram+showLayoutSteps showLayoutSteps}
+     * is enabled. Watch the {@link #dc_graph.diagram+on 'end'} event to know when layout is
+     * complete.
+     * @method redraw
+     * @memberof dc_graph.diagram
+     * @instance
+     * @return {dc_graph.diagram}
+     **/
+    var _needsRedraw = false;
+    _diagram.redraw = function () {
+        // since dc.js can receive UI events and trigger redraws whenever it wants,
+        // and cola absolutely will not tolerate being poked while it's doing layout,
+        // we need to guard the startLayout call.
+        if(_running) {
+            _needsRedraw = true;
+            return this;
+        }
+        else return _diagram.startLayout();
+    };
+
+    /**
+     * Standard dc.js
+     * {@link https://github.com/dc-js/dc.js/blob/develop/web/docs/api-latest.md#dc.baseMixin baseMixin}
+     * method. Erases any existing SVG elements and draws the diagram from scratch. `.render()`
+     * must be called the first time, and `.redraw()` can be called after that.
+     * @method render
+     * @memberof dc_graph.diagram
+     * @instance
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.render = function() {
+        if(_diagram.renderer().isRendered())
+            _dispatch.reset();
+        if(!_diagram.initLayoutOnRedraw())
+            initLayout();
+
+        _nodes = {};
+        _edges = {};
+        _ports = {};
+        _clusters = {};
+
+        // start out with 1:1 zoom
+        _diagram.x(d3.scale.linear()
+                   .domain([0, _diagram.width()])
+                   .range([0, _diagram.width()]));
+        _diagram.y(d3.scale.linear()
+                   .domain([0, _diagram.height()])
+                   .range([0, _diagram.height()]));
+        _diagram.renderer().initializeDrawing();
+        _dispatch.render();
+        _diagram.redraw();
+        return this;
+    };
+
+    _diagram.refresh = call_on_renderer('refresh');
+
+    _diagram.width_is_automatic = function() {
+        return _width === 'auto';
+    };
+
+    _diagram.height_is_automatic = function() {
+        return _height === 'auto';
+    };
+
+    function detect_size_change() {
+        var oldWidth = _lastWidth, oldHeight = _lastHeight;
+        var newWidth = _diagram.width(), newHeight = _diagram.height();
+        if(oldWidth !== newWidth || oldHeight !== newHeight)
+            _diagram.renderer().rezoom(oldWidth, oldHeight, newWidth, newHeight);
+    }
+
+    _diagram.startLayout = function () {
+        var nodes = _diagram.nodeGroup().all();
+        var edges = _diagram.edgeGroup().all();
+        var ports = _diagram.portGroup() ? _diagram.portGroup().all() : [];
+        var clusters = _diagram.clusterGroup() ? _diagram.clusterGroup().all() : [];
+        if(_running) {
+            throw new Error('dc_graph.diagram.redraw already running!');
+        }
+        _running = true;
+
+        if(_diagram.width_is_automatic() || _diagram.height_is_automatic())
+            detect_size_change();
+        else
+            _diagram.renderer().resize();
+
+        if(_diagram.initLayoutOnRedraw())
+            initLayout();
+        _diagram.layoutEngine().stop();
+        _dispatch.preDraw();
+
+        // ordering shouldn't matter, but we support ordering in case it does
+        if(_diagram.nodeOrdering()) {
+            nodes = crossfilter.quicksort.by(_diagram.nodeOrdering())(nodes.slice(0), 0, nodes.length);
+        }
+        if(_diagram.edgeOrdering()) {
+            edges = crossfilter.quicksort.by(_diagram.edgeOrdering())(edges.slice(0), 0, edges.length);
+        }
+
+        var wnodes = regenerate_objects(_nodes, nodes, null, function(v) {
+            return _diagram.nodeKey()(v);
+        }, function(v1, v) {
+            v1.orig = v;
+            v1.cola = v1.cola || {};
+            v1.cola.dcg_nodeKey = _diagram.nodeKey.eval(v1);
+            v1.cola.dcg_nodeParentCluster = _diagram.nodeParentCluster.eval(v1);
+            _diagram.layoutEngine().populateLayoutNode(v1.cola, v1);
+        });
+        var wedges = regenerate_objects(_edges, edges, null, function(e) {
+            return _diagram.edgeKey()(e);
+        }, function(e1, e) {
+            e1.orig = e;
+            e1.cola = e1.cola || {};
+            e1.cola.dcg_edgeKey = _diagram.edgeKey.eval(e1);
+            e1.cola.dcg_edgeSource = _diagram.edgeSource.eval(e1);
+            e1.cola.dcg_edgeTarget = _diagram.edgeTarget.eval(e1);
+            e1.source = _nodes[e1.cola.dcg_edgeSource];
+            e1.target = _nodes[e1.cola.dcg_edgeTarget];
+            e1.sourcePort = e1.sourcePort || {};
+            e1.targetPort = e1.targetPort || {};
+            _diagram.layoutEngine().populateLayoutEdge(e1.cola, e1);
+        });
+
+        // remove edges that don't have both end nodes
+        wedges = wedges.filter(has_source_and_target);
+
+        // remove self-edges (since we can't draw them - will be option later)
+        wedges = wedges.filter(function(e) { return e.source !== e.target; });
+
+        wedges = wedges.filter(_diagram.edgeIsShown.eval);
+
+        // now we know which ports should exist
+        var needports = wedges.map(function(e) {
+            if(_diagram.edgeSourcePortName.eval(e))
+                return port_name(_diagram.edgeSource.eval(e), null, _diagram.edgeSourcePortName.eval(e));
+            else return port_name(null, _diagram.edgeKey.eval(e), 'source');
+        });
+        needports = needports.concat(wedges.map(function(e) {
+            if(_diagram.edgeTargetPortName.eval(e))
+                return port_name(_diagram.edgeTarget.eval(e), null, _diagram.edgeTargetPortName.eval(e));
+            else return port_name(null, _diagram.edgeKey.eval(e), 'target');
+        }));
+        // remove any invalid ports so they don't crash in confusing ways later
+        ports = ports.filter(function(p) {
+            return _diagram.portNodeKey() && _diagram.portNodeKey()(p) ||
+                _diagram.portEdgeKey() && _diagram.portEdgeKey()(p);
+        });
+        var wports = regenerate_objects(_ports, ports, needports, function(p) {
+            return port_name(_diagram.portNodeKey() && _diagram.portNodeKey()(p),
+                             _diagram.portEdgeKey() && _diagram.portEdgeKey()(p),
+                             _diagram.portName()(p));
+        }, function(p1, p) {
+            p1.orig = p;
+            if(p1.named)
+                p1.edges = [];
+        }, function(k, p) {
+            console.assert(k, 'should have screened out invalid ports');
+            // it's dumb to parse the id we just created. as usual, i blame the lack of metagraphs
+            var parse = split_port_name(k);
+            if(parse.nodeKey) {
+                p.node = _nodes[parse.nodeKey];
+                p.named = true;
+            }
+            else {
+                var e = _edges[parse.edgeKey];
+                p.node = e[parse.name];
+                p.edges = [e];
+                p.named = false;
+            }
+            p.name = parse.name;
+        });
+        // remove any ports where the end-node was not found, to avoid crashing elsewhere
+        wports = wports.filter(function(p) { return p.node; });
+
+        // find all edges for named ports
+        wedges.forEach(function(e) {
+            var name = _diagram.edgeSourcePortName.eval(e);
+            if(name)
+                _ports[port_name(_diagram.nodeKey.eval(e.source), null, name)].edges.push(e);
+            name = _diagram.edgeTargetPortName.eval(e);
+            if(name)
+                _ports[port_name(_diagram.nodeKey.eval(e.target), null, name)].edges.push(e);
+        });
+
+        // optionally, delete nodes that have no edges
+        if(_diagram.induceNodes()) {
+            var keeps = {};
+            wedges.forEach(function(e) {
+                keeps[e.cola.dcg_edgeSource] = true;
+                keeps[e.cola.dcg_edgeTarget] = true;
+            });
+            wnodes = wnodes.filter(function(n) { return keeps[n.cola.dcg_nodeKey]; });
+            for(var k in _nodes)
+                if(!keeps[k])
+                    delete _nodes[k];
+        }
+
+        var needclusters = d3.set(wnodes.map(function(n) {
+            return _diagram.nodeParentCluster.eval(n);
+        }).filter(identity)).values();
+
+        var wclusters = regenerate_objects(_clusters, clusters, needclusters, function(c) {
+            return _diagram.clusterKey()(c);
+        }, function(c1, c) { // assign
+            c1.orig = c;
+            c1.cola = c1.cola || {
+                dcg_clusterKey: _diagram.clusterKey.eval(c1),
+                dcg_clusterParent: _diagram.clusterParent.eval(c1)
+            };
+        }, function(k, c) { // create
+        });
+
+        wnodes.forEach(function(v, i) {
+            v.index = i;
+        });
+
+        // announce new data
+        _dispatch.data(_diagram, _nodes, wnodes, _edges, wedges, _ports, wports);
+        _stats = {nnodes: wnodes.length, nedges: wedges.length};
+
+        // fixed nodes may have been affected by .data() so calculate now
+        wnodes.forEach(function(v) {
+            if(_diagram.nodeFixed())
+                v.cola.dcg_nodeFixed = _diagram.nodeFixed.eval(v);
+        });
+
+        // annotate parallel edges so we can draw them specially
+        if(_diagram.parallelEdgeOffset()) {
+            var em = new Array(wnodes.length);
+            for(var i = 0; i < wnodes.length; ++i)
+                em[i] = new Array(i);
+            wedges.forEach(function(e) {
+                e.pos = e.pos || {};
+                var min, max, minattr, maxattr;
+                if(e.source.index < e.target.index) {
+                    min = e.source.index; max = e.target.index;
+                    minattr = 'edgeSourcePortName'; maxattr = 'edgeTargetPortName';
+                } else {
+                    max = e.source.index; min = e.target.index;
+                    maxattr = 'edgeSourcePortName'; minattr = 'edgeTargetPortName';
+                }
+                var minport = _diagram[minattr].eval(e) || 'no port',
+                    maxport = _diagram[maxattr].eval(e) || 'no port';
+                em[max][min] = em[max][min] || {};
+                em[max][min][maxport] = em[max][min][maxport] || {};
+                e.parallel = em[max][min][maxport][minport] = em[max][min][maxport][minport] || {
+                    rev: [],
+                    edges: []
+                };
+                e.parallel.edges.push(e);
+                e.parallel.rev.push(min !== e.source.index);
+            });
+        }
+
+        var drawState = _diagram.renderer().startRedraw(_dispatch, wnodes, wedges);
+
+        // really we should have layout chaining like in the good old Dynagraph days
+        // the ordering of this and the previous 4 statements is somewhat questionable
+        if(_diagram.initialLayout())
+            _diagram.initialLayout()(_diagram, wnodes, wedges);
+
+        // no layout if the topology and layout parameters haven't changed
+        var skip_layout = false;
+        if(!_diagram.layoutUnchanged()) {
+            var nodes_snapshot = JSON.stringify(wnodes.map(function(n) {
+                return {orig: get_original(n), cola: {dcg_nodeFixed: n.cola.dcg_nodeFixed}};
+            }));
+            var edges_snapshot = JSON.stringify(wedges.map(function(e) {
+                return {orig: get_original(e), cola: e.cola};
+            }));
+            if(nodes_snapshot === _nodes_snapshot && edges_snapshot === _edges_snapshot)
+                skip_layout = true;
+            _nodes_snapshot = nodes_snapshot;
+            _edges_snapshot = edges_snapshot;
+        }
+
+        // edge lengths may be affected by node sizes
+        wedges.forEach(function(e) {
+            e.cola.dcg_edgeLength = _diagram.edgeLength.eval(e);
+        });
+
+        // cola constraints always use indices, but node references
+        // are more friendly, so translate those
+
+        // i am not satisfied with this constraint generation api...
+        // https://github.com/dc-js/dc.graph.js/issues/10
+        var constraints = _diagram.constrain()(_diagram, wnodes, wedges);
+
+        // warn if there are any loops (before changing names to indices)
+        // it would be better to do this in webcola
+        // (for one thing, this duplicates logic in rectangle.ts)
+        // but by that time it has lost the names of things,
+        // so the output would be difficult to use
+        var constraints_by_left = constraints.reduce(function(p, c) {
+            if(c.type) {
+                switch(c.type) {
+                case 'alignment':
+                    var left = c.offsets[0].node;
+                    p[left] = p[left] || [];
+                    c.offsets.slice(1).forEach(function(o) {
+                        p[left].push({node: o.node, in_constraint: c});
+                    });
+                    break;
+                }
+            } else if(c.axis) {
+                p[c.left] = p[c.left] || [];
+                p[c.left].push({node: c.right, in_constraint: c});
+            }
+            return p;
+        }, {});
+        var touched = {};
+        function find_constraint_loops(con, stack) {
+            var left = con.node;
+            stack = stack || [];
+            var loop = stack.find(function(con) { return con.node === left; });
+            stack = stack.concat([con]);
+            if(loop)
+                console.warn('found a loop in constraints', stack);
+            if(touched[left])
+                return;
+            touched[left] = true;
+            if(!constraints_by_left[left])
+                return;
+            constraints_by_left[left].forEach(function(right) {
+                find_constraint_loops(right, stack);
+            });
+        }
+        Object.keys(constraints_by_left).forEach(function(left) {
+            if(!touched[left])
+                find_constraint_loops({node: left, in_constraint: null});
+        });
+
+        // translate references from names to indices (ugly)
+        var invalid_constraints = [];
+        constraints.forEach(function(c) {
+            if(c.type) {
+                switch(c.type) {
+                case 'alignment':
+                    c.offsets.forEach(function(o) {
+                        o.node = _nodes[o.node].index;
+                    });
+                    break;
+                case 'circle':
+                    c.nodes.forEach(function(n) {
+                        n.node = _nodes[n.node].index;
+                    });
+                    break;
+                }
+            } else if(c.axis && c.left && c.right) {
+                c.left = _nodes[c.left].index;
+                c.right = _nodes[c.right].index;
+            }
+            else invalid_constraints.push(c);
+        });
+
+        if(invalid_constraints.length)
+            console.warn(invalid_constraints.length + ' invalid constraints', invalid_constraints);
+
+        // pseudo-cola.js features
+
+        // 1. non-layout edges are drawn but not told to cola.js
+        var layout_edges = wedges.filter(_diagram.edgeIsLayout.eval);
+        var nonlayout_edges = wedges.filter(function(x) {
+            return !_diagram.edgeIsLayout.eval(x);
+        });
+
+        // 2. type=circle constraints
+        var circle_constraints = constraints.filter(function(c) {
+            return c.type === 'circle';
+        });
+        constraints = constraints.filter(function(c) {
+            return c.type !== 'circle';
+        });
+        circle_constraints.forEach(function(c) {
+            var R = (c.distance || _diagram.baseLength()*4) / (2*Math.sin(Math.PI/c.nodes.length));
+            var nindices = c.nodes.map(function(x) { return x.node; });
+            var namef = function(i) {
+                return _diagram.nodeKey.eval(wnodes[i]);
+            };
+            var wheel = dc_graph.wheel_edges(namef, nindices, R)
+                    .map(function(e) {
+                        var e1 = {internal: e};
+                        e1.source = _nodes[e.sourcename];
+                        e1.target = _nodes[e.targetname];
+                        return e1;
+                    });
+            layout_edges = layout_edges.concat(wheel);
+        });
+
+        // 3. ordered alignment
+        var ordered_constraints = constraints.filter(function(c) {
+            return c.type === 'ordering';
+        });
+        constraints = constraints.filter(function(c) {
+            return c.type !== 'ordering';
+        });
+        ordered_constraints.forEach(function(c) {
+            var sorted = c.nodes.map(function(n) { return _nodes[n]; });
+            if(c.ordering) {
+                var sort = crossfilter.quicksort.by(param(c.ordering));
+                sorted = sort(sorted, 0, sorted.length);
+            }
+            var left;
+            sorted.forEach(function(n, i) {
+                if(i===0)
+                    left = n;
+                else {
+                    constraints.push({
+                        left: left.index,
+                        right: (left = n).index,
+                        axis: c.axis,
+                        gap: c.gap
+                    });
+                }
+            });
+        });
+        if(skip_layout) {
+            _running = false;
+            // init_node_ports?
+            _diagram.renderer().draw(drawState, true);
+            _diagram.renderer().drawPorts(drawState);
+            _diagram.renderer().fireTSEvent(_dispatch, drawState);
+            check_zoom(drawState);
+            return this;
+        }
+        var startTime = Date.now();
+
+        function populate_cola(rnodes, redges, rclusters) {
+            rnodes.forEach(function(rn) {
+                var n = _nodes[rn.dcg_nodeKey];
+                if(!n) {
+                    console.warn('received node "' + rn.dcg_nodeKey + '" that we did not send, ignored');
+                    return;
+                }
+                n.cola.x = rn.x;
+                n.cola.y = rn.y;
+                n.cola.z = rn.z;
+            });
+            redges.forEach(function(re) {
+                var e = _edges[re.dcg_edgeKey];
+                if(!e) {
+                    console.warn('received edge "' + re.dcg_edgeKey + '" that we did not send, ignored');
+                    return;
+                }
+                if(re.points)
+                    e.cola.points = re.points;
+            });
+            wclusters.forEach(function(c) {
+                c.cola.bounds = null;
+            });
+            if(rclusters)
+                rclusters.forEach(function(rc) {
+                    var c = _clusters[rc.dcg_clusterKey];
+                    if(!c) {
+                        console.warn('received cluster "' + rc.dcg_clusterKey + '" that we did not send, ignored');
+                        return;
+                    }
+                    if(rc.bounds)
+                        c.cola.bounds = rc.bounds;
+                });
+        }
+        _diagram.layoutEngine()
+            .on('tick.diagram', function(nodes, edges, clusters) {
+                var elapsed = Date.now() - startTime;
+                if(!_diagram.initialOnly())
+                    populate_cola(nodes, edges, clusters);
+                if(_diagram.showLayoutSteps()) {
+                    init_node_ports(_nodes, wports);
+                    _dispatch.receivedLayout(_diagram, _nodes, wnodes, _edges, wedges, _ports, wports);
+                    propagate_port_positions(_nodes, wedges, _ports);
+                    _diagram.renderer().draw(drawState, true);
+                    _diagram.renderer().drawPorts(drawState);
+                    // should do this only once
+                    _diagram.renderer().fireTSEvent(_dispatch, drawState);
+                }
+                if(_needsRedraw || _diagram.timeLimit() && elapsed > _diagram.timeLimit()) {
+                    console.log('cancelled');
+                    _diagram.layoutEngine().stop();
+                }
+            })
+            .on('end.diagram', function(nodes, edges, clusters) {
+                if(!_diagram.showLayoutSteps()) {
+                    if(!_diagram.initialOnly())
+                        populate_cola(nodes, edges, clusters);
+                    init_node_ports(_nodes, wports);
+                    _dispatch.receivedLayout(_diagram, _nodes, wnodes, _edges, wedges, _ports, wports);
+                    propagate_port_positions(_nodes, wedges, _ports);
+                    _diagram.renderer().draw(drawState, true);
+                    _diagram.renderer().drawPorts(drawState);
+                    _diagram.renderer().fireTSEvent(_dispatch, drawState);
+                }
+                else _diagram.layoutDone(true);
+                check_zoom(drawState);
+            })
+            .on('start.diagram', function() {
+                console.log('algo ' + _diagram.layoutEngine().layoutAlgorithm() + ' started.');
+                _dispatch.start();
+            });
+
+        if(_diagram.initialOnly())
+            _diagram.layoutEngine().dispatch().end(wnodes, wedges);
+        else {
+            _dispatch.start(); // cola doesn't seem to fire this itself?
+            var engine = _diagram.layoutEngine();
+            engine.data(
+                { width: _diagram.width(), height: _diagram.height() },
+                wnodes.map(function(v) {
+                    var lv = Object.assign({}, v.cola, v.dcg_shape);
+                    if(engine.annotateNode)
+                        engine.annotateNode(lv, v);
+                    else if(engine.extractNodeAttrs)
+                        Object.keys(engine.extractNodeAttrs()).forEach(function(key) {
+                            lv[key] = engine.extractNodeAttrs()[key](v.orig);
+                        });
+                    return lv;
+                }),
+                layout_edges.map(function(e) {
+                    var le = e.cola;
+                    if(engine.annotateEdge)
+                        engine.annotateEdge(le, e);
+                    else if(engine.extractEdgeAttrs)
+                        Object.keys(engine.extractEdgeAttrs()).forEach(function(key) {
+                            le[key] = engine.extractEdgeAttrs()[key](e.orig);
+                        });
+                    return le;
+                }),
+                wclusters.map(function(c) {
+                    return c.cola;
+                }),
+                constraints
+            );
+            engine.start();
+        }
+        return this;
+    };
+
+    function check_zoom(drawState) {
+        var do_zoom, animate = true;
+        if(_diagram.width_is_automatic() || _diagram.height_is_automatic())
+            detect_size_change();
+        switch(_diagram.autoZoom()) {
+        case 'always-skipanimonce':
+            animate = false;
+            _diagram.autoZoom('always');
+        case 'always':
+            do_zoom = true;
+            break;
+        case 'once-noanim':
+            animate = false;
+        case 'once':
+            do_zoom = true;
+            _diagram.autoZoom(null);
+            break;
+        default:
+            do_zoom = false;
+        }
+        calc_bounds(drawState);
+        if(do_zoom)
+            auto_zoom(animate);
+    }
+
+    function norm(v) {
+        var len = Math.hypot(v[0], v[1]);
+        return [v[0]/len, v[1]/len];
+    }
+    function edge_vec(n, e) {
+        var dy = e.target.cola.y - e.source.cola.y,
+            dx = e.target.cola.x - e.source.cola.x;
+        if(dy === 0 && dx === 0)
+            return [1, 0];
+        if(e.source !== n)
+            dy = -dy, dx = -dx;
+        if(e.parallel && e.parallel.edges.length > 1 && e.source.index > e.target.index)
+            dy = -dy, dx = -dx;
+        return norm([dx, dy]);
+    }
+    function init_node_ports(nodes, wports) {
+        _nodePorts = {};
+        // assemble port-lists for nodes, again because we don't have a metagraph.
+        wports.forEach(function(p) {
+            var nid = _diagram.nodeKey.eval(p.node);
+            var np = _nodePorts[nid] = _nodePorts[nid] || [];
+            np.push(p);
+        });
+        for(var nid in _nodePorts) {
+            var n = nodes[nid],
+                nports = _nodePorts[nid];
+            // initial positions: use average of edge vectors, if any, or existing position
+            nports.forEach(function(p) {
+                if(_diagram.portElastic.eval(p) && p.edges.length) {
+                    var vecs = p.edges.map(edge_vec.bind(null, n));
+                    p.vec = [
+                        d3.sum(vecs, function(v) { return v[0]; })/vecs.length,
+                        d3.sum(vecs, function(v) { return v[1]; })/vecs.length
+                    ];
+                } else p.vec = p.vec || undefined;
+                p.pos = null;
+            });
+        }
+    }
+    function propagate_port_positions(nodes, wedges, ports) {
+        // make sure we have projected vectors to positions
+        for(var nid in _nodePorts) {
+            var n = nodes[nid];
+            _nodePorts[nid].forEach(function(p) {
+                if(!p.pos)
+                    project_port(_diagram, n, p);
+            });
+        }
+
+        // propagate port positions to edge endpoints
+        wedges.forEach(function(e) {
+            var name = _diagram.edgeSourcePortName.eval(e);
+            e.sourcePort.pos = name ? ports[port_name(_diagram.nodeKey.eval(e.source), null, name)].pos :
+                ports[port_name(null, _diagram.edgeKey.eval(e), 'source')].pos;
+            name = _diagram.edgeTargetPortName.eval(e);
+            e.targetPort.pos = name ? ports[port_name(_diagram.nodeKey.eval(e.target), null, name)].pos :
+                ports[port_name(null, _diagram.edgeKey.eval(e), 'target')].pos;
+            console.assert(e.sourcePort.pos && e.targetPort.pos);
+        });
+    }
+
+    _diagram.requestRefresh = function(durationOverride) {
+        window.requestAnimationFrame(function() {
+            var transdur;
+            if(durationOverride !== undefined) {
+                transdur = _diagram.transitionDuration();
+                _diagram.transitionDuration(durationOverride);
+            }
+            _diagram.renderer().refresh();
+            if(durationOverride !== undefined)
+                _diagram.transitionDuration(transdur);
+        });
+    };
+
+    _diagram.layoutDone = function(happens) {
+        _dispatch.end(happens);
+        _running = false;
+        if(_needsRedraw) {
+            _needsRedraw = false;
+            window.setTimeout(function() {
+                if(!_diagram.isRunning()) // someone else may already have started
+                    _diagram.redraw();
+            }, 0);
+        }
+    };
+
+    function enforce_path_direction(path, spos, tpos) {
+        var points = path.points, first = points[0], last = points[points.length-1];
+        switch(_diagram.enforceEdgeDirection()) {
+        case 'LR':
+            if(spos.x >= tpos.x) {
+                var dx = first.x - last.x;
+                return {
+                    points: [
+                        first,
+                        {x: first.x + dx, y: first.y - dx/2},
+                        {x: last.x - dx, y: last.y - dx/2},
+                        last
+                    ],
+                    bezDegree: 3,
+                    sourcePort: path.sourcePort,
+                    targetPort: path.targetPort
+                };
+            }
+            break;
+        case 'TB':
+            if(spos.y >= tpos.y) {
+                var dy = first.y - last.y;
+                return {
+                    points: [
+                        first,
+                        {x: first.x + dy/2, y: first.y + dy},
+                        {x: last.x + dy/2, y: last.y - dy},
+                        last
+                    ],
+                    bezDegree: 3,
+                    sourcePort: path.sourcePort,
+                    targetPort: path.targetPort
+                };
+            }
+            break;
+        }
+        return path;
+    }
+    _diagram.calcEdgePath = function(e, age, sx, sy, tx, ty) {
+        var parallel = e.parallel;
+        var source = e.source, target = e.target;
+        if(parallel.edges.length > 1 && e.source.index > e.target.index) {
+            var t;
+            t = target; target = source; source = t;
+            t = tx; tx = sx; sx = t;
+            t = ty; ty = sy; sy = t;
+        }
+        var source_padding = source.dcg_ry +
+            _diagram.nodeStrokeWidth.eval(source) / 2,
+            target_padding = target.dcg_ry +
+            _diagram.nodeStrokeWidth.eval(target) / 2;
+        for(var p = 0; p < parallel.edges.length; ++p) {
+            // alternate parallel edges over, then under
+            var dir = (!!(p%2) === (sx < tx)) ? -1 : 1,
+                port = Math.floor((p+1)/2),
+                last = port > 0 ? parallel.edges[p > 2 ? p - 2 : 0].pos[age].path : null;
+            var path = draw_edge_to_shapes(_diagram, e, sx, sy, tx, ty,
+                                           last, dir, _diagram.parallelEdgeOffset(),
+                                           source_padding, target_padding
+                                          );
+            if(parallel.edges.length > 1 && parallel.rev[p])
+                path.points.reverse();
+            if(_diagram.enforceEdgeDirection())
+                path = enforce_path_direction(path, source.cola, target.cola);
+            var path0 = {
+                points: path.points,
+                bezDegree: path.bezDegree
+            };
+            var alengths = scaled_arrow_lengths(_diagram, parallel.edges[p]);
+            path = clip_path_to_arrows(alengths.headLength, alengths.tailLength, path);
+            var points = path.points, points0 = path0.points;
+            parallel.edges[p].pos[age] = {
+                path: path,
+                full: path0,
+                orienthead: angle_between_points(points[points.length-1], points0[points0.length-1]) + 'rad',
+                orienttail: angle_between_points(points[0], points0[0]) + 'rad'
+            };
+        }
+    };
+
+    function node_bounds(n) {
+        var bounds = {left: n.cola.x - n.dcg_rx, top: n.cola.y - n.dcg_ry,
+                      right: n.cola.x + n.dcg_rx, bottom: n.cola.y + n.dcg_ry};
+        if(_diagram.portStyle.enum().length) {
+            var ports = _nodePorts[_diagram.nodeKey.eval(n)];
+            if(ports)
+                ports.forEach(function(p) {
+                    var portStyle =_diagram.portStyleName.eval(p);
+                    if(!portStyle || !_diagram.portStyle(portStyle))
+                        return;
+                    var pb = _diagram.portStyle(portStyle).portBounds(p);
+                    pb.left += n.cola.x; pb.top += n.cola.y;
+                    pb.right += n.cola.x; pb.bottom += n.cola.y;
+                    bounds = union_bounds(bounds, pb);
+                });
+        }
+        return bounds;
+    }
+
+    function union_bounds(b1, b2) {
+        return {
+            left: Math.min(b1.left, b2.left),
+            top: Math.min(b1.top, b2.top),
+            right: Math.max(b1.right, b2.right),
+            bottom: Math.max(b1.bottom, b2.bottom)
+        };
+    }
+
+    function point_to_bounds(p) {
+        return {
+            left: p.x,
+            top: p.y,
+            right: p.x,
+            bottom: p.y
+        };
+    }
+
+    function edge_bounds(e) {
+        // assumption: edge must have some points
+        var points = e.pos.new.path.points;
+        return points.map(point_to_bounds).reduce(union_bounds);
+    }
+
+    _diagram.calculateBounds = function(ndata, edata) {
+        // assumption: there can be no edges without nodes
+        var bounds = ndata.map(node_bounds).reduce(union_bounds);
+        return edata.map(edge_bounds).reduce(union_bounds, bounds);
+    };
+    var _bounds;
+    function calc_bounds(drawState) {
+        if((_diagram.fitStrategy() || _diagram.restrictPan())) {
+            _bounds = _diagram.renderer().calculateBounds(drawState);
+        }
+    }
+
+    function auto_zoom(animate) {
+        if(_diagram.fitStrategy()) {
+            if(!_bounds)
+                return;
+            var vwidth = _bounds.right - _bounds.left, vheight = _bounds.bottom - _bounds.top,
+                swidth =  _diagram.width() - _diagram.margins().left - _diagram.margins().right,
+                sheight = _diagram.height() - _diagram.margins().top - _diagram.margins().bottom;
+            var fitS = _diagram.fitStrategy(), translate = [0,0], scale = 1;
+            if(['default', 'vertical', 'horizontal'].indexOf(fitS) >= 0) {
+                var sAR = sheight / swidth, vAR = vheight / vwidth,
+                    vrl = vAR<sAR, // view aspect ratio is less (wider)
+                    amv = (fitS === 'default') ? !vrl : (fitS === 'vertical'); // align margins vertically
+                scale = amv ? sheight / vheight : swidth / vwidth;
+                scale = Math.max(_diagram.zoomExtent()[0], Math.min(_diagram.zoomExtent()[1], scale));
+                translate = [_diagram.margins().left - _bounds.left*scale + (swidth - vwidth*scale) / 2,
+                             _diagram.margins().top - _bounds.top*scale + (sheight - vheight*scale) / 2];
+            }
+            else if(typeof fitS === 'string' && fitS.match(/^align_/)) {
+                var sides = fitS.split('_')[1].toLowerCase().split('');
+                if(sides.length > 2)
+                    throw new Error("align_ expecting 0-2 sides, not " + sides.length);
+                var bounds = margined_bounds();
+                translate = _diagram.renderer().translate();
+                scale = _diagram.renderer().scale();
+                var vertalign = false, horzalign = false;
+                sides.forEach(function(s) {
+                    switch(s) {
+                    case 'l':
+                        translate[0] = align_left(translate, bounds.left);
+                        horzalign = true;
+                        break;
+                    case 't':
+                        translate[1] = align_top(translate, bounds.top);
+                        vertalign = true;
+                        break;
+                    case 'r':
+                        translate[0] = align_right(translate, bounds.right);
+                        horzalign = true;
+                        break;
+                    case 'b':
+                        translate[1] = align_bottom(translate, bounds.bottom);
+                        vertalign = true;
+                        break;
+                    case 'c': // handled below
+                        break;
+                    default:
+                        throw new Error("align_ expecting l t r b or c, not '" + s + "'");
+                    }
+                });
+                if(sides.includes('c')) {
+                    if(!horzalign)
+                        translate[0] = center_horizontally(translate, bounds);
+                    if(!vertalign)
+                        translate[1] = center_vertically(translate, bounds);
+                }
+            }
+            else if(fitS === 'zoom') {
+                scale = _diagram.renderer().scale();
+                translate = bring_in_bounds(_diagram.renderer().translate());
+            }
+            else
+                throw new Error('unknown fitStrategy type ' + typeof fitS);
+
+            _animateZoom = animate;
+            _diagram.renderer().translate(translate).scale(scale).commitTranslateScale();
+            _animateZoom = false;
+        }
+    }
+    function namespace_event_reducer(msg_fun) {
+        return function(p, ev) {
+            var namespace = {};
+            p[ev] = function(ns) {
+                return namespace[ns] = namespace[ns] || onetime_trace('trace', msg_fun(ns, ev));
+            };
+            return p;
+        };
+    }
+    var renderer_specific_events = ['drawn', 'transitionsStarted', 'zoomed']
+            .reduce(namespace_event_reducer(function(ns, ev) {
+                return 'subscribing "' + ns + '" to event "' + ev + '" which takes renderer-specific parameters';
+            }), {});
+    var inconsistent_arguments = ['end']
+            .reduce(namespace_event_reducer(function(ns, ev) {
+                return 'subscribing "' + ns + '" to event "' + ev + '" which may receive inconsistent arguments';
+            }), {});
+
+    /**
+     * Standard dc.js
+     * {@link https://github.com/dc-js/dc.js/blob/develop/web/docs/api-latest.md#dc.baseMixin baseMixin}
+     * method. Attaches an event handler to the diagram. The currently supported events are
+     * * `start()` - layout is starting
+     * * `drawn(nodes, edges)` - the node and edge elements have been rendered to the screen
+     * and can be modified through the passed d3 selections.
+     * * `end()` - diagram layout has completed.
+     * @method on
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {String} [event] - the event to subscribe to
+     * @param {Function} [f] - the event handler
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.on = function(event, f) {
+        if(arguments.length === 1)
+            return _dispatch.on(event);
+        var evns = event.split('.'),
+            warning = renderer_specific_events[evns[0]] || inconsistent_arguments[evns[0]];
+        if(warning)
+            warning(evns[1] || '')();
+        _dispatch.on(event, f);
+        return this;
+    };
+
+    /**
+     * Returns an object with current statistics on graph layout.
+     * * `nnodes` - number of nodes displayed
+     * * `nedges` - number of edges displayed
+     * @method getStats
+     * @memberof dc_graph.diagram
+     * @instance
+     * @return {}
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.getStats = function() {
+        return _stats;
+    };
+
+    /**
+     * Standard dc.js
+     * {@link https://github.com/dc-js/dc.js/blob/develop/web/docs/api-latest.md#dc.baseMixin baseMixin}
+     * method. Gets or sets the x scale.
+     * @method x
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {d3.scale} [scale]
+     * @return {d3.scale}
+     * @return {dc_graph.diagram}
+
+     **/
+    _diagram.x = property(null);
+
+    /**
+     * Standard dc.js
+     * {@link https://github.com/dc-js/dc.js/blob/develop/web/docs/api-latest.md#dc.baseMixin baseMixin}
+     * method. Gets or sets the y scale.
+     * @method y
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {d3.scale} [scale]
+     * @return {d3.scale}
+     * @return {dc_graph.diagram}
+
+     **/
+    _diagram.y = property(null);
+
+    /**
+     * Standard dc.js
+     * {@link https://github.com/dc-js/dc.js/blob/develop/web/docs/api-latest.md#dc.baseMixin baseMixin}
+     * method. Causes all charts in the chart group to be redrawn.
+     * @method redrawGroup
+     * @memberof dc_graph.diagram
+     * @instance
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.redrawGroup = function () {
+        dc.redrawAll(_chartGroup);
+    };
+
+    /**
+     * Standard dc.js
+     * {@link https://github.com/dc-js/dc.js/blob/develop/web/docs/api-latest.md#dc.baseMixin baseMixin}
+     * method. Causes all charts in the chart group to be rendered.
+     * @method renderGroup
+     * @memberof dc_graph.diagram
+     * @instance
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.renderGroup = function () {
+        dc.renderAll(_chartGroup);
+    };
+
+    /**
+     * Creates an svg marker definition for drawing edge arrow tails or heads.
+     *
+     * Sorry, this is not currently documented - please see
+     * [arrows.js](https://github.com/dc-js/dc.graph.js/blob/develop/src/arrows.js)
+     * for examples
+     * @return {dc_graph.diagram}
+     **/
+    _diagram.defineArrow = function(name, defn) {
+        if(typeof defn !== 'function')
+            throw new Error('sorry, defineArrow no longer takes specific shape parameters, and the parameters have changed too much to convert them. it takes a name and a function returning a definition - please look at arrows.js for new format');
+        _arrows[name] = defn;
+        return _diagram;
+    };
+
+    // hmm
+    _diagram.arrows = function() {
+        return _arrows;
+    };
+
+    Object.keys(dc_graph.builtin_arrows).forEach(function(aname) {
+        var defn = dc_graph.builtin_arrows[aname];
+        _diagram.defineArrow(aname, defn);
+    });
+
+    function margined_bounds() {
+        var bounds = _bounds || {left: 0, top: 0, right: 0, bottom: 0};
+        var scale = _diagram.renderer().scale();
+        return {
+            left: bounds.left - _diagram.margins().left/scale,
+            top: bounds.top - _diagram.margins().top/scale,
+            right: bounds.right + _diagram.margins().right/scale,
+            bottom: bounds.bottom + _diagram.margins().bottom/scale
+        };
+    }
+
+    // with thanks to comments in https://github.com/d3/d3/issues/1084
+    function align_left(translate, x) {
+        return translate[0] - _diagram.x()(x) + _diagram.x().range()[0];
+    }
+    function align_top(translate, y) {
+        return translate[1] - _diagram.y()(y) + _diagram.y().range()[0];
+    }
+    function align_right(translate, x) {
+        return translate[0] - _diagram.x()(x) + _diagram.x().range()[1];
+    }
+    function align_bottom(translate, y) {
+        return translate[1] - _diagram.y()(y) + _diagram.y().range()[1];;
+    }
+    function center_horizontally(translate, bounds) {
+        return (align_left(translate, bounds.left) + align_right(translate, bounds.right))/2;
+    }
+    function center_vertically(translate, bounds) {
+        return (align_top(translate, bounds.top) + align_bottom(translate, bounds.bottom))/2;
+    }
+
+    function bring_in_bounds(translate) {
+        var xDomain = _diagram.x().domain(), yDomain = _diagram.y().domain();
+        var bounds = margined_bounds();
+        var less1 = bounds.left < xDomain[0], less2 = bounds.right < xDomain[1],
+            lessExt = (bounds.right - bounds.left) < (xDomain[1] - xDomain[0]);
+        var align, nothing = 0;
+        if(less1 && less2)
+            if(lessExt)
+                align = 'left';
+        else
+            align = 'right';
+        else if(!less1 && !less2)
+            if(lessExt)
+                align = 'right';
+        else
+            align = 'left';
+        switch(align) {
+        case 'left':
+            translate[0] = align_left(translate, bounds.left);
+            break;
+        case 'right':
+            translate[0] = align_right(translate, bounds.right);
+            break;
+        default:
+            ++nothing;
+        }
+        less1 = bounds.top < yDomain[0]; less2 = bounds.bottom < yDomain[1];
+        lessExt = (bounds.bottom - bounds.top) < (yDomain[1] - yDomain[0]);
+        if(less1 && less2)
+            if(lessExt)
+                align = 'top';
+        else
+            align = 'bottom';
+        else if(!less1 && !less2)
+            if(lessExt)
+                align = 'bottom';
+        else
+            align = 'top';
+        switch(align) {
+        case 'top':
+            translate[1] = align_top(translate, bounds.top);
+            break;
+        case 'bottom':
+            translate[1] = align_bottom(translate, bounds.bottom);
+            break;
+        default:
+            ++nothing;
+        }
+        return translate;
+
+    }
+
+    _diagram.doZoom = function() {
+        if(_diagram.width_is_automatic() || _diagram.height_is_automatic())
+            detect_size_change();
+        var translate, scale = d3.event.scale;
+        if(_diagram.restrictPan())
+            _diagram.renderer().translate(translate = bring_in_bounds(d3.event.translate));
+        else translate = d3.event.translate;
+        _diagram.renderer().globalTransform(translate, scale, _animateZoom);
+        _dispatch.zoomed(translate, scale, _diagram.x().domain(), _diagram.y().domain());
+    };
+
+    _diagram.invertCoord = function(clientCoord) {
+        return [
+            _diagram.x().invert(clientCoord[0]),
+            _diagram.y().invert(clientCoord[1])
+        ];
+    };
+
+    /**
+     * Set the root SVGElement to either be any valid [d3 single
+     * selector](https://github.com/mbostock/d3/wiki/Selections#selecting-elements) specifying a dom
+     * block element such as a div; or a dom element or d3 selection. This class is called
+     * internally on diagram initialization, but be called again to relocate the diagram. However, it
+     * will orphan any previously created SVGElements.
+     * @method anchor
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {anchorSelector|anchorNode|d3.selection} [parent]
+     * @param {String} [chartGroup]
+     * @return {String|node|d3.selection}
+     * @return {dc_graph.diagram}
+     */
+    _diagram.anchor = function(parent, chartGroup) {
+        if (!arguments.length) {
+            return _anchor;
+        }
+        if (parent) {
+            if (parent.select && parent.classed) { // detect d3 selection
+                _anchor = parent.node();
+            } else {
+                _anchor = parent;
+            }
+            _diagram.root(d3.select(_anchor));
+            _diagram.root().classed(dc_graph.constants.CHART_CLASS, true);
+            dc.registerChart(_diagram, chartGroup);
+        } else {
+            throw new dc.errors.BadArgumentException('parent must be defined');
+        }
+        _chartGroup = chartGroup;
+        return _diagram;
+    };
+
+    /**
+     * Returns the internal numeric ID of the chart.
+     * @method chartID
+     * @memberof dc.baseMixin
+     * @instance
+     * @returns {String}
+     */
+    _diagram.chartID = function () {
+        return _diagram.__dcFlag__;
+    };
+
+    /**
+     * Returns the DOM id for the chart's anchored location.
+     * @method anchorName
+     * @memberof dc_graph.diagram
+     * @instance
+     * @return {String}
+     */
+    _diagram.anchorName = function () {
+        var a = _diagram.anchor();
+        if (a && a.id) {
+            return a.id;
+        }
+        if (a && a.replace) {
+            return a.replace('#', '');
+        }
+        return 'dc-graph' + _diagram.chartID();
+    };
+
+    return _diagram.anchor(parent, chartGroup);
+};
+
+dc_graph.render_svg = function() {
+    var _svg = null, _defs = null, _g = null, _nodeLayer = null, _edgeLayer = null;
+    var _animating = false; // do not refresh during animations
+    var _zoom;
+    var _renderer = {};
+
+    _renderer.rendererType = function() {
+        return 'svg';
+    };
+
+    _renderer.parent = property(null);
+
+    _renderer.renderNode = _renderer._enterNode = function(nodeEnter) {
+        if(_renderer.parent().nodeTitle())
+            nodeEnter.append('title');
+        nodeEnter.each(infer_shape(_renderer.parent()));
+        _renderer.parent().forEachShape(nodeEnter, function(shape, node) {
+            node.call(shape.create);
+        });
+        return _renderer;
+    };
+    _renderer.redrawNode = _renderer._updateNode = function(node) {
+        var changedShape = node.filter(shape_changed(_renderer.parent()));
+        changedShape.selectAll('.node-shape').remove();
+        changedShape.each(infer_shape(_renderer.parent()));
+        _renderer.parent().forEachShape(changedShape, function(shape, node) {
+            node.call(shape.create);
+        });
+        node.select('title')
+            .text(_renderer.parent().nodeTitle.eval);
+        _renderer.parent().forEachContent(node, function(contentType, node) {
+            node.call(contentType.update);
+            _renderer.parent().forEachShape(contentType.selectContent(node), function(shape, content) {
+                content
+                    .call(fit_shape(shape, _renderer.parent()));
+            });
+        });
+        _renderer.parent().forEachShape(node, function(shape, node) {
+            node.call(shape.update);
+        });
+        node.select('.node-shape')
+            .attr({
+                stroke: _renderer.parent().nodeStroke.eval,
+                'stroke-width': _renderer.parent().nodeStrokeWidth.eval,
+                'stroke-dasharray': _renderer.parent().nodeStrokeDashArray.eval,
+                fill: compose(_renderer.parent().nodeFillScale() || identity, _renderer.parent().nodeFill.eval)
+            });
+        return _renderer;
+    };
+    _renderer.redrawEdge = _renderer._updateEdge = function(edge, edgeArrows) {
+        edge
+            .attr('stroke', _renderer.parent().edgeStroke.eval)
+            .attr('stroke-width', _renderer.parent().edgeStrokeWidth.eval)
+            .attr('stroke-dasharray', _renderer.parent().edgeStrokeDashArray.eval);
+        edgeArrows
+            .attr('marker-end', function(e) {
+                var name = _renderer.parent().edgeArrowhead.eval(e),
+                    id = edgeArrow(_renderer.parent(), _renderer.parent().arrows(), e, 'head', name);
+                return id ? 'url(#' + id + ')' : null;
+            })
+            .attr('marker-start', function(e) {
+                var name = _renderer.parent().edgeArrowtail.eval(e),
+                    arrow_id = edgeArrow(_renderer.parent(), _renderer.parent().arrows(), e, 'tail', name);
+                return name ? 'url(#' + arrow_id + ')' : null;
+            })
+            .each(function(e) {
+                var fillEdgeStroke = _renderer.parent().edgeStroke.eval(e);
+                _renderer.selectAll('#' + _renderer.parent().arrowId(e, 'head'))
+                    .attr('fill', _renderer.parent().edgeStroke.eval(e));
+                _renderer.selectAll('#' + _renderer.parent().arrowId(e, 'tail'))
+                    .attr('fill', _renderer.parent().edgeStroke.eval(e));
+            });
+    };
+
+    _renderer.selectAllNodes = function(selector) {
+        selector = selector || '.node';
+        return _nodeLayer && _nodeLayer.selectAll(selector).filter(function(n) {
+            return !n.deleted;
+        }) || d3.selectAll('.foo-this-does-not-exist');
+    };
+
+    _renderer.selectAllEdges = function(selector) {
+        selector = selector || '.edge';
+        return _edgeLayer && _edgeLayer.selectAll(selector).filter(function(e) {
+            return !e.deleted;
+        }) || d3.selectAll('.foo-this-does-not-exist');
+    };
+
+    _renderer.selectAllDefs = function(selector) {
+        return _defs && _defs.selectAll(selector).filter(function(def) {
+            return !def.deleted;
+        }) || d3.selectAll('.foo-this-does-not-exist');
+    };
+
+    _renderer.resize = function(w, h) {
+        if(_svg) {
+            _svg.attr('width', w || (_renderer.parent().width_is_automatic() ? '100%' : _renderer.parent().width()))
+                .attr('height', h || (_renderer.parent().height_is_automatic() ? '100%' : _renderer.parent().height()));
+        }
+        return _renderer;
+    };
+
+    _renderer.rezoom = function(oldWidth, oldHeight, newWidth, newHeight) {
+        var scale = _zoom.scale(), translate = _zoom.translate();
+        _zoom.scale(1).translate([0,0]);
+        var xDomain = _renderer.parent().x().domain(), yDomain = _renderer.parent().y().domain();
+        _renderer.parent().x()
+            .domain([xDomain[0], xDomain[0] + (xDomain[1] - xDomain[0])*newWidth/oldWidth])
+            .range([0, newWidth]);
+        _renderer.parent().y()
+            .domain([yDomain[0], yDomain[0] + (yDomain[1] - yDomain[0])*newHeight/oldHeight])
+            .range([0, newHeight]);
+        _zoom
+            .x(_renderer.parent().x()).y(_renderer.parent().y())
+            .translate(translate).scale(scale);
+    };
+
+    _renderer.globalTransform = function(pos, scale, animate) {
+        // _translate = pos;
+        // _scale = scale;
+        var obj = _g;
+        if(animate)
+            obj = _g.transition().duration(_renderer.parent().zoomDuration());
+        obj.attr('transform', 'translate(' + pos + ')' + ' scale(' + scale + ')');
+    };
+
+    _renderer.translate = function(_) {
+        if(!arguments.length)
+            return _zoom.translate();
+        _zoom.translate(_);
+        return this;
+    };
+
+    _renderer.scale = function(_) {
+        if(!arguments.length)
+            return _zoom ? _zoom.scale() : 1;
+        _zoom.scale(_);
+        return this;
+    };
+
+    // argh
+    _renderer.commitTranslateScale = function() {
+        _zoom.event(_svg);
+    };
+
+    _renderer.zoom = function(_) {
+        if(!arguments.length)
+            return _zoom;
+        _zoom = _; // is this a good idea?
+        return _renderer;
+    };
+
+    _renderer.startRedraw = function(dispatch, wnodes, wedges) {
+        // create edge SVG elements
+        var edge = _edgeLayer.selectAll('.edge')
+                .data(wedges, _renderer.parent().edgeKey.eval);
+        var edgeEnter = edge.enter().append('svg:path')
+                .attr({
+                    class: 'edge',
+                    id: _renderer.parent().edgeId,
+                    opacity: 0
+                })
+            .each(function(e) {
+                e.deleted = false;
+            });
+        edge.exit().each(function(e) {
+            e.deleted = true;
+        }).transition()
+            .duration(_renderer.parent().stagedDuration())
+            .delay(_renderer.parent().deleteDelay())
+            .attr('opacity', 0)
+            .remove();
+
+        var edgeArrows = _edgeLayer.selectAll('.edge-arrows')
+                .data(wedges, _renderer.parent().edgeKey.eval);
+        var edgeArrowsEnter = edgeArrows.enter().append('svg:path')
+                .attr({
+                    class: 'edge-arrows',
+                    id: function(d) {
+                        return _renderer.parent().edgeId(d) + '-arrows';
+                    },
+                    fill: 'none',
+                    opacity: 0
+                });
+        edgeArrows.exit().transition()
+            .duration(_renderer.parent().stagedDuration())
+            .delay(_renderer.parent().deleteDelay())
+            .attr('opacity', 0)
+            .remove()
+            .each('end.delarrow', function(e) {
+                edgeArrow(_renderer.parent(), _renderer.parent().arrows(), e, 'head', null);
+                edgeArrow(_renderer.parent(), _renderer.parent().arrows(), e, 'tail', null);
+            });
+
+        if(_renderer.parent().edgeSort()) {
+            edge.sort(function(a, b) {
+                var as = _renderer.parent().edgeSort.eval(a), bs = _renderer.parent().edgeSort.eval(b);
+                return as < bs ? -1 : bs < as ? 1 : 0;
+            });
+        }
+
+        // another wider copy of the edge just for hover events
+        var edgeHover = _edgeLayer.selectAll('.edge-hover')
+                .data(wedges, _renderer.parent().edgeKey.eval);
+        var edgeHoverEnter = edgeHover.enter().append('svg:path')
+            .attr('class', 'edge-hover')
+            .attr('opacity', 0)
+            .attr('fill', 'none')
+            .attr('stroke', 'green')
+            .attr('stroke-width', 10)
+            .on('mouseover.diagram', function(e) {
+                _renderer.select('#' + _renderer.parent().edgeId(e) + '-label')
+                    .attr('visibility', 'visible');
+            })
+            .on('mouseout.diagram', function(e) {
+                _renderer.select('#' + _renderer.parent().edgeId(e) + '-label')
+                    .attr('visibility', 'hidden');
+            });
+        edgeHover.exit().remove();
+
+        var edgeLabels = _edgeLayer.selectAll('g.edge-label-wrapper')
+            .data(wedges, _renderer.parent().edgeKey.eval);
+        var edgeLabelsEnter = edgeLabels.enter()
+            .append('g')
+              .attr('class', 'edge-label-wrapper')
+              .attr('visibility', 'hidden')
+              .attr('id', function(e) {
+                  return _renderer.parent().edgeId(e) + '-label';
+              });
+        var textPaths = _defs.selectAll('path.edge-label-path')
+                .data(wedges, _renderer.parent().textpathId);
+        var textPathsEnter = textPaths.enter()
+                .append('svg:path').attr({
+                    class: 'edge-label-path',
+                    id: _renderer.parent().textpathId
+                });
+        edgeLabels.exit().transition()
+            .duration(_renderer.parent().stagedDuration())
+            .delay(_renderer.parent().deleteDelay())
+            .attr('opacity', 0).remove();
+
+        // create node SVG elements
+        var node = _nodeLayer.selectAll('.node')
+                .data(wnodes, _renderer.parent().nodeKey.eval);
+        var nodeEnter = node.enter().append('g')
+                .attr('class', 'node')
+                .attr('opacity', '0') // don't show until has layout
+            .each(function(n) {
+                n.deleted = false;
+            });
+        // .call(_d3cola.drag);
+
+        _renderer.renderNode(nodeEnter);
+
+        node.exit().each(function(n) {
+            n.deleted = true;
+        }).transition()
+            .duration(_renderer.parent().stagedDuration())
+            .delay(_renderer.parent().deleteDelay())
+            .attr('opacity', 0)
+            .remove();
+
+        dispatch.drawn(node, edge, edgeHover);
+
+        var drawState = {
+            node: node,
+            nodeEnter: nodeEnter,
+            edge: edge,
+            edgeEnter: edgeEnter,
+            edgeHover: edgeHover,
+            edgeHoverEnter: edgeHoverEnter,
+            edgeLabels: edgeLabels,
+            edgeLabelsEnter: edgeLabelsEnter,
+            edgeArrows: edgeArrows,
+            edgeArrowsEnter: edgeArrowsEnter,
+            textPaths: textPaths,
+            textPathsEnter: textPathsEnter
+        };
+
+        _refresh(drawState);
+
+        return drawState;
+    };
+
+    function _refresh(drawState) {
+        _renderer.redrawEdge(drawState.edge, drawState.edgeArrows);
+        _renderer.redrawNode(drawState.node);
+        _renderer.drawPorts(drawState);
+    }
+
+    _renderer.refresh = function(node, edge, edgeHover, edgeLabels, textPaths) {
+        if(_animating)
+            return this; // but what about changed attributes?
+        node = node || _renderer.selectAllNodes();
+        edge = edge || _renderer.selectAllEdges();
+        var edgeArrows = _renderer.selectAllEdges('.edge-arrows');
+        _refresh({node: node, edge: edge, edgeArrows: edgeArrows});
+
+        edgeHover = edgeHover || _renderer.selectAllEdges('.edge-hover');
+        edgeLabels = edgeLabels || _renderer.selectAllEdges('.edge-label-wrapper');
+        textPaths = textPaths || _renderer.selectAllDefs('path.edge-label-path');
+        var nullSel = d3.select(null); // no enters
+        draw(node, nullSel, edge, nullSel, edgeHover, nullSel, edgeLabels, nullSel, edgeArrows, nullSel, textPaths, nullSel, false);
+        return this;
+    };
+
+    _renderer.reposition = function(node, edge) {
+        node
+            .attr('transform', function (n) {
+                return 'translate(' + n.cola.x + ',' + n.cola.y + ')';
+            });
+        // reset edge ports
+        edge.each(function(e) {
+            e.pos.new = null;
+            e.pos.old = null;
+            _renderer.parent().calcEdgePath(e, 'new', e.source.cola.x, e.source.cola.y, e.target.cola.x, e.target.cola.y);
+            if(_renderer.parent().edgeArrowhead.eval(e))
+                _renderer.select('#' + _renderer.parent().arrowId(e, 'head'))
+                .attr('orient', function() {
+                    return e.pos.new.orienthead;
+                });
+            if(_renderer.parent().edgeArrowtail.eval(e))
+                _renderer.select('#' + _renderer.parent().arrowId(e, 'tail'))
+                .attr('orient', function() {
+                    return e.pos.new.orienttail;
+                });
+        })
+            .attr('d', generate_edge_path('new'));
+        return this;
+    };
+
+    function generate_edge_path(age, full) {
+        var field = full ? 'full' : 'path';
+        return function(e) {
+            var path = e.pos[age][field];
+            return generate_path(path.points, path.bezDegree);
+        };
+    };
+
+    function generate_edge_label_path(age) {
+        return function(e) {
+            var path = e.pos[age].path;
+            var points = path.points[path.points.length-1].x < path.points[0].x ?
+                    path.points.slice(0).reverse() : path.points;
+            return generate_path(points, path.bezDegree);
+        };
+    };
+
+    function with_rad(f) {
+        return function() {
+            return f.apply(this, arguments) + 'rad';
+        };
+    }
+
+    function unsurprising_orient_rad(oldorient, neworient) {
+        return with_rad(unsurprising_orient)(oldorient, neworient);
+   }
+
+    function has_source_and_target(e) {
+        return !!e.source && !!e.target;
+    }
+
+    _renderer.draw = function(drawState, animatePositions) {
+        draw(drawState.node, drawState.nodeEnter,
+             drawState.edge, drawState.edgeEnter,
+             drawState.edgeHover, drawState.edgeHoverEnter,
+             drawState.edgeLabels, drawState.edgeLabelsEnter,
+             drawState.edgeArrows, drawState.edgeArrowsEnter,
+             drawState.textPaths, drawState.textPathsEnter,
+             animatePositions);
+    };
+
+    function draw(node, nodeEnter, edge, edgeEnter, edgeHover, edgeHoverEnter,
+                  edgeLabels, edgeLabelsEnter, edgeArrows, edgeArrowsEnter,
+                  textPaths, textPathsEnter, animatePositions) {
+        console.assert(edge.data().every(has_source_and_target));
+
+        var nodeEntered = {};
+        nodeEnter
+            .each(function(n) {
+                nodeEntered[_renderer.parent().nodeKey.eval(n)] = true;
+            })
+            .attr('transform', function (n) {
+                // start new nodes at their final position
+                return 'translate(' + n.cola.x + ',' + n.cola.y + ')';
+            });
+        var ntrans = node
+                .transition()
+                .duration(_renderer.parent().stagedDuration())
+                .delay(function(n) {
+                    return _renderer.parent().stagedDelay(nodeEntered[_renderer.parent().nodeKey.eval(n)]);
+                })
+                .attr('opacity', _renderer.parent().nodeOpacity.eval);
+        if(animatePositions)
+            ntrans
+                .attr('transform', function (n) {
+                    return 'translate(' + n.cola.x + ',' + n.cola.y + ')';
+                })
+                .each('end.record', function(n) {
+                    n.prevX = n.cola.x;
+                    n.prevY = n.cola.y;
+                });
+
+        // recalculate edge positions
+        edge.each(function(e) {
+            e.pos.new = null;
+        });
+        edge.each(function(e) {
+            if(e.cola.points) {
+                e.pos.new = place_arrows_on_spline(_renderer.parent(), e, e.cola.points);
+            }
+            else {
+                if(!e.pos.old)
+                    _renderer.parent().calcEdgePath(e, 'old', e.source.prevX || e.source.cola.x, e.source.prevY || e.source.cola.y,
+                                   e.target.prevX || e.target.cola.x, e.target.prevY || e.target.cola.y);
+                if(!e.pos.new)
+                    _renderer.parent().calcEdgePath(e, 'new', e.source.cola.x, e.source.cola.y, e.target.cola.x, e.target.cola.y);
+            }
+            if(e.pos.old) {
+                if(e.pos.old.path.bezDegree !== e.pos.new.path.bezDegree ||
+                   e.pos.old.path.points.length !== e.pos.new.path.points.length) {
+                    //console.log('old', e.pos.old.path.points.length, 'new', e.pos.new.path.points.length);
+                    if(is_one_segment(e.pos.old.path)) {
+                        e.pos.new.path.points = as_bezier3(e.pos.new.path);
+                        e.pos.old.path.points = split_bezier_n(as_bezier3(e.pos.old.path),
+                                                               (e.pos.new.path.points.length-1)/3);
+                        e.pos.old.path.bezDegree = e.pos.new.bezDegree = 3;
+                    }
+                    else if(is_one_segment(e.pos.new.path)) {
+                        e.pos.old.path.points = as_bezier3(e.pos.old.path);
+                        e.pos.new.path.points = split_bezier_n(as_bezier3(e.pos.new.path),
+                                                               (e.pos.old.path.points.length-1)/3);
+                        e.pos.old.path.bezDegree = e.pos.new.bezDegree = 3;
+                    }
+                    else console.warn("don't know how to interpolate two multi-segments");
+                }
+            }
+            else
+                e.pos.old = e.pos.new;
+        });
+
+        var edgeEntered = {};
+        edgeEnter
+            .each(function(e) {
+                edgeEntered[_renderer.parent().edgeKey.eval(e)] = true;
+            })
+            .attr('d', generate_edge_path(_renderer.parent().stageTransitions() === 'modins' ? 'new' : 'old'));
+
+        edgeArrowsEnter
+            .each(function(e) {
+                // if staging transitions, just fade new edges in at new position
+                // else start new edges at old positions of nodes, if any, else new positions
+                var age = _renderer.parent().stageTransitions() === 'modins' ? 'new' : 'old';
+                if(_renderer.parent().edgeArrowhead.eval(e))
+                    _renderer.select('#' + _renderer.parent().arrowId(e, 'head'))
+                    .attr('orient', function() {
+                        return e.pos[age].orienthead;
+                    });
+                if(_renderer.parent().edgeArrowtail.eval(e))
+                    _renderer.select('#' + _renderer.parent().arrowId(e, 'tail'))
+                    .attr('orient', function() {
+                        return e.pos[age].orienttail;
+                    });
+            })
+            .attr('d', generate_edge_path(_renderer.parent().stageTransitions() === 'modins' ? 'new' : 'old', true));
+
+        edgeArrows
+            .each(function(e) {
+                if(_renderer.parent().edgeArrowhead.eval(e))
+                    _renderer.select('#' + _renderer.parent().arrowId(e, 'head'))
+                    .attr('orient', unsurprising_orient_rad(e.pos.old.orienthead, e.pos.new.orienthead))
+                    .transition().duration(_renderer.parent().stagedDuration())
+                    .delay(_renderer.parent().stagedDelay(false))
+                    .attr('orient', function() {
+                        return e.pos.new.orienthead;
+                    });
+                if(_renderer.parent().edgeArrowtail.eval(e))
+                    _renderer.select('#' + _renderer.parent().arrowId(e, 'tail'))
+                    .attr('orient', unsurprising_orient_rad(e.pos.old.orienttail, e.pos.new.orienttail))
+                    .transition().duration(_renderer.parent().stagedDuration())
+                    .delay(_renderer.parent().stagedDelay(false))
+                    .attr('orient', function() {
+                        return e.pos.new.orienttail;
+                    });
+            });
+
+        var etrans = edge
+              .transition()
+                .duration(_renderer.parent().stagedDuration())
+                .delay(function(e) {
+                    return _renderer.parent().stagedDelay(edgeEntered[_renderer.parent().edgeKey.eval(e)]);
+                })
+                .attr('opacity', _renderer.parent().edgeOpacity.eval);
+        var arrowtrans = edgeArrows
+              .transition()
+                .duration(_renderer.parent().stagedDuration())
+                .delay(function(e) {
+                    return _renderer.parent().stagedDelay(edgeEntered[_renderer.parent().edgeKey.eval(e)]);
+                })
+                .attr('opacity', _renderer.parent().edgeOpacity.eval);
+        (animatePositions ? etrans : edge)
+            .attr('d', function(e) {
+                var when = _renderer.parent().stageTransitions() === 'insmod' &&
+                        edgeEntered[_renderer.parent().edgeKey.eval(e)] ? 'old' : 'new';
+                return generate_edge_path(when)(e);
+            });
+        (animatePositions ? arrowtrans : edgeArrows)
+            .attr('d', function(e) {
+                var when = _renderer.parent().stageTransitions() === 'insmod' &&
+                        edgeEntered[_renderer.parent().edgeKey.eval(e)] ? 'old' : 'new';
+                return generate_edge_path(when, true)(e);
+            });
+        var elabels = edgeLabels
+            .selectAll('text').data(function(e) {
+                var labels = _renderer.parent().edgeLabel.eval(e);
+                if(!labels)
+                    return [];
+                else if(typeof labels === 'string')
+                    return [labels];
+                else return labels;
+            });
+        elabels.enter()
+          .append('text')
+            .attr({
+                'class': 'edge-label',
+                'text-anchor': 'middle',
+                dy: function(_, i) {
+                    return i * _renderer.parent().edgeLabelSpacing.eval(this.parentNode) -2;
+                }
+            })
+          .append('textPath')
+            .attr('startOffset', '50%');
+        elabels
+          .select('textPath')
+            .html(function(t) { return t; })
+            .attr('opacity', function() {
+                return _renderer.parent().edgeOpacity.eval(d3.select(this.parentNode.parentNode).datum());
+            })
+            .attr('xlink:href', function(e) {
+                var id = _renderer.parent().textpathId(d3.select(this.parentNode.parentNode).datum());
+                // angular on firefox needs absolute paths for fragments
+                return window.location.href.split('#')[0] + '#' + id;
+            });
+        textPathsEnter
+            .attr('d', generate_edge_label_path(_renderer.parent().stageTransitions() === 'modins' ? 'new' : 'old'));
+        var textTrans = textPaths.transition()
+            .duration(_renderer.parent().stagedDuration())
+            .delay(function(e) {
+                return _renderer.parent().stagedDelay(edgeEntered[_renderer.parent().edgeKey.eval(e)]);
+            });
+        if(animatePositions)
+            textTrans
+            .attr('d', function(e) {
+                var when = _renderer.parent().stageTransitions() === 'insmod' &&
+                        edgeEntered[_renderer.parent().edgeKey.eval(e)] ? 'old' : 'new';
+                return generate_edge_label_path(when)(e);
+            });
+        if(_renderer.parent().stageTransitions() === 'insmod' && animatePositions) {
+            // inserted edges transition twice in insmod mode
+            if(_renderer.parent().stagedDuration() >= 50) {
+                etrans = etrans.transition()
+                    .duration(_renderer.parent().stagedDuration())
+                    .attr('d', generate_edge_path('new'));
+                textTrans = textTrans.transition()
+                    .duration(_renderer.parent().stagedDuration())
+                    .attr('d', generate_edge_label_path('new'));
+                arrowtrans.transition()
+                    .duration(_renderer.parent().stagedDuration())
+                    .attr('d', generate_edge_path('new', true));
+            } else {
+                // if transitions are too short, we run into various problems,
+                // from transitions not completing to objects not found
+                // so don't try to chain in that case
+                // this also helped once: d3.timer.flush();
+                etrans
+                    .attr('d', generate_edge_path('new'));
+                textTrans
+                    .attr('d', generate_edge_path('new'));
+                arrowtrans
+                    .attr('d', generate_edge_path('new', true));
+            }
+        }
+
+        // signal layout done when all transitions complete
+        // because otherwise client might start another layout and lock the processor
+        _animating = true;
+        if(!_renderer.parent().showLayoutSteps())
+            endall([ntrans, etrans, textTrans],
+                   function() {
+                       _animating = false;
+                       _renderer.parent().layoutDone(true);
+                   });
+
+        if(animatePositions)
+            edgeHover.attr('d', generate_edge_path('new'));
+
+        edge.each(function(e) {
+            e.pos.old = e.pos.new;
+        });
+    }
+
+    // wait on multiple transitions, adapted from
+    // http://stackoverflow.com/questions/10692100/invoke-a-callback-at-the-end-of-a-transition
+    function endall(transitions, callback) {
+        if (transitions.every(function(transition) { return transition.size() === 0; }))
+            callback();
+        var n = 0;
+        transitions.forEach(function(transition) {
+            transition
+                .each(function() { ++n; })
+                .each('end.all', function() { if (!--n) callback(); });
+        });
+    }
+
+    _renderer.isRendered = function() {
+        return !!_svg;
+    };
+
+    _renderer.initializeDrawing = function () {
+        _renderer.resetSvg();
+        _g = _svg.append('g')
+            .attr('class', 'draw');
+
+        var layers = ['edge-layer', 'node-layer'];
+        if(_renderer.parent().edgesInFront())
+            layers.reverse();
+        _g.selectAll('g').data(layers)
+          .enter().append('g')
+            .attr('class', function(l) { return l; });
+        _edgeLayer = _g.selectAll('g.edge-layer');
+        _nodeLayer = _g.selectAll('g.node-layer');
+        return this;
+    };
+
+
+    /**
+     * Standard dc.js
+     * {@link https://github.com/dc-js/dc.js/blob/develop/web/docs/api-latest.md#dc.baseMixin baseMixin}
+     * method. Execute a d3 single selection in the diagram's scope using the given selector
+     * and return the d3 selection. Roughly the same as
+     * ```js
+     * d3.select('#diagram-id').select(selector)
+     * ```
+     * Since this function returns a d3 selection, it is not chainable. (However, d3 selection
+     * calls can be chained after it.)
+     * @method select
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {String} [selector]
+     * @return {d3.selection}
+     * @return {dc_graph.diagram}
+     **/
+    _renderer.select = function (s) {
+        return _renderer.parent().root().select(s);
+    };
+
+    /**
+     * Standard dc.js
+     * {@link https://github.com/dc-js/dc.js/blob/develop/web/docs/api-latest.md#dc.baseMixin baseMixin}
+     * method. Selects all elements that match the d3 single selector in the diagram's scope,
+     * and return the d3 selection. Roughly the same as
+     *
+     * ```js
+     * d3.select('#diagram-id').selectAll(selector)
+     * ```
+     *
+     * Since this function returns a d3 selection, it is not chainable. (However, d3 selection
+     * calls can be chained after it.)
+     * @method selectAll
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {String} [selector]
+     * @return {d3.selection}
+     * @return {dc_graph.diagram}
+     **/
+    _renderer.selectAll = function (s) {
+        return _renderer.parent().root() ? _renderer.parent().root().selectAll(s) : null;
+    };
+
+    _renderer.selectNodePortsOfStyle = function(node, style) {
+        return node.selectAll('g.port').filter(function(p) {
+            return _renderer.parent().portStyleName.eval(p) === style;
+        });
+    };
+
+    _renderer.drawPorts = function(drawState) {
+        var nodePorts = _renderer.parent().nodePorts();
+        if(!nodePorts)
+            return;
+        _renderer.parent().portStyle.enum().forEach(function(style) {
+            var nodePorts2 = {};
+            for(var nid in nodePorts)
+                nodePorts2[nid] = nodePorts[nid].filter(function(p) {
+                    return _renderer.parent().portStyleName.eval(p) === style;
+                });
+            var port = _renderer.selectNodePortsOfStyle(drawState.node, style);
+            _renderer.parent().portStyle(style).drawPorts(port, nodePorts2, drawState.node);
+        });
+    };
+
+    _renderer.fireTSEvent = function(dispatch, drawState) {
+        dispatch.transitionsStarted(drawState.node, drawState.edge, drawState.edgeHover);
+    };
+
+    _renderer.calculateBounds = function(drawState) {
+        if(!drawState.node.size())
+            return null;
+        return _renderer.parent().calculateBounds(drawState.node.data(), drawState.edge.data());
+    };
+
+    /**
+     * Standard dc.js
+     * {@link https://github.com/dc-js/dc.js/blob/develop/web/docs/api-latest.md#dc.baseMixin baseMixin}
+     * method. Returns the top `svg` element for this specific diagram. You can also pass in a new
+     * svg element, but setting the svg element on a diagram may have unexpected consequences.
+     * @method svg
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {d3.selection} [selection]
+     * @return {d3.selection}
+     * @return {dc_graph.diagram}
+     **/
+    _renderer.svg = function (_) {
+        if (!arguments.length) {
+            return _svg;
+        }
+        _svg = _;
+        return _renderer;
+    };
+
+    /**
+     * Returns the top `g` element for this specific diagram. This method is usually used to
+     * retrieve the g element in order to overlay custom svg drawing
+     * programatically. **Caution**: The root g element is usually generated internally, and
+     * resetting it might produce unpredictable results.
+     * @method g
+     * @memberof dc_graph.diagram
+     * @instance
+     * @param {d3.selection} [selection]
+     * @return {d3.selection}
+     * @return {dc_graph.diagram}
+
+     **/
+    _renderer.g = function (_) {
+        if (!arguments.length) {
+            return _g;
+        }
+        _g = _;
+        return _renderer;
+    };
+
+
+    /**
+     * Standard dc.js
+     * {@link https://github.com/dc-js/dc.js/blob/develop/web/docs/api-latest.md#dc.baseMixin baseMixin}
+     * method. Remove the diagram's SVG elements from the dom and recreate the container SVG
+     * element.
+     * @method resetSvg
+     * @memberof dc_graph.diagram
+     * @instance
+     * @return {dc_graph.diagram}
+     **/
+    _renderer.resetSvg = function () {
+        // we might be re-initialized in a div, in which case
+        // we already have an <svg> element to delete
+        var svg = _svg || _renderer.select('svg');
+        svg.remove();
+        _svg = null;
+        //_renderer.parent().x(null).y(null);
+        return generateSvg();
+    };
+
+    _renderer.addOrRemoveDef = function(id, whether, tag, onEnter) {
+        var data = whether ? [0] : [];
+        var sel = _defs.selectAll('#' + id).data(data);
+
+        var selEnter = sel
+            .enter().append(tag)
+              .attr('id', id);
+        if(selEnter.size() && onEnter)
+            selEnter.call(onEnter);
+        sel.exit().remove();
+        return sel;
+    };
+
+    function enableZoom() {
+        _svg.call(_zoom);
+        _svg.on('dblclick.zoom', null);
+    }
+    function disableZoom() {
+        _svg.on('.zoom', null);
+    }
+
+    function generateSvg() {
+        _svg = _renderer.parent().root().append('svg');
+        _renderer.resize();
+
+        _defs = _svg.append('svg:defs');
+
+        _zoom = d3.behavior.zoom()
+            .on('zoom.diagram', _renderer.parent().doZoom)
+            .x(_renderer.parent().x()).y(_renderer.parent().y())
+            .scaleExtent(_renderer.parent().zoomExtent());
+        if(_renderer.parent().mouseZoomable()) {
+            var mod, mods;
+            var brush = _renderer.parent().child('brush');
+            if((mod = _renderer.parent().modKeyZoom())) {
+                if (Array.isArray (mod))
+                    mods = mod.slice ();
+                else if (typeof mod === "string")
+                    mods = [mod];
+                else
+                    mods = ['Alt'];
+                var mouseDown = false, modDown = false, zoomEnabled = false;
+                _svg.on('mousedown.modkey-zoom', function() {
+                    mouseDown = true;
+                }).on('mouseup.modkey-zoom', function() {
+                    mouseDown = false;
+                    if(!mouseDown && !modDown && zoomEnabled) {
+                        zoomEnabled = false;
+                        disableZoom();
+                        if(brush)
+                            brush.activate();
+                    }
+                });
+                d3.select(document)
+                    .on('keydown.modkey-zoom-' + _renderer.parent().anchorName(), function() {
+                        if(mods.indexOf (d3.event.key) > -1) {
+                            modDown = true;
+                            if(!mouseDown) {
+                                zoomEnabled = true;
+                                enableZoom();
+                                if(brush)
+                                    brush.deactivate();
+                            }
+                        }
+                    })
+                    .on('keyup.modkey-zoom-' + _renderer.parent().anchorName(), function() {
+                        if(mods.indexOf (d3.event.key) > -1) {
+                            modDown = false;
+                            if(!mouseDown) {
+                                zoomEnabled = false;
+                                disableZoom();
+                                if(brush)
+                                    brush.activate();
+                            }
+                        }
+                    });
+            }
+            else enableZoom();
+        }
+
+        return _svg;
+    }
+
+    _renderer.animating = function() {
+        return _animating;
+    };
+
+    return _renderer;
+};
+
+
+dc_graph.render_webgl = function() {
+    //var _svg = null, _defs = null, _g = null, _nodeLayer = null, _edgeLayer = null;
+    var _camera, _scene, _webgl_renderer;
+    var _directionalLight, _ambientLight;
+    var _controls;
+    var _sphereGeometry;
+    var _nodes = {}, _edges = {};
+    var _animating = false; // do not refresh during animations
+    var _renderer = {};
+
+    _renderer.rendererType = function() {
+        return 'webgl';
+    };
+
+    _renderer.parent = property(null);
+
+    _renderer.isRendered = function() {
+        return !!_camera;
+    };
+
+    _renderer.resize = function(w, h) {
+        return _renderer;
+    };
+
+    _renderer.rezoom = function(oldWidth, oldHeight, newWidth, newHeight) {
+        return _renderer;
+    };
+
+    _renderer.globalTransform = function(pos, scale, animate) {
+        return _renderer;
+    };
+
+    _renderer.translate = function(_) {
+        if(!arguments.length)
+            return [0,0];
+        return _renderer;
+    };
+
+    _renderer.scale = function(_) {
+        if(!arguments.length)
+            return 1;
+        return _renderer;
+    };
+
+    // argh
+    _renderer.commitTranslateScale = function() {
+    };
+
+    _renderer.initializeDrawing = function () {
+        if(_scene) // just treat it as a redraw
+            return _renderer;
+
+        _camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 10000);
+        _camera.up = new THREE.Vector3(0, 0, 1);
+
+        _scene = new THREE.Scene();
+
+        _sphereGeometry = new THREE.SphereBufferGeometry(10, 32, 32);
+
+        _directionalLight = new THREE.DirectionalLight(0xffffff, 1);
+        _directionalLight.position.set(-1, -1, 1).normalize();
+        _scene.add(_directionalLight);
+
+        _ambientLight = new THREE.AmbientLight(0xaaaaaa);
+        _scene.add(_ambientLight);
+
+        _webgl_renderer = new THREE.WebGLRenderer({ antialias: true });
+        _webgl_renderer.setPixelRatio(window.devicePixelRatio);
+        var boundRect = _renderer.parent().root().node().getBoundingClientRect();
+        _webgl_renderer.setSize(boundRect.width, boundRect.height);
+        _renderer.parent().root().node().appendChild(_webgl_renderer.domElement);
+
+        _controls = new THREE.OrbitControls(_camera, _webgl_renderer.domElement);
+        _controls.minDistance = 300;
+        _controls.maxDistance = 1000;
+        return _renderer;
+    };
+
+    _renderer.startRedraw = function(dispatch, wnodes, wedges) {
+        wnodes.forEach(infer_shape(_renderer.parent()));
+        var rnodes = regenerate_objects(_nodes, wnodes, null, function(n) {
+            return _renderer.parent().nodeKey.eval(n);
+        }, function(rn, n) {
+            rn.wnode = n;
+        }, null, function(wnode, rnode) {
+            _scene.remove(rnode.mesh);
+            //rnode.mesh.dispose();
+            rnode.material.dispose();
+        });
+        var redges = regenerate_objects(_edges, wedges, null, function(e) {
+            return _renderer.parent().edgeKey.eval(e);
+        }, function(re, e) {
+            re.wedge = e;
+        }, null, function(wedge, redge) {
+            _scene.remove(redge.mesh);
+            //redge.mesh.dispose();
+            redge.geometry.dispose();
+            redge.material.dispose();
+        });
+        animate();
+        return {wnodes: wnodes, wedges: wedges, rnodes: rnodes, redges: redges};
+    };
+
+    function color_to_int(color) {
+        // it better be 6 byte hex RGB
+        if(color.length !== 7 || color[0] !== '#') {
+            console.warn("don't know how to use color " + color);
+            color = '#888888';
+        }
+        return parseInt(color.slice(1), 16);
+    }
+    _renderer.color_to_int = color_to_int;
+
+    _renderer.draw = function(drawState, animatePositions) {
+        drawState.wedges.forEach(function(e) {
+            if(!e.pos.old)
+                _renderer.parent().calcEdgePath(e, 'old', e.source.prevX || e.source.cola.x, e.source.prevY || e.source.cola.y,
+                                                e.target.prevX || e.target.cola.x, e.target.prevY || e.target.cola.y);
+            if(!e.pos.new)
+                _renderer.parent().calcEdgePath(e, 'new', e.source.cola.x, e.source.cola.y, e.target.cola.x, e.target.cola.y);
+        });
+
+        var MULT = _renderer.multiplier();
+        drawState.rnodes.forEach(function(rn) {
+            var color = _renderer.parent().nodeFill.eval(rn.wnode);
+            var add = false;
+            if(!rn.mesh) {
+                add = true;
+                if(_renderer.parent().nodeFillScale())
+                    color = _renderer.parent().nodeFillScale()(color);
+                var cint = color_to_int(color);
+                rn.material = new THREE.MeshLambertMaterial({color: cint});
+                rn.mesh = new THREE.Mesh(_sphereGeometry, rn.material);
+                rn.mesh.name = _renderer.parent().nodeKey.eval(rn.wnode);
+            }
+            rn.mesh.position.x = rn.wnode.cola.x * MULT;
+            rn.mesh.position.y = -rn.wnode.cola.y * MULT;
+            rn.mesh.position.z = rn.wnode.cola.z * MULT || 0;
+            if(add)
+                _scene.add(rn.mesh);
+        });
+
+        var xext = d3.extent(drawState.wnodes, function(n) { return n.cola.x * MULT; }),
+            yext = d3.extent(drawState.wnodes, function(n) { return -n.cola.y * MULT; }),
+            zext = d3.extent(drawState.wnodes, function(n) { return n.cola.z * MULT || 0; });
+        var cx = (xext[0] + xext[1])/2,
+            cy = (yext[0] + yext[1])/2,
+            cz = (zext[0] + zext[1])/2;
+
+        drawState.center = [cx, cy, cz];
+        drawState.extents = [xext, yext, zext];
+        _controls.target.set(cx, cy, cz);
+        _controls.update();
+
+        var vertices = [];
+        drawState.redges.forEach(function(re) {
+            if(!re.wedge.source || !re.wedge.target)
+                return;
+            var a = re.wedge.source.cola, b = re.wedge.target.cola;
+            var add = false;
+            var width = _renderer.parent().edgeStrokeWidth.eval(re.wedge);
+            if(!re.mesh) {
+                add = true;
+                var color = _renderer.parent().edgeStroke.eval(re.wedge);
+                var cint = color_to_int(color);
+                re.material = new THREE.MeshLambertMaterial({ color: cint });
+                re.curve = new THREE.LineCurve3(
+                    new THREE.Vector3(a.x*MULT, -a.y*MULT, a.z*MULT || 0),
+                    new THREE.Vector3(b.x*MULT, -b.y*MULT, b.z*MULT || 0));
+                re.geometry = new THREE.TubeBufferGeometry(re.curve, 20, width/2, 8, false);
+                re.mesh = new THREE.Mesh(re.geometry, re.material);
+                re.mesh.name = _renderer.parent().edgeKey.eval(re.wedge);
+            } else {
+                re.curve = new THREE.LineCurve3(
+                    new THREE.Vector3(a.x*MULT, -a.y*MULT, a.z*MULT || 0),
+                    new THREE.Vector3(b.x*MULT, -b.y*MULT, b.z*MULT || 0));
+                re.geometry.dispose();
+                re.geometry = new THREE.TubeBufferGeometry(re.curve, 20, width/2, 8, false);
+                re.mesh.geometry = re.geometry;
+            }
+            if(add)
+                _scene.add(re.mesh);
+        });
+        _animating = false;
+        _renderer.parent().layoutDone(true);
+        return _renderer;
+    };
+
+    function animate() {
+        window.requestAnimationFrame(animate);
+        render();
+    }
+
+    function render() {
+        _webgl_renderer.render(_scene, _camera);
+    }
+
+    _renderer.drawPorts = function(drawState) {
+        var nodePorts = _renderer.parent().nodePorts();
+        if(!nodePorts)
+            return;
+        _renderer.parent().portStyle.enum().forEach(function(style) {
+            var nodePorts2 = {};
+            for(var nid in nodePorts)
+                nodePorts2[nid] = nodePorts[nid].filter(function(p) {
+                    return _renderer.parent().portStyleName.eval(p) === style;
+                });
+            // not implemented
+            var port = _renderer.selectNodePortsOfStyle(drawState.node, style);
+            //_renderer.parent().portStyle(style).drawPorts(port, nodePorts2, drawState.node);
+        });
+    };
+
+    _renderer.fireTSEvent = function(dispatch, drawState) {
+        dispatch.transitionsStarted(_scene, drawState);
+    };
+
+    _renderer.calculateBounds = function(drawState) {
+        if(!drawState.wnodes.length)
+            return null;
+        return _renderer.parent().calculateBounds(drawState.wnodes, drawState.wedges);
+    };
+
+    _renderer.refresh = function(node, edge, edgeHover, edgeLabels, textPaths) {
+        if(_animating)
+            return _renderer; // but what about changed attributes?
+        return _renderer;
+    };
+
+    _renderer.reposition = function(node, edge) {
+        return _renderer;
+    };
+
+    function has_source_and_target(e) {
+        return !!e.source && !!e.target;
+    }
+
+    _renderer.animating = function() {
+        return _animating;
+    };
+
+    _renderer.multiplier = property(3);
+
+    return _renderer;
+};
+
+
+dc_graph.spawn_engine = function(layout, args, worker) {
+    args = args || {};
+    worker = worker && !!window.Worker;
+    var engine = dc_graph.engines.instantiate(layout, args, worker);
+    if(!engine) {
+        console.warn('layout engine ' + layout + ' not found; using default ' + dc_graph._default_engine);
+        engine = dc_graph.engines.instantiate(dc_graph._default_engine, args, worker);
+    }
+    return engine;
+};
+
+dc_graph._engines = [
+    {
+        name: 'dagre',
+        params: ['rankdir'],
+        instantiate: function() {
+            return dc_graph.dagre_layout();
+        }
+    },
+    {
+        name: 'd3force',
+        instantiate: function() {
+            return dc_graph.d3_force_layout();
+        }
+    },
+    {
+        name: 'd3v4force',
+        instantiate: function() {
+            return dc_graph.d3v4_force_layout();
+        }
+    },
+    {
+        name: 'tree',
+        instantiate: function() {
+            return dc_graph.tree_layout();
+        }
+    },
+    {
+        names: ['circo', 'dot', 'neato', 'osage', 'twopi', 'fdp'],
+        instantiate: function(layout, args) {
+            return dc_graph.graphviz_layout(null, layout, args.server);
+        }
+    },
+    {
+        name: 'cola',
+        params: ['lengthStrategy'],
+        instantiate: function() {
+            return dc_graph.cola_layout();
+        }
+    },
+    {
+        name: 'manual',
+        instantiate: function() {
+            return dc_graph.manual_layout();
+        }
+    },
+    {
+        name: 'flexbox',
+        instantiate: function() {
+            return dc_graph.flexbox_layout();
+        }
+    },
+    {
+        name: 'layered',
+        instantiate: function() {
+            return dc_graph.layered_layout();
+        }
+    }
+];
+dc_graph._default_engine = 'cola';
+
+dc_graph.engines = {
+    entry_pred: function(layoutName) {
+        return function(e) {
+            return e.name && e.name === layoutName || e.names && e.names.includes(layoutName);
+        };
+    },
+    get: function(layoutName) {
+        return dc_graph._engines.find(this.entry_pred(layoutName));
+    },
+    instantiate: function(layout, args, worker) {
+        var entry = this.get(layout);
+        if(!entry)
+            return null;
+        var engine = entry.instantiate(layout, args),
+            params = entry.params || [];
+        params.forEach(function(p) {
+            if(args[p])
+                engine[p](args[p]);
+        });
+        if(engine.supportsWebworker && engine.supportsWebworker() && worker)
+            engine = dc_graph.webworker_layout(engine);
+        return engine;
+    },
+    available: function() {
+        return dc_graph._engines.reduce(function(avail, entry) {
+            return avail.concat(entry.name ? [entry.name] : entry.names);
+        }, []);
+    },
+    unregister: function(layoutName) {
+        // meh. this is a bit much. there is such a thing as making the api too "easy".
+        var i = dc_graph._engines.findIndex(this.entry_pred(layoutName));
+        var remove = false;
+        if(i < 0)
+            return false;
+        var entry = dc_graph._engines[i];
+        if(entry.name === layoutName)
+            remove = true;
+        else {
+            var j = entry.names.indexOf(layoutName);
+            if(j >= 0)
+                entry.names.splice(j, 1);
+            else
+                console.warn('search for engine failed', layoutName);
+            if(entry.names.length === 0)
+                remove = true;
+        }
+        if(remove)
+            dc_graph._engines.splice(i, 1);
+        return true;
+    },
+    register: function(entry) {
+        var that = this;
+        if(!entry.instantiate) {
+            console.error('engine definition needs instantiate: function(layout, args) { ... }');
+            return this;
+        }
+        if(entry.name)
+            this.unregister(entry.name);
+        else if(entry.names)
+            entry.names.forEach(function(layoutName) {
+                that.unregister(layoutName);
+            });
+        else {
+            console.error('engine definition needs name or names[]');
+            return this;
+        }
+        dc_graph._engines.push(entry);
+        return this;
+    }
+};
+
+var _workers = {};
+var NUMBER_RESULTS = 3;
+function create_worker(layoutAlgorithm) {
+    if(!_workers[layoutAlgorithm]) {
+        var worker = _workers[layoutAlgorithm] = {
+            worker: new Worker(script_path() + 'dc.graph.' + layoutAlgorithm + '.worker.js'),
+            layouts: {}
+        };
+        worker.worker.onmessage = function(e) {
+            var layoutId = e.data.layoutId;
+            if(!worker.layouts[layoutId])
+                throw new Error('layoutId "' + layoutId + '" unknown!');
+            var engine = worker.layouts[layoutId].getEngine();
+            if(e.data.args.length > NUMBER_RESULTS && engine.processExtraWorkerResults)
+                engine.processExtraWorkerResults.apply(engine, e.data.args.slice(NUMBER_RESULTS));
+            worker.layouts[layoutId].dispatch()[e.data.response].apply(null, e.data.args);
+        };
+    }
+    return _workers[layoutAlgorithm];
+}
+
+dc_graph.webworker_layout = function(layoutEngine) {
+    var _tick, _done, _dispatch = d3.dispatch('init', 'start', 'tick', 'end');
+    var _worker = create_worker(layoutEngine.layoutAlgorithm());
+    var engine = {};
+    _worker.layouts[layoutEngine.layoutId()] = engine;
+
+    engine.parent = function(parent) {
+        if(layoutEngine.parent)
+            layoutEngine.parent(parent);
+    };
+    engine.init = function(options) {
+        options = layoutEngine.optionNames().reduce(
+            function(options, option) {
+                options[option] = layoutEngine[option]();
+                return options;
+            }, options);
+        if(layoutEngine.propagateOptions)
+            layoutEngine.propagateOptions(options);
+        _worker.worker.postMessage({
+            command: 'init',
+            args: {
+                layoutId: layoutEngine.layoutId(),
+                options: options
+            }
+        });
+        return this;
+    };
+    engine.data = function(graph, nodes, edges, clusters, constraints) {
+        _worker.worker.postMessage({
+            command: 'data',
+            args: {
+                layoutId: layoutEngine.layoutId(),
+                graph: graph,
+                nodes: nodes,
+                edges: edges,
+                clusters: clusters,
+                constraints: constraints
+            }
+        });
+    };
+    engine.start = function() {
+        _worker.worker.postMessage({
+            command: 'start',
+            args: {
+                layoutId: layoutEngine.layoutId()
+            }
+        });
+    };
+    engine.stop = function() {
+        _worker.worker.postMessage({
+            command: 'stop',
+            args: {
+                layoutId: layoutEngine.layoutId()
+            }
+        });
+        return this;
+    };
+    // stopgap while layout options are still on diagram
+    engine.getEngine = function() {
+        return layoutEngine;
+    };
+    // somewhat sketchy - do we want this object to be transparent or not?
+    var passthroughs = ['layoutAlgorithm', 'populateLayoutNode', 'populateLayoutEdge',
+                        'rankdir', 'ranksep'];
+    passthroughs.concat(layoutEngine.optionNames(),
+                        layoutEngine.passThru ? layoutEngine.passThru() : []).forEach(function(name) {
+        engine[name] = function() {
+            var ret = layoutEngine[name].apply(layoutEngine, arguments);
+            return arguments.length ? this : ret;
+        };
+    });
+    engine.on = function(event, f) {
+        if(arguments.length === 1)
+            return _dispatch.on(event);
+        _dispatch.on(event, f);
+        return this;
+    };
+    engine.dispatch = function() {
+        return _dispatch;
+    };
+    return engine;
+};
+
+/**
+ * `dc_graph.graphviz_attrs defines a basic set of attributes which layout engines should
+ * implement - although these are not required, they make it easier for clients and
+ * modes (like expand_collapse) to work with multiple layout engines.
+ *
+ * these attributes are {@link http://www.graphviz.org/doc/info/attrs.html from graphviz}
+ * @class graphviz_attrs
+ * @memberof dc_graph
+ * @return {Object}
+ **/
+dc_graph.graphviz_attrs = function() {
+    return {
+        /**
+         * Direction to draw ranks.
+         * @method rankdir
+         * @memberof dc_graph.graphviz_attrs
+         * @instance
+         * @param {String} [rankdir='TB'] 'TB', 'LR', 'BT', or 'RL'
+         **/
+        rankdir: property('TB'),
+        /**
+         * Spacing in between nodes in the same rank.
+         * @method nodesep
+         * @memberof dc_graph.graphviz_attrs
+         * @instance
+         * @param {String} [nodesep=40]
+         **/
+        nodesep: property(40),
+        /**
+         * Spacing in between ranks.
+         * @method ranksep
+         * @memberof dc_graph.graphviz_attrs
+         * @instance
+         * @param {String} [ranksep=40]
+         **/
+        ranksep: property(40)
+    };
+};
+
+// graphlib-dot seems to wrap nodes in an extra {value}
+// actually this is quite a common problem with generic libs
+function nvalue(n) {
+    return n.value.value ? n.value.value : n.value;
+}
+
+// apply standard accessors to a diagram in order to style it as graphviz would
+// this is a work in progress
+dc_graph.apply_graphviz_accessors = function(diagram) {
+    diagram
+        .nodeLabel(function(n) {
+            var label = nvalue(n).label;
+            if(label === undefined)
+                label = n.key;
+            return label && label.split(/\n|\\n/);
+        })
+        .nodeRadius(function(n) {
+            // should do width & height instead, #25
+            return nvalue(n).radius || 25;
+        })
+        .nodeShape(function(n) { return nvalue(n).shape; })
+        .nodeFill(function(n) { return nvalue(n).fillcolor || 'white'; })
+        .nodeOpacity(function(n) {
+            // not standard gv
+            return nvalue(n).opacity || 1;
+        })
+        .nodeLabelFill(function(n) { return nvalue(n).fontcolor || 'black'; })
+        .nodeTitle(function(n) {
+            return (nvalue(n).htmltip || nvalue(n).jsontip) ? null :
+                nvalue(n).tooltip !== undefined ?
+                nvalue(n).tooltip :
+                diagram.nodeLabel()(n);
+        })
+        .nodeStrokeWidth(function(n) {
+            // it is debatable whether a point === a pixel but they are close
+            // https://graphicdesign.stackexchange.com/questions/199/point-vs-pixel-what-is-the-difference
+            var penwidth = nvalue(n).penwidth;
+            return penwidth !== undefined ? +penwidth : 1;
+        })
+        .edgeLabel(function(e) { return e.value.label ? e.value.label.split(/\n|\\n/) : ''; })
+        .edgeStroke(function(e) { return e.value.color || 'black'; })
+        .edgeOpacity(function(e) {
+            // not standard gv
+            return e.value.opacity || 1;
+        })
+        .edgeArrowSize(function(e) {
+            return e.value.arrowsize || 1;
+        })
+        // need directedness to default these correctly, see #106
+        .edgeArrowhead(function(e) {
+            var head = e.value.arrowhead;
+            return head !== undefined ? head : 'vee';
+        })
+        .edgeArrowtail(function(e) {
+            var tail = e.value.arrowtail;
+            return tail !== undefined ? tail : null;
+        })
+        .edgeStrokeDashArray(function(e) {
+            switch(e.value.style) {
+            case 'dotted':
+                return [1,5];
+            }
+            return null;
+        });
+    var draw_clusters = diagram.child('draw-clusters');
+    if(draw_clusters) {
+        draw_clusters
+            .clusterStroke(function(c) {
+                return c.value.color || 'black';
+            })
+            .clusterFill(function(c) {
+                return c.value.style === 'filled' ? c.value.fillcolor || c.value.color || c.value.bgcolor : null;
+            })
+            .clusterLabel(function(c) {
+                return c.value.label;
+            });
+    }
+};
+
+dc_graph.snapshot_graphviz = function(diagram) {
+    var xDomain = diagram.x().domain(), yDomain = diagram.y().domain();
+    return {
+        nodes: diagram.nodeGroup().all().map(function(n) {
+            return diagram.getWholeNode(n.key);
+        })
+            .filter(function(x) { return x; })
+            .map(function(n) {
+                return {
+                    key: diagram.nodeKey.eval(n),
+                    label: diagram.nodeLabel.eval(n),
+                    fillcolor: diagram.nodeFillScale()(diagram.nodeFill.eval(n)),
+                    penwidth: diagram.nodeStrokeWidth.eval(n),
+                    // not supported as input, see dc.graph.js#25
+                    // width: n.cola.dcg_rx*2,
+                    // height: n.cola.dcg_ry*2,
+
+                    // not graphviz attributes
+                    // until we have w/h
+                    radius: diagram.nodeRadius.eval(n),
+                    // does not seem to exist in gv
+                    opacity: diagram.nodeOpacity.eval(n),
+                    // should be pos
+                    x: n.cola.x,
+                    y: n.cola.y
+                };
+            }),
+        edges: diagram.edgeGroup().all().map(function(e) {
+            return diagram.getWholeEdge(e.key);
+        }).map(function(e) {
+            return {
+                key: diagram.edgeKey.eval(e),
+                source: diagram.edgeSource.eval(e),
+                target: diagram.edgeTarget.eval(e),
+                color: diagram.edgeStroke.eval(e),
+                arrowsize: diagram.edgeArrowSize.eval(e),
+                opacity: diagram.edgeOpacity.eval(e),
+                // should support dir, see dc.graph.js#106
+                arrowhead: diagram.edgeArrowhead.eval(e),
+                arrowtail: diagram.edgeArrowtail.eval(e)
+            };
+        }),
+        bounds: {
+            left: xDomain[0],
+            top: yDomain[0],
+            right: xDomain[1],
+            bottom: yDomain[1]
+        }
+    };
+};
+
+/**
+ * `dc_graph.cola_layout` is an adaptor for cola.js layouts in dc.graph.js
+ * @class cola_layout
+ * @memberof dc_graph
+ * @param {String} [id=uuid()] - Unique identifier
+ * @return {dc_graph.cola_layout}
+ **/
+dc_graph.cola_layout = function(id) {
+    var _layoutId = id || uuid();
+    var _d3cola = null;
+    var _setcola_nodes;
+    var _dispatch = d3.dispatch('tick', 'start', 'end');
+    var _flowLayout;
+    // node and edge objects shared with cola.js, preserved from one iteration
+    // to the next (as long as the object is still in the layout)
+    var _nodes = {}, _edges = {};
+    var _options;
+
+    function init(options) {
+        _options = options;
+        _d3cola = cola.d3adaptor()
+            .avoidOverlaps(true)
+            .size([options.width, options.height])
+            .handleDisconnected(options.handleDisconnected);
+
+        if(_d3cola.tickSize) // non-standard
+            _d3cola.tickSize(options.tickSize);
+
+        switch(options.lengthStrategy) {
+        case 'symmetric':
+            _d3cola.symmetricDiffLinkLengths(options.baseLength);
+            break;
+        case 'jaccard':
+            _d3cola.jaccardLinkLengths(options.baseLength);
+            break;
+        case 'individual':
+            _d3cola.linkDistance(function(e) {
+                return e.dcg_edgeLength || options.baseLength;
+            });
+            break;
+        case 'none':
+        default:
+        }
+        if(options.flowLayout) {
+            _d3cola.flowLayout(options.flowLayout.axis, options.flowLayout.minSeparation);
+        }
+    }
+
+    function data(nodes, edges, clusters, constraints) {
+        var wnodes = regenerate_objects(_nodes, nodes, null, function(v) {
+            return v.dcg_nodeKey;
+        }, function(v1, v) {
+            v1.dcg_nodeKey = v.dcg_nodeKey;
+            v1.dcg_nodeParentCluster = v.dcg_nodeParentCluster;
+            v1.width = v.width;
+            v1.height = v.height;
+            v1.fixed = !!v.dcg_nodeFixed;
+            _options.nodeAttrs.forEach(function(key) {
+                v1[key] = v[key];
+            });
+
+            if(v1.fixed && typeof v.dcg_nodeFixed === 'object') {
+                v1.x = v.dcg_nodeFixed.x;
+                v1.y = v.dcg_nodeFixed.y;
+            }
+            else {
+                // should we support e.g. null to unset x,y?
+                if(v.x !== undefined)
+                    v1.x = v.x;
+                if(v.y !== undefined)
+                    v1.y = v.y;
+            }
+        });
+        var wedges = regenerate_objects(_edges, edges, null, function(e) {
+            return e.dcg_edgeKey;
+        }, function(e1, e) {
+            e1.dcg_edgeKey = e.dcg_edgeKey;
+            // cola edges can work with indices or with object references
+            // but it will replace indices with object references
+            e1.source = _nodes[e.dcg_edgeSource];
+            e1.target = _nodes[e.dcg_edgeTarget];
+            e1.dcg_edgeLength = e.dcg_edgeLength;
+            _options.edgeAttrs.forEach(function(key) {
+                e1[key] = e[key];
+            });
+        });
+
+        // cola needs each node object to have an index property
+        wnodes.forEach(function(v, i) {
+            v.index = i;
+        });
+
+        var groups = null;
+        if(engine.groupConnected()) {
+            var components = cola.separateGraphs(wnodes, wedges);
+            groups = components.map(function(g) {
+                return {
+                    dcg_autoGroup: true,
+                    leaves: g.array.map(function(n) { return n.index; })
+                };
+            });
+        } else if(clusters) {
+            var G = {};
+            groups = clusters.filter(function(c) {
+                return /^cluster/.test(c.dcg_clusterKey);
+            }).map(function(c, i) {
+                return G[c.dcg_clusterKey] = {
+                    dcg_clusterKey: c.dcg_clusterKey,
+                    index: i,
+                    groups: [],
+                    leaves: []
+                };
+            });
+            clusters.forEach(function(c) {
+                if(c.dcg_clusterParent && G[c.dcg_clusterParent])
+                    G[c.dcg_clusterParent].groups.push(G[c.dcg_clusterKey].index);
+            });
+            wnodes.forEach(function(n, i) {
+                if(n.dcg_nodeParentCluster && G[n.dcg_nodeParentCluster])
+                    G[n.dcg_nodeParentCluster].leaves.push(i);
+            });
+        }
+
+        function dispatchState(event) {
+            // clean up extra setcola annotations
+            wnodes.forEach(function(n) {
+                Object.keys(n).forEach(function(key) {
+                    if(/^get/.test(key) && typeof n[key] === 'function')
+                        delete n[key];
+                });
+            });
+            _dispatch[event](
+                wnodes,
+                wedges.map(function(e) {
+                    return {dcg_edgeKey: e.dcg_edgeKey};
+                }),
+                groups.filter(function(g) {
+                    return !g.dcg_autoGroup;
+                }).map(function(g) {
+                    g = Object.assign({}, g);
+                    g.bounds = {
+                        left: g.bounds.x,
+                        top: g.bounds.y,
+                        right: g.bounds.X,
+                        bottom: g.bounds.Y
+                    };
+                    return g;
+                }),
+                _setcola_nodes
+            );
+        }
+        _d3cola.on('tick', /* _tick = */ function() {
+            dispatchState('tick');
+        }).on('start', function() {
+            _dispatch.start();
+        }).on('end', /* _done = */ function() {
+            dispatchState('end');
+        });
+
+        if(_options.setcolaSpec && typeof setcola !== 'undefined') {
+            console.log('generating setcola constrains');
+            var setcola_result = setcola
+                .nodes(wnodes)
+                .links(wedges)
+                .constraints(_options.setcolaSpec)
+                .gap(10) //default value is 10, can be customized in setcolaSpec
+                .layout();
+
+            _setcola_nodes = setcola_result.nodes.filter(function(n) { return n._cid; });
+            _d3cola.nodes(setcola_result.nodes)
+                .links(setcola_result.links)
+                .constraints(setcola_result.constraints)
+                .groups(groups);
+        } else {
+            _d3cola.nodes(wnodes)
+                .links(wedges)
+                .constraints(constraints)
+                .groups(groups);
+        }
+
+    }
+
+    function start() {
+        _d3cola.start(engine.unconstrainedIterations(),
+                      engine.userConstraintIterations(),
+                      engine.allConstraintsIterations(),
+                      engine.gridSnapIterations());
+    }
+
+    function stop() {
+        if(_d3cola)
+            _d3cola.stop();
+    }
+
+    var graphviz = dc_graph.graphviz_attrs(), graphviz_keys = Object.keys(graphviz);
+    graphviz.rankdir(null);
+
+    var engine = Object.assign(graphviz, {
+        layoutAlgorithm: function() {
+            return 'cola';
+        },
+        layoutId: function() {
+            return _layoutId;
+        },
+        supportsWebworker: function() {
+            return true;
+        },
+        parent: property(null),
+        on: function(event, f) {
+            if(arguments.length === 1)
+                return _dispatch.on(event);
+            _dispatch.on(event, f);
+            return this;
+        },
+        init: function(options) {
+            this.optionNames().forEach(function(option) {
+                options[option] = options[option] || this[option]();
+            }.bind(this));
+            this.propagateOptions(options);
+            init(options);
+            return this;
+        },
+        data: function(graph, nodes, edges, clusters, constraints) {
+            data(nodes, edges, clusters, constraints);
+        },
+        start: function() {
+            start();
+        },
+        stop: function() {
+            stop();
+        },
+        optionNames: function() {
+            return ['handleDisconnected', 'lengthStrategy', 'baseLength', 'flowLayout',
+                    'tickSize', 'groupConnected', 'setcolaSpec', 'setcolaNodes']
+                .concat(graphviz_keys);
+        },
+        passThru: function() {
+            return ['extractNodeAttrs', 'extractEdgeAttrs'];
+        },
+        propagateOptions: function(options) {
+            if(!options.nodeAttrs)
+                options.nodeAttrs = Object.keys(engine.extractNodeAttrs());
+            if(!options.edgeAttrs)
+                options.edgeAttrs = Object.keys(engine.extractEdgeAttrs());
+        },
+        populateLayoutNode: function() {},
+        populateLayoutEdge: function() {},
+        /**
+         * Instructs cola.js to fit the connected components.
+         * @method handleDisconnected
+         * @memberof dc_graph.cola_layout
+         * @instance
+         * @param {Boolean} [handleDisconnected=true]
+         * @return {Boolean}
+         * @return {dc_graph.cola_layout}
+         **/
+        handleDisconnected: property(true),
+        /**
+         * Currently, three strategies are supported for specifying the lengths of edges:
+         * * 'individual' - uses the `edgeLength` for each edge. If it returns falsy, uses the
+         * `baseLength`
+         * * 'symmetric', 'jaccard' - compute the edge length based on the graph structure around
+         * the edge. See
+         * {@link https://github.com/tgdwyer/WebCola/wiki/link-lengths the cola.js wiki}
+         * for more details.
+         * 'none' - no edge lengths will be specified
+         * @method lengthStrategy
+         * @memberof dc_graph.cola_layout
+         * @instance
+         * @param {Function|String} [lengthStrategy='symmetric']
+         * @return {Function|String}
+         * @return {dc_graph.cola_layout}
+         **/
+        lengthStrategy: property('symmetric'),
+        /**
+         * Gets or sets the default edge length (in pixels) when the `.lengthStrategy` is
+         * 'individual', and the base value to be multiplied for 'symmetric' and 'jaccard' edge
+         * lengths.
+         * @method baseLength
+         * @memberof dc_graph.cola_layout
+         * @instance
+         * @param {Number} [baseLength=30]
+         * @return {Number}
+         * @return {dc_graph.cola_layout}
+         **/
+        baseLength: property(30),
+        /**
+         * If `flowLayout` is set, it determines the axis and separation for
+         * {@link http://marvl.infotech.monash.edu/webcola/doc/classes/cola.layout.html#flowlayout cola flow layout}.
+         * If it is not set, `flowLayout` will be calculated from the {@link dc_graph.graphviz_attrs#rankdir rankdir}
+         * and {@link dc_graph.graphviz_attrs#ranksep ranksep}; if `rankdir` is also null (the
+         * default for cola layout), then there will be no flow.
+         * @method flowLayout
+         * @memberof dc_graph.cola_layout
+         * @instance
+         * @param {Object} [flowLayout=null]
+         * @example
+         * // No flow (default)
+         * diagram.flowLayout(null)
+         * // flow in x with min separation 200
+         * diagram.flowLayout({axis: 'x', minSeparation: 200})
+         **/
+        flowLayout: function(flow) {
+            if(!arguments.length) {
+                if(_flowLayout)
+                    return _flowLayout;
+                var dir = engine.rankdir();
+                switch(dir) {
+                case 'LR': return {axis: 'x', minSeparation: engine.ranksep() + engine.parent().nodeRadius()*2};
+                case 'TB': return {axis: 'y', minSeparation: engine.ranksep() + engine.parent().nodeRadius()*2};
+                default: return null; // RL, BT do not appear to be possible (negative separation) (?)
+                }
+            }
+            _flowLayout = flow;
+            return this;
+        },
+        unconstrainedIterations: property(10),
+        userConstraintIterations: property(20),
+        allConstraintsIterations: property(20),
+        gridSnapIterations: property(0),
+        tickSize: property(1),
+        groupConnected: property(false),
+        setcolaSpec: property(null),
+        setcolaNodes: function() {
+            return _setcola_nodes;
+        },
+        extractNodeAttrs: property({}), // {attr: function(node)}
+        extractEdgeAttrs: property({}),
+        processExtraWorkerResults: function(setcolaNodes) {
+            _setcola_nodes = setcolaNodes;
+        }
+    });
+    return engine;
+};
+
+dc_graph.cola_layout.scripts = ['d3.js', 'cola.js'];
+dc_graph.cola_layout.optional_scripts = ['setcola.js'];
+
+/**
+ * `dc_graph.dagre_layout` is an adaptor for dagre.js layouts in dc.graph.js
+ *
+ * In addition to the below layout attributes, `dagre_layout` also implements the attributes from
+ * {@link dc_graph.graphviz_attrs graphviz_attrs}
+ * @class dagre_layout
+ * @memberof dc_graph
+ * @param {String} [id=uuid()] - Unique identifier
+ * @return {dc_graph.dagre_layout}
+ **/
+dc_graph.dagre_layout = function(id) {
+    var _layoutId = id || uuid();
+    var _dagreGraph = null, _tick, _done;
+    var _dispatch = d3.dispatch('tick', 'start', 'end');
+    // node and edge objects preserved from one iteration
+    // to the next (as long as the object is still in the layout)
+    var _nodes = {}, _edges = {};
+
+    function init(options) {
+        // Create a new directed graph
+        _dagreGraph = new dagre.graphlib.Graph({multigraph: true, compound: true});
+
+        // Set an object for the graph label
+        _dagreGraph.setGraph({rankdir: options.rankdir, nodesep: options.nodesep, ranksep: options.ranksep});
+
+        // Default to assigning a new object as a label for each new edge.
+        _dagreGraph.setDefaultEdgeLabel(function() { return {}; });
+    }
+
+    function data(nodes, edges, clusters) {
+        var wnodes = regenerate_objects(_nodes, nodes, null, function(v) {
+            return v.dcg_nodeKey;
+        }, function(v1, v) {
+            v1.dcg_nodeKey = v.dcg_nodeKey;
+            v1.width = v.width;
+            v1.height = v.height;
+            /*
+              dagre does not seem to accept input positions
+              if(v.dcg_nodeFixed) {
+                v1.x = v.dcg_nodeFixed.x;
+                v1.y = v.dcg_nodeFixed.y;
+              }
+             */
+        }, function(k, o) {
+            _dagreGraph.setNode(k, o);
+        }, function(k) {
+            _dagreGraph.removeNode(k);
+        });
+        var wedges = regenerate_objects(_edges, edges, null, function(e) {
+            return e.dcg_edgeKey;
+        }, function(e1, e) {
+            e1.dcg_edgeKey = e.dcg_edgeKey;
+            e1.dcg_edgeSource = e.dcg_edgeSource;
+            e1.dcg_edgeTarget = e.dcg_edgeTarget;
+        }, function(k, o, e) {
+            _dagreGraph.setEdge(e.dcg_edgeSource, e.dcg_edgeTarget, o);
+        }, function(k, e) {
+            _dagreGraph.removeEdge(e.dcg_edgeSource, e.dcg_edgeTarget, e.dcg_edgeKey);
+        });
+        clusters = clusters.filter(function(c) {
+            return /^cluster/.test(c.dcg_clusterKey);
+        });
+        clusters.forEach(function(c) {
+            _dagreGraph.setNode(c.dcg_clusterKey, c);
+        });
+        clusters.forEach(function(c) {
+            if(c.dcg_clusterParent)
+                _dagreGraph.setParent(c.dcg_clusterKey, c.dcg_clusterParent);
+        });
+        nodes.forEach(function(n) {
+            if(n.dcg_nodeParentCluster)
+                _dagreGraph.setParent(n.dcg_nodeKey, n.dcg_nodeParentCluster);
+        });
+
+        function dispatchState(event) {
+            _dispatch[event](
+                wnodes,
+                wedges.map(function(e) {
+                    return {dcg_edgeKey: e.dcg_edgeKey};
+                }),
+                clusters.map(function(c) {
+                    var c = Object.assign({}, _dagreGraph.node(c.dcg_clusterKey));
+                    c.bounds = {
+                        left: c.x - c.width/2,
+                        top: c.y - c.height/2,
+                        right: c.x + c.width/2,
+                        bottom: c.y + c.height/2
+                    };
+                    return c;
+                })
+            );
+        }
+        _tick = function() {
+            dispatchState('tick');
+        };
+        _done = function() {
+            dispatchState('end');
+        };
+    }
+
+    function start(options) {
+        _dispatch.start();
+        dagre.layout(_dagreGraph);
+        _done();
+    }
+
+    function stop() {
+    }
+
+    var graphviz = dc_graph.graphviz_attrs(), graphviz_keys = Object.keys(graphviz);
+    return Object.assign(graphviz, {
+        layoutAlgorithm: function() {
+            return 'dagre';
+        },
+        layoutId: function() {
+            return _layoutId;
+        },
+        supportsWebworker: function() {
+            return true;
+        },
+        on: function(event, f) {
+            if(arguments.length === 1)
+                return _dispatch.on(event);
+            _dispatch.on(event, f);
+            return this;
+        },
+        init: function(options) {
+            this.optionNames().forEach(function(option) {
+                options[option] = options[option] || this[option]();
+            }.bind(this));
+            init(options);
+            return this;
+        },
+        data: function(graph, nodes, edges, clusters) {
+            data(nodes, edges, clusters);
+        },
+        start: function() {
+            start();
+        },
+        stop: function() {
+            stop();
+        },
+        optionNames: function() {
+            return graphviz_keys;
+        },
+        populateLayoutNode: function() {},
+        populateLayoutEdge: function() {}
+    });
+};
+
+dc_graph.dagre_layout.scripts = ['d3.js', 'dagre.js'];
+
+/**
+ * `dc_graph.tree_layout` is a very simple and not very bright tree layout. It can draw any DAG, but
+ * tries to position the nodes as a tree.
+ * @class tree_layout
+ * @memberof dc_graph
+ * @param {String} [id=uuid()] - Unique identifier
+ * @return {dc_graph.tree_layout}
+ **/
+dc_graph.tree_layout = function(id) {
+    var _layoutId = id || uuid();
+    var _dispatch = d3.dispatch('tick', 'start', 'end');
+    var _dfs;
+
+    function init(options) {
+        var x;
+        var nodeWidth = d3.functor(options.nodeWidth);
+        function best_dist(left, right) {
+            return (nodeWidth(left) + nodeWidth(right)) / 2;
+        }
+        _dfs = dc_graph.depth_first_traversal({
+            nodeid: function(n) {
+                return n.dcg_nodeKey;
+            },
+            sourceid: function(n) {
+                return n.dcg_edgeSource;
+            },
+            targetid: function(n) {
+                return n.dcg_edgeTarget;
+            },
+            init: function() {
+                x = options.offsetX;
+            },
+            row: function(n) {
+                return n.dcg_rank;
+            },
+            place: function(n, r, row) {
+                if(row.length) {
+                    var left = row[row.length-1];
+                    var g = (nodeWidth(left) + nodeWidth(n)) / 2;
+                    x = Math.max(x, left.left_x + g);
+                }
+                n.left_x = x;
+                n.hit_ins = 1;
+                n.y = r*options.gapY + options.offsetY;
+            },
+            sib: function(isroot, left, right) {
+                var g = best_dist(left, right);
+                if(isroot) g = g*1.5;
+                x += g;
+            },
+            pop: function(n) {
+                n.x = (n.left_x + x)/2;
+            },
+            skip: function(n, indegree) {
+                // rolling average of in-neighbor x positions
+                n.x = (n.hit_ins*n.x + x)/++n.hit_ins;
+                if(n.hit_ins === indegree)
+                    delete n.hit_ins;
+            },
+            finish: function(rows) {
+                // this is disgusting. patch up any places where nodes overlap by scanning
+                // right far enough to find the space, then fill from left to right at the
+                // minimum gap
+                rows.forEach(function(row) {
+                    var sort = row.sort(function(a, b) { return a.x - b.x; });
+                    var badi = null, badl = null, want;
+                    for(var i=0; i<sort.length-1; ++i) {
+                        var left = sort[i], right = sort[i+1];
+                        if(!badi) {
+                            if(right.x - left.x < best_dist(left, right)) {
+                                badi = i;
+                                badl = left.x;
+                                want = best_dist(left, right);
+                            } // else still not bad
+                        } else {
+                            want += best_dist(left, right);
+                            if(i < sort.length - 2 && right.x < badl + want)
+                                continue; // still bad
+                            else {
+                                if(badi>0)
+                                    --badi; // might want to use more left
+                                var l, limit;
+                                if(i < sort.length - 2) { // found space before right
+                                    var extra = right.x - (badl + want);
+                                    l = sort[badi].x + extra/2;
+                                    limit = i+1;
+                                } else {
+                                    l = Math.max(sort[badi].x, badl - best_dist(sort[badi], sort[badi+1]) - (want - right.x + badl)/2);
+                                    limit = sort.length;
+                                }
+                                for(var j = badi+1; j<limit; ++j) {
+                                    l += best_dist(sort[j-1], sort[j]);
+                                    sort[j].x = l;
+                                }
+                                badi = badl = want = null;
+                            }
+                        }
+                    }
+                });
+            }
+        });
+    }
+
+    var _nodes, _edges;
+    function data(nodes, edges) {
+        _nodes = nodes;
+        _edges = edges;
+    }
+
+    function start() {
+        _dfs(_nodes, _edges);
+        _dispatch.end(_nodes, _edges);
+    }
+
+    function stop() {
+    }
+
+    var layout = {
+        layoutAlgorithm: function() {
+            return 'tree';
+        },
+        layoutId: function() {
+            return _layoutId;
+        },
+        supportsWebworker: function() {
+            return false;
+        },
+        on: function(event, f) {
+            if(arguments.length === 1)
+                return _dispatch.on(event);
+            _dispatch.on(event, f);
+            return this;
+        },
+        init: function(options) {
+            this.optionNames().forEach(function(option) {
+                options[option] = options[option] || this[option]();
+            }.bind(this));
+            init(options);
+            return this;
+        },
+        data: function(graph, nodes, edges) {
+            data(nodes, edges);
+        },
+        start: function() {
+            start();
+        },
+        stop: function() {
+            stop();
+        },
+        optionNames: function() {
+            return ['nodeWidth', 'offsetX', 'offsetY', 'rowFunction', 'gapY'];
+        },
+        populateLayoutNode: function(layout, node) {
+            if(this.rowFunction())
+                layout.dcg_rank = this.rowFunction.eval(node);
+        },
+        populateLayoutEdge: function() {},
+        nodeWidth: property(function(n) { return n.width; }),
+        offsetX: property(30),
+        offsetY: property(30),
+        rowFunction: property(null),
+        gapY: property(100)
+    };
+    return layout;
+};
+
+dc_graph.tree_layout.scripts = [];
+
+/**
+ * `dc_graph.graphviz_layout` is an adaptor for viz.js (graphviz) layouts in dc.graph.js
+ *
+ * In addition to the below layout attributes, `graphviz_layout` also implements the attributes from
+ * {@link dc_graph.graphviz_attrs graphviz_attrs}
+ * @class graphviz_layout
+ * @memberof dc_graph
+ * @param {String} [id=uuid()] - Unique identifier
+ * @return {dc_graph.graphviz_layout}
+ **/
+dc_graph.graphviz_layout = function(id, layout, server) {
+    var _layoutId = id || uuid();
+    var _dispatch = d3.dispatch('tick', 'start', 'end');
+    var _dotInput, _dotString;
+
+    function init(options) {
+    }
+
+    function encode_name(name) {
+        return name.replace(/^%/, '&#37;');
+    }
+    function decode_name(name) {
+        return name.replace(/^&#37;/, '%');
+    }
+    function stringize_property(prop, value) {
+        return [prop, '"' + value + '"'].join('=');
+    }
+    function stringize_properties(props) {
+        return '[' + props.join(', ') + ']';
+    }
+    function data(nodes, edges, clusters) {
+        if(_dotInput) {
+            _dotString = _dotInput;
+            return;
+        }
+        var lines = [];
+        var directed = layout !== 'neato';
+        lines.push((directed ? 'digraph' : 'graph') + ' g {');
+        lines.push('graph ' + stringize_properties([
+            stringize_property('nodesep', graphviz.nodesep()/72),
+            stringize_property('ranksep', graphviz.ranksep()/72),
+            stringize_property('rankdir', graphviz.rankdir())
+        ]));
+        var cluster_nodes = {};
+        nodes.forEach(function(n) {
+            var cl = n.dcg_nodeParentCluster;
+            if(cl) {
+                cluster_nodes[cl] = cluster_nodes[cl] || [];
+                cluster_nodes[cl].push(n.dcg_nodeKey);
+            }
+        });
+        var cluster_children = {}, tops = [];
+        clusters.forEach(function(c) {
+            var p = c.dcg_clusterParent;
+            if(p) {
+                cluster_children[p] = cluster_children[p] || [];
+                cluster_children[p].push(c.dcg_clusterKey);
+            } else tops.push(c.dcg_clusterKey);
+        });
+
+        function print_subgraph(i, c) {
+            var indent = ' '.repeat(i*2);
+            lines.push(indent + 'subgraph "' + c + '" {');
+            if(cluster_children[c])
+                cluster_children[c].forEach(print_subgraph.bind(null, i+1));
+            lines.push(indent + '  ' + cluster_nodes[c].join(' '));
+            lines.push(indent + '}');
+        }
+        tops.forEach(print_subgraph.bind(null, 1));
+
+        lines = lines.concat(nodes.map(function(v) {
+            var props = [
+                stringize_property('width', v.width/72),
+                stringize_property('height', v.height/72),
+                stringize_property('fixedsize', 'shape'),
+                stringize_property('shape', v.abstract.shape)
+            ];
+            if(v.dcg_nodeFixed)
+                props.push(stringize_property('pos', [
+                    v.dcg_nodeFixed.x,
+                    1000-v.dcg_nodeFixed.y
+                ].join(',')));
+            return '  "' + encode_name(v.dcg_nodeKey) + '" ' + stringize_properties(props);
+        }));
+        lines = lines.concat(edges.map(function(e) {
+            return '  "' + encode_name(e.dcg_edgeSource) + (directed ? '" -> "' : '" -- "') +
+                encode_name(e.dcg_edgeTarget) + '" ' + stringize_properties([
+                    stringize_property('id', encode_name(e.dcg_edgeKey)),
+                stringize_property('arrowhead', 'none'),
+                stringize_property('arrowtail', 'none')
+                ]);
+        }));
+        lines.push('}');
+        lines.push('');
+        _dotString = lines.join('\n');
+    }
+
+    function process_response(error, result) {
+        if(error) {
+            console.warn("graphviz layout failed: ", error);
+            return;
+        }
+        _dispatch.start();
+        var bb = result.bb.split(',').map(function(x) { return +x; });
+        var nodes = (result.objects || []).filter(function(n) {
+            return n.pos; // remove non-nodes like clusters
+        }).map(function(n) {
+            var pos = n.pos.split(',');
+            if(isNaN(pos[0]) || isNaN(pos[1])) {
+                console.warn('got a NaN position from graphviz');
+                pos[0] = pos[1] = 0;
+            }
+            return {
+                dcg_nodeKey: decode_name(n.name),
+                x: +pos[0],
+                y: bb[3] - pos[1]
+            };
+        });
+        var clusters = (result.objects || []).filter(function(n) {
+            return /^cluster/.test(n.name);
+        });
+        clusters.forEach(function(c) {
+            c.dcg_clusterKey = c.name;
+
+            // gv: llx, lly, urx, ury, up-positive
+            var cbb = c.bb.split(',').map(function(s) { return +s; });
+            c.bounds = {left: cbb[0], top: bb[3] - cbb[3],
+                        right: cbb[2], bottom: bb[3] - cbb[1]};
+        });
+        var edges = (result.edges || []).map(function(e) {
+            var e2 = {
+                dcg_edgeKey: decode_name(e.id || 'n' + e._gvid)
+            };
+            if(e._draw_) {
+                var directive = e._draw_.find(function(d) { return d.op && d.points; });
+                e2.points = directive.points.map(function(p) { return {x: p[0], y: bb[3] - p[1]}; });
+            }
+            return e2;
+        });
+        _dispatch.end(nodes, edges, clusters);
+    }
+
+    function start() {
+        if(server) {
+            d3.json(server)
+                .header("Content-type", "application/x-www-form-urlencoded")
+                .post('layouttool=' + layout + '&' + encodeURIComponent(_dotString), process_response);
+        }
+        else {
+            var result = Viz(_dotString, {format: 'json', engine: layout, totalMemory: 1 << 25});
+            result = JSON.parse(result);
+            process_response(null, result);
+        }
+    }
+
+    function stop() {
+    }
+
+    var graphviz = dc_graph.graphviz_attrs(), graphviz_keys = Object.keys(graphviz);
+    return Object.assign(graphviz, {
+        layoutAlgorithm: function() {
+            return layout;
+        },
+        layoutId: function() {
+            return _layoutId;
+        },
+        supportsWebworker: function() {
+            return false;
+        },
+        on: function(event, f) {
+            if(arguments.length === 1)
+                return _dispatch.on(event);
+            _dispatch.on(event, f);
+            return this;
+        },
+        init: function(options) {
+            this.optionNames().forEach(function(option) {
+                options[option] = options[option] || this[option]();
+            }.bind(this));
+            init(options);
+            return this;
+        },
+        data: function(graph, nodes, edges, clusters) {
+            data(nodes, edges, clusters);
+        },
+        dotInput: function(text) {
+            _dotInput = text;
+            return this;
+        },
+        start: function() {
+            start();
+        },
+        stop: function() {
+            stop();
+        },
+        optionNames: function() {
+            return graphviz_keys;
+        },
+        populateLayoutNode: function() {},
+        populateLayoutEdge: function() {}
+    });
+}
+
+
+/**
+ * `dc_graph.d3_force_layout` is an adaptor for d3-force layouts in dc.graph.js
+ * @class d3_force_layout
+ * @memberof dc_graph
+ * @param {String} [id=uuid()] - Unique identifier
+ * @return {dc_graph.d3_force_layout}
+ **/
+dc_graph.d3_force_layout = function(id) {
+    var _layoutId = id || uuid();
+    var _simulation = null; // d3-force simulation
+    var _dispatch = d3.dispatch('tick', 'start', 'end');
+    // node and edge objects shared with d3-force, preserved from one iteration
+    // to the next (as long as the object is still in the layout)
+    var _nodes = {}, _edges = {};
+    var _wnodes = [], _wedges = [];
+    var _options = null;
+    var _paths = null;
+
+    function init(options) {
+        _options = options;
+
+        _simulation = d3.layout.force()
+            .size([options.width, options.height]);
+        if(options.linkDistance) {
+            if(typeof options.linkDistance === 'number')
+                _simulation.linkDistance(options.linkDistance);
+            else if(options.linkDistance === 'auto')
+                _simulation.linkDistance(function(e) {
+                    return e.dcg_edgeLength;
+                });
+        }
+
+        _simulation.on('tick', /* _tick = */ function() {
+            dispatchState('tick');
+        }).on('start', function() {
+            _dispatch.start();
+        }).on('end', /* _done = */ function() {
+            dispatchState('end');
+        });
+    }
+
+    function dispatchState(event) {
+        _dispatch[event](
+            _wnodes,
+            _wedges.map(function(e) {
+                return {dcg_edgeKey: e.dcg_edgeKey};
+            })
+        );
+    }
+
+    function data(nodes, edges, constraints) {
+        var nodeIDs = {};
+        nodes.forEach(function(d, i) {
+            nodeIDs[d.dcg_nodeKey] = i;
+        });
+
+        _wnodes = regenerate_objects(_nodes, nodes, null, function(v) {
+            return v.dcg_nodeKey;
+        }, function(v1, v) {
+            v1.dcg_nodeKey = v.dcg_nodeKey;
+            v1.width = v.width;
+            v1.height = v.height;
+            v1.id = v.dcg_nodeKey;
+            if(v.dcg_nodeFixed) {
+                v1.fixed = true;
+                v1.x = v.dcg_nodeFixed.x;
+                v1.y = v.dcg_nodeFixed.y;
+            } else v1.fixed = false;
+        });
+
+        _wedges = regenerate_objects(_edges, edges, null, function(e) {
+            return e.dcg_edgeKey;
+        }, function(e1, e) {
+            e1.dcg_edgeKey = e.dcg_edgeKey;
+            // cola edges can work with indices or with object references
+            // but it will replace indices with object references
+            e1.source = _nodes[e.dcg_edgeSource];
+            e1.source.id = nodeIDs[e1.source.dcg_nodeKey];
+            e1.target = _nodes[e.dcg_edgeTarget];
+            e1.target.id = nodeIDs[e1.target.dcg_nodeKey];
+            e1.dcg_edgeLength = e.dcg_edgeLength;
+        });
+
+        _simulation.nodes(_wnodes);
+        _simulation.links(_wedges);
+    }
+
+    function start() {
+        installForces();
+        runSimulation(_options.iterations);
+    }
+
+    function stop() {
+        if(_simulation)
+            _simulation.stop();
+    }
+
+    function savePositions() {
+        var data = {};
+        Object.keys(_nodes).forEach(function(key) {
+            data[key] = {x: _nodes[key].x, y: _nodes[key].y};
+        });
+        return data;
+    }
+
+    function restorePositions(data) {
+        Object.keys(data).forEach(function(key) {
+            if(_nodes[key]) {
+                _nodes[key].fixed = false;
+                _nodes[key].x = data[key].x;
+                _nodes[key].y = data[key].y;
+            }
+        });
+    }
+
+    function installForces() {
+        if(_paths === null)
+            _simulation.gravity(_options.gravityStrength)
+                .charge(_options.initialCharge);
+        else {
+            if(_options.fixOffPathNodes) {
+                var nodesOnPath = d3.set(); // nodes on path
+                _paths.forEach(function(path) {
+                    path.forEach(function(nid) {
+                        nodesOnPath.add(nid);
+                    });
+                });
+
+                // fix nodes not on paths
+                Object.keys(_nodes).forEach(function(key) {
+                    if(!nodesOnPath.has(key)) {
+                        _nodes[key].fixed = true;
+                    } else {
+                        _nodes[key].fixed = false;
+                    }
+                });
+            }
+
+            // enlarge charge force to separate nodes on paths
+            _simulation.charge(_options.chargeForce);
+        }
+    };
+
+    function runSimulation(iterations) {
+        if(!iterations) {
+            dispatchState('end');
+            return;
+        }
+        _simulation.start();
+        for (var i = 0; i < 300; ++i) {
+            _simulation.tick();
+            if(_paths)
+                applyPathAngleForces();
+        }
+        _simulation.stop();
+    }
+
+    function applyPathAngleForces() {
+        function _dot(v1, v2) { return  v1.x*v2.x + v1.y*v2.y; };
+        function _len(v) { return Math.sqrt(v.x*v.x + v.y*v.y); };
+        function _angle(v1, v2) {
+            var a = _dot(v1, v2) / (_len(v1)*_len(v2));
+            a = Math.min(a, 1);
+            a = Math.max(a, -1);
+            return Math.acos(a);
+        };
+        // perpendicular unit length vector
+        function _pVec(v) {
+            var xx = -v.y/v.x, yy = 1;
+            var length = _len({x: xx, y: yy});
+            return {x: xx/length, y: yy/length};
+        };
+
+        function updateNode(node, angle, pVec, alpha) {
+            node.x += pVec.x*(Math.PI-angle)*alpha;
+            node.y += pVec.y*(Math.PI-angle)*alpha;
+        }
+
+        _paths.forEach(function(path) {
+            if(path.length < 3) return; // at least 3 nodes (and 2 edges):  A->B->C
+            for(var i = 1; i < path.length-1; ++i) {
+                var current = _nodes[path[i]];
+                var prev = _nodes[path[i-1]];
+                var next = _nodes[path[i+1]];
+
+                // calculate the angle
+                var vPrev = {x: prev.x - current.x, y: prev.y - current.y};
+                var vNext = {x: next.x - current.x, y: next.y - current.y};
+
+                var angle = _angle(vPrev, vNext); // angle in [0, PI]
+
+                var pvecPrev = _pVec(vPrev);
+                var pvecNext = _pVec(vNext);
+
+                // make sure the perpendicular vector is in the
+                // direction that makes the angle more towards 180 degree
+                // 1. calculate the middle point of node 'prev' and 'next'
+                var mid = {x: (prev.x+next.x)/2.0, y: (prev.y+next.y)/2.0};
+                // 2. calculate the vectors: 'prev' pointing to 'mid', 'next' pointing to 'mid'
+                var prev_mid = {x: mid.x-prev.x, y: mid.y-prev.y};
+                var next_mid = {x: mid.x-next.x, y: mid.y-next.y};
+                // 3. the 'correct' vector: the angle between pvec and prev_mid(next_mid) should
+                //    be an obtuse angle
+                pvecPrev = _angle(prev_mid, pvecPrev) >= Math.PI/2.0 ? pvecPrev : {x: -pvecPrev.x, y: -pvecPrev.y};
+                pvecNext = _angle(next_mid, pvecNext) >= Math.PI/2.0 ? pvecNext : {x: -pvecNext.x, y: -pvecNext.y};
+
+                // modify positions of prev and next
+                updateNode(prev, angle, pvecPrev, _options.angleForce);
+                updateNode(next, angle, pvecNext, _options.angleForce);
+            }
+
+        });
+    }
+
+    var graphviz = dc_graph.graphviz_attrs(), graphviz_keys = Object.keys(graphviz);
+
+    var engine = Object.assign(graphviz, {
+        layoutAlgorithm: function() {
+            return 'd3-force';
+        },
+        layoutId: function() {
+            return _layoutId;
+        },
+        supportsWebworker: function() {
+            return true;
+        },
+        parent: property(null),
+        on: function(event, f) {
+            if(arguments.length === 1)
+                return _dispatch.on(event);
+            _dispatch.on(event, f);
+            return this;
+        },
+        init: function(options) {
+            this.optionNames().forEach(function(option) {
+                options[option] = options[option] || this[option]();
+            }.bind(this));
+            init(options);
+            return this;
+        },
+        data: function(graph, nodes, edges, constraints) {
+            data(nodes, edges, constraints);
+        },
+        start: function() {
+            start();
+        },
+        stop: function() {
+            stop();
+        },
+        paths: function(paths) {
+            _paths = paths;
+        },
+        savePositions: savePositions,
+        restorePositions: restorePositions,
+        optionNames: function() {
+            return ['iterations', 'angleForce', 'chargeForce', 'gravityStrength',
+                    'initialCharge', 'linkDistance', 'fixOffPathNodes']
+                .concat(graphviz_keys);
+        },
+        iterations: property(300),
+        angleForce: property(0.02),
+        chargeForce: property(-500),
+        gravityStrength: property(1.0),
+        initialCharge: property(-400),
+        linkDistance: property(20),
+        fixOffPathNodes: property(false),
+        populateLayoutNode: function() {},
+        populateLayoutEdge: function() {}
+    });
+    return engine;
+};
+
+dc_graph.d3_force_layout.scripts = ['d3.js'];
+
+/**
+ * `dc_graph.d3v4_force_layout` is an adaptor for d3-force version 4 layouts in dc.graph.js
+ * @class d3v4_force_layout
+ * @memberof dc_graph
+ * @param {String} [id=uuid()] - Unique identifier
+ * @return {dc_graph.d3v4_force_layout}
+ **/
+dc_graph.d3v4_force_layout = function(id) {
+    var _layoutId = id || uuid();
+    var _simulation = null; // d3-force simulation
+    var _dispatch = d3.dispatch('tick', 'start', 'end');
+    // node and edge objects shared with d3-force, preserved from one iteration
+    // to the next (as long as the object is still in the layout)
+    var _nodes = {}, _edges = {};
+    var _wnodes = [], _wedges = [];
+    var _options = null;
+    var _paths = null;
+
+    function init(options) {
+        _options = options;
+
+        _simulation = d3v4.forceSimulation()
+            .force('link', d3v4.forceLink())
+            .force('center', d3v4.forceCenter(options.width / 2, options.height / 2))
+            .force('gravityX', d3v4.forceX(options.width / 2).strength(_options.gravityStrength))
+            .force('gravityY', d3v4.forceY(options.height / 2).strength(_options.gravityStrength))
+            .force('collision', d3v4.forceCollide(_options.collisionRadius))
+            .force('charge', d3v4.forceManyBody())
+            .stop();
+    }
+
+    function dispatchState(event) {
+        _dispatch[event](
+            _wnodes,
+            _wedges.map(function(e) {
+                return {dcg_edgeKey: e.dcg_edgeKey};
+            })
+        );
+    }
+
+    function data(nodes, edges) {
+        var nodeIDs = {};
+        nodes.forEach(function(d, i) {
+            nodeIDs[d.dcg_nodeKey] = i;
+        });
+
+        _wnodes = regenerate_objects(_nodes, nodes, null, function(v) {
+            return v.dcg_nodeKey;
+        }, function(v1, v) {
+            v1.dcg_nodeKey = v.dcg_nodeKey;
+            v1.width = v.width;
+            v1.height = v.height;
+            v1.id = v.dcg_nodeKey;
+            if(v.dcg_nodeFixed) {
+                v1.fx = v.dcg_nodeFixed.x;
+                v1.fy = v.dcg_nodeFixed.y;
+            } else v1.fx = v1.fy = null;
+        });
+
+        _wedges = regenerate_objects(_edges, edges, null, function(e) {
+            return e.dcg_edgeKey;
+        }, function(e1, e) {
+            e1.dcg_edgeKey = e.dcg_edgeKey;
+            e1.source = nodeIDs[_nodes[e.dcg_edgeSource].dcg_nodeKey];
+            e1.target = nodeIDs[_nodes[e.dcg_edgeTarget].dcg_nodeKey];
+            e1.dcg_edgeLength = e.dcg_edgeLength;
+        });
+
+        _simulation.force('straighten', null);
+        _simulation.nodes(_wnodes);
+        _simulation.force('link').links(_wedges);
+    }
+
+    function start() {
+        _dispatch.start();
+        installForces(_paths);
+        runSimulation(_options.iterations);
+    }
+
+    function stop() {
+        // not running asynchronously, no _simulation.stop();
+    }
+
+    function savePositions() {
+        var data = {};
+        Object.keys(_nodes).forEach(function(key) {
+            data[key] = {x: _nodes[key].x, y: _nodes[key].y};
+        });
+        return data;
+    }
+    function restorePositions(data) {
+        Object.keys(data).forEach(function(key) {
+            if(_nodes[key]) {
+                _nodes[key].fx = data[key].x;
+                _nodes[key].fy = data[key].y;
+            }
+        });
+    }
+    function installForces(paths) {
+        if(paths)
+            paths = paths.filter(function(path) {
+                return path.nodes.every(function(nk) { return _nodes[nk]; });
+            });
+        if(paths === null || !paths.length) {
+            _simulation.force('charge').strength(_options.initialCharge);
+        } else {
+            var nodesOnPath;
+            if(_options.fixOffPathNodes) {
+                nodesOnPath = d3.set();
+                paths.forEach(function(path) {
+                    path.nodes.forEach(function(nid) {
+                        nodesOnPath.add(nid);
+                    });
+                });
+            }
+
+            // fix nodes not on paths
+            Object.keys(_nodes).forEach(function(key) {
+                if(_options.fixOffPathNodes && !nodesOnPath.has(key)) {
+                    _nodes[key].fx = _nodes[key].x;
+                    _nodes[key].fy = _nodes[key].y;
+                } else {
+                    _nodes[key].fx = null;
+                    _nodes[key].fy = null;
+                }
+            });
+
+            _simulation.force('charge').strength(_options.chargeForce);
+            _simulation.force('straighten', d3v4.forceStraightenPaths()
+                              .id(function(n) { return n.dcg_nodeKey; })
+                              .angleForce(_options.angleForce)
+                              .pathNodes(function(p) { return p.nodes; })
+                              .pathStrength(function(p) { return p.strength; })
+                              .paths(paths));
+        }
+    };
+
+    function runSimulation(iterations) {
+        _simulation.alpha(1);
+        for (var i = 0; i < iterations; ++i) {
+            _simulation.tick();
+            dispatchState('tick');
+        }
+        dispatchState('end');
+    }
+
+    var graphviz = dc_graph.graphviz_attrs(), graphviz_keys = Object.keys(graphviz);
+
+    var engine = Object.assign(graphviz, {
+        layoutAlgorithm: function() {
+            return 'd3v4-force';
+        },
+        layoutId: function() {
+            return _layoutId;
+        },
+        supportsWebworker: function() {
+            return true;
+        },
+        parent: property(null),
+        on: function(event, f) {
+            if(arguments.length === 1)
+                return _dispatch.on(event);
+            _dispatch.on(event, f);
+            return this;
+        },
+        init: function(options) {
+            this.optionNames().forEach(function(option) {
+                options[option] = options[option] || this[option]();
+            }.bind(this));
+            init(options);
+            return this;
+        },
+        data: function(graph, nodes, edges, constraints) {
+            data(nodes, edges, constraints);
+        },
+        start: function() {
+            start();
+        },
+        stop: function() {
+            stop();
+        },
+        paths: function(paths) {
+            _paths = paths;
+        },
+        savePositions: savePositions,
+        restorePositions: restorePositions,
+        optionNames: function() {
+            return ['iterations', 'angleForce', 'chargeForce', 'gravityStrength', 'collisionRadius',
+                    'initialCharge', 'fixOffPathNodes']
+                .concat(graphviz_keys);
+        },
+        iterations: property(300),
+        angleForce: property(0.01),
+        chargeForce: property(-600),
+        gravityStrength: property(0.3),
+        collisionRadius: property(8),
+        initialCharge: property(-100),
+        fixOffPathNodes: property(false),
+        populateLayoutNode: function() {},
+        populateLayoutEdge: function() {}
+    });
+    engine.pathStraightenForce = engine.angleForce;
+    return engine;
+};
+
+dc_graph.d3v4_force_layout.scripts = ['d3.js', 'd3v4-force.js'];
+
+/**
+ * `dc_graph.flexbox_layout` lays out nodes in accordance with the
+ * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Flexible_Box_Layout/Basic_Concepts_of_Flexbox flexbox layout algorithm}.
+ * Nodes fit into a containment hierarchy based on their keys; edges do not affect the layout but
+ * are drawn from node to node.
+ *
+ * Since the flexbox algorithm is not ordinarily available in SVG, this class uses the
+ * {@link https://npmjs.com/package/css-layout css-layout}
+ * package. (It does not currently support css-layout's successor
+ * {@link https://github.com/facebook/yoga yoga} but that should be straightforward to add if
+ * there is interest.)
+ *
+ * Unlike conventional graph layout, where positions are determined based on a few attributes and
+ * the topological structure of the eedges, flexbox layout is determined based on the node hierarchy
+ * and a large number of attributes on the nodes. See css-layout's
+ * {@link https://npmjs.com/package/css-layout#supported-attributes Supported Attributes}
+ * for a list of those attributes, and see below to understand how the hierarchy is inferred from
+ * node keys.
+ *
+ * `flexbox_layout` does not require all internal nodes to be specified. The node keys are parsed as
+ * "addresses" or paths (arrays of strings) and the tree is built from those paths. Wherever a
+ * node's path terminates is where that node's data will be applied.
+ *
+ * Since flexbox supports a vast number of attributes, we don't attempt to create accessors for
+ * every one. Instead, any attributes in the node data are copied which match the names of flexbox
+ * attributes.
+ *
+ * @class flexbox_layout
+ * @memberof dc_graph
+ * @param {String} [id=uuid()] - Unique identifier
+ * @return {dc_graph.flexbox_layout}
+ **/
+dc_graph.flexbox_layout = function(id, options) {
+    var _layoutId = id || uuid();
+    options = options || {algo: 'yoga-layout'};
+    var _dispatch = d3.dispatch('tick', 'start', 'end');
+
+    var _graph, _tree, _nodes = {}, _wnodes;
+
+    function init(options) {
+    }
+    // like d3.nest but address can be of arbitrary (and different) length
+    // probably less efficient too
+    function add_node(adhead, adtail, n, tree) {
+        tree.address = adhead.slice();
+        tree.children = tree.children || {};
+        if(!adtail.length) {
+            tree.node = n;
+            return;
+        }
+        var t = tree.children[adtail[0]] = tree.children[adtail[0]] || {};
+        adhead.push(adtail.shift());
+        add_node(adhead, adtail, n, t);
+    }
+    function all_keys(tree) {
+        var key = _engine.addressToKey()(tree.address);
+        return Array.prototype.concat.apply([key], Object.keys(tree.children || {}).map(function(k) {
+            return all_keys(tree.children[k]);
+        }));
+    }
+    function data(graph, nodes) {
+        _graph = graph;
+        _tree = {address: [], children: {}};
+        nodes.forEach(function(n) {
+            var ad = _engine.keyToAddress()(n.dcg_nodeKey);
+            add_node([], ad, n, _tree);
+        });
+        var need = all_keys(_tree);
+        _wnodes = nodes;
+    }
+    function ensure_inner_nodes(tree) {
+        if(!tree.node)
+            tree.node = {dcg_nodeKey: tree.address.length ? tree.address[tree.address.length-1] : null};
+        Object.values(tree.children).forEach(ensure_inner_nodes);
+    }
+    var yoga_constants = {
+        alignItems: {
+            stretch: yogaLayout.ALIGN_STRETCH,
+            'flex-start': yogaLayout.ALIGN_FLEX_START,
+            center: yogaLayout.ALIGN_CENTER,
+            'flex-end': yogaLayout.ALIGN_FLEX_END,
+            baseline: yogaLayout.ALIGN_BASELINE
+        },
+        alignSelf: {
+            stretch: yogaLayout.ALIGN_STRETCH,
+            'flex-start': yogaLayout.ALIGN_FLEX_START,
+            center: yogaLayout.ALIGN_CENTER,
+            'flex-end': yogaLayout.ALIGN_FLEX_END,
+            baseline: yogaLayout.ALIGN_BASELINE
+        },
+        alignContent: {
+            'flex-start': yogaLayout.ALIGN_FLEX_START,
+            'flex-end': yogaLayout.ALIGN_FLEX_END,
+            stretch: yogaLayout.ALIGN_STRETCH,
+            center: yogaLayout.ALIGN_CENTER,
+            'space-between': yogaLayout.ALIGN_SPACE_BETWEEN,
+            'space-around': yogaLayout.ALIGN_SPACE_AROUND
+        },
+        flexDirection: {
+            column: yogaLayout.FLEX_DIRECTION_COLUMN,
+            'column-reverse': yogaLayout.FLEX_DIRECTION_COLUMN_REVERSE,
+            row: yogaLayout.FLEX_DIRECTION_ROW,
+            'row-reverse': yogaLayout.FLEX_DIRECTION_ROW_REVERSE
+        },
+        justifyContent: {
+            'flex-start': yogaLayout.JUSTIFY_FLEX_START,
+            center: yogaLayout.JUSTIFY_CENTER,
+            'flex-end': yogaLayout.JUSTIFY_FLEX_END,
+            'space-between': yogaLayout.JUSTIFY_SPACE_BETWEEN,
+            'space-around': yogaLayout.JUSTIFY_SPACE_AROUND,
+            'space-evenly': yogaLayout.JUSTIFY_SPACE_EVENLY
+        }
+    };
+    function set_yoga_attr(flexnode, attr, value) {
+        var fname = 'set' + attr.charAt(0).toUpperCase() + attr.slice(1);
+        if(typeof flexnode[fname] !== 'function')
+            throw new Error('Could not set yoga attr "' + attr + '" (' + fname + ')');
+        if(yoga_constants[attr])
+            value = yoga_constants[attr][value];
+        flexnode['set' + attr.charAt(0).toUpperCase() + attr.slice(1)](value);
+    }
+    function get_yoga_attr(flexnode, attr) {
+        var fname = 'getComputed' + attr.charAt(0).toUpperCase() + attr.slice(1);
+        if(typeof flexnode[fname] !== 'function')
+            throw new Error('Could not get yoga attr "' + attr + '" (' + fname + ')');
+        return flexnode[fname]();
+    }
+    var internal_attrs = ['sort', 'order', 'dcg_nodeKey', 'dcg_nodeParentCluster', 'shape', 'abstract', 'rx', 'ry', 'x', 'y', 'z'],
+        skip_on_parents = ['width', 'height'];
+    function create_flextree(attrs, tree) {
+        var flexnode;
+        switch(options.algo) {
+        case 'css-layout':
+            flexnode = {name: _engine.addressToKey()(tree.address), style: {}};
+            break;
+        case 'yoga-layout':
+            flexnode = yogaLayout.Node.create();
+            break;
+        }
+        var attrs2 = Object.assign({}, attrs);
+        var isParent = Object.keys(tree.children).length;
+        if(tree.node)
+            Object.assign(attrs, tree.node);
+        for(var attr in attrs) {
+            if(internal_attrs.includes(attr))
+                continue;
+            if(isParent && skip_on_parents.includes(attr))
+                continue;
+            var value = attrs[attr];
+            if(typeof value === 'function')
+                value = value(tree.node);
+            switch(options.algo) {
+            case 'css-layout':
+                flexnode.style[attr] = value;
+                break;
+            case 'yoga-layout':
+                set_yoga_attr(flexnode, attr, value);
+                break;
+            }
+        }
+        if(isParent) {
+            var children = Object.values(tree.children)
+                .sort(attrs.sort)
+                .map(function(c) { return c.address[c.address.length-1]; })
+                .map(function(key) {
+                    return create_flextree(Object.assign({}, attrs2), tree.children[key]);
+                });
+            switch(options.algo) {
+            case 'css-layout':
+                flexnode.children = children;
+                break;
+            case 'yoga-layout':
+                children.forEach(function(child, i) {
+                    flexnode.insertChild(child, i);
+                });
+                break;
+            }
+        }
+        tree.flexnode = flexnode;
+        return flexnode;
+    }
+    function apply_layout(offset, tree) {
+        var left, top, width, height;
+        switch(options.algo) {
+        case 'css-layout':
+            if(_engine.logStuff())
+                console.log(tree.node.dcg_nodeKey + ': '+ JSON.stringify(tree.flexnode.layout));
+            left = tree.flexnode.layout.left; width = tree.flexnode.layout.width;
+            top = tree.flexnode.layout.top; height = tree.flexnode.layout.height;
+            break;
+        case 'yoga-layout':
+            left = get_yoga_attr(tree.flexnode, 'left'); width = get_yoga_attr(tree.flexnode, 'width');
+            top = get_yoga_attr(tree.flexnode, 'top'); height = get_yoga_attr(tree.flexnode, 'height');
+            break;
+        }
+        tree.node.x = offset.x + left + width/2;
+        tree.node.y = offset.y + top + height/2;
+        Object.keys(tree.children)
+            .map(function(key) { return tree.children[key]; })
+            .forEach(function(child) {
+                apply_layout({x: offset.x + left, y: offset.y + top}, child);
+            });
+    }
+    function dispatchState(wnodes, wedges, event) {
+        _dispatch[event](
+            wnodes,
+            wedges.map(function(e) {
+                return {dcg_edgeKey: e.dcg_edgeKey};
+            })
+        );
+    }
+    function start() {
+        var defaults = {
+            sort: function(a, b) {
+                return d3.ascending(a.node.dcg_nodeKey, b.node.dcg_nodeKey);
+            }
+        };
+        ensure_inner_nodes(_tree);
+        var flexTree = create_flextree(defaults, _tree);
+        switch(options.algo) {
+        case 'css-layout':
+            flexTree.style.width = _graph.width;
+            flexTree.style.height = _graph.height;
+            break;
+        case 'yoga-layout':
+            set_yoga_attr(flexTree, 'width', _graph.width);
+            set_yoga_attr(flexTree, 'height', _graph.height);
+            break;
+        }
+        if(_engine.logStuff())
+            console.log(JSON.stringify(flexTree, null, 2));
+        switch(options.algo) {
+        case 'css-layout':
+            computeLayout(flexTree);
+            break;
+        case 'yoga-layout':
+            flexTree.calculateLayout();
+            break;
+        }
+        apply_layout({x: 0, y: 0}, _tree);
+        dispatchState(_wnodes, [], 'end');
+    }
+    function stop() {
+    }
+
+    // currently dc.graph populates the "cola" (really "layout") member with the attributes
+    // needed for layout and does not pass in the original data. flexbox has a huge number of attributes
+    // and it might be more appropriate for it to look at the original data.
+    // (Especially because it also computes some attributes based on data.)
+    var supportedAttributes = [
+        'width', 'height', // positive number
+        'minWidth', 'minHeight', // positive number
+        'maxWidth', 'maxHeight', // positive number
+        'left', 'right', 'top', 'bottom', // number
+        'margin', 'marginLeft', 'marginRight', 'marginTop', 'marginBottom', // number
+        'padding', 'paddingLeft', 'paddingRight', 'paddingTop', 'paddingBottom', // positive number
+        'borderWidth', 'borderLeftWidth', 'borderRightWidth', 'borderTopWidth', 'borderBottomWidth', // positive number
+        'flexDirection', // 'column', 'row'
+        'justifyContent', // 'flex-start', 'center', 'flex-end', 'space-between', 'space-around'
+        'alignItems', 'alignSelf', // 'flex-start', 'center', 'flex-end', 'stretch'
+        'flex', // positive number
+        'flexWrap', // 'wrap', 'nowrap'
+        'position' // 'relative', 'absolute'
+    ];
+
+    var _engine = {
+        layoutAlgorithm: function() {
+            return 'cola';
+        },
+        layoutId: function() {
+            return _layoutId;
+        },
+        supportsWebworker: function() {
+            return true;
+        },
+        parent: property(null),
+        on: function(event, f) {
+            if(arguments.length === 1)
+                return _dispatch.on(event);
+            _dispatch.on(event, f);
+            return this;
+        },
+        init: function(options) {
+            this.optionNames().forEach(function(option) {
+                options[option] = options[option] || this[option]();
+            }.bind(this));
+            init(options);
+            return this;
+        },
+        data: function(graph, nodes) {
+            data(graph, nodes);
+        },
+        start: function() {
+            start();
+        },
+        stop: function() {
+            stop();
+        },
+        optionNames: function() {
+            return [];
+        },
+        populateLayoutNode: function(n1, n) {
+            ['sort', 'order'].concat(supportedAttributes).forEach(function(attr) {
+                if(n.orig.value[attr])
+                    n1[attr] = n.orig.value[attr];
+            });
+        },
+        populateLayoutEdge: function() {},
+        /**
+         * This function constructs a node key string from an "address". An address is an array of
+         * strings identifying the path from the root to the node.
+         *
+         * By default, it joins the address with commas.
+         * @method addressToKey
+         * @memberof dc_graph.flexbox_layout
+         * @instance
+         * @param {Function} [addressToKey = function(ad) { return ad.join(','); }]
+         * @return {Function}
+         * @return {dc_graph.flexbox_layout}
+         **/
+        addressToKey: property(function(ad) { return ad.join(','); }),
+        /**
+         * This function constructs an "address" from a node key string. An address is an array of
+         * strings identifying the path from the root to the node.
+         *
+         * By default, it splits the key by its commas.
+         * @method keyToAddress
+         * @memberof dc_graph.flexbox_layout
+         * @instance
+         * @param {Function} [keyToAddress = function(nid) { return nid.split(','); }]
+         * @return {Function}
+         * @return {dc_graph.flexbox_layout}
+         **/
+        keyToAddress: property(function(nid) { return nid.split(','); }),
+        yogaConstants: function() {
+            // in case any are missing, they can be added
+            // please file PRs for any missing constants!
+            return yoga_constants;
+        },
+        logStuff: property(false)
+    };
+    return _engine;
+};
+
+dc_graph.flexbox_layout.scripts = ['css-layout.js'];
+
+dc_graph.manual_layout = function(id) {
+    var _layoutId = id || uuid();
+    var _dispatch = d3.dispatch('tick', 'start', 'end');
+
+    var _wnodes;
+
+    function init(options) {
+    }
+    function data(nodes) {
+        _wnodes = nodes;
+    }
+    function dispatchState(wnodes, wedges, event) {
+        _dispatch[event](
+            wnodes,
+            wedges.map(function(e) {
+                return {dcg_edgeKey: e.dcg_edgeKey};
+            })
+        );
+    }
+    function start() {
+        dispatchState(_wnodes, [], 'end');
+    }
+    function stop() {
+    }
+
+    var _engine = {
+        layoutAlgorithm: function() {
+            return 'manual';
+        },
+        layoutId: function() {
+            return _layoutId;
+        },
+        supportsWebworker: function() {
+            return false;
+        },
+        parent: property(null),
+        on: function(event, f) {
+            if(arguments.length === 1)
+                return _dispatch.on(event);
+            _dispatch.on(event, f);
+            return this;
+        },
+        init: function(options) {
+            this.optionNames().forEach(function(option) {
+                options[option] = options[option] || this[option]();
+            }.bind(this));
+            init(options);
+            return this;
+        },
+        data: function(graph, nodes, edges) {
+            data(nodes);
+        },
+        start: function() {
+            start();
+        },
+        stop: function() {
+            stop();
+        },
+        optionNames: function() {
+            return [];
+        },
+        populateLayoutNode: function(n1, n) {
+            ['x', 'y'].forEach(function(attr) {
+                if(n.orig.value[attr] !== undefined)
+                    n1[attr] = n.orig.value[attr];
+            });
+        },
+        populateLayoutEdge: function() {},
+        addressToKey: property(function(ad) { return ad.join(','); }),
+        keyToAddress: property(function(nid) { return nid.split(','); })
+    };
+    return _engine;
+};
+
+dc_graph.manual_layout.scripts = ['css-layout.js'];
+
+/**
+ * `dc_graph.layered_layout` produces 3D layered layouts, utilizing another layout
+ * that supports fixed nodes and position hints for the layers
+ * @class layered_layout
+ * @memberof dc_graph
+ * @param {String} [id=uuid()] - Unique identifier
+ * @return {dc_graph.layered_layout}
+ **/
+dc_graph.layered_layout = function(id) {
+    var _layoutId = id || uuid();
+    var _dispatch = d3.dispatch('tick', 'start', 'end');
+    var _supergraph, _subgraphs;
+    var _layers;
+    var _options = null;
+
+    function init(options) {
+        _options = options;
+
+    }
+
+    function data(nodes, edges, constraints) {
+        _supergraph = dc_graph.supergraph({nodes: nodes, edges: edges}, {
+            nodeKey: function(n) { return n.dcg_nodeKey; },
+            edgeKey: function(n) { return n.dcg_edgeKey; },
+            nodeValue: function(n) { return n; },
+            edgeValue: function(e) { return e; },
+            edgeSource: function(e) { return e.dcg_edgeSource; },
+            edgeTarget: function(e) { return e.dcg_edgeTarget; }
+        });
+
+        // every node belongs natively in one rank
+        var nranks = _supergraph.nodes().reduce(function(p, n) {
+            var rank = engine.layerAccessor()(n.value());
+            p[rank] = p[rank] || [];
+            p[rank].push(n);
+            return p;
+        }, {});
+        var eranks = Object.keys(nranks).reduce(function(p, r) {
+            p[r] = [];
+            return p;
+        }, {});
+
+        // nodes are shadowed into any layers to which they are adjacent
+        // edges are induced from the native&shadow nodes in each layer
+        _supergraph.edges().forEach(function(e) {
+            var srank = engine.layerAccessor()(e.source().value()),
+                trank = engine.layerAccessor()(e.target().value());
+            if(srank == trank) {
+                eranks[srank].push(e);
+                return;
+            }
+            nranks[trank].push(e.source());
+            eranks[trank].push(e);
+            nranks[srank].push(e.target());
+            eranks[srank].push(e);
+        });
+
+        // produce a subgraph for each layer
+        _subgraphs = Object.keys(nranks).reduce(function(p, r) {
+            p[r] = _supergraph.subgraph(
+                nranks[r].map(function(n) { return n.key(); }),
+                eranks[r].map(function(e) { return e.key(); }));
+            return p;
+        }, {});
+
+        // start from the most populous layer
+        var max = null;
+        Object.keys(nranks).forEach(function(r) {
+            if(max === null ||
+               _subgraphs[r].nodes().length > _subgraphs[max].nodes().length)
+                max = +r;
+        });
+
+        // travel up and down from there, each time fixing the nodes from the last layer
+        var ranks = Object.keys(nranks).map(function(r) { return +r; }).sort();
+        _layers = ranks.map(function(r) {
+            return {
+                rank: r,
+                z: -r * engine.layerSeparationZ()
+            };
+        });
+        var mi = ranks.indexOf(max);
+        var ups = ranks.slice(mi+1), downs = ranks.slice(0, mi).reverse();
+        layout_layer(max, -1).then(function(layout) {
+            Promise.all([
+                layout_layers(layout, max, ups),
+                layout_layers(layout, max, downs)
+            ]).then(function() {
+                _dispatch.end(
+                    _supergraph.nodes().map(function(n) { return n.value(); }),
+                    _supergraph.edges().map(function(e) { return e.value(); }));
+            });
+        });
+    }
+
+    function layout_layers(layout, last, layers) {
+        if(layers.length === 0)
+            return Promise.resolve(layout);
+        var curr = layers.shift();
+        return layout_layer(curr, last).then(function(layout) {
+            return layout_layers(layout, curr, layers);
+        });
+    }
+
+    function layout_layer(r, last) {
+        _subgraphs[r].nodes().forEach(function(n) {
+            if(engine.layerAccessor()(n.value()) !== r &&
+               n.value().x !== undefined &&
+               n.value().y !== undefined)
+                n.value().dcg_nodeFixed = {
+                    x: n.value().x,
+                    y: n.value().y
+                };
+            else n.value().dcg_nodeFixed = null;
+        });
+        var subengine = engine.engineFactory()();
+        subengine.init(_options);
+        subengine.data(
+            {},
+            _subgraphs[r].nodes().map(function(n) {
+                return n.value();
+            }),
+            _subgraphs[r].edges().map(function(e) {
+                return e.value();
+            }));
+        return promise_layout(r, subengine);
+    }
+
+    function promise_layout(r, subengine) {
+        // stopgap - engine.start() should return a promise
+        return new Promise(function(resolve, reject) {
+            subengine.on('end', function(nodes, edges) {
+                resolve({nodes: nodes, edges: edges});
+            });
+            subengine.start();
+        }).then(function(layout) {
+            // copy positions back into the subgraph (and hence supergraph)
+            layout.nodes.forEach(function(ln) {
+                var n = _subgraphs[r].node(ln.dcg_nodeKey);
+                // do not copy positions for shadow nodes
+                if(engine.layerAccessor()(n.value()) !== r)
+                    return;
+                n.value().x = ln.x;
+                n.value().y = ln.y;
+                n.value().z = -r * engine.layerSeparationZ(); // lowest rank at top
+            });
+            return layout;
+        });
+    }
+
+    function start() {
+        _dispatch.start();
+    }
+
+    function stop() {
+    }
+
+    var graphviz = dc_graph.graphviz_attrs(), graphviz_keys = Object.keys(graphviz);
+
+    var engine = Object.assign(graphviz, {
+        layoutAlgorithm: function() {
+            return 'layered';
+        },
+        layoutId: function() {
+            return _layoutId;
+        },
+        supportsWebworker: function() {
+            return false;
+        },
+        parent: property(null),
+        on: function(event, f) {
+            if(arguments.length === 1)
+                return _dispatch.on(event);
+            _dispatch.on(event, f);
+            return this;
+        },
+        init: function(options) {
+            this.optionNames().forEach(function(option) {
+                options[option] = options[option] || this[option]();
+            }.bind(this));
+            init(options);
+            return this;
+        },
+        data: function(graph, nodes, edges, constraints) {
+            data(nodes, edges, constraints);
+        },
+        start: function() {
+            start();
+        },
+        stop: function() {
+            stop();
+        },
+        optionNames: function() {
+            return []
+                .concat(graphviz_keys);
+        },
+        engineFactory: property(null),
+        layerAccessor: property(null),
+        layerSeparationZ: property(50),
+        layers: function() {
+            return _layers;
+        },
+        populateLayoutNode: function() {},
+        populateLayoutEdge: function() {},
+        extractNodeAttrs: property({}), // {attr: function(node)}
+        extractEdgeAttrs: property({})
+    });
+    return engine;
+};
+
+
+
+function port_name(nodeId, edgeId, portName) {
+    if(!(nodeId || edgeId))
+        return null; // must have one key or the other
+    if(nodeId) nodeId = nodeId.replace(/\//g, '%2F');
+    if(edgeId) edgeId = edgeId.replace(/\//g, '%2F');
+    return (nodeId ? 'node/' + nodeId : 'edge/' + edgeId) + '/' + portName;
+};
+function split_port_name(portname) {
+    var parts = portname.split('/');
+    console.assert(parts.length === 3);
+    parts = parts.map(function(p) {
+        return p.replace(/%2F/g, '/');
+    });
+    if(parts[0] === 'node')
+        return {
+            nodeKey: parts[1],
+            name: parts[2]
+        };
+    else return {
+        edgeKey: parts[1],
+        name: parts[2]
+    };
+}
+function project_port(diagram, n, p) {
+    if(!p.vec) {
+        console.assert(!p.edges.length);
+        throw new Error("port has not been placed, maybe install place_ports? " + p.name);
+    }
+    p.pos = diagram.shape(n.dcg_shape.shape).intersect_vec(n, p.vec[0]*1000, p.vec[1]*1000);
+}
+
+dc_graph.place_ports = function() {
+    function received_layout(diagram, nodes, wnodes, edges, wedges, ports, wports) {
+        var node_ports = diagram.nodePorts();
+
+        function is_ccw(u, v) {
+            return u[0]*v[1] - u[1]*v[0] > 0;
+        }
+        function in_bounds(v, bounds) {
+            // assume bounds are ccw
+            return is_ccw(bounds[0], v) && is_ccw(v, bounds[1]);
+        }
+        function clip(v, bounds) {
+            if(is_ccw(v, bounds[0]))
+                return bounds[0];
+            else if(is_ccw(bounds[1], v))
+                return bounds[1];
+            else return v;
+        }
+        function a_to_v(a) {
+            return [Math.cos(a), Math.sin(a)];
+        }
+        function v_to_a(v) {
+            return Math.atan2(v[1], v[0]);
+        }
+        function distance(p, p2) {
+            return Math.hypot(p2.pos.x - p.pos.x, p2.pos.y - p.pos.y);
+        }
+        function misses(p, p2) {
+            var dist = distance(p, p2);
+            var misses = dist > _mode.minDistance();
+            return misses;
+        }
+        function rand_within(a, b) {
+            return a + Math.random()*(b-a);
+        }
+        // calculate port positions
+        for(var nid in node_ports) {
+            var n = nodes[nid],
+                nports = node_ports[nid];
+
+            // make sure that we have vector and angle bounds for any ports with specification
+            nports.forEach(function(p) {
+                var bounds = p.orig && diagram.portBounds.eval(p) || [0, 2*Math.PI];
+                if(Array.isArray(bounds[0])) {
+                    p.vbounds = bounds;
+                    p.abounds = bounds.map(v_to_a);
+                }
+                else {
+                    p.vbounds = bounds.map(a_to_v);
+                    p.abounds = bounds;
+                }
+                if(p.abounds[0] > p.abounds[1])
+                    p.abounds[1] += 2*Math.PI;
+                console.assert(p.orig || p.vec, 'unplaced unspecified port');
+            });
+
+            // determine which ports satisfy bounds or are unplaced
+            var inside = [], outside = [], unplaced = [];
+            nports.forEach(function(p) {
+                if(!p.vec)
+                    unplaced.push(p);
+                else if(p.vbounds && !in_bounds(p.vec, p.vbounds))
+                    outside.push(p);
+                else
+                    inside.push(p);
+            });
+
+            // shunt outside ports into their bounds
+            outside.forEach(function(p) {
+                p.vec = clip(p.vec, p.vbounds);
+                inside.push(p);
+            });
+
+            // for all unplaced ports that share a bounds, evenly distribute them within those bounds.
+            // assume that bounds are disjoint.
+            var boundses = {}, boundports = {};
+            unplaced.forEach(function(p) {
+                var boundskey = p.abounds.map(function(x) { return x.toFixed(3); }).join(',');
+                boundses[boundskey] = p.abounds;
+                boundports[boundskey] = boundports[boundskey] || [];
+                boundports[boundskey].push(p);
+            });
+            for(var b in boundports) {
+                var bounds = boundses[b], bports = boundports[b];
+                if(bports.length === 1)
+                    bports[0].vec = a_to_v((bounds[0] + bounds[1])/2);
+                else {
+                    var slice = (bounds[1] - bounds[0]) / (boundports[b].length - 1);
+                    boundports[b].forEach(function(p, i) {
+                        p.vec = a_to_v(bounds[0] + i*slice);
+                    });
+                }
+            }
+            inside = inside.concat(unplaced);
+            unplaced = [];
+
+            // determine positions of all satisfied
+            inside.forEach(function(p) {
+                project_port(diagram, n, p);
+            });
+
+            // detect any existing collisions, unplace the one without edges or second one
+            for(var i = 0; i < inside.length; ++i) {
+                var x = inside[i];
+                if(unplaced.includes(x))
+                    continue;
+                for(var j = i+1; j < inside.length; ++j) {
+                    var y = inside[j];
+                    if(unplaced.includes(y))
+                        continue;
+                    if(!misses(x, y)) {
+                        if(!x.edges.length) {
+                            unplaced.push(x);
+                            continue;
+                        }
+                        else
+                            unplaced.push(y);
+                    }
+                }
+            }
+            inside = inside.filter(function(p) { return !unplaced.includes(p); });
+
+            // place any remaining by trying random spots within the range until it misses all or we give up
+            var patience = _mode.patience(), maxdist = 0, maxvec;
+            while(unplaced.length) {
+                var p = unplaced[0];
+                p.vec = a_to_v(rand_within(p.abounds[0], p.abounds[1]));
+                project_port(diagram, n, p);
+                var mindist = d3.min(inside, function(p2) { return distance(p, p2); });
+                if(mindist > maxdist) {
+                    maxdist = mindist;
+                    maxvec = p.vec;
+                }
+                if(!patience-- || mindist > _mode.minDistance()) {
+                    if(patience<0) {
+                        console.warn('ran out of patience placing a port');
+                        p.vec = maxvec;
+                        project_port(diagram, n, p);
+                    }
+                    inside.push(p);
+                    unplaced.shift();
+                    patience = _mode.patience();
+                    maxdist = 0;
+                }
+            }
+        }
+    };
+    var _mode = {
+        parent: property(null).react(function(p) {
+            if(p) {
+                p.on('receivedLayout.place-ports', received_layout);
+            } else if(_mode.parent())
+                _mode.parent().on('receivedLayout.place-ports', null);
+        }),
+        // minimum distance between ports
+        minDistance: property(20),
+        // number of random places to try when resolving collision
+        patience: property(20)
+    };
+
+    return _mode;
+};
+
+dc_graph.grid = function() {
+    var _gridLayer = null;
+    var _translate, _scale, _xDomain, _yDomain;
+
+    function draw(diagram, node, edge, ehover) {
+        //infer_and_draw(diagram);
+    }
+
+    function remove(diagram, node, edge, ehover) {
+        if(_gridLayer)
+            _gridLayer.remove();
+    }
+
+    function draw(diagram) {
+        _gridLayer = diagram.g().selectAll('g.grid-layer').data([0]);
+        _gridLayer.enter().append('g').attr('class', 'grid-layer');
+        var ofs = _mode.wholeOnLines() ? 0 : 0.5;
+        var vline_data = _scale >= _mode.threshold() ? d3.range(Math.floor(_xDomain[0]), Math.ceil(_xDomain[1]) + 1) : [];
+        var vlines = _gridLayer.selectAll('line.grid-line.vertical')
+            .data(vline_data, function(d) { return d - ofs; });
+        vlines.exit().remove();
+        vlines.enter().append('line')
+            .attr({
+                class: 'grid-line vertical',
+                x1: function(d) { return d - ofs; },
+                x2: function(d) { return d - ofs; }
+            });
+        vlines.attr({
+            'stroke-width': 1/_scale,
+            y1: _yDomain[0],
+            y2: _yDomain[1]
+        });
+        var hline_data = _scale >= _mode.threshold() ? d3.range(Math.floor(_yDomain[0]), Math.ceil(_yDomain[1]) + 1) : [];
+        var hlines = _gridLayer.selectAll('line.grid-line.horizontal')
+            .data(hline_data, function(d) { return d - ofs; });
+        hlines.exit().remove();
+        hlines.enter().append('line')
+            .attr({
+                class: 'grid-line horizontal',
+                y1: function(d) { return d - ofs; },
+                y2: function(d) { return d - ofs; }
+            });
+        hlines.attr({
+            'stroke-width': 1/_scale,
+            x1: _xDomain[0],
+            x2: _xDomain[1]
+        });
+    }
+
+    function on_zoom(translate, scale, xDomain, yDomain) {
+        _translate = translate;
+        _scale = scale;
+        _xDomain = xDomain,
+        _yDomain = yDomain;
+        draw(_mode.parent());
+    }
+
+    function infer_and_draw(diagram) {
+        _translate = diagram.translate();
+        _scale = diagram.scale();
+        _xDomain = diagram.x().domain();
+        _yDomain = diagram.y().domain();
+        draw(diagram);
+    }
+
+    var _mode = dc_graph.mode('highlight-paths', {
+        draw: draw,
+        remove: remove,
+        parent: function(p) {
+            if(p) {
+                p.on('zoomed.grid', on_zoom);
+                infer_and_draw(p);
+            }
+        }
+    });
+
+    _mode.threshold = property(4);
+    _mode.wholeOnLines = property(true);
+
+    return _mode;
+};
+
+
+
+dc_graph.annotate_layers = function() {
+    // svg-specific
+    var _drawLayer;
+    // wegl-specific
+    var _planes = [];
+    var _planeGeometry;
+    var _mode = dc_graph.mode('annotate-layers', {
+        laterDraw: true,
+        renderers: ['svg', 'webgl'],
+        draw: draw,
+        remove: remove
+    });
+    function draw(diagram) {
+        var rendererType = _mode.parent().renderer().rendererType();
+        var engine = _mode.parent().layoutEngine();
+        if(rendererType === 'svg') {
+            if(engine.layoutAlgorithm() === 'cola' &&
+               engine.setcolaSpec() && engine.setcolaNodes()) {
+                _drawLayer = _mode.parent().select('g.draw').selectAll('g.divider-layer').data([0]);
+                _drawLayer.enter().append('g').attr('class', 'divider-layer');
+                var boundary_nodes = engine.setcolaNodes().filter(function(n) {
+                    return /^sort_order_boundary/.test(n.name);
+                });
+                var lines = _drawLayer.selectAll('line.divider').data(boundary_nodes);
+                lines.exit().remove();
+                lines.enter().append('line')
+                    .attr('class', 'divider');
+                lines.attr({
+                    stroke: _mode.stroke(),
+                    'stroke-width': _mode.strokeWidth(),
+                    'stroke-dasharray': _mode.strokeDashArray(),
+                    x1: -5000,
+                    y1: function(n) {
+                        return n.y;
+                    },
+                    x2: 5000,
+                    y2:  function(n) {
+                        return n.y;
+                    }
+                });
+            }
+        } else if(rendererType === 'webgl') {
+            var MULT = _mode.parent().renderer().multiplier();
+            var scene = arguments[1], drawState = arguments[2];
+            if(engine.layoutAlgorithm() === 'layered' && engine.layers()) {
+                var width = drawState.extents[0][1] - drawState.extents[0][0] + _mode.planePadding()*MULT*2,
+                    height = drawState.extents[1][1] - drawState.extents[1][0] + _mode.planePadding()*MULT*2;
+                var delGeom;
+                var shape = new THREE.Shape();
+                shape.moveTo(0, 0);
+                shape.lineTo(0, height);
+                shape.lineTo(width, height);
+                shape.lineTo(width, 0);
+                shape.lineTo(0, 0);
+                if(_planeGeometry)
+                    delGeom = _planeGeometry;
+                _planeGeometry = new THREE.ShapeBufferGeometry(shape);
+
+                var layers = engine.layers();
+                if(layers.length < _planes.length) {
+                    for(var i = layers.length; i < _planes.length; ++i)
+                        scene.remove(_planes[i].mesh);
+                    _planes = _planes.slice(0, layers.length);
+                }
+                layers.forEach(function(layer, i) {
+                    if(!_planes[i])
+                        _planes[i] = Object.assign({}, layer);
+                    if(_planes[i].mesh)
+                        scene.remove(_planes[i].mesh);
+                    var mesh = _planes[i].mesh = new THREE.Mesh(_planeGeometry, new THREE.MeshStandardMaterial({
+                        opacity: _mode.planeOpacity(),
+                        transparent: true,
+                        color: _mode.parent().renderer().color_to_int(_mode.planeColor()),
+                        side: THREE.DoubleSide
+                    }));
+                    mesh.position.set(drawState.extents[0][0] - _mode.planePadding()*MULT,
+                                      drawState.extents[1][0] - _mode.planePadding()*MULT,
+                                      layer.z * MULT);
+                    scene.add(mesh);
+                });
+                if(delGeom)
+                    delGeom.dispose();
+            }
+        } else throw new Error("annotate_layers doesn't know how to work with renderer " + rendererType);
+    }
+    function remove() {
+        if(_drawLayer)
+            _drawLayer.remove();
+    }
+
+    // line properties for svg
+    _mode.stroke = property('black');
+    _mode.strokeWidth = property(2);
+    _mode.strokeDashArray = property([5,5]);
+
+    // plane properties
+    _mode.planePadding = property(5);
+    _mode.planeOpacity = property(0.2);
+    _mode.planeColor = property('#ffffdd');
+    return _mode;
+};
+
+dc_graph.troubleshoot = function() {
+    var _debugLayer = null;
+    var _translate, _scale = 1, _xDomain, _yDomain;
+
+    function draw(diagram, node, edge, ehover) {
+        if(!_debugLayer)
+            _debugLayer = diagram.g().append('g').attr({
+                class: 'troubleshoot',
+                'pointer-events': 'none'
+            });
+        var centers = node.data().map(function(n) {
+            return {
+                x: n.cola.x,
+                y: n.cola.y
+            };
+        });
+        var crosshairs = _debugLayer.selectAll('path.nodecenter').data(centers);
+        crosshairs.exit().remove();
+        crosshairs.enter().append('path').attr('class', 'nodecenter');
+        crosshairs.attr({
+            d: function(c) {
+                return 'M' + (c.x - _mode.xhairWidth()/2) + ',' + c.y + ' h' + _mode.xhairWidth() +
+                    ' M' + c.x + ',' + (c.y - _mode.xhairHeight()/2) + ' v' + _mode.xhairHeight();
+            },
+            opacity: _mode.xhairOpacity() !== null ? _mode.xhairOpacity() : _mode.opacity(),
+            stroke: _mode.xhairColor(),
+            'stroke-width': 1/_scale
+        });
+        function cola_point(n) {
+            return {x: n.cola.x, y: n.cola.y};
+        }
+        var colabounds = node.data().map(function(n) {
+            return boundary(cola_point(n), n.cola.width, n.cola.height);
+        });
+        var colaboundary = _debugLayer.selectAll('path.colaboundary').data(colabounds);
+        draw_corners(colaboundary, 'colaboundary', _mode.boundsColor());
+
+        var textbounds = node.data().map(function(n) {
+            if(!n.bbox || (!n.bbox.width && !n.bbox.height))
+                return null;
+            return boundary(cola_point(n), n.bbox.width, n.bbox.height);
+        }).filter(function(n) { return !!n; });
+        var textboundary = _debugLayer.selectAll('path.textboundary').data(textbounds);
+        draw_corners(textboundary, 'textboundary', _mode.boundsColor());
+
+        var radiibounds = node.data().map(function(n) {
+            if(typeof n.dcg_rx !== 'number')
+                return null;
+            return boundary(cola_point(n), n.dcg_rx*2, n.dcg_ry*2);
+        }).filter(function(n) { return !!n; });
+        var radiiboundary = _debugLayer.selectAll('path.radiiboundary').data(radiibounds);
+        draw_corners(radiiboundary, 'radiiboundary', _mode.boundsColor());
+
+        diagram.addOrRemoveDef('debug-orient-marker-head',
+                               true,
+                               'svg:marker',
+                               orient_marker.bind(null, _mode.arrowHeadColor()));
+        diagram.addOrRemoveDef('debug-orient-marker-tail',
+                               true,
+                               'svg:marker',
+                               orient_marker.bind(null, _mode.arrowTailColor()));
+        var heads = _mode.arrowLength() ? edge.data().map(function(e) {
+            return {pos: e.pos.new.path.points[e.pos.new.path.points.length-1], orient: e.pos.new.orienthead};
+        }) : [];
+        var headOrients = _debugLayer.selectAll('line.heads').data(heads);
+        draw_arrow_orient(headOrients, 'heads', _mode.arrowHeadColor(), '#debug-orient-marker-head');
+
+        var tails = _mode.arrowLength() ? edge.data().map(function(e) {
+            return {pos: e.pos.new.path.points[0], orient: e.pos.new.orienttail};
+        }) : [];
+        var tailOrients = _debugLayer.selectAll('line.tails').data(tails);
+        draw_arrow_orient(tailOrients, 'tails', _mode.arrowTailColor(), '#debug-orient-marker-tail');
+
+        var headpts = Array.prototype.concat.apply([], edge.data().map(function(e) {
+            var arrowSize = diagram.edgeArrowSize.eval(e);
+            return edge_arrow_points(
+                diagram.arrows(),
+                diagram.edgeArrowhead.eval(e),
+                arrowSize,
+                diagram.edgeStrokeWidth.eval(e) / arrowSize,
+                unrad(e.pos.new.orienthead),
+                e.pos.new.full.points[e.pos.new.full.points.length-1],
+                diagram.nodeStrokeWidth.eval(e.target)
+            );
+        }));
+        var hp = _debugLayer.selectAll('path.head-point').data(headpts);
+        draw_x(hp, 'head-point', _mode.arrowHeadColor());
+
+        var tailpts = Array.prototype.concat.apply([], edge.data().map(function(e) {
+            var arrowSize = diagram.edgeArrowSize.eval(e);
+            return edge_arrow_points(
+                diagram.arrows(),
+                diagram.edgeArrowtail.eval(e),
+                arrowSize,
+                diagram.edgeStrokeWidth.eval(e) / arrowSize,
+                unrad(e.pos.new.orienttail),
+                e.pos.new.full.points[0],
+                diagram.nodeStrokeWidth.eval(e.source)
+            );
+        }));
+        var tp = _debugLayer.selectAll('path.tail-point').data(tailpts);
+        draw_x(tp, 'tail-point', _mode.arrowTailColor());
+
+        var domain = _debugLayer.selectAll('rect.domain').data([0]);
+        domain.enter().append('rect');
+        var xd = _mode.parent().x().domain(), yd = _mode.parent().y().domain();
+        domain.attr({
+            class: 'domain',
+            fill: 'none',
+            opacity: _mode.domainOpacity(),
+            stroke: _mode.domainColor(),
+            'stroke-width': _mode.domainStrokeWidth()/_scale,
+            x: xd[0],
+            y: yd[0],
+            width: xd[1] - xd[0],
+            height: yd[1] - yd[0]
+        });
+    }
+    function on_zoom(translate, scale, xDomain, yDomain) {
+        _translate = translate;
+        _scale = scale;
+        _xDomain = xDomain;
+        _yDomain = yDomain;
+        draw(_mode.parent(), _mode.parent().selectAllNodes(), _mode.parent().selectAllEdges());
+    }
+
+    function boundary(point, wid, hei) {
+        return {
+            left: point.x - wid/2,
+            top: point.y - hei/2,
+            right: point.x + wid/2,
+            bottom: point.y + hei/2
+        };
+    };
+    function bound_tick(x, y, dx, dy) {
+        return 'M' + x + ',' + (y + dy) + ' v' + -dy + ' h' + dx;
+    }
+    function corners(bounds) {
+        return [
+            bound_tick(bounds.left, bounds.top, _mode.boundsWidth(), _mode.boundsHeight()),
+            bound_tick(bounds.right, bounds.top, -_mode.boundsWidth(), _mode.boundsHeight()),
+            bound_tick(bounds.right, bounds.bottom, -_mode.boundsWidth(), -_mode.boundsHeight()),
+            bound_tick(bounds.left, bounds.bottom, _mode.boundsWidth(), -_mode.boundsHeight()),
+        ].join(' ');
+    }
+    function draw_corners(binding, classname, color) {
+        binding.exit().remove();
+        binding.enter().append('path').attr('class', classname);
+        binding.attr({
+            d: corners,
+            opacity: _mode.boundsOpacity() !== null ? _mode.boundsOpacity() : _mode.opacity(),
+            stroke: color,
+            'stroke-width': 1/_scale,
+            fill: 'none'
+        });
+    }
+        function unrad(orient) {
+            return +orient.replace('rad','');
+        }
+    function draw_arrow_orient(binding, classname, color, markerUrl) {
+        binding.exit().remove();
+        binding.enter().append('line').attr('class', classname);
+        binding.attr({
+            x1: function(d) { return d.pos.x; },
+            y1: function(d) { return d.pos.y; },
+            x2: function(d) { return d.pos.x - Math.cos(unrad(d.orient))*_mode.arrowLength(); },
+            y2: function(d) { return d.pos.y - Math.sin(unrad(d.orient))*_mode.arrowLength(); },
+            stroke: color,
+            'stroke-width': _mode.arrowStrokeWidth()/_scale,
+            opacity: _mode.arrowOpacity() !== null ? _mode.arrowOpacity() : _mode.opacity(),
+            'marker-end': 'url(' + markerUrl + ')'
+        });
+    }
+    function orient_marker(color, markerEnter) {
+        markerEnter
+            .attr({
+                viewBox: '0 -3 3 6',
+                refX: 3,
+                refY: 0,
+                orient: 'auto'
+            });
+        markerEnter.append('path')
+            .attr('stroke', color)
+            .attr('fill', 'none')
+            .attr('d', 'M0,3 L3,0 L0,-3');
+    }
+    function edge_arrow_points(arrows, defn, arrowSize, stemWidth, orient, endp, strokeWidth) {
+        var parts = arrow_parts(arrows, defn),
+            offsets = arrow_offsets(parts, stemWidth),
+            xunit = [Math.cos(orient), Math.sin(orient)];
+        endp = [endp.x, endp.y];
+        if(!parts.length)
+            return [[endp[0] - xunit[0]*strokeWidth/2,
+                     endp[1] - xunit[1]*strokeWidth/2]];
+        var globofs = add_points(
+            [-strokeWidth/arrowSize/2,0],
+            mult_point(front_ref(parts[0].frontRef), -1));
+        var pts = offsets.map(function(ofs, i) {
+            return mult_point([
+                globofs,
+                front_ref(parts[i].frontRef),
+                ofs.offset
+            ].reduce(add_points), arrowSize);
+        });
+        pts.push(mult_point([
+            globofs,
+            back_ref(parts[parts.length-1].backRef),
+            offsets[parts.length-1].offset
+        ].reduce(add_points), arrowSize));
+        return pts.map(function(p) {
+            return add_points(
+                endp,
+                [p[0]*xunit[0] - p[1]*xunit[1], p[0]*xunit[1] + p[1]*xunit[0]]
+            );
+        });
+    }
+
+
+    function draw_x(binding, classname, color) {
+        var xw = _mode.xWidth()/2, xh = _mode.xHeight()/2;
+        binding.exit().remove();
+        binding.enter().append('path').attr('class', classname);
+        binding.attr({
+            d: function(pos) {
+                return [[[-xw,-xh],[xw,xh]], [[xw,-xh], [-xw,xh]]].map(function(seg) {
+                    return 'M' + seg.map(function(p) {
+                        return (pos[0] + p[0]) + ',' + (pos[1] + p[1]);
+                    }).join(' L');
+                }).join(' ');
+            },
+            'stroke-width': 2/_scale,
+            stroke: color,
+            opacity: _mode.xOpacity()
+        });
+    }
+    function remove(diagram, node, edge, ehover) {
+        if(_debugLayer)
+            _debugLayer.remove();
+    }
+
+    var _mode = dc_graph.mode('highlight-paths', {
+        laterDraw: true,
+        draw: draw,
+        remove: remove,
+        parent: function(p) {
+            if(p) {
+                _translate = p.translate();
+                _scale = p.scale();
+                p.on('zoomed.troubleshoot', on_zoom);
+            }
+            else if(_mode.parent())
+                _mode.parent().on('zoomed.troubleshoot', null);
+        }
+    });
+    _mode.opacity = property(0.75);
+
+    _mode.xhairOpacity = property(null);
+    _mode.xhairWidth = property(10);
+    _mode.xhairHeight = property(10);
+    _mode.xhairColor = property('blue');
+
+    _mode.boundsOpacity = property(null);
+    _mode.boundsWidth = property(10);
+    _mode.boundsHeight = property(10);
+    _mode.boundsColor = property('green');
+
+    _mode.arrowOpacity = property(null);
+    _mode.arrowStrokeWidth = property(3);
+    _mode.arrowColor = _mode.arrowHeadColor = property('darkorange');
+    _mode.arrowTailColor = property('red');
+    _mode.arrowLength = property(100);
+
+    _mode.xWidth = property(1);
+    _mode.xHeight = property(1);
+    _mode.xOpacity = property(0.8);
+
+    _mode.domainOpacity = property(0.6);
+    _mode.domainColor = property('darkorange');
+    _mode.domainStrokeWidth = property(4);
+
+    return _mode;
+};
+
+
+ dc_graph.validate = function(title) {
+    function falsy(objects, accessor, what, who) {
+        var f = objects.filter(function(o) {
+            return !accessor(o);
+        });
+        return f.length ?
+            [what + ' is empty for ' + f.length + ' of ' + objects.length + ' ' + who, f] :
+            null;
+    }
+    function build_index(objects, accessor) {
+        return objects.reduce(function(m, o) {
+            m[accessor(o)] = o;
+            return m;
+        }, {});
+    }
+    function not_found(index, objects, accessor, what, where, who) {
+        var nf = objects.filter(function(o) {
+            return !index[accessor(o)];
+        }).map(function(o) {
+            return {key: accessor(o), value: o};
+        });
+        return nf.length ?
+            [what + ' was not found in ' + where, Object.keys(index), 'for ' + nf.length + ' of ' + objects.length + ' ' + who, nf] :
+            null;
+    }
+    function validate() {
+        var diagram = _mode.parent();
+        var nodes = diagram.nodeGroup().all(),
+            edges = diagram.edgeGroup().all(),
+            ports = diagram.portGroup() ? diagram.portGroup().all() : [];
+        var errors = [];
+
+        function check(error) {
+            if(error)
+                errors.push(error);
+        }
+
+        check(falsy(nodes, diagram.nodeKey(), 'nodeKey', 'nodes'));
+        check(falsy(edges, diagram.edgeSource(), 'edgeSource', 'edges'));
+        check(falsy(edges, diagram.edgeTarget(), 'edgeTarget', 'edges'));
+
+        var contentTypes = d3.set(diagram.content.enum());
+        var ct = dc_graph.functor_wrap(diagram.nodeContent());
+        var noContentNodes = nodes.filter(function(kv) {
+            return !contentTypes.has(ct(kv));
+        });
+        if(noContentNodes.length)
+            errors.push(['there are ' + noContentNodes.length + ' nodes with nodeContent not matching any content', noContentNodes]);
+
+        var nindex = build_index(nodes, diagram.nodeKey()),
+            eindex = build_index(edges, diagram.edgeKey());
+        check(not_found(nindex, edges, diagram.edgeSource(), 'edgeSource', 'nodes', 'edges'));
+        check(not_found(nindex, edges, diagram.edgeTarget(), 'edgeTarget', 'nodes', 'edges'));
+
+        check(falsy(ports, function(p) {
+            return diagram.portNodeKey() && diagram.portNodeKey()(p) ||
+                diagram.portEdgeKey() && diagram.portEdgeKey()(p);
+        }, 'portNodeKey||portEdgeKey', 'ports'));
+
+        var named_ports = !diagram.portNodeKey() && [] || ports.filter(function(p) {
+            return diagram.portNodeKey()(p);
+        });
+        var anonymous_ports = !diagram.portEdgeKey() && [] || ports.filter(function(p) {
+            return diagram.portEdgeKey()(p);
+        });
+        check(not_found(nindex, named_ports, diagram.portNodeKey(), 'portNodeKey', 'nodes', 'ports'));
+        check(not_found(eindex, anonymous_ports, diagram.portEdgeKey(), 'portEdgeKey', 'edges', 'ports'));
+
+        if(diagram.portName()) {
+            var pindex = build_index(named_ports, function(p) {
+                return diagram.portNodeKey()(p) + ' - ' + diagram.portName()(p);
+            });
+            if(diagram.edgeSourcePortName())
+                check(not_found(pindex, edges, function(e) {
+                    return diagram.edgeSource()(e) + ' - ' + d3.functor(diagram.edgeSourcePortName())(e);
+                }, 'edgeSourcePortName', 'ports', 'edges'));
+            if(diagram.edgeTargetPortName())
+                check(not_found(pindex, edges,  function(e) {
+                    return diagram.edgeTarget()(e) + ' - ' + d3.functor(diagram.edgeTargetPortName())(e);
+                }, 'edgeTargetPortName', 'ports', 'edges'));
+        }
+
+        function count_text() {
+            return nodes.length + ' nodes, ' + edges.length + ' edges, ' + ports.length + ' ports';
+        }
+        if(errors.length) {
+            console.warn('validation of ' + title + ' failed with ' + count_text() + ':');
+            errors.forEach(function(err) {
+                console.warn.apply(console, err);
+            });
+        }
+        else
+            console.log('validation of ' + title + ' succeeded with ' + count_text() + '.');
+    }
+    var _mode = {
+        parent: property(null).react(function(p) {
+            if(p)
+                p.on('data.validate', validate);
+            else
+                _mode.parent().on('data.validate', null);
+        })
+    };
+
+    return _mode;
+};
+
+/**
+## Legend
+
+The dc_graph.legend shows labeled examples of nodes & edges, within the frame of a dc_graph.diagram.
+**/
+dc_graph.legend = function(legend_namespace) {
+    legend_namespace = legend_namespace || 'node-legend';
+    var _items, _included = [];
+    var _dispatch = d3.dispatch('filtered');
+    var _totals, _counts;
+
+    var _svg_renderer;
+
+    function apply_filter() {
+        if(_legend.dimension()) {
+            if(_legend.isTagDimension()) {
+                _legend.dimension().filterFunction(function(ks) {
+                    return !_included.length || ks.filter(function(k) {
+                        return _included.includes(k);
+                    }).length;
+                });
+            } else {
+                _legend.dimension().filterFunction(function(k) {
+                    return !_included.length || _included.includes(k);
+                });
+            }
+            _legend.parent().redraw();
+        }
+    }
+
+    var _legend = dc_graph.mode(legend_namespace, {
+        renderers: ['svg', 'webgl'],
+        draw: redraw,
+        remove: function() {},
+        parent: function(p) {
+            if(p) {
+                p
+                    .on('render.' + legend_namespace, render)
+                    .on('data.' + legend_namespace, on_data);
+            }
+            else {
+                _legend.parent()
+                    .on('render.' + legend_namespace, null)
+                    .on('data.' + legend_namespace, null);
+            }
+        }
+    });
+
+    /**
+     #### .type([value])
+     Set or get the handler for the specific type of item to be displayed. Default: dc_graph.legend.node_legend()
+     **/
+    _legend.type = property(dc_graph.legend.node_legend());
+
+    /**
+     #### .x([value])
+     Set or get x coordinate for legend widget. Default: 0.
+     **/
+    _legend.x = property(0);
+
+    /**
+     #### .y([value])
+     Set or get y coordinate for legend widget. Default: 0.
+     **/
+    _legend.y = property(0);
+
+    /**
+     #### .gap([value])
+     Set or get gap between legend items. Default: 5.
+     **/
+    _legend.gap = property(5);
+
+    /**
+     #### .itemWidth([value])
+     Set or get width to reserve for legend item. Default: 30.
+     **/
+    _legend.itemWidth = _legend.nodeWidth = property(40);
+
+    /**
+     #### .itemHeight([value])
+     Set or get height to reserve for legend item. Default: 30.
+    **/
+    _legend.itemHeight = _legend.nodeHeight = property(40);
+
+    _legend.dyLabel = property('0.3em');
+
+    _legend.omitEmpty = property(false);
+
+    /**
+     #### .noLabel([value])
+     Remove item labels, since legend labels are displayed outside of the items. Default: true
+    **/
+    _legend.noLabel = property(true);
+
+    _legend.counter = property(null);
+
+    _legend.replaceFilter = function(filter) {
+        if(filter && filter.length === 1)
+            _included = filter[0];
+        else
+            _included = [];
+        return _legend;
+    };
+
+    _legend.filters = function() {
+        return _included;
+    };
+
+    _legend.on = function(type, f) {
+        _dispatch.on(type, f);
+        return _legend;
+    };
+
+    /**
+     #### .exemplars([object])
+     Specifies an object where the keys are the names of items to add to the legend, and the values are
+     objects which will be passed to the accessors of the attached diagram in order to determine the
+     drawing attributes. Alternately, if the key needs to be specified separately from the name, the
+     function can take an array of {name, key, value} objects.
+     **/
+    _legend.exemplars = property({});
+
+    function on_data(diagram, nodes, wnodes, edges, wedges, ports, wports) {
+        if(_legend.counter())
+            _counts = _legend.counter()(wnodes.map(get_original), wedges.map(get_original), wports.map(get_original));
+    }
+
+    _legend.redraw = deprecate_function("dc_graph.legend is an ordinary mode now; redraw will go away soon", redraw);
+    function redraw() {
+        var legend = (_svg_renderer || _legend.parent()).svg()
+                .selectAll('g.dc-graph-legend.' + legend_namespace)
+                .data([0]);
+        legend.enter().append('g')
+            .attr('class', 'dc-graph-legend ' + legend_namespace)
+            .attr('transform', 'translate(' + _legend.x() + ',' + _legend.y() + ')');
+
+        var items = !_legend.omitEmpty() || !_counts ? _items : _items.filter(function(i) {
+            return _included.length && !_included.includes(i.orig.key) || _counts[i.orig.key];
+        });
+        var item = legend.selectAll(_legend.type().itemSelector())
+                .data(items, function(n) { return n.name; });
+        item.exit().remove();
+        var itemEnter = _legend.type().create(_legend.parent(), item.enter(), _legend.itemWidth(), _legend.itemHeight());
+        itemEnter.append('text')
+            .attr('dy', _legend.dyLabel())
+            .attr('class', 'legend-label');
+        item
+            .attr('transform', function(n, i) {
+                return 'translate(' + _legend.itemWidth()/2 + ',' + (_legend.itemHeight() + _legend.gap())*(i+0.5) + ')';
+            });
+        item.select('text.legend-label')
+            .attr('transform', 'translate(' + (_legend.itemWidth()/2+_legend.gap()) + ',0)')
+            .attr('pointer-events', _legend.dimension() ? 'auto' : 'none')
+            .text(function(d) {
+                return d.name + (_legend.counter() && _counts ? (' (' + (_counts[d.orig.key] || 0) + (_counts[d.orig.key] !== _totals[d.orig.key] ? '/' + (_totals[d.orig.key] || 0) : '') + ')') : '');
+            });
+        _legend.type().draw(_svg_renderer || _legend.parent(), itemEnter, item);
+        if(_legend.noLabel())
+            item.selectAll(_legend.type().labelSelector()).remove();
+
+        if(_legend.dropdown()) {
+            var caret = item.selectAll('text.dropdown-caret').data(function(x) { return [x]; });
+            caret
+              .enter().append('text')
+                .attr('dy', '0.3em')
+                .attr('font-size', '75%')
+                .attr('fill', 'blue')
+                .attr('class', 'dropdown-caret')
+                .style('visibility', 'hidden')
+                .html('&emsp;&#x25BC;');
+            caret
+                .attr('dx', function(d) {
+                    return (_legend.itemWidth()/2+_legend.gap()) + getBBoxNoThrow(d3.select(this.parentNode).select('text.legend-label').node()).width;
+                })
+                .on('mouseenter.' + legend_namespace, function(n) {
+                    var rect = this.getBoundingClientRect();
+                    var key = _legend.parent().nodeKey.eval(n);
+                    _legend.dropdown()
+                        .show(key, rect.x, rect.y);
+                });
+            item
+                .on('mouseenter.' + legend_namespace, function(d) {
+                    if(_counts && _counts[d.orig.key]) {
+                        d3.select(this).selectAll('.dropdown-caret')
+                            .style('visibility', 'visible');
+                    }
+                })
+                .on('mouseleave.' + legend_namespace, function(d) {
+                    d3.select(this).selectAll('.dropdown-caret')
+                        .style('visibility', 'hidden');
+                });
+        }
+
+        if(_legend.dimension()) {
+            item.attr('cursor', 'pointer')
+                .on('click.' + legend_namespace, function(d) {
+                    var key = _legend.parent().nodeKey.eval(d);
+                    if(!_included.length)
+                        _included = _items.map(_legend.parent().nodeKey.eval);
+                    if(_included.includes(key))
+                        _included = _included.filter(function(x) { return x !== key; });
+                    else
+                        _included.push(key);
+                    apply_filter();
+                    _dispatch.filtered(_legend, key);
+                    if(_svg_renderer)
+                        window.setTimeout(redraw, 250);
+                });
+        } else {
+            item.attr('cursor', 'auto')
+                .on('click.' + legend_namespace, null);
+        }
+        item.transition().duration(1000)
+            .attr('opacity', function(d) {
+                return (!_included.length || _included.includes(_legend.parent().nodeKey.eval(d))) ? 1 : 0.25;
+            });
+    };
+
+    _legend.countBaseline = function() {
+        if(_legend.counter())
+            _totals = _legend.counter()(
+                _legend.parent().nodeGroup().all(),
+                _legend.parent().edgeGroup().all(),
+                _legend.parent().portGroup() && _legend.parent().portGroup().all());
+    };
+
+    _legend.render = deprecate_function("dc_graph.legend is an ordinary mode now; render will go away soon", render);
+    function render() {
+        if(_legend.parent().renderer().rendererType() !== 'svg') {
+            _svg_renderer = dc_graph.render_svg();
+            _svg_renderer.parent(_legend.parent())
+                .svg(_legend.parent().root().append('svg')
+                     .style({
+                         position: 'absolute',
+                         left: 0, top: 0,
+                         width: '100%', height: '100%',
+                         fill: 'wheat',
+                         'pointer-events': 'none'
+                     }));
+        }
+
+
+        var exemplars = _legend.exemplars();
+        _legend.countBaseline();
+        if(exemplars instanceof Array) {
+            _items = exemplars.map(function(v) { return {name: v.name, orig: {key: v.key, value: v.value}, cola: {}}; });
+        }
+        else {
+            _items = [];
+            for(var item in exemplars)
+                _items.push({name: item, orig: {key: item, value: exemplars[item]}, cola: {}});
+        }
+        redraw();
+    };
+
+    _legend.dropdown = property(null).react(function(v) {
+        if(!!v !== !!_legend.dropdown() && _legend.parent() && (_svg_renderer || _legend.parent()).svg())
+            window.setTimeout(_legend.redraw, 0);
+    });
+
+    /* enables filtering */
+    _legend.dimension = property(null)
+        .react(function(v) {
+            if(!v) {
+                _included = [];
+                apply_filter();
+            }
+        });
+    _legend.isTagDimension = property(false);
+
+    return _legend;
+};
+
+
+dc_graph.legend.node_legend = function() {
+    return {
+        itemSelector: function() {
+            return '.node';
+        },
+        labelSelector: function() {
+            return '.node-label';
+        },
+        create: function(diagram, selection) {
+            return selection.append('g')
+                .attr('class', 'node');
+        },
+        draw: function(renderer, itemEnter, item) {
+            renderer
+                .renderNode(itemEnter)
+                .redrawNode(item);
+        }
+    };
+};
+
+dc_graph.legend.edge_legend = function() {
+    var _type = {
+        itemSelector: function() {
+            return '.edge-container';
+        },
+        labelSelector: function() {
+            return '.edge-label';
+        },
+        create: function(diagram, selection, w, h) {
+            var edgeEnter = selection.append('g')
+                .attr('class', 'edge-container')
+                .attr('opacity', 0);
+            edgeEnter
+                .append('rect')
+                .attr({
+                    x: -w/2,
+                    y: -h/2,
+                    width: w,
+                    height: h,
+                    fill: 'green',
+                    opacity: 0
+                });
+            edgeEnter
+                .selectAll('circle')
+                .data([-1, 1])
+              .enter()
+                .append('circle')
+                .attr({
+                    r: _type.fakeNodeRadius(),
+                    fill: 'none',
+                    stroke: 'black',
+                    "stroke-dasharray": "4,4",
+                    opacity: 0.15,
+                    transform: function(d) {
+                        return 'translate(' + [d * _type.length() / 2, 0].join(',') + ')';
+                    }
+                });
+            var edgex = _type.length()/2 - _type.fakeNodeRadius();
+            edgeEnter.append('svg:path')
+                .attr({
+                    class: 'edge',
+                    id: function(d) { return d.name; },
+                    d: 'M' + -edgex + ',0 L' + edgex + ',0',
+                    opacity: diagram.edgeOpacity.eval
+                });
+
+            return edgeEnter;
+        },
+        fakeNodeRadius: property(10),
+        length: property(50),
+        draw: function(renderer, itemEnter, item) {
+            renderer.redrawEdge(itemEnter.select('path.edge'), renderer.selectAllEdges('.edge-arrows'));
+        }
+    };
+    return _type;
+};
+
+dc_graph.legend.symbol_legend = function(symbolScale) {
+    return {
+        itemSelector: function() {
+            return '.symbol';
+        },
+        labelSelector: function() {
+            return '.symbol-label';
+        },
+        create: function(diagram, selection, w, h) {
+            var symbolEnter = selection.append('g')
+                .attr('class', 'symbol');
+            return symbolEnter;
+        },
+        draw: function(renderer, symbolEnter, symbol) {
+            symbolEnter.append('text')
+                .html(function(d) {
+                    return symbolScale(d.orig.key);
+                });
+            return symbolEnter;
+        }
+    };
+};
+
+/**
+ * In cola.js there are three factors which influence the positions of nodes:
+ * * *edge length* suggestions, controlled by the
+ * {@link #dc_graph.diagram+lengthStrategy lengthStrategy},
+ * {@link #dc_graph.diagram+baseLength baseLength}, and
+ * {@link #dc_graph.diagram+edgeLength edgeLength} parameters in dc.graph.js
+ * * *automatic constraints* based on the global edge flow direction (`cola.flowLayout`) and overlap
+ * avoidance parameters (`cola.avoidOverlaps`)
+ * * *manual constraints* such as alignment, inequality and equality constraints in a dimension/axis.
+ *
+ * Generally when the
+ * {@link https://github.com/tgdwyer/WebCola/wiki/Constraints cola.js documentation mentions constraints},
+ * it means the manual constraints.
+ *
+ * dc.graph.js allows generation of manual constraints using
+ * {@link #dc_graph.diagram+constrain diagram.constrain} but it can be tedious to write these
+ * functions because it usually means looping over the nodes and edges multiple times to
+ * determine what classes or types of nodes to apply constraints to, and which edges should
+ * take additional constraints.
+ *
+ * This utility creates a constraint generator function from a *pattern*, a graph where:
+ *  1. Nodes represent *types* or classes of layout nodes, annotated with a specification
+ * of how to match the nodes belonging each type.
+ *  2. Edges represent *rules* to generate constraints. There are two kinds of rules:
+ * <ol type='a'>
+ *    <li>To generate additional constraints on edges besides the built-in ones, create a rules
+ * between two different types. The rule will apply to any edges in the layout which match the
+ * source and target types, and generate simple "left/right" constraints. (Note that "left" and
+ * "right" in this context refer to sides of an inequality constraint `left + gap <= right`)
+ *    <li>To generate constraints on a set of nodes, such as alignment, ordering, or circle
+ * constraints, create a rule from a type to itself, a self edge.
+ * </ol>
+ * (It is also conceivable to want constraints between individual nodes which don't
+ * have edges between them. This is not directly supported at this time; right now the workaround
+ * is to create the edge but not draw it, e.g. by setting its {@link #dc_graph.diagram+edgeOpacity}
+ * to zero. If you have a use-case for this, please
+ * {@link https://github.com/dc-js/dc.graph.js/issues/new file an issue}.
+ *
+ * The pattern syntax is an embedded domain specific language designed to be terse without
+ * restricting its power. As such, there are complicated rules for defaulting and inferring
+ * parameters from other parameters. Since most users will want the simplest form, this document
+ * will start from the highest level and then show how to use more complicated forms in order to
+ * gain more control.
+ *
+ * Then we'll build back up from the ground up and show how inference works.
+ * @class constraint_pattern
+ * @memberof dc_graph
+ * @param {dc_graph.diagram} diagram - the diagram to pull attributes from, mostly to determine
+ * the keys of nodes and edge sources and targets
+ * @param {Object} pattern - a graph which defines the constraints to be generated
+ * @return {Function}
+ */
+dc_graph.constraint_pattern = function(pattern) {
+    var types = {}, rules = [];
+
+    pattern.nodes.forEach(function(n) {
+        var id = n.id;
+        var type = types[id] || (types[id] = {});
+        // partitions could be done more efficiently; this is POC
+        if(n.partition) {
+            var partition = n.partition;
+            var value = n.value || n.id;
+            if(n.all || n.typename) {
+                type.match = n.extract ?
+                    function(n2) { return n.extract(n2.value[partition]); } :
+                    function(n2) { return n2.value[partition]; };
+                type.typename = n.typename || function(n2) { return partition + '=' + n2.value[partition]; };
+            }
+            else
+                type.match = function(n2) { return n2.value[partition] === value; };
+        }
+        else if(n.match)
+            type.match = n.match;
+        else throw new Error("couldn't determine matcher for type " + JSON.stringify(n));
+    });
+    pattern.edges.forEach(function(e) {
+        if(e.disable)
+            return;
+        var rule = {source: e.source, target: e.target};
+        rule.produce = typeof e.produce === 'function' ? e.produce : function() {
+            return clone(e.produce);
+        };
+        ['listname', 'wrap', 'reverse'].forEach(function(k) {
+            if(e[k] !== undefined) rule[k] = e[k];
+        });
+        rules.push(rule);
+    });
+
+    return function(diagram, nodes, edges) {
+        var constraints = [];
+        var members = {};
+        nodes.forEach(function(n) {
+            var key = diagram.nodeKey.eval(n);
+            for(var t in types) {
+                var type = types[t], value = type.match(n.orig);
+                if(value) {
+                    var tname = type.typename ? type.typename(t, value) : t;
+                    if(!members[tname])
+                        members[tname] = {
+                            nodes: [], // original ordering
+                            whether: {} // boolean
+                        };
+                    members[tname].nodes.push(key);
+                    members[tname].whether[key] = true;
+                }
+            }
+        });
+        // traversal of rules could be more efficient, again POC
+        var edge_rules = rules.filter(function(r) {
+            return r.source !== r.target;
+        });
+        var type_rules = rules.filter(function(r) {
+            return r.source === r.target;
+        });
+        edges.forEach(function(e) {
+            var source = diagram.edgeSource.eval(e),
+                target = diagram.edgeTarget.eval(e);
+            edge_rules.forEach(function(r) {
+                if(members[r.source] && members[r.source].whether[source] &&
+                   members[r.target] && members[r.target].whether[target]) {
+                    var constraint = r.produce(members, nodes, edges);
+                    if(r.reverse) {
+                        constraint.left = target;
+                        constraint.right = source;
+                    }
+                    else {
+                        constraint.left = source;
+                        constraint.right = target;
+                    }
+                    constraints.push(constraint);
+                }
+            });
+        });
+        type_rules.forEach(function(r) {
+            if(!members[r.source])
+                return;
+            var constraint = r.produce(),
+                listname = r.listname || r.produce.listname || 'nodes',
+                wrap = r.wrap || r.produce.wrap || function(x) { return x; };
+            constraint[listname] = members[r.source].nodes.map(wrap);
+            constraints.push(constraint);
+        });
+        return constraints;
+    };
+};
+
+// constraint generation convenience functions
+dc_graph.gap_y = function(gap, equality) {
+    return {
+        axis: 'y',
+        gap: gap,
+        equality: !!equality
+    };
+};
+dc_graph.gap_x = function(gap, equality) {
+    return {
+        axis: 'x',
+        gap: gap,
+        equality: !!equality
+    };
+};
+
+function align_f(axis) {
+    var ret = function() {
+        return {
+            type: 'alignment',
+            axis: axis
+        };
+    };
+    ret.listname = 'offsets';
+    ret.wrap = function(x) { return {node: x, offset: 0}; };
+    return ret;
+}
+
+dc_graph.align_y = function() {
+    return align_f('y');
+};
+dc_graph.align_x = function() {
+    return align_f('x');
+};
+
+dc_graph.order_x = function(gap, ordering) {
+    return {
+        type: 'ordering',
+        axis: 'x',
+        gap: 60,
+        ordering: ordering
+    };
+};
+dc_graph.order_y = function(gap, ordering) {
+    return {
+        type: 'ordering',
+        axis: 'y',
+        gap: 60,
+        ordering: ordering
+    };
+};
+
+// this naive tree-drawer is paraphrased from memory from dot
+dc_graph.tree_positions = function(rootf, rowf, treef, ofsx, ofsy, nwidth, ygap) {
+    console.warn('dc_graph.tree_positions is deprecated; use the layout engine tree_layout instead');
+    if(rootf || treef) {
+        console.warn('dc_graph.tree_positions: rootf and treef are ignored');
+    }
+    var x;
+    nwidth = d3.functor(nwidth);
+    function best_dist(left, right) {
+        return (nwidth(left) + nwidth(right)) / 2;
+    }
+    var dfs = dc_graph.depth_first_traversal({
+        nodeid: function(n) {
+            return n.cola.dcg_nodeKey;
+        },
+        sourceid: function(n) {
+            return n.cola.dcg_edgeSource;
+        },
+        targetid: function(n) {
+            return n.cola.dcg_edgeTarget;
+        },
+        init: function() {
+            x = ofsx;
+        },
+        row: function(n) {
+            return rowf(n.orig);
+        },
+        place: function(n, r, row) {
+            if(row.length) {
+                var left = row[row.length-1];
+                var g = (nwidth(left) + nwidth(n)) / 2;
+                x = Math.max(x, left.left_x + g);
+            }
+            n.left_x = x;
+            n.hit_ins = 1;
+            n.cola.y = r*ygap + ofsy;
+        },
+        sib: function(isroot, left, right) {
+            var g = best_dist(left, right);
+            if(isroot) g = g*1.5;
+            x += g;
+        },
+        pop: function(n) {
+            n.cola.x = (n.left_x + x)/2;
+        },
+        skip: function(n, indegree) {
+            // rolling average of in-neighbor x positions
+            n.cola.x = (n.hit_ins*n.cola.x + x)/++n.hit_ins;
+            if(n.hit_ins === indegree)
+                delete n.hit_ins;
+        },
+        finish: function(rows) {
+            // this is disgusting. patch up any places where nodes overlap by scanning
+            // right far enough to find the space, then fill from left to right at the
+            // minimum gap
+            rows.forEach(function(row) {
+                var sort = row.sort(function(a, b) { return a.cola.x - b.cola.x; });
+                var badi = null, badl = null, want;
+                for(var i=0; i<sort.length-1; ++i) {
+                    var left = sort[i], right = sort[i+1];
+                    if(!badi) {
+                        if(right.cola.x - left.cola.x < best_dist(left, right)) {
+                            badi = i;
+                            badl = left.cola.x;
+                            want = best_dist(left, right);
+                        } // else still not bad
+                    } else {
+                        want += best_dist(left, right);
+                        if(i < sort.length - 2 && right.cola.x < badl + want)
+                            continue; // still bad
+                        else {
+                            if(badi>0)
+                                --badi; // might want to use more left
+                            var l, limit;
+                            if(i < sort.length - 2) { // found space before right
+                                var extra = right.cola.x - (badl + want);
+                                l = sort[badi].cola.x + extra/2;
+                                limit = i+1;
+                            } else {
+                                l = Math.max(sort[badi].cola.x, badl - best_dist(sort[badi], sort[badi+1]) - (want - right.cola.x + badl)/2);
+                                limit = sort.length;
+                            }
+                            for(var j = badi+1; j<limit; ++j) {
+                                l += best_dist(sort[j-1], sort[j]);
+                                sort[j].cola.x = l;
+                            }
+                            badi = badl = want = null;
+                        }
+                    }
+                }
+            });
+        }
+    });
+
+    return function(diagram, nodes, edges) {
+        return dfs(nodes, edges);
+    };
+};
+
+
+// this naive tree-drawer is paraphrased from memory from dot
+dc_graph.tree_constraints = function(rootf, treef, xgap, ygap) {
+    console.warn('dc_graph.tree_constraints is deprecated - it never worked right and may not be a good idea');
+    return function(diagram, nodes, edges) {
+        var constraints = [];
+        var x = 0;
+        var dfs = dc_graph.depth_first_traversal({
+            root: rootf,
+            tree: treef,
+            place: function(n, r, row) {
+                if(row.length) {
+                    var last = row[row.length-1];
+                    constraints.push({
+                        left: diagram.nodeKey.eval(last),
+                        right: diagram.nodeKey.eval(n),
+                        axis: 'x',
+                        gap: x-last.foo_x,
+                        equality: true
+                    });
+                }
+                n.foo_x = x;
+                // n.cola.x = x;
+                // n.cola.y = r*ygap;
+            },
+            sib: function() {
+                x += xgap;
+            }
+        });
+        dfs(diagram, nodes, edges);
+        return constraints;
+    };
+};
+
+dc_graph.mode = function(event_namespace, options) {
+    var _mode = {};
+    var _eventName = options.laterDraw ? 'transitionsStarted' : 'drawn';
+    var draw = options.draw, remove = options.remove;
+    var supported_renderers = options.renderers || ['svg'];
+
+    if(!draw) {
+        console.warn('behavior.add_behavior has been replaced by mode.draw');
+        draw = options.add_behavior;
+    }
+    if(!remove) {
+        console.warn('behavior.remove_behavior has been replaced by mode.remove');
+        remove = options.remove_behavior;
+    }
+
+    /**
+     #### .parent([object])
+     Assigns this mode to a diagram.
+     **/
+    _mode.parent = property(null)
+        .react(function(p) {
+            var diagram;
+            if(p) {
+                var first = true;
+                diagram = p;
+                p.on(_eventName + '.' + event_namespace, function() {
+                    var args2 = [diagram].concat(Array.prototype.slice.call(arguments));
+                    draw.apply(null, args2);
+                    if(first && options.first) {
+                        options.first.apply(null, args2);
+                        first = false;
+                    }
+                    else if(options.rest)
+                        options.rest.apply(null, args2);
+                });
+                p.on('reset.' + event_namespace, function() {
+                    var rend = diagram.renderer(),
+                        node = rend.selectAllNodes ? rend.selectAllNodes() : null,
+                        edge = rend.selectAllEdges ? rend.selectAllEdges() : null,
+                        edgeHover = rend.selectAllEdges ? rend.selectAllEdges('.edge-hover') : null;
+                    remove(diagram, node, edge, edgeHover);
+                });
+            }
+            else if(_mode.parent()) {
+                diagram = _mode.parent();
+                diagram.on(_eventName + '.' + event_namespace, function(node, edge, ehover) {
+                    remove(diagram, node, edge, ehover);
+                    diagram.on(_eventName + '.' + event_namespace, null);
+                });
+            }
+            options.parent && options.parent(p);
+        });
+
+    _mode.supportsRenderer = function(rendererType) {
+        return supported_renderers.includes(rendererType);
+    };
+
+    return _mode;
+};
+
+dc_graph.behavior = deprecate_function('dc_graph.behavior has been renamed dc_graph.mode', dc_graph.mode);
+
+/**
+ * Asynchronous [d3.tip](https://github.com/Caged/d3-tip) support for dc.graph.js
+ *
+ * Add tooltips to the nodes and edges of a graph using an asynchronous callback to get
+ * the html to show.
+ *
+ * Optional - requires separately loading the d3.tip script and CSS (which are included in
+ * dc.graph.js in `web/js/d3-tip/index.js` and `web/css/d3-tip/example-styles.css`)
+ *
+ * @class tip
+ * @memberof dc_graph
+ * @return {Object}
+ **/
+dc_graph.tip = function(options) {
+    options = options || {};
+    var _namespace = options.namespace || 'tip';
+    var _d3tip = null;
+    var _showTimeout, _hideTimeout;
+    var _dispatch = d3.dispatch('tipped');
+
+    function init(parent) {
+        if(!_d3tip) {
+            _d3tip = d3.tip()
+                .attr('class', options.class || 'd3-tip')
+                .html(function(d) { return "<span>" + d + "</span>"; })
+                .direction(_mode.direction());
+            if(_mode.offset())
+                _d3tip.offset(_mode.offset());
+            parent.svg().call(_d3tip);
+        }
+    }
+    function fetch_and_show_content(d) {
+        if(_mode.disabled() || _mode.selection().exclude && _mode.selection().exclude(d3.event.target)) {
+            hide_tip.call(this);
+            return;
+        }
+        var target = this,
+            next = function() {
+                _mode.content()(d, function(content) {
+                    _d3tip.show.call(target, content, target);
+                    d3.select('div.d3-tip')
+                        .selectAll('a.tip-link')
+                        .on('click.' + _namespace, function() {
+                            d3.event.preventDefault();
+                            if(_mode.linkCallback())
+                                _mode.linkCallback()(this.id);
+                        });
+                    _dispatch.tipped(d);
+                });
+            };
+        if(_hideTimeout)
+            window.clearTimeout(_hideTimeout);
+        if(_mode.delay()) {
+            window.clearTimeout(_showTimeout);
+            _showTimeout = window.setTimeout(next, _mode.delay());
+        }
+        else next();
+    }
+
+    function check_hide_tip() {
+        if(d3.event.relatedTarget &&
+           (!_mode.selection().exclude || !_mode.selection().exclude(d3.event.target)) &&
+           (this && this.contains(d3.event.relatedTarget) || // do not hide when mouse is still over a child
+            _mode.clickable() && d3.event.relatedTarget.classList.contains('d3-tip')))
+            return false;
+        return true;
+    }
+
+    function preempt_tip() {
+        if(_showTimeout) {
+            window.clearTimeout(_showTimeout);
+            _showTimeout = null;
+        }
+    }
+
+    function hide_tip() {
+        if(!check_hide_tip.apply(this))
+            return;
+        preempt_tip();
+        _d3tip.hide();
+    }
+
+    function hide_tip_delay() {
+        if(!check_hide_tip.apply(this))
+            return;
+        preempt_tip();
+        if(_mode.hideDelay())
+            _hideTimeout = window.setTimeout(function () {
+                _d3tip.hide();
+            }, _mode.hideDelay());
+        else
+            _d3tip.hide();
+    }
+
+    function draw(diagram, node, edge, ehover) {
+        init(diagram);
+        _mode.programmatic() || _mode.selection().select(diagram, node, edge, ehover)
+            .on('mouseover.' + _namespace, fetch_and_show_content)
+            .on('mouseout.' + _namespace, hide_tip_delay);
+        if(_mode.clickable()) {
+            d3.select('div.d3-tip')
+                .on('mouseover.' + _namespace, function() {
+                    if(_hideTimeout)
+                        window.clearTimeout(_hideTimeout);
+                })
+                .on('mouseout.' + _namespace, hide_tip_delay);
+        }
+    }
+    function remove(diagram, node, edge, ehover) {
+        _mode.programmatic() || _mode.selection().select(diagram, node, edge, ehover)
+            .on('mouseover.' + _namespace, null)
+            .on('mouseout.' + _namespace, null);
+    }
+
+    var _mode = dc_graph.mode(_namespace, {
+        draw: draw,
+        remove: remove,
+        laterDraw: true
+    });
+    /**
+     * Specify the direction for tooltips. Currently supports the
+     * [cardinal and intercardinal directions](https://en.wikipedia.org/wiki/Points_of_the_compass) supported by
+     * [d3.tip.direction](https://github.com/Caged/d3-tip/blob/master/docs/positioning-tooltips.md#tipdirection):
+     * `'n'`, `'ne'`, `'e'`, etc.
+     * @name direction
+     * @memberof dc_graph.tip
+     * @instance
+     * @param {String} [direction='n']
+     * @return {String}
+     * @return {dc_graph.tip}
+     **/
+    _mode.direction = property('n');
+
+    /**
+     * Specifies the function to generate content for the tooltip. This function has the
+     * signature `function(d, k)`, where `d` is the datum of the thing being hovered over,
+     * and `k` is a continuation. The function should fetch the content, asynchronously if
+     * needed, and then pass html forward to `k`.
+     * @name content
+     * @memberof dc_graph.tip
+     * @instance
+     * @param {Function} [content]
+     * @return {Function}
+     * @example
+     * // Default mode: assume it's a node, show node title
+     * var tip = dc_graph.tip().content(function(n, k) {
+     *     k(_mode.parent() ? _mode.parent().nodeTitle.eval(n) : '');
+     * });
+     **/
+    _mode.content = property(function(n, k) {
+        k(_mode.parent() ? _mode.parent().nodeTitle.eval(n) : '');
+    });
+
+    _mode.on = function(event, f) {
+        return _dispatch.on(event, f);
+    };
+
+    _mode.disabled = property(false);
+    _mode.programmatic = property(false);
+
+    _mode.displayTip = function(filter, n, cb) {
+        if(typeof filter !== 'function') {
+            var d = filter;
+            filter = function(d2) { return d2 === d; };
+        }
+        var found = _mode.selection().select(_mode.parent(), _mode.parent().selectAllNodes(), _mode.parent().selectAllEdges(), null)
+            .filter(filter);
+        if(found.size() > 0) {
+            var action = fetch_and_show_content;
+            // we need to flatten e.g. for ports, which will have nested selections
+            // .nodes() does this better in D3v4
+            var flattened = found.reduce(function(p, v) {
+                return p.concat(v);
+            }, []);
+            var which = (n || 0) % flattened.length;
+            action.call(flattened[which], d3.select(flattened[which]).datum());
+            d = d3.select(flattened[which]).datum();
+            if(cb)
+                cb(d);
+            if(_mode.programmatic())
+                found.on('mouseout.' + _namespace, hide_tip_delay);
+        }
+        return _mode;
+    };
+
+    _mode.hideTip = function(delay) {
+        if(_d3tip) {
+            if(delay)
+                hide_tip_delay();
+            else
+                hide_tip();
+        }
+        return _mode;
+    };
+    _mode.selection = property(dc_graph.tip.select_node_and_edge());
+    _mode.showDelay = _mode.delay = property(0);
+    _mode.hideDelay = property(200);
+    _mode.offset = property(null);
+    _mode.clickable = property(false);
+    _mode.linkCallback = property(null);
+
+    return _mode;
+};
+
+/**
+ * Generates a handler which can be passed to `tip.content` to produce a table of the
+ * attributes and values of the hovered object.
+ *
+ * @name table
+ * @memberof dc_graph.tip
+ * @instance
+ * @return {Function}
+ * @example
+ * // show all the attributes and values in the node and edge objects
+ * var tip = dc_graph.tip();
+ * tip.content(dc_graph.tip.table());
+ **/
+dc_graph.tip.table = function() {
+    var gen = function(d, k) {
+        d = gen.fetch()(d);
+        if(!d)
+            return; // don't display tooltip if no content
+        var data, keys;
+        if(Array.isArray(d))
+            data = d;
+        else if(typeof d === 'number' || typeof d === 'string')
+            data = [d];
+        else { // object
+            data = keys = Object.keys(d).filter(d3.functor(gen.filter()))
+                .filter(function(k) {
+                    return d[k] !== undefined;
+                });
+        }
+        var table = d3.select(document.createElement('table'));
+        var rows = table.selectAll('tr').data(data);
+        var rowsEnter = rows.enter().append('tr');
+        rowsEnter.append('td').text(function(item) {
+            if(keys && typeof item === 'string')
+                return item;
+            return JSON.stringify(item);
+        });
+        if(keys)
+            rowsEnter.append('td').text(function(item) {
+                return JSON.stringify(d[item]);
+            });
+        k(table.node().outerHTML); // optimizing for clarity over speed (?)
+    };
+    gen.filter = property(true);
+    gen.fetch = property(function(d) {
+        return d.orig.value;
+    });
+    return gen;
+};
+
+dc_graph.tip.json_table = function() {
+    var table = dc_graph.tip.table().fetch(function(d) {
+        var jsontip = table.json()(d);
+        if(!jsontip) return null;
+        try {
+            return JSON.parse(jsontip);
+        } catch(xep) {
+            return [jsontip];
+        }
+    });
+    table.json = property(function(d) {
+        return (d.orig.value.value || d.orig.value).jsontip;
+    });
+    return table;
+};
+
+dc_graph.tip.html_or_json_table = function() {
+    var json_table = dc_graph.tip.json_table();
+    var gen = function(d, k) {
+        var html = gen.html()(d);
+        if(html)
+            k(html);
+        else
+            json_table(d, k);
+    };
+    gen.json = json_table.json;
+    gen.html = property(function(d) {
+        return (d.orig.value.value || d.orig.value).htmltip;
+    });
+    return gen;
+};
+
+dc_graph.tip.select_node_and_edge = function() {
+    return {
+        select: function(diagram, node, edge, ehover) {
+            // hack to merge selections, not supported d3v3
+            var selection = diagram.selectAll('.foo-this-does-not-exist');
+            selection[0] = node[0].concat(ehover ? ehover[0] : []);
+            return selection;
+        },
+        exclude: function(element) {
+            return ancestor_has_class(element, 'port');
+        }
+    };
+};
+
+dc_graph.tip.select_node = function() {
+    return {
+        select: function(diagram, node, edge, ehover) {
+            return node;
+        },
+        exclude: function(element) {
+            return ancestor_has_class(element, 'port');
+        }
+    };
+};
+
+dc_graph.tip.select_edge = function() {
+    return {
+        select: function(diagram, node, edge, ehover) {
+            return edge;
+        }
+    };
+};
+
+dc_graph.tip.select_port = function() {
+    return {
+        select: function(diagram, node, edge, ehover) {
+            return node.selectAll('g.port');
+        }
+    };
+};
+
+dc_graph.dropdown = function() {
+    dc_graph.dropdown.unique_id = (dc_graph.dropdown.unique_id || 16) + 1;
+    var _dropdown = {
+        id: 'id' + dc_graph.dropdown.unique_id,
+        parent: property(null),
+        show: function(key, x, y) {
+            var dropdown = _dropdown.parent().root()
+                .selectAll('div.dropdown.' + _dropdown.id).data([0]);
+            var dropdownEnter = dropdown
+                .enter().append('div')
+                .attr('class', 'dropdown ' + _dropdown.id);
+            dropdown
+                .style('visibility', 'visible')
+                .style('left', x + 'px')
+                .style('top', y + 'px');
+            var capture;
+            var hides = _dropdown.hideOn().split('|');
+            var selects = _dropdown.selectOn().split('|');
+            if(hides.includes('leave'))
+                dropdown.on('mouseleave', function() {
+                    dropdown.style('visibility', 'hidden');
+                });
+            else if(hides.includes('clickout')) {
+                var diagram = _dropdown.parent();
+                capture = diagram.svg().append('rect')
+                    .attr('x', 0)
+                    .attr('y', 0)
+                    .attr('width', diagram.width())
+                    .attr('height', diagram.height())
+                    .attr('opacity', 0)
+                    .on('click', function() {
+                        capture.remove();
+                        dropdown.style('visibility', 'hidden');
+                    });
+            }
+            var container = dropdown;
+            if(_dropdown.scrollHeight()) {
+                var height = _dropdown.scrollHeight();
+                if(typeof height === 'number')
+                    height = height + 'px';
+                dropdown
+                    .style('max-height', height)
+                    .property('scrollTop', 0);
+                dropdownEnter
+                    .style('overflow-y', 'auto')
+                  .append('div')
+                    .attr('class', 'scroller');
+                container = dropdown.selectAll('div.scroller');
+            }
+            var values = _dropdown.fetchValues()(key, function(values) {
+                var items = container
+                    .selectAll('div.dropdown-item').data(values);
+                items
+                    .enter().append('div')
+                    .attr('class', 'dropdown-item');
+                items.exit().remove();
+                var select_event = null;
+                if(selects.includes('click'))
+                    select_event = 'click';
+                else if(selects.includes('hover'))
+                    select_event = 'mouseenter';
+                items
+                    .text(function(item) { return _dropdown.itemText()(item); });
+                if(select_event) {
+                    items
+                        .on(select_event + '.select', function(d) {
+                            _dropdown.itemSelected()(d);
+                        });
+                }
+                if(hides.includes('clickitem')) {
+                    items
+                        .on('click.hide', function(d) {
+                            capture.remove();
+                            dropdown.style('visibility', 'hidden');
+                        });
+                }
+            });
+        },
+        hideOn: property('clickout|clickitem'),
+        selectOn: property('click'),
+        height: property(10),
+        itemText: property(function(x) { return x; }),
+        itemSelected: property(function() {}),
+        fetchValues: property(function(key, k) { k([]); }),
+        scrollHeight: property('12em')
+    };
+    return _dropdown;
+};
+
+dc_graph.keyboard = function() {
+    var _input_anchor, _dispatch = d3.dispatch('keydown', 'keyup');
+
+    function keydown() {
+        _dispatch.keydown();
+    }
+    function keyup() {
+        _dispatch.keyup();
+    }
+    function draw(diagram) {
+        _input_anchor = diagram.svg().selectAll('a#dcgraph-keyboard').data([1]);
+        _input_anchor.enter()
+            .insert('a', ':first-child').attr({
+                id: 'dcgraph-keyboard',
+                href: '#'
+            });
+        _input_anchor.on('keydown.keyboard', keydown);
+        _input_anchor.on('keyup.keyboard', keyup);
+
+        // grab focus whenever svg is interacted with (?)
+        diagram.svg().on('mouseup.keyboard', function() {
+            _mode.focus();
+        });
+    }
+    function remove(diagram) {
+        _input_anchor.remove();
+    }
+    var _mode = dc_graph.mode('brush', {
+        draw: draw,
+        remove: remove
+    });
+
+    _mode.on = function(event, f) {
+        if(arguments.length === 1)
+            return _dispatch.on(event);
+        _dispatch.on(event, f);
+        return this;
+    };
+
+    _mode.focus = function() {
+        if(!_mode.disableFocus()) {
+            _input_anchor.node().focus && _input_anchor.node().focus();
+        }
+    };
+
+    _mode.disableFocus = property(false);
+
+    return _mode;
+};
+
+// adapted from
+// http://stackoverflow.com/questions/9308938/inline-text-editing-in-svg/#26644652
+
+dc_graph.edit_text = function(parent, options) {
+    var foreign = parent.append('foreignObject').attr({
+        height: '100%',
+        width: '100%' // don't wrap
+    });
+    var padding = options.padding !== undefined ? options.padding : 2;
+    function reposition() {
+        var pos;
+        switch(options.align) {
+        case 'left':
+            pos = [options.box.x-padding, options.box.y-padding];
+            break;
+        default:
+        case 'center':
+            pos = [
+                options.box.x + (options.box.width - textdiv.node().offsetWidth)/2,
+                options.box.y + (options.box.height - textdiv.node().offsetHeight)/2
+            ];
+            break;
+        }
+        foreign.attr('transform', 'translate(' + pos.join(' ') + ')');
+    }
+    var textdiv = foreign.append('xhtml:div');
+    var text = options.text || "type on me";
+    textdiv.text(text).attr({
+        contenteditable: true,
+        width: 'auto',
+        class: options.class || null
+    }).style({
+        display: 'inline-block',
+        'background-color': 'white',
+        padding: padding + 'px'
+    });
+
+    function stopProp() {
+        d3.event.stopPropagation();
+    }
+    foreign
+        .on('mousedown.edit-text', stopProp)
+        .on('mousemove.edit-text', stopProp)
+        .on('mouseup.edit-text', stopProp)
+        .on('dblclick.edit-text', stopProp);
+
+    function accept() {
+        options.accept && options.accept(textdiv.text());
+        textdiv.on('blur.edit-text', null);
+        foreign.remove();
+        options.finally && options.finally();
+    }
+    function cancel() {
+        options.cancel && options.cancel();
+        textdiv.on('blur.edit-text', null);
+        foreign.remove();
+        options.finally && options.finally();
+    }
+
+    textdiv.on('keydown.edit-text', function() {
+        if(d3.event.keyCode===13) {
+            d3.event.preventDefault();
+        }
+    }).on('keyup.edit-text', function() {
+        if(d3.event.keyCode===13) {
+            accept();
+        } else if(d3.event.keyCode===27) {
+            cancel();
+        }
+        reposition();
+    }).on('blur.edit-text', cancel);
+    reposition();
+    textdiv.node().focus();
+
+    var range = document.createRange();
+    if(options.selectText) {
+        range.selectNodeContents(textdiv.node());
+    } else {
+        range.setStart(textdiv.node(), 1);
+        range.setEnd(textdiv.node(), 1);
+    }
+    var sel = window.getSelection();
+    sel.removeAllRanges();
+    sel.addRange(range);
+};
+
+/**
+ * `dc_graph.brush` is a {@link dc_graph.mode mode} providing a simple wrapper over
+ * [d3.svg.brush](https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Controls.md#brush)
+ * @class brush
+ * @memberof dc_graph
+ * @return {dc_graph.brush}
+ **/
+dc_graph.brush = function() {
+    var _brush = null, _gBrush, _dispatch = d3.dispatch('brushstart', 'brushmove', 'brushend');
+
+    function brushstart() {
+        _dispatch.brushstart();
+    }
+    function brushmove() {
+        var ext = _brush.extent();
+        _dispatch.brushmove(ext);
+    }
+    function brushend() {
+        _dispatch.brushend();
+        _gBrush.call(_brush.clear());
+    }
+    function install_brush(diagram) {
+        if(!_brush) {
+            _brush = d3.svg.brush()
+                .x(diagram.x()).y(diagram.y())
+                .on('brushstart.brush-mode', brushstart)
+                .on('brush.brush-mode', brushmove)
+                .on('brushend.brush-mode', brushend);
+        }
+        if(!_gBrush) {
+            _gBrush = diagram.svg().insert('g', ':first-child')
+                .attr('class', 'brush')
+                .call(_brush);
+        }
+    }
+    function remove_brush() {
+        if(_gBrush) {
+            _gBrush.remove();
+            _gBrush = null;
+        }
+    }
+    var _mode = dc_graph.mode('brush', {
+        draw: function() {},
+        remove: remove_brush
+    });
+
+    /**
+     * Subscribe to a brush event, currently `brushstart`, `brushmove`, or `brushend`
+     * @method on
+     * @memberof dc_graph.brush
+     * @instance
+     * @param {String} event the name of the event; please namespace with `'namespace.event'`
+     * @param {Function} [f] the handler function; if omitted, returns the current handler
+     * @return {dc_graph.brush}
+     * @return {Function}
+     **/
+    _mode.on = function(event, f) {
+        if(arguments.length === 1)
+            return _dispatch.on(event);
+        _dispatch.on(event, f);
+        return this;
+    };
+    /**
+     * Add the brush to the parent diagram's SVG
+     * @method activate
+     * @memberof dc_graph.brush
+     * @instance
+     * @return {dc_graph.brush}
+     **/
+    _mode.activate = function() {
+        install_brush(_mode.parent());
+        return this;
+    };
+    /**
+     * Remove the brush from the parent diagram's SVG
+     * @method deactivate
+     * @memberof dc_graph.brush
+     * @instance
+     * @return {dc_graph.brush}
+     **/
+    _mode.deactivate = function() {
+        remove_brush();
+        return this;
+    };
+    /**
+     * Retrieve whether the brush is currently active
+     * @method isActive
+     * @memberof dc_graph.brush
+     * @instance
+     * @return {Boolean}
+     **/
+    _mode.isActive = function () {
+        return !!_gBrush;
+    };
+
+    return _mode;
+};
+
+dc_graph.select_things = function(things_group, things_name, thinginess) {
+    var _selected = [], _oldSelected;
+    var _mousedownThing = null;
+
+    var contains_predicate = thinginess.keysEqual ?
+            function(k1) {
+                return function(k2) {
+                    return thinginess.keysEqual(k1, k2);
+                };
+            } :
+        function(k1) {
+            return function(k2) {
+                return k1 === k2;
+            };
+        };
+    function contains(array, key) {
+        return !!_selected.find(contains_predicate(key));
+    }
+    function isUnion(event) {
+        return event.shiftKey;
+    }
+    function isToggle(event) {
+        return is_a_mac ? event.metaKey : event.ctrlKey;
+    }
+    function add_array(array, key) {
+        return contains(array, key) ? array : array.concat([key]);
+    }
+    function toggle_array(array, key) {
+        return contains(array, key) ? array.filter(function(x) { return x != key; }) : array.concat([key]);
+    }
+
+    function selection_changed(diagram) {
+        return function(selection, refresh) {
+            if(refresh === undefined)
+                refresh = true;
+            _selected = selection;
+            if(refresh)
+                diagram.requestRefresh();
+        };
+    }
+    var _have_bce = false;
+    function background_click_event(diagram, v) {
+        // we seem to have nodes-background interrupting edges-background by reinstalling uselessly
+        if(_have_bce === v)
+            return;
+        diagram.svg().on('click.' + things_name, v ? function(t) {
+            if(d3.event.target === this)
+                things_group.set_changed([]);
+        } : null);
+        _have_bce = v;
+    }
+    function brushstart() {
+        if(isUnion(d3.event.sourceEvent) || isToggle(d3.event.sourceEvent))
+            _oldSelected = _selected.slice();
+        else {
+            _oldSelected = [];
+            things_group.set_changed([]);
+        }
+    }
+    function brushmove(ext) {
+        if(!thinginess.intersectRect)
+            return;
+        var rectSelect = thinginess.intersectRect(ext);
+        var newSelected;
+        if(isUnion(d3.event.sourceEvent))
+            newSelected = rectSelect.reduce(add_array, _oldSelected);
+        else if(isToggle(d3.event.sourceEvent))
+            newSelected = rectSelect.reduce(toggle_array, _oldSelected);
+        else
+            newSelected = rectSelect;
+        things_group.set_changed(newSelected);
+    }
+
+    function draw(diagram, node, edge) {
+        var condition = _mode.noneIsAll() ? function(t) {
+            return !_selected.length || contains(_selected, thinginess.key(t));
+        } : function(t) {
+            return contains(_selected, thinginess.key(t));
+        };
+        thinginess.applyStyles(condition);
+
+        thinginess.clickables(diagram, node, edge).on('mousedown.' + things_name, function(t) {
+            _mousedownThing = t;
+        });
+
+        thinginess.clickables(diagram, node, edge).on('mouseup.' + things_name, function(t) {
+            if(thinginess.excludeClick && thinginess.excludeClick(d3.event.target))
+                return;
+            // it's only a click if the same target was mousedown & mouseup
+            // but we can't use click event because things may have been reordered
+            if(_mousedownThing !== t)
+                return;
+            var key = thinginess.key(t), newSelected;
+            if(_mode.multipleSelect()) {
+                if(isUnion(d3.event))
+                    newSelected = add_array(_selected, key);
+                else if(isToggle(d3.event))
+                    newSelected = toggle_array(_selected, key);
+            }
+            if(!newSelected)
+                newSelected = [key];
+            things_group.set_changed(newSelected);
+        });
+
+        if(_mode.multipleSelect()) {
+            var brush_mode = diagram.child('brush');
+            brush_mode.activate();
+        }
+        else
+            background_click_event(diagram, _mode.clickBackgroundClears());
+
+        if(_mode.autoCropSelection()) {
+            // drop any selected which no longer exist in the diagram
+            var present = thinginess.clickables(diagram, node, edge).data().map(thinginess.key);
+            var now_selected = _selected.filter(function(k) { return contains(present, k); });
+            if(_selected.length !== now_selected.length)
+                things_group.set_changed(now_selected, false);
+        }
+    }
+
+    function remove(diagram, node, edge) {
+        thinginess.clickables(diagram, node, edge).on('click.' + things_name, null);
+        diagram.svg().on('click.' + things_name, null);
+        thinginess.removeStyles();
+    }
+
+    var _mode = dc_graph.mode(things_name, {
+        draw: draw,
+        remove: remove,
+        parent: function(p) {
+            things_group.on('set_changed.' + things_name, p ? selection_changed(p) : null);
+            if(p && _mode.multipleSelect()) {
+                var brush_mode = p.child('brush');
+                if(!brush_mode) {
+                    brush_mode = dc_graph.brush();
+                    p.child('brush', brush_mode);
+                }
+                brush_mode
+                    .on('brushstart.' + things_name, brushstart)
+                    .on('brushmove.' + things_name, brushmove);
+            }
+        },
+        laterDraw: thinginess.laterDraw || false
+    });
+
+    _mode.multipleSelect = property(true);
+    _mode.clickBackgroundClears = property(true, false).react(function(v) {
+        if(!_mode.multipleSelect() && _mode.parent())
+            background_click_event(_mode.parent(), v);
+    });
+    _mode.noneIsAll = property(false);
+    // if you're replacing the data, you probably want the selection not to be preserved when a thing
+    // with the same key re-appears later (true). however, if you're filtering dc.js-style, you
+    // probably want filters to be independent between diagrams (false)
+    _mode.autoCropSelection = property(true);
+    // if you want to do the cool things select_things can do
+    _mode.thinginess = function() {
+        return thinginess;
+    };
+    return _mode;
+};
+
+dc_graph.select_things_group = function(brushgroup, type) {
+    window.chart_registry.create_type(type, function() {
+        return d3.dispatch('set_changed');
+    });
+
+    return window.chart_registry.create_group(type, brushgroup);
+};
+
+dc_graph.select_nodes = function(props, options) {
+    options = options || {};
+    var select_nodes_group = dc_graph.select_things_group(options.select_nodes_group || 'select-nodes-group', 'select-nodes');
+
+    var thinginess = {
+        intersectRect: function(ext) {
+            return _mode.parent().selectAllNodes().data().filter(function(n) {
+                return n && ext[0][0] < n.cola.x && n.cola.x < ext[1][0] &&
+                    ext[0][1] < n.cola.y && n.cola.y < ext[1][1];
+            }).map(this.key);
+        },
+        clickables: function(diagram, node, edge) {
+            return node;
+        },
+        excludeClick: function(element) {
+            return ancestor_has_class(element, 'port');
+        },
+        key: function(n) {
+            return _mode.parent().nodeKey.eval(n);
+        },
+        applyStyles: function(pred) {
+            _mode.parent().cascade(50, true, node_edge_conditions(pred, null, props));
+        },
+        removeStyles: function() {
+            _mode.parent().cascade(50, false, props);
+        }
+    };
+    var _mode = dc_graph.select_things(select_nodes_group, 'select-nodes', thinginess);
+    return _mode;
+};
+
+dc_graph.select_edges = function(props, options) {
+    options = options || {};
+    var select_edges_group = dc_graph.select_things_group(options.select_edges_group || 'select-edges-group', 'select-edges');
+    var thinginess = {
+        intersectRect: function(ext) {
+            return this.clickables().data().filter(function(e) {
+                // this nonsense because another select_things may have invalidated the edge positions (!!)
+                var sp = {
+                    x: e.source.cola.x + e.sourcePort.pos.x,
+                    y: e.source.cola.y + e.sourcePort.pos.y
+                },
+                    tp = {
+                        x: e.target.cola.x + e.targetPort.pos.x,
+                        y: e.target.cola.y + e.targetPort.pos.y
+                    };
+                return [sp, tp].some(function(p) {
+                    return ext[0][0] < p.x && p.x < ext[1][0] &&
+                        ext[0][1] < p.y && p.y < ext[1][1];
+                });
+            }).map(this.key);
+        },
+        clickables: function() {
+            return _mode.parent().selectAllEdges('.edge-hover');
+        },
+        key: function(e) {
+            return _mode.parent().edgeKey.eval(e);
+        },
+        applyStyles: function(pred) {
+            _mode.parent().cascade(50, true, node_edge_conditions(null, pred, props));
+        },
+        removeStyles: function() {
+            _mode.parent().cascade(50, false, props);
+        }
+    };
+    var _mode = dc_graph.select_things(select_edges_group, 'select-edges', thinginess);
+    return _mode;
+};
+
+dc_graph.select_ports = function(props, options) {
+    options = options || {};
+    var port_style = options.portStyle || 'symbols';
+    var select_ports_group = dc_graph.select_things_group(options.select_ports_group || 'select-ports-group', 'select-ports');
+    var thinginess = {
+        laterDraw: true,
+        intersectRect: null, // multiple selection not supported for now
+        clickables: function() {
+            return _mode.parent().selectAllNodes('g.port');
+        },
+        key: function(p) {
+            // this scheme also won't work with multiselect
+            return p.named ?
+                {node: _mode.parent().nodeKey.eval(p.node), name: p.name} :
+            {edge: _mode.parent().edgeKey.eval(p.edges[0]), name: p.name};
+        },
+        applyStyles: function(pred) {
+            _mode.parent().portStyle(port_style).cascade(50, true, conditional_properties(pred, props));
+        },
+        removeStyles: function() {
+            _mode.parent().portStyle(port_style).cascade(50, false, props);
+        },
+        keysEqual: function(k1, k2) {
+            return k1.name === k2.name && (k1.node ? k1.node === k2.node : k1.edge === k2.edge);
+        }
+    };
+    var _mode = dc_graph.select_things(select_ports_group, 'select-ports', thinginess);
+    return _mode;
+};
+
+dc_graph.move_nodes = function(options) {
+    options = options || {};
+    var select_nodes_group = dc_graph.select_things_group(options.select_nodes_group || 'select-nodes-group', 'select-nodes');
+    var fix_nodes_group = dc_graph.fix_nodes_group('fix-nodes-group');
+    var _selected = [], _startPos = null, _downNode, _moveStarted;
+    var _brush, _drawGraphs, _selectNodes, _restoreBackgroundClick;
+    var _maybeSelect = null;
+
+    function isUnion(event) {
+        return event.shiftKey;
+    }
+    function isToggle(event) {
+        return is_a_mac ? event.metaKey : event.ctrlKey;
+    }
+
+    function selection_changed(diagram) {
+        return function(selection, refresh) {
+            if(refresh === undefined)
+                refresh = true;
+            _selected = selection;
+        };
+    }
+    function for_each_selected(f, selected) {
+        selected = selected || _selected;
+        selected.forEach(function(key) {
+            var n = _mode.parent().getWholeNode(key);
+            f(n, key);
+        });
+    }
+    function draw(diagram, node, edge) {
+        node.on('mousedown.move-nodes', function(n) {
+            // Need a more general way for modes to say "I got this"
+            if(_drawGraphs && _drawGraphs.usePorts() && _drawGraphs.usePorts().eventPort())
+                return;
+            _startPos = dc_graph.event_coords(diagram);
+            _downNode = d3.select(this);
+            // if the node under the mouse is not in the selection, need to
+            // make that node selected
+            var key = diagram.nodeKey.eval(n);
+            var selected = _selected;
+            if(_selected.indexOf(key)<0) {
+                selected = [key];
+                _maybeSelect = key;
+            }
+            else _maybeSelect = null;
+            for_each_selected(function(n) {
+                n.original_position = [n.cola.x, n.cola.y];
+            }, selected);
+            if(_brush)
+                _brush.deactivate();
+        });
+        function mouse_move() {
+            if(_startPos) {
+                if(!(d3.event.buttons & 1)) {
+                    mouse_up();
+                    return;
+                }
+                if(_maybeSelect)
+                    select_nodes_group.set_changed([_maybeSelect]);
+                var pos = dc_graph.event_coords(diagram);
+                var dx = pos[0] - _startPos[0],
+                    dy = pos[1] - _startPos[1];
+                if(!_moveStarted && Math.hypot(dx, dy) > _mode.dragSize()) {
+                    _moveStarted = true;
+                    // prevent click event for this node setting selection just to this
+                    if(_downNode)
+                        _downNode.style('pointer-events', 'none');
+                }
+                if(_moveStarted) {
+                    for_each_selected(function(n) {
+                        n.cola.x = n.original_position[0] + dx;
+                        n.cola.y = n.original_position[1] + dy;
+                    });
+                    diagram.reposition(node, edge);
+                }
+            }
+        }
+        function mouse_up() {
+            if(_startPos) {
+                if(_moveStarted) {
+                    _moveStarted = false;
+                    if(_downNode) {
+                        _downNode.style('pointer-events', null);
+                        _downNode = null;
+                    }
+                    var fixes = [];
+                    for_each_selected(function(n, id) {
+                        fixes.push({
+                            id: id,
+                            pos: {x: n.cola.x, y: n.cola.y}
+                        });
+                    });
+                    fix_nodes_group.request_fixes(fixes);
+                }
+                if(_brush)
+                    _brush.activate();
+                _startPos = null;
+            }
+        }
+        node
+            .on('mousemove.move-nodes', mouse_move)
+            .on('mouseup.move-nodes', mouse_up);
+        diagram.svg()
+            .on('mousemove.move-nodes', mouse_move)
+            .on('mouseup.move-nodes', mouse_up);
+    }
+
+    function remove(diagram, node, edge) {
+        node.on('mousedown.move-nodes', null);
+        node.on('mousemove.move-nodes', null);
+        node.on('mouseup.move-nodes', null);
+    }
+
+    var _mode = dc_graph.mode('move-nodes', {
+        draw: draw,
+        remove: remove,
+        parent: function(p) {
+            select_nodes_group.on('set_changed.move-nodes', p ? selection_changed(p) : null);
+            if(p) {
+                _brush = p.child('brush');
+                _drawGraphs = p.child('draw-graphs');
+                _selectNodes = p.child('select-nodes');
+            }
+            else _brush = _drawGraphs = _selectNodes = null;
+        }
+    });
+
+    // minimum distance that is considered a drag, not a click
+    _mode.dragSize = property(5);
+
+    return _mode;
+};
+
+dc_graph.fix_nodes = function(options) {
+    options = options || {};
+    var fix_nodes_group = dc_graph.fix_nodes_group('fix-nodes-group');
+    var _fixedPosTag = options.fixedPosTag || 'fixedPos';
+    var _fixes = [], _nodes, _wnodes, _edges, _wedges;
+
+    var _execute = {
+        nodeid: function(n) {
+            return _mode.parent().nodeKey.eval(n);
+        },
+        sourceid: function(e) {
+            return _mode.parent().edgeSource.eval(e);
+        },
+        targetid: function(e) {
+            return _mode.parent().edgeTarget.eval(e);
+        },
+        get_fix: function(n) {
+            return _mode.parent().nodeFixed.eval(n);
+        },
+        fix_node: function(n, pos) {
+            n[_fixedPosTag] = pos;
+        },
+        unfix_node: function(n) {
+            n[_fixedPosTag] = null;
+        },
+        clear_fixes: function() {
+            _fixes = {};
+        },
+        register_fix: function(id, pos) {
+            _fixes[id] = pos;
+        }
+    };
+
+    function request_fixes(fixes) {
+        _mode.strategy().request_fixes(_execute, fixes);
+        tell_then_set(find_changes()).then(function() {
+            _mode.parent().redraw();
+        });
+    }
+    function new_node(nid, n, pos) {
+        _mode.strategy().new_node(_execute, nid, n, pos);
+    }
+    function new_edge(eid, sourceid, targetid) {
+        var source = _nodes[sourceid], target = _nodes[targetid];
+        _mode.strategy().new_edge(_execute, eid, source, target);
+    }
+    function find_changes() {
+        var changes = [];
+        _wnodes.forEach(function(n) {
+            var key = _mode.parent().nodeKey.eval(n),
+                fixPos = _fixes[key],
+                oldFixed = n.orig.value[_fixedPosTag],
+                changed = false;
+            if(oldFixed) {
+                if(!fixPos || fixPos.x !== oldFixed.x || fixPos.y !== oldFixed.y)
+                    changed = true;
+            }
+            else changed = fixPos;
+            if(changed)
+                changes.push({n: n, fixed: fixPos ? {x: fixPos.x, y: fixPos.y} : null});
+        });
+        return changes;
+    }
+    function execute_change(n, fixed) {
+        if(fixed)
+            _execute.fix_node(n.orig.value, fixed);
+        else
+            _execute.unfix_node(n.orig.value);
+    }
+    function tell_then_set(changes) {
+        var callback = _mode.fixNode() || function(n, pos) { return Promise.resolve(pos); };
+        var promises = changes.map(function(change) {
+            var key = _mode.parent().nodeKey.eval(change.n);
+            return callback(key, change.fixed)
+                .then(function(fixed) {
+                    execute_change(change.n, fixed);
+                });
+        });
+        return Promise.all(promises);
+    }
+    function set_changes(changes) {
+        changes.forEach(function(change) {
+            execute_change(change.n, change.fixed);
+        });
+    }
+    function tell_changes(changes) {
+        var callback = _mode.fixNode() || function(n, pos) { return Promise.resolve(pos); };
+        var promises = changes.map(function(change) {
+            var key = _mode.parent().nodeKey.eval(change.n);
+            return callback(key, change.fixed);
+        });
+        return Promise.all(promises);
+    }
+    function fix_all_nodes(tell) {
+        if(tell === undefined)
+           tell = true;
+        var changes = _wnodes.map(function(n) {
+            return {n: n, fixed: {x: n.cola.x, y: n.cola.y}};
+        });
+        if(tell)
+            return tell_then_set(changes);
+        else {
+            set_changes(changes);
+            return Promise.resolve(undefined);
+        }
+    }
+    function clear_fixes() {
+        _mode.strategy().clear_all_fixes && _mode.strategy().clear_all_fixes();
+        _execute.clear_fixes();
+    }
+    function on_data(diagram, nodes, wnodes, edges, wedges, ports, wports) {
+        _nodes = nodes;
+        _wnodes = wnodes;
+        _edges = edges;
+        _wedges = wedges;
+        if(_mode.strategy().on_data) {
+            _mode.strategy().on_data(_execute, nodes, wnodes, edges, wedges, ports, wports); // ghastly
+            var changes = find_changes();
+            set_changes(changes);
+            // can't wait for backend to acknowledge/approve so just set then blast
+            if(_mode.reportOverridesAsynchronously())
+                tell_changes(changes); // dangling promise
+        }
+    }
+
+    var _mode = {
+        parent: property(null).react(function(p) {
+            fix_nodes_group
+                .on('request_fixes.fix-nodes', p ? request_fixes : null)
+                .on('new_node.fix_nodes', p ? new_node : null)
+                .on('new_edge.fix_nodes', p ? new_edge : null);
+            if(p) {
+                p.on('data.fix-nodes', on_data);
+            } else if(_mode.parent())
+                _mode.parent().on('data.fix-nodes', null);
+        }),
+        // callback for setting & fixing node position
+        fixNode: property(null),
+        // save/load may want to nail everything / start from scratch
+        // (should probably be automatic though)
+        fixAllNodes: fix_all_nodes,
+        clearFixes: clear_fixes,
+        strategy: property(dc_graph.fix_nodes.strategy.fix_last()),
+        reportOverridesAsynchronously: property(true)
+    };
+
+    return _mode;
+};
+
+dc_graph.fix_nodes.strategy = {};
+dc_graph.fix_nodes.strategy.fix_last = function() {
+    return {
+        request_fixes: function(exec, fixes) {
+            exec.clear_fixes();
+            fixes.forEach(function(fix) {
+                exec.register_fix(fix.id, fix.pos);
+            });
+        },
+        new_node: function(exec, nid, n, pos) {
+            exec.fix_node(n, pos);
+        },
+        new_edge: function(exec, eid, source, target) {
+            exec.unfix_node(source.orig.value);
+            exec.unfix_node(target.orig.value);
+        }
+    };
+};
+dc_graph.fix_nodes.strategy.last_N_per_component = function(maxf) {
+    maxf = maxf || 1;
+    var _age = 0;
+    var _allFixes = {};
+    return {
+        clear_all_fixes: function() {
+            _allFixes = {};
+        },
+        request_fixes: function(exec, fixes) {
+            ++_age;
+            fixes.forEach(function(fix) {
+                _allFixes[fix.id] = {id: fix.id, age: _age, pos: fix.pos};
+            });
+        },
+        new_node: function(exec, nid, n, pos) {
+            ++_age;
+            _allFixes[nid] = {id: nid, age: _age, pos: pos};
+            exec.fix_node(n, pos);
+        },
+        new_edge: function() {},
+        on_data: function(exec, nodes, wnodes, edges, wedges, ports, wports) {
+            ++_age;
+            // add any existing fixes as requests
+            wnodes.forEach(function(n) {
+                var nid = exec.nodeid(n), pos = exec.get_fix(n);
+                if(pos && !_allFixes[nid])
+                    _allFixes[nid] = {id: nid, age: _age, pos: pos};
+            });
+            // determine components
+            var components = [];
+            var dfs = dc_graph.undirected_dfs({
+                nodeid: exec.nodeid,
+                sourceid: exec.sourceid,
+                targetid: exec.targetid,
+                comp: function() {
+                    components.push([]);
+                },
+                node: function(compid, n) {
+                    components[compid].push(n);
+                }
+            });
+            dfs(wnodes, wedges);
+            // start from scratch
+            exec.clear_fixes();
+            // keep or produce enough fixed nodes per component
+            components.forEach(function(comp, i) {
+                var oldcomps = comp.reduce(function(cc, n) {
+                    if(n.last_component) {
+                        var counts = cc[n.last_component] = cc[n.last_component] || {
+                            total: 0,
+                            fixed: 0
+                        };
+                        counts.total++;
+                        if(_allFixes[exec.nodeid(n)])
+                            counts.fixed++;
+                    }
+                    return cc;
+                }, {});
+                var fixed_by_size = Object.keys(oldcomps).reduce(function(ff, compid) {
+                    if(oldcomps[compid].fixed)
+                        ff.push({compid: +compid, total: oldcomps[compid].total, fixed: oldcomps[compid].fixed});
+                    return ff;
+                }, []).sort(function(coa, cob) {
+                    return cob.total - coa.total;
+                });
+                var largest_fixed = fixed_by_size.length && fixed_by_size[0].compid;
+                var fixes = comp.filter(function(n) {
+                    return !n.last_component || n.last_component === largest_fixed;
+                }).map(function(n) {
+                    return _allFixes[exec.nodeid(n)];
+                }).filter(function(fix) {
+                    return fix;
+                });
+                if(fixes.length > maxf) {
+                    fixes.sort(function(f1, f2) {
+                        return f2.age - f1.age;
+                    });
+                    fixes = fixes.slice(0, maxf);
+                }
+                fixes.forEach(function(fix) {
+                    exec.register_fix(fix.id, fix.pos);
+                });
+                var kept = fixes.reduce(function(m, fix) {
+                    m[fix.id] = true;
+                    return m;
+                }, {});
+                comp.forEach(function(n) {
+                    var nid = exec.nodeid(n);
+                    if(!kept[nid])
+                        _allFixes[nid] = null;
+                    n.last_component = i+1;
+                });
+            });
+        }
+    };
+};
+
+dc_graph.fix_nodes_group = function(brushgroup) {
+    window.chart_registry.create_type('fix-nodes', function() {
+        return d3.dispatch('request_fixes', 'new_node', 'new_edge');
+    });
+
+    return window.chart_registry.create_group('fix-nodes', brushgroup);
+};
+
+dc_graph.filter_selection = function(things_group, things_name) {
+    things_name = things_name || 'select-nodes';
+    var select_nodes_group = dc_graph.select_things_group(things_group || 'select-nodes-group', things_name);
+
+    function selection_changed(diagram) {
+        return function(selection) {
+            if(selection.length) {
+                var set = d3.set(selection);
+                _mode.dimensionAccessor()(diagram).filterFunction(function(k) {
+                    return set.has(k);
+                });
+            } else _mode.dimensionAccessor()(diagram).filter(null);
+            diagram.redrawGroup();
+        };
+    }
+
+    var _mode = {
+        parent: property(null).react(function(p) {
+            select_nodes_group.on('set_changed.filter-selection-' + things_name, p ? selection_changed(p) : null);
+        })
+    };
+    _mode.dimensionAccessor = property(function(diagram) {
+        return diagram.nodeDimension();
+    });
+    return _mode;
+};
+
+dc_graph.delete_things = function(things_group, mode_name, id_tag) {
+    id_tag = id_tag || 'id';
+    var _deleteKey = is_a_mac ? 'Backspace' : 'Delete';
+    var _keyboard, _selected = [];
+    function selection_changed(selection) {
+        _selected = selection;
+    }
+    function row_id(r) {
+        return r[id_tag];
+    }
+    function delete_selection(selection) {
+        if(!_mode.crossfilterAccessor())
+            throw new Error('need crossfilterAccessor');
+        if(!_mode.dimensionAccessor())
+            throw new Error('need dimensionAccessor');
+        selection = selection || _selected;
+        if(selection.length === 0)
+            return Promise.resolve([]);
+        var promise = _mode.preDelete() ? _mode.preDelete()(selection) : Promise.resolve(selection);
+        if(_mode.onDelete())
+            promise = promise.then(_mode.onDelete());
+        return promise.then(function(selection) {
+            if(selection && selection.length) {
+                var crossfilter = _mode.crossfilterAccessor()(_mode.parent()),
+                    dimension = _mode.dimensionAccessor()(_mode.parent());
+                var all = crossfilter.all().slice(), n = all.length;
+                dimension.filter(null);
+                crossfilter.remove();
+                var filtered = all.filter(function(r) {
+                    return selection.indexOf(row_id(r)) === -1;
+                });
+                if(all.length !== filtered.length + selection.length)
+                    console.warn('size after deletion is not previous size minus selection size',
+                                 filtered.map(row_id), all.map(row_id), selection);
+                crossfilter.add(filtered);
+
+                _mode.parent().redrawGroup();
+            }
+            return true;
+        });
+    }
+    function draw(diagram) {
+        _keyboard.on('keyup.' + mode_name, function() {
+            if(d3.event.code === _deleteKey)
+                delete_selection();
+        });
+    }
+    function remove(diagram) {
+    }
+    var _mode = dc_graph.mode(mode_name, {
+        draw: draw,
+        remove: remove,
+        parent: function(p) {
+            things_group.on('set_changed.' + mode_name, selection_changed);
+            if(p) {
+                _keyboard = p.child('keyboard');
+                if(!_keyboard)
+                    p.child('keyboard', _keyboard = dc_graph.keyboard());
+            }
+        }
+    });
+    _mode.preDelete = property(null);
+    _mode.onDelete = property(null);
+    _mode.crossfilterAccessor = property(null);
+    _mode.dimensionAccessor = property(null);
+    _mode.deleteSelection = delete_selection;
+    return _mode;
+};
+
+dc_graph.delete_nodes = function(id_tag, options) {
+    options = options || {};
+    var select_nodes_group = dc_graph.select_things_group(options.select_nodes_group || 'select-nodes-group', 'select-nodes');
+    var select_edges_group = dc_graph.select_things_group(options.select_edges_group || 'select-edges-group', 'select-edges');
+    var _mode = dc_graph.delete_things(select_nodes_group, 'delete-nodes', id_tag);
+
+    _mode.preDelete(function(nodes) {
+        // request a delete of all attached edges, using the delete edges mode
+        // kind of horrible
+        var diagram = _mode.parent();
+        var deleteEdgesMode = diagram.child('delete-edges');
+        if(!deleteEdgesMode)
+            return null; // reject if we can't delete the edges
+        // it is likely that the delete_edges mode is listening to the same keyup event we
+        // are. introduce a pause to let it process the delete key now, deleting any selected edges.
+        // then select any remaining edges connected to the selected nodes and delete those.
+        //
+        // more evidence that modes need to be able to say "i got this", or that we should have
+        // batch deletion. otoh, given the current behavior, delete_nodes deferring to delete_edges
+        // makes about as much sense as anything
+        return Promise.resolve(undefined).then(function() {
+            var deleteEdges = diagram.edgeGroup().all().filter(function(e) {
+                return nodes.indexOf(diagram.edgeSource()(e)) !== -1 ||
+                    nodes.indexOf(diagram.edgeTarget()(e)) !== -1;
+            }).map(diagram.edgeKey());
+            select_edges_group.set_changed(deleteEdges);
+            return deleteEdgesMode.deleteSelection().then(function() {
+                return nodes;
+            });
+        });
+    });
+    return _mode;
+};
+
+dc_graph.label_things = function(options) {
+    options = options || {};
+    var select_things_group = dc_graph.select_things_group(options.select_group, options.select_type),
+        label_things_group = dc_graph.label_things_group(options.label_group, options.label_type);
+    var _selected = [];
+    var _keyboard, _selectThings;
+
+    function selection_changed_listener(diagram) {
+        return function(selection) {
+            _selected = selection;
+        };
+    }
+
+    function grab_focus() {
+        _keyboard.focus();
+    }
+
+    function edit_label_listener(diagram) {
+        return function(thing, eventOptions) {
+            var box = options.thing_box(thing);
+            options.hide_thing_label(thing, true);
+            dc_graph.edit_text(
+                diagram.g(),
+                {
+                    text: eventOptions.text || options.thing_label(thing) || options.default_label,
+                    align: options.align,
+                    class: options.class,
+                    box: box,
+                    selectText: eventOptions.selectText,
+                    accept: function(text) {
+                        return options.accept(thing, text);
+                    },
+                    finally: function() {
+                        options.hide_thing_label(thing, false);
+                        grab_focus();
+                    }
+                });
+        };
+    }
+
+    function edit_selection(node, edge, eventOptions) {
+        // less than ideal interface.
+        // what if there are other things? can i blame the missing metagraph?
+        var thing = options.find_thing(_selected[0], node, edge);
+        if(thing.empty()) {
+            console.error("couldn't find thing '" + _selected[0] + "'!");
+            return;
+        }
+        if(thing.size()>1) {
+            console.error("found too many things for '" + _selected[0] + "' (" + thing.size() + ")!");
+            return;
+        }
+        label_things_group.edit_label(thing, eventOptions);
+    }
+    function draw(diagram, node, edge) {
+        _keyboard.on('keyup.' + options.label_type, function() {
+            if(_selected.length) {
+                // printable characters should start edit
+                if(d3.event.key.length !== 1)
+                    return;
+                edit_selection(node, edge, {text: d3.event.key, selectText: false});
+            }
+        });
+        if(_selectThings)
+            _selectThings.thinginess().clickables(diagram, node, edge).on('dblclick.' + options.label_type, function() {
+                edit_selection(node, edge, {selectText: true});
+            });
+    }
+
+    function remove(diagram, node, edge) {
+    }
+
+    var _mode = dc_graph.mode(options.label_type, {
+        draw: draw,
+        remove: remove,
+        parent: function(p) {
+            select_things_group.on('set_changed.' + options.label_type, p ? selection_changed_listener(p) : null);
+            label_things_group.on('edit_label.' + options.label_type, p ? edit_label_listener(p) : null);
+            if(p) {
+                _keyboard = p.child('keyboard');
+                if(!_keyboard)
+                    p.child('keyboard', _keyboard = dc_graph.keyboard());
+                _selectThings = p.child(options.select_type);
+            }
+        }
+    });
+    _mode.editSelection = function(eventOptions) {
+        edit_selection(_mode.parent().selectAllNodes(), _mode.parent().selectAllEdges(), eventOptions);
+    };
+    return _mode;
+};
+
+dc_graph.label_things_group = function(brushgroup, type) {
+    window.chart_registry.create_type(type, function() {
+        return d3.dispatch('edit_label');
+    });
+
+    return window.chart_registry.create_group(type, brushgroup);
+};
+
+dc_graph.label_nodes = function(options) {
+    options = options || {};
+    var _labelTag = options.labelTag || 'label';
+    options.select_group = options.select_group || 'select-nodes-group';
+    options.select_type = options.select_type || 'select-nodes';
+    options.label_group = options.label_group || 'label-nodes-group';
+    options.label_type = options.label_type || 'label-nodes';
+    options.default_label = "node name";
+
+    options.find_thing = function(key, node, edge) {
+        return node.filter(function(n) {
+            return _mode.parent().nodeKey.eval(n) === key;
+        });
+    };
+    options.hide_thing_label = function(node, whether) {
+        var contents = _mode.parent().content(_mode.parent().nodeContent.eval(node.datum()));
+        contents.selectText(node).attr('visibility', whether ? 'hidden' : 'visible');
+    };
+    options.thing_box = function(node, eventOptions) {
+        var contents = _mode.parent().content(_mode.parent().nodeContent.eval(node.datum())),
+            box = contents.textbox(node);
+        box.x += node.datum().cola.x;
+        box.y += node.datum().cola.y;
+        return box;
+    };
+    options.thing_label = function(node) {
+        return _mode.parent().nodeLabel.eval(node.datum());
+    };
+    options.accept = function(node, text) {
+        var callback = _mode.changeNodeLabel() ?
+                _mode.changeNodeLabel()(_mode.parent().nodeKey.eval(node.datum()), text) :
+                Promise.resolve(text);
+        return callback.then(function(text2) {
+            var n = node.datum();
+            n.orig.value[_labelTag] = text2;
+            _mode.parent().redrawGroup();
+        });
+    };
+
+    var _mode = dc_graph.label_things(options);
+    _mode.changeNodeLabel = property(null);
+    return _mode;
+};
+
+dc_graph.label_edges = function(options) {
+    options = options || {};
+    var _labelTag = options.labelTag || 'label';
+    options.select_group = options.select_group || 'select-edges-group';
+    options.select_type = options.select_type || 'select-edges';
+    options.label_group = options.label_group || 'label-edges-group';
+    options.label_type = options.label_type || 'label-edges';
+    options.default_label = "edge name";
+
+    options.find_thing = function(key, node, edge) {
+        return edge.filter(function(e) {
+            return _mode.parent().edgeKey.eval(e) === key;
+        });
+    };
+    options.hide_thing_label = function(edge, whether) {
+        var label = _mode.parent().selectAll('#' + _mode.parent().edgeId(edge.datum()) + '-label textPath');
+        label.attr('visibility', whether ? 'hidden' : 'visible');
+    };
+    options.thing_box = function(edge, eventOptions) {
+        var points = edge.datum().pos.new.path.points,
+            x = (points[0].x + points[1].x)/2,
+            y = (points[0].y + points[1].y)/2;
+        return {x: x, y: y-10, width:0, height: 20};
+    };
+    options.thing_label = function(edge) {
+        return _mode.parent().edgeLabel.eval(edge.datum());
+    };
+    options.accept = function(edge, text) {
+        var callback = _mode.changeEdgeLabel() ?
+                _mode.changeEdgeLabel()(_mode.parent().edgeKey.eval(edge.datum()), text) :
+                Promise.resolve(text);
+        return callback.then(function(text2) {
+            var e = edge.datum();
+            e.orig.value[_labelTag] = text2;
+            _mode.parent().redrawGroup();
+        });
+    };
+
+    var _mode = dc_graph.label_things(options);
+    _mode.changeEdgeLabel = property(null);
+    return _mode;
+};
+
+dc_graph.register_highlight_things_group = function(thingsgroup) {
+    window.chart_registry.create_type('highlight-things', function() {
+        return d3.dispatch('highlight');
+    });
+
+    return window.chart_registry.create_group('highlight-things', thingsgroup);
+};
+
+dc_graph.highlight_things = function(includeprops, excludeprops, modename, groupname, cascbase) {
+    var highlight_things_group = dc_graph.register_highlight_things_group(groupname || 'highlight-things-group');
+    var _active, _nodeset = {}, _edgeset = {};
+    cascbase = cascbase || 150;
+
+    function highlight(nodeset, edgeset) {
+        _active = nodeset || edgeset;
+        _nodeset = nodeset || {};
+        _edgeset = edgeset || {};
+        _mode.parent().requestRefresh(_mode.durationOverride());
+    }
+    function draw(diagram) {
+        diagram.cascade(cascbase, true, node_edge_conditions(
+            function(n) {
+                return _nodeset[_mode.parent().nodeKey.eval(n)];
+            }, function(e) {
+                return _edgeset[_mode.parent().edgeKey.eval(e)];
+            }, includeprops));
+        diagram.cascade(cascbase+10, true, node_edge_conditions(
+            function(n) {
+                return _active && !_nodeset[_mode.parent().nodeKey.eval(n)];
+            }, function(e) {
+                return _active && !_edgeset[_mode.parent().edgeKey.eval(e)];
+            }, excludeprops));
+    }
+    function remove(diagram) {
+        diagram.cascade(cascbase, false, includeprops);
+        diagram.cascade(cascbase + 10, false, excludeprops);
+    }
+    var _mode = dc_graph.mode(modename, {
+        draw: draw,
+        remove: remove,
+        parent: function(p) {
+            highlight_things_group.on('highlight.' + modename, p ? highlight : null);
+        }
+    });
+    _mode.durationOverride = property(undefined);
+    return _mode;
+};
+
+dc_graph.register_highlight_neighbors_group = function(neighborsgroup) {
+    window.chart_registry.create_type('highlight-neighbors', function() {
+        return d3.dispatch('highlight_node');
+    });
+
+    return window.chart_registry.create_group('highlight-neighbors', neighborsgroup);
+};
+
+dc_graph.highlight_neighbors = function(includeprops, excludeprops, neighborsgroup, thingsgroup) {
+    var highlight_neighbors_group = dc_graph.register_highlight_neighbors_group(neighborsgroup || 'highlight-neighbors-group');
+    var highlight_things_group = dc_graph.register_highlight_things_group(thingsgroup || 'highlight-things-group');
+
+    function highlight_node(nodeid) {
+        var diagram = _mode.parent();
+        var nodeset = {}, edgeset = {};
+        if(nodeid) {
+            nodeset[nodeid] = true;
+            _mode.parent().selectAllEdges().each(function(e) {
+                if(diagram.nodeKey.eval(e.source) === nodeid) {
+                    edgeset[diagram.edgeKey.eval(e)] = true;
+                    nodeset[diagram.nodeKey.eval(e.target)] = true;
+                }
+                if(diagram.nodeKey.eval(e.target) === nodeid) {
+                    edgeset[diagram.edgeKey.eval(e)] = true;
+                    nodeset[diagram.nodeKey.eval(e.source)] = true;
+                }
+            });
+            highlight_things_group.highlight(nodeset, edgeset);
+        }
+        else highlight_things_group.highlight(null, null);
+    }
+    function draw(diagram, node, edge) {
+        node
+            .on('mouseover.highlight-neighbors', function(n) {
+                highlight_neighbors_group.highlight_node(_mode.parent().nodeKey.eval(n));
+            })
+            .on('mouseout.highlight-neighbors', function(n) {
+                highlight_neighbors_group.highlight_node(null);
+            });
+    }
+
+    function remove(diagram, node, edge) {
+        node
+            .on('mouseover.highlight-neighbors', null)
+            .on('mouseout.highlight-neighbors', null);
+        highlight_neighbors_group.highlight_node(null);
+    }
+
+    var _mode = dc_graph.mode('highlight-neighbors', {
+        draw: draw,
+        remove: function(diagram, node, edge) {
+            remove(diagram, node, edge);
+        },
+        parent: function(p) {
+            highlight_neighbors_group.on('highlight_node.highlight-neighbors', p ? highlight_node : null);
+            if(p && !p.child('highlight-things'))
+                p.child('highlight-things',
+                        dc_graph.highlight_things(includeprops, excludeprops)
+                          .durationOverride(_mode.durationOverride()));
+        }
+    });
+    _mode.durationOverride = property(undefined);
+    return _mode;
+};
+
+
+dc_graph.highlight_radius = function(options) {
+    options = options || {};
+    var select_nodes_group = dc_graph.select_things_group(options.select_nodes_group || 'select-nodes-group', 'select-nodes');
+    var highlight_things_group = dc_graph.register_highlight_things_group(options.highlight_things_group || 'highlight-things-group');
+    var _graph, _selection = [];
+
+    function recurse(n, r, nodeset, edgeset) {
+        nodeset[n.key()] = true;
+        if(r) {
+            n.outs().filter(function(e) {
+                return !edgeset[e.key()];
+            }).forEach(function(e) {
+                edgeset[e.key()] = true;
+                recurse(e.target(), r-1, nodeset, edgeset);
+            });
+            n.ins().filter(function(e) {
+                return !edgeset[e.key()];
+            }).forEach(function(e) {
+                edgeset[e.key()] = true;
+                recurse(e.source(), r-1, nodeset, edgeset);
+            });
+        }
+    }
+    function selection_changed(nodes) {
+        _selection = nodes;
+        console.assert(_graph);
+        var nodeset = {}, edgeset = {};
+        nodes.forEach(function(nkey) {
+            recurse(_graph.node(nkey), _mode.radius(), nodeset, edgeset);
+        });
+        if(!Object.keys(nodeset).length && !Object.keys(edgeset).length)
+            nodeset = edgeset = null;
+        highlight_things_group.highlight(nodeset, edgeset);
+    }
+
+    function on_data(diagram, nodes, wnodes, edges, wedges, ports, wports) {
+        _graph = metagraph.graph(wnodes, wedges, {
+            nodeKey: diagram.nodeKey.eval,
+            edgeKey: diagram.edgeKey.eval,
+            edgeSource: diagram.edgeSource.eval,
+            edgeTarget: diagram.edgeTarget.eval
+        });
+        var sel2 = _selection.filter(function(nk) {
+            return !!_graph.node(nk);
+        });
+        if(sel2.length < _selection.length)
+            window.setTimeout(function() {
+                select_nodes_group.set_changed(sel2);
+            }, 0);
+    }
+    var _mode = {
+        parent: function(p) {
+            if(p) {
+                p.on('data.highlight-radius', on_data);
+            } else if(_mode.parent())
+                _mode.parent().on('data.highlight-radius', null);
+            select_nodes_group.on('set_changed.highlight-radius', selection_changed);
+        }
+    };
+    _mode.radius = property(1);
+    return _mode;
+};
+
+dc_graph.register_highlight_paths_group = function(pathsgroup) {
+    window.chart_registry.create_type('highlight-paths', function() {
+        return d3.dispatch('paths_changed', 'hover_changed', 'select_changed');
+    });
+
+    return window.chart_registry.create_group('highlight-paths', pathsgroup);
+};
+
+dc_graph.highlight_paths = function(pathprops, hoverprops, selectprops, pathsgroup) {
+    var highlight_paths_group = dc_graph.register_highlight_paths_group(pathsgroup || 'highlight-paths-group');
+    pathprops = pathprops || {};
+    hoverprops = hoverprops || {};
+    selectprops = selectprops || {};
+    var node_on_paths = {}, edge_on_paths = {}, selected = null, hoverpaths = null;
+    var _anchor;
+
+    function refresh() {
+        if(_mode.doRedraw())
+            _mode.parent().relayout().redraw();
+        else
+            _mode.parent().refresh();
+    }
+
+    function paths_changed(nop, eop) {
+        selected = hoverpaths = null;
+        // it would be difficult to check if no change, but at least check if changing from empty to empty
+        if(Object.keys(node_on_paths).length === 0 && Object.keys(nop).length === 0 &&
+           Object.keys(edge_on_paths).length === 0 && Object.keys(eop).length === 0)
+            return;
+        node_on_paths = nop;
+        edge_on_paths = eop;
+        refresh();
+    }
+
+    function hover_changed(hp) {
+        if(hp !== hoverpaths) {
+            hoverpaths = hp;
+            refresh();
+        }
+    }
+
+    function select_changed(sp) {
+        if(sp !== selected) {
+            selected = sp;
+            refresh();
+        }
+    }
+
+    function clear_all_highlights() {
+        node_on_paths = {};
+        edge_on_paths = {};
+    }
+
+    function contains_path(paths) {
+        return function(path) {
+            return paths.indexOf(path)>=0;
+        };
+    }
+
+    // sigh
+    function doesnt_contain_path(paths) {
+        var cp = contains_path(paths);
+        return function(path) {
+            return !cp(path);
+        };
+    }
+
+    function intersect_paths(pathsA, pathsB) {
+        if(!pathsA || !pathsB)
+            return false;
+        return pathsA.some(contains_path(pathsB));
+    }
+
+    function toggle_paths(pathsA, pathsB) {
+        if(!pathsA)
+            return pathsB;
+        else if(!pathsB)
+            return pathsA;
+        if(pathsB.every(contains_path(pathsA)))
+            return pathsA.filter(doesnt_contain_path(pathsB));
+        else return pathsA.concat(pathsB.filter(doesnt_contain_path(pathsA)));
+    }
+
+    function draw(diagram, node, edge, ehover) {
+        diagram
+            .cascade(200, true, node_edge_conditions(function(n) {
+                return !!node_on_paths[diagram.nodeKey.eval(n)];
+            }, function(e) {
+                return !!edge_on_paths[diagram.edgeKey.eval(e)];
+            }, pathprops))
+            .cascade(300, true, node_edge_conditions(function(n) {
+                return intersect_paths(node_on_paths[diagram.nodeKey.eval(n)], selected);
+            }, function(e) {
+                return intersect_paths(edge_on_paths[diagram.edgeKey.eval(e)], selected);
+            }, selectprops))
+            .cascade(400, true, node_edge_conditions(function(n) {
+                return intersect_paths(node_on_paths[diagram.nodeKey.eval(n)], hoverpaths);
+            }, function(e) {
+                return intersect_paths(edge_on_paths[diagram.edgeKey.eval(e)], hoverpaths);
+            }, hoverprops));
+
+        node
+            .on('mouseover.highlight-paths', function(n) {
+                highlight_paths_group.hover_changed(node_on_paths[diagram.nodeKey.eval(n)] || null);
+            })
+            .on('mouseout.highlight-paths', function(n) {
+                highlight_paths_group.hover_changed(null);
+            })
+            .on('click.highlight-paths', function(n) {
+                highlight_paths_group.select_changed(toggle_paths(selected, node_on_paths[diagram.nodeKey.eval(n)]));
+            });
+
+
+        ehover
+            .on('mouseover.highlight-paths', function(e) {
+                highlight_paths_group.hover_changed(edge_on_paths[diagram.edgeKey.eval(e)] || null);
+            })
+            .on('mouseout.highlight-paths', function(e) {
+                highlight_paths_group.hover_changed(null);
+            })
+            .on('click.highlight-paths', function(n) {
+                highlight_paths_group.select_changed(toggle_paths(selected, edge_on_paths[diagram.nodeKey.eval(n)]));
+            });
+    }
+
+    function remove(diagram, node, edge, ehover) {
+        node
+            .on('mouseover.highlight-paths', null)
+            .on('mouseout.highlight-paths', null)
+            .on('click.highlight-paths', null);
+        ehover
+            .on('mouseover.highlight-paths', null)
+            .on('mouseout.highlight-paths', null)
+            .on('click.highlight-paths', null);
+        clear_all_highlights();
+        diagram
+            .cascade(200, false, pathprops)
+            .cascade(300, false, selectprops)
+            .cascade(400, false, hoverprops);
+    }
+
+    var _mode = dc_graph.mode('highlight-paths', {
+        draw: draw,
+        remove: function(diagram, node, edge, ehover) {
+            remove(diagram, node, edge, ehover);
+            return this;
+        },
+        parent: function(p) {
+            if(p)
+                _anchor = p.anchorName();
+            // else we should have received anchor earlier
+            highlight_paths_group.on('paths_changed.highlight-paths-' + _anchor, p ? paths_changed : null);
+            highlight_paths_group.on('hover_changed.highlight-paths-' + _anchor, p ? hover_changed : null);
+            highlight_paths_group.on('select_changed.highlight-paths-' + _anchor, p ? select_changed : null);
+        }
+    });
+
+    // whether to do relayout & redraw (true) or just refresh (false)
+    _mode.doRedraw = property(false);
+
+    return _mode;
+};
+
+
+dc_graph.spline_paths = function(pathreader, pathprops, hoverprops, selectprops, pathsgroup) {
+    var highlight_paths_group = dc_graph.register_highlight_paths_group(pathsgroup || 'highlight-paths-group');
+    pathprops = pathprops || {};
+    hoverprops = hoverprops || {};
+    var _paths = null, _hoverpaths = null, _selected = null;
+    var _anchor;
+    var _layer = null;
+    var _savedPositions = null;
+
+    function paths_changed(nop, eop, paths) {
+        _paths = paths;
+
+        var engine = _mode.parent().layoutEngine(),
+            localPaths = paths.filter(pathIsPresent);
+        if(localPaths.length) {
+            var nidpaths = localPaths.map(function(lpath) {
+                var strength = pathreader.pathStrength.eval(lpath);
+                if(typeof strength !== 'number')
+                    strength = 1;
+                if(_selected && _selected.indexOf(lpath) !== -1)
+                    strength *= _mode.selectedStrength();
+                return {
+                    nodes: path_keys(lpath),
+                    strength: strength
+                };
+            });
+            engine.paths(nidpaths);
+        } else {
+            engine.paths(null);
+            if(_savedPositions)
+                engine.restorePositions(_savedPositions);
+        }
+        if(_selected)
+            _selected = _selected.filter(function(p) { return localPaths.indexOf(p) !== -1; });
+        _mode.parent().redraw();
+    }
+
+    function select_changed(sp) {
+        if(sp !== _selected) {
+            _selected = sp;
+            paths_changed(null, null, _paths);
+        }
+    }
+
+    function path_keys(path, unique) {
+        unique = unique !== false;
+        var keys = pathreader.elementList.eval(path).filter(function(elem) {
+            return pathreader.elementType.eval(elem) === 'node';
+        }).map(function(elem) {
+            return pathreader.nodeKey.eval(elem);
+        });
+        return unique ? uniq(keys) : keys;
+    }
+
+    // check if entire path is present in this view
+    function pathIsPresent(path) {
+        return pathreader.elementList.eval(path).every(function(element) {
+            return pathreader.elementType.eval(element) !== 'node' ||
+                _mode.parent().getWholeNode(pathreader.nodeKey.eval(element));
+        });
+    }
+
+    // get the positions of nodes on path
+    function getNodePositions(path, old) {
+        return path_keys(path, false).map(function(key) {
+            var node = _mode.parent().getWholeNode(key);
+            return {x: old && node.prevX !== undefined ? node.prevX : node.cola.x,
+                    y: old && node.prevY !== undefined ? node.prevY : node.cola.y};
+        });
+    };
+
+    // insert fake nodes to avoid sharp turns
+    function insertDummyNodes(path_coord) {
+        function _distance(node1, node2) {
+            return Math.sqrt(Math.pow((node1.x-node2.x),2) + Math.pow((node1.y-node2.y),2));
+        }
+
+        var new_path_coord = [];
+
+        for(var i = 0; i < path_coord.length; i ++) {
+            if (i-1 >= 0 && i+1 < path_coord.length) {
+                if (path_coord[i-1].x === path_coord[i+1].x &&
+                    path_coord[i-1].y === path_coord[i+1].y ) {
+                    // insert node when the previous and next nodes are the same
+                    var x1 = path_coord[i-1].x, y1 = path_coord[i-1].y;
+                    var x2 = path_coord[i].x, y2 = path_coord[i].y;
+                    var dx = x1 - x2, dy = y1 - y2;
+
+                    var v1 = dy / Math.sqrt(dx*dx + dy*dy);
+                    var v2 = - dx / Math.sqrt(dx*dx + dy*dy);
+
+                    var insert_p1 = {'x': null, 'y': null};
+                    var insert_p2 = {'x': null, 'y': null};
+
+                    var offset = 10;
+
+                    insert_p1.x = (x1+x2)/2.0 + offset*v1;
+                    insert_p1.y = (y1+y2)/2.0 + offset*v2;
+
+                    insert_p2.x = (x1+x2)/2.0 - offset*v1;
+                    insert_p2.y = (y1+y2)/2.0 - offset*v2;
+
+                    new_path_coord.push(insert_p1);
+                    new_path_coord.push(path_coord[i]);
+                    new_path_coord.push(insert_p2);
+                } else if (_distance(path_coord[i-1], path_coord[i+1]) < pathprops.nearNodesDistance){
+                    // insert node when the previous and next nodes are very close
+                    // first node
+                    var x1 = path_coord[i-1].x, y1 = path_coord[i-1].y;
+                    var x2 = path_coord[i].x, y2 = path_coord[i].y;
+                    var dx = x1 - x2, dy = y1 - y2;
+
+                    var v1 = dy / Math.sqrt(dx*dx + dy*dy);
+                    var v2 = - dx / Math.sqrt(dx*dx + dy*dy);
+
+                    var insert_p1 = {'x': null, 'y': null};
+
+                    var offset = 10;
+
+                    insert_p1.x = (x1+x2)/2.0 + offset*v1;
+                    insert_p1.y = (y1+y2)/2.0 + offset*v2;
+
+                    // second node
+                    x1 = path_coord[i].x;
+                    y1 = path_coord[i].y;
+                    x2 = path_coord[i+1].x;
+                    y2 = path_coord[i+1].y;
+                    dx = x1 - x2;
+                    dy = y1 - y2;
+
+                    v1 = dy / Math.sqrt(dx*dx + dy*dy);
+                    v2 = - dx / Math.sqrt(dx*dx + dy*dy);
+
+                    var insert_p2 = {'x': null, 'y': null};
+
+                    insert_p2.x = (x1+x2)/2.0 + offset*v1;
+                    insert_p2.y = (y1+y2)/2.0 + offset*v2;
+
+                    new_path_coord.push(insert_p1);
+                    new_path_coord.push(path_coord[i]);
+                    new_path_coord.push(insert_p2);
+
+                }
+                else {
+                    new_path_coord.push(path_coord[i]);
+                }
+            } else {
+                new_path_coord.push(path_coord[i]);
+            }
+        }
+        return new_path_coord;
+    }
+
+    // helper functions
+    var vecDot = function(v0, v1) { return v0.x*v1.x+v0.y*v1.y; };
+    var vecMag = function(v) { return Math.sqrt(v.x*v.x + v.y*v.y); };
+    var l2Dist = function(p1, p2) {
+        return Math.sqrt((p1.x-p2.x)*(p1.x-p2.x)+(p1.y-p2.y)*(p1.y-p2.y));
+    };
+
+    function drawCardinalSpline(points, lineTension, avoidSharpTurn, angleThreshold) {
+      var c = lineTension || 0;
+      avoidSharpTurn = avoidSharpTurn !== false;
+      angleThreshold = angleThreshold || 0.02;
+
+      // get the path without self loops
+      var path_list = [points[0]];
+      for(var i = 1; i < points.length; i ++) {
+        if(l2Dist(points[i], path_list[path_list.length-1]) > 1e-6) {
+          path_list.push(points[i]);
+        }
+      }
+
+      // repeat first and last node
+      points = [path_list[0]];
+      points = points.concat(path_list);
+      points.push(path_list[path_list.length-1]);
+
+      // a segment is a list of three points: [c0, c1, p1],
+      // representing the coordinates in "C x0,y0,x1,y1,x,y" in svg:path
+      var segments = []; // control points
+      for(var i = 1; i < points.length-2; i ++) {
+        // generate svg:path
+        var m_0_x = (1-c)*(points[i+1].x - points[i-1].x)/2;
+        var m_0_y = (1-c)*(points[i+1].y - points[i-1].y)/2;
+
+        var m_1_x = (1-c)*(points[i+2].x - points[i].x)/2;
+        var m_1_y = (1-c)*(points[i+2].y - points[i].y)/2;
+
+        var p0 = points[i];
+        var p1 = points[i+1];
+        var c0 = p0;
+        if(i !== 1) {
+          c0 = {x: p0.x+(m_0_x/3), y:p0.y+(m_0_y/3)};
+        }
+        var c1 = p1;
+        if(i !== points.length-3) {
+          c1 = {x: p1.x-(m_1_x/3), y:p1.y-(m_1_y/3)};
+        }
+
+        // detect special case by calculating the angle
+        if(avoidSharpTurn) {
+          var v0 = {x:points[i-1].x - points[i].x, y:points[i-1].y - points[i].y};
+          var v1 = {x:points[i+1].x - points[i].x, y:points[i+1].y - points[i].y};
+          var acosValue = vecDot(v0,v1) / (vecMag(v0)*vecMag(v1));
+          acosValue = Math.max(-1, Math.min(1, acosValue));
+          var angle = Math.acos( acosValue );
+
+          if(angle <= angleThreshold ){
+            var m_x = (1-c)*(points[i].x - points[i-1].x)/2;
+            var m_y = (1-c)*(points[i].y - points[i-1].y)/2;
+            var k = 2;
+
+            var cp1 = {x: p0.x+k*(-m_y/3), y:p0.y+k*(m_x/3)};
+            var cp2 = {x: p0.x-k*(-m_y/3), y:p0.y-k*(m_x/3)};
+            // CP_1CP_2
+            var vCP = {x: cp1.x-cp2.x, y:cp1.y-cp2.y}; // vector cp1->cp2
+            var vPN = {x: points[i-2].x - points[i+2].x, y:points[i-2].y-points[i+2].y}; // vector Previous->Next
+            if(vecDot(vCP, vPN) > 0) {
+              c0 = cp1;
+              segments[segments.length-1][1] = cp2;
+            } else {
+              c0 = cp2;
+              segments[segments.length-1][1] = cp1;
+            }
+          }
+        }
+
+        segments.push([c0,c1,p1]);
+      }
+
+      var path_d = "M"+points[0].x+","+points[0].y;
+      for(var i = 0; i < segments.length; i ++) {
+        var s = segments[i];
+        path_d += "C"+s[0].x+","+s[0].y;
+        path_d += ","+s[1].x+","+s[1].y;
+        path_d += ","+s[2].x+","+s[2].y;
+      }
+      return path_d;
+    }
+
+    function drawDedicatedLoops(points, lineTension, avoidSharpTurn, angleThreshold) {
+      // get loops as segments
+      var p1 = 0, p2 = 1;
+      var seg_list = []; // (start, end)
+      while(p1 < points.length-1 && p2 < points.length) {
+        if(l2Dist(points[p1], points[p2]) < 1e-6) {
+          var repeated = points[p2];
+          while(p2 < points.length && l2Dist(points[p2], repeated) < 1e-6) p2++;
+          seg_list.push({'start': Math.max(0, p1-1), 'end': Math.min(points.length-1, p2)});
+          p1 = p2;
+          p2 = p1+1;
+        } else {
+          p1++;
+          p2++;
+        }
+      }
+
+      var loopCurves = "";
+      for(var i = 0; i < seg_list.length; i ++) {
+        var segment = seg_list[i];
+        var loopCount = segment.end - segment.start - 2;
+        var anchorPoint = points[segment.start+1];
+
+        // the vector from previous node to next node
+        var vec_pre_next = {
+          x: points[segment.end].x-points[segment.start].x,
+          y: points[segment.end].y-points[segment.start].y
+        };
+
+        // when previous node and next node are the same node, we need to handle
+        // them differently.
+        // e.g. for a loop segment A->B->B->A, we use the perpendicular vector perp_AB
+        // instead of vector AA(which is vec_pre_next in this case).
+        if(vecMag(vec_pre_next) == 0) {
+          vec_pre_next = {
+            x: -(points[segment.end].y-anchorPoint.y),
+            y: points[segment.end].x-anchorPoint.x
+          };
+        }
+
+        // unit length vector
+        var vec_pre_next_unit = {
+          x: vec_pre_next.x / vecMag(vec_pre_next),
+          y: vec_pre_next.y / vecMag(vec_pre_next)
+        };
+        var vec_pre_next_perp = {
+          x: -vec_pre_next.y / vecMag(vec_pre_next),
+          y: vec_pre_next.x / vecMag(vec_pre_next)
+        };
+
+        var insertP;
+        for(var j = 0; j < loopCount; j ++) {
+          var c1,c2,c3,c4;
+
+          // change the control points every time this loop appears
+          var cp_k = 15+2*j;
+
+          // calculate c1 and c4, their tangent match the tangent at anchorPoint
+          c1 = {
+            x: anchorPoint.x + cp_k*vec_pre_next_unit.x,
+            y: anchorPoint.y + cp_k*vec_pre_next_unit.y
+          };
+
+          c4 = {
+            x: anchorPoint.x - cp_k*vec_pre_next_unit.x,
+            y: anchorPoint.y - cp_k*vec_pre_next_unit.y
+          };
+
+          // change the location of inserted virtual point every time this loop appears
+          var control_k = 25+5*j;
+          var insertP1 = {
+            x: anchorPoint.x+vec_pre_next_perp.x*control_k,
+            y: anchorPoint.y+vec_pre_next_perp.y*control_k
+          };
+          var insertP2 = {
+            x: anchorPoint.x-vec_pre_next_perp.x*control_k,
+            y: anchorPoint.y-vec_pre_next_perp.y*control_k
+          };
+          var vec_i_to_next = {
+            x: points[segment.end].x - anchorPoint.x,
+            y: points[segment.end].y - anchorPoint.y
+          };
+          var vec_i_to_insert = {
+            x: insertP1.x - anchorPoint.x,
+            y: insertP1.y - anchorPoint.y
+          };
+          insertP = insertP1;
+          if(vecDot(vec_i_to_insert, vec_i_to_next) > 0) {
+            insertP = insertP2;
+          }
+
+          // calculate c2 and c3 based on insertP
+          c2 = {
+            x: insertP.x + cp_k*vec_pre_next_unit.x,
+            y: insertP.y + cp_k*vec_pre_next_unit.y
+          };
+
+          c3 = {
+            x: insertP.x - cp_k*vec_pre_next_unit.x,
+            y: insertP.y - cp_k*vec_pre_next_unit.y
+          };
+
+          var curve = "M"+anchorPoint.x+","+anchorPoint.y;
+          curve += "C"+c1.x+","+c1.y+","+c2.x+","+c2.y+","+insertP.x+","+insertP.y;
+          curve += "C"+c3.x+","+c3.y+","+c4.x+","+c4.y+","+anchorPoint.x+","+anchorPoint.y;
+
+          loopCurves += curve;
+        }
+      }
+      return loopCurves;
+    }
+
+    // convert original path data into <d>
+    function genPath(originalPoints, old, lineTension, avoidSharpTurn, angleThreshold) {
+      // get coordinates
+      var path_coord = getNodePositions(originalPoints, old);
+      if(path_coord.length < 2) return "";
+
+      var result = "";
+      // process the points and treat them differently:
+      // 1. sub-path without self loop
+      result += drawCardinalSpline(path_coord, lineTension, avoidSharpTurn, angleThreshold);
+
+      // 2. a list of loop segments
+      result += drawDedicatedLoops(path_coord, lineTension, avoidSharpTurn, angleThreshold);
+
+      return result;
+    }
+
+    // draw the spline for paths
+    function drawSpline(paths) {
+        if(paths === null) {
+            _savedPositions = _mode.parent().layoutEngine().savePositions();
+            return;
+        }
+
+        paths = paths.filter(pathIsPresent);
+        var hoverpaths = _hoverpaths || [],
+            selected = _selected || [];
+
+        // edge spline
+        var edge = _layer.selectAll(".spline-edge").data(paths, function(path) { return path_keys(path).join(','); });
+        edge.exit().remove();
+        var edgeEnter = edge.enter().append("svg:path")
+            .attr('class', 'spline-edge')
+            .attr('id', function(d, i) { return "spline-path-"+i; })
+            .attr('stroke-width', pathprops.edgeStrokeWidth || 1)
+            .attr('fill', 'none')
+            .attr('d', function(d) { return genPath(d, true, pathprops.lineTension, _mode.avoidSharpTurns()); });
+        edge
+            .attr('stroke', function(p) {
+                return selected.indexOf(p) !== -1 && selectprops.edgeStroke ||
+                    hoverpaths.indexOf(p) !== -1 && hoverprops.edgeStroke ||
+                    pathprops.edgeStroke || 'black';
+            })
+            .attr('opacity', function(p) {
+                return selected.indexOf(p) !== -1 && selectprops.edgeOpacity ||
+                    hoverpaths.indexOf(p) !== -1 && hoverprops.edgeOpacity ||
+                    pathprops.edgeOpacity || 1;
+            });
+        function path_order(p) {
+            return hoverpaths.indexOf(p) !== -1 ? 2 :
+                selected.indexOf(p) !== -1 ? 1 :
+                0;
+        }
+        edge.sort(function(a, b) {
+            return path_order(a) - path_order(b);
+        });
+        _layer.selectAll('.spline-edge-hover')
+            .each(function() {this.parentNode.appendChild(this);});
+        edge.transition().duration(_mode.parent().transitionDuration())
+            .attr('d', function(d) { return genPath(d, false, pathprops.lineTension, _mode.avoidSharpTurns()); });
+
+        // another wider copy of the edge just for hover events
+        var edgeHover = _layer.selectAll('.spline-edge-hover')
+            .data(paths, function(path) { return path_keys(path).join(','); });
+        edgeHover.exit().remove();
+        var edgeHoverEnter = edgeHover.enter().append('svg:path')
+            .attr('class', 'spline-edge-hover')
+            .attr('d', function(d) { return genPath(d, true, pathprops.lineTension, _mode.avoidSharpTurns()); })
+            .attr('opacity', 0)
+            .attr('stroke', 'green')
+            .attr('stroke-width', (pathprops.edgeStrokeWidth || 1) + 4)
+            .attr('fill', 'none')
+            .on('mouseover.spline-paths', function(d) {
+                highlight_paths_group.hover_changed([d]);
+             })
+            .on('mouseout.spline-paths', function(d) {
+                highlight_paths_group.hover_changed(null);
+             })
+            .on('click.spline-paths', function(d) {
+                var selected = _selected && _selected.slice(0) || [],
+                    i = selected.indexOf(d);
+                if(i !== -1)
+                    selected.splice(i, 1);
+                else if(d3.event.shiftKey)
+                    selected.push(d);
+                else
+                    selected = [d];
+                highlight_paths_group.select_changed(selected);
+             });
+        edgeHover.transition().duration(_mode.parent().transitionDuration())
+            .attr('d', function(d) { return genPath(d, false, pathprops.lineTension, _mode.avoidSharpTurns()); });
+    };
+
+    function draw(diagram, node, edge, ehover) {
+        _layer = _mode.parent().select('g.draw').selectAll('g.spline-layer').data([0]);
+        _layer.enter().append('g').attr('class', 'spline-layer');
+
+        drawSpline(_paths);
+    }
+
+    function remove(diagram, node, edge, ehover) {
+    }
+
+    var _mode = dc_graph.mode('draw-spline-paths', {
+        laterDraw: true,
+        draw: draw,
+        remove: function(diagram, node, edge, ehover) {
+            remove(diagram, node, edge, ehover);
+            return this;
+        },
+        parent: function(p) {
+            if(p)
+                _anchor = p.anchorName();
+            highlight_paths_group
+                .on('paths_changed.draw-spline-paths-' + _anchor, p ? paths_changed : null)
+                .on('select_changed.draw-spline-paths-' + _anchor, p ? select_changed : null)
+                .on('hover_changed.draw-spline-paths-' + _anchor, p ? function(hpaths) {
+                    _hoverpaths = hpaths;
+                    drawSpline(_paths);
+                } : null);
+        }
+    });
+    _mode.selectedStrength = property(1);
+    _mode.avoidSharpTurns = property(true);
+
+    return _mode;
+};
+
+dc_graph.draw_spline_paths = deprecate_function("draw_spline_paths has been renamed spline_paths, please update", dc_graph.spline_paths);
+
+dc_graph.draw_clusters = function() {
+
+    function apply_bounds(rect) {
+        rect.attr({
+            x: function(c) {
+                return c.cola.bounds.left;
+            },
+            y: function(c) {
+                return c.cola.bounds.top;
+            },
+            width: function(c) {
+                return c.cola.bounds.right - c.cola.bounds.left;
+            },
+            height: function(c) {
+                return c.cola.bounds.bottom - c.cola.bounds.top;
+            }
+        });
+    }
+    function draw(diagram) {
+        if(!diagram.clusterGroup())
+            return;
+        var clayer = diagram.g().selectAll('g.cluster-layer').data([0]);
+        clayer.enter().insert('g', ':first-child')
+            .attr('class', 'cluster-layer');
+        var clusters = diagram.clusterGroup().all().map(function(kv) {
+            return _mode.parent().getWholeCluster(kv.key);
+        }).filter(function(c) {
+            return c && c.cola.bounds;
+        });
+        var rects = clayer.selectAll('rect.cluster')
+            .data(clusters, function(c) { return c.orig.key; });
+        rects.exit().remove();
+        rects.enter().append('rect')
+            .attr({
+                class: 'cluster',
+                opacity: 0,
+                stroke: _mode.clusterStroke.eval,
+                'stroke-width': _mode.clusterStrokeWidth.eval,
+                fill: function(c) {
+                    return _mode.clusterFill.eval(c) || 'none';
+                }
+            })
+            .call(apply_bounds);
+        rects.transition()
+            .duration(_mode.parent().stagedDuration())
+            .attr('opacity', _mode.clusterOpacity.eval)
+            .call(apply_bounds);
+    }
+    function remove(diagram, node, edge, ehover) {
+    }
+    var _mode = dc_graph.mode('draw-clusters', {
+        laterDraw: true,
+        draw: draw,
+        remove: remove
+    });
+    _mode.clusterOpacity = property(0.25);
+    _mode.clusterStroke = property('black');
+    _mode.clusterStrokeWidth = property(1);
+    _mode.clusterFill = property(null);
+    _mode.clusterLabel = property(null);
+    _mode.clusterLabelFill = property('black');
+    _mode.clusterLabelAlignment = property(['bottom','right']);
+
+    return _mode;
+};
+
+
+dc_graph.expand_collapse = function(options) {
+    if(typeof options === 'function') {
+        options = {
+            get_degree: arguments[0],
+            expand: arguments[1],
+            collapse: arguments[2],
+            dirs: arguments[3]
+        };
+    }
+    var _keyboard, _overNode, _overDir, _overEdge, _expanded = {};
+    var expanded_highlight_group = dc_graph.register_highlight_things_group(options.expanded_highlight_group || 'expanded-highlight-group');
+    var collapse_highlight_group = dc_graph.register_highlight_things_group(options.collapse_highlight_group || 'collapse-highlight-group');
+    var hide_highlight_group = dc_graph.register_highlight_things_group(options.hide_highlight_group || 'hide-highlight-group');
+    options.dirs = options.dirs || ['both'];
+    options.dirs.forEach(function(dir) {
+        _expanded[dir] = {};
+    });
+    options.hideKey = options.hideKey || 'Alt';
+    options.linkKey = options.linkKey || (is_a_mac ? 'Meta' : 'Control');
+    if(options.dirs.length > 2)
+        throw new Error('there are only two directions to expand in');
+
+    var _gradients_added = {};
+    function add_gradient_def(color, diagram) {
+        if(_gradients_added[color])
+            return;
+        _gradients_added[color] = true;
+        diagram.addOrRemoveDef('spike-gradient-' + color, true, 'linearGradient', function(gradient) {
+            gradient.attr({
+                x1: '0%',
+                y1: '0%',
+                x2: '100%',
+                y2: '0%',
+                spreadMethod: 'pad'
+            });
+            gradient.selectAll('stop').data([[0, color, 1], [100, color, '0']])
+                .enter().append('stop').attr({
+                    offset: function(d) {
+                        return d[0] + '%';
+                    },
+                    'stop-color': function(d) {
+                        return d[1];
+                    },
+                    'stop-opacity': function(d) {
+                        return d[2];
+                    }
+                });
+        });
+    }
+
+    function visible_edges(diagram, edge, dir, key) {
+        var fil;
+        switch(dir) {
+        case 'out':
+            fil = function(e) {
+                return diagram.edgeSource.eval(e) === key;
+            };
+            break;
+        case 'in':
+            fil = function(e) {
+                return diagram.edgeTarget.eval(e) === key;
+            };
+            break;
+        case 'both':
+            fil = function(e) {
+                return diagram.edgeSource.eval(e) === key || diagram.edgeTarget.eval(e) === key;
+            };
+            break;
+        }
+        return edge.filter(fil).data();
+    }
+
+    function spike_directioner(rankdir, dir, N) {
+        if(dir==='both')
+            return function(i) {
+                return Math.PI * (2 * i / N - 0.5);
+            };
+        else {
+            var sweep = (N-1)*Math.PI/N, ofs;
+            switch(rankdir) {
+            case 'LR':
+                ofs = 0;
+                break;
+            case 'TB':
+                ofs = Math.PI/2;
+                break;
+            case 'RL':
+                ofs = Math.PI;
+                break;
+            case 'BT':
+                ofs = -Math.PI/2;
+                break;
+            }
+            if(dir === 'in')
+                ofs += Math.PI;
+            return function(i) {
+                return ofs + sweep * (-.5 + (N > 1 ? i / (N-1) : 0)); // avoid 0/0
+            };
+        }
+    }
+
+    function draw_stubs(diagram, node, edge, n, spikes) {
+        if(n && _expanded[spikes.dir][diagram.nodeKey.eval(n)])
+            spikes = null;
+        var spike = node
+            .selectAll('g.spikes')
+            .data(function(n2) {
+                return spikes && n === n2 ?
+                    [n2] : [];
+            });
+        spike.exit().remove();
+        spike
+          .enter().insert('g', ':first-child')
+            .classed('spikes', true);
+        var rect = spike
+          .selectAll('rect.spike')
+            .data(function(n) {
+                var key = diagram.nodeKey.eval(n);
+                var dir = spikes.dir,
+                    N = spikes.n,
+                    af = spike_directioner(diagram.layoutEngine().rankdir(), dir, N),
+                    ret = Array(N);
+                for(var i = 0; i<N; ++i) {
+                    var a = af(i);
+                    ret[i] = {
+                        a: a * 180 / Math.PI,
+                        x: Math.cos(a) * n.dcg_rx*.9,
+                        y: Math.sin(a) * n.dcg_ry*.9,
+                        edge: spikes.invisible ? spikes.invisible[i] : null
+                    };
+                }
+                return ret;
+            });
+        rect
+          .enter().append('rect')
+            .classed('spike', true)
+            .attr({
+                width: 25,
+                height: 3,
+                fill: function(s) {
+                    var color = s.edge ? dc_graph.functor_wrap(diagram.edgeStroke())(s.edge) : 'black';
+                    add_gradient_def(color, diagram);
+                    return 'url(#spike-gradient-' + color + ')';
+                },
+                rx: 1,
+                ry: 1,
+                x: 0,
+                y: 0
+            });
+        rect.attr('transform', function(d) {
+            return 'translate(' + d.x + ',' + d.y + ') rotate(' + d.a + ')';
+        });
+        rect.exit().remove();
+    }
+
+    function clear_stubs(diagram, node, edge) {
+        draw_stubs(diagram, node, edge, null, null);
+    }
+
+    function zonedir(diagram, event, dirs, n) {
+        if(dirs.length === 1) // we assume it's ['out', 'in']
+            return dirs[0];
+        var bound = diagram.root().node().getBoundingClientRect();
+        var invert = diagram.invertCoord([event.clientX - bound.left,event.clientY - bound.top]),
+            x = invert[0],
+            y = invert[1];
+        switch(diagram.layoutEngine().rankdir()) {
+        case 'TB':
+            return y > n.cola.y ? 'out' : 'in';
+        case 'BT':
+            return y < n.cola.y ? 'out' : 'in';
+        case 'LR':
+            return x > n.cola.x ? 'out' : 'in';
+        case 'RL':
+            return x < n.cola.x ? 'out' : 'in';
+        }
+        throw new Error('unknown rankdir ' + diagram.layoutEngine().rankdir());
+    }
+
+    function detect_key(key) {
+        switch(key) {
+        case 'Alt':
+            return d3.event.altKey;
+        case 'Meta':
+            return d3.event.metaKey;
+        case 'Shift':
+            return d3.event.shiftKey;
+        case 'Control':
+            return d3.event.ctrlKey;
+        }
+        return false;
+    }
+
+    function highlight_hiding_node(diagram, n, edge) {
+        var nk = diagram.nodeKey.eval(n);
+        var hide_nodes_set = {}, hide_edges_set = {};
+        hide_nodes_set[nk] = true;
+        edge.each(function(e) {
+            if(diagram.edgeSource.eval(e) === nk || diagram.edgeTarget.eval(e) === nk)
+                hide_edges_set[diagram.edgeKey.eval(e)] = true;
+        });
+        hide_highlight_group.highlight(hide_nodes_set, hide_edges_set);
+    }
+    function highlight_hiding_edge(diagram, e) {
+        var hide_edges_set = {};
+        hide_edges_set[diagram.edgeKey.eval(e)] = true;
+        hide_highlight_group.highlight({}, hide_edges_set);
+    }
+
+    function highlight_collapse(diagram, n, node, edge, dir) {
+        var nk = diagram.nodeKey.eval(n);
+        var p;
+        if(options.get_edges)
+            p = Promise.resolve(options.get_edges(nk, dir));
+        else
+            p = Promise.resolve(options.get_degree(nk, dir));
+        p.then(function(de) {
+            var degree, edges;
+            if(typeof de === 'number')
+                degree = de;
+            else {
+                edges = de;
+                degree = edges.length;
+            }
+            var spikes = {
+                dir: dir,
+                visible: visible_edges(diagram, edge, dir, nk)
+            };
+            spikes.n = Math.max(0, degree - spikes.visible.length); // be tolerant of inconsistencies
+            if(edges) {
+                var shown = spikes.visible.reduce(function(p, e) {
+                    p[diagram.edgeKey.eval(e)] = true;
+                    return p;
+                }, {});
+                spikes.invisible = edges.filter(function(e) { return !shown[diagram.edgeKey()(e)]; });
+            }
+            draw_stubs(diagram, node, edge, n, spikes);
+            var collapse_nodes_set = {}, collapse_edges_set = {};
+            if(_expanded[dir][nk] && options.collapsibles) {
+                var clps = options.collapsibles(nk, dir);
+                collapse_nodes_set = clps.nodes;
+                collapse_edges_set = clps.edges;
+            }
+            collapse_highlight_group.highlight(collapse_nodes_set, collapse_edges_set);
+        });
+    }
+
+    function draw(diagram, node, edge, ehover) {
+        function over_node(n) {
+            var dir = zonedir(diagram, d3.event, options.dirs, n);
+            _overNode = n;
+            _overDir = dir;
+            if(options.hideNode && detect_key(options.hideKey))
+                highlight_hiding_node(diagram, n, edge);
+            else if(_mode.nodeURL.eval(_overNode) && detect_key(options.linkKey)) {
+                diagram.selectAllNodes()
+                    .filter(function(n) {
+                        return n === _overNode;
+                    }).attr('cursor', 'pointer');
+                diagram.requestRefresh(0);
+            }
+            else
+                highlight_collapse(diagram, n, node, edge, dir);
+        }
+        function leave_node(n)  {
+            diagram.selectAllNodes()
+                .filter(function(n) {
+                    return n === _overNode;
+                }).attr('cursor', null);
+            _overNode = null;
+            clear_stubs(diagram, node, edge);
+            collapse_highlight_group.highlight({}, {});
+            hide_highlight_group.highlight({}, {});
+        }
+        function click_node(n) {
+            var nk = diagram.nodeKey.eval(n);
+            if(options.hideNode && detect_key(options.hideKey))
+                options.hideNode(nk);
+            else if(detect_key(options.linkKey)) {
+                if(_mode.nodeURL.eval(n) && _mode.urlOpener)
+                    _mode.urlOpener()(_mode, n, _mode.nodeURL.eval(n));
+            } else {
+                clear_stubs(diagram, node, edge);
+                var dir = zonedir(diagram, d3.event, options.dirs, n);
+                expand(dir, nk, !_expanded[dir][nk]);
+            }
+        }
+
+        function enter_edge(e) {
+            _overEdge = e;
+            if(options.hideEdge && detect_key(options.hideKey))
+                highlight_hiding_edge(diagram, e);
+        }
+        function leave_edge(e) {
+            _overEdge = null;
+            hide_highlight_group.highlight({}, {});
+        }
+        function click_edge(e) {
+            if(options.hideEdge && detect_key(options.hideKey))
+                options.hideEdge(diagram.edgeKey.eval(e));
+        }
+
+        node
+            .on('mouseenter.expand-collapse', over_node)
+            .on('mousemove.expand-collapse', over_node)
+            .on('mouseout.expand-collapse', leave_node)
+            .on('click.expand-collapse', click_node)
+            .on('dblclick.expand-collapse', click_node);
+
+        ehover
+            .on('mouseenter.expand-collapse', enter_edge)
+            .on('mouseout.expand-collapse', leave_edge)
+            .on('click.expand-collapse', click_edge);
+
+        _keyboard
+            .on('keydown.expand-collapse', function() {
+                if(d3.event.key === options.hideKey && (_overNode && options.hideNode || _overEdge && options.hideEdge)) {
+                    if(_overNode)
+                        highlight_hiding_node(diagram, _overNode, edge);
+                    if(_overEdge)
+                        highlight_hiding_edge(diagram, _overEdge);
+                    clear_stubs(diagram, node, edge);
+                    collapse_highlight_group.highlight({}, {});
+                }
+                else if(d3.event.key === options.linkKey && _overNode) {
+                    if(_overNode && _mode.nodeURL.eval(_overNode)) {
+                        diagram.selectAllNodes()
+                            .filter(function(n) {
+                                return n === _overNode;
+                            }).attr('cursor', 'pointer');
+                    }
+                    hide_highlight_group.highlight({}, {});
+                    clear_stubs(diagram, node, edge);
+                    collapse_highlight_group.highlight({}, {});
+                }
+            })
+            .on('keyup.expand_collapse', function() {
+                if((d3.event.key === options.hideKey || d3.event.key === options.linkKey) && (_overNode || _overEdge)) {
+                    hide_highlight_group.highlight({}, {});
+                    if(_overNode) {
+                        highlight_collapse(diagram, _overNode, node, edge, _overDir);
+                        if(_mode.nodeURL.eval(_overNode)) {
+                            diagram.selectAllNodes()
+                                .filter(function(n) {
+                                    return n === _overNode;
+                                }).attr('cursor', null);
+                        }
+                    }
+                }
+            });
+        diagram.cascade(97, true, conditional_properties(
+            function(n) {
+                return n === _overNode && n.orig.value.value && n.orig.value.value.URL;
+            },
+            {
+                nodeLabelDecoration: 'underline'
+            }
+        ));
+    }
+
+    function remove(diagram, node, edge, ehover) {
+        node
+            .on('mouseenter.expand-collapse', null)
+            .on('mousemove.expand-collapse', null)
+            .on('mouseout.expand-collapse', null)
+            .on('click.expand-collapse', null)
+            .on('dblclick.expand-collapse', null);
+        ehover
+            .on('mouseenter.expand-collapse', null)
+            .on('mouseout.expand-collapse', null)
+            .on('click.expand-collapse', null);
+        clear_stubs(diagram, node, edge);
+    }
+
+    function expand(dir, nk, whether) {
+        if(dir === 'both' && !_expanded.both)
+            options.dirs.forEach(function(dir2) {
+                _expanded[dir2][nk] = whether;
+            });
+        else
+            _expanded[dir][nk] = whether;
+        var bothmap;
+        if(_expanded.both)
+            bothmap = _expanded.both;
+        else {
+            bothmap = Object.keys(_expanded.in).filter(function(nk2) {
+                return _expanded.in[nk2] && _expanded.out[nk2];
+            }).reduce(function(p, v) {
+                p[v] = true;
+                return p;
+            }, {});
+        }
+        expanded_highlight_group.highlight(bothmap, {});
+        if(dir === 'both' && !_expanded.both)
+            options.dirs.forEach(function(dir2, i) {
+                if(whether)
+                    options.expand(nk, dir2, i !== options.dirs.length-1);
+                else
+                    options.collapse(nk, dir2, i !== options.dirs.length-1);
+            });
+        else {
+            if(whether)
+                options.expand(nk, dir);
+            else
+                options.collapse(nk, dir);
+        }
+    }
+
+    function expandNodes(nks) {
+        var map = nks.reduce(function(p, v) {
+            p[v] = true;
+            return p;
+        }, {});
+        options.dirs.forEach(function(dir) {
+            _expanded[dir] = Object.assign({}, map);
+        });
+        expanded_highlight_group.highlight(map, {});
+        options.expandedNodes(map);
+    }
+
+    var _mode = dc_graph.mode('expand-collapse', {
+        draw: draw,
+        remove: remove,
+        parent: function(p) {
+            if(p) {
+                _keyboard = p.child('keyboard');
+                if(!_keyboard)
+                    p.child('keyboard', _keyboard = dc_graph.keyboard());
+            }
+        }
+    });
+
+    _mode.expand = expand;
+    _mode.expandNodes = expandNodes;
+    _mode.clickableLinks = deprecated_property("warning - clickableLinks doesn't belong in collapse_expand and will be moved", false);
+    _mode.nodeURL = property(function(n) {
+        return n.value && n.value.value && n.value.value.URL;
+    });
+    _mode.urlTargetWindow = property('dcgraphlink');
+    _mode.urlOpener = property(dc_graph.expand_collapse.default_url_opener);
+    return _mode;
+};
+
+dc_graph.expand_collapse.default_url_opener = function(mode, node, url) {
+    window.open(mode.nodeURL.eval(node), mode.urlTargetWindow());
+};
+
+dc_graph.expand_collapse.shown_hidden = function(opts) {
+    var options = Object.assign({
+        nodeKey: function(n) { return n.key; }, // this one is raw rows, others are post-crossfilter-group
+        edgeKey: function(e) { return e.key; },
+        edgeSource: function(e) { return e.value.source; },
+        edgeTarget: function(e) { return e.value.target; }
+    }, opts);
+    var _nodeShown = {}, _nodeHidden = {};
+
+    // independent dimension on keys so that the diagram dimension will observe it
+    var _filter = options.nodeCrossfilter.dimension(options.nodeKey);
+    function apply_filter() {
+        _filter.filterFunction(function(nk) {
+            return _nodeShown[nk];
+        });
+    }
+    function adjacent_edges(nk) {
+        return options.edgeGroup.all().filter(function(e) {
+            return options.edgeSource(e) === nk || options.edgeTarget(e) === nk;
+        });
+    }
+    function adjacent_nodes(nk) {
+        return adjacent_edges(nk).map(function(e) {
+            return options.edgeSource(e) === nk ? options.edgeTarget(e) : options.edgeSource(e);
+        });
+    }
+    function adjacencies(nk) {
+        return adjacent_edges(nk).map(function(e) {
+            return options.edgeSource(e) === nk ? [e,options.edgeTarget(e)] : [e,options.edgeSource(e)];
+        });
+    }
+    function out_edges(nk) {
+        return options.edgeGroup.all().filter(function(e) {
+            return options.edgeSource(e) === nk;
+        });
+    }
+    function in_edges(nk) {
+        return options.edgeGroup.all().filter(function(e) {
+            return options.edgeTarget(e) === nk;
+        });
+    }
+    function is_collapsible(n1, n2) {
+        return options.edgeGroup.all().every(function(e2) {
+            var n3;
+            if(options.edgeSource(e2) === n2)
+                n3 = options.edgeTarget(e2);
+            else if(options.edgeTarget(e2) === n2)
+                n3 = options.edgeSource(e2);
+            return !n3 || n3 === n1 || !_nodeShown[n3];
+        });
+    }
+    apply_filter();
+    var _strategy = {};
+    if(options.directional)
+        Object.assign(_strategy, {
+            get_degree: function(nk, dir) {
+                switch(dir) {
+                case 'out': return out_edges(nk).length;
+                case 'in': return in_edges(nk).length;
+                default: throw new Error('unknown direction ' + dir);
+                }
+            },
+            expand: function(nk, dir, skip_draw) {
+                _nodeShown[nk] = true;
+                switch(dir) {
+                case 'out':
+                    out_edges(nk).forEach(function(e) {
+                        if(!_nodeHidden[options.edgeTarget(e)])
+                            _nodeShown[options.edgeTarget(e)] = true;
+                    });
+                    break;
+                case 'in':
+                    in_edges(nk).forEach(function(e) {
+                        if(!_nodeHidden[options.edgeSource(e)])
+                            _nodeShown[options.edgeSource(e)] = true;
+                    });
+                    break;
+                default: throw new Error('unknown direction ' + dir);
+                }
+                if(!skip_draw) {
+                    apply_filter();
+                    dc.redrawAll();
+                }
+            },
+            expandedNodes: function(_) {
+                if(!arguments.length)
+                    throw new Error('not supported'); // should not be called
+                var that = this;
+                _nodeShown = {};
+                Object.keys(_).forEach(function(nk) {
+                    that.expand(nk, 'out', true);
+                    that.expand(nk, 'in', true);
+                });
+                apply_filter();
+                dc.redrawAll();
+                return this;
+            },
+            collapsibles: function(nk, dir) {
+                var nodes = {}, edges = {};
+                (dir === 'out' ? out_edges(nk) : in_edges(nk)).forEach(function(e) {
+                    var n2 = dir === 'out' ? options.edgeTarget(e) : options.edgeSource(e);
+                    if(is_collapsible(nk, n2)) {
+                        nodes[n2] = true;
+                        adjacent_edges(n2).forEach(function(e) {
+                            edges[options.edgeKey(e)] = true;
+                        });
+                    }
+                });
+                return {nodes: nodes, edges: edges};
+            },
+            collapse: function(nk, dir) {
+                Object.keys(this.collapsibles(nk, dir).nodes).forEach(function(nk) {
+                    _nodeShown[nk] = false;
+                });
+                apply_filter();
+                dc.redrawAll();
+            },
+            hideNode: function(nk) {
+                _nodeHidden[nk] = true;
+                _nodeShown[nk] = false;
+                apply_filter();
+                dc.redrawAll();
+            },
+            dirs: ['out', 'in']
+        });
+    else
+        Object.assign(_strategy, {
+            get_degree: function(nk) {
+                return adjacent_edges(nk).length;
+            },
+            expand: function(nk) {
+                _nodeShown[nk] = true;
+                adjacent_nodes(nk).forEach(function(nk) {
+                    if(!_nodeHidden[nk])
+                        _nodeShown[nk] = true;
+                });
+                apply_filter();
+                dc.redrawAll();
+            },
+            expandedNodes: function(_) {
+                if(!arguments.length)
+                    throw new Error('not supported'); // should not be called
+                var that = this;
+                _nodeShown = {};
+                Object.keys(_).forEach(function(nk) {
+                    that.expand(nk);
+                });
+                return this;
+            },
+            collapsibles: function(nk, dir) {
+                var nodes = {}, edges = {};
+                adjacencies(nk).forEach(function(adj) {
+                    var e = adj[0], n2 = adj[1];
+                    if(is_collapsible(nk, n2)) {
+                        nodes[n2] = true;
+                        edges[options.edgeKey(e)] = true;
+                    }
+                });
+                return {nodes: nodes, edges: edges};
+            },
+            collapse: function(nk, dir) {
+                Object.keys(_strategy.collapsibles(nk, dir).nodes).forEach(function(nk) {
+                    _nodeShown[nk] = false;
+                });
+                apply_filter();
+                dc.redrawAll();
+            },
+            hideNode: function(nk) {
+                _nodeHidden[nk] = true;
+                _nodeShown[nk] = false;
+                apply_filter();
+                dc.redrawAll();
+            }
+        });
+    return _strategy;
+};
+
+dc_graph.expand_collapse.expanded_hidden = function(opts) {
+    var options = Object.assign({
+        nodeKey: function(n) { return n.key; },
+        edgeKey: function(e) { return e.key; },
+        edgeSource: function(e) { return e.value.source; },
+        edgeTarget: function(e) { return e.value.target; }
+    }, opts);
+    var _nodeExpanded = {}, _nodeHidden = {}, _edgeHidden = {};
+
+    // independent dimension on keys so that the diagram dimension will observe it
+    var _nodeDim = options.nodeCrossfilter.dimension(options.nodeKey),
+        _edgeDim = options.edgeCrossfilter && options.edgeCrossfilter.dimension(options.edgeRawKey);
+
+    function get_shown(expanded) {
+        return Object.keys(expanded).reduce(function(p, nk) {
+            p[nk] = true;
+            adjacent_nodes(nk).forEach(function(nk2) {
+                if(!_nodeHidden[nk2])
+                    p[nk2] = true;
+            });
+            return p;
+        }, {});
+    }
+    function apply_filter() {
+        var _shown = get_shown(_nodeExpanded);
+        _nodeDim.filterFunction(function(nk) {
+            return _shown[nk];
+        });
+        _edgeDim && _edgeDim.filterFunction(function(ek) {
+            return !_edgeHidden[ek];
+        });
+    }
+    function adjacent_edges(nk) {
+        return options.edgeGroup.all().filter(function(e) {
+            return options.edgeSource(e) === nk || options.edgeTarget(e) === nk;
+        });
+    }
+    // function out_edges(nk) {
+    //     return options.edgeGroup.all().filter(function(e) {
+    //         return options.edgeSource(e) === nk;
+    //     });
+    // }
+    // function in_edges(nk) {
+    //     return options.edgeGroup.all().filter(function(e) {
+    //         return options.edgeTarget(e) === nk;
+    //     });
+    // }
+    function adjacent_nodes(nk) {
+        return adjacent_edges(nk).map(function(e) {
+            return options.edgeSource(e) === nk ? options.edgeTarget(e) : options.edgeSource(e);
+        });
+    }
+
+    apply_filter();
+    var _strategy = {
+        get_degree: function(nk) {
+            return adjacent_edges(nk).length;
+        },
+        get_edges: function(nk) {
+            return adjacent_edges(nk);
+        },
+        expand: function(nk) {
+            _nodeExpanded[nk] = true;
+            apply_filter();
+            dc.redrawAll();
+        },
+        expandedNodes: function(_) {
+            if(!arguments.length)
+                return _nodeExpanded;
+            _nodeExpanded = _;
+            apply_filter();
+            dc.redrawAll();
+            return this;
+        },
+        collapsibles: function(nk, dir) {
+            var whatif = Object.assign({}, _nodeExpanded);
+            delete whatif[nk];
+            var shown = get_shown(_nodeExpanded), would = get_shown(whatif);
+            var going = Object.keys(shown)
+                .filter(function(nk2) { return !would[nk2]; })
+                .reduce(function(p, v) {
+                    p[v] = true;
+                    return p;
+                }, {});
+            return {
+                nodes: going,
+                edges: options.edgeGroup.all().filter(function(e) {
+                    return going[options.edgeSource(e)] || going[options.edgeTarget(e)];
+                }).reduce(function(p, e) {
+                    p[options.edgeKey(e)] = true;
+                    return p;
+                }, {})
+            };
+        },
+        collapse: function(nk, collapsible) {
+            delete _nodeExpanded[nk];
+            apply_filter();
+            dc.redrawAll();
+        },
+        hideNode: function(nk) {
+            _nodeHidden[nk] = true;
+            this.collapse(nk); // in case
+        },
+        hideEdge: function(ek) {
+            if(!options.edgeCrossfilter)
+                console.warn('expanded_hidden needs edgeCrossfilter to hide edges');
+            _edgeHidden[ek] = true;
+            apply_filter();
+            dc.redrawAll();
+        }
+    };
+    return _strategy;
+};
+
+dc_graph.draw_graphs = function(options) {
+    var select_nodes_group =  dc_graph.select_things_group(options.select_nodes_group || 'select-nodes-group', 'select-nodes'),
+        select_edges_group = dc_graph.select_things_group(options.select_edges_group || 'select-edges-group', 'select-edges'),
+        label_nodes_group = dc_graph.label_things_group('label-nodes-group', 'label-nodes'),
+        label_edges_group = dc_graph.label_things_group('label-edges-group', 'label-edges'),
+        fix_nodes_group = dc_graph.fix_nodes_group('fix-nodes-group');
+    var _nodeIdTag = options.idTag || 'id',
+        _edgeIdTag = options.edgeIdTag || _nodeIdTag,
+        _sourceTag = options.sourceTag || 'source',
+        _targetTag = options.targetTag || 'target',
+        _nodeLabelTag = options.labelTag || 'label',
+        _edgeLabelTag = options.edgeLabelTag || _nodeLabelTag;
+
+    var _sourceDown = null, _targetMove = null, _targetValid = false, _edgeLayer = null, _hintData = [], _crossout;
+
+    function update_hint() {
+        var data = _hintData.filter(function(h) {
+            return h.source && h.target;
+        });
+        var line = _edgeLayer.selectAll('line.hint-edge').data(data);
+        line.exit().remove();
+        line.enter().append('line')
+            .attr('class', 'hint-edge')
+            .style({
+                fill: 'none',
+                stroke: 'black',
+                'pointer-events': 'none'
+            });
+
+        line.attr({
+            x1: function(n) { return n.source.x; },
+            y1: function(n) { return n.source.y; },
+            x2: function(n) { return n.target.x; },
+            y2: function(n) { return n.target.y; }
+        });
+    }
+
+    function port_pos(p) {
+        var style = _mode.parent().portStyle(_mode.parent().portStyleName.eval(p));
+        var pos = style.portPosition(p);
+        pos.x += p.node.cola.x;
+        pos.y += p.node.cola.y;
+        return pos;
+    }
+
+    function update_crossout() {
+        var data;
+        if(_crossout) {
+            if(_mode.usePorts())
+                data = [port_pos(_crossout)];
+            else
+                data = [{x: _crossout.node.cola.x, y: _crossout.node.cola.y}];
+        }
+        else data = [];
+
+        var size = _mode.crossSize(), wid = _mode.crossWidth();
+        var cross = _edgeLayer.selectAll('polygon.graph-draw-crossout').data(data);
+        cross.exit().remove();
+        cross.enter().append('polygon')
+            .attr('class', 'graph-draw-crossout');
+        cross
+            .attr('points', function(d) {
+                var x = d.x, y = d.y;
+                return [
+                    [x-size/2, y+size/2], [x-size/2+wid, y+size/2], [x, y+wid/2],
+                    [x+size/2-wid, y+size/2], [x+size/2, y+size/2], [x+wid/2, y],
+                    [x+size/2, y-size/2], [x+size/2-wid, y-size/2], [x, y-wid/2],
+                    [x-size/2+wid, y-size/2], [x-size/2, y-size/2], [x-wid/2, y]
+                ]
+                    .map(function(p) { return p.join(','); })
+                    .join(' ');
+            });
+    }
+    function erase_hint() {
+        _hintData = [];
+        _targetValid = false;
+        _sourceDown = _targetMove = null;
+        update_hint();
+    }
+
+    function create_node(diagram, pos, data) {
+        if(!_mode.nodeCrossfilter())
+            throw new Error('need nodeCrossfilter');
+        var node, callback = _mode.addNode() || promise_identity;
+        if(data)
+            node = data;
+        else {
+            node = {};
+            node[_nodeIdTag] = uuid();
+            node[_nodeLabelTag] = '';
+        }
+        if(pos)
+            fix_nodes_group.new_node(node[_nodeIdTag], node, {x: pos[0], y: pos[1]});
+        callback(node).then(function(node2) {
+            if(!node2)
+                return;
+            _mode.nodeCrossfilter().add([node2]);
+            diagram.redrawGroup();
+            select_nodes_group.set_changed([node2[_nodeIdTag]]);
+        });
+    }
+
+    function create_edge(diagram, source, target) {
+        if(!_mode.edgeCrossfilter())
+            throw new Error('need edgeCrossfilter');
+        var edge = {}, callback = _mode.addEdge() || promise_identity;
+        edge[_edgeIdTag] = uuid();
+        edge[_edgeLabelTag] = '';
+        if(_mode.conduct().detectReversedEdge && _mode.conduct().detectReversedEdge(edge, source.port, target.port)) {
+            edge[_sourceTag] = target.node.orig.key;
+            edge[_targetTag] = source.node.orig.key;
+            var t;
+            t = source; source = target; target = t;
+        } else {
+            edge[_sourceTag] = source.node.orig.key;
+            edge[_targetTag] = target.node.orig.key;
+        }
+        callback(edge, source.port, target.port).then(function(edge2) {
+            if(!edge2)
+                return;
+            fix_nodes_group.new_edge(edge[_edgeIdTag], edge2[_sourceTag], edge2[_targetTag]);
+            _mode.edgeCrossfilter().add([edge2]);
+            select_nodes_group.set_changed([], false);
+            select_edges_group.set_changed([edge2[_edgeIdTag]], false);
+            diagram.redrawGroup();
+        });
+    }
+
+    function check_invalid_drag(coords) {
+        var msg;
+        if(!(d3.event.buttons & 1)) {
+            // mouse button was released but we missed it
+            _crossout = null;
+            if(_mode.conduct().cancelDragEdge)
+                _mode.conduct().cancelDragEdge(_sourceDown);
+            erase_hint();
+            update_crossout();
+            return true;
+        }
+        if(!_sourceDown.started && Math.hypot(coords[0] - _hintData[0].source.x, coords[1] - _hintData[0].source.y) > _mode.dragSize()) {
+            if(_mode.conduct().startDragEdge) {
+                if(_mode.conduct().startDragEdge(_sourceDown)) {
+                    _sourceDown.started = true;
+                } else {
+                    if(_mode.conduct().invalidSourceMessage) {
+                        msg = _mode.conduct().invalidSourceMessage(_sourceDown);
+                        console.log(msg);
+                        if(options.negativeTip) {
+                            options.negativeTip
+                                .content(function(_, k) { k(msg); })
+                                .displayTip(_mode.usePorts() ? _sourceDown.port : _sourceDown.node);
+                        }
+                    }
+                    erase_hint();
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    function draw(diagram, node, edge, ehover) {
+        var select_nodes = diagram.child('select-nodes');
+        if(select_nodes) {
+            if(_mode.clickCreatesNodes())
+                select_nodes.clickBackgroundClears(false);
+        }
+        node
+            .on('mousedown.draw-graphs', function(n) {
+                d3.event.stopPropagation();
+                if(!_mode.dragCreatesEdges())
+                    return;
+                if(options.tipsDisable)
+                    options.tipsDisable.forEach(function(tip) {
+                        tip
+                            .hideTip()
+                            .disabled(true);
+                    });
+                if(_mode.usePorts()) {
+                    var activePort;
+                    if(typeof _mode.usePorts() === 'object' && _mode.usePorts().eventPort)
+                        activePort = _mode.usePorts().eventPort();
+                    else activePort = diagram.getPort(diagram.nodeKey.eval(n), null, 'out')
+                        || diagram.getPort(diagram.nodeKey.eval(n), null, 'in');
+                    if(!activePort)
+                        return;
+                    _sourceDown = {node: n, port: activePort};
+                    _hintData = [{source: port_pos(activePort)}];
+                } else {
+                    _sourceDown = {node: n};
+                    _hintData = [{source: {x: _sourceDown.node.cola.x, y: _sourceDown.node.cola.y}}];
+                }
+            })
+            .on('mousemove.draw-graphs', function(n) {
+                var msg;
+                d3.event.stopPropagation();
+                if(_sourceDown) {
+                    var coords = dc_graph.event_coords(diagram);
+                    if(check_invalid_drag(coords))
+                        return;
+                    var oldTarget = _targetMove;
+                    if(n === _sourceDown.node) {
+                        _mode.conduct().invalidTargetMessage &&
+                            console.log(_mode.conduct().invalidTargetMessage(_sourceDown, _sourceDown));
+                        _targetMove = null;
+                        _hintData[0].target = null;
+                    }
+                    else if(_mode.usePorts()) {
+                        var activePort;
+                        if(typeof _mode.usePorts() === 'object' && _mode.usePorts().eventPort)
+                            activePort = _mode.usePorts().eventPort();
+                        else activePort = diagram.getPort(diagram.nodeKey.eval(n), null, 'in')
+                            || diagram.getPort(diagram.nodeKey.eval(n), null, 'out');
+                        if(activePort)
+                            _targetMove = {node: n, port: activePort};
+                        else
+                            _targetMove = null;
+                    } else if(!_targetMove || n !== _targetMove.node) {
+                        _targetMove = {node: n};
+                    }
+                    if(_mode.conduct().changeDragTarget) {
+                        var change;
+                        if(_mode.usePorts()) {
+                            var oldPort = oldTarget && oldTarget.port,
+                                newPort = _targetMove && _targetMove.port;
+                            change = oldPort !== newPort;
+                        } else {
+                            var oldNode = oldTarget && oldTarget.node,
+                                newNode = _targetMove && _targetMove.node;
+                             change = oldNode !== newNode;
+                        }
+                        if(change)
+                            if(_mode.conduct().changeDragTarget(_sourceDown, _targetMove)) {
+                                _crossout = null;
+                                if(options.negativeTip)
+                                    options.negativeTip.hideTip();
+                                msg = _mode.conduct().validTargetMessage && _mode.conduct().validTargetMessage() ||
+                                    'matches';
+                                if(options.positiveTip) {
+                                    options.positiveTip
+                                        .content(function(_, k) { k(msg); })
+                                        .displayTip(_mode.usePorts() ? _targetMove.port : _targetMove.node);
+                                }
+                                _targetValid = true;
+                            } else {
+                                _crossout = _mode.usePorts() ?
+                                    _targetMove && _targetMove.port :
+                                    _targetMove && _targetMove.node;
+                                if(_targetMove && _mode.conduct().invalidTargetMessage) {
+                                    if(options.positiveTip)
+                                        options.positiveTip.hideTip();
+                                    msg = _mode.conduct().invalidTargetMessage(_sourceDown, _targetMove);
+                                    console.log(msg);
+                                    if(options.negativeTip) {
+                                        options.negativeTip
+                                            .content(function(_, k) { k(msg); })
+                                            .displayTip(_mode.usePorts() ? _targetMove.port : _targetMove.node);
+                                    }
+                                }
+                                _targetValid = false;
+                            }
+                    } else _targetValid = true;
+                    if(_targetMove) {
+                        if(_targetMove.port)
+                            _hintData[0].target = port_pos(activePort);
+                        else
+                            _hintData[0].target = {x: n.cola.x, y: n.cola.y};
+                    }
+                    else {
+                        _hintData[0].target = {x: coords[0], y: coords[1]};
+                    }
+                    update_hint();
+                    update_crossout();
+                }
+            })
+            .on('mouseup.draw-graphs', function(n) {
+                _crossout = null;
+                if(options.negativeTip)
+                    options.negativeTip.hideTip(true);
+                if(options.positiveTip)
+                    options.positiveTip.hideTip(true);
+                if(options.tipsDisable)
+                    options.tipsDisable.forEach(function(tip) {
+                        tip.disabled(false);
+                    });
+                // allow keyboard mode to hear this one (again, we need better cooperation)
+                // d3.event.stopPropagation();
+                if(_sourceDown && _targetValid) {
+                    var finishPromise;
+                    if(_mode.conduct().finishDragEdge)
+                        finishPromise = _mode.conduct().finishDragEdge(_sourceDown, _targetMove);
+                    else finishPromise = Promise.resolve(true);
+                    var source = _sourceDown, target = _targetMove;
+                    finishPromise.then(function(ok) {
+                        if(ok)
+                            create_edge(diagram, source, target);
+                    });
+                }
+                else if(_sourceDown) {
+                    if(_mode.conduct().cancelDragEdge)
+                        _mode.conduct().cancelDragEdge(_sourceDown);
+                }
+                erase_hint();
+                update_crossout();
+            });
+        diagram.svg()
+            .on('mousedown.draw-graphs', function() {
+                _sourceDown = null;
+            })
+            .on('mousemove.draw-graphs', function() {
+                var data = [];
+                if(_sourceDown) { // drawing edge
+                    var coords = dc_graph.event_coords(diagram);
+                    _crossout = null;
+                    if(check_invalid_drag(coords))
+                        return;
+                    if(_mode.conduct().dragCanvas)
+                        _mode.conduct().dragCanvas(_sourceDown, coords);
+                    if(_mode.conduct().changeDragTarget && _targetMove)
+                        _mode.conduct().changeDragTarget(_sourceDown, null);
+                    _targetMove = null;
+                    _hintData[0].target = {x: coords[0], y: coords[1]};
+                    update_hint();
+                    update_crossout();
+                }
+            })
+            .on('mouseup.draw-graphs', function() {
+                _crossout = null;
+                if(options.negativeTip)
+                    options.negativeTip.hideTip(true);
+                if(options.positiveTip)
+                    options.positiveTip.hideTip(true);
+                if(options.tipsDisable)
+                    options.tipsDisable.forEach(function(tip) {
+                        tip.disabled(false);
+                    });
+                if(_sourceDown) { // drag-edge
+                    if(_mode.conduct().cancelDragEdge)
+                        _mode.conduct().cancelDragEdge(_sourceDown);
+                    erase_hint();
+                } else { // click-node
+                    if(d3.event.target === this && _mode.clickCreatesNodes())
+                        create_node(diagram, dc_graph.event_coords(diagram));
+                }
+                update_crossout();
+            });
+        if(!_edgeLayer)
+            _edgeLayer = diagram.g().append('g').attr('class', 'draw-graphs');
+    }
+
+    function remove(diagram, node, edge, ehover) {
+        node
+            .on('mousedown.draw-graphs', null)
+            .on('mousemove.draw-graphs', null)
+            .on('mouseup.draw-graphs', null);
+        diagram.svg()
+            .on('mousedown.draw-graphs', null)
+            .on('mousemove.draw-graphs', null)
+            .on('mouseup.draw-graphs', null);
+    }
+
+    var _mode = dc_graph.mode('highlight-paths', {
+        draw: draw,
+        remove: remove
+    });
+
+    // update the data source/destination
+    _mode.nodeCrossfilter = property(options.nodeCrossfilter);
+    _mode.edgeCrossfilter = property(options.edgeCrossfilter);
+
+    // modeal options
+    _mode.usePorts = property(null);
+    _mode.clickCreatesNodes = property(true);
+    _mode.dragCreatesEdges = property(true);
+    _mode.dragSize = property(5);
+
+    // draw attributes of indicator for failed edge
+    _mode.crossSize = property(15);
+    _mode.crossWidth = property(5);
+
+    // really this is a behavior or strategy
+    _mode.conduct = property({});
+
+    // callbacks to modify data as it's being added
+    // as of 0.6, function returns a promise of the new data
+    _mode.addNode = property(null); // node -> promise(node2)
+    _mode.addEdge = property(null); // edge, sourceport, targetport -> promise(edge2)
+
+    // or, if you want to drive..
+    _mode.createNode = function(pos, data) {
+        create_node(_mode.parent(), pos, data);
+    };
+
+    return _mode;
+};
+
+
+dc_graph.match_ports = function(diagram, symbolPorts) {
+    var _ports, _wports, _wedges, _validTargets;
+    diagram.on('data.match-ports', function(diagram, nodes, wnodes, edges, wedges, ports, wports) {
+        _ports = ports;
+        _wports = wports;
+        _wedges = wedges;
+    });
+    diagram.on('transitionsStarted.match-ports', function() {
+        symbolPorts.enableHover(true);
+    });
+    function change_state(ports, state) {
+        return ports.map(function(p) {
+            p.state = state;
+            return diagram.portNodeKey.eval(p);
+        });
+    }
+    function reset_ports(source) {
+        var nids = change_state(_validTargets, 'small');
+        source.port.state = 'small';
+        nids.push(diagram.portNodeKey.eval(source.port));
+        symbolPorts.animateNodes(nids);
+    }
+    function has_parallel(sourcePort, targetPort) {
+        return _wedges.some(function(e) {
+            return sourcePort.edges.indexOf(e) >= 0 && targetPort.edges.indexOf(e) >= 0;
+        });
+    }
+    function is_valid(sourcePort, targetPort) {
+        return (_strategy.allowParallel() || !has_parallel(sourcePort, targetPort))
+            && _strategy.isValid()(sourcePort, targetPort);
+    }
+    function why_invalid(sourcePort, targetPort) {
+        return !_strategy.allowParallel() && has_parallel(sourcePort, targetPort) && "can't connect two edges between the same two ports" ||
+            _strategy.whyInvalid()(sourcePort, targetPort);
+    }
+    var _strategy = {
+        isValid: property(function(sourcePort, targetPort) {
+            return targetPort !== sourcePort && targetPort.name === sourcePort.name;
+        }),
+        whyInvalid: property(function(sourcePort, targetPort) {
+            return targetPort === sourcePort && "can't connect port to itself" ||
+                targetPort.name !== sourcePort.name && "must connect ports of the same type";
+        }),
+        allowParallel: property(false),
+        hoverPort: function(port) {
+            if(port) {
+                _validTargets = _wports.filter(is_valid.bind(null, port));
+                if(_validTargets.length)
+                    return change_state(_validTargets, 'shimmer-medium');
+            } else if(_validTargets)
+                return change_state(_validTargets, 'small');
+            return null;
+        },
+        startDragEdge: function(source) {
+            _validTargets = _wports.filter(is_valid.bind(null, source.port));
+            var nids = change_state(_validTargets, 'shimmer');
+            if(_validTargets.length) {
+                symbolPorts.enableHover(false);
+                source.port.state = 'large';
+                nids.push(diagram.portNodeKey.eval(source.port));
+                symbolPorts.animateNodes(nids);
+            }
+            console.log('valid targets', nids);
+            return _validTargets.length !== 0;
+        },
+        invalidSourceMessage: function(source) {
+            return "no valid matches for this port";
+        },
+        changeDragTarget: function(source, target) {
+            var nids, valid = target && is_valid(source.port, target.port), before;
+            if(valid) {
+                nids = change_state(_validTargets, 'small');
+                target.port.state = 'large'; // it's one of the valid
+            }
+            else {
+                nids = change_state(_validTargets, 'small');
+                before = symbolPorts.animateNodes(nids);
+                nids = change_state(_validTargets, 'shimmer');
+            }
+            symbolPorts.animateNodes(nids, before);
+            return valid;
+        },
+        validTargetMessage: function(source, target) {
+            return "it's a match!";
+        },
+        invalidTargetMessage: function(source, target) {
+            return why_invalid(source.port, target.port);
+        },
+        finishDragEdge: function(source, target) {
+            symbolPorts.enableHover(true);
+            reset_ports(source);
+            return Promise.resolve(is_valid(source.port, target.port));
+        },
+        cancelDragEdge: function(source) {
+            symbolPorts.enableHover(true);
+            reset_ports(source);
+            return true;
+        }
+    };
+    return _strategy;
+};
+
+dc_graph.match_opposites = function(diagram, deleteProps, options) {
+    options = Object.assign({
+        multiplier: 2,
+        ease: d3.ease('cubic')
+    }, options);
+    var _ports, _wports, _wedges, _validTargets;
+
+    diagram.cascade(100, true, multiply_properties(function(e) {
+        return options.ease(e.deleting || 0);
+    }, deleteProps, property_interpolate));
+    diagram.on('data.match-opposites', function(diagram, nodes, wnodes, edges, wedges, ports, wports) {
+        _ports = ports;
+        _wports = wports;
+        _wedges = wedges;
+    });
+    function port_pos(p) {
+        return { x: p.node.cola.x + p.pos.x, y: p.node.cola.y + p.pos.y };
+    }
+    function is_valid(sourcePort, targetPort) {
+        return (_strategy.allowParallel() || !_wedges.some(function(e) {
+            return sourcePort.edges.indexOf(e) >= 0 && targetPort.edges.indexOf(e) >= 0;
+        })) && _strategy.isValid()(sourcePort, targetPort);
+    }
+    function reset_deletables(source, targets) {
+        targets.forEach(function(p) {
+            p.edges.forEach(function(e) {
+                e.deleting = 0;
+            });
+        });
+        if(source)
+            source.port.edges.forEach(function(e) {
+                e.deleting = 0;
+            });
+    }
+    var _strategy = {
+        isValid: property(function(sourcePort, targetPort) {
+            // draw_graphs is already enforcing this, but this makes more sense and i use xor any chance i get
+            return (diagram.portName.eval(sourcePort) === 'in') ^ (diagram.portName.eval(targetPort) === 'in');
+        }),
+        allowParallel: property(false),
+        hoverPort: function(port) {
+            // could be called by draw_graphs when node is hovered, isn't
+        },
+        startDragEdge: function(source) {
+            _validTargets = _wports.filter(is_valid.bind(null, source.port));
+            console.log('valid targets', _validTargets.map(diagram.portNodeKey.eval));
+            return _validTargets.length !== 0;
+        },
+        dragCanvas: function(source, coords) {
+            var closest = _validTargets.map(function(p) {
+                var ppos = port_pos(p);
+                return {
+                    distance: Math.hypot(coords[0] - ppos.x, coords[1] - ppos.y),
+                    port: p
+                };
+            }).sort(function(a, b) {
+                return a.distance - b.distance;
+            });
+            var cpos = port_pos(closest[0].port), spos = port_pos(source.port);
+            closest.forEach(function(c) {
+                c.port.edges.forEach(function(e) {
+                    e.deleting = 1 - options.multiplier * c.distance / Math.hypot(cpos.x - spos.x, cpos.y - spos.y);
+                });
+            });
+            source.port.edges.forEach(function(e) {
+                e.deleting = 1 - options.multiplier * closest[0].distance / Math.hypot(cpos.x - spos.x, cpos.y - spos.y);
+            });
+            diagram.requestRefresh(0);
+        },
+        changeDragTarget: function(source, target) {
+            var valid = target && is_valid(source.port, target.port);
+            if(valid) {
+                target.port.edges.forEach(function(e) {
+                    e.deleting = 1;
+                });
+                source.port.edges.forEach(function(e) {
+                    e.deleting = 1;
+                });
+                reset_deletables(null, _validTargets.filter(function(p) {
+                    return p !== target.port;
+                }));
+                diagram.requestRefresh(0);
+            }
+            return valid;
+        },
+        finishDragEdge: function(source, target) {
+            if(is_valid(source.port, target.port)) {
+                reset_deletables(null, _validTargets.filter(function(p) {
+                    return p !== target.port;
+                }));
+                if(options.delete_edges) {
+                    var edgeKeys = source.port.edges.map(diagram.edgeKey.eval).concat(target.port.edges.map(diagram.edgeKey.eval));
+                    return options.delete_edges.deleteSelection(edgeKeys);
+                }
+                return Promise.resolve(true);
+            }
+            reset_deletables(source, _validTargets || []);
+            return Promise.resolve(false);
+        },
+        cancelDragEdge: function(source) {
+            reset_deletables(source, _validTargets || []);
+            return true;
+        },
+        detectReversedEdge: function(edge, sourcePort, targetPort) {
+            return diagram.portName.eval(sourcePort) === 'in';
+        }
+    };
+    return _strategy;
+};
+
+dc_graph.wildcard_ports = function(options) {
+    var diagram = options.diagram,
+        get_type = options.get_type || function(p) { return p.orig.value.type; },
+        set_type = options.set_type || function(p, src) { p.orig.value.type = src.orig.value.type; },
+        get_name = options.get_name || function(p) { return p.orig.value.name; },
+        is_wild = options.is_wild || function(p) { return p.orig.value.wild; },
+        update_ports = options.update_ports || function() {},
+        get_linked = options.get_linked || function() { return []; };
+    function linked_ports(n, port) {
+        if(!diagram)
+            return [];
+        var nid = diagram.nodeKey.eval(n);
+        var name = get_name(port);
+        var links = get_linked(n) || [];
+        var found = links.find(function(set) {
+            return set.includes(name);
+        });
+        if(!found) return [];
+        return found.filter(function(link) { return link !== name; }).map(function(link) {
+            return diagram.getPort(nid, null, link);
+        });
+    }
+    function no_edges(ports) {
+        return ports.every(function(lp) {
+            return lp.edges.length === 0;
+        });
+    }
+    return {
+        isValid: function(p1, p2) {
+            return get_type(p1) === null ^ get_type(p2) === null ||
+                get_type(p1) !== null && get_type(p1) === get_type(p2);
+        },
+        whyInvalid: function(p1, p2) {
+            return get_type(p1) === null && get_type(p2) === null && "can't connect wildcard to wildcard" ||
+                get_type(p1) !== get_type(p2) && "the types of ports must match";
+        },
+        copyLinked: function(n, port) {
+            linked_ports(n, port).forEach(function(lp) {
+                set_type(lp, port);
+            });
+        },
+        copyType: function(e, sport, tport) {
+            if(get_type(sport) === null) {
+                set_type(sport, tport);
+                this.copyLinked(sport.node, sport);
+                update_ports();
+            } else if(get_type(tport) === null) {
+                set_type(tport, sport);
+                this.copyLinked(tport.node, tport);
+                update_ports();
+            }
+            return Promise.resolve(e);
+        },
+        resetTypes: function(edges)  {
+            // backward compatibility: this used to take diagram as
+            // first arg, which was wrong
+            var dia = diagram;
+            if(arguments.length === 2) {
+                dia = arguments[0];
+                edges = arguments[1];
+            }
+            edges.forEach(function(eid) {
+                var e = dia.getWholeEdge(eid),
+                    spname = dia.edgeSourcePortName.eval(e),
+                    tpname = dia.edgeTargetPortName.eval(e);
+                var update = false;
+                var p = dia.getPort(dia.nodeKey.eval(e.source), null, spname);
+                var linked = linked_ports(e.source, p);
+                if(is_wild(p) && p.edges.length === 1 && no_edges(linked)) {
+                    set_type(p, null);
+                    linked.forEach(function(lp) {
+                        set_type(lp, null);
+                        update = true;
+                    });
+                }
+                p = dia.getPort(dia.nodeKey.eval(e.target), null, tpname);
+                linked = linked_ports(e.target, p);
+                if(is_wild(p) && p.edges.length === 1 && no_edges(linked)) {
+                    set_type(p, null);
+                    linked.forEach(function(lp) {
+                        set_type(lp, null);
+                        update = true;
+                    });
+                }
+                if(update)
+                    update_ports();
+            });
+            return Promise.resolve(edges);
+        }
+    };
+};
+
+dc_graph.symbol_port_style = function() {
+    var _style = {};
+    var _nodePorts, _node;
+    var _drawConduct;
+
+    _style.symbolScale = property(null);
+    _style.colorScale = property(d3.scale.ordinal().range(
+         // colorbrewer light qualitative scale
+        d3.shuffle(['#8dd3c7','#ffffb3','#bebada','#fb8072','#80b1d3','#fdb462',
+                    '#b3de69','#fccde5','#d9d9d9','#bc80bd','#ccebc5','#ffed6f'])));
+
+    function name_or_edge(p) {
+        return p.named ? p.name : _style.parent().edgeKey.eval(p.edges[0]);
+    }
+    _style.symbol = _style.portSymbol = property(name_or_edge, false); // non standard properties taking "outer datum"
+    _style.color = _style.portColor = property(name_or_edge, false);
+    _style.outline = property(dc_graph.symbol_port_style.outline.circle());
+    _style.content = property(dc_graph.symbol_port_style.content.d3symbol());
+    _style.smallRadius = _style.portRadius = property(7);
+    _style.mediumRadius = _style.portHoverNodeRadius = property(10);
+    _style.largeRadius = _style.portHoverPortRadius = property(14);
+    _style.displacement = _style.portDisplacement = property(2);
+    _style.outlineFillScale = _style.portBackgroundScale = property(null);
+    _style.outlineFill = _style.portBackgroundFill = property(null);
+    _style.outlineStroke = _style.portBackgroundStroke = property(null);
+    _style.outlineStrokeWidth = _style.portBackgroundStrokeWidth = property(null);
+    _style.padding = _style.portPadding = property(2);
+    _style.label = _style.portLabel = _style.portText = property(function(p) {
+        return p.name;
+    });
+    _style.portLabelPadding = property({x: 5, y: 5});
+    _style.cascade = cascade(_style);
+
+    _style.portPosition = function(p) {
+        var l = Math.hypot(p.pos.x, p.pos.y),
+            u = {x: p.pos.x / l, y: p.pos.y / l},
+            disp = _style.displacement.eval(p);
+        return {x: p.pos.x + disp * u.x, y: p.pos.y + disp * u.y};
+    };
+
+    _style.portBounds = function(p) {
+        var R = _style.largeRadius.eval(p),
+            pos = _style.portPosition(p);
+        return {
+            left: pos.x - R/2,
+            top: pos.y - R/2,
+            right: pos.x + R/2,
+            bottom: pos.y + R/2
+        };
+    };
+
+    function symbol_fill(p) {
+        var symcolor = _style.color.eval(p);
+        return symcolor ?
+            (_style.colorScale() ? _style.colorScale()(symcolor) : symcolor) :
+        'none';
+    }
+    function port_transform(p) {
+        var pos = _style.portPosition(p);
+        return 'translate(' + pos.x + ',' + pos.y + ')';
+    }
+    function port_symbol(p) {
+        if(!_style.symbolScale())
+            _style.symbolScale(d3.scale.ordinal().range(d3.shuffle(_style.content().enum())));
+        var symname = _style.symbol.eval(p);
+        return symname && (_style.symbolScale() ? _style.symbolScale()(symname) : symname);
+    }
+    function is_left(p) {
+        return p.vec[0] < 0;
+    }
+    function hover_radius(p) {
+        switch(p.state) {
+        case 'large':
+            return _style.largeRadius.eval(p);
+        case 'medium':
+            return _style.mediumRadius.eval(p);
+        case 'small':
+        default:
+            return _style.smallRadius.eval(p);
+        }
+    }
+    function shimmer_radius(p) {
+        return /-medium$/.test(p.state) ?
+            _style.mediumRadius.eval(p) :
+            _style.largeRadius.eval(p);
+    }
+    // fall back to node aesthetics if not defined for port
+    function outline_fill(p) {
+        var scale, fill;
+        if(_style.outlineFill.eval(p)) {
+            scale = _style.outlineFillScale() || identity;
+            fill = _style.outlineFill.eval(p);
+        }
+        else {
+            scale = _style.parent().nodeFillScale() || identity;
+            fill = _style.parent().nodeFill.eval(p.node);
+        }
+        return fill === 'none' ? 'none' : scale(fill);
+    }
+    function outline_stroke(p) {
+        return _style.outlineStroke.eval(p) || _style.parent().nodeStroke.eval(p.node);
+    }
+    function outline_stroke_width(p) {
+        var sw = _style.outlineStrokeWidth.eval(p);
+        return typeof sw === 'number' ? sw : _style.parent().nodeStrokeWidth.eval(p.node);
+    }
+    _style.animateNodes = function(nids, before) {
+        var setn = d3.set(nids);
+        var node = _node
+                .filter(function(n) {
+                    return setn.has(_style.parent().nodeKey.eval(n));
+                });
+        var symbol = _style.parent().selectNodePortsOfStyle(node, _style.parent().portStyle.nameOf(this));
+        var shimmer = symbol.filter(function(p) { return /^shimmer/.test(p.state); }),
+            nonshimmer = symbol.filter(function(p) { return !/^shimmer/.test(p.state); });
+        if(shimmer.size()) {
+            if(before)
+                before.each('end', repeat);
+            else repeat();
+        }
+
+        function repeat() {
+            var shimin = shimmer.transition()
+                    .duration(1000)
+                    .ease("bounce");
+            shimin.selectAll('.port-outline')
+                .call(_style.outline().draw(function(p) {
+                    return shimmer_radius(p) + _style.portPadding.eval(p);
+                }));
+            shimin.selectAll('.port-symbol')
+                .call(_style.content().draw(port_symbol, shimmer_radius));
+            var shimout = shimin.transition()
+                    .duration(1000)
+                    .ease('sin');
+            shimout.selectAll('.port-outline')
+                .call(_style.outline().draw(function(p) {
+                    return _style.smallRadius.eval(p) + _style.portPadding.eval(p);
+                }));
+            shimout.selectAll('.port-symbol')
+                .call(_style.content().draw(port_symbol, _style.smallRadius.eval));
+            shimout.each("end", repeat);
+        }
+
+        var trans = nonshimmer.transition()
+                .duration(250);
+        trans.selectAll('.port-outline')
+            .call(_style.outline().draw(function(p) {
+                return hover_radius(p) + _style.portPadding.eval(p);
+            }));
+        trans.selectAll('.port-symbol')
+            .call(_style.content().draw(port_symbol, hover_radius));
+
+        function text_showing(p) {
+            return p.state === 'large' || p.state === 'medium';
+        }
+        trans.selectAll('text.port-label')
+            .attr({
+                opacity: function(p) {
+                    return text_showing(p) ? 1 : 0;
+                },
+                'pointer-events': function(p) {
+                    return text_showing(p) ? 'auto' : 'none';
+                }
+            });
+        trans.selectAll('rect.port-label-background')
+            .attr('opacity', function(p) {
+                return text_showing(p) ? 1 : 0;
+            });
+        // bring all nodes which have labels showing to the front
+        _node.filter(function(n) {
+            var ports = _nodePorts[_style.parent().nodeKey.eval(n)];
+            return ports && ports.some(text_showing);
+        }).each(function() {
+            this.parentNode.appendChild(this);
+        });
+        // bring all active ports to the front
+        symbol.filter(function(p) {
+            return p.state !== 'small';
+        }).each(function() {
+            this.parentNode.appendChild(this);
+        });
+        return trans;
+    };
+    _style.eventPort = function() {
+        var parent = d3.select(d3.event.target.parentNode);
+        if(d3.event.target.parentNode.tagName === 'g' && parent.classed('port'))
+            return parent.datum();
+        return null;
+    };
+    _style.drawPorts = function(ports, nodePorts, node) {
+        _nodePorts = nodePorts; _node = node;
+        var port = ports.data(function(n) {
+            return nodePorts[_style.parent().nodeKey.eval(n)] || [];
+        }, name_or_edge);
+        port.exit().remove();
+        var portEnter = port.enter().append('g')
+            .attr({
+                class: 'port',
+                transform: port_transform
+            });
+        port.transition('port-position')
+            .duration(_style.parent().stagedDuration())
+            .delay(_style.parent().stagedDelay(false)) // need to account for enters as well
+            .attr({
+                transform: port_transform
+            });
+
+        var outline = port.selectAll('.port-outline').data(function(p) {
+            return outline_fill(p) !== 'none' ? [p] : [];
+        });
+        outline.exit().remove();
+        var outlineEnter = outline.enter().append(_style.outline().tag())
+            .attr({
+                class: 'port-outline',
+                fill: outline_fill,
+                'stroke-width': outline_stroke_width,
+                stroke: outline_stroke
+            });
+        if(_style.outline().init)
+            outlineEnter.call(_style.outline().init);
+        outlineEnter
+            .call(_style.outline().draw(function(p) {
+                return _style.smallRadius.eval(p) + _style.portPadding.eval(p);
+            }));
+        // only position and size are animated (?) - anyway these are not on the node
+        // and they are typically used to indicate selection which should be fast
+        outline
+            .attr({
+                fill: outline_fill,
+                'stroke-width': outline_stroke_width,
+                stroke: outline_stroke
+            });
+        outline.transition()
+            .duration(_style.parent().stagedDuration())
+            .delay(_style.parent().stagedDelay(false)) // need to account for enters as well
+            .call(_style.outline().draw(function(p) {
+                return _style.smallRadius.eval(p) + _style.portPadding.eval(p);
+            }));
+
+        var symbolEnter = portEnter.append(_style.content().tag())
+            .attr('class', 'port-symbol')
+            .call(_style.content().draw(port_symbol, _style.smallRadius.eval));
+
+        var symbol = port.select('.port-symbol');
+        symbol.attr('fill', symbol_fill);
+        symbol.transition()
+            .duration(_style.parent().stagedDuration())
+            .delay(_style.parent().stagedDelay(false)) // need to account for enters as well
+            .call(_style.content().draw(port_symbol, _style.smallRadius.eval));
+
+        var label = port.selectAll('text.port-label').data(function(p) {
+            return _style.portLabel.eval(p) ? [p] : [];
+        });
+        label.exit().remove();
+        var labelEnter = label.enter();
+        labelEnter.append('rect')
+            .attr({
+                class: 'port-label-background',
+                'pointer-events': 'none'
+            });
+        labelEnter.append('text')
+            .attr({
+                class: 'port-label',
+                'dominant-baseline': 'middle',
+                'pointer-events': 'none',
+                cursor: 'default',
+                opacity: 0
+            });
+        label
+            .each(function(p) {
+                p.offset = (is_left(p) ? -1 : 1) * (_style.largeRadius.eval(p) + _style.portPadding.eval(p));
+            })
+            .attr({
+                'text-anchor': function(p) {
+                    return is_left(p) ? 'end' : 'start';
+                },
+                transform: function(p) {
+                    return 'translate(' + p.offset + ',0)';
+                }
+            })
+            .text(_style.portLabel.eval)
+            .each(function(p) {
+                p.bbox = getBBoxNoThrow(this);
+            });
+        port.selectAll('rect.port-label-background')
+            .attr({
+                x: function(p) {
+                    return (p.offset < 0 ? p.offset - p.bbox.width : p.offset) - _style.portLabelPadding.eval(p).x;
+                },
+                y: function(p) {
+                    return -p.bbox.height/2 - _style.portLabelPadding.eval(p).y;
+                },
+                width: function(p) {
+                    return p.bbox.width + 2*_style.portLabelPadding.eval(p).x;
+                },
+                height: function(p) {
+                    return p.bbox.height + 2*_style.portLabelPadding.eval(p).y;
+                },
+                fill: 'white',
+                opacity: 0
+            });
+        return _style;
+    };
+
+    _style.enableHover = function(whether) {
+        if(!_drawConduct) {
+            if(_style.parent()) {
+                var draw = _style.parent().child('draw-graphs');
+                if(draw)
+                    _drawConduct = draw.conduct();
+            }
+        }
+        var namespace = 'grow-ports-' + _style.parent().portStyle.nameOf(this);
+        if(whether) {
+            _node.on('mouseover.' + namespace, function(n) {
+                var nid = _style.parent().nodeKey.eval(n);
+                var activePort = _style.eventPort();
+                if(_nodePorts[nid])
+                    _nodePorts[nid].forEach(function(p) {
+                        p.state = p === activePort ? 'large' : activePort ? 'small' : 'medium';
+                    });
+                var nids = _drawConduct && _drawConduct.hoverPort(activePort) || [];
+                nids.push(nid);
+                _style.animateNodes(nids);
+            });
+            _node.on('mouseout.' + namespace, function(n) {
+                var nid = _style.parent().nodeKey.eval(n);
+                if(_nodePorts[nid])
+                    _nodePorts[nid].forEach(function(p) {
+                        p.state = 'small';
+                    });
+                var nids = _drawConduct && _drawConduct.hoverPort(null) || [];
+                nids.push(nid);
+                _style.animateNodes(nids);
+            });
+        } else {
+            _node.on('mouseover.' + namespace, null);
+            _node.on('mouseout.' + namespace, null);
+        }
+        return _style;
+    };
+
+    _style.parent = property(null);
+    return _style;
+};
+
+dc_graph.symbol_port_style.outline = {};
+dc_graph.symbol_port_style.outline.circle = function() {
+    return {
+        tag: function() {
+            return 'circle';
+        },
+        draw: function(rf) {
+            return function(outlines) {
+                outlines.attr('r', function(p) { return rf(p); });
+            };
+        }
+    };
+};
+dc_graph.symbol_port_style.outline.square = function() {
+    return {
+        tag: function() {
+            return 'rect';
+        },
+        init: function(outlines) {
+            // crispEdges can make outline off-center from symbols
+            // outlines.attr('shape-rendering', 'crispEdges');
+        },
+        draw: function(rf) {
+            return function(outlines) {
+                outlines.attr({
+                    x: function(p) { return -rf(p); },
+                    y: function(p) { return -rf(p); },
+                    width: function(p) { return 2*rf(p); },
+                    height: function(p) { return 2*rf(p); }
+                });
+            };
+        }
+    };
+};
+dc_graph.symbol_port_style.outline.arrow = function() {
+    // offset needed for body in order to keep centroid at 0,0
+    var left_portion = 3/4 - Math.PI/8;
+    var _outline = {
+        tag: function() {
+            return 'path';
+        },
+        init: function(outlines) {
+            //outlines.attr('shape-rendering', 'crispEdges');
+        },
+        draw: function(rf) {
+            return function(outlines) {
+                outlines.attr('d', function(p) {
+                    var r = rf(p);
+                    if(!_outline.outie() || _outline.outie()(p.orig))
+                        return 'M' + -left_portion*r + ',' + -r + ' h' + r +
+                        ' l' + r + ',' + r + ' l' + -r + ',' + r +
+                        ' h' + -r +
+                        ' a' + r + ',' + r + ' 0 1,1 0,' + -2*r;
+                    else
+                        return 'M' + -(2-left_portion)*r + ',' + -r + ' h' + 2*r +
+                        ' a' + r + ',' + r + ' 0 1,1 0,' + 2*r +
+                        ' h' + -2*r +
+                        ' l' + r + ',' + -r + ' l' + -r + ',' + -r;
+                });
+            };
+        },
+        outie: property(null)
+    };
+    return _outline;
+};
+
+dc_graph.symbol_port_style.content = {};
+dc_graph.symbol_port_style.content.d3symbol = function() {
+    var _symbol = {
+        tag: function() {
+            return 'path';
+        },
+        enum: function() {
+            return d3.svg.symbolTypes;
+        },
+        draw: function(symf, rf) {
+            return function(symbols) {
+                symbols.attr('d', function(p) {
+                    var sym = symf(p), r = rf(p);
+                    return sym ? d3.svg.symbol()
+                        .type(sym)
+                        .size(r*r)
+                    () : '';
+                });
+                symbols.attr('transform', function(p) {
+                    switch(symf(p)) {
+                    case 'triangle-up':
+                        return 'translate(0, -1)';
+                    case 'triangle-down':
+                        return 'translate(0, 1)';
+                    default: return null;
+                    }
+                });
+            };
+        }
+    };
+    return _symbol;
+};
+dc_graph.symbol_port_style.content.letter = function() {
+    var _symbol = {
+        tag: function() {
+            return 'text';
+        },
+        enum: function() {
+            return d3.range(65, 91).map(String.fromCharCode);
+        },
+        draw: function(symf, rf) {
+            return function(symbols) {
+                symbols.text(symf)
+                    .attr({
+                        'dominant-baseline': 'middle',
+                        'text-anchor': 'middle'
+                    });
+                symbols.each(function(p) {
+                    if(!p.symbol_size)
+                        p.symbol_size = getBBoxNoThrow(this);
+                });
+                symbols.attr('transform', function(p) {
+                    return 'scale(' + (2*rf(p)/p.symbol_size.height) +
+                        ') translate(' + [0,2].join(',') + ')';
+                });
+            };
+        }
+    };
+    return _symbol;
+};
+
+function process_dot(callback, error, text) {
+    if(error) {
+        callback(error, null);
+        return;
+    }
+    var nodes, edges, node_cluster = {}, clusters = [];
+    if(graphlibDot.parse) { // graphlib-dot 1.1.0 (where did i get it from?)
+        var digraph = graphlibDot.parse(text);
+
+        var nodeNames = digraph.nodes();
+        nodes = new Array(nodeNames.length);
+        nodeNames.forEach(function (name, i) {
+            var node = nodes[i] = digraph._nodes[nodeNames[i]];
+            node.id = i;
+            node.name = name;
+        });
+
+        var edgeNames = digraph.edges();
+        edges = [];
+        edgeNames.forEach(function(e) {
+            var edge = digraph._edges[e];
+            edges.push(Object.assign({}, edge.value, {
+                source: digraph._nodes[edge.u].id,
+                target: digraph._nodes[edge.v].id,
+                sourcename: edge.u,
+                targetname: edge.v
+            }));
+        });
+        // TODO: if this version exists in the wild, look at how it does subgraphs/clusters
+    } else { // graphlib-dot 0.6
+        digraph = graphlibDot.read(text);
+
+        nodeNames = digraph.nodes();
+        nodes = new Array(nodeNames.length);
+        nodeNames.forEach(function (name, i) {
+            var node = nodes[i] = digraph._nodes[nodeNames[i]];
+            node.id = i;
+            node.name = name;
+        });
+
+        edges = [];
+        digraph.edges().forEach(function(e) {
+            edges.push(Object.assign({}, digraph.edge(e.v, e.w), {
+                source: digraph._nodes[e.v].id,
+                target: digraph._nodes[e.w].id,
+                sourcename: e.v,
+                targetname: e.w
+            }));
+        });
+
+        // iterative bfs for variety (recursion would work just as well)
+        var cluster_names = {};
+        var queue = digraph.children().map(function(c) { return Object.assign({parent: null, key: c}, digraph.node(c)); });
+        while(queue.length) {
+            var item = queue.shift(),
+                children = digraph.children(item.key);
+            if(children.length) {
+                clusters.push(item);
+                cluster_names[item.key] = true;
+            }
+            else
+                node_cluster[item.key] = item.parent;
+            queue = queue.concat(children.map(function(c) { return {parent: item.key, key: c}; }));
+        }
+        // clusters as nodes not currently supported
+        nodes = nodes.filter(function(n) {
+            return !cluster_names[n.name];
+        });
+    }
+    var graph = {nodes: nodes, links: edges, node_cluster: node_cluster, clusters: clusters};
+    callback(null, graph);
+}
+
+function process_dsv(callback, error, data) {
+    if(error) {
+        callback(error, null);
+        return;
+    }
+    var keys = Object.keys(data[0]);
+    var source = keys[0], target = keys[1];
+    var nodes = d3.set(data.map(function(r) { return r[source]; }));
+    data.forEach(function(r) {
+        nodes.add(r[target]);
+    });
+    nodes = nodes.values().map(function(k) { return {name: k}; });
+    callback(null, {
+        nodes: nodes,
+        links: data.map(function(r, i) {
+            return {
+                key: i,
+                sourcename: r[source],
+                targetname: r[target]
+            };
+        })
+    });
+}
+
+dc_graph.file_formats = [
+    {
+        exts: 'json',
+        mimes: 'application/json',
+        from_url: d3.json,
+        from_text: function(text, callback) {
+            callback(null, JSON.parse(text));
+        }
+    },
+    {
+        exts: ['gv', 'dot'],
+        mimes: 'text/vnd.graphviz',
+        from_url: function(url, callback) {
+            d3.text(url, process_dot.bind(null, callback));
+        },
+        from_text: function(text, callback) {
+            process_dot(callback, null, text);
+        }
+    },
+    {
+        exts: 'psv',
+        mimes: 'text/psv',
+        from_url: function(url, callback) {
+            d3.dsv('|', 'text/plain')(url, process_dsv.bind(null, callback));
+        },
+        from_text: function(text, callback) {
+            process_dsv(callback, null, d3.dsv('|').parse(text));
+        }
+    },
+    {
+        exts: 'csv',
+        mimes: 'text/csv',
+        from_url: function(url, callback) {
+            d3.csv(url, process_dsv.bind(null, callback));
+        },
+        from_text: function(text, callback) {
+            process_dsv(callback, null, d3.csv.parse(text));
+        }
+    }
+];
+
+dc_graph.match_file_format = function(filename) {
+    return dc_graph.file_formats.find(function(format) {
+        var exts = format.exts;
+        if(!Array.isArray(exts))
+            exts = [exts];
+        return exts.find(function(ext) {
+                return new RegExp('\.' + ext + '$').test(filename);
+        });
+    });
+};
+
+dc_graph.match_mime_type = function(mime) {
+    return dc_graph.file_formats.find(function(format) {
+        var mimes = format.mimes;
+        if(!Array.isArray(mimes))
+            mimes = [mimes];
+        return mimes.includes(mime);
+    });
+};
+
+function unknown_format_error(filename) {
+    var spl = filename.split('.');
+    if(spl.length)
+        return new Error('do not know how to process graph file extension ' + spl[spl.length-1]);
+    else
+        return new Error('need file extension to process graph file automatically, filename ' + filename);
+}
+
+function unknown_mime_error(mime) {
+    return new Error('do not know how to process mime type ' + mime);
+}
+
+// load a graph from various formats and return the data in consistent {nodes, links} format
+dc_graph.load_graph = function() {
+    // ignore any query parameters for checking extension
+    function ignore_query(file) {
+        if(!file)
+            return null;
+        return file.replace(/\?.*/, '');
+    }
+    var file1, file2, callback;
+    file1 = arguments[0];
+    if(arguments.length===3) {
+        file2 = arguments[1];
+        callback = arguments[2];
+    }
+    else if(arguments.length===2) {
+        callback = arguments[1];
+    }
+    else throw new Error('need two or three arguments');
+
+    if(file2) {
+        // this is not general - really titan-specific
+        queue()
+            .defer(d3.json, file1)
+            .defer(d3.json, file2)
+            .await(function(error, nodes, edges) {
+                if(error)
+                    callback(error, null);
+                else
+                    callback(null, {nodes: nodes.results, edges: edges.results});
+            });
+    }
+    else {
+        var format;
+        if(/^data:/.test(file1)) {
+            var parts = file1.slice(5).split(/,(.+)/);
+            format = dc_graph.match_mime_type(parts[0]);
+            if(format)
+                format.from_text(parts[1], callback);
+            else callback(unknown_mime_error(parts[0]));
+        } else {
+            var file1noq = ignore_query(file1);
+            format = dc_graph.match_file_format(file1noq);
+            if(format)
+                format.from_url(file1, callback);
+            else callback(unknown_format_error(file1noq));
+        }
+    }
+};
+
+dc_graph.load_graph_text = function(text, filename, callback) {
+    var format = dc_graph.match_file_format(filename);
+    if(format)
+        format.from_text(text, callback);
+    else callback(unknown_format_error(filename));
+};
+
+dc_graph.data_url = function(data) {
+    return 'data:application/json,' + JSON.stringify(data);
+};
+
+function can_get_graph_from_this(data) {
+    return (data.nodes || data.vertices) &&  (data.edges || data.links);
+}
+
+// general-purpose reader of various json-based graph formats
+// (esp but not limited to titan graph database-like formats)
+// this could be generalized a lot
+dc_graph.munge_graph = function(data, nodekeyattr, sourceattr, targetattr) {
+    // we want data = {nodes, edges} and the field names for keys; find those in common json formats
+    var nodes, edges, nka = nodekeyattr || "name",
+        sa = sourceattr || "sourcename", ta = targetattr || "targetname";
+
+    if(!can_get_graph_from_this(data)) {
+        var wrappers = ['database', 'response'];
+        var wi = wrappers.findIndex(function(f) { return data[f] && can_get_graph_from_this(data[f]); });
+        if(wi<0)
+            throw new Error("couldn't find the data!");
+        data = data[wrappers[wi]];
+    }
+    edges = data.edges || data.links;
+    nodes = data.nodes || data.vertices;
+
+    function find_attr(o, attrs) {
+        return attrs.filter(function(a) { return !!o[a]; });
+    }
+
+    //var edgekeyattr = "id";
+    var edge0 = edges[0];
+    if(edge0[sa] === undefined) {
+        var sourceattrs = sourceattr ? [sourceattr] : ['source_ecomp_uid', "node1", "source", "tail"],
+            targetattrs = targetattr ? [targetattr] : ['target_ecomp_uid', "node2", "target", "head"];
+        //var edgekeyattrs = ['id', '_id', 'ecomp_uid'];
+        var edgewrappers = ['edge'];
+        if(edge0.node0 && edge0.node1) { // specific conflict here
+            sa = 'node0';
+            ta = 'node1';
+        }
+        else {
+            var candidates = find_attr(edge0, sourceattrs);
+            if(!candidates.length) {
+                wi = edgewrappers.findIndex(function(w) {
+                    return edge0[w] && find_attr(edge0[w], sourceattrs).length;
+                });
+                if(wi<0) {
+                    if(sourceattr)
+                        throw new Error('sourceattr ' + sa + " didn't work");
+                    else
+                        throw new Error("didn't find any source attr");
+                }
+                edges = edges.map(function(e) { return e[edgewrappers[wi]]; });
+                edge0 = edges[0];
+                candidates = find_attr(edge0, sourceattrs);
+            }
+            if(candidates.length > 1)
+                console.warn('found more than one possible source attr', candidates);
+            sa = candidates[0];
+
+            candidates = find_attr(edge0, targetattrs);
+            if(!candidates.length) {
+                if(targetattr && !edge0[targetattr])
+                    throw new Error('targetattr ' + ta + " didn't work");
+                else
+                    throw new Error("didn't find any target attr");
+            }
+            if(candidates.length > 1)
+                console.warn('found more than one possible target attr', candidates);
+            ta = candidates[0];
+
+            /*
+             // we're currently assembling our own edgeid
+            candidates = find_attr(edge0, edgekeyattrs);
+            if(!candidates.length)
+                throw new Error("didn't find any edge key");
+            if(candidates.length > 1)
+                console.warn('found more than one edge key attr', candidates);
+            edgekeyattr = candidates[0];
+             */
+        }
+    }
+    var node0 = nodes[0];
+    if(node0[nka] === undefined) {
+        var nodekeyattrs = nodekeyattr ? [nodekeyattr] : ['ecomp_uid', 'id', '_id', 'key'];
+        var nodewrappers = ['vertex'];
+        candidates = find_attr(node0, nodekeyattrs);
+        if(!candidates.length) {
+            wi = nodewrappers.findIndex(function(w) {
+                return node0[w] && find_attr(node0[w], nodekeyattrs).length;
+            });
+            if(wi<0) {
+                if(nodekeyattr)
+                    throw new Error('nodekeyattr ' + nka + " didn't work");
+                else
+                    throw new Error("couldn't find the node data");
+            }
+            nodes = nodes.map(function(n) { return n[nodewrappers[wi]]; });
+            node0 = nodes[0];
+            candidates = find_attr(node0, nodekeyattrs);
+        }
+        if(candidates.length > 1)
+            console.warn('found more than one possible node key attr', candidates);
+        nka = candidates[0];
+    }
+
+    return {
+        nodes: nodes,
+        edges: edges,
+        nodekeyattr: nka,
+        sourceattr: sa,
+        targetattr: ta
+    };
+}
+
+/**
+ * `dc_graph.flat_group` implements a
+ * ["fake crossfilter group"](https://github.com/dc-js/dc.js/wiki/FAQ#fake-groups)
+ * for the case of a group which is 1:1 with the rows of the data array.
+ *
+ * Although `dc_graph` can be used with aggregated or reduced data, typically the nodes and edges
+ * are rows of two data arrays, and each row has a column which contains the unique identifier for
+ * the node or edge.
+ *
+ * @namespace flat_group
+ * @memberof dc_graph
+ * @type {{}}
+**/
+
+dc_graph.flat_group = (function() {
+    var reduce_01 = {
+        add: function(p, v) { return v; },
+        remove: function() { return null; },
+        init: function() { return null; }
+    };
+    // now we only really want to see the non-null values, so make a fake group
+    function non_null(group) {
+        return {
+            all: function() {
+                return group.all().filter(function(kv) {
+                    return kv.value !== null;
+                });
+            }
+        };
+    }
+
+    function dim_group(ndx, id_accessor) {
+        var dimension = ndx.dimension(id_accessor);
+        return {
+            crossfilter: ndx,
+            dimension: dimension,
+            group: non_null(dimension.group().reduce(reduce_01.add,
+                                                     reduce_01.remove,
+                                                     reduce_01.init))
+        };
+    }
+
+    return {
+        /**
+         * Create a crossfilter, dimension, and flat group. Returns an object containing all three.
+         *
+         *  1. If `source` is an array, create a crossfilter from it. Otherwise assume it is a
+         *  crossfilter instance.
+         *  2. Create a dimension on the crossfilter keyed by `id_accessor`
+         *  3. Create a group from the dimension, reducing to the row when it's filtered in, or
+         * `null` when it's out.
+         *  4. Wrap the group in a fake group which filters out the nulls.
+         *
+         * The resulting fake group's `.all()` method returns an array of the currently filtered-in
+         * `{key, value}` pairs where the key is `id_accessor(row)` and the value is the row.
+         * @method make
+         * @memberof dc_graph.flat_group
+         * @param {Array} source - the data array for crossfilter, or a crossfilter
+         * @param {Function} id_accessor - accessor function taking a row object and returning its
+         * unique identifier
+         * @return {Object} `{crossfilter, dimension, group}`
+         **/
+        make: function(source, id_accessor) {
+            var cf;
+            if(Array.isArray(source))
+                cf = crossfilter(source);
+            else cf = source;
+            return dim_group(cf, id_accessor);
+        },
+        /**
+         * Create a flat dimension and group from an existing crossfilter.
+         *
+         * @method another
+         * @memberof dc_graph.flat_group
+         * @deprecated use .make() instead
+         * @param {Object} ndx - crossfilter instance
+         * @param {Function} id_accessor - accessor function taking a row object and returning its
+         * unique identifier
+         * @return {Object} `{crossfilter, dimension, group}`
+         **/
+        another: deprecate_function('use .make() instead', function(cf, id_accessor) {
+            return this.make(cf, id_accessor);
+        })
+    };
+})();
+
+
+
+var convert_tree_helper = function(data, attrs, options, parent, level, inherit) {
+    level = level || 0;
+    if(attrs.length > (options.valuesByAttr ? 1 : 0)) {
+        var attr = attrs.shift();
+        var nodes = [], edges = [];
+        var children = data.map(function(v) {
+            var key = v[options.nestKey];
+            var childKey = options.nestKeysUnique ? key : uuid();
+            if(childKey) {
+                var node;
+                if(options.ancestorKeys) {
+                    inherit = inherit || {};
+                    if(attr)
+                        inherit[attr] = key;
+                    node = Object.assign({}, inherit);
+                } else node = {};
+                node[options.nodeKey] = childKey;
+                if(options.label && options.labelFun)
+                    node[options.label] = options.labelFun(key, attr, v);
+                if(options.level)
+                    node[options.level] = level+1;
+                nodes.push(node);
+                if(parent) {
+                    var edge = {};
+                    edge[options.edgeSource] = parent;
+                    edge[options.edgeTarget] = childKey;
+                    edges.push(edge);
+                }
+            }
+            var children = options.valuesByAttr ? v[attrs[0]] : v.values;
+            var recurse = convert_tree_helper(children, attrs.slice(0), options,
+                                              childKey, level+1, Object.assign({}, inherit));
+            return recurse;
+        });
+        return {nodes: Array.prototype.concat.apply(nodes, children.map(dc.pluck('nodes'))),
+                edges: Array.prototype.concat.apply(edges, children.map(dc.pluck('edges')))};
+    }
+    else return {nodes: data.map(function(v) {
+        v = Object.assign({}, v);
+        if(options.level)
+            v[options.level] = level+1;
+        return v;
+    }), edges: data.map(function(v) {
+        var edge = {};
+        edge[options.edgeSource] = parent;
+        edge[options.edgeTarget] = v[options.nodeKey];
+        return edge;
+    })};
+};
+
+dc_graph.convert_tree = function(data, attrs, options) {
+    options = Object.assign({
+        nodeKey: 'key',
+        edgeKey: 'key',
+        edgeSource: 'sourcename',
+        edgeTarget: 'targetname',
+        nestKey: 'key'
+    }, options);
+    if(Array.isArray(data))
+        return convert_tree_helper(data, attrs, options, options.root, 0, options.inherit);
+    else {
+        attrs = [''].concat(attrs);
+        return convert_tree_helper([data], attrs, options, options.root, 0, options.inherit);
+    }
+};
+
+dc_graph.convert_nest = function(nest, attrs, nodeKeyAttr, edgeSourceAttr, edgeTargetAttr, parent, inherit) {
+    return dc_graph.convert_tree(nest, attrs, {
+        nodeKey: nodeKeyAttr,
+        edgeSource: edgeSourceAttr,
+        edgeTarget: edgeTargetAttr,
+        root: parent,
+        inherit: inherit,
+        ancestorKeys: true,
+        label: 'name',
+        labelFun: function(key, attr, v) { return attr + ':' + key; },
+        level: '_level'
+    });
+};
+
+dc_graph.convert_adjacency_list = function(nodes, namesIn, namesOut) {
+    // adjacenciesAttr, edgeKeyAttr, edgeSourceAttr, edgeTargetAttr, parent, inherit) {
+    var edges = Array.prototype.concat.apply([], nodes.map(function(n) {
+        return n[namesIn.adjacencies].map(function(adj) {
+            var e = {};
+            if(namesOut.edgeKey)
+                e[namesOut.edgeKey] = uuid();
+            e[namesOut.edgeSource] = n[namesIn.nodeKey];
+            e[namesOut.edgeTarget] = namesIn.targetKey ? adj[namesIn.targetKey] : adj;
+            if(namesOut.adjacency)
+                e[namesOut.adjacency] = adj;
+            return e;
+        });
+    }));
+    return {
+        nodes: nodes,
+        edges: edges
+    };
+};
+
+
+// collapse edges between same source and target
+dc_graph.deparallelize = function(group, sourceTag, targetTag, options) {
+    options = options || {};
+    var both = options.both || false,
+        reduce = options.reduce || null;
+    return {
+        all: function() {
+            var ST = {};
+            group.all().forEach(function(kv) {
+                var source = kv.value[sourceTag],
+                    target = kv.value[targetTag];
+                var dir = both ? true : source < target;
+                var min = dir ? source : target, max = dir ? target : source;
+                ST[min] = ST[min] || {};
+                var entry;
+                if(ST[min][max]) {
+                    entry = ST[min][max];
+                    if(reduce)
+                        entry.original = reduce(entry.original, kv);
+                } else ST[min][max] = entry = {in: 0, out: 0, original: Object.assign({}, kv)};
+                if(dir)
+                    ++entry.in;
+                else
+                    ++entry.out;
+            });
+            var ret = [];
+            Object.keys(ST).forEach(function(source) {
+                Object.keys(ST[source]).forEach(function(target) {
+                    var entry = ST[source][target];
+                    entry[sourceTag] = source;
+                    entry[targetTag] = target;
+                    ret.push({key: entry.original.key, value: entry});
+                });
+            });
+            return ret;
+        }
+    };
+};
+
+dc_graph.path_reader = function(pathsgroup) {
+    var highlight_paths_group = dc_graph.register_highlight_paths_group(pathsgroup || 'highlight-paths-group');
+    var _intervals, _intervalTree, _time;
+
+    function register_path_objs(path, nop, eop) {
+        reader.elementList.eval(path).forEach(function(element) {
+            var key, paths;
+            switch(reader.elementType.eval(element)) {
+            case 'node':
+                key = reader.nodeKey.eval(element);
+                paths = nop[key] = nop[key] || [];
+                break;
+            case 'edge':
+                key = reader.edgeSource.eval(element) + '-' + reader.edgeTarget.eval(element);
+                paths = eop[key] = eop[key] || [];
+                break;
+            }
+            paths.push(path);
+        });
+    }
+
+    var reader = {
+        pathList: property(identity, false),
+        timeRange: property(null, false),
+        pathStrength: property(null, false),
+        elementList: property(identity, false),
+        elementType: property(null, false),
+        nodeKey: property(null, false),
+        edgeSource: property(null, false),
+        edgeTarget: property(null, false),
+        clear: function() {
+            highlight_paths_group.paths_changed({}, {}, []);
+        },
+        data: function(data) {
+            var nop = {}, eop = {}, allpaths = [], has_ranges;
+            reader.pathList.eval(data).forEach(function(path) {
+                if((path._range = reader.timeRange.eval(path))) { // ugh modifying user data
+                    if(has_ranges===false)
+                        throw new Error("can't have a mix of ranged and non-ranged paths");
+                    has_ranges = true;
+                } else {
+                    if(has_ranges===true)
+                        throw new Error("can't have a mix of ranged and non-ranged paths");
+                    has_ranges = false;
+                    register_path_objs(path, nop, eop);
+                }
+                allpaths.push(path);
+            });
+            if(has_ranges) {
+                _intervals = allpaths.map(function(path) {
+                    var interval = [path._range[0].getTime(), path._range[1].getTime()];
+                    interval.path = path;
+                    return interval;
+                });
+                // currently must include lysenko-interval-tree separately
+                _intervalTree = lysenkoIntervalTree(_intervals);
+                if(_time)
+                    this.setTime(_time);
+            } else {
+                _intervals = null;
+                _intervalTree = null;
+                highlight_paths_group.paths_changed(nop, eop, allpaths);
+            }
+        },
+        getIntervals: function() {
+            return _intervals;
+        },
+        setTime: function(t) {
+            if(t && _intervalTree) {
+                var paths = [], nop = {}, eop = {};
+                _intervalTree.queryPoint(t.getTime(), function(interval) {
+                    paths.push(interval.path);
+                    register_path_objs(interval.path, nop, eop);
+                });
+                highlight_paths_group.paths_changed(nop, eop, paths);
+            }
+            _time = t;
+        }
+    };
+
+    return reader;
+};
+
+
+dc_graph.path_selector = function(parent, reader, pathsgroup, chartgroup) {
+    var highlight_paths_group = dc_graph.register_highlight_paths_group(pathsgroup || 'highlight-paths-group');
+    var root = d3.select(parent).append('svg');
+    var paths_ = [];
+    var hovered = null, selected = null;
+
+    // unfortunately these functions are copied from dc_graph.highlight_paths
+    function contains_path(paths) {
+        return function(path) {
+            return paths ? paths.indexOf(path)>=0 : false;
+        };
+    }
+
+    function doesnt_contain_path(paths) {
+        var cp = contains_path(paths);
+        return function(path) {
+            return !cp(path);
+        };
+    }
+
+    function toggle_paths(pathsA, pathsB) {
+        if(!pathsA)
+            return pathsB;
+        else if(!pathsB)
+            return pathsA;
+        if(pathsB.every(contains_path(pathsA)))
+            return pathsA.filter(doesnt_contain_path(pathsB));
+        else return pathsA.concat(pathsB.filter(doesnt_contain_path(pathsA)));
+    }
+
+    // this should use the whole cascading architecture
+    // and allow customization rather than hardcoding everything
+    // in fact, you can't even reliably overlap attributes without that (so we don't)
+
+    function draw_paths(diagram, paths) {
+        if(paths.length === 0) return;
+        var xpadding = 30;
+        var space = 30;
+        var radius = 8;
+        // set the height of SVG accordingly
+        root.attr('height', 20*(paths.length+1))
+          .attr('width', xpadding+(space+2*radius)*(paths.length/2+1)+20);
+
+        root.selectAll('.path-selector').remove();
+
+        var pathlist = root.selectAll('g.path-selector').data(paths);
+        pathlist.enter()
+          .append('g')
+          .attr('class', 'path-selector')
+          .attr("transform", function(path, i) { return "translate(0, " + i*20 + ")"; })
+          .each(function(path_data, i) {
+            var nodes = path_data.element_list.filter(function(d) { return d.element_type === 'node'; });
+            // line
+            var line = d3.select(this).append('line');
+            line.attr('x1', xpadding+space)
+              .attr('y1', radius+1)
+              .attr('x2', xpadding+space*nodes.length)
+              .attr('y2', radius+1)
+              .attr('opacity', 0.4)
+              .attr('stroke-width', 5)
+              .attr('stroke', '#bdbdbd');
+
+            // dots
+            var path = d3.select(this).selectAll('circle').data(nodes);
+            path.enter()
+              .append('circle')
+              .attr('cx', function(d, i) { return xpadding+space*(i+1); })
+              .attr('cy', radius+1)
+              .attr('r', radius)
+              .attr('opacity', 0.4)
+              .attr('fill', function(d) {
+                // TODO path_selector shouldn't know the data structure of orignal node objects
+                var regeneratedNode = {key:d.property_map.ecomp_uid, value:d.property_map};
+                return diagram.nodeStroke()(regeneratedNode);
+              });
+
+            // label
+            var text = d3.select(this).append('text');
+            text.text('Path '+i)
+              .attr('class', 'path_label')
+              .attr('x', 0)
+              .attr('y', radius*1.7)
+              .on('mouseover.path-selector', function() {
+                  highlight_paths_group.hover_changed([path_data]);
+              })
+              .on('mouseout.path-selector', function() {
+                  highlight_paths_group.hover_changed(null);
+              })
+              .on('click.path-selector', function() {
+                  highlight_paths_group.select_changed(toggle_paths(selected, [path_data]));
+              });
+          });
+        pathlist.exit().transition(1000).attr('opacity', 0).remove();
+    }
+
+    function draw_hovered() {
+      var is_hovered = contains_path(hovered);
+      root.selectAll('g.path-selector')
+        .each(function(d, i) {
+          var textColor = is_hovered(d) ? '#e41a1c' : 'black';
+          var lineColor = is_hovered(d) ? 'black' : '#bdbdbd';
+          var opacity = is_hovered(d) ? '1' : '0.4';
+          d3.select(this).select('.path_label').attr('fill', textColor);
+          d3.select(this).selectAll('line')
+            .attr('stroke', lineColor)
+            .attr('opacity', opacity);
+          d3.select(this).selectAll('circle').attr('opacity', opacity);
+        });
+    }
+
+    function draw_selected() {
+        var is_selected = contains_path(selected);
+        root.selectAll('g.path-selector')
+          .each(function(d, i) {
+            var textWeight = is_selected(d) ? 'bold' : 'normal';
+            var lineColor = is_selected(d) ? 'black' : '#bdbdbd';
+            var opacity = is_selected(d) ? '1' : '0.4';
+            d3.select(this).select('.path_label')
+              .attr('font-weight', textWeight);
+            d3.select(this).selectAll('line')
+              .attr('stroke', lineColor)
+              .attr('opacity', opacity);
+            d3.select(this).selectAll('circle').attr('opacity', opacity);
+          });
+    }
+
+    highlight_paths_group
+        .on('paths_changed.path-selector', function(nop, eop, paths) {
+            hovered = selected = null;
+            paths_ = paths;
+            selector.redraw();
+        })
+        .on('hover_changed.path-selector', function(hpaths) {
+            hovered = hpaths;
+            draw_hovered();
+        })
+        .on('select_changed.path-selector', function(spaths) {
+            selected = spaths;
+            draw_selected();
+        });
+    var selector = {
+        default_text: property('Nothing here'),
+        zero_text: property('No paths'),
+        error_text: property(null),
+        queried: property(false),
+        redraw: function() {
+            draw_paths(diagram, paths_);
+            draw_hovered();
+            draw_selected();
+        },
+        render: function() {
+            this.redraw();
+            return this;
+        }
+    };
+    dc.registerChart(selector, chartgroup);
+    return selector;
+};
+
+dc_graph.node_name = function(i) {
+    // a-z, A-Z, aa-Zz, then quit
+    if(i<26)
+        return String.fromCharCode(97+i);
+    else if(i<52)
+        return String.fromCharCode(65+i-26);
+    else if(i<52*52)
+        return dc_graph.node_name(Math.floor(i/52)) + dc_graph.node_name(i%52);
+    else throw new Error("no, that's too large");
+};
+dc_graph.node_object = function(i, attrs) {
+    attrs = attrs || {};
+    return _.extend({
+        id: i,
+        name: dc_graph.node_name(i)
+    }, attrs);
+};
+
+dc_graph.edge_object = function(namef, i, j, attrs) {
+    attrs = attrs || {};
+    return _.extend({
+        source: i,
+        target: j,
+        sourcename: namef(i),
+        targetname: namef(j)
+    }, attrs);
+};
+
+dc_graph.generate = function(type, args, env, callback) {
+    var nodes, edges, i, j;
+    var nodePrefix = env.nodePrefix || '';
+    var namef = function(i) {
+        return nodes[i].name;
+    };
+    var N = args[0];
+    var linkLength = env.linkLength || 30;
+    switch(type) {
+    case 'clique':
+    case 'cliquestf':
+        nodes = new Array(N);
+        edges = [];
+        for(i = 0; i<N; ++i) {
+            nodes[i] = dc_graph.node_object(i, {circle: "A", name: nodePrefix+dc_graph.node_name(i)});
+            for(j=0; j<i; ++j)
+                edges.push(dc_graph.edge_object(namef, i, j, {notLayout: true, undirected: true}));
+        }
+        if(type==='cliquestf')
+            for(i = 0; i<N; ++i) {
+                nodes[i+N] = dc_graph.node_object(i+N);
+                nodes[i+2*N] = dc_graph.node_object(i+2*N);
+                edges.push(dc_graph.edge_object(namef, i, i+N, {undirected: true}));
+                edges.push(dc_graph.edge_object(namef, i, i+2*N, {undirected: true}));
+            }
+        break;
+    case 'wheel':
+        nodes = new Array(N);
+        for(i = 0; i < N; ++i)
+            nodes[i] = dc_graph.node_object(i, {name: nodePrefix+dc_graph.node_name(i)});
+        edges = dc_graph.wheel_edges(namef, _.range(N), N*linkLength/2);
+        var rimLength = edges[0].distance;
+        for(i = 0; i < args[1]; ++i)
+            for(j = 0; j < N; ++j) {
+                var a = j, b = (j+1)%N, t;
+                if(i%2 === 1) {
+                    t = a;
+                    a = b;
+                    b = t;
+                }
+                edges.push(dc_graph.edge_object(namef, a, b, {distance: rimLength, par: i+2}));
+            }
+        break;
+    default:
+        throw new Error("unknown generation type "+type);
+    }
+    var graph = {nodes: nodes, links: edges};
+    callback(null, graph);
+};
+
+dc_graph.wheel_edges = function(namef, nindices, R) {
+    var N = nindices.length;
+    var edges = [];
+    var strutSkip = Math.floor(N/2),
+        rimLength = 2 * R * Math.sin(Math.PI / N),
+        strutLength = 2 * R * Math.sin(strutSkip * Math.PI / N);
+    for(var i = 0; i < N; ++i)
+        edges.push(dc_graph.edge_object(namef, nindices[i], nindices[(i+1)%N], {distance: rimLength}));
+    for(i = 0; i < N/2; ++i) {
+        edges.push(dc_graph.edge_object(namef, nindices[i], nindices[(i+strutSkip)%N], {distance: strutLength}));
+        if(N%2 && i != Math.floor(N/2))
+            edges.push(dc_graph.edge_object(namef, nindices[i], nindices[(i+N-strutSkip)%N], {distance: strutLength}));
+    }
+    return edges;
+};
+
+dc_graph.random_graph = function(options) {
+    options = Object.assign({
+        ncolors: 5,
+        ndashes: 4,
+        nodeKey: 'key',
+        edgeKey: 'key',
+        sourceKey: 'sourcename',
+        targetKey: 'targetname',
+        colorTag: 'color',
+        dashTag: 'dash',
+        nodeKeyGen: function(i) { return 'n' + i; },
+        edgeKeyGen: function(i) { return 'e' + i; },
+        newComponentProb: 0.1,
+        newNodeProb: 0.9,
+        removeEdgeProb: 0.75,
+        log: false
+    }, options);
+    if(isNaN(options.newNodeProb))
+        options.newNodeProb = 0.9;
+    if(options.newNodProb <= 0)
+        options.newNodeProb = 0.1;
+    var _nodes = [], _edges = [];
+    function new_node() {
+        var n = {};
+        n[options.nodeKey] = options.nodeKeyGen(_nodes.length);
+        n[options.colorTag] = Math.floor(Math.random()*options.ncolors);
+        _nodes.push(n);
+        return n;
+    }
+    function random_node() {
+        return _nodes[Math.floor(Math.random()*_nodes.length)];
+    }
+    return {
+        nodes: function() {
+            return _nodes;
+        },
+        edges: function() {
+            return _edges;
+        },
+        generate: function(N) {
+            while(N-- > 0) {
+                var choice = Math.random();
+                var n1, n2;
+                if(!_nodes.length || choice < options.newComponentProb)
+                    n1 = new_node();
+                else
+                    n1 = random_node();
+                if(choice < options.newNodeProb)
+                    n2 = new_node();
+                else
+                    n2 = random_node();
+                if(n1 && n2) {
+                    var edge = {};
+                    edge[options.edgeKey] = options.edgeKeyGen(_edges.length);
+                    edge[options.sourceKey] = n1[options.nodeKey];
+                    edge[options.targetKey] = n2[options.nodeKey];
+                    edge[options.dashTag] = Math.floor(Math.random()*options.ndashes);
+                    if(options.log)
+                        console.log(n1[options.nodeKey] + ' -> ' + n2[options.nodeKey]);
+                    _edges.push(edge);
+                }
+            }
+        },
+        remove: function(N) {
+            while(N-- > 0) {
+                var choice = Math.random();
+                if(choice < options.removeEdgeProb)
+                    _edges.splice(Math.floor(Math.random()*_edges.length), 1);
+                else {
+                    var n = _nodes[Math.floor(Math.random()*_nodes.length)];
+                    var eis = [];
+                    _edges.forEach(function(e, ei) {
+                        if(e[options.sourceKey] === n[options.nodeKey] ||
+                           e[options.targetKey] === n[options.nodeKey])
+                            eis.push(ei);
+                    });
+                    eis.reverse().forEach(function(ei) {
+                        _edges.splice(ei, 1);
+                    });
+                }
+            }
+        }
+    };
+};
+
+dc_graph.supergraph = function(data, options) {
+    if(!dc_graph.supergraph.pattern) {
+        var mg = metagraph;
+        var graph_and_subgraph = {
+            nodes: {
+                graph: mg.graph_pattern(options),
+                sg: mg.subgraph_pattern(options),
+                subgraph: mg.graph_pattern(options)
+            },
+            edges: {
+                to_sg: {
+                    source: 'graph',
+                    target: 'sg',
+                    input: 'parent'
+                },
+                from_sg: {
+                    source: 'subgraph',
+                    target: 'sg',
+                    input: 'child'
+                }
+            }
+        };
+        dc_graph.supergraph.pattern = mg.compose(mg.graph_detect(graph_and_subgraph));
+    }
+    return dc_graph.supergraph.pattern.node('graph.Graph').value().create(data);
+};
+
+var dont_use_key = deprecation_warning('dc_graph.line_breaks now takes a string - d.key behavior is deprecated and will be removed in a later version');
+
+dc_graph.line_breaks = function(charexp, max_line_length) {
+    var regexp = new RegExp(charexp, 'g');
+    return function(s) {
+        if(typeof s === 'object') { // backward compatibility
+            dont_use_key();
+            s = s.key;
+        }
+        var result;
+        var line = '', lines = [], part, i = 0;
+        do {
+            result = regexp.exec(s);
+            if(result)
+                part = s.slice(i, regexp.lastIndex);
+            else
+                part = s.slice(i);
+            if(line.length + part.length > max_line_length && line.length > 0) {
+                lines.push(line);
+                line = '';
+            }
+            line += part;
+            i = regexp.lastIndex;
+        }
+        while(result !== null);
+        lines.push(line);
+        return lines;
+    };
+};
+
+dc_graph.build_type_graph = function(nodes, edges, nkey, ntype, esource, etarget) {
+    var nmap = {}, tnodes = {}, tedges = {};
+    nodes.forEach(function(n) {
+        nmap[nkey(n)] = n;
+        var t = ntype(n);
+        if(!tnodes[t])
+            tnodes[t] = {type: t};
+    });
+    edges.forEach(function(e) {
+        var source = esource(e), target = etarget(e), sn, tn;
+        if(!(sn = nmap[source]))
+            throw new Error('source key ' + source + ' not found!');
+        if(!(tn = nmap[target]))
+            throw new Error('target key ' + target + ' not found!');
+        var etype = ntype(sn) + '/' + ntype(tn);
+        if(!tedges[etype])
+            tedges[etype] = {
+                type: etype,
+                source: ntype(sn),
+                target: ntype(tn)
+            };
+    });
+    return {
+        nodes: Object.keys(tnodes).map(function(k) { return tnodes[k]; }),
+        edges: Object.keys(tedges).map(function(k) { return tedges[k]; })
+    };
+}
+
+dc_graph.d3 = d3;
+dc_graph.crossfilter = crossfilter;
+dc_graph.dc = dc;
+
+return dc_graph;
+}
+    if (typeof define === 'function' && define.amd) {
+        define(["d3", "crossfilter", "dc"], _dc_graph);
+    } else if (typeof module == "object" && module.exports) {
+        var _d3 = require('d3');
+        var _crossfilter = require('crossfilter');
+        if (typeof _crossfilter !== "function") {
+            _crossfilter = _crossfilter.crossfilter;
+        }
+        var _dc = require('dc');
+        module.exports = _dc_graph(_d3, _crossfilter, _dc);
+    } else {
+        this.dc_graph = _dc_graph(d3, crossfilter, dc);
+    }
+}
+)();
+
+//# sourceMappingURL=dc.graph.js.map
\ No newline at end of file
diff --git a/src/legacy/design-studio/js/dc.js b/src/legacy/design-studio/js/dc.js
new file mode 100644
index 0000000000000000000000000000000000000000..81ae4c8447a68f54563c5f58ba56a83192753a1d
--- /dev/null
+++ b/src/legacy/design-studio/js/dc.js
@@ -0,0 +1,10777 @@
+/*!
+ *  dc 2.0.5
+ *  http://dc-js.github.io/dc.js/
+ *  Copyright 2012-2016 Nick Zhu & the dc.js Developers
+ *  https://github.com/dc-js/dc.js/blob/master/AUTHORS
+ *
+ *  Licensed 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.
+ */
+(function() { function _dc(d3, crossfilter) {
+'use strict';
+
+/**
+ * The entire dc.js library is scoped under the **dc** name space. It does not introduce
+ * anything else into the global name space.
+ *
+ * Most `dc` functions are designed to allow function chaining, meaning they return the current chart
+ * instance whenever it is appropriate.  The getter forms of functions do not participate in function
+ * chaining because they return values that are not the chart, although some,
+ * such as {@link dc.baseMixin#svg .svg} and {@link dc.coordinateGridMixin#xAxis .xAxis},
+ * return values that are themselves chainable d3 objects.
+ * @namespace dc
+ * @version 2.0.5
+ * @example
+ * // Example chaining
+ * chart.width(300)
+ *      .height(300)
+ *      .filter('sunday');
+ */
+/*jshint -W079*/
+var dc = {
+    version: '2.0.5',
+    constants: {
+        CHART_CLASS: 'dc-chart',
+        DEBUG_GROUP_CLASS: 'debug',
+        STACK_CLASS: 'stack',
+        DESELECTED_CLASS: 'deselected',
+        SELECTED_CLASS: 'selected',
+        NODE_INDEX_NAME: '__index__',
+        GROUP_INDEX_NAME: '__group_index__',
+        DEFAULT_CHART_GROUP: '__default_chart_group__',
+        EVENT_DELAY: 40,
+        NEGLIGIBLE_NUMBER: 1e-10
+    },
+    _renderlet: null
+};
+/*jshint +W079*/
+
+/**
+ * The dc.chartRegistry object maintains sets of all instantiated dc.js charts under named groups
+ * and the default group.
+ *
+ * A chart group often corresponds to a crossfilter instance. It specifies
+ * the set of charts which should be updated when a filter changes on one of the charts or when the
+ * global functions {@link dc.filterAll dc.filterAll}, {@link dc.refocusAll dc.refocusAll},
+ * {@link dc.renderAll dc.renderAll}, {@link dc.redrawAll dc.redrawAll}, or chart functions
+ * {@link dc.baseMixin#renderGroup baseMixin.renderGroup},
+ * {@link dc.baseMixin#redrawGroup baseMixin.redrawGroup} are called.
+ *
+ * @namespace chartRegistry
+ * @memberof dc
+ * @type {{has, register, deregister, clear, list}}
+ */
+dc.chartRegistry = (function () {
+    // chartGroup:string => charts:array
+    var _chartMap = {};
+
+    function initializeChartGroup (group) {
+        if (!group) {
+            group = dc.constants.DEFAULT_CHART_GROUP;
+        }
+
+        if (!_chartMap[group]) {
+            _chartMap[group] = [];
+        }
+
+        return group;
+    }
+
+    return {
+        /**
+         * Determine if a given chart instance resides in any group in the registry.
+         * @method has
+         * @memberof dc.chartRegistry
+         * @param {Object} chart dc.js chart instance
+         * @returns {Boolean}
+         */
+        has: function (chart) {
+            for (var e in _chartMap) {
+                if (_chartMap[e].indexOf(chart) >= 0) {
+                    return true;
+                }
+            }
+            return false;
+        },
+
+        /**
+         * Add given chart instance to the given group, creating the group if necessary.
+         * If no group is provided, the default group `dc.constants.DEFAULT_CHART_GROUP` will be used.
+         * @method register
+         * @memberof dc.chartRegistry
+         * @param {Object} chart dc.js chart instance
+         * @param {String} [group] Group name
+         */
+        register: function (chart, group) {
+            group = initializeChartGroup(group);
+            _chartMap[group].push(chart);
+        },
+
+        /**
+         * Remove given chart instance from the given group, creating the group if necessary.
+         * If no group is provided, the default group `dc.constants.DEFAULT_CHART_GROUP` will be used.
+         * @method deregister
+         * @memberof dc.chartRegistry
+         * @param {Object} chart dc.js chart instance
+         * @param {String} [group] Group name
+         */
+        deregister: function (chart, group) {
+            group = initializeChartGroup(group);
+            for (var i = 0; i < _chartMap[group].length; i++) {
+                if (_chartMap[group][i].anchorName() === chart.anchorName()) {
+                    _chartMap[group].splice(i, 1);
+                    break;
+                }
+            }
+        },
+
+        /**
+         * Clear given group if one is provided, otherwise clears all groups.
+         * @method clear
+         * @memberof dc.chartRegistry
+         * @param {String} group Group name
+         */
+        clear: function (group) {
+            if (group) {
+                delete _chartMap[group];
+            } else {
+                _chartMap = {};
+            }
+        },
+
+        /**
+         * Get an array of each chart instance in the given group.
+         * If no group is provided, the charts in the default group are returned.
+         * @method list
+         * @memberof dc.chartRegistry
+         * @param {String} [group] Group name
+         * @returns {Array<Object>}
+         */
+        list: function (group) {
+            group = initializeChartGroup(group);
+            return _chartMap[group];
+        }
+    };
+})();
+
+/**
+ * Add given chart instance to the given group, creating the group if necessary.
+ * If no group is provided, the default group `dc.constants.DEFAULT_CHART_GROUP` will be used.
+ * @memberof dc
+ * @method registerChart
+ * @param {Object} chart dc.js chart instance
+ * @param {String} [group] Group name
+ */
+dc.registerChart = function (chart, group) {
+    dc.chartRegistry.register(chart, group);
+};
+
+/**
+ * Remove given chart instance from the given group, creating the group if necessary.
+ * If no group is provided, the default group `dc.constants.DEFAULT_CHART_GROUP` will be used.
+ * @memberof dc
+ * @method deregisterChart
+ * @param {Object} chart dc.js chart instance
+ * @param {String} [group] Group name
+ */
+dc.deregisterChart = function (chart, group) {
+    dc.chartRegistry.deregister(chart, group);
+};
+
+/**
+ * Determine if a given chart instance resides in any group in the registry.
+ * @memberof dc
+ * @method hasChart
+ * @param {Object} chart dc.js chart instance
+ * @returns {Boolean}
+ */
+dc.hasChart = function (chart) {
+    return dc.chartRegistry.has(chart);
+};
+
+/**
+ * Clear given group if one is provided, otherwise clears all groups.
+ * @memberof dc
+ * @method deregisterAllCharts
+ * @param {String} group Group name
+ */
+dc.deregisterAllCharts = function (group) {
+    dc.chartRegistry.clear(group);
+};
+
+/**
+ * Clear all filters on all charts within the given chart group. If the chart group is not given then
+ * only charts that belong to the default chart group will be reset.
+ * @memberof dc
+ * @method filterAll
+ * @param {String} [group]
+ */
+dc.filterAll = function (group) {
+    var charts = dc.chartRegistry.list(group);
+    for (var i = 0; i < charts.length; ++i) {
+        charts[i].filterAll();
+    }
+};
+
+/**
+ * Reset zoom level / focus on all charts that belong to the given chart group. If the chart group is
+ * not given then only charts that belong to the default chart group will be reset.
+ * @memberof dc
+ * @method refocusAll
+ * @param {String} [group]
+ */
+dc.refocusAll = function (group) {
+    var charts = dc.chartRegistry.list(group);
+    for (var i = 0; i < charts.length; ++i) {
+        if (charts[i].focus) {
+            charts[i].focus();
+        }
+    }
+};
+
+/**
+ * Re-render all charts belong to the given chart group. If the chart group is not given then only
+ * charts that belong to the default chart group will be re-rendered.
+ * @memberof dc
+ * @method renderAll
+ * @param {String} [group]
+ */
+dc.renderAll = function (group) {
+    var charts = dc.chartRegistry.list(group);
+    for (var i = 0; i < charts.length; ++i) {
+        charts[i].render();
+    }
+
+    if (dc._renderlet !== null) {
+        dc._renderlet(group);
+    }
+};
+
+/**
+ * Redraw all charts belong to the given chart group. If the chart group is not given then only charts
+ * that belong to the default chart group will be re-drawn. Redraw is different from re-render since
+ * when redrawing dc tries to update the graphic incrementally, using transitions, instead of starting
+ * from scratch.
+ * @memberof dc
+ * @method redrawAll
+ * @param {String} [group]
+ */
+dc.redrawAll = function (group) {
+    var charts = dc.chartRegistry.list(group);
+    for (var i = 0; i < charts.length; ++i) {
+        charts[i].redraw();
+    }
+
+    if (dc._renderlet !== null) {
+        dc._renderlet(group);
+    }
+};
+
+/**
+ * If this boolean is set truthy, all transitions will be disabled, and changes to the charts will happen
+ * immediately.
+ * @memberof dc
+ * @member disableTransitions
+ * @type {Boolean}
+ * @default false
+ */
+dc.disableTransitions = false;
+
+/**
+ * Start a transition on a selection if transitions are globally enabled
+ * ({@link dc.disableTransitions} is false) and the duration is greater than zero; otherwise return
+ * the selection. Since most operations are the same on a d3 selection and a d3 transition, this
+ * allows a common code path for both cases.
+ * @memberof dc
+ * @method transition
+ * @param {d3.selection} selection - the selection to be transitioned
+ * @param {Number|Function} [duration=250] - the duration of the transition in milliseconds, a
+ * function returning the duration, or 0 for no transition
+ * @param {Number|Function} [delay] - the delay of the transition in milliseconds, or a function
+ * returning the delay, or 0 for no delay
+ * @param {String} [name] - the name of the transition (if concurrent transitions on the same
+ * elements are needed)
+ * @returns {d3.transition|d3.selection}
+ */
+dc.transition = function (selection, duration, delay, name) {
+    if (dc.disableTransitions || duration <= 0) {
+        return selection;
+    }
+
+    var s = selection.transition(name);
+
+    if (duration >= 0 || duration !== undefined) {
+        s = s.duration(duration);
+    }
+    if (delay >= 0 || delay !== undefined) {
+        s = s.delay(delay);
+    }
+
+    return s;
+};
+
+/* somewhat silly, but to avoid duplicating logic */
+dc.optionalTransition = function (enable, duration, delay, name) {
+    if (enable) {
+        return function (selection) {
+            return dc.transition(selection, duration, delay, name);
+        };
+    } else {
+        return function (selection) {
+            return selection;
+        };
+    }
+};
+
+// See http://stackoverflow.com/a/20773846
+dc.afterTransition = function (transition, callback) {
+    if (transition.empty() || !transition.duration) {
+        callback.call(transition);
+    } else {
+        var n = 0;
+        transition
+            .each(function () { ++n; })
+            .each('end', function () {
+                if (!--n) {
+                    callback.call(transition);
+                }
+            });
+    }
+};
+
+/**
+ * @namespace units
+ * @memberof dc
+ * @type {{}}
+ */
+dc.units = {};
+
+/**
+ * The default value for {@link dc.coordinateGridMixin#xUnits .xUnits} for the
+ * {@link dc.coordinateGridMixin Coordinate Grid Chart} and should
+ * be used when the x values are a sequence of integers.
+ * It is a function that counts the number of integers in the range supplied in its start and end parameters.
+ * @method integers
+ * @memberof dc.units
+ * @see {@link dc.coordinateGridMixin#xUnits coordinateGridMixin.xUnits}
+ * @example
+ * chart.xUnits(dc.units.integers) // already the default
+ * @param {Number} start
+ * @param {Number} end
+ * @returns {Number}
+ */
+dc.units.integers = function (start, end) {
+    return Math.abs(end - start);
+};
+
+/**
+ * This argument can be passed to the {@link dc.coordinateGridMixin#xUnits .xUnits} function of the to
+ * specify ordinal units for the x axis. Usually this parameter is used in combination with passing
+ * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Ordinal-Scales.md d3.scale.ordinal} to
+ * {@link dc.coordinateGridMixin#x .x}.
+ * It just returns the domain passed to it, which for ordinal charts is an array of all values.
+ * @method ordinal
+ * @memberof dc.units
+ * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Ordinal-Scales.md d3.scale.ordinal}
+ * @see {@link dc.coordinateGridMixin#xUnits coordinateGridMixin.xUnits}
+ * @see {@link dc.coordinateGridMixin#x coordinateGridMixin.x}
+ * @example
+ * chart.xUnits(dc.units.ordinal)
+ *      .x(d3.scale.ordinal())
+ * @param {*} start
+ * @param {*} end
+ * @param {Array<String>} domain
+ * @returns {Array<String>}
+ */
+dc.units.ordinal = function (start, end, domain) {
+    return domain;
+};
+
+/**
+ * @namespace fp
+ * @memberof dc.units
+ * @type {{}}
+ */
+dc.units.fp = {};
+/**
+ * This function generates an argument for the {@link dc.coordinateGridMixin Coordinate Grid Chart}
+ * {@link dc.coordinateGridMixin#xUnits .xUnits} function specifying that the x values are floating-point
+ * numbers with the given precision.
+ * The returned function determines how many values at the given precision will fit into the range
+ * supplied in its start and end parameters.
+ * @method precision
+ * @memberof dc.units.fp
+ * @see {@link dc.coordinateGridMixin#xUnits coordinateGridMixin.xUnits}
+ * @example
+ * // specify values (and ticks) every 0.1 units
+ * chart.xUnits(dc.units.fp.precision(0.1)
+ * // there are 500 units between 0.5 and 1 if the precision is 0.001
+ * var thousandths = dc.units.fp.precision(0.001);
+ * thousandths(0.5, 1.0) // returns 500
+ * @param {Number} precision
+ * @returns {Function} start-end unit function
+ */
+dc.units.fp.precision = function (precision) {
+    var _f = function (s, e) {
+        var d = Math.abs((e - s) / _f.resolution);
+        if (dc.utils.isNegligible(d - Math.floor(d))) {
+            return Math.floor(d);
+        } else {
+            return Math.ceil(d);
+        }
+    };
+    _f.resolution = precision;
+    return _f;
+};
+
+dc.round = {};
+dc.round.floor = function (n) {
+    return Math.floor(n);
+};
+dc.round.ceil = function (n) {
+    return Math.ceil(n);
+};
+dc.round.round = function (n) {
+    return Math.round(n);
+};
+
+dc.override = function (obj, functionName, newFunction) {
+    var existingFunction = obj[functionName];
+    obj['_' + functionName] = existingFunction;
+    obj[functionName] = newFunction;
+};
+
+dc.renderlet = function (_) {
+    if (!arguments.length) {
+        return dc._renderlet;
+    }
+    dc._renderlet = _;
+    return dc;
+};
+
+dc.instanceOfChart = function (o) {
+    return o instanceof Object && o.__dcFlag__ && true;
+};
+
+dc.errors = {};
+
+dc.errors.Exception = function (msg) {
+    var _msg = msg || 'Unexpected internal error';
+
+    this.message = _msg;
+
+    this.toString = function () {
+        return _msg;
+    };
+    this.stack = (new Error()).stack;
+};
+dc.errors.Exception.prototype = Object.create(Error.prototype);
+dc.errors.Exception.prototype.constructor = dc.errors.Exception;
+
+dc.errors.InvalidStateException = function () {
+    dc.errors.Exception.apply(this, arguments);
+};
+
+dc.errors.InvalidStateException.prototype = Object.create(dc.errors.Exception.prototype);
+dc.errors.InvalidStateException.prototype.constructor = dc.errors.InvalidStateException;
+
+dc.errors.BadArgumentException = function () {
+    dc.errors.Exception.apply(this, arguments);
+};
+
+dc.errors.BadArgumentException.prototype = Object.create(dc.errors.Exception.prototype);
+dc.errors.BadArgumentException.prototype.constructor = dc.errors.BadArgumentException;
+
+/**
+ * The default date format for dc.js
+ * @name dateFormat
+ * @memberof dc
+ * @type {Function}
+ * @default d3.time.format('%m/%d/%Y')
+ */
+dc.dateFormat = d3.time.format('%m/%d/%Y');
+
+/**
+ * @namespace printers
+ * @memberof dc
+ * @type {{}}
+ */
+dc.printers = {};
+
+/**
+ * Converts a list of filters into a readable string.
+ * @method filters
+ * @memberof dc.printers
+ * @param {Array<dc.filters>} filters
+ * @returns {String}
+ */
+dc.printers.filters = function (filters) {
+    var s = '';
+
+    for (var i = 0; i < filters.length; ++i) {
+        if (i > 0) {
+            s += ', ';
+        }
+        s += dc.printers.filter(filters[i]);
+    }
+
+    return s;
+};
+
+/**
+ * Converts a filter into a readable string.
+ * @method filter
+ * @memberof dc.printers
+ * @param {dc.filters|any|Array<any>} filter
+ * @returns {String}
+ */
+dc.printers.filter = function (filter) {
+    var s = '';
+
+    if (typeof filter !== 'undefined' && filter !== null) {
+        if (filter instanceof Array) {
+            if (filter.length >= 2) {
+                s = '[' + dc.utils.printSingleValue(filter[0]) + ' -> ' + dc.utils.printSingleValue(filter[1]) + ']';
+            } else if (filter.length >= 1) {
+                s = dc.utils.printSingleValue(filter[0]);
+            }
+        } else {
+            s = dc.utils.printSingleValue(filter);
+        }
+    }
+
+    return s;
+};
+
+/**
+ * Returns a function that given a string property name, can be used to pluck the property off an object.  A function
+ * can be passed as the second argument to also alter the data being returned.
+ *
+ * This can be a useful shorthand method to create accessor functions.
+ * @method pluck
+ * @memberof dc
+ * @example
+ * var xPluck = dc.pluck('x');
+ * var objA = {x: 1};
+ * xPluck(objA) // 1
+ * @example
+ * var xPosition = dc.pluck('x', function (x, i) {
+ *     // `this` is the original datum,
+ *     // `x` is the x property of the datum,
+ *     // `i` is the position in the array
+ *     return this.radius + x;
+ * });
+ * dc.selectAll('.circle').data(...).x(xPosition);
+ * @param {String} n
+ * @param {Function} [f]
+ * @returns {Function}
+ */
+dc.pluck = function (n, f) {
+    if (!f) {
+        return function (d) { return d[n]; };
+    }
+    return function (d, i) { return f.call(d, d[n], i); };
+};
+
+/**
+ * @namespace utils
+ * @memberof dc
+ * @type {{}}
+ */
+dc.utils = {};
+
+/**
+ * Print a single value filter.
+ * @method printSingleValue
+ * @memberof dc.utils
+ * @param {any} filter
+ * @returns {String}
+ */
+dc.utils.printSingleValue = function (filter) {
+    var s = '' + filter;
+
+    if (filter instanceof Date) {
+        s = dc.dateFormat(filter);
+    } else if (typeof(filter) === 'string') {
+        s = filter;
+    } else if (dc.utils.isFloat(filter)) {
+        s = dc.utils.printSingleValue.fformat(filter);
+    } else if (dc.utils.isInteger(filter)) {
+        s = Math.round(filter);
+    }
+
+    return s;
+};
+dc.utils.printSingleValue.fformat = d3.format('.2f');
+
+/**
+ * Arbitrary add one value to another.
+ * @method add
+ * @memberof dc.utils
+ * @todo
+ * These assume than any string r is a percentage (whether or not it includes %).
+ * They also generate strange results if l is a string.
+ * @param {String|Date|Number} l the value to modify
+ * @param {Number} r the amount by which to modify the value
+ * @param {String} [t] if `l` is a `Date`, the
+ * [interval](https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Intervals.md#interval) in
+ * the `d3.time` namespace
+ * @returns {String|Date|Number}
+ */
+dc.utils.add = function (l, r, t) {
+    if (typeof r === 'string') {
+        r = r.replace('%', '');
+    }
+
+    if (l instanceof Date) {
+        if (typeof r === 'string') {
+            r = +r;
+        }
+        if (t === 'millis') {
+            return new Date(l.getTime() + r);
+        }
+        t = t || 'day';
+        return d3.time[t].offset(l, r);
+    } else if (typeof r === 'string') {
+        var percentage = (+r / 100);
+        return l > 0 ? l * (1 + percentage) : l * (1 - percentage);
+    } else {
+        return l + r;
+    }
+};
+
+/**
+ * Arbitrary subtract one value from another.
+ * @method subtract
+ * @memberof dc.utils
+ * @todo
+ * These assume than any string r is a percentage (whether or not it includes %).
+ * They also generate strange results if l is a string.
+ * @param {String|Date|Number} l the value to modify
+ * @param {Number} r the amount by which to modify the value
+ * @param {String} [t] if `l` is a `Date`, the
+ * [interval](https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Intervals.md#interval) in
+ * the `d3.time` namespace
+ * @returns {String|Date|Number}
+ */
+dc.utils.subtract = function (l, r, t) {
+    if (typeof r === 'string') {
+        r = r.replace('%', '');
+    }
+
+    if (l instanceof Date) {
+        if (typeof r === 'string') {
+            r = +r;
+        }
+        if (t === 'millis') {
+            return new Date(l.getTime() - r);
+        }
+        t = t || 'day';
+        return d3.time[t].offset(l, -r);
+    } else if (typeof r === 'string') {
+        var percentage = (+r / 100);
+        return l < 0 ? l * (1 + percentage) : l * (1 - percentage);
+    } else {
+        return l - r;
+    }
+};
+
+/**
+ * Is the value a number?
+ * @method isNumber
+ * @memberof dc.utils
+ * @param {any} n
+ * @returns {Boolean}
+ */
+dc.utils.isNumber = function (n) {
+    return n === +n;
+};
+
+/**
+ * Is the value a float?
+ * @method isFloat
+ * @memberof dc.utils
+ * @param {any} n
+ * @returns {Boolean}
+ */
+dc.utils.isFloat = function (n) {
+    return n === +n && n !== (n | 0);
+};
+
+/**
+ * Is the value an integer?
+ * @method isInteger
+ * @memberof dc.utils
+ * @param {any} n
+ * @returns {Boolean}
+ */
+dc.utils.isInteger = function (n) {
+    return n === +n && n === (n | 0);
+};
+
+/**
+ * Is the value very close to zero?
+ * @method isNegligible
+ * @memberof dc.utils
+ * @param {any} n
+ * @returns {Boolean}
+ */
+dc.utils.isNegligible = function (n) {
+    return !dc.utils.isNumber(n) || (n < dc.constants.NEGLIGIBLE_NUMBER && n > -dc.constants.NEGLIGIBLE_NUMBER);
+};
+
+/**
+ * Ensure the value is no greater or less than the min/max values.  If it is return the boundary value.
+ * @method clamp
+ * @memberof dc.utils
+ * @param {any} val
+ * @param {any} min
+ * @param {any} max
+ * @returns {any}
+ */
+dc.utils.clamp = function (val, min, max) {
+    return val < min ? min : (val > max ? max : val);
+};
+
+/**
+ * Using a simple static counter, provide a unique integer id.
+ * @method uniqueId
+ * @memberof dc.utils
+ * @returns {Number}
+ */
+var _idCounter = 0;
+dc.utils.uniqueId = function () {
+    return ++_idCounter;
+};
+
+/**
+ * Convert a name to an ID.
+ * @method nameToId
+ * @memberof dc.utils
+ * @param {String} name
+ * @returns {String}
+ */
+dc.utils.nameToId = function (name) {
+    return name.toLowerCase().replace(/[\s]/g, '_').replace(/[\.']/g, '');
+};
+
+/**
+ * Append or select an item on a parent element.
+ * @method appendOrSelect
+ * @memberof dc.utils
+ * @param {d3.selection} parent
+ * @param {String} selector
+ * @param {String} tag
+ * @returns {d3.selection}
+ */
+dc.utils.appendOrSelect = function (parent, selector, tag) {
+    tag = tag || selector;
+    var element = parent.select(selector);
+    if (element.empty()) {
+        element = parent.append(tag);
+    }
+    return element;
+};
+
+/**
+ * Return the number if the value is a number; else 0.
+ * @method safeNumber
+ * @memberof dc.utils
+ * @param {Number|any} n
+ * @returns {Number}
+ */
+dc.utils.safeNumber = function (n) { return dc.utils.isNumber(+n) ? +n : 0;};
+
+dc.logger = {};
+
+dc.logger.enableDebugLog = false;
+
+dc.logger.warn = function (msg) {
+    if (console) {
+        if (console.warn) {
+            console.warn(msg);
+        } else if (console.log) {
+            console.log(msg);
+        }
+    }
+
+    return dc.logger;
+};
+
+dc.logger.debug = function (msg) {
+    if (dc.logger.enableDebugLog && console) {
+        if (console.debug) {
+            console.debug(msg);
+        } else if (console.log) {
+            console.log(msg);
+        }
+    }
+
+    return dc.logger;
+};
+
+dc.logger.deprecate = function (fn, msg) {
+    // Allow logging of deprecation
+    var warned = false;
+    function deprecated () {
+        if (!warned) {
+            dc.logger.warn(msg);
+            warned = true;
+        }
+        return fn.apply(this, arguments);
+    }
+    return deprecated;
+};
+
+dc.events = {
+    current: null
+};
+
+/**
+ * This function triggers a throttled event function with a specified delay (in milli-seconds).  Events
+ * that are triggered repetitively due to user interaction such brush dragging might flood the library
+ * and invoke more renders than can be executed in time. Using this function to wrap your event
+ * function allows the library to smooth out the rendering by throttling events and only responding to
+ * the most recent event.
+ * @name events.trigger
+ * @memberof dc
+ * @example
+ * chart.on('renderlet', function(chart) {
+ *     // smooth the rendering through event throttling
+ *     dc.events.trigger(function(){
+ *         // focus some other chart to the range selected by user on this chart
+ *         someOtherChart.focus(chart.filter());
+ *     });
+ * })
+ * @param {Function} closure
+ * @param {Number} [delay]
+ */
+dc.events.trigger = function (closure, delay) {
+    if (!delay) {
+        closure();
+        return;
+    }
+
+    dc.events.current = closure;
+
+    setTimeout(function () {
+        if (closure === dc.events.current) {
+            closure();
+        }
+    }, delay);
+};
+
+/**
+ * The dc.js filters are functions which are passed into crossfilter to chose which records will be
+ * accumulated to produce values for the charts.  In the crossfilter model, any filters applied on one
+ * dimension will affect all the other dimensions but not that one.  dc always applies a filter
+ * function to the dimension; the function combines multiple filters and if any of them accept a
+ * record, it is filtered in.
+ *
+ * These filter constructors are used as appropriate by the various charts to implement brushing.  We
+ * mention below which chart uses which filter.  In some cases, many instances of a filter will be added.
+ *
+ * Each of the dc.js filters is an object with the following properties:
+ * * `isFiltered` - a function that returns true if a value is within the filter
+ * * `filterType` - a string identifying the filter, here the name of the constructor
+ *
+ * Currently these filter objects are also arrays, but this is not a requirement. Custom filters
+ * can be used as long as they have the properties above.
+ * @namespace filters
+ * @memberof dc
+ * @type {{}}
+ */
+dc.filters = {};
+
+/**
+ * RangedFilter is a filter which accepts keys between `low` and `high`.  It is used to implement X
+ * axis brushing for the {@link dc.coordinateGridMixin coordinate grid charts}.
+ *
+ * Its `filterType` is 'RangedFilter'
+ * @name RangedFilter
+ * @memberof dc.filters
+ * @param {Number} low
+ * @param {Number} high
+ * @returns {Array<Number>}
+ * @constructor
+ */
+dc.filters.RangedFilter = function (low, high) {
+    var range = new Array(low, high);
+    range.isFiltered = function (value) {
+        return value >= this[0] && value < this[1];
+    };
+    range.filterType = 'RangedFilter';
+
+    return range;
+};
+
+/**
+ * TwoDimensionalFilter is a filter which accepts a single two-dimensional value.  It is used by the
+ * {@link dc.heatMap heat map chart} to include particular cells as they are clicked.  (Rows and columns are
+ * filtered by filtering all the cells in the row or column.)
+ *
+ * Its `filterType` is 'TwoDimensionalFilter'
+ * @name TwoDimensionalFilter
+ * @memberof dc.filters
+ * @param {Array<Number>} filter
+ * @returns {Array<Number>}
+ * @constructor
+ */
+dc.filters.TwoDimensionalFilter = function (filter) {
+    if (filter === null) { return null; }
+
+    var f = filter;
+    f.isFiltered = function (value) {
+        return value.length && value.length === f.length &&
+               value[0] === f[0] && value[1] === f[1];
+    };
+    f.filterType = 'TwoDimensionalFilter';
+
+    return f;
+};
+
+/**
+ * The RangedTwoDimensionalFilter allows filtering all values which fit within a rectangular
+ * region. It is used by the {@link dc.scatterPlot scatter plot} to implement rectangular brushing.
+ *
+ * It takes two two-dimensional points in the form `[[x1,y1],[x2,y2]]`, and normalizes them so that
+ * `x1 <= x2` and `y1 <= y2`. It then returns a filter which accepts any points which are in the
+ * rectangular range including the lower values but excluding the higher values.
+ *
+ * If an array of two values are given to the RangedTwoDimensionalFilter, it interprets the values as
+ * two x coordinates `x1` and `x2` and returns a filter which accepts any points for which `x1 <= x <
+ * x2`.
+ *
+ * Its `filterType` is 'RangedTwoDimensionalFilter'
+ * @name RangedTwoDimensionalFilter
+ * @memberof dc.filters
+ * @param {Array<Array<Number>>} filter
+ * @returns {Array<Array<Number>>}
+ * @constructor
+ */
+dc.filters.RangedTwoDimensionalFilter = function (filter) {
+    if (filter === null) { return null; }
+
+    var f = filter;
+    var fromBottomLeft;
+
+    if (f[0] instanceof Array) {
+        fromBottomLeft = [
+            [Math.min(filter[0][0], filter[1][0]), Math.min(filter[0][1], filter[1][1])],
+            [Math.max(filter[0][0], filter[1][0]), Math.max(filter[0][1], filter[1][1])]
+        ];
+    } else {
+        fromBottomLeft = [[filter[0], -Infinity], [filter[1], Infinity]];
+    }
+
+    f.isFiltered = function (value) {
+        var x, y;
+
+        if (value instanceof Array) {
+            x = value[0];
+            y = value[1];
+        } else {
+            x = value;
+            y = fromBottomLeft[0][1];
+        }
+
+        return x >= fromBottomLeft[0][0] && x < fromBottomLeft[1][0] &&
+               y >= fromBottomLeft[0][1] && y < fromBottomLeft[1][1];
+    };
+    f.filterType = 'RangedTwoDimensionalFilter';
+
+    return f;
+};
+
+/**
+ * `dc.baseMixin` is an abstract functional object representing a basic `dc` chart object
+ * for all chart and widget implementations. Methods from the {@link #dc.baseMixin dc.baseMixin} are inherited
+ * and available on all chart implementations in the `dc` library.
+ * @name baseMixin
+ * @memberof dc
+ * @mixin
+ * @param {Object} _chart
+ * @returns {dc.baseMixin}
+ */
+dc.baseMixin = function (_chart) {
+    _chart.__dcFlag__ = dc.utils.uniqueId();
+
+    var _dimension;
+    var _group;
+
+    var _anchor;
+    var _root;
+    var _svg;
+    var _isChild;
+
+    var _minWidth = 200;
+    var _defaultWidthCalc = function (element) {
+        var width = element && element.getBoundingClientRect && element.getBoundingClientRect().width;
+        return (width && width > _minWidth) ? width : _minWidth;
+    };
+    var _widthCalc = _defaultWidthCalc;
+
+    var _minHeight = 200;
+    var _defaultHeightCalc = function (element) {
+        var height = element && element.getBoundingClientRect && element.getBoundingClientRect().height;
+        return (height && height > _minHeight) ? height : _minHeight;
+    };
+    var _heightCalc = _defaultHeightCalc;
+    var _width, _height;
+
+    var _keyAccessor = dc.pluck('key');
+    var _valueAccessor = dc.pluck('value');
+    var _label = dc.pluck('key');
+
+    var _ordering = dc.pluck('key');
+    var _orderSort;
+
+    var _renderLabel = false;
+
+    var _title = function (d) {
+        return _chart.keyAccessor()(d) + ': ' + _chart.valueAccessor()(d);
+    };
+    var _renderTitle = true;
+    var _controlsUseVisibility = false;
+
+    var _transitionDuration = 750;
+
+    var _transitionDelay = 0;
+
+    var _filterPrinter = dc.printers.filters;
+
+    var _mandatoryAttributes = ['dimension', 'group'];
+
+    var _chartGroup = dc.constants.DEFAULT_CHART_GROUP;
+
+    var _listeners = d3.dispatch(
+        'preRender',
+        'postRender',
+        'preRedraw',
+        'postRedraw',
+        'filtered',
+        'zoomed',
+        'renderlet',
+        'pretransition');
+
+    var _legend;
+    var _commitHandler;
+
+    var _filters = [];
+    var _filterHandler = function (dimension, filters) {
+        if (filters.length === 0) {
+            dimension.filter(null);
+        } else if (filters.length === 1 && !filters[0].isFiltered) {
+            // single value and not a function-based filter
+            dimension.filterExact(filters[0]);
+        } else if (filters.length === 1 && filters[0].filterType === 'RangedFilter') {
+            // single range-based filter
+            dimension.filterRange(filters[0]);
+        } else {
+            dimension.filterFunction(function (d) {
+                for (var i = 0; i < filters.length; i++) {
+                    var filter = filters[i];
+                    if (filter.isFiltered && filter.isFiltered(d)) {
+                        return true;
+                    } else if (filter <= d && filter >= d) {
+                        return true;
+                    }
+                }
+                return false;
+            });
+        }
+        return filters;
+    };
+
+    var _data = function (group) {
+        return group.all();
+    };
+
+    /**
+     * Set or get the height attribute of a chart. The height is applied to the SVGElement generated by
+     * the chart when rendered (or re-rendered). If a value is given, then it will be used to calculate
+     * the new height and the chart returned for method chaining.  The value can either be a numeric, a
+     * function, or falsy. If no value is specified then the value of the current height attribute will
+     * be returned.
+     *
+     * By default, without an explicit height being given, the chart will select the width of its
+     * anchor element. If that isn't possible it defaults to 200 (provided by the
+     * {@link dc.baseMixin#minHeight minHeight} property). Setting the value falsy will return
+     * the chart to the default behavior.
+     * @method height
+     * @memberof dc.baseMixin
+     * @instance
+     * @see {@link dc.baseMixin#minHeight minHeight}
+     * @example
+     * // Default height
+     * chart.height(function (element) {
+     *     var height = element && element.getBoundingClientRect && element.getBoundingClientRect().height;
+     *     return (height && height > chart.minHeight()) ? height : chart.minHeight();
+     * });
+     *
+     * chart.height(250); // Set the chart's height to 250px;
+     * chart.height(function(anchor) { return doSomethingWith(anchor); }); // set the chart's height with a function
+     * chart.height(null); // reset the height to the default auto calculation
+     * @param {Number|Function} [height]
+     * @returns {Number|dc.baseMixin}
+     */
+    _chart.height = function (height) {
+        if (!arguments.length) {
+            if (!dc.utils.isNumber(_height)) {
+                // only calculate once
+                _height = _heightCalc(_root.node());
+            }
+            return _height;
+        }
+        _heightCalc = d3.functor(height || _defaultHeightCalc);
+        _height = undefined;
+        return _chart;
+    };
+
+    /**
+     * Set or get the width attribute of a chart.
+     * @method width
+     * @memberof dc.baseMixin
+     * @instance
+     * @see {@link dc.baseMixin#height height}
+     * @see {@link dc.baseMixin#minWidth minWidth}
+     * @example
+     * // Default width
+     * chart.width(function (element) {
+     *     var width = element && element.getBoundingClientRect && element.getBoundingClientRect().width;
+     *     return (width && width > chart.minWidth()) ? width : chart.minWidth();
+     * });
+     * @param {Number|Function} [width]
+     * @returns {Number|dc.baseMixin}
+     */
+    _chart.width = function (width) {
+        if (!arguments.length) {
+            if (!dc.utils.isNumber(_width)) {
+                // only calculate once
+                _width = _widthCalc(_root.node());
+            }
+            return _width;
+        }
+        _widthCalc = d3.functor(width || _defaultWidthCalc);
+        _width = undefined;
+        return _chart;
+    };
+
+    /**
+     * Set or get the minimum width attribute of a chart. This only has effect when used with the default
+     * {@link dc.baseMixin#width width} function.
+     * @method minWidth
+     * @memberof dc.baseMixin
+     * @instance
+     * @see {@link dc.baseMixin#width width}
+     * @param {Number} [minWidth=200]
+     * @returns {Number|dc.baseMixin}
+     */
+    _chart.minWidth = function (minWidth) {
+        if (!arguments.length) {
+            return _minWidth;
+        }
+        _minWidth = minWidth;
+        return _chart;
+    };
+
+    /**
+     * Set or get the minimum height attribute of a chart. This only has effect when used with the default
+     * {@link dc.baseMixin#height height} function.
+     * @method minHeight
+     * @memberof dc.baseMixin
+     * @instance
+     * @see {@link dc.baseMixin#height height}
+     * @param {Number} [minHeight=200]
+     * @returns {Number|dc.baseMixin}
+     */
+    _chart.minHeight = function (minHeight) {
+        if (!arguments.length) {
+            return _minHeight;
+        }
+        _minHeight = minHeight;
+        return _chart;
+    };
+
+    /**
+     * **mandatory**
+     *
+     * Set or get the dimension attribute of a chart. In `dc`, a dimension can be any valid
+     * {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#dimension crossfilter dimension}
+     *
+     * If a value is given, then it will be used as the new dimension. If no value is specified then
+     * the current dimension will be returned.
+     * @method dimension
+     * @memberof dc.baseMixin
+     * @instance
+     * @see {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#dimension crossfilter.dimension}
+     * @example
+     * var index = crossfilter([]);
+     * var dimension = index.dimension(dc.pluck('key'));
+     * chart.dimension(dimension);
+     * @param {crossfilter.dimension} [dimension]
+     * @returns {crossfilter.dimension|dc.baseMixin}
+     */
+    _chart.dimension = function (dimension) {
+        if (!arguments.length) {
+            return _dimension;
+        }
+        _dimension = dimension;
+        _chart.expireCache();
+        return _chart;
+    };
+
+    /**
+     * Set the data callback or retrieve the chart's data set. The data callback is passed the chart's
+     * group and by default will return
+     * {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#group_all group.all}.
+     * This behavior may be modified to, for instance, return only the top 5 groups.
+     * @method data
+     * @memberof dc.baseMixin
+     * @instance
+     * @example
+     * // Default data function
+     * chart.data(function (group) { return group.all(); });
+     *
+     * chart.data(function (group) { return group.top(5); });
+     * @param {Function} [callback]
+     * @returns {*|dc.baseMixin}
+     */
+    _chart.data = function (callback) {
+        if (!arguments.length) {
+            return _data.call(_chart, _group);
+        }
+        _data = d3.functor(callback);
+        _chart.expireCache();
+        return _chart;
+    };
+
+    /**
+     * **mandatory**
+     *
+     * Set or get the group attribute of a chart. In `dc` a group is a
+     * {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#group-map-reduce crossfilter group}.
+     * Usually the group should be created from the particular dimension associated with the same chart. If a value is
+     * given, then it will be used as the new group.
+     *
+     * If no value specified then the current group will be returned.
+     * If `name` is specified then it will be used to generate legend label.
+     * @method group
+     * @memberof dc.baseMixin
+     * @instance
+     * @see {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#group-map-reduce crossfilter.group}
+     * @example
+     * var index = crossfilter([]);
+     * var dimension = index.dimension(dc.pluck('key'));
+     * chart.dimension(dimension);
+     * chart.group(dimension.group(crossfilter.reduceSum()));
+     * @param {crossfilter.group} [group]
+     * @param {String} [name]
+     * @returns {crossfilter.group|dc.baseMixin}
+     */
+    _chart.group = function (group, name) {
+        if (!arguments.length) {
+            return _group;
+        }
+        _group = group;
+        _chart._groupName = name;
+        _chart.expireCache();
+        return _chart;
+    };
+
+    /**
+     * Get or set an accessor to order ordinal dimensions.  The chart uses
+     * {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#quicksort_by crossfilter.quicksort.by}
+     * to sort elements; this accessor returns the value to order on.
+     * @method ordering
+     * @memberof dc.baseMixin
+     * @instance
+     * @see {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#quicksort_by crossfilter.quicksort.by}
+     * @example
+     * // Default ordering accessor
+     * _chart.ordering(dc.pluck('key'));
+     * @param {Function} [orderFunction]
+     * @returns {Function|dc.baseMixin}
+     */
+    _chart.ordering = function (orderFunction) {
+        if (!arguments.length) {
+            return _ordering;
+        }
+        _ordering = orderFunction;
+        _orderSort = crossfilter.quicksort.by(_ordering);
+        _chart.expireCache();
+        return _chart;
+    };
+
+    _chart._computeOrderedGroups = function (data) {
+        var dataCopy = data.slice(0);
+
+        if (dataCopy.length <= 1) {
+            return dataCopy;
+        }
+
+        if (!_orderSort) {
+            _orderSort = crossfilter.quicksort.by(_ordering);
+        }
+
+        return _orderSort(dataCopy, 0, dataCopy.length);
+    };
+
+    /**
+     * Clear all filters associated with this chart. The same effect can be achieved by calling
+     * {@link dc.baseMixin#filter chart.filter(null)}.
+     * @method filterAll
+     * @memberof dc.baseMixin
+     * @instance
+     * @returns {dc.baseMixin}
+     */
+    _chart.filterAll = function () {
+        return _chart.filter(null);
+    };
+
+    /**
+     * Execute d3 single selection in the chart's scope using the given selector and return the d3
+     * selection.
+     *
+     * This function is **not chainable** since it does not return a chart instance; however the d3
+     * selection result can be chained to d3 function calls.
+     * @method select
+     * @memberof dc.baseMixin
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Selections.md#d3_select d3.select}
+     * @example
+     * // Has the same effect as d3.select('#chart-id').select(selector)
+     * chart.select(selector)
+     * @returns {d3.selection}
+     */
+    _chart.select = function (s) {
+        return _root.select(s);
+    };
+
+    /**
+     * Execute in scope d3 selectAll using the given selector and return d3 selection result.
+     *
+     * This function is **not chainable** since it does not return a chart instance; however the d3
+     * selection result can be chained to d3 function calls.
+     * @method selectAll
+     * @memberof dc.baseMixin
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Selections.md#d3_selectAll d3.selectAll}
+     * @example
+     * // Has the same effect as d3.select('#chart-id').selectAll(selector)
+     * chart.selectAll(selector)
+     * @returns {d3.selection}
+     */
+    _chart.selectAll = function (s) {
+        return _root ? _root.selectAll(s) : null;
+    };
+
+    /**
+     * Set the root SVGElement to either be an existing chart's root; or any valid [d3 single
+     * selector](https://github.com/d3/d3-3.x-api-reference/blob/master/Selections.md#selecting-elements) specifying a dom
+     * block element such as a div; or a dom element or d3 selection. Optionally registers the chart
+     * within the chartGroup. This class is called internally on chart initialization, but be called
+     * again to relocate the chart. However, it will orphan any previously created SVGElements.
+     * @method anchor
+     * @memberof dc.baseMixin
+     * @instance
+     * @param {anchorChart|anchorSelector|anchorNode} [parent]
+     * @param {String} [chartGroup]
+     * @returns {String|node|d3.selection|dc.baseMixin}
+     */
+    _chart.anchor = function (parent, chartGroup) {
+        if (!arguments.length) {
+            return _anchor;
+        }
+        if (dc.instanceOfChart(parent)) {
+            _anchor = parent.anchor();
+            _root = parent.root();
+            _isChild = true;
+        } else if (parent) {
+            if (parent.select && parent.classed) { // detect d3 selection
+                _anchor = parent.node();
+            } else {
+                _anchor = parent;
+            }
+            _root = d3.select(_anchor);
+            _root.classed(dc.constants.CHART_CLASS, true);
+            dc.registerChart(_chart, chartGroup);
+            _isChild = false;
+        } else {
+            throw new dc.errors.BadArgumentException('parent must be defined');
+        }
+        _chartGroup = chartGroup;
+        return _chart;
+    };
+
+    /**
+     * Returns the DOM id for the chart's anchored location.
+     * @method anchorName
+     * @memberof dc.baseMixin
+     * @instance
+     * @returns {String}
+     */
+    _chart.anchorName = function () {
+        var a = _chart.anchor();
+        if (a && a.id) {
+            return a.id;
+        }
+        if (a && a.replace) {
+            return a.replace('#', '');
+        }
+        return 'dc-chart' + _chart.chartID();
+    };
+
+    /**
+     * Returns the root element where a chart resides. Usually it will be the parent div element where
+     * the SVGElement was created. You can also pass in a new root element however this is usually handled by
+     * dc internally. Resetting the root element on a chart outside of dc internals may have
+     * unexpected consequences.
+     * @method root
+     * @memberof dc.baseMixin
+     * @instance
+     * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement HTMLElement}
+     * @param {HTMLElement} [rootElement]
+     * @returns {HTMLElement|dc.baseMixin}
+     */
+    _chart.root = function (rootElement) {
+        if (!arguments.length) {
+            return _root;
+        }
+        _root = rootElement;
+        return _chart;
+    };
+
+    /**
+     * Returns the top SVGElement for this specific chart. You can also pass in a new SVGElement,
+     * however this is usually handled by dc internally. Resetting the SVGElement on a chart outside
+     * of dc internals may have unexpected consequences.
+     * @method svg
+     * @memberof dc.baseMixin
+     * @instance
+     * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGElement SVGElement}
+     * @param {SVGElement|d3.selection} [svgElement]
+     * @returns {SVGElement|d3.selection|dc.baseMixin}
+     */
+    _chart.svg = function (svgElement) {
+        if (!arguments.length) {
+            return _svg;
+        }
+        _svg = svgElement;
+        return _chart;
+    };
+
+    /**
+     * Remove the chart's SVGElements from the dom and recreate the container SVGElement.
+     * @method resetSvg
+     * @memberof dc.baseMixin
+     * @instance
+     * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/SVGElement SVGElement}
+     * @returns {SVGElement}
+     */
+    _chart.resetSvg = function () {
+        _chart.select('svg').remove();
+        return generateSvg();
+    };
+
+    function sizeSvg () {
+        if (_svg) {
+            _svg
+                .attr('width', _chart.width())
+                .attr('height', _chart.height());
+        }
+    }
+
+    function generateSvg () {
+        _svg = _chart.root().append('svg');
+        sizeSvg();
+        return _svg;
+    }
+
+    /**
+     * Set or get the filter printer function. The filter printer function is used to generate human
+     * friendly text for filter value(s) associated with the chart instance. The text will get shown
+     * in the `.filter element; see {@link dc.baseMixin#turnOnControls turnOnControls}.
+     *
+     * By default dc charts use a default filter printer {@link dc.printers.filters dc.printers.filters}
+     * that provides simple printing support for both single value and ranged filters.
+     * @method filterPrinter
+     * @memberof dc.baseMixin
+     * @instance
+     * @example
+     * // for a chart with an ordinal brush, print the filters in upper case
+     * chart.filterPrinter(function(filters) {
+     *   return filters.map(function(f) { return f.toUpperCase(); }).join(', ');
+     * });
+     * // for a chart with a range brush, print the filter as start and extent
+     * chart.filterPrinter(function(filters) {
+     *   return 'start ' + dc.utils.printSingleValue(filters[0][0]) +
+     *     ' extent ' + dc.utils.printSingleValue(filters[0][1] - filters[0][0]);
+     * });
+     * @param {Function} [filterPrinterFunction=dc.printers.filters]
+     * @returns {Function|dc.baseMixin}
+     */
+    _chart.filterPrinter = function (filterPrinterFunction) {
+        if (!arguments.length) {
+            return _filterPrinter;
+        }
+        _filterPrinter = filterPrinterFunction;
+        return _chart;
+    };
+
+    /**
+     * If set, use the `visibility` attribute instead of the `display` attribute for showing/hiding
+     * chart reset and filter controls, for less disruption to the layout.
+     * @method controlsUseVisibility
+     * @memberof dc.baseMixin
+     * @instance
+     * @param {Boolean} [controlsUseVisibility=false]
+     * @returns {Boolean|dc.baseMixin}
+     **/
+    _chart.controlsUseVisibility = function (_) {
+        if (!arguments.length) {
+            return _controlsUseVisibility;
+        }
+        _controlsUseVisibility = _;
+        return _chart;
+    };
+
+    /**
+     * Turn on optional control elements within the root element. dc currently supports the
+     * following html control elements.
+     * * root.selectAll('.reset') - elements are turned on if the chart has an active filter. This type
+     * of control element is usually used to store a reset link to allow user to reset filter on a
+     * certain chart. This element will be turned off automatically if the filter is cleared.
+     * * root.selectAll('.filter') elements are turned on if the chart has an active filter. The text
+     * content of this element is then replaced with the current filter value using the filter printer
+     * function. This type of element will be turned off automatically if the filter is cleared.
+     * @method turnOnControls
+     * @memberof dc.baseMixin
+     * @instance
+     * @returns {dc.baseMixin}
+     */
+    _chart.turnOnControls = function () {
+        if (_root) {
+            var attribute = _chart.controlsUseVisibility() ? 'visibility' : 'display';
+            _chart.selectAll('.reset').style(attribute, null);
+            _chart.selectAll('.filter').text(_filterPrinter(_chart.filters())).style(attribute, null);
+        }
+        return _chart;
+    };
+
+    /**
+     * Turn off optional control elements within the root element.
+     * @method turnOffControls
+     * @memberof dc.baseMixin
+     * @see {@link dc.baseMixin#turnOnControls turnOnControls}
+     * @instance
+     * @returns {dc.baseMixin}
+     */
+    _chart.turnOffControls = function () {
+        if (_root) {
+            var attribute = _chart.controlsUseVisibility() ? 'visibility' : 'display';
+            var value = _chart.controlsUseVisibility() ? 'hidden' : 'none';
+            _chart.selectAll('.reset').style(attribute, value);
+            _chart.selectAll('.filter').style(attribute, value).text(_chart.filter());
+        }
+        return _chart;
+    };
+
+    /**
+     * Set or get the animation transition duration (in milliseconds) for this chart instance.
+     * @method transitionDuration
+     * @memberof dc.baseMixin
+     * @instance
+     * @param {Number} [duration=750]
+     * @returns {Number|dc.baseMixin}
+     */
+    _chart.transitionDuration = function (duration) {
+        if (!arguments.length) {
+            return _transitionDuration;
+        }
+        _transitionDuration = duration;
+        return _chart;
+    };
+
+    /**
+     * Set or get the animation transition delay (in milliseconds) for this chart instance.
+     * @method transitionDelay
+     * @memberof dc.baseMixin
+     * @instance
+     * @param {Number} [delay=0]
+     * @returns {Number|dc.baseMixin}
+     */
+    _chart.transitionDelay = function (delay) {
+        if (!arguments.length) {
+            return _transitionDelay;
+        }
+        _transitionDelay = delay;
+        return _chart;
+    };
+
+    _chart._mandatoryAttributes = function (_) {
+        if (!arguments.length) {
+            return _mandatoryAttributes;
+        }
+        _mandatoryAttributes = _;
+        return _chart;
+    };
+
+    function checkForMandatoryAttributes (a) {
+        if (!_chart[a] || !_chart[a]()) {
+            throw new dc.errors.InvalidStateException('Mandatory attribute chart.' + a +
+                ' is missing on chart[#' + _chart.anchorName() + ']');
+        }
+    }
+
+    /**
+     * Invoking this method will force the chart to re-render everything from scratch. Generally it
+     * should only be used to render the chart for the first time on the page or if you want to make
+     * sure everything is redrawn from scratch instead of relying on the default incremental redrawing
+     * behaviour.
+     * @method render
+     * @memberof dc.baseMixin
+     * @instance
+     * @returns {dc.baseMixin}
+     */
+    _chart.render = function () {
+        _height = _width = undefined; // force recalculate
+        _listeners.preRender(_chart);
+
+        if (_mandatoryAttributes) {
+            _mandatoryAttributes.forEach(checkForMandatoryAttributes);
+        }
+
+        var result = _chart._doRender();
+
+        if (_legend) {
+            _legend.render();
+        }
+
+        _chart._activateRenderlets('postRender');
+
+        return result;
+    };
+
+    _chart._activateRenderlets = function (event) {
+        _listeners.pretransition(_chart);
+        if (_chart.transitionDuration() > 0 && _svg) {
+            _svg.transition().duration(_chart.transitionDuration()).delay(_chart.transitionDelay())
+                .each('end', function () {
+                    _listeners.renderlet(_chart);
+                    if (event) {
+                        _listeners[event](_chart);
+                    }
+                });
+        } else {
+            _listeners.renderlet(_chart);
+            if (event) {
+                _listeners[event](_chart);
+            }
+        }
+    };
+
+    /**
+     * Calling redraw will cause the chart to re-render data changes incrementally. If there is no
+     * change in the underlying data dimension then calling this method will have no effect on the
+     * chart. Most chart interaction in dc will automatically trigger this method through internal
+     * events (in particular {@link dc.redrawAll dc.redrawAll}); therefore, you only need to
+     * manually invoke this function if data is manipulated outside of dc's control (for example if
+     * data is loaded in the background using
+     * {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#crossfilter_add crossfilter.add}).
+     * @method redraw
+     * @memberof dc.baseMixin
+     * @instance
+     * @returns {dc.baseMixin}
+     */
+    _chart.redraw = function () {
+        sizeSvg();
+        _listeners.preRedraw(_chart);
+
+        var result = _chart._doRedraw();
+
+        if (_legend) {
+            _legend.render();
+        }
+
+        _chart._activateRenderlets('postRedraw');
+
+        return result;
+    };
+
+    /**
+     * Gets/sets the commit handler. If the chart has a commit handler, the handler will be called when
+     * the chart's filters have changed, in order to send the filter data asynchronously to a server.
+     *
+     * Unlike other functions in dc.js, the commit handler is asynchronous. It takes two arguments:
+     * a flag indicating whether this is a render (true) or a redraw (false), and a callback to be
+     * triggered once the commit is filtered. The callback has the standard node.js continuation signature
+     * with error first and result second.
+     * @method commitHandler
+     * @memberof dc.baseMixin
+     * @instance
+     * @returns {dc.baseMixin}
+     */
+    _chart.commitHandler = function (commitHandler) {
+        if (!arguments.length) {
+            return _commitHandler;
+        }
+        _commitHandler = commitHandler;
+        return _chart;
+    };
+
+    /**
+     * Redraws all charts in the same group as this chart, typically in reaction to a filter
+     * change. If the chart has a {@link dc.baseMixin.commitFilter commitHandler}, it will
+     * be executed and waited for.
+     * @method redrawGroup
+     * @memberof dc.baseMixin
+     * @instance
+     * @returns {dc.baseMixin}
+     */
+    _chart.redrawGroup = function () {
+        if (_commitHandler) {
+            _commitHandler(false, function (error, result) {
+                if (error) {
+                    console.log(error);
+                } else {
+                    dc.redrawAll(_chart.chartGroup());
+                }
+            });
+        } else {
+            dc.redrawAll(_chart.chartGroup());
+        }
+        return _chart;
+    };
+
+    /**
+     * Renders all charts in the same group as this chart. If the chart has a
+     * {@link dc.baseMixin.commitFilter commitHandler}, it will be executed and waited for
+     * @method renderGroup
+     * @memberof dc.baseMixin
+     * @instance
+     * @returns {dc.baseMixin}
+     */
+    _chart.renderGroup = function () {
+        if (_commitHandler) {
+            _commitHandler(false, function (error, result) {
+                if (error) {
+                    console.log(error);
+                } else {
+                    dc.renderAll(_chart.chartGroup());
+                }
+            });
+        } else {
+            dc.renderAll(_chart.chartGroup());
+        }
+        return _chart;
+    };
+
+    _chart._invokeFilteredListener = function (f) {
+        if (f !== undefined) {
+            _listeners.filtered(_chart, f);
+        }
+    };
+
+    _chart._invokeZoomedListener = function () {
+        _listeners.zoomed(_chart);
+    };
+
+    var _hasFilterHandler = function (filters, filter) {
+        if (filter === null || typeof(filter) === 'undefined') {
+            return filters.length > 0;
+        }
+        return filters.some(function (f) {
+            return filter <= f && filter >= f;
+        });
+    };
+
+    /**
+     * Set or get the has filter handler. The has filter handler is a function that checks to see if
+     * the chart's current filters include a specific filter.  Using a custom has filter handler allows
+     * you to change the way filters are checked for and replaced.
+     * @method hasFilterHandler
+     * @memberof dc.baseMixin
+     * @instance
+     * @example
+     * // default has filter handler
+     * chart.hasFilterHandler(function (filters, filter) {
+     *     if (filter === null || typeof(filter) === 'undefined') {
+     *         return filters.length > 0;
+     *     }
+     *     return filters.some(function (f) {
+     *         return filter <= f && filter >= f;
+     *     });
+     * });
+     *
+     * // custom filter handler (no-op)
+     * chart.hasFilterHandler(function(filters, filter) {
+     *     return false;
+     * });
+     * @param {Function} [hasFilterHandler]
+     * @returns {Function|dc.baseMixin}
+     */
+    _chart.hasFilterHandler = function (hasFilterHandler) {
+        if (!arguments.length) {
+            return _hasFilterHandler;
+        }
+        _hasFilterHandler = hasFilterHandler;
+        return _chart;
+    };
+
+    /**
+     * Check whether any active filter or a specific filter is associated with particular chart instance.
+     * This function is **not chainable**.
+     * @method hasFilter
+     * @memberof dc.baseMixin
+     * @instance
+     * @see {@link dc.baseMixin#hasFilterHandler hasFilterHandler}
+     * @param {*} [filter]
+     * @returns {Boolean}
+     */
+    _chart.hasFilter = function (filter) {
+        return _hasFilterHandler(_filters, filter);
+    };
+
+    var _removeFilterHandler = function (filters, filter) {
+        for (var i = 0; i < filters.length; i++) {
+            if (filters[i] <= filter && filters[i] >= filter) {
+                filters.splice(i, 1);
+                break;
+            }
+        }
+        return filters;
+    };
+
+    /**
+     * Set or get the remove filter handler. The remove filter handler is a function that removes a
+     * filter from the chart's current filters. Using a custom remove filter handler allows you to
+     * change how filters are removed or perform additional work when removing a filter, e.g. when
+     * using a filter server other than crossfilter.
+     *
+     * Any changes should modify the `filters` array argument and return that array.
+     * @method removeFilterHandler
+     * @memberof dc.baseMixin
+     * @instance
+     * @example
+     * // default remove filter handler
+     * chart.removeFilterHandler(function (filters, filter) {
+     *     for (var i = 0; i < filters.length; i++) {
+     *         if (filters[i] <= filter && filters[i] >= filter) {
+     *             filters.splice(i, 1);
+     *             break;
+     *         }
+     *     }
+     *     return filters;
+     * });
+     *
+     * // custom filter handler (no-op)
+     * chart.removeFilterHandler(function(filters, filter) {
+     *     return filters;
+     * });
+     * @param {Function} [removeFilterHandler]
+     * @returns {Function|dc.baseMixin}
+     */
+    _chart.removeFilterHandler = function (removeFilterHandler) {
+        if (!arguments.length) {
+            return _removeFilterHandler;
+        }
+        _removeFilterHandler = removeFilterHandler;
+        return _chart;
+    };
+
+    var _addFilterHandler = function (filters, filter) {
+        filters.push(filter);
+        return filters;
+    };
+
+    /**
+     * Set or get the add filter handler. The add filter handler is a function that adds a filter to
+     * the chart's filter list. Using a custom add filter handler allows you to change the way filters
+     * are added or perform additional work when adding a filter, e.g. when using a filter server other
+     * than crossfilter.
+     *
+     * Any changes should modify the `filters` array argument and return that array.
+     * @method addFilterHandler
+     * @memberof dc.baseMixin
+     * @instance
+     * @example
+     * // default add filter handler
+     * chart.addFilterHandler(function (filters, filter) {
+     *     filters.push(filter);
+     *     return filters;
+     * });
+     *
+     * // custom filter handler (no-op)
+     * chart.addFilterHandler(function(filters, filter) {
+     *     return filters;
+     * });
+     * @param {Function} [addFilterHandler]
+     * @returns {Function|dc.baseMixin}
+     */
+    _chart.addFilterHandler = function (addFilterHandler) {
+        if (!arguments.length) {
+            return _addFilterHandler;
+        }
+        _addFilterHandler = addFilterHandler;
+        return _chart;
+    };
+
+    var _resetFilterHandler = function (filters) {
+        return [];
+    };
+
+    /**
+     * Set or get the reset filter handler. The reset filter handler is a function that resets the
+     * chart's filter list by returning a new list. Using a custom reset filter handler allows you to
+     * change the way filters are reset, or perform additional work when resetting the filters,
+     * e.g. when using a filter server other than crossfilter.
+     *
+     * This function should return an array.
+     * @method resetFilterHandler
+     * @memberof dc.baseMixin
+     * @instance
+     * @example
+     * // default remove filter handler
+     * function (filters) {
+     *     return [];
+     * }
+     *
+     * // custom filter handler (no-op)
+     * chart.resetFilterHandler(function(filters) {
+     *     return filters;
+     * });
+     * @param {Function} [resetFilterHandler]
+     * @returns {dc.baseMixin}
+     */
+    _chart.resetFilterHandler = function (resetFilterHandler) {
+        if (!arguments.length) {
+            return _resetFilterHandler;
+        }
+        _resetFilterHandler = resetFilterHandler;
+        return _chart;
+    };
+
+    function applyFilters () {
+        if (_chart.dimension() && _chart.dimension().filter) {
+            var fs = _filterHandler(_chart.dimension(), _filters);
+            _filters = fs ? fs : _filters;
+        }
+    }
+
+    /**
+     * Replace the chart filter. This is equivalent to calling `chart.filter(null).filter(filter)`
+     * but more efficient because the filter is only applied once.
+     *
+     * @method replaceFilter
+     * @memberof dc.baseMixin
+     * @instance
+     * @param {*} [filter]
+     * @returns {dc.baseMixin}
+     **/
+    _chart.replaceFilter = function (filter) {
+        _filters = _resetFilterHandler(_filters);
+        _chart.filter(filter);
+        return _chart;
+    };
+
+    /**
+     * Filter the chart by the given parameter, or return the current filter if no input parameter
+     * is given.
+     *
+     * The filter parameter can take one of these forms:
+     * * A single value: the value will be toggled (added if it is not present in the current
+     * filters, removed if it is present)
+     * * An array containing a single array of values (`[[value,value,value]]`): each value is
+     * toggled
+     * * When appropriate for the chart, a {@link dc.filters dc filter object} such as
+     *   * {@link dc.filters.RangedFilter `dc.filters.RangedFilter`} for the
+     * {@link dc.coordinateGridMixin dc.coordinateGridMixin} charts
+     *   * {@link dc.filters.TwoDimensionalFilter `dc.filters.TwoDimensionalFilter`} for the
+     * {@link dc.heatMap heat map}
+     *   * {@link dc.filters.RangedTwoDimensionalFilter `dc.filters.RangedTwoDimensionalFilter`}
+     * for the {@link dc.scatterPlot scatter plot}
+     * * `null`: the filter will be reset using the
+     * {@link dc.baseMixin#resetFilterHandler resetFilterHandler}
+     *
+     * Note that this is always a toggle (even when it doesn't make sense for the filter type). If
+     * you wish to replace the current filter, either call `chart.filter(null)` first - or it's more
+     * efficient to call {@link dc.baseMixin#replaceFilter `chart.replaceFilter(filter)`} instead.
+     *
+     * Each toggle is executed by checking if the value is already present using the
+     * {@link dc.baseMixin#hasFilterHandler hasFilterHandler}; if it is not present, it is added
+     * using the {@link dc.baseMixin#addFilterHandler addFilterHandler}; if it is already present,
+     * it is removed using the {@link dc.baseMixin#removeFilterHandler removeFilterHandler}.
+     *
+     * Once the filters array has been updated, the filters are applied to the
+     * crossfilter dimension, using the {@link dc.baseMixin#filterHandler filterHandler}.
+     *
+     * Once you have set the filters, call {@link dc.baseMixin#redrawGroup `chart.redrawGroup()`}
+     * (or {@link dc.redrawAll `dc.redrawAll()`}) to redraw the chart's group.
+     * @method filter
+     * @memberof dc.baseMixin
+     * @instance
+     * @see {@link dc.baseMixin#addFilterHandler addFilterHandler}
+     * @see {@link dc.baseMixin#removeFilterHandler removeFilterHandler}
+     * @see {@link dc.baseMixin#resetFilterHandler resetFilterHandler}
+     * @see {@link dc.baseMixin#filterHandler filterHandler}
+     * @example
+     * // filter by a single string
+     * chart.filter('Sunday');
+     * // filter by a single age
+     * chart.filter(18);
+     * // filter by a set of states
+     * chart.filter([['MA', 'TX', 'ND', 'WA']]);
+     * // filter by range -- note the use of dc.filters.RangedFilter, which is different
+     * // from the syntax for filtering a crossfilter dimension directly, dimension.filter([15,20])
+     * chart.filter(dc.filters.RangedFilter(15,20));
+     * @param {*} [filter]
+     * @returns {dc.baseMixin}
+     */
+    _chart.filter = function (filter) {
+        if (!arguments.length) {
+            return _filters.length > 0 ? _filters[0] : null;
+        }
+        if (filter instanceof Array && filter[0] instanceof Array && !filter.isFiltered) {
+            filter[0].forEach(function (d) {
+                if (_chart.hasFilter(d)) {
+                    _removeFilterHandler(_filters, d);
+                } else {
+                    _addFilterHandler(_filters, d);
+                }
+            });
+        } else if (filter === null) {
+            _filters = _resetFilterHandler(_filters);
+        } else {
+            if (_chart.hasFilter(filter)) {
+                _removeFilterHandler(_filters, filter);
+            } else {
+                _addFilterHandler(_filters, filter);
+            }
+        }
+        applyFilters();
+        _chart._invokeFilteredListener(filter);
+
+        if (_root !== null && _chart.hasFilter()) {
+            _chart.turnOnControls();
+        } else {
+            _chart.turnOffControls();
+        }
+
+        return _chart;
+    };
+
+    /**
+     * Returns all current filters. This method does not perform defensive cloning of the internal
+     * filter array before returning, therefore any modification of the returned array will effect the
+     * chart's internal filter storage.
+     * @method filters
+     * @memberof dc.baseMixin
+     * @instance
+     * @returns {Array<*>}
+     */
+    _chart.filters = function () {
+        return _filters;
+    };
+
+    _chart.highlightSelected = function (e) {
+        d3.select(e).classed(dc.constants.SELECTED_CLASS, true);
+        d3.select(e).classed(dc.constants.DESELECTED_CLASS, false);
+    };
+
+    _chart.fadeDeselected = function (e) {
+        d3.select(e).classed(dc.constants.SELECTED_CLASS, false);
+        d3.select(e).classed(dc.constants.DESELECTED_CLASS, true);
+    };
+
+    _chart.resetHighlight = function (e) {
+        d3.select(e).classed(dc.constants.SELECTED_CLASS, false);
+        d3.select(e).classed(dc.constants.DESELECTED_CLASS, false);
+    };
+
+    /**
+     * This function is passed to d3 as the onClick handler for each chart. The default behavior is to
+     * filter on the clicked datum (passed to the callback) and redraw the chart group.
+     * @method onClick
+     * @memberof dc.baseMixin
+     * @instance
+     * @param {*} datum
+     */
+    _chart.onClick = function (datum) {
+        var filter = _chart.keyAccessor()(datum);
+        dc.events.trigger(function () {
+            _chart.filter(filter);
+            _chart.redrawGroup();
+        });
+    };
+
+    /**
+     * Set or get the filter handler. The filter handler is a function that performs the filter action
+     * on a specific dimension. Using a custom filter handler allows you to perform additional logic
+     * before or after filtering.
+     * @method filterHandler
+     * @memberof dc.baseMixin
+     * @instance
+     * @see {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#dimension_filter crossfilter.dimension.filter}
+     * @example
+     * // the default filter handler handles all possible cases for the charts in dc.js
+     * // you can replace it with something more specialized for your own chart
+     * chart.filterHandler(function (dimension, filters) {
+     *     if (filters.length === 0) {
+     *         // the empty case (no filtering)
+     *         dimension.filter(null);
+     *     } else if (filters.length === 1 && !filters[0].isFiltered) {
+     *         // single value and not a function-based filter
+     *         dimension.filterExact(filters[0]);
+     *     } else if (filters.length === 1 && filters[0].filterType === 'RangedFilter') {
+     *         // single range-based filter
+     *         dimension.filterRange(filters[0]);
+     *     } else {
+     *         // an array of values, or an array of filter objects
+     *         dimension.filterFunction(function (d) {
+     *             for (var i = 0; i < filters.length; i++) {
+     *                 var filter = filters[i];
+     *                 if (filter.isFiltered && filter.isFiltered(d)) {
+     *                     return true;
+     *                 } else if (filter <= d && filter >= d) {
+     *                     return true;
+     *                 }
+     *             }
+     *             return false;
+     *         });
+     *     }
+     *     return filters;
+     * });
+     *
+     * // custom filter handler
+     * chart.filterHandler(function(dimension, filter){
+     *     var newFilter = filter + 10;
+     *     dimension.filter(newFilter);
+     *     return newFilter; // set the actual filter value to the new value
+     * });
+     * @param {Function} [filterHandler]
+     * @returns {Function|dc.baseMixin}
+     */
+    _chart.filterHandler = function (filterHandler) {
+        if (!arguments.length) {
+            return _filterHandler;
+        }
+        _filterHandler = filterHandler;
+        return _chart;
+    };
+
+    // abstract function stub
+    _chart._doRender = function () {
+        // do nothing in base, should be overridden by sub-function
+        return _chart;
+    };
+
+    _chart._doRedraw = function () {
+        // do nothing in base, should be overridden by sub-function
+        return _chart;
+    };
+
+    _chart.legendables = function () {
+        // do nothing in base, should be overridden by sub-function
+        return [];
+    };
+
+    _chart.legendHighlight = function () {
+        // do nothing in base, should be overridden by sub-function
+    };
+
+    _chart.legendReset = function () {
+        // do nothing in base, should be overridden by sub-function
+    };
+
+    _chart.legendToggle = function () {
+        // do nothing in base, should be overriden by sub-function
+    };
+
+    _chart.isLegendableHidden = function () {
+        // do nothing in base, should be overridden by sub-function
+        return false;
+    };
+
+    /**
+     * Set or get the key accessor function. The key accessor function is used to retrieve the key
+     * value from the crossfilter group. Key values are used differently in different charts, for
+     * example keys correspond to slices in a pie chart and x axis positions in a grid coordinate chart.
+     * @method keyAccessor
+     * @memberof dc.baseMixin
+     * @instance
+     * @example
+     * // default key accessor
+     * chart.keyAccessor(function(d) { return d.key; });
+     * // custom key accessor for a multi-value crossfilter reduction
+     * chart.keyAccessor(function(p) { return p.value.absGain; });
+     * @param {Function} [keyAccessor]
+     * @returns {Function|dc.baseMixin}
+     */
+    _chart.keyAccessor = function (keyAccessor) {
+        if (!arguments.length) {
+            return _keyAccessor;
+        }
+        _keyAccessor = keyAccessor;
+        return _chart;
+    };
+
+    /**
+     * Set or get the value accessor function. The value accessor function is used to retrieve the
+     * value from the crossfilter group. Group values are used differently in different charts, for
+     * example values correspond to slice sizes in a pie chart and y axis positions in a grid
+     * coordinate chart.
+     * @method valueAccessor
+     * @memberof dc.baseMixin
+     * @instance
+     * @example
+     * // default value accessor
+     * chart.valueAccessor(function(d) { return d.value; });
+     * // custom value accessor for a multi-value crossfilter reduction
+     * chart.valueAccessor(function(p) { return p.value.percentageGain; });
+     * @param {Function} [valueAccessor]
+     * @returns {Function|dc.baseMixin}
+     */
+    _chart.valueAccessor = function (valueAccessor) {
+        if (!arguments.length) {
+            return _valueAccessor;
+        }
+        _valueAccessor = valueAccessor;
+        return _chart;
+    };
+
+    /**
+     * Set or get the label function. The chart class will use this function to render labels for each
+     * child element in the chart, e.g. slices in a pie chart or bubbles in a bubble chart. Not every
+     * chart supports the label function, for example line chart does not use this function
+     * at all. By default, enables labels; pass false for the second parameter if this is not desired.
+     * @method label
+     * @memberof dc.baseMixin
+     * @instance
+     * @example
+     * // default label function just return the key
+     * chart.label(function(d) { return d.key; });
+     * // label function has access to the standard d3 data binding and can get quite complicated
+     * chart.label(function(d) { return d.data.key + '(' + Math.floor(d.data.value / all.value() * 100) + '%)'; });
+     * @param {Function} [labelFunction]
+     * @param {Boolean} [enableLabels=true]
+     * @returns {Function|dc.baseMixin}
+     */
+    _chart.label = function (labelFunction, enableLabels) {
+        if (!arguments.length) {
+            return _label;
+        }
+        _label = labelFunction;
+        if ((enableLabels === undefined) || enableLabels) {
+            _renderLabel = true;
+        }
+        return _chart;
+    };
+
+    /**
+     * Turn on/off label rendering
+     * @method renderLabel
+     * @memberof dc.baseMixin
+     * @instance
+     * @param {Boolean} [renderLabel=false]
+     * @returns {Boolean|dc.baseMixin}
+     */
+    _chart.renderLabel = function (renderLabel) {
+        if (!arguments.length) {
+            return _renderLabel;
+        }
+        _renderLabel = renderLabel;
+        return _chart;
+    };
+
+    /**
+     * Set or get the title function. The chart class will use this function to render the SVGElement title
+     * (usually interpreted by browser as tooltips) for each child element in the chart, e.g. a slice
+     * in a pie chart or a bubble in a bubble chart. Almost every chart supports the title function;
+     * however in grid coordinate charts you need to turn off the brush in order to see titles, because
+     * otherwise the brush layer will block tooltip triggering.
+     * @method title
+     * @memberof dc.baseMixin
+     * @instance
+     * @example
+     * // default title function shows "key: value"
+     * chart.title(function(d) { return d.key + ': ' + d.value; });
+     * // title function has access to the standard d3 data binding and can get quite complicated
+     * chart.title(function(p) {
+     *    return p.key.getFullYear()
+     *        + '\n'
+     *        + 'Index Gain: ' + numberFormat(p.value.absGain) + '\n'
+     *        + 'Index Gain in Percentage: ' + numberFormat(p.value.percentageGain) + '%\n'
+     *        + 'Fluctuation / Index Ratio: ' + numberFormat(p.value.fluctuationPercentage) + '%';
+     * });
+     * @param {Function} [titleFunction]
+     * @returns {Function|dc.baseMixin}
+     */
+    _chart.title = function (titleFunction) {
+        if (!arguments.length) {
+            return _title;
+        }
+        _title = titleFunction;
+        return _chart;
+    };
+
+    /**
+     * Turn on/off title rendering, or return the state of the render title flag if no arguments are
+     * given.
+     * @method renderTitle
+     * @memberof dc.baseMixin
+     * @instance
+     * @param {Boolean} [renderTitle=true]
+     * @returns {Boolean|dc.baseMixin}
+     */
+    _chart.renderTitle = function (renderTitle) {
+        if (!arguments.length) {
+            return _renderTitle;
+        }
+        _renderTitle = renderTitle;
+        return _chart;
+    };
+
+    /**
+     * A renderlet is similar to an event listener on rendering event. Multiple renderlets can be added
+     * to an individual chart.  Each time a chart is rerendered or redrawn the renderlets are invoked
+     * right after the chart finishes its transitions, giving you a way to modify the SVGElements.
+     * Renderlet functions take the chart instance as the only input parameter and you can
+     * use the dc API or use raw d3 to achieve pretty much any effect.
+     *
+     * Use {@link dc.baseMixin#on on} with a 'renderlet' prefix.
+     * Generates a random key for the renderlet, which makes it hard to remove.
+     * @method renderlet
+     * @memberof dc.baseMixin
+     * @instance
+     * @deprecated
+     * @example
+     * // do this instead of .renderlet(function(chart) { ... })
+     * chart.on("renderlet", function(chart){
+     *     // mix of dc API and d3 manipulation
+     *     chart.select('g.y').style('display', 'none');
+     *     // its a closure so you can also access other chart variable available in the closure scope
+     *     moveChart.filter(chart.filter());
+     * });
+     * @param {Function} renderletFunction
+     * @returns {dc.baseMixin}
+     */
+    _chart.renderlet = dc.logger.deprecate(function (renderletFunction) {
+        _chart.on('renderlet.' + dc.utils.uniqueId(), renderletFunction);
+        return _chart;
+    }, 'chart.renderlet has been deprecated.  Please use chart.on("renderlet.<renderletKey>", renderletFunction)');
+
+    /**
+     * Get or set the chart group to which this chart belongs. Chart groups are rendered or redrawn
+     * together since it is expected they share the same underlying crossfilter data set.
+     * @method chartGroup
+     * @memberof dc.baseMixin
+     * @instance
+     * @param {String} [chartGroup]
+     * @returns {String|dc.baseMixin}
+     */
+    _chart.chartGroup = function (chartGroup) {
+        if (!arguments.length) {
+            return _chartGroup;
+        }
+        if (!_isChild) {
+            dc.deregisterChart(_chart, _chartGroup);
+        }
+        _chartGroup = chartGroup;
+        if (!_isChild) {
+            dc.registerChart(_chart, _chartGroup);
+        }
+        return _chart;
+    };
+
+    /**
+     * Expire the internal chart cache. dc charts cache some data internally on a per chart basis to
+     * speed up rendering and avoid unnecessary calculation; however it might be useful to clear the
+     * cache if you have changed state which will affect rendering.  For example, if you invoke
+     * {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#crossfilter_add crossfilter.add}
+     * function or reset group or dimension after rendering, it is a good idea to
+     * clear the cache to make sure charts are rendered properly.
+     * @method expireCache
+     * @memberof dc.baseMixin
+     * @instance
+     * @returns {dc.baseMixin}
+     */
+    _chart.expireCache = function () {
+        // do nothing in base, should be overridden by sub-function
+        return _chart;
+    };
+
+    /**
+     * Attach a dc.legend widget to this chart. The legend widget will automatically draw legend labels
+     * based on the color setting and names associated with each group.
+     * @method legend
+     * @memberof dc.baseMixin
+     * @instance
+     * @example
+     * chart.legend(dc.legend().x(400).y(10).itemHeight(13).gap(5))
+     * @param {dc.legend} [legend]
+     * @returns {dc.legend|dc.baseMixin}
+     */
+    _chart.legend = function (legend) {
+        if (!arguments.length) {
+            return _legend;
+        }
+        _legend = legend;
+        _legend.parent(_chart);
+        return _chart;
+    };
+
+    /**
+     * Returns the internal numeric ID of the chart.
+     * @method chartID
+     * @memberof dc.baseMixin
+     * @instance
+     * @returns {String}
+     */
+    _chart.chartID = function () {
+        return _chart.__dcFlag__;
+    };
+
+    /**
+     * Set chart options using a configuration object. Each key in the object will cause the method of
+     * the same name to be called with the value to set that attribute for the chart.
+     * @method options
+     * @memberof dc.baseMixin
+     * @instance
+     * @example
+     * chart.options({dimension: myDimension, group: myGroup});
+     * @param {{}} opts
+     * @returns {dc.baseMixin}
+     */
+    _chart.options = function (opts) {
+        var applyOptions = [
+            'anchor',
+            'group',
+            'xAxisLabel',
+            'yAxisLabel',
+            'stack',
+            'title',
+            'point',
+            'getColor',
+            'overlayGeoJson'
+        ];
+
+        for (var o in opts) {
+            if (typeof(_chart[o]) === 'function') {
+                if (opts[o] instanceof Array && applyOptions.indexOf(o) !== -1) {
+                    _chart[o].apply(_chart, opts[o]);
+                } else {
+                    _chart[o].call(_chart, opts[o]);
+                }
+            } else {
+                dc.logger.debug('Not a valid option setter name: ' + o);
+            }
+        }
+        return _chart;
+    };
+
+    /**
+     * All dc chart instance supports the following listeners.
+     * Supports the following events:
+     * * `renderlet` - This listener function will be invoked after transitions after redraw and render. Replaces the
+     * deprecated {@link dc.baseMixin#renderlet renderlet} method.
+     * * `pretransition` - Like `.on('renderlet', ...)` but the event is fired before transitions start.
+     * * `preRender` - This listener function will be invoked before chart rendering.
+     * * `postRender` - This listener function will be invoked after chart finish rendering including
+     * all renderlets' logic.
+     * * `preRedraw` - This listener function will be invoked before chart redrawing.
+     * * `postRedraw` - This listener function will be invoked after chart finish redrawing
+     * including all renderlets' logic.
+     * * `filtered` - This listener function will be invoked after a filter is applied, added or removed.
+     * * `zoomed` - This listener function will be invoked after a zoom is triggered.
+     * @method on
+     * @memberof dc.baseMixin
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Internals.md#dispatch_on d3.dispatch.on}
+     * @example
+     * .on('renderlet', function(chart, filter){...})
+     * .on('pretransition', function(chart, filter){...})
+     * .on('preRender', function(chart){...})
+     * .on('postRender', function(chart){...})
+     * .on('preRedraw', function(chart){...})
+     * .on('postRedraw', function(chart){...})
+     * .on('filtered', function(chart, filter){...})
+     * .on('zoomed', function(chart, filter){...})
+     * @param {String} event
+     * @param {Function} listener
+     * @returns {dc.baseMixin}
+     */
+    _chart.on = function (event, listener) {
+        _listeners.on(event, listener);
+        return _chart;
+    };
+
+    return _chart;
+};
+
+/**
+ * Margin is a mixin that provides margin utility functions for both the Row Chart and Coordinate Grid
+ * Charts.
+ * @name marginMixin
+ * @memberof dc
+ * @mixin
+ * @param {Object} _chart
+ * @returns {dc.marginMixin}
+ */
+dc.marginMixin = function (_chart) {
+    var _margin = {top: 10, right: 50, bottom: 30, left: 30};
+
+    /**
+     * Get or set the margins for a particular coordinate grid chart instance. The margins is stored as
+     * an associative Javascript array.
+     * @method margins
+     * @memberof dc.marginMixin
+     * @instance
+     * @example
+     * var leftMargin = chart.margins().left; // 30 by default
+     * chart.margins().left = 50;
+     * leftMargin = chart.margins().left; // now 50
+     * @param {{top: Number, right: Number, left: Number, bottom: Number}} [margins={top: 10, right: 50, bottom: 30, left: 30}]
+     * @returns {{top: Number, right: Number, left: Number, bottom: Number}|dc.marginMixin}
+     */
+    _chart.margins = function (margins) {
+        if (!arguments.length) {
+            return _margin;
+        }
+        _margin = margins;
+        return _chart;
+    };
+
+    _chart.effectiveWidth = function () {
+        return _chart.width() - _chart.margins().left - _chart.margins().right;
+    };
+
+    _chart.effectiveHeight = function () {
+        return _chart.height() - _chart.margins().top - _chart.margins().bottom;
+    };
+
+    return _chart;
+};
+
+/**
+ * The Color Mixin is an abstract chart functional class providing universal coloring support
+ * as a mix-in for any concrete chart implementation.
+ * @name colorMixin
+ * @memberof dc
+ * @mixin
+ * @param {Object} _chart
+ * @returns {dc.colorMixin}
+ */
+dc.colorMixin = function (_chart) {
+    var _colors = d3.scale.category20c();
+    var _defaultAccessor = true;
+
+    var _colorAccessor = function (d) { return _chart.keyAccessor()(d); };
+
+    /**
+     * Retrieve current color scale or set a new color scale. This methods accepts any function that
+     * operates like a d3 scale.
+     * @method colors
+     * @memberof dc.colorMixin
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Scales.md d3.scale}
+     * @example
+     * // alternate categorical scale
+     * chart.colors(d3.scale.category20b());
+     * // ordinal scale
+     * chart.colors(d3.scale.ordinal().range(['red','green','blue']));
+     * // convenience method, the same as above
+     * chart.ordinalColors(['red','green','blue']);
+     * // set a linear scale
+     * chart.linearColors(["#4575b4", "#ffffbf", "#a50026"]);
+     * @param {d3.scale} [colorScale=d3.scale.category20c()]
+     * @returns {d3.scale|dc.colorMixin}
+     */
+    _chart.colors = function (colorScale) {
+        if (!arguments.length) {
+            return _colors;
+        }
+        if (colorScale instanceof Array) {
+            _colors = d3.scale.quantize().range(colorScale); // deprecated legacy support, note: this fails for ordinal domains
+        } else {
+            _colors = d3.functor(colorScale);
+        }
+        return _chart;
+    };
+
+    /**
+     * Convenience method to set the color scale to
+     * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Ordinal-Scales.md#ordinal d3.scale.ordinal} with
+     * range `r`.
+     * @method ordinalColors
+     * @memberof dc.colorMixin
+     * @instance
+     * @param {Array<String>} r
+     * @returns {dc.colorMixin}
+     */
+    _chart.ordinalColors = function (r) {
+        return _chart.colors(d3.scale.ordinal().range(r));
+    };
+
+    /**
+     * Convenience method to set the color scale to an Hcl interpolated linear scale with range `r`.
+     * @method linearColors
+     * @memberof dc.colorMixin
+     * @instance
+     * @param {Array<Number>} r
+     * @returns {dc.colorMixin}
+     */
+    _chart.linearColors = function (r) {
+        return _chart.colors(d3.scale.linear()
+                             .range(r)
+                             .interpolate(d3.interpolateHcl));
+    };
+
+    /**
+     * Set or the get color accessor function. This function will be used to map a data point in a
+     * crossfilter group to a color value on the color scale. The default function uses the key
+     * accessor.
+     * @method colorAccessor
+     * @memberof dc.colorMixin
+     * @instance
+     * @example
+     * // default index based color accessor
+     * .colorAccessor(function (d, i){return i;})
+     * // color accessor for a multi-value crossfilter reduction
+     * .colorAccessor(function (d){return d.value.absGain;})
+     * @param {Function} [colorAccessor]
+     * @returns {Function|dc.colorMixin}
+     */
+    _chart.colorAccessor = function (colorAccessor) {
+        if (!arguments.length) {
+            return _colorAccessor;
+        }
+        _colorAccessor = colorAccessor;
+        _defaultAccessor = false;
+        return _chart;
+    };
+
+    // what is this?
+    _chart.defaultColorAccessor = function () {
+        return _defaultAccessor;
+    };
+
+    /**
+     * Set or get the current domain for the color mapping function. The domain must be supplied as an
+     * array.
+     *
+     * Note: previously this method accepted a callback function. Instead you may use a custom scale
+     * set by {@link dc.colorMixin#colors .colors}.
+     * @method colorDomain
+     * @memberof dc.colorMixin
+     * @instance
+     * @param {Array<String>} [domain]
+     * @returns {Array<String>|dc.colorMixin}
+     */
+    _chart.colorDomain = function (domain) {
+        if (!arguments.length) {
+            return _colors.domain();
+        }
+        _colors.domain(domain);
+        return _chart;
+    };
+
+    /**
+     * Set the domain by determining the min and max values as retrieved by
+     * {@link dc.colorMixin#colorAccessor .colorAccessor} over the chart's dataset.
+     * @method calculateColorDomain
+     * @memberof dc.colorMixin
+     * @instance
+     * @returns {dc.colorMixin}
+     */
+    _chart.calculateColorDomain = function () {
+        var newDomain = [d3.min(_chart.data(), _chart.colorAccessor()),
+                         d3.max(_chart.data(), _chart.colorAccessor())];
+        _colors.domain(newDomain);
+        return _chart;
+    };
+
+    /**
+     * Get the color for the datum d and counter i. This is used internally by charts to retrieve a color.
+     * @method getColor
+     * @memberof dc.colorMixin
+     * @instance
+     * @param {*} d
+     * @param {Number} [i]
+     * @returns {String}
+     */
+    _chart.getColor = function (d, i) {
+        return _colors(_colorAccessor.call(this, d, i));
+    };
+
+    /**
+     * **Deprecated.** Get/set the color calculator. This actually replaces the
+     * {@link dc.colorMixin#getColor getColor} method!
+     *
+     * This is not recommended, since using a {@link dc.colorMixin#colorAccessor colorAccessor} and
+     * color scale ({@link dc.colorMixin#colors .colors}) is more powerful and idiomatic d3.
+     * @method colorCalculator
+     * @memberof dc.colorMixin
+     * @instance
+     * @param {*} [colorCalculator]
+     * @returns {Function|dc.colorMixin}
+     */
+    _chart.colorCalculator = dc.logger.deprecate(function (colorCalculator) {
+        if (!arguments.length) {
+            return _chart.getColor;
+        }
+        _chart.getColor = colorCalculator;
+        return _chart;
+    }, 'colorMixin.colorCalculator has been deprecated. Please colorMixin.colors and colorMixin.colorAccessor instead');
+
+    return _chart;
+};
+
+/**
+ * Coordinate Grid is an abstract base chart designed to support a number of coordinate grid based
+ * concrete chart types, e.g. bar chart, line chart, and bubble chart.
+ * @name coordinateGridMixin
+ * @memberof dc
+ * @mixin
+ * @mixes dc.colorMixin
+ * @mixes dc.marginMixin
+ * @mixes dc.baseMixin
+ * @param {Object} _chart
+ * @returns {dc.coordinateGridMixin}
+ */
+dc.coordinateGridMixin = function (_chart) {
+    var GRID_LINE_CLASS = 'grid-line';
+    var HORIZONTAL_CLASS = 'horizontal';
+    var VERTICAL_CLASS = 'vertical';
+    var Y_AXIS_LABEL_CLASS = 'y-axis-label';
+    var X_AXIS_LABEL_CLASS = 'x-axis-label';
+    var DEFAULT_AXIS_LABEL_PADDING = 12;
+
+    _chart = dc.colorMixin(dc.marginMixin(dc.baseMixin(_chart)));
+
+    _chart.colors(d3.scale.category10());
+    _chart._mandatoryAttributes().push('x');
+    var _parent;
+    var _g;
+    var _chartBodyG;
+
+    var _x;
+    var _xOriginalDomain;
+    var _xAxis = d3.svg.axis().orient('bottom');
+    var _xUnits = dc.units.integers;
+    var _xAxisPadding = 0;
+    var _xAxisPaddingUnit = 'day';
+    var _xElasticity = false;
+    var _xAxisLabel;
+    var _xAxisLabelPadding = 0;
+    var _lastXDomain;
+
+    var _y;
+    var _yAxis = d3.svg.axis().orient('left');
+    var _yAxisPadding = 0;
+    var _yElasticity = false;
+    var _yAxisLabel;
+    var _yAxisLabelPadding = 0;
+
+    var _brush = d3.svg.brush();
+    var _brushOn = true;
+    var _round;
+
+    var _renderHorizontalGridLine = false;
+    var _renderVerticalGridLine = false;
+
+    var _refocused = false, _resizing = false;
+    var _unitCount;
+
+    var _zoomScale = [1, Infinity];
+    var _zoomOutRestrict = true;
+
+    var _zoom = d3.behavior.zoom().on('zoom', zoomHandler);
+    var _nullZoom = d3.behavior.zoom().on('zoom', null);
+    var _hasBeenMouseZoomable = false;
+
+    var _rangeChart;
+    var _focusChart;
+
+    var _mouseZoomable = false;
+    var _clipPadding = 0;
+
+    var _outerRangeBandPadding = 0.5;
+    var _rangeBandPadding = 0;
+
+    var _useRightYAxis = false;
+
+    /**
+     * When changing the domain of the x or y scale, it is necessary to tell the chart to recalculate
+     * and redraw the axes. (`.rescale()` is called automatically when the x or y scale is replaced
+     * with {@link dc.coordinateGridMixin+x .x()} or {@link dc.coordinateGridMixin#y .y()}, and has
+     * no effect on elastic scales.)
+     * @method rescale
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @returns {dc.coordinateGridMixin}
+     */
+    _chart.rescale = function () {
+        _unitCount = undefined;
+        _resizing = true;
+        return _chart;
+    };
+
+    _chart.resizing = function () {
+        return _resizing;
+    };
+
+    /**
+     * Get or set the range selection chart associated with this instance. Setting the range selection
+     * chart using this function will automatically update its selection brush when the current chart
+     * zooms in. In return the given range chart will also automatically attach this chart as its focus
+     * chart hence zoom in when range brush updates.
+     *
+     * Usually the range and focus charts will share a dimension. The range chart will set the zoom
+     * boundaries for the focus chart, so its dimension values must be compatible with the domain of
+     * the focus chart.
+     *
+     * See the [Nasdaq 100 Index](http://dc-js.github.com/dc.js/) example for this effect in action.
+     * @method rangeChart
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @param {dc.coordinateGridMixin} [rangeChart]
+     * @returns {dc.coordinateGridMixin}
+     */
+    _chart.rangeChart = function (rangeChart) {
+        if (!arguments.length) {
+            return _rangeChart;
+        }
+        _rangeChart = rangeChart;
+        _rangeChart.focusChart(_chart);
+        return _chart;
+    };
+
+    /**
+     * Get or set the scale extent for mouse zooms.
+     * @method zoomScale
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @param {Array<Number|Date>} [extent=[1, Infinity]]
+     * @returns {Array<Number|Date>|dc.coordinateGridMixin}
+     */
+    _chart.zoomScale = function (extent) {
+        if (!arguments.length) {
+            return _zoomScale;
+        }
+        _zoomScale = extent;
+        return _chart;
+    };
+
+    /**
+     * Get or set the zoom restriction for the chart. If true limits the zoom to origional domain of the chart.
+     * @method zoomOutRestrict
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @param {Boolean} [zoomOutRestrict=true]
+     * @returns {Boolean|dc.coordinateGridMixin}
+     */
+    _chart.zoomOutRestrict = function (zoomOutRestrict) {
+        if (!arguments.length) {
+            return _zoomOutRestrict;
+        }
+        _zoomScale[0] = zoomOutRestrict ? 1 : 0;
+        _zoomOutRestrict = zoomOutRestrict;
+        return _chart;
+    };
+
+    _chart._generateG = function (parent) {
+        if (parent === undefined) {
+            _parent = _chart.svg();
+        } else {
+            _parent = parent;
+        }
+
+        var href = window.location.href.split('#')[0];
+
+        _g = _parent.append('g');
+
+        _chartBodyG = _g.append('g').attr('class', 'chart-body')
+            .attr('transform', 'translate(' + _chart.margins().left + ', ' + _chart.margins().top + ')')
+            .attr('clip-path', 'url(' + href + '#' + getClipPathId() + ')');
+
+        return _g;
+    };
+
+    /**
+     * Get or set the root g element. This method is usually used to retrieve the g element in order to
+     * overlay custom svg drawing programatically. **Caution**: The root g element is usually generated
+     * by dc.js internals, and resetting it might produce unpredictable result.
+     * @method g
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @param {SVGElement} [gElement]
+     * @returns {SVGElement|dc.coordinateGridMixin}
+     */
+    _chart.g = function (gElement) {
+        if (!arguments.length) {
+            return _g;
+        }
+        _g = gElement;
+        return _chart;
+    };
+
+    /**
+     * Set or get mouse zoom capability flag (default: false). When turned on the chart will be
+     * zoomable using the mouse wheel. If the range selector chart is attached zooming will also update
+     * the range selection brush on the associated range selector chart.
+     * @method mouseZoomable
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @param {Boolean} [mouseZoomable=false]
+     * @returns {Boolean|dc.coordinateGridMixin}
+     */
+    _chart.mouseZoomable = function (mouseZoomable) {
+        if (!arguments.length) {
+            return _mouseZoomable;
+        }
+        _mouseZoomable = mouseZoomable;
+        return _chart;
+    };
+
+    /**
+     * Retrieve the svg group for the chart body.
+     * @method chartBodyG
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @param {SVGElement} [chartBodyG]
+     * @returns {SVGElement}
+     */
+    _chart.chartBodyG = function (chartBodyG) {
+        if (!arguments.length) {
+            return _chartBodyG;
+        }
+        _chartBodyG = chartBodyG;
+        return _chart;
+    };
+
+    /**
+     * **mandatory**
+     *
+     * Get or set the x scale. The x scale can be any d3
+     * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Quantitative-Scales.md quantitive scale} or
+     * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Ordinal-Scales.md ordinal scale}.
+     * @method x
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Scales.md d3.scale}
+     * @example
+     * // set x to a linear scale
+     * chart.x(d3.scale.linear().domain([-2500, 2500]))
+     * // set x to a time scale to generate histogram
+     * chart.x(d3.time.scale().domain([new Date(1985, 0, 1), new Date(2012, 11, 31)]))
+     * @param {d3.scale} [xScale]
+     * @returns {d3.scale|dc.coordinateGridMixin}
+     */
+    _chart.x = function (xScale) {
+        if (!arguments.length) {
+            return _x;
+        }
+        _x = xScale;
+        _xOriginalDomain = _x.domain();
+        _chart.rescale();
+        return _chart;
+    };
+
+    _chart.xOriginalDomain = function () {
+        return _xOriginalDomain;
+    };
+
+    /**
+     * Set or get the xUnits function. The coordinate grid chart uses the xUnits function to calculate
+     * the number of data projections on x axis such as the number of bars for a bar chart or the
+     * number of dots for a line chart. This function is expected to return a Javascript array of all
+     * data points on x axis, or the number of points on the axis. [d3 time range functions
+     * d3.time.days, d3.time.months, and
+     * d3.time.years](https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Intervals.md#aliases) are all valid xUnits
+     * function. dc.js also provides a few units function, see the {@link dc.units Units Namespace} for
+     * a list of built-in units functions.
+     * @method xUnits
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @todo Add docs for utilities
+     * @example
+     * // set x units to count days
+     * chart.xUnits(d3.time.days);
+     * // set x units to count months
+     * chart.xUnits(d3.time.months);
+     *
+     * // A custom xUnits function can be used as long as it follows the following interface:
+     * // units in integer
+     * function(start, end, xDomain) {
+     *      // simply calculates how many integers in the domain
+     *      return Math.abs(end - start);
+     * };
+     *
+     * // fixed units
+     * function(start, end, xDomain) {
+     *      // be aware using fixed units will disable the focus/zoom ability on the chart
+     *      return 1000;
+     * @param {Function} [xUnits=dc.units.integers]
+     * @returns {Function|dc.coordinateGridMixin}
+     */
+    _chart.xUnits = function (xUnits) {
+        if (!arguments.length) {
+            return _xUnits;
+        }
+        _xUnits = xUnits;
+        return _chart;
+    };
+
+    /**
+     * Set or get the x axis used by a particular coordinate grid chart instance. This function is most
+     * useful when x axis customization is required. The x axis in dc.js is an instance of a
+     * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#axis d3 axis object};
+     * therefore it supports any valid d3 axis manipulation.
+     *
+     * **Caution**: The x axis is usually generated internally by dc; resetting it may cause
+     * unexpected results. Note also that when used as a getter, this function is not chainable:
+     * it returns the axis, not the chart,
+     * {@link https://github.com/dc-js/dc.js/wiki/FAQ#why-does-everything-break-after-a-call-to-xaxis-or-yaxis
+     * so attempting to call chart functions after calling `.xAxis()` will fail}.
+     * @method xAxis
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#axis d3.svg.axis}
+     * @example
+     * // customize x axis tick format
+     * chart.xAxis().tickFormat(function(v) {return v + '%';});
+     * // customize x axis tick values
+     * chart.xAxis().tickValues([0, 100, 200, 300]);
+     * @param {d3.svg.axis} [xAxis=d3.svg.axis().orient('bottom')]
+     * @returns {d3.svg.axis|dc.coordinateGridMixin}
+     */
+    _chart.xAxis = function (xAxis) {
+        if (!arguments.length) {
+            return _xAxis;
+        }
+        _xAxis = xAxis;
+        return _chart;
+    };
+
+    /**
+     * Turn on/off elastic x axis behavior. If x axis elasticity is turned on, then the grid chart will
+     * attempt to recalculate the x axis range whenever a redraw event is triggered.
+     * @method elasticX
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @param {Boolean} [elasticX=false]
+     * @returns {Boolean|dc.coordinateGridMixin}
+     */
+    _chart.elasticX = function (elasticX) {
+        if (!arguments.length) {
+            return _xElasticity;
+        }
+        _xElasticity = elasticX;
+        return _chart;
+    };
+
+    /**
+     * Set or get x axis padding for the elastic x axis. The padding will be added to both end of the x
+     * axis if elasticX is turned on; otherwise it is ignored.
+     *
+     * Padding can be an integer or percentage in string (e.g. '10%'). Padding can be applied to
+     * number or date x axes.  When padding a date axis, an integer represents number of units being padded
+     * and a percentage string will be treated the same as an integer. The unit will be determined by the
+     * xAxisPaddingUnit variable.
+     * @method xAxisPadding
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @param {Number|String} [padding=0]
+     * @returns {Number|String|dc.coordinateGridMixin}
+     */
+    _chart.xAxisPadding = function (padding) {
+        if (!arguments.length) {
+            return _xAxisPadding;
+        }
+        _xAxisPadding = padding;
+        return _chart;
+    };
+
+    /**
+     * Set or get x axis padding unit for the elastic x axis. The padding unit will determine which unit to
+     * use when applying xAxis padding if elasticX is turned on and if x-axis uses a time dimension;
+     * otherwise it is ignored.
+     *
+     * Padding unit is a string that will be used when the padding is calculated. Available parameters are
+     * the available d3 time intervals; see
+     * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Time-Intervals.md#interval d3.time.interval}.
+     * @method xAxisPaddingUnit
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @param {String} [unit='days']
+     * @returns {String|dc.coordinateGridMixin}
+     */
+    _chart.xAxisPaddingUnit = function (unit) {
+        if (!arguments.length) {
+            return _xAxisPaddingUnit;
+        }
+        _xAxisPaddingUnit = unit;
+        return _chart;
+    };
+
+    /**
+     * Returns the number of units displayed on the x axis using the unit measure configured by
+     * {@link dc.coordinateGridMixin#xUnits xUnits}.
+     * @method xUnitCount
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @returns {Number}
+     */
+    _chart.xUnitCount = function () {
+        if (_unitCount === undefined) {
+            var units = _chart.xUnits()(_chart.x().domain()[0], _chart.x().domain()[1], _chart.x().domain());
+
+            if (units instanceof Array) {
+                _unitCount = units.length;
+            } else {
+                _unitCount = units;
+            }
+        }
+
+        return _unitCount;
+    };
+
+    /**
+     * Gets or sets whether the chart should be drawn with a right axis instead of a left axis. When
+     * used with a chart in a composite chart, allows both left and right Y axes to be shown on a
+     * chart.
+     * @method useRightYAxis
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @param {Boolean} [useRightYAxis=false]
+     * @returns {Boolean|dc.coordinateGridMixin}
+     */
+    _chart.useRightYAxis = function (useRightYAxis) {
+        if (!arguments.length) {
+            return _useRightYAxis;
+        }
+        _useRightYAxis = useRightYAxis;
+        return _chart;
+    };
+
+    /**
+     * Returns true if the chart is using ordinal xUnits ({@link dc.units.ordinal dc.units.ordinal}, or false
+     * otherwise. Most charts behave differently with ordinal data and use the result of this method to
+     * trigger the appropriate logic.
+     * @method isOrdinal
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @returns {Boolean}
+     */
+    _chart.isOrdinal = function () {
+        return _chart.xUnits() === dc.units.ordinal;
+    };
+
+    _chart._useOuterPadding = function () {
+        return true;
+    };
+
+    _chart._ordinalXDomain = function () {
+        var groups = _chart._computeOrderedGroups(_chart.data());
+        return groups.map(_chart.keyAccessor());
+    };
+
+    function compareDomains (d1, d2) {
+        return !d1 || !d2 || d1.length !== d2.length ||
+            d1.some(function (elem, i) { return (elem && d2[i]) ? elem.toString() !== d2[i].toString() : elem === d2[i]; });
+    }
+
+    function prepareXAxis (g, render) {
+        if (!_chart.isOrdinal()) {
+            if (_chart.elasticX()) {
+                _x.domain([_chart.xAxisMin(), _chart.xAxisMax()]);
+            }
+        } else { // _chart.isOrdinal()
+            if (_chart.elasticX() || _x.domain().length === 0) {
+                _x.domain(_chart._ordinalXDomain());
+            }
+        }
+
+        // has the domain changed?
+        var xdom = _x.domain();
+        if (render || compareDomains(_lastXDomain, xdom)) {
+            _chart.rescale();
+        }
+        _lastXDomain = xdom;
+
+        // please can't we always use rangeBands for bar charts?
+        if (_chart.isOrdinal()) {
+            _x.rangeBands([0, _chart.xAxisLength()], _rangeBandPadding,
+                          _chart._useOuterPadding() ? _outerRangeBandPadding : 0);
+        } else {
+            _x.range([0, _chart.xAxisLength()]);
+        }
+
+        _xAxis = _xAxis.scale(_chart.x());
+
+        renderVerticalGridLines(g);
+    }
+
+    _chart.renderXAxis = function (g) {
+        var axisXG = g.select('g.x');
+
+        if (axisXG.empty()) {
+            axisXG = g.append('g')
+                .attr('class', 'axis x')
+                .attr('transform', 'translate(' + _chart.margins().left + ',' + _chart._xAxisY() + ')');
+        }
+
+        var axisXLab = g.select('text.' + X_AXIS_LABEL_CLASS);
+        if (axisXLab.empty() && _chart.xAxisLabel()) {
+            axisXLab = g.append('text')
+                .attr('class', X_AXIS_LABEL_CLASS)
+                .attr('transform', 'translate(' + (_chart.margins().left + _chart.xAxisLength() / 2) + ',' +
+                      (_chart.height() - _xAxisLabelPadding) + ')')
+                .attr('text-anchor', 'middle');
+        }
+        if (_chart.xAxisLabel() && axisXLab.text() !== _chart.xAxisLabel()) {
+            axisXLab.text(_chart.xAxisLabel());
+        }
+
+        dc.transition(axisXG, _chart.transitionDuration(), _chart.transitionDelay())
+            .attr('transform', 'translate(' + _chart.margins().left + ',' + _chart._xAxisY() + ')')
+            .call(_xAxis);
+        dc.transition(axisXLab, _chart.transitionDuration(), _chart.transitionDelay())
+            .attr('transform', 'translate(' + (_chart.margins().left + _chart.xAxisLength() / 2) + ',' +
+                  (_chart.height() - _xAxisLabelPadding) + ')');
+    };
+
+    function renderVerticalGridLines (g) {
+        var gridLineG = g.select('g.' + VERTICAL_CLASS);
+
+        if (_renderVerticalGridLine) {
+            if (gridLineG.empty()) {
+                gridLineG = g.insert('g', ':first-child')
+                    .attr('class', GRID_LINE_CLASS + ' ' + VERTICAL_CLASS)
+                    .attr('transform', 'translate(' + _chart.margins().left + ',' + _chart.margins().top + ')');
+            }
+
+            var ticks = _xAxis.tickValues() ? _xAxis.tickValues() :
+                    (typeof _x.ticks === 'function' ? _x.ticks(_xAxis.ticks()[0]) : _x.domain());
+
+            var lines = gridLineG.selectAll('line')
+                .data(ticks);
+
+            // enter
+            var linesGEnter = lines.enter()
+                .append('line')
+                .attr('x1', function (d) {
+                    return _x(d);
+                })
+                .attr('y1', _chart._xAxisY() - _chart.margins().top)
+                .attr('x2', function (d) {
+                    return _x(d);
+                })
+                .attr('y2', 0)
+                .attr('opacity', 0);
+            dc.transition(linesGEnter, _chart.transitionDuration(), _chart.transitionDelay())
+                .attr('opacity', 1);
+
+            // update
+            dc.transition(lines, _chart.transitionDuration(), _chart.transitionDelay())
+                .attr('x1', function (d) {
+                    return _x(d);
+                })
+                .attr('y1', _chart._xAxisY() - _chart.margins().top)
+                .attr('x2', function (d) {
+                    return _x(d);
+                })
+                .attr('y2', 0);
+
+            // exit
+            lines.exit().remove();
+        } else {
+            gridLineG.selectAll('line').remove();
+        }
+    }
+
+    _chart._xAxisY = function () {
+        return (_chart.height() - _chart.margins().bottom);
+    };
+
+    _chart.xAxisLength = function () {
+        return _chart.effectiveWidth();
+    };
+
+    /**
+     * Set or get the x axis label. If setting the label, you may optionally include additional padding to
+     * the margin to make room for the label. By default the padded is set to 12 to accomodate the text height.
+     * @method xAxisLabel
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @param {String} [labelText]
+     * @param {Number} [padding=12]
+     * @returns {String}
+     */
+    _chart.xAxisLabel = function (labelText, padding) {
+        if (!arguments.length) {
+            return _xAxisLabel;
+        }
+        _xAxisLabel = labelText;
+        _chart.margins().bottom -= _xAxisLabelPadding;
+        _xAxisLabelPadding = (padding === undefined) ? DEFAULT_AXIS_LABEL_PADDING : padding;
+        _chart.margins().bottom += _xAxisLabelPadding;
+        return _chart;
+    };
+
+    _chart._prepareYAxis = function (g) {
+        if (_y === undefined || _chart.elasticY()) {
+            if (_y === undefined) {
+                _y = d3.scale.linear();
+            }
+            var min = _chart.yAxisMin() || 0,
+                max = _chart.yAxisMax() || 0;
+            _y.domain([min, max]).rangeRound([_chart.yAxisHeight(), 0]);
+        }
+
+        _y.range([_chart.yAxisHeight(), 0]);
+        _yAxis = _yAxis.scale(_y);
+
+        if (_useRightYAxis) {
+            _yAxis.orient('right');
+        }
+
+        _chart._renderHorizontalGridLinesForAxis(g, _y, _yAxis);
+    };
+
+    _chart.renderYAxisLabel = function (axisClass, text, rotation, labelXPosition) {
+        labelXPosition = labelXPosition || _yAxisLabelPadding;
+
+        var axisYLab = _chart.g().select('text.' + Y_AXIS_LABEL_CLASS + '.' + axisClass + '-label');
+        var labelYPosition = (_chart.margins().top + _chart.yAxisHeight() / 2);
+        if (axisYLab.empty() && text) {
+            axisYLab = _chart.g().append('text')
+                .attr('transform', 'translate(' + labelXPosition + ',' + labelYPosition + '),rotate(' + rotation + ')')
+                .attr('class', Y_AXIS_LABEL_CLASS + ' ' + axisClass + '-label')
+                .attr('text-anchor', 'middle')
+                .text(text);
+        }
+        if (text && axisYLab.text() !== text) {
+            axisYLab.text(text);
+        }
+        dc.transition(axisYLab, _chart.transitionDuration(), _chart.transitionDelay())
+            .attr('transform', 'translate(' + labelXPosition + ',' + labelYPosition + '),rotate(' + rotation + ')');
+    };
+
+    _chart.renderYAxisAt = function (axisClass, axis, position) {
+        var axisYG = _chart.g().select('g.' + axisClass);
+        if (axisYG.empty()) {
+            axisYG = _chart.g().append('g')
+                .attr('class', 'axis ' + axisClass)
+                .attr('transform', 'translate(' + position + ',' + _chart.margins().top + ')');
+        }
+
+        dc.transition(axisYG, _chart.transitionDuration(), _chart.transitionDelay())
+            .attr('transform', 'translate(' + position + ',' + _chart.margins().top + ')')
+            .call(axis);
+    };
+
+    _chart.renderYAxis = function () {
+        var axisPosition = _useRightYAxis ? (_chart.width() - _chart.margins().right) : _chart._yAxisX();
+        _chart.renderYAxisAt('y', _yAxis, axisPosition);
+        var labelPosition = _useRightYAxis ? (_chart.width() - _yAxisLabelPadding) : _yAxisLabelPadding;
+        var rotation = _useRightYAxis ? 90 : -90;
+        _chart.renderYAxisLabel('y', _chart.yAxisLabel(), rotation, labelPosition);
+    };
+
+    _chart._renderHorizontalGridLinesForAxis = function (g, scale, axis) {
+        var gridLineG = g.select('g.' + HORIZONTAL_CLASS);
+
+        if (_renderHorizontalGridLine) {
+            var ticks = axis.tickValues() ? axis.tickValues() : scale.ticks(axis.ticks()[0]);
+
+            if (gridLineG.empty()) {
+                gridLineG = g.insert('g', ':first-child')
+                    .attr('class', GRID_LINE_CLASS + ' ' + HORIZONTAL_CLASS)
+                    .attr('transform', 'translate(' + _chart.margins().left + ',' + _chart.margins().top + ')');
+            }
+
+            var lines = gridLineG.selectAll('line')
+                .data(ticks);
+
+            // enter
+            var linesGEnter = lines.enter()
+                .append('line')
+                .attr('x1', 1)
+                .attr('y1', function (d) {
+                    return scale(d);
+                })
+                .attr('x2', _chart.xAxisLength())
+                .attr('y2', function (d) {
+                    return scale(d);
+                })
+                .attr('opacity', 0);
+            dc.transition(linesGEnter, _chart.transitionDuration(), _chart.transitionDelay())
+                .attr('opacity', 1);
+
+            // update
+            dc.transition(lines, _chart.transitionDuration(), _chart.transitionDelay())
+                .attr('x1', 1)
+                .attr('y1', function (d) {
+                    return scale(d);
+                })
+                .attr('x2', _chart.xAxisLength())
+                .attr('y2', function (d) {
+                    return scale(d);
+                });
+
+            // exit
+            lines.exit().remove();
+        } else {
+            gridLineG.selectAll('line').remove();
+        }
+    };
+
+    _chart._yAxisX = function () {
+        return _chart.useRightYAxis() ? _chart.width() - _chart.margins().right : _chart.margins().left;
+    };
+
+    /**
+     * Set or get the y axis label. If setting the label, you may optionally include additional padding
+     * to the margin to make room for the label. By default the padding is set to 12 to accommodate the
+     * text height.
+     * @method yAxisLabel
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @param {String} [labelText]
+     * @param {Number} [padding=12]
+     * @returns {String|dc.coordinateGridMixin}
+     */
+    _chart.yAxisLabel = function (labelText, padding) {
+        if (!arguments.length) {
+            return _yAxisLabel;
+        }
+        _yAxisLabel = labelText;
+        _chart.margins().left -= _yAxisLabelPadding;
+        _yAxisLabelPadding = (padding === undefined) ? DEFAULT_AXIS_LABEL_PADDING : padding;
+        _chart.margins().left += _yAxisLabelPadding;
+        return _chart;
+    };
+
+    /**
+     * Get or set the y scale. The y scale is typically automatically determined by the chart implementation.
+     * @method y
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Scales.md d3.scale}
+     * @param {d3.scale} [yScale]
+     * @returns {d3.scale|dc.coordinateGridMixin}
+     */
+    _chart.y = function (yScale) {
+        if (!arguments.length) {
+            return _y;
+        }
+        _y = yScale;
+        _chart.rescale();
+        return _chart;
+    };
+
+    /**
+     * Set or get the y axis used by the coordinate grid chart instance. This function is most useful
+     * when y axis customization is required. The y axis in dc.js is simply an instance of a [d3 axis
+     * object](https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#axis); therefore it supports any
+     * valid d3 axis manipulation.
+     *
+     * **Caution**: The y axis is usually generated internally by dc; resetting it may cause
+     * unexpected results.  Note also that when used as a getter, this function is not chainable: it
+     * returns the axis, not the chart,
+     * {@link https://github.com/dc-js/dc.js/wiki/FAQ#why-does-everything-break-after-a-call-to-xaxis-or-yaxis
+     * so attempting to call chart functions after calling `.yAxis()` will fail}.
+     * @method yAxis
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#axis d3.svg.axis}
+     * @example
+     * // customize y axis tick format
+     * chart.yAxis().tickFormat(function(v) {return v + '%';});
+     * // customize y axis tick values
+     * chart.yAxis().tickValues([0, 100, 200, 300]);
+     * @param {d3.svg.axis} [yAxis=d3.svg.axis().orient('left')]
+     * @returns {d3.svg.axis|dc.coordinateGridMixin}
+     */
+    _chart.yAxis = function (yAxis) {
+        if (!arguments.length) {
+            return _yAxis;
+        }
+        _yAxis = yAxis;
+        return _chart;
+    };
+
+    /**
+     * Turn on/off elastic y axis behavior. If y axis elasticity is turned on, then the grid chart will
+     * attempt to recalculate the y axis range whenever a redraw event is triggered.
+     * @method elasticY
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @param {Boolean} [elasticY=false]
+     * @returns {Boolean|dc.coordinateGridMixin}
+     */
+    _chart.elasticY = function (elasticY) {
+        if (!arguments.length) {
+            return _yElasticity;
+        }
+        _yElasticity = elasticY;
+        return _chart;
+    };
+
+    /**
+     * Turn on/off horizontal grid lines.
+     * @method renderHorizontalGridLines
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @param {Boolean} [renderHorizontalGridLines=false]
+     * @returns {Boolean|dc.coordinateGridMixin}
+     */
+    _chart.renderHorizontalGridLines = function (renderHorizontalGridLines) {
+        if (!arguments.length) {
+            return _renderHorizontalGridLine;
+        }
+        _renderHorizontalGridLine = renderHorizontalGridLines;
+        return _chart;
+    };
+
+    /**
+     * Turn on/off vertical grid lines.
+     * @method renderVerticalGridLines
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @param {Boolean} [renderVerticalGridLines=false]
+     * @returns {Boolean|dc.coordinateGridMixin}
+     */
+    _chart.renderVerticalGridLines = function (renderVerticalGridLines) {
+        if (!arguments.length) {
+            return _renderVerticalGridLine;
+        }
+        _renderVerticalGridLine = renderVerticalGridLines;
+        return _chart;
+    };
+
+    /**
+     * Calculates the minimum x value to display in the chart. Includes xAxisPadding if set.
+     * @method xAxisMin
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @returns {*}
+     */
+    _chart.xAxisMin = function () {
+        var min = d3.min(_chart.data(), function (e) {
+            return _chart.keyAccessor()(e);
+        });
+        return dc.utils.subtract(min, _xAxisPadding, _xAxisPaddingUnit);
+    };
+
+    /**
+     * Calculates the maximum x value to display in the chart. Includes xAxisPadding if set.
+     * @method xAxisMax
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @returns {*}
+     */
+    _chart.xAxisMax = function () {
+        var max = d3.max(_chart.data(), function (e) {
+            return _chart.keyAccessor()(e);
+        });
+        return dc.utils.add(max, _xAxisPadding, _xAxisPaddingUnit);
+    };
+
+    /**
+     * Calculates the minimum y value to display in the chart. Includes yAxisPadding if set.
+     * @method yAxisMin
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @returns {*}
+     */
+    _chart.yAxisMin = function () {
+        var min = d3.min(_chart.data(), function (e) {
+            return _chart.valueAccessor()(e);
+        });
+        return dc.utils.subtract(min, _yAxisPadding);
+    };
+
+    /**
+     * Calculates the maximum y value to display in the chart. Includes yAxisPadding if set.
+     * @method yAxisMax
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @returns {*}
+     */
+    _chart.yAxisMax = function () {
+        var max = d3.max(_chart.data(), function (e) {
+            return _chart.valueAccessor()(e);
+        });
+        return dc.utils.add(max, _yAxisPadding);
+    };
+
+    /**
+     * Set or get y axis padding for the elastic y axis. The padding will be added to the top and
+     * bottom of the y axis if elasticY is turned on; otherwise it is ignored.
+     *
+     * Padding can be an integer or percentage in string (e.g. '10%'). Padding can be applied to
+     * number or date axes. When padding a date axis, an integer represents number of days being padded
+     * and a percentage string will be treated the same as an integer.
+     * @method yAxisPadding
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @param {Number|String} [padding=0]
+     * @returns {Number|dc.coordinateGridMixin}
+     */
+    _chart.yAxisPadding = function (padding) {
+        if (!arguments.length) {
+            return _yAxisPadding;
+        }
+        _yAxisPadding = padding;
+        return _chart;
+    };
+
+    _chart.yAxisHeight = function () {
+        return _chart.effectiveHeight();
+    };
+
+    /**
+     * Set or get the rounding function used to quantize the selection when brushing is enabled.
+     * @method round
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @example
+     * // set x unit round to by month, this will make sure range selection brush will
+     * // select whole months
+     * chart.round(d3.time.month.round);
+     * @param {Function} [round]
+     * @returns {Function|dc.coordinateGridMixin}
+     */
+    _chart.round = function (round) {
+        if (!arguments.length) {
+            return _round;
+        }
+        _round = round;
+        return _chart;
+    };
+
+    _chart._rangeBandPadding = function (_) {
+        if (!arguments.length) {
+            return _rangeBandPadding;
+        }
+        _rangeBandPadding = _;
+        return _chart;
+    };
+
+    _chart._outerRangeBandPadding = function (_) {
+        if (!arguments.length) {
+            return _outerRangeBandPadding;
+        }
+        _outerRangeBandPadding = _;
+        return _chart;
+    };
+
+    dc.override(_chart, 'filter', function (_) {
+        if (!arguments.length) {
+            return _chart._filter();
+        }
+
+        _chart._filter(_);
+
+        if (_) {
+            _chart.brush().extent(_);
+        } else {
+            _chart.brush().clear();
+        }
+
+        return _chart;
+    });
+
+    _chart.brush = function (_) {
+        if (!arguments.length) {
+            return _brush;
+        }
+        _brush = _;
+        return _chart;
+    };
+
+    function brushHeight () {
+        return _chart._xAxisY() - _chart.margins().top;
+    }
+
+    _chart.renderBrush = function (g) {
+        if (_brushOn) {
+            _brush.on('brush', _chart._brushing);
+            _brush.on('brushstart', _chart._disableMouseZoom);
+            _brush.on('brushend', configureMouseZoom);
+
+            var gBrush = g.append('g')
+                .attr('class', 'brush')
+                .attr('transform', 'translate(' + _chart.margins().left + ',' + _chart.margins().top + ')')
+                .call(_brush.x(_chart.x()));
+            _chart.setBrushY(gBrush, false);
+            _chart.setHandlePaths(gBrush);
+
+            if (_chart.hasFilter()) {
+                _chart.redrawBrush(g, false);
+            }
+        }
+    };
+
+    _chart.setHandlePaths = function (gBrush) {
+        gBrush.selectAll('.resize').append('path').attr('d', _chart.resizeHandlePath);
+    };
+
+    _chart.setBrushY = function (gBrush) {
+        gBrush.selectAll('rect')
+            .attr('height', brushHeight());
+        gBrush.selectAll('.resize path')
+            .attr('d', _chart.resizeHandlePath);
+    };
+
+    _chart.extendBrush = function () {
+        var extent = _brush.extent();
+        if (_chart.round()) {
+            extent[0] = extent.map(_chart.round())[0];
+            extent[1] = extent.map(_chart.round())[1];
+
+            _g.select('.brush')
+                .call(_brush.extent(extent));
+        }
+        return extent;
+    };
+
+    _chart.brushIsEmpty = function (extent) {
+        return _brush.empty() || !extent || extent[1] <= extent[0];
+    };
+
+    _chart._brushing = function () {
+        var extent = _chart.extendBrush();
+
+        _chart.redrawBrush(_g, false);
+
+        if (_chart.brushIsEmpty(extent)) {
+            dc.events.trigger(function () {
+                _chart.filter(null);
+                _chart.redrawGroup();
+            }, dc.constants.EVENT_DELAY);
+        } else {
+            var rangedFilter = dc.filters.RangedFilter(extent[0], extent[1]);
+
+            dc.events.trigger(function () {
+                _chart.replaceFilter(rangedFilter);
+                _chart.redrawGroup();
+            }, dc.constants.EVENT_DELAY);
+        }
+    };
+
+    _chart.redrawBrush = function (g, doTransition) {
+        if (_brushOn) {
+            if (_chart.filter() && _chart.brush().empty()) {
+                _chart.brush().extent(_chart.filter());
+            }
+
+            var gBrush = dc.optionalTransition(doTransition, _chart.transitionDuration(), _chart.transitionDelay())(g.select('g.brush'));
+            _chart.setBrushY(gBrush);
+            gBrush.call(_chart.brush()
+                      .x(_chart.x())
+                      .extent(_chart.brush().extent()));
+        }
+
+        _chart.fadeDeselectedArea();
+    };
+
+    _chart.fadeDeselectedArea = function () {
+        // do nothing, sub-chart should override this function
+    };
+
+    // borrowed from Crossfilter example
+    _chart.resizeHandlePath = function (d) {
+        var e = +(d === 'e'), x = e ? 1 : -1, y = brushHeight() / 3;
+        return 'M' + (0.5 * x) + ',' + y +
+            'A6,6 0 0 ' + e + ' ' + (6.5 * x) + ',' + (y + 6) +
+            'V' + (2 * y - 6) +
+            'A6,6 0 0 ' + e + ' ' + (0.5 * x) + ',' + (2 * y) +
+            'Z' +
+            'M' + (2.5 * x) + ',' + (y + 8) +
+            'V' + (2 * y - 8) +
+            'M' + (4.5 * x) + ',' + (y + 8) +
+            'V' + (2 * y - 8);
+    };
+
+    function getClipPathId () {
+        return _chart.anchorName().replace(/[ .#=\[\]"]/g, '-') + '-clip';
+    }
+
+    /**
+     * Get or set the padding in pixels for the clip path. Once set padding will be applied evenly to
+     * the top, left, right, and bottom when the clip path is generated. If set to zero, the clip area
+     * will be exactly the chart body area minus the margins.
+     * @method clipPadding
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @param {Number} [padding=5]
+     * @returns {Number|dc.coordinateGridMixin}
+     */
+    _chart.clipPadding = function (padding) {
+        if (!arguments.length) {
+            return _clipPadding;
+        }
+        _clipPadding = padding;
+        return _chart;
+    };
+
+    function generateClipPath () {
+        var defs = dc.utils.appendOrSelect(_parent, 'defs');
+        // cannot select <clippath> elements; bug in WebKit, must select by id
+        // https://groups.google.com/forum/#!topic/d3-js/6EpAzQ2gU9I
+        var id = getClipPathId();
+        var chartBodyClip = dc.utils.appendOrSelect(defs, '#' + id, 'clipPath').attr('id', id);
+
+        var padding = _clipPadding * 2;
+
+        dc.utils.appendOrSelect(chartBodyClip, 'rect')
+            .attr('width', _chart.xAxisLength() + padding)
+            .attr('height', _chart.yAxisHeight() + padding)
+            .attr('transform', 'translate(-' + _clipPadding + ', -' + _clipPadding + ')');
+    }
+
+    _chart._preprocessData = function () {};
+
+    _chart._doRender = function () {
+        _chart.resetSvg();
+
+        _chart._preprocessData();
+
+        _chart._generateG();
+        generateClipPath();
+
+        drawChart(true);
+
+        configureMouseZoom();
+
+        return _chart;
+    };
+
+    _chart._doRedraw = function () {
+        _chart._preprocessData();
+
+        drawChart(false);
+        generateClipPath();
+
+        return _chart;
+    };
+
+    function drawChart (render) {
+        if (_chart.isOrdinal()) {
+            _brushOn = false;
+        }
+
+        prepareXAxis(_chart.g(), render);
+        _chart._prepareYAxis(_chart.g());
+
+        _chart.plotData();
+
+        if (_chart.elasticX() || _resizing || render) {
+            _chart.renderXAxis(_chart.g());
+        }
+
+        if (_chart.elasticY() || _resizing || render) {
+            _chart.renderYAxis(_chart.g());
+        }
+
+        if (render) {
+            _chart.renderBrush(_chart.g(), false);
+        } else {
+            _chart.redrawBrush(_chart.g(), _resizing);
+        }
+        _chart.fadeDeselectedArea();
+        _resizing = false;
+    }
+
+    function configureMouseZoom () {
+        if (_mouseZoomable) {
+            _chart._enableMouseZoom();
+        } else if (_hasBeenMouseZoomable) {
+            _chart._disableMouseZoom();
+        }
+    }
+
+    _chart._enableMouseZoom = function () {
+        _hasBeenMouseZoomable = true;
+        _zoom.x(_chart.x())
+            .scaleExtent(_zoomScale)
+            .size([_chart.width(), _chart.height()])
+            .duration(_chart.transitionDuration());
+        _chart.root().call(_zoom);
+    };
+
+    _chart._disableMouseZoom = function () {
+        _chart.root().call(_nullZoom);
+    };
+
+    function zoomHandler () {
+        _refocused = true;
+        if (_zoomOutRestrict) {
+            var constraint = _xOriginalDomain;
+            if (_rangeChart) {
+                constraint = intersectExtents(constraint, _rangeChart.x().domain());
+            }
+            var constrained = constrainExtent(_chart.x().domain(), constraint);
+            if (constrained) {
+                _chart.x().domain(constrained);
+            }
+        }
+
+        var domain = _chart.x().domain();
+        var domFilter = dc.filters.RangedFilter(domain[0], domain[1]);
+
+        _chart.replaceFilter(domFilter);
+        _chart.rescale();
+        _chart.redraw();
+
+        if (_rangeChart && !rangesEqual(_chart.filter(), _rangeChart.filter())) {
+            dc.events.trigger(function () {
+                _rangeChart.replaceFilter(domFilter);
+                _rangeChart.redraw();
+            });
+        }
+
+        _chart._invokeZoomedListener();
+
+        dc.events.trigger(function () {
+            _chart.redrawGroup();
+        }, dc.constants.EVENT_DELAY);
+
+        _refocused = !rangesEqual(domain, _xOriginalDomain);
+    }
+
+    function intersectExtents (ext1, ext2) {
+        if (ext1[0] > ext2[1] || ext1[1] < ext2[0]) {
+            console.warn('could not intersect extents');
+        }
+        return [Math.max(ext1[0], ext2[0]), Math.min(ext1[1], ext2[1])];
+    }
+
+    function constrainExtent (extent, constraint) {
+        var size = extent[1] - extent[0];
+        if (extent[0] < constraint[0]) {
+            return [constraint[0], Math.min(constraint[1], dc.utils.add(constraint[0], size, 'millis'))];
+        } else if (extent[1] > constraint[1]) {
+            return [Math.max(constraint[0], dc.utils.subtract(constraint[1], size, 'millis')), constraint[1]];
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Zoom this chart to focus on the given range. The given range should be an array containing only
+     * 2 elements (`[start, end]`) defining a range in the x domain. If the range is not given or set
+     * to null, then the zoom will be reset. _For focus to work elasticX has to be turned off;
+     * otherwise focus will be ignored.
+     * @method focus
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @example
+     * chart.on('renderlet', function(chart) {
+     *     // smooth the rendering through event throttling
+     *     dc.events.trigger(function(){
+     *          // focus some other chart to the range selected by user on this chart
+     *          someOtherChart.focus(chart.filter());
+     *     });
+     * })
+     * @param {Array<Number>} [range]
+     */
+    _chart.focus = function (range) {
+        if (hasRangeSelected(range)) {
+            _chart.x().domain(range);
+        } else {
+            _chart.x().domain(_xOriginalDomain);
+        }
+
+        _zoom.x(_chart.x());
+        zoomHandler();
+    };
+
+    _chart.refocused = function () {
+        return _refocused;
+    };
+
+    _chart.focusChart = function (c) {
+        if (!arguments.length) {
+            return _focusChart;
+        }
+        _focusChart = c;
+        _chart.on('filtered', function (chart) {
+            if (!chart.filter()) {
+                dc.events.trigger(function () {
+                    _focusChart.x().domain(_focusChart.xOriginalDomain());
+                });
+            } else if (!rangesEqual(chart.filter(), _focusChart.filter())) {
+                dc.events.trigger(function () {
+                    _focusChart.focus(chart.filter());
+                });
+            }
+        });
+        return _chart;
+    };
+
+    function rangesEqual (range1, range2) {
+        if (!range1 && !range2) {
+            return true;
+        } else if (!range1 || !range2) {
+            return false;
+        } else if (range1.length === 0 && range2.length === 0) {
+            return true;
+        } else if (range1[0].valueOf() === range2[0].valueOf() &&
+            range1[1].valueOf() === range2[1].valueOf()) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Turn on/off the brush-based range filter. When brushing is on then user can drag the mouse
+     * across a chart with a quantitative scale to perform range filtering based on the extent of the
+     * brush, or click on the bars of an ordinal bar chart or slices of a pie chart to filter and
+     * un-filter them. However turning on the brush filter will disable other interactive elements on
+     * the chart such as highlighting, tool tips, and reference lines. Zooming will still be possible
+     * if enabled, but only via scrolling (panning will be disabled.)
+     * @method brushOn
+     * @memberof dc.coordinateGridMixin
+     * @instance
+     * @param {Boolean} [brushOn=true]
+     * @returns {Boolean|dc.coordinateGridMixin}
+     */
+    _chart.brushOn = function (brushOn) {
+        if (!arguments.length) {
+            return _brushOn;
+        }
+        _brushOn = brushOn;
+        return _chart;
+    };
+
+    function hasRangeSelected (range) {
+        return range instanceof Array && range.length > 1;
+    }
+
+    return _chart;
+};
+
+/**
+ * Stack Mixin is an mixin that provides cross-chart support of stackability using d3.layout.stack.
+ * @name stackMixin
+ * @memberof dc
+ * @mixin
+ * @param {Object} _chart
+ * @returns {dc.stackMixin}
+ */
+dc.stackMixin = function (_chart) {
+
+    function prepareValues (layer, layerIdx) {
+        var valAccessor = layer.accessor || _chart.valueAccessor();
+        layer.name = String(layer.name || layerIdx);
+        layer.values = layer.group.all().map(function (d, i) {
+            return {
+                x: _chart.keyAccessor()(d, i),
+                y: layer.hidden ? null : valAccessor(d, i),
+                data: d,
+                layer: layer.name,
+                hidden: layer.hidden
+            };
+        });
+
+        layer.values = layer.values.filter(domainFilter());
+        return layer.values;
+    }
+
+    var _stackLayout = d3.layout.stack()
+        .values(prepareValues);
+
+    var _stack = [];
+    var _titles = {};
+
+    var _hidableStacks = false;
+    var _evadeDomainFilter = false;
+
+    function domainFilter () {
+        if (!_chart.x() || _evadeDomainFilter) {
+            return d3.functor(true);
+        }
+        var xDomain = _chart.x().domain();
+        if (_chart.isOrdinal()) {
+            // TODO #416
+            //var domainSet = d3.set(xDomain);
+            return function () {
+                return true; //domainSet.has(p.x);
+            };
+        }
+        if (_chart.elasticX()) {
+            return function () { return true; };
+        }
+        return function (p) {
+            //return true;
+            return p.x >= xDomain[0] && p.x <= xDomain[xDomain.length - 1];
+        };
+    }
+
+    /**
+     * Stack a new crossfilter group onto this chart with an optional custom value accessor. All stacks
+     * in the same chart will share the same key accessor and therefore the same set of keys.
+     *
+     * For example, in a stacked bar chart, the bars of each stack will be positioned using the same set
+     * of keys on the x axis, while stacked vertically. If name is specified then it will be used to
+     * generate the legend label.
+     * @method stack
+     * @memberof dc.stackMixin
+     * @instance
+     * @see {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#group-map-reduce crossfilter.group}
+     * @example
+     * // stack group using default accessor
+     * chart.stack(valueSumGroup)
+     * // stack group using custom accessor
+     * .stack(avgByDayGroup, function(d){return d.value.avgByDay;});
+     * @param {crossfilter.group} group
+     * @param {String} [name]
+     * @param {Function} [accessor]
+     * @returns {Array<{group: crossfilter.group, name: String, accessor: Function}>|dc.stackMixin}
+     */
+    _chart.stack = function (group, name, accessor) {
+        if (!arguments.length) {
+            return _stack;
+        }
+
+        if (arguments.length <= 2) {
+            accessor = name;
+        }
+
+        var layer = {group: group};
+        if (typeof name === 'string') {
+            layer.name = name;
+        }
+        if (typeof accessor === 'function') {
+            layer.accessor = accessor;
+        }
+        _stack.push(layer);
+
+        return _chart;
+    };
+
+    dc.override(_chart, 'group', function (g, n, f) {
+        if (!arguments.length) {
+            return _chart._group();
+        }
+        _stack = [];
+        _titles = {};
+        _chart.stack(g, n);
+        if (f) {
+            _chart.valueAccessor(f);
+        }
+        return _chart._group(g, n);
+    });
+
+    /**
+     * Allow named stacks to be hidden or shown by clicking on legend items.
+     * This does not affect the behavior of hideStack or showStack.
+     * @method hidableStacks
+     * @memberof dc.stackMixin
+     * @instance
+     * @param {Boolean} [hidableStacks=false]
+     * @returns {Boolean|dc.stackMixin}
+     */
+    _chart.hidableStacks = function (hidableStacks) {
+        if (!arguments.length) {
+            return _hidableStacks;
+        }
+        _hidableStacks = hidableStacks;
+        return _chart;
+    };
+
+    function findLayerByName (n) {
+        var i = _stack.map(dc.pluck('name')).indexOf(n);
+        return _stack[i];
+    }
+
+    /**
+     * Hide all stacks on the chart with the given name.
+     * The chart must be re-rendered for this change to appear.
+     * @method hideStack
+     * @memberof dc.stackMixin
+     * @instance
+     * @param {String} stackName
+     * @returns {dc.stackMixin}
+     */
+    _chart.hideStack = function (stackName) {
+        var layer = findLayerByName(stackName);
+        if (layer) {
+            layer.hidden = true;
+        }
+        return _chart;
+    };
+
+    /**
+     * Show all stacks on the chart with the given name.
+     * The chart must be re-rendered for this change to appear.
+     * @method showStack
+     * @memberof dc.stackMixin
+     * @instance
+     * @param {String} stackName
+     * @returns {dc.stackMixin}
+     */
+    _chart.showStack = function (stackName) {
+        var layer = findLayerByName(stackName);
+        if (layer) {
+            layer.hidden = false;
+        }
+        return _chart;
+    };
+
+    _chart.getValueAccessorByIndex = function (index) {
+        return _stack[index].accessor || _chart.valueAccessor();
+    };
+
+    _chart.yAxisMin = function () {
+        var min = d3.min(flattenStack(), function (p) {
+            return (p.y < 0) ? (p.y + p.y0) : p.y0;
+        });
+
+        return dc.utils.subtract(min, _chart.yAxisPadding());
+
+    };
+
+    _chart.yAxisMax = function () {
+        var max = d3.max(flattenStack(), function (p) {
+            return (p.y > 0) ? (p.y + p.y0) : p.y0;
+        });
+
+        return dc.utils.add(max, _chart.yAxisPadding());
+    };
+
+    function flattenStack () {
+        var valueses = _chart.data().map(function (layer) { return layer.values; });
+        return Array.prototype.concat.apply([], valueses);
+    }
+
+    _chart.xAxisMin = function () {
+        var min = d3.min(flattenStack(), dc.pluck('x'));
+        return dc.utils.subtract(min, _chart.xAxisPadding(), _chart.xAxisPaddingUnit());
+    };
+
+    _chart.xAxisMax = function () {
+        var max = d3.max(flattenStack(), dc.pluck('x'));
+        return dc.utils.add(max, _chart.xAxisPadding(), _chart.xAxisPaddingUnit());
+    };
+
+    /**
+     * Set or get the title function. Chart class will use this function to render svg title (usually interpreted by
+     * browser as tooltips) for each child element in the chart, i.e. a slice in a pie chart or a bubble in a bubble chart.
+     * Almost every chart supports title function however in grid coordinate chart you need to turn off brush in order to
+     * use title otherwise the brush layer will block tooltip trigger.
+     *
+     * If the first argument is a stack name, the title function will get or set the title for that stack. If stackName
+     * is not provided, the first stack is implied.
+     * @method title
+     * @memberof dc.stackMixin
+     * @instance
+     * @example
+     * // set a title function on 'first stack'
+     * chart.title('first stack', function(d) { return d.key + ': ' + d.value; });
+     * // get a title function from 'second stack'
+     * var secondTitleFunction = chart.title('second stack');
+     * @param {String} [stackName]
+     * @param {Function} [titleAccessor]
+     * @returns {String|dc.stackMixin}
+     */
+    dc.override(_chart, 'title', function (stackName, titleAccessor) {
+        if (!stackName) {
+            return _chart._title();
+        }
+
+        if (typeof stackName === 'function') {
+            return _chart._title(stackName);
+        }
+        if (stackName === _chart._groupName && typeof titleAccessor === 'function') {
+            return _chart._title(titleAccessor);
+        }
+
+        if (typeof titleAccessor !== 'function') {
+            return _titles[stackName] || _chart._title();
+        }
+
+        _titles[stackName] = titleAccessor;
+
+        return _chart;
+    });
+
+    /**
+     * Gets or sets the stack layout algorithm, which computes a baseline for each stack and
+     * propagates it to the next.
+     * @method stackLayout
+     * @memberof dc.stackMixin
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Stack-Layout.md d3.layout.stack}
+     * @param {Function} [stack=d3.layout.stack]
+     * @returns {Function|dc.stackMixin}
+     */
+    _chart.stackLayout = function (stack) {
+        if (!arguments.length) {
+            return _stackLayout;
+        }
+        _stackLayout = stack;
+        if (_stackLayout.values() === d3.layout.stack().values()) {
+            _stackLayout.values(prepareValues);
+        }
+        return _chart;
+    };
+
+    /**
+     * Since dc.js 2.0, there has been {@link https://github.com/dc-js/dc.js/issues/949 an issue}
+     * where points are filtered to the current domain. While this is a useful optimization, it is
+     * incorrectly implemented: the next point outside the domain is required in order to draw lines
+     * that are clipped to the bounds, as well as bars that are partly clipped.
+     *
+     * A fix will be included in dc.js 2.1.x, but a workaround is needed for dc.js 2.0 and until
+     * that fix is published, so set this flag to skip any filtering of points.
+     *
+     * Once the bug is fixed, this flag will have no effect, and it will be deprecated.
+     * @method evadeDomainFilter
+     * @memberof dc.stackMixin
+     * @instance
+     * @param {Boolean} [evadeDomainFilter=false]
+     * @returns {Boolean|dc.stackMixin}
+     */
+    _chart.evadeDomainFilter = function (evadeDomainFilter) {
+        if (!arguments.length) {
+            return _evadeDomainFilter;
+        }
+        _evadeDomainFilter = evadeDomainFilter;
+        return _chart;
+    };
+
+    function visability (l) {
+        return !l.hidden;
+    }
+
+    _chart.data(function () {
+        var layers = _stack.filter(visability);
+        return layers.length ? _chart.stackLayout()(layers) : [];
+    });
+
+    _chart._ordinalXDomain = function () {
+        var flat = flattenStack().map(dc.pluck('data'));
+        var ordered = _chart._computeOrderedGroups(flat);
+        return ordered.map(_chart.keyAccessor());
+    };
+
+    _chart.colorAccessor(function (d) {
+        var layer = this.layer || this.name || d.name || d.layer;
+        return layer;
+    });
+
+    _chart.legendables = function () {
+        return _stack.map(function (layer, i) {
+            return {
+                chart: _chart,
+                name: layer.name,
+                hidden: layer.hidden || false,
+                color: _chart.getColor.call(layer, layer.values, i)
+            };
+        });
+    };
+
+    _chart.isLegendableHidden = function (d) {
+        var layer = findLayerByName(d.name);
+        return layer ? layer.hidden : false;
+    };
+
+    _chart.legendToggle = function (d) {
+        if (_hidableStacks) {
+            if (_chart.isLegendableHidden(d)) {
+                _chart.showStack(d.name);
+            } else {
+                _chart.hideStack(d.name);
+            }
+            //_chart.redraw();
+            _chart.renderGroup();
+        }
+    };
+
+    return _chart;
+};
+
+/**
+ * Cap is a mixin that groups small data elements below a _cap_ into an *others* grouping for both the
+ * Row and Pie Charts.
+ *
+ * The top ordered elements in the group up to the cap amount will be kept in the chart, and the rest
+ * will be replaced with an *others* element, with value equal to the sum of the replaced values. The
+ * keys of the elements below the cap limit are recorded in order to filter by those keys when the
+ * others* element is clicked.
+ * @name capMixin
+ * @memberof dc
+ * @mixin
+ * @param {Object} _chart
+ * @returns {dc.capMixin}
+ */
+dc.capMixin = function (_chart) {
+
+    var _cap = Infinity;
+
+    var _othersLabel = 'Others';
+
+    var _othersGrouper = function (topRows) {
+        var topRowsSum = d3.sum(topRows, _chart.valueAccessor()),
+            allRows = _chart.group().all(),
+            allRowsSum = d3.sum(allRows, _chart.valueAccessor()),
+            topKeys = topRows.map(_chart.keyAccessor()),
+            allKeys = allRows.map(_chart.keyAccessor()),
+            topSet = d3.set(topKeys),
+            others = allKeys.filter(function (d) {return !topSet.has(d);});
+        if (allRowsSum > topRowsSum) {
+            return topRows.concat([{
+                'others': others,
+                'key': _chart.othersLabel(),
+                'value': allRowsSum - topRowsSum
+            }]);
+        }
+        return topRows;
+    };
+
+    _chart.cappedKeyAccessor = function (d, i) {
+        if (d.others) {
+            return d.key;
+        }
+        return _chart.keyAccessor()(d, i);
+    };
+
+    _chart.cappedValueAccessor = function (d, i) {
+        if (d.others) {
+            return d.value;
+        }
+        return _chart.valueAccessor()(d, i);
+    };
+
+    _chart.data(function (group) {
+        if (_cap === Infinity) {
+            return _chart._computeOrderedGroups(group.all());
+        } else {
+            var topRows = group.top(_cap); // ordered by crossfilter group order (default value)
+            topRows = _chart._computeOrderedGroups(topRows); // re-order using ordering (default key)
+            if (_othersGrouper) {
+                return _othersGrouper(topRows);
+            }
+            return topRows;
+        }
+    });
+
+    /**
+     * Get or set the count of elements to that will be included in the cap. If there is an
+     * {@link dc.capMixin#othersGrouper othersGrouper}, any further elements will be combined in an
+     * extra element with its name determined by {@link dc.capMixin#othersLabel othersLabel}.
+     *
+     * Up through dc.js 2.0.*, capping uses
+     * {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#group_top group.top(N)},
+     * which selects the largest items according to
+     * {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#group_order group.order()}.
+     * The chart then sorts the items according to {@link dc.baseMixin#ordering baseMixin.ordering()}.
+     * So the two values essentially have to agree, but if the former is incorrect (it's easy to
+     * forget about `group.order()`), the latter will mask the problem. This also makes
+     * {@link https://github.com/dc-js/dc.js/wiki/FAQ#fake-groups fake groups} difficult to
+     * implement.
+     *
+     * In dc.js 2.1 and forward, only
+     * {@link https://github.com/crossfilter/crossfilter/wiki/API-Reference#group_all group.all()}
+     * and `baseMixin.ordering()` are used.
+     * @method cap
+     * @memberof dc.capMixin
+     * @instance
+     * @param {Number} [count=Infinity]
+     * @returns {Number|dc.capMixin}
+     */
+    _chart.cap = function (count) {
+        if (!arguments.length) {
+            return _cap;
+        }
+        _cap = count;
+        return _chart;
+    };
+
+    /**
+     * Get or set the label for *Others* slice when slices cap is specified.
+     * @method othersLabel
+     * @memberof dc.capMixin
+     * @instance
+     * @param {String} [label="Others"]
+     * @returns {String|dc.capMixin}
+     */
+    _chart.othersLabel = function (label) {
+        if (!arguments.length) {
+            return _othersLabel;
+        }
+        _othersLabel = label;
+        return _chart;
+    };
+
+    /**
+     * Get or set the grouper function that will perform the insertion of data for the *Others* slice
+     * if the slices cap is specified. If set to a falsy value, no others will be added. By default the
+     * grouper function computes the sum of all values below the cap.
+     * @method othersGrouper
+     * @memberof dc.capMixin
+     * @instance
+     * @example
+     * // Do not show others
+     * chart.othersGrouper(null);
+     * // Default others grouper
+     * chart.othersGrouper(function (topRows) {
+     *    var topRowsSum = d3.sum(topRows, _chart.valueAccessor()),
+     *        allRows = _chart.group().all(),
+     *        allRowsSum = d3.sum(allRows, _chart.valueAccessor()),
+     *        topKeys = topRows.map(_chart.keyAccessor()),
+     *        allKeys = allRows.map(_chart.keyAccessor()),
+     *        topSet = d3.set(topKeys),
+     *        others = allKeys.filter(function (d) {return !topSet.has(d);});
+     *    if (allRowsSum > topRowsSum) {
+     *        return topRows.concat([{
+     *            'others': others,
+     *            'key': _chart.othersLabel(),
+     *            'value': allRowsSum - topRowsSum
+     *        }]);
+     *    }
+     *    return topRows;
+     * });
+     * // Custom others grouper
+     * chart.othersGrouper(function (data) {
+     *     // compute the value for others, presumably the sum of all values below the cap
+     *     var othersSum  = yourComputeOthersValueLogic(data)
+     *
+     *     // the keys are needed to properly filter when the others element is clicked
+     *     var othersKeys = yourComputeOthersKeysArrayLogic(data);
+     *
+     *     // add the others row to the dataset
+     *     data.push({'key': 'Others', 'value': othersSum, 'others': othersKeys });
+     *
+     *     return data;
+     * });
+     * @param {Function} [grouperFunction]
+     * @returns {Function|dc.capMixin}
+     */
+    _chart.othersGrouper = function (grouperFunction) {
+        if (!arguments.length) {
+            return _othersGrouper;
+        }
+        _othersGrouper = grouperFunction;
+        return _chart;
+    };
+
+    dc.override(_chart, 'onClick', function (d) {
+        if (d.others) {
+            _chart.filter([d.others]);
+        }
+        _chart._onClick(d);
+    });
+
+    return _chart;
+};
+
+/**
+ * This Mixin provides reusable functionalities for any chart that needs to visualize data using bubbles.
+ * @name bubbleMixin
+ * @memberof dc
+ * @mixin
+ * @mixes dc.colorMixin
+ * @param {Object} _chart
+ * @returns {dc.bubbleMixin}
+ */
+dc.bubbleMixin = function (_chart) {
+    var _maxBubbleRelativeSize = 0.3;
+    var _minRadiusWithLabel = 10;
+
+    _chart.BUBBLE_NODE_CLASS = 'node';
+    _chart.BUBBLE_CLASS = 'bubble';
+    _chart.MIN_RADIUS = 10;
+
+    _chart = dc.colorMixin(_chart);
+
+    _chart.renderLabel(true);
+
+    _chart.data(function (group) {
+        return group.top(Infinity);
+    });
+
+    var _r = d3.scale.linear().domain([0, 100]);
+
+    var _rValueAccessor = function (d) {
+        return d.r;
+    };
+
+    /**
+     * Get or set the bubble radius scale. By default the bubble chart uses
+     * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Quantitative-Scales.md#linear d3.scale.linear().domain([0, 100])}
+     * as its radius scale.
+     * @method r
+     * @memberof dc.bubbleMixin
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Scales.md d3.scale}
+     * @param {d3.scale} [bubbleRadiusScale=d3.scale.linear().domain([0, 100])]
+     * @returns {d3.scale|dc.bubbleMixin}
+     */
+    _chart.r = function (bubbleRadiusScale) {
+        if (!arguments.length) {
+            return _r;
+        }
+        _r = bubbleRadiusScale;
+        return _chart;
+    };
+
+    /**
+     * Get or set the radius value accessor function. If set, the radius value accessor function will
+     * be used to retrieve a data value for each bubble. The data retrieved then will be mapped using
+     * the r scale to the actual bubble radius. This allows you to encode a data dimension using bubble
+     * size.
+     * @method radiusValueAccessor
+     * @memberof dc.bubbleMixin
+     * @instance
+     * @param {Function} [radiusValueAccessor]
+     * @returns {Function|dc.bubbleMixin}
+     */
+    _chart.radiusValueAccessor = function (radiusValueAccessor) {
+        if (!arguments.length) {
+            return _rValueAccessor;
+        }
+        _rValueAccessor = radiusValueAccessor;
+        return _chart;
+    };
+
+    _chart.rMin = function () {
+        var min = d3.min(_chart.data(), function (e) {
+            return _chart.radiusValueAccessor()(e);
+        });
+        return min;
+    };
+
+    _chart.rMax = function () {
+        var max = d3.max(_chart.data(), function (e) {
+            return _chart.radiusValueAccessor()(e);
+        });
+        return max;
+    };
+
+    _chart.bubbleR = function (d) {
+        var value = _chart.radiusValueAccessor()(d);
+        var r = _chart.r()(value);
+        if (isNaN(r) || value <= 0) {
+            r = 0;
+        }
+        return r;
+    };
+
+    var labelFunction = function (d) {
+        return _chart.label()(d);
+    };
+
+    var shouldLabel = function (d) {
+        return (_chart.bubbleR(d) > _minRadiusWithLabel);
+    };
+
+    var labelOpacity = function (d) {
+        return shouldLabel(d) ? 1 : 0;
+    };
+
+    var labelPointerEvent = function (d) {
+        return shouldLabel(d) ? 'all' : 'none';
+    };
+
+    _chart._doRenderLabel = function (bubbleGEnter) {
+        if (_chart.renderLabel()) {
+            var label = bubbleGEnter.select('text');
+
+            if (label.empty()) {
+                label = bubbleGEnter.append('text')
+                    .attr('text-anchor', 'middle')
+                    .attr('dy', '.3em')
+                    .on('click', _chart.onClick);
+            }
+
+            label
+                .attr('opacity', 0)
+                .attr('pointer-events', labelPointerEvent)
+                .text(labelFunction);
+            dc.transition(label, _chart.transitionDuration(), _chart.transitionDelay())
+                .attr('opacity', labelOpacity);
+        }
+    };
+
+    _chart.doUpdateLabels = function (bubbleGEnter) {
+        if (_chart.renderLabel()) {
+            var labels = bubbleGEnter.select('text')
+                .attr('pointer-events', labelPointerEvent)
+                .text(labelFunction);
+            dc.transition(labels, _chart.transitionDuration(), _chart.transitionDelay())
+                .attr('opacity', labelOpacity);
+        }
+    };
+
+    var titleFunction = function (d) {
+        return _chart.title()(d);
+    };
+
+    _chart._doRenderTitles = function (g) {
+        if (_chart.renderTitle()) {
+            var title = g.select('title');
+
+            if (title.empty()) {
+                g.append('title').text(titleFunction);
+            }
+        }
+    };
+
+    _chart.doUpdateTitles = function (g) {
+        if (_chart.renderTitle()) {
+            g.select('title').text(titleFunction);
+        }
+    };
+
+    /**
+     * Get or set the minimum radius. This will be used to initialize the radius scale's range.
+     * @method minRadius
+     * @memberof dc.bubbleMixin
+     * @instance
+     * @param {Number} [radius=10]
+     * @returns {Number|dc.bubbleMixin}
+     */
+    _chart.minRadius = function (radius) {
+        if (!arguments.length) {
+            return _chart.MIN_RADIUS;
+        }
+        _chart.MIN_RADIUS = radius;
+        return _chart;
+    };
+
+    /**
+     * Get or set the minimum radius for label rendering. If a bubble's radius is less than this value
+     * then no label will be rendered.
+     * @method minRadiusWithLabel
+     * @memberof dc.bubbleMixin
+     * @instance
+     * @param {Number} [radius=10]
+     * @returns {Number|dc.bubbleMixin}
+     */
+
+    _chart.minRadiusWithLabel = function (radius) {
+        if (!arguments.length) {
+            return _minRadiusWithLabel;
+        }
+        _minRadiusWithLabel = radius;
+        return _chart;
+    };
+
+    /**
+     * Get or set the maximum relative size of a bubble to the length of x axis. This value is useful
+     * when the difference in radius between bubbles is too great.
+     * @method maxBubbleRelativeSize
+     * @memberof dc.bubbleMixin
+     * @instance
+     * @param {Number} [relativeSize=0.3]
+     * @returns {Number|dc.bubbleMixin}
+     */
+    _chart.maxBubbleRelativeSize = function (relativeSize) {
+        if (!arguments.length) {
+            return _maxBubbleRelativeSize;
+        }
+        _maxBubbleRelativeSize = relativeSize;
+        return _chart;
+    };
+
+    _chart.fadeDeselectedArea = function () {
+        if (_chart.hasFilter()) {
+            _chart.selectAll('g.' + _chart.BUBBLE_NODE_CLASS).each(function (d) {
+                if (_chart.isSelectedNode(d)) {
+                    _chart.highlightSelected(this);
+                } else {
+                    _chart.fadeDeselected(this);
+                }
+            });
+        } else {
+            _chart.selectAll('g.' + _chart.BUBBLE_NODE_CLASS).each(function () {
+                _chart.resetHighlight(this);
+            });
+        }
+    };
+
+    _chart.isSelectedNode = function (d) {
+        return _chart.hasFilter(d.key);
+    };
+
+    _chart.onClick = function (d) {
+        var filter = d.key;
+        dc.events.trigger(function () {
+            _chart.filter(filter);
+            _chart.redrawGroup();
+        });
+    };
+
+    return _chart;
+};
+
+/**
+ * The pie chart implementation is usually used to visualize a small categorical distribution.  The pie
+ * chart uses keyAccessor to determine the slices, and valueAccessor to calculate the size of each
+ * slice relative to the sum of all values. Slices are ordered by {@link dc.baseMixin#ordering ordering}
+ * which defaults to sorting by key.
+ *
+ * Examples:
+ * - {@link http://dc-js.github.com/dc.js/ Nasdaq 100 Index}
+ * @class pieChart
+ * @memberof dc
+ * @mixes dc.capMixin
+ * @mixes dc.colorMixin
+ * @mixes dc.baseMixin
+ * @example
+ * // create a pie chart under #chart-container1 element using the default global chart group
+ * var chart1 = dc.pieChart('#chart-container1');
+ * // create a pie chart under #chart-container2 element using chart group A
+ * var chart2 = dc.pieChart('#chart-container2', 'chartGroupA');
+ * @param {String|node|d3.selection} parent - Any valid
+ * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Selections.md#selecting-elements d3 single selector} specifying
+ * a dom block element such as a div; or a dom element or d3 selection.
+ * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.
+ * Interaction with a chart will only trigger events and redraws within the chart's group.
+ * @returns {dc.pieChart}
+ */
+dc.pieChart = function (parent, chartGroup) {
+    var DEFAULT_MIN_ANGLE_FOR_LABEL = 0.5;
+
+    var _sliceCssClass = 'pie-slice';
+    var _labelCssClass = 'pie-label';
+    var _sliceGroupCssClass = 'pie-slice-group';
+    var _labelGroupCssClass = 'pie-label-group';
+    var _emptyCssClass = 'empty-chart';
+    var _emptyTitle = 'empty';
+
+    var _radius,
+        _givenRadius, // specified radius, if any
+        _innerRadius = 0,
+        _externalRadiusPadding = 0;
+
+    var _g;
+    var _cx;
+    var _cy;
+    var _minAngleForLabel = DEFAULT_MIN_ANGLE_FOR_LABEL;
+    var _externalLabelRadius;
+    var _drawPaths = false;
+    var _chart = dc.capMixin(dc.colorMixin(dc.baseMixin({})));
+
+    _chart.colorAccessor(_chart.cappedKeyAccessor);
+
+    _chart.title(function (d) {
+        return _chart.cappedKeyAccessor(d) + ': ' + _chart.cappedValueAccessor(d);
+    });
+
+    /**
+     * Get or set the maximum number of slices the pie chart will generate. The top slices are determined by
+     * value from high to low. Other slices exeeding the cap will be rolled up into one single *Others* slice.
+     * @method slicesCap
+     * @memberof dc.pieChart
+     * @instance
+     * @param {Number} [cap]
+     * @returns {Number|dc.pieChart}
+     */
+    _chart.slicesCap = _chart.cap;
+
+    _chart.label(_chart.cappedKeyAccessor);
+    _chart.renderLabel(true);
+
+    _chart.transitionDuration(350);
+    _chart.transitionDelay(0);
+
+    _chart._doRender = function () {
+        _chart.resetSvg();
+
+        _g = _chart.svg()
+            .append('g')
+            .attr('transform', 'translate(' + _chart.cx() + ',' + _chart.cy() + ')');
+
+        _g.append('g').attr('class', _sliceGroupCssClass);
+        _g.append('g').attr('class', _labelGroupCssClass);
+
+        drawChart();
+
+        return _chart;
+    };
+
+    function drawChart () {
+        // set radius from chart size if none given, or if given radius is too large
+        var maxRadius =  d3.min([_chart.width(), _chart.height()]) / 2;
+        _radius = _givenRadius && _givenRadius < maxRadius ? _givenRadius : maxRadius;
+
+        var arc = buildArcs();
+
+        var pie = pieLayout();
+        var pieData;
+        // if we have data...
+        if (d3.sum(_chart.data(), _chart.valueAccessor())) {
+            pieData = pie(_chart.data());
+            _g.classed(_emptyCssClass, false);
+        } else {
+            // otherwise we'd be getting NaNs, so override
+            // note: abuse others for its ignoring the value accessor
+            pieData = pie([{key: _emptyTitle, value: 1, others: [_emptyTitle]}]);
+            _g.classed(_emptyCssClass, true);
+        }
+
+        if (_g) {
+            var slices = _g.select('g.' + _sliceGroupCssClass)
+                .selectAll('g.' + _sliceCssClass)
+                .data(pieData);
+
+            var labels = _g.select('g.' + _labelGroupCssClass)
+                .selectAll('text.' + _labelCssClass)
+                .data(pieData);
+
+            createElements(slices, labels, arc, pieData);
+
+            updateElements(pieData, arc);
+
+            removeElements(slices, labels);
+
+            highlightFilter();
+
+            dc.transition(_g, _chart.transitionDuration(), _chart.transitionDelay())
+                .attr('transform', 'translate(' + _chart.cx() + ',' + _chart.cy() + ')');
+        }
+    }
+
+    function createElements (slices, labels, arc, pieData) {
+        var slicesEnter = createSliceNodes(slices);
+
+        createSlicePath(slicesEnter, arc);
+
+        createTitles(slicesEnter);
+
+        createLabels(labels, pieData, arc);
+    }
+
+    function createSliceNodes (slices) {
+        var slicesEnter = slices
+            .enter()
+            .append('g')
+            .attr('class', function (d, i) {
+                return _sliceCssClass + ' _' + i;
+            });
+        return slicesEnter;
+    }
+
+    function createSlicePath (slicesEnter, arc) {
+        var slicePath = slicesEnter.append('path')
+            .attr('fill', fill)
+            .on('click', onClick)
+            .attr('d', function (d, i) {
+                return safeArc(d, i, arc);
+            });
+
+        var transition = dc.transition(slicePath, _chart.transitionDuration(), _chart.transitionDelay());
+        if (transition.attrTween) {
+            transition.attrTween('d', tweenPie);
+        }
+    }
+
+    function createTitles (slicesEnter) {
+        if (_chart.renderTitle()) {
+            slicesEnter.append('title').text(function (d) {
+                return _chart.title()(d.data);
+            });
+        }
+    }
+
+    _chart._applyLabelText = function (labels) {
+        labels
+            .text(function (d) {
+                var data = d.data;
+                if ((sliceHasNoData(data) || sliceTooSmall(d)) && !isSelectedSlice(d)) {
+                    return '';
+                }
+                return _chart.label()(d.data);
+            });
+    };
+
+    function positionLabels (labels, arc) {
+        _chart._applyLabelText(labels);
+        dc.transition(labels, _chart.transitionDuration(), _chart.transitionDelay())
+            .attr('transform', function (d) {
+                return labelPosition(d, arc);
+            })
+            .attr('text-anchor', 'middle');
+    }
+
+    function highlightSlice (i, whether) {
+        _chart.select('g.pie-slice._' + i)
+            .classed('highlight', whether);
+    }
+
+    function createLabels (labels, pieData, arc) {
+        if (_chart.renderLabel()) {
+            var labelsEnter = labels
+                .enter()
+                .append('text')
+                .attr('class', function (d, i) {
+                    var classes = _sliceCssClass + ' ' + _labelCssClass + ' _' + i;
+                    if (_externalLabelRadius) {
+                        classes += ' external';
+                    }
+                    return classes;
+                })
+                .on('click', onClick)
+                .on('mouseover', function (d, i) {
+                    highlightSlice(i, true);
+                })
+                .on('mouseout', function (d, i) {
+                    highlightSlice(i, false);
+                });
+            positionLabels(labelsEnter, arc);
+            if (_externalLabelRadius && _drawPaths) {
+                updateLabelPaths(pieData, arc);
+            }
+        }
+    }
+
+    function updateLabelPaths (pieData, arc) {
+        var polyline = _g.selectAll('polyline.' + _sliceCssClass)
+                .data(pieData);
+
+        polyline
+                .enter()
+                .append('polyline')
+                .attr('class', function (d, i) {
+                    return 'pie-path _' + i + ' ' + _sliceCssClass;
+                })
+                .on('click', onClick)
+                .on('mouseover', function (d, i) {
+                    highlightSlice(i, true);
+                })
+                .on('mouseout', function (d, i) {
+                    highlightSlice(i, false);
+                });
+
+        polyline.exit().remove();
+        var arc2 = d3.svg.arc()
+                .outerRadius(_radius - _externalRadiusPadding + _externalLabelRadius)
+                .innerRadius(_radius - _externalRadiusPadding);
+        var transition = dc.transition(polyline, _chart.transitionDuration(), _chart.transitionDelay());
+        // this is one rare case where d3.selection differs from d3.transition
+        if (transition.attrTween) {
+            transition
+                .attrTween('points', function (d) {
+                    var current = this._current || d;
+                    current = {startAngle: current.startAngle, endAngle: current.endAngle};
+                    var interpolate = d3.interpolate(current, d);
+                    this._current = interpolate(0);
+                    return function (t) {
+                        var d2 = interpolate(t);
+                        return [arc.centroid(d2), arc2.centroid(d2)];
+                    };
+                });
+        } else {
+            transition.attr('points', function (d) {
+                return [arc.centroid(d), arc2.centroid(d)];
+            });
+        }
+        transition.style('visibility', function (d) {
+            return d.endAngle - d.startAngle < 0.0001 ? 'hidden' : 'visible';
+        });
+
+    }
+
+    function updateElements (pieData, arc) {
+        updateSlicePaths(pieData, arc);
+        updateLabels(pieData, arc);
+        updateTitles(pieData);
+    }
+
+    function updateSlicePaths (pieData, arc) {
+        var slicePaths = _g.selectAll('g.' + _sliceCssClass)
+            .data(pieData)
+            .select('path')
+            .attr('d', function (d, i) {
+                return safeArc(d, i, arc);
+            });
+        var transition = dc.transition(slicePaths, _chart.transitionDuration(), _chart.transitionDelay());
+        if (transition.attrTween) {
+            transition.attrTween('d', tweenPie);
+        }
+        transition.attr('fill', fill);
+    }
+
+    function updateLabels (pieData, arc) {
+        if (_chart.renderLabel()) {
+            var labels = _g.selectAll('text.' + _labelCssClass)
+                .data(pieData);
+            positionLabels(labels, arc);
+            if (_externalLabelRadius && _drawPaths) {
+                updateLabelPaths(pieData, arc);
+            }
+        }
+    }
+
+    function updateTitles (pieData) {
+        if (_chart.renderTitle()) {
+            _g.selectAll('g.' + _sliceCssClass)
+                .data(pieData)
+                .select('title')
+                .text(function (d) {
+                    return _chart.title()(d.data);
+                });
+        }
+    }
+
+    function removeElements (slices, labels) {
+        slices.exit().remove();
+        labels.exit().remove();
+    }
+
+    function highlightFilter () {
+        if (_chart.hasFilter()) {
+            _chart.selectAll('g.' + _sliceCssClass).each(function (d) {
+                if (isSelectedSlice(d)) {
+                    _chart.highlightSelected(this);
+                } else {
+                    _chart.fadeDeselected(this);
+                }
+            });
+        } else {
+            _chart.selectAll('g.' + _sliceCssClass).each(function () {
+                _chart.resetHighlight(this);
+            });
+        }
+    }
+
+    /**
+     * Get or set the external radius padding of the pie chart. This will force the radius of the
+     * pie chart to become smaller or larger depending on the value.
+     * @method externalRadiusPadding
+     * @memberof dc.pieChart
+     * @instance
+     * @param {Number} [externalRadiusPadding=0]
+     * @returns {Number|dc.pieChart}
+     */
+    _chart.externalRadiusPadding = function (externalRadiusPadding) {
+        if (!arguments.length) {
+            return _externalRadiusPadding;
+        }
+        _externalRadiusPadding = externalRadiusPadding;
+        return _chart;
+    };
+
+    /**
+     * Get or set the inner radius of the pie chart. If the inner radius is greater than 0px then the
+     * pie chart will be rendered as a doughnut chart.
+     * @method innerRadius
+     * @memberof dc.pieChart
+     * @instance
+     * @param {Number} [innerRadius=0]
+     * @returns {Number|dc.pieChart}
+     */
+    _chart.innerRadius = function (innerRadius) {
+        if (!arguments.length) {
+            return _innerRadius;
+        }
+        _innerRadius = innerRadius;
+        return _chart;
+    };
+
+    /**
+     * Get or set the outer radius. If the radius is not set, it will be half of the minimum of the
+     * chart width and height.
+     * @method radius
+     * @memberof dc.pieChart
+     * @instance
+     * @param {Number} [radius]
+     * @returns {Number|dc.pieChart}
+     */
+    _chart.radius = function (radius) {
+        if (!arguments.length) {
+            return _givenRadius;
+        }
+        _givenRadius = radius;
+        return _chart;
+    };
+
+    /**
+     * Get or set center x coordinate position. Default is center of svg.
+     * @method cx
+     * @memberof dc.pieChart
+     * @instance
+     * @param {Number} [cx]
+     * @returns {Number|dc.pieChart}
+     */
+    _chart.cx = function (cx) {
+        if (!arguments.length) {
+            return (_cx ||  _chart.width() / 2);
+        }
+        _cx = cx;
+        return _chart;
+    };
+
+    /**
+     * Get or set center y coordinate position. Default is center of svg.
+     * @method cy
+     * @memberof dc.pieChart
+     * @instance
+     * @param {Number} [cy]
+     * @returns {Number|dc.pieChart}
+     */
+    _chart.cy = function (cy) {
+        if (!arguments.length) {
+            return (_cy ||  _chart.height() / 2);
+        }
+        _cy = cy;
+        return _chart;
+    };
+
+    function buildArcs () {
+        return d3.svg.arc()
+            .outerRadius(_radius - _externalRadiusPadding)
+            .innerRadius(_innerRadius);
+    }
+
+    function isSelectedSlice (d) {
+        return _chart.hasFilter(_chart.cappedKeyAccessor(d.data));
+    }
+
+    _chart._doRedraw = function () {
+        drawChart();
+        return _chart;
+    };
+
+    /**
+     * Get or set the minimal slice angle for label rendering. Any slice with a smaller angle will not
+     * display a slice label.
+     * @method minAngleForLabel
+     * @memberof dc.pieChart
+     * @instance
+     * @param {Number} [minAngleForLabel=0.5]
+     * @returns {Number|dc.pieChart}
+     */
+    _chart.minAngleForLabel = function (minAngleForLabel) {
+        if (!arguments.length) {
+            return _minAngleForLabel;
+        }
+        _minAngleForLabel = minAngleForLabel;
+        return _chart;
+    };
+
+    function pieLayout () {
+        return d3.layout.pie().sort(null).value(_chart.cappedValueAccessor);
+    }
+
+    function sliceTooSmall (d) {
+        var angle = (d.endAngle - d.startAngle);
+        return isNaN(angle) || angle < _minAngleForLabel;
+    }
+
+    function sliceHasNoData (d) {
+        return _chart.cappedValueAccessor(d) === 0;
+    }
+
+    function tweenPie (b) {
+        b.innerRadius = _innerRadius;
+        var current = this._current;
+        if (isOffCanvas(current)) {
+            current = {startAngle: 0, endAngle: 0};
+        } else {
+            // only interpolate startAngle & endAngle, not the whole data object
+            current = {startAngle: current.startAngle, endAngle: current.endAngle};
+        }
+        var i = d3.interpolate(current, b);
+        this._current = i(0);
+        return function (t) {
+            return safeArc(i(t), 0, buildArcs());
+        };
+    }
+
+    function isOffCanvas (current) {
+        return !current || isNaN(current.startAngle) || isNaN(current.endAngle);
+    }
+
+    function fill (d, i) {
+        return _chart.getColor(d.data, i);
+    }
+
+    function onClick (d, i) {
+        if (_g.attr('class') !== _emptyCssClass) {
+            _chart.onClick(d.data, i);
+        }
+    }
+
+    function safeArc (d, i, arc) {
+        var path = arc(d, i);
+        if (path.indexOf('NaN') >= 0) {
+            path = 'M0,0';
+        }
+        return path;
+    }
+
+    /**
+     * Title to use for the only slice when there is no data.
+     * @method emptyTitle
+     * @memberof dc.pieChart
+     * @instance
+     * @param {String} [title]
+     * @returns {String|dc.pieChart}
+     */
+    _chart.emptyTitle = function (title) {
+        if (arguments.length === 0) {
+            return _emptyTitle;
+        }
+        _emptyTitle = title;
+        return _chart;
+    };
+
+    /**
+     * Position slice labels offset from the outer edge of the chart.
+     *
+     * The argument specifies the extra radius to be added for slice labels.
+     * @method externalLabels
+     * @memberof dc.pieChart
+     * @instance
+     * @param {Number} [externalLabelRadius]
+     * @returns {Number|dc.pieChart}
+     */
+    _chart.externalLabels = function (externalLabelRadius) {
+        if (arguments.length === 0) {
+            return _externalLabelRadius;
+        } else if (externalLabelRadius) {
+            _externalLabelRadius = externalLabelRadius;
+        } else {
+            _externalLabelRadius = undefined;
+        }
+
+        return _chart;
+    };
+
+    /**
+     * Get or set whether to draw lines from pie slices to their labels.
+     *
+     * @method drawPaths
+     * @memberof dc.pieChart
+     * @instance
+     * @param {Boolean} [drawPaths]
+     * @returns {Boolean|dc.pieChart}
+     */
+    _chart.drawPaths = function (drawPaths) {
+        if (arguments.length === 0) {
+            return _drawPaths;
+        }
+        _drawPaths = drawPaths;
+        return _chart;
+    };
+
+    function labelPosition (d, arc) {
+        var centroid;
+        if (_externalLabelRadius) {
+            centroid = d3.svg.arc()
+                .outerRadius(_radius - _externalRadiusPadding + _externalLabelRadius)
+                .innerRadius(_radius - _externalRadiusPadding + _externalLabelRadius)
+                .centroid(d);
+        } else {
+            centroid = arc.centroid(d);
+        }
+        if (isNaN(centroid[0]) || isNaN(centroid[1])) {
+            return 'translate(0,0)';
+        } else {
+            return 'translate(' + centroid + ')';
+        }
+    }
+
+    _chart.legendables = function () {
+        return _chart.data().map(function (d, i) {
+            var legendable = {name: d.key, data: d.value, others: d.others, chart: _chart};
+            legendable.color = _chart.getColor(d, i);
+            return legendable;
+        });
+    };
+
+    _chart.legendHighlight = function (d) {
+        highlightSliceFromLegendable(d, true);
+    };
+
+    _chart.legendReset = function (d) {
+        highlightSliceFromLegendable(d, false);
+    };
+
+    _chart.legendToggle = function (d) {
+        _chart.onClick({key: d.name, others: d.others});
+    };
+
+    function highlightSliceFromLegendable (legendable, highlighted) {
+        _chart.selectAll('g.pie-slice').each(function (d) {
+            if (legendable.name === d.data.key) {
+                d3.select(this).classed('highlight', highlighted);
+            }
+        });
+    }
+
+    return _chart.anchor(parent, chartGroup);
+};
+
+/**
+ * Concrete bar chart/histogram implementation.
+ *
+ * Examples:
+ * - {@link http://dc-js.github.com/dc.js/ Nasdaq 100 Index}
+ * - {@link http://dc-js.github.com/dc.js/crime/index.html Canadian City Crime Stats}
+ * @class barChart
+ * @memberof dc
+ * @mixes dc.stackMixin
+ * @mixes dc.coordinateGridMixin
+ * @example
+ * // create a bar chart under #chart-container1 element using the default global chart group
+ * var chart1 = dc.barChart('#chart-container1');
+ * // create a bar chart under #chart-container2 element using chart group A
+ * var chart2 = dc.barChart('#chart-container2', 'chartGroupA');
+ * // create a sub-chart under a composite parent chart
+ * var chart3 = dc.barChart(compositeChart);
+ * @param {String|node|d3.selection|dc.compositeChart} parent - Any valid
+ * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Selections.md#selecting-elements d3 single selector}
+ * specifying a dom block element such as a div; or a dom element or d3 selection.  If the bar
+ * chart is a sub-chart in a {@link dc.compositeChart Composite Chart} then pass in the parent
+ * composite chart instance instead.
+ * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.
+ * Interaction with a chart will only trigger events and redraws within the chart's group.
+ * @returns {dc.barChart}
+ */
+dc.barChart = function (parent, chartGroup) {
+    var MIN_BAR_WIDTH = 1;
+    var DEFAULT_GAP_BETWEEN_BARS = 2;
+    var LABEL_PADDING = 3;
+
+    var _chart = dc.stackMixin(dc.coordinateGridMixin({}));
+
+    var _gap = DEFAULT_GAP_BETWEEN_BARS;
+    var _centerBar = false;
+    var _alwaysUseRounding = false;
+
+    var _barWidth;
+
+    dc.override(_chart, 'rescale', function () {
+        _chart._rescale();
+        _barWidth = undefined;
+        return _chart;
+    });
+
+    dc.override(_chart, 'render', function () {
+        if (_chart.round() && _centerBar && !_alwaysUseRounding) {
+            dc.logger.warn('By default, brush rounding is disabled if bars are centered. ' +
+                         'See dc.js bar chart API documentation for details.');
+        }
+
+        return _chart._render();
+    });
+
+    _chart.label(function (d) {
+        return dc.utils.printSingleValue(d.y0 + d.y);
+    }, false);
+
+    _chart.plotData = function () {
+        var layers = _chart.chartBodyG().selectAll('g.stack')
+            .data(_chart.data());
+
+        calculateBarWidth();
+
+        layers
+            .enter()
+            .append('g')
+            .attr('class', function (d, i) {
+                return 'stack ' + '_' + i;
+            });
+
+        var last = layers.size() - 1;
+        layers.each(function (d, i) {
+            var layer = d3.select(this);
+
+            renderBars(layer, i, d);
+
+            if (_chart.renderLabel() && last === i) {
+                renderLabels(layer, i, d);
+            }
+        });
+    };
+
+    function barHeight (d) {
+        return dc.utils.safeNumber(Math.abs(_chart.y()(d.y + d.y0) - _chart.y()(d.y0)));
+    }
+
+    function renderLabels (layer, layerIndex, d) {
+        var labels = layer.selectAll('text.barLabel')
+            .data(d.values, dc.pluck('x'));
+
+        labels.enter()
+            .append('text')
+            .attr('class', 'barLabel')
+            .attr('text-anchor', 'middle');
+
+        if (_chart.isOrdinal()) {
+            labels.on('click', _chart.onClick);
+            labels.attr('cursor', 'pointer');
+        }
+
+        dc.transition(labels, _chart.transitionDuration(), _chart.transitionDelay())
+            .attr('x', function (d) {
+                var x = _chart.x()(d.x);
+                if (!_centerBar) {
+                    x += _barWidth / 2;
+                }
+                return dc.utils.safeNumber(x);
+            })
+            .attr('y', function (d) {
+                var y = _chart.y()(d.y + d.y0);
+
+                if (d.y < 0) {
+                    y -= barHeight(d);
+                }
+
+                return dc.utils.safeNumber(y - LABEL_PADDING);
+            })
+            .text(function (d) {
+                return _chart.label()(d);
+            });
+
+        dc.transition(labels.exit(), _chart.transitionDuration(), _chart.transitionDelay())
+            .attr('height', 0)
+            .remove();
+    }
+
+    function renderBars (layer, layerIndex, d) {
+        var bars = layer.selectAll('rect.bar')
+            .data(d.values, dc.pluck('x'));
+
+        var enter = bars.enter()
+            .append('rect')
+            .attr('class', 'bar')
+            .attr('fill', dc.pluck('data', _chart.getColor))
+            .attr('y', _chart.yAxisHeight())
+            .attr('height', 0);
+
+        if (_chart.renderTitle()) {
+            enter.append('title').text(dc.pluck('data', _chart.title(d.name)));
+        }
+
+        if (_chart.isOrdinal()) {
+            bars.on('click', _chart.onClick);
+        }
+
+        dc.transition(bars, _chart.transitionDuration(), _chart.transitionDelay())
+            .attr('x', function (d) {
+                var x = _chart.x()(d.x);
+                if (_centerBar) {
+                    x -= _barWidth / 2;
+                }
+                if (_chart.isOrdinal() && _gap !== undefined) {
+                    x += _gap / 2;
+                }
+                return dc.utils.safeNumber(x);
+            })
+            .attr('y', function (d) {
+                var y = _chart.y()(d.y + d.y0);
+
+                if (d.y < 0) {
+                    y -= barHeight(d);
+                }
+
+                return dc.utils.safeNumber(y);
+            })
+            .attr('width', _barWidth)
+            .attr('height', function (d) {
+                return barHeight(d);
+            })
+            .attr('fill', dc.pluck('data', _chart.getColor))
+            .select('title').text(dc.pluck('data', _chart.title(d.name)));
+
+        dc.transition(bars.exit(), _chart.transitionDuration(), _chart.transitionDelay())
+            .attr('x', function (d) { return _chart.x()(d.x); })
+            .attr('width', _barWidth * 0.9)
+            .remove();
+    }
+
+    function calculateBarWidth () {
+        if (_barWidth === undefined) {
+            var numberOfBars = _chart.xUnitCount();
+
+            // please can't we always use rangeBands for bar charts?
+            if (_chart.isOrdinal() && _gap === undefined) {
+                _barWidth = Math.floor(_chart.x().rangeBand());
+            } else if (_gap) {
+                _barWidth = Math.floor((_chart.xAxisLength() - (numberOfBars - 1) * _gap) / numberOfBars);
+            } else {
+                _barWidth = Math.floor(_chart.xAxisLength() / (1 + _chart.barPadding()) / numberOfBars);
+            }
+
+            if (_barWidth === Infinity || isNaN(_barWidth) || _barWidth < MIN_BAR_WIDTH) {
+                _barWidth = MIN_BAR_WIDTH;
+            }
+        }
+    }
+
+    _chart.fadeDeselectedArea = function () {
+        var bars = _chart.chartBodyG().selectAll('rect.bar');
+        var extent = _chart.brush().extent();
+
+        if (_chart.isOrdinal()) {
+            if (_chart.hasFilter()) {
+                bars.classed(dc.constants.SELECTED_CLASS, function (d) {
+                    return _chart.hasFilter(d.x);
+                });
+                bars.classed(dc.constants.DESELECTED_CLASS, function (d) {
+                    return !_chart.hasFilter(d.x);
+                });
+            } else {
+                bars.classed(dc.constants.SELECTED_CLASS, false);
+                bars.classed(dc.constants.DESELECTED_CLASS, false);
+            }
+        } else {
+            if (!_chart.brushIsEmpty(extent)) {
+                var start = extent[0];
+                var end = extent[1];
+
+                bars.classed(dc.constants.DESELECTED_CLASS, function (d) {
+                    return d.x < start || d.x >= end;
+                });
+            } else {
+                bars.classed(dc.constants.DESELECTED_CLASS, false);
+            }
+        }
+    };
+
+    /**
+     * Whether the bar chart will render each bar centered around the data position on the x-axis.
+     * @method centerBar
+     * @memberof dc.barChart
+     * @instance
+     * @param {Boolean} [centerBar=false]
+     * @returns {Boolean|dc.barChart}
+     */
+    _chart.centerBar = function (centerBar) {
+        if (!arguments.length) {
+            return _centerBar;
+        }
+        _centerBar = centerBar;
+        return _chart;
+    };
+
+    dc.override(_chart, 'onClick', function (d) {
+        _chart._onClick(d.data);
+    });
+
+    /**
+     * Get or set the spacing between bars as a fraction of bar size. Valid values are between 0-1.
+     * Setting this value will also remove any previously set {@link dc.barChart#gap gap}. See the
+     * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Ordinal-Scales.md#ordinal_rangeBands d3 docs}
+     * for a visual description of how the padding is applied.
+     * @method barPadding
+     * @memberof dc.barChart
+     * @instance
+     * @param {Number} [barPadding=0]
+     * @returns {Number|dc.barChart}
+     */
+    _chart.barPadding = function (barPadding) {
+        if (!arguments.length) {
+            return _chart._rangeBandPadding();
+        }
+        _chart._rangeBandPadding(barPadding);
+        _gap = undefined;
+        return _chart;
+    };
+
+    _chart._useOuterPadding = function () {
+        return _gap === undefined;
+    };
+
+    /**
+     * Get or set the outer padding on an ordinal bar chart. This setting has no effect on non-ordinal charts.
+     * Will pad the width by `padding * barWidth` on each side of the chart.
+     * @method outerPadding
+     * @memberof dc.barChart
+     * @instance
+     * @param {Number} [padding=0.5]
+     * @returns {Number|dc.barChart}
+     */
+    _chart.outerPadding = _chart._outerRangeBandPadding;
+
+    /**
+     * Manually set fixed gap (in px) between bars instead of relying on the default auto-generated
+     * gap.  By default the bar chart implementation will calculate and set the gap automatically
+     * based on the number of data points and the length of the x axis.
+     * @method gap
+     * @memberof dc.barChart
+     * @instance
+     * @param {Number} [gap=2]
+     * @returns {Number|dc.barChart}
+     */
+    _chart.gap = function (gap) {
+        if (!arguments.length) {
+            return _gap;
+        }
+        _gap = gap;
+        return _chart;
+    };
+
+    _chart.extendBrush = function () {
+        var extent = _chart.brush().extent();
+        if (_chart.round() && (!_centerBar || _alwaysUseRounding)) {
+            extent[0] = extent.map(_chart.round())[0];
+            extent[1] = extent.map(_chart.round())[1];
+
+            _chart.chartBodyG().select('.brush')
+                .call(_chart.brush().extent(extent));
+        }
+
+        return extent;
+    };
+
+    /**
+     * Set or get whether rounding is enabled when bars are centered. If false, using
+     * rounding with centered bars will result in a warning and rounding will be ignored.  This flag
+     * has no effect if bars are not {@link dc.barChart#centerBar centered}.
+     * When using standard d3.js rounding methods, the brush often doesn't align correctly with
+     * centered bars since the bars are offset.  The rounding function must add an offset to
+     * compensate, such as in the following example.
+     * @method alwaysUseRounding
+     * @memberof dc.barChart
+     * @instance
+     * @example
+     * chart.round(function(n) { return Math.floor(n) + 0.5; });
+     * @param {Boolean} [alwaysUseRounding=false]
+     * @returns {Boolean|dc.barChart}
+     */
+    _chart.alwaysUseRounding = function (alwaysUseRounding) {
+        if (!arguments.length) {
+            return _alwaysUseRounding;
+        }
+        _alwaysUseRounding = alwaysUseRounding;
+        return _chart;
+    };
+
+    function colorFilter (color, inv) {
+        return function () {
+            var item = d3.select(this);
+            var match = item.attr('fill') === color;
+            return inv ? !match : match;
+        };
+    }
+
+    _chart.legendHighlight = function (d) {
+        if (!_chart.isLegendableHidden(d)) {
+            _chart.g().selectAll('rect.bar')
+                .classed('highlight', colorFilter(d.color))
+                .classed('fadeout', colorFilter(d.color, true));
+        }
+    };
+
+    _chart.legendReset = function () {
+        _chart.g().selectAll('rect.bar')
+            .classed('highlight', false)
+            .classed('fadeout', false);
+    };
+
+    dc.override(_chart, 'xAxisMax', function () {
+        var max = this._xAxisMax();
+        if ('resolution' in _chart.xUnits()) {
+            var res = _chart.xUnits().resolution;
+            max += res;
+        }
+        return max;
+    });
+
+    return _chart.anchor(parent, chartGroup);
+};
+
+/**
+ * Concrete line/area chart implementation.
+ *
+ * Examples:
+ * - {@link http://dc-js.github.com/dc.js/ Nasdaq 100 Index}
+ * - {@link http://dc-js.github.com/dc.js/crime/index.html Canadian City Crime Stats}
+ * @class lineChart
+ * @memberof dc
+ * @mixes dc.stackMixin
+ * @mixes dc.coordinateGridMixin
+ * @example
+ * // create a line chart under #chart-container1 element using the default global chart group
+ * var chart1 = dc.lineChart('#chart-container1');
+ * // create a line chart under #chart-container2 element using chart group A
+ * var chart2 = dc.lineChart('#chart-container2', 'chartGroupA');
+ * // create a sub-chart under a composite parent chart
+ * var chart3 = dc.lineChart(compositeChart);
+ * @param {String|node|d3.selection|dc.compositeChart} parent - Any valid
+ * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Selections.md#selecting-elements d3 single selector}
+ * specifying a dom block element such as a div; or a dom element or d3 selection.  If the line
+ * chart is a sub-chart in a {@link dc.compositeChart Composite Chart} then pass in the parent
+ * composite chart instance instead.
+ * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.
+ * Interaction with a chart will only trigger events and redraws within the chart's group.
+ * @returns {dc.lineChart}
+ */
+dc.lineChart = function (parent, chartGroup) {
+    var DEFAULT_DOT_RADIUS = 5;
+    var TOOLTIP_G_CLASS = 'dc-tooltip';
+    var DOT_CIRCLE_CLASS = 'dot';
+    var Y_AXIS_REF_LINE_CLASS = 'yRef';
+    var X_AXIS_REF_LINE_CLASS = 'xRef';
+    var DEFAULT_DOT_OPACITY = 1e-6;
+    var LABEL_PADDING = 3;
+
+    var _chart = dc.stackMixin(dc.coordinateGridMixin({}));
+    var _renderArea = false;
+    var _dotRadius = DEFAULT_DOT_RADIUS;
+    var _dataPointRadius = null;
+    var _dataPointFillOpacity = DEFAULT_DOT_OPACITY;
+    var _dataPointStrokeOpacity = DEFAULT_DOT_OPACITY;
+    var _interpolate = 'linear';
+    var _tension = 0.7;
+    var _defined;
+    var _dashStyle;
+    var _xyTipsOn = true;
+
+    _chart.transitionDuration(500);
+    _chart.transitionDelay(0);
+    _chart._rangeBandPadding(1);
+
+    _chart.plotData = function () {
+        var chartBody = _chart.chartBodyG();
+        var layersList = chartBody.select('g.stack-list');
+
+        if (layersList.empty()) {
+            layersList = chartBody.append('g').attr('class', 'stack-list');
+        }
+
+        var layers = layersList.selectAll('g.stack').data(_chart.data());
+
+        var layersEnter = layers
+            .enter()
+            .append('g')
+            .attr('class', function (d, i) {
+                return 'stack ' + '_' + i;
+            });
+
+        drawLine(layersEnter, layers);
+
+        drawArea(layersEnter, layers);
+
+        drawDots(chartBody, layers);
+
+        if (_chart.renderLabel()) {
+            drawLabels(layers);
+        }
+    };
+
+    /**
+     * Gets or sets the interpolator to use for lines drawn, by string name, allowing e.g. step
+     * functions, splines, and cubic interpolation.  This is passed to
+     * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#line_interpolate d3.svg.line.interpolate} and
+     * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#area_interpolate d3.svg.area.interpolate},
+     * where you can find a complete list of valid arguments.
+     * @method interpolate
+     * @memberof dc.lineChart
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#line_interpolate d3.svg.line.interpolate}
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#area_interpolate d3.svg.area.interpolate}
+     * @param  {String} [interpolate='linear']
+     * @returns {String|dc.lineChart}
+     */
+    _chart.interpolate = function (interpolate) {
+        if (!arguments.length) {
+            return _interpolate;
+        }
+        _interpolate = interpolate;
+        return _chart;
+    };
+
+    /**
+     * Gets or sets the tension to use for lines drawn, in the range 0 to 1.
+     * This parameter further customizes the interpolation behavior.  It is passed to
+     * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#line_tension d3.svg.line.tension} and
+     * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#area_tension d3.svg.area.tension}.
+     * @method tension
+     * @memberof dc.lineChart
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#line_interpolate d3.svg.line.interpolate}
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#area_interpolate d3.svg.area.interpolate}
+     * @param  {Number} [tension=0.7]
+     * @returns {Number|dc.lineChart}
+     */
+    _chart.tension = function (tension) {
+        if (!arguments.length) {
+            return _tension;
+        }
+        _tension = tension;
+        return _chart;
+    };
+
+    /**
+     * Gets or sets a function that will determine discontinuities in the line which should be
+     * skipped: the path will be broken into separate subpaths if some points are undefined.
+     * This function is passed to
+     * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#line_defined d3.svg.line.defined}
+     *
+     * Note: crossfilter will sometimes coerce nulls to 0, so you may need to carefully write
+     * custom reduce functions to get this to work, depending on your data. See
+     * {@link https://github.com/dc-js/dc.js/issues/615#issuecomment-49089248 this GitHub comment}
+     * for more details and an example.
+     * @method defined
+     * @memberof dc.lineChart
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#line_defined d3.svg.line.defined}
+     * @param  {Function} [defined]
+     * @returns {Function|dc.lineChart}
+     */
+    _chart.defined = function (defined) {
+        if (!arguments.length) {
+            return _defined;
+        }
+        _defined = defined;
+        return _chart;
+    };
+
+    /**
+     * Set the line's d3 dashstyle. This value becomes the 'stroke-dasharray' of line. Defaults to empty
+     * array (solid line).
+     * @method dashStyle
+     * @memberof dc.lineChart
+     * @instance
+     * @see {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-dasharray stroke-dasharray}
+     * @example
+     * // create a Dash Dot Dot Dot
+     * chart.dashStyle([3,1,1,1]);
+     * @param  {Array<Number>} [dashStyle=[]]
+     * @returns {Array<Number>|dc.lineChart}
+     */
+    _chart.dashStyle = function (dashStyle) {
+        if (!arguments.length) {
+            return _dashStyle;
+        }
+        _dashStyle = dashStyle;
+        return _chart;
+    };
+
+    /**
+     * Get or set render area flag. If the flag is set to true then the chart will render the area
+     * beneath each line and the line chart effectively becomes an area chart.
+     * @method renderArea
+     * @memberof dc.lineChart
+     * @instance
+     * @param  {Boolean} [renderArea=false]
+     * @returns {Boolean|dc.lineChart}
+     */
+    _chart.renderArea = function (renderArea) {
+        if (!arguments.length) {
+            return _renderArea;
+        }
+        _renderArea = renderArea;
+        return _chart;
+    };
+
+    function colors (d, i) {
+        return _chart.getColor.call(d, d.values, i);
+    }
+
+    function drawLine (layersEnter, layers) {
+        var line = d3.svg.line()
+            .x(function (d) {
+                return _chart.x()(d.x);
+            })
+            .y(function (d) {
+                return _chart.y()(d.y + d.y0);
+            })
+            .interpolate(_interpolate)
+            .tension(_tension);
+        if (_defined) {
+            line.defined(_defined);
+        }
+
+        var path = layersEnter.append('path')
+            .attr('class', 'line')
+            .attr('stroke', colors);
+        if (_dashStyle) {
+            path.attr('stroke-dasharray', _dashStyle);
+        }
+
+        dc.transition(layers.select('path.line'), _chart.transitionDuration(), _chart.transitionDelay())
+            //.ease('linear')
+            .attr('stroke', colors)
+            .attr('d', function (d) {
+                return safeD(line(d.values));
+            });
+    }
+
+    function drawArea (layersEnter, layers) {
+        if (_renderArea) {
+            var area = d3.svg.area()
+                .x(function (d) {
+                    return _chart.x()(d.x);
+                })
+                .y(function (d) {
+                    return _chart.y()(d.y + d.y0);
+                })
+                .y0(function (d) {
+                    return _chart.y()(d.y0);
+                })
+                .interpolate(_interpolate)
+                .tension(_tension);
+            if (_defined) {
+                area.defined(_defined);
+            }
+
+            layersEnter.append('path')
+                .attr('class', 'area')
+                .attr('fill', colors)
+                .attr('d', function (d) {
+                    return safeD(area(d.values));
+                });
+
+            dc.transition(layers.select('path.area'), _chart.transitionDuration(), _chart.transitionDelay())
+                //.ease('linear')
+                .attr('fill', colors)
+                .attr('d', function (d) {
+                    return safeD(area(d.values));
+                });
+        }
+    }
+
+    function safeD (d) {
+        return (!d || d.indexOf('NaN') >= 0) ? 'M0,0' : d;
+    }
+
+    function drawDots (chartBody, layers) {
+        if (_chart.xyTipsOn() === 'always' || (!_chart.brushOn() && _chart.xyTipsOn())) {
+            var tooltipListClass = TOOLTIP_G_CLASS + '-list';
+            var tooltips = chartBody.select('g.' + tooltipListClass);
+
+            if (tooltips.empty()) {
+                tooltips = chartBody.append('g').attr('class', tooltipListClass);
+            }
+
+            layers.each(function (d, layerIndex) {
+                var points = d.values;
+                if (_defined) {
+                    points = points.filter(_defined);
+                }
+
+                var g = tooltips.select('g.' + TOOLTIP_G_CLASS + '._' + layerIndex);
+                if (g.empty()) {
+                    g = tooltips.append('g').attr('class', TOOLTIP_G_CLASS + ' _' + layerIndex);
+                }
+
+                createRefLines(g);
+
+                var dots = g.selectAll('circle.' + DOT_CIRCLE_CLASS)
+                    .data(points, dc.pluck('x'));
+
+                dots.enter()
+                    .append('circle')
+                    .attr('class', DOT_CIRCLE_CLASS)
+                    .attr('r', getDotRadius())
+                    .style('fill-opacity', _dataPointFillOpacity)
+                    .style('stroke-opacity', _dataPointStrokeOpacity)
+                    .attr('fill', _chart.getColor)
+                    .on('mousemove', function () {
+                        var dot = d3.select(this);
+                        showDot(dot);
+                        showRefLines(dot, g);
+                    })
+                    .on('mouseout', function () {
+                        var dot = d3.select(this);
+                        hideDot(dot);
+                        hideRefLines(g);
+                    });
+
+                dots.call(renderTitle, d);
+
+                dc.transition(dots, _chart.transitionDuration())
+                    .attr('cx', function (d) {
+                        return dc.utils.safeNumber(_chart.x()(d.x));
+                    })
+                    .attr('cy', function (d) {
+                        return dc.utils.safeNumber(_chart.y()(d.y + d.y0));
+                    })
+                    .attr('fill', _chart.getColor);
+
+                dots.exit().remove();
+            });
+        }
+    }
+
+    _chart.label(function (d) {
+        return dc.utils.printSingleValue(d.y0 + d.y);
+    }, false);
+
+    function drawLabels (layers) {
+        layers.each(function (d, layerIndex) {
+            var layer = d3.select(this);
+            var labels = layer.selectAll('text.lineLabel')
+                .data(d.values, dc.pluck('x'));
+
+            labels.enter()
+                .append('text')
+                .attr('class', 'lineLabel')
+                .attr('text-anchor', 'middle');
+
+            dc.transition(labels, _chart.transitionDuration())
+                .attr('x', function (d) {
+                    return dc.utils.safeNumber(_chart.x()(d.x));
+                })
+                .attr('y', function (d) {
+                    var y = _chart.y()(d.y + d.y0) - LABEL_PADDING;
+                    return dc.utils.safeNumber(y);
+                })
+                .text(function (d) {
+                    return _chart.label()(d);
+                });
+
+            dc.transition(labels.exit(), _chart.transitionDuration())
+                .attr('height', 0)
+                .remove();
+        });
+    }
+
+    function createRefLines (g) {
+        var yRefLine = g.select('path.' + Y_AXIS_REF_LINE_CLASS).empty() ?
+            g.append('path').attr('class', Y_AXIS_REF_LINE_CLASS) : g.select('path.' + Y_AXIS_REF_LINE_CLASS);
+        yRefLine.style('display', 'none').attr('stroke-dasharray', '5,5');
+
+        var xRefLine = g.select('path.' + X_AXIS_REF_LINE_CLASS).empty() ?
+            g.append('path').attr('class', X_AXIS_REF_LINE_CLASS) : g.select('path.' + X_AXIS_REF_LINE_CLASS);
+        xRefLine.style('display', 'none').attr('stroke-dasharray', '5,5');
+    }
+
+    function showDot (dot) {
+        dot.style('fill-opacity', 0.8);
+        dot.style('stroke-opacity', 0.8);
+        dot.attr('r', _dotRadius);
+        return dot;
+    }
+
+    function showRefLines (dot, g) {
+        var x = dot.attr('cx');
+        var y = dot.attr('cy');
+        var yAxisX = (_chart._yAxisX() - _chart.margins().left);
+        var yAxisRefPathD = 'M' + yAxisX + ' ' + y + 'L' + (x) + ' ' + (y);
+        var xAxisRefPathD = 'M' + x + ' ' + _chart.yAxisHeight() + 'L' + x + ' ' + y;
+        g.select('path.' + Y_AXIS_REF_LINE_CLASS).style('display', '').attr('d', yAxisRefPathD);
+        g.select('path.' + X_AXIS_REF_LINE_CLASS).style('display', '').attr('d', xAxisRefPathD);
+    }
+
+    function getDotRadius () {
+        return _dataPointRadius || _dotRadius;
+    }
+
+    function hideDot (dot) {
+        dot.style('fill-opacity', _dataPointFillOpacity)
+            .style('stroke-opacity', _dataPointStrokeOpacity)
+            .attr('r', getDotRadius());
+    }
+
+    function hideRefLines (g) {
+        g.select('path.' + Y_AXIS_REF_LINE_CLASS).style('display', 'none');
+        g.select('path.' + X_AXIS_REF_LINE_CLASS).style('display', 'none');
+    }
+
+    function renderTitle (dot, d) {
+        if (_chart.renderTitle()) {
+            dot.select('title').remove();
+            dot.append('title').text(dc.pluck('data', _chart.title(d.name)));
+        }
+    }
+
+    /**
+     * Turn on/off the mouseover behavior of an individual data point which renders a circle and x/y axis
+     * dashed lines back to each respective axis.  This is ignored if the chart
+     * {@link dc.coordinateGridMixin#brushOn brush} is on
+     * @method xyTipsOn
+     * @memberof dc.lineChart
+     * @instance
+     * @param  {Boolean} [xyTipsOn=false]
+     * @returns {Boolean|dc.lineChart}
+     */
+    _chart.xyTipsOn = function (xyTipsOn) {
+        if (!arguments.length) {
+            return _xyTipsOn;
+        }
+        _xyTipsOn = xyTipsOn;
+        return _chart;
+    };
+
+    /**
+     * Get or set the radius (in px) for dots displayed on the data points.
+     * @method dotRadius
+     * @memberof dc.lineChart
+     * @instance
+     * @param  {Number} [dotRadius=5]
+     * @returns {Number|dc.lineChart}
+     */
+    _chart.dotRadius = function (dotRadius) {
+        if (!arguments.length) {
+            return _dotRadius;
+        }
+        _dotRadius = dotRadius;
+        return _chart;
+    };
+
+    /**
+     * Always show individual dots for each datapoint.
+     *
+     * If `options` is falsy, it disables data point rendering. If no `options` are provided, the
+     * current `options` values are instead returned.
+     * @method renderDataPoints
+     * @memberof dc.lineChart
+     * @instance
+     * @example
+     * chart.renderDataPoints({radius: 2, fillOpacity: 0.8, strokeOpacity: 0.8})
+     * @param  {{fillOpacity: Number, strokeOpacity: Number, radius: Number}} [options={fillOpacity: 0.8, strokeOpacity: 0.8, radius: 2}]
+     * @returns {{fillOpacity: Number, strokeOpacity: Number, radius: Number}|dc.lineChart}
+     */
+    _chart.renderDataPoints = function (options) {
+        if (!arguments.length) {
+            return {
+                fillOpacity: _dataPointFillOpacity,
+                strokeOpacity: _dataPointStrokeOpacity,
+                radius: _dataPointRadius
+            };
+        } else if (!options) {
+            _dataPointFillOpacity = DEFAULT_DOT_OPACITY;
+            _dataPointStrokeOpacity = DEFAULT_DOT_OPACITY;
+            _dataPointRadius = null;
+        } else {
+            _dataPointFillOpacity = options.fillOpacity || 0.8;
+            _dataPointStrokeOpacity = options.strokeOpacity || 0.8;
+            _dataPointRadius = options.radius || 2;
+        }
+        return _chart;
+    };
+
+    function colorFilter (color, dashstyle, inv) {
+        return function () {
+            var item = d3.select(this);
+            var match = (item.attr('stroke') === color &&
+                item.attr('stroke-dasharray') === ((dashstyle instanceof Array) ?
+                    dashstyle.join(',') : null)) || item.attr('fill') === color;
+            return inv ? !match : match;
+        };
+    }
+
+    _chart.legendHighlight = function (d) {
+        if (!_chart.isLegendableHidden(d)) {
+            _chart.g().selectAll('path.line, path.area')
+                .classed('highlight', colorFilter(d.color, d.dashstyle))
+                .classed('fadeout', colorFilter(d.color, d.dashstyle, true));
+        }
+    };
+
+    _chart.legendReset = function () {
+        _chart.g().selectAll('path.line, path.area')
+            .classed('highlight', false)
+            .classed('fadeout', false);
+    };
+
+    dc.override(_chart, 'legendables', function () {
+        var legendables = _chart._legendables();
+        if (!_dashStyle) {
+            return legendables;
+        }
+        return legendables.map(function (l) {
+            l.dashstyle = _dashStyle;
+            return l;
+        });
+    });
+
+    return _chart.anchor(parent, chartGroup);
+};
+
+/**
+ * The data count widget is a simple widget designed to display the number of records selected by the
+ * current filters out of the total number of records in the data set. Once created the data count widget
+ * will automatically update the text content of child elements with the following classes:
+ *
+ * * `.total-count` - total number of records
+ * * `.filter-count` - number of records matched by the current filters
+ *
+ * Note: this widget works best for the specific case of showing the number of records out of a
+ * total. If you want a more general-purpose numeric display, please use the
+ * {@link dc.numberDisplay} widget instead.
+ *
+ * Examples:
+ * - {@link http://dc-js.github.com/dc.js/ Nasdaq 100 Index}
+ * @class dataCount
+ * @memberof dc
+ * @mixes dc.baseMixin
+ * @example
+ * var ndx = crossfilter(data);
+ * var all = ndx.groupAll();
+ *
+ * dc.dataCount('.dc-data-count')
+ *     .dimension(ndx)
+ *     .group(all);
+ * @param {String|node|d3.selection} parent - Any valid
+ * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Selections.md#selecting-elements d3 single selector} specifying
+ * a dom block element such as a div; or a dom element or d3 selection.
+ * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.
+ * Interaction with a chart will only trigger events and redraws within the chart's group.
+ * @returns {dc.dataCount}
+ */
+dc.dataCount = function (parent, chartGroup) {
+    var _formatNumber = d3.format(',d');
+    var _chart = dc.baseMixin({});
+    var _html = {some: '', all: ''};
+
+    /**
+     * Gets or sets an optional object specifying HTML templates to use depending how many items are
+     * selected. The text `%total-count` will replaced with the total number of records, and the text
+     * `%filter-count` will be replaced with the number of selected records.
+     * - all: HTML template to use if all items are selected
+     * - some: HTML template to use if not all items are selected
+     * @method html
+     * @memberof dc.dataCount
+     * @instance
+     * @example
+     * counter.html({
+     *      some: '%filter-count out of %total-count records selected',
+     *      all: 'All records selected. Click on charts to apply filters'
+     * })
+     * @param {{some:String, all: String}} [options]
+     * @returns {{some:String, all: String}|dc.dataCount}
+     */
+    _chart.html = function (options) {
+        if (!arguments.length) {
+            return _html;
+        }
+        if (options.all) {
+            _html.all = options.all;
+        }
+        if (options.some) {
+            _html.some = options.some;
+        }
+        return _chart;
+    };
+
+    /**
+     * Gets or sets an optional function to format the filter count and total count.
+     * @method formatNumber
+     * @memberof dc.dataCount
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md d3.format}
+     * @example
+     * counter.formatNumber(d3.format('.2g'))
+     * @param {Function} [formatter=d3.format('.2g')]
+     * @returns {Function|dc.dataCount}
+     */
+    _chart.formatNumber = function (formatter) {
+        if (!arguments.length) {
+            return _formatNumber;
+        }
+        _formatNumber = formatter;
+        return _chart;
+    };
+
+    _chart._doRender = function () {
+        var tot = _chart.dimension().size(),
+            val = _chart.group().value();
+        var all = _formatNumber(tot);
+        var selected = _formatNumber(val);
+
+        if ((tot === val) && (_html.all !== '')) {
+            _chart.root().html(_html.all.replace('%total-count', all).replace('%filter-count', selected));
+        } else if (_html.some !== '') {
+            _chart.root().html(_html.some.replace('%total-count', all).replace('%filter-count', selected));
+        } else {
+            _chart.selectAll('.total-count').text(all);
+            _chart.selectAll('.filter-count').text(selected);
+        }
+        return _chart;
+    };
+
+    _chart._doRedraw = function () {
+        return _chart._doRender();
+    };
+
+    return _chart.anchor(parent, chartGroup);
+};
+
+/**
+ * The data table is a simple widget designed to list crossfilter focused data set (rows being
+ * filtered) in a good old tabular fashion.
+ *
+ * Note: Unlike other charts, the data table (and data grid chart) use the {@link dc.dataTable#group group} attribute as a
+ * keying function for {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Arrays.md#nest nesting} the data
+ * together in groups.  Do not pass in a crossfilter group as this will not work.
+ *
+ * Another interesting feature of the data table is that you can pass a crossfilter group to the `dimension`, as
+ * long as you specify the {@link dc.dataTable#order order} as `d3.descending`, since the data
+ * table will use `dimension.top()` to fetch the data in that case, and the method is equally
+ * supported on the crossfilter group as the crossfilter dimension.
+ *
+ * Examples:
+ * - {@link http://dc-js.github.com/dc.js/ Nasdaq 100 Index}
+ * - {@link http://dc-js.github.io/dc.js/examples/table-on-aggregated-data.html dataTable on a crossfilter group}
+ * ({@link https://github.com/dc-js/dc.js/blob/develop/web/examples/table-on-aggregated-data.html source})
+ * @class dataTable
+ * @memberof dc
+ * @mixes dc.baseMixin
+ * @param {String|node|d3.selection} parent - Any valid
+ * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Selections.md#selecting-elements d3 single selector} specifying
+ * a dom block element such as a div; or a dom element or d3 selection.
+ * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.
+ * Interaction with a chart will only trigger events and redraws within the chart's group.
+ * @returns {dc.dataTable}
+ */
+dc.dataTable = function (parent, chartGroup) {
+    var LABEL_CSS_CLASS = 'dc-table-label';
+    var ROW_CSS_CLASS = 'dc-table-row';
+    var COLUMN_CSS_CLASS = 'dc-table-column';
+    var GROUP_CSS_CLASS = 'dc-table-group';
+    var HEAD_CSS_CLASS = 'dc-table-head';
+
+    var _chart = dc.baseMixin({});
+
+    var _size = 25;
+    var _columns = [];
+    var _sortBy = function (d) {
+        return d;
+    };
+    var _order = d3.ascending;
+    var _beginSlice = 0;
+    var _endSlice;
+    var _showGroups = true;
+
+    _chart._doRender = function () {
+        _chart.selectAll('tbody').remove();
+
+        renderRows(renderGroups());
+
+        return _chart;
+    };
+
+    _chart._doColumnValueFormat = function (v, d) {
+        return ((typeof v === 'function') ?
+                v(d) :                          // v as function
+                ((typeof v === 'string') ?
+                 d[v] :                         // v is field name string
+                 v.format(d)                        // v is Object, use fn (element 2)
+                )
+               );
+    };
+
+    _chart._doColumnHeaderFormat = function (d) {
+        // if 'function', convert to string representation
+        // show a string capitalized
+        // if an object then display its label string as-is.
+        return (typeof d === 'function') ?
+                _chart._doColumnHeaderFnToString(d) :
+                ((typeof d === 'string') ?
+                 _chart._doColumnHeaderCapitalize(d) : String(d.label));
+    };
+
+    _chart._doColumnHeaderCapitalize = function (s) {
+        // capitalize
+        return s.charAt(0).toUpperCase() + s.slice(1);
+    };
+
+    _chart._doColumnHeaderFnToString = function (f) {
+        // columnString(f) {
+        var s = String(f);
+        var i1 = s.indexOf('return ');
+        if (i1 >= 0) {
+            var i2 = s.lastIndexOf(';');
+            if (i2 >= 0) {
+                s = s.substring(i1 + 7, i2);
+                var i3 = s.indexOf('numberFormat');
+                if (i3 >= 0) {
+                    s = s.replace('numberFormat', '');
+                }
+            }
+        }
+        return s;
+    };
+
+    function renderGroups () {
+        // The 'original' example uses all 'functions'.
+        // If all 'functions' are used, then don't remove/add a header, and leave
+        // the html alone. This preserves the functionality of earlier releases.
+        // A 2nd option is a string representing a field in the data.
+        // A third option is to supply an Object such as an array of 'information', and
+        // supply your own _doColumnHeaderFormat and _doColumnValueFormat functions to
+        // create what you need.
+        var bAllFunctions = true;
+        _columns.forEach(function (f) {
+            bAllFunctions = bAllFunctions & (typeof f === 'function');
+        });
+
+        if (!bAllFunctions) {
+            // ensure one thead
+            var thead = _chart.selectAll('thead').data([0]);
+            thead.enter().append('thead');
+            thead.exit().remove();
+
+            // with one tr
+            var headrow = thead.selectAll('tr').data([0]);
+            headrow.enter().append('tr');
+            headrow.exit().remove();
+
+            // with a th for each column
+            var headcols = headrow.selectAll('th')
+                .data(_columns);
+            headcols.enter().append('th');
+            headcols.exit().remove();
+
+            headcols
+                .attr('class', HEAD_CSS_CLASS)
+                    .html(function (d) {
+                        return (_chart._doColumnHeaderFormat(d));
+
+                    });
+        }
+
+        var groups = _chart.root().selectAll('tbody')
+            .data(nestEntries(), function (d) {
+                return _chart.keyAccessor()(d);
+            });
+
+        var rowGroup = groups
+            .enter()
+            .append('tbody');
+
+        if (_showGroups === true) {
+            rowGroup
+                .append('tr')
+                .attr('class', GROUP_CSS_CLASS)
+                    .append('td')
+                    .attr('class', LABEL_CSS_CLASS)
+                    .attr('colspan', _columns.length)
+                    .html(function (d) {
+                        return _chart.keyAccessor()(d);
+                    });
+        }
+
+        groups.exit().remove();
+
+        return rowGroup;
+    }
+
+    function nestEntries () {
+        var entries;
+        if (_order === d3.ascending) {
+            entries = _chart.dimension().bottom(_size);
+        } else {
+            entries = _chart.dimension().top(_size);
+        }
+
+        return d3.nest()
+            .key(_chart.group())
+            .sortKeys(_order)
+            .entries(entries.sort(function (a, b) {
+                return _order(_sortBy(a), _sortBy(b));
+            }).slice(_beginSlice, _endSlice));
+    }
+
+    function renderRows (groups) {
+        var rows = groups.order()
+            .selectAll('tr.' + ROW_CSS_CLASS)
+            .data(function (d) {
+                return d.values;
+            });
+
+        var rowEnter = rows.enter()
+            .append('tr')
+            .attr('class', ROW_CSS_CLASS);
+
+        _columns.forEach(function (v, i) {
+            rowEnter.append('td')
+                .attr('class', COLUMN_CSS_CLASS + ' _' + i)
+                .html(function (d) {
+                    return _chart._doColumnValueFormat(v, d);
+                });
+        });
+
+        rows.exit().remove();
+
+        return rows;
+    }
+
+    _chart._doRedraw = function () {
+        return _chart._doRender();
+    };
+
+    /**
+     * Get or set the group function for the data table. The group function takes a data row and
+     * returns the key to specify to {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Arrays.md#d3_nest d3.nest}
+     * to split rows into groups.
+     *
+     * Do not pass in a crossfilter group as this will not work.
+     * @method group
+     * @memberof dc.dataTable
+     * @instance
+     * @example
+     * // group rows by the value of their field
+     * chart
+     *     .group(function(d) { return d.field; })
+     * @param {Function} groupFunction Function taking a row of data and returning the nest key.
+     * @returns {Function|dc.dataTable}
+     */
+
+    /**
+     * Get or set the table size which determines the number of rows displayed by the widget.
+     * @method size
+     * @memberof dc.dataTable
+     * @instance
+     * @param {Number} [size=25]
+     * @returns {Number|dc.dataTable}
+     */
+    _chart.size = function (size) {
+        if (!arguments.length) {
+            return _size;
+        }
+        _size = size;
+        return _chart;
+    };
+
+    /**
+     * Get or set the index of the beginning slice which determines which entries get displayed
+     * by the widget. Useful when implementing pagination.
+     *
+     * Note: the sortBy function will determine how the rows are ordered for pagination purposes.
+
+     * See the {@link http://dc-js.github.io/dc.js/examples/table-pagination.html table pagination example}
+     * to see how to implement the pagination user interface using `beginSlice` and `endSlice`.
+     * @method beginSlice
+     * @memberof dc.dataTable
+     * @instance
+     * @param {Number} [beginSlice=0]
+     * @returns {Number|dc.dataTable}
+     */
+    _chart.beginSlice = function (beginSlice) {
+        if (!arguments.length) {
+            return _beginSlice;
+        }
+        _beginSlice = beginSlice;
+        return _chart;
+    };
+
+    /**
+     * Get or set the index of the end slice which determines which entries get displayed by the
+     * widget. Useful when implementing pagination. See {@link dc.dataTable#beginSlice `beginSlice`} for more information.
+     * @method endSlice
+     * @memberof dc.dataTable
+     * @instance
+     * @param {Number|undefined} [endSlice=undefined]
+     * @returns {Number|dc.dataTable}
+     */
+    _chart.endSlice = function (endSlice) {
+        if (!arguments.length) {
+            return _endSlice;
+        }
+        _endSlice = endSlice;
+        return _chart;
+    };
+
+    /**
+     * Get or set column functions. The data table widget supports several methods of specifying the
+     * columns to display.
+     *
+     * The original method uses an array of functions to generate dynamic columns. Column functions
+     * are simple javascript functions with only one input argument `d` which represents a row in
+     * the data set. The return value of these functions will be used to generate the content for
+     * each cell. However, this method requires the HTML for the table to have a fixed set of column
+     * headers.
+     *
+     * <pre><code>chart.columns([
+     *     function(d) { return d.date; },
+     *     function(d) { return d.open; },
+     *     function(d) { return d.close; },
+     *     function(d) { return numberFormat(d.close - d.open); },
+     *     function(d) { return d.volume; }
+     * ]);
+     * </code></pre>
+     *
+     * In the second method, you can list the columns to read from the data without specifying it as
+     * a function, except where necessary (ie, computed columns).  Note the data element name is
+     * capitalized when displayed in the table header. You can also mix in functions as necessary,
+     * using the third `{label, format}` form, as shown below.
+     *
+     * <pre><code>chart.columns([
+     *     "date",    // d["date"], ie, a field accessor; capitalized automatically
+     *     "open",    // ...
+     *     "close",   // ...
+     *     {
+     *         label: "Change",
+     *         format: function (d) {
+     *             return numberFormat(d.close - d.open);
+     *         }
+     *     },
+     *     "volume"   // d["volume"], ie, a field accessor; capitalized automatically
+     * ]);
+     * </code></pre>
+     *
+     * In the third example, we specify all fields using the `{label, format}` method:
+     * <pre><code>chart.columns([
+     *     {
+     *         label: "Date",
+     *         format: function (d) { return d.date; }
+     *     },
+     *     {
+     *         label: "Open",
+     *         format: function (d) { return numberFormat(d.open); }
+     *     },
+     *     {
+     *         label: "Close",
+     *         format: function (d) { return numberFormat(d.close); }
+     *     },
+     *     {
+     *         label: "Change",
+     *         format: function (d) { return numberFormat(d.close - d.open); }
+     *     },
+     *     {
+     *         label: "Volume",
+     *         format: function (d) { return d.volume; }
+     *     }
+     * ]);
+     * </code></pre>
+     *
+     * You may wish to override the dataTable functions `_doColumnHeaderCapitalize` and
+     * `_doColumnHeaderFnToString`, which are used internally to translate the column information or
+     * function into a displayed header. The first one is used on the "string" column specifier; the
+     * second is used to transform a stringified function into something displayable. For the Stock
+     * example, the function for Change becomes the table header **d.close - d.open**.
+     *
+     * Finally, you can even specify a completely different form of column definition. To do this,
+     * override `_chart._doColumnHeaderFormat` and `_chart._doColumnValueFormat` Be aware that
+     * fields without numberFormat specification will be displayed just as they are stored in the
+     * data, unformatted.
+     * @method columns
+     * @memberof dc.dataTable
+     * @instance
+     * @param {Array<Function>} [columns=[]]
+     * @returns {Array<Function>}|dc.dataTable}
+     */
+    _chart.columns = function (columns) {
+        if (!arguments.length) {
+            return _columns;
+        }
+        _columns = columns;
+        return _chart;
+    };
+
+    /**
+     * Get or set sort-by function. This function works as a value accessor at row level and returns a
+     * particular field to be sorted by.
+     * @method sortBy
+     * @memberof dc.dataTable
+     * @instance
+     * @example
+     * chart.sortBy(function(d) {
+     *     return d.date;
+     * });
+     * @param {Function} [sortBy=identity function]
+     * @returns {Function|dc.dataTable}
+     */
+    _chart.sortBy = function (sortBy) {
+        if (!arguments.length) {
+            return _sortBy;
+        }
+        _sortBy = sortBy;
+        return _chart;
+    };
+
+    /**
+     * Get or set sort order. If the order is `d3.ascending`, the data table will use
+     * `dimension().bottom()` to fetch the data; otherwise it will use `dimension().top()`
+     * @method order
+     * @memberof dc.dataTable
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Arrays.md#d3_ascending d3.ascending}
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Arrays.md#d3_descending d3.descending}
+     * @example
+     * chart.order(d3.descending);
+     * @param {Function} [order=d3.ascending]
+     * @returns {Function|dc.dataTable}
+     */
+    _chart.order = function (order) {
+        if (!arguments.length) {
+            return _order;
+        }
+        _order = order;
+        return _chart;
+    };
+
+    /**
+     * Get or set if group rows will be shown. The dataTable {@link dc.dataTable#group group}
+     * function must be specified even if groups are not shown.
+     * @method showGroups
+     * @memberof dc.dataTable
+     * @instance
+     * @example
+     * chart
+     *     .group([value], [name])
+     *     .showGroups(true|false);
+     * @param {Boolean} [showGroups=true]
+     * @returns {Boolean|dc.dataTable}
+     */
+    _chart.showGroups = function (showGroups) {
+        if (!arguments.length) {
+            return _showGroups;
+        }
+        _showGroups = showGroups;
+        return _chart;
+    };
+
+    return _chart.anchor(parent, chartGroup);
+};
+
+/**
+ * Data grid is a simple widget designed to list the filtered records, providing
+ * a simple way to define how the items are displayed.
+ *
+ * Note: Unlike other charts, the data grid chart (and data table) use the {@link dc.dataGrid#group group} attribute as a keying function
+ * for {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Arrays.md#nest nesting} the data together in groups.
+ * Do not pass in a crossfilter group as this will not work.
+ *
+ * Examples:
+ * - {@link http://europarl.me/dc.js/web/ep/index.html List of members of the european parliament}
+ * @class dataGrid
+ * @memberof dc
+ * @mixes dc.baseMixin
+ * @param {String|node|d3.selection} parent - Any valid
+ * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Selections.md#selecting-elements d3 single selector} specifying
+ * a dom block element such as a div; or a dom element or d3 selection.
+ * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.
+ * Interaction with a chart will only trigger events and redraws within the chart's group.
+ * @returns {dc.dataGrid}
+ */
+dc.dataGrid = function (parent, chartGroup) {
+    var LABEL_CSS_CLASS = 'dc-grid-label';
+    var ITEM_CSS_CLASS = 'dc-grid-item';
+    var GROUP_CSS_CLASS = 'dc-grid-group';
+    var GRID_CSS_CLASS = 'dc-grid-top';
+
+    var _chart = dc.baseMixin({});
+
+    var _size = 999; // shouldn't be needed, but you might
+    var _html = function (d) { return 'you need to provide an html() handling param:  ' + JSON.stringify(d); };
+    var _sortBy = function (d) {
+        return d;
+    };
+    var _order = d3.ascending;
+    var _beginSlice = 0, _endSlice;
+
+    var _htmlGroup = function (d) {
+        return '<div class=\'' + GROUP_CSS_CLASS + '\'><h1 class=\'' + LABEL_CSS_CLASS + '\'>' +
+            _chart.keyAccessor()(d) + '</h1></div>';
+    };
+
+    _chart._doRender = function () {
+        _chart.selectAll('div.' + GRID_CSS_CLASS).remove();
+
+        renderItems(renderGroups());
+
+        return _chart;
+    };
+
+    function renderGroups () {
+        var groups = _chart.root().selectAll('div.' + GRID_CSS_CLASS)
+                .data(nestEntries(), function (d) {
+                    return _chart.keyAccessor()(d);
+                });
+
+        var itemGroup = groups
+                .enter()
+                .append('div')
+                .attr('class', GRID_CSS_CLASS);
+
+        if (_htmlGroup) {
+            itemGroup
+                .html(function (d) {
+                    return _htmlGroup(d);
+                });
+        }
+
+        groups.exit().remove();
+        return itemGroup;
+    }
+
+    function nestEntries () {
+        var entries = _chart.dimension().top(_size);
+
+        return d3.nest()
+            .key(_chart.group())
+            .sortKeys(_order)
+            .entries(entries.sort(function (a, b) {
+                return _order(_sortBy(a), _sortBy(b));
+            }).slice(_beginSlice, _endSlice));
+    }
+
+    function renderItems (groups) {
+        var items = groups.order()
+                .selectAll('div.' + ITEM_CSS_CLASS)
+                .data(function (d) {
+                    return d.values;
+                });
+
+        items.enter()
+            .append('div')
+            .attr('class', ITEM_CSS_CLASS)
+            .html(function (d) {
+                return _html(d);
+            });
+
+        items.exit().remove();
+
+        return items;
+    }
+
+    _chart._doRedraw = function () {
+        return _chart._doRender();
+    };
+
+    /**
+     * Get or set the group function for the data grid. The group function takes a data row and
+     * returns the key to specify to {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Arrays.md#d3_nest d3.nest}
+     * to split rows into groups.
+     *
+     * Do not pass in a crossfilter group as this will not work.
+     * @method group
+     * @memberof dc.dataGrid
+     * @instance
+     * @example
+     * // group rows by the value of their field
+     * chart
+     *     .group(function(d) { return d.field; })
+     * @param {Function} groupFunction Function taking a row of data and returning the nest key.
+     * @returns {Function|dc.dataTable}
+     */
+
+    /**
+     * Get or set the index of the beginning slice which determines which entries get displayed by the widget.
+     * Useful when implementing pagination.
+     * @method beginSlice
+     * @memberof dc.dataGrid
+     * @instance
+     * @param {Number} [beginSlice=0]
+     * @returns {Number|dc.dataGrid}
+     */
+    _chart.beginSlice = function (beginSlice) {
+        if (!arguments.length) {
+            return _beginSlice;
+        }
+        _beginSlice = beginSlice;
+        return _chart;
+    };
+
+    /**
+     * Get or set the index of the end slice which determines which entries get displayed by the widget.
+     * Useful when implementing pagination.
+     * @method endSlice
+     * @memberof dc.dataGrid
+     * @instance
+     * @param {Number} [endSlice]
+     * @returns {Number|dc.dataGrid}
+     */
+    _chart.endSlice = function (endSlice) {
+        if (!arguments.length) {
+            return _endSlice;
+        }
+        _endSlice = endSlice;
+        return _chart;
+    };
+
+    /**
+     * Get or set the grid size which determines the number of items displayed by the widget.
+     * @method size
+     * @memberof dc.dataGrid
+     * @instance
+     * @param {Number} [size=999]
+     * @returns {Number|dc.dataGrid}
+     */
+    _chart.size = function (size) {
+        if (!arguments.length) {
+            return _size;
+        }
+        _size = size;
+        return _chart;
+    };
+
+    /**
+     * Get or set the function that formats an item. The data grid widget uses a
+     * function to generate dynamic html. Use your favourite templating engine or
+     * generate the string directly.
+     * @method html
+     * @memberof dc.dataGrid
+     * @instance
+     * @example
+     * chart.html(function (d) { return '<div class='item '+data.exampleCategory+''>'+data.exampleString+'</div>';});
+     * @param {Function} [html]
+     * @returns {Function|dc.dataGrid}
+     */
+    _chart.html = function (html) {
+        if (!arguments.length) {
+            return _html;
+        }
+        _html = html;
+        return _chart;
+    };
+
+    /**
+     * Get or set the function that formats a group label.
+     * @method htmlGroup
+     * @memberof dc.dataGrid
+     * @instance
+     * @example
+     * chart.htmlGroup (function (d) { return '<h2>'.d.key . 'with ' . d.values.length .' items</h2>'});
+     * @param {Function} [htmlGroup]
+     * @returns {Function|dc.dataGrid}
+     */
+    _chart.htmlGroup = function (htmlGroup) {
+        if (!arguments.length) {
+            return _htmlGroup;
+        }
+        _htmlGroup = htmlGroup;
+        return _chart;
+    };
+
+    /**
+     * Get or set sort-by function. This function works as a value accessor at the item
+     * level and returns a particular field to be sorted.
+     * @method sortBy
+     * @memberof dc.dataGrid
+     * @instance
+     * @example
+     * chart.sortBy(function(d) {
+     *     return d.date;
+     * });
+     * @param {Function} [sortByFunction]
+     * @returns {Function|dc.dataGrid}
+     */
+    _chart.sortBy = function (sortByFunction) {
+        if (!arguments.length) {
+            return _sortBy;
+        }
+        _sortBy = sortByFunction;
+        return _chart;
+    };
+
+    /**
+     * Get or set sort the order function.
+     * @method order
+     * @memberof dc.dataGrid
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Arrays.md#d3_ascending d3.ascending}
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Arrays.md#d3_descending d3.descending}
+     * @example
+     * chart.order(d3.descending);
+     * @param {Function} [order=d3.ascending]
+     * @returns {Function|dc.dataGrid}
+     */
+    _chart.order = function (order) {
+        if (!arguments.length) {
+            return _order;
+        }
+        _order = order;
+        return _chart;
+    };
+
+    return _chart.anchor(parent, chartGroup);
+};
+
+/**
+ * A concrete implementation of a general purpose bubble chart that allows data visualization using the
+ * following dimensions:
+ * - x axis position
+ * - y axis position
+ * - bubble radius
+ * - color
+ *
+ * Examples:
+ * - {@link http://dc-js.github.com/dc.js/ Nasdaq 100 Index}
+ * - {@link http://dc-js.github.com/dc.js/vc/index.html US Venture Capital Landscape 2011}
+ * @class bubbleChart
+ * @memberof dc
+ * @mixes dc.bubbleMixin
+ * @mixes dc.coordinateGridMixin
+ * @example
+ * // create a bubble chart under #chart-container1 element using the default global chart group
+ * var bubbleChart1 = dc.bubbleChart('#chart-container1');
+ * // create a bubble chart under #chart-container2 element using chart group A
+ * var bubbleChart2 = dc.bubbleChart('#chart-container2', 'chartGroupA');
+ * @param {String|node|d3.selection} parent - Any valid
+ * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Selections.md#selecting-elements d3 single selector} specifying
+ * a dom block element such as a div; or a dom element or d3 selection.
+ * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.
+ * Interaction with a chart will only trigger events and redraws within the chart's group.
+ * @returns {dc.bubbleChart}
+ */
+dc.bubbleChart = function (parent, chartGroup) {
+    var _chart = dc.bubbleMixin(dc.coordinateGridMixin({}));
+
+    var _elasticRadius = false;
+    var _sortBubbleSize = false;
+
+    _chart.transitionDuration(750);
+
+    _chart.transitionDelay(0);
+
+    var bubbleLocator = function (d) {
+        return 'translate(' + (bubbleX(d)) + ',' + (bubbleY(d)) + ')';
+    };
+
+    /**
+     * Turn on or off the elastic bubble radius feature, or return the value of the flag. If this
+     * feature is turned on, then bubble radii will be automatically rescaled to fit the chart better.
+     * @method elasticRadius
+     * @memberof dc.bubbleChart
+     * @instance
+     * @param {Boolean} [elasticRadius=false]
+     * @returns {Boolean|dc.bubbleChart}
+     */
+    _chart.elasticRadius = function (elasticRadius) {
+        if (!arguments.length) {
+            return _elasticRadius;
+        }
+        _elasticRadius = elasticRadius;
+        return _chart;
+    };
+
+    /**
+     * Turn on or off the bubble sorting feature, or return the value of the flag. If enabled,
+     * bubbles will be sorted by their radius, with smaller bubbles in front.
+     * @method sortBubbleSize
+     * @memberof dc.bubbleChart
+     * @instance
+     * @param {Boolean} [sortBubbleSize=false]
+     * @returns {Boolean|dc.bubbleChart}
+     */
+    _chart.sortBubbleSize = function (sortBubbleSize) {
+        if (!arguments.length) {
+            return _sortBubbleSize;
+        }
+        _sortBubbleSize = sortBubbleSize;
+        return _chart;
+    };
+
+    _chart.plotData = function () {
+        if (_elasticRadius) {
+            _chart.r().domain([_chart.rMin(), _chart.rMax()]);
+        }
+
+        _chart.r().range([_chart.MIN_RADIUS, _chart.xAxisLength() * _chart.maxBubbleRelativeSize()]);
+
+        var data = _chart.data();
+        if (_sortBubbleSize) {
+            // sort descending so smaller bubbles are on top
+            var radiusAccessor = _chart.radiusValueAccessor();
+            data.sort(function (a, b) { return d3.descending(radiusAccessor(a), radiusAccessor(b)); });
+        }
+        var bubbleG = _chart.chartBodyG().selectAll('g.' + _chart.BUBBLE_NODE_CLASS)
+                .data(data, function (d) { return d.key; });
+        if (_sortBubbleSize) {
+            // Call order here to update dom order based on sort
+            bubbleG.order();
+        }
+
+        renderNodes(bubbleG);
+
+        updateNodes(bubbleG);
+
+        removeNodes(bubbleG);
+
+        _chart.fadeDeselectedArea();
+    };
+
+    function renderNodes (bubbleG) {
+        var bubbleGEnter = bubbleG.enter().append('g');
+
+        bubbleGEnter
+            .attr('class', _chart.BUBBLE_NODE_CLASS)
+            .attr('transform', bubbleLocator)
+            .append('circle').attr('class', function (d, i) {
+                return _chart.BUBBLE_CLASS + ' _' + i;
+            })
+            .on('click', _chart.onClick)
+            .attr('fill', _chart.getColor)
+            .attr('r', 0);
+        dc.transition(bubbleG, _chart.transitionDuration(), _chart.transitionDelay())
+            .select('circle.' + _chart.BUBBLE_CLASS)
+            .attr('r', function (d) {
+                return _chart.bubbleR(d);
+            })
+            .attr('opacity', function (d) {
+                return (_chart.bubbleR(d) > 0) ? 1 : 0;
+            });
+
+        _chart._doRenderLabel(bubbleGEnter);
+
+        _chart._doRenderTitles(bubbleGEnter);
+    }
+
+    function updateNodes (bubbleG) {
+        dc.transition(bubbleG, _chart.transitionDuration(), _chart.transitionDelay())
+            .attr('transform', bubbleLocator)
+            .select('circle.' + _chart.BUBBLE_CLASS)
+            .attr('fill', _chart.getColor)
+            .attr('r', function (d) {
+                return _chart.bubbleR(d);
+            })
+            .attr('opacity', function (d) {
+                return (_chart.bubbleR(d) > 0) ? 1 : 0;
+            });
+
+        _chart.doUpdateLabels(bubbleG);
+        _chart.doUpdateTitles(bubbleG);
+    }
+
+    function removeNodes (bubbleG) {
+        bubbleG.exit().remove();
+    }
+
+    function bubbleX (d) {
+        var x = _chart.x()(_chart.keyAccessor()(d));
+        if (isNaN(x)) {
+            x = 0;
+        }
+        return x;
+    }
+
+    function bubbleY (d) {
+        var y = _chart.y()(_chart.valueAccessor()(d));
+        if (isNaN(y)) {
+            y = 0;
+        }
+        return y;
+    }
+
+    _chart.renderBrush = function () {
+        // override default x axis brush from parent chart
+    };
+
+    _chart.redrawBrush = function () {
+        // override default x axis brush from parent chart
+        _chart.fadeDeselectedArea();
+    };
+
+    return _chart.anchor(parent, chartGroup);
+};
+
+/**
+ * Composite charts are a special kind of chart that render multiple charts on the same Coordinate
+ * Grid. You can overlay (compose) different bar/line/area charts in a single composite chart to
+ * achieve some quite flexible charting effects.
+ * @class compositeChart
+ * @memberof dc
+ * @mixes dc.coordinateGridMixin
+ * @example
+ * // create a composite chart under #chart-container1 element using the default global chart group
+ * var compositeChart1 = dc.compositeChart('#chart-container1');
+ * // create a composite chart under #chart-container2 element using chart group A
+ * var compositeChart2 = dc.compositeChart('#chart-container2', 'chartGroupA');
+ * @param {String|node|d3.selection} parent - Any valid
+ * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Selections.md#selecting-elements d3 single selector} specifying
+ * a dom block element such as a div; or a dom element or d3 selection.
+ * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.
+ * Interaction with a chart will only trigger events and redraws within the chart's group.
+ * @returns {dc.compositeChart}
+ */
+dc.compositeChart = function (parent, chartGroup) {
+
+    var SUB_CHART_CLASS = 'sub';
+    var DEFAULT_RIGHT_Y_AXIS_LABEL_PADDING = 12;
+
+    var _chart = dc.coordinateGridMixin({});
+    var _children = [];
+
+    var _childOptions = {};
+
+    var _shareColors = false,
+        _shareTitle = true,
+        _alignYAxes = false;
+
+    var _rightYAxis = d3.svg.axis(),
+        _rightYAxisLabel = 0,
+        _rightYAxisLabelPadding = DEFAULT_RIGHT_Y_AXIS_LABEL_PADDING,
+        _rightY,
+        _rightAxisGridLines = false;
+
+    _chart._mandatoryAttributes([]);
+    _chart.transitionDuration(500);
+    _chart.transitionDelay(0);
+
+    dc.override(_chart, '_generateG', function () {
+        var g = this.__generateG();
+
+        for (var i = 0; i < _children.length; ++i) {
+            var child = _children[i];
+
+            generateChildG(child, i);
+
+            if (!child.dimension()) {
+                child.dimension(_chart.dimension());
+            }
+            if (!child.group()) {
+                child.group(_chart.group());
+            }
+
+            child.chartGroup(_chart.chartGroup());
+            child.svg(_chart.svg());
+            child.xUnits(_chart.xUnits());
+            child.transitionDuration(_chart.transitionDuration(), _chart.transitionDelay());
+            child.brushOn(_chart.brushOn());
+            child.renderTitle(_chart.renderTitle());
+            child.elasticX(_chart.elasticX());
+        }
+
+        return g;
+    });
+
+    _chart._brushing = function () {
+        var extent = _chart.extendBrush();
+        var brushIsEmpty = _chart.brushIsEmpty(extent);
+
+        for (var i = 0; i < _children.length; ++i) {
+            _children[i].replaceFilter(brushIsEmpty ? null : extent);
+        }
+    };
+
+    _chart._prepareYAxis = function () {
+        var left = (leftYAxisChildren().length !== 0);
+        var right = (rightYAxisChildren().length !== 0);
+        var ranges = calculateYAxisRanges(left, right);
+
+        if (left) { prepareLeftYAxis(ranges); }
+        if (right) { prepareRightYAxis(ranges); }
+
+        if (leftYAxisChildren().length > 0 && !_rightAxisGridLines) {
+            _chart._renderHorizontalGridLinesForAxis(_chart.g(), _chart.y(), _chart.yAxis());
+        } else if (rightYAxisChildren().length > 0) {
+            _chart._renderHorizontalGridLinesForAxis(_chart.g(), _rightY, _rightYAxis);
+        }
+    };
+
+    _chart.renderYAxis = function () {
+        if (leftYAxisChildren().length !== 0) {
+            _chart.renderYAxisAt('y', _chart.yAxis(), _chart.margins().left);
+            _chart.renderYAxisLabel('y', _chart.yAxisLabel(), -90);
+        }
+
+        if (rightYAxisChildren().length !== 0) {
+            _chart.renderYAxisAt('yr', _chart.rightYAxis(), _chart.width() - _chart.margins().right);
+            _chart.renderYAxisLabel('yr', _chart.rightYAxisLabel(), 90, _chart.width() - _rightYAxisLabelPadding);
+        }
+    };
+
+    function calculateYAxisRanges (left, right) {
+        var lyAxisMin, lyAxisMax, ryAxisMin, ryAxisMax;
+        var ranges;
+
+        if (left) {
+            lyAxisMin = yAxisMin();
+            lyAxisMax = yAxisMax();
+        }
+
+        if (right) {
+            ryAxisMin = rightYAxisMin();
+            ryAxisMax = rightYAxisMax();
+        }
+
+        if (_chart.alignYAxes() && left && right) {
+            ranges = alignYAxisRanges(lyAxisMin, lyAxisMax, ryAxisMin, ryAxisMax);
+        }
+
+        return ranges || {
+            lyAxisMin: lyAxisMin,
+            lyAxisMax: lyAxisMax,
+            ryAxisMin: ryAxisMin,
+            ryAxisMax: ryAxisMax
+        };
+    }
+
+    function alignYAxisRanges (lyAxisMin, lyAxisMax, ryAxisMin, ryAxisMax) {
+        // since the two series will share a zero, each Y is just a multiple
+        // of the other. and the ratio should be the ratio of the ranges of the
+        // input data, so that they come out the same height. so we just min/max
+
+        // note: both ranges already include zero due to the stack mixin (#667)
+        // if #667 changes, we can reconsider whether we want data height or
+        // height from zero to be equal. and it will be possible for the axes
+        // to be aligned but not visible.
+        var extentRatio = (ryAxisMax - ryAxisMin) / (lyAxisMax - lyAxisMin);
+
+        return {
+            lyAxisMin: Math.min(lyAxisMin, ryAxisMin / extentRatio),
+            lyAxisMax: Math.max(lyAxisMax, ryAxisMax / extentRatio),
+            ryAxisMin: Math.min(ryAxisMin, lyAxisMin * extentRatio),
+            ryAxisMax: Math.max(ryAxisMax, lyAxisMax * extentRatio)
+        };
+    }
+
+    function prepareRightYAxis (ranges) {
+        var needDomain = _chart.rightY() === undefined || _chart.elasticY(),
+            needRange = needDomain || _chart.resizing();
+        if (_chart.rightY() === undefined) {
+            _chart.rightY(d3.scale.linear());
+        }
+        if (needDomain) {
+            _chart.rightY().domain([ranges.ryAxisMin, ranges.ryAxisMax]);
+        }
+        if (needRange) {
+            _chart.rightY().rangeRound([_chart.yAxisHeight(), 0]);
+        }
+
+        _chart.rightY().range([_chart.yAxisHeight(), 0]);
+        _chart.rightYAxis(_chart.rightYAxis().scale(_chart.rightY()));
+
+        _chart.rightYAxis().orient('right');
+    }
+
+    function prepareLeftYAxis (ranges) {
+        var needDomain = _chart.y() === undefined || _chart.elasticY(),
+            needRange = needDomain || _chart.resizing();
+        if (_chart.y() === undefined) {
+            _chart.y(d3.scale.linear());
+        }
+        if (needDomain) {
+            _chart.y().domain([ranges.lyAxisMin, ranges.lyAxisMax]);
+        }
+        if (needRange) {
+            _chart.y().rangeRound([_chart.yAxisHeight(), 0]);
+        }
+
+        _chart.y().range([_chart.yAxisHeight(), 0]);
+        _chart.yAxis(_chart.yAxis().scale(_chart.y()));
+
+        _chart.yAxis().orient('left');
+    }
+
+    function generateChildG (child, i) {
+        child._generateG(_chart.g());
+        child.g().attr('class', SUB_CHART_CLASS + ' _' + i);
+    }
+
+    _chart.plotData = function () {
+        for (var i = 0; i < _children.length; ++i) {
+            var child = _children[i];
+
+            if (!child.g()) {
+                generateChildG(child, i);
+            }
+
+            if (_shareColors) {
+                child.colors(_chart.colors());
+            }
+
+            child.x(_chart.x());
+
+            child.xAxis(_chart.xAxis());
+
+            if (child.useRightYAxis()) {
+                child.y(_chart.rightY());
+                child.yAxis(_chart.rightYAxis());
+            } else {
+                child.y(_chart.y());
+                child.yAxis(_chart.yAxis());
+            }
+
+            child.plotData();
+
+            child._activateRenderlets();
+        }
+    };
+
+    /**
+     * Get or set whether to draw gridlines from the right y axis.  Drawing from the left y axis is the
+     * default behavior. This option is only respected when subcharts with both left and right y-axes
+     * are present.
+     * @method useRightAxisGridLines
+     * @memberof dc.compositeChart
+     * @instance
+     * @param {Boolean} [useRightAxisGridLines=false]
+     * @returns {Boolean|dc.compositeChart}
+     */
+    _chart.useRightAxisGridLines = function (useRightAxisGridLines) {
+        if (!arguments) {
+            return _rightAxisGridLines;
+        }
+
+        _rightAxisGridLines = useRightAxisGridLines;
+        return _chart;
+    };
+
+    /**
+     * Get or set chart-specific options for all child charts. This is equivalent to calling
+     * {@link dc.baseMixin#options .options} on each child chart.
+     * @method childOptions
+     * @memberof dc.compositeChart
+     * @instance
+     * @param {Object} [childOptions]
+     * @returns {Object|dc.compositeChart}
+     */
+    _chart.childOptions = function (childOptions) {
+        if (!arguments.length) {
+            return _childOptions;
+        }
+        _childOptions = childOptions;
+        _children.forEach(function (child) {
+            child.options(_childOptions);
+        });
+        return _chart;
+    };
+
+    _chart.fadeDeselectedArea = function () {
+        for (var i = 0; i < _children.length; ++i) {
+            var child = _children[i];
+            child.brush(_chart.brush());
+            child.fadeDeselectedArea();
+        }
+    };
+
+    /**
+     * Set or get the right y axis label.
+     * @method rightYAxisLabel
+     * @memberof dc.compositeChart
+     * @instance
+     * @param {String} [rightYAxisLabel]
+     * @param {Number} [padding]
+     * @returns {String|dc.compositeChart}
+     */
+    _chart.rightYAxisLabel = function (rightYAxisLabel, padding) {
+        if (!arguments.length) {
+            return _rightYAxisLabel;
+        }
+        _rightYAxisLabel = rightYAxisLabel;
+        _chart.margins().right -= _rightYAxisLabelPadding;
+        _rightYAxisLabelPadding = (padding === undefined) ? DEFAULT_RIGHT_Y_AXIS_LABEL_PADDING : padding;
+        _chart.margins().right += _rightYAxisLabelPadding;
+        return _chart;
+    };
+
+    /**
+     * Combine the given charts into one single composite coordinate grid chart.
+     * @method compose
+     * @memberof dc.compositeChart
+     * @instance
+     * @example
+     * moveChart.compose([
+     *     // when creating sub-chart you need to pass in the parent chart
+     *     dc.lineChart(moveChart)
+     *         .group(indexAvgByMonthGroup) // if group is missing then parent's group will be used
+     *         .valueAccessor(function (d){return d.value.avg;})
+     *         // most of the normal functions will continue to work in a composed chart
+     *         .renderArea(true)
+     *         .stack(monthlyMoveGroup, function (d){return d.value;})
+     *         .title(function (d){
+     *             var value = d.value.avg?d.value.avg:d.value;
+     *             if(isNaN(value)) value = 0;
+     *             return dateFormat(d.key) + '\n' + numberFormat(value);
+     *         }),
+     *     dc.barChart(moveChart)
+     *         .group(volumeByMonthGroup)
+     *         .centerBar(true)
+     * ]);
+     * @param {Array<Chart>} [subChartArray]
+     * @returns {dc.compositeChart}
+     */
+    _chart.compose = function (subChartArray) {
+        _children = subChartArray;
+        _children.forEach(function (child) {
+            child.height(_chart.height());
+            child.width(_chart.width());
+            child.margins(_chart.margins());
+
+            if (_shareTitle) {
+                child.title(_chart.title());
+            }
+
+            child.options(_childOptions);
+        });
+        return _chart;
+    };
+
+    /**
+     * Returns the child charts which are composed into the composite chart.
+     * @method children
+     * @memberof dc.compositeChart
+     * @instance
+     * @returns {Array<dc.baseMixin>}
+     */
+    _chart.children = function () {
+        return _children;
+    };
+
+    /**
+     * Get or set color sharing for the chart. If set, the {@link dc.colorMixin#colors .colors()} value from this chart
+     * will be shared with composed children. Additionally if the child chart implements
+     * Stackable and has not set a custom .colorAccessor, then it will generate a color
+     * specific to its order in the composition.
+     * @method shareColors
+     * @memberof dc.compositeChart
+     * @instance
+     * @param {Boolean} [shareColors=false]
+     * @returns {Boolean|dc.compositeChart}
+     */
+    _chart.shareColors = function (shareColors) {
+        if (!arguments.length) {
+            return _shareColors;
+        }
+        _shareColors = shareColors;
+        return _chart;
+    };
+
+    /**
+     * Get or set title sharing for the chart. If set, the {@link dc.baseMixin#title .title()} value from
+     * this chart will be shared with composed children.
+     * @method shareTitle
+     * @memberof dc.compositeChart
+     * @instance
+     * @param {Boolean} [shareTitle=true]
+     * @returns {Boolean|dc.compositeChart}
+     */
+    _chart.shareTitle = function (shareTitle) {
+        if (!arguments.length) {
+            return _shareTitle;
+        }
+        _shareTitle = shareTitle;
+        return _chart;
+    };
+
+    /**
+     * Get or set the y scale for the right axis. The right y scale is typically automatically
+     * generated by the chart implementation.
+     * @method rightY
+     * @memberof dc.compositeChart
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Scales.md d3.scale}
+     * @param {d3.scale} [yScale]
+     * @returns {d3.scale|dc.compositeChart}
+     */
+    _chart.rightY = function (yScale) {
+        if (!arguments.length) {
+            return _rightY;
+        }
+        _rightY = yScale;
+        _chart.rescale();
+        return _chart;
+    };
+
+    /**
+     * Get or set alignment between left and right y axes. A line connecting '0' on both y axis
+     * will be parallel to x axis. This only has effect when {@link #dc.coordinateGridMixin+elasticY elasticY} is true.
+     * @method alignYAxes
+     * @memberof dc.compositeChart
+     * @instance
+     * @param {Boolean} [alignYAxes=false]
+     * @returns {Chart}
+     */
+    _chart.alignYAxes = function (alignYAxes) {
+        if (!arguments.length) {
+            return _alignYAxes;
+        }
+        _alignYAxes = alignYAxes;
+        _chart.rescale();
+        return _chart;
+    };
+
+    function leftYAxisChildren () {
+        return _children.filter(function (child) {
+            return !child.useRightYAxis();
+        });
+    }
+
+    function rightYAxisChildren () {
+        return _children.filter(function (child) {
+            return child.useRightYAxis();
+        });
+    }
+
+    function getYAxisMin (charts) {
+        return charts.map(function (c) {
+            return c.yAxisMin();
+        });
+    }
+
+    delete _chart.yAxisMin;
+    function yAxisMin () {
+        return d3.min(getYAxisMin(leftYAxisChildren()));
+    }
+
+    function rightYAxisMin () {
+        return d3.min(getYAxisMin(rightYAxisChildren()));
+    }
+
+    function getYAxisMax (charts) {
+        return charts.map(function (c) {
+            return c.yAxisMax();
+        });
+    }
+
+    delete _chart.yAxisMax;
+    function yAxisMax () {
+        return dc.utils.add(d3.max(getYAxisMax(leftYAxisChildren())), _chart.yAxisPadding());
+    }
+
+    function rightYAxisMax () {
+        return dc.utils.add(d3.max(getYAxisMax(rightYAxisChildren())), _chart.yAxisPadding());
+    }
+
+    function getAllXAxisMinFromChildCharts () {
+        return _children.map(function (c) {
+            return c.xAxisMin();
+        });
+    }
+
+    dc.override(_chart, 'xAxisMin', function () {
+        return dc.utils.subtract(d3.min(getAllXAxisMinFromChildCharts()), _chart.xAxisPadding());
+    });
+
+    function getAllXAxisMaxFromChildCharts () {
+        return _children.map(function (c) {
+            return c.xAxisMax();
+        });
+    }
+
+    dc.override(_chart, 'xAxisMax', function () {
+        return dc.utils.add(d3.max(getAllXAxisMaxFromChildCharts()), _chart.xAxisPadding());
+    });
+
+    _chart.legendables = function () {
+        return _children.reduce(function (items, child) {
+            if (_shareColors) {
+                child.colors(_chart.colors());
+            }
+            items.push.apply(items, child.legendables());
+            return items;
+        }, []);
+    };
+
+    _chart.legendHighlight = function (d) {
+        for (var j = 0; j < _children.length; ++j) {
+            var child = _children[j];
+            child.legendHighlight(d);
+        }
+    };
+
+    _chart.legendReset = function (d) {
+        for (var j = 0; j < _children.length; ++j) {
+            var child = _children[j];
+            child.legendReset(d);
+        }
+    };
+
+    _chart.legendToggle = function () {
+        console.log('composite should not be getting legendToggle itself');
+    };
+
+    /**
+     * Set or get the right y axis used by the composite chart. This function is most useful when y
+     * axis customization is required. The y axis in dc.js is an instance of a [d3 axis
+     * object](https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#axis) therefore it supports any valid
+     * d3 axis manipulation.
+     *
+     * **Caution**: The y axis is usually generated internally by dc; resetting it may cause
+     * unexpected results.
+     * @method rightYAxis
+     * @memberof dc.compositeChart
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#axis d3.svg.axis}
+     * @example
+     * // customize y axis tick format
+     * chart.rightYAxis().tickFormat(function (v) {return v + '%';});
+     * // customize y axis tick values
+     * chart.rightYAxis().tickValues([0, 100, 200, 300]);
+     * @param {d3.svg.axis} [rightYAxis]
+     * @returns {d3.svg.axis|dc.compositeChart}
+     */
+    _chart.rightYAxis = function (rightYAxis) {
+        if (!arguments.length) {
+            return _rightYAxis;
+        }
+        _rightYAxis = rightYAxis;
+        return _chart;
+    };
+
+    return _chart.anchor(parent, chartGroup);
+};
+
+/**
+ * A series chart is a chart that shows multiple series of data overlaid on one chart, where the
+ * series is specified in the data. It is a specialization of Composite Chart and inherits all
+ * composite features other than recomposing the chart.
+ *
+ * Examples:
+ * - {@link http://dc-js.github.io/dc.js/examples/series.html Series Chart}
+ * @class seriesChart
+ * @memberof dc
+ * @mixes dc.compositeChart
+ * @example
+ * // create a series chart under #chart-container1 element using the default global chart group
+ * var seriesChart1 = dc.seriesChart("#chart-container1");
+ * // create a series chart under #chart-container2 element using chart group A
+ * var seriesChart2 = dc.seriesChart("#chart-container2", "chartGroupA");
+ * @param {String|node|d3.selection} parent - Any valid
+ * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Selections.md#selecting-elements d3 single selector} specifying
+ * a dom block element such as a div; or a dom element or d3 selection.
+ * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.
+ * Interaction with a chart will only trigger events and redraws within the chart's group.
+ * @returns {dc.seriesChart}
+ */
+dc.seriesChart = function (parent, chartGroup) {
+    var _chart = dc.compositeChart(parent, chartGroup);
+
+    function keySort (a, b) {
+        return d3.ascending(_chart.keyAccessor()(a), _chart.keyAccessor()(b));
+    }
+
+    var _charts = {};
+    var _chartFunction = dc.lineChart;
+    var _seriesAccessor;
+    var _seriesSort = d3.ascending;
+    var _valueSort = keySort;
+
+    _chart._mandatoryAttributes().push('seriesAccessor', 'chart');
+    _chart.shareColors(true);
+
+    _chart._preprocessData = function () {
+        var keep = [];
+        var childrenChanged;
+        var nester = d3.nest().key(_seriesAccessor);
+        if (_seriesSort) {
+            nester.sortKeys(_seriesSort);
+        }
+        if (_valueSort) {
+            nester.sortValues(_valueSort);
+        }
+        var nesting = nester.entries(_chart.data());
+        var children =
+            nesting.map(function (sub, i) {
+                var subChart = _charts[sub.key] || _chartFunction.call(_chart, _chart, chartGroup, sub.key, i);
+                if (!_charts[sub.key]) {
+                    childrenChanged = true;
+                }
+                _charts[sub.key] = subChart;
+                keep.push(sub.key);
+                return subChart
+                    .dimension(_chart.dimension())
+                    .group({all: d3.functor(sub.values)}, sub.key)
+                    .keyAccessor(_chart.keyAccessor())
+                    .valueAccessor(_chart.valueAccessor())
+                    .brushOn(_chart.brushOn());
+            });
+        // this works around the fact compositeChart doesn't really
+        // have a removal interface
+        Object.keys(_charts)
+            .filter(function (c) {return keep.indexOf(c) === -1;})
+            .forEach(function (c) {
+                clearChart(c);
+                childrenChanged = true;
+            });
+        _chart._compose(children);
+        if (childrenChanged && _chart.legend()) {
+            _chart.legend().render();
+        }
+    };
+
+    function clearChart (c) {
+        if (_charts[c].g()) {
+            _charts[c].g().remove();
+        }
+        delete _charts[c];
+    }
+
+    function resetChildren () {
+        Object.keys(_charts).map(clearChart);
+        _charts = {};
+    }
+
+    /**
+     * Get or set the chart function, which generates the child charts.
+     * @method chart
+     * @memberof dc.seriesChart
+     * @instance
+     * @example
+     * // put interpolation on the line charts used for the series
+     * chart.chart(function(c) { return dc.lineChart(c).interpolate('basis'); })
+     * // do a scatter series chart
+     * chart.chart(dc.scatterPlot)
+     * @param {Function} [chartFunction=dc.lineChart]
+     * @returns {Function|dc.seriesChart}
+     */
+    _chart.chart = function (chartFunction) {
+        if (!arguments.length) {
+            return _chartFunction;
+        }
+        _chartFunction = chartFunction;
+        resetChildren();
+        return _chart;
+    };
+
+    /**
+     * **mandatory**
+     *
+     * Get or set accessor function for the displayed series. Given a datum, this function
+     * should return the series that datum belongs to.
+     * @method seriesAccessor
+     * @memberof dc.seriesChart
+     * @instance
+     * @example
+     * // simple series accessor
+     * chart.seriesAccessor(function(d) { return "Expt: " + d.key[0]; })
+     * @param {Function} [accessor]
+     * @returns {Function|dc.seriesChart}
+     */
+    _chart.seriesAccessor = function (accessor) {
+        if (!arguments.length) {
+            return _seriesAccessor;
+        }
+        _seriesAccessor = accessor;
+        resetChildren();
+        return _chart;
+    };
+
+    /**
+     * Get or set a function to sort the list of series by, given series values.
+     * @method seriesSort
+     * @memberof dc.seriesChart
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Arrays.md#d3_ascending d3.ascending}
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Arrays.md#d3_descending d3.descending}
+     * @example
+     * chart.seriesSort(d3.descending);
+     * @param {Function} [sortFunction=d3.ascending]
+     * @returns {Function|dc.seriesChart}
+     */
+    _chart.seriesSort = function (sortFunction) {
+        if (!arguments.length) {
+            return _seriesSort;
+        }
+        _seriesSort = sortFunction;
+        resetChildren();
+        return _chart;
+    };
+
+    /**
+     * Get or set a function to sort each series values by. By default this is the key accessor which,
+     * for example, will ensure a lineChart series connects its points in increasing key/x order,
+     * rather than haphazardly.
+     * @method valueSort
+     * @memberof dc.seriesChart
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Arrays.md#d3_ascending d3.ascending}
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Arrays.md#d3_descending d3.descending}
+     * @example
+     * // Default value sort
+     * _chart.valueSort(function keySort (a, b) {
+     *     return d3.ascending(_chart.keyAccessor()(a), _chart.keyAccessor()(b));
+     * });
+     * @param {Function} [sortFunction]
+     * @returns {Function|dc.seriesChart}
+     */
+    _chart.valueSort = function (sortFunction) {
+        if (!arguments.length) {
+            return _valueSort;
+        }
+        _valueSort = sortFunction;
+        resetChildren();
+        return _chart;
+    };
+
+    // make compose private
+    _chart._compose = _chart.compose;
+    delete _chart.compose;
+
+    return _chart;
+};
+
+/**
+ * The geo choropleth chart is designed as an easy way to create a crossfilter driven choropleth map
+ * from GeoJson data. This chart implementation was inspired by
+ * {@link http://bl.ocks.org/4060606 the great d3 choropleth example}.
+ *
+ * Examples:
+ * - {@link http://dc-js.github.com/dc.js/vc/index.html US Venture Capital Landscape 2011}
+ * @class geoChoroplethChart
+ * @memberof dc
+ * @mixes dc.colorMixin
+ * @mixes dc.baseMixin
+ * @example
+ * // create a choropleth chart under '#us-chart' element using the default global chart group
+ * var chart1 = dc.geoChoroplethChart('#us-chart');
+ * // create a choropleth chart under '#us-chart2' element using chart group A
+ * var chart2 = dc.compositeChart('#us-chart2', 'chartGroupA');
+ * @param {String|node|d3.selection} parent - Any valid
+ * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Selections.md#selecting-elements d3 single selector} specifying
+ * a dom block element such as a div; or a dom element or d3 selection.
+ * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.
+ * Interaction with a chart will only trigger events and redraws within the chart's group.
+ * @returns {dc.geoChoroplethChart}
+ */
+dc.geoChoroplethChart = function (parent, chartGroup) {
+    var _chart = dc.colorMixin(dc.baseMixin({}));
+
+    _chart.colorAccessor(function (d) {
+        return d || 0;
+    });
+
+    var _geoPath = d3.geo.path();
+    var _projectionFlag;
+
+    var _geoJsons = [];
+
+    _chart._doRender = function () {
+        _chart.resetSvg();
+        for (var layerIndex = 0; layerIndex < _geoJsons.length; ++layerIndex) {
+            var states = _chart.svg().append('g')
+                .attr('class', 'layer' + layerIndex);
+
+            var regionG = states.selectAll('g.' + geoJson(layerIndex).name)
+                .data(geoJson(layerIndex).data)
+                .enter()
+                .append('g')
+                .attr('class', geoJson(layerIndex).name);
+
+            regionG
+                .append('path')
+                .attr('fill', 'white')
+                .attr('d', _geoPath);
+
+            regionG.append('title');
+
+            plotData(layerIndex);
+        }
+        _projectionFlag = false;
+    };
+
+    function plotData (layerIndex) {
+        var data = generateLayeredData();
+
+        if (isDataLayer(layerIndex)) {
+            var regionG = renderRegionG(layerIndex);
+
+            renderPaths(regionG, layerIndex, data);
+
+            renderTitle(regionG, layerIndex, data);
+        }
+    }
+
+    function generateLayeredData () {
+        var data = {};
+        var groupAll = _chart.data();
+        for (var i = 0; i < groupAll.length; ++i) {
+            data[_chart.keyAccessor()(groupAll[i])] = _chart.valueAccessor()(groupAll[i]);
+        }
+        return data;
+    }
+
+    function isDataLayer (layerIndex) {
+        return geoJson(layerIndex).keyAccessor;
+    }
+
+    function renderRegionG (layerIndex) {
+        var regionG = _chart.svg()
+            .selectAll(layerSelector(layerIndex))
+            .classed('selected', function (d) {
+                return isSelected(layerIndex, d);
+            })
+            .classed('deselected', function (d) {
+                return isDeselected(layerIndex, d);
+            })
+            .attr('class', function (d) {
+                var layerNameClass = geoJson(layerIndex).name;
+                var regionClass = dc.utils.nameToId(geoJson(layerIndex).keyAccessor(d));
+                var baseClasses = layerNameClass + ' ' + regionClass;
+                if (isSelected(layerIndex, d)) {
+                    baseClasses += ' selected';
+                }
+                if (isDeselected(layerIndex, d)) {
+                    baseClasses += ' deselected';
+                }
+                return baseClasses;
+            });
+        return regionG;
+    }
+
+    function layerSelector (layerIndex) {
+        return 'g.layer' + layerIndex + ' g.' + geoJson(layerIndex).name;
+    }
+
+    function isSelected (layerIndex, d) {
+        return _chart.hasFilter() && _chart.hasFilter(getKey(layerIndex, d));
+    }
+
+    function isDeselected (layerIndex, d) {
+        return _chart.hasFilter() && !_chart.hasFilter(getKey(layerIndex, d));
+    }
+
+    function getKey (layerIndex, d) {
+        return geoJson(layerIndex).keyAccessor(d);
+    }
+
+    function geoJson (index) {
+        return _geoJsons[index];
+    }
+
+    function renderPaths (regionG, layerIndex, data) {
+        var paths = regionG
+            .select('path')
+            .attr('fill', function () {
+                var currentFill = d3.select(this).attr('fill');
+                if (currentFill) {
+                    return currentFill;
+                }
+                return 'none';
+            })
+            .on('click', function (d) {
+                return _chart.onClick(d, layerIndex);
+            });
+
+        dc.transition(paths, _chart.transitionDuration(), _chart.transitionDelay()).attr('fill', function (d, i) {
+            return _chart.getColor(data[geoJson(layerIndex).keyAccessor(d)], i);
+        });
+    }
+
+    _chart.onClick = function (d, layerIndex) {
+        var selectedRegion = geoJson(layerIndex).keyAccessor(d);
+        dc.events.trigger(function () {
+            _chart.filter(selectedRegion);
+            _chart.redrawGroup();
+        });
+    };
+
+    function renderTitle (regionG, layerIndex, data) {
+        if (_chart.renderTitle()) {
+            regionG.selectAll('title').text(function (d) {
+                var key = getKey(layerIndex, d);
+                var value = data[key];
+                return _chart.title()({key: key, value: value});
+            });
+        }
+    }
+
+    _chart._doRedraw = function () {
+        for (var layerIndex = 0; layerIndex < _geoJsons.length; ++layerIndex) {
+            plotData(layerIndex);
+            if (_projectionFlag) {
+                _chart.svg().selectAll('g.' + geoJson(layerIndex).name + ' path').attr('d', _geoPath);
+            }
+        }
+        _projectionFlag = false;
+    };
+
+    /**
+     * **mandatory**
+     *
+     * Use this function to insert a new GeoJson map layer. This function can be invoked multiple times
+     * if you have multiple GeoJson data layers to render on top of each other. If you overlay multiple
+     * layers with the same name the new overlay will override the existing one.
+     * @method overlayGeoJson
+     * @memberof dc.geoChoroplethChart
+     * @instance
+     * @see {@link http://geojson.org/ GeoJSON}
+     * @see {@link https://github.com/topojson/topojson/wiki TopoJSON}
+     * @see {@link https://github.com/topojson/topojson-1.x-api-reference/blob/master/API-Reference.md#wiki-feature topojson.feature}
+     * @example
+     * // insert a layer for rendering US states
+     * chart.overlayGeoJson(statesJson.features, 'state', function(d) {
+     *      return d.properties.name;
+     * });
+     * @param {geoJson} json - a geojson feed
+     * @param {String} name - name of the layer
+     * @param {Function} keyAccessor - accessor function used to extract 'key' from the GeoJson data. The key extracted by
+     * this function should match the keys returned by the crossfilter groups.
+     * @returns {dc.geoChoroplethChart}
+     */
+    _chart.overlayGeoJson = function (json, name, keyAccessor) {
+        for (var i = 0; i < _geoJsons.length; ++i) {
+            if (_geoJsons[i].name === name) {
+                _geoJsons[i].data = json;
+                _geoJsons[i].keyAccessor = keyAccessor;
+                return _chart;
+            }
+        }
+        _geoJsons.push({name: name, data: json, keyAccessor: keyAccessor});
+        return _chart;
+    };
+
+    /**
+     * Set custom geo projection function. See the available
+     * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Geo-Projections.md d3 geo projection functions}.
+     * @method projection
+     * @memberof dc.geoChoroplethChart
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Geo-Projections.md d3.geo.projection}
+     * @see {@link https://github.com/d3/d3-geo-projection Extended d3.geo.projection}
+     * @param {d3.projection} [projection=d3.geo.albersUsa()]
+     * @returns {dc.geoChoroplethChart}
+     */
+    _chart.projection = function (projection) {
+        _geoPath.projection(projection);
+        _projectionFlag = true;
+        return _chart;
+    };
+
+    /**
+     * Returns all GeoJson layers currently registered with this chart. The returned array is a
+     * reference to this chart's internal data structure, so any modification to this array will also
+     * modify this chart's internal registration.
+     * @method geoJsons
+     * @memberof dc.geoChoroplethChart
+     * @instance
+     * @returns {Array<{name:String, data: Object, accessor: Function}>}
+     */
+    _chart.geoJsons = function () {
+        return _geoJsons;
+    };
+
+    /**
+     * Returns the {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Geo-Paths.md#path d3.geo.path} object used to
+     * render the projection and features.  Can be useful for figuring out the bounding box of the
+     * feature set and thus a way to calculate scale and translation for the projection.
+     * @method geoPath
+     * @memberof dc.geoChoroplethChart
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Geo-Paths.md#path d3.geo.path}
+     * @returns {d3.geo.path}
+     */
+    _chart.geoPath = function () {
+        return _geoPath;
+    };
+
+    /**
+     * Remove a GeoJson layer from this chart by name
+     * @method removeGeoJson
+     * @memberof dc.geoChoroplethChart
+     * @instance
+     * @param {String} name
+     * @returns {dc.geoChoroplethChart}
+     */
+    _chart.removeGeoJson = function (name) {
+        var geoJsons = [];
+
+        for (var i = 0; i < _geoJsons.length; ++i) {
+            var layer = _geoJsons[i];
+            if (layer.name !== name) {
+                geoJsons.push(layer);
+            }
+        }
+
+        _geoJsons = geoJsons;
+
+        return _chart;
+    };
+
+    return _chart.anchor(parent, chartGroup);
+};
+
+/**
+ * The bubble overlay chart is quite different from the typical bubble chart. With the bubble overlay
+ * chart you can arbitrarily place bubbles on an existing svg or bitmap image, thus changing the
+ * typical x and y positioning while retaining the capability to visualize data using bubble radius
+ * and coloring.
+ *
+ * Examples:
+ * - {@link http://dc-js.github.com/dc.js/crime/index.html Canadian City Crime Stats}
+ * @class bubbleOverlay
+ * @memberof dc
+ * @mixes dc.bubbleMixin
+ * @mixes dc.baseMixin
+ * @example
+ * // create a bubble overlay chart on top of the '#chart-container1 svg' element using the default global chart group
+ * var bubbleChart1 = dc.bubbleOverlayChart('#chart-container1').svg(d3.select('#chart-container1 svg'));
+ * // create a bubble overlay chart on top of the '#chart-container2 svg' element using chart group A
+ * var bubbleChart2 = dc.compositeChart('#chart-container2', 'chartGroupA').svg(d3.select('#chart-container2 svg'));
+ * @param {String|node|d3.selection} parent - Any valid
+ * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Selections.md#selecting-elements d3 single selector} specifying
+ * a dom block element such as a div; or a dom element or d3 selection.
+ * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.
+ * Interaction with a chart will only trigger events and redraws within the chart's group.
+ * @returns {dc.bubbleOverlay}
+ */
+dc.bubbleOverlay = function (parent, chartGroup) {
+    var BUBBLE_OVERLAY_CLASS = 'bubble-overlay';
+    var BUBBLE_NODE_CLASS = 'node';
+    var BUBBLE_CLASS = 'bubble';
+
+    /**
+     * **mandatory**
+     *
+     * Set the underlying svg image element. Unlike other dc charts this chart will not generate a svg
+     * element; therefore the bubble overlay chart will not work if this function is not invoked. If the
+     * underlying image is a bitmap, then an empty svg will need to be created on top of the image.
+     * @method svg
+     * @memberof dc.bubbleOverlay
+     * @instance
+     * @example
+     * // set up underlying svg element
+     * chart.svg(d3.select('#chart svg'));
+     * @param {SVGElement|d3.selection} [imageElement]
+     * @returns {dc.bubbleOverlay}
+     */
+    var _chart = dc.bubbleMixin(dc.baseMixin({}));
+    var _g;
+    var _points = [];
+
+    _chart.transitionDuration(750);
+
+    _chart.transitionDelay(0);
+
+    _chart.radiusValueAccessor(function (d) {
+        return d.value;
+    });
+
+    /**
+     * **mandatory**
+     *
+     * Set up a data point on the overlay. The name of a data point should match a specific 'key' among
+     * data groups generated using keyAccessor.  If a match is found (point name <-> data group key)
+     * then a bubble will be generated at the position specified by the function. x and y
+     * value specified here are relative to the underlying svg.
+     * @method point
+     * @memberof dc.bubbleOverlay
+     * @instance
+     * @param {String} name
+     * @param {Number} x
+     * @param {Number} y
+     * @returns {dc.bubbleOverlay}
+     */
+    _chart.point = function (name, x, y) {
+        _points.push({name: name, x: x, y: y});
+        return _chart;
+    };
+
+    _chart._doRender = function () {
+        _g = initOverlayG();
+
+        _chart.r().range([_chart.MIN_RADIUS, _chart.width() * _chart.maxBubbleRelativeSize()]);
+
+        initializeBubbles();
+
+        _chart.fadeDeselectedArea();
+
+        return _chart;
+    };
+
+    function initOverlayG () {
+        _g = _chart.select('g.' + BUBBLE_OVERLAY_CLASS);
+        if (_g.empty()) {
+            _g = _chart.svg().append('g').attr('class', BUBBLE_OVERLAY_CLASS);
+        }
+        return _g;
+    }
+
+    function initializeBubbles () {
+        var data = mapData();
+
+        _points.forEach(function (point) {
+            var nodeG = getNodeG(point, data);
+
+            var circle = nodeG.select('circle.' + BUBBLE_CLASS);
+
+            if (circle.empty()) {
+                circle = nodeG.append('circle')
+                    .attr('class', BUBBLE_CLASS)
+                    .attr('r', 0)
+                    .attr('fill', _chart.getColor)
+                    .on('click', _chart.onClick);
+            }
+
+            dc.transition(circle, _chart.transitionDuration(), _chart.transitionDelay())
+                .attr('r', function (d) {
+                    return _chart.bubbleR(d);
+                });
+
+            _chart._doRenderLabel(nodeG);
+
+            _chart._doRenderTitles(nodeG);
+        });
+    }
+
+    function mapData () {
+        var data = {};
+        _chart.data().forEach(function (datum) {
+            data[_chart.keyAccessor()(datum)] = datum;
+        });
+        return data;
+    }
+
+    function getNodeG (point, data) {
+        var bubbleNodeClass = BUBBLE_NODE_CLASS + ' ' + dc.utils.nameToId(point.name);
+
+        var nodeG = _g.select('g.' + dc.utils.nameToId(point.name));
+
+        if (nodeG.empty()) {
+            nodeG = _g.append('g')
+                .attr('class', bubbleNodeClass)
+                .attr('transform', 'translate(' + point.x + ',' + point.y + ')');
+        }
+
+        nodeG.datum(data[point.name]);
+
+        return nodeG;
+    }
+
+    _chart._doRedraw = function () {
+        updateBubbles();
+
+        _chart.fadeDeselectedArea();
+
+        return _chart;
+    };
+
+    function updateBubbles () {
+        var data = mapData();
+
+        _points.forEach(function (point) {
+            var nodeG = getNodeG(point, data);
+
+            var circle = nodeG.select('circle.' + BUBBLE_CLASS);
+
+            dc.transition(circle, _chart.transitionDuration(), _chart.transitionDelay())
+                .attr('r', function (d) {
+                    return _chart.bubbleR(d);
+                })
+                .attr('fill', _chart.getColor);
+
+            _chart.doUpdateLabels(nodeG);
+
+            _chart.doUpdateTitles(nodeG);
+        });
+    }
+
+    _chart.debug = function (flag) {
+        if (flag) {
+            var debugG = _chart.select('g.' + dc.constants.DEBUG_GROUP_CLASS);
+
+            if (debugG.empty()) {
+                debugG = _chart.svg()
+                    .append('g')
+                    .attr('class', dc.constants.DEBUG_GROUP_CLASS);
+            }
+
+            var debugText = debugG.append('text')
+                .attr('x', 10)
+                .attr('y', 20);
+
+            debugG
+                .append('rect')
+                .attr('width', _chart.width())
+                .attr('height', _chart.height())
+                .on('mousemove', function () {
+                    var position = d3.mouse(debugG.node());
+                    var msg = position[0] + ', ' + position[1];
+                    debugText.text(msg);
+                });
+        } else {
+            _chart.selectAll('.debug').remove();
+        }
+
+        return _chart;
+    };
+
+    _chart.anchor(parent, chartGroup);
+
+    return _chart;
+};
+
+/**
+ * Concrete row chart implementation.
+ *
+ * Examples:
+ * - {@link http://dc-js.github.com/dc.js/ Nasdaq 100 Index}
+ * @class rowChart
+ * @memberof dc
+ * @mixes dc.capMixin
+ * @mixes dc.marginMixin
+ * @mixes dc.colorMixin
+ * @mixes dc.baseMixin
+ * @example
+ * // create a row chart under #chart-container1 element using the default global chart group
+ * var chart1 = dc.rowChart('#chart-container1');
+ * // create a row chart under #chart-container2 element using chart group A
+ * var chart2 = dc.rowChart('#chart-container2', 'chartGroupA');
+ * @param {String|node|d3.selection} parent - Any valid
+ * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Selections.md#selecting-elements d3 single selector} specifying
+ * a dom block element such as a div; or a dom element or d3 selection.
+ * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.
+ * Interaction with a chart will only trigger events and redraws within the chart's group.
+ * @returns {dc.rowChart}
+ */
+dc.rowChart = function (parent, chartGroup) {
+
+    var _g;
+
+    var _labelOffsetX = 10;
+    var _labelOffsetY = 15;
+    var _hasLabelOffsetY = false;
+    var _dyOffset = '0.35em';  // this helps center labels https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#svg_text
+    var _titleLabelOffsetX = 2;
+
+    var _gap = 5;
+
+    var _fixedBarHeight = false;
+    var _rowCssClass = 'row';
+    var _titleRowCssClass = 'titlerow';
+    var _renderTitleLabel = false;
+
+    var _chart = dc.capMixin(dc.marginMixin(dc.colorMixin(dc.baseMixin({}))));
+
+    var _x;
+
+    var _elasticX;
+
+    var _xAxis = d3.svg.axis().orient('bottom');
+
+    var _rowData;
+
+    _chart.rowsCap = _chart.cap;
+
+    function calculateAxisScale () {
+        if (!_x || _elasticX) {
+            var extent = d3.extent(_rowData, _chart.cappedValueAccessor);
+            if (extent[0] > 0) {
+                extent[0] = 0;
+            }
+            if (extent[1] < 0) {
+                extent[1] = 0;
+            }
+            _x = d3.scale.linear().domain(extent)
+                .range([0, _chart.effectiveWidth()]);
+        }
+        _xAxis.scale(_x);
+    }
+
+    function drawAxis () {
+        var axisG = _g.select('g.axis');
+
+        calculateAxisScale();
+
+        if (axisG.empty()) {
+            axisG = _g.append('g').attr('class', 'axis');
+        }
+        axisG.attr('transform', 'translate(0, ' + _chart.effectiveHeight() + ')');
+
+        dc.transition(axisG, _chart.transitionDuration(), _chart.transitionDelay())
+            .call(_xAxis);
+    }
+
+    _chart._doRender = function () {
+        _chart.resetSvg();
+
+        _g = _chart.svg()
+            .append('g')
+            .attr('transform', 'translate(' + _chart.margins().left + ',' + _chart.margins().top + ')');
+
+        drawChart();
+
+        return _chart;
+    };
+
+    _chart.title(function (d) {
+        return _chart.cappedKeyAccessor(d) + ': ' + _chart.cappedValueAccessor(d);
+    });
+
+    _chart.label(_chart.cappedKeyAccessor);
+
+    /**
+     * Gets or sets the x scale. The x scale can be any d3
+     * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Quantitative-Scales.md quantitive scale}.
+     * @method x
+     * @memberof dc.rowChart
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Quantitative-Scales.md quantitive scale}
+     * @param {d3.scale} [scale]
+     * @returns {d3.scale|dc.rowChart}
+     */
+    _chart.x = function (scale) {
+        if (!arguments.length) {
+            return _x;
+        }
+        _x = scale;
+        return _chart;
+    };
+
+    function drawGridLines () {
+        _g.selectAll('g.tick')
+            .select('line.grid-line')
+            .remove();
+
+        _g.selectAll('g.tick')
+            .append('line')
+            .attr('class', 'grid-line')
+            .attr('x1', 0)
+            .attr('y1', 0)
+            .attr('x2', 0)
+            .attr('y2', function () {
+                return -_chart.effectiveHeight();
+            });
+    }
+
+    function drawChart () {
+        _rowData = _chart.data();
+
+        drawAxis();
+        drawGridLines();
+
+        var rows = _g.selectAll('g.' + _rowCssClass)
+            .data(_rowData);
+
+        createElements(rows);
+        removeElements(rows);
+        updateElements(rows);
+    }
+
+    function createElements (rows) {
+        var rowEnter = rows.enter()
+            .append('g')
+            .attr('class', function (d, i) {
+                return _rowCssClass + ' _' + i;
+            });
+
+        rowEnter.append('rect').attr('width', 0);
+
+        createLabels(rowEnter);
+    }
+
+    function removeElements (rows) {
+        rows.exit().remove();
+    }
+
+    function rootValue () {
+        var root = _x(0);
+        return (root === -Infinity || root !== root) ? _x(1) : root;
+    }
+
+    function updateElements (rows) {
+        var n = _rowData.length;
+
+        var height;
+        if (!_fixedBarHeight) {
+            height = (_chart.effectiveHeight() - (n + 1) * _gap) / n;
+        } else {
+            height = _fixedBarHeight;
+        }
+
+        // vertically align label in center unless they override the value via property setter
+        if (!_hasLabelOffsetY) {
+            _labelOffsetY = height / 2;
+        }
+
+        var rect = rows.attr('transform', function (d, i) {
+                return 'translate(0,' + ((i + 1) * _gap + i * height) + ')';
+            }).select('rect')
+            .attr('height', height)
+            .attr('fill', _chart.getColor)
+            .on('click', onClick)
+            .classed('deselected', function (d) {
+                return (_chart.hasFilter()) ? !isSelectedRow(d) : false;
+            })
+            .classed('selected', function (d) {
+                return (_chart.hasFilter()) ? isSelectedRow(d) : false;
+            });
+
+        dc.transition(rect, _chart.transitionDuration(), _chart.transitionDelay())
+            .attr('width', function (d) {
+                return Math.abs(rootValue() - _x(_chart.valueAccessor()(d)));
+            })
+            .attr('transform', translateX);
+
+        createTitles(rows);
+        updateLabels(rows);
+    }
+
+    function createTitles (rows) {
+        if (_chart.renderTitle()) {
+            rows.select('title').remove();
+            rows.append('title').text(_chart.title());
+        }
+    }
+
+    function createLabels (rowEnter) {
+        if (_chart.renderLabel()) {
+            rowEnter.append('text')
+                .on('click', onClick);
+        }
+        if (_chart.renderTitleLabel()) {
+            rowEnter.append('text')
+                .attr('class', _titleRowCssClass)
+                .on('click', onClick);
+        }
+    }
+
+    function updateLabels (rows) {
+        if (_chart.renderLabel()) {
+            var lab = rows.select('text')
+                .attr('x', _labelOffsetX)
+                .attr('y', _labelOffsetY)
+                .attr('dy', _dyOffset)
+                .on('click', onClick)
+                .attr('class', function (d, i) {
+                    return _rowCssClass + ' _' + i;
+                })
+                .text(function (d) {
+                    return _chart.label()(d);
+                });
+            dc.transition(lab, _chart.transitionDuration(), _chart.transitionDelay())
+                .attr('transform', translateX);
+        }
+        if (_chart.renderTitleLabel()) {
+            var titlelab = rows.select('.' + _titleRowCssClass)
+                    .attr('x', _chart.effectiveWidth() - _titleLabelOffsetX)
+                    .attr('y', _labelOffsetY)
+                    .attr('dy', _dyOffset)
+                    .attr('text-anchor', 'end')
+                    .on('click', onClick)
+                    .attr('class', function (d, i) {
+                        return _titleRowCssClass + ' _' + i ;
+                    })
+                    .text(function (d) {
+                        return _chart.title()(d);
+                    });
+            dc.transition(titlelab, _chart.transitionDuration(), _chart.transitionDelay())
+                .attr('transform', translateX);
+        }
+    }
+
+    /**
+     * Turn on/off Title label rendering (values) using SVG style of text-anchor 'end'.
+     * @method renderTitleLabel
+     * @memberof dc.rowChart
+     * @instance
+     * @param {Boolean} [renderTitleLabel=false]
+     * @returns {Boolean|dc.rowChart}
+     */
+    _chart.renderTitleLabel = function (renderTitleLabel) {
+        if (!arguments.length) {
+            return _renderTitleLabel;
+        }
+        _renderTitleLabel = renderTitleLabel;
+        return _chart;
+    };
+
+    function onClick (d) {
+        _chart.onClick(d);
+    }
+
+    function translateX (d) {
+        var x = _x(_chart.cappedValueAccessor(d)),
+            x0 = rootValue(),
+            s = x > x0 ? x0 : x;
+        return 'translate(' + s + ',0)';
+    }
+
+    _chart._doRedraw = function () {
+        drawChart();
+        return _chart;
+    };
+
+    /**
+     * Get the x axis for the row chart instance.  Note: not settable for row charts.
+     * See the {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#axis d3 axis object}
+     * documention for more information.
+     * @method xAxis
+     * @memberof dc.rowChart
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Axes.md#axis d3.svg.axis}
+     * @example
+     * // customize x axis tick format
+     * chart.xAxis().tickFormat(function (v) {return v + '%';});
+     * // customize x axis tick values
+     * chart.xAxis().tickValues([0, 100, 200, 300]);
+     * @returns {d3.svg.axis}
+     */
+    _chart.xAxis = function () {
+        return _xAxis;
+    };
+
+    /**
+     * Get or set the fixed bar height. Default is [false] which will auto-scale bars.
+     * For example, if you want to fix the height for a specific number of bars (useful in TopN charts)
+     * you could fix height as follows (where count = total number of bars in your TopN and gap is
+     * your vertical gap space).
+     * @method fixedBarHeight
+     * @memberof dc.rowChart
+     * @instance
+     * @example
+     * chart.fixedBarHeight( chartheight - (count + 1) * gap / count);
+     * @param {Boolean|Number} [fixedBarHeight=false]
+     * @returns {Boolean|Number|dc.rowChart}
+     */
+    _chart.fixedBarHeight = function (fixedBarHeight) {
+        if (!arguments.length) {
+            return _fixedBarHeight;
+        }
+        _fixedBarHeight = fixedBarHeight;
+        return _chart;
+    };
+
+    /**
+     * Get or set the vertical gap space between rows on a particular row chart instance.
+     * @method gap
+     * @memberof dc.rowChart
+     * @instance
+     * @param {Number} [gap=5]
+     * @returns {Number|dc.rowChart}
+     */
+    _chart.gap = function (gap) {
+        if (!arguments.length) {
+            return _gap;
+        }
+        _gap = gap;
+        return _chart;
+    };
+
+    /**
+     * Get or set the elasticity on x axis. If this attribute is set to true, then the x axis will rescle to auto-fit the
+     * data range when filtered.
+     * @method elasticX
+     * @memberof dc.rowChart
+     * @instance
+     * @param {Boolean} [elasticX]
+     * @returns {Boolean|dc.rowChart}
+     */
+    _chart.elasticX = function (elasticX) {
+        if (!arguments.length) {
+            return _elasticX;
+        }
+        _elasticX = elasticX;
+        return _chart;
+    };
+
+    /**
+     * Get or set the x offset (horizontal space to the top left corner of a row) for labels on a particular row chart.
+     * @method labelOffsetX
+     * @memberof dc.rowChart
+     * @instance
+     * @param {Number} [labelOffsetX=10]
+     * @returns {Number|dc.rowChart}
+     */
+    _chart.labelOffsetX = function (labelOffsetX) {
+        if (!arguments.length) {
+            return _labelOffsetX;
+        }
+        _labelOffsetX = labelOffsetX;
+        return _chart;
+    };
+
+    /**
+     * Get or set the y offset (vertical space to the top left corner of a row) for labels on a particular row chart.
+     * @method labelOffsetY
+     * @memberof dc.rowChart
+     * @instance
+     * @param {Number} [labelOffsety=15]
+     * @returns {Number|dc.rowChart}
+     */
+    _chart.labelOffsetY = function (labelOffsety) {
+        if (!arguments.length) {
+            return _labelOffsetY;
+        }
+        _labelOffsetY = labelOffsety;
+        _hasLabelOffsetY = true;
+        return _chart;
+    };
+
+    /**
+     * Get of set the x offset (horizontal space between right edge of row and right edge or text.
+     * @method titleLabelOffsetX
+     * @memberof dc.rowChart
+     * @instance
+     * @param {Number} [titleLabelOffsetX=2]
+     * @returns {Number|dc.rowChart}
+     */
+    _chart.titleLabelOffsetX = function (titleLabelOffsetX) {
+        if (!arguments.length) {
+            return _titleLabelOffsetX;
+        }
+        _titleLabelOffsetX = titleLabelOffsetX;
+        return _chart;
+    };
+
+    function isSelectedRow (d) {
+        return _chart.hasFilter(_chart.cappedKeyAccessor(d));
+    }
+
+    return _chart.anchor(parent, chartGroup);
+};
+
+/**
+ * Legend is a attachable widget that can be added to other dc charts to render horizontal legend
+ * labels.
+ *
+ * Examples:
+ * - {@link http://dc-js.github.com/dc.js/ Nasdaq 100 Index}
+ * - {@link http://dc-js.github.com/dc.js/crime/index.html Canadian City Crime Stats}
+ * @class legend
+ * @memberof dc
+ * @example
+ * chart.legend(dc.legend().x(400).y(10).itemHeight(13).gap(5))
+ * @returns {dc.legend}
+ */
+dc.legend = function () {
+    var LABEL_GAP = 2;
+
+    var _legend = {},
+        _parent,
+        _x = 0,
+        _y = 0,
+        _itemHeight = 12,
+        _gap = 5,
+        _horizontal = false,
+        _legendWidth = 560,
+        _itemWidth = 70,
+        _autoItemWidth = false,
+        _legendText = dc.pluck('name'),
+        _maxItems;
+
+    var _g;
+
+    _legend.parent = function (p) {
+        if (!arguments.length) {
+            return _parent;
+        }
+        _parent = p;
+        return _legend;
+    };
+
+    _legend.render = function () {
+        _parent.svg().select('g.dc-legend').remove();
+        _g = _parent.svg().append('g')
+            .attr('class', 'dc-legend')
+            .attr('transform', 'translate(' + _x + ',' + _y + ')');
+        var legendables = _parent.legendables();
+
+        if (_maxItems !== undefined) {
+            legendables = legendables.slice(0, _maxItems);
+        }
+
+        var itemEnter = _g.selectAll('g.dc-legend-item')
+            .data(legendables)
+            .enter()
+            .append('g')
+            .attr('class', 'dc-legend-item')
+            .on('mouseover', function (d) {
+                _parent.legendHighlight(d);
+            })
+            .on('mouseout', function (d) {
+                _parent.legendReset(d);
+            })
+            .on('click', function (d) {
+                d.chart.legendToggle(d);
+            });
+
+        _g.selectAll('g.dc-legend-item')
+            .classed('fadeout', function (d) {
+                return d.chart.isLegendableHidden(d);
+            });
+
+        if (legendables.some(dc.pluck('dashstyle'))) {
+            itemEnter
+                .append('line')
+                .attr('x1', 0)
+                .attr('y1', _itemHeight / 2)
+                .attr('x2', _itemHeight)
+                .attr('y2', _itemHeight / 2)
+                .attr('stroke-width', 2)
+                .attr('stroke-dasharray', dc.pluck('dashstyle'))
+                .attr('stroke', dc.pluck('color'));
+        } else {
+            itemEnter
+                .append('rect')
+                .attr('width', _itemHeight)
+                .attr('height', _itemHeight)
+                .attr('fill', function (d) {return d ? d.color : 'blue';});
+        }
+
+        itemEnter.append('text')
+                .text(_legendText)
+                .attr('x', _itemHeight + LABEL_GAP)
+                .attr('y', function () {
+                    return _itemHeight / 2 + (this.clientHeight ? this.clientHeight : 13) / 2 - 2;
+                });
+
+        var _cumulativeLegendTextWidth = 0;
+        var row = 0;
+        itemEnter.attr('transform', function (d, i) {
+            if (_horizontal) {
+                var itemWidth   = _autoItemWidth === true ? this.getBBox().width + _gap : _itemWidth;
+                if ((_cumulativeLegendTextWidth + itemWidth) > _legendWidth && _cumulativeLegendTextWidth > 0) {
+                    ++row;
+                    _cumulativeLegendTextWidth = 0;
+                }
+                var translateBy = 'translate(' + _cumulativeLegendTextWidth + ',' + row * legendItemHeight() + ')';
+                _cumulativeLegendTextWidth += itemWidth;
+                return translateBy;
+            } else {
+                return 'translate(0,' + i * legendItemHeight() + ')';
+            }
+        });
+    };
+
+    function legendItemHeight () {
+        return _gap + _itemHeight;
+    }
+
+    /**
+     * Set or get x coordinate for legend widget.
+     * @method x
+     * @memberof dc.legend
+     * @instance
+     * @param  {Number} [x=0]
+     * @returns {Number|dc.legend}
+     */
+    _legend.x = function (x) {
+        if (!arguments.length) {
+            return _x;
+        }
+        _x = x;
+        return _legend;
+    };
+
+    /**
+     * Set or get y coordinate for legend widget.
+     * @method y
+     * @memberof dc.legend
+     * @instance
+     * @param  {Number} [y=0]
+     * @returns {Number|dc.legend}
+     */
+    _legend.y = function (y) {
+        if (!arguments.length) {
+            return _y;
+        }
+        _y = y;
+        return _legend;
+    };
+
+    /**
+     * Set or get gap between legend items.
+     * @method gap
+     * @memberof dc.legend
+     * @instance
+     * @param  {Number} [gap=5]
+     * @returns {Number|dc.legend}
+     */
+    _legend.gap = function (gap) {
+        if (!arguments.length) {
+            return _gap;
+        }
+        _gap = gap;
+        return _legend;
+    };
+
+    /**
+     * Set or get legend item height.
+     * @method itemHeight
+     * @memberof dc.legend
+     * @instance
+     * @param  {Number} [itemHeight=12]
+     * @returns {Number|dc.legend}
+     */
+    _legend.itemHeight = function (itemHeight) {
+        if (!arguments.length) {
+            return _itemHeight;
+        }
+        _itemHeight = itemHeight;
+        return _legend;
+    };
+
+    /**
+     * Position legend horizontally instead of vertically.
+     * @method horizontal
+     * @memberof dc.legend
+     * @instance
+     * @param  {Boolean} [horizontal=false]
+     * @returns {Boolean|dc.legend}
+     */
+    _legend.horizontal = function (horizontal) {
+        if (!arguments.length) {
+            return _horizontal;
+        }
+        _horizontal = horizontal;
+        return _legend;
+    };
+
+    /**
+     * Maximum width for horizontal legend.
+     * @method legendWidth
+     * @memberof dc.legend
+     * @instance
+     * @param  {Number} [legendWidth=500]
+     * @returns {Number|dc.legend}
+     */
+    _legend.legendWidth = function (legendWidth) {
+        if (!arguments.length) {
+            return _legendWidth;
+        }
+        _legendWidth = legendWidth;
+        return _legend;
+    };
+
+    /**
+     * Legend item width for horizontal legend.
+     * @method itemWidth
+     * @memberof dc.legend
+     * @instance
+     * @param  {Number} [itemWidth=70]
+     * @returns {Number|dc.legend}
+     */
+    _legend.itemWidth = function (itemWidth) {
+        if (!arguments.length) {
+            return _itemWidth;
+        }
+        _itemWidth = itemWidth;
+        return _legend;
+    };
+
+    /**
+     * Turn automatic width for legend items on or off. If true, {@link dc.legend#itemWidth itemWidth} is ignored.
+     * This setting takes into account the {@link dc.legend#gap gap}.
+     * @method autoItemWidth
+     * @memberof dc.legend
+     * @instance
+     * @param  {Boolean} [autoItemWidth=false]
+     * @returns {Boolean|dc.legend}
+     */
+    _legend.autoItemWidth = function (autoItemWidth) {
+        if (!arguments.length) {
+            return _autoItemWidth;
+        }
+        _autoItemWidth = autoItemWidth;
+        return _legend;
+    };
+
+    /**
+     * Set or get the legend text function. The legend widget uses this function to render the legend
+     * text for each item. If no function is specified the legend widget will display the names
+     * associated with each group.
+     * @method legendText
+     * @memberof dc.legend
+     * @instance
+     * @param  {Function} [legendText]
+     * @returns {Function|dc.legend}
+     * @example
+     * // default legendText
+     * legend.legendText(dc.pluck('name'))
+     *
+     * // create numbered legend items
+     * chart.legend(dc.legend().legendText(function(d, i) { return i + '. ' + d.name; }))
+     *
+     * // create legend displaying group counts
+     * chart.legend(dc.legend().legendText(function(d) { return d.name + ': ' d.data; }))
+     **/
+    _legend.legendText = function (legendText) {
+        if (!arguments.length) {
+            return _legendText;
+        }
+        _legendText = legendText;
+        return _legend;
+    };
+
+    /**
+     * Maximum number of legend items to display
+     * @method maxItems
+     * @memberof dc.legend
+     * @instance
+     * @param  {Number} [maxItems]
+     * @return {dc.legend}
+     */
+    _legend.maxItems = function (maxItems) {
+        if (!arguments.length) {
+            return _maxItems;
+        }
+        _maxItems = dc.utils.isNumber(maxItems) ? maxItems : undefined;
+        return _legend;
+    };
+
+    return _legend;
+};
+
+/**
+ * A scatter plot chart
+ *
+ * Examples:
+ * - {@link http://dc-js.github.io/dc.js/examples/scatter.html Scatter Chart}
+ * - {@link http://dc-js.github.io/dc.js/examples/multi-scatter.html Multi-Scatter Chart}
+ * @class scatterPlot
+ * @memberof dc
+ * @mixes dc.coordinateGridMixin
+ * @example
+ * // create a scatter plot under #chart-container1 element using the default global chart group
+ * var chart1 = dc.scatterPlot('#chart-container1');
+ * // create a scatter plot under #chart-container2 element using chart group A
+ * var chart2 = dc.scatterPlot('#chart-container2', 'chartGroupA');
+ * // create a sub-chart under a composite parent chart
+ * var chart3 = dc.scatterPlot(compositeChart);
+ * @param {String|node|d3.selection} parent - Any valid
+ * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Selections.md#selecting-elements d3 single selector} specifying
+ * a dom block element such as a div; or a dom element or d3 selection.
+ * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.
+ * Interaction with a chart will only trigger events and redraws within the chart's group.
+ * @returns {dc.scatterPlot}
+ */
+dc.scatterPlot = function (parent, chartGroup) {
+    var _chart = dc.coordinateGridMixin({});
+    var _symbol = d3.svg.symbol();
+
+    var _existenceAccessor = function (d) { return d.value; };
+
+    var originalKeyAccessor = _chart.keyAccessor();
+    _chart.keyAccessor(function (d) { return originalKeyAccessor(d)[0]; });
+    _chart.valueAccessor(function (d) { return originalKeyAccessor(d)[1]; });
+    _chart.colorAccessor(function () { return _chart._groupName; });
+
+    _chart.title(function (d) {
+        // this basically just counteracts the setting of its own key/value accessors
+        // see https://github.com/dc-js/dc.js/issues/702
+        return _chart.keyAccessor()(d) + ',' + _chart.valueAccessor()(d) + ': ' +
+            _chart.existenceAccessor()(d);
+    });
+
+    var _locator = function (d) {
+        return 'translate(' + _chart.x()(_chart.keyAccessor()(d)) + ',' +
+                              _chart.y()(_chart.valueAccessor()(d)) + ')';
+    };
+
+    var _highlightedSize = 7;
+    var _symbolSize = 5;
+    var _excludedSize = 3;
+    var _excludedColor = null;
+    var _excludedOpacity = 1.0;
+    var _emptySize = 0;
+    var _emptyOpacity = 0;
+    var _nonemptyOpacity = 1;
+    var _emptyColor = null;
+    var _filtered = [];
+
+    function elementSize (d, i) {
+        if (!_existenceAccessor(d)) {
+            return Math.pow(_emptySize, 2);
+        } else if (_filtered[i]) {
+            return Math.pow(_symbolSize, 2);
+        } else {
+            return Math.pow(_excludedSize, 2);
+        }
+    }
+    _symbol.size(elementSize);
+
+    dc.override(_chart, '_filter', function (filter) {
+        if (!arguments.length) {
+            return _chart.__filter();
+        }
+
+        return _chart.__filter(dc.filters.RangedTwoDimensionalFilter(filter));
+    });
+
+    _chart.plotData = function () {
+        var symbols = _chart.chartBodyG().selectAll('path.symbol')
+            .data(_chart.data());
+
+        symbols
+            .enter()
+        .append('path')
+            .attr('class', 'symbol')
+            .attr('opacity', 0)
+            .attr('fill', _chart.getColor)
+            .attr('transform', _locator);
+
+        symbols.call(renderTitles, _chart.data());
+
+        symbols.each(function (d, i) {
+            _filtered[i] = !_chart.filter() || _chart.filter().isFiltered([d.key[0], d.key[1]]);
+        });
+
+        dc.transition(symbols, _chart.transitionDuration(), _chart.transitionDelay())
+            .attr('opacity', function (d, i) {
+                if (!_existenceAccessor(d)) {
+                    return _emptyOpacity;
+                } else if (_filtered[i]) {
+                    return _nonemptyOpacity;
+                } else {
+                    return _chart.excludedOpacity();
+                }
+            })
+            .attr('fill', function (d, i) {
+                if (_emptyColor && !_existenceAccessor(d)) {
+                    return _emptyColor;
+                } else if (_chart.excludedColor() && !_filtered[i]) {
+                    return _chart.excludedColor();
+                } else {
+                    return _chart.getColor(d);
+                }
+            })
+            .attr('transform', _locator)
+            .attr('d', _symbol);
+
+        dc.transition(symbols.exit(), _chart.transitionDuration(), _chart.transitionDelay())
+            .attr('opacity', 0).remove();
+    };
+
+    function renderTitles (symbol, d) {
+        if (_chart.renderTitle()) {
+            symbol.selectAll('title').remove();
+            symbol.append('title').text(function (d) {
+                return _chart.title()(d);
+            });
+        }
+    }
+
+    /**
+     * Get or set the existence accessor.  If a point exists, it is drawn with
+     * {@link dc.scatterPlot#symbolSize symbolSize} radius and
+     * opacity 1; if it does not exist, it is drawn with
+     * {@link dc.scatterPlot#emptySize emptySize} radius and opacity 0. By default,
+     * the existence accessor checks if the reduced value is truthy.
+     * @method existenceAccessor
+     * @memberof dc.scatterPlot
+     * @instance
+     * @see {@link dc.scatterPlot#symbolSize symbolSize}
+     * @see {@link dc.scatterPlot#emptySize emptySize}
+     * @example
+     * // default accessor
+     * chart.existenceAccessor(function (d) { return d.value; });
+     * @param {Function} [accessor]
+     * @returns {Function|dc.scatterPlot}
+     */
+    _chart.existenceAccessor = function (accessor) {
+        if (!arguments.length) {
+            return _existenceAccessor;
+        }
+        _existenceAccessor = accessor;
+        return this;
+    };
+
+    /**
+     * Get or set the symbol type used for each point. By default the symbol is a circle.
+     * Type can be a constant or an accessor.
+     * @method symbol
+     * @memberof dc.scatterPlot
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#symbol_type d3.svg.symbol.type}
+     * @example
+     * // Circle type
+     * chart.symbol('circle');
+     * // Square type
+     * chart.symbol('square');
+     * @param {String|Function} [type='circle']
+     * @returns {String|Function|dc.scatterPlot}
+     */
+    _chart.symbol = function (type) {
+        if (!arguments.length) {
+            return _symbol.type();
+        }
+        _symbol.type(type);
+        return _chart;
+    };
+
+    /**
+     * Get or set the symbol generator. By default `dc.scatterPlot` will use
+     * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#symbol d3.svg.symbol()}
+     * to generate symbols. `dc.scatterPlot` will set the
+     * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#symbol_size size accessor}
+     * on the symbol generator.
+     * @method customSymbol
+     * @memberof dc.scatterPlot
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#symbol d3.svg.symbol}
+     * @see {@link https://stackoverflow.com/questions/25332120/create-additional-d3-js-symbols Create additional D3.js symbols}
+     * @param {String|Function} [customSymbol=d3.svg.symbol()]
+     * @returns {String|Function|dc.scatterPlot}
+     */
+    _chart.customSymbol = function (customSymbol) {
+        if (!arguments.length) {
+            return _symbol;
+        }
+        _symbol = customSymbol;
+        _symbol.size(elementSize);
+        return _chart;
+    };
+
+    /**
+     * Set or get radius for symbols.
+     * @method symbolSize
+     * @memberof dc.scatterPlot
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#symbol_size d3.svg.symbol.size}
+     * @param {Number} [symbolSize=3]
+     * @returns {Number|dc.scatterPlot}
+     */
+    _chart.symbolSize = function (symbolSize) {
+        if (!arguments.length) {
+            return _symbolSize;
+        }
+        _symbolSize = symbolSize;
+        return _chart;
+    };
+
+    /**
+     * Set or get radius for highlighted symbols.
+     * @method highlightedSize
+     * @memberof dc.scatterPlot
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#symbol_size d3.svg.symbol.size}
+     * @param {Number} [highlightedSize=5]
+     * @returns {Number|dc.scatterPlot}
+     */
+    _chart.highlightedSize = function (highlightedSize) {
+        if (!arguments.length) {
+            return _highlightedSize;
+        }
+        _highlightedSize = highlightedSize;
+        return _chart;
+    };
+
+    /**
+     * Set or get size for symbols excluded from this chart's filter. If null, no
+     * special size is applied for symbols based on their filter status.
+     * @method excludedSize
+     * @memberof dc.scatterPlot
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#symbol_size d3.svg.symbol.size}
+     * @param {Number} [excludedSize=null]
+     * @returns {Number|dc.scatterPlot}
+     */
+    _chart.excludedSize = function (excludedSize) {
+        if (!arguments.length) {
+            return _excludedSize;
+        }
+        _excludedSize = excludedSize;
+        return _chart;
+    };
+
+    /**
+     * Set or get color for symbols excluded from this chart's filter. If null, no
+     * special color is applied for symbols based on their filter status.
+     * @method excludedColor
+     * @memberof dc.scatterPlot
+     * @instance
+     * @param {Number} [excludedColor=null]
+     * @returns {Number|dc.scatterPlot}
+     */
+    _chart.excludedColor = function (excludedColor) {
+        if (!arguments.length) {
+            return _excludedColor;
+        }
+        _excludedColor = excludedColor;
+        return _chart;
+    };
+
+    /**
+     * Set or get opacity for symbols excluded from this chart's filter.
+     * @method excludedOpacity
+     * @memberof dc.scatterPlot
+     * @instance
+     * @param {Number} [excludedOpacity=1.0]
+     * @returns {Number|dc.scatterPlot}
+     */
+    _chart.excludedOpacity = function (excludedOpacity) {
+        if (!arguments.length) {
+            return _excludedOpacity;
+        }
+        _excludedOpacity = excludedOpacity;
+        return _chart;
+    };
+
+    /**
+     * Set or get radius for symbols when the group is empty.
+     * @method emptySize
+     * @memberof dc.scatterPlot
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/SVG-Shapes.md#symbol_size d3.svg.symbol.size}
+     * @param {Number} [emptySize=0]
+     * @returns {Number|dc.scatterPlot}
+     */
+    _chart.hiddenSize = _chart.emptySize = function (emptySize) {
+        if (!arguments.length) {
+            return _emptySize;
+        }
+        _emptySize = emptySize;
+        return _chart;
+    };
+
+    /**
+     * Set or get color for symbols when the group is empty. If null, just use the
+     * {@link dc.colorMixin#colors colorMixin.colors} color scale zero value.
+     * @name emptyColor
+     * @memberof dc.scatterPlot
+     * @instance
+     * @param {String} [emptyColor=null]
+     * @return {String}
+     * @return {dc.scatterPlot}/
+     */
+    _chart.emptyColor = function (emptyColor) {
+        if (!arguments.length) {
+            return _emptyColor;
+        }
+        _emptyColor = emptyColor;
+        return _chart;
+    };
+
+    /**
+     * Set or get opacity for symbols when the group is empty.
+     * @name emptyOpacity
+     * @memberof dc.scatterPlot
+     * @instance
+     * @param {Number} [emptyOpacity=0]
+     * @return {Number}
+     * @return {dc.scatterPlot}
+     */
+    _chart.emptyOpacity = function (emptyOpacity) {
+        if (!arguments.length) {
+            return _emptyOpacity;
+        }
+        _emptyOpacity = emptyOpacity;
+        return _chart;
+    };
+
+    /**
+     * Set or get opacity for symbols when the group is not empty.
+     * @name nonemptyOpacity
+     * @memberof dc.scatterPlot
+     * @instance
+     * @param {Number} [nonemptyOpacity=1]
+     * @return {Number}
+     * @return {dc.scatterPlot}
+     */
+    _chart.nonemptyOpacity = function (nonemptyOpacity) {
+        if (!arguments.length) {
+            return _emptyOpacity;
+        }
+        _nonemptyOpacity = nonemptyOpacity;
+        return _chart;
+    };
+
+    _chart.legendables = function () {
+        return [{chart: _chart, name: _chart._groupName, color: _chart.getColor()}];
+    };
+
+    _chart.legendHighlight = function (d) {
+        resizeSymbolsWhere(function (symbol) {
+            return symbol.attr('fill') === d.color;
+        }, _highlightedSize);
+        _chart.chartBodyG().selectAll('.chart-body path.symbol').filter(function () {
+            return d3.select(this).attr('fill') !== d.color;
+        }).classed('fadeout', true);
+    };
+
+    _chart.legendReset = function (d) {
+        resizeSymbolsWhere(function (symbol) {
+            return symbol.attr('fill') === d.color;
+        }, _symbolSize);
+        _chart.chartBodyG().selectAll('.chart-body path.symbol').filter(function () {
+            return d3.select(this).attr('fill') !== d.color;
+        }).classed('fadeout', false);
+    };
+
+    function resizeSymbolsWhere (condition, size) {
+        var symbols = _chart.chartBodyG().selectAll('.chart-body path.symbol').filter(function () {
+            return condition(d3.select(this));
+        });
+        var oldSize = _symbol.size();
+        _symbol.size(Math.pow(size, 2));
+        dc.transition(symbols, _chart.transitionDuration(), _chart.transitionDelay()).attr('d', _symbol);
+        _symbol.size(oldSize);
+    }
+
+    _chart.setHandlePaths = function () {
+        // no handle paths for poly-brushes
+    };
+
+    _chart.extendBrush = function () {
+        var extent = _chart.brush().extent();
+        if (_chart.round()) {
+            extent[0] = extent[0].map(_chart.round());
+            extent[1] = extent[1].map(_chart.round());
+
+            _chart.g().select('.brush')
+                .call(_chart.brush().extent(extent));
+        }
+        return extent;
+    };
+
+    _chart.brushIsEmpty = function (extent) {
+        return _chart.brush().empty() || !extent || extent[0][0] >= extent[1][0] || extent[0][1] >= extent[1][1];
+    };
+
+    _chart._brushing = function () {
+        var extent = _chart.extendBrush();
+
+        _chart.redrawBrush(_chart.g());
+
+        if (_chart.brushIsEmpty(extent)) {
+            dc.events.trigger(function () {
+                _chart.filter(null);
+                _chart.redrawGroup();
+            });
+
+        } else {
+            var ranged2DFilter = dc.filters.RangedTwoDimensionalFilter(extent);
+            dc.events.trigger(function () {
+                _chart.filter(null);
+                _chart.filter(ranged2DFilter);
+                _chart.redrawGroup();
+            }, dc.constants.EVENT_DELAY);
+
+        }
+    };
+
+    _chart.setBrushY = function (gBrush) {
+        gBrush.call(_chart.brush().y(_chart.y()));
+    };
+
+    return _chart.anchor(parent, chartGroup);
+};
+
+/**
+ * A display of a single numeric value.
+ * Unlike other charts, you do not need to set a dimension. Instead a group object must be provided and
+ * a valueAccessor that returns a single value.
+ * @class numberDisplay
+ * @memberof dc
+ * @mixes dc.baseMixin
+ * @example
+ * // create a number display under #chart-container1 element using the default global chart group
+ * var display1 = dc.numberDisplay('#chart-container1');
+ * @param {String|node|d3.selection} parent - Any valid
+ * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Selections.md#selecting-elements d3 single selector} specifying
+ * a dom block element such as a div; or a dom element or d3 selection.
+ * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.
+ * Interaction with a chart will only trigger events and redraws within the chart's group.
+ * @returns {dc.numberDisplay}
+ */
+dc.numberDisplay = function (parent, chartGroup) {
+    var SPAN_CLASS = 'number-display';
+    var _formatNumber = d3.format('.2s');
+    var _chart = dc.baseMixin({});
+    var _html = {one: '', some: '', none: ''};
+    var _lastValue;
+
+    // dimension not required
+    _chart._mandatoryAttributes(['group']);
+
+    /**
+     * Gets or sets an optional object specifying HTML templates to use depending on the number
+     * displayed.  The text `%number` will be replaced with the current value.
+     * - one: HTML template to use if the number is 1
+     * - zero: HTML template to use if the number is 0
+     * - some: HTML template to use otherwise
+     * @method html
+     * @memberof dc.numberDisplay
+     * @instance
+     * @example
+     * numberWidget.html({
+     *      one:'%number record',
+     *      some:'%number records',
+     *      none:'no records'})
+     * @param {{one:String, some:String, none:String}} [html={one: '', some: '', none: ''}]
+     * @returns {{one:String, some:String, none:String}|dc.numberDisplay}
+     */
+    _chart.html = function (html) {
+        if (!arguments.length) {
+            return _html;
+        }
+        if (html.none) {
+            _html.none = html.none;//if none available
+        } else if (html.one) {
+            _html.none = html.one;//if none not available use one
+        } else if (html.some) {
+            _html.none = html.some;//if none and one not available use some
+        }
+        if (html.one) {
+            _html.one = html.one;//if one available
+        } else if (html.some) {
+            _html.one = html.some;//if one not available use some
+        }
+        if (html.some) {
+            _html.some = html.some;//if some available
+        } else if (html.one) {
+            _html.some = html.one;//if some not available use one
+        }
+        return _chart;
+    };
+
+    /**
+     * Calculate and return the underlying value of the display.
+     * @method value
+     * @memberof dc.numberDisplay
+     * @instance
+     * @returns {Number}
+     */
+    _chart.value = function () {
+        return _chart.data();
+    };
+
+    _chart.data(function (group) {
+        var valObj = group.value ? group.value() : group.top(1)[0];
+        return _chart.valueAccessor()(valObj);
+    });
+
+    _chart.transitionDuration(250); // good default
+    _chart.transitionDelay(0);
+
+    _chart._doRender = function () {
+        var newValue = _chart.value(),
+            span = _chart.selectAll('.' + SPAN_CLASS);
+
+        if (span.empty()) {
+            span = span.data([0])
+                .enter()
+                .append('span')
+                .attr('class', SPAN_CLASS);
+        }
+
+        span.transition()
+            .duration(_chart.transitionDuration())
+            .delay(_chart.transitionDelay())
+            .ease('quad-out-in')
+            .tween('text', function () {
+                // [XA] don't try and interpolate from Infinity, else this breaks.
+                var interpStart = isFinite(_lastValue) ? _lastValue : 0;
+                var interp = d3.interpolateNumber(interpStart || 0, newValue);
+                _lastValue = newValue;
+                return function (t) {
+                    var html = null, num = _chart.formatNumber()(interp(t));
+                    if (newValue === 0 && (_html.none !== '')) {
+                        html = _html.none;
+                    } else if (newValue === 1 && (_html.one !== '')) {
+                        html = _html.one;
+                    } else if (_html.some !== '') {
+                        html = _html.some;
+                    }
+                    this.innerHTML = html ? html.replace('%number', num) : num;
+                };
+            });
+    };
+
+    _chart._doRedraw = function () {
+        return _chart._doRender();
+    };
+
+    /**
+     * Get or set a function to format the value for the display.
+     * @method formatNumber
+     * @memberof dc.numberDisplay
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Formatting.md d3.format}
+     * @param {Function} [formatter=d3.format('.2s')]
+     * @returns {Function|dc.numberDisplay}
+     */
+    _chart.formatNumber = function (formatter) {
+        if (!arguments.length) {
+            return _formatNumber;
+        }
+        _formatNumber = formatter;
+        return _chart;
+    };
+
+    return _chart.anchor(parent, chartGroup);
+};
+
+/**
+ * A heat map is matrix that represents the values of two dimensions of data using colors.
+ * @class heatMap
+ * @memberof dc
+ * @mixes dc.colorMixin
+ * @mixes dc.marginMixin
+ * @mixes dc.baseMixin
+ * @example
+ * // create a heat map under #chart-container1 element using the default global chart group
+ * var heatMap1 = dc.heatMap('#chart-container1');
+ * // create a heat map under #chart-container2 element using chart group A
+ * var heatMap2 = dc.heatMap('#chart-container2', 'chartGroupA');
+ * @param {String|node|d3.selection} parent - Any valid
+ * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Selections.md#selecting-elements d3 single selector} specifying
+ * a dom block element such as a div; or a dom element or d3 selection.
+ * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.
+ * Interaction with a chart will only trigger events and redraws within the chart's group.
+ * @returns {dc.heatMap}
+ */
+dc.heatMap = function (parent, chartGroup) {
+
+    var DEFAULT_BORDER_RADIUS = 6.75;
+
+    var _chartBody;
+
+    var _cols;
+    var _rows;
+    var _xBorderRadius = DEFAULT_BORDER_RADIUS;
+    var _yBorderRadius = DEFAULT_BORDER_RADIUS;
+
+    var _chart = dc.colorMixin(dc.marginMixin(dc.baseMixin({})));
+    _chart._mandatoryAttributes(['group']);
+    _chart.title(_chart.colorAccessor());
+
+    var _colsLabel = function (d) {
+        return d;
+    };
+    var _rowsLabel = function (d) {
+        return d;
+    };
+
+    /**
+     * Set or get the column label function. The chart class uses this function to render
+     * column labels on the X axis. It is passed the column name.
+     * @method colsLabel
+     * @memberof dc.heatMap
+     * @instance
+     * @example
+     * // the default label function just returns the name
+     * chart.colsLabel(function(d) { return d; });
+     * @param  {Function} [labelFunction=function(d) { return d; }]
+     * @returns {Function|dc.heatMap}
+     */
+    _chart.colsLabel = function (labelFunction) {
+        if (!arguments.length) {
+            return _colsLabel;
+        }
+        _colsLabel = labelFunction;
+        return _chart;
+    };
+
+    /**
+     * Set or get the row label function. The chart class uses this function to render
+     * row labels on the Y axis. It is passed the row name.
+     * @method rowsLabel
+     * @memberof dc.heatMap
+     * @instance
+     * @example
+     * // the default label function just returns the name
+     * chart.rowsLabel(function(d) { return d; });
+     * @param  {Function} [labelFunction=function(d) { return d; }]
+     * @returns {Function|dc.heatMap}
+     */
+    _chart.rowsLabel = function (labelFunction) {
+        if (!arguments.length) {
+            return _rowsLabel;
+        }
+        _rowsLabel = labelFunction;
+        return _chart;
+    };
+
+    var _xAxisOnClick = function (d) { filterAxis(0, d); };
+    var _yAxisOnClick = function (d) { filterAxis(1, d); };
+    var _boxOnClick = function (d) {
+        var filter = d.key;
+        dc.events.trigger(function () {
+            _chart.filter(filter);
+            _chart.redrawGroup();
+        });
+    };
+
+    function filterAxis (axis, value) {
+        var cellsOnAxis = _chart.selectAll('.box-group').filter(function (d) {
+            return d.key[axis] === value;
+        });
+        var unfilteredCellsOnAxis = cellsOnAxis.filter(function (d) {
+            return !_chart.hasFilter(d.key);
+        });
+        dc.events.trigger(function () {
+            var selection = unfilteredCellsOnAxis.empty() ? cellsOnAxis : unfilteredCellsOnAxis;
+            var filters = selection.data().map(function (kv) {
+                return dc.filters.TwoDimensionalFilter(kv.key);
+            });
+            _chart._filter([filters]);
+            _chart.redrawGroup();
+        });
+    }
+
+    dc.override(_chart, 'filter', function (filter) {
+        if (!arguments.length) {
+            return _chart._filter();
+        }
+
+        return _chart._filter(dc.filters.TwoDimensionalFilter(filter));
+    });
+
+    function uniq (d, i, a) {
+        return !i || a[i - 1] !== d;
+    }
+
+    /**
+     * Gets or sets the values used to create the rows of the heatmap, as an array. By default, all
+     * the values will be fetched from the data using the value accessor, and they will be sorted in
+     * ascending order.
+     * @method rows
+     * @memberof dc.heatMap
+     * @instance
+     * @param  {Array<String|Number>} [rows]
+     * @returns {Array<String|Number>|dc.heatMap}
+     */
+    _chart.rows = function (rows) {
+        if (arguments.length) {
+            _rows = rows;
+            return _chart;
+        }
+        if (_rows) {
+            return _rows;
+        }
+        var rowValues = _chart.data().map(_chart.valueAccessor());
+        rowValues.sort(d3.ascending);
+        return d3.scale.ordinal().domain(rowValues.filter(uniq));
+    };
+
+    /**
+     * Gets or sets the keys used to create the columns of the heatmap, as an array. By default, all
+     * the values will be fetched from the data using the key accessor, and they will be sorted in
+     * ascending order.
+     * @method cols
+     * @memberof dc.heatMap
+     * @instance
+     * @param  {Array<String|Number>} [cols]
+     * @returns {Array<String|Number>|dc.heatMap}
+     */
+    _chart.cols = function (cols) {
+        if (arguments.length) {
+            _cols = cols;
+            return _chart;
+        }
+        if (_cols) {
+            return _cols;
+        }
+        var colValues = _chart.data().map(_chart.keyAccessor());
+        colValues.sort(d3.ascending);
+        return d3.scale.ordinal().domain(colValues.filter(uniq));
+    };
+
+    _chart._doRender = function () {
+        _chart.resetSvg();
+
+        _chartBody = _chart.svg()
+            .append('g')
+            .attr('class', 'heatmap')
+            .attr('transform', 'translate(' + _chart.margins().left + ',' + _chart.margins().top + ')');
+
+        return _chart._doRedraw();
+    };
+
+    _chart._doRedraw = function () {
+        var rows = _chart.rows(),
+            cols = _chart.cols(),
+            rowCount = rows.domain().length,
+            colCount = cols.domain().length,
+            boxWidth = Math.floor(_chart.effectiveWidth() / colCount),
+            boxHeight = Math.floor(_chart.effectiveHeight() / rowCount);
+
+        cols.rangeRoundBands([0, _chart.effectiveWidth()]);
+        rows.rangeRoundBands([_chart.effectiveHeight(), 0]);
+
+        var boxes = _chartBody.selectAll('g.box-group').data(_chart.data(), function (d, i) {
+            return _chart.keyAccessor()(d, i) + '\0' + _chart.valueAccessor()(d, i);
+        });
+        var gEnter = boxes.enter().append('g')
+            .attr('class', 'box-group');
+
+        gEnter.append('rect')
+            .attr('class', 'heat-box')
+            .attr('fill', 'white')
+            .on('click', _chart.boxOnClick());
+
+        if (_chart.renderTitle()) {
+            gEnter.append('title');
+            boxes.select('title').text(_chart.title());
+        }
+
+        dc.transition(boxes.select('rect'), _chart.transitionDuration(), _chart.transitionDelay())
+            .attr('x', function (d, i) { return cols(_chart.keyAccessor()(d, i)); })
+            .attr('y', function (d, i) { return rows(_chart.valueAccessor()(d, i)); })
+            .attr('rx', _xBorderRadius)
+            .attr('ry', _yBorderRadius)
+            .attr('fill', _chart.getColor)
+            .attr('width', boxWidth)
+            .attr('height', boxHeight);
+
+        boxes.exit().remove();
+
+        var gCols = _chartBody.select('g.cols');
+        if (gCols.empty()) {
+            gCols = _chartBody.append('g').attr('class', 'cols axis');
+        }
+        var gColsText = gCols.selectAll('text').data(cols.domain());
+        gColsText.enter().append('text')
+              .attr('x', function (d) { return cols(d) + boxWidth / 2; })
+              .style('text-anchor', 'middle')
+              .attr('y', _chart.effectiveHeight())
+              .attr('dy', 12)
+              .on('click', _chart.xAxisOnClick())
+              .text(_chart.colsLabel());
+        dc.transition(gColsText, _chart.transitionDuration(), _chart.transitionDelay())
+               .text(_chart.colsLabel())
+               .attr('x', function (d) { return cols(d) + boxWidth / 2; })
+               .attr('y', _chart.effectiveHeight());
+        gColsText.exit().remove();
+        var gRows = _chartBody.select('g.rows');
+        if (gRows.empty()) {
+            gRows = _chartBody.append('g').attr('class', 'rows axis');
+        }
+        var gRowsText = gRows.selectAll('text').data(rows.domain());
+        gRowsText.enter().append('text')
+              .attr('dy', 6)
+              .style('text-anchor', 'end')
+              .attr('x', 0)
+              .attr('dx', -2)
+              .on('click', _chart.yAxisOnClick())
+              .text(_chart.rowsLabel());
+        dc.transition(gRowsText, _chart.transitionDuration(), _chart.transitionDelay())
+              .text(_chart.rowsLabel())
+              .attr('y', function (d) { return rows(d) + boxHeight / 2; });
+        gRowsText.exit().remove();
+
+        if (_chart.hasFilter()) {
+            _chart.selectAll('g.box-group').each(function (d) {
+                if (_chart.isSelectedNode(d)) {
+                    _chart.highlightSelected(this);
+                } else {
+                    _chart.fadeDeselected(this);
+                }
+            });
+        } else {
+            _chart.selectAll('g.box-group').each(function () {
+                _chart.resetHighlight(this);
+            });
+        }
+        return _chart;
+    };
+
+    /**
+     * Gets or sets the handler that fires when an individual cell is clicked in the heatmap.
+     * By default, filtering of the cell will be toggled.
+     * @method boxOnClick
+     * @memberof dc.heatMap
+     * @instance
+     * @example
+     * // default box on click handler
+     * chart.boxOnClick(function (d) {
+     *     var filter = d.key;
+     *     dc.events.trigger(function () {
+     *         _chart.filter(filter);
+     *         _chart.redrawGroup();
+     *     });
+     * });
+     * @param  {Function} [handler]
+     * @returns {Function|dc.heatMap}
+     */
+    _chart.boxOnClick = function (handler) {
+        if (!arguments.length) {
+            return _boxOnClick;
+        }
+        _boxOnClick = handler;
+        return _chart;
+    };
+
+    /**
+     * Gets or sets the handler that fires when a column tick is clicked in the x axis.
+     * By default, if any cells in the column are unselected, the whole column will be selected,
+     * otherwise the whole column will be unselected.
+     * @method xAxisOnClick
+     * @memberof dc.heatMap
+     * @instance
+     * @param  {Function} [handler]
+     * @returns {Function|dc.heatMap}
+     */
+    _chart.xAxisOnClick = function (handler) {
+        if (!arguments.length) {
+            return _xAxisOnClick;
+        }
+        _xAxisOnClick = handler;
+        return _chart;
+    };
+
+    /**
+     * Gets or sets the handler that fires when a row tick is clicked in the y axis.
+     * By default, if any cells in the row are unselected, the whole row will be selected,
+     * otherwise the whole row will be unselected.
+     * @method yAxisOnClick
+     * @memberof dc.heatMap
+     * @instance
+     * @param  {Function} [handler]
+     * @returns {Function|dc.heatMap}
+     */
+    _chart.yAxisOnClick = function (handler) {
+        if (!arguments.length) {
+            return _yAxisOnClick;
+        }
+        _yAxisOnClick = handler;
+        return _chart;
+    };
+
+    /**
+     * Gets or sets the X border radius.  Set to 0 to get full rectangles.
+     * @method xBorderRadius
+     * @memberof dc.heatMap
+     * @instance
+     * @param  {Number} [xBorderRadius=6.75]
+     * @returns {Number|dc.heatMap}
+     */
+    _chart.xBorderRadius = function (xBorderRadius) {
+        if (!arguments.length) {
+            return _xBorderRadius;
+        }
+        _xBorderRadius = xBorderRadius;
+        return _chart;
+    };
+
+    /**
+     * Gets or sets the Y border radius.  Set to 0 to get full rectangles.
+     * @method yBorderRadius
+     * @memberof dc.heatMap
+     * @instance
+     * @param  {Number} [yBorderRadius=6.75]
+     * @returns {Number|dc.heatMap}
+     */
+    _chart.yBorderRadius = function (yBorderRadius) {
+        if (!arguments.length) {
+            return _yBorderRadius;
+        }
+        _yBorderRadius = yBorderRadius;
+        return _chart;
+    };
+
+    _chart.isSelectedNode = function (d) {
+        return _chart.hasFilter(d.key);
+    };
+
+    return _chart.anchor(parent, chartGroup);
+};
+
+// https://github.com/d3/d3-plugins/blob/master/box/box.js
+(function () {
+
+    // Inspired by http://informationandvisualization.de/blog/box-plot
+    d3.box = function () {
+        var width = 1,
+            height = 1,
+            duration = 0,
+            delay = 0,
+            domain = null,
+            value = Number,
+            whiskers = boxWhiskers,
+            quartiles = boxQuartiles,
+            tickFormat = null;
+
+        // For each small multiple…
+        function box (g) {
+            g.each(function (d, i) {
+                d = d.map(value).sort(d3.ascending);
+                var g = d3.select(this),
+                    n = d.length,
+                    min = d[0],
+                    max = d[n - 1];
+
+                // Compute quartiles. Must return exactly 3 elements.
+                var quartileData = d.quartiles = quartiles(d);
+
+                // Compute whiskers. Must return exactly 2 elements, or null.
+                var whiskerIndices = whiskers && whiskers.call(this, d, i),
+                    whiskerData = whiskerIndices && whiskerIndices.map(function (i) { return d[i]; });
+
+                // Compute outliers. If no whiskers are specified, all data are 'outliers'.
+                // We compute the outliers as indices, so that we can join across transitions!
+                var outlierIndices = whiskerIndices ?
+                    d3.range(0, whiskerIndices[0]).concat(d3.range(whiskerIndices[1] + 1, n)) : d3.range(n);
+
+                // Compute the new x-scale.
+                var x1 = d3.scale.linear()
+                    .domain(domain && domain.call(this, d, i) || [min, max])
+                    .range([height, 0]);
+
+                // Retrieve the old x-scale, if this is an update.
+                var x0 = this.__chart__ || d3.scale.linear()
+                    .domain([0, Infinity])
+                    .range(x1.range());
+
+                // Stash the new scale.
+                this.__chart__ = x1;
+
+                // Note: the box, median, and box tick elements are fixed in number,
+                // so we only have to handle enter and update. In contrast, the outliers
+                // and other elements are variable, so we need to exit them! Variable
+                // elements also fade in and out.
+
+                // Update center line: the vertical line spanning the whiskers.
+                var center = g.selectAll('line.center')
+                    .data(whiskerData ? [whiskerData] : []);
+
+                center.enter().insert('line', 'rect')
+                    .attr('class', 'center')
+                    .attr('x1', width / 2)
+                    .attr('y1', function (d) { return x0(d[0]); })
+                    .attr('x2', width / 2)
+                    .attr('y2', function (d) { return x0(d[1]); })
+                    .style('opacity', 1e-6)
+                    .transition()
+                    .duration(duration)
+                    .delay(delay)
+                    .style('opacity', 1)
+                    .attr('y1', function (d) { return x1(d[0]); })
+                    .attr('y2', function (d) { return x1(d[1]); });
+
+                center.transition()
+                    .duration(duration)
+                    .delay(delay)
+                    .style('opacity', 1)
+                    .attr('x1', width / 2)
+                    .attr('x2', width / 2)
+                    .attr('y1', function (d) { return x1(d[0]); })
+                    .attr('y2', function (d) { return x1(d[1]); });
+
+                center.exit().transition()
+                    .duration(duration)
+                    .delay(delay)
+                    .style('opacity', 1e-6)
+                    .attr('y1', function (d) { return x1(d[0]); })
+                    .attr('y2', function (d) { return x1(d[1]); })
+                    .remove();
+
+                // Update innerquartile box.
+                var box = g.selectAll('rect.box')
+                    .data([quartileData]);
+
+                box.enter().append('rect')
+                    .attr('class', 'box')
+                    .attr('x', 0)
+                    .attr('y', function (d) { return x0(d[2]); })
+                    .attr('width', width)
+                    .attr('height', function (d) { return x0(d[0]) - x0(d[2]); })
+                  .transition()
+                    .duration(duration)
+                    .delay(delay)
+                    .attr('y', function (d) { return x1(d[2]); })
+                    .attr('height', function (d) { return x1(d[0]) - x1(d[2]); });
+
+                box.transition()
+                    .duration(duration)
+                    .delay(delay)
+                    .attr('width', width)
+                    .attr('y', function (d) { return x1(d[2]); })
+                    .attr('height', function (d) { return x1(d[0]) - x1(d[2]); });
+
+                // Update median line.
+                var medianLine = g.selectAll('line.median')
+                    .data([quartileData[1]]);
+
+                medianLine.enter().append('line')
+                    .attr('class', 'median')
+                    .attr('x1', 0)
+                    .attr('y1', x0)
+                    .attr('x2', width)
+                    .attr('y2', x0)
+                    .transition()
+                    .duration(duration)
+                    .delay(delay)
+                    .attr('y1', x1)
+                    .attr('y2', x1);
+
+                medianLine.transition()
+                    .duration(duration)
+                    .delay(delay)
+                    .attr('x1', 0)
+                    .attr('x2', width)
+                    .attr('y1', x1)
+                    .attr('y2', x1);
+
+                // Update whiskers.
+                var whisker = g.selectAll('line.whisker')
+                    .data(whiskerData || []);
+
+                whisker.enter().insert('line', 'circle, text')
+                    .attr('class', 'whisker')
+                    .attr('x1', 0)
+                    .attr('y1', x0)
+                    .attr('x2', width)
+                    .attr('y2', x0)
+                    .style('opacity', 1e-6)
+                  .transition()
+                    .duration(duration)
+                    .delay(delay)
+                    .attr('y1', x1)
+                    .attr('y2', x1)
+                    .style('opacity', 1);
+
+                whisker.transition()
+                    .duration(duration)
+                    .delay(delay)
+                    .attr('x1', 0)
+                    .attr('x2', width)
+                    .attr('y1', x1)
+                    .attr('y2', x1)
+                    .style('opacity', 1);
+
+                whisker.exit().transition()
+                    .duration(duration)
+                    .delay(delay)
+                    .attr('y1', x1)
+                    .attr('y2', x1)
+                    .style('opacity', 1e-6)
+                    .remove();
+
+                // Update outliers.
+                var outlier = g.selectAll('circle.outlier')
+                    .data(outlierIndices, Number);
+
+                outlier.enter().insert('circle', 'text')
+                    .attr('class', 'outlier')
+                    .attr('r', 5)
+                    .attr('cx', width / 2)
+                    .attr('cy', function (i) { return x0(d[i]); })
+                    .style('opacity', 1e-6)
+                    .transition()
+                    .duration(duration)
+                    .delay(delay)
+                    .attr('cy', function (i) { return x1(d[i]); })
+                    .style('opacity', 1);
+
+                outlier.transition()
+                    .duration(duration)
+                    .delay(delay)
+                    .attr('cx', width / 2)
+                    .attr('cy', function (i) { return x1(d[i]); })
+                    .style('opacity', 1);
+
+                outlier.exit().transition()
+                    .duration(duration)
+                    .delay(delay)
+                    .attr('cy', function (i) { return x1(d[i]); })
+                    .style('opacity', 1e-6)
+                    .remove();
+
+                // Compute the tick format.
+                var format = tickFormat || x1.tickFormat(8);
+
+                // Update box ticks.
+                var boxTick = g.selectAll('text.box')
+                    .data(quartileData);
+
+                boxTick.enter().append('text')
+                    .attr('class', 'box')
+                    .attr('dy', '.3em')
+                    .attr('dx', function (d, i) { return i & 1 ? 6 : -6; })
+                    .attr('x', function (d, i) { return i & 1 ? width : 0; })
+                    .attr('y', x0)
+                    .attr('text-anchor', function (d, i) { return i & 1 ? 'start' : 'end'; })
+                    .text(format)
+                    .transition()
+                    .duration(duration)
+                    .delay(delay)
+                    .attr('y', x1);
+
+                boxTick.transition()
+                    .duration(duration)
+                    .delay(delay)
+                    .text(format)
+                    .attr('x', function (d, i) { return i & 1 ? width : 0; })
+                    .attr('y', x1);
+
+                // Update whisker ticks. These are handled separately from the box
+                // ticks because they may or may not exist, and we want don't want
+                // to join box ticks pre-transition with whisker ticks post-.
+                var whiskerTick = g.selectAll('text.whisker')
+                    .data(whiskerData || []);
+
+                whiskerTick.enter().append('text')
+                    .attr('class', 'whisker')
+                    .attr('dy', '.3em')
+                    .attr('dx', 6)
+                    .attr('x', width)
+                    .attr('y', x0)
+                    .text(format)
+                    .style('opacity', 1e-6)
+                    .transition()
+                    .duration(duration)
+                    .delay(delay)
+                    .attr('y', x1)
+                    .style('opacity', 1);
+
+                whiskerTick.transition()
+                    .duration(duration)
+                    .delay(delay)
+                    .text(format)
+                    .attr('x', width)
+                    .attr('y', x1)
+                    .style('opacity', 1);
+
+                whiskerTick.exit().transition()
+                    .duration(duration)
+                    .delay(delay)
+                    .attr('y', x1)
+                    .style('opacity', 1e-6)
+                    .remove();
+            });
+            d3.timer.flush();
+        }
+
+        box.width = function (x) {
+            if (!arguments.length) {
+                return width;
+            }
+            width = x;
+            return box;
+        };
+
+        box.height = function (x) {
+            if (!arguments.length) {
+                return height;
+            }
+            height = x;
+            return box;
+        };
+
+        box.tickFormat = function (x) {
+            if (!arguments.length) {
+                return tickFormat;
+            }
+            tickFormat = x;
+            return box;
+        };
+
+        box.duration = function (x) {
+            if (!arguments.length) {
+                return duration;
+            }
+            duration = x;
+            return box;
+        };
+
+        box.domain = function (x) {
+            if (!arguments.length) {
+                return domain;
+            }
+            domain = x === null ? x : d3.functor(x);
+            return box;
+        };
+
+        box.value = function (x) {
+            if (!arguments.length) {
+                return value;
+            }
+            value = x;
+            return box;
+        };
+
+        box.whiskers = function (x) {
+            if (!arguments.length) {
+                return whiskers;
+            }
+            whiskers = x;
+            return box;
+        };
+
+        box.quartiles = function (x) {
+            if (!arguments.length) {
+                return quartiles;
+            }
+            quartiles = x;
+            return box;
+        };
+
+        return box;
+    };
+
+    function boxWhiskers (d) {
+        return [0, d.length - 1];
+    }
+
+    function boxQuartiles (d) {
+        return [
+            d3.quantile(d, 0.25),
+            d3.quantile(d, 0.5),
+            d3.quantile(d, 0.75)
+        ];
+    }
+
+})();
+
+
+/**
+ * A box plot is a chart that depicts numerical data via their quartile ranges.
+ *
+ * Examples:
+ * - {@link http://dc-js.github.io/dc.js/examples/box-plot-time.html Box plot time example}
+ * - {@link http://dc-js.github.io/dc.js/examples/box-plot.html Box plot example}
+ * @class boxPlot
+ * @memberof dc
+ * @mixes dc.coordinateGridMixin
+ * @example
+ * // create a box plot under #chart-container1 element using the default global chart group
+ * var boxPlot1 = dc.boxPlot('#chart-container1');
+ * // create a box plot under #chart-container2 element using chart group A
+ * var boxPlot2 = dc.boxPlot('#chart-container2', 'chartGroupA');
+ * @param {String|node|d3.selection} parent - Any valid
+ * {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Selections.md#selecting-elements d3 single selector} specifying
+ * a dom block element such as a div; or a dom element or d3 selection.
+ * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in.
+ * Interaction with a chart will only trigger events and redraws within the chart's group.
+ * @returns {dc.boxPlot}
+ */
+dc.boxPlot = function (parent, chartGroup) {
+    var _chart = dc.coordinateGridMixin({});
+
+    // Returns a function to compute the interquartile range.
+    function DEFAULT_WHISKERS_IQR (k) {
+        return function (d) {
+            var q1 = d.quartiles[0],
+                q3 = d.quartiles[2],
+                iqr = (q3 - q1) * k,
+                i = -1,
+                j = d.length;
+            do { ++i; } while (d[i] < q1 - iqr);
+            do { --j; } while (d[j] > q3 + iqr);
+            return [i, j];
+        };
+    }
+
+    var _whiskerIqrFactor = 1.5;
+    var _whiskersIqr = DEFAULT_WHISKERS_IQR;
+    var _whiskers = _whiskersIqr(_whiskerIqrFactor);
+
+    var _box = d3.box();
+    var _tickFormat = null;
+
+    var _boxWidth = function (innerChartWidth, xUnits) {
+        if (_chart.isOrdinal()) {
+            return _chart.x().rangeBand();
+        } else {
+            return innerChartWidth / (1 + _chart.boxPadding()) / xUnits;
+        }
+    };
+
+    // default padding to handle min/max whisker text
+    _chart.yAxisPadding(12);
+
+    // default to ordinal
+    _chart.x(d3.scale.ordinal());
+    _chart.xUnits(dc.units.ordinal);
+
+    // valueAccessor should return an array of values that can be coerced into numbers
+    // or if data is overloaded for a static array of arrays, it should be `Number`.
+    // Empty arrays are not included.
+    _chart.data(function (group) {
+        return group.all().map(function (d) {
+            d.map = function (accessor) { return accessor.call(d, d); };
+            return d;
+        }).filter(function (d) {
+            var values = _chart.valueAccessor()(d);
+            return values.length !== 0;
+        });
+    });
+
+    /**
+     * Get or set the spacing between boxes as a fraction of box size. Valid values are within 0-1.
+     * See the {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Ordinal-Scales.md#ordinal_rangeBands d3 docs}
+     * for a visual description of how the padding is applied.
+     * @method boxPadding
+     * @memberof dc.boxPlot
+     * @instance
+     * @see {@link https://github.com/d3/d3-3.x-api-reference/blob/master/Ordinal-Scales.md#ordinal_rangeBands d3.scale.ordinal.rangeBands}
+     * @param {Number} [padding=0.8]
+     * @returns {Number|dc.boxPlot}
+     */
+    _chart.boxPadding = _chart._rangeBandPadding;
+    _chart.boxPadding(0.8);
+
+    /**
+     * Get or set the outer padding on an ordinal box chart. This setting has no effect on non-ordinal charts
+     * or on charts with a custom {@link dc.boxPlot#boxWidth .boxWidth}. Will pad the width by
+     * `padding * barWidth` on each side of the chart.
+     * @method outerPadding
+     * @memberof dc.boxPlot
+     * @instance
+     * @param {Number} [padding=0.5]
+     * @returns {Number|dc.boxPlot}
+     */
+    _chart.outerPadding = _chart._outerRangeBandPadding;
+    _chart.outerPadding(0.5);
+
+    /**
+     * Get or set the numerical width of the boxplot box. The width may also be a function taking as
+     * parameters the chart width excluding the right and left margins, as well as the number of x
+     * units.
+     * @example
+     * // Using numerical parameter
+     * chart.boxWidth(10);
+     * // Using function
+     * chart.boxWidth((innerChartWidth, xUnits) { ... });
+     * @method boxWidth
+     * @memberof dc.boxPlot
+     * @instance
+     * @param {Number|Function} [boxWidth=0.5]
+     * @returns {Number|Function|dc.boxPlot}
+     */
+    _chart.boxWidth = function (boxWidth) {
+        if (!arguments.length) {
+            return _boxWidth;
+        }
+        _boxWidth = d3.functor(boxWidth);
+        return _chart;
+    };
+
+    var boxTransform = function (d, i) {
+        var xOffset = _chart.x()(_chart.keyAccessor()(d, i));
+        return 'translate(' + xOffset + ', 0)';
+    };
+
+    _chart._preprocessData = function () {
+        if (_chart.elasticX()) {
+            _chart.x().domain([]);
+        }
+    };
+
+    _chart.plotData = function () {
+        var _calculatedBoxWidth = _boxWidth(_chart.effectiveWidth(), _chart.xUnitCount());
+
+        _box.whiskers(_whiskers)
+            .width(_calculatedBoxWidth)
+            .height(_chart.effectiveHeight())
+            .value(_chart.valueAccessor())
+            .domain(_chart.y().domain())
+            .duration(_chart.transitionDuration())
+            .tickFormat(_tickFormat);
+
+        var boxesG = _chart.chartBodyG().selectAll('g.box').data(_chart.data(), _chart.keyAccessor());
+
+        renderBoxes(boxesG);
+        updateBoxes(boxesG);
+        removeBoxes(boxesG);
+
+        _chart.fadeDeselectedArea();
+    };
+
+    function renderBoxes (boxesG) {
+        var boxesGEnter = boxesG.enter().append('g');
+
+        boxesGEnter
+            .attr('class', 'box')
+            .attr('transform', boxTransform)
+            .call(_box)
+            .on('click', function (d) {
+                _chart.filter(_chart.keyAccessor()(d));
+                _chart.redrawGroup();
+            });
+    }
+
+    function updateBoxes (boxesG) {
+        dc.transition(boxesG, _chart.transitionDuration(), _chart.transitionDelay())
+            .attr('transform', boxTransform)
+            .call(_box)
+            .each(function () {
+                d3.select(this).select('rect.box').attr('fill', _chart.getColor);
+            });
+    }
+
+    function removeBoxes (boxesG) {
+        boxesG.exit().remove().call(_box);
+    }
+
+    _chart.fadeDeselectedArea = function () {
+        if (_chart.hasFilter()) {
+            if (_chart.isOrdinal()) {
+                _chart.g().selectAll('g.box').each(function (d) {
+                    if (_chart.isSelectedNode(d)) {
+                        _chart.highlightSelected(this);
+                    } else {
+                        _chart.fadeDeselected(this);
+                    }
+                });
+            } else {
+                var extent = _chart.brush().extent();
+                var start = extent[0];
+                var end = extent[1];
+                var keyAccessor = _chart.keyAccessor();
+                _chart.g().selectAll('g.box').each(function (d) {
+                    var key = keyAccessor(d);
+                    if (key < start || key >= end) {
+                        _chart.fadeDeselected(this);
+                    } else {
+                        _chart.highlightSelected(this);
+                    }
+                });
+            }
+        } else {
+            _chart.g().selectAll('g.box').each(function () {
+                _chart.resetHighlight(this);
+            });
+        }
+    };
+
+    _chart.isSelectedNode = function (d) {
+        return _chart.hasFilter(_chart.keyAccessor()(d));
+    };
+
+    _chart.yAxisMin = function () {
+        var min = d3.min(_chart.data(), function (e) {
+            return d3.min(_chart.valueAccessor()(e));
+        });
+        return dc.utils.subtract(min, _chart.yAxisPadding());
+    };
+
+    _chart.yAxisMax = function () {
+        var max = d3.max(_chart.data(), function (e) {
+            return d3.max(_chart.valueAccessor()(e));
+        });
+        return dc.utils.add(max, _chart.yAxisPadding());
+    };
+
+    /**
+     * Set the numerical format of the boxplot median, whiskers and quartile labels. Defaults to
+     * integer formatting.
+     * @example
+     * // format ticks to 2 decimal places
+     * chart.tickFormat(d3.format('.2f'));
+     * @method tickFormat
+     * @memberof dc.boxPlot
+     * @instance
+     * @param {Function} [tickFormat]
+     * @returns {Number|Function|dc.boxPlot}
+     */
+    _chart.tickFormat = function (tickFormat) {
+        if (!arguments.length) {
+            return _tickFormat;
+        }
+        _tickFormat = tickFormat;
+        return _chart;
+    };
+
+    return _chart.anchor(parent, chartGroup);
+};
+
+// Renamed functions
+
+dc.abstractBubbleChart = dc.bubbleMixin;
+dc.baseChart = dc.baseMixin;
+dc.capped = dc.capMixin;
+dc.colorChart = dc.colorMixin;
+dc.coordinateGridChart = dc.coordinateGridMixin;
+dc.marginable = dc.marginMixin;
+dc.stackableChart = dc.stackMixin;
+
+// Expose d3 and crossfilter, so that clients in browserify
+// case can obtain them if they need them.
+dc.d3 = d3;
+dc.crossfilter = crossfilter;
+
+return dc;}
+    if(typeof define === "function" && define.amd) {
+        define(["d3", "crossfilter2"], _dc);
+    } else if(typeof module === "object" && module.exports) {
+        var _d3 = require('d3');
+        var _crossfilter = require('crossfilter2');
+        // When using npm + browserify, 'crossfilter' is a function,
+        // since package.json specifies index.js as main function, and it
+        // does special handling. When using bower + browserify,
+        // there's no main in bower.json (in fact, there's no bower.json),
+        // so we need to fix it.
+        if (typeof _crossfilter !== "function") {
+            _crossfilter = _crossfilter.crossfilter;
+        }
+        module.exports = _dc(_d3, _crossfilter);
+    } else {
+        this.dc = _dc(d3, crossfilter);
+    }
+}
+)();
+
+//# sourceMappingURL=dc.js.map
\ No newline at end of file
diff --git a/src/legacy/design-studio/js/jquery-ui.js b/src/legacy/design-studio/js/jquery-ui.js
new file mode 100644
index 0000000000000000000000000000000000000000..0213552372f1cddab3995c41dd751a624f9a9efc
--- /dev/null
+++ b/src/legacy/design-studio/js/jquery-ui.js
@@ -0,0 +1,18706 @@
+/*! jQuery UI - v1.12.1 - 2016-09-14
+* http://jqueryui.com
+* Includes: widget.js, position.js, data.js, disable-selection.js, effect.js, effects/effect-blind.js, effects/effect-bounce.js, effects/effect-clip.js, effects/effect-drop.js, effects/effect-explode.js, effects/effect-fade.js, effects/effect-fold.js, effects/effect-highlight.js, effects/effect-puff.js, effects/effect-pulsate.js, effects/effect-scale.js, effects/effect-shake.js, effects/effect-size.js, effects/effect-slide.js, effects/effect-transfer.js, focusable.js, form-reset-mixin.js, jquery-1-7.js, keycode.js, labels.js, scroll-parent.js, tabbable.js, unique-id.js, widgets/accordion.js, widgets/autocomplete.js, widgets/button.js, widgets/checkboxradio.js, widgets/controlgroup.js, widgets/datepicker.js, widgets/dialog.js, widgets/draggable.js, widgets/droppable.js, widgets/menu.js, widgets/mouse.js, widgets/progressbar.js, widgets/resizable.js, widgets/selectable.js, widgets/selectmenu.js, widgets/slider.js, widgets/sortable.js, widgets/spinner.js, widgets/tabs.js, widgets/tooltip.js
+* Copyright jQuery Foundation and other contributors; Licensed MIT */
+
+(function( factory ) {
+	if ( typeof define === "function" && define.amd ) {
+
+		// AMD. Register as an anonymous module.
+		define([ "jquery" ], factory );
+	} else {
+
+		// Browser globals
+		factory( jQuery );
+	}
+}(function( $ ) {
+
+$.ui = $.ui || {};
+
+var version = $.ui.version = "1.12.1";
+
+
+/*!
+ * jQuery UI Widget 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Widget
+//>>group: Core
+//>>description: Provides a factory for creating stateful widgets with a common API.
+//>>docs: http://api.jqueryui.com/jQuery.widget/
+//>>demos: http://jqueryui.com/widget/
+
+
+
+var widgetUuid = 0;
+var widgetSlice = Array.prototype.slice;
+
+$.cleanData = ( function( orig ) {
+	return function( elems ) {
+		var events, elem, i;
+		for ( i = 0; ( elem = elems[ i ] ) != null; i++ ) {
+			try {
+
+				// Only trigger remove when necessary to save time
+				events = $._data( elem, "events" );
+				if ( events && events.remove ) {
+					$( elem ).triggerHandler( "remove" );
+				}
+
+			// Http://bugs.jquery.com/ticket/8235
+			} catch ( e ) {}
+		}
+		orig( elems );
+	};
+} )( $.cleanData );
+
+$.widget = function( name, base, prototype ) {
+	var existingConstructor, constructor, basePrototype;
+
+	// ProxiedPrototype allows the provided prototype to remain unmodified
+	// so that it can be used as a mixin for multiple widgets (#8876)
+	var proxiedPrototype = {};
+
+	var namespace = name.split( "." )[ 0 ];
+	name = name.split( "." )[ 1 ];
+	var fullName = namespace + "-" + name;
+
+	if ( !prototype ) {
+		prototype = base;
+		base = $.Widget;
+	}
+
+	if ( $.isArray( prototype ) ) {
+		prototype = $.extend.apply( null, [ {} ].concat( prototype ) );
+	}
+
+	// Create selector for plugin
+	$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
+		return !!$.data( elem, fullName );
+	};
+
+	$[ namespace ] = $[ namespace ] || {};
+	existingConstructor = $[ namespace ][ name ];
+	constructor = $[ namespace ][ name ] = function( options, element ) {
+
+		// Allow instantiation without "new" keyword
+		if ( !this._createWidget ) {
+			return new constructor( options, element );
+		}
+
+		// Allow instantiation without initializing for simple inheritance
+		// must use "new" keyword (the code above always passes args)
+		if ( arguments.length ) {
+			this._createWidget( options, element );
+		}
+	};
+
+	// Extend with the existing constructor to carry over any static properties
+	$.extend( constructor, existingConstructor, {
+		version: prototype.version,
+
+		// Copy the object used to create the prototype in case we need to
+		// redefine the widget later
+		_proto: $.extend( {}, prototype ),
+
+		// Track widgets that inherit from this widget in case this widget is
+		// redefined after a widget inherits from it
+		_childConstructors: []
+	} );
+
+	basePrototype = new base();
+
+	// We need to make the options hash a property directly on the new instance
+	// otherwise we'll modify the options hash on the prototype that we're
+	// inheriting from
+	basePrototype.options = $.widget.extend( {}, basePrototype.options );
+	$.each( prototype, function( prop, value ) {
+		if ( !$.isFunction( value ) ) {
+			proxiedPrototype[ prop ] = value;
+			return;
+		}
+		proxiedPrototype[ prop ] = ( function() {
+			function _super() {
+				return base.prototype[ prop ].apply( this, arguments );
+			}
+
+			function _superApply( args ) {
+				return base.prototype[ prop ].apply( this, args );
+			}
+
+			return function() {
+				var __super = this._super;
+				var __superApply = this._superApply;
+				var returnValue;
+
+				this._super = _super;
+				this._superApply = _superApply;
+
+				returnValue = value.apply( this, arguments );
+
+				this._super = __super;
+				this._superApply = __superApply;
+
+				return returnValue;
+			};
+		} )();
+	} );
+	constructor.prototype = $.widget.extend( basePrototype, {
+
+		// TODO: remove support for widgetEventPrefix
+		// always use the name + a colon as the prefix, e.g., draggable:start
+		// don't prefix for widgets that aren't DOM-based
+		widgetEventPrefix: existingConstructor ? ( basePrototype.widgetEventPrefix || name ) : name
+	}, proxiedPrototype, {
+		constructor: constructor,
+		namespace: namespace,
+		widgetName: name,
+		widgetFullName: fullName
+	} );
+
+	// If this widget is being redefined then we need to find all widgets that
+	// are inheriting from it and redefine all of them so that they inherit from
+	// the new version of this widget. We're essentially trying to replace one
+	// level in the prototype chain.
+	if ( existingConstructor ) {
+		$.each( existingConstructor._childConstructors, function( i, child ) {
+			var childPrototype = child.prototype;
+
+			// Redefine the child widget using the same prototype that was
+			// originally used, but inherit from the new version of the base
+			$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor,
+				child._proto );
+		} );
+
+		// Remove the list of existing child constructors from the old constructor
+		// so the old child constructors can be garbage collected
+		delete existingConstructor._childConstructors;
+	} else {
+		base._childConstructors.push( constructor );
+	}
+
+	$.widget.bridge( name, constructor );
+
+	return constructor;
+};
+
+$.widget.extend = function( target ) {
+	var input = widgetSlice.call( arguments, 1 );
+	var inputIndex = 0;
+	var inputLength = input.length;
+	var key;
+	var value;
+
+	for ( ; inputIndex < inputLength; inputIndex++ ) {
+		for ( key in input[ inputIndex ] ) {
+			value = input[ inputIndex ][ key ];
+			if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
+
+				// Clone objects
+				if ( $.isPlainObject( value ) ) {
+					target[ key ] = $.isPlainObject( target[ key ] ) ?
+						$.widget.extend( {}, target[ key ], value ) :
+
+						// Don't extend strings, arrays, etc. with objects
+						$.widget.extend( {}, value );
+
+				// Copy everything else by reference
+				} else {
+					target[ key ] = value;
+				}
+			}
+		}
+	}
+	return target;
+};
+
+$.widget.bridge = function( name, object ) {
+	var fullName = object.prototype.widgetFullName || name;
+	$.fn[ name ] = function( options ) {
+		var isMethodCall = typeof options === "string";
+		var args = widgetSlice.call( arguments, 1 );
+		var returnValue = this;
+
+		if ( isMethodCall ) {
+
+			// If this is an empty collection, we need to have the instance method
+			// return undefined instead of the jQuery instance
+			if ( !this.length && options === "instance" ) {
+				returnValue = undefined;
+			} else {
+				this.each( function() {
+					var methodValue;
+					var instance = $.data( this, fullName );
+
+					if ( options === "instance" ) {
+						returnValue = instance;
+						return false;
+					}
+
+					if ( !instance ) {
+						return $.error( "cannot call methods on " + name +
+							" prior to initialization; " +
+							"attempted to call method '" + options + "'" );
+					}
+
+					if ( !$.isFunction( instance[ options ] ) || options.charAt( 0 ) === "_" ) {
+						return $.error( "no such method '" + options + "' for " + name +
+							" widget instance" );
+					}
+
+					methodValue = instance[ options ].apply( instance, args );
+
+					if ( methodValue !== instance && methodValue !== undefined ) {
+						returnValue = methodValue && methodValue.jquery ?
+							returnValue.pushStack( methodValue.get() ) :
+							methodValue;
+						return false;
+					}
+				} );
+			}
+		} else {
+
+			// Allow multiple hashes to be passed on init
+			if ( args.length ) {
+				options = $.widget.extend.apply( null, [ options ].concat( args ) );
+			}
+
+			this.each( function() {
+				var instance = $.data( this, fullName );
+				if ( instance ) {
+					instance.option( options || {} );
+					if ( instance._init ) {
+						instance._init();
+					}
+				} else {
+					$.data( this, fullName, new object( options, this ) );
+				}
+			} );
+		}
+
+		return returnValue;
+	};
+};
+
+$.Widget = function( /* options, element */ ) {};
+$.Widget._childConstructors = [];
+
+$.Widget.prototype = {
+	widgetName: "widget",
+	widgetEventPrefix: "",
+	defaultElement: "<div>",
+
+	options: {
+		classes: {},
+		disabled: false,
+
+		// Callbacks
+		create: null
+	},
+
+	_createWidget: function( options, element ) {
+		element = $( element || this.defaultElement || this )[ 0 ];
+		this.element = $( element );
+		this.uuid = widgetUuid++;
+		this.eventNamespace = "." + this.widgetName + this.uuid;
+
+		this.bindings = $();
+		this.hoverable = $();
+		this.focusable = $();
+		this.classesElementLookup = {};
+
+		if ( element !== this ) {
+			$.data( element, this.widgetFullName, this );
+			this._on( true, this.element, {
+				remove: function( event ) {
+					if ( event.target === element ) {
+						this.destroy();
+					}
+				}
+			} );
+			this.document = $( element.style ?
+
+				// Element within the document
+				element.ownerDocument :
+
+				// Element is window or document
+				element.document || element );
+			this.window = $( this.document[ 0 ].defaultView || this.document[ 0 ].parentWindow );
+		}
+
+		this.options = $.widget.extend( {},
+			this.options,
+			this._getCreateOptions(),
+			options );
+
+		this._create();
+
+		if ( this.options.disabled ) {
+			this._setOptionDisabled( this.options.disabled );
+		}
+
+		this._trigger( "create", null, this._getCreateEventData() );
+		this._init();
+	},
+
+	_getCreateOptions: function() {
+		return {};
+	},
+
+	_getCreateEventData: $.noop,
+
+	_create: $.noop,
+
+	_init: $.noop,
+
+	destroy: function() {
+		var that = this;
+
+		this._destroy();
+		$.each( this.classesElementLookup, function( key, value ) {
+			that._removeClass( value, key );
+		} );
+
+		// We can probably remove the unbind calls in 2.0
+		// all event bindings should go through this._on()
+		this.element
+			.off( this.eventNamespace )
+			.removeData( this.widgetFullName );
+		this.widget()
+			.off( this.eventNamespace )
+			.removeAttr( "aria-disabled" );
+
+		// Clean up events and states
+		this.bindings.off( this.eventNamespace );
+	},
+
+	_destroy: $.noop,
+
+	widget: function() {
+		return this.element;
+	},
+
+	option: function( key, value ) {
+		var options = key;
+		var parts;
+		var curOption;
+		var i;
+
+		if ( arguments.length === 0 ) {
+
+			// Don't return a reference to the internal hash
+			return $.widget.extend( {}, this.options );
+		}
+
+		if ( typeof key === "string" ) {
+
+			// Handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
+			options = {};
+			parts = key.split( "." );
+			key = parts.shift();
+			if ( parts.length ) {
+				curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
+				for ( i = 0; i < parts.length - 1; i++ ) {
+					curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
+					curOption = curOption[ parts[ i ] ];
+				}
+				key = parts.pop();
+				if ( arguments.length === 1 ) {
+					return curOption[ key ] === undefined ? null : curOption[ key ];
+				}
+				curOption[ key ] = value;
+			} else {
+				if ( arguments.length === 1 ) {
+					return this.options[ key ] === undefined ? null : this.options[ key ];
+				}
+				options[ key ] = value;
+			}
+		}
+
+		this._setOptions( options );
+
+		return this;
+	},
+
+	_setOptions: function( options ) {
+		var key;
+
+		for ( key in options ) {
+			this._setOption( key, options[ key ] );
+		}
+
+		return this;
+	},
+
+	_setOption: function( key, value ) {
+		if ( key === "classes" ) {
+			this._setOptionClasses( value );
+		}
+
+		this.options[ key ] = value;
+
+		if ( key === "disabled" ) {
+			this._setOptionDisabled( value );
+		}
+
+		return this;
+	},
+
+	_setOptionClasses: function( value ) {
+		var classKey, elements, currentElements;
+
+		for ( classKey in value ) {
+			currentElements = this.classesElementLookup[ classKey ];
+			if ( value[ classKey ] === this.options.classes[ classKey ] ||
+					!currentElements ||
+					!currentElements.length ) {
+				continue;
+			}
+
+			// We are doing this to create a new jQuery object because the _removeClass() call
+			// on the next line is going to destroy the reference to the current elements being
+			// tracked. We need to save a copy of this collection so that we can add the new classes
+			// below.
+			elements = $( currentElements.get() );
+			this._removeClass( currentElements, classKey );
+
+			// We don't use _addClass() here, because that uses this.options.classes
+			// for generating the string of classes. We want to use the value passed in from
+			// _setOption(), this is the new value of the classes option which was passed to
+			// _setOption(). We pass this value directly to _classes().
+			elements.addClass( this._classes( {
+				element: elements,
+				keys: classKey,
+				classes: value,
+				add: true
+			} ) );
+		}
+	},
+
+	_setOptionDisabled: function( value ) {
+		this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null, !!value );
+
+		// If the widget is becoming disabled, then nothing is interactive
+		if ( value ) {
+			this._removeClass( this.hoverable, null, "ui-state-hover" );
+			this._removeClass( this.focusable, null, "ui-state-focus" );
+		}
+	},
+
+	enable: function() {
+		return this._setOptions( { disabled: false } );
+	},
+
+	disable: function() {
+		return this._setOptions( { disabled: true } );
+	},
+
+	_classes: function( options ) {
+		var full = [];
+		var that = this;
+
+		options = $.extend( {
+			element: this.element,
+			classes: this.options.classes || {}
+		}, options );
+
+		function processClassString( classes, checkOption ) {
+			var current, i;
+			for ( i = 0; i < classes.length; i++ ) {
+				current = that.classesElementLookup[ classes[ i ] ] || $();
+				if ( options.add ) {
+					current = $( $.unique( current.get().concat( options.element.get() ) ) );
+				} else {
+					current = $( current.not( options.element ).get() );
+				}
+				that.classesElementLookup[ classes[ i ] ] = current;
+				full.push( classes[ i ] );
+				if ( checkOption && options.classes[ classes[ i ] ] ) {
+					full.push( options.classes[ classes[ i ] ] );
+				}
+			}
+		}
+
+		this._on( options.element, {
+			"remove": "_untrackClassesElement"
+		} );
+
+		if ( options.keys ) {
+			processClassString( options.keys.match( /\S+/g ) || [], true );
+		}
+		if ( options.extra ) {
+			processClassString( options.extra.match( /\S+/g ) || [] );
+		}
+
+		return full.join( " " );
+	},
+
+	_untrackClassesElement: function( event ) {
+		var that = this;
+		$.each( that.classesElementLookup, function( key, value ) {
+			if ( $.inArray( event.target, value ) !== -1 ) {
+				that.classesElementLookup[ key ] = $( value.not( event.target ).get() );
+			}
+		} );
+	},
+
+	_removeClass: function( element, keys, extra ) {
+		return this._toggleClass( element, keys, extra, false );
+	},
+
+	_addClass: function( element, keys, extra ) {
+		return this._toggleClass( element, keys, extra, true );
+	},
+
+	_toggleClass: function( element, keys, extra, add ) {
+		add = ( typeof add === "boolean" ) ? add : extra;
+		var shift = ( typeof element === "string" || element === null ),
+			options = {
+				extra: shift ? keys : extra,
+				keys: shift ? element : keys,
+				element: shift ? this.element : element,
+				add: add
+			};
+		options.element.toggleClass( this._classes( options ), add );
+		return this;
+	},
+
+	_on: function( suppressDisabledCheck, element, handlers ) {
+		var delegateElement;
+		var instance = this;
+
+		// No suppressDisabledCheck flag, shuffle arguments
+		if ( typeof suppressDisabledCheck !== "boolean" ) {
+			handlers = element;
+			element = suppressDisabledCheck;
+			suppressDisabledCheck = false;
+		}
+
+		// No element argument, shuffle and use this.element
+		if ( !handlers ) {
+			handlers = element;
+			element = this.element;
+			delegateElement = this.widget();
+		} else {
+			element = delegateElement = $( element );
+			this.bindings = this.bindings.add( element );
+		}
+
+		$.each( handlers, function( event, handler ) {
+			function handlerProxy() {
+
+				// Allow widgets to customize the disabled handling
+				// - disabled as an array instead of boolean
+				// - disabled class as method for disabling individual parts
+				if ( !suppressDisabledCheck &&
+						( instance.options.disabled === true ||
+						$( this ).hasClass( "ui-state-disabled" ) ) ) {
+					return;
+				}
+				return ( typeof handler === "string" ? instance[ handler ] : handler )
+					.apply( instance, arguments );
+			}
+
+			// Copy the guid so direct unbinding works
+			if ( typeof handler !== "string" ) {
+				handlerProxy.guid = handler.guid =
+					handler.guid || handlerProxy.guid || $.guid++;
+			}
+
+			var match = event.match( /^([\w:-]*)\s*(.*)$/ );
+			var eventName = match[ 1 ] + instance.eventNamespace;
+			var selector = match[ 2 ];
+
+			if ( selector ) {
+				delegateElement.on( eventName, selector, handlerProxy );
+			} else {
+				element.on( eventName, handlerProxy );
+			}
+		} );
+	},
+
+	_off: function( element, eventName ) {
+		eventName = ( eventName || "" ).split( " " ).join( this.eventNamespace + " " ) +
+			this.eventNamespace;
+		element.off( eventName ).off( eventName );
+
+		// Clear the stack to avoid memory leaks (#10056)
+		this.bindings = $( this.bindings.not( element ).get() );
+		this.focusable = $( this.focusable.not( element ).get() );
+		this.hoverable = $( this.hoverable.not( element ).get() );
+	},
+
+	_delay: function( handler, delay ) {
+		function handlerProxy() {
+			return ( typeof handler === "string" ? instance[ handler ] : handler )
+				.apply( instance, arguments );
+		}
+		var instance = this;
+		return setTimeout( handlerProxy, delay || 0 );
+	},
+
+	_hoverable: function( element ) {
+		this.hoverable = this.hoverable.add( element );
+		this._on( element, {
+			mouseenter: function( event ) {
+				this._addClass( $( event.currentTarget ), null, "ui-state-hover" );
+			},
+			mouseleave: function( event ) {
+				this._removeClass( $( event.currentTarget ), null, "ui-state-hover" );
+			}
+		} );
+	},
+
+	_focusable: function( element ) {
+		this.focusable = this.focusable.add( element );
+		this._on( element, {
+			focusin: function( event ) {
+				this._addClass( $( event.currentTarget ), null, "ui-state-focus" );
+			},
+			focusout: function( event ) {
+				this._removeClass( $( event.currentTarget ), null, "ui-state-focus" );
+			}
+		} );
+	},
+
+	_trigger: function( type, event, data ) {
+		var prop, orig;
+		var callback = this.options[ type ];
+
+		data = data || {};
+		event = $.Event( event );
+		event.type = ( type === this.widgetEventPrefix ?
+			type :
+			this.widgetEventPrefix + type ).toLowerCase();
+
+		// The original event may come from any element
+		// so we need to reset the target on the new event
+		event.target = this.element[ 0 ];
+
+		// Copy original event properties over to the new event
+		orig = event.originalEvent;
+		if ( orig ) {
+			for ( prop in orig ) {
+				if ( !( prop in event ) ) {
+					event[ prop ] = orig[ prop ];
+				}
+			}
+		}
+
+		this.element.trigger( event, data );
+		return !( $.isFunction( callback ) &&
+			callback.apply( this.element[ 0 ], [ event ].concat( data ) ) === false ||
+			event.isDefaultPrevented() );
+	}
+};
+
+$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
+	$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
+		if ( typeof options === "string" ) {
+			options = { effect: options };
+		}
+
+		var hasOptions;
+		var effectName = !options ?
+			method :
+			options === true || typeof options === "number" ?
+				defaultEffect :
+				options.effect || defaultEffect;
+
+		options = options || {};
+		if ( typeof options === "number" ) {
+			options = { duration: options };
+		}
+
+		hasOptions = !$.isEmptyObject( options );
+		options.complete = callback;
+
+		if ( options.delay ) {
+			element.delay( options.delay );
+		}
+
+		if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
+			element[ method ]( options );
+		} else if ( effectName !== method && element[ effectName ] ) {
+			element[ effectName ]( options.duration, options.easing, callback );
+		} else {
+			element.queue( function( next ) {
+				$( this )[ method ]();
+				if ( callback ) {
+					callback.call( element[ 0 ] );
+				}
+				next();
+			} );
+		}
+	};
+} );
+
+var widget = $.widget;
+
+
+/*!
+ * jQuery UI Position 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * http://api.jqueryui.com/position/
+ */
+
+//>>label: Position
+//>>group: Core
+//>>description: Positions elements relative to other elements.
+//>>docs: http://api.jqueryui.com/position/
+//>>demos: http://jqueryui.com/position/
+
+
+( function() {
+var cachedScrollbarWidth,
+	max = Math.max,
+	abs = Math.abs,
+	rhorizontal = /left|center|right/,
+	rvertical = /top|center|bottom/,
+	roffset = /[\+\-]\d+(\.[\d]+)?%?/,
+	rposition = /^\w+/,
+	rpercent = /%$/,
+	_position = $.fn.position;
+
+function getOffsets( offsets, width, height ) {
+	return [
+		parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
+		parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
+	];
+}
+
+function parseCss( element, property ) {
+	return parseInt( $.css( element, property ), 10 ) || 0;
+}
+
+function getDimensions( elem ) {
+	var raw = elem[ 0 ];
+	if ( raw.nodeType === 9 ) {
+		return {
+			width: elem.width(),
+			height: elem.height(),
+			offset: { top: 0, left: 0 }
+		};
+	}
+	if ( $.isWindow( raw ) ) {
+		return {
+			width: elem.width(),
+			height: elem.height(),
+			offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
+		};
+	}
+	if ( raw.preventDefault ) {
+		return {
+			width: 0,
+			height: 0,
+			offset: { top: raw.pageY, left: raw.pageX }
+		};
+	}
+	return {
+		width: elem.outerWidth(),
+		height: elem.outerHeight(),
+		offset: elem.offset()
+	};
+}
+
+$.position = {
+	scrollbarWidth: function() {
+		if ( cachedScrollbarWidth !== undefined ) {
+			return cachedScrollbarWidth;
+		}
+		var w1, w2,
+			div = $( "<div " +
+				"style='display:block;position:absolute;width:50px;height:50px;overflow:hidden;'>" +
+				"<div style='height:100px;width:auto;'></div></div>" ),
+			innerDiv = div.children()[ 0 ];
+
+		$( "body" ).append( div );
+		w1 = innerDiv.offsetWidth;
+		div.css( "overflow", "scroll" );
+
+		w2 = innerDiv.offsetWidth;
+
+		if ( w1 === w2 ) {
+			w2 = div[ 0 ].clientWidth;
+		}
+
+		div.remove();
+
+		return ( cachedScrollbarWidth = w1 - w2 );
+	},
+	getScrollInfo: function( within ) {
+		var overflowX = within.isWindow || within.isDocument ? "" :
+				within.element.css( "overflow-x" ),
+			overflowY = within.isWindow || within.isDocument ? "" :
+				within.element.css( "overflow-y" ),
+			hasOverflowX = overflowX === "scroll" ||
+				( overflowX === "auto" && within.width < within.element[ 0 ].scrollWidth ),
+			hasOverflowY = overflowY === "scroll" ||
+				( overflowY === "auto" && within.height < within.element[ 0 ].scrollHeight );
+		return {
+			width: hasOverflowY ? $.position.scrollbarWidth() : 0,
+			height: hasOverflowX ? $.position.scrollbarWidth() : 0
+		};
+	},
+	getWithinInfo: function( element ) {
+		var withinElement = $( element || window ),
+			isWindow = $.isWindow( withinElement[ 0 ] ),
+			isDocument = !!withinElement[ 0 ] && withinElement[ 0 ].nodeType === 9,
+			hasOffset = !isWindow && !isDocument;
+		return {
+			element: withinElement,
+			isWindow: isWindow,
+			isDocument: isDocument,
+			offset: hasOffset ? $( element ).offset() : { left: 0, top: 0 },
+			scrollLeft: withinElement.scrollLeft(),
+			scrollTop: withinElement.scrollTop(),
+			width: withinElement.outerWidth(),
+			height: withinElement.outerHeight()
+		};
+	}
+};
+
+$.fn.position = function( options ) {
+	if ( !options || !options.of ) {
+		return _position.apply( this, arguments );
+	}
+
+	// Make a copy, we don't want to modify arguments
+	options = $.extend( {}, options );
+
+	var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
+		target = $( options.of ),
+		within = $.position.getWithinInfo( options.within ),
+		scrollInfo = $.position.getScrollInfo( within ),
+		collision = ( options.collision || "flip" ).split( " " ),
+		offsets = {};
+
+	dimensions = getDimensions( target );
+	if ( target[ 0 ].preventDefault ) {
+
+		// Force left top to allow flipping
+		options.at = "left top";
+	}
+	targetWidth = dimensions.width;
+	targetHeight = dimensions.height;
+	targetOffset = dimensions.offset;
+
+	// Clone to reuse original targetOffset later
+	basePosition = $.extend( {}, targetOffset );
+
+	// Force my and at to have valid horizontal and vertical positions
+	// if a value is missing or invalid, it will be converted to center
+	$.each( [ "my", "at" ], function() {
+		var pos = ( options[ this ] || "" ).split( " " ),
+			horizontalOffset,
+			verticalOffset;
+
+		if ( pos.length === 1 ) {
+			pos = rhorizontal.test( pos[ 0 ] ) ?
+				pos.concat( [ "center" ] ) :
+				rvertical.test( pos[ 0 ] ) ?
+					[ "center" ].concat( pos ) :
+					[ "center", "center" ];
+		}
+		pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
+		pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
+
+		// Calculate offsets
+		horizontalOffset = roffset.exec( pos[ 0 ] );
+		verticalOffset = roffset.exec( pos[ 1 ] );
+		offsets[ this ] = [
+			horizontalOffset ? horizontalOffset[ 0 ] : 0,
+			verticalOffset ? verticalOffset[ 0 ] : 0
+		];
+
+		// Reduce to just the positions without the offsets
+		options[ this ] = [
+			rposition.exec( pos[ 0 ] )[ 0 ],
+			rposition.exec( pos[ 1 ] )[ 0 ]
+		];
+	} );
+
+	// Normalize collision option
+	if ( collision.length === 1 ) {
+		collision[ 1 ] = collision[ 0 ];
+	}
+
+	if ( options.at[ 0 ] === "right" ) {
+		basePosition.left += targetWidth;
+	} else if ( options.at[ 0 ] === "center" ) {
+		basePosition.left += targetWidth / 2;
+	}
+
+	if ( options.at[ 1 ] === "bottom" ) {
+		basePosition.top += targetHeight;
+	} else if ( options.at[ 1 ] === "center" ) {
+		basePosition.top += targetHeight / 2;
+	}
+
+	atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
+	basePosition.left += atOffset[ 0 ];
+	basePosition.top += atOffset[ 1 ];
+
+	return this.each( function() {
+		var collisionPosition, using,
+			elem = $( this ),
+			elemWidth = elem.outerWidth(),
+			elemHeight = elem.outerHeight(),
+			marginLeft = parseCss( this, "marginLeft" ),
+			marginTop = parseCss( this, "marginTop" ),
+			collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) +
+				scrollInfo.width,
+			collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) +
+				scrollInfo.height,
+			position = $.extend( {}, basePosition ),
+			myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
+
+		if ( options.my[ 0 ] === "right" ) {
+			position.left -= elemWidth;
+		} else if ( options.my[ 0 ] === "center" ) {
+			position.left -= elemWidth / 2;
+		}
+
+		if ( options.my[ 1 ] === "bottom" ) {
+			position.top -= elemHeight;
+		} else if ( options.my[ 1 ] === "center" ) {
+			position.top -= elemHeight / 2;
+		}
+
+		position.left += myOffset[ 0 ];
+		position.top += myOffset[ 1 ];
+
+		collisionPosition = {
+			marginLeft: marginLeft,
+			marginTop: marginTop
+		};
+
+		$.each( [ "left", "top" ], function( i, dir ) {
+			if ( $.ui.position[ collision[ i ] ] ) {
+				$.ui.position[ collision[ i ] ][ dir ]( position, {
+					targetWidth: targetWidth,
+					targetHeight: targetHeight,
+					elemWidth: elemWidth,
+					elemHeight: elemHeight,
+					collisionPosition: collisionPosition,
+					collisionWidth: collisionWidth,
+					collisionHeight: collisionHeight,
+					offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
+					my: options.my,
+					at: options.at,
+					within: within,
+					elem: elem
+				} );
+			}
+		} );
+
+		if ( options.using ) {
+
+			// Adds feedback as second argument to using callback, if present
+			using = function( props ) {
+				var left = targetOffset.left - position.left,
+					right = left + targetWidth - elemWidth,
+					top = targetOffset.top - position.top,
+					bottom = top + targetHeight - elemHeight,
+					feedback = {
+						target: {
+							element: target,
+							left: targetOffset.left,
+							top: targetOffset.top,
+							width: targetWidth,
+							height: targetHeight
+						},
+						element: {
+							element: elem,
+							left: position.left,
+							top: position.top,
+							width: elemWidth,
+							height: elemHeight
+						},
+						horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
+						vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
+					};
+				if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
+					feedback.horizontal = "center";
+				}
+				if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
+					feedback.vertical = "middle";
+				}
+				if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
+					feedback.important = "horizontal";
+				} else {
+					feedback.important = "vertical";
+				}
+				options.using.call( this, props, feedback );
+			};
+		}
+
+		elem.offset( $.extend( position, { using: using } ) );
+	} );
+};
+
+$.ui.position = {
+	fit: {
+		left: function( position, data ) {
+			var within = data.within,
+				withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
+				outerWidth = within.width,
+				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
+				overLeft = withinOffset - collisionPosLeft,
+				overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
+				newOverRight;
+
+			// Element is wider than within
+			if ( data.collisionWidth > outerWidth ) {
+
+				// Element is initially over the left side of within
+				if ( overLeft > 0 && overRight <= 0 ) {
+					newOverRight = position.left + overLeft + data.collisionWidth - outerWidth -
+						withinOffset;
+					position.left += overLeft - newOverRight;
+
+				// Element is initially over right side of within
+				} else if ( overRight > 0 && overLeft <= 0 ) {
+					position.left = withinOffset;
+
+				// Element is initially over both left and right sides of within
+				} else {
+					if ( overLeft > overRight ) {
+						position.left = withinOffset + outerWidth - data.collisionWidth;
+					} else {
+						position.left = withinOffset;
+					}
+				}
+
+			// Too far left -> align with left edge
+			} else if ( overLeft > 0 ) {
+				position.left += overLeft;
+
+			// Too far right -> align with right edge
+			} else if ( overRight > 0 ) {
+				position.left -= overRight;
+
+			// Adjust based on position and margin
+			} else {
+				position.left = max( position.left - collisionPosLeft, position.left );
+			}
+		},
+		top: function( position, data ) {
+			var within = data.within,
+				withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
+				outerHeight = data.within.height,
+				collisionPosTop = position.top - data.collisionPosition.marginTop,
+				overTop = withinOffset - collisionPosTop,
+				overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
+				newOverBottom;
+
+			// Element is taller than within
+			if ( data.collisionHeight > outerHeight ) {
+
+				// Element is initially over the top of within
+				if ( overTop > 0 && overBottom <= 0 ) {
+					newOverBottom = position.top + overTop + data.collisionHeight - outerHeight -
+						withinOffset;
+					position.top += overTop - newOverBottom;
+
+				// Element is initially over bottom of within
+				} else if ( overBottom > 0 && overTop <= 0 ) {
+					position.top = withinOffset;
+
+				// Element is initially over both top and bottom of within
+				} else {
+					if ( overTop > overBottom ) {
+						position.top = withinOffset + outerHeight - data.collisionHeight;
+					} else {
+						position.top = withinOffset;
+					}
+				}
+
+			// Too far up -> align with top
+			} else if ( overTop > 0 ) {
+				position.top += overTop;
+
+			// Too far down -> align with bottom edge
+			} else if ( overBottom > 0 ) {
+				position.top -= overBottom;
+
+			// Adjust based on position and margin
+			} else {
+				position.top = max( position.top - collisionPosTop, position.top );
+			}
+		}
+	},
+	flip: {
+		left: function( position, data ) {
+			var within = data.within,
+				withinOffset = within.offset.left + within.scrollLeft,
+				outerWidth = within.width,
+				offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
+				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
+				overLeft = collisionPosLeft - offsetLeft,
+				overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
+				myOffset = data.my[ 0 ] === "left" ?
+					-data.elemWidth :
+					data.my[ 0 ] === "right" ?
+						data.elemWidth :
+						0,
+				atOffset = data.at[ 0 ] === "left" ?
+					data.targetWidth :
+					data.at[ 0 ] === "right" ?
+						-data.targetWidth :
+						0,
+				offset = -2 * data.offset[ 0 ],
+				newOverRight,
+				newOverLeft;
+
+			if ( overLeft < 0 ) {
+				newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth -
+					outerWidth - withinOffset;
+				if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
+					position.left += myOffset + atOffset + offset;
+				}
+			} else if ( overRight > 0 ) {
+				newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset +
+					atOffset + offset - offsetLeft;
+				if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
+					position.left += myOffset + atOffset + offset;
+				}
+			}
+		},
+		top: function( position, data ) {
+			var within = data.within,
+				withinOffset = within.offset.top + within.scrollTop,
+				outerHeight = within.height,
+				offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
+				collisionPosTop = position.top - data.collisionPosition.marginTop,
+				overTop = collisionPosTop - offsetTop,
+				overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
+				top = data.my[ 1 ] === "top",
+				myOffset = top ?
+					-data.elemHeight :
+					data.my[ 1 ] === "bottom" ?
+						data.elemHeight :
+						0,
+				atOffset = data.at[ 1 ] === "top" ?
+					data.targetHeight :
+					data.at[ 1 ] === "bottom" ?
+						-data.targetHeight :
+						0,
+				offset = -2 * data.offset[ 1 ],
+				newOverTop,
+				newOverBottom;
+			if ( overTop < 0 ) {
+				newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight -
+					outerHeight - withinOffset;
+				if ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) {
+					position.top += myOffset + atOffset + offset;
+				}
+			} else if ( overBottom > 0 ) {
+				newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset +
+					offset - offsetTop;
+				if ( newOverTop > 0 || abs( newOverTop ) < overBottom ) {
+					position.top += myOffset + atOffset + offset;
+				}
+			}
+		}
+	},
+	flipfit: {
+		left: function() {
+			$.ui.position.flip.left.apply( this, arguments );
+			$.ui.position.fit.left.apply( this, arguments );
+		},
+		top: function() {
+			$.ui.position.flip.top.apply( this, arguments );
+			$.ui.position.fit.top.apply( this, arguments );
+		}
+	}
+};
+
+} )();
+
+var position = $.ui.position;
+
+
+/*!
+ * jQuery UI :data 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: :data Selector
+//>>group: Core
+//>>description: Selects elements which have data stored under the specified key.
+//>>docs: http://api.jqueryui.com/data-selector/
+
+
+var data = $.extend( $.expr[ ":" ], {
+	data: $.expr.createPseudo ?
+		$.expr.createPseudo( function( dataName ) {
+			return function( elem ) {
+				return !!$.data( elem, dataName );
+			};
+		} ) :
+
+		// Support: jQuery <1.8
+		function( elem, i, match ) {
+			return !!$.data( elem, match[ 3 ] );
+		}
+} );
+
+/*!
+ * jQuery UI Disable Selection 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: disableSelection
+//>>group: Core
+//>>description: Disable selection of text content within the set of matched elements.
+//>>docs: http://api.jqueryui.com/disableSelection/
+
+// This file is deprecated
+
+
+var disableSelection = $.fn.extend( {
+	disableSelection: ( function() {
+		var eventType = "onselectstart" in document.createElement( "div" ) ?
+			"selectstart" :
+			"mousedown";
+
+		return function() {
+			return this.on( eventType + ".ui-disableSelection", function( event ) {
+				event.preventDefault();
+			} );
+		};
+	} )(),
+
+	enableSelection: function() {
+		return this.off( ".ui-disableSelection" );
+	}
+} );
+
+
+/*!
+ * jQuery UI Effects 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Effects Core
+//>>group: Effects
+// jscs:disable maximumLineLength
+//>>description: Extends the internal jQuery effects. Includes morphing and easing. Required by all other effects.
+// jscs:enable maximumLineLength
+//>>docs: http://api.jqueryui.com/category/effects-core/
+//>>demos: http://jqueryui.com/effect/
+
+
+
+var dataSpace = "ui-effects-",
+	dataSpaceStyle = "ui-effects-style",
+	dataSpaceAnimated = "ui-effects-animated",
+
+	// Create a local jQuery because jQuery Color relies on it and the
+	// global may not exist with AMD and a custom build (#10199)
+	jQuery = $;
+
+$.effects = {
+	effect: {}
+};
+
+/*!
+ * jQuery Color Animations v2.1.2
+ * https://github.com/jquery/jquery-color
+ *
+ * Copyright 2014 jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ * Date: Wed Jan 16 08:47:09 2013 -0600
+ */
+( function( jQuery, undefined ) {
+
+	var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor " +
+		"borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",
+
+	// Plusequals test for += 100 -= 100
+	rplusequals = /^([\-+])=\s*(\d+\.?\d*)/,
+
+	// A set of RE's that can match strings and generate color tuples.
+	stringParsers = [ {
+			re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
+			parse: function( execResult ) {
+				return [
+					execResult[ 1 ],
+					execResult[ 2 ],
+					execResult[ 3 ],
+					execResult[ 4 ]
+				];
+			}
+		}, {
+			re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
+			parse: function( execResult ) {
+				return [
+					execResult[ 1 ] * 2.55,
+					execResult[ 2 ] * 2.55,
+					execResult[ 3 ] * 2.55,
+					execResult[ 4 ]
+				];
+			}
+		}, {
+
+			// This regex ignores A-F because it's compared against an already lowercased string
+			re: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,
+			parse: function( execResult ) {
+				return [
+					parseInt( execResult[ 1 ], 16 ),
+					parseInt( execResult[ 2 ], 16 ),
+					parseInt( execResult[ 3 ], 16 )
+				];
+			}
+		}, {
+
+			// This regex ignores A-F because it's compared against an already lowercased string
+			re: /#([a-f0-9])([a-f0-9])([a-f0-9])/,
+			parse: function( execResult ) {
+				return [
+					parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ),
+					parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ),
+					parseInt( execResult[ 3 ] + execResult[ 3 ], 16 )
+				];
+			}
+		}, {
+			re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
+			space: "hsla",
+			parse: function( execResult ) {
+				return [
+					execResult[ 1 ],
+					execResult[ 2 ] / 100,
+					execResult[ 3 ] / 100,
+					execResult[ 4 ]
+				];
+			}
+		} ],
+
+	// JQuery.Color( )
+	color = jQuery.Color = function( color, green, blue, alpha ) {
+		return new jQuery.Color.fn.parse( color, green, blue, alpha );
+	},
+	spaces = {
+		rgba: {
+			props: {
+				red: {
+					idx: 0,
+					type: "byte"
+				},
+				green: {
+					idx: 1,
+					type: "byte"
+				},
+				blue: {
+					idx: 2,
+					type: "byte"
+				}
+			}
+		},
+
+		hsla: {
+			props: {
+				hue: {
+					idx: 0,
+					type: "degrees"
+				},
+				saturation: {
+					idx: 1,
+					type: "percent"
+				},
+				lightness: {
+					idx: 2,
+					type: "percent"
+				}
+			}
+		}
+	},
+	propTypes = {
+		"byte": {
+			floor: true,
+			max: 255
+		},
+		"percent": {
+			max: 1
+		},
+		"degrees": {
+			mod: 360,
+			floor: true
+		}
+	},
+	support = color.support = {},
+
+	// Element for support tests
+	supportElem = jQuery( "<p>" )[ 0 ],
+
+	// Colors = jQuery.Color.names
+	colors,
+
+	// Local aliases of functions called often
+	each = jQuery.each;
+
+// Determine rgba support immediately
+supportElem.style.cssText = "background-color:rgba(1,1,1,.5)";
+support.rgba = supportElem.style.backgroundColor.indexOf( "rgba" ) > -1;
+
+// Define cache name and alpha properties
+// for rgba and hsla spaces
+each( spaces, function( spaceName, space ) {
+	space.cache = "_" + spaceName;
+	space.props.alpha = {
+		idx: 3,
+		type: "percent",
+		def: 1
+	};
+} );
+
+function clamp( value, prop, allowEmpty ) {
+	var type = propTypes[ prop.type ] || {};
+
+	if ( value == null ) {
+		return ( allowEmpty || !prop.def ) ? null : prop.def;
+	}
+
+	// ~~ is an short way of doing floor for positive numbers
+	value = type.floor ? ~~value : parseFloat( value );
+
+	// IE will pass in empty strings as value for alpha,
+	// which will hit this case
+	if ( isNaN( value ) ) {
+		return prop.def;
+	}
+
+	if ( type.mod ) {
+
+		// We add mod before modding to make sure that negatives values
+		// get converted properly: -10 -> 350
+		return ( value + type.mod ) % type.mod;
+	}
+
+	// For now all property types without mod have min and max
+	return 0 > value ? 0 : type.max < value ? type.max : value;
+}
+
+function stringParse( string ) {
+	var inst = color(),
+		rgba = inst._rgba = [];
+
+	string = string.toLowerCase();
+
+	each( stringParsers, function( i, parser ) {
+		var parsed,
+			match = parser.re.exec( string ),
+			values = match && parser.parse( match ),
+			spaceName = parser.space || "rgba";
+
+		if ( values ) {
+			parsed = inst[ spaceName ]( values );
+
+			// If this was an rgba parse the assignment might happen twice
+			// oh well....
+			inst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ];
+			rgba = inst._rgba = parsed._rgba;
+
+			// Exit each( stringParsers ) here because we matched
+			return false;
+		}
+	} );
+
+	// Found a stringParser that handled it
+	if ( rgba.length ) {
+
+		// If this came from a parsed string, force "transparent" when alpha is 0
+		// chrome, (and maybe others) return "transparent" as rgba(0,0,0,0)
+		if ( rgba.join() === "0,0,0,0" ) {
+			jQuery.extend( rgba, colors.transparent );
+		}
+		return inst;
+	}
+
+	// Named colors
+	return colors[ string ];
+}
+
+color.fn = jQuery.extend( color.prototype, {
+	parse: function( red, green, blue, alpha ) {
+		if ( red === undefined ) {
+			this._rgba = [ null, null, null, null ];
+			return this;
+		}
+		if ( red.jquery || red.nodeType ) {
+			red = jQuery( red ).css( green );
+			green = undefined;
+		}
+
+		var inst = this,
+			type = jQuery.type( red ),
+			rgba = this._rgba = [];
+
+		// More than 1 argument specified - assume ( red, green, blue, alpha )
+		if ( green !== undefined ) {
+			red = [ red, green, blue, alpha ];
+			type = "array";
+		}
+
+		if ( type === "string" ) {
+			return this.parse( stringParse( red ) || colors._default );
+		}
+
+		if ( type === "array" ) {
+			each( spaces.rgba.props, function( key, prop ) {
+				rgba[ prop.idx ] = clamp( red[ prop.idx ], prop );
+			} );
+			return this;
+		}
+
+		if ( type === "object" ) {
+			if ( red instanceof color ) {
+				each( spaces, function( spaceName, space ) {
+					if ( red[ space.cache ] ) {
+						inst[ space.cache ] = red[ space.cache ].slice();
+					}
+				} );
+			} else {
+				each( spaces, function( spaceName, space ) {
+					var cache = space.cache;
+					each( space.props, function( key, prop ) {
+
+						// If the cache doesn't exist, and we know how to convert
+						if ( !inst[ cache ] && space.to ) {
+
+							// If the value was null, we don't need to copy it
+							// if the key was alpha, we don't need to copy it either
+							if ( key === "alpha" || red[ key ] == null ) {
+								return;
+							}
+							inst[ cache ] = space.to( inst._rgba );
+						}
+
+						// This is the only case where we allow nulls for ALL properties.
+						// call clamp with alwaysAllowEmpty
+						inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true );
+					} );
+
+					// Everything defined but alpha?
+					if ( inst[ cache ] &&
+							jQuery.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) {
+
+						// Use the default of 1
+						inst[ cache ][ 3 ] = 1;
+						if ( space.from ) {
+							inst._rgba = space.from( inst[ cache ] );
+						}
+					}
+				} );
+			}
+			return this;
+		}
+	},
+	is: function( compare ) {
+		var is = color( compare ),
+			same = true,
+			inst = this;
+
+		each( spaces, function( _, space ) {
+			var localCache,
+				isCache = is[ space.cache ];
+			if ( isCache ) {
+				localCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || [];
+				each( space.props, function( _, prop ) {
+					if ( isCache[ prop.idx ] != null ) {
+						same = ( isCache[ prop.idx ] === localCache[ prop.idx ] );
+						return same;
+					}
+				} );
+			}
+			return same;
+		} );
+		return same;
+	},
+	_space: function() {
+		var used = [],
+			inst = this;
+		each( spaces, function( spaceName, space ) {
+			if ( inst[ space.cache ] ) {
+				used.push( spaceName );
+			}
+		} );
+		return used.pop();
+	},
+	transition: function( other, distance ) {
+		var end = color( other ),
+			spaceName = end._space(),
+			space = spaces[ spaceName ],
+			startColor = this.alpha() === 0 ? color( "transparent" ) : this,
+			start = startColor[ space.cache ] || space.to( startColor._rgba ),
+			result = start.slice();
+
+		end = end[ space.cache ];
+		each( space.props, function( key, prop ) {
+			var index = prop.idx,
+				startValue = start[ index ],
+				endValue = end[ index ],
+				type = propTypes[ prop.type ] || {};
+
+			// If null, don't override start value
+			if ( endValue === null ) {
+				return;
+			}
+
+			// If null - use end
+			if ( startValue === null ) {
+				result[ index ] = endValue;
+			} else {
+				if ( type.mod ) {
+					if ( endValue - startValue > type.mod / 2 ) {
+						startValue += type.mod;
+					} else if ( startValue - endValue > type.mod / 2 ) {
+						startValue -= type.mod;
+					}
+				}
+				result[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop );
+			}
+		} );
+		return this[ spaceName ]( result );
+	},
+	blend: function( opaque ) {
+
+		// If we are already opaque - return ourself
+		if ( this._rgba[ 3 ] === 1 ) {
+			return this;
+		}
+
+		var rgb = this._rgba.slice(),
+			a = rgb.pop(),
+			blend = color( opaque )._rgba;
+
+		return color( jQuery.map( rgb, function( v, i ) {
+			return ( 1 - a ) * blend[ i ] + a * v;
+		} ) );
+	},
+	toRgbaString: function() {
+		var prefix = "rgba(",
+			rgba = jQuery.map( this._rgba, function( v, i ) {
+				return v == null ? ( i > 2 ? 1 : 0 ) : v;
+			} );
+
+		if ( rgba[ 3 ] === 1 ) {
+			rgba.pop();
+			prefix = "rgb(";
+		}
+
+		return prefix + rgba.join() + ")";
+	},
+	toHslaString: function() {
+		var prefix = "hsla(",
+			hsla = jQuery.map( this.hsla(), function( v, i ) {
+				if ( v == null ) {
+					v = i > 2 ? 1 : 0;
+				}
+
+				// Catch 1 and 2
+				if ( i && i < 3 ) {
+					v = Math.round( v * 100 ) + "%";
+				}
+				return v;
+			} );
+
+		if ( hsla[ 3 ] === 1 ) {
+			hsla.pop();
+			prefix = "hsl(";
+		}
+		return prefix + hsla.join() + ")";
+	},
+	toHexString: function( includeAlpha ) {
+		var rgba = this._rgba.slice(),
+			alpha = rgba.pop();
+
+		if ( includeAlpha ) {
+			rgba.push( ~~( alpha * 255 ) );
+		}
+
+		return "#" + jQuery.map( rgba, function( v ) {
+
+			// Default to 0 when nulls exist
+			v = ( v || 0 ).toString( 16 );
+			return v.length === 1 ? "0" + v : v;
+		} ).join( "" );
+	},
+	toString: function() {
+		return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString();
+	}
+} );
+color.fn.parse.prototype = color.fn;
+
+// Hsla conversions adapted from:
+// https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021
+
+function hue2rgb( p, q, h ) {
+	h = ( h + 1 ) % 1;
+	if ( h * 6 < 1 ) {
+		return p + ( q - p ) * h * 6;
+	}
+	if ( h * 2 < 1 ) {
+		return q;
+	}
+	if ( h * 3 < 2 ) {
+		return p + ( q - p ) * ( ( 2 / 3 ) - h ) * 6;
+	}
+	return p;
+}
+
+spaces.hsla.to = function( rgba ) {
+	if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) {
+		return [ null, null, null, rgba[ 3 ] ];
+	}
+	var r = rgba[ 0 ] / 255,
+		g = rgba[ 1 ] / 255,
+		b = rgba[ 2 ] / 255,
+		a = rgba[ 3 ],
+		max = Math.max( r, g, b ),
+		min = Math.min( r, g, b ),
+		diff = max - min,
+		add = max + min,
+		l = add * 0.5,
+		h, s;
+
+	if ( min === max ) {
+		h = 0;
+	} else if ( r === max ) {
+		h = ( 60 * ( g - b ) / diff ) + 360;
+	} else if ( g === max ) {
+		h = ( 60 * ( b - r ) / diff ) + 120;
+	} else {
+		h = ( 60 * ( r - g ) / diff ) + 240;
+	}
+
+	// Chroma (diff) == 0 means greyscale which, by definition, saturation = 0%
+	// otherwise, saturation is based on the ratio of chroma (diff) to lightness (add)
+	if ( diff === 0 ) {
+		s = 0;
+	} else if ( l <= 0.5 ) {
+		s = diff / add;
+	} else {
+		s = diff / ( 2 - add );
+	}
+	return [ Math.round( h ) % 360, s, l, a == null ? 1 : a ];
+};
+
+spaces.hsla.from = function( hsla ) {
+	if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) {
+		return [ null, null, null, hsla[ 3 ] ];
+	}
+	var h = hsla[ 0 ] / 360,
+		s = hsla[ 1 ],
+		l = hsla[ 2 ],
+		a = hsla[ 3 ],
+		q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s,
+		p = 2 * l - q;
+
+	return [
+		Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ),
+		Math.round( hue2rgb( p, q, h ) * 255 ),
+		Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ),
+		a
+	];
+};
+
+each( spaces, function( spaceName, space ) {
+	var props = space.props,
+		cache = space.cache,
+		to = space.to,
+		from = space.from;
+
+	// Makes rgba() and hsla()
+	color.fn[ spaceName ] = function( value ) {
+
+		// Generate a cache for this space if it doesn't exist
+		if ( to && !this[ cache ] ) {
+			this[ cache ] = to( this._rgba );
+		}
+		if ( value === undefined ) {
+			return this[ cache ].slice();
+		}
+
+		var ret,
+			type = jQuery.type( value ),
+			arr = ( type === "array" || type === "object" ) ? value : arguments,
+			local = this[ cache ].slice();
+
+		each( props, function( key, prop ) {
+			var val = arr[ type === "object" ? key : prop.idx ];
+			if ( val == null ) {
+				val = local[ prop.idx ];
+			}
+			local[ prop.idx ] = clamp( val, prop );
+		} );
+
+		if ( from ) {
+			ret = color( from( local ) );
+			ret[ cache ] = local;
+			return ret;
+		} else {
+			return color( local );
+		}
+	};
+
+	// Makes red() green() blue() alpha() hue() saturation() lightness()
+	each( props, function( key, prop ) {
+
+		// Alpha is included in more than one space
+		if ( color.fn[ key ] ) {
+			return;
+		}
+		color.fn[ key ] = function( value ) {
+			var vtype = jQuery.type( value ),
+				fn = ( key === "alpha" ? ( this._hsla ? "hsla" : "rgba" ) : spaceName ),
+				local = this[ fn ](),
+				cur = local[ prop.idx ],
+				match;
+
+			if ( vtype === "undefined" ) {
+				return cur;
+			}
+
+			if ( vtype === "function" ) {
+				value = value.call( this, cur );
+				vtype = jQuery.type( value );
+			}
+			if ( value == null && prop.empty ) {
+				return this;
+			}
+			if ( vtype === "string" ) {
+				match = rplusequals.exec( value );
+				if ( match ) {
+					value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 );
+				}
+			}
+			local[ prop.idx ] = value;
+			return this[ fn ]( local );
+		};
+	} );
+} );
+
+// Add cssHook and .fx.step function for each named hook.
+// accept a space separated string of properties
+color.hook = function( hook ) {
+	var hooks = hook.split( " " );
+	each( hooks, function( i, hook ) {
+		jQuery.cssHooks[ hook ] = {
+			set: function( elem, value ) {
+				var parsed, curElem,
+					backgroundColor = "";
+
+				if ( value !== "transparent" && ( jQuery.type( value ) !== "string" ||
+						( parsed = stringParse( value ) ) ) ) {
+					value = color( parsed || value );
+					if ( !support.rgba && value._rgba[ 3 ] !== 1 ) {
+						curElem = hook === "backgroundColor" ? elem.parentNode : elem;
+						while (
+							( backgroundColor === "" || backgroundColor === "transparent" ) &&
+							curElem && curElem.style
+						) {
+							try {
+								backgroundColor = jQuery.css( curElem, "backgroundColor" );
+								curElem = curElem.parentNode;
+							} catch ( e ) {
+							}
+						}
+
+						value = value.blend( backgroundColor && backgroundColor !== "transparent" ?
+							backgroundColor :
+							"_default" );
+					}
+
+					value = value.toRgbaString();
+				}
+				try {
+					elem.style[ hook ] = value;
+				} catch ( e ) {
+
+					// Wrapped to prevent IE from throwing errors on "invalid" values like
+					// 'auto' or 'inherit'
+				}
+			}
+		};
+		jQuery.fx.step[ hook ] = function( fx ) {
+			if ( !fx.colorInit ) {
+				fx.start = color( fx.elem, hook );
+				fx.end = color( fx.end );
+				fx.colorInit = true;
+			}
+			jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) );
+		};
+	} );
+
+};
+
+color.hook( stepHooks );
+
+jQuery.cssHooks.borderColor = {
+	expand: function( value ) {
+		var expanded = {};
+
+		each( [ "Top", "Right", "Bottom", "Left" ], function( i, part ) {
+			expanded[ "border" + part + "Color" ] = value;
+		} );
+		return expanded;
+	}
+};
+
+// Basic color names only.
+// Usage of any of the other color names requires adding yourself or including
+// jquery.color.svg-names.js.
+colors = jQuery.Color.names = {
+
+	// 4.1. Basic color keywords
+	aqua: "#00ffff",
+	black: "#000000",
+	blue: "#0000ff",
+	fuchsia: "#ff00ff",
+	gray: "#808080",
+	green: "#008000",
+	lime: "#00ff00",
+	maroon: "#800000",
+	navy: "#000080",
+	olive: "#808000",
+	purple: "#800080",
+	red: "#ff0000",
+	silver: "#c0c0c0",
+	teal: "#008080",
+	white: "#ffffff",
+	yellow: "#ffff00",
+
+	// 4.2.3. "transparent" color keyword
+	transparent: [ null, null, null, 0 ],
+
+	_default: "#ffffff"
+};
+
+} )( jQuery );
+
+/******************************************************************************/
+/****************************** CLASS ANIMATIONS ******************************/
+/******************************************************************************/
+( function() {
+
+var classAnimationActions = [ "add", "remove", "toggle" ],
+	shorthandStyles = {
+		border: 1,
+		borderBottom: 1,
+		borderColor: 1,
+		borderLeft: 1,
+		borderRight: 1,
+		borderTop: 1,
+		borderWidth: 1,
+		margin: 1,
+		padding: 1
+	};
+
+$.each(
+	[ "borderLeftStyle", "borderRightStyle", "borderBottomStyle", "borderTopStyle" ],
+	function( _, prop ) {
+		$.fx.step[ prop ] = function( fx ) {
+			if ( fx.end !== "none" && !fx.setAttr || fx.pos === 1 && !fx.setAttr ) {
+				jQuery.style( fx.elem, prop, fx.end );
+				fx.setAttr = true;
+			}
+		};
+	}
+);
+
+function getElementStyles( elem ) {
+	var key, len,
+		style = elem.ownerDocument.defaultView ?
+			elem.ownerDocument.defaultView.getComputedStyle( elem, null ) :
+			elem.currentStyle,
+		styles = {};
+
+	if ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) {
+		len = style.length;
+		while ( len-- ) {
+			key = style[ len ];
+			if ( typeof style[ key ] === "string" ) {
+				styles[ $.camelCase( key ) ] = style[ key ];
+			}
+		}
+
+	// Support: Opera, IE <9
+	} else {
+		for ( key in style ) {
+			if ( typeof style[ key ] === "string" ) {
+				styles[ key ] = style[ key ];
+			}
+		}
+	}
+
+	return styles;
+}
+
+function styleDifference( oldStyle, newStyle ) {
+	var diff = {},
+		name, value;
+
+	for ( name in newStyle ) {
+		value = newStyle[ name ];
+		if ( oldStyle[ name ] !== value ) {
+			if ( !shorthandStyles[ name ] ) {
+				if ( $.fx.step[ name ] || !isNaN( parseFloat( value ) ) ) {
+					diff[ name ] = value;
+				}
+			}
+		}
+	}
+
+	return diff;
+}
+
+// Support: jQuery <1.8
+if ( !$.fn.addBack ) {
+	$.fn.addBack = function( selector ) {
+		return this.add( selector == null ?
+			this.prevObject : this.prevObject.filter( selector )
+		);
+	};
+}
+
+$.effects.animateClass = function( value, duration, easing, callback ) {
+	var o = $.speed( duration, easing, callback );
+
+	return this.queue( function() {
+		var animated = $( this ),
+			baseClass = animated.attr( "class" ) || "",
+			applyClassChange,
+			allAnimations = o.children ? animated.find( "*" ).addBack() : animated;
+
+		// Map the animated objects to store the original styles.
+		allAnimations = allAnimations.map( function() {
+			var el = $( this );
+			return {
+				el: el,
+				start: getElementStyles( this )
+			};
+		} );
+
+		// Apply class change
+		applyClassChange = function() {
+			$.each( classAnimationActions, function( i, action ) {
+				if ( value[ action ] ) {
+					animated[ action + "Class" ]( value[ action ] );
+				}
+			} );
+		};
+		applyClassChange();
+
+		// Map all animated objects again - calculate new styles and diff
+		allAnimations = allAnimations.map( function() {
+			this.end = getElementStyles( this.el[ 0 ] );
+			this.diff = styleDifference( this.start, this.end );
+			return this;
+		} );
+
+		// Apply original class
+		animated.attr( "class", baseClass );
+
+		// Map all animated objects again - this time collecting a promise
+		allAnimations = allAnimations.map( function() {
+			var styleInfo = this,
+				dfd = $.Deferred(),
+				opts = $.extend( {}, o, {
+					queue: false,
+					complete: function() {
+						dfd.resolve( styleInfo );
+					}
+				} );
+
+			this.el.animate( this.diff, opts );
+			return dfd.promise();
+		} );
+
+		// Once all animations have completed:
+		$.when.apply( $, allAnimations.get() ).done( function() {
+
+			// Set the final class
+			applyClassChange();
+
+			// For each animated element,
+			// clear all css properties that were animated
+			$.each( arguments, function() {
+				var el = this.el;
+				$.each( this.diff, function( key ) {
+					el.css( key, "" );
+				} );
+			} );
+
+			// This is guarnteed to be there if you use jQuery.speed()
+			// it also handles dequeuing the next anim...
+			o.complete.call( animated[ 0 ] );
+		} );
+	} );
+};
+
+$.fn.extend( {
+	addClass: ( function( orig ) {
+		return function( classNames, speed, easing, callback ) {
+			return speed ?
+				$.effects.animateClass.call( this,
+					{ add: classNames }, speed, easing, callback ) :
+				orig.apply( this, arguments );
+		};
+	} )( $.fn.addClass ),
+
+	removeClass: ( function( orig ) {
+		return function( classNames, speed, easing, callback ) {
+			return arguments.length > 1 ?
+				$.effects.animateClass.call( this,
+					{ remove: classNames }, speed, easing, callback ) :
+				orig.apply( this, arguments );
+		};
+	} )( $.fn.removeClass ),
+
+	toggleClass: ( function( orig ) {
+		return function( classNames, force, speed, easing, callback ) {
+			if ( typeof force === "boolean" || force === undefined ) {
+				if ( !speed ) {
+
+					// Without speed parameter
+					return orig.apply( this, arguments );
+				} else {
+					return $.effects.animateClass.call( this,
+						( force ? { add: classNames } : { remove: classNames } ),
+						speed, easing, callback );
+				}
+			} else {
+
+				// Without force parameter
+				return $.effects.animateClass.call( this,
+					{ toggle: classNames }, force, speed, easing );
+			}
+		};
+	} )( $.fn.toggleClass ),
+
+	switchClass: function( remove, add, speed, easing, callback ) {
+		return $.effects.animateClass.call( this, {
+			add: add,
+			remove: remove
+		}, speed, easing, callback );
+	}
+} );
+
+} )();
+
+/******************************************************************************/
+/*********************************** EFFECTS **********************************/
+/******************************************************************************/
+
+( function() {
+
+if ( $.expr && $.expr.filters && $.expr.filters.animated ) {
+	$.expr.filters.animated = ( function( orig ) {
+		return function( elem ) {
+			return !!$( elem ).data( dataSpaceAnimated ) || orig( elem );
+		};
+	} )( $.expr.filters.animated );
+}
+
+if ( $.uiBackCompat !== false ) {
+	$.extend( $.effects, {
+
+		// Saves a set of properties in a data storage
+		save: function( element, set ) {
+			var i = 0, length = set.length;
+			for ( ; i < length; i++ ) {
+				if ( set[ i ] !== null ) {
+					element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] );
+				}
+			}
+		},
+
+		// Restores a set of previously saved properties from a data storage
+		restore: function( element, set ) {
+			var val, i = 0, length = set.length;
+			for ( ; i < length; i++ ) {
+				if ( set[ i ] !== null ) {
+					val = element.data( dataSpace + set[ i ] );
+					element.css( set[ i ], val );
+				}
+			}
+		},
+
+		setMode: function( el, mode ) {
+			if ( mode === "toggle" ) {
+				mode = el.is( ":hidden" ) ? "show" : "hide";
+			}
+			return mode;
+		},
+
+		// Wraps the element around a wrapper that copies position properties
+		createWrapper: function( element ) {
+
+			// If the element is already wrapped, return it
+			if ( element.parent().is( ".ui-effects-wrapper" ) ) {
+				return element.parent();
+			}
+
+			// Wrap the element
+			var props = {
+					width: element.outerWidth( true ),
+					height: element.outerHeight( true ),
+					"float": element.css( "float" )
+				},
+				wrapper = $( "<div></div>" )
+					.addClass( "ui-effects-wrapper" )
+					.css( {
+						fontSize: "100%",
+						background: "transparent",
+						border: "none",
+						margin: 0,
+						padding: 0
+					} ),
+
+				// Store the size in case width/height are defined in % - Fixes #5245
+				size = {
+					width: element.width(),
+					height: element.height()
+				},
+				active = document.activeElement;
+
+			// Support: Firefox
+			// Firefox incorrectly exposes anonymous content
+			// https://bugzilla.mozilla.org/show_bug.cgi?id=561664
+			try {
+				active.id;
+			} catch ( e ) {
+				active = document.body;
+			}
+
+			element.wrap( wrapper );
+
+			// Fixes #7595 - Elements lose focus when wrapped.
+			if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
+				$( active ).trigger( "focus" );
+			}
+
+			// Hotfix for jQuery 1.4 since some change in wrap() seems to actually
+			// lose the reference to the wrapped element
+			wrapper = element.parent();
+
+			// Transfer positioning properties to the wrapper
+			if ( element.css( "position" ) === "static" ) {
+				wrapper.css( { position: "relative" } );
+				element.css( { position: "relative" } );
+			} else {
+				$.extend( props, {
+					position: element.css( "position" ),
+					zIndex: element.css( "z-index" )
+				} );
+				$.each( [ "top", "left", "bottom", "right" ], function( i, pos ) {
+					props[ pos ] = element.css( pos );
+					if ( isNaN( parseInt( props[ pos ], 10 ) ) ) {
+						props[ pos ] = "auto";
+					}
+				} );
+				element.css( {
+					position: "relative",
+					top: 0,
+					left: 0,
+					right: "auto",
+					bottom: "auto"
+				} );
+			}
+			element.css( size );
+
+			return wrapper.css( props ).show();
+		},
+
+		removeWrapper: function( element ) {
+			var active = document.activeElement;
+
+			if ( element.parent().is( ".ui-effects-wrapper" ) ) {
+				element.parent().replaceWith( element );
+
+				// Fixes #7595 - Elements lose focus when wrapped.
+				if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
+					$( active ).trigger( "focus" );
+				}
+			}
+
+			return element;
+		}
+	} );
+}
+
+$.extend( $.effects, {
+	version: "1.12.1",
+
+	define: function( name, mode, effect ) {
+		if ( !effect ) {
+			effect = mode;
+			mode = "effect";
+		}
+
+		$.effects.effect[ name ] = effect;
+		$.effects.effect[ name ].mode = mode;
+
+		return effect;
+	},
+
+	scaledDimensions: function( element, percent, direction ) {
+		if ( percent === 0 ) {
+			return {
+				height: 0,
+				width: 0,
+				outerHeight: 0,
+				outerWidth: 0
+			};
+		}
+
+		var x = direction !== "horizontal" ? ( ( percent || 100 ) / 100 ) : 1,
+			y = direction !== "vertical" ? ( ( percent || 100 ) / 100 ) : 1;
+
+		return {
+			height: element.height() * y,
+			width: element.width() * x,
+			outerHeight: element.outerHeight() * y,
+			outerWidth: element.outerWidth() * x
+		};
+
+	},
+
+	clipToBox: function( animation ) {
+		return {
+			width: animation.clip.right - animation.clip.left,
+			height: animation.clip.bottom - animation.clip.top,
+			left: animation.clip.left,
+			top: animation.clip.top
+		};
+	},
+
+	// Injects recently queued functions to be first in line (after "inprogress")
+	unshift: function( element, queueLength, count ) {
+		var queue = element.queue();
+
+		if ( queueLength > 1 ) {
+			queue.splice.apply( queue,
+				[ 1, 0 ].concat( queue.splice( queueLength, count ) ) );
+		}
+		element.dequeue();
+	},
+
+	saveStyle: function( element ) {
+		element.data( dataSpaceStyle, element[ 0 ].style.cssText );
+	},
+
+	restoreStyle: function( element ) {
+		element[ 0 ].style.cssText = element.data( dataSpaceStyle ) || "";
+		element.removeData( dataSpaceStyle );
+	},
+
+	mode: function( element, mode ) {
+		var hidden = element.is( ":hidden" );
+
+		if ( mode === "toggle" ) {
+			mode = hidden ? "show" : "hide";
+		}
+		if ( hidden ? mode === "hide" : mode === "show" ) {
+			mode = "none";
+		}
+		return mode;
+	},
+
+	// Translates a [top,left] array into a baseline value
+	getBaseline: function( origin, original ) {
+		var y, x;
+
+		switch ( origin[ 0 ] ) {
+		case "top":
+			y = 0;
+			break;
+		case "middle":
+			y = 0.5;
+			break;
+		case "bottom":
+			y = 1;
+			break;
+		default:
+			y = origin[ 0 ] / original.height;
+		}
+
+		switch ( origin[ 1 ] ) {
+		case "left":
+			x = 0;
+			break;
+		case "center":
+			x = 0.5;
+			break;
+		case "right":
+			x = 1;
+			break;
+		default:
+			x = origin[ 1 ] / original.width;
+		}
+
+		return {
+			x: x,
+			y: y
+		};
+	},
+
+	// Creates a placeholder element so that the original element can be made absolute
+	createPlaceholder: function( element ) {
+		var placeholder,
+			cssPosition = element.css( "position" ),
+			position = element.position();
+
+		// Lock in margins first to account for form elements, which
+		// will change margin if you explicitly set height
+		// see: http://jsfiddle.net/JZSMt/3/ https://bugs.webkit.org/show_bug.cgi?id=107380
+		// Support: Safari
+		element.css( {
+			marginTop: element.css( "marginTop" ),
+			marginBottom: element.css( "marginBottom" ),
+			marginLeft: element.css( "marginLeft" ),
+			marginRight: element.css( "marginRight" )
+		} )
+		.outerWidth( element.outerWidth() )
+		.outerHeight( element.outerHeight() );
+
+		if ( /^(static|relative)/.test( cssPosition ) ) {
+			cssPosition = "absolute";
+
+			placeholder = $( "<" + element[ 0 ].nodeName + ">" ).insertAfter( element ).css( {
+
+				// Convert inline to inline block to account for inline elements
+				// that turn to inline block based on content (like img)
+				display: /^(inline|ruby)/.test( element.css( "display" ) ) ?
+					"inline-block" :
+					"block",
+				visibility: "hidden",
+
+				// Margins need to be set to account for margin collapse
+				marginTop: element.css( "marginTop" ),
+				marginBottom: element.css( "marginBottom" ),
+				marginLeft: element.css( "marginLeft" ),
+				marginRight: element.css( "marginRight" ),
+				"float": element.css( "float" )
+			} )
+			.outerWidth( element.outerWidth() )
+			.outerHeight( element.outerHeight() )
+			.addClass( "ui-effects-placeholder" );
+
+			element.data( dataSpace + "placeholder", placeholder );
+		}
+
+		element.css( {
+			position: cssPosition,
+			left: position.left,
+			top: position.top
+		} );
+
+		return placeholder;
+	},
+
+	removePlaceholder: function( element ) {
+		var dataKey = dataSpace + "placeholder",
+				placeholder = element.data( dataKey );
+
+		if ( placeholder ) {
+			placeholder.remove();
+			element.removeData( dataKey );
+		}
+	},
+
+	// Removes a placeholder if it exists and restores
+	// properties that were modified during placeholder creation
+	cleanUp: function( element ) {
+		$.effects.restoreStyle( element );
+		$.effects.removePlaceholder( element );
+	},
+
+	setTransition: function( element, list, factor, value ) {
+		value = value || {};
+		$.each( list, function( i, x ) {
+			var unit = element.cssUnit( x );
+			if ( unit[ 0 ] > 0 ) {
+				value[ x ] = unit[ 0 ] * factor + unit[ 1 ];
+			}
+		} );
+		return value;
+	}
+} );
+
+// Return an effect options object for the given parameters:
+function _normalizeArguments( effect, options, speed, callback ) {
+
+	// Allow passing all options as the first parameter
+	if ( $.isPlainObject( effect ) ) {
+		options = effect;
+		effect = effect.effect;
+	}
+
+	// Convert to an object
+	effect = { effect: effect };
+
+	// Catch (effect, null, ...)
+	if ( options == null ) {
+		options = {};
+	}
+
+	// Catch (effect, callback)
+	if ( $.isFunction( options ) ) {
+		callback = options;
+		speed = null;
+		options = {};
+	}
+
+	// Catch (effect, speed, ?)
+	if ( typeof options === "number" || $.fx.speeds[ options ] ) {
+		callback = speed;
+		speed = options;
+		options = {};
+	}
+
+	// Catch (effect, options, callback)
+	if ( $.isFunction( speed ) ) {
+		callback = speed;
+		speed = null;
+	}
+
+	// Add options to effect
+	if ( options ) {
+		$.extend( effect, options );
+	}
+
+	speed = speed || options.duration;
+	effect.duration = $.fx.off ? 0 :
+		typeof speed === "number" ? speed :
+		speed in $.fx.speeds ? $.fx.speeds[ speed ] :
+		$.fx.speeds._default;
+
+	effect.complete = callback || options.complete;
+
+	return effect;
+}
+
+function standardAnimationOption( option ) {
+
+	// Valid standard speeds (nothing, number, named speed)
+	if ( !option || typeof option === "number" || $.fx.speeds[ option ] ) {
+		return true;
+	}
+
+	// Invalid strings - treat as "normal" speed
+	if ( typeof option === "string" && !$.effects.effect[ option ] ) {
+		return true;
+	}
+
+	// Complete callback
+	if ( $.isFunction( option ) ) {
+		return true;
+	}
+
+	// Options hash (but not naming an effect)
+	if ( typeof option === "object" && !option.effect ) {
+		return true;
+	}
+
+	// Didn't match any standard API
+	return false;
+}
+
+$.fn.extend( {
+	effect: function( /* effect, options, speed, callback */ ) {
+		var args = _normalizeArguments.apply( this, arguments ),
+			effectMethod = $.effects.effect[ args.effect ],
+			defaultMode = effectMethod.mode,
+			queue = args.queue,
+			queueName = queue || "fx",
+			complete = args.complete,
+			mode = args.mode,
+			modes = [],
+			prefilter = function( next ) {
+				var el = $( this ),
+					normalizedMode = $.effects.mode( el, mode ) || defaultMode;
+
+				// Sentinel for duck-punching the :animated psuedo-selector
+				el.data( dataSpaceAnimated, true );
+
+				// Save effect mode for later use,
+				// we can't just call $.effects.mode again later,
+				// as the .show() below destroys the initial state
+				modes.push( normalizedMode );
+
+				// See $.uiBackCompat inside of run() for removal of defaultMode in 1.13
+				if ( defaultMode && ( normalizedMode === "show" ||
+						( normalizedMode === defaultMode && normalizedMode === "hide" ) ) ) {
+					el.show();
+				}
+
+				if ( !defaultMode || normalizedMode !== "none" ) {
+					$.effects.saveStyle( el );
+				}
+
+				if ( $.isFunction( next ) ) {
+					next();
+				}
+			};
+
+		if ( $.fx.off || !effectMethod ) {
+
+			// Delegate to the original method (e.g., .show()) if possible
+			if ( mode ) {
+				return this[ mode ]( args.duration, complete );
+			} else {
+				return this.each( function() {
+					if ( complete ) {
+						complete.call( this );
+					}
+				} );
+			}
+		}
+
+		function run( next ) {
+			var elem = $( this );
+
+			function cleanup() {
+				elem.removeData( dataSpaceAnimated );
+
+				$.effects.cleanUp( elem );
+
+				if ( args.mode === "hide" ) {
+					elem.hide();
+				}
+
+				done();
+			}
+
+			function done() {
+				if ( $.isFunction( complete ) ) {
+					complete.call( elem[ 0 ] );
+				}
+
+				if ( $.isFunction( next ) ) {
+					next();
+				}
+			}
+
+			// Override mode option on a per element basis,
+			// as toggle can be either show or hide depending on element state
+			args.mode = modes.shift();
+
+			if ( $.uiBackCompat !== false && !defaultMode ) {
+				if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) {
+
+					// Call the core method to track "olddisplay" properly
+					elem[ mode ]();
+					done();
+				} else {
+					effectMethod.call( elem[ 0 ], args, done );
+				}
+			} else {
+				if ( args.mode === "none" ) {
+
+					// Call the core method to track "olddisplay" properly
+					elem[ mode ]();
+					done();
+				} else {
+					effectMethod.call( elem[ 0 ], args, cleanup );
+				}
+			}
+		}
+
+		// Run prefilter on all elements first to ensure that
+		// any showing or hiding happens before placeholder creation,
+		// which ensures that any layout changes are correctly captured.
+		return queue === false ?
+			this.each( prefilter ).each( run ) :
+			this.queue( queueName, prefilter ).queue( queueName, run );
+	},
+
+	show: ( function( orig ) {
+		return function( option ) {
+			if ( standardAnimationOption( option ) ) {
+				return orig.apply( this, arguments );
+			} else {
+				var args = _normalizeArguments.apply( this, arguments );
+				args.mode = "show";
+				return this.effect.call( this, args );
+			}
+		};
+	} )( $.fn.show ),
+
+	hide: ( function( orig ) {
+		return function( option ) {
+			if ( standardAnimationOption( option ) ) {
+				return orig.apply( this, arguments );
+			} else {
+				var args = _normalizeArguments.apply( this, arguments );
+				args.mode = "hide";
+				return this.effect.call( this, args );
+			}
+		};
+	} )( $.fn.hide ),
+
+	toggle: ( function( orig ) {
+		return function( option ) {
+			if ( standardAnimationOption( option ) || typeof option === "boolean" ) {
+				return orig.apply( this, arguments );
+			} else {
+				var args = _normalizeArguments.apply( this, arguments );
+				args.mode = "toggle";
+				return this.effect.call( this, args );
+			}
+		};
+	} )( $.fn.toggle ),
+
+	cssUnit: function( key ) {
+		var style = this.css( key ),
+			val = [];
+
+		$.each( [ "em", "px", "%", "pt" ], function( i, unit ) {
+			if ( style.indexOf( unit ) > 0 ) {
+				val = [ parseFloat( style ), unit ];
+			}
+		} );
+		return val;
+	},
+
+	cssClip: function( clipObj ) {
+		if ( clipObj ) {
+			return this.css( "clip", "rect(" + clipObj.top + "px " + clipObj.right + "px " +
+				clipObj.bottom + "px " + clipObj.left + "px)" );
+		}
+		return parseClip( this.css( "clip" ), this );
+	},
+
+	transfer: function( options, done ) {
+		var element = $( this ),
+			target = $( options.to ),
+			targetFixed = target.css( "position" ) === "fixed",
+			body = $( "body" ),
+			fixTop = targetFixed ? body.scrollTop() : 0,
+			fixLeft = targetFixed ? body.scrollLeft() : 0,
+			endPosition = target.offset(),
+			animation = {
+				top: endPosition.top - fixTop,
+				left: endPosition.left - fixLeft,
+				height: target.innerHeight(),
+				width: target.innerWidth()
+			},
+			startPosition = element.offset(),
+			transfer = $( "<div class='ui-effects-transfer'></div>" )
+				.appendTo( "body" )
+				.addClass( options.className )
+				.css( {
+					top: startPosition.top - fixTop,
+					left: startPosition.left - fixLeft,
+					height: element.innerHeight(),
+					width: element.innerWidth(),
+					position: targetFixed ? "fixed" : "absolute"
+				} )
+				.animate( animation, options.duration, options.easing, function() {
+					transfer.remove();
+					if ( $.isFunction( done ) ) {
+						done();
+					}
+				} );
+	}
+} );
+
+function parseClip( str, element ) {
+		var outerWidth = element.outerWidth(),
+			outerHeight = element.outerHeight(),
+			clipRegex = /^rect\((-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto),?\s*(-?\d*\.?\d*px|-?\d+%|auto)\)$/,
+			values = clipRegex.exec( str ) || [ "", 0, outerWidth, outerHeight, 0 ];
+
+		return {
+			top: parseFloat( values[ 1 ] ) || 0,
+			right: values[ 2 ] === "auto" ? outerWidth : parseFloat( values[ 2 ] ),
+			bottom: values[ 3 ] === "auto" ? outerHeight : parseFloat( values[ 3 ] ),
+			left: parseFloat( values[ 4 ] ) || 0
+		};
+}
+
+$.fx.step.clip = function( fx ) {
+	if ( !fx.clipInit ) {
+		fx.start = $( fx.elem ).cssClip();
+		if ( typeof fx.end === "string" ) {
+			fx.end = parseClip( fx.end, fx.elem );
+		}
+		fx.clipInit = true;
+	}
+
+	$( fx.elem ).cssClip( {
+		top: fx.pos * ( fx.end.top - fx.start.top ) + fx.start.top,
+		right: fx.pos * ( fx.end.right - fx.start.right ) + fx.start.right,
+		bottom: fx.pos * ( fx.end.bottom - fx.start.bottom ) + fx.start.bottom,
+		left: fx.pos * ( fx.end.left - fx.start.left ) + fx.start.left
+	} );
+};
+
+} )();
+
+/******************************************************************************/
+/*********************************** EASING ***********************************/
+/******************************************************************************/
+
+( function() {
+
+// Based on easing equations from Robert Penner (http://www.robertpenner.com/easing)
+
+var baseEasings = {};
+
+$.each( [ "Quad", "Cubic", "Quart", "Quint", "Expo" ], function( i, name ) {
+	baseEasings[ name ] = function( p ) {
+		return Math.pow( p, i + 2 );
+	};
+} );
+
+$.extend( baseEasings, {
+	Sine: function( p ) {
+		return 1 - Math.cos( p * Math.PI / 2 );
+	},
+	Circ: function( p ) {
+		return 1 - Math.sqrt( 1 - p * p );
+	},
+	Elastic: function( p ) {
+		return p === 0 || p === 1 ? p :
+			-Math.pow( 2, 8 * ( p - 1 ) ) * Math.sin( ( ( p - 1 ) * 80 - 7.5 ) * Math.PI / 15 );
+	},
+	Back: function( p ) {
+		return p * p * ( 3 * p - 2 );
+	},
+	Bounce: function( p ) {
+		var pow2,
+			bounce = 4;
+
+		while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {}
+		return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 );
+	}
+} );
+
+$.each( baseEasings, function( name, easeIn ) {
+	$.easing[ "easeIn" + name ] = easeIn;
+	$.easing[ "easeOut" + name ] = function( p ) {
+		return 1 - easeIn( 1 - p );
+	};
+	$.easing[ "easeInOut" + name ] = function( p ) {
+		return p < 0.5 ?
+			easeIn( p * 2 ) / 2 :
+			1 - easeIn( p * -2 + 2 ) / 2;
+	};
+} );
+
+} )();
+
+var effect = $.effects;
+
+
+/*!
+ * jQuery UI Effects Blind 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Blind Effect
+//>>group: Effects
+//>>description: Blinds the element.
+//>>docs: http://api.jqueryui.com/blind-effect/
+//>>demos: http://jqueryui.com/effect/
+
+
+
+var effectsEffectBlind = $.effects.define( "blind", "hide", function( options, done ) {
+	var map = {
+			up: [ "bottom", "top" ],
+			vertical: [ "bottom", "top" ],
+			down: [ "top", "bottom" ],
+			left: [ "right", "left" ],
+			horizontal: [ "right", "left" ],
+			right: [ "left", "right" ]
+		},
+		element = $( this ),
+		direction = options.direction || "up",
+		start = element.cssClip(),
+		animate = { clip: $.extend( {}, start ) },
+		placeholder = $.effects.createPlaceholder( element );
+
+	animate.clip[ map[ direction ][ 0 ] ] = animate.clip[ map[ direction ][ 1 ] ];
+
+	if ( options.mode === "show" ) {
+		element.cssClip( animate.clip );
+		if ( placeholder ) {
+			placeholder.css( $.effects.clipToBox( animate ) );
+		}
+
+		animate.clip = start;
+	}
+
+	if ( placeholder ) {
+		placeholder.animate( $.effects.clipToBox( animate ), options.duration, options.easing );
+	}
+
+	element.animate( animate, {
+		queue: false,
+		duration: options.duration,
+		easing: options.easing,
+		complete: done
+	} );
+} );
+
+
+/*!
+ * jQuery UI Effects Bounce 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Bounce Effect
+//>>group: Effects
+//>>description: Bounces an element horizontally or vertically n times.
+//>>docs: http://api.jqueryui.com/bounce-effect/
+//>>demos: http://jqueryui.com/effect/
+
+
+
+var effectsEffectBounce = $.effects.define( "bounce", function( options, done ) {
+	var upAnim, downAnim, refValue,
+		element = $( this ),
+
+		// Defaults:
+		mode = options.mode,
+		hide = mode === "hide",
+		show = mode === "show",
+		direction = options.direction || "up",
+		distance = options.distance,
+		times = options.times || 5,
+
+		// Number of internal animations
+		anims = times * 2 + ( show || hide ? 1 : 0 ),
+		speed = options.duration / anims,
+		easing = options.easing,
+
+		// Utility:
+		ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
+		motion = ( direction === "up" || direction === "left" ),
+		i = 0,
+
+		queuelen = element.queue().length;
+
+	$.effects.createPlaceholder( element );
+
+	refValue = element.css( ref );
+
+	// Default distance for the BIGGEST bounce is the outer Distance / 3
+	if ( !distance ) {
+		distance = element[ ref === "top" ? "outerHeight" : "outerWidth" ]() / 3;
+	}
+
+	if ( show ) {
+		downAnim = { opacity: 1 };
+		downAnim[ ref ] = refValue;
+
+		// If we are showing, force opacity 0 and set the initial position
+		// then do the "first" animation
+		element
+			.css( "opacity", 0 )
+			.css( ref, motion ? -distance * 2 : distance * 2 )
+			.animate( downAnim, speed, easing );
+	}
+
+	// Start at the smallest distance if we are hiding
+	if ( hide ) {
+		distance = distance / Math.pow( 2, times - 1 );
+	}
+
+	downAnim = {};
+	downAnim[ ref ] = refValue;
+
+	// Bounces up/down/left/right then back to 0 -- times * 2 animations happen here
+	for ( ; i < times; i++ ) {
+		upAnim = {};
+		upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance;
+
+		element
+			.animate( upAnim, speed, easing )
+			.animate( downAnim, speed, easing );
+
+		distance = hide ? distance * 2 : distance / 2;
+	}
+
+	// Last Bounce when Hiding
+	if ( hide ) {
+		upAnim = { opacity: 0 };
+		upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance;
+
+		element.animate( upAnim, speed, easing );
+	}
+
+	element.queue( done );
+
+	$.effects.unshift( element, queuelen, anims + 1 );
+} );
+
+
+/*!
+ * jQuery UI Effects Clip 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Clip Effect
+//>>group: Effects
+//>>description: Clips the element on and off like an old TV.
+//>>docs: http://api.jqueryui.com/clip-effect/
+//>>demos: http://jqueryui.com/effect/
+
+
+
+var effectsEffectClip = $.effects.define( "clip", "hide", function( options, done ) {
+	var start,
+		animate = {},
+		element = $( this ),
+		direction = options.direction || "vertical",
+		both = direction === "both",
+		horizontal = both || direction === "horizontal",
+		vertical = both || direction === "vertical";
+
+	start = element.cssClip();
+	animate.clip = {
+		top: vertical ? ( start.bottom - start.top ) / 2 : start.top,
+		right: horizontal ? ( start.right - start.left ) / 2 : start.right,
+		bottom: vertical ? ( start.bottom - start.top ) / 2 : start.bottom,
+		left: horizontal ? ( start.right - start.left ) / 2 : start.left
+	};
+
+	$.effects.createPlaceholder( element );
+
+	if ( options.mode === "show" ) {
+		element.cssClip( animate.clip );
+		animate.clip = start;
+	}
+
+	element.animate( animate, {
+		queue: false,
+		duration: options.duration,
+		easing: options.easing,
+		complete: done
+	} );
+
+} );
+
+
+/*!
+ * jQuery UI Effects Drop 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Drop Effect
+//>>group: Effects
+//>>description: Moves an element in one direction and hides it at the same time.
+//>>docs: http://api.jqueryui.com/drop-effect/
+//>>demos: http://jqueryui.com/effect/
+
+
+
+var effectsEffectDrop = $.effects.define( "drop", "hide", function( options, done ) {
+
+	var distance,
+		element = $( this ),
+		mode = options.mode,
+		show = mode === "show",
+		direction = options.direction || "left",
+		ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
+		motion = ( direction === "up" || direction === "left" ) ? "-=" : "+=",
+		oppositeMotion = ( motion === "+=" ) ? "-=" : "+=",
+		animation = {
+			opacity: 0
+		};
+
+	$.effects.createPlaceholder( element );
+
+	distance = options.distance ||
+		element[ ref === "top" ? "outerHeight" : "outerWidth" ]( true ) / 2;
+
+	animation[ ref ] = motion + distance;
+
+	if ( show ) {
+		element.css( animation );
+
+		animation[ ref ] = oppositeMotion + distance;
+		animation.opacity = 1;
+	}
+
+	// Animate
+	element.animate( animation, {
+		queue: false,
+		duration: options.duration,
+		easing: options.easing,
+		complete: done
+	} );
+} );
+
+
+/*!
+ * jQuery UI Effects Explode 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Explode Effect
+//>>group: Effects
+// jscs:disable maximumLineLength
+//>>description: Explodes an element in all directions into n pieces. Implodes an element to its original wholeness.
+// jscs:enable maximumLineLength
+//>>docs: http://api.jqueryui.com/explode-effect/
+//>>demos: http://jqueryui.com/effect/
+
+
+
+var effectsEffectExplode = $.effects.define( "explode", "hide", function( options, done ) {
+
+	var i, j, left, top, mx, my,
+		rows = options.pieces ? Math.round( Math.sqrt( options.pieces ) ) : 3,
+		cells = rows,
+		element = $( this ),
+		mode = options.mode,
+		show = mode === "show",
+
+		// Show and then visibility:hidden the element before calculating offset
+		offset = element.show().css( "visibility", "hidden" ).offset(),
+
+		// Width and height of a piece
+		width = Math.ceil( element.outerWidth() / cells ),
+		height = Math.ceil( element.outerHeight() / rows ),
+		pieces = [];
+
+	// Children animate complete:
+	function childComplete() {
+		pieces.push( this );
+		if ( pieces.length === rows * cells ) {
+			animComplete();
+		}
+	}
+
+	// Clone the element for each row and cell.
+	for ( i = 0; i < rows; i++ ) { // ===>
+		top = offset.top + i * height;
+		my = i - ( rows - 1 ) / 2;
+
+		for ( j = 0; j < cells; j++ ) { // |||
+			left = offset.left + j * width;
+			mx = j - ( cells - 1 ) / 2;
+
+			// Create a clone of the now hidden main element that will be absolute positioned
+			// within a wrapper div off the -left and -top equal to size of our pieces
+			element
+				.clone()
+				.appendTo( "body" )
+				.wrap( "<div></div>" )
+				.css( {
+					position: "absolute",
+					visibility: "visible",
+					left: -j * width,
+					top: -i * height
+				} )
+
+				// Select the wrapper - make it overflow: hidden and absolute positioned based on
+				// where the original was located +left and +top equal to the size of pieces
+				.parent()
+					.addClass( "ui-effects-explode" )
+					.css( {
+						position: "absolute",
+						overflow: "hidden",
+						width: width,
+						height: height,
+						left: left + ( show ? mx * width : 0 ),
+						top: top + ( show ? my * height : 0 ),
+						opacity: show ? 0 : 1
+					} )
+					.animate( {
+						left: left + ( show ? 0 : mx * width ),
+						top: top + ( show ? 0 : my * height ),
+						opacity: show ? 1 : 0
+					}, options.duration || 500, options.easing, childComplete );
+		}
+	}
+
+	function animComplete() {
+		element.css( {
+			visibility: "visible"
+		} );
+		$( pieces ).remove();
+		done();
+	}
+} );
+
+
+/*!
+ * jQuery UI Effects Fade 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Fade Effect
+//>>group: Effects
+//>>description: Fades the element.
+//>>docs: http://api.jqueryui.com/fade-effect/
+//>>demos: http://jqueryui.com/effect/
+
+
+
+var effectsEffectFade = $.effects.define( "fade", "toggle", function( options, done ) {
+	var show = options.mode === "show";
+
+	$( this )
+		.css( "opacity", show ? 0 : 1 )
+		.animate( {
+			opacity: show ? 1 : 0
+		}, {
+			queue: false,
+			duration: options.duration,
+			easing: options.easing,
+			complete: done
+		} );
+} );
+
+
+/*!
+ * jQuery UI Effects Fold 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Fold Effect
+//>>group: Effects
+//>>description: Folds an element first horizontally and then vertically.
+//>>docs: http://api.jqueryui.com/fold-effect/
+//>>demos: http://jqueryui.com/effect/
+
+
+
+var effectsEffectFold = $.effects.define( "fold", "hide", function( options, done ) {
+
+	// Create element
+	var element = $( this ),
+		mode = options.mode,
+		show = mode === "show",
+		hide = mode === "hide",
+		size = options.size || 15,
+		percent = /([0-9]+)%/.exec( size ),
+		horizFirst = !!options.horizFirst,
+		ref = horizFirst ? [ "right", "bottom" ] : [ "bottom", "right" ],
+		duration = options.duration / 2,
+
+		placeholder = $.effects.createPlaceholder( element ),
+
+		start = element.cssClip(),
+		animation1 = { clip: $.extend( {}, start ) },
+		animation2 = { clip: $.extend( {}, start ) },
+
+		distance = [ start[ ref[ 0 ] ], start[ ref[ 1 ] ] ],
+
+		queuelen = element.queue().length;
+
+	if ( percent ) {
+		size = parseInt( percent[ 1 ], 10 ) / 100 * distance[ hide ? 0 : 1 ];
+	}
+	animation1.clip[ ref[ 0 ] ] = size;
+	animation2.clip[ ref[ 0 ] ] = size;
+	animation2.clip[ ref[ 1 ] ] = 0;
+
+	if ( show ) {
+		element.cssClip( animation2.clip );
+		if ( placeholder ) {
+			placeholder.css( $.effects.clipToBox( animation2 ) );
+		}
+
+		animation2.clip = start;
+	}
+
+	// Animate
+	element
+		.queue( function( next ) {
+			if ( placeholder ) {
+				placeholder
+					.animate( $.effects.clipToBox( animation1 ), duration, options.easing )
+					.animate( $.effects.clipToBox( animation2 ), duration, options.easing );
+			}
+
+			next();
+		} )
+		.animate( animation1, duration, options.easing )
+		.animate( animation2, duration, options.easing )
+		.queue( done );
+
+	$.effects.unshift( element, queuelen, 4 );
+} );
+
+
+/*!
+ * jQuery UI Effects Highlight 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Highlight Effect
+//>>group: Effects
+//>>description: Highlights the background of an element in a defined color for a custom duration.
+//>>docs: http://api.jqueryui.com/highlight-effect/
+//>>demos: http://jqueryui.com/effect/
+
+
+
+var effectsEffectHighlight = $.effects.define( "highlight", "show", function( options, done ) {
+	var element = $( this ),
+		animation = {
+			backgroundColor: element.css( "backgroundColor" )
+		};
+
+	if ( options.mode === "hide" ) {
+		animation.opacity = 0;
+	}
+
+	$.effects.saveStyle( element );
+
+	element
+		.css( {
+			backgroundImage: "none",
+			backgroundColor: options.color || "#ffff99"
+		} )
+		.animate( animation, {
+			queue: false,
+			duration: options.duration,
+			easing: options.easing,
+			complete: done
+		} );
+} );
+
+
+/*!
+ * jQuery UI Effects Size 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Size Effect
+//>>group: Effects
+//>>description: Resize an element to a specified width and height.
+//>>docs: http://api.jqueryui.com/size-effect/
+//>>demos: http://jqueryui.com/effect/
+
+
+
+var effectsEffectSize = $.effects.define( "size", function( options, done ) {
+
+	// Create element
+	var baseline, factor, temp,
+		element = $( this ),
+
+		// Copy for children
+		cProps = [ "fontSize" ],
+		vProps = [ "borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom" ],
+		hProps = [ "borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight" ],
+
+		// Set options
+		mode = options.mode,
+		restore = mode !== "effect",
+		scale = options.scale || "both",
+		origin = options.origin || [ "middle", "center" ],
+		position = element.css( "position" ),
+		pos = element.position(),
+		original = $.effects.scaledDimensions( element ),
+		from = options.from || original,
+		to = options.to || $.effects.scaledDimensions( element, 0 );
+
+	$.effects.createPlaceholder( element );
+
+	if ( mode === "show" ) {
+		temp = from;
+		from = to;
+		to = temp;
+	}
+
+	// Set scaling factor
+	factor = {
+		from: {
+			y: from.height / original.height,
+			x: from.width / original.width
+		},
+		to: {
+			y: to.height / original.height,
+			x: to.width / original.width
+		}
+	};
+
+	// Scale the css box
+	if ( scale === "box" || scale === "both" ) {
+
+		// Vertical props scaling
+		if ( factor.from.y !== factor.to.y ) {
+			from = $.effects.setTransition( element, vProps, factor.from.y, from );
+			to = $.effects.setTransition( element, vProps, factor.to.y, to );
+		}
+
+		// Horizontal props scaling
+		if ( factor.from.x !== factor.to.x ) {
+			from = $.effects.setTransition( element, hProps, factor.from.x, from );
+			to = $.effects.setTransition( element, hProps, factor.to.x, to );
+		}
+	}
+
+	// Scale the content
+	if ( scale === "content" || scale === "both" ) {
+
+		// Vertical props scaling
+		if ( factor.from.y !== factor.to.y ) {
+			from = $.effects.setTransition( element, cProps, factor.from.y, from );
+			to = $.effects.setTransition( element, cProps, factor.to.y, to );
+		}
+	}
+
+	// Adjust the position properties based on the provided origin points
+	if ( origin ) {
+		baseline = $.effects.getBaseline( origin, original );
+		from.top = ( original.outerHeight - from.outerHeight ) * baseline.y + pos.top;
+		from.left = ( original.outerWidth - from.outerWidth ) * baseline.x + pos.left;
+		to.top = ( original.outerHeight - to.outerHeight ) * baseline.y + pos.top;
+		to.left = ( original.outerWidth - to.outerWidth ) * baseline.x + pos.left;
+	}
+	element.css( from );
+
+	// Animate the children if desired
+	if ( scale === "content" || scale === "both" ) {
+
+		vProps = vProps.concat( [ "marginTop", "marginBottom" ] ).concat( cProps );
+		hProps = hProps.concat( [ "marginLeft", "marginRight" ] );
+
+		// Only animate children with width attributes specified
+		// TODO: is this right? should we include anything with css width specified as well
+		element.find( "*[width]" ).each( function() {
+			var child = $( this ),
+				childOriginal = $.effects.scaledDimensions( child ),
+				childFrom = {
+					height: childOriginal.height * factor.from.y,
+					width: childOriginal.width * factor.from.x,
+					outerHeight: childOriginal.outerHeight * factor.from.y,
+					outerWidth: childOriginal.outerWidth * factor.from.x
+				},
+				childTo = {
+					height: childOriginal.height * factor.to.y,
+					width: childOriginal.width * factor.to.x,
+					outerHeight: childOriginal.height * factor.to.y,
+					outerWidth: childOriginal.width * factor.to.x
+				};
+
+			// Vertical props scaling
+			if ( factor.from.y !== factor.to.y ) {
+				childFrom = $.effects.setTransition( child, vProps, factor.from.y, childFrom );
+				childTo = $.effects.setTransition( child, vProps, factor.to.y, childTo );
+			}
+
+			// Horizontal props scaling
+			if ( factor.from.x !== factor.to.x ) {
+				childFrom = $.effects.setTransition( child, hProps, factor.from.x, childFrom );
+				childTo = $.effects.setTransition( child, hProps, factor.to.x, childTo );
+			}
+
+			if ( restore ) {
+				$.effects.saveStyle( child );
+			}
+
+			// Animate children
+			child.css( childFrom );
+			child.animate( childTo, options.duration, options.easing, function() {
+
+				// Restore children
+				if ( restore ) {
+					$.effects.restoreStyle( child );
+				}
+			} );
+		} );
+	}
+
+	// Animate
+	element.animate( to, {
+		queue: false,
+		duration: options.duration,
+		easing: options.easing,
+		complete: function() {
+
+			var offset = element.offset();
+
+			if ( to.opacity === 0 ) {
+				element.css( "opacity", from.opacity );
+			}
+
+			if ( !restore ) {
+				element
+					.css( "position", position === "static" ? "relative" : position )
+					.offset( offset );
+
+				// Need to save style here so that automatic style restoration
+				// doesn't restore to the original styles from before the animation.
+				$.effects.saveStyle( element );
+			}
+
+			done();
+		}
+	} );
+
+} );
+
+
+/*!
+ * jQuery UI Effects Scale 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Scale Effect
+//>>group: Effects
+//>>description: Grows or shrinks an element and its content.
+//>>docs: http://api.jqueryui.com/scale-effect/
+//>>demos: http://jqueryui.com/effect/
+
+
+
+var effectsEffectScale = $.effects.define( "scale", function( options, done ) {
+
+	// Create element
+	var el = $( this ),
+		mode = options.mode,
+		percent = parseInt( options.percent, 10 ) ||
+			( parseInt( options.percent, 10 ) === 0 ? 0 : ( mode !== "effect" ? 0 : 100 ) ),
+
+		newOptions = $.extend( true, {
+			from: $.effects.scaledDimensions( el ),
+			to: $.effects.scaledDimensions( el, percent, options.direction || "both" ),
+			origin: options.origin || [ "middle", "center" ]
+		}, options );
+
+	// Fade option to support puff
+	if ( options.fade ) {
+		newOptions.from.opacity = 1;
+		newOptions.to.opacity = 0;
+	}
+
+	$.effects.effect.size.call( this, newOptions, done );
+} );
+
+
+/*!
+ * jQuery UI Effects Puff 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Puff Effect
+//>>group: Effects
+//>>description: Creates a puff effect by scaling the element up and hiding it at the same time.
+//>>docs: http://api.jqueryui.com/puff-effect/
+//>>demos: http://jqueryui.com/effect/
+
+
+
+var effectsEffectPuff = $.effects.define( "puff", "hide", function( options, done ) {
+	var newOptions = $.extend( true, {}, options, {
+		fade: true,
+		percent: parseInt( options.percent, 10 ) || 150
+	} );
+
+	$.effects.effect.scale.call( this, newOptions, done );
+} );
+
+
+/*!
+ * jQuery UI Effects Pulsate 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Pulsate Effect
+//>>group: Effects
+//>>description: Pulsates an element n times by changing the opacity to zero and back.
+//>>docs: http://api.jqueryui.com/pulsate-effect/
+//>>demos: http://jqueryui.com/effect/
+
+
+
+var effectsEffectPulsate = $.effects.define( "pulsate", "show", function( options, done ) {
+	var element = $( this ),
+		mode = options.mode,
+		show = mode === "show",
+		hide = mode === "hide",
+		showhide = show || hide,
+
+		// Showing or hiding leaves off the "last" animation
+		anims = ( ( options.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ),
+		duration = options.duration / anims,
+		animateTo = 0,
+		i = 1,
+		queuelen = element.queue().length;
+
+	if ( show || !element.is( ":visible" ) ) {
+		element.css( "opacity", 0 ).show();
+		animateTo = 1;
+	}
+
+	// Anims - 1 opacity "toggles"
+	for ( ; i < anims; i++ ) {
+		element.animate( { opacity: animateTo }, duration, options.easing );
+		animateTo = 1 - animateTo;
+	}
+
+	element.animate( { opacity: animateTo }, duration, options.easing );
+
+	element.queue( done );
+
+	$.effects.unshift( element, queuelen, anims + 1 );
+} );
+
+
+/*!
+ * jQuery UI Effects Shake 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Shake Effect
+//>>group: Effects
+//>>description: Shakes an element horizontally or vertically n times.
+//>>docs: http://api.jqueryui.com/shake-effect/
+//>>demos: http://jqueryui.com/effect/
+
+
+
+var effectsEffectShake = $.effects.define( "shake", function( options, done ) {
+
+	var i = 1,
+		element = $( this ),
+		direction = options.direction || "left",
+		distance = options.distance || 20,
+		times = options.times || 3,
+		anims = times * 2 + 1,
+		speed = Math.round( options.duration / anims ),
+		ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
+		positiveMotion = ( direction === "up" || direction === "left" ),
+		animation = {},
+		animation1 = {},
+		animation2 = {},
+
+		queuelen = element.queue().length;
+
+	$.effects.createPlaceholder( element );
+
+	// Animation
+	animation[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance;
+	animation1[ ref ] = ( positiveMotion ? "+=" : "-=" ) + distance * 2;
+	animation2[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance * 2;
+
+	// Animate
+	element.animate( animation, speed, options.easing );
+
+	// Shakes
+	for ( ; i < times; i++ ) {
+		element
+			.animate( animation1, speed, options.easing )
+			.animate( animation2, speed, options.easing );
+	}
+
+	element
+		.animate( animation1, speed, options.easing )
+		.animate( animation, speed / 2, options.easing )
+		.queue( done );
+
+	$.effects.unshift( element, queuelen, anims + 1 );
+} );
+
+
+/*!
+ * jQuery UI Effects Slide 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Slide Effect
+//>>group: Effects
+//>>description: Slides an element in and out of the viewport.
+//>>docs: http://api.jqueryui.com/slide-effect/
+//>>demos: http://jqueryui.com/effect/
+
+
+
+var effectsEffectSlide = $.effects.define( "slide", "show", function( options, done ) {
+	var startClip, startRef,
+		element = $( this ),
+		map = {
+			up: [ "bottom", "top" ],
+			down: [ "top", "bottom" ],
+			left: [ "right", "left" ],
+			right: [ "left", "right" ]
+		},
+		mode = options.mode,
+		direction = options.direction || "left",
+		ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
+		positiveMotion = ( direction === "up" || direction === "left" ),
+		distance = options.distance ||
+			element[ ref === "top" ? "outerHeight" : "outerWidth" ]( true ),
+		animation = {};
+
+	$.effects.createPlaceholder( element );
+
+	startClip = element.cssClip();
+	startRef = element.position()[ ref ];
+
+	// Define hide animation
+	animation[ ref ] = ( positiveMotion ? -1 : 1 ) * distance + startRef;
+	animation.clip = element.cssClip();
+	animation.clip[ map[ direction ][ 1 ] ] = animation.clip[ map[ direction ][ 0 ] ];
+
+	// Reverse the animation if we're showing
+	if ( mode === "show" ) {
+		element.cssClip( animation.clip );
+		element.css( ref, animation[ ref ] );
+		animation.clip = startClip;
+		animation[ ref ] = startRef;
+	}
+
+	// Actually animate
+	element.animate( animation, {
+		queue: false,
+		duration: options.duration,
+		easing: options.easing,
+		complete: done
+	} );
+} );
+
+
+/*!
+ * jQuery UI Effects Transfer 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Transfer Effect
+//>>group: Effects
+//>>description: Displays a transfer effect from one element to another.
+//>>docs: http://api.jqueryui.com/transfer-effect/
+//>>demos: http://jqueryui.com/effect/
+
+
+
+var effect;
+if ( $.uiBackCompat !== false ) {
+	effect = $.effects.define( "transfer", function( options, done ) {
+		$( this ).transfer( options, done );
+	} );
+}
+var effectsEffectTransfer = effect;
+
+
+/*!
+ * jQuery UI Focusable 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: :focusable Selector
+//>>group: Core
+//>>description: Selects elements which can be focused.
+//>>docs: http://api.jqueryui.com/focusable-selector/
+
+
+
+// Selectors
+$.ui.focusable = function( element, hasTabindex ) {
+	var map, mapName, img, focusableIfVisible, fieldset,
+		nodeName = element.nodeName.toLowerCase();
+
+	if ( "area" === nodeName ) {
+		map = element.parentNode;
+		mapName = map.name;
+		if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
+			return false;
+		}
+		img = $( "img[usemap='#" + mapName + "']" );
+		return img.length > 0 && img.is( ":visible" );
+	}
+
+	if ( /^(input|select|textarea|button|object)$/.test( nodeName ) ) {
+		focusableIfVisible = !element.disabled;
+
+		if ( focusableIfVisible ) {
+
+			// Form controls within a disabled fieldset are disabled.
+			// However, controls within the fieldset's legend do not get disabled.
+			// Since controls generally aren't placed inside legends, we skip
+			// this portion of the check.
+			fieldset = $( element ).closest( "fieldset" )[ 0 ];
+			if ( fieldset ) {
+				focusableIfVisible = !fieldset.disabled;
+			}
+		}
+	} else if ( "a" === nodeName ) {
+		focusableIfVisible = element.href || hasTabindex;
+	} else {
+		focusableIfVisible = hasTabindex;
+	}
+
+	return focusableIfVisible && $( element ).is( ":visible" ) && visible( $( element ) );
+};
+
+// Support: IE 8 only
+// IE 8 doesn't resolve inherit to visible/hidden for computed values
+function visible( element ) {
+	var visibility = element.css( "visibility" );
+	while ( visibility === "inherit" ) {
+		element = element.parent();
+		visibility = element.css( "visibility" );
+	}
+	return visibility !== "hidden";
+}
+
+$.extend( $.expr[ ":" ], {
+	focusable: function( element ) {
+		return $.ui.focusable( element, $.attr( element, "tabindex" ) != null );
+	}
+} );
+
+var focusable = $.ui.focusable;
+
+
+
+
+// Support: IE8 Only
+// IE8 does not support the form attribute and when it is supplied. It overwrites the form prop
+// with a string, so we need to find the proper form.
+var form = $.fn.form = function() {
+	return typeof this[ 0 ].form === "string" ? this.closest( "form" ) : $( this[ 0 ].form );
+};
+
+
+/*!
+ * jQuery UI Form Reset Mixin 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Form Reset Mixin
+//>>group: Core
+//>>description: Refresh input widgets when their form is reset
+//>>docs: http://api.jqueryui.com/form-reset-mixin/
+
+
+
+var formResetMixin = $.ui.formResetMixin = {
+	_formResetHandler: function() {
+		var form = $( this );
+
+		// Wait for the form reset to actually happen before refreshing
+		setTimeout( function() {
+			var instances = form.data( "ui-form-reset-instances" );
+			$.each( instances, function() {
+				this.refresh();
+			} );
+		} );
+	},
+
+	_bindFormResetHandler: function() {
+		this.form = this.element.form();
+		if ( !this.form.length ) {
+			return;
+		}
+
+		var instances = this.form.data( "ui-form-reset-instances" ) || [];
+		if ( !instances.length ) {
+
+			// We don't use _on() here because we use a single event handler per form
+			this.form.on( "reset.ui-form-reset", this._formResetHandler );
+		}
+		instances.push( this );
+		this.form.data( "ui-form-reset-instances", instances );
+	},
+
+	_unbindFormResetHandler: function() {
+		if ( !this.form.length ) {
+			return;
+		}
+
+		var instances = this.form.data( "ui-form-reset-instances" );
+		instances.splice( $.inArray( this, instances ), 1 );
+		if ( instances.length ) {
+			this.form.data( "ui-form-reset-instances", instances );
+		} else {
+			this.form
+				.removeData( "ui-form-reset-instances" )
+				.off( "reset.ui-form-reset" );
+		}
+	}
+};
+
+
+/*!
+ * jQuery UI Support for jQuery core 1.7.x 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ *
+ */
+
+//>>label: jQuery 1.7 Support
+//>>group: Core
+//>>description: Support version 1.7.x of jQuery core
+
+
+
+// Support: jQuery 1.7 only
+// Not a great way to check versions, but since we only support 1.7+ and only
+// need to detect <1.8, this is a simple check that should suffice. Checking
+// for "1.7." would be a bit safer, but the version string is 1.7, not 1.7.0
+// and we'll never reach 1.70.0 (if we do, we certainly won't be supporting
+// 1.7 anymore). See #11197 for why we're not using feature detection.
+if ( $.fn.jquery.substring( 0, 3 ) === "1.7" ) {
+
+	// Setters for .innerWidth(), .innerHeight(), .outerWidth(), .outerHeight()
+	// Unlike jQuery Core 1.8+, these only support numeric values to set the
+	// dimensions in pixels
+	$.each( [ "Width", "Height" ], function( i, name ) {
+		var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
+			type = name.toLowerCase(),
+			orig = {
+				innerWidth: $.fn.innerWidth,
+				innerHeight: $.fn.innerHeight,
+				outerWidth: $.fn.outerWidth,
+				outerHeight: $.fn.outerHeight
+			};
+
+		function reduce( elem, size, border, margin ) {
+			$.each( side, function() {
+				size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
+				if ( border ) {
+					size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
+				}
+				if ( margin ) {
+					size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
+				}
+			} );
+			return size;
+		}
+
+		$.fn[ "inner" + name ] = function( size ) {
+			if ( size === undefined ) {
+				return orig[ "inner" + name ].call( this );
+			}
+
+			return this.each( function() {
+				$( this ).css( type, reduce( this, size ) + "px" );
+			} );
+		};
+
+		$.fn[ "outer" + name ] = function( size, margin ) {
+			if ( typeof size !== "number" ) {
+				return orig[ "outer" + name ].call( this, size );
+			}
+
+			return this.each( function() {
+				$( this ).css( type, reduce( this, size, true, margin ) + "px" );
+			} );
+		};
+	} );
+
+	$.fn.addBack = function( selector ) {
+		return this.add( selector == null ?
+			this.prevObject : this.prevObject.filter( selector )
+		);
+	};
+}
+
+;
+/*!
+ * jQuery UI Keycode 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Keycode
+//>>group: Core
+//>>description: Provide keycodes as keynames
+//>>docs: http://api.jqueryui.com/jQuery.ui.keyCode/
+
+
+var keycode = $.ui.keyCode = {
+	BACKSPACE: 8,
+	COMMA: 188,
+	DELETE: 46,
+	DOWN: 40,
+	END: 35,
+	ENTER: 13,
+	ESCAPE: 27,
+	HOME: 36,
+	LEFT: 37,
+	PAGE_DOWN: 34,
+	PAGE_UP: 33,
+	PERIOD: 190,
+	RIGHT: 39,
+	SPACE: 32,
+	TAB: 9,
+	UP: 38
+};
+
+
+
+
+// Internal use only
+var escapeSelector = $.ui.escapeSelector = ( function() {
+	var selectorEscape = /([!"#$%&'()*+,./:;<=>?@[\]^`{|}~])/g;
+	return function( selector ) {
+		return selector.replace( selectorEscape, "\\$1" );
+	};
+} )();
+
+
+/*!
+ * jQuery UI Labels 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: labels
+//>>group: Core
+//>>description: Find all the labels associated with a given input
+//>>docs: http://api.jqueryui.com/labels/
+
+
+
+var labels = $.fn.labels = function() {
+	var ancestor, selector, id, labels, ancestors;
+
+	// Check control.labels first
+	if ( this[ 0 ].labels && this[ 0 ].labels.length ) {
+		return this.pushStack( this[ 0 ].labels );
+	}
+
+	// Support: IE <= 11, FF <= 37, Android <= 2.3 only
+	// Above browsers do not support control.labels. Everything below is to support them
+	// as well as document fragments. control.labels does not work on document fragments
+	labels = this.eq( 0 ).parents( "label" );
+
+	// Look for the label based on the id
+	id = this.attr( "id" );
+	if ( id ) {
+
+		// We don't search against the document in case the element
+		// is disconnected from the DOM
+		ancestor = this.eq( 0 ).parents().last();
+
+		// Get a full set of top level ancestors
+		ancestors = ancestor.add( ancestor.length ? ancestor.siblings() : this.siblings() );
+
+		// Create a selector for the label based on the id
+		selector = "label[for='" + $.ui.escapeSelector( id ) + "']";
+
+		labels = labels.add( ancestors.find( selector ).addBack( selector ) );
+
+	}
+
+	// Return whatever we have found for labels
+	return this.pushStack( labels );
+};
+
+
+/*!
+ * jQuery UI Scroll Parent 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: scrollParent
+//>>group: Core
+//>>description: Get the closest ancestor element that is scrollable.
+//>>docs: http://api.jqueryui.com/scrollParent/
+
+
+
+var scrollParent = $.fn.scrollParent = function( includeHidden ) {
+	var position = this.css( "position" ),
+		excludeStaticParent = position === "absolute",
+		overflowRegex = includeHidden ? /(auto|scroll|hidden)/ : /(auto|scroll)/,
+		scrollParent = this.parents().filter( function() {
+			var parent = $( this );
+			if ( excludeStaticParent && parent.css( "position" ) === "static" ) {
+				return false;
+			}
+			return overflowRegex.test( parent.css( "overflow" ) + parent.css( "overflow-y" ) +
+				parent.css( "overflow-x" ) );
+		} ).eq( 0 );
+
+	return position === "fixed" || !scrollParent.length ?
+		$( this[ 0 ].ownerDocument || document ) :
+		scrollParent;
+};
+
+
+/*!
+ * jQuery UI Tabbable 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: :tabbable Selector
+//>>group: Core
+//>>description: Selects elements which can be tabbed to.
+//>>docs: http://api.jqueryui.com/tabbable-selector/
+
+
+
+var tabbable = $.extend( $.expr[ ":" ], {
+	tabbable: function( element ) {
+		var tabIndex = $.attr( element, "tabindex" ),
+			hasTabindex = tabIndex != null;
+		return ( !hasTabindex || tabIndex >= 0 ) && $.ui.focusable( element, hasTabindex );
+	}
+} );
+
+
+/*!
+ * jQuery UI Unique ID 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: uniqueId
+//>>group: Core
+//>>description: Functions to generate and remove uniqueId's
+//>>docs: http://api.jqueryui.com/uniqueId/
+
+
+
+var uniqueId = $.fn.extend( {
+	uniqueId: ( function() {
+		var uuid = 0;
+
+		return function() {
+			return this.each( function() {
+				if ( !this.id ) {
+					this.id = "ui-id-" + ( ++uuid );
+				}
+			} );
+		};
+	} )(),
+
+	removeUniqueId: function() {
+		return this.each( function() {
+			if ( /^ui-id-\d+$/.test( this.id ) ) {
+				$( this ).removeAttr( "id" );
+			}
+		} );
+	}
+} );
+
+
+/*!
+ * jQuery UI Accordion 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Accordion
+//>>group: Widgets
+// jscs:disable maximumLineLength
+//>>description: Displays collapsible content panels for presenting information in a limited amount of space.
+// jscs:enable maximumLineLength
+//>>docs: http://api.jqueryui.com/accordion/
+//>>demos: http://jqueryui.com/accordion/
+//>>css.structure: ../../themes/base/core.css
+//>>css.structure: ../../themes/base/accordion.css
+//>>css.theme: ../../themes/base/theme.css
+
+
+
+var widgetsAccordion = $.widget( "ui.accordion", {
+	version: "1.12.1",
+	options: {
+		active: 0,
+		animate: {},
+		classes: {
+			"ui-accordion-header": "ui-corner-top",
+			"ui-accordion-header-collapsed": "ui-corner-all",
+			"ui-accordion-content": "ui-corner-bottom"
+		},
+		collapsible: false,
+		event: "click",
+		header: "> li > :first-child, > :not(li):even",
+		heightStyle: "auto",
+		icons: {
+			activeHeader: "ui-icon-triangle-1-s",
+			header: "ui-icon-triangle-1-e"
+		},
+
+		// Callbacks
+		activate: null,
+		beforeActivate: null
+	},
+
+	hideProps: {
+		borderTopWidth: "hide",
+		borderBottomWidth: "hide",
+		paddingTop: "hide",
+		paddingBottom: "hide",
+		height: "hide"
+	},
+
+	showProps: {
+		borderTopWidth: "show",
+		borderBottomWidth: "show",
+		paddingTop: "show",
+		paddingBottom: "show",
+		height: "show"
+	},
+
+	_create: function() {
+		var options = this.options;
+
+		this.prevShow = this.prevHide = $();
+		this._addClass( "ui-accordion", "ui-widget ui-helper-reset" );
+		this.element.attr( "role", "tablist" );
+
+		// Don't allow collapsible: false and active: false / null
+		if ( !options.collapsible && ( options.active === false || options.active == null ) ) {
+			options.active = 0;
+		}
+
+		this._processPanels();
+
+		// handle negative values
+		if ( options.active < 0 ) {
+			options.active += this.headers.length;
+		}
+		this._refresh();
+	},
+
+	_getCreateEventData: function() {
+		return {
+			header: this.active,
+			panel: !this.active.length ? $() : this.active.next()
+		};
+	},
+
+	_createIcons: function() {
+		var icon, children,
+			icons = this.options.icons;
+
+		if ( icons ) {
+			icon = $( "<span>" );
+			this._addClass( icon, "ui-accordion-header-icon", "ui-icon " + icons.header );
+			icon.prependTo( this.headers );
+			children = this.active.children( ".ui-accordion-header-icon" );
+			this._removeClass( children, icons.header )
+				._addClass( children, null, icons.activeHeader )
+				._addClass( this.headers, "ui-accordion-icons" );
+		}
+	},
+
+	_destroyIcons: function() {
+		this._removeClass( this.headers, "ui-accordion-icons" );
+		this.headers.children( ".ui-accordion-header-icon" ).remove();
+	},
+
+	_destroy: function() {
+		var contents;
+
+		// Clean up main element
+		this.element.removeAttr( "role" );
+
+		// Clean up headers
+		this.headers
+			.removeAttr( "role aria-expanded aria-selected aria-controls tabIndex" )
+			.removeUniqueId();
+
+		this._destroyIcons();
+
+		// Clean up content panels
+		contents = this.headers.next()
+			.css( "display", "" )
+			.removeAttr( "role aria-hidden aria-labelledby" )
+			.removeUniqueId();
+
+		if ( this.options.heightStyle !== "content" ) {
+			contents.css( "height", "" );
+		}
+	},
+
+	_setOption: function( key, value ) {
+		if ( key === "active" ) {
+
+			// _activate() will handle invalid values and update this.options
+			this._activate( value );
+			return;
+		}
+
+		if ( key === "event" ) {
+			if ( this.options.event ) {
+				this._off( this.headers, this.options.event );
+			}
+			this._setupEvents( value );
+		}
+
+		this._super( key, value );
+
+		// Setting collapsible: false while collapsed; open first panel
+		if ( key === "collapsible" && !value && this.options.active === false ) {
+			this._activate( 0 );
+		}
+
+		if ( key === "icons" ) {
+			this._destroyIcons();
+			if ( value ) {
+				this._createIcons();
+			}
+		}
+	},
+
+	_setOptionDisabled: function( value ) {
+		this._super( value );
+
+		this.element.attr( "aria-disabled", value );
+
+		// Support: IE8 Only
+		// #5332 / #6059 - opacity doesn't cascade to positioned elements in IE
+		// so we need to add the disabled class to the headers and panels
+		this._toggleClass( null, "ui-state-disabled", !!value );
+		this._toggleClass( this.headers.add( this.headers.next() ), null, "ui-state-disabled",
+			!!value );
+	},
+
+	_keydown: function( event ) {
+		if ( event.altKey || event.ctrlKey ) {
+			return;
+		}
+
+		var keyCode = $.ui.keyCode,
+			length = this.headers.length,
+			currentIndex = this.headers.index( event.target ),
+			toFocus = false;
+
+		switch ( event.keyCode ) {
+		case keyCode.RIGHT:
+		case keyCode.DOWN:
+			toFocus = this.headers[ ( currentIndex + 1 ) % length ];
+			break;
+		case keyCode.LEFT:
+		case keyCode.UP:
+			toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
+			break;
+		case keyCode.SPACE:
+		case keyCode.ENTER:
+			this._eventHandler( event );
+			break;
+		case keyCode.HOME:
+			toFocus = this.headers[ 0 ];
+			break;
+		case keyCode.END:
+			toFocus = this.headers[ length - 1 ];
+			break;
+		}
+
+		if ( toFocus ) {
+			$( event.target ).attr( "tabIndex", -1 );
+			$( toFocus ).attr( "tabIndex", 0 );
+			$( toFocus ).trigger( "focus" );
+			event.preventDefault();
+		}
+	},
+
+	_panelKeyDown: function( event ) {
+		if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) {
+			$( event.currentTarget ).prev().trigger( "focus" );
+		}
+	},
+
+	refresh: function() {
+		var options = this.options;
+		this._processPanels();
+
+		// Was collapsed or no panel
+		if ( ( options.active === false && options.collapsible === true ) ||
+				!this.headers.length ) {
+			options.active = false;
+			this.active = $();
+
+		// active false only when collapsible is true
+		} else if ( options.active === false ) {
+			this._activate( 0 );
+
+		// was active, but active panel is gone
+		} else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
+
+			// all remaining panel are disabled
+			if ( this.headers.length === this.headers.find( ".ui-state-disabled" ).length ) {
+				options.active = false;
+				this.active = $();
+
+			// activate previous panel
+			} else {
+				this._activate( Math.max( 0, options.active - 1 ) );
+			}
+
+		// was active, active panel still exists
+		} else {
+
+			// make sure active index is correct
+			options.active = this.headers.index( this.active );
+		}
+
+		this._destroyIcons();
+
+		this._refresh();
+	},
+
+	_processPanels: function() {
+		var prevHeaders = this.headers,
+			prevPanels = this.panels;
+
+		this.headers = this.element.find( this.options.header );
+		this._addClass( this.headers, "ui-accordion-header ui-accordion-header-collapsed",
+			"ui-state-default" );
+
+		this.panels = this.headers.next().filter( ":not(.ui-accordion-content-active)" ).hide();
+		this._addClass( this.panels, "ui-accordion-content", "ui-helper-reset ui-widget-content" );
+
+		// Avoid memory leaks (#10056)
+		if ( prevPanels ) {
+			this._off( prevHeaders.not( this.headers ) );
+			this._off( prevPanels.not( this.panels ) );
+		}
+	},
+
+	_refresh: function() {
+		var maxHeight,
+			options = this.options,
+			heightStyle = options.heightStyle,
+			parent = this.element.parent();
+
+		this.active = this._findActive( options.active );
+		this._addClass( this.active, "ui-accordion-header-active", "ui-state-active" )
+			._removeClass( this.active, "ui-accordion-header-collapsed" );
+		this._addClass( this.active.next(), "ui-accordion-content-active" );
+		this.active.next().show();
+
+		this.headers
+			.attr( "role", "tab" )
+			.each( function() {
+				var header = $( this ),
+					headerId = header.uniqueId().attr( "id" ),
+					panel = header.next(),
+					panelId = panel.uniqueId().attr( "id" );
+				header.attr( "aria-controls", panelId );
+				panel.attr( "aria-labelledby", headerId );
+			} )
+			.next()
+				.attr( "role", "tabpanel" );
+
+		this.headers
+			.not( this.active )
+				.attr( {
+					"aria-selected": "false",
+					"aria-expanded": "false",
+					tabIndex: -1
+				} )
+				.next()
+					.attr( {
+						"aria-hidden": "true"
+					} )
+					.hide();
+
+		// Make sure at least one header is in the tab order
+		if ( !this.active.length ) {
+			this.headers.eq( 0 ).attr( "tabIndex", 0 );
+		} else {
+			this.active.attr( {
+				"aria-selected": "true",
+				"aria-expanded": "true",
+				tabIndex: 0
+			} )
+				.next()
+					.attr( {
+						"aria-hidden": "false"
+					} );
+		}
+
+		this._createIcons();
+
+		this._setupEvents( options.event );
+
+		if ( heightStyle === "fill" ) {
+			maxHeight = parent.height();
+			this.element.siblings( ":visible" ).each( function() {
+				var elem = $( this ),
+					position = elem.css( "position" );
+
+				if ( position === "absolute" || position === "fixed" ) {
+					return;
+				}
+				maxHeight -= elem.outerHeight( true );
+			} );
+
+			this.headers.each( function() {
+				maxHeight -= $( this ).outerHeight( true );
+			} );
+
+			this.headers.next()
+				.each( function() {
+					$( this ).height( Math.max( 0, maxHeight -
+						$( this ).innerHeight() + $( this ).height() ) );
+				} )
+				.css( "overflow", "auto" );
+		} else if ( heightStyle === "auto" ) {
+			maxHeight = 0;
+			this.headers.next()
+				.each( function() {
+					var isVisible = $( this ).is( ":visible" );
+					if ( !isVisible ) {
+						$( this ).show();
+					}
+					maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() );
+					if ( !isVisible ) {
+						$( this ).hide();
+					}
+				} )
+				.height( maxHeight );
+		}
+	},
+
+	_activate: function( index ) {
+		var active = this._findActive( index )[ 0 ];
+
+		// Trying to activate the already active panel
+		if ( active === this.active[ 0 ] ) {
+			return;
+		}
+
+		// Trying to collapse, simulate a click on the currently active header
+		active = active || this.active[ 0 ];
+
+		this._eventHandler( {
+			target: active,
+			currentTarget: active,
+			preventDefault: $.noop
+		} );
+	},
+
+	_findActive: function( selector ) {
+		return typeof selector === "number" ? this.headers.eq( selector ) : $();
+	},
+
+	_setupEvents: function( event ) {
+		var events = {
+			keydown: "_keydown"
+		};
+		if ( event ) {
+			$.each( event.split( " " ), function( index, eventName ) {
+				events[ eventName ] = "_eventHandler";
+			} );
+		}
+
+		this._off( this.headers.add( this.headers.next() ) );
+		this._on( this.headers, events );
+		this._on( this.headers.next(), { keydown: "_panelKeyDown" } );
+		this._hoverable( this.headers );
+		this._focusable( this.headers );
+	},
+
+	_eventHandler: function( event ) {
+		var activeChildren, clickedChildren,
+			options = this.options,
+			active = this.active,
+			clicked = $( event.currentTarget ),
+			clickedIsActive = clicked[ 0 ] === active[ 0 ],
+			collapsing = clickedIsActive && options.collapsible,
+			toShow = collapsing ? $() : clicked.next(),
+			toHide = active.next(),
+			eventData = {
+				oldHeader: active,
+				oldPanel: toHide,
+				newHeader: collapsing ? $() : clicked,
+				newPanel: toShow
+			};
+
+		event.preventDefault();
+
+		if (
+
+				// click on active header, but not collapsible
+				( clickedIsActive && !options.collapsible ) ||
+
+				// allow canceling activation
+				( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
+			return;
+		}
+
+		options.active = collapsing ? false : this.headers.index( clicked );
+
+		// When the call to ._toggle() comes after the class changes
+		// it causes a very odd bug in IE 8 (see #6720)
+		this.active = clickedIsActive ? $() : clicked;
+		this._toggle( eventData );
+
+		// Switch classes
+		// corner classes on the previously active header stay after the animation
+		this._removeClass( active, "ui-accordion-header-active", "ui-state-active" );
+		if ( options.icons ) {
+			activeChildren = active.children( ".ui-accordion-header-icon" );
+			this._removeClass( activeChildren, null, options.icons.activeHeader )
+				._addClass( activeChildren, null, options.icons.header );
+		}
+
+		if ( !clickedIsActive ) {
+			this._removeClass( clicked, "ui-accordion-header-collapsed" )
+				._addClass( clicked, "ui-accordion-header-active", "ui-state-active" );
+			if ( options.icons ) {
+				clickedChildren = clicked.children( ".ui-accordion-header-icon" );
+				this._removeClass( clickedChildren, null, options.icons.header )
+					._addClass( clickedChildren, null, options.icons.activeHeader );
+			}
+
+			this._addClass( clicked.next(), "ui-accordion-content-active" );
+		}
+	},
+
+	_toggle: function( data ) {
+		var toShow = data.newPanel,
+			toHide = this.prevShow.length ? this.prevShow : data.oldPanel;
+
+		// Handle activating a panel during the animation for another activation
+		this.prevShow.add( this.prevHide ).stop( true, true );
+		this.prevShow = toShow;
+		this.prevHide = toHide;
+
+		if ( this.options.animate ) {
+			this._animate( toShow, toHide, data );
+		} else {
+			toHide.hide();
+			toShow.show();
+			this._toggleComplete( data );
+		}
+
+		toHide.attr( {
+			"aria-hidden": "true"
+		} );
+		toHide.prev().attr( {
+			"aria-selected": "false",
+			"aria-expanded": "false"
+		} );
+
+		// if we're switching panels, remove the old header from the tab order
+		// if we're opening from collapsed state, remove the previous header from the tab order
+		// if we're collapsing, then keep the collapsing header in the tab order
+		if ( toShow.length && toHide.length ) {
+			toHide.prev().attr( {
+				"tabIndex": -1,
+				"aria-expanded": "false"
+			} );
+		} else if ( toShow.length ) {
+			this.headers.filter( function() {
+				return parseInt( $( this ).attr( "tabIndex" ), 10 ) === 0;
+			} )
+				.attr( "tabIndex", -1 );
+		}
+
+		toShow
+			.attr( "aria-hidden", "false" )
+			.prev()
+				.attr( {
+					"aria-selected": "true",
+					"aria-expanded": "true",
+					tabIndex: 0
+				} );
+	},
+
+	_animate: function( toShow, toHide, data ) {
+		var total, easing, duration,
+			that = this,
+			adjust = 0,
+			boxSizing = toShow.css( "box-sizing" ),
+			down = toShow.length &&
+				( !toHide.length || ( toShow.index() < toHide.index() ) ),
+			animate = this.options.animate || {},
+			options = down && animate.down || animate,
+			complete = function() {
+				that._toggleComplete( data );
+			};
+
+		if ( typeof options === "number" ) {
+			duration = options;
+		}
+		if ( typeof options === "string" ) {
+			easing = options;
+		}
+
+		// fall back from options to animation in case of partial down settings
+		easing = easing || options.easing || animate.easing;
+		duration = duration || options.duration || animate.duration;
+
+		if ( !toHide.length ) {
+			return toShow.animate( this.showProps, duration, easing, complete );
+		}
+		if ( !toShow.length ) {
+			return toHide.animate( this.hideProps, duration, easing, complete );
+		}
+
+		total = toShow.show().outerHeight();
+		toHide.animate( this.hideProps, {
+			duration: duration,
+			easing: easing,
+			step: function( now, fx ) {
+				fx.now = Math.round( now );
+			}
+		} );
+		toShow
+			.hide()
+			.animate( this.showProps, {
+				duration: duration,
+				easing: easing,
+				complete: complete,
+				step: function( now, fx ) {
+					fx.now = Math.round( now );
+					if ( fx.prop !== "height" ) {
+						if ( boxSizing === "content-box" ) {
+							adjust += fx.now;
+						}
+					} else if ( that.options.heightStyle !== "content" ) {
+						fx.now = Math.round( total - toHide.outerHeight() - adjust );
+						adjust = 0;
+					}
+				}
+			} );
+	},
+
+	_toggleComplete: function( data ) {
+		var toHide = data.oldPanel,
+			prev = toHide.prev();
+
+		this._removeClass( toHide, "ui-accordion-content-active" );
+		this._removeClass( prev, "ui-accordion-header-active" )
+			._addClass( prev, "ui-accordion-header-collapsed" );
+
+		// Work around for rendering bug in IE (#5421)
+		if ( toHide.length ) {
+			toHide.parent()[ 0 ].className = toHide.parent()[ 0 ].className;
+		}
+		this._trigger( "activate", null, data );
+	}
+} );
+
+
+
+var safeActiveElement = $.ui.safeActiveElement = function( document ) {
+	var activeElement;
+
+	// Support: IE 9 only
+	// IE9 throws an "Unspecified error" accessing document.activeElement from an <iframe>
+	try {
+		activeElement = document.activeElement;
+	} catch ( error ) {
+		activeElement = document.body;
+	}
+
+	// Support: IE 9 - 11 only
+	// IE may return null instead of an element
+	// Interestingly, this only seems to occur when NOT in an iframe
+	if ( !activeElement ) {
+		activeElement = document.body;
+	}
+
+	// Support: IE 11 only
+	// IE11 returns a seemingly empty object in some cases when accessing
+	// document.activeElement from an <iframe>
+	if ( !activeElement.nodeName ) {
+		activeElement = document.body;
+	}
+
+	return activeElement;
+};
+
+
+/*!
+ * jQuery UI Menu 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Menu
+//>>group: Widgets
+//>>description: Creates nestable menus.
+//>>docs: http://api.jqueryui.com/menu/
+//>>demos: http://jqueryui.com/menu/
+//>>css.structure: ../../themes/base/core.css
+//>>css.structure: ../../themes/base/menu.css
+//>>css.theme: ../../themes/base/theme.css
+
+
+
+var widgetsMenu = $.widget( "ui.menu", {
+	version: "1.12.1",
+	defaultElement: "<ul>",
+	delay: 300,
+	options: {
+		icons: {
+			submenu: "ui-icon-caret-1-e"
+		},
+		items: "> *",
+		menus: "ul",
+		position: {
+			my: "left top",
+			at: "right top"
+		},
+		role: "menu",
+
+		// Callbacks
+		blur: null,
+		focus: null,
+		select: null
+	},
+
+	_create: function() {
+		this.activeMenu = this.element;
+
+		// Flag used to prevent firing of the click handler
+		// as the event bubbles up through nested menus
+		this.mouseHandled = false;
+		this.element
+			.uniqueId()
+			.attr( {
+				role: this.options.role,
+				tabIndex: 0
+			} );
+
+		this._addClass( "ui-menu", "ui-widget ui-widget-content" );
+		this._on( {
+
+			// Prevent focus from sticking to links inside menu after clicking
+			// them (focus should always stay on UL during navigation).
+			"mousedown .ui-menu-item": function( event ) {
+				event.preventDefault();
+			},
+			"click .ui-menu-item": function( event ) {
+				var target = $( event.target );
+				var active = $( $.ui.safeActiveElement( this.document[ 0 ] ) );
+				if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) {
+					this.select( event );
+
+					// Only set the mouseHandled flag if the event will bubble, see #9469.
+					if ( !event.isPropagationStopped() ) {
+						this.mouseHandled = true;
+					}
+
+					// Open submenu on click
+					if ( target.has( ".ui-menu" ).length ) {
+						this.expand( event );
+					} else if ( !this.element.is( ":focus" ) &&
+							active.closest( ".ui-menu" ).length ) {
+
+						// Redirect focus to the menu
+						this.element.trigger( "focus", [ true ] );
+
+						// If the active item is on the top level, let it stay active.
+						// Otherwise, blur the active item since it is no longer visible.
+						if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) {
+							clearTimeout( this.timer );
+						}
+					}
+				}
+			},
+			"mouseenter .ui-menu-item": function( event ) {
+
+				// Ignore mouse events while typeahead is active, see #10458.
+				// Prevents focusing the wrong item when typeahead causes a scroll while the mouse
+				// is over an item in the menu
+				if ( this.previousFilter ) {
+					return;
+				}
+
+				var actualTarget = $( event.target ).closest( ".ui-menu-item" ),
+					target = $( event.currentTarget );
+
+				// Ignore bubbled events on parent items, see #11641
+				if ( actualTarget[ 0 ] !== target[ 0 ] ) {
+					return;
+				}
+
+				// Remove ui-state-active class from siblings of the newly focused menu item
+				// to avoid a jump caused by adjacent elements both having a class with a border
+				this._removeClass( target.siblings().children( ".ui-state-active" ),
+					null, "ui-state-active" );
+				this.focus( event, target );
+			},
+			mouseleave: "collapseAll",
+			"mouseleave .ui-menu": "collapseAll",
+			focus: function( event, keepActiveItem ) {
+
+				// If there's already an active item, keep it active
+				// If not, activate the first item
+				var item = this.active || this.element.find( this.options.items ).eq( 0 );
+
+				if ( !keepActiveItem ) {
+					this.focus( event, item );
+				}
+			},
+			blur: function( event ) {
+				this._delay( function() {
+					var notContained = !$.contains(
+						this.element[ 0 ],
+						$.ui.safeActiveElement( this.document[ 0 ] )
+					);
+					if ( notContained ) {
+						this.collapseAll( event );
+					}
+				} );
+			},
+			keydown: "_keydown"
+		} );
+
+		this.refresh();
+
+		// Clicks outside of a menu collapse any open menus
+		this._on( this.document, {
+			click: function( event ) {
+				if ( this._closeOnDocumentClick( event ) ) {
+					this.collapseAll( event );
+				}
+
+				// Reset the mouseHandled flag
+				this.mouseHandled = false;
+			}
+		} );
+	},
+
+	_destroy: function() {
+		var items = this.element.find( ".ui-menu-item" )
+				.removeAttr( "role aria-disabled" ),
+			submenus = items.children( ".ui-menu-item-wrapper" )
+				.removeUniqueId()
+				.removeAttr( "tabIndex role aria-haspopup" );
+
+		// Destroy (sub)menus
+		this.element
+			.removeAttr( "aria-activedescendant" )
+			.find( ".ui-menu" ).addBack()
+				.removeAttr( "role aria-labelledby aria-expanded aria-hidden aria-disabled " +
+					"tabIndex" )
+				.removeUniqueId()
+				.show();
+
+		submenus.children().each( function() {
+			var elem = $( this );
+			if ( elem.data( "ui-menu-submenu-caret" ) ) {
+				elem.remove();
+			}
+		} );
+	},
+
+	_keydown: function( event ) {
+		var match, prev, character, skip,
+			preventDefault = true;
+
+		switch ( event.keyCode ) {
+		case $.ui.keyCode.PAGE_UP:
+			this.previousPage( event );
+			break;
+		case $.ui.keyCode.PAGE_DOWN:
+			this.nextPage( event );
+			break;
+		case $.ui.keyCode.HOME:
+			this._move( "first", "first", event );
+			break;
+		case $.ui.keyCode.END:
+			this._move( "last", "last", event );
+			break;
+		case $.ui.keyCode.UP:
+			this.previous( event );
+			break;
+		case $.ui.keyCode.DOWN:
+			this.next( event );
+			break;
+		case $.ui.keyCode.LEFT:
+			this.collapse( event );
+			break;
+		case $.ui.keyCode.RIGHT:
+			if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
+				this.expand( event );
+			}
+			break;
+		case $.ui.keyCode.ENTER:
+		case $.ui.keyCode.SPACE:
+			this._activate( event );
+			break;
+		case $.ui.keyCode.ESCAPE:
+			this.collapse( event );
+			break;
+		default:
+			preventDefault = false;
+			prev = this.previousFilter || "";
+			skip = false;
+
+			// Support number pad values
+			character = event.keyCode >= 96 && event.keyCode <= 105 ?
+				( event.keyCode - 96 ).toString() : String.fromCharCode( event.keyCode );
+
+			clearTimeout( this.filterTimer );
+
+			if ( character === prev ) {
+				skip = true;
+			} else {
+				character = prev + character;
+			}
+
+			match = this._filterMenuItems( character );
+			match = skip && match.index( this.active.next() ) !== -1 ?
+				this.active.nextAll( ".ui-menu-item" ) :
+				match;
+
+			// If no matches on the current filter, reset to the last character pressed
+			// to move down the menu to the first item that starts with that character
+			if ( !match.length ) {
+				character = String.fromCharCode( event.keyCode );
+				match = this._filterMenuItems( character );
+			}
+
+			if ( match.length ) {
+				this.focus( event, match );
+				this.previousFilter = character;
+				this.filterTimer = this._delay( function() {
+					delete this.previousFilter;
+				}, 1000 );
+			} else {
+				delete this.previousFilter;
+			}
+		}
+
+		if ( preventDefault ) {
+			event.preventDefault();
+		}
+	},
+
+	_activate: function( event ) {
+		if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
+			if ( this.active.children( "[aria-haspopup='true']" ).length ) {
+				this.expand( event );
+			} else {
+				this.select( event );
+			}
+		}
+	},
+
+	refresh: function() {
+		var menus, items, newSubmenus, newItems, newWrappers,
+			that = this,
+			icon = this.options.icons.submenu,
+			submenus = this.element.find( this.options.menus );
+
+		this._toggleClass( "ui-menu-icons", null, !!this.element.find( ".ui-icon" ).length );
+
+		// Initialize nested menus
+		newSubmenus = submenus.filter( ":not(.ui-menu)" )
+			.hide()
+			.attr( {
+				role: this.options.role,
+				"aria-hidden": "true",
+				"aria-expanded": "false"
+			} )
+			.each( function() {
+				var menu = $( this ),
+					item = menu.prev(),
+					submenuCaret = $( "<span>" ).data( "ui-menu-submenu-caret", true );
+
+				that._addClass( submenuCaret, "ui-menu-icon", "ui-icon " + icon );
+				item
+					.attr( "aria-haspopup", "true" )
+					.prepend( submenuCaret );
+				menu.attr( "aria-labelledby", item.attr( "id" ) );
+			} );
+
+		this._addClass( newSubmenus, "ui-menu", "ui-widget ui-widget-content ui-front" );
+
+		menus = submenus.add( this.element );
+		items = menus.find( this.options.items );
+
+		// Initialize menu-items containing spaces and/or dashes only as dividers
+		items.not( ".ui-menu-item" ).each( function() {
+			var item = $( this );
+			if ( that._isDivider( item ) ) {
+				that._addClass( item, "ui-menu-divider", "ui-widget-content" );
+			}
+		} );
+
+		// Don't refresh list items that are already adapted
+		newItems = items.not( ".ui-menu-item, .ui-menu-divider" );
+		newWrappers = newItems.children()
+			.not( ".ui-menu" )
+				.uniqueId()
+				.attr( {
+					tabIndex: -1,
+					role: this._itemRole()
+				} );
+		this._addClass( newItems, "ui-menu-item" )
+			._addClass( newWrappers, "ui-menu-item-wrapper" );
+
+		// Add aria-disabled attribute to any disabled menu item
+		items.filter( ".ui-state-disabled" ).attr( "aria-disabled", "true" );
+
+		// If the active item has been removed, blur the menu
+		if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
+			this.blur();
+		}
+	},
+
+	_itemRole: function() {
+		return {
+			menu: "menuitem",
+			listbox: "option"
+		}[ this.options.role ];
+	},
+
+	_setOption: function( key, value ) {
+		if ( key === "icons" ) {
+			var icons = this.element.find( ".ui-menu-icon" );
+			this._removeClass( icons, null, this.options.icons.submenu )
+				._addClass( icons, null, value.submenu );
+		}
+		this._super( key, value );
+	},
+
+	_setOptionDisabled: function( value ) {
+		this._super( value );
+
+		this.element.attr( "aria-disabled", String( value ) );
+		this._toggleClass( null, "ui-state-disabled", !!value );
+	},
+
+	focus: function( event, item ) {
+		var nested, focused, activeParent;
+		this.blur( event, event && event.type === "focus" );
+
+		this._scrollIntoView( item );
+
+		this.active = item.first();
+
+		focused = this.active.children( ".ui-menu-item-wrapper" );
+		this._addClass( focused, null, "ui-state-active" );
+
+		// Only update aria-activedescendant if there's a role
+		// otherwise we assume focus is managed elsewhere
+		if ( this.options.role ) {
+			this.element.attr( "aria-activedescendant", focused.attr( "id" ) );
+		}
+
+		// Highlight active parent menu item, if any
+		activeParent = this.active
+			.parent()
+				.closest( ".ui-menu-item" )
+					.children( ".ui-menu-item-wrapper" );
+		this._addClass( activeParent, null, "ui-state-active" );
+
+		if ( event && event.type === "keydown" ) {
+			this._close();
+		} else {
+			this.timer = this._delay( function() {
+				this._close();
+			}, this.delay );
+		}
+
+		nested = item.children( ".ui-menu" );
+		if ( nested.length && event && ( /^mouse/.test( event.type ) ) ) {
+			this._startOpening( nested );
+		}
+		this.activeMenu = item.parent();
+
+		this._trigger( "focus", event, { item: item } );
+	},
+
+	_scrollIntoView: function( item ) {
+		var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
+		if ( this._hasScroll() ) {
+			borderTop = parseFloat( $.css( this.activeMenu[ 0 ], "borderTopWidth" ) ) || 0;
+			paddingTop = parseFloat( $.css( this.activeMenu[ 0 ], "paddingTop" ) ) || 0;
+			offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
+			scroll = this.activeMenu.scrollTop();
+			elementHeight = this.activeMenu.height();
+			itemHeight = item.outerHeight();
+
+			if ( offset < 0 ) {
+				this.activeMenu.scrollTop( scroll + offset );
+			} else if ( offset + itemHeight > elementHeight ) {
+				this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
+			}
+		}
+	},
+
+	blur: function( event, fromFocus ) {
+		if ( !fromFocus ) {
+			clearTimeout( this.timer );
+		}
+
+		if ( !this.active ) {
+			return;
+		}
+
+		this._removeClass( this.active.children( ".ui-menu-item-wrapper" ),
+			null, "ui-state-active" );
+
+		this._trigger( "blur", event, { item: this.active } );
+		this.active = null;
+	},
+
+	_startOpening: function( submenu ) {
+		clearTimeout( this.timer );
+
+		// Don't open if already open fixes a Firefox bug that caused a .5 pixel
+		// shift in the submenu position when mousing over the caret icon
+		if ( submenu.attr( "aria-hidden" ) !== "true" ) {
+			return;
+		}
+
+		this.timer = this._delay( function() {
+			this._close();
+			this._open( submenu );
+		}, this.delay );
+	},
+
+	_open: function( submenu ) {
+		var position = $.extend( {
+			of: this.active
+		}, this.options.position );
+
+		clearTimeout( this.timer );
+		this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) )
+			.hide()
+			.attr( "aria-hidden", "true" );
+
+		submenu
+			.show()
+			.removeAttr( "aria-hidden" )
+			.attr( "aria-expanded", "true" )
+			.position( position );
+	},
+
+	collapseAll: function( event, all ) {
+		clearTimeout( this.timer );
+		this.timer = this._delay( function() {
+
+			// If we were passed an event, look for the submenu that contains the event
+			var currentMenu = all ? this.element :
+				$( event && event.target ).closest( this.element.find( ".ui-menu" ) );
+
+			// If we found no valid submenu ancestor, use the main menu to close all
+			// sub menus anyway
+			if ( !currentMenu.length ) {
+				currentMenu = this.element;
+			}
+
+			this._close( currentMenu );
+
+			this.blur( event );
+
+			// Work around active item staying active after menu is blurred
+			this._removeClass( currentMenu.find( ".ui-state-active" ), null, "ui-state-active" );
+
+			this.activeMenu = currentMenu;
+		}, this.delay );
+	},
+
+	// With no arguments, closes the currently active menu - if nothing is active
+	// it closes all menus.  If passed an argument, it will search for menus BELOW
+	_close: function( startMenu ) {
+		if ( !startMenu ) {
+			startMenu = this.active ? this.active.parent() : this.element;
+		}
+
+		startMenu.find( ".ui-menu" )
+			.hide()
+			.attr( "aria-hidden", "true" )
+			.attr( "aria-expanded", "false" );
+	},
+
+	_closeOnDocumentClick: function( event ) {
+		return !$( event.target ).closest( ".ui-menu" ).length;
+	},
+
+	_isDivider: function( item ) {
+
+		// Match hyphen, em dash, en dash
+		return !/[^\-\u2014\u2013\s]/.test( item.text() );
+	},
+
+	collapse: function( event ) {
+		var newItem = this.active &&
+			this.active.parent().closest( ".ui-menu-item", this.element );
+		if ( newItem && newItem.length ) {
+			this._close();
+			this.focus( event, newItem );
+		}
+	},
+
+	expand: function( event ) {
+		var newItem = this.active &&
+			this.active
+				.children( ".ui-menu " )
+					.find( this.options.items )
+						.first();
+
+		if ( newItem && newItem.length ) {
+			this._open( newItem.parent() );
+
+			// Delay so Firefox will not hide activedescendant change in expanding submenu from AT
+			this._delay( function() {
+				this.focus( event, newItem );
+			} );
+		}
+	},
+
+	next: function( event ) {
+		this._move( "next", "first", event );
+	},
+
+	previous: function( event ) {
+		this._move( "prev", "last", event );
+	},
+
+	isFirstItem: function() {
+		return this.active && !this.active.prevAll( ".ui-menu-item" ).length;
+	},
+
+	isLastItem: function() {
+		return this.active && !this.active.nextAll( ".ui-menu-item" ).length;
+	},
+
+	_move: function( direction, filter, event ) {
+		var next;
+		if ( this.active ) {
+			if ( direction === "first" || direction === "last" ) {
+				next = this.active
+					[ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" )
+					.eq( -1 );
+			} else {
+				next = this.active
+					[ direction + "All" ]( ".ui-menu-item" )
+					.eq( 0 );
+			}
+		}
+		if ( !next || !next.length || !this.active ) {
+			next = this.activeMenu.find( this.options.items )[ filter ]();
+		}
+
+		this.focus( event, next );
+	},
+
+	nextPage: function( event ) {
+		var item, base, height;
+
+		if ( !this.active ) {
+			this.next( event );
+			return;
+		}
+		if ( this.isLastItem() ) {
+			return;
+		}
+		if ( this._hasScroll() ) {
+			base = this.active.offset().top;
+			height = this.element.height();
+			this.active.nextAll( ".ui-menu-item" ).each( function() {
+				item = $( this );
+				return item.offset().top - base - height < 0;
+			} );
+
+			this.focus( event, item );
+		} else {
+			this.focus( event, this.activeMenu.find( this.options.items )
+				[ !this.active ? "first" : "last" ]() );
+		}
+	},
+
+	previousPage: function( event ) {
+		var item, base, height;
+		if ( !this.active ) {
+			this.next( event );
+			return;
+		}
+		if ( this.isFirstItem() ) {
+			return;
+		}
+		if ( this._hasScroll() ) {
+			base = this.active.offset().top;
+			height = this.element.height();
+			this.active.prevAll( ".ui-menu-item" ).each( function() {
+				item = $( this );
+				return item.offset().top - base + height > 0;
+			} );
+
+			this.focus( event, item );
+		} else {
+			this.focus( event, this.activeMenu.find( this.options.items ).first() );
+		}
+	},
+
+	_hasScroll: function() {
+		return this.element.outerHeight() < this.element.prop( "scrollHeight" );
+	},
+
+	select: function( event ) {
+
+		// TODO: It should never be possible to not have an active item at this
+		// point, but the tests don't trigger mouseenter before click.
+		this.active = this.active || $( event.target ).closest( ".ui-menu-item" );
+		var ui = { item: this.active };
+		if ( !this.active.has( ".ui-menu" ).length ) {
+			this.collapseAll( event, true );
+		}
+		this._trigger( "select", event, ui );
+	},
+
+	_filterMenuItems: function( character ) {
+		var escapedCharacter = character.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" ),
+			regex = new RegExp( "^" + escapedCharacter, "i" );
+
+		return this.activeMenu
+			.find( this.options.items )
+
+				// Only match on items, not dividers or other content (#10571)
+				.filter( ".ui-menu-item" )
+					.filter( function() {
+						return regex.test(
+							$.trim( $( this ).children( ".ui-menu-item-wrapper" ).text() ) );
+					} );
+	}
+} );
+
+
+/*!
+ * jQuery UI Autocomplete 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Autocomplete
+//>>group: Widgets
+//>>description: Lists suggested words as the user is typing.
+//>>docs: http://api.jqueryui.com/autocomplete/
+//>>demos: http://jqueryui.com/autocomplete/
+//>>css.structure: ../../themes/base/core.css
+//>>css.structure: ../../themes/base/autocomplete.css
+//>>css.theme: ../../themes/base/theme.css
+
+
+
+$.widget( "ui.autocomplete", {
+	version: "1.12.1",
+	defaultElement: "<input>",
+	options: {
+		appendTo: null,
+		autoFocus: false,
+		delay: 300,
+		minLength: 1,
+		position: {
+			my: "left top",
+			at: "left bottom",
+			collision: "none"
+		},
+		source: null,
+
+		// Callbacks
+		change: null,
+		close: null,
+		focus: null,
+		open: null,
+		response: null,
+		search: null,
+		select: null
+	},
+
+	requestIndex: 0,
+	pending: 0,
+
+	_create: function() {
+
+		// Some browsers only repeat keydown events, not keypress events,
+		// so we use the suppressKeyPress flag to determine if we've already
+		// handled the keydown event. #7269
+		// Unfortunately the code for & in keypress is the same as the up arrow,
+		// so we use the suppressKeyPressRepeat flag to avoid handling keypress
+		// events when we know the keydown event was used to modify the
+		// search term. #7799
+		var suppressKeyPress, suppressKeyPressRepeat, suppressInput,
+			nodeName = this.element[ 0 ].nodeName.toLowerCase(),
+			isTextarea = nodeName === "textarea",
+			isInput = nodeName === "input";
+
+		// Textareas are always multi-line
+		// Inputs are always single-line, even if inside a contentEditable element
+		// IE also treats inputs as contentEditable
+		// All other element types are determined by whether or not they're contentEditable
+		this.isMultiLine = isTextarea || !isInput && this._isContentEditable( this.element );
+
+		this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ];
+		this.isNewMenu = true;
+
+		this._addClass( "ui-autocomplete-input" );
+		this.element.attr( "autocomplete", "off" );
+
+		this._on( this.element, {
+			keydown: function( event ) {
+				if ( this.element.prop( "readOnly" ) ) {
+					suppressKeyPress = true;
+					suppressInput = true;
+					suppressKeyPressRepeat = true;
+					return;
+				}
+
+				suppressKeyPress = false;
+				suppressInput = false;
+				suppressKeyPressRepeat = false;
+				var keyCode = $.ui.keyCode;
+				switch ( event.keyCode ) {
+				case keyCode.PAGE_UP:
+					suppressKeyPress = true;
+					this._move( "previousPage", event );
+					break;
+				case keyCode.PAGE_DOWN:
+					suppressKeyPress = true;
+					this._move( "nextPage", event );
+					break;
+				case keyCode.UP:
+					suppressKeyPress = true;
+					this._keyEvent( "previous", event );
+					break;
+				case keyCode.DOWN:
+					suppressKeyPress = true;
+					this._keyEvent( "next", event );
+					break;
+				case keyCode.ENTER:
+
+					// when menu is open and has focus
+					if ( this.menu.active ) {
+
+						// #6055 - Opera still allows the keypress to occur
+						// which causes forms to submit
+						suppressKeyPress = true;
+						event.preventDefault();
+						this.menu.select( event );
+					}
+					break;
+				case keyCode.TAB:
+					if ( this.menu.active ) {
+						this.menu.select( event );
+					}
+					break;
+				case keyCode.ESCAPE:
+					if ( this.menu.element.is( ":visible" ) ) {
+						if ( !this.isMultiLine ) {
+							this._value( this.term );
+						}
+						this.close( event );
+
+						// Different browsers have different default behavior for escape
+						// Single press can mean undo or clear
+						// Double press in IE means clear the whole form
+						event.preventDefault();
+					}
+					break;
+				default:
+					suppressKeyPressRepeat = true;
+
+					// search timeout should be triggered before the input value is changed
+					this._searchTimeout( event );
+					break;
+				}
+			},
+			keypress: function( event ) {
+				if ( suppressKeyPress ) {
+					suppressKeyPress = false;
+					if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
+						event.preventDefault();
+					}
+					return;
+				}
+				if ( suppressKeyPressRepeat ) {
+					return;
+				}
+
+				// Replicate some key handlers to allow them to repeat in Firefox and Opera
+				var keyCode = $.ui.keyCode;
+				switch ( event.keyCode ) {
+				case keyCode.PAGE_UP:
+					this._move( "previousPage", event );
+					break;
+				case keyCode.PAGE_DOWN:
+					this._move( "nextPage", event );
+					break;
+				case keyCode.UP:
+					this._keyEvent( "previous", event );
+					break;
+				case keyCode.DOWN:
+					this._keyEvent( "next", event );
+					break;
+				}
+			},
+			input: function( event ) {
+				if ( suppressInput ) {
+					suppressInput = false;
+					event.preventDefault();
+					return;
+				}
+				this._searchTimeout( event );
+			},
+			focus: function() {
+				this.selectedItem = null;
+				this.previous = this._value();
+			},
+			blur: function( event ) {
+				if ( this.cancelBlur ) {
+					delete this.cancelBlur;
+					return;
+				}
+
+				clearTimeout( this.searching );
+				this.close( event );
+				this._change( event );
+			}
+		} );
+
+		this._initSource();
+		this.menu = $( "<ul>" )
+			.appendTo( this._appendTo() )
+			.menu( {
+
+				// disable ARIA support, the live region takes care of that
+				role: null
+			} )
+			.hide()
+			.menu( "instance" );
+
+		this._addClass( this.menu.element, "ui-autocomplete", "ui-front" );
+		this._on( this.menu.element, {
+			mousedown: function( event ) {
+
+				// prevent moving focus out of the text field
+				event.preventDefault();
+
+				// IE doesn't prevent moving focus even with event.preventDefault()
+				// so we set a flag to know when we should ignore the blur event
+				this.cancelBlur = true;
+				this._delay( function() {
+					delete this.cancelBlur;
+
+					// Support: IE 8 only
+					// Right clicking a menu item or selecting text from the menu items will
+					// result in focus moving out of the input. However, we've already received
+					// and ignored the blur event because of the cancelBlur flag set above. So
+					// we restore focus to ensure that the menu closes properly based on the user's
+					// next actions.
+					if ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) {
+						this.element.trigger( "focus" );
+					}
+				} );
+			},
+			menufocus: function( event, ui ) {
+				var label, item;
+
+				// support: Firefox
+				// Prevent accidental activation of menu items in Firefox (#7024 #9118)
+				if ( this.isNewMenu ) {
+					this.isNewMenu = false;
+					if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) {
+						this.menu.blur();
+
+						this.document.one( "mousemove", function() {
+							$( event.target ).trigger( event.originalEvent );
+						} );
+
+						return;
+					}
+				}
+
+				item = ui.item.data( "ui-autocomplete-item" );
+				if ( false !== this._trigger( "focus", event, { item: item } ) ) {
+
+					// use value to match what will end up in the input, if it was a key event
+					if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) {
+						this._value( item.value );
+					}
+				}
+
+				// Announce the value in the liveRegion
+				label = ui.item.attr( "aria-label" ) || item.value;
+				if ( label && $.trim( label ).length ) {
+					this.liveRegion.children().hide();
+					$( "<div>" ).text( label ).appendTo( this.liveRegion );
+				}
+			},
+			menuselect: function( event, ui ) {
+				var item = ui.item.data( "ui-autocomplete-item" ),
+					previous = this.previous;
+
+				// Only trigger when focus was lost (click on menu)
+				if ( this.element[ 0 ] !== $.ui.safeActiveElement( this.document[ 0 ] ) ) {
+					this.element.trigger( "focus" );
+					this.previous = previous;
+
+					// #6109 - IE triggers two focus events and the second
+					// is asynchronous, so we need to reset the previous
+					// term synchronously and asynchronously :-(
+					this._delay( function() {
+						this.previous = previous;
+						this.selectedItem = item;
+					} );
+				}
+
+				if ( false !== this._trigger( "select", event, { item: item } ) ) {
+					this._value( item.value );
+				}
+
+				// reset the term after the select event
+				// this allows custom select handling to work properly
+				this.term = this._value();
+
+				this.close( event );
+				this.selectedItem = item;
+			}
+		} );
+
+		this.liveRegion = $( "<div>", {
+			role: "status",
+			"aria-live": "assertive",
+			"aria-relevant": "additions"
+		} )
+			.appendTo( this.document[ 0 ].body );
+
+		this._addClass( this.liveRegion, null, "ui-helper-hidden-accessible" );
+
+		// Turning off autocomplete prevents the browser from remembering the
+		// value when navigating through history, so we re-enable autocomplete
+		// if the page is unloaded before the widget is destroyed. #7790
+		this._on( this.window, {
+			beforeunload: function() {
+				this.element.removeAttr( "autocomplete" );
+			}
+		} );
+	},
+
+	_destroy: function() {
+		clearTimeout( this.searching );
+		this.element.removeAttr( "autocomplete" );
+		this.menu.element.remove();
+		this.liveRegion.remove();
+	},
+
+	_setOption: function( key, value ) {
+		this._super( key, value );
+		if ( key === "source" ) {
+			this._initSource();
+		}
+		if ( key === "appendTo" ) {
+			this.menu.element.appendTo( this._appendTo() );
+		}
+		if ( key === "disabled" && value && this.xhr ) {
+			this.xhr.abort();
+		}
+	},
+
+	_isEventTargetInWidget: function( event ) {
+		var menuElement = this.menu.element[ 0 ];
+
+		return event.target === this.element[ 0 ] ||
+			event.target === menuElement ||
+			$.contains( menuElement, event.target );
+	},
+
+	_closeOnClickOutside: function( event ) {
+		if ( !this._isEventTargetInWidget( event ) ) {
+			this.close();
+		}
+	},
+
+	_appendTo: function() {
+		var element = this.options.appendTo;
+
+		if ( element ) {
+			element = element.jquery || element.nodeType ?
+				$( element ) :
+				this.document.find( element ).eq( 0 );
+		}
+
+		if ( !element || !element[ 0 ] ) {
+			element = this.element.closest( ".ui-front, dialog" );
+		}
+
+		if ( !element.length ) {
+			element = this.document[ 0 ].body;
+		}
+
+		return element;
+	},
+
+	_initSource: function() {
+		var array, url,
+			that = this;
+		if ( $.isArray( this.options.source ) ) {
+			array = this.options.source;
+			this.source = function( request, response ) {
+				response( $.ui.autocomplete.filter( array, request.term ) );
+			};
+		} else if ( typeof this.options.source === "string" ) {
+			url = this.options.source;
+			this.source = function( request, response ) {
+				if ( that.xhr ) {
+					that.xhr.abort();
+				}
+				that.xhr = $.ajax( {
+					url: url,
+					data: request,
+					dataType: "json",
+					success: function( data ) {
+						response( data );
+					},
+					error: function() {
+						response( [] );
+					}
+				} );
+			};
+		} else {
+			this.source = this.options.source;
+		}
+	},
+
+	_searchTimeout: function( event ) {
+		clearTimeout( this.searching );
+		this.searching = this._delay( function() {
+
+			// Search if the value has changed, or if the user retypes the same value (see #7434)
+			var equalValues = this.term === this._value(),
+				menuVisible = this.menu.element.is( ":visible" ),
+				modifierKey = event.altKey || event.ctrlKey || event.metaKey || event.shiftKey;
+
+			if ( !equalValues || ( equalValues && !menuVisible && !modifierKey ) ) {
+				this.selectedItem = null;
+				this.search( null, event );
+			}
+		}, this.options.delay );
+	},
+
+	search: function( value, event ) {
+		value = value != null ? value : this._value();
+
+		// Always save the actual value, not the one passed as an argument
+		this.term = this._value();
+
+		if ( value.length < this.options.minLength ) {
+			return this.close( event );
+		}
+
+		if ( this._trigger( "search", event ) === false ) {
+			return;
+		}
+
+		return this._search( value );
+	},
+
+	_search: function( value ) {
+		this.pending++;
+		this._addClass( "ui-autocomplete-loading" );
+		this.cancelSearch = false;
+
+		this.source( { term: value }, this._response() );
+	},
+
+	_response: function() {
+		var index = ++this.requestIndex;
+
+		return $.proxy( function( content ) {
+			if ( index === this.requestIndex ) {
+				this.__response( content );
+			}
+
+			this.pending--;
+			if ( !this.pending ) {
+				this._removeClass( "ui-autocomplete-loading" );
+			}
+		}, this );
+	},
+
+	__response: function( content ) {
+		if ( content ) {
+			content = this._normalize( content );
+		}
+		this._trigger( "response", null, { content: content } );
+		if ( !this.options.disabled && content && content.length && !this.cancelSearch ) {
+			this._suggest( content );
+			this._trigger( "open" );
+		} else {
+
+			// use ._close() instead of .close() so we don't cancel future searches
+			this._close();
+		}
+	},
+
+	close: function( event ) {
+		this.cancelSearch = true;
+		this._close( event );
+	},
+
+	_close: function( event ) {
+
+		// Remove the handler that closes the menu on outside clicks
+		this._off( this.document, "mousedown" );
+
+		if ( this.menu.element.is( ":visible" ) ) {
+			this.menu.element.hide();
+			this.menu.blur();
+			this.isNewMenu = true;
+			this._trigger( "close", event );
+		}
+	},
+
+	_change: function( event ) {
+		if ( this.previous !== this._value() ) {
+			this._trigger( "change", event, { item: this.selectedItem } );
+		}
+	},
+
+	_normalize: function( items ) {
+
+		// assume all items have the right format when the first item is complete
+		if ( items.length && items[ 0 ].label && items[ 0 ].value ) {
+			return items;
+		}
+		return $.map( items, function( item ) {
+			if ( typeof item === "string" ) {
+				return {
+					label: item,
+					value: item
+				};
+			}
+			return $.extend( {}, item, {
+				label: item.label || item.value,
+				value: item.value || item.label
+			} );
+		} );
+	},
+
+	_suggest: function( items ) {
+		var ul = this.menu.element.empty();
+		this._renderMenu( ul, items );
+		this.isNewMenu = true;
+		this.menu.refresh();
+
+		// Size and position menu
+		ul.show();
+		this._resizeMenu();
+		ul.position( $.extend( {
+			of: this.element
+		}, this.options.position ) );
+
+		if ( this.options.autoFocus ) {
+			this.menu.next();
+		}
+
+		// Listen for interactions outside of the widget (#6642)
+		this._on( this.document, {
+			mousedown: "_closeOnClickOutside"
+		} );
+	},
+
+	_resizeMenu: function() {
+		var ul = this.menu.element;
+		ul.outerWidth( Math.max(
+
+			// Firefox wraps long text (possibly a rounding bug)
+			// so we add 1px to avoid the wrapping (#7513)
+			ul.width( "" ).outerWidth() + 1,
+			this.element.outerWidth()
+		) );
+	},
+
+	_renderMenu: function( ul, items ) {
+		var that = this;
+		$.each( items, function( index, item ) {
+			that._renderItemData( ul, item );
+		} );
+	},
+
+	_renderItemData: function( ul, item ) {
+		return this._renderItem( ul, item ).data( "ui-autocomplete-item", item );
+	},
+
+	_renderItem: function( ul, item ) {
+		return $( "<li>" )
+			.append( $( "<div>" ).text( item.label ) )
+			.appendTo( ul );
+	},
+
+	_move: function( direction, event ) {
+		if ( !this.menu.element.is( ":visible" ) ) {
+			this.search( null, event );
+			return;
+		}
+		if ( this.menu.isFirstItem() && /^previous/.test( direction ) ||
+				this.menu.isLastItem() && /^next/.test( direction ) ) {
+
+			if ( !this.isMultiLine ) {
+				this._value( this.term );
+			}
+
+			this.menu.blur();
+			return;
+		}
+		this.menu[ direction ]( event );
+	},
+
+	widget: function() {
+		return this.menu.element;
+	},
+
+	_value: function() {
+		return this.valueMethod.apply( this.element, arguments );
+	},
+
+	_keyEvent: function( keyEvent, event ) {
+		if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
+			this._move( keyEvent, event );
+
+			// Prevents moving cursor to beginning/end of the text field in some browsers
+			event.preventDefault();
+		}
+	},
+
+	// Support: Chrome <=50
+	// We should be able to just use this.element.prop( "isContentEditable" )
+	// but hidden elements always report false in Chrome.
+	// https://code.google.com/p/chromium/issues/detail?id=313082
+	_isContentEditable: function( element ) {
+		if ( !element.length ) {
+			return false;
+		}
+
+		var editable = element.prop( "contentEditable" );
+
+		if ( editable === "inherit" ) {
+		  return this._isContentEditable( element.parent() );
+		}
+
+		return editable === "true";
+	}
+} );
+
+$.extend( $.ui.autocomplete, {
+	escapeRegex: function( value ) {
+		return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
+	},
+	filter: function( array, term ) {
+		var matcher = new RegExp( $.ui.autocomplete.escapeRegex( term ), "i" );
+		return $.grep( array, function( value ) {
+			return matcher.test( value.label || value.value || value );
+		} );
+	}
+} );
+
+// Live region extension, adding a `messages` option
+// NOTE: This is an experimental API. We are still investigating
+// a full solution for string manipulation and internationalization.
+$.widget( "ui.autocomplete", $.ui.autocomplete, {
+	options: {
+		messages: {
+			noResults: "No search results.",
+			results: function( amount ) {
+				return amount + ( amount > 1 ? " results are" : " result is" ) +
+					" available, use up and down arrow keys to navigate.";
+			}
+		}
+	},
+
+	__response: function( content ) {
+		var message;
+		this._superApply( arguments );
+		if ( this.options.disabled || this.cancelSearch ) {
+			return;
+		}
+		if ( content && content.length ) {
+			message = this.options.messages.results( content.length );
+		} else {
+			message = this.options.messages.noResults;
+		}
+		this.liveRegion.children().hide();
+		$( "<div>" ).text( message ).appendTo( this.liveRegion );
+	}
+} );
+
+var widgetsAutocomplete = $.ui.autocomplete;
+
+
+/*!
+ * jQuery UI Controlgroup 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Controlgroup
+//>>group: Widgets
+//>>description: Visually groups form control widgets
+//>>docs: http://api.jqueryui.com/controlgroup/
+//>>demos: http://jqueryui.com/controlgroup/
+//>>css.structure: ../../themes/base/core.css
+//>>css.structure: ../../themes/base/controlgroup.css
+//>>css.theme: ../../themes/base/theme.css
+
+
+var controlgroupCornerRegex = /ui-corner-([a-z]){2,6}/g;
+
+var widgetsControlgroup = $.widget( "ui.controlgroup", {
+	version: "1.12.1",
+	defaultElement: "<div>",
+	options: {
+		direction: "horizontal",
+		disabled: null,
+		onlyVisible: true,
+		items: {
+			"button": "input[type=button], input[type=submit], input[type=reset], button, a",
+			"controlgroupLabel": ".ui-controlgroup-label",
+			"checkboxradio": "input[type='checkbox'], input[type='radio']",
+			"selectmenu": "select",
+			"spinner": ".ui-spinner-input"
+		}
+	},
+
+	_create: function() {
+		this._enhance();
+	},
+
+	// To support the enhanced option in jQuery Mobile, we isolate DOM manipulation
+	_enhance: function() {
+		this.element.attr( "role", "toolbar" );
+		this.refresh();
+	},
+
+	_destroy: function() {
+		this._callChildMethod( "destroy" );
+		this.childWidgets.removeData( "ui-controlgroup-data" );
+		this.element.removeAttr( "role" );
+		if ( this.options.items.controlgroupLabel ) {
+			this.element
+				.find( this.options.items.controlgroupLabel )
+				.find( ".ui-controlgroup-label-contents" )
+				.contents().unwrap();
+		}
+	},
+
+	_initWidgets: function() {
+		var that = this,
+			childWidgets = [];
+
+		// First we iterate over each of the items options
+		$.each( this.options.items, function( widget, selector ) {
+			var labels;
+			var options = {};
+
+			// Make sure the widget has a selector set
+			if ( !selector ) {
+				return;
+			}
+
+			if ( widget === "controlgroupLabel" ) {
+				labels = that.element.find( selector );
+				labels.each( function() {
+					var element = $( this );
+
+					if ( element.children( ".ui-controlgroup-label-contents" ).length ) {
+						return;
+					}
+					element.contents()
+						.wrapAll( "<span class='ui-controlgroup-label-contents'></span>" );
+				} );
+				that._addClass( labels, null, "ui-widget ui-widget-content ui-state-default" );
+				childWidgets = childWidgets.concat( labels.get() );
+				return;
+			}
+
+			// Make sure the widget actually exists
+			if ( !$.fn[ widget ] ) {
+				return;
+			}
+
+			// We assume everything is in the middle to start because we can't determine
+			// first / last elements until all enhancments are done.
+			if ( that[ "_" + widget + "Options" ] ) {
+				options = that[ "_" + widget + "Options" ]( "middle" );
+			} else {
+				options = { classes: {} };
+			}
+
+			// Find instances of this widget inside controlgroup and init them
+			that.element
+				.find( selector )
+				.each( function() {
+					var element = $( this );
+					var instance = element[ widget ]( "instance" );
+
+					// We need to clone the default options for this type of widget to avoid
+					// polluting the variable options which has a wider scope than a single widget.
+					var instanceOptions = $.widget.extend( {}, options );
+
+					// If the button is the child of a spinner ignore it
+					// TODO: Find a more generic solution
+					if ( widget === "button" && element.parent( ".ui-spinner" ).length ) {
+						return;
+					}
+
+					// Create the widget if it doesn't exist
+					if ( !instance ) {
+						instance = element[ widget ]()[ widget ]( "instance" );
+					}
+					if ( instance ) {
+						instanceOptions.classes =
+							that._resolveClassesValues( instanceOptions.classes, instance );
+					}
+					element[ widget ]( instanceOptions );
+
+					// Store an instance of the controlgroup to be able to reference
+					// from the outermost element for changing options and refresh
+					var widgetElement = element[ widget ]( "widget" );
+					$.data( widgetElement[ 0 ], "ui-controlgroup-data",
+						instance ? instance : element[ widget ]( "instance" ) );
+
+					childWidgets.push( widgetElement[ 0 ] );
+				} );
+		} );
+
+		this.childWidgets = $( $.unique( childWidgets ) );
+		this._addClass( this.childWidgets, "ui-controlgroup-item" );
+	},
+
+	_callChildMethod: function( method ) {
+		this.childWidgets.each( function() {
+			var element = $( this ),
+				data = element.data( "ui-controlgroup-data" );
+			if ( data && data[ method ] ) {
+				data[ method ]();
+			}
+		} );
+	},
+
+	_updateCornerClass: function( element, position ) {
+		var remove = "ui-corner-top ui-corner-bottom ui-corner-left ui-corner-right ui-corner-all";
+		var add = this._buildSimpleOptions( position, "label" ).classes.label;
+
+		this._removeClass( element, null, remove );
+		this._addClass( element, null, add );
+	},
+
+	_buildSimpleOptions: function( position, key ) {
+		var direction = this.options.direction === "vertical";
+		var result = {
+			classes: {}
+		};
+		result.classes[ key ] = {
+			"middle": "",
+			"first": "ui-corner-" + ( direction ? "top" : "left" ),
+			"last": "ui-corner-" + ( direction ? "bottom" : "right" ),
+			"only": "ui-corner-all"
+		}[ position ];
+
+		return result;
+	},
+
+	_spinnerOptions: function( position ) {
+		var options = this._buildSimpleOptions( position, "ui-spinner" );
+
+		options.classes[ "ui-spinner-up" ] = "";
+		options.classes[ "ui-spinner-down" ] = "";
+
+		return options;
+	},
+
+	_buttonOptions: function( position ) {
+		return this._buildSimpleOptions( position, "ui-button" );
+	},
+
+	_checkboxradioOptions: function( position ) {
+		return this._buildSimpleOptions( position, "ui-checkboxradio-label" );
+	},
+
+	_selectmenuOptions: function( position ) {
+		var direction = this.options.direction === "vertical";
+		return {
+			width: direction ? "auto" : false,
+			classes: {
+				middle: {
+					"ui-selectmenu-button-open": "",
+					"ui-selectmenu-button-closed": ""
+				},
+				first: {
+					"ui-selectmenu-button-open": "ui-corner-" + ( direction ? "top" : "tl" ),
+					"ui-selectmenu-button-closed": "ui-corner-" + ( direction ? "top" : "left" )
+				},
+				last: {
+					"ui-selectmenu-button-open": direction ? "" : "ui-corner-tr",
+					"ui-selectmenu-button-closed": "ui-corner-" + ( direction ? "bottom" : "right" )
+				},
+				only: {
+					"ui-selectmenu-button-open": "ui-corner-top",
+					"ui-selectmenu-button-closed": "ui-corner-all"
+				}
+
+			}[ position ]
+		};
+	},
+
+	_resolveClassesValues: function( classes, instance ) {
+		var result = {};
+		$.each( classes, function( key ) {
+			var current = instance.options.classes[ key ] || "";
+			current = $.trim( current.replace( controlgroupCornerRegex, "" ) );
+			result[ key ] = ( current + " " + classes[ key ] ).replace( /\s+/g, " " );
+		} );
+		return result;
+	},
+
+	_setOption: function( key, value ) {
+		if ( key === "direction" ) {
+			this._removeClass( "ui-controlgroup-" + this.options.direction );
+		}
+
+		this._super( key, value );
+		if ( key === "disabled" ) {
+			this._callChildMethod( value ? "disable" : "enable" );
+			return;
+		}
+
+		this.refresh();
+	},
+
+	refresh: function() {
+		var children,
+			that = this;
+
+		this._addClass( "ui-controlgroup ui-controlgroup-" + this.options.direction );
+
+		if ( this.options.direction === "horizontal" ) {
+			this._addClass( null, "ui-helper-clearfix" );
+		}
+		this._initWidgets();
+
+		children = this.childWidgets;
+
+		// We filter here because we need to track all childWidgets not just the visible ones
+		if ( this.options.onlyVisible ) {
+			children = children.filter( ":visible" );
+		}
+
+		if ( children.length ) {
+
+			// We do this last because we need to make sure all enhancment is done
+			// before determining first and last
+			$.each( [ "first", "last" ], function( index, value ) {
+				var instance = children[ value ]().data( "ui-controlgroup-data" );
+
+				if ( instance && that[ "_" + instance.widgetName + "Options" ] ) {
+					var options = that[ "_" + instance.widgetName + "Options" ](
+						children.length === 1 ? "only" : value
+					);
+					options.classes = that._resolveClassesValues( options.classes, instance );
+					instance.element[ instance.widgetName ]( options );
+				} else {
+					that._updateCornerClass( children[ value ](), value );
+				}
+			} );
+
+			// Finally call the refresh method on each of the child widgets.
+			this._callChildMethod( "refresh" );
+		}
+	}
+} );
+
+/*!
+ * jQuery UI Checkboxradio 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Checkboxradio
+//>>group: Widgets
+//>>description: Enhances a form with multiple themeable checkboxes or radio buttons.
+//>>docs: http://api.jqueryui.com/checkboxradio/
+//>>demos: http://jqueryui.com/checkboxradio/
+//>>css.structure: ../../themes/base/core.css
+//>>css.structure: ../../themes/base/button.css
+//>>css.structure: ../../themes/base/checkboxradio.css
+//>>css.theme: ../../themes/base/theme.css
+
+
+
+$.widget( "ui.checkboxradio", [ $.ui.formResetMixin, {
+	version: "1.12.1",
+	options: {
+		disabled: null,
+		label: null,
+		icon: true,
+		classes: {
+			"ui-checkboxradio-label": "ui-corner-all",
+			"ui-checkboxradio-icon": "ui-corner-all"
+		}
+	},
+
+	_getCreateOptions: function() {
+		var disabled, labels;
+		var that = this;
+		var options = this._super() || {};
+
+		// We read the type here, because it makes more sense to throw a element type error first,
+		// rather then the error for lack of a label. Often if its the wrong type, it
+		// won't have a label (e.g. calling on a div, btn, etc)
+		this._readType();
+
+		labels = this.element.labels();
+
+		// If there are multiple labels, use the last one
+		this.label = $( labels[ labels.length - 1 ] );
+		if ( !this.label.length ) {
+			$.error( "No label found for checkboxradio widget" );
+		}
+
+		this.originalLabel = "";
+
+		// We need to get the label text but this may also need to make sure it does not contain the
+		// input itself.
+		this.label.contents().not( this.element[ 0 ] ).each( function() {
+
+			// The label contents could be text, html, or a mix. We concat each element to get a
+			// string representation of the label, without the input as part of it.
+			that.originalLabel += this.nodeType === 3 ? $( this ).text() : this.outerHTML;
+		} );
+
+		// Set the label option if we found label text
+		if ( this.originalLabel ) {
+			options.label = this.originalLabel;
+		}
+
+		disabled = this.element[ 0 ].disabled;
+		if ( disabled != null ) {
+			options.disabled = disabled;
+		}
+		return options;
+	},
+
+	_create: function() {
+		var checked = this.element[ 0 ].checked;
+
+		this._bindFormResetHandler();
+
+		if ( this.options.disabled == null ) {
+			this.options.disabled = this.element[ 0 ].disabled;
+		}
+
+		this._setOption( "disabled", this.options.disabled );
+		this._addClass( "ui-checkboxradio", "ui-helper-hidden-accessible" );
+		this._addClass( this.label, "ui-checkboxradio-label", "ui-button ui-widget" );
+
+		if ( this.type === "radio" ) {
+			this._addClass( this.label, "ui-checkboxradio-radio-label" );
+		}
+
+		if ( this.options.label && this.options.label !== this.originalLabel ) {
+			this._updateLabel();
+		} else if ( this.originalLabel ) {
+			this.options.label = this.originalLabel;
+		}
+
+		this._enhance();
+
+		if ( checked ) {
+			this._addClass( this.label, "ui-checkboxradio-checked", "ui-state-active" );
+			if ( this.icon ) {
+				this._addClass( this.icon, null, "ui-state-hover" );
+			}
+		}
+
+		this._on( {
+			change: "_toggleClasses",
+			focus: function() {
+				this._addClass( this.label, null, "ui-state-focus ui-visual-focus" );
+			},
+			blur: function() {
+				this._removeClass( this.label, null, "ui-state-focus ui-visual-focus" );
+			}
+		} );
+	},
+
+	_readType: function() {
+		var nodeName = this.element[ 0 ].nodeName.toLowerCase();
+		this.type = this.element[ 0 ].type;
+		if ( nodeName !== "input" || !/radio|checkbox/.test( this.type ) ) {
+			$.error( "Can't create checkboxradio on element.nodeName=" + nodeName +
+				" and element.type=" + this.type );
+		}
+	},
+
+	// Support jQuery Mobile enhanced option
+	_enhance: function() {
+		this._updateIcon( this.element[ 0 ].checked );
+	},
+
+	widget: function() {
+		return this.label;
+	},
+
+	_getRadioGroup: function() {
+		var group;
+		var name = this.element[ 0 ].name;
+		var nameSelector = "input[name='" + $.ui.escapeSelector( name ) + "']";
+
+		if ( !name ) {
+			return $( [] );
+		}
+
+		if ( this.form.length ) {
+			group = $( this.form[ 0 ].elements ).filter( nameSelector );
+		} else {
+
+			// Not inside a form, check all inputs that also are not inside a form
+			group = $( nameSelector ).filter( function() {
+				return $( this ).form().length === 0;
+			} );
+		}
+
+		return group.not( this.element );
+	},
+
+	_toggleClasses: function() {
+		var checked = this.element[ 0 ].checked;
+		this._toggleClass( this.label, "ui-checkboxradio-checked", "ui-state-active", checked );
+
+		if ( this.options.icon && this.type === "checkbox" ) {
+			this._toggleClass( this.icon, null, "ui-icon-check ui-state-checked", checked )
+				._toggleClass( this.icon, null, "ui-icon-blank", !checked );
+		}
+
+		if ( this.type === "radio" ) {
+			this._getRadioGroup()
+				.each( function() {
+					var instance = $( this ).checkboxradio( "instance" );
+
+					if ( instance ) {
+						instance._removeClass( instance.label,
+							"ui-checkboxradio-checked", "ui-state-active" );
+					}
+				} );
+		}
+	},
+
+	_destroy: function() {
+		this._unbindFormResetHandler();
+
+		if ( this.icon ) {
+			this.icon.remove();
+			this.iconSpace.remove();
+		}
+	},
+
+	_setOption: function( key, value ) {
+
+		// We don't allow the value to be set to nothing
+		if ( key === "label" && !value ) {
+			return;
+		}
+
+		this._super( key, value );
+
+		if ( key === "disabled" ) {
+			this._toggleClass( this.label, null, "ui-state-disabled", value );
+			this.element[ 0 ].disabled = value;
+
+			// Don't refresh when setting disabled
+			return;
+		}
+		this.refresh();
+	},
+
+	_updateIcon: function( checked ) {
+		var toAdd = "ui-icon ui-icon-background ";
+
+		if ( this.options.icon ) {
+			if ( !this.icon ) {
+				this.icon = $( "<span>" );
+				this.iconSpace = $( "<span> </span>" );
+				this._addClass( this.iconSpace, "ui-checkboxradio-icon-space" );
+			}
+
+			if ( this.type === "checkbox" ) {
+				toAdd += checked ? "ui-icon-check ui-state-checked" : "ui-icon-blank";
+				this._removeClass( this.icon, null, checked ? "ui-icon-blank" : "ui-icon-check" );
+			} else {
+				toAdd += "ui-icon-blank";
+			}
+			this._addClass( this.icon, "ui-checkboxradio-icon", toAdd );
+			if ( !checked ) {
+				this._removeClass( this.icon, null, "ui-icon-check ui-state-checked" );
+			}
+			this.icon.prependTo( this.label ).after( this.iconSpace );
+		} else if ( this.icon !== undefined ) {
+			this.icon.remove();
+			this.iconSpace.remove();
+			delete this.icon;
+		}
+	},
+
+	_updateLabel: function() {
+
+		// Remove the contents of the label ( minus the icon, icon space, and input )
+		var contents = this.label.contents().not( this.element[ 0 ] );
+		if ( this.icon ) {
+			contents = contents.not( this.icon[ 0 ] );
+		}
+		if ( this.iconSpace ) {
+			contents = contents.not( this.iconSpace[ 0 ] );
+		}
+		contents.remove();
+
+		this.label.append( this.options.label );
+	},
+
+	refresh: function() {
+		var checked = this.element[ 0 ].checked,
+			isDisabled = this.element[ 0 ].disabled;
+
+		this._updateIcon( checked );
+		this._toggleClass( this.label, "ui-checkboxradio-checked", "ui-state-active", checked );
+		if ( this.options.label !== null ) {
+			this._updateLabel();
+		}
+
+		if ( isDisabled !== this.options.disabled ) {
+			this._setOptions( { "disabled": isDisabled } );
+		}
+	}
+
+} ] );
+
+var widgetsCheckboxradio = $.ui.checkboxradio;
+
+
+/*!
+ * jQuery UI Button 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Button
+//>>group: Widgets
+//>>description: Enhances a form with themeable buttons.
+//>>docs: http://api.jqueryui.com/button/
+//>>demos: http://jqueryui.com/button/
+//>>css.structure: ../../themes/base/core.css
+//>>css.structure: ../../themes/base/button.css
+//>>css.theme: ../../themes/base/theme.css
+
+
+
+$.widget( "ui.button", {
+	version: "1.12.1",
+	defaultElement: "<button>",
+	options: {
+		classes: {
+			"ui-button": "ui-corner-all"
+		},
+		disabled: null,
+		icon: null,
+		iconPosition: "beginning",
+		label: null,
+		showLabel: true
+	},
+
+	_getCreateOptions: function() {
+		var disabled,
+
+			// This is to support cases like in jQuery Mobile where the base widget does have
+			// an implementation of _getCreateOptions
+			options = this._super() || {};
+
+		this.isInput = this.element.is( "input" );
+
+		disabled = this.element[ 0 ].disabled;
+		if ( disabled != null ) {
+			options.disabled = disabled;
+		}
+
+		this.originalLabel = this.isInput ? this.element.val() : this.element.html();
+		if ( this.originalLabel ) {
+			options.label = this.originalLabel;
+		}
+
+		return options;
+	},
+
+	_create: function() {
+		if ( !this.option.showLabel & !this.options.icon ) {
+			this.options.showLabel = true;
+		}
+
+		// We have to check the option again here even though we did in _getCreateOptions,
+		// because null may have been passed on init which would override what was set in
+		// _getCreateOptions
+		if ( this.options.disabled == null ) {
+			this.options.disabled = this.element[ 0 ].disabled || false;
+		}
+
+		this.hasTitle = !!this.element.attr( "title" );
+
+		// Check to see if the label needs to be set or if its already correct
+		if ( this.options.label && this.options.label !== this.originalLabel ) {
+			if ( this.isInput ) {
+				this.element.val( this.options.label );
+			} else {
+				this.element.html( this.options.label );
+			}
+		}
+		this._addClass( "ui-button", "ui-widget" );
+		this._setOption( "disabled", this.options.disabled );
+		this._enhance();
+
+		if ( this.element.is( "a" ) ) {
+			this._on( {
+				"keyup": function( event ) {
+					if ( event.keyCode === $.ui.keyCode.SPACE ) {
+						event.preventDefault();
+
+						// Support: PhantomJS <= 1.9, IE 8 Only
+						// If a native click is available use it so we actually cause navigation
+						// otherwise just trigger a click event
+						if ( this.element[ 0 ].click ) {
+							this.element[ 0 ].click();
+						} else {
+							this.element.trigger( "click" );
+						}
+					}
+				}
+			} );
+		}
+	},
+
+	_enhance: function() {
+		if ( !this.element.is( "button" ) ) {
+			this.element.attr( "role", "button" );
+		}
+
+		if ( this.options.icon ) {
+			this._updateIcon( "icon", this.options.icon );
+			this._updateTooltip();
+		}
+	},
+
+	_updateTooltip: function() {
+		this.title = this.element.attr( "title" );
+
+		if ( !this.options.showLabel && !this.title ) {
+			this.element.attr( "title", this.options.label );
+		}
+	},
+
+	_updateIcon: function( option, value ) {
+		var icon = option !== "iconPosition",
+			position = icon ? this.options.iconPosition : value,
+			displayBlock = position === "top" || position === "bottom";
+
+		// Create icon
+		if ( !this.icon ) {
+			this.icon = $( "<span>" );
+
+			this._addClass( this.icon, "ui-button-icon", "ui-icon" );
+
+			if ( !this.options.showLabel ) {
+				this._addClass( "ui-button-icon-only" );
+			}
+		} else if ( icon ) {
+
+			// If we are updating the icon remove the old icon class
+			this._removeClass( this.icon, null, this.options.icon );
+		}
+
+		// If we are updating the icon add the new icon class
+		if ( icon ) {
+			this._addClass( this.icon, null, value );
+		}
+
+		this._attachIcon( position );
+
+		// If the icon is on top or bottom we need to add the ui-widget-icon-block class and remove
+		// the iconSpace if there is one.
+		if ( displayBlock ) {
+			this._addClass( this.icon, null, "ui-widget-icon-block" );
+			if ( this.iconSpace ) {
+				this.iconSpace.remove();
+			}
+		} else {
+
+			// Position is beginning or end so remove the ui-widget-icon-block class and add the
+			// space if it does not exist
+			if ( !this.iconSpace ) {
+				this.iconSpace = $( "<span> </span>" );
+				this._addClass( this.iconSpace, "ui-button-icon-space" );
+			}
+			this._removeClass( this.icon, null, "ui-wiget-icon-block" );
+			this._attachIconSpace( position );
+		}
+	},
+
+	_destroy: function() {
+		this.element.removeAttr( "role" );
+
+		if ( this.icon ) {
+			this.icon.remove();
+		}
+		if ( this.iconSpace ) {
+			this.iconSpace.remove();
+		}
+		if ( !this.hasTitle ) {
+			this.element.removeAttr( "title" );
+		}
+	},
+
+	_attachIconSpace: function( iconPosition ) {
+		this.icon[ /^(?:end|bottom)/.test( iconPosition ) ? "before" : "after" ]( this.iconSpace );
+	},
+
+	_attachIcon: function( iconPosition ) {
+		this.element[ /^(?:end|bottom)/.test( iconPosition ) ? "append" : "prepend" ]( this.icon );
+	},
+
+	_setOptions: function( options ) {
+		var newShowLabel = options.showLabel === undefined ?
+				this.options.showLabel :
+				options.showLabel,
+			newIcon = options.icon === undefined ? this.options.icon : options.icon;
+
+		if ( !newShowLabel && !newIcon ) {
+			options.showLabel = true;
+		}
+		this._super( options );
+	},
+
+	_setOption: function( key, value ) {
+		if ( key === "icon" ) {
+			if ( value ) {
+				this._updateIcon( key, value );
+			} else if ( this.icon ) {
+				this.icon.remove();
+				if ( this.iconSpace ) {
+					this.iconSpace.remove();
+				}
+			}
+		}
+
+		if ( key === "iconPosition" ) {
+			this._updateIcon( key, value );
+		}
+
+		// Make sure we can't end up with a button that has neither text nor icon
+		if ( key === "showLabel" ) {
+				this._toggleClass( "ui-button-icon-only", null, !value );
+				this._updateTooltip();
+		}
+
+		if ( key === "label" ) {
+			if ( this.isInput ) {
+				this.element.val( value );
+			} else {
+
+				// If there is an icon, append it, else nothing then append the value
+				// this avoids removal of the icon when setting label text
+				this.element.html( value );
+				if ( this.icon ) {
+					this._attachIcon( this.options.iconPosition );
+					this._attachIconSpace( this.options.iconPosition );
+				}
+			}
+		}
+
+		this._super( key, value );
+
+		if ( key === "disabled" ) {
+			this._toggleClass( null, "ui-state-disabled", value );
+			this.element[ 0 ].disabled = value;
+			if ( value ) {
+				this.element.blur();
+			}
+		}
+	},
+
+	refresh: function() {
+
+		// Make sure to only check disabled if its an element that supports this otherwise
+		// check for the disabled class to determine state
+		var isDisabled = this.element.is( "input, button" ) ?
+			this.element[ 0 ].disabled : this.element.hasClass( "ui-button-disabled" );
+
+		if ( isDisabled !== this.options.disabled ) {
+			this._setOptions( { disabled: isDisabled } );
+		}
+
+		this._updateTooltip();
+	}
+} );
+
+// DEPRECATED
+if ( $.uiBackCompat !== false ) {
+
+	// Text and Icons options
+	$.widget( "ui.button", $.ui.button, {
+		options: {
+			text: true,
+			icons: {
+				primary: null,
+				secondary: null
+			}
+		},
+
+		_create: function() {
+			if ( this.options.showLabel && !this.options.text ) {
+				this.options.showLabel = this.options.text;
+			}
+			if ( !this.options.showLabel && this.options.text ) {
+				this.options.text = this.options.showLabel;
+			}
+			if ( !this.options.icon && ( this.options.icons.primary ||
+					this.options.icons.secondary ) ) {
+				if ( this.options.icons.primary ) {
+					this.options.icon = this.options.icons.primary;
+				} else {
+					this.options.icon = this.options.icons.secondary;
+					this.options.iconPosition = "end";
+				}
+			} else if ( this.options.icon ) {
+				this.options.icons.primary = this.options.icon;
+			}
+			this._super();
+		},
+
+		_setOption: function( key, value ) {
+			if ( key === "text" ) {
+				this._super( "showLabel", value );
+				return;
+			}
+			if ( key === "showLabel" ) {
+				this.options.text = value;
+			}
+			if ( key === "icon" ) {
+				this.options.icons.primary = value;
+			}
+			if ( key === "icons" ) {
+				if ( value.primary ) {
+					this._super( "icon", value.primary );
+					this._super( "iconPosition", "beginning" );
+				} else if ( value.secondary ) {
+					this._super( "icon", value.secondary );
+					this._super( "iconPosition", "end" );
+				}
+			}
+			this._superApply( arguments );
+		}
+	} );
+
+	$.fn.button = ( function( orig ) {
+		return function() {
+			if ( !this.length || ( this.length && this[ 0 ].tagName !== "INPUT" ) ||
+					( this.length && this[ 0 ].tagName === "INPUT" && (
+						this.attr( "type" ) !== "checkbox" && this.attr( "type" ) !== "radio"
+					) ) ) {
+				return orig.apply( this, arguments );
+			}
+			if ( !$.ui.checkboxradio ) {
+				$.error( "Checkboxradio widget missing" );
+			}
+			if ( arguments.length === 0 ) {
+				return this.checkboxradio( {
+					"icon": false
+				} );
+			}
+			return this.checkboxradio.apply( this, arguments );
+		};
+	} )( $.fn.button );
+
+	$.fn.buttonset = function() {
+		if ( !$.ui.controlgroup ) {
+			$.error( "Controlgroup widget missing" );
+		}
+		if ( arguments[ 0 ] === "option" && arguments[ 1 ] === "items" && arguments[ 2 ] ) {
+			return this.controlgroup.apply( this,
+				[ arguments[ 0 ], "items.button", arguments[ 2 ] ] );
+		}
+		if ( arguments[ 0 ] === "option" && arguments[ 1 ] === "items" ) {
+			return this.controlgroup.apply( this, [ arguments[ 0 ], "items.button" ] );
+		}
+		if ( typeof arguments[ 0 ] === "object" && arguments[ 0 ].items ) {
+			arguments[ 0 ].items = {
+				button: arguments[ 0 ].items
+			};
+		}
+		return this.controlgroup.apply( this, arguments );
+	};
+}
+
+var widgetsButton = $.ui.button;
+
+
+// jscs:disable maximumLineLength
+/* jscs:disable requireCamelCaseOrUpperCaseIdentifiers */
+/*!
+ * jQuery UI Datepicker 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Datepicker
+//>>group: Widgets
+//>>description: Displays a calendar from an input or inline for selecting dates.
+//>>docs: http://api.jqueryui.com/datepicker/
+//>>demos: http://jqueryui.com/datepicker/
+//>>css.structure: ../../themes/base/core.css
+//>>css.structure: ../../themes/base/datepicker.css
+//>>css.theme: ../../themes/base/theme.css
+
+
+
+$.extend( $.ui, { datepicker: { version: "1.12.1" } } );
+
+var datepicker_instActive;
+
+function datepicker_getZindex( elem ) {
+	var position, value;
+	while ( elem.length && elem[ 0 ] !== document ) {
+
+		// Ignore z-index if position is set to a value where z-index is ignored by the browser
+		// This makes behavior of this function consistent across browsers
+		// WebKit always returns auto if the element is positioned
+		position = elem.css( "position" );
+		if ( position === "absolute" || position === "relative" || position === "fixed" ) {
+
+			// IE returns 0 when zIndex is not specified
+			// other browsers return a string
+			// we ignore the case of nested elements with an explicit value of 0
+			// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
+			value = parseInt( elem.css( "zIndex" ), 10 );
+			if ( !isNaN( value ) && value !== 0 ) {
+				return value;
+			}
+		}
+		elem = elem.parent();
+	}
+
+	return 0;
+}
+/* Date picker manager.
+   Use the singleton instance of this class, $.datepicker, to interact with the date picker.
+   Settings for (groups of) date pickers are maintained in an instance object,
+   allowing multiple different settings on the same page. */
+
+function Datepicker() {
+	this._curInst = null; // The current instance in use
+	this._keyEvent = false; // If the last event was a key event
+	this._disabledInputs = []; // List of date picker inputs that have been disabled
+	this._datepickerShowing = false; // True if the popup picker is showing , false if not
+	this._inDialog = false; // True if showing within a "dialog", false if not
+	this._mainDivId = "ui-datepicker-div"; // The ID of the main datepicker division
+	this._inlineClass = "ui-datepicker-inline"; // The name of the inline marker class
+	this._appendClass = "ui-datepicker-append"; // The name of the append marker class
+	this._triggerClass = "ui-datepicker-trigger"; // The name of the trigger marker class
+	this._dialogClass = "ui-datepicker-dialog"; // The name of the dialog marker class
+	this._disableClass = "ui-datepicker-disabled"; // The name of the disabled covering marker class
+	this._unselectableClass = "ui-datepicker-unselectable"; // The name of the unselectable cell marker class
+	this._currentClass = "ui-datepicker-current-day"; // The name of the current day marker class
+	this._dayOverClass = "ui-datepicker-days-cell-over"; // The name of the day hover marker class
+	this.regional = []; // Available regional settings, indexed by language code
+	this.regional[ "" ] = { // Default regional settings
+		closeText: "Done", // Display text for close link
+		prevText: "Prev", // Display text for previous month link
+		nextText: "Next", // Display text for next month link
+		currentText: "Today", // Display text for current month link
+		monthNames: [ "January","February","March","April","May","June",
+			"July","August","September","October","November","December" ], // Names of months for drop-down and formatting
+		monthNamesShort: [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ], // For formatting
+		dayNames: [ "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday" ], // For formatting
+		dayNamesShort: [ "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" ], // For formatting
+		dayNamesMin: [ "Su","Mo","Tu","We","Th","Fr","Sa" ], // Column headings for days starting at Sunday
+		weekHeader: "Wk", // Column header for week of the year
+		dateFormat: "mm/dd/yy", // See format options on parseDate
+		firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ...
+		isRTL: false, // True if right-to-left language, false if left-to-right
+		showMonthAfterYear: false, // True if the year select precedes month, false for month then year
+		yearSuffix: "" // Additional text to append to the year in the month headers
+	};
+	this._defaults = { // Global defaults for all the date picker instances
+		showOn: "focus", // "focus" for popup on focus,
+			// "button" for trigger button, or "both" for either
+		showAnim: "fadeIn", // Name of jQuery animation for popup
+		showOptions: {}, // Options for enhanced animations
+		defaultDate: null, // Used when field is blank: actual date,
+			// +/-number for offset from today, null for today
+		appendText: "", // Display text following the input box, e.g. showing the format
+		buttonText: "...", // Text for trigger button
+		buttonImage: "", // URL for trigger button image
+		buttonImageOnly: false, // True if the image appears alone, false if it appears on a button
+		hideIfNoPrevNext: false, // True to hide next/previous month links
+			// if not applicable, false to just disable them
+		navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links
+		gotoCurrent: false, // True if today link goes back to current selection instead
+		changeMonth: false, // True if month can be selected directly, false if only prev/next
+		changeYear: false, // True if year can be selected directly, false if only prev/next
+		yearRange: "c-10:c+10", // Range of years to display in drop-down,
+			// either relative to today's year (-nn:+nn), relative to currently displayed year
+			// (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n)
+		showOtherMonths: false, // True to show dates in other months, false to leave blank
+		selectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable
+		showWeek: false, // True to show week of the year, false to not show it
+		calculateWeek: this.iso8601Week, // How to calculate the week of the year,
+			// takes a Date and returns the number of the week for it
+		shortYearCutoff: "+10", // Short year values < this are in the current century,
+			// > this are in the previous century,
+			// string value starting with "+" for current year + value
+		minDate: null, // The earliest selectable date, or null for no limit
+		maxDate: null, // The latest selectable date, or null for no limit
+		duration: "fast", // Duration of display/closure
+		beforeShowDay: null, // Function that takes a date and returns an array with
+			// [0] = true if selectable, false if not, [1] = custom CSS class name(s) or "",
+			// [2] = cell title (optional), e.g. $.datepicker.noWeekends
+		beforeShow: null, // Function that takes an input field and
+			// returns a set of custom settings for the date picker
+		onSelect: null, // Define a callback function when a date is selected
+		onChangeMonthYear: null, // Define a callback function when the month or year is changed
+		onClose: null, // Define a callback function when the datepicker is closed
+		numberOfMonths: 1, // Number of months to show at a time
+		showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0)
+		stepMonths: 1, // Number of months to step back/forward
+		stepBigMonths: 12, // Number of months to step back/forward for the big links
+		altField: "", // Selector for an alternate field to store selected dates into
+		altFormat: "", // The date format to use for the alternate field
+		constrainInput: true, // The input is constrained by the current date format
+		showButtonPanel: false, // True to show button panel, false to not show it
+		autoSize: false, // True to size the input for the date format, false to leave as is
+		disabled: false // The initial disabled state
+	};
+	$.extend( this._defaults, this.regional[ "" ] );
+	this.regional.en = $.extend( true, {}, this.regional[ "" ] );
+	this.regional[ "en-US" ] = $.extend( true, {}, this.regional.en );
+	this.dpDiv = datepicker_bindHover( $( "<div id='" + this._mainDivId + "' class='ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>" ) );
+}
+
+$.extend( Datepicker.prototype, {
+	/* Class name added to elements to indicate already configured with a date picker. */
+	markerClassName: "hasDatepicker",
+
+	//Keep track of the maximum number of rows displayed (see #7043)
+	maxRows: 4,
+
+	// TODO rename to "widget" when switching to widget factory
+	_widgetDatepicker: function() {
+		return this.dpDiv;
+	},
+
+	/* Override the default settings for all instances of the date picker.
+	 * @param  settings  object - the new settings to use as defaults (anonymous object)
+	 * @return the manager object
+	 */
+	setDefaults: function( settings ) {
+		datepicker_extendRemove( this._defaults, settings || {} );
+		return this;
+	},
+
+	/* Attach the date picker to a jQuery selection.
+	 * @param  target	element - the target input field or division or span
+	 * @param  settings  object - the new settings to use for this date picker instance (anonymous)
+	 */
+	_attachDatepicker: function( target, settings ) {
+		var nodeName, inline, inst;
+		nodeName = target.nodeName.toLowerCase();
+		inline = ( nodeName === "div" || nodeName === "span" );
+		if ( !target.id ) {
+			this.uuid += 1;
+			target.id = "dp" + this.uuid;
+		}
+		inst = this._newInst( $( target ), inline );
+		inst.settings = $.extend( {}, settings || {} );
+		if ( nodeName === "input" ) {
+			this._connectDatepicker( target, inst );
+		} else if ( inline ) {
+			this._inlineDatepicker( target, inst );
+		}
+	},
+
+	/* Create a new instance object. */
+	_newInst: function( target, inline ) {
+		var id = target[ 0 ].id.replace( /([^A-Za-z0-9_\-])/g, "\\\\$1" ); // escape jQuery meta chars
+		return { id: id, input: target, // associated target
+			selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection
+			drawMonth: 0, drawYear: 0, // month being drawn
+			inline: inline, // is datepicker inline or not
+			dpDiv: ( !inline ? this.dpDiv : // presentation div
+			datepicker_bindHover( $( "<div class='" + this._inlineClass + " ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>" ) ) ) };
+	},
+
+	/* Attach the date picker to an input field. */
+	_connectDatepicker: function( target, inst ) {
+		var input = $( target );
+		inst.append = $( [] );
+		inst.trigger = $( [] );
+		if ( input.hasClass( this.markerClassName ) ) {
+			return;
+		}
+		this._attachments( input, inst );
+		input.addClass( this.markerClassName ).on( "keydown", this._doKeyDown ).
+			on( "keypress", this._doKeyPress ).on( "keyup", this._doKeyUp );
+		this._autoSize( inst );
+		$.data( target, "datepicker", inst );
+
+		//If disabled option is true, disable the datepicker once it has been attached to the input (see ticket #5665)
+		if ( inst.settings.disabled ) {
+			this._disableDatepicker( target );
+		}
+	},
+
+	/* Make attachments based on settings. */
+	_attachments: function( input, inst ) {
+		var showOn, buttonText, buttonImage,
+			appendText = this._get( inst, "appendText" ),
+			isRTL = this._get( inst, "isRTL" );
+
+		if ( inst.append ) {
+			inst.append.remove();
+		}
+		if ( appendText ) {
+			inst.append = $( "<span class='" + this._appendClass + "'>" + appendText + "</span>" );
+			input[ isRTL ? "before" : "after" ]( inst.append );
+		}
+
+		input.off( "focus", this._showDatepicker );
+
+		if ( inst.trigger ) {
+			inst.trigger.remove();
+		}
+
+		showOn = this._get( inst, "showOn" );
+		if ( showOn === "focus" || showOn === "both" ) { // pop-up date picker when in the marked field
+			input.on( "focus", this._showDatepicker );
+		}
+		if ( showOn === "button" || showOn === "both" ) { // pop-up date picker when button clicked
+			buttonText = this._get( inst, "buttonText" );
+			buttonImage = this._get( inst, "buttonImage" );
+			inst.trigger = $( this._get( inst, "buttonImageOnly" ) ?
+				$( "<img/>" ).addClass( this._triggerClass ).
+					attr( { src: buttonImage, alt: buttonText, title: buttonText } ) :
+				$( "<button type='button'></button>" ).addClass( this._triggerClass ).
+					html( !buttonImage ? buttonText : $( "<img/>" ).attr(
+					{ src:buttonImage, alt:buttonText, title:buttonText } ) ) );
+			input[ isRTL ? "before" : "after" ]( inst.trigger );
+			inst.trigger.on( "click", function() {
+				if ( $.datepicker._datepickerShowing && $.datepicker._lastInput === input[ 0 ] ) {
+					$.datepicker._hideDatepicker();
+				} else if ( $.datepicker._datepickerShowing && $.datepicker._lastInput !== input[ 0 ] ) {
+					$.datepicker._hideDatepicker();
+					$.datepicker._showDatepicker( input[ 0 ] );
+				} else {
+					$.datepicker._showDatepicker( input[ 0 ] );
+				}
+				return false;
+			} );
+		}
+	},
+
+	/* Apply the maximum length for the date format. */
+	_autoSize: function( inst ) {
+		if ( this._get( inst, "autoSize" ) && !inst.inline ) {
+			var findMax, max, maxI, i,
+				date = new Date( 2009, 12 - 1, 20 ), // Ensure double digits
+				dateFormat = this._get( inst, "dateFormat" );
+
+			if ( dateFormat.match( /[DM]/ ) ) {
+				findMax = function( names ) {
+					max = 0;
+					maxI = 0;
+					for ( i = 0; i < names.length; i++ ) {
+						if ( names[ i ].length > max ) {
+							max = names[ i ].length;
+							maxI = i;
+						}
+					}
+					return maxI;
+				};
+				date.setMonth( findMax( this._get( inst, ( dateFormat.match( /MM/ ) ?
+					"monthNames" : "monthNamesShort" ) ) ) );
+				date.setDate( findMax( this._get( inst, ( dateFormat.match( /DD/ ) ?
+					"dayNames" : "dayNamesShort" ) ) ) + 20 - date.getDay() );
+			}
+			inst.input.attr( "size", this._formatDate( inst, date ).length );
+		}
+	},
+
+	/* Attach an inline date picker to a div. */
+	_inlineDatepicker: function( target, inst ) {
+		var divSpan = $( target );
+		if ( divSpan.hasClass( this.markerClassName ) ) {
+			return;
+		}
+		divSpan.addClass( this.markerClassName ).append( inst.dpDiv );
+		$.data( target, "datepicker", inst );
+		this._setDate( inst, this._getDefaultDate( inst ), true );
+		this._updateDatepicker( inst );
+		this._updateAlternate( inst );
+
+		//If disabled option is true, disable the datepicker before showing it (see ticket #5665)
+		if ( inst.settings.disabled ) {
+			this._disableDatepicker( target );
+		}
+
+		// Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements
+		// http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height
+		inst.dpDiv.css( "display", "block" );
+	},
+
+	/* Pop-up the date picker in a "dialog" box.
+	 * @param  input element - ignored
+	 * @param  date	string or Date - the initial date to display
+	 * @param  onSelect  function - the function to call when a date is selected
+	 * @param  settings  object - update the dialog date picker instance's settings (anonymous object)
+	 * @param  pos int[2] - coordinates for the dialog's position within the screen or
+	 *					event - with x/y coordinates or
+	 *					leave empty for default (screen centre)
+	 * @return the manager object
+	 */
+	_dialogDatepicker: function( input, date, onSelect, settings, pos ) {
+		var id, browserWidth, browserHeight, scrollX, scrollY,
+			inst = this._dialogInst; // internal instance
+
+		if ( !inst ) {
+			this.uuid += 1;
+			id = "dp" + this.uuid;
+			this._dialogInput = $( "<input type='text' id='" + id +
+				"' style='position: absolute; top: -100px; width: 0px;'/>" );
+			this._dialogInput.on( "keydown", this._doKeyDown );
+			$( "body" ).append( this._dialogInput );
+			inst = this._dialogInst = this._newInst( this._dialogInput, false );
+			inst.settings = {};
+			$.data( this._dialogInput[ 0 ], "datepicker", inst );
+		}
+		datepicker_extendRemove( inst.settings, settings || {} );
+		date = ( date && date.constructor === Date ? this._formatDate( inst, date ) : date );
+		this._dialogInput.val( date );
+
+		this._pos = ( pos ? ( pos.length ? pos : [ pos.pageX, pos.pageY ] ) : null );
+		if ( !this._pos ) {
+			browserWidth = document.documentElement.clientWidth;
+			browserHeight = document.documentElement.clientHeight;
+			scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
+			scrollY = document.documentElement.scrollTop || document.body.scrollTop;
+			this._pos = // should use actual width/height below
+				[ ( browserWidth / 2 ) - 100 + scrollX, ( browserHeight / 2 ) - 150 + scrollY ];
+		}
+
+		// Move input on screen for focus, but hidden behind dialog
+		this._dialogInput.css( "left", ( this._pos[ 0 ] + 20 ) + "px" ).css( "top", this._pos[ 1 ] + "px" );
+		inst.settings.onSelect = onSelect;
+		this._inDialog = true;
+		this.dpDiv.addClass( this._dialogClass );
+		this._showDatepicker( this._dialogInput[ 0 ] );
+		if ( $.blockUI ) {
+			$.blockUI( this.dpDiv );
+		}
+		$.data( this._dialogInput[ 0 ], "datepicker", inst );
+		return this;
+	},
+
+	/* Detach a datepicker from its control.
+	 * @param  target	element - the target input field or division or span
+	 */
+	_destroyDatepicker: function( target ) {
+		var nodeName,
+			$target = $( target ),
+			inst = $.data( target, "datepicker" );
+
+		if ( !$target.hasClass( this.markerClassName ) ) {
+			return;
+		}
+
+		nodeName = target.nodeName.toLowerCase();
+		$.removeData( target, "datepicker" );
+		if ( nodeName === "input" ) {
+			inst.append.remove();
+			inst.trigger.remove();
+			$target.removeClass( this.markerClassName ).
+				off( "focus", this._showDatepicker ).
+				off( "keydown", this._doKeyDown ).
+				off( "keypress", this._doKeyPress ).
+				off( "keyup", this._doKeyUp );
+		} else if ( nodeName === "div" || nodeName === "span" ) {
+			$target.removeClass( this.markerClassName ).empty();
+		}
+
+		if ( datepicker_instActive === inst ) {
+			datepicker_instActive = null;
+		}
+	},
+
+	/* Enable the date picker to a jQuery selection.
+	 * @param  target	element - the target input field or division or span
+	 */
+	_enableDatepicker: function( target ) {
+		var nodeName, inline,
+			$target = $( target ),
+			inst = $.data( target, "datepicker" );
+
+		if ( !$target.hasClass( this.markerClassName ) ) {
+			return;
+		}
+
+		nodeName = target.nodeName.toLowerCase();
+		if ( nodeName === "input" ) {
+			target.disabled = false;
+			inst.trigger.filter( "button" ).
+				each( function() { this.disabled = false; } ).end().
+				filter( "img" ).css( { opacity: "1.0", cursor: "" } );
+		} else if ( nodeName === "div" || nodeName === "span" ) {
+			inline = $target.children( "." + this._inlineClass );
+			inline.children().removeClass( "ui-state-disabled" );
+			inline.find( "select.ui-datepicker-month, select.ui-datepicker-year" ).
+				prop( "disabled", false );
+		}
+		this._disabledInputs = $.map( this._disabledInputs,
+			function( value ) { return ( value === target ? null : value ); } ); // delete entry
+	},
+
+	/* Disable the date picker to a jQuery selection.
+	 * @param  target	element - the target input field or division or span
+	 */
+	_disableDatepicker: function( target ) {
+		var nodeName, inline,
+			$target = $( target ),
+			inst = $.data( target, "datepicker" );
+
+		if ( !$target.hasClass( this.markerClassName ) ) {
+			return;
+		}
+
+		nodeName = target.nodeName.toLowerCase();
+		if ( nodeName === "input" ) {
+			target.disabled = true;
+			inst.trigger.filter( "button" ).
+				each( function() { this.disabled = true; } ).end().
+				filter( "img" ).css( { opacity: "0.5", cursor: "default" } );
+		} else if ( nodeName === "div" || nodeName === "span" ) {
+			inline = $target.children( "." + this._inlineClass );
+			inline.children().addClass( "ui-state-disabled" );
+			inline.find( "select.ui-datepicker-month, select.ui-datepicker-year" ).
+				prop( "disabled", true );
+		}
+		this._disabledInputs = $.map( this._disabledInputs,
+			function( value ) { return ( value === target ? null : value ); } ); // delete entry
+		this._disabledInputs[ this._disabledInputs.length ] = target;
+	},
+
+	/* Is the first field in a jQuery collection disabled as a datepicker?
+	 * @param  target	element - the target input field or division or span
+	 * @return boolean - true if disabled, false if enabled
+	 */
+	_isDisabledDatepicker: function( target ) {
+		if ( !target ) {
+			return false;
+		}
+		for ( var i = 0; i < this._disabledInputs.length; i++ ) {
+			if ( this._disabledInputs[ i ] === target ) {
+				return true;
+			}
+		}
+		return false;
+	},
+
+	/* Retrieve the instance data for the target control.
+	 * @param  target  element - the target input field or division or span
+	 * @return  object - the associated instance data
+	 * @throws  error if a jQuery problem getting data
+	 */
+	_getInst: function( target ) {
+		try {
+			return $.data( target, "datepicker" );
+		}
+		catch ( err ) {
+			throw "Missing instance data for this datepicker";
+		}
+	},
+
+	/* Update or retrieve the settings for a date picker attached to an input field or division.
+	 * @param  target  element - the target input field or division or span
+	 * @param  name	object - the new settings to update or
+	 *				string - the name of the setting to change or retrieve,
+	 *				when retrieving also "all" for all instance settings or
+	 *				"defaults" for all global defaults
+	 * @param  value   any - the new value for the setting
+	 *				(omit if above is an object or to retrieve a value)
+	 */
+	_optionDatepicker: function( target, name, value ) {
+		var settings, date, minDate, maxDate,
+			inst = this._getInst( target );
+
+		if ( arguments.length === 2 && typeof name === "string" ) {
+			return ( name === "defaults" ? $.extend( {}, $.datepicker._defaults ) :
+				( inst ? ( name === "all" ? $.extend( {}, inst.settings ) :
+				this._get( inst, name ) ) : null ) );
+		}
+
+		settings = name || {};
+		if ( typeof name === "string" ) {
+			settings = {};
+			settings[ name ] = value;
+		}
+
+		if ( inst ) {
+			if ( this._curInst === inst ) {
+				this._hideDatepicker();
+			}
+
+			date = this._getDateDatepicker( target, true );
+			minDate = this._getMinMaxDate( inst, "min" );
+			maxDate = this._getMinMaxDate( inst, "max" );
+			datepicker_extendRemove( inst.settings, settings );
+
+			// reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided
+			if ( minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined ) {
+				inst.settings.minDate = this._formatDate( inst, minDate );
+			}
+			if ( maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined ) {
+				inst.settings.maxDate = this._formatDate( inst, maxDate );
+			}
+			if ( "disabled" in settings ) {
+				if ( settings.disabled ) {
+					this._disableDatepicker( target );
+				} else {
+					this._enableDatepicker( target );
+				}
+			}
+			this._attachments( $( target ), inst );
+			this._autoSize( inst );
+			this._setDate( inst, date );
+			this._updateAlternate( inst );
+			this._updateDatepicker( inst );
+		}
+	},
+
+	// Change method deprecated
+	_changeDatepicker: function( target, name, value ) {
+		this._optionDatepicker( target, name, value );
+	},
+
+	/* Redraw the date picker attached to an input field or division.
+	 * @param  target  element - the target input field or division or span
+	 */
+	_refreshDatepicker: function( target ) {
+		var inst = this._getInst( target );
+		if ( inst ) {
+			this._updateDatepicker( inst );
+		}
+	},
+
+	/* Set the dates for a jQuery selection.
+	 * @param  target element - the target input field or division or span
+	 * @param  date	Date - the new date
+	 */
+	_setDateDatepicker: function( target, date ) {
+		var inst = this._getInst( target );
+		if ( inst ) {
+			this._setDate( inst, date );
+			this._updateDatepicker( inst );
+			this._updateAlternate( inst );
+		}
+	},
+
+	/* Get the date(s) for the first entry in a jQuery selection.
+	 * @param  target element - the target input field or division or span
+	 * @param  noDefault boolean - true if no default date is to be used
+	 * @return Date - the current date
+	 */
+	_getDateDatepicker: function( target, noDefault ) {
+		var inst = this._getInst( target );
+		if ( inst && !inst.inline ) {
+			this._setDateFromField( inst, noDefault );
+		}
+		return ( inst ? this._getDate( inst ) : null );
+	},
+
+	/* Handle keystrokes. */
+	_doKeyDown: function( event ) {
+		var onSelect, dateStr, sel,
+			inst = $.datepicker._getInst( event.target ),
+			handled = true,
+			isRTL = inst.dpDiv.is( ".ui-datepicker-rtl" );
+
+		inst._keyEvent = true;
+		if ( $.datepicker._datepickerShowing ) {
+			switch ( event.keyCode ) {
+				case 9: $.datepicker._hideDatepicker();
+						handled = false;
+						break; // hide on tab out
+				case 13: sel = $( "td." + $.datepicker._dayOverClass + ":not(." +
+									$.datepicker._currentClass + ")", inst.dpDiv );
+						if ( sel[ 0 ] ) {
+							$.datepicker._selectDay( event.target, inst.selectedMonth, inst.selectedYear, sel[ 0 ] );
+						}
+
+						onSelect = $.datepicker._get( inst, "onSelect" );
+						if ( onSelect ) {
+							dateStr = $.datepicker._formatDate( inst );
+
+							// Trigger custom callback
+							onSelect.apply( ( inst.input ? inst.input[ 0 ] : null ), [ dateStr, inst ] );
+						} else {
+							$.datepicker._hideDatepicker();
+						}
+
+						return false; // don't submit the form
+				case 27: $.datepicker._hideDatepicker();
+						break; // hide on escape
+				case 33: $.datepicker._adjustDate( event.target, ( event.ctrlKey ?
+							-$.datepicker._get( inst, "stepBigMonths" ) :
+							-$.datepicker._get( inst, "stepMonths" ) ), "M" );
+						break; // previous month/year on page up/+ ctrl
+				case 34: $.datepicker._adjustDate( event.target, ( event.ctrlKey ?
+							+$.datepicker._get( inst, "stepBigMonths" ) :
+							+$.datepicker._get( inst, "stepMonths" ) ), "M" );
+						break; // next month/year on page down/+ ctrl
+				case 35: if ( event.ctrlKey || event.metaKey ) {
+							$.datepicker._clearDate( event.target );
+						}
+						handled = event.ctrlKey || event.metaKey;
+						break; // clear on ctrl or command +end
+				case 36: if ( event.ctrlKey || event.metaKey ) {
+							$.datepicker._gotoToday( event.target );
+						}
+						handled = event.ctrlKey || event.metaKey;
+						break; // current on ctrl or command +home
+				case 37: if ( event.ctrlKey || event.metaKey ) {
+							$.datepicker._adjustDate( event.target, ( isRTL ? +1 : -1 ), "D" );
+						}
+						handled = event.ctrlKey || event.metaKey;
+
+						// -1 day on ctrl or command +left
+						if ( event.originalEvent.altKey ) {
+							$.datepicker._adjustDate( event.target, ( event.ctrlKey ?
+								-$.datepicker._get( inst, "stepBigMonths" ) :
+								-$.datepicker._get( inst, "stepMonths" ) ), "M" );
+						}
+
+						// next month/year on alt +left on Mac
+						break;
+				case 38: if ( event.ctrlKey || event.metaKey ) {
+							$.datepicker._adjustDate( event.target, -7, "D" );
+						}
+						handled = event.ctrlKey || event.metaKey;
+						break; // -1 week on ctrl or command +up
+				case 39: if ( event.ctrlKey || event.metaKey ) {
+							$.datepicker._adjustDate( event.target, ( isRTL ? -1 : +1 ), "D" );
+						}
+						handled = event.ctrlKey || event.metaKey;
+
+						// +1 day on ctrl or command +right
+						if ( event.originalEvent.altKey ) {
+							$.datepicker._adjustDate( event.target, ( event.ctrlKey ?
+								+$.datepicker._get( inst, "stepBigMonths" ) :
+								+$.datepicker._get( inst, "stepMonths" ) ), "M" );
+						}
+
+						// next month/year on alt +right
+						break;
+				case 40: if ( event.ctrlKey || event.metaKey ) {
+							$.datepicker._adjustDate( event.target, +7, "D" );
+						}
+						handled = event.ctrlKey || event.metaKey;
+						break; // +1 week on ctrl or command +down
+				default: handled = false;
+			}
+		} else if ( event.keyCode === 36 && event.ctrlKey ) { // display the date picker on ctrl+home
+			$.datepicker._showDatepicker( this );
+		} else {
+			handled = false;
+		}
+
+		if ( handled ) {
+			event.preventDefault();
+			event.stopPropagation();
+		}
+	},
+
+	/* Filter entered characters - based on date format. */
+	_doKeyPress: function( event ) {
+		var chars, chr,
+			inst = $.datepicker._getInst( event.target );
+
+		if ( $.datepicker._get( inst, "constrainInput" ) ) {
+			chars = $.datepicker._possibleChars( $.datepicker._get( inst, "dateFormat" ) );
+			chr = String.fromCharCode( event.charCode == null ? event.keyCode : event.charCode );
+			return event.ctrlKey || event.metaKey || ( chr < " " || !chars || chars.indexOf( chr ) > -1 );
+		}
+	},
+
+	/* Synchronise manual entry and field/alternate field. */
+	_doKeyUp: function( event ) {
+		var date,
+			inst = $.datepicker._getInst( event.target );
+
+		if ( inst.input.val() !== inst.lastVal ) {
+			try {
+				date = $.datepicker.parseDate( $.datepicker._get( inst, "dateFormat" ),
+					( inst.input ? inst.input.val() : null ),
+					$.datepicker._getFormatConfig( inst ) );
+
+				if ( date ) { // only if valid
+					$.datepicker._setDateFromField( inst );
+					$.datepicker._updateAlternate( inst );
+					$.datepicker._updateDatepicker( inst );
+				}
+			}
+			catch ( err ) {
+			}
+		}
+		return true;
+	},
+
+	/* Pop-up the date picker for a given input field.
+	 * If false returned from beforeShow event handler do not show.
+	 * @param  input  element - the input field attached to the date picker or
+	 *					event - if triggered by focus
+	 */
+	_showDatepicker: function( input ) {
+		input = input.target || input;
+		if ( input.nodeName.toLowerCase() !== "input" ) { // find from button/image trigger
+			input = $( "input", input.parentNode )[ 0 ];
+		}
+
+		if ( $.datepicker._isDisabledDatepicker( input ) || $.datepicker._lastInput === input ) { // already here
+			return;
+		}
+
+		var inst, beforeShow, beforeShowSettings, isFixed,
+			offset, showAnim, duration;
+
+		inst = $.datepicker._getInst( input );
+		if ( $.datepicker._curInst && $.datepicker._curInst !== inst ) {
+			$.datepicker._curInst.dpDiv.stop( true, true );
+			if ( inst && $.datepicker._datepickerShowing ) {
+				$.datepicker._hideDatepicker( $.datepicker._curInst.input[ 0 ] );
+			}
+		}
+
+		beforeShow = $.datepicker._get( inst, "beforeShow" );
+		beforeShowSettings = beforeShow ? beforeShow.apply( input, [ input, inst ] ) : {};
+		if ( beforeShowSettings === false ) {
+			return;
+		}
+		datepicker_extendRemove( inst.settings, beforeShowSettings );
+
+		inst.lastVal = null;
+		$.datepicker._lastInput = input;
+		$.datepicker._setDateFromField( inst );
+
+		if ( $.datepicker._inDialog ) { // hide cursor
+			input.value = "";
+		}
+		if ( !$.datepicker._pos ) { // position below input
+			$.datepicker._pos = $.datepicker._findPos( input );
+			$.datepicker._pos[ 1 ] += input.offsetHeight; // add the height
+		}
+
+		isFixed = false;
+		$( input ).parents().each( function() {
+			isFixed |= $( this ).css( "position" ) === "fixed";
+			return !isFixed;
+		} );
+
+		offset = { left: $.datepicker._pos[ 0 ], top: $.datepicker._pos[ 1 ] };
+		$.datepicker._pos = null;
+
+		//to avoid flashes on Firefox
+		inst.dpDiv.empty();
+
+		// determine sizing offscreen
+		inst.dpDiv.css( { position: "absolute", display: "block", top: "-1000px" } );
+		$.datepicker._updateDatepicker( inst );
+
+		// fix width for dynamic number of date pickers
+		// and adjust position before showing
+		offset = $.datepicker._checkOffset( inst, offset, isFixed );
+		inst.dpDiv.css( { position: ( $.datepicker._inDialog && $.blockUI ?
+			"static" : ( isFixed ? "fixed" : "absolute" ) ), display: "none",
+			left: offset.left + "px", top: offset.top + "px" } );
+
+		if ( !inst.inline ) {
+			showAnim = $.datepicker._get( inst, "showAnim" );
+			duration = $.datepicker._get( inst, "duration" );
+			inst.dpDiv.css( "z-index", datepicker_getZindex( $( input ) ) + 1 );
+			$.datepicker._datepickerShowing = true;
+
+			if ( $.effects && $.effects.effect[ showAnim ] ) {
+				inst.dpDiv.show( showAnim, $.datepicker._get( inst, "showOptions" ), duration );
+			} else {
+				inst.dpDiv[ showAnim || "show" ]( showAnim ? duration : null );
+			}
+
+			if ( $.datepicker._shouldFocusInput( inst ) ) {
+				inst.input.trigger( "focus" );
+			}
+
+			$.datepicker._curInst = inst;
+		}
+	},
+
+	/* Generate the date picker content. */
+	_updateDatepicker: function( inst ) {
+		this.maxRows = 4; //Reset the max number of rows being displayed (see #7043)
+		datepicker_instActive = inst; // for delegate hover events
+		inst.dpDiv.empty().append( this._generateHTML( inst ) );
+		this._attachHandlers( inst );
+
+		var origyearshtml,
+			numMonths = this._getNumberOfMonths( inst ),
+			cols = numMonths[ 1 ],
+			width = 17,
+			activeCell = inst.dpDiv.find( "." + this._dayOverClass + " a" );
+
+		if ( activeCell.length > 0 ) {
+			datepicker_handleMouseover.apply( activeCell.get( 0 ) );
+		}
+
+		inst.dpDiv.removeClass( "ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4" ).width( "" );
+		if ( cols > 1 ) {
+			inst.dpDiv.addClass( "ui-datepicker-multi-" + cols ).css( "width", ( width * cols ) + "em" );
+		}
+		inst.dpDiv[ ( numMonths[ 0 ] !== 1 || numMonths[ 1 ] !== 1 ? "add" : "remove" ) +
+			"Class" ]( "ui-datepicker-multi" );
+		inst.dpDiv[ ( this._get( inst, "isRTL" ) ? "add" : "remove" ) +
+			"Class" ]( "ui-datepicker-rtl" );
+
+		if ( inst === $.datepicker._curInst && $.datepicker._datepickerShowing && $.datepicker._shouldFocusInput( inst ) ) {
+			inst.input.trigger( "focus" );
+		}
+
+		// Deffered render of the years select (to avoid flashes on Firefox)
+		if ( inst.yearshtml ) {
+			origyearshtml = inst.yearshtml;
+			setTimeout( function() {
+
+				//assure that inst.yearshtml didn't change.
+				if ( origyearshtml === inst.yearshtml && inst.yearshtml ) {
+					inst.dpDiv.find( "select.ui-datepicker-year:first" ).replaceWith( inst.yearshtml );
+				}
+				origyearshtml = inst.yearshtml = null;
+			}, 0 );
+		}
+	},
+
+	// #6694 - don't focus the input if it's already focused
+	// this breaks the change event in IE
+	// Support: IE and jQuery <1.9
+	_shouldFocusInput: function( inst ) {
+		return inst.input && inst.input.is( ":visible" ) && !inst.input.is( ":disabled" ) && !inst.input.is( ":focus" );
+	},
+
+	/* Check positioning to remain on screen. */
+	_checkOffset: function( inst, offset, isFixed ) {
+		var dpWidth = inst.dpDiv.outerWidth(),
+			dpHeight = inst.dpDiv.outerHeight(),
+			inputWidth = inst.input ? inst.input.outerWidth() : 0,
+			inputHeight = inst.input ? inst.input.outerHeight() : 0,
+			viewWidth = document.documentElement.clientWidth + ( isFixed ? 0 : $( document ).scrollLeft() ),
+			viewHeight = document.documentElement.clientHeight + ( isFixed ? 0 : $( document ).scrollTop() );
+
+		offset.left -= ( this._get( inst, "isRTL" ) ? ( dpWidth - inputWidth ) : 0 );
+		offset.left -= ( isFixed && offset.left === inst.input.offset().left ) ? $( document ).scrollLeft() : 0;
+		offset.top -= ( isFixed && offset.top === ( inst.input.offset().top + inputHeight ) ) ? $( document ).scrollTop() : 0;
+
+		// Now check if datepicker is showing outside window viewport - move to a better place if so.
+		offset.left -= Math.min( offset.left, ( offset.left + dpWidth > viewWidth && viewWidth > dpWidth ) ?
+			Math.abs( offset.left + dpWidth - viewWidth ) : 0 );
+		offset.top -= Math.min( offset.top, ( offset.top + dpHeight > viewHeight && viewHeight > dpHeight ) ?
+			Math.abs( dpHeight + inputHeight ) : 0 );
+
+		return offset;
+	},
+
+	/* Find an object's position on the screen. */
+	_findPos: function( obj ) {
+		var position,
+			inst = this._getInst( obj ),
+			isRTL = this._get( inst, "isRTL" );
+
+		while ( obj && ( obj.type === "hidden" || obj.nodeType !== 1 || $.expr.filters.hidden( obj ) ) ) {
+			obj = obj[ isRTL ? "previousSibling" : "nextSibling" ];
+		}
+
+		position = $( obj ).offset();
+		return [ position.left, position.top ];
+	},
+
+	/* Hide the date picker from view.
+	 * @param  input  element - the input field attached to the date picker
+	 */
+	_hideDatepicker: function( input ) {
+		var showAnim, duration, postProcess, onClose,
+			inst = this._curInst;
+
+		if ( !inst || ( input && inst !== $.data( input, "datepicker" ) ) ) {
+			return;
+		}
+
+		if ( this._datepickerShowing ) {
+			showAnim = this._get( inst, "showAnim" );
+			duration = this._get( inst, "duration" );
+			postProcess = function() {
+				$.datepicker._tidyDialog( inst );
+			};
+
+			// DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed
+			if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) {
+				inst.dpDiv.hide( showAnim, $.datepicker._get( inst, "showOptions" ), duration, postProcess );
+			} else {
+				inst.dpDiv[ ( showAnim === "slideDown" ? "slideUp" :
+					( showAnim === "fadeIn" ? "fadeOut" : "hide" ) ) ]( ( showAnim ? duration : null ), postProcess );
+			}
+
+			if ( !showAnim ) {
+				postProcess();
+			}
+			this._datepickerShowing = false;
+
+			onClose = this._get( inst, "onClose" );
+			if ( onClose ) {
+				onClose.apply( ( inst.input ? inst.input[ 0 ] : null ), [ ( inst.input ? inst.input.val() : "" ), inst ] );
+			}
+
+			this._lastInput = null;
+			if ( this._inDialog ) {
+				this._dialogInput.css( { position: "absolute", left: "0", top: "-100px" } );
+				if ( $.blockUI ) {
+					$.unblockUI();
+					$( "body" ).append( this.dpDiv );
+				}
+			}
+			this._inDialog = false;
+		}
+	},
+
+	/* Tidy up after a dialog display. */
+	_tidyDialog: function( inst ) {
+		inst.dpDiv.removeClass( this._dialogClass ).off( ".ui-datepicker-calendar" );
+	},
+
+	/* Close date picker if clicked elsewhere. */
+	_checkExternalClick: function( event ) {
+		if ( !$.datepicker._curInst ) {
+			return;
+		}
+
+		var $target = $( event.target ),
+			inst = $.datepicker._getInst( $target[ 0 ] );
+
+		if ( ( ( $target[ 0 ].id !== $.datepicker._mainDivId &&
+				$target.parents( "#" + $.datepicker._mainDivId ).length === 0 &&
+				!$target.hasClass( $.datepicker.markerClassName ) &&
+				!$target.closest( "." + $.datepicker._triggerClass ).length &&
+				$.datepicker._datepickerShowing && !( $.datepicker._inDialog && $.blockUI ) ) ) ||
+			( $target.hasClass( $.datepicker.markerClassName ) && $.datepicker._curInst !== inst ) ) {
+				$.datepicker._hideDatepicker();
+		}
+	},
+
+	/* Adjust one of the date sub-fields. */
+	_adjustDate: function( id, offset, period ) {
+		var target = $( id ),
+			inst = this._getInst( target[ 0 ] );
+
+		if ( this._isDisabledDatepicker( target[ 0 ] ) ) {
+			return;
+		}
+		this._adjustInstDate( inst, offset +
+			( period === "M" ? this._get( inst, "showCurrentAtPos" ) : 0 ), // undo positioning
+			period );
+		this._updateDatepicker( inst );
+	},
+
+	/* Action for current link. */
+	_gotoToday: function( id ) {
+		var date,
+			target = $( id ),
+			inst = this._getInst( target[ 0 ] );
+
+		if ( this._get( inst, "gotoCurrent" ) && inst.currentDay ) {
+			inst.selectedDay = inst.currentDay;
+			inst.drawMonth = inst.selectedMonth = inst.currentMonth;
+			inst.drawYear = inst.selectedYear = inst.currentYear;
+		} else {
+			date = new Date();
+			inst.selectedDay = date.getDate();
+			inst.drawMonth = inst.selectedMonth = date.getMonth();
+			inst.drawYear = inst.selectedYear = date.getFullYear();
+		}
+		this._notifyChange( inst );
+		this._adjustDate( target );
+	},
+
+	/* Action for selecting a new month/year. */
+	_selectMonthYear: function( id, select, period ) {
+		var target = $( id ),
+			inst = this._getInst( target[ 0 ] );
+
+		inst[ "selected" + ( period === "M" ? "Month" : "Year" ) ] =
+		inst[ "draw" + ( period === "M" ? "Month" : "Year" ) ] =
+			parseInt( select.options[ select.selectedIndex ].value, 10 );
+
+		this._notifyChange( inst );
+		this._adjustDate( target );
+	},
+
+	/* Action for selecting a day. */
+	_selectDay: function( id, month, year, td ) {
+		var inst,
+			target = $( id );
+
+		if ( $( td ).hasClass( this._unselectableClass ) || this._isDisabledDatepicker( target[ 0 ] ) ) {
+			return;
+		}
+
+		inst = this._getInst( target[ 0 ] );
+		inst.selectedDay = inst.currentDay = $( "a", td ).html();
+		inst.selectedMonth = inst.currentMonth = month;
+		inst.selectedYear = inst.currentYear = year;
+		this._selectDate( id, this._formatDate( inst,
+			inst.currentDay, inst.currentMonth, inst.currentYear ) );
+	},
+
+	/* Erase the input field and hide the date picker. */
+	_clearDate: function( id ) {
+		var target = $( id );
+		this._selectDate( target, "" );
+	},
+
+	/* Update the input field with the selected date. */
+	_selectDate: function( id, dateStr ) {
+		var onSelect,
+			target = $( id ),
+			inst = this._getInst( target[ 0 ] );
+
+		dateStr = ( dateStr != null ? dateStr : this._formatDate( inst ) );
+		if ( inst.input ) {
+			inst.input.val( dateStr );
+		}
+		this._updateAlternate( inst );
+
+		onSelect = this._get( inst, "onSelect" );
+		if ( onSelect ) {
+			onSelect.apply( ( inst.input ? inst.input[ 0 ] : null ), [ dateStr, inst ] );  // trigger custom callback
+		} else if ( inst.input ) {
+			inst.input.trigger( "change" ); // fire the change event
+		}
+
+		if ( inst.inline ) {
+			this._updateDatepicker( inst );
+		} else {
+			this._hideDatepicker();
+			this._lastInput = inst.input[ 0 ];
+			if ( typeof( inst.input[ 0 ] ) !== "object" ) {
+				inst.input.trigger( "focus" ); // restore focus
+			}
+			this._lastInput = null;
+		}
+	},
+
+	/* Update any alternate field to synchronise with the main field. */
+	_updateAlternate: function( inst ) {
+		var altFormat, date, dateStr,
+			altField = this._get( inst, "altField" );
+
+		if ( altField ) { // update alternate field too
+			altFormat = this._get( inst, "altFormat" ) || this._get( inst, "dateFormat" );
+			date = this._getDate( inst );
+			dateStr = this.formatDate( altFormat, date, this._getFormatConfig( inst ) );
+			$( altField ).val( dateStr );
+		}
+	},
+
+	/* Set as beforeShowDay function to prevent selection of weekends.
+	 * @param  date  Date - the date to customise
+	 * @return [boolean, string] - is this date selectable?, what is its CSS class?
+	 */
+	noWeekends: function( date ) {
+		var day = date.getDay();
+		return [ ( day > 0 && day < 6 ), "" ];
+	},
+
+	/* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
+	 * @param  date  Date - the date to get the week for
+	 * @return  number - the number of the week within the year that contains this date
+	 */
+	iso8601Week: function( date ) {
+		var time,
+			checkDate = new Date( date.getTime() );
+
+		// Find Thursday of this week starting on Monday
+		checkDate.setDate( checkDate.getDate() + 4 - ( checkDate.getDay() || 7 ) );
+
+		time = checkDate.getTime();
+		checkDate.setMonth( 0 ); // Compare with Jan 1
+		checkDate.setDate( 1 );
+		return Math.floor( Math.round( ( time - checkDate ) / 86400000 ) / 7 ) + 1;
+	},
+
+	/* Parse a string value into a date object.
+	 * See formatDate below for the possible formats.
+	 *
+	 * @param  format string - the expected format of the date
+	 * @param  value string - the date in the above format
+	 * @param  settings Object - attributes include:
+	 *					shortYearCutoff  number - the cutoff year for determining the century (optional)
+	 *					dayNamesShort	string[7] - abbreviated names of the days from Sunday (optional)
+	 *					dayNames		string[7] - names of the days from Sunday (optional)
+	 *					monthNamesShort string[12] - abbreviated names of the months (optional)
+	 *					monthNames		string[12] - names of the months (optional)
+	 * @return  Date - the extracted date value or null if value is blank
+	 */
+	parseDate: function( format, value, settings ) {
+		if ( format == null || value == null ) {
+			throw "Invalid arguments";
+		}
+
+		value = ( typeof value === "object" ? value.toString() : value + "" );
+		if ( value === "" ) {
+			return null;
+		}
+
+		var iFormat, dim, extra,
+			iValue = 0,
+			shortYearCutoffTemp = ( settings ? settings.shortYearCutoff : null ) || this._defaults.shortYearCutoff,
+			shortYearCutoff = ( typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp :
+				new Date().getFullYear() % 100 + parseInt( shortYearCutoffTemp, 10 ) ),
+			dayNamesShort = ( settings ? settings.dayNamesShort : null ) || this._defaults.dayNamesShort,
+			dayNames = ( settings ? settings.dayNames : null ) || this._defaults.dayNames,
+			monthNamesShort = ( settings ? settings.monthNamesShort : null ) || this._defaults.monthNamesShort,
+			monthNames = ( settings ? settings.monthNames : null ) || this._defaults.monthNames,
+			year = -1,
+			month = -1,
+			day = -1,
+			doy = -1,
+			literal = false,
+			date,
+
+			// Check whether a format character is doubled
+			lookAhead = function( match ) {
+				var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match );
+				if ( matches ) {
+					iFormat++;
+				}
+				return matches;
+			},
+
+			// Extract a number from the string value
+			getNumber = function( match ) {
+				var isDoubled = lookAhead( match ),
+					size = ( match === "@" ? 14 : ( match === "!" ? 20 :
+					( match === "y" && isDoubled ? 4 : ( match === "o" ? 3 : 2 ) ) ) ),
+					minSize = ( match === "y" ? size : 1 ),
+					digits = new RegExp( "^\\d{" + minSize + "," + size + "}" ),
+					num = value.substring( iValue ).match( digits );
+				if ( !num ) {
+					throw "Missing number at position " + iValue;
+				}
+				iValue += num[ 0 ].length;
+				return parseInt( num[ 0 ], 10 );
+			},
+
+			// Extract a name from the string value and convert to an index
+			getName = function( match, shortNames, longNames ) {
+				var index = -1,
+					names = $.map( lookAhead( match ) ? longNames : shortNames, function( v, k ) {
+						return [ [ k, v ] ];
+					} ).sort( function( a, b ) {
+						return -( a[ 1 ].length - b[ 1 ].length );
+					} );
+
+				$.each( names, function( i, pair ) {
+					var name = pair[ 1 ];
+					if ( value.substr( iValue, name.length ).toLowerCase() === name.toLowerCase() ) {
+						index = pair[ 0 ];
+						iValue += name.length;
+						return false;
+					}
+				} );
+				if ( index !== -1 ) {
+					return index + 1;
+				} else {
+					throw "Unknown name at position " + iValue;
+				}
+			},
+
+			// Confirm that a literal character matches the string value
+			checkLiteral = function() {
+				if ( value.charAt( iValue ) !== format.charAt( iFormat ) ) {
+					throw "Unexpected literal at position " + iValue;
+				}
+				iValue++;
+			};
+
+		for ( iFormat = 0; iFormat < format.length; iFormat++ ) {
+			if ( literal ) {
+				if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) {
+					literal = false;
+				} else {
+					checkLiteral();
+				}
+			} else {
+				switch ( format.charAt( iFormat ) ) {
+					case "d":
+						day = getNumber( "d" );
+						break;
+					case "D":
+						getName( "D", dayNamesShort, dayNames );
+						break;
+					case "o":
+						doy = getNumber( "o" );
+						break;
+					case "m":
+						month = getNumber( "m" );
+						break;
+					case "M":
+						month = getName( "M", monthNamesShort, monthNames );
+						break;
+					case "y":
+						year = getNumber( "y" );
+						break;
+					case "@":
+						date = new Date( getNumber( "@" ) );
+						year = date.getFullYear();
+						month = date.getMonth() + 1;
+						day = date.getDate();
+						break;
+					case "!":
+						date = new Date( ( getNumber( "!" ) - this._ticksTo1970 ) / 10000 );
+						year = date.getFullYear();
+						month = date.getMonth() + 1;
+						day = date.getDate();
+						break;
+					case "'":
+						if ( lookAhead( "'" ) ) {
+							checkLiteral();
+						} else {
+							literal = true;
+						}
+						break;
+					default:
+						checkLiteral();
+				}
+			}
+		}
+
+		if ( iValue < value.length ) {
+			extra = value.substr( iValue );
+			if ( !/^\s+/.test( extra ) ) {
+				throw "Extra/unparsed characters found in date: " + extra;
+			}
+		}
+
+		if ( year === -1 ) {
+			year = new Date().getFullYear();
+		} else if ( year < 100 ) {
+			year += new Date().getFullYear() - new Date().getFullYear() % 100 +
+				( year <= shortYearCutoff ? 0 : -100 );
+		}
+
+		if ( doy > -1 ) {
+			month = 1;
+			day = doy;
+			do {
+				dim = this._getDaysInMonth( year, month - 1 );
+				if ( day <= dim ) {
+					break;
+				}
+				month++;
+				day -= dim;
+			} while ( true );
+		}
+
+		date = this._daylightSavingAdjust( new Date( year, month - 1, day ) );
+		if ( date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day ) {
+			throw "Invalid date"; // E.g. 31/02/00
+		}
+		return date;
+	},
+
+	/* Standard date formats. */
+	ATOM: "yy-mm-dd", // RFC 3339 (ISO 8601)
+	COOKIE: "D, dd M yy",
+	ISO_8601: "yy-mm-dd",
+	RFC_822: "D, d M y",
+	RFC_850: "DD, dd-M-y",
+	RFC_1036: "D, d M y",
+	RFC_1123: "D, d M yy",
+	RFC_2822: "D, d M yy",
+	RSS: "D, d M y", // RFC 822
+	TICKS: "!",
+	TIMESTAMP: "@",
+	W3C: "yy-mm-dd", // ISO 8601
+
+	_ticksTo1970: ( ( ( 1970 - 1 ) * 365 + Math.floor( 1970 / 4 ) - Math.floor( 1970 / 100 ) +
+		Math.floor( 1970 / 400 ) ) * 24 * 60 * 60 * 10000000 ),
+
+	/* Format a date object into a string value.
+	 * The format can be combinations of the following:
+	 * d  - day of month (no leading zero)
+	 * dd - day of month (two digit)
+	 * o  - day of year (no leading zeros)
+	 * oo - day of year (three digit)
+	 * D  - day name short
+	 * DD - day name long
+	 * m  - month of year (no leading zero)
+	 * mm - month of year (two digit)
+	 * M  - month name short
+	 * MM - month name long
+	 * y  - year (two digit)
+	 * yy - year (four digit)
+	 * @ - Unix timestamp (ms since 01/01/1970)
+	 * ! - Windows ticks (100ns since 01/01/0001)
+	 * "..." - literal text
+	 * '' - single quote
+	 *
+	 * @param  format string - the desired format of the date
+	 * @param  date Date - the date value to format
+	 * @param  settings Object - attributes include:
+	 *					dayNamesShort	string[7] - abbreviated names of the days from Sunday (optional)
+	 *					dayNames		string[7] - names of the days from Sunday (optional)
+	 *					monthNamesShort string[12] - abbreviated names of the months (optional)
+	 *					monthNames		string[12] - names of the months (optional)
+	 * @return  string - the date in the above format
+	 */
+	formatDate: function( format, date, settings ) {
+		if ( !date ) {
+			return "";
+		}
+
+		var iFormat,
+			dayNamesShort = ( settings ? settings.dayNamesShort : null ) || this._defaults.dayNamesShort,
+			dayNames = ( settings ? settings.dayNames : null ) || this._defaults.dayNames,
+			monthNamesShort = ( settings ? settings.monthNamesShort : null ) || this._defaults.monthNamesShort,
+			monthNames = ( settings ? settings.monthNames : null ) || this._defaults.monthNames,
+
+			// Check whether a format character is doubled
+			lookAhead = function( match ) {
+				var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match );
+				if ( matches ) {
+					iFormat++;
+				}
+				return matches;
+			},
+
+			// Format a number, with leading zero if necessary
+			formatNumber = function( match, value, len ) {
+				var num = "" + value;
+				if ( lookAhead( match ) ) {
+					while ( num.length < len ) {
+						num = "0" + num;
+					}
+				}
+				return num;
+			},
+
+			// Format a name, short or long as requested
+			formatName = function( match, value, shortNames, longNames ) {
+				return ( lookAhead( match ) ? longNames[ value ] : shortNames[ value ] );
+			},
+			output = "",
+			literal = false;
+
+		if ( date ) {
+			for ( iFormat = 0; iFormat < format.length; iFormat++ ) {
+				if ( literal ) {
+					if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) {
+						literal = false;
+					} else {
+						output += format.charAt( iFormat );
+					}
+				} else {
+					switch ( format.charAt( iFormat ) ) {
+						case "d":
+							output += formatNumber( "d", date.getDate(), 2 );
+							break;
+						case "D":
+							output += formatName( "D", date.getDay(), dayNamesShort, dayNames );
+							break;
+						case "o":
+							output += formatNumber( "o",
+								Math.round( ( new Date( date.getFullYear(), date.getMonth(), date.getDate() ).getTime() - new Date( date.getFullYear(), 0, 0 ).getTime() ) / 86400000 ), 3 );
+							break;
+						case "m":
+							output += formatNumber( "m", date.getMonth() + 1, 2 );
+							break;
+						case "M":
+							output += formatName( "M", date.getMonth(), monthNamesShort, monthNames );
+							break;
+						case "y":
+							output += ( lookAhead( "y" ) ? date.getFullYear() :
+								( date.getFullYear() % 100 < 10 ? "0" : "" ) + date.getFullYear() % 100 );
+							break;
+						case "@":
+							output += date.getTime();
+							break;
+						case "!":
+							output += date.getTime() * 10000 + this._ticksTo1970;
+							break;
+						case "'":
+							if ( lookAhead( "'" ) ) {
+								output += "'";
+							} else {
+								literal = true;
+							}
+							break;
+						default:
+							output += format.charAt( iFormat );
+					}
+				}
+			}
+		}
+		return output;
+	},
+
+	/* Extract all possible characters from the date format. */
+	_possibleChars: function( format ) {
+		var iFormat,
+			chars = "",
+			literal = false,
+
+			// Check whether a format character is doubled
+			lookAhead = function( match ) {
+				var matches = ( iFormat + 1 < format.length && format.charAt( iFormat + 1 ) === match );
+				if ( matches ) {
+					iFormat++;
+				}
+				return matches;
+			};
+
+		for ( iFormat = 0; iFormat < format.length; iFormat++ ) {
+			if ( literal ) {
+				if ( format.charAt( iFormat ) === "'" && !lookAhead( "'" ) ) {
+					literal = false;
+				} else {
+					chars += format.charAt( iFormat );
+				}
+			} else {
+				switch ( format.charAt( iFormat ) ) {
+					case "d": case "m": case "y": case "@":
+						chars += "0123456789";
+						break;
+					case "D": case "M":
+						return null; // Accept anything
+					case "'":
+						if ( lookAhead( "'" ) ) {
+							chars += "'";
+						} else {
+							literal = true;
+						}
+						break;
+					default:
+						chars += format.charAt( iFormat );
+				}
+			}
+		}
+		return chars;
+	},
+
+	/* Get a setting value, defaulting if necessary. */
+	_get: function( inst, name ) {
+		return inst.settings[ name ] !== undefined ?
+			inst.settings[ name ] : this._defaults[ name ];
+	},
+
+	/* Parse existing date and initialise date picker. */
+	_setDateFromField: function( inst, noDefault ) {
+		if ( inst.input.val() === inst.lastVal ) {
+			return;
+		}
+
+		var dateFormat = this._get( inst, "dateFormat" ),
+			dates = inst.lastVal = inst.input ? inst.input.val() : null,
+			defaultDate = this._getDefaultDate( inst ),
+			date = defaultDate,
+			settings = this._getFormatConfig( inst );
+
+		try {
+			date = this.parseDate( dateFormat, dates, settings ) || defaultDate;
+		} catch ( event ) {
+			dates = ( noDefault ? "" : dates );
+		}
+		inst.selectedDay = date.getDate();
+		inst.drawMonth = inst.selectedMonth = date.getMonth();
+		inst.drawYear = inst.selectedYear = date.getFullYear();
+		inst.currentDay = ( dates ? date.getDate() : 0 );
+		inst.currentMonth = ( dates ? date.getMonth() : 0 );
+		inst.currentYear = ( dates ? date.getFullYear() : 0 );
+		this._adjustInstDate( inst );
+	},
+
+	/* Retrieve the default date shown on opening. */
+	_getDefaultDate: function( inst ) {
+		return this._restrictMinMax( inst,
+			this._determineDate( inst, this._get( inst, "defaultDate" ), new Date() ) );
+	},
+
+	/* A date may be specified as an exact value or a relative one. */
+	_determineDate: function( inst, date, defaultDate ) {
+		var offsetNumeric = function( offset ) {
+				var date = new Date();
+				date.setDate( date.getDate() + offset );
+				return date;
+			},
+			offsetString = function( offset ) {
+				try {
+					return $.datepicker.parseDate( $.datepicker._get( inst, "dateFormat" ),
+						offset, $.datepicker._getFormatConfig( inst ) );
+				}
+				catch ( e ) {
+
+					// Ignore
+				}
+
+				var date = ( offset.toLowerCase().match( /^c/ ) ?
+					$.datepicker._getDate( inst ) : null ) || new Date(),
+					year = date.getFullYear(),
+					month = date.getMonth(),
+					day = date.getDate(),
+					pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,
+					matches = pattern.exec( offset );
+
+				while ( matches ) {
+					switch ( matches[ 2 ] || "d" ) {
+						case "d" : case "D" :
+							day += parseInt( matches[ 1 ], 10 ); break;
+						case "w" : case "W" :
+							day += parseInt( matches[ 1 ], 10 ) * 7; break;
+						case "m" : case "M" :
+							month += parseInt( matches[ 1 ], 10 );
+							day = Math.min( day, $.datepicker._getDaysInMonth( year, month ) );
+							break;
+						case "y": case "Y" :
+							year += parseInt( matches[ 1 ], 10 );
+							day = Math.min( day, $.datepicker._getDaysInMonth( year, month ) );
+							break;
+					}
+					matches = pattern.exec( offset );
+				}
+				return new Date( year, month, day );
+			},
+			newDate = ( date == null || date === "" ? defaultDate : ( typeof date === "string" ? offsetString( date ) :
+				( typeof date === "number" ? ( isNaN( date ) ? defaultDate : offsetNumeric( date ) ) : new Date( date.getTime() ) ) ) );
+
+		newDate = ( newDate && newDate.toString() === "Invalid Date" ? defaultDate : newDate );
+		if ( newDate ) {
+			newDate.setHours( 0 );
+			newDate.setMinutes( 0 );
+			newDate.setSeconds( 0 );
+			newDate.setMilliseconds( 0 );
+		}
+		return this._daylightSavingAdjust( newDate );
+	},
+
+	/* Handle switch to/from daylight saving.
+	 * Hours may be non-zero on daylight saving cut-over:
+	 * > 12 when midnight changeover, but then cannot generate
+	 * midnight datetime, so jump to 1AM, otherwise reset.
+	 * @param  date  (Date) the date to check
+	 * @return  (Date) the corrected date
+	 */
+	_daylightSavingAdjust: function( date ) {
+		if ( !date ) {
+			return null;
+		}
+		date.setHours( date.getHours() > 12 ? date.getHours() + 2 : 0 );
+		return date;
+	},
+
+	/* Set the date(s) directly. */
+	_setDate: function( inst, date, noChange ) {
+		var clear = !date,
+			origMonth = inst.selectedMonth,
+			origYear = inst.selectedYear,
+			newDate = this._restrictMinMax( inst, this._determineDate( inst, date, new Date() ) );
+
+		inst.selectedDay = inst.currentDay = newDate.getDate();
+		inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth();
+		inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear();
+		if ( ( origMonth !== inst.selectedMonth || origYear !== inst.selectedYear ) && !noChange ) {
+			this._notifyChange( inst );
+		}
+		this._adjustInstDate( inst );
+		if ( inst.input ) {
+			inst.input.val( clear ? "" : this._formatDate( inst ) );
+		}
+	},
+
+	/* Retrieve the date(s) directly. */
+	_getDate: function( inst ) {
+		var startDate = ( !inst.currentYear || ( inst.input && inst.input.val() === "" ) ? null :
+			this._daylightSavingAdjust( new Date(
+			inst.currentYear, inst.currentMonth, inst.currentDay ) ) );
+			return startDate;
+	},
+
+	/* Attach the onxxx handlers.  These are declared statically so
+	 * they work with static code transformers like Caja.
+	 */
+	_attachHandlers: function( inst ) {
+		var stepMonths = this._get( inst, "stepMonths" ),
+			id = "#" + inst.id.replace( /\\\\/g, "\\" );
+		inst.dpDiv.find( "[data-handler]" ).map( function() {
+			var handler = {
+				prev: function() {
+					$.datepicker._adjustDate( id, -stepMonths, "M" );
+				},
+				next: function() {
+					$.datepicker._adjustDate( id, +stepMonths, "M" );
+				},
+				hide: function() {
+					$.datepicker._hideDatepicker();
+				},
+				today: function() {
+					$.datepicker._gotoToday( id );
+				},
+				selectDay: function() {
+					$.datepicker._selectDay( id, +this.getAttribute( "data-month" ), +this.getAttribute( "data-year" ), this );
+					return false;
+				},
+				selectMonth: function() {
+					$.datepicker._selectMonthYear( id, this, "M" );
+					return false;
+				},
+				selectYear: function() {
+					$.datepicker._selectMonthYear( id, this, "Y" );
+					return false;
+				}
+			};
+			$( this ).on( this.getAttribute( "data-event" ), handler[ this.getAttribute( "data-handler" ) ] );
+		} );
+	},
+
+	/* Generate the HTML for the current state of the date picker. */
+	_generateHTML: function( inst ) {
+		var maxDraw, prevText, prev, nextText, next, currentText, gotoDate,
+			controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin,
+			monthNames, monthNamesShort, beforeShowDay, showOtherMonths,
+			selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate,
+			cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows,
+			printDate, dRow, tbody, daySettings, otherMonth, unselectable,
+			tempDate = new Date(),
+			today = this._daylightSavingAdjust(
+				new Date( tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate() ) ), // clear time
+			isRTL = this._get( inst, "isRTL" ),
+			showButtonPanel = this._get( inst, "showButtonPanel" ),
+			hideIfNoPrevNext = this._get( inst, "hideIfNoPrevNext" ),
+			navigationAsDateFormat = this._get( inst, "navigationAsDateFormat" ),
+			numMonths = this._getNumberOfMonths( inst ),
+			showCurrentAtPos = this._get( inst, "showCurrentAtPos" ),
+			stepMonths = this._get( inst, "stepMonths" ),
+			isMultiMonth = ( numMonths[ 0 ] !== 1 || numMonths[ 1 ] !== 1 ),
+			currentDate = this._daylightSavingAdjust( ( !inst.currentDay ? new Date( 9999, 9, 9 ) :
+				new Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) ),
+			minDate = this._getMinMaxDate( inst, "min" ),
+			maxDate = this._getMinMaxDate( inst, "max" ),
+			drawMonth = inst.drawMonth - showCurrentAtPos,
+			drawYear = inst.drawYear;
+
+		if ( drawMonth < 0 ) {
+			drawMonth += 12;
+			drawYear--;
+		}
+		if ( maxDate ) {
+			maxDraw = this._daylightSavingAdjust( new Date( maxDate.getFullYear(),
+				maxDate.getMonth() - ( numMonths[ 0 ] * numMonths[ 1 ] ) + 1, maxDate.getDate() ) );
+			maxDraw = ( minDate && maxDraw < minDate ? minDate : maxDraw );
+			while ( this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 ) ) > maxDraw ) {
+				drawMonth--;
+				if ( drawMonth < 0 ) {
+					drawMonth = 11;
+					drawYear--;
+				}
+			}
+		}
+		inst.drawMonth = drawMonth;
+		inst.drawYear = drawYear;
+
+		prevText = this._get( inst, "prevText" );
+		prevText = ( !navigationAsDateFormat ? prevText : this.formatDate( prevText,
+			this._daylightSavingAdjust( new Date( drawYear, drawMonth - stepMonths, 1 ) ),
+			this._getFormatConfig( inst ) ) );
+
+		prev = ( this._canAdjustMonth( inst, -1, drawYear, drawMonth ) ?
+			"<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click'" +
+			" title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w" ) + "'>" + prevText + "</span></a>" :
+			( hideIfNoPrevNext ? "" : "<a class='ui-datepicker-prev ui-corner-all ui-state-disabled' title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w" ) + "'>" + prevText + "</span></a>" ) );
+
+		nextText = this._get( inst, "nextText" );
+		nextText = ( !navigationAsDateFormat ? nextText : this.formatDate( nextText,
+			this._daylightSavingAdjust( new Date( drawYear, drawMonth + stepMonths, 1 ) ),
+			this._getFormatConfig( inst ) ) );
+
+		next = ( this._canAdjustMonth( inst, +1, drawYear, drawMonth ) ?
+			"<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click'" +
+			" title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e" ) + "'>" + nextText + "</span></a>" :
+			( hideIfNoPrevNext ? "" : "<a class='ui-datepicker-next ui-corner-all ui-state-disabled' title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e" ) + "'>" + nextText + "</span></a>" ) );
+
+		currentText = this._get( inst, "currentText" );
+		gotoDate = ( this._get( inst, "gotoCurrent" ) && inst.currentDay ? currentDate : today );
+		currentText = ( !navigationAsDateFormat ? currentText :
+			this.formatDate( currentText, gotoDate, this._getFormatConfig( inst ) ) );
+
+		controls = ( !inst.inline ? "<button type='button' class='ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all' data-handler='hide' data-event='click'>" +
+			this._get( inst, "closeText" ) + "</button>" : "" );
+
+		buttonPanel = ( showButtonPanel ) ? "<div class='ui-datepicker-buttonpane ui-widget-content'>" + ( isRTL ? controls : "" ) +
+			( this._isInRange( inst, gotoDate ) ? "<button type='button' class='ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all' data-handler='today' data-event='click'" +
+			">" + currentText + "</button>" : "" ) + ( isRTL ? "" : controls ) + "</div>" : "";
+
+		firstDay = parseInt( this._get( inst, "firstDay" ), 10 );
+		firstDay = ( isNaN( firstDay ) ? 0 : firstDay );
+
+		showWeek = this._get( inst, "showWeek" );
+		dayNames = this._get( inst, "dayNames" );
+		dayNamesMin = this._get( inst, "dayNamesMin" );
+		monthNames = this._get( inst, "monthNames" );
+		monthNamesShort = this._get( inst, "monthNamesShort" );
+		beforeShowDay = this._get( inst, "beforeShowDay" );
+		showOtherMonths = this._get( inst, "showOtherMonths" );
+		selectOtherMonths = this._get( inst, "selectOtherMonths" );
+		defaultDate = this._getDefaultDate( inst );
+		html = "";
+
+		for ( row = 0; row < numMonths[ 0 ]; row++ ) {
+			group = "";
+			this.maxRows = 4;
+			for ( col = 0; col < numMonths[ 1 ]; col++ ) {
+				selectedDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, inst.selectedDay ) );
+				cornerClass = " ui-corner-all";
+				calender = "";
+				if ( isMultiMonth ) {
+					calender += "<div class='ui-datepicker-group";
+					if ( numMonths[ 1 ] > 1 ) {
+						switch ( col ) {
+							case 0: calender += " ui-datepicker-group-first";
+								cornerClass = " ui-corner-" + ( isRTL ? "right" : "left" ); break;
+							case numMonths[ 1 ] - 1: calender += " ui-datepicker-group-last";
+								cornerClass = " ui-corner-" + ( isRTL ? "left" : "right" ); break;
+							default: calender += " ui-datepicker-group-middle"; cornerClass = ""; break;
+						}
+					}
+					calender += "'>";
+				}
+				calender += "<div class='ui-datepicker-header ui-widget-header ui-helper-clearfix" + cornerClass + "'>" +
+					( /all|left/.test( cornerClass ) && row === 0 ? ( isRTL ? next : prev ) : "" ) +
+					( /all|right/.test( cornerClass ) && row === 0 ? ( isRTL ? prev : next ) : "" ) +
+					this._generateMonthYearHeader( inst, drawMonth, drawYear, minDate, maxDate,
+					row > 0 || col > 0, monthNames, monthNamesShort ) + // draw month headers
+					"</div><table class='ui-datepicker-calendar'><thead>" +
+					"<tr>";
+				thead = ( showWeek ? "<th class='ui-datepicker-week-col'>" + this._get( inst, "weekHeader" ) + "</th>" : "" );
+				for ( dow = 0; dow < 7; dow++ ) { // days of the week
+					day = ( dow + firstDay ) % 7;
+					thead += "<th scope='col'" + ( ( dow + firstDay + 6 ) % 7 >= 5 ? " class='ui-datepicker-week-end'" : "" ) + ">" +
+						"<span title='" + dayNames[ day ] + "'>" + dayNamesMin[ day ] + "</span></th>";
+				}
+				calender += thead + "</tr></thead><tbody>";
+				daysInMonth = this._getDaysInMonth( drawYear, drawMonth );
+				if ( drawYear === inst.selectedYear && drawMonth === inst.selectedMonth ) {
+					inst.selectedDay = Math.min( inst.selectedDay, daysInMonth );
+				}
+				leadDays = ( this._getFirstDayOfMonth( drawYear, drawMonth ) - firstDay + 7 ) % 7;
+				curRows = Math.ceil( ( leadDays + daysInMonth ) / 7 ); // calculate the number of rows to generate
+				numRows = ( isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows ); //If multiple months, use the higher number of rows (see #7043)
+				this.maxRows = numRows;
+				printDate = this._daylightSavingAdjust( new Date( drawYear, drawMonth, 1 - leadDays ) );
+				for ( dRow = 0; dRow < numRows; dRow++ ) { // create date picker rows
+					calender += "<tr>";
+					tbody = ( !showWeek ? "" : "<td class='ui-datepicker-week-col'>" +
+						this._get( inst, "calculateWeek" )( printDate ) + "</td>" );
+					for ( dow = 0; dow < 7; dow++ ) { // create date picker days
+						daySettings = ( beforeShowDay ?
+							beforeShowDay.apply( ( inst.input ? inst.input[ 0 ] : null ), [ printDate ] ) : [ true, "" ] );
+						otherMonth = ( printDate.getMonth() !== drawMonth );
+						unselectable = ( otherMonth && !selectOtherMonths ) || !daySettings[ 0 ] ||
+							( minDate && printDate < minDate ) || ( maxDate && printDate > maxDate );
+						tbody += "<td class='" +
+							( ( dow + firstDay + 6 ) % 7 >= 5 ? " ui-datepicker-week-end" : "" ) + // highlight weekends
+							( otherMonth ? " ui-datepicker-other-month" : "" ) + // highlight days from other months
+							( ( printDate.getTime() === selectedDate.getTime() && drawMonth === inst.selectedMonth && inst._keyEvent ) || // user pressed key
+							( defaultDate.getTime() === printDate.getTime() && defaultDate.getTime() === selectedDate.getTime() ) ?
+
+							// or defaultDate is current printedDate and defaultDate is selectedDate
+							" " + this._dayOverClass : "" ) + // highlight selected day
+							( unselectable ? " " + this._unselectableClass + " ui-state-disabled" : "" ) +  // highlight unselectable days
+							( otherMonth && !showOtherMonths ? "" : " " + daySettings[ 1 ] + // highlight custom dates
+							( printDate.getTime() === currentDate.getTime() ? " " + this._currentClass : "" ) + // highlight selected day
+							( printDate.getTime() === today.getTime() ? " ui-datepicker-today" : "" ) ) + "'" + // highlight today (if different)
+							( ( !otherMonth || showOtherMonths ) && daySettings[ 2 ] ? " title='" + daySettings[ 2 ].replace( /'/g, "&#39;" ) + "'" : "" ) + // cell title
+							( unselectable ? "" : " data-handler='selectDay' data-event='click' data-month='" + printDate.getMonth() + "' data-year='" + printDate.getFullYear() + "'" ) + ">" + // actions
+							( otherMonth && !showOtherMonths ? "&#xa0;" : // display for other months
+							( unselectable ? "<span class='ui-state-default'>" + printDate.getDate() + "</span>" : "<a class='ui-state-default" +
+							( printDate.getTime() === today.getTime() ? " ui-state-highlight" : "" ) +
+							( printDate.getTime() === currentDate.getTime() ? " ui-state-active" : "" ) + // highlight selected day
+							( otherMonth ? " ui-priority-secondary" : "" ) + // distinguish dates from other months
+							"' href='#'>" + printDate.getDate() + "</a>" ) ) + "</td>"; // display selectable date
+						printDate.setDate( printDate.getDate() + 1 );
+						printDate = this._daylightSavingAdjust( printDate );
+					}
+					calender += tbody + "</tr>";
+				}
+				drawMonth++;
+				if ( drawMonth > 11 ) {
+					drawMonth = 0;
+					drawYear++;
+				}
+				calender += "</tbody></table>" + ( isMultiMonth ? "</div>" +
+							( ( numMonths[ 0 ] > 0 && col === numMonths[ 1 ] - 1 ) ? "<div class='ui-datepicker-row-break'></div>" : "" ) : "" );
+				group += calender;
+			}
+			html += group;
+		}
+		html += buttonPanel;
+		inst._keyEvent = false;
+		return html;
+	},
+
+	/* Generate the month and year header. */
+	_generateMonthYearHeader: function( inst, drawMonth, drawYear, minDate, maxDate,
+			secondary, monthNames, monthNamesShort ) {
+
+		var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear,
+			changeMonth = this._get( inst, "changeMonth" ),
+			changeYear = this._get( inst, "changeYear" ),
+			showMonthAfterYear = this._get( inst, "showMonthAfterYear" ),
+			html = "<div class='ui-datepicker-title'>",
+			monthHtml = "";
+
+		// Month selection
+		if ( secondary || !changeMonth ) {
+			monthHtml += "<span class='ui-datepicker-month'>" + monthNames[ drawMonth ] + "</span>";
+		} else {
+			inMinYear = ( minDate && minDate.getFullYear() === drawYear );
+			inMaxYear = ( maxDate && maxDate.getFullYear() === drawYear );
+			monthHtml += "<select class='ui-datepicker-month' data-handler='selectMonth' data-event='change'>";
+			for ( month = 0; month < 12; month++ ) {
+				if ( ( !inMinYear || month >= minDate.getMonth() ) && ( !inMaxYear || month <= maxDate.getMonth() ) ) {
+					monthHtml += "<option value='" + month + "'" +
+						( month === drawMonth ? " selected='selected'" : "" ) +
+						">" + monthNamesShort[ month ] + "</option>";
+				}
+			}
+			monthHtml += "</select>";
+		}
+
+		if ( !showMonthAfterYear ) {
+			html += monthHtml + ( secondary || !( changeMonth && changeYear ) ? "&#xa0;" : "" );
+		}
+
+		// Year selection
+		if ( !inst.yearshtml ) {
+			inst.yearshtml = "";
+			if ( secondary || !changeYear ) {
+				html += "<span class='ui-datepicker-year'>" + drawYear + "</span>";
+			} else {
+
+				// determine range of years to display
+				years = this._get( inst, "yearRange" ).split( ":" );
+				thisYear = new Date().getFullYear();
+				determineYear = function( value ) {
+					var year = ( value.match( /c[+\-].*/ ) ? drawYear + parseInt( value.substring( 1 ), 10 ) :
+						( value.match( /[+\-].*/ ) ? thisYear + parseInt( value, 10 ) :
+						parseInt( value, 10 ) ) );
+					return ( isNaN( year ) ? thisYear : year );
+				};
+				year = determineYear( years[ 0 ] );
+				endYear = Math.max( year, determineYear( years[ 1 ] || "" ) );
+				year = ( minDate ? Math.max( year, minDate.getFullYear() ) : year );
+				endYear = ( maxDate ? Math.min( endYear, maxDate.getFullYear() ) : endYear );
+				inst.yearshtml += "<select class='ui-datepicker-year' data-handler='selectYear' data-event='change'>";
+				for ( ; year <= endYear; year++ ) {
+					inst.yearshtml += "<option value='" + year + "'" +
+						( year === drawYear ? " selected='selected'" : "" ) +
+						">" + year + "</option>";
+				}
+				inst.yearshtml += "</select>";
+
+				html += inst.yearshtml;
+				inst.yearshtml = null;
+			}
+		}
+
+		html += this._get( inst, "yearSuffix" );
+		if ( showMonthAfterYear ) {
+			html += ( secondary || !( changeMonth && changeYear ) ? "&#xa0;" : "" ) + monthHtml;
+		}
+		html += "</div>"; // Close datepicker_header
+		return html;
+	},
+
+	/* Adjust one of the date sub-fields. */
+	_adjustInstDate: function( inst, offset, period ) {
+		var year = inst.selectedYear + ( period === "Y" ? offset : 0 ),
+			month = inst.selectedMonth + ( period === "M" ? offset : 0 ),
+			day = Math.min( inst.selectedDay, this._getDaysInMonth( year, month ) ) + ( period === "D" ? offset : 0 ),
+			date = this._restrictMinMax( inst, this._daylightSavingAdjust( new Date( year, month, day ) ) );
+
+		inst.selectedDay = date.getDate();
+		inst.drawMonth = inst.selectedMonth = date.getMonth();
+		inst.drawYear = inst.selectedYear = date.getFullYear();
+		if ( period === "M" || period === "Y" ) {
+			this._notifyChange( inst );
+		}
+	},
+
+	/* Ensure a date is within any min/max bounds. */
+	_restrictMinMax: function( inst, date ) {
+		var minDate = this._getMinMaxDate( inst, "min" ),
+			maxDate = this._getMinMaxDate( inst, "max" ),
+			newDate = ( minDate && date < minDate ? minDate : date );
+		return ( maxDate && newDate > maxDate ? maxDate : newDate );
+	},
+
+	/* Notify change of month/year. */
+	_notifyChange: function( inst ) {
+		var onChange = this._get( inst, "onChangeMonthYear" );
+		if ( onChange ) {
+			onChange.apply( ( inst.input ? inst.input[ 0 ] : null ),
+				[ inst.selectedYear, inst.selectedMonth + 1, inst ] );
+		}
+	},
+
+	/* Determine the number of months to show. */
+	_getNumberOfMonths: function( inst ) {
+		var numMonths = this._get( inst, "numberOfMonths" );
+		return ( numMonths == null ? [ 1, 1 ] : ( typeof numMonths === "number" ? [ 1, numMonths ] : numMonths ) );
+	},
+
+	/* Determine the current maximum date - ensure no time components are set. */
+	_getMinMaxDate: function( inst, minMax ) {
+		return this._determineDate( inst, this._get( inst, minMax + "Date" ), null );
+	},
+
+	/* Find the number of days in a given month. */
+	_getDaysInMonth: function( year, month ) {
+		return 32 - this._daylightSavingAdjust( new Date( year, month, 32 ) ).getDate();
+	},
+
+	/* Find the day of the week of the first of a month. */
+	_getFirstDayOfMonth: function( year, month ) {
+		return new Date( year, month, 1 ).getDay();
+	},
+
+	/* Determines if we should allow a "next/prev" month display change. */
+	_canAdjustMonth: function( inst, offset, curYear, curMonth ) {
+		var numMonths = this._getNumberOfMonths( inst ),
+			date = this._daylightSavingAdjust( new Date( curYear,
+			curMonth + ( offset < 0 ? offset : numMonths[ 0 ] * numMonths[ 1 ] ), 1 ) );
+
+		if ( offset < 0 ) {
+			date.setDate( this._getDaysInMonth( date.getFullYear(), date.getMonth() ) );
+		}
+		return this._isInRange( inst, date );
+	},
+
+	/* Is the given date in the accepted range? */
+	_isInRange: function( inst, date ) {
+		var yearSplit, currentYear,
+			minDate = this._getMinMaxDate( inst, "min" ),
+			maxDate = this._getMinMaxDate( inst, "max" ),
+			minYear = null,
+			maxYear = null,
+			years = this._get( inst, "yearRange" );
+			if ( years ) {
+				yearSplit = years.split( ":" );
+				currentYear = new Date().getFullYear();
+				minYear = parseInt( yearSplit[ 0 ], 10 );
+				maxYear = parseInt( yearSplit[ 1 ], 10 );
+				if ( yearSplit[ 0 ].match( /[+\-].*/ ) ) {
+					minYear += currentYear;
+				}
+				if ( yearSplit[ 1 ].match( /[+\-].*/ ) ) {
+					maxYear += currentYear;
+				}
+			}
+
+		return ( ( !minDate || date.getTime() >= minDate.getTime() ) &&
+			( !maxDate || date.getTime() <= maxDate.getTime() ) &&
+			( !minYear || date.getFullYear() >= minYear ) &&
+			( !maxYear || date.getFullYear() <= maxYear ) );
+	},
+
+	/* Provide the configuration settings for formatting/parsing. */
+	_getFormatConfig: function( inst ) {
+		var shortYearCutoff = this._get( inst, "shortYearCutoff" );
+		shortYearCutoff = ( typeof shortYearCutoff !== "string" ? shortYearCutoff :
+			new Date().getFullYear() % 100 + parseInt( shortYearCutoff, 10 ) );
+		return { shortYearCutoff: shortYearCutoff,
+			dayNamesShort: this._get( inst, "dayNamesShort" ), dayNames: this._get( inst, "dayNames" ),
+			monthNamesShort: this._get( inst, "monthNamesShort" ), monthNames: this._get( inst, "monthNames" ) };
+	},
+
+	/* Format the given date for display. */
+	_formatDate: function( inst, day, month, year ) {
+		if ( !day ) {
+			inst.currentDay = inst.selectedDay;
+			inst.currentMonth = inst.selectedMonth;
+			inst.currentYear = inst.selectedYear;
+		}
+		var date = ( day ? ( typeof day === "object" ? day :
+			this._daylightSavingAdjust( new Date( year, month, day ) ) ) :
+			this._daylightSavingAdjust( new Date( inst.currentYear, inst.currentMonth, inst.currentDay ) ) );
+		return this.formatDate( this._get( inst, "dateFormat" ), date, this._getFormatConfig( inst ) );
+	}
+} );
+
+/*
+ * Bind hover events for datepicker elements.
+ * Done via delegate so the binding only occurs once in the lifetime of the parent div.
+ * Global datepicker_instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker.
+ */
+function datepicker_bindHover( dpDiv ) {
+	var selector = "button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";
+	return dpDiv.on( "mouseout", selector, function() {
+			$( this ).removeClass( "ui-state-hover" );
+			if ( this.className.indexOf( "ui-datepicker-prev" ) !== -1 ) {
+				$( this ).removeClass( "ui-datepicker-prev-hover" );
+			}
+			if ( this.className.indexOf( "ui-datepicker-next" ) !== -1 ) {
+				$( this ).removeClass( "ui-datepicker-next-hover" );
+			}
+		} )
+		.on( "mouseover", selector, datepicker_handleMouseover );
+}
+
+function datepicker_handleMouseover() {
+	if ( !$.datepicker._isDisabledDatepicker( datepicker_instActive.inline ? datepicker_instActive.dpDiv.parent()[ 0 ] : datepicker_instActive.input[ 0 ] ) ) {
+		$( this ).parents( ".ui-datepicker-calendar" ).find( "a" ).removeClass( "ui-state-hover" );
+		$( this ).addClass( "ui-state-hover" );
+		if ( this.className.indexOf( "ui-datepicker-prev" ) !== -1 ) {
+			$( this ).addClass( "ui-datepicker-prev-hover" );
+		}
+		if ( this.className.indexOf( "ui-datepicker-next" ) !== -1 ) {
+			$( this ).addClass( "ui-datepicker-next-hover" );
+		}
+	}
+}
+
+/* jQuery extend now ignores nulls! */
+function datepicker_extendRemove( target, props ) {
+	$.extend( target, props );
+	for ( var name in props ) {
+		if ( props[ name ] == null ) {
+			target[ name ] = props[ name ];
+		}
+	}
+	return target;
+}
+
+/* Invoke the datepicker functionality.
+   @param  options  string - a command, optionally followed by additional parameters or
+					Object - settings for attaching new datepicker functionality
+   @return  jQuery object */
+$.fn.datepicker = function( options ) {
+
+	/* Verify an empty collection wasn't passed - Fixes #6976 */
+	if ( !this.length ) {
+		return this;
+	}
+
+	/* Initialise the date picker. */
+	if ( !$.datepicker.initialized ) {
+		$( document ).on( "mousedown", $.datepicker._checkExternalClick );
+		$.datepicker.initialized = true;
+	}
+
+	/* Append datepicker main container to body if not exist. */
+	if ( $( "#" + $.datepicker._mainDivId ).length === 0 ) {
+		$( "body" ).append( $.datepicker.dpDiv );
+	}
+
+	var otherArgs = Array.prototype.slice.call( arguments, 1 );
+	if ( typeof options === "string" && ( options === "isDisabled" || options === "getDate" || options === "widget" ) ) {
+		return $.datepicker[ "_" + options + "Datepicker" ].
+			apply( $.datepicker, [ this[ 0 ] ].concat( otherArgs ) );
+	}
+	if ( options === "option" && arguments.length === 2 && typeof arguments[ 1 ] === "string" ) {
+		return $.datepicker[ "_" + options + "Datepicker" ].
+			apply( $.datepicker, [ this[ 0 ] ].concat( otherArgs ) );
+	}
+	return this.each( function() {
+		typeof options === "string" ?
+			$.datepicker[ "_" + options + "Datepicker" ].
+				apply( $.datepicker, [ this ].concat( otherArgs ) ) :
+			$.datepicker._attachDatepicker( this, options );
+	} );
+};
+
+$.datepicker = new Datepicker(); // singleton instance
+$.datepicker.initialized = false;
+$.datepicker.uuid = new Date().getTime();
+$.datepicker.version = "1.12.1";
+
+var widgetsDatepicker = $.datepicker;
+
+
+
+
+// This file is deprecated
+var ie = $.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() );
+
+/*!
+ * jQuery UI Mouse 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Mouse
+//>>group: Widgets
+//>>description: Abstracts mouse-based interactions to assist in creating certain widgets.
+//>>docs: http://api.jqueryui.com/mouse/
+
+
+
+var mouseHandled = false;
+$( document ).on( "mouseup", function() {
+	mouseHandled = false;
+} );
+
+var widgetsMouse = $.widget( "ui.mouse", {
+	version: "1.12.1",
+	options: {
+		cancel: "input, textarea, button, select, option",
+		distance: 1,
+		delay: 0
+	},
+	_mouseInit: function() {
+		var that = this;
+
+		this.element
+			.on( "mousedown." + this.widgetName, function( event ) {
+				return that._mouseDown( event );
+			} )
+			.on( "click." + this.widgetName, function( event ) {
+				if ( true === $.data( event.target, that.widgetName + ".preventClickEvent" ) ) {
+					$.removeData( event.target, that.widgetName + ".preventClickEvent" );
+					event.stopImmediatePropagation();
+					return false;
+				}
+			} );
+
+		this.started = false;
+	},
+
+	// TODO: make sure destroying one instance of mouse doesn't mess with
+	// other instances of mouse
+	_mouseDestroy: function() {
+		this.element.off( "." + this.widgetName );
+		if ( this._mouseMoveDelegate ) {
+			this.document
+				.off( "mousemove." + this.widgetName, this._mouseMoveDelegate )
+				.off( "mouseup." + this.widgetName, this._mouseUpDelegate );
+		}
+	},
+
+	_mouseDown: function( event ) {
+
+		// don't let more than one widget handle mouseStart
+		if ( mouseHandled ) {
+			return;
+		}
+
+		this._mouseMoved = false;
+
+		// We may have missed mouseup (out of window)
+		( this._mouseStarted && this._mouseUp( event ) );
+
+		this._mouseDownEvent = event;
+
+		var that = this,
+			btnIsLeft = ( event.which === 1 ),
+
+			// event.target.nodeName works around a bug in IE 8 with
+			// disabled inputs (#7620)
+			elIsCancel = ( typeof this.options.cancel === "string" && event.target.nodeName ?
+				$( event.target ).closest( this.options.cancel ).length : false );
+		if ( !btnIsLeft || elIsCancel || !this._mouseCapture( event ) ) {
+			return true;
+		}
+
+		this.mouseDelayMet = !this.options.delay;
+		if ( !this.mouseDelayMet ) {
+			this._mouseDelayTimer = setTimeout( function() {
+				that.mouseDelayMet = true;
+			}, this.options.delay );
+		}
+
+		if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) {
+			this._mouseStarted = ( this._mouseStart( event ) !== false );
+			if ( !this._mouseStarted ) {
+				event.preventDefault();
+				return true;
+			}
+		}
+
+		// Click event may never have fired (Gecko & Opera)
+		if ( true === $.data( event.target, this.widgetName + ".preventClickEvent" ) ) {
+			$.removeData( event.target, this.widgetName + ".preventClickEvent" );
+		}
+
+		// These delegates are required to keep context
+		this._mouseMoveDelegate = function( event ) {
+			return that._mouseMove( event );
+		};
+		this._mouseUpDelegate = function( event ) {
+			return that._mouseUp( event );
+		};
+
+		this.document
+			.on( "mousemove." + this.widgetName, this._mouseMoveDelegate )
+			.on( "mouseup." + this.widgetName, this._mouseUpDelegate );
+
+		event.preventDefault();
+
+		mouseHandled = true;
+		return true;
+	},
+
+	_mouseMove: function( event ) {
+
+		// Only check for mouseups outside the document if you've moved inside the document
+		// at least once. This prevents the firing of mouseup in the case of IE<9, which will
+		// fire a mousemove event if content is placed under the cursor. See #7778
+		// Support: IE <9
+		if ( this._mouseMoved ) {
+
+			// IE mouseup check - mouseup happened when mouse was out of window
+			if ( $.ui.ie && ( !document.documentMode || document.documentMode < 9 ) &&
+					!event.button ) {
+				return this._mouseUp( event );
+
+			// Iframe mouseup check - mouseup occurred in another document
+			} else if ( !event.which ) {
+
+				// Support: Safari <=8 - 9
+				// Safari sets which to 0 if you press any of the following keys
+				// during a drag (#14461)
+				if ( event.originalEvent.altKey || event.originalEvent.ctrlKey ||
+						event.originalEvent.metaKey || event.originalEvent.shiftKey ) {
+					this.ignoreMissingWhich = true;
+				} else if ( !this.ignoreMissingWhich ) {
+					return this._mouseUp( event );
+				}
+			}
+		}
+
+		if ( event.which || event.button ) {
+			this._mouseMoved = true;
+		}
+
+		if ( this._mouseStarted ) {
+			this._mouseDrag( event );
+			return event.preventDefault();
+		}
+
+		if ( this._mouseDistanceMet( event ) && this._mouseDelayMet( event ) ) {
+			this._mouseStarted =
+				( this._mouseStart( this._mouseDownEvent, event ) !== false );
+			( this._mouseStarted ? this._mouseDrag( event ) : this._mouseUp( event ) );
+		}
+
+		return !this._mouseStarted;
+	},
+
+	_mouseUp: function( event ) {
+		this.document
+			.off( "mousemove." + this.widgetName, this._mouseMoveDelegate )
+			.off( "mouseup." + this.widgetName, this._mouseUpDelegate );
+
+		if ( this._mouseStarted ) {
+			this._mouseStarted = false;
+
+			if ( event.target === this._mouseDownEvent.target ) {
+				$.data( event.target, this.widgetName + ".preventClickEvent", true );
+			}
+
+			this._mouseStop( event );
+		}
+
+		if ( this._mouseDelayTimer ) {
+			clearTimeout( this._mouseDelayTimer );
+			delete this._mouseDelayTimer;
+		}
+
+		this.ignoreMissingWhich = false;
+		mouseHandled = false;
+		event.preventDefault();
+	},
+
+	_mouseDistanceMet: function( event ) {
+		return ( Math.max(
+				Math.abs( this._mouseDownEvent.pageX - event.pageX ),
+				Math.abs( this._mouseDownEvent.pageY - event.pageY )
+			) >= this.options.distance
+		);
+	},
+
+	_mouseDelayMet: function( /* event */ ) {
+		return this.mouseDelayMet;
+	},
+
+	// These are placeholder methods, to be overriden by extending plugin
+	_mouseStart: function( /* event */ ) {},
+	_mouseDrag: function( /* event */ ) {},
+	_mouseStop: function( /* event */ ) {},
+	_mouseCapture: function( /* event */ ) { return true; }
+} );
+
+
+
+
+// $.ui.plugin is deprecated. Use $.widget() extensions instead.
+var plugin = $.ui.plugin = {
+	add: function( module, option, set ) {
+		var i,
+			proto = $.ui[ module ].prototype;
+		for ( i in set ) {
+			proto.plugins[ i ] = proto.plugins[ i ] || [];
+			proto.plugins[ i ].push( [ option, set[ i ] ] );
+		}
+	},
+	call: function( instance, name, args, allowDisconnected ) {
+		var i,
+			set = instance.plugins[ name ];
+
+		if ( !set ) {
+			return;
+		}
+
+		if ( !allowDisconnected && ( !instance.element[ 0 ].parentNode ||
+				instance.element[ 0 ].parentNode.nodeType === 11 ) ) {
+			return;
+		}
+
+		for ( i = 0; i < set.length; i++ ) {
+			if ( instance.options[ set[ i ][ 0 ] ] ) {
+				set[ i ][ 1 ].apply( instance.element, args );
+			}
+		}
+	}
+};
+
+
+
+var safeBlur = $.ui.safeBlur = function( element ) {
+
+	// Support: IE9 - 10 only
+	// If the <body> is blurred, IE will switch windows, see #9420
+	if ( element && element.nodeName.toLowerCase() !== "body" ) {
+		$( element ).trigger( "blur" );
+	}
+};
+
+
+/*!
+ * jQuery UI Draggable 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Draggable
+//>>group: Interactions
+//>>description: Enables dragging functionality for any element.
+//>>docs: http://api.jqueryui.com/draggable/
+//>>demos: http://jqueryui.com/draggable/
+//>>css.structure: ../../themes/base/draggable.css
+
+
+
+$.widget( "ui.draggable", $.ui.mouse, {
+	version: "1.12.1",
+	widgetEventPrefix: "drag",
+	options: {
+		addClasses: true,
+		appendTo: "parent",
+		axis: false,
+		connectToSortable: false,
+		containment: false,
+		cursor: "auto",
+		cursorAt: false,
+		grid: false,
+		handle: false,
+		helper: "original",
+		iframeFix: false,
+		opacity: false,
+		refreshPositions: false,
+		revert: false,
+		revertDuration: 500,
+		scope: "default",
+		scroll: true,
+		scrollSensitivity: 20,
+		scrollSpeed: 20,
+		snap: false,
+		snapMode: "both",
+		snapTolerance: 20,
+		stack: false,
+		zIndex: false,
+
+		// Callbacks
+		drag: null,
+		start: null,
+		stop: null
+	},
+	_create: function() {
+
+		if ( this.options.helper === "original" ) {
+			this._setPositionRelative();
+		}
+		if ( this.options.addClasses ) {
+			this._addClass( "ui-draggable" );
+		}
+		this._setHandleClassName();
+
+		this._mouseInit();
+	},
+
+	_setOption: function( key, value ) {
+		this._super( key, value );
+		if ( key === "handle" ) {
+			this._removeHandleClassName();
+			this._setHandleClassName();
+		}
+	},
+
+	_destroy: function() {
+		if ( ( this.helper || this.element ).is( ".ui-draggable-dragging" ) ) {
+			this.destroyOnClear = true;
+			return;
+		}
+		this._removeHandleClassName();
+		this._mouseDestroy();
+	},
+
+	_mouseCapture: function( event ) {
+		var o = this.options;
+
+		// Among others, prevent a drag on a resizable-handle
+		if ( this.helper || o.disabled ||
+				$( event.target ).closest( ".ui-resizable-handle" ).length > 0 ) {
+			return false;
+		}
+
+		//Quit if we're not on a valid handle
+		this.handle = this._getHandle( event );
+		if ( !this.handle ) {
+			return false;
+		}
+
+		this._blurActiveElement( event );
+
+		this._blockFrames( o.iframeFix === true ? "iframe" : o.iframeFix );
+
+		return true;
+
+	},
+
+	_blockFrames: function( selector ) {
+		this.iframeBlocks = this.document.find( selector ).map( function() {
+			var iframe = $( this );
+
+			return $( "<div>" )
+				.css( "position", "absolute" )
+				.appendTo( iframe.parent() )
+				.outerWidth( iframe.outerWidth() )
+				.outerHeight( iframe.outerHeight() )
+				.offset( iframe.offset() )[ 0 ];
+		} );
+	},
+
+	_unblockFrames: function() {
+		if ( this.iframeBlocks ) {
+			this.iframeBlocks.remove();
+			delete this.iframeBlocks;
+		}
+	},
+
+	_blurActiveElement: function( event ) {
+		var activeElement = $.ui.safeActiveElement( this.document[ 0 ] ),
+			target = $( event.target );
+
+		// Don't blur if the event occurred on an element that is within
+		// the currently focused element
+		// See #10527, #12472
+		if ( target.closest( activeElement ).length ) {
+			return;
+		}
+
+		// Blur any element that currently has focus, see #4261
+		$.ui.safeBlur( activeElement );
+	},
+
+	_mouseStart: function( event ) {
+
+		var o = this.options;
+
+		//Create and append the visible helper
+		this.helper = this._createHelper( event );
+
+		this._addClass( this.helper, "ui-draggable-dragging" );
+
+		//Cache the helper size
+		this._cacheHelperProportions();
+
+		//If ddmanager is used for droppables, set the global draggable
+		if ( $.ui.ddmanager ) {
+			$.ui.ddmanager.current = this;
+		}
+
+		/*
+		 * - Position generation -
+		 * This block generates everything position related - it's the core of draggables.
+		 */
+
+		//Cache the margins of the original element
+		this._cacheMargins();
+
+		//Store the helper's css position
+		this.cssPosition = this.helper.css( "position" );
+		this.scrollParent = this.helper.scrollParent( true );
+		this.offsetParent = this.helper.offsetParent();
+		this.hasFixedAncestor = this.helper.parents().filter( function() {
+				return $( this ).css( "position" ) === "fixed";
+			} ).length > 0;
+
+		//The element's absolute position on the page minus margins
+		this.positionAbs = this.element.offset();
+		this._refreshOffsets( event );
+
+		//Generate the original position
+		this.originalPosition = this.position = this._generatePosition( event, false );
+		this.originalPageX = event.pageX;
+		this.originalPageY = event.pageY;
+
+		//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
+		( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) );
+
+		//Set a containment if given in the options
+		this._setContainment();
+
+		//Trigger event + callbacks
+		if ( this._trigger( "start", event ) === false ) {
+			this._clear();
+			return false;
+		}
+
+		//Recache the helper size
+		this._cacheHelperProportions();
+
+		//Prepare the droppable offsets
+		if ( $.ui.ddmanager && !o.dropBehaviour ) {
+			$.ui.ddmanager.prepareOffsets( this, event );
+		}
+
+		// Execute the drag once - this causes the helper not to be visible before getting its
+		// correct position
+		this._mouseDrag( event, true );
+
+		// If the ddmanager is used for droppables, inform the manager that dragging has started
+		// (see #5003)
+		if ( $.ui.ddmanager ) {
+			$.ui.ddmanager.dragStart( this, event );
+		}
+
+		return true;
+	},
+
+	_refreshOffsets: function( event ) {
+		this.offset = {
+			top: this.positionAbs.top - this.margins.top,
+			left: this.positionAbs.left - this.margins.left,
+			scroll: false,
+			parent: this._getParentOffset(),
+			relative: this._getRelativeOffset()
+		};
+
+		this.offset.click = {
+			left: event.pageX - this.offset.left,
+			top: event.pageY - this.offset.top
+		};
+	},
+
+	_mouseDrag: function( event, noPropagation ) {
+
+		// reset any necessary cached properties (see #5009)
+		if ( this.hasFixedAncestor ) {
+			this.offset.parent = this._getParentOffset();
+		}
+
+		//Compute the helpers position
+		this.position = this._generatePosition( event, true );
+		this.positionAbs = this._convertPositionTo( "absolute" );
+
+		//Call plugins and callbacks and use the resulting position if something is returned
+		if ( !noPropagation ) {
+			var ui = this._uiHash();
+			if ( this._trigger( "drag", event, ui ) === false ) {
+				this._mouseUp( new $.Event( "mouseup", event ) );
+				return false;
+			}
+			this.position = ui.position;
+		}
+
+		this.helper[ 0 ].style.left = this.position.left + "px";
+		this.helper[ 0 ].style.top = this.position.top + "px";
+
+		if ( $.ui.ddmanager ) {
+			$.ui.ddmanager.drag( this, event );
+		}
+
+		return false;
+	},
+
+	_mouseStop: function( event ) {
+
+		//If we are using droppables, inform the manager about the drop
+		var that = this,
+			dropped = false;
+		if ( $.ui.ddmanager && !this.options.dropBehaviour ) {
+			dropped = $.ui.ddmanager.drop( this, event );
+		}
+
+		//if a drop comes from outside (a sortable)
+		if ( this.dropped ) {
+			dropped = this.dropped;
+			this.dropped = false;
+		}
+
+		if ( ( this.options.revert === "invalid" && !dropped ) ||
+				( this.options.revert === "valid" && dropped ) ||
+				this.options.revert === true || ( $.isFunction( this.options.revert ) &&
+				this.options.revert.call( this.element, dropped ) )
+		) {
+			$( this.helper ).animate(
+				this.originalPosition,
+				parseInt( this.options.revertDuration, 10 ),
+				function() {
+					if ( that._trigger( "stop", event ) !== false ) {
+						that._clear();
+					}
+				}
+			);
+		} else {
+			if ( this._trigger( "stop", event ) !== false ) {
+				this._clear();
+			}
+		}
+
+		return false;
+	},
+
+	_mouseUp: function( event ) {
+		this._unblockFrames();
+
+		// If the ddmanager is used for droppables, inform the manager that dragging has stopped
+		// (see #5003)
+		if ( $.ui.ddmanager ) {
+			$.ui.ddmanager.dragStop( this, event );
+		}
+
+		// Only need to focus if the event occurred on the draggable itself, see #10527
+		if ( this.handleElement.is( event.target ) ) {
+
+			// The interaction is over; whether or not the click resulted in a drag,
+			// focus the element
+			this.element.trigger( "focus" );
+		}
+
+		return $.ui.mouse.prototype._mouseUp.call( this, event );
+	},
+
+	cancel: function() {
+
+		if ( this.helper.is( ".ui-draggable-dragging" ) ) {
+			this._mouseUp( new $.Event( "mouseup", { target: this.element[ 0 ] } ) );
+		} else {
+			this._clear();
+		}
+
+		return this;
+
+	},
+
+	_getHandle: function( event ) {
+		return this.options.handle ?
+			!!$( event.target ).closest( this.element.find( this.options.handle ) ).length :
+			true;
+	},
+
+	_setHandleClassName: function() {
+		this.handleElement = this.options.handle ?
+			this.element.find( this.options.handle ) : this.element;
+		this._addClass( this.handleElement, "ui-draggable-handle" );
+	},
+
+	_removeHandleClassName: function() {
+		this._removeClass( this.handleElement, "ui-draggable-handle" );
+	},
+
+	_createHelper: function( event ) {
+
+		var o = this.options,
+			helperIsFunction = $.isFunction( o.helper ),
+			helper = helperIsFunction ?
+				$( o.helper.apply( this.element[ 0 ], [ event ] ) ) :
+				( o.helper === "clone" ?
+					this.element.clone().removeAttr( "id" ) :
+					this.element );
+
+		if ( !helper.parents( "body" ).length ) {
+			helper.appendTo( ( o.appendTo === "parent" ?
+				this.element[ 0 ].parentNode :
+				o.appendTo ) );
+		}
+
+		// Http://bugs.jqueryui.com/ticket/9446
+		// a helper function can return the original element
+		// which wouldn't have been set to relative in _create
+		if ( helperIsFunction && helper[ 0 ] === this.element[ 0 ] ) {
+			this._setPositionRelative();
+		}
+
+		if ( helper[ 0 ] !== this.element[ 0 ] &&
+				!( /(fixed|absolute)/ ).test( helper.css( "position" ) ) ) {
+			helper.css( "position", "absolute" );
+		}
+
+		return helper;
+
+	},
+
+	_setPositionRelative: function() {
+		if ( !( /^(?:r|a|f)/ ).test( this.element.css( "position" ) ) ) {
+			this.element[ 0 ].style.position = "relative";
+		}
+	},
+
+	_adjustOffsetFromHelper: function( obj ) {
+		if ( typeof obj === "string" ) {
+			obj = obj.split( " " );
+		}
+		if ( $.isArray( obj ) ) {
+			obj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 };
+		}
+		if ( "left" in obj ) {
+			this.offset.click.left = obj.left + this.margins.left;
+		}
+		if ( "right" in obj ) {
+			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
+		}
+		if ( "top" in obj ) {
+			this.offset.click.top = obj.top + this.margins.top;
+		}
+		if ( "bottom" in obj ) {
+			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
+		}
+	},
+
+	_isRootNode: function( element ) {
+		return ( /(html|body)/i ).test( element.tagName ) || element === this.document[ 0 ];
+	},
+
+	_getParentOffset: function() {
+
+		//Get the offsetParent and cache its position
+		var po = this.offsetParent.offset(),
+			document = this.document[ 0 ];
+
+		// This is a special case where we need to modify a offset calculated on start, since the
+		// following happened:
+		// 1. The position of the helper is absolute, so it's position is calculated based on the
+		// next positioned parent
+		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't
+		// the document, which means that the scroll is included in the initial calculation of the
+		// offset of the parent, and never recalculated upon drag
+		if ( this.cssPosition === "absolute" && this.scrollParent[ 0 ] !== document &&
+				$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) {
+			po.left += this.scrollParent.scrollLeft();
+			po.top += this.scrollParent.scrollTop();
+		}
+
+		if ( this._isRootNode( this.offsetParent[ 0 ] ) ) {
+			po = { top: 0, left: 0 };
+		}
+
+		return {
+			top: po.top + ( parseInt( this.offsetParent.css( "borderTopWidth" ), 10 ) || 0 ),
+			left: po.left + ( parseInt( this.offsetParent.css( "borderLeftWidth" ), 10 ) || 0 )
+		};
+
+	},
+
+	_getRelativeOffset: function() {
+		if ( this.cssPosition !== "relative" ) {
+			return { top: 0, left: 0 };
+		}
+
+		var p = this.element.position(),
+			scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );
+
+		return {
+			top: p.top - ( parseInt( this.helper.css( "top" ), 10 ) || 0 ) +
+				( !scrollIsRootNode ? this.scrollParent.scrollTop() : 0 ),
+			left: p.left - ( parseInt( this.helper.css( "left" ), 10 ) || 0 ) +
+				( !scrollIsRootNode ? this.scrollParent.scrollLeft() : 0 )
+		};
+
+	},
+
+	_cacheMargins: function() {
+		this.margins = {
+			left: ( parseInt( this.element.css( "marginLeft" ), 10 ) || 0 ),
+			top: ( parseInt( this.element.css( "marginTop" ), 10 ) || 0 ),
+			right: ( parseInt( this.element.css( "marginRight" ), 10 ) || 0 ),
+			bottom: ( parseInt( this.element.css( "marginBottom" ), 10 ) || 0 )
+		};
+	},
+
+	_cacheHelperProportions: function() {
+		this.helperProportions = {
+			width: this.helper.outerWidth(),
+			height: this.helper.outerHeight()
+		};
+	},
+
+	_setContainment: function() {
+
+		var isUserScrollable, c, ce,
+			o = this.options,
+			document = this.document[ 0 ];
+
+		this.relativeContainer = null;
+
+		if ( !o.containment ) {
+			this.containment = null;
+			return;
+		}
+
+		if ( o.containment === "window" ) {
+			this.containment = [
+				$( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
+				$( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top,
+				$( window ).scrollLeft() + $( window ).width() -
+					this.helperProportions.width - this.margins.left,
+				$( window ).scrollTop() +
+					( $( window ).height() || document.body.parentNode.scrollHeight ) -
+					this.helperProportions.height - this.margins.top
+			];
+			return;
+		}
+
+		if ( o.containment === "document" ) {
+			this.containment = [
+				0,
+				0,
+				$( document ).width() - this.helperProportions.width - this.margins.left,
+				( $( document ).height() || document.body.parentNode.scrollHeight ) -
+					this.helperProportions.height - this.margins.top
+			];
+			return;
+		}
+
+		if ( o.containment.constructor === Array ) {
+			this.containment = o.containment;
+			return;
+		}
+
+		if ( o.containment === "parent" ) {
+			o.containment = this.helper[ 0 ].parentNode;
+		}
+
+		c = $( o.containment );
+		ce = c[ 0 ];
+
+		if ( !ce ) {
+			return;
+		}
+
+		isUserScrollable = /(scroll|auto)/.test( c.css( "overflow" ) );
+
+		this.containment = [
+			( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) +
+				( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ),
+			( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) +
+				( parseInt( c.css( "paddingTop" ), 10 ) || 0 ),
+			( isUserScrollable ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) -
+				( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) -
+				( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) -
+				this.helperProportions.width -
+				this.margins.left -
+				this.margins.right,
+			( isUserScrollable ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) -
+				( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) -
+				( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) -
+				this.helperProportions.height -
+				this.margins.top -
+				this.margins.bottom
+		];
+		this.relativeContainer = c;
+	},
+
+	_convertPositionTo: function( d, pos ) {
+
+		if ( !pos ) {
+			pos = this.position;
+		}
+
+		var mod = d === "absolute" ? 1 : -1,
+			scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] );
+
+		return {
+			top: (
+
+				// The absolute mouse position
+				pos.top	+
+
+				// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.relative.top * mod +
+
+				// The offsetParent's offset without borders (offset + border)
+				this.offset.parent.top * mod -
+				( ( this.cssPosition === "fixed" ?
+					-this.offset.scroll.top :
+					( scrollIsRootNode ? 0 : this.offset.scroll.top ) ) * mod )
+			),
+			left: (
+
+				// The absolute mouse position
+				pos.left +
+
+				// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.relative.left * mod +
+
+				// The offsetParent's offset without borders (offset + border)
+				this.offset.parent.left * mod	-
+				( ( this.cssPosition === "fixed" ?
+					-this.offset.scroll.left :
+					( scrollIsRootNode ? 0 : this.offset.scroll.left ) ) * mod )
+			)
+		};
+
+	},
+
+	_generatePosition: function( event, constrainPosition ) {
+
+		var containment, co, top, left,
+			o = this.options,
+			scrollIsRootNode = this._isRootNode( this.scrollParent[ 0 ] ),
+			pageX = event.pageX,
+			pageY = event.pageY;
+
+		// Cache the scroll
+		if ( !scrollIsRootNode || !this.offset.scroll ) {
+			this.offset.scroll = {
+				top: this.scrollParent.scrollTop(),
+				left: this.scrollParent.scrollLeft()
+			};
+		}
+
+		/*
+		 * - Position constraining -
+		 * Constrain the position to a mix of grid, containment.
+		 */
+
+		// If we are not dragging yet, we won't check for options
+		if ( constrainPosition ) {
+			if ( this.containment ) {
+				if ( this.relativeContainer ) {
+					co = this.relativeContainer.offset();
+					containment = [
+						this.containment[ 0 ] + co.left,
+						this.containment[ 1 ] + co.top,
+						this.containment[ 2 ] + co.left,
+						this.containment[ 3 ] + co.top
+					];
+				} else {
+					containment = this.containment;
+				}
+
+				if ( event.pageX - this.offset.click.left < containment[ 0 ] ) {
+					pageX = containment[ 0 ] + this.offset.click.left;
+				}
+				if ( event.pageY - this.offset.click.top < containment[ 1 ] ) {
+					pageY = containment[ 1 ] + this.offset.click.top;
+				}
+				if ( event.pageX - this.offset.click.left > containment[ 2 ] ) {
+					pageX = containment[ 2 ] + this.offset.click.left;
+				}
+				if ( event.pageY - this.offset.click.top > containment[ 3 ] ) {
+					pageY = containment[ 3 ] + this.offset.click.top;
+				}
+			}
+
+			if ( o.grid ) {
+
+				//Check for grid elements set to 0 to prevent divide by 0 error causing invalid
+				// argument errors in IE (see ticket #6950)
+				top = o.grid[ 1 ] ? this.originalPageY + Math.round( ( pageY -
+					this.originalPageY ) / o.grid[ 1 ] ) * o.grid[ 1 ] : this.originalPageY;
+				pageY = containment ? ( ( top - this.offset.click.top >= containment[ 1 ] ||
+					top - this.offset.click.top > containment[ 3 ] ) ?
+						top :
+						( ( top - this.offset.click.top >= containment[ 1 ] ) ?
+							top - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) : top;
+
+				left = o.grid[ 0 ] ? this.originalPageX +
+					Math.round( ( pageX - this.originalPageX ) / o.grid[ 0 ] ) * o.grid[ 0 ] :
+					this.originalPageX;
+				pageX = containment ? ( ( left - this.offset.click.left >= containment[ 0 ] ||
+					left - this.offset.click.left > containment[ 2 ] ) ?
+						left :
+						( ( left - this.offset.click.left >= containment[ 0 ] ) ?
+							left - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) : left;
+			}
+
+			if ( o.axis === "y" ) {
+				pageX = this.originalPageX;
+			}
+
+			if ( o.axis === "x" ) {
+				pageY = this.originalPageY;
+			}
+		}
+
+		return {
+			top: (
+
+				// The absolute mouse position
+				pageY -
+
+				// Click offset (relative to the element)
+				this.offset.click.top -
+
+				// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.relative.top -
+
+				// The offsetParent's offset without borders (offset + border)
+				this.offset.parent.top +
+				( this.cssPosition === "fixed" ?
+					-this.offset.scroll.top :
+					( scrollIsRootNode ? 0 : this.offset.scroll.top ) )
+			),
+			left: (
+
+				// The absolute mouse position
+				pageX -
+
+				// Click offset (relative to the element)
+				this.offset.click.left -
+
+				// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.relative.left -
+
+				// The offsetParent's offset without borders (offset + border)
+				this.offset.parent.left +
+				( this.cssPosition === "fixed" ?
+					-this.offset.scroll.left :
+					( scrollIsRootNode ? 0 : this.offset.scroll.left ) )
+			)
+		};
+
+	},
+
+	_clear: function() {
+		this._removeClass( this.helper, "ui-draggable-dragging" );
+		if ( this.helper[ 0 ] !== this.element[ 0 ] && !this.cancelHelperRemoval ) {
+			this.helper.remove();
+		}
+		this.helper = null;
+		this.cancelHelperRemoval = false;
+		if ( this.destroyOnClear ) {
+			this.destroy();
+		}
+	},
+
+	// From now on bulk stuff - mainly helpers
+
+	_trigger: function( type, event, ui ) {
+		ui = ui || this._uiHash();
+		$.ui.plugin.call( this, type, [ event, ui, this ], true );
+
+		// Absolute position and offset (see #6884 ) have to be recalculated after plugins
+		if ( /^(drag|start|stop)/.test( type ) ) {
+			this.positionAbs = this._convertPositionTo( "absolute" );
+			ui.offset = this.positionAbs;
+		}
+		return $.Widget.prototype._trigger.call( this, type, event, ui );
+	},
+
+	plugins: {},
+
+	_uiHash: function() {
+		return {
+			helper: this.helper,
+			position: this.position,
+			originalPosition: this.originalPosition,
+			offset: this.positionAbs
+		};
+	}
+
+} );
+
+$.ui.plugin.add( "draggable", "connectToSortable", {
+	start: function( event, ui, draggable ) {
+		var uiSortable = $.extend( {}, ui, {
+			item: draggable.element
+		} );
+
+		draggable.sortables = [];
+		$( draggable.options.connectToSortable ).each( function() {
+			var sortable = $( this ).sortable( "instance" );
+
+			if ( sortable && !sortable.options.disabled ) {
+				draggable.sortables.push( sortable );
+
+				// RefreshPositions is called at drag start to refresh the containerCache
+				// which is used in drag. This ensures it's initialized and synchronized
+				// with any changes that might have happened on the page since initialization.
+				sortable.refreshPositions();
+				sortable._trigger( "activate", event, uiSortable );
+			}
+		} );
+	},
+	stop: function( event, ui, draggable ) {
+		var uiSortable = $.extend( {}, ui, {
+			item: draggable.element
+		} );
+
+		draggable.cancelHelperRemoval = false;
+
+		$.each( draggable.sortables, function() {
+			var sortable = this;
+
+			if ( sortable.isOver ) {
+				sortable.isOver = 0;
+
+				// Allow this sortable to handle removing the helper
+				draggable.cancelHelperRemoval = true;
+				sortable.cancelHelperRemoval = false;
+
+				// Use _storedCSS To restore properties in the sortable,
+				// as this also handles revert (#9675) since the draggable
+				// may have modified them in unexpected ways (#8809)
+				sortable._storedCSS = {
+					position: sortable.placeholder.css( "position" ),
+					top: sortable.placeholder.css( "top" ),
+					left: sortable.placeholder.css( "left" )
+				};
+
+				sortable._mouseStop( event );
+
+				// Once drag has ended, the sortable should return to using
+				// its original helper, not the shared helper from draggable
+				sortable.options.helper = sortable.options._helper;
+			} else {
+
+				// Prevent this Sortable from removing the helper.
+				// However, don't set the draggable to remove the helper
+				// either as another connected Sortable may yet handle the removal.
+				sortable.cancelHelperRemoval = true;
+
+				sortable._trigger( "deactivate", event, uiSortable );
+			}
+		} );
+	},
+	drag: function( event, ui, draggable ) {
+		$.each( draggable.sortables, function() {
+			var innermostIntersecting = false,
+				sortable = this;
+
+			// Copy over variables that sortable's _intersectsWith uses
+			sortable.positionAbs = draggable.positionAbs;
+			sortable.helperProportions = draggable.helperProportions;
+			sortable.offset.click = draggable.offset.click;
+
+			if ( sortable._intersectsWith( sortable.containerCache ) ) {
+				innermostIntersecting = true;
+
+				$.each( draggable.sortables, function() {
+
+					// Copy over variables that sortable's _intersectsWith uses
+					this.positionAbs = draggable.positionAbs;
+					this.helperProportions = draggable.helperProportions;
+					this.offset.click = draggable.offset.click;
+
+					if ( this !== sortable &&
+							this._intersectsWith( this.containerCache ) &&
+							$.contains( sortable.element[ 0 ], this.element[ 0 ] ) ) {
+						innermostIntersecting = false;
+					}
+
+					return innermostIntersecting;
+				} );
+			}
+
+			if ( innermostIntersecting ) {
+
+				// If it intersects, we use a little isOver variable and set it once,
+				// so that the move-in stuff gets fired only once.
+				if ( !sortable.isOver ) {
+					sortable.isOver = 1;
+
+					// Store draggable's parent in case we need to reappend to it later.
+					draggable._parent = ui.helper.parent();
+
+					sortable.currentItem = ui.helper
+						.appendTo( sortable.element )
+						.data( "ui-sortable-item", true );
+
+					// Store helper option to later restore it
+					sortable.options._helper = sortable.options.helper;
+
+					sortable.options.helper = function() {
+						return ui.helper[ 0 ];
+					};
+
+					// Fire the start events of the sortable with our passed browser event,
+					// and our own helper (so it doesn't create a new one)
+					event.target = sortable.currentItem[ 0 ];
+					sortable._mouseCapture( event, true );
+					sortable._mouseStart( event, true, true );
+
+					// Because the browser event is way off the new appended portlet,
+					// modify necessary variables to reflect the changes
+					sortable.offset.click.top = draggable.offset.click.top;
+					sortable.offset.click.left = draggable.offset.click.left;
+					sortable.offset.parent.left -= draggable.offset.parent.left -
+						sortable.offset.parent.left;
+					sortable.offset.parent.top -= draggable.offset.parent.top -
+						sortable.offset.parent.top;
+
+					draggable._trigger( "toSortable", event );
+
+					// Inform draggable that the helper is in a valid drop zone,
+					// used solely in the revert option to handle "valid/invalid".
+					draggable.dropped = sortable.element;
+
+					// Need to refreshPositions of all sortables in the case that
+					// adding to one sortable changes the location of the other sortables (#9675)
+					$.each( draggable.sortables, function() {
+						this.refreshPositions();
+					} );
+
+					// Hack so receive/update callbacks work (mostly)
+					draggable.currentItem = draggable.element;
+					sortable.fromOutside = draggable;
+				}
+
+				if ( sortable.currentItem ) {
+					sortable._mouseDrag( event );
+
+					// Copy the sortable's position because the draggable's can potentially reflect
+					// a relative position, while sortable is always absolute, which the dragged
+					// element has now become. (#8809)
+					ui.position = sortable.position;
+				}
+			} else {
+
+				// If it doesn't intersect with the sortable, and it intersected before,
+				// we fake the drag stop of the sortable, but make sure it doesn't remove
+				// the helper by using cancelHelperRemoval.
+				if ( sortable.isOver ) {
+
+					sortable.isOver = 0;
+					sortable.cancelHelperRemoval = true;
+
+					// Calling sortable's mouseStop would trigger a revert,
+					// so revert must be temporarily false until after mouseStop is called.
+					sortable.options._revert = sortable.options.revert;
+					sortable.options.revert = false;
+
+					sortable._trigger( "out", event, sortable._uiHash( sortable ) );
+					sortable._mouseStop( event, true );
+
+					// Restore sortable behaviors that were modfied
+					// when the draggable entered the sortable area (#9481)
+					sortable.options.revert = sortable.options._revert;
+					sortable.options.helper = sortable.options._helper;
+
+					if ( sortable.placeholder ) {
+						sortable.placeholder.remove();
+					}
+
+					// Restore and recalculate the draggable's offset considering the sortable
+					// may have modified them in unexpected ways. (#8809, #10669)
+					ui.helper.appendTo( draggable._parent );
+					draggable._refreshOffsets( event );
+					ui.position = draggable._generatePosition( event, true );
+
+					draggable._trigger( "fromSortable", event );
+
+					// Inform draggable that the helper is no longer in a valid drop zone
+					draggable.dropped = false;
+
+					// Need to refreshPositions of all sortables just in case removing
+					// from one sortable changes the location of other sortables (#9675)
+					$.each( draggable.sortables, function() {
+						this.refreshPositions();
+					} );
+				}
+			}
+		} );
+	}
+} );
+
+$.ui.plugin.add( "draggable", "cursor", {
+	start: function( event, ui, instance ) {
+		var t = $( "body" ),
+			o = instance.options;
+
+		if ( t.css( "cursor" ) ) {
+			o._cursor = t.css( "cursor" );
+		}
+		t.css( "cursor", o.cursor );
+	},
+	stop: function( event, ui, instance ) {
+		var o = instance.options;
+		if ( o._cursor ) {
+			$( "body" ).css( "cursor", o._cursor );
+		}
+	}
+} );
+
+$.ui.plugin.add( "draggable", "opacity", {
+	start: function( event, ui, instance ) {
+		var t = $( ui.helper ),
+			o = instance.options;
+		if ( t.css( "opacity" ) ) {
+			o._opacity = t.css( "opacity" );
+		}
+		t.css( "opacity", o.opacity );
+	},
+	stop: function( event, ui, instance ) {
+		var o = instance.options;
+		if ( o._opacity ) {
+			$( ui.helper ).css( "opacity", o._opacity );
+		}
+	}
+} );
+
+$.ui.plugin.add( "draggable", "scroll", {
+	start: function( event, ui, i ) {
+		if ( !i.scrollParentNotHidden ) {
+			i.scrollParentNotHidden = i.helper.scrollParent( false );
+		}
+
+		if ( i.scrollParentNotHidden[ 0 ] !== i.document[ 0 ] &&
+				i.scrollParentNotHidden[ 0 ].tagName !== "HTML" ) {
+			i.overflowOffset = i.scrollParentNotHidden.offset();
+		}
+	},
+	drag: function( event, ui, i  ) {
+
+		var o = i.options,
+			scrolled = false,
+			scrollParent = i.scrollParentNotHidden[ 0 ],
+			document = i.document[ 0 ];
+
+		if ( scrollParent !== document && scrollParent.tagName !== "HTML" ) {
+			if ( !o.axis || o.axis !== "x" ) {
+				if ( ( i.overflowOffset.top + scrollParent.offsetHeight ) - event.pageY <
+						o.scrollSensitivity ) {
+					scrollParent.scrollTop = scrolled = scrollParent.scrollTop + o.scrollSpeed;
+				} else if ( event.pageY - i.overflowOffset.top < o.scrollSensitivity ) {
+					scrollParent.scrollTop = scrolled = scrollParent.scrollTop - o.scrollSpeed;
+				}
+			}
+
+			if ( !o.axis || o.axis !== "y" ) {
+				if ( ( i.overflowOffset.left + scrollParent.offsetWidth ) - event.pageX <
+						o.scrollSensitivity ) {
+					scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft + o.scrollSpeed;
+				} else if ( event.pageX - i.overflowOffset.left < o.scrollSensitivity ) {
+					scrollParent.scrollLeft = scrolled = scrollParent.scrollLeft - o.scrollSpeed;
+				}
+			}
+
+		} else {
+
+			if ( !o.axis || o.axis !== "x" ) {
+				if ( event.pageY - $( document ).scrollTop() < o.scrollSensitivity ) {
+					scrolled = $( document ).scrollTop( $( document ).scrollTop() - o.scrollSpeed );
+				} else if ( $( window ).height() - ( event.pageY - $( document ).scrollTop() ) <
+						o.scrollSensitivity ) {
+					scrolled = $( document ).scrollTop( $( document ).scrollTop() + o.scrollSpeed );
+				}
+			}
+
+			if ( !o.axis || o.axis !== "y" ) {
+				if ( event.pageX - $( document ).scrollLeft() < o.scrollSensitivity ) {
+					scrolled = $( document ).scrollLeft(
+						$( document ).scrollLeft() - o.scrollSpeed
+					);
+				} else if ( $( window ).width() - ( event.pageX - $( document ).scrollLeft() ) <
+						o.scrollSensitivity ) {
+					scrolled = $( document ).scrollLeft(
+						$( document ).scrollLeft() + o.scrollSpeed
+					);
+				}
+			}
+
+		}
+
+		if ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) {
+			$.ui.ddmanager.prepareOffsets( i, event );
+		}
+
+	}
+} );
+
+$.ui.plugin.add( "draggable", "snap", {
+	start: function( event, ui, i ) {
+
+		var o = i.options;
+
+		i.snapElements = [];
+
+		$( o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap )
+			.each( function() {
+				var $t = $( this ),
+					$o = $t.offset();
+				if ( this !== i.element[ 0 ] ) {
+					i.snapElements.push( {
+						item: this,
+						width: $t.outerWidth(), height: $t.outerHeight(),
+						top: $o.top, left: $o.left
+					} );
+				}
+			} );
+
+	},
+	drag: function( event, ui, inst ) {
+
+		var ts, bs, ls, rs, l, r, t, b, i, first,
+			o = inst.options,
+			d = o.snapTolerance,
+			x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
+			y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
+
+		for ( i = inst.snapElements.length - 1; i >= 0; i-- ) {
+
+			l = inst.snapElements[ i ].left - inst.margins.left;
+			r = l + inst.snapElements[ i ].width;
+			t = inst.snapElements[ i ].top - inst.margins.top;
+			b = t + inst.snapElements[ i ].height;
+
+			if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d ||
+					!$.contains( inst.snapElements[ i ].item.ownerDocument,
+					inst.snapElements[ i ].item ) ) {
+				if ( inst.snapElements[ i ].snapping ) {
+					( inst.options.snap.release &&
+						inst.options.snap.release.call(
+							inst.element,
+							event,
+							$.extend( inst._uiHash(), { snapItem: inst.snapElements[ i ].item } )
+						) );
+				}
+				inst.snapElements[ i ].snapping = false;
+				continue;
+			}
+
+			if ( o.snapMode !== "inner" ) {
+				ts = Math.abs( t - y2 ) <= d;
+				bs = Math.abs( b - y1 ) <= d;
+				ls = Math.abs( l - x2 ) <= d;
+				rs = Math.abs( r - x1 ) <= d;
+				if ( ts ) {
+					ui.position.top = inst._convertPositionTo( "relative", {
+						top: t - inst.helperProportions.height,
+						left: 0
+					} ).top;
+				}
+				if ( bs ) {
+					ui.position.top = inst._convertPositionTo( "relative", {
+						top: b,
+						left: 0
+					} ).top;
+				}
+				if ( ls ) {
+					ui.position.left = inst._convertPositionTo( "relative", {
+						top: 0,
+						left: l - inst.helperProportions.width
+					} ).left;
+				}
+				if ( rs ) {
+					ui.position.left = inst._convertPositionTo( "relative", {
+						top: 0,
+						left: r
+					} ).left;
+				}
+			}
+
+			first = ( ts || bs || ls || rs );
+
+			if ( o.snapMode !== "outer" ) {
+				ts = Math.abs( t - y1 ) <= d;
+				bs = Math.abs( b - y2 ) <= d;
+				ls = Math.abs( l - x1 ) <= d;
+				rs = Math.abs( r - x2 ) <= d;
+				if ( ts ) {
+					ui.position.top = inst._convertPositionTo( "relative", {
+						top: t,
+						left: 0
+					} ).top;
+				}
+				if ( bs ) {
+					ui.position.top = inst._convertPositionTo( "relative", {
+						top: b - inst.helperProportions.height,
+						left: 0
+					} ).top;
+				}
+				if ( ls ) {
+					ui.position.left = inst._convertPositionTo( "relative", {
+						top: 0,
+						left: l
+					} ).left;
+				}
+				if ( rs ) {
+					ui.position.left = inst._convertPositionTo( "relative", {
+						top: 0,
+						left: r - inst.helperProportions.width
+					} ).left;
+				}
+			}
+
+			if ( !inst.snapElements[ i ].snapping && ( ts || bs || ls || rs || first ) ) {
+				( inst.options.snap.snap &&
+					inst.options.snap.snap.call(
+						inst.element,
+						event,
+						$.extend( inst._uiHash(), {
+							snapItem: inst.snapElements[ i ].item
+						} ) ) );
+			}
+			inst.snapElements[ i ].snapping = ( ts || bs || ls || rs || first );
+
+		}
+
+	}
+} );
+
+$.ui.plugin.add( "draggable", "stack", {
+	start: function( event, ui, instance ) {
+		var min,
+			o = instance.options,
+			group = $.makeArray( $( o.stack ) ).sort( function( a, b ) {
+				return ( parseInt( $( a ).css( "zIndex" ), 10 ) || 0 ) -
+					( parseInt( $( b ).css( "zIndex" ), 10 ) || 0 );
+			} );
+
+		if ( !group.length ) { return; }
+
+		min = parseInt( $( group[ 0 ] ).css( "zIndex" ), 10 ) || 0;
+		$( group ).each( function( i ) {
+			$( this ).css( "zIndex", min + i );
+		} );
+		this.css( "zIndex", ( min + group.length ) );
+	}
+} );
+
+$.ui.plugin.add( "draggable", "zIndex", {
+	start: function( event, ui, instance ) {
+		var t = $( ui.helper ),
+			o = instance.options;
+
+		if ( t.css( "zIndex" ) ) {
+			o._zIndex = t.css( "zIndex" );
+		}
+		t.css( "zIndex", o.zIndex );
+	},
+	stop: function( event, ui, instance ) {
+		var o = instance.options;
+
+		if ( o._zIndex ) {
+			$( ui.helper ).css( "zIndex", o._zIndex );
+		}
+	}
+} );
+
+var widgetsDraggable = $.ui.draggable;
+
+
+/*!
+ * jQuery UI Resizable 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Resizable
+//>>group: Interactions
+//>>description: Enables resize functionality for any element.
+//>>docs: http://api.jqueryui.com/resizable/
+//>>demos: http://jqueryui.com/resizable/
+//>>css.structure: ../../themes/base/core.css
+//>>css.structure: ../../themes/base/resizable.css
+//>>css.theme: ../../themes/base/theme.css
+
+
+
+$.widget( "ui.resizable", $.ui.mouse, {
+	version: "1.12.1",
+	widgetEventPrefix: "resize",
+	options: {
+		alsoResize: false,
+		animate: false,
+		animateDuration: "slow",
+		animateEasing: "swing",
+		aspectRatio: false,
+		autoHide: false,
+		classes: {
+			"ui-resizable-se": "ui-icon ui-icon-gripsmall-diagonal-se"
+		},
+		containment: false,
+		ghost: false,
+		grid: false,
+		handles: "e,s,se",
+		helper: false,
+		maxHeight: null,
+		maxWidth: null,
+		minHeight: 10,
+		minWidth: 10,
+
+		// See #7960
+		zIndex: 90,
+
+		// Callbacks
+		resize: null,
+		start: null,
+		stop: null
+	},
+
+	_num: function( value ) {
+		return parseFloat( value ) || 0;
+	},
+
+	_isNumber: function( value ) {
+		return !isNaN( parseFloat( value ) );
+	},
+
+	_hasScroll: function( el, a ) {
+
+		if ( $( el ).css( "overflow" ) === "hidden" ) {
+			return false;
+		}
+
+		var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
+			has = false;
+
+		if ( el[ scroll ] > 0 ) {
+			return true;
+		}
+
+		// TODO: determine which cases actually cause this to happen
+		// if the element doesn't have the scroll set, see if it's possible to
+		// set the scroll
+		el[ scroll ] = 1;
+		has = ( el[ scroll ] > 0 );
+		el[ scroll ] = 0;
+		return has;
+	},
+
+	_create: function() {
+
+		var margins,
+			o = this.options,
+			that = this;
+		this._addClass( "ui-resizable" );
+
+		$.extend( this, {
+			_aspectRatio: !!( o.aspectRatio ),
+			aspectRatio: o.aspectRatio,
+			originalElement: this.element,
+			_proportionallyResizeElements: [],
+			_helper: o.helper || o.ghost || o.animate ? o.helper || "ui-resizable-helper" : null
+		} );
+
+		// Wrap the element if it cannot hold child nodes
+		if ( this.element[ 0 ].nodeName.match( /^(canvas|textarea|input|select|button|img)$/i ) ) {
+
+			this.element.wrap(
+				$( "<div class='ui-wrapper' style='overflow: hidden;'></div>" ).css( {
+					position: this.element.css( "position" ),
+					width: this.element.outerWidth(),
+					height: this.element.outerHeight(),
+					top: this.element.css( "top" ),
+					left: this.element.css( "left" )
+				} )
+			);
+
+			this.element = this.element.parent().data(
+				"ui-resizable", this.element.resizable( "instance" )
+			);
+
+			this.elementIsWrapper = true;
+
+			margins = {
+				marginTop: this.originalElement.css( "marginTop" ),
+				marginRight: this.originalElement.css( "marginRight" ),
+				marginBottom: this.originalElement.css( "marginBottom" ),
+				marginLeft: this.originalElement.css( "marginLeft" )
+			};
+
+			this.element.css( margins );
+			this.originalElement.css( "margin", 0 );
+
+			// support: Safari
+			// Prevent Safari textarea resize
+			this.originalResizeStyle = this.originalElement.css( "resize" );
+			this.originalElement.css( "resize", "none" );
+
+			this._proportionallyResizeElements.push( this.originalElement.css( {
+				position: "static",
+				zoom: 1,
+				display: "block"
+			} ) );
+
+			// Support: IE9
+			// avoid IE jump (hard set the margin)
+			this.originalElement.css( margins );
+
+			this._proportionallyResize();
+		}
+
+		this._setupHandles();
+
+		if ( o.autoHide ) {
+			$( this.element )
+				.on( "mouseenter", function() {
+					if ( o.disabled ) {
+						return;
+					}
+					that._removeClass( "ui-resizable-autohide" );
+					that._handles.show();
+				} )
+				.on( "mouseleave", function() {
+					if ( o.disabled ) {
+						return;
+					}
+					if ( !that.resizing ) {
+						that._addClass( "ui-resizable-autohide" );
+						that._handles.hide();
+					}
+				} );
+		}
+
+		this._mouseInit();
+	},
+
+	_destroy: function() {
+
+		this._mouseDestroy();
+
+		var wrapper,
+			_destroy = function( exp ) {
+				$( exp )
+					.removeData( "resizable" )
+					.removeData( "ui-resizable" )
+					.off( ".resizable" )
+					.find( ".ui-resizable-handle" )
+						.remove();
+			};
+
+		// TODO: Unwrap at same DOM position
+		if ( this.elementIsWrapper ) {
+			_destroy( this.element );
+			wrapper = this.element;
+			this.originalElement.css( {
+				position: wrapper.css( "position" ),
+				width: wrapper.outerWidth(),
+				height: wrapper.outerHeight(),
+				top: wrapper.css( "top" ),
+				left: wrapper.css( "left" )
+			} ).insertAfter( wrapper );
+			wrapper.remove();
+		}
+
+		this.originalElement.css( "resize", this.originalResizeStyle );
+		_destroy( this.originalElement );
+
+		return this;
+	},
+
+	_setOption: function( key, value ) {
+		this._super( key, value );
+
+		switch ( key ) {
+		case "handles":
+			this._removeHandles();
+			this._setupHandles();
+			break;
+		default:
+			break;
+		}
+	},
+
+	_setupHandles: function() {
+		var o = this.options, handle, i, n, hname, axis, that = this;
+		this.handles = o.handles ||
+			( !$( ".ui-resizable-handle", this.element ).length ?
+				"e,s,se" : {
+					n: ".ui-resizable-n",
+					e: ".ui-resizable-e",
+					s: ".ui-resizable-s",
+					w: ".ui-resizable-w",
+					se: ".ui-resizable-se",
+					sw: ".ui-resizable-sw",
+					ne: ".ui-resizable-ne",
+					nw: ".ui-resizable-nw"
+				} );
+
+		this._handles = $();
+		if ( this.handles.constructor === String ) {
+
+			if ( this.handles === "all" ) {
+				this.handles = "n,e,s,w,se,sw,ne,nw";
+			}
+
+			n = this.handles.split( "," );
+			this.handles = {};
+
+			for ( i = 0; i < n.length; i++ ) {
+
+				handle = $.trim( n[ i ] );
+				hname = "ui-resizable-" + handle;
+				axis = $( "<div>" );
+				this._addClass( axis, "ui-resizable-handle " + hname );
+
+				axis.css( { zIndex: o.zIndex } );
+
+				this.handles[ handle ] = ".ui-resizable-" + handle;
+				this.element.append( axis );
+			}
+
+		}
+
+		this._renderAxis = function( target ) {
+
+			var i, axis, padPos, padWrapper;
+
+			target = target || this.element;
+
+			for ( i in this.handles ) {
+
+				if ( this.handles[ i ].constructor === String ) {
+					this.handles[ i ] = this.element.children( this.handles[ i ] ).first().show();
+				} else if ( this.handles[ i ].jquery || this.handles[ i ].nodeType ) {
+					this.handles[ i ] = $( this.handles[ i ] );
+					this._on( this.handles[ i ], { "mousedown": that._mouseDown } );
+				}
+
+				if ( this.elementIsWrapper &&
+						this.originalElement[ 0 ]
+							.nodeName
+							.match( /^(textarea|input|select|button)$/i ) ) {
+					axis = $( this.handles[ i ], this.element );
+
+					padWrapper = /sw|ne|nw|se|n|s/.test( i ) ?
+						axis.outerHeight() :
+						axis.outerWidth();
+
+					padPos = [ "padding",
+						/ne|nw|n/.test( i ) ? "Top" :
+						/se|sw|s/.test( i ) ? "Bottom" :
+						/^e$/.test( i ) ? "Right" : "Left" ].join( "" );
+
+					target.css( padPos, padWrapper );
+
+					this._proportionallyResize();
+				}
+
+				this._handles = this._handles.add( this.handles[ i ] );
+			}
+		};
+
+		// TODO: make renderAxis a prototype function
+		this._renderAxis( this.element );
+
+		this._handles = this._handles.add( this.element.find( ".ui-resizable-handle" ) );
+		this._handles.disableSelection();
+
+		this._handles.on( "mouseover", function() {
+			if ( !that.resizing ) {
+				if ( this.className ) {
+					axis = this.className.match( /ui-resizable-(se|sw|ne|nw|n|e|s|w)/i );
+				}
+				that.axis = axis && axis[ 1 ] ? axis[ 1 ] : "se";
+			}
+		} );
+
+		if ( o.autoHide ) {
+			this._handles.hide();
+			this._addClass( "ui-resizable-autohide" );
+		}
+	},
+
+	_removeHandles: function() {
+		this._handles.remove();
+	},
+
+	_mouseCapture: function( event ) {
+		var i, handle,
+			capture = false;
+
+		for ( i in this.handles ) {
+			handle = $( this.handles[ i ] )[ 0 ];
+			if ( handle === event.target || $.contains( handle, event.target ) ) {
+				capture = true;
+			}
+		}
+
+		return !this.options.disabled && capture;
+	},
+
+	_mouseStart: function( event ) {
+
+		var curleft, curtop, cursor,
+			o = this.options,
+			el = this.element;
+
+		this.resizing = true;
+
+		this._renderProxy();
+
+		curleft = this._num( this.helper.css( "left" ) );
+		curtop = this._num( this.helper.css( "top" ) );
+
+		if ( o.containment ) {
+			curleft += $( o.containment ).scrollLeft() || 0;
+			curtop += $( o.containment ).scrollTop() || 0;
+		}
+
+		this.offset = this.helper.offset();
+		this.position = { left: curleft, top: curtop };
+
+		this.size = this._helper ? {
+				width: this.helper.width(),
+				height: this.helper.height()
+			} : {
+				width: el.width(),
+				height: el.height()
+			};
+
+		this.originalSize = this._helper ? {
+				width: el.outerWidth(),
+				height: el.outerHeight()
+			} : {
+				width: el.width(),
+				height: el.height()
+			};
+
+		this.sizeDiff = {
+			width: el.outerWidth() - el.width(),
+			height: el.outerHeight() - el.height()
+		};
+
+		this.originalPosition = { left: curleft, top: curtop };
+		this.originalMousePosition = { left: event.pageX, top: event.pageY };
+
+		this.aspectRatio = ( typeof o.aspectRatio === "number" ) ?
+			o.aspectRatio :
+			( ( this.originalSize.width / this.originalSize.height ) || 1 );
+
+		cursor = $( ".ui-resizable-" + this.axis ).css( "cursor" );
+		$( "body" ).css( "cursor", cursor === "auto" ? this.axis + "-resize" : cursor );
+
+		this._addClass( "ui-resizable-resizing" );
+		this._propagate( "start", event );
+		return true;
+	},
+
+	_mouseDrag: function( event ) {
+
+		var data, props,
+			smp = this.originalMousePosition,
+			a = this.axis,
+			dx = ( event.pageX - smp.left ) || 0,
+			dy = ( event.pageY - smp.top ) || 0,
+			trigger = this._change[ a ];
+
+		this._updatePrevProperties();
+
+		if ( !trigger ) {
+			return false;
+		}
+
+		data = trigger.apply( this, [ event, dx, dy ] );
+
+		this._updateVirtualBoundaries( event.shiftKey );
+		if ( this._aspectRatio || event.shiftKey ) {
+			data = this._updateRatio( data, event );
+		}
+
+		data = this._respectSize( data, event );
+
+		this._updateCache( data );
+
+		this._propagate( "resize", event );
+
+		props = this._applyChanges();
+
+		if ( !this._helper && this._proportionallyResizeElements.length ) {
+			this._proportionallyResize();
+		}
+
+		if ( !$.isEmptyObject( props ) ) {
+			this._updatePrevProperties();
+			this._trigger( "resize", event, this.ui() );
+			this._applyChanges();
+		}
+
+		return false;
+	},
+
+	_mouseStop: function( event ) {
+
+		this.resizing = false;
+		var pr, ista, soffseth, soffsetw, s, left, top,
+			o = this.options, that = this;
+
+		if ( this._helper ) {
+
+			pr = this._proportionallyResizeElements;
+			ista = pr.length && ( /textarea/i ).test( pr[ 0 ].nodeName );
+			soffseth = ista && this._hasScroll( pr[ 0 ], "left" ) ? 0 : that.sizeDiff.height;
+			soffsetw = ista ? 0 : that.sizeDiff.width;
+
+			s = {
+				width: ( that.helper.width()  - soffsetw ),
+				height: ( that.helper.height() - soffseth )
+			};
+			left = ( parseFloat( that.element.css( "left" ) ) +
+				( that.position.left - that.originalPosition.left ) ) || null;
+			top = ( parseFloat( that.element.css( "top" ) ) +
+				( that.position.top - that.originalPosition.top ) ) || null;
+
+			if ( !o.animate ) {
+				this.element.css( $.extend( s, { top: top, left: left } ) );
+			}
+
+			that.helper.height( that.size.height );
+			that.helper.width( that.size.width );
+
+			if ( this._helper && !o.animate ) {
+				this._proportionallyResize();
+			}
+		}
+
+		$( "body" ).css( "cursor", "auto" );
+
+		this._removeClass( "ui-resizable-resizing" );
+
+		this._propagate( "stop", event );
+
+		if ( this._helper ) {
+			this.helper.remove();
+		}
+
+		return false;
+
+	},
+
+	_updatePrevProperties: function() {
+		this.prevPosition = {
+			top: this.position.top,
+			left: this.position.left
+		};
+		this.prevSize = {
+			width: this.size.width,
+			height: this.size.height
+		};
+	},
+
+	_applyChanges: function() {
+		var props = {};
+
+		if ( this.position.top !== this.prevPosition.top ) {
+			props.top = this.position.top + "px";
+		}
+		if ( this.position.left !== this.prevPosition.left ) {
+			props.left = this.position.left + "px";
+		}
+		if ( this.size.width !== this.prevSize.width ) {
+			props.width = this.size.width + "px";
+		}
+		if ( this.size.height !== this.prevSize.height ) {
+			props.height = this.size.height + "px";
+		}
+
+		this.helper.css( props );
+
+		return props;
+	},
+
+	_updateVirtualBoundaries: function( forceAspectRatio ) {
+		var pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b,
+			o = this.options;
+
+		b = {
+			minWidth: this._isNumber( o.minWidth ) ? o.minWidth : 0,
+			maxWidth: this._isNumber( o.maxWidth ) ? o.maxWidth : Infinity,
+			minHeight: this._isNumber( o.minHeight ) ? o.minHeight : 0,
+			maxHeight: this._isNumber( o.maxHeight ) ? o.maxHeight : Infinity
+		};
+
+		if ( this._aspectRatio || forceAspectRatio ) {
+			pMinWidth = b.minHeight * this.aspectRatio;
+			pMinHeight = b.minWidth / this.aspectRatio;
+			pMaxWidth = b.maxHeight * this.aspectRatio;
+			pMaxHeight = b.maxWidth / this.aspectRatio;
+
+			if ( pMinWidth > b.minWidth ) {
+				b.minWidth = pMinWidth;
+			}
+			if ( pMinHeight > b.minHeight ) {
+				b.minHeight = pMinHeight;
+			}
+			if ( pMaxWidth < b.maxWidth ) {
+				b.maxWidth = pMaxWidth;
+			}
+			if ( pMaxHeight < b.maxHeight ) {
+				b.maxHeight = pMaxHeight;
+			}
+		}
+		this._vBoundaries = b;
+	},
+
+	_updateCache: function( data ) {
+		this.offset = this.helper.offset();
+		if ( this._isNumber( data.left ) ) {
+			this.position.left = data.left;
+		}
+		if ( this._isNumber( data.top ) ) {
+			this.position.top = data.top;
+		}
+		if ( this._isNumber( data.height ) ) {
+			this.size.height = data.height;
+		}
+		if ( this._isNumber( data.width ) ) {
+			this.size.width = data.width;
+		}
+	},
+
+	_updateRatio: function( data ) {
+
+		var cpos = this.position,
+			csize = this.size,
+			a = this.axis;
+
+		if ( this._isNumber( data.height ) ) {
+			data.width = ( data.height * this.aspectRatio );
+		} else if ( this._isNumber( data.width ) ) {
+			data.height = ( data.width / this.aspectRatio );
+		}
+
+		if ( a === "sw" ) {
+			data.left = cpos.left + ( csize.width - data.width );
+			data.top = null;
+		}
+		if ( a === "nw" ) {
+			data.top = cpos.top + ( csize.height - data.height );
+			data.left = cpos.left + ( csize.width - data.width );
+		}
+
+		return data;
+	},
+
+	_respectSize: function( data ) {
+
+		var o = this._vBoundaries,
+			a = this.axis,
+			ismaxw = this._isNumber( data.width ) && o.maxWidth && ( o.maxWidth < data.width ),
+			ismaxh = this._isNumber( data.height ) && o.maxHeight && ( o.maxHeight < data.height ),
+			isminw = this._isNumber( data.width ) && o.minWidth && ( o.minWidth > data.width ),
+			isminh = this._isNumber( data.height ) && o.minHeight && ( o.minHeight > data.height ),
+			dw = this.originalPosition.left + this.originalSize.width,
+			dh = this.originalPosition.top + this.originalSize.height,
+			cw = /sw|nw|w/.test( a ), ch = /nw|ne|n/.test( a );
+		if ( isminw ) {
+			data.width = o.minWidth;
+		}
+		if ( isminh ) {
+			data.height = o.minHeight;
+		}
+		if ( ismaxw ) {
+			data.width = o.maxWidth;
+		}
+		if ( ismaxh ) {
+			data.height = o.maxHeight;
+		}
+
+		if ( isminw && cw ) {
+			data.left = dw - o.minWidth;
+		}
+		if ( ismaxw && cw ) {
+			data.left = dw - o.maxWidth;
+		}
+		if ( isminh && ch ) {
+			data.top = dh - o.minHeight;
+		}
+		if ( ismaxh && ch ) {
+			data.top = dh - o.maxHeight;
+		}
+
+		// Fixing jump error on top/left - bug #2330
+		if ( !data.width && !data.height && !data.left && data.top ) {
+			data.top = null;
+		} else if ( !data.width && !data.height && !data.top && data.left ) {
+			data.left = null;
+		}
+
+		return data;
+	},
+
+	_getPaddingPlusBorderDimensions: function( element ) {
+		var i = 0,
+			widths = [],
+			borders = [
+				element.css( "borderTopWidth" ),
+				element.css( "borderRightWidth" ),
+				element.css( "borderBottomWidth" ),
+				element.css( "borderLeftWidth" )
+			],
+			paddings = [
+				element.css( "paddingTop" ),
+				element.css( "paddingRight" ),
+				element.css( "paddingBottom" ),
+				element.css( "paddingLeft" )
+			];
+
+		for ( ; i < 4; i++ ) {
+			widths[ i ] = ( parseFloat( borders[ i ] ) || 0 );
+			widths[ i ] += ( parseFloat( paddings[ i ] ) || 0 );
+		}
+
+		return {
+			height: widths[ 0 ] + widths[ 2 ],
+			width: widths[ 1 ] + widths[ 3 ]
+		};
+	},
+
+	_proportionallyResize: function() {
+
+		if ( !this._proportionallyResizeElements.length ) {
+			return;
+		}
+
+		var prel,
+			i = 0,
+			element = this.helper || this.element;
+
+		for ( ; i < this._proportionallyResizeElements.length; i++ ) {
+
+			prel = this._proportionallyResizeElements[ i ];
+
+			// TODO: Seems like a bug to cache this.outerDimensions
+			// considering that we are in a loop.
+			if ( !this.outerDimensions ) {
+				this.outerDimensions = this._getPaddingPlusBorderDimensions( prel );
+			}
+
+			prel.css( {
+				height: ( element.height() - this.outerDimensions.height ) || 0,
+				width: ( element.width() - this.outerDimensions.width ) || 0
+			} );
+
+		}
+
+	},
+
+	_renderProxy: function() {
+
+		var el = this.element, o = this.options;
+		this.elementOffset = el.offset();
+
+		if ( this._helper ) {
+
+			this.helper = this.helper || $( "<div style='overflow:hidden;'></div>" );
+
+			this._addClass( this.helper, this._helper );
+			this.helper.css( {
+				width: this.element.outerWidth(),
+				height: this.element.outerHeight(),
+				position: "absolute",
+				left: this.elementOffset.left + "px",
+				top: this.elementOffset.top + "px",
+				zIndex: ++o.zIndex //TODO: Don't modify option
+			} );
+
+			this.helper
+				.appendTo( "body" )
+				.disableSelection();
+
+		} else {
+			this.helper = this.element;
+		}
+
+	},
+
+	_change: {
+		e: function( event, dx ) {
+			return { width: this.originalSize.width + dx };
+		},
+		w: function( event, dx ) {
+			var cs = this.originalSize, sp = this.originalPosition;
+			return { left: sp.left + dx, width: cs.width - dx };
+		},
+		n: function( event, dx, dy ) {
+			var cs = this.originalSize, sp = this.originalPosition;
+			return { top: sp.top + dy, height: cs.height - dy };
+		},
+		s: function( event, dx, dy ) {
+			return { height: this.originalSize.height + dy };
+		},
+		se: function( event, dx, dy ) {
+			return $.extend( this._change.s.apply( this, arguments ),
+				this._change.e.apply( this, [ event, dx, dy ] ) );
+		},
+		sw: function( event, dx, dy ) {
+			return $.extend( this._change.s.apply( this, arguments ),
+				this._change.w.apply( this, [ event, dx, dy ] ) );
+		},
+		ne: function( event, dx, dy ) {
+			return $.extend( this._change.n.apply( this, arguments ),
+				this._change.e.apply( this, [ event, dx, dy ] ) );
+		},
+		nw: function( event, dx, dy ) {
+			return $.extend( this._change.n.apply( this, arguments ),
+				this._change.w.apply( this, [ event, dx, dy ] ) );
+		}
+	},
+
+	_propagate: function( n, event ) {
+		$.ui.plugin.call( this, n, [ event, this.ui() ] );
+		( n !== "resize" && this._trigger( n, event, this.ui() ) );
+	},
+
+	plugins: {},
+
+	ui: function() {
+		return {
+			originalElement: this.originalElement,
+			element: this.element,
+			helper: this.helper,
+			position: this.position,
+			size: this.size,
+			originalSize: this.originalSize,
+			originalPosition: this.originalPosition
+		};
+	}
+
+} );
+
+/*
+ * Resizable Extensions
+ */
+
+$.ui.plugin.add( "resizable", "animate", {
+
+	stop: function( event ) {
+		var that = $( this ).resizable( "instance" ),
+			o = that.options,
+			pr = that._proportionallyResizeElements,
+			ista = pr.length && ( /textarea/i ).test( pr[ 0 ].nodeName ),
+			soffseth = ista && that._hasScroll( pr[ 0 ], "left" ) ? 0 : that.sizeDiff.height,
+			soffsetw = ista ? 0 : that.sizeDiff.width,
+			style = {
+				width: ( that.size.width - soffsetw ),
+				height: ( that.size.height - soffseth )
+			},
+			left = ( parseFloat( that.element.css( "left" ) ) +
+				( that.position.left - that.originalPosition.left ) ) || null,
+			top = ( parseFloat( that.element.css( "top" ) ) +
+				( that.position.top - that.originalPosition.top ) ) || null;
+
+		that.element.animate(
+			$.extend( style, top && left ? { top: top, left: left } : {} ), {
+				duration: o.animateDuration,
+				easing: o.animateEasing,
+				step: function() {
+
+					var data = {
+						width: parseFloat( that.element.css( "width" ) ),
+						height: parseFloat( that.element.css( "height" ) ),
+						top: parseFloat( that.element.css( "top" ) ),
+						left: parseFloat( that.element.css( "left" ) )
+					};
+
+					if ( pr && pr.length ) {
+						$( pr[ 0 ] ).css( { width: data.width, height: data.height } );
+					}
+
+					// Propagating resize, and updating values for each animation step
+					that._updateCache( data );
+					that._propagate( "resize", event );
+
+				}
+			}
+		);
+	}
+
+} );
+
+$.ui.plugin.add( "resizable", "containment", {
+
+	start: function() {
+		var element, p, co, ch, cw, width, height,
+			that = $( this ).resizable( "instance" ),
+			o = that.options,
+			el = that.element,
+			oc = o.containment,
+			ce = ( oc instanceof $ ) ?
+				oc.get( 0 ) :
+				( /parent/.test( oc ) ) ? el.parent().get( 0 ) : oc;
+
+		if ( !ce ) {
+			return;
+		}
+
+		that.containerElement = $( ce );
+
+		if ( /document/.test( oc ) || oc === document ) {
+			that.containerOffset = {
+				left: 0,
+				top: 0
+			};
+			that.containerPosition = {
+				left: 0,
+				top: 0
+			};
+
+			that.parentData = {
+				element: $( document ),
+				left: 0,
+				top: 0,
+				width: $( document ).width(),
+				height: $( document ).height() || document.body.parentNode.scrollHeight
+			};
+		} else {
+			element = $( ce );
+			p = [];
+			$( [ "Top", "Right", "Left", "Bottom" ] ).each( function( i, name ) {
+				p[ i ] = that._num( element.css( "padding" + name ) );
+			} );
+
+			that.containerOffset = element.offset();
+			that.containerPosition = element.position();
+			that.containerSize = {
+				height: ( element.innerHeight() - p[ 3 ] ),
+				width: ( element.innerWidth() - p[ 1 ] )
+			};
+
+			co = that.containerOffset;
+			ch = that.containerSize.height;
+			cw = that.containerSize.width;
+			width = ( that._hasScroll ( ce, "left" ) ? ce.scrollWidth : cw );
+			height = ( that._hasScroll ( ce ) ? ce.scrollHeight : ch ) ;
+
+			that.parentData = {
+				element: ce,
+				left: co.left,
+				top: co.top,
+				width: width,
+				height: height
+			};
+		}
+	},
+
+	resize: function( event ) {
+		var woset, hoset, isParent, isOffsetRelative,
+			that = $( this ).resizable( "instance" ),
+			o = that.options,
+			co = that.containerOffset,
+			cp = that.position,
+			pRatio = that._aspectRatio || event.shiftKey,
+			cop = {
+				top: 0,
+				left: 0
+			},
+			ce = that.containerElement,
+			continueResize = true;
+
+		if ( ce[ 0 ] !== document && ( /static/ ).test( ce.css( "position" ) ) ) {
+			cop = co;
+		}
+
+		if ( cp.left < ( that._helper ? co.left : 0 ) ) {
+			that.size.width = that.size.width +
+				( that._helper ?
+					( that.position.left - co.left ) :
+					( that.position.left - cop.left ) );
+
+			if ( pRatio ) {
+				that.size.height = that.size.width / that.aspectRatio;
+				continueResize = false;
+			}
+			that.position.left = o.helper ? co.left : 0;
+		}
+
+		if ( cp.top < ( that._helper ? co.top : 0 ) ) {
+			that.size.height = that.size.height +
+				( that._helper ?
+					( that.position.top - co.top ) :
+					that.position.top );
+
+			if ( pRatio ) {
+				that.size.width = that.size.height * that.aspectRatio;
+				continueResize = false;
+			}
+			that.position.top = that._helper ? co.top : 0;
+		}
+
+		isParent = that.containerElement.get( 0 ) === that.element.parent().get( 0 );
+		isOffsetRelative = /relative|absolute/.test( that.containerElement.css( "position" ) );
+
+		if ( isParent && isOffsetRelative ) {
+			that.offset.left = that.parentData.left + that.position.left;
+			that.offset.top = that.parentData.top + that.position.top;
+		} else {
+			that.offset.left = that.element.offset().left;
+			that.offset.top = that.element.offset().top;
+		}
+
+		woset = Math.abs( that.sizeDiff.width +
+			( that._helper ?
+				that.offset.left - cop.left :
+				( that.offset.left - co.left ) ) );
+
+		hoset = Math.abs( that.sizeDiff.height +
+			( that._helper ?
+				that.offset.top - cop.top :
+				( that.offset.top - co.top ) ) );
+
+		if ( woset + that.size.width >= that.parentData.width ) {
+			that.size.width = that.parentData.width - woset;
+			if ( pRatio ) {
+				that.size.height = that.size.width / that.aspectRatio;
+				continueResize = false;
+			}
+		}
+
+		if ( hoset + that.size.height >= that.parentData.height ) {
+			that.size.height = that.parentData.height - hoset;
+			if ( pRatio ) {
+				that.size.width = that.size.height * that.aspectRatio;
+				continueResize = false;
+			}
+		}
+
+		if ( !continueResize ) {
+			that.position.left = that.prevPosition.left;
+			that.position.top = that.prevPosition.top;
+			that.size.width = that.prevSize.width;
+			that.size.height = that.prevSize.height;
+		}
+	},
+
+	stop: function() {
+		var that = $( this ).resizable( "instance" ),
+			o = that.options,
+			co = that.containerOffset,
+			cop = that.containerPosition,
+			ce = that.containerElement,
+			helper = $( that.helper ),
+			ho = helper.offset(),
+			w = helper.outerWidth() - that.sizeDiff.width,
+			h = helper.outerHeight() - that.sizeDiff.height;
+
+		if ( that._helper && !o.animate && ( /relative/ ).test( ce.css( "position" ) ) ) {
+			$( this ).css( {
+				left: ho.left - cop.left - co.left,
+				width: w,
+				height: h
+			} );
+		}
+
+		if ( that._helper && !o.animate && ( /static/ ).test( ce.css( "position" ) ) ) {
+			$( this ).css( {
+				left: ho.left - cop.left - co.left,
+				width: w,
+				height: h
+			} );
+		}
+	}
+} );
+
+$.ui.plugin.add( "resizable", "alsoResize", {
+
+	start: function() {
+		var that = $( this ).resizable( "instance" ),
+			o = that.options;
+
+		$( o.alsoResize ).each( function() {
+			var el = $( this );
+			el.data( "ui-resizable-alsoresize", {
+				width: parseFloat( el.width() ), height: parseFloat( el.height() ),
+				left: parseFloat( el.css( "left" ) ), top: parseFloat( el.css( "top" ) )
+			} );
+		} );
+	},
+
+	resize: function( event, ui ) {
+		var that = $( this ).resizable( "instance" ),
+			o = that.options,
+			os = that.originalSize,
+			op = that.originalPosition,
+			delta = {
+				height: ( that.size.height - os.height ) || 0,
+				width: ( that.size.width - os.width ) || 0,
+				top: ( that.position.top - op.top ) || 0,
+				left: ( that.position.left - op.left ) || 0
+			};
+
+			$( o.alsoResize ).each( function() {
+				var el = $( this ), start = $( this ).data( "ui-resizable-alsoresize" ), style = {},
+					css = el.parents( ui.originalElement[ 0 ] ).length ?
+							[ "width", "height" ] :
+							[ "width", "height", "top", "left" ];
+
+				$.each( css, function( i, prop ) {
+					var sum = ( start[ prop ] || 0 ) + ( delta[ prop ] || 0 );
+					if ( sum && sum >= 0 ) {
+						style[ prop ] = sum || null;
+					}
+				} );
+
+				el.css( style );
+			} );
+	},
+
+	stop: function() {
+		$( this ).removeData( "ui-resizable-alsoresize" );
+	}
+} );
+
+$.ui.plugin.add( "resizable", "ghost", {
+
+	start: function() {
+
+		var that = $( this ).resizable( "instance" ), cs = that.size;
+
+		that.ghost = that.originalElement.clone();
+		that.ghost.css( {
+			opacity: 0.25,
+			display: "block",
+			position: "relative",
+			height: cs.height,
+			width: cs.width,
+			margin: 0,
+			left: 0,
+			top: 0
+		} );
+
+		that._addClass( that.ghost, "ui-resizable-ghost" );
+
+		// DEPRECATED
+		// TODO: remove after 1.12
+		if ( $.uiBackCompat !== false && typeof that.options.ghost === "string" ) {
+
+			// Ghost option
+			that.ghost.addClass( this.options.ghost );
+		}
+
+		that.ghost.appendTo( that.helper );
+
+	},
+
+	resize: function() {
+		var that = $( this ).resizable( "instance" );
+		if ( that.ghost ) {
+			that.ghost.css( {
+				position: "relative",
+				height: that.size.height,
+				width: that.size.width
+			} );
+		}
+	},
+
+	stop: function() {
+		var that = $( this ).resizable( "instance" );
+		if ( that.ghost && that.helper ) {
+			that.helper.get( 0 ).removeChild( that.ghost.get( 0 ) );
+		}
+	}
+
+} );
+
+$.ui.plugin.add( "resizable", "grid", {
+
+	resize: function() {
+		var outerDimensions,
+			that = $( this ).resizable( "instance" ),
+			o = that.options,
+			cs = that.size,
+			os = that.originalSize,
+			op = that.originalPosition,
+			a = that.axis,
+			grid = typeof o.grid === "number" ? [ o.grid, o.grid ] : o.grid,
+			gridX = ( grid[ 0 ] || 1 ),
+			gridY = ( grid[ 1 ] || 1 ),
+			ox = Math.round( ( cs.width - os.width ) / gridX ) * gridX,
+			oy = Math.round( ( cs.height - os.height ) / gridY ) * gridY,
+			newWidth = os.width + ox,
+			newHeight = os.height + oy,
+			isMaxWidth = o.maxWidth && ( o.maxWidth < newWidth ),
+			isMaxHeight = o.maxHeight && ( o.maxHeight < newHeight ),
+			isMinWidth = o.minWidth && ( o.minWidth > newWidth ),
+			isMinHeight = o.minHeight && ( o.minHeight > newHeight );
+
+		o.grid = grid;
+
+		if ( isMinWidth ) {
+			newWidth += gridX;
+		}
+		if ( isMinHeight ) {
+			newHeight += gridY;
+		}
+		if ( isMaxWidth ) {
+			newWidth -= gridX;
+		}
+		if ( isMaxHeight ) {
+			newHeight -= gridY;
+		}
+
+		if ( /^(se|s|e)$/.test( a ) ) {
+			that.size.width = newWidth;
+			that.size.height = newHeight;
+		} else if ( /^(ne)$/.test( a ) ) {
+			that.size.width = newWidth;
+			that.size.height = newHeight;
+			that.position.top = op.top - oy;
+		} else if ( /^(sw)$/.test( a ) ) {
+			that.size.width = newWidth;
+			that.size.height = newHeight;
+			that.position.left = op.left - ox;
+		} else {
+			if ( newHeight - gridY <= 0 || newWidth - gridX <= 0 ) {
+				outerDimensions = that._getPaddingPlusBorderDimensions( this );
+			}
+
+			if ( newHeight - gridY > 0 ) {
+				that.size.height = newHeight;
+				that.position.top = op.top - oy;
+			} else {
+				newHeight = gridY - outerDimensions.height;
+				that.size.height = newHeight;
+				that.position.top = op.top + os.height - newHeight;
+			}
+			if ( newWidth - gridX > 0 ) {
+				that.size.width = newWidth;
+				that.position.left = op.left - ox;
+			} else {
+				newWidth = gridX - outerDimensions.width;
+				that.size.width = newWidth;
+				that.position.left = op.left + os.width - newWidth;
+			}
+		}
+	}
+
+} );
+
+var widgetsResizable = $.ui.resizable;
+
+
+/*!
+ * jQuery UI Dialog 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Dialog
+//>>group: Widgets
+//>>description: Displays customizable dialog windows.
+//>>docs: http://api.jqueryui.com/dialog/
+//>>demos: http://jqueryui.com/dialog/
+//>>css.structure: ../../themes/base/core.css
+//>>css.structure: ../../themes/base/dialog.css
+//>>css.theme: ../../themes/base/theme.css
+
+
+
+$.widget( "ui.dialog", {
+	version: "1.12.1",
+	options: {
+		appendTo: "body",
+		autoOpen: true,
+		buttons: [],
+		classes: {
+			"ui-dialog": "ui-corner-all",
+			"ui-dialog-titlebar": "ui-corner-all"
+		},
+		closeOnEscape: true,
+		closeText: "Close",
+		draggable: true,
+		hide: null,
+		height: "auto",
+		maxHeight: null,
+		maxWidth: null,
+		minHeight: 150,
+		minWidth: 150,
+		modal: false,
+		position: {
+			my: "center",
+			at: "center",
+			of: window,
+			collision: "fit",
+
+			// Ensure the titlebar is always visible
+			using: function( pos ) {
+				var topOffset = $( this ).css( pos ).offset().top;
+				if ( topOffset < 0 ) {
+					$( this ).css( "top", pos.top - topOffset );
+				}
+			}
+		},
+		resizable: true,
+		show: null,
+		title: null,
+		width: 300,
+
+		// Callbacks
+		beforeClose: null,
+		close: null,
+		drag: null,
+		dragStart: null,
+		dragStop: null,
+		focus: null,
+		open: null,
+		resize: null,
+		resizeStart: null,
+		resizeStop: null
+	},
+
+	sizeRelatedOptions: {
+		buttons: true,
+		height: true,
+		maxHeight: true,
+		maxWidth: true,
+		minHeight: true,
+		minWidth: true,
+		width: true
+	},
+
+	resizableRelatedOptions: {
+		maxHeight: true,
+		maxWidth: true,
+		minHeight: true,
+		minWidth: true
+	},
+
+	_create: function() {
+		this.originalCss = {
+			display: this.element[ 0 ].style.display,
+			width: this.element[ 0 ].style.width,
+			minHeight: this.element[ 0 ].style.minHeight,
+			maxHeight: this.element[ 0 ].style.maxHeight,
+			height: this.element[ 0 ].style.height
+		};
+		this.originalPosition = {
+			parent: this.element.parent(),
+			index: this.element.parent().children().index( this.element )
+		};
+		this.originalTitle = this.element.attr( "title" );
+		if ( this.options.title == null && this.originalTitle != null ) {
+			this.options.title = this.originalTitle;
+		}
+
+		// Dialogs can't be disabled
+		if ( this.options.disabled ) {
+			this.options.disabled = false;
+		}
+
+		this._createWrapper();
+
+		this.element
+			.show()
+			.removeAttr( "title" )
+			.appendTo( this.uiDialog );
+
+		this._addClass( "ui-dialog-content", "ui-widget-content" );
+
+		this._createTitlebar();
+		this._createButtonPane();
+
+		if ( this.options.draggable && $.fn.draggable ) {
+			this._makeDraggable();
+		}
+		if ( this.options.resizable && $.fn.resizable ) {
+			this._makeResizable();
+		}
+
+		this._isOpen = false;
+
+		this._trackFocus();
+	},
+
+	_init: function() {
+		if ( this.options.autoOpen ) {
+			this.open();
+		}
+	},
+
+	_appendTo: function() {
+		var element = this.options.appendTo;
+		if ( element && ( element.jquery || element.nodeType ) ) {
+			return $( element );
+		}
+		return this.document.find( element || "body" ).eq( 0 );
+	},
+
+	_destroy: function() {
+		var next,
+			originalPosition = this.originalPosition;
+
+		this._untrackInstance();
+		this._destroyOverlay();
+
+		this.element
+			.removeUniqueId()
+			.css( this.originalCss )
+
+			// Without detaching first, the following becomes really slow
+			.detach();
+
+		this.uiDialog.remove();
+
+		if ( this.originalTitle ) {
+			this.element.attr( "title", this.originalTitle );
+		}
+
+		next = originalPosition.parent.children().eq( originalPosition.index );
+
+		// Don't try to place the dialog next to itself (#8613)
+		if ( next.length && next[ 0 ] !== this.element[ 0 ] ) {
+			next.before( this.element );
+		} else {
+			originalPosition.parent.append( this.element );
+		}
+	},
+
+	widget: function() {
+		return this.uiDialog;
+	},
+
+	disable: $.noop,
+	enable: $.noop,
+
+	close: function( event ) {
+		var that = this;
+
+		if ( !this._isOpen || this._trigger( "beforeClose", event ) === false ) {
+			return;
+		}
+
+		this._isOpen = false;
+		this._focusedElement = null;
+		this._destroyOverlay();
+		this._untrackInstance();
+
+		if ( !this.opener.filter( ":focusable" ).trigger( "focus" ).length ) {
+
+			// Hiding a focused element doesn't trigger blur in WebKit
+			// so in case we have nothing to focus on, explicitly blur the active element
+			// https://bugs.webkit.org/show_bug.cgi?id=47182
+			$.ui.safeBlur( $.ui.safeActiveElement( this.document[ 0 ] ) );
+		}
+
+		this._hide( this.uiDialog, this.options.hide, function() {
+			that._trigger( "close", event );
+		} );
+	},
+
+	isOpen: function() {
+		return this._isOpen;
+	},
+
+	moveToTop: function() {
+		this._moveToTop();
+	},
+
+	_moveToTop: function( event, silent ) {
+		var moved = false,
+			zIndices = this.uiDialog.siblings( ".ui-front:visible" ).map( function() {
+				return +$( this ).css( "z-index" );
+			} ).get(),
+			zIndexMax = Math.max.apply( null, zIndices );
+
+		if ( zIndexMax >= +this.uiDialog.css( "z-index" ) ) {
+			this.uiDialog.css( "z-index", zIndexMax + 1 );
+			moved = true;
+		}
+
+		if ( moved && !silent ) {
+			this._trigger( "focus", event );
+		}
+		return moved;
+	},
+
+	open: function() {
+		var that = this;
+		if ( this._isOpen ) {
+			if ( this._moveToTop() ) {
+				this._focusTabbable();
+			}
+			return;
+		}
+
+		this._isOpen = true;
+		this.opener = $( $.ui.safeActiveElement( this.document[ 0 ] ) );
+
+		this._size();
+		this._position();
+		this._createOverlay();
+		this._moveToTop( null, true );
+
+		// Ensure the overlay is moved to the top with the dialog, but only when
+		// opening. The overlay shouldn't move after the dialog is open so that
+		// modeless dialogs opened after the modal dialog stack properly.
+		if ( this.overlay ) {
+			this.overlay.css( "z-index", this.uiDialog.css( "z-index" ) - 1 );
+		}
+
+		this._show( this.uiDialog, this.options.show, function() {
+			that._focusTabbable();
+			that._trigger( "focus" );
+		} );
+
+		// Track the dialog immediately upon openening in case a focus event
+		// somehow occurs outside of the dialog before an element inside the
+		// dialog is focused (#10152)
+		this._makeFocusTarget();
+
+		this._trigger( "open" );
+	},
+
+	_focusTabbable: function() {
+
+		// Set focus to the first match:
+		// 1. An element that was focused previously
+		// 2. First element inside the dialog matching [autofocus]
+		// 3. Tabbable element inside the content element
+		// 4. Tabbable element inside the buttonpane
+		// 5. The close button
+		// 6. The dialog itself
+		var hasFocus = this._focusedElement;
+		if ( !hasFocus ) {
+			hasFocus = this.element.find( "[autofocus]" );
+		}
+		if ( !hasFocus.length ) {
+			hasFocus = this.element.find( ":tabbable" );
+		}
+		if ( !hasFocus.length ) {
+			hasFocus = this.uiDialogButtonPane.find( ":tabbable" );
+		}
+		if ( !hasFocus.length ) {
+			hasFocus = this.uiDialogTitlebarClose.filter( ":tabbable" );
+		}
+		if ( !hasFocus.length ) {
+			hasFocus = this.uiDialog;
+		}
+		hasFocus.eq( 0 ).trigger( "focus" );
+	},
+
+	_keepFocus: function( event ) {
+		function checkFocus() {
+			var activeElement = $.ui.safeActiveElement( this.document[ 0 ] ),
+				isActive = this.uiDialog[ 0 ] === activeElement ||
+					$.contains( this.uiDialog[ 0 ], activeElement );
+			if ( !isActive ) {
+				this._focusTabbable();
+			}
+		}
+		event.preventDefault();
+		checkFocus.call( this );
+
+		// support: IE
+		// IE <= 8 doesn't prevent moving focus even with event.preventDefault()
+		// so we check again later
+		this._delay( checkFocus );
+	},
+
+	_createWrapper: function() {
+		this.uiDialog = $( "<div>" )
+			.hide()
+			.attr( {
+
+				// Setting tabIndex makes the div focusable
+				tabIndex: -1,
+				role: "dialog"
+			} )
+			.appendTo( this._appendTo() );
+
+		this._addClass( this.uiDialog, "ui-dialog", "ui-widget ui-widget-content ui-front" );
+		this._on( this.uiDialog, {
+			keydown: function( event ) {
+				if ( this.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode &&
+						event.keyCode === $.ui.keyCode.ESCAPE ) {
+					event.preventDefault();
+					this.close( event );
+					return;
+				}
+
+				// Prevent tabbing out of dialogs
+				if ( event.keyCode !== $.ui.keyCode.TAB || event.isDefaultPrevented() ) {
+					return;
+				}
+				var tabbables = this.uiDialog.find( ":tabbable" ),
+					first = tabbables.filter( ":first" ),
+					last = tabbables.filter( ":last" );
+
+				if ( ( event.target === last[ 0 ] || event.target === this.uiDialog[ 0 ] ) &&
+						!event.shiftKey ) {
+					this._delay( function() {
+						first.trigger( "focus" );
+					} );
+					event.preventDefault();
+				} else if ( ( event.target === first[ 0 ] ||
+						event.target === this.uiDialog[ 0 ] ) && event.shiftKey ) {
+					this._delay( function() {
+						last.trigger( "focus" );
+					} );
+					event.preventDefault();
+				}
+			},
+			mousedown: function( event ) {
+				if ( this._moveToTop( event ) ) {
+					this._focusTabbable();
+				}
+			}
+		} );
+
+		// We assume that any existing aria-describedby attribute means
+		// that the dialog content is marked up properly
+		// otherwise we brute force the content as the description
+		if ( !this.element.find( "[aria-describedby]" ).length ) {
+			this.uiDialog.attr( {
+				"aria-describedby": this.element.uniqueId().attr( "id" )
+			} );
+		}
+	},
+
+	_createTitlebar: function() {
+		var uiDialogTitle;
+
+		this.uiDialogTitlebar = $( "<div>" );
+		this._addClass( this.uiDialogTitlebar,
+			"ui-dialog-titlebar", "ui-widget-header ui-helper-clearfix" );
+		this._on( this.uiDialogTitlebar, {
+			mousedown: function( event ) {
+
+				// Don't prevent click on close button (#8838)
+				// Focusing a dialog that is partially scrolled out of view
+				// causes the browser to scroll it into view, preventing the click event
+				if ( !$( event.target ).closest( ".ui-dialog-titlebar-close" ) ) {
+
+					// Dialog isn't getting focus when dragging (#8063)
+					this.uiDialog.trigger( "focus" );
+				}
+			}
+		} );
+
+		// Support: IE
+		// Use type="button" to prevent enter keypresses in textboxes from closing the
+		// dialog in IE (#9312)
+		this.uiDialogTitlebarClose = $( "<button type='button'></button>" )
+			.button( {
+				label: $( "<a>" ).text( this.options.closeText ).html(),
+				icon: "ui-icon-closethick",
+				showLabel: false
+			} )
+			.appendTo( this.uiDialogTitlebar );
+
+		this._addClass( this.uiDialogTitlebarClose, "ui-dialog-titlebar-close" );
+		this._on( this.uiDialogTitlebarClose, {
+			click: function( event ) {
+				event.preventDefault();
+				this.close( event );
+			}
+		} );
+
+		uiDialogTitle = $( "<span>" ).uniqueId().prependTo( this.uiDialogTitlebar );
+		this._addClass( uiDialogTitle, "ui-dialog-title" );
+		this._title( uiDialogTitle );
+
+		this.uiDialogTitlebar.prependTo( this.uiDialog );
+
+		this.uiDialog.attr( {
+			"aria-labelledby": uiDialogTitle.attr( "id" )
+		} );
+	},
+
+	_title: function( title ) {
+		if ( this.options.title ) {
+			title.text( this.options.title );
+		} else {
+			title.html( "&#160;" );
+		}
+	},
+
+	_createButtonPane: function() {
+		this.uiDialogButtonPane = $( "<div>" );
+		this._addClass( this.uiDialogButtonPane, "ui-dialog-buttonpane",
+			"ui-widget-content ui-helper-clearfix" );
+
+		this.uiButtonSet = $( "<div>" )
+			.appendTo( this.uiDialogButtonPane );
+		this._addClass( this.uiButtonSet, "ui-dialog-buttonset" );
+
+		this._createButtons();
+	},
+
+	_createButtons: function() {
+		var that = this,
+			buttons = this.options.buttons;
+
+		// If we already have a button pane, remove it
+		this.uiDialogButtonPane.remove();
+		this.uiButtonSet.empty();
+
+		if ( $.isEmptyObject( buttons ) || ( $.isArray( buttons ) && !buttons.length ) ) {
+			this._removeClass( this.uiDialog, "ui-dialog-buttons" );
+			return;
+		}
+
+		$.each( buttons, function( name, props ) {
+			var click, buttonOptions;
+			props = $.isFunction( props ) ?
+				{ click: props, text: name } :
+				props;
+
+			// Default to a non-submitting button
+			props = $.extend( { type: "button" }, props );
+
+			// Change the context for the click callback to be the main element
+			click = props.click;
+			buttonOptions = {
+				icon: props.icon,
+				iconPosition: props.iconPosition,
+				showLabel: props.showLabel,
+
+				// Deprecated options
+				icons: props.icons,
+				text: props.text
+			};
+
+			delete props.click;
+			delete props.icon;
+			delete props.iconPosition;
+			delete props.showLabel;
+
+			// Deprecated options
+			delete props.icons;
+			if ( typeof props.text === "boolean" ) {
+				delete props.text;
+			}
+
+			$( "<button></button>", props )
+				.button( buttonOptions )
+				.appendTo( that.uiButtonSet )
+				.on( "click", function() {
+					click.apply( that.element[ 0 ], arguments );
+				} );
+		} );
+		this._addClass( this.uiDialog, "ui-dialog-buttons" );
+		this.uiDialogButtonPane.appendTo( this.uiDialog );
+	},
+
+	_makeDraggable: function() {
+		var that = this,
+			options = this.options;
+
+		function filteredUi( ui ) {
+			return {
+				position: ui.position,
+				offset: ui.offset
+			};
+		}
+
+		this.uiDialog.draggable( {
+			cancel: ".ui-dialog-content, .ui-dialog-titlebar-close",
+			handle: ".ui-dialog-titlebar",
+			containment: "document",
+			start: function( event, ui ) {
+				that._addClass( $( this ), "ui-dialog-dragging" );
+				that._blockFrames();
+				that._trigger( "dragStart", event, filteredUi( ui ) );
+			},
+			drag: function( event, ui ) {
+				that._trigger( "drag", event, filteredUi( ui ) );
+			},
+			stop: function( event, ui ) {
+				var left = ui.offset.left - that.document.scrollLeft(),
+					top = ui.offset.top - that.document.scrollTop();
+
+				options.position = {
+					my: "left top",
+					at: "left" + ( left >= 0 ? "+" : "" ) + left + " " +
+						"top" + ( top >= 0 ? "+" : "" ) + top,
+					of: that.window
+				};
+				that._removeClass( $( this ), "ui-dialog-dragging" );
+				that._unblockFrames();
+				that._trigger( "dragStop", event, filteredUi( ui ) );
+			}
+		} );
+	},
+
+	_makeResizable: function() {
+		var that = this,
+			options = this.options,
+			handles = options.resizable,
+
+			// .ui-resizable has position: relative defined in the stylesheet
+			// but dialogs have to use absolute or fixed positioning
+			position = this.uiDialog.css( "position" ),
+			resizeHandles = typeof handles === "string" ?
+				handles :
+				"n,e,s,w,se,sw,ne,nw";
+
+		function filteredUi( ui ) {
+			return {
+				originalPosition: ui.originalPosition,
+				originalSize: ui.originalSize,
+				position: ui.position,
+				size: ui.size
+			};
+		}
+
+		this.uiDialog.resizable( {
+			cancel: ".ui-dialog-content",
+			containment: "document",
+			alsoResize: this.element,
+			maxWidth: options.maxWidth,
+			maxHeight: options.maxHeight,
+			minWidth: options.minWidth,
+			minHeight: this._minHeight(),
+			handles: resizeHandles,
+			start: function( event, ui ) {
+				that._addClass( $( this ), "ui-dialog-resizing" );
+				that._blockFrames();
+				that._trigger( "resizeStart", event, filteredUi( ui ) );
+			},
+			resize: function( event, ui ) {
+				that._trigger( "resize", event, filteredUi( ui ) );
+			},
+			stop: function( event, ui ) {
+				var offset = that.uiDialog.offset(),
+					left = offset.left - that.document.scrollLeft(),
+					top = offset.top - that.document.scrollTop();
+
+				options.height = that.uiDialog.height();
+				options.width = that.uiDialog.width();
+				options.position = {
+					my: "left top",
+					at: "left" + ( left >= 0 ? "+" : "" ) + left + " " +
+						"top" + ( top >= 0 ? "+" : "" ) + top,
+					of: that.window
+				};
+				that._removeClass( $( this ), "ui-dialog-resizing" );
+				that._unblockFrames();
+				that._trigger( "resizeStop", event, filteredUi( ui ) );
+			}
+		} )
+			.css( "position", position );
+	},
+
+	_trackFocus: function() {
+		this._on( this.widget(), {
+			focusin: function( event ) {
+				this._makeFocusTarget();
+				this._focusedElement = $( event.target );
+			}
+		} );
+	},
+
+	_makeFocusTarget: function() {
+		this._untrackInstance();
+		this._trackingInstances().unshift( this );
+	},
+
+	_untrackInstance: function() {
+		var instances = this._trackingInstances(),
+			exists = $.inArray( this, instances );
+		if ( exists !== -1 ) {
+			instances.splice( exists, 1 );
+		}
+	},
+
+	_trackingInstances: function() {
+		var instances = this.document.data( "ui-dialog-instances" );
+		if ( !instances ) {
+			instances = [];
+			this.document.data( "ui-dialog-instances", instances );
+		}
+		return instances;
+	},
+
+	_minHeight: function() {
+		var options = this.options;
+
+		return options.height === "auto" ?
+			options.minHeight :
+			Math.min( options.minHeight, options.height );
+	},
+
+	_position: function() {
+
+		// Need to show the dialog to get the actual offset in the position plugin
+		var isVisible = this.uiDialog.is( ":visible" );
+		if ( !isVisible ) {
+			this.uiDialog.show();
+		}
+		this.uiDialog.position( this.options.position );
+		if ( !isVisible ) {
+			this.uiDialog.hide();
+		}
+	},
+
+	_setOptions: function( options ) {
+		var that = this,
+			resize = false,
+			resizableOptions = {};
+
+		$.each( options, function( key, value ) {
+			that._setOption( key, value );
+
+			if ( key in that.sizeRelatedOptions ) {
+				resize = true;
+			}
+			if ( key in that.resizableRelatedOptions ) {
+				resizableOptions[ key ] = value;
+			}
+		} );
+
+		if ( resize ) {
+			this._size();
+			this._position();
+		}
+		if ( this.uiDialog.is( ":data(ui-resizable)" ) ) {
+			this.uiDialog.resizable( "option", resizableOptions );
+		}
+	},
+
+	_setOption: function( key, value ) {
+		var isDraggable, isResizable,
+			uiDialog = this.uiDialog;
+
+		if ( key === "disabled" ) {
+			return;
+		}
+
+		this._super( key, value );
+
+		if ( key === "appendTo" ) {
+			this.uiDialog.appendTo( this._appendTo() );
+		}
+
+		if ( key === "buttons" ) {
+			this._createButtons();
+		}
+
+		if ( key === "closeText" ) {
+			this.uiDialogTitlebarClose.button( {
+
+				// Ensure that we always pass a string
+				label: $( "<a>" ).text( "" + this.options.closeText ).html()
+			} );
+		}
+
+		if ( key === "draggable" ) {
+			isDraggable = uiDialog.is( ":data(ui-draggable)" );
+			if ( isDraggable && !value ) {
+				uiDialog.draggable( "destroy" );
+			}
+
+			if ( !isDraggable && value ) {
+				this._makeDraggable();
+			}
+		}
+
+		if ( key === "position" ) {
+			this._position();
+		}
+
+		if ( key === "resizable" ) {
+
+			// currently resizable, becoming non-resizable
+			isResizable = uiDialog.is( ":data(ui-resizable)" );
+			if ( isResizable && !value ) {
+				uiDialog.resizable( "destroy" );
+			}
+
+			// Currently resizable, changing handles
+			if ( isResizable && typeof value === "string" ) {
+				uiDialog.resizable( "option", "handles", value );
+			}
+
+			// Currently non-resizable, becoming resizable
+			if ( !isResizable && value !== false ) {
+				this._makeResizable();
+			}
+		}
+
+		if ( key === "title" ) {
+			this._title( this.uiDialogTitlebar.find( ".ui-dialog-title" ) );
+		}
+	},
+
+	_size: function() {
+
+		// If the user has resized the dialog, the .ui-dialog and .ui-dialog-content
+		// divs will both have width and height set, so we need to reset them
+		var nonContentHeight, minContentHeight, maxContentHeight,
+			options = this.options;
+
+		// Reset content sizing
+		this.element.show().css( {
+			width: "auto",
+			minHeight: 0,
+			maxHeight: "none",
+			height: 0
+		} );
+
+		if ( options.minWidth > options.width ) {
+			options.width = options.minWidth;
+		}
+
+		// Reset wrapper sizing
+		// determine the height of all the non-content elements
+		nonContentHeight = this.uiDialog.css( {
+			height: "auto",
+			width: options.width
+		} )
+			.outerHeight();
+		minContentHeight = Math.max( 0, options.minHeight - nonContentHeight );
+		maxContentHeight = typeof options.maxHeight === "number" ?
+			Math.max( 0, options.maxHeight - nonContentHeight ) :
+			"none";
+
+		if ( options.height === "auto" ) {
+			this.element.css( {
+				minHeight: minContentHeight,
+				maxHeight: maxContentHeight,
+				height: "auto"
+			} );
+		} else {
+			this.element.height( Math.max( 0, options.height - nonContentHeight ) );
+		}
+
+		if ( this.uiDialog.is( ":data(ui-resizable)" ) ) {
+			this.uiDialog.resizable( "option", "minHeight", this._minHeight() );
+		}
+	},
+
+	_blockFrames: function() {
+		this.iframeBlocks = this.document.find( "iframe" ).map( function() {
+			var iframe = $( this );
+
+			return $( "<div>" )
+				.css( {
+					position: "absolute",
+					width: iframe.outerWidth(),
+					height: iframe.outerHeight()
+				} )
+				.appendTo( iframe.parent() )
+				.offset( iframe.offset() )[ 0 ];
+		} );
+	},
+
+	_unblockFrames: function() {
+		if ( this.iframeBlocks ) {
+			this.iframeBlocks.remove();
+			delete this.iframeBlocks;
+		}
+	},
+
+	_allowInteraction: function( event ) {
+		if ( $( event.target ).closest( ".ui-dialog" ).length ) {
+			return true;
+		}
+
+		// TODO: Remove hack when datepicker implements
+		// the .ui-front logic (#8989)
+		return !!$( event.target ).closest( ".ui-datepicker" ).length;
+	},
+
+	_createOverlay: function() {
+		if ( !this.options.modal ) {
+			return;
+		}
+
+		// We use a delay in case the overlay is created from an
+		// event that we're going to be cancelling (#2804)
+		var isOpening = true;
+		this._delay( function() {
+			isOpening = false;
+		} );
+
+		if ( !this.document.data( "ui-dialog-overlays" ) ) {
+
+			// Prevent use of anchors and inputs
+			// Using _on() for an event handler shared across many instances is
+			// safe because the dialogs stack and must be closed in reverse order
+			this._on( this.document, {
+				focusin: function( event ) {
+					if ( isOpening ) {
+						return;
+					}
+
+					if ( !this._allowInteraction( event ) ) {
+						event.preventDefault();
+						this._trackingInstances()[ 0 ]._focusTabbable();
+					}
+				}
+			} );
+		}
+
+		this.overlay = $( "<div>" )
+			.appendTo( this._appendTo() );
+
+		this._addClass( this.overlay, null, "ui-widget-overlay ui-front" );
+		this._on( this.overlay, {
+			mousedown: "_keepFocus"
+		} );
+		this.document.data( "ui-dialog-overlays",
+			( this.document.data( "ui-dialog-overlays" ) || 0 ) + 1 );
+	},
+
+	_destroyOverlay: function() {
+		if ( !this.options.modal ) {
+			return;
+		}
+
+		if ( this.overlay ) {
+			var overlays = this.document.data( "ui-dialog-overlays" ) - 1;
+
+			if ( !overlays ) {
+				this._off( this.document, "focusin" );
+				this.document.removeData( "ui-dialog-overlays" );
+			} else {
+				this.document.data( "ui-dialog-overlays", overlays );
+			}
+
+			this.overlay.remove();
+			this.overlay = null;
+		}
+	}
+} );
+
+// DEPRECATED
+// TODO: switch return back to widget declaration at top of file when this is removed
+if ( $.uiBackCompat !== false ) {
+
+	// Backcompat for dialogClass option
+	$.widget( "ui.dialog", $.ui.dialog, {
+		options: {
+			dialogClass: ""
+		},
+		_createWrapper: function() {
+			this._super();
+			this.uiDialog.addClass( this.options.dialogClass );
+		},
+		_setOption: function( key, value ) {
+			if ( key === "dialogClass" ) {
+				this.uiDialog
+					.removeClass( this.options.dialogClass )
+					.addClass( value );
+			}
+			this._superApply( arguments );
+		}
+	} );
+}
+
+var widgetsDialog = $.ui.dialog;
+
+
+/*!
+ * jQuery UI Droppable 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Droppable
+//>>group: Interactions
+//>>description: Enables drop targets for draggable elements.
+//>>docs: http://api.jqueryui.com/droppable/
+//>>demos: http://jqueryui.com/droppable/
+
+
+
+$.widget( "ui.droppable", {
+	version: "1.12.1",
+	widgetEventPrefix: "drop",
+	options: {
+		accept: "*",
+		addClasses: true,
+		greedy: false,
+		scope: "default",
+		tolerance: "intersect",
+
+		// Callbacks
+		activate: null,
+		deactivate: null,
+		drop: null,
+		out: null,
+		over: null
+	},
+	_create: function() {
+
+		var proportions,
+			o = this.options,
+			accept = o.accept;
+
+		this.isover = false;
+		this.isout = true;
+
+		this.accept = $.isFunction( accept ) ? accept : function( d ) {
+			return d.is( accept );
+		};
+
+		this.proportions = function( /* valueToWrite */ ) {
+			if ( arguments.length ) {
+
+				// Store the droppable's proportions
+				proportions = arguments[ 0 ];
+			} else {
+
+				// Retrieve or derive the droppable's proportions
+				return proportions ?
+					proportions :
+					proportions = {
+						width: this.element[ 0 ].offsetWidth,
+						height: this.element[ 0 ].offsetHeight
+					};
+			}
+		};
+
+		this._addToManager( o.scope );
+
+		o.addClasses && this._addClass( "ui-droppable" );
+
+	},
+
+	_addToManager: function( scope ) {
+
+		// Add the reference and positions to the manager
+		$.ui.ddmanager.droppables[ scope ] = $.ui.ddmanager.droppables[ scope ] || [];
+		$.ui.ddmanager.droppables[ scope ].push( this );
+	},
+
+	_splice: function( drop ) {
+		var i = 0;
+		for ( ; i < drop.length; i++ ) {
+			if ( drop[ i ] === this ) {
+				drop.splice( i, 1 );
+			}
+		}
+	},
+
+	_destroy: function() {
+		var drop = $.ui.ddmanager.droppables[ this.options.scope ];
+
+		this._splice( drop );
+	},
+
+	_setOption: function( key, value ) {
+
+		if ( key === "accept" ) {
+			this.accept = $.isFunction( value ) ? value : function( d ) {
+				return d.is( value );
+			};
+		} else if ( key === "scope" ) {
+			var drop = $.ui.ddmanager.droppables[ this.options.scope ];
+
+			this._splice( drop );
+			this._addToManager( value );
+		}
+
+		this._super( key, value );
+	},
+
+	_activate: function( event ) {
+		var draggable = $.ui.ddmanager.current;
+
+		this._addActiveClass();
+		if ( draggable ) {
+			this._trigger( "activate", event, this.ui( draggable ) );
+		}
+	},
+
+	_deactivate: function( event ) {
+		var draggable = $.ui.ddmanager.current;
+
+		this._removeActiveClass();
+		if ( draggable ) {
+			this._trigger( "deactivate", event, this.ui( draggable ) );
+		}
+	},
+
+	_over: function( event ) {
+
+		var draggable = $.ui.ddmanager.current;
+
+		// Bail if draggable and droppable are same element
+		if ( !draggable || ( draggable.currentItem ||
+				draggable.element )[ 0 ] === this.element[ 0 ] ) {
+			return;
+		}
+
+		if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem ||
+				draggable.element ) ) ) {
+			this._addHoverClass();
+			this._trigger( "over", event, this.ui( draggable ) );
+		}
+
+	},
+
+	_out: function( event ) {
+
+		var draggable = $.ui.ddmanager.current;
+
+		// Bail if draggable and droppable are same element
+		if ( !draggable || ( draggable.currentItem ||
+				draggable.element )[ 0 ] === this.element[ 0 ] ) {
+			return;
+		}
+
+		if ( this.accept.call( this.element[ 0 ], ( draggable.currentItem ||
+				draggable.element ) ) ) {
+			this._removeHoverClass();
+			this._trigger( "out", event, this.ui( draggable ) );
+		}
+
+	},
+
+	_drop: function( event, custom ) {
+
+		var draggable = custom || $.ui.ddmanager.current,
+			childrenIntersection = false;
+
+		// Bail if draggable and droppable are same element
+		if ( !draggable || ( draggable.currentItem ||
+				draggable.element )[ 0 ] === this.element[ 0 ] ) {
+			return false;
+		}
+
+		this.element
+			.find( ":data(ui-droppable)" )
+			.not( ".ui-draggable-dragging" )
+			.each( function() {
+				var inst = $( this ).droppable( "instance" );
+				if (
+					inst.options.greedy &&
+					!inst.options.disabled &&
+					inst.options.scope === draggable.options.scope &&
+					inst.accept.call(
+						inst.element[ 0 ], ( draggable.currentItem || draggable.element )
+					) &&
+					intersect(
+						draggable,
+						$.extend( inst, { offset: inst.element.offset() } ),
+						inst.options.tolerance, event
+					)
+				) {
+					childrenIntersection = true;
+					return false; }
+			} );
+		if ( childrenIntersection ) {
+			return false;
+		}
+
+		if ( this.accept.call( this.element[ 0 ],
+				( draggable.currentItem || draggable.element ) ) ) {
+			this._removeActiveClass();
+			this._removeHoverClass();
+
+			this._trigger( "drop", event, this.ui( draggable ) );
+			return this.element;
+		}
+
+		return false;
+
+	},
+
+	ui: function( c ) {
+		return {
+			draggable: ( c.currentItem || c.element ),
+			helper: c.helper,
+			position: c.position,
+			offset: c.positionAbs
+		};
+	},
+
+	// Extension points just to make backcompat sane and avoid duplicating logic
+	// TODO: Remove in 1.13 along with call to it below
+	_addHoverClass: function() {
+		this._addClass( "ui-droppable-hover" );
+	},
+
+	_removeHoverClass: function() {
+		this._removeClass( "ui-droppable-hover" );
+	},
+
+	_addActiveClass: function() {
+		this._addClass( "ui-droppable-active" );
+	},
+
+	_removeActiveClass: function() {
+		this._removeClass( "ui-droppable-active" );
+	}
+} );
+
+var intersect = $.ui.intersect = ( function() {
+	function isOverAxis( x, reference, size ) {
+		return ( x >= reference ) && ( x < ( reference + size ) );
+	}
+
+	return function( draggable, droppable, toleranceMode, event ) {
+
+		if ( !droppable.offset ) {
+			return false;
+		}
+
+		var x1 = ( draggable.positionAbs ||
+				draggable.position.absolute ).left + draggable.margins.left,
+			y1 = ( draggable.positionAbs ||
+				draggable.position.absolute ).top + draggable.margins.top,
+			x2 = x1 + draggable.helperProportions.width,
+			y2 = y1 + draggable.helperProportions.height,
+			l = droppable.offset.left,
+			t = droppable.offset.top,
+			r = l + droppable.proportions().width,
+			b = t + droppable.proportions().height;
+
+		switch ( toleranceMode ) {
+		case "fit":
+			return ( l <= x1 && x2 <= r && t <= y1 && y2 <= b );
+		case "intersect":
+			return ( l < x1 + ( draggable.helperProportions.width / 2 ) && // Right Half
+				x2 - ( draggable.helperProportions.width / 2 ) < r && // Left Half
+				t < y1 + ( draggable.helperProportions.height / 2 ) && // Bottom Half
+				y2 - ( draggable.helperProportions.height / 2 ) < b ); // Top Half
+		case "pointer":
+			return isOverAxis( event.pageY, t, droppable.proportions().height ) &&
+				isOverAxis( event.pageX, l, droppable.proportions().width );
+		case "touch":
+			return (
+				( y1 >= t && y1 <= b ) || // Top edge touching
+				( y2 >= t && y2 <= b ) || // Bottom edge touching
+				( y1 < t && y2 > b ) // Surrounded vertically
+			) && (
+				( x1 >= l && x1 <= r ) || // Left edge touching
+				( x2 >= l && x2 <= r ) || // Right edge touching
+				( x1 < l && x2 > r ) // Surrounded horizontally
+			);
+		default:
+			return false;
+		}
+	};
+} )();
+
+/*
+	This manager tracks offsets of draggables and droppables
+*/
+$.ui.ddmanager = {
+	current: null,
+	droppables: { "default": [] },
+	prepareOffsets: function( t, event ) {
+
+		var i, j,
+			m = $.ui.ddmanager.droppables[ t.options.scope ] || [],
+			type = event ? event.type : null, // workaround for #2317
+			list = ( t.currentItem || t.element ).find( ":data(ui-droppable)" ).addBack();
+
+		droppablesLoop: for ( i = 0; i < m.length; i++ ) {
+
+			// No disabled and non-accepted
+			if ( m[ i ].options.disabled || ( t && !m[ i ].accept.call( m[ i ].element[ 0 ],
+					( t.currentItem || t.element ) ) ) ) {
+				continue;
+			}
+
+			// Filter out elements in the current dragged item
+			for ( j = 0; j < list.length; j++ ) {
+				if ( list[ j ] === m[ i ].element[ 0 ] ) {
+					m[ i ].proportions().height = 0;
+					continue droppablesLoop;
+				}
+			}
+
+			m[ i ].visible = m[ i ].element.css( "display" ) !== "none";
+			if ( !m[ i ].visible ) {
+				continue;
+			}
+
+			// Activate the droppable if used directly from draggables
+			if ( type === "mousedown" ) {
+				m[ i ]._activate.call( m[ i ], event );
+			}
+
+			m[ i ].offset = m[ i ].element.offset();
+			m[ i ].proportions( {
+				width: m[ i ].element[ 0 ].offsetWidth,
+				height: m[ i ].element[ 0 ].offsetHeight
+			} );
+
+		}
+
+	},
+	drop: function( draggable, event ) {
+
+		var dropped = false;
+
+		// Create a copy of the droppables in case the list changes during the drop (#9116)
+		$.each( ( $.ui.ddmanager.droppables[ draggable.options.scope ] || [] ).slice(), function() {
+
+			if ( !this.options ) {
+				return;
+			}
+			if ( !this.options.disabled && this.visible &&
+					intersect( draggable, this, this.options.tolerance, event ) ) {
+				dropped = this._drop.call( this, event ) || dropped;
+			}
+
+			if ( !this.options.disabled && this.visible && this.accept.call( this.element[ 0 ],
+					( draggable.currentItem || draggable.element ) ) ) {
+				this.isout = true;
+				this.isover = false;
+				this._deactivate.call( this, event );
+			}
+
+		} );
+		return dropped;
+
+	},
+	dragStart: function( draggable, event ) {
+
+		// Listen for scrolling so that if the dragging causes scrolling the position of the
+		// droppables can be recalculated (see #5003)
+		draggable.element.parentsUntil( "body" ).on( "scroll.droppable", function() {
+			if ( !draggable.options.refreshPositions ) {
+				$.ui.ddmanager.prepareOffsets( draggable, event );
+			}
+		} );
+	},
+	drag: function( draggable, event ) {
+
+		// If you have a highly dynamic page, you might try this option. It renders positions
+		// every time you move the mouse.
+		if ( draggable.options.refreshPositions ) {
+			$.ui.ddmanager.prepareOffsets( draggable, event );
+		}
+
+		// Run through all droppables and check their positions based on specific tolerance options
+		$.each( $.ui.ddmanager.droppables[ draggable.options.scope ] || [], function() {
+
+			if ( this.options.disabled || this.greedyChild || !this.visible ) {
+				return;
+			}
+
+			var parentInstance, scope, parent,
+				intersects = intersect( draggable, this, this.options.tolerance, event ),
+				c = !intersects && this.isover ?
+					"isout" :
+					( intersects && !this.isover ? "isover" : null );
+			if ( !c ) {
+				return;
+			}
+
+			if ( this.options.greedy ) {
+
+				// find droppable parents with same scope
+				scope = this.options.scope;
+				parent = this.element.parents( ":data(ui-droppable)" ).filter( function() {
+					return $( this ).droppable( "instance" ).options.scope === scope;
+				} );
+
+				if ( parent.length ) {
+					parentInstance = $( parent[ 0 ] ).droppable( "instance" );
+					parentInstance.greedyChild = ( c === "isover" );
+				}
+			}
+
+			// We just moved into a greedy child
+			if ( parentInstance && c === "isover" ) {
+				parentInstance.isover = false;
+				parentInstance.isout = true;
+				parentInstance._out.call( parentInstance, event );
+			}
+
+			this[ c ] = true;
+			this[ c === "isout" ? "isover" : "isout" ] = false;
+			this[ c === "isover" ? "_over" : "_out" ].call( this, event );
+
+			// We just moved out of a greedy child
+			if ( parentInstance && c === "isout" ) {
+				parentInstance.isout = false;
+				parentInstance.isover = true;
+				parentInstance._over.call( parentInstance, event );
+			}
+		} );
+
+	},
+	dragStop: function( draggable, event ) {
+		draggable.element.parentsUntil( "body" ).off( "scroll.droppable" );
+
+		// Call prepareOffsets one final time since IE does not fire return scroll events when
+		// overflow was caused by drag (see #5003)
+		if ( !draggable.options.refreshPositions ) {
+			$.ui.ddmanager.prepareOffsets( draggable, event );
+		}
+	}
+};
+
+// DEPRECATED
+// TODO: switch return back to widget declaration at top of file when this is removed
+if ( $.uiBackCompat !== false ) {
+
+	// Backcompat for activeClass and hoverClass options
+	$.widget( "ui.droppable", $.ui.droppable, {
+		options: {
+			hoverClass: false,
+			activeClass: false
+		},
+		_addActiveClass: function() {
+			this._super();
+			if ( this.options.activeClass ) {
+				this.element.addClass( this.options.activeClass );
+			}
+		},
+		_removeActiveClass: function() {
+			this._super();
+			if ( this.options.activeClass ) {
+				this.element.removeClass( this.options.activeClass );
+			}
+		},
+		_addHoverClass: function() {
+			this._super();
+			if ( this.options.hoverClass ) {
+				this.element.addClass( this.options.hoverClass );
+			}
+		},
+		_removeHoverClass: function() {
+			this._super();
+			if ( this.options.hoverClass ) {
+				this.element.removeClass( this.options.hoverClass );
+			}
+		}
+	} );
+}
+
+var widgetsDroppable = $.ui.droppable;
+
+
+/*!
+ * jQuery UI Progressbar 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Progressbar
+//>>group: Widgets
+// jscs:disable maximumLineLength
+//>>description: Displays a status indicator for loading state, standard percentage, and other progress indicators.
+// jscs:enable maximumLineLength
+//>>docs: http://api.jqueryui.com/progressbar/
+//>>demos: http://jqueryui.com/progressbar/
+//>>css.structure: ../../themes/base/core.css
+//>>css.structure: ../../themes/base/progressbar.css
+//>>css.theme: ../../themes/base/theme.css
+
+
+
+var widgetsProgressbar = $.widget( "ui.progressbar", {
+	version: "1.12.1",
+	options: {
+		classes: {
+			"ui-progressbar": "ui-corner-all",
+			"ui-progressbar-value": "ui-corner-left",
+			"ui-progressbar-complete": "ui-corner-right"
+		},
+		max: 100,
+		value: 0,
+
+		change: null,
+		complete: null
+	},
+
+	min: 0,
+
+	_create: function() {
+
+		// Constrain initial value
+		this.oldValue = this.options.value = this._constrainedValue();
+
+		this.element.attr( {
+
+			// Only set static values; aria-valuenow and aria-valuemax are
+			// set inside _refreshValue()
+			role: "progressbar",
+			"aria-valuemin": this.min
+		} );
+		this._addClass( "ui-progressbar", "ui-widget ui-widget-content" );
+
+		this.valueDiv = $( "<div>" ).appendTo( this.element );
+		this._addClass( this.valueDiv, "ui-progressbar-value", "ui-widget-header" );
+		this._refreshValue();
+	},
+
+	_destroy: function() {
+		this.element.removeAttr( "role aria-valuemin aria-valuemax aria-valuenow" );
+
+		this.valueDiv.remove();
+	},
+
+	value: function( newValue ) {
+		if ( newValue === undefined ) {
+			return this.options.value;
+		}
+
+		this.options.value = this._constrainedValue( newValue );
+		this._refreshValue();
+	},
+
+	_constrainedValue: function( newValue ) {
+		if ( newValue === undefined ) {
+			newValue = this.options.value;
+		}
+
+		this.indeterminate = newValue === false;
+
+		// Sanitize value
+		if ( typeof newValue !== "number" ) {
+			newValue = 0;
+		}
+
+		return this.indeterminate ? false :
+			Math.min( this.options.max, Math.max( this.min, newValue ) );
+	},
+
+	_setOptions: function( options ) {
+
+		// Ensure "value" option is set after other values (like max)
+		var value = options.value;
+		delete options.value;
+
+		this._super( options );
+
+		this.options.value = this._constrainedValue( value );
+		this._refreshValue();
+	},
+
+	_setOption: function( key, value ) {
+		if ( key === "max" ) {
+
+			// Don't allow a max less than min
+			value = Math.max( this.min, value );
+		}
+		this._super( key, value );
+	},
+
+	_setOptionDisabled: function( value ) {
+		this._super( value );
+
+		this.element.attr( "aria-disabled", value );
+		this._toggleClass( null, "ui-state-disabled", !!value );
+	},
+
+	_percentage: function() {
+		return this.indeterminate ?
+			100 :
+			100 * ( this.options.value - this.min ) / ( this.options.max - this.min );
+	},
+
+	_refreshValue: function() {
+		var value = this.options.value,
+			percentage = this._percentage();
+
+		this.valueDiv
+			.toggle( this.indeterminate || value > this.min )
+			.width( percentage.toFixed( 0 ) + "%" );
+
+		this
+			._toggleClass( this.valueDiv, "ui-progressbar-complete", null,
+				value === this.options.max )
+			._toggleClass( "ui-progressbar-indeterminate", null, this.indeterminate );
+
+		if ( this.indeterminate ) {
+			this.element.removeAttr( "aria-valuenow" );
+			if ( !this.overlayDiv ) {
+				this.overlayDiv = $( "<div>" ).appendTo( this.valueDiv );
+				this._addClass( this.overlayDiv, "ui-progressbar-overlay" );
+			}
+		} else {
+			this.element.attr( {
+				"aria-valuemax": this.options.max,
+				"aria-valuenow": value
+			} );
+			if ( this.overlayDiv ) {
+				this.overlayDiv.remove();
+				this.overlayDiv = null;
+			}
+		}
+
+		if ( this.oldValue !== value ) {
+			this.oldValue = value;
+			this._trigger( "change" );
+		}
+		if ( value === this.options.max ) {
+			this._trigger( "complete" );
+		}
+	}
+} );
+
+
+/*!
+ * jQuery UI Selectable 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Selectable
+//>>group: Interactions
+//>>description: Allows groups of elements to be selected with the mouse.
+//>>docs: http://api.jqueryui.com/selectable/
+//>>demos: http://jqueryui.com/selectable/
+//>>css.structure: ../../themes/base/selectable.css
+
+
+
+var widgetsSelectable = $.widget( "ui.selectable", $.ui.mouse, {
+	version: "1.12.1",
+	options: {
+		appendTo: "body",
+		autoRefresh: true,
+		distance: 0,
+		filter: "*",
+		tolerance: "touch",
+
+		// Callbacks
+		selected: null,
+		selecting: null,
+		start: null,
+		stop: null,
+		unselected: null,
+		unselecting: null
+	},
+	_create: function() {
+		var that = this;
+
+		this._addClass( "ui-selectable" );
+
+		this.dragged = false;
+
+		// Cache selectee children based on filter
+		this.refresh = function() {
+			that.elementPos = $( that.element[ 0 ] ).offset();
+			that.selectees = $( that.options.filter, that.element[ 0 ] );
+			that._addClass( that.selectees, "ui-selectee" );
+			that.selectees.each( function() {
+				var $this = $( this ),
+					selecteeOffset = $this.offset(),
+					pos = {
+						left: selecteeOffset.left - that.elementPos.left,
+						top: selecteeOffset.top - that.elementPos.top
+					};
+				$.data( this, "selectable-item", {
+					element: this,
+					$element: $this,
+					left: pos.left,
+					top: pos.top,
+					right: pos.left + $this.outerWidth(),
+					bottom: pos.top + $this.outerHeight(),
+					startselected: false,
+					selected: $this.hasClass( "ui-selected" ),
+					selecting: $this.hasClass( "ui-selecting" ),
+					unselecting: $this.hasClass( "ui-unselecting" )
+				} );
+			} );
+		};
+		this.refresh();
+
+		this._mouseInit();
+
+		this.helper = $( "<div>" );
+		this._addClass( this.helper, "ui-selectable-helper" );
+	},
+
+	_destroy: function() {
+		this.selectees.removeData( "selectable-item" );
+		this._mouseDestroy();
+	},
+
+	_mouseStart: function( event ) {
+		var that = this,
+			options = this.options;
+
+		this.opos = [ event.pageX, event.pageY ];
+		this.elementPos = $( this.element[ 0 ] ).offset();
+
+		if ( this.options.disabled ) {
+			return;
+		}
+
+		this.selectees = $( options.filter, this.element[ 0 ] );
+
+		this._trigger( "start", event );
+
+		$( options.appendTo ).append( this.helper );
+
+		// position helper (lasso)
+		this.helper.css( {
+			"left": event.pageX,
+			"top": event.pageY,
+			"width": 0,
+			"height": 0
+		} );
+
+		if ( options.autoRefresh ) {
+			this.refresh();
+		}
+
+		this.selectees.filter( ".ui-selected" ).each( function() {
+			var selectee = $.data( this, "selectable-item" );
+			selectee.startselected = true;
+			if ( !event.metaKey && !event.ctrlKey ) {
+				that._removeClass( selectee.$element, "ui-selected" );
+				selectee.selected = false;
+				that._addClass( selectee.$element, "ui-unselecting" );
+				selectee.unselecting = true;
+
+				// selectable UNSELECTING callback
+				that._trigger( "unselecting", event, {
+					unselecting: selectee.element
+				} );
+			}
+		} );
+
+		$( event.target ).parents().addBack().each( function() {
+			var doSelect,
+				selectee = $.data( this, "selectable-item" );
+			if ( selectee ) {
+				doSelect = ( !event.metaKey && !event.ctrlKey ) ||
+					!selectee.$element.hasClass( "ui-selected" );
+				that._removeClass( selectee.$element, doSelect ? "ui-unselecting" : "ui-selected" )
+					._addClass( selectee.$element, doSelect ? "ui-selecting" : "ui-unselecting" );
+				selectee.unselecting = !doSelect;
+				selectee.selecting = doSelect;
+				selectee.selected = doSelect;
+
+				// selectable (UN)SELECTING callback
+				if ( doSelect ) {
+					that._trigger( "selecting", event, {
+						selecting: selectee.element
+					} );
+				} else {
+					that._trigger( "unselecting", event, {
+						unselecting: selectee.element
+					} );
+				}
+				return false;
+			}
+		} );
+
+	},
+
+	_mouseDrag: function( event ) {
+
+		this.dragged = true;
+
+		if ( this.options.disabled ) {
+			return;
+		}
+
+		var tmp,
+			that = this,
+			options = this.options,
+			x1 = this.opos[ 0 ],
+			y1 = this.opos[ 1 ],
+			x2 = event.pageX,
+			y2 = event.pageY;
+
+		if ( x1 > x2 ) { tmp = x2; x2 = x1; x1 = tmp; }
+		if ( y1 > y2 ) { tmp = y2; y2 = y1; y1 = tmp; }
+		this.helper.css( { left: x1, top: y1, width: x2 - x1, height: y2 - y1 } );
+
+		this.selectees.each( function() {
+			var selectee = $.data( this, "selectable-item" ),
+				hit = false,
+				offset = {};
+
+			//prevent helper from being selected if appendTo: selectable
+			if ( !selectee || selectee.element === that.element[ 0 ] ) {
+				return;
+			}
+
+			offset.left   = selectee.left   + that.elementPos.left;
+			offset.right  = selectee.right  + that.elementPos.left;
+			offset.top    = selectee.top    + that.elementPos.top;
+			offset.bottom = selectee.bottom + that.elementPos.top;
+
+			if ( options.tolerance === "touch" ) {
+				hit = ( !( offset.left > x2 || offset.right < x1 || offset.top > y2 ||
+                    offset.bottom < y1 ) );
+			} else if ( options.tolerance === "fit" ) {
+				hit = ( offset.left > x1 && offset.right < x2 && offset.top > y1 &&
+                    offset.bottom < y2 );
+			}
+
+			if ( hit ) {
+
+				// SELECT
+				if ( selectee.selected ) {
+					that._removeClass( selectee.$element, "ui-selected" );
+					selectee.selected = false;
+				}
+				if ( selectee.unselecting ) {
+					that._removeClass( selectee.$element, "ui-unselecting" );
+					selectee.unselecting = false;
+				}
+				if ( !selectee.selecting ) {
+					that._addClass( selectee.$element, "ui-selecting" );
+					selectee.selecting = true;
+
+					// selectable SELECTING callback
+					that._trigger( "selecting", event, {
+						selecting: selectee.element
+					} );
+				}
+			} else {
+
+				// UNSELECT
+				if ( selectee.selecting ) {
+					if ( ( event.metaKey || event.ctrlKey ) && selectee.startselected ) {
+						that._removeClass( selectee.$element, "ui-selecting" );
+						selectee.selecting = false;
+						that._addClass( selectee.$element, "ui-selected" );
+						selectee.selected = true;
+					} else {
+						that._removeClass( selectee.$element, "ui-selecting" );
+						selectee.selecting = false;
+						if ( selectee.startselected ) {
+							that._addClass( selectee.$element, "ui-unselecting" );
+							selectee.unselecting = true;
+						}
+
+						// selectable UNSELECTING callback
+						that._trigger( "unselecting", event, {
+							unselecting: selectee.element
+						} );
+					}
+				}
+				if ( selectee.selected ) {
+					if ( !event.metaKey && !event.ctrlKey && !selectee.startselected ) {
+						that._removeClass( selectee.$element, "ui-selected" );
+						selectee.selected = false;
+
+						that._addClass( selectee.$element, "ui-unselecting" );
+						selectee.unselecting = true;
+
+						// selectable UNSELECTING callback
+						that._trigger( "unselecting", event, {
+							unselecting: selectee.element
+						} );
+					}
+				}
+			}
+		} );
+
+		return false;
+	},
+
+	_mouseStop: function( event ) {
+		var that = this;
+
+		this.dragged = false;
+
+		$( ".ui-unselecting", this.element[ 0 ] ).each( function() {
+			var selectee = $.data( this, "selectable-item" );
+			that._removeClass( selectee.$element, "ui-unselecting" );
+			selectee.unselecting = false;
+			selectee.startselected = false;
+			that._trigger( "unselected", event, {
+				unselected: selectee.element
+			} );
+		} );
+		$( ".ui-selecting", this.element[ 0 ] ).each( function() {
+			var selectee = $.data( this, "selectable-item" );
+			that._removeClass( selectee.$element, "ui-selecting" )
+				._addClass( selectee.$element, "ui-selected" );
+			selectee.selecting = false;
+			selectee.selected = true;
+			selectee.startselected = true;
+			that._trigger( "selected", event, {
+				selected: selectee.element
+			} );
+		} );
+		this._trigger( "stop", event );
+
+		this.helper.remove();
+
+		return false;
+	}
+
+} );
+
+
+/*!
+ * jQuery UI Selectmenu 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Selectmenu
+//>>group: Widgets
+// jscs:disable maximumLineLength
+//>>description: Duplicates and extends the functionality of a native HTML select element, allowing it to be customizable in behavior and appearance far beyond the limitations of a native select.
+// jscs:enable maximumLineLength
+//>>docs: http://api.jqueryui.com/selectmenu/
+//>>demos: http://jqueryui.com/selectmenu/
+//>>css.structure: ../../themes/base/core.css
+//>>css.structure: ../../themes/base/selectmenu.css, ../../themes/base/button.css
+//>>css.theme: ../../themes/base/theme.css
+
+
+
+var widgetsSelectmenu = $.widget( "ui.selectmenu", [ $.ui.formResetMixin, {
+	version: "1.12.1",
+	defaultElement: "<select>",
+	options: {
+		appendTo: null,
+		classes: {
+			"ui-selectmenu-button-open": "ui-corner-top",
+			"ui-selectmenu-button-closed": "ui-corner-all"
+		},
+		disabled: null,
+		icons: {
+			button: "ui-icon-triangle-1-s"
+		},
+		position: {
+			my: "left top",
+			at: "left bottom",
+			collision: "none"
+		},
+		width: false,
+
+		// Callbacks
+		change: null,
+		close: null,
+		focus: null,
+		open: null,
+		select: null
+	},
+
+	_create: function() {
+		var selectmenuId = this.element.uniqueId().attr( "id" );
+		this.ids = {
+			element: selectmenuId,
+			button: selectmenuId + "-button",
+			menu: selectmenuId + "-menu"
+		};
+
+		this._drawButton();
+		this._drawMenu();
+		this._bindFormResetHandler();
+
+		this._rendered = false;
+		this.menuItems = $();
+	},
+
+	_drawButton: function() {
+		var icon,
+			that = this,
+			item = this._parseOption(
+				this.element.find( "option:selected" ),
+				this.element[ 0 ].selectedIndex
+			);
+
+		// Associate existing label with the new button
+		this.labels = this.element.labels().attr( "for", this.ids.button );
+		this._on( this.labels, {
+			click: function( event ) {
+				this.button.focus();
+				event.preventDefault();
+			}
+		} );
+
+		// Hide original select element
+		this.element.hide();
+
+		// Create button
+		this.button = $( "<span>", {
+			tabindex: this.options.disabled ? -1 : 0,
+			id: this.ids.button,
+			role: "combobox",
+			"aria-expanded": "false",
+			"aria-autocomplete": "list",
+			"aria-owns": this.ids.menu,
+			"aria-haspopup": "true",
+			title: this.element.attr( "title" )
+		} )
+			.insertAfter( this.element );
+
+		this._addClass( this.button, "ui-selectmenu-button ui-selectmenu-button-closed",
+			"ui-button ui-widget" );
+
+		icon = $( "<span>" ).appendTo( this.button );
+		this._addClass( icon, "ui-selectmenu-icon", "ui-icon " + this.options.icons.button );
+		this.buttonItem = this._renderButtonItem( item )
+			.appendTo( this.button );
+
+		if ( this.options.width !== false ) {
+			this._resizeButton();
+		}
+
+		this._on( this.button, this._buttonEvents );
+		this.button.one( "focusin", function() {
+
+			// Delay rendering the menu items until the button receives focus.
+			// The menu may have already been rendered via a programmatic open.
+			if ( !that._rendered ) {
+				that._refreshMenu();
+			}
+		} );
+	},
+
+	_drawMenu: function() {
+		var that = this;
+
+		// Create menu
+		this.menu = $( "<ul>", {
+			"aria-hidden": "true",
+			"aria-labelledby": this.ids.button,
+			id: this.ids.menu
+		} );
+
+		// Wrap menu
+		this.menuWrap = $( "<div>" ).append( this.menu );
+		this._addClass( this.menuWrap, "ui-selectmenu-menu", "ui-front" );
+		this.menuWrap.appendTo( this._appendTo() );
+
+		// Initialize menu widget
+		this.menuInstance = this.menu
+			.menu( {
+				classes: {
+					"ui-menu": "ui-corner-bottom"
+				},
+				role: "listbox",
+				select: function( event, ui ) {
+					event.preventDefault();
+
+					// Support: IE8
+					// If the item was selected via a click, the text selection
+					// will be destroyed in IE
+					that._setSelection();
+
+					that._select( ui.item.data( "ui-selectmenu-item" ), event );
+				},
+				focus: function( event, ui ) {
+					var item = ui.item.data( "ui-selectmenu-item" );
+
+					// Prevent inital focus from firing and check if its a newly focused item
+					if ( that.focusIndex != null && item.index !== that.focusIndex ) {
+						that._trigger( "focus", event, { item: item } );
+						if ( !that.isOpen ) {
+							that._select( item, event );
+						}
+					}
+					that.focusIndex = item.index;
+
+					that.button.attr( "aria-activedescendant",
+						that.menuItems.eq( item.index ).attr( "id" ) );
+				}
+			} )
+			.menu( "instance" );
+
+		// Don't close the menu on mouseleave
+		this.menuInstance._off( this.menu, "mouseleave" );
+
+		// Cancel the menu's collapseAll on document click
+		this.menuInstance._closeOnDocumentClick = function() {
+			return false;
+		};
+
+		// Selects often contain empty items, but never contain dividers
+		this.menuInstance._isDivider = function() {
+			return false;
+		};
+	},
+
+	refresh: function() {
+		this._refreshMenu();
+		this.buttonItem.replaceWith(
+			this.buttonItem = this._renderButtonItem(
+
+				// Fall back to an empty object in case there are no options
+				this._getSelectedItem().data( "ui-selectmenu-item" ) || {}
+			)
+		);
+		if ( this.options.width === null ) {
+			this._resizeButton();
+		}
+	},
+
+	_refreshMenu: function() {
+		var item,
+			options = this.element.find( "option" );
+
+		this.menu.empty();
+
+		this._parseOptions( options );
+		this._renderMenu( this.menu, this.items );
+
+		this.menuInstance.refresh();
+		this.menuItems = this.menu.find( "li" )
+			.not( ".ui-selectmenu-optgroup" )
+				.find( ".ui-menu-item-wrapper" );
+
+		this._rendered = true;
+
+		if ( !options.length ) {
+			return;
+		}
+
+		item = this._getSelectedItem();
+
+		// Update the menu to have the correct item focused
+		this.menuInstance.focus( null, item );
+		this._setAria( item.data( "ui-selectmenu-item" ) );
+
+		// Set disabled state
+		this._setOption( "disabled", this.element.prop( "disabled" ) );
+	},
+
+	open: function( event ) {
+		if ( this.options.disabled ) {
+			return;
+		}
+
+		// If this is the first time the menu is being opened, render the items
+		if ( !this._rendered ) {
+			this._refreshMenu();
+		} else {
+
+			// Menu clears focus on close, reset focus to selected item
+			this._removeClass( this.menu.find( ".ui-state-active" ), null, "ui-state-active" );
+			this.menuInstance.focus( null, this._getSelectedItem() );
+		}
+
+		// If there are no options, don't open the menu
+		if ( !this.menuItems.length ) {
+			return;
+		}
+
+		this.isOpen = true;
+		this._toggleAttr();
+		this._resizeMenu();
+		this._position();
+
+		this._on( this.document, this._documentClick );
+
+		this._trigger( "open", event );
+	},
+
+	_position: function() {
+		this.menuWrap.position( $.extend( { of: this.button }, this.options.position ) );
+	},
+
+	close: function( event ) {
+		if ( !this.isOpen ) {
+			return;
+		}
+
+		this.isOpen = false;
+		this._toggleAttr();
+
+		this.range = null;
+		this._off( this.document );
+
+		this._trigger( "close", event );
+	},
+
+	widget: function() {
+		return this.button;
+	},
+
+	menuWidget: function() {
+		return this.menu;
+	},
+
+	_renderButtonItem: function( item ) {
+		var buttonItem = $( "<span>" );
+
+		this._setText( buttonItem, item.label );
+		this._addClass( buttonItem, "ui-selectmenu-text" );
+
+		return buttonItem;
+	},
+
+	_renderMenu: function( ul, items ) {
+		var that = this,
+			currentOptgroup = "";
+
+		$.each( items, function( index, item ) {
+			var li;
+
+			if ( item.optgroup !== currentOptgroup ) {
+				li = $( "<li>", {
+					text: item.optgroup
+				} );
+				that._addClass( li, "ui-selectmenu-optgroup", "ui-menu-divider" +
+					( item.element.parent( "optgroup" ).prop( "disabled" ) ?
+						" ui-state-disabled" :
+						"" ) );
+
+				li.appendTo( ul );
+
+				currentOptgroup = item.optgroup;
+			}
+
+			that._renderItemData( ul, item );
+		} );
+	},
+
+	_renderItemData: function( ul, item ) {
+		return this._renderItem( ul, item ).data( "ui-selectmenu-item", item );
+	},
+
+	_renderItem: function( ul, item ) {
+		var li = $( "<li>" ),
+			wrapper = $( "<div>", {
+				title: item.element.attr( "title" )
+			} );
+
+		if ( item.disabled ) {
+			this._addClass( li, null, "ui-state-disabled" );
+		}
+		this._setText( wrapper, item.label );
+
+		return li.append( wrapper ).appendTo( ul );
+	},
+
+	_setText: function( element, value ) {
+		if ( value ) {
+			element.text( value );
+		} else {
+			element.html( "&#160;" );
+		}
+	},
+
+	_move: function( direction, event ) {
+		var item, next,
+			filter = ".ui-menu-item";
+
+		if ( this.isOpen ) {
+			item = this.menuItems.eq( this.focusIndex ).parent( "li" );
+		} else {
+			item = this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( "li" );
+			filter += ":not(.ui-state-disabled)";
+		}
+
+		if ( direction === "first" || direction === "last" ) {
+			next = item[ direction === "first" ? "prevAll" : "nextAll" ]( filter ).eq( -1 );
+		} else {
+			next = item[ direction + "All" ]( filter ).eq( 0 );
+		}
+
+		if ( next.length ) {
+			this.menuInstance.focus( event, next );
+		}
+	},
+
+	_getSelectedItem: function() {
+		return this.menuItems.eq( this.element[ 0 ].selectedIndex ).parent( "li" );
+	},
+
+	_toggle: function( event ) {
+		this[ this.isOpen ? "close" : "open" ]( event );
+	},
+
+	_setSelection: function() {
+		var selection;
+
+		if ( !this.range ) {
+			return;
+		}
+
+		if ( window.getSelection ) {
+			selection = window.getSelection();
+			selection.removeAllRanges();
+			selection.addRange( this.range );
+
+		// Support: IE8
+		} else {
+			this.range.select();
+		}
+
+		// Support: IE
+		// Setting the text selection kills the button focus in IE, but
+		// restoring the focus doesn't kill the selection.
+		this.button.focus();
+	},
+
+	_documentClick: {
+		mousedown: function( event ) {
+			if ( !this.isOpen ) {
+				return;
+			}
+
+			if ( !$( event.target ).closest( ".ui-selectmenu-menu, #" +
+					$.ui.escapeSelector( this.ids.button ) ).length ) {
+				this.close( event );
+			}
+		}
+	},
+
+	_buttonEvents: {
+
+		// Prevent text selection from being reset when interacting with the selectmenu (#10144)
+		mousedown: function() {
+			var selection;
+
+			if ( window.getSelection ) {
+				selection = window.getSelection();
+				if ( selection.rangeCount ) {
+					this.range = selection.getRangeAt( 0 );
+				}
+
+			// Support: IE8
+			} else {
+				this.range = document.selection.createRange();
+			}
+		},
+
+		click: function( event ) {
+			this._setSelection();
+			this._toggle( event );
+		},
+
+		keydown: function( event ) {
+			var preventDefault = true;
+			switch ( event.keyCode ) {
+			case $.ui.keyCode.TAB:
+			case $.ui.keyCode.ESCAPE:
+				this.close( event );
+				preventDefault = false;
+				break;
+			case $.ui.keyCode.ENTER:
+				if ( this.isOpen ) {
+					this._selectFocusedItem( event );
+				}
+				break;
+			case $.ui.keyCode.UP:
+				if ( event.altKey ) {
+					this._toggle( event );
+				} else {
+					this._move( "prev", event );
+				}
+				break;
+			case $.ui.keyCode.DOWN:
+				if ( event.altKey ) {
+					this._toggle( event );
+				} else {
+					this._move( "next", event );
+				}
+				break;
+			case $.ui.keyCode.SPACE:
+				if ( this.isOpen ) {
+					this._selectFocusedItem( event );
+				} else {
+					this._toggle( event );
+				}
+				break;
+			case $.ui.keyCode.LEFT:
+				this._move( "prev", event );
+				break;
+			case $.ui.keyCode.RIGHT:
+				this._move( "next", event );
+				break;
+			case $.ui.keyCode.HOME:
+			case $.ui.keyCode.PAGE_UP:
+				this._move( "first", event );
+				break;
+			case $.ui.keyCode.END:
+			case $.ui.keyCode.PAGE_DOWN:
+				this._move( "last", event );
+				break;
+			default:
+				this.menu.trigger( event );
+				preventDefault = false;
+			}
+
+			if ( preventDefault ) {
+				event.preventDefault();
+			}
+		}
+	},
+
+	_selectFocusedItem: function( event ) {
+		var item = this.menuItems.eq( this.focusIndex ).parent( "li" );
+		if ( !item.hasClass( "ui-state-disabled" ) ) {
+			this._select( item.data( "ui-selectmenu-item" ), event );
+		}
+	},
+
+	_select: function( item, event ) {
+		var oldIndex = this.element[ 0 ].selectedIndex;
+
+		// Change native select element
+		this.element[ 0 ].selectedIndex = item.index;
+		this.buttonItem.replaceWith( this.buttonItem = this._renderButtonItem( item ) );
+		this._setAria( item );
+		this._trigger( "select", event, { item: item } );
+
+		if ( item.index !== oldIndex ) {
+			this._trigger( "change", event, { item: item } );
+		}
+
+		this.close( event );
+	},
+
+	_setAria: function( item ) {
+		var id = this.menuItems.eq( item.index ).attr( "id" );
+
+		this.button.attr( {
+			"aria-labelledby": id,
+			"aria-activedescendant": id
+		} );
+		this.menu.attr( "aria-activedescendant", id );
+	},
+
+	_setOption: function( key, value ) {
+		if ( key === "icons" ) {
+			var icon = this.button.find( "span.ui-icon" );
+			this._removeClass( icon, null, this.options.icons.button )
+				._addClass( icon, null, value.button );
+		}
+
+		this._super( key, value );
+
+		if ( key === "appendTo" ) {
+			this.menuWrap.appendTo( this._appendTo() );
+		}
+
+		if ( key === "width" ) {
+			this._resizeButton();
+		}
+	},
+
+	_setOptionDisabled: function( value ) {
+		this._super( value );
+
+		this.menuInstance.option( "disabled", value );
+		this.button.attr( "aria-disabled", value );
+		this._toggleClass( this.button, null, "ui-state-disabled", value );
+
+		this.element.prop( "disabled", value );
+		if ( value ) {
+			this.button.attr( "tabindex", -1 );
+			this.close();
+		} else {
+			this.button.attr( "tabindex", 0 );
+		}
+	},
+
+	_appendTo: function() {
+		var element = this.options.appendTo;
+
+		if ( element ) {
+			element = element.jquery || element.nodeType ?
+				$( element ) :
+				this.document.find( element ).eq( 0 );
+		}
+
+		if ( !element || !element[ 0 ] ) {
+			element = this.element.closest( ".ui-front, dialog" );
+		}
+
+		if ( !element.length ) {
+			element = this.document[ 0 ].body;
+		}
+
+		return element;
+	},
+
+	_toggleAttr: function() {
+		this.button.attr( "aria-expanded", this.isOpen );
+
+		// We can't use two _toggleClass() calls here, because we need to make sure
+		// we always remove classes first and add them second, otherwise if both classes have the
+		// same theme class, it will be removed after we add it.
+		this._removeClass( this.button, "ui-selectmenu-button-" +
+			( this.isOpen ? "closed" : "open" ) )
+			._addClass( this.button, "ui-selectmenu-button-" +
+				( this.isOpen ? "open" : "closed" ) )
+			._toggleClass( this.menuWrap, "ui-selectmenu-open", null, this.isOpen );
+
+		this.menu.attr( "aria-hidden", !this.isOpen );
+	},
+
+	_resizeButton: function() {
+		var width = this.options.width;
+
+		// For `width: false`, just remove inline style and stop
+		if ( width === false ) {
+			this.button.css( "width", "" );
+			return;
+		}
+
+		// For `width: null`, match the width of the original element
+		if ( width === null ) {
+			width = this.element.show().outerWidth();
+			this.element.hide();
+		}
+
+		this.button.outerWidth( width );
+	},
+
+	_resizeMenu: function() {
+		this.menu.outerWidth( Math.max(
+			this.button.outerWidth(),
+
+			// Support: IE10
+			// IE10 wraps long text (possibly a rounding bug)
+			// so we add 1px to avoid the wrapping
+			this.menu.width( "" ).outerWidth() + 1
+		) );
+	},
+
+	_getCreateOptions: function() {
+		var options = this._super();
+
+		options.disabled = this.element.prop( "disabled" );
+
+		return options;
+	},
+
+	_parseOptions: function( options ) {
+		var that = this,
+			data = [];
+		options.each( function( index, item ) {
+			data.push( that._parseOption( $( item ), index ) );
+		} );
+		this.items = data;
+	},
+
+	_parseOption: function( option, index ) {
+		var optgroup = option.parent( "optgroup" );
+
+		return {
+			element: option,
+			index: index,
+			value: option.val(),
+			label: option.text(),
+			optgroup: optgroup.attr( "label" ) || "",
+			disabled: optgroup.prop( "disabled" ) || option.prop( "disabled" )
+		};
+	},
+
+	_destroy: function() {
+		this._unbindFormResetHandler();
+		this.menuWrap.remove();
+		this.button.remove();
+		this.element.show();
+		this.element.removeUniqueId();
+		this.labels.attr( "for", this.ids.element );
+	}
+} ] );
+
+
+/*!
+ * jQuery UI Slider 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Slider
+//>>group: Widgets
+//>>description: Displays a flexible slider with ranges and accessibility via keyboard.
+//>>docs: http://api.jqueryui.com/slider/
+//>>demos: http://jqueryui.com/slider/
+//>>css.structure: ../../themes/base/core.css
+//>>css.structure: ../../themes/base/slider.css
+//>>css.theme: ../../themes/base/theme.css
+
+
+
+var widgetsSlider = $.widget( "ui.slider", $.ui.mouse, {
+	version: "1.12.1",
+	widgetEventPrefix: "slide",
+
+	options: {
+		animate: false,
+		classes: {
+			"ui-slider": "ui-corner-all",
+			"ui-slider-handle": "ui-corner-all",
+
+			// Note: ui-widget-header isn't the most fittingly semantic framework class for this
+			// element, but worked best visually with a variety of themes
+			"ui-slider-range": "ui-corner-all ui-widget-header"
+		},
+		distance: 0,
+		max: 100,
+		min: 0,
+		orientation: "horizontal",
+		range: false,
+		step: 1,
+		value: 0,
+		values: null,
+
+		// Callbacks
+		change: null,
+		slide: null,
+		start: null,
+		stop: null
+	},
+
+	// Number of pages in a slider
+	// (how many times can you page up/down to go through the whole range)
+	numPages: 5,
+
+	_create: function() {
+		this._keySliding = false;
+		this._mouseSliding = false;
+		this._animateOff = true;
+		this._handleIndex = null;
+		this._detectOrientation();
+		this._mouseInit();
+		this._calculateNewMax();
+
+		this._addClass( "ui-slider ui-slider-" + this.orientation,
+			"ui-widget ui-widget-content" );
+
+		this._refresh();
+
+		this._animateOff = false;
+	},
+
+	_refresh: function() {
+		this._createRange();
+		this._createHandles();
+		this._setupEvents();
+		this._refreshValue();
+	},
+
+	_createHandles: function() {
+		var i, handleCount,
+			options = this.options,
+			existingHandles = this.element.find( ".ui-slider-handle" ),
+			handle = "<span tabindex='0'></span>",
+			handles = [];
+
+		handleCount = ( options.values && options.values.length ) || 1;
+
+		if ( existingHandles.length > handleCount ) {
+			existingHandles.slice( handleCount ).remove();
+			existingHandles = existingHandles.slice( 0, handleCount );
+		}
+
+		for ( i = existingHandles.length; i < handleCount; i++ ) {
+			handles.push( handle );
+		}
+
+		this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) );
+
+		this._addClass( this.handles, "ui-slider-handle", "ui-state-default" );
+
+		this.handle = this.handles.eq( 0 );
+
+		this.handles.each( function( i ) {
+			$( this )
+				.data( "ui-slider-handle-index", i )
+				.attr( "tabIndex", 0 );
+		} );
+	},
+
+	_createRange: function() {
+		var options = this.options;
+
+		if ( options.range ) {
+			if ( options.range === true ) {
+				if ( !options.values ) {
+					options.values = [ this._valueMin(), this._valueMin() ];
+				} else if ( options.values.length && options.values.length !== 2 ) {
+					options.values = [ options.values[ 0 ], options.values[ 0 ] ];
+				} else if ( $.isArray( options.values ) ) {
+					options.values = options.values.slice( 0 );
+				}
+			}
+
+			if ( !this.range || !this.range.length ) {
+				this.range = $( "<div>" )
+					.appendTo( this.element );
+
+				this._addClass( this.range, "ui-slider-range" );
+			} else {
+				this._removeClass( this.range, "ui-slider-range-min ui-slider-range-max" );
+
+				// Handle range switching from true to min/max
+				this.range.css( {
+					"left": "",
+					"bottom": ""
+				} );
+			}
+			if ( options.range === "min" || options.range === "max" ) {
+				this._addClass( this.range, "ui-slider-range-" + options.range );
+			}
+		} else {
+			if ( this.range ) {
+				this.range.remove();
+			}
+			this.range = null;
+		}
+	},
+
+	_setupEvents: function() {
+		this._off( this.handles );
+		this._on( this.handles, this._handleEvents );
+		this._hoverable( this.handles );
+		this._focusable( this.handles );
+	},
+
+	_destroy: function() {
+		this.handles.remove();
+		if ( this.range ) {
+			this.range.remove();
+		}
+
+		this._mouseDestroy();
+	},
+
+	_mouseCapture: function( event ) {
+		var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle,
+			that = this,
+			o = this.options;
+
+		if ( o.disabled ) {
+			return false;
+		}
+
+		this.elementSize = {
+			width: this.element.outerWidth(),
+			height: this.element.outerHeight()
+		};
+		this.elementOffset = this.element.offset();
+
+		position = { x: event.pageX, y: event.pageY };
+		normValue = this._normValueFromMouse( position );
+		distance = this._valueMax() - this._valueMin() + 1;
+		this.handles.each( function( i ) {
+			var thisDistance = Math.abs( normValue - that.values( i ) );
+			if ( ( distance > thisDistance ) ||
+				( distance === thisDistance &&
+					( i === that._lastChangedValue || that.values( i ) === o.min ) ) ) {
+				distance = thisDistance;
+				closestHandle = $( this );
+				index = i;
+			}
+		} );
+
+		allowed = this._start( event, index );
+		if ( allowed === false ) {
+			return false;
+		}
+		this._mouseSliding = true;
+
+		this._handleIndex = index;
+
+		this._addClass( closestHandle, null, "ui-state-active" );
+		closestHandle.trigger( "focus" );
+
+		offset = closestHandle.offset();
+		mouseOverHandle = !$( event.target ).parents().addBack().is( ".ui-slider-handle" );
+		this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
+			left: event.pageX - offset.left - ( closestHandle.width() / 2 ),
+			top: event.pageY - offset.top -
+				( closestHandle.height() / 2 ) -
+				( parseInt( closestHandle.css( "borderTopWidth" ), 10 ) || 0 ) -
+				( parseInt( closestHandle.css( "borderBottomWidth" ), 10 ) || 0 ) +
+				( parseInt( closestHandle.css( "marginTop" ), 10 ) || 0 )
+		};
+
+		if ( !this.handles.hasClass( "ui-state-hover" ) ) {
+			this._slide( event, index, normValue );
+		}
+		this._animateOff = true;
+		return true;
+	},
+
+	_mouseStart: function() {
+		return true;
+	},
+
+	_mouseDrag: function( event ) {
+		var position = { x: event.pageX, y: event.pageY },
+			normValue = this._normValueFromMouse( position );
+
+		this._slide( event, this._handleIndex, normValue );
+
+		return false;
+	},
+
+	_mouseStop: function( event ) {
+		this._removeClass( this.handles, null, "ui-state-active" );
+		this._mouseSliding = false;
+
+		this._stop( event, this._handleIndex );
+		this._change( event, this._handleIndex );
+
+		this._handleIndex = null;
+		this._clickOffset = null;
+		this._animateOff = false;
+
+		return false;
+	},
+
+	_detectOrientation: function() {
+		this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
+	},
+
+	_normValueFromMouse: function( position ) {
+		var pixelTotal,
+			pixelMouse,
+			percentMouse,
+			valueTotal,
+			valueMouse;
+
+		if ( this.orientation === "horizontal" ) {
+			pixelTotal = this.elementSize.width;
+			pixelMouse = position.x - this.elementOffset.left -
+				( this._clickOffset ? this._clickOffset.left : 0 );
+		} else {
+			pixelTotal = this.elementSize.height;
+			pixelMouse = position.y - this.elementOffset.top -
+				( this._clickOffset ? this._clickOffset.top : 0 );
+		}
+
+		percentMouse = ( pixelMouse / pixelTotal );
+		if ( percentMouse > 1 ) {
+			percentMouse = 1;
+		}
+		if ( percentMouse < 0 ) {
+			percentMouse = 0;
+		}
+		if ( this.orientation === "vertical" ) {
+			percentMouse = 1 - percentMouse;
+		}
+
+		valueTotal = this._valueMax() - this._valueMin();
+		valueMouse = this._valueMin() + percentMouse * valueTotal;
+
+		return this._trimAlignValue( valueMouse );
+	},
+
+	_uiHash: function( index, value, values ) {
+		var uiHash = {
+			handle: this.handles[ index ],
+			handleIndex: index,
+			value: value !== undefined ? value : this.value()
+		};
+
+		if ( this._hasMultipleValues() ) {
+			uiHash.value = value !== undefined ? value : this.values( index );
+			uiHash.values = values || this.values();
+		}
+
+		return uiHash;
+	},
+
+	_hasMultipleValues: function() {
+		return this.options.values && this.options.values.length;
+	},
+
+	_start: function( event, index ) {
+		return this._trigger( "start", event, this._uiHash( index ) );
+	},
+
+	_slide: function( event, index, newVal ) {
+		var allowed, otherVal,
+			currentValue = this.value(),
+			newValues = this.values();
+
+		if ( this._hasMultipleValues() ) {
+			otherVal = this.values( index ? 0 : 1 );
+			currentValue = this.values( index );
+
+			if ( this.options.values.length === 2 && this.options.range === true ) {
+				newVal =  index === 0 ? Math.min( otherVal, newVal ) : Math.max( otherVal, newVal );
+			}
+
+			newValues[ index ] = newVal;
+		}
+
+		if ( newVal === currentValue ) {
+			return;
+		}
+
+		allowed = this._trigger( "slide", event, this._uiHash( index, newVal, newValues ) );
+
+		// A slide can be canceled by returning false from the slide callback
+		if ( allowed === false ) {
+			return;
+		}
+
+		if ( this._hasMultipleValues() ) {
+			this.values( index, newVal );
+		} else {
+			this.value( newVal );
+		}
+	},
+
+	_stop: function( event, index ) {
+		this._trigger( "stop", event, this._uiHash( index ) );
+	},
+
+	_change: function( event, index ) {
+		if ( !this._keySliding && !this._mouseSliding ) {
+
+			//store the last changed value index for reference when handles overlap
+			this._lastChangedValue = index;
+			this._trigger( "change", event, this._uiHash( index ) );
+		}
+	},
+
+	value: function( newValue ) {
+		if ( arguments.length ) {
+			this.options.value = this._trimAlignValue( newValue );
+			this._refreshValue();
+			this._change( null, 0 );
+			return;
+		}
+
+		return this._value();
+	},
+
+	values: function( index, newValue ) {
+		var vals,
+			newValues,
+			i;
+
+		if ( arguments.length > 1 ) {
+			this.options.values[ index ] = this._trimAlignValue( newValue );
+			this._refreshValue();
+			this._change( null, index );
+			return;
+		}
+
+		if ( arguments.length ) {
+			if ( $.isArray( arguments[ 0 ] ) ) {
+				vals = this.options.values;
+				newValues = arguments[ 0 ];
+				for ( i = 0; i < vals.length; i += 1 ) {
+					vals[ i ] = this._trimAlignValue( newValues[ i ] );
+					this._change( null, i );
+				}
+				this._refreshValue();
+			} else {
+				if ( this._hasMultipleValues() ) {
+					return this._values( index );
+				} else {
+					return this.value();
+				}
+			}
+		} else {
+			return this._values();
+		}
+	},
+
+	_setOption: function( key, value ) {
+		var i,
+			valsLength = 0;
+
+		if ( key === "range" && this.options.range === true ) {
+			if ( value === "min" ) {
+				this.options.value = this._values( 0 );
+				this.options.values = null;
+			} else if ( value === "max" ) {
+				this.options.value = this._values( this.options.values.length - 1 );
+				this.options.values = null;
+			}
+		}
+
+		if ( $.isArray( this.options.values ) ) {
+			valsLength = this.options.values.length;
+		}
+
+		this._super( key, value );
+
+		switch ( key ) {
+			case "orientation":
+				this._detectOrientation();
+				this._removeClass( "ui-slider-horizontal ui-slider-vertical" )
+					._addClass( "ui-slider-" + this.orientation );
+				this._refreshValue();
+				if ( this.options.range ) {
+					this._refreshRange( value );
+				}
+
+				// Reset positioning from previous orientation
+				this.handles.css( value === "horizontal" ? "bottom" : "left", "" );
+				break;
+			case "value":
+				this._animateOff = true;
+				this._refreshValue();
+				this._change( null, 0 );
+				this._animateOff = false;
+				break;
+			case "values":
+				this._animateOff = true;
+				this._refreshValue();
+
+				// Start from the last handle to prevent unreachable handles (#9046)
+				for ( i = valsLength - 1; i >= 0; i-- ) {
+					this._change( null, i );
+				}
+				this._animateOff = false;
+				break;
+			case "step":
+			case "min":
+			case "max":
+				this._animateOff = true;
+				this._calculateNewMax();
+				this._refreshValue();
+				this._animateOff = false;
+				break;
+			case "range":
+				this._animateOff = true;
+				this._refresh();
+				this._animateOff = false;
+				break;
+		}
+	},
+
+	_setOptionDisabled: function( value ) {
+		this._super( value );
+
+		this._toggleClass( null, "ui-state-disabled", !!value );
+	},
+
+	//internal value getter
+	// _value() returns value trimmed by min and max, aligned by step
+	_value: function() {
+		var val = this.options.value;
+		val = this._trimAlignValue( val );
+
+		return val;
+	},
+
+	//internal values getter
+	// _values() returns array of values trimmed by min and max, aligned by step
+	// _values( index ) returns single value trimmed by min and max, aligned by step
+	_values: function( index ) {
+		var val,
+			vals,
+			i;
+
+		if ( arguments.length ) {
+			val = this.options.values[ index ];
+			val = this._trimAlignValue( val );
+
+			return val;
+		} else if ( this._hasMultipleValues() ) {
+
+			// .slice() creates a copy of the array
+			// this copy gets trimmed by min and max and then returned
+			vals = this.options.values.slice();
+			for ( i = 0; i < vals.length; i += 1 ) {
+				vals[ i ] = this._trimAlignValue( vals[ i ] );
+			}
+
+			return vals;
+		} else {
+			return [];
+		}
+	},
+
+	// Returns the step-aligned value that val is closest to, between (inclusive) min and max
+	_trimAlignValue: function( val ) {
+		if ( val <= this._valueMin() ) {
+			return this._valueMin();
+		}
+		if ( val >= this._valueMax() ) {
+			return this._valueMax();
+		}
+		var step = ( this.options.step > 0 ) ? this.options.step : 1,
+			valModStep = ( val - this._valueMin() ) % step,
+			alignValue = val - valModStep;
+
+		if ( Math.abs( valModStep ) * 2 >= step ) {
+			alignValue += ( valModStep > 0 ) ? step : ( -step );
+		}
+
+		// Since JavaScript has problems with large floats, round
+		// the final value to 5 digits after the decimal point (see #4124)
+		return parseFloat( alignValue.toFixed( 5 ) );
+	},
+
+	_calculateNewMax: function() {
+		var max = this.options.max,
+			min = this._valueMin(),
+			step = this.options.step,
+			aboveMin = Math.round( ( max - min ) / step ) * step;
+		max = aboveMin + min;
+		if ( max > this.options.max ) {
+
+			//If max is not divisible by step, rounding off may increase its value
+			max -= step;
+		}
+		this.max = parseFloat( max.toFixed( this._precision() ) );
+	},
+
+	_precision: function() {
+		var precision = this._precisionOf( this.options.step );
+		if ( this.options.min !== null ) {
+			precision = Math.max( precision, this._precisionOf( this.options.min ) );
+		}
+		return precision;
+	},
+
+	_precisionOf: function( num ) {
+		var str = num.toString(),
+			decimal = str.indexOf( "." );
+		return decimal === -1 ? 0 : str.length - decimal - 1;
+	},
+
+	_valueMin: function() {
+		return this.options.min;
+	},
+
+	_valueMax: function() {
+		return this.max;
+	},
+
+	_refreshRange: function( orientation ) {
+		if ( orientation === "vertical" ) {
+			this.range.css( { "width": "", "left": "" } );
+		}
+		if ( orientation === "horizontal" ) {
+			this.range.css( { "height": "", "bottom": "" } );
+		}
+	},
+
+	_refreshValue: function() {
+		var lastValPercent, valPercent, value, valueMin, valueMax,
+			oRange = this.options.range,
+			o = this.options,
+			that = this,
+			animate = ( !this._animateOff ) ? o.animate : false,
+			_set = {};
+
+		if ( this._hasMultipleValues() ) {
+			this.handles.each( function( i ) {
+				valPercent = ( that.values( i ) - that._valueMin() ) / ( that._valueMax() -
+					that._valueMin() ) * 100;
+				_set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
+				$( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
+				if ( that.options.range === true ) {
+					if ( that.orientation === "horizontal" ) {
+						if ( i === 0 ) {
+							that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( {
+								left: valPercent + "%"
+							}, o.animate );
+						}
+						if ( i === 1 ) {
+							that.range[ animate ? "animate" : "css" ]( {
+								width: ( valPercent - lastValPercent ) + "%"
+							}, {
+								queue: false,
+								duration: o.animate
+							} );
+						}
+					} else {
+						if ( i === 0 ) {
+							that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( {
+								bottom: ( valPercent ) + "%"
+							}, o.animate );
+						}
+						if ( i === 1 ) {
+							that.range[ animate ? "animate" : "css" ]( {
+								height: ( valPercent - lastValPercent ) + "%"
+							}, {
+								queue: false,
+								duration: o.animate
+							} );
+						}
+					}
+				}
+				lastValPercent = valPercent;
+			} );
+		} else {
+			value = this.value();
+			valueMin = this._valueMin();
+			valueMax = this._valueMax();
+			valPercent = ( valueMax !== valueMin ) ?
+					( value - valueMin ) / ( valueMax - valueMin ) * 100 :
+					0;
+			_set[ this.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
+			this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
+
+			if ( oRange === "min" && this.orientation === "horizontal" ) {
+				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( {
+					width: valPercent + "%"
+				}, o.animate );
+			}
+			if ( oRange === "max" && this.orientation === "horizontal" ) {
+				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( {
+					width: ( 100 - valPercent ) + "%"
+				}, o.animate );
+			}
+			if ( oRange === "min" && this.orientation === "vertical" ) {
+				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( {
+					height: valPercent + "%"
+				}, o.animate );
+			}
+			if ( oRange === "max" && this.orientation === "vertical" ) {
+				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( {
+					height: ( 100 - valPercent ) + "%"
+				}, o.animate );
+			}
+		}
+	},
+
+	_handleEvents: {
+		keydown: function( event ) {
+			var allowed, curVal, newVal, step,
+				index = $( event.target ).data( "ui-slider-handle-index" );
+
+			switch ( event.keyCode ) {
+				case $.ui.keyCode.HOME:
+				case $.ui.keyCode.END:
+				case $.ui.keyCode.PAGE_UP:
+				case $.ui.keyCode.PAGE_DOWN:
+				case $.ui.keyCode.UP:
+				case $.ui.keyCode.RIGHT:
+				case $.ui.keyCode.DOWN:
+				case $.ui.keyCode.LEFT:
+					event.preventDefault();
+					if ( !this._keySliding ) {
+						this._keySliding = true;
+						this._addClass( $( event.target ), null, "ui-state-active" );
+						allowed = this._start( event, index );
+						if ( allowed === false ) {
+							return;
+						}
+					}
+					break;
+			}
+
+			step = this.options.step;
+			if ( this._hasMultipleValues() ) {
+				curVal = newVal = this.values( index );
+			} else {
+				curVal = newVal = this.value();
+			}
+
+			switch ( event.keyCode ) {
+				case $.ui.keyCode.HOME:
+					newVal = this._valueMin();
+					break;
+				case $.ui.keyCode.END:
+					newVal = this._valueMax();
+					break;
+				case $.ui.keyCode.PAGE_UP:
+					newVal = this._trimAlignValue(
+						curVal + ( ( this._valueMax() - this._valueMin() ) / this.numPages )
+					);
+					break;
+				case $.ui.keyCode.PAGE_DOWN:
+					newVal = this._trimAlignValue(
+						curVal - ( ( this._valueMax() - this._valueMin() ) / this.numPages ) );
+					break;
+				case $.ui.keyCode.UP:
+				case $.ui.keyCode.RIGHT:
+					if ( curVal === this._valueMax() ) {
+						return;
+					}
+					newVal = this._trimAlignValue( curVal + step );
+					break;
+				case $.ui.keyCode.DOWN:
+				case $.ui.keyCode.LEFT:
+					if ( curVal === this._valueMin() ) {
+						return;
+					}
+					newVal = this._trimAlignValue( curVal - step );
+					break;
+			}
+
+			this._slide( event, index, newVal );
+		},
+		keyup: function( event ) {
+			var index = $( event.target ).data( "ui-slider-handle-index" );
+
+			if ( this._keySliding ) {
+				this._keySliding = false;
+				this._stop( event, index );
+				this._change( event, index );
+				this._removeClass( $( event.target ), null, "ui-state-active" );
+			}
+		}
+	}
+} );
+
+
+/*!
+ * jQuery UI Sortable 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Sortable
+//>>group: Interactions
+//>>description: Enables items in a list to be sorted using the mouse.
+//>>docs: http://api.jqueryui.com/sortable/
+//>>demos: http://jqueryui.com/sortable/
+//>>css.structure: ../../themes/base/sortable.css
+
+
+
+var widgetsSortable = $.widget( "ui.sortable", $.ui.mouse, {
+	version: "1.12.1",
+	widgetEventPrefix: "sort",
+	ready: false,
+	options: {
+		appendTo: "parent",
+		axis: false,
+		connectWith: false,
+		containment: false,
+		cursor: "auto",
+		cursorAt: false,
+		dropOnEmpty: true,
+		forcePlaceholderSize: false,
+		forceHelperSize: false,
+		grid: false,
+		handle: false,
+		helper: "original",
+		items: "> *",
+		opacity: false,
+		placeholder: false,
+		revert: false,
+		scroll: true,
+		scrollSensitivity: 20,
+		scrollSpeed: 20,
+		scope: "default",
+		tolerance: "intersect",
+		zIndex: 1000,
+
+		// Callbacks
+		activate: null,
+		beforeStop: null,
+		change: null,
+		deactivate: null,
+		out: null,
+		over: null,
+		receive: null,
+		remove: null,
+		sort: null,
+		start: null,
+		stop: null,
+		update: null
+	},
+
+	_isOverAxis: function( x, reference, size ) {
+		return ( x >= reference ) && ( x < ( reference + size ) );
+	},
+
+	_isFloating: function( item ) {
+		return ( /left|right/ ).test( item.css( "float" ) ) ||
+			( /inline|table-cell/ ).test( item.css( "display" ) );
+	},
+
+	_create: function() {
+		this.containerCache = {};
+		this._addClass( "ui-sortable" );
+
+		//Get the items
+		this.refresh();
+
+		//Let's determine the parent's offset
+		this.offset = this.element.offset();
+
+		//Initialize mouse events for interaction
+		this._mouseInit();
+
+		this._setHandleClassName();
+
+		//We're ready to go
+		this.ready = true;
+
+	},
+
+	_setOption: function( key, value ) {
+		this._super( key, value );
+
+		if ( key === "handle" ) {
+			this._setHandleClassName();
+		}
+	},
+
+	_setHandleClassName: function() {
+		var that = this;
+		this._removeClass( this.element.find( ".ui-sortable-handle" ), "ui-sortable-handle" );
+		$.each( this.items, function() {
+			that._addClass(
+				this.instance.options.handle ?
+					this.item.find( this.instance.options.handle ) :
+					this.item,
+				"ui-sortable-handle"
+			);
+		} );
+	},
+
+	_destroy: function() {
+		this._mouseDestroy();
+
+		for ( var i = this.items.length - 1; i >= 0; i-- ) {
+			this.items[ i ].item.removeData( this.widgetName + "-item" );
+		}
+
+		return this;
+	},
+
+	_mouseCapture: function( event, overrideHandle ) {
+		var currentItem = null,
+			validHandle = false,
+			that = this;
+
+		if ( this.reverting ) {
+			return false;
+		}
+
+		if ( this.options.disabled || this.options.type === "static" ) {
+			return false;
+		}
+
+		//We have to refresh the items data once first
+		this._refreshItems( event );
+
+		//Find out if the clicked node (or one of its parents) is a actual item in this.items
+		$( event.target ).parents().each( function() {
+			if ( $.data( this, that.widgetName + "-item" ) === that ) {
+				currentItem = $( this );
+				return false;
+			}
+		} );
+		if ( $.data( event.target, that.widgetName + "-item" ) === that ) {
+			currentItem = $( event.target );
+		}
+
+		if ( !currentItem ) {
+			return false;
+		}
+		if ( this.options.handle && !overrideHandle ) {
+			$( this.options.handle, currentItem ).find( "*" ).addBack().each( function() {
+				if ( this === event.target ) {
+					validHandle = true;
+				}
+			} );
+			if ( !validHandle ) {
+				return false;
+			}
+		}
+
+		this.currentItem = currentItem;
+		this._removeCurrentsFromItems();
+		return true;
+
+	},
+
+	_mouseStart: function( event, overrideHandle, noActivation ) {
+
+		var i, body,
+			o = this.options;
+
+		this.currentContainer = this;
+
+		//We only need to call refreshPositions, because the refreshItems call has been moved to
+		// mouseCapture
+		this.refreshPositions();
+
+		//Create and append the visible helper
+		this.helper = this._createHelper( event );
+
+		//Cache the helper size
+		this._cacheHelperProportions();
+
+		/*
+		 * - Position generation -
+		 * This block generates everything position related - it's the core of draggables.
+		 */
+
+		//Cache the margins of the original element
+		this._cacheMargins();
+
+		//Get the next scrolling parent
+		this.scrollParent = this.helper.scrollParent();
+
+		//The element's absolute position on the page minus margins
+		this.offset = this.currentItem.offset();
+		this.offset = {
+			top: this.offset.top - this.margins.top,
+			left: this.offset.left - this.margins.left
+		};
+
+		$.extend( this.offset, {
+			click: { //Where the click happened, relative to the element
+				left: event.pageX - this.offset.left,
+				top: event.pageY - this.offset.top
+			},
+			parent: this._getParentOffset(),
+
+			// This is a relative to absolute position minus the actual position calculation -
+			// only used for relative positioned helper
+			relative: this._getRelativeOffset()
+		} );
+
+		// Only after we got the offset, we can change the helper's position to absolute
+		// TODO: Still need to figure out a way to make relative sorting possible
+		this.helper.css( "position", "absolute" );
+		this.cssPosition = this.helper.css( "position" );
+
+		//Generate the original position
+		this.originalPosition = this._generatePosition( event );
+		this.originalPageX = event.pageX;
+		this.originalPageY = event.pageY;
+
+		//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
+		( o.cursorAt && this._adjustOffsetFromHelper( o.cursorAt ) );
+
+		//Cache the former DOM position
+		this.domPosition = {
+			prev: this.currentItem.prev()[ 0 ],
+			parent: this.currentItem.parent()[ 0 ]
+		};
+
+		// If the helper is not the original, hide the original so it's not playing any role during
+		// the drag, won't cause anything bad this way
+		if ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) {
+			this.currentItem.hide();
+		}
+
+		//Create the placeholder
+		this._createPlaceholder();
+
+		//Set a containment if given in the options
+		if ( o.containment ) {
+			this._setContainment();
+		}
+
+		if ( o.cursor && o.cursor !== "auto" ) { // cursor option
+			body = this.document.find( "body" );
+
+			// Support: IE
+			this.storedCursor = body.css( "cursor" );
+			body.css( "cursor", o.cursor );
+
+			this.storedStylesheet =
+				$( "<style>*{ cursor: " + o.cursor + " !important; }</style>" ).appendTo( body );
+		}
+
+		if ( o.opacity ) { // opacity option
+			if ( this.helper.css( "opacity" ) ) {
+				this._storedOpacity = this.helper.css( "opacity" );
+			}
+			this.helper.css( "opacity", o.opacity );
+		}
+
+		if ( o.zIndex ) { // zIndex option
+			if ( this.helper.css( "zIndex" ) ) {
+				this._storedZIndex = this.helper.css( "zIndex" );
+			}
+			this.helper.css( "zIndex", o.zIndex );
+		}
+
+		//Prepare scrolling
+		if ( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
+				this.scrollParent[ 0 ].tagName !== "HTML" ) {
+			this.overflowOffset = this.scrollParent.offset();
+		}
+
+		//Call callbacks
+		this._trigger( "start", event, this._uiHash() );
+
+		//Recache the helper size
+		if ( !this._preserveHelperProportions ) {
+			this._cacheHelperProportions();
+		}
+
+		//Post "activate" events to possible containers
+		if ( !noActivation ) {
+			for ( i = this.containers.length - 1; i >= 0; i-- ) {
+				this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) );
+			}
+		}
+
+		//Prepare possible droppables
+		if ( $.ui.ddmanager ) {
+			$.ui.ddmanager.current = this;
+		}
+
+		if ( $.ui.ddmanager && !o.dropBehaviour ) {
+			$.ui.ddmanager.prepareOffsets( this, event );
+		}
+
+		this.dragging = true;
+
+		this._addClass( this.helper, "ui-sortable-helper" );
+
+		// Execute the drag once - this causes the helper not to be visiblebefore getting its
+		// correct position
+		this._mouseDrag( event );
+		return true;
+
+	},
+
+	_mouseDrag: function( event ) {
+		var i, item, itemElement, intersection,
+			o = this.options,
+			scrolled = false;
+
+		//Compute the helpers position
+		this.position = this._generatePosition( event );
+		this.positionAbs = this._convertPositionTo( "absolute" );
+
+		if ( !this.lastPositionAbs ) {
+			this.lastPositionAbs = this.positionAbs;
+		}
+
+		//Do scrolling
+		if ( this.options.scroll ) {
+			if ( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
+					this.scrollParent[ 0 ].tagName !== "HTML" ) {
+
+				if ( ( this.overflowOffset.top + this.scrollParent[ 0 ].offsetHeight ) -
+						event.pageY < o.scrollSensitivity ) {
+					this.scrollParent[ 0 ].scrollTop =
+						scrolled = this.scrollParent[ 0 ].scrollTop + o.scrollSpeed;
+				} else if ( event.pageY - this.overflowOffset.top < o.scrollSensitivity ) {
+					this.scrollParent[ 0 ].scrollTop =
+						scrolled = this.scrollParent[ 0 ].scrollTop - o.scrollSpeed;
+				}
+
+				if ( ( this.overflowOffset.left + this.scrollParent[ 0 ].offsetWidth ) -
+						event.pageX < o.scrollSensitivity ) {
+					this.scrollParent[ 0 ].scrollLeft = scrolled =
+						this.scrollParent[ 0 ].scrollLeft + o.scrollSpeed;
+				} else if ( event.pageX - this.overflowOffset.left < o.scrollSensitivity ) {
+					this.scrollParent[ 0 ].scrollLeft = scrolled =
+						this.scrollParent[ 0 ].scrollLeft - o.scrollSpeed;
+				}
+
+			} else {
+
+				if ( event.pageY - this.document.scrollTop() < o.scrollSensitivity ) {
+					scrolled = this.document.scrollTop( this.document.scrollTop() - o.scrollSpeed );
+				} else if ( this.window.height() - ( event.pageY - this.document.scrollTop() ) <
+						o.scrollSensitivity ) {
+					scrolled = this.document.scrollTop( this.document.scrollTop() + o.scrollSpeed );
+				}
+
+				if ( event.pageX - this.document.scrollLeft() < o.scrollSensitivity ) {
+					scrolled = this.document.scrollLeft(
+						this.document.scrollLeft() - o.scrollSpeed
+					);
+				} else if ( this.window.width() - ( event.pageX - this.document.scrollLeft() ) <
+						o.scrollSensitivity ) {
+					scrolled = this.document.scrollLeft(
+						this.document.scrollLeft() + o.scrollSpeed
+					);
+				}
+
+			}
+
+			if ( scrolled !== false && $.ui.ddmanager && !o.dropBehaviour ) {
+				$.ui.ddmanager.prepareOffsets( this, event );
+			}
+		}
+
+		//Regenerate the absolute position used for position checks
+		this.positionAbs = this._convertPositionTo( "absolute" );
+
+		//Set the helper position
+		if ( !this.options.axis || this.options.axis !== "y" ) {
+			this.helper[ 0 ].style.left = this.position.left + "px";
+		}
+		if ( !this.options.axis || this.options.axis !== "x" ) {
+			this.helper[ 0 ].style.top = this.position.top + "px";
+		}
+
+		//Rearrange
+		for ( i = this.items.length - 1; i >= 0; i-- ) {
+
+			//Cache variables and intersection, continue if no intersection
+			item = this.items[ i ];
+			itemElement = item.item[ 0 ];
+			intersection = this._intersectsWithPointer( item );
+			if ( !intersection ) {
+				continue;
+			}
+
+			// Only put the placeholder inside the current Container, skip all
+			// items from other containers. This works because when moving
+			// an item from one container to another the
+			// currentContainer is switched before the placeholder is moved.
+			//
+			// Without this, moving items in "sub-sortables" can cause
+			// the placeholder to jitter between the outer and inner container.
+			if ( item.instance !== this.currentContainer ) {
+				continue;
+			}
+
+			// Cannot intersect with itself
+			// no useless actions that have been done before
+			// no action if the item moved is the parent of the item checked
+			if ( itemElement !== this.currentItem[ 0 ] &&
+				this.placeholder[ intersection === 1 ? "next" : "prev" ]()[ 0 ] !== itemElement &&
+				!$.contains( this.placeholder[ 0 ], itemElement ) &&
+				( this.options.type === "semi-dynamic" ?
+					!$.contains( this.element[ 0 ], itemElement ) :
+					true
+				)
+			) {
+
+				this.direction = intersection === 1 ? "down" : "up";
+
+				if ( this.options.tolerance === "pointer" || this._intersectsWithSides( item ) ) {
+					this._rearrange( event, item );
+				} else {
+					break;
+				}
+
+				this._trigger( "change", event, this._uiHash() );
+				break;
+			}
+		}
+
+		//Post events to containers
+		this._contactContainers( event );
+
+		//Interconnect with droppables
+		if ( $.ui.ddmanager ) {
+			$.ui.ddmanager.drag( this, event );
+		}
+
+		//Call callbacks
+		this._trigger( "sort", event, this._uiHash() );
+
+		this.lastPositionAbs = this.positionAbs;
+		return false;
+
+	},
+
+	_mouseStop: function( event, noPropagation ) {
+
+		if ( !event ) {
+			return;
+		}
+
+		//If we are using droppables, inform the manager about the drop
+		if ( $.ui.ddmanager && !this.options.dropBehaviour ) {
+			$.ui.ddmanager.drop( this, event );
+		}
+
+		if ( this.options.revert ) {
+			var that = this,
+				cur = this.placeholder.offset(),
+				axis = this.options.axis,
+				animation = {};
+
+			if ( !axis || axis === "x" ) {
+				animation.left = cur.left - this.offset.parent.left - this.margins.left +
+					( this.offsetParent[ 0 ] === this.document[ 0 ].body ?
+						0 :
+						this.offsetParent[ 0 ].scrollLeft
+					);
+			}
+			if ( !axis || axis === "y" ) {
+				animation.top = cur.top - this.offset.parent.top - this.margins.top +
+					( this.offsetParent[ 0 ] === this.document[ 0 ].body ?
+						0 :
+						this.offsetParent[ 0 ].scrollTop
+					);
+			}
+			this.reverting = true;
+			$( this.helper ).animate(
+				animation,
+				parseInt( this.options.revert, 10 ) || 500,
+				function() {
+					that._clear( event );
+				}
+			);
+		} else {
+			this._clear( event, noPropagation );
+		}
+
+		return false;
+
+	},
+
+	cancel: function() {
+
+		if ( this.dragging ) {
+
+			this._mouseUp( new $.Event( "mouseup", { target: null } ) );
+
+			if ( this.options.helper === "original" ) {
+				this.currentItem.css( this._storedCSS );
+				this._removeClass( this.currentItem, "ui-sortable-helper" );
+			} else {
+				this.currentItem.show();
+			}
+
+			//Post deactivating events to containers
+			for ( var i = this.containers.length - 1; i >= 0; i-- ) {
+				this.containers[ i ]._trigger( "deactivate", null, this._uiHash( this ) );
+				if ( this.containers[ i ].containerCache.over ) {
+					this.containers[ i ]._trigger( "out", null, this._uiHash( this ) );
+					this.containers[ i ].containerCache.over = 0;
+				}
+			}
+
+		}
+
+		if ( this.placeholder ) {
+
+			//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately,
+			// it unbinds ALL events from the original node!
+			if ( this.placeholder[ 0 ].parentNode ) {
+				this.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] );
+			}
+			if ( this.options.helper !== "original" && this.helper &&
+					this.helper[ 0 ].parentNode ) {
+				this.helper.remove();
+			}
+
+			$.extend( this, {
+				helper: null,
+				dragging: false,
+				reverting: false,
+				_noFinalSort: null
+			} );
+
+			if ( this.domPosition.prev ) {
+				$( this.domPosition.prev ).after( this.currentItem );
+			} else {
+				$( this.domPosition.parent ).prepend( this.currentItem );
+			}
+		}
+
+		return this;
+
+	},
+
+	serialize: function( o ) {
+
+		var items = this._getItemsAsjQuery( o && o.connected ),
+			str = [];
+		o = o || {};
+
+		$( items ).each( function() {
+			var res = ( $( o.item || this ).attr( o.attribute || "id" ) || "" )
+				.match( o.expression || ( /(.+)[\-=_](.+)/ ) );
+			if ( res ) {
+				str.push(
+					( o.key || res[ 1 ] + "[]" ) +
+					"=" + ( o.key && o.expression ? res[ 1 ] : res[ 2 ] ) );
+			}
+		} );
+
+		if ( !str.length && o.key ) {
+			str.push( o.key + "=" );
+		}
+
+		return str.join( "&" );
+
+	},
+
+	toArray: function( o ) {
+
+		var items = this._getItemsAsjQuery( o && o.connected ),
+			ret = [];
+
+		o = o || {};
+
+		items.each( function() {
+			ret.push( $( o.item || this ).attr( o.attribute || "id" ) || "" );
+		} );
+		return ret;
+
+	},
+
+	/* Be careful with the following core functions */
+	_intersectsWith: function( item ) {
+
+		var x1 = this.positionAbs.left,
+			x2 = x1 + this.helperProportions.width,
+			y1 = this.positionAbs.top,
+			y2 = y1 + this.helperProportions.height,
+			l = item.left,
+			r = l + item.width,
+			t = item.top,
+			b = t + item.height,
+			dyClick = this.offset.click.top,
+			dxClick = this.offset.click.left,
+			isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t &&
+				( y1 + dyClick ) < b ),
+			isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l &&
+				( x1 + dxClick ) < r ),
+			isOverElement = isOverElementHeight && isOverElementWidth;
+
+		if ( this.options.tolerance === "pointer" ||
+			this.options.forcePointerForContainers ||
+			( this.options.tolerance !== "pointer" &&
+				this.helperProportions[ this.floating ? "width" : "height" ] >
+				item[ this.floating ? "width" : "height" ] )
+		) {
+			return isOverElement;
+		} else {
+
+			return ( l < x1 + ( this.helperProportions.width / 2 ) && // Right Half
+				x2 - ( this.helperProportions.width / 2 ) < r && // Left Half
+				t < y1 + ( this.helperProportions.height / 2 ) && // Bottom Half
+				y2 - ( this.helperProportions.height / 2 ) < b ); // Top Half
+
+		}
+	},
+
+	_intersectsWithPointer: function( item ) {
+		var verticalDirection, horizontalDirection,
+			isOverElementHeight = ( this.options.axis === "x" ) ||
+				this._isOverAxis(
+					this.positionAbs.top + this.offset.click.top, item.top, item.height ),
+			isOverElementWidth = ( this.options.axis === "y" ) ||
+				this._isOverAxis(
+					this.positionAbs.left + this.offset.click.left, item.left, item.width ),
+			isOverElement = isOverElementHeight && isOverElementWidth;
+
+		if ( !isOverElement ) {
+			return false;
+		}
+
+		verticalDirection = this._getDragVerticalDirection();
+		horizontalDirection = this._getDragHorizontalDirection();
+
+		return this.floating ?
+			( ( horizontalDirection === "right" || verticalDirection === "down" ) ? 2 : 1 )
+			: ( verticalDirection && ( verticalDirection === "down" ? 2 : 1 ) );
+
+	},
+
+	_intersectsWithSides: function( item ) {
+
+		var isOverBottomHalf = this._isOverAxis( this.positionAbs.top +
+				this.offset.click.top, item.top + ( item.height / 2 ), item.height ),
+			isOverRightHalf = this._isOverAxis( this.positionAbs.left +
+				this.offset.click.left, item.left + ( item.width / 2 ), item.width ),
+			verticalDirection = this._getDragVerticalDirection(),
+			horizontalDirection = this._getDragHorizontalDirection();
+
+		if ( this.floating && horizontalDirection ) {
+			return ( ( horizontalDirection === "right" && isOverRightHalf ) ||
+				( horizontalDirection === "left" && !isOverRightHalf ) );
+		} else {
+			return verticalDirection && ( ( verticalDirection === "down" && isOverBottomHalf ) ||
+				( verticalDirection === "up" && !isOverBottomHalf ) );
+		}
+
+	},
+
+	_getDragVerticalDirection: function() {
+		var delta = this.positionAbs.top - this.lastPositionAbs.top;
+		return delta !== 0 && ( delta > 0 ? "down" : "up" );
+	},
+
+	_getDragHorizontalDirection: function() {
+		var delta = this.positionAbs.left - this.lastPositionAbs.left;
+		return delta !== 0 && ( delta > 0 ? "right" : "left" );
+	},
+
+	refresh: function( event ) {
+		this._refreshItems( event );
+		this._setHandleClassName();
+		this.refreshPositions();
+		return this;
+	},
+
+	_connectWith: function() {
+		var options = this.options;
+		return options.connectWith.constructor === String ?
+			[ options.connectWith ] :
+			options.connectWith;
+	},
+
+	_getItemsAsjQuery: function( connected ) {
+
+		var i, j, cur, inst,
+			items = [],
+			queries = [],
+			connectWith = this._connectWith();
+
+		if ( connectWith && connected ) {
+			for ( i = connectWith.length - 1; i >= 0; i-- ) {
+				cur = $( connectWith[ i ], this.document[ 0 ] );
+				for ( j = cur.length - 1; j >= 0; j-- ) {
+					inst = $.data( cur[ j ], this.widgetFullName );
+					if ( inst && inst !== this && !inst.options.disabled ) {
+						queries.push( [ $.isFunction( inst.options.items ) ?
+							inst.options.items.call( inst.element ) :
+							$( inst.options.items, inst.element )
+								.not( ".ui-sortable-helper" )
+								.not( ".ui-sortable-placeholder" ), inst ] );
+					}
+				}
+			}
+		}
+
+		queries.push( [ $.isFunction( this.options.items ) ?
+			this.options.items
+				.call( this.element, null, { options: this.options, item: this.currentItem } ) :
+			$( this.options.items, this.element )
+				.not( ".ui-sortable-helper" )
+				.not( ".ui-sortable-placeholder" ), this ] );
+
+		function addItems() {
+			items.push( this );
+		}
+		for ( i = queries.length - 1; i >= 0; i-- ) {
+			queries[ i ][ 0 ].each( addItems );
+		}
+
+		return $( items );
+
+	},
+
+	_removeCurrentsFromItems: function() {
+
+		var list = this.currentItem.find( ":data(" + this.widgetName + "-item)" );
+
+		this.items = $.grep( this.items, function( item ) {
+			for ( var j = 0; j < list.length; j++ ) {
+				if ( list[ j ] === item.item[ 0 ] ) {
+					return false;
+				}
+			}
+			return true;
+		} );
+
+	},
+
+	_refreshItems: function( event ) {
+
+		this.items = [];
+		this.containers = [ this ];
+
+		var i, j, cur, inst, targetData, _queries, item, queriesLength,
+			items = this.items,
+			queries = [ [ $.isFunction( this.options.items ) ?
+				this.options.items.call( this.element[ 0 ], event, { item: this.currentItem } ) :
+				$( this.options.items, this.element ), this ] ],
+			connectWith = this._connectWith();
+
+		//Shouldn't be run the first time through due to massive slow-down
+		if ( connectWith && this.ready ) {
+			for ( i = connectWith.length - 1; i >= 0; i-- ) {
+				cur = $( connectWith[ i ], this.document[ 0 ] );
+				for ( j = cur.length - 1; j >= 0; j-- ) {
+					inst = $.data( cur[ j ], this.widgetFullName );
+					if ( inst && inst !== this && !inst.options.disabled ) {
+						queries.push( [ $.isFunction( inst.options.items ) ?
+							inst.options.items
+								.call( inst.element[ 0 ], event, { item: this.currentItem } ) :
+							$( inst.options.items, inst.element ), inst ] );
+						this.containers.push( inst );
+					}
+				}
+			}
+		}
+
+		for ( i = queries.length - 1; i >= 0; i-- ) {
+			targetData = queries[ i ][ 1 ];
+			_queries = queries[ i ][ 0 ];
+
+			for ( j = 0, queriesLength = _queries.length; j < queriesLength; j++ ) {
+				item = $( _queries[ j ] );
+
+				// Data for target checking (mouse manager)
+				item.data( this.widgetName + "-item", targetData );
+
+				items.push( {
+					item: item,
+					instance: targetData,
+					width: 0, height: 0,
+					left: 0, top: 0
+				} );
+			}
+		}
+
+	},
+
+	refreshPositions: function( fast ) {
+
+		// Determine whether items are being displayed horizontally
+		this.floating = this.items.length ?
+			this.options.axis === "x" || this._isFloating( this.items[ 0 ].item ) :
+			false;
+
+		//This has to be redone because due to the item being moved out/into the offsetParent,
+		// the offsetParent's position will change
+		if ( this.offsetParent && this.helper ) {
+			this.offset.parent = this._getParentOffset();
+		}
+
+		var i, item, t, p;
+
+		for ( i = this.items.length - 1; i >= 0; i-- ) {
+			item = this.items[ i ];
+
+			//We ignore calculating positions of all connected containers when we're not over them
+			if ( item.instance !== this.currentContainer && this.currentContainer &&
+					item.item[ 0 ] !== this.currentItem[ 0 ] ) {
+				continue;
+			}
+
+			t = this.options.toleranceElement ?
+				$( this.options.toleranceElement, item.item ) :
+				item.item;
+
+			if ( !fast ) {
+				item.width = t.outerWidth();
+				item.height = t.outerHeight();
+			}
+
+			p = t.offset();
+			item.left = p.left;
+			item.top = p.top;
+		}
+
+		if ( this.options.custom && this.options.custom.refreshContainers ) {
+			this.options.custom.refreshContainers.call( this );
+		} else {
+			for ( i = this.containers.length - 1; i >= 0; i-- ) {
+				p = this.containers[ i ].element.offset();
+				this.containers[ i ].containerCache.left = p.left;
+				this.containers[ i ].containerCache.top = p.top;
+				this.containers[ i ].containerCache.width =
+					this.containers[ i ].element.outerWidth();
+				this.containers[ i ].containerCache.height =
+					this.containers[ i ].element.outerHeight();
+			}
+		}
+
+		return this;
+	},
+
+	_createPlaceholder: function( that ) {
+		that = that || this;
+		var className,
+			o = that.options;
+
+		if ( !o.placeholder || o.placeholder.constructor === String ) {
+			className = o.placeholder;
+			o.placeholder = {
+				element: function() {
+
+					var nodeName = that.currentItem[ 0 ].nodeName.toLowerCase(),
+						element = $( "<" + nodeName + ">", that.document[ 0 ] );
+
+						that._addClass( element, "ui-sortable-placeholder",
+								className || that.currentItem[ 0 ].className )
+							._removeClass( element, "ui-sortable-helper" );
+
+					if ( nodeName === "tbody" ) {
+						that._createTrPlaceholder(
+							that.currentItem.find( "tr" ).eq( 0 ),
+							$( "<tr>", that.document[ 0 ] ).appendTo( element )
+						);
+					} else if ( nodeName === "tr" ) {
+						that._createTrPlaceholder( that.currentItem, element );
+					} else if ( nodeName === "img" ) {
+						element.attr( "src", that.currentItem.attr( "src" ) );
+					}
+
+					if ( !className ) {
+						element.css( "visibility", "hidden" );
+					}
+
+					return element;
+				},
+				update: function( container, p ) {
+
+					// 1. If a className is set as 'placeholder option, we don't force sizes -
+					// the class is responsible for that
+					// 2. The option 'forcePlaceholderSize can be enabled to force it even if a
+					// class name is specified
+					if ( className && !o.forcePlaceholderSize ) {
+						return;
+					}
+
+					//If the element doesn't have a actual height by itself (without styles coming
+					// from a stylesheet), it receives the inline height from the dragged item
+					if ( !p.height() ) {
+						p.height(
+							that.currentItem.innerHeight() -
+							parseInt( that.currentItem.css( "paddingTop" ) || 0, 10 ) -
+							parseInt( that.currentItem.css( "paddingBottom" ) || 0, 10 ) );
+					}
+					if ( !p.width() ) {
+						p.width(
+							that.currentItem.innerWidth() -
+							parseInt( that.currentItem.css( "paddingLeft" ) || 0, 10 ) -
+							parseInt( that.currentItem.css( "paddingRight" ) || 0, 10 ) );
+					}
+				}
+			};
+		}
+
+		//Create the placeholder
+		that.placeholder = $( o.placeholder.element.call( that.element, that.currentItem ) );
+
+		//Append it after the actual current item
+		that.currentItem.after( that.placeholder );
+
+		//Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
+		o.placeholder.update( that, that.placeholder );
+
+	},
+
+	_createTrPlaceholder: function( sourceTr, targetTr ) {
+		var that = this;
+
+		sourceTr.children().each( function() {
+			$( "<td>&#160;</td>", that.document[ 0 ] )
+				.attr( "colspan", $( this ).attr( "colspan" ) || 1 )
+				.appendTo( targetTr );
+		} );
+	},
+
+	_contactContainers: function( event ) {
+		var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, cur, nearBottom,
+			floating, axis,
+			innermostContainer = null,
+			innermostIndex = null;
+
+		// Get innermost container that intersects with item
+		for ( i = this.containers.length - 1; i >= 0; i-- ) {
+
+			// Never consider a container that's located within the item itself
+			if ( $.contains( this.currentItem[ 0 ], this.containers[ i ].element[ 0 ] ) ) {
+				continue;
+			}
+
+			if ( this._intersectsWith( this.containers[ i ].containerCache ) ) {
+
+				// If we've already found a container and it's more "inner" than this, then continue
+				if ( innermostContainer &&
+						$.contains(
+							this.containers[ i ].element[ 0 ],
+							innermostContainer.element[ 0 ] ) ) {
+					continue;
+				}
+
+				innermostContainer = this.containers[ i ];
+				innermostIndex = i;
+
+			} else {
+
+				// container doesn't intersect. trigger "out" event if necessary
+				if ( this.containers[ i ].containerCache.over ) {
+					this.containers[ i ]._trigger( "out", event, this._uiHash( this ) );
+					this.containers[ i ].containerCache.over = 0;
+				}
+			}
+
+		}
+
+		// If no intersecting containers found, return
+		if ( !innermostContainer ) {
+			return;
+		}
+
+		// Move the item into the container if it's not there already
+		if ( this.containers.length === 1 ) {
+			if ( !this.containers[ innermostIndex ].containerCache.over ) {
+				this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash( this ) );
+				this.containers[ innermostIndex ].containerCache.over = 1;
+			}
+		} else {
+
+			// When entering a new container, we will find the item with the least distance and
+			// append our item near it
+			dist = 10000;
+			itemWithLeastDistance = null;
+			floating = innermostContainer.floating || this._isFloating( this.currentItem );
+			posProperty = floating ? "left" : "top";
+			sizeProperty = floating ? "width" : "height";
+			axis = floating ? "pageX" : "pageY";
+
+			for ( j = this.items.length - 1; j >= 0; j-- ) {
+				if ( !$.contains(
+						this.containers[ innermostIndex ].element[ 0 ], this.items[ j ].item[ 0 ] )
+				) {
+					continue;
+				}
+				if ( this.items[ j ].item[ 0 ] === this.currentItem[ 0 ] ) {
+					continue;
+				}
+
+				cur = this.items[ j ].item.offset()[ posProperty ];
+				nearBottom = false;
+				if ( event[ axis ] - cur > this.items[ j ][ sizeProperty ] / 2 ) {
+					nearBottom = true;
+				}
+
+				if ( Math.abs( event[ axis ] - cur ) < dist ) {
+					dist = Math.abs( event[ axis ] - cur );
+					itemWithLeastDistance = this.items[ j ];
+					this.direction = nearBottom ? "up" : "down";
+				}
+			}
+
+			//Check if dropOnEmpty is enabled
+			if ( !itemWithLeastDistance && !this.options.dropOnEmpty ) {
+				return;
+			}
+
+			if ( this.currentContainer === this.containers[ innermostIndex ] ) {
+				if ( !this.currentContainer.containerCache.over ) {
+					this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash() );
+					this.currentContainer.containerCache.over = 1;
+				}
+				return;
+			}
+
+			itemWithLeastDistance ?
+				this._rearrange( event, itemWithLeastDistance, null, true ) :
+				this._rearrange( event, null, this.containers[ innermostIndex ].element, true );
+			this._trigger( "change", event, this._uiHash() );
+			this.containers[ innermostIndex ]._trigger( "change", event, this._uiHash( this ) );
+			this.currentContainer = this.containers[ innermostIndex ];
+
+			//Update the placeholder
+			this.options.placeholder.update( this.currentContainer, this.placeholder );
+
+			this.containers[ innermostIndex ]._trigger( "over", event, this._uiHash( this ) );
+			this.containers[ innermostIndex ].containerCache.over = 1;
+		}
+
+	},
+
+	_createHelper: function( event ) {
+
+		var o = this.options,
+			helper = $.isFunction( o.helper ) ?
+				$( o.helper.apply( this.element[ 0 ], [ event, this.currentItem ] ) ) :
+				( o.helper === "clone" ? this.currentItem.clone() : this.currentItem );
+
+		//Add the helper to the DOM if that didn't happen already
+		if ( !helper.parents( "body" ).length ) {
+			$( o.appendTo !== "parent" ?
+				o.appendTo :
+				this.currentItem[ 0 ].parentNode )[ 0 ].appendChild( helper[ 0 ] );
+		}
+
+		if ( helper[ 0 ] === this.currentItem[ 0 ] ) {
+			this._storedCSS = {
+				width: this.currentItem[ 0 ].style.width,
+				height: this.currentItem[ 0 ].style.height,
+				position: this.currentItem.css( "position" ),
+				top: this.currentItem.css( "top" ),
+				left: this.currentItem.css( "left" )
+			};
+		}
+
+		if ( !helper[ 0 ].style.width || o.forceHelperSize ) {
+			helper.width( this.currentItem.width() );
+		}
+		if ( !helper[ 0 ].style.height || o.forceHelperSize ) {
+			helper.height( this.currentItem.height() );
+		}
+
+		return helper;
+
+	},
+
+	_adjustOffsetFromHelper: function( obj ) {
+		if ( typeof obj === "string" ) {
+			obj = obj.split( " " );
+		}
+		if ( $.isArray( obj ) ) {
+			obj = { left: +obj[ 0 ], top: +obj[ 1 ] || 0 };
+		}
+		if ( "left" in obj ) {
+			this.offset.click.left = obj.left + this.margins.left;
+		}
+		if ( "right" in obj ) {
+			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
+		}
+		if ( "top" in obj ) {
+			this.offset.click.top = obj.top + this.margins.top;
+		}
+		if ( "bottom" in obj ) {
+			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
+		}
+	},
+
+	_getParentOffset: function() {
+
+		//Get the offsetParent and cache its position
+		this.offsetParent = this.helper.offsetParent();
+		var po = this.offsetParent.offset();
+
+		// This is a special case where we need to modify a offset calculated on start, since the
+		// following happened:
+		// 1. The position of the helper is absolute, so it's position is calculated based on the
+		// next positioned parent
+		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't
+		// the document, which means that the scroll is included in the initial calculation of the
+		// offset of the parent, and never recalculated upon drag
+		if ( this.cssPosition === "absolute" && this.scrollParent[ 0 ] !== this.document[ 0 ] &&
+				$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) {
+			po.left += this.scrollParent.scrollLeft();
+			po.top += this.scrollParent.scrollTop();
+		}
+
+		// This needs to be actually done for all browsers, since pageX/pageY includes this
+		// information with an ugly IE fix
+		if ( this.offsetParent[ 0 ] === this.document[ 0 ].body ||
+				( this.offsetParent[ 0 ].tagName &&
+				this.offsetParent[ 0 ].tagName.toLowerCase() === "html" && $.ui.ie ) ) {
+			po = { top: 0, left: 0 };
+		}
+
+		return {
+			top: po.top + ( parseInt( this.offsetParent.css( "borderTopWidth" ), 10 ) || 0 ),
+			left: po.left + ( parseInt( this.offsetParent.css( "borderLeftWidth" ), 10 ) || 0 )
+		};
+
+	},
+
+	_getRelativeOffset: function() {
+
+		if ( this.cssPosition === "relative" ) {
+			var p = this.currentItem.position();
+			return {
+				top: p.top - ( parseInt( this.helper.css( "top" ), 10 ) || 0 ) +
+					this.scrollParent.scrollTop(),
+				left: p.left - ( parseInt( this.helper.css( "left" ), 10 ) || 0 ) +
+					this.scrollParent.scrollLeft()
+			};
+		} else {
+			return { top: 0, left: 0 };
+		}
+
+	},
+
+	_cacheMargins: function() {
+		this.margins = {
+			left: ( parseInt( this.currentItem.css( "marginLeft" ), 10 ) || 0 ),
+			top: ( parseInt( this.currentItem.css( "marginTop" ), 10 ) || 0 )
+		};
+	},
+
+	_cacheHelperProportions: function() {
+		this.helperProportions = {
+			width: this.helper.outerWidth(),
+			height: this.helper.outerHeight()
+		};
+	},
+
+	_setContainment: function() {
+
+		var ce, co, over,
+			o = this.options;
+		if ( o.containment === "parent" ) {
+			o.containment = this.helper[ 0 ].parentNode;
+		}
+		if ( o.containment === "document" || o.containment === "window" ) {
+			this.containment = [
+				0 - this.offset.relative.left - this.offset.parent.left,
+				0 - this.offset.relative.top - this.offset.parent.top,
+				o.containment === "document" ?
+					this.document.width() :
+					this.window.width() - this.helperProportions.width - this.margins.left,
+				( o.containment === "document" ?
+					( this.document.height() || document.body.parentNode.scrollHeight ) :
+					this.window.height() || this.document[ 0 ].body.parentNode.scrollHeight
+				) - this.helperProportions.height - this.margins.top
+			];
+		}
+
+		if ( !( /^(document|window|parent)$/ ).test( o.containment ) ) {
+			ce = $( o.containment )[ 0 ];
+			co = $( o.containment ).offset();
+			over = ( $( ce ).css( "overflow" ) !== "hidden" );
+
+			this.containment = [
+				co.left + ( parseInt( $( ce ).css( "borderLeftWidth" ), 10 ) || 0 ) +
+					( parseInt( $( ce ).css( "paddingLeft" ), 10 ) || 0 ) - this.margins.left,
+				co.top + ( parseInt( $( ce ).css( "borderTopWidth" ), 10 ) || 0 ) +
+					( parseInt( $( ce ).css( "paddingTop" ), 10 ) || 0 ) - this.margins.top,
+				co.left + ( over ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) -
+					( parseInt( $( ce ).css( "borderLeftWidth" ), 10 ) || 0 ) -
+					( parseInt( $( ce ).css( "paddingRight" ), 10 ) || 0 ) -
+					this.helperProportions.width - this.margins.left,
+				co.top + ( over ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) -
+					( parseInt( $( ce ).css( "borderTopWidth" ), 10 ) || 0 ) -
+					( parseInt( $( ce ).css( "paddingBottom" ), 10 ) || 0 ) -
+					this.helperProportions.height - this.margins.top
+			];
+		}
+
+	},
+
+	_convertPositionTo: function( d, pos ) {
+
+		if ( !pos ) {
+			pos = this.position;
+		}
+		var mod = d === "absolute" ? 1 : -1,
+			scroll = this.cssPosition === "absolute" &&
+				!( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
+				$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ?
+					this.offsetParent :
+					this.scrollParent,
+			scrollIsRootNode = ( /(html|body)/i ).test( scroll[ 0 ].tagName );
+
+		return {
+			top: (
+
+				// The absolute mouse position
+				pos.top	+
+
+				// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.relative.top * mod +
+
+				// The offsetParent's offset without borders (offset + border)
+				this.offset.parent.top * mod -
+				( ( this.cssPosition === "fixed" ?
+					-this.scrollParent.scrollTop() :
+					( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod )
+			),
+			left: (
+
+				// The absolute mouse position
+				pos.left +
+
+				// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.relative.left * mod +
+
+				// The offsetParent's offset without borders (offset + border)
+				this.offset.parent.left * mod	-
+				( ( this.cssPosition === "fixed" ?
+					-this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 :
+					scroll.scrollLeft() ) * mod )
+			)
+		};
+
+	},
+
+	_generatePosition: function( event ) {
+
+		var top, left,
+			o = this.options,
+			pageX = event.pageX,
+			pageY = event.pageY,
+			scroll = this.cssPosition === "absolute" &&
+				!( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
+				$.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ?
+					this.offsetParent :
+					this.scrollParent,
+				scrollIsRootNode = ( /(html|body)/i ).test( scroll[ 0 ].tagName );
+
+		// This is another very weird special case that only happens for relative elements:
+		// 1. If the css position is relative
+		// 2. and the scroll parent is the document or similar to the offset parent
+		// we have to refresh the relative offset during the scroll so there are no jumps
+		if ( this.cssPosition === "relative" && !( this.scrollParent[ 0 ] !== this.document[ 0 ] &&
+				this.scrollParent[ 0 ] !== this.offsetParent[ 0 ] ) ) {
+			this.offset.relative = this._getRelativeOffset();
+		}
+
+		/*
+		 * - Position constraining -
+		 * Constrain the position to a mix of grid, containment.
+		 */
+
+		if ( this.originalPosition ) { //If we are not dragging yet, we won't check for options
+
+			if ( this.containment ) {
+				if ( event.pageX - this.offset.click.left < this.containment[ 0 ] ) {
+					pageX = this.containment[ 0 ] + this.offset.click.left;
+				}
+				if ( event.pageY - this.offset.click.top < this.containment[ 1 ] ) {
+					pageY = this.containment[ 1 ] + this.offset.click.top;
+				}
+				if ( event.pageX - this.offset.click.left > this.containment[ 2 ] ) {
+					pageX = this.containment[ 2 ] + this.offset.click.left;
+				}
+				if ( event.pageY - this.offset.click.top > this.containment[ 3 ] ) {
+					pageY = this.containment[ 3 ] + this.offset.click.top;
+				}
+			}
+
+			if ( o.grid ) {
+				top = this.originalPageY + Math.round( ( pageY - this.originalPageY ) /
+					o.grid[ 1 ] ) * o.grid[ 1 ];
+				pageY = this.containment ?
+					( ( top - this.offset.click.top >= this.containment[ 1 ] &&
+						top - this.offset.click.top <= this.containment[ 3 ] ) ?
+							top :
+							( ( top - this.offset.click.top >= this.containment[ 1 ] ) ?
+								top - o.grid[ 1 ] : top + o.grid[ 1 ] ) ) :
+								top;
+
+				left = this.originalPageX + Math.round( ( pageX - this.originalPageX ) /
+					o.grid[ 0 ] ) * o.grid[ 0 ];
+				pageX = this.containment ?
+					( ( left - this.offset.click.left >= this.containment[ 0 ] &&
+						left - this.offset.click.left <= this.containment[ 2 ] ) ?
+							left :
+							( ( left - this.offset.click.left >= this.containment[ 0 ] ) ?
+								left - o.grid[ 0 ] : left + o.grid[ 0 ] ) ) :
+								left;
+			}
+
+		}
+
+		return {
+			top: (
+
+				// The absolute mouse position
+				pageY -
+
+				// Click offset (relative to the element)
+				this.offset.click.top -
+
+				// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.relative.top -
+
+				// The offsetParent's offset without borders (offset + border)
+				this.offset.parent.top +
+				( ( this.cssPosition === "fixed" ?
+					-this.scrollParent.scrollTop() :
+					( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) )
+			),
+			left: (
+
+				// The absolute mouse position
+				pageX -
+
+				// Click offset (relative to the element)
+				this.offset.click.left -
+
+				// Only for relative positioned nodes: Relative offset from element to offset parent
+				this.offset.relative.left -
+
+				// The offsetParent's offset without borders (offset + border)
+				this.offset.parent.left +
+				( ( this.cssPosition === "fixed" ?
+					-this.scrollParent.scrollLeft() :
+					scrollIsRootNode ? 0 : scroll.scrollLeft() ) )
+			)
+		};
+
+	},
+
+	_rearrange: function( event, i, a, hardRefresh ) {
+
+		a ? a[ 0 ].appendChild( this.placeholder[ 0 ] ) :
+			i.item[ 0 ].parentNode.insertBefore( this.placeholder[ 0 ],
+				( this.direction === "down" ? i.item[ 0 ] : i.item[ 0 ].nextSibling ) );
+
+		//Various things done here to improve the performance:
+		// 1. we create a setTimeout, that calls refreshPositions
+		// 2. on the instance, we have a counter variable, that get's higher after every append
+		// 3. on the local scope, we copy the counter variable, and check in the timeout,
+		// if it's still the same
+		// 4. this lets only the last addition to the timeout stack through
+		this.counter = this.counter ? ++this.counter : 1;
+		var counter = this.counter;
+
+		this._delay( function() {
+			if ( counter === this.counter ) {
+
+				//Precompute after each DOM insertion, NOT on mousemove
+				this.refreshPositions( !hardRefresh );
+			}
+		} );
+
+	},
+
+	_clear: function( event, noPropagation ) {
+
+		this.reverting = false;
+
+		// We delay all events that have to be triggered to after the point where the placeholder
+		// has been removed and everything else normalized again
+		var i,
+			delayedTriggers = [];
+
+		// We first have to update the dom position of the actual currentItem
+		// Note: don't do it if the current item is already removed (by a user), or it gets
+		// reappended (see #4088)
+		if ( !this._noFinalSort && this.currentItem.parent().length ) {
+			this.placeholder.before( this.currentItem );
+		}
+		this._noFinalSort = null;
+
+		if ( this.helper[ 0 ] === this.currentItem[ 0 ] ) {
+			for ( i in this._storedCSS ) {
+				if ( this._storedCSS[ i ] === "auto" || this._storedCSS[ i ] === "static" ) {
+					this._storedCSS[ i ] = "";
+				}
+			}
+			this.currentItem.css( this._storedCSS );
+			this._removeClass( this.currentItem, "ui-sortable-helper" );
+		} else {
+			this.currentItem.show();
+		}
+
+		if ( this.fromOutside && !noPropagation ) {
+			delayedTriggers.push( function( event ) {
+				this._trigger( "receive", event, this._uiHash( this.fromOutside ) );
+			} );
+		}
+		if ( ( this.fromOutside ||
+				this.domPosition.prev !==
+				this.currentItem.prev().not( ".ui-sortable-helper" )[ 0 ] ||
+				this.domPosition.parent !== this.currentItem.parent()[ 0 ] ) && !noPropagation ) {
+
+			// Trigger update callback if the DOM position has changed
+			delayedTriggers.push( function( event ) {
+				this._trigger( "update", event, this._uiHash() );
+			} );
+		}
+
+		// Check if the items Container has Changed and trigger appropriate
+		// events.
+		if ( this !== this.currentContainer ) {
+			if ( !noPropagation ) {
+				delayedTriggers.push( function( event ) {
+					this._trigger( "remove", event, this._uiHash() );
+				} );
+				delayedTriggers.push( ( function( c ) {
+					return function( event ) {
+						c._trigger( "receive", event, this._uiHash( this ) );
+					};
+				} ).call( this, this.currentContainer ) );
+				delayedTriggers.push( ( function( c ) {
+					return function( event ) {
+						c._trigger( "update", event, this._uiHash( this ) );
+					};
+				} ).call( this, this.currentContainer ) );
+			}
+		}
+
+		//Post events to containers
+		function delayEvent( type, instance, container ) {
+			return function( event ) {
+				container._trigger( type, event, instance._uiHash( instance ) );
+			};
+		}
+		for ( i = this.containers.length - 1; i >= 0; i-- ) {
+			if ( !noPropagation ) {
+				delayedTriggers.push( delayEvent( "deactivate", this, this.containers[ i ] ) );
+			}
+			if ( this.containers[ i ].containerCache.over ) {
+				delayedTriggers.push( delayEvent( "out", this, this.containers[ i ] ) );
+				this.containers[ i ].containerCache.over = 0;
+			}
+		}
+
+		//Do what was originally in plugins
+		if ( this.storedCursor ) {
+			this.document.find( "body" ).css( "cursor", this.storedCursor );
+			this.storedStylesheet.remove();
+		}
+		if ( this._storedOpacity ) {
+			this.helper.css( "opacity", this._storedOpacity );
+		}
+		if ( this._storedZIndex ) {
+			this.helper.css( "zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex );
+		}
+
+		this.dragging = false;
+
+		if ( !noPropagation ) {
+			this._trigger( "beforeStop", event, this._uiHash() );
+		}
+
+		//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately,
+		// it unbinds ALL events from the original node!
+		this.placeholder[ 0 ].parentNode.removeChild( this.placeholder[ 0 ] );
+
+		if ( !this.cancelHelperRemoval ) {
+			if ( this.helper[ 0 ] !== this.currentItem[ 0 ] ) {
+				this.helper.remove();
+			}
+			this.helper = null;
+		}
+
+		if ( !noPropagation ) {
+			for ( i = 0; i < delayedTriggers.length; i++ ) {
+
+				// Trigger all delayed events
+				delayedTriggers[ i ].call( this, event );
+			}
+			this._trigger( "stop", event, this._uiHash() );
+		}
+
+		this.fromOutside = false;
+		return !this.cancelHelperRemoval;
+
+	},
+
+	_trigger: function() {
+		if ( $.Widget.prototype._trigger.apply( this, arguments ) === false ) {
+			this.cancel();
+		}
+	},
+
+	_uiHash: function( _inst ) {
+		var inst = _inst || this;
+		return {
+			helper: inst.helper,
+			placeholder: inst.placeholder || $( [] ),
+			position: inst.position,
+			originalPosition: inst.originalPosition,
+			offset: inst.positionAbs,
+			item: inst.currentItem,
+			sender: _inst ? _inst.element : null
+		};
+	}
+
+} );
+
+
+/*!
+ * jQuery UI Spinner 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Spinner
+//>>group: Widgets
+//>>description: Displays buttons to easily input numbers via the keyboard or mouse.
+//>>docs: http://api.jqueryui.com/spinner/
+//>>demos: http://jqueryui.com/spinner/
+//>>css.structure: ../../themes/base/core.css
+//>>css.structure: ../../themes/base/spinner.css
+//>>css.theme: ../../themes/base/theme.css
+
+
+
+function spinnerModifer( fn ) {
+	return function() {
+		var previous = this.element.val();
+		fn.apply( this, arguments );
+		this._refresh();
+		if ( previous !== this.element.val() ) {
+			this._trigger( "change" );
+		}
+	};
+}
+
+$.widget( "ui.spinner", {
+	version: "1.12.1",
+	defaultElement: "<input>",
+	widgetEventPrefix: "spin",
+	options: {
+		classes: {
+			"ui-spinner": "ui-corner-all",
+			"ui-spinner-down": "ui-corner-br",
+			"ui-spinner-up": "ui-corner-tr"
+		},
+		culture: null,
+		icons: {
+			down: "ui-icon-triangle-1-s",
+			up: "ui-icon-triangle-1-n"
+		},
+		incremental: true,
+		max: null,
+		min: null,
+		numberFormat: null,
+		page: 10,
+		step: 1,
+
+		change: null,
+		spin: null,
+		start: null,
+		stop: null
+	},
+
+	_create: function() {
+
+		// handle string values that need to be parsed
+		this._setOption( "max", this.options.max );
+		this._setOption( "min", this.options.min );
+		this._setOption( "step", this.options.step );
+
+		// Only format if there is a value, prevents the field from being marked
+		// as invalid in Firefox, see #9573.
+		if ( this.value() !== "" ) {
+
+			// Format the value, but don't constrain.
+			this._value( this.element.val(), true );
+		}
+
+		this._draw();
+		this._on( this._events );
+		this._refresh();
+
+		// Turning off autocomplete prevents the browser from remembering the
+		// value when navigating through history, so we re-enable autocomplete
+		// if the page is unloaded before the widget is destroyed. #7790
+		this._on( this.window, {
+			beforeunload: function() {
+				this.element.removeAttr( "autocomplete" );
+			}
+		} );
+	},
+
+	_getCreateOptions: function() {
+		var options = this._super();
+		var element = this.element;
+
+		$.each( [ "min", "max", "step" ], function( i, option ) {
+			var value = element.attr( option );
+			if ( value != null && value.length ) {
+				options[ option ] = value;
+			}
+		} );
+
+		return options;
+	},
+
+	_events: {
+		keydown: function( event ) {
+			if ( this._start( event ) && this._keydown( event ) ) {
+				event.preventDefault();
+			}
+		},
+		keyup: "_stop",
+		focus: function() {
+			this.previous = this.element.val();
+		},
+		blur: function( event ) {
+			if ( this.cancelBlur ) {
+				delete this.cancelBlur;
+				return;
+			}
+
+			this._stop();
+			this._refresh();
+			if ( this.previous !== this.element.val() ) {
+				this._trigger( "change", event );
+			}
+		},
+		mousewheel: function( event, delta ) {
+			if ( !delta ) {
+				return;
+			}
+			if ( !this.spinning && !this._start( event ) ) {
+				return false;
+			}
+
+			this._spin( ( delta > 0 ? 1 : -1 ) * this.options.step, event );
+			clearTimeout( this.mousewheelTimer );
+			this.mousewheelTimer = this._delay( function() {
+				if ( this.spinning ) {
+					this._stop( event );
+				}
+			}, 100 );
+			event.preventDefault();
+		},
+		"mousedown .ui-spinner-button": function( event ) {
+			var previous;
+
+			// We never want the buttons to have focus; whenever the user is
+			// interacting with the spinner, the focus should be on the input.
+			// If the input is focused then this.previous is properly set from
+			// when the input first received focus. If the input is not focused
+			// then we need to set this.previous based on the value before spinning.
+			previous = this.element[ 0 ] === $.ui.safeActiveElement( this.document[ 0 ] ) ?
+				this.previous : this.element.val();
+			function checkFocus() {
+				var isActive = this.element[ 0 ] === $.ui.safeActiveElement( this.document[ 0 ] );
+				if ( !isActive ) {
+					this.element.trigger( "focus" );
+					this.previous = previous;
+
+					// support: IE
+					// IE sets focus asynchronously, so we need to check if focus
+					// moved off of the input because the user clicked on the button.
+					this._delay( function() {
+						this.previous = previous;
+					} );
+				}
+			}
+
+			// Ensure focus is on (or stays on) the text field
+			event.preventDefault();
+			checkFocus.call( this );
+
+			// Support: IE
+			// IE doesn't prevent moving focus even with event.preventDefault()
+			// so we set a flag to know when we should ignore the blur event
+			// and check (again) if focus moved off of the input.
+			this.cancelBlur = true;
+			this._delay( function() {
+				delete this.cancelBlur;
+				checkFocus.call( this );
+			} );
+
+			if ( this._start( event ) === false ) {
+				return;
+			}
+
+			this._repeat( null, $( event.currentTarget )
+				.hasClass( "ui-spinner-up" ) ? 1 : -1, event );
+		},
+		"mouseup .ui-spinner-button": "_stop",
+		"mouseenter .ui-spinner-button": function( event ) {
+
+			// button will add ui-state-active if mouse was down while mouseleave and kept down
+			if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) {
+				return;
+			}
+
+			if ( this._start( event ) === false ) {
+				return false;
+			}
+			this._repeat( null, $( event.currentTarget )
+				.hasClass( "ui-spinner-up" ) ? 1 : -1, event );
+		},
+
+		// TODO: do we really want to consider this a stop?
+		// shouldn't we just stop the repeater and wait until mouseup before
+		// we trigger the stop event?
+		"mouseleave .ui-spinner-button": "_stop"
+	},
+
+	// Support mobile enhanced option and make backcompat more sane
+	_enhance: function() {
+		this.uiSpinner = this.element
+			.attr( "autocomplete", "off" )
+			.wrap( "<span>" )
+			.parent()
+
+				// Add buttons
+				.append(
+					"<a></a><a></a>"
+				);
+	},
+
+	_draw: function() {
+		this._enhance();
+
+		this._addClass( this.uiSpinner, "ui-spinner", "ui-widget ui-widget-content" );
+		this._addClass( "ui-spinner-input" );
+
+		this.element.attr( "role", "spinbutton" );
+
+		// Button bindings
+		this.buttons = this.uiSpinner.children( "a" )
+			.attr( "tabIndex", -1 )
+			.attr( "aria-hidden", true )
+			.button( {
+				classes: {
+					"ui-button": ""
+				}
+			} );
+
+		// TODO: Right now button does not support classes this is already updated in button PR
+		this._removeClass( this.buttons, "ui-corner-all" );
+
+		this._addClass( this.buttons.first(), "ui-spinner-button ui-spinner-up" );
+		this._addClass( this.buttons.last(), "ui-spinner-button ui-spinner-down" );
+		this.buttons.first().button( {
+			"icon": this.options.icons.up,
+			"showLabel": false
+		} );
+		this.buttons.last().button( {
+			"icon": this.options.icons.down,
+			"showLabel": false
+		} );
+
+		// IE 6 doesn't understand height: 50% for the buttons
+		// unless the wrapper has an explicit height
+		if ( this.buttons.height() > Math.ceil( this.uiSpinner.height() * 0.5 ) &&
+				this.uiSpinner.height() > 0 ) {
+			this.uiSpinner.height( this.uiSpinner.height() );
+		}
+	},
+
+	_keydown: function( event ) {
+		var options = this.options,
+			keyCode = $.ui.keyCode;
+
+		switch ( event.keyCode ) {
+		case keyCode.UP:
+			this._repeat( null, 1, event );
+			return true;
+		case keyCode.DOWN:
+			this._repeat( null, -1, event );
+			return true;
+		case keyCode.PAGE_UP:
+			this._repeat( null, options.page, event );
+			return true;
+		case keyCode.PAGE_DOWN:
+			this._repeat( null, -options.page, event );
+			return true;
+		}
+
+		return false;
+	},
+
+	_start: function( event ) {
+		if ( !this.spinning && this._trigger( "start", event ) === false ) {
+			return false;
+		}
+
+		if ( !this.counter ) {
+			this.counter = 1;
+		}
+		this.spinning = true;
+		return true;
+	},
+
+	_repeat: function( i, steps, event ) {
+		i = i || 500;
+
+		clearTimeout( this.timer );
+		this.timer = this._delay( function() {
+			this._repeat( 40, steps, event );
+		}, i );
+
+		this._spin( steps * this.options.step, event );
+	},
+
+	_spin: function( step, event ) {
+		var value = this.value() || 0;
+
+		if ( !this.counter ) {
+			this.counter = 1;
+		}
+
+		value = this._adjustValue( value + step * this._increment( this.counter ) );
+
+		if ( !this.spinning || this._trigger( "spin", event, { value: value } ) !== false ) {
+			this._value( value );
+			this.counter++;
+		}
+	},
+
+	_increment: function( i ) {
+		var incremental = this.options.incremental;
+
+		if ( incremental ) {
+			return $.isFunction( incremental ) ?
+				incremental( i ) :
+				Math.floor( i * i * i / 50000 - i * i / 500 + 17 * i / 200 + 1 );
+		}
+
+		return 1;
+	},
+
+	_precision: function() {
+		var precision = this._precisionOf( this.options.step );
+		if ( this.options.min !== null ) {
+			precision = Math.max( precision, this._precisionOf( this.options.min ) );
+		}
+		return precision;
+	},
+
+	_precisionOf: function( num ) {
+		var str = num.toString(),
+			decimal = str.indexOf( "." );
+		return decimal === -1 ? 0 : str.length - decimal - 1;
+	},
+
+	_adjustValue: function( value ) {
+		var base, aboveMin,
+			options = this.options;
+
+		// Make sure we're at a valid step
+		// - find out where we are relative to the base (min or 0)
+		base = options.min !== null ? options.min : 0;
+		aboveMin = value - base;
+
+		// - round to the nearest step
+		aboveMin = Math.round( aboveMin / options.step ) * options.step;
+
+		// - rounding is based on 0, so adjust back to our base
+		value = base + aboveMin;
+
+		// Fix precision from bad JS floating point math
+		value = parseFloat( value.toFixed( this._precision() ) );
+
+		// Clamp the value
+		if ( options.max !== null && value > options.max ) {
+			return options.max;
+		}
+		if ( options.min !== null && value < options.min ) {
+			return options.min;
+		}
+
+		return value;
+	},
+
+	_stop: function( event ) {
+		if ( !this.spinning ) {
+			return;
+		}
+
+		clearTimeout( this.timer );
+		clearTimeout( this.mousewheelTimer );
+		this.counter = 0;
+		this.spinning = false;
+		this._trigger( "stop", event );
+	},
+
+	_setOption: function( key, value ) {
+		var prevValue, first, last;
+
+		if ( key === "culture" || key === "numberFormat" ) {
+			prevValue = this._parse( this.element.val() );
+			this.options[ key ] = value;
+			this.element.val( this._format( prevValue ) );
+			return;
+		}
+
+		if ( key === "max" || key === "min" || key === "step" ) {
+			if ( typeof value === "string" ) {
+				value = this._parse( value );
+			}
+		}
+		if ( key === "icons" ) {
+			first = this.buttons.first().find( ".ui-icon" );
+			this._removeClass( first, null, this.options.icons.up );
+			this._addClass( first, null, value.up );
+			last = this.buttons.last().find( ".ui-icon" );
+			this._removeClass( last, null, this.options.icons.down );
+			this._addClass( last, null, value.down );
+		}
+
+		this._super( key, value );
+	},
+
+	_setOptionDisabled: function( value ) {
+		this._super( value );
+
+		this._toggleClass( this.uiSpinner, null, "ui-state-disabled", !!value );
+		this.element.prop( "disabled", !!value );
+		this.buttons.button( value ? "disable" : "enable" );
+	},
+
+	_setOptions: spinnerModifer( function( options ) {
+		this._super( options );
+	} ),
+
+	_parse: function( val ) {
+		if ( typeof val === "string" && val !== "" ) {
+			val = window.Globalize && this.options.numberFormat ?
+				Globalize.parseFloat( val, 10, this.options.culture ) : +val;
+		}
+		return val === "" || isNaN( val ) ? null : val;
+	},
+
+	_format: function( value ) {
+		if ( value === "" ) {
+			return "";
+		}
+		return window.Globalize && this.options.numberFormat ?
+			Globalize.format( value, this.options.numberFormat, this.options.culture ) :
+			value;
+	},
+
+	_refresh: function() {
+		this.element.attr( {
+			"aria-valuemin": this.options.min,
+			"aria-valuemax": this.options.max,
+
+			// TODO: what should we do with values that can't be parsed?
+			"aria-valuenow": this._parse( this.element.val() )
+		} );
+	},
+
+	isValid: function() {
+		var value = this.value();
+
+		// Null is invalid
+		if ( value === null ) {
+			return false;
+		}
+
+		// If value gets adjusted, it's invalid
+		return value === this._adjustValue( value );
+	},
+
+	// Update the value without triggering change
+	_value: function( value, allowAny ) {
+		var parsed;
+		if ( value !== "" ) {
+			parsed = this._parse( value );
+			if ( parsed !== null ) {
+				if ( !allowAny ) {
+					parsed = this._adjustValue( parsed );
+				}
+				value = this._format( parsed );
+			}
+		}
+		this.element.val( value );
+		this._refresh();
+	},
+
+	_destroy: function() {
+		this.element
+			.prop( "disabled", false )
+			.removeAttr( "autocomplete role aria-valuemin aria-valuemax aria-valuenow" );
+
+		this.uiSpinner.replaceWith( this.element );
+	},
+
+	stepUp: spinnerModifer( function( steps ) {
+		this._stepUp( steps );
+	} ),
+	_stepUp: function( steps ) {
+		if ( this._start() ) {
+			this._spin( ( steps || 1 ) * this.options.step );
+			this._stop();
+		}
+	},
+
+	stepDown: spinnerModifer( function( steps ) {
+		this._stepDown( steps );
+	} ),
+	_stepDown: function( steps ) {
+		if ( this._start() ) {
+			this._spin( ( steps || 1 ) * -this.options.step );
+			this._stop();
+		}
+	},
+
+	pageUp: spinnerModifer( function( pages ) {
+		this._stepUp( ( pages || 1 ) * this.options.page );
+	} ),
+
+	pageDown: spinnerModifer( function( pages ) {
+		this._stepDown( ( pages || 1 ) * this.options.page );
+	} ),
+
+	value: function( newVal ) {
+		if ( !arguments.length ) {
+			return this._parse( this.element.val() );
+		}
+		spinnerModifer( this._value ).call( this, newVal );
+	},
+
+	widget: function() {
+		return this.uiSpinner;
+	}
+} );
+
+// DEPRECATED
+// TODO: switch return back to widget declaration at top of file when this is removed
+if ( $.uiBackCompat !== false ) {
+
+	// Backcompat for spinner html extension points
+	$.widget( "ui.spinner", $.ui.spinner, {
+		_enhance: function() {
+			this.uiSpinner = this.element
+				.attr( "autocomplete", "off" )
+				.wrap( this._uiSpinnerHtml() )
+				.parent()
+
+					// Add buttons
+					.append( this._buttonHtml() );
+		},
+		_uiSpinnerHtml: function() {
+			return "<span>";
+		},
+
+		_buttonHtml: function() {
+			return "<a></a><a></a>";
+		}
+	} );
+}
+
+var widgetsSpinner = $.ui.spinner;
+
+
+/*!
+ * jQuery UI Tabs 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Tabs
+//>>group: Widgets
+//>>description: Transforms a set of container elements into a tab structure.
+//>>docs: http://api.jqueryui.com/tabs/
+//>>demos: http://jqueryui.com/tabs/
+//>>css.structure: ../../themes/base/core.css
+//>>css.structure: ../../themes/base/tabs.css
+//>>css.theme: ../../themes/base/theme.css
+
+
+
+$.widget( "ui.tabs", {
+	version: "1.12.1",
+	delay: 300,
+	options: {
+		active: null,
+		classes: {
+			"ui-tabs": "ui-corner-all",
+			"ui-tabs-nav": "ui-corner-all",
+			"ui-tabs-panel": "ui-corner-bottom",
+			"ui-tabs-tab": "ui-corner-top"
+		},
+		collapsible: false,
+		event: "click",
+		heightStyle: "content",
+		hide: null,
+		show: null,
+
+		// Callbacks
+		activate: null,
+		beforeActivate: null,
+		beforeLoad: null,
+		load: null
+	},
+
+	_isLocal: ( function() {
+		var rhash = /#.*$/;
+
+		return function( anchor ) {
+			var anchorUrl, locationUrl;
+
+			anchorUrl = anchor.href.replace( rhash, "" );
+			locationUrl = location.href.replace( rhash, "" );
+
+			// Decoding may throw an error if the URL isn't UTF-8 (#9518)
+			try {
+				anchorUrl = decodeURIComponent( anchorUrl );
+			} catch ( error ) {}
+			try {
+				locationUrl = decodeURIComponent( locationUrl );
+			} catch ( error ) {}
+
+			return anchor.hash.length > 1 && anchorUrl === locationUrl;
+		};
+	} )(),
+
+	_create: function() {
+		var that = this,
+			options = this.options;
+
+		this.running = false;
+
+		this._addClass( "ui-tabs", "ui-widget ui-widget-content" );
+		this._toggleClass( "ui-tabs-collapsible", null, options.collapsible );
+
+		this._processTabs();
+		options.active = this._initialActive();
+
+		// Take disabling tabs via class attribute from HTML
+		// into account and update option properly.
+		if ( $.isArray( options.disabled ) ) {
+			options.disabled = $.unique( options.disabled.concat(
+				$.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) {
+					return that.tabs.index( li );
+				} )
+			) ).sort();
+		}
+
+		// Check for length avoids error when initializing empty list
+		if ( this.options.active !== false && this.anchors.length ) {
+			this.active = this._findActive( options.active );
+		} else {
+			this.active = $();
+		}
+
+		this._refresh();
+
+		if ( this.active.length ) {
+			this.load( options.active );
+		}
+	},
+
+	_initialActive: function() {
+		var active = this.options.active,
+			collapsible = this.options.collapsible,
+			locationHash = location.hash.substring( 1 );
+
+		if ( active === null ) {
+
+			// check the fragment identifier in the URL
+			if ( locationHash ) {
+				this.tabs.each( function( i, tab ) {
+					if ( $( tab ).attr( "aria-controls" ) === locationHash ) {
+						active = i;
+						return false;
+					}
+				} );
+			}
+
+			// Check for a tab marked active via a class
+			if ( active === null ) {
+				active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) );
+			}
+
+			// No active tab, set to false
+			if ( active === null || active === -1 ) {
+				active = this.tabs.length ? 0 : false;
+			}
+		}
+
+		// Handle numbers: negative, out of range
+		if ( active !== false ) {
+			active = this.tabs.index( this.tabs.eq( active ) );
+			if ( active === -1 ) {
+				active = collapsible ? false : 0;
+			}
+		}
+
+		// Don't allow collapsible: false and active: false
+		if ( !collapsible && active === false && this.anchors.length ) {
+			active = 0;
+		}
+
+		return active;
+	},
+
+	_getCreateEventData: function() {
+		return {
+			tab: this.active,
+			panel: !this.active.length ? $() : this._getPanelForTab( this.active )
+		};
+	},
+
+	_tabKeydown: function( event ) {
+		var focusedTab = $( $.ui.safeActiveElement( this.document[ 0 ] ) ).closest( "li" ),
+			selectedIndex = this.tabs.index( focusedTab ),
+			goingForward = true;
+
+		if ( this._handlePageNav( event ) ) {
+			return;
+		}
+
+		switch ( event.keyCode ) {
+		case $.ui.keyCode.RIGHT:
+		case $.ui.keyCode.DOWN:
+			selectedIndex++;
+			break;
+		case $.ui.keyCode.UP:
+		case $.ui.keyCode.LEFT:
+			goingForward = false;
+			selectedIndex--;
+			break;
+		case $.ui.keyCode.END:
+			selectedIndex = this.anchors.length - 1;
+			break;
+		case $.ui.keyCode.HOME:
+			selectedIndex = 0;
+			break;
+		case $.ui.keyCode.SPACE:
+
+			// Activate only, no collapsing
+			event.preventDefault();
+			clearTimeout( this.activating );
+			this._activate( selectedIndex );
+			return;
+		case $.ui.keyCode.ENTER:
+
+			// Toggle (cancel delayed activation, allow collapsing)
+			event.preventDefault();
+			clearTimeout( this.activating );
+
+			// Determine if we should collapse or activate
+			this._activate( selectedIndex === this.options.active ? false : selectedIndex );
+			return;
+		default:
+			return;
+		}
+
+		// Focus the appropriate tab, based on which key was pressed
+		event.preventDefault();
+		clearTimeout( this.activating );
+		selectedIndex = this._focusNextTab( selectedIndex, goingForward );
+
+		// Navigating with control/command key will prevent automatic activation
+		if ( !event.ctrlKey && !event.metaKey ) {
+
+			// Update aria-selected immediately so that AT think the tab is already selected.
+			// Otherwise AT may confuse the user by stating that they need to activate the tab,
+			// but the tab will already be activated by the time the announcement finishes.
+			focusedTab.attr( "aria-selected", "false" );
+			this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" );
+
+			this.activating = this._delay( function() {
+				this.option( "active", selectedIndex );
+			}, this.delay );
+		}
+	},
+
+	_panelKeydown: function( event ) {
+		if ( this._handlePageNav( event ) ) {
+			return;
+		}
+
+		// Ctrl+up moves focus to the current tab
+		if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) {
+			event.preventDefault();
+			this.active.trigger( "focus" );
+		}
+	},
+
+	// Alt+page up/down moves focus to the previous/next tab (and activates)
+	_handlePageNav: function( event ) {
+		if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) {
+			this._activate( this._focusNextTab( this.options.active - 1, false ) );
+			return true;
+		}
+		if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) {
+			this._activate( this._focusNextTab( this.options.active + 1, true ) );
+			return true;
+		}
+	},
+
+	_findNextTab: function( index, goingForward ) {
+		var lastTabIndex = this.tabs.length - 1;
+
+		function constrain() {
+			if ( index > lastTabIndex ) {
+				index = 0;
+			}
+			if ( index < 0 ) {
+				index = lastTabIndex;
+			}
+			return index;
+		}
+
+		while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) {
+			index = goingForward ? index + 1 : index - 1;
+		}
+
+		return index;
+	},
+
+	_focusNextTab: function( index, goingForward ) {
+		index = this._findNextTab( index, goingForward );
+		this.tabs.eq( index ).trigger( "focus" );
+		return index;
+	},
+
+	_setOption: function( key, value ) {
+		if ( key === "active" ) {
+
+			// _activate() will handle invalid values and update this.options
+			this._activate( value );
+			return;
+		}
+
+		this._super( key, value );
+
+		if ( key === "collapsible" ) {
+			this._toggleClass( "ui-tabs-collapsible", null, value );
+
+			// Setting collapsible: false while collapsed; open first panel
+			if ( !value && this.options.active === false ) {
+				this._activate( 0 );
+			}
+		}
+
+		if ( key === "event" ) {
+			this._setupEvents( value );
+		}
+
+		if ( key === "heightStyle" ) {
+			this._setupHeightStyle( value );
+		}
+	},
+
+	_sanitizeSelector: function( hash ) {
+		return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : "";
+	},
+
+	refresh: function() {
+		var options = this.options,
+			lis = this.tablist.children( ":has(a[href])" );
+
+		// Get disabled tabs from class attribute from HTML
+		// this will get converted to a boolean if needed in _refresh()
+		options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) {
+			return lis.index( tab );
+		} );
+
+		this._processTabs();
+
+		// Was collapsed or no tabs
+		if ( options.active === false || !this.anchors.length ) {
+			options.active = false;
+			this.active = $();
+
+		// was active, but active tab is gone
+		} else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) {
+
+			// all remaining tabs are disabled
+			if ( this.tabs.length === options.disabled.length ) {
+				options.active = false;
+				this.active = $();
+
+			// activate previous tab
+			} else {
+				this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) );
+			}
+
+		// was active, active tab still exists
+		} else {
+
+			// make sure active index is correct
+			options.active = this.tabs.index( this.active );
+		}
+
+		this._refresh();
+	},
+
+	_refresh: function() {
+		this._setOptionDisabled( this.options.disabled );
+		this._setupEvents( this.options.event );
+		this._setupHeightStyle( this.options.heightStyle );
+
+		this.tabs.not( this.active ).attr( {
+			"aria-selected": "false",
+			"aria-expanded": "false",
+			tabIndex: -1
+		} );
+		this.panels.not( this._getPanelForTab( this.active ) )
+			.hide()
+			.attr( {
+				"aria-hidden": "true"
+			} );
+
+		// Make sure one tab is in the tab order
+		if ( !this.active.length ) {
+			this.tabs.eq( 0 ).attr( "tabIndex", 0 );
+		} else {
+			this.active
+				.attr( {
+					"aria-selected": "true",
+					"aria-expanded": "true",
+					tabIndex: 0
+				} );
+			this._addClass( this.active, "ui-tabs-active", "ui-state-active" );
+			this._getPanelForTab( this.active )
+				.show()
+				.attr( {
+					"aria-hidden": "false"
+				} );
+		}
+	},
+
+	_processTabs: function() {
+		var that = this,
+			prevTabs = this.tabs,
+			prevAnchors = this.anchors,
+			prevPanels = this.panels;
+
+		this.tablist = this._getList().attr( "role", "tablist" );
+		this._addClass( this.tablist, "ui-tabs-nav",
+			"ui-helper-reset ui-helper-clearfix ui-widget-header" );
+
+		// Prevent users from focusing disabled tabs via click
+		this.tablist
+			.on( "mousedown" + this.eventNamespace, "> li", function( event ) {
+				if ( $( this ).is( ".ui-state-disabled" ) ) {
+					event.preventDefault();
+				}
+			} )
+
+			// Support: IE <9
+			// Preventing the default action in mousedown doesn't prevent IE
+			// from focusing the element, so if the anchor gets focused, blur.
+			// We don't have to worry about focusing the previously focused
+			// element since clicking on a non-focusable element should focus
+			// the body anyway.
+			.on( "focus" + this.eventNamespace, ".ui-tabs-anchor", function() {
+				if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) {
+					this.blur();
+				}
+			} );
+
+		this.tabs = this.tablist.find( "> li:has(a[href])" )
+			.attr( {
+				role: "tab",
+				tabIndex: -1
+			} );
+		this._addClass( this.tabs, "ui-tabs-tab", "ui-state-default" );
+
+		this.anchors = this.tabs.map( function() {
+			return $( "a", this )[ 0 ];
+		} )
+			.attr( {
+				role: "presentation",
+				tabIndex: -1
+			} );
+		this._addClass( this.anchors, "ui-tabs-anchor" );
+
+		this.panels = $();
+
+		this.anchors.each( function( i, anchor ) {
+			var selector, panel, panelId,
+				anchorId = $( anchor ).uniqueId().attr( "id" ),
+				tab = $( anchor ).closest( "li" ),
+				originalAriaControls = tab.attr( "aria-controls" );
+
+			// Inline tab
+			if ( that._isLocal( anchor ) ) {
+				selector = anchor.hash;
+				panelId = selector.substring( 1 );
+				panel = that.element.find( that._sanitizeSelector( selector ) );
+
+			// remote tab
+			} else {
+
+				// If the tab doesn't already have aria-controls,
+				// generate an id by using a throw-away element
+				panelId = tab.attr( "aria-controls" ) || $( {} ).uniqueId()[ 0 ].id;
+				selector = "#" + panelId;
+				panel = that.element.find( selector );
+				if ( !panel.length ) {
+					panel = that._createPanel( panelId );
+					panel.insertAfter( that.panels[ i - 1 ] || that.tablist );
+				}
+				panel.attr( "aria-live", "polite" );
+			}
+
+			if ( panel.length ) {
+				that.panels = that.panels.add( panel );
+			}
+			if ( originalAriaControls ) {
+				tab.data( "ui-tabs-aria-controls", originalAriaControls );
+			}
+			tab.attr( {
+				"aria-controls": panelId,
+				"aria-labelledby": anchorId
+			} );
+			panel.attr( "aria-labelledby", anchorId );
+		} );
+
+		this.panels.attr( "role", "tabpanel" );
+		this._addClass( this.panels, "ui-tabs-panel", "ui-widget-content" );
+
+		// Avoid memory leaks (#10056)
+		if ( prevTabs ) {
+			this._off( prevTabs.not( this.tabs ) );
+			this._off( prevAnchors.not( this.anchors ) );
+			this._off( prevPanels.not( this.panels ) );
+		}
+	},
+
+	// Allow overriding how to find the list for rare usage scenarios (#7715)
+	_getList: function() {
+		return this.tablist || this.element.find( "ol, ul" ).eq( 0 );
+	},
+
+	_createPanel: function( id ) {
+		return $( "<div>" )
+			.attr( "id", id )
+			.data( "ui-tabs-destroy", true );
+	},
+
+	_setOptionDisabled: function( disabled ) {
+		var currentItem, li, i;
+
+		if ( $.isArray( disabled ) ) {
+			if ( !disabled.length ) {
+				disabled = false;
+			} else if ( disabled.length === this.anchors.length ) {
+				disabled = true;
+			}
+		}
+
+		// Disable tabs
+		for ( i = 0; ( li = this.tabs[ i ] ); i++ ) {
+			currentItem = $( li );
+			if ( disabled === true || $.inArray( i, disabled ) !== -1 ) {
+				currentItem.attr( "aria-disabled", "true" );
+				this._addClass( currentItem, null, "ui-state-disabled" );
+			} else {
+				currentItem.removeAttr( "aria-disabled" );
+				this._removeClass( currentItem, null, "ui-state-disabled" );
+			}
+		}
+
+		this.options.disabled = disabled;
+
+		this._toggleClass( this.widget(), this.widgetFullName + "-disabled", null,
+			disabled === true );
+	},
+
+	_setupEvents: function( event ) {
+		var events = {};
+		if ( event ) {
+			$.each( event.split( " " ), function( index, eventName ) {
+				events[ eventName ] = "_eventHandler";
+			} );
+		}
+
+		this._off( this.anchors.add( this.tabs ).add( this.panels ) );
+
+		// Always prevent the default action, even when disabled
+		this._on( true, this.anchors, {
+			click: function( event ) {
+				event.preventDefault();
+			}
+		} );
+		this._on( this.anchors, events );
+		this._on( this.tabs, { keydown: "_tabKeydown" } );
+		this._on( this.panels, { keydown: "_panelKeydown" } );
+
+		this._focusable( this.tabs );
+		this._hoverable( this.tabs );
+	},
+
+	_setupHeightStyle: function( heightStyle ) {
+		var maxHeight,
+			parent = this.element.parent();
+
+		if ( heightStyle === "fill" ) {
+			maxHeight = parent.height();
+			maxHeight -= this.element.outerHeight() - this.element.height();
+
+			this.element.siblings( ":visible" ).each( function() {
+				var elem = $( this ),
+					position = elem.css( "position" );
+
+				if ( position === "absolute" || position === "fixed" ) {
+					return;
+				}
+				maxHeight -= elem.outerHeight( true );
+			} );
+
+			this.element.children().not( this.panels ).each( function() {
+				maxHeight -= $( this ).outerHeight( true );
+			} );
+
+			this.panels.each( function() {
+				$( this ).height( Math.max( 0, maxHeight -
+					$( this ).innerHeight() + $( this ).height() ) );
+			} )
+				.css( "overflow", "auto" );
+		} else if ( heightStyle === "auto" ) {
+			maxHeight = 0;
+			this.panels.each( function() {
+				maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
+			} ).height( maxHeight );
+		}
+	},
+
+	_eventHandler: function( event ) {
+		var options = this.options,
+			active = this.active,
+			anchor = $( event.currentTarget ),
+			tab = anchor.closest( "li" ),
+			clickedIsActive = tab[ 0 ] === active[ 0 ],
+			collapsing = clickedIsActive && options.collapsible,
+			toShow = collapsing ? $() : this._getPanelForTab( tab ),
+			toHide = !active.length ? $() : this._getPanelForTab( active ),
+			eventData = {
+				oldTab: active,
+				oldPanel: toHide,
+				newTab: collapsing ? $() : tab,
+				newPanel: toShow
+			};
+
+		event.preventDefault();
+
+		if ( tab.hasClass( "ui-state-disabled" ) ||
+
+				// tab is already loading
+				tab.hasClass( "ui-tabs-loading" ) ||
+
+				// can't switch durning an animation
+				this.running ||
+
+				// click on active header, but not collapsible
+				( clickedIsActive && !options.collapsible ) ||
+
+				// allow canceling activation
+				( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
+			return;
+		}
+
+		options.active = collapsing ? false : this.tabs.index( tab );
+
+		this.active = clickedIsActive ? $() : tab;
+		if ( this.xhr ) {
+			this.xhr.abort();
+		}
+
+		if ( !toHide.length && !toShow.length ) {
+			$.error( "jQuery UI Tabs: Mismatching fragment identifier." );
+		}
+
+		if ( toShow.length ) {
+			this.load( this.tabs.index( tab ), event );
+		}
+		this._toggle( event, eventData );
+	},
+
+	// Handles show/hide for selecting tabs
+	_toggle: function( event, eventData ) {
+		var that = this,
+			toShow = eventData.newPanel,
+			toHide = eventData.oldPanel;
+
+		this.running = true;
+
+		function complete() {
+			that.running = false;
+			that._trigger( "activate", event, eventData );
+		}
+
+		function show() {
+			that._addClass( eventData.newTab.closest( "li" ), "ui-tabs-active", "ui-state-active" );
+
+			if ( toShow.length && that.options.show ) {
+				that._show( toShow, that.options.show, complete );
+			} else {
+				toShow.show();
+				complete();
+			}
+		}
+
+		// Start out by hiding, then showing, then completing
+		if ( toHide.length && this.options.hide ) {
+			this._hide( toHide, this.options.hide, function() {
+				that._removeClass( eventData.oldTab.closest( "li" ),
+					"ui-tabs-active", "ui-state-active" );
+				show();
+			} );
+		} else {
+			this._removeClass( eventData.oldTab.closest( "li" ),
+				"ui-tabs-active", "ui-state-active" );
+			toHide.hide();
+			show();
+		}
+
+		toHide.attr( "aria-hidden", "true" );
+		eventData.oldTab.attr( {
+			"aria-selected": "false",
+			"aria-expanded": "false"
+		} );
+
+		// If we're switching tabs, remove the old tab from the tab order.
+		// If we're opening from collapsed state, remove the previous tab from the tab order.
+		// If we're collapsing, then keep the collapsing tab in the tab order.
+		if ( toShow.length && toHide.length ) {
+			eventData.oldTab.attr( "tabIndex", -1 );
+		} else if ( toShow.length ) {
+			this.tabs.filter( function() {
+				return $( this ).attr( "tabIndex" ) === 0;
+			} )
+				.attr( "tabIndex", -1 );
+		}
+
+		toShow.attr( "aria-hidden", "false" );
+		eventData.newTab.attr( {
+			"aria-selected": "true",
+			"aria-expanded": "true",
+			tabIndex: 0
+		} );
+	},
+
+	_activate: function( index ) {
+		var anchor,
+			active = this._findActive( index );
+
+		// Trying to activate the already active panel
+		if ( active[ 0 ] === this.active[ 0 ] ) {
+			return;
+		}
+
+		// Trying to collapse, simulate a click on the current active header
+		if ( !active.length ) {
+			active = this.active;
+		}
+
+		anchor = active.find( ".ui-tabs-anchor" )[ 0 ];
+		this._eventHandler( {
+			target: anchor,
+			currentTarget: anchor,
+			preventDefault: $.noop
+		} );
+	},
+
+	_findActive: function( index ) {
+		return index === false ? $() : this.tabs.eq( index );
+	},
+
+	_getIndex: function( index ) {
+
+		// meta-function to give users option to provide a href string instead of a numerical index.
+		if ( typeof index === "string" ) {
+			index = this.anchors.index( this.anchors.filter( "[href$='" +
+				$.ui.escapeSelector( index ) + "']" ) );
+		}
+
+		return index;
+	},
+
+	_destroy: function() {
+		if ( this.xhr ) {
+			this.xhr.abort();
+		}
+
+		this.tablist
+			.removeAttr( "role" )
+			.off( this.eventNamespace );
+
+		this.anchors
+			.removeAttr( "role tabIndex" )
+			.removeUniqueId();
+
+		this.tabs.add( this.panels ).each( function() {
+			if ( $.data( this, "ui-tabs-destroy" ) ) {
+				$( this ).remove();
+			} else {
+				$( this ).removeAttr( "role tabIndex " +
+					"aria-live aria-busy aria-selected aria-labelledby aria-hidden aria-expanded" );
+			}
+		} );
+
+		this.tabs.each( function() {
+			var li = $( this ),
+				prev = li.data( "ui-tabs-aria-controls" );
+			if ( prev ) {
+				li
+					.attr( "aria-controls", prev )
+					.removeData( "ui-tabs-aria-controls" );
+			} else {
+				li.removeAttr( "aria-controls" );
+			}
+		} );
+
+		this.panels.show();
+
+		if ( this.options.heightStyle !== "content" ) {
+			this.panels.css( "height", "" );
+		}
+	},
+
+	enable: function( index ) {
+		var disabled = this.options.disabled;
+		if ( disabled === false ) {
+			return;
+		}
+
+		if ( index === undefined ) {
+			disabled = false;
+		} else {
+			index = this._getIndex( index );
+			if ( $.isArray( disabled ) ) {
+				disabled = $.map( disabled, function( num ) {
+					return num !== index ? num : null;
+				} );
+			} else {
+				disabled = $.map( this.tabs, function( li, num ) {
+					return num !== index ? num : null;
+				} );
+			}
+		}
+		this._setOptionDisabled( disabled );
+	},
+
+	disable: function( index ) {
+		var disabled = this.options.disabled;
+		if ( disabled === true ) {
+			return;
+		}
+
+		if ( index === undefined ) {
+			disabled = true;
+		} else {
+			index = this._getIndex( index );
+			if ( $.inArray( index, disabled ) !== -1 ) {
+				return;
+			}
+			if ( $.isArray( disabled ) ) {
+				disabled = $.merge( [ index ], disabled ).sort();
+			} else {
+				disabled = [ index ];
+			}
+		}
+		this._setOptionDisabled( disabled );
+	},
+
+	load: function( index, event ) {
+		index = this._getIndex( index );
+		var that = this,
+			tab = this.tabs.eq( index ),
+			anchor = tab.find( ".ui-tabs-anchor" ),
+			panel = this._getPanelForTab( tab ),
+			eventData = {
+				tab: tab,
+				panel: panel
+			},
+			complete = function( jqXHR, status ) {
+				if ( status === "abort" ) {
+					that.panels.stop( false, true );
+				}
+
+				that._removeClass( tab, "ui-tabs-loading" );
+				panel.removeAttr( "aria-busy" );
+
+				if ( jqXHR === that.xhr ) {
+					delete that.xhr;
+				}
+			};
+
+		// Not remote
+		if ( this._isLocal( anchor[ 0 ] ) ) {
+			return;
+		}
+
+		this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) );
+
+		// Support: jQuery <1.8
+		// jQuery <1.8 returns false if the request is canceled in beforeSend,
+		// but as of 1.8, $.ajax() always returns a jqXHR object.
+		if ( this.xhr && this.xhr.statusText !== "canceled" ) {
+			this._addClass( tab, "ui-tabs-loading" );
+			panel.attr( "aria-busy", "true" );
+
+			this.xhr
+				.done( function( response, status, jqXHR ) {
+
+					// support: jQuery <1.8
+					// http://bugs.jquery.com/ticket/11778
+					setTimeout( function() {
+						panel.html( response );
+						that._trigger( "load", event, eventData );
+
+						complete( jqXHR, status );
+					}, 1 );
+				} )
+				.fail( function( jqXHR, status ) {
+
+					// support: jQuery <1.8
+					// http://bugs.jquery.com/ticket/11778
+					setTimeout( function() {
+						complete( jqXHR, status );
+					}, 1 );
+				} );
+		}
+	},
+
+	_ajaxSettings: function( anchor, event, eventData ) {
+		var that = this;
+		return {
+
+			// Support: IE <11 only
+			// Strip any hash that exists to prevent errors with the Ajax request
+			url: anchor.attr( "href" ).replace( /#.*$/, "" ),
+			beforeSend: function( jqXHR, settings ) {
+				return that._trigger( "beforeLoad", event,
+					$.extend( { jqXHR: jqXHR, ajaxSettings: settings }, eventData ) );
+			}
+		};
+	},
+
+	_getPanelForTab: function( tab ) {
+		var id = $( tab ).attr( "aria-controls" );
+		return this.element.find( this._sanitizeSelector( "#" + id ) );
+	}
+} );
+
+// DEPRECATED
+// TODO: Switch return back to widget declaration at top of file when this is removed
+if ( $.uiBackCompat !== false ) {
+
+	// Backcompat for ui-tab class (now ui-tabs-tab)
+	$.widget( "ui.tabs", $.ui.tabs, {
+		_processTabs: function() {
+			this._superApply( arguments );
+			this._addClass( this.tabs, "ui-tab" );
+		}
+	} );
+}
+
+var widgetsTabs = $.ui.tabs;
+
+
+/*!
+ * jQuery UI Tooltip 1.12.1
+ * http://jqueryui.com
+ *
+ * Copyright jQuery Foundation and other contributors
+ * Released under the MIT license.
+ * http://jquery.org/license
+ */
+
+//>>label: Tooltip
+//>>group: Widgets
+//>>description: Shows additional information for any element on hover or focus.
+//>>docs: http://api.jqueryui.com/tooltip/
+//>>demos: http://jqueryui.com/tooltip/
+//>>css.structure: ../../themes/base/core.css
+//>>css.structure: ../../themes/base/tooltip.css
+//>>css.theme: ../../themes/base/theme.css
+
+
+
+$.widget( "ui.tooltip", {
+	version: "1.12.1",
+	options: {
+		classes: {
+			"ui-tooltip": "ui-corner-all ui-widget-shadow"
+		},
+		content: function() {
+
+			// support: IE<9, Opera in jQuery <1.7
+			// .text() can't accept undefined, so coerce to a string
+			var title = $( this ).attr( "title" ) || "";
+
+			// Escape title, since we're going from an attribute to raw HTML
+			return $( "<a>" ).text( title ).html();
+		},
+		hide: true,
+
+		// Disabled elements have inconsistent behavior across browsers (#8661)
+		items: "[title]:not([disabled])",
+		position: {
+			my: "left top+15",
+			at: "left bottom",
+			collision: "flipfit flip"
+		},
+		show: true,
+		track: false,
+
+		// Callbacks
+		close: null,
+		open: null
+	},
+
+	_addDescribedBy: function( elem, id ) {
+		var describedby = ( elem.attr( "aria-describedby" ) || "" ).split( /\s+/ );
+		describedby.push( id );
+		elem
+			.data( "ui-tooltip-id", id )
+			.attr( "aria-describedby", $.trim( describedby.join( " " ) ) );
+	},
+
+	_removeDescribedBy: function( elem ) {
+		var id = elem.data( "ui-tooltip-id" ),
+			describedby = ( elem.attr( "aria-describedby" ) || "" ).split( /\s+/ ),
+			index = $.inArray( id, describedby );
+
+		if ( index !== -1 ) {
+			describedby.splice( index, 1 );
+		}
+
+		elem.removeData( "ui-tooltip-id" );
+		describedby = $.trim( describedby.join( " " ) );
+		if ( describedby ) {
+			elem.attr( "aria-describedby", describedby );
+		} else {
+			elem.removeAttr( "aria-describedby" );
+		}
+	},
+
+	_create: function() {
+		this._on( {
+			mouseover: "open",
+			focusin: "open"
+		} );
+
+		// IDs of generated tooltips, needed for destroy
+		this.tooltips = {};
+
+		// IDs of parent tooltips where we removed the title attribute
+		this.parents = {};
+
+		// Append the aria-live region so tooltips announce correctly
+		this.liveRegion = $( "<div>" )
+			.attr( {
+				role: "log",
+				"aria-live": "assertive",
+				"aria-relevant": "additions"
+			} )
+			.appendTo( this.document[ 0 ].body );
+		this._addClass( this.liveRegion, null, "ui-helper-hidden-accessible" );
+
+		this.disabledTitles = $( [] );
+	},
+
+	_setOption: function( key, value ) {
+		var that = this;
+
+		this._super( key, value );
+
+		if ( key === "content" ) {
+			$.each( this.tooltips, function( id, tooltipData ) {
+				that._updateContent( tooltipData.element );
+			} );
+		}
+	},
+
+	_setOptionDisabled: function( value ) {
+		this[ value ? "_disable" : "_enable" ]();
+	},
+
+	_disable: function() {
+		var that = this;
+
+		// Close open tooltips
+		$.each( this.tooltips, function( id, tooltipData ) {
+			var event = $.Event( "blur" );
+			event.target = event.currentTarget = tooltipData.element[ 0 ];
+			that.close( event, true );
+		} );
+
+		// Remove title attributes to prevent native tooltips
+		this.disabledTitles = this.disabledTitles.add(
+			this.element.find( this.options.items ).addBack()
+				.filter( function() {
+					var element = $( this );
+					if ( element.is( "[title]" ) ) {
+						return element
+							.data( "ui-tooltip-title", element.attr( "title" ) )
+							.removeAttr( "title" );
+					}
+				} )
+		);
+	},
+
+	_enable: function() {
+
+		// restore title attributes
+		this.disabledTitles.each( function() {
+			var element = $( this );
+			if ( element.data( "ui-tooltip-title" ) ) {
+				element.attr( "title", element.data( "ui-tooltip-title" ) );
+			}
+		} );
+		this.disabledTitles = $( [] );
+	},
+
+	open: function( event ) {
+		var that = this,
+			target = $( event ? event.target : this.element )
+
+				// we need closest here due to mouseover bubbling,
+				// but always pointing at the same event target
+				.closest( this.options.items );
+
+		// No element to show a tooltip for or the tooltip is already open
+		if ( !target.length || target.data( "ui-tooltip-id" ) ) {
+			return;
+		}
+
+		if ( target.attr( "title" ) ) {
+			target.data( "ui-tooltip-title", target.attr( "title" ) );
+		}
+
+		target.data( "ui-tooltip-open", true );
+
+		// Kill parent tooltips, custom or native, for hover
+		if ( event && event.type === "mouseover" ) {
+			target.parents().each( function() {
+				var parent = $( this ),
+					blurEvent;
+				if ( parent.data( "ui-tooltip-open" ) ) {
+					blurEvent = $.Event( "blur" );
+					blurEvent.target = blurEvent.currentTarget = this;
+					that.close( blurEvent, true );
+				}
+				if ( parent.attr( "title" ) ) {
+					parent.uniqueId();
+					that.parents[ this.id ] = {
+						element: this,
+						title: parent.attr( "title" )
+					};
+					parent.attr( "title", "" );
+				}
+			} );
+		}
+
+		this._registerCloseHandlers( event, target );
+		this._updateContent( target, event );
+	},
+
+	_updateContent: function( target, event ) {
+		var content,
+			contentOption = this.options.content,
+			that = this,
+			eventType = event ? event.type : null;
+
+		if ( typeof contentOption === "string" || contentOption.nodeType ||
+				contentOption.jquery ) {
+			return this._open( event, target, contentOption );
+		}
+
+		content = contentOption.call( target[ 0 ], function( response ) {
+
+			// IE may instantly serve a cached response for ajax requests
+			// delay this call to _open so the other call to _open runs first
+			that._delay( function() {
+
+				// Ignore async response if tooltip was closed already
+				if ( !target.data( "ui-tooltip-open" ) ) {
+					return;
+				}
+
+				// JQuery creates a special event for focusin when it doesn't
+				// exist natively. To improve performance, the native event
+				// object is reused and the type is changed. Therefore, we can't
+				// rely on the type being correct after the event finished
+				// bubbling, so we set it back to the previous value. (#8740)
+				if ( event ) {
+					event.type = eventType;
+				}
+				this._open( event, target, response );
+			} );
+		} );
+		if ( content ) {
+			this._open( event, target, content );
+		}
+	},
+
+	_open: function( event, target, content ) {
+		var tooltipData, tooltip, delayedShow, a11yContent,
+			positionOption = $.extend( {}, this.options.position );
+
+		if ( !content ) {
+			return;
+		}
+
+		// Content can be updated multiple times. If the tooltip already
+		// exists, then just update the content and bail.
+		tooltipData = this._find( target );
+		if ( tooltipData ) {
+			tooltipData.tooltip.find( ".ui-tooltip-content" ).html( content );
+			return;
+		}
+
+		// If we have a title, clear it to prevent the native tooltip
+		// we have to check first to avoid defining a title if none exists
+		// (we don't want to cause an element to start matching [title])
+		//
+		// We use removeAttr only for key events, to allow IE to export the correct
+		// accessible attributes. For mouse events, set to empty string to avoid
+		// native tooltip showing up (happens only when removing inside mouseover).
+		if ( target.is( "[title]" ) ) {
+			if ( event && event.type === "mouseover" ) {
+				target.attr( "title", "" );
+			} else {
+				target.removeAttr( "title" );
+			}
+		}
+
+		tooltipData = this._tooltip( target );
+		tooltip = tooltipData.tooltip;
+		this._addDescribedBy( target, tooltip.attr( "id" ) );
+		tooltip.find( ".ui-tooltip-content" ).html( content );
+
+		// Support: Voiceover on OS X, JAWS on IE <= 9
+		// JAWS announces deletions even when aria-relevant="additions"
+		// Voiceover will sometimes re-read the entire log region's contents from the beginning
+		this.liveRegion.children().hide();
+		a11yContent = $( "<div>" ).html( tooltip.find( ".ui-tooltip-content" ).html() );
+		a11yContent.removeAttr( "name" ).find( "[name]" ).removeAttr( "name" );
+		a11yContent.removeAttr( "id" ).find( "[id]" ).removeAttr( "id" );
+		a11yContent.appendTo( this.liveRegion );
+
+		function position( event ) {
+			positionOption.of = event;
+			if ( tooltip.is( ":hidden" ) ) {
+				return;
+			}
+			tooltip.position( positionOption );
+		}
+		if ( this.options.track && event && /^mouse/.test( event.type ) ) {
+			this._on( this.document, {
+				mousemove: position
+			} );
+
+			// trigger once to override element-relative positioning
+			position( event );
+		} else {
+			tooltip.position( $.extend( {
+				of: target
+			}, this.options.position ) );
+		}
+
+		tooltip.hide();
+
+		this._show( tooltip, this.options.show );
+
+		// Handle tracking tooltips that are shown with a delay (#8644). As soon
+		// as the tooltip is visible, position the tooltip using the most recent
+		// event.
+		// Adds the check to add the timers only when both delay and track options are set (#14682)
+		if ( this.options.track && this.options.show && this.options.show.delay ) {
+			delayedShow = this.delayedShow = setInterval( function() {
+				if ( tooltip.is( ":visible" ) ) {
+					position( positionOption.of );
+					clearInterval( delayedShow );
+				}
+			}, $.fx.interval );
+		}
+
+		this._trigger( "open", event, { tooltip: tooltip } );
+	},
+
+	_registerCloseHandlers: function( event, target ) {
+		var events = {
+			keyup: function( event ) {
+				if ( event.keyCode === $.ui.keyCode.ESCAPE ) {
+					var fakeEvent = $.Event( event );
+					fakeEvent.currentTarget = target[ 0 ];
+					this.close( fakeEvent, true );
+				}
+			}
+		};
+
+		// Only bind remove handler for delegated targets. Non-delegated
+		// tooltips will handle this in destroy.
+		if ( target[ 0 ] !== this.element[ 0 ] ) {
+			events.remove = function() {
+				this._removeTooltip( this._find( target ).tooltip );
+			};
+		}
+
+		if ( !event || event.type === "mouseover" ) {
+			events.mouseleave = "close";
+		}
+		if ( !event || event.type === "focusin" ) {
+			events.focusout = "close";
+		}
+		this._on( true, target, events );
+	},
+
+	close: function( event ) {
+		var tooltip,
+			that = this,
+			target = $( event ? event.currentTarget : this.element ),
+			tooltipData = this._find( target );
+
+		// The tooltip may already be closed
+		if ( !tooltipData ) {
+
+			// We set ui-tooltip-open immediately upon open (in open()), but only set the
+			// additional data once there's actually content to show (in _open()). So even if the
+			// tooltip doesn't have full data, we always remove ui-tooltip-open in case we're in
+			// the period between open() and _open().
+			target.removeData( "ui-tooltip-open" );
+			return;
+		}
+
+		tooltip = tooltipData.tooltip;
+
+		// Disabling closes the tooltip, so we need to track when we're closing
+		// to avoid an infinite loop in case the tooltip becomes disabled on close
+		if ( tooltipData.closing ) {
+			return;
+		}
+
+		// Clear the interval for delayed tracking tooltips
+		clearInterval( this.delayedShow );
+
+		// Only set title if we had one before (see comment in _open())
+		// If the title attribute has changed since open(), don't restore
+		if ( target.data( "ui-tooltip-title" ) && !target.attr( "title" ) ) {
+			target.attr( "title", target.data( "ui-tooltip-title" ) );
+		}
+
+		this._removeDescribedBy( target );
+
+		tooltipData.hiding = true;
+		tooltip.stop( true );
+		this._hide( tooltip, this.options.hide, function() {
+			that._removeTooltip( $( this ) );
+		} );
+
+		target.removeData( "ui-tooltip-open" );
+		this._off( target, "mouseleave focusout keyup" );
+
+		// Remove 'remove' binding only on delegated targets
+		if ( target[ 0 ] !== this.element[ 0 ] ) {
+			this._off( target, "remove" );
+		}
+		this._off( this.document, "mousemove" );
+
+		if ( event && event.type === "mouseleave" ) {
+			$.each( this.parents, function( id, parent ) {
+				$( parent.element ).attr( "title", parent.title );
+				delete that.parents[ id ];
+			} );
+		}
+
+		tooltipData.closing = true;
+		this._trigger( "close", event, { tooltip: tooltip } );
+		if ( !tooltipData.hiding ) {
+			tooltipData.closing = false;
+		}
+	},
+
+	_tooltip: function( element ) {
+		var tooltip = $( "<div>" ).attr( "role", "tooltip" ),
+			content = $( "<div>" ).appendTo( tooltip ),
+			id = tooltip.uniqueId().attr( "id" );
+
+		this._addClass( content, "ui-tooltip-content" );
+		this._addClass( tooltip, "ui-tooltip", "ui-widget ui-widget-content" );
+
+		tooltip.appendTo( this._appendTo( element ) );
+
+		return this.tooltips[ id ] = {
+			element: element,
+			tooltip: tooltip
+		};
+	},
+
+	_find: function( target ) {
+		var id = target.data( "ui-tooltip-id" );
+		return id ? this.tooltips[ id ] : null;
+	},
+
+	_removeTooltip: function( tooltip ) {
+		tooltip.remove();
+		delete this.tooltips[ tooltip.attr( "id" ) ];
+	},
+
+	_appendTo: function( target ) {
+		var element = target.closest( ".ui-front, dialog" );
+
+		if ( !element.length ) {
+			element = this.document[ 0 ].body;
+		}
+
+		return element;
+	},
+
+	_destroy: function() {
+		var that = this;
+
+		// Close open tooltips
+		$.each( this.tooltips, function( id, tooltipData ) {
+
+			// Delegate to close method to handle common cleanup
+			var event = $.Event( "blur" ),
+				element = tooltipData.element;
+			event.target = event.currentTarget = element[ 0 ];
+			that.close( event, true );
+
+			// Remove immediately; destroying an open tooltip doesn't use the
+			// hide animation
+			$( "#" + id ).remove();
+
+			// Restore the title
+			if ( element.data( "ui-tooltip-title" ) ) {
+
+				// If the title attribute has changed since open(), don't restore
+				if ( !element.attr( "title" ) ) {
+					element.attr( "title", element.data( "ui-tooltip-title" ) );
+				}
+				element.removeData( "ui-tooltip-title" );
+			}
+		} );
+		this.liveRegion.remove();
+	}
+} );
+
+// DEPRECATED
+// TODO: Switch return back to widget declaration at top of file when this is removed
+if ( $.uiBackCompat !== false ) {
+
+	// Backcompat for tooltipClass option
+	$.widget( "ui.tooltip", $.ui.tooltip, {
+		options: {
+			tooltipClass: null
+		},
+		_tooltip: function() {
+			var tooltipData = this._superApply( arguments );
+			if ( this.options.tooltipClass ) {
+				tooltipData.tooltip.addClass( this.options.tooltipClass );
+			}
+			return tooltipData;
+		}
+	} );
+}
+
+var widgetsTooltip = $.ui.tooltip;
+
+
+
+
+}));
\ No newline at end of file
diff --git a/src/legacy/design-studio/js/jquery.dataTables.js b/src/legacy/design-studio/js/jquery.dataTables.js
new file mode 100644
index 0000000000000000000000000000000000000000..ddf1539fb79df3e886af5a3f0c1f9ae69d0a3235
--- /dev/null
+++ b/src/legacy/design-studio/js/jquery.dataTables.js
@@ -0,0 +1,15307 @@
+/*! DataTables 1.10.13
+ * ©2008-2016 SpryMedia Ltd - datatables.net/license
+ */
+
+/**
+ * @summary     DataTables
+ * @description Paginate, search and order HTML tables
+ * @version     1.10.13
+ * @file        jquery.dataTables.js
+ * @author      SpryMedia Ltd
+ * @contact     www.datatables.net
+ * @copyright   Copyright 2008-2016 SpryMedia Ltd.
+ *
+ * This source file is free software, available under the following license:
+ *   MIT license - http://datatables.net/license
+ *
+ * This source file is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the license files for details.
+ *
+ * For details please refer to: http://www.datatables.net
+ */
+
+/*jslint evil: true, undef: true, browser: true */
+/*globals $,require,jQuery,define,_selector_run,_selector_opts,_selector_first,_selector_row_indexes,_ext,_Api,_api_register,_api_registerPlural,_re_new_lines,_re_html,_re_formatted_numeric,_re_escape_regex,_empty,_intVal,_numToDecimal,_isNumber,_isHtml,_htmlNumeric,_pluck,_pluck_order,_range,_stripHtml,_unique,_fnBuildAjax,_fnAjaxUpdate,_fnAjaxParameters,_fnAjaxUpdateDraw,_fnAjaxDataSrc,_fnAddColumn,_fnColumnOptions,_fnAdjustColumnSizing,_fnVisibleToColumnIndex,_fnColumnIndexToVisible,_fnVisbleColumns,_fnGetColumns,_fnColumnTypes,_fnApplyColumnDefs,_fnHungarianMap,_fnCamelToHungarian,_fnLanguageCompat,_fnBrowserDetect,_fnAddData,_fnAddTr,_fnNodeToDataIndex,_fnNodeToColumnIndex,_fnGetCellData,_fnSetCellData,_fnSplitObjNotation,_fnGetObjectDataFn,_fnSetObjectDataFn,_fnGetDataMaster,_fnClearTable,_fnDeleteIndex,_fnInvalidate,_fnGetRowElements,_fnCreateTr,_fnBuildHead,_fnDrawHead,_fnDraw,_fnReDraw,_fnAddOptionsHtml,_fnDetectHeader,_fnGetUniqueThs,_fnFeatureHtmlFilter,_fnFilterComplete,_fnFilterCustom,_fnFilterColumn,_fnFilter,_fnFilterCreateSearch,_fnEscapeRegex,_fnFilterData,_fnFeatureHtmlInfo,_fnUpdateInfo,_fnInfoMacros,_fnInitialise,_fnInitComplete,_fnLengthChange,_fnFeatureHtmlLength,_fnFeatureHtmlPaginate,_fnPageChange,_fnFeatureHtmlProcessing,_fnProcessingDisplay,_fnFeatureHtmlTable,_fnScrollDraw,_fnApplyToChildren,_fnCalculateColumnWidths,_fnThrottle,_fnConvertToWidth,_fnGetWidestNode,_fnGetMaxLenString,_fnStringToCss,_fnSortFlatten,_fnSort,_fnSortAria,_fnSortListener,_fnSortAttachListener,_fnSortingClasses,_fnSortData,_fnSaveState,_fnLoadState,_fnSettingsFromNode,_fnLog,_fnMap,_fnBindAction,_fnCallbackReg,_fnCallbackFire,_fnLengthOverflow,_fnRenderer,_fnDataSource,_fnRowAttributes*/
+
+(function( factory ) {
+	"use strict";
+
+	if ( typeof define === 'function' && define.amd ) {
+		// AMD
+		define( ['jquery'], function ( $ ) {
+			return factory( $, window, document );
+		} );
+	}
+	else if ( typeof exports === 'object' ) {
+		// CommonJS
+		module.exports = function (root, $) {
+			if ( ! root ) {
+				// CommonJS environments without a window global must pass a
+				// root. This will give an error otherwise
+				root = window;
+			}
+
+			if ( ! $ ) {
+				$ = typeof window !== 'undefined' ? // jQuery's factory checks for a global window
+					require('jquery') :
+					require('jquery')( root );
+			}
+
+			return factory( $, root, root.document );
+		};
+	}
+	else {
+		// Browser
+		factory( jQuery, window, document );
+	}
+}
+(function( $, window, document, undefined ) {
+	"use strict";
+
+	/**
+	 * DataTables is a plug-in for the jQuery Javascript library. It is a highly
+	 * flexible tool, based upon the foundations of progressive enhancement,
+	 * which will add advanced interaction controls to any HTML table. For a
+	 * full list of features please refer to
+	 * [DataTables.net](href="http://datatables.net).
+	 *
+	 * Note that the `DataTable` object is not a global variable but is aliased
+	 * to `jQuery.fn.DataTable` and `jQuery.fn.dataTable` through which it may
+	 * be  accessed.
+	 *
+	 *  @class
+	 *  @param {object} [init={}] Configuration object for DataTables. Options
+	 *    are defined by {@link DataTable.defaults}
+	 *  @requires jQuery 1.7+
+	 *
+	 *  @example
+	 *    // Basic initialisation
+	 *    $(document).ready( function {
+	 *      $('#example').dataTable();
+	 *    } );
+	 *
+	 *  @example
+	 *    // Initialisation with configuration options - in this case, disable
+	 *    // pagination and sorting.
+	 *    $(document).ready( function {
+	 *      $('#example').dataTable( {
+	 *        "paginate": false,
+	 *        "sort": false
+	 *      } );
+	 *    } );
+	 */
+	var DataTable = function ( options )
+	{
+		/**
+		 * Perform a jQuery selector action on the table's TR elements (from the tbody) and
+		 * return the resulting jQuery object.
+		 *  @param {string|node|jQuery} sSelector jQuery selector or node collection to act on
+		 *  @param {object} [oOpts] Optional parameters for modifying the rows to be included
+		 *  @param {string} [oOpts.filter=none] Select TR elements that meet the current filter
+		 *    criterion ("applied") or all TR elements (i.e. no filter).
+		 *  @param {string} [oOpts.order=current] Order of the TR elements in the processed array.
+		 *    Can be either 'current', whereby the current sorting of the table is used, or
+		 *    'original' whereby the original order the data was read into the table is used.
+		 *  @param {string} [oOpts.page=all] Limit the selection to the currently displayed page
+		 *    ("current") or not ("all"). If 'current' is given, then order is assumed to be
+		 *    'current' and filter is 'applied', regardless of what they might be given as.
+		 *  @returns {object} jQuery object, filtered by the given selector.
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      var oTable = $('#example').dataTable();
+		 *
+		 *      // Highlight every second row
+		 *      oTable.$('tr:odd').css('backgroundColor', 'blue');
+		 *    } );
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      var oTable = $('#example').dataTable();
+		 *
+		 *      // Filter to rows with 'Webkit' in them, add a background colour and then
+		 *      // remove the filter, thus highlighting the 'Webkit' rows only.
+		 *      oTable.fnFilter('Webkit');
+		 *      oTable.$('tr', {"search": "applied"}).css('backgroundColor', 'blue');
+		 *      oTable.fnFilter('');
+		 *    } );
+		 */
+		this.$ = function ( sSelector, oOpts )
+		{
+			return this.api(true).$( sSelector, oOpts );
+		};
+		
+		
+		/**
+		 * Almost identical to $ in operation, but in this case returns the data for the matched
+		 * rows - as such, the jQuery selector used should match TR row nodes or TD/TH cell nodes
+		 * rather than any descendants, so the data can be obtained for the row/cell. If matching
+		 * rows are found, the data returned is the original data array/object that was used to
+		 * create the row (or a generated array if from a DOM source).
+		 *
+		 * This method is often useful in-combination with $ where both functions are given the
+		 * same parameters and the array indexes will match identically.
+		 *  @param {string|node|jQuery} sSelector jQuery selector or node collection to act on
+		 *  @param {object} [oOpts] Optional parameters for modifying the rows to be included
+		 *  @param {string} [oOpts.filter=none] Select elements that meet the current filter
+		 *    criterion ("applied") or all elements (i.e. no filter).
+		 *  @param {string} [oOpts.order=current] Order of the data in the processed array.
+		 *    Can be either 'current', whereby the current sorting of the table is used, or
+		 *    'original' whereby the original order the data was read into the table is used.
+		 *  @param {string} [oOpts.page=all] Limit the selection to the currently displayed page
+		 *    ("current") or not ("all"). If 'current' is given, then order is assumed to be
+		 *    'current' and filter is 'applied', regardless of what they might be given as.
+		 *  @returns {array} Data for the matched elements. If any elements, as a result of the
+		 *    selector, were not TR, TD or TH elements in the DataTable, they will have a null
+		 *    entry in the array.
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      var oTable = $('#example').dataTable();
+		 *
+		 *      // Get the data from the first row in the table
+		 *      var data = oTable._('tr:first');
+		 *
+		 *      // Do something useful with the data
+		 *      alert( "First cell is: "+data[0] );
+		 *    } );
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      var oTable = $('#example').dataTable();
+		 *
+		 *      // Filter to 'Webkit' and get all data for
+		 *      oTable.fnFilter('Webkit');
+		 *      var data = oTable._('tr', {"search": "applied"});
+		 *
+		 *      // Do something with the data
+		 *      alert( data.length+" rows matched the search" );
+		 *    } );
+		 */
+		this._ = function ( sSelector, oOpts )
+		{
+			return this.api(true).rows( sSelector, oOpts ).data();
+		};
+		
+		
+		/**
+		 * Create a DataTables Api instance, with the currently selected tables for
+		 * the Api's context.
+		 * @param {boolean} [traditional=false] Set the API instance's context to be
+		 *   only the table referred to by the `DataTable.ext.iApiIndex` option, as was
+		 *   used in the API presented by DataTables 1.9- (i.e. the traditional mode),
+		 *   or if all tables captured in the jQuery object should be used.
+		 * @return {DataTables.Api}
+		 */
+		this.api = function ( traditional )
+		{
+			return traditional ?
+				new _Api(
+					_fnSettingsFromNode( this[ _ext.iApiIndex ] )
+				) :
+				new _Api( this );
+		};
+		
+		
+		/**
+		 * Add a single new row or multiple rows of data to the table. Please note
+		 * that this is suitable for client-side processing only - if you are using
+		 * server-side processing (i.e. "bServerSide": true), then to add data, you
+		 * must add it to the data source, i.e. the server-side, through an Ajax call.
+		 *  @param {array|object} data The data to be added to the table. This can be:
+		 *    <ul>
+		 *      <li>1D array of data - add a single row with the data provided</li>
+		 *      <li>2D array of arrays - add multiple rows in a single call</li>
+		 *      <li>object - data object when using <i>mData</i></li>
+		 *      <li>array of objects - multiple data objects when using <i>mData</i></li>
+		 *    </ul>
+		 *  @param {bool} [redraw=true] redraw the table or not
+		 *  @returns {array} An array of integers, representing the list of indexes in
+		 *    <i>aoData</i> ({@link DataTable.models.oSettings}) that have been added to
+		 *    the table.
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    // Global var for counter
+		 *    var giCount = 2;
+		 *
+		 *    $(document).ready(function() {
+		 *      $('#example').dataTable();
+		 *    } );
+		 *
+		 *    function fnClickAddRow() {
+		 *      $('#example').dataTable().fnAddData( [
+		 *        giCount+".1",
+		 *        giCount+".2",
+		 *        giCount+".3",
+		 *        giCount+".4" ]
+		 *      );
+		 *
+		 *      giCount++;
+		 *    }
+		 */
+		this.fnAddData = function( data, redraw )
+		{
+			var api = this.api( true );
+		
+			/* Check if we want to add multiple rows or not */
+			var rows = $.isArray(data) && ( $.isArray(data[0]) || $.isPlainObject(data[0]) ) ?
+				api.rows.add( data ) :
+				api.row.add( data );
+		
+			if ( redraw === undefined || redraw ) {
+				api.draw();
+			}
+		
+			return rows.flatten().toArray();
+		};
+		
+		
+		/**
+		 * This function will make DataTables recalculate the column sizes, based on the data
+		 * contained in the table and the sizes applied to the columns (in the DOM, CSS or
+		 * through the sWidth parameter). This can be useful when the width of the table's
+		 * parent element changes (for example a window resize).
+		 *  @param {boolean} [bRedraw=true] Redraw the table or not, you will typically want to
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      var oTable = $('#example').dataTable( {
+		 *        "sScrollY": "200px",
+		 *        "bPaginate": false
+		 *      } );
+		 *
+		 *      $(window).on('resize', function () {
+		 *        oTable.fnAdjustColumnSizing();
+		 *      } );
+		 *    } );
+		 */
+		this.fnAdjustColumnSizing = function ( bRedraw )
+		{
+			var api = this.api( true ).columns.adjust();
+			var settings = api.settings()[0];
+			var scroll = settings.oScroll;
+		
+			if ( bRedraw === undefined || bRedraw ) {
+				api.draw( false );
+			}
+			else if ( scroll.sX !== "" || scroll.sY !== "" ) {
+				/* If not redrawing, but scrolling, we want to apply the new column sizes anyway */
+				_fnScrollDraw( settings );
+			}
+		};
+		
+		
+		/**
+		 * Quickly and simply clear a table
+		 *  @param {bool} [bRedraw=true] redraw the table or not
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      var oTable = $('#example').dataTable();
+		 *
+		 *      // Immediately 'nuke' the current rows (perhaps waiting for an Ajax callback...)
+		 *      oTable.fnClearTable();
+		 *    } );
+		 */
+		this.fnClearTable = function( bRedraw )
+		{
+			var api = this.api( true ).clear();
+		
+			if ( bRedraw === undefined || bRedraw ) {
+				api.draw();
+			}
+		};
+		
+		
+		/**
+		 * The exact opposite of 'opening' a row, this function will close any rows which
+		 * are currently 'open'.
+		 *  @param {node} nTr the table row to 'close'
+		 *  @returns {int} 0 on success, or 1 if failed (can't find the row)
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      var oTable;
+		 *
+		 *      // 'open' an information row when a row is clicked on
+		 *      $('#example tbody tr').click( function () {
+		 *        if ( oTable.fnIsOpen(this) ) {
+		 *          oTable.fnClose( this );
+		 *        } else {
+		 *          oTable.fnOpen( this, "Temporary row opened", "info_row" );
+		 *        }
+		 *      } );
+		 *
+		 *      oTable = $('#example').dataTable();
+		 *    } );
+		 */
+		this.fnClose = function( nTr )
+		{
+			this.api( true ).row( nTr ).child.hide();
+		};
+		
+		
+		/**
+		 * Remove a row for the table
+		 *  @param {mixed} target The index of the row from aoData to be deleted, or
+		 *    the TR element you want to delete
+		 *  @param {function|null} [callBack] Callback function
+		 *  @param {bool} [redraw=true] Redraw the table or not
+		 *  @returns {array} The row that was deleted
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      var oTable = $('#example').dataTable();
+		 *
+		 *      // Immediately remove the first row
+		 *      oTable.fnDeleteRow( 0 );
+		 *    } );
+		 */
+		this.fnDeleteRow = function( target, callback, redraw )
+		{
+			var api = this.api( true );
+			var rows = api.rows( target );
+			var settings = rows.settings()[0];
+			var data = settings.aoData[ rows[0][0] ];
+		
+			rows.remove();
+		
+			if ( callback ) {
+				callback.call( this, settings, data );
+			}
+		
+			if ( redraw === undefined || redraw ) {
+				api.draw();
+			}
+		
+			return data;
+		};
+		
+		
+		/**
+		 * Restore the table to it's original state in the DOM by removing all of DataTables
+		 * enhancements, alterations to the DOM structure of the table and event listeners.
+		 *  @param {boolean} [remove=false] Completely remove the table from the DOM
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      // This example is fairly pointless in reality, but shows how fnDestroy can be used
+		 *      var oTable = $('#example').dataTable();
+		 *      oTable.fnDestroy();
+		 *    } );
+		 */
+		this.fnDestroy = function ( remove )
+		{
+			this.api( true ).destroy( remove );
+		};
+		
+		
+		/**
+		 * Redraw the table
+		 *  @param {bool} [complete=true] Re-filter and resort (if enabled) the table before the draw.
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      var oTable = $('#example').dataTable();
+		 *
+		 *      // Re-draw the table - you wouldn't want to do it here, but it's an example :-)
+		 *      oTable.fnDraw();
+		 *    } );
+		 */
+		this.fnDraw = function( complete )
+		{
+			// Note that this isn't an exact match to the old call to _fnDraw - it takes
+			// into account the new data, but can hold position.
+			this.api( true ).draw( complete );
+		};
+		
+		
+		/**
+		 * Filter the input based on data
+		 *  @param {string} sInput String to filter the table on
+		 *  @param {int|null} [iColumn] Column to limit filtering to
+		 *  @param {bool} [bRegex=false] Treat as regular expression or not
+		 *  @param {bool} [bSmart=true] Perform smart filtering or not
+		 *  @param {bool} [bShowGlobal=true] Show the input global filter in it's input box(es)
+		 *  @param {bool} [bCaseInsensitive=true] Do case-insensitive matching (true) or not (false)
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      var oTable = $('#example').dataTable();
+		 *
+		 *      // Sometime later - filter...
+		 *      oTable.fnFilter( 'test string' );
+		 *    } );
+		 */
+		this.fnFilter = function( sInput, iColumn, bRegex, bSmart, bShowGlobal, bCaseInsensitive )
+		{
+			var api = this.api( true );
+		
+			if ( iColumn === null || iColumn === undefined ) {
+				api.search( sInput, bRegex, bSmart, bCaseInsensitive );
+			}
+			else {
+				api.column( iColumn ).search( sInput, bRegex, bSmart, bCaseInsensitive );
+			}
+		
+			api.draw();
+		};
+		
+		
+		/**
+		 * Get the data for the whole table, an individual row or an individual cell based on the
+		 * provided parameters.
+		 *  @param {int|node} [src] A TR row node, TD/TH cell node or an integer. If given as
+		 *    a TR node then the data source for the whole row will be returned. If given as a
+		 *    TD/TH cell node then iCol will be automatically calculated and the data for the
+		 *    cell returned. If given as an integer, then this is treated as the aoData internal
+		 *    data index for the row (see fnGetPosition) and the data for that row used.
+		 *  @param {int} [col] Optional column index that you want the data of.
+		 *  @returns {array|object|string} If mRow is undefined, then the data for all rows is
+		 *    returned. If mRow is defined, just data for that row, and is iCol is
+		 *    defined, only data for the designated cell is returned.
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    // Row data
+		 *    $(document).ready(function() {
+		 *      oTable = $('#example').dataTable();
+		 *
+		 *      oTable.$('tr').click( function () {
+		 *        var data = oTable.fnGetData( this );
+		 *        // ... do something with the array / object of data for the row
+		 *      } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Individual cell data
+		 *    $(document).ready(function() {
+		 *      oTable = $('#example').dataTable();
+		 *
+		 *      oTable.$('td').click( function () {
+		 *        var sData = oTable.fnGetData( this );
+		 *        alert( 'The cell clicked on had the value of '+sData );
+		 *      } );
+		 *    } );
+		 */
+		this.fnGetData = function( src, col )
+		{
+			var api = this.api( true );
+		
+			if ( src !== undefined ) {
+				var type = src.nodeName ? src.nodeName.toLowerCase() : '';
+		
+				return col !== undefined || type == 'td' || type == 'th' ?
+					api.cell( src, col ).data() :
+					api.row( src ).data() || null;
+			}
+		
+			return api.data().toArray();
+		};
+		
+		
+		/**
+		 * Get an array of the TR nodes that are used in the table's body. Note that you will
+		 * typically want to use the '$' API method in preference to this as it is more
+		 * flexible.
+		 *  @param {int} [iRow] Optional row index for the TR element you want
+		 *  @returns {array|node} If iRow is undefined, returns an array of all TR elements
+		 *    in the table's body, or iRow is defined, just the TR element requested.
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      var oTable = $('#example').dataTable();
+		 *
+		 *      // Get the nodes from the table
+		 *      var nNodes = oTable.fnGetNodes( );
+		 *    } );
+		 */
+		this.fnGetNodes = function( iRow )
+		{
+			var api = this.api( true );
+		
+			return iRow !== undefined ?
+				api.row( iRow ).node() :
+				api.rows().nodes().flatten().toArray();
+		};
+		
+		
+		/**
+		 * Get the array indexes of a particular cell from it's DOM element
+		 * and column index including hidden columns
+		 *  @param {node} node this can either be a TR, TD or TH in the table's body
+		 *  @returns {int} If nNode is given as a TR, then a single index is returned, or
+		 *    if given as a cell, an array of [row index, column index (visible),
+		 *    column index (all)] is given.
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      $('#example tbody td').click( function () {
+		 *        // Get the position of the current data from the node
+		 *        var aPos = oTable.fnGetPosition( this );
+		 *
+		 *        // Get the data array for this row
+		 *        var aData = oTable.fnGetData( aPos[0] );
+		 *
+		 *        // Update the data array and return the value
+		 *        aData[ aPos[1] ] = 'clicked';
+		 *        this.innerHTML = 'clicked';
+		 *      } );
+		 *
+		 *      // Init DataTables
+		 *      oTable = $('#example').dataTable();
+		 *    } );
+		 */
+		this.fnGetPosition = function( node )
+		{
+			var api = this.api( true );
+			var nodeName = node.nodeName.toUpperCase();
+		
+			if ( nodeName == 'TR' ) {
+				return api.row( node ).index();
+			}
+			else if ( nodeName == 'TD' || nodeName == 'TH' ) {
+				var cell = api.cell( node ).index();
+		
+				return [
+					cell.row,
+					cell.columnVisible,
+					cell.column
+				];
+			}
+			return null;
+		};
+		
+		
+		/**
+		 * Check to see if a row is 'open' or not.
+		 *  @param {node} nTr the table row to check
+		 *  @returns {boolean} true if the row is currently open, false otherwise
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      var oTable;
+		 *
+		 *      // 'open' an information row when a row is clicked on
+		 *      $('#example tbody tr').click( function () {
+		 *        if ( oTable.fnIsOpen(this) ) {
+		 *          oTable.fnClose( this );
+		 *        } else {
+		 *          oTable.fnOpen( this, "Temporary row opened", "info_row" );
+		 *        }
+		 *      } );
+		 *
+		 *      oTable = $('#example').dataTable();
+		 *    } );
+		 */
+		this.fnIsOpen = function( nTr )
+		{
+			return this.api( true ).row( nTr ).child.isShown();
+		};
+		
+		
+		/**
+		 * This function will place a new row directly after a row which is currently
+		 * on display on the page, with the HTML contents that is passed into the
+		 * function. This can be used, for example, to ask for confirmation that a
+		 * particular record should be deleted.
+		 *  @param {node} nTr The table row to 'open'
+		 *  @param {string|node|jQuery} mHtml The HTML to put into the row
+		 *  @param {string} sClass Class to give the new TD cell
+		 *  @returns {node} The row opened. Note that if the table row passed in as the
+		 *    first parameter, is not found in the table, this method will silently
+		 *    return.
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      var oTable;
+		 *
+		 *      // 'open' an information row when a row is clicked on
+		 *      $('#example tbody tr').click( function () {
+		 *        if ( oTable.fnIsOpen(this) ) {
+		 *          oTable.fnClose( this );
+		 *        } else {
+		 *          oTable.fnOpen( this, "Temporary row opened", "info_row" );
+		 *        }
+		 *      } );
+		 *
+		 *      oTable = $('#example').dataTable();
+		 *    } );
+		 */
+		this.fnOpen = function( nTr, mHtml, sClass )
+		{
+			return this.api( true )
+				.row( nTr )
+				.child( mHtml, sClass )
+				.show()
+				.child()[0];
+		};
+		
+		
+		/**
+		 * Change the pagination - provides the internal logic for pagination in a simple API
+		 * function. With this function you can have a DataTables table go to the next,
+		 * previous, first or last pages.
+		 *  @param {string|int} mAction Paging action to take: "first", "previous", "next" or "last"
+		 *    or page number to jump to (integer), note that page 0 is the first page.
+		 *  @param {bool} [bRedraw=true] Redraw the table or not
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      var oTable = $('#example').dataTable();
+		 *      oTable.fnPageChange( 'next' );
+		 *    } );
+		 */
+		this.fnPageChange = function ( mAction, bRedraw )
+		{
+			var api = this.api( true ).page( mAction );
+		
+			if ( bRedraw === undefined || bRedraw ) {
+				api.draw(false);
+			}
+		};
+		
+		
+		/**
+		 * Show a particular column
+		 *  @param {int} iCol The column whose display should be changed
+		 *  @param {bool} bShow Show (true) or hide (false) the column
+		 *  @param {bool} [bRedraw=true] Redraw the table or not
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      var oTable = $('#example').dataTable();
+		 *
+		 *      // Hide the second column after initialisation
+		 *      oTable.fnSetColumnVis( 1, false );
+		 *    } );
+		 */
+		this.fnSetColumnVis = function ( iCol, bShow, bRedraw )
+		{
+			var api = this.api( true ).column( iCol ).visible( bShow );
+		
+			if ( bRedraw === undefined || bRedraw ) {
+				api.columns.adjust().draw();
+			}
+		};
+		
+		
+		/**
+		 * Get the settings for a particular table for external manipulation
+		 *  @returns {object} DataTables settings object. See
+		 *    {@link DataTable.models.oSettings}
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      var oTable = $('#example').dataTable();
+		 *      var oSettings = oTable.fnSettings();
+		 *
+		 *      // Show an example parameter from the settings
+		 *      alert( oSettings._iDisplayStart );
+		 *    } );
+		 */
+		this.fnSettings = function()
+		{
+			return _fnSettingsFromNode( this[_ext.iApiIndex] );
+		};
+		
+		
+		/**
+		 * Sort the table by a particular column
+		 *  @param {int} iCol the data index to sort on. Note that this will not match the
+		 *    'display index' if you have hidden data entries
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      var oTable = $('#example').dataTable();
+		 *
+		 *      // Sort immediately with columns 0 and 1
+		 *      oTable.fnSort( [ [0,'asc'], [1,'asc'] ] );
+		 *    } );
+		 */
+		this.fnSort = function( aaSort )
+		{
+			this.api( true ).order( aaSort ).draw();
+		};
+		
+		
+		/**
+		 * Attach a sort listener to an element for a given column
+		 *  @param {node} nNode the element to attach the sort listener to
+		 *  @param {int} iColumn the column that a click on this node will sort on
+		 *  @param {function} [fnCallback] callback function when sort is run
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      var oTable = $('#example').dataTable();
+		 *
+		 *      // Sort on column 1, when 'sorter' is clicked on
+		 *      oTable.fnSortListener( document.getElementById('sorter'), 1 );
+		 *    } );
+		 */
+		this.fnSortListener = function( nNode, iColumn, fnCallback )
+		{
+			this.api( true ).order.listener( nNode, iColumn, fnCallback );
+		};
+		
+		
+		/**
+		 * Update a table cell or row - this method will accept either a single value to
+		 * update the cell with, an array of values with one element for each column or
+		 * an object in the same format as the original data source. The function is
+		 * self-referencing in order to make the multi column updates easier.
+		 *  @param {object|array|string} mData Data to update the cell/row with
+		 *  @param {node|int} mRow TR element you want to update or the aoData index
+		 *  @param {int} [iColumn] The column to update, give as null or undefined to
+		 *    update a whole row.
+		 *  @param {bool} [bRedraw=true] Redraw the table or not
+		 *  @param {bool} [bAction=true] Perform pre-draw actions or not
+		 *  @returns {int} 0 on success, 1 on error
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      var oTable = $('#example').dataTable();
+		 *      oTable.fnUpdate( 'Example update', 0, 0 ); // Single cell
+		 *      oTable.fnUpdate( ['a', 'b', 'c', 'd', 'e'], $('tbody tr')[0] ); // Row
+		 *    } );
+		 */
+		this.fnUpdate = function( mData, mRow, iColumn, bRedraw, bAction )
+		{
+			var api = this.api( true );
+		
+			if ( iColumn === undefined || iColumn === null ) {
+				api.row( mRow ).data( mData );
+			}
+			else {
+				api.cell( mRow, iColumn ).data( mData );
+			}
+		
+			if ( bAction === undefined || bAction ) {
+				api.columns.adjust();
+			}
+		
+			if ( bRedraw === undefined || bRedraw ) {
+				api.draw();
+			}
+			return 0;
+		};
+		
+		
+		/**
+		 * Provide a common method for plug-ins to check the version of DataTables being used, in order
+		 * to ensure compatibility.
+		 *  @param {string} sVersion Version string to check for, in the format "X.Y.Z". Note that the
+		 *    formats "X" and "X.Y" are also acceptable.
+		 *  @returns {boolean} true if this version of DataTables is greater or equal to the required
+		 *    version, or false if this version of DataTales is not suitable
+		 *  @method
+		 *  @dtopt API
+		 *  @deprecated Since v1.10
+		 *
+		 *  @example
+		 *    $(document).ready(function() {
+		 *      var oTable = $('#example').dataTable();
+		 *      alert( oTable.fnVersionCheck( '1.9.0' ) );
+		 *    } );
+		 */
+		this.fnVersionCheck = _ext.fnVersionCheck;
+		
+
+		var _that = this;
+		var emptyInit = options === undefined;
+		var len = this.length;
+
+		if ( emptyInit ) {
+			options = {};
+		}
+
+		this.oApi = this.internal = _ext.internal;
+
+		// Extend with old style plug-in API methods
+		for ( var fn in DataTable.ext.internal ) {
+			if ( fn ) {
+				this[fn] = _fnExternApiFunc(fn);
+			}
+		}
+
+		this.each(function() {
+			// For each initialisation we want to give it a clean initialisation
+			// object that can be bashed around
+			var o = {};
+			var oInit = len > 1 ? // optimisation for single table case
+				_fnExtend( o, options, true ) :
+				options;
+
+			/*global oInit,_that,emptyInit*/
+			var i=0, iLen, j, jLen, k, kLen;
+			var sId = this.getAttribute( 'id' );
+			var bInitHandedOff = false;
+			var defaults = DataTable.defaults;
+			var $this = $(this);
+			
+			
+			/* Sanity check */
+			if ( this.nodeName.toLowerCase() != 'table' )
+			{
+				_fnLog( null, 0, 'Non-table node initialisation ('+this.nodeName+')', 2 );
+				return;
+			}
+			
+			/* Backwards compatibility for the defaults */
+			_fnCompatOpts( defaults );
+			_fnCompatCols( defaults.column );
+			
+			/* Convert the camel-case defaults to Hungarian */
+			_fnCamelToHungarian( defaults, defaults, true );
+			_fnCamelToHungarian( defaults.column, defaults.column, true );
+			
+			/* Setting up the initialisation object */
+			_fnCamelToHungarian( defaults, $.extend( oInit, $this.data() ) );
+			
+			
+			
+			/* Check to see if we are re-initialising a table */
+			var allSettings = DataTable.settings;
+			for ( i=0, iLen=allSettings.length ; i<iLen ; i++ )
+			{
+				var s = allSettings[i];
+			
+				/* Base check on table node */
+				if ( s.nTable == this || s.nTHead.parentNode == this || (s.nTFoot && s.nTFoot.parentNode == this) )
+				{
+					var bRetrieve = oInit.bRetrieve !== undefined ? oInit.bRetrieve : defaults.bRetrieve;
+					var bDestroy = oInit.bDestroy !== undefined ? oInit.bDestroy : defaults.bDestroy;
+			
+					if ( emptyInit || bRetrieve )
+					{
+						return s.oInstance;
+					}
+					else if ( bDestroy )
+					{
+						s.oInstance.fnDestroy();
+						break;
+					}
+					else
+					{
+						_fnLog( s, 0, 'Cannot reinitialise DataTable', 3 );
+						return;
+					}
+				}
+			
+				/* If the element we are initialising has the same ID as a table which was previously
+				 * initialised, but the table nodes don't match (from before) then we destroy the old
+				 * instance by simply deleting it. This is under the assumption that the table has been
+				 * destroyed by other methods. Anyone using non-id selectors will need to do this manually
+				 */
+				if ( s.sTableId == this.id )
+				{
+					allSettings.splice( i, 1 );
+					break;
+				}
+			}
+			
+			/* Ensure the table has an ID - required for accessibility */
+			if ( sId === null || sId === "" )
+			{
+				sId = "DataTables_Table_"+(DataTable.ext._unique++);
+				this.id = sId;
+			}
+			
+			/* Create the settings object for this table and set some of the default parameters */
+			var oSettings = $.extend( true, {}, DataTable.models.oSettings, {
+				"sDestroyWidth": $this[0].style.width,
+				"sInstance":     sId,
+				"sTableId":      sId
+			} );
+			oSettings.nTable = this;
+			oSettings.oApi   = _that.internal;
+			oSettings.oInit  = oInit;
+			
+			allSettings.push( oSettings );
+			
+			// Need to add the instance after the instance after the settings object has been added
+			// to the settings array, so we can self reference the table instance if more than one
+			oSettings.oInstance = (_that.length===1) ? _that : $this.dataTable();
+			
+			// Backwards compatibility, before we apply all the defaults
+			_fnCompatOpts( oInit );
+			
+			if ( oInit.oLanguage )
+			{
+				_fnLanguageCompat( oInit.oLanguage );
+			}
+			
+			// If the length menu is given, but the init display length is not, use the length menu
+			if ( oInit.aLengthMenu && ! oInit.iDisplayLength )
+			{
+				oInit.iDisplayLength = $.isArray( oInit.aLengthMenu[0] ) ?
+					oInit.aLengthMenu[0][0] : oInit.aLengthMenu[0];
+			}
+			
+			// Apply the defaults and init options to make a single init object will all
+			// options defined from defaults and instance options.
+			oInit = _fnExtend( $.extend( true, {}, defaults ), oInit );
+			
+			
+			// Map the initialisation options onto the settings object
+			_fnMap( oSettings.oFeatures, oInit, [
+				"bPaginate",
+				"bLengthChange",
+				"bFilter",
+				"bSort",
+				"bSortMulti",
+				"bInfo",
+				"bProcessing",
+				"bAutoWidth",
+				"bSortClasses",
+				"bServerSide",
+				"bDeferRender"
+			] );
+			_fnMap( oSettings, oInit, [
+				"asStripeClasses",
+				"ajax",
+				"fnServerData",
+				"fnFormatNumber",
+				"sServerMethod",
+				"aaSorting",
+				"aaSortingFixed",
+				"aLengthMenu",
+				"sPaginationType",
+				"sAjaxSource",
+				"sAjaxDataProp",
+				"iStateDuration",
+				"sDom",
+				"bSortCellsTop",
+				"iTabIndex",
+				"fnStateLoadCallback",
+				"fnStateSaveCallback",
+				"renderer",
+				"searchDelay",
+				"rowId",
+				[ "iCookieDuration", "iStateDuration" ], // backwards compat
+				[ "oSearch", "oPreviousSearch" ],
+				[ "aoSearchCols", "aoPreSearchCols" ],
+				[ "iDisplayLength", "_iDisplayLength" ],
+				[ "bJQueryUI", "bJUI" ]
+			] );
+			_fnMap( oSettings.oScroll, oInit, [
+				[ "sScrollX", "sX" ],
+				[ "sScrollXInner", "sXInner" ],
+				[ "sScrollY", "sY" ],
+				[ "bScrollCollapse", "bCollapse" ]
+			] );
+			_fnMap( oSettings.oLanguage, oInit, "fnInfoCallback" );
+			
+			/* Callback functions which are array driven */
+			_fnCallbackReg( oSettings, 'aoDrawCallback',       oInit.fnDrawCallback,      'user' );
+			_fnCallbackReg( oSettings, 'aoServerParams',       oInit.fnServerParams,      'user' );
+			_fnCallbackReg( oSettings, 'aoStateSaveParams',    oInit.fnStateSaveParams,   'user' );
+			_fnCallbackReg( oSettings, 'aoStateLoadParams',    oInit.fnStateLoadParams,   'user' );
+			_fnCallbackReg( oSettings, 'aoStateLoaded',        oInit.fnStateLoaded,       'user' );
+			_fnCallbackReg( oSettings, 'aoRowCallback',        oInit.fnRowCallback,       'user' );
+			_fnCallbackReg( oSettings, 'aoRowCreatedCallback', oInit.fnCreatedRow,        'user' );
+			_fnCallbackReg( oSettings, 'aoHeaderCallback',     oInit.fnHeaderCallback,    'user' );
+			_fnCallbackReg( oSettings, 'aoFooterCallback',     oInit.fnFooterCallback,    'user' );
+			_fnCallbackReg( oSettings, 'aoInitComplete',       oInit.fnInitComplete,      'user' );
+			_fnCallbackReg( oSettings, 'aoPreDrawCallback',    oInit.fnPreDrawCallback,   'user' );
+			
+			oSettings.rowIdFn = _fnGetObjectDataFn( oInit.rowId );
+			
+			/* Browser support detection */
+			_fnBrowserDetect( oSettings );
+			
+			var oClasses = oSettings.oClasses;
+			
+			// @todo Remove in 1.11
+			if ( oInit.bJQueryUI )
+			{
+				/* Use the JUI classes object for display. You could clone the oStdClasses object if
+				 * you want to have multiple tables with multiple independent classes
+				 */
+				$.extend( oClasses, DataTable.ext.oJUIClasses, oInit.oClasses );
+			
+				if ( oInit.sDom === defaults.sDom && defaults.sDom === "lfrtip" )
+				{
+					/* Set the DOM to use a layout suitable for jQuery UI's theming */
+					oSettings.sDom = '<"H"lfr>t<"F"ip>';
+				}
+			
+				if ( ! oSettings.renderer ) {
+					oSettings.renderer = 'jqueryui';
+				}
+				else if ( $.isPlainObject( oSettings.renderer ) && ! oSettings.renderer.header ) {
+					oSettings.renderer.header = 'jqueryui';
+				}
+			}
+			else
+			{
+				$.extend( oClasses, DataTable.ext.classes, oInit.oClasses );
+			}
+			$this.addClass( oClasses.sTable );
+			
+			
+			if ( oSettings.iInitDisplayStart === undefined )
+			{
+				/* Display start point, taking into account the save saving */
+				oSettings.iInitDisplayStart = oInit.iDisplayStart;
+				oSettings._iDisplayStart = oInit.iDisplayStart;
+			}
+			
+			if ( oInit.iDeferLoading !== null )
+			{
+				oSettings.bDeferLoading = true;
+				var tmp = $.isArray( oInit.iDeferLoading );
+				oSettings._iRecordsDisplay = tmp ? oInit.iDeferLoading[0] : oInit.iDeferLoading;
+				oSettings._iRecordsTotal = tmp ? oInit.iDeferLoading[1] : oInit.iDeferLoading;
+			}
+			
+			/* Language definitions */
+			var oLanguage = oSettings.oLanguage;
+			$.extend( true, oLanguage, oInit.oLanguage );
+			
+			if ( oLanguage.sUrl )
+			{
+				/* Get the language definitions from a file - because this Ajax call makes the language
+				 * get async to the remainder of this function we use bInitHandedOff to indicate that
+				 * _fnInitialise will be fired by the returned Ajax handler, rather than the constructor
+				 */
+				$.ajax( {
+					dataType: 'json',
+					url: oLanguage.sUrl,
+					success: function ( json ) {
+						_fnLanguageCompat( json );
+						_fnCamelToHungarian( defaults.oLanguage, json );
+						$.extend( true, oLanguage, json );
+						_fnInitialise( oSettings );
+					},
+					error: function () {
+						// Error occurred loading language file, continue on as best we can
+						_fnInitialise( oSettings );
+					}
+				} );
+				bInitHandedOff = true;
+			}
+			
+			/*
+			 * Stripes
+			 */
+			if ( oInit.asStripeClasses === null )
+			{
+				oSettings.asStripeClasses =[
+					oClasses.sStripeOdd,
+					oClasses.sStripeEven
+				];
+			}
+			
+			/* Remove row stripe classes if they are already on the table row */
+			var stripeClasses = oSettings.asStripeClasses;
+			var rowOne = $this.children('tbody').find('tr').eq(0);
+			if ( $.inArray( true, $.map( stripeClasses, function(el, i) {
+				return rowOne.hasClass(el);
+			} ) ) !== -1 ) {
+				$('tbody tr', this).removeClass( stripeClasses.join(' ') );
+				oSettings.asDestroyStripes = stripeClasses.slice();
+			}
+			
+			/*
+			 * Columns
+			 * See if we should load columns automatically or use defined ones
+			 */
+			var anThs = [];
+			var aoColumnsInit;
+			var nThead = this.getElementsByTagName('thead');
+			if ( nThead.length !== 0 )
+			{
+				_fnDetectHeader( oSettings.aoHeader, nThead[0] );
+				anThs = _fnGetUniqueThs( oSettings );
+			}
+			
+			/* If not given a column array, generate one with nulls */
+			if ( oInit.aoColumns === null )
+			{
+				aoColumnsInit = [];
+				for ( i=0, iLen=anThs.length ; i<iLen ; i++ )
+				{
+					aoColumnsInit.push( null );
+				}
+			}
+			else
+			{
+				aoColumnsInit = oInit.aoColumns;
+			}
+			
+			/* Add the columns */
+			for ( i=0, iLen=aoColumnsInit.length ; i<iLen ; i++ )
+			{
+				_fnAddColumn( oSettings, anThs ? anThs[i] : null );
+			}
+			
+			/* Apply the column definitions */
+			_fnApplyColumnDefs( oSettings, oInit.aoColumnDefs, aoColumnsInit, function (iCol, oDef) {
+				_fnColumnOptions( oSettings, iCol, oDef );
+			} );
+			
+			/* HTML5 attribute detection - build an mData object automatically if the
+			 * attributes are found
+			 */
+			if ( rowOne.length ) {
+				var a = function ( cell, name ) {
+					return cell.getAttribute( 'data-'+name ) !== null ? name : null;
+				};
+			
+				$( rowOne[0] ).children('th, td').each( function (i, cell) {
+					var col = oSettings.aoColumns[i];
+			
+					if ( col.mData === i ) {
+						var sort = a( cell, 'sort' ) || a( cell, 'order' );
+						var filter = a( cell, 'filter' ) || a( cell, 'search' );
+			
+						if ( sort !== null || filter !== null ) {
+							col.mData = {
+								_:      i+'.display',
+								sort:   sort !== null   ? i+'.@data-'+sort   : undefined,
+								type:   sort !== null   ? i+'.@data-'+sort   : undefined,
+								filter: filter !== null ? i+'.@data-'+filter : undefined
+							};
+			
+							_fnColumnOptions( oSettings, i );
+						}
+					}
+				} );
+			}
+			
+			var features = oSettings.oFeatures;
+			var loadedInit = function () {
+				/*
+				 * Sorting
+				 * @todo For modularisation (1.11) this needs to do into a sort start up handler
+				 */
+			
+				// If aaSorting is not defined, then we use the first indicator in asSorting
+				// in case that has been altered, so the default sort reflects that option
+				if ( oInit.aaSorting === undefined ) {
+					var sorting = oSettings.aaSorting;
+					for ( i=0, iLen=sorting.length ; i<iLen ; i++ ) {
+						sorting[i][1] = oSettings.aoColumns[ i ].asSorting[0];
+					}
+				}
+			
+				/* Do a first pass on the sorting classes (allows any size changes to be taken into
+				 * account, and also will apply sorting disabled classes if disabled
+				 */
+				_fnSortingClasses( oSettings );
+			
+				if ( features.bSort ) {
+					_fnCallbackReg( oSettings, 'aoDrawCallback', function () {
+						if ( oSettings.bSorted ) {
+							var aSort = _fnSortFlatten( oSettings );
+							var sortedColumns = {};
+			
+							$.each( aSort, function (i, val) {
+								sortedColumns[ val.src ] = val.dir;
+							} );
+			
+							_fnCallbackFire( oSettings, null, 'order', [oSettings, aSort, sortedColumns] );
+							_fnSortAria( oSettings );
+						}
+					} );
+				}
+			
+				_fnCallbackReg( oSettings, 'aoDrawCallback', function () {
+					if ( oSettings.bSorted || _fnDataSource( oSettings ) === 'ssp' || features.bDeferRender ) {
+						_fnSortingClasses( oSettings );
+					}
+				}, 'sc' );
+			
+			
+				/*
+				 * Final init
+				 * Cache the header, body and footer as required, creating them if needed
+				 */
+			
+				// Work around for Webkit bug 83867 - store the caption-side before removing from doc
+				var captions = $this.children('caption').each( function () {
+					this._captionSide = $(this).css('caption-side');
+				} );
+			
+				var thead = $this.children('thead');
+				if ( thead.length === 0 ) {
+					thead = $('<thead/>').appendTo($this);
+				}
+				oSettings.nTHead = thead[0];
+			
+				var tbody = $this.children('tbody');
+				if ( tbody.length === 0 ) {
+					tbody = $('<tbody/>').appendTo($this);
+				}
+				oSettings.nTBody = tbody[0];
+			
+				var tfoot = $this.children('tfoot');
+				if ( tfoot.length === 0 && captions.length > 0 && (oSettings.oScroll.sX !== "" || oSettings.oScroll.sY !== "") ) {
+					// If we are a scrolling table, and no footer has been given, then we need to create
+					// a tfoot element for the caption element to be appended to
+					tfoot = $('<tfoot/>').appendTo($this);
+				}
+			
+				if ( tfoot.length === 0 || tfoot.children().length === 0 ) {
+					$this.addClass( oClasses.sNoFooter );
+				}
+				else if ( tfoot.length > 0 ) {
+					oSettings.nTFoot = tfoot[0];
+					_fnDetectHeader( oSettings.aoFooter, oSettings.nTFoot );
+				}
+			
+				/* Check if there is data passing into the constructor */
+				if ( oInit.aaData ) {
+					for ( i=0 ; i<oInit.aaData.length ; i++ ) {
+						_fnAddData( oSettings, oInit.aaData[ i ] );
+					}
+				}
+				else if ( oSettings.bDeferLoading || _fnDataSource( oSettings ) == 'dom' ) {
+					/* Grab the data from the page - only do this when deferred loading or no Ajax
+					 * source since there is no point in reading the DOM data if we are then going
+					 * to replace it with Ajax data
+					 */
+					_fnAddTr( oSettings, $(oSettings.nTBody).children('tr') );
+				}
+			
+				/* Copy the data index array */
+				oSettings.aiDisplay = oSettings.aiDisplayMaster.slice();
+			
+				/* Initialisation complete - table can be drawn */
+				oSettings.bInitialised = true;
+			
+				/* Check if we need to initialise the table (it might not have been handed off to the
+				 * language processor)
+				 */
+				if ( bInitHandedOff === false ) {
+					_fnInitialise( oSettings );
+				}
+			};
+			
+			/* Must be done after everything which can be overridden by the state saving! */
+			if ( oInit.bStateSave )
+			{
+				features.bStateSave = true;
+				_fnCallbackReg( oSettings, 'aoDrawCallback', _fnSaveState, 'state_save' );
+				_fnLoadState( oSettings, oInit, loadedInit );
+			}
+			else {
+				loadedInit();
+			}
+			
+		} );
+		_that = null;
+		return this;
+	};
+
+	
+	/*
+	 * It is useful to have variables which are scoped locally so only the
+	 * DataTables functions can access them and they don't leak into global space.
+	 * At the same time these functions are often useful over multiple files in the
+	 * core and API, so we list, or at least document, all variables which are used
+	 * by DataTables as private variables here. This also ensures that there is no
+	 * clashing of variable names and that they can easily referenced for reuse.
+	 */
+	
+	
+	// Defined else where
+	//  _selector_run
+	//  _selector_opts
+	//  _selector_first
+	//  _selector_row_indexes
+	
+	var _ext; // DataTable.ext
+	var _Api; // DataTable.Api
+	var _api_register; // DataTable.Api.register
+	var _api_registerPlural; // DataTable.Api.registerPlural
+	
+	var _re_dic = {};
+	var _re_new_lines = /[\r\n]/g;
+	var _re_html = /<.*?>/g;
+	
+	// This is not strict ISO8601 - Date.parse() is quite lax, although
+	// implementations differ between browsers.
+	var _re_date = /^\d{2,4}[\.\/\-]\d{1,2}[\.\/\-]\d{1,2}([T ]{1}\d{1,2}[:\.]\d{2}([\.:]\d{2})?)?$/;
+	
+	// Escape regular expression special characters
+	var _re_escape_regex = new RegExp( '(\\' + [ '/', '.', '*', '+', '?', '|', '(', ')', '[', ']', '{', '}', '\\', '$', '^', '-' ].join('|\\') + ')', 'g' );
+	
+	// http://en.wikipedia.org/wiki/Foreign_exchange_market
+	// - \u20BD - Russian ruble.
+	// - \u20a9 - South Korean Won
+	// - \u20BA - Turkish Lira
+	// - \u20B9 - Indian Rupee
+	// - R - Brazil (R$) and South Africa
+	// - fr - Swiss Franc
+	// - kr - Swedish krona, Norwegian krone and Danish krone
+	// - \u2009 is thin space and \u202F is narrow no-break space, both used in many
+	//   standards as thousands separators.
+	var _re_formatted_numeric = /[',$£€¥%\u2009\u202F\u20BD\u20a9\u20BArfk]/gi;
+	
+	
+	var _empty = function ( d ) {
+		return !d || d === true || d === '-' ? true : false;
+	};
+	
+	
+	var _intVal = function ( s ) {
+		var integer = parseInt( s, 10 );
+		return !isNaN(integer) && isFinite(s) ? integer : null;
+	};
+	
+	// Convert from a formatted number with characters other than `.` as the
+	// decimal place, to a Javascript number
+	var _numToDecimal = function ( num, decimalPoint ) {
+		// Cache created regular expressions for speed as this function is called often
+		if ( ! _re_dic[ decimalPoint ] ) {
+			_re_dic[ decimalPoint ] = new RegExp( _fnEscapeRegex( decimalPoint ), 'g' );
+		}
+		return typeof num === 'string' && decimalPoint !== '.' ?
+			num.replace( /\./g, '' ).replace( _re_dic[ decimalPoint ], '.' ) :
+			num;
+	};
+	
+	
+	var _isNumber = function ( d, decimalPoint, formatted ) {
+		var strType = typeof d === 'string';
+	
+		// If empty return immediately so there must be a number if it is a
+		// formatted string (this stops the string "k", or "kr", etc being detected
+		// as a formatted number for currency
+		if ( _empty( d ) ) {
+			return true;
+		}
+	
+		if ( decimalPoint && strType ) {
+			d = _numToDecimal( d, decimalPoint );
+		}
+	
+		if ( formatted && strType ) {
+			d = d.replace( _re_formatted_numeric, '' );
+		}
+	
+		return !isNaN( parseFloat(d) ) && isFinite( d );
+	};
+	
+	
+	// A string without HTML in it can be considered to be HTML still
+	var _isHtml = function ( d ) {
+		return _empty( d ) || typeof d === 'string';
+	};
+	
+	
+	var _htmlNumeric = function ( d, decimalPoint, formatted ) {
+		if ( _empty( d ) ) {
+			return true;
+		}
+	
+		var html = _isHtml( d );
+		return ! html ?
+			null :
+			_isNumber( _stripHtml( d ), decimalPoint, formatted ) ?
+				true :
+				null;
+	};
+	
+	
+	var _pluck = function ( a, prop, prop2 ) {
+		var out = [];
+		var i=0, ien=a.length;
+	
+		// Could have the test in the loop for slightly smaller code, but speed
+		// is essential here
+		if ( prop2 !== undefined ) {
+			for ( ; i<ien ; i++ ) {
+				if ( a[i] && a[i][ prop ] ) {
+					out.push( a[i][ prop ][ prop2 ] );
+				}
+			}
+		}
+		else {
+			for ( ; i<ien ; i++ ) {
+				if ( a[i] ) {
+					out.push( a[i][ prop ] );
+				}
+			}
+		}
+	
+		return out;
+	};
+	
+	
+	// Basically the same as _pluck, but rather than looping over `a` we use `order`
+	// as the indexes to pick from `a`
+	var _pluck_order = function ( a, order, prop, prop2 )
+	{
+		var out = [];
+		var i=0, ien=order.length;
+	
+		// Could have the test in the loop for slightly smaller code, but speed
+		// is essential here
+		if ( prop2 !== undefined ) {
+			for ( ; i<ien ; i++ ) {
+				if ( a[ order[i] ][ prop ] ) {
+					out.push( a[ order[i] ][ prop ][ prop2 ] );
+				}
+			}
+		}
+		else {
+			for ( ; i<ien ; i++ ) {
+				out.push( a[ order[i] ][ prop ] );
+			}
+		}
+	
+		return out;
+	};
+	
+	
+	var _range = function ( len, start )
+	{
+		var out = [];
+		var end;
+	
+		if ( start === undefined ) {
+			start = 0;
+			end = len;
+		}
+		else {
+			end = start;
+			start = len;
+		}
+	
+		for ( var i=start ; i<end ; i++ ) {
+			out.push( i );
+		}
+	
+		return out;
+	};
+	
+	
+	var _removeEmpty = function ( a )
+	{
+		var out = [];
+	
+		for ( var i=0, ien=a.length ; i<ien ; i++ ) {
+			if ( a[i] ) { // careful - will remove all falsy values!
+				out.push( a[i] );
+			}
+		}
+	
+		return out;
+	};
+	
+	
+	var _stripHtml = function ( d ) {
+		return d.replace( _re_html, '' );
+	};
+	
+	
+	/**
+	 * Find the unique elements in a source array.
+	 *
+	 * @param  {array} src Source array
+	 * @return {array} Array of unique items
+	 * @ignore
+	 */
+	var _unique = function ( src )
+	{
+		// A faster unique method is to use object keys to identify used values,
+		// but this doesn't work with arrays or objects, which we must also
+		// consider. See jsperf.com/compare-array-unique-versions/4 for more
+		// information.
+		var
+			out = [],
+			val,
+			i, ien=src.length,
+			j, k=0;
+	
+		again: for ( i=0 ; i<ien ; i++ ) {
+			val = src[i];
+	
+			for ( j=0 ; j<k ; j++ ) {
+				if ( out[j] === val ) {
+					continue again;
+				}
+			}
+	
+			out.push( val );
+			k++;
+		}
+	
+		return out;
+	};
+	
+	
+	/**
+	 * DataTables utility methods
+	 * 
+	 * This namespace provides helper methods that DataTables uses internally to
+	 * create a DataTable, but which are not exclusively used only for DataTables.
+	 * These methods can be used by extension authors to save the duplication of
+	 * code.
+	 *
+	 *  @namespace
+	 */
+	DataTable.util = {
+		/**
+		 * Throttle the calls to a function. Arguments and context are maintained
+		 * for the throttled function.
+		 *
+		 * @param {function} fn Function to be called
+		 * @param {integer} freq Call frequency in mS
+		 * @return {function} Wrapped function
+		 */
+		throttle: function ( fn, freq ) {
+			var
+				frequency = freq !== undefined ? freq : 200,
+				last,
+				timer;
+	
+			return function () {
+				var
+					that = this,
+					now  = +new Date(),
+					args = arguments;
+	
+				if ( last && now < last + frequency ) {
+					clearTimeout( timer );
+	
+					timer = setTimeout( function () {
+						last = undefined;
+						fn.apply( that, args );
+					}, frequency );
+				}
+				else {
+					last = now;
+					fn.apply( that, args );
+				}
+			};
+		},
+	
+	
+		/**
+		 * Escape a string such that it can be used in a regular expression
+		 *
+		 *  @param {string} val string to escape
+		 *  @returns {string} escaped string
+		 */
+		escapeRegex: function ( val ) {
+			return val.replace( _re_escape_regex, '\\$1' );
+		}
+	};
+	
+	
+	
+	/**
+	 * Create a mapping object that allows camel case parameters to be looked up
+	 * for their Hungarian counterparts. The mapping is stored in a private
+	 * parameter called `_hungarianMap` which can be accessed on the source object.
+	 *  @param {object} o
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnHungarianMap ( o )
+	{
+		var
+			hungarian = 'a aa ai ao as b fn i m o s ',
+			match,
+			newKey,
+			map = {};
+	
+		$.each( o, function (key, val) {
+			match = key.match(/^([^A-Z]+?)([A-Z])/);
+	
+			if ( match && hungarian.indexOf(match[1]+' ') !== -1 )
+			{
+				newKey = key.replace( match[0], match[2].toLowerCase() );
+				map[ newKey ] = key;
+	
+				if ( match[1] === 'o' )
+				{
+					_fnHungarianMap( o[key] );
+				}
+			}
+		} );
+	
+		o._hungarianMap = map;
+	}
+	
+	
+	/**
+	 * Convert from camel case parameters to Hungarian, based on a Hungarian map
+	 * created by _fnHungarianMap.
+	 *  @param {object} src The model object which holds all parameters that can be
+	 *    mapped.
+	 *  @param {object} user The object to convert from camel case to Hungarian.
+	 *  @param {boolean} force When set to `true`, properties which already have a
+	 *    Hungarian value in the `user` object will be overwritten. Otherwise they
+	 *    won't be.
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnCamelToHungarian ( src, user, force )
+	{
+		if ( ! src._hungarianMap ) {
+			_fnHungarianMap( src );
+		}
+	
+		var hungarianKey;
+	
+		$.each( user, function (key, val) {
+			hungarianKey = src._hungarianMap[ key ];
+	
+			if ( hungarianKey !== undefined && (force || user[hungarianKey] === undefined) )
+			{
+				// For objects, we need to buzz down into the object to copy parameters
+				if ( hungarianKey.charAt(0) === 'o' )
+				{
+					// Copy the camelCase options over to the hungarian
+					if ( ! user[ hungarianKey ] ) {
+						user[ hungarianKey ] = {};
+					}
+					$.extend( true, user[hungarianKey], user[key] );
+	
+					_fnCamelToHungarian( src[hungarianKey], user[hungarianKey], force );
+				}
+				else {
+					user[hungarianKey] = user[ key ];
+				}
+			}
+		} );
+	}
+	
+	
+	/**
+	 * Language compatibility - when certain options are given, and others aren't, we
+	 * need to duplicate the values over, in order to provide backwards compatibility
+	 * with older language files.
+	 *  @param {object} oSettings dataTables settings object
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnLanguageCompat( lang )
+	{
+		var defaults = DataTable.defaults.oLanguage;
+		var zeroRecords = lang.sZeroRecords;
+	
+		/* Backwards compatibility - if there is no sEmptyTable given, then use the same as
+		 * sZeroRecords - assuming that is given.
+		 */
+		if ( ! lang.sEmptyTable && zeroRecords &&
+			defaults.sEmptyTable === "No Results Found" )
+		{
+			_fnMap( lang, lang, 'sZeroRecords', 'sEmptyTable' );
+		}
+	
+		/* Likewise with loading records */
+		if ( ! lang.sLoadingRecords && zeroRecords &&
+			defaults.sLoadingRecords === "Loading..." )
+		{
+			_fnMap( lang, lang, 'sZeroRecords', 'sLoadingRecords' );
+		}
+	
+		// Old parameter name of the thousands separator mapped onto the new
+		if ( lang.sInfoThousands ) {
+			lang.sThousands = lang.sInfoThousands;
+		}
+	
+		var decimal = lang.sDecimal;
+		if ( decimal ) {
+			_addNumericSort( decimal );
+		}
+	}
+	
+	
+	/**
+	 * Map one parameter onto another
+	 *  @param {object} o Object to map
+	 *  @param {*} knew The new parameter name
+	 *  @param {*} old The old parameter name
+	 */
+	var _fnCompatMap = function ( o, knew, old ) {
+		if ( o[ knew ] !== undefined ) {
+			o[ old ] = o[ knew ];
+		}
+	};
+	
+	
+	/**
+	 * Provide backwards compatibility for the main DT options. Note that the new
+	 * options are mapped onto the old parameters, so this is an external interface
+	 * change only.
+	 *  @param {object} init Object to map
+	 */
+	function _fnCompatOpts ( init )
+	{
+		_fnCompatMap( init, 'ordering',      'bSort' );
+		_fnCompatMap( init, 'orderMulti',    'bSortMulti' );
+		_fnCompatMap( init, 'orderClasses',  'bSortClasses' );
+		_fnCompatMap( init, 'orderCellsTop', 'bSortCellsTop' );
+		_fnCompatMap( init, 'order',         'aaSorting' );
+		_fnCompatMap( init, 'orderFixed',    'aaSortingFixed' );
+		_fnCompatMap( init, 'paging',        'bPaginate' );
+		_fnCompatMap( init, 'pagingType',    'sPaginationType' );
+		_fnCompatMap( init, 'pageLength',    'iDisplayLength' );
+		_fnCompatMap( init, 'searching',     'bFilter' );
+	
+		// Boolean initialisation of x-scrolling
+		if ( typeof init.sScrollX === 'boolean' ) {
+			init.sScrollX = init.sScrollX ? '100%' : '';
+		}
+		if ( typeof init.scrollX === 'boolean' ) {
+			init.scrollX = init.scrollX ? '100%' : '';
+		}
+	
+		// Column search objects are in an array, so it needs to be converted
+		// element by element
+		var searchCols = init.aoSearchCols;
+	
+		if ( searchCols ) {
+			for ( var i=0, ien=searchCols.length ; i<ien ; i++ ) {
+				if ( searchCols[i] ) {
+					_fnCamelToHungarian( DataTable.models.oSearch, searchCols[i] );
+				}
+			}
+		}
+	}
+	
+	
+	/**
+	 * Provide backwards compatibility for column options. Note that the new options
+	 * are mapped onto the old parameters, so this is an external interface change
+	 * only.
+	 *  @param {object} init Object to map
+	 */
+	function _fnCompatCols ( init )
+	{
+		_fnCompatMap( init, 'orderable',     'bSortable' );
+		_fnCompatMap( init, 'orderData',     'aDataSort' );
+		_fnCompatMap( init, 'orderSequence', 'asSorting' );
+		_fnCompatMap( init, 'orderDataType', 'sortDataType' );
+	
+		// orderData can be given as an integer
+		var dataSort = init.aDataSort;
+		if ( dataSort && ! $.isArray( dataSort ) ) {
+			init.aDataSort = [ dataSort ];
+		}
+	}
+	
+	
+	/**
+	 * Browser feature detection for capabilities, quirks
+	 *  @param {object} settings dataTables settings object
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnBrowserDetect( settings )
+	{
+		// We don't need to do this every time DataTables is constructed, the values
+		// calculated are specific to the browser and OS configuration which we
+		// don't expect to change between initialisations
+		if ( ! DataTable.__browser ) {
+			var browser = {};
+			DataTable.__browser = browser;
+	
+			// Scrolling feature / quirks detection
+			var n = $('<div/>')
+				.css( {
+					position: 'fixed',
+					top: 0,
+					left: $(window).scrollLeft()*-1, // allow for scrolling
+					height: 1,
+					width: 1,
+					overflow: 'hidden'
+				} )
+				.append(
+					$('<div/>')
+						.css( {
+							position: 'absolute',
+							top: 1,
+							left: 1,
+							width: 100,
+							overflow: 'scroll'
+						} )
+						.append(
+							$('<div/>')
+								.css( {
+									width: '100%',
+									height: 10
+								} )
+						)
+				)
+				.appendTo( 'body' );
+	
+			var outer = n.children();
+			var inner = outer.children();
+	
+			// Numbers below, in order, are:
+			// inner.offsetWidth, inner.clientWidth, outer.offsetWidth, outer.clientWidth
+			//
+			// IE6 XP:                           100 100 100  83
+			// IE7 Vista:                        100 100 100  83
+			// IE 8+ Windows:                     83  83 100  83
+			// Evergreen Windows:                 83  83 100  83
+			// Evergreen Mac with scrollbars:     85  85 100  85
+			// Evergreen Mac without scrollbars: 100 100 100 100
+	
+			// Get scrollbar width
+			browser.barWidth = outer[0].offsetWidth - outer[0].clientWidth;
+	
+			// IE6/7 will oversize a width 100% element inside a scrolling element, to
+			// include the width of the scrollbar, while other browsers ensure the inner
+			// element is contained without forcing scrolling
+			browser.bScrollOversize = inner[0].offsetWidth === 100 && outer[0].clientWidth !== 100;
+	
+			// In rtl text layout, some browsers (most, but not all) will place the
+			// scrollbar on the left, rather than the right.
+			browser.bScrollbarLeft = Math.round( inner.offset().left ) !== 1;
+	
+			// IE8- don't provide height and width for getBoundingClientRect
+			browser.bBounding = n[0].getBoundingClientRect().width ? true : false;
+	
+			n.remove();
+		}
+	
+		$.extend( settings.oBrowser, DataTable.__browser );
+		settings.oScroll.iBarWidth = DataTable.__browser.barWidth;
+	}
+	
+	
+	/**
+	 * Array.prototype reduce[Right] method, used for browsers which don't support
+	 * JS 1.6. Done this way to reduce code size, since we iterate either way
+	 *  @param {object} settings dataTables settings object
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnReduce ( that, fn, init, start, end, inc )
+	{
+		var
+			i = start,
+			value,
+			isSet = false;
+	
+		if ( init !== undefined ) {
+			value = init;
+			isSet = true;
+		}
+	
+		while ( i !== end ) {
+			if ( ! that.hasOwnProperty(i) ) {
+				continue;
+			}
+	
+			value = isSet ?
+				fn( value, that[i], i, that ) :
+				that[i];
+	
+			isSet = true;
+			i += inc;
+		}
+	
+		return value;
+	}
+	
+	/**
+	 * Add a column to the list used for the table with default values
+	 *  @param {object} oSettings dataTables settings object
+	 *  @param {node} nTh The th element for this column
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnAddColumn( oSettings, nTh )
+	{
+		// Add column to aoColumns array
+		var oDefaults = DataTable.defaults.column;
+		var iCol = oSettings.aoColumns.length;
+		var oCol = $.extend( {}, DataTable.models.oColumn, oDefaults, {
+			"nTh": nTh ? nTh : document.createElement('th'),
+			"sTitle":    oDefaults.sTitle    ? oDefaults.sTitle    : nTh ? nTh.innerHTML : '',
+			"aDataSort": oDefaults.aDataSort ? oDefaults.aDataSort : [iCol],
+			"mData": oDefaults.mData ? oDefaults.mData : iCol,
+			idx: iCol
+		} );
+		oSettings.aoColumns.push( oCol );
+	
+		// Add search object for column specific search. Note that the `searchCols[ iCol ]`
+		// passed into extend can be undefined. This allows the user to give a default
+		// with only some of the parameters defined, and also not give a default
+		var searchCols = oSettings.aoPreSearchCols;
+		searchCols[ iCol ] = $.extend( {}, DataTable.models.oSearch, searchCols[ iCol ] );
+	
+		// Use the default column options function to initialise classes etc
+		_fnColumnOptions( oSettings, iCol, $(nTh).data() );
+	}
+	
+	
+	/**
+	 * Apply options for a column
+	 *  @param {object} oSettings dataTables settings object
+	 *  @param {int} iCol column index to consider
+	 *  @param {object} oOptions object with sType, bVisible and bSearchable etc
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnColumnOptions( oSettings, iCol, oOptions )
+	{
+		var oCol = oSettings.aoColumns[ iCol ];
+		var oClasses = oSettings.oClasses;
+		var th = $(oCol.nTh);
+	
+		// Try to get width information from the DOM. We can't get it from CSS
+		// as we'd need to parse the CSS stylesheet. `width` option can override
+		if ( ! oCol.sWidthOrig ) {
+			// Width attribute
+			oCol.sWidthOrig = th.attr('width') || null;
+	
+			// Style attribute
+			var t = (th.attr('style') || '').match(/width:\s*(\d+[pxem%]+)/);
+			if ( t ) {
+				oCol.sWidthOrig = t[1];
+			}
+		}
+	
+		/* User specified column options */
+		if ( oOptions !== undefined && oOptions !== null )
+		{
+			// Backwards compatibility
+			_fnCompatCols( oOptions );
+	
+			// Map camel case parameters to their Hungarian counterparts
+			_fnCamelToHungarian( DataTable.defaults.column, oOptions );
+	
+			/* Backwards compatibility for mDataProp */
+			if ( oOptions.mDataProp !== undefined && !oOptions.mData )
+			{
+				oOptions.mData = oOptions.mDataProp;
+			}
+	
+			if ( oOptions.sType )
+			{
+				oCol._sManualType = oOptions.sType;
+			}
+	
+			// `class` is a reserved word in Javascript, so we need to provide
+			// the ability to use a valid name for the camel case input
+			if ( oOptions.className && ! oOptions.sClass )
+			{
+				oOptions.sClass = oOptions.className;
+			}
+	
+			$.extend( oCol, oOptions );
+			_fnMap( oCol, oOptions, "sWidth", "sWidthOrig" );
+	
+			/* iDataSort to be applied (backwards compatibility), but aDataSort will take
+			 * priority if defined
+			 */
+			if ( oOptions.iDataSort !== undefined )
+			{
+				oCol.aDataSort = [ oOptions.iDataSort ];
+			}
+			_fnMap( oCol, oOptions, "aDataSort" );
+		}
+	
+		/* Cache the data get and set functions for speed */
+		var mDataSrc = oCol.mData;
+		var mData = _fnGetObjectDataFn( mDataSrc );
+		var mRender = oCol.mRender ? _fnGetObjectDataFn( oCol.mRender ) : null;
+	
+		var attrTest = function( src ) {
+			return typeof src === 'string' && src.indexOf('@') !== -1;
+		};
+		oCol._bAttrSrc = $.isPlainObject( mDataSrc ) && (
+			attrTest(mDataSrc.sort) || attrTest(mDataSrc.type) || attrTest(mDataSrc.filter)
+		);
+		oCol._setter = null;
+	
+		oCol.fnGetData = function (rowData, type, meta) {
+			var innerData = mData( rowData, type, undefined, meta );
+	
+			return mRender && type ?
+				mRender( innerData, type, rowData, meta ) :
+				innerData;
+		};
+		oCol.fnSetData = function ( rowData, val, meta ) {
+			return _fnSetObjectDataFn( mDataSrc )( rowData, val, meta );
+		};
+	
+		// Indicate if DataTables should read DOM data as an object or array
+		// Used in _fnGetRowElements
+		if ( typeof mDataSrc !== 'number' ) {
+			oSettings._rowReadObject = true;
+		}
+	
+		/* Feature sorting overrides column specific when off */
+		if ( !oSettings.oFeatures.bSort )
+		{
+			oCol.bSortable = false;
+			th.addClass( oClasses.sSortableNone ); // Have to add class here as order event isn't called
+		}
+	
+		/* Check that the class assignment is correct for sorting */
+		var bAsc = $.inArray('asc', oCol.asSorting) !== -1;
+		var bDesc = $.inArray('desc', oCol.asSorting) !== -1;
+		if ( !oCol.bSortable || (!bAsc && !bDesc) )
+		{
+			oCol.sSortingClass = oClasses.sSortableNone;
+			oCol.sSortingClassJUI = "";
+		}
+		else if ( bAsc && !bDesc )
+		{
+			oCol.sSortingClass = oClasses.sSortableAsc;
+			oCol.sSortingClassJUI = oClasses.sSortJUIAscAllowed;
+		}
+		else if ( !bAsc && bDesc )
+		{
+			oCol.sSortingClass = oClasses.sSortableDesc;
+			oCol.sSortingClassJUI = oClasses.sSortJUIDescAllowed;
+		}
+		else
+		{
+			oCol.sSortingClass = oClasses.sSortable;
+			oCol.sSortingClassJUI = oClasses.sSortJUI;
+		}
+	}
+	
+	
+	/**
+	 * Adjust the table column widths for new data. Note: you would probably want to
+	 * do a redraw after calling this function!
+	 *  @param {object} settings dataTables settings object
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnAdjustColumnSizing ( settings )
+	{
+		/* Not interested in doing column width calculation if auto-width is disabled */
+		if ( settings.oFeatures.bAutoWidth !== false )
+		{
+			var columns = settings.aoColumns;
+	
+			_fnCalculateColumnWidths( settings );
+			for ( var i=0 , iLen=columns.length ; i<iLen ; i++ )
+			{
+				columns[i].nTh.style.width = columns[i].sWidth;
+			}
+		}
+	
+		var scroll = settings.oScroll;
+		if ( scroll.sY !== '' || scroll.sX !== '')
+		{
+			_fnScrollDraw( settings );
+		}
+	
+		_fnCallbackFire( settings, null, 'column-sizing', [settings] );
+	}
+	
+	
+	/**
+	 * Covert the index of a visible column to the index in the data array (take account
+	 * of hidden columns)
+	 *  @param {object} oSettings dataTables settings object
+	 *  @param {int} iMatch Visible column index to lookup
+	 *  @returns {int} i the data index
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnVisibleToColumnIndex( oSettings, iMatch )
+	{
+		var aiVis = _fnGetColumns( oSettings, 'bVisible' );
+	
+		return typeof aiVis[iMatch] === 'number' ?
+			aiVis[iMatch] :
+			null;
+	}
+	
+	
+	/**
+	 * Covert the index of an index in the data array and convert it to the visible
+	 *   column index (take account of hidden columns)
+	 *  @param {int} iMatch Column index to lookup
+	 *  @param {object} oSettings dataTables settings object
+	 *  @returns {int} i the data index
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnColumnIndexToVisible( oSettings, iMatch )
+	{
+		var aiVis = _fnGetColumns( oSettings, 'bVisible' );
+		var iPos = $.inArray( iMatch, aiVis );
+	
+		return iPos !== -1 ? iPos : null;
+	}
+	
+	
+	/**
+	 * Get the number of visible columns
+	 *  @param {object} oSettings dataTables settings object
+	 *  @returns {int} i the number of visible columns
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnVisbleColumns( oSettings )
+	{
+		var vis = 0;
+	
+		// No reduce in IE8, use a loop for now
+		$.each( oSettings.aoColumns, function ( i, col ) {
+			if ( col.bVisible && $(col.nTh).css('display') !== 'none' ) {
+				vis++;
+			}
+		} );
+	
+		return vis;
+	}
+	
+	
+	/**
+	 * Get an array of column indexes that match a given property
+	 *  @param {object} oSettings dataTables settings object
+	 *  @param {string} sParam Parameter in aoColumns to look for - typically
+	 *    bVisible or bSearchable
+	 *  @returns {array} Array of indexes with matched properties
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnGetColumns( oSettings, sParam )
+	{
+		var a = [];
+	
+		$.map( oSettings.aoColumns, function(val, i) {
+			if ( val[sParam] ) {
+				a.push( i );
+			}
+		} );
+	
+		return a;
+	}
+	
+	
+	/**
+	 * Calculate the 'type' of a column
+	 *  @param {object} settings dataTables settings object
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnColumnTypes ( settings )
+	{
+		var columns = settings.aoColumns;
+		var data = settings.aoData;
+		var types = DataTable.ext.type.detect;
+		var i, ien, j, jen, k, ken;
+		var col, cell, detectedType, cache;
+	
+		// For each column, spin over the 
+		for ( i=0, ien=columns.length ; i<ien ; i++ ) {
+			col = columns[i];
+			cache = [];
+	
+			if ( ! col.sType && col._sManualType ) {
+				col.sType = col._sManualType;
+			}
+			else if ( ! col.sType ) {
+				for ( j=0, jen=types.length ; j<jen ; j++ ) {
+					for ( k=0, ken=data.length ; k<ken ; k++ ) {
+						// Use a cache array so we only need to get the type data
+						// from the formatter once (when using multiple detectors)
+						if ( cache[k] === undefined ) {
+							cache[k] = _fnGetCellData( settings, k, i, 'type' );
+						}
+	
+						detectedType = types[j]( cache[k], settings );
+	
+						// If null, then this type can't apply to this column, so
+						// rather than testing all cells, break out. There is an
+						// exception for the last type which is `html`. We need to
+						// scan all rows since it is possible to mix string and HTML
+						// types
+						if ( ! detectedType && j !== types.length-1 ) {
+							break;
+						}
+	
+						// Only a single match is needed for html type since it is
+						// bottom of the pile and very similar to string
+						if ( detectedType === 'html' ) {
+							break;
+						}
+					}
+	
+					// Type is valid for all data points in the column - use this
+					// type
+					if ( detectedType ) {
+						col.sType = detectedType;
+						break;
+					}
+				}
+	
+				// Fall back - if no type was detected, always use string
+				if ( ! col.sType ) {
+					col.sType = 'string';
+				}
+			}
+		}
+	}
+	
+	
+	/**
+	 * Take the column definitions and static columns arrays and calculate how
+	 * they relate to column indexes. The callback function will then apply the
+	 * definition found for a column to a suitable configuration object.
+	 *  @param {object} oSettings dataTables settings object
+	 *  @param {array} aoColDefs The aoColumnDefs array that is to be applied
+	 *  @param {array} aoCols The aoColumns array that defines columns individually
+	 *  @param {function} fn Callback function - takes two parameters, the calculated
+	 *    column index and the definition for that column.
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnApplyColumnDefs( oSettings, aoColDefs, aoCols, fn )
+	{
+		var i, iLen, j, jLen, k, kLen, def;
+		var columns = oSettings.aoColumns;
+	
+		// Column definitions with aTargets
+		if ( aoColDefs )
+		{
+			/* Loop over the definitions array - loop in reverse so first instance has priority */
+			for ( i=aoColDefs.length-1 ; i>=0 ; i-- )
+			{
+				def = aoColDefs[i];
+	
+				/* Each definition can target multiple columns, as it is an array */
+				var aTargets = def.targets !== undefined ?
+					def.targets :
+					def.aTargets;
+	
+				if ( ! $.isArray( aTargets ) )
+				{
+					aTargets = [ aTargets ];
+				}
+	
+				for ( j=0, jLen=aTargets.length ; j<jLen ; j++ )
+				{
+					if ( typeof aTargets[j] === 'number' && aTargets[j] >= 0 )
+					{
+						/* Add columns that we don't yet know about */
+						while( columns.length <= aTargets[j] )
+						{
+							_fnAddColumn( oSettings );
+						}
+	
+						/* Integer, basic index */
+						fn( aTargets[j], def );
+					}
+					else if ( typeof aTargets[j] === 'number' && aTargets[j] < 0 )
+					{
+						/* Negative integer, right to left column counting */
+						fn( columns.length+aTargets[j], def );
+					}
+					else if ( typeof aTargets[j] === 'string' )
+					{
+						/* Class name matching on TH element */
+						for ( k=0, kLen=columns.length ; k<kLen ; k++ )
+						{
+							if ( aTargets[j] == "_all" ||
+							     $(columns[k].nTh).hasClass( aTargets[j] ) )
+							{
+								fn( k, def );
+							}
+						}
+					}
+				}
+			}
+		}
+	
+		// Statically defined columns array
+		if ( aoCols )
+		{
+			for ( i=0, iLen=aoCols.length ; i<iLen ; i++ )
+			{
+				fn( i, aoCols[i] );
+			}
+		}
+	}
+	
+	/**
+	 * Add a data array to the table, creating DOM node etc. This is the parallel to
+	 * _fnGatherData, but for adding rows from a Javascript source, rather than a
+	 * DOM source.
+	 *  @param {object} oSettings dataTables settings object
+	 *  @param {array} aData data array to be added
+	 *  @param {node} [nTr] TR element to add to the table - optional. If not given,
+	 *    DataTables will create a row automatically
+	 *  @param {array} [anTds] Array of TD|TH elements for the row - must be given
+	 *    if nTr is.
+	 *  @returns {int} >=0 if successful (index of new aoData entry), -1 if failed
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnAddData ( oSettings, aDataIn, nTr, anTds )
+	{
+		/* Create the object for storing information about this new row */
+		var iRow = oSettings.aoData.length;
+		var oData = $.extend( true, {}, DataTable.models.oRow, {
+			src: nTr ? 'dom' : 'data',
+			idx: iRow
+		} );
+	
+		oData._aData = aDataIn;
+		oSettings.aoData.push( oData );
+	
+		/* Create the cells */
+		var nTd, sThisType;
+		var columns = oSettings.aoColumns;
+	
+		// Invalidate the column types as the new data needs to be revalidated
+		for ( var i=0, iLen=columns.length ; i<iLen ; i++ )
+		{
+			columns[i].sType = null;
+		}
+	
+		/* Add to the display array */
+		oSettings.aiDisplayMaster.push( iRow );
+	
+		var id = oSettings.rowIdFn( aDataIn );
+		if ( id !== undefined ) {
+			oSettings.aIds[ id ] = oData;
+		}
+	
+		/* Create the DOM information, or register it if already present */
+		if ( nTr || ! oSettings.oFeatures.bDeferRender )
+		{
+			_fnCreateTr( oSettings, iRow, nTr, anTds );
+		}
+	
+		return iRow;
+	}
+	
+	
+	/**
+	 * Add one or more TR elements to the table. Generally we'd expect to
+	 * use this for reading data from a DOM sourced table, but it could be
+	 * used for an TR element. Note that if a TR is given, it is used (i.e.
+	 * it is not cloned).
+	 *  @param {object} settings dataTables settings object
+	 *  @param {array|node|jQuery} trs The TR element(s) to add to the table
+	 *  @returns {array} Array of indexes for the added rows
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnAddTr( settings, trs )
+	{
+		var row;
+	
+		// Allow an individual node to be passed in
+		if ( ! (trs instanceof $) ) {
+			trs = $(trs);
+		}
+	
+		return trs.map( function (i, el) {
+			row = _fnGetRowElements( settings, el );
+			return _fnAddData( settings, row.data, el, row.cells );
+		} );
+	}
+	
+	
+	/**
+	 * Take a TR element and convert it to an index in aoData
+	 *  @param {object} oSettings dataTables settings object
+	 *  @param {node} n the TR element to find
+	 *  @returns {int} index if the node is found, null if not
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnNodeToDataIndex( oSettings, n )
+	{
+		return (n._DT_RowIndex!==undefined) ? n._DT_RowIndex : null;
+	}
+	
+	
+	/**
+	 * Take a TD element and convert it into a column data index (not the visible index)
+	 *  @param {object} oSettings dataTables settings object
+	 *  @param {int} iRow The row number the TD/TH can be found in
+	 *  @param {node} n The TD/TH element to find
+	 *  @returns {int} index if the node is found, -1 if not
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnNodeToColumnIndex( oSettings, iRow, n )
+	{
+		return $.inArray( n, oSettings.aoData[ iRow ].anCells );
+	}
+	
+	
+	/**
+	 * Get the data for a given cell from the internal cache, taking into account data mapping
+	 *  @param {object} settings dataTables settings object
+	 *  @param {int} rowIdx aoData row id
+	 *  @param {int} colIdx Column index
+	 *  @param {string} type data get type ('display', 'type' 'filter' 'sort')
+	 *  @returns {*} Cell data
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnGetCellData( settings, rowIdx, colIdx, type )
+	{
+		var draw           = settings.iDraw;
+		var col            = settings.aoColumns[colIdx];
+		var rowData        = settings.aoData[rowIdx]._aData;
+		var defaultContent = col.sDefaultContent;
+		var cellData       = col.fnGetData( rowData, type, {
+			settings: settings,
+			row:      rowIdx,
+			col:      colIdx
+		} );
+	
+		if ( cellData === undefined ) {
+			if ( settings.iDrawError != draw && defaultContent === null ) {
+				_fnLog( settings, 0, "Requested unknown parameter "+
+					(typeof col.mData=='function' ? '{function}' : "'"+col.mData+"'")+
+					" for row "+rowIdx+", column "+colIdx, 4 );
+				settings.iDrawError = draw;
+			}
+			return defaultContent;
+		}
+	
+		// When the data source is null and a specific data type is requested (i.e.
+		// not the original data), we can use default column data
+		if ( (cellData === rowData || cellData === null) && defaultContent !== null && type !== undefined ) {
+			cellData = defaultContent;
+		}
+		else if ( typeof cellData === 'function' ) {
+			// If the data source is a function, then we run it and use the return,
+			// executing in the scope of the data object (for instances)
+			return cellData.call( rowData );
+		}
+	
+		if ( cellData === null && type == 'display' ) {
+			return '';
+		}
+		return cellData;
+	}
+	
+	
+	/**
+	 * Set the value for a specific cell, into the internal data cache
+	 *  @param {object} settings dataTables settings object
+	 *  @param {int} rowIdx aoData row id
+	 *  @param {int} colIdx Column index
+	 *  @param {*} val Value to set
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnSetCellData( settings, rowIdx, colIdx, val )
+	{
+		var col     = settings.aoColumns[colIdx];
+		var rowData = settings.aoData[rowIdx]._aData;
+	
+		col.fnSetData( rowData, val, {
+			settings: settings,
+			row:      rowIdx,
+			col:      colIdx
+		}  );
+	}
+	
+	
+	// Private variable that is used to match action syntax in the data property object
+	var __reArray = /\[.*?\]$/;
+	var __reFn = /\(\)$/;
+	
+	/**
+	 * Split string on periods, taking into account escaped periods
+	 * @param  {string} str String to split
+	 * @return {array} Split string
+	 */
+	function _fnSplitObjNotation( str )
+	{
+		return $.map( str.match(/(\\.|[^\.])+/g) || [''], function ( s ) {
+			return s.replace(/\\\./g, '.');
+		} );
+	}
+	
+	
+	/**
+	 * Return a function that can be used to get data from a source object, taking
+	 * into account the ability to use nested objects as a source
+	 *  @param {string|int|function} mSource The data source for the object
+	 *  @returns {function} Data get function
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnGetObjectDataFn( mSource )
+	{
+		if ( $.isPlainObject( mSource ) )
+		{
+			/* Build an object of get functions, and wrap them in a single call */
+			var o = {};
+			$.each( mSource, function (key, val) {
+				if ( val ) {
+					o[key] = _fnGetObjectDataFn( val );
+				}
+			} );
+	
+			return function (data, type, row, meta) {
+				var t = o[type] || o._;
+				return t !== undefined ?
+					t(data, type, row, meta) :
+					data;
+			};
+		}
+		else if ( mSource === null )
+		{
+			/* Give an empty string for rendering / sorting etc */
+			return function (data) { // type, row and meta also passed, but not used
+				return data;
+			};
+		}
+		else if ( typeof mSource === 'function' )
+		{
+			return function (data, type, row, meta) {
+				return mSource( data, type, row, meta );
+			};
+		}
+		else if ( typeof mSource === 'string' && (mSource.indexOf('.') !== -1 ||
+			      mSource.indexOf('[') !== -1 || mSource.indexOf('(') !== -1) )
+		{
+			/* If there is a . in the source string then the data source is in a
+			 * nested object so we loop over the data for each level to get the next
+			 * level down. On each loop we test for undefined, and if found immediately
+			 * return. This allows entire objects to be missing and sDefaultContent to
+			 * be used if defined, rather than throwing an error
+			 */
+			var fetchData = function (data, type, src) {
+				var arrayNotation, funcNotation, out, innerSrc;
+	
+				if ( src !== "" )
+				{
+					var a = _fnSplitObjNotation( src );
+	
+					for ( var i=0, iLen=a.length ; i<iLen ; i++ )
+					{
+						// Check if we are dealing with special notation
+						arrayNotation = a[i].match(__reArray);
+						funcNotation = a[i].match(__reFn);
+	
+						if ( arrayNotation )
+						{
+							// Array notation
+							a[i] = a[i].replace(__reArray, '');
+	
+							// Condition allows simply [] to be passed in
+							if ( a[i] !== "" ) {
+								data = data[ a[i] ];
+							}
+							out = [];
+	
+							// Get the remainder of the nested object to get
+							a.splice( 0, i+1 );
+							innerSrc = a.join('.');
+	
+							// Traverse each entry in the array getting the properties requested
+							if ( $.isArray( data ) ) {
+								for ( var j=0, jLen=data.length ; j<jLen ; j++ ) {
+									out.push( fetchData( data[j], type, innerSrc ) );
+								}
+							}
+	
+							// If a string is given in between the array notation indicators, that
+							// is used to join the strings together, otherwise an array is returned
+							var join = arrayNotation[0].substring(1, arrayNotation[0].length-1);
+							data = (join==="") ? out : out.join(join);
+	
+							// The inner call to fetchData has already traversed through the remainder
+							// of the source requested, so we exit from the loop
+							break;
+						}
+						else if ( funcNotation )
+						{
+							// Function call
+							a[i] = a[i].replace(__reFn, '');
+							data = data[ a[i] ]();
+							continue;
+						}
+	
+						if ( data === null || data[ a[i] ] === undefined )
+						{
+							return undefined;
+						}
+						data = data[ a[i] ];
+					}
+				}
+	
+				return data;
+			};
+	
+			return function (data, type) { // row and meta also passed, but not used
+				return fetchData( data, type, mSource );
+			};
+		}
+		else
+		{
+			/* Array or flat object mapping */
+			return function (data, type) { // row and meta also passed, but not used
+				return data[mSource];
+			};
+		}
+	}
+	
+	
+	/**
+	 * Return a function that can be used to set data from a source object, taking
+	 * into account the ability to use nested objects as a source
+	 *  @param {string|int|function} mSource The data source for the object
+	 *  @returns {function} Data set function
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnSetObjectDataFn( mSource )
+	{
+		if ( $.isPlainObject( mSource ) )
+		{
+			/* Unlike get, only the underscore (global) option is used for for
+			 * setting data since we don't know the type here. This is why an object
+			 * option is not documented for `mData` (which is read/write), but it is
+			 * for `mRender` which is read only.
+			 */
+			return _fnSetObjectDataFn( mSource._ );
+		}
+		else if ( mSource === null )
+		{
+			/* Nothing to do when the data source is null */
+			return function () {};
+		}
+		else if ( typeof mSource === 'function' )
+		{
+			return function (data, val, meta) {
+				mSource( data, 'set', val, meta );
+			};
+		}
+		else if ( typeof mSource === 'string' && (mSource.indexOf('.') !== -1 ||
+			      mSource.indexOf('[') !== -1 || mSource.indexOf('(') !== -1) )
+		{
+			/* Like the get, we need to get data from a nested object */
+			var setData = function (data, val, src) {
+				var a = _fnSplitObjNotation( src ), b;
+				var aLast = a[a.length-1];
+				var arrayNotation, funcNotation, o, innerSrc;
+	
+				for ( var i=0, iLen=a.length-1 ; i<iLen ; i++ )
+				{
+					// Check if we are dealing with an array notation request
+					arrayNotation = a[i].match(__reArray);
+					funcNotation = a[i].match(__reFn);
+	
+					if ( arrayNotation )
+					{
+						a[i] = a[i].replace(__reArray, '');
+						data[ a[i] ] = [];
+	
+						// Get the remainder of the nested object to set so we can recurse
+						b = a.slice();
+						b.splice( 0, i+1 );
+						innerSrc = b.join('.');
+	
+						// Traverse each entry in the array setting the properties requested
+						if ( $.isArray( val ) )
+						{
+							for ( var j=0, jLen=val.length ; j<jLen ; j++ )
+							{
+								o = {};
+								setData( o, val[j], innerSrc );
+								data[ a[i] ].push( o );
+							}
+						}
+						else
+						{
+							// We've been asked to save data to an array, but it
+							// isn't array data to be saved. Best that can be done
+							// is to just save the value.
+							data[ a[i] ] = val;
+						}
+	
+						// The inner call to setData has already traversed through the remainder
+						// of the source and has set the data, thus we can exit here
+						return;
+					}
+					else if ( funcNotation )
+					{
+						// Function call
+						a[i] = a[i].replace(__reFn, '');
+						data = data[ a[i] ]( val );
+					}
+	
+					// If the nested object doesn't currently exist - since we are
+					// trying to set the value - create it
+					if ( data[ a[i] ] === null || data[ a[i] ] === undefined )
+					{
+						data[ a[i] ] = {};
+					}
+					data = data[ a[i] ];
+				}
+	
+				// Last item in the input - i.e, the actual set
+				if ( aLast.match(__reFn ) )
+				{
+					// Function call
+					data = data[ aLast.replace(__reFn, '') ]( val );
+				}
+				else
+				{
+					// If array notation is used, we just want to strip it and use the property name
+					// and assign the value. If it isn't used, then we get the result we want anyway
+					data[ aLast.replace(__reArray, '') ] = val;
+				}
+			};
+	
+			return function (data, val) { // meta is also passed in, but not used
+				return setData( data, val, mSource );
+			};
+		}
+		else
+		{
+			/* Array or flat object mapping */
+			return function (data, val) { // meta is also passed in, but not used
+				data[mSource] = val;
+			};
+		}
+	}
+	
+	
+	/**
+	 * Return an array with the full table data
+	 *  @param {object} oSettings dataTables settings object
+	 *  @returns array {array} aData Master data array
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnGetDataMaster ( settings )
+	{
+		return _pluck( settings.aoData, '_aData' );
+	}
+	
+	
+	/**
+	 * Nuke the table
+	 *  @param {object} oSettings dataTables settings object
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnClearTable( settings )
+	{
+		settings.aoData.length = 0;
+		settings.aiDisplayMaster.length = 0;
+		settings.aiDisplay.length = 0;
+		settings.aIds = {};
+	}
+	
+	
+	 /**
+	 * Take an array of integers (index array) and remove a target integer (value - not
+	 * the key!)
+	 *  @param {array} a Index array to target
+	 *  @param {int} iTarget value to find
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnDeleteIndex( a, iTarget, splice )
+	{
+		var iTargetIndex = -1;
+	
+		for ( var i=0, iLen=a.length ; i<iLen ; i++ )
+		{
+			if ( a[i] == iTarget )
+			{
+				iTargetIndex = i;
+			}
+			else if ( a[i] > iTarget )
+			{
+				a[i]--;
+			}
+		}
+	
+		if ( iTargetIndex != -1 && splice === undefined )
+		{
+			a.splice( iTargetIndex, 1 );
+		}
+	}
+	
+	
+	/**
+	 * Mark cached data as invalid such that a re-read of the data will occur when
+	 * the cached data is next requested. Also update from the data source object.
+	 *
+	 * @param {object} settings DataTables settings object
+	 * @param {int}    rowIdx   Row index to invalidate
+	 * @param {string} [src]    Source to invalidate from: undefined, 'auto', 'dom'
+	 *     or 'data'
+	 * @param {int}    [colIdx] Column index to invalidate. If undefined the whole
+	 *     row will be invalidated
+	 * @memberof DataTable#oApi
+	 *
+	 * @todo For the modularisation of v1.11 this will need to become a callback, so
+	 *   the sort and filter methods can subscribe to it. That will required
+	 *   initialisation options for sorting, which is why it is not already baked in
+	 */
+	function _fnInvalidate( settings, rowIdx, src, colIdx )
+	{
+		var row = settings.aoData[ rowIdx ];
+		var i, ien;
+		var cellWrite = function ( cell, col ) {
+			// This is very frustrating, but in IE if you just write directly
+			// to innerHTML, and elements that are overwritten are GC'ed,
+			// even if there is a reference to them elsewhere
+			while ( cell.childNodes.length ) {
+				cell.removeChild( cell.firstChild );
+			}
+	
+			cell.innerHTML = _fnGetCellData( settings, rowIdx, col, 'display' );
+		};
+	
+		// Are we reading last data from DOM or the data object?
+		if ( src === 'dom' || ((! src || src === 'auto') && row.src === 'dom') ) {
+			// Read the data from the DOM
+			row._aData = _fnGetRowElements(
+					settings, row, colIdx, colIdx === undefined ? undefined : row._aData
+				)
+				.data;
+		}
+		else {
+			// Reading from data object, update the DOM
+			var cells = row.anCells;
+	
+			if ( cells ) {
+				if ( colIdx !== undefined ) {
+					cellWrite( cells[colIdx], colIdx );
+				}
+				else {
+					for ( i=0, ien=cells.length ; i<ien ; i++ ) {
+						cellWrite( cells[i], i );
+					}
+				}
+			}
+		}
+	
+		// For both row and cell invalidation, the cached data for sorting and
+		// filtering is nulled out
+		row._aSortData = null;
+		row._aFilterData = null;
+	
+		// Invalidate the type for a specific column (if given) or all columns since
+		// the data might have changed
+		var cols = settings.aoColumns;
+		if ( colIdx !== undefined ) {
+			cols[ colIdx ].sType = null;
+		}
+		else {
+			for ( i=0, ien=cols.length ; i<ien ; i++ ) {
+				cols[i].sType = null;
+			}
+	
+			// Update DataTables special `DT_*` attributes for the row
+			_fnRowAttributes( settings, row );
+		}
+	}
+	
+	
+	/**
+	 * Build a data source object from an HTML row, reading the contents of the
+	 * cells that are in the row.
+	 *
+	 * @param {object} settings DataTables settings object
+	 * @param {node|object} TR element from which to read data or existing row
+	 *   object from which to re-read the data from the cells
+	 * @param {int} [colIdx] Optional column index
+	 * @param {array|object} [d] Data source object. If `colIdx` is given then this
+	 *   parameter should also be given and will be used to write the data into.
+	 *   Only the column in question will be written
+	 * @returns {object} Object with two parameters: `data` the data read, in
+	 *   document order, and `cells` and array of nodes (they can be useful to the
+	 *   caller, so rather than needing a second traversal to get them, just return
+	 *   them from here).
+	 * @memberof DataTable#oApi
+	 */
+	function _fnGetRowElements( settings, row, colIdx, d )
+	{
+		var
+			tds = [],
+			td = row.firstChild,
+			name, col, o, i=0, contents,
+			columns = settings.aoColumns,
+			objectRead = settings._rowReadObject;
+	
+		// Allow the data object to be passed in, or construct
+		d = d !== undefined ?
+			d :
+			objectRead ?
+				{} :
+				[];
+	
+		var attr = function ( str, td  ) {
+			if ( typeof str === 'string' ) {
+				var idx = str.indexOf('@');
+	
+				if ( idx !== -1 ) {
+					var attr = str.substring( idx+1 );
+					var setter = _fnSetObjectDataFn( str );
+					setter( d, td.getAttribute( attr ) );
+				}
+			}
+		};
+	
+		// Read data from a cell and store into the data object
+		var cellProcess = function ( cell ) {
+			if ( colIdx === undefined || colIdx === i ) {
+				col = columns[i];
+				contents = $.trim(cell.innerHTML);
+	
+				if ( col && col._bAttrSrc ) {
+					var setter = _fnSetObjectDataFn( col.mData._ );
+					setter( d, contents );
+	
+					attr( col.mData.sort, cell );
+					attr( col.mData.type, cell );
+					attr( col.mData.filter, cell );
+				}
+				else {
+					// Depending on the `data` option for the columns the data can
+					// be read to either an object or an array.
+					if ( objectRead ) {
+						if ( ! col._setter ) {
+							// Cache the setter function
+							col._setter = _fnSetObjectDataFn( col.mData );
+						}
+						col._setter( d, contents );
+					}
+					else {
+						d[i] = contents;
+					}
+				}
+			}
+	
+			i++;
+		};
+	
+		if ( td ) {
+			// `tr` element was passed in
+			while ( td ) {
+				name = td.nodeName.toUpperCase();
+	
+				if ( name == "TD" || name == "TH" ) {
+					cellProcess( td );
+					tds.push( td );
+				}
+	
+				td = td.nextSibling;
+			}
+		}
+		else {
+			// Existing row object passed in
+			tds = row.anCells;
+	
+			for ( var j=0, jen=tds.length ; j<jen ; j++ ) {
+				cellProcess( tds[j] );
+			}
+		}
+	
+		// Read the ID from the DOM if present
+		var rowNode = row.firstChild ? row : row.nTr;
+	
+		if ( rowNode ) {
+			var id = rowNode.getAttribute( 'id' );
+	
+			if ( id ) {
+				_fnSetObjectDataFn( settings.rowId )( d, id );
+			}
+		}
+	
+		return {
+			data: d,
+			cells: tds
+		};
+	}
+	/**
+	 * Create a new TR element (and it's TD children) for a row
+	 *  @param {object} oSettings dataTables settings object
+	 *  @param {int} iRow Row to consider
+	 *  @param {node} [nTrIn] TR element to add to the table - optional. If not given,
+	 *    DataTables will create a row automatically
+	 *  @param {array} [anTds] Array of TD|TH elements for the row - must be given
+	 *    if nTr is.
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnCreateTr ( oSettings, iRow, nTrIn, anTds )
+	{
+		var
+			row = oSettings.aoData[iRow],
+			rowData = row._aData,
+			cells = [],
+			nTr, nTd, oCol,
+			i, iLen;
+	
+		if ( row.nTr === null )
+		{
+			nTr = nTrIn || document.createElement('tr');
+	
+			row.nTr = nTr;
+			row.anCells = cells;
+	
+			/* Use a private property on the node to allow reserve mapping from the node
+			 * to the aoData array for fast look up
+			 */
+			nTr._DT_RowIndex = iRow;
+	
+			/* Special parameters can be given by the data source to be used on the row */
+			_fnRowAttributes( oSettings, row );
+	
+			/* Process each column */
+			for ( i=0, iLen=oSettings.aoColumns.length ; i<iLen ; i++ )
+			{
+				oCol = oSettings.aoColumns[i];
+	
+				nTd = nTrIn ? anTds[i] : document.createElement( oCol.sCellType );
+				nTd._DT_CellIndex = {
+					row: iRow,
+					column: i
+				};
+				
+				cells.push( nTd );
+	
+				// Need to create the HTML if new, or if a rendering function is defined
+				if ( (!nTrIn || oCol.mRender || oCol.mData !== i) &&
+					 (!$.isPlainObject(oCol.mData) || oCol.mData._ !== i+'.display')
+				) {
+					nTd.innerHTML = _fnGetCellData( oSettings, iRow, i, 'display' );
+				}
+	
+				/* Add user defined class */
+				if ( oCol.sClass )
+				{
+					nTd.className += ' '+oCol.sClass;
+				}
+	
+				// Visibility - add or remove as required
+				if ( oCol.bVisible && ! nTrIn )
+				{
+					nTr.appendChild( nTd );
+				}
+				else if ( ! oCol.bVisible && nTrIn )
+				{
+					nTd.parentNode.removeChild( nTd );
+				}
+	
+				if ( oCol.fnCreatedCell )
+				{
+					oCol.fnCreatedCell.call( oSettings.oInstance,
+						nTd, _fnGetCellData( oSettings, iRow, i ), rowData, iRow, i
+					);
+				}
+			}
+	
+			_fnCallbackFire( oSettings, 'aoRowCreatedCallback', null, [nTr, rowData, iRow] );
+		}
+	
+		// Remove once webkit bug 131819 and Chromium bug 365619 have been resolved
+		// and deployed
+		row.nTr.setAttribute( 'role', 'row' );
+	}
+	
+	
+	/**
+	 * Add attributes to a row based on the special `DT_*` parameters in a data
+	 * source object.
+	 *  @param {object} settings DataTables settings object
+	 *  @param {object} DataTables row object for the row to be modified
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnRowAttributes( settings, row )
+	{
+		var tr = row.nTr;
+		var data = row._aData;
+	
+		if ( tr ) {
+			var id = settings.rowIdFn( data );
+	
+			if ( id ) {
+				tr.id = id;
+			}
+	
+			if ( data.DT_RowClass ) {
+				// Remove any classes added by DT_RowClass before
+				var a = data.DT_RowClass.split(' ');
+				row.__rowc = row.__rowc ?
+					_unique( row.__rowc.concat( a ) ) :
+					a;
+	
+				$(tr)
+					.removeClass( row.__rowc.join(' ') )
+					.addClass( data.DT_RowClass );
+			}
+	
+			if ( data.DT_RowAttr ) {
+				$(tr).attr( data.DT_RowAttr );
+			}
+	
+			if ( data.DT_RowData ) {
+				$(tr).data( data.DT_RowData );
+			}
+		}
+	}
+	
+	
+	/**
+	 * Create the HTML header for the table
+	 *  @param {object} oSettings dataTables settings object
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnBuildHead( oSettings )
+	{
+		var i, ien, cell, row, column;
+		var thead = oSettings.nTHead;
+		var tfoot = oSettings.nTFoot;
+		var createHeader = $('th, td', thead).length === 0;
+		var classes = oSettings.oClasses;
+		var columns = oSettings.aoColumns;
+	
+		if ( createHeader ) {
+			row = $('<tr/>').appendTo( thead );
+		}
+	
+		for ( i=0, ien=columns.length ; i<ien ; i++ ) {
+			column = columns[i];
+			cell = $( column.nTh ).addClass( column.sClass );
+	
+			if ( createHeader ) {
+				cell.appendTo( row );
+			}
+	
+			// 1.11 move into sorting
+			if ( oSettings.oFeatures.bSort ) {
+				cell.addClass( column.sSortingClass );
+	
+				if ( column.bSortable !== false ) {
+					cell
+						.attr( 'tabindex', oSettings.iTabIndex )
+						.attr( 'aria-controls', oSettings.sTableId );
+	
+					_fnSortAttachListener( oSettings, column.nTh, i );
+				}
+			}
+	
+			if ( column.sTitle != cell[0].innerHTML ) {
+				cell.html( column.sTitle );
+			}
+	
+			_fnRenderer( oSettings, 'header' )(
+				oSettings, cell, column, classes
+			);
+		}
+	
+		if ( createHeader ) {
+			_fnDetectHeader( oSettings.aoHeader, thead );
+		}
+		
+		/* ARIA role for the rows */
+	 	$(thead).find('>tr').attr('role', 'row');
+	
+		/* Deal with the footer - add classes if required */
+		$(thead).find('>tr>th, >tr>td').addClass( classes.sHeaderTH );
+		$(tfoot).find('>tr>th, >tr>td').addClass( classes.sFooterTH );
+	
+		// Cache the footer cells. Note that we only take the cells from the first
+		// row in the footer. If there is more than one row the user wants to
+		// interact with, they need to use the table().foot() method. Note also this
+		// allows cells to be used for multiple columns using colspan
+		if ( tfoot !== null ) {
+			var cells = oSettings.aoFooter[0];
+	
+			for ( i=0, ien=cells.length ; i<ien ; i++ ) {
+				column = columns[i];
+				column.nTf = cells[i].cell;
+	
+				if ( column.sClass ) {
+					$(column.nTf).addClass( column.sClass );
+				}
+			}
+		}
+	}
+	
+	
+	/**
+	 * Draw the header (or footer) element based on the column visibility states. The
+	 * methodology here is to use the layout array from _fnDetectHeader, modified for
+	 * the instantaneous column visibility, to construct the new layout. The grid is
+	 * traversed over cell at a time in a rows x columns grid fashion, although each
+	 * cell insert can cover multiple elements in the grid - which is tracks using the
+	 * aApplied array. Cell inserts in the grid will only occur where there isn't
+	 * already a cell in that position.
+	 *  @param {object} oSettings dataTables settings object
+	 *  @param array {objects} aoSource Layout array from _fnDetectHeader
+	 *  @param {boolean} [bIncludeHidden=false] If true then include the hidden columns in the calc,
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnDrawHead( oSettings, aoSource, bIncludeHidden )
+	{
+		var i, iLen, j, jLen, k, kLen, n, nLocalTr;
+		var aoLocal = [];
+		var aApplied = [];
+		var iColumns = oSettings.aoColumns.length;
+		var iRowspan, iColspan;
+	
+		if ( ! aoSource )
+		{
+			return;
+		}
+	
+		if (  bIncludeHidden === undefined )
+		{
+			bIncludeHidden = false;
+		}
+	
+		/* Make a copy of the master layout array, but without the visible columns in it */
+		for ( i=0, iLen=aoSource.length ; i<iLen ; i++ )
+		{
+			aoLocal[i] = aoSource[i].slice();
+			aoLocal[i].nTr = aoSource[i].nTr;
+	
+			/* Remove any columns which are currently hidden */
+			for ( j=iColumns-1 ; j>=0 ; j-- )
+			{
+				if ( !oSettings.aoColumns[j].bVisible && !bIncludeHidden )
+				{
+					aoLocal[i].splice( j, 1 );
+				}
+			}
+	
+			/* Prep the applied array - it needs an element for each row */
+			aApplied.push( [] );
+		}
+	
+		for ( i=0, iLen=aoLocal.length ; i<iLen ; i++ )
+		{
+			nLocalTr = aoLocal[i].nTr;
+	
+			/* All cells are going to be replaced, so empty out the row */
+			if ( nLocalTr )
+			{
+				while( (n = nLocalTr.firstChild) )
+				{
+					nLocalTr.removeChild( n );
+				}
+			}
+	
+			for ( j=0, jLen=aoLocal[i].length ; j<jLen ; j++ )
+			{
+				iRowspan = 1;
+				iColspan = 1;
+	
+				/* Check to see if there is already a cell (row/colspan) covering our target
+				 * insert point. If there is, then there is nothing to do.
+				 */
+				if ( aApplied[i][j] === undefined )
+				{
+					nLocalTr.appendChild( aoLocal[i][j].cell );
+					aApplied[i][j] = 1;
+	
+					/* Expand the cell to cover as many rows as needed */
+					while ( aoLocal[i+iRowspan] !== undefined &&
+					        aoLocal[i][j].cell == aoLocal[i+iRowspan][j].cell )
+					{
+						aApplied[i+iRowspan][j] = 1;
+						iRowspan++;
+					}
+	
+					/* Expand the cell to cover as many columns as needed */
+					while ( aoLocal[i][j+iColspan] !== undefined &&
+					        aoLocal[i][j].cell == aoLocal[i][j+iColspan].cell )
+					{
+						/* Must update the applied array over the rows for the columns */
+						for ( k=0 ; k<iRowspan ; k++ )
+						{
+							aApplied[i+k][j+iColspan] = 1;
+						}
+						iColspan++;
+					}
+	
+					/* Do the actual expansion in the DOM */
+					$(aoLocal[i][j].cell)
+						.attr('rowspan', iRowspan)
+						.attr('colspan', iColspan);
+				}
+			}
+		}
+	}
+	
+	
+	/**
+	 * Insert the required TR nodes into the table for display
+	 *  @param {object} oSettings dataTables settings object
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnDraw( oSettings )
+	{
+		/* Provide a pre-callback function which can be used to cancel the draw is false is returned */
+		var aPreDraw = _fnCallbackFire( oSettings, 'aoPreDrawCallback', 'preDraw', [oSettings] );
+		if ( $.inArray( false, aPreDraw ) !== -1 )
+		{
+			_fnProcessingDisplay( oSettings, false );
+			return;
+		}
+	
+		var i, iLen, n;
+		var anRows = [];
+		var iRowCount = 0;
+		var asStripeClasses = oSettings.asStripeClasses;
+		var iStripes = asStripeClasses.length;
+		var iOpenRows = oSettings.aoOpenRows.length;
+		var oLang = oSettings.oLanguage;
+		var iInitDisplayStart = oSettings.iInitDisplayStart;
+		var bServerSide = _fnDataSource( oSettings ) == 'ssp';
+		var aiDisplay = oSettings.aiDisplay;
+	
+		oSettings.bDrawing = true;
+	
+		/* Check and see if we have an initial draw position from state saving */
+		if ( iInitDisplayStart !== undefined && iInitDisplayStart !== -1 )
+		{
+			oSettings._iDisplayStart = bServerSide ?
+				iInitDisplayStart :
+				iInitDisplayStart >= oSettings.fnRecordsDisplay() ?
+					0 :
+					iInitDisplayStart;
+	
+			oSettings.iInitDisplayStart = -1;
+		}
+	
+		var iDisplayStart = oSettings._iDisplayStart;
+		var iDisplayEnd = oSettings.fnDisplayEnd();
+	
+		/* Server-side processing draw intercept */
+		if ( oSettings.bDeferLoading )
+		{
+			oSettings.bDeferLoading = false;
+			oSettings.iDraw++;
+			_fnProcessingDisplay( oSettings, false );
+		}
+		else if ( !bServerSide )
+		{
+			oSettings.iDraw++;
+		}
+		else if ( !oSettings.bDestroying && !_fnAjaxUpdate( oSettings ) )
+		{
+			return;
+		}
+	
+		if ( aiDisplay.length !== 0 )
+		{
+			var iStart = bServerSide ? 0 : iDisplayStart;
+			var iEnd = bServerSide ? oSettings.aoData.length : iDisplayEnd;
+	
+			for ( var j=iStart ; j<iEnd ; j++ )
+			{
+				var iDataIndex = aiDisplay[j];
+				var aoData = oSettings.aoData[ iDataIndex ];
+				if ( aoData.nTr === null )
+				{
+					_fnCreateTr( oSettings, iDataIndex );
+				}
+	
+				var nRow = aoData.nTr;
+	
+				/* Remove the old striping classes and then add the new one */
+				if ( iStripes !== 0 )
+				{
+					var sStripe = asStripeClasses[ iRowCount % iStripes ];
+					if ( aoData._sRowStripe != sStripe )
+					{
+						$(nRow).removeClass( aoData._sRowStripe ).addClass( sStripe );
+						aoData._sRowStripe = sStripe;
+					}
+				}
+	
+				// Row callback functions - might want to manipulate the row
+				// iRowCount and j are not currently documented. Are they at all
+				// useful?
+				_fnCallbackFire( oSettings, 'aoRowCallback', null,
+					[nRow, aoData._aData, iRowCount, j] );
+	
+				anRows.push( nRow );
+				iRowCount++;
+			}
+		}
+		else
+		{
+			/* Table is empty - create a row with an empty message in it */
+			var sZero = oLang.sZeroRecords;
+			if ( oSettings.iDraw == 1 &&  _fnDataSource( oSettings ) == 'ajax' )
+			{
+				sZero = oLang.sLoadingRecords;
+			}
+			else if ( oLang.sEmptyTable && oSettings.fnRecordsTotal() === 0 )
+			{
+				sZero = oLang.sEmptyTable;
+			}
+	
+			anRows[ 0 ] = $( '<tr/>', { 'class': iStripes ? asStripeClasses[0] : '' } )
+				.append( $('<td />', {
+					'valign':  'top',
+					'colSpan': _fnVisbleColumns( oSettings ),
+					'class':   oSettings.oClasses.sRowEmpty
+				} ).html( sZero ) )[0];
+		}
+	
+		/* Header and footer callbacks */
+		_fnCallbackFire( oSettings, 'aoHeaderCallback', 'header', [ $(oSettings.nTHead).children('tr')[0],
+			_fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] );
+	
+		_fnCallbackFire( oSettings, 'aoFooterCallback', 'footer', [ $(oSettings.nTFoot).children('tr')[0],
+			_fnGetDataMaster( oSettings ), iDisplayStart, iDisplayEnd, aiDisplay ] );
+	
+		var body = $(oSettings.nTBody);
+	
+		body.children().detach();
+		body.append( $(anRows) );
+	
+		/* Call all required callback functions for the end of a draw */
+		_fnCallbackFire( oSettings, 'aoDrawCallback', 'draw', [oSettings] );
+	
+		/* Draw is complete, sorting and filtering must be as well */
+		oSettings.bSorted = false;
+		oSettings.bFiltered = false;
+		oSettings.bDrawing = false;
+	}
+	
+	
+	/**
+	 * Redraw the table - taking account of the various features which are enabled
+	 *  @param {object} oSettings dataTables settings object
+	 *  @param {boolean} [holdPosition] Keep the current paging position. By default
+	 *    the paging is reset to the first page
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnReDraw( settings, holdPosition )
+	{
+		var
+			features = settings.oFeatures,
+			sort     = features.bSort,
+			filter   = features.bFilter;
+	
+		if ( sort ) {
+			_fnSort( settings );
+		}
+	
+		if ( filter ) {
+			_fnFilterComplete( settings, settings.oPreviousSearch );
+		}
+		else {
+			// No filtering, so we want to just use the display master
+			settings.aiDisplay = settings.aiDisplayMaster.slice();
+		}
+	
+		if ( holdPosition !== true ) {
+			settings._iDisplayStart = 0;
+		}
+	
+		// Let any modules know about the draw hold position state (used by
+		// scrolling internally)
+		settings._drawHold = holdPosition;
+	
+		_fnDraw( settings );
+	
+		settings._drawHold = false;
+	}
+	
+	
+	/**
+	 * Add the options to the page HTML for the table
+	 *  @param {object} oSettings dataTables settings object
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnAddOptionsHtml ( oSettings )
+	{
+		var classes = oSettings.oClasses;
+		var table = $(oSettings.nTable);
+		var holding = $('<div/>').insertBefore( table ); // Holding element for speed
+		var features = oSettings.oFeatures;
+	
+		// All DataTables are wrapped in a div
+		var insert = $('<div/>', {
+			id:      oSettings.sTableId+'_wrapper',
+			'class': classes.sWrapper + (oSettings.nTFoot ? '' : ' '+classes.sNoFooter)
+		} );
+	
+		oSettings.nHolding = holding[0];
+		oSettings.nTableWrapper = insert[0];
+		oSettings.nTableReinsertBefore = oSettings.nTable.nextSibling;
+	
+		/* Loop over the user set positioning and place the elements as needed */
+		var aDom = oSettings.sDom.split('');
+		var featureNode, cOption, nNewNode, cNext, sAttr, j;
+		for ( var i=0 ; i<aDom.length ; i++ )
+		{
+			featureNode = null;
+			cOption = aDom[i];
+	
+			if ( cOption == '<' )
+			{
+				/* New container div */
+				nNewNode = $('<div/>')[0];
+	
+				/* Check to see if we should append an id and/or a class name to the container */
+				cNext = aDom[i+1];
+				if ( cNext == "'" || cNext == '"' )
+				{
+					sAttr = "";
+					j = 2;
+					while ( aDom[i+j] != cNext )
+					{
+						sAttr += aDom[i+j];
+						j++;
+					}
+	
+					/* Replace jQuery UI constants @todo depreciated */
+					if ( sAttr == "H" )
+					{
+						sAttr = classes.sJUIHeader;
+					}
+					else if ( sAttr == "F" )
+					{
+						sAttr = classes.sJUIFooter;
+					}
+	
+					/* The attribute can be in the format of "#id.class", "#id" or "class" This logic
+					 * breaks the string into parts and applies them as needed
+					 */
+					if ( sAttr.indexOf('.') != -1 )
+					{
+						var aSplit = sAttr.split('.');
+						nNewNode.id = aSplit[0].substr(1, aSplit[0].length-1);
+						nNewNode.className = aSplit[1];
+					}
+					else if ( sAttr.charAt(0) == "#" )
+					{
+						nNewNode.id = sAttr.substr(1, sAttr.length-1);
+					}
+					else
+					{
+						nNewNode.className = sAttr;
+					}
+	
+					i += j; /* Move along the position array */
+				}
+	
+				insert.append( nNewNode );
+				insert = $(nNewNode);
+			}
+			else if ( cOption == '>' )
+			{
+				/* End container div */
+				insert = insert.parent();
+			}
+			// @todo Move options into their own plugins?
+			else if ( cOption == 'l' && features.bPaginate && features.bLengthChange )
+			{
+				/* Length */
+				featureNode = _fnFeatureHtmlLength( oSettings );
+			}
+			else if ( cOption == 'f' && features.bFilter )
+			{
+				/* Filter */
+				featureNode = _fnFeatureHtmlFilter( oSettings );
+			}
+			else if ( cOption == 'r' && features.bProcessing )
+			{
+				/* pRocessing */
+				featureNode = _fnFeatureHtmlProcessing( oSettings );
+			}
+			else if ( cOption == 't' )
+			{
+				/* Table */
+				featureNode = _fnFeatureHtmlTable( oSettings );
+			}
+			else if ( cOption ==  'i' && features.bInfo )
+			{
+				/* Info */
+				featureNode = _fnFeatureHtmlInfo( oSettings );
+			}
+			else if ( cOption == 'p' && features.bPaginate )
+			{
+				/* Pagination */
+				featureNode = _fnFeatureHtmlPaginate( oSettings );
+			}
+			else if ( DataTable.ext.feature.length !== 0 )
+			{
+				/* Plug-in features */
+				var aoFeatures = DataTable.ext.feature;
+				for ( var k=0, kLen=aoFeatures.length ; k<kLen ; k++ )
+				{
+					if ( cOption == aoFeatures[k].cFeature )
+					{
+						featureNode = aoFeatures[k].fnInit( oSettings );
+						break;
+					}
+				}
+			}
+	
+			/* Add to the 2D features array */
+			if ( featureNode )
+			{
+				var aanFeatures = oSettings.aanFeatures;
+	
+				if ( ! aanFeatures[cOption] )
+				{
+					aanFeatures[cOption] = [];
+				}
+	
+				aanFeatures[cOption].push( featureNode );
+				insert.append( featureNode );
+			}
+		}
+	
+		/* Built our DOM structure - replace the holding div with what we want */
+		holding.replaceWith( insert );
+		oSettings.nHolding = null;
+	}
+	
+	
+	/**
+	 * Use the DOM source to create up an array of header cells. The idea here is to
+	 * create a layout grid (array) of rows x columns, which contains a reference
+	 * to the cell that that point in the grid (regardless of col/rowspan), such that
+	 * any column / row could be removed and the new grid constructed
+	 *  @param array {object} aLayout Array to store the calculated layout in
+	 *  @param {node} nThead The header/footer element for the table
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnDetectHeader ( aLayout, nThead )
+	{
+		var nTrs = $(nThead).children('tr');
+		var nTr, nCell;
+		var i, k, l, iLen, jLen, iColShifted, iColumn, iColspan, iRowspan;
+		var bUnique;
+		var fnShiftCol = function ( a, i, j ) {
+			var k = a[i];
+	                while ( k[j] ) {
+				j++;
+			}
+			return j;
+		};
+	
+		aLayout.splice( 0, aLayout.length );
+	
+		/* We know how many rows there are in the layout - so prep it */
+		for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
+		{
+			aLayout.push( [] );
+		}
+	
+		/* Calculate a layout array */
+		for ( i=0, iLen=nTrs.length ; i<iLen ; i++ )
+		{
+			nTr = nTrs[i];
+			iColumn = 0;
+	
+			/* For every cell in the row... */
+			nCell = nTr.firstChild;
+			while ( nCell ) {
+				if ( nCell.nodeName.toUpperCase() == "TD" ||
+				     nCell.nodeName.toUpperCase() == "TH" )
+				{
+					/* Get the col and rowspan attributes from the DOM and sanitise them */
+					iColspan = nCell.getAttribute('colspan') * 1;
+					iRowspan = nCell.getAttribute('rowspan') * 1;
+					iColspan = (!iColspan || iColspan===0 || iColspan===1) ? 1 : iColspan;
+					iRowspan = (!iRowspan || iRowspan===0 || iRowspan===1) ? 1 : iRowspan;
+	
+					/* There might be colspan cells already in this row, so shift our target
+					 * accordingly
+					 */
+					iColShifted = fnShiftCol( aLayout, i, iColumn );
+	
+					/* Cache calculation for unique columns */
+					bUnique = iColspan === 1 ? true : false;
+	
+					/* If there is col / rowspan, copy the information into the layout grid */
+					for ( l=0 ; l<iColspan ; l++ )
+					{
+						for ( k=0 ; k<iRowspan ; k++ )
+						{
+							aLayout[i+k][iColShifted+l] = {
+								"cell": nCell,
+								"unique": bUnique
+							};
+							aLayout[i+k].nTr = nTr;
+						}
+					}
+				}
+				nCell = nCell.nextSibling;
+			}
+		}
+	}
+	
+	
+	/**
+	 * Get an array of unique th elements, one for each column
+	 *  @param {object} oSettings dataTables settings object
+	 *  @param {node} nHeader automatically detect the layout from this node - optional
+	 *  @param {array} aLayout thead/tfoot layout from _fnDetectHeader - optional
+	 *  @returns array {node} aReturn list of unique th's
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnGetUniqueThs ( oSettings, nHeader, aLayout )
+	{
+		var aReturn = [];
+		if ( !aLayout )
+		{
+			aLayout = oSettings.aoHeader;
+			if ( nHeader )
+			{
+				aLayout = [];
+				_fnDetectHeader( aLayout, nHeader );
+			}
+		}
+	
+		for ( var i=0, iLen=aLayout.length ; i<iLen ; i++ )
+		{
+			for ( var j=0, jLen=aLayout[i].length ; j<jLen ; j++ )
+			{
+				if ( aLayout[i][j].unique &&
+					 (!aReturn[j] || !oSettings.bSortCellsTop) )
+				{
+					aReturn[j] = aLayout[i][j].cell;
+				}
+			}
+		}
+	
+		return aReturn;
+	}
+	
+	/**
+	 * Create an Ajax call based on the table's settings, taking into account that
+	 * parameters can have multiple forms, and backwards compatibility.
+	 *
+	 * @param {object} oSettings dataTables settings object
+	 * @param {array} data Data to send to the server, required by
+	 *     DataTables - may be augmented by developer callbacks
+	 * @param {function} fn Callback function to run when data is obtained
+	 */
+	function _fnBuildAjax( oSettings, data, fn )
+	{
+		// Compatibility with 1.9-, allow fnServerData and event to manipulate
+		_fnCallbackFire( oSettings, 'aoServerParams', 'serverParams', [data] );
+	
+		// Convert to object based for 1.10+ if using the old array scheme which can
+		// come from server-side processing or serverParams
+		if ( data && $.isArray(data) ) {
+			var tmp = {};
+			var rbracket = /(.*?)\[\]$/;
+	
+			$.each( data, function (key, val) {
+				var match = val.name.match(rbracket);
+	
+				if ( match ) {
+					// Support for arrays
+					var name = match[0];
+	
+					if ( ! tmp[ name ] ) {
+						tmp[ name ] = [];
+					}
+					tmp[ name ].push( val.value );
+				}
+				else {
+					tmp[val.name] = val.value;
+				}
+			} );
+			data = tmp;
+		}
+	
+		var ajaxData;
+		var ajax = oSettings.ajax;
+		var instance = oSettings.oInstance;
+		var callback = function ( json ) {
+			_fnCallbackFire( oSettings, null, 'xhr', [oSettings, json, oSettings.jqXHR] );
+			fn( json );
+		};
+	
+		if ( $.isPlainObject( ajax ) && ajax.data )
+		{
+			ajaxData = ajax.data;
+	
+			var newData = $.isFunction( ajaxData ) ?
+				ajaxData( data, oSettings ) :  // fn can manipulate data or return
+				ajaxData;                      // an object object or array to merge
+	
+			// If the function returned something, use that alone
+			data = $.isFunction( ajaxData ) && newData ?
+				newData :
+				$.extend( true, data, newData );
+	
+			// Remove the data property as we've resolved it already and don't want
+			// jQuery to do it again (it is restored at the end of the function)
+			delete ajax.data;
+		}
+	
+		var baseAjax = {
+			"data": data,
+			"success": function (json) {
+				var error = json.error || json.sError;
+				if ( error ) {
+					_fnLog( oSettings, 0, error );
+				}
+	
+				oSettings.json = json;
+				callback( json );
+			},
+			"dataType": "json",
+			"cache": false,
+			"type": oSettings.sServerMethod,
+			"error": function (xhr, error, thrown) {
+				var ret = _fnCallbackFire( oSettings, null, 'xhr', [oSettings, null, oSettings.jqXHR] );
+	
+				if ( $.inArray( true, ret ) === -1 ) {
+					if ( error == "parsererror" ) {
+						_fnLog( oSettings, 0, 'Invalid JSON response', 1 );
+					}
+					else if ( xhr.readyState === 4 ) {
+						_fnLog( oSettings, 0, 'Ajax error', 7 );
+					}
+				}
+	
+				_fnProcessingDisplay( oSettings, false );
+			}
+		};
+	
+		// Store the data submitted for the API
+		oSettings.oAjaxData = data;
+	
+		// Allow plug-ins and external processes to modify the data
+		_fnCallbackFire( oSettings, null, 'preXhr', [oSettings, data] );
+	
+		if ( oSettings.fnServerData )
+		{
+			// DataTables 1.9- compatibility
+			oSettings.fnServerData.call( instance,
+				oSettings.sAjaxSource,
+				$.map( data, function (val, key) { // Need to convert back to 1.9 trad format
+					return { name: key, value: val };
+				} ),
+				callback,
+				oSettings
+			);
+		}
+		else if ( oSettings.sAjaxSource || typeof ajax === 'string' )
+		{
+			// DataTables 1.9- compatibility
+			oSettings.jqXHR = $.ajax( $.extend( baseAjax, {
+				url: ajax || oSettings.sAjaxSource
+			} ) );
+		}
+		else if ( $.isFunction( ajax ) )
+		{
+			// Is a function - let the caller define what needs to be done
+			oSettings.jqXHR = ajax.call( instance, data, callback, oSettings );
+		}
+		else
+		{
+			// Object to extend the base settings
+			oSettings.jqXHR = $.ajax( $.extend( baseAjax, ajax ) );
+	
+			// Restore for next time around
+			ajax.data = ajaxData;
+		}
+	}
+	
+	
+	/**
+	 * Update the table using an Ajax call
+	 *  @param {object} settings dataTables settings object
+	 *  @returns {boolean} Block the table drawing or not
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnAjaxUpdate( settings )
+	{
+		if ( settings.bAjaxDataGet ) {
+			settings.iDraw++;
+			_fnProcessingDisplay( settings, true );
+	
+			_fnBuildAjax(
+				settings,
+				_fnAjaxParameters( settings ),
+				function(json) {
+					_fnAjaxUpdateDraw( settings, json );
+				}
+			);
+	
+			return false;
+		}
+		return true;
+	}
+	
+	
+	/**
+	 * Build up the parameters in an object needed for a server-side processing
+	 * request. Note that this is basically done twice, is different ways - a modern
+	 * method which is used by default in DataTables 1.10 which uses objects and
+	 * arrays, or the 1.9- method with is name / value pairs. 1.9 method is used if
+	 * the sAjaxSource option is used in the initialisation, or the legacyAjax
+	 * option is set.
+	 *  @param {object} oSettings dataTables settings object
+	 *  @returns {bool} block the table drawing or not
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnAjaxParameters( settings )
+	{
+		var
+			columns = settings.aoColumns,
+			columnCount = columns.length,
+			features = settings.oFeatures,
+			preSearch = settings.oPreviousSearch,
+			preColSearch = settings.aoPreSearchCols,
+			i, data = [], dataProp, column, columnSearch,
+			sort = _fnSortFlatten( settings ),
+			displayStart = settings._iDisplayStart,
+			displayLength = features.bPaginate !== false ?
+				settings._iDisplayLength :
+				-1;
+	
+		var param = function ( name, value ) {
+			data.push( { 'name': name, 'value': value } );
+		};
+	
+		// DataTables 1.9- compatible method
+		param( 'sEcho',          settings.iDraw );
+		param( 'iColumns',       columnCount );
+		param( 'sColumns',       _pluck( columns, 'sName' ).join(',') );
+		param( 'iDisplayStart',  displayStart );
+		param( 'iDisplayLength', displayLength );
+	
+		// DataTables 1.10+ method
+		var d = {
+			draw:    settings.iDraw,
+			columns: [],
+			order:   [],
+			start:   displayStart,
+			length:  displayLength,
+			search:  {
+				value: preSearch.sSearch,
+				regex: preSearch.bRegex
+			}
+		};
+	
+		for ( i=0 ; i<columnCount ; i++ ) {
+			column = columns[i];
+			columnSearch = preColSearch[i];
+			dataProp = typeof column.mData=="function" ? 'function' : column.mData ;
+	
+			d.columns.push( {
+				data:       dataProp,
+				name:       column.sName,
+				searchable: column.bSearchable,
+				orderable:  column.bSortable,
+				search:     {
+					value: columnSearch.sSearch,
+					regex: columnSearch.bRegex
+				}
+			} );
+	
+			param( "mDataProp_"+i, dataProp );
+	
+			if ( features.bFilter ) {
+				param( 'sSearch_'+i,     columnSearch.sSearch );
+				param( 'bRegex_'+i,      columnSearch.bRegex );
+				param( 'bSearchable_'+i, column.bSearchable );
+			}
+	
+			if ( features.bSort ) {
+				param( 'bSortable_'+i, column.bSortable );
+			}
+		}
+	
+		if ( features.bFilter ) {
+			param( 'sSearch', preSearch.sSearch );
+			param( 'bRegex', preSearch.bRegex );
+		}
+	
+		if ( features.bSort ) {
+			$.each( sort, function ( i, val ) {
+				d.order.push( { column: val.col, dir: val.dir } );
+	
+				param( 'iSortCol_'+i, val.col );
+				param( 'sSortDir_'+i, val.dir );
+			} );
+	
+			param( 'iSortingCols', sort.length );
+		}
+	
+		// If the legacy.ajax parameter is null, then we automatically decide which
+		// form to use, based on sAjaxSource
+		var legacy = DataTable.ext.legacy.ajax;
+		if ( legacy === null ) {
+			return settings.sAjaxSource ? data : d;
+		}
+	
+		// Otherwise, if legacy has been specified then we use that to decide on the
+		// form
+		return legacy ? data : d;
+	}
+	
+	
+	/**
+	 * Data the data from the server (nuking the old) and redraw the table
+	 *  @param {object} oSettings dataTables settings object
+	 *  @param {object} json json data return from the server.
+	 *  @param {string} json.sEcho Tracking flag for DataTables to match requests
+	 *  @param {int} json.iTotalRecords Number of records in the data set, not accounting for filtering
+	 *  @param {int} json.iTotalDisplayRecords Number of records in the data set, accounting for filtering
+	 *  @param {array} json.aaData The data to display on this page
+	 *  @param {string} [json.sColumns] Column ordering (sName, comma separated)
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnAjaxUpdateDraw ( settings, json )
+	{
+		// v1.10 uses camelCase variables, while 1.9 uses Hungarian notation.
+		// Support both
+		var compat = function ( old, modern ) {
+			return json[old] !== undefined ? json[old] : json[modern];
+		};
+	
+		var data = _fnAjaxDataSrc( settings, json );
+		var draw            = compat( 'sEcho',                'draw' );
+		var recordsTotal    = compat( 'iTotalRecords',        'recordsTotal' );
+		var recordsFiltered = compat( 'iTotalDisplayRecords', 'recordsFiltered' );
+	
+		if ( draw ) {
+			// Protect against out of sequence returns
+			if ( draw*1 < settings.iDraw ) {
+				return;
+			}
+			settings.iDraw = draw * 1;
+		}
+	
+		_fnClearTable( settings );
+		settings._iRecordsTotal   = parseInt(recordsTotal, 10);
+		settings._iRecordsDisplay = parseInt(recordsFiltered, 10);
+	
+		for ( var i=0, ien=data.length ; i<ien ; i++ ) {
+			_fnAddData( settings, data[i] );
+		}
+		settings.aiDisplay = settings.aiDisplayMaster.slice();
+	
+		settings.bAjaxDataGet = false;
+		_fnDraw( settings );
+	
+		if ( ! settings._bInitComplete ) {
+			_fnInitComplete( settings, json );
+		}
+	
+		settings.bAjaxDataGet = true;
+		_fnProcessingDisplay( settings, false );
+	}
+	
+	
+	/**
+	 * Get the data from the JSON data source to use for drawing a table. Using
+	 * `_fnGetObjectDataFn` allows the data to be sourced from a property of the
+	 * source object, or from a processing function.
+	 *  @param {object} oSettings dataTables settings object
+	 *  @param  {object} json Data source object / array from the server
+	 *  @return {array} Array of data to use
+	 */
+	function _fnAjaxDataSrc ( oSettings, json )
+	{
+		var dataSrc = $.isPlainObject( oSettings.ajax ) && oSettings.ajax.dataSrc !== undefined ?
+			oSettings.ajax.dataSrc :
+			oSettings.sAjaxDataProp; // Compatibility with 1.9-.
+	
+		// Compatibility with 1.9-. In order to read from aaData, check if the
+		// default has been changed, if not, check for aaData
+		if ( dataSrc === 'data' ) {
+			return json.aaData || json[dataSrc];
+		}
+	
+		return dataSrc !== "" ?
+			_fnGetObjectDataFn( dataSrc )( json ) :
+			json;
+	}
+	
+	/**
+	 * Generate the node required for filtering text
+	 *  @returns {node} Filter control element
+	 *  @param {object} oSettings dataTables settings object
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnFeatureHtmlFilter ( settings )
+	{
+		var classes = settings.oClasses;
+		var tableId = settings.sTableId;
+		var language = settings.oLanguage;
+		var previousSearch = settings.oPreviousSearch;
+		var features = settings.aanFeatures;
+		var input = '<input type="search" class="'+classes.sFilterInput+'"/>';
+	
+		var str = language.sSearch;
+		str = str.match(/_INPUT_/) ?
+			str.replace('_INPUT_', input) :
+			str+input;
+	
+		var filter = $('<div/>', {
+				'id': ! features.f ? tableId+'_filter' : null,
+				'class': classes.sFilter
+			} )
+			.append( $('<label/>' ).append( str ) );
+	
+		var searchFn = function() {
+			/* Update all other filter input elements for the new display */
+			var n = features.f;
+			var val = !this.value ? "" : this.value; // mental IE8 fix :-(
+	
+			/* Now do the filter */
+			if ( val != previousSearch.sSearch ) {
+				_fnFilterComplete( settings, {
+					"sSearch": val,
+					"bRegex": previousSearch.bRegex,
+					"bSmart": previousSearch.bSmart ,
+					"bCaseInsensitive": previousSearch.bCaseInsensitive
+				} );
+	
+				// Need to redraw, without resorting
+				settings._iDisplayStart = 0;
+				_fnDraw( settings );
+			}
+		};
+	
+		var searchDelay = settings.searchDelay !== null ?
+			settings.searchDelay :
+			_fnDataSource( settings ) === 'ssp' ?
+				400 :
+				0;
+	
+		var jqFilter = $('input', filter)
+			.val( previousSearch.sSearch )
+			.attr( 'placeholder', language.sSearchPlaceholder )
+			.on(
+				'keyup.DT search.DT input.DT paste.DT cut.DT',
+				searchDelay ?
+					_fnThrottle( searchFn, searchDelay ) :
+					searchFn
+			)
+			.on( 'keypress.DT', function(e) {
+				/* Prevent form submission */
+				if ( e.keyCode == 13 ) {
+					return false;
+				}
+			} )
+			.attr('aria-controls', tableId);
+	
+		// Update the input elements whenever the table is filtered
+		$(settings.nTable).on( 'search.dt.DT', function ( ev, s ) {
+			if ( settings === s ) {
+				// IE9 throws an 'unknown error' if document.activeElement is used
+				// inside an iframe or frame...
+				try {
+					if ( jqFilter[0] !== document.activeElement ) {
+						jqFilter.val( previousSearch.sSearch );
+					}
+				}
+				catch ( e ) {}
+			}
+		} );
+	
+		return filter[0];
+	}
+	
+	
+	/**
+	 * Filter the table using both the global filter and column based filtering
+	 *  @param {object} oSettings dataTables settings object
+	 *  @param {object} oSearch search information
+	 *  @param {int} [iForce] force a research of the master array (1) or not (undefined or 0)
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnFilterComplete ( oSettings, oInput, iForce )
+	{
+		var oPrevSearch = oSettings.oPreviousSearch;
+		var aoPrevSearch = oSettings.aoPreSearchCols;
+		var fnSaveFilter = function ( oFilter ) {
+			/* Save the filtering values */
+			oPrevSearch.sSearch = oFilter.sSearch;
+			oPrevSearch.bRegex = oFilter.bRegex;
+			oPrevSearch.bSmart = oFilter.bSmart;
+			oPrevSearch.bCaseInsensitive = oFilter.bCaseInsensitive;
+		};
+		var fnRegex = function ( o ) {
+			// Backwards compatibility with the bEscapeRegex option
+			return o.bEscapeRegex !== undefined ? !o.bEscapeRegex : o.bRegex;
+		};
+	
+		// Resolve any column types that are unknown due to addition or invalidation
+		// @todo As per sort - can this be moved into an event handler?
+		_fnColumnTypes( oSettings );
+	
+		/* In server-side processing all filtering is done by the server, so no point hanging around here */
+		if ( _fnDataSource( oSettings ) != 'ssp' )
+		{
+			/* Global filter */
+			_fnFilter( oSettings, oInput.sSearch, iForce, fnRegex(oInput), oInput.bSmart, oInput.bCaseInsensitive );
+			fnSaveFilter( oInput );
+	
+			/* Now do the individual column filter */
+			for ( var i=0 ; i<aoPrevSearch.length ; i++ )
+			{
+				_fnFilterColumn( oSettings, aoPrevSearch[i].sSearch, i, fnRegex(aoPrevSearch[i]),
+					aoPrevSearch[i].bSmart, aoPrevSearch[i].bCaseInsensitive );
+			}
+	
+			/* Custom filtering */
+			_fnFilterCustom( oSettings );
+		}
+		else
+		{
+			fnSaveFilter( oInput );
+		}
+	
+		/* Tell the draw function we have been filtering */
+		oSettings.bFiltered = true;
+		_fnCallbackFire( oSettings, null, 'search', [oSettings] );
+	}
+	
+	
+	/**
+	 * Apply custom filtering functions
+	 *  @param {object} oSettings dataTables settings object
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnFilterCustom( settings )
+	{
+		var filters = DataTable.ext.search;
+		var displayRows = settings.aiDisplay;
+		var row, rowIdx;
+	
+		for ( var i=0, ien=filters.length ; i<ien ; i++ ) {
+			var rows = [];
+	
+			// Loop over each row and see if it should be included
+			for ( var j=0, jen=displayRows.length ; j<jen ; j++ ) {
+				rowIdx = displayRows[ j ];
+				row = settings.aoData[ rowIdx ];
+	
+				if ( filters[i]( settings, row._aFilterData, rowIdx, row._aData, j ) ) {
+					rows.push( rowIdx );
+				}
+			}
+	
+			// So the array reference doesn't break set the results into the
+			// existing array
+			displayRows.length = 0;
+			$.merge( displayRows, rows );
+		}
+	}
+	
+	
+	/**
+	 * Filter the table on a per-column basis
+	 *  @param {object} oSettings dataTables settings object
+	 *  @param {string} sInput string to filter on
+	 *  @param {int} iColumn column to filter
+	 *  @param {bool} bRegex treat search string as a regular expression or not
+	 *  @param {bool} bSmart use smart filtering or not
+	 *  @param {bool} bCaseInsensitive Do case insenstive matching or not
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnFilterColumn ( settings, searchStr, colIdx, regex, smart, caseInsensitive )
+	{
+		if ( searchStr === '' ) {
+			return;
+		}
+	
+		var data;
+		var out = [];
+		var display = settings.aiDisplay;
+		var rpSearch = _fnFilterCreateSearch( searchStr, regex, smart, caseInsensitive );
+	
+		for ( var i=0 ; i<display.length ; i++ ) {
+			data = settings.aoData[ display[i] ]._aFilterData[ colIdx ];
+	
+			if ( rpSearch.test( data ) ) {
+				out.push( display[i] );
+			}
+		}
+	
+		settings.aiDisplay = out;
+	}
+	
+	
+	/**
+	 * Filter the data table based on user input and draw the table
+	 *  @param {object} settings dataTables settings object
+	 *  @param {string} input string to filter on
+	 *  @param {int} force optional - force a research of the master array (1) or not (undefined or 0)
+	 *  @param {bool} regex treat as a regular expression or not
+	 *  @param {bool} smart perform smart filtering or not
+	 *  @param {bool} caseInsensitive Do case insenstive matching or not
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnFilter( settings, input, force, regex, smart, caseInsensitive )
+	{
+		var rpSearch = _fnFilterCreateSearch( input, regex, smart, caseInsensitive );
+		var prevSearch = settings.oPreviousSearch.sSearch;
+		var displayMaster = settings.aiDisplayMaster;
+		var display, invalidated, i;
+		var filtered = [];
+	
+		// Need to take account of custom filtering functions - always filter
+		if ( DataTable.ext.search.length !== 0 ) {
+			force = true;
+		}
+	
+		// Check if any of the rows were invalidated
+		invalidated = _fnFilterData( settings );
+	
+		// If the input is blank - we just want the full data set
+		if ( input.length <= 0 ) {
+			settings.aiDisplay = displayMaster.slice();
+		}
+		else {
+			// New search - start from the master array
+			if ( invalidated ||
+				 force ||
+				 prevSearch.length > input.length ||
+				 input.indexOf(prevSearch) !== 0 ||
+				 settings.bSorted // On resort, the display master needs to be
+				                  // re-filtered since indexes will have changed
+			) {
+				settings.aiDisplay = displayMaster.slice();
+			}
+	
+			// Search the display array
+			display = settings.aiDisplay;
+	
+			for ( i=0 ; i<display.length ; i++ ) {
+				if ( rpSearch.test( settings.aoData[ display[i] ]._sFilterRow ) ) {
+					filtered.push( display[i] );
+				}
+			}
+	
+			settings.aiDisplay = filtered;
+		}
+	}
+	
+	
+	/**
+	 * Build a regular expression object suitable for searching a table
+	 *  @param {string} sSearch string to search for
+	 *  @param {bool} bRegex treat as a regular expression or not
+	 *  @param {bool} bSmart perform smart filtering or not
+	 *  @param {bool} bCaseInsensitive Do case insensitive matching or not
+	 *  @returns {RegExp} constructed object
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnFilterCreateSearch( search, regex, smart, caseInsensitive )
+	{
+		search = regex ?
+			search :
+			_fnEscapeRegex( search );
+		
+		if ( smart ) {
+			/* For smart filtering we want to allow the search to work regardless of
+			 * word order. We also want double quoted text to be preserved, so word
+			 * order is important - a la google. So this is what we want to
+			 * generate:
+			 * 
+			 * ^(?=.*?\bone\b)(?=.*?\btwo three\b)(?=.*?\bfour\b).*$
+			 */
+			var a = $.map( search.match( /"[^"]+"|[^ ]+/g ) || [''], function ( word ) {
+				if ( word.charAt(0) === '"' ) {
+					var m = word.match( /^"(.*)"$/ );
+					word = m ? m[1] : word;
+				}
+	
+				return word.replace('"', '');
+			} );
+	
+			search = '^(?=.*?'+a.join( ')(?=.*?' )+').*$';
+		}
+	
+		return new RegExp( search, caseInsensitive ? 'i' : '' );
+	}
+	
+	
+	/**
+	 * Escape a string such that it can be used in a regular expression
+	 *  @param {string} sVal string to escape
+	 *  @returns {string} escaped string
+	 *  @memberof DataTable#oApi
+	 */
+	var _fnEscapeRegex = DataTable.util.escapeRegex;
+	
+	var __filter_div = $('<div>')[0];
+	var __filter_div_textContent = __filter_div.textContent !== undefined;
+	
+	// Update the filtering data for each row if needed (by invalidation or first run)
+	function _fnFilterData ( settings )
+	{
+		var columns = settings.aoColumns;
+		var column;
+		var i, j, ien, jen, filterData, cellData, row;
+		var fomatters = DataTable.ext.type.search;
+		var wasInvalidated = false;
+	
+		for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
+			row = settings.aoData[i];
+	
+			if ( ! row._aFilterData ) {
+				filterData = [];
+	
+				for ( j=0, jen=columns.length ; j<jen ; j++ ) {
+					column = columns[j];
+	
+					if ( column.bSearchable ) {
+						cellData = _fnGetCellData( settings, i, j, 'filter' );
+	
+						if ( fomatters[ column.sType ] ) {
+							cellData = fomatters[ column.sType ]( cellData );
+						}
+	
+						// Search in DataTables 1.10 is string based. In 1.11 this
+						// should be altered to also allow strict type checking.
+						if ( cellData === null ) {
+							cellData = '';
+						}
+	
+						if ( typeof cellData !== 'string' && cellData.toString ) {
+							cellData = cellData.toString();
+						}
+					}
+					else {
+						cellData = '';
+					}
+	
+					// If it looks like there is an HTML entity in the string,
+					// attempt to decode it so sorting works as expected. Note that
+					// we could use a single line of jQuery to do this, but the DOM
+					// method used here is much faster http://jsperf.com/html-decode
+					if ( cellData.indexOf && cellData.indexOf('&') !== -1 ) {
+						__filter_div.innerHTML = cellData;
+						cellData = __filter_div_textContent ?
+							__filter_div.textContent :
+							__filter_div.innerText;
+					}
+	
+					if ( cellData.replace ) {
+						cellData = cellData.replace(/[\r\n]/g, '');
+					}
+	
+					filterData.push( cellData );
+				}
+	
+				row._aFilterData = filterData;
+				row._sFilterRow = filterData.join('  ');
+				wasInvalidated = true;
+			}
+		}
+	
+		return wasInvalidated;
+	}
+	
+	
+	/**
+	 * Convert from the internal Hungarian notation to camelCase for external
+	 * interaction
+	 *  @param {object} obj Object to convert
+	 *  @returns {object} Inverted object
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnSearchToCamel ( obj )
+	{
+		return {
+			search:          obj.sSearch,
+			smart:           obj.bSmart,
+			regex:           obj.bRegex,
+			caseInsensitive: obj.bCaseInsensitive
+		};
+	}
+	
+	
+	
+	/**
+	 * Convert from camelCase notation to the internal Hungarian. We could use the
+	 * Hungarian convert function here, but this is cleaner
+	 *  @param {object} obj Object to convert
+	 *  @returns {object} Inverted object
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnSearchToHung ( obj )
+	{
+		return {
+			sSearch:          obj.search,
+			bSmart:           obj.smart,
+			bRegex:           obj.regex,
+			bCaseInsensitive: obj.caseInsensitive
+		};
+	}
+	
+	/**
+	 * Generate the node required for the info display
+	 *  @param {object} oSettings dataTables settings object
+	 *  @returns {node} Information element
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnFeatureHtmlInfo ( settings )
+	{
+		var
+			tid = settings.sTableId,
+			nodes = settings.aanFeatures.i,
+			n = $('<div/>', {
+				'class': settings.oClasses.sInfo,
+				'id': ! nodes ? tid+'_info' : null
+			} );
+	
+		if ( ! nodes ) {
+			// Update display on each draw
+			settings.aoDrawCallback.push( {
+				"fn": _fnUpdateInfo,
+				"sName": "information"
+			} );
+	
+			n
+				.attr( 'role', 'status' )
+				.attr( 'aria-live', 'polite' );
+	
+			// Table is described by our info div
+			$(settings.nTable).attr( 'aria-describedby', tid+'_info' );
+		}
+	
+		return n[0];
+	}
+	
+	
+	/**
+	 * Update the information elements in the display
+	 *  @param {object} settings dataTables settings object
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnUpdateInfo ( settings )
+	{
+		/* Show information about the table */
+		var nodes = settings.aanFeatures.i;
+		if ( nodes.length === 0 ) {
+			return;
+		}
+	
+		var
+			lang  = settings.oLanguage,
+			start = settings._iDisplayStart+1,
+			end   = settings.fnDisplayEnd(),
+			max   = settings.fnRecordsTotal(),
+			total = settings.fnRecordsDisplay(),
+			out   = total ?
+				lang.sInfo :
+				lang.sInfoEmpty;
+	
+		if ( total !== max ) {
+			/* Record set after filtering */
+			out += ' ' + lang.sInfoFiltered;
+		}
+	
+		// Convert the macros
+		out += lang.sInfoPostFix;
+		out = _fnInfoMacros( settings, out );
+	
+		var callback = lang.fnInfoCallback;
+		if ( callback !== null ) {
+			out = callback.call( settings.oInstance,
+				settings, start, end, max, total, out
+			);
+		}
+	
+		$(nodes).html( out );
+	}
+	
+	
+	function _fnInfoMacros ( settings, str )
+	{
+		// When infinite scrolling, we are always starting at 1. _iDisplayStart is used only
+		// internally
+		var
+			formatter  = settings.fnFormatNumber,
+			start      = settings._iDisplayStart+1,
+			len        = settings._iDisplayLength,
+			vis        = settings.fnRecordsDisplay(),
+			all        = len === -1;
+	
+		return str.
+			replace(/_START_/g, formatter.call( settings, start ) ).
+			replace(/_END_/g,   formatter.call( settings, settings.fnDisplayEnd() ) ).
+			replace(/_MAX_/g,   formatter.call( settings, settings.fnRecordsTotal() ) ).
+			replace(/_TOTAL_/g, formatter.call( settings, vis ) ).
+			replace(/_PAGE_/g,  formatter.call( settings, all ? 1 : Math.ceil( start / len ) ) ).
+			replace(/_PAGES_/g, formatter.call( settings, all ? 1 : Math.ceil( vis / len ) ) );
+	}
+	
+	
+	
+	/**
+	 * Draw the table for the first time, adding all required features
+	 *  @param {object} settings dataTables settings object
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnInitialise ( settings )
+	{
+		var i, iLen, iAjaxStart=settings.iInitDisplayStart;
+		var columns = settings.aoColumns, column;
+		var features = settings.oFeatures;
+		var deferLoading = settings.bDeferLoading; // value modified by the draw
+	
+		/* Ensure that the table data is fully initialised */
+		if ( ! settings.bInitialised ) {
+			setTimeout( function(){ _fnInitialise( settings ); }, 200 );
+			return;
+		}
+	
+		/* Show the display HTML options */
+		_fnAddOptionsHtml( settings );
+	
+		/* Build and draw the header / footer for the table */
+		_fnBuildHead( settings );
+		_fnDrawHead( settings, settings.aoHeader );
+		_fnDrawHead( settings, settings.aoFooter );
+	
+		/* Okay to show that something is going on now */
+		_fnProcessingDisplay( settings, true );
+	
+		/* Calculate sizes for columns */
+		if ( features.bAutoWidth ) {
+			_fnCalculateColumnWidths( settings );
+		}
+	
+		for ( i=0, iLen=columns.length ; i<iLen ; i++ ) {
+			column = columns[i];
+	
+			if ( column.sWidth ) {
+				column.nTh.style.width = _fnStringToCss( column.sWidth );
+			}
+		}
+	
+		_fnCallbackFire( settings, null, 'preInit', [settings] );
+	
+		// If there is default sorting required - let's do it. The sort function
+		// will do the drawing for us. Otherwise we draw the table regardless of the
+		// Ajax source - this allows the table to look initialised for Ajax sourcing
+		// data (show 'loading' message possibly)
+		_fnReDraw( settings );
+	
+		// Server-side processing init complete is done by _fnAjaxUpdateDraw
+		var dataSrc = _fnDataSource( settings );
+		if ( dataSrc != 'ssp' || deferLoading ) {
+			// if there is an ajax source load the data
+			if ( dataSrc == 'ajax' ) {
+				_fnBuildAjax( settings, [], function(json) {
+					var aData = _fnAjaxDataSrc( settings, json );
+	
+					// Got the data - add it to the table
+					for ( i=0 ; i<aData.length ; i++ ) {
+						_fnAddData( settings, aData[i] );
+					}
+	
+					// Reset the init display for cookie saving. We've already done
+					// a filter, and therefore cleared it before. So we need to make
+					// it appear 'fresh'
+					settings.iInitDisplayStart = iAjaxStart;
+	
+					_fnReDraw( settings );
+	
+					_fnProcessingDisplay( settings, false );
+					_fnInitComplete( settings, json );
+				}, settings );
+			}
+			else {
+				_fnProcessingDisplay( settings, false );
+				_fnInitComplete( settings );
+			}
+		}
+	}
+	
+	
+	/**
+	 * Draw the table for the first time, adding all required features
+	 *  @param {object} oSettings dataTables settings object
+	 *  @param {object} [json] JSON from the server that completed the table, if using Ajax source
+	 *    with client-side processing (optional)
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnInitComplete ( settings, json )
+	{
+		settings._bInitComplete = true;
+	
+		// When data was added after the initialisation (data or Ajax) we need to
+		// calculate the column sizing
+		if ( json || settings.oInit.aaData ) {
+			_fnAdjustColumnSizing( settings );
+		}
+	
+		_fnCallbackFire( settings, null, 'plugin-init', [settings, json] );
+		_fnCallbackFire( settings, 'aoInitComplete', 'init', [settings, json] );
+	}
+	
+	
+	function _fnLengthChange ( settings, val )
+	{
+		var len = parseInt( val, 10 );
+		settings._iDisplayLength = len;
+	
+		_fnLengthOverflow( settings );
+	
+		// Fire length change event
+		_fnCallbackFire( settings, null, 'length', [settings, len] );
+	}
+	
+	
+	/**
+	 * Generate the node required for user display length changing
+	 *  @param {object} settings dataTables settings object
+	 *  @returns {node} Display length feature node
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnFeatureHtmlLength ( settings )
+	{
+		var
+			classes  = settings.oClasses,
+			tableId  = settings.sTableId,
+			menu     = settings.aLengthMenu,
+			d2       = $.isArray( menu[0] ),
+			lengths  = d2 ? menu[0] : menu,
+			language = d2 ? menu[1] : menu;
+	
+		var select = $('<select/>', {
+			'name':          tableId+'_length',
+			'aria-controls': tableId,
+			'class':         classes.sLengthSelect
+		} );
+	
+		for ( var i=0, ien=lengths.length ; i<ien ; i++ ) {
+			select[0][ i ] = new Option( language[i], lengths[i] );
+		}
+	
+		var div = $('<div><label/></div>').addClass( classes.sLength );
+		if ( ! settings.aanFeatures.l ) {
+			div[0].id = tableId+'_length';
+		}
+	
+		div.children().append(
+			settings.oLanguage.sLengthMenu.replace( '_MENU_', select[0].outerHTML )
+		);
+	
+		// Can't use `select` variable as user might provide their own and the
+		// reference is broken by the use of outerHTML
+		$('select', div)
+			.val( settings._iDisplayLength )
+			.on( 'change.DT', function(e) {
+				_fnLengthChange( settings, $(this).val() );
+				_fnDraw( settings );
+			} );
+	
+		// Update node value whenever anything changes the table's length
+		$(settings.nTable).on( 'length.dt.DT', function (e, s, len) {
+			if ( settings === s ) {
+				$('select', div).val( len );
+			}
+		} );
+	
+		return div[0];
+	}
+	
+	
+	
+	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+	 * Note that most of the paging logic is done in
+	 * DataTable.ext.pager
+	 */
+	
+	/**
+	 * Generate the node required for default pagination
+	 *  @param {object} oSettings dataTables settings object
+	 *  @returns {node} Pagination feature node
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnFeatureHtmlPaginate ( settings )
+	{
+		var
+			type   = settings.sPaginationType,
+			plugin = DataTable.ext.pager[ type ],
+			modern = typeof plugin === 'function',
+			redraw = function( settings ) {
+				_fnDraw( settings );
+			},
+			node = $('<div/>').addClass( settings.oClasses.sPaging + type )[0],
+			features = settings.aanFeatures;
+	
+		if ( ! modern ) {
+			plugin.fnInit( settings, node, redraw );
+		}
+	
+		/* Add a draw callback for the pagination on first instance, to update the paging display */
+		if ( ! features.p )
+		{
+			node.id = settings.sTableId+'_paginate';
+	
+			settings.aoDrawCallback.push( {
+				"fn": function( settings ) {
+					if ( modern ) {
+						var
+							start      = settings._iDisplayStart,
+							len        = settings._iDisplayLength,
+							visRecords = settings.fnRecordsDisplay(),
+							all        = len === -1,
+							page = all ? 0 : Math.ceil( start / len ),
+							pages = all ? 1 : Math.ceil( visRecords / len ),
+							buttons = plugin(page, pages),
+							i, ien;
+	
+						for ( i=0, ien=features.p.length ; i<ien ; i++ ) {
+							_fnRenderer( settings, 'pageButton' )(
+								settings, features.p[i], i, buttons, page, pages
+							);
+						}
+					}
+					else {
+						plugin.fnUpdate( settings, redraw );
+					}
+				},
+				"sName": "pagination"
+			} );
+		}
+	
+		return node;
+	}
+	
+	
+	/**
+	 * Alter the display settings to change the page
+	 *  @param {object} settings DataTables settings object
+	 *  @param {string|int} action Paging action to take: "first", "previous",
+	 *    "next" or "last" or page number to jump to (integer)
+	 *  @param [bool] redraw Automatically draw the update or not
+	 *  @returns {bool} true page has changed, false - no change
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnPageChange ( settings, action, redraw )
+	{
+		var
+			start     = settings._iDisplayStart,
+			len       = settings._iDisplayLength,
+			records   = settings.fnRecordsDisplay();
+	
+		if ( records === 0 || len === -1 )
+		{
+			start = 0;
+		}
+		else if ( typeof action === "number" )
+		{
+			start = action * len;
+	
+			if ( start > records )
+			{
+				start = 0;
+			}
+		}
+		else if ( action == "first" )
+		{
+			start = 0;
+		}
+		else if ( action == "previous" )
+		{
+			start = len >= 0 ?
+				start - len :
+				0;
+	
+			if ( start < 0 )
+			{
+			  start = 0;
+			}
+		}
+		else if ( action == "next" )
+		{
+			if ( start + len < records )
+			{
+				start += len;
+			}
+		}
+		else if ( action == "last" )
+		{
+			start = Math.floor( (records-1) / len) * len;
+		}
+		else
+		{
+			_fnLog( settings, 0, "Unknown paging action: "+action, 5 );
+		}
+	
+		var changed = settings._iDisplayStart !== start;
+		settings._iDisplayStart = start;
+	
+		if ( changed ) {
+			_fnCallbackFire( settings, null, 'page', [settings] );
+	
+			if ( redraw ) {
+				_fnDraw( settings );
+			}
+		}
+	
+		return changed;
+	}
+	
+	
+	
+	/**
+	 * Generate the node required for the processing node
+	 *  @param {object} settings dataTables settings object
+	 *  @returns {node} Processing element
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnFeatureHtmlProcessing ( settings )
+	{
+		return $('<div/>', {
+				'id': ! settings.aanFeatures.r ? settings.sTableId+'_processing' : null,
+				'class': settings.oClasses.sProcessing
+			} )
+			.html( settings.oLanguage.sProcessing )
+			.insertBefore( settings.nTable )[0];
+	}
+	
+	
+	/**
+	 * Display or hide the processing indicator
+	 *  @param {object} settings dataTables settings object
+	 *  @param {bool} show Show the processing indicator (true) or not (false)
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnProcessingDisplay ( settings, show )
+	{
+		if ( settings.oFeatures.bProcessing ) {
+			$(settings.aanFeatures.r).css( 'display', show ? 'block' : 'none' );
+		}
+	
+		_fnCallbackFire( settings, null, 'processing', [settings, show] );
+	}
+	
+	/**
+	 * Add any control elements for the table - specifically scrolling
+	 *  @param {object} settings dataTables settings object
+	 *  @returns {node} Node to add to the DOM
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnFeatureHtmlTable ( settings )
+	{
+		var table = $(settings.nTable);
+	
+		// Add the ARIA grid role to the table
+		table.attr( 'role', 'grid' );
+	
+		// Scrolling from here on in
+		var scroll = settings.oScroll;
+	
+		if ( scroll.sX === '' && scroll.sY === '' ) {
+			return settings.nTable;
+		}
+	
+		var scrollX = scroll.sX;
+		var scrollY = scroll.sY;
+		var classes = settings.oClasses;
+		var caption = table.children('caption');
+		var captionSide = caption.length ? caption[0]._captionSide : null;
+		var headerClone = $( table[0].cloneNode(false) );
+		var footerClone = $( table[0].cloneNode(false) );
+		var footer = table.children('tfoot');
+		var _div = '<div/>';
+		var size = function ( s ) {
+			return !s ? null : _fnStringToCss( s );
+		};
+	
+		if ( ! footer.length ) {
+			footer = null;
+		}
+	
+		/*
+		 * The HTML structure that we want to generate in this function is:
+		 *  div - scroller
+		 *    div - scroll head
+		 *      div - scroll head inner
+		 *        table - scroll head table
+		 *          thead - thead
+		 *    div - scroll body
+		 *      table - table (master table)
+		 *        thead - thead clone for sizing
+		 *        tbody - tbody
+		 *    div - scroll foot
+		 *      div - scroll foot inner
+		 *        table - scroll foot table
+		 *          tfoot - tfoot
+		 */
+		var scroller = $( _div, { 'class': classes.sScrollWrapper } )
+			.append(
+				$(_div, { 'class': classes.sScrollHead } )
+					.css( {
+						overflow: 'hidden',
+						position: 'relative',
+						border: 0,
+						width: scrollX ? size(scrollX) : '100%'
+					} )
+					.append(
+						$(_div, { 'class': classes.sScrollHeadInner } )
+							.css( {
+								'box-sizing': 'content-box',
+								width: scroll.sXInner || '100%'
+							} )
+							.append(
+								headerClone
+									.removeAttr('id')
+									.css( 'margin-left', 0 )
+									.append( captionSide === 'top' ? caption : null )
+									.append(
+										table.children('thead')
+									)
+							)
+					)
+			)
+			.append(
+				$(_div, { 'class': classes.sScrollBody } )
+					.css( {
+						position: 'relative',
+						overflow: 'auto',
+						width: size( scrollX )
+					} )
+					.append( table )
+			);
+	
+		if ( footer ) {
+			scroller.append(
+				$(_div, { 'class': classes.sScrollFoot } )
+					.css( {
+						overflow: 'hidden',
+						border: 0,
+						width: scrollX ? size(scrollX) : '100%'
+					} )
+					.append(
+						$(_div, { 'class': classes.sScrollFootInner } )
+							.append(
+								footerClone
+									.removeAttr('id')
+									.css( 'margin-left', 0 )
+									.append( captionSide === 'bottom' ? caption : null )
+									.append(
+										table.children('tfoot')
+									)
+							)
+					)
+			);
+		}
+	
+		var children = scroller.children();
+		var scrollHead = children[0];
+		var scrollBody = children[1];
+		var scrollFoot = footer ? children[2] : null;
+	
+		// When the body is scrolled, then we also want to scroll the headers
+		if ( scrollX ) {
+			$(scrollBody).on( 'scroll.DT', function (e) {
+				var scrollLeft = this.scrollLeft;
+	
+				scrollHead.scrollLeft = scrollLeft;
+	
+				if ( footer ) {
+					scrollFoot.scrollLeft = scrollLeft;
+				}
+			} );
+		}
+	
+		$(scrollBody).css(
+			scrollY && scroll.bCollapse ? 'max-height' : 'height', 
+			scrollY
+		);
+	
+		settings.nScrollHead = scrollHead;
+		settings.nScrollBody = scrollBody;
+		settings.nScrollFoot = scrollFoot;
+	
+		// On redraw - align columns
+		settings.aoDrawCallback.push( {
+			"fn": _fnScrollDraw,
+			"sName": "scrolling"
+		} );
+	
+		return scroller[0];
+	}
+	
+	
+	
+	/**
+	 * Update the header, footer and body tables for resizing - i.e. column
+	 * alignment.
+	 *
+	 * Welcome to the most horrible function DataTables. The process that this
+	 * function follows is basically:
+	 *   1. Re-create the table inside the scrolling div
+	 *   2. Take live measurements from the DOM
+	 *   3. Apply the measurements to align the columns
+	 *   4. Clean up
+	 *
+	 *  @param {object} settings dataTables settings object
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnScrollDraw ( settings )
+	{
+		// Given that this is such a monster function, a lot of variables are use
+		// to try and keep the minimised size as small as possible
+		var
+			scroll         = settings.oScroll,
+			scrollX        = scroll.sX,
+			scrollXInner   = scroll.sXInner,
+			scrollY        = scroll.sY,
+			barWidth       = scroll.iBarWidth,
+			divHeader      = $(settings.nScrollHead),
+			divHeaderStyle = divHeader[0].style,
+			divHeaderInner = divHeader.children('div'),
+			divHeaderInnerStyle = divHeaderInner[0].style,
+			divHeaderTable = divHeaderInner.children('table'),
+			divBodyEl      = settings.nScrollBody,
+			divBody        = $(divBodyEl),
+			divBodyStyle   = divBodyEl.style,
+			divFooter      = $(settings.nScrollFoot),
+			divFooterInner = divFooter.children('div'),
+			divFooterTable = divFooterInner.children('table'),
+			header         = $(settings.nTHead),
+			table          = $(settings.nTable),
+			tableEl        = table[0],
+			tableStyle     = tableEl.style,
+			footer         = settings.nTFoot ? $(settings.nTFoot) : null,
+			browser        = settings.oBrowser,
+			ie67           = browser.bScrollOversize,
+			dtHeaderCells  = _pluck( settings.aoColumns, 'nTh' ),
+			headerTrgEls, footerTrgEls,
+			headerSrcEls, footerSrcEls,
+			headerCopy, footerCopy,
+			headerWidths=[], footerWidths=[],
+			headerContent=[], footerContent=[],
+			idx, correction, sanityWidth,
+			zeroOut = function(nSizer) {
+				var style = nSizer.style;
+				style.paddingTop = "0";
+				style.paddingBottom = "0";
+				style.borderTopWidth = "0";
+				style.borderBottomWidth = "0";
+				style.height = 0;
+			};
+	
+		// If the scrollbar visibility has changed from the last draw, we need to
+		// adjust the column sizes as the table width will have changed to account
+		// for the scrollbar
+		var scrollBarVis = divBodyEl.scrollHeight > divBodyEl.clientHeight;
+		
+		if ( settings.scrollBarVis !== scrollBarVis && settings.scrollBarVis !== undefined ) {
+			settings.scrollBarVis = scrollBarVis;
+			_fnAdjustColumnSizing( settings );
+			return; // adjust column sizing will call this function again
+		}
+		else {
+			settings.scrollBarVis = scrollBarVis;
+		}
+	
+		/*
+		 * 1. Re-create the table inside the scrolling div
+		 */
+	
+		// Remove the old minimised thead and tfoot elements in the inner table
+		table.children('thead, tfoot').remove();
+	
+		if ( footer ) {
+			footerCopy = footer.clone().prependTo( table );
+			footerTrgEls = footer.find('tr'); // the original tfoot is in its own table and must be sized
+			footerSrcEls = footerCopy.find('tr');
+		}
+	
+		// Clone the current header and footer elements and then place it into the inner table
+		headerCopy = header.clone().prependTo( table );
+		headerTrgEls = header.find('tr'); // original header is in its own table
+		headerSrcEls = headerCopy.find('tr');
+		headerCopy.find('th, td').removeAttr('tabindex');
+	
+	
+		/*
+		 * 2. Take live measurements from the DOM - do not alter the DOM itself!
+		 */
+	
+		// Remove old sizing and apply the calculated column widths
+		// Get the unique column headers in the newly created (cloned) header. We want to apply the
+		// calculated sizes to this header
+		if ( ! scrollX )
+		{
+			divBodyStyle.width = '100%';
+			divHeader[0].style.width = '100%';
+		}
+	
+		$.each( _fnGetUniqueThs( settings, headerCopy ), function ( i, el ) {
+			idx = _fnVisibleToColumnIndex( settings, i );
+			el.style.width = settings.aoColumns[idx].sWidth;
+		} );
+	
+		if ( footer ) {
+			_fnApplyToChildren( function(n) {
+				n.style.width = "";
+			}, footerSrcEls );
+		}
+	
+		// Size the table as a whole
+		sanityWidth = table.outerWidth();
+		if ( scrollX === "" ) {
+			// No x scrolling
+			tableStyle.width = "100%";
+	
+			// IE7 will make the width of the table when 100% include the scrollbar
+			// - which is shouldn't. When there is a scrollbar we need to take this
+			// into account.
+			if ( ie67 && (table.find('tbody').height() > divBodyEl.offsetHeight ||
+				divBody.css('overflow-y') == "scroll")
+			) {
+				tableStyle.width = _fnStringToCss( table.outerWidth() - barWidth);
+			}
+	
+			// Recalculate the sanity width
+			sanityWidth = table.outerWidth();
+		}
+		else if ( scrollXInner !== "" ) {
+			// legacy x scroll inner has been given - use it
+			tableStyle.width = _fnStringToCss(scrollXInner);
+	
+			// Recalculate the sanity width
+			sanityWidth = table.outerWidth();
+		}
+	
+		// Hidden header should have zero height, so remove padding and borders. Then
+		// set the width based on the real headers
+	
+		// Apply all styles in one pass
+		_fnApplyToChildren( zeroOut, headerSrcEls );
+	
+		// Read all widths in next pass
+		_fnApplyToChildren( function(nSizer) {
+			headerContent.push( nSizer.innerHTML );
+			headerWidths.push( _fnStringToCss( $(nSizer).css('width') ) );
+		}, headerSrcEls );
+	
+		// Apply all widths in final pass
+		_fnApplyToChildren( function(nToSize, i) {
+			// Only apply widths to the DataTables detected header cells - this
+			// prevents complex headers from having contradictory sizes applied
+			if ( $.inArray( nToSize, dtHeaderCells ) !== -1 ) {
+				nToSize.style.width = headerWidths[i];
+			}
+		}, headerTrgEls );
+	
+		$(headerSrcEls).height(0);
+	
+		/* Same again with the footer if we have one */
+		if ( footer )
+		{
+			_fnApplyToChildren( zeroOut, footerSrcEls );
+	
+			_fnApplyToChildren( function(nSizer) {
+				footerContent.push( nSizer.innerHTML );
+				footerWidths.push( _fnStringToCss( $(nSizer).css('width') ) );
+			}, footerSrcEls );
+	
+			_fnApplyToChildren( function(nToSize, i) {
+				nToSize.style.width = footerWidths[i];
+			}, footerTrgEls );
+	
+			$(footerSrcEls).height(0);
+		}
+	
+	
+		/*
+		 * 3. Apply the measurements
+		 */
+	
+		// "Hide" the header and footer that we used for the sizing. We need to keep
+		// the content of the cell so that the width applied to the header and body
+		// both match, but we want to hide it completely. We want to also fix their
+		// width to what they currently are
+		_fnApplyToChildren( function(nSizer, i) {
+			nSizer.innerHTML = '<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+headerContent[i]+'</div>';
+			nSizer.style.width = headerWidths[i];
+		}, headerSrcEls );
+	
+		if ( footer )
+		{
+			_fnApplyToChildren( function(nSizer, i) {
+				nSizer.innerHTML = '<div class="dataTables_sizing" style="height:0;overflow:hidden;">'+footerContent[i]+'</div>';
+				nSizer.style.width = footerWidths[i];
+			}, footerSrcEls );
+		}
+	
+		// Sanity check that the table is of a sensible width. If not then we are going to get
+		// misalignment - try to prevent this by not allowing the table to shrink below its min width
+		if ( table.outerWidth() < sanityWidth )
+		{
+			// The min width depends upon if we have a vertical scrollbar visible or not */
+			correction = ((divBodyEl.scrollHeight > divBodyEl.offsetHeight ||
+				divBody.css('overflow-y') == "scroll")) ?
+					sanityWidth+barWidth :
+					sanityWidth;
+	
+			// IE6/7 are a law unto themselves...
+			if ( ie67 && (divBodyEl.scrollHeight >
+				divBodyEl.offsetHeight || divBody.css('overflow-y') == "scroll")
+			) {
+				tableStyle.width = _fnStringToCss( correction-barWidth );
+			}
+	
+			// And give the user a warning that we've stopped the table getting too small
+			if ( scrollX === "" || scrollXInner !== "" ) {
+				_fnLog( settings, 1, 'Possible column misalignment', 6 );
+			}
+		}
+		else
+		{
+			correction = '100%';
+		}
+	
+		// Apply to the container elements
+		divBodyStyle.width = _fnStringToCss( correction );
+		divHeaderStyle.width = _fnStringToCss( correction );
+	
+		if ( footer ) {
+			settings.nScrollFoot.style.width = _fnStringToCss( correction );
+		}
+	
+	
+		/*
+		 * 4. Clean up
+		 */
+		if ( ! scrollY ) {
+			/* IE7< puts a vertical scrollbar in place (when it shouldn't be) due to subtracting
+			 * the scrollbar height from the visible display, rather than adding it on. We need to
+			 * set the height in order to sort this. Don't want to do it in any other browsers.
+			 */
+			if ( ie67 ) {
+				divBodyStyle.height = _fnStringToCss( tableEl.offsetHeight+barWidth );
+			}
+		}
+	
+		/* Finally set the width's of the header and footer tables */
+		var iOuterWidth = table.outerWidth();
+		divHeaderTable[0].style.width = _fnStringToCss( iOuterWidth );
+		divHeaderInnerStyle.width = _fnStringToCss( iOuterWidth );
+	
+		// Figure out if there are scrollbar present - if so then we need a the header and footer to
+		// provide a bit more space to allow "overflow" scrolling (i.e. past the scrollbar)
+		var bScrolling = table.height() > divBodyEl.clientHeight || divBody.css('overflow-y') == "scroll";
+		var padding = 'padding' + (browser.bScrollbarLeft ? 'Left' : 'Right' );
+		divHeaderInnerStyle[ padding ] = bScrolling ? barWidth+"px" : "0px";
+	
+		if ( footer ) {
+			divFooterTable[0].style.width = _fnStringToCss( iOuterWidth );
+			divFooterInner[0].style.width = _fnStringToCss( iOuterWidth );
+			divFooterInner[0].style[padding] = bScrolling ? barWidth+"px" : "0px";
+		}
+	
+		// Correct DOM ordering for colgroup - comes before the thead
+		table.children('colgroup').insertBefore( table.children('thead') );
+	
+		/* Adjust the position of the header in case we loose the y-scrollbar */
+		divBody.scroll();
+	
+		// If sorting or filtering has occurred, jump the scrolling back to the top
+		// only if we aren't holding the position
+		if ( (settings.bSorted || settings.bFiltered) && ! settings._drawHold ) {
+			divBodyEl.scrollTop = 0;
+		}
+	}
+	
+	
+	
+	/**
+	 * Apply a given function to the display child nodes of an element array (typically
+	 * TD children of TR rows
+	 *  @param {function} fn Method to apply to the objects
+	 *  @param array {nodes} an1 List of elements to look through for display children
+	 *  @param array {nodes} an2 Another list (identical structure to the first) - optional
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnApplyToChildren( fn, an1, an2 )
+	{
+		var index=0, i=0, iLen=an1.length;
+		var nNode1, nNode2;
+	
+		while ( i < iLen ) {
+			nNode1 = an1[i].firstChild;
+			nNode2 = an2 ? an2[i].firstChild : null;
+	
+			while ( nNode1 ) {
+				if ( nNode1.nodeType === 1 ) {
+					if ( an2 ) {
+						fn( nNode1, nNode2, index );
+					}
+					else {
+						fn( nNode1, index );
+					}
+	
+					index++;
+				}
+	
+				nNode1 = nNode1.nextSibling;
+				nNode2 = an2 ? nNode2.nextSibling : null;
+			}
+	
+			i++;
+		}
+	}
+	
+	
+	
+	var __re_html_remove = /<.*?>/g;
+	
+	
+	/**
+	 * Calculate the width of columns for the table
+	 *  @param {object} oSettings dataTables settings object
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnCalculateColumnWidths ( oSettings )
+	{
+		var
+			table = oSettings.nTable,
+			columns = oSettings.aoColumns,
+			scroll = oSettings.oScroll,
+			scrollY = scroll.sY,
+			scrollX = scroll.sX,
+			scrollXInner = scroll.sXInner,
+			columnCount = columns.length,
+			visibleColumns = _fnGetColumns( oSettings, 'bVisible' ),
+			headerCells = $('th', oSettings.nTHead),
+			tableWidthAttr = table.getAttribute('width'), // from DOM element
+			tableContainer = table.parentNode,
+			userInputs = false,
+			i, column, columnIdx, width, outerWidth,
+			browser = oSettings.oBrowser,
+			ie67 = browser.bScrollOversize;
+	
+		var styleWidth = table.style.width;
+		if ( styleWidth && styleWidth.indexOf('%') !== -1 ) {
+			tableWidthAttr = styleWidth;
+		}
+	
+		/* Convert any user input sizes into pixel sizes */
+		for ( i=0 ; i<visibleColumns.length ; i++ ) {
+			column = columns[ visibleColumns[i] ];
+	
+			if ( column.sWidth !== null ) {
+				column.sWidth = _fnConvertToWidth( column.sWidthOrig, tableContainer );
+	
+				userInputs = true;
+			}
+		}
+	
+		/* If the number of columns in the DOM equals the number that we have to
+		 * process in DataTables, then we can use the offsets that are created by
+		 * the web- browser. No custom sizes can be set in order for this to happen,
+		 * nor scrolling used
+		 */
+		if ( ie67 || ! userInputs && ! scrollX && ! scrollY &&
+		     columnCount == _fnVisbleColumns( oSettings ) &&
+		     columnCount == headerCells.length
+		) {
+			for ( i=0 ; i<columnCount ; i++ ) {
+				var colIdx = _fnVisibleToColumnIndex( oSettings, i );
+	
+				if ( colIdx !== null ) {
+					columns[ colIdx ].sWidth = _fnStringToCss( headerCells.eq(i).width() );
+				}
+			}
+		}
+		else
+		{
+			// Otherwise construct a single row, worst case, table with the widest
+			// node in the data, assign any user defined widths, then insert it into
+			// the DOM and allow the browser to do all the hard work of calculating
+			// table widths
+			var tmpTable = $(table).clone() // don't use cloneNode - IE8 will remove events on the main table
+				.css( 'visibility', 'hidden' )
+				.removeAttr( 'id' );
+	
+			// Clean up the table body
+			tmpTable.find('tbody tr').remove();
+			var tr = $('<tr/>').appendTo( tmpTable.find('tbody') );
+	
+			// Clone the table header and footer - we can't use the header / footer
+			// from the cloned table, since if scrolling is active, the table's
+			// real header and footer are contained in different table tags
+			tmpTable.find('thead, tfoot').remove();
+			tmpTable
+				.append( $(oSettings.nTHead).clone() )
+				.append( $(oSettings.nTFoot).clone() );
+	
+			// Remove any assigned widths from the footer (from scrolling)
+			tmpTable.find('tfoot th, tfoot td').css('width', '');
+	
+			// Apply custom sizing to the cloned header
+			headerCells = _fnGetUniqueThs( oSettings, tmpTable.find('thead')[0] );
+	
+			for ( i=0 ; i<visibleColumns.length ; i++ ) {
+				column = columns[ visibleColumns[i] ];
+	
+				headerCells[i].style.width = column.sWidthOrig !== null && column.sWidthOrig !== '' ?
+					_fnStringToCss( column.sWidthOrig ) :
+					'';
+	
+				// For scrollX we need to force the column width otherwise the
+				// browser will collapse it. If this width is smaller than the
+				// width the column requires, then it will have no effect
+				if ( column.sWidthOrig && scrollX ) {
+					$( headerCells[i] ).append( $('<div/>').css( {
+						width: column.sWidthOrig,
+						margin: 0,
+						padding: 0,
+						border: 0,
+						height: 1
+					} ) );
+				}
+			}
+	
+			// Find the widest cell for each column and put it into the table
+			if ( oSettings.aoData.length ) {
+				for ( i=0 ; i<visibleColumns.length ; i++ ) {
+					columnIdx = visibleColumns[i];
+					column = columns[ columnIdx ];
+	
+					$( _fnGetWidestNode( oSettings, columnIdx ) )
+						.clone( false )
+						.append( column.sContentPadding )
+						.appendTo( tr );
+				}
+			}
+	
+			// Tidy the temporary table - remove name attributes so there aren't
+			// duplicated in the dom (radio elements for example)
+			$('[name]', tmpTable).removeAttr('name');
+	
+			// Table has been built, attach to the document so we can work with it.
+			// A holding element is used, positioned at the top of the container
+			// with minimal height, so it has no effect on if the container scrolls
+			// or not. Otherwise it might trigger scrolling when it actually isn't
+			// needed
+			var holder = $('<div/>').css( scrollX || scrollY ?
+					{
+						position: 'absolute',
+						top: 0,
+						left: 0,
+						height: 1,
+						right: 0,
+						overflow: 'hidden'
+					} :
+					{}
+				)
+				.append( tmpTable )
+				.appendTo( tableContainer );
+	
+			// When scrolling (X or Y) we want to set the width of the table as 
+			// appropriate. However, when not scrolling leave the table width as it
+			// is. This results in slightly different, but I think correct behaviour
+			if ( scrollX && scrollXInner ) {
+				tmpTable.width( scrollXInner );
+			}
+			else if ( scrollX ) {
+				tmpTable.css( 'width', 'auto' );
+				tmpTable.removeAttr('width');
+	
+				// If there is no width attribute or style, then allow the table to
+				// collapse
+				if ( tmpTable.width() < tableContainer.clientWidth && tableWidthAttr ) {
+					tmpTable.width( tableContainer.clientWidth );
+				}
+			}
+			else if ( scrollY ) {
+				tmpTable.width( tableContainer.clientWidth );
+			}
+			else if ( tableWidthAttr ) {
+				tmpTable.width( tableWidthAttr );
+			}
+	
+			// Get the width of each column in the constructed table - we need to
+			// know the inner width (so it can be assigned to the other table's
+			// cells) and the outer width so we can calculate the full width of the
+			// table. This is safe since DataTables requires a unique cell for each
+			// column, but if ever a header can span multiple columns, this will
+			// need to be modified.
+			var total = 0;
+			for ( i=0 ; i<visibleColumns.length ; i++ ) {
+				var cell = $(headerCells[i]);
+				var border = cell.outerWidth() - cell.width();
+	
+				// Use getBounding... where possible (not IE8-) because it can give
+				// sub-pixel accuracy, which we then want to round up!
+				var bounding = browser.bBounding ?
+					Math.ceil( headerCells[i].getBoundingClientRect().width ) :
+					cell.outerWidth();
+	
+				// Total is tracked to remove any sub-pixel errors as the outerWidth
+				// of the table might not equal the total given here (IE!).
+				total += bounding;
+	
+				// Width for each column to use
+				columns[ visibleColumns[i] ].sWidth = _fnStringToCss( bounding - border );
+			}
+	
+			table.style.width = _fnStringToCss( total );
+	
+			// Finished with the table - ditch it
+			holder.remove();
+		}
+	
+		// If there is a width attr, we want to attach an event listener which
+		// allows the table sizing to automatically adjust when the window is
+		// resized. Use the width attr rather than CSS, since we can't know if the
+		// CSS is a relative value or absolute - DOM read is always px.
+		if ( tableWidthAttr ) {
+			table.style.width = _fnStringToCss( tableWidthAttr );
+		}
+	
+		if ( (tableWidthAttr || scrollX) && ! oSettings._reszEvt ) {
+			var bindResize = function () {
+				$(window).on('resize.DT-'+oSettings.sInstance, _fnThrottle( function () {
+					_fnAdjustColumnSizing( oSettings );
+				} ) );
+			};
+	
+			// IE6/7 will crash if we bind a resize event handler on page load.
+			// To be removed in 1.11 which drops IE6/7 support
+			if ( ie67 ) {
+				setTimeout( bindResize, 1000 );
+			}
+			else {
+				bindResize();
+			}
+	
+			oSettings._reszEvt = true;
+		}
+	}
+	
+	
+	/**
+	 * Throttle the calls to a function. Arguments and context are maintained for
+	 * the throttled function
+	 *  @param {function} fn Function to be called
+	 *  @param {int} [freq=200] call frequency in mS
+	 *  @returns {function} wrapped function
+	 *  @memberof DataTable#oApi
+	 */
+	var _fnThrottle = DataTable.util.throttle;
+	
+	
+	/**
+	 * Convert a CSS unit width to pixels (e.g. 2em)
+	 *  @param {string} width width to be converted
+	 *  @param {node} parent parent to get the with for (required for relative widths) - optional
+	 *  @returns {int} width in pixels
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnConvertToWidth ( width, parent )
+	{
+		if ( ! width ) {
+			return 0;
+		}
+	
+		var n = $('<div/>')
+			.css( 'width', _fnStringToCss( width ) )
+			.appendTo( parent || document.body );
+	
+		var val = n[0].offsetWidth;
+		n.remove();
+	
+		return val;
+	}
+	
+	
+	/**
+	 * Get the widest node
+	 *  @param {object} settings dataTables settings object
+	 *  @param {int} colIdx column of interest
+	 *  @returns {node} widest table node
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnGetWidestNode( settings, colIdx )
+	{
+		var idx = _fnGetMaxLenString( settings, colIdx );
+		if ( idx < 0 ) {
+			return null;
+		}
+	
+		var data = settings.aoData[ idx ];
+		return ! data.nTr ? // Might not have been created when deferred rendering
+			$('<td/>').html( _fnGetCellData( settings, idx, colIdx, 'display' ) )[0] :
+			data.anCells[ colIdx ];
+	}
+	
+	
+	/**
+	 * Get the maximum strlen for each data column
+	 *  @param {object} settings dataTables settings object
+	 *  @param {int} colIdx column of interest
+	 *  @returns {string} max string length for each column
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnGetMaxLenString( settings, colIdx )
+	{
+		var s, max=-1, maxIdx = -1;
+	
+		for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
+			s = _fnGetCellData( settings, i, colIdx, 'display' )+'';
+			s = s.replace( __re_html_remove, '' );
+			s = s.replace( /&nbsp;/g, ' ' );
+	
+			if ( s.length > max ) {
+				max = s.length;
+				maxIdx = i;
+			}
+		}
+	
+		return maxIdx;
+	}
+	
+	
+	/**
+	 * Append a CSS unit (only if required) to a string
+	 *  @param {string} value to css-ify
+	 *  @returns {string} value with css unit
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnStringToCss( s )
+	{
+		if ( s === null ) {
+			return '0px';
+		}
+	
+		if ( typeof s == 'number' ) {
+			return s < 0 ?
+				'0px' :
+				s+'px';
+		}
+	
+		// Check it has a unit character already
+		return s.match(/\d$/) ?
+			s+'px' :
+			s;
+	}
+	
+	
+	
+	function _fnSortFlatten ( settings )
+	{
+		var
+			i, iLen, k, kLen,
+			aSort = [],
+			aiOrig = [],
+			aoColumns = settings.aoColumns,
+			aDataSort, iCol, sType, srcCol,
+			fixed = settings.aaSortingFixed,
+			fixedObj = $.isPlainObject( fixed ),
+			nestedSort = [],
+			add = function ( a ) {
+				if ( a.length && ! $.isArray( a[0] ) ) {
+					// 1D array
+					nestedSort.push( a );
+				}
+				else {
+					// 2D array
+					$.merge( nestedSort, a );
+				}
+			};
+	
+		// Build the sort array, with pre-fix and post-fix options if they have been
+		// specified
+		if ( $.isArray( fixed ) ) {
+			add( fixed );
+		}
+	
+		if ( fixedObj && fixed.pre ) {
+			add( fixed.pre );
+		}
+	
+		add( settings.aaSorting );
+	
+		if (fixedObj && fixed.post ) {
+			add( fixed.post );
+		}
+	
+		for ( i=0 ; i<nestedSort.length ; i++ )
+		{
+			srcCol = nestedSort[i][0];
+			aDataSort = aoColumns[ srcCol ].aDataSort;
+	
+			for ( k=0, kLen=aDataSort.length ; k<kLen ; k++ )
+			{
+				iCol = aDataSort[k];
+				sType = aoColumns[ iCol ].sType || 'string';
+	
+				if ( nestedSort[i]._idx === undefined ) {
+					nestedSort[i]._idx = $.inArray( nestedSort[i][1], aoColumns[iCol].asSorting );
+				}
+	
+				aSort.push( {
+					src:       srcCol,
+					col:       iCol,
+					dir:       nestedSort[i][1],
+					index:     nestedSort[i]._idx,
+					type:      sType,
+					formatter: DataTable.ext.type.order[ sType+"-pre" ]
+				} );
+			}
+		}
+	
+		return aSort;
+	}
+	
+	/**
+	 * Change the order of the table
+	 *  @param {object} oSettings dataTables settings object
+	 *  @memberof DataTable#oApi
+	 *  @todo This really needs split up!
+	 */
+	function _fnSort ( oSettings )
+	{
+		var
+			i, ien, iLen, j, jLen, k, kLen,
+			sDataType, nTh,
+			aiOrig = [],
+			oExtSort = DataTable.ext.type.order,
+			aoData = oSettings.aoData,
+			aoColumns = oSettings.aoColumns,
+			aDataSort, data, iCol, sType, oSort,
+			formatters = 0,
+			sortCol,
+			displayMaster = oSettings.aiDisplayMaster,
+			aSort;
+	
+		// Resolve any column types that are unknown due to addition or invalidation
+		// @todo Can this be moved into a 'data-ready' handler which is called when
+		//   data is going to be used in the table?
+		_fnColumnTypes( oSettings );
+	
+		aSort = _fnSortFlatten( oSettings );
+	
+		for ( i=0, ien=aSort.length ; i<ien ; i++ ) {
+			sortCol = aSort[i];
+	
+			// Track if we can use the fast sort algorithm
+			if ( sortCol.formatter ) {
+				formatters++;
+			}
+	
+			// Load the data needed for the sort, for each cell
+			_fnSortData( oSettings, sortCol.col );
+		}
+	
+		/* No sorting required if server-side or no sorting array */
+		if ( _fnDataSource( oSettings ) != 'ssp' && aSort.length !== 0 )
+		{
+			// Create a value - key array of the current row positions such that we can use their
+			// current position during the sort, if values match, in order to perform stable sorting
+			for ( i=0, iLen=displayMaster.length ; i<iLen ; i++ ) {
+				aiOrig[ displayMaster[i] ] = i;
+			}
+	
+			/* Do the sort - here we want multi-column sorting based on a given data source (column)
+			 * and sorting function (from oSort) in a certain direction. It's reasonably complex to
+			 * follow on it's own, but this is what we want (example two column sorting):
+			 *  fnLocalSorting = function(a,b){
+			 *    var iTest;
+			 *    iTest = oSort['string-asc']('data11', 'data12');
+			 *      if (iTest !== 0)
+			 *        return iTest;
+			 *    iTest = oSort['numeric-desc']('data21', 'data22');
+			 *    if (iTest !== 0)
+			 *      return iTest;
+			 *    return oSort['numeric-asc']( aiOrig[a], aiOrig[b] );
+			 *  }
+			 * Basically we have a test for each sorting column, if the data in that column is equal,
+			 * test the next column. If all columns match, then we use a numeric sort on the row
+			 * positions in the original data array to provide a stable sort.
+			 *
+			 * Note - I know it seems excessive to have two sorting methods, but the first is around
+			 * 15% faster, so the second is only maintained for backwards compatibility with sorting
+			 * methods which do not have a pre-sort formatting function.
+			 */
+			if ( formatters === aSort.length ) {
+				// All sort types have formatting functions
+				displayMaster.sort( function ( a, b ) {
+					var
+						x, y, k, test, sort,
+						len=aSort.length,
+						dataA = aoData[a]._aSortData,
+						dataB = aoData[b]._aSortData;
+	
+					for ( k=0 ; k<len ; k++ ) {
+						sort = aSort[k];
+	
+						x = dataA[ sort.col ];
+						y = dataB[ sort.col ];
+	
+						test = x<y ? -1 : x>y ? 1 : 0;
+						if ( test !== 0 ) {
+							return sort.dir === 'asc' ? test : -test;
+						}
+					}
+	
+					x = aiOrig[a];
+					y = aiOrig[b];
+					return x<y ? -1 : x>y ? 1 : 0;
+				} );
+			}
+			else {
+				// Depreciated - remove in 1.11 (providing a plug-in option)
+				// Not all sort types have formatting methods, so we have to call their sorting
+				// methods.
+				displayMaster.sort( function ( a, b ) {
+					var
+						x, y, k, l, test, sort, fn,
+						len=aSort.length,
+						dataA = aoData[a]._aSortData,
+						dataB = aoData[b]._aSortData;
+	
+					for ( k=0 ; k<len ; k++ ) {
+						sort = aSort[k];
+	
+						x = dataA[ sort.col ];
+						y = dataB[ sort.col ];
+	
+						fn = oExtSort[ sort.type+"-"+sort.dir ] || oExtSort[ "string-"+sort.dir ];
+						test = fn( x, y );
+						if ( test !== 0 ) {
+							return test;
+						}
+					}
+	
+					x = aiOrig[a];
+					y = aiOrig[b];
+					return x<y ? -1 : x>y ? 1 : 0;
+				} );
+			}
+		}
+	
+		/* Tell the draw function that we have sorted the data */
+		oSettings.bSorted = true;
+	}
+	
+	
+	function _fnSortAria ( settings )
+	{
+		var label;
+		var nextSort;
+		var columns = settings.aoColumns;
+		var aSort = _fnSortFlatten( settings );
+		var oAria = settings.oLanguage.oAria;
+	
+		// ARIA attributes - need to loop all columns, to update all (removing old
+		// attributes as needed)
+		for ( var i=0, iLen=columns.length ; i<iLen ; i++ )
+		{
+			var col = columns[i];
+			var asSorting = col.asSorting;
+			var sTitle = col.sTitle.replace( /<.*?>/g, "" );
+			var th = col.nTh;
+	
+			// IE7 is throwing an error when setting these properties with jQuery's
+			// attr() and removeAttr() methods...
+			th.removeAttribute('aria-sort');
+	
+			/* In ARIA only the first sorting column can be marked as sorting - no multi-sort option */
+			if ( col.bSortable ) {
+				if ( aSort.length > 0 && aSort[0].col == i ) {
+					th.setAttribute('aria-sort', aSort[0].dir=="asc" ? "ascending" : "descending" );
+					nextSort = asSorting[ aSort[0].index+1 ] || asSorting[0];
+				}
+				else {
+					nextSort = asSorting[0];
+				}
+	
+				label = sTitle + ( nextSort === "asc" ?
+					oAria.sSortAscending :
+					oAria.sSortDescending
+				);
+			}
+			else {
+				label = sTitle;
+			}
+	
+			th.setAttribute('aria-label', label);
+		}
+	}
+	
+	
+	/**
+	 * Function to run on user sort request
+	 *  @param {object} settings dataTables settings object
+	 *  @param {node} attachTo node to attach the handler to
+	 *  @param {int} colIdx column sorting index
+	 *  @param {boolean} [append=false] Append the requested sort to the existing
+	 *    sort if true (i.e. multi-column sort)
+	 *  @param {function} [callback] callback function
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnSortListener ( settings, colIdx, append, callback )
+	{
+		var col = settings.aoColumns[ colIdx ];
+		var sorting = settings.aaSorting;
+		var asSorting = col.asSorting;
+		var nextSortIdx;
+		var next = function ( a, overflow ) {
+			var idx = a._idx;
+			if ( idx === undefined ) {
+				idx = $.inArray( a[1], asSorting );
+			}
+	
+			return idx+1 < asSorting.length ?
+				idx+1 :
+				overflow ?
+					null :
+					0;
+		};
+	
+		// Convert to 2D array if needed
+		if ( typeof sorting[0] === 'number' ) {
+			sorting = settings.aaSorting = [ sorting ];
+		}
+	
+		// If appending the sort then we are multi-column sorting
+		if ( append && settings.oFeatures.bSortMulti ) {
+			// Are we already doing some kind of sort on this column?
+			var sortIdx = $.inArray( colIdx, _pluck(sorting, '0') );
+	
+			if ( sortIdx !== -1 ) {
+				// Yes, modify the sort
+				nextSortIdx = next( sorting[sortIdx], true );
+	
+				if ( nextSortIdx === null && sorting.length === 1 ) {
+					nextSortIdx = 0; // can't remove sorting completely
+				}
+	
+				if ( nextSortIdx === null ) {
+					sorting.splice( sortIdx, 1 );
+				}
+				else {
+					sorting[sortIdx][1] = asSorting[ nextSortIdx ];
+					sorting[sortIdx]._idx = nextSortIdx;
+				}
+			}
+			else {
+				// No sort on this column yet
+				sorting.push( [ colIdx, asSorting[0], 0 ] );
+				sorting[sorting.length-1]._idx = 0;
+			}
+		}
+		else if ( sorting.length && sorting[0][0] == colIdx ) {
+			// Single column - already sorting on this column, modify the sort
+			nextSortIdx = next( sorting[0] );
+	
+			sorting.length = 1;
+			sorting[0][1] = asSorting[ nextSortIdx ];
+			sorting[0]._idx = nextSortIdx;
+		}
+		else {
+			// Single column - sort only on this column
+			sorting.length = 0;
+			sorting.push( [ colIdx, asSorting[0] ] );
+			sorting[0]._idx = 0;
+		}
+	
+		// Run the sort by calling a full redraw
+		_fnReDraw( settings );
+	
+		// callback used for async user interaction
+		if ( typeof callback == 'function' ) {
+			callback( settings );
+		}
+	}
+	
+	
+	/**
+	 * Attach a sort handler (click) to a node
+	 *  @param {object} settings dataTables settings object
+	 *  @param {node} attachTo node to attach the handler to
+	 *  @param {int} colIdx column sorting index
+	 *  @param {function} [callback] callback function
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnSortAttachListener ( settings, attachTo, colIdx, callback )
+	{
+		var col = settings.aoColumns[ colIdx ];
+	
+		_fnBindAction( attachTo, {}, function (e) {
+			/* If the column is not sortable - don't to anything */
+			if ( col.bSortable === false ) {
+				return;
+			}
+	
+			// If processing is enabled use a timeout to allow the processing
+			// display to be shown - otherwise to it synchronously
+			if ( settings.oFeatures.bProcessing ) {
+				_fnProcessingDisplay( settings, true );
+	
+				setTimeout( function() {
+					_fnSortListener( settings, colIdx, e.shiftKey, callback );
+	
+					// In server-side processing, the draw callback will remove the
+					// processing display
+					if ( _fnDataSource( settings ) !== 'ssp' ) {
+						_fnProcessingDisplay( settings, false );
+					}
+				}, 0 );
+			}
+			else {
+				_fnSortListener( settings, colIdx, e.shiftKey, callback );
+			}
+		} );
+	}
+	
+	
+	/**
+	 * Set the sorting classes on table's body, Note: it is safe to call this function
+	 * when bSort and bSortClasses are false
+	 *  @param {object} oSettings dataTables settings object
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnSortingClasses( settings )
+	{
+		var oldSort = settings.aLastSort;
+		var sortClass = settings.oClasses.sSortColumn;
+		var sort = _fnSortFlatten( settings );
+		var features = settings.oFeatures;
+		var i, ien, colIdx;
+	
+		if ( features.bSort && features.bSortClasses ) {
+			// Remove old sorting classes
+			for ( i=0, ien=oldSort.length ; i<ien ; i++ ) {
+				colIdx = oldSort[i].src;
+	
+				// Remove column sorting
+				$( _pluck( settings.aoData, 'anCells', colIdx ) )
+					.removeClass( sortClass + (i<2 ? i+1 : 3) );
+			}
+	
+			// Add new column sorting
+			for ( i=0, ien=sort.length ; i<ien ; i++ ) {
+				colIdx = sort[i].src;
+	
+				$( _pluck( settings.aoData, 'anCells', colIdx ) )
+					.addClass( sortClass + (i<2 ? i+1 : 3) );
+			}
+		}
+	
+		settings.aLastSort = sort;
+	}
+	
+	
+	// Get the data to sort a column, be it from cache, fresh (populating the
+	// cache), or from a sort formatter
+	function _fnSortData( settings, idx )
+	{
+		// Custom sorting function - provided by the sort data type
+		var column = settings.aoColumns[ idx ];
+		var customSort = DataTable.ext.order[ column.sSortDataType ];
+		var customData;
+	
+		if ( customSort ) {
+			customData = customSort.call( settings.oInstance, settings, idx,
+				_fnColumnIndexToVisible( settings, idx )
+			);
+		}
+	
+		// Use / populate cache
+		var row, cellData;
+		var formatter = DataTable.ext.type.order[ column.sType+"-pre" ];
+	
+		for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
+			row = settings.aoData[i];
+	
+			if ( ! row._aSortData ) {
+				row._aSortData = [];
+			}
+	
+			if ( ! row._aSortData[idx] || customSort ) {
+				cellData = customSort ?
+					customData[i] : // If there was a custom sort function, use data from there
+					_fnGetCellData( settings, i, idx, 'sort' );
+	
+				row._aSortData[ idx ] = formatter ?
+					formatter( cellData ) :
+					cellData;
+			}
+		}
+	}
+	
+	
+	
+	/**
+	 * Save the state of a table
+	 *  @param {object} oSettings dataTables settings object
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnSaveState ( settings )
+	{
+		if ( !settings.oFeatures.bStateSave || settings.bDestroying )
+		{
+			return;
+		}
+	
+		/* Store the interesting variables */
+		var state = {
+			time:    +new Date(),
+			start:   settings._iDisplayStart,
+			length:  settings._iDisplayLength,
+			order:   $.extend( true, [], settings.aaSorting ),
+			search:  _fnSearchToCamel( settings.oPreviousSearch ),
+			columns: $.map( settings.aoColumns, function ( col, i ) {
+				return {
+					visible: col.bVisible,
+					search: _fnSearchToCamel( settings.aoPreSearchCols[i] )
+				};
+			} )
+		};
+	
+		_fnCallbackFire( settings, "aoStateSaveParams", 'stateSaveParams', [settings, state] );
+	
+		settings.oSavedState = state;
+		settings.fnStateSaveCallback.call( settings.oInstance, settings, state );
+	}
+	
+	
+	/**
+	 * Attempt to load a saved table state
+	 *  @param {object} oSettings dataTables settings object
+	 *  @param {object} oInit DataTables init object so we can override settings
+	 *  @param {function} callback Callback to execute when the state has been loaded
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnLoadState ( settings, oInit, callback )
+	{
+		var i, ien;
+		var columns = settings.aoColumns;
+		var loaded = function ( s ) {
+			if ( ! s || ! s.time ) {
+				callback();
+				return;
+			}
+	
+			// Allow custom and plug-in manipulation functions to alter the saved data set and
+			// cancelling of loading by returning false
+			var abStateLoad = _fnCallbackFire( settings, 'aoStateLoadParams', 'stateLoadParams', [settings, state] );
+			if ( $.inArray( false, abStateLoad ) !== -1 ) {
+				callback();
+				return;
+			}
+	
+			// Reject old data
+			var duration = settings.iStateDuration;
+			if ( duration > 0 && s.time < +new Date() - (duration*1000) ) {
+				callback();
+				return;
+			}
+	
+			// Number of columns have changed - all bets are off, no restore of settings
+			if ( s.columns && columns.length !== s.columns.length ) {
+				callback();
+				return;
+			}
+	
+			// Store the saved state so it might be accessed at any time
+			settings.oLoadedState = $.extend( true, {}, state );
+	
+			// Restore key features - todo - for 1.11 this needs to be done by
+			// subscribed events
+			if ( s.start !== undefined ) {
+				settings._iDisplayStart    = s.start;
+				settings.iInitDisplayStart = s.start;
+			}
+			if ( s.length !== undefined ) {
+				settings._iDisplayLength   = s.length;
+			}
+	
+			// Order
+			if ( s.order !== undefined ) {
+				settings.aaSorting = [];
+				$.each( s.order, function ( i, col ) {
+					settings.aaSorting.push( col[0] >= columns.length ?
+						[ 0, col[1] ] :
+						col
+					);
+				} );
+			}
+	
+			// Search
+			if ( s.search !== undefined ) {
+				$.extend( settings.oPreviousSearch, _fnSearchToHung( s.search ) );
+			}
+	
+			// Columns
+			// 
+			if ( s.columns ) {
+				for ( i=0, ien=s.columns.length ; i<ien ; i++ ) {
+					var col = s.columns[i];
+	
+					// Visibility
+					if ( col.visible !== undefined ) {
+						columns[i].bVisible = col.visible;
+					}
+	
+					// Search
+					if ( col.search !== undefined ) {
+						$.extend( settings.aoPreSearchCols[i], _fnSearchToHung( col.search ) );
+					}
+				}
+			}
+	
+			_fnCallbackFire( settings, 'aoStateLoaded', 'stateLoaded', [settings, state] );
+			callback();
+		}
+	
+		if ( ! settings.oFeatures.bStateSave ) {
+			callback();
+			return;
+		}
+	
+		var state = settings.fnStateLoadCallback.call( settings.oInstance, settings, loaded );
+	
+		if ( state !== undefined ) {
+			loaded( state );
+		}
+		// otherwise, wait for the loaded callback to be executed
+	}
+	
+	
+	/**
+	 * Return the settings object for a particular table
+	 *  @param {node} table table we are using as a dataTable
+	 *  @returns {object} Settings object - or null if not found
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnSettingsFromNode ( table )
+	{
+		var settings = DataTable.settings;
+		var idx = $.inArray( table, _pluck( settings, 'nTable' ) );
+	
+		return idx !== -1 ?
+			settings[ idx ] :
+			null;
+	}
+	
+	
+	/**
+	 * Log an error message
+	 *  @param {object} settings dataTables settings object
+	 *  @param {int} level log error messages, or display them to the user
+	 *  @param {string} msg error message
+	 *  @param {int} tn Technical note id to get more information about the error.
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnLog( settings, level, msg, tn )
+	{
+		msg = 'DataTables warning: '+
+			(settings ? 'table id='+settings.sTableId+' - ' : '')+msg;
+	
+		if ( tn ) {
+			msg += '. For more information about this error, please see '+
+			'http://datatables.net/tn/'+tn;
+		}
+	
+		if ( ! level  ) {
+			// Backwards compatibility pre 1.10
+			var ext = DataTable.ext;
+			var type = ext.sErrMode || ext.errMode;
+	
+			if ( settings ) {
+				_fnCallbackFire( settings, null, 'error', [ settings, tn, msg ] );
+			}
+	
+			if ( type == 'alert' ) {
+				alert( msg );
+			}
+			else if ( type == 'throw' ) {
+				throw new Error(msg);
+			}
+			else if ( typeof type == 'function' ) {
+				type( settings, tn, msg );
+			}
+		}
+		else if ( window.console && console.log ) {
+			console.log( msg );
+		}
+	}
+	
+	
+	/**
+	 * See if a property is defined on one object, if so assign it to the other object
+	 *  @param {object} ret target object
+	 *  @param {object} src source object
+	 *  @param {string} name property
+	 *  @param {string} [mappedName] name to map too - optional, name used if not given
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnMap( ret, src, name, mappedName )
+	{
+		if ( $.isArray( name ) ) {
+			$.each( name, function (i, val) {
+				if ( $.isArray( val ) ) {
+					_fnMap( ret, src, val[0], val[1] );
+				}
+				else {
+					_fnMap( ret, src, val );
+				}
+			} );
+	
+			return;
+		}
+	
+		if ( mappedName === undefined ) {
+			mappedName = name;
+		}
+	
+		if ( src[name] !== undefined ) {
+			ret[mappedName] = src[name];
+		}
+	}
+	
+	
+	/**
+	 * Extend objects - very similar to jQuery.extend, but deep copy objects, and
+	 * shallow copy arrays. The reason we need to do this, is that we don't want to
+	 * deep copy array init values (such as aaSorting) since the dev wouldn't be
+	 * able to override them, but we do want to deep copy arrays.
+	 *  @param {object} out Object to extend
+	 *  @param {object} extender Object from which the properties will be applied to
+	 *      out
+	 *  @param {boolean} breakRefs If true, then arrays will be sliced to take an
+	 *      independent copy with the exception of the `data` or `aaData` parameters
+	 *      if they are present. This is so you can pass in a collection to
+	 *      DataTables and have that used as your data source without breaking the
+	 *      references
+	 *  @returns {object} out Reference, just for convenience - out === the return.
+	 *  @memberof DataTable#oApi
+	 *  @todo This doesn't take account of arrays inside the deep copied objects.
+	 */
+	function _fnExtend( out, extender, breakRefs )
+	{
+		var val;
+	
+		for ( var prop in extender ) {
+			if ( extender.hasOwnProperty(prop) ) {
+				val = extender[prop];
+	
+				if ( $.isPlainObject( val ) ) {
+					if ( ! $.isPlainObject( out[prop] ) ) {
+						out[prop] = {};
+					}
+					$.extend( true, out[prop], val );
+				}
+				else if ( breakRefs && prop !== 'data' && prop !== 'aaData' && $.isArray(val) ) {
+					out[prop] = val.slice();
+				}
+				else {
+					out[prop] = val;
+				}
+			}
+		}
+	
+		return out;
+	}
+	
+	
+	/**
+	 * Bind an event handers to allow a click or return key to activate the callback.
+	 * This is good for accessibility since a return on the keyboard will have the
+	 * same effect as a click, if the element has focus.
+	 *  @param {element} n Element to bind the action to
+	 *  @param {object} oData Data object to pass to the triggered function
+	 *  @param {function} fn Callback function for when the event is triggered
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnBindAction( n, oData, fn )
+	{
+		$(n)
+			.on( 'click.DT', oData, function (e) {
+					n.blur(); // Remove focus outline for mouse users
+					fn(e);
+				} )
+			.on( 'keypress.DT', oData, function (e){
+					if ( e.which === 13 ) {
+						e.preventDefault();
+						fn(e);
+					}
+				} )
+			.on( 'selectstart.DT', function () {
+					/* Take the brutal approach to cancelling text selection */
+					return false;
+				} );
+	}
+	
+	
+	/**
+	 * Register a callback function. Easily allows a callback function to be added to
+	 * an array store of callback functions that can then all be called together.
+	 *  @param {object} oSettings dataTables settings object
+	 *  @param {string} sStore Name of the array storage for the callbacks in oSettings
+	 *  @param {function} fn Function to be called back
+	 *  @param {string} sName Identifying name for the callback (i.e. a label)
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnCallbackReg( oSettings, sStore, fn, sName )
+	{
+		if ( fn )
+		{
+			oSettings[sStore].push( {
+				"fn": fn,
+				"sName": sName
+			} );
+		}
+	}
+	
+	
+	/**
+	 * Fire callback functions and trigger events. Note that the loop over the
+	 * callback array store is done backwards! Further note that you do not want to
+	 * fire off triggers in time sensitive applications (for example cell creation)
+	 * as its slow.
+	 *  @param {object} settings dataTables settings object
+	 *  @param {string} callbackArr Name of the array storage for the callbacks in
+	 *      oSettings
+	 *  @param {string} eventName Name of the jQuery custom event to trigger. If
+	 *      null no trigger is fired
+	 *  @param {array} args Array of arguments to pass to the callback function /
+	 *      trigger
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnCallbackFire( settings, callbackArr, eventName, args )
+	{
+		var ret = [];
+	
+		if ( callbackArr ) {
+			ret = $.map( settings[callbackArr].slice().reverse(), function (val, i) {
+				return val.fn.apply( settings.oInstance, args );
+			} );
+		}
+	
+		if ( eventName !== null ) {
+			var e = $.Event( eventName+'.dt' );
+	
+			$(settings.nTable).trigger( e, args );
+	
+			ret.push( e.result );
+		}
+	
+		return ret;
+	}
+	
+	
+	function _fnLengthOverflow ( settings )
+	{
+		var
+			start = settings._iDisplayStart,
+			end = settings.fnDisplayEnd(),
+			len = settings._iDisplayLength;
+	
+		/* If we have space to show extra rows (backing up from the end point - then do so */
+		if ( start >= end )
+		{
+			start = end - len;
+		}
+	
+		// Keep the start record on the current page
+		start -= (start % len);
+	
+		if ( len === -1 || start < 0 )
+		{
+			start = 0;
+		}
+	
+		settings._iDisplayStart = start;
+	}
+	
+	
+	function _fnRenderer( settings, type )
+	{
+		var renderer = settings.renderer;
+		var host = DataTable.ext.renderer[type];
+	
+		if ( $.isPlainObject( renderer ) && renderer[type] ) {
+			// Specific renderer for this type. If available use it, otherwise use
+			// the default.
+			return host[renderer[type]] || host._;
+		}
+		else if ( typeof renderer === 'string' ) {
+			// Common renderer - if there is one available for this type use it,
+			// otherwise use the default
+			return host[renderer] || host._;
+		}
+	
+		// Use the default
+		return host._;
+	}
+	
+	
+	/**
+	 * Detect the data source being used for the table. Used to simplify the code
+	 * a little (ajax) and to make it compress a little smaller.
+	 *
+	 *  @param {object} settings dataTables settings object
+	 *  @returns {string} Data source
+	 *  @memberof DataTable#oApi
+	 */
+	function _fnDataSource ( settings )
+	{
+		if ( settings.oFeatures.bServerSide ) {
+			return 'ssp';
+		}
+		else if ( settings.ajax || settings.sAjaxSource ) {
+			return 'ajax';
+		}
+		return 'dom';
+	}
+	
+
+	
+	
+	/**
+	 * Computed structure of the DataTables API, defined by the options passed to
+	 * `DataTable.Api.register()` when building the API.
+	 *
+	 * The structure is built in order to speed creation and extension of the Api
+	 * objects since the extensions are effectively pre-parsed.
+	 *
+	 * The array is an array of objects with the following structure, where this
+	 * base array represents the Api prototype base:
+	 *
+	 *     [
+	 *       {
+	 *         name:      'data'                -- string   - Property name
+	 *         val:       function () {},       -- function - Api method (or undefined if just an object
+	 *         methodExt: [ ... ],              -- array    - Array of Api object definitions to extend the method result
+	 *         propExt:   [ ... ]               -- array    - Array of Api object definitions to extend the property
+	 *       },
+	 *       {
+	 *         name:     'row'
+	 *         val:       {},
+	 *         methodExt: [ ... ],
+	 *         propExt:   [
+	 *           {
+	 *             name:      'data'
+	 *             val:       function () {},
+	 *             methodExt: [ ... ],
+	 *             propExt:   [ ... ]
+	 *           },
+	 *           ...
+	 *         ]
+	 *       }
+	 *     ]
+	 *
+	 * @type {Array}
+	 * @ignore
+	 */
+	var __apiStruct = [];
+	
+	
+	/**
+	 * `Array.prototype` reference.
+	 *
+	 * @type object
+	 * @ignore
+	 */
+	var __arrayProto = Array.prototype;
+	
+	
+	/**
+	 * Abstraction for `context` parameter of the `Api` constructor to allow it to
+	 * take several different forms for ease of use.
+	 *
+	 * Each of the input parameter types will be converted to a DataTables settings
+	 * object where possible.
+	 *
+	 * @param  {string|node|jQuery|object} mixed DataTable identifier. Can be one
+	 *   of:
+	 *
+	 *   * `string` - jQuery selector. Any DataTables' matching the given selector
+	 *     with be found and used.
+	 *   * `node` - `TABLE` node which has already been formed into a DataTable.
+	 *   * `jQuery` - A jQuery object of `TABLE` nodes.
+	 *   * `object` - DataTables settings object
+	 *   * `DataTables.Api` - API instance
+	 * @return {array|null} Matching DataTables settings objects. `null` or
+	 *   `undefined` is returned if no matching DataTable is found.
+	 * @ignore
+	 */
+	var _toSettings = function ( mixed )
+	{
+		var idx, jq;
+		var settings = DataTable.settings;
+		var tables = $.map( settings, function (el, i) {
+			return el.nTable;
+		} );
+	
+		if ( ! mixed ) {
+			return [];
+		}
+		else if ( mixed.nTable && mixed.oApi ) {
+			// DataTables settings object
+			return [ mixed ];
+		}
+		else if ( mixed.nodeName && mixed.nodeName.toLowerCase() === 'table' ) {
+			// Table node
+			idx = $.inArray( mixed, tables );
+			return idx !== -1 ? [ settings[idx] ] : null;
+		}
+		else if ( mixed && typeof mixed.settings === 'function' ) {
+			return mixed.settings().toArray();
+		}
+		else if ( typeof mixed === 'string' ) {
+			// jQuery selector
+			jq = $(mixed);
+		}
+		else if ( mixed instanceof $ ) {
+			// jQuery object (also DataTables instance)
+			jq = mixed;
+		}
+	
+		if ( jq ) {
+			return jq.map( function(i) {
+				idx = $.inArray( this, tables );
+				return idx !== -1 ? settings[idx] : null;
+			} ).toArray();
+		}
+	};
+	
+	
+	/**
+	 * DataTables API class - used to control and interface with  one or more
+	 * DataTables enhanced tables.
+	 *
+	 * The API class is heavily based on jQuery, presenting a chainable interface
+	 * that you can use to interact with tables. Each instance of the API class has
+	 * a "context" - i.e. the tables that it will operate on. This could be a single
+	 * table, all tables on a page or a sub-set thereof.
+	 *
+	 * Additionally the API is designed to allow you to easily work with the data in
+	 * the tables, retrieving and manipulating it as required. This is done by
+	 * presenting the API class as an array like interface. The contents of the
+	 * array depend upon the actions requested by each method (for example
+	 * `rows().nodes()` will return an array of nodes, while `rows().data()` will
+	 * return an array of objects or arrays depending upon your table's
+	 * configuration). The API object has a number of array like methods (`push`,
+	 * `pop`, `reverse` etc) as well as additional helper methods (`each`, `pluck`,
+	 * `unique` etc) to assist your working with the data held in a table.
+	 *
+	 * Most methods (those which return an Api instance) are chainable, which means
+	 * the return from a method call also has all of the methods available that the
+	 * top level object had. For example, these two calls are equivalent:
+	 *
+	 *     // Not chained
+	 *     api.row.add( {...} );
+	 *     api.draw();
+	 *
+	 *     // Chained
+	 *     api.row.add( {...} ).draw();
+	 *
+	 * @class DataTable.Api
+	 * @param {array|object|string|jQuery} context DataTable identifier. This is
+	 *   used to define which DataTables enhanced tables this API will operate on.
+	 *   Can be one of:
+	 *
+	 *   * `string` - jQuery selector. Any DataTables' matching the given selector
+	 *     with be found and used.
+	 *   * `node` - `TABLE` node which has already been formed into a DataTable.
+	 *   * `jQuery` - A jQuery object of `TABLE` nodes.
+	 *   * `object` - DataTables settings object
+	 * @param {array} [data] Data to initialise the Api instance with.
+	 *
+	 * @example
+	 *   // Direct initialisation during DataTables construction
+	 *   var api = $('#example').DataTable();
+	 *
+	 * @example
+	 *   // Initialisation using a DataTables jQuery object
+	 *   var api = $('#example').dataTable().api();
+	 *
+	 * @example
+	 *   // Initialisation as a constructor
+	 *   var api = new $.fn.DataTable.Api( 'table.dataTable' );
+	 */
+	_Api = function ( context, data )
+	{
+		if ( ! (this instanceof _Api) ) {
+			return new _Api( context, data );
+		}
+	
+		var settings = [];
+		var ctxSettings = function ( o ) {
+			var a = _toSettings( o );
+			if ( a ) {
+				settings = settings.concat( a );
+			}
+		};
+	
+		if ( $.isArray( context ) ) {
+			for ( var i=0, ien=context.length ; i<ien ; i++ ) {
+				ctxSettings( context[i] );
+			}
+		}
+		else {
+			ctxSettings( context );
+		}
+	
+		// Remove duplicates
+		this.context = _unique( settings );
+	
+		// Initial data
+		if ( data ) {
+			$.merge( this, data );
+		}
+	
+		// selector
+		this.selector = {
+			rows: null,
+			cols: null,
+			opts: null
+		};
+	
+		_Api.extend( this, this, __apiStruct );
+	};
+	
+	DataTable.Api = _Api;
+	
+	// Don't destroy the existing prototype, just extend it. Required for jQuery 2's
+	// isPlainObject.
+	$.extend( _Api.prototype, {
+		any: function ()
+		{
+			return this.count() !== 0;
+		},
+	
+	
+		concat:  __arrayProto.concat,
+	
+	
+		context: [], // array of table settings objects
+	
+	
+		count: function ()
+		{
+			return this.flatten().length;
+		},
+	
+	
+		each: function ( fn )
+		{
+			for ( var i=0, ien=this.length ; i<ien; i++ ) {
+				fn.call( this, this[i], i, this );
+			}
+	
+			return this;
+		},
+	
+	
+		eq: function ( idx )
+		{
+			var ctx = this.context;
+	
+			return ctx.length > idx ?
+				new _Api( ctx[idx], this[idx] ) :
+				null;
+		},
+	
+	
+		filter: function ( fn )
+		{
+			var a = [];
+	
+			if ( __arrayProto.filter ) {
+				a = __arrayProto.filter.call( this, fn, this );
+			}
+			else {
+				// Compatibility for browsers without EMCA-252-5 (JS 1.6)
+				for ( var i=0, ien=this.length ; i<ien ; i++ ) {
+					if ( fn.call( this, this[i], i, this ) ) {
+						a.push( this[i] );
+					}
+				}
+			}
+	
+			return new _Api( this.context, a );
+		},
+	
+	
+		flatten: function ()
+		{
+			var a = [];
+			return new _Api( this.context, a.concat.apply( a, this.toArray() ) );
+		},
+	
+	
+		join:    __arrayProto.join,
+	
+	
+		indexOf: __arrayProto.indexOf || function (obj, start)
+		{
+			for ( var i=(start || 0), ien=this.length ; i<ien ; i++ ) {
+				if ( this[i] === obj ) {
+					return i;
+				}
+			}
+			return -1;
+		},
+	
+		iterator: function ( flatten, type, fn, alwaysNew ) {
+			var
+				a = [], ret,
+				i, ien, j, jen,
+				context = this.context,
+				rows, items, item,
+				selector = this.selector;
+	
+			// Argument shifting
+			if ( typeof flatten === 'string' ) {
+				alwaysNew = fn;
+				fn = type;
+				type = flatten;
+				flatten = false;
+			}
+	
+			for ( i=0, ien=context.length ; i<ien ; i++ ) {
+				var apiInst = new _Api( context[i] );
+	
+				if ( type === 'table' ) {
+					ret = fn.call( apiInst, context[i], i );
+	
+					if ( ret !== undefined ) {
+						a.push( ret );
+					}
+				}
+				else if ( type === 'columns' || type === 'rows' ) {
+					// this has same length as context - one entry for each table
+					ret = fn.call( apiInst, context[i], this[i], i );
+	
+					if ( ret !== undefined ) {
+						a.push( ret );
+					}
+				}
+				else if ( type === 'column' || type === 'column-rows' || type === 'row' || type === 'cell' ) {
+					// columns and rows share the same structure.
+					// 'this' is an array of column indexes for each context
+					items = this[i];
+	
+					if ( type === 'column-rows' ) {
+						rows = _selector_row_indexes( context[i], selector.opts );
+					}
+	
+					for ( j=0, jen=items.length ; j<jen ; j++ ) {
+						item = items[j];
+	
+						if ( type === 'cell' ) {
+							ret = fn.call( apiInst, context[i], item.row, item.column, i, j );
+						}
+						else {
+							ret = fn.call( apiInst, context[i], item, i, j, rows );
+						}
+	
+						if ( ret !== undefined ) {
+							a.push( ret );
+						}
+					}
+				}
+			}
+	
+			if ( a.length || alwaysNew ) {
+				var api = new _Api( context, flatten ? a.concat.apply( [], a ) : a );
+				var apiSelector = api.selector;
+				apiSelector.rows = selector.rows;
+				apiSelector.cols = selector.cols;
+				apiSelector.opts = selector.opts;
+				return api;
+			}
+			return this;
+		},
+	
+	
+		lastIndexOf: __arrayProto.lastIndexOf || function (obj, start)
+		{
+			// Bit cheeky...
+			return this.indexOf.apply( this.toArray.reverse(), arguments );
+		},
+	
+	
+		length:  0,
+	
+	
+		map: function ( fn )
+		{
+			var a = [];
+	
+			if ( __arrayProto.map ) {
+				a = __arrayProto.map.call( this, fn, this );
+			}
+			else {
+				// Compatibility for browsers without EMCA-252-5 (JS 1.6)
+				for ( var i=0, ien=this.length ; i<ien ; i++ ) {
+					a.push( fn.call( this, this[i], i ) );
+				}
+			}
+	
+			return new _Api( this.context, a );
+		},
+	
+	
+		pluck: function ( prop )
+		{
+			return this.map( function ( el ) {
+				return el[ prop ];
+			} );
+		},
+	
+		pop:     __arrayProto.pop,
+	
+	
+		push:    __arrayProto.push,
+	
+	
+		// Does not return an API instance
+		reduce: __arrayProto.reduce || function ( fn, init )
+		{
+			return _fnReduce( this, fn, init, 0, this.length, 1 );
+		},
+	
+	
+		reduceRight: __arrayProto.reduceRight || function ( fn, init )
+		{
+			return _fnReduce( this, fn, init, this.length-1, -1, -1 );
+		},
+	
+	
+		reverse: __arrayProto.reverse,
+	
+	
+		// Object with rows, columns and opts
+		selector: null,
+	
+	
+		shift:   __arrayProto.shift,
+	
+	
+		sort:    __arrayProto.sort, // ? name - order?
+	
+	
+		splice:  __arrayProto.splice,
+	
+	
+		toArray: function ()
+		{
+			return __arrayProto.slice.call( this );
+		},
+	
+	
+		to$: function ()
+		{
+			return $( this );
+		},
+	
+	
+		toJQuery: function ()
+		{
+			return $( this );
+		},
+	
+	
+		unique: function ()
+		{
+			return new _Api( this.context, _unique(this) );
+		},
+	
+	
+		unshift: __arrayProto.unshift
+	} );
+	
+	
+	_Api.extend = function ( scope, obj, ext )
+	{
+		// Only extend API instances and static properties of the API
+		if ( ! ext.length || ! obj || ( ! (obj instanceof _Api) && ! obj.__dt_wrapper ) ) {
+			return;
+		}
+	
+		var
+			i, ien,
+			j, jen,
+			struct, inner,
+			methodScoping = function ( scope, fn, struc ) {
+				return function () {
+					var ret = fn.apply( scope, arguments );
+	
+					// Method extension
+					_Api.extend( ret, ret, struc.methodExt );
+					return ret;
+				};
+			};
+	
+		for ( i=0, ien=ext.length ; i<ien ; i++ ) {
+			struct = ext[i];
+	
+			// Value
+			obj[ struct.name ] = typeof struct.val === 'function' ?
+				methodScoping( scope, struct.val, struct ) :
+				$.isPlainObject( struct.val ) ?
+					{} :
+					struct.val;
+	
+			obj[ struct.name ].__dt_wrapper = true;
+	
+			// Property extension
+			_Api.extend( scope, obj[ struct.name ], struct.propExt );
+		}
+	};
+	
+	
+	// @todo - Is there need for an augment function?
+	// _Api.augment = function ( inst, name )
+	// {
+	// 	// Find src object in the structure from the name
+	// 	var parts = name.split('.');
+	
+	// 	_Api.extend( inst, obj );
+	// };
+	
+	
+	//     [
+	//       {
+	//         name:      'data'                -- string   - Property name
+	//         val:       function () {},       -- function - Api method (or undefined if just an object
+	//         methodExt: [ ... ],              -- array    - Array of Api object definitions to extend the method result
+	//         propExt:   [ ... ]               -- array    - Array of Api object definitions to extend the property
+	//       },
+	//       {
+	//         name:     'row'
+	//         val:       {},
+	//         methodExt: [ ... ],
+	//         propExt:   [
+	//           {
+	//             name:      'data'
+	//             val:       function () {},
+	//             methodExt: [ ... ],
+	//             propExt:   [ ... ]
+	//           },
+	//           ...
+	//         ]
+	//       }
+	//     ]
+	
+	_Api.register = _api_register = function ( name, val )
+	{
+		if ( $.isArray( name ) ) {
+			for ( var j=0, jen=name.length ; j<jen ; j++ ) {
+				_Api.register( name[j], val );
+			}
+			return;
+		}
+	
+		var
+			i, ien,
+			heir = name.split('.'),
+			struct = __apiStruct,
+			key, method;
+	
+		var find = function ( src, name ) {
+			for ( var i=0, ien=src.length ; i<ien ; i++ ) {
+				if ( src[i].name === name ) {
+					return src[i];
+				}
+			}
+			return null;
+		};
+	
+		for ( i=0, ien=heir.length ; i<ien ; i++ ) {
+			method = heir[i].indexOf('()') !== -1;
+			key = method ?
+				heir[i].replace('()', '') :
+				heir[i];
+	
+			var src = find( struct, key );
+			if ( ! src ) {
+				src = {
+					name:      key,
+					val:       {},
+					methodExt: [],
+					propExt:   []
+				};
+				struct.push( src );
+			}
+	
+			if ( i === ien-1 ) {
+				src.val = val;
+			}
+			else {
+				struct = method ?
+					src.methodExt :
+					src.propExt;
+			}
+		}
+	};
+	
+	
+	_Api.registerPlural = _api_registerPlural = function ( pluralName, singularName, val ) {
+		_Api.register( pluralName, val );
+	
+		_Api.register( singularName, function () {
+			var ret = val.apply( this, arguments );
+	
+			if ( ret === this ) {
+				// Returned item is the API instance that was passed in, return it
+				return this;
+			}
+			else if ( ret instanceof _Api ) {
+				// New API instance returned, want the value from the first item
+				// in the returned array for the singular result.
+				return ret.length ?
+					$.isArray( ret[0] ) ?
+						new _Api( ret.context, ret[0] ) : // Array results are 'enhanced'
+						ret[0] :
+					undefined;
+			}
+	
+			// Non-API return - just fire it back
+			return ret;
+		} );
+	};
+	
+	
+	/**
+	 * Selector for HTML tables. Apply the given selector to the give array of
+	 * DataTables settings objects.
+	 *
+	 * @param {string|integer} [selector] jQuery selector string or integer
+	 * @param  {array} Array of DataTables settings objects to be filtered
+	 * @return {array}
+	 * @ignore
+	 */
+	var __table_selector = function ( selector, a )
+	{
+		// Integer is used to pick out a table by index
+		if ( typeof selector === 'number' ) {
+			return [ a[ selector ] ];
+		}
+	
+		// Perform a jQuery selector on the table nodes
+		var nodes = $.map( a, function (el, i) {
+			return el.nTable;
+		} );
+	
+		return $(nodes)
+			.filter( selector )
+			.map( function (i) {
+				// Need to translate back from the table node to the settings
+				var idx = $.inArray( this, nodes );
+				return a[ idx ];
+			} )
+			.toArray();
+	};
+	
+	
+	
+	/**
+	 * Context selector for the API's context (i.e. the tables the API instance
+	 * refers to.
+	 *
+	 * @name    DataTable.Api#tables
+	 * @param {string|integer} [selector] Selector to pick which tables the iterator
+	 *   should operate on. If not given, all tables in the current context are
+	 *   used. This can be given as a jQuery selector (for example `':gt(0)'`) to
+	 *   select multiple tables or as an integer to select a single table.
+	 * @returns {DataTable.Api} Returns a new API instance if a selector is given.
+	 */
+	_api_register( 'tables()', function ( selector ) {
+		// A new instance is created if there was a selector specified
+		return selector ?
+			new _Api( __table_selector( selector, this.context ) ) :
+			this;
+	} );
+	
+	
+	_api_register( 'table()', function ( selector ) {
+		var tables = this.tables( selector );
+		var ctx = tables.context;
+	
+		// Truncate to the first matched table
+		return ctx.length ?
+			new _Api( ctx[0] ) :
+			tables;
+	} );
+	
+	
+	_api_registerPlural( 'tables().nodes()', 'table().node()' , function () {
+		return this.iterator( 'table', function ( ctx ) {
+			return ctx.nTable;
+		}, 1 );
+	} );
+	
+	
+	_api_registerPlural( 'tables().body()', 'table().body()' , function () {
+		return this.iterator( 'table', function ( ctx ) {
+			return ctx.nTBody;
+		}, 1 );
+	} );
+	
+	
+	_api_registerPlural( 'tables().header()', 'table().header()' , function () {
+		return this.iterator( 'table', function ( ctx ) {
+			return ctx.nTHead;
+		}, 1 );
+	} );
+	
+	
+	_api_registerPlural( 'tables().footer()', 'table().footer()' , function () {
+		return this.iterator( 'table', function ( ctx ) {
+			return ctx.nTFoot;
+		}, 1 );
+	} );
+	
+	
+	_api_registerPlural( 'tables().containers()', 'table().container()' , function () {
+		return this.iterator( 'table', function ( ctx ) {
+			return ctx.nTableWrapper;
+		}, 1 );
+	} );
+	
+	
+	
+	/**
+	 * Redraw the tables in the current context.
+	 */
+	_api_register( 'draw()', function ( paging ) {
+		return this.iterator( 'table', function ( settings ) {
+			if ( paging === 'page' ) {
+				_fnDraw( settings );
+			}
+			else {
+				if ( typeof paging === 'string' ) {
+					paging = paging === 'full-hold' ?
+						false :
+						true;
+				}
+	
+				_fnReDraw( settings, paging===false );
+			}
+		} );
+	} );
+	
+	
+	
+	/**
+	 * Get the current page index.
+	 *
+	 * @return {integer} Current page index (zero based)
+	 *//**
+	 * Set the current page.
+	 *
+	 * Note that if you attempt to show a page which does not exist, DataTables will
+	 * not throw an error, but rather reset the paging.
+	 *
+	 * @param {integer|string} action The paging action to take. This can be one of:
+	 *  * `integer` - The page index to jump to
+	 *  * `string` - An action to take:
+	 *    * `first` - Jump to first page.
+	 *    * `next` - Jump to the next page
+	 *    * `previous` - Jump to previous page
+	 *    * `last` - Jump to the last page.
+	 * @returns {DataTables.Api} this
+	 */
+	_api_register( 'page()', function ( action ) {
+		if ( action === undefined ) {
+			return this.page.info().page; // not an expensive call
+		}
+	
+		// else, have an action to take on all tables
+		return this.iterator( 'table', function ( settings ) {
+			_fnPageChange( settings, action );
+		} );
+	} );
+	
+	
+	/**
+	 * Paging information for the first table in the current context.
+	 *
+	 * If you require paging information for another table, use the `table()` method
+	 * with a suitable selector.
+	 *
+	 * @return {object} Object with the following properties set:
+	 *  * `page` - Current page index (zero based - i.e. the first page is `0`)
+	 *  * `pages` - Total number of pages
+	 *  * `start` - Display index for the first record shown on the current page
+	 *  * `end` - Display index for the last record shown on the current page
+	 *  * `length` - Display length (number of records). Note that generally `start
+	 *    + length = end`, but this is not always true, for example if there are
+	 *    only 2 records to show on the final page, with a length of 10.
+	 *  * `recordsTotal` - Full data set length
+	 *  * `recordsDisplay` - Data set length once the current filtering criterion
+	 *    are applied.
+	 */
+	_api_register( 'page.info()', function ( action ) {
+		if ( this.context.length === 0 ) {
+			return undefined;
+		}
+	
+		var
+			settings   = this.context[0],
+			start      = settings._iDisplayStart,
+			len        = settings.oFeatures.bPaginate ? settings._iDisplayLength : -1,
+			visRecords = settings.fnRecordsDisplay(),
+			all        = len === -1;
+	
+		return {
+			"page":           all ? 0 : Math.floor( start / len ),
+			"pages":          all ? 1 : Math.ceil( visRecords / len ),
+			"start":          start,
+			"end":            settings.fnDisplayEnd(),
+			"length":         len,
+			"recordsTotal":   settings.fnRecordsTotal(),
+			"recordsDisplay": visRecords,
+			"serverSide":     _fnDataSource( settings ) === 'ssp'
+		};
+	} );
+	
+	
+	/**
+	 * Get the current page length.
+	 *
+	 * @return {integer} Current page length. Note `-1` indicates that all records
+	 *   are to be shown.
+	 *//**
+	 * Set the current page length.
+	 *
+	 * @param {integer} Page length to set. Use `-1` to show all records.
+	 * @returns {DataTables.Api} this
+	 */
+	_api_register( 'page.len()', function ( len ) {
+		// Note that we can't call this function 'length()' because `length`
+		// is a Javascript property of functions which defines how many arguments
+		// the function expects.
+		if ( len === undefined ) {
+			return this.context.length !== 0 ?
+				this.context[0]._iDisplayLength :
+				undefined;
+		}
+	
+		// else, set the page length
+		return this.iterator( 'table', function ( settings ) {
+			_fnLengthChange( settings, len );
+		} );
+	} );
+	
+	
+	
+	var __reload = function ( settings, holdPosition, callback ) {
+		// Use the draw event to trigger a callback
+		if ( callback ) {
+			var api = new _Api( settings );
+	
+			api.one( 'draw', function () {
+				callback( api.ajax.json() );
+			} );
+		}
+	
+		if ( _fnDataSource( settings ) == 'ssp' ) {
+			_fnReDraw( settings, holdPosition );
+		}
+		else {
+			_fnProcessingDisplay( settings, true );
+	
+			// Cancel an existing request
+			var xhr = settings.jqXHR;
+			if ( xhr && xhr.readyState !== 4 ) {
+				xhr.abort();
+			}
+	
+			// Trigger xhr
+			_fnBuildAjax( settings, [], function( json ) {
+				_fnClearTable( settings );
+	
+				var data = _fnAjaxDataSrc( settings, json );
+				for ( var i=0, ien=data.length ; i<ien ; i++ ) {
+					_fnAddData( settings, data[i] );
+				}
+	
+				_fnReDraw( settings, holdPosition );
+				_fnProcessingDisplay( settings, false );
+			} );
+		}
+	};
+	
+	
+	/**
+	 * Get the JSON response from the last Ajax request that DataTables made to the
+	 * server. Note that this returns the JSON from the first table in the current
+	 * context.
+	 *
+	 * @return {object} JSON received from the server.
+	 */
+	_api_register( 'ajax.json()', function () {
+		var ctx = this.context;
+	
+		if ( ctx.length > 0 ) {
+			return ctx[0].json;
+		}
+	
+		// else return undefined;
+	} );
+	
+	
+	/**
+	 * Get the data submitted in the last Ajax request
+	 */
+	_api_register( 'ajax.params()', function () {
+		var ctx = this.context;
+	
+		if ( ctx.length > 0 ) {
+			return ctx[0].oAjaxData;
+		}
+	
+		// else return undefined;
+	} );
+	
+	
+	/**
+	 * Reload tables from the Ajax data source. Note that this function will
+	 * automatically re-draw the table when the remote data has been loaded.
+	 *
+	 * @param {boolean} [reset=true] Reset (default) or hold the current paging
+	 *   position. A full re-sort and re-filter is performed when this method is
+	 *   called, which is why the pagination reset is the default action.
+	 * @returns {DataTables.Api} this
+	 */
+	_api_register( 'ajax.reload()', function ( callback, resetPaging ) {
+		return this.iterator( 'table', function (settings) {
+			__reload( settings, resetPaging===false, callback );
+		} );
+	} );
+	
+	
+	/**
+	 * Get the current Ajax URL. Note that this returns the URL from the first
+	 * table in the current context.
+	 *
+	 * @return {string} Current Ajax source URL
+	 *//**
+	 * Set the Ajax URL. Note that this will set the URL for all tables in the
+	 * current context.
+	 *
+	 * @param {string} url URL to set.
+	 * @returns {DataTables.Api} this
+	 */
+	_api_register( 'ajax.url()', function ( url ) {
+		var ctx = this.context;
+	
+		if ( url === undefined ) {
+			// get
+			if ( ctx.length === 0 ) {
+				return undefined;
+			}
+			ctx = ctx[0];
+	
+			return ctx.ajax ?
+				$.isPlainObject( ctx.ajax ) ?
+					ctx.ajax.url :
+					ctx.ajax :
+				ctx.sAjaxSource;
+		}
+	
+		// set
+		return this.iterator( 'table', function ( settings ) {
+			if ( $.isPlainObject( settings.ajax ) ) {
+				settings.ajax.url = url;
+			}
+			else {
+				settings.ajax = url;
+			}
+			// No need to consider sAjaxSource here since DataTables gives priority
+			// to `ajax` over `sAjaxSource`. So setting `ajax` here, renders any
+			// value of `sAjaxSource` redundant.
+		} );
+	} );
+	
+	
+	/**
+	 * Load data from the newly set Ajax URL. Note that this method is only
+	 * available when `ajax.url()` is used to set a URL. Additionally, this method
+	 * has the same effect as calling `ajax.reload()` but is provided for
+	 * convenience when setting a new URL. Like `ajax.reload()` it will
+	 * automatically redraw the table once the remote data has been loaded.
+	 *
+	 * @returns {DataTables.Api} this
+	 */
+	_api_register( 'ajax.url().load()', function ( callback, resetPaging ) {
+		// Same as a reload, but makes sense to present it for easy access after a
+		// url change
+		return this.iterator( 'table', function ( ctx ) {
+			__reload( ctx, resetPaging===false, callback );
+		} );
+	} );
+	
+	
+	
+	
+	var _selector_run = function ( type, selector, selectFn, settings, opts )
+	{
+		var
+			out = [], res,
+			a, i, ien, j, jen,
+			selectorType = typeof selector;
+	
+		// Can't just check for isArray here, as an API or jQuery instance might be
+		// given with their array like look
+		if ( ! selector || selectorType === 'string' || selectorType === 'function' || selector.length === undefined ) {
+			selector = [ selector ];
+		}
+	
+		for ( i=0, ien=selector.length ; i<ien ; i++ ) {
+			// Only split on simple strings - complex expressions will be jQuery selectors
+			a = selector[i] && selector[i].split && ! selector[i].match(/[\[\(:]/) ?
+				selector[i].split(',') :
+				[ selector[i] ];
+	
+			for ( j=0, jen=a.length ; j<jen ; j++ ) {
+				res = selectFn( typeof a[j] === 'string' ? $.trim(a[j]) : a[j] );
+	
+				if ( res && res.length ) {
+					out = out.concat( res );
+				}
+			}
+		}
+	
+		// selector extensions
+		var ext = _ext.selector[ type ];
+		if ( ext.length ) {
+			for ( i=0, ien=ext.length ; i<ien ; i++ ) {
+				out = ext[i]( settings, opts, out );
+			}
+		}
+	
+		return _unique( out );
+	};
+	
+	
+	var _selector_opts = function ( opts )
+	{
+		if ( ! opts ) {
+			opts = {};
+		}
+	
+		// Backwards compatibility for 1.9- which used the terminology filter rather
+		// than search
+		if ( opts.filter && opts.search === undefined ) {
+			opts.search = opts.filter;
+		}
+	
+		return $.extend( {
+			search: 'none',
+			order: 'current',
+			page: 'all'
+		}, opts );
+	};
+	
+	
+	var _selector_first = function ( inst )
+	{
+		// Reduce the API instance to the first item found
+		for ( var i=0, ien=inst.length ; i<ien ; i++ ) {
+			if ( inst[i].length > 0 ) {
+				// Assign the first element to the first item in the instance
+				// and truncate the instance and context
+				inst[0] = inst[i];
+				inst[0].length = 1;
+				inst.length = 1;
+				inst.context = [ inst.context[i] ];
+	
+				return inst;
+			}
+		}
+	
+		// Not found - return an empty instance
+		inst.length = 0;
+		return inst;
+	};
+	
+	
+	var _selector_row_indexes = function ( settings, opts )
+	{
+		var
+			i, ien, tmp, a=[],
+			displayFiltered = settings.aiDisplay,
+			displayMaster = settings.aiDisplayMaster;
+	
+		var
+			search = opts.search,  // none, applied, removed
+			order  = opts.order,   // applied, current, index (original - compatibility with 1.9)
+			page   = opts.page;    // all, current
+	
+		if ( _fnDataSource( settings ) == 'ssp' ) {
+			// In server-side processing mode, most options are irrelevant since
+			// rows not shown don't exist and the index order is the applied order
+			// Removed is a special case - for consistency just return an empty
+			// array
+			return search === 'removed' ?
+				[] :
+				_range( 0, displayMaster.length );
+		}
+		else if ( page == 'current' ) {
+			// Current page implies that order=current and fitler=applied, since it is
+			// fairly senseless otherwise, regardless of what order and search actually
+			// are
+			for ( i=settings._iDisplayStart, ien=settings.fnDisplayEnd() ; i<ien ; i++ ) {
+				a.push( displayFiltered[i] );
+			}
+		}
+		else if ( order == 'current' || order == 'applied' ) {
+			a = search == 'none' ?
+				displayMaster.slice() :                      // no search
+				search == 'applied' ?
+					displayFiltered.slice() :                // applied search
+					$.map( displayMaster, function (el, i) { // removed search
+						return $.inArray( el, displayFiltered ) === -1 ? el : null;
+					} );
+		}
+		else if ( order == 'index' || order == 'original' ) {
+			for ( i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
+				if ( search == 'none' ) {
+					a.push( i );
+				}
+				else { // applied | removed
+					tmp = $.inArray( i, displayFiltered );
+	
+					if ((tmp === -1 && search == 'removed') ||
+						(tmp >= 0   && search == 'applied') )
+					{
+						a.push( i );
+					}
+				}
+			}
+		}
+	
+		return a;
+	};
+	
+	
+	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+	 * Rows
+	 *
+	 * {}          - no selector - use all available rows
+	 * {integer}   - row aoData index
+	 * {node}      - TR node
+	 * {string}    - jQuery selector to apply to the TR elements
+	 * {array}     - jQuery array of nodes, or simply an array of TR nodes
+	 *
+	 */
+	
+	
+	var __row_selector = function ( settings, selector, opts )
+	{
+		var rows;
+		var run = function ( sel ) {
+			var selInt = _intVal( sel );
+			var i, ien;
+	
+			// Short cut - selector is a number and no options provided (default is
+			// all records, so no need to check if the index is in there, since it
+			// must be - dev error if the index doesn't exist).
+			if ( selInt !== null && ! opts ) {
+				return [ selInt ];
+			}
+	
+			if ( ! rows ) {
+				rows = _selector_row_indexes( settings, opts );
+			}
+	
+			if ( selInt !== null && $.inArray( selInt, rows ) !== -1 ) {
+				// Selector - integer
+				return [ selInt ];
+			}
+			else if ( sel === null || sel === undefined || sel === '' ) {
+				// Selector - none
+				return rows;
+			}
+	
+			// Selector - function
+			if ( typeof sel === 'function' ) {
+				return $.map( rows, function (idx) {
+					var row = settings.aoData[ idx ];
+					return sel( idx, row._aData, row.nTr ) ? idx : null;
+				} );
+			}
+	
+			// Get nodes in the order from the `rows` array with null values removed
+			var nodes = _removeEmpty(
+				_pluck_order( settings.aoData, rows, 'nTr' )
+			);
+	
+			// Selector - node
+			if ( sel.nodeName ) {
+				if ( sel._DT_RowIndex !== undefined ) {
+					return [ sel._DT_RowIndex ]; // Property added by DT for fast lookup
+				}
+				else if ( sel._DT_CellIndex ) {
+					return [ sel._DT_CellIndex.row ];
+				}
+				else {
+					var host = $(sel).closest('*[data-dt-row]');
+					return host.length ?
+						[ host.data('dt-row') ] :
+						[];
+				}
+			}
+	
+			// ID selector. Want to always be able to select rows by id, regardless
+			// of if the tr element has been created or not, so can't rely upon
+			// jQuery here - hence a custom implementation. This does not match
+			// Sizzle's fast selector or HTML4 - in HTML5 the ID can be anything,
+			// but to select it using a CSS selector engine (like Sizzle or
+			// querySelect) it would need to need to be escaped for some characters.
+			// DataTables simplifies this for row selectors since you can select
+			// only a row. A # indicates an id any anything that follows is the id -
+			// unescaped.
+			if ( typeof sel === 'string' && sel.charAt(0) === '#' ) {
+				// get row index from id
+				var rowObj = settings.aIds[ sel.replace( /^#/, '' ) ];
+				if ( rowObj !== undefined ) {
+					return [ rowObj.idx ];
+				}
+	
+				// need to fall through to jQuery in case there is DOM id that
+				// matches
+			}
+	
+			// Selector - jQuery selector string, array of nodes or jQuery object/
+			// As jQuery's .filter() allows jQuery objects to be passed in filter,
+			// it also allows arrays, so this will cope with all three options
+			return $(nodes)
+				.filter( sel )
+				.map( function () {
+					return this._DT_RowIndex;
+				} )
+				.toArray();
+		};
+	
+		return _selector_run( 'row', selector, run, settings, opts );
+	};
+	
+	
+	_api_register( 'rows()', function ( selector, opts ) {
+		// argument shifting
+		if ( selector === undefined ) {
+			selector = '';
+		}
+		else if ( $.isPlainObject( selector ) ) {
+			opts = selector;
+			selector = '';
+		}
+	
+		opts = _selector_opts( opts );
+	
+		var inst = this.iterator( 'table', function ( settings ) {
+			return __row_selector( settings, selector, opts );
+		}, 1 );
+	
+		// Want argument shifting here and in __row_selector?
+		inst.selector.rows = selector;
+		inst.selector.opts = opts;
+	
+		return inst;
+	} );
+	
+	_api_register( 'rows().nodes()', function () {
+		return this.iterator( 'row', function ( settings, row ) {
+			return settings.aoData[ row ].nTr || undefined;
+		}, 1 );
+	} );
+	
+	_api_register( 'rows().data()', function () {
+		return this.iterator( true, 'rows', function ( settings, rows ) {
+			return _pluck_order( settings.aoData, rows, '_aData' );
+		}, 1 );
+	} );
+	
+	_api_registerPlural( 'rows().cache()', 'row().cache()', function ( type ) {
+		return this.iterator( 'row', function ( settings, row ) {
+			var r = settings.aoData[ row ];
+			return type === 'search' ? r._aFilterData : r._aSortData;
+		}, 1 );
+	} );
+	
+	_api_registerPlural( 'rows().invalidate()', 'row().invalidate()', function ( src ) {
+		return this.iterator( 'row', function ( settings, row ) {
+			_fnInvalidate( settings, row, src );
+		} );
+	} );
+	
+	_api_registerPlural( 'rows().indexes()', 'row().index()', function () {
+		return this.iterator( 'row', function ( settings, row ) {
+			return row;
+		}, 1 );
+	} );
+	
+	_api_registerPlural( 'rows().ids()', 'row().id()', function ( hash ) {
+		var a = [];
+		var context = this.context;
+	
+		// `iterator` will drop undefined values, but in this case we want them
+		for ( var i=0, ien=context.length ; i<ien ; i++ ) {
+			for ( var j=0, jen=this[i].length ; j<jen ; j++ ) {
+				var id = context[i].rowIdFn( context[i].aoData[ this[i][j] ]._aData );
+				a.push( (hash === true ? '#' : '' )+ id );
+			}
+		}
+	
+		return new _Api( context, a );
+	} );
+	
+	_api_registerPlural( 'rows().remove()', 'row().remove()', function () {
+		var that = this;
+	
+		this.iterator( 'row', function ( settings, row, thatIdx ) {
+			var data = settings.aoData;
+			var rowData = data[ row ];
+			var i, ien, j, jen;
+			var loopRow, loopCells;
+	
+			data.splice( row, 1 );
+	
+			// Update the cached indexes
+			for ( i=0, ien=data.length ; i<ien ; i++ ) {
+				loopRow = data[i];
+				loopCells = loopRow.anCells;
+	
+				// Rows
+				if ( loopRow.nTr !== null ) {
+					loopRow.nTr._DT_RowIndex = i;
+				}
+	
+				// Cells
+				if ( loopCells !== null ) {
+					for ( j=0, jen=loopCells.length ; j<jen ; j++ ) {
+						loopCells[j]._DT_CellIndex.row = i;
+					}
+				}
+			}
+	
+			// Delete from the display arrays
+			_fnDeleteIndex( settings.aiDisplayMaster, row );
+			_fnDeleteIndex( settings.aiDisplay, row );
+			_fnDeleteIndex( that[ thatIdx ], row, false ); // maintain local indexes
+	
+			// Check for an 'overflow' they case for displaying the table
+			_fnLengthOverflow( settings );
+	
+			// Remove the row's ID reference if there is one
+			var id = settings.rowIdFn( rowData._aData );
+			if ( id !== undefined ) {
+				delete settings.aIds[ id ];
+			}
+		} );
+	
+		this.iterator( 'table', function ( settings ) {
+			for ( var i=0, ien=settings.aoData.length ; i<ien ; i++ ) {
+				settings.aoData[i].idx = i;
+			}
+		} );
+	
+		return this;
+	} );
+	
+	
+	_api_register( 'rows.add()', function ( rows ) {
+		var newRows = this.iterator( 'table', function ( settings ) {
+				var row, i, ien;
+				var out = [];
+	
+				for ( i=0, ien=rows.length ; i<ien ; i++ ) {
+					row = rows[i];
+	
+					if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) {
+						out.push( _fnAddTr( settings, row )[0] );
+					}
+					else {
+						out.push( _fnAddData( settings, row ) );
+					}
+				}
+	
+				return out;
+			}, 1 );
+	
+		// Return an Api.rows() extended instance, so rows().nodes() etc can be used
+		var modRows = this.rows( -1 );
+		modRows.pop();
+		$.merge( modRows, newRows );
+	
+		return modRows;
+	} );
+	
+	
+	
+	
+	
+	/**
+	 *
+	 */
+	_api_register( 'row()', function ( selector, opts ) {
+		return _selector_first( this.rows( selector, opts ) );
+	} );
+	
+	
+	_api_register( 'row().data()', function ( data ) {
+		var ctx = this.context;
+	
+		if ( data === undefined ) {
+			// Get
+			return ctx.length && this.length ?
+				ctx[0].aoData[ this[0] ]._aData :
+				undefined;
+		}
+	
+		// Set
+		ctx[0].aoData[ this[0] ]._aData = data;
+	
+		// Automatically invalidate
+		_fnInvalidate( ctx[0], this[0], 'data' );
+	
+		return this;
+	} );
+	
+	
+	_api_register( 'row().node()', function () {
+		var ctx = this.context;
+	
+		return ctx.length && this.length ?
+			ctx[0].aoData[ this[0] ].nTr || null :
+			null;
+	} );
+	
+	
+	_api_register( 'row.add()', function ( row ) {
+		// Allow a jQuery object to be passed in - only a single row is added from
+		// it though - the first element in the set
+		if ( row instanceof $ && row.length ) {
+			row = row[0];
+		}
+	
+		var rows = this.iterator( 'table', function ( settings ) {
+			if ( row.nodeName && row.nodeName.toUpperCase() === 'TR' ) {
+				return _fnAddTr( settings, row )[0];
+			}
+			return _fnAddData( settings, row );
+		} );
+	
+		// Return an Api.rows() extended instance, with the newly added row selected
+		return this.row( rows[0] );
+	} );
+	
+	
+	
+	var __details_add = function ( ctx, row, data, klass )
+	{
+		// Convert to array of TR elements
+		var rows = [];
+		var addRow = function ( r, k ) {
+			// Recursion to allow for arrays of jQuery objects
+			if ( $.isArray( r ) || r instanceof $ ) {
+				for ( var i=0, ien=r.length ; i<ien ; i++ ) {
+					addRow( r[i], k );
+				}
+				return;
+			}
+	
+			// If we get a TR element, then just add it directly - up to the dev
+			// to add the correct number of columns etc
+			if ( r.nodeName && r.nodeName.toLowerCase() === 'tr' ) {
+				rows.push( r );
+			}
+			else {
+				// Otherwise create a row with a wrapper
+				var created = $('<tr><td/></tr>').addClass( k );
+				$('td', created)
+					.addClass( k )
+					.html( r )
+					[0].colSpan = _fnVisbleColumns( ctx );
+	
+				rows.push( created[0] );
+			}
+		};
+	
+		addRow( data, klass );
+	
+		if ( row._details ) {
+			row._details.detach();
+		}
+	
+		row._details = $(rows);
+	
+		// If the children were already shown, that state should be retained
+		if ( row._detailsShow ) {
+			row._details.insertAfter( row.nTr );
+		}
+	};
+	
+	
+	var __details_remove = function ( api, idx )
+	{
+		var ctx = api.context;
+	
+		if ( ctx.length ) {
+			var row = ctx[0].aoData[ idx !== undefined ? idx : api[0] ];
+	
+			if ( row && row._details ) {
+				row._details.remove();
+	
+				row._detailsShow = undefined;
+				row._details = undefined;
+			}
+		}
+	};
+	
+	
+	var __details_display = function ( api, show ) {
+		var ctx = api.context;
+	
+		if ( ctx.length && api.length ) {
+			var row = ctx[0].aoData[ api[0] ];
+	
+			if ( row._details ) {
+				row._detailsShow = show;
+	
+				if ( show ) {
+					row._details.insertAfter( row.nTr );
+				}
+				else {
+					row._details.detach();
+				}
+	
+				__details_events( ctx[0] );
+			}
+		}
+	};
+	
+	
+	var __details_events = function ( settings )
+	{
+		var api = new _Api( settings );
+		var namespace = '.dt.DT_details';
+		var drawEvent = 'draw'+namespace;
+		var colvisEvent = 'column-visibility'+namespace;
+		var destroyEvent = 'destroy'+namespace;
+		var data = settings.aoData;
+	
+		api.off( drawEvent +' '+ colvisEvent +' '+ destroyEvent );
+	
+		if ( _pluck( data, '_details' ).length > 0 ) {
+			// On each draw, insert the required elements into the document
+			api.on( drawEvent, function ( e, ctx ) {
+				if ( settings !== ctx ) {
+					return;
+				}
+	
+				api.rows( {page:'current'} ).eq(0).each( function (idx) {
+					// Internal data grab
+					var row = data[ idx ];
+	
+					if ( row._detailsShow ) {
+						row._details.insertAfter( row.nTr );
+					}
+				} );
+			} );
+	
+			// Column visibility change - update the colspan
+			api.on( colvisEvent, function ( e, ctx, idx, vis ) {
+				if ( settings !== ctx ) {
+					return;
+				}
+	
+				// Update the colspan for the details rows (note, only if it already has
+				// a colspan)
+				var row, visible = _fnVisbleColumns( ctx );
+	
+				for ( var i=0, ien=data.length ; i<ien ; i++ ) {
+					row = data[i];
+	
+					if ( row._details ) {
+						row._details.children('td[colspan]').attr('colspan', visible );
+					}
+				}
+			} );
+	
+			// Table destroyed - nuke any child rows
+			api.on( destroyEvent, function ( e, ctx ) {
+				if ( settings !== ctx ) {
+					return;
+				}
+	
+				for ( var i=0, ien=data.length ; i<ien ; i++ ) {
+					if ( data[i]._details ) {
+						__details_remove( api, i );
+					}
+				}
+			} );
+		}
+	};
+	
+	// Strings for the method names to help minification
+	var _emp = '';
+	var _child_obj = _emp+'row().child';
+	var _child_mth = _child_obj+'()';
+	
+	// data can be:
+	//  tr
+	//  string
+	//  jQuery or array of any of the above
+	_api_register( _child_mth, function ( data, klass ) {
+		var ctx = this.context;
+	
+		if ( data === undefined ) {
+			// get
+			return ctx.length && this.length ?
+				ctx[0].aoData[ this[0] ]._details :
+				undefined;
+		}
+		else if ( data === true ) {
+			// show
+			this.child.show();
+		}
+		else if ( data === false ) {
+			// remove
+			__details_remove( this );
+		}
+		else if ( ctx.length && this.length ) {
+			// set
+			__details_add( ctx[0], ctx[0].aoData[ this[0] ], data, klass );
+		}
+	
+		return this;
+	} );
+	
+	
+	_api_register( [
+		_child_obj+'.show()',
+		_child_mth+'.show()' // only when `child()` was called with parameters (without
+	], function ( show ) {   // it returns an object and this method is not executed)
+		__details_display( this, true );
+		return this;
+	} );
+	
+	
+	_api_register( [
+		_child_obj+'.hide()',
+		_child_mth+'.hide()' // only when `child()` was called with parameters (without
+	], function () {         // it returns an object and this method is not executed)
+		__details_display( this, false );
+		return this;
+	} );
+	
+	
+	_api_register( [
+		_child_obj+'.remove()',
+		_child_mth+'.remove()' // only when `child()` was called with parameters (without
+	], function () {           // it returns an object and this method is not executed)
+		__details_remove( this );
+		return this;
+	} );
+	
+	
+	_api_register( _child_obj+'.isShown()', function () {
+		var ctx = this.context;
+	
+		if ( ctx.length && this.length ) {
+			// _detailsShown as false or undefined will fall through to return false
+			return ctx[0].aoData[ this[0] ]._detailsShow || false;
+		}
+		return false;
+	} );
+	
+	
+	
+	/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+	 * Columns
+	 *
+	 * {integer}           - column index (>=0 count from left, <0 count from right)
+	 * "{integer}:visIdx"  - visible column index (i.e. translate to column index)  (>=0 count from left, <0 count from right)
+	 * "{integer}:visible" - alias for {integer}:visIdx  (>=0 count from left, <0 count from right)
+	 * "{string}:name"     - column name
+	 * "{string}"          - jQuery selector on column header nodes
+	 *
+	 */
+	
+	// can be an array of these items, comma separated list, or an array of comma
+	// separated lists
+	
+	var __re_column_selector = /^([^:]+):(name|visIdx|visible)$/;
+	
+	
+	// r1 and r2 are redundant - but it means that the parameters match for the
+	// iterator callback in columns().data()
+	var __columnData = function ( settings, column, r1, r2, rows ) {
+		var a = [];
+		for ( var row=0, ien=rows.length ; row<ien ; row++ ) {
+			a.push( _fnGetCellData( settings, rows[row], column ) );
+		}
+		return a;
+	};
+	
+	
+	var __column_selector = function ( settings, selector, opts )
+	{
+		var
+			columns = settings.aoColumns,
+			names = _pluck( columns, 'sName' ),
+			nodes = _pluck( columns, 'nTh' );
+	
+		var run = function ( s ) {
+			var selInt = _intVal( s );
+	
+			// Selector - all
+			if ( s === '' ) {
+				return _range( columns.length );
+			}
+	
+			// Selector - index
+			if ( selInt !== null ) {
+				return [ selInt >= 0 ?
+					selInt : // Count from left
+					columns.length + selInt // Count from right (+ because its a negative value)
+				];
+			}
+	
+			// Selector = function
+			if ( typeof s === 'function' ) {
+				var rows = _selector_row_indexes( settings, opts );
+	
+				return $.map( columns, function (col, idx) {
+					return s(
+							idx,
+							__columnData( settings, idx, 0, 0, rows ),
+							nodes[ idx ]
+						) ? idx : null;
+				} );
+			}
+	
+			// jQuery or string selector
+			var match = typeof s === 'string' ?
+				s.match( __re_column_selector ) :
+				'';
+	
+			if ( match ) {
+				switch( match[2] ) {
+					case 'visIdx':
+					case 'visible':
+						var idx = parseInt( match[1], 10 );
+						// Visible index given, convert to column index
+						if ( idx < 0 ) {
+							// Counting from the right
+							var visColumns = $.map( columns, function (col,i) {
+								return col.bVisible ? i : null;
+							} );
+							return [ visColumns[ visColumns.length + idx ] ];
+						}
+						// Counting from the left
+						return [ _fnVisibleToColumnIndex( settings, idx ) ];
+	
+					case 'name':
+						// match by name. `names` is column index complete and in order
+						return $.map( names, function (name, i) {
+							return name === match[1] ? i : null;
+						} );
+	
+					default:
+						return [];
+				}
+			}
+	
+			// Cell in the table body
+			if ( s.nodeName && s._DT_CellIndex ) {
+				return [ s._DT_CellIndex.column ];
+			}
+	
+			// jQuery selector on the TH elements for the columns
+			var jqResult = $( nodes )
+				.filter( s )
+				.map( function () {
+					return $.inArray( this, nodes ); // `nodes` is column index complete and in order
+				} )
+				.toArray();
+	
+			if ( jqResult.length || ! s.nodeName ) {
+				return jqResult;
+			}
+	
+			// Otherwise a node which might have a `dt-column` data attribute, or be
+			// a child or such an element
+			var host = $(s).closest('*[data-dt-column]');
+			return host.length ?
+				[ host.data('dt-column') ] :
+				[];
+		};
+	
+		return _selector_run( 'column', selector, run, settings, opts );
+	};
+	
+	
+	var __setColumnVis = function ( settings, column, vis ) {
+		var
+			cols = settings.aoColumns,
+			col  = cols[ column ],
+			data = settings.aoData,
+			row, cells, i, ien, tr;
+	
+		// Get
+		if ( vis === undefined ) {
+			return col.bVisible;
+		}
+	
+		// Set
+		// No change
+		if ( col.bVisible === vis ) {
+			return;
+		}
+	
+		if ( vis ) {
+			// Insert column
+			// Need to decide if we should use appendChild or insertBefore
+			var insertBefore = $.inArray( true, _pluck(cols, 'bVisible'), column+1 );
+	
+			for ( i=0, ien=data.length ; i<ien ; i++ ) {
+				tr = data[i].nTr;
+				cells = data[i].anCells;
+	
+				if ( tr ) {
+					// insertBefore can act like appendChild if 2nd arg is null
+					tr.insertBefore( cells[ column ], cells[ insertBefore ] || null );
+				}
+			}
+		}
+		else {
+			// Remove column
+			$( _pluck( settings.aoData, 'anCells', column ) ).detach();
+		}
+	
+		// Common actions
+		col.bVisible = vis;
+		_fnDrawHead( settings, settings.aoHeader );
+		_fnDrawHead( settings, settings.aoFooter );
+	
+		_fnSaveState( settings );
+	};
+	
+	
+	_api_register( 'columns()', function ( selector, opts ) {
+		// argument shifting
+		if ( selector === undefined ) {
+			selector = '';
+		}
+		else if ( $.isPlainObject( selector ) ) {
+			opts = selector;
+			selector = '';
+		}
+	
+		opts = _selector_opts( opts );
+	
+		var inst = this.iterator( 'table', function ( settings ) {
+			return __column_selector( settings, selector, opts );
+		}, 1 );
+	
+		// Want argument shifting here and in _row_selector?
+		inst.selector.cols = selector;
+		inst.selector.opts = opts;
+	
+		return inst;
+	} );
+	
+	_api_registerPlural( 'columns().header()', 'column().header()', function ( selector, opts ) {
+		return this.iterator( 'column', function ( settings, column ) {
+			return settings.aoColumns[column].nTh;
+		}, 1 );
+	} );
+	
+	_api_registerPlural( 'columns().footer()', 'column().footer()', function ( selector, opts ) {
+		return this.iterator( 'column', function ( settings, column ) {
+			return settings.aoColumns[column].nTf;
+		}, 1 );
+	} );
+	
+	_api_registerPlural( 'columns().data()', 'column().data()', function () {
+		return this.iterator( 'column-rows', __columnData, 1 );
+	} );
+	
+	_api_registerPlural( 'columns().dataSrc()', 'column().dataSrc()', function () {
+		return this.iterator( 'column', function ( settings, column ) {
+			return settings.aoColumns[column].mData;
+		}, 1 );
+	} );
+	
+	_api_registerPlural( 'columns().cache()', 'column().cache()', function ( type ) {
+		return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) {
+			return _pluck_order( settings.aoData, rows,
+				type === 'search' ? '_aFilterData' : '_aSortData', column
+			);
+		}, 1 );
+	} );
+	
+	_api_registerPlural( 'columns().nodes()', 'column().nodes()', function () {
+		return this.iterator( 'column-rows', function ( settings, column, i, j, rows ) {
+			return _pluck_order( settings.aoData, rows, 'anCells', column ) ;
+		}, 1 );
+	} );
+	
+	_api_registerPlural( 'columns().visible()', 'column().visible()', function ( vis, calc ) {
+		var ret = this.iterator( 'column', function ( settings, column ) {
+			if ( vis === undefined ) {
+				return settings.aoColumns[ column ].bVisible;
+			} // else
+			__setColumnVis( settings, column, vis );
+		} );
+	
+		// Group the column visibility changes
+		if ( vis !== undefined ) {
+			// Second loop once the first is done for events
+			this.iterator( 'column', function ( settings, column ) {
+				_fnCallbackFire( settings, null, 'column-visibility', [settings, column, vis, calc] );
+			} );
+	
+			if ( calc === undefined || calc ) {
+				this.columns.adjust();
+			}
+		}
+	
+		return ret;
+	} );
+	
+	_api_registerPlural( 'columns().indexes()', 'column().index()', function ( type ) {
+		return this.iterator( 'column', function ( settings, column ) {
+			return type === 'visible' ?
+				_fnColumnIndexToVisible( settings, column ) :
+				column;
+		}, 1 );
+	} );
+	
+	_api_register( 'columns.adjust()', function () {
+		return this.iterator( 'table', function ( settings ) {
+			_fnAdjustColumnSizing( settings );
+		}, 1 );
+	} );
+	
+	_api_register( 'column.index()', function ( type, idx ) {
+		if ( this.context.length !== 0 ) {
+			var ctx = this.context[0];
+	
+			if ( type === 'fromVisible' || type === 'toData' ) {
+				return _fnVisibleToColumnIndex( ctx, idx );
+			}
+			else if ( type === 'fromData' || type === 'toVisible' ) {
+				return _fnColumnIndexToVisible( ctx, idx );
+			}
+		}
+	} );
+	
+	_api_register( 'column()', function ( selector, opts ) {
+		return _selector_first( this.columns( selector, opts ) );
+	} );
+	
+	
+	
+	var __cell_selector = function ( settings, selector, opts )
+	{
+		var data = settings.aoData;
+		var rows = _selector_row_indexes( settings, opts );
+		var cells = _removeEmpty( _pluck_order( data, rows, 'anCells' ) );
+		var allCells = $( [].concat.apply([], cells) );
+		var row;
+		var columns = settings.aoColumns.length;
+		var a, i, ien, j, o, host;
+	
+		var run = function ( s ) {
+			var fnSelector = typeof s === 'function';
+	
+			if ( s === null || s === undefined || fnSelector ) {
+				// All cells and function selectors
+				a = [];
+	
+				for ( i=0, ien=rows.length ; i<ien ; i++ ) {
+					row = rows[i];
+	
+					for ( j=0 ; j<columns ; j++ ) {
+						o = {
+							row: row,
+							column: j
+						};
+	
+						if ( fnSelector ) {
+							// Selector - function
+							host = data[ row ];
+	
+							if ( s( o, _fnGetCellData(settings, row, j), host.anCells ? host.anCells[j] : null ) ) {
+								a.push( o );
+							}
+						}
+						else {
+							// Selector - all
+							a.push( o );
+						}
+					}
+				}
+	
+				return a;
+			}
+			
+			// Selector - index
+			if ( $.isPlainObject( s ) ) {
+				return [s];
+			}
+	
+			// Selector - jQuery filtered cells
+			var jqResult = allCells
+				.filter( s )
+				.map( function (i, el) {
+					return { // use a new object, in case someone changes the values
+						row:    el._DT_CellIndex.row,
+						column: el._DT_CellIndex.column
+	 				};
+				} )
+				.toArray();
+	
+			if ( jqResult.length || ! s.nodeName ) {
+				return jqResult;
+			}
+	
+			// Otherwise the selector is a node, and there is one last option - the
+			// element might be a child of an element which has dt-row and dt-column
+			// data attributes
+			host = $(s).closest('*[data-dt-row]');
+			return host.length ?
+				[ {
+					row: host.data('dt-row'),
+					column: host.data('dt-column')
+				} ] :
+				[];
+		};
+	
+		return _selector_run( 'cell', selector, run, settings, opts );
+	};
+	
+	
+	
+	
+	_api_register( 'cells()', function ( rowSelector, columnSelector, opts ) {
+		// Argument shifting
+		if ( $.isPlainObject( rowSelector ) ) {
+			// Indexes
+			if ( rowSelector.row === undefined ) {
+				// Selector options in first parameter
+				opts = rowSelector;
+				rowSelector = null;
+			}
+			else {
+				// Cell index objects in first parameter
+				opts = columnSelector;
+				columnSelector = null;
+			}
+		}
+		if ( $.isPlainObject( columnSelector ) ) {
+			opts = columnSelector;
+			columnSelector = null;
+		}
+	
+		// Cell selector
+		if ( columnSelector === null || columnSelector === undefined ) {
+			return this.iterator( 'table', function ( settings ) {
+				return __cell_selector( settings, rowSelector, _selector_opts( opts ) );
+			} );
+		}
+	
+		// Row + column selector
+		var columns = this.columns( columnSelector, opts );
+		var rows = this.rows( rowSelector, opts );
+		var a, i, ien, j, jen;
+	
+		var cells = this.iterator( 'table', function ( settings, idx ) {
+			a = [];
+	
+			for ( i=0, ien=rows[idx].length ; i<ien ; i++ ) {
+				for ( j=0, jen=columns[idx].length ; j<jen ; j++ ) {
+					a.push( {
+						row:    rows[idx][i],
+						column: columns[idx][j]
+					} );
+				}
+			}
+	
+			return a;
+		}, 1 );
+	
+		$.extend( cells.selector, {
+			cols: columnSelector,
+			rows: rowSelector,
+			opts: opts
+		} );
+	
+		return cells;
+	} );
+	
+	
+	_api_registerPlural( 'cells().nodes()', 'cell().node()', function () {
+		return this.iterator( 'cell', function ( settings, row, column ) {
+			var data = settings.aoData[ row ];
+	
+			return data && data.anCells ?
+				data.anCells[ column ] :
+				undefined;
+		}, 1 );
+	} );
+	
+	
+	_api_register( 'cells().data()', function () {
+		return this.iterator( 'cell', function ( settings, row, column ) {
+			return _fnGetCellData( settings, row, column );
+		}, 1 );
+	} );
+	
+	
+	_api_registerPlural( 'cells().cache()', 'cell().cache()', function ( type ) {
+		type = type === 'search' ? '_aFilterData' : '_aSortData';
+	
+		return this.iterator( 'cell', function ( settings, row, column ) {
+			return settings.aoData[ row ][ type ][ column ];
+		}, 1 );
+	} );
+	
+	
+	_api_registerPlural( 'cells().render()', 'cell().render()', function ( type ) {
+		return this.iterator( 'cell', function ( settings, row, column ) {
+			return _fnGetCellData( settings, row, column, type );
+		}, 1 );
+	} );
+	
+	
+	_api_registerPlural( 'cells().indexes()', 'cell().index()', function () {
+		return this.iterator( 'cell', function ( settings, row, column ) {
+			return {
+				row: row,
+				column: column,
+				columnVisible: _fnColumnIndexToVisible( settings, column )
+			};
+		}, 1 );
+	} );
+	
+	
+	_api_registerPlural( 'cells().invalidate()', 'cell().invalidate()', function ( src ) {
+		return this.iterator( 'cell', function ( settings, row, column ) {
+			_fnInvalidate( settings, row, src, column );
+		} );
+	} );
+	
+	
+	
+	_api_register( 'cell()', function ( rowSelector, columnSelector, opts ) {
+		return _selector_first( this.cells( rowSelector, columnSelector, opts ) );
+	} );
+	
+	
+	_api_register( 'cell().data()', function ( data ) {
+		var ctx = this.context;
+		var cell = this[0];
+	
+		if ( data === undefined ) {
+			// Get
+			return ctx.length && cell.length ?
+				_fnGetCellData( ctx[0], cell[0].row, cell[0].column ) :
+				undefined;
+		}
+	
+		// Set
+		_fnSetCellData( ctx[0], cell[0].row, cell[0].column, data );
+		_fnInvalidate( ctx[0], cell[0].row, 'data', cell[0].column );
+	
+		return this;
+	} );
+	
+	
+	
+	/**
+	 * Get current ordering (sorting) that has been applied to the table.
+	 *
+	 * @returns {array} 2D array containing the sorting information for the first
+	 *   table in the current context. Each element in the parent array represents
+	 *   a column being sorted upon (i.e. multi-sorting with two columns would have
+	 *   2 inner arrays). The inner arrays may have 2 or 3 elements. The first is
+	 *   the column index that the sorting condition applies to, the second is the
+	 *   direction of the sort (`desc` or `asc`) and, optionally, the third is the
+	 *   index of the sorting order from the `column.sorting` initialisation array.
+	 *//**
+	 * Set the ordering for the table.
+	 *
+	 * @param {integer} order Column index to sort upon.
+	 * @param {string} direction Direction of the sort to be applied (`asc` or `desc`)
+	 * @returns {DataTables.Api} this
+	 *//**
+	 * Set the ordering for the table.
+	 *
+	 * @param {array} order 1D array of sorting information to be applied.
+	 * @param {array} [...] Optional additional sorting conditions
+	 * @returns {DataTables.Api} this
+	 *//**
+	 * Set the ordering for the table.
+	 *
+	 * @param {array} order 2D array of sorting information to be applied.
+	 * @returns {DataTables.Api} this
+	 */
+	_api_register( 'order()', function ( order, dir ) {
+		var ctx = this.context;
+	
+		if ( order === undefined ) {
+			// get
+			return ctx.length !== 0 ?
+				ctx[0].aaSorting :
+				undefined;
+		}
+	
+		// set
+		if ( typeof order === 'number' ) {
+			// Simple column / direction passed in
+			order = [ [ order, dir ] ];
+		}
+		else if ( order.length && ! $.isArray( order[0] ) ) {
+			// Arguments passed in (list of 1D arrays)
+			order = Array.prototype.slice.call( arguments );
+		}
+		// otherwise a 2D array was passed in
+	
+		return this.iterator( 'table', function ( settings ) {
+			settings.aaSorting = order.slice();
+		} );
+	} );
+	
+	
+	/**
+	 * Attach a sort listener to an element for a given column
+	 *
+	 * @param {node|jQuery|string} node Identifier for the element(s) to attach the
+	 *   listener to. This can take the form of a single DOM node, a jQuery
+	 *   collection of nodes or a jQuery selector which will identify the node(s).
+	 * @param {integer} column the column that a click on this node will sort on
+	 * @param {function} [callback] callback function when sort is run
+	 * @returns {DataTables.Api} this
+	 */
+	_api_register( 'order.listener()', function ( node, column, callback ) {
+		return this.iterator( 'table', function ( settings ) {
+			_fnSortAttachListener( settings, node, column, callback );
+		} );
+	} );
+	
+	
+	_api_register( 'order.fixed()', function ( set ) {
+		if ( ! set ) {
+			var ctx = this.context;
+			var fixed = ctx.length ?
+				ctx[0].aaSortingFixed :
+				undefined;
+	
+			return $.isArray( fixed ) ?
+				{ pre: fixed } :
+				fixed;
+		}
+	
+		return this.iterator( 'table', function ( settings ) {
+			settings.aaSortingFixed = $.extend( true, {}, set );
+		} );
+	} );
+	
+	
+	// Order by the selected column(s)
+	_api_register( [
+		'columns().order()',
+		'column().order()'
+	], function ( dir ) {
+		var that = this;
+	
+		return this.iterator( 'table', function ( settings, i ) {
+			var sort = [];
+	
+			$.each( that[i], function (j, col) {
+				sort.push( [ col, dir ] );
+			} );
+	
+			settings.aaSorting = sort;
+		} );
+	} );
+	
+	
+	
+	_api_register( 'search()', function ( input, regex, smart, caseInsen ) {
+		var ctx = this.context;
+	
+		if ( input === undefined ) {
+			// get
+			return ctx.length !== 0 ?
+				ctx[0].oPreviousSearch.sSearch :
+				undefined;
+		}
+	
+		// set
+		return this.iterator( 'table', function ( settings ) {
+			if ( ! settings.oFeatures.bFilter ) {
+				return;
+			}
+	
+			_fnFilterComplete( settings, $.extend( {}, settings.oPreviousSearch, {
+				"sSearch": input+"",
+				"bRegex":  regex === null ? false : regex,
+				"bSmart":  smart === null ? true  : smart,
+				"bCaseInsensitive": caseInsen === null ? true : caseInsen
+			} ), 1 );
+		} );
+	} );
+	
+	
+	_api_registerPlural(
+		'columns().search()',
+		'column().search()',
+		function ( input, regex, smart, caseInsen ) {
+			return this.iterator( 'column', function ( settings, column ) {
+				var preSearch = settings.aoPreSearchCols;
+	
+				if ( input === undefined ) {
+					// get
+					return preSearch[ column ].sSearch;
+				}
+	
+				// set
+				if ( ! settings.oFeatures.bFilter ) {
+					return;
+				}
+	
+				$.extend( preSearch[ column ], {
+					"sSearch": input+"",
+					"bRegex":  regex === null ? false : regex,
+					"bSmart":  smart === null ? true  : smart,
+					"bCaseInsensitive": caseInsen === null ? true : caseInsen
+				} );
+	
+				_fnFilterComplete( settings, settings.oPreviousSearch, 1 );
+			} );
+		}
+	);
+	
+	/*
+	 * State API methods
+	 */
+	
+	_api_register( 'state()', function () {
+		return this.context.length ?
+			this.context[0].oSavedState :
+			null;
+	} );
+	
+	
+	_api_register( 'state.clear()', function () {
+		return this.iterator( 'table', function ( settings ) {
+			// Save an empty object
+			settings.fnStateSaveCallback.call( settings.oInstance, settings, {} );
+		} );
+	} );
+	
+	
+	_api_register( 'state.loaded()', function () {
+		return this.context.length ?
+			this.context[0].oLoadedState :
+			null;
+	} );
+	
+	
+	_api_register( 'state.save()', function () {
+		return this.iterator( 'table', function ( settings ) {
+			_fnSaveState( settings );
+		} );
+	} );
+	
+	
+	
+	/**
+	 * Provide a common method for plug-ins to check the version of DataTables being
+	 * used, in order to ensure compatibility.
+	 *
+	 *  @param {string} version Version string to check for, in the format "X.Y.Z".
+	 *    Note that the formats "X" and "X.Y" are also acceptable.
+	 *  @returns {boolean} true if this version of DataTables is greater or equal to
+	 *    the required version, or false if this version of DataTales is not
+	 *    suitable
+	 *  @static
+	 *  @dtopt API-Static
+	 *
+	 *  @example
+	 *    alert( $.fn.dataTable.versionCheck( '1.9.0' ) );
+	 */
+	DataTable.versionCheck = DataTable.fnVersionCheck = function( version )
+	{
+		var aThis = DataTable.version.split('.');
+		var aThat = version.split('.');
+		var iThis, iThat;
+	
+		for ( var i=0, iLen=aThat.length ; i<iLen ; i++ ) {
+			iThis = parseInt( aThis[i], 10 ) || 0;
+			iThat = parseInt( aThat[i], 10 ) || 0;
+	
+			// Parts are the same, keep comparing
+			if (iThis === iThat) {
+				continue;
+			}
+	
+			// Parts are different, return immediately
+			return iThis > iThat;
+		}
+	
+		return true;
+	};
+	
+	
+	/**
+	 * Check if a `<table>` node is a DataTable table already or not.
+	 *
+	 *  @param {node|jquery|string} table Table node, jQuery object or jQuery
+	 *      selector for the table to test. Note that if more than more than one
+	 *      table is passed on, only the first will be checked
+	 *  @returns {boolean} true the table given is a DataTable, or false otherwise
+	 *  @static
+	 *  @dtopt API-Static
+	 *
+	 *  @example
+	 *    if ( ! $.fn.DataTable.isDataTable( '#example' ) ) {
+	 *      $('#example').dataTable();
+	 *    }
+	 */
+	DataTable.isDataTable = DataTable.fnIsDataTable = function ( table )
+	{
+		var t = $(table).get(0);
+		var is = false;
+	
+		if ( table instanceof DataTable.Api ) {
+			return true;
+		}
+	
+		$.each( DataTable.settings, function (i, o) {
+			var head = o.nScrollHead ? $('table', o.nScrollHead)[0] : null;
+			var foot = o.nScrollFoot ? $('table', o.nScrollFoot)[0] : null;
+	
+			if ( o.nTable === t || head === t || foot === t ) {
+				is = true;
+			}
+		} );
+	
+		return is;
+	};
+	
+	
+	/**
+	 * Get all DataTable tables that have been initialised - optionally you can
+	 * select to get only currently visible tables.
+	 *
+	 *  @param {boolean} [visible=false] Flag to indicate if you want all (default)
+	 *    or visible tables only.
+	 *  @returns {array} Array of `table` nodes (not DataTable instances) which are
+	 *    DataTables
+	 *  @static
+	 *  @dtopt API-Static
+	 *
+	 *  @example
+	 *    $.each( $.fn.dataTable.tables(true), function () {
+	 *      $(table).DataTable().columns.adjust();
+	 *    } );
+	 */
+	DataTable.tables = DataTable.fnTables = function ( visible )
+	{
+		var api = false;
+	
+		if ( $.isPlainObject( visible ) ) {
+			api = visible.api;
+			visible = visible.visible;
+		}
+	
+		var a = $.map( DataTable.settings, function (o) {
+			if ( !visible || (visible && $(o.nTable).is(':visible')) ) {
+				return o.nTable;
+			}
+		} );
+	
+		return api ?
+			new _Api( a ) :
+			a;
+	};
+	
+	
+	/**
+	 * Convert from camel case parameters to Hungarian notation. This is made public
+	 * for the extensions to provide the same ability as DataTables core to accept
+	 * either the 1.9 style Hungarian notation, or the 1.10+ style camelCase
+	 * parameters.
+	 *
+	 *  @param {object} src The model object which holds all parameters that can be
+	 *    mapped.
+	 *  @param {object} user The object to convert from camel case to Hungarian.
+	 *  @param {boolean} force When set to `true`, properties which already have a
+	 *    Hungarian value in the `user` object will be overwritten. Otherwise they
+	 *    won't be.
+	 */
+	DataTable.camelToHungarian = _fnCamelToHungarian;
+	
+	
+	
+	/**
+	 *
+	 */
+	_api_register( '$()', function ( selector, opts ) {
+		var
+			rows   = this.rows( opts ).nodes(), // Get all rows
+			jqRows = $(rows);
+	
+		return $( [].concat(
+			jqRows.filter( selector ).toArray(),
+			jqRows.find( selector ).toArray()
+		) );
+	} );
+	
+	
+	// jQuery functions to operate on the tables
+	$.each( [ 'on', 'one', 'off' ], function (i, key) {
+		_api_register( key+'()', function ( /* event, handler */ ) {
+			var args = Array.prototype.slice.call(arguments);
+	
+			// Add the `dt` namespace automatically if it isn't already present
+			args[0] = $.map( args[0].split( /\s/ ), function ( e ) {
+				return ! e.match(/\.dt\b/) ?
+					e+'.dt' :
+					e;
+				} ).join( ' ' );
+	
+			var inst = $( this.tables().nodes() );
+			inst[key].apply( inst, args );
+			return this;
+		} );
+	} );
+	
+	
+	_api_register( 'clear()', function () {
+		return this.iterator( 'table', function ( settings ) {
+			_fnClearTable( settings );
+		} );
+	} );
+	
+	
+	_api_register( 'settings()', function () {
+		return new _Api( this.context, this.context );
+	} );
+	
+	
+	_api_register( 'init()', function () {
+		var ctx = this.context;
+		return ctx.length ? ctx[0].oInit : null;
+	} );
+	
+	
+	_api_register( 'data()', function () {
+		return this.iterator( 'table', function ( settings ) {
+			return _pluck( settings.aoData, '_aData' );
+		} ).flatten();
+	} );
+	
+	
+	_api_register( 'destroy()', function ( remove ) {
+		remove = remove || false;
+	
+		return this.iterator( 'table', function ( settings ) {
+			var orig      = settings.nTableWrapper.parentNode;
+			var classes   = settings.oClasses;
+			var table     = settings.nTable;
+			var tbody     = settings.nTBody;
+			var thead     = settings.nTHead;
+			var tfoot     = settings.nTFoot;
+			var jqTable   = $(table);
+			var jqTbody   = $(tbody);
+			var jqWrapper = $(settings.nTableWrapper);
+			var rows      = $.map( settings.aoData, function (r) { return r.nTr; } );
+			var i, ien;
+	
+			// Flag to note that the table is currently being destroyed - no action
+			// should be taken
+			settings.bDestroying = true;
+	
+			// Fire off the destroy callbacks for plug-ins etc
+			_fnCallbackFire( settings, "aoDestroyCallback", "destroy", [settings] );
+	
+			// If not being removed from the document, make all columns visible
+			if ( ! remove ) {
+				new _Api( settings ).columns().visible( true );
+			}
+	
+			// Blitz all `DT` namespaced events (these are internal events, the
+			// lowercase, `dt` events are user subscribed and they are responsible
+			// for removing them
+			jqWrapper.off('.DT').find(':not(tbody *)').off('.DT');
+			$(window).off('.DT-'+settings.sInstance);
+	
+			// When scrolling we had to break the table up - restore it
+			if ( table != thead.parentNode ) {
+				jqTable.children('thead').detach();
+				jqTable.append( thead );
+			}
+	
+			if ( tfoot && table != tfoot.parentNode ) {
+				jqTable.children('tfoot').detach();
+				jqTable.append( tfoot );
+			}
+	
+			settings.aaSorting = [];
+			settings.aaSortingFixed = [];
+			_fnSortingClasses( settings );
+	
+			$( rows ).removeClass( settings.asStripeClasses.join(' ') );
+	
+			$('th, td', thead).removeClass( classes.sSortable+' '+
+				classes.sSortableAsc+' '+classes.sSortableDesc+' '+classes.sSortableNone
+			);
+	
+			if ( settings.bJUI ) {
+				$('th span.'+classes.sSortIcon+ ', td span.'+classes.sSortIcon, thead).detach();
+				$('th, td', thead).each( function () {
+					var wrapper = $('div.'+classes.sSortJUIWrapper, this);
+					$(this).append( wrapper.contents() );
+					wrapper.detach();
+				} );
+			}
+	
+			// Add the TR elements back into the table in their original order
+			jqTbody.children().detach();
+			jqTbody.append( rows );
+	
+			// Remove the DataTables generated nodes, events and classes
+			var removedMethod = remove ? 'remove' : 'detach';
+			jqTable[ removedMethod ]();
+			jqWrapper[ removedMethod ]();
+	
+			// If we need to reattach the table to the document
+			if ( ! remove && orig ) {
+				// insertBefore acts like appendChild if !arg[1]
+				orig.insertBefore( table, settings.nTableReinsertBefore );
+	
+				// Restore the width of the original table - was read from the style property,
+				// so we can restore directly to that
+				jqTable
+					.css( 'width', settings.sDestroyWidth )
+					.removeClass( classes.sTable );
+	
+				// If the were originally stripe classes - then we add them back here.
+				// Note this is not fool proof (for example if not all rows had stripe
+				// classes - but it's a good effort without getting carried away
+				ien = settings.asDestroyStripes.length;
+	
+				if ( ien ) {
+					jqTbody.children().each( function (i) {
+						$(this).addClass( settings.asDestroyStripes[i % ien] );
+					} );
+				}
+			}
+	
+			/* Remove the settings object from the settings array */
+			var idx = $.inArray( settings, DataTable.settings );
+			if ( idx !== -1 ) {
+				DataTable.settings.splice( idx, 1 );
+			}
+		} );
+	} );
+	
+	
+	// Add the `every()` method for rows, columns and cells in a compact form
+	$.each( [ 'column', 'row', 'cell' ], function ( i, type ) {
+		_api_register( type+'s().every()', function ( fn ) {
+			var opts = this.selector.opts;
+			var api = this;
+	
+			return this.iterator( type, function ( settings, arg1, arg2, arg3, arg4 ) {
+				// Rows and columns:
+				//  arg1 - index
+				//  arg2 - table counter
+				//  arg3 - loop counter
+				//  arg4 - undefined
+				// Cells:
+				//  arg1 - row index
+				//  arg2 - column index
+				//  arg3 - table counter
+				//  arg4 - loop counter
+				fn.call(
+					api[ type ](
+						arg1,
+						type==='cell' ? arg2 : opts,
+						type==='cell' ? opts : undefined
+					),
+					arg1, arg2, arg3, arg4
+				);
+			} );
+		} );
+	} );
+	
+	
+	// i18n method for extensions to be able to use the language object from the
+	// DataTable
+	_api_register( 'i18n()', function ( token, def, plural ) {
+		var ctx = this.context[0];
+		var resolved = _fnGetObjectDataFn( token )( ctx.oLanguage );
+	
+		if ( resolved === undefined ) {
+			resolved = def;
+		}
+	
+		if ( plural !== undefined && $.isPlainObject( resolved ) ) {
+			resolved = resolved[ plural ] !== undefined ?
+				resolved[ plural ] :
+				resolved._;
+		}
+	
+		return resolved.replace( '%d', plural ); // nb: plural might be undefined,
+	} );
+
+	/**
+	 * Version string for plug-ins to check compatibility. Allowed format is
+	 * `a.b.c-d` where: a:int, b:int, c:int, d:string(dev|beta|alpha). `d` is used
+	 * only for non-release builds. See http://semver.org/ for more information.
+	 *  @member
+	 *  @type string
+	 *  @default Version number
+	 */
+	DataTable.version = "1.10.13";
+
+	/**
+	 * Private data store, containing all of the settings objects that are
+	 * created for the tables on a given page.
+	 *
+	 * Note that the `DataTable.settings` object is aliased to
+	 * `jQuery.fn.dataTableExt` through which it may be accessed and
+	 * manipulated, or `jQuery.fn.dataTable.settings`.
+	 *  @member
+	 *  @type array
+	 *  @default []
+	 *  @private
+	 */
+	DataTable.settings = [];
+
+	/**
+	 * Object models container, for the various models that DataTables has
+	 * available to it. These models define the objects that are used to hold
+	 * the active state and configuration of the table.
+	 *  @namespace
+	 */
+	DataTable.models = {};
+	
+	
+	
+	/**
+	 * Template object for the way in which DataTables holds information about
+	 * search information for the global filter and individual column filters.
+	 *  @namespace
+	 */
+	DataTable.models.oSearch = {
+		/**
+		 * Flag to indicate if the filtering should be case insensitive or not
+		 *  @type boolean
+		 *  @default true
+		 */
+		"bCaseInsensitive": true,
+	
+		/**
+		 * Applied search term
+		 *  @type string
+		 *  @default <i>Empty string</i>
+		 */
+		"sSearch": "",
+	
+		/**
+		 * Flag to indicate if the search term should be interpreted as a
+		 * regular expression (true) or not (false) and therefore and special
+		 * regex characters escaped.
+		 *  @type boolean
+		 *  @default false
+		 */
+		"bRegex": false,
+	
+		/**
+		 * Flag to indicate if DataTables is to use its smart filtering or not.
+		 *  @type boolean
+		 *  @default true
+		 */
+		"bSmart": true
+	};
+	
+	
+	
+	
+	/**
+	 * Template object for the way in which DataTables holds information about
+	 * each individual row. This is the object format used for the settings
+	 * aoData array.
+	 *  @namespace
+	 */
+	DataTable.models.oRow = {
+		/**
+		 * TR element for the row
+		 *  @type node
+		 *  @default null
+		 */
+		"nTr": null,
+	
+		/**
+		 * Array of TD elements for each row. This is null until the row has been
+		 * created.
+		 *  @type array nodes
+		 *  @default []
+		 */
+		"anCells": null,
+	
+		/**
+		 * Data object from the original data source for the row. This is either
+		 * an array if using the traditional form of DataTables, or an object if
+		 * using mData options. The exact type will depend on the passed in
+		 * data from the data source, or will be an array if using DOM a data
+		 * source.
+		 *  @type array|object
+		 *  @default []
+		 */
+		"_aData": [],
+	
+		/**
+		 * Sorting data cache - this array is ostensibly the same length as the
+		 * number of columns (although each index is generated only as it is
+		 * needed), and holds the data that is used for sorting each column in the
+		 * row. We do this cache generation at the start of the sort in order that
+		 * the formatting of the sort data need be done only once for each cell
+		 * per sort. This array should not be read from or written to by anything
+		 * other than the master sorting methods.
+		 *  @type array
+		 *  @default null
+		 *  @private
+		 */
+		"_aSortData": null,
+	
+		/**
+		 * Per cell filtering data cache. As per the sort data cache, used to
+		 * increase the performance of the filtering in DataTables
+		 *  @type array
+		 *  @default null
+		 *  @private
+		 */
+		"_aFilterData": null,
+	
+		/**
+		 * Filtering data cache. This is the same as the cell filtering cache, but
+		 * in this case a string rather than an array. This is easily computed with
+		 * a join on `_aFilterData`, but is provided as a cache so the join isn't
+		 * needed on every search (memory traded for performance)
+		 *  @type array
+		 *  @default null
+		 *  @private
+		 */
+		"_sFilterRow": null,
+	
+		/**
+		 * Cache of the class name that DataTables has applied to the row, so we
+		 * can quickly look at this variable rather than needing to do a DOM check
+		 * on className for the nTr property.
+		 *  @type string
+		 *  @default <i>Empty string</i>
+		 *  @private
+		 */
+		"_sRowStripe": "",
+	
+		/**
+		 * Denote if the original data source was from the DOM, or the data source
+		 * object. This is used for invalidating data, so DataTables can
+		 * automatically read data from the original source, unless uninstructed
+		 * otherwise.
+		 *  @type string
+		 *  @default null
+		 *  @private
+		 */
+		"src": null,
+	
+		/**
+		 * Index in the aoData array. This saves an indexOf lookup when we have the
+		 * object, but want to know the index
+		 *  @type integer
+		 *  @default -1
+		 *  @private
+		 */
+		"idx": -1
+	};
+	
+	
+	/**
+	 * Template object for the column information object in DataTables. This object
+	 * is held in the settings aoColumns array and contains all the information that
+	 * DataTables needs about each individual column.
+	 *
+	 * Note that this object is related to {@link DataTable.defaults.column}
+	 * but this one is the internal data store for DataTables's cache of columns.
+	 * It should NOT be manipulated outside of DataTables. Any configuration should
+	 * be done through the initialisation options.
+	 *  @namespace
+	 */
+	DataTable.models.oColumn = {
+		/**
+		 * Column index. This could be worked out on-the-fly with $.inArray, but it
+		 * is faster to just hold it as a variable
+		 *  @type integer
+		 *  @default null
+		 */
+		"idx": null,
+	
+		/**
+		 * A list of the columns that sorting should occur on when this column
+		 * is sorted. That this property is an array allows multi-column sorting
+		 * to be defined for a column (for example first name / last name columns
+		 * would benefit from this). The values are integers pointing to the
+		 * columns to be sorted on (typically it will be a single integer pointing
+		 * at itself, but that doesn't need to be the case).
+		 *  @type array
+		 */
+		"aDataSort": null,
+	
+		/**
+		 * Define the sorting directions that are applied to the column, in sequence
+		 * as the column is repeatedly sorted upon - i.e. the first value is used
+		 * as the sorting direction when the column if first sorted (clicked on).
+		 * Sort it again (click again) and it will move on to the next index.
+		 * Repeat until loop.
+		 *  @type array
+		 */
+		"asSorting": null,
+	
+		/**
+		 * Flag to indicate if the column is searchable, and thus should be included
+		 * in the filtering or not.
+		 *  @type boolean
+		 */
+		"bSearchable": null,
+	
+		/**
+		 * Flag to indicate if the column is sortable or not.
+		 *  @type boolean
+		 */
+		"bSortable": null,
+	
+		/**
+		 * Flag to indicate if the column is currently visible in the table or not
+		 *  @type boolean
+		 */
+		"bVisible": null,
+	
+		/**
+		 * Store for manual type assignment using the `column.type` option. This
+		 * is held in store so we can manipulate the column's `sType` property.
+		 *  @type string
+		 *  @default null
+		 *  @private
+		 */
+		"_sManualType": null,
+	
+		/**
+		 * Flag to indicate if HTML5 data attributes should be used as the data
+		 * source for filtering or sorting. True is either are.
+		 *  @type boolean
+		 *  @default false
+		 *  @private
+		 */
+		"_bAttrSrc": false,
+	
+		/**
+		 * Developer definable function that is called whenever a cell is created (Ajax source,
+		 * etc) or processed for input (DOM source). This can be used as a compliment to mRender
+		 * allowing you to modify the DOM element (add background colour for example) when the
+		 * element is available.
+		 *  @type function
+		 *  @param {element} nTd The TD node that has been created
+		 *  @param {*} sData The Data for the cell
+		 *  @param {array|object} oData The data for the whole row
+		 *  @param {int} iRow The row index for the aoData data store
+		 *  @default null
+		 */
+		"fnCreatedCell": null,
+	
+		/**
+		 * Function to get data from a cell in a column. You should <b>never</b>
+		 * access data directly through _aData internally in DataTables - always use
+		 * the method attached to this property. It allows mData to function as
+		 * required. This function is automatically assigned by the column
+		 * initialisation method
+		 *  @type function
+		 *  @param {array|object} oData The data array/object for the array
+		 *    (i.e. aoData[]._aData)
+		 *  @param {string} sSpecific The specific data type you want to get -
+		 *    'display', 'type' 'filter' 'sort'
+		 *  @returns {*} The data for the cell from the given row's data
+		 *  @default null
+		 */
+		"fnGetData": null,
+	
+		/**
+		 * Function to set data for a cell in the column. You should <b>never</b>
+		 * set the data directly to _aData internally in DataTables - always use
+		 * this method. It allows mData to function as required. This function
+		 * is automatically assigned by the column initialisation method
+		 *  @type function
+		 *  @param {array|object} oData The data array/object for the array
+		 *    (i.e. aoData[]._aData)
+		 *  @param {*} sValue Value to set
+		 *  @default null
+		 */
+		"fnSetData": null,
+	
+		/**
+		 * Property to read the value for the cells in the column from the data
+		 * source array / object. If null, then the default content is used, if a
+		 * function is given then the return from the function is used.
+		 *  @type function|int|string|null
+		 *  @default null
+		 */
+		"mData": null,
+	
+		/**
+		 * Partner property to mData which is used (only when defined) to get
+		 * the data - i.e. it is basically the same as mData, but without the
+		 * 'set' option, and also the data fed to it is the result from mData.
+		 * This is the rendering method to match the data method of mData.
+		 *  @type function|int|string|null
+		 *  @default null
+		 */
+		"mRender": null,
+	
+		/**
+		 * Unique header TH/TD element for this column - this is what the sorting
+		 * listener is attached to (if sorting is enabled.)
+		 *  @type node
+		 *  @default null
+		 */
+		"nTh": null,
+	
+		/**
+		 * Unique footer TH/TD element for this column (if there is one). Not used
+		 * in DataTables as such, but can be used for plug-ins to reference the
+		 * footer for each column.
+		 *  @type node
+		 *  @default null
+		 */
+		"nTf": null,
+	
+		/**
+		 * The class to apply to all TD elements in the table's TBODY for the column
+		 *  @type string
+		 *  @default null
+		 */
+		"sClass": null,
+	
+		/**
+		 * When DataTables calculates the column widths to assign to each column,
+		 * it finds the longest string in each column and then constructs a
+		 * temporary table and reads the widths from that. The problem with this
+		 * is that "mmm" is much wider then "iiii", but the latter is a longer
+		 * string - thus the calculation can go wrong (doing it properly and putting
+		 * it into an DOM object and measuring that is horribly(!) slow). Thus as
+		 * a "work around" we provide this option. It will append its value to the
+		 * text that is found to be the longest string for the column - i.e. padding.
+		 *  @type string
+		 */
+		"sContentPadding": null,
+	
+		/**
+		 * Allows a default value to be given for a column's data, and will be used
+		 * whenever a null data source is encountered (this can be because mData
+		 * is set to null, or because the data source itself is null).
+		 *  @type string
+		 *  @default null
+		 */
+		"sDefaultContent": null,
+	
+		/**
+		 * Name for the column, allowing reference to the column by name as well as
+		 * by index (needs a lookup to work by name).
+		 *  @type string
+		 */
+		"sName": null,
+	
+		/**
+		 * Custom sorting data type - defines which of the available plug-ins in
+		 * afnSortData the custom sorting will use - if any is defined.
+		 *  @type string
+		 *  @default std
+		 */
+		"sSortDataType": 'std',
+	
+		/**
+		 * Class to be applied to the header element when sorting on this column
+		 *  @type string
+		 *  @default null
+		 */
+		"sSortingClass": null,
+	
+		/**
+		 * Class to be applied to the header element when sorting on this column -
+		 * when jQuery UI theming is used.
+		 *  @type string
+		 *  @default null
+		 */
+		"sSortingClassJUI": null,
+	
+		/**
+		 * Title of the column - what is seen in the TH element (nTh).
+		 *  @type string
+		 */
+		"sTitle": null,
+	
+		/**
+		 * Column sorting and filtering type
+		 *  @type string
+		 *  @default null
+		 */
+		"sType": null,
+	
+		/**
+		 * Width of the column
+		 *  @type string
+		 *  @default null
+		 */
+		"sWidth": null,
+	
+		/**
+		 * Width of the column when it was first "encountered"
+		 *  @type string
+		 *  @default null
+		 */
+		"sWidthOrig": null
+	};
+	
+	
+	/*
+	 * Developer note: The properties of the object below are given in Hungarian
+	 * notation, that was used as the interface for DataTables prior to v1.10, however
+	 * from v1.10 onwards the primary interface is camel case. In order to avoid
+	 * breaking backwards compatibility utterly with this change, the Hungarian
+	 * version is still, internally the primary interface, but is is not documented
+	 * - hence the @name tags in each doc comment. This allows a Javascript function
+	 * to create a map from Hungarian notation to camel case (going the other direction
+	 * would require each property to be listed, which would at around 3K to the size
+	 * of DataTables, while this method is about a 0.5K hit.
+	 *
+	 * Ultimately this does pave the way for Hungarian notation to be dropped
+	 * completely, but that is a massive amount of work and will break current
+	 * installs (therefore is on-hold until v2).
+	 */
+	
+	/**
+	 * Initialisation options that can be given to DataTables at initialisation
+	 * time.
+	 *  @namespace
+	 */
+	DataTable.defaults = {
+		/**
+		 * An array of data to use for the table, passed in at initialisation which
+		 * will be used in preference to any data which is already in the DOM. This is
+		 * particularly useful for constructing tables purely in Javascript, for
+		 * example with a custom Ajax call.
+		 *  @type array
+		 *  @default null
+		 *
+		 *  @dtopt Option
+		 *  @name DataTable.defaults.data
+		 *
+		 *  @example
+		 *    // Using a 2D array data source
+		 *    $(document).ready( function () {
+		 *      $('#example').dataTable( {
+		 *        "data": [
+		 *          ['Trident', 'Internet Explorer 4.0', 'Win 95+', 4, 'X'],
+		 *          ['Trident', 'Internet Explorer 5.0', 'Win 95+', 5, 'C'],
+		 *        ],
+		 *        "columns": [
+		 *          { "title": "Engine" },
+		 *          { "title": "Browser" },
+		 *          { "title": "Platform" },
+		 *          { "title": "Version" },
+		 *          { "title": "Grade" }
+		 *        ]
+		 *      } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Using an array of objects as a data source (`data`)
+		 *    $(document).ready( function () {
+		 *      $('#example').dataTable( {
+		 *        "data": [
+		 *          {
+		 *            "engine":   "Trident",
+		 *            "browser":  "Internet Explorer 4.0",
+		 *            "platform": "Win 95+",
+		 *            "version":  4,
+		 *            "grade":    "X"
+		 *          },
+		 *          {
+		 *            "engine":   "Trident",
+		 *            "browser":  "Internet Explorer 5.0",
+		 *            "platform": "Win 95+",
+		 *            "version":  5,
+		 *            "grade":    "C"
+		 *          }
+		 *        ],
+		 *        "columns": [
+		 *          { "title": "Engine",   "data": "engine" },
+		 *          { "title": "Browser",  "data": "browser" },
+		 *          { "title": "Platform", "data": "platform" },
+		 *          { "title": "Version",  "data": "version" },
+		 *          { "title": "Grade",    "data": "grade" }
+		 *        ]
+		 *      } );
+		 *    } );
+		 */
+		"aaData": null,
+	
+	
+		/**
+		 * If ordering is enabled, then DataTables will perform a first pass sort on
+		 * initialisation. You can define which column(s) the sort is performed
+		 * upon, and the sorting direction, with this variable. The `sorting` array
+		 * should contain an array for each column to be sorted initially containing
+		 * the column's index and a direction string ('asc' or 'desc').
+		 *  @type array
+		 *  @default [[0,'asc']]
+		 *
+		 *  @dtopt Option
+		 *  @name DataTable.defaults.order
+		 *
+		 *  @example
+		 *    // Sort by 3rd column first, and then 4th column
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "order": [[2,'asc'], [3,'desc']]
+		 *      } );
+		 *    } );
+		 *
+		 *    // No initial sorting
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "order": []
+		 *      } );
+		 *    } );
+		 */
+		"aaSorting": [[0,'asc']],
+	
+	
+		/**
+		 * This parameter is basically identical to the `sorting` parameter, but
+		 * cannot be overridden by user interaction with the table. What this means
+		 * is that you could have a column (visible or hidden) which the sorting
+		 * will always be forced on first - any sorting after that (from the user)
+		 * will then be performed as required. This can be useful for grouping rows
+		 * together.
+		 *  @type array
+		 *  @default null
+		 *
+		 *  @dtopt Option
+		 *  @name DataTable.defaults.orderFixed
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "orderFixed": [[0,'asc']]
+		 *      } );
+		 *    } )
+		 */
+		"aaSortingFixed": [],
+	
+	
+		/**
+		 * DataTables can be instructed to load data to display in the table from a
+		 * Ajax source. This option defines how that Ajax call is made and where to.
+		 *
+		 * The `ajax` property has three different modes of operation, depending on
+		 * how it is defined. These are:
+		 *
+		 * * `string` - Set the URL from where the data should be loaded from.
+		 * * `object` - Define properties for `jQuery.ajax`.
+		 * * `function` - Custom data get function
+		 *
+		 * `string`
+		 * --------
+		 *
+		 * As a string, the `ajax` property simply defines the URL from which
+		 * DataTables will load data.
+		 *
+		 * `object`
+		 * --------
+		 *
+		 * As an object, the parameters in the object are passed to
+		 * [jQuery.ajax](http://api.jquery.com/jQuery.ajax/) allowing fine control
+		 * of the Ajax request. DataTables has a number of default parameters which
+		 * you can override using this option. Please refer to the jQuery
+		 * documentation for a full description of the options available, although
+		 * the following parameters provide additional options in DataTables or
+		 * require special consideration:
+		 *
+		 * * `data` - As with jQuery, `data` can be provided as an object, but it
+		 *   can also be used as a function to manipulate the data DataTables sends
+		 *   to the server. The function takes a single parameter, an object of
+		 *   parameters with the values that DataTables has readied for sending. An
+		 *   object may be returned which will be merged into the DataTables
+		 *   defaults, or you can add the items to the object that was passed in and
+		 *   not return anything from the function. This supersedes `fnServerParams`
+		 *   from DataTables 1.9-.
+		 *
+		 * * `dataSrc` - By default DataTables will look for the property `data` (or
+		 *   `aaData` for compatibility with DataTables 1.9-) when obtaining data
+		 *   from an Ajax source or for server-side processing - this parameter
+		 *   allows that property to be changed. You can use Javascript dotted
+		 *   object notation to get a data source for multiple levels of nesting, or
+		 *   it my be used as a function. As a function it takes a single parameter,
+		 *   the JSON returned from the server, which can be manipulated as
+		 *   required, with the returned value being that used by DataTables as the
+		 *   data source for the table. This supersedes `sAjaxDataProp` from
+		 *   DataTables 1.9-.
+		 *
+		 * * `success` - Should not be overridden it is used internally in
+		 *   DataTables. To manipulate / transform the data returned by the server
+		 *   use `ajax.dataSrc`, or use `ajax` as a function (see below).
+		 *
+		 * `function`
+		 * ----------
+		 *
+		 * As a function, making the Ajax call is left up to yourself allowing
+		 * complete control of the Ajax request. Indeed, if desired, a method other
+		 * than Ajax could be used to obtain the required data, such as Web storage
+		 * or an AIR database.
+		 *
+		 * The function is given four parameters and no return is required. The
+		 * parameters are:
+		 *
+		 * 1. _object_ - Data to send to the server
+		 * 2. _function_ - Callback function that must be executed when the required
+		 *    data has been obtained. That data should be passed into the callback
+		 *    as the only parameter
+		 * 3. _object_ - DataTables settings object for the table
+		 *
+		 * Note that this supersedes `fnServerData` from DataTables 1.9-.
+		 *
+		 *  @type string|object|function
+		 *  @default null
+		 *
+		 *  @dtopt Option
+		 *  @name DataTable.defaults.ajax
+		 *  @since 1.10.0
+		 *
+		 * @example
+		 *   // Get JSON data from a file via Ajax.
+		 *   // Note DataTables expects data in the form `{ data: [ ...data... ] }` by default).
+		 *   $('#example').dataTable( {
+		 *     "ajax": "data.json"
+		 *   } );
+		 *
+		 * @example
+		 *   // Get JSON data from a file via Ajax, using `dataSrc` to change
+		 *   // `data` to `tableData` (i.e. `{ tableData: [ ...data... ] }`)
+		 *   $('#example').dataTable( {
+		 *     "ajax": {
+		 *       "url": "data.json",
+		 *       "dataSrc": "tableData"
+		 *     }
+		 *   } );
+		 *
+		 * @example
+		 *   // Get JSON data from a file via Ajax, using `dataSrc` to read data
+		 *   // from a plain array rather than an array in an object
+		 *   $('#example').dataTable( {
+		 *     "ajax": {
+		 *       "url": "data.json",
+		 *       "dataSrc": ""
+		 *     }
+		 *   } );
+		 *
+		 * @example
+		 *   // Manipulate the data returned from the server - add a link to data
+		 *   // (note this can, should, be done using `render` for the column - this
+		 *   // is just a simple example of how the data can be manipulated).
+		 *   $('#example').dataTable( {
+		 *     "ajax": {
+		 *       "url": "data.json",
+		 *       "dataSrc": function ( json ) {
+		 *         for ( var i=0, ien=json.length ; i<ien ; i++ ) {
+		 *           json[i][0] = '<a href="/message/'+json[i][0]+'>View message</a>';
+		 *         }
+		 *         return json;
+		 *       }
+		 *     }
+		 *   } );
+		 *
+		 * @example
+		 *   // Add data to the request
+		 *   $('#example').dataTable( {
+		 *     "ajax": {
+		 *       "url": "data.json",
+		 *       "data": function ( d ) {
+		 *         return {
+		 *           "extra_search": $('#extra').val()
+		 *         };
+		 *       }
+		 *     }
+		 *   } );
+		 *
+		 * @example
+		 *   // Send request as POST
+		 *   $('#example').dataTable( {
+		 *     "ajax": {
+		 *       "url": "data.json",
+		 *       "type": "POST"
+		 *     }
+		 *   } );
+		 *
+		 * @example
+		 *   // Get the data from localStorage (could interface with a form for
+		 *   // adding, editing and removing rows).
+		 *   $('#example').dataTable( {
+		 *     "ajax": function (data, callback, settings) {
+		 *       callback(
+		 *         JSON.parse( localStorage.getItem('dataTablesData') )
+		 *       );
+		 *     }
+		 *   } );
+		 */
+		"ajax": null,
+	
+	
+		/**
+		 * This parameter allows you to readily specify the entries in the length drop
+		 * down menu that DataTables shows when pagination is enabled. It can be
+		 * either a 1D array of options which will be used for both the displayed
+		 * option and the value, or a 2D array which will use the array in the first
+		 * position as the value, and the array in the second position as the
+		 * displayed options (useful for language strings such as 'All').
+		 *
+		 * Note that the `pageLength` property will be automatically set to the
+		 * first value given in this array, unless `pageLength` is also provided.
+		 *  @type array
+		 *  @default [ 10, 25, 50, 100 ]
+		 *
+		 *  @dtopt Option
+		 *  @name DataTable.defaults.lengthMenu
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "lengthMenu": [[10, 25, 50, -1], [10, 25, 50, "All"]]
+		 *      } );
+		 *    } );
+		 */
+		"aLengthMenu": [ 10, 25, 50, 100 ],
+	
+	
+		/**
+		 * The `columns` option in the initialisation parameter allows you to define
+		 * details about the way individual columns behave. For a full list of
+		 * column options that can be set, please see
+		 * {@link DataTable.defaults.column}. Note that if you use `columns` to
+		 * define your columns, you must have an entry in the array for every single
+		 * column that you have in your table (these can be null if you don't which
+		 * to specify any options).
+		 *  @member
+		 *
+		 *  @name DataTable.defaults.column
+		 */
+		"aoColumns": null,
+	
+		/**
+		 * Very similar to `columns`, `columnDefs` allows you to target a specific
+		 * column, multiple columns, or all columns, using the `targets` property of
+		 * each object in the array. This allows great flexibility when creating
+		 * tables, as the `columnDefs` arrays can be of any length, targeting the
+		 * columns you specifically want. `columnDefs` may use any of the column
+		 * options available: {@link DataTable.defaults.column}, but it _must_
+		 * have `targets` defined in each object in the array. Values in the `targets`
+		 * array may be:
+		 *   <ul>
+		 *     <li>a string - class name will be matched on the TH for the column</li>
+		 *     <li>0 or a positive integer - column index counting from the left</li>
+		 *     <li>a negative integer - column index counting from the right</li>
+		 *     <li>the string "_all" - all columns (i.e. assign a default)</li>
+		 *   </ul>
+		 *  @member
+		 *
+		 *  @name DataTable.defaults.columnDefs
+		 */
+		"aoColumnDefs": null,
+	
+	
+		/**
+		 * Basically the same as `search`, this parameter defines the individual column
+		 * filtering state at initialisation time. The array must be of the same size
+		 * as the number of columns, and each element be an object with the parameters
+		 * `search` and `escapeRegex` (the latter is optional). 'null' is also
+		 * accepted and the default will be used.
+		 *  @type array
+		 *  @default []
+		 *
+		 *  @dtopt Option
+		 *  @name DataTable.defaults.searchCols
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "searchCols": [
+		 *          null,
+		 *          { "search": "My filter" },
+		 *          null,
+		 *          { "search": "^[0-9]", "escapeRegex": false }
+		 *        ]
+		 *      } );
+		 *    } )
+		 */
+		"aoSearchCols": [],
+	
+	
+		/**
+		 * An array of CSS classes that should be applied to displayed rows. This
+		 * array may be of any length, and DataTables will apply each class
+		 * sequentially, looping when required.
+		 *  @type array
+		 *  @default null <i>Will take the values determined by the `oClasses.stripe*`
+		 *    options</i>
+		 *
+		 *  @dtopt Option
+		 *  @name DataTable.defaults.stripeClasses
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "stripeClasses": [ 'strip1', 'strip2', 'strip3' ]
+		 *      } );
+		 *    } )
+		 */
+		"asStripeClasses": null,
+	
+	
+		/**
+		 * Enable or disable automatic column width calculation. This can be disabled
+		 * as an optimisation (it takes some time to calculate the widths) if the
+		 * tables widths are passed in using `columns`.
+		 *  @type boolean
+		 *  @default true
+		 *
+		 *  @dtopt Features
+		 *  @name DataTable.defaults.autoWidth
+		 *
+		 *  @example
+		 *    $(document).ready( function () {
+		 *      $('#example').dataTable( {
+		 *        "autoWidth": false
+		 *      } );
+		 *    } );
+		 */
+		"bAutoWidth": true,
+	
+	
+		/**
+		 * Deferred rendering can provide DataTables with a huge speed boost when you
+		 * are using an Ajax or JS data source for the table. This option, when set to
+		 * true, will cause DataTables to defer the creation of the table elements for
+		 * each row until they are needed for a draw - saving a significant amount of
+		 * time.
+		 *  @type boolean
+		 *  @default false
+		 *
+		 *  @dtopt Features
+		 *  @name DataTable.defaults.deferRender
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "ajax": "sources/arrays.txt",
+		 *        "deferRender": true
+		 *      } );
+		 *    } );
+		 */
+		"bDeferRender": false,
+	
+	
+		/**
+		 * Replace a DataTable which matches the given selector and replace it with
+		 * one which has the properties of the new initialisation object passed. If no
+		 * table matches the selector, then the new DataTable will be constructed as
+		 * per normal.
+		 *  @type boolean
+		 *  @default false
+		 *
+		 *  @dtopt Options
+		 *  @name DataTable.defaults.destroy
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "srollY": "200px",
+		 *        "paginate": false
+		 *      } );
+		 *
+		 *      // Some time later....
+		 *      $('#example').dataTable( {
+		 *        "filter": false,
+		 *        "destroy": true
+		 *      } );
+		 *    } );
+		 */
+		"bDestroy": false,
+	
+	
+		/**
+		 * Enable or disable filtering of data. Filtering in DataTables is "smart" in
+		 * that it allows the end user to input multiple words (space separated) and
+		 * will match a row containing those words, even if not in the order that was
+		 * specified (this allow matching across multiple columns). Note that if you
+		 * wish to use filtering in DataTables this must remain 'true' - to remove the
+		 * default filtering input box and retain filtering abilities, please use
+		 * {@link DataTable.defaults.dom}.
+		 *  @type boolean
+		 *  @default true
+		 *
+		 *  @dtopt Features
+		 *  @name DataTable.defaults.searching
+		 *
+		 *  @example
+		 *    $(document).ready( function () {
+		 *      $('#example').dataTable( {
+		 *        "searching": false
+		 *      } );
+		 *    } );
+		 */
+		"bFilter": true,
+	
+	
+		/**
+		 * Enable or disable the table information display. This shows information
+		 * about the data that is currently visible on the page, including information
+		 * about filtered data if that action is being performed.
+		 *  @type boolean
+		 *  @default true
+		 *
+		 *  @dtopt Features
+		 *  @name DataTable.defaults.info
+		 *
+		 *  @example
+		 *    $(document).ready( function () {
+		 *      $('#example').dataTable( {
+		 *        "info": false
+		 *      } );
+		 *    } );
+		 */
+		"bInfo": true,
+	
+	
+		/**
+		 * Enable jQuery UI ThemeRoller support (required as ThemeRoller requires some
+		 * slightly different and additional mark-up from what DataTables has
+		 * traditionally used).
+		 *  @type boolean
+		 *  @default false
+		 *
+		 *  @dtopt Features
+		 *  @name DataTable.defaults.jQueryUI
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "jQueryUI": true
+		 *      } );
+		 *    } );
+		 */
+		"bJQueryUI": false,
+	
+	
+		/**
+		 * Allows the end user to select the size of a formatted page from a select
+		 * menu (sizes are 10, 25, 50 and 100). Requires pagination (`paginate`).
+		 *  @type boolean
+		 *  @default true
+		 *
+		 *  @dtopt Features
+		 *  @name DataTable.defaults.lengthChange
+		 *
+		 *  @example
+		 *    $(document).ready( function () {
+		 *      $('#example').dataTable( {
+		 *        "lengthChange": false
+		 *      } );
+		 *    } );
+		 */
+		"bLengthChange": true,
+	
+	
+		/**
+		 * Enable or disable pagination.
+		 *  @type boolean
+		 *  @default true
+		 *
+		 *  @dtopt Features
+		 *  @name DataTable.defaults.paging
+		 *
+		 *  @example
+		 *    $(document).ready( function () {
+		 *      $('#example').dataTable( {
+		 *        "paging": false
+		 *      } );
+		 *    } );
+		 */
+		"bPaginate": true,
+	
+	
+		/**
+		 * Enable or disable the display of a 'processing' indicator when the table is
+		 * being processed (e.g. a sort). This is particularly useful for tables with
+		 * large amounts of data where it can take a noticeable amount of time to sort
+		 * the entries.
+		 *  @type boolean
+		 *  @default false
+		 *
+		 *  @dtopt Features
+		 *  @name DataTable.defaults.processing
+		 *
+		 *  @example
+		 *    $(document).ready( function () {
+		 *      $('#example').dataTable( {
+		 *        "processing": true
+		 *      } );
+		 *    } );
+		 */
+		"bProcessing": false,
+	
+	
+		/**
+		 * Retrieve the DataTables object for the given selector. Note that if the
+		 * table has already been initialised, this parameter will cause DataTables
+		 * to simply return the object that has already been set up - it will not take
+		 * account of any changes you might have made to the initialisation object
+		 * passed to DataTables (setting this parameter to true is an acknowledgement
+		 * that you understand this). `destroy` can be used to reinitialise a table if
+		 * you need.
+		 *  @type boolean
+		 *  @default false
+		 *
+		 *  @dtopt Options
+		 *  @name DataTable.defaults.retrieve
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      initTable();
+		 *      tableActions();
+		 *    } );
+		 *
+		 *    function initTable ()
+		 *    {
+		 *      return $('#example').dataTable( {
+		 *        "scrollY": "200px",
+		 *        "paginate": false,
+		 *        "retrieve": true
+		 *      } );
+		 *    }
+		 *
+		 *    function tableActions ()
+		 *    {
+		 *      var table = initTable();
+		 *      // perform API operations with oTable
+		 *    }
+		 */
+		"bRetrieve": false,
+	
+	
+		/**
+		 * When vertical (y) scrolling is enabled, DataTables will force the height of
+		 * the table's viewport to the given height at all times (useful for layout).
+		 * However, this can look odd when filtering data down to a small data set,
+		 * and the footer is left "floating" further down. This parameter (when
+		 * enabled) will cause DataTables to collapse the table's viewport down when
+		 * the result set will fit within the given Y height.
+		 *  @type boolean
+		 *  @default false
+		 *
+		 *  @dtopt Options
+		 *  @name DataTable.defaults.scrollCollapse
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "scrollY": "200",
+		 *        "scrollCollapse": true
+		 *      } );
+		 *    } );
+		 */
+		"bScrollCollapse": false,
+	
+	
+		/**
+		 * Configure DataTables to use server-side processing. Note that the
+		 * `ajax` parameter must also be given in order to give DataTables a
+		 * source to obtain the required data for each draw.
+		 *  @type boolean
+		 *  @default false
+		 *
+		 *  @dtopt Features
+		 *  @dtopt Server-side
+		 *  @name DataTable.defaults.serverSide
+		 *
+		 *  @example
+		 *    $(document).ready( function () {
+		 *      $('#example').dataTable( {
+		 *        "serverSide": true,
+		 *        "ajax": "xhr.php"
+		 *      } );
+		 *    } );
+		 */
+		"bServerSide": false,
+	
+	
+		/**
+		 * Enable or disable sorting of columns. Sorting of individual columns can be
+		 * disabled by the `sortable` option for each column.
+		 *  @type boolean
+		 *  @default true
+		 *
+		 *  @dtopt Features
+		 *  @name DataTable.defaults.ordering
+		 *
+		 *  @example
+		 *    $(document).ready( function () {
+		 *      $('#example').dataTable( {
+		 *        "ordering": false
+		 *      } );
+		 *    } );
+		 */
+		"bSort": true,
+	
+	
+		/**
+		 * Enable or display DataTables' ability to sort multiple columns at the
+		 * same time (activated by shift-click by the user).
+		 *  @type boolean
+		 *  @default true
+		 *
+		 *  @dtopt Options
+		 *  @name DataTable.defaults.orderMulti
+		 *
+		 *  @example
+		 *    // Disable multiple column sorting ability
+		 *    $(document).ready( function () {
+		 *      $('#example').dataTable( {
+		 *        "orderMulti": false
+		 *      } );
+		 *    } );
+		 */
+		"bSortMulti": true,
+	
+	
+		/**
+		 * Allows control over whether DataTables should use the top (true) unique
+		 * cell that is found for a single column, or the bottom (false - default).
+		 * This is useful when using complex headers.
+		 *  @type boolean
+		 *  @default false
+		 *
+		 *  @dtopt Options
+		 *  @name DataTable.defaults.orderCellsTop
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "orderCellsTop": true
+		 *      } );
+		 *    } );
+		 */
+		"bSortCellsTop": false,
+	
+	
+		/**
+		 * Enable or disable the addition of the classes `sorting\_1`, `sorting\_2` and
+		 * `sorting\_3` to the columns which are currently being sorted on. This is
+		 * presented as a feature switch as it can increase processing time (while
+		 * classes are removed and added) so for large data sets you might want to
+		 * turn this off.
+		 *  @type boolean
+		 *  @default true
+		 *
+		 *  @dtopt Features
+		 *  @name DataTable.defaults.orderClasses
+		 *
+		 *  @example
+		 *    $(document).ready( function () {
+		 *      $('#example').dataTable( {
+		 *        "orderClasses": false
+		 *      } );
+		 *    } );
+		 */
+		"bSortClasses": true,
+	
+	
+		/**
+		 * Enable or disable state saving. When enabled HTML5 `localStorage` will be
+		 * used to save table display information such as pagination information,
+		 * display length, filtering and sorting. As such when the end user reloads
+		 * the page the display display will match what thy had previously set up.
+		 *
+		 * Due to the use of `localStorage` the default state saving is not supported
+		 * in IE6 or 7. If state saving is required in those browsers, use
+		 * `stateSaveCallback` to provide a storage solution such as cookies.
+		 *  @type boolean
+		 *  @default false
+		 *
+		 *  @dtopt Features
+		 *  @name DataTable.defaults.stateSave
+		 *
+		 *  @example
+		 *    $(document).ready( function () {
+		 *      $('#example').dataTable( {
+		 *        "stateSave": true
+		 *      } );
+		 *    } );
+		 */
+		"bStateSave": false,
+	
+	
+		/**
+		 * This function is called when a TR element is created (and all TD child
+		 * elements have been inserted), or registered if using a DOM source, allowing
+		 * manipulation of the TR element (adding classes etc).
+		 *  @type function
+		 *  @param {node} row "TR" element for the current row
+		 *  @param {array} data Raw data array for this row
+		 *  @param {int} dataIndex The index of this row in the internal aoData array
+		 *
+		 *  @dtopt Callbacks
+		 *  @name DataTable.defaults.createdRow
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "createdRow": function( row, data, dataIndex ) {
+		 *          // Bold the grade for all 'A' grade browsers
+		 *          if ( data[4] == "A" )
+		 *          {
+		 *            $('td:eq(4)', row).html( '<b>A</b>' );
+		 *          }
+		 *        }
+		 *      } );
+		 *    } );
+		 */
+		"fnCreatedRow": null,
+	
+	
+		/**
+		 * This function is called on every 'draw' event, and allows you to
+		 * dynamically modify any aspect you want about the created DOM.
+		 *  @type function
+		 *  @param {object} settings DataTables settings object
+		 *
+		 *  @dtopt Callbacks
+		 *  @name DataTable.defaults.drawCallback
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "drawCallback": function( settings ) {
+		 *          alert( 'DataTables has redrawn the table' );
+		 *        }
+		 *      } );
+		 *    } );
+		 */
+		"fnDrawCallback": null,
+	
+	
+		/**
+		 * Identical to fnHeaderCallback() but for the table footer this function
+		 * allows you to modify the table footer on every 'draw' event.
+		 *  @type function
+		 *  @param {node} foot "TR" element for the footer
+		 *  @param {array} data Full table data (as derived from the original HTML)
+		 *  @param {int} start Index for the current display starting point in the
+		 *    display array
+		 *  @param {int} end Index for the current display ending point in the
+		 *    display array
+		 *  @param {array int} display Index array to translate the visual position
+		 *    to the full data array
+		 *
+		 *  @dtopt Callbacks
+		 *  @name DataTable.defaults.footerCallback
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "footerCallback": function( tfoot, data, start, end, display ) {
+		 *          tfoot.getElementsByTagName('th')[0].innerHTML = "Starting index is "+start;
+		 *        }
+		 *      } );
+		 *    } )
+		 */
+		"fnFooterCallback": null,
+	
+	
+		/**
+		 * When rendering large numbers in the information element for the table
+		 * (i.e. "Showing 1 to 10 of 57 entries") DataTables will render large numbers
+		 * to have a comma separator for the 'thousands' units (e.g. 1 million is
+		 * rendered as "1,000,000") to help readability for the end user. This
+		 * function will override the default method DataTables uses.
+		 *  @type function
+		 *  @member
+		 *  @param {int} toFormat number to be formatted
+		 *  @returns {string} formatted string for DataTables to show the number
+		 *
+		 *  @dtopt Callbacks
+		 *  @name DataTable.defaults.formatNumber
+		 *
+		 *  @example
+		 *    // Format a number using a single quote for the separator (note that
+		 *    // this can also be done with the language.thousands option)
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "formatNumber": function ( toFormat ) {
+		 *          return toFormat.toString().replace(
+		 *            /\B(?=(\d{3})+(?!\d))/g, "'"
+		 *          );
+		 *        };
+		 *      } );
+		 *    } );
+		 */
+		"fnFormatNumber": function ( toFormat ) {
+			return toFormat.toString().replace(
+				/\B(?=(\d{3})+(?!\d))/g,
+				this.oLanguage.sThousands
+			);
+		},
+	
+	
+		/**
+		 * This function is called on every 'draw' event, and allows you to
+		 * dynamically modify the header row. This can be used to calculate and
+		 * display useful information about the table.
+		 *  @type function
+		 *  @param {node} head "TR" element for the header
+		 *  @param {array} data Full table data (as derived from the original HTML)
+		 *  @param {int} start Index for the current display starting point in the
+		 *    display array
+		 *  @param {int} end Index for the current display ending point in the
+		 *    display array
+		 *  @param {array int} display Index array to translate the visual position
+		 *    to the full data array
+		 *
+		 *  @dtopt Callbacks
+		 *  @name DataTable.defaults.headerCallback
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "fheaderCallback": function( head, data, start, end, display ) {
+		 *          head.getElementsByTagName('th')[0].innerHTML = "Displaying "+(end-start)+" records";
+		 *        }
+		 *      } );
+		 *    } )
+		 */
+		"fnHeaderCallback": null,
+	
+	
+		/**
+		 * The information element can be used to convey information about the current
+		 * state of the table. Although the internationalisation options presented by
+		 * DataTables are quite capable of dealing with most customisations, there may
+		 * be times where you wish to customise the string further. This callback
+		 * allows you to do exactly that.
+		 *  @type function
+		 *  @param {object} oSettings DataTables settings object
+		 *  @param {int} start Starting position in data for the draw
+		 *  @param {int} end End position in data for the draw
+		 *  @param {int} max Total number of rows in the table (regardless of
+		 *    filtering)
+		 *  @param {int} total Total number of rows in the data set, after filtering
+		 *  @param {string} pre The string that DataTables has formatted using it's
+		 *    own rules
+		 *  @returns {string} The string to be displayed in the information element.
+		 *
+		 *  @dtopt Callbacks
+		 *  @name DataTable.defaults.infoCallback
+		 *
+		 *  @example
+		 *    $('#example').dataTable( {
+		 *      "infoCallback": function( settings, start, end, max, total, pre ) {
+		 *        return start +" to "+ end;
+		 *      }
+		 *    } );
+		 */
+		"fnInfoCallback": null,
+	
+	
+		/**
+		 * Called when the table has been initialised. Normally DataTables will
+		 * initialise sequentially and there will be no need for this function,
+		 * however, this does not hold true when using external language information
+		 * since that is obtained using an async XHR call.
+		 *  @type function
+		 *  @param {object} settings DataTables settings object
+		 *  @param {object} json The JSON object request from the server - only
+		 *    present if client-side Ajax sourced data is used
+		 *
+		 *  @dtopt Callbacks
+		 *  @name DataTable.defaults.initComplete
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "initComplete": function(settings, json) {
+		 *          alert( 'DataTables has finished its initialisation.' );
+		 *        }
+		 *      } );
+		 *    } )
+		 */
+		"fnInitComplete": null,
+	
+	
+		/**
+		 * Called at the very start of each table draw and can be used to cancel the
+		 * draw by returning false, any other return (including undefined) results in
+		 * the full draw occurring).
+		 *  @type function
+		 *  @param {object} settings DataTables settings object
+		 *  @returns {boolean} False will cancel the draw, anything else (including no
+		 *    return) will allow it to complete.
+		 *
+		 *  @dtopt Callbacks
+		 *  @name DataTable.defaults.preDrawCallback
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "preDrawCallback": function( settings ) {
+		 *          if ( $('#test').val() == 1 ) {
+		 *            return false;
+		 *          }
+		 *        }
+		 *      } );
+		 *    } );
+		 */
+		"fnPreDrawCallback": null,
+	
+	
+		/**
+		 * This function allows you to 'post process' each row after it have been
+		 * generated for each table draw, but before it is rendered on screen. This
+		 * function might be used for setting the row class name etc.
+		 *  @type function
+		 *  @param {node} row "TR" element for the current row
+		 *  @param {array} data Raw data array for this row
+		 *  @param {int} displayIndex The display index for the current table draw
+		 *  @param {int} displayIndexFull The index of the data in the full list of
+		 *    rows (after filtering)
+		 *
+		 *  @dtopt Callbacks
+		 *  @name DataTable.defaults.rowCallback
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "rowCallback": function( row, data, displayIndex, displayIndexFull ) {
+		 *          // Bold the grade for all 'A' grade browsers
+		 *          if ( data[4] == "A" ) {
+		 *            $('td:eq(4)', row).html( '<b>A</b>' );
+		 *          }
+		 *        }
+		 *      } );
+		 *    } );
+		 */
+		"fnRowCallback": null,
+	
+	
+		/**
+		 * __Deprecated__ The functionality provided by this parameter has now been
+		 * superseded by that provided through `ajax`, which should be used instead.
+		 *
+		 * This parameter allows you to override the default function which obtains
+		 * the data from the server so something more suitable for your application.
+		 * For example you could use POST data, or pull information from a Gears or
+		 * AIR database.
+		 *  @type function
+		 *  @member
+		 *  @param {string} source HTTP source to obtain the data from (`ajax`)
+		 *  @param {array} data A key/value pair object containing the data to send
+		 *    to the server
+		 *  @param {function} callback to be called on completion of the data get
+		 *    process that will draw the data on the page.
+		 *  @param {object} settings DataTables settings object
+		 *
+		 *  @dtopt Callbacks
+		 *  @dtopt Server-side
+		 *  @name DataTable.defaults.serverData
+		 *
+		 *  @deprecated 1.10. Please use `ajax` for this functionality now.
+		 */
+		"fnServerData": null,
+	
+	
+		/**
+		 * __Deprecated__ The functionality provided by this parameter has now been
+		 * superseded by that provided through `ajax`, which should be used instead.
+		 *
+		 *  It is often useful to send extra data to the server when making an Ajax
+		 * request - for example custom filtering information, and this callback
+		 * function makes it trivial to send extra information to the server. The
+		 * passed in parameter is the data set that has been constructed by
+		 * DataTables, and you can add to this or modify it as you require.
+		 *  @type function
+		 *  @param {array} data Data array (array of objects which are name/value
+		 *    pairs) that has been constructed by DataTables and will be sent to the
+		 *    server. In the case of Ajax sourced data with server-side processing
+		 *    this will be an empty array, for server-side processing there will be a
+		 *    significant number of parameters!
+		 *  @returns {undefined} Ensure that you modify the data array passed in,
+		 *    as this is passed by reference.
+		 *
+		 *  @dtopt Callbacks
+		 *  @dtopt Server-side
+		 *  @name DataTable.defaults.serverParams
+		 *
+		 *  @deprecated 1.10. Please use `ajax` for this functionality now.
+		 */
+		"fnServerParams": null,
+	
+	
+		/**
+		 * Load the table state. With this function you can define from where, and how, the
+		 * state of a table is loaded. By default DataTables will load from `localStorage`
+		 * but you might wish to use a server-side database or cookies.
+		 *  @type function
+		 *  @member
+		 *  @param {object} settings DataTables settings object
+		 *  @param {object} callback Callback that can be executed when done. It
+		 *    should be passed the loaded state object.
+		 *  @return {object} The DataTables state object to be loaded
+		 *
+		 *  @dtopt Callbacks
+		 *  @name DataTable.defaults.stateLoadCallback
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "stateSave": true,
+		 *        "stateLoadCallback": function (settings, callback) {
+		 *          $.ajax( {
+		 *            "url": "/state_load",
+		 *            "dataType": "json",
+		 *            "success": function (json) {
+		 *              callback( json );
+		 *            }
+		 *          } );
+		 *        }
+		 *      } );
+		 *    } );
+		 */
+		"fnStateLoadCallback": function ( settings ) {
+			try {
+				return JSON.parse(
+					(settings.iStateDuration === -1 ? sessionStorage : localStorage).getItem(
+						'DataTables_'+settings.sInstance+'_'+location.pathname
+					)
+				);
+			} catch (e) {}
+		},
+	
+	
+		/**
+		 * Callback which allows modification of the saved state prior to loading that state.
+		 * This callback is called when the table is loading state from the stored data, but
+		 * prior to the settings object being modified by the saved state. Note that for
+		 * plug-in authors, you should use the `stateLoadParams` event to load parameters for
+		 * a plug-in.
+		 *  @type function
+		 *  @param {object} settings DataTables settings object
+		 *  @param {object} data The state object that is to be loaded
+		 *
+		 *  @dtopt Callbacks
+		 *  @name DataTable.defaults.stateLoadParams
+		 *
+		 *  @example
+		 *    // Remove a saved filter, so filtering is never loaded
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "stateSave": true,
+		 *        "stateLoadParams": function (settings, data) {
+		 *          data.oSearch.sSearch = "";
+		 *        }
+		 *      } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Disallow state loading by returning false
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "stateSave": true,
+		 *        "stateLoadParams": function (settings, data) {
+		 *          return false;
+		 *        }
+		 *      } );
+		 *    } );
+		 */
+		"fnStateLoadParams": null,
+	
+	
+		/**
+		 * Callback that is called when the state has been loaded from the state saving method
+		 * and the DataTables settings object has been modified as a result of the loaded state.
+		 *  @type function
+		 *  @param {object} settings DataTables settings object
+		 *  @param {object} data The state object that was loaded
+		 *
+		 *  @dtopt Callbacks
+		 *  @name DataTable.defaults.stateLoaded
+		 *
+		 *  @example
+		 *    // Show an alert with the filtering value that was saved
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "stateSave": true,
+		 *        "stateLoaded": function (settings, data) {
+		 *          alert( 'Saved filter was: '+data.oSearch.sSearch );
+		 *        }
+		 *      } );
+		 *    } );
+		 */
+		"fnStateLoaded": null,
+	
+	
+		/**
+		 * Save the table state. This function allows you to define where and how the state
+		 * information for the table is stored By default DataTables will use `localStorage`
+		 * but you might wish to use a server-side database or cookies.
+		 *  @type function
+		 *  @member
+		 *  @param {object} settings DataTables settings object
+		 *  @param {object} data The state object to be saved
+		 *
+		 *  @dtopt Callbacks
+		 *  @name DataTable.defaults.stateSaveCallback
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "stateSave": true,
+		 *        "stateSaveCallback": function (settings, data) {
+		 *          // Send an Ajax request to the server with the state object
+		 *          $.ajax( {
+		 *            "url": "/state_save",
+		 *            "data": data,
+		 *            "dataType": "json",
+		 *            "method": "POST"
+		 *            "success": function () {}
+		 *          } );
+		 *        }
+		 *      } );
+		 *    } );
+		 */
+		"fnStateSaveCallback": function ( settings, data ) {
+			try {
+				(settings.iStateDuration === -1 ? sessionStorage : localStorage).setItem(
+					'DataTables_'+settings.sInstance+'_'+location.pathname,
+					JSON.stringify( data )
+				);
+			} catch (e) {}
+		},
+	
+	
+		/**
+		 * Callback which allows modification of the state to be saved. Called when the table
+		 * has changed state a new state save is required. This method allows modification of
+		 * the state saving object prior to actually doing the save, including addition or
+		 * other state properties or modification. Note that for plug-in authors, you should
+		 * use the `stateSaveParams` event to save parameters for a plug-in.
+		 *  @type function
+		 *  @param {object} settings DataTables settings object
+		 *  @param {object} data The state object to be saved
+		 *
+		 *  @dtopt Callbacks
+		 *  @name DataTable.defaults.stateSaveParams
+		 *
+		 *  @example
+		 *    // Remove a saved filter, so filtering is never saved
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "stateSave": true,
+		 *        "stateSaveParams": function (settings, data) {
+		 *          data.oSearch.sSearch = "";
+		 *        }
+		 *      } );
+		 *    } );
+		 */
+		"fnStateSaveParams": null,
+	
+	
+		/**
+		 * Duration for which the saved state information is considered valid. After this period
+		 * has elapsed the state will be returned to the default.
+		 * Value is given in seconds.
+		 *  @type int
+		 *  @default 7200 <i>(2 hours)</i>
+		 *
+		 *  @dtopt Options
+		 *  @name DataTable.defaults.stateDuration
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "stateDuration": 60*60*24; // 1 day
+		 *      } );
+		 *    } )
+		 */
+		"iStateDuration": 7200,
+	
+	
+		/**
+		 * When enabled DataTables will not make a request to the server for the first
+		 * page draw - rather it will use the data already on the page (no sorting etc
+		 * will be applied to it), thus saving on an XHR at load time. `deferLoading`
+		 * is used to indicate that deferred loading is required, but it is also used
+		 * to tell DataTables how many records there are in the full table (allowing
+		 * the information element and pagination to be displayed correctly). In the case
+		 * where a filtering is applied to the table on initial load, this can be
+		 * indicated by giving the parameter as an array, where the first element is
+		 * the number of records available after filtering and the second element is the
+		 * number of records without filtering (allowing the table information element
+		 * to be shown correctly).
+		 *  @type int | array
+		 *  @default null
+		 *
+		 *  @dtopt Options
+		 *  @name DataTable.defaults.deferLoading
+		 *
+		 *  @example
+		 *    // 57 records available in the table, no filtering applied
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "serverSide": true,
+		 *        "ajax": "scripts/server_processing.php",
+		 *        "deferLoading": 57
+		 *      } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // 57 records after filtering, 100 without filtering (an initial filter applied)
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "serverSide": true,
+		 *        "ajax": "scripts/server_processing.php",
+		 *        "deferLoading": [ 57, 100 ],
+		 *        "search": {
+		 *          "search": "my_filter"
+		 *        }
+		 *      } );
+		 *    } );
+		 */
+		"iDeferLoading": null,
+	
+	
+		/**
+		 * Number of rows to display on a single page when using pagination. If
+		 * feature enabled (`lengthChange`) then the end user will be able to override
+		 * this to a custom setting using a pop-up menu.
+		 *  @type int
+		 *  @default 10
+		 *
+		 *  @dtopt Options
+		 *  @name DataTable.defaults.pageLength
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "pageLength": 50
+		 *      } );
+		 *    } )
+		 */
+		"iDisplayLength": 10,
+	
+	
+		/**
+		 * Define the starting point for data display when using DataTables with
+		 * pagination. Note that this parameter is the number of records, rather than
+		 * the page number, so if you have 10 records per page and want to start on
+		 * the third page, it should be "20".
+		 *  @type int
+		 *  @default 0
+		 *
+		 *  @dtopt Options
+		 *  @name DataTable.defaults.displayStart
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "displayStart": 20
+		 *      } );
+		 *    } )
+		 */
+		"iDisplayStart": 0,
+	
+	
+		/**
+		 * By default DataTables allows keyboard navigation of the table (sorting, paging,
+		 * and filtering) by adding a `tabindex` attribute to the required elements. This
+		 * allows you to tab through the controls and press the enter key to activate them.
+		 * The tabindex is default 0, meaning that the tab follows the flow of the document.
+		 * You can overrule this using this parameter if you wish. Use a value of -1 to
+		 * disable built-in keyboard navigation.
+		 *  @type int
+		 *  @default 0
+		 *
+		 *  @dtopt Options
+		 *  @name DataTable.defaults.tabIndex
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "tabIndex": 1
+		 *      } );
+		 *    } );
+		 */
+		"iTabIndex": 0,
+	
+	
+		/**
+		 * Classes that DataTables assigns to the various components and features
+		 * that it adds to the HTML table. This allows classes to be configured
+		 * during initialisation in addition to through the static
+		 * {@link DataTable.ext.oStdClasses} object).
+		 *  @namespace
+		 *  @name DataTable.defaults.classes
+		 */
+		"oClasses": {},
+	
+	
+		/**
+		 * All strings that DataTables uses in the user interface that it creates
+		 * are defined in this object, allowing you to modified them individually or
+		 * completely replace them all as required.
+		 *  @namespace
+		 *  @name DataTable.defaults.language
+		 */
+		"oLanguage": {
+			/**
+			 * Strings that are used for WAI-ARIA labels and controls only (these are not
+			 * actually visible on the page, but will be read by screenreaders, and thus
+			 * must be internationalised as well).
+			 *  @namespace
+			 *  @name DataTable.defaults.language.aria
+			 */
+			"oAria": {
+				/**
+				 * ARIA label that is added to the table headers when the column may be
+				 * sorted ascending by activing the column (click or return when focused).
+				 * Note that the column header is prefixed to this string.
+				 *  @type string
+				 *  @default : activate to sort column ascending
+				 *
+				 *  @dtopt Language
+				 *  @name DataTable.defaults.language.aria.sortAscending
+				 *
+				 *  @example
+				 *    $(document).ready( function() {
+				 *      $('#example').dataTable( {
+				 *        "language": {
+				 *          "aria": {
+				 *            "sortAscending": " - click/return to sort ascending"
+				 *          }
+				 *        }
+				 *      } );
+				 *    } );
+				 */
+				"sSortAscending": ": activate to sort column ascending",
+	
+				/**
+				 * ARIA label that is added to the table headers when the column may be
+				 * sorted descending by activing the column (click or return when focused).
+				 * Note that the column header is prefixed to this string.
+				 *  @type string
+				 *  @default : activate to sort column ascending
+				 *
+				 *  @dtopt Language
+				 *  @name DataTable.defaults.language.aria.sortDescending
+				 *
+				 *  @example
+				 *    $(document).ready( function() {
+				 *      $('#example').dataTable( {
+				 *        "language": {
+				 *          "aria": {
+				 *            "sortDescending": " - click/return to sort descending"
+				 *          }
+				 *        }
+				 *      } );
+				 *    } );
+				 */
+				"sSortDescending": ": activate to sort column descending"
+			},
+	
+			/**
+			 * Pagination string used by DataTables for the built-in pagination
+			 * control types.
+			 *  @namespace
+			 *  @name DataTable.defaults.language.paginate
+			 */
+			"oPaginate": {
+				/**
+				 * Text to use when using the 'full_numbers' type of pagination for the
+				 * button to take the user to the first page.
+				 *  @type string
+				 *  @default First
+				 *
+				 *  @dtopt Language
+				 *  @name DataTable.defaults.language.paginate.first
+				 *
+				 *  @example
+				 *    $(document).ready( function() {
+				 *      $('#example').dataTable( {
+				 *        "language": {
+				 *          "paginate": {
+				 *            "first": "First page"
+				 *          }
+				 *        }
+				 *      } );
+				 *    } );
+				 */
+				"sFirst": "First",
+	
+	
+				/**
+				 * Text to use when using the 'full_numbers' type of pagination for the
+				 * button to take the user to the last page.
+				 *  @type string
+				 *  @default Last
+				 *
+				 *  @dtopt Language
+				 *  @name DataTable.defaults.language.paginate.last
+				 *
+				 *  @example
+				 *    $(document).ready( function() {
+				 *      $('#example').dataTable( {
+				 *        "language": {
+				 *          "paginate": {
+				 *            "last": "Last page"
+				 *          }
+				 *        }
+				 *      } );
+				 *    } );
+				 */
+				"sLast": "Last",
+	
+	
+				/**
+				 * Text to use for the 'next' pagination button (to take the user to the
+				 * next page).
+				 *  @type string
+				 *  @default Next
+				 *
+				 *  @dtopt Language
+				 *  @name DataTable.defaults.language.paginate.next
+				 *
+				 *  @example
+				 *    $(document).ready( function() {
+				 *      $('#example').dataTable( {
+				 *        "language": {
+				 *          "paginate": {
+				 *            "next": "Next page"
+				 *          }
+				 *        }
+				 *      } );
+				 *    } );
+				 */
+				"sNext": "Next",
+	
+	
+				/**
+				 * Text to use for the 'previous' pagination button (to take the user to
+				 * the previous page).
+				 *  @type string
+				 *  @default Previous
+				 *
+				 *  @dtopt Language
+				 *  @name DataTable.defaults.language.paginate.previous
+				 *
+				 *  @example
+				 *    $(document).ready( function() {
+				 *      $('#example').dataTable( {
+				 *        "language": {
+				 *          "paginate": {
+				 *            "previous": "Previous page"
+				 *          }
+				 *        }
+				 *      } );
+				 *    } );
+				 */
+				"sPrevious": "Previous"
+			},
+	
+			/**
+			 * This string is shown in preference to `zeroRecords` when the table is
+			 * empty of data (regardless of filtering). Note that this is an optional
+			 * parameter - if it is not given, the value of `zeroRecords` will be used
+			 * instead (either the default or given value).
+			 *  @type string
+			 *  @default No data available in table
+			 *
+			 *  @dtopt Language
+			 *  @name DataTable.defaults.language.emptyTable
+			 *
+			 *  @example
+			 *    $(document).ready( function() {
+			 *      $('#example').dataTable( {
+			 *        "language": {
+			 *          "emptyTable": "No data available in table"
+			 *        }
+			 *      } );
+			 *    } );
+			 */
+			"sEmptyTable": "No Results Found",
+	
+	
+			/**
+			 * This string gives information to the end user about the information
+			 * that is current on display on the page. The following tokens can be
+			 * used in the string and will be dynamically replaced as the table
+			 * display updates. This tokens can be placed anywhere in the string, or
+			 * removed as needed by the language requires:
+			 *
+			 * * `\_START\_` - Display index of the first record on the current page
+			 * * `\_END\_` - Display index of the last record on the current page
+			 * * `\_TOTAL\_` - Number of records in the table after filtering
+			 * * `\_MAX\_` - Number of records in the table without filtering
+			 * * `\_PAGE\_` - Current page number
+			 * * `\_PAGES\_` - Total number of pages of data in the table
+			 *
+			 *  @type string
+			 *  @default Showing _START_ to _END_ of _TOTAL_ entries
+			 *
+			 *  @dtopt Language
+			 *  @name DataTable.defaults.language.info
+			 *
+			 *  @example
+			 *    $(document).ready( function() {
+			 *      $('#example').dataTable( {
+			 *        "language": {
+			 *          "info": "Showing page _PAGE_ of _PAGES_"
+			 *        }
+			 *      } );
+			 *    } );
+			 */
+			"sInfo": "Showing _START_ to _END_ of _TOTAL_ entries",
+	
+	
+			/**
+			 * Display information string for when the table is empty. Typically the
+			 * format of this string should match `info`.
+			 *  @type string
+			 *  @default Showing 0 to 0 of 0 entries
+			 *
+			 *  @dtopt Language
+			 *  @name DataTable.defaults.language.infoEmpty
+			 *
+			 *  @example
+			 *    $(document).ready( function() {
+			 *      $('#example').dataTable( {
+			 *        "language": {
+			 *          "infoEmpty": "No entries to show"
+			 *        }
+			 *      } );
+			 *    } );
+			 */
+			"sInfoEmpty": "Showing 0 to 0 of 0 entries",
+	
+	
+			/**
+			 * When a user filters the information in a table, this string is appended
+			 * to the information (`info`) to give an idea of how strong the filtering
+			 * is. The variable _MAX_ is dynamically updated.
+			 *  @type string
+			 *  @default (filtered from _MAX_ total entries)
+			 *
+			 *  @dtopt Language
+			 *  @name DataTable.defaults.language.infoFiltered
+			 *
+			 *  @example
+			 *    $(document).ready( function() {
+			 *      $('#example').dataTable( {
+			 *        "language": {
+			 *          "infoFiltered": " - filtering from _MAX_ records"
+			 *        }
+			 *      } );
+			 *    } );
+			 */
+			"sInfoFiltered": "(filtered from _MAX_ total entries)",
+	
+	
+			/**
+			 * If can be useful to append extra information to the info string at times,
+			 * and this variable does exactly that. This information will be appended to
+			 * the `info` (`infoEmpty` and `infoFiltered` in whatever combination they are
+			 * being used) at all times.
+			 *  @type string
+			 *  @default <i>Empty string</i>
+			 *
+			 *  @dtopt Language
+			 *  @name DataTable.defaults.language.infoPostFix
+			 *
+			 *  @example
+			 *    $(document).ready( function() {
+			 *      $('#example').dataTable( {
+			 *        "language": {
+			 *          "infoPostFix": "All records shown are derived from real information."
+			 *        }
+			 *      } );
+			 *    } );
+			 */
+			"sInfoPostFix": "",
+	
+	
+			/**
+			 * This decimal place operator is a little different from the other
+			 * language options since DataTables doesn't output floating point
+			 * numbers, so it won't ever use this for display of a number. Rather,
+			 * what this parameter does is modify the sort methods of the table so
+			 * that numbers which are in a format which has a character other than
+			 * a period (`.`) as a decimal place will be sorted numerically.
+			 *
+			 * Note that numbers with different decimal places cannot be shown in
+			 * the same table and still be sortable, the table must be consistent.
+			 * However, multiple different tables on the page can use different
+			 * decimal place characters.
+			 *  @type string
+			 *  @default 
+			 *
+			 *  @dtopt Language
+			 *  @name DataTable.defaults.language.decimal
+			 *
+			 *  @example
+			 *    $(document).ready( function() {
+			 *      $('#example').dataTable( {
+			 *        "language": {
+			 *          "decimal": ","
+			 *          "thousands": "."
+			 *        }
+			 *      } );
+			 *    } );
+			 */
+			"sDecimal": "",
+	
+	
+			/**
+			 * DataTables has a build in number formatter (`formatNumber`) which is
+			 * used to format large numbers that are used in the table information.
+			 * By default a comma is used, but this can be trivially changed to any
+			 * character you wish with this parameter.
+			 *  @type string
+			 *  @default ,
+			 *
+			 *  @dtopt Language
+			 *  @name DataTable.defaults.language.thousands
+			 *
+			 *  @example
+			 *    $(document).ready( function() {
+			 *      $('#example').dataTable( {
+			 *        "language": {
+			 *          "thousands": "'"
+			 *        }
+			 *      } );
+			 *    } );
+			 */
+			"sThousands": ",",
+	
+	
+			/**
+			 * Detail the action that will be taken when the drop down menu for the
+			 * pagination length option is changed. The '_MENU_' variable is replaced
+			 * with a default select list of 10, 25, 50 and 100, and can be replaced
+			 * with a custom select box if required.
+			 *  @type string
+			 *  @default Show _MENU_ entries
+			 *
+			 *  @dtopt Language
+			 *  @name DataTable.defaults.language.lengthMenu
+			 *
+			 *  @example
+			 *    // Language change only
+			 *    $(document).ready( function() {
+			 *      $('#example').dataTable( {
+			 *        "language": {
+			 *          "lengthMenu": "Display _MENU_ records"
+			 *        }
+			 *      } );
+			 *    } );
+			 *
+			 *  @example
+			 *    // Language and options change
+			 *    $(document).ready( function() {
+			 *      $('#example').dataTable( {
+			 *        "language": {
+			 *          "lengthMenu": 'Display <select>'+
+			 *            '<option value="10">10</option>'+
+			 *            '<option value="20">20</option>'+
+			 *            '<option value="30">30</option>'+
+			 *            '<option value="40">40</option>'+
+			 *            '<option value="50">50</option>'+
+			 *            '<option value="-1">All</option>'+
+			 *            '</select> records'
+			 *        }
+			 *      } );
+			 *    } );
+			 */
+			"sLengthMenu": "Show _MENU_ entries",
+	
+	
+			/**
+			 * When using Ajax sourced data and during the first draw when DataTables is
+			 * gathering the data, this message is shown in an empty row in the table to
+			 * indicate to the end user the the data is being loaded. Note that this
+			 * parameter is not used when loading data by server-side processing, just
+			 * Ajax sourced data with client-side processing.
+			 *  @type string
+			 *  @default Loading...
+			 *
+			 *  @dtopt Language
+			 *  @name DataTable.defaults.language.loadingRecords
+			 *
+			 *  @example
+			 *    $(document).ready( function() {
+			 *      $('#example').dataTable( {
+			 *        "language": {
+			 *          "loadingRecords": "Please wait - loading..."
+			 *        }
+			 *      } );
+			 *    } );
+			 */
+			"sLoadingRecords": "Loading...",
+	
+	
+			/**
+			 * Text which is displayed when the table is processing a user action
+			 * (usually a sort command or similar).
+			 *  @type string
+			 *  @default Processing...
+			 *
+			 *  @dtopt Language
+			 *  @name DataTable.defaults.language.processing
+			 *
+			 *  @example
+			 *    $(document).ready( function() {
+			 *      $('#example').dataTable( {
+			 *        "language": {
+			 *          "processing": "DataTables is currently busy"
+			 *        }
+			 *      } );
+			 *    } );
+			 */
+			"sProcessing": "Processing...",
+	
+	
+			/**
+			 * Details the actions that will be taken when the user types into the
+			 * filtering input text box. The variable "_INPUT_", if used in the string,
+			 * is replaced with the HTML text box for the filtering input allowing
+			 * control over where it appears in the string. If "_INPUT_" is not given
+			 * then the input box is appended to the string automatically.
+			 *  @type string
+			 *  @default Search:
+			 *
+			 *  @dtopt Language
+			 *  @name DataTable.defaults.language.search
+			 *
+			 *  @example
+			 *    // Input text box will be appended at the end automatically
+			 *    $(document).ready( function() {
+			 *      $('#example').dataTable( {
+			 *        "language": {
+			 *          "search": "Filter records:"
+			 *        }
+			 *      } );
+			 *    } );
+			 *
+			 *  @example
+			 *    // Specify where the filter should appear
+			 *    $(document).ready( function() {
+			 *      $('#example').dataTable( {
+			 *        "language": {
+			 *          "search": "Apply filter _INPUT_ to table"
+			 *        }
+			 *      } );
+			 *    } );
+			 */
+			"sSearch": "Search:",
+	
+	
+			/**
+			 * Assign a `placeholder` attribute to the search `input` element
+			 *  @type string
+			 *  @default 
+			 *
+			 *  @dtopt Language
+			 *  @name DataTable.defaults.language.searchPlaceholder
+			 */
+			"sSearchPlaceholder": "",
+	
+	
+			/**
+			 * All of the language information can be stored in a file on the
+			 * server-side, which DataTables will look up if this parameter is passed.
+			 * It must store the URL of the language file, which is in a JSON format,
+			 * and the object has the same properties as the oLanguage object in the
+			 * initialiser object (i.e. the above parameters). Please refer to one of
+			 * the example language files to see how this works in action.
+			 *  @type string
+			 *  @default <i>Empty string - i.e. disabled</i>
+			 *
+			 *  @dtopt Language
+			 *  @name DataTable.defaults.language.url
+			 *
+			 *  @example
+			 *    $(document).ready( function() {
+			 *      $('#example').dataTable( {
+			 *        "language": {
+			 *          "url": "http://www.sprymedia.co.uk/dataTables/lang.txt"
+			 *        }
+			 *      } );
+			 *    } );
+			 */
+			"sUrl": "",
+	
+	
+			/**
+			 * Text shown inside the table records when the is no information to be
+			 * displayed after filtering. `emptyTable` is shown when there is simply no
+			 * information in the table at all (regardless of filtering).
+			 *  @type string
+			 *  @default No matching records found
+			 *
+			 *  @dtopt Language
+			 *  @name DataTable.defaults.language.zeroRecords
+			 *
+			 *  @example
+			 *    $(document).ready( function() {
+			 *      $('#example').dataTable( {
+			 *        "language": {
+			 *          "zeroRecords": "No records to display"
+			 *        }
+			 *      } );
+			 *    } );
+			 */
+			"sZeroRecords": "No matching records found"
+		},
+	
+	
+		/**
+		 * This parameter allows you to have define the global filtering state at
+		 * initialisation time. As an object the `search` parameter must be
+		 * defined, but all other parameters are optional. When `regex` is true,
+		 * the search string will be treated as a regular expression, when false
+		 * (default) it will be treated as a straight string. When `smart`
+		 * DataTables will use it's smart filtering methods (to word match at
+		 * any point in the data), when false this will not be done.
+		 *  @namespace
+		 *  @extends DataTable.models.oSearch
+		 *
+		 *  @dtopt Options
+		 *  @name DataTable.defaults.search
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "search": {"search": "Initial search"}
+		 *      } );
+		 *    } )
+		 */
+		"oSearch": $.extend( {}, DataTable.models.oSearch ),
+	
+	
+		/**
+		 * __Deprecated__ The functionality provided by this parameter has now been
+		 * superseded by that provided through `ajax`, which should be used instead.
+		 *
+		 * By default DataTables will look for the property `data` (or `aaData` for
+		 * compatibility with DataTables 1.9-) when obtaining data from an Ajax
+		 * source or for server-side processing - this parameter allows that
+		 * property to be changed. You can use Javascript dotted object notation to
+		 * get a data source for multiple levels of nesting.
+		 *  @type string
+		 *  @default data
+		 *
+		 *  @dtopt Options
+		 *  @dtopt Server-side
+		 *  @name DataTable.defaults.ajaxDataProp
+		 *
+		 *  @deprecated 1.10. Please use `ajax` for this functionality now.
+		 */
+		"sAjaxDataProp": "data",
+	
+	
+		/**
+		 * __Deprecated__ The functionality provided by this parameter has now been
+		 * superseded by that provided through `ajax`, which should be used instead.
+		 *
+		 * You can instruct DataTables to load data from an external
+		 * source using this parameter (use aData if you want to pass data in you
+		 * already have). Simply provide a url a JSON object can be obtained from.
+		 *  @type string
+		 *  @default null
+		 *
+		 *  @dtopt Options
+		 *  @dtopt Server-side
+		 *  @name DataTable.defaults.ajaxSource
+		 *
+		 *  @deprecated 1.10. Please use `ajax` for this functionality now.
+		 */
+		"sAjaxSource": null,
+	
+	
+		/**
+		 * This initialisation variable allows you to specify exactly where in the
+		 * DOM you want DataTables to inject the various controls it adds to the page
+		 * (for example you might want the pagination controls at the top of the
+		 * table). DIV elements (with or without a custom class) can also be added to
+		 * aid styling. The follow syntax is used:
+		 *   <ul>
+		 *     <li>The following options are allowed:
+		 *       <ul>
+		 *         <li>'l' - Length changing</li>
+		 *         <li>'f' - Filtering input</li>
+		 *         <li>'t' - The table!</li>
+		 *         <li>'i' - Information</li>
+		 *         <li>'p' - Pagination</li>
+		 *         <li>'r' - pRocessing</li>
+		 *       </ul>
+		 *     </li>
+		 *     <li>The following constants are allowed:
+		 *       <ul>
+		 *         <li>'H' - jQueryUI theme "header" classes ('fg-toolbar ui-widget-header ui-corner-tl ui-corner-tr ui-helper-clearfix')</li>
+		 *         <li>'F' - jQueryUI theme "footer" classes ('fg-toolbar ui-widget-header ui-corner-bl ui-corner-br ui-helper-clearfix')</li>
+		 *       </ul>
+		 *     </li>
+		 *     <li>The following syntax is expected:
+		 *       <ul>
+		 *         <li>'&lt;' and '&gt;' - div elements</li>
+		 *         <li>'&lt;"class" and '&gt;' - div with a class</li>
+		 *         <li>'&lt;"#id" and '&gt;' - div with an ID</li>
+		 *       </ul>
+		 *     </li>
+		 *     <li>Examples:
+		 *       <ul>
+		 *         <li>'&lt;"wrapper"flipt&gt;'</li>
+		 *         <li>'&lt;lf&lt;t&gt;ip&gt;'</li>
+		 *       </ul>
+		 *     </li>
+		 *   </ul>
+		 *  @type string
+		 *  @default lfrtip <i>(when `jQueryUI` is false)</i> <b>or</b>
+		 *    <"H"lfr>t<"F"ip> <i>(when `jQueryUI` is true)</i>
+		 *
+		 *  @dtopt Options
+		 *  @name DataTable.defaults.dom
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "dom": '&lt;"top"i&gt;rt&lt;"bottom"flp&gt;&lt;"clear"&gt;'
+		 *      } );
+		 *    } );
+		 */
+		"sDom": "lfrtip",
+	
+	
+		/**
+		 * Search delay option. This will throttle full table searches that use the
+		 * DataTables provided search input element (it does not effect calls to
+		 * `dt-api search()`, providing a delay before the search is made.
+		 *  @type integer
+		 *  @default 0
+		 *
+		 *  @dtopt Options
+		 *  @name DataTable.defaults.searchDelay
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "searchDelay": 200
+		 *      } );
+		 *    } )
+		 */
+		"searchDelay": null,
+	
+	
+		/**
+		 * DataTables features six different built-in options for the buttons to
+		 * display for pagination control:
+		 *
+		 * * `numbers` - Page number buttons only
+		 * * `simple` - 'Previous' and 'Next' buttons only
+		 * * 'simple_numbers` - 'Previous' and 'Next' buttons, plus page numbers
+		 * * `full` - 'First', 'Previous', 'Next' and 'Last' buttons
+		 * * `full_numbers` - 'First', 'Previous', 'Next' and 'Last' buttons, plus page numbers
+		 * * `first_last_numbers` - 'First' and 'Last' buttons, plus page numbers
+		 *  
+		 * Further methods can be added using {@link DataTable.ext.oPagination}.
+		 *  @type string
+		 *  @default simple_numbers
+		 *
+		 *  @dtopt Options
+		 *  @name DataTable.defaults.pagingType
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "pagingType": "full_numbers"
+		 *      } );
+		 *    } )
+		 */
+		"sPaginationType": "simple_numbers",
+	
+	
+		/**
+		 * Enable horizontal scrolling. When a table is too wide to fit into a
+		 * certain layout, or you have a large number of columns in the table, you
+		 * can enable x-scrolling to show the table in a viewport, which can be
+		 * scrolled. This property can be `true` which will allow the table to
+		 * scroll horizontally when needed, or any CSS unit, or a number (in which
+		 * case it will be treated as a pixel measurement). Setting as simply `true`
+		 * is recommended.
+		 *  @type boolean|string
+		 *  @default <i>blank string - i.e. disabled</i>
+		 *
+		 *  @dtopt Features
+		 *  @name DataTable.defaults.scrollX
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "scrollX": true,
+		 *        "scrollCollapse": true
+		 *      } );
+		 *    } );
+		 */
+		"sScrollX": "",
+	
+	
+		/**
+		 * This property can be used to force a DataTable to use more width than it
+		 * might otherwise do when x-scrolling is enabled. For example if you have a
+		 * table which requires to be well spaced, this parameter is useful for
+		 * "over-sizing" the table, and thus forcing scrolling. This property can by
+		 * any CSS unit, or a number (in which case it will be treated as a pixel
+		 * measurement).
+		 *  @type string
+		 *  @default <i>blank string - i.e. disabled</i>
+		 *
+		 *  @dtopt Options
+		 *  @name DataTable.defaults.scrollXInner
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "scrollX": "100%",
+		 *        "scrollXInner": "110%"
+		 *      } );
+		 *    } );
+		 */
+		"sScrollXInner": "",
+	
+	
+		/**
+		 * Enable vertical scrolling. Vertical scrolling will constrain the DataTable
+		 * to the given height, and enable scrolling for any data which overflows the
+		 * current viewport. This can be used as an alternative to paging to display
+		 * a lot of data in a small area (although paging and scrolling can both be
+		 * enabled at the same time). This property can be any CSS unit, or a number
+		 * (in which case it will be treated as a pixel measurement).
+		 *  @type string
+		 *  @default <i>blank string - i.e. disabled</i>
+		 *
+		 *  @dtopt Features
+		 *  @name DataTable.defaults.scrollY
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "scrollY": "200px",
+		 *        "paginate": false
+		 *      } );
+		 *    } );
+		 */
+		"sScrollY": "",
+	
+	
+		/**
+		 * __Deprecated__ The functionality provided by this parameter has now been
+		 * superseded by that provided through `ajax`, which should be used instead.
+		 *
+		 * Set the HTTP method that is used to make the Ajax call for server-side
+		 * processing or Ajax sourced data.
+		 *  @type string
+		 *  @default GET
+		 *
+		 *  @dtopt Options
+		 *  @dtopt Server-side
+		 *  @name DataTable.defaults.serverMethod
+		 *
+		 *  @deprecated 1.10. Please use `ajax` for this functionality now.
+		 */
+		"sServerMethod": "GET",
+	
+	
+		/**
+		 * DataTables makes use of renderers when displaying HTML elements for
+		 * a table. These renderers can be added or modified by plug-ins to
+		 * generate suitable mark-up for a site. For example the Bootstrap
+		 * integration plug-in for DataTables uses a paging button renderer to
+		 * display pagination buttons in the mark-up required by Bootstrap.
+		 *
+		 * For further information about the renderers available see
+		 * DataTable.ext.renderer
+		 *  @type string|object
+		 *  @default null
+		 *
+		 *  @name DataTable.defaults.renderer
+		 *
+		 */
+		"renderer": null,
+	
+	
+		/**
+		 * Set the data property name that DataTables should use to get a row's id
+		 * to set as the `id` property in the node.
+		 *  @type string
+		 *  @default DT_RowId
+		 *
+		 *  @name DataTable.defaults.rowId
+		 */
+		"rowId": "DT_RowId"
+	};
+	
+	_fnHungarianMap( DataTable.defaults );
+	
+	
+	
+	/*
+	 * Developer note - See note in model.defaults.js about the use of Hungarian
+	 * notation and camel case.
+	 */
+	
+	/**
+	 * Column options that can be given to DataTables at initialisation time.
+	 *  @namespace
+	 */
+	DataTable.defaults.column = {
+		/**
+		 * Define which column(s) an order will occur on for this column. This
+		 * allows a column's ordering to take multiple columns into account when
+		 * doing a sort or use the data from a different column. For example first
+		 * name / last name columns make sense to do a multi-column sort over the
+		 * two columns.
+		 *  @type array|int
+		 *  @default null <i>Takes the value of the column index automatically</i>
+		 *
+		 *  @name DataTable.defaults.column.orderData
+		 *  @dtopt Columns
+		 *
+		 *  @example
+		 *    // Using `columnDefs`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columnDefs": [
+		 *          { "orderData": [ 0, 1 ], "targets": [ 0 ] },
+		 *          { "orderData": [ 1, 0 ], "targets": [ 1 ] },
+		 *          { "orderData": 2, "targets": [ 2 ] }
+		 *        ]
+		 *      } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Using `columns`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columns": [
+		 *          { "orderData": [ 0, 1 ] },
+		 *          { "orderData": [ 1, 0 ] },
+		 *          { "orderData": 2 },
+		 *          null,
+		 *          null
+		 *        ]
+		 *      } );
+		 *    } );
+		 */
+		"aDataSort": null,
+		"iDataSort": -1,
+	
+	
+		/**
+		 * You can control the default ordering direction, and even alter the
+		 * behaviour of the sort handler (i.e. only allow ascending ordering etc)
+		 * using this parameter.
+		 *  @type array
+		 *  @default [ 'asc', 'desc' ]
+		 *
+		 *  @name DataTable.defaults.column.orderSequence
+		 *  @dtopt Columns
+		 *
+		 *  @example
+		 *    // Using `columnDefs`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columnDefs": [
+		 *          { "orderSequence": [ "asc" ], "targets": [ 1 ] },
+		 *          { "orderSequence": [ "desc", "asc", "asc" ], "targets": [ 2 ] },
+		 *          { "orderSequence": [ "desc" ], "targets": [ 3 ] }
+		 *        ]
+		 *      } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Using `columns`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columns": [
+		 *          null,
+		 *          { "orderSequence": [ "asc" ] },
+		 *          { "orderSequence": [ "desc", "asc", "asc" ] },
+		 *          { "orderSequence": [ "desc" ] },
+		 *          null
+		 *        ]
+		 *      } );
+		 *    } );
+		 */
+		"asSorting": [ 'asc', 'desc' ],
+	
+	
+		/**
+		 * Enable or disable filtering on the data in this column.
+		 *  @type boolean
+		 *  @default true
+		 *
+		 *  @name DataTable.defaults.column.searchable
+		 *  @dtopt Columns
+		 *
+		 *  @example
+		 *    // Using `columnDefs`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columnDefs": [
+		 *          { "searchable": false, "targets": [ 0 ] }
+		 *        ] } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Using `columns`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columns": [
+		 *          { "searchable": false },
+		 *          null,
+		 *          null,
+		 *          null,
+		 *          null
+		 *        ] } );
+		 *    } );
+		 */
+		"bSearchable": true,
+	
+	
+		/**
+		 * Enable or disable ordering on this column.
+		 *  @type boolean
+		 *  @default true
+		 *
+		 *  @name DataTable.defaults.column.orderable
+		 *  @dtopt Columns
+		 *
+		 *  @example
+		 *    // Using `columnDefs`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columnDefs": [
+		 *          { "orderable": false, "targets": [ 0 ] }
+		 *        ] } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Using `columns`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columns": [
+		 *          { "orderable": false },
+		 *          null,
+		 *          null,
+		 *          null,
+		 *          null
+		 *        ] } );
+		 *    } );
+		 */
+		"bSortable": true,
+	
+	
+		/**
+		 * Enable or disable the display of this column.
+		 *  @type boolean
+		 *  @default true
+		 *
+		 *  @name DataTable.defaults.column.visible
+		 *  @dtopt Columns
+		 *
+		 *  @example
+		 *    // Using `columnDefs`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columnDefs": [
+		 *          { "visible": false, "targets": [ 0 ] }
+		 *        ] } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Using `columns`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columns": [
+		 *          { "visible": false },
+		 *          null,
+		 *          null,
+		 *          null,
+		 *          null
+		 *        ] } );
+		 *    } );
+		 */
+		"bVisible": true,
+	
+	
+		/**
+		 * Developer definable function that is called whenever a cell is created (Ajax source,
+		 * etc) or processed for input (DOM source). This can be used as a compliment to mRender
+		 * allowing you to modify the DOM element (add background colour for example) when the
+		 * element is available.
+		 *  @type function
+		 *  @param {element} td The TD node that has been created
+		 *  @param {*} cellData The Data for the cell
+		 *  @param {array|object} rowData The data for the whole row
+		 *  @param {int} row The row index for the aoData data store
+		 *  @param {int} col The column index for aoColumns
+		 *
+		 *  @name DataTable.defaults.column.createdCell
+		 *  @dtopt Columns
+		 *
+		 *  @example
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columnDefs": [ {
+		 *          "targets": [3],
+		 *          "createdCell": function (td, cellData, rowData, row, col) {
+		 *            if ( cellData == "1.7" ) {
+		 *              $(td).css('color', 'blue')
+		 *            }
+		 *          }
+		 *        } ]
+		 *      });
+		 *    } );
+		 */
+		"fnCreatedCell": null,
+	
+	
+		/**
+		 * This parameter has been replaced by `data` in DataTables to ensure naming
+		 * consistency. `dataProp` can still be used, as there is backwards
+		 * compatibility in DataTables for this option, but it is strongly
+		 * recommended that you use `data` in preference to `dataProp`.
+		 *  @name DataTable.defaults.column.dataProp
+		 */
+	
+	
+		/**
+		 * This property can be used to read data from any data source property,
+		 * including deeply nested objects / properties. `data` can be given in a
+		 * number of different ways which effect its behaviour:
+		 *
+		 * * `integer` - treated as an array index for the data source. This is the
+		 *   default that DataTables uses (incrementally increased for each column).
+		 * * `string` - read an object property from the data source. There are
+		 *   three 'special' options that can be used in the string to alter how
+		 *   DataTables reads the data from the source object:
+		 *    * `.` - Dotted Javascript notation. Just as you use a `.` in
+		 *      Javascript to read from nested objects, so to can the options
+		 *      specified in `data`. For example: `browser.version` or
+		 *      `browser.name`. If your object parameter name contains a period, use
+		 *      `\\` to escape it - i.e. `first\\.name`.
+		 *    * `[]` - Array notation. DataTables can automatically combine data
+		 *      from and array source, joining the data with the characters provided
+		 *      between the two brackets. For example: `name[, ]` would provide a
+		 *      comma-space separated list from the source array. If no characters
+		 *      are provided between the brackets, the original array source is
+		 *      returned.
+		 *    * `()` - Function notation. Adding `()` to the end of a parameter will
+		 *      execute a function of the name given. For example: `browser()` for a
+		 *      simple function on the data source, `browser.version()` for a
+		 *      function in a nested property or even `browser().version` to get an
+		 *      object property if the function called returns an object. Note that
+		 *      function notation is recommended for use in `render` rather than
+		 *      `data` as it is much simpler to use as a renderer.
+		 * * `null` - use the original data source for the row rather than plucking
+		 *   data directly from it. This action has effects on two other
+		 *   initialisation options:
+		 *    * `defaultContent` - When null is given as the `data` option and
+		 *      `defaultContent` is specified for the column, the value defined by
+		 *      `defaultContent` will be used for the cell.
+		 *    * `render` - When null is used for the `data` option and the `render`
+		 *      option is specified for the column, the whole data source for the
+		 *      row is used for the renderer.
+		 * * `function` - the function given will be executed whenever DataTables
+		 *   needs to set or get the data for a cell in the column. The function
+		 *   takes three parameters:
+		 *    * Parameters:
+		 *      * `{array|object}` The data source for the row
+		 *      * `{string}` The type call data requested - this will be 'set' when
+		 *        setting data or 'filter', 'display', 'type', 'sort' or undefined
+		 *        when gathering data. Note that when `undefined` is given for the
+		 *        type DataTables expects to get the raw data for the object back<
+		 *      * `{*}` Data to set when the second parameter is 'set'.
+		 *    * Return:
+		 *      * The return value from the function is not required when 'set' is
+		 *        the type of call, but otherwise the return is what will be used
+		 *        for the data requested.
+		 *
+		 * Note that `data` is a getter and setter option. If you just require
+		 * formatting of data for output, you will likely want to use `render` which
+		 * is simply a getter and thus simpler to use.
+		 *
+		 * Note that prior to DataTables 1.9.2 `data` was called `mDataProp`. The
+		 * name change reflects the flexibility of this property and is consistent
+		 * with the naming of mRender. If 'mDataProp' is given, then it will still
+		 * be used by DataTables, as it automatically maps the old name to the new
+		 * if required.
+		 *
+		 *  @type string|int|function|null
+		 *  @default null <i>Use automatically calculated column index</i>
+		 *
+		 *  @name DataTable.defaults.column.data
+		 *  @dtopt Columns
+		 *
+		 *  @example
+		 *    // Read table data from objects
+		 *    // JSON structure for each row:
+		 *    //   {
+		 *    //      "engine": {value},
+		 *    //      "browser": {value},
+		 *    //      "platform": {value},
+		 *    //      "version": {value},
+		 *    //      "grade": {value}
+		 *    //   }
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "ajaxSource": "sources/objects.txt",
+		 *        "columns": [
+		 *          { "data": "engine" },
+		 *          { "data": "browser" },
+		 *          { "data": "platform" },
+		 *          { "data": "version" },
+		 *          { "data": "grade" }
+		 *        ]
+		 *      } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Read information from deeply nested objects
+		 *    // JSON structure for each row:
+		 *    //   {
+		 *    //      "engine": {value},
+		 *    //      "browser": {value},
+		 *    //      "platform": {
+		 *    //         "inner": {value}
+		 *    //      },
+		 *    //      "details": [
+		 *    //         {value}, {value}
+		 *    //      ]
+		 *    //   }
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "ajaxSource": "sources/deep.txt",
+		 *        "columns": [
+		 *          { "data": "engine" },
+		 *          { "data": "browser" },
+		 *          { "data": "platform.inner" },
+		 *          { "data": "platform.details.0" },
+		 *          { "data": "platform.details.1" }
+		 *        ]
+		 *      } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Using `data` as a function to provide different information for
+		 *    // sorting, filtering and display. In this case, currency (price)
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columnDefs": [ {
+		 *          "targets": [ 0 ],
+		 *          "data": function ( source, type, val ) {
+		 *            if (type === 'set') {
+		 *              source.price = val;
+		 *              // Store the computed dislay and filter values for efficiency
+		 *              source.price_display = val=="" ? "" : "$"+numberFormat(val);
+		 *              source.price_filter  = val=="" ? "" : "$"+numberFormat(val)+" "+val;
+		 *              return;
+		 *            }
+		 *            else if (type === 'display') {
+		 *              return source.price_display;
+		 *            }
+		 *            else if (type === 'filter') {
+		 *              return source.price_filter;
+		 *            }
+		 *            // 'sort', 'type' and undefined all just use the integer
+		 *            return source.price;
+		 *          }
+		 *        } ]
+		 *      } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Using default content
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columnDefs": [ {
+		 *          "targets": [ 0 ],
+		 *          "data": null,
+		 *          "defaultContent": "Click to edit"
+		 *        } ]
+		 *      } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Using array notation - outputting a list from an array
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columnDefs": [ {
+		 *          "targets": [ 0 ],
+		 *          "data": "name[, ]"
+		 *        } ]
+		 *      } );
+		 *    } );
+		 *
+		 */
+		"mData": null,
+	
+	
+		/**
+		 * This property is the rendering partner to `data` and it is suggested that
+		 * when you want to manipulate data for display (including filtering,
+		 * sorting etc) without altering the underlying data for the table, use this
+		 * property. `render` can be considered to be the the read only companion to
+		 * `data` which is read / write (then as such more complex). Like `data`
+		 * this option can be given in a number of different ways to effect its
+		 * behaviour:
+		 *
+		 * * `integer` - treated as an array index for the data source. This is the
+		 *   default that DataTables uses (incrementally increased for each column).
+		 * * `string` - read an object property from the data source. There are
+		 *   three 'special' options that can be used in the string to alter how
+		 *   DataTables reads the data from the source object:
+		 *    * `.` - Dotted Javascript notation. Just as you use a `.` in
+		 *      Javascript to read from nested objects, so to can the options
+		 *      specified in `data`. For example: `browser.version` or
+		 *      `browser.name`. If your object parameter name contains a period, use
+		 *      `\\` to escape it - i.e. `first\\.name`.
+		 *    * `[]` - Array notation. DataTables can automatically combine data
+		 *      from and array source, joining the data with the characters provided
+		 *      between the two brackets. For example: `name[, ]` would provide a
+		 *      comma-space separated list from the source array. If no characters
+		 *      are provided between the brackets, the original array source is
+		 *      returned.
+		 *    * `()` - Function notation. Adding `()` to the end of a parameter will
+		 *      execute a function of the name given. For example: `browser()` for a
+		 *      simple function on the data source, `browser.version()` for a
+		 *      function in a nested property or even `browser().version` to get an
+		 *      object property if the function called returns an object.
+		 * * `object` - use different data for the different data types requested by
+		 *   DataTables ('filter', 'display', 'type' or 'sort'). The property names
+		 *   of the object is the data type the property refers to and the value can
+		 *   defined using an integer, string or function using the same rules as
+		 *   `render` normally does. Note that an `_` option _must_ be specified.
+		 *   This is the default value to use if you haven't specified a value for
+		 *   the data type requested by DataTables.
+		 * * `function` - the function given will be executed whenever DataTables
+		 *   needs to set or get the data for a cell in the column. The function
+		 *   takes three parameters:
+		 *    * Parameters:
+		 *      * {array|object} The data source for the row (based on `data`)
+		 *      * {string} The type call data requested - this will be 'filter',
+		 *        'display', 'type' or 'sort'.
+		 *      * {array|object} The full data source for the row (not based on
+		 *        `data`)
+		 *    * Return:
+		 *      * The return value from the function is what will be used for the
+		 *        data requested.
+		 *
+		 *  @type string|int|function|object|null
+		 *  @default null Use the data source value.
+		 *
+		 *  @name DataTable.defaults.column.render
+		 *  @dtopt Columns
+		 *
+		 *  @example
+		 *    // Create a comma separated list from an array of objects
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "ajaxSource": "sources/deep.txt",
+		 *        "columns": [
+		 *          { "data": "engine" },
+		 *          { "data": "browser" },
+		 *          {
+		 *            "data": "platform",
+		 *            "render": "[, ].name"
+		 *          }
+		 *        ]
+		 *      } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Execute a function to obtain data
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columnDefs": [ {
+		 *          "targets": [ 0 ],
+		 *          "data": null, // Use the full data source object for the renderer's source
+		 *          "render": "browserName()"
+		 *        } ]
+		 *      } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // As an object, extracting different data for the different types
+		 *    // This would be used with a data source such as:
+		 *    //   { "phone": 5552368, "phone_filter": "5552368 555-2368", "phone_display": "555-2368" }
+		 *    // Here the `phone` integer is used for sorting and type detection, while `phone_filter`
+		 *    // (which has both forms) is used for filtering for if a user inputs either format, while
+		 *    // the formatted phone number is the one that is shown in the table.
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columnDefs": [ {
+		 *          "targets": [ 0 ],
+		 *          "data": null, // Use the full data source object for the renderer's source
+		 *          "render": {
+		 *            "_": "phone",
+		 *            "filter": "phone_filter",
+		 *            "display": "phone_display"
+		 *          }
+		 *        } ]
+		 *      } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Use as a function to create a link from the data source
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columnDefs": [ {
+		 *          "targets": [ 0 ],
+		 *          "data": "download_link",
+		 *          "render": function ( data, type, full ) {
+		 *            return '<a href="'+data+'">Download</a>';
+		 *          }
+		 *        } ]
+		 *      } );
+		 *    } );
+		 */
+		"mRender": null,
+	
+	
+		/**
+		 * Change the cell type created for the column - either TD cells or TH cells. This
+		 * can be useful as TH cells have semantic meaning in the table body, allowing them
+		 * to act as a header for a row (you may wish to add scope='row' to the TH elements).
+		 *  @type string
+		 *  @default td
+		 *
+		 *  @name DataTable.defaults.column.cellType
+		 *  @dtopt Columns
+		 *
+		 *  @example
+		 *    // Make the first column use TH cells
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columnDefs": [ {
+		 *          "targets": [ 0 ],
+		 *          "cellType": "th"
+		 *        } ]
+		 *      } );
+		 *    } );
+		 */
+		"sCellType": "td",
+	
+	
+		/**
+		 * Class to give to each cell in this column.
+		 *  @type string
+		 *  @default <i>Empty string</i>
+		 *
+		 *  @name DataTable.defaults.column.class
+		 *  @dtopt Columns
+		 *
+		 *  @example
+		 *    // Using `columnDefs`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columnDefs": [
+		 *          { "class": "my_class", "targets": [ 0 ] }
+		 *        ]
+		 *      } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Using `columns`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columns": [
+		 *          { "class": "my_class" },
+		 *          null,
+		 *          null,
+		 *          null,
+		 *          null
+		 *        ]
+		 *      } );
+		 *    } );
+		 */
+		"sClass": "",
+	
+		/**
+		 * When DataTables calculates the column widths to assign to each column,
+		 * it finds the longest string in each column and then constructs a
+		 * temporary table and reads the widths from that. The problem with this
+		 * is that "mmm" is much wider then "iiii", but the latter is a longer
+		 * string - thus the calculation can go wrong (doing it properly and putting
+		 * it into an DOM object and measuring that is horribly(!) slow). Thus as
+		 * a "work around" we provide this option. It will append its value to the
+		 * text that is found to be the longest string for the column - i.e. padding.
+		 * Generally you shouldn't need this!
+		 *  @type string
+		 *  @default <i>Empty string<i>
+		 *
+		 *  @name DataTable.defaults.column.contentPadding
+		 *  @dtopt Columns
+		 *
+		 *  @example
+		 *    // Using `columns`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columns": [
+		 *          null,
+		 *          null,
+		 *          null,
+		 *          {
+		 *            "contentPadding": "mmm"
+		 *          }
+		 *        ]
+		 *      } );
+		 *    } );
+		 */
+		"sContentPadding": "",
+	
+	
+		/**
+		 * Allows a default value to be given for a column's data, and will be used
+		 * whenever a null data source is encountered (this can be because `data`
+		 * is set to null, or because the data source itself is null).
+		 *  @type string
+		 *  @default null
+		 *
+		 *  @name DataTable.defaults.column.defaultContent
+		 *  @dtopt Columns
+		 *
+		 *  @example
+		 *    // Using `columnDefs`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columnDefs": [
+		 *          {
+		 *            "data": null,
+		 *            "defaultContent": "Edit",
+		 *            "targets": [ -1 ]
+		 *          }
+		 *        ]
+		 *      } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Using `columns`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columns": [
+		 *          null,
+		 *          null,
+		 *          null,
+		 *          {
+		 *            "data": null,
+		 *            "defaultContent": "Edit"
+		 *          }
+		 *        ]
+		 *      } );
+		 *    } );
+		 */
+		"sDefaultContent": null,
+	
+	
+		/**
+		 * This parameter is only used in DataTables' server-side processing. It can
+		 * be exceptionally useful to know what columns are being displayed on the
+		 * client side, and to map these to database fields. When defined, the names
+		 * also allow DataTables to reorder information from the server if it comes
+		 * back in an unexpected order (i.e. if you switch your columns around on the
+		 * client-side, your server-side code does not also need updating).
+		 *  @type string
+		 *  @default <i>Empty string</i>
+		 *
+		 *  @name DataTable.defaults.column.name
+		 *  @dtopt Columns
+		 *
+		 *  @example
+		 *    // Using `columnDefs`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columnDefs": [
+		 *          { "name": "engine", "targets": [ 0 ] },
+		 *          { "name": "browser", "targets": [ 1 ] },
+		 *          { "name": "platform", "targets": [ 2 ] },
+		 *          { "name": "version", "targets": [ 3 ] },
+		 *          { "name": "grade", "targets": [ 4 ] }
+		 *        ]
+		 *      } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Using `columns`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columns": [
+		 *          { "name": "engine" },
+		 *          { "name": "browser" },
+		 *          { "name": "platform" },
+		 *          { "name": "version" },
+		 *          { "name": "grade" }
+		 *        ]
+		 *      } );
+		 *    } );
+		 */
+		"sName": "",
+	
+	
+		/**
+		 * Defines a data source type for the ordering which can be used to read
+		 * real-time information from the table (updating the internally cached
+		 * version) prior to ordering. This allows ordering to occur on user
+		 * editable elements such as form inputs.
+		 *  @type string
+		 *  @default std
+		 *
+		 *  @name DataTable.defaults.column.orderDataType
+		 *  @dtopt Columns
+		 *
+		 *  @example
+		 *    // Using `columnDefs`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columnDefs": [
+		 *          { "orderDataType": "dom-text", "targets": [ 2, 3 ] },
+		 *          { "type": "numeric", "targets": [ 3 ] },
+		 *          { "orderDataType": "dom-select", "targets": [ 4 ] },
+		 *          { "orderDataType": "dom-checkbox", "targets": [ 5 ] }
+		 *        ]
+		 *      } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Using `columns`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columns": [
+		 *          null,
+		 *          null,
+		 *          { "orderDataType": "dom-text" },
+		 *          { "orderDataType": "dom-text", "type": "numeric" },
+		 *          { "orderDataType": "dom-select" },
+		 *          { "orderDataType": "dom-checkbox" }
+		 *        ]
+		 *      } );
+		 *    } );
+		 */
+		"sSortDataType": "std",
+	
+	
+		/**
+		 * The title of this column.
+		 *  @type string
+		 *  @default null <i>Derived from the 'TH' value for this column in the
+		 *    original HTML table.</i>
+		 *
+		 *  @name DataTable.defaults.column.title
+		 *  @dtopt Columns
+		 *
+		 *  @example
+		 *    // Using `columnDefs`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columnDefs": [
+		 *          { "title": "My column title", "targets": [ 0 ] }
+		 *        ]
+		 *      } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Using `columns`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columns": [
+		 *          { "title": "My column title" },
+		 *          null,
+		 *          null,
+		 *          null,
+		 *          null
+		 *        ]
+		 *      } );
+		 *    } );
+		 */
+		"sTitle": null,
+	
+	
+		/**
+		 * The type allows you to specify how the data for this column will be
+		 * ordered. Four types (string, numeric, date and html (which will strip
+		 * HTML tags before ordering)) are currently available. Note that only date
+		 * formats understood by Javascript's Date() object will be accepted as type
+		 * date. For example: "Mar 26, 2008 5:03 PM". May take the values: 'string',
+		 * 'numeric', 'date' or 'html' (by default). Further types can be adding
+		 * through plug-ins.
+		 *  @type string
+		 *  @default null <i>Auto-detected from raw data</i>
+		 *
+		 *  @name DataTable.defaults.column.type
+		 *  @dtopt Columns
+		 *
+		 *  @example
+		 *    // Using `columnDefs`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columnDefs": [
+		 *          { "type": "html", "targets": [ 0 ] }
+		 *        ]
+		 *      } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Using `columns`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columns": [
+		 *          { "type": "html" },
+		 *          null,
+		 *          null,
+		 *          null,
+		 *          null
+		 *        ]
+		 *      } );
+		 *    } );
+		 */
+		"sType": null,
+	
+	
+		/**
+		 * Defining the width of the column, this parameter may take any CSS value
+		 * (3em, 20px etc). DataTables applies 'smart' widths to columns which have not
+		 * been given a specific width through this interface ensuring that the table
+		 * remains readable.
+		 *  @type string
+		 *  @default null <i>Automatic</i>
+		 *
+		 *  @name DataTable.defaults.column.width
+		 *  @dtopt Columns
+		 *
+		 *  @example
+		 *    // Using `columnDefs`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columnDefs": [
+		 *          { "width": "20%", "targets": [ 0 ] }
+		 *        ]
+		 *      } );
+		 *    } );
+		 *
+		 *  @example
+		 *    // Using `columns`
+		 *    $(document).ready( function() {
+		 *      $('#example').dataTable( {
+		 *        "columns": [
+		 *          { "width": "20%" },
+		 *          null,
+		 *          null,
+		 *          null,
+		 *          null
+		 *        ]
+		 *      } );
+		 *    } );
+		 */
+		"sWidth": null
+	};
+	
+	_fnHungarianMap( DataTable.defaults.column );
+	
+	
+	
+	/**
+	 * DataTables settings object - this holds all the information needed for a
+	 * given table, including configuration, data and current application of the
+	 * table options. DataTables does not have a single instance for each DataTable
+	 * with the settings attached to that instance, but rather instances of the
+	 * DataTable "class" are created on-the-fly as needed (typically by a
+	 * $().dataTable() call) and the settings object is then applied to that
+	 * instance.
+	 *
+	 * Note that this object is related to {@link DataTable.defaults} but this
+	 * one is the internal data store for DataTables's cache of columns. It should
+	 * NOT be manipulated outside of DataTables. Any configuration should be done
+	 * through the initialisation options.
+	 *  @namespace
+	 *  @todo Really should attach the settings object to individual instances so we
+	 *    don't need to create new instances on each $().dataTable() call (if the
+	 *    table already exists). It would also save passing oSettings around and
+	 *    into every single function. However, this is a very significant
+	 *    architecture change for DataTables and will almost certainly break
+	 *    backwards compatibility with older installations. This is something that
+	 *    will be done in 2.0.
+	 */
+	DataTable.models.oSettings = {
+		/**
+		 * Primary features of DataTables and their enablement state.
+		 *  @namespace
+		 */
+		"oFeatures": {
+	
+			/**
+			 * Flag to say if DataTables should automatically try to calculate the
+			 * optimum table and columns widths (true) or not (false).
+			 * Note that this parameter will be set by the initialisation routine. To
+			 * set a default use {@link DataTable.defaults}.
+			 *  @type boolean
+			 */
+			"bAutoWidth": null,
+	
+			/**
+			 * Delay the creation of TR and TD elements until they are actually
+			 * needed by a driven page draw. This can give a significant speed
+			 * increase for Ajax source and Javascript source data, but makes no
+			 * difference at all fro DOM and server-side processing tables.
+			 * Note that this parameter will be set by the initialisation routine. To
+			 * set a default use {@link DataTable.defaults}.
+			 *  @type boolean
+			 */
+			"bDeferRender": null,
+	
+			/**
+			 * Enable filtering on the table or not. Note that if this is disabled
+			 * then there is no filtering at all on the table, including fnFilter.
+			 * To just remove the filtering input use sDom and remove the 'f' option.
+			 * Note that this parameter will be set by the initialisation routine. To
+			 * set a default use {@link DataTable.defaults}.
+			 *  @type boolean
+			 */
+			"bFilter": null,
+	
+			/**
+			 * Table information element (the 'Showing x of y records' div) enable
+			 * flag.
+			 * Note that this parameter will be set by the initialisation routine. To
+			 * set a default use {@link DataTable.defaults}.
+			 *  @type boolean
+			 */
+			"bInfo": null,
+	
+			/**
+			 * Present a user control allowing the end user to change the page size
+			 * when pagination is enabled.
+			 * Note that this parameter will be set by the initialisation routine. To
+			 * set a default use {@link DataTable.defaults}.
+			 *  @type boolean
+			 */
+			"bLengthChange": null,
+	
+			/**
+			 * Pagination enabled or not. Note that if this is disabled then length
+			 * changing must also be disabled.
+			 * Note that this parameter will be set by the initialisation routine. To
+			 * set a default use {@link DataTable.defaults}.
+			 *  @type boolean
+			 */
+			"bPaginate": null,
+	
+			/**
+			 * Processing indicator enable flag whenever DataTables is enacting a
+			 * user request - typically an Ajax request for server-side processing.
+			 * Note that this parameter will be set by the initialisation routine. To
+			 * set a default use {@link DataTable.defaults}.
+			 *  @type boolean
+			 */
+			"bProcessing": null,
+	
+			/**
+			 * Server-side processing enabled flag - when enabled DataTables will
+			 * get all data from the server for every draw - there is no filtering,
+			 * sorting or paging done on the client-side.
+			 * Note that this parameter will be set by the initialisation routine. To
+			 * set a default use {@link DataTable.defaults}.
+			 *  @type boolean
+			 */
+			"bServerSide": null,
+	
+			/**
+			 * Sorting enablement flag.
+			 * Note that this parameter will be set by the initialisation routine. To
+			 * set a default use {@link DataTable.defaults}.
+			 *  @type boolean
+			 */
+			"bSort": null,
+	
+			/**
+			 * Multi-column sorting
+			 * Note that this parameter will be set by the initialisation routine. To
+			 * set a default use {@link DataTable.defaults}.
+			 *  @type boolean
+			 */
+			"bSortMulti": null,
+	
+			/**
+			 * Apply a class to the columns which are being sorted to provide a
+			 * visual highlight or not. This can slow things down when enabled since
+			 * there is a lot of DOM interaction.
+			 * Note that this parameter will be set by the initialisation routine. To
+			 * set a default use {@link DataTable.defaults}.
+			 *  @type boolean
+			 */
+			"bSortClasses": null,
+	
+			/**
+			 * State saving enablement flag.
+			 * Note that this parameter will be set by the initialisation routine. To
+			 * set a default use {@link DataTable.defaults}.
+			 *  @type boolean
+			 */
+			"bStateSave": null
+		},
+	
+	
+		/**
+		 * Scrolling settings for a table.
+		 *  @namespace
+		 */
+		"oScroll": {
+			/**
+			 * When the table is shorter in height than sScrollY, collapse the
+			 * table container down to the height of the table (when true).
+			 * Note that this parameter will be set by the initialisation routine. To
+			 * set a default use {@link DataTable.defaults}.
+			 *  @type boolean
+			 */
+			"bCollapse": null,
+	
+			/**
+			 * Width of the scrollbar for the web-browser's platform. Calculated
+			 * during table initialisation.
+			 *  @type int
+			 *  @default 0
+			 */
+			"iBarWidth": 0,
+	
+			/**
+			 * Viewport width for horizontal scrolling. Horizontal scrolling is
+			 * disabled if an empty string.
+			 * Note that this parameter will be set by the initialisation routine. To
+			 * set a default use {@link DataTable.defaults}.
+			 *  @type string
+			 */
+			"sX": null,
+	
+			/**
+			 * Width to expand the table to when using x-scrolling. Typically you
+			 * should not need to use this.
+			 * Note that this parameter will be set by the initialisation routine. To
+			 * set a default use {@link DataTable.defaults}.
+			 *  @type string
+			 *  @deprecated
+			 */
+			"sXInner": null,
+	
+			/**
+			 * Viewport height for vertical scrolling. Vertical scrolling is disabled
+			 * if an empty string.
+			 * Note that this parameter will be set by the initialisation routine. To
+			 * set a default use {@link DataTable.defaults}.
+			 *  @type string
+			 */
+			"sY": null
+		},
+	
+		/**
+		 * Language information for the table.
+		 *  @namespace
+		 *  @extends DataTable.defaults.oLanguage
+		 */
+		"oLanguage": {
+			/**
+			 * Information callback function. See
+			 * {@link DataTable.defaults.fnInfoCallback}
+			 *  @type function
+			 *  @default null
+			 */
+			"fnInfoCallback": null
+		},
+	
+		/**
+		 * Browser support parameters
+		 *  @namespace
+		 */
+		"oBrowser": {
+			/**
+			 * Indicate if the browser incorrectly calculates width:100% inside a
+			 * scrolling element (IE6/7)
+			 *  @type boolean
+			 *  @default false
+			 */
+			"bScrollOversize": false,
+	
+			/**
+			 * Determine if the vertical scrollbar is on the right or left of the
+			 * scrolling container - needed for rtl language layout, although not
+			 * all browsers move the scrollbar (Safari).
+			 *  @type boolean
+			 *  @default false
+			 */
+			"bScrollbarLeft": false,
+	
+			/**
+			 * Flag for if `getBoundingClientRect` is fully supported or not
+			 *  @type boolean
+			 *  @default false
+			 */
+			"bBounding": false,
+	
+			/**
+			 * Browser scrollbar width
+			 *  @type integer
+			 *  @default 0
+			 */
+			"barWidth": 0
+		},
+	
+	
+		"ajax": null,
+	
+	
+		/**
+		 * Array referencing the nodes which are used for the features. The
+		 * parameters of this object match what is allowed by sDom - i.e.
+		 *   <ul>
+		 *     <li>'l' - Length changing</li>
+		 *     <li>'f' - Filtering input</li>
+		 *     <li>'t' - The table!</li>
+		 *     <li>'i' - Information</li>
+		 *     <li>'p' - Pagination</li>
+		 *     <li>'r' - pRocessing</li>
+		 *   </ul>
+		 *  @type array
+		 *  @default []
+		 */
+		"aanFeatures": [],
+	
+		/**
+		 * Store data information - see {@link DataTable.models.oRow} for detailed
+		 * information.
+		 *  @type array
+		 *  @default []
+		 */
+		"aoData": [],
+	
+		/**
+		 * Array of indexes which are in the current display (after filtering etc)
+		 *  @type array
+		 *  @default []
+		 */
+		"aiDisplay": [],
+	
+		/**
+		 * Array of indexes for display - no filtering
+		 *  @type array
+		 *  @default []
+		 */
+		"aiDisplayMaster": [],
+	
+		/**
+		 * Map of row ids to data indexes
+		 *  @type object
+		 *  @default {}
+		 */
+		"aIds": {},
+	
+		/**
+		 * Store information about each column that is in use
+		 *  @type array
+		 *  @default []
+		 */
+		"aoColumns": [],
+	
+		/**
+		 * Store information about the table's header
+		 *  @type array
+		 *  @default []
+		 */
+		"aoHeader": [],
+	
+		/**
+		 * Store information about the table's footer
+		 *  @type array
+		 *  @default []
+		 */
+		"aoFooter": [],
+	
+		/**
+		 * Store the applied global search information in case we want to force a
+		 * research or compare the old search to a new one.
+		 * Note that this parameter will be set by the initialisation routine. To
+		 * set a default use {@link DataTable.defaults}.
+		 *  @namespace
+		 *  @extends DataTable.models.oSearch
+		 */
+		"oPreviousSearch": {},
+	
+		/**
+		 * Store the applied search for each column - see
+		 * {@link DataTable.models.oSearch} for the format that is used for the
+		 * filtering information for each column.
+		 *  @type array
+		 *  @default []
+		 */
+		"aoPreSearchCols": [],
+	
+		/**
+		 * Sorting that is applied to the table. Note that the inner arrays are
+		 * used in the following manner:
+		 * <ul>
+		 *   <li>Index 0 - column number</li>
+		 *   <li>Index 1 - current sorting direction</li>
+		 * </ul>
+		 * Note that this parameter will be set by the initialisation routine. To
+		 * set a default use {@link DataTable.defaults}.
+		 *  @type array
+		 *  @todo These inner arrays should really be objects
+		 */
+		"aaSorting": null,
+	
+		/**
+		 * Sorting that is always applied to the table (i.e. prefixed in front of
+		 * aaSorting).
+		 * Note that this parameter will be set by the initialisation routine. To
+		 * set a default use {@link DataTable.defaults}.
+		 *  @type array
+		 *  @default []
+		 */
+		"aaSortingFixed": [],
+	
+		/**
+		 * Classes to use for the striping of a table.
+		 * Note that this parameter will be set by the initialisation routine. To
+		 * set a default use {@link DataTable.defaults}.
+		 *  @type array
+		 *  @default []
+		 */
+		"asStripeClasses": null,
+	
+		/**
+		 * If restoring a table - we should restore its striping classes as well
+		 *  @type array
+		 *  @default []
+		 */
+		"asDestroyStripes": [],
+	
+		/**
+		 * If restoring a table - we should restore its width
+		 *  @type int
+		 *  @default 0
+		 */
+		"sDestroyWidth": 0,
+	
+		/**
+		 * Callback functions array for every time a row is inserted (i.e. on a draw).
+		 *  @type array
+		 *  @default []
+		 */
+		"aoRowCallback": [],
+	
+		/**
+		 * Callback functions for the header on each draw.
+		 *  @type array
+		 *  @default []
+		 */
+		"aoHeaderCallback": [],
+	
+		/**
+		 * Callback function for the footer on each draw.
+		 *  @type array
+		 *  @default []
+		 */
+		"aoFooterCallback": [],
+	
+		/**
+		 * Array of callback functions for draw callback functions
+		 *  @type array
+		 *  @default []
+		 */
+		"aoDrawCallback": [],
+	
+		/**
+		 * Array of callback functions for row created function
+		 *  @type array
+		 *  @default []
+		 */
+		"aoRowCreatedCallback": [],
+	
+		/**
+		 * Callback functions for just before the table is redrawn. A return of
+		 * false will be used to cancel the draw.
+		 *  @type array
+		 *  @default []
+		 */
+		"aoPreDrawCallback": [],
+	
+		/**
+		 * Callback functions for when the table has been initialised.
+		 *  @type array
+		 *  @default []
+		 */
+		"aoInitComplete": [],
+	
+	
+		/**
+		 * Callbacks for modifying the settings to be stored for state saving, prior to
+		 * saving state.
+		 *  @type array
+		 *  @default []
+		 */
+		"aoStateSaveParams": [],
+	
+		/**
+		 * Callbacks for modifying the settings that have been stored for state saving
+		 * prior to using the stored values to restore the state.
+		 *  @type array
+		 *  @default []
+		 */
+		"aoStateLoadParams": [],
+	
+		/**
+		 * Callbacks for operating on the settings object once the saved state has been
+		 * loaded
+		 *  @type array
+		 *  @default []
+		 */
+		"aoStateLoaded": [],
+	
+		/**
+		 * Cache the table ID for quick access
+		 *  @type string
+		 *  @default <i>Empty string</i>
+		 */
+		"sTableId": "",
+	
+		/**
+		 * The TABLE node for the main table
+		 *  @type node
+		 *  @default null
+		 */
+		"nTable": null,
+	
+		/**
+		 * Permanent ref to the thead element
+		 *  @type node
+		 *  @default null
+		 */
+		"nTHead": null,
+	
+		/**
+		 * Permanent ref to the tfoot element - if it exists
+		 *  @type node
+		 *  @default null
+		 */
+		"nTFoot": null,
+	
+		/**
+		 * Permanent ref to the tbody element
+		 *  @type node
+		 *  @default null
+		 */
+		"nTBody": null,
+	
+		/**
+		 * Cache the wrapper node (contains all DataTables controlled elements)
+		 *  @type node
+		 *  @default null
+		 */
+		"nTableWrapper": null,
+	
+		/**
+		 * Indicate if when using server-side processing the loading of data
+		 * should be deferred until the second draw.
+		 * Note that this parameter will be set by the initialisation routine. To
+		 * set a default use {@link DataTable.defaults}.
+		 *  @type boolean
+		 *  @default false
+		 */
+		"bDeferLoading": false,
+	
+		/**
+		 * Indicate if all required information has been read in
+		 *  @type boolean
+		 *  @default false
+		 */
+		"bInitialised": false,
+	
+		/**
+		 * Information about open rows. Each object in the array has the parameters
+		 * 'nTr' and 'nParent'
+		 *  @type array
+		 *  @default []
+		 */
+		"aoOpenRows": [],
+	
+		/**
+		 * Dictate the positioning of DataTables' control elements - see
+		 * {@link DataTable.model.oInit.sDom}.
+		 * Note that this parameter will be set by the initialisation routine. To
+		 * set a default use {@link DataTable.defaults}.
+		 *  @type string
+		 *  @default null
+		 */
+		"sDom": null,
+	
+		/**
+		 * Search delay (in mS)
+		 *  @type integer
+		 *  @default null
+		 */
+		"searchDelay": null,
+	
+		/**
+		 * Which type of pagination should be used.
+		 * Note that this parameter will be set by the initialisation routine. To
+		 * set a default use {@link DataTable.defaults}.
+		 *  @type string
+		 *  @default two_button
+		 */
+		"sPaginationType": "two_button",
+	
+		/**
+		 * The state duration (for `stateSave`) in seconds.
+		 * Note that this parameter will be set by the initialisation routine. To
+		 * set a default use {@link DataTable.defaults}.
+		 *  @type int
+		 *  @default 0
+		 */
+		"iStateDuration": 0,
+	
+		/**
+		 * Array of callback functions for state saving. Each array element is an
+		 * object with the following parameters:
+		 *   <ul>
+		 *     <li>function:fn - function to call. Takes two parameters, oSettings
+		 *       and the JSON string to save that has been thus far created. Returns
+		 *       a JSON string to be inserted into a json object
+		 *       (i.e. '"param": [ 0, 1, 2]')</li>
+		 *     <li>string:sName - name of callback</li>
+		 *   </ul>
+		 *  @type array
+		 *  @default []
+		 */
+		"aoStateSave": [],
+	
+		/**
+		 * Array of callback functions for state loading. Each array element is an
+		 * object with the following parameters:
+		 *   <ul>
+		 *     <li>function:fn - function to call. Takes two parameters, oSettings
+		 *       and the object stored. May return false to cancel state loading</li>
+		 *     <li>string:sName - name of callback</li>
+		 *   </ul>
+		 *  @type array
+		 *  @default []
+		 */
+		"aoStateLoad": [],
+	
+		/**
+		 * State that was saved. Useful for back reference
+		 *  @type object
+		 *  @default null
+		 */
+		"oSavedState": null,
+	
+		/**
+		 * State that was loaded. Useful for back reference
+		 *  @type object
+		 *  @default null
+		 */
+		"oLoadedState": null,
+	
+		/**
+		 * Source url for AJAX data for the table.
+		 * Note that this parameter will be set by the initialisation routine. To
+		 * set a default use {@link DataTable.defaults}.
+		 *  @type string
+		 *  @default null
+		 */
+		"sAjaxSource": null,
+	
+		/**
+		 * Property from a given object from which to read the table data from. This
+		 * can be an empty string (when not server-side processing), in which case
+		 * it is  assumed an an array is given directly.
+		 * Note that this parameter will be set by the initialisation routine. To
+		 * set a default use {@link DataTable.defaults}.
+		 *  @type string
+		 */
+		"sAjaxDataProp": null,
+	
+		/**
+		 * Note if draw should be blocked while getting data
+		 *  @type boolean
+		 *  @default true
+		 */
+		"bAjaxDataGet": true,
+	
+		/**
+		 * The last jQuery XHR object that was used for server-side data gathering.
+		 * This can be used for working with the XHR information in one of the
+		 * callbacks
+		 *  @type object
+		 *  @default null
+		 */
+		"jqXHR": null,
+	
+		/**
+		 * JSON returned from the server in the last Ajax request
+		 *  @type object
+		 *  @default undefined
+		 */
+		"json": undefined,
+	
+		/**
+		 * Data submitted as part of the last Ajax request
+		 *  @type object
+		 *  @default undefined
+		 */
+		"oAjaxData": undefined,
+	
+		/**
+		 * Function to get the server-side data.
+		 * Note that this parameter will be set by the initialisation routine. To
+		 * set a default use {@link DataTable.defaults}.
+		 *  @type function
+		 */
+		"fnServerData": null,
+	
+		/**
+		 * Functions which are called prior to sending an Ajax request so extra
+		 * parameters can easily be sent to the server
+		 *  @type array
+		 *  @default []
+		 */
+		"aoServerParams": [],
+	
+		/**
+		 * Send the XHR HTTP method - GET or POST (could be PUT or DELETE if
+		 * required).
+		 * Note that this parameter will be set by the initialisation routine. To
+		 * set a default use {@link DataTable.defaults}.
+		 *  @type string
+		 */
+		"sServerMethod": null,
+	
+		/**
+		 * Format numbers for display.
+		 * Note that this parameter will be set by the initialisation routine. To
+		 * set a default use {@link DataTable.defaults}.
+		 *  @type function
+		 */
+		"fnFormatNumber": null,
+	
+		/**
+		 * List of options that can be used for the user selectable length menu.
+		 * Note that this parameter will be set by the initialisation routine. To
+		 * set a default use {@link DataTable.defaults}.
+		 *  @type array
+		 *  @default []
+		 */
+		"aLengthMenu": null,
+	
+		/**
+		 * Counter for the draws that the table does. Also used as a tracker for
+		 * server-side processing
+		 *  @type int
+		 *  @default 0
+		 */
+		"iDraw": 0,
+	
+		/**
+		 * Indicate if a redraw is being done - useful for Ajax
+		 *  @type boolean
+		 *  @default false
+		 */
+		"bDrawing": false,
+	
+		/**
+		 * Draw index (iDraw) of the last error when parsing the returned data
+		 *  @type int
+		 *  @default -1
+		 */
+		"iDrawError": -1,
+	
+		/**
+		 * Paging display length
+		 *  @type int
+		 *  @default 10
+		 */
+		"_iDisplayLength": 10,
+	
+		/**
+		 * Paging start point - aiDisplay index
+		 *  @type int
+		 *  @default 0
+		 */
+		"_iDisplayStart": 0,
+	
+		/**
+		 * Server-side processing - number of records in the result set
+		 * (i.e. before filtering), Use fnRecordsTotal rather than
+		 * this property to get the value of the number of records, regardless of
+		 * the server-side processing setting.
+		 *  @type int
+		 *  @default 0
+		 *  @private
+		 */
+		"_iRecordsTotal": 0,
+	
+		/**
+		 * Server-side processing - number of records in the current display set
+		 * (i.e. after filtering). Use fnRecordsDisplay rather than
+		 * this property to get the value of the number of records, regardless of
+		 * the server-side processing setting.
+		 *  @type boolean
+		 *  @default 0
+		 *  @private
+		 */
+		"_iRecordsDisplay": 0,
+	
+		/**
+		 * Flag to indicate if jQuery UI marking and classes should be used.
+		 * Note that this parameter will be set by the initialisation routine. To
+		 * set a default use {@link DataTable.defaults}.
+		 *  @type boolean
+		 */
+		"bJUI": null,
+	
+		/**
+		 * The classes to use for the table
+		 *  @type object
+		 *  @default {}
+		 */
+		"oClasses": {},
+	
+		/**
+		 * Flag attached to the settings object so you can check in the draw
+		 * callback if filtering has been done in the draw. Deprecated in favour of
+		 * events.
+		 *  @type boolean
+		 *  @default false
+		 *  @deprecated
+		 */
+		"bFiltered": false,
+	
+		/**
+		 * Flag attached to the settings object so you can check in the draw
+		 * callback if sorting has been done in the draw. Deprecated in favour of
+		 * events.
+		 *  @type boolean
+		 *  @default false
+		 *  @deprecated
+		 */
+		"bSorted": false,
+	
+		/**
+		 * Indicate that if multiple rows are in the header and there is more than
+		 * one unique cell per column, if the top one (true) or bottom one (false)
+		 * should be used for sorting / title by DataTables.
+		 * Note that this parameter will be set by the initialisation routine. To
+		 * set a default use {@link DataTable.defaults}.
+		 *  @type boolean
+		 */
+		"bSortCellsTop": null,
+	
+		/**
+		 * Initialisation object that is used for the table
+		 *  @type object
+		 *  @default null
+		 */
+		"oInit": null,
+	
+		/**
+		 * Destroy callback functions - for plug-ins to attach themselves to the
+		 * destroy so they can clean up markup and events.
+		 *  @type array
+		 *  @default []
+		 */
+		"aoDestroyCallback": [],
+	
+	
+		/**
+		 * Get the number of records in the current record set, before filtering
+		 *  @type function
+		 */
+		"fnRecordsTotal": function ()
+		{
+			return _fnDataSource( this ) == 'ssp' ?
+				this._iRecordsTotal * 1 :
+				this.aiDisplayMaster.length;
+		},
+	
+		/**
+		 * Get the number of records in the current record set, after filtering
+		 *  @type function
+		 */
+		"fnRecordsDisplay": function ()
+		{
+			return _fnDataSource( this ) == 'ssp' ?
+				this._iRecordsDisplay * 1 :
+				this.aiDisplay.length;
+		},
+	
+		/**
+		 * Get the display end point - aiDisplay index
+		 *  @type function
+		 */
+		"fnDisplayEnd": function ()
+		{
+			var
+				len      = this._iDisplayLength,
+				start    = this._iDisplayStart,
+				calc     = start + len,
+				records  = this.aiDisplay.length,
+				features = this.oFeatures,
+				paginate = features.bPaginate;
+	
+			if ( features.bServerSide ) {
+				return paginate === false || len === -1 ?
+					start + records :
+					Math.min( start+len, this._iRecordsDisplay );
+			}
+			else {
+				return ! paginate || calc>records || len===-1 ?
+					records :
+					calc;
+			}
+		},
+	
+		/**
+		 * The DataTables object for this table
+		 *  @type object
+		 *  @default null
+		 */
+		"oInstance": null,
+	
+		/**
+		 * Unique identifier for each instance of the DataTables object. If there
+		 * is an ID on the table node, then it takes that value, otherwise an
+		 * incrementing internal counter is used.
+		 *  @type string
+		 *  @default null
+		 */
+		"sInstance": null,
+	
+		/**
+		 * tabindex attribute value that is added to DataTables control elements, allowing
+		 * keyboard navigation of the table and its controls.
+		 */
+		"iTabIndex": 0,
+	
+		/**
+		 * DIV container for the footer scrolling table if scrolling
+		 */
+		"nScrollHead": null,
+	
+		/**
+		 * DIV container for the footer scrolling table if scrolling
+		 */
+		"nScrollFoot": null,
+	
+		/**
+		 * Last applied sort
+		 *  @type array
+		 *  @default []
+		 */
+		"aLastSort": [],
+	
+		/**
+		 * Stored plug-in instances
+		 *  @type object
+		 *  @default {}
+		 */
+		"oPlugins": {},
+	
+		/**
+		 * Function used to get a row's id from the row's data
+		 *  @type function
+		 *  @default null
+		 */
+		"rowIdFn": null,
+	
+		/**
+		 * Data location where to store a row's id
+		 *  @type string
+		 *  @default null
+		 */
+		"rowId": null
+	};
+
+	/**
+	 * Extension object for DataTables that is used to provide all extension
+	 * options.
+	 *
+	 * Note that the `DataTable.ext` object is available through
+	 * `jQuery.fn.dataTable.ext` where it may be accessed and manipulated. It is
+	 * also aliased to `jQuery.fn.dataTableExt` for historic reasons.
+	 *  @namespace
+	 *  @extends DataTable.models.ext
+	 */
+	
+	
+	/**
+	 * DataTables extensions
+	 * 
+	 * This namespace acts as a collection area for plug-ins that can be used to
+	 * extend DataTables capabilities. Indeed many of the build in methods
+	 * use this method to provide their own capabilities (sorting methods for
+	 * example).
+	 *
+	 * Note that this namespace is aliased to `jQuery.fn.dataTableExt` for legacy
+	 * reasons
+	 *
+	 *  @namespace
+	 */
+	DataTable.ext = _ext = {
+		/**
+		 * Buttons. For use with the Buttons extension for DataTables. This is
+		 * defined here so other extensions can define buttons regardless of load
+		 * order. It is _not_ used by DataTables core.
+		 *
+		 *  @type object
+		 *  @default {}
+		 */
+		buttons: {},
+	
+	
+		/**
+		 * Element class names
+		 *
+		 *  @type object
+		 *  @default {}
+		 */
+		classes: {},
+	
+	
+		/**
+		 * DataTables build type (expanded by the download builder)
+		 *
+		 *  @type string
+		 */
+		builder: "-source-",
+	
+	
+		/**
+		 * Error reporting.
+		 * 
+		 * How should DataTables report an error. Can take the value 'alert',
+		 * 'throw', 'none' or a function.
+		 *
+		 *  @type string|function
+		 *  @default alert
+		 */
+		errMode: "alert",
+	
+	
+		/**
+		 * Feature plug-ins.
+		 * 
+		 * This is an array of objects which describe the feature plug-ins that are
+		 * available to DataTables. These feature plug-ins are then available for
+		 * use through the `dom` initialisation option.
+		 * 
+		 * Each feature plug-in is described by an object which must have the
+		 * following properties:
+		 * 
+		 * * `fnInit` - function that is used to initialise the plug-in,
+		 * * `cFeature` - a character so the feature can be enabled by the `dom`
+		 *   instillation option. This is case sensitive.
+		 *
+		 * The `fnInit` function has the following input parameters:
+		 *
+		 * 1. `{object}` DataTables settings object: see
+		 *    {@link DataTable.models.oSettings}
+		 *
+		 * And the following return is expected:
+		 * 
+		 * * {node|null} The element which contains your feature. Note that the
+		 *   return may also be void if your plug-in does not require to inject any
+		 *   DOM elements into DataTables control (`dom`) - for example this might
+		 *   be useful when developing a plug-in which allows table control via
+		 *   keyboard entry
+		 *
+		 *  @type array
+		 *
+		 *  @example
+		 *    $.fn.dataTable.ext.features.push( {
+		 *      "fnInit": function( oSettings ) {
+		 *        return new TableTools( { "oDTSettings": oSettings } );
+		 *      },
+		 *      "cFeature": "T"
+		 *    } );
+		 */
+		feature: [],
+	
+	
+		/**
+		 * Row searching.
+		 * 
+		 * This method of searching is complimentary to the default type based
+		 * searching, and a lot more comprehensive as it allows you complete control
+		 * over the searching logic. Each element in this array is a function
+		 * (parameters described below) that is called for every row in the table,
+		 * and your logic decides if it should be included in the searching data set
+		 * or not.
+		 *
+		 * Searching functions have the following input parameters:
+		 *
+		 * 1. `{object}` DataTables settings object: see
+		 *    {@link DataTable.models.oSettings}
+		 * 2. `{array|object}` Data for the row to be processed (same as the
+		 *    original format that was passed in as the data source, or an array
+		 *    from a DOM data source
+		 * 3. `{int}` Row index ({@link DataTable.models.oSettings.aoData}), which
+		 *    can be useful to retrieve the `TR` element if you need DOM interaction.
+		 *
+		 * And the following return is expected:
+		 *
+		 * * {boolean} Include the row in the searched result set (true) or not
+		 *   (false)
+		 *
+		 * Note that as with the main search ability in DataTables, technically this
+		 * is "filtering", since it is subtractive. However, for consistency in
+		 * naming we call it searching here.
+		 *
+		 *  @type array
+		 *  @default []
+		 *
+		 *  @example
+		 *    // The following example shows custom search being applied to the
+		 *    // fourth column (i.e. the data[3] index) based on two input values
+		 *    // from the end-user, matching the data in a certain range.
+		 *    $.fn.dataTable.ext.search.push(
+		 *      function( settings, data, dataIndex ) {
+		 *        var min = document.getElementById('min').value * 1;
+		 *        var max = document.getElementById('max').value * 1;
+		 *        var version = data[3] == "-" ? 0 : data[3]*1;
+		 *
+		 *        if ( min == "" && max == "" ) {
+		 *          return true;
+		 *        }
+		 *        else if ( min == "" && version < max ) {
+		 *          return true;
+		 *        }
+		 *        else if ( min < version && "" == max ) {
+		 *          return true;
+		 *        }
+		 *        else if ( min < version && version < max ) {
+		 *          return true;
+		 *        }
+		 *        return false;
+		 *      }
+		 *    );
+		 */
+		search: [],
+	
+	
+		/**
+		 * Selector extensions
+		 *
+		 * The `selector` option can be used to extend the options available for the
+		 * selector modifier options (`selector-modifier` object data type) that
+		 * each of the three built in selector types offer (row, column and cell +
+		 * their plural counterparts). For example the Select extension uses this
+		 * mechanism to provide an option to select only rows, columns and cells
+		 * that have been marked as selected by the end user (`{selected: true}`),
+		 * which can be used in conjunction with the existing built in selector
+		 * options.
+		 *
+		 * Each property is an array to which functions can be pushed. The functions
+		 * take three attributes:
+		 *
+		 * * Settings object for the host table
+		 * * Options object (`selector-modifier` object type)
+		 * * Array of selected item indexes
+		 *
+		 * The return is an array of the resulting item indexes after the custom
+		 * selector has been applied.
+		 *
+		 *  @type object
+		 */
+		selector: {
+			cell: [],
+			column: [],
+			row: []
+		},
+	
+	
+		/**
+		 * Internal functions, exposed for used in plug-ins.
+		 * 
+		 * Please note that you should not need to use the internal methods for
+		 * anything other than a plug-in (and even then, try to avoid if possible).
+		 * The internal function may change between releases.
+		 *
+		 *  @type object
+		 *  @default {}
+		 */
+		internal: {},
+	
+	
+		/**
+		 * Legacy configuration options. Enable and disable legacy options that
+		 * are available in DataTables.
+		 *
+		 *  @type object
+		 */
+		legacy: {
+			/**
+			 * Enable / disable DataTables 1.9 compatible server-side processing
+			 * requests
+			 *
+			 *  @type boolean
+			 *  @default null
+			 */
+			ajax: null
+		},
+	
+	
+		/**
+		 * Pagination plug-in methods.
+		 * 
+		 * Each entry in this object is a function and defines which buttons should
+		 * be shown by the pagination rendering method that is used for the table:
+		 * {@link DataTable.ext.renderer.pageButton}. The renderer addresses how the
+		 * buttons are displayed in the document, while the functions here tell it
+		 * what buttons to display. This is done by returning an array of button
+		 * descriptions (what each button will do).
+		 *
+		 * Pagination types (the four built in options and any additional plug-in
+		 * options defined here) can be used through the `paginationType`
+		 * initialisation parameter.
+		 *
+		 * The functions defined take two parameters:
+		 *
+		 * 1. `{int} page` The current page index
+		 * 2. `{int} pages` The number of pages in the table
+		 *
+		 * Each function is expected to return an array where each element of the
+		 * array can be one of:
+		 *
+		 * * `first` - Jump to first page when activated
+		 * * `last` - Jump to last page when activated
+		 * * `previous` - Show previous page when activated
+		 * * `next` - Show next page when activated
+		 * * `{int}` - Show page of the index given
+		 * * `{array}` - A nested array containing the above elements to add a
+		 *   containing 'DIV' element (might be useful for styling).
+		 *
+		 * Note that DataTables v1.9- used this object slightly differently whereby
+		 * an object with two functions would be defined for each plug-in. That
+		 * ability is still supported by DataTables 1.10+ to provide backwards
+		 * compatibility, but this option of use is now decremented and no longer
+		 * documented in DataTables 1.10+.
+		 *
+		 *  @type object
+		 *  @default {}
+		 *
+		 *  @example
+		 *    // Show previous, next and current page buttons only
+		 *    $.fn.dataTableExt.oPagination.current = function ( page, pages ) {
+		 *      return [ 'previous', page, 'next' ];
+		 *    };
+		 */
+		pager: {},
+	
+	
+		renderer: {
+			pageButton: {},
+			header: {}
+		},
+	
+	
+		/**
+		 * Ordering plug-ins - custom data source
+		 * 
+		 * The extension options for ordering of data available here is complimentary
+		 * to the default type based ordering that DataTables typically uses. It
+		 * allows much greater control over the the data that is being used to
+		 * order a column, but is necessarily therefore more complex.
+		 * 
+		 * This type of ordering is useful if you want to do ordering based on data
+		 * live from the DOM (for example the contents of an 'input' element) rather
+		 * than just the static string that DataTables knows of.
+		 * 
+		 * The way these plug-ins work is that you create an array of the values you
+		 * wish to be ordering for the column in question and then return that
+		 * array. The data in the array much be in the index order of the rows in
+		 * the table (not the currently ordering order!). Which order data gathering
+		 * function is run here depends on the `dt-init columns.orderDataType`
+		 * parameter that is used for the column (if any).
+		 *
+		 * The functions defined take two parameters:
+		 *
+		 * 1. `{object}` DataTables settings object: see
+		 *    {@link DataTable.models.oSettings}
+		 * 2. `{int}` Target column index
+		 *
+		 * Each function is expected to return an array:
+		 *
+		 * * `{array}` Data for the column to be ordering upon
+		 *
+		 *  @type array
+		 *
+		 *  @example
+		 *    // Ordering using `input` node values
+		 *    $.fn.dataTable.ext.order['dom-text'] = function  ( settings, col )
+		 *    {
+		 *      return this.api().column( col, {order:'index'} ).nodes().map( function ( td, i ) {
+		 *        return $('input', td).val();
+		 *      } );
+		 *    }
+		 */
+		order: {},
+	
+	
+		/**
+		 * Type based plug-ins.
+		 *
+		 * Each column in DataTables has a type assigned to it, either by automatic
+		 * detection or by direct assignment using the `type` option for the column.
+		 * The type of a column will effect how it is ordering and search (plug-ins
+		 * can also make use of the column type if required).
+		 *
+		 * @namespace
+		 */
+		type: {
+			/**
+			 * Type detection functions.
+			 *
+			 * The functions defined in this object are used to automatically detect
+			 * a column's type, making initialisation of DataTables super easy, even
+			 * when complex data is in the table.
+			 *
+			 * The functions defined take two parameters:
+			 *
+		     *  1. `{*}` Data from the column cell to be analysed
+		     *  2. `{settings}` DataTables settings object. This can be used to
+		     *     perform context specific type detection - for example detection
+		     *     based on language settings such as using a comma for a decimal
+		     *     place. Generally speaking the options from the settings will not
+		     *     be required
+			 *
+			 * Each function is expected to return:
+			 *
+			 * * `{string|null}` Data type detected, or null if unknown (and thus
+			 *   pass it on to the other type detection functions.
+			 *
+			 *  @type array
+			 *
+			 *  @example
+			 *    // Currency type detection plug-in:
+			 *    $.fn.dataTable.ext.type.detect.push(
+			 *      function ( data, settings ) {
+			 *        // Check the numeric part
+			 *        if ( ! $.isNumeric( data.substring(1) ) ) {
+			 *          return null;
+			 *        }
+			 *
+			 *        // Check prefixed by currency
+			 *        if ( data.charAt(0) == '$' || data.charAt(0) == '&pound;' ) {
+			 *          return 'currency';
+			 *        }
+			 *        return null;
+			 *      }
+			 *    );
+			 */
+			detect: [],
+	
+	
+			/**
+			 * Type based search formatting.
+			 *
+			 * The type based searching functions can be used to pre-format the
+			 * data to be search on. For example, it can be used to strip HTML
+			 * tags or to de-format telephone numbers for numeric only searching.
+			 *
+			 * Note that is a search is not defined for a column of a given type,
+			 * no search formatting will be performed.
+			 * 
+			 * Pre-processing of searching data plug-ins - When you assign the sType
+			 * for a column (or have it automatically detected for you by DataTables
+			 * or a type detection plug-in), you will typically be using this for
+			 * custom sorting, but it can also be used to provide custom searching
+			 * by allowing you to pre-processing the data and returning the data in
+			 * the format that should be searched upon. This is done by adding
+			 * functions this object with a parameter name which matches the sType
+			 * for that target column. This is the corollary of <i>afnSortData</i>
+			 * for searching data.
+			 *
+			 * The functions defined take a single parameter:
+			 *
+		     *  1. `{*}` Data from the column cell to be prepared for searching
+			 *
+			 * Each function is expected to return:
+			 *
+			 * * `{string|null}` Formatted string that will be used for the searching.
+			 *
+			 *  @type object
+			 *  @default {}
+			 *
+			 *  @example
+			 *    $.fn.dataTable.ext.type.search['title-numeric'] = function ( d ) {
+			 *      return d.replace(/\n/g," ").replace( /<.*?>/g, "" );
+			 *    }
+			 */
+			search: {},
+	
+	
+			/**
+			 * Type based ordering.
+			 *
+			 * The column type tells DataTables what ordering to apply to the table
+			 * when a column is sorted upon. The order for each type that is defined,
+			 * is defined by the functions available in this object.
+			 *
+			 * Each ordering option can be described by three properties added to
+			 * this object:
+			 *
+			 * * `{type}-pre` - Pre-formatting function
+			 * * `{type}-asc` - Ascending order function
+			 * * `{type}-desc` - Descending order function
+			 *
+			 * All three can be used together, only `{type}-pre` or only
+			 * `{type}-asc` and `{type}-desc` together. It is generally recommended
+			 * that only `{type}-pre` is used, as this provides the optimal
+			 * implementation in terms of speed, although the others are provided
+			 * for compatibility with existing Javascript sort functions.
+			 *
+			 * `{type}-pre`: Functions defined take a single parameter:
+			 *
+		     *  1. `{*}` Data from the column cell to be prepared for ordering
+			 *
+			 * And return:
+			 *
+			 * * `{*}` Data to be sorted upon
+			 *
+			 * `{type}-asc` and `{type}-desc`: Functions are typical Javascript sort
+			 * functions, taking two parameters:
+			 *
+		     *  1. `{*}` Data to compare to the second parameter
+		     *  2. `{*}` Data to compare to the first parameter
+			 *
+			 * And returning:
+			 *
+			 * * `{*}` Ordering match: <0 if first parameter should be sorted lower
+			 *   than the second parameter, ===0 if the two parameters are equal and
+			 *   >0 if the first parameter should be sorted height than the second
+			 *   parameter.
+			 * 
+			 *  @type object
+			 *  @default {}
+			 *
+			 *  @example
+			 *    // Numeric ordering of formatted numbers with a pre-formatter
+			 *    $.extend( $.fn.dataTable.ext.type.order, {
+			 *      "string-pre": function(x) {
+			 *        a = (a === "-" || a === "") ? 0 : a.replace( /[^\d\-\.]/g, "" );
+			 *        return parseFloat( a );
+			 *      }
+			 *    } );
+			 *
+			 *  @example
+			 *    // Case-sensitive string ordering, with no pre-formatting method
+			 *    $.extend( $.fn.dataTable.ext.order, {
+			 *      "string-case-asc": function(x,y) {
+			 *        return ((x < y) ? -1 : ((x > y) ? 1 : 0));
+			 *      },
+			 *      "string-case-desc": function(x,y) {
+			 *        return ((x < y) ? 1 : ((x > y) ? -1 : 0));
+			 *      }
+			 *    } );
+			 */
+			order: {}
+		},
+	
+		/**
+		 * Unique DataTables instance counter
+		 *
+		 * @type int
+		 * @private
+		 */
+		_unique: 0,
+	
+	
+		//
+		// Depreciated
+		// The following properties are retained for backwards compatiblity only.
+		// The should not be used in new projects and will be removed in a future
+		// version
+		//
+	
+		/**
+		 * Version check function.
+		 *  @type function
+		 *  @depreciated Since 1.10
+		 */
+		fnVersionCheck: DataTable.fnVersionCheck,
+	
+	
+		/**
+		 * Index for what 'this' index API functions should use
+		 *  @type int
+		 *  @deprecated Since v1.10
+		 */
+		iApiIndex: 0,
+	
+	
+		/**
+		 * jQuery UI class container
+		 *  @type object
+		 *  @deprecated Since v1.10
+		 */
+		oJUIClasses: {},
+	
+	
+		/**
+		 * Software version
+		 *  @type string
+		 *  @deprecated Since v1.10
+		 */
+		sVersion: DataTable.version
+	};
+	
+	
+	//
+	// Backwards compatibility. Alias to pre 1.10 Hungarian notation counter parts
+	//
+	$.extend( _ext, {
+		afnFiltering: _ext.search,
+		aTypes:       _ext.type.detect,
+		ofnSearch:    _ext.type.search,
+		oSort:        _ext.type.order,
+		afnSortData:  _ext.order,
+		aoFeatures:   _ext.feature,
+		oApi:         _ext.internal,
+		oStdClasses:  _ext.classes,
+		oPagination:  _ext.pager
+	} );
+	
+	
+	$.extend( DataTable.ext.classes, {
+		"sTable": "dataTable",
+		"sNoFooter": "no-footer",
+	
+		/* Paging buttons */
+		"sPageButton": "paginate_button",
+		"sPageButtonActive": "current",
+		"sPageButtonDisabled": "disabled",
+	
+		/* Striping classes */
+		"sStripeOdd": "odd",
+		"sStripeEven": "even",
+	
+		/* Empty row */
+		"sRowEmpty": "dataTables_empty",
+	
+		/* Features */
+		"sWrapper": "dataTables_wrapper",
+		"sFilter": "dataTables_filter",
+		"sInfo": "dataTables_info",
+		"sPaging": "dataTables_paginate paging_", /* Note that the type is postfixed */
+		"sLength": "dataTables_length",
+		"sProcessing": "dataTables_processing",
+	
+		/* Sorting */
+		"sSortAsc": "sorting_asc",
+		"sSortDesc": "sorting_desc",
+		"sSortable": "sorting", /* Sortable in both directions */
+		"sSortableAsc": "sorting_asc_disabled",
+		"sSortableDesc": "sorting_desc_disabled",
+		"sSortableNone": "sorting_disabled",
+		"sSortColumn": "sorting_", /* Note that an int is postfixed for the sorting order */
+	
+		/* Filtering */
+		"sFilterInput": "",
+	
+		/* Page length */
+		"sLengthSelect": "",
+	
+		/* Scrolling */
+		"sScrollWrapper": "dataTables_scroll",
+		"sScrollHead": "dataTables_scrollHead",
+		"sScrollHeadInner": "dataTables_scrollHeadInner",
+		"sScrollBody": "dataTables_scrollBody",
+		"sScrollFoot": "dataTables_scrollFoot",
+		"sScrollFootInner": "dataTables_scrollFootInner",
+	
+		/* Misc */
+		"sHeaderTH": "",
+		"sFooterTH": "",
+	
+		// Deprecated
+		"sSortJUIAsc": "",
+		"sSortJUIDesc": "",
+		"sSortJUI": "",
+		"sSortJUIAscAllowed": "",
+		"sSortJUIDescAllowed": "",
+		"sSortJUIWrapper": "",
+		"sSortIcon": "",
+		"sJUIHeader": "",
+		"sJUIFooter": ""
+	} );
+	
+	
+	(function() {
+	
+	// Reused strings for better compression. Closure compiler appears to have a
+	// weird edge case where it is trying to expand strings rather than use the
+	// variable version. This results in about 200 bytes being added, for very
+	// little preference benefit since it this run on script load only.
+	var _empty = '';
+	_empty = '';
+	
+	var _stateDefault = _empty + 'ui-state-default';
+	var _sortIcon     = _empty + 'css_right ui-icon ui-icon-';
+	var _headerFooter = _empty + 'fg-toolbar ui-toolbar ui-widget-header ui-helper-clearfix';
+	
+	$.extend( DataTable.ext.oJUIClasses, DataTable.ext.classes, {
+		/* Full numbers paging buttons */
+		"sPageButton":         "fg-button ui-button "+_stateDefault,
+		"sPageButtonActive":   "ui-state-disabled",
+		"sPageButtonDisabled": "ui-state-disabled",
+	
+		/* Features */
+		"sPaging": "dataTables_paginate fg-buttonset ui-buttonset fg-buttonset-multi "+
+			"ui-buttonset-multi paging_", /* Note that the type is postfixed */
+	
+		/* Sorting */
+		"sSortAsc":            _stateDefault+" sorting_asc",
+		"sSortDesc":           _stateDefault+" sorting_desc",
+		"sSortable":           _stateDefault+" sorting",
+		"sSortableAsc":        _stateDefault+" sorting_asc_disabled",
+		"sSortableDesc":       _stateDefault+" sorting_desc_disabled",
+		"sSortableNone":       _stateDefault+" sorting_disabled",
+		"sSortJUIAsc":         _sortIcon+"triangle-1-n",
+		"sSortJUIDesc":        _sortIcon+"triangle-1-s",
+		"sSortJUI":            _sortIcon+"carat-2-n-s",
+		"sSortJUIAscAllowed":  _sortIcon+"carat-1-n",
+		"sSortJUIDescAllowed": _sortIcon+"carat-1-s",
+		"sSortJUIWrapper":     "DataTables_sort_wrapper",
+		"sSortIcon":           "DataTables_sort_icon",
+	
+		/* Scrolling */
+		"sScrollHead": "dataTables_scrollHead "+_stateDefault,
+		"sScrollFoot": "dataTables_scrollFoot "+_stateDefault,
+	
+		/* Misc */
+		"sHeaderTH":  _stateDefault,
+		"sFooterTH":  _stateDefault,
+		"sJUIHeader": _headerFooter+" ui-corner-tl ui-corner-tr",
+		"sJUIFooter": _headerFooter+" ui-corner-bl ui-corner-br"
+	} );
+	
+	}());
+	
+	
+	
+	var extPagination = DataTable.ext.pager;
+	
+	function _numbers ( page, pages ) {
+		var
+			numbers = [],
+			buttons = extPagination.numbers_length,
+			half = Math.floor( buttons / 2 ),
+			i = 1;
+	
+		if ( pages <= buttons ) {
+			numbers = _range( 0, pages );
+		}
+		else if ( page <= half ) {
+			numbers = _range( 0, buttons-2 );
+			numbers.push( 'ellipsis' );
+			numbers.push( pages-1 );
+		}
+		else if ( page >= pages - 1 - half ) {
+			numbers = _range( pages-(buttons-2), pages );
+			numbers.splice( 0, 0, 'ellipsis' ); // no unshift in ie6
+			numbers.splice( 0, 0, 0 );
+		}
+		else {
+			numbers = _range( page-half+2, page+half-1 );
+			numbers.push( 'ellipsis' );
+			numbers.push( pages-1 );
+			numbers.splice( 0, 0, 'ellipsis' );
+			numbers.splice( 0, 0, 0 );
+		}
+	
+		numbers.DT_el = 'span';
+		return numbers;
+	}
+	
+	
+	$.extend( extPagination, {
+		simple: function ( page, pages ) {
+			return [ 'previous', 'next' ];
+		},
+	
+		full: function ( page, pages ) {
+			return [  'first', 'previous', 'next', 'last' ];
+		},
+	
+		numbers: function ( page, pages ) {
+			return [ _numbers(page, pages) ];
+		},
+	
+		simple_numbers: function ( page, pages ) {
+			return [ 'previous', _numbers(page, pages), 'next' ];
+		},
+	
+		full_numbers: function ( page, pages ) {
+			return [ 'first', 'previous', _numbers(page, pages), 'next', 'last' ];
+		},
+		
+		first_last_numbers: function (page, pages) {
+	 		return ['first', _numbers(page, pages), 'last'];
+	 	},
+	
+		// For testing and plug-ins to use
+		_numbers: _numbers,
+	
+		// Number of number buttons (including ellipsis) to show. _Must be odd!_
+		numbers_length: 7
+	} );
+	
+	
+	$.extend( true, DataTable.ext.renderer, {
+		pageButton: {
+			_: function ( settings, host, idx, buttons, page, pages ) {
+				var classes = settings.oClasses;
+				var lang = settings.oLanguage.oPaginate;
+				var aria = settings.oLanguage.oAria.paginate || {};
+				var btnDisplay, btnClass, counter=0;
+	
+				var attach = function( container, buttons ) {
+					var i, ien, node, button;
+					var clickHandler = function ( e ) {
+						_fnPageChange( settings, e.data.action, true );
+					};
+	
+					for ( i=0, ien=buttons.length ; i<ien ; i++ ) {
+						button = buttons[i];
+	
+						if ( $.isArray( button ) ) {
+							var inner = $( '<'+(button.DT_el || 'div')+'/>' )
+								.appendTo( container );
+							attach( inner, button );
+						}
+						else {
+							btnDisplay = null;
+							btnClass = '';
+	
+							switch ( button ) {
+								case 'ellipsis':
+									container.append('<span class="ellipsis">&#x2026;</span>');
+									break;
+	
+								case 'first':
+									btnDisplay = lang.sFirst;
+									btnClass = button + (page > 0 ?
+										'' : ' '+classes.sPageButtonDisabled);
+									break;
+	
+								case 'previous':
+									btnDisplay = lang.sPrevious;
+									btnClass = button + (page > 0 ?
+										'' : ' '+classes.sPageButtonDisabled);
+									break;
+	
+								case 'next':
+									btnDisplay = lang.sNext;
+									btnClass = button + (page < pages-1 ?
+										'' : ' '+classes.sPageButtonDisabled);
+									break;
+	
+								case 'last':
+									btnDisplay = lang.sLast;
+									btnClass = button + (page < pages-1 ?
+										'' : ' '+classes.sPageButtonDisabled);
+									break;
+	
+								default:
+									btnDisplay = button + 1;
+									btnClass = page === button ?
+										classes.sPageButtonActive : '';
+									break;
+							}
+	
+							if ( btnDisplay !== null ) {
+								node = $('<a>', {
+										'class': classes.sPageButton+' '+btnClass,
+										'aria-controls': settings.sTableId,
+										'aria-label': aria[ button ],
+										'data-dt-idx': counter,
+										'tabindex': settings.iTabIndex,
+										'id': idx === 0 && typeof button === 'string' ?
+											settings.sTableId +'_'+ button :
+											null
+									} )
+									.html( btnDisplay )
+									.appendTo( container );
+	
+								_fnBindAction(
+									node, {action: button}, clickHandler
+								);
+	
+								counter++;
+							}
+						}
+					}
+				};
+	
+				// IE9 throws an 'unknown error' if document.activeElement is used
+				// inside an iframe or frame. Try / catch the error. Not good for
+				// accessibility, but neither are frames.
+				var activeEl;
+	
+				try {
+					// Because this approach is destroying and recreating the paging
+					// elements, focus is lost on the select button which is bad for
+					// accessibility. So we want to restore focus once the draw has
+					// completed
+					activeEl = $(host).find(document.activeElement).data('dt-idx');
+				}
+				catch (e) {}
+	
+				attach( $(host).empty(), buttons );
+	
+				if ( activeEl !== undefined ) {
+					$(host).find( '[data-dt-idx='+activeEl+']' ).focus();
+				}
+			}
+		}
+	} );
+	
+	
+	
+	// Built in type detection. See model.ext.aTypes for information about
+	// what is required from this methods.
+	$.extend( DataTable.ext.type.detect, [
+		// Plain numbers - first since V8 detects some plain numbers as dates
+		// e.g. Date.parse('55') (but not all, e.g. Date.parse('22')...).
+		function ( d, settings )
+		{
+			var decimal = settings.oLanguage.sDecimal;
+			return _isNumber( d, decimal ) ? 'num'+decimal : null;
+		},
+	
+		// Dates (only those recognised by the browser's Date.parse)
+		function ( d, settings )
+		{
+			// V8 tries _very_ hard to make a string passed into `Date.parse()`
+			// valid, so we need to use a regex to restrict date formats. Use a
+			// plug-in for anything other than ISO8601 style strings
+			if ( d && !(d instanceof Date) && ! _re_date.test(d) ) {
+				return null;
+			}
+			var parsed = Date.parse(d);
+			return (parsed !== null && !isNaN(parsed)) || _empty(d) ? 'date' : null;
+		},
+	
+		// Formatted numbers
+		function ( d, settings )
+		{
+			var decimal = settings.oLanguage.sDecimal;
+			return _isNumber( d, decimal, true ) ? 'num-fmt'+decimal : null;
+		},
+	
+		// HTML numeric
+		function ( d, settings )
+		{
+			var decimal = settings.oLanguage.sDecimal;
+			return _htmlNumeric( d, decimal ) ? 'html-num'+decimal : null;
+		},
+	
+		// HTML numeric, formatted
+		function ( d, settings )
+		{
+			var decimal = settings.oLanguage.sDecimal;
+			return _htmlNumeric( d, decimal, true ) ? 'html-num-fmt'+decimal : null;
+		},
+	
+		// HTML (this is strict checking - there must be html)
+		function ( d, settings )
+		{
+			return _empty( d ) || (typeof d === 'string' && d.indexOf('<') !== -1) ?
+				'html' : null;
+		}
+	] );
+	
+	
+	
+	// Filter formatting functions. See model.ext.ofnSearch for information about
+	// what is required from these methods.
+	// 
+	// Note that additional search methods are added for the html numbers and
+	// html formatted numbers by `_addNumericSort()` when we know what the decimal
+	// place is
+	
+	
+	$.extend( DataTable.ext.type.search, {
+		html: function ( data ) {
+			return _empty(data) ?
+				data :
+				typeof data === 'string' ?
+					data
+						.replace( _re_new_lines, " " )
+						.replace( _re_html, "" ) :
+					'';
+		},
+	
+		string: function ( data ) {
+			return _empty(data) ?
+				data :
+				typeof data === 'string' ?
+					data.replace( _re_new_lines, " " ) :
+					data;
+		}
+	} );
+	
+	
+	
+	var __numericReplace = function ( d, decimalPlace, re1, re2 ) {
+		if ( d !== 0 && (!d || d === '-') ) {
+			return -Infinity;
+		}
+	
+		// If a decimal place other than `.` is used, it needs to be given to the
+		// function so we can detect it and replace with a `.` which is the only
+		// decimal place Javascript recognises - it is not locale aware.
+		if ( decimalPlace ) {
+			d = _numToDecimal( d, decimalPlace );
+		}
+	
+		if ( d.replace ) {
+			if ( re1 ) {
+				d = d.replace( re1, '' );
+			}
+	
+			if ( re2 ) {
+				d = d.replace( re2, '' );
+			}
+		}
+	
+		return d * 1;
+	};
+	
+	
+	// Add the numeric 'deformatting' functions for sorting and search. This is done
+	// in a function to provide an easy ability for the language options to add
+	// additional methods if a non-period decimal place is used.
+	function _addNumericSort ( decimalPlace ) {
+		$.each(
+			{
+				// Plain numbers
+				"num": function ( d ) {
+					return __numericReplace( d, decimalPlace );
+				},
+	
+				// Formatted numbers
+				"num-fmt": function ( d ) {
+					return __numericReplace( d, decimalPlace, _re_formatted_numeric );
+				},
+	
+				// HTML numeric
+				"html-num": function ( d ) {
+					return __numericReplace( d, decimalPlace, _re_html );
+				},
+	
+				// HTML numeric, formatted
+				"html-num-fmt": function ( d ) {
+					return __numericReplace( d, decimalPlace, _re_html, _re_formatted_numeric );
+				}
+			},
+			function ( key, fn ) {
+				// Add the ordering method
+				_ext.type.order[ key+decimalPlace+'-pre' ] = fn;
+	
+				// For HTML types add a search formatter that will strip the HTML
+				if ( key.match(/^html\-/) ) {
+					_ext.type.search[ key+decimalPlace ] = _ext.type.search.html;
+				}
+			}
+		);
+	}
+	
+	
+	// Default sort methods
+	$.extend( _ext.type.order, {
+		// Dates
+		"date-pre": function ( d ) {
+			return Date.parse( d ) || -Infinity;
+		},
+	
+		// html
+		"html-pre": function ( a ) {
+			return _empty(a) ?
+				'' :
+				a.replace ?
+					a.replace( /<.*?>/g, "" ).toLowerCase() :
+					a+'';
+		},
+	
+		// string
+		"string-pre": function ( a ) {
+			// This is a little complex, but faster than always calling toString,
+			// http://jsperf.com/tostring-v-check
+			return _empty(a) ?
+				'' :
+				typeof a === 'string' ?
+					a.toLowerCase() :
+					! a.toString ?
+						'' :
+						a.toString();
+		},
+	
+		// string-asc and -desc are retained only for compatibility with the old
+		// sort methods
+		"string-asc": function ( x, y ) {
+			return ((x < y) ? -1 : ((x > y) ? 1 : 0));
+		},
+	
+		"string-desc": function ( x, y ) {
+			return ((x < y) ? 1 : ((x > y) ? -1 : 0));
+		}
+	} );
+	
+	
+	// Numeric sorting types - order doesn't matter here
+	_addNumericSort( '' );
+	
+	
+	$.extend( true, DataTable.ext.renderer, {
+		header: {
+			_: function ( settings, cell, column, classes ) {
+				// No additional mark-up required
+				// Attach a sort listener to update on sort - note that using the
+				// `DT` namespace will allow the event to be removed automatically
+				// on destroy, while the `dt` namespaced event is the one we are
+				// listening for
+				$(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) {
+					if ( settings !== ctx ) { // need to check this this is the host
+						return;               // table, not a nested one
+					}
+	
+					var colIdx = column.idx;
+	
+					cell
+						.removeClass(
+							column.sSortingClass +' '+
+							classes.sSortAsc +' '+
+							classes.sSortDesc
+						)
+						.addClass( columns[ colIdx ] == 'asc' ?
+							classes.sSortAsc : columns[ colIdx ] == 'desc' ?
+								classes.sSortDesc :
+								column.sSortingClass
+						);
+				} );
+			},
+	
+			jqueryui: function ( settings, cell, column, classes ) {
+				$('<div/>')
+					.addClass( classes.sSortJUIWrapper )
+					.append( cell.contents() )
+					.append( $('<span/>')
+						.addClass( classes.sSortIcon+' '+column.sSortingClassJUI )
+					)
+					.appendTo( cell );
+	
+				// Attach a sort listener to update on sort
+				$(settings.nTable).on( 'order.dt.DT', function ( e, ctx, sorting, columns ) {
+					if ( settings !== ctx ) {
+						return;
+					}
+	
+					var colIdx = column.idx;
+	
+					cell
+						.removeClass( classes.sSortAsc +" "+classes.sSortDesc )
+						.addClass( columns[ colIdx ] == 'asc' ?
+							classes.sSortAsc : columns[ colIdx ] == 'desc' ?
+								classes.sSortDesc :
+								column.sSortingClass
+						);
+	
+					cell
+						.find( 'span.'+classes.sSortIcon )
+						.removeClass(
+							classes.sSortJUIAsc +" "+
+							classes.sSortJUIDesc +" "+
+							classes.sSortJUI +" "+
+							classes.sSortJUIAscAllowed +" "+
+							classes.sSortJUIDescAllowed
+						)
+						.addClass( columns[ colIdx ] == 'asc' ?
+							classes.sSortJUIAsc : columns[ colIdx ] == 'desc' ?
+								classes.sSortJUIDesc :
+								column.sSortingClassJUI
+						);
+				} );
+			}
+		}
+	} );
+	
+	/*
+	 * Public helper functions. These aren't used internally by DataTables, or
+	 * called by any of the options passed into DataTables, but they can be used
+	 * externally by developers working with DataTables. They are helper functions
+	 * to make working with DataTables a little bit easier.
+	 */
+	
+	var __htmlEscapeEntities = function ( d ) {
+		return typeof d === 'string' ?
+			d.replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;') :
+			d;
+	};
+	
+	/**
+	 * Helpers for `columns.render`.
+	 *
+	 * The options defined here can be used with the `columns.render` initialisation
+	 * option to provide a display renderer. The following functions are defined:
+	 *
+	 * * `number` - Will format numeric data (defined by `columns.data`) for
+	 *   display, retaining the original unformatted data for sorting and filtering.
+	 *   It takes 5 parameters:
+	 *   * `string` - Thousands grouping separator
+	 *   * `string` - Decimal point indicator
+	 *   * `integer` - Number of decimal points to show
+	 *   * `string` (optional) - Prefix.
+	 *   * `string` (optional) - Postfix (/suffix).
+	 * * `text` - Escape HTML to help prevent XSS attacks. It has no optional
+	 *   parameters.
+	 *
+	 * @example
+	 *   // Column definition using the number renderer
+	 *   {
+	 *     data: "salary",
+	 *     render: $.fn.dataTable.render.number( '\'', '.', 0, '$' )
+	 *   }
+	 *
+	 * @namespace
+	 */
+	DataTable.render = {
+		number: function ( thousands, decimal, precision, prefix, postfix ) {
+			return {
+				display: function ( d ) {
+					if ( typeof d !== 'number' && typeof d !== 'string' ) {
+						return d;
+					}
+	
+					var negative = d < 0 ? '-' : '';
+					var flo = parseFloat( d );
+	
+					// If NaN then there isn't much formatting that we can do - just
+					// return immediately, escaping any HTML (this was supposed to
+					// be a number after all)
+					if ( isNaN( flo ) ) {
+						return __htmlEscapeEntities( d );
+					}
+	
+					flo = flo.toFixed( precision );
+					d = Math.abs( flo );
+	
+					var intPart = parseInt( d, 10 );
+					var floatPart = precision ?
+						decimal+(d - intPart).toFixed( precision ).substring( 2 ):
+						'';
+	
+					return negative + (prefix||'') +
+						intPart.toString().replace(
+							/\B(?=(\d{3})+(?!\d))/g, thousands
+						) +
+						floatPart +
+						(postfix||'');
+				}
+			};
+		},
+	
+		text: function () {
+			return {
+				display: __htmlEscapeEntities
+			};
+		}
+	};
+	
+	
+	/*
+	 * This is really a good bit rubbish this method of exposing the internal methods
+	 * publicly... - To be fixed in 2.0 using methods on the prototype
+	 */
+	
+	
+	/**
+	 * Create a wrapper function for exporting an internal functions to an external API.
+	 *  @param {string} fn API function name
+	 *  @returns {function} wrapped function
+	 *  @memberof DataTable#internal
+	 */
+	function _fnExternApiFunc (fn)
+	{
+		return function() {
+			var args = [_fnSettingsFromNode( this[DataTable.ext.iApiIndex] )].concat(
+				Array.prototype.slice.call(arguments)
+			);
+			return DataTable.ext.internal[fn].apply( this, args );
+		};
+	}
+	
+	
+	/**
+	 * Reference to internal functions for use by plug-in developers. Note that
+	 * these methods are references to internal functions and are considered to be
+	 * private. If you use these methods, be aware that they are liable to change
+	 * between versions.
+	 *  @namespace
+	 */
+	$.extend( DataTable.ext.internal, {
+		_fnExternApiFunc: _fnExternApiFunc,
+		_fnBuildAjax: _fnBuildAjax,
+		_fnAjaxUpdate: _fnAjaxUpdate,
+		_fnAjaxParameters: _fnAjaxParameters,
+		_fnAjaxUpdateDraw: _fnAjaxUpdateDraw,
+		_fnAjaxDataSrc: _fnAjaxDataSrc,
+		_fnAddColumn: _fnAddColumn,
+		_fnColumnOptions: _fnColumnOptions,
+		_fnAdjustColumnSizing: _fnAdjustColumnSizing,
+		_fnVisibleToColumnIndex: _fnVisibleToColumnIndex,
+		_fnColumnIndexToVisible: _fnColumnIndexToVisible,
+		_fnVisbleColumns: _fnVisbleColumns,
+		_fnGetColumns: _fnGetColumns,
+		_fnColumnTypes: _fnColumnTypes,
+		_fnApplyColumnDefs: _fnApplyColumnDefs,
+		_fnHungarianMap: _fnHungarianMap,
+		_fnCamelToHungarian: _fnCamelToHungarian,
+		_fnLanguageCompat: _fnLanguageCompat,
+		_fnBrowserDetect: _fnBrowserDetect,
+		_fnAddData: _fnAddData,
+		_fnAddTr: _fnAddTr,
+		_fnNodeToDataIndex: _fnNodeToDataIndex,
+		_fnNodeToColumnIndex: _fnNodeToColumnIndex,
+		_fnGetCellData: _fnGetCellData,
+		_fnSetCellData: _fnSetCellData,
+		_fnSplitObjNotation: _fnSplitObjNotation,
+		_fnGetObjectDataFn: _fnGetObjectDataFn,
+		_fnSetObjectDataFn: _fnSetObjectDataFn,
+		_fnGetDataMaster: _fnGetDataMaster,
+		_fnClearTable: _fnClearTable,
+		_fnDeleteIndex: _fnDeleteIndex,
+		_fnInvalidate: _fnInvalidate,
+		_fnGetRowElements: _fnGetRowElements,
+		_fnCreateTr: _fnCreateTr,
+		_fnBuildHead: _fnBuildHead,
+		_fnDrawHead: _fnDrawHead,
+		_fnDraw: _fnDraw,
+		_fnReDraw: _fnReDraw,
+		_fnAddOptionsHtml: _fnAddOptionsHtml,
+		_fnDetectHeader: _fnDetectHeader,
+		_fnGetUniqueThs: _fnGetUniqueThs,
+		_fnFeatureHtmlFilter: _fnFeatureHtmlFilter,
+		_fnFilterComplete: _fnFilterComplete,
+		_fnFilterCustom: _fnFilterCustom,
+		_fnFilterColumn: _fnFilterColumn,
+		_fnFilter: _fnFilter,
+		_fnFilterCreateSearch: _fnFilterCreateSearch,
+		_fnEscapeRegex: _fnEscapeRegex,
+		_fnFilterData: _fnFilterData,
+		_fnFeatureHtmlInfo: _fnFeatureHtmlInfo,
+		_fnUpdateInfo: _fnUpdateInfo,
+		_fnInfoMacros: _fnInfoMacros,
+		_fnInitialise: _fnInitialise,
+		_fnInitComplete: _fnInitComplete,
+		_fnLengthChange: _fnLengthChange,
+		_fnFeatureHtmlLength: _fnFeatureHtmlLength,
+		_fnFeatureHtmlPaginate: _fnFeatureHtmlPaginate,
+		_fnPageChange: _fnPageChange,
+		_fnFeatureHtmlProcessing: _fnFeatureHtmlProcessing,
+		_fnProcessingDisplay: _fnProcessingDisplay,
+		_fnFeatureHtmlTable: _fnFeatureHtmlTable,
+		_fnScrollDraw: _fnScrollDraw,
+		_fnApplyToChildren: _fnApplyToChildren,
+		_fnCalculateColumnWidths: _fnCalculateColumnWidths,
+		_fnThrottle: _fnThrottle,
+		_fnConvertToWidth: _fnConvertToWidth,
+		_fnGetWidestNode: _fnGetWidestNode,
+		_fnGetMaxLenString: _fnGetMaxLenString,
+		_fnStringToCss: _fnStringToCss,
+		_fnSortFlatten: _fnSortFlatten,
+		_fnSort: _fnSort,
+		_fnSortAria: _fnSortAria,
+		_fnSortListener: _fnSortListener,
+		_fnSortAttachListener: _fnSortAttachListener,
+		_fnSortingClasses: _fnSortingClasses,
+		_fnSortData: _fnSortData,
+		_fnSaveState: _fnSaveState,
+		_fnLoadState: _fnLoadState,
+		_fnSettingsFromNode: _fnSettingsFromNode,
+		_fnLog: _fnLog,
+		_fnMap: _fnMap,
+		_fnBindAction: _fnBindAction,
+		_fnCallbackReg: _fnCallbackReg,
+		_fnCallbackFire: _fnCallbackFire,
+		_fnLengthOverflow: _fnLengthOverflow,
+		_fnRenderer: _fnRenderer,
+		_fnDataSource: _fnDataSource,
+		_fnRowAttributes: _fnRowAttributes,
+		_fnCalculateEnd: function () {} // Used by a lot of plug-ins, but redundant
+		                                // in 1.10, so this dead-end function is
+		                                // added to prevent errors
+	} );
+	
+
+	// jQuery access
+	$.fn.dataTable = DataTable;
+
+	// Provide access to the host jQuery object (circular reference)
+	DataTable.$ = $;
+
+	// Legacy aliases
+	$.fn.dataTableSettings = DataTable.settings;
+	$.fn.dataTableExt = DataTable.ext;
+
+	// With a capital `D` we return a DataTables API instance rather than a
+	// jQuery object
+	$.fn.DataTable = function ( opts ) {
+		return $(this).dataTable( opts ).api();
+	};
+
+	// All properties that are available to $.fn.dataTable should also be
+	// available on $.fn.DataTable
+	$.each( DataTable, function ( prop, val ) {
+		$.fn.DataTable[ prop ] = val;
+	} );
+
+
+	// Information about events fired by DataTables - for documentation.
+	/**
+	 * Draw event, fired whenever the table is redrawn on the page, at the same
+	 * point as fnDrawCallback. This may be useful for binding events or
+	 * performing calculations when the table is altered at all.
+	 *  @name DataTable#draw.dt
+	 *  @event
+	 *  @param {event} e jQuery event object
+	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+	 */
+
+	/**
+	 * Search event, fired when the searching applied to the table (using the
+	 * built-in global search, or column filters) is altered.
+	 *  @name DataTable#search.dt
+	 *  @event
+	 *  @param {event} e jQuery event object
+	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+	 */
+
+	/**
+	 * Page change event, fired when the paging of the table is altered.
+	 *  @name DataTable#page.dt
+	 *  @event
+	 *  @param {event} e jQuery event object
+	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+	 */
+
+	/**
+	 * Order event, fired when the ordering applied to the table is altered.
+	 *  @name DataTable#order.dt
+	 *  @event
+	 *  @param {event} e jQuery event object
+	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+	 */
+
+	/**
+	 * DataTables initialisation complete event, fired when the table is fully
+	 * drawn, including Ajax data loaded, if Ajax data is required.
+	 *  @name DataTable#init.dt
+	 *  @event
+	 *  @param {event} e jQuery event object
+	 *  @param {object} oSettings DataTables settings object
+	 *  @param {object} json The JSON object request from the server - only
+	 *    present if client-side Ajax sourced data is used</li></ol>
+	 */
+
+	/**
+	 * State save event, fired when the table has changed state a new state save
+	 * is required. This event allows modification of the state saving object
+	 * prior to actually doing the save, including addition or other state
+	 * properties (for plug-ins) or modification of a DataTables core property.
+	 *  @name DataTable#stateSaveParams.dt
+	 *  @event
+	 *  @param {event} e jQuery event object
+	 *  @param {object} oSettings DataTables settings object
+	 *  @param {object} json The state information to be saved
+	 */
+
+	/**
+	 * State load event, fired when the table is loading state from the stored
+	 * data, but prior to the settings object being modified by the saved state
+	 * - allowing modification of the saved state is required or loading of
+	 * state for a plug-in.
+	 *  @name DataTable#stateLoadParams.dt
+	 *  @event
+	 *  @param {event} e jQuery event object
+	 *  @param {object} oSettings DataTables settings object
+	 *  @param {object} json The saved state information
+	 */
+
+	/**
+	 * State loaded event, fired when state has been loaded from stored data and
+	 * the settings object has been modified by the loaded data.
+	 *  @name DataTable#stateLoaded.dt
+	 *  @event
+	 *  @param {event} e jQuery event object
+	 *  @param {object} oSettings DataTables settings object
+	 *  @param {object} json The saved state information
+	 */
+
+	/**
+	 * Processing event, fired when DataTables is doing some kind of processing
+	 * (be it, order, searcg or anything else). It can be used to indicate to
+	 * the end user that there is something happening, or that something has
+	 * finished.
+	 *  @name DataTable#processing.dt
+	 *  @event
+	 *  @param {event} e jQuery event object
+	 *  @param {object} oSettings DataTables settings object
+	 *  @param {boolean} bShow Flag for if DataTables is doing processing or not
+	 */
+
+	/**
+	 * Ajax (XHR) event, fired whenever an Ajax request is completed from a
+	 * request to made to the server for new data. This event is called before
+	 * DataTables processed the returned data, so it can also be used to pre-
+	 * process the data returned from the server, if needed.
+	 *
+	 * Note that this trigger is called in `fnServerData`, if you override
+	 * `fnServerData` and which to use this event, you need to trigger it in you
+	 * success function.
+	 *  @name DataTable#xhr.dt
+	 *  @event
+	 *  @param {event} e jQuery event object
+	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+	 *  @param {object} json JSON returned from the server
+	 *
+	 *  @example
+	 *     // Use a custom property returned from the server in another DOM element
+	 *     $('#table').dataTable().on('xhr.dt', function (e, settings, json) {
+	 *       $('#status').html( json.status );
+	 *     } );
+	 *
+	 *  @example
+	 *     // Pre-process the data returned from the server
+	 *     $('#table').dataTable().on('xhr.dt', function (e, settings, json) {
+	 *       for ( var i=0, ien=json.aaData.length ; i<ien ; i++ ) {
+	 *         json.aaData[i].sum = json.aaData[i].one + json.aaData[i].two;
+	 *       }
+	 *       // Note no return - manipulate the data directly in the JSON object.
+	 *     } );
+	 */
+
+	/**
+	 * Destroy event, fired when the DataTable is destroyed by calling fnDestroy
+	 * or passing the bDestroy:true parameter in the initialisation object. This
+	 * can be used to remove bound events, added DOM nodes, etc.
+	 *  @name DataTable#destroy.dt
+	 *  @event
+	 *  @param {event} e jQuery event object
+	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+	 */
+
+	/**
+	 * Page length change event, fired when number of records to show on each
+	 * page (the length) is changed.
+	 *  @name DataTable#length.dt
+	 *  @event
+	 *  @param {event} e jQuery event object
+	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+	 *  @param {integer} len New length
+	 */
+
+	/**
+	 * Column sizing has changed.
+	 *  @name DataTable#column-sizing.dt
+	 *  @event
+	 *  @param {event} e jQuery event object
+	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+	 */
+
+	/**
+	 * Column visibility has changed.
+	 *  @name DataTable#column-visibility.dt
+	 *  @event
+	 *  @param {event} e jQuery event object
+	 *  @param {object} o DataTables settings object {@link DataTable.models.oSettings}
+	 *  @param {int} column Column index
+	 *  @param {bool} vis `false` if column now hidden, or `true` if visible
+	 */
+
+	return $.fn.dataTable;
+}));
diff --git a/src/legacy/design-studio/js/jquery.js b/src/legacy/design-studio/js/jquery.js
new file mode 100644
index 0000000000000000000000000000000000000000..eed17778c688271208406367c0c1681d81feca6f
--- /dev/null
+++ b/src/legacy/design-studio/js/jquery.js
@@ -0,0 +1,9210 @@
+/*!
+ * jQuery JavaScript Library v2.1.4
+ * http://jquery.com/
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ *
+ * Copyright 2005, 2014 jQuery Foundation, Inc. and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2015-04-28T16:01Z
+ */
+
+(function( global, factory ) {
+
+	if ( typeof module === "object" && typeof module.exports === "object" ) {
+		// For CommonJS and CommonJS-like environments where a proper `window`
+		// is present, execute the factory and get jQuery.
+		// For environments that do not have a `window` with a `document`
+		// (such as Node.js), expose a factory as module.exports.
+		// This accentuates the need for the creation of a real `window`.
+		// e.g. var jQuery = require("jquery")(window);
+		// See ticket #14549 for more info.
+		module.exports = global.document ?
+			factory( global, true ) :
+			function( w ) {
+				if ( !w.document ) {
+					throw new Error( "jQuery requires a window with a document" );
+				}
+				return factory( w );
+			};
+	} else {
+		factory( global );
+	}
+
+// Pass this if window is not defined yet
+}(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
+
+// Support: Firefox 18+
+// Can't be in strict mode, several libs including ASP.NET trace
+// the stack via arguments.caller.callee and Firefox dies if
+// you try to trace through "use strict" call chains. (#13335)
+//
+
+var arr = [];
+
+var slice = arr.slice;
+
+var concat = arr.concat;
+
+var push = arr.push;
+
+var indexOf = arr.indexOf;
+
+var class2type = {};
+
+var toString = class2type.toString;
+
+var hasOwn = class2type.hasOwnProperty;
+
+var support = {};
+
+
+
+var
+	// Use the correct document accordingly with window argument (sandbox)
+	document = window.document,
+
+	version = "2.1.4",
+
+	// Define a local copy of jQuery
+	jQuery = function( selector, context ) {
+		// The jQuery object is actually just the init constructor 'enhanced'
+		// Need init if jQuery is called (just allow error to be thrown if not included)
+		return new jQuery.fn.init( selector, context );
+	},
+
+	// Support: Android<4.1
+	// Make sure we trim BOM and NBSP
+	rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
+
+	// Matches dashed string for camelizing
+	rmsPrefix = /^-ms-/,
+	rdashAlpha = /-([\da-z])/gi,
+
+	// Used by jQuery.camelCase as callback to replace()
+	fcamelCase = function( all, letter ) {
+		return letter.toUpperCase();
+	};
+
+jQuery.fn = jQuery.prototype = {
+	// The current version of jQuery being used
+	jquery: version,
+
+	constructor: jQuery,
+
+	// Start with an empty selector
+	selector: "",
+
+	// The default length of a jQuery object is 0
+	length: 0,
+
+	toArray: function() {
+		return slice.call( this );
+	},
+
+	// Get the Nth element in the matched element set OR
+	// Get the whole matched element set as a clean array
+	get: function( num ) {
+		return num != null ?
+
+			// Return just the one element from the set
+			( num < 0 ? this[ num + this.length ] : this[ num ] ) :
+
+			// Return all the elements in a clean array
+			slice.call( this );
+	},
+
+	// Take an array of elements and push it onto the stack
+	// (returning the new matched element set)
+	pushStack: function( elems ) {
+
+		// Build a new jQuery matched element set
+		var ret = jQuery.merge( this.constructor(), elems );
+
+		// Add the old object onto the stack (as a reference)
+		ret.prevObject = this;
+		ret.context = this.context;
+
+		// Return the newly-formed element set
+		return ret;
+	},
+
+	// Execute a callback for every element in the matched set.
+	// (You can seed the arguments with an array of args, but this is
+	// only used internally.)
+	each: function( callback, args ) {
+		return jQuery.each( this, callback, args );
+	},
+
+	map: function( callback ) {
+		return this.pushStack( jQuery.map(this, function( elem, i ) {
+			return callback.call( elem, i, elem );
+		}));
+	},
+
+	slice: function() {
+		return this.pushStack( slice.apply( this, arguments ) );
+	},
+
+	first: function() {
+		return this.eq( 0 );
+	},
+
+	last: function() {
+		return this.eq( -1 );
+	},
+
+	eq: function( i ) {
+		var len = this.length,
+			j = +i + ( i < 0 ? len : 0 );
+		return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
+	},
+
+	end: function() {
+		return this.prevObject || this.constructor(null);
+	},
+
+	// For internal use only.
+	// Behaves like an Array's method, not like a jQuery method.
+	push: push,
+	sort: arr.sort,
+	splice: arr.splice
+};
+
+jQuery.extend = jQuery.fn.extend = function() {
+	var options, name, src, copy, copyIsArray, clone,
+		target = arguments[0] || {},
+		i = 1,
+		length = arguments.length,
+		deep = false;
+
+	// Handle a deep copy situation
+	if ( typeof target === "boolean" ) {
+		deep = target;
+
+		// Skip the boolean and the target
+		target = arguments[ i ] || {};
+		i++;
+	}
+
+	// Handle case when target is a string or something (possible in deep copy)
+	if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+		target = {};
+	}
+
+	// Extend jQuery itself if only one argument is passed
+	if ( i === length ) {
+		target = this;
+		i--;
+	}
+
+	for ( ; i < length; i++ ) {
+		// Only deal with non-null/undefined values
+		if ( (options = arguments[ i ]) != null ) {
+			// Extend the base object
+			for ( name in options ) {
+				src = target[ name ];
+				copy = options[ name ];
+
+				// Prevent never-ending loop
+				if ( target === copy ) {
+					continue;
+				}
+
+				// Recurse if we're merging plain objects or arrays
+				if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+					if ( copyIsArray ) {
+						copyIsArray = false;
+						clone = src && jQuery.isArray(src) ? src : [];
+
+					} else {
+						clone = src && jQuery.isPlainObject(src) ? src : {};
+					}
+
+					// Never move original objects, clone them
+					target[ name ] = jQuery.extend( deep, clone, copy );
+
+				// Don't bring in undefined values
+				} else if ( copy !== undefined ) {
+					target[ name ] = copy;
+				}
+			}
+		}
+	}
+
+	// Return the modified object
+	return target;
+};
+
+jQuery.extend({
+	// Unique for each copy of jQuery on the page
+	expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ),
+
+	// Assume jQuery is ready without the ready module
+	isReady: true,
+
+	error: function( msg ) {
+		throw new Error( msg );
+	},
+
+	noop: function() {},
+
+	isFunction: function( obj ) {
+		return jQuery.type(obj) === "function";
+	},
+
+	isArray: Array.isArray,
+
+	isWindow: function( obj ) {
+		return obj != null && obj === obj.window;
+	},
+
+	isNumeric: function( obj ) {
+		// parseFloat NaNs numeric-cast false positives (null|true|false|"")
+		// ...but misinterprets leading-number strings, particularly hex literals ("0x...")
+		// subtraction forces infinities to NaN
+		// adding 1 corrects loss of precision from parseFloat (#15100)
+		return !jQuery.isArray( obj ) && (obj - parseFloat( obj ) + 1) >= 0;
+	},
+
+	isPlainObject: function( obj ) {
+		// Not plain objects:
+		// - Any object or value whose internal [[Class]] property is not "[object Object]"
+		// - DOM nodes
+		// - window
+		if ( jQuery.type( obj ) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+			return false;
+		}
+
+		if ( obj.constructor &&
+				!hasOwn.call( obj.constructor.prototype, "isPrototypeOf" ) ) {
+			return false;
+		}
+
+		// If the function hasn't returned already, we're confident that
+		// |obj| is a plain object, created by {} or constructed with new Object
+		return true;
+	},
+
+	isEmptyObject: function( obj ) {
+		var name;
+		for ( name in obj ) {
+			return false;
+		}
+		return true;
+	},
+
+	type: function( obj ) {
+		if ( obj == null ) {
+			return obj + "";
+		}
+		// Support: Android<4.0, iOS<6 (functionish RegExp)
+		return typeof obj === "object" || typeof obj === "function" ?
+			class2type[ toString.call(obj) ] || "object" :
+			typeof obj;
+	},
+
+	// Evaluates a script in a global context
+	globalEval: function( code ) {
+		var script,
+			indirect = eval;
+
+		code = jQuery.trim( code );
+
+		if ( code ) {
+			// If the code includes a valid, prologue position
+			// strict mode pragma, execute code by injecting a
+			// script tag into the document.
+			if ( code.indexOf("use strict") === 1 ) {
+				script = document.createElement("script");
+				script.text = code;
+				document.head.appendChild( script ).parentNode.removeChild( script );
+			} else {
+			// Otherwise, avoid the DOM node creation, insertion
+			// and removal by using an indirect global eval
+				indirect( code );
+			}
+		}
+	},
+
+	// Convert dashed to camelCase; used by the css and data modules
+	// Support: IE9-11+
+	// Microsoft forgot to hump their vendor prefix (#9572)
+	camelCase: function( string ) {
+		return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+	},
+
+	nodeName: function( elem, name ) {
+		return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+	},
+
+	// args is for internal usage only
+	each: function( obj, callback, args ) {
+		var value,
+			i = 0,
+			length = obj.length,
+			isArray = isArraylike( obj );
+
+		if ( args ) {
+			if ( isArray ) {
+				for ( ; i < length; i++ ) {
+					value = callback.apply( obj[ i ], args );
+
+					if ( value === false ) {
+						break;
+					}
+				}
+			} else {
+				for ( i in obj ) {
+					value = callback.apply( obj[ i ], args );
+
+					if ( value === false ) {
+						break;
+					}
+				}
+			}
+
+		// A special, fast, case for the most common use of each
+		} else {
+			if ( isArray ) {
+				for ( ; i < length; i++ ) {
+					value = callback.call( obj[ i ], i, obj[ i ] );
+
+					if ( value === false ) {
+						break;
+					}
+				}
+			} else {
+				for ( i in obj ) {
+					value = callback.call( obj[ i ], i, obj[ i ] );
+
+					if ( value === false ) {
+						break;
+					}
+				}
+			}
+		}
+
+		return obj;
+	},
+
+	// Support: Android<4.1
+	trim: function( text ) {
+		return text == null ?
+			"" :
+			( text + "" ).replace( rtrim, "" );
+	},
+
+	// results is for internal usage only
+	makeArray: function( arr, results ) {
+		var ret = results || [];
+
+		if ( arr != null ) {
+			if ( isArraylike( Object(arr) ) ) {
+				jQuery.merge( ret,
+					typeof arr === "string" ?
+					[ arr ] : arr
+				);
+			} else {
+				push.call( ret, arr );
+			}
+		}
+
+		return ret;
+	},
+
+	inArray: function( elem, arr, i ) {
+		return arr == null ? -1 : indexOf.call( arr, elem, i );
+	},
+
+	merge: function( first, second ) {
+		var len = +second.length,
+			j = 0,
+			i = first.length;
+
+		for ( ; j < len; j++ ) {
+			first[ i++ ] = second[ j ];
+		}
+
+		first.length = i;
+
+		return first;
+	},
+
+	grep: function( elems, callback, invert ) {
+		var callbackInverse,
+			matches = [],
+			i = 0,
+			length = elems.length,
+			callbackExpect = !invert;
+
+		// Go through the array, only saving the items
+		// that pass the validator function
+		for ( ; i < length; i++ ) {
+			callbackInverse = !callback( elems[ i ], i );
+			if ( callbackInverse !== callbackExpect ) {
+				matches.push( elems[ i ] );
+			}
+		}
+
+		return matches;
+	},
+
+	// arg is for internal usage only
+	map: function( elems, callback, arg ) {
+		var value,
+			i = 0,
+			length = elems.length,
+			isArray = isArraylike( elems ),
+			ret = [];
+
+		// Go through the array, translating each of the items to their new values
+		if ( isArray ) {
+			for ( ; i < length; i++ ) {
+				value = callback( elems[ i ], i, arg );
+
+				if ( value != null ) {
+					ret.push( value );
+				}
+			}
+
+		// Go through every key on the object,
+		} else {
+			for ( i in elems ) {
+				value = callback( elems[ i ], i, arg );
+
+				if ( value != null ) {
+					ret.push( value );
+				}
+			}
+		}
+
+		// Flatten any nested arrays
+		return concat.apply( [], ret );
+	},
+
+	// A global GUID counter for objects
+	guid: 1,
+
+	// Bind a function to a context, optionally partially applying any
+	// arguments.
+	proxy: function( fn, context ) {
+		var tmp, args, proxy;
+
+		if ( typeof context === "string" ) {
+			tmp = fn[ context ];
+			context = fn;
+			fn = tmp;
+		}
+
+		// Quick check to determine if target is callable, in the spec
+		// this throws a TypeError, but we will just return undefined.
+		if ( !jQuery.isFunction( fn ) ) {
+			return undefined;
+		}
+
+		// Simulated bind
+		args = slice.call( arguments, 2 );
+		proxy = function() {
+			return fn.apply( context || this, args.concat( slice.call( arguments ) ) );
+		};
+
+		// Set the guid of unique handler to the same of original handler, so it can be removed
+		proxy.guid = fn.guid = fn.guid || jQuery.guid++;
+
+		return proxy;
+	},
+
+	now: Date.now,
+
+	// jQuery.support is not used in Core but other projects attach their
+	// properties to it so it needs to exist.
+	support: support
+});
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
+	class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+function isArraylike( obj ) {
+
+	// Support: iOS 8.2 (not reproducible in simulator)
+	// `in` check used to prevent JIT error (gh-2145)
+	// hasOwn isn't used here due to false negatives
+	// regarding Nodelist length in IE
+	var length = "length" in obj && obj.length,
+		type = jQuery.type( obj );
+
+	if ( type === "function" || jQuery.isWindow( obj ) ) {
+		return false;
+	}
+
+	if ( obj.nodeType === 1 && length ) {
+		return true;
+	}
+
+	return type === "array" || length === 0 ||
+		typeof length === "number" && length > 0 && ( length - 1 ) in obj;
+}
+var Sizzle =
+/*!
+ * Sizzle CSS Selector Engine v2.2.0-pre
+ * http://sizzlejs.com/
+ *
+ * Copyright 2008, 2014 jQuery Foundation, Inc. and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: 2014-12-16
+ */
+(function( window ) {
+
+var i,
+	support,
+	Expr,
+	getText,
+	isXML,
+	tokenize,
+	compile,
+	select,
+	outermostContext,
+	sortInput,
+	hasDuplicate,
+
+	// Local document vars
+	setDocument,
+	document,
+	docElem,
+	documentIsHTML,
+	rbuggyQSA,
+	rbuggyMatches,
+	matches,
+	contains,
+
+	// Instance-specific data
+	expando = "sizzle" + 1 * new Date(),
+	preferredDoc = window.document,
+	dirruns = 0,
+	done = 0,
+	classCache = createCache(),
+	tokenCache = createCache(),
+	compilerCache = createCache(),
+	sortOrder = function( a, b ) {
+		if ( a === b ) {
+			hasDuplicate = true;
+		}
+		return 0;
+	},
+
+	// General-purpose constants
+	MAX_NEGATIVE = 1 << 31,
+
+	// Instance methods
+	hasOwn = ({}).hasOwnProperty,
+	arr = [],
+	pop = arr.pop,
+	push_native = arr.push,
+	push = arr.push,
+	slice = arr.slice,
+	// Use a stripped-down indexOf as it's faster than native
+	// http://jsperf.com/thor-indexof-vs-for/5
+	indexOf = function( list, elem ) {
+		var i = 0,
+			len = list.length;
+		for ( ; i < len; i++ ) {
+			if ( list[i] === elem ) {
+				return i;
+			}
+		}
+		return -1;
+	},
+
+	booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
+
+	// Regular expressions
+
+	// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
+	whitespace = "[\\x20\\t\\r\\n\\f]",
+	// http://www.w3.org/TR/css3-syntax/#characters
+	characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
+
+	// Loosely modeled on CSS identifier characters
+	// An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
+	// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+	identifier = characterEncoding.replace( "w", "w#" ),
+
+	// Attribute selectors: http://www.w3.org/TR/selectors/#attribute-selectors
+	attributes = "\\[" + whitespace + "*(" + characterEncoding + ")(?:" + whitespace +
+		// Operator (capture 2)
+		"*([*^$|!~]?=)" + whitespace +
+		// "Attribute values must be CSS identifiers [capture 5] or strings [capture 3 or capture 4]"
+		"*(?:'((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\"|(" + identifier + "))|)" + whitespace +
+		"*\\]",
+
+	pseudos = ":(" + characterEncoding + ")(?:\\((" +
+		// To reduce the number of selectors needing tokenize in the preFilter, prefer arguments:
+		// 1. quoted (capture 3; capture 4 or capture 5)
+		"('((?:\\\\.|[^\\\\'])*)'|\"((?:\\\\.|[^\\\\\"])*)\")|" +
+		// 2. simple (capture 6)
+		"((?:\\\\.|[^\\\\()[\\]]|" + attributes + ")*)|" +
+		// 3. anything else (capture 2)
+		".*" +
+		")\\)|)",
+
+	// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+	rwhitespace = new RegExp( whitespace + "+", "g" ),
+	rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
+
+	rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+	rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
+
+	rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*?)" + whitespace + "*\\]", "g" ),
+
+	rpseudo = new RegExp( pseudos ),
+	ridentifier = new RegExp( "^" + identifier + "$" ),
+
+	matchExpr = {
+		"ID": new RegExp( "^#(" + characterEncoding + ")" ),
+		"CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
+		"TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
+		"ATTR": new RegExp( "^" + attributes ),
+		"PSEUDO": new RegExp( "^" + pseudos ),
+		"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
+			"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
+			"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+		"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
+		// For use in libraries implementing .is()
+		// We use this for POS matching in `select`
+		"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
+			whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
+	},
+
+	rinputs = /^(?:input|select|textarea|button)$/i,
+	rheader = /^h\d$/i,
+
+	rnative = /^[^{]+\{\s*\[native \w/,
+
+	// Easily-parseable/retrievable ID or TAG or CLASS selectors
+	rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
+
+	rsibling = /[+~]/,
+	rescape = /'|\\/g,
+
+	// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
+	runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
+	funescape = function( _, escaped, escapedWhitespace ) {
+		var high = "0x" + escaped - 0x10000;
+		// NaN means non-codepoint
+		// Support: Firefox<24
+		// Workaround erroneous numeric interpretation of +"0x"
+		return high !== high || escapedWhitespace ?
+			escaped :
+			high < 0 ?
+				// BMP codepoint
+				String.fromCharCode( high + 0x10000 ) :
+				// Supplemental Plane codepoint (surrogate pair)
+				String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
+	},
+
+	// Used for iframes
+	// See setDocument()
+	// Removing the function wrapper causes a "Permission Denied"
+	// error in IE
+	unloadHandler = function() {
+		setDocument();
+	};
+
+// Optimize for push.apply( _, NodeList )
+try {
+	push.apply(
+		(arr = slice.call( preferredDoc.childNodes )),
+		preferredDoc.childNodes
+	);
+	// Support: Android<4.0
+	// Detect silently failing push.apply
+	arr[ preferredDoc.childNodes.length ].nodeType;
+} catch ( e ) {
+	push = { apply: arr.length ?
+
+		// Leverage slice if possible
+		function( target, els ) {
+			push_native.apply( target, slice.call(els) );
+		} :
+
+		// Support: IE<9
+		// Otherwise append directly
+		function( target, els ) {
+			var j = target.length,
+				i = 0;
+			// Can't trust NodeList.length
+			while ( (target[j++] = els[i++]) ) {}
+			target.length = j - 1;
+		}
+	};
+}
+
+function Sizzle( selector, context, results, seed ) {
+	var match, elem, m, nodeType,
+		// QSA vars
+		i, groups, old, nid, newContext, newSelector;
+
+	if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
+		setDocument( context );
+	}
+
+	context = context || document;
+	results = results || [];
+	nodeType = context.nodeType;
+
+	if ( typeof selector !== "string" || !selector ||
+		nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
+
+		return results;
+	}
+
+	if ( !seed && documentIsHTML ) {
+
+		// Try to shortcut find operations when possible (e.g., not under DocumentFragment)
+		if ( nodeType !== 11 && (match = rquickExpr.exec( selector )) ) {
+			// Speed-up: Sizzle("#ID")
+			if ( (m = match[1]) ) {
+				if ( nodeType === 9 ) {
+					elem = context.getElementById( m );
+					// Check parentNode to catch when Blackberry 4.6 returns
+					// nodes that are no longer in the document (jQuery #6963)
+					if ( elem && elem.parentNode ) {
+						// Handle the case where IE, Opera, and Webkit return items
+						// by name instead of ID
+						if ( elem.id === m ) {
+							results.push( elem );
+							return results;
+						}
+					} else {
+						return results;
+					}
+				} else {
+					// Context is not a document
+					if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
+						contains( context, elem ) && elem.id === m ) {
+						results.push( elem );
+						return results;
+					}
+				}
+
+			// Speed-up: Sizzle("TAG")
+			} else if ( match[2] ) {
+				push.apply( results, context.getElementsByTagName( selector ) );
+				return results;
+
+			// Speed-up: Sizzle(".CLASS")
+			} else if ( (m = match[3]) && support.getElementsByClassName ) {
+				push.apply( results, context.getElementsByClassName( m ) );
+				return results;
+			}
+		}
+
+		// QSA path
+		if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
+			nid = old = expando;
+			newContext = context;
+			newSelector = nodeType !== 1 && selector;
+
+			// qSA works strangely on Element-rooted queries
+			// We can work around this by specifying an extra ID on the root
+			// and working up from there (Thanks to Andrew Dupont for the technique)
+			// IE 8 doesn't work on object elements
+			if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+				groups = tokenize( selector );
+
+				if ( (old = context.getAttribute("id")) ) {
+					nid = old.replace( rescape, "\\$&" );
+				} else {
+					context.setAttribute( "id", nid );
+				}
+				nid = "[id='" + nid + "'] ";
+
+				i = groups.length;
+				while ( i-- ) {
+					groups[i] = nid + toSelector( groups[i] );
+				}
+				newContext = rsibling.test( selector ) && testContext( context.parentNode ) || context;
+				newSelector = groups.join(",");
+			}
+
+			if ( newSelector ) {
+				try {
+					push.apply( results,
+						newContext.querySelectorAll( newSelector )
+					);
+					return results;
+				} catch(qsaError) {
+				} finally {
+					if ( !old ) {
+						context.removeAttribute("id");
+					}
+				}
+			}
+		}
+	}
+
+	// All others
+	return select( selector.replace( rtrim, "$1" ), context, results, seed );
+}
+
+/**
+ * Create key-value caches of limited size
+ * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
+ *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
+ *	deleting the oldest entry
+ */
+function createCache() {
+	var keys = [];
+
+	function cache( key, value ) {
+		// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
+		if ( keys.push( key + " " ) > Expr.cacheLength ) {
+			// Only keep the most recent entries
+			delete cache[ keys.shift() ];
+		}
+		return (cache[ key + " " ] = value);
+	}
+	return cache;
+}
+
+/**
+ * Mark a function for special use by Sizzle
+ * @param {Function} fn The function to mark
+ */
+function markFunction( fn ) {
+	fn[ expando ] = true;
+	return fn;
+}
+
+/**
+ * Support testing using an element
+ * @param {Function} fn Passed the created div and expects a boolean result
+ */
+function assert( fn ) {
+	var div = document.createElement("div");
+
+	try {
+		return !!fn( div );
+	} catch (e) {
+		return false;
+	} finally {
+		// Remove from its parent by default
+		if ( div.parentNode ) {
+			div.parentNode.removeChild( div );
+		}
+		// release memory in IE
+		div = null;
+	}
+}
+
+/**
+ * Adds the same handler for all of the specified attrs
+ * @param {String} attrs Pipe-separated list of attributes
+ * @param {Function} handler The method that will be applied
+ */
+function addHandle( attrs, handler ) {
+	var arr = attrs.split("|"),
+		i = attrs.length;
+
+	while ( i-- ) {
+		Expr.attrHandle[ arr[i] ] = handler;
+	}
+}
+
+/**
+ * Checks document order of two siblings
+ * @param {Element} a
+ * @param {Element} b
+ * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
+ */
+function siblingCheck( a, b ) {
+	var cur = b && a,
+		diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
+			( ~b.sourceIndex || MAX_NEGATIVE ) -
+			( ~a.sourceIndex || MAX_NEGATIVE );
+
+	// Use IE sourceIndex if available on both nodes
+	if ( diff ) {
+		return diff;
+	}
+
+	// Check if b follows a
+	if ( cur ) {
+		while ( (cur = cur.nextSibling) ) {
+			if ( cur === b ) {
+				return -1;
+			}
+		}
+	}
+
+	return a ? 1 : -1;
+}
+
+/**
+ * Returns a function to use in pseudos for input types
+ * @param {String} type
+ */
+function createInputPseudo( type ) {
+	return function( elem ) {
+		var name = elem.nodeName.toLowerCase();
+		return name === "input" && elem.type === type;
+	};
+}
+
+/**
+ * Returns a function to use in pseudos for buttons
+ * @param {String} type
+ */
+function createButtonPseudo( type ) {
+	return function( elem ) {
+		var name = elem.nodeName.toLowerCase();
+		return (name === "input" || name === "button") && elem.type === type;
+	};
+}
+
+/**
+ * Returns a function to use in pseudos for positionals
+ * @param {Function} fn
+ */
+function createPositionalPseudo( fn ) {
+	return markFunction(function( argument ) {
+		argument = +argument;
+		return markFunction(function( seed, matches ) {
+			var j,
+				matchIndexes = fn( [], seed.length, argument ),
+				i = matchIndexes.length;
+
+			// Match elements found at the specified indexes
+			while ( i-- ) {
+				if ( seed[ (j = matchIndexes[i]) ] ) {
+					seed[j] = !(matches[j] = seed[j]);
+				}
+			}
+		});
+	});
+}
+
+/**
+ * Checks a node for validity as a Sizzle context
+ * @param {Element|Object=} context
+ * @returns {Element|Object|Boolean} The input node if acceptable, otherwise a falsy value
+ */
+function testContext( context ) {
+	return context && typeof context.getElementsByTagName !== "undefined" && context;
+}
+
+// Expose support vars for convenience
+support = Sizzle.support = {};
+
+/**
+ * Detects XML nodes
+ * @param {Element|Object} elem An element or a document
+ * @returns {Boolean} True iff elem is a non-HTML XML node
+ */
+isXML = Sizzle.isXML = function( elem ) {
+	// documentElement is verified for cases where it doesn't yet exist
+	// (such as loading iframes in IE - #4833)
+	var documentElement = elem && (elem.ownerDocument || elem).documentElement;
+	return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+/**
+ * Sets document-related variables once based on the current document
+ * @param {Element|Object} [doc] An element or document object to use to set the document
+ * @returns {Object} Returns the current document
+ */
+setDocument = Sizzle.setDocument = function( node ) {
+	var hasCompare, parent,
+		doc = node ? node.ownerDocument || node : preferredDoc;
+
+	// If no document and documentElement is available, return
+	if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
+		return document;
+	}
+
+	// Set our document
+	document = doc;
+	docElem = doc.documentElement;
+	parent = doc.defaultView;
+
+	// Support: IE>8
+	// If iframe document is assigned to "document" variable and if iframe has been reloaded,
+	// IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
+	// IE6-8 do not support the defaultView property so parent will be undefined
+	if ( parent && parent !== parent.top ) {
+		// IE11 does not have attachEvent, so all must suffer
+		if ( parent.addEventListener ) {
+			parent.addEventListener( "unload", unloadHandler, false );
+		} else if ( parent.attachEvent ) {
+			parent.attachEvent( "onunload", unloadHandler );
+		}
+	}
+
+	/* Support tests
+	---------------------------------------------------------------------- */
+	documentIsHTML = !isXML( doc );
+
+	/* Attributes
+	---------------------------------------------------------------------- */
+
+	// Support: IE<8
+	// Verify that getAttribute really returns attributes and not properties
+	// (excepting IE8 booleans)
+	support.attributes = assert(function( div ) {
+		div.className = "i";
+		return !div.getAttribute("className");
+	});
+
+	/* getElement(s)By*
+	---------------------------------------------------------------------- */
+
+	// Check if getElementsByTagName("*") returns only elements
+	support.getElementsByTagName = assert(function( div ) {
+		div.appendChild( doc.createComment("") );
+		return !div.getElementsByTagName("*").length;
+	});
+
+	// Support: IE<9
+	support.getElementsByClassName = rnative.test( doc.getElementsByClassName );
+
+	// Support: IE<10
+	// Check if getElementById returns elements by name
+	// The broken getElementById methods don't pick up programatically-set names,
+	// so use a roundabout getElementsByName test
+	support.getById = assert(function( div ) {
+		docElem.appendChild( div ).id = expando;
+		return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
+	});
+
+	// ID find and filter
+	if ( support.getById ) {
+		Expr.find["ID"] = function( id, context ) {
+			if ( typeof context.getElementById !== "undefined" && documentIsHTML ) {
+				var m = context.getElementById( id );
+				// Check parentNode to catch when Blackberry 4.6 returns
+				// nodes that are no longer in the document #6963
+				return m && m.parentNode ? [ m ] : [];
+			}
+		};
+		Expr.filter["ID"] = function( id ) {
+			var attrId = id.replace( runescape, funescape );
+			return function( elem ) {
+				return elem.getAttribute("id") === attrId;
+			};
+		};
+	} else {
+		// Support: IE6/7
+		// getElementById is not reliable as a find shortcut
+		delete Expr.find["ID"];
+
+		Expr.filter["ID"] =  function( id ) {
+			var attrId = id.replace( runescape, funescape );
+			return function( elem ) {
+				var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+				return node && node.value === attrId;
+			};
+		};
+	}
+
+	// Tag
+	Expr.find["TAG"] = support.getElementsByTagName ?
+		function( tag, context ) {
+			if ( typeof context.getElementsByTagName !== "undefined" ) {
+				return context.getElementsByTagName( tag );
+
+			// DocumentFragment nodes don't have gEBTN
+			} else if ( support.qsa ) {
+				return context.querySelectorAll( tag );
+			}
+		} :
+
+		function( tag, context ) {
+			var elem,
+				tmp = [],
+				i = 0,
+				// By happy coincidence, a (broken) gEBTN appears on DocumentFragment nodes too
+				results = context.getElementsByTagName( tag );
+
+			// Filter out possible comments
+			if ( tag === "*" ) {
+				while ( (elem = results[i++]) ) {
+					if ( elem.nodeType === 1 ) {
+						tmp.push( elem );
+					}
+				}
+
+				return tmp;
+			}
+			return results;
+		};
+
+	// Class
+	Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
+		if ( documentIsHTML ) {
+			return context.getElementsByClassName( className );
+		}
+	};
+
+	/* QSA/matchesSelector
+	---------------------------------------------------------------------- */
+
+	// QSA and matchesSelector support
+
+	// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+	rbuggyMatches = [];
+
+	// qSa(:focus) reports false when true (Chrome 21)
+	// We allow this because of a bug in IE8/9 that throws an error
+	// whenever `document.activeElement` is accessed on an iframe
+	// So, we allow :focus to pass through QSA all the time to avoid the IE error
+	// See http://bugs.jquery.com/ticket/13378
+	rbuggyQSA = [];
+
+	if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {
+		// Build QSA regex
+		// Regex strategy adopted from Diego Perini
+		assert(function( div ) {
+			// Select is set to empty string on purpose
+			// This is to test IE's treatment of not explicitly
+			// setting a boolean content attribute,
+			// since its presence should be enough
+			// http://bugs.jquery.com/ticket/12359
+			docElem.appendChild( div ).innerHTML = "<a id='" + expando + "'></a>" +
+				"<select id='" + expando + "-\f]' msallowcapture=''>" +
+				"<option selected=''></option></select>";
+
+			// Support: IE8, Opera 11-12.16
+			// Nothing should be selected when empty strings follow ^= or $= or *=
+			// The test attribute must be unknown in Opera but "safe" for WinRT
+			// http://msdn.microsoft.com/en-us/library/ie/hh465388.aspx#attribute_section
+			if ( div.querySelectorAll("[msallowcapture^='']").length ) {
+				rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
+			}
+
+			// Support: IE8
+			// Boolean attributes and "value" are not treated correctly
+			if ( !div.querySelectorAll("[selected]").length ) {
+				rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
+			}
+
+			// Support: Chrome<29, Android<4.2+, Safari<7.0+, iOS<7.0+, PhantomJS<1.9.7+
+			if ( !div.querySelectorAll( "[id~=" + expando + "-]" ).length ) {
+				rbuggyQSA.push("~=");
+			}
+
+			// Webkit/Opera - :checked should return selected option elements
+			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+			// IE8 throws error here and will not see later tests
+			if ( !div.querySelectorAll(":checked").length ) {
+				rbuggyQSA.push(":checked");
+			}
+
+			// Support: Safari 8+, iOS 8+
+			// https://bugs.webkit.org/show_bug.cgi?id=136851
+			// In-page `selector#id sibing-combinator selector` fails
+			if ( !div.querySelectorAll( "a#" + expando + "+*" ).length ) {
+				rbuggyQSA.push(".#.+[+~]");
+			}
+		});
+
+		assert(function( div ) {
+			// Support: Windows 8 Native Apps
+			// The type and name attributes are restricted during .innerHTML assignment
+			var input = doc.createElement("input");
+			input.setAttribute( "type", "hidden" );
+			div.appendChild( input ).setAttribute( "name", "D" );
+
+			// Support: IE8
+			// Enforce case-sensitivity of name attribute
+			if ( div.querySelectorAll("[name=d]").length ) {
+				rbuggyQSA.push( "name" + whitespace + "*[*^$|!~]?=" );
+			}
+
+			// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+			// IE8 throws error here and will not see later tests
+			if ( !div.querySelectorAll(":enabled").length ) {
+				rbuggyQSA.push( ":enabled", ":disabled" );
+			}
+
+			// Opera 10-11 does not throw on post-comma invalid pseudos
+			div.querySelectorAll("*,:x");
+			rbuggyQSA.push(",.*:");
+		});
+	}
+
+	if ( (support.matchesSelector = rnative.test( (matches = docElem.matches ||
+		docElem.webkitMatchesSelector ||
+		docElem.mozMatchesSelector ||
+		docElem.oMatchesSelector ||
+		docElem.msMatchesSelector) )) ) {
+
+		assert(function( div ) {
+			// Check to see if it's possible to do matchesSelector
+			// on a disconnected node (IE 9)
+			support.disconnectedMatch = matches.call( div, "div" );
+
+			// This should fail with an exception
+			// Gecko does not error, returns false instead
+			matches.call( div, "[s!='']:x" );
+			rbuggyMatches.push( "!=", pseudos );
+		});
+	}
+
+	rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
+	rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
+
+	/* Contains
+	---------------------------------------------------------------------- */
+	hasCompare = rnative.test( docElem.compareDocumentPosition );
+
+	// Element contains another
+	// Purposefully does not implement inclusive descendent
+	// As in, an element does not contain itself
+	contains = hasCompare || rnative.test( docElem.contains ) ?
+		function( a, b ) {
+			var adown = a.nodeType === 9 ? a.documentElement : a,
+				bup = b && b.parentNode;
+			return a === bup || !!( bup && bup.nodeType === 1 && (
+				adown.contains ?
+					adown.contains( bup ) :
+					a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
+			));
+		} :
+		function( a, b ) {
+			if ( b ) {
+				while ( (b = b.parentNode) ) {
+					if ( b === a ) {
+						return true;
+					}
+				}
+			}
+			return false;
+		};
+
+	/* Sorting
+	---------------------------------------------------------------------- */
+
+	// Document order sorting
+	sortOrder = hasCompare ?
+	function( a, b ) {
+
+		// Flag for duplicate removal
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+		}
+
+		// Sort on method existence if only one input has compareDocumentPosition
+		var compare = !a.compareDocumentPosition - !b.compareDocumentPosition;
+		if ( compare ) {
+			return compare;
+		}
+
+		// Calculate position if both inputs belong to the same document
+		compare = ( a.ownerDocument || a ) === ( b.ownerDocument || b ) ?
+			a.compareDocumentPosition( b ) :
+
+			// Otherwise we know they are disconnected
+			1;
+
+		// Disconnected nodes
+		if ( compare & 1 ||
+			(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
+
+			// Choose the first element that is related to our preferred document
+			if ( a === doc || a.ownerDocument === preferredDoc && contains(preferredDoc, a) ) {
+				return -1;
+			}
+			if ( b === doc || b.ownerDocument === preferredDoc && contains(preferredDoc, b) ) {
+				return 1;
+			}
+
+			// Maintain original order
+			return sortInput ?
+				( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+				0;
+		}
+
+		return compare & 4 ? -1 : 1;
+	} :
+	function( a, b ) {
+		// Exit early if the nodes are identical
+		if ( a === b ) {
+			hasDuplicate = true;
+			return 0;
+		}
+
+		var cur,
+			i = 0,
+			aup = a.parentNode,
+			bup = b.parentNode,
+			ap = [ a ],
+			bp = [ b ];
+
+		// Parentless nodes are either documents or disconnected
+		if ( !aup || !bup ) {
+			return a === doc ? -1 :
+				b === doc ? 1 :
+				aup ? -1 :
+				bup ? 1 :
+				sortInput ?
+				( indexOf( sortInput, a ) - indexOf( sortInput, b ) ) :
+				0;
+
+		// If the nodes are siblings, we can do a quick check
+		} else if ( aup === bup ) {
+			return siblingCheck( a, b );
+		}
+
+		// Otherwise we need full lists of their ancestors for comparison
+		cur = a;
+		while ( (cur = cur.parentNode) ) {
+			ap.unshift( cur );
+		}
+		cur = b;
+		while ( (cur = cur.parentNode) ) {
+			bp.unshift( cur );
+		}
+
+		// Walk down the tree looking for a discrepancy
+		while ( ap[i] === bp[i] ) {
+			i++;
+		}
+
+		return i ?
+			// Do a sibling check if the nodes have a common ancestor
+			siblingCheck( ap[i], bp[i] ) :
+
+			// Otherwise nodes in our document sort first
+			ap[i] === preferredDoc ? -1 :
+			bp[i] === preferredDoc ? 1 :
+			0;
+	};
+
+	return doc;
+};
+
+Sizzle.matches = function( expr, elements ) {
+	return Sizzle( expr, null, null, elements );
+};
+
+Sizzle.matchesSelector = function( elem, expr ) {
+	// Set document vars if needed
+	if ( ( elem.ownerDocument || elem ) !== document ) {
+		setDocument( elem );
+	}
+
+	// Make sure that attribute selectors are quoted
+	expr = expr.replace( rattributeQuotes, "='$1']" );
+
+	if ( support.matchesSelector && documentIsHTML &&
+		( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
+		( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {
+
+		try {
+			var ret = matches.call( elem, expr );
+
+			// IE 9's matchesSelector returns false on disconnected nodes
+			if ( ret || support.disconnectedMatch ||
+					// As well, disconnected nodes are said to be in a document
+					// fragment in IE 9
+					elem.document && elem.document.nodeType !== 11 ) {
+				return ret;
+			}
+		} catch (e) {}
+	}
+
+	return Sizzle( expr, document, null, [ elem ] ).length > 0;
+};
+
+Sizzle.contains = function( context, elem ) {
+	// Set document vars if needed
+	if ( ( context.ownerDocument || context ) !== document ) {
+		setDocument( context );
+	}
+	return contains( context, elem );
+};
+
+Sizzle.attr = function( elem, name ) {
+	// Set document vars if needed
+	if ( ( elem.ownerDocument || elem ) !== document ) {
+		setDocument( elem );
+	}
+
+	var fn = Expr.attrHandle[ name.toLowerCase() ],
+		// Don't get fooled by Object.prototype properties (jQuery #13807)
+		val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
+			fn( elem, name, !documentIsHTML ) :
+			undefined;
+
+	return val !== undefined ?
+		val :
+		support.attributes || !documentIsHTML ?
+			elem.getAttribute( name ) :
+			(val = elem.getAttributeNode(name)) && val.specified ?
+				val.value :
+				null;
+};
+
+Sizzle.error = function( msg ) {
+	throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+/**
+ * Document sorting and removing duplicates
+ * @param {ArrayLike} results
+ */
+Sizzle.uniqueSort = function( results ) {
+	var elem,
+		duplicates = [],
+		j = 0,
+		i = 0;
+
+	// Unless we *know* we can detect duplicates, assume their presence
+	hasDuplicate = !support.detectDuplicates;
+	sortInput = !support.sortStable && results.slice( 0 );
+	results.sort( sortOrder );
+
+	if ( hasDuplicate ) {
+		while ( (elem = results[i++]) ) {
+			if ( elem === results[ i ] ) {
+				j = duplicates.push( i );
+			}
+		}
+		while ( j-- ) {
+			results.splice( duplicates[ j ], 1 );
+		}
+	}
+
+	// Clear input after sorting to release objects
+	// See https://github.com/jquery/sizzle/pull/225
+	sortInput = null;
+
+	return results;
+};
+
+/**
+ * Utility function for retrieving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+getText = Sizzle.getText = function( elem ) {
+	var node,
+		ret = "",
+		i = 0,
+		nodeType = elem.nodeType;
+
+	if ( !nodeType ) {
+		// If no nodeType, this is expected to be an array
+		while ( (node = elem[i++]) ) {
+			// Do not traverse comment nodes
+			ret += getText( node );
+		}
+	} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+		// Use textContent for elements
+		// innerText usage removed for consistency of new lines (jQuery #11153)
+		if ( typeof elem.textContent === "string" ) {
+			return elem.textContent;
+		} else {
+			// Traverse its children
+			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+				ret += getText( elem );
+			}
+		}
+	} else if ( nodeType === 3 || nodeType === 4 ) {
+		return elem.nodeValue;
+	}
+	// Do not include comment or processing instruction nodes
+
+	return ret;
+};
+
+Expr = Sizzle.selectors = {
+
+	// Can be adjusted by the user
+	cacheLength: 50,
+
+	createPseudo: markFunction,
+
+	match: matchExpr,
+
+	attrHandle: {},
+
+	find: {},
+
+	relative: {
+		">": { dir: "parentNode", first: true },
+		" ": { dir: "parentNode" },
+		"+": { dir: "previousSibling", first: true },
+		"~": { dir: "previousSibling" }
+	},
+
+	preFilter: {
+		"ATTR": function( match ) {
+			match[1] = match[1].replace( runescape, funescape );
+
+			// Move the given value to match[3] whether quoted or unquoted
+			match[3] = ( match[3] || match[4] || match[5] || "" ).replace( runescape, funescape );
+
+			if ( match[2] === "~=" ) {
+				match[3] = " " + match[3] + " ";
+			}
+
+			return match.slice( 0, 4 );
+		},
+
+		"CHILD": function( match ) {
+			/* matches from matchExpr["CHILD"]
+				1 type (only|nth|...)
+				2 what (child|of-type)
+				3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+				4 xn-component of xn+y argument ([+-]?\d*n|)
+				5 sign of xn-component
+				6 x of xn-component
+				7 sign of y-component
+				8 y of y-component
+			*/
+			match[1] = match[1].toLowerCase();
+
+			if ( match[1].slice( 0, 3 ) === "nth" ) {
+				// nth-* requires argument
+				if ( !match[3] ) {
+					Sizzle.error( match[0] );
+				}
+
+				// numeric x and y parameters for Expr.filter.CHILD
+				// remember that false/true cast respectively to 0/1
+				match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
+				match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
+
+			// other types prohibit arguments
+			} else if ( match[3] ) {
+				Sizzle.error( match[0] );
+			}
+
+			return match;
+		},
+
+		"PSEUDO": function( match ) {
+			var excess,
+				unquoted = !match[6] && match[2];
+
+			if ( matchExpr["CHILD"].test( match[0] ) ) {
+				return null;
+			}
+
+			// Accept quoted arguments as-is
+			if ( match[3] ) {
+				match[2] = match[4] || match[5] || "";
+
+			// Strip excess characters from unquoted arguments
+			} else if ( unquoted && rpseudo.test( unquoted ) &&
+				// Get excess from tokenize (recursively)
+				(excess = tokenize( unquoted, true )) &&
+				// advance to the next closing parenthesis
+				(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
+
+				// excess is a negative index
+				match[0] = match[0].slice( 0, excess );
+				match[2] = unquoted.slice( 0, excess );
+			}
+
+			// Return only captures needed by the pseudo filter method (type and argument)
+			return match.slice( 0, 3 );
+		}
+	},
+
+	filter: {
+
+		"TAG": function( nodeNameSelector ) {
+			var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
+			return nodeNameSelector === "*" ?
+				function() { return true; } :
+				function( elem ) {
+					return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+				};
+		},
+
+		"CLASS": function( className ) {
+			var pattern = classCache[ className + " " ];
+
+			return pattern ||
+				(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
+				classCache( className, function( elem ) {
+					return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== "undefined" && elem.getAttribute("class") || "" );
+				});
+		},
+
+		"ATTR": function( name, operator, check ) {
+			return function( elem ) {
+				var result = Sizzle.attr( elem, name );
+
+				if ( result == null ) {
+					return operator === "!=";
+				}
+				if ( !operator ) {
+					return true;
+				}
+
+				result += "";
+
+				return operator === "=" ? result === check :
+					operator === "!=" ? result !== check :
+					operator === "^=" ? check && result.indexOf( check ) === 0 :
+					operator === "*=" ? check && result.indexOf( check ) > -1 :
+					operator === "$=" ? check && result.slice( -check.length ) === check :
+					operator === "~=" ? ( " " + result.replace( rwhitespace, " " ) + " " ).indexOf( check ) > -1 :
+					operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
+					false;
+			};
+		},
+
+		"CHILD": function( type, what, argument, first, last ) {
+			var simple = type.slice( 0, 3 ) !== "nth",
+				forward = type.slice( -4 ) !== "last",
+				ofType = what === "of-type";
+
+			return first === 1 && last === 0 ?
+
+				// Shortcut for :nth-*(n)
+				function( elem ) {
+					return !!elem.parentNode;
+				} :
+
+				function( elem, context, xml ) {
+					var cache, outerCache, node, diff, nodeIndex, start,
+						dir = simple !== forward ? "nextSibling" : "previousSibling",
+						parent = elem.parentNode,
+						name = ofType && elem.nodeName.toLowerCase(),
+						useCache = !xml && !ofType;
+
+					if ( parent ) {
+
+						// :(first|last|only)-(child|of-type)
+						if ( simple ) {
+							while ( dir ) {
+								node = elem;
+								while ( (node = node[ dir ]) ) {
+									if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
+										return false;
+									}
+								}
+								// Reverse direction for :only-* (if we haven't yet done so)
+								start = dir = type === "only" && !start && "nextSibling";
+							}
+							return true;
+						}
+
+						start = [ forward ? parent.firstChild : parent.lastChild ];
+
+						// non-xml :nth-child(...) stores cache data on `parent`
+						if ( forward && useCache ) {
+							// Seek `elem` from a previously-cached index
+							outerCache = parent[ expando ] || (parent[ expando ] = {});
+							cache = outerCache[ type ] || [];
+							nodeIndex = cache[0] === dirruns && cache[1];
+							diff = cache[0] === dirruns && cache[2];
+							node = nodeIndex && parent.childNodes[ nodeIndex ];
+
+							while ( (node = ++nodeIndex && node && node[ dir ] ||
+
+								// Fallback to seeking `elem` from the start
+								(diff = nodeIndex = 0) || start.pop()) ) {
+
+								// When found, cache indexes on `parent` and break
+								if ( node.nodeType === 1 && ++diff && node === elem ) {
+									outerCache[ type ] = [ dirruns, nodeIndex, diff ];
+									break;
+								}
+							}
+
+						// Use previously-cached element index if available
+						} else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
+							diff = cache[1];
+
+						// xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
+						} else {
+							// Use the same loop as above to seek `elem` from the start
+							while ( (node = ++nodeIndex && node && node[ dir ] ||
+								(diff = nodeIndex = 0) || start.pop()) ) {
+
+								if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
+									// Cache the index of each encountered element
+									if ( useCache ) {
+										(node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
+									}
+
+									if ( node === elem ) {
+										break;
+									}
+								}
+							}
+						}
+
+						// Incorporate the offset, then check against cycle size
+						diff -= last;
+						return diff === first || ( diff % first === 0 && diff / first >= 0 );
+					}
+				};
+		},
+
+		"PSEUDO": function( pseudo, argument ) {
+			// pseudo-class names are case-insensitive
+			// http://www.w3.org/TR/selectors/#pseudo-classes
+			// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+			// Remember that setFilters inherits from pseudos
+			var args,
+				fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+					Sizzle.error( "unsupported pseudo: " + pseudo );
+
+			// The user may use createPseudo to indicate that
+			// arguments are needed to create the filter function
+			// just as Sizzle does
+			if ( fn[ expando ] ) {
+				return fn( argument );
+			}
+
+			// But maintain support for old signatures
+			if ( fn.length > 1 ) {
+				args = [ pseudo, pseudo, "", argument ];
+				return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+					markFunction(function( seed, matches ) {
+						var idx,
+							matched = fn( seed, argument ),
+							i = matched.length;
+						while ( i-- ) {
+							idx = indexOf( seed, matched[i] );
+							seed[ idx ] = !( matches[ idx ] = matched[i] );
+						}
+					}) :
+					function( elem ) {
+						return fn( elem, 0, args );
+					};
+			}
+
+			return fn;
+		}
+	},
+
+	pseudos: {
+		// Potentially complex pseudos
+		"not": markFunction(function( selector ) {
+			// Trim the selector passed to compile
+			// to avoid treating leading and trailing
+			// spaces as combinators
+			var input = [],
+				results = [],
+				matcher = compile( selector.replace( rtrim, "$1" ) );
+
+			return matcher[ expando ] ?
+				markFunction(function( seed, matches, context, xml ) {
+					var elem,
+						unmatched = matcher( seed, null, xml, [] ),
+						i = seed.length;
+
+					// Match elements unmatched by `matcher`
+					while ( i-- ) {
+						if ( (elem = unmatched[i]) ) {
+							seed[i] = !(matches[i] = elem);
+						}
+					}
+				}) :
+				function( elem, context, xml ) {
+					input[0] = elem;
+					matcher( input, null, xml, results );
+					// Don't keep the element (issue #299)
+					input[0] = null;
+					return !results.pop();
+				};
+		}),
+
+		"has": markFunction(function( selector ) {
+			return function( elem ) {
+				return Sizzle( selector, elem ).length > 0;
+			};
+		}),
+
+		"contains": markFunction(function( text ) {
+			text = text.replace( runescape, funescape );
+			return function( elem ) {
+				return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
+			};
+		}),
+
+		// "Whether an element is represented by a :lang() selector
+		// is based solely on the element's language value
+		// being equal to the identifier C,
+		// or beginning with the identifier C immediately followed by "-".
+		// The matching of C against the element's language value is performed case-insensitively.
+		// The identifier C does not have to be a valid language name."
+		// http://www.w3.org/TR/selectors/#lang-pseudo
+		"lang": markFunction( function( lang ) {
+			// lang value must be a valid identifier
+			if ( !ridentifier.test(lang || "") ) {
+				Sizzle.error( "unsupported lang: " + lang );
+			}
+			lang = lang.replace( runescape, funescape ).toLowerCase();
+			return function( elem ) {
+				var elemLang;
+				do {
+					if ( (elemLang = documentIsHTML ?
+						elem.lang :
+						elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
+
+						elemLang = elemLang.toLowerCase();
+						return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
+					}
+				} while ( (elem = elem.parentNode) && elem.nodeType === 1 );
+				return false;
+			};
+		}),
+
+		// Miscellaneous
+		"target": function( elem ) {
+			var hash = window.location && window.location.hash;
+			return hash && hash.slice( 1 ) === elem.id;
+		},
+
+		"root": function( elem ) {
+			return elem === docElem;
+		},
+
+		"focus": function( elem ) {
+			return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
+		},
+
+		// Boolean properties
+		"enabled": function( elem ) {
+			return elem.disabled === false;
+		},
+
+		"disabled": function( elem ) {
+			return elem.disabled === true;
+		},
+
+		"checked": function( elem ) {
+			// In CSS3, :checked should return both checked and selected elements
+			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+			var nodeName = elem.nodeName.toLowerCase();
+			return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
+		},
+
+		"selected": function( elem ) {
+			// Accessing this property makes selected-by-default
+			// options in Safari work properly
+			if ( elem.parentNode ) {
+				elem.parentNode.selectedIndex;
+			}
+
+			return elem.selected === true;
+		},
+
+		// Contents
+		"empty": function( elem ) {
+			// http://www.w3.org/TR/selectors/#empty-pseudo
+			// :empty is negated by element (1) or content nodes (text: 3; cdata: 4; entity ref: 5),
+			//   but not by others (comment: 8; processing instruction: 7; etc.)
+			// nodeType < 6 works because attributes (2) do not appear as children
+			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+				if ( elem.nodeType < 6 ) {
+					return false;
+				}
+			}
+			return true;
+		},
+
+		"parent": function( elem ) {
+			return !Expr.pseudos["empty"]( elem );
+		},
+
+		// Element/input types
+		"header": function( elem ) {
+			return rheader.test( elem.nodeName );
+		},
+
+		"input": function( elem ) {
+			return rinputs.test( elem.nodeName );
+		},
+
+		"button": function( elem ) {
+			var name = elem.nodeName.toLowerCase();
+			return name === "input" && elem.type === "button" || name === "button";
+		},
+
+		"text": function( elem ) {
+			var attr;
+			return elem.nodeName.toLowerCase() === "input" &&
+				elem.type === "text" &&
+
+				// Support: IE<8
+				// New HTML5 attribute values (e.g., "search") appear with elem.type === "text"
+				( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === "text" );
+		},
+
+		// Position-in-collection
+		"first": createPositionalPseudo(function() {
+			return [ 0 ];
+		}),
+
+		"last": createPositionalPseudo(function( matchIndexes, length ) {
+			return [ length - 1 ];
+		}),
+
+		"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
+			return [ argument < 0 ? argument + length : argument ];
+		}),
+
+		"even": createPositionalPseudo(function( matchIndexes, length ) {
+			var i = 0;
+			for ( ; i < length; i += 2 ) {
+				matchIndexes.push( i );
+			}
+			return matchIndexes;
+		}),
+
+		"odd": createPositionalPseudo(function( matchIndexes, length ) {
+			var i = 1;
+			for ( ; i < length; i += 2 ) {
+				matchIndexes.push( i );
+			}
+			return matchIndexes;
+		}),
+
+		"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+			var i = argument < 0 ? argument + length : argument;
+			for ( ; --i >= 0; ) {
+				matchIndexes.push( i );
+			}
+			return matchIndexes;
+		}),
+
+		"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+			var i = argument < 0 ? argument + length : argument;
+			for ( ; ++i < length; ) {
+				matchIndexes.push( i );
+			}
+			return matchIndexes;
+		})
+	}
+};
+
+Expr.pseudos["nth"] = Expr.pseudos["eq"];
+
+// Add button/input type pseudos
+for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
+	Expr.pseudos[ i ] = createInputPseudo( i );
+}
+for ( i in { submit: true, reset: true } ) {
+	Expr.pseudos[ i ] = createButtonPseudo( i );
+}
+
+// Easy API for creating new setFilters
+function setFilters() {}
+setFilters.prototype = Expr.filters = Expr.pseudos;
+Expr.setFilters = new setFilters();
+
+tokenize = Sizzle.tokenize = function( selector, parseOnly ) {
+	var matched, match, tokens, type,
+		soFar, groups, preFilters,
+		cached = tokenCache[ selector + " " ];
+
+	if ( cached ) {
+		return parseOnly ? 0 : cached.slice( 0 );
+	}
+
+	soFar = selector;
+	groups = [];
+	preFilters = Expr.preFilter;
+
+	while ( soFar ) {
+
+		// Comma and first run
+		if ( !matched || (match = rcomma.exec( soFar )) ) {
+			if ( match ) {
+				// Don't consume trailing commas as valid
+				soFar = soFar.slice( match[0].length ) || soFar;
+			}
+			groups.push( (tokens = []) );
+		}
+
+		matched = false;
+
+		// Combinators
+		if ( (match = rcombinators.exec( soFar )) ) {
+			matched = match.shift();
+			tokens.push({
+				value: matched,
+				// Cast descendant combinators to space
+				type: match[0].replace( rtrim, " " )
+			});
+			soFar = soFar.slice( matched.length );
+		}
+
+		// Filters
+		for ( type in Expr.filter ) {
+			if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
+				(match = preFilters[ type ]( match ))) ) {
+				matched = match.shift();
+				tokens.push({
+					value: matched,
+					type: type,
+					matches: match
+				});
+				soFar = soFar.slice( matched.length );
+			}
+		}
+
+		if ( !matched ) {
+			break;
+		}
+	}
+
+	// Return the length of the invalid excess
+	// if we're just parsing
+	// Otherwise, throw an error or return tokens
+	return parseOnly ?
+		soFar.length :
+		soFar ?
+			Sizzle.error( selector ) :
+			// Cache the tokens
+			tokenCache( selector, groups ).slice( 0 );
+};
+
+function toSelector( tokens ) {
+	var i = 0,
+		len = tokens.length,
+		selector = "";
+	for ( ; i < len; i++ ) {
+		selector += tokens[i].value;
+	}
+	return selector;
+}
+
+function addCombinator( matcher, combinator, base ) {
+	var dir = combinator.dir,
+		checkNonElements = base && dir === "parentNode",
+		doneName = done++;
+
+	return combinator.first ?
+		// Check against closest ancestor/preceding element
+		function( elem, context, xml ) {
+			while ( (elem = elem[ dir ]) ) {
+				if ( elem.nodeType === 1 || checkNonElements ) {
+					return matcher( elem, context, xml );
+				}
+			}
+		} :
+
+		// Check against all ancestor/preceding elements
+		function( elem, context, xml ) {
+			var oldCache, outerCache,
+				newCache = [ dirruns, doneName ];
+
+			// We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
+			if ( xml ) {
+				while ( (elem = elem[ dir ]) ) {
+					if ( elem.nodeType === 1 || checkNonElements ) {
+						if ( matcher( elem, context, xml ) ) {
+							return true;
+						}
+					}
+				}
+			} else {
+				while ( (elem = elem[ dir ]) ) {
+					if ( elem.nodeType === 1 || checkNonElements ) {
+						outerCache = elem[ expando ] || (elem[ expando ] = {});
+						if ( (oldCache = outerCache[ dir ]) &&
+							oldCache[ 0 ] === dirruns && oldCache[ 1 ] === doneName ) {
+
+							// Assign to newCache so results back-propagate to previous elements
+							return (newCache[ 2 ] = oldCache[ 2 ]);
+						} else {
+							// Reuse newcache so results back-propagate to previous elements
+							outerCache[ dir ] = newCache;
+
+							// A match means we're done; a fail means we have to keep checking
+							if ( (newCache[ 2 ] = matcher( elem, context, xml )) ) {
+								return true;
+							}
+						}
+					}
+				}
+			}
+		};
+}
+
+function elementMatcher( matchers ) {
+	return matchers.length > 1 ?
+		function( elem, context, xml ) {
+			var i = matchers.length;
+			while ( i-- ) {
+				if ( !matchers[i]( elem, context, xml ) ) {
+					return false;
+				}
+			}
+			return true;
+		} :
+		matchers[0];
+}
+
+function multipleContexts( selector, contexts, results ) {
+	var i = 0,
+		len = contexts.length;
+	for ( ; i < len; i++ ) {
+		Sizzle( selector, contexts[i], results );
+	}
+	return results;
+}
+
+function condense( unmatched, map, filter, context, xml ) {
+	var elem,
+		newUnmatched = [],
+		i = 0,
+		len = unmatched.length,
+		mapped = map != null;
+
+	for ( ; i < len; i++ ) {
+		if ( (elem = unmatched[i]) ) {
+			if ( !filter || filter( elem, context, xml ) ) {
+				newUnmatched.push( elem );
+				if ( mapped ) {
+					map.push( i );
+				}
+			}
+		}
+	}
+
+	return newUnmatched;
+}
+
+function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+	if ( postFilter && !postFilter[ expando ] ) {
+		postFilter = setMatcher( postFilter );
+	}
+	if ( postFinder && !postFinder[ expando ] ) {
+		postFinder = setMatcher( postFinder, postSelector );
+	}
+	return markFunction(function( seed, results, context, xml ) {
+		var temp, i, elem,
+			preMap = [],
+			postMap = [],
+			preexisting = results.length,
+
+			// Get initial elements from seed or context
+			elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
+
+			// Prefilter to get matcher input, preserving a map for seed-results synchronization
+			matcherIn = preFilter && ( seed || !selector ) ?
+				condense( elems, preMap, preFilter, context, xml ) :
+				elems,
+
+			matcherOut = matcher ?
+				// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+				postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+					// ...intermediate processing is necessary
+					[] :
+
+					// ...otherwise use results directly
+					results :
+				matcherIn;
+
+		// Find primary matches
+		if ( matcher ) {
+			matcher( matcherIn, matcherOut, context, xml );
+		}
+
+		// Apply postFilter
+		if ( postFilter ) {
+			temp = condense( matcherOut, postMap );
+			postFilter( temp, [], context, xml );
+
+			// Un-match failing elements by moving them back to matcherIn
+			i = temp.length;
+			while ( i-- ) {
+				if ( (elem = temp[i]) ) {
+					matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
+				}
+			}
+		}
+
+		if ( seed ) {
+			if ( postFinder || preFilter ) {
+				if ( postFinder ) {
+					// Get the final matcherOut by condensing this intermediate into postFinder contexts
+					temp = [];
+					i = matcherOut.length;
+					while ( i-- ) {
+						if ( (elem = matcherOut[i]) ) {
+							// Restore matcherIn since elem is not yet a final match
+							temp.push( (matcherIn[i] = elem) );
+						}
+					}
+					postFinder( null, (matcherOut = []), temp, xml );
+				}
+
+				// Move matched elements from seed to results to keep them synchronized
+				i = matcherOut.length;
+				while ( i-- ) {
+					if ( (elem = matcherOut[i]) &&
+						(temp = postFinder ? indexOf( seed, elem ) : preMap[i]) > -1 ) {
+
+						seed[temp] = !(results[temp] = elem);
+					}
+				}
+			}
+
+		// Add elements to results, through postFinder if defined
+		} else {
+			matcherOut = condense(
+				matcherOut === results ?
+					matcherOut.splice( preexisting, matcherOut.length ) :
+					matcherOut
+			);
+			if ( postFinder ) {
+				postFinder( null, results, matcherOut, xml );
+			} else {
+				push.apply( results, matcherOut );
+			}
+		}
+	});
+}
+
+function matcherFromTokens( tokens ) {
+	var checkContext, matcher, j,
+		len = tokens.length,
+		leadingRelative = Expr.relative[ tokens[0].type ],
+		implicitRelative = leadingRelative || Expr.relative[" "],
+		i = leadingRelative ? 1 : 0,
+
+		// The foundational matcher ensures that elements are reachable from top-level context(s)
+		matchContext = addCombinator( function( elem ) {
+			return elem === checkContext;
+		}, implicitRelative, true ),
+		matchAnyContext = addCombinator( function( elem ) {
+			return indexOf( checkContext, elem ) > -1;
+		}, implicitRelative, true ),
+		matchers = [ function( elem, context, xml ) {
+			var ret = ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+				(checkContext = context).nodeType ?
+					matchContext( elem, context, xml ) :
+					matchAnyContext( elem, context, xml ) );
+			// Avoid hanging onto element (issue #299)
+			checkContext = null;
+			return ret;
+		} ];
+
+	for ( ; i < len; i++ ) {
+		if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
+			matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
+		} else {
+			matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
+
+			// Return special upon seeing a positional matcher
+			if ( matcher[ expando ] ) {
+				// Find the next relative operator (if any) for proper handling
+				j = ++i;
+				for ( ; j < len; j++ ) {
+					if ( Expr.relative[ tokens[j].type ] ) {
+						break;
+					}
+				}
+				return setMatcher(
+					i > 1 && elementMatcher( matchers ),
+					i > 1 && toSelector(
+						// If the preceding token was a descendant combinator, insert an implicit any-element `*`
+						tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
+					).replace( rtrim, "$1" ),
+					matcher,
+					i < j && matcherFromTokens( tokens.slice( i, j ) ),
+					j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
+					j < len && toSelector( tokens )
+				);
+			}
+			matchers.push( matcher );
+		}
+	}
+
+	return elementMatcher( matchers );
+}
+
+function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+	var bySet = setMatchers.length > 0,
+		byElement = elementMatchers.length > 0,
+		superMatcher = function( seed, context, xml, results, outermost ) {
+			var elem, j, matcher,
+				matchedCount = 0,
+				i = "0",
+				unmatched = seed && [],
+				setMatched = [],
+				contextBackup = outermostContext,
+				// We must always have either seed elements or outermost context
+				elems = seed || byElement && Expr.find["TAG"]( "*", outermost ),
+				// Use integer dirruns iff this is the outermost matcher
+				dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1),
+				len = elems.length;
+
+			if ( outermost ) {
+				outermostContext = context !== document && context;
+			}
+
+			// Add elements passing elementMatchers directly to results
+			// Keep `i` a string if there are no elements so `matchedCount` will be "00" below
+			// Support: IE<9, Safari
+			// Tolerate NodeList properties (IE: "length"; Safari: <number>) matching elements by id
+			for ( ; i !== len && (elem = elems[i]) != null; i++ ) {
+				if ( byElement && elem ) {
+					j = 0;
+					while ( (matcher = elementMatchers[j++]) ) {
+						if ( matcher( elem, context, xml ) ) {
+							results.push( elem );
+							break;
+						}
+					}
+					if ( outermost ) {
+						dirruns = dirrunsUnique;
+					}
+				}
+
+				// Track unmatched elements for set filters
+				if ( bySet ) {
+					// They will have gone through all possible matchers
+					if ( (elem = !matcher && elem) ) {
+						matchedCount--;
+					}
+
+					// Lengthen the array for every element, matched or not
+					if ( seed ) {
+						unmatched.push( elem );
+					}
+				}
+			}
+
+			// Apply set filters to unmatched elements
+			matchedCount += i;
+			if ( bySet && i !== matchedCount ) {
+				j = 0;
+				while ( (matcher = setMatchers[j++]) ) {
+					matcher( unmatched, setMatched, context, xml );
+				}
+
+				if ( seed ) {
+					// Reintegrate element matches to eliminate the need for sorting
+					if ( matchedCount > 0 ) {
+						while ( i-- ) {
+							if ( !(unmatched[i] || setMatched[i]) ) {
+								setMatched[i] = pop.call( results );
+							}
+						}
+					}
+
+					// Discard index placeholder values to get only actual matches
+					setMatched = condense( setMatched );
+				}
+
+				// Add matches to results
+				push.apply( results, setMatched );
+
+				// Seedless set matches succeeding multiple successful matchers stipulate sorting
+				if ( outermost && !seed && setMatched.length > 0 &&
+					( matchedCount + setMatchers.length ) > 1 ) {
+
+					Sizzle.uniqueSort( results );
+				}
+			}
+
+			// Override manipulation of globals by nested matchers
+			if ( outermost ) {
+				dirruns = dirrunsUnique;
+				outermostContext = contextBackup;
+			}
+
+			return unmatched;
+		};
+
+	return bySet ?
+		markFunction( superMatcher ) :
+		superMatcher;
+}
+
+compile = Sizzle.compile = function( selector, match /* Internal Use Only */ ) {
+	var i,
+		setMatchers = [],
+		elementMatchers = [],
+		cached = compilerCache[ selector + " " ];
+
+	if ( !cached ) {
+		// Generate a function of recursive functions that can be used to check each element
+		if ( !match ) {
+			match = tokenize( selector );
+		}
+		i = match.length;
+		while ( i-- ) {
+			cached = matcherFromTokens( match[i] );
+			if ( cached[ expando ] ) {
+				setMatchers.push( cached );
+			} else {
+				elementMatchers.push( cached );
+			}
+		}
+
+		// Cache the compiled function
+		cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
+
+		// Save selector and tokenization
+		cached.selector = selector;
+	}
+	return cached;
+};
+
+/**
+ * A low-level selection function that works with Sizzle's compiled
+ *  selector functions
+ * @param {String|Function} selector A selector or a pre-compiled
+ *  selector function built with Sizzle.compile
+ * @param {Element} context
+ * @param {Array} [results]
+ * @param {Array} [seed] A set of elements to match against
+ */
+select = Sizzle.select = function( selector, context, results, seed ) {
+	var i, tokens, token, type, find,
+		compiled = typeof selector === "function" && selector,
+		match = !seed && tokenize( (selector = compiled.selector || selector) );
+
+	results = results || [];
+
+	// Try to minimize operations if there is no seed and only one group
+	if ( match.length === 1 ) {
+
+		// Take a shortcut and set the context if the root selector is an ID
+		tokens = match[0] = match[0].slice( 0 );
+		if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
+				support.getById && context.nodeType === 9 && documentIsHTML &&
+				Expr.relative[ tokens[1].type ] ) {
+
+			context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
+			if ( !context ) {
+				return results;
+
+			// Precompiled matchers will still verify ancestry, so step up a level
+			} else if ( compiled ) {
+				context = context.parentNode;
+			}
+
+			selector = selector.slice( tokens.shift().value.length );
+		}
+
+		// Fetch a seed set for right-to-left matching
+		i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
+		while ( i-- ) {
+			token = tokens[i];
+
+			// Abort if we hit a combinator
+			if ( Expr.relative[ (type = token.type) ] ) {
+				break;
+			}
+			if ( (find = Expr.find[ type ]) ) {
+				// Search, expanding context for leading sibling combinators
+				if ( (seed = find(
+					token.matches[0].replace( runescape, funescape ),
+					rsibling.test( tokens[0].type ) && testContext( context.parentNode ) || context
+				)) ) {
+
+					// If seed is empty or no tokens remain, we can return early
+					tokens.splice( i, 1 );
+					selector = seed.length && toSelector( tokens );
+					if ( !selector ) {
+						push.apply( results, seed );
+						return results;
+					}
+
+					break;
+				}
+			}
+		}
+	}
+
+	// Compile and execute a filtering function if one is not provided
+	// Provide `match` to avoid retokenization if we modified the selector above
+	( compiled || compile( selector, match ) )(
+		seed,
+		context,
+		!documentIsHTML,
+		results,
+		rsibling.test( selector ) && testContext( context.parentNode ) || context
+	);
+	return results;
+};
+
+// One-time assignments
+
+// Sort stability
+support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
+
+// Support: Chrome 14-35+
+// Always assume duplicates if they aren't passed to the comparison function
+support.detectDuplicates = !!hasDuplicate;
+
+// Initialize against the default document
+setDocument();
+
+// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
+// Detached nodes confoundingly follow *each other*
+support.sortDetached = assert(function( div1 ) {
+	// Should return 1, but returns 4 (following)
+	return div1.compareDocumentPosition( document.createElement("div") ) & 1;
+});
+
+// Support: IE<8
+// Prevent attribute/property "interpolation"
+// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
+if ( !assert(function( div ) {
+	div.innerHTML = "<a href='#'></a>";
+	return div.firstChild.getAttribute("href") === "#" ;
+}) ) {
+	addHandle( "type|href|height|width", function( elem, name, isXML ) {
+		if ( !isXML ) {
+			return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
+		}
+	});
+}
+
+// Support: IE<9
+// Use defaultValue in place of getAttribute("value")
+if ( !support.attributes || !assert(function( div ) {
+	div.innerHTML = "<input/>";
+	div.firstChild.setAttribute( "value", "" );
+	return div.firstChild.getAttribute( "value" ) === "";
+}) ) {
+	addHandle( "value", function( elem, name, isXML ) {
+		if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
+			return elem.defaultValue;
+		}
+	});
+}
+
+// Support: IE<9
+// Use getAttributeNode to fetch booleans when getAttribute lies
+if ( !assert(function( div ) {
+	return div.getAttribute("disabled") == null;
+}) ) {
+	addHandle( booleans, function( elem, name, isXML ) {
+		var val;
+		if ( !isXML ) {
+			return elem[ name ] === true ? name.toLowerCase() :
+					(val = elem.getAttributeNode( name )) && val.specified ?
+					val.value :
+				null;
+		}
+	});
+}
+
+return Sizzle;
+
+})( window );
+
+
+
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.pseudos;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+
+var rneedsContext = jQuery.expr.match.needsContext;
+
+var rsingleTag = (/^<(\w+)\s*\/?>(?:<\/\1>|)$/);
+
+
+
+var risSimple = /^.[^:#\[\.,]*$/;
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, not ) {
+	if ( jQuery.isFunction( qualifier ) ) {
+		return jQuery.grep( elements, function( elem, i ) {
+			/* jshint -W018 */
+			return !!qualifier.call( elem, i, elem ) !== not;
+		});
+
+	}
+
+	if ( qualifier.nodeType ) {
+		return jQuery.grep( elements, function( elem ) {
+			return ( elem === qualifier ) !== not;
+		});
+
+	}
+
+	if ( typeof qualifier === "string" ) {
+		if ( risSimple.test( qualifier ) ) {
+			return jQuery.filter( qualifier, elements, not );
+		}
+
+		qualifier = jQuery.filter( qualifier, elements );
+	}
+
+	return jQuery.grep( elements, function( elem ) {
+		return ( indexOf.call( qualifier, elem ) >= 0 ) !== not;
+	});
+}
+
+jQuery.filter = function( expr, elems, not ) {
+	var elem = elems[ 0 ];
+
+	if ( not ) {
+		expr = ":not(" + expr + ")";
+	}
+
+	return elems.length === 1 && elem.nodeType === 1 ?
+		jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
+		jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
+			return elem.nodeType === 1;
+		}));
+};
+
+jQuery.fn.extend({
+	find: function( selector ) {
+		var i,
+			len = this.length,
+			ret = [],
+			self = this;
+
+		if ( typeof selector !== "string" ) {
+			return this.pushStack( jQuery( selector ).filter(function() {
+				for ( i = 0; i < len; i++ ) {
+					if ( jQuery.contains( self[ i ], this ) ) {
+						return true;
+					}
+				}
+			}) );
+		}
+
+		for ( i = 0; i < len; i++ ) {
+			jQuery.find( selector, self[ i ], ret );
+		}
+
+		// Needed because $( selector, context ) becomes $( context ).find( selector )
+		ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
+		ret.selector = this.selector ? this.selector + " " + selector : selector;
+		return ret;
+	},
+	filter: function( selector ) {
+		return this.pushStack( winnow(this, selector || [], false) );
+	},
+	not: function( selector ) {
+		return this.pushStack( winnow(this, selector || [], true) );
+	},
+	is: function( selector ) {
+		return !!winnow(
+			this,
+
+			// If this is a positional/relative selector, check membership in the returned set
+			// so $("p:first").is("p:last") won't return true for a doc with two "p".
+			typeof selector === "string" && rneedsContext.test( selector ) ?
+				jQuery( selector ) :
+				selector || [],
+			false
+		).length;
+	}
+});
+
+
+// Initialize a jQuery object
+
+
+// A central reference to the root jQuery(document)
+var rootjQuery,
+
+	// A simple way to check for HTML strings
+	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+	// Strict HTML recognition (#11290: must start with <)
+	rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
+
+	init = jQuery.fn.init = function( selector, context ) {
+		var match, elem;
+
+		// HANDLE: $(""), $(null), $(undefined), $(false)
+		if ( !selector ) {
+			return this;
+		}
+
+		// Handle HTML strings
+		if ( typeof selector === "string" ) {
+			if ( selector[0] === "<" && selector[ selector.length - 1 ] === ">" && selector.length >= 3 ) {
+				// Assume that strings that start and end with <> are HTML and skip the regex check
+				match = [ null, selector, null ];
+
+			} else {
+				match = rquickExpr.exec( selector );
+			}
+
+			// Match html or make sure no context is specified for #id
+			if ( match && (match[1] || !context) ) {
+
+				// HANDLE: $(html) -> $(array)
+				if ( match[1] ) {
+					context = context instanceof jQuery ? context[0] : context;
+
+					// Option to run scripts is true for back-compat
+					// Intentionally let the error be thrown if parseHTML is not present
+					jQuery.merge( this, jQuery.parseHTML(
+						match[1],
+						context && context.nodeType ? context.ownerDocument || context : document,
+						true
+					) );
+
+					// HANDLE: $(html, props)
+					if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
+						for ( match in context ) {
+							// Properties of context are called as methods if possible
+							if ( jQuery.isFunction( this[ match ] ) ) {
+								this[ match ]( context[ match ] );
+
+							// ...and otherwise set as attributes
+							} else {
+								this.attr( match, context[ match ] );
+							}
+						}
+					}
+
+					return this;
+
+				// HANDLE: $(#id)
+				} else {
+					elem = document.getElementById( match[2] );
+
+					// Support: Blackberry 4.6
+					// gEBID returns nodes no longer in the document (#6963)
+					if ( elem && elem.parentNode ) {
+						// Inject the element directly into the jQuery object
+						this.length = 1;
+						this[0] = elem;
+					}
+
+					this.context = document;
+					this.selector = selector;
+					return this;
+				}
+
+			// HANDLE: $(expr, $(...))
+			} else if ( !context || context.jquery ) {
+				return ( context || rootjQuery ).find( selector );
+
+			// HANDLE: $(expr, context)
+			// (which is just equivalent to: $(context).find(expr)
+			} else {
+				return this.constructor( context ).find( selector );
+			}
+
+		// HANDLE: $(DOMElement)
+		} else if ( selector.nodeType ) {
+			this.context = this[0] = selector;
+			this.length = 1;
+			return this;
+
+		// HANDLE: $(function)
+		// Shortcut for document ready
+		} else if ( jQuery.isFunction( selector ) ) {
+			return typeof rootjQuery.ready !== "undefined" ?
+				rootjQuery.ready( selector ) :
+				// Execute immediately if ready is not present
+				selector( jQuery );
+		}
+
+		if ( selector.selector !== undefined ) {
+			this.selector = selector.selector;
+			this.context = selector.context;
+		}
+
+		return jQuery.makeArray( selector, this );
+	};
+
+// Give the init function the jQuery prototype for later instantiation
+init.prototype = jQuery.fn;
+
+// Initialize central reference
+rootjQuery = jQuery( document );
+
+
+var rparentsprev = /^(?:parents|prev(?:Until|All))/,
+	// Methods guaranteed to produce a unique set when starting from a unique set
+	guaranteedUnique = {
+		children: true,
+		contents: true,
+		next: true,
+		prev: true
+	};
+
+jQuery.extend({
+	dir: function( elem, dir, until ) {
+		var matched = [],
+			truncate = until !== undefined;
+
+		while ( (elem = elem[ dir ]) && elem.nodeType !== 9 ) {
+			if ( elem.nodeType === 1 ) {
+				if ( truncate && jQuery( elem ).is( until ) ) {
+					break;
+				}
+				matched.push( elem );
+			}
+		}
+		return matched;
+	},
+
+	sibling: function( n, elem ) {
+		var matched = [];
+
+		for ( ; n; n = n.nextSibling ) {
+			if ( n.nodeType === 1 && n !== elem ) {
+				matched.push( n );
+			}
+		}
+
+		return matched;
+	}
+});
+
+jQuery.fn.extend({
+	has: function( target ) {
+		var targets = jQuery( target, this ),
+			l = targets.length;
+
+		return this.filter(function() {
+			var i = 0;
+			for ( ; i < l; i++ ) {
+				if ( jQuery.contains( this, targets[i] ) ) {
+					return true;
+				}
+			}
+		});
+	},
+
+	closest: function( selectors, context ) {
+		var cur,
+			i = 0,
+			l = this.length,
+			matched = [],
+			pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
+				jQuery( selectors, context || this.context ) :
+				0;
+
+		for ( ; i < l; i++ ) {
+			for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {
+				// Always skip document fragments
+				if ( cur.nodeType < 11 && (pos ?
+					pos.index(cur) > -1 :
+
+					// Don't pass non-elements to Sizzle
+					cur.nodeType === 1 &&
+						jQuery.find.matchesSelector(cur, selectors)) ) {
+
+					matched.push( cur );
+					break;
+				}
+			}
+		}
+
+		return this.pushStack( matched.length > 1 ? jQuery.unique( matched ) : matched );
+	},
+
+	// Determine the position of an element within the set
+	index: function( elem ) {
+
+		// No argument, return index in parent
+		if ( !elem ) {
+			return ( this[ 0 ] && this[ 0 ].parentNode ) ? this.first().prevAll().length : -1;
+		}
+
+		// Index in selector
+		if ( typeof elem === "string" ) {
+			return indexOf.call( jQuery( elem ), this[ 0 ] );
+		}
+
+		// Locate the position of the desired element
+		return indexOf.call( this,
+
+			// If it receives a jQuery object, the first element is used
+			elem.jquery ? elem[ 0 ] : elem
+		);
+	},
+
+	add: function( selector, context ) {
+		return this.pushStack(
+			jQuery.unique(
+				jQuery.merge( this.get(), jQuery( selector, context ) )
+			)
+		);
+	},
+
+	addBack: function( selector ) {
+		return this.add( selector == null ?
+			this.prevObject : this.prevObject.filter(selector)
+		);
+	}
+});
+
+function sibling( cur, dir ) {
+	while ( (cur = cur[dir]) && cur.nodeType !== 1 ) {}
+	return cur;
+}
+
+jQuery.each({
+	parent: function( elem ) {
+		var parent = elem.parentNode;
+		return parent && parent.nodeType !== 11 ? parent : null;
+	},
+	parents: function( elem ) {
+		return jQuery.dir( elem, "parentNode" );
+	},
+	parentsUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "parentNode", until );
+	},
+	next: function( elem ) {
+		return sibling( elem, "nextSibling" );
+	},
+	prev: function( elem ) {
+		return sibling( elem, "previousSibling" );
+	},
+	nextAll: function( elem ) {
+		return jQuery.dir( elem, "nextSibling" );
+	},
+	prevAll: function( elem ) {
+		return jQuery.dir( elem, "previousSibling" );
+	},
+	nextUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "nextSibling", until );
+	},
+	prevUntil: function( elem, i, until ) {
+		return jQuery.dir( elem, "previousSibling", until );
+	},
+	siblings: function( elem ) {
+		return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
+	},
+	children: function( elem ) {
+		return jQuery.sibling( elem.firstChild );
+	},
+	contents: function( elem ) {
+		return elem.contentDocument || jQuery.merge( [], elem.childNodes );
+	}
+}, function( name, fn ) {
+	jQuery.fn[ name ] = function( until, selector ) {
+		var matched = jQuery.map( this, fn, until );
+
+		if ( name.slice( -5 ) !== "Until" ) {
+			selector = until;
+		}
+
+		if ( selector && typeof selector === "string" ) {
+			matched = jQuery.filter( selector, matched );
+		}
+
+		if ( this.length > 1 ) {
+			// Remove duplicates
+			if ( !guaranteedUnique[ name ] ) {
+				jQuery.unique( matched );
+			}
+
+			// Reverse order for parents* and prev-derivatives
+			if ( rparentsprev.test( name ) ) {
+				matched.reverse();
+			}
+		}
+
+		return this.pushStack( matched );
+	};
+});
+var rnotwhite = (/\S+/g);
+
+
+
+// String to Object options format cache
+var optionsCache = {};
+
+// Convert String-formatted options into Object-formatted ones and store in cache
+function createOptions( options ) {
+	var object = optionsCache[ options ] = {};
+	jQuery.each( options.match( rnotwhite ) || [], function( _, flag ) {
+		object[ flag ] = true;
+	});
+	return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ *	options: an optional list of space-separated options that will change how
+ *			the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ *	once:			will ensure the callback list can only be fired once (like a Deferred)
+ *
+ *	memory:			will keep track of previous values and will call any callback added
+ *					after the list has been fired right away with the latest "memorized"
+ *					values (like a Deferred)
+ *
+ *	unique:			will ensure a callback can only be added once (no duplicate in the list)
+ *
+ *	stopOnFalse:	interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( options ) {
+
+	// Convert options from String-formatted to Object-formatted if needed
+	// (we check in cache first)
+	options = typeof options === "string" ?
+		( optionsCache[ options ] || createOptions( options ) ) :
+		jQuery.extend( {}, options );
+
+	var // Last fire value (for non-forgettable lists)
+		memory,
+		// Flag to know if list was already fired
+		fired,
+		// Flag to know if list is currently firing
+		firing,
+		// First callback to fire (used internally by add and fireWith)
+		firingStart,
+		// End of the loop when firing
+		firingLength,
+		// Index of currently firing callback (modified by remove if needed)
+		firingIndex,
+		// Actual callback list
+		list = [],
+		// Stack of fire calls for repeatable lists
+		stack = !options.once && [],
+		// Fire callbacks
+		fire = function( data ) {
+			memory = options.memory && data;
+			fired = true;
+			firingIndex = firingStart || 0;
+			firingStart = 0;
+			firingLength = list.length;
+			firing = true;
+			for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+				if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
+					memory = false; // To prevent further calls using add
+					break;
+				}
+			}
+			firing = false;
+			if ( list ) {
+				if ( stack ) {
+					if ( stack.length ) {
+						fire( stack.shift() );
+					}
+				} else if ( memory ) {
+					list = [];
+				} else {
+					self.disable();
+				}
+			}
+		},
+		// Actual Callbacks object
+		self = {
+			// Add a callback or a collection of callbacks to the list
+			add: function() {
+				if ( list ) {
+					// First, we save the current length
+					var start = list.length;
+					(function add( args ) {
+						jQuery.each( args, function( _, arg ) {
+							var type = jQuery.type( arg );
+							if ( type === "function" ) {
+								if ( !options.unique || !self.has( arg ) ) {
+									list.push( arg );
+								}
+							} else if ( arg && arg.length && type !== "string" ) {
+								// Inspect recursively
+								add( arg );
+							}
+						});
+					})( arguments );
+					// Do we need to add the callbacks to the
+					// current firing batch?
+					if ( firing ) {
+						firingLength = list.length;
+					// With memory, if we're not firing then
+					// we should call right away
+					} else if ( memory ) {
+						firingStart = start;
+						fire( memory );
+					}
+				}
+				return this;
+			},
+			// Remove a callback from the list
+			remove: function() {
+				if ( list ) {
+					jQuery.each( arguments, function( _, arg ) {
+						var index;
+						while ( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+							list.splice( index, 1 );
+							// Handle firing indexes
+							if ( firing ) {
+								if ( index <= firingLength ) {
+									firingLength--;
+								}
+								if ( index <= firingIndex ) {
+									firingIndex--;
+								}
+							}
+						}
+					});
+				}
+				return this;
+			},
+			// Check if a given callback is in the list.
+			// If no argument is given, return whether or not list has callbacks attached.
+			has: function( fn ) {
+				return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
+			},
+			// Remove all callbacks from the list
+			empty: function() {
+				list = [];
+				firingLength = 0;
+				return this;
+			},
+			// Have the list do nothing anymore
+			disable: function() {
+				list = stack = memory = undefined;
+				return this;
+			},
+			// Is it disabled?
+			disabled: function() {
+				return !list;
+			},
+			// Lock the list in its current state
+			lock: function() {
+				stack = undefined;
+				if ( !memory ) {
+					self.disable();
+				}
+				return this;
+			},
+			// Is it locked?
+			locked: function() {
+				return !stack;
+			},
+			// Call all callbacks with the given context and arguments
+			fireWith: function( context, args ) {
+				if ( list && ( !fired || stack ) ) {
+					args = args || [];
+					args = [ context, args.slice ? args.slice() : args ];
+					if ( firing ) {
+						stack.push( args );
+					} else {
+						fire( args );
+					}
+				}
+				return this;
+			},
+			// Call all the callbacks with the given arguments
+			fire: function() {
+				self.fireWith( this, arguments );
+				return this;
+			},
+			// To know if the callbacks have already been called at least once
+			fired: function() {
+				return !!fired;
+			}
+		};
+
+	return self;
+};
+
+
+jQuery.extend({
+
+	Deferred: function( func ) {
+		var tuples = [
+				// action, add listener, listener list, final state
+				[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
+				[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
+				[ "notify", "progress", jQuery.Callbacks("memory") ]
+			],
+			state = "pending",
+			promise = {
+				state: function() {
+					return state;
+				},
+				always: function() {
+					deferred.done( arguments ).fail( arguments );
+					return this;
+				},
+				then: function( /* fnDone, fnFail, fnProgress */ ) {
+					var fns = arguments;
+					return jQuery.Deferred(function( newDefer ) {
+						jQuery.each( tuples, function( i, tuple ) {
+							var fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
+							// deferred[ done | fail | progress ] for forwarding actions to newDefer
+							deferred[ tuple[1] ](function() {
+								var returned = fn && fn.apply( this, arguments );
+								if ( returned && jQuery.isFunction( returned.promise ) ) {
+									returned.promise()
+										.done( newDefer.resolve )
+										.fail( newDefer.reject )
+										.progress( newDefer.notify );
+								} else {
+									newDefer[ tuple[ 0 ] + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
+								}
+							});
+						});
+						fns = null;
+					}).promise();
+				},
+				// Get a promise for this deferred
+				// If obj is provided, the promise aspect is added to the object
+				promise: function( obj ) {
+					return obj != null ? jQuery.extend( obj, promise ) : promise;
+				}
+			},
+			deferred = {};
+
+		// Keep pipe for back-compat
+		promise.pipe = promise.then;
+
+		// Add list-specific methods
+		jQuery.each( tuples, function( i, tuple ) {
+			var list = tuple[ 2 ],
+				stateString = tuple[ 3 ];
+
+			// promise[ done | fail | progress ] = list.add
+			promise[ tuple[1] ] = list.add;
+
+			// Handle state
+			if ( stateString ) {
+				list.add(function() {
+					// state = [ resolved | rejected ]
+					state = stateString;
+
+				// [ reject_list | resolve_list ].disable; progress_list.lock
+				}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+			}
+
+			// deferred[ resolve | reject | notify ]
+			deferred[ tuple[0] ] = function() {
+				deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
+				return this;
+			};
+			deferred[ tuple[0] + "With" ] = list.fireWith;
+		});
+
+		// Make the deferred a promise
+		promise.promise( deferred );
+
+		// Call given func if any
+		if ( func ) {
+			func.call( deferred, deferred );
+		}
+
+		// All done!
+		return deferred;
+	},
+
+	// Deferred helper
+	when: function( subordinate /* , ..., subordinateN */ ) {
+		var i = 0,
+			resolveValues = slice.call( arguments ),
+			length = resolveValues.length,
+
+			// the count of uncompleted subordinates
+			remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
+
+			// the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+			deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
+
+			// Update function for both resolve and progress values
+			updateFunc = function( i, contexts, values ) {
+				return function( value ) {
+					contexts[ i ] = this;
+					values[ i ] = arguments.length > 1 ? slice.call( arguments ) : value;
+					if ( values === progressValues ) {
+						deferred.notifyWith( contexts, values );
+					} else if ( !( --remaining ) ) {
+						deferred.resolveWith( contexts, values );
+					}
+				};
+			},
+
+			progressValues, progressContexts, resolveContexts;
+
+		// Add listeners to Deferred subordinates; treat others as resolved
+		if ( length > 1 ) {
+			progressValues = new Array( length );
+			progressContexts = new Array( length );
+			resolveContexts = new Array( length );
+			for ( ; i < length; i++ ) {
+				if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
+					resolveValues[ i ].promise()
+						.done( updateFunc( i, resolveContexts, resolveValues ) )
+						.fail( deferred.reject )
+						.progress( updateFunc( i, progressContexts, progressValues ) );
+				} else {
+					--remaining;
+				}
+			}
+		}
+
+		// If we're not waiting on anything, resolve the master
+		if ( !remaining ) {
+			deferred.resolveWith( resolveContexts, resolveValues );
+		}
+
+		return deferred.promise();
+	}
+});
+
+
+// The deferred used on DOM ready
+var readyList;
+
+jQuery.fn.ready = function( fn ) {
+	// Add the callback
+	jQuery.ready.promise().done( fn );
+
+	return this;
+};
+
+jQuery.extend({
+	// Is the DOM ready to be used? Set to true once it occurs.
+	isReady: false,
+
+	// A counter to track how many items to wait for before
+	// the ready event fires. See #6781
+	readyWait: 1,
+
+	// Hold (or release) the ready event
+	holdReady: function( hold ) {
+		if ( hold ) {
+			jQuery.readyWait++;
+		} else {
+			jQuery.ready( true );
+		}
+	},
+
+	// Handle when the DOM is ready
+	ready: function( wait ) {
+
+		// Abort if there are pending holds or we're already ready
+		if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+			return;
+		}
+
+		// Remember that the DOM is ready
+		jQuery.isReady = true;
+
+		// If a normal DOM Ready event fired, decrement, and wait if need be
+		if ( wait !== true && --jQuery.readyWait > 0 ) {
+			return;
+		}
+
+		// If there are functions bound, to execute
+		readyList.resolveWith( document, [ jQuery ] );
+
+		// Trigger any bound ready events
+		if ( jQuery.fn.triggerHandler ) {
+			jQuery( document ).triggerHandler( "ready" );
+			jQuery( document ).off( "ready" );
+		}
+	}
+});
+
+/**
+ * The ready event handler and self cleanup method
+ */
+function completed() {
+	document.removeEventListener( "DOMContentLoaded", completed, false );
+	window.removeEventListener( "load", completed, false );
+	jQuery.ready();
+}
+
+jQuery.ready.promise = function( obj ) {
+	if ( !readyList ) {
+
+		readyList = jQuery.Deferred();
+
+		// Catch cases where $(document).ready() is called after the browser event has already occurred.
+		// We once tried to use readyState "interactive" here, but it caused issues like the one
+		// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
+		if ( document.readyState === "complete" ) {
+			// Handle it asynchronously to allow scripts the opportunity to delay ready
+			setTimeout( jQuery.ready );
+
+		} else {
+
+			// Use the handy event callback
+			document.addEventListener( "DOMContentLoaded", completed, false );
+
+			// A fallback to window.onload, that will always work
+			window.addEventListener( "load", completed, false );
+		}
+	}
+	return readyList.promise( obj );
+};
+
+// Kick off the DOM ready check even if the user does not
+jQuery.ready.promise();
+
+
+
+
+// Multifunctional method to get and set values of a collection
+// The value/s can optionally be executed if it's a function
+var access = jQuery.access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
+	var i = 0,
+		len = elems.length,
+		bulk = key == null;
+
+	// Sets many values
+	if ( jQuery.type( key ) === "object" ) {
+		chainable = true;
+		for ( i in key ) {
+			jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
+		}
+
+	// Sets one value
+	} else if ( value !== undefined ) {
+		chainable = true;
+
+		if ( !jQuery.isFunction( value ) ) {
+			raw = true;
+		}
+
+		if ( bulk ) {
+			// Bulk operations run against the entire set
+			if ( raw ) {
+				fn.call( elems, value );
+				fn = null;
+
+			// ...except when executing function values
+			} else {
+				bulk = fn;
+				fn = function( elem, key, value ) {
+					return bulk.call( jQuery( elem ), value );
+				};
+			}
+		}
+
+		if ( fn ) {
+			for ( ; i < len; i++ ) {
+				fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
+			}
+		}
+	}
+
+	return chainable ?
+		elems :
+
+		// Gets
+		bulk ?
+			fn.call( elems ) :
+			len ? fn( elems[0], key ) : emptyGet;
+};
+
+
+/**
+ * Determines whether an object can have data
+ */
+jQuery.acceptData = function( owner ) {
+	// Accepts only:
+	//  - Node
+	//    - Node.ELEMENT_NODE
+	//    - Node.DOCUMENT_NODE
+	//  - Object
+	//    - Any
+	/* jshint -W018 */
+	return owner.nodeType === 1 || owner.nodeType === 9 || !( +owner.nodeType );
+};
+
+
+function Data() {
+	// Support: Android<4,
+	// Old WebKit does not have Object.preventExtensions/freeze method,
+	// return new empty object instead with no [[set]] accessor
+	Object.defineProperty( this.cache = {}, 0, {
+		get: function() {
+			return {};
+		}
+	});
+
+	this.expando = jQuery.expando + Data.uid++;
+}
+
+Data.uid = 1;
+Data.accepts = jQuery.acceptData;
+
+Data.prototype = {
+	key: function( owner ) {
+		// We can accept data for non-element nodes in modern browsers,
+		// but we should not, see #8335.
+		// Always return the key for a frozen object.
+		if ( !Data.accepts( owner ) ) {
+			return 0;
+		}
+
+		var descriptor = {},
+			// Check if the owner object already has a cache key
+			unlock = owner[ this.expando ];
+
+		// If not, create one
+		if ( !unlock ) {
+			unlock = Data.uid++;
+
+			// Secure it in a non-enumerable, non-writable property
+			try {
+				descriptor[ this.expando ] = { value: unlock };
+				Object.defineProperties( owner, descriptor );
+
+			// Support: Android<4
+			// Fallback to a less secure definition
+			} catch ( e ) {
+				descriptor[ this.expando ] = unlock;
+				jQuery.extend( owner, descriptor );
+			}
+		}
+
+		// Ensure the cache object
+		if ( !this.cache[ unlock ] ) {
+			this.cache[ unlock ] = {};
+		}
+
+		return unlock;
+	},
+	set: function( owner, data, value ) {
+		var prop,
+			// There may be an unlock assigned to this node,
+			// if there is no entry for this "owner", create one inline
+			// and set the unlock as though an owner entry had always existed
+			unlock = this.key( owner ),
+			cache = this.cache[ unlock ];
+
+		// Handle: [ owner, key, value ] args
+		if ( typeof data === "string" ) {
+			cache[ data ] = value;
+
+		// Handle: [ owner, { properties } ] args
+		} else {
+			// Fresh assignments by object are shallow copied
+			if ( jQuery.isEmptyObject( cache ) ) {
+				jQuery.extend( this.cache[ unlock ], data );
+			// Otherwise, copy the properties one-by-one to the cache object
+			} else {
+				for ( prop in data ) {
+					cache[ prop ] = data[ prop ];
+				}
+			}
+		}
+		return cache;
+	},
+	get: function( owner, key ) {
+		// Either a valid cache is found, or will be created.
+		// New caches will be created and the unlock returned,
+		// allowing direct access to the newly created
+		// empty data object. A valid owner object must be provided.
+		var cache = this.cache[ this.key( owner ) ];
+
+		return key === undefined ?
+			cache : cache[ key ];
+	},
+	access: function( owner, key, value ) {
+		var stored;
+		// In cases where either:
+		//
+		//   1. No key was specified
+		//   2. A string key was specified, but no value provided
+		//
+		// Take the "read" path and allow the get method to determine
+		// which value to return, respectively either:
+		//
+		//   1. The entire cache object
+		//   2. The data stored at the key
+		//
+		if ( key === undefined ||
+				((key && typeof key === "string") && value === undefined) ) {
+
+			stored = this.get( owner, key );
+
+			return stored !== undefined ?
+				stored : this.get( owner, jQuery.camelCase(key) );
+		}
+
+		// [*]When the key is not a string, or both a key and value
+		// are specified, set or extend (existing objects) with either:
+		//
+		//   1. An object of properties
+		//   2. A key and value
+		//
+		this.set( owner, key, value );
+
+		// Since the "set" path can have two possible entry points
+		// return the expected data based on which path was taken[*]
+		return value !== undefined ? value : key;
+	},
+	remove: function( owner, key ) {
+		var i, name, camel,
+			unlock = this.key( owner ),
+			cache = this.cache[ unlock ];
+
+		if ( key === undefined ) {
+			this.cache[ unlock ] = {};
+
+		} else {
+			// Support array or space separated string of keys
+			if ( jQuery.isArray( key ) ) {
+				// If "name" is an array of keys...
+				// When data is initially created, via ("key", "val") signature,
+				// keys will be converted to camelCase.
+				// Since there is no way to tell _how_ a key was added, remove
+				// both plain key and camelCase key. #12786
+				// This will only penalize the array argument path.
+				name = key.concat( key.map( jQuery.camelCase ) );
+			} else {
+				camel = jQuery.camelCase( key );
+				// Try the string as a key before any manipulation
+				if ( key in cache ) {
+					name = [ key, camel ];
+				} else {
+					// If a key with the spaces exists, use it.
+					// Otherwise, create an array by matching non-whitespace
+					name = camel;
+					name = name in cache ?
+						[ name ] : ( name.match( rnotwhite ) || [] );
+				}
+			}
+
+			i = name.length;
+			while ( i-- ) {
+				delete cache[ name[ i ] ];
+			}
+		}
+	},
+	hasData: function( owner ) {
+		return !jQuery.isEmptyObject(
+			this.cache[ owner[ this.expando ] ] || {}
+		);
+	},
+	discard: function( owner ) {
+		if ( owner[ this.expando ] ) {
+			delete this.cache[ owner[ this.expando ] ];
+		}
+	}
+};
+var data_priv = new Data();
+
+var data_user = new Data();
+
+
+
+//	Implementation Summary
+//
+//	1. Enforce API surface and semantic compatibility with 1.9.x branch
+//	2. Improve the module's maintainability by reducing the storage
+//		paths to a single mechanism.
+//	3. Use the same single mechanism to support "private" and "user" data.
+//	4. _Never_ expose "private" data to user code (TODO: Drop _data, _removeData)
+//	5. Avoid exposing implementation details on user objects (eg. expando properties)
+//	6. Provide a clear path for implementation upgrade to WeakMap in 2014
+
+var rbrace = /^(?:\{[\w\W]*\}|\[[\w\W]*\])$/,
+	rmultiDash = /([A-Z])/g;
+
+function dataAttr( elem, key, data ) {
+	var name;
+
+	// If nothing was found internally, try to fetch any
+	// data from the HTML5 data-* attribute
+	if ( data === undefined && elem.nodeType === 1 ) {
+		name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+		data = elem.getAttribute( name );
+
+		if ( typeof data === "string" ) {
+			try {
+				data = data === "true" ? true :
+					data === "false" ? false :
+					data === "null" ? null :
+					// Only convert to a number if it doesn't change the string
+					+data + "" === data ? +data :
+					rbrace.test( data ) ? jQuery.parseJSON( data ) :
+					data;
+			} catch( e ) {}
+
+			// Make sure we set the data so it isn't changed later
+			data_user.set( elem, key, data );
+		} else {
+			data = undefined;
+		}
+	}
+	return data;
+}
+
+jQuery.extend({
+	hasData: function( elem ) {
+		return data_user.hasData( elem ) || data_priv.hasData( elem );
+	},
+
+	data: function( elem, name, data ) {
+		return data_user.access( elem, name, data );
+	},
+
+	removeData: function( elem, name ) {
+		data_user.remove( elem, name );
+	},
+
+	// TODO: Now that all calls to _data and _removeData have been replaced
+	// with direct calls to data_priv methods, these can be deprecated.
+	_data: function( elem, name, data ) {
+		return data_priv.access( elem, name, data );
+	},
+
+	_removeData: function( elem, name ) {
+		data_priv.remove( elem, name );
+	}
+});
+
+jQuery.fn.extend({
+	data: function( key, value ) {
+		var i, name, data,
+			elem = this[ 0 ],
+			attrs = elem && elem.attributes;
+
+		// Gets all values
+		if ( key === undefined ) {
+			if ( this.length ) {
+				data = data_user.get( elem );
+
+				if ( elem.nodeType === 1 && !data_priv.get( elem, "hasDataAttrs" ) ) {
+					i = attrs.length;
+					while ( i-- ) {
+
+						// Support: IE11+
+						// The attrs elements can be null (#14894)
+						if ( attrs[ i ] ) {
+							name = attrs[ i ].name;
+							if ( name.indexOf( "data-" ) === 0 ) {
+								name = jQuery.camelCase( name.slice(5) );
+								dataAttr( elem, name, data[ name ] );
+							}
+						}
+					}
+					data_priv.set( elem, "hasDataAttrs", true );
+				}
+			}
+
+			return data;
+		}
+
+		// Sets multiple values
+		if ( typeof key === "object" ) {
+			return this.each(function() {
+				data_user.set( this, key );
+			});
+		}
+
+		return access( this, function( value ) {
+			var data,
+				camelKey = jQuery.camelCase( key );
+
+			// The calling jQuery object (element matches) is not empty
+			// (and therefore has an element appears at this[ 0 ]) and the
+			// `value` parameter was not undefined. An empty jQuery object
+			// will result in `undefined` for elem = this[ 0 ] which will
+			// throw an exception if an attempt to read a data cache is made.
+			if ( elem && value === undefined ) {
+				// Attempt to get data from the cache
+				// with the key as-is
+				data = data_user.get( elem, key );
+				if ( data !== undefined ) {
+					return data;
+				}
+
+				// Attempt to get data from the cache
+				// with the key camelized
+				data = data_user.get( elem, camelKey );
+				if ( data !== undefined ) {
+					return data;
+				}
+
+				// Attempt to "discover" the data in
+				// HTML5 custom data-* attrs
+				data = dataAttr( elem, camelKey, undefined );
+				if ( data !== undefined ) {
+					return data;
+				}
+
+				// We tried really hard, but the data doesn't exist.
+				return;
+			}
+
+			// Set the data...
+			this.each(function() {
+				// First, attempt to store a copy or reference of any
+				// data that might've been store with a camelCased key.
+				var data = data_user.get( this, camelKey );
+
+				// For HTML5 data-* attribute interop, we have to
+				// store property names with dashes in a camelCase form.
+				// This might not apply to all properties...*
+				data_user.set( this, camelKey, value );
+
+				// *... In the case of properties that might _actually_
+				// have dashes, we need to also store a copy of that
+				// unchanged property.
+				if ( key.indexOf("-") !== -1 && data !== undefined ) {
+					data_user.set( this, key, value );
+				}
+			});
+		}, null, value, arguments.length > 1, null, true );
+	},
+
+	removeData: function( key ) {
+		return this.each(function() {
+			data_user.remove( this, key );
+		});
+	}
+});
+
+
+jQuery.extend({
+	queue: function( elem, type, data ) {
+		var queue;
+
+		if ( elem ) {
+			type = ( type || "fx" ) + "queue";
+			queue = data_priv.get( elem, type );
+
+			// Speed up dequeue by getting out quickly if this is just a lookup
+			if ( data ) {
+				if ( !queue || jQuery.isArray( data ) ) {
+					queue = data_priv.access( elem, type, jQuery.makeArray(data) );
+				} else {
+					queue.push( data );
+				}
+			}
+			return queue || [];
+		}
+	},
+
+	dequeue: function( elem, type ) {
+		type = type || "fx";
+
+		var queue = jQuery.queue( elem, type ),
+			startLength = queue.length,
+			fn = queue.shift(),
+			hooks = jQuery._queueHooks( elem, type ),
+			next = function() {
+				jQuery.dequeue( elem, type );
+			};
+
+		// If the fx queue is dequeued, always remove the progress sentinel
+		if ( fn === "inprogress" ) {
+			fn = queue.shift();
+			startLength--;
+		}
+
+		if ( fn ) {
+
+			// Add a progress sentinel to prevent the fx queue from being
+			// automatically dequeued
+			if ( type === "fx" ) {
+				queue.unshift( "inprogress" );
+			}
+
+			// Clear up the last queue stop function
+			delete hooks.stop;
+			fn.call( elem, next, hooks );
+		}
+
+		if ( !startLength && hooks ) {
+			hooks.empty.fire();
+		}
+	},
+
+	// Not public - generate a queueHooks object, or return the current one
+	_queueHooks: function( elem, type ) {
+		var key = type + "queueHooks";
+		return data_priv.get( elem, key ) || data_priv.access( elem, key, {
+			empty: jQuery.Callbacks("once memory").add(function() {
+				data_priv.remove( elem, [ type + "queue", key ] );
+			})
+		});
+	}
+});
+
+jQuery.fn.extend({
+	queue: function( type, data ) {
+		var setter = 2;
+
+		if ( typeof type !== "string" ) {
+			data = type;
+			type = "fx";
+			setter--;
+		}
+
+		if ( arguments.length < setter ) {
+			return jQuery.queue( this[0], type );
+		}
+
+		return data === undefined ?
+			this :
+			this.each(function() {
+				var queue = jQuery.queue( this, type, data );
+
+				// Ensure a hooks for this queue
+				jQuery._queueHooks( this, type );
+
+				if ( type === "fx" && queue[0] !== "inprogress" ) {
+					jQuery.dequeue( this, type );
+				}
+			});
+	},
+	dequeue: function( type ) {
+		return this.each(function() {
+			jQuery.dequeue( this, type );
+		});
+	},
+	clearQueue: function( type ) {
+		return this.queue( type || "fx", [] );
+	},
+	// Get a promise resolved when queues of a certain type
+	// are emptied (fx is the type by default)
+	promise: function( type, obj ) {
+		var tmp,
+			count = 1,
+			defer = jQuery.Deferred(),
+			elements = this,
+			i = this.length,
+			resolve = function() {
+				if ( !( --count ) ) {
+					defer.resolveWith( elements, [ elements ] );
+				}
+			};
+
+		if ( typeof type !== "string" ) {
+			obj = type;
+			type = undefined;
+		}
+		type = type || "fx";
+
+		while ( i-- ) {
+			tmp = data_priv.get( elements[ i ], type + "queueHooks" );
+			if ( tmp && tmp.empty ) {
+				count++;
+				tmp.empty.add( resolve );
+			}
+		}
+		resolve();
+		return defer.promise( obj );
+	}
+});
+var pnum = (/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/).source;
+
+var cssExpand = [ "Top", "Right", "Bottom", "Left" ];
+
+var isHidden = function( elem, el ) {
+		// isHidden might be called from jQuery#filter function;
+		// in that case, element will be second argument
+		elem = el || elem;
+		return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
+	};
+
+var rcheckableType = (/^(?:checkbox|radio)$/i);
+
+
+
+(function() {
+	var fragment = document.createDocumentFragment(),
+		div = fragment.appendChild( document.createElement( "div" ) ),
+		input = document.createElement( "input" );
+
+	// Support: Safari<=5.1
+	// Check state lost if the name is set (#11217)
+	// Support: Windows Web Apps (WWA)
+	// `name` and `type` must use .setAttribute for WWA (#14901)
+	input.setAttribute( "type", "radio" );
+	input.setAttribute( "checked", "checked" );
+	input.setAttribute( "name", "t" );
+
+	div.appendChild( input );
+
+	// Support: Safari<=5.1, Android<4.2
+	// Older WebKit doesn't clone checked state correctly in fragments
+	support.checkClone = div.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+	// Support: IE<=11+
+	// Make sure textarea (and checkbox) defaultValue is properly cloned
+	div.innerHTML = "<textarea>x</textarea>";
+	support.noCloneChecked = !!div.cloneNode( true ).lastChild.defaultValue;
+})();
+var strundefined = typeof undefined;
+
+
+
+support.focusinBubbles = "onfocusin" in window;
+
+
+var
+	rkeyEvent = /^key/,
+	rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/,
+	rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+	rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
+
+function returnTrue() {
+	return true;
+}
+
+function returnFalse() {
+	return false;
+}
+
+function safeActiveElement() {
+	try {
+		return document.activeElement;
+	} catch ( err ) { }
+}
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+	global: {},
+
+	add: function( elem, types, handler, data, selector ) {
+
+		var handleObjIn, eventHandle, tmp,
+			events, t, handleObj,
+			special, handlers, type, namespaces, origType,
+			elemData = data_priv.get( elem );
+
+		// Don't attach events to noData or text/comment nodes (but allow plain objects)
+		if ( !elemData ) {
+			return;
+		}
+
+		// Caller can pass in an object of custom data in lieu of the handler
+		if ( handler.handler ) {
+			handleObjIn = handler;
+			handler = handleObjIn.handler;
+			selector = handleObjIn.selector;
+		}
+
+		// Make sure that the handler has a unique ID, used to find/remove it later
+		if ( !handler.guid ) {
+			handler.guid = jQuery.guid++;
+		}
+
+		// Init the element's event structure and main handler, if this is the first
+		if ( !(events = elemData.events) ) {
+			events = elemData.events = {};
+		}
+		if ( !(eventHandle = elemData.handle) ) {
+			eventHandle = elemData.handle = function( e ) {
+				// Discard the second event of a jQuery.event.trigger() and
+				// when an event is called after a page has unloaded
+				return typeof jQuery !== strundefined && jQuery.event.triggered !== e.type ?
+					jQuery.event.dispatch.apply( elem, arguments ) : undefined;
+			};
+		}
+
+		// Handle multiple events separated by a space
+		types = ( types || "" ).match( rnotwhite ) || [ "" ];
+		t = types.length;
+		while ( t-- ) {
+			tmp = rtypenamespace.exec( types[t] ) || [];
+			type = origType = tmp[1];
+			namespaces = ( tmp[2] || "" ).split( "." ).sort();
+
+			// There *must* be a type, no attaching namespace-only handlers
+			if ( !type ) {
+				continue;
+			}
+
+			// If event changes its type, use the special event handlers for the changed type
+			special = jQuery.event.special[ type ] || {};
+
+			// If selector defined, determine special event api type, otherwise given type
+			type = ( selector ? special.delegateType : special.bindType ) || type;
+
+			// Update special based on newly reset type
+			special = jQuery.event.special[ type ] || {};
+
+			// handleObj is passed to all event handlers
+			handleObj = jQuery.extend({
+				type: type,
+				origType: origType,
+				data: data,
+				handler: handler,
+				guid: handler.guid,
+				selector: selector,
+				needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+				namespace: namespaces.join(".")
+			}, handleObjIn );
+
+			// Init the event handler queue if we're the first
+			if ( !(handlers = events[ type ]) ) {
+				handlers = events[ type ] = [];
+				handlers.delegateCount = 0;
+
+				// Only use addEventListener if the special events handler returns false
+				if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+					if ( elem.addEventListener ) {
+						elem.addEventListener( type, eventHandle, false );
+					}
+				}
+			}
+
+			if ( special.add ) {
+				special.add.call( elem, handleObj );
+
+				if ( !handleObj.handler.guid ) {
+					handleObj.handler.guid = handler.guid;
+				}
+			}
+
+			// Add to the element's handler list, delegates in front
+			if ( selector ) {
+				handlers.splice( handlers.delegateCount++, 0, handleObj );
+			} else {
+				handlers.push( handleObj );
+			}
+
+			// Keep track of which events have ever been used, for event optimization
+			jQuery.event.global[ type ] = true;
+		}
+
+	},
+
+	// Detach an event or set of events from an element
+	remove: function( elem, types, handler, selector, mappedTypes ) {
+
+		var j, origCount, tmp,
+			events, t, handleObj,
+			special, handlers, type, namespaces, origType,
+			elemData = data_priv.hasData( elem ) && data_priv.get( elem );
+
+		if ( !elemData || !(events = elemData.events) ) {
+			return;
+		}
+
+		// Once for each type.namespace in types; type may be omitted
+		types = ( types || "" ).match( rnotwhite ) || [ "" ];
+		t = types.length;
+		while ( t-- ) {
+			tmp = rtypenamespace.exec( types[t] ) || [];
+			type = origType = tmp[1];
+			namespaces = ( tmp[2] || "" ).split( "." ).sort();
+
+			// Unbind all events (on this namespace, if provided) for the element
+			if ( !type ) {
+				for ( type in events ) {
+					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+				}
+				continue;
+			}
+
+			special = jQuery.event.special[ type ] || {};
+			type = ( selector ? special.delegateType : special.bindType ) || type;
+			handlers = events[ type ] || [];
+			tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );
+
+			// Remove matching events
+			origCount = j = handlers.length;
+			while ( j-- ) {
+				handleObj = handlers[ j ];
+
+				if ( ( mappedTypes || origType === handleObj.origType ) &&
+					( !handler || handler.guid === handleObj.guid ) &&
+					( !tmp || tmp.test( handleObj.namespace ) ) &&
+					( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+					handlers.splice( j, 1 );
+
+					if ( handleObj.selector ) {
+						handlers.delegateCount--;
+					}
+					if ( special.remove ) {
+						special.remove.call( elem, handleObj );
+					}
+				}
+			}
+
+			// Remove generic event handler if we removed something and no more handlers exist
+			// (avoids potential for endless recursion during removal of special event handlers)
+			if ( origCount && !handlers.length ) {
+				if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+					jQuery.removeEvent( elem, type, elemData.handle );
+				}
+
+				delete events[ type ];
+			}
+		}
+
+		// Remove the expando if it's no longer used
+		if ( jQuery.isEmptyObject( events ) ) {
+			delete elemData.handle;
+			data_priv.remove( elem, "events" );
+		}
+	},
+
+	trigger: function( event, data, elem, onlyHandlers ) {
+
+		var i, cur, tmp, bubbleType, ontype, handle, special,
+			eventPath = [ elem || document ],
+			type = hasOwn.call( event, "type" ) ? event.type : event,
+			namespaces = hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];
+
+		cur = tmp = elem = elem || document;
+
+		// Don't do events on text and comment nodes
+		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
+			return;
+		}
+
+		// focus/blur morphs to focusin/out; ensure we're not firing them right now
+		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+			return;
+		}
+
+		if ( type.indexOf(".") >= 0 ) {
+			// Namespaced trigger; create a regexp to match event type in handle()
+			namespaces = type.split(".");
+			type = namespaces.shift();
+			namespaces.sort();
+		}
+		ontype = type.indexOf(":") < 0 && "on" + type;
+
+		// Caller can pass in a jQuery.Event object, Object, or just an event type string
+		event = event[ jQuery.expando ] ?
+			event :
+			new jQuery.Event( type, typeof event === "object" && event );
+
+		// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
+		event.isTrigger = onlyHandlers ? 2 : 3;
+		event.namespace = namespaces.join(".");
+		event.namespace_re = event.namespace ?
+			new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
+			null;
+
+		// Clean up the event in case it is being reused
+		event.result = undefined;
+		if ( !event.target ) {
+			event.target = elem;
+		}
+
+		// Clone any incoming data and prepend the event, creating the handler arg list
+		data = data == null ?
+			[ event ] :
+			jQuery.makeArray( data, [ event ] );
+
+		// Allow special events to draw outside the lines
+		special = jQuery.event.special[ type ] || {};
+		if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
+			return;
+		}
+
+		// Determine event propagation path in advance, per W3C events spec (#9951)
+		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+			bubbleType = special.delegateType || type;
+			if ( !rfocusMorph.test( bubbleType + type ) ) {
+				cur = cur.parentNode;
+			}
+			for ( ; cur; cur = cur.parentNode ) {
+				eventPath.push( cur );
+				tmp = cur;
+			}
+
+			// Only add window if we got to document (e.g., not plain obj or detached DOM)
+			if ( tmp === (elem.ownerDocument || document) ) {
+				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
+			}
+		}
+
+		// Fire handlers on the event path
+		i = 0;
+		while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
+
+			event.type = i > 1 ?
+				bubbleType :
+				special.bindType || type;
+
+			// jQuery handler
+			handle = ( data_priv.get( cur, "events" ) || {} )[ event.type ] && data_priv.get( cur, "handle" );
+			if ( handle ) {
+				handle.apply( cur, data );
+			}
+
+			// Native handler
+			handle = ontype && cur[ ontype ];
+			if ( handle && handle.apply && jQuery.acceptData( cur ) ) {
+				event.result = handle.apply( cur, data );
+				if ( event.result === false ) {
+					event.preventDefault();
+				}
+			}
+		}
+		event.type = type;
+
+		// If nobody prevented the default action, do it now
+		if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+			if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
+				jQuery.acceptData( elem ) ) {
+
+				// Call a native DOM method on the target with the same name name as the event.
+				// Don't do default actions on window, that's where global variables be (#6170)
+				if ( ontype && jQuery.isFunction( elem[ type ] ) && !jQuery.isWindow( elem ) ) {
+
+					// Don't re-trigger an onFOO event when we call its FOO() method
+					tmp = elem[ ontype ];
+
+					if ( tmp ) {
+						elem[ ontype ] = null;
+					}
+
+					// Prevent re-triggering of the same event, since we already bubbled it above
+					jQuery.event.triggered = type;
+					elem[ type ]();
+					jQuery.event.triggered = undefined;
+
+					if ( tmp ) {
+						elem[ ontype ] = tmp;
+					}
+				}
+			}
+		}
+
+		return event.result;
+	},
+
+	dispatch: function( event ) {
+
+		// Make a writable jQuery.Event from the native event object
+		event = jQuery.event.fix( event );
+
+		var i, j, ret, matched, handleObj,
+			handlerQueue = [],
+			args = slice.call( arguments ),
+			handlers = ( data_priv.get( this, "events" ) || {} )[ event.type ] || [],
+			special = jQuery.event.special[ event.type ] || {};
+
+		// Use the fix-ed jQuery.Event rather than the (read-only) native event
+		args[0] = event;
+		event.delegateTarget = this;
+
+		// Call the preDispatch hook for the mapped type, and let it bail if desired
+		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+			return;
+		}
+
+		// Determine handlers
+		handlerQueue = jQuery.event.handlers.call( this, event, handlers );
+
+		// Run delegates first; they may want to stop propagation beneath us
+		i = 0;
+		while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
+			event.currentTarget = matched.elem;
+
+			j = 0;
+			while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {
+
+				// Triggered event must either 1) have no namespace, or 2) have namespace(s)
+				// a subset or equal to those in the bound event (both can have no namespace).
+				if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {
+
+					event.handleObj = handleObj;
+					event.data = handleObj.data;
+
+					ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+							.apply( matched.elem, args );
+
+					if ( ret !== undefined ) {
+						if ( (event.result = ret) === false ) {
+							event.preventDefault();
+							event.stopPropagation();
+						}
+					}
+				}
+			}
+		}
+
+		// Call the postDispatch hook for the mapped type
+		if ( special.postDispatch ) {
+			special.postDispatch.call( this, event );
+		}
+
+		return event.result;
+	},
+
+	handlers: function( event, handlers ) {
+		var i, matches, sel, handleObj,
+			handlerQueue = [],
+			delegateCount = handlers.delegateCount,
+			cur = event.target;
+
+		// Find delegate handlers
+		// Black-hole SVG <use> instance trees (#13180)
+		// Avoid non-left-click bubbling in Firefox (#3861)
+		if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {
+
+			for ( ; cur !== this; cur = cur.parentNode || this ) {
+
+				// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
+				if ( cur.disabled !== true || event.type !== "click" ) {
+					matches = [];
+					for ( i = 0; i < delegateCount; i++ ) {
+						handleObj = handlers[ i ];
+
+						// Don't conflict with Object.prototype properties (#13203)
+						sel = handleObj.selector + " ";
+
+						if ( matches[ sel ] === undefined ) {
+							matches[ sel ] = handleObj.needsContext ?
+								jQuery( sel, this ).index( cur ) >= 0 :
+								jQuery.find( sel, this, null, [ cur ] ).length;
+						}
+						if ( matches[ sel ] ) {
+							matches.push( handleObj );
+						}
+					}
+					if ( matches.length ) {
+						handlerQueue.push({ elem: cur, handlers: matches });
+					}
+				}
+			}
+		}
+
+		// Add the remaining (directly-bound) handlers
+		if ( delegateCount < handlers.length ) {
+			handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
+		}
+
+		return handlerQueue;
+	},
+
+	// Includes some event props shared by KeyEvent and MouseEvent
+	props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+	fixHooks: {},
+
+	keyHooks: {
+		props: "char charCode key keyCode".split(" "),
+		filter: function( event, original ) {
+
+			// Add which for key events
+			if ( event.which == null ) {
+				event.which = original.charCode != null ? original.charCode : original.keyCode;
+			}
+
+			return event;
+		}
+	},
+
+	mouseHooks: {
+		props: "button buttons clientX clientY offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+		filter: function( event, original ) {
+			var eventDoc, doc, body,
+				button = original.button;
+
+			// Calculate pageX/Y if missing and clientX/Y available
+			if ( event.pageX == null && original.clientX != null ) {
+				eventDoc = event.target.ownerDocument || document;
+				doc = eventDoc.documentElement;
+				body = eventDoc.body;
+
+				event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+				event.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );
+			}
+
+			// Add which for click: 1 === left; 2 === middle; 3 === right
+			// Note: button is not normalized, so don't use it
+			if ( !event.which && button !== undefined ) {
+				event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+			}
+
+			return event;
+		}
+	},
+
+	fix: function( event ) {
+		if ( event[ jQuery.expando ] ) {
+			return event;
+		}
+
+		// Create a writable copy of the event object and normalize some properties
+		var i, prop, copy,
+			type = event.type,
+			originalEvent = event,
+			fixHook = this.fixHooks[ type ];
+
+		if ( !fixHook ) {
+			this.fixHooks[ type ] = fixHook =
+				rmouseEvent.test( type ) ? this.mouseHooks :
+				rkeyEvent.test( type ) ? this.keyHooks :
+				{};
+		}
+		copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+		event = new jQuery.Event( originalEvent );
+
+		i = copy.length;
+		while ( i-- ) {
+			prop = copy[ i ];
+			event[ prop ] = originalEvent[ prop ];
+		}
+
+		// Support: Cordova 2.5 (WebKit) (#13255)
+		// All events should have a target; Cordova deviceready doesn't
+		if ( !event.target ) {
+			event.target = document;
+		}
+
+		// Support: Safari 6.0+, Chrome<28
+		// Target should not be a text node (#504, #13143)
+		if ( event.target.nodeType === 3 ) {
+			event.target = event.target.parentNode;
+		}
+
+		return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
+	},
+
+	special: {
+		load: {
+			// Prevent triggered image.load events from bubbling to window.load
+			noBubble: true
+		},
+		focus: {
+			// Fire native event if possible so blur/focus sequence is correct
+			trigger: function() {
+				if ( this !== safeActiveElement() && this.focus ) {
+					this.focus();
+					return false;
+				}
+			},
+			delegateType: "focusin"
+		},
+		blur: {
+			trigger: function() {
+				if ( this === safeActiveElement() && this.blur ) {
+					this.blur();
+					return false;
+				}
+			},
+			delegateType: "focusout"
+		},
+		click: {
+			// For checkbox, fire native event so checked state will be right
+			trigger: function() {
+				if ( this.type === "checkbox" && this.click && jQuery.nodeName( this, "input" ) ) {
+					this.click();
+					return false;
+				}
+			},
+
+			// For cross-browser consistency, don't fire native .click() on links
+			_default: function( event ) {
+				return jQuery.nodeName( event.target, "a" );
+			}
+		},
+
+		beforeunload: {
+			postDispatch: function( event ) {
+
+				// Support: Firefox 20+
+				// Firefox doesn't alert if the returnValue field is not set.
+				if ( event.result !== undefined && event.originalEvent ) {
+					event.originalEvent.returnValue = event.result;
+				}
+			}
+		}
+	},
+
+	simulate: function( type, elem, event, bubble ) {
+		// Piggyback on a donor event to simulate a different one.
+		// Fake originalEvent to avoid donor's stopPropagation, but if the
+		// simulated event prevents default then we do the same on the donor.
+		var e = jQuery.extend(
+			new jQuery.Event(),
+			event,
+			{
+				type: type,
+				isSimulated: true,
+				originalEvent: {}
+			}
+		);
+		if ( bubble ) {
+			jQuery.event.trigger( e, null, elem );
+		} else {
+			jQuery.event.dispatch.call( elem, e );
+		}
+		if ( e.isDefaultPrevented() ) {
+			event.preventDefault();
+		}
+	}
+};
+
+jQuery.removeEvent = function( elem, type, handle ) {
+	if ( elem.removeEventListener ) {
+		elem.removeEventListener( type, handle, false );
+	}
+};
+
+jQuery.Event = function( src, props ) {
+	// Allow instantiation without the 'new' keyword
+	if ( !(this instanceof jQuery.Event) ) {
+		return new jQuery.Event( src, props );
+	}
+
+	// Event object
+	if ( src && src.type ) {
+		this.originalEvent = src;
+		this.type = src.type;
+
+		// Events bubbling up the document may have been marked as prevented
+		// by a handler lower down the tree; reflect the correct value.
+		this.isDefaultPrevented = src.defaultPrevented ||
+				src.defaultPrevented === undefined &&
+				// Support: Android<4.0
+				src.returnValue === false ?
+			returnTrue :
+			returnFalse;
+
+	// Event type
+	} else {
+		this.type = src;
+	}
+
+	// Put explicitly provided properties onto the event object
+	if ( props ) {
+		jQuery.extend( this, props );
+	}
+
+	// Create a timestamp if incoming event doesn't have one
+	this.timeStamp = src && src.timeStamp || jQuery.now();
+
+	// Mark it as fixed
+	this[ jQuery.expando ] = true;
+};
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+	isDefaultPrevented: returnFalse,
+	isPropagationStopped: returnFalse,
+	isImmediatePropagationStopped: returnFalse,
+
+	preventDefault: function() {
+		var e = this.originalEvent;
+
+		this.isDefaultPrevented = returnTrue;
+
+		if ( e && e.preventDefault ) {
+			e.preventDefault();
+		}
+	},
+	stopPropagation: function() {
+		var e = this.originalEvent;
+
+		this.isPropagationStopped = returnTrue;
+
+		if ( e && e.stopPropagation ) {
+			e.stopPropagation();
+		}
+	},
+	stopImmediatePropagation: function() {
+		var e = this.originalEvent;
+
+		this.isImmediatePropagationStopped = returnTrue;
+
+		if ( e && e.stopImmediatePropagation ) {
+			e.stopImmediatePropagation();
+		}
+
+		this.stopPropagation();
+	}
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+// Support: Chrome 15+
+jQuery.each({
+	mouseenter: "mouseover",
+	mouseleave: "mouseout",
+	pointerenter: "pointerover",
+	pointerleave: "pointerout"
+}, function( orig, fix ) {
+	jQuery.event.special[ orig ] = {
+		delegateType: fix,
+		bindType: fix,
+
+		handle: function( event ) {
+			var ret,
+				target = this,
+				related = event.relatedTarget,
+				handleObj = event.handleObj;
+
+			// For mousenter/leave call the handler if related is outside the target.
+			// NB: No relatedTarget if the mouse left/entered the browser window
+			if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+				event.type = handleObj.origType;
+				ret = handleObj.handler.apply( this, arguments );
+				event.type = fix;
+			}
+			return ret;
+		}
+	};
+});
+
+// Support: Firefox, Chrome, Safari
+// Create "bubbling" focus and blur events
+if ( !support.focusinBubbles ) {
+	jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+		// Attach a single capturing handler on the document while someone wants focusin/focusout
+		var handler = function( event ) {
+				jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+			};
+
+		jQuery.event.special[ fix ] = {
+			setup: function() {
+				var doc = this.ownerDocument || this,
+					attaches = data_priv.access( doc, fix );
+
+				if ( !attaches ) {
+					doc.addEventListener( orig, handler, true );
+				}
+				data_priv.access( doc, fix, ( attaches || 0 ) + 1 );
+			},
+			teardown: function() {
+				var doc = this.ownerDocument || this,
+					attaches = data_priv.access( doc, fix ) - 1;
+
+				if ( !attaches ) {
+					doc.removeEventListener( orig, handler, true );
+					data_priv.remove( doc, fix );
+
+				} else {
+					data_priv.access( doc, fix, attaches );
+				}
+			}
+		};
+	});
+}
+
+jQuery.fn.extend({
+
+	on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+		var origFn, type;
+
+		// Types can be a map of types/handlers
+		if ( typeof types === "object" ) {
+			// ( types-Object, selector, data )
+			if ( typeof selector !== "string" ) {
+				// ( types-Object, data )
+				data = data || selector;
+				selector = undefined;
+			}
+			for ( type in types ) {
+				this.on( type, selector, data, types[ type ], one );
+			}
+			return this;
+		}
+
+		if ( data == null && fn == null ) {
+			// ( types, fn )
+			fn = selector;
+			data = selector = undefined;
+		} else if ( fn == null ) {
+			if ( typeof selector === "string" ) {
+				// ( types, selector, fn )
+				fn = data;
+				data = undefined;
+			} else {
+				// ( types, data, fn )
+				fn = data;
+				data = selector;
+				selector = undefined;
+			}
+		}
+		if ( fn === false ) {
+			fn = returnFalse;
+		} else if ( !fn ) {
+			return this;
+		}
+
+		if ( one === 1 ) {
+			origFn = fn;
+			fn = function( event ) {
+				// Can use an empty set, since event contains the info
+				jQuery().off( event );
+				return origFn.apply( this, arguments );
+			};
+			// Use same guid so caller can remove using origFn
+			fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+		}
+		return this.each( function() {
+			jQuery.event.add( this, types, fn, data, selector );
+		});
+	},
+	one: function( types, selector, data, fn ) {
+		return this.on( types, selector, data, fn, 1 );
+	},
+	off: function( types, selector, fn ) {
+		var handleObj, type;
+		if ( types && types.preventDefault && types.handleObj ) {
+			// ( event )  dispatched jQuery.Event
+			handleObj = types.handleObj;
+			jQuery( types.delegateTarget ).off(
+				handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
+				handleObj.selector,
+				handleObj.handler
+			);
+			return this;
+		}
+		if ( typeof types === "object" ) {
+			// ( types-object [, selector] )
+			for ( type in types ) {
+				this.off( type, selector, types[ type ] );
+			}
+			return this;
+		}
+		if ( selector === false || typeof selector === "function" ) {
+			// ( types [, fn] )
+			fn = selector;
+			selector = undefined;
+		}
+		if ( fn === false ) {
+			fn = returnFalse;
+		}
+		return this.each(function() {
+			jQuery.event.remove( this, types, fn, selector );
+		});
+	},
+
+	trigger: function( type, data ) {
+		return this.each(function() {
+			jQuery.event.trigger( type, data, this );
+		});
+	},
+	triggerHandler: function( type, data ) {
+		var elem = this[0];
+		if ( elem ) {
+			return jQuery.event.trigger( type, data, elem, true );
+		}
+	}
+});
+
+
+var
+	rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
+	rtagName = /<([\w:]+)/,
+	rhtml = /<|&#?\w+;/,
+	rnoInnerhtml = /<(?:script|style|link)/i,
+	// checked="checked" or checked
+	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+	rscriptType = /^$|\/(?:java|ecma)script/i,
+	rscriptTypeMasked = /^true\/(.*)/,
+	rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,
+
+	// We have to close these tags to support XHTML (#13200)
+	wrapMap = {
+
+		// Support: IE9
+		option: [ 1, "<select multiple='multiple'>", "</select>" ],
+
+		thead: [ 1, "<table>", "</table>" ],
+		col: [ 2, "<table><colgroup>", "</colgroup></table>" ],
+		tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+		td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+
+		_default: [ 0, "", "" ]
+	};
+
+// Support: IE9
+wrapMap.optgroup = wrapMap.option;
+
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// Support: 1.x compatibility
+// Manipulating tables requires a tbody
+function manipulationTarget( elem, content ) {
+	return jQuery.nodeName( elem, "table" ) &&
+		jQuery.nodeName( content.nodeType !== 11 ? content : content.firstChild, "tr" ) ?
+
+		elem.getElementsByTagName("tbody")[0] ||
+			elem.appendChild( elem.ownerDocument.createElement("tbody") ) :
+		elem;
+}
+
+// Replace/restore the type attribute of script elements for safe DOM manipulation
+function disableScript( elem ) {
+	elem.type = (elem.getAttribute("type") !== null) + "/" + elem.type;
+	return elem;
+}
+function restoreScript( elem ) {
+	var match = rscriptTypeMasked.exec( elem.type );
+
+	if ( match ) {
+		elem.type = match[ 1 ];
+	} else {
+		elem.removeAttribute("type");
+	}
+
+	return elem;
+}
+
+// Mark scripts as having already been evaluated
+function setGlobalEval( elems, refElements ) {
+	var i = 0,
+		l = elems.length;
+
+	for ( ; i < l; i++ ) {
+		data_priv.set(
+			elems[ i ], "globalEval", !refElements || data_priv.get( refElements[ i ], "globalEval" )
+		);
+	}
+}
+
+function cloneCopyEvent( src, dest ) {
+	var i, l, type, pdataOld, pdataCur, udataOld, udataCur, events;
+
+	if ( dest.nodeType !== 1 ) {
+		return;
+	}
+
+	// 1. Copy private data: events, handlers, etc.
+	if ( data_priv.hasData( src ) ) {
+		pdataOld = data_priv.access( src );
+		pdataCur = data_priv.set( dest, pdataOld );
+		events = pdataOld.events;
+
+		if ( events ) {
+			delete pdataCur.handle;
+			pdataCur.events = {};
+
+			for ( type in events ) {
+				for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+					jQuery.event.add( dest, type, events[ type ][ i ] );
+				}
+			}
+		}
+	}
+
+	// 2. Copy user data
+	if ( data_user.hasData( src ) ) {
+		udataOld = data_user.access( src );
+		udataCur = jQuery.extend( {}, udataOld );
+
+		data_user.set( dest, udataCur );
+	}
+}
+
+function getAll( context, tag ) {
+	var ret = context.getElementsByTagName ? context.getElementsByTagName( tag || "*" ) :
+			context.querySelectorAll ? context.querySelectorAll( tag || "*" ) :
+			[];
+
+	return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
+		jQuery.merge( [ context ], ret ) :
+		ret;
+}
+
+// Fix IE bugs, see support tests
+function fixInput( src, dest ) {
+	var nodeName = dest.nodeName.toLowerCase();
+
+	// Fails to persist the checked state of a cloned checkbox or radio button.
+	if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
+		dest.checked = src.checked;
+
+	// Fails to return the selected option to the default selected state when cloning options
+	} else if ( nodeName === "input" || nodeName === "textarea" ) {
+		dest.defaultValue = src.defaultValue;
+	}
+}
+
+jQuery.extend({
+	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+		var i, l, srcElements, destElements,
+			clone = elem.cloneNode( true ),
+			inPage = jQuery.contains( elem.ownerDocument, elem );
+
+		// Fix IE cloning issues
+		if ( !support.noCloneChecked && ( elem.nodeType === 1 || elem.nodeType === 11 ) &&
+				!jQuery.isXMLDoc( elem ) ) {
+
+			// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
+			destElements = getAll( clone );
+			srcElements = getAll( elem );
+
+			for ( i = 0, l = srcElements.length; i < l; i++ ) {
+				fixInput( srcElements[ i ], destElements[ i ] );
+			}
+		}
+
+		// Copy the events from the original to the clone
+		if ( dataAndEvents ) {
+			if ( deepDataAndEvents ) {
+				srcElements = srcElements || getAll( elem );
+				destElements = destElements || getAll( clone );
+
+				for ( i = 0, l = srcElements.length; i < l; i++ ) {
+					cloneCopyEvent( srcElements[ i ], destElements[ i ] );
+				}
+			} else {
+				cloneCopyEvent( elem, clone );
+			}
+		}
+
+		// Preserve script evaluation history
+		destElements = getAll( clone, "script" );
+		if ( destElements.length > 0 ) {
+			setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
+		}
+
+		// Return the cloned set
+		return clone;
+	},
+
+	buildFragment: function( elems, context, scripts, selection ) {
+		var elem, tmp, tag, wrap, contains, j,
+			fragment = context.createDocumentFragment(),
+			nodes = [],
+			i = 0,
+			l = elems.length;
+
+		for ( ; i < l; i++ ) {
+			elem = elems[ i ];
+
+			if ( elem || elem === 0 ) {
+
+				// Add nodes directly
+				if ( jQuery.type( elem ) === "object" ) {
+					// Support: QtWebKit, PhantomJS
+					// push.apply(_, arraylike) throws on ancient WebKit
+					jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
+
+				// Convert non-html into a text node
+				} else if ( !rhtml.test( elem ) ) {
+					nodes.push( context.createTextNode( elem ) );
+
+				// Convert html into DOM nodes
+				} else {
+					tmp = tmp || fragment.appendChild( context.createElement("div") );
+
+					// Deserialize a standard representation
+					tag = ( rtagName.exec( elem ) || [ "", "" ] )[ 1 ].toLowerCase();
+					wrap = wrapMap[ tag ] || wrapMap._default;
+					tmp.innerHTML = wrap[ 1 ] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[ 2 ];
+
+					// Descend through wrappers to the right content
+					j = wrap[ 0 ];
+					while ( j-- ) {
+						tmp = tmp.lastChild;
+					}
+
+					// Support: QtWebKit, PhantomJS
+					// push.apply(_, arraylike) throws on ancient WebKit
+					jQuery.merge( nodes, tmp.childNodes );
+
+					// Remember the top-level container
+					tmp = fragment.firstChild;
+
+					// Ensure the created nodes are orphaned (#12392)
+					tmp.textContent = "";
+				}
+			}
+		}
+
+		// Remove wrapper from fragment
+		fragment.textContent = "";
+
+		i = 0;
+		while ( (elem = nodes[ i++ ]) ) {
+
+			// #4087 - If origin and destination elements are the same, and this is
+			// that element, do not do anything
+			if ( selection && jQuery.inArray( elem, selection ) !== -1 ) {
+				continue;
+			}
+
+			contains = jQuery.contains( elem.ownerDocument, elem );
+
+			// Append to fragment
+			tmp = getAll( fragment.appendChild( elem ), "script" );
+
+			// Preserve script evaluation history
+			if ( contains ) {
+				setGlobalEval( tmp );
+			}
+
+			// Capture executables
+			if ( scripts ) {
+				j = 0;
+				while ( (elem = tmp[ j++ ]) ) {
+					if ( rscriptType.test( elem.type || "" ) ) {
+						scripts.push( elem );
+					}
+				}
+			}
+		}
+
+		return fragment;
+	},
+
+	cleanData: function( elems ) {
+		var data, elem, type, key,
+			special = jQuery.event.special,
+			i = 0;
+
+		for ( ; (elem = elems[ i ]) !== undefined; i++ ) {
+			if ( jQuery.acceptData( elem ) ) {
+				key = elem[ data_priv.expando ];
+
+				if ( key && (data = data_priv.cache[ key ]) ) {
+					if ( data.events ) {
+						for ( type in data.events ) {
+							if ( special[ type ] ) {
+								jQuery.event.remove( elem, type );
+
+							// This is a shortcut to avoid jQuery.event.remove's overhead
+							} else {
+								jQuery.removeEvent( elem, type, data.handle );
+							}
+						}
+					}
+					if ( data_priv.cache[ key ] ) {
+						// Discard any remaining `private` data
+						delete data_priv.cache[ key ];
+					}
+				}
+			}
+			// Discard any remaining `user` data
+			delete data_user.cache[ elem[ data_user.expando ] ];
+		}
+	}
+});
+
+jQuery.fn.extend({
+	text: function( value ) {
+		return access( this, function( value ) {
+			return value === undefined ?
+				jQuery.text( this ) :
+				this.empty().each(function() {
+					if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+						this.textContent = value;
+					}
+				});
+		}, null, value, arguments.length );
+	},
+
+	append: function() {
+		return this.domManip( arguments, function( elem ) {
+			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+				var target = manipulationTarget( this, elem );
+				target.appendChild( elem );
+			}
+		});
+	},
+
+	prepend: function() {
+		return this.domManip( arguments, function( elem ) {
+			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
+				var target = manipulationTarget( this, elem );
+				target.insertBefore( elem, target.firstChild );
+			}
+		});
+	},
+
+	before: function() {
+		return this.domManip( arguments, function( elem ) {
+			if ( this.parentNode ) {
+				this.parentNode.insertBefore( elem, this );
+			}
+		});
+	},
+
+	after: function() {
+		return this.domManip( arguments, function( elem ) {
+			if ( this.parentNode ) {
+				this.parentNode.insertBefore( elem, this.nextSibling );
+			}
+		});
+	},
+
+	remove: function( selector, keepData /* Internal Use Only */ ) {
+		var elem,
+			elems = selector ? jQuery.filter( selector, this ) : this,
+			i = 0;
+
+		for ( ; (elem = elems[i]) != null; i++ ) {
+			if ( !keepData && elem.nodeType === 1 ) {
+				jQuery.cleanData( getAll( elem ) );
+			}
+
+			if ( elem.parentNode ) {
+				if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {
+					setGlobalEval( getAll( elem, "script" ) );
+				}
+				elem.parentNode.removeChild( elem );
+			}
+		}
+
+		return this;
+	},
+
+	empty: function() {
+		var elem,
+			i = 0;
+
+		for ( ; (elem = this[i]) != null; i++ ) {
+			if ( elem.nodeType === 1 ) {
+
+				// Prevent memory leaks
+				jQuery.cleanData( getAll( elem, false ) );
+
+				// Remove any remaining nodes
+				elem.textContent = "";
+			}
+		}
+
+		return this;
+	},
+
+	clone: function( dataAndEvents, deepDataAndEvents ) {
+		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+		return this.map(function() {
+			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+		});
+	},
+
+	html: function( value ) {
+		return access( this, function( value ) {
+			var elem = this[ 0 ] || {},
+				i = 0,
+				l = this.length;
+
+			if ( value === undefined && elem.nodeType === 1 ) {
+				return elem.innerHTML;
+			}
+
+			// See if we can take a shortcut and just use innerHTML
+			if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+				!wrapMap[ ( rtagName.exec( value ) || [ "", "" ] )[ 1 ].toLowerCase() ] ) {
+
+				value = value.replace( rxhtmlTag, "<$1></$2>" );
+
+				try {
+					for ( ; i < l; i++ ) {
+						elem = this[ i ] || {};
+
+						// Remove element nodes and prevent memory leaks
+						if ( elem.nodeType === 1 ) {
+							jQuery.cleanData( getAll( elem, false ) );
+							elem.innerHTML = value;
+						}
+					}
+
+					elem = 0;
+
+				// If using innerHTML throws an exception, use the fallback method
+				} catch( e ) {}
+			}
+
+			if ( elem ) {
+				this.empty().append( value );
+			}
+		}, null, value, arguments.length );
+	},
+
+	replaceWith: function() {
+		var arg = arguments[ 0 ];
+
+		// Make the changes, replacing each context element with the new content
+		this.domManip( arguments, function( elem ) {
+			arg = this.parentNode;
+
+			jQuery.cleanData( getAll( this ) );
+
+			if ( arg ) {
+				arg.replaceChild( elem, this );
+			}
+		});
+
+		// Force removal if there was no new content (e.g., from empty arguments)
+		return arg && (arg.length || arg.nodeType) ? this : this.remove();
+	},
+
+	detach: function( selector ) {
+		return this.remove( selector, true );
+	},
+
+	domManip: function( args, callback ) {
+
+		// Flatten any nested arrays
+		args = concat.apply( [], args );
+
+		var fragment, first, scripts, hasScripts, node, doc,
+			i = 0,
+			l = this.length,
+			set = this,
+			iNoClone = l - 1,
+			value = args[ 0 ],
+			isFunction = jQuery.isFunction( value );
+
+		// We can't cloneNode fragments that contain checked, in WebKit
+		if ( isFunction ||
+				( l > 1 && typeof value === "string" &&
+					!support.checkClone && rchecked.test( value ) ) ) {
+			return this.each(function( index ) {
+				var self = set.eq( index );
+				if ( isFunction ) {
+					args[ 0 ] = value.call( this, index, self.html() );
+				}
+				self.domManip( args, callback );
+			});
+		}
+
+		if ( l ) {
+			fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, this );
+			first = fragment.firstChild;
+
+			if ( fragment.childNodes.length === 1 ) {
+				fragment = first;
+			}
+
+			if ( first ) {
+				scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
+				hasScripts = scripts.length;
+
+				// Use the original fragment for the last item instead of the first because it can end up
+				// being emptied incorrectly in certain situations (#8070).
+				for ( ; i < l; i++ ) {
+					node = fragment;
+
+					if ( i !== iNoClone ) {
+						node = jQuery.clone( node, true, true );
+
+						// Keep references to cloned scripts for later restoration
+						if ( hasScripts ) {
+							// Support: QtWebKit
+							// jQuery.merge because push.apply(_, arraylike) throws
+							jQuery.merge( scripts, getAll( node, "script" ) );
+						}
+					}
+
+					callback.call( this[ i ], node, i );
+				}
+
+				if ( hasScripts ) {
+					doc = scripts[ scripts.length - 1 ].ownerDocument;
+
+					// Reenable scripts
+					jQuery.map( scripts, restoreScript );
+
+					// Evaluate executable scripts on first document insertion
+					for ( i = 0; i < hasScripts; i++ ) {
+						node = scripts[ i ];
+						if ( rscriptType.test( node.type || "" ) &&
+							!data_priv.access( node, "globalEval" ) && jQuery.contains( doc, node ) ) {
+
+							if ( node.src ) {
+								// Optional AJAX dependency, but won't run scripts if not present
+								if ( jQuery._evalUrl ) {
+									jQuery._evalUrl( node.src );
+								}
+							} else {
+								jQuery.globalEval( node.textContent.replace( rcleanScript, "" ) );
+							}
+						}
+					}
+				}
+			}
+		}
+
+		return this;
+	}
+});
+
+jQuery.each({
+	appendTo: "append",
+	prependTo: "prepend",
+	insertBefore: "before",
+	insertAfter: "after",
+	replaceAll: "replaceWith"
+}, function( name, original ) {
+	jQuery.fn[ name ] = function( selector ) {
+		var elems,
+			ret = [],
+			insert = jQuery( selector ),
+			last = insert.length - 1,
+			i = 0;
+
+		for ( ; i <= last; i++ ) {
+			elems = i === last ? this : this.clone( true );
+			jQuery( insert[ i ] )[ original ]( elems );
+
+			// Support: QtWebKit
+			// .get() because push.apply(_, arraylike) throws
+			push.apply( ret, elems.get() );
+		}
+
+		return this.pushStack( ret );
+	};
+});
+
+
+var iframe,
+	elemdisplay = {};
+
+/**
+ * Retrieve the actual display of a element
+ * @param {String} name nodeName of the element
+ * @param {Object} doc Document object
+ */
+// Called only from within defaultDisplay
+function actualDisplay( name, doc ) {
+	var style,
+		elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),
+
+		// getDefaultComputedStyle might be reliably used only on attached element
+		display = window.getDefaultComputedStyle && ( style = window.getDefaultComputedStyle( elem[ 0 ] ) ) ?
+
+			// Use of this method is a temporary fix (more like optimization) until something better comes along,
+			// since it was removed from specification and supported only in FF
+			style.display : jQuery.css( elem[ 0 ], "display" );
+
+	// We don't have any data stored on the element,
+	// so use "detach" method as fast way to get rid of the element
+	elem.detach();
+
+	return display;
+}
+
+/**
+ * Try to determine the default display value of an element
+ * @param {String} nodeName
+ */
+function defaultDisplay( nodeName ) {
+	var doc = document,
+		display = elemdisplay[ nodeName ];
+
+	if ( !display ) {
+		display = actualDisplay( nodeName, doc );
+
+		// If the simple way fails, read from inside an iframe
+		if ( display === "none" || !display ) {
+
+			// Use the already-created iframe if possible
+			iframe = (iframe || jQuery( "<iframe frameborder='0' width='0' height='0'/>" )).appendTo( doc.documentElement );
+
+			// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
+			doc = iframe[ 0 ].contentDocument;
+
+			// Support: IE
+			doc.write();
+			doc.close();
+
+			display = actualDisplay( nodeName, doc );
+			iframe.detach();
+		}
+
+		// Store the correct default display
+		elemdisplay[ nodeName ] = display;
+	}
+
+	return display;
+}
+var rmargin = (/^margin/);
+
+var rnumnonpx = new RegExp( "^(" + pnum + ")(?!px)[a-z%]+$", "i" );
+
+var getStyles = function( elem ) {
+		// Support: IE<=11+, Firefox<=30+ (#15098, #14150)
+		// IE throws on elements created in popups
+		// FF meanwhile throws on frame elements through "defaultView.getComputedStyle"
+		if ( elem.ownerDocument.defaultView.opener ) {
+			return elem.ownerDocument.defaultView.getComputedStyle( elem, null );
+		}
+
+		return window.getComputedStyle( elem, null );
+	};
+
+
+
+function curCSS( elem, name, computed ) {
+	var width, minWidth, maxWidth, ret,
+		style = elem.style;
+
+	computed = computed || getStyles( elem );
+
+	// Support: IE9
+	// getPropertyValue is only needed for .css('filter') (#12537)
+	if ( computed ) {
+		ret = computed.getPropertyValue( name ) || computed[ name ];
+	}
+
+	if ( computed ) {
+
+		if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
+			ret = jQuery.style( elem, name );
+		}
+
+		// Support: iOS < 6
+		// A tribute to the "awesome hack by Dean Edwards"
+		// iOS < 6 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
+		// this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
+		if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
+
+			// Remember the original values
+			width = style.width;
+			minWidth = style.minWidth;
+			maxWidth = style.maxWidth;
+
+			// Put in the new values to get a computed value out
+			style.minWidth = style.maxWidth = style.width = ret;
+			ret = computed.width;
+
+			// Revert the changed values
+			style.width = width;
+			style.minWidth = minWidth;
+			style.maxWidth = maxWidth;
+		}
+	}
+
+	return ret !== undefined ?
+		// Support: IE
+		// IE returns zIndex value as an integer.
+		ret + "" :
+		ret;
+}
+
+
+function addGetHookIf( conditionFn, hookFn ) {
+	// Define the hook, we'll check on the first run if it's really needed.
+	return {
+		get: function() {
+			if ( conditionFn() ) {
+				// Hook not needed (or it's not possible to use it due
+				// to missing dependency), remove it.
+				delete this.get;
+				return;
+			}
+
+			// Hook needed; redefine it so that the support test is not executed again.
+			return (this.get = hookFn).apply( this, arguments );
+		}
+	};
+}
+
+
+(function() {
+	var pixelPositionVal, boxSizingReliableVal,
+		docElem = document.documentElement,
+		container = document.createElement( "div" ),
+		div = document.createElement( "div" );
+
+	if ( !div.style ) {
+		return;
+	}
+
+	// Support: IE9-11+
+	// Style of cloned element affects source element cloned (#8908)
+	div.style.backgroundClip = "content-box";
+	div.cloneNode( true ).style.backgroundClip = "";
+	support.clearCloneStyle = div.style.backgroundClip === "content-box";
+
+	container.style.cssText = "border:0;width:0;height:0;top:0;left:-9999px;margin-top:1px;" +
+		"position:absolute";
+	container.appendChild( div );
+
+	// Executing both pixelPosition & boxSizingReliable tests require only one layout
+	// so they're executed at the same time to save the second computation.
+	function computePixelPositionAndBoxSizingReliable() {
+		div.style.cssText =
+			// Support: Firefox<29, Android 2.3
+			// Vendor-prefix box-sizing
+			"-webkit-box-sizing:border-box;-moz-box-sizing:border-box;" +
+			"box-sizing:border-box;display:block;margin-top:1%;top:1%;" +
+			"border:1px;padding:1px;width:4px;position:absolute";
+		div.innerHTML = "";
+		docElem.appendChild( container );
+
+		var divStyle = window.getComputedStyle( div, null );
+		pixelPositionVal = divStyle.top !== "1%";
+		boxSizingReliableVal = divStyle.width === "4px";
+
+		docElem.removeChild( container );
+	}
+
+	// Support: node.js jsdom
+	// Don't assume that getComputedStyle is a property of the global object
+	if ( window.getComputedStyle ) {
+		jQuery.extend( support, {
+			pixelPosition: function() {
+
+				// This test is executed only once but we still do memoizing
+				// since we can use the boxSizingReliable pre-computing.
+				// No need to check if the test was already performed, though.
+				computePixelPositionAndBoxSizingReliable();
+				return pixelPositionVal;
+			},
+			boxSizingReliable: function() {
+				if ( boxSizingReliableVal == null ) {
+					computePixelPositionAndBoxSizingReliable();
+				}
+				return boxSizingReliableVal;
+			},
+			reliableMarginRight: function() {
+
+				// Support: Android 2.3
+				// Check if div with explicit width and no margin-right incorrectly
+				// gets computed margin-right based on width of container. (#3333)
+				// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+				// This support function is only executed once so no memoizing is needed.
+				var ret,
+					marginDiv = div.appendChild( document.createElement( "div" ) );
+
+				// Reset CSS: box-sizing; display; margin; border; padding
+				marginDiv.style.cssText = div.style.cssText =
+					// Support: Firefox<29, Android 2.3
+					// Vendor-prefix box-sizing
+					"-webkit-box-sizing:content-box;-moz-box-sizing:content-box;" +
+					"box-sizing:content-box;display:block;margin:0;border:0;padding:0";
+				marginDiv.style.marginRight = marginDiv.style.width = "0";
+				div.style.width = "1px";
+				docElem.appendChild( container );
+
+				ret = !parseFloat( window.getComputedStyle( marginDiv, null ).marginRight );
+
+				docElem.removeChild( container );
+				div.removeChild( marginDiv );
+
+				return ret;
+			}
+		});
+	}
+})();
+
+
+// A method for quickly swapping in/out CSS properties to get correct calculations.
+jQuery.swap = function( elem, options, callback, args ) {
+	var ret, name,
+		old = {};
+
+	// Remember the old values, and insert the new ones
+	for ( name in options ) {
+		old[ name ] = elem.style[ name ];
+		elem.style[ name ] = options[ name ];
+	}
+
+	ret = callback.apply( elem, args || [] );
+
+	// Revert the old values
+	for ( name in options ) {
+		elem.style[ name ] = old[ name ];
+	}
+
+	return ret;
+};
+
+
+var
+	// Swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
+	// See here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
+	rdisplayswap = /^(none|table(?!-c[ea]).+)/,
+	rnumsplit = new RegExp( "^(" + pnum + ")(.*)$", "i" ),
+	rrelNum = new RegExp( "^([+-])=(" + pnum + ")", "i" ),
+
+	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+	cssNormalTransform = {
+		letterSpacing: "0",
+		fontWeight: "400"
+	},
+
+	cssPrefixes = [ "Webkit", "O", "Moz", "ms" ];
+
+// Return a css property mapped to a potentially vendor prefixed property
+function vendorPropName( style, name ) {
+
+	// Shortcut for names that are not vendor prefixed
+	if ( name in style ) {
+		return name;
+	}
+
+	// Check for vendor prefixed names
+	var capName = name[0].toUpperCase() + name.slice(1),
+		origName = name,
+		i = cssPrefixes.length;
+
+	while ( i-- ) {
+		name = cssPrefixes[ i ] + capName;
+		if ( name in style ) {
+			return name;
+		}
+	}
+
+	return origName;
+}
+
+function setPositiveNumber( elem, value, subtract ) {
+	var matches = rnumsplit.exec( value );
+	return matches ?
+		// Guard against undefined "subtract", e.g., when used as in cssHooks
+		Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
+		value;
+}
+
+function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
+	var i = extra === ( isBorderBox ? "border" : "content" ) ?
+		// If we already have the right measurement, avoid augmentation
+		4 :
+		// Otherwise initialize for horizontal or vertical properties
+		name === "width" ? 1 : 0,
+
+		val = 0;
+
+	for ( ; i < 4; i += 2 ) {
+		// Both box models exclude margin, so add it if we want it
+		if ( extra === "margin" ) {
+			val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
+		}
+
+		if ( isBorderBox ) {
+			// border-box includes padding, so remove it if we want content
+			if ( extra === "content" ) {
+				val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+			}
+
+			// At this point, extra isn't border nor margin, so remove border
+			if ( extra !== "margin" ) {
+				val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+			}
+		} else {
+			// At this point, extra isn't content, so add padding
+			val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
+
+			// At this point, extra isn't content nor padding, so add border
+			if ( extra !== "padding" ) {
+				val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
+			}
+		}
+	}
+
+	return val;
+}
+
+function getWidthOrHeight( elem, name, extra ) {
+
+	// Start with offset property, which is equivalent to the border-box value
+	var valueIsBorderBox = true,
+		val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+		styles = getStyles( elem ),
+		isBorderBox = jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
+
+	// Some non-html elements return undefined for offsetWidth, so check for null/undefined
+	// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
+	// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
+	if ( val <= 0 || val == null ) {
+		// Fall back to computed then uncomputed css if necessary
+		val = curCSS( elem, name, styles );
+		if ( val < 0 || val == null ) {
+			val = elem.style[ name ];
+		}
+
+		// Computed unit is not pixels. Stop here and return.
+		if ( rnumnonpx.test(val) ) {
+			return val;
+		}
+
+		// Check for style in case a browser which returns unreliable values
+		// for getComputedStyle silently falls back to the reliable elem.style
+		valueIsBorderBox = isBorderBox &&
+			( support.boxSizingReliable() || val === elem.style[ name ] );
+
+		// Normalize "", auto, and prepare for extra
+		val = parseFloat( val ) || 0;
+	}
+
+	// Use the active box-sizing model to add/subtract irrelevant styles
+	return ( val +
+		augmentWidthOrHeight(
+			elem,
+			name,
+			extra || ( isBorderBox ? "border" : "content" ),
+			valueIsBorderBox,
+			styles
+		)
+	) + "px";
+}
+
+function showHide( elements, show ) {
+	var display, elem, hidden,
+		values = [],
+		index = 0,
+		length = elements.length;
+
+	for ( ; index < length; index++ ) {
+		elem = elements[ index ];
+		if ( !elem.style ) {
+			continue;
+		}
+
+		values[ index ] = data_priv.get( elem, "olddisplay" );
+		display = elem.style.display;
+		if ( show ) {
+			// Reset the inline display of this element to learn if it is
+			// being hidden by cascaded rules or not
+			if ( !values[ index ] && display === "none" ) {
+				elem.style.display = "";
+			}
+
+			// Set elements which have been overridden with display: none
+			// in a stylesheet to whatever the default browser style is
+			// for such an element
+			if ( elem.style.display === "" && isHidden( elem ) ) {
+				values[ index ] = data_priv.access( elem, "olddisplay", defaultDisplay(elem.nodeName) );
+			}
+		} else {
+			hidden = isHidden( elem );
+
+			if ( display !== "none" || !hidden ) {
+				data_priv.set( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) );
+			}
+		}
+	}
+
+	// Set the display of most of the elements in a second loop
+	// to avoid the constant reflow
+	for ( index = 0; index < length; index++ ) {
+		elem = elements[ index ];
+		if ( !elem.style ) {
+			continue;
+		}
+		if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
+			elem.style.display = show ? values[ index ] || "" : "none";
+		}
+	}
+
+	return elements;
+}
+
+jQuery.extend({
+
+	// Add in style property hooks for overriding the default
+	// behavior of getting and setting a style property
+	cssHooks: {
+		opacity: {
+			get: function( elem, computed ) {
+				if ( computed ) {
+
+					// We should always get a number back from opacity
+					var ret = curCSS( elem, "opacity" );
+					return ret === "" ? "1" : ret;
+				}
+			}
+		}
+	},
+
+	// Don't automatically add "px" to these possibly-unitless properties
+	cssNumber: {
+		"columnCount": true,
+		"fillOpacity": true,
+		"flexGrow": true,
+		"flexShrink": true,
+		"fontWeight": true,
+		"lineHeight": true,
+		"opacity": true,
+		"order": true,
+		"orphans": true,
+		"widows": true,
+		"zIndex": true,
+		"zoom": true
+	},
+
+	// Add in properties whose names you wish to fix before
+	// setting or getting the value
+	cssProps: {
+		"float": "cssFloat"
+	},
+
+	// Get and set the style property on a DOM Node
+	style: function( elem, name, value, extra ) {
+
+		// Don't set styles on text and comment nodes
+		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+			return;
+		}
+
+		// Make sure that we're working with the right name
+		var ret, type, hooks,
+			origName = jQuery.camelCase( name ),
+			style = elem.style;
+
+		name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
+
+		// Gets hook for the prefixed version, then unprefixed version
+		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+		// Check if we're setting a value
+		if ( value !== undefined ) {
+			type = typeof value;
+
+			// Convert "+=" or "-=" to relative numbers (#7345)
+			if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+				value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
+				// Fixes bug #9237
+				type = "number";
+			}
+
+			// Make sure that null and NaN values aren't set (#7116)
+			if ( value == null || value !== value ) {
+				return;
+			}
+
+			// If a number, add 'px' to the (except for certain CSS properties)
+			if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+				value += "px";
+			}
+
+			// Support: IE9-11+
+			// background-* props affect original clone's values
+			if ( !support.clearCloneStyle && value === "" && name.indexOf( "background" ) === 0 ) {
+				style[ name ] = "inherit";
+			}
+
+			// If a hook was provided, use that value, otherwise just set the specified value
+			if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
+				style[ name ] = value;
+			}
+
+		} else {
+			// If a hook was provided get the non-computed value from there
+			if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+				return ret;
+			}
+
+			// Otherwise just get the value from the style object
+			return style[ name ];
+		}
+	},
+
+	css: function( elem, name, extra, styles ) {
+		var val, num, hooks,
+			origName = jQuery.camelCase( name );
+
+		// Make sure that we're working with the right name
+		name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
+
+		// Try prefixed name followed by the unprefixed name
+		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+		// If a hook was provided get the computed value from there
+		if ( hooks && "get" in hooks ) {
+			val = hooks.get( elem, true, extra );
+		}
+
+		// Otherwise, if a way to get the computed value exists, use that
+		if ( val === undefined ) {
+			val = curCSS( elem, name, styles );
+		}
+
+		// Convert "normal" to computed value
+		if ( val === "normal" && name in cssNormalTransform ) {
+			val = cssNormalTransform[ name ];
+		}
+
+		// Make numeric if forced or a qualifier was provided and val looks numeric
+		if ( extra === "" || extra ) {
+			num = parseFloat( val );
+			return extra === true || jQuery.isNumeric( num ) ? num || 0 : val;
+		}
+		return val;
+	}
+});
+
+jQuery.each([ "height", "width" ], function( i, name ) {
+	jQuery.cssHooks[ name ] = {
+		get: function( elem, computed, extra ) {
+			if ( computed ) {
+
+				// Certain elements can have dimension info if we invisibly show them
+				// but it must have a current display style that would benefit
+				return rdisplayswap.test( jQuery.css( elem, "display" ) ) && elem.offsetWidth === 0 ?
+					jQuery.swap( elem, cssShow, function() {
+						return getWidthOrHeight( elem, name, extra );
+					}) :
+					getWidthOrHeight( elem, name, extra );
+			}
+		},
+
+		set: function( elem, value, extra ) {
+			var styles = extra && getStyles( elem );
+			return setPositiveNumber( elem, value, extra ?
+				augmentWidthOrHeight(
+					elem,
+					name,
+					extra,
+					jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
+					styles
+				) : 0
+			);
+		}
+	};
+});
+
+// Support: Android 2.3
+jQuery.cssHooks.marginRight = addGetHookIf( support.reliableMarginRight,
+	function( elem, computed ) {
+		if ( computed ) {
+			return jQuery.swap( elem, { "display": "inline-block" },
+				curCSS, [ elem, "marginRight" ] );
+		}
+	}
+);
+
+// These hooks are used by animate to expand properties
+jQuery.each({
+	margin: "",
+	padding: "",
+	border: "Width"
+}, function( prefix, suffix ) {
+	jQuery.cssHooks[ prefix + suffix ] = {
+		expand: function( value ) {
+			var i = 0,
+				expanded = {},
+
+				// Assumes a single number if not a string
+				parts = typeof value === "string" ? value.split(" ") : [ value ];
+
+			for ( ; i < 4; i++ ) {
+				expanded[ prefix + cssExpand[ i ] + suffix ] =
+					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+			}
+
+			return expanded;
+		}
+	};
+
+	if ( !rmargin.test( prefix ) ) {
+		jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+	}
+});
+
+jQuery.fn.extend({
+	css: function( name, value ) {
+		return access( this, function( elem, name, value ) {
+			var styles, len,
+				map = {},
+				i = 0;
+
+			if ( jQuery.isArray( name ) ) {
+				styles = getStyles( elem );
+				len = name.length;
+
+				for ( ; i < len; i++ ) {
+					map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
+				}
+
+				return map;
+			}
+
+			return value !== undefined ?
+				jQuery.style( elem, name, value ) :
+				jQuery.css( elem, name );
+		}, name, value, arguments.length > 1 );
+	},
+	show: function() {
+		return showHide( this, true );
+	},
+	hide: function() {
+		return showHide( this );
+	},
+	toggle: function( state ) {
+		if ( typeof state === "boolean" ) {
+			return state ? this.show() : this.hide();
+		}
+
+		return this.each(function() {
+			if ( isHidden( this ) ) {
+				jQuery( this ).show();
+			} else {
+				jQuery( this ).hide();
+			}
+		});
+	}
+});
+
+
+function Tween( elem, options, prop, end, easing ) {
+	return new Tween.prototype.init( elem, options, prop, end, easing );
+}
+jQuery.Tween = Tween;
+
+Tween.prototype = {
+	constructor: Tween,
+	init: function( elem, options, prop, end, easing, unit ) {
+		this.elem = elem;
+		this.prop = prop;
+		this.easing = easing || "swing";
+		this.options = options;
+		this.start = this.now = this.cur();
+		this.end = end;
+		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+	},
+	cur: function() {
+		var hooks = Tween.propHooks[ this.prop ];
+
+		return hooks && hooks.get ?
+			hooks.get( this ) :
+			Tween.propHooks._default.get( this );
+	},
+	run: function( percent ) {
+		var eased,
+			hooks = Tween.propHooks[ this.prop ];
+
+		if ( this.options.duration ) {
+			this.pos = eased = jQuery.easing[ this.easing ](
+				percent, this.options.duration * percent, 0, 1, this.options.duration
+			);
+		} else {
+			this.pos = eased = percent;
+		}
+		this.now = ( this.end - this.start ) * eased + this.start;
+
+		if ( this.options.step ) {
+			this.options.step.call( this.elem, this.now, this );
+		}
+
+		if ( hooks && hooks.set ) {
+			hooks.set( this );
+		} else {
+			Tween.propHooks._default.set( this );
+		}
+		return this;
+	}
+};
+
+Tween.prototype.init.prototype = Tween.prototype;
+
+Tween.propHooks = {
+	_default: {
+		get: function( tween ) {
+			var result;
+
+			if ( tween.elem[ tween.prop ] != null &&
+				(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
+				return tween.elem[ tween.prop ];
+			}
+
+			// Passing an empty string as a 3rd parameter to .css will automatically
+			// attempt a parseFloat and fallback to a string if the parse fails.
+			// Simple values such as "10px" are parsed to Float;
+			// complex values such as "rotate(1rad)" are returned as-is.
+			result = jQuery.css( tween.elem, tween.prop, "" );
+			// Empty strings, null, undefined and "auto" are converted to 0.
+			return !result || result === "auto" ? 0 : result;
+		},
+		set: function( tween ) {
+			// Use step hook for back compat.
+			// Use cssHook if its there.
+			// Use .style if available and use plain properties where available.
+			if ( jQuery.fx.step[ tween.prop ] ) {
+				jQuery.fx.step[ tween.prop ]( tween );
+			} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
+				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
+			} else {
+				tween.elem[ tween.prop ] = tween.now;
+			}
+		}
+	}
+};
+
+// Support: IE9
+// Panic based approach to setting things on disconnected nodes
+Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
+	set: function( tween ) {
+		if ( tween.elem.nodeType && tween.elem.parentNode ) {
+			tween.elem[ tween.prop ] = tween.now;
+		}
+	}
+};
+
+jQuery.easing = {
+	linear: function( p ) {
+		return p;
+	},
+	swing: function( p ) {
+		return 0.5 - Math.cos( p * Math.PI ) / 2;
+	}
+};
+
+jQuery.fx = Tween.prototype.init;
+
+// Back Compat <1.8 extension point
+jQuery.fx.step = {};
+
+
+
+
+var
+	fxNow, timerId,
+	rfxtypes = /^(?:toggle|show|hide)$/,
+	rfxnum = new RegExp( "^(?:([+-])=|)(" + pnum + ")([a-z%]*)$", "i" ),
+	rrun = /queueHooks$/,
+	animationPrefilters = [ defaultPrefilter ],
+	tweeners = {
+		"*": [ function( prop, value ) {
+			var tween = this.createTween( prop, value ),
+				target = tween.cur(),
+				parts = rfxnum.exec( value ),
+				unit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
+
+				// Starting value computation is required for potential unit mismatches
+				start = ( jQuery.cssNumber[ prop ] || unit !== "px" && +target ) &&
+					rfxnum.exec( jQuery.css( tween.elem, prop ) ),
+				scale = 1,
+				maxIterations = 20;
+
+			if ( start && start[ 3 ] !== unit ) {
+				// Trust units reported by jQuery.css
+				unit = unit || start[ 3 ];
+
+				// Make sure we update the tween properties later on
+				parts = parts || [];
+
+				// Iteratively approximate from a nonzero starting point
+				start = +target || 1;
+
+				do {
+					// If previous iteration zeroed out, double until we get *something*.
+					// Use string for doubling so we don't accidentally see scale as unchanged below
+					scale = scale || ".5";
+
+					// Adjust and apply
+					start = start / scale;
+					jQuery.style( tween.elem, prop, start + unit );
+
+				// Update scale, tolerating zero or NaN from tween.cur(),
+				// break the loop if scale is unchanged or perfect, or if we've just had enough
+				} while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
+			}
+
+			// Update tween properties
+			if ( parts ) {
+				start = tween.start = +start || +target || 0;
+				tween.unit = unit;
+				// If a +=/-= token was provided, we're doing a relative animation
+				tween.end = parts[ 1 ] ?
+					start + ( parts[ 1 ] + 1 ) * parts[ 2 ] :
+					+parts[ 2 ];
+			}
+
+			return tween;
+		} ]
+	};
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+	setTimeout(function() {
+		fxNow = undefined;
+	});
+	return ( fxNow = jQuery.now() );
+}
+
+// Generate parameters to create a standard animation
+function genFx( type, includeWidth ) {
+	var which,
+		i = 0,
+		attrs = { height: type };
+
+	// If we include width, step value is 1 to do all cssExpand values,
+	// otherwise step value is 2 to skip over Left and Right
+	includeWidth = includeWidth ? 1 : 0;
+	for ( ; i < 4 ; i += 2 - includeWidth ) {
+		which = cssExpand[ i ];
+		attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
+	}
+
+	if ( includeWidth ) {
+		attrs.opacity = attrs.width = type;
+	}
+
+	return attrs;
+}
+
+function createTween( value, prop, animation ) {
+	var tween,
+		collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
+		index = 0,
+		length = collection.length;
+	for ( ; index < length; index++ ) {
+		if ( (tween = collection[ index ].call( animation, prop, value )) ) {
+
+			// We're done with this property
+			return tween;
+		}
+	}
+}
+
+function defaultPrefilter( elem, props, opts ) {
+	/* jshint validthis: true */
+	var prop, value, toggle, tween, hooks, oldfire, display, checkDisplay,
+		anim = this,
+		orig = {},
+		style = elem.style,
+		hidden = elem.nodeType && isHidden( elem ),
+		dataShow = data_priv.get( elem, "fxshow" );
+
+	// Handle queue: false promises
+	if ( !opts.queue ) {
+		hooks = jQuery._queueHooks( elem, "fx" );
+		if ( hooks.unqueued == null ) {
+			hooks.unqueued = 0;
+			oldfire = hooks.empty.fire;
+			hooks.empty.fire = function() {
+				if ( !hooks.unqueued ) {
+					oldfire();
+				}
+			};
+		}
+		hooks.unqueued++;
+
+		anim.always(function() {
+			// Ensure the complete handler is called before this completes
+			anim.always(function() {
+				hooks.unqueued--;
+				if ( !jQuery.queue( elem, "fx" ).length ) {
+					hooks.empty.fire();
+				}
+			});
+		});
+	}
+
+	// Height/width overflow pass
+	if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
+		// Make sure that nothing sneaks out
+		// Record all 3 overflow attributes because IE9-10 do not
+		// change the overflow attribute when overflowX and
+		// overflowY are set to the same value
+		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
+
+		// Set display property to inline-block for height/width
+		// animations on inline elements that are having width/height animated
+		display = jQuery.css( elem, "display" );
+
+		// Test default display if display is currently "none"
+		checkDisplay = display === "none" ?
+			data_priv.get( elem, "olddisplay" ) || defaultDisplay( elem.nodeName ) : display;
+
+		if ( checkDisplay === "inline" && jQuery.css( elem, "float" ) === "none" ) {
+			style.display = "inline-block";
+		}
+	}
+
+	if ( opts.overflow ) {
+		style.overflow = "hidden";
+		anim.always(function() {
+			style.overflow = opts.overflow[ 0 ];
+			style.overflowX = opts.overflow[ 1 ];
+			style.overflowY = opts.overflow[ 2 ];
+		});
+	}
+
+	// show/hide pass
+	for ( prop in props ) {
+		value = props[ prop ];
+		if ( rfxtypes.exec( value ) ) {
+			delete props[ prop ];
+			toggle = toggle || value === "toggle";
+			if ( value === ( hidden ? "hide" : "show" ) ) {
+
+				// If there is dataShow left over from a stopped hide or show and we are going to proceed with show, we should pretend to be hidden
+				if ( value === "show" && dataShow && dataShow[ prop ] !== undefined ) {
+					hidden = true;
+				} else {
+					continue;
+				}
+			}
+			orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
+
+		// Any non-fx value stops us from restoring the original display value
+		} else {
+			display = undefined;
+		}
+	}
+
+	if ( !jQuery.isEmptyObject( orig ) ) {
+		if ( dataShow ) {
+			if ( "hidden" in dataShow ) {
+				hidden = dataShow.hidden;
+			}
+		} else {
+			dataShow = data_priv.access( elem, "fxshow", {} );
+		}
+
+		// Store state if its toggle - enables .stop().toggle() to "reverse"
+		if ( toggle ) {
+			dataShow.hidden = !hidden;
+		}
+		if ( hidden ) {
+			jQuery( elem ).show();
+		} else {
+			anim.done(function() {
+				jQuery( elem ).hide();
+			});
+		}
+		anim.done(function() {
+			var prop;
+
+			data_priv.remove( elem, "fxshow" );
+			for ( prop in orig ) {
+				jQuery.style( elem, prop, orig[ prop ] );
+			}
+		});
+		for ( prop in orig ) {
+			tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
+
+			if ( !( prop in dataShow ) ) {
+				dataShow[ prop ] = tween.start;
+				if ( hidden ) {
+					tween.end = tween.start;
+					tween.start = prop === "width" || prop === "height" ? 1 : 0;
+				}
+			}
+		}
+
+	// If this is a noop like .hide().hide(), restore an overwritten display value
+	} else if ( (display === "none" ? defaultDisplay( elem.nodeName ) : display) === "inline" ) {
+		style.display = display;
+	}
+}
+
+function propFilter( props, specialEasing ) {
+	var index, name, easing, value, hooks;
+
+	// camelCase, specialEasing and expand cssHook pass
+	for ( index in props ) {
+		name = jQuery.camelCase( index );
+		easing = specialEasing[ name ];
+		value = props[ index ];
+		if ( jQuery.isArray( value ) ) {
+			easing = value[ 1 ];
+			value = props[ index ] = value[ 0 ];
+		}
+
+		if ( index !== name ) {
+			props[ name ] = value;
+			delete props[ index ];
+		}
+
+		hooks = jQuery.cssHooks[ name ];
+		if ( hooks && "expand" in hooks ) {
+			value = hooks.expand( value );
+			delete props[ name ];
+
+			// Not quite $.extend, this won't overwrite existing keys.
+			// Reusing 'index' because we have the correct "name"
+			for ( index in value ) {
+				if ( !( index in props ) ) {
+					props[ index ] = value[ index ];
+					specialEasing[ index ] = easing;
+				}
+			}
+		} else {
+			specialEasing[ name ] = easing;
+		}
+	}
+}
+
+function Animation( elem, properties, options ) {
+	var result,
+		stopped,
+		index = 0,
+		length = animationPrefilters.length,
+		deferred = jQuery.Deferred().always( function() {
+			// Don't match elem in the :animated selector
+			delete tick.elem;
+		}),
+		tick = function() {
+			if ( stopped ) {
+				return false;
+			}
+			var currentTime = fxNow || createFxNow(),
+				remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
+				// Support: Android 2.3
+				// Archaic crash bug won't allow us to use `1 - ( 0.5 || 0 )` (#12497)
+				temp = remaining / animation.duration || 0,
+				percent = 1 - temp,
+				index = 0,
+				length = animation.tweens.length;
+
+			for ( ; index < length ; index++ ) {
+				animation.tweens[ index ].run( percent );
+			}
+
+			deferred.notifyWith( elem, [ animation, percent, remaining ]);
+
+			if ( percent < 1 && length ) {
+				return remaining;
+			} else {
+				deferred.resolveWith( elem, [ animation ] );
+				return false;
+			}
+		},
+		animation = deferred.promise({
+			elem: elem,
+			props: jQuery.extend( {}, properties ),
+			opts: jQuery.extend( true, { specialEasing: {} }, options ),
+			originalProperties: properties,
+			originalOptions: options,
+			startTime: fxNow || createFxNow(),
+			duration: options.duration,
+			tweens: [],
+			createTween: function( prop, end ) {
+				var tween = jQuery.Tween( elem, animation.opts, prop, end,
+						animation.opts.specialEasing[ prop ] || animation.opts.easing );
+				animation.tweens.push( tween );
+				return tween;
+			},
+			stop: function( gotoEnd ) {
+				var index = 0,
+					// If we are going to the end, we want to run all the tweens
+					// otherwise we skip this part
+					length = gotoEnd ? animation.tweens.length : 0;
+				if ( stopped ) {
+					return this;
+				}
+				stopped = true;
+				for ( ; index < length ; index++ ) {
+					animation.tweens[ index ].run( 1 );
+				}
+
+				// Resolve when we played the last frame; otherwise, reject
+				if ( gotoEnd ) {
+					deferred.resolveWith( elem, [ animation, gotoEnd ] );
+				} else {
+					deferred.rejectWith( elem, [ animation, gotoEnd ] );
+				}
+				return this;
+			}
+		}),
+		props = animation.props;
+
+	propFilter( props, animation.opts.specialEasing );
+
+	for ( ; index < length ; index++ ) {
+		result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
+		if ( result ) {
+			return result;
+		}
+	}
+
+	jQuery.map( props, createTween, animation );
+
+	if ( jQuery.isFunction( animation.opts.start ) ) {
+		animation.opts.start.call( elem, animation );
+	}
+
+	jQuery.fx.timer(
+		jQuery.extend( tick, {
+			elem: elem,
+			anim: animation,
+			queue: animation.opts.queue
+		})
+	);
+
+	// attach callbacks from options
+	return animation.progress( animation.opts.progress )
+		.done( animation.opts.done, animation.opts.complete )
+		.fail( animation.opts.fail )
+		.always( animation.opts.always );
+}
+
+jQuery.Animation = jQuery.extend( Animation, {
+
+	tweener: function( props, callback ) {
+		if ( jQuery.isFunction( props ) ) {
+			callback = props;
+			props = [ "*" ];
+		} else {
+			props = props.split(" ");
+		}
+
+		var prop,
+			index = 0,
+			length = props.length;
+
+		for ( ; index < length ; index++ ) {
+			prop = props[ index ];
+			tweeners[ prop ] = tweeners[ prop ] || [];
+			tweeners[ prop ].unshift( callback );
+		}
+	},
+
+	prefilter: function( callback, prepend ) {
+		if ( prepend ) {
+			animationPrefilters.unshift( callback );
+		} else {
+			animationPrefilters.push( callback );
+		}
+	}
+});
+
+jQuery.speed = function( speed, easing, fn ) {
+	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+		complete: fn || !fn && easing ||
+			jQuery.isFunction( speed ) && speed,
+		duration: speed,
+		easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+	};
+
+	opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+		opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+	// Normalize opt.queue - true/undefined/null -> "fx"
+	if ( opt.queue == null || opt.queue === true ) {
+		opt.queue = "fx";
+	}
+
+	// Queueing
+	opt.old = opt.complete;
+
+	opt.complete = function() {
+		if ( jQuery.isFunction( opt.old ) ) {
+			opt.old.call( this );
+		}
+
+		if ( opt.queue ) {
+			jQuery.dequeue( this, opt.queue );
+		}
+	};
+
+	return opt;
+};
+
+jQuery.fn.extend({
+	fadeTo: function( speed, to, easing, callback ) {
+
+		// Show any hidden elements after setting opacity to 0
+		return this.filter( isHidden ).css( "opacity", 0 ).show()
+
+			// Animate to the value specified
+			.end().animate({ opacity: to }, speed, easing, callback );
+	},
+	animate: function( prop, speed, easing, callback ) {
+		var empty = jQuery.isEmptyObject( prop ),
+			optall = jQuery.speed( speed, easing, callback ),
+			doAnimation = function() {
+				// Operate on a copy of prop so per-property easing won't be lost
+				var anim = Animation( this, jQuery.extend( {}, prop ), optall );
+
+				// Empty animations, or finishing resolves immediately
+				if ( empty || data_priv.get( this, "finish" ) ) {
+					anim.stop( true );
+				}
+			};
+			doAnimation.finish = doAnimation;
+
+		return empty || optall.queue === false ?
+			this.each( doAnimation ) :
+			this.queue( optall.queue, doAnimation );
+	},
+	stop: function( type, clearQueue, gotoEnd ) {
+		var stopQueue = function( hooks ) {
+			var stop = hooks.stop;
+			delete hooks.stop;
+			stop( gotoEnd );
+		};
+
+		if ( typeof type !== "string" ) {
+			gotoEnd = clearQueue;
+			clearQueue = type;
+			type = undefined;
+		}
+		if ( clearQueue && type !== false ) {
+			this.queue( type || "fx", [] );
+		}
+
+		return this.each(function() {
+			var dequeue = true,
+				index = type != null && type + "queueHooks",
+				timers = jQuery.timers,
+				data = data_priv.get( this );
+
+			if ( index ) {
+				if ( data[ index ] && data[ index ].stop ) {
+					stopQueue( data[ index ] );
+				}
+			} else {
+				for ( index in data ) {
+					if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
+						stopQueue( data[ index ] );
+					}
+				}
+			}
+
+			for ( index = timers.length; index--; ) {
+				if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+					timers[ index ].anim.stop( gotoEnd );
+					dequeue = false;
+					timers.splice( index, 1 );
+				}
+			}
+
+			// Start the next in the queue if the last step wasn't forced.
+			// Timers currently will call their complete callbacks, which
+			// will dequeue but only if they were gotoEnd.
+			if ( dequeue || !gotoEnd ) {
+				jQuery.dequeue( this, type );
+			}
+		});
+	},
+	finish: function( type ) {
+		if ( type !== false ) {
+			type = type || "fx";
+		}
+		return this.each(function() {
+			var index,
+				data = data_priv.get( this ),
+				queue = data[ type + "queue" ],
+				hooks = data[ type + "queueHooks" ],
+				timers = jQuery.timers,
+				length = queue ? queue.length : 0;
+
+			// Enable finishing flag on private data
+			data.finish = true;
+
+			// Empty the queue first
+			jQuery.queue( this, type, [] );
+
+			if ( hooks && hooks.stop ) {
+				hooks.stop.call( this, true );
+			}
+
+			// Look for any active animations, and finish them
+			for ( index = timers.length; index--; ) {
+				if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
+					timers[ index ].anim.stop( true );
+					timers.splice( index, 1 );
+				}
+			}
+
+			// Look for any animations in the old queue and finish them
+			for ( index = 0; index < length; index++ ) {
+				if ( queue[ index ] && queue[ index ].finish ) {
+					queue[ index ].finish.call( this );
+				}
+			}
+
+			// Turn off finishing flag
+			delete data.finish;
+		});
+	}
+});
+
+jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
+	var cssFn = jQuery.fn[ name ];
+	jQuery.fn[ name ] = function( speed, easing, callback ) {
+		return speed == null || typeof speed === "boolean" ?
+			cssFn.apply( this, arguments ) :
+			this.animate( genFx( name, true ), speed, easing, callback );
+	};
+});
+
+// Generate shortcuts for custom animations
+jQuery.each({
+	slideDown: genFx("show"),
+	slideUp: genFx("hide"),
+	slideToggle: genFx("toggle"),
+	fadeIn: { opacity: "show" },
+	fadeOut: { opacity: "hide" },
+	fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+	jQuery.fn[ name ] = function( speed, easing, callback ) {
+		return this.animate( props, speed, easing, callback );
+	};
+});
+
+jQuery.timers = [];
+jQuery.fx.tick = function() {
+	var timer,
+		i = 0,
+		timers = jQuery.timers;
+
+	fxNow = jQuery.now();
+
+	for ( ; i < timers.length; i++ ) {
+		timer = timers[ i ];
+		// Checks the timer has not already been removed
+		if ( !timer() && timers[ i ] === timer ) {
+			timers.splice( i--, 1 );
+		}
+	}
+
+	if ( !timers.length ) {
+		jQuery.fx.stop();
+	}
+	fxNow = undefined;
+};
+
+jQuery.fx.timer = function( timer ) {
+	jQuery.timers.push( timer );
+	if ( timer() ) {
+		jQuery.fx.start();
+	} else {
+		jQuery.timers.pop();
+	}
+};
+
+jQuery.fx.interval = 13;
+
+jQuery.fx.start = function() {
+	if ( !timerId ) {
+		timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
+	}
+};
+
+jQuery.fx.stop = function() {
+	clearInterval( timerId );
+	timerId = null;
+};
+
+jQuery.fx.speeds = {
+	slow: 600,
+	fast: 200,
+	// Default speed
+	_default: 400
+};
+
+
+// Based off of the plugin by Clint Helfers, with permission.
+// http://blindsignals.com/index.php/2009/07/jquery-delay/
+jQuery.fn.delay = function( time, type ) {
+	time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+	type = type || "fx";
+
+	return this.queue( type, function( next, hooks ) {
+		var timeout = setTimeout( next, time );
+		hooks.stop = function() {
+			clearTimeout( timeout );
+		};
+	});
+};
+
+
+(function() {
+	var input = document.createElement( "input" ),
+		select = document.createElement( "select" ),
+		opt = select.appendChild( document.createElement( "option" ) );
+
+	input.type = "checkbox";
+
+	// Support: iOS<=5.1, Android<=4.2+
+	// Default value for a checkbox should be "on"
+	support.checkOn = input.value !== "";
+
+	// Support: IE<=11+
+	// Must access selectedIndex to make default options select
+	support.optSelected = opt.selected;
+
+	// Support: Android<=2.3
+	// Options inside disabled selects are incorrectly marked as disabled
+	select.disabled = true;
+	support.optDisabled = !opt.disabled;
+
+	// Support: IE<=11+
+	// An input loses its value after becoming a radio
+	input = document.createElement( "input" );
+	input.value = "t";
+	input.type = "radio";
+	support.radioValue = input.value === "t";
+})();
+
+
+var nodeHook, boolHook,
+	attrHandle = jQuery.expr.attrHandle;
+
+jQuery.fn.extend({
+	attr: function( name, value ) {
+		return access( this, jQuery.attr, name, value, arguments.length > 1 );
+	},
+
+	removeAttr: function( name ) {
+		return this.each(function() {
+			jQuery.removeAttr( this, name );
+		});
+	}
+});
+
+jQuery.extend({
+	attr: function( elem, name, value ) {
+		var hooks, ret,
+			nType = elem.nodeType;
+
+		// don't get/set attributes on text, comment and attribute nodes
+		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+			return;
+		}
+
+		// Fallback to prop when attributes are not supported
+		if ( typeof elem.getAttribute === strundefined ) {
+			return jQuery.prop( elem, name, value );
+		}
+
+		// All attributes are lowercase
+		// Grab necessary hook if one is defined
+		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
+			name = name.toLowerCase();
+			hooks = jQuery.attrHooks[ name ] ||
+				( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );
+		}
+
+		if ( value !== undefined ) {
+
+			if ( value === null ) {
+				jQuery.removeAttr( elem, name );
+
+			} else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+				return ret;
+
+			} else {
+				elem.setAttribute( name, value + "" );
+				return value;
+			}
+
+		} else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+			return ret;
+
+		} else {
+			ret = jQuery.find.attr( elem, name );
+
+			// Non-existent attributes return null, we normalize to undefined
+			return ret == null ?
+				undefined :
+				ret;
+		}
+	},
+
+	removeAttr: function( elem, value ) {
+		var name, propName,
+			i = 0,
+			attrNames = value && value.match( rnotwhite );
+
+		if ( attrNames && elem.nodeType === 1 ) {
+			while ( (name = attrNames[i++]) ) {
+				propName = jQuery.propFix[ name ] || name;
+
+				// Boolean attributes get special treatment (#10870)
+				if ( jQuery.expr.match.bool.test( name ) ) {
+					// Set corresponding property to false
+					elem[ propName ] = false;
+				}
+
+				elem.removeAttribute( name );
+			}
+		}
+	},
+
+	attrHooks: {
+		type: {
+			set: function( elem, value ) {
+				if ( !support.radioValue && value === "radio" &&
+					jQuery.nodeName( elem, "input" ) ) {
+					var val = elem.value;
+					elem.setAttribute( "type", value );
+					if ( val ) {
+						elem.value = val;
+					}
+					return value;
+				}
+			}
+		}
+	}
+});
+
+// Hooks for boolean attributes
+boolHook = {
+	set: function( elem, value, name ) {
+		if ( value === false ) {
+			// Remove boolean attributes when set to false
+			jQuery.removeAttr( elem, name );
+		} else {
+			elem.setAttribute( name, name );
+		}
+		return name;
+	}
+};
+jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
+	var getter = attrHandle[ name ] || jQuery.find.attr;
+
+	attrHandle[ name ] = function( elem, name, isXML ) {
+		var ret, handle;
+		if ( !isXML ) {
+			// Avoid an infinite loop by temporarily removing this function from the getter
+			handle = attrHandle[ name ];
+			attrHandle[ name ] = ret;
+			ret = getter( elem, name, isXML ) != null ?
+				name.toLowerCase() :
+				null;
+			attrHandle[ name ] = handle;
+		}
+		return ret;
+	};
+});
+
+
+
+
+var rfocusable = /^(?:input|select|textarea|button)$/i;
+
+jQuery.fn.extend({
+	prop: function( name, value ) {
+		return access( this, jQuery.prop, name, value, arguments.length > 1 );
+	},
+
+	removeProp: function( name ) {
+		return this.each(function() {
+			delete this[ jQuery.propFix[ name ] || name ];
+		});
+	}
+});
+
+jQuery.extend({
+	propFix: {
+		"for": "htmlFor",
+		"class": "className"
+	},
+
+	prop: function( elem, name, value ) {
+		var ret, hooks, notxml,
+			nType = elem.nodeType;
+
+		// Don't get/set properties on text, comment and attribute nodes
+		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+			return;
+		}
+
+		notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+		if ( notxml ) {
+			// Fix name and attach hooks
+			name = jQuery.propFix[ name ] || name;
+			hooks = jQuery.propHooks[ name ];
+		}
+
+		if ( value !== undefined ) {
+			return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?
+				ret :
+				( elem[ name ] = value );
+
+		} else {
+			return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ?
+				ret :
+				elem[ name ];
+		}
+	},
+
+	propHooks: {
+		tabIndex: {
+			get: function( elem ) {
+				return elem.hasAttribute( "tabindex" ) || rfocusable.test( elem.nodeName ) || elem.href ?
+					elem.tabIndex :
+					-1;
+			}
+		}
+	}
+});
+
+if ( !support.optSelected ) {
+	jQuery.propHooks.selected = {
+		get: function( elem ) {
+			var parent = elem.parentNode;
+			if ( parent && parent.parentNode ) {
+				parent.parentNode.selectedIndex;
+			}
+			return null;
+		}
+	};
+}
+
+jQuery.each([
+	"tabIndex",
+	"readOnly",
+	"maxLength",
+	"cellSpacing",
+	"cellPadding",
+	"rowSpan",
+	"colSpan",
+	"useMap",
+	"frameBorder",
+	"contentEditable"
+], function() {
+	jQuery.propFix[ this.toLowerCase() ] = this;
+});
+
+
+
+
+var rclass = /[\t\r\n\f]/g;
+
+jQuery.fn.extend({
+	addClass: function( value ) {
+		var classes, elem, cur, clazz, j, finalValue,
+			proceed = typeof value === "string" && value,
+			i = 0,
+			len = this.length;
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( j ) {
+				jQuery( this ).addClass( value.call( this, j, this.className ) );
+			});
+		}
+
+		if ( proceed ) {
+			// The disjunction here is for better compressibility (see removeClass)
+			classes = ( value || "" ).match( rnotwhite ) || [];
+
+			for ( ; i < len; i++ ) {
+				elem = this[ i ];
+				cur = elem.nodeType === 1 && ( elem.className ?
+					( " " + elem.className + " " ).replace( rclass, " " ) :
+					" "
+				);
+
+				if ( cur ) {
+					j = 0;
+					while ( (clazz = classes[j++]) ) {
+						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
+							cur += clazz + " ";
+						}
+					}
+
+					// only assign if different to avoid unneeded rendering.
+					finalValue = jQuery.trim( cur );
+					if ( elem.className !== finalValue ) {
+						elem.className = finalValue;
+					}
+				}
+			}
+		}
+
+		return this;
+	},
+
+	removeClass: function( value ) {
+		var classes, elem, cur, clazz, j, finalValue,
+			proceed = arguments.length === 0 || typeof value === "string" && value,
+			i = 0,
+			len = this.length;
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( j ) {
+				jQuery( this ).removeClass( value.call( this, j, this.className ) );
+			});
+		}
+		if ( proceed ) {
+			classes = ( value || "" ).match( rnotwhite ) || [];
+
+			for ( ; i < len; i++ ) {
+				elem = this[ i ];
+				// This expression is here for better compressibility (see addClass)
+				cur = elem.nodeType === 1 && ( elem.className ?
+					( " " + elem.className + " " ).replace( rclass, " " ) :
+					""
+				);
+
+				if ( cur ) {
+					j = 0;
+					while ( (clazz = classes[j++]) ) {
+						// Remove *all* instances
+						while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
+							cur = cur.replace( " " + clazz + " ", " " );
+						}
+					}
+
+					// Only assign if different to avoid unneeded rendering.
+					finalValue = value ? jQuery.trim( cur ) : "";
+					if ( elem.className !== finalValue ) {
+						elem.className = finalValue;
+					}
+				}
+			}
+		}
+
+		return this;
+	},
+
+	toggleClass: function( value, stateVal ) {
+		var type = typeof value;
+
+		if ( typeof stateVal === "boolean" && type === "string" ) {
+			return stateVal ? this.addClass( value ) : this.removeClass( value );
+		}
+
+		if ( jQuery.isFunction( value ) ) {
+			return this.each(function( i ) {
+				jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+			});
+		}
+
+		return this.each(function() {
+			if ( type === "string" ) {
+				// Toggle individual class names
+				var className,
+					i = 0,
+					self = jQuery( this ),
+					classNames = value.match( rnotwhite ) || [];
+
+				while ( (className = classNames[ i++ ]) ) {
+					// Check each className given, space separated list
+					if ( self.hasClass( className ) ) {
+						self.removeClass( className );
+					} else {
+						self.addClass( className );
+					}
+				}
+
+			// Toggle whole class name
+			} else if ( type === strundefined || type === "boolean" ) {
+				if ( this.className ) {
+					// store className if set
+					data_priv.set( this, "__className__", this.className );
+				}
+
+				// If the element has a class name or if we're passed `false`,
+				// then remove the whole classname (if there was one, the above saved it).
+				// Otherwise bring back whatever was previously saved (if anything),
+				// falling back to the empty string if nothing was stored.
+				this.className = this.className || value === false ? "" : data_priv.get( this, "__className__" ) || "";
+			}
+		});
+	},
+
+	hasClass: function( selector ) {
+		var className = " " + selector + " ",
+			i = 0,
+			l = this.length;
+		for ( ; i < l; i++ ) {
+			if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
+				return true;
+			}
+		}
+
+		return false;
+	}
+});
+
+
+
+
+var rreturn = /\r/g;
+
+jQuery.fn.extend({
+	val: function( value ) {
+		var hooks, ret, isFunction,
+			elem = this[0];
+
+		if ( !arguments.length ) {
+			if ( elem ) {
+				hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+				if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+					return ret;
+				}
+
+				ret = elem.value;
+
+				return typeof ret === "string" ?
+					// Handle most common string cases
+					ret.replace(rreturn, "") :
+					// Handle cases where value is null/undef or number
+					ret == null ? "" : ret;
+			}
+
+			return;
+		}
+
+		isFunction = jQuery.isFunction( value );
+
+		return this.each(function( i ) {
+			var val;
+
+			if ( this.nodeType !== 1 ) {
+				return;
+			}
+
+			if ( isFunction ) {
+				val = value.call( this, i, jQuery( this ).val() );
+			} else {
+				val = value;
+			}
+
+			// Treat null/undefined as ""; convert numbers to string
+			if ( val == null ) {
+				val = "";
+
+			} else if ( typeof val === "number" ) {
+				val += "";
+
+			} else if ( jQuery.isArray( val ) ) {
+				val = jQuery.map( val, function( value ) {
+					return value == null ? "" : value + "";
+				});
+			}
+
+			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+			// If set returns undefined, fall back to normal setting
+			if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+				this.value = val;
+			}
+		});
+	}
+});
+
+jQuery.extend({
+	valHooks: {
+		option: {
+			get: function( elem ) {
+				var val = jQuery.find.attr( elem, "value" );
+				return val != null ?
+					val :
+					// Support: IE10-11+
+					// option.text throws exceptions (#14686, #14858)
+					jQuery.trim( jQuery.text( elem ) );
+			}
+		},
+		select: {
+			get: function( elem ) {
+				var value, option,
+					options = elem.options,
+					index = elem.selectedIndex,
+					one = elem.type === "select-one" || index < 0,
+					values = one ? null : [],
+					max = one ? index + 1 : options.length,
+					i = index < 0 ?
+						max :
+						one ? index : 0;
+
+				// Loop through all the selected options
+				for ( ; i < max; i++ ) {
+					option = options[ i ];
+
+					// IE6-9 doesn't update selected after form reset (#2551)
+					if ( ( option.selected || i === index ) &&
+							// Don't return options that are disabled or in a disabled optgroup
+							( support.optDisabled ? !option.disabled : option.getAttribute( "disabled" ) === null ) &&
+							( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
+
+						// Get the specific value for the option
+						value = jQuery( option ).val();
+
+						// We don't need an array for one selects
+						if ( one ) {
+							return value;
+						}
+
+						// Multi-Selects return an array
+						values.push( value );
+					}
+				}
+
+				return values;
+			},
+
+			set: function( elem, value ) {
+				var optionSet, option,
+					options = elem.options,
+					values = jQuery.makeArray( value ),
+					i = options.length;
+
+				while ( i-- ) {
+					option = options[ i ];
+					if ( (option.selected = jQuery.inArray( option.value, values ) >= 0) ) {
+						optionSet = true;
+					}
+				}
+
+				// Force browsers to behave consistently when non-matching value is set
+				if ( !optionSet ) {
+					elem.selectedIndex = -1;
+				}
+				return values;
+			}
+		}
+	}
+});
+
+// Radios and checkboxes getter/setter
+jQuery.each([ "radio", "checkbox" ], function() {
+	jQuery.valHooks[ this ] = {
+		set: function( elem, value ) {
+			if ( jQuery.isArray( value ) ) {
+				return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+			}
+		}
+	};
+	if ( !support.checkOn ) {
+		jQuery.valHooks[ this ].get = function( elem ) {
+			return elem.getAttribute("value") === null ? "on" : elem.value;
+		};
+	}
+});
+
+
+
+
+// Return jQuery for attributes-only inclusion
+
+
+jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
+	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
+	"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
+
+	// Handle event binding
+	jQuery.fn[ name ] = function( data, fn ) {
+		return arguments.length > 0 ?
+			this.on( name, null, data, fn ) :
+			this.trigger( name );
+	};
+});
+
+jQuery.fn.extend({
+	hover: function( fnOver, fnOut ) {
+		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+	},
+
+	bind: function( types, data, fn ) {
+		return this.on( types, null, data, fn );
+	},
+	unbind: function( types, fn ) {
+		return this.off( types, null, fn );
+	},
+
+	delegate: function( selector, types, data, fn ) {
+		return this.on( types, selector, data, fn );
+	},
+	undelegate: function( selector, types, fn ) {
+		// ( namespace ) or ( selector, types [, fn] )
+		return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
+	}
+});
+
+
+var nonce = jQuery.now();
+
+var rquery = (/\?/);
+
+
+
+// Support: Android 2.3
+// Workaround failure to string-cast null input
+jQuery.parseJSON = function( data ) {
+	return JSON.parse( data + "" );
+};
+
+
+// Cross-browser xml parsing
+jQuery.parseXML = function( data ) {
+	var xml, tmp;
+	if ( !data || typeof data !== "string" ) {
+		return null;
+	}
+
+	// Support: IE9
+	try {
+		tmp = new DOMParser();
+		xml = tmp.parseFromString( data, "text/xml" );
+	} catch ( e ) {
+		xml = undefined;
+	}
+
+	if ( !xml || xml.getElementsByTagName( "parsererror" ).length ) {
+		jQuery.error( "Invalid XML: " + data );
+	}
+	return xml;
+};
+
+
+var
+	rhash = /#.*$/,
+	rts = /([?&])_=[^&]*/,
+	rheaders = /^(.*?):[ \t]*([^\r\n]*)$/mg,
+	// #7653, #8125, #8152: local protocol detection
+	rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
+	rnoContent = /^(?:GET|HEAD)$/,
+	rprotocol = /^\/\//,
+	rurl = /^([\w.+-]+:)(?:\/\/(?:[^\/?#]*@|)([^\/?#:]*)(?::(\d+)|)|)/,
+
+	/* Prefilters
+	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+	 * 2) These are called:
+	 *    - BEFORE asking for a transport
+	 *    - AFTER param serialization (s.data is a string if s.processData is true)
+	 * 3) key is the dataType
+	 * 4) the catchall symbol "*" can be used
+	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+	 */
+	prefilters = {},
+
+	/* Transports bindings
+	 * 1) key is the dataType
+	 * 2) the catchall symbol "*" can be used
+	 * 3) selection will start with transport dataType and THEN go to "*" if needed
+	 */
+	transports = {},
+
+	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+	allTypes = "*/".concat( "*" ),
+
+	// Document location
+	ajaxLocation = window.location.href,
+
+	// Segment location into parts
+	ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+	// dataTypeExpression is optional and defaults to "*"
+	return function( dataTypeExpression, func ) {
+
+		if ( typeof dataTypeExpression !== "string" ) {
+			func = dataTypeExpression;
+			dataTypeExpression = "*";
+		}
+
+		var dataType,
+			i = 0,
+			dataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || [];
+
+		if ( jQuery.isFunction( func ) ) {
+			// For each dataType in the dataTypeExpression
+			while ( (dataType = dataTypes[i++]) ) {
+				// Prepend if requested
+				if ( dataType[0] === "+" ) {
+					dataType = dataType.slice( 1 ) || "*";
+					(structure[ dataType ] = structure[ dataType ] || []).unshift( func );
+
+				// Otherwise append
+				} else {
+					(structure[ dataType ] = structure[ dataType ] || []).push( func );
+				}
+			}
+		}
+	};
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
+
+	var inspected = {},
+		seekingTransport = ( structure === transports );
+
+	function inspect( dataType ) {
+		var selected;
+		inspected[ dataType ] = true;
+		jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
+			var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
+			if ( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {
+				options.dataTypes.unshift( dataTypeOrTransport );
+				inspect( dataTypeOrTransport );
+				return false;
+			} else if ( seekingTransport ) {
+				return !( selected = dataTypeOrTransport );
+			}
+		});
+		return selected;
+	}
+
+	return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+	var key, deep,
+		flatOptions = jQuery.ajaxSettings.flatOptions || {};
+
+	for ( key in src ) {
+		if ( src[ key ] !== undefined ) {
+			( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];
+		}
+	}
+	if ( deep ) {
+		jQuery.extend( true, target, deep );
+	}
+
+	return target;
+}
+
+/* Handles responses to an ajax request:
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+	var ct, type, finalDataType, firstDataType,
+		contents = s.contents,
+		dataTypes = s.dataTypes;
+
+	// Remove auto dataType and get content-type in the process
+	while ( dataTypes[ 0 ] === "*" ) {
+		dataTypes.shift();
+		if ( ct === undefined ) {
+			ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
+		}
+	}
+
+	// Check if we're dealing with a known content-type
+	if ( ct ) {
+		for ( type in contents ) {
+			if ( contents[ type ] && contents[ type ].test( ct ) ) {
+				dataTypes.unshift( type );
+				break;
+			}
+		}
+	}
+
+	// Check to see if we have a response for the expected dataType
+	if ( dataTypes[ 0 ] in responses ) {
+		finalDataType = dataTypes[ 0 ];
+	} else {
+		// Try convertible dataTypes
+		for ( type in responses ) {
+			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+				finalDataType = type;
+				break;
+			}
+			if ( !firstDataType ) {
+				firstDataType = type;
+			}
+		}
+		// Or just use first one
+		finalDataType = finalDataType || firstDataType;
+	}
+
+	// If we found a dataType
+	// We add the dataType to the list if needed
+	// and return the corresponding response
+	if ( finalDataType ) {
+		if ( finalDataType !== dataTypes[ 0 ] ) {
+			dataTypes.unshift( finalDataType );
+		}
+		return responses[ finalDataType ];
+	}
+}
+
+/* Chain conversions given the request and the original response
+ * Also sets the responseXXX fields on the jqXHR instance
+ */
+function ajaxConvert( s, response, jqXHR, isSuccess ) {
+	var conv2, current, conv, tmp, prev,
+		converters = {},
+		// Work with a copy of dataTypes in case we need to modify it for conversion
+		dataTypes = s.dataTypes.slice();
+
+	// Create converters map with lowercased keys
+	if ( dataTypes[ 1 ] ) {
+		for ( conv in s.converters ) {
+			converters[ conv.toLowerCase() ] = s.converters[ conv ];
+		}
+	}
+
+	current = dataTypes.shift();
+
+	// Convert to each sequential dataType
+	while ( current ) {
+
+		if ( s.responseFields[ current ] ) {
+			jqXHR[ s.responseFields[ current ] ] = response;
+		}
+
+		// Apply the dataFilter if provided
+		if ( !prev && isSuccess && s.dataFilter ) {
+			response = s.dataFilter( response, s.dataType );
+		}
+
+		prev = current;
+		current = dataTypes.shift();
+
+		if ( current ) {
+
+		// There's only work to do if current dataType is non-auto
+			if ( current === "*" ) {
+
+				current = prev;
+
+			// Convert response if prev dataType is non-auto and differs from current
+			} else if ( prev !== "*" && prev !== current ) {
+
+				// Seek a direct converter
+				conv = converters[ prev + " " + current ] || converters[ "* " + current ];
+
+				// If none found, seek a pair
+				if ( !conv ) {
+					for ( conv2 in converters ) {
+
+						// If conv2 outputs current
+						tmp = conv2.split( " " );
+						if ( tmp[ 1 ] === current ) {
+
+							// If prev can be converted to accepted input
+							conv = converters[ prev + " " + tmp[ 0 ] ] ||
+								converters[ "* " + tmp[ 0 ] ];
+							if ( conv ) {
+								// Condense equivalence converters
+								if ( conv === true ) {
+									conv = converters[ conv2 ];
+
+								// Otherwise, insert the intermediate dataType
+								} else if ( converters[ conv2 ] !== true ) {
+									current = tmp[ 0 ];
+									dataTypes.unshift( tmp[ 1 ] );
+								}
+								break;
+							}
+						}
+					}
+				}
+
+				// Apply converter (if not an equivalence)
+				if ( conv !== true ) {
+
+					// Unless errors are allowed to bubble, catch and return them
+					if ( conv && s[ "throws" ] ) {
+						response = conv( response );
+					} else {
+						try {
+							response = conv( response );
+						} catch ( e ) {
+							return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
+						}
+					}
+				}
+			}
+		}
+	}
+
+	return { state: "success", data: response };
+}
+
+jQuery.extend({
+
+	// Counter for holding the number of active queries
+	active: 0,
+
+	// Last-Modified header cache for next request
+	lastModified: {},
+	etag: {},
+
+	ajaxSettings: {
+		url: ajaxLocation,
+		type: "GET",
+		isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+		global: true,
+		processData: true,
+		async: true,
+		contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+		/*
+		timeout: 0,
+		data: null,
+		dataType: null,
+		username: null,
+		password: null,
+		cache: null,
+		throws: false,
+		traditional: false,
+		headers: {},
+		*/
+
+		accepts: {
+			"*": allTypes,
+			text: "text/plain",
+			html: "text/html",
+			xml: "application/xml, text/xml",
+			json: "application/json, text/javascript"
+		},
+
+		contents: {
+			xml: /xml/,
+			html: /html/,
+			json: /json/
+		},
+
+		responseFields: {
+			xml: "responseXML",
+			text: "responseText",
+			json: "responseJSON"
+		},
+
+		// Data converters
+		// Keys separate source (or catchall "*") and destination types with a single space
+		converters: {
+
+			// Convert anything to text
+			"* text": String,
+
+			// Text to html (true = no transformation)
+			"text html": true,
+
+			// Evaluate text as a json expression
+			"text json": jQuery.parseJSON,
+
+			// Parse text as xml
+			"text xml": jQuery.parseXML
+		},
+
+		// For options that shouldn't be deep extended:
+		// you can add your own custom options here if
+		// and when you create one that shouldn't be
+		// deep extended (see ajaxExtend)
+		flatOptions: {
+			url: true,
+			context: true
+		}
+	},
+
+	// Creates a full fledged settings object into target
+	// with both ajaxSettings and settings fields.
+	// If target is omitted, writes into ajaxSettings.
+	ajaxSetup: function( target, settings ) {
+		return settings ?
+
+			// Building a settings object
+			ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
+
+			// Extending ajaxSettings
+			ajaxExtend( jQuery.ajaxSettings, target );
+	},
+
+	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+	ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+	// Main method
+	ajax: function( url, options ) {
+
+		// If url is an object, simulate pre-1.5 signature
+		if ( typeof url === "object" ) {
+			options = url;
+			url = undefined;
+		}
+
+		// Force options to be an object
+		options = options || {};
+
+		var transport,
+			// URL without anti-cache param
+			cacheURL,
+			// Response headers
+			responseHeadersString,
+			responseHeaders,
+			// timeout handle
+			timeoutTimer,
+			// Cross-domain detection vars
+			parts,
+			// To know if global events are to be dispatched
+			fireGlobals,
+			// Loop variable
+			i,
+			// Create the final options object
+			s = jQuery.ajaxSetup( {}, options ),
+			// Callbacks context
+			callbackContext = s.context || s,
+			// Context for global events is callbackContext if it is a DOM node or jQuery collection
+			globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
+				jQuery( callbackContext ) :
+				jQuery.event,
+			// Deferreds
+			deferred = jQuery.Deferred(),
+			completeDeferred = jQuery.Callbacks("once memory"),
+			// Status-dependent callbacks
+			statusCode = s.statusCode || {},
+			// Headers (they are sent all at once)
+			requestHeaders = {},
+			requestHeadersNames = {},
+			// The jqXHR state
+			state = 0,
+			// Default abort message
+			strAbort = "canceled",
+			// Fake xhr
+			jqXHR = {
+				readyState: 0,
+
+				// Builds headers hashtable if needed
+				getResponseHeader: function( key ) {
+					var match;
+					if ( state === 2 ) {
+						if ( !responseHeaders ) {
+							responseHeaders = {};
+							while ( (match = rheaders.exec( responseHeadersString )) ) {
+								responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+							}
+						}
+						match = responseHeaders[ key.toLowerCase() ];
+					}
+					return match == null ? null : match;
+				},
+
+				// Raw string
+				getAllResponseHeaders: function() {
+					return state === 2 ? responseHeadersString : null;
+				},
+
+				// Caches the header
+				setRequestHeader: function( name, value ) {
+					var lname = name.toLowerCase();
+					if ( !state ) {
+						name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+						requestHeaders[ name ] = value;
+					}
+					return this;
+				},
+
+				// Overrides response content-type header
+				overrideMimeType: function( type ) {
+					if ( !state ) {
+						s.mimeType = type;
+					}
+					return this;
+				},
+
+				// Status-dependent callbacks
+				statusCode: function( map ) {
+					var code;
+					if ( map ) {
+						if ( state < 2 ) {
+							for ( code in map ) {
+								// Lazy-add the new callback in a way that preserves old ones
+								statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
+							}
+						} else {
+							// Execute the appropriate callbacks
+							jqXHR.always( map[ jqXHR.status ] );
+						}
+					}
+					return this;
+				},
+
+				// Cancel the request
+				abort: function( statusText ) {
+					var finalText = statusText || strAbort;
+					if ( transport ) {
+						transport.abort( finalText );
+					}
+					done( 0, finalText );
+					return this;
+				}
+			};
+
+		// Attach deferreds
+		deferred.promise( jqXHR ).complete = completeDeferred.add;
+		jqXHR.success = jqXHR.done;
+		jqXHR.error = jqXHR.fail;
+
+		// Remove hash character (#7531: and string promotion)
+		// Add protocol if not provided (prefilters might expect it)
+		// Handle falsy url in the settings object (#10093: consistency with old signature)
+		// We also use the url parameter if available
+		s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" )
+			.replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+		// Alias method option to type as per ticket #12004
+		s.type = options.method || options.type || s.method || s.type;
+
+		// Extract dataTypes list
+		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( rnotwhite ) || [ "" ];
+
+		// A cross-domain request is in order when we have a protocol:host:port mismatch
+		if ( s.crossDomain == null ) {
+			parts = rurl.exec( s.url.toLowerCase() );
+			s.crossDomain = !!( parts &&
+				( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
+					( parts[ 3 ] || ( parts[ 1 ] === "http:" ? "80" : "443" ) ) !==
+						( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? "80" : "443" ) ) )
+			);
+		}
+
+		// Convert data if not already a string
+		if ( s.data && s.processData && typeof s.data !== "string" ) {
+			s.data = jQuery.param( s.data, s.traditional );
+		}
+
+		// Apply prefilters
+		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+		// If request was aborted inside a prefilter, stop there
+		if ( state === 2 ) {
+			return jqXHR;
+		}
+
+		// We can fire global events as of now if asked to
+		// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
+		fireGlobals = jQuery.event && s.global;
+
+		// Watch for a new set of requests
+		if ( fireGlobals && jQuery.active++ === 0 ) {
+			jQuery.event.trigger("ajaxStart");
+		}
+
+		// Uppercase the type
+		s.type = s.type.toUpperCase();
+
+		// Determine if request has content
+		s.hasContent = !rnoContent.test( s.type );
+
+		// Save the URL in case we're toying with the If-Modified-Since
+		// and/or If-None-Match header later on
+		cacheURL = s.url;
+
+		// More options handling for requests with no content
+		if ( !s.hasContent ) {
+
+			// If data is available, append data to url
+			if ( s.data ) {
+				cacheURL = ( s.url += ( rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
+				// #9682: remove data so that it's not used in an eventual retry
+				delete s.data;
+			}
+
+			// Add anti-cache in url if needed
+			if ( s.cache === false ) {
+				s.url = rts.test( cacheURL ) ?
+
+					// If there is already a '_' parameter, set its value
+					cacheURL.replace( rts, "$1_=" + nonce++ ) :
+
+					// Otherwise add one to the end
+					cacheURL + ( rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + nonce++;
+			}
+		}
+
+		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+		if ( s.ifModified ) {
+			if ( jQuery.lastModified[ cacheURL ] ) {
+				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
+			}
+			if ( jQuery.etag[ cacheURL ] ) {
+				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
+			}
+		}
+
+		// Set the correct header, if data is being sent
+		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+			jqXHR.setRequestHeader( "Content-Type", s.contentType );
+		}
+
+		// Set the Accepts header for the server, depending on the dataType
+		jqXHR.setRequestHeader(
+			"Accept",
+			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+				s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+				s.accepts[ "*" ]
+		);
+
+		// Check for headers option
+		for ( i in s.headers ) {
+			jqXHR.setRequestHeader( i, s.headers[ i ] );
+		}
+
+		// Allow custom headers/mimetypes and early abort
+		if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+			// Abort if not done already and return
+			return jqXHR.abort();
+		}
+
+		// Aborting is no longer a cancellation
+		strAbort = "abort";
+
+		// Install callbacks on deferreds
+		for ( i in { success: 1, error: 1, complete: 1 } ) {
+			jqXHR[ i ]( s[ i ] );
+		}
+
+		// Get transport
+		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+		// If no transport, we auto-abort
+		if ( !transport ) {
+			done( -1, "No Transport" );
+		} else {
+			jqXHR.readyState = 1;
+
+			// Send global event
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+			}
+			// Timeout
+			if ( s.async && s.timeout > 0 ) {
+				timeoutTimer = setTimeout(function() {
+					jqXHR.abort("timeout");
+				}, s.timeout );
+			}
+
+			try {
+				state = 1;
+				transport.send( requestHeaders, done );
+			} catch ( e ) {
+				// Propagate exception as error if not done
+				if ( state < 2 ) {
+					done( -1, e );
+				// Simply rethrow otherwise
+				} else {
+					throw e;
+				}
+			}
+		}
+
+		// Callback for when everything is done
+		function done( status, nativeStatusText, responses, headers ) {
+			var isSuccess, success, error, response, modified,
+				statusText = nativeStatusText;
+
+			// Called once
+			if ( state === 2 ) {
+				return;
+			}
+
+			// State is "done" now
+			state = 2;
+
+			// Clear timeout if it exists
+			if ( timeoutTimer ) {
+				clearTimeout( timeoutTimer );
+			}
+
+			// Dereference transport for early garbage collection
+			// (no matter how long the jqXHR object will be used)
+			transport = undefined;
+
+			// Cache response headers
+			responseHeadersString = headers || "";
+
+			// Set readyState
+			jqXHR.readyState = status > 0 ? 4 : 0;
+
+			// Determine if successful
+			isSuccess = status >= 200 && status < 300 || status === 304;
+
+			// Get response data
+			if ( responses ) {
+				response = ajaxHandleResponses( s, jqXHR, responses );
+			}
+
+			// Convert no matter what (that way responseXXX fields are always set)
+			response = ajaxConvert( s, response, jqXHR, isSuccess );
+
+			// If successful, handle type chaining
+			if ( isSuccess ) {
+
+				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+				if ( s.ifModified ) {
+					modified = jqXHR.getResponseHeader("Last-Modified");
+					if ( modified ) {
+						jQuery.lastModified[ cacheURL ] = modified;
+					}
+					modified = jqXHR.getResponseHeader("etag");
+					if ( modified ) {
+						jQuery.etag[ cacheURL ] = modified;
+					}
+				}
+
+				// if no content
+				if ( status === 204 || s.type === "HEAD" ) {
+					statusText = "nocontent";
+
+				// if not modified
+				} else if ( status === 304 ) {
+					statusText = "notmodified";
+
+				// If we have data, let's convert it
+				} else {
+					statusText = response.state;
+					success = response.data;
+					error = response.error;
+					isSuccess = !error;
+				}
+			} else {
+				// Extract error from statusText and normalize for non-aborts
+				error = statusText;
+				if ( status || !statusText ) {
+					statusText = "error";
+					if ( status < 0 ) {
+						status = 0;
+					}
+				}
+			}
+
+			// Set data for the fake xhr object
+			jqXHR.status = status;
+			jqXHR.statusText = ( nativeStatusText || statusText ) + "";
+
+			// Success/Error
+			if ( isSuccess ) {
+				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+			} else {
+				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+			}
+
+			// Status-dependent callbacks
+			jqXHR.statusCode( statusCode );
+			statusCode = undefined;
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
+					[ jqXHR, s, isSuccess ? success : error ] );
+			}
+
+			// Complete
+			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+			if ( fireGlobals ) {
+				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+				// Handle the global AJAX counter
+				if ( !( --jQuery.active ) ) {
+					jQuery.event.trigger("ajaxStop");
+				}
+			}
+		}
+
+		return jqXHR;
+	},
+
+	getJSON: function( url, data, callback ) {
+		return jQuery.get( url, data, callback, "json" );
+	},
+
+	getScript: function( url, callback ) {
+		return jQuery.get( url, undefined, callback, "script" );
+	}
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+	jQuery[ method ] = function( url, data, callback, type ) {
+		// Shift arguments if data argument was omitted
+		if ( jQuery.isFunction( data ) ) {
+			type = type || callback;
+			callback = data;
+			data = undefined;
+		}
+
+		return jQuery.ajax({
+			url: url,
+			type: method,
+			dataType: type,
+			data: data,
+			success: callback
+		});
+	};
+});
+
+
+jQuery._evalUrl = function( url ) {
+	return jQuery.ajax({
+		url: url,
+		type: "GET",
+		dataType: "script",
+		async: false,
+		global: false,
+		"throws": true
+	});
+};
+
+
+jQuery.fn.extend({
+	wrapAll: function( html ) {
+		var wrap;
+
+		if ( jQuery.isFunction( html ) ) {
+			return this.each(function( i ) {
+				jQuery( this ).wrapAll( html.call(this, i) );
+			});
+		}
+
+		if ( this[ 0 ] ) {
+
+			// The elements to wrap the target around
+			wrap = jQuery( html, this[ 0 ].ownerDocument ).eq( 0 ).clone( true );
+
+			if ( this[ 0 ].parentNode ) {
+				wrap.insertBefore( this[ 0 ] );
+			}
+
+			wrap.map(function() {
+				var elem = this;
+
+				while ( elem.firstElementChild ) {
+					elem = elem.firstElementChild;
+				}
+
+				return elem;
+			}).append( this );
+		}
+
+		return this;
+	},
+
+	wrapInner: function( html ) {
+		if ( jQuery.isFunction( html ) ) {
+			return this.each(function( i ) {
+				jQuery( this ).wrapInner( html.call(this, i) );
+			});
+		}
+
+		return this.each(function() {
+			var self = jQuery( this ),
+				contents = self.contents();
+
+			if ( contents.length ) {
+				contents.wrapAll( html );
+
+			} else {
+				self.append( html );
+			}
+		});
+	},
+
+	wrap: function( html ) {
+		var isFunction = jQuery.isFunction( html );
+
+		return this.each(function( i ) {
+			jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+		});
+	},
+
+	unwrap: function() {
+		return this.parent().each(function() {
+			if ( !jQuery.nodeName( this, "body" ) ) {
+				jQuery( this ).replaceWith( this.childNodes );
+			}
+		}).end();
+	}
+});
+
+
+jQuery.expr.filters.hidden = function( elem ) {
+	// Support: Opera <= 12.12
+	// Opera reports offsetWidths and offsetHeights less than zero on some elements
+	return elem.offsetWidth <= 0 && elem.offsetHeight <= 0;
+};
+jQuery.expr.filters.visible = function( elem ) {
+	return !jQuery.expr.filters.hidden( elem );
+};
+
+
+
+
+var r20 = /%20/g,
+	rbracket = /\[\]$/,
+	rCRLF = /\r?\n/g,
+	rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
+	rsubmittable = /^(?:input|select|textarea|keygen)/i;
+
+function buildParams( prefix, obj, traditional, add ) {
+	var name;
+
+	if ( jQuery.isArray( obj ) ) {
+		// Serialize array item.
+		jQuery.each( obj, function( i, v ) {
+			if ( traditional || rbracket.test( prefix ) ) {
+				// Treat each array item as a scalar.
+				add( prefix, v );
+
+			} else {
+				// Item is non-scalar (array or object), encode its numeric index.
+				buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
+			}
+		});
+
+	} else if ( !traditional && jQuery.type( obj ) === "object" ) {
+		// Serialize object item.
+		for ( name in obj ) {
+			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+		}
+
+	} else {
+		// Serialize scalar item.
+		add( prefix, obj );
+	}
+}
+
+// Serialize an array of form elements or a set of
+// key/values into a query string
+jQuery.param = function( a, traditional ) {
+	var prefix,
+		s = [],
+		add = function( key, value ) {
+			// If value is a function, invoke it and return its value
+			value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
+			s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+		};
+
+	// Set traditional to true for jQuery <= 1.3.2 behavior.
+	if ( traditional === undefined ) {
+		traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
+	}
+
+	// If an array was passed in, assume that it is an array of form elements.
+	if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+		// Serialize the form elements
+		jQuery.each( a, function() {
+			add( this.name, this.value );
+		});
+
+	} else {
+		// If traditional, encode the "old" way (the way 1.3.2 or older
+		// did it), otherwise encode params recursively.
+		for ( prefix in a ) {
+			buildParams( prefix, a[ prefix ], traditional, add );
+		}
+	}
+
+	// Return the resulting serialization
+	return s.join( "&" ).replace( r20, "+" );
+};
+
+jQuery.fn.extend({
+	serialize: function() {
+		return jQuery.param( this.serializeArray() );
+	},
+	serializeArray: function() {
+		return this.map(function() {
+			// Can add propHook for "elements" to filter or add form elements
+			var elements = jQuery.prop( this, "elements" );
+			return elements ? jQuery.makeArray( elements ) : this;
+		})
+		.filter(function() {
+			var type = this.type;
+
+			// Use .is( ":disabled" ) so that fieldset[disabled] works
+			return this.name && !jQuery( this ).is( ":disabled" ) &&
+				rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
+				( this.checked || !rcheckableType.test( type ) );
+		})
+		.map(function( i, elem ) {
+			var val = jQuery( this ).val();
+
+			return val == null ?
+				null :
+				jQuery.isArray( val ) ?
+					jQuery.map( val, function( val ) {
+						return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+					}) :
+					{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+		}).get();
+	}
+});
+
+
+jQuery.ajaxSettings.xhr = function() {
+	try {
+		return new XMLHttpRequest();
+	} catch( e ) {}
+};
+
+var xhrId = 0,
+	xhrCallbacks = {},
+	xhrSuccessStatus = {
+		// file protocol always yields status code 0, assume 200
+		0: 200,
+		// Support: IE9
+		// #1450: sometimes IE returns 1223 when it should be 204
+		1223: 204
+	},
+	xhrSupported = jQuery.ajaxSettings.xhr();
+
+// Support: IE9
+// Open requests must be manually aborted on unload (#5280)
+// See https://support.microsoft.com/kb/2856746 for more info
+if ( window.attachEvent ) {
+	window.attachEvent( "onunload", function() {
+		for ( var key in xhrCallbacks ) {
+			xhrCallbacks[ key ]();
+		}
+	});
+}
+
+support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
+support.ajax = xhrSupported = !!xhrSupported;
+
+jQuery.ajaxTransport(function( options ) {
+	var callback;
+
+	// Cross domain only allowed if supported through XMLHttpRequest
+	if ( support.cors || xhrSupported && !options.crossDomain ) {
+		return {
+			send: function( headers, complete ) {
+				var i,
+					xhr = options.xhr(),
+					id = ++xhrId;
+
+				xhr.open( options.type, options.url, options.async, options.username, options.password );
+
+				// Apply custom fields if provided
+				if ( options.xhrFields ) {
+					for ( i in options.xhrFields ) {
+						xhr[ i ] = options.xhrFields[ i ];
+					}
+				}
+
+				// Override mime type if needed
+				if ( options.mimeType && xhr.overrideMimeType ) {
+					xhr.overrideMimeType( options.mimeType );
+				}
+
+				// X-Requested-With header
+				// For cross-domain requests, seeing as conditions for a preflight are
+				// akin to a jigsaw puzzle, we simply never set it to be sure.
+				// (it can always be set on a per-request basis or even using ajaxSetup)
+				// For same-domain requests, won't change header if already provided.
+				if ( !options.crossDomain && !headers["X-Requested-With"] ) {
+					headers["X-Requested-With"] = "XMLHttpRequest";
+				}
+
+				// Set headers
+				for ( i in headers ) {
+					xhr.setRequestHeader( i, headers[ i ] );
+				}
+
+				// Callback
+				callback = function( type ) {
+					return function() {
+						if ( callback ) {
+							delete xhrCallbacks[ id ];
+							callback = xhr.onload = xhr.onerror = null;
+
+							if ( type === "abort" ) {
+								xhr.abort();
+							} else if ( type === "error" ) {
+								complete(
+									// file: protocol always yields status 0; see #8605, #14207
+									xhr.status,
+									xhr.statusText
+								);
+							} else {
+								complete(
+									xhrSuccessStatus[ xhr.status ] || xhr.status,
+									xhr.statusText,
+									// Support: IE9
+									// Accessing binary-data responseText throws an exception
+									// (#11426)
+									typeof xhr.responseText === "string" ? {
+										text: xhr.responseText
+									} : undefined,
+									xhr.getAllResponseHeaders()
+								);
+							}
+						}
+					};
+				};
+
+				// Listen to events
+				xhr.onload = callback();
+				xhr.onerror = callback("error");
+
+				// Create the abort callback
+				callback = xhrCallbacks[ id ] = callback("abort");
+
+				try {
+					// Do send the request (this may raise an exception)
+					xhr.send( options.hasContent && options.data || null );
+				} catch ( e ) {
+					// #14683: Only rethrow if this hasn't been notified as an error yet
+					if ( callback ) {
+						throw e;
+					}
+				}
+			},
+
+			abort: function() {
+				if ( callback ) {
+					callback();
+				}
+			}
+		};
+	}
+});
+
+
+
+
+// Install script dataType
+jQuery.ajaxSetup({
+	accepts: {
+		script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+	},
+	contents: {
+		script: /(?:java|ecma)script/
+	},
+	converters: {
+		"text script": function( text ) {
+			jQuery.globalEval( text );
+			return text;
+		}
+	}
+});
+
+// Handle cache's special case and crossDomain
+jQuery.ajaxPrefilter( "script", function( s ) {
+	if ( s.cache === undefined ) {
+		s.cache = false;
+	}
+	if ( s.crossDomain ) {
+		s.type = "GET";
+	}
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function( s ) {
+	// This transport only deals with cross domain requests
+	if ( s.crossDomain ) {
+		var script, callback;
+		return {
+			send: function( _, complete ) {
+				script = jQuery("<script>").prop({
+					async: true,
+					charset: s.scriptCharset,
+					src: s.url
+				}).on(
+					"load error",
+					callback = function( evt ) {
+						script.remove();
+						callback = null;
+						if ( evt ) {
+							complete( evt.type === "error" ? 404 : 200, evt.type );
+						}
+					}
+				);
+				document.head.appendChild( script[ 0 ] );
+			},
+			abort: function() {
+				if ( callback ) {
+					callback();
+				}
+			}
+		};
+	}
+});
+
+
+
+
+var oldCallbacks = [],
+	rjsonp = /(=)\?(?=&|$)|\?\?/;
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+	jsonp: "callback",
+	jsonpCallback: function() {
+		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
+		this[ callback ] = true;
+		return callback;
+	}
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+	var callbackName, overwritten, responseContainer,
+		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
+			"url" :
+			typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data"
+		);
+
+	// Handle iff the expected data type is "jsonp" or we have a parameter to set
+	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
+
+		// Get callback name, remembering preexisting value associated with it
+		callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
+			s.jsonpCallback() :
+			s.jsonpCallback;
+
+		// Insert callback into url or form data
+		if ( jsonProp ) {
+			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
+		} else if ( s.jsonp !== false ) {
+			s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
+		}
+
+		// Use data converter to retrieve json after script execution
+		s.converters["script json"] = function() {
+			if ( !responseContainer ) {
+				jQuery.error( callbackName + " was not called" );
+			}
+			return responseContainer[ 0 ];
+		};
+
+		// force json dataType
+		s.dataTypes[ 0 ] = "json";
+
+		// Install callback
+		overwritten = window[ callbackName ];
+		window[ callbackName ] = function() {
+			responseContainer = arguments;
+		};
+
+		// Clean-up function (fires after converters)
+		jqXHR.always(function() {
+			// Restore preexisting value
+			window[ callbackName ] = overwritten;
+
+			// Save back as free
+			if ( s[ callbackName ] ) {
+				// make sure that re-using the options doesn't screw things around
+				s.jsonpCallback = originalSettings.jsonpCallback;
+
+				// save the callback name for future use
+				oldCallbacks.push( callbackName );
+			}
+
+			// Call if it was a function and we have a response
+			if ( responseContainer && jQuery.isFunction( overwritten ) ) {
+				overwritten( responseContainer[ 0 ] );
+			}
+
+			responseContainer = overwritten = undefined;
+		});
+
+		// Delegate to script
+		return "script";
+	}
+});
+
+
+
+
+// data: string of html
+// context (optional): If specified, the fragment will be created in this context, defaults to document
+// keepScripts (optional): If true, will include scripts passed in the html string
+jQuery.parseHTML = function( data, context, keepScripts ) {
+	if ( !data || typeof data !== "string" ) {
+		return null;
+	}
+	if ( typeof context === "boolean" ) {
+		keepScripts = context;
+		context = false;
+	}
+	context = context || document;
+
+	var parsed = rsingleTag.exec( data ),
+		scripts = !keepScripts && [];
+
+	// Single tag
+	if ( parsed ) {
+		return [ context.createElement( parsed[1] ) ];
+	}
+
+	parsed = jQuery.buildFragment( [ data ], context, scripts );
+
+	if ( scripts && scripts.length ) {
+		jQuery( scripts ).remove();
+	}
+
+	return jQuery.merge( [], parsed.childNodes );
+};
+
+
+// Keep a copy of the old load method
+var _load = jQuery.fn.load;
+
+/**
+ * Load a url into a page
+ */
+jQuery.fn.load = function( url, params, callback ) {
+	if ( typeof url !== "string" && _load ) {
+		return _load.apply( this, arguments );
+	}
+
+	var selector, type, response,
+		self = this,
+		off = url.indexOf(" ");
+
+	if ( off >= 0 ) {
+		selector = jQuery.trim( url.slice( off ) );
+		url = url.slice( 0, off );
+	}
+
+	// If it's a function
+	if ( jQuery.isFunction( params ) ) {
+
+		// We assume that it's the callback
+		callback = params;
+		params = undefined;
+
+	// Otherwise, build a param string
+	} else if ( params && typeof params === "object" ) {
+		type = "POST";
+	}
+
+	// If we have elements to modify, make the request
+	if ( self.length > 0 ) {
+		jQuery.ajax({
+			url: url,
+
+			// if "type" variable is undefined, then "GET" method will be used
+			type: type,
+			dataType: "html",
+			data: params
+		}).done(function( responseText ) {
+
+			// Save response for use in complete callback
+			response = arguments;
+
+			self.html( selector ?
+
+				// If a selector was specified, locate the right elements in a dummy div
+				// Exclude scripts to avoid IE 'Permission Denied' errors
+				jQuery("<div>").append( jQuery.parseHTML( responseText ) ).find( selector ) :
+
+				// Otherwise use the full result
+				responseText );
+
+		}).complete( callback && function( jqXHR, status ) {
+			self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
+		});
+	}
+
+	return this;
+};
+
+
+
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ) {
+	jQuery.fn[ type ] = function( fn ) {
+		return this.on( type, fn );
+	};
+});
+
+
+
+
+jQuery.expr.filters.animated = function( elem ) {
+	return jQuery.grep(jQuery.timers, function( fn ) {
+		return elem === fn.elem;
+	}).length;
+};
+
+
+
+
+var docElem = window.document.documentElement;
+
+/**
+ * Gets a window from an element
+ */
+function getWindow( elem ) {
+	return jQuery.isWindow( elem ) ? elem : elem.nodeType === 9 && elem.defaultView;
+}
+
+jQuery.offset = {
+	setOffset: function( elem, options, i ) {
+		var curPosition, curLeft, curCSSTop, curTop, curOffset, curCSSLeft, calculatePosition,
+			position = jQuery.css( elem, "position" ),
+			curElem = jQuery( elem ),
+			props = {};
+
+		// Set position first, in-case top/left are set even on static elem
+		if ( position === "static" ) {
+			elem.style.position = "relative";
+		}
+
+		curOffset = curElem.offset();
+		curCSSTop = jQuery.css( elem, "top" );
+		curCSSLeft = jQuery.css( elem, "left" );
+		calculatePosition = ( position === "absolute" || position === "fixed" ) &&
+			( curCSSTop + curCSSLeft ).indexOf("auto") > -1;
+
+		// Need to be able to calculate position if either
+		// top or left is auto and position is either absolute or fixed
+		if ( calculatePosition ) {
+			curPosition = curElem.position();
+			curTop = curPosition.top;
+			curLeft = curPosition.left;
+
+		} else {
+			curTop = parseFloat( curCSSTop ) || 0;
+			curLeft = parseFloat( curCSSLeft ) || 0;
+		}
+
+		if ( jQuery.isFunction( options ) ) {
+			options = options.call( elem, i, curOffset );
+		}
+
+		if ( options.top != null ) {
+			props.top = ( options.top - curOffset.top ) + curTop;
+		}
+		if ( options.left != null ) {
+			props.left = ( options.left - curOffset.left ) + curLeft;
+		}
+
+		if ( "using" in options ) {
+			options.using.call( elem, props );
+
+		} else {
+			curElem.css( props );
+		}
+	}
+};
+
+jQuery.fn.extend({
+	offset: function( options ) {
+		if ( arguments.length ) {
+			return options === undefined ?
+				this :
+				this.each(function( i ) {
+					jQuery.offset.setOffset( this, options, i );
+				});
+		}
+
+		var docElem, win,
+			elem = this[ 0 ],
+			box = { top: 0, left: 0 },
+			doc = elem && elem.ownerDocument;
+
+		if ( !doc ) {
+			return;
+		}
+
+		docElem = doc.documentElement;
+
+		// Make sure it's not a disconnected DOM node
+		if ( !jQuery.contains( docElem, elem ) ) {
+			return box;
+		}
+
+		// Support: BlackBerry 5, iOS 3 (original iPhone)
+		// If we don't have gBCR, just use 0,0 rather than error
+		if ( typeof elem.getBoundingClientRect !== strundefined ) {
+			box = elem.getBoundingClientRect();
+		}
+		win = getWindow( doc );
+		return {
+			top: box.top + win.pageYOffset - docElem.clientTop,
+			left: box.left + win.pageXOffset - docElem.clientLeft
+		};
+	},
+
+	position: function() {
+		if ( !this[ 0 ] ) {
+			return;
+		}
+
+		var offsetParent, offset,
+			elem = this[ 0 ],
+			parentOffset = { top: 0, left: 0 };
+
+		// Fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is its only offset parent
+		if ( jQuery.css( elem, "position" ) === "fixed" ) {
+			// Assume getBoundingClientRect is there when computed position is fixed
+			offset = elem.getBoundingClientRect();
+
+		} else {
+			// Get *real* offsetParent
+			offsetParent = this.offsetParent();
+
+			// Get correct offsets
+			offset = this.offset();
+			if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
+				parentOffset = offsetParent.offset();
+			}
+
+			// Add offsetParent borders
+			parentOffset.top += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
+			parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
+		}
+
+		// Subtract parent offsets and element margins
+		return {
+			top: offset.top - parentOffset.top - jQuery.css( elem, "marginTop", true ),
+			left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true )
+		};
+	},
+
+	offsetParent: function() {
+		return this.map(function() {
+			var offsetParent = this.offsetParent || docElem;
+
+			while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position" ) === "static" ) ) {
+				offsetParent = offsetParent.offsetParent;
+			}
+
+			return offsetParent || docElem;
+		});
+	}
+});
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( { scrollLeft: "pageXOffset", scrollTop: "pageYOffset" }, function( method, prop ) {
+	var top = "pageYOffset" === prop;
+
+	jQuery.fn[ method ] = function( val ) {
+		return access( this, function( elem, method, val ) {
+			var win = getWindow( elem );
+
+			if ( val === undefined ) {
+				return win ? win[ prop ] : elem[ method ];
+			}
+
+			if ( win ) {
+				win.scrollTo(
+					!top ? val : window.pageXOffset,
+					top ? val : window.pageYOffset
+				);
+
+			} else {
+				elem[ method ] = val;
+			}
+		}, method, val, arguments.length, null );
+	};
+});
+
+// Support: Safari<7+, Chrome<37+
+// Add the top/left cssHooks using jQuery.fn.position
+// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+// Blink bug: https://code.google.com/p/chromium/issues/detail?id=229280
+// getComputedStyle returns percent when specified for top/left/bottom/right;
+// rather than make the css module depend on the offset module, just check for it here
+jQuery.each( [ "top", "left" ], function( i, prop ) {
+	jQuery.cssHooks[ prop ] = addGetHookIf( support.pixelPosition,
+		function( elem, computed ) {
+			if ( computed ) {
+				computed = curCSS( elem, prop );
+				// If curCSS returns percentage, fallback to offset
+				return rnumnonpx.test( computed ) ?
+					jQuery( elem ).position()[ prop ] + "px" :
+					computed;
+			}
+		}
+	);
+});
+
+
+// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+	jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
+		// Margin is only for outerHeight, outerWidth
+		jQuery.fn[ funcName ] = function( margin, value ) {
+			var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
+				extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
+
+			return access( this, function( elem, type, value ) {
+				var doc;
+
+				if ( jQuery.isWindow( elem ) ) {
+					// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
+					// isn't a whole lot we can do. See pull request at this URL for discussion:
+					// https://github.com/jquery/jquery/pull/764
+					return elem.document.documentElement[ "client" + name ];
+				}
+
+				// Get document width or height
+				if ( elem.nodeType === 9 ) {
+					doc = elem.documentElement;
+
+					// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height],
+					// whichever is greatest
+					return Math.max(
+						elem.body[ "scroll" + name ], doc[ "scroll" + name ],
+						elem.body[ "offset" + name ], doc[ "offset" + name ],
+						doc[ "client" + name ]
+					);
+				}
+
+				return value === undefined ?
+					// Get width or height on the element, requesting but not forcing parseFloat
+					jQuery.css( elem, type, extra ) :
+
+					// Set width or height on the element
+					jQuery.style( elem, type, value, extra );
+			}, type, chainable ? margin : undefined, chainable, null );
+		};
+	});
+});
+
+
+// The number of elements contained in the matched element set
+jQuery.fn.size = function() {
+	return this.length;
+};
+
+jQuery.fn.andSelf = jQuery.fn.addBack;
+
+
+
+
+// Register as a named AMD module, since jQuery can be concatenated with other
+// files that may use define, but not via a proper concatenation script that
+// understands anonymous AMD modules. A named AMD is safest and most robust
+// way to register. Lowercase jquery is used because AMD module names are
+// derived from file names, and jQuery is normally delivered in a lowercase
+// file name. Do this after creating the global so that if an AMD module wants
+// to call noConflict to hide this version of jQuery, it will work.
+
+// Note that for maximum portability, libraries that are not jQuery should
+// declare themselves as anonymous modules, and avoid setting a global if an
+// AMD loader is present. jQuery is a special case. For more information, see
+// https://github.com/jrburke/requirejs/wiki/Updating-existing-libraries#wiki-anon
+
+if ( typeof define === "function" && define.amd ) {
+	define( "jquery", [], function() {
+		return jQuery;
+	});
+}
+
+
+
+
+var
+	// Map over jQuery in case of overwrite
+	_jQuery = window.jQuery,
+
+	// Map over the $ in case of overwrite
+	_$ = window.$;
+
+jQuery.noConflict = function( deep ) {
+	if ( window.$ === jQuery ) {
+		window.$ = _$;
+	}
+
+	if ( deep && window.jQuery === jQuery ) {
+		window.jQuery = _jQuery;
+	}
+
+	return jQuery;
+};
+
+// Expose jQuery and $ identifiers, even in AMD
+// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
+// and CommonJS for browser emulators (#13566)
+if ( typeof noGlobal === strundefined ) {
+	window.jQuery = window.$ = jQuery;
+}
+
+
+
+
+return jQuery;
+
+}));
diff --git a/src/legacy/design-studio/js/jqueryui-editable.js b/src/legacy/design-studio/js/jqueryui-editable.js
new file mode 100644
index 0000000000000000000000000000000000000000..83b966176b0eef555eba0f07c4603108e39f2de0
--- /dev/null
+++ b/src/legacy/design-studio/js/jqueryui-editable.js
@@ -0,0 +1,5065 @@
+/*! X-editable - v1.5.1 
+* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
+* http://github.com/vitalets/x-editable
+* Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
+/**
+Form with single input element, two buttons and two states: normal/loading.
+Applied as jQuery method to DIV tag (not to form tag!). This is because form can be in loading state when spinner shown.
+Editableform is linked with one of input types, e.g. 'text', 'select' etc.
+
+@class editableform
+@uses text
+@uses textarea
+**/
+(function ($) {
+    "use strict";
+    
+    var EditableForm = function (div, options) {
+        this.options = $.extend({}, $.fn.editableform.defaults, options);
+        this.$div = $(div); //div, containing form. Not form tag. Not editable-element.
+        if(!this.options.scope) {
+            this.options.scope = this;
+        }
+        //nothing shown after init
+    };
+
+    EditableForm.prototype = {
+        constructor: EditableForm,
+        initInput: function() {  //called once
+            //take input from options (as it is created in editable-element)
+            this.input = this.options.input;
+            
+            //set initial value
+            //todo: may be add check: typeof str === 'string' ? 
+            this.value = this.input.str2value(this.options.value); 
+            
+            //prerender: get input.$input
+            this.input.prerender();
+        },
+        initTemplate: function() {
+            this.$form = $($.fn.editableform.template); 
+        },
+        initButtons: function() {
+            var $btn = this.$form.find('.editable-buttons');
+            $btn.append($.fn.editableform.buttons);
+            if(this.options.showbuttons === 'bottom') {
+                $btn.addClass('editable-buttons-bottom');
+            }
+        },
+        /**
+        Renders editableform
+
+        @method render
+        **/        
+        render: function() {
+            //init loader
+            this.$loading = $($.fn.editableform.loading);        
+            this.$div.empty().append(this.$loading);
+            
+            //init form template and buttons
+            this.initTemplate();
+            if(this.options.showbuttons) {
+                this.initButtons();
+            } else {
+                this.$form.find('.editable-buttons').remove();
+            }
+
+            //show loading state
+            this.showLoading();            
+            
+            //flag showing is form now saving value to server. 
+            //It is needed to wait when closing form.
+            this.isSaving = false;
+            
+            /**        
+            Fired when rendering starts
+            @event rendering 
+            @param {Object} event event object
+            **/            
+            this.$div.triggerHandler('rendering');
+            
+            //init input
+            this.initInput();
+            
+            //append input to form
+            this.$form.find('div.editable-input').append(this.input.$tpl);            
+            
+            //append form to container
+            this.$div.append(this.$form);
+            
+            //render input
+            $.when(this.input.render())
+            .then($.proxy(function () {
+                //setup input to submit automatically when no buttons shown
+                if(!this.options.showbuttons) {
+                    this.input.autosubmit(); 
+                }
+                 
+                //attach 'cancel' handler
+                this.$form.find('.editable-cancel').click($.proxy(this.cancel, this));
+                
+                if(this.input.error) {
+                    this.error(this.input.error);
+                    this.$form.find('.editable-submit').attr('disabled', true);
+                    this.input.$input.attr('disabled', true);
+                    //prevent form from submitting
+                    this.$form.submit(function(e){ e.preventDefault(); });
+                } else {
+                    this.error(false);
+                    this.input.$input.removeAttr('disabled');
+                    this.$form.find('.editable-submit').removeAttr('disabled');
+                    var value = (this.value === null || this.value === undefined || this.value === '') ? this.options.defaultValue : this.value;
+                    this.input.value2input(value);
+                    //attach submit handler
+                    this.$form.submit($.proxy(this.submit, this));
+                }
+
+                /**        
+                Fired when form is rendered
+                @event rendered
+                @param {Object} event event object
+                **/            
+                this.$div.triggerHandler('rendered');                
+
+                this.showForm();
+                
+                //call postrender method to perform actions required visibility of form
+                if(this.input.postrender) {
+                    this.input.postrender();
+                }                
+            }, this));
+        },
+        cancel: function() {   
+            /**        
+            Fired when form was cancelled by user
+            @event cancel 
+            @param {Object} event event object
+            **/              
+            this.$div.triggerHandler('cancel');
+        },
+        showLoading: function() {
+            var w, h;
+            if(this.$form) {
+                //set loading size equal to form
+                w = this.$form.outerWidth();
+                h = this.$form.outerHeight(); 
+                if(w) {
+                    this.$loading.width(w);
+                }
+                if(h) {
+                    this.$loading.height(h);
+                }
+                this.$form.hide();
+            } else {
+                //stretch loading to fill container width
+                w = this.$loading.parent().width();
+                if(w) {
+                    this.$loading.width(w);
+                }
+            }
+            this.$loading.show(); 
+        },
+
+        showForm: function(activate) {
+            this.$loading.hide();
+            this.$form.show();
+            if(activate !== false) {
+                this.input.activate(); 
+            }
+            /**        
+            Fired when form is shown
+            @event show 
+            @param {Object} event event object
+            **/                    
+            this.$div.triggerHandler('show');
+        },
+
+        error: function(msg) {
+            var $group = this.$form.find('.control-group'),
+                $block = this.$form.find('.editable-error-block'),
+                lines;
+
+            if(msg === false) {
+                $group.removeClass($.fn.editableform.errorGroupClass);
+                $block.removeClass($.fn.editableform.errorBlockClass).empty().hide(); 
+            } else {
+                //convert newline to <br> for more pretty error display
+                if(msg) {
+                    lines = (''+msg).split('\n');
+                    for (var i = 0; i < lines.length; i++) {
+                        lines[i] = $('<div>').text(lines[i]).html();
+                    }
+                    msg = lines.join('<br>');
+                }
+                $group.addClass($.fn.editableform.errorGroupClass);
+                $block.addClass($.fn.editableform.errorBlockClass).html(msg).show();
+            }
+        },
+
+        submit: function(e) {
+            e.stopPropagation();
+            e.preventDefault();
+            
+            //get new value from input
+            var newValue = this.input.input2value(); 
+
+            //validation: if validate returns string or truthy value - means error
+            //if returns object like {newValue: '...'} => submitted value is reassigned to it
+            var error = this.validate(newValue);
+            if ($.type(error) === 'object' && error.newValue !== undefined) {
+                newValue = error.newValue;
+                this.input.value2input(newValue);
+                if(typeof error.msg === 'string') {
+                    this.error(error.msg);
+                    this.showForm();
+                    return;
+                }
+            } else if (error) {
+                this.error(error);
+                this.showForm();
+                return;
+            } 
+            
+            //if value not changed --> trigger 'nochange' event and return
+            /*jslint eqeq: true*/
+            if (!this.options.savenochange && this.input.value2str(newValue) == this.input.value2str(this.value)) {
+            /*jslint eqeq: false*/                
+                /**        
+                Fired when value not changed but form is submitted. Requires savenochange = false.
+                @event nochange 
+                @param {Object} event event object
+                **/                    
+                this.$div.triggerHandler('nochange');            
+                return;
+            } 
+
+            //convert value for submitting to server
+            var submitValue = this.input.value2submit(newValue);
+            
+            this.isSaving = true;
+            
+            //sending data to server
+            $.when(this.save(submitValue))
+            .done($.proxy(function(response) {
+                this.isSaving = false;
+
+                //run success callback
+                var res = typeof this.options.success === 'function' ? this.options.success.call(this.options.scope, response, newValue) : null;
+
+                //if success callback returns false --> keep form open and do not activate input
+                if(res === false) {
+                    this.error(false);
+                    this.showForm(false);
+                    return;
+                }
+
+                //if success callback returns string -->  keep form open, show error and activate input               
+                if(typeof res === 'string') {
+                    this.error(res);
+                    this.showForm();
+                    return;
+                }
+
+                //if success callback returns object like {newValue: <something>} --> use that value instead of submitted
+                //it is usefull if you want to chnage value in url-function
+                if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) {
+                    newValue = res.newValue;
+                }
+
+                //clear error message
+                this.error(false);   
+                this.value = newValue;
+                /**        
+                Fired when form is submitted
+                @event save 
+                @param {Object} event event object
+                @param {Object} params additional params
+                @param {mixed} params.newValue raw new value
+                @param {mixed} params.submitValue submitted value as string
+                @param {Object} params.response ajax response
+
+                @example
+                $('#form-div').on('save'), function(e, params){
+                    if(params.newValue === 'username') {...}
+                });
+                **/
+                this.$div.triggerHandler('save', {newValue: newValue, submitValue: submitValue, response: response});
+            }, this))
+            .fail($.proxy(function(xhr) {
+                this.isSaving = false;
+
+                var msg;
+                if(typeof this.options.error === 'function') {
+                    msg = this.options.error.call(this.options.scope, xhr, newValue);
+                } else {
+                    msg = typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!';
+                }
+
+                this.error(msg);
+                this.showForm();
+            }, this));
+        },
+
+        save: function(submitValue) {
+            //try parse composite pk defined as json string in data-pk 
+            this.options.pk = $.fn.editableutils.tryParseJson(this.options.pk, true); 
+            
+            var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this.options.scope) : this.options.pk,
+            /*
+              send on server in following cases:
+              1. url is function
+              2. url is string AND (pk defined OR send option = always) 
+            */
+            send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk !== null && pk !== undefined)))),
+            params;
+
+            if (send) { //send to server
+                this.showLoading();
+
+                //standard params
+                params = {
+                    name: this.options.name || '',
+                    value: submitValue,
+                    pk: pk 
+                };
+
+                //additional params
+                if(typeof this.options.params === 'function') {
+                    params = this.options.params.call(this.options.scope, params);  
+                } else {
+                    //try parse json in single quotes (from data-params attribute)
+                    this.options.params = $.fn.editableutils.tryParseJson(this.options.params, true);   
+                    $.extend(params, this.options.params);
+                }
+
+                if(typeof this.options.url === 'function') { //user's function
+                    return this.options.url.call(this.options.scope, params);
+                } else {  
+                    //send ajax to server and return deferred object
+                    return $.ajax($.extend({
+                        url     : this.options.url,
+                        data    : params,
+                        type    : 'POST'
+                    }, this.options.ajaxOptions));
+                }
+            }
+        }, 
+
+        validate: function (value) {
+            if (value === undefined) {
+                value = this.value;
+            }
+            if (typeof this.options.validate === 'function') {
+                return this.options.validate.call(this.options.scope, value);
+            }
+        },
+
+        option: function(key, value) {
+            if(key in this.options) {
+                this.options[key] = value;
+            }
+            
+            if(key === 'value') {
+                this.setValue(value);
+            }
+            
+            //do not pass option to input as it is passed in editable-element
+        },
+
+        setValue: function(value, convertStr) {
+            if(convertStr) {
+                this.value = this.input.str2value(value);
+            } else {
+                this.value = value;
+            }
+            
+            //if form is visible, update input
+            if(this.$form && this.$form.is(':visible')) {
+                this.input.value2input(this.value);
+            }            
+        }               
+    };
+
+    /*
+    Initialize editableform. Applied to jQuery object.
+
+    @method $().editableform(options)
+    @params {Object} options
+    @example
+    var $form = $('&lt;div&gt;').editableform({
+        type: 'text',
+        name: 'username',
+        url: '/post',
+        value: 'vitaliy'
+    });
+
+    //to display form you should call 'render' method
+    $form.editableform('render');     
+    */
+    $.fn.editableform = function (option) {
+        var args = arguments;
+        return this.each(function () {
+            var $this = $(this), 
+            data = $this.data('editableform'), 
+            options = typeof option === 'object' && option; 
+            if (!data) {
+                $this.data('editableform', (data = new EditableForm(this, options)));
+            }
+
+            if (typeof option === 'string') { //call method 
+                data[option].apply(data, Array.prototype.slice.call(args, 1));
+            } 
+        });
+    };
+
+    //keep link to constructor to allow inheritance
+    $.fn.editableform.Constructor = EditableForm;    
+
+    //defaults
+    $.fn.editableform.defaults = {
+        /* see also defaults for input */
+
+        /**
+        Type of input. Can be <code>text|textarea|select|date|checklist</code>
+
+        @property type 
+        @type string
+        @default 'text'
+        **/
+        type: 'text',
+        /**
+        Url for submit, e.g. <code>'/post'</code>  
+        If function - it will be called instead of ajax. Function should return deferred object to run fail/done callbacks.
+
+        @property url 
+        @type string|function
+        @default null
+        @example
+        url: function(params) {
+            var d = new $.Deferred;
+            if(params.value === 'abc') {
+                return d.reject('error message'); //returning error via deferred object
+            } else {
+                //async saving data in js model
+                someModel.asyncSaveMethod({
+                   ..., 
+                   success: function(){
+                      d.resolve();
+                   }
+                }); 
+                return d.promise();
+            }
+        } 
+        **/        
+        url:null,
+        /**
+        Additional params for submit. If defined as <code>object</code> - it is **appended** to original ajax data (pk, name and value).  
+        If defined as <code>function</code> - returned object **overwrites** original ajax data.
+        @example
+        params: function(params) {
+            //originally params contain pk, name and value
+            params.a = 1;
+            return params;
+        }
+
+        @property params 
+        @type object|function
+        @default null
+        **/          
+        params:null,
+        /**
+        Name of field. Will be submitted on server. Can be taken from <code>id</code> attribute
+
+        @property name 
+        @type string
+        @default null
+        **/         
+        name: null,
+        /**
+        Primary key of editable object (e.g. record id in database). For composite keys use object, e.g. <code>{id: 1, lang: 'en'}</code>.
+        Can be calculated dynamically via function.
+
+        @property pk 
+        @type string|object|function
+        @default null
+        **/         
+        pk: null,
+        /**
+        Initial value. If not defined - will be taken from element's content.
+        For __select__ type should be defined (as it is ID of shown text).
+
+        @property value 
+        @type string|object
+        @default null
+        **/        
+        value: null,
+        /**
+        Value that will be displayed in input if original field value is empty (`null|undefined|''`).
+
+        @property defaultValue 
+        @type string|object
+        @default null
+        @since 1.4.6
+        **/        
+        defaultValue: null,
+        /**
+        Strategy for sending data on server. Can be `auto|always|never`.
+        When 'auto' data will be sent on server **only if pk and url defined**, otherwise new value will be stored locally.
+
+        @property send 
+        @type string
+        @default 'auto'
+        **/          
+        send: 'auto', 
+        /**
+        Function for client-side validation. If returns string - means validation not passed and string showed as error.
+        Since 1.5.1 you can modify submitted value by returning object from `validate`: 
+        `{newValue: '...'}` or `{newValue: '...', msg: '...'}`
+
+        @property validate 
+        @type function
+        @default null
+        @example
+        validate: function(value) {
+            if($.trim(value) == '') {
+                return 'This field is required';
+            }
+        }
+        **/         
+        validate: null,
+        /**
+        Success callback. Called when value successfully sent on server and **response status = 200**.  
+        Usefull to work with json response. For example, if your backend response can be <code>{success: true}</code>
+        or <code>{success: false, msg: "server error"}</code> you can check it inside this callback.  
+        If it returns **string** - means error occured and string is shown as error message.  
+        If it returns **object like** <code>{newValue: &lt;something&gt;}</code> - it overwrites value, submitted by user.  
+        Otherwise newValue simply rendered into element.
+        
+        @property success 
+        @type function
+        @default null
+        @example
+        success: function(response, newValue) {
+            if(!response.success) return response.msg;
+        }
+        **/          
+        success: null,
+        /**
+        Error callback. Called when request failed (response status != 200).  
+        Usefull when you want to parse error response and display a custom message.
+        Must return **string** - the message to be displayed in the error block.
+                
+        @property error 
+        @type function
+        @default null
+        @since 1.4.4
+        @example
+        error: function(response, newValue) {
+            if(response.status === 500) {
+                return 'Service unavailable. Please try later.';
+            } else {
+                return response.responseText;
+            }
+        }
+        **/          
+        error: null,
+        /**
+        Additional options for submit ajax request.
+        List of values: http://api.jquery.com/jQuery.ajax
+        
+        @property ajaxOptions 
+        @type object
+        @default null
+        @since 1.1.1        
+        @example 
+        ajaxOptions: {
+            type: 'put',
+            dataType: 'json'
+        }        
+        **/        
+        ajaxOptions: null,
+        /**
+        Where to show buttons: left(true)|bottom|false  
+        Form without buttons is auto-submitted.
+
+        @property showbuttons 
+        @type boolean|string
+        @default true
+        @since 1.1.1
+        **/         
+        showbuttons: true,
+        /**
+        Scope for callback methods (success, validate).  
+        If <code>null</code> means editableform instance itself. 
+
+        @property scope 
+        @type DOMElement|object
+        @default null
+        @since 1.2.0
+        @private
+        **/            
+        scope: null,
+        /**
+        Whether to save or cancel value when it was not changed but form was submitted
+
+        @property savenochange 
+        @type boolean
+        @default false
+        @since 1.2.0
+        **/
+        savenochange: false
+    };   
+
+    /*
+    Note: following params could redefined in engine: bootstrap or jqueryui:
+    Classes 'control-group' and 'editable-error-block' must always present!
+    */      
+    $.fn.editableform.template = '<form class="form-inline editableform">'+
+    '<div class="control-group">' + 
+    '<div><div class="editable-input"></div><div class="editable-buttons"></div></div>'+
+    '<div class="editable-error-block"></div>' + 
+    '</div>' + 
+    '</form>';
+
+    //loading div
+    $.fn.editableform.loading = '<div class="editableform-loading"></div>';
+
+    //buttons
+    $.fn.editableform.buttons = '<button type="submit" class="editable-submit">ok</button>'+
+    '<button type="button" class="editable-cancel">cancel</button>';      
+
+    //error class attached to control-group
+    $.fn.editableform.errorGroupClass = null;  
+
+    //error class attached to editable-error-block
+    $.fn.editableform.errorBlockClass = 'editable-error';
+    
+    //engine
+    $.fn.editableform.engine = 'jquery';
+}(window.jQuery));
+
+/**
+* EditableForm utilites
+*/
+(function ($) {
+    "use strict";
+    
+    //utils
+    $.fn.editableutils = {
+        /**
+        * classic JS inheritance function
+        */  
+        inherit: function (Child, Parent) {
+            var F = function() { };
+            F.prototype = Parent.prototype;
+            Child.prototype = new F();
+            Child.prototype.constructor = Child;
+            Child.superclass = Parent.prototype;
+        },
+
+        /**
+        * set caret position in input
+        * see http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
+        */        
+        setCursorPosition: function(elem, pos) {
+            if (elem.setSelectionRange) {
+                elem.setSelectionRange(pos, pos);
+            } else if (elem.createTextRange) {
+                var range = elem.createTextRange();
+                range.collapse(true);
+                range.moveEnd('character', pos);
+                range.moveStart('character', pos);
+                range.select();
+            }
+        },
+
+        /**
+        * function to parse JSON in *single* quotes. (jquery automatically parse only double quotes)
+        * That allows such code as: <a data-source="{'a': 'b', 'c': 'd'}">
+        * safe = true --> means no exception will be thrown
+        * for details see http://stackoverflow.com/questions/7410348/how-to-set-json-format-to-html5-data-attributes-in-the-jquery
+        */
+        tryParseJson: function(s, safe) {
+            if (typeof s === 'string' && s.length && s.match(/^[\{\[].*[\}\]]$/)) {
+                if (safe) {
+                    try {
+                        /*jslint evil: true*/
+                        s = (new Function('return ' + s))();
+                        /*jslint evil: false*/
+                    } catch (e) {} finally {
+                        return s;
+                    }
+                } else {
+                    /*jslint evil: true*/
+                    s = (new Function('return ' + s))();
+                    /*jslint evil: false*/
+                }
+            }
+            return s;
+        },
+
+        /**
+        * slice object by specified keys
+        */
+        sliceObj: function(obj, keys, caseSensitive /* default: false */) {
+            var key, keyLower, newObj = {};
+
+            if (!$.isArray(keys) || !keys.length) {
+                return newObj;
+            }
+
+            for (var i = 0; i < keys.length; i++) {
+                key = keys[i];
+                if (obj.hasOwnProperty(key)) {
+                    newObj[key] = obj[key];
+                }
+
+                if(caseSensitive === true) {
+                    continue;
+                }
+
+                //when getting data-* attributes via $.data() it's converted to lowercase.
+                //details: http://stackoverflow.com/questions/7602565/using-data-attributes-with-jquery
+                //workaround is code below.
+                keyLower = key.toLowerCase();
+                if (obj.hasOwnProperty(keyLower)) {
+                    newObj[key] = obj[keyLower];
+                }
+            }
+
+            return newObj;
+        },
+
+        /*
+        exclude complex objects from $.data() before pass to config
+        */
+        getConfigData: function($element) {
+            var data = {};
+            $.each($element.data(), function(k, v) {
+                if(typeof v !== 'object' || (v && typeof v === 'object' && (v.constructor === Object || v.constructor === Array))) {
+                    data[k] = v;
+                }
+            });
+            return data;
+        },
+
+        /*
+         returns keys of object
+        */
+        objectKeys: function(o) {
+            if (Object.keys) {
+                return Object.keys(o);  
+            } else {
+                if (o !== Object(o)) {
+                    throw new TypeError('Object.keys called on a non-object');
+                }
+                var k=[], p;
+                for (p in o) {
+                    if (Object.prototype.hasOwnProperty.call(o,p)) {
+                        k.push(p);
+                    }
+                }
+                return k;
+            }
+
+        },
+        
+       /**
+        method to escape html.
+       **/
+       escape: function(str) {
+           return $('<div>').text(str).html();
+       },
+       
+       /*
+        returns array items from sourceData having value property equal or inArray of 'value'
+       */
+       itemsByValue: function(value, sourceData, valueProp) {
+           if(!sourceData || value === null) {
+               return [];
+           }
+           
+           if (typeof(valueProp) !== "function") {
+               var idKey = valueProp || 'value';
+               valueProp = function (e) { return e[idKey]; };
+           }
+                      
+           var isValArray = $.isArray(value),
+           result = [], 
+           that = this;
+
+           $.each(sourceData, function(i, o) {
+               if(o.children) {
+                   result = result.concat(that.itemsByValue(value, o.children, valueProp));
+               } else {
+                   /*jslint eqeq: true*/
+                   if(isValArray) {
+                       if($.grep(value, function(v){  return v == (o && typeof o === 'object' ? valueProp(o) : o); }).length) {
+                           result.push(o); 
+                       }
+                   } else {
+                       var itemValue = (o && (typeof o === 'object')) ? valueProp(o) : o;
+                       if(value == itemValue) {
+                           result.push(o); 
+                       }
+                   }
+                   /*jslint eqeq: false*/
+               }
+           });
+           
+           return result;
+       },
+       
+       /*
+       Returns input by options: type, mode. 
+       */
+       createInput: function(options) {
+           var TypeConstructor, typeOptions, input,
+           type = options.type;
+
+           //`date` is some kind of virtual type that is transformed to one of exact types
+           //depending on mode and core lib
+           if(type === 'date') {
+               //inline
+               if(options.mode === 'inline') {
+                   if($.fn.editabletypes.datefield) {
+                       type = 'datefield';
+                   } else if($.fn.editabletypes.dateuifield) {
+                       type = 'dateuifield';
+                   }
+               //popup
+               } else {
+                   if($.fn.editabletypes.date) {
+                       type = 'date';
+                   } else if($.fn.editabletypes.dateui) {
+                       type = 'dateui';
+                   }
+               }
+               
+               //if type still `date` and not exist in types, replace with `combodate` that is base input
+               if(type === 'date' && !$.fn.editabletypes.date) {
+                   type = 'combodate';
+               } 
+           }
+           
+           //`datetime` should be datetimefield in 'inline' mode
+           if(type === 'datetime' && options.mode === 'inline') {
+             type = 'datetimefield';  
+           }           
+
+           //change wysihtml5 to textarea for jquery UI and plain versions
+           if(type === 'wysihtml5' && !$.fn.editabletypes[type]) {
+               type = 'textarea';
+           }
+
+           //create input of specified type. Input will be used for converting value, not in form
+           if(typeof $.fn.editabletypes[type] === 'function') {
+               TypeConstructor = $.fn.editabletypes[type];
+               typeOptions = this.sliceObj(options, this.objectKeys(TypeConstructor.defaults));
+               input = new TypeConstructor(typeOptions);
+               return input;
+           } else {
+               $.error('Unknown type: '+ type);
+               return false; 
+           }  
+       },
+       
+       //see http://stackoverflow.com/questions/7264899/detect-css-transitions-using-javascript-and-without-modernizr
+       supportsTransitions: function () {
+           var b = document.body || document.documentElement,
+               s = b.style,
+               p = 'transition',
+               v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'];
+               
+           if(typeof s[p] === 'string') {
+               return true; 
+           }
+
+           // Tests for vendor specific prop
+           p = p.charAt(0).toUpperCase() + p.substr(1);
+           for(var i=0; i<v.length; i++) {
+               if(typeof s[v[i] + p] === 'string') { 
+                   return true; 
+               }
+           }
+           return false;
+       }            
+       
+    };      
+}(window.jQuery));
+
+/**
+Attaches stand-alone container with editable-form to HTML element. Element is used only for positioning, value is not stored anywhere.<br>
+This method applied internally in <code>$().editable()</code>. You should subscribe on it's events (save / cancel) to get profit of it.<br>
+Final realization can be different: bootstrap-popover, jqueryui-tooltip, poshytip, inline-div. It depends on which js file you include.<br>
+Applied as jQuery method.
+
+@class editableContainer
+@uses editableform
+**/
+(function ($) {
+    "use strict";
+
+    var Popup = function (element, options) {
+        this.init(element, options);
+    };
+    
+    var Inline = function (element, options) {
+        this.init(element, options);
+    };    
+
+    //methods
+    Popup.prototype = {
+        containerName: null, //method to call container on element
+        containerDataName: null, //object name in element's .data()
+        innerCss: null, //tbd in child class
+        containerClass: 'editable-container editable-popup', //css class applied to container element
+        defaults: {}, //container itself defaults
+        
+        init: function(element, options) {
+            this.$element = $(element);
+            //since 1.4.1 container do not use data-* directly as they already merged into options.
+            this.options = $.extend({}, $.fn.editableContainer.defaults, options);         
+            this.splitOptions();
+            
+            //set scope of form callbacks to element
+            this.formOptions.scope = this.$element[0]; 
+            
+            this.initContainer();
+            
+            //flag to hide container, when saving value will finish
+            this.delayedHide = false;
+
+            //bind 'destroyed' listener to destroy container when element is removed from dom
+            this.$element.on('destroyed', $.proxy(function(){
+                this.destroy();
+            }, this)); 
+            
+            //attach document handler to close containers on click / escape
+            if(!$(document).data('editable-handlers-attached')) {
+                //close all on escape
+                $(document).on('keyup.editable', function (e) {
+                    if (e.which === 27) {
+                        $('.editable-open').editableContainer('hide');
+                        //todo: return focus on element 
+                    }
+                });
+
+                //close containers when click outside 
+                //(mousedown could be better than click, it closes everything also on drag drop)
+                $(document).on('click.editable', function(e) {
+                    var $target = $(e.target), i,
+                        exclude_classes = ['.editable-container', 
+                                           '.ui-datepicker-header', 
+                                           '.datepicker', //in inline mode datepicker is rendered into body
+                                           '.modal-backdrop', 
+                                           '.bootstrap-wysihtml5-insert-image-modal', 
+                                           '.bootstrap-wysihtml5-insert-link-modal'
+                                           ];
+                    
+                    //check if element is detached. It occurs when clicking in bootstrap datepicker
+                    if (!$.contains(document.documentElement, e.target)) {
+                      return;
+                    }
+
+                    //for some reason FF 20 generates extra event (click) in select2 widget with e.target = document
+                    //we need to filter it via construction below. See https://github.com/vitalets/x-editable/issues/199
+                    //Possibly related to http://stackoverflow.com/questions/10119793/why-does-firefox-react-differently-from-webkit-and-ie-to-click-event-on-selec
+                    if($target.is(document)) {
+                       return; 
+                    }
+                    
+                    //if click inside one of exclude classes --> no nothing
+                    for(i=0; i<exclude_classes.length; i++) {
+                         if($target.is(exclude_classes[i]) || $target.parents(exclude_classes[i]).length) {
+                             return;
+                         }
+                    }
+                      
+                    //close all open containers (except one - target)
+                    Popup.prototype.closeOthers(e.target);
+                });
+                
+                $(document).data('editable-handlers-attached', true);
+            }                        
+        },
+
+        //split options on containerOptions and formOptions
+        splitOptions: function() {
+            this.containerOptions = {};
+            this.formOptions = {};
+            
+            if(!$.fn[this.containerName]) {
+                throw new Error(this.containerName + ' not found. Have you included corresponding js file?');   
+            }
+            
+            //keys defined in container defaults go to container, others go to form
+            for(var k in this.options) {
+              if(k in this.defaults) {
+                 this.containerOptions[k] = this.options[k];
+              } else {
+                 this.formOptions[k] = this.options[k];
+              } 
+            }
+        },
+        
+        /*
+        Returns jquery object of container
+        @method tip()
+        */         
+        tip: function() {
+            return this.container() ? this.container().$tip : null;
+        },
+
+        /* returns container object */
+        container: function() {
+            var container;
+            //first, try get it by `containerDataName`
+            if(this.containerDataName) {
+                if(container = this.$element.data(this.containerDataName)) {
+                    return container;
+                }
+            }
+            //second, try `containerName`
+            container = this.$element.data(this.containerName);
+            return container;
+        },
+
+        /* call native method of underlying container, e.g. this.$element.popover('method') */ 
+        call: function() {
+            this.$element[this.containerName].apply(this.$element, arguments); 
+        },        
+        
+        initContainer: function(){
+            this.call(this.containerOptions);
+        },
+
+        renderForm: function() {
+            this.$form
+            .editableform(this.formOptions)
+            .on({
+                save: $.proxy(this.save, this), //click on submit button (value changed)
+                nochange: $.proxy(function(){ this.hide('nochange'); }, this), //click on submit button (value NOT changed)                
+                cancel: $.proxy(function(){ this.hide('cancel'); }, this), //click on calcel button
+                show: $.proxy(function() {
+                    if(this.delayedHide) {
+                        this.hide(this.delayedHide.reason);
+                        this.delayedHide = false;
+                    } else {
+                        this.setPosition();
+                    }
+                }, this), //re-position container every time form is shown (occurs each time after loading state)
+                rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown
+                resize: $.proxy(this.setPosition, this), //this allows to re-position container when form size is changed 
+                rendered: $.proxy(function(){
+                    /**        
+                    Fired when container is shown and form is rendered (for select will wait for loading dropdown options).  
+                    **Note:** Bootstrap popover has own `shown` event that now cannot be separated from x-editable's one.
+                    The workaround is to check `arguments.length` that is always `2` for x-editable.                     
+                    
+                    @event shown 
+                    @param {Object} event event object
+                    @example
+                    $('#username').on('shown', function(e, editable) {
+                        editable.input.$input.val('overwriting value of input..');
+                    });                     
+                    **/                      
+                    /*
+                     TODO: added second param mainly to distinguish from bootstrap's shown event. It's a hotfix that will be solved in future versions via namespaced events.  
+                    */
+                    this.$element.triggerHandler('shown', $(this.options.scope).data('editable')); 
+                }, this) 
+            })
+            .editableform('render');
+        },        
+
+        /**
+        Shows container with form
+        @method show()
+        @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
+        **/
+        /* Note: poshytip owerwrites this method totally! */          
+        show: function (closeAll) {
+            this.$element.addClass('editable-open');
+            if(closeAll !== false) {
+                //close all open containers (except this)
+                this.closeOthers(this.$element[0]);  
+            }
+            
+            //show container itself
+            this.innerShow();
+            this.tip().addClass(this.containerClass);
+
+            /*
+            Currently, form is re-rendered on every show. 
+            The main reason is that we dont know, what will container do with content when closed:
+            remove(), detach() or just hide() - it depends on container.
+            
+            Detaching form itself before hide and re-insert before show is good solution, 
+            but visually it looks ugly --> container changes size before hide.  
+            */             
+            
+            //if form already exist - delete previous data 
+            if(this.$form) {
+                //todo: destroy prev data!
+                //this.$form.destroy();
+            }
+
+            this.$form = $('<div>');
+            
+            //insert form into container body
+            if(this.tip().is(this.innerCss)) {
+                //for inline container
+                this.tip().append(this.$form); 
+            } else {
+                this.tip().find(this.innerCss).append(this.$form);
+            } 
+            
+            //render form
+            this.renderForm();
+        },
+
+        /**
+        Hides container with form
+        @method hide()
+        @param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|undefined (=manual)</code>
+        **/         
+        hide: function(reason) {  
+            if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) {
+                return;
+            }
+            
+            //if form is saving value, schedule hide
+            if(this.$form.data('editableform').isSaving) {
+                this.delayedHide = {reason: reason};
+                return;    
+            } else {
+                this.delayedHide = false;
+            }
+
+            this.$element.removeClass('editable-open');   
+            this.innerHide();
+
+            /**
+            Fired when container was hidden. It occurs on both save or cancel.  
+            **Note:** Bootstrap popover has own `hidden` event that now cannot be separated from x-editable's one.
+            The workaround is to check `arguments.length` that is always `2` for x-editable. 
+
+            @event hidden 
+            @param {object} event event object
+            @param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|manual</code>
+            @example
+            $('#username').on('hidden', function(e, reason) {
+                if(reason === 'save' || reason === 'cancel') {
+                    //auto-open next editable
+                    $(this).closest('tr').next().find('.editable').editable('show');
+                } 
+            });
+            **/
+            this.$element.triggerHandler('hidden', reason || 'manual');   
+        },
+
+        /* internal show method. To be overwritten in child classes */
+        innerShow: function () {
+             
+        },        
+
+        /* internal hide method. To be overwritten in child classes */
+        innerHide: function () {
+
+        },
+        
+        /**
+        Toggles container visibility (show / hide)
+        @method toggle()
+        @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
+        **/          
+        toggle: function(closeAll) {
+            if(this.container() && this.tip() && this.tip().is(':visible')) {
+                this.hide();
+            } else {
+                this.show(closeAll);
+            } 
+        },
+
+        /*
+        Updates the position of container when content changed.
+        @method setPosition()
+        */       
+        setPosition: function() {
+            //tbd in child class
+        },
+
+        save: function(e, params) {
+            /**        
+            Fired when new value was submitted. You can use <code>$(this).data('editableContainer')</code> inside handler to access to editableContainer instance
+            
+            @event save 
+            @param {Object} event event object
+            @param {Object} params additional params
+            @param {mixed} params.newValue submitted value
+            @param {Object} params.response ajax response
+            @example
+            $('#username').on('save', function(e, params) {
+                //assuming server response: '{success: true}'
+                var pk = $(this).data('editableContainer').options.pk;
+                if(params.response && params.response.success) {
+                    alert('value: ' + params.newValue + ' with pk: ' + pk + ' saved!');
+                } else {
+                    alert('error!'); 
+                } 
+            });
+            **/             
+            this.$element.triggerHandler('save', params);
+            
+            //hide must be after trigger, as saving value may require methods of plugin, applied to input
+            this.hide('save');
+        },
+
+        /**
+        Sets new option
+        
+        @method option(key, value)
+        @param {string} key 
+        @param {mixed} value 
+        **/         
+        option: function(key, value) {
+            this.options[key] = value;
+            if(key in this.containerOptions) {
+                this.containerOptions[key] = value;
+                this.setContainerOption(key, value); 
+            } else {
+                this.formOptions[key] = value;
+                if(this.$form) {
+                    this.$form.editableform('option', key, value);  
+                }
+            }
+        },
+        
+        setContainerOption: function(key, value) {
+            this.call('option', key, value);
+        },
+
+        /**
+        Destroys the container instance
+        @method destroy()
+        **/        
+        destroy: function() {
+            this.hide();
+            this.innerDestroy();
+            this.$element.off('destroyed');
+            this.$element.removeData('editableContainer');
+        },
+        
+        /* to be overwritten in child classes */
+        innerDestroy: function() {
+            
+        }, 
+        
+        /*
+        Closes other containers except one related to passed element. 
+        Other containers can be cancelled or submitted (depends on onblur option)
+        */
+        closeOthers: function(element) {
+            $('.editable-open').each(function(i, el){
+                //do nothing with passed element and it's children
+                if(el === element || $(el).find(element).length) {
+                    return;
+                }
+
+                //otherwise cancel or submit all open containers 
+                var $el = $(el),
+                ec = $el.data('editableContainer');
+
+                if(!ec) {
+                    return;  
+                }
+                
+                if(ec.options.onblur === 'cancel') {
+                    $el.data('editableContainer').hide('onblur');
+                } else if(ec.options.onblur === 'submit') {
+                    $el.data('editableContainer').tip().find('form').submit();
+                }
+            });
+
+        },
+        
+        /**
+        Activates input of visible container (e.g. set focus)
+        @method activate()
+        **/         
+        activate: function() {
+            if(this.tip && this.tip().is(':visible') && this.$form) {
+               this.$form.data('editableform').input.activate(); 
+            }
+        } 
+
+    };
+
+    /**
+    jQuery method to initialize editableContainer.
+    
+    @method $().editableContainer(options)
+    @params {Object} options
+    @example
+    $('#edit').editableContainer({
+        type: 'text',
+        url: '/post',
+        pk: 1,
+        value: 'hello'
+    });
+    **/  
+    $.fn.editableContainer = function (option) {
+        var args = arguments;
+        return this.each(function () {
+            var $this = $(this),
+            dataKey = 'editableContainer', 
+            data = $this.data(dataKey),
+            options = typeof option === 'object' && option,
+            Constructor = (options.mode === 'inline') ? Inline : Popup;             
+
+            if (!data) {
+                $this.data(dataKey, (data = new Constructor(this, options)));
+            }
+
+            if (typeof option === 'string') { //call method 
+                data[option].apply(data, Array.prototype.slice.call(args, 1));
+            }            
+        });
+    };     
+
+    //store constructors
+    $.fn.editableContainer.Popup = Popup;
+    $.fn.editableContainer.Inline = Inline;
+
+    //defaults
+    $.fn.editableContainer.defaults = {
+        /**
+        Initial value of form input
+
+        @property value 
+        @type mixed
+        @default null
+        @private
+        **/        
+        value: null,
+        /**
+        Placement of container relative to element. Can be <code>top|right|bottom|left</code>. Not used for inline container.
+
+        @property placement 
+        @type string
+        @default 'top'
+        **/        
+        placement: 'top',
+        /**
+        Whether to hide container on save/cancel.
+
+        @property autohide 
+        @type boolean
+        @default true
+        @private 
+        **/        
+        autohide: true,
+        /**
+        Action when user clicks outside the container. Can be <code>cancel|submit|ignore</code>.  
+        Setting <code>ignore</code> allows to have several containers open. 
+
+        @property onblur 
+        @type string
+        @default 'cancel'
+        @since 1.1.1
+        **/        
+        onblur: 'cancel',
+        
+        /**
+        Animation speed (inline mode only)
+        @property anim 
+        @type string
+        @default false
+        **/        
+        anim: false,
+        
+        /**
+        Mode of editable, can be `popup` or `inline` 
+        
+        @property mode 
+        @type string         
+        @default 'popup'
+        @since 1.4.0        
+        **/        
+        mode: 'popup'        
+    };
+
+    /* 
+    * workaround to have 'destroyed' event to destroy popover when element is destroyed
+    * see http://stackoverflow.com/questions/2200494/jquery-trigger-event-when-an-element-is-removed-from-the-dom
+    */
+    jQuery.event.special.destroyed = {
+        remove: function(o) {
+            if (o.handler) {
+                o.handler();
+            }
+        }
+    };    
+
+}(window.jQuery));
+
+/**
+* Editable Inline 
+* ---------------------
+*/
+(function ($) {
+    "use strict";
+    
+    //copy prototype from EditableContainer
+    //extend methods
+    $.extend($.fn.editableContainer.Inline.prototype, $.fn.editableContainer.Popup.prototype, {
+        containerName: 'editableform',
+        innerCss: '.editable-inline',
+        containerClass: 'editable-container editable-inline', //css class applied to container element
+                 
+        initContainer: function(){
+            //container is <span> element
+            this.$tip = $('<span></span>');
+            
+            //convert anim to miliseconds (int)
+            if(!this.options.anim) {
+                this.options.anim = 0;
+            }         
+        },
+        
+        splitOptions: function() {
+            //all options are passed to form
+            this.containerOptions = {};
+            this.formOptions = this.options;
+        },
+        
+        tip: function() {
+           return this.$tip; 
+        },
+        
+        innerShow: function () {
+            this.$element.hide();
+            this.tip().insertAfter(this.$element).show();
+        }, 
+        
+        innerHide: function () {
+            this.$tip.hide(this.options.anim, $.proxy(function() {
+                this.$element.show();
+                this.innerDestroy();
+            }, this)); 
+        },
+        
+        innerDestroy: function() {
+            if(this.tip()) {
+                this.tip().empty().remove();
+            }
+        } 
+    });
+
+}(window.jQuery));
+/**
+Makes editable any HTML element on the page. Applied as jQuery method.
+
+@class editable
+@uses editableContainer
+**/
+(function ($) {
+    "use strict";
+
+    var Editable = function (element, options) {
+        this.$element = $(element);
+        //data-* has more priority over js options: because dynamically created elements may change data-* 
+        this.options = $.extend({}, $.fn.editable.defaults, options, $.fn.editableutils.getConfigData(this.$element));  
+        if(this.options.selector) {
+            this.initLive();
+        } else {
+            this.init();
+        }
+        
+        //check for transition support
+        if(this.options.highlight && !$.fn.editableutils.supportsTransitions()) {
+            this.options.highlight = false;
+        }
+    };
+
+    Editable.prototype = {
+        constructor: Editable, 
+        init: function () {
+            var isValueByText = false, 
+                doAutotext, finalize;
+
+            //name
+            this.options.name = this.options.name || this.$element.attr('id');
+             
+            //create input of specified type. Input needed already here to convert value for initial display (e.g. show text by id for select)
+            //also we set scope option to have access to element inside input specific callbacks (e. g. source as function)
+            this.options.scope = this.$element[0]; 
+            this.input = $.fn.editableutils.createInput(this.options);
+            if(!this.input) {
+                return; 
+            }            
+
+            //set value from settings or by element's text
+            if (this.options.value === undefined || this.options.value === null) {
+                this.value = this.input.html2value($.trim(this.$element.html()));
+                isValueByText = true;
+            } else {
+                /*
+                  value can be string when received from 'data-value' attribute
+                  for complext objects value can be set as json string in data-value attribute, 
+                  e.g. data-value="{city: 'Moscow', street: 'Lenina'}"
+                */
+                this.options.value = $.fn.editableutils.tryParseJson(this.options.value, true); 
+                if(typeof this.options.value === 'string') {
+                    this.value = this.input.str2value(this.options.value);
+                } else {
+                    this.value = this.options.value;
+                }
+            }
+            
+            //add 'editable' class to every editable element
+            this.$element.addClass('editable');
+            
+            //specifically for "textarea" add class .editable-pre-wrapped to keep linebreaks
+            if(this.input.type === 'textarea') {
+                this.$element.addClass('editable-pre-wrapped');
+            }
+            
+            //attach handler activating editable. In disabled mode it just prevent default action (useful for links)
+            if(this.options.toggle !== 'manual') {
+                this.$element.addClass('editable-click');
+                this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){
+                    //prevent following link if editable enabled
+                    if(!this.options.disabled) {
+                        e.preventDefault();
+                    }
+                    
+                    //stop propagation not required because in document click handler it checks event target
+                    //e.stopPropagation();
+                    
+                    if(this.options.toggle === 'mouseenter') {
+                        //for hover only show container
+                        this.show();
+                    } else {
+                        //when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
+                        var closeAll = (this.options.toggle !== 'click');
+                        this.toggle(closeAll);
+                    }
+                }, this));
+            } else {
+                this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually
+            }
+            
+            //if display is function it's far more convinient to have autotext = always to render correctly on init
+            //see https://github.com/vitalets/x-editable-yii/issues/34
+            if(typeof this.options.display === 'function') {
+                this.options.autotext = 'always';
+            }
+            
+            //check conditions for autotext:
+            switch(this.options.autotext) {
+              case 'always':
+               doAutotext = true;
+              break;
+              case 'auto':
+                //if element text is empty and value is defined and value not generated by text --> run autotext
+                doAutotext = !$.trim(this.$element.text()).length && this.value !== null && this.value !== undefined && !isValueByText;
+              break;
+              default:
+               doAutotext = false;
+            }
+
+            //depending on autotext run render() or just finilize init
+            $.when(doAutotext ? this.render() : true).then($.proxy(function() {
+                if(this.options.disabled) {
+                    this.disable();
+                } else {
+                    this.enable(); 
+                }
+               /**        
+               Fired when element was initialized by `$().editable()` method. 
+               Please note that you should setup `init` handler **before** applying `editable`. 
+                              
+               @event init 
+               @param {Object} event event object
+               @param {Object} editable editable instance (as here it cannot accessed via data('editable'))
+               @since 1.2.0
+               @example
+               $('#username').on('init', function(e, editable) {
+                   alert('initialized ' + editable.options.name);
+               });
+               $('#username').editable();
+               **/                  
+                this.$element.triggerHandler('init', this);
+            }, this));
+        },
+
+        /*
+         Initializes parent element for live editables 
+        */
+        initLive: function() {
+           //store selector 
+           var selector = this.options.selector;
+           //modify options for child elements
+           this.options.selector = false; 
+           this.options.autotext = 'never';
+           //listen toggle events
+           this.$element.on(this.options.toggle + '.editable', selector, $.proxy(function(e){
+               var $target = $(e.target);
+               if(!$target.data('editable')) {
+                   //if delegated element initially empty, we need to clear it's text (that was manually set to `empty` by user)
+                   //see https://github.com/vitalets/x-editable/issues/137 
+                   if($target.hasClass(this.options.emptyclass)) {
+                      $target.empty();
+                   }
+                   $target.editable(this.options).trigger(e);
+               }
+           }, this)); 
+        },
+        
+        /*
+        Renders value into element's text.
+        Can call custom display method from options.
+        Can return deferred object.
+        @method render()
+        @param {mixed} response server response (if exist) to pass into display function
+        */          
+        render: function(response) {
+            //do not display anything
+            if(this.options.display === false) {
+                return;
+            }
+            
+            //if input has `value2htmlFinal` method, we pass callback in third param to be called when source is loaded
+            if(this.input.value2htmlFinal) {
+                return this.input.value2html(this.value, this.$element[0], this.options.display, response); 
+            //if display method defined --> use it    
+            } else if(typeof this.options.display === 'function') {
+                return this.options.display.call(this.$element[0], this.value, response);
+            //else use input's original value2html() method    
+            } else {
+                return this.input.value2html(this.value, this.$element[0]); 
+            }
+        },
+        
+        /**
+        Enables editable
+        @method enable()
+        **/          
+        enable: function() {
+            this.options.disabled = false;
+            this.$element.removeClass('editable-disabled');
+            this.handleEmpty(this.isEmpty);
+            if(this.options.toggle !== 'manual') {
+                if(this.$element.attr('tabindex') === '-1') {    
+                    this.$element.removeAttr('tabindex');                                
+                }
+            }
+        },
+        
+        /**
+        Disables editable
+        @method disable()
+        **/         
+        disable: function() {
+            this.options.disabled = true; 
+            this.hide();           
+            this.$element.addClass('editable-disabled');
+            this.handleEmpty(this.isEmpty);
+            //do not stop focus on this element
+            this.$element.attr('tabindex', -1);                
+        },
+        
+        /**
+        Toggles enabled / disabled state of editable element
+        @method toggleDisabled()
+        **/         
+        toggleDisabled: function() {
+            if(this.options.disabled) {
+                this.enable();
+            } else { 
+                this.disable(); 
+            }
+        },  
+        
+        /**
+        Sets new option
+        
+        @method option(key, value)
+        @param {string|object} key option name or object with several options
+        @param {mixed} value option new value
+        @example
+        $('.editable').editable('option', 'pk', 2);
+        **/          
+        option: function(key, value) {
+            //set option(s) by object
+            if(key && typeof key === 'object') {
+               $.each(key, $.proxy(function(k, v){
+                  this.option($.trim(k), v); 
+               }, this)); 
+               return;
+            }
+
+            //set option by string             
+            this.options[key] = value;                          
+            
+            //disabled
+            if(key === 'disabled') {
+               return value ? this.disable() : this.enable();
+            } 
+            
+            //value
+            if(key === 'value') {
+                this.setValue(value);
+            }
+            
+            //transfer new option to container! 
+            if(this.container) {
+                this.container.option(key, value);  
+            }
+             
+            //pass option to input directly (as it points to the same in form)
+            if(this.input.option) {
+                this.input.option(key, value);
+            }
+            
+        },              
+        
+        /*
+        * set emptytext if element is empty
+        */
+        handleEmpty: function (isEmpty) {
+            //do not handle empty if we do not display anything
+            if(this.options.display === false) {
+                return;
+            }
+
+            /* 
+            isEmpty may be set directly as param of method.
+            It is required when we enable/disable field and can't rely on content 
+            as node content is text: "Empty" that is not empty %)
+            */
+            if(isEmpty !== undefined) { 
+                this.isEmpty = isEmpty;
+            } else {
+                //detect empty
+                //for some inputs we need more smart check
+                //e.g. wysihtml5 may have <br>, <p></p>, <img>
+                if(typeof(this.input.isEmpty) === 'function') {
+                    this.isEmpty = this.input.isEmpty(this.$element);                    
+                } else {
+                    this.isEmpty = $.trim(this.$element.html()) === '';
+                }
+            }           
+            
+            //emptytext shown only for enabled
+            if(!this.options.disabled) {
+                if (this.isEmpty) {
+                    this.$element.html(this.options.emptytext);
+                    if(this.options.emptyclass) {
+                        this.$element.addClass(this.options.emptyclass);
+                    }
+                } else if(this.options.emptyclass) {
+                    this.$element.removeClass(this.options.emptyclass);
+                }
+            } else {
+                //below required if element disable property was changed
+                if(this.isEmpty) {
+                    this.$element.empty();
+                    if(this.options.emptyclass) {
+                        this.$element.removeClass(this.options.emptyclass);
+                    }
+                }
+            }
+        },        
+        
+        /**
+        Shows container with form
+        @method show()
+        @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
+        **/  
+        show: function (closeAll) {
+            if(this.options.disabled) {
+                return;
+            }
+            
+            //init editableContainer: popover, tooltip, inline, etc..
+            if(!this.container) {
+                var containerOptions = $.extend({}, this.options, {
+                    value: this.value,
+                    input: this.input //pass input to form (as it is already created)
+                });
+                this.$element.editableContainer(containerOptions);
+                //listen `save` event 
+                this.$element.on("save.internal", $.proxy(this.save, this));
+                this.container = this.$element.data('editableContainer'); 
+            } else if(this.container.tip().is(':visible')) {
+                return;
+            }      
+            
+            //show container
+            this.container.show(closeAll);
+        },
+        
+        /**
+        Hides container with form
+        @method hide()
+        **/       
+        hide: function () {   
+            if(this.container) {  
+                this.container.hide();
+            }
+        },
+        
+        /**
+        Toggles container visibility (show / hide)
+        @method toggle()
+        @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
+        **/  
+        toggle: function(closeAll) {
+            if(this.container && this.container.tip().is(':visible')) {
+                this.hide();
+            } else {
+                this.show(closeAll);
+            }
+        },
+        
+        /*
+        * called when form was submitted
+        */          
+        save: function(e, params) {
+            //mark element with unsaved class if needed
+            if(this.options.unsavedclass) {
+                /*
+                 Add unsaved css to element if:
+                  - url is not user's function 
+                  - value was not sent to server
+                  - params.response === undefined, that means data was not sent
+                  - value changed 
+                */
+                var sent = false;
+                sent = sent || typeof this.options.url === 'function';
+                sent = sent || this.options.display === false; 
+                sent = sent || params.response !== undefined; 
+                sent = sent || (this.options.savenochange && this.input.value2str(this.value) !== this.input.value2str(params.newValue)); 
+                
+                if(sent) {
+                    this.$element.removeClass(this.options.unsavedclass); 
+                } else {
+                    this.$element.addClass(this.options.unsavedclass);                    
+                }
+            }
+            
+            //highlight when saving
+            if(this.options.highlight) {
+                var $e = this.$element,
+                    bgColor = $e.css('background-color');
+                    
+                $e.css('background-color', this.options.highlight);
+                setTimeout(function(){
+                    if(bgColor === 'transparent') {
+                        bgColor = ''; 
+                    }
+                    $e.css('background-color', bgColor);
+                    $e.addClass('editable-bg-transition');
+                    setTimeout(function(){
+                       $e.removeClass('editable-bg-transition');  
+                    }, 1700);
+                }, 10);
+            }
+            
+            //set new value
+            this.setValue(params.newValue, false, params.response);
+            
+            /**        
+            Fired when new value was submitted. You can use <code>$(this).data('editable')</code> to access to editable instance
+            
+            @event save 
+            @param {Object} event event object
+            @param {Object} params additional params
+            @param {mixed} params.newValue submitted value
+            @param {Object} params.response ajax response
+            @example
+            $('#username').on('save', function(e, params) {
+                alert('Saved value: ' + params.newValue);
+            });
+            **/
+            //event itself is triggered by editableContainer. Description here is only for documentation              
+        },
+
+        validate: function () {
+            if (typeof this.options.validate === 'function') {
+                return this.options.validate.call(this, this.value);
+            }
+        },
+        
+        /**
+        Sets new value of editable
+        @method setValue(value, convertStr)
+        @param {mixed} value new value 
+        @param {boolean} convertStr whether to convert value from string to internal format
+        **/         
+        setValue: function(value, convertStr, response) {
+            if(convertStr) {
+                this.value = this.input.str2value(value);
+            } else {
+                this.value = value;
+            }
+            if(this.container) {
+                this.container.option('value', this.value);
+            }
+            $.when(this.render(response))
+            .then($.proxy(function() {
+                this.handleEmpty();
+            }, this));
+        },
+        
+        /**
+        Activates input of visible container (e.g. set focus)
+        @method activate()
+        **/         
+        activate: function() {
+            if(this.container) {
+               this.container.activate(); 
+            }
+        },
+        
+        /**
+        Removes editable feature from element
+        @method destroy()
+        **/        
+        destroy: function() {
+            this.disable();
+            
+            if(this.container) {
+               this.container.destroy(); 
+            }
+            
+            this.input.destroy();
+
+            if(this.options.toggle !== 'manual') {
+                this.$element.removeClass('editable-click');
+                this.$element.off(this.options.toggle + '.editable');
+            } 
+            
+            this.$element.off("save.internal");
+            
+            this.$element.removeClass('editable editable-open editable-disabled');
+            this.$element.removeData('editable');
+        }        
+    };
+
+    /* EDITABLE PLUGIN DEFINITION
+    * ======================= */
+
+    /**
+    jQuery method to initialize editable element.
+    
+    @method $().editable(options)
+    @params {Object} options
+    @example
+    $('#username').editable({
+        type: 'text',
+        url: '/post',
+        pk: 1
+    });
+    **/
+    $.fn.editable = function (option) {
+        //special API methods returning non-jquery object
+        var result = {}, args = arguments, datakey = 'editable';
+        switch (option) {
+            /**
+            Runs client-side validation for all matched editables
+            
+            @method validate()
+            @returns {Object} validation errors map
+            @example
+            $('#username, #fullname').editable('validate');
+            // possible result:
+            {
+              username: "username is required",
+              fullname: "fullname should be minimum 3 letters length"
+            }
+            **/
+            case 'validate':
+                this.each(function () {
+                    var $this = $(this), data = $this.data(datakey), error;
+                    if (data && (error = data.validate())) {
+                        result[data.options.name] = error;
+                    }
+                });
+            return result;
+
+            /**
+            Returns current values of editable elements.   
+            Note that it returns an **object** with name-value pairs, not a value itself. It allows to get data from several elements.    
+            If value of some editable is `null` or `undefined` it is excluded from result object.
+            When param `isSingle` is set to **true** - it is supposed you have single element and will return value of editable instead of object.   
+             
+            @method getValue()
+            @param {bool} isSingle whether to return just value of single element
+            @returns {Object} object of element names and values
+            @example
+            $('#username, #fullname').editable('getValue');
+            //result:
+            {
+            username: "superuser",
+            fullname: "John"
+            }
+            //isSingle = true
+            $('#username').editable('getValue', true);
+            //result "superuser" 
+            **/
+            case 'getValue':
+                if(arguments.length === 2 && arguments[1] === true) { //isSingle = true
+                    result = this.eq(0).data(datakey).value;
+                } else {
+                    this.each(function () {
+                        var $this = $(this), data = $this.data(datakey);
+                        if (data && data.value !== undefined && data.value !== null) {
+                            result[data.options.name] = data.input.value2submit(data.value);
+                        }
+                    });
+                }
+            return result;
+
+            /**
+            This method collects values from several editable elements and submit them all to server.   
+            Internally it runs client-side validation for all fields and submits only in case of success.  
+            See <a href="#newrecord">creating new records</a> for details.  
+            Since 1.5.1 `submit` can be applied to single element to send data programmatically. In that case
+            `url`, `success` and `error` is taken from initial options and you can just call `$('#username').editable('submit')`. 
+            
+            @method submit(options)
+            @param {object} options 
+            @param {object} options.url url to submit data 
+            @param {object} options.data additional data to submit
+            @param {object} options.ajaxOptions additional ajax options
+            @param {function} options.error(obj) error handler 
+            @param {function} options.success(obj,config) success handler
+            @returns {Object} jQuery object
+            **/
+            case 'submit':  //collects value, validate and submit to server for creating new record
+                var config = arguments[1] || {},
+                $elems = this,
+                errors = this.editable('validate');
+
+                // validation ok
+                if($.isEmptyObject(errors)) {
+                    var ajaxOptions = {};
+                                                      
+                    // for single element use url, success etc from options
+                    if($elems.length === 1) {
+                        var editable = $elems.data('editable');
+                        //standard params
+                        var params = {
+                            name: editable.options.name || '',
+                            value: editable.input.value2submit(editable.value),
+                            pk: (typeof editable.options.pk === 'function') ? 
+                                editable.options.pk.call(editable.options.scope) : 
+                                editable.options.pk 
+                        };
+
+                        //additional params
+                        if(typeof editable.options.params === 'function') {
+                            params = editable.options.params.call(editable.options.scope, params);  
+                        } else {
+                            //try parse json in single quotes (from data-params attribute)
+                            editable.options.params = $.fn.editableutils.tryParseJson(editable.options.params, true);   
+                            $.extend(params, editable.options.params);
+                        }
+
+                        ajaxOptions = {
+                            url: editable.options.url,
+                            data: params,
+                            type: 'POST'  
+                        };
+                        
+                        // use success / error from options 
+                        config.success = config.success || editable.options.success;
+                        config.error = config.error || editable.options.error;
+                        
+                    // multiple elements
+                    } else {
+                        var values = this.editable('getValue'); 
+                        
+                        ajaxOptions = {
+                            url: config.url,
+                            data: values, 
+                            type: 'POST'
+                        };                        
+                    }                    
+
+                    // ajax success callabck (response 200 OK)
+                    ajaxOptions.success = typeof config.success === 'function' ? function(response) {
+                            config.success.call($elems, response, config);
+                        } : $.noop;
+                                  
+                    // ajax error callabck
+                    ajaxOptions.error = typeof config.error === 'function' ? function() {
+                             config.error.apply($elems, arguments);
+                        } : $.noop;
+                       
+                    // extend ajaxOptions    
+                    if(config.ajaxOptions) { 
+                        $.extend(ajaxOptions, config.ajaxOptions);
+                    }
+                    
+                    // extra data 
+                    if(config.data) {
+                        $.extend(ajaxOptions.data, config.data);
+                    }                     
+                    
+                    // perform ajax request
+                    $.ajax(ajaxOptions);
+                } else { //client-side validation error
+                    if(typeof config.error === 'function') {
+                        config.error.call($elems, errors);
+                    }
+                }
+            return this;
+        }
+
+        //return jquery object
+        return this.each(function () {
+            var $this = $(this), 
+                data = $this.data(datakey), 
+                options = typeof option === 'object' && option;
+
+            //for delegated targets do not store `editable` object for element
+            //it's allows several different selectors.
+            //see: https://github.com/vitalets/x-editable/issues/312    
+            if(options && options.selector) {
+                data = new Editable(this, options);
+                return; 
+            }    
+            
+            if (!data) {
+                $this.data(datakey, (data = new Editable(this, options)));
+            }
+
+            if (typeof option === 'string') { //call method 
+                data[option].apply(data, Array.prototype.slice.call(args, 1));
+            } 
+        });
+    };    
+            
+
+    $.fn.editable.defaults = {
+        /**
+        Type of input. Can be <code>text|textarea|select|date|checklist</code> and more
+
+        @property type 
+        @type string
+        @default 'text'
+        **/
+        type: 'text',        
+        /**
+        Sets disabled state of editable
+
+        @property disabled 
+        @type boolean
+        @default false
+        **/         
+        disabled: false,
+        /**
+        How to toggle editable. Can be <code>click|dblclick|mouseenter|manual</code>.   
+        When set to <code>manual</code> you should manually call <code>show/hide</code> methods of editable.    
+        **Note**: if you call <code>show</code> or <code>toggle</code> inside **click** handler of some DOM element, 
+        you need to apply <code>e.stopPropagation()</code> because containers are being closed on any click on document.
+        
+        @example
+        $('#edit-button').click(function(e) {
+            e.stopPropagation();
+            $('#username').editable('toggle');
+        });
+
+        @property toggle 
+        @type string
+        @default 'click'
+        **/          
+        toggle: 'click',
+        /**
+        Text shown when element is empty.
+
+        @property emptytext 
+        @type string
+        @default 'Empty'
+        **/         
+        emptytext: 'Empty',
+        /**
+        Allows to automatically set element's text based on it's value. Can be <code>auto|always|never</code>. Useful for select and date.
+        For example, if dropdown list is <code>{1: 'a', 2: 'b'}</code> and element's value set to <code>1</code>, it's html will be automatically set to <code>'a'</code>.  
+        <code>auto</code> - text will be automatically set only if element is empty.  
+        <code>always|never</code> - always(never) try to set element's text.
+
+        @property autotext 
+        @type string
+        @default 'auto'
+        **/          
+        autotext: 'auto', 
+        /**
+        Initial value of input. If not set, taken from element's text.  
+        Note, that if element's text is empty - text is automatically generated from value and can be customized (see `autotext` option).  
+        For example, to display currency sign:
+        @example
+        <a id="price" data-type="text" data-value="100"></a>
+        <script>
+        $('#price').editable({
+            ...
+            display: function(value) {
+              $(this).text(value + '$');
+            } 
+        }) 
+        </script>
+                
+        @property value 
+        @type mixed
+        @default element's text
+        **/
+        value: null,
+        /**
+        Callback to perform custom displaying of value in element's text.  
+        If `null`, default input's display used.  
+        If `false`, no displaying methods will be called, element's text will never change.  
+        Runs under element's scope.  
+        _**Parameters:**_  
+        
+        * `value` current value to be displayed
+        * `response` server response (if display called after ajax submit), since 1.4.0
+         
+        For _inputs with source_ (select, checklist) parameters are different:  
+          
+        * `value` current value to be displayed
+        * `sourceData` array of items for current input (e.g. dropdown items) 
+        * `response` server response (if display called after ajax submit), since 1.4.0
+                  
+        To get currently selected items use `$.fn.editableutils.itemsByValue(value, sourceData)`.
+        
+        @property display 
+        @type function|boolean
+        @default null
+        @since 1.2.0
+        @example
+        display: function(value, sourceData) {
+           //display checklist as comma-separated values
+           var html = [],
+               checked = $.fn.editableutils.itemsByValue(value, sourceData);
+               
+           if(checked.length) {
+               $.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
+               $(this).html(html.join(', '));
+           } else {
+               $(this).empty(); 
+           }
+        }
+        **/          
+        display: null,
+        /**
+        Css class applied when editable text is empty.
+
+        @property emptyclass 
+        @type string
+        @since 1.4.1        
+        @default editable-empty
+        **/        
+        emptyclass: 'editable-empty',
+        /**
+        Css class applied when value was stored but not sent to server (`pk` is empty or `send = 'never'`).  
+        You may set it to `null` if you work with editables locally and submit them together.  
+
+        @property unsavedclass 
+        @type string
+        @since 1.4.1        
+        @default editable-unsaved
+        **/        
+        unsavedclass: 'editable-unsaved',
+        /**
+        If selector is provided, editable will be delegated to the specified targets.  
+        Usefull for dynamically generated DOM elements.  
+        **Please note**, that delegated targets can't be initialized with `emptytext` and `autotext` options, 
+        as they actually become editable only after first click.  
+        You should manually set class `editable-click` to these elements.  
+        Also, if element originally empty you should add class `editable-empty`, set `data-value=""` and write emptytext into element:
+
+        @property selector 
+        @type string
+        @since 1.4.1        
+        @default null
+        @example
+        <div id="user">
+          <!-- empty -->
+          <a href="#" data-name="username" data-type="text" class="editable-click editable-empty" data-value="" title="Username">Empty</a>
+          <!-- non-empty -->
+          <a href="#" data-name="group" data-type="select" data-source="/groups" data-value="1" class="editable-click" title="Group">Operator</a>
+        </div>     
+        
+        <script>
+        $('#user').editable({
+            selector: 'a',
+            url: '/post',
+            pk: 1
+        });
+        </script>
+        **/         
+        selector: null,
+        /**
+        Color used to highlight element after update. Implemented via CSS3 transition, works in modern browsers.
+        
+        @property highlight 
+        @type string|boolean
+        @since 1.4.5        
+        @default #FFFF80 
+        **/
+        highlight: '#FFFF80'
+    };
+    
+}(window.jQuery));
+
+/**
+AbstractInput - base class for all editable inputs.
+It defines interface to be implemented by any input type.
+To create your own input you can inherit from this class.
+
+@class abstractinput
+**/
+(function ($) {
+    "use strict";
+
+    //types
+    $.fn.editabletypes = {};
+
+    var AbstractInput = function () { };
+
+    AbstractInput.prototype = {
+       /**
+        Initializes input
+
+        @method init() 
+        **/
+       init: function(type, options, defaults) {
+           this.type = type;
+           this.options = $.extend({}, defaults, options);
+       },
+
+       /*
+       this method called before render to init $tpl that is inserted in DOM
+       */
+       prerender: function() {
+           this.$tpl = $(this.options.tpl); //whole tpl as jquery object    
+           this.$input = this.$tpl;         //control itself, can be changed in render method
+           this.$clear = null;              //clear button
+           this.error = null;               //error message, if input cannot be rendered           
+       },
+       
+       /**
+        Renders input from tpl. Can return jQuery deferred object.
+        Can be overwritten in child objects
+
+        @method render()
+       **/
+       render: function() {
+
+       }, 
+
+       /**
+        Sets element's html by value. 
+
+        @method value2html(value, element)
+        @param {mixed} value
+        @param {DOMElement} element
+       **/
+       value2html: function(value, element) {
+           $(element)[this.options.escape ? 'text' : 'html']($.trim(value));
+       },
+
+       /**
+        Converts element's html to value
+
+        @method html2value(html)
+        @param {string} html
+        @returns {mixed}
+       **/
+       html2value: function(html) {
+           return $('<div>').html(html).text();
+       },
+
+       /**
+        Converts value to string (for internal compare). For submitting to server used value2submit().
+
+        @method value2str(value) 
+        @param {mixed} value
+        @returns {string}
+       **/
+       value2str: function(value) {
+           return value;
+       }, 
+
+       /**
+        Converts string received from server into value. Usually from `data-value` attribute.
+
+        @method str2value(str)
+        @param {string} str
+        @returns {mixed}
+       **/
+       str2value: function(str) {
+           return str;
+       }, 
+       
+       /**
+        Converts value for submitting to server. Result can be string or object.
+
+        @method value2submit(value) 
+        @param {mixed} value
+        @returns {mixed}
+       **/
+       value2submit: function(value) {
+           return value;
+       },
+
+       /**
+        Sets value of input.
+
+        @method value2input(value) 
+        @param {mixed} value
+       **/
+       value2input: function(value) {
+           this.$input.val(value);
+       },
+
+       /**
+        Returns value of input. Value can be object (e.g. datepicker)
+
+        @method input2value() 
+       **/
+       input2value: function() { 
+           return this.$input.val();
+       }, 
+
+       /**
+        Activates input. For text it sets focus.
+
+        @method activate() 
+       **/
+       activate: function() {
+           if(this.$input.is(':visible')) {
+               this.$input.focus();
+           }
+       },
+
+       /**
+        Creates input.
+
+        @method clear() 
+       **/        
+       clear: function() {
+           this.$input.val(null);
+       },
+
+       /**
+        method to escape html.
+       **/
+       escape: function(str) {
+           return $('<div>').text(str).html();
+       },
+       
+       /**
+        attach handler to automatically submit form when value changed (useful when buttons not shown)
+       **/
+       autosubmit: function() {
+        
+       },
+       
+       /**
+       Additional actions when destroying element 
+       **/
+       destroy: function() {
+       },
+
+       // -------- helper functions --------
+       setClass: function() {          
+           if(this.options.inputclass) {
+               this.$input.addClass(this.options.inputclass); 
+           } 
+       },
+
+       setAttr: function(attr) {
+           if (this.options[attr] !== undefined && this.options[attr] !== null) {
+               this.$input.attr(attr, this.options[attr]);
+           } 
+       },
+       
+       option: function(key, value) {
+            this.options[key] = value;
+       }
+       
+    };
+        
+    AbstractInput.defaults = {  
+        /**
+        HTML template of input. Normally you should not change it.
+
+        @property tpl 
+        @type string
+        @default ''
+        **/   
+        tpl: '',
+        /**
+        CSS class automatically applied to input
+        
+        @property inputclass 
+        @type string
+        @default null
+        **/         
+        inputclass: null,
+        
+        /**
+        If `true` - html will be escaped in content of element via $.text() method.  
+        If `false` - html will not be escaped, $.html() used.  
+        When you use own `display` function, this option obviosly has no effect.
+        
+        @property escape 
+        @type boolean
+        @since 1.5.0
+        @default true
+        **/         
+        escape: true,
+                
+        //scope for external methods (e.g. source defined as function)
+        //for internal use only
+        scope: null,
+        
+        //need to re-declare showbuttons here to get it's value from common config (passed only options existing in defaults)
+        showbuttons: true 
+    };
+    
+    $.extend($.fn.editabletypes, {abstractinput: AbstractInput});
+        
+}(window.jQuery));
+
+/**
+List - abstract class for inputs that have source option loaded from js array or via ajax
+
+@class list
+@extends abstractinput
+**/
+(function ($) {
+    "use strict";
+    
+    var List = function (options) {
+       
+    };
+
+    $.fn.editableutils.inherit(List, $.fn.editabletypes.abstractinput);
+
+    $.extend(List.prototype, {
+        render: function () {
+            var deferred = $.Deferred();
+
+            this.error = null;
+            this.onSourceReady(function () {
+                this.renderList();
+                deferred.resolve();
+            }, function () {
+                this.error = this.options.sourceError;
+                deferred.resolve();
+            });
+
+            return deferred.promise();
+        },
+
+        html2value: function (html) {
+            return null; //can't set value by text
+        },
+        
+        value2html: function (value, element, display, response) {
+            var deferred = $.Deferred(),
+                success = function () {
+                    if(typeof display === 'function') {
+                        //custom display method
+                        display.call(element, value, this.sourceData, response); 
+                    } else {
+                        this.value2htmlFinal(value, element);
+                    }
+                    deferred.resolve();
+               };
+            
+            //for null value just call success without loading source
+            if(value === null) {
+               success.call(this);   
+            } else {
+               this.onSourceReady(success, function () { deferred.resolve(); });
+            }
+
+            return deferred.promise();
+        },  
+
+        // ------------- additional functions ------------
+
+        onSourceReady: function (success, error) {
+            //run source if it function
+            var source;
+            if ($.isFunction(this.options.source)) {
+                source = this.options.source.call(this.options.scope);
+                this.sourceData = null;
+                //note: if function returns the same source as URL - sourceData will be taken from cahce and no extra request performed
+            } else {
+                source = this.options.source;
+            }            
+            
+            //if allready loaded just call success
+            if(this.options.sourceCache && $.isArray(this.sourceData)) {
+                success.call(this);
+                return; 
+            }
+
+            //try parse json in single quotes (for double quotes jquery does automatically)
+            try {
+                source = $.fn.editableutils.tryParseJson(source, false);
+            } catch (e) {
+                error.call(this);
+                return;
+            }
+
+            //loading from url
+            if (typeof source === 'string') {
+                //try to get sourceData from cache
+                if(this.options.sourceCache) {
+                    var cacheID = source,
+                    cache;
+
+                    if (!$(document).data(cacheID)) {
+                        $(document).data(cacheID, {});
+                    }
+                    cache = $(document).data(cacheID);
+
+                    //check for cached data
+                    if (cache.loading === false && cache.sourceData) { //take source from cache
+                        this.sourceData = cache.sourceData;
+                        this.doPrepend();
+                        success.call(this);
+                        return;
+                    } else if (cache.loading === true) { //cache is loading, put callback in stack to be called later
+                        cache.callbacks.push($.proxy(function () {
+                            this.sourceData = cache.sourceData;
+                            this.doPrepend();
+                            success.call(this);
+                        }, this));
+
+                        //also collecting error callbacks
+                        cache.err_callbacks.push($.proxy(error, this));
+                        return;
+                    } else { //no cache yet, activate it
+                        cache.loading = true;
+                        cache.callbacks = [];
+                        cache.err_callbacks = [];
+                    }
+                }
+                
+                //ajaxOptions for source. Can be overwritten bt options.sourceOptions
+                var ajaxOptions = $.extend({
+                    url: source,
+                    type: 'get',
+                    cache: false,
+                    dataType: 'json',
+                    success: $.proxy(function (data) {
+                        if(cache) {
+                            cache.loading = false;
+                        }
+                        this.sourceData = this.makeArray(data);
+                        if($.isArray(this.sourceData)) {
+                            if(cache) {
+                                //store result in cache
+                                cache.sourceData = this.sourceData;
+                                //run success callbacks for other fields waiting for this source
+                                $.each(cache.callbacks, function () { this.call(); }); 
+                            }
+                            this.doPrepend();
+                            success.call(this);
+                        } else {
+                            error.call(this);
+                            if(cache) {
+                                //run error callbacks for other fields waiting for this source
+                                $.each(cache.err_callbacks, function () { this.call(); }); 
+                            }
+                        }
+                    }, this),
+                    error: $.proxy(function () {
+                        error.call(this);
+                        if(cache) {
+                             cache.loading = false;
+                             //run error callbacks for other fields
+                             $.each(cache.err_callbacks, function () { this.call(); }); 
+                        }
+                    }, this)
+                }, this.options.sourceOptions);
+                
+                //loading sourceData from server
+                $.ajax(ajaxOptions);
+                
+            } else { //options as json/array
+                this.sourceData = this.makeArray(source);
+                    
+                if($.isArray(this.sourceData)) {
+                    this.doPrepend();
+                    success.call(this);   
+                } else {
+                    error.call(this);
+                }
+            }
+        },
+
+        doPrepend: function () {
+            if(this.options.prepend === null || this.options.prepend === undefined) {
+                return;  
+            }
+            
+            if(!$.isArray(this.prependData)) {
+                //run prepend if it is function (once)
+                if ($.isFunction(this.options.prepend)) {
+                    this.options.prepend = this.options.prepend.call(this.options.scope);
+                }
+              
+                //try parse json in single quotes
+                this.options.prepend = $.fn.editableutils.tryParseJson(this.options.prepend, true);
+                
+                //convert prepend from string to object
+                if (typeof this.options.prepend === 'string') {
+                    this.options.prepend = {'': this.options.prepend};
+                }
+                
+                this.prependData = this.makeArray(this.options.prepend);
+            }
+
+            if($.isArray(this.prependData) && $.isArray(this.sourceData)) {
+                this.sourceData = this.prependData.concat(this.sourceData);
+            }
+        },
+
+        /*
+         renders input list
+        */
+        renderList: function() {
+            // this method should be overwritten in child class
+        },
+       
+         /*
+         set element's html by value
+        */
+        value2htmlFinal: function(value, element) {
+            // this method should be overwritten in child class
+        },        
+
+        /**
+        * convert data to array suitable for sourceData, e.g. [{value: 1, text: 'abc'}, {...}]
+        */
+        makeArray: function(data) {
+            var count, obj, result = [], item, iterateItem;
+            if(!data || typeof data === 'string') {
+                return null; 
+            }
+
+            if($.isArray(data)) { //array
+                /* 
+                   function to iterate inside item of array if item is object.
+                   Caclulates count of keys in item and store in obj. 
+                */
+                iterateItem = function (k, v) {
+                    obj = {value: k, text: v};
+                    if(count++ >= 2) {
+                        return false;// exit from `each` if item has more than one key.
+                    }
+                };
+            
+                for(var i = 0; i < data.length; i++) {
+                    item = data[i]; 
+                    if(typeof item === 'object') {
+                        count = 0; //count of keys inside item
+                        $.each(item, iterateItem);
+                        //case: [{val1: 'text1'}, {val2: 'text2} ...]
+                        if(count === 1) { 
+                            result.push(obj); 
+                            //case: [{value: 1, text: 'text1'}, {value: 2, text: 'text2'}, ...]
+                        } else if(count > 1) {
+                            //removed check of existance: item.hasOwnProperty('value') && item.hasOwnProperty('text')
+                            if(item.children) {
+                                item.children = this.makeArray(item.children);   
+                            }
+                            result.push(item);
+                        }
+                    } else {
+                        //case: ['text1', 'text2' ...]
+                        result.push({value: item, text: item}); 
+                    }
+                }
+            } else {  //case: {val1: 'text1', val2: 'text2, ...}
+                $.each(data, function (k, v) {
+                    result.push({value: k, text: v});
+                });  
+            }
+            return result;
+        },
+        
+        option: function(key, value) {
+            this.options[key] = value;
+            if(key === 'source') {
+                this.sourceData = null;
+            }
+            if(key === 'prepend') {
+                this.prependData = null;
+            }            
+        }        
+
+    });      
+
+    List.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
+        /**
+        Source data for list.  
+        If **array** - it should be in format: `[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]`  
+        For compability, object format is also supported: `{"1": "text1", "2": "text2" ...}` but it does not guarantee elements order.
+        
+        If **string** - considered ajax url to load items. In that case results will be cached for fields with the same source and name. See also `sourceCache` option.
+          
+        If **function**, it should return data in format above (since 1.4.0).
+        
+        Since 1.4.1 key `children` supported to render OPTGROUP (for **select** input only).  
+        `[{text: "group1", children: [{value: 1, text: "text1"}, {value: 2, text: "text2"}]}, ...]` 
+
+		
+        @property source 
+        @type string | array | object | function
+        @default null
+        **/         
+        source: null, 
+        /**
+        Data automatically prepended to the beginning of dropdown list.
+        
+        @property prepend 
+        @type string | array | object | function
+        @default false
+        **/         
+        prepend: false,
+        /**
+        Error message when list cannot be loaded (e.g. ajax error)
+        
+        @property sourceError 
+        @type string
+        @default Error when loading list
+        **/          
+        sourceError: 'Error when loading list',
+        /**
+        if <code>true</code> and source is **string url** - results will be cached for fields with the same source.    
+        Usefull for editable column in grid to prevent extra requests.
+        
+        @property sourceCache 
+        @type boolean
+        @default true
+        @since 1.2.0
+        **/        
+        sourceCache: true,
+        /**
+        Additional ajax options to be used in $.ajax() when loading list from server.
+        Useful to send extra parameters (`data` key) or change request method (`type` key).
+        
+        @property sourceOptions 
+        @type object|function
+        @default null
+        @since 1.5.0
+        **/        
+        sourceOptions: null
+    });
+
+    $.fn.editabletypes.list = List;      
+
+}(window.jQuery));
+
+/**
+Text input
+
+@class text
+@extends abstractinput
+@final
+@example
+<a href="#" id="username" data-type="text" data-pk="1">awesome</a>
+<script>
+$(function(){
+    $('#username').editable({
+        url: '/post',
+        title: 'Enter username'
+    });
+});
+</script>
+**/
+(function ($) {
+    "use strict";
+    
+    var Text = function (options) {
+        this.init('text', options, Text.defaults);
+    };
+
+    $.fn.editableutils.inherit(Text, $.fn.editabletypes.abstractinput);
+
+    $.extend(Text.prototype, {
+        render: function() {
+           this.renderClear();
+           this.setClass();
+           this.setAttr('placeholder');
+        },
+        
+        activate: function() {
+            if(this.$input.is(':visible')) {
+                this.$input.focus();
+                $.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length);
+                if(this.toggleClear) {
+                    this.toggleClear();
+                }
+            }
+        },
+        
+        //render clear button
+        renderClear:  function() {
+           if (this.options.clear) {
+               this.$clear = $('<span class="editable-clear-x"></span>');
+               this.$input.after(this.$clear)
+                          .css('padding-right', 24)
+                          .keyup($.proxy(function(e) {
+                              //arrows, enter, tab, etc
+                              if(~$.inArray(e.keyCode, [40,38,9,13,27])) {
+                                return;
+                              }                            
+
+                              clearTimeout(this.t);
+                              var that = this;
+                              this.t = setTimeout(function() {
+                                that.toggleClear(e);
+                              }, 100);
+                              
+                          }, this))
+                          .parent().css('position', 'relative');
+                          
+               this.$clear.click($.proxy(this.clear, this));                       
+           }            
+        },
+        
+        postrender: function() {
+            /*
+            //now `clear` is positioned via css
+            if(this.$clear) {
+                //can position clear button only here, when form is shown and height can be calculated
+//                var h = this.$input.outerHeight(true) || 20,
+                var h = this.$clear.parent().height(),
+                    delta = (h - this.$clear.height()) / 2;
+                    
+                //this.$clear.css({bottom: delta, right: delta});
+            }
+            */ 
+        },
+        
+        //show / hide clear button
+        toggleClear: function(e) {
+            if(!this.$clear) {
+                return;
+            }
+            
+            var len = this.$input.val().length,
+                visible = this.$clear.is(':visible');
+                 
+            if(len && !visible) {
+                this.$clear.show();
+            } 
+            
+            if(!len && visible) {
+                this.$clear.hide();
+            } 
+        },
+        
+        clear: function() {
+           this.$clear.hide();
+           this.$input.val('').focus();
+        }          
+    });
+
+    Text.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
+        /**
+        @property tpl 
+        @default <input type="text">
+        **/         
+        tpl: '<input type="text">',
+        /**
+        Placeholder attribute of input. Shown when input is empty.
+
+        @property placeholder 
+        @type string
+        @default null
+        **/             
+        placeholder: null,
+        
+        /**
+        Whether to show `clear` button 
+        
+        @property clear 
+        @type boolean
+        @default true        
+        **/
+        clear: true
+    });
+
+    $.fn.editabletypes.text = Text;
+
+}(window.jQuery));
+
+/**
+Textarea input
+
+@class textarea
+@extends abstractinput
+@final
+@example
+<a href="#" id="comments" data-type="textarea" data-pk="1">awesome comment!</a>
+<script>
+$(function(){
+    $('#comments').editable({
+        url: '/post',
+        title: 'Enter comments',
+        rows: 10
+    });
+});
+</script>
+**/
+(function ($) {
+    "use strict";
+    
+    var Textarea = function (options) {
+        this.init('textarea', options, Textarea.defaults);
+    };
+
+    $.fn.editableutils.inherit(Textarea, $.fn.editabletypes.abstractinput);
+
+    $.extend(Textarea.prototype, {
+        render: function () {
+            this.setClass();
+            this.setAttr('placeholder');
+            this.setAttr('rows');                        
+            
+            //ctrl + enter
+            this.$input.keydown(function (e) {
+                if (e.ctrlKey && e.which === 13) {
+                    $(this).closest('form').submit();
+                }
+            });
+        },
+        
+       //using `white-space: pre-wrap` solves \n  <--> BR conversion very elegant!
+       /* 
+       value2html: function(value, element) {
+            var html = '', lines;
+            if(value) {
+                lines = value.split("\n");
+                for (var i = 0; i < lines.length; i++) {
+                    lines[i] = $('<div>').text(lines[i]).html();
+                }
+                html = lines.join('<br>');
+            }
+            $(element).html(html);
+        },
+       
+        html2value: function(html) {
+            if(!html) {
+                return '';
+            }
+
+            var regex = new RegExp(String.fromCharCode(10), 'g');
+            var lines = html.split(/<br\s*\/?>/i);
+            for (var i = 0; i < lines.length; i++) {
+                var text = $('<div>').html(lines[i]).text();
+
+                // Remove newline characters (\n) to avoid them being converted by value2html() method
+                // thus adding extra <br> tags
+                text = text.replace(regex, '');
+
+                lines[i] = text;
+            }
+            return lines.join("\n");
+        },
+         */
+        activate: function() {
+            $.fn.editabletypes.text.prototype.activate.call(this);
+        }
+    });
+
+    Textarea.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
+        /**
+        @property tpl
+        @default <textarea></textarea>
+        **/
+        tpl:'<textarea></textarea>',
+        /**
+        @property inputclass
+        @default input-large
+        **/
+        inputclass: 'input-large',
+        /**
+        Placeholder attribute of input. Shown when input is empty.
+
+        @property placeholder
+        @type string
+        @default null
+        **/
+        placeholder: null,
+        /**
+        Number of rows in textarea
+
+        @property rows
+        @type integer
+        @default 7
+        **/        
+        rows: 7        
+    });
+
+    $.fn.editabletypes.textarea = Textarea;
+
+}(window.jQuery));
+
+/**
+Select (dropdown)
+
+@class select
+@extends list
+@final
+@example
+<a href="#" id="status" data-type="select" data-pk="1" data-url="/post" data-title="Select status"></a>
+<script>
+$(function(){
+    $('#status').editable({
+        value: 2,    
+        source: [
+              {value: 1, text: 'Active'},
+              {value: 2, text: 'Blocked'},
+              {value: 3, text: 'Deleted'}
+           ]
+    });
+});
+</script>
+**/
+(function ($) {
+    "use strict";
+    
+    var Select = function (options) {
+        this.init('select', options, Select.defaults);
+    };
+
+    $.fn.editableutils.inherit(Select, $.fn.editabletypes.list);
+
+    $.extend(Select.prototype, {
+        renderList: function() {
+            this.$input.empty();
+
+            var fillItems = function($el, data) {
+                var attr;
+                if($.isArray(data)) {
+                    for(var i=0; i<data.length; i++) {
+                        attr = {};
+                        if(data[i].children) {
+                            attr.label = data[i].text;
+                            $el.append(fillItems($('<optgroup>', attr), data[i].children)); 
+                        } else {
+                            attr.value = data[i].value;
+                            if(data[i].disabled) {
+                                attr.disabled = true;
+                            }
+                            $el.append($('<option>', attr).text(data[i].text)); 
+                        }
+                    }
+                }
+                return $el;
+            };        
+
+            fillItems(this.$input, this.sourceData);
+            
+            this.setClass();
+            
+            //enter submit
+            this.$input.on('keydown.editable', function (e) {
+                if (e.which === 13) {
+                    $(this).closest('form').submit();
+                }
+            });            
+        },
+       
+        value2htmlFinal: function(value, element) {
+            var text = '', 
+                items = $.fn.editableutils.itemsByValue(value, this.sourceData);
+                
+            if(items.length) {
+                text = items[0].text;
+            }
+            
+            //$(element).text(text);
+            $.fn.editabletypes.abstractinput.prototype.value2html.call(this, text, element);
+        },
+        
+        autosubmit: function() {
+            this.$input.off('keydown.editable').on('change.editable', function(){
+                $(this).closest('form').submit();
+            });
+        }
+    });      
+
+    Select.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
+        /**
+        @property tpl 
+        @default <select></select>
+        **/         
+        tpl:'<select></select>'
+    });
+
+    $.fn.editabletypes.select = Select;      
+
+}(window.jQuery));
+
+/**
+List of checkboxes. 
+Internally value stored as javascript array of values.
+
+@class checklist
+@extends list
+@final
+@example
+<a href="#" id="options" data-type="checklist" data-pk="1" data-url="/post" data-title="Select options"></a>
+<script>
+$(function(){
+    $('#options').editable({
+        value: [2, 3],    
+        source: [
+              {value: 1, text: 'option1'},
+              {value: 2, text: 'option2'},
+              {value: 3, text: 'option3'}
+           ]
+    });
+});
+</script>
+**/
+(function ($) {
+    "use strict";
+    
+    var Checklist = function (options) {
+        this.init('checklist', options, Checklist.defaults);
+    };
+
+    $.fn.editableutils.inherit(Checklist, $.fn.editabletypes.list);
+
+    $.extend(Checklist.prototype, {
+        renderList: function() {
+            var $label, $div;
+            
+            this.$tpl.empty();
+            
+            if(!$.isArray(this.sourceData)) {
+                return;
+            }
+
+            for(var i=0; i<this.sourceData.length; i++) {
+                $label = $('<label>').append($('<input>', {
+                                           type: 'checkbox',
+                                           value: this.sourceData[i].value 
+                                     }))
+                                     .append($('<span>').text(' '+this.sourceData[i].text));
+                
+                $('<div>').append($label).appendTo(this.$tpl);
+            }
+            
+            this.$input = this.$tpl.find('input[type="checkbox"]');
+            this.setClass();
+        },
+       
+       value2str: function(value) {
+           return $.isArray(value) ? value.sort().join($.trim(this.options.separator)) : '';
+       },  
+       
+       //parse separated string
+        str2value: function(str) {
+           var reg, value = null;
+           if(typeof str === 'string' && str.length) {
+               reg = new RegExp('\\s*'+$.trim(this.options.separator)+'\\s*');
+               value = str.split(reg);
+           } else if($.isArray(str)) {
+               value = str; 
+           } else {
+               value = [str];
+           }
+           return value;
+        },       
+       
+       //set checked on required checkboxes
+       value2input: function(value) {
+            this.$input.prop('checked', false);
+            if($.isArray(value) && value.length) {
+               this.$input.each(function(i, el) {
+                   var $el = $(el);
+                   // cannot use $.inArray as it performs strict comparison
+                   $.each(value, function(j, val){
+                       /*jslint eqeq: true*/
+                       if($el.val() == val) {
+                       /*jslint eqeq: false*/                           
+                           $el.prop('checked', true);
+                       }
+                   });
+               }); 
+            }  
+        },  
+        
+       input2value: function() { 
+           var checked = [];
+           this.$input.filter(':checked').each(function(i, el) {
+               checked.push($(el).val());
+           });
+           return checked;
+       },            
+          
+       //collect text of checked boxes
+        value2htmlFinal: function(value, element) {
+           var html = [],
+               checked = $.fn.editableutils.itemsByValue(value, this.sourceData),
+               escape = this.options.escape;
+               
+           if(checked.length) {
+               $.each(checked, function(i, v) {
+                   var text = escape ? $.fn.editableutils.escape(v.text) : v.text; 
+                   html.push(text); 
+               });
+               $(element).html(html.join('<br>'));
+           } else {
+               $(element).empty(); 
+           }
+        },
+        
+       activate: function() {
+           this.$input.first().focus();
+       },
+       
+       autosubmit: function() {
+           this.$input.on('keydown', function(e){
+               if (e.which === 13) {
+                   $(this).closest('form').submit();
+               }
+           });
+       }
+    });      
+
+    Checklist.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
+        /**
+        @property tpl 
+        @default <div></div>
+        **/         
+        tpl:'<div class="editable-checklist"></div>',
+        
+        /**
+        @property inputclass 
+        @type string
+        @default null
+        **/         
+        inputclass: null,        
+        
+        /**
+        Separator of values when reading from `data-value` attribute
+
+        @property separator 
+        @type string
+        @default ','
+        **/         
+        separator: ','
+    });
+
+    $.fn.editabletypes.checklist = Checklist;      
+
+}(window.jQuery));
+
+/**
+HTML5 input types.
+Following types are supported:
+
+* password
+* email
+* url
+* tel
+* number
+* range
+* time
+
+Learn more about html5 inputs:  
+http://www.w3.org/wiki/HTML5_form_additions  
+To check browser compatibility please see:  
+https://developer.mozilla.org/en-US/docs/HTML/Element/Input
+            
+@class html5types 
+@extends text
+@final
+@since 1.3.0
+@example
+<a href="#" id="email" data-type="email" data-pk="1">admin@example.com</a>
+<script>
+$(function(){
+    $('#email').editable({
+        url: '/post',
+        title: 'Enter email'
+    });
+});
+</script>
+**/
+
+/**
+@property tpl 
+@default depends on type
+**/ 
+
+/*
+Password
+*/
+(function ($) {
+    "use strict";
+    
+    var Password = function (options) {
+        this.init('password', options, Password.defaults);
+    };
+    $.fn.editableutils.inherit(Password, $.fn.editabletypes.text);
+    $.extend(Password.prototype, {
+       //do not display password, show '[hidden]' instead
+       value2html: function(value, element) {
+           if(value) {
+               $(element).text('[hidden]');
+           } else {
+               $(element).empty(); 
+           }
+       },
+       //as password not displayed, should not set value by html
+       html2value: function(html) {
+           return null;
+       }       
+    });    
+    Password.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
+        tpl: '<input type="password">'
+    });
+    $.fn.editabletypes.password = Password;
+}(window.jQuery));
+
+
+/*
+Email
+*/
+(function ($) {
+    "use strict";
+    
+    var Email = function (options) {
+        this.init('email', options, Email.defaults);
+    };
+    $.fn.editableutils.inherit(Email, $.fn.editabletypes.text);
+    Email.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
+        tpl: '<input type="email">'
+    });
+    $.fn.editabletypes.email = Email;
+}(window.jQuery));
+
+
+/*
+Url
+*/
+(function ($) {
+    "use strict";
+    
+    var Url = function (options) {
+        this.init('url', options, Url.defaults);
+    };
+    $.fn.editableutils.inherit(Url, $.fn.editabletypes.text);
+    Url.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
+        tpl: '<input type="url">'
+    });
+    $.fn.editabletypes.url = Url;
+}(window.jQuery));
+
+
+/*
+Tel
+*/
+(function ($) {
+    "use strict";
+    
+    var Tel = function (options) {
+        this.init('tel', options, Tel.defaults);
+    };
+    $.fn.editableutils.inherit(Tel, $.fn.editabletypes.text);
+    Tel.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
+        tpl: '<input type="tel">'
+    });
+    $.fn.editabletypes.tel = Tel;
+}(window.jQuery));
+
+
+/*
+Number
+*/
+(function ($) {
+    "use strict";
+    
+    var NumberInput = function (options) {
+        this.init('number', options, NumberInput.defaults);
+    };
+    $.fn.editableutils.inherit(NumberInput, $.fn.editabletypes.text);
+    $.extend(NumberInput.prototype, {
+         render: function () {
+            NumberInput.superclass.render.call(this);
+            this.setAttr('min');
+            this.setAttr('max');
+            this.setAttr('step');
+        },
+        postrender: function() {
+            if(this.$clear) {
+                //increase right ffset  for up/down arrows
+                this.$clear.css({right: 24});
+                /*
+                //can position clear button only here, when form is shown and height can be calculated
+                var h = this.$input.outerHeight(true) || 20,
+                    delta = (h - this.$clear.height()) / 2;
+                
+                //add 12px to offset right for up/down arrows    
+                this.$clear.css({top: delta, right: delta + 16});
+                */
+            } 
+        }        
+    });     
+    NumberInput.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {
+        tpl: '<input type="number">',
+        inputclass: 'input-mini',
+        min: null,
+        max: null,
+        step: null
+    });
+    $.fn.editabletypes.number = NumberInput;
+}(window.jQuery));
+
+
+/*
+Range (inherit from number)
+*/
+(function ($) {
+    "use strict";
+    
+    var Range = function (options) {
+        this.init('range', options, Range.defaults);
+    };
+    $.fn.editableutils.inherit(Range, $.fn.editabletypes.number);
+    $.extend(Range.prototype, {
+        render: function () {
+            this.$input = this.$tpl.filter('input');
+            
+            this.setClass();
+            this.setAttr('min');
+            this.setAttr('max');
+            this.setAttr('step');           
+            
+            this.$input.on('input', function(){
+                $(this).siblings('output').text($(this).val()); 
+            });  
+        },
+        activate: function() {
+            this.$input.focus();
+        }         
+    });
+    Range.defaults = $.extend({}, $.fn.editabletypes.number.defaults, {
+        tpl: '<input type="range"><output style="width: 30px; display: inline-block"></output>',
+        inputclass: 'input-medium'
+    });
+    $.fn.editabletypes.range = Range;
+}(window.jQuery));
+
+/*
+Time
+*/
+(function ($) {
+    "use strict";
+
+    var Time = function (options) {
+        this.init('time', options, Time.defaults);
+    };
+    //inherit from abstract, as inheritance from text gives selection error.
+    $.fn.editableutils.inherit(Time, $.fn.editabletypes.abstractinput);
+    $.extend(Time.prototype, {
+        render: function() {
+           this.setClass();
+        }        
+    });
+    Time.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
+        tpl: '<input type="time">'
+    });
+    $.fn.editabletypes.time = Time;
+}(window.jQuery));
+
+/**
+Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2.  
+Please see [original select2 docs](http://ivaynberg.github.com/select2) for detailed description and options.  
+ 
+You should manually download and include select2 distributive:  
+
+    <link href="select2/select2.css" rel="stylesheet" type="text/css"></link>  
+    <script src="select2/select2.js"></script>  
+    
+To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css): 
+
+    <link href="select2-bootstrap.css" rel="stylesheet" type="text/css"></link>    
+    
+**Note:** currently `autotext` feature does not work for select2 with `ajax` remote source.    
+You need initially put both `data-value` and element's text youself:    
+
+    <a href="#" data-type="select2" data-value="1">Text1</a>
+    
+    
+@class select2
+@extends abstractinput
+@since 1.4.1
+@final
+@example
+<a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-title="Select country"></a>
+<script>
+$(function(){
+    //local source
+    $('#country').editable({
+        source: [
+              {id: 'gb', text: 'Great Britain'},
+              {id: 'us', text: 'United States'},
+              {id: 'ru', text: 'Russia'}
+           ],
+        select2: {
+           multiple: true
+        }
+    });
+    //remote source (simple)
+    $('#country').editable({
+        source: '/getCountries',
+        select2: {
+            placeholder: 'Select Country',
+            minimumInputLength: 1
+        }
+    });
+    //remote source (advanced)
+    $('#country').editable({
+        select2: {
+            placeholder: 'Select Country',
+            allowClear: true,
+            minimumInputLength: 3,
+            id: function (item) {
+                return item.CountryId;
+            },
+            ajax: {
+                url: '/getCountries',
+                dataType: 'json',
+                data: function (term, page) {
+                    return { query: term };
+                },
+                results: function (data, page) {
+                    return { results: data };
+                }
+            },
+            formatResult: function (item) {
+                return item.CountryName;
+            },
+            formatSelection: function (item) {
+                return item.CountryName;
+            },
+            initSelection: function (element, callback) {
+                return $.get('/getCountryById', { query: element.val() }, function (data) {
+                    callback(data);
+                });
+            } 
+        }  
+    });
+});
+</script>
+**/
+(function ($) {
+    "use strict";
+    
+    var Constructor = function (options) {
+        this.init('select2', options, Constructor.defaults);
+
+        options.select2 = options.select2 || {};
+
+        this.sourceData = null;
+        
+        //placeholder
+        if(options.placeholder) {
+            options.select2.placeholder = options.placeholder;
+        }
+       
+        //if not `tags` mode, use source
+        if(!options.select2.tags && options.source) {
+            var source = options.source;
+            //if source is function, call it (once!)
+            if ($.isFunction(options.source)) {
+                source = options.source.call(options.scope);
+            }               
+
+            if (typeof source === 'string') {
+                options.select2.ajax = options.select2.ajax || {};
+                //some default ajax params
+                if(!options.select2.ajax.data) {
+                    options.select2.ajax.data = function(term) {return { query:term };};
+                }
+                if(!options.select2.ajax.results) {
+                    options.select2.ajax.results = function(data) { return {results:data };};
+                }
+                options.select2.ajax.url = source;
+            } else {
+                //check format and convert x-editable format to select2 format (if needed)
+                this.sourceData = this.convertSource(source);
+                options.select2.data = this.sourceData;
+            }
+        } 
+
+        //overriding objects in config (as by default jQuery extend() is not recursive)
+        this.options.select2 = $.extend({}, Constructor.defaults.select2, options.select2);
+
+        //detect whether it is multi-valued
+        this.isMultiple = this.options.select2.tags || this.options.select2.multiple;
+        this.isRemote = ('ajax' in this.options.select2);
+
+        //store function returning ID of item
+        //should be here as used inautotext for local source
+        this.idFunc = this.options.select2.id;
+        if (typeof(this.idFunc) !== "function") {
+            var idKey = this.idFunc || 'id';
+            this.idFunc = function (e) { return e[idKey]; };
+        }
+
+        //store function that renders text in select2
+        this.formatSelection = this.options.select2.formatSelection;
+        if (typeof(this.formatSelection) !== "function") {
+            this.formatSelection = function (e) { return e.text; };
+        }
+    };
+
+    $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
+
+    $.extend(Constructor.prototype, {
+        render: function() {
+            this.setClass();
+
+            //can not apply select2 here as it calls initSelection 
+            //over input that does not have correct value yet.
+            //apply select2 only in value2input
+            //this.$input.select2(this.options.select2);
+
+            //when data is loaded via ajax, we need to know when it's done to populate listData
+            if(this.isRemote) {
+                //listen to loaded event to populate data
+                this.$input.on('select2-loaded', $.proxy(function(e) {
+                    this.sourceData = e.items.results;
+                }, this));
+            }
+
+            //trigger resize of editableform to re-position container in multi-valued mode
+            if(this.isMultiple) {
+               this.$input.on('change', function() {
+                   $(this).closest('form').parent().triggerHandler('resize');
+               });
+            }
+       },
+
+       value2html: function(value, element) {
+           var text = '', data,
+               that = this;
+
+           if(this.options.select2.tags) { //in tags mode just assign value
+              data = value; 
+              //data = $.fn.editableutils.itemsByValue(value, this.options.select2.tags, this.idFunc);
+           } else if(this.sourceData) {
+              data = $.fn.editableutils.itemsByValue(value, this.sourceData, this.idFunc); 
+           } else {
+              //can not get list of possible values 
+              //(e.g. autotext for select2 with ajax source)
+           }
+
+           //data may be array (when multiple values allowed)
+           if($.isArray(data)) {
+               //collect selected data and show with separator
+               text = [];
+               $.each(data, function(k, v){
+                   text.push(v && typeof v === 'object' ? that.formatSelection(v) : v);
+               });
+           } else if(data) {
+               text = that.formatSelection(data);
+           }
+
+           text = $.isArray(text) ? text.join(this.options.viewseparator) : text;
+
+           //$(element).text(text);
+           Constructor.superclass.value2html.call(this, text, element); 
+       },
+
+       html2value: function(html) {
+           return this.options.select2.tags ? this.str2value(html, this.options.viewseparator) : null;
+       },
+
+       value2input: function(value) {
+           // if value array => join it anyway
+           if($.isArray(value)) {
+              value = value.join(this.getSeparator());
+           }
+
+           //for remote source just set value, text is updated by initSelection
+           if(!this.$input.data('select2')) {
+               this.$input.val(value);
+               this.$input.select2(this.options.select2);
+           } else {
+               //second argument needed to separate initial change from user's click (for autosubmit)   
+               this.$input.val(value).trigger('change', true); 
+
+               //Uncaught Error: cannot call val() if initSelection() is not defined
+               //this.$input.select2('val', value);
+           }
+
+           // if defined remote source AND no multiple mode AND no user's initSelection provided --> 
+           // we should somehow get text for provided id.
+           // The solution is to use element's text as text for that id (exclude empty)
+           if(this.isRemote && !this.isMultiple && !this.options.select2.initSelection) {
+               // customId and customText are methods to extract `id` and `text` from data object
+               // we can use this workaround only if user did not define these methods
+               // otherwise we cant construct data object
+               var customId = this.options.select2.id,
+                   customText = this.options.select2.formatSelection;
+
+               if(!customId && !customText) {
+                   var $el = $(this.options.scope);
+                   if (!$el.data('editable').isEmpty) {
+                       var data = {id: value, text: $el.text()};
+                       this.$input.select2('data', data); 
+                   }
+               }
+           }
+       },
+       
+       input2value: function() { 
+           return this.$input.select2('val');
+       },
+
+       str2value: function(str, separator) {
+            if(typeof str !== 'string' || !this.isMultiple) {
+                return str;
+            }
+
+            separator = separator || this.getSeparator();
+
+            var val, i, l;
+
+            if (str === null || str.length < 1) {
+                return null;
+            }
+            val = str.split(separator);
+            for (i = 0, l = val.length; i < l; i = i + 1) {
+                val[i] = $.trim(val[i]);
+            }
+
+            return val;
+       },
+
+        autosubmit: function() {
+            this.$input.on('change', function(e, isInitial){
+                if(!isInitial) {
+                  $(this).closest('form').submit();
+                }
+            });
+        },
+
+        getSeparator: function() {
+            return this.options.select2.separator || $.fn.select2.defaults.separator;
+        },
+
+        /*
+        Converts source from x-editable format: {value: 1, text: "1"} to
+        select2 format: {id: 1, text: "1"}
+        */
+        convertSource: function(source) {
+            if($.isArray(source) && source.length && source[0].value !== undefined) {
+                for(var i = 0; i<source.length; i++) {
+                    if(source[i].value !== undefined) {
+                        source[i].id = source[i].value;
+                        delete source[i].value;
+                    }
+                }
+            }
+            return source;
+        },
+        
+        destroy: function() {
+            if(this.$input.data('select2')) {
+                this.$input.select2('destroy');
+            }
+        }
+        
+    });
+
+    Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
+        /**
+        @property tpl 
+        @default <input type="hidden">
+        **/
+        tpl:'<input type="hidden">',
+        /**
+        Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).
+
+        @property select2 
+        @type object
+        @default null
+        **/
+        select2: null,
+        /**
+        Placeholder attribute of select
+
+        @property placeholder 
+        @type string
+        @default null
+        **/
+        placeholder: null,
+        /**
+        Source data for select. It will be assigned to select2 `data` property and kept here just for convenience.
+        Please note, that format is different from simple `select` input: use 'id' instead of 'value'.
+        E.g. `[{id: 1, text: "text1"}, {id: 2, text: "text2"}, ...]`.
+
+        @property source 
+        @type array|string|function
+        @default null        
+        **/
+        source: null,
+        /**
+        Separator used to display tags.
+
+        @property viewseparator 
+        @type string
+        @default ', '        
+        **/
+        viewseparator: ', '
+    });
+
+    $.fn.editabletypes.select2 = Constructor;
+
+}(window.jQuery));
+
+/**
+* Combodate - 1.0.5
+* Dropdown date and time picker.
+* Converts text input into dropdowns to pick day, month, year, hour, minute and second.
+* Uses momentjs as datetime library http://momentjs.com.
+* For i18n include corresponding file from https://github.com/timrwood/moment/tree/master/lang 
+*
+* Confusion at noon and midnight - see http://en.wikipedia.org/wiki/12-hour_clock#Confusion_at_noon_and_midnight
+* In combodate: 
+* 12:00 pm --> 12:00 (24-h format, midday)
+* 12:00 am --> 00:00 (24-h format, midnight, start of day)
+* 
+* Differs from momentjs parse rules:
+* 00:00 pm, 12:00 pm --> 12:00 (24-h format, day not change)
+* 00:00 am, 12:00 am --> 00:00 (24-h format, day not change)
+* 
+* 
+* Author: Vitaliy Potapov
+* Project page: http://github.com/vitalets/combodate
+* Copyright (c) 2012 Vitaliy Potapov. Released under MIT License.
+**/
+(function ($) {
+
+    var Combodate = function (element, options) {
+        this.$element = $(element);
+        if(!this.$element.is('input')) {
+            $.error('Combodate should be applied to INPUT element');
+            return;
+        }
+        this.options = $.extend({}, $.fn.combodate.defaults, options, this.$element.data());
+        this.init();  
+     };
+
+    Combodate.prototype = {
+        constructor: Combodate, 
+        init: function () {
+            this.map = {
+                //key   regexp    moment.method
+                day:    ['D',    'date'], 
+                month:  ['M',    'month'], 
+                year:   ['Y',    'year'], 
+                hour:   ['[Hh]', 'hours'],
+                minute: ['m',    'minutes'], 
+                second: ['s',    'seconds'],
+                ampm:   ['[Aa]', ''] 
+            };
+            
+            this.$widget = $('<span class="combodate"></span>').html(this.getTemplate());
+                      
+            this.initCombos();
+            
+            //update original input on change 
+            this.$widget.on('change', 'select', $.proxy(function(e) {
+                this.$element.val(this.getValue()).change();
+                // update days count if month or year changes
+                if (this.options.smartDays) {
+                    if ($(e.target).is('.month') || $(e.target).is('.year')) {
+                        this.fillCombo('day');
+                    }
+                }
+            }, this));
+            
+            this.$widget.find('select').css('width', 'auto');
+                                       
+            // hide original input and insert widget                                       
+            this.$element.hide().after(this.$widget);
+            
+            // set initial value
+            this.setValue(this.$element.val() || this.options.value);
+        },
+        
+        /*
+         Replace tokens in template with <select> elements 
+        */         
+        getTemplate: function() {
+            var tpl = this.options.template;
+
+            //first pass
+            $.each(this.map, function(k, v) {
+                v = v[0]; 
+                var r = new RegExp(v+'+'),
+                    token = v.length > 1 ? v.substring(1, 2) : v;
+                    
+                tpl = tpl.replace(r, '{'+token+'}');
+            });
+
+            //replace spaces with &nbsp;
+            tpl = tpl.replace(/ /g, '&nbsp;');
+
+            //second pass
+            $.each(this.map, function(k, v) {
+                v = v[0];
+                var token = v.length > 1 ? v.substring(1, 2) : v;
+                    
+                tpl = tpl.replace('{'+token+'}', '<select class="'+k+'"></select>');
+            });   
+
+            return tpl;
+        },
+        
+        /*
+         Initialize combos that presents in template 
+        */        
+        initCombos: function() {
+            for (var k in this.map) {
+                var $c = this.$widget.find('.'+k);
+                // set properties like this.$day, this.$month etc.
+                this['$'+k] = $c.length ? $c : null;
+                // fill with items
+                this.fillCombo(k);
+            }
+        },
+
+        /*
+         Fill combo with items 
+        */        
+        fillCombo: function(k) {
+            var $combo = this['$'+k];
+            if (!$combo) {
+                return;
+            }
+
+            // define method name to fill items, e.g `fillDays`
+            var f = 'fill' + k.charAt(0).toUpperCase() + k.slice(1); 
+            var items = this[f]();
+            var value = $combo.val();
+
+            $combo.empty();
+            for(var i=0; i<items.length; i++) {
+                $combo.append('<option value="'+items[i][0]+'">'+items[i][1]+'</option>');
+            }
+
+            $combo.val(value);
+        },
+
+        /*
+         Initialize items of combos. Handles `firstItem` option 
+        */
+        fillCommon: function(key) {
+            var values = [],
+                relTime;
+                
+            if(this.options.firstItem === 'name') {
+                //need both to support moment ver < 2 and  >= 2
+                relTime = moment.relativeTime || moment.langData()._relativeTime; 
+                var header = typeof relTime[key] === 'function' ? relTime[key](1, true, key, false) : relTime[key];
+                //take last entry (see momentjs lang files structure) 
+                header = header.split(' ').reverse()[0];                
+                values.push(['', header]);
+            } else if(this.options.firstItem === 'empty') {
+                values.push(['', '']);
+            }
+            return values;
+        },  
+
+
+        /*
+        fill day
+        */
+        fillDay: function() {
+            var items = this.fillCommon('d'), name, i,
+                twoDigit = this.options.template.indexOf('DD') !== -1,
+                daysCount = 31;
+
+            // detect days count (depends on month and year)
+            // originally https://github.com/vitalets/combodate/pull/7
+            if (this.options.smartDays && this.$month && this.$year) {
+                var month = parseInt(this.$month.val(), 10);
+                var year = parseInt(this.$year.val(), 10);
+
+                if (!isNaN(month) && !isNaN(year)) {
+                    daysCount = moment([year, month]).daysInMonth();
+                }
+            }
+
+            for (i = 1; i <= daysCount; i++) {
+                name = twoDigit ? this.leadZero(i) : i;
+                items.push([i, name]);
+            }
+            return items;        
+        },
+        
+        /*
+        fill month
+        */
+        fillMonth: function() {
+            var items = this.fillCommon('M'), name, i, 
+                longNames = this.options.template.indexOf('MMMM') !== -1,
+                shortNames = this.options.template.indexOf('MMM') !== -1,
+                twoDigit = this.options.template.indexOf('MM') !== -1;
+                
+            for(i=0; i<=11; i++) {
+                if(longNames) {
+                    //see https://github.com/timrwood/momentjs.com/pull/36
+                    name = moment().date(1).month(i).format('MMMM');
+                } else if(shortNames) {
+                    name = moment().date(1).month(i).format('MMM');
+                } else if(twoDigit) {
+                    name = this.leadZero(i+1);
+                } else {
+                    name = i+1;
+                }
+                items.push([i, name]);
+            } 
+            return items;
+        },  
+        
+        /*
+        fill year
+        */
+        fillYear: function() {
+            var items = [], name, i, 
+                longNames = this.options.template.indexOf('YYYY') !== -1;
+           
+            for(i=this.options.maxYear; i>=this.options.minYear; i--) {
+                name = longNames ? i : (i+'').substring(2);
+                items[this.options.yearDescending ? 'push' : 'unshift']([i, name]);
+            }
+            
+            items = this.fillCommon('y').concat(items);
+            
+            return items;              
+        },    
+        
+        /*
+        fill hour
+        */
+        fillHour: function() {
+            var items = this.fillCommon('h'), name, i,
+                h12 = this.options.template.indexOf('h') !== -1,
+                h24 = this.options.template.indexOf('H') !== -1,
+                twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1,
+                min = h12 ? 1 : 0, 
+                max = h12 ? 12 : 23;
+                
+            for(i=min; i<=max; i++) {
+                name = twoDigit ? this.leadZero(i) : i;
+                items.push([i, name]);
+            } 
+            return items;                 
+        },    
+        
+        /*
+        fill minute
+        */
+        fillMinute: function() {
+            var items = this.fillCommon('m'), name, i,
+                twoDigit = this.options.template.indexOf('mm') !== -1;
+
+            for(i=0; i<=59; i+= this.options.minuteStep) {
+                name = twoDigit ? this.leadZero(i) : i;
+                items.push([i, name]);
+            }    
+            return items;              
+        },  
+        
+        /*
+        fill second
+        */
+        fillSecond: function() {
+            var items = this.fillCommon('s'), name, i,
+                twoDigit = this.options.template.indexOf('ss') !== -1;
+
+            for(i=0; i<=59; i+= this.options.secondStep) {
+                name = twoDigit ? this.leadZero(i) : i;
+                items.push([i, name]);
+            }    
+            return items;              
+        },  
+        
+        /*
+        fill ampm
+        */
+        fillAmpm: function() {
+            var ampmL = this.options.template.indexOf('a') !== -1,
+                ampmU = this.options.template.indexOf('A') !== -1,            
+                items = [
+                    ['am', ampmL ? 'am' : 'AM'],
+                    ['pm', ampmL ? 'pm' : 'PM']
+                ];
+            return items;                              
+        },                                       
+
+        /*
+         Returns current date value from combos. 
+         If format not specified - `options.format` used.
+         If format = `null` - Moment object returned.
+        */
+        getValue: function(format) {
+            var dt, values = {}, 
+                that = this,
+                notSelected = false;
+                
+            //getting selected values    
+            $.each(this.map, function(k, v) {
+                if(k === 'ampm') {
+                    return;
+                }
+                var def = k === 'day' ? 1 : 0;
+                  
+                values[k] = that['$'+k] ? parseInt(that['$'+k].val(), 10) : def; 
+                
+                if(isNaN(values[k])) {
+                   notSelected = true;
+                   return false; 
+                }
+            });
+            
+            //if at least one visible combo not selected - return empty string
+            if(notSelected) {
+               return '';
+            }
+            
+            //convert hours 12h --> 24h 
+            if(this.$ampm) {
+                //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
+                if(values.hour === 12) {
+                    values.hour = this.$ampm.val() === 'am' ? 0 : 12;                    
+                } else {
+                    values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12;
+                }
+            }    
+            
+            dt = moment([values.year, values.month, values.day, values.hour, values.minute, values.second]);
+            
+            //highlight invalid date
+            this.highlight(dt);
+                              
+            format = format === undefined ? this.options.format : format;
+            if(format === null) {
+               return dt.isValid() ? dt : null; 
+            } else {
+               return dt.isValid() ? dt.format(format) : ''; 
+            }           
+        },
+        
+        setValue: function(value) {
+            if(!value) {
+                return;
+            }
+            
+            var dt = typeof value === 'string' ? moment(value, this.options.format) : moment(value),
+                that = this,
+                values = {};
+            
+            //function to find nearest value in select options
+            function getNearest($select, value) {
+                var delta = {};
+                $select.children('option').each(function(i, opt){
+                    var optValue = $(opt).attr('value'),
+                    distance;
+
+                    if(optValue === '') return;
+                    distance = Math.abs(optValue - value); 
+                    if(typeof delta.distance === 'undefined' || distance < delta.distance) {
+                        delta = {value: optValue, distance: distance};
+                    } 
+                }); 
+                return delta.value;
+            }             
+            
+            if(dt.isValid()) {
+                //read values from date object
+                $.each(this.map, function(k, v) {
+                    if(k === 'ampm') {
+                       return; 
+                    }
+                    values[k] = dt[v[1]]();
+                });
+               
+                if(this.$ampm) {
+                    //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
+                    if(values.hour >= 12) {
+                        values.ampm = 'pm';
+                        if(values.hour > 12) {
+                            values.hour -= 12;
+                        }
+                    } else {
+                        values.ampm = 'am';
+                        if(values.hour === 0) {
+                            values.hour = 12;
+                        }
+                    } 
+                }
+               
+                $.each(values, function(k, v) {
+                    //call val() for each existing combo, e.g. this.$hour.val()
+                    if(that['$'+k]) {
+                       
+                        if(k === 'minute' && that.options.minuteStep > 1 && that.options.roundTime) {
+                           v = getNearest(that['$'+k], v);
+                        }
+                       
+                        if(k === 'second' && that.options.secondStep > 1 && that.options.roundTime) {
+                           v = getNearest(that['$'+k], v);
+                        }                       
+                       
+                        that['$'+k].val(v);
+                    }
+                });
+
+                // update days count
+                if (this.options.smartDays) {
+                    this.fillCombo('day');
+                }
+               
+               this.$element.val(dt.format(this.options.format)).change();
+            }
+        },
+        
+        /*
+         highlight combos if date is invalid
+        */
+        highlight: function(dt) {
+            if(!dt.isValid()) {
+                if(this.options.errorClass) {
+                    this.$widget.addClass(this.options.errorClass);
+                } else {
+                    //store original border color
+                    if(!this.borderColor) {
+                        this.borderColor = this.$widget.find('select').css('border-color'); 
+                    }
+                    this.$widget.find('select').css('border-color', 'red');
+                } 
+            } else {
+                if(this.options.errorClass) {
+                    this.$widget.removeClass(this.options.errorClass);
+                } else {
+                    this.$widget.find('select').css('border-color', this.borderColor);
+                }  
+            }
+        },
+        
+        leadZero: function(v) {
+            return v <= 9 ? '0' + v : v; 
+        },
+        
+        destroy: function() {
+            this.$widget.remove();
+            this.$element.removeData('combodate').show();
+        }
+        
+        //todo: clear method        
+    };
+
+    $.fn.combodate = function ( option ) {
+        var d, args = Array.apply(null, arguments);
+        args.shift();
+
+        //getValue returns date as string / object (not jQuery object)
+        if(option === 'getValue' && this.length && (d = this.eq(0).data('combodate'))) {
+          return d.getValue.apply(d, args);
+        }        
+        
+        return this.each(function () {
+            var $this = $(this),
+            data = $this.data('combodate'),
+            options = typeof option == 'object' && option;
+            if (!data) {
+                $this.data('combodate', (data = new Combodate(this, options)));
+            }
+            if (typeof option == 'string' && typeof data[option] == 'function') {
+                data[option].apply(data, args);
+            }
+        });
+    };  
+    
+    $.fn.combodate.defaults = {
+         //in this format value stored in original input
+        format: 'DD-MM-YYYY HH:mm',      
+        //in this format items in dropdowns are displayed
+        template: 'D / MMM / YYYY   H : mm',
+        //initial value, can be `new Date()`    
+        value: null,                       
+        minYear: 1970,
+        maxYear: 2015,
+        yearDescending: true,
+        minuteStep: 5,
+        secondStep: 1,
+        firstItem: 'empty', //'name', 'empty', 'none'
+        errorClass: null,
+        roundTime: true, // whether to round minutes and seconds if step > 1
+        smartDays: false // whether days in combo depend on selected month: 31, 30, 28
+    };
+
+}(window.jQuery));
+/**
+Combodate input - dropdown date and time picker.    
+Based on [combodate](http://vitalets.github.com/combodate) plugin (included). To use it you should manually include [momentjs](http://momentjs.com).
+
+    <script src="js/moment.min.js"></script>
+   
+Allows to input:
+
+* only date
+* only time 
+* both date and time  
+
+Please note, that format is taken from momentjs and **not compatible** with bootstrap-datepicker / jquery UI datepicker.  
+Internally value stored as `momentjs` object. 
+
+@class combodate
+@extends abstractinput
+@final
+@since 1.4.0
+@example
+<a href="#" id="dob" data-type="combodate" data-pk="1" data-url="/post" data-value="1984-05-15" data-title="Select date"></a>
+<script>
+$(function(){
+    $('#dob').editable({
+        format: 'YYYY-MM-DD',    
+        viewformat: 'DD.MM.YYYY',    
+        template: 'D / MMMM / YYYY',    
+        combodate: {
+                minYear: 2000,
+                maxYear: 2015,
+                minuteStep: 1
+           }
+        }
+    });
+});
+</script>
+**/
+
+/*global moment*/
+
+(function ($) {
+    "use strict";
+    
+    var Constructor = function (options) {
+        this.init('combodate', options, Constructor.defaults);
+        
+        //by default viewformat equals to format
+        if(!this.options.viewformat) {
+            this.options.viewformat = this.options.format;
+        }        
+        
+        //try parse combodate config defined as json string in data-combodate
+        options.combodate = $.fn.editableutils.tryParseJson(options.combodate, true);
+
+        //overriding combodate config (as by default jQuery extend() is not recursive)
+        this.options.combodate = $.extend({}, Constructor.defaults.combodate, options.combodate, {
+            format: this.options.format,
+            template: this.options.template
+        });
+    };
+
+    $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);    
+    
+    $.extend(Constructor.prototype, {
+        render: function () {
+            this.$input.combodate(this.options.combodate);
+                    
+            if($.fn.editableform.engine === 'bs3') {
+                this.$input.siblings().find('select').addClass('form-control');
+            }
+            
+            if(this.options.inputclass) {
+                this.$input.siblings().find('select').addClass(this.options.inputclass);
+            }            
+            //"clear" link
+            /*
+            if(this.options.clear) {
+                this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
+                    e.preventDefault();
+                    e.stopPropagation();
+                    this.clear();
+                }, this));
+                
+                this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));  
+            } 
+            */               
+        },
+        
+        value2html: function(value, element) {
+            var text = value ? value.format(this.options.viewformat) : '';
+            //$(element).text(text);
+            Constructor.superclass.value2html.call(this, text, element);  
+        },
+
+        html2value: function(html) {
+            return html ? moment(html, this.options.viewformat) : null;
+        },   
+        
+        value2str: function(value) {
+            return value ? value.format(this.options.format) : '';
+       }, 
+       
+       str2value: function(str) {
+           return str ? moment(str, this.options.format) : null;
+       }, 
+       
+       value2submit: function(value) {
+           return this.value2str(value);
+       },                    
+
+       value2input: function(value) {
+           this.$input.combodate('setValue', value);
+       },
+        
+       input2value: function() { 
+           return this.$input.combodate('getValue', null);
+       },       
+       
+       activate: function() {
+           this.$input.siblings('.combodate').find('select').eq(0).focus();
+       },
+       
+       /*
+       clear:  function() {
+          this.$input.data('datepicker').date = null;
+          this.$input.find('.active').removeClass('active');
+       },
+       */
+       
+       autosubmit: function() {
+           
+       }
+
+    });
+    
+    Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
+        /**
+        @property tpl 
+        @default <input type="text">
+        **/         
+        tpl:'<input type="text">',
+        /**
+        @property inputclass 
+        @default null
+        **/         
+        inputclass: null,
+        /**
+        Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
+        See list of tokens in [momentjs docs](http://momentjs.com/docs/#/parsing/string-format)  
+        
+        @property format 
+        @type string
+        @default YYYY-MM-DD
+        **/         
+        format:'YYYY-MM-DD',
+        /**
+        Format used for displaying date. Also applied when converting date from element's text on init.   
+        If not specified equals to `format`.
+        
+        @property viewformat 
+        @type string
+        @default null
+        **/          
+        viewformat: null,        
+        /**
+        Template used for displaying dropdowns.
+        
+        @property template 
+        @type string
+        @default D / MMM / YYYY
+        **/          
+        template: 'D / MMM / YYYY',  
+        /**
+        Configuration of combodate.
+        Full list of options: http://vitalets.github.com/combodate/#docs
+        
+        @property combodate 
+        @type object
+        @default null
+        **/
+        combodate: null
+        
+        /*
+        (not implemented yet)
+        Text shown as clear date button. 
+        If <code>false</code> clear button will not be rendered.
+        
+        @property clear 
+        @type boolean|string
+        @default 'x clear'         
+        */
+        //clear: '&times; clear'
+    });   
+
+    $.fn.editabletypes.combodate = Constructor;
+
+}(window.jQuery));
+
+/*
+Editableform based on jQuery UI
+*/
+(function ($) {
+    "use strict";
+    
+    $.extend($.fn.editableform.Constructor.prototype, {
+        initButtons: function() {
+            var $btn = this.$form.find('.editable-buttons');
+            $btn.append($.fn.editableform.buttons);
+            if(this.options.showbuttons === 'bottom') {
+                $btn.addClass('editable-buttons-bottom');
+            }
+                          
+            this.$form.find('.editable-submit').button({
+                icons: { primary: "ui-icon-check" },
+                text: false
+            }).removeAttr('title');
+            this.$form.find('.editable-cancel').button({
+                icons: { primary: "ui-icon-closethick" },
+                text: false
+            }).removeAttr('title');
+        }
+    });
+    
+    //error classes
+    $.fn.editableform.errorGroupClass = null;
+    $.fn.editableform.errorBlockClass = 'ui-state-error';
+    //engine
+    $.fn.editableform.engine = 'jquery-ui';
+    
+}(window.jQuery));
+/**
+* Editable jQuery UI Tooltip 
+* ---------------------
+* requires jquery ui 1.9.x 
+*/
+(function ($) {
+    "use strict";
+
+    //extend methods
+    $.extend($.fn.editableContainer.Popup.prototype, {
+        containerName: 'tooltip',  //jQuery method, aplying the widget
+        //object name in element's .data() 
+        containerDataName: 'ui-tooltip', 
+        innerCss: '.ui-tooltip-content', 
+        defaults: $.ui.tooltip.prototype.options,
+        
+        //split options on containerOptions and formOptions
+        splitOptions: function() {
+            this.containerOptions = {};
+            this.formOptions = {};
+            
+            //check that jQueryUI build contains tooltip widget
+            if(!$.ui[this.containerName]) {
+                $.error('Please use jQueryUI with "tooltip" widget! http://jqueryui.com/download');
+                return;
+            }
+            
+            //defaults for tooltip
+            for(var k in this.options) {
+              if(k in this.defaults) {
+                 this.containerOptions[k] = this.options[k];
+              } else {
+                 this.formOptions[k] = this.options[k];
+              } 
+            }
+        },        
+        
+        initContainer: function(){
+            this.handlePlacement();
+            $.extend(this.containerOptions, {
+                items: '*',
+                content: ' ',
+                track:  false,
+                open: $.proxy(function() {
+                        //disable events hiding tooltip by default
+                        this.container()._on(this.container().element, {
+                            mouseleave: function(e){ e.stopImmediatePropagation(); },
+                            focusout: function(e){ e.stopImmediatePropagation(); }
+                        });  
+                    }, this)
+            });
+            
+            this.call(this.containerOptions);
+            
+            //disable standart triggering tooltip events
+            this.container()._off(this.container().element, 'mouseover focusin');
+        },         
+        
+        tip: function() {
+            return this.container() ? this.container()._find(this.container().element) : null;
+        },
+        
+        innerShow: function() {
+            this.call('open');
+            var label = this.options.title || this.$element.data( "ui-tooltip-title") || this.$element.data( "originalTitle"); 
+            this.tip().find(this.innerCss).empty().append($('<label>').text(label));
+        },  
+        
+        innerHide: function() {
+            this.call('close'); 
+        },
+        
+        innerDestroy: function() {
+            /* tooltip destroys itself on hide */
+        },         
+        
+        setPosition: function() {
+            this.tip().position( $.extend({
+                of: this.$element
+            }, this.containerOptions.position ) );     
+        },
+        
+        handlePlacement: function() {
+           var pos; 
+           switch(this.options.placement) {
+               case 'top':
+                      pos = {
+                          my: "center bottom-5", 
+                          at: "center top", 
+                          collision: 'flipfit'
+                      };
+               break;
+               case 'right':
+                      pos = {
+                          my: "left+5 center", 
+                          at: "right center", 
+                          collision: 'flipfit'
+                      };
+               break;
+               case 'bottom':
+                      pos = {
+                          my: "center top+5", 
+                          at: "center bottom", 
+                          collision: 'flipfit'
+                      };
+               break;
+               case 'left':
+                      pos = {
+                          my: "right-5 center", 
+                          at: "left center", 
+                          collision: 'flipfit'
+                      };
+               break;                                             
+           }
+           
+           this.containerOptions.position = pos;
+        }
+                
+    });
+    
+}(window.jQuery));
+
+/**
+jQuery UI Datepicker.  
+Description and examples: http://jqueryui.com/datepicker.   
+This input is also accessible as **date** type. Do not use it together with __bootstrap-datepicker__ as both apply <code>$().datepicker()</code> method.  
+For **i18n** you should include js file from here: https://github.com/jquery/jquery-ui/tree/master/ui/i18n.
+
+@class dateui
+@extends abstractinput
+@final
+@example
+<a href="#" id="dob" data-type="date" data-pk="1" data-url="/post" data-title="Select date">15/05/1984</a>
+<script>
+$(function(){
+    $('#dob').editable({
+        format: 'yyyy-mm-dd',    
+        viewformat: 'dd/mm/yyyy',    
+        datepicker: {
+                firstDay: 1
+           }
+        }
+    });
+});
+</script>
+**/
+(function ($) {
+    "use strict";
+    
+    var DateUI = function (options) {
+        this.init('dateui', options, DateUI.defaults);
+        this.initPicker(options, DateUI.defaults);
+    };
+
+    $.fn.editableutils.inherit(DateUI, $.fn.editabletypes.abstractinput);    
+    
+    $.extend(DateUI.prototype, {
+        initPicker: function(options, defaults) {
+            //by default viewformat equals to format
+            if(!this.options.viewformat) {
+                this.options.viewformat = this.options.format;
+            }
+            
+            //correct formats: replace yyyy with yy (for compatibility with bootstrap datepicker)
+            this.options.viewformat = this.options.viewformat.replace('yyyy', 'yy'); 
+            this.options.format = this.options.format.replace('yyyy', 'yy');             
+            
+            //overriding datepicker config (as by default jQuery extend() is not recursive)
+            //since 1.4 datepicker internally uses viewformat instead of format. Format is for submit only
+            this.options.datepicker = $.extend({}, defaults.datepicker, options.datepicker, {
+                dateFormat: this.options.viewformat
+            });                        
+        },
+        
+        render: function () {
+            this.$input.datepicker(this.options.datepicker);
+            
+            //"clear" link
+            if(this.options.clear) {
+                this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
+                    e.preventDefault();
+                    e.stopPropagation();
+                    this.clear();
+                }, this));
+                
+                this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));  
+            }              
+        },
+
+        value2html: function(value, element) {
+            var text = $.datepicker.formatDate(this.options.viewformat, value);
+            DateUI.superclass.value2html.call(this, text, element); 
+        },
+
+        html2value: function(html) {
+           if(typeof html !== 'string') {
+               return html;
+           }            
+            
+           //if string does not match format, UI datepicker throws exception
+           var d;
+           try {
+              d = $.datepicker.parseDate(this.options.viewformat, html);
+           } catch(e) {}
+           
+           return d;            
+        },   
+        
+        value2str: function(value) {
+           return $.datepicker.formatDate(this.options.format, value);
+       }, 
+       
+       str2value: function(str) {
+           if(typeof str !== 'string') {
+               return str;
+           }
+           
+           //if string does not match format, UI datepicker throws exception
+           var d;
+           try {
+              d = $.datepicker.parseDate(this.options.format, str);
+           } catch(e) {}
+           
+           return d;
+       }, 
+       
+       value2submit: function(value) { 
+           return this.value2str(value);
+       },                     
+
+       value2input: function(value) {
+           this.$input.datepicker('setDate', value);
+       },
+        
+       input2value: function() { 
+           return this.$input.datepicker('getDate');
+       },       
+       
+       activate: function() {
+       },
+       
+       clear:  function() {
+           this.$input.datepicker('setDate', null);
+           // submit automatically whe that are no buttons
+           if(this.isAutosubmit) {
+              this.submit();
+           }
+       },
+       
+       autosubmit: function() {
+           this.isAutosubmit = true; 
+           this.$input.on('mouseup', 'table.ui-datepicker-calendar a.ui-state-default', $.proxy(this.submit, this));
+       },
+
+       submit: function() {
+           var $form = this.$input.closest('form');
+           setTimeout(function() {
+               $form.submit();
+           }, 200);
+       }
+
+    });
+    
+    DateUI.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
+        /**
+        @property tpl 
+        @default <div></div>
+        **/         
+        tpl:'<div class="editable-date"></div>',
+        /**
+        @property inputclass 
+        @default null
+        **/         
+        inputclass: null,
+        /**
+        Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
+        Full list of tokens: http://docs.jquery.com/UI/Datepicker/formatDate
+        
+        @property format 
+        @type string
+        @default yyyy-mm-dd
+        **/          
+        format:'yyyy-mm-dd', 
+        /**
+        Format used for displaying date. Also applied when converting date from element's text on init.    
+        If not specified equals to <code>format</code>
+        
+        @property viewformat 
+        @type string
+        @default null
+        **/          
+        viewformat: null,
+        
+        /**
+        Configuration of datepicker.
+        Full list of options: http://api.jqueryui.com/datepicker
+        
+        @property datepicker 
+        @type object
+        @default {
+           firstDay: 0, 
+           changeYear: true, 
+           changeMonth: true
+        }
+        **/
+        datepicker: {
+            firstDay: 0,
+            changeYear: true,
+            changeMonth: true,
+            showOtherMonths: true
+        },
+        /**
+        Text shown as clear date button. 
+        If <code>false</code> clear button will not be rendered.
+        
+        @property clear 
+        @type boolean|string
+        @default 'x clear'         
+        **/
+        clear: '&times; clear'        
+    });   
+
+    $.fn.editabletypes.dateui = DateUI;
+
+}(window.jQuery));
+
+/**
+jQuery UI datefield input - modification for inline mode.
+Shows normal <input type="text"> and binds popup datepicker.  
+Automatically shown in inline mode.
+
+@class dateuifield
+@extends dateui
+
+@since 1.4.0
+**/
+(function ($) {
+    "use strict";
+    
+    var DateUIField = function (options) {
+        this.init('dateuifield', options, DateUIField.defaults);
+        this.initPicker(options, DateUIField.defaults);
+    };
+
+    $.fn.editableutils.inherit(DateUIField, $.fn.editabletypes.dateui);    
+    
+    $.extend(DateUIField.prototype, {
+       render: function () {
+          //  this.$input = this.$tpl.find('input'); 
+            this.$input.datepicker(this.options.datepicker);
+            $.fn.editabletypes.text.prototype.renderClear.call(this);
+       },
+      
+       value2input: function(value) {
+           this.$input.val($.datepicker.formatDate(this.options.viewformat, value));
+       },
+        
+       input2value: function() { 
+           return this.html2value(this.$input.val());
+       },        
+        
+       activate: function() {
+           $.fn.editabletypes.text.prototype.activate.call(this);
+       },
+       
+       toggleClear: function() {
+           $.fn.editabletypes.text.prototype.toggleClear.call(this);
+       },
+       
+       autosubmit: function() {
+          //reset autosubmit to empty  
+       }
+    });
+    
+    DateUIField.defaults = $.extend({}, $.fn.editabletypes.dateui.defaults, {
+        /**
+        @property tpl 
+        @default <input type="text">
+        **/         
+        tpl: '<input type="text"/>',
+        /**
+        @property inputclass 
+        @default null
+        **/         
+        inputclass: null,
+        
+        /* datepicker config */
+        datepicker: {
+            showOn: "button",
+            buttonImage: "http://jqueryui.com/resources/demos/datepicker/images/calendar.gif",
+            buttonImageOnly: true,            
+            firstDay: 0,
+            changeYear: true,
+            changeMonth: true,
+            showOtherMonths: true
+        },
+        
+        /* disable clear link */ 
+        clear: false
+    });
+    
+    $.fn.editabletypes.dateuifield = DateUIField;
+
+}(window.jQuery));
\ No newline at end of file
diff --git a/src/legacy/design-studio/js/lodash.js b/src/legacy/design-studio/js/lodash.js
new file mode 100644
index 0000000000000000000000000000000000000000..b39ddce69b3e2360bf23a7f51a824d4b2f4ea299
--- /dev/null
+++ b/src/legacy/design-studio/js/lodash.js
@@ -0,0 +1,17084 @@
+/**
+ * @license
+ * Lodash <https://lodash.com/>
+ * Copyright JS Foundation and other contributors <https://js.foundation/>
+ * Released under MIT license <https://lodash.com/license>
+ * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
+ * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ */
+;(function() {
+
+  /** Used as a safe reference for `undefined` in pre-ES5 environments. */
+  var undefined;
+
+  /** Used as the semantic version number. */
+  var VERSION = '4.17.4';
+
+  /** Used as the size to enable large array optimizations. */
+  var LARGE_ARRAY_SIZE = 200;
+
+  /** Error message constants. */
+  var CORE_ERROR_TEXT = 'Unsupported core-js use. Try https://npms.io/search?q=ponyfill.',
+      FUNC_ERROR_TEXT = 'Expected a function';
+
+  /** Used to stand-in for `undefined` hash values. */
+  var HASH_UNDEFINED = '__lodash_hash_undefined__';
+
+  /** Used as the maximum memoize cache size. */
+  var MAX_MEMOIZE_SIZE = 500;
+
+  /** Used as the internal argument placeholder. */
+  var PLACEHOLDER = '__lodash_placeholder__';
+
+  /** Used to compose bitmasks for cloning. */
+  var CLONE_DEEP_FLAG = 1,
+      CLONE_FLAT_FLAG = 2,
+      CLONE_SYMBOLS_FLAG = 4;
+
+  /** Used to compose bitmasks for value comparisons. */
+  var COMPARE_PARTIAL_FLAG = 1,
+      COMPARE_UNORDERED_FLAG = 2;
+
+  /** Used to compose bitmasks for function metadata. */
+  var WRAP_BIND_FLAG = 1,
+      WRAP_BIND_KEY_FLAG = 2,
+      WRAP_CURRY_BOUND_FLAG = 4,
+      WRAP_CURRY_FLAG = 8,
+      WRAP_CURRY_RIGHT_FLAG = 16,
+      WRAP_PARTIAL_FLAG = 32,
+      WRAP_PARTIAL_RIGHT_FLAG = 64,
+      WRAP_ARY_FLAG = 128,
+      WRAP_REARG_FLAG = 256,
+      WRAP_FLIP_FLAG = 512;
+
+  /** Used as default options for `_.truncate`. */
+  var DEFAULT_TRUNC_LENGTH = 30,
+      DEFAULT_TRUNC_OMISSION = '...';
+
+  /** Used to detect hot functions by number of calls within a span of milliseconds. */
+  var HOT_COUNT = 800,
+      HOT_SPAN = 16;
+
+  /** Used to indicate the type of lazy iteratees. */
+  var LAZY_FILTER_FLAG = 1,
+      LAZY_MAP_FLAG = 2,
+      LAZY_WHILE_FLAG = 3;
+
+  /** Used as references for various `Number` constants. */
+  var INFINITY = 1 / 0,
+      MAX_SAFE_INTEGER = 9007199254740991,
+      MAX_INTEGER = 1.7976931348623157e+308,
+      NAN = 0 / 0;
+
+  /** Used as references for the maximum length and index of an array. */
+  var MAX_ARRAY_LENGTH = 4294967295,
+      MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1,
+      HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1;
+
+  /** Used to associate wrap methods with their bit flags. */
+  var wrapFlags = [
+    ['ary', WRAP_ARY_FLAG],
+    ['bind', WRAP_BIND_FLAG],
+    ['bindKey', WRAP_BIND_KEY_FLAG],
+    ['curry', WRAP_CURRY_FLAG],
+    ['curryRight', WRAP_CURRY_RIGHT_FLAG],
+    ['flip', WRAP_FLIP_FLAG],
+    ['partial', WRAP_PARTIAL_FLAG],
+    ['partialRight', WRAP_PARTIAL_RIGHT_FLAG],
+    ['rearg', WRAP_REARG_FLAG]
+  ];
+
+  /** `Object#toString` result references. */
+  var argsTag = '[object Arguments]',
+      arrayTag = '[object Array]',
+      asyncTag = '[object AsyncFunction]',
+      boolTag = '[object Boolean]',
+      dateTag = '[object Date]',
+      domExcTag = '[object DOMException]',
+      errorTag = '[object Error]',
+      funcTag = '[object Function]',
+      genTag = '[object GeneratorFunction]',
+      mapTag = '[object Map]',
+      numberTag = '[object Number]',
+      nullTag = '[object Null]',
+      objectTag = '[object Object]',
+      promiseTag = '[object Promise]',
+      proxyTag = '[object Proxy]',
+      regexpTag = '[object RegExp]',
+      setTag = '[object Set]',
+      stringTag = '[object String]',
+      symbolTag = '[object Symbol]',
+      undefinedTag = '[object Undefined]',
+      weakMapTag = '[object WeakMap]',
+      weakSetTag = '[object WeakSet]';
+
+  var arrayBufferTag = '[object ArrayBuffer]',
+      dataViewTag = '[object DataView]',
+      float32Tag = '[object Float32Array]',
+      float64Tag = '[object Float64Array]',
+      int8Tag = '[object Int8Array]',
+      int16Tag = '[object Int16Array]',
+      int32Tag = '[object Int32Array]',
+      uint8Tag = '[object Uint8Array]',
+      uint8ClampedTag = '[object Uint8ClampedArray]',
+      uint16Tag = '[object Uint16Array]',
+      uint32Tag = '[object Uint32Array]';
+
+  /** Used to match empty string literals in compiled template source. */
+  var reEmptyStringLeading = /\b__p \+= '';/g,
+      reEmptyStringMiddle = /\b(__p \+=) '' \+/g,
+      reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g;
+
+  /** Used to match HTML entities and HTML characters. */
+  var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g,
+      reUnescapedHtml = /[&<>"']/g,
+      reHasEscapedHtml = RegExp(reEscapedHtml.source),
+      reHasUnescapedHtml = RegExp(reUnescapedHtml.source);
+
+  /** Used to match template delimiters. */
+  var reEscape = /<%-([\s\S]+?)%>/g,
+      reEvaluate = /<%([\s\S]+?)%>/g,
+      reInterpolate = /<%=([\s\S]+?)%>/g;
+
+  /** Used to match property names within property paths. */
+  var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
+      reIsPlainProp = /^\w*$/,
+      reLeadingDot = /^\./,
+      rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g;
+
+  /**
+   * Used to match `RegExp`
+   * [syntax characters](http://ecma-international.org/ecma-262/7.0/#sec-patterns).
+   */
+  var reRegExpChar = /[\\^$.*+?()[\]{}|]/g,
+      reHasRegExpChar = RegExp(reRegExpChar.source);
+
+  /** Used to match leading and trailing whitespace. */
+  var reTrim = /^\s+|\s+$/g,
+      reTrimStart = /^\s+/,
+      reTrimEnd = /\s+$/;
+
+  /** Used to match wrap detail comments. */
+  var reWrapComment = /\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,
+      reWrapDetails = /\{\n\/\* \[wrapped with (.+)\] \*/,
+      reSplitDetails = /,? & /;
+
+  /** Used to match words composed of alphanumeric characters. */
+  var reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g;
+
+  /** Used to match backslashes in property paths. */
+  var reEscapeChar = /\\(\\)?/g;
+
+  /**
+   * Used to match
+   * [ES template delimiters](http://ecma-international.org/ecma-262/7.0/#sec-template-literal-lexical-components).
+   */
+  var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
+
+  /** Used to match `RegExp` flags from their coerced string values. */
+  var reFlags = /\w*$/;
+
+  /** Used to detect bad signed hexadecimal string values. */
+  var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
+
+  /** Used to detect binary string values. */
+  var reIsBinary = /^0b[01]+$/i;
+
+  /** Used to detect host constructors (Safari). */
+  var reIsHostCtor = /^\[object .+?Constructor\]$/;
+
+  /** Used to detect octal string values. */
+  var reIsOctal = /^0o[0-7]+$/i;
+
+  /** Used to detect unsigned integer values. */
+  var reIsUint = /^(?:0|[1-9]\d*)$/;
+
+  /** Used to match Latin Unicode letters (excluding mathematical operators). */
+  var reLatin = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g;
+
+  /** Used to ensure capturing order of template delimiters. */
+  var reNoMatch = /($^)/;
+
+  /** Used to match unescaped characters in compiled string literals. */
+  var reUnescapedString = /['\n\r\u2028\u2029\\]/g;
+
+  /** Used to compose unicode character classes. */
+  var rsAstralRange = '\\ud800-\\udfff',
+      rsComboMarksRange = '\\u0300-\\u036f',
+      reComboHalfMarksRange = '\\ufe20-\\ufe2f',
+      rsComboSymbolsRange = '\\u20d0-\\u20ff',
+      rsComboRange = rsComboMarksRange + reComboHalfMarksRange + rsComboSymbolsRange,
+      rsDingbatRange = '\\u2700-\\u27bf',
+      rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff',
+      rsMathOpRange = '\\xac\\xb1\\xd7\\xf7',
+      rsNonCharRange = '\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf',
+      rsPunctuationRange = '\\u2000-\\u206f',
+      rsSpaceRange = ' \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000',
+      rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde',
+      rsVarRange = '\\ufe0e\\ufe0f',
+      rsBreakRange = rsMathOpRange + rsNonCharRange + rsPunctuationRange + rsSpaceRange;
+
+  /** Used to compose unicode capture groups. */
+  var rsApos = "['\u2019]",
+      rsAstral = '[' + rsAstralRange + ']',
+      rsBreak = '[' + rsBreakRange + ']',
+      rsCombo = '[' + rsComboRange + ']',
+      rsDigits = '\\d+',
+      rsDingbat = '[' + rsDingbatRange + ']',
+      rsLower = '[' + rsLowerRange + ']',
+      rsMisc = '[^' + rsAstralRange + rsBreakRange + rsDigits + rsDingbatRange + rsLowerRange + rsUpperRange + ']',
+      rsFitz = '\\ud83c[\\udffb-\\udfff]',
+      rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')',
+      rsNonAstral = '[^' + rsAstralRange + ']',
+      rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}',
+      rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]',
+      rsUpper = '[' + rsUpperRange + ']',
+      rsZWJ = '\\u200d';
+
+  /** Used to compose unicode regexes. */
+  var rsMiscLower = '(?:' + rsLower + '|' + rsMisc + ')',
+      rsMiscUpper = '(?:' + rsUpper + '|' + rsMisc + ')',
+      rsOptContrLower = '(?:' + rsApos + '(?:d|ll|m|re|s|t|ve))?',
+      rsOptContrUpper = '(?:' + rsApos + '(?:D|LL|M|RE|S|T|VE))?',
+      reOptMod = rsModifier + '?',
+      rsOptVar = '[' + rsVarRange + ']?',
+      rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*',
+      rsOrdLower = '\\d*(?:(?:1st|2nd|3rd|(?![123])\\dth)\\b)',
+      rsOrdUpper = '\\d*(?:(?:1ST|2ND|3RD|(?![123])\\dTH)\\b)',
+      rsSeq = rsOptVar + reOptMod + rsOptJoin,
+      rsEmoji = '(?:' + [rsDingbat, rsRegional, rsSurrPair].join('|') + ')' + rsSeq,
+      rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')';
+
+  /** Used to match apostrophes. */
+  var reApos = RegExp(rsApos, 'g');
+
+  /**
+   * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and
+   * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols).
+   */
+  var reComboMark = RegExp(rsCombo, 'g');
+
+  /** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */
+  var reUnicode = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g');
+
+  /** Used to match complex or compound words. */
+  var reUnicodeWord = RegExp([
+    rsUpper + '?' + rsLower + '+' + rsOptContrLower + '(?=' + [rsBreak, rsUpper, '$'].join('|') + ')',
+    rsMiscUpper + '+' + rsOptContrUpper + '(?=' + [rsBreak, rsUpper + rsMiscLower, '$'].join('|') + ')',
+    rsUpper + '?' + rsMiscLower + '+' + rsOptContrLower,
+    rsUpper + '+' + rsOptContrUpper,
+    rsOrdUpper,
+    rsOrdLower,
+    rsDigits,
+    rsEmoji
+  ].join('|'), 'g');
+
+  /** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */
+  var reHasUnicode = RegExp('[' + rsZWJ + rsAstralRange  + rsComboRange + rsVarRange + ']');
+
+  /** Used to detect strings that need a more robust regexp to match words. */
+  var reHasUnicodeWord = /[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;
+
+  /** Used to assign default `context` object properties. */
+  var contextProps = [
+    'Array', 'Buffer', 'DataView', 'Date', 'Error', 'Float32Array', 'Float64Array',
+    'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Map', 'Math', 'Object',
+    'Promise', 'RegExp', 'Set', 'String', 'Symbol', 'TypeError', 'Uint8Array',
+    'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap',
+    '_', 'clearTimeout', 'isFinite', 'parseInt', 'setTimeout'
+  ];
+
+  /** Used to make template sourceURLs easier to identify. */
+  var templateCounter = -1;
+
+  /** Used to identify `toStringTag` values of typed arrays. */
+  var typedArrayTags = {};
+  typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
+  typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
+  typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
+  typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
+  typedArrayTags[uint32Tag] = true;
+  typedArrayTags[argsTag] = typedArrayTags[arrayTag] =
+  typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
+  typedArrayTags[dataViewTag] = typedArrayTags[dateTag] =
+  typedArrayTags[errorTag] = typedArrayTags[funcTag] =
+  typedArrayTags[mapTag] = typedArrayTags[numberTag] =
+  typedArrayTags[objectTag] = typedArrayTags[regexpTag] =
+  typedArrayTags[setTag] = typedArrayTags[stringTag] =
+  typedArrayTags[weakMapTag] = false;
+
+  /** Used to identify `toStringTag` values supported by `_.clone`. */
+  var cloneableTags = {};
+  cloneableTags[argsTag] = cloneableTags[arrayTag] =
+  cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] =
+  cloneableTags[boolTag] = cloneableTags[dateTag] =
+  cloneableTags[float32Tag] = cloneableTags[float64Tag] =
+  cloneableTags[int8Tag] = cloneableTags[int16Tag] =
+  cloneableTags[int32Tag] = cloneableTags[mapTag] =
+  cloneableTags[numberTag] = cloneableTags[objectTag] =
+  cloneableTags[regexpTag] = cloneableTags[setTag] =
+  cloneableTags[stringTag] = cloneableTags[symbolTag] =
+  cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] =
+  cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;
+  cloneableTags[errorTag] = cloneableTags[funcTag] =
+  cloneableTags[weakMapTag] = false;
+
+  /** Used to map Latin Unicode letters to basic Latin letters. */
+  var deburredLetters = {
+    // Latin-1 Supplement block.
+    '\xc0': 'A',  '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A',
+    '\xe0': 'a',  '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a',
+    '\xc7': 'C',  '\xe7': 'c',
+    '\xd0': 'D',  '\xf0': 'd',
+    '\xc8': 'E',  '\xc9': 'E', '\xca': 'E', '\xcb': 'E',
+    '\xe8': 'e',  '\xe9': 'e', '\xea': 'e', '\xeb': 'e',
+    '\xcc': 'I',  '\xcd': 'I', '\xce': 'I', '\xcf': 'I',
+    '\xec': 'i',  '\xed': 'i', '\xee': 'i', '\xef': 'i',
+    '\xd1': 'N',  '\xf1': 'n',
+    '\xd2': 'O',  '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O',
+    '\xf2': 'o',  '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o',
+    '\xd9': 'U',  '\xda': 'U', '\xdb': 'U', '\xdc': 'U',
+    '\xf9': 'u',  '\xfa': 'u', '\xfb': 'u', '\xfc': 'u',
+    '\xdd': 'Y',  '\xfd': 'y', '\xff': 'y',
+    '\xc6': 'Ae', '\xe6': 'ae',
+    '\xde': 'Th', '\xfe': 'th',
+    '\xdf': 'ss',
+    // Latin Extended-A block.
+    '\u0100': 'A',  '\u0102': 'A', '\u0104': 'A',
+    '\u0101': 'a',  '\u0103': 'a', '\u0105': 'a',
+    '\u0106': 'C',  '\u0108': 'C', '\u010a': 'C', '\u010c': 'C',
+    '\u0107': 'c',  '\u0109': 'c', '\u010b': 'c', '\u010d': 'c',
+    '\u010e': 'D',  '\u0110': 'D', '\u010f': 'd', '\u0111': 'd',
+    '\u0112': 'E',  '\u0114': 'E', '\u0116': 'E', '\u0118': 'E', '\u011a': 'E',
+    '\u0113': 'e',  '\u0115': 'e', '\u0117': 'e', '\u0119': 'e', '\u011b': 'e',
+    '\u011c': 'G',  '\u011e': 'G', '\u0120': 'G', '\u0122': 'G',
+    '\u011d': 'g',  '\u011f': 'g', '\u0121': 'g', '\u0123': 'g',
+    '\u0124': 'H',  '\u0126': 'H', '\u0125': 'h', '\u0127': 'h',
+    '\u0128': 'I',  '\u012a': 'I', '\u012c': 'I', '\u012e': 'I', '\u0130': 'I',
+    '\u0129': 'i',  '\u012b': 'i', '\u012d': 'i', '\u012f': 'i', '\u0131': 'i',
+    '\u0134': 'J',  '\u0135': 'j',
+    '\u0136': 'K',  '\u0137': 'k', '\u0138': 'k',
+    '\u0139': 'L',  '\u013b': 'L', '\u013d': 'L', '\u013f': 'L', '\u0141': 'L',
+    '\u013a': 'l',  '\u013c': 'l', '\u013e': 'l', '\u0140': 'l', '\u0142': 'l',
+    '\u0143': 'N',  '\u0145': 'N', '\u0147': 'N', '\u014a': 'N',
+    '\u0144': 'n',  '\u0146': 'n', '\u0148': 'n', '\u014b': 'n',
+    '\u014c': 'O',  '\u014e': 'O', '\u0150': 'O',
+    '\u014d': 'o',  '\u014f': 'o', '\u0151': 'o',
+    '\u0154': 'R',  '\u0156': 'R', '\u0158': 'R',
+    '\u0155': 'r',  '\u0157': 'r', '\u0159': 'r',
+    '\u015a': 'S',  '\u015c': 'S', '\u015e': 'S', '\u0160': 'S',
+    '\u015b': 's',  '\u015d': 's', '\u015f': 's', '\u0161': 's',
+    '\u0162': 'T',  '\u0164': 'T', '\u0166': 'T',
+    '\u0163': 't',  '\u0165': 't', '\u0167': 't',
+    '\u0168': 'U',  '\u016a': 'U', '\u016c': 'U', '\u016e': 'U', '\u0170': 'U', '\u0172': 'U',
+    '\u0169': 'u',  '\u016b': 'u', '\u016d': 'u', '\u016f': 'u', '\u0171': 'u', '\u0173': 'u',
+    '\u0174': 'W',  '\u0175': 'w',
+    '\u0176': 'Y',  '\u0177': 'y', '\u0178': 'Y',
+    '\u0179': 'Z',  '\u017b': 'Z', '\u017d': 'Z',
+    '\u017a': 'z',  '\u017c': 'z', '\u017e': 'z',
+    '\u0132': 'IJ', '\u0133': 'ij',
+    '\u0152': 'Oe', '\u0153': 'oe',
+    '\u0149': "'n", '\u017f': 's'
+  };
+
+  /** Used to map characters to HTML entities. */
+  var htmlEscapes = {
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;',
+    '"': '&quot;',
+    "'": '&#39;'
+  };
+
+  /** Used to map HTML entities to characters. */
+  var htmlUnescapes = {
+    '&amp;': '&',
+    '&lt;': '<',
+    '&gt;': '>',
+    '&quot;': '"',
+    '&#39;': "'"
+  };
+
+  /** Used to escape characters for inclusion in compiled string literals. */
+  var stringEscapes = {
+    '\\': '\\',
+    "'": "'",
+    '\n': 'n',
+    '\r': 'r',
+    '\u2028': 'u2028',
+    '\u2029': 'u2029'
+  };
+
+  /** Built-in method references without a dependency on `root`. */
+  var freeParseFloat = parseFloat,
+      freeParseInt = parseInt;
+
+  /** Detect free variable `global` from Node.js. */
+  var freeGlobal = typeof global == 'object' && global && global.Object === Object && global;
+
+  /** Detect free variable `self`. */
+  var freeSelf = typeof self == 'object' && self && self.Object === Object && self;
+
+  /** Used as a reference to the global object. */
+  var root = freeGlobal || freeSelf || Function('return this')();
+
+  /** Detect free variable `exports`. */
+  var freeExports = typeof exports == 'object' && exports && !exports.nodeType && exports;
+
+  /** Detect free variable `module`. */
+  var freeModule = freeExports && typeof module == 'object' && module && !module.nodeType && module;
+
+  /** Detect the popular CommonJS extension `module.exports`. */
+  var moduleExports = freeModule && freeModule.exports === freeExports;
+
+  /** Detect free variable `process` from Node.js. */
+  var freeProcess = moduleExports && freeGlobal.process;
+
+  /** Used to access faster Node.js helpers. */
+  var nodeUtil = (function() {
+    try {
+      return freeProcess && freeProcess.binding && freeProcess.binding('util');
+    } catch (e) {}
+  }());
+
+  /* Node.js helper references. */
+  var nodeIsArrayBuffer = nodeUtil && nodeUtil.isArrayBuffer,
+      nodeIsDate = nodeUtil && nodeUtil.isDate,
+      nodeIsMap = nodeUtil && nodeUtil.isMap,
+      nodeIsRegExp = nodeUtil && nodeUtil.isRegExp,
+      nodeIsSet = nodeUtil && nodeUtil.isSet,
+      nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray;
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Adds the key-value `pair` to `map`.
+   *
+   * @private
+   * @param {Object} map The map to modify.
+   * @param {Array} pair The key-value pair to add.
+   * @returns {Object} Returns `map`.
+   */
+  function addMapEntry(map, pair) {
+    // Don't return `map.set` because it's not chainable in IE 11.
+    map.set(pair[0], pair[1]);
+    return map;
+  }
+
+  /**
+   * Adds `value` to `set`.
+   *
+   * @private
+   * @param {Object} set The set to modify.
+   * @param {*} value The value to add.
+   * @returns {Object} Returns `set`.
+   */
+  function addSetEntry(set, value) {
+    // Don't return `set.add` because it's not chainable in IE 11.
+    set.add(value);
+    return set;
+  }
+
+  /**
+   * A faster alternative to `Function#apply`, this function invokes `func`
+   * with the `this` binding of `thisArg` and the arguments of `args`.
+   *
+   * @private
+   * @param {Function} func The function to invoke.
+   * @param {*} thisArg The `this` binding of `func`.
+   * @param {Array} args The arguments to invoke `func` with.
+   * @returns {*} Returns the result of `func`.
+   */
+  function apply(func, thisArg, args) {
+    switch (args.length) {
+      case 0: return func.call(thisArg);
+      case 1: return func.call(thisArg, args[0]);
+      case 2: return func.call(thisArg, args[0], args[1]);
+      case 3: return func.call(thisArg, args[0], args[1], args[2]);
+    }
+    return func.apply(thisArg, args);
+  }
+
+  /**
+   * A specialized version of `baseAggregator` for arrays.
+   *
+   * @private
+   * @param {Array} [array] The array to iterate over.
+   * @param {Function} setter The function to set `accumulator` values.
+   * @param {Function} iteratee The iteratee to transform keys.
+   * @param {Object} accumulator The initial aggregated object.
+   * @returns {Function} Returns `accumulator`.
+   */
+  function arrayAggregator(array, setter, iteratee, accumulator) {
+    var index = -1,
+        length = array == null ? 0 : array.length;
+
+    while (++index < length) {
+      var value = array[index];
+      setter(accumulator, value, iteratee(value), array);
+    }
+    return accumulator;
+  }
+
+  /**
+   * A specialized version of `_.forEach` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} [array] The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {Array} Returns `array`.
+   */
+  function arrayEach(array, iteratee) {
+    var index = -1,
+        length = array == null ? 0 : array.length;
+
+    while (++index < length) {
+      if (iteratee(array[index], index, array) === false) {
+        break;
+      }
+    }
+    return array;
+  }
+
+  /**
+   * A specialized version of `_.forEachRight` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} [array] The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {Array} Returns `array`.
+   */
+  function arrayEachRight(array, iteratee) {
+    var length = array == null ? 0 : array.length;
+
+    while (length--) {
+      if (iteratee(array[length], length, array) === false) {
+        break;
+      }
+    }
+    return array;
+  }
+
+  /**
+   * A specialized version of `_.every` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} [array] The array to iterate over.
+   * @param {Function} predicate The function invoked per iteration.
+   * @returns {boolean} Returns `true` if all elements pass the predicate check,
+   *  else `false`.
+   */
+  function arrayEvery(array, predicate) {
+    var index = -1,
+        length = array == null ? 0 : array.length;
+
+    while (++index < length) {
+      if (!predicate(array[index], index, array)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * A specialized version of `_.filter` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} [array] The array to iterate over.
+   * @param {Function} predicate The function invoked per iteration.
+   * @returns {Array} Returns the new filtered array.
+   */
+  function arrayFilter(array, predicate) {
+    var index = -1,
+        length = array == null ? 0 : array.length,
+        resIndex = 0,
+        result = [];
+
+    while (++index < length) {
+      var value = array[index];
+      if (predicate(value, index, array)) {
+        result[resIndex++] = value;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * A specialized version of `_.includes` for arrays without support for
+   * specifying an index to search from.
+   *
+   * @private
+   * @param {Array} [array] The array to inspect.
+   * @param {*} target The value to search for.
+   * @returns {boolean} Returns `true` if `target` is found, else `false`.
+   */
+  function arrayIncludes(array, value) {
+    var length = array == null ? 0 : array.length;
+    return !!length && baseIndexOf(array, value, 0) > -1;
+  }
+
+  /**
+   * This function is like `arrayIncludes` except that it accepts a comparator.
+   *
+   * @private
+   * @param {Array} [array] The array to inspect.
+   * @param {*} target The value to search for.
+   * @param {Function} comparator The comparator invoked per element.
+   * @returns {boolean} Returns `true` if `target` is found, else `false`.
+   */
+  function arrayIncludesWith(array, value, comparator) {
+    var index = -1,
+        length = array == null ? 0 : array.length;
+
+    while (++index < length) {
+      if (comparator(value, array[index])) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * A specialized version of `_.map` for arrays without support for iteratee
+   * shorthands.
+   *
+   * @private
+   * @param {Array} [array] The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {Array} Returns the new mapped array.
+   */
+  function arrayMap(array, iteratee) {
+    var index = -1,
+        length = array == null ? 0 : array.length,
+        result = Array(length);
+
+    while (++index < length) {
+      result[index] = iteratee(array[index], index, array);
+    }
+    return result;
+  }
+
+  /**
+   * Appends the elements of `values` to `array`.
+   *
+   * @private
+   * @param {Array} array The array to modify.
+   * @param {Array} values The values to append.
+   * @returns {Array} Returns `array`.
+   */
+  function arrayPush(array, values) {
+    var index = -1,
+        length = values.length,
+        offset = array.length;
+
+    while (++index < length) {
+      array[offset + index] = values[index];
+    }
+    return array;
+  }
+
+  /**
+   * A specialized version of `_.reduce` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} [array] The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @param {*} [accumulator] The initial value.
+   * @param {boolean} [initAccum] Specify using the first element of `array` as
+   *  the initial value.
+   * @returns {*} Returns the accumulated value.
+   */
+  function arrayReduce(array, iteratee, accumulator, initAccum) {
+    var index = -1,
+        length = array == null ? 0 : array.length;
+
+    if (initAccum && length) {
+      accumulator = array[++index];
+    }
+    while (++index < length) {
+      accumulator = iteratee(accumulator, array[index], index, array);
+    }
+    return accumulator;
+  }
+
+  /**
+   * A specialized version of `_.reduceRight` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} [array] The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @param {*} [accumulator] The initial value.
+   * @param {boolean} [initAccum] Specify using the last element of `array` as
+   *  the initial value.
+   * @returns {*} Returns the accumulated value.
+   */
+  function arrayReduceRight(array, iteratee, accumulator, initAccum) {
+    var length = array == null ? 0 : array.length;
+    if (initAccum && length) {
+      accumulator = array[--length];
+    }
+    while (length--) {
+      accumulator = iteratee(accumulator, array[length], length, array);
+    }
+    return accumulator;
+  }
+
+  /**
+   * A specialized version of `_.some` for arrays without support for iteratee
+   * shorthands.
+   *
+   * @private
+   * @param {Array} [array] The array to iterate over.
+   * @param {Function} predicate The function invoked per iteration.
+   * @returns {boolean} Returns `true` if any element passes the predicate check,
+   *  else `false`.
+   */
+  function arraySome(array, predicate) {
+    var index = -1,
+        length = array == null ? 0 : array.length;
+
+    while (++index < length) {
+      if (predicate(array[index], index, array)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * Gets the size of an ASCII `string`.
+   *
+   * @private
+   * @param {string} string The string inspect.
+   * @returns {number} Returns the string size.
+   */
+  var asciiSize = baseProperty('length');
+
+  /**
+   * Converts an ASCII `string` to an array.
+   *
+   * @private
+   * @param {string} string The string to convert.
+   * @returns {Array} Returns the converted array.
+   */
+  function asciiToArray(string) {
+    return string.split('');
+  }
+
+  /**
+   * Splits an ASCII `string` into an array of its words.
+   *
+   * @private
+   * @param {string} The string to inspect.
+   * @returns {Array} Returns the words of `string`.
+   */
+  function asciiWords(string) {
+    return string.match(reAsciiWord) || [];
+  }
+
+  /**
+   * The base implementation of methods like `_.findKey` and `_.findLastKey`,
+   * without support for iteratee shorthands, which iterates over `collection`
+   * using `eachFunc`.
+   *
+   * @private
+   * @param {Array|Object} collection The collection to inspect.
+   * @param {Function} predicate The function invoked per iteration.
+   * @param {Function} eachFunc The function to iterate over `collection`.
+   * @returns {*} Returns the found element or its key, else `undefined`.
+   */
+  function baseFindKey(collection, predicate, eachFunc) {
+    var result;
+    eachFunc(collection, function(value, key, collection) {
+      if (predicate(value, key, collection)) {
+        result = key;
+        return false;
+      }
+    });
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.findIndex` and `_.findLastIndex` without
+   * support for iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to inspect.
+   * @param {Function} predicate The function invoked per iteration.
+   * @param {number} fromIndex The index to search from.
+   * @param {boolean} [fromRight] Specify iterating from right to left.
+   * @returns {number} Returns the index of the matched value, else `-1`.
+   */
+  function baseFindIndex(array, predicate, fromIndex, fromRight) {
+    var length = array.length,
+        index = fromIndex + (fromRight ? 1 : -1);
+
+    while ((fromRight ? index-- : ++index < length)) {
+      if (predicate(array[index], index, array)) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * The base implementation of `_.indexOf` without `fromIndex` bounds checks.
+   *
+   * @private
+   * @param {Array} array The array to inspect.
+   * @param {*} value The value to search for.
+   * @param {number} fromIndex The index to search from.
+   * @returns {number} Returns the index of the matched value, else `-1`.
+   */
+  function baseIndexOf(array, value, fromIndex) {
+    return value === value
+      ? strictIndexOf(array, value, fromIndex)
+      : baseFindIndex(array, baseIsNaN, fromIndex);
+  }
+
+  /**
+   * This function is like `baseIndexOf` except that it accepts a comparator.
+   *
+   * @private
+   * @param {Array} array The array to inspect.
+   * @param {*} value The value to search for.
+   * @param {number} fromIndex The index to search from.
+   * @param {Function} comparator The comparator invoked per element.
+   * @returns {number} Returns the index of the matched value, else `-1`.
+   */
+  function baseIndexOfWith(array, value, fromIndex, comparator) {
+    var index = fromIndex - 1,
+        length = array.length;
+
+    while (++index < length) {
+      if (comparator(array[index], value)) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * The base implementation of `_.isNaN` without support for number objects.
+   *
+   * @private
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
+   */
+  function baseIsNaN(value) {
+    return value !== value;
+  }
+
+  /**
+   * The base implementation of `_.mean` and `_.meanBy` without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {number} Returns the mean.
+   */
+  function baseMean(array, iteratee) {
+    var length = array == null ? 0 : array.length;
+    return length ? (baseSum(array, iteratee) / length) : NAN;
+  }
+
+  /**
+   * The base implementation of `_.property` without support for deep paths.
+   *
+   * @private
+   * @param {string} key The key of the property to get.
+   * @returns {Function} Returns the new accessor function.
+   */
+  function baseProperty(key) {
+    return function(object) {
+      return object == null ? undefined : object[key];
+    };
+  }
+
+  /**
+   * The base implementation of `_.propertyOf` without support for deep paths.
+   *
+   * @private
+   * @param {Object} object The object to query.
+   * @returns {Function} Returns the new accessor function.
+   */
+  function basePropertyOf(object) {
+    return function(key) {
+      return object == null ? undefined : object[key];
+    };
+  }
+
+  /**
+   * The base implementation of `_.reduce` and `_.reduceRight`, without support
+   * for iteratee shorthands, which iterates over `collection` using `eachFunc`.
+   *
+   * @private
+   * @param {Array|Object} collection The collection to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @param {*} accumulator The initial value.
+   * @param {boolean} initAccum Specify using the first or last element of
+   *  `collection` as the initial value.
+   * @param {Function} eachFunc The function to iterate over `collection`.
+   * @returns {*} Returns the accumulated value.
+   */
+  function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) {
+    eachFunc(collection, function(value, index, collection) {
+      accumulator = initAccum
+        ? (initAccum = false, value)
+        : iteratee(accumulator, value, index, collection);
+    });
+    return accumulator;
+  }
+
+  /**
+   * The base implementation of `_.sortBy` which uses `comparer` to define the
+   * sort order of `array` and replaces criteria objects with their corresponding
+   * values.
+   *
+   * @private
+   * @param {Array} array The array to sort.
+   * @param {Function} comparer The function to define sort order.
+   * @returns {Array} Returns `array`.
+   */
+  function baseSortBy(array, comparer) {
+    var length = array.length;
+
+    array.sort(comparer);
+    while (length--) {
+      array[length] = array[length].value;
+    }
+    return array;
+  }
+
+  /**
+   * The base implementation of `_.sum` and `_.sumBy` without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {number} Returns the sum.
+   */
+  function baseSum(array, iteratee) {
+    var result,
+        index = -1,
+        length = array.length;
+
+    while (++index < length) {
+      var current = iteratee(array[index]);
+      if (current !== undefined) {
+        result = result === undefined ? current : (result + current);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.times` without support for iteratee shorthands
+   * or max array length checks.
+   *
+   * @private
+   * @param {number} n The number of times to invoke `iteratee`.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {Array} Returns the array of results.
+   */
+  function baseTimes(n, iteratee) {
+    var index = -1,
+        result = Array(n);
+
+    while (++index < n) {
+      result[index] = iteratee(index);
+    }
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.toPairs` and `_.toPairsIn` which creates an array
+   * of key-value pairs for `object` corresponding to the property names of `props`.
+   *
+   * @private
+   * @param {Object} object The object to query.
+   * @param {Array} props The property names to get values for.
+   * @returns {Object} Returns the key-value pairs.
+   */
+  function baseToPairs(object, props) {
+    return arrayMap(props, function(key) {
+      return [key, object[key]];
+    });
+  }
+
+  /**
+   * The base implementation of `_.unary` without support for storing metadata.
+   *
+   * @private
+   * @param {Function} func The function to cap arguments for.
+   * @returns {Function} Returns the new capped function.
+   */
+  function baseUnary(func) {
+    return function(value) {
+      return func(value);
+    };
+  }
+
+  /**
+   * The base implementation of `_.values` and `_.valuesIn` which creates an
+   * array of `object` property values corresponding to the property names
+   * of `props`.
+   *
+   * @private
+   * @param {Object} object The object to query.
+   * @param {Array} props The property names to get values for.
+   * @returns {Object} Returns the array of property values.
+   */
+  function baseValues(object, props) {
+    return arrayMap(props, function(key) {
+      return object[key];
+    });
+  }
+
+  /**
+   * Checks if a `cache` value for `key` exists.
+   *
+   * @private
+   * @param {Object} cache The cache to query.
+   * @param {string} key The key of the entry to check.
+   * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+   */
+  function cacheHas(cache, key) {
+    return cache.has(key);
+  }
+
+  /**
+   * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol
+   * that is not found in the character symbols.
+   *
+   * @private
+   * @param {Array} strSymbols The string symbols to inspect.
+   * @param {Array} chrSymbols The character symbols to find.
+   * @returns {number} Returns the index of the first unmatched string symbol.
+   */
+  function charsStartIndex(strSymbols, chrSymbols) {
+    var index = -1,
+        length = strSymbols.length;
+
+    while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
+    return index;
+  }
+
+  /**
+   * Used by `_.trim` and `_.trimEnd` to get the index of the last string symbol
+   * that is not found in the character symbols.
+   *
+   * @private
+   * @param {Array} strSymbols The string symbols to inspect.
+   * @param {Array} chrSymbols The character symbols to find.
+   * @returns {number} Returns the index of the last unmatched string symbol.
+   */
+  function charsEndIndex(strSymbols, chrSymbols) {
+    var index = strSymbols.length;
+
+    while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
+    return index;
+  }
+
+  /**
+   * Gets the number of `placeholder` occurrences in `array`.
+   *
+   * @private
+   * @param {Array} array The array to inspect.
+   * @param {*} placeholder The placeholder to search for.
+   * @returns {number} Returns the placeholder count.
+   */
+  function countHolders(array, placeholder) {
+    var length = array.length,
+        result = 0;
+
+    while (length--) {
+      if (array[length] === placeholder) {
+        ++result;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Used by `_.deburr` to convert Latin-1 Supplement and Latin Extended-A
+   * letters to basic Latin letters.
+   *
+   * @private
+   * @param {string} letter The matched letter to deburr.
+   * @returns {string} Returns the deburred letter.
+   */
+  var deburrLetter = basePropertyOf(deburredLetters);
+
+  /**
+   * Used by `_.escape` to convert characters to HTML entities.
+   *
+   * @private
+   * @param {string} chr The matched character to escape.
+   * @returns {string} Returns the escaped character.
+   */
+  var escapeHtmlChar = basePropertyOf(htmlEscapes);
+
+  /**
+   * Used by `_.template` to escape characters for inclusion in compiled string literals.
+   *
+   * @private
+   * @param {string} chr The matched character to escape.
+   * @returns {string} Returns the escaped character.
+   */
+  function escapeStringChar(chr) {
+    return '\\' + stringEscapes[chr];
+  }
+
+  /**
+   * Gets the value at `key` of `object`.
+   *
+   * @private
+   * @param {Object} [object] The object to query.
+   * @param {string} key The key of the property to get.
+   * @returns {*} Returns the property value.
+   */
+  function getValue(object, key) {
+    return object == null ? undefined : object[key];
+  }
+
+  /**
+   * Checks if `string` contains Unicode symbols.
+   *
+   * @private
+   * @param {string} string The string to inspect.
+   * @returns {boolean} Returns `true` if a symbol is found, else `false`.
+   */
+  function hasUnicode(string) {
+    return reHasUnicode.test(string);
+  }
+
+  /**
+   * Checks if `string` contains a word composed of Unicode symbols.
+   *
+   * @private
+   * @param {string} string The string to inspect.
+   * @returns {boolean} Returns `true` if a word is found, else `false`.
+   */
+  function hasUnicodeWord(string) {
+    return reHasUnicodeWord.test(string);
+  }
+
+  /**
+   * Converts `iterator` to an array.
+   *
+   * @private
+   * @param {Object} iterator The iterator to convert.
+   * @returns {Array} Returns the converted array.
+   */
+  function iteratorToArray(iterator) {
+    var data,
+        result = [];
+
+    while (!(data = iterator.next()).done) {
+      result.push(data.value);
+    }
+    return result;
+  }
+
+  /**
+   * Converts `map` to its key-value pairs.
+   *
+   * @private
+   * @param {Object} map The map to convert.
+   * @returns {Array} Returns the key-value pairs.
+   */
+  function mapToArray(map) {
+    var index = -1,
+        result = Array(map.size);
+
+    map.forEach(function(value, key) {
+      result[++index] = [key, value];
+    });
+    return result;
+  }
+
+  /**
+   * Creates a unary function that invokes `func` with its argument transformed.
+   *
+   * @private
+   * @param {Function} func The function to wrap.
+   * @param {Function} transform The argument transform.
+   * @returns {Function} Returns the new function.
+   */
+  function overArg(func, transform) {
+    return function(arg) {
+      return func(transform(arg));
+    };
+  }
+
+  /**
+   * Replaces all `placeholder` elements in `array` with an internal placeholder
+   * and returns an array of their indexes.
+   *
+   * @private
+   * @param {Array} array The array to modify.
+   * @param {*} placeholder The placeholder to replace.
+   * @returns {Array} Returns the new array of placeholder indexes.
+   */
+  function replaceHolders(array, placeholder) {
+    var index = -1,
+        length = array.length,
+        resIndex = 0,
+        result = [];
+
+    while (++index < length) {
+      var value = array[index];
+      if (value === placeholder || value === PLACEHOLDER) {
+        array[index] = PLACEHOLDER;
+        result[resIndex++] = index;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Converts `set` to an array of its values.
+   *
+   * @private
+   * @param {Object} set The set to convert.
+   * @returns {Array} Returns the values.
+   */
+  function setToArray(set) {
+    var index = -1,
+        result = Array(set.size);
+
+    set.forEach(function(value) {
+      result[++index] = value;
+    });
+    return result;
+  }
+
+  /**
+   * Converts `set` to its value-value pairs.
+   *
+   * @private
+   * @param {Object} set The set to convert.
+   * @returns {Array} Returns the value-value pairs.
+   */
+  function setToPairs(set) {
+    var index = -1,
+        result = Array(set.size);
+
+    set.forEach(function(value) {
+      result[++index] = [value, value];
+    });
+    return result;
+  }
+
+  /**
+   * A specialized version of `_.indexOf` which performs strict equality
+   * comparisons of values, i.e. `===`.
+   *
+   * @private
+   * @param {Array} array The array to inspect.
+   * @param {*} value The value to search for.
+   * @param {number} fromIndex The index to search from.
+   * @returns {number} Returns the index of the matched value, else `-1`.
+   */
+  function strictIndexOf(array, value, fromIndex) {
+    var index = fromIndex - 1,
+        length = array.length;
+
+    while (++index < length) {
+      if (array[index] === value) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * A specialized version of `_.lastIndexOf` which performs strict equality
+   * comparisons of values, i.e. `===`.
+   *
+   * @private
+   * @param {Array} array The array to inspect.
+   * @param {*} value The value to search for.
+   * @param {number} fromIndex The index to search from.
+   * @returns {number} Returns the index of the matched value, else `-1`.
+   */
+  function strictLastIndexOf(array, value, fromIndex) {
+    var index = fromIndex + 1;
+    while (index--) {
+      if (array[index] === value) {
+        return index;
+      }
+    }
+    return index;
+  }
+
+  /**
+   * Gets the number of symbols in `string`.
+   *
+   * @private
+   * @param {string} string The string to inspect.
+   * @returns {number} Returns the string size.
+   */
+  function stringSize(string) {
+    return hasUnicode(string)
+      ? unicodeSize(string)
+      : asciiSize(string);
+  }
+
+  /**
+   * Converts `string` to an array.
+   *
+   * @private
+   * @param {string} string The string to convert.
+   * @returns {Array} Returns the converted array.
+   */
+  function stringToArray(string) {
+    return hasUnicode(string)
+      ? unicodeToArray(string)
+      : asciiToArray(string);
+  }
+
+  /**
+   * Used by `_.unescape` to convert HTML entities to characters.
+   *
+   * @private
+   * @param {string} chr The matched character to unescape.
+   * @returns {string} Returns the unescaped character.
+   */
+  var unescapeHtmlChar = basePropertyOf(htmlUnescapes);
+
+  /**
+   * Gets the size of a Unicode `string`.
+   *
+   * @private
+   * @param {string} string The string inspect.
+   * @returns {number} Returns the string size.
+   */
+  function unicodeSize(string) {
+    var result = reUnicode.lastIndex = 0;
+    while (reUnicode.test(string)) {
+      ++result;
+    }
+    return result;
+  }
+
+  /**
+   * Converts a Unicode `string` to an array.
+   *
+   * @private
+   * @param {string} string The string to convert.
+   * @returns {Array} Returns the converted array.
+   */
+  function unicodeToArray(string) {
+    return string.match(reUnicode) || [];
+  }
+
+  /**
+   * Splits a Unicode `string` into an array of its words.
+   *
+   * @private
+   * @param {string} The string to inspect.
+   * @returns {Array} Returns the words of `string`.
+   */
+  function unicodeWords(string) {
+    return string.match(reUnicodeWord) || [];
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Create a new pristine `lodash` function using the `context` object.
+   *
+   * @static
+   * @memberOf _
+   * @since 1.1.0
+   * @category Util
+   * @param {Object} [context=root] The context object.
+   * @returns {Function} Returns a new `lodash` function.
+   * @example
+   *
+   * _.mixin({ 'foo': _.constant('foo') });
+   *
+   * var lodash = _.runInContext();
+   * lodash.mixin({ 'bar': lodash.constant('bar') });
+   *
+   * _.isFunction(_.foo);
+   * // => true
+   * _.isFunction(_.bar);
+   * // => false
+   *
+   * lodash.isFunction(lodash.foo);
+   * // => false
+   * lodash.isFunction(lodash.bar);
+   * // => true
+   *
+   * // Create a suped-up `defer` in Node.js.
+   * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer;
+   */
+  var runInContext = (function runInContext(context) {
+    context = context == null ? root : _.defaults(root.Object(), context, _.pick(root, contextProps));
+
+    /** Built-in constructor references. */
+    var Array = context.Array,
+        Date = context.Date,
+        Error = context.Error,
+        Function = context.Function,
+        Math = context.Math,
+        Object = context.Object,
+        RegExp = context.RegExp,
+        String = context.String,
+        TypeError = context.TypeError;
+
+    /** Used for built-in method references. */
+    var arrayProto = Array.prototype,
+        funcProto = Function.prototype,
+        objectProto = Object.prototype;
+
+    /** Used to detect overreaching core-js shims. */
+    var coreJsData = context['__core-js_shared__'];
+
+    /** Used to resolve the decompiled source of functions. */
+    var funcToString = funcProto.toString;
+
+    /** Used to check objects for own properties. */
+    var hasOwnProperty = objectProto.hasOwnProperty;
+
+    /** Used to generate unique IDs. */
+    var idCounter = 0;
+
+    /** Used to detect methods masquerading as native. */
+    var maskSrcKey = (function() {
+      var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || '');
+      return uid ? ('Symbol(src)_1.' + uid) : '';
+    }());
+
+    /**
+     * Used to resolve the
+     * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring)
+     * of values.
+     */
+    var nativeObjectToString = objectProto.toString;
+
+    /** Used to infer the `Object` constructor. */
+    var objectCtorString = funcToString.call(Object);
+
+    /** Used to restore the original `_` reference in `_.noConflict`. */
+    var oldDash = root._;
+
+    /** Used to detect if a method is native. */
+    var reIsNative = RegExp('^' +
+      funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&')
+      .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
+    );
+
+    /** Built-in value references. */
+    var Buffer = moduleExports ? context.Buffer : undefined,
+        Symbol = context.Symbol,
+        Uint8Array = context.Uint8Array,
+        allocUnsafe = Buffer ? Buffer.allocUnsafe : undefined,
+        getPrototype = overArg(Object.getPrototypeOf, Object),
+        objectCreate = Object.create,
+        propertyIsEnumerable = objectProto.propertyIsEnumerable,
+        splice = arrayProto.splice,
+        spreadableSymbol = Symbol ? Symbol.isConcatSpreadable : undefined,
+        symIterator = Symbol ? Symbol.iterator : undefined,
+        symToStringTag = Symbol ? Symbol.toStringTag : undefined;
+
+    var defineProperty = (function() {
+      try {
+        var func = getNative(Object, 'defineProperty');
+        func({}, '', {});
+        return func;
+      } catch (e) {}
+    }());
+
+    /** Mocked built-ins. */
+    var ctxClearTimeout = context.clearTimeout !== root.clearTimeout && context.clearTimeout,
+        ctxNow = Date && Date.now !== root.Date.now && Date.now,
+        ctxSetTimeout = context.setTimeout !== root.setTimeout && context.setTimeout;
+
+    /* Built-in method references for those with the same name as other `lodash` methods. */
+    var nativeCeil = Math.ceil,
+        nativeFloor = Math.floor,
+        nativeGetSymbols = Object.getOwnPropertySymbols,
+        nativeIsBuffer = Buffer ? Buffer.isBuffer : undefined,
+        nativeIsFinite = context.isFinite,
+        nativeJoin = arrayProto.join,
+        nativeKeys = overArg(Object.keys, Object),
+        nativeMax = Math.max,
+        nativeMin = Math.min,
+        nativeNow = Date.now,
+        nativeParseInt = context.parseInt,
+        nativeRandom = Math.random,
+        nativeReverse = arrayProto.reverse;
+
+    /* Built-in method references that are verified to be native. */
+    var DataView = getNative(context, 'DataView'),
+        Map = getNative(context, 'Map'),
+        Promise = getNative(context, 'Promise'),
+        Set = getNative(context, 'Set'),
+        WeakMap = getNative(context, 'WeakMap'),
+        nativeCreate = getNative(Object, 'create');
+
+    /** Used to store function metadata. */
+    var metaMap = WeakMap && new WeakMap;
+
+    /** Used to lookup unminified function names. */
+    var realNames = {};
+
+    /** Used to detect maps, sets, and weakmaps. */
+    var dataViewCtorString = toSource(DataView),
+        mapCtorString = toSource(Map),
+        promiseCtorString = toSource(Promise),
+        setCtorString = toSource(Set),
+        weakMapCtorString = toSource(WeakMap);
+
+    /** Used to convert symbols to primitives and strings. */
+    var symbolProto = Symbol ? Symbol.prototype : undefined,
+        symbolValueOf = symbolProto ? symbolProto.valueOf : undefined,
+        symbolToString = symbolProto ? symbolProto.toString : undefined;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a `lodash` object which wraps `value` to enable implicit method
+     * chain sequences. Methods that operate on and return arrays, collections,
+     * and functions can be chained together. Methods that retrieve a single value
+     * or may return a primitive value will automatically end the chain sequence
+     * and return the unwrapped value. Otherwise, the value must be unwrapped
+     * with `_#value`.
+     *
+     * Explicit chain sequences, which must be unwrapped with `_#value`, may be
+     * enabled using `_.chain`.
+     *
+     * The execution of chained methods is lazy, that is, it's deferred until
+     * `_#value` is implicitly or explicitly called.
+     *
+     * Lazy evaluation allows several methods to support shortcut fusion.
+     * Shortcut fusion is an optimization to merge iteratee calls; this avoids
+     * the creation of intermediate arrays and can greatly reduce the number of
+     * iteratee executions. Sections of a chain sequence qualify for shortcut
+     * fusion if the section is applied to an array and iteratees accept only
+     * one argument. The heuristic for whether a section qualifies for shortcut
+     * fusion is subject to change.
+     *
+     * Chaining is supported in custom builds as long as the `_#value` method is
+     * directly or indirectly included in the build.
+     *
+     * In addition to lodash methods, wrappers have `Array` and `String` methods.
+     *
+     * The wrapper `Array` methods are:
+     * `concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift`
+     *
+     * The wrapper `String` methods are:
+     * `replace` and `split`
+     *
+     * The wrapper methods that support shortcut fusion are:
+     * `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`,
+     * `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`,
+     * `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray`
+     *
+     * The chainable wrapper methods are:
+     * `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`,
+     * `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`,
+     * `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`,
+     * `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`,
+     * `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`,
+     * `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`,
+     * `flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`,
+     * `flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`,
+     * `functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`,
+     * `intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`,
+     * `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`,
+     * `memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`,
+     * `nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`,
+     * `overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`,
+     * `pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`,
+     * `pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`,
+     * `remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`,
+     * `slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`,
+     * `takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`,
+     * `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`,
+     * `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`,
+     * `unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`,
+     * `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`,
+     * `zipObject`, `zipObjectDeep`, and `zipWith`
+     *
+     * The wrapper methods that are **not** chainable by default are:
+     * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`,
+     * `cloneDeep`, `cloneDeepWith`, `cloneWith`, `conformsTo`, `deburr`,
+     * `defaultTo`, `divide`, `each`, `eachRight`, `endsWith`, `eq`, `escape`,
+     * `escapeRegExp`, `every`, `find`, `findIndex`, `findKey`, `findLast`,
+     * `findLastIndex`, `findLastKey`, `first`, `floor`, `forEach`, `forEachRight`,
+     * `forIn`, `forInRight`, `forOwn`, `forOwnRight`, `get`, `gt`, `gte`, `has`,
+     * `hasIn`, `head`, `identity`, `includes`, `indexOf`, `inRange`, `invoke`,
+     * `isArguments`, `isArray`, `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`,
+     * `isBoolean`, `isBuffer`, `isDate`, `isElement`, `isEmpty`, `isEqual`,
+     * `isEqualWith`, `isError`, `isFinite`, `isFunction`, `isInteger`, `isLength`,
+     * `isMap`, `isMatch`, `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`,
+     * `isNumber`, `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`,
+     * `isSafeInteger`, `isSet`, `isString`, `isUndefined`, `isTypedArray`,
+     * `isWeakMap`, `isWeakSet`, `join`, `kebabCase`, `last`, `lastIndexOf`,
+     * `lowerCase`, `lowerFirst`, `lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`,
+     * `min`, `minBy`, `multiply`, `noConflict`, `noop`, `now`, `nth`, `pad`,
+     * `padEnd`, `padStart`, `parseInt`, `pop`, `random`, `reduce`, `reduceRight`,
+     * `repeat`, `result`, `round`, `runInContext`, `sample`, `shift`, `size`,
+     * `snakeCase`, `some`, `sortedIndex`, `sortedIndexBy`, `sortedLastIndex`,
+     * `sortedLastIndexBy`, `startCase`, `startsWith`, `stubArray`, `stubFalse`,
+     * `stubObject`, `stubString`, `stubTrue`, `subtract`, `sum`, `sumBy`,
+     * `template`, `times`, `toFinite`, `toInteger`, `toJSON`, `toLength`,
+     * `toLower`, `toNumber`, `toSafeInteger`, `toString`, `toUpper`, `trim`,
+     * `trimEnd`, `trimStart`, `truncate`, `unescape`, `uniqueId`, `upperCase`,
+     * `upperFirst`, `value`, and `words`
+     *
+     * @name _
+     * @constructor
+     * @category Seq
+     * @param {*} value The value to wrap in a `lodash` instance.
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var wrapped = _([1, 2, 3]);
+     *
+     * // Returns an unwrapped value.
+     * wrapped.reduce(_.add);
+     * // => 6
+     *
+     * // Returns a wrapped value.
+     * var squares = wrapped.map(square);
+     *
+     * _.isArray(squares);
+     * // => false
+     *
+     * _.isArray(squares.value());
+     * // => true
+     */
+    function lodash(value) {
+      if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) {
+        if (value instanceof LodashWrapper) {
+          return value;
+        }
+        if (hasOwnProperty.call(value, '__wrapped__')) {
+          return wrapperClone(value);
+        }
+      }
+      return new LodashWrapper(value);
+    }
+
+    /**
+     * The base implementation of `_.create` without support for assigning
+     * properties to the created object.
+     *
+     * @private
+     * @param {Object} proto The object to inherit from.
+     * @returns {Object} Returns the new object.
+     */
+    var baseCreate = (function() {
+      function object() {}
+      return function(proto) {
+        if (!isObject(proto)) {
+          return {};
+        }
+        if (objectCreate) {
+          return objectCreate(proto);
+        }
+        object.prototype = proto;
+        var result = new object;
+        object.prototype = undefined;
+        return result;
+      };
+    }());
+
+    /**
+     * The function whose prototype chain sequence wrappers inherit from.
+     *
+     * @private
+     */
+    function baseLodash() {
+      // No operation performed.
+    }
+
+    /**
+     * The base constructor for creating `lodash` wrapper objects.
+     *
+     * @private
+     * @param {*} value The value to wrap.
+     * @param {boolean} [chainAll] Enable explicit method chain sequences.
+     */
+    function LodashWrapper(value, chainAll) {
+      this.__wrapped__ = value;
+      this.__actions__ = [];
+      this.__chain__ = !!chainAll;
+      this.__index__ = 0;
+      this.__values__ = undefined;
+    }
+
+    /**
+     * By default, the template delimiters used by lodash are like those in
+     * embedded Ruby (ERB) as well as ES2015 template strings. Change the
+     * following template settings to use alternative delimiters.
+     *
+     * @static
+     * @memberOf _
+     * @type {Object}
+     */
+    lodash.templateSettings = {
+
+      /**
+       * Used to detect `data` property values to be HTML-escaped.
+       *
+       * @memberOf _.templateSettings
+       * @type {RegExp}
+       */
+      'escape': reEscape,
+
+      /**
+       * Used to detect code to be evaluated.
+       *
+       * @memberOf _.templateSettings
+       * @type {RegExp}
+       */
+      'evaluate': reEvaluate,
+
+      /**
+       * Used to detect `data` property values to inject.
+       *
+       * @memberOf _.templateSettings
+       * @type {RegExp}
+       */
+      'interpolate': reInterpolate,
+
+      /**
+       * Used to reference the data object in the template text.
+       *
+       * @memberOf _.templateSettings
+       * @type {string}
+       */
+      'variable': '',
+
+      /**
+       * Used to import variables into the compiled template.
+       *
+       * @memberOf _.templateSettings
+       * @type {Object}
+       */
+      'imports': {
+
+        /**
+         * A reference to the `lodash` function.
+         *
+         * @memberOf _.templateSettings.imports
+         * @type {Function}
+         */
+        '_': lodash
+      }
+    };
+
+    // Ensure wrappers are instances of `baseLodash`.
+    lodash.prototype = baseLodash.prototype;
+    lodash.prototype.constructor = lodash;
+
+    LodashWrapper.prototype = baseCreate(baseLodash.prototype);
+    LodashWrapper.prototype.constructor = LodashWrapper;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation.
+     *
+     * @private
+     * @constructor
+     * @param {*} value The value to wrap.
+     */
+    function LazyWrapper(value) {
+      this.__wrapped__ = value;
+      this.__actions__ = [];
+      this.__dir__ = 1;
+      this.__filtered__ = false;
+      this.__iteratees__ = [];
+      this.__takeCount__ = MAX_ARRAY_LENGTH;
+      this.__views__ = [];
+    }
+
+    /**
+     * Creates a clone of the lazy wrapper object.
+     *
+     * @private
+     * @name clone
+     * @memberOf LazyWrapper
+     * @returns {Object} Returns the cloned `LazyWrapper` object.
+     */
+    function lazyClone() {
+      var result = new LazyWrapper(this.__wrapped__);
+      result.__actions__ = copyArray(this.__actions__);
+      result.__dir__ = this.__dir__;
+      result.__filtered__ = this.__filtered__;
+      result.__iteratees__ = copyArray(this.__iteratees__);
+      result.__takeCount__ = this.__takeCount__;
+      result.__views__ = copyArray(this.__views__);
+      return result;
+    }
+
+    /**
+     * Reverses the direction of lazy iteration.
+     *
+     * @private
+     * @name reverse
+     * @memberOf LazyWrapper
+     * @returns {Object} Returns the new reversed `LazyWrapper` object.
+     */
+    function lazyReverse() {
+      if (this.__filtered__) {
+        var result = new LazyWrapper(this);
+        result.__dir__ = -1;
+        result.__filtered__ = true;
+      } else {
+        result = this.clone();
+        result.__dir__ *= -1;
+      }
+      return result;
+    }
+
+    /**
+     * Extracts the unwrapped value from its lazy wrapper.
+     *
+     * @private
+     * @name value
+     * @memberOf LazyWrapper
+     * @returns {*} Returns the unwrapped value.
+     */
+    function lazyValue() {
+      var array = this.__wrapped__.value(),
+          dir = this.__dir__,
+          isArr = isArray(array),
+          isRight = dir < 0,
+          arrLength = isArr ? array.length : 0,
+          view = getView(0, arrLength, this.__views__),
+          start = view.start,
+          end = view.end,
+          length = end - start,
+          index = isRight ? end : (start - 1),
+          iteratees = this.__iteratees__,
+          iterLength = iteratees.length,
+          resIndex = 0,
+          takeCount = nativeMin(length, this.__takeCount__);
+
+      if (!isArr || (!isRight && arrLength == length && takeCount == length)) {
+        return baseWrapperValue(array, this.__actions__);
+      }
+      var result = [];
+
+      outer:
+      while (length-- && resIndex < takeCount) {
+        index += dir;
+
+        var iterIndex = -1,
+            value = array[index];
+
+        while (++iterIndex < iterLength) {
+          var data = iteratees[iterIndex],
+              iteratee = data.iteratee,
+              type = data.type,
+              computed = iteratee(value);
+
+          if (type == LAZY_MAP_FLAG) {
+            value = computed;
+          } else if (!computed) {
+            if (type == LAZY_FILTER_FLAG) {
+              continue outer;
+            } else {
+              break outer;
+            }
+          }
+        }
+        result[resIndex++] = value;
+      }
+      return result;
+    }
+
+    // Ensure `LazyWrapper` is an instance of `baseLodash`.
+    LazyWrapper.prototype = baseCreate(baseLodash.prototype);
+    LazyWrapper.prototype.constructor = LazyWrapper;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a hash object.
+     *
+     * @private
+     * @constructor
+     * @param {Array} [entries] The key-value pairs to cache.
+     */
+    function Hash(entries) {
+      var index = -1,
+          length = entries == null ? 0 : entries.length;
+
+      this.clear();
+      while (++index < length) {
+        var entry = entries[index];
+        this.set(entry[0], entry[1]);
+      }
+    }
+
+    /**
+     * Removes all key-value entries from the hash.
+     *
+     * @private
+     * @name clear
+     * @memberOf Hash
+     */
+    function hashClear() {
+      this.__data__ = nativeCreate ? nativeCreate(null) : {};
+      this.size = 0;
+    }
+
+    /**
+     * Removes `key` and its value from the hash.
+     *
+     * @private
+     * @name delete
+     * @memberOf Hash
+     * @param {Object} hash The hash to modify.
+     * @param {string} key The key of the value to remove.
+     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+     */
+    function hashDelete(key) {
+      var result = this.has(key) && delete this.__data__[key];
+      this.size -= result ? 1 : 0;
+      return result;
+    }
+
+    /**
+     * Gets the hash value for `key`.
+     *
+     * @private
+     * @name get
+     * @memberOf Hash
+     * @param {string} key The key of the value to get.
+     * @returns {*} Returns the entry value.
+     */
+    function hashGet(key) {
+      var data = this.__data__;
+      if (nativeCreate) {
+        var result = data[key];
+        return result === HASH_UNDEFINED ? undefined : result;
+      }
+      return hasOwnProperty.call(data, key) ? data[key] : undefined;
+    }
+
+    /**
+     * Checks if a hash value for `key` exists.
+     *
+     * @private
+     * @name has
+     * @memberOf Hash
+     * @param {string} key The key of the entry to check.
+     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+     */
+    function hashHas(key) {
+      var data = this.__data__;
+      return nativeCreate ? (data[key] !== undefined) : hasOwnProperty.call(data, key);
+    }
+
+    /**
+     * Sets the hash `key` to `value`.
+     *
+     * @private
+     * @name set
+     * @memberOf Hash
+     * @param {string} key The key of the value to set.
+     * @param {*} value The value to set.
+     * @returns {Object} Returns the hash instance.
+     */
+    function hashSet(key, value) {
+      var data = this.__data__;
+      this.size += this.has(key) ? 0 : 1;
+      data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;
+      return this;
+    }
+
+    // Add methods to `Hash`.
+    Hash.prototype.clear = hashClear;
+    Hash.prototype['delete'] = hashDelete;
+    Hash.prototype.get = hashGet;
+    Hash.prototype.has = hashHas;
+    Hash.prototype.set = hashSet;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates an list cache object.
+     *
+     * @private
+     * @constructor
+     * @param {Array} [entries] The key-value pairs to cache.
+     */
+    function ListCache(entries) {
+      var index = -1,
+          length = entries == null ? 0 : entries.length;
+
+      this.clear();
+      while (++index < length) {
+        var entry = entries[index];
+        this.set(entry[0], entry[1]);
+      }
+    }
+
+    /**
+     * Removes all key-value entries from the list cache.
+     *
+     * @private
+     * @name clear
+     * @memberOf ListCache
+     */
+    function listCacheClear() {
+      this.__data__ = [];
+      this.size = 0;
+    }
+
+    /**
+     * Removes `key` and its value from the list cache.
+     *
+     * @private
+     * @name delete
+     * @memberOf ListCache
+     * @param {string} key The key of the value to remove.
+     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+     */
+    function listCacheDelete(key) {
+      var data = this.__data__,
+          index = assocIndexOf(data, key);
+
+      if (index < 0) {
+        return false;
+      }
+      var lastIndex = data.length - 1;
+      if (index == lastIndex) {
+        data.pop();
+      } else {
+        splice.call(data, index, 1);
+      }
+      --this.size;
+      return true;
+    }
+
+    /**
+     * Gets the list cache value for `key`.
+     *
+     * @private
+     * @name get
+     * @memberOf ListCache
+     * @param {string} key The key of the value to get.
+     * @returns {*} Returns the entry value.
+     */
+    function listCacheGet(key) {
+      var data = this.__data__,
+          index = assocIndexOf(data, key);
+
+      return index < 0 ? undefined : data[index][1];
+    }
+
+    /**
+     * Checks if a list cache value for `key` exists.
+     *
+     * @private
+     * @name has
+     * @memberOf ListCache
+     * @param {string} key The key of the entry to check.
+     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+     */
+    function listCacheHas(key) {
+      return assocIndexOf(this.__data__, key) > -1;
+    }
+
+    /**
+     * Sets the list cache `key` to `value`.
+     *
+     * @private
+     * @name set
+     * @memberOf ListCache
+     * @param {string} key The key of the value to set.
+     * @param {*} value The value to set.
+     * @returns {Object} Returns the list cache instance.
+     */
+    function listCacheSet(key, value) {
+      var data = this.__data__,
+          index = assocIndexOf(data, key);
+
+      if (index < 0) {
+        ++this.size;
+        data.push([key, value]);
+      } else {
+        data[index][1] = value;
+      }
+      return this;
+    }
+
+    // Add methods to `ListCache`.
+    ListCache.prototype.clear = listCacheClear;
+    ListCache.prototype['delete'] = listCacheDelete;
+    ListCache.prototype.get = listCacheGet;
+    ListCache.prototype.has = listCacheHas;
+    ListCache.prototype.set = listCacheSet;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a map cache object to store key-value pairs.
+     *
+     * @private
+     * @constructor
+     * @param {Array} [entries] The key-value pairs to cache.
+     */
+    function MapCache(entries) {
+      var index = -1,
+          length = entries == null ? 0 : entries.length;
+
+      this.clear();
+      while (++index < length) {
+        var entry = entries[index];
+        this.set(entry[0], entry[1]);
+      }
+    }
+
+    /**
+     * Removes all key-value entries from the map.
+     *
+     * @private
+     * @name clear
+     * @memberOf MapCache
+     */
+    function mapCacheClear() {
+      this.size = 0;
+      this.__data__ = {
+        'hash': new Hash,
+        'map': new (Map || ListCache),
+        'string': new Hash
+      };
+    }
+
+    /**
+     * Removes `key` and its value from the map.
+     *
+     * @private
+     * @name delete
+     * @memberOf MapCache
+     * @param {string} key The key of the value to remove.
+     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+     */
+    function mapCacheDelete(key) {
+      var result = getMapData(this, key)['delete'](key);
+      this.size -= result ? 1 : 0;
+      return result;
+    }
+
+    /**
+     * Gets the map value for `key`.
+     *
+     * @private
+     * @name get
+     * @memberOf MapCache
+     * @param {string} key The key of the value to get.
+     * @returns {*} Returns the entry value.
+     */
+    function mapCacheGet(key) {
+      return getMapData(this, key).get(key);
+    }
+
+    /**
+     * Checks if a map value for `key` exists.
+     *
+     * @private
+     * @name has
+     * @memberOf MapCache
+     * @param {string} key The key of the entry to check.
+     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+     */
+    function mapCacheHas(key) {
+      return getMapData(this, key).has(key);
+    }
+
+    /**
+     * Sets the map `key` to `value`.
+     *
+     * @private
+     * @name set
+     * @memberOf MapCache
+     * @param {string} key The key of the value to set.
+     * @param {*} value The value to set.
+     * @returns {Object} Returns the map cache instance.
+     */
+    function mapCacheSet(key, value) {
+      var data = getMapData(this, key),
+          size = data.size;
+
+      data.set(key, value);
+      this.size += data.size == size ? 0 : 1;
+      return this;
+    }
+
+    // Add methods to `MapCache`.
+    MapCache.prototype.clear = mapCacheClear;
+    MapCache.prototype['delete'] = mapCacheDelete;
+    MapCache.prototype.get = mapCacheGet;
+    MapCache.prototype.has = mapCacheHas;
+    MapCache.prototype.set = mapCacheSet;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     *
+     * Creates an array cache object to store unique values.
+     *
+     * @private
+     * @constructor
+     * @param {Array} [values] The values to cache.
+     */
+    function SetCache(values) {
+      var index = -1,
+          length = values == null ? 0 : values.length;
+
+      this.__data__ = new MapCache;
+      while (++index < length) {
+        this.add(values[index]);
+      }
+    }
+
+    /**
+     * Adds `value` to the array cache.
+     *
+     * @private
+     * @name add
+     * @memberOf SetCache
+     * @alias push
+     * @param {*} value The value to cache.
+     * @returns {Object} Returns the cache instance.
+     */
+    function setCacheAdd(value) {
+      this.__data__.set(value, HASH_UNDEFINED);
+      return this;
+    }
+
+    /**
+     * Checks if `value` is in the array cache.
+     *
+     * @private
+     * @name has
+     * @memberOf SetCache
+     * @param {*} value The value to search for.
+     * @returns {number} Returns `true` if `value` is found, else `false`.
+     */
+    function setCacheHas(value) {
+      return this.__data__.has(value);
+    }
+
+    // Add methods to `SetCache`.
+    SetCache.prototype.add = SetCache.prototype.push = setCacheAdd;
+    SetCache.prototype.has = setCacheHas;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a stack cache object to store key-value pairs.
+     *
+     * @private
+     * @constructor
+     * @param {Array} [entries] The key-value pairs to cache.
+     */
+    function Stack(entries) {
+      var data = this.__data__ = new ListCache(entries);
+      this.size = data.size;
+    }
+
+    /**
+     * Removes all key-value entries from the stack.
+     *
+     * @private
+     * @name clear
+     * @memberOf Stack
+     */
+    function stackClear() {
+      this.__data__ = new ListCache;
+      this.size = 0;
+    }
+
+    /**
+     * Removes `key` and its value from the stack.
+     *
+     * @private
+     * @name delete
+     * @memberOf Stack
+     * @param {string} key The key of the value to remove.
+     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+     */
+    function stackDelete(key) {
+      var data = this.__data__,
+          result = data['delete'](key);
+
+      this.size = data.size;
+      return result;
+    }
+
+    /**
+     * Gets the stack value for `key`.
+     *
+     * @private
+     * @name get
+     * @memberOf Stack
+     * @param {string} key The key of the value to get.
+     * @returns {*} Returns the entry value.
+     */
+    function stackGet(key) {
+      return this.__data__.get(key);
+    }
+
+    /**
+     * Checks if a stack value for `key` exists.
+     *
+     * @private
+     * @name has
+     * @memberOf Stack
+     * @param {string} key The key of the entry to check.
+     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+     */
+    function stackHas(key) {
+      return this.__data__.has(key);
+    }
+
+    /**
+     * Sets the stack `key` to `value`.
+     *
+     * @private
+     * @name set
+     * @memberOf Stack
+     * @param {string} key The key of the value to set.
+     * @param {*} value The value to set.
+     * @returns {Object} Returns the stack cache instance.
+     */
+    function stackSet(key, value) {
+      var data = this.__data__;
+      if (data instanceof ListCache) {
+        var pairs = data.__data__;
+        if (!Map || (pairs.length < LARGE_ARRAY_SIZE - 1)) {
+          pairs.push([key, value]);
+          this.size = ++data.size;
+          return this;
+        }
+        data = this.__data__ = new MapCache(pairs);
+      }
+      data.set(key, value);
+      this.size = data.size;
+      return this;
+    }
+
+    // Add methods to `Stack`.
+    Stack.prototype.clear = stackClear;
+    Stack.prototype['delete'] = stackDelete;
+    Stack.prototype.get = stackGet;
+    Stack.prototype.has = stackHas;
+    Stack.prototype.set = stackSet;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates an array of the enumerable property names of the array-like `value`.
+     *
+     * @private
+     * @param {*} value The value to query.
+     * @param {boolean} inherited Specify returning inherited property names.
+     * @returns {Array} Returns the array of property names.
+     */
+    function arrayLikeKeys(value, inherited) {
+      var isArr = isArray(value),
+          isArg = !isArr && isArguments(value),
+          isBuff = !isArr && !isArg && isBuffer(value),
+          isType = !isArr && !isArg && !isBuff && isTypedArray(value),
+          skipIndexes = isArr || isArg || isBuff || isType,
+          result = skipIndexes ? baseTimes(value.length, String) : [],
+          length = result.length;
+
+      for (var key in value) {
+        if ((inherited || hasOwnProperty.call(value, key)) &&
+            !(skipIndexes && (
+               // Safari 9 has enumerable `arguments.length` in strict mode.
+               key == 'length' ||
+               // Node.js 0.10 has enumerable non-index properties on buffers.
+               (isBuff && (key == 'offset' || key == 'parent')) ||
+               // PhantomJS 2 has enumerable non-index properties on typed arrays.
+               (isType && (key == 'buffer' || key == 'byteLength' || key == 'byteOffset')) ||
+               // Skip index properties.
+               isIndex(key, length)
+            ))) {
+          result.push(key);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * A specialized version of `_.sample` for arrays.
+     *
+     * @private
+     * @param {Array} array The array to sample.
+     * @returns {*} Returns the random element.
+     */
+    function arraySample(array) {
+      var length = array.length;
+      return length ? array[baseRandom(0, length - 1)] : undefined;
+    }
+
+    /**
+     * A specialized version of `_.sampleSize` for arrays.
+     *
+     * @private
+     * @param {Array} array The array to sample.
+     * @param {number} n The number of elements to sample.
+     * @returns {Array} Returns the random elements.
+     */
+    function arraySampleSize(array, n) {
+      return shuffleSelf(copyArray(array), baseClamp(n, 0, array.length));
+    }
+
+    /**
+     * A specialized version of `_.shuffle` for arrays.
+     *
+     * @private
+     * @param {Array} array The array to shuffle.
+     * @returns {Array} Returns the new shuffled array.
+     */
+    function arrayShuffle(array) {
+      return shuffleSelf(copyArray(array));
+    }
+
+    /**
+     * This function is like `assignValue` except that it doesn't assign
+     * `undefined` values.
+     *
+     * @private
+     * @param {Object} object The object to modify.
+     * @param {string} key The key of the property to assign.
+     * @param {*} value The value to assign.
+     */
+    function assignMergeValue(object, key, value) {
+      if ((value !== undefined && !eq(object[key], value)) ||
+          (value === undefined && !(key in object))) {
+        baseAssignValue(object, key, value);
+      }
+    }
+
+    /**
+     * Assigns `value` to `key` of `object` if the existing value is not equivalent
+     * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * @private
+     * @param {Object} object The object to modify.
+     * @param {string} key The key of the property to assign.
+     * @param {*} value The value to assign.
+     */
+    function assignValue(object, key, value) {
+      var objValue = object[key];
+      if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) ||
+          (value === undefined && !(key in object))) {
+        baseAssignValue(object, key, value);
+      }
+    }
+
+    /**
+     * Gets the index at which the `key` is found in `array` of key-value pairs.
+     *
+     * @private
+     * @param {Array} array The array to inspect.
+     * @param {*} key The key to search for.
+     * @returns {number} Returns the index of the matched value, else `-1`.
+     */
+    function assocIndexOf(array, key) {
+      var length = array.length;
+      while (length--) {
+        if (eq(array[length][0], key)) {
+          return length;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * Aggregates elements of `collection` on `accumulator` with keys transformed
+     * by `iteratee` and values set by `setter`.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} setter The function to set `accumulator` values.
+     * @param {Function} iteratee The iteratee to transform keys.
+     * @param {Object} accumulator The initial aggregated object.
+     * @returns {Function} Returns `accumulator`.
+     */
+    function baseAggregator(collection, setter, iteratee, accumulator) {
+      baseEach(collection, function(value, key, collection) {
+        setter(accumulator, value, iteratee(value), collection);
+      });
+      return accumulator;
+    }
+
+    /**
+     * The base implementation of `_.assign` without support for multiple sources
+     * or `customizer` functions.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @returns {Object} Returns `object`.
+     */
+    function baseAssign(object, source) {
+      return object && copyObject(source, keys(source), object);
+    }
+
+    /**
+     * The base implementation of `_.assignIn` without support for multiple sources
+     * or `customizer` functions.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @returns {Object} Returns `object`.
+     */
+    function baseAssignIn(object, source) {
+      return object && copyObject(source, keysIn(source), object);
+    }
+
+    /**
+     * The base implementation of `assignValue` and `assignMergeValue` without
+     * value checks.
+     *
+     * @private
+     * @param {Object} object The object to modify.
+     * @param {string} key The key of the property to assign.
+     * @param {*} value The value to assign.
+     */
+    function baseAssignValue(object, key, value) {
+      if (key == '__proto__' && defineProperty) {
+        defineProperty(object, key, {
+          'configurable': true,
+          'enumerable': true,
+          'value': value,
+          'writable': true
+        });
+      } else {
+        object[key] = value;
+      }
+    }
+
+    /**
+     * The base implementation of `_.at` without support for individual paths.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {string[]} paths The property paths to pick.
+     * @returns {Array} Returns the picked elements.
+     */
+    function baseAt(object, paths) {
+      var index = -1,
+          length = paths.length,
+          result = Array(length),
+          skip = object == null;
+
+      while (++index < length) {
+        result[index] = skip ? undefined : get(object, paths[index]);
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.clamp` which doesn't coerce arguments.
+     *
+     * @private
+     * @param {number} number The number to clamp.
+     * @param {number} [lower] The lower bound.
+     * @param {number} upper The upper bound.
+     * @returns {number} Returns the clamped number.
+     */
+    function baseClamp(number, lower, upper) {
+      if (number === number) {
+        if (upper !== undefined) {
+          number = number <= upper ? number : upper;
+        }
+        if (lower !== undefined) {
+          number = number >= lower ? number : lower;
+        }
+      }
+      return number;
+    }
+
+    /**
+     * The base implementation of `_.clone` and `_.cloneDeep` which tracks
+     * traversed objects.
+     *
+     * @private
+     * @param {*} value The value to clone.
+     * @param {boolean} bitmask The bitmask flags.
+     *  1 - Deep clone
+     *  2 - Flatten inherited properties
+     *  4 - Clone symbols
+     * @param {Function} [customizer] The function to customize cloning.
+     * @param {string} [key] The key of `value`.
+     * @param {Object} [object] The parent object of `value`.
+     * @param {Object} [stack] Tracks traversed objects and their clone counterparts.
+     * @returns {*} Returns the cloned value.
+     */
+    function baseClone(value, bitmask, customizer, key, object, stack) {
+      var result,
+          isDeep = bitmask & CLONE_DEEP_FLAG,
+          isFlat = bitmask & CLONE_FLAT_FLAG,
+          isFull = bitmask & CLONE_SYMBOLS_FLAG;
+
+      if (customizer) {
+        result = object ? customizer(value, key, object, stack) : customizer(value);
+      }
+      if (result !== undefined) {
+        return result;
+      }
+      if (!isObject(value)) {
+        return value;
+      }
+      var isArr = isArray(value);
+      if (isArr) {
+        result = initCloneArray(value);
+        if (!isDeep) {
+          return copyArray(value, result);
+        }
+      } else {
+        var tag = getTag(value),
+            isFunc = tag == funcTag || tag == genTag;
+
+        if (isBuffer(value)) {
+          return cloneBuffer(value, isDeep);
+        }
+        if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
+          result = (isFlat || isFunc) ? {} : initCloneObject(value);
+          if (!isDeep) {
+            return isFlat
+              ? copySymbolsIn(value, baseAssignIn(result, value))
+              : copySymbols(value, baseAssign(result, value));
+          }
+        } else {
+          if (!cloneableTags[tag]) {
+            return object ? value : {};
+          }
+          result = initCloneByTag(value, tag, baseClone, isDeep);
+        }
+      }
+      // Check for circular references and return its corresponding clone.
+      stack || (stack = new Stack);
+      var stacked = stack.get(value);
+      if (stacked) {
+        return stacked;
+      }
+      stack.set(value, result);
+
+      var keysFunc = isFull
+        ? (isFlat ? getAllKeysIn : getAllKeys)
+        : (isFlat ? keysIn : keys);
+
+      var props = isArr ? undefined : keysFunc(value);
+      arrayEach(props || value, function(subValue, key) {
+        if (props) {
+          key = subValue;
+          subValue = value[key];
+        }
+        // Recursively populate clone (susceptible to call stack limits).
+        assignValue(result, key, baseClone(subValue, bitmask, customizer, key, value, stack));
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.conforms` which doesn't clone `source`.
+     *
+     * @private
+     * @param {Object} source The object of property predicates to conform to.
+     * @returns {Function} Returns the new spec function.
+     */
+    function baseConforms(source) {
+      var props = keys(source);
+      return function(object) {
+        return baseConformsTo(object, source, props);
+      };
+    }
+
+    /**
+     * The base implementation of `_.conformsTo` which accepts `props` to check.
+     *
+     * @private
+     * @param {Object} object The object to inspect.
+     * @param {Object} source The object of property predicates to conform to.
+     * @returns {boolean} Returns `true` if `object` conforms, else `false`.
+     */
+    function baseConformsTo(object, source, props) {
+      var length = props.length;
+      if (object == null) {
+        return !length;
+      }
+      object = Object(object);
+      while (length--) {
+        var key = props[length],
+            predicate = source[key],
+            value = object[key];
+
+        if ((value === undefined && !(key in object)) || !predicate(value)) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    /**
+     * The base implementation of `_.delay` and `_.defer` which accepts `args`
+     * to provide to `func`.
+     *
+     * @private
+     * @param {Function} func The function to delay.
+     * @param {number} wait The number of milliseconds to delay invocation.
+     * @param {Array} args The arguments to provide to `func`.
+     * @returns {number|Object} Returns the timer id or timeout object.
+     */
+    function baseDelay(func, wait, args) {
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      return setTimeout(function() { func.apply(undefined, args); }, wait);
+    }
+
+    /**
+     * The base implementation of methods like `_.difference` without support
+     * for excluding multiple arrays or iteratee shorthands.
+     *
+     * @private
+     * @param {Array} array The array to inspect.
+     * @param {Array} values The values to exclude.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of filtered values.
+     */
+    function baseDifference(array, values, iteratee, comparator) {
+      var index = -1,
+          includes = arrayIncludes,
+          isCommon = true,
+          length = array.length,
+          result = [],
+          valuesLength = values.length;
+
+      if (!length) {
+        return result;
+      }
+      if (iteratee) {
+        values = arrayMap(values, baseUnary(iteratee));
+      }
+      if (comparator) {
+        includes = arrayIncludesWith;
+        isCommon = false;
+      }
+      else if (values.length >= LARGE_ARRAY_SIZE) {
+        includes = cacheHas;
+        isCommon = false;
+        values = new SetCache(values);
+      }
+      outer:
+      while (++index < length) {
+        var value = array[index],
+            computed = iteratee == null ? value : iteratee(value);
+
+        value = (comparator || value !== 0) ? value : 0;
+        if (isCommon && computed === computed) {
+          var valuesIndex = valuesLength;
+          while (valuesIndex--) {
+            if (values[valuesIndex] === computed) {
+              continue outer;
+            }
+          }
+          result.push(value);
+        }
+        else if (!includes(values, computed, comparator)) {
+          result.push(value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.forEach` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Array|Object} Returns `collection`.
+     */
+    var baseEach = createBaseEach(baseForOwn);
+
+    /**
+     * The base implementation of `_.forEachRight` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Array|Object} Returns `collection`.
+     */
+    var baseEachRight = createBaseEach(baseForOwnRight, true);
+
+    /**
+     * The base implementation of `_.every` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} predicate The function invoked per iteration.
+     * @returns {boolean} Returns `true` if all elements pass the predicate check,
+     *  else `false`
+     */
+    function baseEvery(collection, predicate) {
+      var result = true;
+      baseEach(collection, function(value, index, collection) {
+        result = !!predicate(value, index, collection);
+        return result;
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of methods like `_.max` and `_.min` which accepts a
+     * `comparator` to determine the extremum value.
+     *
+     * @private
+     * @param {Array} array The array to iterate over.
+     * @param {Function} iteratee The iteratee invoked per iteration.
+     * @param {Function} comparator The comparator used to compare values.
+     * @returns {*} Returns the extremum value.
+     */
+    function baseExtremum(array, iteratee, comparator) {
+      var index = -1,
+          length = array.length;
+
+      while (++index < length) {
+        var value = array[index],
+            current = iteratee(value);
+
+        if (current != null && (computed === undefined
+              ? (current === current && !isSymbol(current))
+              : comparator(current, computed)
+            )) {
+          var computed = current,
+              result = value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.fill` without an iteratee call guard.
+     *
+     * @private
+     * @param {Array} array The array to fill.
+     * @param {*} value The value to fill `array` with.
+     * @param {number} [start=0] The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns `array`.
+     */
+    function baseFill(array, value, start, end) {
+      var length = array.length;
+
+      start = toInteger(start);
+      if (start < 0) {
+        start = -start > length ? 0 : (length + start);
+      }
+      end = (end === undefined || end > length) ? length : toInteger(end);
+      if (end < 0) {
+        end += length;
+      }
+      end = start > end ? 0 : toLength(end);
+      while (start < end) {
+        array[start++] = value;
+      }
+      return array;
+    }
+
+    /**
+     * The base implementation of `_.filter` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} predicate The function invoked per iteration.
+     * @returns {Array} Returns the new filtered array.
+     */
+    function baseFilter(collection, predicate) {
+      var result = [];
+      baseEach(collection, function(value, index, collection) {
+        if (predicate(value, index, collection)) {
+          result.push(value);
+        }
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.flatten` with support for restricting flattening.
+     *
+     * @private
+     * @param {Array} array The array to flatten.
+     * @param {number} depth The maximum recursion depth.
+     * @param {boolean} [predicate=isFlattenable] The function invoked per iteration.
+     * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks.
+     * @param {Array} [result=[]] The initial result value.
+     * @returns {Array} Returns the new flattened array.
+     */
+    function baseFlatten(array, depth, predicate, isStrict, result) {
+      var index = -1,
+          length = array.length;
+
+      predicate || (predicate = isFlattenable);
+      result || (result = []);
+
+      while (++index < length) {
+        var value = array[index];
+        if (depth > 0 && predicate(value)) {
+          if (depth > 1) {
+            // Recursively flatten arrays (susceptible to call stack limits).
+            baseFlatten(value, depth - 1, predicate, isStrict, result);
+          } else {
+            arrayPush(result, value);
+          }
+        } else if (!isStrict) {
+          result[result.length] = value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `baseForOwn` which iterates over `object`
+     * properties returned by `keysFunc` and invokes `iteratee` for each property.
+     * Iteratee functions may exit iteration early by explicitly returning `false`.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @param {Function} keysFunc The function to get the keys of `object`.
+     * @returns {Object} Returns `object`.
+     */
+    var baseFor = createBaseFor();
+
+    /**
+     * This function is like `baseFor` except that it iterates over properties
+     * in the opposite order.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @param {Function} keysFunc The function to get the keys of `object`.
+     * @returns {Object} Returns `object`.
+     */
+    var baseForRight = createBaseFor(true);
+
+    /**
+     * The base implementation of `_.forOwn` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     */
+    function baseForOwn(object, iteratee) {
+      return object && baseFor(object, iteratee, keys);
+    }
+
+    /**
+     * The base implementation of `_.forOwnRight` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     */
+    function baseForOwnRight(object, iteratee) {
+      return object && baseForRight(object, iteratee, keys);
+    }
+
+    /**
+     * The base implementation of `_.functions` which creates an array of
+     * `object` function property names filtered from `props`.
+     *
+     * @private
+     * @param {Object} object The object to inspect.
+     * @param {Array} props The property names to filter.
+     * @returns {Array} Returns the function names.
+     */
+    function baseFunctions(object, props) {
+      return arrayFilter(props, function(key) {
+        return isFunction(object[key]);
+      });
+    }
+
+    /**
+     * The base implementation of `_.get` without support for default values.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the property to get.
+     * @returns {*} Returns the resolved value.
+     */
+    function baseGet(object, path) {
+      path = castPath(path, object);
+
+      var index = 0,
+          length = path.length;
+
+      while (object != null && index < length) {
+        object = object[toKey(path[index++])];
+      }
+      return (index && index == length) ? object : undefined;
+    }
+
+    /**
+     * The base implementation of `getAllKeys` and `getAllKeysIn` which uses
+     * `keysFunc` and `symbolsFunc` to get the enumerable property names and
+     * symbols of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Function} keysFunc The function to get the keys of `object`.
+     * @param {Function} symbolsFunc The function to get the symbols of `object`.
+     * @returns {Array} Returns the array of property names and symbols.
+     */
+    function baseGetAllKeys(object, keysFunc, symbolsFunc) {
+      var result = keysFunc(object);
+      return isArray(object) ? result : arrayPush(result, symbolsFunc(object));
+    }
+
+    /**
+     * The base implementation of `getTag` without fallbacks for buggy environments.
+     *
+     * @private
+     * @param {*} value The value to query.
+     * @returns {string} Returns the `toStringTag`.
+     */
+    function baseGetTag(value) {
+      if (value == null) {
+        return value === undefined ? undefinedTag : nullTag;
+      }
+      return (symToStringTag && symToStringTag in Object(value))
+        ? getRawTag(value)
+        : objectToString(value);
+    }
+
+    /**
+     * The base implementation of `_.gt` which doesn't coerce arguments.
+     *
+     * @private
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is greater than `other`,
+     *  else `false`.
+     */
+    function baseGt(value, other) {
+      return value > other;
+    }
+
+    /**
+     * The base implementation of `_.has` without support for deep paths.
+     *
+     * @private
+     * @param {Object} [object] The object to query.
+     * @param {Array|string} key The key to check.
+     * @returns {boolean} Returns `true` if `key` exists, else `false`.
+     */
+    function baseHas(object, key) {
+      return object != null && hasOwnProperty.call(object, key);
+    }
+
+    /**
+     * The base implementation of `_.hasIn` without support for deep paths.
+     *
+     * @private
+     * @param {Object} [object] The object to query.
+     * @param {Array|string} key The key to check.
+     * @returns {boolean} Returns `true` if `key` exists, else `false`.
+     */
+    function baseHasIn(object, key) {
+      return object != null && key in Object(object);
+    }
+
+    /**
+     * The base implementation of `_.inRange` which doesn't coerce arguments.
+     *
+     * @private
+     * @param {number} number The number to check.
+     * @param {number} start The start of the range.
+     * @param {number} end The end of the range.
+     * @returns {boolean} Returns `true` if `number` is in the range, else `false`.
+     */
+    function baseInRange(number, start, end) {
+      return number >= nativeMin(start, end) && number < nativeMax(start, end);
+    }
+
+    /**
+     * The base implementation of methods like `_.intersection`, without support
+     * for iteratee shorthands, that accepts an array of arrays to inspect.
+     *
+     * @private
+     * @param {Array} arrays The arrays to inspect.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of shared values.
+     */
+    function baseIntersection(arrays, iteratee, comparator) {
+      var includes = comparator ? arrayIncludesWith : arrayIncludes,
+          length = arrays[0].length,
+          othLength = arrays.length,
+          othIndex = othLength,
+          caches = Array(othLength),
+          maxLength = Infinity,
+          result = [];
+
+      while (othIndex--) {
+        var array = arrays[othIndex];
+        if (othIndex && iteratee) {
+          array = arrayMap(array, baseUnary(iteratee));
+        }
+        maxLength = nativeMin(array.length, maxLength);
+        caches[othIndex] = !comparator && (iteratee || (length >= 120 && array.length >= 120))
+          ? new SetCache(othIndex && array)
+          : undefined;
+      }
+      array = arrays[0];
+
+      var index = -1,
+          seen = caches[0];
+
+      outer:
+      while (++index < length && result.length < maxLength) {
+        var value = array[index],
+            computed = iteratee ? iteratee(value) : value;
+
+        value = (comparator || value !== 0) ? value : 0;
+        if (!(seen
+              ? cacheHas(seen, computed)
+              : includes(result, computed, comparator)
+            )) {
+          othIndex = othLength;
+          while (--othIndex) {
+            var cache = caches[othIndex];
+            if (!(cache
+                  ? cacheHas(cache, computed)
+                  : includes(arrays[othIndex], computed, comparator))
+                ) {
+              continue outer;
+            }
+          }
+          if (seen) {
+            seen.push(computed);
+          }
+          result.push(value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.invert` and `_.invertBy` which inverts
+     * `object` with values transformed by `iteratee` and set by `setter`.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} setter The function to set `accumulator` values.
+     * @param {Function} iteratee The iteratee to transform values.
+     * @param {Object} accumulator The initial inverted object.
+     * @returns {Function} Returns `accumulator`.
+     */
+    function baseInverter(object, setter, iteratee, accumulator) {
+      baseForOwn(object, function(value, key, object) {
+        setter(accumulator, iteratee(value), key, object);
+      });
+      return accumulator;
+    }
+
+    /**
+     * The base implementation of `_.invoke` without support for individual
+     * method arguments.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the method to invoke.
+     * @param {Array} args The arguments to invoke the method with.
+     * @returns {*} Returns the result of the invoked method.
+     */
+    function baseInvoke(object, path, args) {
+      path = castPath(path, object);
+      object = parent(object, path);
+      var func = object == null ? object : object[toKey(last(path))];
+      return func == null ? undefined : apply(func, object, args);
+    }
+
+    /**
+     * The base implementation of `_.isArguments`.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an `arguments` object,
+     */
+    function baseIsArguments(value) {
+      return isObjectLike(value) && baseGetTag(value) == argsTag;
+    }
+
+    /**
+     * The base implementation of `_.isArrayBuffer` without Node.js optimizations.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`.
+     */
+    function baseIsArrayBuffer(value) {
+      return isObjectLike(value) && baseGetTag(value) == arrayBufferTag;
+    }
+
+    /**
+     * The base implementation of `_.isDate` without Node.js optimizations.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a date object, else `false`.
+     */
+    function baseIsDate(value) {
+      return isObjectLike(value) && baseGetTag(value) == dateTag;
+    }
+
+    /**
+     * The base implementation of `_.isEqual` which supports partial comparisons
+     * and tracks traversed objects.
+     *
+     * @private
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @param {boolean} bitmask The bitmask flags.
+     *  1 - Unordered comparison
+     *  2 - Partial comparison
+     * @param {Function} [customizer] The function to customize comparisons.
+     * @param {Object} [stack] Tracks traversed `value` and `other` objects.
+     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+     */
+    function baseIsEqual(value, other, bitmask, customizer, stack) {
+      if (value === other) {
+        return true;
+      }
+      if (value == null || other == null || (!isObjectLike(value) && !isObjectLike(other))) {
+        return value !== value && other !== other;
+      }
+      return baseIsEqualDeep(value, other, bitmask, customizer, baseIsEqual, stack);
+    }
+
+    /**
+     * A specialized version of `baseIsEqual` for arrays and objects which performs
+     * deep comparisons and tracks traversed objects enabling objects with circular
+     * references to be compared.
+     *
+     * @private
+     * @param {Object} object The object to compare.
+     * @param {Object} other The other object to compare.
+     * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
+     * @param {Function} customizer The function to customize comparisons.
+     * @param {Function} equalFunc The function to determine equivalents of values.
+     * @param {Object} [stack] Tracks traversed `object` and `other` objects.
+     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+     */
+    function baseIsEqualDeep(object, other, bitmask, customizer, equalFunc, stack) {
+      var objIsArr = isArray(object),
+          othIsArr = isArray(other),
+          objTag = objIsArr ? arrayTag : getTag(object),
+          othTag = othIsArr ? arrayTag : getTag(other);
+
+      objTag = objTag == argsTag ? objectTag : objTag;
+      othTag = othTag == argsTag ? objectTag : othTag;
+
+      var objIsObj = objTag == objectTag,
+          othIsObj = othTag == objectTag,
+          isSameTag = objTag == othTag;
+
+      if (isSameTag && isBuffer(object)) {
+        if (!isBuffer(other)) {
+          return false;
+        }
+        objIsArr = true;
+        objIsObj = false;
+      }
+      if (isSameTag && !objIsObj) {
+        stack || (stack = new Stack);
+        return (objIsArr || isTypedArray(object))
+          ? equalArrays(object, other, bitmask, customizer, equalFunc, stack)
+          : equalByTag(object, other, objTag, bitmask, customizer, equalFunc, stack);
+      }
+      if (!(bitmask & COMPARE_PARTIAL_FLAG)) {
+        var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
+            othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
+
+        if (objIsWrapped || othIsWrapped) {
+          var objUnwrapped = objIsWrapped ? object.value() : object,
+              othUnwrapped = othIsWrapped ? other.value() : other;
+
+          stack || (stack = new Stack);
+          return equalFunc(objUnwrapped, othUnwrapped, bitmask, customizer, stack);
+        }
+      }
+      if (!isSameTag) {
+        return false;
+      }
+      stack || (stack = new Stack);
+      return equalObjects(object, other, bitmask, customizer, equalFunc, stack);
+    }
+
+    /**
+     * The base implementation of `_.isMap` without Node.js optimizations.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a map, else `false`.
+     */
+    function baseIsMap(value) {
+      return isObjectLike(value) && getTag(value) == mapTag;
+    }
+
+    /**
+     * The base implementation of `_.isMatch` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Object} object The object to inspect.
+     * @param {Object} source The object of property values to match.
+     * @param {Array} matchData The property names, values, and compare flags to match.
+     * @param {Function} [customizer] The function to customize comparisons.
+     * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+     */
+    function baseIsMatch(object, source, matchData, customizer) {
+      var index = matchData.length,
+          length = index,
+          noCustomizer = !customizer;
+
+      if (object == null) {
+        return !length;
+      }
+      object = Object(object);
+      while (index--) {
+        var data = matchData[index];
+        if ((noCustomizer && data[2])
+              ? data[1] !== object[data[0]]
+              : !(data[0] in object)
+            ) {
+          return false;
+        }
+      }
+      while (++index < length) {
+        data = matchData[index];
+        var key = data[0],
+            objValue = object[key],
+            srcValue = data[1];
+
+        if (noCustomizer && data[2]) {
+          if (objValue === undefined && !(key in object)) {
+            return false;
+          }
+        } else {
+          var stack = new Stack;
+          if (customizer) {
+            var result = customizer(objValue, srcValue, key, object, source, stack);
+          }
+          if (!(result === undefined
+                ? baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG, customizer, stack)
+                : result
+              )) {
+            return false;
+          }
+        }
+      }
+      return true;
+    }
+
+    /**
+     * The base implementation of `_.isNative` without bad shim checks.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a native function,
+     *  else `false`.
+     */
+    function baseIsNative(value) {
+      if (!isObject(value) || isMasked(value)) {
+        return false;
+      }
+      var pattern = isFunction(value) ? reIsNative : reIsHostCtor;
+      return pattern.test(toSource(value));
+    }
+
+    /**
+     * The base implementation of `_.isRegExp` without Node.js optimizations.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a regexp, else `false`.
+     */
+    function baseIsRegExp(value) {
+      return isObjectLike(value) && baseGetTag(value) == regexpTag;
+    }
+
+    /**
+     * The base implementation of `_.isSet` without Node.js optimizations.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a set, else `false`.
+     */
+    function baseIsSet(value) {
+      return isObjectLike(value) && getTag(value) == setTag;
+    }
+
+    /**
+     * The base implementation of `_.isTypedArray` without Node.js optimizations.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
+     */
+    function baseIsTypedArray(value) {
+      return isObjectLike(value) &&
+        isLength(value.length) && !!typedArrayTags[baseGetTag(value)];
+    }
+
+    /**
+     * The base implementation of `_.iteratee`.
+     *
+     * @private
+     * @param {*} [value=_.identity] The value to convert to an iteratee.
+     * @returns {Function} Returns the iteratee.
+     */
+    function baseIteratee(value) {
+      // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9.
+      // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details.
+      if (typeof value == 'function') {
+        return value;
+      }
+      if (value == null) {
+        return identity;
+      }
+      if (typeof value == 'object') {
+        return isArray(value)
+          ? baseMatchesProperty(value[0], value[1])
+          : baseMatches(value);
+      }
+      return property(value);
+    }
+
+    /**
+     * The base implementation of `_.keys` which doesn't treat sparse arrays as dense.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names.
+     */
+    function baseKeys(object) {
+      if (!isPrototype(object)) {
+        return nativeKeys(object);
+      }
+      var result = [];
+      for (var key in Object(object)) {
+        if (hasOwnProperty.call(object, key) && key != 'constructor') {
+          result.push(key);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.keysIn` which doesn't treat sparse arrays as dense.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names.
+     */
+    function baseKeysIn(object) {
+      if (!isObject(object)) {
+        return nativeKeysIn(object);
+      }
+      var isProto = isPrototype(object),
+          result = [];
+
+      for (var key in object) {
+        if (!(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
+          result.push(key);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.lt` which doesn't coerce arguments.
+     *
+     * @private
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is less than `other`,
+     *  else `false`.
+     */
+    function baseLt(value, other) {
+      return value < other;
+    }
+
+    /**
+     * The base implementation of `_.map` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Array} Returns the new mapped array.
+     */
+    function baseMap(collection, iteratee) {
+      var index = -1,
+          result = isArrayLike(collection) ? Array(collection.length) : [];
+
+      baseEach(collection, function(value, key, collection) {
+        result[++index] = iteratee(value, key, collection);
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.matches` which doesn't clone `source`.
+     *
+     * @private
+     * @param {Object} source The object of property values to match.
+     * @returns {Function} Returns the new spec function.
+     */
+    function baseMatches(source) {
+      var matchData = getMatchData(source);
+      if (matchData.length == 1 && matchData[0][2]) {
+        return matchesStrictComparable(matchData[0][0], matchData[0][1]);
+      }
+      return function(object) {
+        return object === source || baseIsMatch(object, source, matchData);
+      };
+    }
+
+    /**
+     * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`.
+     *
+     * @private
+     * @param {string} path The path of the property to get.
+     * @param {*} srcValue The value to match.
+     * @returns {Function} Returns the new spec function.
+     */
+    function baseMatchesProperty(path, srcValue) {
+      if (isKey(path) && isStrictComparable(srcValue)) {
+        return matchesStrictComparable(toKey(path), srcValue);
+      }
+      return function(object) {
+        var objValue = get(object, path);
+        return (objValue === undefined && objValue === srcValue)
+          ? hasIn(object, path)
+          : baseIsEqual(srcValue, objValue, COMPARE_PARTIAL_FLAG | COMPARE_UNORDERED_FLAG);
+      };
+    }
+
+    /**
+     * The base implementation of `_.merge` without support for multiple sources.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @param {number} srcIndex The index of `source`.
+     * @param {Function} [customizer] The function to customize merged values.
+     * @param {Object} [stack] Tracks traversed source values and their merged
+     *  counterparts.
+     */
+    function baseMerge(object, source, srcIndex, customizer, stack) {
+      if (object === source) {
+        return;
+      }
+      baseFor(source, function(srcValue, key) {
+        if (isObject(srcValue)) {
+          stack || (stack = new Stack);
+          baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack);
+        }
+        else {
+          var newValue = customizer
+            ? customizer(object[key], srcValue, (key + ''), object, source, stack)
+            : undefined;
+
+          if (newValue === undefined) {
+            newValue = srcValue;
+          }
+          assignMergeValue(object, key, newValue);
+        }
+      }, keysIn);
+    }
+
+    /**
+     * A specialized version of `baseMerge` for arrays and objects which performs
+     * deep merges and tracks traversed objects enabling objects with circular
+     * references to be merged.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @param {string} key The key of the value to merge.
+     * @param {number} srcIndex The index of `source`.
+     * @param {Function} mergeFunc The function to merge values.
+     * @param {Function} [customizer] The function to customize assigned values.
+     * @param {Object} [stack] Tracks traversed source values and their merged
+     *  counterparts.
+     */
+    function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) {
+      var objValue = object[key],
+          srcValue = source[key],
+          stacked = stack.get(srcValue);
+
+      if (stacked) {
+        assignMergeValue(object, key, stacked);
+        return;
+      }
+      var newValue = customizer
+        ? customizer(objValue, srcValue, (key + ''), object, source, stack)
+        : undefined;
+
+      var isCommon = newValue === undefined;
+
+      if (isCommon) {
+        var isArr = isArray(srcValue),
+            isBuff = !isArr && isBuffer(srcValue),
+            isTyped = !isArr && !isBuff && isTypedArray(srcValue);
+
+        newValue = srcValue;
+        if (isArr || isBuff || isTyped) {
+          if (isArray(objValue)) {
+            newValue = objValue;
+          }
+          else if (isArrayLikeObject(objValue)) {
+            newValue = copyArray(objValue);
+          }
+          else if (isBuff) {
+            isCommon = false;
+            newValue = cloneBuffer(srcValue, true);
+          }
+          else if (isTyped) {
+            isCommon = false;
+            newValue = cloneTypedArray(srcValue, true);
+          }
+          else {
+            newValue = [];
+          }
+        }
+        else if (isPlainObject(srcValue) || isArguments(srcValue)) {
+          newValue = objValue;
+          if (isArguments(objValue)) {
+            newValue = toPlainObject(objValue);
+          }
+          else if (!isObject(objValue) || (srcIndex && isFunction(objValue))) {
+            newValue = initCloneObject(srcValue);
+          }
+        }
+        else {
+          isCommon = false;
+        }
+      }
+      if (isCommon) {
+        // Recursively merge objects and arrays (susceptible to call stack limits).
+        stack.set(srcValue, newValue);
+        mergeFunc(newValue, srcValue, srcIndex, customizer, stack);
+        stack['delete'](srcValue);
+      }
+      assignMergeValue(object, key, newValue);
+    }
+
+    /**
+     * The base implementation of `_.nth` which doesn't coerce arguments.
+     *
+     * @private
+     * @param {Array} array The array to query.
+     * @param {number} n The index of the element to return.
+     * @returns {*} Returns the nth element of `array`.
+     */
+    function baseNth(array, n) {
+      var length = array.length;
+      if (!length) {
+        return;
+      }
+      n += n < 0 ? length : 0;
+      return isIndex(n, length) ? array[n] : undefined;
+    }
+
+    /**
+     * The base implementation of `_.orderBy` without param guards.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.
+     * @param {string[]} orders The sort orders of `iteratees`.
+     * @returns {Array} Returns the new sorted array.
+     */
+    function baseOrderBy(collection, iteratees, orders) {
+      var index = -1;
+      iteratees = arrayMap(iteratees.length ? iteratees : [identity], baseUnary(getIteratee()));
+
+      var result = baseMap(collection, function(value, key, collection) {
+        var criteria = arrayMap(iteratees, function(iteratee) {
+          return iteratee(value);
+        });
+        return { 'criteria': criteria, 'index': ++index, 'value': value };
+      });
+
+      return baseSortBy(result, function(object, other) {
+        return compareMultiple(object, other, orders);
+      });
+    }
+
+    /**
+     * The base implementation of `_.pick` without support for individual
+     * property identifiers.
+     *
+     * @private
+     * @param {Object} object The source object.
+     * @param {string[]} paths The property paths to pick.
+     * @returns {Object} Returns the new object.
+     */
+    function basePick(object, paths) {
+      return basePickBy(object, paths, function(value, path) {
+        return hasIn(object, path);
+      });
+    }
+
+    /**
+     * The base implementation of  `_.pickBy` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Object} object The source object.
+     * @param {string[]} paths The property paths to pick.
+     * @param {Function} predicate The function invoked per property.
+     * @returns {Object} Returns the new object.
+     */
+    function basePickBy(object, paths, predicate) {
+      var index = -1,
+          length = paths.length,
+          result = {};
+
+      while (++index < length) {
+        var path = paths[index],
+            value = baseGet(object, path);
+
+        if (predicate(value, path)) {
+          baseSet(result, castPath(path, object), value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * A specialized version of `baseProperty` which supports deep paths.
+     *
+     * @private
+     * @param {Array|string} path The path of the property to get.
+     * @returns {Function} Returns the new accessor function.
+     */
+    function basePropertyDeep(path) {
+      return function(object) {
+        return baseGet(object, path);
+      };
+    }
+
+    /**
+     * The base implementation of `_.pullAllBy` without support for iteratee
+     * shorthands.
+     *
+     * @private
+     * @param {Array} array The array to modify.
+     * @param {Array} values The values to remove.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns `array`.
+     */
+    function basePullAll(array, values, iteratee, comparator) {
+      var indexOf = comparator ? baseIndexOfWith : baseIndexOf,
+          index = -1,
+          length = values.length,
+          seen = array;
+
+      if (array === values) {
+        values = copyArray(values);
+      }
+      if (iteratee) {
+        seen = arrayMap(array, baseUnary(iteratee));
+      }
+      while (++index < length) {
+        var fromIndex = 0,
+            value = values[index],
+            computed = iteratee ? iteratee(value) : value;
+
+        while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) {
+          if (seen !== array) {
+            splice.call(seen, fromIndex, 1);
+          }
+          splice.call(array, fromIndex, 1);
+        }
+      }
+      return array;
+    }
+
+    /**
+     * The base implementation of `_.pullAt` without support for individual
+     * indexes or capturing the removed elements.
+     *
+     * @private
+     * @param {Array} array The array to modify.
+     * @param {number[]} indexes The indexes of elements to remove.
+     * @returns {Array} Returns `array`.
+     */
+    function basePullAt(array, indexes) {
+      var length = array ? indexes.length : 0,
+          lastIndex = length - 1;
+
+      while (length--) {
+        var index = indexes[length];
+        if (length == lastIndex || index !== previous) {
+          var previous = index;
+          if (isIndex(index)) {
+            splice.call(array, index, 1);
+          } else {
+            baseUnset(array, index);
+          }
+        }
+      }
+      return array;
+    }
+
+    /**
+     * The base implementation of `_.random` without support for returning
+     * floating-point numbers.
+     *
+     * @private
+     * @param {number} lower The lower bound.
+     * @param {number} upper The upper bound.
+     * @returns {number} Returns the random number.
+     */
+    function baseRandom(lower, upper) {
+      return lower + nativeFloor(nativeRandom() * (upper - lower + 1));
+    }
+
+    /**
+     * The base implementation of `_.range` and `_.rangeRight` which doesn't
+     * coerce arguments.
+     *
+     * @private
+     * @param {number} start The start of the range.
+     * @param {number} end The end of the range.
+     * @param {number} step The value to increment or decrement by.
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Array} Returns the range of numbers.
+     */
+    function baseRange(start, end, step, fromRight) {
+      var index = -1,
+          length = nativeMax(nativeCeil((end - start) / (step || 1)), 0),
+          result = Array(length);
+
+      while (length--) {
+        result[fromRight ? length : ++index] = start;
+        start += step;
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.repeat` which doesn't coerce arguments.
+     *
+     * @private
+     * @param {string} string The string to repeat.
+     * @param {number} n The number of times to repeat the string.
+     * @returns {string} Returns the repeated string.
+     */
+    function baseRepeat(string, n) {
+      var result = '';
+      if (!string || n < 1 || n > MAX_SAFE_INTEGER) {
+        return result;
+      }
+      // Leverage the exponentiation by squaring algorithm for a faster repeat.
+      // See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more details.
+      do {
+        if (n % 2) {
+          result += string;
+        }
+        n = nativeFloor(n / 2);
+        if (n) {
+          string += string;
+        }
+      } while (n);
+
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.rest` which doesn't validate or coerce arguments.
+     *
+     * @private
+     * @param {Function} func The function to apply a rest parameter to.
+     * @param {number} [start=func.length-1] The start position of the rest parameter.
+     * @returns {Function} Returns the new function.
+     */
+    function baseRest(func, start) {
+      return setToString(overRest(func, start, identity), func + '');
+    }
+
+    /**
+     * The base implementation of `_.sample`.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to sample.
+     * @returns {*} Returns the random element.
+     */
+    function baseSample(collection) {
+      return arraySample(values(collection));
+    }
+
+    /**
+     * The base implementation of `_.sampleSize` without param guards.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to sample.
+     * @param {number} n The number of elements to sample.
+     * @returns {Array} Returns the random elements.
+     */
+    function baseSampleSize(collection, n) {
+      var array = values(collection);
+      return shuffleSelf(array, baseClamp(n, 0, array.length));
+    }
+
+    /**
+     * The base implementation of `_.set`.
+     *
+     * @private
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to set.
+     * @param {*} value The value to set.
+     * @param {Function} [customizer] The function to customize path creation.
+     * @returns {Object} Returns `object`.
+     */
+    function baseSet(object, path, value, customizer) {
+      if (!isObject(object)) {
+        return object;
+      }
+      path = castPath(path, object);
+
+      var index = -1,
+          length = path.length,
+          lastIndex = length - 1,
+          nested = object;
+
+      while (nested != null && ++index < length) {
+        var key = toKey(path[index]),
+            newValue = value;
+
+        if (index != lastIndex) {
+          var objValue = nested[key];
+          newValue = customizer ? customizer(objValue, key, nested) : undefined;
+          if (newValue === undefined) {
+            newValue = isObject(objValue)
+              ? objValue
+              : (isIndex(path[index + 1]) ? [] : {});
+          }
+        }
+        assignValue(nested, key, newValue);
+        nested = nested[key];
+      }
+      return object;
+    }
+
+    /**
+     * The base implementation of `setData` without support for hot loop shorting.
+     *
+     * @private
+     * @param {Function} func The function to associate metadata with.
+     * @param {*} data The metadata.
+     * @returns {Function} Returns `func`.
+     */
+    var baseSetData = !metaMap ? identity : function(func, data) {
+      metaMap.set(func, data);
+      return func;
+    };
+
+    /**
+     * The base implementation of `setToString` without support for hot loop shorting.
+     *
+     * @private
+     * @param {Function} func The function to modify.
+     * @param {Function} string The `toString` result.
+     * @returns {Function} Returns `func`.
+     */
+    var baseSetToString = !defineProperty ? identity : function(func, string) {
+      return defineProperty(func, 'toString', {
+        'configurable': true,
+        'enumerable': false,
+        'value': constant(string),
+        'writable': true
+      });
+    };
+
+    /**
+     * The base implementation of `_.shuffle`.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to shuffle.
+     * @returns {Array} Returns the new shuffled array.
+     */
+    function baseShuffle(collection) {
+      return shuffleSelf(values(collection));
+    }
+
+    /**
+     * The base implementation of `_.slice` without an iteratee call guard.
+     *
+     * @private
+     * @param {Array} array The array to slice.
+     * @param {number} [start=0] The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns the slice of `array`.
+     */
+    function baseSlice(array, start, end) {
+      var index = -1,
+          length = array.length;
+
+      if (start < 0) {
+        start = -start > length ? 0 : (length + start);
+      }
+      end = end > length ? length : end;
+      if (end < 0) {
+        end += length;
+      }
+      length = start > end ? 0 : ((end - start) >>> 0);
+      start >>>= 0;
+
+      var result = Array(length);
+      while (++index < length) {
+        result[index] = array[index + start];
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.some` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} predicate The function invoked per iteration.
+     * @returns {boolean} Returns `true` if any element passes the predicate check,
+     *  else `false`.
+     */
+    function baseSome(collection, predicate) {
+      var result;
+
+      baseEach(collection, function(value, index, collection) {
+        result = predicate(value, index, collection);
+        return !result;
+      });
+      return !!result;
+    }
+
+    /**
+     * The base implementation of `_.sortedIndex` and `_.sortedLastIndex` which
+     * performs a binary search of `array` to determine the index at which `value`
+     * should be inserted into `array` in order to maintain its sort order.
+     *
+     * @private
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {boolean} [retHighest] Specify returning the highest qualified index.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     */
+    function baseSortedIndex(array, value, retHighest) {
+      var low = 0,
+          high = array == null ? low : array.length;
+
+      if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) {
+        while (low < high) {
+          var mid = (low + high) >>> 1,
+              computed = array[mid];
+
+          if (computed !== null && !isSymbol(computed) &&
+              (retHighest ? (computed <= value) : (computed < value))) {
+            low = mid + 1;
+          } else {
+            high = mid;
+          }
+        }
+        return high;
+      }
+      return baseSortedIndexBy(array, value, identity, retHighest);
+    }
+
+    /**
+     * The base implementation of `_.sortedIndexBy` and `_.sortedLastIndexBy`
+     * which invokes `iteratee` for `value` and each element of `array` to compute
+     * their sort ranking. The iteratee is invoked with one argument; (value).
+     *
+     * @private
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {Function} iteratee The iteratee invoked per element.
+     * @param {boolean} [retHighest] Specify returning the highest qualified index.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     */
+    function baseSortedIndexBy(array, value, iteratee, retHighest) {
+      value = iteratee(value);
+
+      var low = 0,
+          high = array == null ? 0 : array.length,
+          valIsNaN = value !== value,
+          valIsNull = value === null,
+          valIsSymbol = isSymbol(value),
+          valIsUndefined = value === undefined;
+
+      while (low < high) {
+        var mid = nativeFloor((low + high) / 2),
+            computed = iteratee(array[mid]),
+            othIsDefined = computed !== undefined,
+            othIsNull = computed === null,
+            othIsReflexive = computed === computed,
+            othIsSymbol = isSymbol(computed);
+
+        if (valIsNaN) {
+          var setLow = retHighest || othIsReflexive;
+        } else if (valIsUndefined) {
+          setLow = othIsReflexive && (retHighest || othIsDefined);
+        } else if (valIsNull) {
+          setLow = othIsReflexive && othIsDefined && (retHighest || !othIsNull);
+        } else if (valIsSymbol) {
+          setLow = othIsReflexive && othIsDefined && !othIsNull && (retHighest || !othIsSymbol);
+        } else if (othIsNull || othIsSymbol) {
+          setLow = false;
+        } else {
+          setLow = retHighest ? (computed <= value) : (computed < value);
+        }
+        if (setLow) {
+          low = mid + 1;
+        } else {
+          high = mid;
+        }
+      }
+      return nativeMin(high, MAX_ARRAY_INDEX);
+    }
+
+    /**
+     * The base implementation of `_.sortedUniq` and `_.sortedUniqBy` without
+     * support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array} array The array to inspect.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @returns {Array} Returns the new duplicate free array.
+     */
+    function baseSortedUniq(array, iteratee) {
+      var index = -1,
+          length = array.length,
+          resIndex = 0,
+          result = [];
+
+      while (++index < length) {
+        var value = array[index],
+            computed = iteratee ? iteratee(value) : value;
+
+        if (!index || !eq(computed, seen)) {
+          var seen = computed;
+          result[resIndex++] = value === 0 ? 0 : value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.toNumber` which doesn't ensure correct
+     * conversions of binary, hexadecimal, or octal string values.
+     *
+     * @private
+     * @param {*} value The value to process.
+     * @returns {number} Returns the number.
+     */
+    function baseToNumber(value) {
+      if (typeof value == 'number') {
+        return value;
+      }
+      if (isSymbol(value)) {
+        return NAN;
+      }
+      return +value;
+    }
+
+    /**
+     * The base implementation of `_.toString` which doesn't convert nullish
+     * values to empty strings.
+     *
+     * @private
+     * @param {*} value The value to process.
+     * @returns {string} Returns the string.
+     */
+    function baseToString(value) {
+      // Exit early for strings to avoid a performance hit in some environments.
+      if (typeof value == 'string') {
+        return value;
+      }
+      if (isArray(value)) {
+        // Recursively convert values (susceptible to call stack limits).
+        return arrayMap(value, baseToString) + '';
+      }
+      if (isSymbol(value)) {
+        return symbolToString ? symbolToString.call(value) : '';
+      }
+      var result = (value + '');
+      return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
+    }
+
+    /**
+     * The base implementation of `_.uniqBy` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array} array The array to inspect.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new duplicate free array.
+     */
+    function baseUniq(array, iteratee, comparator) {
+      var index = -1,
+          includes = arrayIncludes,
+          length = array.length,
+          isCommon = true,
+          result = [],
+          seen = result;
+
+      if (comparator) {
+        isCommon = false;
+        includes = arrayIncludesWith;
+      }
+      else if (length >= LARGE_ARRAY_SIZE) {
+        var set = iteratee ? null : createSet(array);
+        if (set) {
+          return setToArray(set);
+        }
+        isCommon = false;
+        includes = cacheHas;
+        seen = new SetCache;
+      }
+      else {
+        seen = iteratee ? [] : result;
+      }
+      outer:
+      while (++index < length) {
+        var value = array[index],
+            computed = iteratee ? iteratee(value) : value;
+
+        value = (comparator || value !== 0) ? value : 0;
+        if (isCommon && computed === computed) {
+          var seenIndex = seen.length;
+          while (seenIndex--) {
+            if (seen[seenIndex] === computed) {
+              continue outer;
+            }
+          }
+          if (iteratee) {
+            seen.push(computed);
+          }
+          result.push(value);
+        }
+        else if (!includes(seen, computed, comparator)) {
+          if (seen !== result) {
+            seen.push(computed);
+          }
+          result.push(value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.unset`.
+     *
+     * @private
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The property path to unset.
+     * @returns {boolean} Returns `true` if the property is deleted, else `false`.
+     */
+    function baseUnset(object, path) {
+      path = castPath(path, object);
+      object = parent(object, path);
+      return object == null || delete object[toKey(last(path))];
+    }
+
+    /**
+     * The base implementation of `_.update`.
+     *
+     * @private
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to update.
+     * @param {Function} updater The function to produce the updated value.
+     * @param {Function} [customizer] The function to customize path creation.
+     * @returns {Object} Returns `object`.
+     */
+    function baseUpdate(object, path, updater, customizer) {
+      return baseSet(object, path, updater(baseGet(object, path)), customizer);
+    }
+
+    /**
+     * The base implementation of methods like `_.dropWhile` and `_.takeWhile`
+     * without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array} array The array to query.
+     * @param {Function} predicate The function invoked per iteration.
+     * @param {boolean} [isDrop] Specify dropping elements instead of taking them.
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Array} Returns the slice of `array`.
+     */
+    function baseWhile(array, predicate, isDrop, fromRight) {
+      var length = array.length,
+          index = fromRight ? length : -1;
+
+      while ((fromRight ? index-- : ++index < length) &&
+        predicate(array[index], index, array)) {}
+
+      return isDrop
+        ? baseSlice(array, (fromRight ? 0 : index), (fromRight ? index + 1 : length))
+        : baseSlice(array, (fromRight ? index + 1 : 0), (fromRight ? length : index));
+    }
+
+    /**
+     * The base implementation of `wrapperValue` which returns the result of
+     * performing a sequence of actions on the unwrapped `value`, where each
+     * successive action is supplied the return value of the previous.
+     *
+     * @private
+     * @param {*} value The unwrapped value.
+     * @param {Array} actions Actions to perform to resolve the unwrapped value.
+     * @returns {*} Returns the resolved value.
+     */
+    function baseWrapperValue(value, actions) {
+      var result = value;
+      if (result instanceof LazyWrapper) {
+        result = result.value();
+      }
+      return arrayReduce(actions, function(result, action) {
+        return action.func.apply(action.thisArg, arrayPush([result], action.args));
+      }, result);
+    }
+
+    /**
+     * The base implementation of methods like `_.xor`, without support for
+     * iteratee shorthands, that accepts an array of arrays to inspect.
+     *
+     * @private
+     * @param {Array} arrays The arrays to inspect.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of values.
+     */
+    function baseXor(arrays, iteratee, comparator) {
+      var length = arrays.length;
+      if (length < 2) {
+        return length ? baseUniq(arrays[0]) : [];
+      }
+      var index = -1,
+          result = Array(length);
+
+      while (++index < length) {
+        var array = arrays[index],
+            othIndex = -1;
+
+        while (++othIndex < length) {
+          if (othIndex != index) {
+            result[index] = baseDifference(result[index] || array, arrays[othIndex], iteratee, comparator);
+          }
+        }
+      }
+      return baseUniq(baseFlatten(result, 1), iteratee, comparator);
+    }
+
+    /**
+     * This base implementation of `_.zipObject` which assigns values using `assignFunc`.
+     *
+     * @private
+     * @param {Array} props The property identifiers.
+     * @param {Array} values The property values.
+     * @param {Function} assignFunc The function to assign values.
+     * @returns {Object} Returns the new object.
+     */
+    function baseZipObject(props, values, assignFunc) {
+      var index = -1,
+          length = props.length,
+          valsLength = values.length,
+          result = {};
+
+      while (++index < length) {
+        var value = index < valsLength ? values[index] : undefined;
+        assignFunc(result, props[index], value);
+      }
+      return result;
+    }
+
+    /**
+     * Casts `value` to an empty array if it's not an array like object.
+     *
+     * @private
+     * @param {*} value The value to inspect.
+     * @returns {Array|Object} Returns the cast array-like object.
+     */
+    function castArrayLikeObject(value) {
+      return isArrayLikeObject(value) ? value : [];
+    }
+
+    /**
+     * Casts `value` to `identity` if it's not a function.
+     *
+     * @private
+     * @param {*} value The value to inspect.
+     * @returns {Function} Returns cast function.
+     */
+    function castFunction(value) {
+      return typeof value == 'function' ? value : identity;
+    }
+
+    /**
+     * Casts `value` to a path array if it's not one.
+     *
+     * @private
+     * @param {*} value The value to inspect.
+     * @param {Object} [object] The object to query keys on.
+     * @returns {Array} Returns the cast property path array.
+     */
+    function castPath(value, object) {
+      if (isArray(value)) {
+        return value;
+      }
+      return isKey(value, object) ? [value] : stringToPath(toString(value));
+    }
+
+    /**
+     * A `baseRest` alias which can be replaced with `identity` by module
+     * replacement plugins.
+     *
+     * @private
+     * @type {Function}
+     * @param {Function} func The function to apply a rest parameter to.
+     * @returns {Function} Returns the new function.
+     */
+    var castRest = baseRest;
+
+    /**
+     * Casts `array` to a slice if it's needed.
+     *
+     * @private
+     * @param {Array} array The array to inspect.
+     * @param {number} start The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns the cast slice.
+     */
+    function castSlice(array, start, end) {
+      var length = array.length;
+      end = end === undefined ? length : end;
+      return (!start && end >= length) ? array : baseSlice(array, start, end);
+    }
+
+    /**
+     * A simple wrapper around the global [`clearTimeout`](https://mdn.io/clearTimeout).
+     *
+     * @private
+     * @param {number|Object} id The timer id or timeout object of the timer to clear.
+     */
+    var clearTimeout = ctxClearTimeout || function(id) {
+      return root.clearTimeout(id);
+    };
+
+    /**
+     * Creates a clone of  `buffer`.
+     *
+     * @private
+     * @param {Buffer} buffer The buffer to clone.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Buffer} Returns the cloned buffer.
+     */
+    function cloneBuffer(buffer, isDeep) {
+      if (isDeep) {
+        return buffer.slice();
+      }
+      var length = buffer.length,
+          result = allocUnsafe ? allocUnsafe(length) : new buffer.constructor(length);
+
+      buffer.copy(result);
+      return result;
+    }
+
+    /**
+     * Creates a clone of `arrayBuffer`.
+     *
+     * @private
+     * @param {ArrayBuffer} arrayBuffer The array buffer to clone.
+     * @returns {ArrayBuffer} Returns the cloned array buffer.
+     */
+    function cloneArrayBuffer(arrayBuffer) {
+      var result = new arrayBuffer.constructor(arrayBuffer.byteLength);
+      new Uint8Array(result).set(new Uint8Array(arrayBuffer));
+      return result;
+    }
+
+    /**
+     * Creates a clone of `dataView`.
+     *
+     * @private
+     * @param {Object} dataView The data view to clone.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the cloned data view.
+     */
+    function cloneDataView(dataView, isDeep) {
+      var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer;
+      return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength);
+    }
+
+    /**
+     * Creates a clone of `map`.
+     *
+     * @private
+     * @param {Object} map The map to clone.
+     * @param {Function} cloneFunc The function to clone values.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the cloned map.
+     */
+    function cloneMap(map, isDeep, cloneFunc) {
+      var array = isDeep ? cloneFunc(mapToArray(map), CLONE_DEEP_FLAG) : mapToArray(map);
+      return arrayReduce(array, addMapEntry, new map.constructor);
+    }
+
+    /**
+     * Creates a clone of `regexp`.
+     *
+     * @private
+     * @param {Object} regexp The regexp to clone.
+     * @returns {Object} Returns the cloned regexp.
+     */
+    function cloneRegExp(regexp) {
+      var result = new regexp.constructor(regexp.source, reFlags.exec(regexp));
+      result.lastIndex = regexp.lastIndex;
+      return result;
+    }
+
+    /**
+     * Creates a clone of `set`.
+     *
+     * @private
+     * @param {Object} set The set to clone.
+     * @param {Function} cloneFunc The function to clone values.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the cloned set.
+     */
+    function cloneSet(set, isDeep, cloneFunc) {
+      var array = isDeep ? cloneFunc(setToArray(set), CLONE_DEEP_FLAG) : setToArray(set);
+      return arrayReduce(array, addSetEntry, new set.constructor);
+    }
+
+    /**
+     * Creates a clone of the `symbol` object.
+     *
+     * @private
+     * @param {Object} symbol The symbol object to clone.
+     * @returns {Object} Returns the cloned symbol object.
+     */
+    function cloneSymbol(symbol) {
+      return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {};
+    }
+
+    /**
+     * Creates a clone of `typedArray`.
+     *
+     * @private
+     * @param {Object} typedArray The typed array to clone.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the cloned typed array.
+     */
+    function cloneTypedArray(typedArray, isDeep) {
+      var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer;
+      return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length);
+    }
+
+    /**
+     * Compares values to sort them in ascending order.
+     *
+     * @private
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {number} Returns the sort order indicator for `value`.
+     */
+    function compareAscending(value, other) {
+      if (value !== other) {
+        var valIsDefined = value !== undefined,
+            valIsNull = value === null,
+            valIsReflexive = value === value,
+            valIsSymbol = isSymbol(value);
+
+        var othIsDefined = other !== undefined,
+            othIsNull = other === null,
+            othIsReflexive = other === other,
+            othIsSymbol = isSymbol(other);
+
+        if ((!othIsNull && !othIsSymbol && !valIsSymbol && value > other) ||
+            (valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol) ||
+            (valIsNull && othIsDefined && othIsReflexive) ||
+            (!valIsDefined && othIsReflexive) ||
+            !valIsReflexive) {
+          return 1;
+        }
+        if ((!valIsNull && !valIsSymbol && !othIsSymbol && value < other) ||
+            (othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol) ||
+            (othIsNull && valIsDefined && valIsReflexive) ||
+            (!othIsDefined && valIsReflexive) ||
+            !othIsReflexive) {
+          return -1;
+        }
+      }
+      return 0;
+    }
+
+    /**
+     * Used by `_.orderBy` to compare multiple properties of a value to another
+     * and stable sort them.
+     *
+     * If `orders` is unspecified, all values are sorted in ascending order. Otherwise,
+     * specify an order of "desc" for descending or "asc" for ascending sort order
+     * of corresponding values.
+     *
+     * @private
+     * @param {Object} object The object to compare.
+     * @param {Object} other The other object to compare.
+     * @param {boolean[]|string[]} orders The order to sort by for each property.
+     * @returns {number} Returns the sort order indicator for `object`.
+     */
+    function compareMultiple(object, other, orders) {
+      var index = -1,
+          objCriteria = object.criteria,
+          othCriteria = other.criteria,
+          length = objCriteria.length,
+          ordersLength = orders.length;
+
+      while (++index < length) {
+        var result = compareAscending(objCriteria[index], othCriteria[index]);
+        if (result) {
+          if (index >= ordersLength) {
+            return result;
+          }
+          var order = orders[index];
+          return result * (order == 'desc' ? -1 : 1);
+        }
+      }
+      // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
+      // that causes it, under certain circumstances, to provide the same value for
+      // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247
+      // for more details.
+      //
+      // This also ensures a stable sort in V8 and other engines.
+      // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details.
+      return object.index - other.index;
+    }
+
+    /**
+     * Creates an array that is the composition of partially applied arguments,
+     * placeholders, and provided arguments into a single array of arguments.
+     *
+     * @private
+     * @param {Array} args The provided arguments.
+     * @param {Array} partials The arguments to prepend to those provided.
+     * @param {Array} holders The `partials` placeholder indexes.
+     * @params {boolean} [isCurried] Specify composing for a curried function.
+     * @returns {Array} Returns the new array of composed arguments.
+     */
+    function composeArgs(args, partials, holders, isCurried) {
+      var argsIndex = -1,
+          argsLength = args.length,
+          holdersLength = holders.length,
+          leftIndex = -1,
+          leftLength = partials.length,
+          rangeLength = nativeMax(argsLength - holdersLength, 0),
+          result = Array(leftLength + rangeLength),
+          isUncurried = !isCurried;
+
+      while (++leftIndex < leftLength) {
+        result[leftIndex] = partials[leftIndex];
+      }
+      while (++argsIndex < holdersLength) {
+        if (isUncurried || argsIndex < argsLength) {
+          result[holders[argsIndex]] = args[argsIndex];
+        }
+      }
+      while (rangeLength--) {
+        result[leftIndex++] = args[argsIndex++];
+      }
+      return result;
+    }
+
+    /**
+     * This function is like `composeArgs` except that the arguments composition
+     * is tailored for `_.partialRight`.
+     *
+     * @private
+     * @param {Array} args The provided arguments.
+     * @param {Array} partials The arguments to append to those provided.
+     * @param {Array} holders The `partials` placeholder indexes.
+     * @params {boolean} [isCurried] Specify composing for a curried function.
+     * @returns {Array} Returns the new array of composed arguments.
+     */
+    function composeArgsRight(args, partials, holders, isCurried) {
+      var argsIndex = -1,
+          argsLength = args.length,
+          holdersIndex = -1,
+          holdersLength = holders.length,
+          rightIndex = -1,
+          rightLength = partials.length,
+          rangeLength = nativeMax(argsLength - holdersLength, 0),
+          result = Array(rangeLength + rightLength),
+          isUncurried = !isCurried;
+
+      while (++argsIndex < rangeLength) {
+        result[argsIndex] = args[argsIndex];
+      }
+      var offset = argsIndex;
+      while (++rightIndex < rightLength) {
+        result[offset + rightIndex] = partials[rightIndex];
+      }
+      while (++holdersIndex < holdersLength) {
+        if (isUncurried || argsIndex < argsLength) {
+          result[offset + holders[holdersIndex]] = args[argsIndex++];
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Copies the values of `source` to `array`.
+     *
+     * @private
+     * @param {Array} source The array to copy values from.
+     * @param {Array} [array=[]] The array to copy values to.
+     * @returns {Array} Returns `array`.
+     */
+    function copyArray(source, array) {
+      var index = -1,
+          length = source.length;
+
+      array || (array = Array(length));
+      while (++index < length) {
+        array[index] = source[index];
+      }
+      return array;
+    }
+
+    /**
+     * Copies properties of `source` to `object`.
+     *
+     * @private
+     * @param {Object} source The object to copy properties from.
+     * @param {Array} props The property identifiers to copy.
+     * @param {Object} [object={}] The object to copy properties to.
+     * @param {Function} [customizer] The function to customize copied values.
+     * @returns {Object} Returns `object`.
+     */
+    function copyObject(source, props, object, customizer) {
+      var isNew = !object;
+      object || (object = {});
+
+      var index = -1,
+          length = props.length;
+
+      while (++index < length) {
+        var key = props[index];
+
+        var newValue = customizer
+          ? customizer(object[key], source[key], key, object, source)
+          : undefined;
+
+        if (newValue === undefined) {
+          newValue = source[key];
+        }
+        if (isNew) {
+          baseAssignValue(object, key, newValue);
+        } else {
+          assignValue(object, key, newValue);
+        }
+      }
+      return object;
+    }
+
+    /**
+     * Copies own symbols of `source` to `object`.
+     *
+     * @private
+     * @param {Object} source The object to copy symbols from.
+     * @param {Object} [object={}] The object to copy symbols to.
+     * @returns {Object} Returns `object`.
+     */
+    function copySymbols(source, object) {
+      return copyObject(source, getSymbols(source), object);
+    }
+
+    /**
+     * Copies own and inherited symbols of `source` to `object`.
+     *
+     * @private
+     * @param {Object} source The object to copy symbols from.
+     * @param {Object} [object={}] The object to copy symbols to.
+     * @returns {Object} Returns `object`.
+     */
+    function copySymbolsIn(source, object) {
+      return copyObject(source, getSymbolsIn(source), object);
+    }
+
+    /**
+     * Creates a function like `_.groupBy`.
+     *
+     * @private
+     * @param {Function} setter The function to set accumulator values.
+     * @param {Function} [initializer] The accumulator object initializer.
+     * @returns {Function} Returns the new aggregator function.
+     */
+    function createAggregator(setter, initializer) {
+      return function(collection, iteratee) {
+        var func = isArray(collection) ? arrayAggregator : baseAggregator,
+            accumulator = initializer ? initializer() : {};
+
+        return func(collection, setter, getIteratee(iteratee, 2), accumulator);
+      };
+    }
+
+    /**
+     * Creates a function like `_.assign`.
+     *
+     * @private
+     * @param {Function} assigner The function to assign values.
+     * @returns {Function} Returns the new assigner function.
+     */
+    function createAssigner(assigner) {
+      return baseRest(function(object, sources) {
+        var index = -1,
+            length = sources.length,
+            customizer = length > 1 ? sources[length - 1] : undefined,
+            guard = length > 2 ? sources[2] : undefined;
+
+        customizer = (assigner.length > 3 && typeof customizer == 'function')
+          ? (length--, customizer)
+          : undefined;
+
+        if (guard && isIterateeCall(sources[0], sources[1], guard)) {
+          customizer = length < 3 ? undefined : customizer;
+          length = 1;
+        }
+        object = Object(object);
+        while (++index < length) {
+          var source = sources[index];
+          if (source) {
+            assigner(object, source, index, customizer);
+          }
+        }
+        return object;
+      });
+    }
+
+    /**
+     * Creates a `baseEach` or `baseEachRight` function.
+     *
+     * @private
+     * @param {Function} eachFunc The function to iterate over a collection.
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new base function.
+     */
+    function createBaseEach(eachFunc, fromRight) {
+      return function(collection, iteratee) {
+        if (collection == null) {
+          return collection;
+        }
+        if (!isArrayLike(collection)) {
+          return eachFunc(collection, iteratee);
+        }
+        var length = collection.length,
+            index = fromRight ? length : -1,
+            iterable = Object(collection);
+
+        while ((fromRight ? index-- : ++index < length)) {
+          if (iteratee(iterable[index], index, iterable) === false) {
+            break;
+          }
+        }
+        return collection;
+      };
+    }
+
+    /**
+     * Creates a base function for methods like `_.forIn` and `_.forOwn`.
+     *
+     * @private
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new base function.
+     */
+    function createBaseFor(fromRight) {
+      return function(object, iteratee, keysFunc) {
+        var index = -1,
+            iterable = Object(object),
+            props = keysFunc(object),
+            length = props.length;
+
+        while (length--) {
+          var key = props[fromRight ? length : ++index];
+          if (iteratee(iterable[key], key, iterable) === false) {
+            break;
+          }
+        }
+        return object;
+      };
+    }
+
+    /**
+     * Creates a function that wraps `func` to invoke it with the optional `this`
+     * binding of `thisArg`.
+     *
+     * @private
+     * @param {Function} func The function to wrap.
+     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createBind(func, bitmask, thisArg) {
+      var isBind = bitmask & WRAP_BIND_FLAG,
+          Ctor = createCtor(func);
+
+      function wrapper() {
+        var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+        return fn.apply(isBind ? thisArg : this, arguments);
+      }
+      return wrapper;
+    }
+
+    /**
+     * Creates a function like `_.lowerFirst`.
+     *
+     * @private
+     * @param {string} methodName The name of the `String` case method to use.
+     * @returns {Function} Returns the new case function.
+     */
+    function createCaseFirst(methodName) {
+      return function(string) {
+        string = toString(string);
+
+        var strSymbols = hasUnicode(string)
+          ? stringToArray(string)
+          : undefined;
+
+        var chr = strSymbols
+          ? strSymbols[0]
+          : string.charAt(0);
+
+        var trailing = strSymbols
+          ? castSlice(strSymbols, 1).join('')
+          : string.slice(1);
+
+        return chr[methodName]() + trailing;
+      };
+    }
+
+    /**
+     * Creates a function like `_.camelCase`.
+     *
+     * @private
+     * @param {Function} callback The function to combine each word.
+     * @returns {Function} Returns the new compounder function.
+     */
+    function createCompounder(callback) {
+      return function(string) {
+        return arrayReduce(words(deburr(string).replace(reApos, '')), callback, '');
+      };
+    }
+
+    /**
+     * Creates a function that produces an instance of `Ctor` regardless of
+     * whether it was invoked as part of a `new` expression or by `call` or `apply`.
+     *
+     * @private
+     * @param {Function} Ctor The constructor to wrap.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createCtor(Ctor) {
+      return function() {
+        // Use a `switch` statement to work with class constructors. See
+        // http://ecma-international.org/ecma-262/7.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist
+        // for more details.
+        var args = arguments;
+        switch (args.length) {
+          case 0: return new Ctor;
+          case 1: return new Ctor(args[0]);
+          case 2: return new Ctor(args[0], args[1]);
+          case 3: return new Ctor(args[0], args[1], args[2]);
+          case 4: return new Ctor(args[0], args[1], args[2], args[3]);
+          case 5: return new Ctor(args[0], args[1], args[2], args[3], args[4]);
+          case 6: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]);
+          case 7: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
+        }
+        var thisBinding = baseCreate(Ctor.prototype),
+            result = Ctor.apply(thisBinding, args);
+
+        // Mimic the constructor's `return` behavior.
+        // See https://es5.github.io/#x13.2.2 for more details.
+        return isObject(result) ? result : thisBinding;
+      };
+    }
+
+    /**
+     * Creates a function that wraps `func` to enable currying.
+     *
+     * @private
+     * @param {Function} func The function to wrap.
+     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+     * @param {number} arity The arity of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createCurry(func, bitmask, arity) {
+      var Ctor = createCtor(func);
+
+      function wrapper() {
+        var length = arguments.length,
+            args = Array(length),
+            index = length,
+            placeholder = getHolder(wrapper);
+
+        while (index--) {
+          args[index] = arguments[index];
+        }
+        var holders = (length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder)
+          ? []
+          : replaceHolders(args, placeholder);
+
+        length -= holders.length;
+        if (length < arity) {
+          return createRecurry(
+            func, bitmask, createHybrid, wrapper.placeholder, undefined,
+            args, holders, undefined, undefined, arity - length);
+        }
+        var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+        return apply(fn, this, args);
+      }
+      return wrapper;
+    }
+
+    /**
+     * Creates a `_.find` or `_.findLast` function.
+     *
+     * @private
+     * @param {Function} findIndexFunc The function to find the collection index.
+     * @returns {Function} Returns the new find function.
+     */
+    function createFind(findIndexFunc) {
+      return function(collection, predicate, fromIndex) {
+        var iterable = Object(collection);
+        if (!isArrayLike(collection)) {
+          var iteratee = getIteratee(predicate, 3);
+          collection = keys(collection);
+          predicate = function(key) { return iteratee(iterable[key], key, iterable); };
+        }
+        var index = findIndexFunc(collection, predicate, fromIndex);
+        return index > -1 ? iterable[iteratee ? collection[index] : index] : undefined;
+      };
+    }
+
+    /**
+     * Creates a `_.flow` or `_.flowRight` function.
+     *
+     * @private
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new flow function.
+     */
+    function createFlow(fromRight) {
+      return flatRest(function(funcs) {
+        var length = funcs.length,
+            index = length,
+            prereq = LodashWrapper.prototype.thru;
+
+        if (fromRight) {
+          funcs.reverse();
+        }
+        while (index--) {
+          var func = funcs[index];
+          if (typeof func != 'function') {
+            throw new TypeError(FUNC_ERROR_TEXT);
+          }
+          if (prereq && !wrapper && getFuncName(func) == 'wrapper') {
+            var wrapper = new LodashWrapper([], true);
+          }
+        }
+        index = wrapper ? index : length;
+        while (++index < length) {
+          func = funcs[index];
+
+          var funcName = getFuncName(func),
+              data = funcName == 'wrapper' ? getData(func) : undefined;
+
+          if (data && isLaziable(data[0]) &&
+                data[1] == (WRAP_ARY_FLAG | WRAP_CURRY_FLAG | WRAP_PARTIAL_FLAG | WRAP_REARG_FLAG) &&
+                !data[4].length && data[9] == 1
+              ) {
+            wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]);
+          } else {
+            wrapper = (func.length == 1 && isLaziable(func))
+              ? wrapper[funcName]()
+              : wrapper.thru(func);
+          }
+        }
+        return function() {
+          var args = arguments,
+              value = args[0];
+
+          if (wrapper && args.length == 1 && isArray(value)) {
+            return wrapper.plant(value).value();
+          }
+          var index = 0,
+              result = length ? funcs[index].apply(this, args) : value;
+
+          while (++index < length) {
+            result = funcs[index].call(this, result);
+          }
+          return result;
+        };
+      });
+    }
+
+    /**
+     * Creates a function that wraps `func` to invoke it with optional `this`
+     * binding of `thisArg`, partial application, and currying.
+     *
+     * @private
+     * @param {Function|string} func The function or method name to wrap.
+     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {Array} [partials] The arguments to prepend to those provided to
+     *  the new function.
+     * @param {Array} [holders] The `partials` placeholder indexes.
+     * @param {Array} [partialsRight] The arguments to append to those provided
+     *  to the new function.
+     * @param {Array} [holdersRight] The `partialsRight` placeholder indexes.
+     * @param {Array} [argPos] The argument positions of the new function.
+     * @param {number} [ary] The arity cap of `func`.
+     * @param {number} [arity] The arity of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createHybrid(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) {
+      var isAry = bitmask & WRAP_ARY_FLAG,
+          isBind = bitmask & WRAP_BIND_FLAG,
+          isBindKey = bitmask & WRAP_BIND_KEY_FLAG,
+          isCurried = bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG),
+          isFlip = bitmask & WRAP_FLIP_FLAG,
+          Ctor = isBindKey ? undefined : createCtor(func);
+
+      function wrapper() {
+        var length = arguments.length,
+            args = Array(length),
+            index = length;
+
+        while (index--) {
+          args[index] = arguments[index];
+        }
+        if (isCurried) {
+          var placeholder = getHolder(wrapper),
+              holdersCount = countHolders(args, placeholder);
+        }
+        if (partials) {
+          args = composeArgs(args, partials, holders, isCurried);
+        }
+        if (partialsRight) {
+          args = composeArgsRight(args, partialsRight, holdersRight, isCurried);
+        }
+        length -= holdersCount;
+        if (isCurried && length < arity) {
+          var newHolders = replaceHolders(args, placeholder);
+          return createRecurry(
+            func, bitmask, createHybrid, wrapper.placeholder, thisArg,
+            args, newHolders, argPos, ary, arity - length
+          );
+        }
+        var thisBinding = isBind ? thisArg : this,
+            fn = isBindKey ? thisBinding[func] : func;
+
+        length = args.length;
+        if (argPos) {
+          args = reorder(args, argPos);
+        } else if (isFlip && length > 1) {
+          args.reverse();
+        }
+        if (isAry && ary < length) {
+          args.length = ary;
+        }
+        if (this && this !== root && this instanceof wrapper) {
+          fn = Ctor || createCtor(fn);
+        }
+        return fn.apply(thisBinding, args);
+      }
+      return wrapper;
+    }
+
+    /**
+     * Creates a function like `_.invertBy`.
+     *
+     * @private
+     * @param {Function} setter The function to set accumulator values.
+     * @param {Function} toIteratee The function to resolve iteratees.
+     * @returns {Function} Returns the new inverter function.
+     */
+    function createInverter(setter, toIteratee) {
+      return function(object, iteratee) {
+        return baseInverter(object, setter, toIteratee(iteratee), {});
+      };
+    }
+
+    /**
+     * Creates a function that performs a mathematical operation on two values.
+     *
+     * @private
+     * @param {Function} operator The function to perform the operation.
+     * @param {number} [defaultValue] The value used for `undefined` arguments.
+     * @returns {Function} Returns the new mathematical operation function.
+     */
+    function createMathOperation(operator, defaultValue) {
+      return function(value, other) {
+        var result;
+        if (value === undefined && other === undefined) {
+          return defaultValue;
+        }
+        if (value !== undefined) {
+          result = value;
+        }
+        if (other !== undefined) {
+          if (result === undefined) {
+            return other;
+          }
+          if (typeof value == 'string' || typeof other == 'string') {
+            value = baseToString(value);
+            other = baseToString(other);
+          } else {
+            value = baseToNumber(value);
+            other = baseToNumber(other);
+          }
+          result = operator(value, other);
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Creates a function like `_.over`.
+     *
+     * @private
+     * @param {Function} arrayFunc The function to iterate over iteratees.
+     * @returns {Function} Returns the new over function.
+     */
+    function createOver(arrayFunc) {
+      return flatRest(function(iteratees) {
+        iteratees = arrayMap(iteratees, baseUnary(getIteratee()));
+        return baseRest(function(args) {
+          var thisArg = this;
+          return arrayFunc(iteratees, function(iteratee) {
+            return apply(iteratee, thisArg, args);
+          });
+        });
+      });
+    }
+
+    /**
+     * Creates the padding for `string` based on `length`. The `chars` string
+     * is truncated if the number of characters exceeds `length`.
+     *
+     * @private
+     * @param {number} length The padding length.
+     * @param {string} [chars=' '] The string used as padding.
+     * @returns {string} Returns the padding for `string`.
+     */
+    function createPadding(length, chars) {
+      chars = chars === undefined ? ' ' : baseToString(chars);
+
+      var charsLength = chars.length;
+      if (charsLength < 2) {
+        return charsLength ? baseRepeat(chars, length) : chars;
+      }
+      var result = baseRepeat(chars, nativeCeil(length / stringSize(chars)));
+      return hasUnicode(chars)
+        ? castSlice(stringToArray(result), 0, length).join('')
+        : result.slice(0, length);
+    }
+
+    /**
+     * Creates a function that wraps `func` to invoke it with the `this` binding
+     * of `thisArg` and `partials` prepended to the arguments it receives.
+     *
+     * @private
+     * @param {Function} func The function to wrap.
+     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+     * @param {*} thisArg The `this` binding of `func`.
+     * @param {Array} partials The arguments to prepend to those provided to
+     *  the new function.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createPartial(func, bitmask, thisArg, partials) {
+      var isBind = bitmask & WRAP_BIND_FLAG,
+          Ctor = createCtor(func);
+
+      function wrapper() {
+        var argsIndex = -1,
+            argsLength = arguments.length,
+            leftIndex = -1,
+            leftLength = partials.length,
+            args = Array(leftLength + argsLength),
+            fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+
+        while (++leftIndex < leftLength) {
+          args[leftIndex] = partials[leftIndex];
+        }
+        while (argsLength--) {
+          args[leftIndex++] = arguments[++argsIndex];
+        }
+        return apply(fn, isBind ? thisArg : this, args);
+      }
+      return wrapper;
+    }
+
+    /**
+     * Creates a `_.range` or `_.rangeRight` function.
+     *
+     * @private
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new range function.
+     */
+    function createRange(fromRight) {
+      return function(start, end, step) {
+        if (step && typeof step != 'number' && isIterateeCall(start, end, step)) {
+          end = step = undefined;
+        }
+        // Ensure the sign of `-0` is preserved.
+        start = toFinite(start);
+        if (end === undefined) {
+          end = start;
+          start = 0;
+        } else {
+          end = toFinite(end);
+        }
+        step = step === undefined ? (start < end ? 1 : -1) : toFinite(step);
+        return baseRange(start, end, step, fromRight);
+      };
+    }
+
+    /**
+     * Creates a function that performs a relational operation on two values.
+     *
+     * @private
+     * @param {Function} operator The function to perform the operation.
+     * @returns {Function} Returns the new relational operation function.
+     */
+    function createRelationalOperation(operator) {
+      return function(value, other) {
+        if (!(typeof value == 'string' && typeof other == 'string')) {
+          value = toNumber(value);
+          other = toNumber(other);
+        }
+        return operator(value, other);
+      };
+    }
+
+    /**
+     * Creates a function that wraps `func` to continue currying.
+     *
+     * @private
+     * @param {Function} func The function to wrap.
+     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+     * @param {Function} wrapFunc The function to create the `func` wrapper.
+     * @param {*} placeholder The placeholder value.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {Array} [partials] The arguments to prepend to those provided to
+     *  the new function.
+     * @param {Array} [holders] The `partials` placeholder indexes.
+     * @param {Array} [argPos] The argument positions of the new function.
+     * @param {number} [ary] The arity cap of `func`.
+     * @param {number} [arity] The arity of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createRecurry(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) {
+      var isCurry = bitmask & WRAP_CURRY_FLAG,
+          newHolders = isCurry ? holders : undefined,
+          newHoldersRight = isCurry ? undefined : holders,
+          newPartials = isCurry ? partials : undefined,
+          newPartialsRight = isCurry ? undefined : partials;
+
+      bitmask |= (isCurry ? WRAP_PARTIAL_FLAG : WRAP_PARTIAL_RIGHT_FLAG);
+      bitmask &= ~(isCurry ? WRAP_PARTIAL_RIGHT_FLAG : WRAP_PARTIAL_FLAG);
+
+      if (!(bitmask & WRAP_CURRY_BOUND_FLAG)) {
+        bitmask &= ~(WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG);
+      }
+      var newData = [
+        func, bitmask, thisArg, newPartials, newHolders, newPartialsRight,
+        newHoldersRight, argPos, ary, arity
+      ];
+
+      var result = wrapFunc.apply(undefined, newData);
+      if (isLaziable(func)) {
+        setData(result, newData);
+      }
+      result.placeholder = placeholder;
+      return setWrapToString(result, func, bitmask);
+    }
+
+    /**
+     * Creates a function like `_.round`.
+     *
+     * @private
+     * @param {string} methodName The name of the `Math` method to use when rounding.
+     * @returns {Function} Returns the new round function.
+     */
+    function createRound(methodName) {
+      var func = Math[methodName];
+      return function(number, precision) {
+        number = toNumber(number);
+        precision = precision == null ? 0 : nativeMin(toInteger(precision), 292);
+        if (precision) {
+          // Shift with exponential notation to avoid floating-point issues.
+          // See [MDN](https://mdn.io/round#Examples) for more details.
+          var pair = (toString(number) + 'e').split('e'),
+              value = func(pair[0] + 'e' + (+pair[1] + precision));
+
+          pair = (toString(value) + 'e').split('e');
+          return +(pair[0] + 'e' + (+pair[1] - precision));
+        }
+        return func(number);
+      };
+    }
+
+    /**
+     * Creates a set object of `values`.
+     *
+     * @private
+     * @param {Array} values The values to add to the set.
+     * @returns {Object} Returns the new set.
+     */
+    var createSet = !(Set && (1 / setToArray(new Set([,-0]))[1]) == INFINITY) ? noop : function(values) {
+      return new Set(values);
+    };
+
+    /**
+     * Creates a `_.toPairs` or `_.toPairsIn` function.
+     *
+     * @private
+     * @param {Function} keysFunc The function to get the keys of a given object.
+     * @returns {Function} Returns the new pairs function.
+     */
+    function createToPairs(keysFunc) {
+      return function(object) {
+        var tag = getTag(object);
+        if (tag == mapTag) {
+          return mapToArray(object);
+        }
+        if (tag == setTag) {
+          return setToPairs(object);
+        }
+        return baseToPairs(object, keysFunc(object));
+      };
+    }
+
+    /**
+     * Creates a function that either curries or invokes `func` with optional
+     * `this` binding and partially applied arguments.
+     *
+     * @private
+     * @param {Function|string} func The function or method name to wrap.
+     * @param {number} bitmask The bitmask flags.
+     *    1 - `_.bind`
+     *    2 - `_.bindKey`
+     *    4 - `_.curry` or `_.curryRight` of a bound function
+     *    8 - `_.curry`
+     *   16 - `_.curryRight`
+     *   32 - `_.partial`
+     *   64 - `_.partialRight`
+     *  128 - `_.rearg`
+     *  256 - `_.ary`
+     *  512 - `_.flip`
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {Array} [partials] The arguments to be partially applied.
+     * @param {Array} [holders] The `partials` placeholder indexes.
+     * @param {Array} [argPos] The argument positions of the new function.
+     * @param {number} [ary] The arity cap of `func`.
+     * @param {number} [arity] The arity of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createWrap(func, bitmask, thisArg, partials, holders, argPos, ary, arity) {
+      var isBindKey = bitmask & WRAP_BIND_KEY_FLAG;
+      if (!isBindKey && typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      var length = partials ? partials.length : 0;
+      if (!length) {
+        bitmask &= ~(WRAP_PARTIAL_FLAG | WRAP_PARTIAL_RIGHT_FLAG);
+        partials = holders = undefined;
+      }
+      ary = ary === undefined ? ary : nativeMax(toInteger(ary), 0);
+      arity = arity === undefined ? arity : toInteger(arity);
+      length -= holders ? holders.length : 0;
+
+      if (bitmask & WRAP_PARTIAL_RIGHT_FLAG) {
+        var partialsRight = partials,
+            holdersRight = holders;
+
+        partials = holders = undefined;
+      }
+      var data = isBindKey ? undefined : getData(func);
+
+      var newData = [
+        func, bitmask, thisArg, partials, holders, partialsRight, holdersRight,
+        argPos, ary, arity
+      ];
+
+      if (data) {
+        mergeData(newData, data);
+      }
+      func = newData[0];
+      bitmask = newData[1];
+      thisArg = newData[2];
+      partials = newData[3];
+      holders = newData[4];
+      arity = newData[9] = newData[9] === undefined
+        ? (isBindKey ? 0 : func.length)
+        : nativeMax(newData[9] - length, 0);
+
+      if (!arity && bitmask & (WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG)) {
+        bitmask &= ~(WRAP_CURRY_FLAG | WRAP_CURRY_RIGHT_FLAG);
+      }
+      if (!bitmask || bitmask == WRAP_BIND_FLAG) {
+        var result = createBind(func, bitmask, thisArg);
+      } else if (bitmask == WRAP_CURRY_FLAG || bitmask == WRAP_CURRY_RIGHT_FLAG) {
+        result = createCurry(func, bitmask, arity);
+      } else if ((bitmask == WRAP_PARTIAL_FLAG || bitmask == (WRAP_BIND_FLAG | WRAP_PARTIAL_FLAG)) && !holders.length) {
+        result = createPartial(func, bitmask, thisArg, partials);
+      } else {
+        result = createHybrid.apply(undefined, newData);
+      }
+      var setter = data ? baseSetData : setData;
+      return setWrapToString(setter(result, newData), func, bitmask);
+    }
+
+    /**
+     * Used by `_.defaults` to customize its `_.assignIn` use to assign properties
+     * of source objects to the destination object for all destination properties
+     * that resolve to `undefined`.
+     *
+     * @private
+     * @param {*} objValue The destination value.
+     * @param {*} srcValue The source value.
+     * @param {string} key The key of the property to assign.
+     * @param {Object} object The parent object of `objValue`.
+     * @returns {*} Returns the value to assign.
+     */
+    function customDefaultsAssignIn(objValue, srcValue, key, object) {
+      if (objValue === undefined ||
+          (eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key))) {
+        return srcValue;
+      }
+      return objValue;
+    }
+
+    /**
+     * Used by `_.defaultsDeep` to customize its `_.merge` use to merge source
+     * objects into destination objects that are passed thru.
+     *
+     * @private
+     * @param {*} objValue The destination value.
+     * @param {*} srcValue The source value.
+     * @param {string} key The key of the property to merge.
+     * @param {Object} object The parent object of `objValue`.
+     * @param {Object} source The parent object of `srcValue`.
+     * @param {Object} [stack] Tracks traversed source values and their merged
+     *  counterparts.
+     * @returns {*} Returns the value to assign.
+     */
+    function customDefaultsMerge(objValue, srcValue, key, object, source, stack) {
+      if (isObject(objValue) && isObject(srcValue)) {
+        // Recursively merge objects and arrays (susceptible to call stack limits).
+        stack.set(srcValue, objValue);
+        baseMerge(objValue, srcValue, undefined, customDefaultsMerge, stack);
+        stack['delete'](srcValue);
+      }
+      return objValue;
+    }
+
+    /**
+     * Used by `_.omit` to customize its `_.cloneDeep` use to only clone plain
+     * objects.
+     *
+     * @private
+     * @param {*} value The value to inspect.
+     * @param {string} key The key of the property to inspect.
+     * @returns {*} Returns the uncloned value or `undefined` to defer cloning to `_.cloneDeep`.
+     */
+    function customOmitClone(value) {
+      return isPlainObject(value) ? undefined : value;
+    }
+
+    /**
+     * A specialized version of `baseIsEqualDeep` for arrays with support for
+     * partial deep comparisons.
+     *
+     * @private
+     * @param {Array} array The array to compare.
+     * @param {Array} other The other array to compare.
+     * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
+     * @param {Function} customizer The function to customize comparisons.
+     * @param {Function} equalFunc The function to determine equivalents of values.
+     * @param {Object} stack Tracks traversed `array` and `other` objects.
+     * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
+     */
+    function equalArrays(array, other, bitmask, customizer, equalFunc, stack) {
+      var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
+          arrLength = array.length,
+          othLength = other.length;
+
+      if (arrLength != othLength && !(isPartial && othLength > arrLength)) {
+        return false;
+      }
+      // Assume cyclic values are equal.
+      var stacked = stack.get(array);
+      if (stacked && stack.get(other)) {
+        return stacked == other;
+      }
+      var index = -1,
+          result = true,
+          seen = (bitmask & COMPARE_UNORDERED_FLAG) ? new SetCache : undefined;
+
+      stack.set(array, other);
+      stack.set(other, array);
+
+      // Ignore non-index properties.
+      while (++index < arrLength) {
+        var arrValue = array[index],
+            othValue = other[index];
+
+        if (customizer) {
+          var compared = isPartial
+            ? customizer(othValue, arrValue, index, other, array, stack)
+            : customizer(arrValue, othValue, index, array, other, stack);
+        }
+        if (compared !== undefined) {
+          if (compared) {
+            continue;
+          }
+          result = false;
+          break;
+        }
+        // Recursively compare arrays (susceptible to call stack limits).
+        if (seen) {
+          if (!arraySome(other, function(othValue, othIndex) {
+                if (!cacheHas(seen, othIndex) &&
+                    (arrValue === othValue || equalFunc(arrValue, othValue, bitmask, customizer, stack))) {
+                  return seen.push(othIndex);
+                }
+              })) {
+            result = false;
+            break;
+          }
+        } else if (!(
+              arrValue === othValue ||
+                equalFunc(arrValue, othValue, bitmask, customizer, stack)
+            )) {
+          result = false;
+          break;
+        }
+      }
+      stack['delete'](array);
+      stack['delete'](other);
+      return result;
+    }
+
+    /**
+     * A specialized version of `baseIsEqualDeep` for comparing objects of
+     * the same `toStringTag`.
+     *
+     * **Note:** This function only supports comparing values with tags of
+     * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
+     *
+     * @private
+     * @param {Object} object The object to compare.
+     * @param {Object} other The other object to compare.
+     * @param {string} tag The `toStringTag` of the objects to compare.
+     * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
+     * @param {Function} customizer The function to customize comparisons.
+     * @param {Function} equalFunc The function to determine equivalents of values.
+     * @param {Object} stack Tracks traversed `object` and `other` objects.
+     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+     */
+    function equalByTag(object, other, tag, bitmask, customizer, equalFunc, stack) {
+      switch (tag) {
+        case dataViewTag:
+          if ((object.byteLength != other.byteLength) ||
+              (object.byteOffset != other.byteOffset)) {
+            return false;
+          }
+          object = object.buffer;
+          other = other.buffer;
+
+        case arrayBufferTag:
+          if ((object.byteLength != other.byteLength) ||
+              !equalFunc(new Uint8Array(object), new Uint8Array(other))) {
+            return false;
+          }
+          return true;
+
+        case boolTag:
+        case dateTag:
+        case numberTag:
+          // Coerce booleans to `1` or `0` and dates to milliseconds.
+          // Invalid dates are coerced to `NaN`.
+          return eq(+object, +other);
+
+        case errorTag:
+          return object.name == other.name && object.message == other.message;
+
+        case regexpTag:
+        case stringTag:
+          // Coerce regexes to strings and treat strings, primitives and objects,
+          // as equal. See http://www.ecma-international.org/ecma-262/7.0/#sec-regexp.prototype.tostring
+          // for more details.
+          return object == (other + '');
+
+        case mapTag:
+          var convert = mapToArray;
+
+        case setTag:
+          var isPartial = bitmask & COMPARE_PARTIAL_FLAG;
+          convert || (convert = setToArray);
+
+          if (object.size != other.size && !isPartial) {
+            return false;
+          }
+          // Assume cyclic values are equal.
+          var stacked = stack.get(object);
+          if (stacked) {
+            return stacked == other;
+          }
+          bitmask |= COMPARE_UNORDERED_FLAG;
+
+          // Recursively compare objects (susceptible to call stack limits).
+          stack.set(object, other);
+          var result = equalArrays(convert(object), convert(other), bitmask, customizer, equalFunc, stack);
+          stack['delete'](object);
+          return result;
+
+        case symbolTag:
+          if (symbolValueOf) {
+            return symbolValueOf.call(object) == symbolValueOf.call(other);
+          }
+      }
+      return false;
+    }
+
+    /**
+     * A specialized version of `baseIsEqualDeep` for objects with support for
+     * partial deep comparisons.
+     *
+     * @private
+     * @param {Object} object The object to compare.
+     * @param {Object} other The other object to compare.
+     * @param {number} bitmask The bitmask flags. See `baseIsEqual` for more details.
+     * @param {Function} customizer The function to customize comparisons.
+     * @param {Function} equalFunc The function to determine equivalents of values.
+     * @param {Object} stack Tracks traversed `object` and `other` objects.
+     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+     */
+    function equalObjects(object, other, bitmask, customizer, equalFunc, stack) {
+      var isPartial = bitmask & COMPARE_PARTIAL_FLAG,
+          objProps = getAllKeys(object),
+          objLength = objProps.length,
+          othProps = getAllKeys(other),
+          othLength = othProps.length;
+
+      if (objLength != othLength && !isPartial) {
+        return false;
+      }
+      var index = objLength;
+      while (index--) {
+        var key = objProps[index];
+        if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) {
+          return false;
+        }
+      }
+      // Assume cyclic values are equal.
+      var stacked = stack.get(object);
+      if (stacked && stack.get(other)) {
+        return stacked == other;
+      }
+      var result = true;
+      stack.set(object, other);
+      stack.set(other, object);
+
+      var skipCtor = isPartial;
+      while (++index < objLength) {
+        key = objProps[index];
+        var objValue = object[key],
+            othValue = other[key];
+
+        if (customizer) {
+          var compared = isPartial
+            ? customizer(othValue, objValue, key, other, object, stack)
+            : customizer(objValue, othValue, key, object, other, stack);
+        }
+        // Recursively compare objects (susceptible to call stack limits).
+        if (!(compared === undefined
+              ? (objValue === othValue || equalFunc(objValue, othValue, bitmask, customizer, stack))
+              : compared
+            )) {
+          result = false;
+          break;
+        }
+        skipCtor || (skipCtor = key == 'constructor');
+      }
+      if (result && !skipCtor) {
+        var objCtor = object.constructor,
+            othCtor = other.constructor;
+
+        // Non `Object` object instances with different constructors are not equal.
+        if (objCtor != othCtor &&
+            ('constructor' in object && 'constructor' in other) &&
+            !(typeof objCtor == 'function' && objCtor instanceof objCtor &&
+              typeof othCtor == 'function' && othCtor instanceof othCtor)) {
+          result = false;
+        }
+      }
+      stack['delete'](object);
+      stack['delete'](other);
+      return result;
+    }
+
+    /**
+     * A specialized version of `baseRest` which flattens the rest array.
+     *
+     * @private
+     * @param {Function} func The function to apply a rest parameter to.
+     * @returns {Function} Returns the new function.
+     */
+    function flatRest(func) {
+      return setToString(overRest(func, undefined, flatten), func + '');
+    }
+
+    /**
+     * Creates an array of own enumerable property names and symbols of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names and symbols.
+     */
+    function getAllKeys(object) {
+      return baseGetAllKeys(object, keys, getSymbols);
+    }
+
+    /**
+     * Creates an array of own and inherited enumerable property names and
+     * symbols of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names and symbols.
+     */
+    function getAllKeysIn(object) {
+      return baseGetAllKeys(object, keysIn, getSymbolsIn);
+    }
+
+    /**
+     * Gets metadata for `func`.
+     *
+     * @private
+     * @param {Function} func The function to query.
+     * @returns {*} Returns the metadata for `func`.
+     */
+    var getData = !metaMap ? noop : function(func) {
+      return metaMap.get(func);
+    };
+
+    /**
+     * Gets the name of `func`.
+     *
+     * @private
+     * @param {Function} func The function to query.
+     * @returns {string} Returns the function name.
+     */
+    function getFuncName(func) {
+      var result = (func.name + ''),
+          array = realNames[result],
+          length = hasOwnProperty.call(realNames, result) ? array.length : 0;
+
+      while (length--) {
+        var data = array[length],
+            otherFunc = data.func;
+        if (otherFunc == null || otherFunc == func) {
+          return data.name;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Gets the argument placeholder value for `func`.
+     *
+     * @private
+     * @param {Function} func The function to inspect.
+     * @returns {*} Returns the placeholder value.
+     */
+    function getHolder(func) {
+      var object = hasOwnProperty.call(lodash, 'placeholder') ? lodash : func;
+      return object.placeholder;
+    }
+
+    /**
+     * Gets the appropriate "iteratee" function. If `_.iteratee` is customized,
+     * this function returns the custom method, otherwise it returns `baseIteratee`.
+     * If arguments are provided, the chosen function is invoked with them and
+     * its result is returned.
+     *
+     * @private
+     * @param {*} [value] The value to convert to an iteratee.
+     * @param {number} [arity] The arity of the created iteratee.
+     * @returns {Function} Returns the chosen function or its result.
+     */
+    function getIteratee() {
+      var result = lodash.iteratee || iteratee;
+      result = result === iteratee ? baseIteratee : result;
+      return arguments.length ? result(arguments[0], arguments[1]) : result;
+    }
+
+    /**
+     * Gets the data for `map`.
+     *
+     * @private
+     * @param {Object} map The map to query.
+     * @param {string} key The reference key.
+     * @returns {*} Returns the map data.
+     */
+    function getMapData(map, key) {
+      var data = map.__data__;
+      return isKeyable(key)
+        ? data[typeof key == 'string' ? 'string' : 'hash']
+        : data.map;
+    }
+
+    /**
+     * Gets the property names, values, and compare flags of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the match data of `object`.
+     */
+    function getMatchData(object) {
+      var result = keys(object),
+          length = result.length;
+
+      while (length--) {
+        var key = result[length],
+            value = object[key];
+
+        result[length] = [key, value, isStrictComparable(value)];
+      }
+      return result;
+    }
+
+    /**
+     * Gets the native function at `key` of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {string} key The key of the method to get.
+     * @returns {*} Returns the function if it's native, else `undefined`.
+     */
+    function getNative(object, key) {
+      var value = getValue(object, key);
+      return baseIsNative(value) ? value : undefined;
+    }
+
+    /**
+     * A specialized version of `baseGetTag` which ignores `Symbol.toStringTag` values.
+     *
+     * @private
+     * @param {*} value The value to query.
+     * @returns {string} Returns the raw `toStringTag`.
+     */
+    function getRawTag(value) {
+      var isOwn = hasOwnProperty.call(value, symToStringTag),
+          tag = value[symToStringTag];
+
+      try {
+        value[symToStringTag] = undefined;
+        var unmasked = true;
+      } catch (e) {}
+
+      var result = nativeObjectToString.call(value);
+      if (unmasked) {
+        if (isOwn) {
+          value[symToStringTag] = tag;
+        } else {
+          delete value[symToStringTag];
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Creates an array of the own enumerable symbols of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of symbols.
+     */
+    var getSymbols = !nativeGetSymbols ? stubArray : function(object) {
+      if (object == null) {
+        return [];
+      }
+      object = Object(object);
+      return arrayFilter(nativeGetSymbols(object), function(symbol) {
+        return propertyIsEnumerable.call(object, symbol);
+      });
+    };
+
+    /**
+     * Creates an array of the own and inherited enumerable symbols of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of symbols.
+     */
+    var getSymbolsIn = !nativeGetSymbols ? stubArray : function(object) {
+      var result = [];
+      while (object) {
+        arrayPush(result, getSymbols(object));
+        object = getPrototype(object);
+      }
+      return result;
+    };
+
+    /**
+     * Gets the `toStringTag` of `value`.
+     *
+     * @private
+     * @param {*} value The value to query.
+     * @returns {string} Returns the `toStringTag`.
+     */
+    var getTag = baseGetTag;
+
+    // Fallback for data views, maps, sets, and weak maps in IE 11 and promises in Node.js < 6.
+    if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) ||
+        (Map && getTag(new Map) != mapTag) ||
+        (Promise && getTag(Promise.resolve()) != promiseTag) ||
+        (Set && getTag(new Set) != setTag) ||
+        (WeakMap && getTag(new WeakMap) != weakMapTag)) {
+      getTag = function(value) {
+        var result = baseGetTag(value),
+            Ctor = result == objectTag ? value.constructor : undefined,
+            ctorString = Ctor ? toSource(Ctor) : '';
+
+        if (ctorString) {
+          switch (ctorString) {
+            case dataViewCtorString: return dataViewTag;
+            case mapCtorString: return mapTag;
+            case promiseCtorString: return promiseTag;
+            case setCtorString: return setTag;
+            case weakMapCtorString: return weakMapTag;
+          }
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Gets the view, applying any `transforms` to the `start` and `end` positions.
+     *
+     * @private
+     * @param {number} start The start of the view.
+     * @param {number} end The end of the view.
+     * @param {Array} transforms The transformations to apply to the view.
+     * @returns {Object} Returns an object containing the `start` and `end`
+     *  positions of the view.
+     */
+    function getView(start, end, transforms) {
+      var index = -1,
+          length = transforms.length;
+
+      while (++index < length) {
+        var data = transforms[index],
+            size = data.size;
+
+        switch (data.type) {
+          case 'drop':      start += size; break;
+          case 'dropRight': end -= size; break;
+          case 'take':      end = nativeMin(end, start + size); break;
+          case 'takeRight': start = nativeMax(start, end - size); break;
+        }
+      }
+      return { 'start': start, 'end': end };
+    }
+
+    /**
+     * Extracts wrapper details from the `source` body comment.
+     *
+     * @private
+     * @param {string} source The source to inspect.
+     * @returns {Array} Returns the wrapper details.
+     */
+    function getWrapDetails(source) {
+      var match = source.match(reWrapDetails);
+      return match ? match[1].split(reSplitDetails) : [];
+    }
+
+    /**
+     * Checks if `path` exists on `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path to check.
+     * @param {Function} hasFunc The function to check properties.
+     * @returns {boolean} Returns `true` if `path` exists, else `false`.
+     */
+    function hasPath(object, path, hasFunc) {
+      path = castPath(path, object);
+
+      var index = -1,
+          length = path.length,
+          result = false;
+
+      while (++index < length) {
+        var key = toKey(path[index]);
+        if (!(result = object != null && hasFunc(object, key))) {
+          break;
+        }
+        object = object[key];
+      }
+      if (result || ++index != length) {
+        return result;
+      }
+      length = object == null ? 0 : object.length;
+      return !!length && isLength(length) && isIndex(key, length) &&
+        (isArray(object) || isArguments(object));
+    }
+
+    /**
+     * Initializes an array clone.
+     *
+     * @private
+     * @param {Array} array The array to clone.
+     * @returns {Array} Returns the initialized clone.
+     */
+    function initCloneArray(array) {
+      var length = array.length,
+          result = array.constructor(length);
+
+      // Add properties assigned by `RegExp#exec`.
+      if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
+        result.index = array.index;
+        result.input = array.input;
+      }
+      return result;
+    }
+
+    /**
+     * Initializes an object clone.
+     *
+     * @private
+     * @param {Object} object The object to clone.
+     * @returns {Object} Returns the initialized clone.
+     */
+    function initCloneObject(object) {
+      return (typeof object.constructor == 'function' && !isPrototype(object))
+        ? baseCreate(getPrototype(object))
+        : {};
+    }
+
+    /**
+     * Initializes an object clone based on its `toStringTag`.
+     *
+     * **Note:** This function only supports cloning values with tags of
+     * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
+     *
+     * @private
+     * @param {Object} object The object to clone.
+     * @param {string} tag The `toStringTag` of the object to clone.
+     * @param {Function} cloneFunc The function to clone values.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the initialized clone.
+     */
+    function initCloneByTag(object, tag, cloneFunc, isDeep) {
+      var Ctor = object.constructor;
+      switch (tag) {
+        case arrayBufferTag:
+          return cloneArrayBuffer(object);
+
+        case boolTag:
+        case dateTag:
+          return new Ctor(+object);
+
+        case dataViewTag:
+          return cloneDataView(object, isDeep);
+
+        case float32Tag: case float64Tag:
+        case int8Tag: case int16Tag: case int32Tag:
+        case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:
+          return cloneTypedArray(object, isDeep);
+
+        case mapTag:
+          return cloneMap(object, isDeep, cloneFunc);
+
+        case numberTag:
+        case stringTag:
+          return new Ctor(object);
+
+        case regexpTag:
+          return cloneRegExp(object);
+
+        case setTag:
+          return cloneSet(object, isDeep, cloneFunc);
+
+        case symbolTag:
+          return cloneSymbol(object);
+      }
+    }
+
+    /**
+     * Inserts wrapper `details` in a comment at the top of the `source` body.
+     *
+     * @private
+     * @param {string} source The source to modify.
+     * @returns {Array} details The details to insert.
+     * @returns {string} Returns the modified source.
+     */
+    function insertWrapDetails(source, details) {
+      var length = details.length;
+      if (!length) {
+        return source;
+      }
+      var lastIndex = length - 1;
+      details[lastIndex] = (length > 1 ? '& ' : '') + details[lastIndex];
+      details = details.join(length > 2 ? ', ' : ' ');
+      return source.replace(reWrapComment, '{\n/* [wrapped with ' + details + '] */\n');
+    }
+
+    /**
+     * Checks if `value` is a flattenable `arguments` object or array.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is flattenable, else `false`.
+     */
+    function isFlattenable(value) {
+      return isArray(value) || isArguments(value) ||
+        !!(spreadableSymbol && value && value[spreadableSymbol]);
+    }
+
+    /**
+     * Checks if `value` is a valid array-like index.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
+     * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
+     */
+    function isIndex(value, length) {
+      length = length == null ? MAX_SAFE_INTEGER : length;
+      return !!length &&
+        (typeof value == 'number' || reIsUint.test(value)) &&
+        (value > -1 && value % 1 == 0 && value < length);
+    }
+
+    /**
+     * Checks if the given arguments are from an iteratee call.
+     *
+     * @private
+     * @param {*} value The potential iteratee value argument.
+     * @param {*} index The potential iteratee index or key argument.
+     * @param {*} object The potential iteratee object argument.
+     * @returns {boolean} Returns `true` if the arguments are from an iteratee call,
+     *  else `false`.
+     */
+    function isIterateeCall(value, index, object) {
+      if (!isObject(object)) {
+        return false;
+      }
+      var type = typeof index;
+      if (type == 'number'
+            ? (isArrayLike(object) && isIndex(index, object.length))
+            : (type == 'string' && index in object)
+          ) {
+        return eq(object[index], value);
+      }
+      return false;
+    }
+
+    /**
+     * Checks if `value` is a property name and not a property path.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @param {Object} [object] The object to query keys on.
+     * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
+     */
+    function isKey(value, object) {
+      if (isArray(value)) {
+        return false;
+      }
+      var type = typeof value;
+      if (type == 'number' || type == 'symbol' || type == 'boolean' ||
+          value == null || isSymbol(value)) {
+        return true;
+      }
+      return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
+        (object != null && value in Object(object));
+    }
+
+    /**
+     * Checks if `value` is suitable for use as unique object key.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is suitable, else `false`.
+     */
+    function isKeyable(value) {
+      var type = typeof value;
+      return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')
+        ? (value !== '__proto__')
+        : (value === null);
+    }
+
+    /**
+     * Checks if `func` has a lazy counterpart.
+     *
+     * @private
+     * @param {Function} func The function to check.
+     * @returns {boolean} Returns `true` if `func` has a lazy counterpart,
+     *  else `false`.
+     */
+    function isLaziable(func) {
+      var funcName = getFuncName(func),
+          other = lodash[funcName];
+
+      if (typeof other != 'function' || !(funcName in LazyWrapper.prototype)) {
+        return false;
+      }
+      if (func === other) {
+        return true;
+      }
+      var data = getData(other);
+      return !!data && func === data[0];
+    }
+
+    /**
+     * Checks if `func` has its source masked.
+     *
+     * @private
+     * @param {Function} func The function to check.
+     * @returns {boolean} Returns `true` if `func` is masked, else `false`.
+     */
+    function isMasked(func) {
+      return !!maskSrcKey && (maskSrcKey in func);
+    }
+
+    /**
+     * Checks if `func` is capable of being masked.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `func` is maskable, else `false`.
+     */
+    var isMaskable = coreJsData ? isFunction : stubFalse;
+
+    /**
+     * Checks if `value` is likely a prototype object.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
+     */
+    function isPrototype(value) {
+      var Ctor = value && value.constructor,
+          proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto;
+
+      return value === proto;
+    }
+
+    /**
+     * Checks if `value` is suitable for strict equality comparisons, i.e. `===`.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` if suitable for strict
+     *  equality comparisons, else `false`.
+     */
+    function isStrictComparable(value) {
+      return value === value && !isObject(value);
+    }
+
+    /**
+     * A specialized version of `matchesProperty` for source values suitable
+     * for strict equality comparisons, i.e. `===`.
+     *
+     * @private
+     * @param {string} key The key of the property to get.
+     * @param {*} srcValue The value to match.
+     * @returns {Function} Returns the new spec function.
+     */
+    function matchesStrictComparable(key, srcValue) {
+      return function(object) {
+        if (object == null) {
+          return false;
+        }
+        return object[key] === srcValue &&
+          (srcValue !== undefined || (key in Object(object)));
+      };
+    }
+
+    /**
+     * A specialized version of `_.memoize` which clears the memoized function's
+     * cache when it exceeds `MAX_MEMOIZE_SIZE`.
+     *
+     * @private
+     * @param {Function} func The function to have its output memoized.
+     * @returns {Function} Returns the new memoized function.
+     */
+    function memoizeCapped(func) {
+      var result = memoize(func, function(key) {
+        if (cache.size === MAX_MEMOIZE_SIZE) {
+          cache.clear();
+        }
+        return key;
+      });
+
+      var cache = result.cache;
+      return result;
+    }
+
+    /**
+     * Merges the function metadata of `source` into `data`.
+     *
+     * Merging metadata reduces the number of wrappers used to invoke a function.
+     * This is possible because methods like `_.bind`, `_.curry`, and `_.partial`
+     * may be applied regardless of execution order. Methods like `_.ary` and
+     * `_.rearg` modify function arguments, making the order in which they are
+     * executed important, preventing the merging of metadata. However, we make
+     * an exception for a safe combined case where curried functions have `_.ary`
+     * and or `_.rearg` applied.
+     *
+     * @private
+     * @param {Array} data The destination metadata.
+     * @param {Array} source The source metadata.
+     * @returns {Array} Returns `data`.
+     */
+    function mergeData(data, source) {
+      var bitmask = data[1],
+          srcBitmask = source[1],
+          newBitmask = bitmask | srcBitmask,
+          isCommon = newBitmask < (WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG | WRAP_ARY_FLAG);
+
+      var isCombo =
+        ((srcBitmask == WRAP_ARY_FLAG) && (bitmask == WRAP_CURRY_FLAG)) ||
+        ((srcBitmask == WRAP_ARY_FLAG) && (bitmask == WRAP_REARG_FLAG) && (data[7].length <= source[8])) ||
+        ((srcBitmask == (WRAP_ARY_FLAG | WRAP_REARG_FLAG)) && (source[7].length <= source[8]) && (bitmask == WRAP_CURRY_FLAG));
+
+      // Exit early if metadata can't be merged.
+      if (!(isCommon || isCombo)) {
+        return data;
+      }
+      // Use source `thisArg` if available.
+      if (srcBitmask & WRAP_BIND_FLAG) {
+        data[2] = source[2];
+        // Set when currying a bound function.
+        newBitmask |= bitmask & WRAP_BIND_FLAG ? 0 : WRAP_CURRY_BOUND_FLAG;
+      }
+      // Compose partial arguments.
+      var value = source[3];
+      if (value) {
+        var partials = data[3];
+        data[3] = partials ? composeArgs(partials, value, source[4]) : value;
+        data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : source[4];
+      }
+      // Compose partial right arguments.
+      value = source[5];
+      if (value) {
+        partials = data[5];
+        data[5] = partials ? composeArgsRight(partials, value, source[6]) : value;
+        data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : source[6];
+      }
+      // Use source `argPos` if available.
+      value = source[7];
+      if (value) {
+        data[7] = value;
+      }
+      // Use source `ary` if it's smaller.
+      if (srcBitmask & WRAP_ARY_FLAG) {
+        data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]);
+      }
+      // Use source `arity` if one is not provided.
+      if (data[9] == null) {
+        data[9] = source[9];
+      }
+      // Use source `func` and merge bitmasks.
+      data[0] = source[0];
+      data[1] = newBitmask;
+
+      return data;
+    }
+
+    /**
+     * This function is like
+     * [`Object.keys`](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
+     * except that it includes inherited enumerable properties.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names.
+     */
+    function nativeKeysIn(object) {
+      var result = [];
+      if (object != null) {
+        for (var key in Object(object)) {
+          result.push(key);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Converts `value` to a string using `Object.prototype.toString`.
+     *
+     * @private
+     * @param {*} value The value to convert.
+     * @returns {string} Returns the converted string.
+     */
+    function objectToString(value) {
+      return nativeObjectToString.call(value);
+    }
+
+    /**
+     * A specialized version of `baseRest` which transforms the rest array.
+     *
+     * @private
+     * @param {Function} func The function to apply a rest parameter to.
+     * @param {number} [start=func.length-1] The start position of the rest parameter.
+     * @param {Function} transform The rest array transform.
+     * @returns {Function} Returns the new function.
+     */
+    function overRest(func, start, transform) {
+      start = nativeMax(start === undefined ? (func.length - 1) : start, 0);
+      return function() {
+        var args = arguments,
+            index = -1,
+            length = nativeMax(args.length - start, 0),
+            array = Array(length);
+
+        while (++index < length) {
+          array[index] = args[start + index];
+        }
+        index = -1;
+        var otherArgs = Array(start + 1);
+        while (++index < start) {
+          otherArgs[index] = args[index];
+        }
+        otherArgs[start] = transform(array);
+        return apply(func, this, otherArgs);
+      };
+    }
+
+    /**
+     * Gets the parent value at `path` of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array} path The path to get the parent value of.
+     * @returns {*} Returns the parent value.
+     */
+    function parent(object, path) {
+      return path.length < 2 ? object : baseGet(object, baseSlice(path, 0, -1));
+    }
+
+    /**
+     * Reorder `array` according to the specified indexes where the element at
+     * the first index is assigned as the first element, the element at
+     * the second index is assigned as the second element, and so on.
+     *
+     * @private
+     * @param {Array} array The array to reorder.
+     * @param {Array} indexes The arranged array indexes.
+     * @returns {Array} Returns `array`.
+     */
+    function reorder(array, indexes) {
+      var arrLength = array.length,
+          length = nativeMin(indexes.length, arrLength),
+          oldArray = copyArray(array);
+
+      while (length--) {
+        var index = indexes[length];
+        array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined;
+      }
+      return array;
+    }
+
+    /**
+     * Sets metadata for `func`.
+     *
+     * **Note:** If this function becomes hot, i.e. is invoked a lot in a short
+     * period of time, it will trip its breaker and transition to an identity
+     * function to avoid garbage collection pauses in V8. See
+     * [V8 issue 2070](https://bugs.chromium.org/p/v8/issues/detail?id=2070)
+     * for more details.
+     *
+     * @private
+     * @param {Function} func The function to associate metadata with.
+     * @param {*} data The metadata.
+     * @returns {Function} Returns `func`.
+     */
+    var setData = shortOut(baseSetData);
+
+    /**
+     * A simple wrapper around the global [`setTimeout`](https://mdn.io/setTimeout).
+     *
+     * @private
+     * @param {Function} func The function to delay.
+     * @param {number} wait The number of milliseconds to delay invocation.
+     * @returns {number|Object} Returns the timer id or timeout object.
+     */
+    var setTimeout = ctxSetTimeout || function(func, wait) {
+      return root.setTimeout(func, wait);
+    };
+
+    /**
+     * Sets the `toString` method of `func` to return `string`.
+     *
+     * @private
+     * @param {Function} func The function to modify.
+     * @param {Function} string The `toString` result.
+     * @returns {Function} Returns `func`.
+     */
+    var setToString = shortOut(baseSetToString);
+
+    /**
+     * Sets the `toString` method of `wrapper` to mimic the source of `reference`
+     * with wrapper details in a comment at the top of the source body.
+     *
+     * @private
+     * @param {Function} wrapper The function to modify.
+     * @param {Function} reference The reference function.
+     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+     * @returns {Function} Returns `wrapper`.
+     */
+    function setWrapToString(wrapper, reference, bitmask) {
+      var source = (reference + '');
+      return setToString(wrapper, insertWrapDetails(source, updateWrapDetails(getWrapDetails(source), bitmask)));
+    }
+
+    /**
+     * Creates a function that'll short out and invoke `identity` instead
+     * of `func` when it's called `HOT_COUNT` or more times in `HOT_SPAN`
+     * milliseconds.
+     *
+     * @private
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new shortable function.
+     */
+    function shortOut(func) {
+      var count = 0,
+          lastCalled = 0;
+
+      return function() {
+        var stamp = nativeNow(),
+            remaining = HOT_SPAN - (stamp - lastCalled);
+
+        lastCalled = stamp;
+        if (remaining > 0) {
+          if (++count >= HOT_COUNT) {
+            return arguments[0];
+          }
+        } else {
+          count = 0;
+        }
+        return func.apply(undefined, arguments);
+      };
+    }
+
+    /**
+     * A specialized version of `_.shuffle` which mutates and sets the size of `array`.
+     *
+     * @private
+     * @param {Array} array The array to shuffle.
+     * @param {number} [size=array.length] The size of `array`.
+     * @returns {Array} Returns `array`.
+     */
+    function shuffleSelf(array, size) {
+      var index = -1,
+          length = array.length,
+          lastIndex = length - 1;
+
+      size = size === undefined ? length : size;
+      while (++index < size) {
+        var rand = baseRandom(index, lastIndex),
+            value = array[rand];
+
+        array[rand] = array[index];
+        array[index] = value;
+      }
+      array.length = size;
+      return array;
+    }
+
+    /**
+     * Converts `string` to a property path array.
+     *
+     * @private
+     * @param {string} string The string to convert.
+     * @returns {Array} Returns the property path array.
+     */
+    var stringToPath = memoizeCapped(function(string) {
+      var result = [];
+      if (reLeadingDot.test(string)) {
+        result.push('');
+      }
+      string.replace(rePropName, function(match, number, quote, string) {
+        result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
+      });
+      return result;
+    });
+
+    /**
+     * Converts `value` to a string key if it's not a string or symbol.
+     *
+     * @private
+     * @param {*} value The value to inspect.
+     * @returns {string|symbol} Returns the key.
+     */
+    function toKey(value) {
+      if (typeof value == 'string' || isSymbol(value)) {
+        return value;
+      }
+      var result = (value + '');
+      return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
+    }
+
+    /**
+     * Converts `func` to its source code.
+     *
+     * @private
+     * @param {Function} func The function to convert.
+     * @returns {string} Returns the source code.
+     */
+    function toSource(func) {
+      if (func != null) {
+        try {
+          return funcToString.call(func);
+        } catch (e) {}
+        try {
+          return (func + '');
+        } catch (e) {}
+      }
+      return '';
+    }
+
+    /**
+     * Updates wrapper `details` based on `bitmask` flags.
+     *
+     * @private
+     * @returns {Array} details The details to modify.
+     * @param {number} bitmask The bitmask flags. See `createWrap` for more details.
+     * @returns {Array} Returns `details`.
+     */
+    function updateWrapDetails(details, bitmask) {
+      arrayEach(wrapFlags, function(pair) {
+        var value = '_.' + pair[0];
+        if ((bitmask & pair[1]) && !arrayIncludes(details, value)) {
+          details.push(value);
+        }
+      });
+      return details.sort();
+    }
+
+    /**
+     * Creates a clone of `wrapper`.
+     *
+     * @private
+     * @param {Object} wrapper The wrapper to clone.
+     * @returns {Object} Returns the cloned wrapper.
+     */
+    function wrapperClone(wrapper) {
+      if (wrapper instanceof LazyWrapper) {
+        return wrapper.clone();
+      }
+      var result = new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__);
+      result.__actions__ = copyArray(wrapper.__actions__);
+      result.__index__  = wrapper.__index__;
+      result.__values__ = wrapper.__values__;
+      return result;
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates an array of elements split into groups the length of `size`.
+     * If `array` can't be split evenly, the final chunk will be the remaining
+     * elements.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to process.
+     * @param {number} [size=1] The length of each chunk
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the new array of chunks.
+     * @example
+     *
+     * _.chunk(['a', 'b', 'c', 'd'], 2);
+     * // => [['a', 'b'], ['c', 'd']]
+     *
+     * _.chunk(['a', 'b', 'c', 'd'], 3);
+     * // => [['a', 'b', 'c'], ['d']]
+     */
+    function chunk(array, size, guard) {
+      if ((guard ? isIterateeCall(array, size, guard) : size === undefined)) {
+        size = 1;
+      } else {
+        size = nativeMax(toInteger(size), 0);
+      }
+      var length = array == null ? 0 : array.length;
+      if (!length || size < 1) {
+        return [];
+      }
+      var index = 0,
+          resIndex = 0,
+          result = Array(nativeCeil(length / size));
+
+      while (index < length) {
+        result[resIndex++] = baseSlice(array, index, (index += size));
+      }
+      return result;
+    }
+
+    /**
+     * Creates an array with all falsey values removed. The values `false`, `null`,
+     * `0`, `""`, `undefined`, and `NaN` are falsey.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to compact.
+     * @returns {Array} Returns the new array of filtered values.
+     * @example
+     *
+     * _.compact([0, 1, false, 2, '', 3]);
+     * // => [1, 2, 3]
+     */
+    function compact(array) {
+      var index = -1,
+          length = array == null ? 0 : array.length,
+          resIndex = 0,
+          result = [];
+
+      while (++index < length) {
+        var value = array[index];
+        if (value) {
+          result[resIndex++] = value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Creates a new array concatenating `array` with any additional arrays
+     * and/or values.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to concatenate.
+     * @param {...*} [values] The values to concatenate.
+     * @returns {Array} Returns the new concatenated array.
+     * @example
+     *
+     * var array = [1];
+     * var other = _.concat(array, 2, [3], [[4]]);
+     *
+     * console.log(other);
+     * // => [1, 2, 3, [4]]
+     *
+     * console.log(array);
+     * // => [1]
+     */
+    function concat() {
+      var length = arguments.length;
+      if (!length) {
+        return [];
+      }
+      var args = Array(length - 1),
+          array = arguments[0],
+          index = length;
+
+      while (index--) {
+        args[index - 1] = arguments[index];
+      }
+      return arrayPush(isArray(array) ? copyArray(array) : [array], baseFlatten(args, 1));
+    }
+
+    /**
+     * Creates an array of `array` values not included in the other given arrays
+     * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+     * for equality comparisons. The order and references of result values are
+     * determined by the first array.
+     *
+     * **Note:** Unlike `_.pullAll`, this method returns a new array.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {...Array} [values] The values to exclude.
+     * @returns {Array} Returns the new array of filtered values.
+     * @see _.without, _.xor
+     * @example
+     *
+     * _.difference([2, 1], [2, 3]);
+     * // => [1]
+     */
+    var difference = baseRest(function(array, values) {
+      return isArrayLikeObject(array)
+        ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true))
+        : [];
+    });
+
+    /**
+     * This method is like `_.difference` except that it accepts `iteratee` which
+     * is invoked for each element of `array` and `values` to generate the criterion
+     * by which they're compared. The order and references of result values are
+     * determined by the first array. The iteratee is invoked with one argument:
+     * (value).
+     *
+     * **Note:** Unlike `_.pullAllBy`, this method returns a new array.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {...Array} [values] The values to exclude.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {Array} Returns the new array of filtered values.
+     * @example
+     *
+     * _.differenceBy([2.1, 1.2], [2.3, 3.4], Math.floor);
+     * // => [1.2]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');
+     * // => [{ 'x': 2 }]
+     */
+    var differenceBy = baseRest(function(array, values) {
+      var iteratee = last(values);
+      if (isArrayLikeObject(iteratee)) {
+        iteratee = undefined;
+      }
+      return isArrayLikeObject(array)
+        ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), getIteratee(iteratee, 2))
+        : [];
+    });
+
+    /**
+     * This method is like `_.difference` except that it accepts `comparator`
+     * which is invoked to compare elements of `array` to `values`. The order and
+     * references of result values are determined by the first array. The comparator
+     * is invoked with two arguments: (arrVal, othVal).
+     *
+     * **Note:** Unlike `_.pullAllWith`, this method returns a new array.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {...Array} [values] The values to exclude.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of filtered values.
+     * @example
+     *
+     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+     *
+     * _.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual);
+     * // => [{ 'x': 2, 'y': 1 }]
+     */
+    var differenceWith = baseRest(function(array, values) {
+      var comparator = last(values);
+      if (isArrayLikeObject(comparator)) {
+        comparator = undefined;
+      }
+      return isArrayLikeObject(array)
+        ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), undefined, comparator)
+        : [];
+    });
+
+    /**
+     * Creates a slice of `array` with `n` elements dropped from the beginning.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.5.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=1] The number of elements to drop.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.drop([1, 2, 3]);
+     * // => [2, 3]
+     *
+     * _.drop([1, 2, 3], 2);
+     * // => [3]
+     *
+     * _.drop([1, 2, 3], 5);
+     * // => []
+     *
+     * _.drop([1, 2, 3], 0);
+     * // => [1, 2, 3]
+     */
+    function drop(array, n, guard) {
+      var length = array == null ? 0 : array.length;
+      if (!length) {
+        return [];
+      }
+      n = (guard || n === undefined) ? 1 : toInteger(n);
+      return baseSlice(array, n < 0 ? 0 : n, length);
+    }
+
+    /**
+     * Creates a slice of `array` with `n` elements dropped from the end.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=1] The number of elements to drop.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.dropRight([1, 2, 3]);
+     * // => [1, 2]
+     *
+     * _.dropRight([1, 2, 3], 2);
+     * // => [1]
+     *
+     * _.dropRight([1, 2, 3], 5);
+     * // => []
+     *
+     * _.dropRight([1, 2, 3], 0);
+     * // => [1, 2, 3]
+     */
+    function dropRight(array, n, guard) {
+      var length = array == null ? 0 : array.length;
+      if (!length) {
+        return [];
+      }
+      n = (guard || n === undefined) ? 1 : toInteger(n);
+      n = length - n;
+      return baseSlice(array, 0, n < 0 ? 0 : n);
+    }
+
+    /**
+     * Creates a slice of `array` excluding elements dropped from the end.
+     * Elements are dropped until `predicate` returns falsey. The predicate is
+     * invoked with three arguments: (value, index, array).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': true },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': false }
+     * ];
+     *
+     * _.dropRightWhile(users, function(o) { return !o.active; });
+     * // => objects for ['barney']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.dropRightWhile(users, { 'user': 'pebbles', 'active': false });
+     * // => objects for ['barney', 'fred']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.dropRightWhile(users, ['active', false]);
+     * // => objects for ['barney']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.dropRightWhile(users, 'active');
+     * // => objects for ['barney', 'fred', 'pebbles']
+     */
+    function dropRightWhile(array, predicate) {
+      return (array && array.length)
+        ? baseWhile(array, getIteratee(predicate, 3), true, true)
+        : [];
+    }
+
+    /**
+     * Creates a slice of `array` excluding elements dropped from the beginning.
+     * Elements are dropped until `predicate` returns falsey. The predicate is
+     * invoked with three arguments: (value, index, array).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': false },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': true }
+     * ];
+     *
+     * _.dropWhile(users, function(o) { return !o.active; });
+     * // => objects for ['pebbles']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.dropWhile(users, { 'user': 'barney', 'active': false });
+     * // => objects for ['fred', 'pebbles']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.dropWhile(users, ['active', false]);
+     * // => objects for ['pebbles']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.dropWhile(users, 'active');
+     * // => objects for ['barney', 'fred', 'pebbles']
+     */
+    function dropWhile(array, predicate) {
+      return (array && array.length)
+        ? baseWhile(array, getIteratee(predicate, 3), true)
+        : [];
+    }
+
+    /**
+     * Fills elements of `array` with `value` from `start` up to, but not
+     * including, `end`.
+     *
+     * **Note:** This method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.2.0
+     * @category Array
+     * @param {Array} array The array to fill.
+     * @param {*} value The value to fill `array` with.
+     * @param {number} [start=0] The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [1, 2, 3];
+     *
+     * _.fill(array, 'a');
+     * console.log(array);
+     * // => ['a', 'a', 'a']
+     *
+     * _.fill(Array(3), 2);
+     * // => [2, 2, 2]
+     *
+     * _.fill([4, 6, 8, 10], '*', 1, 3);
+     * // => [4, '*', '*', 10]
+     */
+    function fill(array, value, start, end) {
+      var length = array == null ? 0 : array.length;
+      if (!length) {
+        return [];
+      }
+      if (start && typeof start != 'number' && isIterateeCall(array, value, start)) {
+        start = 0;
+        end = length;
+      }
+      return baseFill(array, value, start, end);
+    }
+
+    /**
+     * This method is like `_.find` except that it returns the index of the first
+     * element `predicate` returns truthy for instead of the element itself.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.1.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @param {number} [fromIndex=0] The index to search from.
+     * @returns {number} Returns the index of the found element, else `-1`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': false },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': true }
+     * ];
+     *
+     * _.findIndex(users, function(o) { return o.user == 'barney'; });
+     * // => 0
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.findIndex(users, { 'user': 'fred', 'active': false });
+     * // => 1
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.findIndex(users, ['active', false]);
+     * // => 0
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.findIndex(users, 'active');
+     * // => 2
+     */
+    function findIndex(array, predicate, fromIndex) {
+      var length = array == null ? 0 : array.length;
+      if (!length) {
+        return -1;
+      }
+      var index = fromIndex == null ? 0 : toInteger(fromIndex);
+      if (index < 0) {
+        index = nativeMax(length + index, 0);
+      }
+      return baseFindIndex(array, getIteratee(predicate, 3), index);
+    }
+
+    /**
+     * This method is like `_.findIndex` except that it iterates over elements
+     * of `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @param {number} [fromIndex=array.length-1] The index to search from.
+     * @returns {number} Returns the index of the found element, else `-1`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': true },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': false }
+     * ];
+     *
+     * _.findLastIndex(users, function(o) { return o.user == 'pebbles'; });
+     * // => 2
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.findLastIndex(users, { 'user': 'barney', 'active': true });
+     * // => 0
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.findLastIndex(users, ['active', false]);
+     * // => 2
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.findLastIndex(users, 'active');
+     * // => 0
+     */
+    function findLastIndex(array, predicate, fromIndex) {
+      var length = array == null ? 0 : array.length;
+      if (!length) {
+        return -1;
+      }
+      var index = length - 1;
+      if (fromIndex !== undefined) {
+        index = toInteger(fromIndex);
+        index = fromIndex < 0
+          ? nativeMax(length + index, 0)
+          : nativeMin(index, length - 1);
+      }
+      return baseFindIndex(array, getIteratee(predicate, 3), index, true);
+    }
+
+    /**
+     * Flattens `array` a single level deep.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to flatten.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * _.flatten([1, [2, [3, [4]], 5]]);
+     * // => [1, 2, [3, [4]], 5]
+     */
+    function flatten(array) {
+      var length = array == null ? 0 : array.length;
+      return length ? baseFlatten(array, 1) : [];
+    }
+
+    /**
+     * Recursively flattens `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to flatten.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * _.flattenDeep([1, [2, [3, [4]], 5]]);
+     * // => [1, 2, 3, 4, 5]
+     */
+    function flattenDeep(array) {
+      var length = array == null ? 0 : array.length;
+      return length ? baseFlatten(array, INFINITY) : [];
+    }
+
+    /**
+     * Recursively flatten `array` up to `depth` times.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.4.0
+     * @category Array
+     * @param {Array} array The array to flatten.
+     * @param {number} [depth=1] The maximum recursion depth.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * var array = [1, [2, [3, [4]], 5]];
+     *
+     * _.flattenDepth(array, 1);
+     * // => [1, 2, [3, [4]], 5]
+     *
+     * _.flattenDepth(array, 2);
+     * // => [1, 2, 3, [4], 5]
+     */
+    function flattenDepth(array, depth) {
+      var length = array == null ? 0 : array.length;
+      if (!length) {
+        return [];
+      }
+      depth = depth === undefined ? 1 : toInteger(depth);
+      return baseFlatten(array, depth);
+    }
+
+    /**
+     * The inverse of `_.toPairs`; this method returns an object composed
+     * from key-value `pairs`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} pairs The key-value pairs.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * _.fromPairs([['a', 1], ['b', 2]]);
+     * // => { 'a': 1, 'b': 2 }
+     */
+    function fromPairs(pairs) {
+      var index = -1,
+          length = pairs == null ? 0 : pairs.length,
+          result = {};
+
+      while (++index < length) {
+        var pair = pairs[index];
+        result[pair[0]] = pair[1];
+      }
+      return result;
+    }
+
+    /**
+     * Gets the first element of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @alias first
+     * @category Array
+     * @param {Array} array The array to query.
+     * @returns {*} Returns the first element of `array`.
+     * @example
+     *
+     * _.head([1, 2, 3]);
+     * // => 1
+     *
+     * _.head([]);
+     * // => undefined
+     */
+    function head(array) {
+      return (array && array.length) ? array[0] : undefined;
+    }
+
+    /**
+     * Gets the index at which the first occurrence of `value` is found in `array`
+     * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+     * for equality comparisons. If `fromIndex` is negative, it's used as the
+     * offset from the end of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {*} value The value to search for.
+     * @param {number} [fromIndex=0] The index to search from.
+     * @returns {number} Returns the index of the matched value, else `-1`.
+     * @example
+     *
+     * _.indexOf([1, 2, 1, 2], 2);
+     * // => 1
+     *
+     * // Search from the `fromIndex`.
+     * _.indexOf([1, 2, 1, 2], 2, 2);
+     * // => 3
+     */
+    function indexOf(array, value, fromIndex) {
+      var length = array == null ? 0 : array.length;
+      if (!length) {
+        return -1;
+      }
+      var index = fromIndex == null ? 0 : toInteger(fromIndex);
+      if (index < 0) {
+        index = nativeMax(length + index, 0);
+      }
+      return baseIndexOf(array, value, index);
+    }
+
+    /**
+     * Gets all but the last element of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.initial([1, 2, 3]);
+     * // => [1, 2]
+     */
+    function initial(array) {
+      var length = array == null ? 0 : array.length;
+      return length ? baseSlice(array, 0, -1) : [];
+    }
+
+    /**
+     * Creates an array of unique values that are included in all given arrays
+     * using [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+     * for equality comparisons. The order and references of result values are
+     * determined by the first array.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @returns {Array} Returns the new array of intersecting values.
+     * @example
+     *
+     * _.intersection([2, 1], [2, 3]);
+     * // => [2]
+     */
+    var intersection = baseRest(function(arrays) {
+      var mapped = arrayMap(arrays, castArrayLikeObject);
+      return (mapped.length && mapped[0] === arrays[0])
+        ? baseIntersection(mapped)
+        : [];
+    });
+
+    /**
+     * This method is like `_.intersection` except that it accepts `iteratee`
+     * which is invoked for each element of each `arrays` to generate the criterion
+     * by which they're compared. The order and references of result values are
+     * determined by the first array. The iteratee is invoked with one argument:
+     * (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {Array} Returns the new array of intersecting values.
+     * @example
+     *
+     * _.intersectionBy([2.1, 1.2], [2.3, 3.4], Math.floor);
+     * // => [2.1]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+     * // => [{ 'x': 1 }]
+     */
+    var intersectionBy = baseRest(function(arrays) {
+      var iteratee = last(arrays),
+          mapped = arrayMap(arrays, castArrayLikeObject);
+
+      if (iteratee === last(mapped)) {
+        iteratee = undefined;
+      } else {
+        mapped.pop();
+      }
+      return (mapped.length && mapped[0] === arrays[0])
+        ? baseIntersection(mapped, getIteratee(iteratee, 2))
+        : [];
+    });
+
+    /**
+     * This method is like `_.intersection` except that it accepts `comparator`
+     * which is invoked to compare elements of `arrays`. The order and references
+     * of result values are determined by the first array. The comparator is
+     * invoked with two arguments: (arrVal, othVal).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of intersecting values.
+     * @example
+     *
+     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+     *
+     * _.intersectionWith(objects, others, _.isEqual);
+     * // => [{ 'x': 1, 'y': 2 }]
+     */
+    var intersectionWith = baseRest(function(arrays) {
+      var comparator = last(arrays),
+          mapped = arrayMap(arrays, castArrayLikeObject);
+
+      comparator = typeof comparator == 'function' ? comparator : undefined;
+      if (comparator) {
+        mapped.pop();
+      }
+      return (mapped.length && mapped[0] === arrays[0])
+        ? baseIntersection(mapped, undefined, comparator)
+        : [];
+    });
+
+    /**
+     * Converts all elements in `array` into a string separated by `separator`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to convert.
+     * @param {string} [separator=','] The element separator.
+     * @returns {string} Returns the joined string.
+     * @example
+     *
+     * _.join(['a', 'b', 'c'], '~');
+     * // => 'a~b~c'
+     */
+    function join(array, separator) {
+      return array == null ? '' : nativeJoin.call(array, separator);
+    }
+
+    /**
+     * Gets the last element of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @returns {*} Returns the last element of `array`.
+     * @example
+     *
+     * _.last([1, 2, 3]);
+     * // => 3
+     */
+    function last(array) {
+      var length = array == null ? 0 : array.length;
+      return length ? array[length - 1] : undefined;
+    }
+
+    /**
+     * This method is like `_.indexOf` except that it iterates over elements of
+     * `array` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {*} value The value to search for.
+     * @param {number} [fromIndex=array.length-1] The index to search from.
+     * @returns {number} Returns the index of the matched value, else `-1`.
+     * @example
+     *
+     * _.lastIndexOf([1, 2, 1, 2], 2);
+     * // => 3
+     *
+     * // Search from the `fromIndex`.
+     * _.lastIndexOf([1, 2, 1, 2], 2, 2);
+     * // => 1
+     */
+    function lastIndexOf(array, value, fromIndex) {
+      var length = array == null ? 0 : array.length;
+      if (!length) {
+        return -1;
+      }
+      var index = length;
+      if (fromIndex !== undefined) {
+        index = toInteger(fromIndex);
+        index = index < 0 ? nativeMax(length + index, 0) : nativeMin(index, length - 1);
+      }
+      return value === value
+        ? strictLastIndexOf(array, value, index)
+        : baseFindIndex(array, baseIsNaN, index, true);
+    }
+
+    /**
+     * Gets the element at index `n` of `array`. If `n` is negative, the nth
+     * element from the end is returned.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.11.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=0] The index of the element to return.
+     * @returns {*} Returns the nth element of `array`.
+     * @example
+     *
+     * var array = ['a', 'b', 'c', 'd'];
+     *
+     * _.nth(array, 1);
+     * // => 'b'
+     *
+     * _.nth(array, -2);
+     * // => 'c';
+     */
+    function nth(array, n) {
+      return (array && array.length) ? baseNth(array, toInteger(n)) : undefined;
+    }
+
+    /**
+     * Removes all given values from `array` using
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * **Note:** Unlike `_.without`, this method mutates `array`. Use `_.remove`
+     * to remove elements from an array by predicate.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {...*} [values] The values to remove.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = ['a', 'b', 'c', 'a', 'b', 'c'];
+     *
+     * _.pull(array, 'a', 'c');
+     * console.log(array);
+     * // => ['b', 'b']
+     */
+    var pull = baseRest(pullAll);
+
+    /**
+     * This method is like `_.pull` except that it accepts an array of values to remove.
+     *
+     * **Note:** Unlike `_.difference`, this method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {Array} values The values to remove.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = ['a', 'b', 'c', 'a', 'b', 'c'];
+     *
+     * _.pullAll(array, ['a', 'c']);
+     * console.log(array);
+     * // => ['b', 'b']
+     */
+    function pullAll(array, values) {
+      return (array && array.length && values && values.length)
+        ? basePullAll(array, values)
+        : array;
+    }
+
+    /**
+     * This method is like `_.pullAll` except that it accepts `iteratee` which is
+     * invoked for each element of `array` and `values` to generate the criterion
+     * by which they're compared. The iteratee is invoked with one argument: (value).
+     *
+     * **Note:** Unlike `_.differenceBy`, this method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {Array} values The values to remove.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }];
+     *
+     * _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x');
+     * console.log(array);
+     * // => [{ 'x': 2 }]
+     */
+    function pullAllBy(array, values, iteratee) {
+      return (array && array.length && values && values.length)
+        ? basePullAll(array, values, getIteratee(iteratee, 2))
+        : array;
+    }
+
+    /**
+     * This method is like `_.pullAll` except that it accepts `comparator` which
+     * is invoked to compare elements of `array` to `values`. The comparator is
+     * invoked with two arguments: (arrVal, othVal).
+     *
+     * **Note:** Unlike `_.differenceWith`, this method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.6.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {Array} values The values to remove.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [{ 'x': 1, 'y': 2 }, { 'x': 3, 'y': 4 }, { 'x': 5, 'y': 6 }];
+     *
+     * _.pullAllWith(array, [{ 'x': 3, 'y': 4 }], _.isEqual);
+     * console.log(array);
+     * // => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }]
+     */
+    function pullAllWith(array, values, comparator) {
+      return (array && array.length && values && values.length)
+        ? basePullAll(array, values, undefined, comparator)
+        : array;
+    }
+
+    /**
+     * Removes elements from `array` corresponding to `indexes` and returns an
+     * array of removed elements.
+     *
+     * **Note:** Unlike `_.at`, this method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {...(number|number[])} [indexes] The indexes of elements to remove.
+     * @returns {Array} Returns the new array of removed elements.
+     * @example
+     *
+     * var array = ['a', 'b', 'c', 'd'];
+     * var pulled = _.pullAt(array, [1, 3]);
+     *
+     * console.log(array);
+     * // => ['a', 'c']
+     *
+     * console.log(pulled);
+     * // => ['b', 'd']
+     */
+    var pullAt = flatRest(function(array, indexes) {
+      var length = array == null ? 0 : array.length,
+          result = baseAt(array, indexes);
+
+      basePullAt(array, arrayMap(indexes, function(index) {
+        return isIndex(index, length) ? +index : index;
+      }).sort(compareAscending));
+
+      return result;
+    });
+
+    /**
+     * Removes all elements from `array` that `predicate` returns truthy for
+     * and returns an array of the removed elements. The predicate is invoked
+     * with three arguments: (value, index, array).
+     *
+     * **Note:** Unlike `_.filter`, this method mutates `array`. Use `_.pull`
+     * to pull elements from an array by value.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the new array of removed elements.
+     * @example
+     *
+     * var array = [1, 2, 3, 4];
+     * var evens = _.remove(array, function(n) {
+     *   return n % 2 == 0;
+     * });
+     *
+     * console.log(array);
+     * // => [1, 3]
+     *
+     * console.log(evens);
+     * // => [2, 4]
+     */
+    function remove(array, predicate) {
+      var result = [];
+      if (!(array && array.length)) {
+        return result;
+      }
+      var index = -1,
+          indexes = [],
+          length = array.length;
+
+      predicate = getIteratee(predicate, 3);
+      while (++index < length) {
+        var value = array[index];
+        if (predicate(value, index, array)) {
+          result.push(value);
+          indexes.push(index);
+        }
+      }
+      basePullAt(array, indexes);
+      return result;
+    }
+
+    /**
+     * Reverses `array` so that the first element becomes the last, the second
+     * element becomes the second to last, and so on.
+     *
+     * **Note:** This method mutates `array` and is based on
+     * [`Array#reverse`](https://mdn.io/Array/reverse).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [1, 2, 3];
+     *
+     * _.reverse(array);
+     * // => [3, 2, 1]
+     *
+     * console.log(array);
+     * // => [3, 2, 1]
+     */
+    function reverse(array) {
+      return array == null ? array : nativeReverse.call(array);
+    }
+
+    /**
+     * Creates a slice of `array` from `start` up to, but not including, `end`.
+     *
+     * **Note:** This method is used instead of
+     * [`Array#slice`](https://mdn.io/Array/slice) to ensure dense arrays are
+     * returned.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to slice.
+     * @param {number} [start=0] The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns the slice of `array`.
+     */
+    function slice(array, start, end) {
+      var length = array == null ? 0 : array.length;
+      if (!length) {
+        return [];
+      }
+      if (end && typeof end != 'number' && isIterateeCall(array, start, end)) {
+        start = 0;
+        end = length;
+      }
+      else {
+        start = start == null ? 0 : toInteger(start);
+        end = end === undefined ? length : toInteger(end);
+      }
+      return baseSlice(array, start, end);
+    }
+
+    /**
+     * Uses a binary search to determine the lowest index at which `value`
+     * should be inserted into `array` in order to maintain its sort order.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * _.sortedIndex([30, 50], 40);
+     * // => 1
+     */
+    function sortedIndex(array, value) {
+      return baseSortedIndex(array, value);
+    }
+
+    /**
+     * This method is like `_.sortedIndex` except that it accepts `iteratee`
+     * which is invoked for `value` and each element of `array` to compute their
+     * sort ranking. The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * var objects = [{ 'x': 4 }, { 'x': 5 }];
+     *
+     * _.sortedIndexBy(objects, { 'x': 4 }, function(o) { return o.x; });
+     * // => 0
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.sortedIndexBy(objects, { 'x': 4 }, 'x');
+     * // => 0
+     */
+    function sortedIndexBy(array, value, iteratee) {
+      return baseSortedIndexBy(array, value, getIteratee(iteratee, 2));
+    }
+
+    /**
+     * This method is like `_.indexOf` except that it performs a binary
+     * search on a sorted `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {*} value The value to search for.
+     * @returns {number} Returns the index of the matched value, else `-1`.
+     * @example
+     *
+     * _.sortedIndexOf([4, 5, 5, 5, 6], 5);
+     * // => 1
+     */
+    function sortedIndexOf(array, value) {
+      var length = array == null ? 0 : array.length;
+      if (length) {
+        var index = baseSortedIndex(array, value);
+        if (index < length && eq(array[index], value)) {
+          return index;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * This method is like `_.sortedIndex` except that it returns the highest
+     * index at which `value` should be inserted into `array` in order to
+     * maintain its sort order.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * _.sortedLastIndex([4, 5, 5, 5, 6], 5);
+     * // => 4
+     */
+    function sortedLastIndex(array, value) {
+      return baseSortedIndex(array, value, true);
+    }
+
+    /**
+     * This method is like `_.sortedLastIndex` except that it accepts `iteratee`
+     * which is invoked for `value` and each element of `array` to compute their
+     * sort ranking. The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * var objects = [{ 'x': 4 }, { 'x': 5 }];
+     *
+     * _.sortedLastIndexBy(objects, { 'x': 4 }, function(o) { return o.x; });
+     * // => 1
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.sortedLastIndexBy(objects, { 'x': 4 }, 'x');
+     * // => 1
+     */
+    function sortedLastIndexBy(array, value, iteratee) {
+      return baseSortedIndexBy(array, value, getIteratee(iteratee, 2), true);
+    }
+
+    /**
+     * This method is like `_.lastIndexOf` except that it performs a binary
+     * search on a sorted `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {*} value The value to search for.
+     * @returns {number} Returns the index of the matched value, else `-1`.
+     * @example
+     *
+     * _.sortedLastIndexOf([4, 5, 5, 5, 6], 5);
+     * // => 3
+     */
+    function sortedLastIndexOf(array, value) {
+      var length = array == null ? 0 : array.length;
+      if (length) {
+        var index = baseSortedIndex(array, value, true) - 1;
+        if (eq(array[index], value)) {
+          return index;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * This method is like `_.uniq` except that it's designed and optimized
+     * for sorted arrays.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @returns {Array} Returns the new duplicate free array.
+     * @example
+     *
+     * _.sortedUniq([1, 1, 2]);
+     * // => [1, 2]
+     */
+    function sortedUniq(array) {
+      return (array && array.length)
+        ? baseSortedUniq(array)
+        : [];
+    }
+
+    /**
+     * This method is like `_.uniqBy` except that it's designed and optimized
+     * for sorted arrays.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @returns {Array} Returns the new duplicate free array.
+     * @example
+     *
+     * _.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor);
+     * // => [1.1, 2.3]
+     */
+    function sortedUniqBy(array, iteratee) {
+      return (array && array.length)
+        ? baseSortedUniq(array, getIteratee(iteratee, 2))
+        : [];
+    }
+
+    /**
+     * Gets all but the first element of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.tail([1, 2, 3]);
+     * // => [2, 3]
+     */
+    function tail(array) {
+      var length = array == null ? 0 : array.length;
+      return length ? baseSlice(array, 1, length) : [];
+    }
+
+    /**
+     * Creates a slice of `array` with `n` elements taken from the beginning.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=1] The number of elements to take.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.take([1, 2, 3]);
+     * // => [1]
+     *
+     * _.take([1, 2, 3], 2);
+     * // => [1, 2]
+     *
+     * _.take([1, 2, 3], 5);
+     * // => [1, 2, 3]
+     *
+     * _.take([1, 2, 3], 0);
+     * // => []
+     */
+    function take(array, n, guard) {
+      if (!(array && array.length)) {
+        return [];
+      }
+      n = (guard || n === undefined) ? 1 : toInteger(n);
+      return baseSlice(array, 0, n < 0 ? 0 : n);
+    }
+
+    /**
+     * Creates a slice of `array` with `n` elements taken from the end.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=1] The number of elements to take.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.takeRight([1, 2, 3]);
+     * // => [3]
+     *
+     * _.takeRight([1, 2, 3], 2);
+     * // => [2, 3]
+     *
+     * _.takeRight([1, 2, 3], 5);
+     * // => [1, 2, 3]
+     *
+     * _.takeRight([1, 2, 3], 0);
+     * // => []
+     */
+    function takeRight(array, n, guard) {
+      var length = array == null ? 0 : array.length;
+      if (!length) {
+        return [];
+      }
+      n = (guard || n === undefined) ? 1 : toInteger(n);
+      n = length - n;
+      return baseSlice(array, n < 0 ? 0 : n, length);
+    }
+
+    /**
+     * Creates a slice of `array` with elements taken from the end. Elements are
+     * taken until `predicate` returns falsey. The predicate is invoked with
+     * three arguments: (value, index, array).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': true },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': false }
+     * ];
+     *
+     * _.takeRightWhile(users, function(o) { return !o.active; });
+     * // => objects for ['fred', 'pebbles']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.takeRightWhile(users, { 'user': 'pebbles', 'active': false });
+     * // => objects for ['pebbles']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.takeRightWhile(users, ['active', false]);
+     * // => objects for ['fred', 'pebbles']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.takeRightWhile(users, 'active');
+     * // => []
+     */
+    function takeRightWhile(array, predicate) {
+      return (array && array.length)
+        ? baseWhile(array, getIteratee(predicate, 3), false, true)
+        : [];
+    }
+
+    /**
+     * Creates a slice of `array` with elements taken from the beginning. Elements
+     * are taken until `predicate` returns falsey. The predicate is invoked with
+     * three arguments: (value, index, array).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': false },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': true }
+     * ];
+     *
+     * _.takeWhile(users, function(o) { return !o.active; });
+     * // => objects for ['barney', 'fred']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.takeWhile(users, { 'user': 'barney', 'active': false });
+     * // => objects for ['barney']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.takeWhile(users, ['active', false]);
+     * // => objects for ['barney', 'fred']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.takeWhile(users, 'active');
+     * // => []
+     */
+    function takeWhile(array, predicate) {
+      return (array && array.length)
+        ? baseWhile(array, getIteratee(predicate, 3))
+        : [];
+    }
+
+    /**
+     * Creates an array of unique values, in order, from all given arrays using
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @returns {Array} Returns the new array of combined values.
+     * @example
+     *
+     * _.union([2], [1, 2]);
+     * // => [2, 1]
+     */
+    var union = baseRest(function(arrays) {
+      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true));
+    });
+
+    /**
+     * This method is like `_.union` except that it accepts `iteratee` which is
+     * invoked for each element of each `arrays` to generate the criterion by
+     * which uniqueness is computed. Result values are chosen from the first
+     * array in which the value occurs. The iteratee is invoked with one argument:
+     * (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {Array} Returns the new array of combined values.
+     * @example
+     *
+     * _.unionBy([2.1], [1.2, 2.3], Math.floor);
+     * // => [2.1, 1.2]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.unionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+     * // => [{ 'x': 1 }, { 'x': 2 }]
+     */
+    var unionBy = baseRest(function(arrays) {
+      var iteratee = last(arrays);
+      if (isArrayLikeObject(iteratee)) {
+        iteratee = undefined;
+      }
+      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), getIteratee(iteratee, 2));
+    });
+
+    /**
+     * This method is like `_.union` except that it accepts `comparator` which
+     * is invoked to compare elements of `arrays`. Result values are chosen from
+     * the first array in which the value occurs. The comparator is invoked
+     * with two arguments: (arrVal, othVal).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of combined values.
+     * @example
+     *
+     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+     *
+     * _.unionWith(objects, others, _.isEqual);
+     * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
+     */
+    var unionWith = baseRest(function(arrays) {
+      var comparator = last(arrays);
+      comparator = typeof comparator == 'function' ? comparator : undefined;
+      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), undefined, comparator);
+    });
+
+    /**
+     * Creates a duplicate-free version of an array, using
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+     * for equality comparisons, in which only the first occurrence of each element
+     * is kept. The order of result values is determined by the order they occur
+     * in the array.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @returns {Array} Returns the new duplicate free array.
+     * @example
+     *
+     * _.uniq([2, 1, 2]);
+     * // => [2, 1]
+     */
+    function uniq(array) {
+      return (array && array.length) ? baseUniq(array) : [];
+    }
+
+    /**
+     * This method is like `_.uniq` except that it accepts `iteratee` which is
+     * invoked for each element in `array` to generate the criterion by which
+     * uniqueness is computed. The order of result values is determined by the
+     * order they occur in the array. The iteratee is invoked with one argument:
+     * (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {Array} Returns the new duplicate free array.
+     * @example
+     *
+     * _.uniqBy([2.1, 1.2, 2.3], Math.floor);
+     * // => [2.1, 1.2]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
+     * // => [{ 'x': 1 }, { 'x': 2 }]
+     */
+    function uniqBy(array, iteratee) {
+      return (array && array.length) ? baseUniq(array, getIteratee(iteratee, 2)) : [];
+    }
+
+    /**
+     * This method is like `_.uniq` except that it accepts `comparator` which
+     * is invoked to compare elements of `array`. The order of result values is
+     * determined by the order they occur in the array.The comparator is invoked
+     * with two arguments: (arrVal, othVal).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new duplicate free array.
+     * @example
+     *
+     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 2 }];
+     *
+     * _.uniqWith(objects, _.isEqual);
+     * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]
+     */
+    function uniqWith(array, comparator) {
+      comparator = typeof comparator == 'function' ? comparator : undefined;
+      return (array && array.length) ? baseUniq(array, undefined, comparator) : [];
+    }
+
+    /**
+     * This method is like `_.zip` except that it accepts an array of grouped
+     * elements and creates an array regrouping the elements to their pre-zip
+     * configuration.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.2.0
+     * @category Array
+     * @param {Array} array The array of grouped elements to process.
+     * @returns {Array} Returns the new array of regrouped elements.
+     * @example
+     *
+     * var zipped = _.zip(['a', 'b'], [1, 2], [true, false]);
+     * // => [['a', 1, true], ['b', 2, false]]
+     *
+     * _.unzip(zipped);
+     * // => [['a', 'b'], [1, 2], [true, false]]
+     */
+    function unzip(array) {
+      if (!(array && array.length)) {
+        return [];
+      }
+      var length = 0;
+      array = arrayFilter(array, function(group) {
+        if (isArrayLikeObject(group)) {
+          length = nativeMax(group.length, length);
+          return true;
+        }
+      });
+      return baseTimes(length, function(index) {
+        return arrayMap(array, baseProperty(index));
+      });
+    }
+
+    /**
+     * This method is like `_.unzip` except that it accepts `iteratee` to specify
+     * how regrouped values should be combined. The iteratee is invoked with the
+     * elements of each group: (...group).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.8.0
+     * @category Array
+     * @param {Array} array The array of grouped elements to process.
+     * @param {Function} [iteratee=_.identity] The function to combine
+     *  regrouped values.
+     * @returns {Array} Returns the new array of regrouped elements.
+     * @example
+     *
+     * var zipped = _.zip([1, 2], [10, 20], [100, 200]);
+     * // => [[1, 10, 100], [2, 20, 200]]
+     *
+     * _.unzipWith(zipped, _.add);
+     * // => [3, 30, 300]
+     */
+    function unzipWith(array, iteratee) {
+      if (!(array && array.length)) {
+        return [];
+      }
+      var result = unzip(array);
+      if (iteratee == null) {
+        return result;
+      }
+      return arrayMap(result, function(group) {
+        return apply(iteratee, undefined, group);
+      });
+    }
+
+    /**
+     * Creates an array excluding all given values using
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * **Note:** Unlike `_.pull`, this method returns a new array.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {...*} [values] The values to exclude.
+     * @returns {Array} Returns the new array of filtered values.
+     * @see _.difference, _.xor
+     * @example
+     *
+     * _.without([2, 1, 2, 3], 1, 2);
+     * // => [3]
+     */
+    var without = baseRest(function(array, values) {
+      return isArrayLikeObject(array)
+        ? baseDifference(array, values)
+        : [];
+    });
+
+    /**
+     * Creates an array of unique values that is the
+     * [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference)
+     * of the given arrays. The order of result values is determined by the order
+     * they occur in the arrays.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.4.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @returns {Array} Returns the new array of filtered values.
+     * @see _.difference, _.without
+     * @example
+     *
+     * _.xor([2, 1], [2, 3]);
+     * // => [1, 3]
+     */
+    var xor = baseRest(function(arrays) {
+      return baseXor(arrayFilter(arrays, isArrayLikeObject));
+    });
+
+    /**
+     * This method is like `_.xor` except that it accepts `iteratee` which is
+     * invoked for each element of each `arrays` to generate the criterion by
+     * which by which they're compared. The order of result values is determined
+     * by the order they occur in the arrays. The iteratee is invoked with one
+     * argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {Array} Returns the new array of filtered values.
+     * @example
+     *
+     * _.xorBy([2.1, 1.2], [2.3, 3.4], Math.floor);
+     * // => [1.2, 3.4]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+     * // => [{ 'x': 2 }]
+     */
+    var xorBy = baseRest(function(arrays) {
+      var iteratee = last(arrays);
+      if (isArrayLikeObject(iteratee)) {
+        iteratee = undefined;
+      }
+      return baseXor(arrayFilter(arrays, isArrayLikeObject), getIteratee(iteratee, 2));
+    });
+
+    /**
+     * This method is like `_.xor` except that it accepts `comparator` which is
+     * invoked to compare elements of `arrays`. The order of result values is
+     * determined by the order they occur in the arrays. The comparator is invoked
+     * with two arguments: (arrVal, othVal).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of filtered values.
+     * @example
+     *
+     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+     *
+     * _.xorWith(objects, others, _.isEqual);
+     * // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
+     */
+    var xorWith = baseRest(function(arrays) {
+      var comparator = last(arrays);
+      comparator = typeof comparator == 'function' ? comparator : undefined;
+      return baseXor(arrayFilter(arrays, isArrayLikeObject), undefined, comparator);
+    });
+
+    /**
+     * Creates an array of grouped elements, the first of which contains the
+     * first elements of the given arrays, the second of which contains the
+     * second elements of the given arrays, and so on.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to process.
+     * @returns {Array} Returns the new array of grouped elements.
+     * @example
+     *
+     * _.zip(['a', 'b'], [1, 2], [true, false]);
+     * // => [['a', 1, true], ['b', 2, false]]
+     */
+    var zip = baseRest(unzip);
+
+    /**
+     * This method is like `_.fromPairs` except that it accepts two arrays,
+     * one of property identifiers and one of corresponding values.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.4.0
+     * @category Array
+     * @param {Array} [props=[]] The property identifiers.
+     * @param {Array} [values=[]] The property values.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * _.zipObject(['a', 'b'], [1, 2]);
+     * // => { 'a': 1, 'b': 2 }
+     */
+    function zipObject(props, values) {
+      return baseZipObject(props || [], values || [], assignValue);
+    }
+
+    /**
+     * This method is like `_.zipObject` except that it supports property paths.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.1.0
+     * @category Array
+     * @param {Array} [props=[]] The property identifiers.
+     * @param {Array} [values=[]] The property values.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * _.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]);
+     * // => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } }
+     */
+    function zipObjectDeep(props, values) {
+      return baseZipObject(props || [], values || [], baseSet);
+    }
+
+    /**
+     * This method is like `_.zip` except that it accepts `iteratee` to specify
+     * how grouped values should be combined. The iteratee is invoked with the
+     * elements of each group: (...group).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.8.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to process.
+     * @param {Function} [iteratee=_.identity] The function to combine
+     *  grouped values.
+     * @returns {Array} Returns the new array of grouped elements.
+     * @example
+     *
+     * _.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) {
+     *   return a + b + c;
+     * });
+     * // => [111, 222]
+     */
+    var zipWith = baseRest(function(arrays) {
+      var length = arrays.length,
+          iteratee = length > 1 ? arrays[length - 1] : undefined;
+
+      iteratee = typeof iteratee == 'function' ? (arrays.pop(), iteratee) : undefined;
+      return unzipWith(arrays, iteratee);
+    });
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a `lodash` wrapper instance that wraps `value` with explicit method
+     * chain sequences enabled. The result of such sequences must be unwrapped
+     * with `_#value`.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.3.0
+     * @category Seq
+     * @param {*} value The value to wrap.
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'age': 36 },
+     *   { 'user': 'fred',    'age': 40 },
+     *   { 'user': 'pebbles', 'age': 1 }
+     * ];
+     *
+     * var youngest = _
+     *   .chain(users)
+     *   .sortBy('age')
+     *   .map(function(o) {
+     *     return o.user + ' is ' + o.age;
+     *   })
+     *   .head()
+     *   .value();
+     * // => 'pebbles is 1'
+     */
+    function chain(value) {
+      var result = lodash(value);
+      result.__chain__ = true;
+      return result;
+    }
+
+    /**
+     * This method invokes `interceptor` and returns `value`. The interceptor
+     * is invoked with one argument; (value). The purpose of this method is to
+     * "tap into" a method chain sequence in order to modify intermediate results.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Seq
+     * @param {*} value The value to provide to `interceptor`.
+     * @param {Function} interceptor The function to invoke.
+     * @returns {*} Returns `value`.
+     * @example
+     *
+     * _([1, 2, 3])
+     *  .tap(function(array) {
+     *    // Mutate input array.
+     *    array.pop();
+     *  })
+     *  .reverse()
+     *  .value();
+     * // => [2, 1]
+     */
+    function tap(value, interceptor) {
+      interceptor(value);
+      return value;
+    }
+
+    /**
+     * This method is like `_.tap` except that it returns the result of `interceptor`.
+     * The purpose of this method is to "pass thru" values replacing intermediate
+     * results in a method chain sequence.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Seq
+     * @param {*} value The value to provide to `interceptor`.
+     * @param {Function} interceptor The function to invoke.
+     * @returns {*} Returns the result of `interceptor`.
+     * @example
+     *
+     * _('  abc  ')
+     *  .chain()
+     *  .trim()
+     *  .thru(function(value) {
+     *    return [value];
+     *  })
+     *  .value();
+     * // => ['abc']
+     */
+    function thru(value, interceptor) {
+      return interceptor(value);
+    }
+
+    /**
+     * This method is the wrapper version of `_.at`.
+     *
+     * @name at
+     * @memberOf _
+     * @since 1.0.0
+     * @category Seq
+     * @param {...(string|string[])} [paths] The property paths to pick.
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
+     *
+     * _(object).at(['a[0].b.c', 'a[1]']).value();
+     * // => [3, 4]
+     */
+    var wrapperAt = flatRest(function(paths) {
+      var length = paths.length,
+          start = length ? paths[0] : 0,
+          value = this.__wrapped__,
+          interceptor = function(object) { return baseAt(object, paths); };
+
+      if (length > 1 || this.__actions__.length ||
+          !(value instanceof LazyWrapper) || !isIndex(start)) {
+        return this.thru(interceptor);
+      }
+      value = value.slice(start, +start + (length ? 1 : 0));
+      value.__actions__.push({
+        'func': thru,
+        'args': [interceptor],
+        'thisArg': undefined
+      });
+      return new LodashWrapper(value, this.__chain__).thru(function(array) {
+        if (length && !array.length) {
+          array.push(undefined);
+        }
+        return array;
+      });
+    });
+
+    /**
+     * Creates a `lodash` wrapper instance with explicit method chain sequences enabled.
+     *
+     * @name chain
+     * @memberOf _
+     * @since 0.1.0
+     * @category Seq
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36 },
+     *   { 'user': 'fred',   'age': 40 }
+     * ];
+     *
+     * // A sequence without explicit chaining.
+     * _(users).head();
+     * // => { 'user': 'barney', 'age': 36 }
+     *
+     * // A sequence with explicit chaining.
+     * _(users)
+     *   .chain()
+     *   .head()
+     *   .pick('user')
+     *   .value();
+     * // => { 'user': 'barney' }
+     */
+    function wrapperChain() {
+      return chain(this);
+    }
+
+    /**
+     * Executes the chain sequence and returns the wrapped result.
+     *
+     * @name commit
+     * @memberOf _
+     * @since 3.2.0
+     * @category Seq
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var array = [1, 2];
+     * var wrapped = _(array).push(3);
+     *
+     * console.log(array);
+     * // => [1, 2]
+     *
+     * wrapped = wrapped.commit();
+     * console.log(array);
+     * // => [1, 2, 3]
+     *
+     * wrapped.last();
+     * // => 3
+     *
+     * console.log(array);
+     * // => [1, 2, 3]
+     */
+    function wrapperCommit() {
+      return new LodashWrapper(this.value(), this.__chain__);
+    }
+
+    /**
+     * Gets the next value on a wrapped object following the
+     * [iterator protocol](https://mdn.io/iteration_protocols#iterator).
+     *
+     * @name next
+     * @memberOf _
+     * @since 4.0.0
+     * @category Seq
+     * @returns {Object} Returns the next iterator value.
+     * @example
+     *
+     * var wrapped = _([1, 2]);
+     *
+     * wrapped.next();
+     * // => { 'done': false, 'value': 1 }
+     *
+     * wrapped.next();
+     * // => { 'done': false, 'value': 2 }
+     *
+     * wrapped.next();
+     * // => { 'done': true, 'value': undefined }
+     */
+    function wrapperNext() {
+      if (this.__values__ === undefined) {
+        this.__values__ = toArray(this.value());
+      }
+      var done = this.__index__ >= this.__values__.length,
+          value = done ? undefined : this.__values__[this.__index__++];
+
+      return { 'done': done, 'value': value };
+    }
+
+    /**
+     * Enables the wrapper to be iterable.
+     *
+     * @name Symbol.iterator
+     * @memberOf _
+     * @since 4.0.0
+     * @category Seq
+     * @returns {Object} Returns the wrapper object.
+     * @example
+     *
+     * var wrapped = _([1, 2]);
+     *
+     * wrapped[Symbol.iterator]() === wrapped;
+     * // => true
+     *
+     * Array.from(wrapped);
+     * // => [1, 2]
+     */
+    function wrapperToIterator() {
+      return this;
+    }
+
+    /**
+     * Creates a clone of the chain sequence planting `value` as the wrapped value.
+     *
+     * @name plant
+     * @memberOf _
+     * @since 3.2.0
+     * @category Seq
+     * @param {*} value The value to plant.
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var wrapped = _([1, 2]).map(square);
+     * var other = wrapped.plant([3, 4]);
+     *
+     * other.value();
+     * // => [9, 16]
+     *
+     * wrapped.value();
+     * // => [1, 4]
+     */
+    function wrapperPlant(value) {
+      var result,
+          parent = this;
+
+      while (parent instanceof baseLodash) {
+        var clone = wrapperClone(parent);
+        clone.__index__ = 0;
+        clone.__values__ = undefined;
+        if (result) {
+          previous.__wrapped__ = clone;
+        } else {
+          result = clone;
+        }
+        var previous = clone;
+        parent = parent.__wrapped__;
+      }
+      previous.__wrapped__ = value;
+      return result;
+    }
+
+    /**
+     * This method is the wrapper version of `_.reverse`.
+     *
+     * **Note:** This method mutates the wrapped array.
+     *
+     * @name reverse
+     * @memberOf _
+     * @since 0.1.0
+     * @category Seq
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var array = [1, 2, 3];
+     *
+     * _(array).reverse().value()
+     * // => [3, 2, 1]
+     *
+     * console.log(array);
+     * // => [3, 2, 1]
+     */
+    function wrapperReverse() {
+      var value = this.__wrapped__;
+      if (value instanceof LazyWrapper) {
+        var wrapped = value;
+        if (this.__actions__.length) {
+          wrapped = new LazyWrapper(this);
+        }
+        wrapped = wrapped.reverse();
+        wrapped.__actions__.push({
+          'func': thru,
+          'args': [reverse],
+          'thisArg': undefined
+        });
+        return new LodashWrapper(wrapped, this.__chain__);
+      }
+      return this.thru(reverse);
+    }
+
+    /**
+     * Executes the chain sequence to resolve the unwrapped value.
+     *
+     * @name value
+     * @memberOf _
+     * @since 0.1.0
+     * @alias toJSON, valueOf
+     * @category Seq
+     * @returns {*} Returns the resolved unwrapped value.
+     * @example
+     *
+     * _([1, 2, 3]).value();
+     * // => [1, 2, 3]
+     */
+    function wrapperValue() {
+      return baseWrapperValue(this.__wrapped__, this.__actions__);
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of `collection` thru `iteratee`. The corresponding value of
+     * each key is the number of times the key was returned by `iteratee`. The
+     * iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.5.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The iteratee to transform keys.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * _.countBy([6.1, 4.2, 6.3], Math.floor);
+     * // => { '4': 1, '6': 2 }
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.countBy(['one', 'two', 'three'], 'length');
+     * // => { '3': 2, '5': 1 }
+     */
+    var countBy = createAggregator(function(result, value, key) {
+      if (hasOwnProperty.call(result, key)) {
+        ++result[key];
+      } else {
+        baseAssignValue(result, key, 1);
+      }
+    });
+
+    /**
+     * Checks if `predicate` returns truthy for **all** elements of `collection`.
+     * Iteration is stopped once `predicate` returns falsey. The predicate is
+     * invoked with three arguments: (value, index|key, collection).
+     *
+     * **Note:** This method returns `true` for
+     * [empty collections](https://en.wikipedia.org/wiki/Empty_set) because
+     * [everything is true](https://en.wikipedia.org/wiki/Vacuous_truth) of
+     * elements of empty collections.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {boolean} Returns `true` if all elements pass the predicate check,
+     *  else `false`.
+     * @example
+     *
+     * _.every([true, 1, null, 'yes'], Boolean);
+     * // => false
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': false },
+     *   { 'user': 'fred',   'age': 40, 'active': false }
+     * ];
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.every(users, { 'user': 'barney', 'active': false });
+     * // => false
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.every(users, ['active', false]);
+     * // => true
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.every(users, 'active');
+     * // => false
+     */
+    function every(collection, predicate, guard) {
+      var func = isArray(collection) ? arrayEvery : baseEvery;
+      if (guard && isIterateeCall(collection, predicate, guard)) {
+        predicate = undefined;
+      }
+      return func(collection, getIteratee(predicate, 3));
+    }
+
+    /**
+     * Iterates over elements of `collection`, returning an array of all elements
+     * `predicate` returns truthy for. The predicate is invoked with three
+     * arguments: (value, index|key, collection).
+     *
+     * **Note:** Unlike `_.remove`, this method returns a new array.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the new filtered array.
+     * @see _.reject
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': true },
+     *   { 'user': 'fred',   'age': 40, 'active': false }
+     * ];
+     *
+     * _.filter(users, function(o) { return !o.active; });
+     * // => objects for ['fred']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.filter(users, { 'age': 36, 'active': true });
+     * // => objects for ['barney']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.filter(users, ['active', false]);
+     * // => objects for ['fred']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.filter(users, 'active');
+     * // => objects for ['barney']
+     */
+    function filter(collection, predicate) {
+      var func = isArray(collection) ? arrayFilter : baseFilter;
+      return func(collection, getIteratee(predicate, 3));
+    }
+
+    /**
+     * Iterates over elements of `collection`, returning the first element
+     * `predicate` returns truthy for. The predicate is invoked with three
+     * arguments: (value, index|key, collection).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to inspect.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @param {number} [fromIndex=0] The index to search from.
+     * @returns {*} Returns the matched element, else `undefined`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'age': 36, 'active': true },
+     *   { 'user': 'fred',    'age': 40, 'active': false },
+     *   { 'user': 'pebbles', 'age': 1,  'active': true }
+     * ];
+     *
+     * _.find(users, function(o) { return o.age < 40; });
+     * // => object for 'barney'
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.find(users, { 'age': 1, 'active': true });
+     * // => object for 'pebbles'
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.find(users, ['active', false]);
+     * // => object for 'fred'
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.find(users, 'active');
+     * // => object for 'barney'
+     */
+    var find = createFind(findIndex);
+
+    /**
+     * This method is like `_.find` except that it iterates over elements of
+     * `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to inspect.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @param {number} [fromIndex=collection.length-1] The index to search from.
+     * @returns {*} Returns the matched element, else `undefined`.
+     * @example
+     *
+     * _.findLast([1, 2, 3, 4], function(n) {
+     *   return n % 2 == 1;
+     * });
+     * // => 3
+     */
+    var findLast = createFind(findLastIndex);
+
+    /**
+     * Creates a flattened array of values by running each element in `collection`
+     * thru `iteratee` and flattening the mapped results. The iteratee is invoked
+     * with three arguments: (value, index|key, collection).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * function duplicate(n) {
+     *   return [n, n];
+     * }
+     *
+     * _.flatMap([1, 2], duplicate);
+     * // => [1, 1, 2, 2]
+     */
+    function flatMap(collection, iteratee) {
+      return baseFlatten(map(collection, iteratee), 1);
+    }
+
+    /**
+     * This method is like `_.flatMap` except that it recursively flattens the
+     * mapped results.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.7.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * function duplicate(n) {
+     *   return [[[n, n]]];
+     * }
+     *
+     * _.flatMapDeep([1, 2], duplicate);
+     * // => [1, 1, 2, 2]
+     */
+    function flatMapDeep(collection, iteratee) {
+      return baseFlatten(map(collection, iteratee), INFINITY);
+    }
+
+    /**
+     * This method is like `_.flatMap` except that it recursively flattens the
+     * mapped results up to `depth` times.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.7.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {number} [depth=1] The maximum recursion depth.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * function duplicate(n) {
+     *   return [[[n, n]]];
+     * }
+     *
+     * _.flatMapDepth([1, 2], duplicate, 2);
+     * // => [[1, 1], [2, 2]]
+     */
+    function flatMapDepth(collection, iteratee, depth) {
+      depth = depth === undefined ? 1 : toInteger(depth);
+      return baseFlatten(map(collection, iteratee), depth);
+    }
+
+    /**
+     * Iterates over elements of `collection` and invokes `iteratee` for each element.
+     * The iteratee is invoked with three arguments: (value, index|key, collection).
+     * Iteratee functions may exit iteration early by explicitly returning `false`.
+     *
+     * **Note:** As with other "Collections" methods, objects with a "length"
+     * property are iterated like arrays. To avoid this behavior use `_.forIn`
+     * or `_.forOwn` for object iteration.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @alias each
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Array|Object} Returns `collection`.
+     * @see _.forEachRight
+     * @example
+     *
+     * _.forEach([1, 2], function(value) {
+     *   console.log(value);
+     * });
+     * // => Logs `1` then `2`.
+     *
+     * _.forEach({ 'a': 1, 'b': 2 }, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => Logs 'a' then 'b' (iteration order is not guaranteed).
+     */
+    function forEach(collection, iteratee) {
+      var func = isArray(collection) ? arrayEach : baseEach;
+      return func(collection, getIteratee(iteratee, 3));
+    }
+
+    /**
+     * This method is like `_.forEach` except that it iterates over elements of
+     * `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @alias eachRight
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Array|Object} Returns `collection`.
+     * @see _.forEach
+     * @example
+     *
+     * _.forEachRight([1, 2], function(value) {
+     *   console.log(value);
+     * });
+     * // => Logs `2` then `1`.
+     */
+    function forEachRight(collection, iteratee) {
+      var func = isArray(collection) ? arrayEachRight : baseEachRight;
+      return func(collection, getIteratee(iteratee, 3));
+    }
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of `collection` thru `iteratee`. The order of grouped values
+     * is determined by the order they occur in `collection`. The corresponding
+     * value of each key is an array of elements responsible for generating the
+     * key. The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The iteratee to transform keys.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * _.groupBy([6.1, 4.2, 6.3], Math.floor);
+     * // => { '4': [4.2], '6': [6.1, 6.3] }
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.groupBy(['one', 'two', 'three'], 'length');
+     * // => { '3': ['one', 'two'], '5': ['three'] }
+     */
+    var groupBy = createAggregator(function(result, value, key) {
+      if (hasOwnProperty.call(result, key)) {
+        result[key].push(value);
+      } else {
+        baseAssignValue(result, key, [value]);
+      }
+    });
+
+    /**
+     * Checks if `value` is in `collection`. If `collection` is a string, it's
+     * checked for a substring of `value`, otherwise
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+     * is used for equality comparisons. If `fromIndex` is negative, it's used as
+     * the offset from the end of `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to inspect.
+     * @param {*} value The value to search for.
+     * @param {number} [fromIndex=0] The index to search from.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
+     * @returns {boolean} Returns `true` if `value` is found, else `false`.
+     * @example
+     *
+     * _.includes([1, 2, 3], 1);
+     * // => true
+     *
+     * _.includes([1, 2, 3], 1, 2);
+     * // => false
+     *
+     * _.includes({ 'a': 1, 'b': 2 }, 1);
+     * // => true
+     *
+     * _.includes('abcd', 'bc');
+     * // => true
+     */
+    function includes(collection, value, fromIndex, guard) {
+      collection = isArrayLike(collection) ? collection : values(collection);
+      fromIndex = (fromIndex && !guard) ? toInteger(fromIndex) : 0;
+
+      var length = collection.length;
+      if (fromIndex < 0) {
+        fromIndex = nativeMax(length + fromIndex, 0);
+      }
+      return isString(collection)
+        ? (fromIndex <= length && collection.indexOf(value, fromIndex) > -1)
+        : (!!length && baseIndexOf(collection, value, fromIndex) > -1);
+    }
+
+    /**
+     * Invokes the method at `path` of each element in `collection`, returning
+     * an array of the results of each invoked method. Any additional arguments
+     * are provided to each invoked method. If `path` is a function, it's invoked
+     * for, and `this` bound to, each element in `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|string} path The path of the method to invoke or
+     *  the function invoked per iteration.
+     * @param {...*} [args] The arguments to invoke each method with.
+     * @returns {Array} Returns the array of results.
+     * @example
+     *
+     * _.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort');
+     * // => [[1, 5, 7], [1, 2, 3]]
+     *
+     * _.invokeMap([123, 456], String.prototype.split, '');
+     * // => [['1', '2', '3'], ['4', '5', '6']]
+     */
+    var invokeMap = baseRest(function(collection, path, args) {
+      var index = -1,
+          isFunc = typeof path == 'function',
+          result = isArrayLike(collection) ? Array(collection.length) : [];
+
+      baseEach(collection, function(value) {
+        result[++index] = isFunc ? apply(path, value, args) : baseInvoke(value, path, args);
+      });
+      return result;
+    });
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of `collection` thru `iteratee`. The corresponding value of
+     * each key is the last element responsible for generating the key. The
+     * iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The iteratee to transform keys.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * var array = [
+     *   { 'dir': 'left', 'code': 97 },
+     *   { 'dir': 'right', 'code': 100 }
+     * ];
+     *
+     * _.keyBy(array, function(o) {
+     *   return String.fromCharCode(o.code);
+     * });
+     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+     *
+     * _.keyBy(array, 'dir');
+     * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
+     */
+    var keyBy = createAggregator(function(result, value, key) {
+      baseAssignValue(result, key, value);
+    });
+
+    /**
+     * Creates an array of values by running each element in `collection` thru
+     * `iteratee`. The iteratee is invoked with three arguments:
+     * (value, index|key, collection).
+     *
+     * Many lodash methods are guarded to work as iteratees for methods like
+     * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`.
+     *
+     * The guarded methods are:
+     * `ary`, `chunk`, `curry`, `curryRight`, `drop`, `dropRight`, `every`,
+     * `fill`, `invert`, `parseInt`, `random`, `range`, `rangeRight`, `repeat`,
+     * `sampleSize`, `slice`, `some`, `sortBy`, `split`, `take`, `takeRight`,
+     * `template`, `trim`, `trimEnd`, `trimStart`, and `words`
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the new mapped array.
+     * @example
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * _.map([4, 8], square);
+     * // => [16, 64]
+     *
+     * _.map({ 'a': 4, 'b': 8 }, square);
+     * // => [16, 64] (iteration order is not guaranteed)
+     *
+     * var users = [
+     *   { 'user': 'barney' },
+     *   { 'user': 'fred' }
+     * ];
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.map(users, 'user');
+     * // => ['barney', 'fred']
+     */
+    function map(collection, iteratee) {
+      var func = isArray(collection) ? arrayMap : baseMap;
+      return func(collection, getIteratee(iteratee, 3));
+    }
+
+    /**
+     * This method is like `_.sortBy` except that it allows specifying the sort
+     * orders of the iteratees to sort by. If `orders` is unspecified, all values
+     * are sorted in ascending order. Otherwise, specify an order of "desc" for
+     * descending or "asc" for ascending sort order of corresponding values.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array[]|Function[]|Object[]|string[]} [iteratees=[_.identity]]
+     *  The iteratees to sort by.
+     * @param {string[]} [orders] The sort orders of `iteratees`.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
+     * @returns {Array} Returns the new sorted array.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'fred',   'age': 48 },
+     *   { 'user': 'barney', 'age': 34 },
+     *   { 'user': 'fred',   'age': 40 },
+     *   { 'user': 'barney', 'age': 36 }
+     * ];
+     *
+     * // Sort by `user` in ascending order and by `age` in descending order.
+     * _.orderBy(users, ['user', 'age'], ['asc', 'desc']);
+     * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
+     */
+    function orderBy(collection, iteratees, orders, guard) {
+      if (collection == null) {
+        return [];
+      }
+      if (!isArray(iteratees)) {
+        iteratees = iteratees == null ? [] : [iteratees];
+      }
+      orders = guard ? undefined : orders;
+      if (!isArray(orders)) {
+        orders = orders == null ? [] : [orders];
+      }
+      return baseOrderBy(collection, iteratees, orders);
+    }
+
+    /**
+     * Creates an array of elements split into two groups, the first of which
+     * contains elements `predicate` returns truthy for, the second of which
+     * contains elements `predicate` returns falsey for. The predicate is
+     * invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the array of grouped elements.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'age': 36, 'active': false },
+     *   { 'user': 'fred',    'age': 40, 'active': true },
+     *   { 'user': 'pebbles', 'age': 1,  'active': false }
+     * ];
+     *
+     * _.partition(users, function(o) { return o.active; });
+     * // => objects for [['fred'], ['barney', 'pebbles']]
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.partition(users, { 'age': 1, 'active': false });
+     * // => objects for [['pebbles'], ['barney', 'fred']]
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.partition(users, ['active', false]);
+     * // => objects for [['barney', 'pebbles'], ['fred']]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.partition(users, 'active');
+     * // => objects for [['fred'], ['barney', 'pebbles']]
+     */
+    var partition = createAggregator(function(result, value, key) {
+      result[key ? 0 : 1].push(value);
+    }, function() { return [[], []]; });
+
+    /**
+     * Reduces `collection` to a value which is the accumulated result of running
+     * each element in `collection` thru `iteratee`, where each successive
+     * invocation is supplied the return value of the previous. If `accumulator`
+     * is not given, the first element of `collection` is used as the initial
+     * value. The iteratee is invoked with four arguments:
+     * (accumulator, value, index|key, collection).
+     *
+     * Many lodash methods are guarded to work as iteratees for methods like
+     * `_.reduce`, `_.reduceRight`, and `_.transform`.
+     *
+     * The guarded methods are:
+     * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`,
+     * and `sortBy`
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [accumulator] The initial value.
+     * @returns {*} Returns the accumulated value.
+     * @see _.reduceRight
+     * @example
+     *
+     * _.reduce([1, 2], function(sum, n) {
+     *   return sum + n;
+     * }, 0);
+     * // => 3
+     *
+     * _.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
+     *   (result[value] || (result[value] = [])).push(key);
+     *   return result;
+     * }, {});
+     * // => { '1': ['a', 'c'], '2': ['b'] } (iteration order is not guaranteed)
+     */
+    function reduce(collection, iteratee, accumulator) {
+      var func = isArray(collection) ? arrayReduce : baseReduce,
+          initAccum = arguments.length < 3;
+
+      return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEach);
+    }
+
+    /**
+     * This method is like `_.reduce` except that it iterates over elements of
+     * `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [accumulator] The initial value.
+     * @returns {*} Returns the accumulated value.
+     * @see _.reduce
+     * @example
+     *
+     * var array = [[0, 1], [2, 3], [4, 5]];
+     *
+     * _.reduceRight(array, function(flattened, other) {
+     *   return flattened.concat(other);
+     * }, []);
+     * // => [4, 5, 2, 3, 0, 1]
+     */
+    function reduceRight(collection, iteratee, accumulator) {
+      var func = isArray(collection) ? arrayReduceRight : baseReduce,
+          initAccum = arguments.length < 3;
+
+      return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEachRight);
+    }
+
+    /**
+     * The opposite of `_.filter`; this method returns the elements of `collection`
+     * that `predicate` does **not** return truthy for.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the new filtered array.
+     * @see _.filter
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': false },
+     *   { 'user': 'fred',   'age': 40, 'active': true }
+     * ];
+     *
+     * _.reject(users, function(o) { return !o.active; });
+     * // => objects for ['fred']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.reject(users, { 'age': 40, 'active': true });
+     * // => objects for ['barney']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.reject(users, ['active', false]);
+     * // => objects for ['fred']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.reject(users, 'active');
+     * // => objects for ['barney']
+     */
+    function reject(collection, predicate) {
+      var func = isArray(collection) ? arrayFilter : baseFilter;
+      return func(collection, negate(getIteratee(predicate, 3)));
+    }
+
+    /**
+     * Gets a random element from `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to sample.
+     * @returns {*} Returns the random element.
+     * @example
+     *
+     * _.sample([1, 2, 3, 4]);
+     * // => 2
+     */
+    function sample(collection) {
+      var func = isArray(collection) ? arraySample : baseSample;
+      return func(collection);
+    }
+
+    /**
+     * Gets `n` random elements at unique keys from `collection` up to the
+     * size of `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to sample.
+     * @param {number} [n=1] The number of elements to sample.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the random elements.
+     * @example
+     *
+     * _.sampleSize([1, 2, 3], 2);
+     * // => [3, 1]
+     *
+     * _.sampleSize([1, 2, 3], 4);
+     * // => [2, 3, 1]
+     */
+    function sampleSize(collection, n, guard) {
+      if ((guard ? isIterateeCall(collection, n, guard) : n === undefined)) {
+        n = 1;
+      } else {
+        n = toInteger(n);
+      }
+      var func = isArray(collection) ? arraySampleSize : baseSampleSize;
+      return func(collection, n);
+    }
+
+    /**
+     * Creates an array of shuffled values, using a version of the
+     * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to shuffle.
+     * @returns {Array} Returns the new shuffled array.
+     * @example
+     *
+     * _.shuffle([1, 2, 3, 4]);
+     * // => [4, 1, 3, 2]
+     */
+    function shuffle(collection) {
+      var func = isArray(collection) ? arrayShuffle : baseShuffle;
+      return func(collection);
+    }
+
+    /**
+     * Gets the size of `collection` by returning its length for array-like
+     * values or the number of own enumerable string keyed properties for objects.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to inspect.
+     * @returns {number} Returns the collection size.
+     * @example
+     *
+     * _.size([1, 2, 3]);
+     * // => 3
+     *
+     * _.size({ 'a': 1, 'b': 2 });
+     * // => 2
+     *
+     * _.size('pebbles');
+     * // => 7
+     */
+    function size(collection) {
+      if (collection == null) {
+        return 0;
+      }
+      if (isArrayLike(collection)) {
+        return isString(collection) ? stringSize(collection) : collection.length;
+      }
+      var tag = getTag(collection);
+      if (tag == mapTag || tag == setTag) {
+        return collection.size;
+      }
+      return baseKeys(collection).length;
+    }
+
+    /**
+     * Checks if `predicate` returns truthy for **any** element of `collection`.
+     * Iteration is stopped once `predicate` returns truthy. The predicate is
+     * invoked with three arguments: (value, index|key, collection).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {boolean} Returns `true` if any element passes the predicate check,
+     *  else `false`.
+     * @example
+     *
+     * _.some([null, 0, 'yes', false], Boolean);
+     * // => true
+     *
+     * var users = [
+     *   { 'user': 'barney', 'active': true },
+     *   { 'user': 'fred',   'active': false }
+     * ];
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.some(users, { 'user': 'barney', 'active': false });
+     * // => false
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.some(users, ['active', false]);
+     * // => true
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.some(users, 'active');
+     * // => true
+     */
+    function some(collection, predicate, guard) {
+      var func = isArray(collection) ? arraySome : baseSome;
+      if (guard && isIterateeCall(collection, predicate, guard)) {
+        predicate = undefined;
+      }
+      return func(collection, getIteratee(predicate, 3));
+    }
+
+    /**
+     * Creates an array of elements, sorted in ascending order by the results of
+     * running each element in a collection thru each iteratee. This method
+     * performs a stable sort, that is, it preserves the original sort order of
+     * equal elements. The iteratees are invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {...(Function|Function[])} [iteratees=[_.identity]]
+     *  The iteratees to sort by.
+     * @returns {Array} Returns the new sorted array.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'fred',   'age': 48 },
+     *   { 'user': 'barney', 'age': 36 },
+     *   { 'user': 'fred',   'age': 40 },
+     *   { 'user': 'barney', 'age': 34 }
+     * ];
+     *
+     * _.sortBy(users, [function(o) { return o.user; }]);
+     * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
+     *
+     * _.sortBy(users, ['user', 'age']);
+     * // => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]
+     */
+    var sortBy = baseRest(function(collection, iteratees) {
+      if (collection == null) {
+        return [];
+      }
+      var length = iteratees.length;
+      if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) {
+        iteratees = [];
+      } else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) {
+        iteratees = [iteratees[0]];
+      }
+      return baseOrderBy(collection, baseFlatten(iteratees, 1), []);
+    });
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Gets the timestamp of the number of milliseconds that have elapsed since
+     * the Unix epoch (1 January 1970 00:00:00 UTC).
+     *
+     * @static
+     * @memberOf _
+     * @since 2.4.0
+     * @category Date
+     * @returns {number} Returns the timestamp.
+     * @example
+     *
+     * _.defer(function(stamp) {
+     *   console.log(_.now() - stamp);
+     * }, _.now());
+     * // => Logs the number of milliseconds it took for the deferred invocation.
+     */
+    var now = ctxNow || function() {
+      return root.Date.now();
+    };
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * The opposite of `_.before`; this method creates a function that invokes
+     * `func` once it's called `n` or more times.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {number} n The number of calls before `func` is invoked.
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * var saves = ['profile', 'settings'];
+     *
+     * var done = _.after(saves.length, function() {
+     *   console.log('done saving!');
+     * });
+     *
+     * _.forEach(saves, function(type) {
+     *   asyncSave({ 'type': type, 'complete': done });
+     * });
+     * // => Logs 'done saving!' after the two async saves have completed.
+     */
+    function after(n, func) {
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      n = toInteger(n);
+      return function() {
+        if (--n < 1) {
+          return func.apply(this, arguments);
+        }
+      };
+    }
+
+    /**
+     * Creates a function that invokes `func`, with up to `n` arguments,
+     * ignoring any additional arguments.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Function
+     * @param {Function} func The function to cap arguments for.
+     * @param {number} [n=func.length] The arity cap.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Function} Returns the new capped function.
+     * @example
+     *
+     * _.map(['6', '8', '10'], _.ary(parseInt, 1));
+     * // => [6, 8, 10]
+     */
+    function ary(func, n, guard) {
+      n = guard ? undefined : n;
+      n = (func && n == null) ? func.length : n;
+      return createWrap(func, WRAP_ARY_FLAG, undefined, undefined, undefined, undefined, n);
+    }
+
+    /**
+     * Creates a function that invokes `func`, with the `this` binding and arguments
+     * of the created function, while it's called less than `n` times. Subsequent
+     * calls to the created function return the result of the last `func` invocation.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Function
+     * @param {number} n The number of calls at which `func` is no longer invoked.
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * jQuery(element).on('click', _.before(5, addContactToList));
+     * // => Allows adding up to 4 contacts to the list.
+     */
+    function before(n, func) {
+      var result;
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      n = toInteger(n);
+      return function() {
+        if (--n > 0) {
+          result = func.apply(this, arguments);
+        }
+        if (n <= 1) {
+          func = undefined;
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Creates a function that invokes `func` with the `this` binding of `thisArg`
+     * and `partials` prepended to the arguments it receives.
+     *
+     * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds,
+     * may be used as a placeholder for partially applied arguments.
+     *
+     * **Note:** Unlike native `Function#bind`, this method doesn't set the "length"
+     * property of bound functions.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to bind.
+     * @param {*} thisArg The `this` binding of `func`.
+     * @param {...*} [partials] The arguments to be partially applied.
+     * @returns {Function} Returns the new bound function.
+     * @example
+     *
+     * function greet(greeting, punctuation) {
+     *   return greeting + ' ' + this.user + punctuation;
+     * }
+     *
+     * var object = { 'user': 'fred' };
+     *
+     * var bound = _.bind(greet, object, 'hi');
+     * bound('!');
+     * // => 'hi fred!'
+     *
+     * // Bound with placeholders.
+     * var bound = _.bind(greet, object, _, '!');
+     * bound('hi');
+     * // => 'hi fred!'
+     */
+    var bind = baseRest(function(func, thisArg, partials) {
+      var bitmask = WRAP_BIND_FLAG;
+      if (partials.length) {
+        var holders = replaceHolders(partials, getHolder(bind));
+        bitmask |= WRAP_PARTIAL_FLAG;
+      }
+      return createWrap(func, bitmask, thisArg, partials, holders);
+    });
+
+    /**
+     * Creates a function that invokes the method at `object[key]` with `partials`
+     * prepended to the arguments it receives.
+     *
+     * This method differs from `_.bind` by allowing bound functions to reference
+     * methods that may be redefined or don't yet exist. See
+     * [Peter Michaux's article](http://peter.michaux.ca/articles/lazy-function-definition-pattern)
+     * for more details.
+     *
+     * The `_.bindKey.placeholder` value, which defaults to `_` in monolithic
+     * builds, may be used as a placeholder for partially applied arguments.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.10.0
+     * @category Function
+     * @param {Object} object The object to invoke the method on.
+     * @param {string} key The key of the method.
+     * @param {...*} [partials] The arguments to be partially applied.
+     * @returns {Function} Returns the new bound function.
+     * @example
+     *
+     * var object = {
+     *   'user': 'fred',
+     *   'greet': function(greeting, punctuation) {
+     *     return greeting + ' ' + this.user + punctuation;
+     *   }
+     * };
+     *
+     * var bound = _.bindKey(object, 'greet', 'hi');
+     * bound('!');
+     * // => 'hi fred!'
+     *
+     * object.greet = function(greeting, punctuation) {
+     *   return greeting + 'ya ' + this.user + punctuation;
+     * };
+     *
+     * bound('!');
+     * // => 'hiya fred!'
+     *
+     * // Bound with placeholders.
+     * var bound = _.bindKey(object, 'greet', _, '!');
+     * bound('hi');
+     * // => 'hiya fred!'
+     */
+    var bindKey = baseRest(function(object, key, partials) {
+      var bitmask = WRAP_BIND_FLAG | WRAP_BIND_KEY_FLAG;
+      if (partials.length) {
+        var holders = replaceHolders(partials, getHolder(bindKey));
+        bitmask |= WRAP_PARTIAL_FLAG;
+      }
+      return createWrap(key, bitmask, object, partials, holders);
+    });
+
+    /**
+     * Creates a function that accepts arguments of `func` and either invokes
+     * `func` returning its result, if at least `arity` number of arguments have
+     * been provided, or returns a function that accepts the remaining `func`
+     * arguments, and so on. The arity of `func` may be specified if `func.length`
+     * is not sufficient.
+     *
+     * The `_.curry.placeholder` value, which defaults to `_` in monolithic builds,
+     * may be used as a placeholder for provided arguments.
+     *
+     * **Note:** This method doesn't set the "length" property of curried functions.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Function
+     * @param {Function} func The function to curry.
+     * @param {number} [arity=func.length] The arity of `func`.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Function} Returns the new curried function.
+     * @example
+     *
+     * var abc = function(a, b, c) {
+     *   return [a, b, c];
+     * };
+     *
+     * var curried = _.curry(abc);
+     *
+     * curried(1)(2)(3);
+     * // => [1, 2, 3]
+     *
+     * curried(1, 2)(3);
+     * // => [1, 2, 3]
+     *
+     * curried(1, 2, 3);
+     * // => [1, 2, 3]
+     *
+     * // Curried with placeholders.
+     * curried(1)(_, 3)(2);
+     * // => [1, 2, 3]
+     */
+    function curry(func, arity, guard) {
+      arity = guard ? undefined : arity;
+      var result = createWrap(func, WRAP_CURRY_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
+      result.placeholder = curry.placeholder;
+      return result;
+    }
+
+    /**
+     * This method is like `_.curry` except that arguments are applied to `func`
+     * in the manner of `_.partialRight` instead of `_.partial`.
+     *
+     * The `_.curryRight.placeholder` value, which defaults to `_` in monolithic
+     * builds, may be used as a placeholder for provided arguments.
+     *
+     * **Note:** This method doesn't set the "length" property of curried functions.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Function
+     * @param {Function} func The function to curry.
+     * @param {number} [arity=func.length] The arity of `func`.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Function} Returns the new curried function.
+     * @example
+     *
+     * var abc = function(a, b, c) {
+     *   return [a, b, c];
+     * };
+     *
+     * var curried = _.curryRight(abc);
+     *
+     * curried(3)(2)(1);
+     * // => [1, 2, 3]
+     *
+     * curried(2, 3)(1);
+     * // => [1, 2, 3]
+     *
+     * curried(1, 2, 3);
+     * // => [1, 2, 3]
+     *
+     * // Curried with placeholders.
+     * curried(3)(1, _)(2);
+     * // => [1, 2, 3]
+     */
+    function curryRight(func, arity, guard) {
+      arity = guard ? undefined : arity;
+      var result = createWrap(func, WRAP_CURRY_RIGHT_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
+      result.placeholder = curryRight.placeholder;
+      return result;
+    }
+
+    /**
+     * Creates a debounced function that delays invoking `func` until after `wait`
+     * milliseconds have elapsed since the last time the debounced function was
+     * invoked. The debounced function comes with a `cancel` method to cancel
+     * delayed `func` invocations and a `flush` method to immediately invoke them.
+     * Provide `options` to indicate whether `func` should be invoked on the
+     * leading and/or trailing edge of the `wait` timeout. The `func` is invoked
+     * with the last arguments provided to the debounced function. Subsequent
+     * calls to the debounced function return the result of the last `func`
+     * invocation.
+     *
+     * **Note:** If `leading` and `trailing` options are `true`, `func` is
+     * invoked on the trailing edge of the timeout only if the debounced function
+     * is invoked more than once during the `wait` timeout.
+     *
+     * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
+     * until to the next tick, similar to `setTimeout` with a timeout of `0`.
+     *
+     * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
+     * for details over the differences between `_.debounce` and `_.throttle`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to debounce.
+     * @param {number} [wait=0] The number of milliseconds to delay.
+     * @param {Object} [options={}] The options object.
+     * @param {boolean} [options.leading=false]
+     *  Specify invoking on the leading edge of the timeout.
+     * @param {number} [options.maxWait]
+     *  The maximum time `func` is allowed to be delayed before it's invoked.
+     * @param {boolean} [options.trailing=true]
+     *  Specify invoking on the trailing edge of the timeout.
+     * @returns {Function} Returns the new debounced function.
+     * @example
+     *
+     * // Avoid costly calculations while the window size is in flux.
+     * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
+     *
+     * // Invoke `sendMail` when clicked, debouncing subsequent calls.
+     * jQuery(element).on('click', _.debounce(sendMail, 300, {
+     *   'leading': true,
+     *   'trailing': false
+     * }));
+     *
+     * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
+     * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
+     * var source = new EventSource('/stream');
+     * jQuery(source).on('message', debounced);
+     *
+     * // Cancel the trailing debounced invocation.
+     * jQuery(window).on('popstate', debounced.cancel);
+     */
+    function debounce(func, wait, options) {
+      var lastArgs,
+          lastThis,
+          maxWait,
+          result,
+          timerId,
+          lastCallTime,
+          lastInvokeTime = 0,
+          leading = false,
+          maxing = false,
+          trailing = true;
+
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      wait = toNumber(wait) || 0;
+      if (isObject(options)) {
+        leading = !!options.leading;
+        maxing = 'maxWait' in options;
+        maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
+        trailing = 'trailing' in options ? !!options.trailing : trailing;
+      }
+
+      function invokeFunc(time) {
+        var args = lastArgs,
+            thisArg = lastThis;
+
+        lastArgs = lastThis = undefined;
+        lastInvokeTime = time;
+        result = func.apply(thisArg, args);
+        return result;
+      }
+
+      function leadingEdge(time) {
+        // Reset any `maxWait` timer.
+        lastInvokeTime = time;
+        // Start the timer for the trailing edge.
+        timerId = setTimeout(timerExpired, wait);
+        // Invoke the leading edge.
+        return leading ? invokeFunc(time) : result;
+      }
+
+      function remainingWait(time) {
+        var timeSinceLastCall = time - lastCallTime,
+            timeSinceLastInvoke = time - lastInvokeTime,
+            result = wait - timeSinceLastCall;
+
+        return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result;
+      }
+
+      function shouldInvoke(time) {
+        var timeSinceLastCall = time - lastCallTime,
+            timeSinceLastInvoke = time - lastInvokeTime;
+
+        // Either this is the first call, activity has stopped and we're at the
+        // trailing edge, the system time has gone backwards and we're treating
+        // it as the trailing edge, or we've hit the `maxWait` limit.
+        return (lastCallTime === undefined || (timeSinceLastCall >= wait) ||
+          (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
+      }
+
+      function timerExpired() {
+        var time = now();
+        if (shouldInvoke(time)) {
+          return trailingEdge(time);
+        }
+        // Restart the timer.
+        timerId = setTimeout(timerExpired, remainingWait(time));
+      }
+
+      function trailingEdge(time) {
+        timerId = undefined;
+
+        // Only invoke if we have `lastArgs` which means `func` has been
+        // debounced at least once.
+        if (trailing && lastArgs) {
+          return invokeFunc(time);
+        }
+        lastArgs = lastThis = undefined;
+        return result;
+      }
+
+      function cancel() {
+        if (timerId !== undefined) {
+          clearTimeout(timerId);
+        }
+        lastInvokeTime = 0;
+        lastArgs = lastCallTime = lastThis = timerId = undefined;
+      }
+
+      function flush() {
+        return timerId === undefined ? result : trailingEdge(now());
+      }
+
+      function debounced() {
+        var time = now(),
+            isInvoking = shouldInvoke(time);
+
+        lastArgs = arguments;
+        lastThis = this;
+        lastCallTime = time;
+
+        if (isInvoking) {
+          if (timerId === undefined) {
+            return leadingEdge(lastCallTime);
+          }
+          if (maxing) {
+            // Handle invocations in a tight loop.
+            timerId = setTimeout(timerExpired, wait);
+            return invokeFunc(lastCallTime);
+          }
+        }
+        if (timerId === undefined) {
+          timerId = setTimeout(timerExpired, wait);
+        }
+        return result;
+      }
+      debounced.cancel = cancel;
+      debounced.flush = flush;
+      return debounced;
+    }
+
+    /**
+     * Defers invoking the `func` until the current call stack has cleared. Any
+     * additional arguments are provided to `func` when it's invoked.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to defer.
+     * @param {...*} [args] The arguments to invoke `func` with.
+     * @returns {number} Returns the timer id.
+     * @example
+     *
+     * _.defer(function(text) {
+     *   console.log(text);
+     * }, 'deferred');
+     * // => Logs 'deferred' after one millisecond.
+     */
+    var defer = baseRest(function(func, args) {
+      return baseDelay(func, 1, args);
+    });
+
+    /**
+     * Invokes `func` after `wait` milliseconds. Any additional arguments are
+     * provided to `func` when it's invoked.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to delay.
+     * @param {number} wait The number of milliseconds to delay invocation.
+     * @param {...*} [args] The arguments to invoke `func` with.
+     * @returns {number} Returns the timer id.
+     * @example
+     *
+     * _.delay(function(text) {
+     *   console.log(text);
+     * }, 1000, 'later');
+     * // => Logs 'later' after one second.
+     */
+    var delay = baseRest(function(func, wait, args) {
+      return baseDelay(func, toNumber(wait) || 0, args);
+    });
+
+    /**
+     * Creates a function that invokes `func` with arguments reversed.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Function
+     * @param {Function} func The function to flip arguments for.
+     * @returns {Function} Returns the new flipped function.
+     * @example
+     *
+     * var flipped = _.flip(function() {
+     *   return _.toArray(arguments);
+     * });
+     *
+     * flipped('a', 'b', 'c', 'd');
+     * // => ['d', 'c', 'b', 'a']
+     */
+    function flip(func) {
+      return createWrap(func, WRAP_FLIP_FLAG);
+    }
+
+    /**
+     * Creates a function that memoizes the result of `func`. If `resolver` is
+     * provided, it determines the cache key for storing the result based on the
+     * arguments provided to the memoized function. By default, the first argument
+     * provided to the memoized function is used as the map cache key. The `func`
+     * is invoked with the `this` binding of the memoized function.
+     *
+     * **Note:** The cache is exposed as the `cache` property on the memoized
+     * function. Its creation may be customized by replacing the `_.memoize.Cache`
+     * constructor with one whose instances implement the
+     * [`Map`](http://ecma-international.org/ecma-262/7.0/#sec-properties-of-the-map-prototype-object)
+     * method interface of `clear`, `delete`, `get`, `has`, and `set`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to have its output memoized.
+     * @param {Function} [resolver] The function to resolve the cache key.
+     * @returns {Function} Returns the new memoized function.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': 2 };
+     * var other = { 'c': 3, 'd': 4 };
+     *
+     * var values = _.memoize(_.values);
+     * values(object);
+     * // => [1, 2]
+     *
+     * values(other);
+     * // => [3, 4]
+     *
+     * object.a = 2;
+     * values(object);
+     * // => [1, 2]
+     *
+     * // Modify the result cache.
+     * values.cache.set(object, ['a', 'b']);
+     * values(object);
+     * // => ['a', 'b']
+     *
+     * // Replace `_.memoize.Cache`.
+     * _.memoize.Cache = WeakMap;
+     */
+    function memoize(func, resolver) {
+      if (typeof func != 'function' || (resolver != null && typeof resolver != 'function')) {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      var memoized = function() {
+        var args = arguments,
+            key = resolver ? resolver.apply(this, args) : args[0],
+            cache = memoized.cache;
+
+        if (cache.has(key)) {
+          return cache.get(key);
+        }
+        var result = func.apply(this, args);
+        memoized.cache = cache.set(key, result) || cache;
+        return result;
+      };
+      memoized.cache = new (memoize.Cache || MapCache);
+      return memoized;
+    }
+
+    // Expose `MapCache`.
+    memoize.Cache = MapCache;
+
+    /**
+     * Creates a function that negates the result of the predicate `func`. The
+     * `func` predicate is invoked with the `this` binding and arguments of the
+     * created function.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Function
+     * @param {Function} predicate The predicate to negate.
+     * @returns {Function} Returns the new negated function.
+     * @example
+     *
+     * function isEven(n) {
+     *   return n % 2 == 0;
+     * }
+     *
+     * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));
+     * // => [1, 3, 5]
+     */
+    function negate(predicate) {
+      if (typeof predicate != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      return function() {
+        var args = arguments;
+        switch (args.length) {
+          case 0: return !predicate.call(this);
+          case 1: return !predicate.call(this, args[0]);
+          case 2: return !predicate.call(this, args[0], args[1]);
+          case 3: return !predicate.call(this, args[0], args[1], args[2]);
+        }
+        return !predicate.apply(this, args);
+      };
+    }
+
+    /**
+     * Creates a function that is restricted to invoking `func` once. Repeat calls
+     * to the function return the value of the first invocation. The `func` is
+     * invoked with the `this` binding and arguments of the created function.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * var initialize = _.once(createApplication);
+     * initialize();
+     * initialize();
+     * // => `createApplication` is invoked once
+     */
+    function once(func) {
+      return before(2, func);
+    }
+
+    /**
+     * Creates a function that invokes `func` with its arguments transformed.
+     *
+     * @static
+     * @since 4.0.0
+     * @memberOf _
+     * @category Function
+     * @param {Function} func The function to wrap.
+     * @param {...(Function|Function[])} [transforms=[_.identity]]
+     *  The argument transforms.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * function doubled(n) {
+     *   return n * 2;
+     * }
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var func = _.overArgs(function(x, y) {
+     *   return [x, y];
+     * }, [square, doubled]);
+     *
+     * func(9, 3);
+     * // => [81, 6]
+     *
+     * func(10, 5);
+     * // => [100, 10]
+     */
+    var overArgs = castRest(function(func, transforms) {
+      transforms = (transforms.length == 1 && isArray(transforms[0]))
+        ? arrayMap(transforms[0], baseUnary(getIteratee()))
+        : arrayMap(baseFlatten(transforms, 1), baseUnary(getIteratee()));
+
+      var funcsLength = transforms.length;
+      return baseRest(function(args) {
+        var index = -1,
+            length = nativeMin(args.length, funcsLength);
+
+        while (++index < length) {
+          args[index] = transforms[index].call(this, args[index]);
+        }
+        return apply(func, this, args);
+      });
+    });
+
+    /**
+     * Creates a function that invokes `func` with `partials` prepended to the
+     * arguments it receives. This method is like `_.bind` except it does **not**
+     * alter the `this` binding.
+     *
+     * The `_.partial.placeholder` value, which defaults to `_` in monolithic
+     * builds, may be used as a placeholder for partially applied arguments.
+     *
+     * **Note:** This method doesn't set the "length" property of partially
+     * applied functions.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.2.0
+     * @category Function
+     * @param {Function} func The function to partially apply arguments to.
+     * @param {...*} [partials] The arguments to be partially applied.
+     * @returns {Function} Returns the new partially applied function.
+     * @example
+     *
+     * function greet(greeting, name) {
+     *   return greeting + ' ' + name;
+     * }
+     *
+     * var sayHelloTo = _.partial(greet, 'hello');
+     * sayHelloTo('fred');
+     * // => 'hello fred'
+     *
+     * // Partially applied with placeholders.
+     * var greetFred = _.partial(greet, _, 'fred');
+     * greetFred('hi');
+     * // => 'hi fred'
+     */
+    var partial = baseRest(function(func, partials) {
+      var holders = replaceHolders(partials, getHolder(partial));
+      return createWrap(func, WRAP_PARTIAL_FLAG, undefined, partials, holders);
+    });
+
+    /**
+     * This method is like `_.partial` except that partially applied arguments
+     * are appended to the arguments it receives.
+     *
+     * The `_.partialRight.placeholder` value, which defaults to `_` in monolithic
+     * builds, may be used as a placeholder for partially applied arguments.
+     *
+     * **Note:** This method doesn't set the "length" property of partially
+     * applied functions.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.0.0
+     * @category Function
+     * @param {Function} func The function to partially apply arguments to.
+     * @param {...*} [partials] The arguments to be partially applied.
+     * @returns {Function} Returns the new partially applied function.
+     * @example
+     *
+     * function greet(greeting, name) {
+     *   return greeting + ' ' + name;
+     * }
+     *
+     * var greetFred = _.partialRight(greet, 'fred');
+     * greetFred('hi');
+     * // => 'hi fred'
+     *
+     * // Partially applied with placeholders.
+     * var sayHelloTo = _.partialRight(greet, 'hello', _);
+     * sayHelloTo('fred');
+     * // => 'hello fred'
+     */
+    var partialRight = baseRest(function(func, partials) {
+      var holders = replaceHolders(partials, getHolder(partialRight));
+      return createWrap(func, WRAP_PARTIAL_RIGHT_FLAG, undefined, partials, holders);
+    });
+
+    /**
+     * Creates a function that invokes `func` with arguments arranged according
+     * to the specified `indexes` where the argument value at the first index is
+     * provided as the first argument, the argument value at the second index is
+     * provided as the second argument, and so on.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Function
+     * @param {Function} func The function to rearrange arguments for.
+     * @param {...(number|number[])} indexes The arranged argument indexes.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var rearged = _.rearg(function(a, b, c) {
+     *   return [a, b, c];
+     * }, [2, 0, 1]);
+     *
+     * rearged('b', 'c', 'a')
+     * // => ['a', 'b', 'c']
+     */
+    var rearg = flatRest(function(func, indexes) {
+      return createWrap(func, WRAP_REARG_FLAG, undefined, undefined, undefined, indexes);
+    });
+
+    /**
+     * Creates a function that invokes `func` with the `this` binding of the
+     * created function and arguments from `start` and beyond provided as
+     * an array.
+     *
+     * **Note:** This method is based on the
+     * [rest parameter](https://mdn.io/rest_parameters).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Function
+     * @param {Function} func The function to apply a rest parameter to.
+     * @param {number} [start=func.length-1] The start position of the rest parameter.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var say = _.rest(function(what, names) {
+     *   return what + ' ' + _.initial(names).join(', ') +
+     *     (_.size(names) > 1 ? ', & ' : '') + _.last(names);
+     * });
+     *
+     * say('hello', 'fred', 'barney', 'pebbles');
+     * // => 'hello fred, barney, & pebbles'
+     */
+    function rest(func, start) {
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      start = start === undefined ? start : toInteger(start);
+      return baseRest(func, start);
+    }
+
+    /**
+     * Creates a function that invokes `func` with the `this` binding of the
+     * create function and an array of arguments much like
+     * [`Function#apply`](http://www.ecma-international.org/ecma-262/7.0/#sec-function.prototype.apply).
+     *
+     * **Note:** This method is based on the
+     * [spread operator](https://mdn.io/spread_operator).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.2.0
+     * @category Function
+     * @param {Function} func The function to spread arguments over.
+     * @param {number} [start=0] The start position of the spread.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var say = _.spread(function(who, what) {
+     *   return who + ' says ' + what;
+     * });
+     *
+     * say(['fred', 'hello']);
+     * // => 'fred says hello'
+     *
+     * var numbers = Promise.all([
+     *   Promise.resolve(40),
+     *   Promise.resolve(36)
+     * ]);
+     *
+     * numbers.then(_.spread(function(x, y) {
+     *   return x + y;
+     * }));
+     * // => a Promise of 76
+     */
+    function spread(func, start) {
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      start = start == null ? 0 : nativeMax(toInteger(start), 0);
+      return baseRest(function(args) {
+        var array = args[start],
+            otherArgs = castSlice(args, 0, start);
+
+        if (array) {
+          arrayPush(otherArgs, array);
+        }
+        return apply(func, this, otherArgs);
+      });
+    }
+
+    /**
+     * Creates a throttled function that only invokes `func` at most once per
+     * every `wait` milliseconds. The throttled function comes with a `cancel`
+     * method to cancel delayed `func` invocations and a `flush` method to
+     * immediately invoke them. Provide `options` to indicate whether `func`
+     * should be invoked on the leading and/or trailing edge of the `wait`
+     * timeout. The `func` is invoked with the last arguments provided to the
+     * throttled function. Subsequent calls to the throttled function return the
+     * result of the last `func` invocation.
+     *
+     * **Note:** If `leading` and `trailing` options are `true`, `func` is
+     * invoked on the trailing edge of the timeout only if the throttled function
+     * is invoked more than once during the `wait` timeout.
+     *
+     * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred
+     * until to the next tick, similar to `setTimeout` with a timeout of `0`.
+     *
+     * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
+     * for details over the differences between `_.throttle` and `_.debounce`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to throttle.
+     * @param {number} [wait=0] The number of milliseconds to throttle invocations to.
+     * @param {Object} [options={}] The options object.
+     * @param {boolean} [options.leading=true]
+     *  Specify invoking on the leading edge of the timeout.
+     * @param {boolean} [options.trailing=true]
+     *  Specify invoking on the trailing edge of the timeout.
+     * @returns {Function} Returns the new throttled function.
+     * @example
+     *
+     * // Avoid excessively updating the position while scrolling.
+     * jQuery(window).on('scroll', _.throttle(updatePosition, 100));
+     *
+     * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
+     * var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
+     * jQuery(element).on('click', throttled);
+     *
+     * // Cancel the trailing throttled invocation.
+     * jQuery(window).on('popstate', throttled.cancel);
+     */
+    function throttle(func, wait, options) {
+      var leading = true,
+          trailing = true;
+
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      if (isObject(options)) {
+        leading = 'leading' in options ? !!options.leading : leading;
+        trailing = 'trailing' in options ? !!options.trailing : trailing;
+      }
+      return debounce(func, wait, {
+        'leading': leading,
+        'maxWait': wait,
+        'trailing': trailing
+      });
+    }
+
+    /**
+     * Creates a function that accepts up to one argument, ignoring any
+     * additional arguments.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Function
+     * @param {Function} func The function to cap arguments for.
+     * @returns {Function} Returns the new capped function.
+     * @example
+     *
+     * _.map(['6', '8', '10'], _.unary(parseInt));
+     * // => [6, 8, 10]
+     */
+    function unary(func) {
+      return ary(func, 1);
+    }
+
+    /**
+     * Creates a function that provides `value` to `wrapper` as its first
+     * argument. Any additional arguments provided to the function are appended
+     * to those provided to the `wrapper`. The wrapper is invoked with the `this`
+     * binding of the created function.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {*} value The value to wrap.
+     * @param {Function} [wrapper=identity] The wrapper function.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var p = _.wrap(_.escape, function(func, text) {
+     *   return '<p>' + func(text) + '</p>';
+     * });
+     *
+     * p('fred, barney, & pebbles');
+     * // => '<p>fred, barney, &amp; pebbles</p>'
+     */
+    function wrap(value, wrapper) {
+      return partial(castFunction(wrapper), value);
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Casts `value` as an array if it's not one.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.4.0
+     * @category Lang
+     * @param {*} value The value to inspect.
+     * @returns {Array} Returns the cast array.
+     * @example
+     *
+     * _.castArray(1);
+     * // => [1]
+     *
+     * _.castArray({ 'a': 1 });
+     * // => [{ 'a': 1 }]
+     *
+     * _.castArray('abc');
+     * // => ['abc']
+     *
+     * _.castArray(null);
+     * // => [null]
+     *
+     * _.castArray(undefined);
+     * // => [undefined]
+     *
+     * _.castArray();
+     * // => []
+     *
+     * var array = [1, 2, 3];
+     * console.log(_.castArray(array) === array);
+     * // => true
+     */
+    function castArray() {
+      if (!arguments.length) {
+        return [];
+      }
+      var value = arguments[0];
+      return isArray(value) ? value : [value];
+    }
+
+    /**
+     * Creates a shallow clone of `value`.
+     *
+     * **Note:** This method is loosely based on the
+     * [structured clone algorithm](https://mdn.io/Structured_clone_algorithm)
+     * and supports cloning arrays, array buffers, booleans, date objects, maps,
+     * numbers, `Object` objects, regexes, sets, strings, symbols, and typed
+     * arrays. The own enumerable properties of `arguments` objects are cloned
+     * as plain objects. An empty object is returned for uncloneable values such
+     * as error objects, functions, DOM nodes, and WeakMaps.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to clone.
+     * @returns {*} Returns the cloned value.
+     * @see _.cloneDeep
+     * @example
+     *
+     * var objects = [{ 'a': 1 }, { 'b': 2 }];
+     *
+     * var shallow = _.clone(objects);
+     * console.log(shallow[0] === objects[0]);
+     * // => true
+     */
+    function clone(value) {
+      return baseClone(value, CLONE_SYMBOLS_FLAG);
+    }
+
+    /**
+     * This method is like `_.clone` except that it accepts `customizer` which
+     * is invoked to produce the cloned value. If `customizer` returns `undefined`,
+     * cloning is handled by the method instead. The `customizer` is invoked with
+     * up to four arguments; (value [, index|key, object, stack]).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to clone.
+     * @param {Function} [customizer] The function to customize cloning.
+     * @returns {*} Returns the cloned value.
+     * @see _.cloneDeepWith
+     * @example
+     *
+     * function customizer(value) {
+     *   if (_.isElement(value)) {
+     *     return value.cloneNode(false);
+     *   }
+     * }
+     *
+     * var el = _.cloneWith(document.body, customizer);
+     *
+     * console.log(el === document.body);
+     * // => false
+     * console.log(el.nodeName);
+     * // => 'BODY'
+     * console.log(el.childNodes.length);
+     * // => 0
+     */
+    function cloneWith(value, customizer) {
+      customizer = typeof customizer == 'function' ? customizer : undefined;
+      return baseClone(value, CLONE_SYMBOLS_FLAG, customizer);
+    }
+
+    /**
+     * This method is like `_.clone` except that it recursively clones `value`.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.0.0
+     * @category Lang
+     * @param {*} value The value to recursively clone.
+     * @returns {*} Returns the deep cloned value.
+     * @see _.clone
+     * @example
+     *
+     * var objects = [{ 'a': 1 }, { 'b': 2 }];
+     *
+     * var deep = _.cloneDeep(objects);
+     * console.log(deep[0] === objects[0]);
+     * // => false
+     */
+    function cloneDeep(value) {
+      return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG);
+    }
+
+    /**
+     * This method is like `_.cloneWith` except that it recursively clones `value`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to recursively clone.
+     * @param {Function} [customizer] The function to customize cloning.
+     * @returns {*} Returns the deep cloned value.
+     * @see _.cloneWith
+     * @example
+     *
+     * function customizer(value) {
+     *   if (_.isElement(value)) {
+     *     return value.cloneNode(true);
+     *   }
+     * }
+     *
+     * var el = _.cloneDeepWith(document.body, customizer);
+     *
+     * console.log(el === document.body);
+     * // => false
+     * console.log(el.nodeName);
+     * // => 'BODY'
+     * console.log(el.childNodes.length);
+     * // => 20
+     */
+    function cloneDeepWith(value, customizer) {
+      customizer = typeof customizer == 'function' ? customizer : undefined;
+      return baseClone(value, CLONE_DEEP_FLAG | CLONE_SYMBOLS_FLAG, customizer);
+    }
+
+    /**
+     * Checks if `object` conforms to `source` by invoking the predicate
+     * properties of `source` with the corresponding property values of `object`.
+     *
+     * **Note:** This method is equivalent to `_.conforms` when `source` is
+     * partially applied.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.14.0
+     * @category Lang
+     * @param {Object} object The object to inspect.
+     * @param {Object} source The object of property predicates to conform to.
+     * @returns {boolean} Returns `true` if `object` conforms, else `false`.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': 2 };
+     *
+     * _.conformsTo(object, { 'b': function(n) { return n > 1; } });
+     * // => true
+     *
+     * _.conformsTo(object, { 'b': function(n) { return n > 2; } });
+     * // => false
+     */
+    function conformsTo(object, source) {
+      return source == null || baseConformsTo(object, source, keys(source));
+    }
+
+    /**
+     * Performs a
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/7.0/#sec-samevaluezero)
+     * comparison between two values to determine if they are equivalent.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+     * @example
+     *
+     * var object = { 'a': 1 };
+     * var other = { 'a': 1 };
+     *
+     * _.eq(object, object);
+     * // => true
+     *
+     * _.eq(object, other);
+     * // => false
+     *
+     * _.eq('a', 'a');
+     * // => true
+     *
+     * _.eq('a', Object('a'));
+     * // => false
+     *
+     * _.eq(NaN, NaN);
+     * // => true
+     */
+    function eq(value, other) {
+      return value === other || (value !== value && other !== other);
+    }
+
+    /**
+     * Checks if `value` is greater than `other`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.9.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is greater than `other`,
+     *  else `false`.
+     * @see _.lt
+     * @example
+     *
+     * _.gt(3, 1);
+     * // => true
+     *
+     * _.gt(3, 3);
+     * // => false
+     *
+     * _.gt(1, 3);
+     * // => false
+     */
+    var gt = createRelationalOperation(baseGt);
+
+    /**
+     * Checks if `value` is greater than or equal to `other`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.9.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is greater than or equal to
+     *  `other`, else `false`.
+     * @see _.lte
+     * @example
+     *
+     * _.gte(3, 1);
+     * // => true
+     *
+     * _.gte(3, 3);
+     * // => true
+     *
+     * _.gte(1, 3);
+     * // => false
+     */
+    var gte = createRelationalOperation(function(value, other) {
+      return value >= other;
+    });
+
+    /**
+     * Checks if `value` is likely an `arguments` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an `arguments` object,
+     *  else `false`.
+     * @example
+     *
+     * _.isArguments(function() { return arguments; }());
+     * // => true
+     *
+     * _.isArguments([1, 2, 3]);
+     * // => false
+     */
+    var isArguments = baseIsArguments(function() { return arguments; }()) ? baseIsArguments : function(value) {
+      return isObjectLike(value) && hasOwnProperty.call(value, 'callee') &&
+        !propertyIsEnumerable.call(value, 'callee');
+    };
+
+    /**
+     * Checks if `value` is classified as an `Array` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an array, else `false`.
+     * @example
+     *
+     * _.isArray([1, 2, 3]);
+     * // => true
+     *
+     * _.isArray(document.body.children);
+     * // => false
+     *
+     * _.isArray('abc');
+     * // => false
+     *
+     * _.isArray(_.noop);
+     * // => false
+     */
+    var isArray = Array.isArray;
+
+    /**
+     * Checks if `value` is classified as an `ArrayBuffer` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.3.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an array buffer, else `false`.
+     * @example
+     *
+     * _.isArrayBuffer(new ArrayBuffer(2));
+     * // => true
+     *
+     * _.isArrayBuffer(new Array(2));
+     * // => false
+     */
+    var isArrayBuffer = nodeIsArrayBuffer ? baseUnary(nodeIsArrayBuffer) : baseIsArrayBuffer;
+
+    /**
+     * Checks if `value` is array-like. A value is considered array-like if it's
+     * not a function and has a `value.length` that's an integer greater than or
+     * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
+     * @example
+     *
+     * _.isArrayLike([1, 2, 3]);
+     * // => true
+     *
+     * _.isArrayLike(document.body.children);
+     * // => true
+     *
+     * _.isArrayLike('abc');
+     * // => true
+     *
+     * _.isArrayLike(_.noop);
+     * // => false
+     */
+    function isArrayLike(value) {
+      return value != null && isLength(value.length) && !isFunction(value);
+    }
+
+    /**
+     * This method is like `_.isArrayLike` except that it also checks if `value`
+     * is an object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an array-like object,
+     *  else `false`.
+     * @example
+     *
+     * _.isArrayLikeObject([1, 2, 3]);
+     * // => true
+     *
+     * _.isArrayLikeObject(document.body.children);
+     * // => true
+     *
+     * _.isArrayLikeObject('abc');
+     * // => false
+     *
+     * _.isArrayLikeObject(_.noop);
+     * // => false
+     */
+    function isArrayLikeObject(value) {
+      return isObjectLike(value) && isArrayLike(value);
+    }
+
+    /**
+     * Checks if `value` is classified as a boolean primitive or object.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a boolean, else `false`.
+     * @example
+     *
+     * _.isBoolean(false);
+     * // => true
+     *
+     * _.isBoolean(null);
+     * // => false
+     */
+    function isBoolean(value) {
+      return value === true || value === false ||
+        (isObjectLike(value) && baseGetTag(value) == boolTag);
+    }
+
+    /**
+     * Checks if `value` is a buffer.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.3.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a buffer, else `false`.
+     * @example
+     *
+     * _.isBuffer(new Buffer(2));
+     * // => true
+     *
+     * _.isBuffer(new Uint8Array(2));
+     * // => false
+     */
+    var isBuffer = nativeIsBuffer || stubFalse;
+
+    /**
+     * Checks if `value` is classified as a `Date` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a date object, else `false`.
+     * @example
+     *
+     * _.isDate(new Date);
+     * // => true
+     *
+     * _.isDate('Mon April 23 2012');
+     * // => false
+     */
+    var isDate = nodeIsDate ? baseUnary(nodeIsDate) : baseIsDate;
+
+    /**
+     * Checks if `value` is likely a DOM element.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a DOM element, else `false`.
+     * @example
+     *
+     * _.isElement(document.body);
+     * // => true
+     *
+     * _.isElement('<body>');
+     * // => false
+     */
+    function isElement(value) {
+      return isObjectLike(value) && value.nodeType === 1 && !isPlainObject(value);
+    }
+
+    /**
+     * Checks if `value` is an empty object, collection, map, or set.
+     *
+     * Objects are considered empty if they have no own enumerable string keyed
+     * properties.
+     *
+     * Array-like values such as `arguments` objects, arrays, buffers, strings, or
+     * jQuery-like collections are considered empty if they have a `length` of `0`.
+     * Similarly, maps and sets are considered empty if they have a `size` of `0`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is empty, else `false`.
+     * @example
+     *
+     * _.isEmpty(null);
+     * // => true
+     *
+     * _.isEmpty(true);
+     * // => true
+     *
+     * _.isEmpty(1);
+     * // => true
+     *
+     * _.isEmpty([1, 2, 3]);
+     * // => false
+     *
+     * _.isEmpty({ 'a': 1 });
+     * // => false
+     */
+    function isEmpty(value) {
+      if (value == null) {
+        return true;
+      }
+      if (isArrayLike(value) &&
+          (isArray(value) || typeof value == 'string' || typeof value.splice == 'function' ||
+            isBuffer(value) || isTypedArray(value) || isArguments(value))) {
+        return !value.length;
+      }
+      var tag = getTag(value);
+      if (tag == mapTag || tag == setTag) {
+        return !value.size;
+      }
+      if (isPrototype(value)) {
+        return !baseKeys(value).length;
+      }
+      for (var key in value) {
+        if (hasOwnProperty.call(value, key)) {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    /**
+     * Performs a deep comparison between two values to determine if they are
+     * equivalent.
+     *
+     * **Note:** This method supports comparing arrays, array buffers, booleans,
+     * date objects, error objects, maps, numbers, `Object` objects, regexes,
+     * sets, strings, symbols, and typed arrays. `Object` objects are compared
+     * by their own, not inherited, enumerable properties. Functions and DOM
+     * nodes are compared by strict equality, i.e. `===`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+     * @example
+     *
+     * var object = { 'a': 1 };
+     * var other = { 'a': 1 };
+     *
+     * _.isEqual(object, other);
+     * // => true
+     *
+     * object === other;
+     * // => false
+     */
+    function isEqual(value, other) {
+      return baseIsEqual(value, other);
+    }
+
+    /**
+     * This method is like `_.isEqual` except that it accepts `customizer` which
+     * is invoked to compare values. If `customizer` returns `undefined`, comparisons
+     * are handled by the method instead. The `customizer` is invoked with up to
+     * six arguments: (objValue, othValue [, index|key, object, other, stack]).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @param {Function} [customizer] The function to customize comparisons.
+     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+     * @example
+     *
+     * function isGreeting(value) {
+     *   return /^h(?:i|ello)$/.test(value);
+     * }
+     *
+     * function customizer(objValue, othValue) {
+     *   if (isGreeting(objValue) && isGreeting(othValue)) {
+     *     return true;
+     *   }
+     * }
+     *
+     * var array = ['hello', 'goodbye'];
+     * var other = ['hi', 'goodbye'];
+     *
+     * _.isEqualWith(array, other, customizer);
+     * // => true
+     */
+    function isEqualWith(value, other, customizer) {
+      customizer = typeof customizer == 'function' ? customizer : undefined;
+      var result = customizer ? customizer(value, other) : undefined;
+      return result === undefined ? baseIsEqual(value, other, undefined, customizer) : !!result;
+    }
+
+    /**
+     * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`,
+     * `SyntaxError`, `TypeError`, or `URIError` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an error object, else `false`.
+     * @example
+     *
+     * _.isError(new Error);
+     * // => true
+     *
+     * _.isError(Error);
+     * // => false
+     */
+    function isError(value) {
+      if (!isObjectLike(value)) {
+        return false;
+      }
+      var tag = baseGetTag(value);
+      return tag == errorTag || tag == domExcTag ||
+        (typeof value.message == 'string' && typeof value.name == 'string' && !isPlainObject(value));
+    }
+
+    /**
+     * Checks if `value` is a finite primitive number.
+     *
+     * **Note:** This method is based on
+     * [`Number.isFinite`](https://mdn.io/Number/isFinite).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a finite number, else `false`.
+     * @example
+     *
+     * _.isFinite(3);
+     * // => true
+     *
+     * _.isFinite(Number.MIN_VALUE);
+     * // => true
+     *
+     * _.isFinite(Infinity);
+     * // => false
+     *
+     * _.isFinite('3');
+     * // => false
+     */
+    function isFinite(value) {
+      return typeof value == 'number' && nativeIsFinite(value);
+    }
+
+    /**
+     * Checks if `value` is classified as a `Function` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a function, else `false`.
+     * @example
+     *
+     * _.isFunction(_);
+     * // => true
+     *
+     * _.isFunction(/abc/);
+     * // => false
+     */
+    function isFunction(value) {
+      if (!isObject(value)) {
+        return false;
+      }
+      // The use of `Object#toString` avoids issues with the `typeof` operator
+      // in Safari 9 which returns 'object' for typed arrays and other constructors.
+      var tag = baseGetTag(value);
+      return tag == funcTag || tag == genTag || tag == asyncTag || tag == proxyTag;
+    }
+
+    /**
+     * Checks if `value` is an integer.
+     *
+     * **Note:** This method is based on
+     * [`Number.isInteger`](https://mdn.io/Number/isInteger).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an integer, else `false`.
+     * @example
+     *
+     * _.isInteger(3);
+     * // => true
+     *
+     * _.isInteger(Number.MIN_VALUE);
+     * // => false
+     *
+     * _.isInteger(Infinity);
+     * // => false
+     *
+     * _.isInteger('3');
+     * // => false
+     */
+    function isInteger(value) {
+      return typeof value == 'number' && value == toInteger(value);
+    }
+
+    /**
+     * Checks if `value` is a valid array-like length.
+     *
+     * **Note:** This method is loosely based on
+     * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a valid length, else `false`.
+     * @example
+     *
+     * _.isLength(3);
+     * // => true
+     *
+     * _.isLength(Number.MIN_VALUE);
+     * // => false
+     *
+     * _.isLength(Infinity);
+     * // => false
+     *
+     * _.isLength('3');
+     * // => false
+     */
+    function isLength(value) {
+      return typeof value == 'number' &&
+        value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
+    }
+
+    /**
+     * Checks if `value` is the
+     * [language type](http://www.ecma-international.org/ecma-262/7.0/#sec-ecmascript-language-types)
+     * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an object, else `false`.
+     * @example
+     *
+     * _.isObject({});
+     * // => true
+     *
+     * _.isObject([1, 2, 3]);
+     * // => true
+     *
+     * _.isObject(_.noop);
+     * // => true
+     *
+     * _.isObject(null);
+     * // => false
+     */
+    function isObject(value) {
+      var type = typeof value;
+      return value != null && (type == 'object' || type == 'function');
+    }
+
+    /**
+     * Checks if `value` is object-like. A value is object-like if it's not `null`
+     * and has a `typeof` result of "object".
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
+     * @example
+     *
+     * _.isObjectLike({});
+     * // => true
+     *
+     * _.isObjectLike([1, 2, 3]);
+     * // => true
+     *
+     * _.isObjectLike(_.noop);
+     * // => false
+     *
+     * _.isObjectLike(null);
+     * // => false
+     */
+    function isObjectLike(value) {
+      return value != null && typeof value == 'object';
+    }
+
+    /**
+     * Checks if `value` is classified as a `Map` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.3.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a map, else `false`.
+     * @example
+     *
+     * _.isMap(new Map);
+     * // => true
+     *
+     * _.isMap(new WeakMap);
+     * // => false
+     */
+    var isMap = nodeIsMap ? baseUnary(nodeIsMap) : baseIsMap;
+
+    /**
+     * Performs a partial deep comparison between `object` and `source` to
+     * determine if `object` contains equivalent property values.
+     *
+     * **Note:** This method is equivalent to `_.matches` when `source` is
+     * partially applied.
+     *
+     * Partial comparisons will match empty array and empty object `source`
+     * values against any array or object value, respectively. See `_.isEqual`
+     * for a list of supported value comparisons.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Lang
+     * @param {Object} object The object to inspect.
+     * @param {Object} source The object of property values to match.
+     * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': 2 };
+     *
+     * _.isMatch(object, { 'b': 2 });
+     * // => true
+     *
+     * _.isMatch(object, { 'b': 1 });
+     * // => false
+     */
+    function isMatch(object, source) {
+      return object === source || baseIsMatch(object, source, getMatchData(source));
+    }
+
+    /**
+     * This method is like `_.isMatch` except that it accepts `customizer` which
+     * is invoked to compare values. If `customizer` returns `undefined`, comparisons
+     * are handled by the method instead. The `customizer` is invoked with five
+     * arguments: (objValue, srcValue, index|key, object, source).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {Object} object The object to inspect.
+     * @param {Object} source The object of property values to match.
+     * @param {Function} [customizer] The function to customize comparisons.
+     * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+     * @example
+     *
+     * function isGreeting(value) {
+     *   return /^h(?:i|ello)$/.test(value);
+     * }
+     *
+     * function customizer(objValue, srcValue) {
+     *   if (isGreeting(objValue) && isGreeting(srcValue)) {
+     *     return true;
+     *   }
+     * }
+     *
+     * var object = { 'greeting': 'hello' };
+     * var source = { 'greeting': 'hi' };
+     *
+     * _.isMatchWith(object, source, customizer);
+     * // => true
+     */
+    function isMatchWith(object, source, customizer) {
+      customizer = typeof customizer == 'function' ? customizer : undefined;
+      return baseIsMatch(object, source, getMatchData(source), customizer);
+    }
+
+    /**
+     * Checks if `value` is `NaN`.
+     *
+     * **Note:** This method is based on
+     * [`Number.isNaN`](https://mdn.io/Number/isNaN) and is not the same as
+     * global [`isNaN`](https://mdn.io/isNaN) which returns `true` for
+     * `undefined` and other non-number values.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
+     * @example
+     *
+     * _.isNaN(NaN);
+     * // => true
+     *
+     * _.isNaN(new Number(NaN));
+     * // => true
+     *
+     * isNaN(undefined);
+     * // => true
+     *
+     * _.isNaN(undefined);
+     * // => false
+     */
+    function isNaN(value) {
+      // An `NaN` primitive is the only value that is not equal to itself.
+      // Perform the `toStringTag` check first to avoid errors with some
+      // ActiveX objects in IE.
+      return isNumber(value) && value != +value;
+    }
+
+    /**
+     * Checks if `value` is a pristine native function.
+     *
+     * **Note:** This method can't reliably detect native functions in the presence
+     * of the core-js package because core-js circumvents this kind of detection.
+     * Despite multiple requests, the core-js maintainer has made it clear: any
+     * attempt to fix the detection will be obstructed. As a result, we're left
+     * with little choice but to throw an error. Unfortunately, this also affects
+     * packages, like [babel-polyfill](https://www.npmjs.com/package/babel-polyfill),
+     * which rely on core-js.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a native function,
+     *  else `false`.
+     * @example
+     *
+     * _.isNative(Array.prototype.push);
+     * // => true
+     *
+     * _.isNative(_);
+     * // => false
+     */
+    function isNative(value) {
+      if (isMaskable(value)) {
+        throw new Error(CORE_ERROR_TEXT);
+      }
+      return baseIsNative(value);
+    }
+
+    /**
+     * Checks if `value` is `null`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is `null`, else `false`.
+     * @example
+     *
+     * _.isNull(null);
+     * // => true
+     *
+     * _.isNull(void 0);
+     * // => false
+     */
+    function isNull(value) {
+      return value === null;
+    }
+
+    /**
+     * Checks if `value` is `null` or `undefined`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is nullish, else `false`.
+     * @example
+     *
+     * _.isNil(null);
+     * // => true
+     *
+     * _.isNil(void 0);
+     * // => true
+     *
+     * _.isNil(NaN);
+     * // => false
+     */
+    function isNil(value) {
+      return value == null;
+    }
+
+    /**
+     * Checks if `value` is classified as a `Number` primitive or object.
+     *
+     * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are
+     * classified as numbers, use the `_.isFinite` method.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a number, else `false`.
+     * @example
+     *
+     * _.isNumber(3);
+     * // => true
+     *
+     * _.isNumber(Number.MIN_VALUE);
+     * // => true
+     *
+     * _.isNumber(Infinity);
+     * // => true
+     *
+     * _.isNumber('3');
+     * // => false
+     */
+    function isNumber(value) {
+      return typeof value == 'number' ||
+        (isObjectLike(value) && baseGetTag(value) == numberTag);
+    }
+
+    /**
+     * Checks if `value` is a plain object, that is, an object created by the
+     * `Object` constructor or one with a `[[Prototype]]` of `null`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.8.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a plain object, else `false`.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     * }
+     *
+     * _.isPlainObject(new Foo);
+     * // => false
+     *
+     * _.isPlainObject([1, 2, 3]);
+     * // => false
+     *
+     * _.isPlainObject({ 'x': 0, 'y': 0 });
+     * // => true
+     *
+     * _.isPlainObject(Object.create(null));
+     * // => true
+     */
+    function isPlainObject(value) {
+      if (!isObjectLike(value) || baseGetTag(value) != objectTag) {
+        return false;
+      }
+      var proto = getPrototype(value);
+      if (proto === null) {
+        return true;
+      }
+      var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor;
+      return typeof Ctor == 'function' && Ctor instanceof Ctor &&
+        funcToString.call(Ctor) == objectCtorString;
+    }
+
+    /**
+     * Checks if `value` is classified as a `RegExp` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a regexp, else `false`.
+     * @example
+     *
+     * _.isRegExp(/abc/);
+     * // => true
+     *
+     * _.isRegExp('/abc/');
+     * // => false
+     */
+    var isRegExp = nodeIsRegExp ? baseUnary(nodeIsRegExp) : baseIsRegExp;
+
+    /**
+     * Checks if `value` is a safe integer. An integer is safe if it's an IEEE-754
+     * double precision number which isn't the result of a rounded unsafe integer.
+     *
+     * **Note:** This method is based on
+     * [`Number.isSafeInteger`](https://mdn.io/Number/isSafeInteger).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a safe integer, else `false`.
+     * @example
+     *
+     * _.isSafeInteger(3);
+     * // => true
+     *
+     * _.isSafeInteger(Number.MIN_VALUE);
+     * // => false
+     *
+     * _.isSafeInteger(Infinity);
+     * // => false
+     *
+     * _.isSafeInteger('3');
+     * // => false
+     */
+    function isSafeInteger(value) {
+      return isInteger(value) && value >= -MAX_SAFE_INTEGER && value <= MAX_SAFE_INTEGER;
+    }
+
+    /**
+     * Checks if `value` is classified as a `Set` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.3.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a set, else `false`.
+     * @example
+     *
+     * _.isSet(new Set);
+     * // => true
+     *
+     * _.isSet(new WeakSet);
+     * // => false
+     */
+    var isSet = nodeIsSet ? baseUnary(nodeIsSet) : baseIsSet;
+
+    /**
+     * Checks if `value` is classified as a `String` primitive or object.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a string, else `false`.
+     * @example
+     *
+     * _.isString('abc');
+     * // => true
+     *
+     * _.isString(1);
+     * // => false
+     */
+    function isString(value) {
+      return typeof value == 'string' ||
+        (!isArray(value) && isObjectLike(value) && baseGetTag(value) == stringTag);
+    }
+
+    /**
+     * Checks if `value` is classified as a `Symbol` primitive or object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a symbol, else `false`.
+     * @example
+     *
+     * _.isSymbol(Symbol.iterator);
+     * // => true
+     *
+     * _.isSymbol('abc');
+     * // => false
+     */
+    function isSymbol(value) {
+      return typeof value == 'symbol' ||
+        (isObjectLike(value) && baseGetTag(value) == symbolTag);
+    }
+
+    /**
+     * Checks if `value` is classified as a typed array.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a typed array, else `false`.
+     * @example
+     *
+     * _.isTypedArray(new Uint8Array);
+     * // => true
+     *
+     * _.isTypedArray([]);
+     * // => false
+     */
+    var isTypedArray = nodeIsTypedArray ? baseUnary(nodeIsTypedArray) : baseIsTypedArray;
+
+    /**
+     * Checks if `value` is `undefined`.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
+     * @example
+     *
+     * _.isUndefined(void 0);
+     * // => true
+     *
+     * _.isUndefined(null);
+     * // => false
+     */
+    function isUndefined(value) {
+      return value === undefined;
+    }
+
+    /**
+     * Checks if `value` is classified as a `WeakMap` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.3.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a weak map, else `false`.
+     * @example
+     *
+     * _.isWeakMap(new WeakMap);
+     * // => true
+     *
+     * _.isWeakMap(new Map);
+     * // => false
+     */
+    function isWeakMap(value) {
+      return isObjectLike(value) && getTag(value) == weakMapTag;
+    }
+
+    /**
+     * Checks if `value` is classified as a `WeakSet` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.3.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a weak set, else `false`.
+     * @example
+     *
+     * _.isWeakSet(new WeakSet);
+     * // => true
+     *
+     * _.isWeakSet(new Set);
+     * // => false
+     */
+    function isWeakSet(value) {
+      return isObjectLike(value) && baseGetTag(value) == weakSetTag;
+    }
+
+    /**
+     * Checks if `value` is less than `other`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.9.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is less than `other`,
+     *  else `false`.
+     * @see _.gt
+     * @example
+     *
+     * _.lt(1, 3);
+     * // => true
+     *
+     * _.lt(3, 3);
+     * // => false
+     *
+     * _.lt(3, 1);
+     * // => false
+     */
+    var lt = createRelationalOperation(baseLt);
+
+    /**
+     * Checks if `value` is less than or equal to `other`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.9.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is less than or equal to
+     *  `other`, else `false`.
+     * @see _.gte
+     * @example
+     *
+     * _.lte(1, 3);
+     * // => true
+     *
+     * _.lte(3, 3);
+     * // => true
+     *
+     * _.lte(3, 1);
+     * // => false
+     */
+    var lte = createRelationalOperation(function(value, other) {
+      return value <= other;
+    });
+
+    /**
+     * Converts `value` to an array.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to convert.
+     * @returns {Array} Returns the converted array.
+     * @example
+     *
+     * _.toArray({ 'a': 1, 'b': 2 });
+     * // => [1, 2]
+     *
+     * _.toArray('abc');
+     * // => ['a', 'b', 'c']
+     *
+     * _.toArray(1);
+     * // => []
+     *
+     * _.toArray(null);
+     * // => []
+     */
+    function toArray(value) {
+      if (!value) {
+        return [];
+      }
+      if (isArrayLike(value)) {
+        return isString(value) ? stringToArray(value) : copyArray(value);
+      }
+      if (symIterator && value[symIterator]) {
+        return iteratorToArray(value[symIterator]());
+      }
+      var tag = getTag(value),
+          func = tag == mapTag ? mapToArray : (tag == setTag ? setToArray : values);
+
+      return func(value);
+    }
+
+    /**
+     * Converts `value` to a finite number.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.12.0
+     * @category Lang
+     * @param {*} value The value to convert.
+     * @returns {number} Returns the converted number.
+     * @example
+     *
+     * _.toFinite(3.2);
+     * // => 3.2
+     *
+     * _.toFinite(Number.MIN_VALUE);
+     * // => 5e-324
+     *
+     * _.toFinite(Infinity);
+     * // => 1.7976931348623157e+308
+     *
+     * _.toFinite('3.2');
+     * // => 3.2
+     */
+    function toFinite(value) {
+      if (!value) {
+        return value === 0 ? value : 0;
+      }
+      value = toNumber(value);
+      if (value === INFINITY || value === -INFINITY) {
+        var sign = (value < 0 ? -1 : 1);
+        return sign * MAX_INTEGER;
+      }
+      return value === value ? value : 0;
+    }
+
+    /**
+     * Converts `value` to an integer.
+     *
+     * **Note:** This method is loosely based on
+     * [`ToInteger`](http://www.ecma-international.org/ecma-262/7.0/#sec-tointeger).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to convert.
+     * @returns {number} Returns the converted integer.
+     * @example
+     *
+     * _.toInteger(3.2);
+     * // => 3
+     *
+     * _.toInteger(Number.MIN_VALUE);
+     * // => 0
+     *
+     * _.toInteger(Infinity);
+     * // => 1.7976931348623157e+308
+     *
+     * _.toInteger('3.2');
+     * // => 3
+     */
+    function toInteger(value) {
+      var result = toFinite(value),
+          remainder = result % 1;
+
+      return result === result ? (remainder ? result - remainder : result) : 0;
+    }
+
+    /**
+     * Converts `value` to an integer suitable for use as the length of an
+     * array-like object.
+     *
+     * **Note:** This method is based on
+     * [`ToLength`](http://ecma-international.org/ecma-262/7.0/#sec-tolength).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to convert.
+     * @returns {number} Returns the converted integer.
+     * @example
+     *
+     * _.toLength(3.2);
+     * // => 3
+     *
+     * _.toLength(Number.MIN_VALUE);
+     * // => 0
+     *
+     * _.toLength(Infinity);
+     * // => 4294967295
+     *
+     * _.toLength('3.2');
+     * // => 3
+     */
+    function toLength(value) {
+      return value ? baseClamp(toInteger(value), 0, MAX_ARRAY_LENGTH) : 0;
+    }
+
+    /**
+     * Converts `value` to a number.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to process.
+     * @returns {number} Returns the number.
+     * @example
+     *
+     * _.toNumber(3.2);
+     * // => 3.2
+     *
+     * _.toNumber(Number.MIN_VALUE);
+     * // => 5e-324
+     *
+     * _.toNumber(Infinity);
+     * // => Infinity
+     *
+     * _.toNumber('3.2');
+     * // => 3.2
+     */
+    function toNumber(value) {
+      if (typeof value == 'number') {
+        return value;
+      }
+      if (isSymbol(value)) {
+        return NAN;
+      }
+      if (isObject(value)) {
+        var other = typeof value.valueOf == 'function' ? value.valueOf() : value;
+        value = isObject(other) ? (other + '') : other;
+      }
+      if (typeof value != 'string') {
+        return value === 0 ? value : +value;
+      }
+      value = value.replace(reTrim, '');
+      var isBinary = reIsBinary.test(value);
+      return (isBinary || reIsOctal.test(value))
+        ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
+        : (reIsBadHex.test(value) ? NAN : +value);
+    }
+
+    /**
+     * Converts `value` to a plain object flattening inherited enumerable string
+     * keyed properties of `value` to own properties of the plain object.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Lang
+     * @param {*} value The value to convert.
+     * @returns {Object} Returns the converted plain object.
+     * @example
+     *
+     * function Foo() {
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.assign({ 'a': 1 }, new Foo);
+     * // => { 'a': 1, 'b': 2 }
+     *
+     * _.assign({ 'a': 1 }, _.toPlainObject(new Foo));
+     * // => { 'a': 1, 'b': 2, 'c': 3 }
+     */
+    function toPlainObject(value) {
+      return copyObject(value, keysIn(value));
+    }
+
+    /**
+     * Converts `value` to a safe integer. A safe integer can be compared and
+     * represented correctly.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to convert.
+     * @returns {number} Returns the converted integer.
+     * @example
+     *
+     * _.toSafeInteger(3.2);
+     * // => 3
+     *
+     * _.toSafeInteger(Number.MIN_VALUE);
+     * // => 0
+     *
+     * _.toSafeInteger(Infinity);
+     * // => 9007199254740991
+     *
+     * _.toSafeInteger('3.2');
+     * // => 3
+     */
+    function toSafeInteger(value) {
+      return value
+        ? baseClamp(toInteger(value), -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER)
+        : (value === 0 ? value : 0);
+    }
+
+    /**
+     * Converts `value` to a string. An empty string is returned for `null`
+     * and `undefined` values. The sign of `-0` is preserved.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to convert.
+     * @returns {string} Returns the converted string.
+     * @example
+     *
+     * _.toString(null);
+     * // => ''
+     *
+     * _.toString(-0);
+     * // => '-0'
+     *
+     * _.toString([1, 2, 3]);
+     * // => '1,2,3'
+     */
+    function toString(value) {
+      return value == null ? '' : baseToString(value);
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Assigns own enumerable string keyed properties of source objects to the
+     * destination object. Source objects are applied from left to right.
+     * Subsequent sources overwrite property assignments of previous sources.
+     *
+     * **Note:** This method mutates `object` and is loosely based on
+     * [`Object.assign`](https://mdn.io/Object/assign).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.10.0
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} [sources] The source objects.
+     * @returns {Object} Returns `object`.
+     * @see _.assignIn
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     * }
+     *
+     * function Bar() {
+     *   this.c = 3;
+     * }
+     *
+     * Foo.prototype.b = 2;
+     * Bar.prototype.d = 4;
+     *
+     * _.assign({ 'a': 0 }, new Foo, new Bar);
+     * // => { 'a': 1, 'c': 3 }
+     */
+    var assign = createAssigner(function(object, source) {
+      if (isPrototype(source) || isArrayLike(source)) {
+        copyObject(source, keys(source), object);
+        return;
+      }
+      for (var key in source) {
+        if (hasOwnProperty.call(source, key)) {
+          assignValue(object, key, source[key]);
+        }
+      }
+    });
+
+    /**
+     * This method is like `_.assign` except that it iterates over own and
+     * inherited source properties.
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @alias extend
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} [sources] The source objects.
+     * @returns {Object} Returns `object`.
+     * @see _.assign
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     * }
+     *
+     * function Bar() {
+     *   this.c = 3;
+     * }
+     *
+     * Foo.prototype.b = 2;
+     * Bar.prototype.d = 4;
+     *
+     * _.assignIn({ 'a': 0 }, new Foo, new Bar);
+     * // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4 }
+     */
+    var assignIn = createAssigner(function(object, source) {
+      copyObject(source, keysIn(source), object);
+    });
+
+    /**
+     * This method is like `_.assignIn` except that it accepts `customizer`
+     * which is invoked to produce the assigned values. If `customizer` returns
+     * `undefined`, assignment is handled by the method instead. The `customizer`
+     * is invoked with five arguments: (objValue, srcValue, key, object, source).
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @alias extendWith
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} sources The source objects.
+     * @param {Function} [customizer] The function to customize assigned values.
+     * @returns {Object} Returns `object`.
+     * @see _.assignWith
+     * @example
+     *
+     * function customizer(objValue, srcValue) {
+     *   return _.isUndefined(objValue) ? srcValue : objValue;
+     * }
+     *
+     * var defaults = _.partialRight(_.assignInWith, customizer);
+     *
+     * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
+     * // => { 'a': 1, 'b': 2 }
+     */
+    var assignInWith = createAssigner(function(object, source, srcIndex, customizer) {
+      copyObject(source, keysIn(source), object, customizer);
+    });
+
+    /**
+     * This method is like `_.assign` except that it accepts `customizer`
+     * which is invoked to produce the assigned values. If `customizer` returns
+     * `undefined`, assignment is handled by the method instead. The `customizer`
+     * is invoked with five arguments: (objValue, srcValue, key, object, source).
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} sources The source objects.
+     * @param {Function} [customizer] The function to customize assigned values.
+     * @returns {Object} Returns `object`.
+     * @see _.assignInWith
+     * @example
+     *
+     * function customizer(objValue, srcValue) {
+     *   return _.isUndefined(objValue) ? srcValue : objValue;
+     * }
+     *
+     * var defaults = _.partialRight(_.assignWith, customizer);
+     *
+     * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
+     * // => { 'a': 1, 'b': 2 }
+     */
+    var assignWith = createAssigner(function(object, source, srcIndex, customizer) {
+      copyObject(source, keys(source), object, customizer);
+    });
+
+    /**
+     * Creates an array of values corresponding to `paths` of `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.0.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {...(string|string[])} [paths] The property paths to pick.
+     * @returns {Array} Returns the picked values.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
+     *
+     * _.at(object, ['a[0].b.c', 'a[1]']);
+     * // => [3, 4]
+     */
+    var at = flatRest(baseAt);
+
+    /**
+     * Creates an object that inherits from the `prototype` object. If a
+     * `properties` object is given, its own enumerable string keyed properties
+     * are assigned to the created object.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.3.0
+     * @category Object
+     * @param {Object} prototype The object to inherit from.
+     * @param {Object} [properties] The properties to assign to the object.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * function Shape() {
+     *   this.x = 0;
+     *   this.y = 0;
+     * }
+     *
+     * function Circle() {
+     *   Shape.call(this);
+     * }
+     *
+     * Circle.prototype = _.create(Shape.prototype, {
+     *   'constructor': Circle
+     * });
+     *
+     * var circle = new Circle;
+     * circle instanceof Circle;
+     * // => true
+     *
+     * circle instanceof Shape;
+     * // => true
+     */
+    function create(prototype, properties) {
+      var result = baseCreate(prototype);
+      return properties == null ? result : baseAssign(result, properties);
+    }
+
+    /**
+     * Assigns own and inherited enumerable string keyed properties of source
+     * objects to the destination object for all destination properties that
+     * resolve to `undefined`. Source objects are applied from left to right.
+     * Once a property is set, additional values of the same property are ignored.
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} [sources] The source objects.
+     * @returns {Object} Returns `object`.
+     * @see _.defaultsDeep
+     * @example
+     *
+     * _.defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
+     * // => { 'a': 1, 'b': 2 }
+     */
+    var defaults = baseRest(function(args) {
+      args.push(undefined, customDefaultsAssignIn);
+      return apply(assignInWith, undefined, args);
+    });
+
+    /**
+     * This method is like `_.defaults` except that it recursively assigns
+     * default properties.
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.10.0
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} [sources] The source objects.
+     * @returns {Object} Returns `object`.
+     * @see _.defaults
+     * @example
+     *
+     * _.defaultsDeep({ 'a': { 'b': 2 } }, { 'a': { 'b': 1, 'c': 3 } });
+     * // => { 'a': { 'b': 2, 'c': 3 } }
+     */
+    var defaultsDeep = baseRest(function(args) {
+      args.push(undefined, customDefaultsMerge);
+      return apply(mergeWith, undefined, args);
+    });
+
+    /**
+     * This method is like `_.find` except that it returns the key of the first
+     * element `predicate` returns truthy for instead of the element itself.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.1.0
+     * @category Object
+     * @param {Object} object The object to inspect.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @returns {string|undefined} Returns the key of the matched element,
+     *  else `undefined`.
+     * @example
+     *
+     * var users = {
+     *   'barney':  { 'age': 36, 'active': true },
+     *   'fred':    { 'age': 40, 'active': false },
+     *   'pebbles': { 'age': 1,  'active': true }
+     * };
+     *
+     * _.findKey(users, function(o) { return o.age < 40; });
+     * // => 'barney' (iteration order is not guaranteed)
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.findKey(users, { 'age': 1, 'active': true });
+     * // => 'pebbles'
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.findKey(users, ['active', false]);
+     * // => 'fred'
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.findKey(users, 'active');
+     * // => 'barney'
+     */
+    function findKey(object, predicate) {
+      return baseFindKey(object, getIteratee(predicate, 3), baseForOwn);
+    }
+
+    /**
+     * This method is like `_.findKey` except that it iterates over elements of
+     * a collection in the opposite order.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Object
+     * @param {Object} object The object to inspect.
+     * @param {Function} [predicate=_.identity] The function invoked per iteration.
+     * @returns {string|undefined} Returns the key of the matched element,
+     *  else `undefined`.
+     * @example
+     *
+     * var users = {
+     *   'barney':  { 'age': 36, 'active': true },
+     *   'fred':    { 'age': 40, 'active': false },
+     *   'pebbles': { 'age': 1,  'active': true }
+     * };
+     *
+     * _.findLastKey(users, function(o) { return o.age < 40; });
+     * // => returns 'pebbles' assuming `_.findKey` returns 'barney'
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.findLastKey(users, { 'age': 36, 'active': true });
+     * // => 'barney'
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.findLastKey(users, ['active', false]);
+     * // => 'fred'
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.findLastKey(users, 'active');
+     * // => 'pebbles'
+     */
+    function findLastKey(object, predicate) {
+      return baseFindKey(object, getIteratee(predicate, 3), baseForOwnRight);
+    }
+
+    /**
+     * Iterates over own and inherited enumerable string keyed properties of an
+     * object and invokes `iteratee` for each property. The iteratee is invoked
+     * with three arguments: (value, key, object). Iteratee functions may exit
+     * iteration early by explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.3.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     * @see _.forInRight
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.forIn(new Foo, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => Logs 'a', 'b', then 'c' (iteration order is not guaranteed).
+     */
+    function forIn(object, iteratee) {
+      return object == null
+        ? object
+        : baseFor(object, getIteratee(iteratee, 3), keysIn);
+    }
+
+    /**
+     * This method is like `_.forIn` except that it iterates over properties of
+     * `object` in the opposite order.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     * @see _.forIn
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.forInRight(new Foo, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => Logs 'c', 'b', then 'a' assuming `_.forIn` logs 'a', 'b', then 'c'.
+     */
+    function forInRight(object, iteratee) {
+      return object == null
+        ? object
+        : baseForRight(object, getIteratee(iteratee, 3), keysIn);
+    }
+
+    /**
+     * Iterates over own enumerable string keyed properties of an object and
+     * invokes `iteratee` for each property. The iteratee is invoked with three
+     * arguments: (value, key, object). Iteratee functions may exit iteration
+     * early by explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.3.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     * @see _.forOwnRight
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.forOwn(new Foo, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => Logs 'a' then 'b' (iteration order is not guaranteed).
+     */
+    function forOwn(object, iteratee) {
+      return object && baseForOwn(object, getIteratee(iteratee, 3));
+    }
+
+    /**
+     * This method is like `_.forOwn` except that it iterates over properties of
+     * `object` in the opposite order.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     * @see _.forOwn
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.forOwnRight(new Foo, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => Logs 'b' then 'a' assuming `_.forOwn` logs 'a' then 'b'.
+     */
+    function forOwnRight(object, iteratee) {
+      return object && baseForOwnRight(object, getIteratee(iteratee, 3));
+    }
+
+    /**
+     * Creates an array of function property names from own enumerable properties
+     * of `object`.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns the function names.
+     * @see _.functionsIn
+     * @example
+     *
+     * function Foo() {
+     *   this.a = _.constant('a');
+     *   this.b = _.constant('b');
+     * }
+     *
+     * Foo.prototype.c = _.constant('c');
+     *
+     * _.functions(new Foo);
+     * // => ['a', 'b']
+     */
+    function functions(object) {
+      return object == null ? [] : baseFunctions(object, keys(object));
+    }
+
+    /**
+     * Creates an array of function property names from own and inherited
+     * enumerable properties of `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns the function names.
+     * @see _.functions
+     * @example
+     *
+     * function Foo() {
+     *   this.a = _.constant('a');
+     *   this.b = _.constant('b');
+     * }
+     *
+     * Foo.prototype.c = _.constant('c');
+     *
+     * _.functionsIn(new Foo);
+     * // => ['a', 'b', 'c']
+     */
+    function functionsIn(object) {
+      return object == null ? [] : baseFunctions(object, keysIn(object));
+    }
+
+    /**
+     * Gets the value at `path` of `object`. If the resolved value is
+     * `undefined`, the `defaultValue` is returned in its place.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.7.0
+     * @category Object
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the property to get.
+     * @param {*} [defaultValue] The value returned for `undefined` resolved values.
+     * @returns {*} Returns the resolved value.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+     *
+     * _.get(object, 'a[0].b.c');
+     * // => 3
+     *
+     * _.get(object, ['a', '0', 'b', 'c']);
+     * // => 3
+     *
+     * _.get(object, 'a.b.c', 'default');
+     * // => 'default'
+     */
+    function get(object, path, defaultValue) {
+      var result = object == null ? undefined : baseGet(object, path);
+      return result === undefined ? defaultValue : result;
+    }
+
+    /**
+     * Checks if `path` is a direct property of `object`.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path to check.
+     * @returns {boolean} Returns `true` if `path` exists, else `false`.
+     * @example
+     *
+     * var object = { 'a': { 'b': 2 } };
+     * var other = _.create({ 'a': _.create({ 'b': 2 }) });
+     *
+     * _.has(object, 'a');
+     * // => true
+     *
+     * _.has(object, 'a.b');
+     * // => true
+     *
+     * _.has(object, ['a', 'b']);
+     * // => true
+     *
+     * _.has(other, 'a');
+     * // => false
+     */
+    function has(object, path) {
+      return object != null && hasPath(object, path, baseHas);
+    }
+
+    /**
+     * Checks if `path` is a direct or inherited property of `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path to check.
+     * @returns {boolean} Returns `true` if `path` exists, else `false`.
+     * @example
+     *
+     * var object = _.create({ 'a': _.create({ 'b': 2 }) });
+     *
+     * _.hasIn(object, 'a');
+     * // => true
+     *
+     * _.hasIn(object, 'a.b');
+     * // => true
+     *
+     * _.hasIn(object, ['a', 'b']);
+     * // => true
+     *
+     * _.hasIn(object, 'b');
+     * // => false
+     */
+    function hasIn(object, path) {
+      return object != null && hasPath(object, path, baseHasIn);
+    }
+
+    /**
+     * Creates an object composed of the inverted keys and values of `object`.
+     * If `object` contains duplicate values, subsequent values overwrite
+     * property assignments of previous values.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.7.0
+     * @category Object
+     * @param {Object} object The object to invert.
+     * @returns {Object} Returns the new inverted object.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': 2, 'c': 1 };
+     *
+     * _.invert(object);
+     * // => { '1': 'c', '2': 'b' }
+     */
+    var invert = createInverter(function(result, value, key) {
+      result[value] = key;
+    }, constant(identity));
+
+    /**
+     * This method is like `_.invert` except that the inverted object is generated
+     * from the results of running each element of `object` thru `iteratee`. The
+     * corresponding inverted value of each inverted key is an array of keys
+     * responsible for generating the inverted value. The iteratee is invoked
+     * with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.1.0
+     * @category Object
+     * @param {Object} object The object to invert.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {Object} Returns the new inverted object.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': 2, 'c': 1 };
+     *
+     * _.invertBy(object);
+     * // => { '1': ['a', 'c'], '2': ['b'] }
+     *
+     * _.invertBy(object, function(value) {
+     *   return 'group' + value;
+     * });
+     * // => { 'group1': ['a', 'c'], 'group2': ['b'] }
+     */
+    var invertBy = createInverter(function(result, value, key) {
+      if (hasOwnProperty.call(result, value)) {
+        result[value].push(key);
+      } else {
+        result[value] = [key];
+      }
+    }, getIteratee);
+
+    /**
+     * Invokes the method at `path` of `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the method to invoke.
+     * @param {...*} [args] The arguments to invoke the method with.
+     * @returns {*} Returns the result of the invoked method.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] };
+     *
+     * _.invoke(object, 'a[0].b.c.slice', 1, 3);
+     * // => [2, 3]
+     */
+    var invoke = baseRest(baseInvoke);
+
+    /**
+     * Creates an array of the own enumerable property names of `object`.
+     *
+     * **Note:** Non-object values are coerced to objects. See the
+     * [ES spec](http://ecma-international.org/ecma-262/7.0/#sec-object.keys)
+     * for more details.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.keys(new Foo);
+     * // => ['a', 'b'] (iteration order is not guaranteed)
+     *
+     * _.keys('hi');
+     * // => ['0', '1']
+     */
+    function keys(object) {
+      return isArrayLike(object) ? arrayLikeKeys(object) : baseKeys(object);
+    }
+
+    /**
+     * Creates an array of the own and inherited enumerable property names of `object`.
+     *
+     * **Note:** Non-object values are coerced to objects.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.keysIn(new Foo);
+     * // => ['a', 'b', 'c'] (iteration order is not guaranteed)
+     */
+    function keysIn(object) {
+      return isArrayLike(object) ? arrayLikeKeys(object, true) : baseKeysIn(object);
+    }
+
+    /**
+     * The opposite of `_.mapValues`; this method creates an object with the
+     * same values as `object` and keys generated by running each own enumerable
+     * string keyed property of `object` thru `iteratee`. The iteratee is invoked
+     * with three arguments: (value, key, object).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.8.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Object} Returns the new mapped object.
+     * @see _.mapValues
+     * @example
+     *
+     * _.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) {
+     *   return key + value;
+     * });
+     * // => { 'a1': 1, 'b2': 2 }
+     */
+    function mapKeys(object, iteratee) {
+      var result = {};
+      iteratee = getIteratee(iteratee, 3);
+
+      baseForOwn(object, function(value, key, object) {
+        baseAssignValue(result, iteratee(value, key, object), value);
+      });
+      return result;
+    }
+
+    /**
+     * Creates an object with the same keys as `object` and values generated
+     * by running each own enumerable string keyed property of `object` thru
+     * `iteratee`. The iteratee is invoked with three arguments:
+     * (value, key, object).
+     *
+     * @static
+     * @memberOf _
+     * @since 2.4.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Object} Returns the new mapped object.
+     * @see _.mapKeys
+     * @example
+     *
+     * var users = {
+     *   'fred':    { 'user': 'fred',    'age': 40 },
+     *   'pebbles': { 'user': 'pebbles', 'age': 1 }
+     * };
+     *
+     * _.mapValues(users, function(o) { return o.age; });
+     * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.mapValues(users, 'age');
+     * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
+     */
+    function mapValues(object, iteratee) {
+      var result = {};
+      iteratee = getIteratee(iteratee, 3);
+
+      baseForOwn(object, function(value, key, object) {
+        baseAssignValue(result, key, iteratee(value, key, object));
+      });
+      return result;
+    }
+
+    /**
+     * This method is like `_.assign` except that it recursively merges own and
+     * inherited enumerable string keyed properties of source objects into the
+     * destination object. Source properties that resolve to `undefined` are
+     * skipped if a destination value exists. Array and plain object properties
+     * are merged recursively. Other objects and value types are overridden by
+     * assignment. Source objects are applied from left to right. Subsequent
+     * sources overwrite property assignments of previous sources.
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.5.0
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} [sources] The source objects.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var object = {
+     *   'a': [{ 'b': 2 }, { 'd': 4 }]
+     * };
+     *
+     * var other = {
+     *   'a': [{ 'c': 3 }, { 'e': 5 }]
+     * };
+     *
+     * _.merge(object, other);
+     * // => { 'a': [{ 'b': 2, 'c': 3 }, { 'd': 4, 'e': 5 }] }
+     */
+    var merge = createAssigner(function(object, source, srcIndex) {
+      baseMerge(object, source, srcIndex);
+    });
+
+    /**
+     * This method is like `_.merge` except that it accepts `customizer` which
+     * is invoked to produce the merged values of the destination and source
+     * properties. If `customizer` returns `undefined`, merging is handled by the
+     * method instead. The `customizer` is invoked with six arguments:
+     * (objValue, srcValue, key, object, source, stack).
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} sources The source objects.
+     * @param {Function} customizer The function to customize assigned values.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * function customizer(objValue, srcValue) {
+     *   if (_.isArray(objValue)) {
+     *     return objValue.concat(srcValue);
+     *   }
+     * }
+     *
+     * var object = { 'a': [1], 'b': [2] };
+     * var other = { 'a': [3], 'b': [4] };
+     *
+     * _.mergeWith(object, other, customizer);
+     * // => { 'a': [1, 3], 'b': [2, 4] }
+     */
+    var mergeWith = createAssigner(function(object, source, srcIndex, customizer) {
+      baseMerge(object, source, srcIndex, customizer);
+    });
+
+    /**
+     * The opposite of `_.pick`; this method creates an object composed of the
+     * own and inherited enumerable property paths of `object` that are not omitted.
+     *
+     * **Note:** This method is considerably slower than `_.pick`.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The source object.
+     * @param {...(string|string[])} [paths] The property paths to omit.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': '2', 'c': 3 };
+     *
+     * _.omit(object, ['a', 'c']);
+     * // => { 'b': '2' }
+     */
+    var omit = flatRest(function(object, paths) {
+      var result = {};
+      if (object == null) {
+        return result;
+      }
+      var isDeep = false;
+      paths = arrayMap(paths, function(path) {
+        path = castPath(path, object);
+        isDeep || (isDeep = path.length > 1);
+        return path;
+      });
+      copyObject(object, getAllKeysIn(object), result);
+      if (isDeep) {
+        result = baseClone(result, CLONE_DEEP_FLAG | CLONE_FLAT_FLAG | CLONE_SYMBOLS_FLAG, customOmitClone);
+      }
+      var length = paths.length;
+      while (length--) {
+        baseUnset(result, paths[length]);
+      }
+      return result;
+    });
+
+    /**
+     * The opposite of `_.pickBy`; this method creates an object composed of
+     * the own and inherited enumerable string keyed properties of `object` that
+     * `predicate` doesn't return truthy for. The predicate is invoked with two
+     * arguments: (value, key).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The source object.
+     * @param {Function} [predicate=_.identity] The function invoked per property.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': '2', 'c': 3 };
+     *
+     * _.omitBy(object, _.isNumber);
+     * // => { 'b': '2' }
+     */
+    function omitBy(object, predicate) {
+      return pickBy(object, negate(getIteratee(predicate)));
+    }
+
+    /**
+     * Creates an object composed of the picked `object` properties.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The source object.
+     * @param {...(string|string[])} [paths] The property paths to pick.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': '2', 'c': 3 };
+     *
+     * _.pick(object, ['a', 'c']);
+     * // => { 'a': 1, 'c': 3 }
+     */
+    var pick = flatRest(function(object, paths) {
+      return object == null ? {} : basePick(object, paths);
+    });
+
+    /**
+     * Creates an object composed of the `object` properties `predicate` returns
+     * truthy for. The predicate is invoked with two arguments: (value, key).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The source object.
+     * @param {Function} [predicate=_.identity] The function invoked per property.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': '2', 'c': 3 };
+     *
+     * _.pickBy(object, _.isNumber);
+     * // => { 'a': 1, 'c': 3 }
+     */
+    function pickBy(object, predicate) {
+      if (object == null) {
+        return {};
+      }
+      var props = arrayMap(getAllKeysIn(object), function(prop) {
+        return [prop];
+      });
+      predicate = getIteratee(predicate);
+      return basePickBy(object, props, function(value, path) {
+        return predicate(value, path[0]);
+      });
+    }
+
+    /**
+     * This method is like `_.get` except that if the resolved value is a
+     * function it's invoked with the `this` binding of its parent object and
+     * its result is returned.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the property to resolve.
+     * @param {*} [defaultValue] The value returned for `undefined` resolved values.
+     * @returns {*} Returns the resolved value.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] };
+     *
+     * _.result(object, 'a[0].b.c1');
+     * // => 3
+     *
+     * _.result(object, 'a[0].b.c2');
+     * // => 4
+     *
+     * _.result(object, 'a[0].b.c3', 'default');
+     * // => 'default'
+     *
+     * _.result(object, 'a[0].b.c3', _.constant('default'));
+     * // => 'default'
+     */
+    function result(object, path, defaultValue) {
+      path = castPath(path, object);
+
+      var index = -1,
+          length = path.length;
+
+      // Ensure the loop is entered when path is empty.
+      if (!length) {
+        length = 1;
+        object = undefined;
+      }
+      while (++index < length) {
+        var value = object == null ? undefined : object[toKey(path[index])];
+        if (value === undefined) {
+          index = length;
+          value = defaultValue;
+        }
+        object = isFunction(value) ? value.call(object) : value;
+      }
+      return object;
+    }
+
+    /**
+     * Sets the value at `path` of `object`. If a portion of `path` doesn't exist,
+     * it's created. Arrays are created for missing index properties while objects
+     * are created for all other missing properties. Use `_.setWith` to customize
+     * `path` creation.
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.7.0
+     * @category Object
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to set.
+     * @param {*} value The value to set.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+     *
+     * _.set(object, 'a[0].b.c', 4);
+     * console.log(object.a[0].b.c);
+     * // => 4
+     *
+     * _.set(object, ['x', '0', 'y', 'z'], 5);
+     * console.log(object.x[0].y.z);
+     * // => 5
+     */
+    function set(object, path, value) {
+      return object == null ? object : baseSet(object, path, value);
+    }
+
+    /**
+     * This method is like `_.set` except that it accepts `customizer` which is
+     * invoked to produce the objects of `path`.  If `customizer` returns `undefined`
+     * path creation is handled by the method instead. The `customizer` is invoked
+     * with three arguments: (nsValue, key, nsObject).
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to set.
+     * @param {*} value The value to set.
+     * @param {Function} [customizer] The function to customize assigned values.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var object = {};
+     *
+     * _.setWith(object, '[0][1]', 'a', Object);
+     * // => { '0': { '1': 'a' } }
+     */
+    function setWith(object, path, value, customizer) {
+      customizer = typeof customizer == 'function' ? customizer : undefined;
+      return object == null ? object : baseSet(object, path, value, customizer);
+    }
+
+    /**
+     * Creates an array of own enumerable string keyed-value pairs for `object`
+     * which can be consumed by `_.fromPairs`. If `object` is a map or set, its
+     * entries are returned.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @alias entries
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the key-value pairs.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.toPairs(new Foo);
+     * // => [['a', 1], ['b', 2]] (iteration order is not guaranteed)
+     */
+    var toPairs = createToPairs(keys);
+
+    /**
+     * Creates an array of own and inherited enumerable string keyed-value pairs
+     * for `object` which can be consumed by `_.fromPairs`. If `object` is a map
+     * or set, its entries are returned.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @alias entriesIn
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the key-value pairs.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.toPairsIn(new Foo);
+     * // => [['a', 1], ['b', 2], ['c', 3]] (iteration order is not guaranteed)
+     */
+    var toPairsIn = createToPairs(keysIn);
+
+    /**
+     * An alternative to `_.reduce`; this method transforms `object` to a new
+     * `accumulator` object which is the result of running each of its own
+     * enumerable string keyed properties thru `iteratee`, with each invocation
+     * potentially mutating the `accumulator` object. If `accumulator` is not
+     * provided, a new object with the same `[[Prototype]]` will be used. The
+     * iteratee is invoked with four arguments: (accumulator, value, key, object).
+     * Iteratee functions may exit iteration early by explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.3.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [accumulator] The custom accumulator value.
+     * @returns {*} Returns the accumulated value.
+     * @example
+     *
+     * _.transform([2, 3, 4], function(result, n) {
+     *   result.push(n *= n);
+     *   return n % 2 == 0;
+     * }, []);
+     * // => [4, 9]
+     *
+     * _.transform({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
+     *   (result[value] || (result[value] = [])).push(key);
+     * }, {});
+     * // => { '1': ['a', 'c'], '2': ['b'] }
+     */
+    function transform(object, iteratee, accumulator) {
+      var isArr = isArray(object),
+          isArrLike = isArr || isBuffer(object) || isTypedArray(object);
+
+      iteratee = getIteratee(iteratee, 4);
+      if (accumulator == null) {
+        var Ctor = object && object.constructor;
+        if (isArrLike) {
+          accumulator = isArr ? new Ctor : [];
+        }
+        else if (isObject(object)) {
+          accumulator = isFunction(Ctor) ? baseCreate(getPrototype(object)) : {};
+        }
+        else {
+          accumulator = {};
+        }
+      }
+      (isArrLike ? arrayEach : baseForOwn)(object, function(value, index, object) {
+        return iteratee(accumulator, value, index, object);
+      });
+      return accumulator;
+    }
+
+    /**
+     * Removes the property at `path` of `object`.
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to unset.
+     * @returns {boolean} Returns `true` if the property is deleted, else `false`.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 7 } }] };
+     * _.unset(object, 'a[0].b.c');
+     * // => true
+     *
+     * console.log(object);
+     * // => { 'a': [{ 'b': {} }] };
+     *
+     * _.unset(object, ['a', '0', 'b', 'c']);
+     * // => true
+     *
+     * console.log(object);
+     * // => { 'a': [{ 'b': {} }] };
+     */
+    function unset(object, path) {
+      return object == null ? true : baseUnset(object, path);
+    }
+
+    /**
+     * This method is like `_.set` except that accepts `updater` to produce the
+     * value to set. Use `_.updateWith` to customize `path` creation. The `updater`
+     * is invoked with one argument: (value).
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.6.0
+     * @category Object
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to set.
+     * @param {Function} updater The function to produce the updated value.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+     *
+     * _.update(object, 'a[0].b.c', function(n) { return n * n; });
+     * console.log(object.a[0].b.c);
+     * // => 9
+     *
+     * _.update(object, 'x[0].y.z', function(n) { return n ? n + 1 : 0; });
+     * console.log(object.x[0].y.z);
+     * // => 0
+     */
+    function update(object, path, updater) {
+      return object == null ? object : baseUpdate(object, path, castFunction(updater));
+    }
+
+    /**
+     * This method is like `_.update` except that it accepts `customizer` which is
+     * invoked to produce the objects of `path`.  If `customizer` returns `undefined`
+     * path creation is handled by the method instead. The `customizer` is invoked
+     * with three arguments: (nsValue, key, nsObject).
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.6.0
+     * @category Object
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to set.
+     * @param {Function} updater The function to produce the updated value.
+     * @param {Function} [customizer] The function to customize assigned values.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var object = {};
+     *
+     * _.updateWith(object, '[0][1]', _.constant('a'), Object);
+     * // => { '0': { '1': 'a' } }
+     */
+    function updateWith(object, path, updater, customizer) {
+      customizer = typeof customizer == 'function' ? customizer : undefined;
+      return object == null ? object : baseUpdate(object, path, castFunction(updater), customizer);
+    }
+
+    /**
+     * Creates an array of the own enumerable string keyed property values of `object`.
+     *
+     * **Note:** Non-object values are coerced to objects.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property values.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.values(new Foo);
+     * // => [1, 2] (iteration order is not guaranteed)
+     *
+     * _.values('hi');
+     * // => ['h', 'i']
+     */
+    function values(object) {
+      return object == null ? [] : baseValues(object, keys(object));
+    }
+
+    /**
+     * Creates an array of the own and inherited enumerable string keyed property
+     * values of `object`.
+     *
+     * **Note:** Non-object values are coerced to objects.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property values.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.valuesIn(new Foo);
+     * // => [1, 2, 3] (iteration order is not guaranteed)
+     */
+    function valuesIn(object) {
+      return object == null ? [] : baseValues(object, keysIn(object));
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Clamps `number` within the inclusive `lower` and `upper` bounds.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Number
+     * @param {number} number The number to clamp.
+     * @param {number} [lower] The lower bound.
+     * @param {number} upper The upper bound.
+     * @returns {number} Returns the clamped number.
+     * @example
+     *
+     * _.clamp(-10, -5, 5);
+     * // => -5
+     *
+     * _.clamp(10, -5, 5);
+     * // => 5
+     */
+    function clamp(number, lower, upper) {
+      if (upper === undefined) {
+        upper = lower;
+        lower = undefined;
+      }
+      if (upper !== undefined) {
+        upper = toNumber(upper);
+        upper = upper === upper ? upper : 0;
+      }
+      if (lower !== undefined) {
+        lower = toNumber(lower);
+        lower = lower === lower ? lower : 0;
+      }
+      return baseClamp(toNumber(number), lower, upper);
+    }
+
+    /**
+     * Checks if `n` is between `start` and up to, but not including, `end`. If
+     * `end` is not specified, it's set to `start` with `start` then set to `0`.
+     * If `start` is greater than `end` the params are swapped to support
+     * negative ranges.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.3.0
+     * @category Number
+     * @param {number} number The number to check.
+     * @param {number} [start=0] The start of the range.
+     * @param {number} end The end of the range.
+     * @returns {boolean} Returns `true` if `number` is in the range, else `false`.
+     * @see _.range, _.rangeRight
+     * @example
+     *
+     * _.inRange(3, 2, 4);
+     * // => true
+     *
+     * _.inRange(4, 8);
+     * // => true
+     *
+     * _.inRange(4, 2);
+     * // => false
+     *
+     * _.inRange(2, 2);
+     * // => false
+     *
+     * _.inRange(1.2, 2);
+     * // => true
+     *
+     * _.inRange(5.2, 4);
+     * // => false
+     *
+     * _.inRange(-3, -2, -6);
+     * // => true
+     */
+    function inRange(number, start, end) {
+      start = toFinite(start);
+      if (end === undefined) {
+        end = start;
+        start = 0;
+      } else {
+        end = toFinite(end);
+      }
+      number = toNumber(number);
+      return baseInRange(number, start, end);
+    }
+
+    /**
+     * Produces a random number between the inclusive `lower` and `upper` bounds.
+     * If only one argument is provided a number between `0` and the given number
+     * is returned. If `floating` is `true`, or either `lower` or `upper` are
+     * floats, a floating-point number is returned instead of an integer.
+     *
+     * **Note:** JavaScript follows the IEEE-754 standard for resolving
+     * floating-point values which can produce unexpected results.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.7.0
+     * @category Number
+     * @param {number} [lower=0] The lower bound.
+     * @param {number} [upper=1] The upper bound.
+     * @param {boolean} [floating] Specify returning a floating-point number.
+     * @returns {number} Returns the random number.
+     * @example
+     *
+     * _.random(0, 5);
+     * // => an integer between 0 and 5
+     *
+     * _.random(5);
+     * // => also an integer between 0 and 5
+     *
+     * _.random(5, true);
+     * // => a floating-point number between 0 and 5
+     *
+     * _.random(1.2, 5.2);
+     * // => a floating-point number between 1.2 and 5.2
+     */
+    function random(lower, upper, floating) {
+      if (floating && typeof floating != 'boolean' && isIterateeCall(lower, upper, floating)) {
+        upper = floating = undefined;
+      }
+      if (floating === undefined) {
+        if (typeof upper == 'boolean') {
+          floating = upper;
+          upper = undefined;
+        }
+        else if (typeof lower == 'boolean') {
+          floating = lower;
+          lower = undefined;
+        }
+      }
+      if (lower === undefined && upper === undefined) {
+        lower = 0;
+        upper = 1;
+      }
+      else {
+        lower = toFinite(lower);
+        if (upper === undefined) {
+          upper = lower;
+          lower = 0;
+        } else {
+          upper = toFinite(upper);
+        }
+      }
+      if (lower > upper) {
+        var temp = lower;
+        lower = upper;
+        upper = temp;
+      }
+      if (floating || lower % 1 || upper % 1) {
+        var rand = nativeRandom();
+        return nativeMin(lower + (rand * (upper - lower + freeParseFloat('1e-' + ((rand + '').length - 1)))), upper);
+      }
+      return baseRandom(lower, upper);
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the camel cased string.
+     * @example
+     *
+     * _.camelCase('Foo Bar');
+     * // => 'fooBar'
+     *
+     * _.camelCase('--foo-bar--');
+     * // => 'fooBar'
+     *
+     * _.camelCase('__FOO_BAR__');
+     * // => 'fooBar'
+     */
+    var camelCase = createCompounder(function(result, word, index) {
+      word = word.toLowerCase();
+      return result + (index ? capitalize(word) : word);
+    });
+
+    /**
+     * Converts the first character of `string` to upper case and the remaining
+     * to lower case.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to capitalize.
+     * @returns {string} Returns the capitalized string.
+     * @example
+     *
+     * _.capitalize('FRED');
+     * // => 'Fred'
+     */
+    function capitalize(string) {
+      return upperFirst(toString(string).toLowerCase());
+    }
+
+    /**
+     * Deburrs `string` by converting
+     * [Latin-1 Supplement](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table)
+     * and [Latin Extended-A](https://en.wikipedia.org/wiki/Latin_Extended-A)
+     * letters to basic Latin letters and removing
+     * [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to deburr.
+     * @returns {string} Returns the deburred string.
+     * @example
+     *
+     * _.deburr('déjà vu');
+     * // => 'deja vu'
+     */
+    function deburr(string) {
+      string = toString(string);
+      return string && string.replace(reLatin, deburrLetter).replace(reComboMark, '');
+    }
+
+    /**
+     * Checks if `string` ends with the given target string.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to inspect.
+     * @param {string} [target] The string to search for.
+     * @param {number} [position=string.length] The position to search up to.
+     * @returns {boolean} Returns `true` if `string` ends with `target`,
+     *  else `false`.
+     * @example
+     *
+     * _.endsWith('abc', 'c');
+     * // => true
+     *
+     * _.endsWith('abc', 'b');
+     * // => false
+     *
+     * _.endsWith('abc', 'b', 2);
+     * // => true
+     */
+    function endsWith(string, target, position) {
+      string = toString(string);
+      target = baseToString(target);
+
+      var length = string.length;
+      position = position === undefined
+        ? length
+        : baseClamp(toInteger(position), 0, length);
+
+      var end = position;
+      position -= target.length;
+      return position >= 0 && string.slice(position, end) == target;
+    }
+
+    /**
+     * Converts the characters "&", "<", ">", '"', and "'" in `string` to their
+     * corresponding HTML entities.
+     *
+     * **Note:** No other characters are escaped. To escape additional
+     * characters use a third-party library like [_he_](https://mths.be/he).
+     *
+     * Though the ">" character is escaped for symmetry, characters like
+     * ">" and "/" don't need escaping in HTML and have no special meaning
+     * unless they're part of a tag or unquoted attribute value. See
+     * [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands)
+     * (under "semi-related fun fact") for more details.
+     *
+     * When working with HTML you should always
+     * [quote attribute values](http://wonko.com/post/html-escaping) to reduce
+     * XSS vectors.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to escape.
+     * @returns {string} Returns the escaped string.
+     * @example
+     *
+     * _.escape('fred, barney, & pebbles');
+     * // => 'fred, barney, &amp; pebbles'
+     */
+    function escape(string) {
+      string = toString(string);
+      return (string && reHasUnescapedHtml.test(string))
+        ? string.replace(reUnescapedHtml, escapeHtmlChar)
+        : string;
+    }
+
+    /**
+     * Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+",
+     * "?", "(", ")", "[", "]", "{", "}", and "|" in `string`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to escape.
+     * @returns {string} Returns the escaped string.
+     * @example
+     *
+     * _.escapeRegExp('[lodash](https://lodash.com/)');
+     * // => '\[lodash\]\(https://lodash\.com/\)'
+     */
+    function escapeRegExp(string) {
+      string = toString(string);
+      return (string && reHasRegExpChar.test(string))
+        ? string.replace(reRegExpChar, '\\$&')
+        : string;
+    }
+
+    /**
+     * Converts `string` to
+     * [kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the kebab cased string.
+     * @example
+     *
+     * _.kebabCase('Foo Bar');
+     * // => 'foo-bar'
+     *
+     * _.kebabCase('fooBar');
+     * // => 'foo-bar'
+     *
+     * _.kebabCase('__FOO_BAR__');
+     * // => 'foo-bar'
+     */
+    var kebabCase = createCompounder(function(result, word, index) {
+      return result + (index ? '-' : '') + word.toLowerCase();
+    });
+
+    /**
+     * Converts `string`, as space separated words, to lower case.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the lower cased string.
+     * @example
+     *
+     * _.lowerCase('--Foo-Bar--');
+     * // => 'foo bar'
+     *
+     * _.lowerCase('fooBar');
+     * // => 'foo bar'
+     *
+     * _.lowerCase('__FOO_BAR__');
+     * // => 'foo bar'
+     */
+    var lowerCase = createCompounder(function(result, word, index) {
+      return result + (index ? ' ' : '') + word.toLowerCase();
+    });
+
+    /**
+     * Converts the first character of `string` to lower case.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the converted string.
+     * @example
+     *
+     * _.lowerFirst('Fred');
+     * // => 'fred'
+     *
+     * _.lowerFirst('FRED');
+     * // => 'fRED'
+     */
+    var lowerFirst = createCaseFirst('toLowerCase');
+
+    /**
+     * Pads `string` on the left and right sides if it's shorter than `length`.
+     * Padding characters are truncated if they can't be evenly divided by `length`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to pad.
+     * @param {number} [length=0] The padding length.
+     * @param {string} [chars=' '] The string used as padding.
+     * @returns {string} Returns the padded string.
+     * @example
+     *
+     * _.pad('abc', 8);
+     * // => '  abc   '
+     *
+     * _.pad('abc', 8, '_-');
+     * // => '_-abc_-_'
+     *
+     * _.pad('abc', 3);
+     * // => 'abc'
+     */
+    function pad(string, length, chars) {
+      string = toString(string);
+      length = toInteger(length);
+
+      var strLength = length ? stringSize(string) : 0;
+      if (!length || strLength >= length) {
+        return string;
+      }
+      var mid = (length - strLength) / 2;
+      return (
+        createPadding(nativeFloor(mid), chars) +
+        string +
+        createPadding(nativeCeil(mid), chars)
+      );
+    }
+
+    /**
+     * Pads `string` on the right side if it's shorter than `length`. Padding
+     * characters are truncated if they exceed `length`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to pad.
+     * @param {number} [length=0] The padding length.
+     * @param {string} [chars=' '] The string used as padding.
+     * @returns {string} Returns the padded string.
+     * @example
+     *
+     * _.padEnd('abc', 6);
+     * // => 'abc   '
+     *
+     * _.padEnd('abc', 6, '_-');
+     * // => 'abc_-_'
+     *
+     * _.padEnd('abc', 3);
+     * // => 'abc'
+     */
+    function padEnd(string, length, chars) {
+      string = toString(string);
+      length = toInteger(length);
+
+      var strLength = length ? stringSize(string) : 0;
+      return (length && strLength < length)
+        ? (string + createPadding(length - strLength, chars))
+        : string;
+    }
+
+    /**
+     * Pads `string` on the left side if it's shorter than `length`. Padding
+     * characters are truncated if they exceed `length`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to pad.
+     * @param {number} [length=0] The padding length.
+     * @param {string} [chars=' '] The string used as padding.
+     * @returns {string} Returns the padded string.
+     * @example
+     *
+     * _.padStart('abc', 6);
+     * // => '   abc'
+     *
+     * _.padStart('abc', 6, '_-');
+     * // => '_-_abc'
+     *
+     * _.padStart('abc', 3);
+     * // => 'abc'
+     */
+    function padStart(string, length, chars) {
+      string = toString(string);
+      length = toInteger(length);
+
+      var strLength = length ? stringSize(string) : 0;
+      return (length && strLength < length)
+        ? (createPadding(length - strLength, chars) + string)
+        : string;
+    }
+
+    /**
+     * Converts `string` to an integer of the specified radix. If `radix` is
+     * `undefined` or `0`, a `radix` of `10` is used unless `value` is a
+     * hexadecimal, in which case a `radix` of `16` is used.
+     *
+     * **Note:** This method aligns with the
+     * [ES5 implementation](https://es5.github.io/#x15.1.2.2) of `parseInt`.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.1.0
+     * @category String
+     * @param {string} string The string to convert.
+     * @param {number} [radix=10] The radix to interpret `value` by.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {number} Returns the converted integer.
+     * @example
+     *
+     * _.parseInt('08');
+     * // => 8
+     *
+     * _.map(['6', '08', '10'], _.parseInt);
+     * // => [6, 8, 10]
+     */
+    function parseInt(string, radix, guard) {
+      if (guard || radix == null) {
+        radix = 0;
+      } else if (radix) {
+        radix = +radix;
+      }
+      return nativeParseInt(toString(string).replace(reTrimStart, ''), radix || 0);
+    }
+
+    /**
+     * Repeats the given string `n` times.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to repeat.
+     * @param {number} [n=1] The number of times to repeat the string.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {string} Returns the repeated string.
+     * @example
+     *
+     * _.repeat('*', 3);
+     * // => '***'
+     *
+     * _.repeat('abc', 2);
+     * // => 'abcabc'
+     *
+     * _.repeat('abc', 0);
+     * // => ''
+     */
+    function repeat(string, n, guard) {
+      if ((guard ? isIterateeCall(string, n, guard) : n === undefined)) {
+        n = 1;
+      } else {
+        n = toInteger(n);
+      }
+      return baseRepeat(toString(string), n);
+    }
+
+    /**
+     * Replaces matches for `pattern` in `string` with `replacement`.
+     *
+     * **Note:** This method is based on
+     * [`String#replace`](https://mdn.io/String/replace).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to modify.
+     * @param {RegExp|string} pattern The pattern to replace.
+     * @param {Function|string} replacement The match replacement.
+     * @returns {string} Returns the modified string.
+     * @example
+     *
+     * _.replace('Hi Fred', 'Fred', 'Barney');
+     * // => 'Hi Barney'
+     */
+    function replace() {
+      var args = arguments,
+          string = toString(args[0]);
+
+      return args.length < 3 ? string : string.replace(args[1], args[2]);
+    }
+
+    /**
+     * Converts `string` to
+     * [snake case](https://en.wikipedia.org/wiki/Snake_case).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the snake cased string.
+     * @example
+     *
+     * _.snakeCase('Foo Bar');
+     * // => 'foo_bar'
+     *
+     * _.snakeCase('fooBar');
+     * // => 'foo_bar'
+     *
+     * _.snakeCase('--FOO-BAR--');
+     * // => 'foo_bar'
+     */
+    var snakeCase = createCompounder(function(result, word, index) {
+      return result + (index ? '_' : '') + word.toLowerCase();
+    });
+
+    /**
+     * Splits `string` by `separator`.
+     *
+     * **Note:** This method is based on
+     * [`String#split`](https://mdn.io/String/split).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to split.
+     * @param {RegExp|string} separator The separator pattern to split by.
+     * @param {number} [limit] The length to truncate results to.
+     * @returns {Array} Returns the string segments.
+     * @example
+     *
+     * _.split('a-b-c', '-', 2);
+     * // => ['a', 'b']
+     */
+    function split(string, separator, limit) {
+      if (limit && typeof limit != 'number' && isIterateeCall(string, separator, limit)) {
+        separator = limit = undefined;
+      }
+      limit = limit === undefined ? MAX_ARRAY_LENGTH : limit >>> 0;
+      if (!limit) {
+        return [];
+      }
+      string = toString(string);
+      if (string && (
+            typeof separator == 'string' ||
+            (separator != null && !isRegExp(separator))
+          )) {
+        separator = baseToString(separator);
+        if (!separator && hasUnicode(string)) {
+          return castSlice(stringToArray(string), 0, limit);
+        }
+      }
+      return string.split(separator, limit);
+    }
+
+    /**
+     * Converts `string` to
+     * [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.1.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the start cased string.
+     * @example
+     *
+     * _.startCase('--foo-bar--');
+     * // => 'Foo Bar'
+     *
+     * _.startCase('fooBar');
+     * // => 'Foo Bar'
+     *
+     * _.startCase('__FOO_BAR__');
+     * // => 'FOO BAR'
+     */
+    var startCase = createCompounder(function(result, word, index) {
+      return result + (index ? ' ' : '') + upperFirst(word);
+    });
+
+    /**
+     * Checks if `string` starts with the given target string.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to inspect.
+     * @param {string} [target] The string to search for.
+     * @param {number} [position=0] The position to search from.
+     * @returns {boolean} Returns `true` if `string` starts with `target`,
+     *  else `false`.
+     * @example
+     *
+     * _.startsWith('abc', 'a');
+     * // => true
+     *
+     * _.startsWith('abc', 'b');
+     * // => false
+     *
+     * _.startsWith('abc', 'b', 1);
+     * // => true
+     */
+    function startsWith(string, target, position) {
+      string = toString(string);
+      position = position == null
+        ? 0
+        : baseClamp(toInteger(position), 0, string.length);
+
+      target = baseToString(target);
+      return string.slice(position, position + target.length) == target;
+    }
+
+    /**
+     * Creates a compiled template function that can interpolate data properties
+     * in "interpolate" delimiters, HTML-escape interpolated data properties in
+     * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data
+     * properties may be accessed as free variables in the template. If a setting
+     * object is given, it takes precedence over `_.templateSettings` values.
+     *
+     * **Note:** In the development build `_.template` utilizes
+     * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl)
+     * for easier debugging.
+     *
+     * For more information on precompiling templates see
+     * [lodash's custom builds documentation](https://lodash.com/custom-builds).
+     *
+     * For more information on Chrome extension sandboxes see
+     * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval).
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The template string.
+     * @param {Object} [options={}] The options object.
+     * @param {RegExp} [options.escape=_.templateSettings.escape]
+     *  The HTML "escape" delimiter.
+     * @param {RegExp} [options.evaluate=_.templateSettings.evaluate]
+     *  The "evaluate" delimiter.
+     * @param {Object} [options.imports=_.templateSettings.imports]
+     *  An object to import into the template as free variables.
+     * @param {RegExp} [options.interpolate=_.templateSettings.interpolate]
+     *  The "interpolate" delimiter.
+     * @param {string} [options.sourceURL='lodash.templateSources[n]']
+     *  The sourceURL of the compiled template.
+     * @param {string} [options.variable='obj']
+     *  The data object variable name.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Function} Returns the compiled template function.
+     * @example
+     *
+     * // Use the "interpolate" delimiter to create a compiled template.
+     * var compiled = _.template('hello <%= user %>!');
+     * compiled({ 'user': 'fred' });
+     * // => 'hello fred!'
+     *
+     * // Use the HTML "escape" delimiter to escape data property values.
+     * var compiled = _.template('<b><%- value %></b>');
+     * compiled({ 'value': '<script>' });
+     * // => '<b>&lt;script&gt;</b>'
+     *
+     * // Use the "evaluate" delimiter to execute JavaScript and generate HTML.
+     * var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>');
+     * compiled({ 'users': ['fred', 'barney'] });
+     * // => '<li>fred</li><li>barney</li>'
+     *
+     * // Use the internal `print` function in "evaluate" delimiters.
+     * var compiled = _.template('<% print("hello " + user); %>!');
+     * compiled({ 'user': 'barney' });
+     * // => 'hello barney!'
+     *
+     * // Use the ES template literal delimiter as an "interpolate" delimiter.
+     * // Disable support by replacing the "interpolate" delimiter.
+     * var compiled = _.template('hello ${ user }!');
+     * compiled({ 'user': 'pebbles' });
+     * // => 'hello pebbles!'
+     *
+     * // Use backslashes to treat delimiters as plain text.
+     * var compiled = _.template('<%= "\\<%- value %\\>" %>');
+     * compiled({ 'value': 'ignored' });
+     * // => '<%- value %>'
+     *
+     * // Use the `imports` option to import `jQuery` as `jq`.
+     * var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>';
+     * var compiled = _.template(text, { 'imports': { 'jq': jQuery } });
+     * compiled({ 'users': ['fred', 'barney'] });
+     * // => '<li>fred</li><li>barney</li>'
+     *
+     * // Use the `sourceURL` option to specify a custom sourceURL for the template.
+     * var compiled = _.template('hello <%= user %>!', { 'sourceURL': '/basic/greeting.jst' });
+     * compiled(data);
+     * // => Find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector.
+     *
+     * // Use the `variable` option to ensure a with-statement isn't used in the compiled template.
+     * var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' });
+     * compiled.source;
+     * // => function(data) {
+     * //   var __t, __p = '';
+     * //   __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!';
+     * //   return __p;
+     * // }
+     *
+     * // Use custom template delimiters.
+     * _.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
+     * var compiled = _.template('hello {{ user }}!');
+     * compiled({ 'user': 'mustache' });
+     * // => 'hello mustache!'
+     *
+     * // Use the `source` property to inline compiled templates for meaningful
+     * // line numbers in error messages and stack traces.
+     * fs.writeFileSync(path.join(process.cwd(), 'jst.js'), '\
+     *   var JST = {\
+     *     "main": ' + _.template(mainText).source + '\
+     *   };\
+     * ');
+     */
+    function template(string, options, guard) {
+      // Based on John Resig's `tmpl` implementation
+      // (http://ejohn.org/blog/javascript-micro-templating/)
+      // and Laura Doktorova's doT.js (https://github.com/olado/doT).
+      var settings = lodash.templateSettings;
+
+      if (guard && isIterateeCall(string, options, guard)) {
+        options = undefined;
+      }
+      string = toString(string);
+      options = assignInWith({}, options, settings, customDefaultsAssignIn);
+
+      var imports = assignInWith({}, options.imports, settings.imports, customDefaultsAssignIn),
+          importsKeys = keys(imports),
+          importsValues = baseValues(imports, importsKeys);
+
+      var isEscaping,
+          isEvaluating,
+          index = 0,
+          interpolate = options.interpolate || reNoMatch,
+          source = "__p += '";
+
+      // Compile the regexp to match each delimiter.
+      var reDelimiters = RegExp(
+        (options.escape || reNoMatch).source + '|' +
+        interpolate.source + '|' +
+        (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' +
+        (options.evaluate || reNoMatch).source + '|$'
+      , 'g');
+
+      // Use a sourceURL for easier debugging.
+      var sourceURL = '//# sourceURL=' +
+        ('sourceURL' in options
+          ? options.sourceURL
+          : ('lodash.templateSources[' + (++templateCounter) + ']')
+        ) + '\n';
+
+      string.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
+        interpolateValue || (interpolateValue = esTemplateValue);
+
+        // Escape characters that can't be included in string literals.
+        source += string.slice(index, offset).replace(reUnescapedString, escapeStringChar);
+
+        // Replace delimiters with snippets.
+        if (escapeValue) {
+          isEscaping = true;
+          source += "' +\n__e(" + escapeValue + ") +\n'";
+        }
+        if (evaluateValue) {
+          isEvaluating = true;
+          source += "';\n" + evaluateValue + ";\n__p += '";
+        }
+        if (interpolateValue) {
+          source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
+        }
+        index = offset + match.length;
+
+        // The JS engine embedded in Adobe products needs `match` returned in
+        // order to produce the correct `offset` value.
+        return match;
+      });
+
+      source += "';\n";
+
+      // If `variable` is not specified wrap a with-statement around the generated
+      // code to add the data object to the top of the scope chain.
+      var variable = options.variable;
+      if (!variable) {
+        source = 'with (obj) {\n' + source + '\n}\n';
+      }
+      // Cleanup code by stripping empty strings.
+      source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)
+        .replace(reEmptyStringMiddle, '$1')
+        .replace(reEmptyStringTrailing, '$1;');
+
+      // Frame code as the function body.
+      source = 'function(' + (variable || 'obj') + ') {\n' +
+        (variable
+          ? ''
+          : 'obj || (obj = {});\n'
+        ) +
+        "var __t, __p = ''" +
+        (isEscaping
+           ? ', __e = _.escape'
+           : ''
+        ) +
+        (isEvaluating
+          ? ', __j = Array.prototype.join;\n' +
+            "function print() { __p += __j.call(arguments, '') }\n"
+          : ';\n'
+        ) +
+        source +
+        'return __p\n}';
+
+      var result = attempt(function() {
+        return Function(importsKeys, sourceURL + 'return ' + source)
+          .apply(undefined, importsValues);
+      });
+
+      // Provide the compiled function's source by its `toString` method or
+      // the `source` property as a convenience for inlining compiled templates.
+      result.source = source;
+      if (isError(result)) {
+        throw result;
+      }
+      return result;
+    }
+
+    /**
+     * Converts `string`, as a whole, to lower case just like
+     * [String#toLowerCase](https://mdn.io/toLowerCase).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the lower cased string.
+     * @example
+     *
+     * _.toLower('--Foo-Bar--');
+     * // => '--foo-bar--'
+     *
+     * _.toLower('fooBar');
+     * // => 'foobar'
+     *
+     * _.toLower('__FOO_BAR__');
+     * // => '__foo_bar__'
+     */
+    function toLower(value) {
+      return toString(value).toLowerCase();
+    }
+
+    /**
+     * Converts `string`, as a whole, to upper case just like
+     * [String#toUpperCase](https://mdn.io/toUpperCase).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the upper cased string.
+     * @example
+     *
+     * _.toUpper('--foo-bar--');
+     * // => '--FOO-BAR--'
+     *
+     * _.toUpper('fooBar');
+     * // => 'FOOBAR'
+     *
+     * _.toUpper('__foo_bar__');
+     * // => '__FOO_BAR__'
+     */
+    function toUpper(value) {
+      return toString(value).toUpperCase();
+    }
+
+    /**
+     * Removes leading and trailing whitespace or specified characters from `string`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to trim.
+     * @param {string} [chars=whitespace] The characters to trim.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {string} Returns the trimmed string.
+     * @example
+     *
+     * _.trim('  abc  ');
+     * // => 'abc'
+     *
+     * _.trim('-_-abc-_-', '_-');
+     * // => 'abc'
+     *
+     * _.map(['  foo  ', '  bar  '], _.trim);
+     * // => ['foo', 'bar']
+     */
+    function trim(string, chars, guard) {
+      string = toString(string);
+      if (string && (guard || chars === undefined)) {
+        return string.replace(reTrim, '');
+      }
+      if (!string || !(chars = baseToString(chars))) {
+        return string;
+      }
+      var strSymbols = stringToArray(string),
+          chrSymbols = stringToArray(chars),
+          start = charsStartIndex(strSymbols, chrSymbols),
+          end = charsEndIndex(strSymbols, chrSymbols) + 1;
+
+      return castSlice(strSymbols, start, end).join('');
+    }
+
+    /**
+     * Removes trailing whitespace or specified characters from `string`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to trim.
+     * @param {string} [chars=whitespace] The characters to trim.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {string} Returns the trimmed string.
+     * @example
+     *
+     * _.trimEnd('  abc  ');
+     * // => '  abc'
+     *
+     * _.trimEnd('-_-abc-_-', '_-');
+     * // => '-_-abc'
+     */
+    function trimEnd(string, chars, guard) {
+      string = toString(string);
+      if (string && (guard || chars === undefined)) {
+        return string.replace(reTrimEnd, '');
+      }
+      if (!string || !(chars = baseToString(chars))) {
+        return string;
+      }
+      var strSymbols = stringToArray(string),
+          end = charsEndIndex(strSymbols, stringToArray(chars)) + 1;
+
+      return castSlice(strSymbols, 0, end).join('');
+    }
+
+    /**
+     * Removes leading whitespace or specified characters from `string`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to trim.
+     * @param {string} [chars=whitespace] The characters to trim.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {string} Returns the trimmed string.
+     * @example
+     *
+     * _.trimStart('  abc  ');
+     * // => 'abc  '
+     *
+     * _.trimStart('-_-abc-_-', '_-');
+     * // => 'abc-_-'
+     */
+    function trimStart(string, chars, guard) {
+      string = toString(string);
+      if (string && (guard || chars === undefined)) {
+        return string.replace(reTrimStart, '');
+      }
+      if (!string || !(chars = baseToString(chars))) {
+        return string;
+      }
+      var strSymbols = stringToArray(string),
+          start = charsStartIndex(strSymbols, stringToArray(chars));
+
+      return castSlice(strSymbols, start).join('');
+    }
+
+    /**
+     * Truncates `string` if it's longer than the given maximum string length.
+     * The last characters of the truncated string are replaced with the omission
+     * string which defaults to "...".
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to truncate.
+     * @param {Object} [options={}] The options object.
+     * @param {number} [options.length=30] The maximum string length.
+     * @param {string} [options.omission='...'] The string to indicate text is omitted.
+     * @param {RegExp|string} [options.separator] The separator pattern to truncate to.
+     * @returns {string} Returns the truncated string.
+     * @example
+     *
+     * _.truncate('hi-diddly-ho there, neighborino');
+     * // => 'hi-diddly-ho there, neighbo...'
+     *
+     * _.truncate('hi-diddly-ho there, neighborino', {
+     *   'length': 24,
+     *   'separator': ' '
+     * });
+     * // => 'hi-diddly-ho there,...'
+     *
+     * _.truncate('hi-diddly-ho there, neighborino', {
+     *   'length': 24,
+     *   'separator': /,? +/
+     * });
+     * // => 'hi-diddly-ho there...'
+     *
+     * _.truncate('hi-diddly-ho there, neighborino', {
+     *   'omission': ' [...]'
+     * });
+     * // => 'hi-diddly-ho there, neig [...]'
+     */
+    function truncate(string, options) {
+      var length = DEFAULT_TRUNC_LENGTH,
+          omission = DEFAULT_TRUNC_OMISSION;
+
+      if (isObject(options)) {
+        var separator = 'separator' in options ? options.separator : separator;
+        length = 'length' in options ? toInteger(options.length) : length;
+        omission = 'omission' in options ? baseToString(options.omission) : omission;
+      }
+      string = toString(string);
+
+      var strLength = string.length;
+      if (hasUnicode(string)) {
+        var strSymbols = stringToArray(string);
+        strLength = strSymbols.length;
+      }
+      if (length >= strLength) {
+        return string;
+      }
+      var end = length - stringSize(omission);
+      if (end < 1) {
+        return omission;
+      }
+      var result = strSymbols
+        ? castSlice(strSymbols, 0, end).join('')
+        : string.slice(0, end);
+
+      if (separator === undefined) {
+        return result + omission;
+      }
+      if (strSymbols) {
+        end += (result.length - end);
+      }
+      if (isRegExp(separator)) {
+        if (string.slice(end).search(separator)) {
+          var match,
+              substring = result;
+
+          if (!separator.global) {
+            separator = RegExp(separator.source, toString(reFlags.exec(separator)) + 'g');
+          }
+          separator.lastIndex = 0;
+          while ((match = separator.exec(substring))) {
+            var newEnd = match.index;
+          }
+          result = result.slice(0, newEnd === undefined ? end : newEnd);
+        }
+      } else if (string.indexOf(baseToString(separator), end) != end) {
+        var index = result.lastIndexOf(separator);
+        if (index > -1) {
+          result = result.slice(0, index);
+        }
+      }
+      return result + omission;
+    }
+
+    /**
+     * The inverse of `_.escape`; this method converts the HTML entities
+     * `&amp;`, `&lt;`, `&gt;`, `&quot;`, and `&#39;` in `string` to
+     * their corresponding characters.
+     *
+     * **Note:** No other HTML entities are unescaped. To unescape additional
+     * HTML entities use a third-party library like [_he_](https://mths.be/he).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.6.0
+     * @category String
+     * @param {string} [string=''] The string to unescape.
+     * @returns {string} Returns the unescaped string.
+     * @example
+     *
+     * _.unescape('fred, barney, &amp; pebbles');
+     * // => 'fred, barney, & pebbles'
+     */
+    function unescape(string) {
+      string = toString(string);
+      return (string && reHasEscapedHtml.test(string))
+        ? string.replace(reEscapedHtml, unescapeHtmlChar)
+        : string;
+    }
+
+    /**
+     * Converts `string`, as space separated words, to upper case.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the upper cased string.
+     * @example
+     *
+     * _.upperCase('--foo-bar');
+     * // => 'FOO BAR'
+     *
+     * _.upperCase('fooBar');
+     * // => 'FOO BAR'
+     *
+     * _.upperCase('__foo_bar__');
+     * // => 'FOO BAR'
+     */
+    var upperCase = createCompounder(function(result, word, index) {
+      return result + (index ? ' ' : '') + word.toUpperCase();
+    });
+
+    /**
+     * Converts the first character of `string` to upper case.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the converted string.
+     * @example
+     *
+     * _.upperFirst('fred');
+     * // => 'Fred'
+     *
+     * _.upperFirst('FRED');
+     * // => 'FRED'
+     */
+    var upperFirst = createCaseFirst('toUpperCase');
+
+    /**
+     * Splits `string` into an array of its words.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to inspect.
+     * @param {RegExp|string} [pattern] The pattern to match words.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the words of `string`.
+     * @example
+     *
+     * _.words('fred, barney, & pebbles');
+     * // => ['fred', 'barney', 'pebbles']
+     *
+     * _.words('fred, barney, & pebbles', /[^, ]+/g);
+     * // => ['fred', 'barney', '&', 'pebbles']
+     */
+    function words(string, pattern, guard) {
+      string = toString(string);
+      pattern = guard ? undefined : pattern;
+
+      if (pattern === undefined) {
+        return hasUnicodeWord(string) ? unicodeWords(string) : asciiWords(string);
+      }
+      return string.match(pattern) || [];
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Attempts to invoke `func`, returning either the result or the caught error
+     * object. Any additional arguments are provided to `func` when it's invoked.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Util
+     * @param {Function} func The function to attempt.
+     * @param {...*} [args] The arguments to invoke `func` with.
+     * @returns {*} Returns the `func` result or error object.
+     * @example
+     *
+     * // Avoid throwing errors for invalid selectors.
+     * var elements = _.attempt(function(selector) {
+     *   return document.querySelectorAll(selector);
+     * }, '>_>');
+     *
+     * if (_.isError(elements)) {
+     *   elements = [];
+     * }
+     */
+    var attempt = baseRest(function(func, args) {
+      try {
+        return apply(func, undefined, args);
+      } catch (e) {
+        return isError(e) ? e : new Error(e);
+      }
+    });
+
+    /**
+     * Binds methods of an object to the object itself, overwriting the existing
+     * method.
+     *
+     * **Note:** This method doesn't set the "length" property of bound functions.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @param {Object} object The object to bind and assign the bound methods to.
+     * @param {...(string|string[])} methodNames The object method names to bind.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var view = {
+     *   'label': 'docs',
+     *   'click': function() {
+     *     console.log('clicked ' + this.label);
+     *   }
+     * };
+     *
+     * _.bindAll(view, ['click']);
+     * jQuery(element).on('click', view.click);
+     * // => Logs 'clicked docs' when clicked.
+     */
+    var bindAll = flatRest(function(object, methodNames) {
+      arrayEach(methodNames, function(key) {
+        key = toKey(key);
+        baseAssignValue(object, key, bind(object[key], object));
+      });
+      return object;
+    });
+
+    /**
+     * Creates a function that iterates over `pairs` and invokes the corresponding
+     * function of the first predicate to return truthy. The predicate-function
+     * pairs are invoked with the `this` binding and arguments of the created
+     * function.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {Array} pairs The predicate-function pairs.
+     * @returns {Function} Returns the new composite function.
+     * @example
+     *
+     * var func = _.cond([
+     *   [_.matches({ 'a': 1 }),           _.constant('matches A')],
+     *   [_.conforms({ 'b': _.isNumber }), _.constant('matches B')],
+     *   [_.stubTrue,                      _.constant('no match')]
+     * ]);
+     *
+     * func({ 'a': 1, 'b': 2 });
+     * // => 'matches A'
+     *
+     * func({ 'a': 0, 'b': 1 });
+     * // => 'matches B'
+     *
+     * func({ 'a': '1', 'b': '2' });
+     * // => 'no match'
+     */
+    function cond(pairs) {
+      var length = pairs == null ? 0 : pairs.length,
+          toIteratee = getIteratee();
+
+      pairs = !length ? [] : arrayMap(pairs, function(pair) {
+        if (typeof pair[1] != 'function') {
+          throw new TypeError(FUNC_ERROR_TEXT);
+        }
+        return [toIteratee(pair[0]), pair[1]];
+      });
+
+      return baseRest(function(args) {
+        var index = -1;
+        while (++index < length) {
+          var pair = pairs[index];
+          if (apply(pair[0], this, args)) {
+            return apply(pair[1], this, args);
+          }
+        }
+      });
+    }
+
+    /**
+     * Creates a function that invokes the predicate properties of `source` with
+     * the corresponding property values of a given object, returning `true` if
+     * all predicates return truthy, else `false`.
+     *
+     * **Note:** The created function is equivalent to `_.conformsTo` with
+     * `source` partially applied.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {Object} source The object of property predicates to conform to.
+     * @returns {Function} Returns the new spec function.
+     * @example
+     *
+     * var objects = [
+     *   { 'a': 2, 'b': 1 },
+     *   { 'a': 1, 'b': 2 }
+     * ];
+     *
+     * _.filter(objects, _.conforms({ 'b': function(n) { return n > 1; } }));
+     * // => [{ 'a': 1, 'b': 2 }]
+     */
+    function conforms(source) {
+      return baseConforms(baseClone(source, CLONE_DEEP_FLAG));
+    }
+
+    /**
+     * Creates a function that returns `value`.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.4.0
+     * @category Util
+     * @param {*} value The value to return from the new function.
+     * @returns {Function} Returns the new constant function.
+     * @example
+     *
+     * var objects = _.times(2, _.constant({ 'a': 1 }));
+     *
+     * console.log(objects);
+     * // => [{ 'a': 1 }, { 'a': 1 }]
+     *
+     * console.log(objects[0] === objects[1]);
+     * // => true
+     */
+    function constant(value) {
+      return function() {
+        return value;
+      };
+    }
+
+    /**
+     * Checks `value` to determine whether a default value should be returned in
+     * its place. The `defaultValue` is returned if `value` is `NaN`, `null`,
+     * or `undefined`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.14.0
+     * @category Util
+     * @param {*} value The value to check.
+     * @param {*} defaultValue The default value.
+     * @returns {*} Returns the resolved value.
+     * @example
+     *
+     * _.defaultTo(1, 10);
+     * // => 1
+     *
+     * _.defaultTo(undefined, 10);
+     * // => 10
+     */
+    function defaultTo(value, defaultValue) {
+      return (value == null || value !== value) ? defaultValue : value;
+    }
+
+    /**
+     * Creates a function that returns the result of invoking the given functions
+     * with the `this` binding of the created function, where each successive
+     * invocation is supplied the return value of the previous.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Util
+     * @param {...(Function|Function[])} [funcs] The functions to invoke.
+     * @returns {Function} Returns the new composite function.
+     * @see _.flowRight
+     * @example
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var addSquare = _.flow([_.add, square]);
+     * addSquare(1, 2);
+     * // => 9
+     */
+    var flow = createFlow();
+
+    /**
+     * This method is like `_.flow` except that it creates a function that
+     * invokes the given functions from right to left.
+     *
+     * @static
+     * @since 3.0.0
+     * @memberOf _
+     * @category Util
+     * @param {...(Function|Function[])} [funcs] The functions to invoke.
+     * @returns {Function} Returns the new composite function.
+     * @see _.flow
+     * @example
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var addSquare = _.flowRight([square, _.add]);
+     * addSquare(1, 2);
+     * // => 9
+     */
+    var flowRight = createFlow(true);
+
+    /**
+     * This method returns the first argument it receives.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @param {*} value Any value.
+     * @returns {*} Returns `value`.
+     * @example
+     *
+     * var object = { 'a': 1 };
+     *
+     * console.log(_.identity(object) === object);
+     * // => true
+     */
+    function identity(value) {
+      return value;
+    }
+
+    /**
+     * Creates a function that invokes `func` with the arguments of the created
+     * function. If `func` is a property name, the created function returns the
+     * property value for a given element. If `func` is an array or object, the
+     * created function returns `true` for elements that contain the equivalent
+     * source properties, otherwise it returns `false`.
+     *
+     * @static
+     * @since 4.0.0
+     * @memberOf _
+     * @category Util
+     * @param {*} [func=_.identity] The value to convert to a callback.
+     * @returns {Function} Returns the callback.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': true },
+     *   { 'user': 'fred',   'age': 40, 'active': false }
+     * ];
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.filter(users, _.iteratee({ 'user': 'barney', 'active': true }));
+     * // => [{ 'user': 'barney', 'age': 36, 'active': true }]
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.filter(users, _.iteratee(['user', 'fred']));
+     * // => [{ 'user': 'fred', 'age': 40 }]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.map(users, _.iteratee('user'));
+     * // => ['barney', 'fred']
+     *
+     * // Create custom iteratee shorthands.
+     * _.iteratee = _.wrap(_.iteratee, function(iteratee, func) {
+     *   return !_.isRegExp(func) ? iteratee(func) : function(string) {
+     *     return func.test(string);
+     *   };
+     * });
+     *
+     * _.filter(['abc', 'def'], /ef/);
+     * // => ['def']
+     */
+    function iteratee(func) {
+      return baseIteratee(typeof func == 'function' ? func : baseClone(func, CLONE_DEEP_FLAG));
+    }
+
+    /**
+     * Creates a function that performs a partial deep comparison between a given
+     * object and `source`, returning `true` if the given object has equivalent
+     * property values, else `false`.
+     *
+     * **Note:** The created function is equivalent to `_.isMatch` with `source`
+     * partially applied.
+     *
+     * Partial comparisons will match empty array and empty object `source`
+     * values against any array or object value, respectively. See `_.isEqual`
+     * for a list of supported value comparisons.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Util
+     * @param {Object} source The object of property values to match.
+     * @returns {Function} Returns the new spec function.
+     * @example
+     *
+     * var objects = [
+     *   { 'a': 1, 'b': 2, 'c': 3 },
+     *   { 'a': 4, 'b': 5, 'c': 6 }
+     * ];
+     *
+     * _.filter(objects, _.matches({ 'a': 4, 'c': 6 }));
+     * // => [{ 'a': 4, 'b': 5, 'c': 6 }]
+     */
+    function matches(source) {
+      return baseMatches(baseClone(source, CLONE_DEEP_FLAG));
+    }
+
+    /**
+     * Creates a function that performs a partial deep comparison between the
+     * value at `path` of a given object to `srcValue`, returning `true` if the
+     * object value is equivalent, else `false`.
+     *
+     * **Note:** Partial comparisons will match empty array and empty object
+     * `srcValue` values against any array or object value, respectively. See
+     * `_.isEqual` for a list of supported value comparisons.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.2.0
+     * @category Util
+     * @param {Array|string} path The path of the property to get.
+     * @param {*} srcValue The value to match.
+     * @returns {Function} Returns the new spec function.
+     * @example
+     *
+     * var objects = [
+     *   { 'a': 1, 'b': 2, 'c': 3 },
+     *   { 'a': 4, 'b': 5, 'c': 6 }
+     * ];
+     *
+     * _.find(objects, _.matchesProperty('a', 4));
+     * // => { 'a': 4, 'b': 5, 'c': 6 }
+     */
+    function matchesProperty(path, srcValue) {
+      return baseMatchesProperty(path, baseClone(srcValue, CLONE_DEEP_FLAG));
+    }
+
+    /**
+     * Creates a function that invokes the method at `path` of a given object.
+     * Any additional arguments are provided to the invoked method.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.7.0
+     * @category Util
+     * @param {Array|string} path The path of the method to invoke.
+     * @param {...*} [args] The arguments to invoke the method with.
+     * @returns {Function} Returns the new invoker function.
+     * @example
+     *
+     * var objects = [
+     *   { 'a': { 'b': _.constant(2) } },
+     *   { 'a': { 'b': _.constant(1) } }
+     * ];
+     *
+     * _.map(objects, _.method('a.b'));
+     * // => [2, 1]
+     *
+     * _.map(objects, _.method(['a', 'b']));
+     * // => [2, 1]
+     */
+    var method = baseRest(function(path, args) {
+      return function(object) {
+        return baseInvoke(object, path, args);
+      };
+    });
+
+    /**
+     * The opposite of `_.method`; this method creates a function that invokes
+     * the method at a given path of `object`. Any additional arguments are
+     * provided to the invoked method.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.7.0
+     * @category Util
+     * @param {Object} object The object to query.
+     * @param {...*} [args] The arguments to invoke the method with.
+     * @returns {Function} Returns the new invoker function.
+     * @example
+     *
+     * var array = _.times(3, _.constant),
+     *     object = { 'a': array, 'b': array, 'c': array };
+     *
+     * _.map(['a[2]', 'c[0]'], _.methodOf(object));
+     * // => [2, 0]
+     *
+     * _.map([['a', '2'], ['c', '0']], _.methodOf(object));
+     * // => [2, 0]
+     */
+    var methodOf = baseRest(function(object, args) {
+      return function(path) {
+        return baseInvoke(object, path, args);
+      };
+    });
+
+    /**
+     * Adds all own enumerable string keyed function properties of a source
+     * object to the destination object. If `object` is a function, then methods
+     * are added to its prototype as well.
+     *
+     * **Note:** Use `_.runInContext` to create a pristine `lodash` function to
+     * avoid conflicts caused by modifying the original.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @param {Function|Object} [object=lodash] The destination object.
+     * @param {Object} source The object of functions to add.
+     * @param {Object} [options={}] The options object.
+     * @param {boolean} [options.chain=true] Specify whether mixins are chainable.
+     * @returns {Function|Object} Returns `object`.
+     * @example
+     *
+     * function vowels(string) {
+     *   return _.filter(string, function(v) {
+     *     return /[aeiou]/i.test(v);
+     *   });
+     * }
+     *
+     * _.mixin({ 'vowels': vowels });
+     * _.vowels('fred');
+     * // => ['e']
+     *
+     * _('fred').vowels().value();
+     * // => ['e']
+     *
+     * _.mixin({ 'vowels': vowels }, { 'chain': false });
+     * _('fred').vowels();
+     * // => ['e']
+     */
+    function mixin(object, source, options) {
+      var props = keys(source),
+          methodNames = baseFunctions(source, props);
+
+      if (options == null &&
+          !(isObject(source) && (methodNames.length || !props.length))) {
+        options = source;
+        source = object;
+        object = this;
+        methodNames = baseFunctions(source, keys(source));
+      }
+      var chain = !(isObject(options) && 'chain' in options) || !!options.chain,
+          isFunc = isFunction(object);
+
+      arrayEach(methodNames, function(methodName) {
+        var func = source[methodName];
+        object[methodName] = func;
+        if (isFunc) {
+          object.prototype[methodName] = function() {
+            var chainAll = this.__chain__;
+            if (chain || chainAll) {
+              var result = object(this.__wrapped__),
+                  actions = result.__actions__ = copyArray(this.__actions__);
+
+              actions.push({ 'func': func, 'args': arguments, 'thisArg': object });
+              result.__chain__ = chainAll;
+              return result;
+            }
+            return func.apply(object, arrayPush([this.value()], arguments));
+          };
+        }
+      });
+
+      return object;
+    }
+
+    /**
+     * Reverts the `_` variable to its previous value and returns a reference to
+     * the `lodash` function.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @returns {Function} Returns the `lodash` function.
+     * @example
+     *
+     * var lodash = _.noConflict();
+     */
+    function noConflict() {
+      if (root._ === this) {
+        root._ = oldDash;
+      }
+      return this;
+    }
+
+    /**
+     * This method returns `undefined`.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.3.0
+     * @category Util
+     * @example
+     *
+     * _.times(2, _.noop);
+     * // => [undefined, undefined]
+     */
+    function noop() {
+      // No operation performed.
+    }
+
+    /**
+     * Creates a function that gets the argument at index `n`. If `n` is negative,
+     * the nth argument from the end is returned.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {number} [n=0] The index of the argument to return.
+     * @returns {Function} Returns the new pass-thru function.
+     * @example
+     *
+     * var func = _.nthArg(1);
+     * func('a', 'b', 'c', 'd');
+     * // => 'b'
+     *
+     * var func = _.nthArg(-2);
+     * func('a', 'b', 'c', 'd');
+     * // => 'c'
+     */
+    function nthArg(n) {
+      n = toInteger(n);
+      return baseRest(function(args) {
+        return baseNth(args, n);
+      });
+    }
+
+    /**
+     * Creates a function that invokes `iteratees` with the arguments it receives
+     * and returns their results.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {...(Function|Function[])} [iteratees=[_.identity]]
+     *  The iteratees to invoke.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var func = _.over([Math.max, Math.min]);
+     *
+     * func(1, 2, 3, 4);
+     * // => [4, 1]
+     */
+    var over = createOver(arrayMap);
+
+    /**
+     * Creates a function that checks if **all** of the `predicates` return
+     * truthy when invoked with the arguments it receives.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {...(Function|Function[])} [predicates=[_.identity]]
+     *  The predicates to check.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var func = _.overEvery([Boolean, isFinite]);
+     *
+     * func('1');
+     * // => true
+     *
+     * func(null);
+     * // => false
+     *
+     * func(NaN);
+     * // => false
+     */
+    var overEvery = createOver(arrayEvery);
+
+    /**
+     * Creates a function that checks if **any** of the `predicates` return
+     * truthy when invoked with the arguments it receives.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {...(Function|Function[])} [predicates=[_.identity]]
+     *  The predicates to check.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var func = _.overSome([Boolean, isFinite]);
+     *
+     * func('1');
+     * // => true
+     *
+     * func(null);
+     * // => true
+     *
+     * func(NaN);
+     * // => false
+     */
+    var overSome = createOver(arraySome);
+
+    /**
+     * Creates a function that returns the value at `path` of a given object.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.4.0
+     * @category Util
+     * @param {Array|string} path The path of the property to get.
+     * @returns {Function} Returns the new accessor function.
+     * @example
+     *
+     * var objects = [
+     *   { 'a': { 'b': 2 } },
+     *   { 'a': { 'b': 1 } }
+     * ];
+     *
+     * _.map(objects, _.property('a.b'));
+     * // => [2, 1]
+     *
+     * _.map(_.sortBy(objects, _.property(['a', 'b'])), 'a.b');
+     * // => [1, 2]
+     */
+    function property(path) {
+      return isKey(path) ? baseProperty(toKey(path)) : basePropertyDeep(path);
+    }
+
+    /**
+     * The opposite of `_.property`; this method creates a function that returns
+     * the value at a given path of `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Util
+     * @param {Object} object The object to query.
+     * @returns {Function} Returns the new accessor function.
+     * @example
+     *
+     * var array = [0, 1, 2],
+     *     object = { 'a': array, 'b': array, 'c': array };
+     *
+     * _.map(['a[2]', 'c[0]'], _.propertyOf(object));
+     * // => [2, 0]
+     *
+     * _.map([['a', '2'], ['c', '0']], _.propertyOf(object));
+     * // => [2, 0]
+     */
+    function propertyOf(object) {
+      return function(path) {
+        return object == null ? undefined : baseGet(object, path);
+      };
+    }
+
+    /**
+     * Creates an array of numbers (positive and/or negative) progressing from
+     * `start` up to, but not including, `end`. A step of `-1` is used if a negative
+     * `start` is specified without an `end` or `step`. If `end` is not specified,
+     * it's set to `start` with `start` then set to `0`.
+     *
+     * **Note:** JavaScript follows the IEEE-754 standard for resolving
+     * floating-point values which can produce unexpected results.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @param {number} [start=0] The start of the range.
+     * @param {number} end The end of the range.
+     * @param {number} [step=1] The value to increment or decrement by.
+     * @returns {Array} Returns the range of numbers.
+     * @see _.inRange, _.rangeRight
+     * @example
+     *
+     * _.range(4);
+     * // => [0, 1, 2, 3]
+     *
+     * _.range(-4);
+     * // => [0, -1, -2, -3]
+     *
+     * _.range(1, 5);
+     * // => [1, 2, 3, 4]
+     *
+     * _.range(0, 20, 5);
+     * // => [0, 5, 10, 15]
+     *
+     * _.range(0, -4, -1);
+     * // => [0, -1, -2, -3]
+     *
+     * _.range(1, 4, 0);
+     * // => [1, 1, 1]
+     *
+     * _.range(0);
+     * // => []
+     */
+    var range = createRange();
+
+    /**
+     * This method is like `_.range` except that it populates values in
+     * descending order.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {number} [start=0] The start of the range.
+     * @param {number} end The end of the range.
+     * @param {number} [step=1] The value to increment or decrement by.
+     * @returns {Array} Returns the range of numbers.
+     * @see _.inRange, _.range
+     * @example
+     *
+     * _.rangeRight(4);
+     * // => [3, 2, 1, 0]
+     *
+     * _.rangeRight(-4);
+     * // => [-3, -2, -1, 0]
+     *
+     * _.rangeRight(1, 5);
+     * // => [4, 3, 2, 1]
+     *
+     * _.rangeRight(0, 20, 5);
+     * // => [15, 10, 5, 0]
+     *
+     * _.rangeRight(0, -4, -1);
+     * // => [-3, -2, -1, 0]
+     *
+     * _.rangeRight(1, 4, 0);
+     * // => [1, 1, 1]
+     *
+     * _.rangeRight(0);
+     * // => []
+     */
+    var rangeRight = createRange(true);
+
+    /**
+     * This method returns a new empty array.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.13.0
+     * @category Util
+     * @returns {Array} Returns the new empty array.
+     * @example
+     *
+     * var arrays = _.times(2, _.stubArray);
+     *
+     * console.log(arrays);
+     * // => [[], []]
+     *
+     * console.log(arrays[0] === arrays[1]);
+     * // => false
+     */
+    function stubArray() {
+      return [];
+    }
+
+    /**
+     * This method returns `false`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.13.0
+     * @category Util
+     * @returns {boolean} Returns `false`.
+     * @example
+     *
+     * _.times(2, _.stubFalse);
+     * // => [false, false]
+     */
+    function stubFalse() {
+      return false;
+    }
+
+    /**
+     * This method returns a new empty object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.13.0
+     * @category Util
+     * @returns {Object} Returns the new empty object.
+     * @example
+     *
+     * var objects = _.times(2, _.stubObject);
+     *
+     * console.log(objects);
+     * // => [{}, {}]
+     *
+     * console.log(objects[0] === objects[1]);
+     * // => false
+     */
+    function stubObject() {
+      return {};
+    }
+
+    /**
+     * This method returns an empty string.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.13.0
+     * @category Util
+     * @returns {string} Returns the empty string.
+     * @example
+     *
+     * _.times(2, _.stubString);
+     * // => ['', '']
+     */
+    function stubString() {
+      return '';
+    }
+
+    /**
+     * This method returns `true`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.13.0
+     * @category Util
+     * @returns {boolean} Returns `true`.
+     * @example
+     *
+     * _.times(2, _.stubTrue);
+     * // => [true, true]
+     */
+    function stubTrue() {
+      return true;
+    }
+
+    /**
+     * Invokes the iteratee `n` times, returning an array of the results of
+     * each invocation. The iteratee is invoked with one argument; (index).
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @param {number} n The number of times to invoke `iteratee`.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the array of results.
+     * @example
+     *
+     * _.times(3, String);
+     * // => ['0', '1', '2']
+     *
+     *  _.times(4, _.constant(0));
+     * // => [0, 0, 0, 0]
+     */
+    function times(n, iteratee) {
+      n = toInteger(n);
+      if (n < 1 || n > MAX_SAFE_INTEGER) {
+        return [];
+      }
+      var index = MAX_ARRAY_LENGTH,
+          length = nativeMin(n, MAX_ARRAY_LENGTH);
+
+      iteratee = getIteratee(iteratee);
+      n -= MAX_ARRAY_LENGTH;
+
+      var result = baseTimes(length, iteratee);
+      while (++index < n) {
+        iteratee(index);
+      }
+      return result;
+    }
+
+    /**
+     * Converts `value` to a property path array.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {*} value The value to convert.
+     * @returns {Array} Returns the new property path array.
+     * @example
+     *
+     * _.toPath('a.b.c');
+     * // => ['a', 'b', 'c']
+     *
+     * _.toPath('a[0].b.c');
+     * // => ['a', '0', 'b', 'c']
+     */
+    function toPath(value) {
+      if (isArray(value)) {
+        return arrayMap(value, toKey);
+      }
+      return isSymbol(value) ? [value] : copyArray(stringToPath(toString(value)));
+    }
+
+    /**
+     * Generates a unique ID. If `prefix` is given, the ID is appended to it.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @param {string} [prefix=''] The value to prefix the ID with.
+     * @returns {string} Returns the unique ID.
+     * @example
+     *
+     * _.uniqueId('contact_');
+     * // => 'contact_104'
+     *
+     * _.uniqueId();
+     * // => '105'
+     */
+    function uniqueId(prefix) {
+      var id = ++idCounter;
+      return toString(prefix) + id;
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Adds two numbers.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.4.0
+     * @category Math
+     * @param {number} augend The first number in an addition.
+     * @param {number} addend The second number in an addition.
+     * @returns {number} Returns the total.
+     * @example
+     *
+     * _.add(6, 4);
+     * // => 10
+     */
+    var add = createMathOperation(function(augend, addend) {
+      return augend + addend;
+    }, 0);
+
+    /**
+     * Computes `number` rounded up to `precision`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.10.0
+     * @category Math
+     * @param {number} number The number to round up.
+     * @param {number} [precision=0] The precision to round up to.
+     * @returns {number} Returns the rounded up number.
+     * @example
+     *
+     * _.ceil(4.006);
+     * // => 5
+     *
+     * _.ceil(6.004, 2);
+     * // => 6.01
+     *
+     * _.ceil(6040, -2);
+     * // => 6100
+     */
+    var ceil = createRound('ceil');
+
+    /**
+     * Divide two numbers.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.7.0
+     * @category Math
+     * @param {number} dividend The first number in a division.
+     * @param {number} divisor The second number in a division.
+     * @returns {number} Returns the quotient.
+     * @example
+     *
+     * _.divide(6, 4);
+     * // => 1.5
+     */
+    var divide = createMathOperation(function(dividend, divisor) {
+      return dividend / divisor;
+    }, 1);
+
+    /**
+     * Computes `number` rounded down to `precision`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.10.0
+     * @category Math
+     * @param {number} number The number to round down.
+     * @param {number} [precision=0] The precision to round down to.
+     * @returns {number} Returns the rounded down number.
+     * @example
+     *
+     * _.floor(4.006);
+     * // => 4
+     *
+     * _.floor(0.046, 2);
+     * // => 0.04
+     *
+     * _.floor(4060, -2);
+     * // => 4000
+     */
+    var floor = createRound('floor');
+
+    /**
+     * Computes the maximum value of `array`. If `array` is empty or falsey,
+     * `undefined` is returned.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @returns {*} Returns the maximum value.
+     * @example
+     *
+     * _.max([4, 2, 8, 6]);
+     * // => 8
+     *
+     * _.max([]);
+     * // => undefined
+     */
+    function max(array) {
+      return (array && array.length)
+        ? baseExtremum(array, identity, baseGt)
+        : undefined;
+    }
+
+    /**
+     * This method is like `_.max` except that it accepts `iteratee` which is
+     * invoked for each element in `array` to generate the criterion by which
+     * the value is ranked. The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {*} Returns the maximum value.
+     * @example
+     *
+     * var objects = [{ 'n': 1 }, { 'n': 2 }];
+     *
+     * _.maxBy(objects, function(o) { return o.n; });
+     * // => { 'n': 2 }
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.maxBy(objects, 'n');
+     * // => { 'n': 2 }
+     */
+    function maxBy(array, iteratee) {
+      return (array && array.length)
+        ? baseExtremum(array, getIteratee(iteratee, 2), baseGt)
+        : undefined;
+    }
+
+    /**
+     * Computes the mean of the values in `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @returns {number} Returns the mean.
+     * @example
+     *
+     * _.mean([4, 2, 8, 6]);
+     * // => 5
+     */
+    function mean(array) {
+      return baseMean(array, identity);
+    }
+
+    /**
+     * This method is like `_.mean` except that it accepts `iteratee` which is
+     * invoked for each element in `array` to generate the value to be averaged.
+     * The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.7.0
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {number} Returns the mean.
+     * @example
+     *
+     * var objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }];
+     *
+     * _.meanBy(objects, function(o) { return o.n; });
+     * // => 5
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.meanBy(objects, 'n');
+     * // => 5
+     */
+    function meanBy(array, iteratee) {
+      return baseMean(array, getIteratee(iteratee, 2));
+    }
+
+    /**
+     * Computes the minimum value of `array`. If `array` is empty or falsey,
+     * `undefined` is returned.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @returns {*} Returns the minimum value.
+     * @example
+     *
+     * _.min([4, 2, 8, 6]);
+     * // => 2
+     *
+     * _.min([]);
+     * // => undefined
+     */
+    function min(array) {
+      return (array && array.length)
+        ? baseExtremum(array, identity, baseLt)
+        : undefined;
+    }
+
+    /**
+     * This method is like `_.min` except that it accepts `iteratee` which is
+     * invoked for each element in `array` to generate the criterion by which
+     * the value is ranked. The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {*} Returns the minimum value.
+     * @example
+     *
+     * var objects = [{ 'n': 1 }, { 'n': 2 }];
+     *
+     * _.minBy(objects, function(o) { return o.n; });
+     * // => { 'n': 1 }
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.minBy(objects, 'n');
+     * // => { 'n': 1 }
+     */
+    function minBy(array, iteratee) {
+      return (array && array.length)
+        ? baseExtremum(array, getIteratee(iteratee, 2), baseLt)
+        : undefined;
+    }
+
+    /**
+     * Multiply two numbers.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.7.0
+     * @category Math
+     * @param {number} multiplier The first number in a multiplication.
+     * @param {number} multiplicand The second number in a multiplication.
+     * @returns {number} Returns the product.
+     * @example
+     *
+     * _.multiply(6, 4);
+     * // => 24
+     */
+    var multiply = createMathOperation(function(multiplier, multiplicand) {
+      return multiplier * multiplicand;
+    }, 1);
+
+    /**
+     * Computes `number` rounded to `precision`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.10.0
+     * @category Math
+     * @param {number} number The number to round.
+     * @param {number} [precision=0] The precision to round to.
+     * @returns {number} Returns the rounded number.
+     * @example
+     *
+     * _.round(4.006);
+     * // => 4
+     *
+     * _.round(4.006, 2);
+     * // => 4.01
+     *
+     * _.round(4060, -2);
+     * // => 4100
+     */
+    var round = createRound('round');
+
+    /**
+     * Subtract two numbers.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Math
+     * @param {number} minuend The first number in a subtraction.
+     * @param {number} subtrahend The second number in a subtraction.
+     * @returns {number} Returns the difference.
+     * @example
+     *
+     * _.subtract(6, 4);
+     * // => 2
+     */
+    var subtract = createMathOperation(function(minuend, subtrahend) {
+      return minuend - subtrahend;
+    }, 0);
+
+    /**
+     * Computes the sum of the values in `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.4.0
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @returns {number} Returns the sum.
+     * @example
+     *
+     * _.sum([4, 2, 8, 6]);
+     * // => 20
+     */
+    function sum(array) {
+      return (array && array.length)
+        ? baseSum(array, identity)
+        : 0;
+    }
+
+    /**
+     * This method is like `_.sum` except that it accepts `iteratee` which is
+     * invoked for each element in `array` to generate the value to be summed.
+     * The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @param {Function} [iteratee=_.identity] The iteratee invoked per element.
+     * @returns {number} Returns the sum.
+     * @example
+     *
+     * var objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }];
+     *
+     * _.sumBy(objects, function(o) { return o.n; });
+     * // => 20
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.sumBy(objects, 'n');
+     * // => 20
+     */
+    function sumBy(array, iteratee) {
+      return (array && array.length)
+        ? baseSum(array, getIteratee(iteratee, 2))
+        : 0;
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    // Add methods that return wrapped values in chain sequences.
+    lodash.after = after;
+    lodash.ary = ary;
+    lodash.assign = assign;
+    lodash.assignIn = assignIn;
+    lodash.assignInWith = assignInWith;
+    lodash.assignWith = assignWith;
+    lodash.at = at;
+    lodash.before = before;
+    lodash.bind = bind;
+    lodash.bindAll = bindAll;
+    lodash.bindKey = bindKey;
+    lodash.castArray = castArray;
+    lodash.chain = chain;
+    lodash.chunk = chunk;
+    lodash.compact = compact;
+    lodash.concat = concat;
+    lodash.cond = cond;
+    lodash.conforms = conforms;
+    lodash.constant = constant;
+    lodash.countBy = countBy;
+    lodash.create = create;
+    lodash.curry = curry;
+    lodash.curryRight = curryRight;
+    lodash.debounce = debounce;
+    lodash.defaults = defaults;
+    lodash.defaultsDeep = defaultsDeep;
+    lodash.defer = defer;
+    lodash.delay = delay;
+    lodash.difference = difference;
+    lodash.differenceBy = differenceBy;
+    lodash.differenceWith = differenceWith;
+    lodash.drop = drop;
+    lodash.dropRight = dropRight;
+    lodash.dropRightWhile = dropRightWhile;
+    lodash.dropWhile = dropWhile;
+    lodash.fill = fill;
+    lodash.filter = filter;
+    lodash.flatMap = flatMap;
+    lodash.flatMapDeep = flatMapDeep;
+    lodash.flatMapDepth = flatMapDepth;
+    lodash.flatten = flatten;
+    lodash.flattenDeep = flattenDeep;
+    lodash.flattenDepth = flattenDepth;
+    lodash.flip = flip;
+    lodash.flow = flow;
+    lodash.flowRight = flowRight;
+    lodash.fromPairs = fromPairs;
+    lodash.functions = functions;
+    lodash.functionsIn = functionsIn;
+    lodash.groupBy = groupBy;
+    lodash.initial = initial;
+    lodash.intersection = intersection;
+    lodash.intersectionBy = intersectionBy;
+    lodash.intersectionWith = intersectionWith;
+    lodash.invert = invert;
+    lodash.invertBy = invertBy;
+    lodash.invokeMap = invokeMap;
+    lodash.iteratee = iteratee;
+    lodash.keyBy = keyBy;
+    lodash.keys = keys;
+    lodash.keysIn = keysIn;
+    lodash.map = map;
+    lodash.mapKeys = mapKeys;
+    lodash.mapValues = mapValues;
+    lodash.matches = matches;
+    lodash.matchesProperty = matchesProperty;
+    lodash.memoize = memoize;
+    lodash.merge = merge;
+    lodash.mergeWith = mergeWith;
+    lodash.method = method;
+    lodash.methodOf = methodOf;
+    lodash.mixin = mixin;
+    lodash.negate = negate;
+    lodash.nthArg = nthArg;
+    lodash.omit = omit;
+    lodash.omitBy = omitBy;
+    lodash.once = once;
+    lodash.orderBy = orderBy;
+    lodash.over = over;
+    lodash.overArgs = overArgs;
+    lodash.overEvery = overEvery;
+    lodash.overSome = overSome;
+    lodash.partial = partial;
+    lodash.partialRight = partialRight;
+    lodash.partition = partition;
+    lodash.pick = pick;
+    lodash.pickBy = pickBy;
+    lodash.property = property;
+    lodash.propertyOf = propertyOf;
+    lodash.pull = pull;
+    lodash.pullAll = pullAll;
+    lodash.pullAllBy = pullAllBy;
+    lodash.pullAllWith = pullAllWith;
+    lodash.pullAt = pullAt;
+    lodash.range = range;
+    lodash.rangeRight = rangeRight;
+    lodash.rearg = rearg;
+    lodash.reject = reject;
+    lodash.remove = remove;
+    lodash.rest = rest;
+    lodash.reverse = reverse;
+    lodash.sampleSize = sampleSize;
+    lodash.set = set;
+    lodash.setWith = setWith;
+    lodash.shuffle = shuffle;
+    lodash.slice = slice;
+    lodash.sortBy = sortBy;
+    lodash.sortedUniq = sortedUniq;
+    lodash.sortedUniqBy = sortedUniqBy;
+    lodash.split = split;
+    lodash.spread = spread;
+    lodash.tail = tail;
+    lodash.take = take;
+    lodash.takeRight = takeRight;
+    lodash.takeRightWhile = takeRightWhile;
+    lodash.takeWhile = takeWhile;
+    lodash.tap = tap;
+    lodash.throttle = throttle;
+    lodash.thru = thru;
+    lodash.toArray = toArray;
+    lodash.toPairs = toPairs;
+    lodash.toPairsIn = toPairsIn;
+    lodash.toPath = toPath;
+    lodash.toPlainObject = toPlainObject;
+    lodash.transform = transform;
+    lodash.unary = unary;
+    lodash.union = union;
+    lodash.unionBy = unionBy;
+    lodash.unionWith = unionWith;
+    lodash.uniq = uniq;
+    lodash.uniqBy = uniqBy;
+    lodash.uniqWith = uniqWith;
+    lodash.unset = unset;
+    lodash.unzip = unzip;
+    lodash.unzipWith = unzipWith;
+    lodash.update = update;
+    lodash.updateWith = updateWith;
+    lodash.values = values;
+    lodash.valuesIn = valuesIn;
+    lodash.without = without;
+    lodash.words = words;
+    lodash.wrap = wrap;
+    lodash.xor = xor;
+    lodash.xorBy = xorBy;
+    lodash.xorWith = xorWith;
+    lodash.zip = zip;
+    lodash.zipObject = zipObject;
+    lodash.zipObjectDeep = zipObjectDeep;
+    lodash.zipWith = zipWith;
+
+    // Add aliases.
+    lodash.entries = toPairs;
+    lodash.entriesIn = toPairsIn;
+    lodash.extend = assignIn;
+    lodash.extendWith = assignInWith;
+
+    // Add methods to `lodash.prototype`.
+    mixin(lodash, lodash);
+
+    /*------------------------------------------------------------------------*/
+
+    // Add methods that return unwrapped values in chain sequences.
+    lodash.add = add;
+    lodash.attempt = attempt;
+    lodash.camelCase = camelCase;
+    lodash.capitalize = capitalize;
+    lodash.ceil = ceil;
+    lodash.clamp = clamp;
+    lodash.clone = clone;
+    lodash.cloneDeep = cloneDeep;
+    lodash.cloneDeepWith = cloneDeepWith;
+    lodash.cloneWith = cloneWith;
+    lodash.conformsTo = conformsTo;
+    lodash.deburr = deburr;
+    lodash.defaultTo = defaultTo;
+    lodash.divide = divide;
+    lodash.endsWith = endsWith;
+    lodash.eq = eq;
+    lodash.escape = escape;
+    lodash.escapeRegExp = escapeRegExp;
+    lodash.every = every;
+    lodash.find = find;
+    lodash.findIndex = findIndex;
+    lodash.findKey = findKey;
+    lodash.findLast = findLast;
+    lodash.findLastIndex = findLastIndex;
+    lodash.findLastKey = findLastKey;
+    lodash.floor = floor;
+    lodash.forEach = forEach;
+    lodash.forEachRight = forEachRight;
+    lodash.forIn = forIn;
+    lodash.forInRight = forInRight;
+    lodash.forOwn = forOwn;
+    lodash.forOwnRight = forOwnRight;
+    lodash.get = get;
+    lodash.gt = gt;
+    lodash.gte = gte;
+    lodash.has = has;
+    lodash.hasIn = hasIn;
+    lodash.head = head;
+    lodash.identity = identity;
+    lodash.includes = includes;
+    lodash.indexOf = indexOf;
+    lodash.inRange = inRange;
+    lodash.invoke = invoke;
+    lodash.isArguments = isArguments;
+    lodash.isArray = isArray;
+    lodash.isArrayBuffer = isArrayBuffer;
+    lodash.isArrayLike = isArrayLike;
+    lodash.isArrayLikeObject = isArrayLikeObject;
+    lodash.isBoolean = isBoolean;
+    lodash.isBuffer = isBuffer;
+    lodash.isDate = isDate;
+    lodash.isElement = isElement;
+    lodash.isEmpty = isEmpty;
+    lodash.isEqual = isEqual;
+    lodash.isEqualWith = isEqualWith;
+    lodash.isError = isError;
+    lodash.isFinite = isFinite;
+    lodash.isFunction = isFunction;
+    lodash.isInteger = isInteger;
+    lodash.isLength = isLength;
+    lodash.isMap = isMap;
+    lodash.isMatch = isMatch;
+    lodash.isMatchWith = isMatchWith;
+    lodash.isNaN = isNaN;
+    lodash.isNative = isNative;
+    lodash.isNil = isNil;
+    lodash.isNull = isNull;
+    lodash.isNumber = isNumber;
+    lodash.isObject = isObject;
+    lodash.isObjectLike = isObjectLike;
+    lodash.isPlainObject = isPlainObject;
+    lodash.isRegExp = isRegExp;
+    lodash.isSafeInteger = isSafeInteger;
+    lodash.isSet = isSet;
+    lodash.isString = isString;
+    lodash.isSymbol = isSymbol;
+    lodash.isTypedArray = isTypedArray;
+    lodash.isUndefined = isUndefined;
+    lodash.isWeakMap = isWeakMap;
+    lodash.isWeakSet = isWeakSet;
+    lodash.join = join;
+    lodash.kebabCase = kebabCase;
+    lodash.last = last;
+    lodash.lastIndexOf = lastIndexOf;
+    lodash.lowerCase = lowerCase;
+    lodash.lowerFirst = lowerFirst;
+    lodash.lt = lt;
+    lodash.lte = lte;
+    lodash.max = max;
+    lodash.maxBy = maxBy;
+    lodash.mean = mean;
+    lodash.meanBy = meanBy;
+    lodash.min = min;
+    lodash.minBy = minBy;
+    lodash.stubArray = stubArray;
+    lodash.stubFalse = stubFalse;
+    lodash.stubObject = stubObject;
+    lodash.stubString = stubString;
+    lodash.stubTrue = stubTrue;
+    lodash.multiply = multiply;
+    lodash.nth = nth;
+    lodash.noConflict = noConflict;
+    lodash.noop = noop;
+    lodash.now = now;
+    lodash.pad = pad;
+    lodash.padEnd = padEnd;
+    lodash.padStart = padStart;
+    lodash.parseInt = parseInt;
+    lodash.random = random;
+    lodash.reduce = reduce;
+    lodash.reduceRight = reduceRight;
+    lodash.repeat = repeat;
+    lodash.replace = replace;
+    lodash.result = result;
+    lodash.round = round;
+    lodash.runInContext = runInContext;
+    lodash.sample = sample;
+    lodash.size = size;
+    lodash.snakeCase = snakeCase;
+    lodash.some = some;
+    lodash.sortedIndex = sortedIndex;
+    lodash.sortedIndexBy = sortedIndexBy;
+    lodash.sortedIndexOf = sortedIndexOf;
+    lodash.sortedLastIndex = sortedLastIndex;
+    lodash.sortedLastIndexBy = sortedLastIndexBy;
+    lodash.sortedLastIndexOf = sortedLastIndexOf;
+    lodash.startCase = startCase;
+    lodash.startsWith = startsWith;
+    lodash.subtract = subtract;
+    lodash.sum = sum;
+    lodash.sumBy = sumBy;
+    lodash.template = template;
+    lodash.times = times;
+    lodash.toFinite = toFinite;
+    lodash.toInteger = toInteger;
+    lodash.toLength = toLength;
+    lodash.toLower = toLower;
+    lodash.toNumber = toNumber;
+    lodash.toSafeInteger = toSafeInteger;
+    lodash.toString = toString;
+    lodash.toUpper = toUpper;
+    lodash.trim = trim;
+    lodash.trimEnd = trimEnd;
+    lodash.trimStart = trimStart;
+    lodash.truncate = truncate;
+    lodash.unescape = unescape;
+    lodash.uniqueId = uniqueId;
+    lodash.upperCase = upperCase;
+    lodash.upperFirst = upperFirst;
+
+    // Add aliases.
+    lodash.each = forEach;
+    lodash.eachRight = forEachRight;
+    lodash.first = head;
+
+    mixin(lodash, (function() {
+      var source = {};
+      baseForOwn(lodash, function(func, methodName) {
+        if (!hasOwnProperty.call(lodash.prototype, methodName)) {
+          source[methodName] = func;
+        }
+      });
+      return source;
+    }()), { 'chain': false });
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * The semantic version number.
+     *
+     * @static
+     * @memberOf _
+     * @type {string}
+     */
+    lodash.VERSION = VERSION;
+
+    // Assign default placeholders.
+    arrayEach(['bind', 'bindKey', 'curry', 'curryRight', 'partial', 'partialRight'], function(methodName) {
+      lodash[methodName].placeholder = lodash;
+    });
+
+    // Add `LazyWrapper` methods for `_.drop` and `_.take` variants.
+    arrayEach(['drop', 'take'], function(methodName, index) {
+      LazyWrapper.prototype[methodName] = function(n) {
+        n = n === undefined ? 1 : nativeMax(toInteger(n), 0);
+
+        var result = (this.__filtered__ && !index)
+          ? new LazyWrapper(this)
+          : this.clone();
+
+        if (result.__filtered__) {
+          result.__takeCount__ = nativeMin(n, result.__takeCount__);
+        } else {
+          result.__views__.push({
+            'size': nativeMin(n, MAX_ARRAY_LENGTH),
+            'type': methodName + (result.__dir__ < 0 ? 'Right' : '')
+          });
+        }
+        return result;
+      };
+
+      LazyWrapper.prototype[methodName + 'Right'] = function(n) {
+        return this.reverse()[methodName](n).reverse();
+      };
+    });
+
+    // Add `LazyWrapper` methods that accept an `iteratee` value.
+    arrayEach(['filter', 'map', 'takeWhile'], function(methodName, index) {
+      var type = index + 1,
+          isFilter = type == LAZY_FILTER_FLAG || type == LAZY_WHILE_FLAG;
+
+      LazyWrapper.prototype[methodName] = function(iteratee) {
+        var result = this.clone();
+        result.__iteratees__.push({
+          'iteratee': getIteratee(iteratee, 3),
+          'type': type
+        });
+        result.__filtered__ = result.__filtered__ || isFilter;
+        return result;
+      };
+    });
+
+    // Add `LazyWrapper` methods for `_.head` and `_.last`.
+    arrayEach(['head', 'last'], function(methodName, index) {
+      var takeName = 'take' + (index ? 'Right' : '');
+
+      LazyWrapper.prototype[methodName] = function() {
+        return this[takeName](1).value()[0];
+      };
+    });
+
+    // Add `LazyWrapper` methods for `_.initial` and `_.tail`.
+    arrayEach(['initial', 'tail'], function(methodName, index) {
+      var dropName = 'drop' + (index ? '' : 'Right');
+
+      LazyWrapper.prototype[methodName] = function() {
+        return this.__filtered__ ? new LazyWrapper(this) : this[dropName](1);
+      };
+    });
+
+    LazyWrapper.prototype.compact = function() {
+      return this.filter(identity);
+    };
+
+    LazyWrapper.prototype.find = function(predicate) {
+      return this.filter(predicate).head();
+    };
+
+    LazyWrapper.prototype.findLast = function(predicate) {
+      return this.reverse().find(predicate);
+    };
+
+    LazyWrapper.prototype.invokeMap = baseRest(function(path, args) {
+      if (typeof path == 'function') {
+        return new LazyWrapper(this);
+      }
+      return this.map(function(value) {
+        return baseInvoke(value, path, args);
+      });
+    });
+
+    LazyWrapper.prototype.reject = function(predicate) {
+      return this.filter(negate(getIteratee(predicate)));
+    };
+
+    LazyWrapper.prototype.slice = function(start, end) {
+      start = toInteger(start);
+
+      var result = this;
+      if (result.__filtered__ && (start > 0 || end < 0)) {
+        return new LazyWrapper(result);
+      }
+      if (start < 0) {
+        result = result.takeRight(-start);
+      } else if (start) {
+        result = result.drop(start);
+      }
+      if (end !== undefined) {
+        end = toInteger(end);
+        result = end < 0 ? result.dropRight(-end) : result.take(end - start);
+      }
+      return result;
+    };
+
+    LazyWrapper.prototype.takeRightWhile = function(predicate) {
+      return this.reverse().takeWhile(predicate).reverse();
+    };
+
+    LazyWrapper.prototype.toArray = function() {
+      return this.take(MAX_ARRAY_LENGTH);
+    };
+
+    // Add `LazyWrapper` methods to `lodash.prototype`.
+    baseForOwn(LazyWrapper.prototype, function(func, methodName) {
+      var checkIteratee = /^(?:filter|find|map|reject)|While$/.test(methodName),
+          isTaker = /^(?:head|last)$/.test(methodName),
+          lodashFunc = lodash[isTaker ? ('take' + (methodName == 'last' ? 'Right' : '')) : methodName],
+          retUnwrapped = isTaker || /^find/.test(methodName);
+
+      if (!lodashFunc) {
+        return;
+      }
+      lodash.prototype[methodName] = function() {
+        var value = this.__wrapped__,
+            args = isTaker ? [1] : arguments,
+            isLazy = value instanceof LazyWrapper,
+            iteratee = args[0],
+            useLazy = isLazy || isArray(value);
+
+        var interceptor = function(value) {
+          var result = lodashFunc.apply(lodash, arrayPush([value], args));
+          return (isTaker && chainAll) ? result[0] : result;
+        };
+
+        if (useLazy && checkIteratee && typeof iteratee == 'function' && iteratee.length != 1) {
+          // Avoid lazy use if the iteratee has a "length" value other than `1`.
+          isLazy = useLazy = false;
+        }
+        var chainAll = this.__chain__,
+            isHybrid = !!this.__actions__.length,
+            isUnwrapped = retUnwrapped && !chainAll,
+            onlyLazy = isLazy && !isHybrid;
+
+        if (!retUnwrapped && useLazy) {
+          value = onlyLazy ? value : new LazyWrapper(this);
+          var result = func.apply(value, args);
+          result.__actions__.push({ 'func': thru, 'args': [interceptor], 'thisArg': undefined });
+          return new LodashWrapper(result, chainAll);
+        }
+        if (isUnwrapped && onlyLazy) {
+          return func.apply(this, args);
+        }
+        result = this.thru(interceptor);
+        return isUnwrapped ? (isTaker ? result.value()[0] : result.value()) : result;
+      };
+    });
+
+    // Add `Array` methods to `lodash.prototype`.
+    arrayEach(['pop', 'push', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {
+      var func = arrayProto[methodName],
+          chainName = /^(?:push|sort|unshift)$/.test(methodName) ? 'tap' : 'thru',
+          retUnwrapped = /^(?:pop|shift)$/.test(methodName);
+
+      lodash.prototype[methodName] = function() {
+        var args = arguments;
+        if (retUnwrapped && !this.__chain__) {
+          var value = this.value();
+          return func.apply(isArray(value) ? value : [], args);
+        }
+        return this[chainName](function(value) {
+          return func.apply(isArray(value) ? value : [], args);
+        });
+      };
+    });
+
+    // Map minified method names to their real names.
+    baseForOwn(LazyWrapper.prototype, function(func, methodName) {
+      var lodashFunc = lodash[methodName];
+      if (lodashFunc) {
+        var key = (lodashFunc.name + ''),
+            names = realNames[key] || (realNames[key] = []);
+
+        names.push({ 'name': methodName, 'func': lodashFunc });
+      }
+    });
+
+    realNames[createHybrid(undefined, WRAP_BIND_KEY_FLAG).name] = [{
+      'name': 'wrapper',
+      'func': undefined
+    }];
+
+    // Add methods to `LazyWrapper`.
+    LazyWrapper.prototype.clone = lazyClone;
+    LazyWrapper.prototype.reverse = lazyReverse;
+    LazyWrapper.prototype.value = lazyValue;
+
+    // Add chain sequence methods to the `lodash` wrapper.
+    lodash.prototype.at = wrapperAt;
+    lodash.prototype.chain = wrapperChain;
+    lodash.prototype.commit = wrapperCommit;
+    lodash.prototype.next = wrapperNext;
+    lodash.prototype.plant = wrapperPlant;
+    lodash.prototype.reverse = wrapperReverse;
+    lodash.prototype.toJSON = lodash.prototype.valueOf = lodash.prototype.value = wrapperValue;
+
+    // Add lazy aliases.
+    lodash.prototype.first = lodash.prototype.head;
+
+    if (symIterator) {
+      lodash.prototype[symIterator] = wrapperToIterator;
+    }
+    return lodash;
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  // Export lodash.
+  var _ = runInContext();
+
+  // Some AMD build optimizers, like r.js, check for condition patterns like:
+  if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
+    // Expose Lodash on the global object to prevent errors when Lodash is
+    // loaded by a script tag in the presence of an AMD loader.
+    // See http://requirejs.org/docs/errors.html#mismatch for more details.
+    // Use `_.noConflict` to remove Lodash from the global object.
+    root._ = _;
+
+    // Define as an anonymous module so, through path mapping, it can be
+    // referenced as the "underscore" module.
+    define(function() {
+      return _;
+    });
+  }
+  // Check for `exports` after `define` in case a build optimizer adds it.
+  else if (freeModule) {
+    // Export for Node.js.
+    (freeModule.exports = _)._ = _;
+    // Export for CommonJS support.
+    freeExports._ = _;
+  }
+  else {
+    // Export to the global object.
+    root._ = _;
+  }
+}.call(this));
diff --git a/src/legacy/design-studio/js/promise-polyfill.js b/src/legacy/design-studio/js/promise-polyfill.js
new file mode 100644
index 0000000000000000000000000000000000000000..1619c9cfbe8c1ffc2f245b5bf127908d9f8d3e63
--- /dev/null
+++ b/src/legacy/design-studio/js/promise-polyfill.js
@@ -0,0 +1,286 @@
+(function (global, factory) {
+	typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
+	typeof define === 'function' && define.amd ? define(factory) :
+	(factory());
+}(this, (function () { 'use strict';
+
+/**
+ * @this {Promise}
+ */
+function finallyConstructor(callback) {
+  var constructor = this.constructor;
+  return this.then(
+    function(value) {
+      return constructor.resolve(callback()).then(function() {
+        return value;
+      });
+    },
+    function(reason) {
+      return constructor.resolve(callback()).then(function() {
+        return constructor.reject(reason);
+      });
+    }
+  );
+}
+
+// Store setTimeout reference so promise-polyfill will be unaffected by
+// other code modifying setTimeout (like sinon.useFakeTimers())
+var setTimeoutFunc = setTimeout;
+
+function noop() {}
+
+// Polyfill for Function.prototype.bind
+function bind(fn, thisArg) {
+  return function() {
+    fn.apply(thisArg, arguments);
+  };
+}
+
+/**
+ * @constructor
+ * @param {Function} fn
+ */
+function Promise(fn) {
+  if (!(this instanceof Promise))
+    throw new TypeError('Promises must be constructed via new');
+  if (typeof fn !== 'function') throw new TypeError('not a function');
+  /** @type {!number} */
+  this._state = 0;
+  /** @type {!boolean} */
+  this._handled = false;
+  /** @type {Promise|undefined} */
+  this._value = undefined;
+  /** @type {!Array<!Function>} */
+  this._deferreds = [];
+
+  doResolve(fn, this);
+}
+
+function handle(self, deferred) {
+  while (self._state === 3) {
+    self = self._value;
+  }
+  if (self._state === 0) {
+    self._deferreds.push(deferred);
+    return;
+  }
+  self._handled = true;
+  Promise._immediateFn(function() {
+    var cb = self._state === 1 ? deferred.onFulfilled : deferred.onRejected;
+    if (cb === null) {
+      (self._state === 1 ? resolve : reject)(deferred.promise, self._value);
+      return;
+    }
+    var ret;
+    try {
+      ret = cb(self._value);
+    } catch (e) {
+      reject(deferred.promise, e);
+      return;
+    }
+    resolve(deferred.promise, ret);
+  });
+}
+
+function resolve(self, newValue) {
+  try {
+    // Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure
+    if (newValue === self)
+      throw new TypeError('A promise cannot be resolved with itself.');
+    if (
+      newValue &&
+      (typeof newValue === 'object' || typeof newValue === 'function')
+    ) {
+      var then = newValue.then;
+      if (newValue instanceof Promise) {
+        self._state = 3;
+        self._value = newValue;
+        finale(self);
+        return;
+      } else if (typeof then === 'function') {
+        doResolve(bind(then, newValue), self);
+        return;
+      }
+    }
+    self._state = 1;
+    self._value = newValue;
+    finale(self);
+  } catch (e) {
+    reject(self, e);
+  }
+}
+
+function reject(self, newValue) {
+  self._state = 2;
+  self._value = newValue;
+  finale(self);
+}
+
+function finale(self) {
+  if (self._state === 2 && self._deferreds.length === 0) {
+    Promise._immediateFn(function() {
+      if (!self._handled) {
+        Promise._unhandledRejectionFn(self._value);
+      }
+    });
+  }
+
+  for (var i = 0, len = self._deferreds.length; i < len; i++) {
+    handle(self, self._deferreds[i]);
+  }
+  self._deferreds = null;
+}
+
+/**
+ * @constructor
+ */
+function Handler(onFulfilled, onRejected, promise) {
+  this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null;
+  this.onRejected = typeof onRejected === 'function' ? onRejected : null;
+  this.promise = promise;
+}
+
+/**
+ * Take a potentially misbehaving resolver function and make sure
+ * onFulfilled and onRejected are only called once.
+ *
+ * Makes no guarantees about asynchrony.
+ */
+function doResolve(fn, self) {
+  var done = false;
+  try {
+    fn(
+      function(value) {
+        if (done) return;
+        done = true;
+        resolve(self, value);
+      },
+      function(reason) {
+        if (done) return;
+        done = true;
+        reject(self, reason);
+      }
+    );
+  } catch (ex) {
+    if (done) return;
+    done = true;
+    reject(self, ex);
+  }
+}
+
+Promise.prototype['catch'] = function(onRejected) {
+  return this.then(null, onRejected);
+};
+
+Promise.prototype.then = function(onFulfilled, onRejected) {
+  // @ts-ignore
+  var prom = new this.constructor(noop);
+
+  handle(this, new Handler(onFulfilled, onRejected, prom));
+  return prom;
+};
+
+Promise.prototype['finally'] = finallyConstructor;
+
+Promise.all = function(arr) {
+  return new Promise(function(resolve, reject) {
+    if (!arr || typeof arr.length === 'undefined')
+      throw new TypeError('Promise.all accepts an array');
+    var args = Array.prototype.slice.call(arr);
+    if (args.length === 0) return resolve([]);
+    var remaining = args.length;
+
+    function res(i, val) {
+      try {
+        if (val && (typeof val === 'object' || typeof val === 'function')) {
+          var then = val.then;
+          if (typeof then === 'function') {
+            then.call(
+              val,
+              function(val) {
+                res(i, val);
+              },
+              reject
+            );
+            return;
+          }
+        }
+        args[i] = val;
+        if (--remaining === 0) {
+          resolve(args);
+        }
+      } catch (ex) {
+        reject(ex);
+      }
+    }
+
+    for (var i = 0; i < args.length; i++) {
+      res(i, args[i]);
+    }
+  });
+};
+
+Promise.resolve = function(value) {
+  if (value && typeof value === 'object' && value.constructor === Promise) {
+    return value;
+  }
+
+  return new Promise(function(resolve) {
+    resolve(value);
+  });
+};
+
+Promise.reject = function(value) {
+  return new Promise(function(resolve, reject) {
+    reject(value);
+  });
+};
+
+Promise.race = function(values) {
+  return new Promise(function(resolve, reject) {
+    for (var i = 0, len = values.length; i < len; i++) {
+      values[i].then(resolve, reject);
+    }
+  });
+};
+
+// Use polyfill for setImmediate for performance gains
+Promise._immediateFn =
+  (typeof setImmediate === 'function' &&
+    function(fn) {
+      setImmediate(fn);
+    }) ||
+  function(fn) {
+    setTimeoutFunc(fn, 0);
+  };
+
+Promise._unhandledRejectionFn = function _unhandledRejectionFn(err) {
+  if (typeof console !== 'undefined' && console) {
+    console.warn('Possible Unhandled Promise Rejection:', err); // eslint-disable-line no-console
+  }
+};
+
+/** @suppress {undefinedVars} */
+var globalNS = (function() {
+  // the only reliable means to get the global object is
+  // `Function('return this')()`
+  // However, this causes CSP violations in Chrome apps.
+  if (typeof self !== 'undefined') {
+    return self;
+  }
+  if (typeof window !== 'undefined') {
+    return window;
+  }
+  if (typeof global !== 'undefined') {
+    return global;
+  }
+  throw new Error('unable to locate global object');
+})();
+
+if (!('Promise' in globalNS)) {
+  globalNS['Promise'] = Promise;
+} else if (!globalNS.Promise.prototype['finally']) {
+  globalNS.Promise.prototype['finally'] = finallyConstructor;
+}
+
+})));
\ No newline at end of file
diff --git a/src/legacy/design-studio/js/querystring.js b/src/legacy/design-studio/js/querystring.js
new file mode 100644
index 0000000000000000000000000000000000000000..5a8e3f187c56f8e63e839b11fb7a24ccab58186e
--- /dev/null
+++ b/src/legacy/design-studio/js/querystring.js
@@ -0,0 +1,187 @@
+/* this independent library is looking for a home
+ it provides round-trip query string parsing & generating
+ Copyright 2016 Gordon Woodhull, MIT License
+ */
+
+var querystring = (function() {
+    var listsep_ = '|';
+    function read_query(type, val) {
+        switch(type) {
+        case 'boolean':
+            return val === 'true';
+        case 'number':
+            return +val;
+        case 'string':
+            return val;
+        case 'array':
+            return val.split(listsep_);
+        default: throw new Error('unsupported query type ' + type);
+        }
+    }
+
+    function write_query(type, val) {
+        switch(type) {
+        case 'array':
+            return val.join(listsep_);
+        case 'boolean':
+        case 'number':
+        case 'string':
+            return '' + val;
+        default: throw new Error('unsupported query type ' + type);
+        }
+    }
+
+    function query_type(val) {
+        return Array.isArray(val) ? 'array' : typeof val;
+    }
+
+    // we could probably depend on _, but _.pick is the only thing we need atm
+    function pick(object, fields) {
+        return fields.reduce(function(reduced, key) {
+            if(key in object)
+                reduced[key] = object[key];
+            return reduced;
+        }, {});
+    }
+
+    function create_tracker(options, domain, args) {
+        var qs = querystring.parse();
+        var settings = {};
+
+        function update_interesting(qs) {
+            var interesting = Object.keys(options)
+                    .filter(function(k) {
+                        return qs[options[k].query] !== write_query(query_type(options[k].default), options[k].default);
+                    }).map(function(k) {
+                        return options[k].query || k;
+                    });
+            var interested = pick(qs, interesting);
+            querystring.update(interested);
+        }
+
+        function do_option(key, opt, callback) {
+            settings[key] = opt.default;
+            var query = opt.query = opt.query || key;
+            var type = query_type(opt.default);
+            if(query in qs)
+                settings[key] = read_query(type, qs[query]);
+
+            function update_setting(opt, val) {
+                settings[key] = val;
+                if(opt.query) {
+                    qs[opt.query] = write_query(type, val);
+                    update_interesting(qs);
+                }
+            }
+            if(opt.selector) {
+                switch(type) {
+                case 'boolean':
+                    if(!opt.set)
+                        opt.set = function(val) {
+                            $(opt.selector)
+                                .prop('checked', val);
+                        };
+                    if(!opt.subscribe)
+                        opt.subscribe = function(k) {
+                            $(opt.selector)
+                                .change(function() {
+                                    var val = $(this).is(':checked');
+                                    k(val);
+                                });
+                        };
+                    break;
+                case 'number':
+                case 'string':
+                    if(!opt.set)
+                        opt.set = function(val) {
+                            $(opt.selector)
+                                .val(val);
+                        };
+                    if(!opt.subscribe)
+                        opt.subscribe = function(k) {
+                            $(opt.selector)
+                                .change(function() {
+                                    var val = $(this).val();
+                                    k(val);
+                                });
+                        };
+                    break;
+                default: throw new Error('unsupported selector type ' + type);
+                }
+            }
+            if(opt.set)
+                opt.set(settings[key]);
+            if(opt.subscribe)
+                opt.subscribe(function(val) {
+                    update_setting(opt, val);
+                    callback && callback(val);
+                });
+        }
+
+        for(var key in options) {
+            var callback = function(opt, val) {
+                args[0] = val;
+                if(opt.exert && !opt.dont_exert_after_subscribe)
+                    opt.exert.apply(opt, args);
+                if(domain && domain.on_exert)
+                    domain.on_exert(opt);
+            };
+            do_option(key, options[key], callback.bind(null, options[key]));
+        }
+
+        return {
+            vals: settings,
+            exert: function() {
+                for(var key in options)
+                    if(options[key].exert) {
+                        args[0] = settings[key];
+                        options[key].exert.apply(options[key], args);
+                    }
+            }
+        };
+    }
+
+    return {
+        listsep: function(s) {
+            if(!arguments.length)
+                return listsep_;
+            listsep_ = s;
+            return this;
+        },
+        parse: function(opts) {
+            opts = opts || {};
+            return (function(a) {
+                if (a == "") return {};
+                var b = {};
+                for (var i = 0; i < a.length; ++i)
+                {
+                    var p=a[i].split('=', 2);
+                    if (p.length == 1)
+                        b[p[0]] = opts.boolean ? true : "";
+                    else
+                        b[p[0]] = decodeURIComponent(p[1].replace(/\+/g, " "));
+                }
+                return b;
+            })(window.location.search.substr(1).split('&'));
+        },
+        generate: function(m) {
+            var parts = [];
+            for(var k in m)
+                parts.push(k + '=' + encodeURIComponent(m[k]));
+            return parts.length ? parts.join('&') : '';
+        },
+        update: function(m) {
+            var url = window.location.protocol + '//' + window.location.host + window.location.pathname;
+            var params = this.generate(m);
+            if(params)
+                url += '?' + params;
+            window.history.pushState(null, null, url);
+            return this;
+        },
+        option_tracker: function(options, domain /* ... arguments for exert ... */) {
+            var args = Array.prototype.slice.call(arguments, 2);
+            args.unshift(0);
+            return create_tracker(options, domain, args);
+        }
+    };
+})();
diff --git a/src/legacy/design-studio/js/queue.js b/src/legacy/design-studio/js/queue.js
new file mode 100644
index 0000000000000000000000000000000000000000..2d1020d95462106add8daa02835051d99c18afac
--- /dev/null
+++ b/src/legacy/design-studio/js/queue.js
@@ -0,0 +1,106 @@
+(function (global, factory) {
+  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+  typeof define === 'function' && define.amd ? define('queue', factory) :
+  (global.queue = factory());
+}(this, function () { 'use strict';
+
+  var slice = [].slice;
+
+  function noop() {}
+
+  var noabort = {};
+  var success = [null];
+  function newQueue(concurrency) {
+    if (!(concurrency >= 1)) throw new Error;
+
+    var q,
+        tasks = [],
+        results = [],
+        waiting = 0,
+        active = 0,
+        ended = 0,
+        starting, // inside a synchronous task callback?
+        error,
+        callback = noop,
+        callbackAll = true;
+
+    function start() {
+      if (starting) return; // let the current task complete
+      while (starting = waiting && active < concurrency) {
+        var i = ended + active,
+            t = tasks[i],
+            j = t.length - 1,
+            c = t[j];
+        t[j] = end(i);
+        --waiting, ++active, tasks[i] = c.apply(null, t) || noabort;
+      }
+    }
+
+    function end(i) {
+      return function(e, r) {
+        if (!tasks[i]) throw new Error; // detect multiple callbacks
+        --active, ++ended, tasks[i] = null;
+        if (error != null) return; // only report the first error
+        if (e != null) {
+          abort(e);
+        } else {
+          results[i] = r;
+          if (waiting) start();
+          else if (!active) notify();
+        }
+      };
+    }
+
+    function abort(e) {
+      error = e; // ignore new tasks and squelch active callbacks
+      waiting = NaN; // stop queued tasks from starting
+      notify();
+    }
+
+    function notify() {
+      if (error != null) callback(error);
+      else if (callbackAll) callback(null, results);
+      else callback.apply(null, success.concat(results));
+    }
+
+    return q = {
+      defer: function(f) {
+        if (callback !== noop) throw new Error;
+        var t = slice.call(arguments, 1);
+        t.push(f);
+        ++waiting, tasks.push(t);
+        start();
+        return q;
+      },
+      abort: function() {
+        if (error == null) {
+          var i = ended + active, t;
+          while (--i >= 0) (t = tasks[i]) && t.abort && t.abort();
+          abort(new Error("abort"));
+        }
+        return q;
+      },
+      await: function(f) {
+        if (callback !== noop) throw new Error;
+        callback = f, callbackAll = false;
+        if (!waiting && !active) notify();
+        return q;
+      },
+      awaitAll: function(f) {
+        if (callback !== noop) throw new Error;
+        callback = f, callbackAll = true;
+        if (!waiting && !active) notify();
+        return q;
+      }
+    };
+  }
+
+  function queue(concurrency) {
+    return newQueue(arguments.length ? +concurrency : Infinity);
+  }
+
+  queue.version = "1.2.1";
+
+  return queue;
+
+}));
\ No newline at end of file
diff --git a/src/legacy/design-studio/js/tether.js b/src/legacy/design-studio/js/tether.js
new file mode 100644
index 0000000000000000000000000000000000000000..ea141f01d08d7176e4582b20b4fb080187f088bc
--- /dev/null
+++ b/src/legacy/design-studio/js/tether.js
@@ -0,0 +1,1811 @@
+/*! tether 1.4.0 */
+
+(function(root, factory) {
+  if (typeof define === 'function' && define.amd) {
+    define(factory);
+  } else if (typeof exports === 'object') {
+    module.exports = factory(require, exports, module);
+  } else {
+    root.Tether = factory();
+  }
+}(this, function(require, exports, module) {
+
+'use strict';
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+var TetherBase = undefined;
+if (typeof TetherBase === 'undefined') {
+  TetherBase = { modules: [] };
+}
+
+var zeroElement = null;
+
+// Same as native getBoundingClientRect, except it takes into account parent <frame> offsets
+// if the element lies within a nested document (<frame> or <iframe>-like).
+function getActualBoundingClientRect(node) {
+  var boundingRect = node.getBoundingClientRect();
+
+  // The original object returned by getBoundingClientRect is immutable, so we clone it
+  // We can't use extend because the properties are not considered part of the object by hasOwnProperty in IE9
+  var rect = {};
+  for (var k in boundingRect) {
+    rect[k] = boundingRect[k];
+  }
+
+  if (node.ownerDocument !== document) {
+    var _frameElement = node.ownerDocument.defaultView.frameElement;
+    if (_frameElement) {
+      var frameRect = getActualBoundingClientRect(_frameElement);
+      rect.top += frameRect.top;
+      rect.bottom += frameRect.top;
+      rect.left += frameRect.left;
+      rect.right += frameRect.left;
+    }
+  }
+
+  return rect;
+}
+
+function getScrollParents(el) {
+  // In firefox if the el is inside an iframe with display: none; window.getComputedStyle() will return null;
+  // https://bugzilla.mozilla.org/show_bug.cgi?id=548397
+  var computedStyle = getComputedStyle(el) || {};
+  var position = computedStyle.position;
+  var parents = [];
+
+  if (position === 'fixed') {
+    return [el];
+  }
+
+  var parent = el;
+  while ((parent = parent.parentNode) && parent && parent.nodeType === 1) {
+    var style = undefined;
+    try {
+      style = getComputedStyle(parent);
+    } catch (err) {}
+
+    if (typeof style === 'undefined' || style === null) {
+      parents.push(parent);
+      return parents;
+    }
+
+    var _style = style;
+    var overflow = _style.overflow;
+    var overflowX = _style.overflowX;
+    var overflowY = _style.overflowY;
+
+    if (/(auto|scroll)/.test(overflow + overflowY + overflowX)) {
+      if (position !== 'absolute' || ['relative', 'absolute', 'fixed'].indexOf(style.position) >= 0) {
+        parents.push(parent);
+      }
+    }
+  }
+
+  parents.push(el.ownerDocument.body);
+
+  // If the node is within a frame, account for the parent window scroll
+  if (el.ownerDocument !== document) {
+    parents.push(el.ownerDocument.defaultView);
+  }
+
+  return parents;
+}
+
+var uniqueId = (function () {
+  var id = 0;
+  return function () {
+    return ++id;
+  };
+})();
+
+var zeroPosCache = {};
+var getOrigin = function getOrigin() {
+  // getBoundingClientRect is unfortunately too accurate.  It introduces a pixel or two of
+  // jitter as the user scrolls that messes with our ability to detect if two positions
+  // are equivilant or not.  We place an element at the top left of the page that will
+  // get the same jitter, so we can cancel the two out.
+  var node = zeroElement;
+  if (!node || !document.body.contains(node)) {
+    node = document.createElement('div');
+    node.setAttribute('data-tether-id', uniqueId());
+    extend(node.style, {
+      top: 0,
+      left: 0,
+      position: 'absolute'
+    });
+
+    document.body.appendChild(node);
+
+    zeroElement = node;
+  }
+
+  var id = node.getAttribute('data-tether-id');
+  if (typeof zeroPosCache[id] === 'undefined') {
+    zeroPosCache[id] = getActualBoundingClientRect(node);
+
+    // Clear the cache when this position call is done
+    defer(function () {
+      delete zeroPosCache[id];
+    });
+  }
+
+  return zeroPosCache[id];
+};
+
+function removeUtilElements() {
+  if (zeroElement) {
+    document.body.removeChild(zeroElement);
+  }
+  zeroElement = null;
+};
+
+function getBounds(el) {
+  var doc = undefined;
+  if (el === document) {
+    doc = document;
+    el = document.documentElement;
+  } else {
+    doc = el.ownerDocument;
+  }
+
+  var docEl = doc.documentElement;
+
+  var box = getActualBoundingClientRect(el);
+
+  var origin = getOrigin();
+
+  box.top -= origin.top;
+  box.left -= origin.left;
+
+  if (typeof box.width === 'undefined') {
+    box.width = document.body.scrollWidth - box.left - box.right;
+  }
+  if (typeof box.height === 'undefined') {
+    box.height = document.body.scrollHeight - box.top - box.bottom;
+  }
+
+  box.top = box.top - docEl.clientTop;
+  box.left = box.left - docEl.clientLeft;
+  box.right = doc.body.clientWidth - box.width - box.left;
+  box.bottom = doc.body.clientHeight - box.height - box.top;
+
+  return box;
+}
+
+function getOffsetParent(el) {
+  return el.offsetParent || document.documentElement;
+}
+
+var _scrollBarSize = null;
+function getScrollBarSize() {
+  if (_scrollBarSize) {
+    return _scrollBarSize;
+  }
+  var inner = document.createElement('div');
+  inner.style.width = '100%';
+  inner.style.height = '200px';
+
+  var outer = document.createElement('div');
+  extend(outer.style, {
+    position: 'absolute',
+    top: 0,
+    left: 0,
+    pointerEvents: 'none',
+    visibility: 'hidden',
+    width: '200px',
+    height: '150px',
+    overflow: 'hidden'
+  });
+
+  outer.appendChild(inner);
+
+  document.body.appendChild(outer);
+
+  var widthContained = inner.offsetWidth;
+  outer.style.overflow = 'scroll';
+  var widthScroll = inner.offsetWidth;
+
+  if (widthContained === widthScroll) {
+    widthScroll = outer.clientWidth;
+  }
+
+  document.body.removeChild(outer);
+
+  var width = widthContained - widthScroll;
+
+  _scrollBarSize = { width: width, height: width };
+  return _scrollBarSize;
+}
+
+function extend() {
+  var out = arguments.length <= 0 || arguments[0] === undefined ? {} : arguments[0];
+
+  var args = [];
+
+  Array.prototype.push.apply(args, arguments);
+
+  args.slice(1).forEach(function (obj) {
+    if (obj) {
+      for (var key in obj) {
+        if (({}).hasOwnProperty.call(obj, key)) {
+          out[key] = obj[key];
+        }
+      }
+    }
+  });
+
+  return out;
+}
+
+function removeClass(el, name) {
+  if (typeof el.classList !== 'undefined') {
+    name.split(' ').forEach(function (cls) {
+      if (cls.trim()) {
+        el.classList.remove(cls);
+      }
+    });
+  } else {
+    var regex = new RegExp('(^| )' + name.split(' ').join('|') + '( |$)', 'gi');
+    var className = getClassName(el).replace(regex, ' ');
+    setClassName(el, className);
+  }
+}
+
+function addClass(el, name) {
+  if (typeof el.classList !== 'undefined') {
+    name.split(' ').forEach(function (cls) {
+      if (cls.trim()) {
+        el.classList.add(cls);
+      }
+    });
+  } else {
+    removeClass(el, name);
+    var cls = getClassName(el) + (' ' + name);
+    setClassName(el, cls);
+  }
+}
+
+function hasClass(el, name) {
+  if (typeof el.classList !== 'undefined') {
+    return el.classList.contains(name);
+  }
+  var className = getClassName(el);
+  return new RegExp('(^| )' + name + '( |$)', 'gi').test(className);
+}
+
+function getClassName(el) {
+  // Can't use just SVGAnimatedString here since nodes within a Frame in IE have
+  // completely separately SVGAnimatedString base classes
+  if (el.className instanceof el.ownerDocument.defaultView.SVGAnimatedString) {
+    return el.className.baseVal;
+  }
+  return el.className;
+}
+
+function setClassName(el, className) {
+  el.setAttribute('class', className);
+}
+
+function updateClasses(el, add, all) {
+  // Of the set of 'all' classes, we need the 'add' classes, and only the
+  // 'add' classes to be set.
+  all.forEach(function (cls) {
+    if (add.indexOf(cls) === -1 && hasClass(el, cls)) {
+      removeClass(el, cls);
+    }
+  });
+
+  add.forEach(function (cls) {
+    if (!hasClass(el, cls)) {
+      addClass(el, cls);
+    }
+  });
+}
+
+var deferred = [];
+
+var defer = function defer(fn) {
+  deferred.push(fn);
+};
+
+var flush = function flush() {
+  var fn = undefined;
+  while (fn = deferred.pop()) {
+    fn();
+  }
+};
+
+var Evented = (function () {
+  function Evented() {
+    _classCallCheck(this, Evented);
+  }
+
+  _createClass(Evented, [{
+    key: 'on',
+    value: function on(event, handler, ctx) {
+      var once = arguments.length <= 3 || arguments[3] === undefined ? false : arguments[3];
+
+      if (typeof this.bindings === 'undefined') {
+        this.bindings = {};
+      }
+      if (typeof this.bindings[event] === 'undefined') {
+        this.bindings[event] = [];
+      }
+      this.bindings[event].push({ handler: handler, ctx: ctx, once: once });
+    }
+  }, {
+    key: 'once',
+    value: function once(event, handler, ctx) {
+      this.on(event, handler, ctx, true);
+    }
+  }, {
+    key: 'off',
+    value: function off(event, handler) {
+      if (typeof this.bindings === 'undefined' || typeof this.bindings[event] === 'undefined') {
+        return;
+      }
+
+      if (typeof handler === 'undefined') {
+        delete this.bindings[event];
+      } else {
+        var i = 0;
+        while (i < this.bindings[event].length) {
+          if (this.bindings[event][i].handler === handler) {
+            this.bindings[event].splice(i, 1);
+          } else {
+            ++i;
+          }
+        }
+      }
+    }
+  }, {
+    key: 'trigger',
+    value: function trigger(event) {
+      if (typeof this.bindings !== 'undefined' && this.bindings[event]) {
+        var i = 0;
+
+        for (var _len = arguments.length, args = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
+          args[_key - 1] = arguments[_key];
+        }
+
+        while (i < this.bindings[event].length) {
+          var _bindings$event$i = this.bindings[event][i];
+          var handler = _bindings$event$i.handler;
+          var ctx = _bindings$event$i.ctx;
+          var once = _bindings$event$i.once;
+
+          var context = ctx;
+          if (typeof context === 'undefined') {
+            context = this;
+          }
+
+          handler.apply(context, args);
+
+          if (once) {
+            this.bindings[event].splice(i, 1);
+          } else {
+            ++i;
+          }
+        }
+      }
+    }
+  }]);
+
+  return Evented;
+})();
+
+TetherBase.Utils = {
+  getActualBoundingClientRect: getActualBoundingClientRect,
+  getScrollParents: getScrollParents,
+  getBounds: getBounds,
+  getOffsetParent: getOffsetParent,
+  extend: extend,
+  addClass: addClass,
+  removeClass: removeClass,
+  hasClass: hasClass,
+  updateClasses: updateClasses,
+  defer: defer,
+  flush: flush,
+  uniqueId: uniqueId,
+  Evented: Evented,
+  getScrollBarSize: getScrollBarSize,
+  removeUtilElements: removeUtilElements
+};
+/* globals TetherBase, performance */
+
+'use strict';
+
+var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })();
+
+var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+
+var _get = function get(_x6, _x7, _x8) { var _again = true; _function: while (_again) { var object = _x6, property = _x7, receiver = _x8; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x6 = parent; _x7 = property; _x8 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+
+function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+
+if (typeof TetherBase === 'undefined') {
+  throw new Error('You must include the utils.js file before tether.js');
+}
+
+var _TetherBase$Utils = TetherBase.Utils;
+var getScrollParents = _TetherBase$Utils.getScrollParents;
+var getBounds = _TetherBase$Utils.getBounds;
+var getOffsetParent = _TetherBase$Utils.getOffsetParent;
+var extend = _TetherBase$Utils.extend;
+var addClass = _TetherBase$Utils.addClass;
+var removeClass = _TetherBase$Utils.removeClass;
+var updateClasses = _TetherBase$Utils.updateClasses;
+var defer = _TetherBase$Utils.defer;
+var flush = _TetherBase$Utils.flush;
+var getScrollBarSize = _TetherBase$Utils.getScrollBarSize;
+var removeUtilElements = _TetherBase$Utils.removeUtilElements;
+
+function within(a, b) {
+  var diff = arguments.length <= 2 || arguments[2] === undefined ? 1 : arguments[2];
+
+  return a + diff >= b && b >= a - diff;
+}
+
+var transformKey = (function () {
+  if (typeof document === 'undefined') {
+    return '';
+  }
+  var el = document.createElement('div');
+
+  var transforms = ['transform', 'WebkitTransform', 'OTransform', 'MozTransform', 'msTransform'];
+  for (var i = 0; i < transforms.length; ++i) {
+    var key = transforms[i];
+    if (el.style[key] !== undefined) {
+      return key;
+    }
+  }
+})();
+
+var tethers = [];
+
+var position = function position() {
+  tethers.forEach(function (tether) {
+    tether.position(false);
+  });
+  flush();
+};
+
+function now() {
+  if (typeof performance !== 'undefined' && typeof performance.now !== 'undefined') {
+    return performance.now();
+  }
+  return +new Date();
+}
+
+(function () {
+  var lastCall = null;
+  var lastDuration = null;
+  var pendingTimeout = null;
+
+  var tick = function tick() {
+    if (typeof lastDuration !== 'undefined' && lastDuration > 16) {
+      // We voluntarily throttle ourselves if we can't manage 60fps
+      lastDuration = Math.min(lastDuration - 16, 250);
+
+      // Just in case this is the last event, remember to position just once more
+      pendingTimeout = setTimeout(tick, 250);
+      return;
+    }
+
+    if (typeof lastCall !== 'undefined' && now() - lastCall < 10) {
+      // Some browsers call events a little too frequently, refuse to run more than is reasonable
+      return;
+    }
+
+    if (pendingTimeout != null) {
+      clearTimeout(pendingTimeout);
+      pendingTimeout = null;
+    }
+
+    lastCall = now();
+    position();
+    lastDuration = now() - lastCall;
+  };
+
+  if (typeof window !== 'undefined' && typeof window.addEventListener !== 'undefined') {
+    ['resize', 'scroll', 'touchmove'].forEach(function (event) {
+      window.addEventListener(event, tick);
+    });
+  }
+})();
+
+var MIRROR_LR = {
+  center: 'center',
+  left: 'right',
+  right: 'left'
+};
+
+var MIRROR_TB = {
+  middle: 'middle',
+  top: 'bottom',
+  bottom: 'top'
+};
+
+var OFFSET_MAP = {
+  top: 0,
+  left: 0,
+  middle: '50%',
+  center: '50%',
+  bottom: '100%',
+  right: '100%'
+};
+
+var autoToFixedAttachment = function autoToFixedAttachment(attachment, relativeToAttachment) {
+  var left = attachment.left;
+  var top = attachment.top;
+
+  if (left === 'auto') {
+    left = MIRROR_LR[relativeToAttachment.left];
+  }
+
+  if (top === 'auto') {
+    top = MIRROR_TB[relativeToAttachment.top];
+  }
+
+  return { left: left, top: top };
+};
+
+var attachmentToOffset = function attachmentToOffset(attachment) {
+  var left = attachment.left;
+  var top = attachment.top;
+
+  if (typeof OFFSET_MAP[attachment.left] !== 'undefined') {
+    left = OFFSET_MAP[attachment.left];
+  }
+
+  if (typeof OFFSET_MAP[attachment.top] !== 'undefined') {
+    top = OFFSET_MAP[attachment.top];
+  }
+
+  return { left: left, top: top };
+};
+
+function addOffset() {
+  var out = { top: 0, left: 0 };
+
+  for (var _len = arguments.length, offsets = Array(_len), _key = 0; _key < _len; _key++) {
+    offsets[_key] = arguments[_key];
+  }
+
+  offsets.forEach(function (_ref) {
+    var top = _ref.top;
+    var left = _ref.left;
+
+    if (typeof top === 'string') {
+      top = parseFloat(top, 10);
+    }
+    if (typeof left === 'string') {
+      left = parseFloat(left, 10);
+    }
+
+    out.top += top;
+    out.left += left;
+  });
+
+  return out;
+}
+
+function offsetToPx(offset, size) {
+  if (typeof offset.left === 'string' && offset.left.indexOf('%') !== -1) {
+    offset.left = parseFloat(offset.left, 10) / 100 * size.width;
+  }
+  if (typeof offset.top === 'string' && offset.top.indexOf('%') !== -1) {
+    offset.top = parseFloat(offset.top, 10) / 100 * size.height;
+  }
+
+  return offset;
+}
+
+var parseOffset = function parseOffset(value) {
+  var _value$split = value.split(' ');
+
+  var _value$split2 = _slicedToArray(_value$split, 2);
+
+  var top = _value$split2[0];
+  var left = _value$split2[1];
+
+  return { top: top, left: left };
+};
+var parseAttachment = parseOffset;
+
+var TetherClass = (function (_Evented) {
+  _inherits(TetherClass, _Evented);
+
+  function TetherClass(options) {
+    var _this = this;
+
+    _classCallCheck(this, TetherClass);
+
+    _get(Object.getPrototypeOf(TetherClass.prototype), 'constructor', this).call(this);
+    this.position = this.position.bind(this);
+
+    tethers.push(this);
+
+    this.history = [];
+
+    this.setOptions(options, false);
+
+    TetherBase.modules.forEach(function (module) {
+      if (typeof module.initialize !== 'undefined') {
+        module.initialize.call(_this);
+      }
+    });
+
+    this.position();
+  }
+
+  _createClass(TetherClass, [{
+    key: 'getClass',
+    value: function getClass() {
+      var key = arguments.length <= 0 || arguments[0] === undefined ? '' : arguments[0];
+      var classes = this.options.classes;
+
+      if (typeof classes !== 'undefined' && classes[key]) {
+        return this.options.classes[key];
+      } else if (this.options.classPrefix) {
+        return this.options.classPrefix + '-' + key;
+      } else {
+        return key;
+      }
+    }
+  }, {
+    key: 'setOptions',
+    value: function setOptions(options) {
+      var _this2 = this;
+
+      var pos = arguments.length <= 1 || arguments[1] === undefined ? true : arguments[1];
+
+      var defaults = {
+        offset: '0 0',
+        targetOffset: '0 0',
+        targetAttachment: 'auto auto',
+        classPrefix: 'tether'
+      };
+
+      this.options = extend(defaults, options);
+
+      var _options = this.options;
+      var element = _options.element;
+      var target = _options.target;
+      var targetModifier = _options.targetModifier;
+
+      this.element = element;
+      this.target = target;
+      this.targetModifier = targetModifier;
+
+      if (this.target === 'viewport') {
+        this.target = document.body;
+        this.targetModifier = 'visible';
+      } else if (this.target === 'scroll-handle') {
+        this.target = document.body;
+        this.targetModifier = 'scroll-handle';
+      }
+
+      ['element', 'target'].forEach(function (key) {
+        if (typeof _this2[key] === 'undefined') {
+          throw new Error('Tether Error: Both element and target must be defined');
+        }
+
+        if (typeof _this2[key].jquery !== 'undefined') {
+          _this2[key] = _this2[key][0];
+        } else if (typeof _this2[key] === 'string') {
+          _this2[key] = document.querySelector(_this2[key]);
+        }
+      });
+
+      addClass(this.element, this.getClass('element'));
+      if (!(this.options.addTargetClasses === false)) {
+        addClass(this.target, this.getClass('target'));
+      }
+
+      if (!this.options.attachment) {
+        throw new Error('Tether Error: You must provide an attachment');
+      }
+
+      this.targetAttachment = parseAttachment(this.options.targetAttachment);
+      this.attachment = parseAttachment(this.options.attachment);
+      this.offset = parseOffset(this.options.offset);
+      this.targetOffset = parseOffset(this.options.targetOffset);
+
+      if (typeof this.scrollParents !== 'undefined') {
+        this.disable();
+      }
+
+      if (this.targetModifier === 'scroll-handle') {
+        this.scrollParents = [this.target];
+      } else {
+        this.scrollParents = getScrollParents(this.target);
+      }
+
+      if (!(this.options.enabled === false)) {
+        this.enable(pos);
+      }
+    }
+  }, {
+    key: 'getTargetBounds',
+    value: function getTargetBounds() {
+      if (typeof this.targetModifier !== 'undefined') {
+        if (this.targetModifier === 'visible') {
+          if (this.target === document.body) {
+            return { top: pageYOffset, left: pageXOffset, height: innerHeight, width: innerWidth };
+          } else {
+            var bounds = getBounds(this.target);
+
+            var out = {
+              height: bounds.height,
+              width: bounds.width,
+              top: bounds.top,
+              left: bounds.left
+            };
+
+            out.height = Math.min(out.height, bounds.height - (pageYOffset - bounds.top));
+            out.height = Math.min(out.height, bounds.height - (bounds.top + bounds.height - (pageYOffset + innerHeight)));
+            out.height = Math.min(innerHeight, out.height);
+            out.height -= 2;
+
+            out.width = Math.min(out.width, bounds.width - (pageXOffset - bounds.left));
+            out.width = Math.min(out.width, bounds.width - (bounds.left + bounds.width - (pageXOffset + innerWidth)));
+            out.width = Math.min(innerWidth, out.width);
+            out.width -= 2;
+
+            if (out.top < pageYOffset) {
+              out.top = pageYOffset;
+            }
+            if (out.left < pageXOffset) {
+              out.left = pageXOffset;
+            }
+
+            return out;
+          }
+        } else if (this.targetModifier === 'scroll-handle') {
+          var bounds = undefined;
+          var target = this.target;
+          if (target === document.body) {
+            target = document.documentElement;
+
+            bounds = {
+              left: pageXOffset,
+              top: pageYOffset,
+              height: innerHeight,
+              width: innerWidth
+            };
+          } else {
+            bounds = getBounds(target);
+          }
+
+          var style = getComputedStyle(target);
+
+          var hasBottomScroll = target.scrollWidth > target.clientWidth || [style.overflow, style.overflowX].indexOf('scroll') >= 0 || this.target !== document.body;
+
+          var scrollBottom = 0;
+          if (hasBottomScroll) {
+            scrollBottom = 15;
+          }
+
+          var height = bounds.height - parseFloat(style.borderTopWidth) - parseFloat(style.borderBottomWidth) - scrollBottom;
+
+          var out = {
+            width: 15,
+            height: height * 0.975 * (height / target.scrollHeight),
+            left: bounds.left + bounds.width - parseFloat(style.borderLeftWidth) - 15
+          };
+
+          var fitAdj = 0;
+          if (height < 408 && this.target === document.body) {
+            fitAdj = -0.00011 * Math.pow(height, 2) - 0.00727 * height + 22.58;
+          }
+
+          if (this.target !== document.body) {
+            out.height = Math.max(out.height, 24);
+          }
+
+          var scrollPercentage = this.target.scrollTop / (target.scrollHeight - height);
+          out.top = scrollPercentage * (height - out.height - fitAdj) + bounds.top + parseFloat(style.borderTopWidth);
+
+          if (this.target === document.body) {
+            out.height = Math.max(out.height, 24);
+          }
+
+          return out;
+        }
+      } else {
+        return getBounds(this.target);
+      }
+    }
+  }, {
+    key: 'clearCache',
+    value: function clearCache() {
+      this._cache = {};
+    }
+  }, {
+    key: 'cache',
+    value: function cache(k, getter) {
+      // More than one module will often need the same DOM info, so
+      // we keep a cache which is cleared on each position call
+      if (typeof this._cache === 'undefined') {
+        this._cache = {};
+      }
+
+      if (typeof this._cache[k] === 'undefined') {
+        this._cache[k] = getter.call(this);
+      }
+
+      return this._cache[k];
+    }
+  }, {
+    key: 'enable',
+    value: function enable() {
+      var _this3 = this;
+
+      var pos = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
+
+      if (!(this.options.addTargetClasses === false)) {
+        addClass(this.target, this.getClass('enabled'));
+      }
+      addClass(this.element, this.getClass('enabled'));
+      this.enabled = true;
+
+      this.scrollParents.forEach(function (parent) {
+        if (parent !== _this3.target.ownerDocument) {
+          parent.addEventListener('scroll', _this3.position);
+        }
+      });
+
+      if (pos) {
+        this.position();
+      }
+    }
+  }, {
+    key: 'disable',
+    value: function disable() {
+      var _this4 = this;
+
+      removeClass(this.target, this.getClass('enabled'));
+      removeClass(this.element, this.getClass('enabled'));
+      this.enabled = false;
+
+      if (typeof this.scrollParents !== 'undefined') {
+        this.scrollParents.forEach(function (parent) {
+          parent.removeEventListener('scroll', _this4.position);
+        });
+      }
+    }
+  }, {
+    key: 'destroy',
+    value: function destroy() {
+      var _this5 = this;
+
+      this.disable();
+
+      tethers.forEach(function (tether, i) {
+        if (tether === _this5) {
+          tethers.splice(i, 1);
+        }
+      });
+
+      // Remove any elements we were using for convenience from the DOM
+      if (tethers.length === 0) {
+        removeUtilElements();
+      }
+    }
+  }, {
+    key: 'updateAttachClasses',
+    value: function updateAttachClasses(elementAttach, targetAttach) {
+      var _this6 = this;
+
+      elementAttach = elementAttach || this.attachment;
+      targetAttach = targetAttach || this.targetAttachment;
+      var sides = ['left', 'top', 'bottom', 'right', 'middle', 'center'];
+
+      if (typeof this._addAttachClasses !== 'undefined' && this._addAttachClasses.length) {
+        // updateAttachClasses can be called more than once in a position call, so
+        // we need to clean up after ourselves such that when the last defer gets
+        // ran it doesn't add any extra classes from previous calls.
+        this._addAttachClasses.splice(0, this._addAttachClasses.length);
+      }
+
+      if (typeof this._addAttachClasses === 'undefined') {
+        this._addAttachClasses = [];
+      }
+      var add = this._addAttachClasses;
+
+      if (elementAttach.top) {
+        add.push(this.getClass('element-attached') + '-' + elementAttach.top);
+      }
+      if (elementAttach.left) {
+        add.push(this.getClass('element-attached') + '-' + elementAttach.left);
+      }
+      if (targetAttach.top) {
+        add.push(this.getClass('target-attached') + '-' + targetAttach.top);
+      }
+      if (targetAttach.left) {
+        add.push(this.getClass('target-attached') + '-' + targetAttach.left);
+      }
+
+      var all = [];
+      sides.forEach(function (side) {
+        all.push(_this6.getClass('element-attached') + '-' + side);
+        all.push(_this6.getClass('target-attached') + '-' + side);
+      });
+
+      defer(function () {
+        if (!(typeof _this6._addAttachClasses !== 'undefined')) {
+          return;
+        }
+
+        updateClasses(_this6.element, _this6._addAttachClasses, all);
+        if (!(_this6.options.addTargetClasses === false)) {
+          updateClasses(_this6.target, _this6._addAttachClasses, all);
+        }
+
+        delete _this6._addAttachClasses;
+      });
+    }
+  }, {
+    key: 'position',
+    value: function position() {
+      var _this7 = this;
+
+      var flushChanges = arguments.length <= 0 || arguments[0] === undefined ? true : arguments[0];
+
+      // flushChanges commits the changes immediately, leave true unless you are positioning multiple
+      // tethers (in which case call Tether.Utils.flush yourself when you're done)
+
+      if (!this.enabled) {
+        return;
+      }
+
+      this.clearCache();
+
+      // Turn 'auto' attachments into the appropriate corner or edge
+      var targetAttachment = autoToFixedAttachment(this.targetAttachment, this.attachment);
+
+      this.updateAttachClasses(this.attachment, targetAttachment);
+
+      var elementPos = this.cache('element-bounds', function () {
+        return getBounds(_this7.element);
+      });
+
+      var width = elementPos.width;
+      var height = elementPos.height;
+
+      if (width === 0 && height === 0 && typeof this.lastSize !== 'undefined') {
+        var _lastSize = this.lastSize;
+
+        // We cache the height and width to make it possible to position elements that are
+        // getting hidden.
+        width = _lastSize.width;
+        height = _lastSize.height;
+      } else {
+        this.lastSize = { width: width, height: height };
+      }
+
+      var targetPos = this.cache('target-bounds', function () {
+        return _this7.getTargetBounds();
+      });
+      var targetSize = targetPos;
+
+      // Get an actual px offset from the attachment
+      var offset = offsetToPx(attachmentToOffset(this.attachment), { width: width, height: height });
+      var targetOffset = offsetToPx(attachmentToOffset(targetAttachment), targetSize);
+
+      var manualOffset = offsetToPx(this.offset, { width: width, height: height });
+      var manualTargetOffset = offsetToPx(this.targetOffset, targetSize);
+
+      // Add the manually provided offset
+      offset = addOffset(offset, manualOffset);
+      targetOffset = addOffset(targetOffset, manualTargetOffset);
+
+      // It's now our goal to make (element position + offset) == (target position + target offset)
+      var left = targetPos.left + targetOffset.left - offset.left;
+      var top = targetPos.top + targetOffset.top - offset.top;
+
+      for (var i = 0; i < TetherBase.modules.length; ++i) {
+        var _module2 = TetherBase.modules[i];
+        var ret = _module2.position.call(this, {
+          left: left,
+          top: top,
+          targetAttachment: targetAttachment,
+          targetPos: targetPos,
+          elementPos: elementPos,
+          offset: offset,
+          targetOffset: targetOffset,
+          manualOffset: manualOffset,
+          manualTargetOffset: manualTargetOffset,
+          scrollbarSize: scrollbarSize,
+          attachment: this.attachment
+        });
+
+        if (ret === false) {
+          return false;
+        } else if (typeof ret === 'undefined' || typeof ret !== 'object') {
+          continue;
+        } else {
+          top = ret.top;
+          left = ret.left;
+        }
+      }
+
+      // We describe the position three different ways to give the optimizer
+      // a chance to decide the best possible way to position the element
+      // with the fewest repaints.
+      var next = {
+        // It's position relative to the page (absolute positioning when
+        // the element is a child of the body)
+        page: {
+          top: top,
+          left: left
+        },
+
+        // It's position relative to the viewport (fixed positioning)
+        viewport: {
+          top: top - pageYOffset,
+          bottom: pageYOffset - top - height + innerHeight,
+          left: left - pageXOffset,
+          right: pageXOffset - left - width + innerWidth
+        }
+      };
+
+      var doc = this.target.ownerDocument;
+      var win = doc.defaultView;
+
+      var scrollbarSize = undefined;
+      if (win.innerHeight > doc.documentElement.clientHeight) {
+        scrollbarSize = this.cache('scrollbar-size', getScrollBarSize);
+        next.viewport.bottom -= scrollbarSize.height;
+      }
+
+      if (win.innerWidth > doc.documentElement.clientWidth) {
+        scrollbarSize = this.cache('scrollbar-size', getScrollBarSize);
+        next.viewport.right -= scrollbarSize.width;
+      }
+
+      if (['', 'static'].indexOf(doc.body.style.position) === -1 || ['', 'static'].indexOf(doc.body.parentElement.style.position) === -1) {
+        // Absolute positioning in the body will be relative to the page, not the 'initial containing block'
+        next.page.bottom = doc.body.scrollHeight - top - height;
+        next.page.right = doc.body.scrollWidth - left - width;
+      }
+
+      if (typeof this.options.optimizations !== 'undefined' && this.options.optimizations.moveElement !== false && !(typeof this.targetModifier !== 'undefined')) {
+        (function () {
+          var offsetParent = _this7.cache('target-offsetparent', function () {
+            return getOffsetParent(_this7.target);
+          });
+          var offsetPosition = _this7.cache('target-offsetparent-bounds', function () {
+            return getBounds(offsetParent);
+          });
+          var offsetParentStyle = getComputedStyle(offsetParent);
+          var offsetParentSize = offsetPosition;
+
+          var offsetBorder = {};
+          ['Top', 'Left', 'Bottom', 'Right'].forEach(function (side) {
+            offsetBorder[side.toLowerCase()] = parseFloat(offsetParentStyle['border' + side + 'Width']);
+          });
+
+          offsetPosition.right = doc.body.scrollWidth - offsetPosition.left - offsetParentSize.width + offsetBorder.right;
+          offsetPosition.bottom = doc.body.scrollHeight - offsetPosition.top - offsetParentSize.height + offsetBorder.bottom;
+
+          if (next.page.top >= offsetPosition.top + offsetBorder.top && next.page.bottom >= offsetPosition.bottom) {
+            if (next.page.left >= offsetPosition.left + offsetBorder.left && next.page.right >= offsetPosition.right) {
+              // We're within the visible part of the target's scroll parent
+              var scrollTop = offsetParent.scrollTop;
+              var scrollLeft = offsetParent.scrollLeft;
+
+              // It's position relative to the target's offset parent (absolute positioning when
+              // the element is moved to be a child of the target's offset parent).
+              next.offset = {
+                top: next.page.top - offsetPosition.top + scrollTop - offsetBorder.top,
+                left: next.page.left - offsetPosition.left + scrollLeft - offsetBorder.left
+              };
+            }
+          }
+        })();
+      }
+
+      // We could also travel up the DOM and try each containing context, rather than only
+      // looking at the body, but we're gonna get diminishing returns.
+
+      this.move(next);
+
+      this.history.unshift(next);
+
+      if (this.history.length > 3) {
+        this.history.pop();
+      }
+
+      if (flushChanges) {
+        flush();
+      }
+
+      return true;
+    }
+
+    // THE ISSUE
+  }, {
+    key: 'move',
+    value: function move(pos) {
+      var _this8 = this;
+
+      if (!(typeof this.element.parentNode !== 'undefined')) {
+        return;
+      }
+
+      var same = {};
+
+      for (var type in pos) {
+        same[type] = {};
+
+        for (var key in pos[type]) {
+          var found = false;
+
+          for (var i = 0; i < this.history.length; ++i) {
+            var point = this.history[i];
+            if (typeof point[type] !== 'undefined' && !within(point[type][key], pos[type][key])) {
+              found = true;
+              break;
+            }
+          }
+
+          if (!found) {
+            same[type][key] = true;
+          }
+        }
+      }
+
+      var css = { top: '', left: '', right: '', bottom: '' };
+
+      var transcribe = function transcribe(_same, _pos) {
+        var hasOptimizations = typeof _this8.options.optimizations !== 'undefined';
+        var gpu = hasOptimizations ? _this8.options.optimizations.gpu : null;
+        if (gpu !== false) {
+          var yPos = undefined,
+              xPos = undefined;
+          if (_same.top) {
+            css.top = 0;
+            yPos = _pos.top;
+          } else {
+            css.bottom = 0;
+            yPos = -_pos.bottom;
+          }
+
+          if (_same.left) {
+            css.left = 0;
+            xPos = _pos.left;
+          } else {
+            css.right = 0;
+            xPos = -_pos.right;
+          }
+
+          if (window.matchMedia) {
+            // HubSpot/tether#207
+            var retina = window.matchMedia('only screen and (min-resolution: 1.3dppx)').matches || window.matchMedia('only screen and (-webkit-min-device-pixel-ratio: 1.3)').matches;
+            if (!retina) {
+              xPos = Math.round(xPos);
+              yPos = Math.round(yPos);
+            }
+          }
+
+          css[transformKey] = 'translateX(' + xPos + 'px) translateY(' + yPos + 'px)';
+
+          if (transformKey !== 'msTransform') {
+            // The Z transform will keep this in the GPU (faster, and prevents artifacts),
+            // but IE9 doesn't support 3d transforms and will choke.
+            css[transformKey] += " translateZ(0)";
+          }
+        } else {
+          if (_same.top) {
+            css.top = _pos.top + 'px';
+          } else {
+            css.bottom = _pos.bottom + 'px';
+          }
+
+          if (_same.left) {
+            css.left = _pos.left + 'px';
+          } else {
+            css.right = _pos.right + 'px';
+          }
+        }
+      };
+
+      var moved = false;
+      if ((same.page.top || same.page.bottom) && (same.page.left || same.page.right)) {
+        css.position = 'absolute';
+        transcribe(same.page, pos.page);
+      } else if ((same.viewport.top || same.viewport.bottom) && (same.viewport.left || same.viewport.right)) {
+        css.position = 'fixed';
+        transcribe(same.viewport, pos.viewport);
+      } else if (typeof same.offset !== 'undefined' && same.offset.top && same.offset.left) {
+        (function () {
+          css.position = 'absolute';
+          var offsetParent = _this8.cache('target-offsetparent', function () {
+            return getOffsetParent(_this8.target);
+          });
+
+          if (getOffsetParent(_this8.element) !== offsetParent) {
+            defer(function () {
+              _this8.element.parentNode.removeChild(_this8.element);
+              offsetParent.appendChild(_this8.element);
+            });
+          }
+
+          transcribe(same.offset, pos.offset);
+          moved = true;
+        })();
+      } else {
+        css.position = 'absolute';
+        transcribe({ top: true, left: true }, pos.page);
+      }
+
+      if (!moved) {
+        if (this.options.bodyElement) {
+          this.options.bodyElement.appendChild(this.element);
+        } else {
+          var offsetParentIsBody = true;
+          var currentNode = this.element.parentNode;
+          while (currentNode && currentNode.nodeType === 1 && currentNode.tagName !== 'BODY') {
+            if (getComputedStyle(currentNode).position !== 'static') {
+              offsetParentIsBody = false;
+              break;
+            }
+
+            currentNode = currentNode.parentNode;
+          }
+
+          if (!offsetParentIsBody) {
+            this.element.parentNode.removeChild(this.element);
+            this.element.ownerDocument.body.appendChild(this.element);
+          }
+        }
+      }
+
+      // Any css change will trigger a repaint, so let's avoid one if nothing changed
+      var writeCSS = {};
+      var write = false;
+      for (var key in css) {
+        var val = css[key];
+        var elVal = this.element.style[key];
+
+        if (elVal !== val) {
+          write = true;
+          writeCSS[key] = val;
+        }
+      }
+
+      if (write) {
+        defer(function () {
+          extend(_this8.element.style, writeCSS);
+          _this8.trigger('repositioned');
+        });
+      }
+    }
+  }]);
+
+  return TetherClass;
+})(Evented);
+
+TetherClass.modules = [];
+
+TetherBase.position = position;
+
+var Tether = extend(TetherClass, TetherBase);
+/* globals TetherBase */
+
+'use strict';
+
+var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })();
+
+var _TetherBase$Utils = TetherBase.Utils;
+var getBounds = _TetherBase$Utils.getBounds;
+var extend = _TetherBase$Utils.extend;
+var updateClasses = _TetherBase$Utils.updateClasses;
+var defer = _TetherBase$Utils.defer;
+
+var BOUNDS_FORMAT = ['left', 'top', 'right', 'bottom'];
+
+function getBoundingRect(tether, to) {
+  if (to === 'scrollParent') {
+    to = tether.scrollParents[0];
+  } else if (to === 'window') {
+    to = [pageXOffset, pageYOffset, innerWidth + pageXOffset, innerHeight + pageYOffset];
+  }
+
+  if (to === document) {
+    to = to.documentElement;
+  }
+
+  if (typeof to.nodeType !== 'undefined') {
+    (function () {
+      var node = to;
+      var size = getBounds(to);
+      var pos = size;
+      var style = getComputedStyle(to);
+
+      to = [pos.left, pos.top, size.width + pos.left, size.height + pos.top];
+
+      // Account any parent Frames scroll offset
+      if (node.ownerDocument !== document) {
+        var win = node.ownerDocument.defaultView;
+        to[0] += win.pageXOffset;
+        to[1] += win.pageYOffset;
+        to[2] += win.pageXOffset;
+        to[3] += win.pageYOffset;
+      }
+
+      BOUNDS_FORMAT.forEach(function (side, i) {
+        side = side[0].toUpperCase() + side.substr(1);
+        if (side === 'Top' || side === 'Left') {
+          to[i] += parseFloat(style['border' + side + 'Width']);
+        } else {
+          to[i] -= parseFloat(style['border' + side + 'Width']);
+        }
+      });
+    })();
+  }
+
+  return to;
+}
+
+TetherBase.modules.push({
+  position: function position(_ref) {
+    var _this = this;
+
+    var top = _ref.top;
+    var left = _ref.left;
+    var targetAttachment = _ref.targetAttachment;
+
+    if (!this.options.constraints) {
+      return true;
+    }
+
+    var _cache = this.cache('element-bounds', function () {
+      return getBounds(_this.element);
+    });
+
+    var height = _cache.height;
+    var width = _cache.width;
+
+    if (width === 0 && height === 0 && typeof this.lastSize !== 'undefined') {
+      var _lastSize = this.lastSize;
+
+      // Handle the item getting hidden as a result of our positioning without glitching
+      // the classes in and out
+      width = _lastSize.width;
+      height = _lastSize.height;
+    }
+
+    var targetSize = this.cache('target-bounds', function () {
+      return _this.getTargetBounds();
+    });
+
+    var targetHeight = targetSize.height;
+    var targetWidth = targetSize.width;
+
+    var allClasses = [this.getClass('pinned'), this.getClass('out-of-bounds')];
+
+    this.options.constraints.forEach(function (constraint) {
+      var outOfBoundsClass = constraint.outOfBoundsClass;
+      var pinnedClass = constraint.pinnedClass;
+
+      if (outOfBoundsClass) {
+        allClasses.push(outOfBoundsClass);
+      }
+      if (pinnedClass) {
+        allClasses.push(pinnedClass);
+      }
+    });
+
+    allClasses.forEach(function (cls) {
+      ['left', 'top', 'right', 'bottom'].forEach(function (side) {
+        allClasses.push(cls + '-' + side);
+      });
+    });
+
+    var addClasses = [];
+
+    var tAttachment = extend({}, targetAttachment);
+    var eAttachment = extend({}, this.attachment);
+
+    this.options.constraints.forEach(function (constraint) {
+      var to = constraint.to;
+      var attachment = constraint.attachment;
+      var pin = constraint.pin;
+
+      if (typeof attachment === 'undefined') {
+        attachment = '';
+      }
+
+      var changeAttachX = undefined,
+          changeAttachY = undefined;
+      if (attachment.indexOf(' ') >= 0) {
+        var _attachment$split = attachment.split(' ');
+
+        var _attachment$split2 = _slicedToArray(_attachment$split, 2);
+
+        changeAttachY = _attachment$split2[0];
+        changeAttachX = _attachment$split2[1];
+      } else {
+        changeAttachX = changeAttachY = attachment;
+      }
+
+      var bounds = getBoundingRect(_this, to);
+
+      if (changeAttachY === 'target' || changeAttachY === 'both') {
+        if (top < bounds[1] && tAttachment.top === 'top') {
+          top += targetHeight;
+          tAttachment.top = 'bottom';
+        }
+
+        if (top + height > bounds[3] && tAttachment.top === 'bottom') {
+          top -= targetHeight;
+          tAttachment.top = 'top';
+        }
+      }
+
+      if (changeAttachY === 'together') {
+        if (tAttachment.top === 'top') {
+          if (eAttachment.top === 'bottom' && top < bounds[1]) {
+            top += targetHeight;
+            tAttachment.top = 'bottom';
+
+            top += height;
+            eAttachment.top = 'top';
+          } else if (eAttachment.top === 'top' && top + height > bounds[3] && top - (height - targetHeight) >= bounds[1]) {
+            top -= height - targetHeight;
+            tAttachment.top = 'bottom';
+
+            eAttachment.top = 'bottom';
+          }
+        }
+
+        if (tAttachment.top === 'bottom') {
+          if (eAttachment.top === 'top' && top + height > bounds[3]) {
+            top -= targetHeight;
+            tAttachment.top = 'top';
+
+            top -= height;
+            eAttachment.top = 'bottom';
+          } else if (eAttachment.top === 'bottom' && top < bounds[1] && top + (height * 2 - targetHeight) <= bounds[3]) {
+            top += height - targetHeight;
+            tAttachment.top = 'top';
+
+            eAttachment.top = 'top';
+          }
+        }
+
+        if (tAttachment.top === 'middle') {
+          if (top + height > bounds[3] && eAttachment.top === 'top') {
+            top -= height;
+            eAttachment.top = 'bottom';
+          } else if (top < bounds[1] && eAttachment.top === 'bottom') {
+            top += height;
+            eAttachment.top = 'top';
+          }
+        }
+      }
+
+      if (changeAttachX === 'target' || changeAttachX === 'both') {
+        if (left < bounds[0] && tAttachment.left === 'left') {
+          left += targetWidth;
+          tAttachment.left = 'right';
+        }
+
+        if (left + width > bounds[2] && tAttachment.left === 'right') {
+          left -= targetWidth;
+          tAttachment.left = 'left';
+        }
+      }
+
+      if (changeAttachX === 'together') {
+        if (left < bounds[0] && tAttachment.left === 'left') {
+          if (eAttachment.left === 'right') {
+            left += targetWidth;
+            tAttachment.left = 'right';
+
+            left += width;
+            eAttachment.left = 'left';
+          } else if (eAttachment.left === 'left') {
+            left += targetWidth;
+            tAttachment.left = 'right';
+
+            left -= width;
+            eAttachment.left = 'right';
+          }
+        } else if (left + width > bounds[2] && tAttachment.left === 'right') {
+          if (eAttachment.left === 'left') {
+            left -= targetWidth;
+            tAttachment.left = 'left';
+
+            left -= width;
+            eAttachment.left = 'right';
+          } else if (eAttachment.left === 'right') {
+            left -= targetWidth;
+            tAttachment.left = 'left';
+
+            left += width;
+            eAttachment.left = 'left';
+          }
+        } else if (tAttachment.left === 'center') {
+          if (left + width > bounds[2] && eAttachment.left === 'left') {
+            left -= width;
+            eAttachment.left = 'right';
+          } else if (left < bounds[0] && eAttachment.left === 'right') {
+            left += width;
+            eAttachment.left = 'left';
+          }
+        }
+      }
+
+      if (changeAttachY === 'element' || changeAttachY === 'both') {
+        if (top < bounds[1] && eAttachment.top === 'bottom') {
+          top += height;
+          eAttachment.top = 'top';
+        }
+
+        if (top + height > bounds[3] && eAttachment.top === 'top') {
+          top -= height;
+          eAttachment.top = 'bottom';
+        }
+      }
+
+      if (changeAttachX === 'element' || changeAttachX === 'both') {
+        if (left < bounds[0]) {
+          if (eAttachment.left === 'right') {
+            left += width;
+            eAttachment.left = 'left';
+          } else if (eAttachment.left === 'center') {
+            left += width / 2;
+            eAttachment.left = 'left';
+          }
+        }
+
+        if (left + width > bounds[2]) {
+          if (eAttachment.left === 'left') {
+            left -= width;
+            eAttachment.left = 'right';
+          } else if (eAttachment.left === 'center') {
+            left -= width / 2;
+            eAttachment.left = 'right';
+          }
+        }
+      }
+
+      if (typeof pin === 'string') {
+        pin = pin.split(',').map(function (p) {
+          return p.trim();
+        });
+      } else if (pin === true) {
+        pin = ['top', 'left', 'right', 'bottom'];
+      }
+
+      pin = pin || [];
+
+      var pinned = [];
+      var oob = [];
+
+      if (top < bounds[1]) {
+        if (pin.indexOf('top') >= 0) {
+          top = bounds[1];
+          pinned.push('top');
+        } else {
+          oob.push('top');
+        }
+      }
+
+      if (top + height > bounds[3]) {
+        if (pin.indexOf('bottom') >= 0) {
+          top = bounds[3] - height;
+          pinned.push('bottom');
+        } else {
+          oob.push('bottom');
+        }
+      }
+
+      if (left < bounds[0]) {
+        if (pin.indexOf('left') >= 0) {
+          left = bounds[0];
+          pinned.push('left');
+        } else {
+          oob.push('left');
+        }
+      }
+
+      if (left + width > bounds[2]) {
+        if (pin.indexOf('right') >= 0) {
+          left = bounds[2] - width;
+          pinned.push('right');
+        } else {
+          oob.push('right');
+        }
+      }
+
+      if (pinned.length) {
+        (function () {
+          var pinnedClass = undefined;
+          if (typeof _this.options.pinnedClass !== 'undefined') {
+            pinnedClass = _this.options.pinnedClass;
+          } else {
+            pinnedClass = _this.getClass('pinned');
+          }
+
+          addClasses.push(pinnedClass);
+          pinned.forEach(function (side) {
+            addClasses.push(pinnedClass + '-' + side);
+          });
+        })();
+      }
+
+      if (oob.length) {
+        (function () {
+          var oobClass = undefined;
+          if (typeof _this.options.outOfBoundsClass !== 'undefined') {
+            oobClass = _this.options.outOfBoundsClass;
+          } else {
+            oobClass = _this.getClass('out-of-bounds');
+          }
+
+          addClasses.push(oobClass);
+          oob.forEach(function (side) {
+            addClasses.push(oobClass + '-' + side);
+          });
+        })();
+      }
+
+      if (pinned.indexOf('left') >= 0 || pinned.indexOf('right') >= 0) {
+        eAttachment.left = tAttachment.left = false;
+      }
+      if (pinned.indexOf('top') >= 0 || pinned.indexOf('bottom') >= 0) {
+        eAttachment.top = tAttachment.top = false;
+      }
+
+      if (tAttachment.top !== targetAttachment.top || tAttachment.left !== targetAttachment.left || eAttachment.top !== _this.attachment.top || eAttachment.left !== _this.attachment.left) {
+        _this.updateAttachClasses(eAttachment, tAttachment);
+        _this.trigger('update', {
+          attachment: eAttachment,
+          targetAttachment: tAttachment
+        });
+      }
+    });
+
+    defer(function () {
+      if (!(_this.options.addTargetClasses === false)) {
+        updateClasses(_this.target, addClasses, allClasses);
+      }
+      updateClasses(_this.element, addClasses, allClasses);
+    });
+
+    return { top: top, left: left };
+  }
+});
+/* globals TetherBase */
+
+'use strict';
+
+var _TetherBase$Utils = TetherBase.Utils;
+var getBounds = _TetherBase$Utils.getBounds;
+var updateClasses = _TetherBase$Utils.updateClasses;
+var defer = _TetherBase$Utils.defer;
+
+TetherBase.modules.push({
+  position: function position(_ref) {
+    var _this = this;
+
+    var top = _ref.top;
+    var left = _ref.left;
+
+    var _cache = this.cache('element-bounds', function () {
+      return getBounds(_this.element);
+    });
+
+    var height = _cache.height;
+    var width = _cache.width;
+
+    var targetPos = this.getTargetBounds();
+
+    var bottom = top + height;
+    var right = left + width;
+
+    var abutted = [];
+    if (top <= targetPos.bottom && bottom >= targetPos.top) {
+      ['left', 'right'].forEach(function (side) {
+        var targetPosSide = targetPos[side];
+        if (targetPosSide === left || targetPosSide === right) {
+          abutted.push(side);
+        }
+      });
+    }
+
+    if (left <= targetPos.right && right >= targetPos.left) {
+      ['top', 'bottom'].forEach(function (side) {
+        var targetPosSide = targetPos[side];
+        if (targetPosSide === top || targetPosSide === bottom) {
+          abutted.push(side);
+        }
+      });
+    }
+
+    var allClasses = [];
+    var addClasses = [];
+
+    var sides = ['left', 'top', 'right', 'bottom'];
+    allClasses.push(this.getClass('abutted'));
+    sides.forEach(function (side) {
+      allClasses.push(_this.getClass('abutted') + '-' + side);
+    });
+
+    if (abutted.length) {
+      addClasses.push(this.getClass('abutted'));
+    }
+
+    abutted.forEach(function (side) {
+      addClasses.push(_this.getClass('abutted') + '-' + side);
+    });
+
+    defer(function () {
+      if (!(_this.options.addTargetClasses === false)) {
+        updateClasses(_this.target, addClasses, allClasses);
+      }
+      updateClasses(_this.element, addClasses, allClasses);
+    });
+
+    return true;
+  }
+});
+/* globals TetherBase */
+
+'use strict';
+
+var _slicedToArray = (function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i['return']) _i['return'](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError('Invalid attempt to destructure non-iterable instance'); } }; })();
+
+TetherBase.modules.push({
+  position: function position(_ref) {
+    var top = _ref.top;
+    var left = _ref.left;
+
+    if (!this.options.shift) {
+      return;
+    }
+
+    var shift = this.options.shift;
+    if (typeof this.options.shift === 'function') {
+      shift = this.options.shift.call(this, { top: top, left: left });
+    }
+
+    var shiftTop = undefined,
+        shiftLeft = undefined;
+    if (typeof shift === 'string') {
+      shift = shift.split(' ');
+      shift[1] = shift[1] || shift[0];
+
+      var _shift = shift;
+
+      var _shift2 = _slicedToArray(_shift, 2);
+
+      shiftTop = _shift2[0];
+      shiftLeft = _shift2[1];
+
+      shiftTop = parseFloat(shiftTop, 10);
+      shiftLeft = parseFloat(shiftLeft, 10);
+    } else {
+      shiftTop = shift.top;
+      shiftLeft = shift.left;
+    }
+
+    top += shiftTop;
+    left += shiftLeft;
+
+    return { top: top, left: left };
+  }
+});
+return Tether;
+
+}));
diff --git a/src/legacy/design-studio/js/yoga-layout.js b/src/legacy/design-studio/js/yoga-layout.js
new file mode 100644
index 0000000000000000000000000000000000000000..992e3573ab9df7ace5d485dc787132598eb301d1
--- /dev/null
+++ b/src/legacy/design-studio/js/yoga-layout.js
@@ -0,0 +1,10202 @@
+(function (global, factory) {
+	typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
+	typeof define === 'function' && define.amd ? define(factory) :
+	(global.yogaLayout = factory());
+}(this, (function () { 'use strict';
+
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * 
+ * @format
+ */
+
+var CONSTANTS = {
+  ALIGN_COUNT: 8,
+  ALIGN_AUTO: 0,
+  ALIGN_FLEX_START: 1,
+  ALIGN_CENTER: 2,
+  ALIGN_FLEX_END: 3,
+  ALIGN_STRETCH: 4,
+  ALIGN_BASELINE: 5,
+  ALIGN_SPACE_BETWEEN: 6,
+  ALIGN_SPACE_AROUND: 7,
+
+  DIMENSION_COUNT: 2,
+  DIMENSION_WIDTH: 0,
+  DIMENSION_HEIGHT: 1,
+
+  DIRECTION_COUNT: 3,
+  DIRECTION_INHERIT: 0,
+  DIRECTION_LTR: 1,
+  DIRECTION_RTL: 2,
+
+  DISPLAY_COUNT: 2,
+  DISPLAY_FLEX: 0,
+  DISPLAY_NONE: 1,
+
+  EDGE_COUNT: 9,
+  EDGE_LEFT: 0,
+  EDGE_TOP: 1,
+  EDGE_RIGHT: 2,
+  EDGE_BOTTOM: 3,
+  EDGE_START: 4,
+  EDGE_END: 5,
+  EDGE_HORIZONTAL: 6,
+  EDGE_VERTICAL: 7,
+  EDGE_ALL: 8,
+
+  EXPERIMENTAL_FEATURE_COUNT: 1,
+  EXPERIMENTAL_FEATURE_WEB_FLEX_BASIS: 0,
+
+  FLEX_DIRECTION_COUNT: 4,
+  FLEX_DIRECTION_COLUMN: 0,
+  FLEX_DIRECTION_COLUMN_REVERSE: 1,
+  FLEX_DIRECTION_ROW: 2,
+  FLEX_DIRECTION_ROW_REVERSE: 3,
+
+  JUSTIFY_COUNT: 6,
+  JUSTIFY_FLEX_START: 0,
+  JUSTIFY_CENTER: 1,
+  JUSTIFY_FLEX_END: 2,
+  JUSTIFY_SPACE_BETWEEN: 3,
+  JUSTIFY_SPACE_AROUND: 4,
+  JUSTIFY_SPACE_EVENLY: 5,
+
+  LOG_LEVEL_COUNT: 6,
+  LOG_LEVEL_ERROR: 0,
+  LOG_LEVEL_WARN: 1,
+  LOG_LEVEL_INFO: 2,
+  LOG_LEVEL_DEBUG: 3,
+  LOG_LEVEL_VERBOSE: 4,
+  LOG_LEVEL_FATAL: 5,
+
+  MEASURE_MODE_COUNT: 3,
+  MEASURE_MODE_UNDEFINED: 0,
+  MEASURE_MODE_EXACTLY: 1,
+  MEASURE_MODE_AT_MOST: 2,
+
+  NODE_TYPE_COUNT: 2,
+  NODE_TYPE_DEFAULT: 0,
+  NODE_TYPE_TEXT: 1,
+
+  OVERFLOW_COUNT: 3,
+  OVERFLOW_VISIBLE: 0,
+  OVERFLOW_HIDDEN: 1,
+  OVERFLOW_SCROLL: 2,
+
+  POSITION_TYPE_COUNT: 2,
+  POSITION_TYPE_RELATIVE: 0,
+  POSITION_TYPE_ABSOLUTE: 1,
+
+  PRINT_OPTIONS_COUNT: 3,
+  PRINT_OPTIONS_LAYOUT: 1,
+  PRINT_OPTIONS_STYLE: 2,
+  PRINT_OPTIONS_CHILDREN: 4,
+
+  UNIT_COUNT: 4,
+  UNIT_UNDEFINED: 0,
+  UNIT_POINT: 1,
+  UNIT_PERCENT: 2,
+  UNIT_AUTO: 3,
+
+  WRAP_COUNT: 3,
+  WRAP_NO_WRAP: 0,
+  WRAP_WRAP: 1,
+  WRAP_WRAP_REVERSE: 2
+};
+
+var YGEnums = CONSTANTS;
+
+var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * 
+ * @format
+ */
+
+
+
+var Layout = function () {
+  function Layout(left, right, top, bottom, width, height) {
+    _classCallCheck(this, Layout);
+
+    this.left = left;
+    this.right = right;
+    this.top = top;
+    this.bottom = bottom;
+    this.width = width;
+    this.height = height;
+  }
+
+  _createClass(Layout, [{
+    key: 'fromJS',
+    value: function fromJS(expose) {
+      expose(this.left, this.right, this.top, this.bottom, this.width, this.height);
+    }
+  }, {
+    key: 'toString',
+    value: function toString() {
+      return '<Layout#' + this.left + ':' + this.right + ';' + this.top + ':' + this.bottom + ';' + this.width + ':' + this.height + '>';
+    }
+  }]);
+
+  return Layout;
+}();
+
+var Size = function () {
+  _createClass(Size, null, [{
+    key: 'fromJS',
+    value: function fromJS(_ref) {
+      var width = _ref.width,
+          height = _ref.height;
+
+      return new Size(width, height);
+    }
+  }]);
+
+  function Size(width, height) {
+    _classCallCheck(this, Size);
+
+    this.width = width;
+    this.height = height;
+  }
+
+  _createClass(Size, [{
+    key: 'fromJS',
+    value: function fromJS(expose) {
+      expose(this.width, this.height);
+    }
+  }, {
+    key: 'toString',
+    value: function toString() {
+      return '<Size#' + this.width + 'x' + this.height + '>';
+    }
+  }]);
+
+  return Size;
+}();
+
+var Value = function () {
+  function Value(unit, value) {
+    _classCallCheck(this, Value);
+
+    this.unit = unit;
+    this.value = value;
+  }
+
+  _createClass(Value, [{
+    key: 'fromJS',
+    value: function fromJS(expose) {
+      expose(this.unit, this.value);
+    }
+  }, {
+    key: 'toString',
+    value: function toString() {
+      switch (this.unit) {
+        case YGEnums.UNIT_POINT:
+          return String(this.value);
+        case YGEnums.UNIT_PERCENT:
+          return this.value + '%';
+        case YGEnums.UNIT_AUTO:
+          return 'auto';
+        default:
+          {
+            return this.value + '?';
+          }
+      }
+    }
+  }, {
+    key: 'valueOf',
+    value: function valueOf() {
+      return this.value;
+    }
+  }]);
+
+  return Value;
+}();
+
+var entryCommon = function (bind, lib) {
+  function patch(prototype, name, fn) {
+    var original = prototype[name];
+
+    prototype[name] = function () {
+      for (var _len = arguments.length, args = Array(_len), _key = 0; _key < _len; _key++) {
+        args[_key] = arguments[_key];
+      }
+
+      return fn.call.apply(fn, [this, original].concat(args));
+    };
+  }
+
+  var _arr = ['setPosition', 'setMargin', 'setFlexBasis', 'setWidth', 'setHeight', 'setMinWidth', 'setMinHeight', 'setMaxWidth', 'setMaxHeight', 'setPadding'];
+
+  var _loop = function _loop() {
+    var _methods;
+
+    var fnName = _arr[_i];
+    var methods = (_methods = {}, _defineProperty(_methods, YGEnums.UNIT_POINT, lib.Node.prototype[fnName]), _defineProperty(_methods, YGEnums.UNIT_PERCENT, lib.Node.prototype[fnName + 'Percent']), _defineProperty(_methods, YGEnums.UNIT_AUTO, lib.Node.prototype[fnName + 'Auto']), _methods);
+
+    patch(lib.Node.prototype, fnName, function (original) {
+      for (var _len2 = arguments.length, args = Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
+        args[_key2 - 1] = arguments[_key2];
+      }
+
+      // We patch all these functions to add support for the following calls:
+      // .setWidth(100) / .setWidth("100%") / .setWidth(.getWidth()) / .setWidth("auto")
+
+      var value = args.pop();
+      var unit = void 0,
+          asNumber = void 0;
+
+      if (value === 'auto') {
+        unit = YGEnums.UNIT_AUTO;
+        asNumber = undefined;
+      } else if (value instanceof Value) {
+        unit = value.unit;
+        asNumber = value.valueOf();
+      } else {
+        unit = typeof value === 'string' && value.endsWith('%') ? YGEnums.UNIT_PERCENT : YGEnums.UNIT_POINT;
+        asNumber = parseFloat(value);
+        if (!Number.isNaN(value) && Number.isNaN(asNumber)) {
+          throw new Error('Invalid value ' + value + ' for ' + fnName);
+        }
+      }
+
+      if (!methods[unit]) throw new Error('Failed to execute "' + fnName + '": Unsupported unit \'' + value + '\'');
+
+      if (asNumber !== undefined) {
+        var _methods$unit;
+
+        return (_methods$unit = methods[unit]).call.apply(_methods$unit, [this].concat(args, [asNumber]));
+      } else {
+        var _methods$unit2;
+
+        return (_methods$unit2 = methods[unit]).call.apply(_methods$unit2, [this].concat(args));
+      }
+    });
+  };
+
+  for (var _i = 0; _i < _arr.length; _i++) {
+    _loop();
+  }
+
+  patch(lib.Config.prototype, 'free', function () {
+    // Since we handle the memory allocation ourselves (via lib.Config.create),
+    // we also need to handle the deallocation
+    lib.Config.destroy(this);
+  });
+
+  patch(lib.Node, 'create', function (_, config) {
+    // We decide the constructor we want to call depending on the parameters
+    return config ? lib.Node.createWithConfig(config) : lib.Node.createDefault();
+  });
+
+  patch(lib.Node.prototype, 'free', function () {
+    // Since we handle the memory allocation ourselves (via lib.Node.create),
+    // we also need to handle the deallocation
+    lib.Node.destroy(this);
+  });
+
+  patch(lib.Node.prototype, 'freeRecursive', function () {
+    for (var t = 0, T = this.getChildCount(); t < T; ++t) {
+      this.getChild(0).freeRecursive();
+    }
+    this.free();
+  });
+
+  patch(lib.Node.prototype, 'setMeasureFunc', function (original, measureFunc) {
+    // This patch is just a convenience patch, since it helps write more
+    // idiomatic source code (such as .setMeasureFunc(null))
+    // We also automatically convert the return value of the measureFunc
+    // to a Size object, so that we can return anything that has .width and
+    // .height properties
+    if (measureFunc) {
+      return original.call(this, function () {
+        return Size.fromJS(measureFunc.apply(undefined, arguments));
+      });
+    } else {
+      return this.unsetMeasureFunc();
+    }
+  });
+
+  patch(lib.Node.prototype, 'calculateLayout', function (original) {
+    var width = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : NaN;
+    var height = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : NaN;
+    var direction = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : YGEnums.DIRECTION_LTR;
+
+    // Just a small patch to add support for the function default parameters
+    return original.call(this, width, height, direction);
+  });
+
+  return _extends({
+    Config: lib.Config,
+    Node: lib.Node,
+    Layout: bind('Layout', Layout),
+    Size: bind('Size', Size),
+    Value: bind('Value', Value),
+    getInstanceCount: function getInstanceCount() {
+      return lib.getInstanceCount.apply(lib, arguments);
+    }
+  }, YGEnums);
+};
+
+var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
+
+function commonjsRequire () {
+	throw new Error('Dynamic requires are not currently supported by rollup-plugin-commonjs');
+}
+
+function createCommonjsModule(fn, module) {
+	return module = { exports: {} }, fn(module, module.exports), module.exports;
+}
+
+var nbind = createCommonjsModule(function (module) {
+(function (root, wrapper) {
+  if (typeof undefined == "function" && undefined.amd) undefined([], function () {
+    return wrapper;
+  });else if ('object' == "object" && module.exports) module.exports = wrapper;else (root.nbind = root.nbind || {}).init = wrapper;
+})(commonjsGlobal, function (Module, cb) {
+  if (typeof Module == "function") {
+    cb = Module;Module = {};
+  }Module.onRuntimeInitialized = function (init, cb) {
+    return function () {
+      if (init) init.apply(this, arguments);try {
+        Module.ccall("nbind_init");
+      } catch (err) {
+        cb(err);return;
+      }cb(null, { bind: Module._nbind_value, reflect: Module.NBind.reflect, queryType: Module.NBind.queryType, toggleLightGC: Module.toggleLightGC, lib: Module });
+    };
+  }(Module.onRuntimeInitialized, cb);var Module;if (!Module) Module = (typeof Module !== "undefined" ? Module : null) || {};var moduleOverrides = {};for (var key in Module) {
+    if (Module.hasOwnProperty(key)) {
+      moduleOverrides[key] = Module[key];
+    }
+  }var ENVIRONMENT_IS_WEB = false;var ENVIRONMENT_IS_WORKER = false;var ENVIRONMENT_IS_NODE = false;var ENVIRONMENT_IS_SHELL = false;if (Module["ENVIRONMENT"]) {
+    if (Module["ENVIRONMENT"] === "WEB") {
+      ENVIRONMENT_IS_WEB = true;
+    } else if (Module["ENVIRONMENT"] === "WORKER") {
+      ENVIRONMENT_IS_WORKER = true;
+    } else if (Module["ENVIRONMENT"] === "NODE") {
+      ENVIRONMENT_IS_NODE = true;
+    } else if (Module["ENVIRONMENT"] === "SHELL") {
+      ENVIRONMENT_IS_SHELL = true;
+    } else {
+      throw new Error("The provided Module['ENVIRONMENT'] value is not valid. It must be one of: WEB|WORKER|NODE|SHELL.");
+    }
+  } else {
+    ENVIRONMENT_IS_WEB = typeof window === "object";ENVIRONMENT_IS_WORKER = typeof importScripts === "function";ENVIRONMENT_IS_NODE = typeof process === "object" && typeof commonjsRequire === "function" && !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_WORKER;ENVIRONMENT_IS_SHELL = !ENVIRONMENT_IS_WEB && !ENVIRONMENT_IS_NODE && !ENVIRONMENT_IS_WORKER;
+  }if (ENVIRONMENT_IS_NODE) {
+    if (!Module["print"]) Module["print"] = console.log;if (!Module["printErr"]) Module["printErr"] = console.warn;var nodeFS;var nodePath;Module["read"] = function shell_read(filename, binary) {
+      if (!nodeFS) nodeFS = {}("");if (!nodePath) nodePath = {}("");filename = nodePath["normalize"](filename);var ret = nodeFS["readFileSync"](filename);return binary ? ret : ret.toString();
+    };Module["readBinary"] = function readBinary(filename) {
+      var ret = Module["read"](filename, true);if (!ret.buffer) {
+        ret = new Uint8Array(ret);
+      }assert(ret.buffer);return ret;
+    };Module["load"] = function load(f) {
+      globalEval(read(f));
+    };if (!Module["thisProgram"]) {
+      if (process["argv"].length > 1) {
+        Module["thisProgram"] = process["argv"][1].replace(/\\/g, "/");
+      } else {
+        Module["thisProgram"] = "unknown-program";
+      }
+    }Module["arguments"] = process["argv"].slice(2);{
+      module["exports"] = Module;
+    }process["on"]("uncaughtException", function (ex) {
+      if (!(ex instanceof ExitStatus)) {
+        throw ex;
+      }
+    });Module["inspect"] = function () {
+      return "[Emscripten Module object]";
+    };
+  } else if (ENVIRONMENT_IS_SHELL) {
+    if (!Module["print"]) Module["print"] = print;if (typeof printErr != "undefined") Module["printErr"] = printErr;if (typeof read != "undefined") {
+      Module["read"] = read;
+    } else {
+      Module["read"] = function shell_read() {
+        throw "no read() available";
+      };
+    }Module["readBinary"] = function readBinary(f) {
+      if (typeof readbuffer === "function") {
+        return new Uint8Array(readbuffer(f));
+      }var data = read(f, "binary");assert(typeof data === "object");return data;
+    };if (typeof scriptArgs != "undefined") {
+      Module["arguments"] = scriptArgs;
+    } else if (typeof arguments != "undefined") {
+      Module["arguments"] = arguments;
+    }if (typeof quit === "function") {
+      Module["quit"] = function (status, toThrow) {
+        quit(status);
+      };
+    }
+  } else if (ENVIRONMENT_IS_WEB || ENVIRONMENT_IS_WORKER) {
+    Module["read"] = function shell_read(url) {
+      var xhr = new XMLHttpRequest();xhr.open("GET", url, false);xhr.send(null);return xhr.responseText;
+    };if (ENVIRONMENT_IS_WORKER) {
+      Module["readBinary"] = function readBinary(url) {
+        var xhr = new XMLHttpRequest();xhr.open("GET", url, false);xhr.responseType = "arraybuffer";xhr.send(null);return new Uint8Array(xhr.response);
+      };
+    }Module["readAsync"] = function readAsync(url, onload, onerror) {
+      var xhr = new XMLHttpRequest();xhr.open("GET", url, true);xhr.responseType = "arraybuffer";xhr.onload = function xhr_onload() {
+        if (xhr.status == 200 || xhr.status == 0 && xhr.response) {
+          onload(xhr.response);
+        } else {
+          onerror();
+        }
+      };xhr.onerror = onerror;xhr.send(null);
+    };if (typeof arguments != "undefined") {
+      Module["arguments"] = arguments;
+    }if (typeof console !== "undefined") {
+      if (!Module["print"]) Module["print"] = function shell_print(x) {
+        console.log(x);
+      };if (!Module["printErr"]) Module["printErr"] = function shell_printErr(x) {
+        console.warn(x);
+      };
+    } else {
+      var TRY_USE_DUMP = false;if (!Module["print"]) Module["print"] = TRY_USE_DUMP && typeof dump !== "undefined" ? function (x) {
+        dump(x);
+      } : function (x) {};
+    }if (ENVIRONMENT_IS_WORKER) {
+      Module["load"] = importScripts;
+    }if (typeof Module["setWindowTitle"] === "undefined") {
+      Module["setWindowTitle"] = function (title) {
+        document.title = title;
+      };
+    }
+  } else {
+    throw "Unknown runtime environment. Where are we?";
+  }function globalEval(x) {
+    eval.call(null, x);
+  }if (!Module["load"] && Module["read"]) {
+    Module["load"] = function load(f) {
+      globalEval(Module["read"](f));
+    };
+  }if (!Module["print"]) {
+    Module["print"] = function () {};
+  }if (!Module["printErr"]) {
+    Module["printErr"] = Module["print"];
+  }if (!Module["arguments"]) {
+    Module["arguments"] = [];
+  }if (!Module["thisProgram"]) {
+    Module["thisProgram"] = "./this.program";
+  }if (!Module["quit"]) {
+    Module["quit"] = function (status, toThrow) {
+      throw toThrow;
+    };
+  }Module.print = Module["print"];Module.printErr = Module["printErr"];Module["preRun"] = [];Module["postRun"] = [];for (var key in moduleOverrides) {
+    if (moduleOverrides.hasOwnProperty(key)) {
+      Module[key] = moduleOverrides[key];
+    }
+  }moduleOverrides = undefined;var Runtime = { setTempRet0: function (value) {
+      tempRet0 = value;return value;
+    }, getTempRet0: function () {
+      return tempRet0;
+    }, stackSave: function () {
+      return STACKTOP;
+    }, stackRestore: function (stackTop) {
+      STACKTOP = stackTop;
+    }, getNativeTypeSize: function (type) {
+      switch (type) {case "i1":case "i8":
+          return 1;case "i16":
+          return 2;case "i32":
+          return 4;case "i64":
+          return 8;case "float":
+          return 4;case "double":
+          return 8;default:
+          {
+            if (type[type.length - 1] === "*") {
+              return Runtime.QUANTUM_SIZE;
+            } else if (type[0] === "i") {
+              var bits = parseInt(type.substr(1));assert(bits % 8 === 0);return bits / 8;
+            } else {
+              return 0;
+            }
+          }}
+    }, getNativeFieldSize: function (type) {
+      return Math.max(Runtime.getNativeTypeSize(type), Runtime.QUANTUM_SIZE);
+    }, STACK_ALIGN: 16, prepVararg: function (ptr, type) {
+      if (type === "double" || type === "i64") {
+        if (ptr & 7) {
+          assert((ptr & 7) === 4);ptr += 4;
+        }
+      } else {
+        assert((ptr & 3) === 0);
+      }return ptr;
+    }, getAlignSize: function (type, size, vararg) {
+      if (!vararg && (type == "i64" || type == "double")) return 8;if (!type) return Math.min(size, 8);return Math.min(size || (type ? Runtime.getNativeFieldSize(type) : 0), Runtime.QUANTUM_SIZE);
+    }, dynCall: function (sig, ptr, args) {
+      if (args && args.length) {
+        return Module["dynCall_" + sig].apply(null, [ptr].concat(args));
+      } else {
+        return Module["dynCall_" + sig].call(null, ptr);
+      }
+    }, functionPointers: [], addFunction: function (func) {
+      for (var i = 0; i < Runtime.functionPointers.length; i++) {
+        if (!Runtime.functionPointers[i]) {
+          Runtime.functionPointers[i] = func;return 2 * (1 + i);
+        }
+      }throw "Finished up all reserved function pointers. Use a higher value for RESERVED_FUNCTION_POINTERS.";
+    }, removeFunction: function (index) {
+      Runtime.functionPointers[(index - 2) / 2] = null;
+    }, warnOnce: function (text) {
+      if (!Runtime.warnOnce.shown) Runtime.warnOnce.shown = {};if (!Runtime.warnOnce.shown[text]) {
+        Runtime.warnOnce.shown[text] = 1;Module.printErr(text);
+      }
+    }, funcWrappers: {}, getFuncWrapper: function (func, sig) {
+      if (!func) return;assert(sig);if (!Runtime.funcWrappers[sig]) {
+        Runtime.funcWrappers[sig] = {};
+      }var sigCache = Runtime.funcWrappers[sig];if (!sigCache[func]) {
+        if (sig.length === 1) {
+          sigCache[func] = function dynCall_wrapper() {
+            return Runtime.dynCall(sig, func);
+          };
+        } else if (sig.length === 2) {
+          sigCache[func] = function dynCall_wrapper(arg) {
+            return Runtime.dynCall(sig, func, [arg]);
+          };
+        } else {
+          sigCache[func] = function dynCall_wrapper() {
+            return Runtime.dynCall(sig, func, Array.prototype.slice.call(arguments));
+          };
+        }
+      }return sigCache[func];
+    }, getCompilerSetting: function (name) {
+      throw "You must build with -s RETAIN_COMPILER_SETTINGS=1 for Runtime.getCompilerSetting or emscripten_get_compiler_setting to work";
+    }, stackAlloc: function (size) {
+      var ret = STACKTOP;STACKTOP = STACKTOP + size | 0;STACKTOP = STACKTOP + 15 & -16;return ret;
+    }, staticAlloc: function (size) {
+      var ret = STATICTOP;STATICTOP = STATICTOP + size | 0;STATICTOP = STATICTOP + 15 & -16;return ret;
+    }, dynamicAlloc: function (size) {
+      var ret = HEAP32[DYNAMICTOP_PTR >> 2];var end = (ret + size + 15 | 0) & -16;HEAP32[DYNAMICTOP_PTR >> 2] = end;if (end >= TOTAL_MEMORY) {
+        var success = enlargeMemory();if (!success) {
+          HEAP32[DYNAMICTOP_PTR >> 2] = ret;return 0;
+        }
+      }return ret;
+    }, alignMemory: function (size, quantum) {
+      var ret = size = Math.ceil(size / (quantum ? quantum : 16)) * (quantum ? quantum : 16);return ret;
+    }, makeBigInt: function (low, high, unsigned) {
+      var ret = unsigned ? +(low >>> 0) + +(high >>> 0) * +4294967296 : +(low >>> 0) + +(high | 0) * +4294967296;return ret;
+    }, GLOBAL_BASE: 8, QUANTUM_SIZE: 4, __dummy__: 0 };Module["Runtime"] = Runtime;var ABORT = 0;function assert(condition, text) {
+    if (!condition) {
+      abort("Assertion failed: " + text);
+    }
+  }function getCFunc(ident) {
+    var func = Module["_" + ident];if (!func) {
+      try {
+        func = eval("_" + ident);
+      } catch (e) {}
+    }assert(func, "Cannot call unknown function " + ident + " (perhaps LLVM optimizations or closure removed it?)");return func;
+  }var cwrap, ccall;(function () {
+    var JSfuncs = { "stackSave": function () {
+        Runtime.stackSave();
+      }, "stackRestore": function () {
+        Runtime.stackRestore();
+      }, "arrayToC": function (arr) {
+        var ret = Runtime.stackAlloc(arr.length);writeArrayToMemory(arr, ret);return ret;
+      }, "stringToC": function (str) {
+        var ret = 0;if (str !== null && str !== undefined && str !== 0) {
+          var len = (str.length << 2) + 1;ret = Runtime.stackAlloc(len);stringToUTF8(str, ret, len);
+        }return ret;
+      } };var toC = { "string": JSfuncs["stringToC"], "array": JSfuncs["arrayToC"] };ccall = function ccallFunc(ident, returnType, argTypes, args, opts) {
+      var func = getCFunc(ident);var cArgs = [];var stack = 0;if (args) {
+        for (var i = 0; i < args.length; i++) {
+          var converter = toC[argTypes[i]];if (converter) {
+            if (stack === 0) stack = Runtime.stackSave();cArgs[i] = converter(args[i]);
+          } else {
+            cArgs[i] = args[i];
+          }
+        }
+      }var ret = func.apply(null, cArgs);if (returnType === "string") ret = Pointer_stringify(ret);if (stack !== 0) {
+        if (opts && opts.async) {
+          EmterpreterAsync.asyncFinalizers.push(function () {
+            Runtime.stackRestore(stack);
+          });return;
+        }Runtime.stackRestore(stack);
+      }return ret;
+    };var sourceRegex = /^function\s*[a-zA-Z$_0-9]*\s*\(([^)]*)\)\s*{\s*([^*]*?)[\s;]*(?:return\s*(.*?)[;\s]*)?}$/;function parseJSFunc(jsfunc) {
+      var parsed = jsfunc.toString().match(sourceRegex).slice(1);return { arguments: parsed[0], body: parsed[1], returnValue: parsed[2] };
+    }var JSsource = null;function ensureJSsource() {
+      if (!JSsource) {
+        JSsource = {};for (var fun in JSfuncs) {
+          if (JSfuncs.hasOwnProperty(fun)) {
+            JSsource[fun] = parseJSFunc(JSfuncs[fun]);
+          }
+        }
+      }
+    }cwrap = function cwrap(ident, returnType, argTypes) {
+      argTypes = argTypes || [];var cfunc = getCFunc(ident);var numericArgs = argTypes.every(function (type) {
+        return type === "number";
+      });var numericRet = returnType !== "string";if (numericRet && numericArgs) {
+        return cfunc;
+      }var argNames = argTypes.map(function (x, i) {
+        return "$" + i;
+      });var funcstr = "(function(" + argNames.join(",") + ") {";var nargs = argTypes.length;if (!numericArgs) {
+        ensureJSsource();funcstr += "var stack = " + JSsource["stackSave"].body + ";";for (var i = 0; i < nargs; i++) {
+          var arg = argNames[i],
+              type = argTypes[i];if (type === "number") continue;var convertCode = JSsource[type + "ToC"];funcstr += "var " + convertCode.arguments + " = " + arg + ";";funcstr += convertCode.body + ";";funcstr += arg + "=(" + convertCode.returnValue + ");";
+        }
+      }var cfuncname = parseJSFunc(function () {
+        return cfunc;
+      }).returnValue;funcstr += "var ret = " + cfuncname + "(" + argNames.join(",") + ");";if (!numericRet) {
+        var strgfy = parseJSFunc(function () {
+          return Pointer_stringify;
+        }).returnValue;funcstr += "ret = " + strgfy + "(ret);";
+      }if (!numericArgs) {
+        ensureJSsource();funcstr += JSsource["stackRestore"].body.replace("()", "(stack)") + ";";
+      }funcstr += "return ret})";return eval(funcstr);
+    };
+  })();Module["ccall"] = ccall;Module["cwrap"] = cwrap;function setValue(ptr, value, type, noSafe) {
+    type = type || "i8";if (type.charAt(type.length - 1) === "*") type = "i32";switch (type) {case "i1":
+        HEAP8[ptr >> 0] = value;break;case "i8":
+        HEAP8[ptr >> 0] = value;break;case "i16":
+        HEAP16[ptr >> 1] = value;break;case "i32":
+        HEAP32[ptr >> 2] = value;break;case "i64":
+        tempI64 = [value >>> 0, (tempDouble = value, +Math_abs(tempDouble) >= +1 ? tempDouble > +0 ? (Math_min(+Math_floor(tempDouble / +4294967296), +4294967295) | 0) >>> 0 : ~~+Math_ceil((tempDouble - +(~~tempDouble >>> 0)) / +4294967296) >>> 0 : 0)], HEAP32[ptr >> 2] = tempI64[0], HEAP32[ptr + 4 >> 2] = tempI64[1];break;case "float":
+        HEAPF32[ptr >> 2] = value;break;case "double":
+        HEAPF64[ptr >> 3] = value;break;default:
+        abort("invalid type for setValue: " + type);}
+  }Module["setValue"] = setValue;function getValue(ptr, type, noSafe) {
+    type = type || "i8";if (type.charAt(type.length - 1) === "*") type = "i32";switch (type) {case "i1":
+        return HEAP8[ptr >> 0];case "i8":
+        return HEAP8[ptr >> 0];case "i16":
+        return HEAP16[ptr >> 1];case "i32":
+        return HEAP32[ptr >> 2];case "i64":
+        return HEAP32[ptr >> 2];case "float":
+        return HEAPF32[ptr >> 2];case "double":
+        return HEAPF64[ptr >> 3];default:
+        abort("invalid type for setValue: " + type);}return null;
+  }Module["getValue"] = getValue;var ALLOC_NORMAL = 0;var ALLOC_STACK = 1;var ALLOC_STATIC = 2;var ALLOC_DYNAMIC = 3;var ALLOC_NONE = 4;Module["ALLOC_NORMAL"] = ALLOC_NORMAL;Module["ALLOC_STACK"] = ALLOC_STACK;Module["ALLOC_STATIC"] = ALLOC_STATIC;Module["ALLOC_DYNAMIC"] = ALLOC_DYNAMIC;Module["ALLOC_NONE"] = ALLOC_NONE;function allocate(slab, types, allocator, ptr) {
+    var zeroinit, size;if (typeof slab === "number") {
+      zeroinit = true;size = slab;
+    } else {
+      zeroinit = false;size = slab.length;
+    }var singleType = typeof types === "string" ? types : null;var ret;if (allocator == ALLOC_NONE) {
+      ret = ptr;
+    } else {
+      ret = [typeof _malloc === "function" ? _malloc : Runtime.staticAlloc, Runtime.stackAlloc, Runtime.staticAlloc, Runtime.dynamicAlloc][allocator === undefined ? ALLOC_STATIC : allocator](Math.max(size, singleType ? 1 : types.length));
+    }if (zeroinit) {
+      var ptr = ret,
+          stop;assert((ret & 3) == 0);stop = ret + (size & ~3);for (; ptr < stop; ptr += 4) {
+        HEAP32[ptr >> 2] = 0;
+      }stop = ret + size;while (ptr < stop) {
+        HEAP8[ptr++ >> 0] = 0;
+      }return ret;
+    }if (singleType === "i8") {
+      if (slab.subarray || slab.slice) {
+        HEAPU8.set(slab, ret);
+      } else {
+        HEAPU8.set(new Uint8Array(slab), ret);
+      }return ret;
+    }var i = 0,
+        type,
+        typeSize,
+        previousType;while (i < size) {
+      var curr = slab[i];if (typeof curr === "function") {
+        curr = Runtime.getFunctionIndex(curr);
+      }type = singleType || types[i];if (type === 0) {
+        i++;continue;
+      }if (type == "i64") type = "i32";setValue(ret + i, curr, type);if (previousType !== type) {
+        typeSize = Runtime.getNativeTypeSize(type);previousType = type;
+      }i += typeSize;
+    }return ret;
+  }Module["allocate"] = allocate;function getMemory(size) {
+    if (!staticSealed) return Runtime.staticAlloc(size);if (!runtimeInitialized) return Runtime.dynamicAlloc(size);return _malloc(size);
+  }Module["getMemory"] = getMemory;function Pointer_stringify(ptr, length) {
+    if (length === 0 || !ptr) return "";var hasUtf = 0;var t;var i = 0;while (1) {
+      t = HEAPU8[ptr + i >> 0];hasUtf |= t;if (t == 0 && !length) break;i++;if (length && i == length) break;
+    }if (!length) length = i;var ret = "";if (hasUtf < 128) {
+      var MAX_CHUNK = 1024;var curr;while (length > 0) {
+        curr = String.fromCharCode.apply(String, HEAPU8.subarray(ptr, ptr + Math.min(length, MAX_CHUNK)));ret = ret ? ret + curr : curr;ptr += MAX_CHUNK;length -= MAX_CHUNK;
+      }return ret;
+    }return Module["UTF8ToString"](ptr);
+  }Module["Pointer_stringify"] = Pointer_stringify;function AsciiToString(ptr) {
+    var str = "";while (1) {
+      var ch = HEAP8[ptr++ >> 0];if (!ch) return str;str += String.fromCharCode(ch);
+    }
+  }Module["AsciiToString"] = AsciiToString;function stringToAscii(str, outPtr) {
+    return writeAsciiToMemory(str, outPtr, false);
+  }Module["stringToAscii"] = stringToAscii;var UTF8Decoder = typeof TextDecoder !== "undefined" ? new TextDecoder("utf8") : undefined;function UTF8ArrayToString(u8Array, idx) {
+    var endPtr = idx;while (u8Array[endPtr]) ++endPtr;if (endPtr - idx > 16 && u8Array.subarray && UTF8Decoder) {
+      return UTF8Decoder.decode(u8Array.subarray(idx, endPtr));
+    } else {
+      var u0, u1, u2, u3, u4, u5;var str = "";while (1) {
+        u0 = u8Array[idx++];if (!u0) return str;if (!(u0 & 128)) {
+          str += String.fromCharCode(u0);continue;
+        }u1 = u8Array[idx++] & 63;if ((u0 & 224) == 192) {
+          str += String.fromCharCode((u0 & 31) << 6 | u1);continue;
+        }u2 = u8Array[idx++] & 63;if ((u0 & 240) == 224) {
+          u0 = (u0 & 15) << 12 | u1 << 6 | u2;
+        } else {
+          u3 = u8Array[idx++] & 63;if ((u0 & 248) == 240) {
+            u0 = (u0 & 7) << 18 | u1 << 12 | u2 << 6 | u3;
+          } else {
+            u4 = u8Array[idx++] & 63;if ((u0 & 252) == 248) {
+              u0 = (u0 & 3) << 24 | u1 << 18 | u2 << 12 | u3 << 6 | u4;
+            } else {
+              u5 = u8Array[idx++] & 63;u0 = (u0 & 1) << 30 | u1 << 24 | u2 << 18 | u3 << 12 | u4 << 6 | u5;
+            }
+          }
+        }if (u0 < 65536) {
+          str += String.fromCharCode(u0);
+        } else {
+          var ch = u0 - 65536;str += String.fromCharCode(55296 | ch >> 10, 56320 | ch & 1023);
+        }
+      }
+    }
+  }Module["UTF8ArrayToString"] = UTF8ArrayToString;function UTF8ToString(ptr) {
+    return UTF8ArrayToString(HEAPU8, ptr);
+  }Module["UTF8ToString"] = UTF8ToString;function stringToUTF8Array(str, outU8Array, outIdx, maxBytesToWrite) {
+    if (!(maxBytesToWrite > 0)) return 0;var startIdx = outIdx;var endIdx = outIdx + maxBytesToWrite - 1;for (var i = 0; i < str.length; ++i) {
+      var u = str.charCodeAt(i);if (u >= 55296 && u <= 57343) u = 65536 + ((u & 1023) << 10) | str.charCodeAt(++i) & 1023;if (u <= 127) {
+        if (outIdx >= endIdx) break;outU8Array[outIdx++] = u;
+      } else if (u <= 2047) {
+        if (outIdx + 1 >= endIdx) break;outU8Array[outIdx++] = 192 | u >> 6;outU8Array[outIdx++] = 128 | u & 63;
+      } else if (u <= 65535) {
+        if (outIdx + 2 >= endIdx) break;outU8Array[outIdx++] = 224 | u >> 12;outU8Array[outIdx++] = 128 | u >> 6 & 63;outU8Array[outIdx++] = 128 | u & 63;
+      } else if (u <= 2097151) {
+        if (outIdx + 3 >= endIdx) break;outU8Array[outIdx++] = 240 | u >> 18;outU8Array[outIdx++] = 128 | u >> 12 & 63;outU8Array[outIdx++] = 128 | u >> 6 & 63;outU8Array[outIdx++] = 128 | u & 63;
+      } else if (u <= 67108863) {
+        if (outIdx + 4 >= endIdx) break;outU8Array[outIdx++] = 248 | u >> 24;outU8Array[outIdx++] = 128 | u >> 18 & 63;outU8Array[outIdx++] = 128 | u >> 12 & 63;outU8Array[outIdx++] = 128 | u >> 6 & 63;outU8Array[outIdx++] = 128 | u & 63;
+      } else {
+        if (outIdx + 5 >= endIdx) break;outU8Array[outIdx++] = 252 | u >> 30;outU8Array[outIdx++] = 128 | u >> 24 & 63;outU8Array[outIdx++] = 128 | u >> 18 & 63;outU8Array[outIdx++] = 128 | u >> 12 & 63;outU8Array[outIdx++] = 128 | u >> 6 & 63;outU8Array[outIdx++] = 128 | u & 63;
+      }
+    }outU8Array[outIdx] = 0;return outIdx - startIdx;
+  }Module["stringToUTF8Array"] = stringToUTF8Array;function stringToUTF8(str, outPtr, maxBytesToWrite) {
+    return stringToUTF8Array(str, HEAPU8, outPtr, maxBytesToWrite);
+  }Module["stringToUTF8"] = stringToUTF8;function lengthBytesUTF8(str) {
+    var len = 0;for (var i = 0; i < str.length; ++i) {
+      var u = str.charCodeAt(i);if (u >= 55296 && u <= 57343) u = 65536 + ((u & 1023) << 10) | str.charCodeAt(++i) & 1023;if (u <= 127) {
+        ++len;
+      } else if (u <= 2047) {
+        len += 2;
+      } else if (u <= 65535) {
+        len += 3;
+      } else if (u <= 2097151) {
+        len += 4;
+      } else if (u <= 67108863) {
+        len += 5;
+      } else {
+        len += 6;
+      }
+    }return len;
+  }Module["lengthBytesUTF8"] = lengthBytesUTF8;var UTF16Decoder = typeof TextDecoder !== "undefined" ? new TextDecoder("utf-16le") : undefined;function demangle(func) {
+    var __cxa_demangle_func = Module["___cxa_demangle"] || Module["__cxa_demangle"];if (__cxa_demangle_func) {
+      try {
+        var s = func.substr(1);var len = lengthBytesUTF8(s) + 1;var buf = _malloc(len);stringToUTF8(s, buf, len);var status = _malloc(4);var ret = __cxa_demangle_func(buf, 0, 0, status);if (getValue(status, "i32") === 0 && ret) {
+          return Pointer_stringify(ret);
+        }
+      } catch (e) {} finally {
+        if (buf) _free(buf);if (status) _free(status);if (ret) _free(ret);
+      }return func;
+    }Runtime.warnOnce("warning: build with  -s DEMANGLE_SUPPORT=1  to link in libcxxabi demangling");return func;
+  }function demangleAll(text) {
+    var regex = /__Z[\w\d_]+/g;return text.replace(regex, function (x) {
+      var y = demangle(x);return x === y ? x : x + " [" + y + "]";
+    });
+  }function jsStackTrace() {
+    var err = new Error();if (!err.stack) {
+      try {
+        throw new Error(0);
+      } catch (e) {
+        err = e;
+      }if (!err.stack) {
+        return "(no stack trace available)";
+      }
+    }return err.stack.toString();
+  }function stackTrace() {
+    var js = jsStackTrace();if (Module["extraStackTrace"]) js += "\n" + Module["extraStackTrace"]();return demangleAll(js);
+  }Module["stackTrace"] = stackTrace;var HEAP, buffer, HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32, HEAPF32, HEAPF64;function updateGlobalBufferViews() {
+    Module["HEAP8"] = HEAP8 = new Int8Array(buffer);Module["HEAP16"] = HEAP16 = new Int16Array(buffer);Module["HEAP32"] = HEAP32 = new Int32Array(buffer);Module["HEAPU8"] = HEAPU8 = new Uint8Array(buffer);Module["HEAPU16"] = HEAPU16 = new Uint16Array(buffer);Module["HEAPU32"] = HEAPU32 = new Uint32Array(buffer);Module["HEAPF32"] = HEAPF32 = new Float32Array(buffer);Module["HEAPF64"] = HEAPF64 = new Float64Array(buffer);
+  }var STATIC_BASE, STATICTOP, staticSealed;var STACK_BASE, STACKTOP, STACK_MAX;var DYNAMIC_BASE, DYNAMICTOP_PTR;STATIC_BASE = STATICTOP = STACK_BASE = STACKTOP = STACK_MAX = DYNAMIC_BASE = DYNAMICTOP_PTR = 0;staticSealed = false;function abortOnCannotGrowMemory() {
+    abort("Cannot enlarge memory arrays. Either (1) compile with  -s TOTAL_MEMORY=X  with X higher than the current value " + TOTAL_MEMORY + ", (2) compile with  -s ALLOW_MEMORY_GROWTH=1  which allows increasing the size at runtime but prevents some optimizations, (3) set Module.TOTAL_MEMORY to a higher value before the program runs, or (4) if you want malloc to return NULL (0) instead of this abort, compile with  -s ABORTING_MALLOC=0 ");
+  }function enlargeMemory() {
+    abortOnCannotGrowMemory();
+  }var TOTAL_STACK = Module["TOTAL_STACK"] || 5242880;var TOTAL_MEMORY = Module["TOTAL_MEMORY"] || 134217728;if (TOTAL_MEMORY < TOTAL_STACK) Module.printErr("TOTAL_MEMORY should be larger than TOTAL_STACK, was " + TOTAL_MEMORY + "! (TOTAL_STACK=" + TOTAL_STACK + ")");if (Module["buffer"]) {
+    buffer = Module["buffer"];
+  } else {
+    {
+      buffer = new ArrayBuffer(TOTAL_MEMORY);
+    }
+  }updateGlobalBufferViews();function getTotalMemory() {
+    return TOTAL_MEMORY;
+  }HEAP32[0] = 1668509029;HEAP16[1] = 25459;if (HEAPU8[2] !== 115 || HEAPU8[3] !== 99) throw "Runtime error: expected the system to be little-endian!";Module["HEAP"] = HEAP;Module["buffer"] = buffer;Module["HEAP8"] = HEAP8;Module["HEAP16"] = HEAP16;Module["HEAP32"] = HEAP32;Module["HEAPU8"] = HEAPU8;Module["HEAPU16"] = HEAPU16;Module["HEAPU32"] = HEAPU32;Module["HEAPF32"] = HEAPF32;Module["HEAPF64"] = HEAPF64;function callRuntimeCallbacks(callbacks) {
+    while (callbacks.length > 0) {
+      var callback = callbacks.shift();if (typeof callback == "function") {
+        callback();continue;
+      }var func = callback.func;if (typeof func === "number") {
+        if (callback.arg === undefined) {
+          Module["dynCall_v"](func);
+        } else {
+          Module["dynCall_vi"](func, callback.arg);
+        }
+      } else {
+        func(callback.arg === undefined ? null : callback.arg);
+      }
+    }
+  }var __ATPRERUN__ = [];var __ATINIT__ = [];var __ATMAIN__ = [];var __ATEXIT__ = [];var __ATPOSTRUN__ = [];var runtimeInitialized = false;function preRun() {
+    if (Module["preRun"]) {
+      if (typeof Module["preRun"] == "function") Module["preRun"] = [Module["preRun"]];while (Module["preRun"].length) {
+        addOnPreRun(Module["preRun"].shift());
+      }
+    }callRuntimeCallbacks(__ATPRERUN__);
+  }function ensureInitRuntime() {
+    if (runtimeInitialized) return;runtimeInitialized = true;callRuntimeCallbacks(__ATINIT__);
+  }function preMain() {
+    callRuntimeCallbacks(__ATMAIN__);
+  }function exitRuntime() {
+    callRuntimeCallbacks(__ATEXIT__);  }function postRun() {
+    if (Module["postRun"]) {
+      if (typeof Module["postRun"] == "function") Module["postRun"] = [Module["postRun"]];while (Module["postRun"].length) {
+        addOnPostRun(Module["postRun"].shift());
+      }
+    }callRuntimeCallbacks(__ATPOSTRUN__);
+  }function addOnPreRun(cb) {
+    __ATPRERUN__.unshift(cb);
+  }Module["addOnPreRun"] = addOnPreRun;function addOnInit(cb) {
+    __ATINIT__.unshift(cb);
+  }Module["addOnInit"] = addOnInit;function addOnPreMain(cb) {
+    __ATMAIN__.unshift(cb);
+  }Module["addOnPreMain"] = addOnPreMain;function addOnExit(cb) {
+    __ATEXIT__.unshift(cb);
+  }Module["addOnExit"] = addOnExit;function addOnPostRun(cb) {
+    __ATPOSTRUN__.unshift(cb);
+  }Module["addOnPostRun"] = addOnPostRun;function intArrayFromString(stringy, dontAddNull, length) {
+    var len = length > 0 ? length : lengthBytesUTF8(stringy) + 1;var u8array = new Array(len);var numBytesWritten = stringToUTF8Array(stringy, u8array, 0, u8array.length);if (dontAddNull) u8array.length = numBytesWritten;return u8array;
+  }Module["intArrayFromString"] = intArrayFromString;function intArrayToString(array) {
+    var ret = [];for (var i = 0; i < array.length; i++) {
+      var chr = array[i];if (chr > 255) {
+        chr &= 255;
+      }ret.push(String.fromCharCode(chr));
+    }return ret.join("");
+  }Module["intArrayToString"] = intArrayToString;function writeStringToMemory(string, buffer, dontAddNull) {
+    Runtime.warnOnce("writeStringToMemory is deprecated and should not be called! Use stringToUTF8() instead!");var lastChar, end;if (dontAddNull) {
+      end = buffer + lengthBytesUTF8(string);lastChar = HEAP8[end];
+    }stringToUTF8(string, buffer, Infinity);if (dontAddNull) HEAP8[end] = lastChar;
+  }Module["writeStringToMemory"] = writeStringToMemory;function writeArrayToMemory(array, buffer) {
+    HEAP8.set(array, buffer);
+  }Module["writeArrayToMemory"] = writeArrayToMemory;function writeAsciiToMemory(str, buffer, dontAddNull) {
+    for (var i = 0; i < str.length; ++i) {
+      HEAP8[buffer++ >> 0] = str.charCodeAt(i);
+    }if (!dontAddNull) HEAP8[buffer >> 0] = 0;
+  }Module["writeAsciiToMemory"] = writeAsciiToMemory;if (!Math["imul"] || Math["imul"](4294967295, 5) !== -5) Math["imul"] = function imul(a, b) {
+    var ah = a >>> 16;var al = a & 65535;var bh = b >>> 16;var bl = b & 65535;return al * bl + (ah * bl + al * bh << 16) | 0;
+  };Math.imul = Math["imul"];if (!Math["fround"]) {
+    var froundBuffer = new Float32Array(1);Math["fround"] = function (x) {
+      froundBuffer[0] = x;return froundBuffer[0];
+    };
+  }Math.fround = Math["fround"];if (!Math["clz32"]) Math["clz32"] = function (x) {
+    x = x >>> 0;for (var i = 0; i < 32; i++) {
+      if (x & 1 << 31 - i) return i;
+    }return 32;
+  };Math.clz32 = Math["clz32"];if (!Math["trunc"]) Math["trunc"] = function (x) {
+    return x < 0 ? Math.ceil(x) : Math.floor(x);
+  };Math.trunc = Math["trunc"];var Math_abs = Math.abs;var Math_ceil = Math.ceil;var Math_floor = Math.floor;var Math_min = Math.min;var runDependencies = 0;var runDependencyWatcher = null;var dependenciesFulfilled = null;function getUniqueRunDependency(id) {
+    return id;
+  }function addRunDependency(id) {
+    runDependencies++;if (Module["monitorRunDependencies"]) {
+      Module["monitorRunDependencies"](runDependencies);
+    }
+  }Module["addRunDependency"] = addRunDependency;function removeRunDependency(id) {
+    runDependencies--;if (Module["monitorRunDependencies"]) {
+      Module["monitorRunDependencies"](runDependencies);
+    }if (runDependencies == 0) {
+      if (runDependencyWatcher !== null) {
+        clearInterval(runDependencyWatcher);runDependencyWatcher = null;
+      }if (dependenciesFulfilled) {
+        var callback = dependenciesFulfilled;dependenciesFulfilled = null;callback();
+      }
+    }
+  }Module["removeRunDependency"] = removeRunDependency;Module["preloadedImages"] = {};Module["preloadedAudios"] = {};var ASM_CONSTS = [function ($0, $1, $2, $3, $4, $5, $6, $7) {
+    return _nbind.callbackSignatureList[$0].apply(this, arguments);
+  }];function _emscripten_asm_const_iiiiiiii(code, a0, a1, a2, a3, a4, a5, a6) {
+    return ASM_CONSTS[code](a0, a1, a2, a3, a4, a5, a6);
+  }function _emscripten_asm_const_iiiii(code, a0, a1, a2, a3) {
+    return ASM_CONSTS[code](a0, a1, a2, a3);
+  }function _emscripten_asm_const_iiidddddd(code, a0, a1, a2, a3, a4, a5, a6, a7) {
+    return ASM_CONSTS[code](a0, a1, a2, a3, a4, a5, a6, a7);
+  }function _emscripten_asm_const_iiididi(code, a0, a1, a2, a3, a4, a5) {
+    return ASM_CONSTS[code](a0, a1, a2, a3, a4, a5);
+  }function _emscripten_asm_const_iiii(code, a0, a1, a2) {
+    return ASM_CONSTS[code](a0, a1, a2);
+  }function _emscripten_asm_const_iiiid(code, a0, a1, a2, a3) {
+    return ASM_CONSTS[code](a0, a1, a2, a3);
+  }function _emscripten_asm_const_iiiiii(code, a0, a1, a2, a3, a4) {
+    return ASM_CONSTS[code](a0, a1, a2, a3, a4);
+  }STATIC_BASE = Runtime.GLOBAL_BASE;STATICTOP = STATIC_BASE + 12800;__ATINIT__.push({ func: function () {
+      __GLOBAL__sub_I_Yoga_cpp();
+    } }, { func: function () {
+      __GLOBAL__sub_I_nbind_cc();
+    } }, { func: function () {
+      __GLOBAL__sub_I_common_cc();
+    } }, { func: function () {
+      __GLOBAL__sub_I_Binding_cc();
+    } });allocate([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 192, 127, 0, 0, 192, 127, 0, 0, 192, 127, 3, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 3, 0, 0, 0, 0, 0, 192, 127, 3, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 192, 127, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 0, 0, 128, 191, 0, 0, 128, 191, 0, 0, 192, 127, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 128, 63, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 190, 12, 0, 0, 200, 12, 0, 0, 208, 12, 0, 0, 216, 12, 0, 0, 230, 12, 0, 0, 242, 12, 0, 0, 1, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 192, 127, 3, 0, 0, 0, 180, 45, 0, 0, 181, 45, 0, 0, 182, 45, 0, 0, 181, 45, 0, 0, 182, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 1, 0, 0, 0, 4, 0, 0, 0, 183, 45, 0, 0, 181, 45, 0, 0, 181, 45, 0, 0, 181, 45, 0, 0, 181, 45, 0, 0, 181, 45, 0, 0, 181, 45, 0, 0, 184, 45, 0, 0, 185, 45, 0, 0, 181, 45, 0, 0, 181, 45, 0, 0, 182, 45, 0, 0, 186, 45, 0, 0, 185, 45, 0, 0, 148, 4, 0, 0, 3, 0, 0, 0, 187, 45, 0, 0, 164, 4, 0, 0, 188, 45, 0, 0, 2, 0, 0, 0, 189, 45, 0, 0, 164, 4, 0, 0, 188, 45, 0, 0, 185, 45, 0, 0, 164, 4, 0, 0, 185, 45, 0, 0, 164, 4, 0, 0, 188, 45, 0, 0, 181, 45, 0, 0, 182, 45, 0, 0, 181, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 5, 0, 0, 0, 6, 0, 0, 0, 1, 0, 0, 0, 7, 0, 0, 0, 183, 45, 0, 0, 182, 45, 0, 0, 181, 45, 0, 0, 190, 45, 0, 0, 190, 45, 0, 0, 182, 45, 0, 0, 182, 45, 0, 0, 185, 45, 0, 0, 181, 45, 0, 0, 185, 45, 0, 0, 182, 45, 0, 0, 181, 45, 0, 0, 185, 45, 0, 0, 182, 45, 0, 0, 185, 45, 0, 0, 48, 5, 0, 0, 3, 0, 0, 0, 56, 5, 0, 0, 1, 0, 0, 0, 189, 45, 0, 0, 185, 45, 0, 0, 164, 4, 0, 0, 76, 5, 0, 0, 2, 0, 0, 0, 191, 45, 0, 0, 186, 45, 0, 0, 182, 45, 0, 0, 185, 45, 0, 0, 192, 45, 0, 0, 185, 45, 0, 0, 182, 45, 0, 0, 186, 45, 0, 0, 185, 45, 0, 0, 76, 5, 0, 0, 76, 5, 0, 0, 136, 5, 0, 0, 182, 45, 0, 0, 181, 45, 0, 0, 2, 0, 0, 0, 190, 45, 0, 0, 136, 5, 0, 0, 56, 19, 0, 0, 156, 5, 0, 0, 2, 0, 0, 0, 184, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 8, 0, 0, 0, 9, 0, 0, 0, 1, 0, 0, 0, 10, 0, 0, 0, 204, 5, 0, 0, 181, 45, 0, 0, 181, 45, 0, 0, 2, 0, 0, 0, 180, 45, 0, 0, 204, 5, 0, 0, 2, 0, 0, 0, 195, 45, 0, 0, 236, 5, 0, 0, 97, 19, 0, 0, 198, 45, 0, 0, 211, 45, 0, 0, 212, 45, 0, 0, 213, 45, 0, 0, 214, 45, 0, 0, 215, 45, 0, 0, 188, 45, 0, 0, 182, 45, 0, 0, 216, 45, 0, 0, 217, 45, 0, 0, 218, 45, 0, 0, 219, 45, 0, 0, 192, 45, 0, 0, 181, 45, 0, 0, 0, 0, 0, 0, 185, 45, 0, 0, 110, 19, 0, 0, 186, 45, 0, 0, 115, 19, 0, 0, 221, 45, 0, 0, 120, 19, 0, 0, 148, 4, 0, 0, 132, 19, 0, 0, 96, 6, 0, 0, 145, 19, 0, 0, 222, 45, 0, 0, 164, 19, 0, 0, 223, 45, 0, 0, 173, 19, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 104, 6, 0, 0, 1, 0, 0, 0, 187, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 11, 0, 0, 0, 12, 0, 0, 0, 1, 0, 0, 0, 13, 0, 0, 0, 185, 45, 0, 0, 224, 45, 0, 0, 164, 6, 0, 0, 188, 45, 0, 0, 172, 6, 0, 0, 180, 6, 0, 0, 2, 0, 0, 0, 188, 6, 0, 0, 7, 0, 0, 0, 224, 45, 0, 0, 7, 0, 0, 0, 164, 6, 0, 0, 1, 0, 0, 0, 213, 45, 0, 0, 185, 45, 0, 0, 224, 45, 0, 0, 172, 6, 0, 0, 185, 45, 0, 0, 224, 45, 0, 0, 164, 6, 0, 0, 185, 45, 0, 0, 224, 45, 0, 0, 211, 45, 0, 0, 211, 45, 0, 0, 222, 45, 0, 0, 211, 45, 0, 0, 224, 45, 0, 0, 222, 45, 0, 0, 211, 45, 0, 0, 224, 45, 0, 0, 172, 6, 0, 0, 222, 45, 0, 0, 211, 45, 0, 0, 224, 45, 0, 0, 188, 45, 0, 0, 222, 45, 0, 0, 211, 45, 0, 0, 40, 7, 0, 0, 188, 45, 0, 0, 2, 0, 0, 0, 224, 45, 0, 0, 185, 45, 0, 0, 188, 45, 0, 0, 188, 45, 0, 0, 188, 45, 0, 0, 188, 45, 0, 0, 222, 45, 0, 0, 224, 45, 0, 0, 148, 4, 0, 0, 185, 45, 0, 0, 148, 4, 0, 0, 148, 4, 0, 0, 148, 4, 0, 0, 148, 4, 0, 0, 148, 4, 0, 0, 185, 45, 0, 0, 164, 6, 0, 0, 148, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 14, 0, 0, 0, 15, 0, 0, 0, 1, 0, 0, 0, 16, 0, 0, 0, 148, 7, 0, 0, 2, 0, 0, 0, 225, 45, 0, 0, 183, 45, 0, 0, 188, 45, 0, 0, 168, 7, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 2, 0, 0, 0, 234, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 148, 45, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 28, 9, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 2, 0, 0, 0, 242, 45, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 255, 255, 255, 255, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 67, 111, 117, 108, 100, 32, 110, 111, 116, 32, 97, 108, 108, 111, 99, 97, 116, 101, 32, 109, 101, 109, 111, 114, 121, 32, 102, 111, 114, 32, 110, 111, 100, 101, 0, 67, 97, 110, 110, 111, 116, 32, 114, 101, 115, 101, 116, 32, 97, 32, 110, 111, 100, 101, 32, 119, 104, 105, 99, 104, 32, 115, 116, 105, 108, 108, 32, 104, 97, 115, 32, 99, 104, 105, 108, 100, 114, 101, 110, 32, 97, 116, 116, 97, 99, 104, 101, 100, 0, 67, 97, 110, 110, 111, 116, 32, 114, 101, 115, 101, 116, 32, 97, 32, 110, 111, 100, 101, 32, 115, 116, 105, 108, 108, 32, 97, 116, 116, 97, 99, 104, 101, 100, 32, 116, 111, 32, 97, 32, 112, 97, 114, 101, 110, 116, 0, 67, 111, 117, 108, 100, 32, 110, 111, 116, 32, 97, 108, 108, 111, 99, 97, 116, 101, 32, 109, 101, 109, 111, 114, 121, 32, 102, 111, 114, 32, 99, 111, 110, 102, 105, 103, 0, 67, 97, 110, 110, 111, 116, 32, 115, 101, 116, 32, 109, 101, 97, 115, 117, 114, 101, 32, 102, 117, 110, 99, 116, 105, 111, 110, 58, 32, 78, 111, 100, 101, 115, 32, 119, 105, 116, 104, 32, 109, 101, 97, 115, 117, 114, 101, 32, 102, 117, 110, 99, 116, 105, 111, 110, 115, 32, 99, 97, 110, 110, 111, 116, 32, 104, 97, 118, 101, 32, 99, 104, 105, 108, 100, 114, 101, 110, 46, 0, 67, 104, 105, 108, 100, 32, 97, 108, 114, 101, 97, 100, 121, 32, 104, 97, 115, 32, 97, 32, 112, 97, 114, 101, 110, 116, 44, 32, 105, 116, 32, 109, 117, 115, 116, 32, 98, 101, 32, 114, 101, 109, 111, 118, 101, 100, 32, 102, 105, 114, 115, 116, 46, 0, 67, 97, 110, 110, 111, 116, 32, 97, 100, 100, 32, 99, 104, 105, 108, 100, 58, 32, 78, 111, 100, 101, 115, 32, 119, 105, 116, 104, 32, 109, 101, 97, 115, 117, 114, 101, 32, 102, 117, 110, 99, 116, 105, 111, 110, 115, 32, 99, 97, 110, 110, 111, 116, 32, 104, 97, 118, 101, 32, 99, 104, 105, 108, 100, 114, 101, 110, 46, 0, 79, 110, 108, 121, 32, 108, 101, 97, 102, 32, 110, 111, 100, 101, 115, 32, 119, 105, 116, 104, 32, 99, 117, 115, 116, 111, 109, 32, 109, 101, 97, 115, 117, 114, 101, 32, 102, 117, 110, 99, 116, 105, 111, 110, 115, 115, 104, 111, 117, 108, 100, 32, 109, 97, 110, 117, 97, 108, 108, 121, 32, 109, 97, 114, 107, 32, 116, 104, 101, 109, 115, 101, 108, 118, 101, 115, 32, 97, 115, 32, 100, 105, 114, 116, 121, 0, 67, 97, 110, 110, 111, 116, 32, 103, 101, 116, 32, 108, 97, 121, 111, 117, 116, 32, 112, 114, 111, 112, 101, 114, 116, 105, 101, 115, 32, 111, 102, 32, 109, 117, 108, 116, 105, 45, 101, 100, 103, 101, 32, 115, 104, 111, 114, 116, 104, 97, 110, 100, 115, 0, 37, 115, 37, 100, 46, 123, 91, 115, 107, 105, 112, 112, 101, 100, 93, 32, 0, 119, 109, 58, 32, 37, 115, 44, 32, 104, 109, 58, 32, 37, 115, 44, 32, 97, 119, 58, 32, 37, 102, 32, 97, 104, 58, 32, 37, 102, 32, 61, 62, 32, 100, 58, 32, 40, 37, 102, 44, 32, 37, 102, 41, 32, 37, 115, 10, 0, 37, 115, 37, 100, 46, 123, 37, 115, 0, 42, 0, 119, 109, 58, 32, 37, 115, 44, 32, 104, 109, 58, 32, 37, 115, 44, 32, 97, 119, 58, 32, 37, 102, 32, 97, 104, 58, 32, 37, 102, 32, 37, 115, 10, 0, 37, 115, 37, 100, 46, 125, 37, 115, 0, 119, 109, 58, 32, 37, 115, 44, 32, 104, 109, 58, 32, 37, 115, 44, 32, 100, 58, 32, 40, 37, 102, 44, 32, 37, 102, 41, 32, 37, 115, 10, 0, 79, 117, 116, 32, 111, 102, 32, 99, 97, 99, 104, 101, 32, 101, 110, 116, 114, 105, 101, 115, 33, 10, 0, 83, 99, 97, 108, 101, 32, 102, 97, 99, 116, 111, 114, 32, 115, 104, 111, 117, 108, 100, 32, 110, 111, 116, 32, 98, 101, 32, 108, 101, 115, 115, 32, 116, 104, 97, 110, 32, 122, 101, 114, 111, 0, 105, 110, 105, 116, 105, 97, 108, 0, 37, 115, 10, 0, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 0, 85, 78, 68, 69, 70, 73, 78, 69, 68, 0, 69, 88, 65, 67, 84, 76, 89, 0, 65, 84, 95, 77, 79, 83, 84, 0, 76, 65, 89, 95, 85, 78, 68, 69, 70, 73, 78, 69, 68, 0, 76, 65, 89, 95, 69, 88, 65, 67, 84, 76, 89, 0, 76, 65, 89, 95, 65, 84, 95, 77, 79, 83, 84, 0, 97, 118, 97, 105, 108, 97, 98, 108, 101, 87, 105, 100, 116, 104, 32, 105, 115, 32, 105, 110, 100, 101, 102, 105, 110, 105, 116, 101, 32, 115, 111, 32, 119, 105, 100, 116, 104, 77, 101, 97, 115, 117, 114, 101, 77, 111, 100, 101, 32, 109, 117, 115, 116, 32, 98, 101, 32, 89, 71, 77, 101, 97, 115, 117, 114, 101, 77, 111, 100, 101, 85, 110, 100, 101, 102, 105, 110, 101, 100, 0, 97, 118, 97, 105, 108, 97, 98, 108, 101, 72, 101, 105, 103, 104, 116, 32, 105, 115, 32, 105, 110, 100, 101, 102, 105, 110, 105, 116, 101, 32, 115, 111, 32, 104, 101, 105, 103, 104, 116, 77, 101, 97, 115, 117, 114, 101, 77, 111, 100, 101, 32, 109, 117, 115, 116, 32, 98, 101, 32, 89, 71, 77, 101, 97, 115, 117, 114, 101, 77, 111, 100, 101, 85, 110, 100, 101, 102, 105, 110, 101, 100, 0, 102, 108, 101, 120, 0, 115, 116, 114, 101, 116, 99, 104, 0, 109, 117, 108, 116, 105, 108, 105, 110, 101, 45, 115, 116, 114, 101, 116, 99, 104, 0, 69, 120, 112, 101, 99, 116, 101, 100, 32, 110, 111, 100, 101, 32, 116, 111, 32, 104, 97, 118, 101, 32, 99, 117, 115, 116, 111, 109, 32, 109, 101, 97, 115, 117, 114, 101, 32, 102, 117, 110, 99, 116, 105, 111, 110, 0, 109, 101, 97, 115, 117, 114, 101, 0, 69, 120, 112, 101, 99, 116, 32, 99, 117, 115, 116, 111, 109, 32, 98, 97, 115, 101, 108, 105, 110, 101, 32, 102, 117, 110, 99, 116, 105, 111, 110, 32, 116, 111, 32, 110, 111, 116, 32, 114, 101, 116, 117, 114, 110, 32, 78, 97, 78, 0, 97, 98, 115, 45, 109, 101, 97, 115, 117, 114, 101, 0, 97, 98, 115, 45, 108, 97, 121, 111, 117, 116, 0, 78, 111, 100, 101, 0, 99, 114, 101, 97, 116, 101, 68, 101, 102, 97, 117, 108, 116, 0, 99, 114, 101, 97, 116, 101, 87, 105, 116, 104, 67, 111, 110, 102, 105, 103, 0, 100, 101, 115, 116, 114, 111, 121, 0, 114, 101, 115, 101, 116, 0, 99, 111, 112, 121, 83, 116, 121, 108, 101, 0, 115, 101, 116, 80, 111, 115, 105, 116, 105, 111, 110, 84, 121, 112, 101, 0, 115, 101, 116, 80, 111, 115, 105, 116, 105, 111, 110, 0, 115, 101, 116, 80, 111, 115, 105, 116, 105, 111, 110, 80, 101, 114, 99, 101, 110, 116, 0, 115, 101, 116, 65, 108, 105, 103, 110, 67, 111, 110, 116, 101, 110, 116, 0, 115, 101, 116, 65, 108, 105, 103, 110, 73, 116, 101, 109, 115, 0, 115, 101, 116, 65, 108, 105, 103, 110, 83, 101, 108, 102, 0, 115, 101, 116, 70, 108, 101, 120, 68, 105, 114, 101, 99, 116, 105, 111, 110, 0, 115, 101, 116, 70, 108, 101, 120, 87, 114, 97, 112, 0, 115, 101, 116, 74, 117, 115, 116, 105, 102, 121, 67, 111, 110, 116, 101, 110, 116, 0, 115, 101, 116, 77, 97, 114, 103, 105, 110, 0, 115, 101, 116, 77, 97, 114, 103, 105, 110, 80, 101, 114, 99, 101, 110, 116, 0, 115, 101, 116, 77, 97, 114, 103, 105, 110, 65, 117, 116, 111, 0, 115, 101, 116, 79, 118, 101, 114, 102, 108, 111, 119, 0, 115, 101, 116, 68, 105, 115, 112, 108, 97, 121, 0, 115, 101, 116, 70, 108, 101, 120, 0, 115, 101, 116, 70, 108, 101, 120, 66, 97, 115, 105, 115, 0, 115, 101, 116, 70, 108, 101, 120, 66, 97, 115, 105, 115, 80, 101, 114, 99, 101, 110, 116, 0, 115, 101, 116, 70, 108, 101, 120, 71, 114, 111, 119, 0, 115, 101, 116, 70, 108, 101, 120, 83, 104, 114, 105, 110, 107, 0, 115, 101, 116, 87, 105, 100, 116, 104, 0, 115, 101, 116, 87, 105, 100, 116, 104, 80, 101, 114, 99, 101, 110, 116, 0, 115, 101, 116, 87, 105, 100, 116, 104, 65, 117, 116, 111, 0, 115, 101, 116, 72, 101, 105, 103, 104, 116, 0, 115, 101, 116, 72, 101, 105, 103, 104, 116, 80, 101, 114, 99, 101, 110, 116, 0, 115, 101, 116, 72, 101, 105, 103, 104, 116, 65, 117, 116, 111, 0, 115, 101, 116, 77, 105, 110, 87, 105, 100, 116, 104, 0, 115, 101, 116, 77, 105, 110, 87, 105, 100, 116, 104, 80, 101, 114, 99, 101, 110, 116, 0, 115, 101, 116, 77, 105, 110, 72, 101, 105, 103, 104, 116, 0, 115, 101, 116, 77, 105, 110, 72, 101, 105, 103, 104, 116, 80, 101, 114, 99, 101, 110, 116, 0, 115, 101, 116, 77, 97, 120, 87, 105, 100, 116, 104, 0, 115, 101, 116, 77, 97, 120, 87, 105, 100, 116, 104, 80, 101, 114, 99, 101, 110, 116, 0, 115, 101, 116, 77, 97, 120, 72, 101, 105, 103, 104, 116, 0, 115, 101, 116, 77, 97, 120, 72, 101, 105, 103, 104, 116, 80, 101, 114, 99, 101, 110, 116, 0, 115, 101, 116, 65, 115, 112, 101, 99, 116, 82, 97, 116, 105, 111, 0, 115, 101, 116, 66, 111, 114, 100, 101, 114, 0, 115, 101, 116, 80, 97, 100, 100, 105, 110, 103, 0, 115, 101, 116, 80, 97, 100, 100, 105, 110, 103, 80, 101, 114, 99, 101, 110, 116, 0, 103, 101, 116, 80, 111, 115, 105, 116, 105, 111, 110, 84, 121, 112, 101, 0, 103, 101, 116, 80, 111, 115, 105, 116, 105, 111, 110, 0, 103, 101, 116, 65, 108, 105, 103, 110, 67, 111, 110, 116, 101, 110, 116, 0, 103, 101, 116, 65, 108, 105, 103, 110, 73, 116, 101, 109, 115, 0, 103, 101, 116, 65, 108, 105, 103, 110, 83, 101, 108, 102, 0, 103, 101, 116, 70, 108, 101, 120, 68, 105, 114, 101, 99, 116, 105, 111, 110, 0, 103, 101, 116, 70, 108, 101, 120, 87, 114, 97, 112, 0, 103, 101, 116, 74, 117, 115, 116, 105, 102, 121, 67, 111, 110, 116, 101, 110, 116, 0, 103, 101, 116, 77, 97, 114, 103, 105, 110, 0, 103, 101, 116, 70, 108, 101, 120, 66, 97, 115, 105, 115, 0, 103, 101, 116, 70, 108, 101, 120, 71, 114, 111, 119, 0, 103, 101, 116, 70, 108, 101, 120, 83, 104, 114, 105, 110, 107, 0, 103, 101, 116, 87, 105, 100, 116, 104, 0, 103, 101, 116, 72, 101, 105, 103, 104, 116, 0, 103, 101, 116, 77, 105, 110, 87, 105, 100, 116, 104, 0, 103, 101, 116, 77, 105, 110, 72, 101, 105, 103, 104, 116, 0, 103, 101, 116, 77, 97, 120, 87, 105, 100, 116, 104, 0, 103, 101, 116, 77, 97, 120, 72, 101, 105, 103, 104, 116, 0, 103, 101, 116, 65, 115, 112, 101, 99, 116, 82, 97, 116, 105, 111, 0, 103, 101, 116, 66, 111, 114, 100, 101, 114, 0, 103, 101, 116, 79, 118, 101, 114, 102, 108, 111, 119, 0, 103, 101, 116, 68, 105, 115, 112, 108, 97, 121, 0, 103, 101, 116, 80, 97, 100, 100, 105, 110, 103, 0, 105, 110, 115, 101, 114, 116, 67, 104, 105, 108, 100, 0, 114, 101, 109, 111, 118, 101, 67, 104, 105, 108, 100, 0, 103, 101, 116, 67, 104, 105, 108, 100, 67, 111, 117, 110, 116, 0, 103, 101, 116, 80, 97, 114, 101, 110, 116, 0, 103, 101, 116, 67, 104, 105, 108, 100, 0, 115, 101, 116, 77, 101, 97, 115, 117, 114, 101, 70, 117, 110, 99, 0, 117, 110, 115, 101, 116, 77, 101, 97, 115, 117, 114, 101, 70, 117, 110, 99, 0, 109, 97, 114, 107, 68, 105, 114, 116, 121, 0, 105, 115, 68, 105, 114, 116, 121, 0, 99, 97, 108, 99, 117, 108, 97, 116, 101, 76, 97, 121, 111, 117, 116, 0, 103, 101, 116, 67, 111, 109, 112, 117, 116, 101, 100, 76, 101, 102, 116, 0, 103, 101, 116, 67, 111, 109, 112, 117, 116, 101, 100, 82, 105, 103, 104, 116, 0, 103, 101, 116, 67, 111, 109, 112, 117, 116, 101, 100, 84, 111, 112, 0, 103, 101, 116, 67, 111, 109, 112, 117, 116, 101, 100, 66, 111, 116, 116, 111, 109, 0, 103, 101, 116, 67, 111, 109, 112, 117, 116, 101, 100, 87, 105, 100, 116, 104, 0, 103, 101, 116, 67, 111, 109, 112, 117, 116, 101, 100, 72, 101, 105, 103, 104, 116, 0, 103, 101, 116, 67, 111, 109, 112, 117, 116, 101, 100, 76, 97, 121, 111, 117, 116, 0, 103, 101, 116, 67, 111, 109, 112, 117, 116, 101, 100, 77, 97, 114, 103, 105, 110, 0, 103, 101, 116, 67, 111, 109, 112, 117, 116, 101, 100, 66, 111, 114, 100, 101, 114, 0, 103, 101, 116, 67, 111, 109, 112, 117, 116, 101, 100, 80, 97, 100, 100, 105, 110, 103, 0, 67, 111, 110, 102, 105, 103, 0, 99, 114, 101, 97, 116, 101, 0, 115, 101, 116, 69, 120, 112, 101, 114, 105, 109, 101, 110, 116, 97, 108, 70, 101, 97, 116, 117, 114, 101, 69, 110, 97, 98, 108, 101, 100, 0, 115, 101, 116, 80, 111, 105, 110, 116, 83, 99, 97, 108, 101, 70, 97, 99, 116, 111, 114, 0, 105, 115, 69, 120, 112, 101, 114, 105, 109, 101, 110, 116, 97, 108, 70, 101, 97, 116, 117, 114, 101, 69, 110, 97, 98, 108, 101, 100, 0, 86, 97, 108, 117, 101, 0, 76, 97, 121, 111, 117, 116, 0, 83, 105, 122, 101, 0, 103, 101, 116, 73, 110, 115, 116, 97, 110, 99, 101, 67, 111, 117, 110, 116, 0, 73, 110, 116, 54, 52, 0, 1, 1, 1, 2, 2, 4, 4, 4, 4, 8, 8, 4, 8, 118, 111, 105, 100, 0, 98, 111, 111, 108, 0, 115, 116, 100, 58, 58, 115, 116, 114, 105, 110, 103, 0, 99, 98, 70, 117, 110, 99, 116, 105, 111, 110, 32, 38, 0, 99, 111, 110, 115, 116, 32, 99, 98, 70, 117, 110, 99, 116, 105, 111, 110, 32, 38, 0, 69, 120, 116, 101, 114, 110, 97, 108, 0, 66, 117, 102, 102, 101, 114, 0, 78, 66, 105, 110, 100, 73, 68, 0, 78, 66, 105, 110, 100, 0, 98, 105, 110, 100, 95, 118, 97, 108, 117, 101, 0, 114, 101, 102, 108, 101, 99, 116, 0, 113, 117, 101, 114, 121, 84, 121, 112, 101, 0, 108, 97, 108, 108, 111, 99, 0, 108, 114, 101, 115, 101, 116, 0, 123, 114, 101, 116, 117, 114, 110, 40, 95, 110, 98, 105, 110, 100, 46, 99, 97, 108, 108, 98, 97, 99, 107, 83, 105, 103, 110, 97, 116, 117, 114, 101, 76, 105, 115, 116, 91, 36, 48, 93, 46, 97, 112, 112, 108, 121, 40, 116, 104, 105, 115, 44, 97, 114, 103, 117, 109, 101, 110, 116, 115, 41, 41, 59, 125, 0, 95, 110, 98, 105, 110, 100, 95, 110, 101, 119, 0, 17, 0, 10, 0, 17, 17, 17, 0, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 15, 10, 17, 17, 17, 3, 10, 7, 0, 1, 19, 9, 11, 11, 0, 0, 9, 6, 11, 0, 0, 11, 0, 6, 17, 0, 0, 0, 17, 17, 17, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 17, 0, 10, 10, 17, 17, 17, 0, 10, 0, 0, 2, 0, 9, 11, 0, 0, 0, 9, 0, 11, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 12, 0, 0, 0, 0, 9, 12, 0, 0, 0, 0, 0, 12, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 13, 0, 0, 0, 4, 13, 0, 0, 0, 0, 9, 14, 0, 0, 0, 0, 0, 14, 0, 0, 14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 16, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 0, 0, 0, 0, 15, 0, 0, 0, 0, 9, 16, 0, 0, 0, 0, 0, 16, 0, 0, 16, 0, 0, 18, 0, 0, 0, 18, 18, 18, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 18, 0, 0, 0, 18, 18, 18, 0, 0, 0, 0, 0, 0, 9, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 0, 0, 0, 0, 10, 0, 0, 0, 0, 9, 11, 0, 0, 0, 0, 0, 11, 0, 0, 11, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 12, 0, 0, 0, 0, 12, 0, 0, 0, 0, 9, 12, 0, 0, 0, 0, 0, 12, 0, 0, 12, 0, 0, 45, 43, 32, 32, 32, 48, 88, 48, 120, 0, 40, 110, 117, 108, 108, 41, 0, 45, 48, 88, 43, 48, 88, 32, 48, 88, 45, 48, 120, 43, 48, 120, 32, 48, 120, 0, 105, 110, 102, 0, 73, 78, 70, 0, 110, 97, 110, 0, 78, 65, 78, 0, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 46, 0, 84, 33, 34, 25, 13, 1, 2, 3, 17, 75, 28, 12, 16, 4, 11, 29, 18, 30, 39, 104, 110, 111, 112, 113, 98, 32, 5, 6, 15, 19, 20, 21, 26, 8, 22, 7, 40, 36, 23, 24, 9, 10, 14, 27, 31, 37, 35, 131, 130, 125, 38, 42, 43, 60, 61, 62, 63, 67, 71, 74, 77, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 99, 100, 101, 102, 103, 105, 106, 107, 108, 114, 115, 116, 121, 122, 123, 124, 0, 73, 108, 108, 101, 103, 97, 108, 32, 98, 121, 116, 101, 32, 115, 101, 113, 117, 101, 110, 99, 101, 0, 68, 111, 109, 97, 105, 110, 32, 101, 114, 114, 111, 114, 0, 82, 101, 115, 117, 108, 116, 32, 110, 111, 116, 32, 114, 101, 112, 114, 101, 115, 101, 110, 116, 97, 98, 108, 101, 0, 78, 111, 116, 32, 97, 32, 116, 116, 121, 0, 80, 101, 114, 109, 105, 115, 115, 105, 111, 110, 32, 100, 101, 110, 105, 101, 100, 0, 79, 112, 101, 114, 97, 116, 105, 111, 110, 32, 110, 111, 116, 32, 112, 101, 114, 109, 105, 116, 116, 101, 100, 0, 78, 111, 32, 115, 117, 99, 104, 32, 102, 105, 108, 101, 32, 111, 114, 32, 100, 105, 114, 101, 99, 116, 111, 114, 121, 0, 78, 111, 32, 115, 117, 99, 104, 32, 112, 114, 111, 99, 101, 115, 115, 0, 70, 105, 108, 101, 32, 101, 120, 105, 115, 116, 115, 0, 86, 97, 108, 117, 101, 32, 116, 111, 111, 32, 108, 97, 114, 103, 101, 32, 102, 111, 114, 32, 100, 97, 116, 97, 32, 116, 121, 112, 101, 0, 78, 111, 32, 115, 112, 97, 99, 101, 32, 108, 101, 102, 116, 32, 111, 110, 32, 100, 101, 118, 105, 99, 101, 0, 79, 117, 116, 32, 111, 102, 32, 109, 101, 109, 111, 114, 121, 0, 82, 101, 115, 111, 117, 114, 99, 101, 32, 98, 117, 115, 121, 0, 73, 110, 116, 101, 114, 114, 117, 112, 116, 101, 100, 32, 115, 121, 115, 116, 101, 109, 32, 99, 97, 108, 108, 0, 82, 101, 115, 111, 117, 114, 99, 101, 32, 116, 101, 109, 112, 111, 114, 97, 114, 105, 108, 121, 32, 117, 110, 97, 118, 97, 105, 108, 97, 98, 108, 101, 0, 73, 110, 118, 97, 108, 105, 100, 32, 115, 101, 101, 107, 0, 67, 114, 111, 115, 115, 45, 100, 101, 118, 105, 99, 101, 32, 108, 105, 110, 107, 0, 82, 101, 97, 100, 45, 111, 110, 108, 121, 32, 102, 105, 108, 101, 32, 115, 121, 115, 116, 101, 109, 0, 68, 105, 114, 101, 99, 116, 111, 114, 121, 32, 110, 111, 116, 32, 101, 109, 112, 116, 121, 0, 67, 111, 110, 110, 101, 99, 116, 105, 111, 110, 32, 114, 101, 115, 101, 116, 32, 98, 121, 32, 112, 101, 101, 114, 0, 79, 112, 101, 114, 97, 116, 105, 111, 110, 32, 116, 105, 109, 101, 100, 32, 111, 117, 116, 0, 67, 111, 110, 110, 101, 99, 116, 105, 111, 110, 32, 114, 101, 102, 117, 115, 101, 100, 0, 72, 111, 115, 116, 32, 105, 115, 32, 100, 111, 119, 110, 0, 72, 111, 115, 116, 32, 105, 115, 32, 117, 110, 114, 101, 97, 99, 104, 97, 98, 108, 101, 0, 65, 100, 100, 114, 101, 115, 115, 32, 105, 110, 32, 117, 115, 101, 0, 66, 114, 111, 107, 101, 110, 32, 112, 105, 112, 101, 0, 73, 47, 79, 32, 101, 114, 114, 111, 114, 0, 78, 111, 32, 115, 117, 99, 104, 32, 100, 101, 118, 105, 99, 101, 32, 111, 114, 32, 97, 100, 100, 114, 101, 115, 115, 0, 66, 108, 111, 99, 107, 32, 100, 101, 118, 105, 99, 101, 32, 114, 101, 113, 117, 105, 114, 101, 100, 0, 78, 111, 32, 115, 117, 99, 104, 32, 100, 101, 118, 105, 99, 101, 0, 78, 111, 116, 32, 97, 32, 100, 105, 114, 101, 99, 116, 111, 114, 121, 0, 73, 115, 32, 97, 32, 100, 105, 114, 101, 99, 116, 111, 114, 121, 0, 84, 101, 120, 116, 32, 102, 105, 108, 101, 32, 98, 117, 115, 121, 0, 69, 120, 101, 99, 32, 102, 111, 114, 109, 97, 116, 32, 101, 114, 114, 111, 114, 0, 73, 110, 118, 97, 108, 105, 100, 32, 97, 114, 103, 117, 109, 101, 110, 116, 0, 65, 114, 103, 117, 109, 101, 110, 116, 32, 108, 105, 115, 116, 32, 116, 111, 111, 32, 108, 111, 110, 103, 0, 83, 121, 109, 98, 111, 108, 105, 99, 32, 108, 105, 110, 107, 32, 108, 111, 111, 112, 0, 70, 105, 108, 101, 110, 97, 109, 101, 32, 116, 111, 111, 32, 108, 111, 110, 103, 0, 84, 111, 111, 32, 109, 97, 110, 121, 32, 111, 112, 101, 110, 32, 102, 105, 108, 101, 115, 32, 105, 110, 32, 115, 121, 115, 116, 101, 109, 0, 78, 111, 32, 102, 105, 108, 101, 32, 100, 101, 115, 99, 114, 105, 112, 116, 111, 114, 115, 32, 97, 118, 97, 105, 108, 97, 98, 108, 101, 0, 66, 97, 100, 32, 102, 105, 108, 101, 32, 100, 101, 115, 99, 114, 105, 112, 116, 111, 114, 0, 78, 111, 32, 99, 104, 105, 108, 100, 32, 112, 114, 111, 99, 101, 115, 115, 0, 66, 97, 100, 32, 97, 100, 100, 114, 101, 115, 115, 0, 70, 105, 108, 101, 32, 116, 111, 111, 32, 108, 97, 114, 103, 101, 0, 84, 111, 111, 32, 109, 97, 110, 121, 32, 108, 105, 110, 107, 115, 0, 78, 111, 32, 108, 111, 99, 107, 115, 32, 97, 118, 97, 105, 108, 97, 98, 108, 101, 0, 82, 101, 115, 111, 117, 114, 99, 101, 32, 100, 101, 97, 100, 108, 111, 99, 107, 32, 119, 111, 117, 108, 100, 32, 111, 99, 99, 117, 114, 0, 83, 116, 97, 116, 101, 32, 110, 111, 116, 32, 114, 101, 99, 111, 118, 101, 114, 97, 98, 108, 101, 0, 80, 114, 101, 118, 105, 111, 117, 115, 32, 111, 119, 110, 101, 114, 32, 100, 105, 101, 100, 0, 79, 112, 101, 114, 97, 116, 105, 111, 110, 32, 99, 97, 110, 99, 101, 108, 101, 100, 0, 70, 117, 110, 99, 116, 105, 111, 110, 32, 110, 111, 116, 32, 105, 109, 112, 108, 101, 109, 101, 110, 116, 101, 100, 0, 78, 111, 32, 109, 101, 115, 115, 97, 103, 101, 32, 111, 102, 32, 100, 101, 115, 105, 114, 101, 100, 32, 116, 121, 112, 101, 0, 73, 100, 101, 110, 116, 105, 102, 105, 101, 114, 32, 114, 101, 109, 111, 118, 101, 100, 0, 68, 101, 118, 105, 99, 101, 32, 110, 111, 116, 32, 97, 32, 115, 116, 114, 101, 97, 109, 0, 78, 111, 32, 100, 97, 116, 97, 32, 97, 118, 97, 105, 108, 97, 98, 108, 101, 0, 68, 101, 118, 105, 99, 101, 32, 116, 105, 109, 101, 111, 117, 116, 0, 79, 117, 116, 32, 111, 102, 32, 115, 116, 114, 101, 97, 109, 115, 32, 114, 101, 115, 111, 117, 114, 99, 101, 115, 0, 76, 105, 110, 107, 32, 104, 97, 115, 32, 98, 101, 101, 110, 32, 115, 101, 118, 101, 114, 101, 100, 0, 80, 114, 111, 116, 111, 99, 111, 108, 32, 101, 114, 114, 111, 114, 0, 66, 97, 100, 32, 109, 101, 115, 115, 97, 103, 101, 0, 70, 105, 108, 101, 32, 100, 101, 115, 99, 114, 105, 112, 116, 111, 114, 32, 105, 110, 32, 98, 97, 100, 32, 115, 116, 97, 116, 101, 0, 78, 111, 116, 32, 97, 32, 115, 111, 99, 107, 101, 116, 0, 68, 101, 115, 116, 105, 110, 97, 116, 105, 111, 110, 32, 97, 100, 100, 114, 101, 115, 115, 32, 114, 101, 113, 117, 105, 114, 101, 100, 0, 77, 101, 115, 115, 97, 103, 101, 32, 116, 111, 111, 32, 108, 97, 114, 103, 101, 0, 80, 114, 111, 116, 111, 99, 111, 108, 32, 119, 114, 111, 110, 103, 32, 116, 121, 112, 101, 32, 102, 111, 114, 32, 115, 111, 99, 107, 101, 116, 0, 80, 114, 111, 116, 111, 99, 111, 108, 32, 110, 111, 116, 32, 97, 118, 97, 105, 108, 97, 98, 108, 101, 0, 80, 114, 111, 116, 111, 99, 111, 108, 32, 110, 111, 116, 32, 115, 117, 112, 112, 111, 114, 116, 101, 100, 0, 83, 111, 99, 107, 101, 116, 32, 116, 121, 112, 101, 32, 110, 111, 116, 32, 115, 117, 112, 112, 111, 114, 116, 101, 100, 0, 78, 111, 116, 32, 115, 117, 112, 112, 111, 114, 116, 101, 100, 0, 80, 114, 111, 116, 111, 99, 111, 108, 32, 102, 97, 109, 105, 108, 121, 32, 110, 111, 116, 32, 115, 117, 112, 112, 111, 114, 116, 101, 100, 0, 65, 100, 100, 114, 101, 115, 115, 32, 102, 97, 109, 105, 108, 121, 32, 110, 111, 116, 32, 115, 117, 112, 112, 111, 114, 116, 101, 100, 32, 98, 121, 32, 112, 114, 111, 116, 111, 99, 111, 108, 0, 65, 100, 100, 114, 101, 115, 115, 32, 110, 111, 116, 32, 97, 118, 97, 105, 108, 97, 98, 108, 101, 0, 78, 101, 116, 119, 111, 114, 107, 32, 105, 115, 32, 100, 111, 119, 110, 0, 78, 101, 116, 119, 111, 114, 107, 32, 117, 110, 114, 101, 97, 99, 104, 97, 98, 108, 101, 0, 67, 111, 110, 110, 101, 99, 116, 105, 111, 110, 32, 114, 101, 115, 101, 116, 32, 98, 121, 32, 110, 101, 116, 119, 111, 114, 107, 0, 67, 111, 110, 110, 101, 99, 116, 105, 111, 110, 32, 97, 98, 111, 114, 116, 101, 100, 0, 78, 111, 32, 98, 117, 102, 102, 101, 114, 32, 115, 112, 97, 99, 101, 32, 97, 118, 97, 105, 108, 97, 98, 108, 101, 0, 83, 111, 99, 107, 101, 116, 32, 105, 115, 32, 99, 111, 110, 110, 101, 99, 116, 101, 100, 0, 83, 111, 99, 107, 101, 116, 32, 110, 111, 116, 32, 99, 111, 110, 110, 101, 99, 116, 101, 100, 0, 67, 97, 110, 110, 111, 116, 32, 115, 101, 110, 100, 32, 97, 102, 116, 101, 114, 32, 115, 111, 99, 107, 101, 116, 32, 115, 104, 117, 116, 100, 111, 119, 110, 0, 79, 112, 101, 114, 97, 116, 105, 111, 110, 32, 97, 108, 114, 101, 97, 100, 121, 32, 105, 110, 32, 112, 114, 111, 103, 114, 101, 115, 115, 0, 79, 112, 101, 114, 97, 116, 105, 111, 110, 32, 105, 110, 32, 112, 114, 111, 103, 114, 101, 115, 115, 0, 83, 116, 97, 108, 101, 32, 102, 105, 108, 101, 32, 104, 97, 110, 100, 108, 101, 0, 82, 101, 109, 111, 116, 101, 32, 73, 47, 79, 32, 101, 114, 114, 111, 114, 0, 81, 117, 111, 116, 97, 32, 101, 120, 99, 101, 101, 100, 101, 100, 0, 78, 111, 32, 109, 101, 100, 105, 117, 109, 32, 102, 111, 117, 110, 100, 0, 87, 114, 111, 110, 103, 32, 109, 101, 100, 105, 117, 109, 32, 116, 121, 112, 101, 0, 78, 111, 32, 101, 114, 114, 111, 114, 32, 105, 110, 102, 111, 114, 109, 97, 116, 105, 111, 110, 0, 0], "i8", ALLOC_NONE, Runtime.GLOBAL_BASE);var tempDoublePtr = STATICTOP;STATICTOP += 16;function _atexit(func, arg) {
+    __ATEXIT__.unshift({ func: func, arg: arg });
+  }function ___cxa_atexit() {
+    return _atexit.apply(null, arguments);
+  }function _abort() {
+    Module["abort"]();
+  }function __ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj() {
+    Module["printErr"]("missing function: _ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj");abort(-1);
+  }function __decorate(decorators, target, key, desc) {
+    var c = arguments.length,
+        r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc,
+        d;if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;return c > 3 && r && Object.defineProperty(target, key, r), r;
+  }function _defineHidden(value) {
+    return function (target, key) {
+      Object.defineProperty(target, key, { configurable: false, enumerable: false, value: value, writable: true });
+    };
+  }var _nbind = {};function __nbind_free_external(num) {
+    _nbind.externalList[num].dereference(num);
+  }function __nbind_reference_external(num) {
+    _nbind.externalList[num].reference();
+  }function _llvm_stackrestore(p) {
+    var self = _llvm_stacksave;var ret = self.LLVM_SAVEDSTACKS[p];self.LLVM_SAVEDSTACKS.splice(p, 1);Runtime.stackRestore(ret);
+  }function __nbind_register_pool(pageSize, usedPtr, rootPtr, pagePtr) {
+    _nbind.Pool.pageSize = pageSize;_nbind.Pool.usedPtr = usedPtr / 4;_nbind.Pool.rootPtr = rootPtr;_nbind.Pool.pagePtr = pagePtr / 4;HEAP32[usedPtr / 4] = 16909060;if (HEAP8[usedPtr] == 1) _nbind.bigEndian = true;HEAP32[usedPtr / 4] = 0;_nbind.makeTypeKindTbl = (_a = {}, _a[1024] = _nbind.PrimitiveType, _a[64] = _nbind.Int64Type, _a[2048] = _nbind.BindClass, _a[3072] = _nbind.BindClassPtr, _a[4096] = _nbind.SharedClassPtr, _a[5120] = _nbind.ArrayType, _a[6144] = _nbind.ArrayType, _a[7168] = _nbind.CStringType, _a[9216] = _nbind.CallbackType, _a[10240] = _nbind.BindType, _a);_nbind.makeTypeNameTbl = { "Buffer": _nbind.BufferType, "External": _nbind.ExternalType, "Int64": _nbind.Int64Type, "_nbind_new": _nbind.CreateValueType, "bool": _nbind.BooleanType, "cbFunction &": _nbind.CallbackType, "const cbFunction &": _nbind.CallbackType, "const std::string &": _nbind.StringType, "std::string": _nbind.StringType };Module["toggleLightGC"] = _nbind.toggleLightGC;_nbind.callUpcast = Module["dynCall_ii"];var globalScope = _nbind.makeType(_nbind.constructType, { flags: 2048, id: 0, name: "" });globalScope.proto = Module;_nbind.BindClass.list.push(globalScope);var _a;
+  }function _emscripten_set_main_loop_timing(mode, value) {
+    Browser.mainLoop.timingMode = mode;Browser.mainLoop.timingValue = value;if (!Browser.mainLoop.func) {
+      return 1;
+    }if (mode == 0) {
+      Browser.mainLoop.scheduler = function Browser_mainLoop_scheduler_setTimeout() {
+        var timeUntilNextTick = Math.max(0, Browser.mainLoop.tickStartTime + value - _emscripten_get_now()) | 0;setTimeout(Browser.mainLoop.runner, timeUntilNextTick);
+      };Browser.mainLoop.method = "timeout";
+    } else if (mode == 1) {
+      Browser.mainLoop.scheduler = function Browser_mainLoop_scheduler_rAF() {
+        Browser.requestAnimationFrame(Browser.mainLoop.runner);
+      };Browser.mainLoop.method = "rAF";
+    } else if (mode == 2) {
+      if (!window["setImmediate"]) {
+        var setImmediates = [];var emscriptenMainLoopMessageId = "setimmediate";function Browser_setImmediate_messageHandler(event) {
+          if (event.source === window && event.data === emscriptenMainLoopMessageId) {
+            event.stopPropagation();setImmediates.shift()();
+          }
+        }window.addEventListener("message", Browser_setImmediate_messageHandler, true);window["setImmediate"] = function Browser_emulated_setImmediate(func) {
+          setImmediates.push(func);if (ENVIRONMENT_IS_WORKER) {
+            if (Module["setImmediates"] === undefined) Module["setImmediates"] = [];Module["setImmediates"].push(func);window.postMessage({ target: emscriptenMainLoopMessageId });
+          } else window.postMessage(emscriptenMainLoopMessageId, "*");
+        };
+      }Browser.mainLoop.scheduler = function Browser_mainLoop_scheduler_setImmediate() {
+        window["setImmediate"](Browser.mainLoop.runner);
+      };Browser.mainLoop.method = "immediate";
+    }return 0;
+  }function _emscripten_get_now() {
+    abort();
+  }function _emscripten_set_main_loop(func, fps, simulateInfiniteLoop, arg, noSetTiming) {
+    Module["noExitRuntime"] = true;assert(!Browser.mainLoop.func, "emscripten_set_main_loop: there can only be one main loop function at once: call emscripten_cancel_main_loop to cancel the previous one before setting a new one with different parameters.");Browser.mainLoop.func = func;Browser.mainLoop.arg = arg;var browserIterationFunc;if (typeof arg !== "undefined") {
+      browserIterationFunc = function () {
+        Module["dynCall_vi"](func, arg);
+      };
+    } else {
+      browserIterationFunc = function () {
+        Module["dynCall_v"](func);
+      };
+    }var thisMainLoopId = Browser.mainLoop.currentlyRunningMainloop;Browser.mainLoop.runner = function Browser_mainLoop_runner() {
+      if (ABORT) return;if (Browser.mainLoop.queue.length > 0) {
+        var start = Date.now();var blocker = Browser.mainLoop.queue.shift();blocker.func(blocker.arg);if (Browser.mainLoop.remainingBlockers) {
+          var remaining = Browser.mainLoop.remainingBlockers;var next = remaining % 1 == 0 ? remaining - 1 : Math.floor(remaining);if (blocker.counted) {
+            Browser.mainLoop.remainingBlockers = next;
+          } else {
+            next = next + .5;Browser.mainLoop.remainingBlockers = (8 * remaining + next) / 9;
+          }
+        }console.log('main loop blocker "' + blocker.name + '" took ' + (Date.now() - start) + " ms");Browser.mainLoop.updateStatus();if (thisMainLoopId < Browser.mainLoop.currentlyRunningMainloop) return;setTimeout(Browser.mainLoop.runner, 0);return;
+      }if (thisMainLoopId < Browser.mainLoop.currentlyRunningMainloop) return;Browser.mainLoop.currentFrameNumber = Browser.mainLoop.currentFrameNumber + 1 | 0;if (Browser.mainLoop.timingMode == 1 && Browser.mainLoop.timingValue > 1 && Browser.mainLoop.currentFrameNumber % Browser.mainLoop.timingValue != 0) {
+        Browser.mainLoop.scheduler();return;
+      } else if (Browser.mainLoop.timingMode == 0) {
+        Browser.mainLoop.tickStartTime = _emscripten_get_now();
+      }if (Browser.mainLoop.method === "timeout" && Module.ctx) {
+        Module.printErr("Looks like you are rendering without using requestAnimationFrame for the main loop. You should use 0 for the frame rate in emscripten_set_main_loop in order to use requestAnimationFrame, as that can greatly improve your frame rates!");Browser.mainLoop.method = "";
+      }Browser.mainLoop.runIter(browserIterationFunc);if (thisMainLoopId < Browser.mainLoop.currentlyRunningMainloop) return;if (typeof SDL === "object" && SDL.audio && SDL.audio.queueNewAudioData) SDL.audio.queueNewAudioData();Browser.mainLoop.scheduler();
+    };if (!noSetTiming) {
+      if (fps && fps > 0) _emscripten_set_main_loop_timing(0, 1e3 / fps);else _emscripten_set_main_loop_timing(1, 1);Browser.mainLoop.scheduler();
+    }if (simulateInfiniteLoop) {
+      throw "SimulateInfiniteLoop";
+    }
+  }var Browser = { mainLoop: { scheduler: null, method: "", currentlyRunningMainloop: 0, func: null, arg: 0, timingMode: 0, timingValue: 0, currentFrameNumber: 0, queue: [], pause: function () {
+        Browser.mainLoop.scheduler = null;Browser.mainLoop.currentlyRunningMainloop++;
+      }, resume: function () {
+        Browser.mainLoop.currentlyRunningMainloop++;var timingMode = Browser.mainLoop.timingMode;var timingValue = Browser.mainLoop.timingValue;var func = Browser.mainLoop.func;Browser.mainLoop.func = null;_emscripten_set_main_loop(func, 0, false, Browser.mainLoop.arg, true);_emscripten_set_main_loop_timing(timingMode, timingValue);Browser.mainLoop.scheduler();
+      }, updateStatus: function () {
+        if (Module["setStatus"]) {
+          var message = Module["statusMessage"] || "Please wait...";var remaining = Browser.mainLoop.remainingBlockers;var expected = Browser.mainLoop.expectedBlockers;if (remaining) {
+            if (remaining < expected) {
+              Module["setStatus"](message + " (" + (expected - remaining) + "/" + expected + ")");
+            } else {
+              Module["setStatus"](message);
+            }
+          } else {
+            Module["setStatus"]("");
+          }
+        }
+      }, runIter: function (func) {
+        if (ABORT) return;if (Module["preMainLoop"]) {
+          var preRet = Module["preMainLoop"]();if (preRet === false) {
+            return;
+          }
+        }try {
+          func();
+        } catch (e) {
+          if (e instanceof ExitStatus) {
+            return;
+          } else {
+            if (e && typeof e === "object" && e.stack) Module.printErr("exception thrown: " + [e, e.stack]);throw e;
+          }
+        }if (Module["postMainLoop"]) Module["postMainLoop"]();
+      } }, isFullscreen: false, pointerLock: false, moduleContextCreatedCallbacks: [], workers: [], init: function () {
+      if (!Module["preloadPlugins"]) Module["preloadPlugins"] = [];if (Browser.initted) return;Browser.initted = true;try {
+        new Blob();Browser.hasBlobConstructor = true;
+      } catch (e) {
+        Browser.hasBlobConstructor = false;console.log("warning: no blob constructor, cannot create blobs with mimetypes");
+      }Browser.BlobBuilder = typeof MozBlobBuilder != "undefined" ? MozBlobBuilder : typeof WebKitBlobBuilder != "undefined" ? WebKitBlobBuilder : !Browser.hasBlobConstructor ? console.log("warning: no BlobBuilder") : null;Browser.URLObject = typeof window != "undefined" ? window.URL ? window.URL : window.webkitURL : undefined;if (!Module.noImageDecoding && typeof Browser.URLObject === "undefined") {
+        console.log("warning: Browser does not support creating object URLs. Built-in browser image decoding will not be available.");Module.noImageDecoding = true;
+      }var imagePlugin = {};imagePlugin["canHandle"] = function imagePlugin_canHandle(name) {
+        return !Module.noImageDecoding && /\.(jpg|jpeg|png|bmp)$/i.test(name);
+      };imagePlugin["handle"] = function imagePlugin_handle(byteArray, name, onload, onerror) {
+        var b = null;if (Browser.hasBlobConstructor) {
+          try {
+            b = new Blob([byteArray], { type: Browser.getMimetype(name) });if (b.size !== byteArray.length) {
+              b = new Blob([new Uint8Array(byteArray).buffer], { type: Browser.getMimetype(name) });
+            }
+          } catch (e) {
+            Runtime.warnOnce("Blob constructor present but fails: " + e + "; falling back to blob builder");
+          }
+        }if (!b) {
+          var bb = new Browser.BlobBuilder();bb.append(new Uint8Array(byteArray).buffer);b = bb.getBlob();
+        }var url = Browser.URLObject.createObjectURL(b);var img = new Image();img.onload = function img_onload() {
+          assert(img.complete, "Image " + name + " could not be decoded");var canvas = document.createElement("canvas");canvas.width = img.width;canvas.height = img.height;var ctx = canvas.getContext("2d");ctx.drawImage(img, 0, 0);Module["preloadedImages"][name] = canvas;Browser.URLObject.revokeObjectURL(url);if (onload) onload(byteArray);
+        };img.onerror = function img_onerror(event) {
+          console.log("Image " + url + " could not be decoded");if (onerror) onerror();
+        };img.src = url;
+      };Module["preloadPlugins"].push(imagePlugin);var audioPlugin = {};audioPlugin["canHandle"] = function audioPlugin_canHandle(name) {
+        return !Module.noAudioDecoding && name.substr(-4) in { ".ogg": 1, ".wav": 1, ".mp3": 1 };
+      };audioPlugin["handle"] = function audioPlugin_handle(byteArray, name, onload, onerror) {
+        var done = false;function finish(audio) {
+          if (done) return;done = true;Module["preloadedAudios"][name] = audio;if (onload) onload(byteArray);
+        }function fail() {
+          if (done) return;done = true;Module["preloadedAudios"][name] = new Audio();if (onerror) onerror();
+        }if (Browser.hasBlobConstructor) {
+          try {
+            var b = new Blob([byteArray], { type: Browser.getMimetype(name) });
+          } catch (e) {
+            return fail();
+          }var url = Browser.URLObject.createObjectURL(b);var audio = new Audio();audio.addEventListener("canplaythrough", function () {
+            finish(audio);
+          }, false);audio.onerror = function audio_onerror(event) {
+            if (done) return;console.log("warning: browser could not fully decode audio " + name + ", trying slower base64 approach");function encode64(data) {
+              var BASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";var PAD = "=";var ret = "";var leftchar = 0;var leftbits = 0;for (var i = 0; i < data.length; i++) {
+                leftchar = leftchar << 8 | data[i];leftbits += 8;while (leftbits >= 6) {
+                  var curr = leftchar >> leftbits - 6 & 63;leftbits -= 6;ret += BASE[curr];
+                }
+              }if (leftbits == 2) {
+                ret += BASE[(leftchar & 3) << 4];ret += PAD + PAD;
+              } else if (leftbits == 4) {
+                ret += BASE[(leftchar & 15) << 2];ret += PAD;
+              }return ret;
+            }audio.src = "data:audio/x-" + name.substr(-3) + ";base64," + encode64(byteArray);finish(audio);
+          };audio.src = url;Browser.safeSetTimeout(function () {
+            finish(audio);
+          }, 1e4);
+        } else {
+          return fail();
+        }
+      };Module["preloadPlugins"].push(audioPlugin);function pointerLockChange() {
+        Browser.pointerLock = document["pointerLockElement"] === Module["canvas"] || document["mozPointerLockElement"] === Module["canvas"] || document["webkitPointerLockElement"] === Module["canvas"] || document["msPointerLockElement"] === Module["canvas"];
+      }var canvas = Module["canvas"];if (canvas) {
+        canvas.requestPointerLock = canvas["requestPointerLock"] || canvas["mozRequestPointerLock"] || canvas["webkitRequestPointerLock"] || canvas["msRequestPointerLock"] || function () {};canvas.exitPointerLock = document["exitPointerLock"] || document["mozExitPointerLock"] || document["webkitExitPointerLock"] || document["msExitPointerLock"] || function () {};canvas.exitPointerLock = canvas.exitPointerLock.bind(document);document.addEventListener("pointerlockchange", pointerLockChange, false);document.addEventListener("mozpointerlockchange", pointerLockChange, false);document.addEventListener("webkitpointerlockchange", pointerLockChange, false);document.addEventListener("mspointerlockchange", pointerLockChange, false);if (Module["elementPointerLock"]) {
+          canvas.addEventListener("click", function (ev) {
+            if (!Browser.pointerLock && Module["canvas"].requestPointerLock) {
+              Module["canvas"].requestPointerLock();ev.preventDefault();
+            }
+          }, false);
+        }
+      }
+    }, createContext: function (canvas, useWebGL, setInModule, webGLContextAttributes) {
+      if (useWebGL && Module.ctx && canvas == Module.canvas) return Module.ctx;var ctx;var contextHandle;if (useWebGL) {
+        var contextAttributes = { antialias: false, alpha: false };if (webGLContextAttributes) {
+          for (var attribute in webGLContextAttributes) {
+            contextAttributes[attribute] = webGLContextAttributes[attribute];
+          }
+        }contextHandle = GL.createContext(canvas, contextAttributes);if (contextHandle) {
+          ctx = GL.getContext(contextHandle).GLctx;
+        }
+      } else {
+        ctx = canvas.getContext("2d");
+      }if (!ctx) return null;if (setInModule) {
+        if (!useWebGL) assert(typeof GLctx === "undefined", "cannot set in module if GLctx is used, but we are a non-GL context that would replace it");Module.ctx = ctx;if (useWebGL) GL.makeContextCurrent(contextHandle);Module.useWebGL = useWebGL;Browser.moduleContextCreatedCallbacks.forEach(function (callback) {
+          callback();
+        });Browser.init();
+      }return ctx;
+    }, destroyContext: function (canvas, useWebGL, setInModule) {}, fullscreenHandlersInstalled: false, lockPointer: undefined, resizeCanvas: undefined, requestFullscreen: function (lockPointer, resizeCanvas, vrDevice) {
+      Browser.lockPointer = lockPointer;Browser.resizeCanvas = resizeCanvas;Browser.vrDevice = vrDevice;if (typeof Browser.lockPointer === "undefined") Browser.lockPointer = true;if (typeof Browser.resizeCanvas === "undefined") Browser.resizeCanvas = false;if (typeof Browser.vrDevice === "undefined") Browser.vrDevice = null;var canvas = Module["canvas"];function fullscreenChange() {
+        Browser.isFullscreen = false;var canvasContainer = canvas.parentNode;if ((document["fullscreenElement"] || document["mozFullScreenElement"] || document["msFullscreenElement"] || document["webkitFullscreenElement"] || document["webkitCurrentFullScreenElement"]) === canvasContainer) {
+          canvas.exitFullscreen = document["exitFullscreen"] || document["cancelFullScreen"] || document["mozCancelFullScreen"] || document["msExitFullscreen"] || document["webkitCancelFullScreen"] || function () {};canvas.exitFullscreen = canvas.exitFullscreen.bind(document);if (Browser.lockPointer) canvas.requestPointerLock();Browser.isFullscreen = true;if (Browser.resizeCanvas) Browser.setFullscreenCanvasSize();
+        } else {
+          canvasContainer.parentNode.insertBefore(canvas, canvasContainer);canvasContainer.parentNode.removeChild(canvasContainer);if (Browser.resizeCanvas) Browser.setWindowedCanvasSize();
+        }if (Module["onFullScreen"]) Module["onFullScreen"](Browser.isFullscreen);if (Module["onFullscreen"]) Module["onFullscreen"](Browser.isFullscreen);Browser.updateCanvasDimensions(canvas);
+      }if (!Browser.fullscreenHandlersInstalled) {
+        Browser.fullscreenHandlersInstalled = true;document.addEventListener("fullscreenchange", fullscreenChange, false);document.addEventListener("mozfullscreenchange", fullscreenChange, false);document.addEventListener("webkitfullscreenchange", fullscreenChange, false);document.addEventListener("MSFullscreenChange", fullscreenChange, false);
+      }var canvasContainer = document.createElement("div");canvas.parentNode.insertBefore(canvasContainer, canvas);canvasContainer.appendChild(canvas);canvasContainer.requestFullscreen = canvasContainer["requestFullscreen"] || canvasContainer["mozRequestFullScreen"] || canvasContainer["msRequestFullscreen"] || (canvasContainer["webkitRequestFullscreen"] ? function () {
+        canvasContainer["webkitRequestFullscreen"](Element["ALLOW_KEYBOARD_INPUT"]);
+      } : null) || (canvasContainer["webkitRequestFullScreen"] ? function () {
+        canvasContainer["webkitRequestFullScreen"](Element["ALLOW_KEYBOARD_INPUT"]);
+      } : null);if (vrDevice) {
+        canvasContainer.requestFullscreen({ vrDisplay: vrDevice });
+      } else {
+        canvasContainer.requestFullscreen();
+      }
+    }, requestFullScreen: function (lockPointer, resizeCanvas, vrDevice) {
+      Module.printErr("Browser.requestFullScreen() is deprecated. Please call Browser.requestFullscreen instead.");Browser.requestFullScreen = function (lockPointer, resizeCanvas, vrDevice) {
+        return Browser.requestFullscreen(lockPointer, resizeCanvas, vrDevice);
+      };return Browser.requestFullscreen(lockPointer, resizeCanvas, vrDevice);
+    }, nextRAF: 0, fakeRequestAnimationFrame: function (func) {
+      var now = Date.now();if (Browser.nextRAF === 0) {
+        Browser.nextRAF = now + 1e3 / 60;
+      } else {
+        while (now + 2 >= Browser.nextRAF) {
+          Browser.nextRAF += 1e3 / 60;
+        }
+      }var delay = Math.max(Browser.nextRAF - now, 0);setTimeout(func, delay);
+    }, requestAnimationFrame: function requestAnimationFrame(func) {
+      if (typeof window === "undefined") {
+        Browser.fakeRequestAnimationFrame(func);
+      } else {
+        if (!window.requestAnimationFrame) {
+          window.requestAnimationFrame = window["requestAnimationFrame"] || window["mozRequestAnimationFrame"] || window["webkitRequestAnimationFrame"] || window["msRequestAnimationFrame"] || window["oRequestAnimationFrame"] || Browser.fakeRequestAnimationFrame;
+        }window.requestAnimationFrame(func);
+      }
+    }, safeCallback: function (func) {
+      return function () {
+        if (!ABORT) return func.apply(null, arguments);
+      };
+    }, allowAsyncCallbacks: true, queuedAsyncCallbacks: [], pauseAsyncCallbacks: function () {
+      Browser.allowAsyncCallbacks = false;
+    }, resumeAsyncCallbacks: function () {
+      Browser.allowAsyncCallbacks = true;if (Browser.queuedAsyncCallbacks.length > 0) {
+        var callbacks = Browser.queuedAsyncCallbacks;Browser.queuedAsyncCallbacks = [];callbacks.forEach(function (func) {
+          func();
+        });
+      }
+    }, safeRequestAnimationFrame: function (func) {
+      return Browser.requestAnimationFrame(function () {
+        if (ABORT) return;if (Browser.allowAsyncCallbacks) {
+          func();
+        } else {
+          Browser.queuedAsyncCallbacks.push(func);
+        }
+      });
+    }, safeSetTimeout: function (func, timeout) {
+      Module["noExitRuntime"] = true;return setTimeout(function () {
+        if (ABORT) return;if (Browser.allowAsyncCallbacks) {
+          func();
+        } else {
+          Browser.queuedAsyncCallbacks.push(func);
+        }
+      }, timeout);
+    }, safeSetInterval: function (func, timeout) {
+      Module["noExitRuntime"] = true;return setInterval(function () {
+        if (ABORT) return;if (Browser.allowAsyncCallbacks) {
+          func();
+        }
+      }, timeout);
+    }, getMimetype: function (name) {
+      return { "jpg": "image/jpeg", "jpeg": "image/jpeg", "png": "image/png", "bmp": "image/bmp", "ogg": "audio/ogg", "wav": "audio/wav", "mp3": "audio/mpeg" }[name.substr(name.lastIndexOf(".") + 1)];
+    }, getUserMedia: function (func) {
+      if (!window.getUserMedia) {
+        window.getUserMedia = navigator["getUserMedia"] || navigator["mozGetUserMedia"];
+      }window.getUserMedia(func);
+    }, getMovementX: function (event) {
+      return event["movementX"] || event["mozMovementX"] || event["webkitMovementX"] || 0;
+    }, getMovementY: function (event) {
+      return event["movementY"] || event["mozMovementY"] || event["webkitMovementY"] || 0;
+    }, getMouseWheelDelta: function (event) {
+      var delta = 0;switch (event.type) {case "DOMMouseScroll":
+          delta = event.detail;break;case "mousewheel":
+          delta = event.wheelDelta;break;case "wheel":
+          delta = event["deltaY"];break;default:
+          throw "unrecognized mouse wheel event: " + event.type;}return delta;
+    }, mouseX: 0, mouseY: 0, mouseMovementX: 0, mouseMovementY: 0, touches: {}, lastTouches: {}, calculateMouseEvent: function (event) {
+      if (Browser.pointerLock) {
+        if (event.type != "mousemove" && "mozMovementX" in event) {
+          Browser.mouseMovementX = Browser.mouseMovementY = 0;
+        } else {
+          Browser.mouseMovementX = Browser.getMovementX(event);Browser.mouseMovementY = Browser.getMovementY(event);
+        }if (typeof SDL != "undefined") {
+          Browser.mouseX = SDL.mouseX + Browser.mouseMovementX;Browser.mouseY = SDL.mouseY + Browser.mouseMovementY;
+        } else {
+          Browser.mouseX += Browser.mouseMovementX;Browser.mouseY += Browser.mouseMovementY;
+        }
+      } else {
+        var rect = Module["canvas"].getBoundingClientRect();var cw = Module["canvas"].width;var ch = Module["canvas"].height;var scrollX = typeof window.scrollX !== "undefined" ? window.scrollX : window.pageXOffset;var scrollY = typeof window.scrollY !== "undefined" ? window.scrollY : window.pageYOffset;if (event.type === "touchstart" || event.type === "touchend" || event.type === "touchmove") {
+          var touch = event.touch;if (touch === undefined) {
+            return;
+          }var adjustedX = touch.pageX - (scrollX + rect.left);var adjustedY = touch.pageY - (scrollY + rect.top);adjustedX = adjustedX * (cw / rect.width);adjustedY = adjustedY * (ch / rect.height);var coords = { x: adjustedX, y: adjustedY };if (event.type === "touchstart") {
+            Browser.lastTouches[touch.identifier] = coords;Browser.touches[touch.identifier] = coords;
+          } else if (event.type === "touchend" || event.type === "touchmove") {
+            var last = Browser.touches[touch.identifier];if (!last) last = coords;Browser.lastTouches[touch.identifier] = last;Browser.touches[touch.identifier] = coords;
+          }return;
+        }var x = event.pageX - (scrollX + rect.left);var y = event.pageY - (scrollY + rect.top);x = x * (cw / rect.width);y = y * (ch / rect.height);Browser.mouseMovementX = x - Browser.mouseX;Browser.mouseMovementY = y - Browser.mouseY;Browser.mouseX = x;Browser.mouseY = y;
+      }
+    }, asyncLoad: function (url, onload, onerror, noRunDep) {
+      var dep = !noRunDep ? getUniqueRunDependency("al " + url) : "";Module["readAsync"](url, function (arrayBuffer) {
+        assert(arrayBuffer, 'Loading data file "' + url + '" failed (no arrayBuffer).');onload(new Uint8Array(arrayBuffer));if (dep) removeRunDependency(dep);
+      }, function (event) {
+        if (onerror) {
+          onerror();
+        } else {
+          throw 'Loading data file "' + url + '" failed.';
+        }
+      });if (dep) addRunDependency(dep);
+    }, resizeListeners: [], updateResizeListeners: function () {
+      var canvas = Module["canvas"];Browser.resizeListeners.forEach(function (listener) {
+        listener(canvas.width, canvas.height);
+      });
+    }, setCanvasSize: function (width, height, noUpdates) {
+      var canvas = Module["canvas"];Browser.updateCanvasDimensions(canvas, width, height);if (!noUpdates) Browser.updateResizeListeners();
+    }, windowedWidth: 0, windowedHeight: 0, setFullscreenCanvasSize: function () {
+      if (typeof SDL != "undefined") {
+        var flags = HEAPU32[SDL.screen + Runtime.QUANTUM_SIZE * 0 >> 2];flags = flags | 8388608;HEAP32[SDL.screen + Runtime.QUANTUM_SIZE * 0 >> 2] = flags;
+      }Browser.updateResizeListeners();
+    }, setWindowedCanvasSize: function () {
+      if (typeof SDL != "undefined") {
+        var flags = HEAPU32[SDL.screen + Runtime.QUANTUM_SIZE * 0 >> 2];flags = flags & ~8388608;HEAP32[SDL.screen + Runtime.QUANTUM_SIZE * 0 >> 2] = flags;
+      }Browser.updateResizeListeners();
+    }, updateCanvasDimensions: function (canvas, wNative, hNative) {
+      if (wNative && hNative) {
+        canvas.widthNative = wNative;canvas.heightNative = hNative;
+      } else {
+        wNative = canvas.widthNative;hNative = canvas.heightNative;
+      }var w = wNative;var h = hNative;if (Module["forcedAspectRatio"] && Module["forcedAspectRatio"] > 0) {
+        if (w / h < Module["forcedAspectRatio"]) {
+          w = Math.round(h * Module["forcedAspectRatio"]);
+        } else {
+          h = Math.round(w / Module["forcedAspectRatio"]);
+        }
+      }if ((document["fullscreenElement"] || document["mozFullScreenElement"] || document["msFullscreenElement"] || document["webkitFullscreenElement"] || document["webkitCurrentFullScreenElement"]) === canvas.parentNode && typeof screen != "undefined") {
+        var factor = Math.min(screen.width / w, screen.height / h);w = Math.round(w * factor);h = Math.round(h * factor);
+      }if (Browser.resizeCanvas) {
+        if (canvas.width != w) canvas.width = w;if (canvas.height != h) canvas.height = h;if (typeof canvas.style != "undefined") {
+          canvas.style.removeProperty("width");canvas.style.removeProperty("height");
+        }
+      } else {
+        if (canvas.width != wNative) canvas.width = wNative;if (canvas.height != hNative) canvas.height = hNative;if (typeof canvas.style != "undefined") {
+          if (w != wNative || h != hNative) {
+            canvas.style.setProperty("width", w + "px", "important");canvas.style.setProperty("height", h + "px", "important");
+          } else {
+            canvas.style.removeProperty("width");canvas.style.removeProperty("height");
+          }
+        }
+      }
+    }, wgetRequests: {}, nextWgetRequestHandle: 0, getNextWgetRequestHandle: function () {
+      var handle = Browser.nextWgetRequestHandle;Browser.nextWgetRequestHandle++;return handle;
+    } };var SYSCALLS = { varargs: 0, get: function (varargs) {
+      SYSCALLS.varargs += 4;var ret = HEAP32[SYSCALLS.varargs - 4 >> 2];return ret;
+    }, getStr: function () {
+      var ret = Pointer_stringify(SYSCALLS.get());return ret;
+    }, get64: function () {
+      var low = SYSCALLS.get(),
+          high = SYSCALLS.get();if (low >= 0) assert(high === 0);else assert(high === -1);return low;
+    }, getZero: function () {
+      assert(SYSCALLS.get() === 0);
+    } };function ___syscall6(which, varargs) {
+    SYSCALLS.varargs = varargs;try {
+      var stream = SYSCALLS.getStreamFromFD();FS.close(stream);return 0;
+    } catch (e) {
+      if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e);return -e.errno;
+    }
+  }function ___syscall54(which, varargs) {
+    SYSCALLS.varargs = varargs;try {
+      return 0;
+    } catch (e) {
+      if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e);return -e.errno;
+    }
+  }function _typeModule(self) {
+    var structureList = [[0, 1, "X"], [1, 1, "const X"], [128, 1, "X *"], [256, 1, "X &"], [384, 1, "X &&"], [512, 1, "std::shared_ptr<X>"], [640, 1, "std::unique_ptr<X>"], [5120, 1, "std::vector<X>"], [6144, 2, "std::array<X, Y>"], [9216, -1, "std::function<X (Y)>"]];function applyStructure(outerName, outerFlags, innerName, innerFlags, param, flip) {
+      if (outerFlags == 1) {
+        var ref = innerFlags & 896;if (ref == 128 || ref == 256 || ref == 384) outerName = "X const";
+      }var name;if (flip) {
+        name = innerName.replace("X", outerName).replace("Y", param);
+      } else {
+        name = outerName.replace("X", innerName).replace("Y", param);
+      }return name.replace(/([*&]) (?=[*&])/g, "$1");
+    }function reportProblem(problem, id, kind, structureType, place) {
+      throw new Error(problem + " type " + kind.replace("X", id + "?") + (structureType ? " with flag " + structureType : "") + " in " + place);
+    }function getComplexType(id, constructType, getType, queryType, place, kind, prevStructure, depth) {
+      if (kind === void 0) {
+        kind = "X";
+      }if (depth === void 0) {
+        depth = 1;
+      }var result = getType(id);if (result) return result;var query = queryType(id);var structureType = query.placeholderFlag;var structure = structureList[structureType];if (prevStructure && structure) {
+        kind = applyStructure(prevStructure[2], prevStructure[0], kind, structure[0], "?", true);
+      }var problem;if (structureType == 0) problem = "Unbound";if (structureType >= 10) problem = "Corrupt";if (depth > 20) problem = "Deeply nested";if (problem) reportProblem(problem, id, kind, structureType, place || "?");var subId = query.paramList[0];var subType = getComplexType(subId, constructType, getType, queryType, place, kind, structure, depth + 1);var srcSpec;var spec = { flags: structure[0], id: id, name: "", paramList: [subType] };var argList = [];var structureParam = "?";switch (query.placeholderFlag) {case 1:
+          srcSpec = subType.spec;break;case 2:
+          if ((subType.flags & 15360) == 1024 && subType.spec.ptrSize == 1) {
+            spec.flags = 7168;break;
+          }case 3:case 6:case 5:
+          srcSpec = subType.spec;if ((subType.flags & 15360) != 2048) {}break;case 8:
+          structureParam = "" + query.paramList[1];spec.paramList.push(query.paramList[1]);break;case 9:
+          for (var _i = 0, _a = query.paramList[1]; _i < _a.length; _i++) {
+            var paramId = _a[_i];var paramType = getComplexType(paramId, constructType, getType, queryType, place, kind, structure, depth + 1);argList.push(paramType.name);spec.paramList.push(paramType);
+          }structureParam = argList.join(", ");break;default:
+          break;}spec.name = applyStructure(structure[2], structure[0], subType.name, subType.flags, structureParam);if (srcSpec) {
+        for (var _b = 0, _c = Object.keys(srcSpec); _b < _c.length; _b++) {
+          var key = _c[_b];spec[key] = spec[key] || srcSpec[key];
+        }spec.flags |= srcSpec.flags;
+      }return makeType(constructType, spec);
+    }function makeType(constructType, spec) {
+      var flags = spec.flags;var refKind = flags & 896;var kind = flags & 15360;if (!spec.name && kind == 1024) {
+        if (spec.ptrSize == 1) {
+          spec.name = (flags & 16 ? "" : (flags & 8 ? "un" : "") + "signed ") + "char";
+        } else {
+          spec.name = (flags & 8 ? "u" : "") + (flags & 32 ? "float" : "int") + (spec.ptrSize * 8 + "_t");
+        }
+      }if (spec.ptrSize == 8 && !(flags & 32)) kind = 64;if (kind == 2048) {
+        if (refKind == 512 || refKind == 640) {
+          kind = 4096;
+        } else if (refKind) kind = 3072;
+      }return constructType(kind, spec);
+    }var Type = function () {
+      function Type(spec) {
+        this.id = spec.id;this.name = spec.name;this.flags = spec.flags;this.spec = spec;
+      }Type.prototype.toString = function () {
+        return this.name;
+      };return Type;
+    }();var output = { Type: Type, getComplexType: getComplexType, makeType: makeType, structureList: structureList };self.output = output;return self.output || output;
+  }function __nbind_register_type(id, namePtr) {
+    var name = _nbind.readAsciiString(namePtr);var spec = { flags: 10240, id: id, name: name };_nbind.makeType(_nbind.constructType, spec);
+  }function __nbind_register_callback_signature(typeListPtr, typeCount) {
+    var typeList = _nbind.readTypeIdList(typeListPtr, typeCount);var num = _nbind.callbackSignatureList.length;_nbind.callbackSignatureList[num] = _nbind.makeJSCaller(typeList);return num;
+  }function __extends(Class, Parent) {
+    for (var key in Parent) if (Parent.hasOwnProperty(key)) Class[key] = Parent[key];function Base() {
+      this.constructor = Class;
+    }Base.prototype = Parent.prototype;Class.prototype = new Base();
+  }function __nbind_register_class(idListPtr, policyListPtr, superListPtr, upcastListPtr, superCount, destructorPtr, namePtr) {
+    var name = _nbind.readAsciiString(namePtr);var policyTbl = _nbind.readPolicyList(policyListPtr);var idList = HEAPU32.subarray(idListPtr / 4, idListPtr / 4 + 2);var spec = { flags: 2048 | (policyTbl["Value"] ? 2 : 0), id: idList[0], name: name };var bindClass = _nbind.makeType(_nbind.constructType, spec);bindClass.ptrType = _nbind.getComplexType(idList[1], _nbind.constructType, _nbind.getType, _nbind.queryType);bindClass.destroy = _nbind.makeMethodCaller(bindClass.ptrType, { boundID: spec.id, flags: 0, name: "destroy", num: 0, ptr: destructorPtr, title: bindClass.name + ".free", typeList: ["void", "uint32_t", "uint32_t"] });if (superCount) {
+      bindClass.superIdList = Array.prototype.slice.call(HEAPU32.subarray(superListPtr / 4, superListPtr / 4 + superCount));bindClass.upcastList = Array.prototype.slice.call(HEAPU32.subarray(upcastListPtr / 4, upcastListPtr / 4 + superCount));
+    }Module[bindClass.name] = bindClass.makeBound(policyTbl);_nbind.BindClass.list.push(bindClass);
+  }function _removeAccessorPrefix(name) {
+    var prefixMatcher = /^[Gg]et_?([A-Z]?([A-Z]?))/;return name.replace(prefixMatcher, function (match, initial, second) {
+      return second ? initial : initial.toLowerCase();
+    });
+  }function __nbind_register_function(boundID, policyListPtr, typeListPtr, typeCount, ptr, direct, signatureType, namePtr, num, flags) {
+    var bindClass = _nbind.getType(boundID);var policyTbl = _nbind.readPolicyList(policyListPtr);var typeList = _nbind.readTypeIdList(typeListPtr, typeCount);var specList;if (signatureType == 5) {
+      specList = [{ direct: ptr, name: "__nbindConstructor", ptr: 0, title: bindClass.name + " constructor", typeList: ["uint32_t"].concat(typeList.slice(1)) }, { direct: direct, name: "__nbindValueConstructor", ptr: 0, title: bindClass.name + " value constructor", typeList: ["void", "uint32_t"].concat(typeList.slice(1)) }];
+    } else {
+      var name_1 = _nbind.readAsciiString(namePtr);var title = (bindClass.name && bindClass.name + ".") + name_1;if (signatureType == 3 || signatureType == 4) {
+        name_1 = _removeAccessorPrefix(name_1);
+      }specList = [{ boundID: boundID, direct: direct, name: name_1, ptr: ptr, title: title, typeList: typeList }];
+    }for (var _i = 0, specList_1 = specList; _i < specList_1.length; _i++) {
+      var spec = specList_1[_i];spec.signatureType = signatureType;spec.policyTbl = policyTbl;spec.num = num;spec.flags = flags;bindClass.addMethod(spec);
+    }
+  }function _nbind_value(name, proto) {
+    if (!_nbind.typeNameTbl[name]) _nbind.throwError("Unknown value type " + name);Module["NBind"].bind_value(name, proto);_defineHidden(_nbind.typeNameTbl[name].proto.prototype.__nbindValueConstructor)(proto.prototype, "__nbindValueConstructor");
+  }Module["_nbind_value"] = _nbind_value;function __nbind_get_value_object(num, ptr) {
+    var obj = _nbind.popValue(num);if (!obj.fromJS) {
+      throw new Error("Object " + obj + " has no fromJS function");
+    }obj.fromJS(function () {
+      obj.__nbindValueConstructor.apply(this, Array.prototype.concat.apply([ptr], arguments));
+    });
+  }function _emscripten_memcpy_big(dest, src, num) {
+    HEAPU8.set(HEAPU8.subarray(src, src + num), dest);return dest;
+  }function __nbind_register_primitive(id, size, flags) {
+    var spec = { flags: 1024 | flags, id: id, ptrSize: size };_nbind.makeType(_nbind.constructType, spec);
+  }var cttz_i8 = allocate([8, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0, 4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0], "i8", ALLOC_STATIC);function ___setErrNo(value) {
+    if (Module["___errno_location"]) HEAP32[Module["___errno_location"]() >> 2] = value;return value;
+  }function _llvm_stacksave() {
+    var self = _llvm_stacksave;if (!self.LLVM_SAVEDSTACKS) {
+      self.LLVM_SAVEDSTACKS = [];
+    }self.LLVM_SAVEDSTACKS.push(Runtime.stackSave());return self.LLVM_SAVEDSTACKS.length - 1;
+  }function ___syscall140(which, varargs) {
+    SYSCALLS.varargs = varargs;try {
+      var stream = SYSCALLS.getStreamFromFD(),
+          offset_high = SYSCALLS.get(),
+          offset_low = SYSCALLS.get(),
+          result = SYSCALLS.get(),
+          whence = SYSCALLS.get();var offset = offset_low;FS.llseek(stream, offset, whence);HEAP32[result >> 2] = stream.position;if (stream.getdents && offset === 0 && whence === 0) stream.getdents = null;return 0;
+    } catch (e) {
+      if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e);return -e.errno;
+    }
+  }function ___syscall146(which, varargs) {
+    SYSCALLS.varargs = varargs;try {
+      var stream = SYSCALLS.get(),
+          iov = SYSCALLS.get(),
+          iovcnt = SYSCALLS.get();var ret = 0;if (!___syscall146.buffer) {
+        ___syscall146.buffers = [null, [], []];___syscall146.printChar = function (stream, curr) {
+          var buffer = ___syscall146.buffers[stream];assert(buffer);if (curr === 0 || curr === 10) {
+            (stream === 1 ? Module["print"] : Module["printErr"])(UTF8ArrayToString(buffer, 0));buffer.length = 0;
+          } else {
+            buffer.push(curr);
+          }
+        };
+      }for (var i = 0; i < iovcnt; i++) {
+        var ptr = HEAP32[iov + i * 8 >> 2];var len = HEAP32[iov + (i * 8 + 4) >> 2];for (var j = 0; j < len; j++) {
+          ___syscall146.printChar(stream, HEAPU8[ptr + j]);
+        }ret += len;
+      }return ret;
+    } catch (e) {
+      if (typeof FS === "undefined" || !(e instanceof FS.ErrnoError)) abort(e);return -e.errno;
+    }
+  }function __nbind_finish() {
+    for (var _i = 0, _a = _nbind.BindClass.list; _i < _a.length; _i++) {
+      var bindClass = _a[_i];bindClass.finish();
+    }
+  }var ___dso_handle = STATICTOP;STATICTOP += 16;(function (_nbind) {
+    var typeIdTbl = {};_nbind.typeNameTbl = {};var Pool = function () {
+      function Pool() {}Pool.lalloc = function (size) {
+        size = size + 7 & ~7;var used = HEAPU32[Pool.usedPtr];if (size > Pool.pageSize / 2 || size > Pool.pageSize - used) {
+          var NBind = _nbind.typeNameTbl["NBind"].proto;return NBind.lalloc(size);
+        } else {
+          HEAPU32[Pool.usedPtr] = used + size;return Pool.rootPtr + used;
+        }
+      };Pool.lreset = function (used, page) {
+        var topPage = HEAPU32[Pool.pagePtr];if (topPage) {
+          var NBind = _nbind.typeNameTbl["NBind"].proto;NBind.lreset(used, page);
+        } else {
+          HEAPU32[Pool.usedPtr] = used;
+        }
+      };return Pool;
+    }();_nbind.Pool = Pool;function constructType(kind, spec) {
+      var construct = kind == 10240 ? _nbind.makeTypeNameTbl[spec.name] || _nbind.BindType : _nbind.makeTypeKindTbl[kind];var bindType = new construct(spec);typeIdTbl[spec.id] = bindType;_nbind.typeNameTbl[spec.name] = bindType;return bindType;
+    }_nbind.constructType = constructType;function getType(id) {
+      return typeIdTbl[id];
+    }_nbind.getType = getType;function queryType(id) {
+      var placeholderFlag = HEAPU8[id];var paramCount = _nbind.structureList[placeholderFlag][1];id /= 4;if (paramCount < 0) {
+        ++id;paramCount = HEAPU32[id] + 1;
+      }var paramList = Array.prototype.slice.call(HEAPU32.subarray(id + 1, id + 1 + paramCount));if (placeholderFlag == 9) {
+        paramList = [paramList[0], paramList.slice(1)];
+      }return { paramList: paramList, placeholderFlag: placeholderFlag };
+    }_nbind.queryType = queryType;function getTypes(idList, place) {
+      return idList.map(function (id) {
+        return typeof id == "number" ? _nbind.getComplexType(id, constructType, getType, queryType, place) : _nbind.typeNameTbl[id];
+      });
+    }_nbind.getTypes = getTypes;function readTypeIdList(typeListPtr, typeCount) {
+      return Array.prototype.slice.call(HEAPU32, typeListPtr / 4, typeListPtr / 4 + typeCount);
+    }_nbind.readTypeIdList = readTypeIdList;function readAsciiString(ptr) {
+      var endPtr = ptr;while (HEAPU8[endPtr++]);return String.fromCharCode.apply("", HEAPU8.subarray(ptr, endPtr - 1));
+    }_nbind.readAsciiString = readAsciiString;function readPolicyList(policyListPtr) {
+      var policyTbl = {};if (policyListPtr) {
+        while (1) {
+          var namePtr = HEAPU32[policyListPtr / 4];if (!namePtr) break;policyTbl[readAsciiString(namePtr)] = true;policyListPtr += 4;
+        }
+      }return policyTbl;
+    }_nbind.readPolicyList = readPolicyList;function getDynCall(typeList, name) {
+      var mangleMap = { float32_t: "d", float64_t: "d", int64_t: "d", uint64_t: "d", "void": "v" };var signature = typeList.map(function (type) {
+        return mangleMap[type.name] || "i";
+      }).join("");var dynCall = Module["dynCall_" + signature];if (!dynCall) {
+        throw new Error("dynCall_" + signature + " not found for " + name + "(" + typeList.map(function (type) {
+          return type.name;
+        }).join(", ") + ")");
+      }return dynCall;
+    }_nbind.getDynCall = getDynCall;function addMethod(obj, name, func, arity) {
+      var overload = obj[name];if (obj.hasOwnProperty(name) && overload) {
+        if (overload.arity || overload.arity === 0) {
+          overload = _nbind.makeOverloader(overload, overload.arity);obj[name] = overload;
+        }overload.addMethod(func, arity);
+      } else {
+        func.arity = arity;obj[name] = func;
+      }
+    }_nbind.addMethod = addMethod;function throwError(message) {
+      throw new Error(message);
+    }_nbind.throwError = throwError;_nbind.bigEndian = false;var _a = _typeModule(_typeModule); _nbind.Type = _a.Type, _nbind.makeType = _a.makeType, _nbind.getComplexType = _a.getComplexType, _nbind.structureList = _a.structureList;var BindType = function (_super) {
+      __extends(BindType, _super);function BindType() {
+        var _this = _super !== null && _super.apply(this, arguments) || this;_this.heap = HEAPU32;_this.ptrSize = 4;return _this;
+      }BindType.prototype.needsWireRead = function (policyTbl) {
+        return !!this.wireRead || !!this.makeWireRead;
+      };BindType.prototype.needsWireWrite = function (policyTbl) {
+        return !!this.wireWrite || !!this.makeWireWrite;
+      };return BindType;
+    }(_nbind.Type);_nbind.BindType = BindType;var PrimitiveType = function (_super) {
+      __extends(PrimitiveType, _super);function PrimitiveType(spec) {
+        var _this = _super.call(this, spec) || this;var heapTbl = spec.flags & 32 ? { 32: HEAPF32, 64: HEAPF64 } : spec.flags & 8 ? { 8: HEAPU8, 16: HEAPU16, 32: HEAPU32 } : { 8: HEAP8, 16: HEAP16, 32: HEAP32 };_this.heap = heapTbl[spec.ptrSize * 8];_this.ptrSize = spec.ptrSize;return _this;
+      }PrimitiveType.prototype.needsWireWrite = function (policyTbl) {
+        return !!policyTbl && !!policyTbl["Strict"];
+      };PrimitiveType.prototype.makeWireWrite = function (expr, policyTbl) {
+        return policyTbl && policyTbl["Strict"] && function (arg) {
+          if (typeof arg == "number") return arg;throw new Error("Type mismatch");
+        };
+      };return PrimitiveType;
+    }(BindType);_nbind.PrimitiveType = PrimitiveType;function pushCString(str, policyTbl) {
+      if (str === null || str === undefined) {
+        if (policyTbl && policyTbl["Nullable"]) {
+          return 0;
+        } else throw new Error("Type mismatch");
+      }if (policyTbl && policyTbl["Strict"]) {
+        if (typeof str != "string") throw new Error("Type mismatch");
+      } else str = str.toString();var length = Module.lengthBytesUTF8(str) + 1;var result = _nbind.Pool.lalloc(length);Module.stringToUTF8Array(str, HEAPU8, result, length);return result;
+    }_nbind.pushCString = pushCString;function popCString(ptr) {
+      if (ptr === 0) return null;return Module.Pointer_stringify(ptr);
+    }_nbind.popCString = popCString;var CStringType = function (_super) {
+      __extends(CStringType, _super);function CStringType() {
+        var _this = _super !== null && _super.apply(this, arguments) || this;_this.wireRead = popCString;_this.wireWrite = pushCString;_this.readResources = [_nbind.resources.pool];_this.writeResources = [_nbind.resources.pool];return _this;
+      }CStringType.prototype.makeWireWrite = function (expr, policyTbl) {
+        return function (arg) {
+          return pushCString(arg, policyTbl);
+        };
+      };return CStringType;
+    }(BindType);_nbind.CStringType = CStringType;var BooleanType = function (_super) {
+      __extends(BooleanType, _super);function BooleanType() {
+        var _this = _super !== null && _super.apply(this, arguments) || this;_this.wireRead = function (arg) {
+          return !!arg;
+        };return _this;
+      }BooleanType.prototype.needsWireWrite = function (policyTbl) {
+        return !!policyTbl && !!policyTbl["Strict"];
+      };BooleanType.prototype.makeWireRead = function (expr) {
+        return "!!(" + expr + ")";
+      };BooleanType.prototype.makeWireWrite = function (expr, policyTbl) {
+        return policyTbl && policyTbl["Strict"] && function (arg) {
+          if (typeof arg == "boolean") return arg;throw new Error("Type mismatch");
+        } || expr;
+      };return BooleanType;
+    }(BindType);_nbind.BooleanType = BooleanType;var Wrapper = function () {
+      function Wrapper() {}Wrapper.prototype.persist = function () {
+        this.__nbindState |= 1;
+      };return Wrapper;
+    }();_nbind.Wrapper = Wrapper;function makeBound(policyTbl, bindClass) {
+      var Bound = function (_super) {
+        __extends(Bound, _super);function Bound(marker, flags, ptr, shared) {
+          var _this = _super.call(this) || this;if (!(_this instanceof Bound)) {
+            return new (Function.prototype.bind.apply(Bound, Array.prototype.concat.apply([null], arguments)))();
+          }var nbindFlags = flags;var nbindPtr = ptr;var nbindShared = shared;if (marker !== _nbind.ptrMarker) {
+            var wirePtr = _this.__nbindConstructor.apply(_this, arguments);nbindFlags = 4096 | 512;nbindShared = HEAPU32[wirePtr / 4];nbindPtr = HEAPU32[wirePtr / 4 + 1];
+          }var spec = { configurable: true, enumerable: false, value: null, writable: false };var propTbl = { "__nbindFlags": nbindFlags, "__nbindPtr": nbindPtr };if (nbindShared) {
+            propTbl["__nbindShared"] = nbindShared;_nbind.mark(_this);
+          }for (var _i = 0, _a = Object.keys(propTbl); _i < _a.length; _i++) {
+            var key = _a[_i];spec.value = propTbl[key];Object.defineProperty(_this, key, spec);
+          }_defineHidden(0)(_this, "__nbindState");return _this;
+        }Bound.prototype.free = function () {
+          bindClass.destroy.call(this, this.__nbindShared, this.__nbindFlags);this.__nbindState |= 2;disableMember(this, "__nbindShared");disableMember(this, "__nbindPtr");
+        };return Bound;
+      }(Wrapper);__decorate([_defineHidden()], Bound.prototype, "__nbindConstructor", void 0);__decorate([_defineHidden()], Bound.prototype, "__nbindValueConstructor", void 0);__decorate([_defineHidden(policyTbl)], Bound.prototype, "__nbindPolicies", void 0);return Bound;
+    }_nbind.makeBound = makeBound;function disableMember(obj, name) {
+      function die() {
+        throw new Error("Accessing deleted object");
+      }Object.defineProperty(obj, name, { configurable: false, enumerable: false, get: die, set: die });
+    }_nbind.ptrMarker = {};var BindClass = function (_super) {
+      __extends(BindClass, _super);function BindClass(spec) {
+        var _this = _super.call(this, spec) || this;_this.wireRead = function (arg) {
+          return _nbind.popValue(arg, _this.ptrType);
+        };_this.wireWrite = function (arg) {
+          return pushPointer(arg, _this.ptrType, true);
+        };_this.pendingSuperCount = 0;_this.ready = false;_this.methodTbl = {};if (spec.paramList) {
+          _this.classType = spec.paramList[0].classType;_this.proto = _this.classType.proto;
+        } else _this.classType = _this;return _this;
+      }BindClass.prototype.makeBound = function (policyTbl) {
+        var Bound = _nbind.makeBound(policyTbl, this);this.proto = Bound;this.ptrType.proto = Bound;return Bound;
+      };BindClass.prototype.addMethod = function (spec) {
+        var overloadList = this.methodTbl[spec.name] || [];overloadList.push(spec);this.methodTbl[spec.name] = overloadList;
+      };BindClass.prototype.registerMethods = function (src, staticOnly) {
+        var setter;for (var _i = 0, _a = Object.keys(src.methodTbl); _i < _a.length; _i++) {
+          var name_1 = _a[_i];var overloadList = src.methodTbl[name_1];for (var _b = 0, overloadList_1 = overloadList; _b < overloadList_1.length; _b++) {
+            var spec = overloadList_1[_b];var target = void 0;var caller = void 0;target = this.proto.prototype;if (staticOnly && spec.signatureType != 1) continue;switch (spec.signatureType) {case 1:
+                target = this.proto;case 5:
+                caller = _nbind.makeCaller(spec);_nbind.addMethod(target, spec.name, caller, spec.typeList.length - 1);break;case 4:
+                setter = _nbind.makeMethodCaller(src.ptrType, spec);break;case 3:
+                Object.defineProperty(target, spec.name, { configurable: true, enumerable: false, get: _nbind.makeMethodCaller(src.ptrType, spec), set: setter });break;case 2:
+                caller = _nbind.makeMethodCaller(src.ptrType, spec);_nbind.addMethod(target, spec.name, caller, spec.typeList.length - 1);break;default:
+                break;}
+          }
+        }
+      };BindClass.prototype.registerSuperMethods = function (src, firstSuper, visitTbl) {
+        if (visitTbl[src.name]) return;visitTbl[src.name] = true;var superNum = 0;var nextFirst;for (var _i = 0, _a = src.superIdList || []; _i < _a.length; _i++) {
+          var superId = _a[_i];var superClass = _nbind.getType(superId);if (superNum++ < firstSuper || firstSuper < 0) {
+            nextFirst = -1;
+          } else {
+            nextFirst = 0;
+          }this.registerSuperMethods(superClass, nextFirst, visitTbl);
+        }this.registerMethods(src, firstSuper < 0);
+      };BindClass.prototype.finish = function () {
+        if (this.ready) return this;this.ready = true;this.superList = (this.superIdList || []).map(function (superId) {
+          return _nbind.getType(superId).finish();
+        });var Bound = this.proto;if (this.superList.length) {
+          var Proto = function () {
+            this.constructor = Bound;
+          };Proto.prototype = this.superList[0].proto.prototype;Bound.prototype = new Proto();
+        }if (Bound != Module) Bound.prototype.__nbindType = this;this.registerSuperMethods(this, 1, {});return this;
+      };BindClass.prototype.upcastStep = function (dst, ptr) {
+        if (dst == this) return ptr;for (var i = 0; i < this.superList.length; ++i) {
+          var superPtr = this.superList[i].upcastStep(dst, _nbind.callUpcast(this.upcastList[i], ptr));if (superPtr) return superPtr;
+        }return 0;
+      };return BindClass;
+    }(_nbind.BindType);BindClass.list = [];_nbind.BindClass = BindClass;function popPointer(ptr, type) {
+      return ptr ? new type.proto(_nbind.ptrMarker, type.flags, ptr) : null;
+    }_nbind.popPointer = popPointer;function pushPointer(obj, type, tryValue) {
+      if (!(obj instanceof _nbind.Wrapper)) {
+        if (tryValue) {
+          return _nbind.pushValue(obj);
+        } else throw new Error("Type mismatch");
+      }var ptr = obj.__nbindPtr;var objType = obj.__nbindType.classType;var classType = type.classType;if (obj instanceof type.proto) {
+        while (objType != classType) {
+          ptr = _nbind.callUpcast(objType.upcastList[0], ptr);objType = objType.superList[0];
+        }
+      } else {
+        ptr = objType.upcastStep(classType, ptr);if (!ptr) throw new Error("Type mismatch");
+      }return ptr;
+    }_nbind.pushPointer = pushPointer;function pushMutablePointer(obj, type) {
+      var ptr = pushPointer(obj, type);if (obj.__nbindFlags & 1) {
+        throw new Error("Passing a const value as a non-const argument");
+      }return ptr;
+    }var BindClassPtr = function (_super) {
+      __extends(BindClassPtr, _super);function BindClassPtr(spec) {
+        var _this = _super.call(this, spec) || this;_this.classType = spec.paramList[0].classType;_this.proto = _this.classType.proto;var isConst = spec.flags & 1;var isValue = (_this.flags & 896) == 256 && spec.flags & 2;var push = isConst ? pushPointer : pushMutablePointer;var pop = isValue ? _nbind.popValue : popPointer;_this.makeWireWrite = function (expr, policyTbl) {
+          return policyTbl["Nullable"] ? function (arg) {
+            return arg ? push(arg, _this) : 0;
+          } : function (arg) {
+            return push(arg, _this);
+          };
+        };_this.wireRead = function (arg) {
+          return pop(arg, _this);
+        };_this.wireWrite = function (arg) {
+          return push(arg, _this);
+        };return _this;
+      }return BindClassPtr;
+    }(_nbind.BindType);_nbind.BindClassPtr = BindClassPtr;function popShared(ptr, type) {
+      var shared = HEAPU32[ptr / 4];var unsafe = HEAPU32[ptr / 4 + 1];return unsafe ? new type.proto(_nbind.ptrMarker, type.flags, unsafe, shared) : null;
+    }_nbind.popShared = popShared;function pushShared(obj, type) {
+      if (!(obj instanceof type.proto)) throw new Error("Type mismatch");return obj.__nbindShared;
+    }function pushMutableShared(obj, type) {
+      if (!(obj instanceof type.proto)) throw new Error("Type mismatch");if (obj.__nbindFlags & 1) {
+        throw new Error("Passing a const value as a non-const argument");
+      }return obj.__nbindShared;
+    }var SharedClassPtr = function (_super) {
+      __extends(SharedClassPtr, _super);function SharedClassPtr(spec) {
+        var _this = _super.call(this, spec) || this;_this.readResources = [_nbind.resources.pool];_this.classType = spec.paramList[0].classType;_this.proto = _this.classType.proto;var isConst = spec.flags & 1;var push = isConst ? pushShared : pushMutableShared;_this.wireRead = function (arg) {
+          return popShared(arg, _this);
+        };_this.wireWrite = function (arg) {
+          return push(arg, _this);
+        };return _this;
+      }return SharedClassPtr;
+    }(_nbind.BindType);_nbind.SharedClassPtr = SharedClassPtr;_nbind.externalList = [0];var firstFreeExternal = 0;var External = function () {
+      function External(data) {
+        this.refCount = 1;this.data = data;
+      }External.prototype.register = function () {
+        var num = firstFreeExternal;if (num) {
+          firstFreeExternal = _nbind.externalList[num];
+        } else num = _nbind.externalList.length;_nbind.externalList[num] = this;return num;
+      };External.prototype.reference = function () {
+        ++this.refCount;
+      };External.prototype.dereference = function (num) {
+        if (--this.refCount == 0) {
+          if (this.free) this.free();_nbind.externalList[num] = firstFreeExternal;firstFreeExternal = num;
+        }
+      };return External;
+    }();_nbind.External = External;function popExternal(num) {
+      var obj = _nbind.externalList[num];obj.dereference(num);return obj.data;
+    }function pushExternal(obj) {
+      var external = new External(obj);external.reference();return external.register();
+    }var ExternalType = function (_super) {
+      __extends(ExternalType, _super);function ExternalType() {
+        var _this = _super !== null && _super.apply(this, arguments) || this;_this.wireRead = popExternal;_this.wireWrite = pushExternal;return _this;
+      }return ExternalType;
+    }(_nbind.BindType);_nbind.ExternalType = ExternalType;_nbind.callbackSignatureList = [];var CallbackType = function (_super) {
+      __extends(CallbackType, _super);function CallbackType() {
+        var _this = _super !== null && _super.apply(this, arguments) || this;_this.wireWrite = function (func) {
+          if (typeof func != "function") _nbind.throwError("Type mismatch");return new _nbind.External(func).register();
+        };return _this;
+      }return CallbackType;
+    }(_nbind.BindType);_nbind.CallbackType = CallbackType;_nbind.valueList = [0];var firstFreeValue = 0;function pushValue(value) {
+      var num = firstFreeValue;if (num) {
+        firstFreeValue = _nbind.valueList[num];
+      } else num = _nbind.valueList.length;_nbind.valueList[num] = value;return num * 2 + 1;
+    }_nbind.pushValue = pushValue;function popValue(num, type) {
+      if (!num) _nbind.throwError("Value type JavaScript class is missing or not registered");if (num & 1) {
+        num >>= 1;var obj = _nbind.valueList[num];_nbind.valueList[num] = firstFreeValue;firstFreeValue = num;return obj;
+      } else if (type) {
+        return _nbind.popShared(num, type);
+      } else throw new Error("Invalid value slot " + num);
+    }_nbind.popValue = popValue;var valueBase = 0x10000000000000000;function push64(num) {
+      if (typeof num == "number") return num;return pushValue(num) * 4096 + valueBase;
+    }function pop64(num) {
+      if (num < valueBase) return num;return popValue((num - valueBase) / 4096);
+    }var CreateValueType = function (_super) {
+      __extends(CreateValueType, _super);function CreateValueType() {
+        return _super !== null && _super.apply(this, arguments) || this;
+      }CreateValueType.prototype.makeWireWrite = function (expr) {
+        return "(_nbind.pushValue(new " + expr + "))";
+      };return CreateValueType;
+    }(_nbind.BindType);_nbind.CreateValueType = CreateValueType;var Int64Type = function (_super) {
+      __extends(Int64Type, _super);function Int64Type() {
+        var _this = _super !== null && _super.apply(this, arguments) || this;_this.wireWrite = push64;_this.wireRead = pop64;return _this;
+      }return Int64Type;
+    }(_nbind.BindType);_nbind.Int64Type = Int64Type;function pushArray(arr, type) {
+      if (!arr) return 0;var length = arr.length;if ((type.size || type.size === 0) && length < type.size) {
+        throw new Error("Type mismatch");
+      }var ptrSize = type.memberType.ptrSize;var result = _nbind.Pool.lalloc(4 + length * ptrSize);HEAPU32[result / 4] = length;var heap = type.memberType.heap;var ptr = (result + 4) / ptrSize;var wireWrite = type.memberType.wireWrite;var num = 0;if (wireWrite) {
+        while (num < length) {
+          heap[ptr++] = wireWrite(arr[num++]);
+        }
+      } else {
+        while (num < length) {
+          heap[ptr++] = arr[num++];
+        }
+      }return result;
+    }_nbind.pushArray = pushArray;function popArray(ptr, type) {
+      if (ptr === 0) return null;var length = HEAPU32[ptr / 4];var arr = new Array(length);var heap = type.memberType.heap;ptr = (ptr + 4) / type.memberType.ptrSize;var wireRead = type.memberType.wireRead;var num = 0;if (wireRead) {
+        while (num < length) {
+          arr[num++] = wireRead(heap[ptr++]);
+        }
+      } else {
+        while (num < length) {
+          arr[num++] = heap[ptr++];
+        }
+      }return arr;
+    }_nbind.popArray = popArray;var ArrayType = function (_super) {
+      __extends(ArrayType, _super);function ArrayType(spec) {
+        var _this = _super.call(this, spec) || this;_this.wireRead = function (arg) {
+          return popArray(arg, _this);
+        };_this.wireWrite = function (arg) {
+          return pushArray(arg, _this);
+        };_this.readResources = [_nbind.resources.pool];_this.writeResources = [_nbind.resources.pool];_this.memberType = spec.paramList[0];if (spec.paramList[1]) _this.size = spec.paramList[1];return _this;
+      }return ArrayType;
+    }(_nbind.BindType);_nbind.ArrayType = ArrayType;function pushString(str, policyTbl) {
+      if (str === null || str === undefined) {
+        if (policyTbl && policyTbl["Nullable"]) {
+          str = "";
+        } else throw new Error("Type mismatch");
+      }if (policyTbl && policyTbl["Strict"]) {
+        if (typeof str != "string") throw new Error("Type mismatch");
+      } else str = str.toString();var length = Module.lengthBytesUTF8(str);var result = _nbind.Pool.lalloc(4 + length + 1);HEAPU32[result / 4] = length;Module.stringToUTF8Array(str, HEAPU8, result + 4, length + 1);return result;
+    }_nbind.pushString = pushString;function popString(ptr) {
+      if (ptr === 0) return null;var length = HEAPU32[ptr / 4];return Module.Pointer_stringify(ptr + 4, length);
+    }_nbind.popString = popString;var StringType = function (_super) {
+      __extends(StringType, _super);function StringType() {
+        var _this = _super !== null && _super.apply(this, arguments) || this;_this.wireRead = popString;_this.wireWrite = pushString;_this.readResources = [_nbind.resources.pool];_this.writeResources = [_nbind.resources.pool];return _this;
+      }StringType.prototype.makeWireWrite = function (expr, policyTbl) {
+        return function (arg) {
+          return pushString(arg, policyTbl);
+        };
+      };return StringType;
+    }(_nbind.BindType);_nbind.StringType = StringType;function makeArgList(argCount) {
+      return Array.apply(null, Array(argCount)).map(function (dummy, num) {
+        return "a" + (num + 1);
+      });
+    }function anyNeedsWireWrite(typeList, policyTbl) {
+      return typeList.reduce(function (result, type) {
+        return result || type.needsWireWrite(policyTbl);
+      }, false);
+    }function anyNeedsWireRead(typeList, policyTbl) {
+      return typeList.reduce(function (result, type) {
+        return result || !!type.needsWireRead(policyTbl);
+      }, false);
+    }function makeWireRead(convertParamList, policyTbl, type, expr) {
+      var paramNum = convertParamList.length;if (type.makeWireRead) {
+        return type.makeWireRead(expr, convertParamList, paramNum);
+      } else if (type.wireRead) {
+        convertParamList[paramNum] = type.wireRead;return "(convertParamList[" + paramNum + "](" + expr + "))";
+      } else return expr;
+    }function makeWireWrite(convertParamList, policyTbl, type, expr) {
+      var wireWrite;var paramNum = convertParamList.length;if (type.makeWireWrite) {
+        wireWrite = type.makeWireWrite(expr, policyTbl, convertParamList, paramNum);
+      } else wireWrite = type.wireWrite;if (wireWrite) {
+        if (typeof wireWrite == "string") {
+          return wireWrite;
+        } else {
+          convertParamList[paramNum] = wireWrite;return "(convertParamList[" + paramNum + "](" + expr + "))";
+        }
+      } else return expr;
+    }function buildCallerFunction(dynCall, ptrType, ptr, num, policyTbl, needsWireWrite, prefix, returnType, argTypeList, mask, err) {
+      var argList = makeArgList(argTypeList.length);var convertParamList = [];var callExpression = makeWireRead(convertParamList, policyTbl, returnType, "dynCall(" + [prefix].concat(argList.map(function (name, index) {
+        return makeWireWrite(convertParamList, policyTbl, argTypeList[index], name);
+      })).join(",") + ")");var resourceSet = _nbind.listResources([returnType], argTypeList);var sourceCode = "function(" + argList.join(",") + "){" + (mask ? "this.__nbindFlags&mask&&err();" : "") + resourceSet.makeOpen() + "var r=" + callExpression + ";" + resourceSet.makeClose() + "return r;" + "}";return eval("(" + sourceCode + ")");
+    }function buildJSCallerFunction(returnType, argTypeList) {
+      var argList = makeArgList(argTypeList.length);var convertParamList = [];var callExpression = makeWireWrite(convertParamList, null, returnType, "_nbind.externalList[num].data(" + argList.map(function (name, index) {
+        return makeWireRead(convertParamList, null, argTypeList[index], name);
+      }).join(",") + ")");var resourceSet = _nbind.listResources(argTypeList, [returnType]);resourceSet.remove(_nbind.resources.pool);var sourceCode = "function(" + ["dummy", "num"].concat(argList).join(",") + "){" + resourceSet.makeOpen() + "var r=" + callExpression + ";" + resourceSet.makeClose() + "return r;" + "}";return eval("(" + sourceCode + ")");
+    }_nbind.buildJSCallerFunction = buildJSCallerFunction;function makeJSCaller(idList) {
+      var argCount = idList.length - 1;var typeList = _nbind.getTypes(idList, "callback");var returnType = typeList[0];var argTypeList = typeList.slice(1);var needsWireRead = anyNeedsWireRead(argTypeList, null);var needsWireWrite = returnType.needsWireWrite(null);if (!needsWireWrite && !needsWireRead) {
+        switch (argCount) {case 0:
+            return function (dummy, num) {
+              return _nbind.externalList[num].data();
+            };case 1:
+            return function (dummy, num, a1) {
+              return _nbind.externalList[num].data(a1);
+            };case 2:
+            return function (dummy, num, a1, a2) {
+              return _nbind.externalList[num].data(a1, a2);
+            };case 3:
+            return function (dummy, num, a1, a2, a3) {
+              return _nbind.externalList[num].data(a1, a2, a3);
+            };default:
+            break;}
+      }return buildJSCallerFunction(returnType, argTypeList);
+    }_nbind.makeJSCaller = makeJSCaller;function makeMethodCaller(ptrType, spec) {
+      var argCount = spec.typeList.length - 1;var typeIdList = spec.typeList.slice(0);typeIdList.splice(1, 0, "uint32_t", spec.boundID);var typeList = _nbind.getTypes(typeIdList, spec.title);var returnType = typeList[0];var argTypeList = typeList.slice(3);var needsWireRead = returnType.needsWireRead(spec.policyTbl);var needsWireWrite = anyNeedsWireWrite(argTypeList, spec.policyTbl);var ptr = spec.ptr;var num = spec.num;var dynCall = _nbind.getDynCall(typeList, spec.title);var mask = ~spec.flags & 1;function err() {
+        throw new Error("Calling a non-const method on a const object");
+      }if (!needsWireRead && !needsWireWrite) {
+        switch (argCount) {case 0:
+            return function () {
+              return this.__nbindFlags & mask ? err() : dynCall(ptr, num, _nbind.pushPointer(this, ptrType));
+            };case 1:
+            return function (a1) {
+              return this.__nbindFlags & mask ? err() : dynCall(ptr, num, _nbind.pushPointer(this, ptrType), a1);
+            };case 2:
+            return function (a1, a2) {
+              return this.__nbindFlags & mask ? err() : dynCall(ptr, num, _nbind.pushPointer(this, ptrType), a1, a2);
+            };case 3:
+            return function (a1, a2, a3) {
+              return this.__nbindFlags & mask ? err() : dynCall(ptr, num, _nbind.pushPointer(this, ptrType), a1, a2, a3);
+            };default:
+            break;}
+      }return buildCallerFunction(dynCall, ptrType, ptr, num, spec.policyTbl, needsWireWrite, "ptr,num,pushPointer(this,ptrType)", returnType, argTypeList, mask, err);
+    }_nbind.makeMethodCaller = makeMethodCaller;function makeCaller(spec) {
+      var argCount = spec.typeList.length - 1;var typeList = _nbind.getTypes(spec.typeList, spec.title);var returnType = typeList[0];var argTypeList = typeList.slice(1);var needsWireRead = returnType.needsWireRead(spec.policyTbl);var needsWireWrite = anyNeedsWireWrite(argTypeList, spec.policyTbl);var direct = spec.direct;var ptr = spec.ptr;if (spec.direct && !needsWireRead && !needsWireWrite) {
+        var dynCall_1 = _nbind.getDynCall(typeList, spec.title);switch (argCount) {case 0:
+            return function () {
+              return dynCall_1(direct);
+            };case 1:
+            return function (a1) {
+              return dynCall_1(direct, a1);
+            };case 2:
+            return function (a1, a2) {
+              return dynCall_1(direct, a1, a2);
+            };case 3:
+            return function (a1, a2, a3) {
+              return dynCall_1(direct, a1, a2, a3);
+            };default:
+            break;}ptr = 0;
+      }var prefix;if (ptr) {
+        var typeIdList = spec.typeList.slice(0);typeIdList.splice(1, 0, "uint32_t");typeList = _nbind.getTypes(typeIdList, spec.title);prefix = "ptr,num";
+      } else {
+        ptr = direct;prefix = "ptr";
+      }var dynCall = _nbind.getDynCall(typeList, spec.title);return buildCallerFunction(dynCall, null, ptr, spec.num, spec.policyTbl, needsWireWrite, prefix, returnType, argTypeList);
+    }_nbind.makeCaller = makeCaller;function makeOverloader(func, arity) {
+      var callerList = [];function call() {
+        return callerList[arguments.length].apply(this, arguments);
+      }call.addMethod = function (_func, _arity) {
+        callerList[_arity] = _func;
+      };call.addMethod(func, arity);return call;
+    }_nbind.makeOverloader = makeOverloader;var Resource = function () {
+      function Resource(open, close) {
+        var _this = this;this.makeOpen = function () {
+          return Object.keys(_this.openTbl).join("");
+        };this.makeClose = function () {
+          return Object.keys(_this.closeTbl).join("");
+        };this.openTbl = {};this.closeTbl = {};if (open) this.openTbl[open] = true;if (close) this.closeTbl[close] = true;
+      }Resource.prototype.add = function (other) {
+        for (var _i = 0, _a = Object.keys(other.openTbl); _i < _a.length; _i++) {
+          var key = _a[_i];this.openTbl[key] = true;
+        }for (var _b = 0, _c = Object.keys(other.closeTbl); _b < _c.length; _b++) {
+          var key = _c[_b];this.closeTbl[key] = true;
+        }
+      };Resource.prototype.remove = function (other) {
+        for (var _i = 0, _a = Object.keys(other.openTbl); _i < _a.length; _i++) {
+          var key = _a[_i];delete this.openTbl[key];
+        }for (var _b = 0, _c = Object.keys(other.closeTbl); _b < _c.length; _b++) {
+          var key = _c[_b];delete this.closeTbl[key];
+        }
+      };return Resource;
+    }();_nbind.Resource = Resource;function listResources(readList, writeList) {
+      var result = new Resource();for (var _i = 0, readList_1 = readList; _i < readList_1.length; _i++) {
+        var bindType = readList_1[_i];for (var _a = 0, _b = bindType.readResources || []; _a < _b.length; _a++) {
+          var resource = _b[_a];result.add(resource);
+        }
+      }for (var _c = 0, writeList_1 = writeList; _c < writeList_1.length; _c++) {
+        var bindType = writeList_1[_c];for (var _d = 0, _e = bindType.writeResources || []; _d < _e.length; _d++) {
+          var resource = _e[_d];result.add(resource);
+        }
+      }return result;
+    }_nbind.listResources = listResources;_nbind.resources = { pool: new Resource("var used=HEAPU32[_nbind.Pool.usedPtr],page=HEAPU32[_nbind.Pool.pagePtr];", "_nbind.Pool.lreset(used,page);") };var ExternalBuffer = function (_super) {
+      __extends(ExternalBuffer, _super);function ExternalBuffer(buf, ptr) {
+        var _this = _super.call(this, buf) || this;_this.ptr = ptr;return _this;
+      }ExternalBuffer.prototype.free = function () {
+        _free(this.ptr);
+      };return ExternalBuffer;
+    }(_nbind.External);function getBuffer(buf) {
+      if (buf instanceof ArrayBuffer) {
+        return new Uint8Array(buf);
+      } else if (buf instanceof DataView) {
+        return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
+      } else return buf;
+    }function pushBuffer(buf, policyTbl) {
+      if (buf === null || buf === undefined) {
+        if (policyTbl && policyTbl["Nullable"]) buf = [];
+      }if (typeof buf != "object") throw new Error("Type mismatch");var b = buf;var length = b.byteLength || b.length;if (!length && length !== 0 && b.byteLength !== 0) throw new Error("Type mismatch");var result = _nbind.Pool.lalloc(8);var data = _malloc(length);var ptr = result / 4;HEAPU32[ptr++] = length;HEAPU32[ptr++] = data;HEAPU32[ptr++] = new ExternalBuffer(buf, data).register();HEAPU8.set(getBuffer(buf), data);return result;
+    }var BufferType = function (_super) {
+      __extends(BufferType, _super);function BufferType() {
+        var _this = _super !== null && _super.apply(this, arguments) || this;_this.wireWrite = pushBuffer;_this.readResources = [_nbind.resources.pool];_this.writeResources = [_nbind.resources.pool];return _this;
+      }BufferType.prototype.makeWireWrite = function (expr, policyTbl) {
+        return function (arg) {
+          return pushBuffer(arg, policyTbl);
+        };
+      };return BufferType;
+    }(_nbind.BindType);_nbind.BufferType = BufferType;function commitBuffer(num, data, length) {
+      var buf = _nbind.externalList[num].data;var NodeBuffer = Buffer;if (typeof Buffer != "function") NodeBuffer = function () {};if (buf instanceof Array) {} else {
+        var src = HEAPU8.subarray(data, data + length);if (buf instanceof NodeBuffer) {
+          var srcBuf = void 0;if (typeof Buffer.from == "function" && Buffer.from.length >= 3) {
+            srcBuf = Buffer.from(src);
+          } else srcBuf = new Buffer(src);srcBuf.copy(buf);
+        } else getBuffer(buf).set(src);
+      }
+    }_nbind.commitBuffer = commitBuffer;var dirtyList = [];var gcTimer = 0;function sweep() {
+      for (var _i = 0, dirtyList_1 = dirtyList; _i < dirtyList_1.length; _i++) {
+        var obj = dirtyList_1[_i];if (!(obj.__nbindState & (1 | 2))) {
+          obj.free();
+        }
+      }dirtyList = [];gcTimer = 0;
+    }_nbind.mark = function (obj) {};function toggleLightGC(enable) {
+      if (enable) {
+        _nbind.mark = function (obj) {
+          dirtyList.push(obj);if (!gcTimer) gcTimer = setTimeout(sweep, 0);
+        };
+      } else {
+        _nbind.mark = function (obj) {};
+      }
+    }_nbind.toggleLightGC = toggleLightGC;
+  })(_nbind);Module["requestFullScreen"] = function Module_requestFullScreen(lockPointer, resizeCanvas, vrDevice) {
+    Module.printErr("Module.requestFullScreen is deprecated. Please call Module.requestFullscreen instead.");Module["requestFullScreen"] = Module["requestFullscreen"];Browser.requestFullScreen(lockPointer, resizeCanvas, vrDevice);
+  };Module["requestFullscreen"] = function Module_requestFullscreen(lockPointer, resizeCanvas, vrDevice) {
+    Browser.requestFullscreen(lockPointer, resizeCanvas, vrDevice);
+  };Module["requestAnimationFrame"] = function Module_requestAnimationFrame(func) {
+    Browser.requestAnimationFrame(func);
+  };Module["setCanvasSize"] = function Module_setCanvasSize(width, height, noUpdates) {
+    Browser.setCanvasSize(width, height, noUpdates);
+  };Module["pauseMainLoop"] = function Module_pauseMainLoop() {
+    Browser.mainLoop.pause();
+  };Module["resumeMainLoop"] = function Module_resumeMainLoop() {
+    Browser.mainLoop.resume();
+  };Module["getUserMedia"] = function Module_getUserMedia() {
+    Browser.getUserMedia();
+  };Module["createContext"] = function Module_createContext(canvas, useWebGL, setInModule, webGLContextAttributes) {
+    return Browser.createContext(canvas, useWebGL, setInModule, webGLContextAttributes);
+  };if (ENVIRONMENT_IS_NODE) {
+    _emscripten_get_now = function _emscripten_get_now_actual() {
+      var t = process["hrtime"]();return t[0] * 1e3 + t[1] / 1e6;
+    };
+  } else if (typeof dateNow !== "undefined") {
+    _emscripten_get_now = dateNow;
+  } else if (typeof self === "object" && self["performance"] && typeof self["performance"]["now"] === "function") {
+    _emscripten_get_now = function () {
+      return self["performance"]["now"]();
+    };
+  } else if (typeof performance === "object" && typeof performance["now"] === "function") {
+    _emscripten_get_now = function () {
+      return performance["now"]();
+    };
+  } else {
+    _emscripten_get_now = Date.now;
+  }__ATEXIT__.push(function () {
+    var fflush = Module["_fflush"];if (fflush) fflush(0);var printChar = ___syscall146.printChar;if (!printChar) return;var buffers = ___syscall146.buffers;if (buffers[1].length) printChar(1, 10);if (buffers[2].length) printChar(2, 10);
+  });DYNAMICTOP_PTR = allocate(1, "i32", ALLOC_STATIC);STACK_BASE = STACKTOP = Runtime.alignMemory(STATICTOP);STACK_MAX = STACK_BASE + TOTAL_STACK;DYNAMIC_BASE = Runtime.alignMemory(STACK_MAX);HEAP32[DYNAMICTOP_PTR >> 2] = DYNAMIC_BASE;staticSealed = true;function invoke_viiiii(index, a1, a2, a3, a4, a5) {
+    try {
+      Module["dynCall_viiiii"](index, a1, a2, a3, a4, a5);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_vif(index, a1, a2) {
+    try {
+      Module["dynCall_vif"](index, a1, a2);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_vid(index, a1, a2) {
+    try {
+      Module["dynCall_vid"](index, a1, a2);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_fiff(index, a1, a2, a3) {
+    try {
+      return Module["dynCall_fiff"](index, a1, a2, a3);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_vi(index, a1) {
+    try {
+      Module["dynCall_vi"](index, a1);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_vii(index, a1, a2) {
+    try {
+      Module["dynCall_vii"](index, a1, a2);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_ii(index, a1) {
+    try {
+      return Module["dynCall_ii"](index, a1);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_viddi(index, a1, a2, a3, a4) {
+    try {
+      Module["dynCall_viddi"](index, a1, a2, a3, a4);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_vidd(index, a1, a2, a3) {
+    try {
+      Module["dynCall_vidd"](index, a1, a2, a3);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_iiii(index, a1, a2, a3) {
+    try {
+      return Module["dynCall_iiii"](index, a1, a2, a3);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_diii(index, a1, a2, a3) {
+    try {
+      return Module["dynCall_diii"](index, a1, a2, a3);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_di(index, a1) {
+    try {
+      return Module["dynCall_di"](index, a1);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_iid(index, a1, a2) {
+    try {
+      return Module["dynCall_iid"](index, a1, a2);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_iii(index, a1, a2) {
+    try {
+      return Module["dynCall_iii"](index, a1, a2);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_viiddi(index, a1, a2, a3, a4, a5) {
+    try {
+      Module["dynCall_viiddi"](index, a1, a2, a3, a4, a5);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_viiiiii(index, a1, a2, a3, a4, a5, a6) {
+    try {
+      Module["dynCall_viiiiii"](index, a1, a2, a3, a4, a5, a6);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_dii(index, a1, a2) {
+    try {
+      return Module["dynCall_dii"](index, a1, a2);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_i(index) {
+    try {
+      return Module["dynCall_i"](index);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_iiiiii(index, a1, a2, a3, a4, a5) {
+    try {
+      return Module["dynCall_iiiiii"](index, a1, a2, a3, a4, a5);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_viiid(index, a1, a2, a3, a4) {
+    try {
+      Module["dynCall_viiid"](index, a1, a2, a3, a4);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_viififi(index, a1, a2, a3, a4, a5, a6) {
+    try {
+      Module["dynCall_viififi"](index, a1, a2, a3, a4, a5, a6);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_viii(index, a1, a2, a3) {
+    try {
+      Module["dynCall_viii"](index, a1, a2, a3);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_v(index) {
+    try {
+      Module["dynCall_v"](index);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_viid(index, a1, a2, a3) {
+    try {
+      Module["dynCall_viid"](index, a1, a2, a3);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_idd(index, a1, a2) {
+    try {
+      return Module["dynCall_idd"](index, a1, a2);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }function invoke_viiii(index, a1, a2, a3, a4) {
+    try {
+      Module["dynCall_viiii"](index, a1, a2, a3, a4);
+    } catch (e) {
+      if (typeof e !== "number" && e !== "longjmp") throw e;Module["setThrew"](1, 0);
+    }
+  }Module.asmGlobalArg = { "Math": Math, "Int8Array": Int8Array, "Int16Array": Int16Array, "Int32Array": Int32Array, "Uint8Array": Uint8Array, "Uint16Array": Uint16Array, "Uint32Array": Uint32Array, "Float32Array": Float32Array, "Float64Array": Float64Array, "NaN": NaN, "Infinity": Infinity };Module.asmLibraryArg = { "abort": abort, "assert": assert, "enlargeMemory": enlargeMemory, "getTotalMemory": getTotalMemory, "abortOnCannotGrowMemory": abortOnCannotGrowMemory, "invoke_viiiii": invoke_viiiii, "invoke_vif": invoke_vif, "invoke_vid": invoke_vid, "invoke_fiff": invoke_fiff, "invoke_vi": invoke_vi, "invoke_vii": invoke_vii, "invoke_ii": invoke_ii, "invoke_viddi": invoke_viddi, "invoke_vidd": invoke_vidd, "invoke_iiii": invoke_iiii, "invoke_diii": invoke_diii, "invoke_di": invoke_di, "invoke_iid": invoke_iid, "invoke_iii": invoke_iii, "invoke_viiddi": invoke_viiddi, "invoke_viiiiii": invoke_viiiiii, "invoke_dii": invoke_dii, "invoke_i": invoke_i, "invoke_iiiiii": invoke_iiiiii, "invoke_viiid": invoke_viiid, "invoke_viififi": invoke_viififi, "invoke_viii": invoke_viii, "invoke_v": invoke_v, "invoke_viid": invoke_viid, "invoke_idd": invoke_idd, "invoke_viiii": invoke_viiii, "_emscripten_asm_const_iiiii": _emscripten_asm_const_iiiii, "_emscripten_asm_const_iiidddddd": _emscripten_asm_const_iiidddddd, "_emscripten_asm_const_iiiid": _emscripten_asm_const_iiiid, "__nbind_reference_external": __nbind_reference_external, "_emscripten_asm_const_iiiiiiii": _emscripten_asm_const_iiiiiiii, "_removeAccessorPrefix": _removeAccessorPrefix, "_typeModule": _typeModule, "__nbind_register_pool": __nbind_register_pool, "__decorate": __decorate, "_llvm_stackrestore": _llvm_stackrestore, "___cxa_atexit": ___cxa_atexit, "__extends": __extends, "__nbind_get_value_object": __nbind_get_value_object, "__ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj": __ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj, "_emscripten_set_main_loop_timing": _emscripten_set_main_loop_timing, "__nbind_register_primitive": __nbind_register_primitive, "__nbind_register_type": __nbind_register_type, "_emscripten_memcpy_big": _emscripten_memcpy_big, "__nbind_register_function": __nbind_register_function, "___setErrNo": ___setErrNo, "__nbind_register_class": __nbind_register_class, "__nbind_finish": __nbind_finish, "_abort": _abort, "_nbind_value": _nbind_value, "_llvm_stacksave": _llvm_stacksave, "___syscall54": ___syscall54, "_defineHidden": _defineHidden, "_emscripten_set_main_loop": _emscripten_set_main_loop, "_emscripten_get_now": _emscripten_get_now, "__nbind_register_callback_signature": __nbind_register_callback_signature, "_emscripten_asm_const_iiiiii": _emscripten_asm_const_iiiiii, "__nbind_free_external": __nbind_free_external, "_emscripten_asm_const_iiii": _emscripten_asm_const_iiii, "_emscripten_asm_const_iiididi": _emscripten_asm_const_iiididi, "___syscall6": ___syscall6, "_atexit": _atexit, "___syscall140": ___syscall140, "___syscall146": ___syscall146, "DYNAMICTOP_PTR": DYNAMICTOP_PTR, "tempDoublePtr": tempDoublePtr, "ABORT": ABORT, "STACKTOP": STACKTOP, "STACK_MAX": STACK_MAX, "cttz_i8": cttz_i8, "___dso_handle": ___dso_handle }; // EMSCRIPTEN_START_ASM
+  var asm = function (global, env, buffer) {
+    "use asm";
+    var a = new global.Int8Array(buffer);var b = new global.Int16Array(buffer);var c = new global.Int32Array(buffer);var d = new global.Uint8Array(buffer);var e = new global.Uint16Array(buffer);var f = new global.Uint32Array(buffer);var g = new global.Float32Array(buffer);var h = new global.Float64Array(buffer);var i = env.DYNAMICTOP_PTR | 0;var j = env.tempDoublePtr | 0;var k = env.ABORT | 0;var l = env.STACKTOP | 0;var m = env.STACK_MAX | 0;var n = env.cttz_i8 | 0;var o = env.___dso_handle | 0;var t = global.NaN,
+        u = global.Infinity;var A = 0;var B = global.Math.floor;var C = global.Math.abs;var D = global.Math.sqrt;var E = global.Math.pow;var F = global.Math.cos;var G = global.Math.sin;var H = global.Math.tan;var I = global.Math.acos;var J = global.Math.asin;var K = global.Math.atan;var L = global.Math.atan2;var M = global.Math.exp;var N = global.Math.log;var O = global.Math.ceil;var P = global.Math.imul;var Q = global.Math.min;var R = global.Math.max;var S = global.Math.clz32;var T = global.Math.fround;var U = env.abort;var V = env.assert;var W = env.enlargeMemory;var X = env.getTotalMemory;var Y = env.abortOnCannotGrowMemory;var Z = env.invoke_viiiii;var _ = env.invoke_vif;var $ = env.invoke_vid;var aa = env.invoke_fiff;var ba = env.invoke_vi;var ca = env.invoke_vii;var da = env.invoke_ii;var ea = env.invoke_viddi;var fa = env.invoke_vidd;var ga = env.invoke_iiii;var ha = env.invoke_diii;var ia = env.invoke_di;var ja = env.invoke_iid;var ka = env.invoke_iii;var la = env.invoke_viiddi;var ma = env.invoke_viiiiii;var na = env.invoke_dii;var oa = env.invoke_i;var pa = env.invoke_iiiiii;var qa = env.invoke_viiid;var ra = env.invoke_viififi;var sa = env.invoke_viii;var ta = env.invoke_v;var ua = env.invoke_viid;var va = env.invoke_idd;var wa = env.invoke_viiii;var xa = env._emscripten_asm_const_iiiii;var ya = env._emscripten_asm_const_iiidddddd;var za = env._emscripten_asm_const_iiiid;var Aa = env.__nbind_reference_external;var Ba = env._emscripten_asm_const_iiiiiiii;var Ca = env._removeAccessorPrefix;var Da = env._typeModule;var Ea = env.__nbind_register_pool;var Fa = env.__decorate;var Ga = env._llvm_stackrestore;var Ha = env.___cxa_atexit;var Ia = env.__extends;var Ja = env.__nbind_get_value_object;var Ka = env.__ZN8facebook4yoga14YGNodeToStringEPNSt3__212basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEP6YGNode14YGPrintOptionsj;var La = env._emscripten_set_main_loop_timing;var Ma = env.__nbind_register_primitive;var Na = env.__nbind_register_type;var Oa = env._emscripten_memcpy_big;var Pa = env.__nbind_register_function;var Qa = env.___setErrNo;var Ra = env.__nbind_register_class;var Sa = env.__nbind_finish;var Ta = env._abort;var Ua = env._nbind_value;var Va = env._llvm_stacksave;var Wa = env.___syscall54;var Xa = env._defineHidden;var Ya = env._emscripten_set_main_loop;var Za = env._emscripten_get_now;var _a = env.__nbind_register_callback_signature;var $a = env._emscripten_asm_const_iiiiii;var ab = env.__nbind_free_external;var bb = env._emscripten_asm_const_iiii;var cb = env._emscripten_asm_const_iiididi;var db = env.___syscall6;var eb = env._atexit;var fb = env.___syscall140;var gb = env.___syscall146;var hb = T(0);const ib = T(0);
+    // EMSCRIPTEN_START_FUNCS
+    function Jb(a) {
+      a = a | 0;var b = 0;b = l;l = l + a | 0;l = l + 15 & -16;return b | 0;
+    }function Kb() {
+      return l | 0;
+    }function Lb(a) {
+      a = a | 0;l = a;
+    }function Mb(a, b) {
+      a = a | 0;b = b | 0;l = a;m = b;
+    }function Nb(a, b) {
+      a = a | 0;b = b | 0;    }function Ob(a) {
+      a = a | 0;A = a;
+    }function Pb() {
+      return A | 0;
+    }function Qb() {
+      var b = 0,
+          d = 0;BC(8104, 8, 400) | 0;BC(8504, 408, 540) | 0;b = 9044;d = b + 44 | 0;do {
+        c[b >> 2] = 0;b = b + 4 | 0;
+      } while ((b | 0) < (d | 0));a[9088] = 0;a[9089] = 1;c[2273] = 0;c[2274] = 948;c[2275] = 948;Ha(17, 8104, o | 0) | 0;return;
+    }function Rb(a) {
+      a = a | 0;oc(a + 948 | 0);return;
+    }function Sb(a) {
+      a = T(a);return ((af(a) | 0) & 2147483647) >>> 0 > 2139095040 | 0;
+    }function Tb(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;a: do if (!(c[a + (b << 3) + 4 >> 2] | 0)) {
+        if ((b | 2 | 0) == 3 ? c[a + 60 >> 2] | 0 : 0) {
+          a = a + 56 | 0;break;
+        }switch (b | 0) {case 0:case 2:case 4:case 5:
+            {
+              if (c[a + 52 >> 2] | 0) {
+                a = a + 48 | 0;break a;
+              }break;
+            }default:
+}if (!(c[a + 68 >> 2] | 0)) {
+          a = (b | 1 | 0) == 5 ? 948 : d;break;
+        } else {
+          a = a + 64 | 0;break;
+        }
+      } else a = a + (b << 3) | 0; while (0);return a | 0;
+    }function Ub(b) {
+      b = b | 0;var d = 0;d = oB(1e3) | 0;Vb(b, (d | 0) != 0, 2456);c[2276] = (c[2276] | 0) + 1;BC(d | 0, 8104, 1e3) | 0;if (a[b + 2 >> 0] | 0) {
+        c[d + 4 >> 2] = 2;c[d + 12 >> 2] = 4;
+      }c[d + 976 >> 2] = b;return d | 0;
+    }function Vb(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0;f = l;l = l + 16 | 0;e = f;if (!b) {
+        c[e >> 2] = d;fe(a, 5, 3197, e);
+      }l = f;return;
+    }function Wb() {
+      return Ub(956) | 0;
+    }function Xb(a) {
+      a = a | 0;var b = 0;b = qC(1e3) | 0;Yb(b, a);Vb(c[a + 976 >> 2] | 0, 1, 2456);c[2276] = (c[2276] | 0) + 1;c[b + 944 >> 2] = 0;return b | 0;
+    }function Yb(a, b) {
+      a = a | 0;b = b | 0;var d = 0;BC(a | 0, b | 0, 948) | 0;ie(a + 948 | 0, b + 948 | 0);d = a + 960 | 0;a = b + 960 | 0;b = d + 40 | 0;do {
+        c[d >> 2] = c[a >> 2];d = d + 4 | 0;a = a + 4 | 0;
+      } while ((d | 0) < (b | 0));return;
+    }function Zb(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0,
+          f = 0;b = a + 944 | 0;d = c[b >> 2] | 0;if (d | 0) {
+        _b(d + 948 | 0, a) | 0;c[b >> 2] = 0;
+      }d = $b(a) | 0;if (d | 0) {
+        b = 0;do {
+          c[(ac(a, b) | 0) + 944 >> 2] = 0;b = b + 1 | 0;
+        } while ((b | 0) != (d | 0));
+      }d = a + 948 | 0;e = c[d >> 2] | 0;f = a + 952 | 0;b = c[f >> 2] | 0;if ((b | 0) != (e | 0)) c[f >> 2] = b + (~((b + -4 - e | 0) >>> 2) << 2);bc(d);pB(a);c[2276] = (c[2276] | 0) + -1;return;
+    }function _b(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = c[a >> 2] | 0;i = a + 4 | 0;d = c[i >> 2] | 0;g = d;a: do if ((e | 0) == (d | 0)) {
+        f = e;h = 4;
+      } else {
+        a = e;while (1) {
+          if ((c[a >> 2] | 0) == (b | 0)) {
+            f = a;h = 4;break a;
+          }a = a + 4 | 0;if ((a | 0) == (d | 0)) {
+            a = 0;break;
+          }
+        }
+      } while (0);if ((h | 0) == 4) if ((f | 0) != (d | 0)) {
+        e = f + 4 | 0;a = g - e | 0;b = a >> 2;if (b) {
+          GC(f | 0, e | 0, a | 0) | 0;d = c[i >> 2] | 0;
+        }a = f + (b << 2) | 0;if ((d | 0) == (a | 0)) a = 1;else {
+          c[i >> 2] = d + (~((d + -4 - a | 0) >>> 2) << 2);a = 1;
+        }
+      } else a = 0;return a | 0;
+    }function $b(a) {
+      a = a | 0;return (c[a + 952 >> 2] | 0) - (c[a + 948 >> 2] | 0) >> 2 | 0;
+    }function ac(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = c[a + 948 >> 2] | 0;if ((c[a + 952 >> 2] | 0) - d >> 2 >>> 0 > b >>> 0) a = c[d + (b << 2) >> 2] | 0;else a = 0;return a | 0;
+    }function bc(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0,
+          f = 0;e = l;l = l + 32 | 0;b = e;f = c[a >> 2] | 0;d = (c[a + 4 >> 2] | 0) - f | 0;if (((c[a + 8 >> 2] | 0) - f | 0) >>> 0 > d >>> 0) {
+        f = d >> 2;bf(b, f, f, a + 8 | 0);cf(a, b);df(b);
+      }l = e;return;
+    }function cc(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0;k = $b(a) | 0;do if (k | 0) {
+        if ((c[(ac(a, 0) | 0) + 944 >> 2] | 0) == (a | 0)) {
+          if (!(_b(a + 948 | 0, b) | 0)) break;BC(b + 400 | 0, 8504, 540) | 0;c[b + 944 >> 2] = 0;nc(a);break;
+        }h = c[(c[a + 976 >> 2] | 0) + 12 >> 2] | 0;i = a + 948 | 0;j = (h | 0) == 0;d = 0;g = 0;do {
+          e = c[(c[i >> 2] | 0) + (g << 2) >> 2] | 0;if ((e | 0) == (b | 0)) nc(a);else {
+            f = Xb(e) | 0;c[(c[i >> 2] | 0) + (d << 2) >> 2] = f;c[f + 944 >> 2] = a;if (!j) Ib[h & 15](e, f, a, d);d = d + 1 | 0;
+          }g = g + 1 | 0;
+        } while ((g | 0) != (k | 0));if (d >>> 0 < k >>> 0) {
+          j = a + 948 | 0;i = a + 952 | 0;h = d;d = c[i >> 2] | 0;do {
+            g = (c[j >> 2] | 0) + (h << 2) | 0;e = g + 4 | 0;f = d - e | 0;b = f >> 2;if (!b) f = d;else {
+              GC(g | 0, e | 0, f | 0) | 0;d = c[i >> 2] | 0;f = d;
+            }e = g + (b << 2) | 0;if ((f | 0) != (e | 0)) {
+              d = f + (~((f + -4 - e | 0) >>> 2) << 2) | 0;c[i >> 2] = d;
+            }h = h + 1 | 0;
+          } while ((h | 0) != (k | 0));
+        }
+      } while (0);return;
+    }function dc(b) {
+      b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0;ec(b, ($b(b) | 0) == 0, 2491);ec(b, (c[b + 944 >> 2] | 0) == 0, 2545);d = b + 948 | 0;e = c[d >> 2] | 0;f = b + 952 | 0;g = c[f >> 2] | 0;if ((g | 0) != (e | 0)) c[f >> 2] = g + (~((g + -4 - e | 0) >>> 2) << 2);bc(d);d = b + 976 | 0;e = c[d >> 2] | 0;BC(b | 0, 8104, 1e3) | 0;if (a[e + 2 >> 0] | 0) {
+        c[b + 4 >> 2] = 2;c[b + 12 >> 2] = 4;
+      }c[d >> 2] = e;return;
+    }function ec(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0;f = l;l = l + 16 | 0;e = f;if (!b) {
+        c[e >> 2] = d;Vd(a, 5, 3197, e);
+      }l = f;return;
+    }function fc() {
+      return c[2276] | 0;
+    }function gc() {
+      var a = 0;a = oB(20) | 0;hc((a | 0) != 0, 2592);c[2277] = (c[2277] | 0) + 1;c[a >> 2] = c[239];c[a + 4 >> 2] = c[240];c[a + 8 >> 2] = c[241];c[a + 12 >> 2] = c[242];c[a + 16 >> 2] = c[243];return a | 0;
+    }function hc(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;e = l;l = l + 16 | 0;d = e;if (!a) {
+        c[d >> 2] = b;Vd(0, 5, 3197, d);
+      }l = e;return;
+    }function ic(a) {
+      a = a | 0;pB(a);c[2277] = (c[2277] | 0) + -1;return;
+    }function jc(a, b) {
+      a = a | 0;b = b | 0;var d = 0;if (!b) {
+        d = 0;b = 0;
+      } else {
+        ec(a, ($b(a) | 0) == 0, 2629);d = 1;
+      }c[a + 964 >> 2] = b;c[a + 988 >> 2] = d;return;
+    }function kc(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;g = e + 8 | 0;f = e + 4 | 0;h = e;c[f >> 2] = b;ec(a, (c[b + 944 >> 2] | 0) == 0, 2709);ec(a, (c[a + 964 >> 2] | 0) == 0, 2763);lc(a);b = a + 948 | 0;c[h >> 2] = (c[b >> 2] | 0) + (d << 2);c[g >> 2] = c[h >> 2];mc(b, g, f) | 0;c[(c[f >> 2] | 0) + 944 >> 2] = a;nc(a);l = e;return;
+    }function lc(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;d = $b(a) | 0;if (d | 0 ? (c[(ac(a, 0) | 0) + 944 >> 2] | 0) != (a | 0) : 0) {
+        e = c[(c[a + 976 >> 2] | 0) + 12 >> 2] | 0;f = a + 948 | 0;g = (e | 0) == 0;b = 0;do {
+          h = c[(c[f >> 2] | 0) + (b << 2) >> 2] | 0;i = Xb(h) | 0;c[(c[f >> 2] | 0) + (b << 2) >> 2] = i;c[i + 944 >> 2] = a;if (!g) Ib[e & 15](h, i, a, b);b = b + 1 | 0;
+        } while ((b | 0) != (d | 0));
+      }return;
+    }function mc(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0,
+          p = 0,
+          q = 0,
+          r = 0,
+          s = 0,
+          t = 0;s = l;l = l + 64 | 0;n = s + 52 | 0;i = s + 48 | 0;o = s + 28 | 0;p = s + 24 | 0;q = s + 20 | 0;r = s;e = c[a >> 2] | 0;g = e;b = e + ((c[b >> 2] | 0) - g >> 2 << 2) | 0;e = a + 4 | 0;f = c[e >> 2] | 0;h = a + 8 | 0;do if (f >>> 0 < (c[h >> 2] | 0) >>> 0) {
+        if ((b | 0) == (f | 0)) {
+          c[b >> 2] = c[d >> 2];c[e >> 2] = (c[e >> 2] | 0) + 4;break;
+        }ef(a, b, f, b + 4 | 0);if (b >>> 0 <= d >>> 0) d = (c[e >> 2] | 0) >>> 0 > d >>> 0 ? d + 4 | 0 : d;c[b >> 2] = c[d >> 2];
+      } else {
+        e = (f - g >> 2) + 1 | 0;f = le(a) | 0;if (f >>> 0 < e >>> 0) jC(a);m = c[a >> 2] | 0;k = (c[h >> 2] | 0) - m | 0;g = k >> 1;bf(r, k >> 2 >>> 0 < f >>> 1 >>> 0 ? g >>> 0 < e >>> 0 ? e : g : f, b - m >> 2, a + 8 | 0);m = r + 8 | 0;e = c[m >> 2] | 0;g = r + 12 | 0;k = c[g >> 2] | 0;h = k;j = e;do if ((e | 0) == (k | 0)) {
+          k = r + 4 | 0;e = c[k >> 2] | 0;t = c[r >> 2] | 0;f = t;if (e >>> 0 <= t >>> 0) {
+            e = h - f >> 1;e = (e | 0) == 0 ? 1 : e;bf(o, e, e >>> 2, c[r + 16 >> 2] | 0);c[p >> 2] = c[k >> 2];c[q >> 2] = c[m >> 2];c[i >> 2] = c[p >> 2];c[n >> 2] = c[q >> 2];gf(o, i, n);e = c[r >> 2] | 0;c[r >> 2] = c[o >> 2];c[o >> 2] = e;e = o + 4 | 0;t = c[k >> 2] | 0;c[k >> 2] = c[e >> 2];c[e >> 2] = t;e = o + 8 | 0;t = c[m >> 2] | 0;c[m >> 2] = c[e >> 2];c[e >> 2] = t;e = o + 12 | 0;t = c[g >> 2] | 0;c[g >> 2] = c[e >> 2];c[e >> 2] = t;df(o);e = c[m >> 2] | 0;break;
+          }g = e;h = ((g - f >> 2) + 1 | 0) / -2 | 0;i = e + (h << 2) | 0;f = j - g | 0;g = f >> 2;if (g) {
+            GC(i | 0, e | 0, f | 0) | 0;e = c[k >> 2] | 0;
+          }t = i + (g << 2) | 0;c[m >> 2] = t;c[k >> 2] = e + (h << 2);e = t;
+        } while (0);c[e >> 2] = c[d >> 2];c[m >> 2] = (c[m >> 2] | 0) + 4;b = ff(a, r, b) | 0;df(r);
+      } while (0);l = s;return b | 0;
+    }function nc(b) {
+      b = b | 0;var d = 0;do {
+        d = b + 984 | 0;if (a[d >> 0] | 0) break;a[d >> 0] = 1;g[b + 504 >> 2] = T(t);b = c[b + 944 >> 2] | 0;
+      } while ((b | 0) != 0);return;
+    }function oc(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -4 - e | 0) >>> 2) << 2);sC(d);
+      }return;
+    }function pc(a) {
+      a = a | 0;return c[a + 944 >> 2] | 0;
+    }function qc(a) {
+      a = a | 0;ec(a, (c[a + 964 >> 2] | 0) != 0, 2832);nc(a);return;
+    }function rc(b) {
+      b = b | 0;return (a[b + 984 >> 0] | 0) != 0 | 0;
+    }function sc(a, b) {
+      a = a | 0;b = b | 0;if (BB(a, b, 400) | 0) {
+        BC(a | 0, b | 0, 400) | 0;nc(a);
+      }return;
+    }function tc(a) {
+      a = a | 0;var b = ib;b = T(g[a + 44 >> 2]);a = Sb(b) | 0;return T(a ? T(0.0) : b);
+    }function uc(b) {
+      b = b | 0;var d = ib;d = T(g[b + 48 >> 2]);if (Sb(d) | 0) d = a[(c[b + 976 >> 2] | 0) + 2 >> 0] | 0 ? T(1.0) : T(0.0);return T(d);
+    }function vc(a, b) {
+      a = a | 0;b = b | 0;c[a + 980 >> 2] = b;return;
+    }function wc(a) {
+      a = a | 0;return c[a + 980 >> 2] | 0;
+    }function xc(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = a + 4 | 0;if ((c[d >> 2] | 0) != (b | 0)) {
+        c[d >> 2] = b;nc(a);
+      }return;
+    }function yc(a) {
+      a = a | 0;return c[a + 4 >> 2] | 0;
+    }function zc(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = a + 8 | 0;if ((c[d >> 2] | 0) != (b | 0)) {
+        c[d >> 2] = b;nc(a);
+      }return;
+    }function Ac(a) {
+      a = a | 0;return c[a + 8 >> 2] | 0;
+    }function Bc(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = a + 12 | 0;if ((c[d >> 2] | 0) != (b | 0)) {
+        c[d >> 2] = b;nc(a);
+      }return;
+    }function Cc(a) {
+      a = a | 0;return c[a + 12 >> 2] | 0;
+    }function Dc(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = a + 16 | 0;if ((c[d >> 2] | 0) != (b | 0)) {
+        c[d >> 2] = b;nc(a);
+      }return;
+    }function Ec(a) {
+      a = a | 0;return c[a + 16 >> 2] | 0;
+    }function Fc(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = a + 20 | 0;if ((c[d >> 2] | 0) != (b | 0)) {
+        c[d >> 2] = b;nc(a);
+      }return;
+    }function Gc(a) {
+      a = a | 0;return c[a + 20 >> 2] | 0;
+    }function Hc(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = a + 24 | 0;if ((c[d >> 2] | 0) != (b | 0)) {
+        c[d >> 2] = b;nc(a);
+      }return;
+    }function Ic(a) {
+      a = a | 0;return c[a + 24 >> 2] | 0;
+    }function Jc(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = a + 28 | 0;if ((c[d >> 2] | 0) != (b | 0)) {
+        c[d >> 2] = b;nc(a);
+      }return;
+    }function Kc(a) {
+      a = a | 0;return c[a + 28 >> 2] | 0;
+    }function Lc(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = a + 32 | 0;if ((c[d >> 2] | 0) != (b | 0)) {
+        c[d >> 2] = b;nc(a);
+      }return;
+    }function Mc(a) {
+      a = a | 0;return c[a + 32 >> 2] | 0;
+    }function Nc(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = a + 36 | 0;if ((c[d >> 2] | 0) != (b | 0)) {
+        c[d >> 2] = b;nc(a);
+      }return;
+    }function Oc(a) {
+      a = a | 0;return c[a + 36 >> 2] | 0;
+    }function Pc(a, b) {
+      a = a | 0;b = T(b);var c = 0;c = a + 40 | 0;if (T(g[c >> 2]) != b) {
+        g[c >> 2] = b;nc(a);
+      }return;
+    }function Qc(a, b) {
+      a = a | 0;b = T(b);var c = 0;c = a + 44 | 0;if (T(g[c >> 2]) != b) {
+        g[c >> 2] = b;nc(a);
+      }return;
+    }function Rc(a, b) {
+      a = a | 0;b = T(b);var c = 0;c = a + 48 | 0;if (T(g[c >> 2]) != b) {
+        g[c >> 2] = b;nc(a);
+      }return;
+    }function Sc(a, b) {
+      a = a | 0;b = T(b);var d = 0,
+          e = 0,
+          f = 0,
+          h = 0;h = Sb(b) | 0;d = (h ^ 1) & 1;e = a + 52 | 0;f = a + 56 | 0;if (!(h | T(g[e >> 2]) == b ? (c[f >> 2] | 0) == (d | 0) : 0)) {
+        g[e >> 2] = b;c[f >> 2] = d;nc(a);
+      }return;
+    }function Tc(a, b) {
+      a = a | 0;b = T(b);var d = 0,
+          e = 0;e = a + 52 | 0;d = a + 56 | 0;if (!(!(T(g[e >> 2]) != b) ? (c[d >> 2] | 0) == 2 : 0)) {
+        g[e >> 2] = b;e = Sb(b) | 0;c[d >> 2] = e ? 3 : 2;nc(a);
+      }return;
+    }function Uc(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;e = b + 52 | 0;d = c[e + 4 >> 2] | 0;b = a;c[b >> 2] = c[e >> 2];c[b + 4 >> 2] = d;return;
+    }function Vc(a, b, d) {
+      a = a | 0;b = b | 0;d = T(d);var e = 0,
+          f = 0,
+          h = 0;h = Sb(d) | 0;e = (h ^ 1) & 1;f = a + 132 + (b << 3) | 0;b = a + 132 + (b << 3) + 4 | 0;if (!(h | T(g[f >> 2]) == d ? (c[b >> 2] | 0) == (e | 0) : 0)) {
+        g[f >> 2] = d;c[b >> 2] = e;nc(a);
+      }return;
+    }function Wc(a, b, d) {
+      a = a | 0;b = b | 0;d = T(d);var e = 0,
+          f = 0,
+          h = 0;h = Sb(d) | 0;e = h ? 0 : 2;f = a + 132 + (b << 3) | 0;b = a + 132 + (b << 3) + 4 | 0;if (!(h | T(g[f >> 2]) == d ? (c[b >> 2] | 0) == (e | 0) : 0)) {
+        g[f >> 2] = d;c[b >> 2] = e;nc(a);
+      }return;
+    }function Xc(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = b + 132 + (d << 3) | 0;b = c[e + 4 >> 2] | 0;d = a;c[d >> 2] = c[e >> 2];c[d + 4 >> 2] = b;return;
+    }function Yc(a, b, d) {
+      a = a | 0;b = b | 0;d = T(d);var e = 0,
+          f = 0,
+          h = 0;h = Sb(d) | 0;e = (h ^ 1) & 1;f = a + 60 + (b << 3) | 0;b = a + 60 + (b << 3) + 4 | 0;if (!(h | T(g[f >> 2]) == d ? (c[b >> 2] | 0) == (e | 0) : 0)) {
+        g[f >> 2] = d;c[b >> 2] = e;nc(a);
+      }return;
+    }function Zc(a, b, d) {
+      a = a | 0;b = b | 0;d = T(d);var e = 0,
+          f = 0,
+          h = 0;h = Sb(d) | 0;e = h ? 0 : 2;f = a + 60 + (b << 3) | 0;b = a + 60 + (b << 3) + 4 | 0;if (!(h | T(g[f >> 2]) == d ? (c[b >> 2] | 0) == (e | 0) : 0)) {
+        g[f >> 2] = d;c[b >> 2] = e;nc(a);
+      }return;
+    }function _c(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = b + 60 + (d << 3) | 0;b = c[e + 4 >> 2] | 0;d = a;c[d >> 2] = c[e >> 2];c[d + 4 >> 2] = b;return;
+    }function $c(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = a + 60 + (b << 3) + 4 | 0;if ((c[d >> 2] | 0) != 3) {
+        g[a + 60 + (b << 3) >> 2] = T(t);c[d >> 2] = 3;nc(a);
+      }return;
+    }function ad(a, b, d) {
+      a = a | 0;b = b | 0;d = T(d);var e = 0,
+          f = 0,
+          h = 0;h = Sb(d) | 0;e = (h ^ 1) & 1;f = a + 204 + (b << 3) | 0;b = a + 204 + (b << 3) + 4 | 0;if (!(h | T(g[f >> 2]) == d ? (c[b >> 2] | 0) == (e | 0) : 0)) {
+        g[f >> 2] = d;c[b >> 2] = e;nc(a);
+      }return;
+    }function bd(a, b, d) {
+      a = a | 0;b = b | 0;d = T(d);var e = 0,
+          f = 0,
+          h = 0;h = Sb(d) | 0;e = h ? 0 : 2;f = a + 204 + (b << 3) | 0;b = a + 204 + (b << 3) + 4 | 0;if (!(h | T(g[f >> 2]) == d ? (c[b >> 2] | 0) == (e | 0) : 0)) {
+        g[f >> 2] = d;c[b >> 2] = e;nc(a);
+      }return;
+    }function cd(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = b + 204 + (d << 3) | 0;b = c[e + 4 >> 2] | 0;d = a;c[d >> 2] = c[e >> 2];c[d + 4 >> 2] = b;return;
+    }function dd(a, b, d) {
+      a = a | 0;b = b | 0;d = T(d);var e = 0,
+          f = 0,
+          h = 0;h = Sb(d) | 0;e = (h ^ 1) & 1;f = a + 276 + (b << 3) | 0;b = a + 276 + (b << 3) + 4 | 0;if (!(h | T(g[f >> 2]) == d ? (c[b >> 2] | 0) == (e | 0) : 0)) {
+        g[f >> 2] = d;c[b >> 2] = e;nc(a);
+      }return;
+    }function ed(a, b) {
+      a = a | 0;b = b | 0;return T(g[a + 276 + (b << 3) >> 2]);
+    }function fd(a, b) {
+      a = a | 0;b = T(b);var d = 0,
+          e = 0,
+          f = 0,
+          h = 0;h = Sb(b) | 0;d = (h ^ 1) & 1;e = a + 348 | 0;f = a + 352 | 0;if (!(h | T(g[e >> 2]) == b ? (c[f >> 2] | 0) == (d | 0) : 0)) {
+        g[e >> 2] = b;c[f >> 2] = d;nc(a);
+      }return;
+    }function gd(a, b) {
+      a = a | 0;b = T(b);var d = 0,
+          e = 0;e = a + 348 | 0;d = a + 352 | 0;if (!(!(T(g[e >> 2]) != b) ? (c[d >> 2] | 0) == 2 : 0)) {
+        g[e >> 2] = b;e = Sb(b) | 0;c[d >> 2] = e ? 3 : 2;nc(a);
+      }return;
+    }function hd(a) {
+      a = a | 0;var b = 0;b = a + 352 | 0;if ((c[b >> 2] | 0) != 3) {
+        g[a + 348 >> 2] = T(t);c[b >> 2] = 3;nc(a);
+      }return;
+    }function id(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;e = b + 348 | 0;d = c[e + 4 >> 2] | 0;b = a;c[b >> 2] = c[e >> 2];c[b + 4 >> 2] = d;return;
+    }function jd(a, b) {
+      a = a | 0;b = T(b);var d = 0,
+          e = 0,
+          f = 0,
+          h = 0;h = Sb(b) | 0;d = (h ^ 1) & 1;e = a + 356 | 0;f = a + 360 | 0;if (!(h | T(g[e >> 2]) == b ? (c[f >> 2] | 0) == (d | 0) : 0)) {
+        g[e >> 2] = b;c[f >> 2] = d;nc(a);
+      }return;
+    }function kd(a, b) {
+      a = a | 0;b = T(b);var d = 0,
+          e = 0;e = a + 356 | 0;d = a + 360 | 0;if (!(!(T(g[e >> 2]) != b) ? (c[d >> 2] | 0) == 2 : 0)) {
+        g[e >> 2] = b;e = Sb(b) | 0;c[d >> 2] = e ? 3 : 2;nc(a);
+      }return;
+    }function ld(a) {
+      a = a | 0;var b = 0;b = a + 360 | 0;if ((c[b >> 2] | 0) != 3) {
+        g[a + 356 >> 2] = T(t);c[b >> 2] = 3;nc(a);
+      }return;
+    }function md(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;e = b + 356 | 0;d = c[e + 4 >> 2] | 0;b = a;c[b >> 2] = c[e >> 2];c[b + 4 >> 2] = d;return;
+    }function nd(a, b) {
+      a = a | 0;b = T(b);var d = 0,
+          e = 0,
+          f = 0,
+          h = 0;h = Sb(b) | 0;d = (h ^ 1) & 1;e = a + 364 | 0;f = a + 368 | 0;if (!(h | T(g[e >> 2]) == b ? (c[f >> 2] | 0) == (d | 0) : 0)) {
+        g[e >> 2] = b;c[f >> 2] = d;nc(a);
+      }return;
+    }function od(a, b) {
+      a = a | 0;b = T(b);var d = 0,
+          e = 0,
+          f = 0,
+          h = 0;h = Sb(b) | 0;d = h ? 0 : 2;e = a + 364 | 0;f = a + 368 | 0;if (!(h | T(g[e >> 2]) == b ? (c[f >> 2] | 0) == (d | 0) : 0)) {
+        g[e >> 2] = b;c[f >> 2] = d;nc(a);
+      }return;
+    }function pd(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;e = b + 364 | 0;d = c[e + 4 >> 2] | 0;b = a;c[b >> 2] = c[e >> 2];c[b + 4 >> 2] = d;return;
+    }function qd(a, b) {
+      a = a | 0;b = T(b);var d = 0,
+          e = 0,
+          f = 0,
+          h = 0;h = Sb(b) | 0;d = (h ^ 1) & 1;e = a + 372 | 0;f = a + 376 | 0;if (!(h | T(g[e >> 2]) == b ? (c[f >> 2] | 0) == (d | 0) : 0)) {
+        g[e >> 2] = b;c[f >> 2] = d;nc(a);
+      }return;
+    }function rd(a, b) {
+      a = a | 0;b = T(b);var d = 0,
+          e = 0,
+          f = 0,
+          h = 0;h = Sb(b) | 0;d = h ? 0 : 2;e = a + 372 | 0;f = a + 376 | 0;if (!(h | T(g[e >> 2]) == b ? (c[f >> 2] | 0) == (d | 0) : 0)) {
+        g[e >> 2] = b;c[f >> 2] = d;nc(a);
+      }return;
+    }function sd(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;e = b + 372 | 0;d = c[e + 4 >> 2] | 0;b = a;c[b >> 2] = c[e >> 2];c[b + 4 >> 2] = d;return;
+    }function td(a, b) {
+      a = a | 0;b = T(b);var d = 0,
+          e = 0,
+          f = 0,
+          h = 0;h = Sb(b) | 0;d = (h ^ 1) & 1;e = a + 380 | 0;f = a + 384 | 0;if (!(h | T(g[e >> 2]) == b ? (c[f >> 2] | 0) == (d | 0) : 0)) {
+        g[e >> 2] = b;c[f >> 2] = d;nc(a);
+      }return;
+    }function ud(a, b) {
+      a = a | 0;b = T(b);var d = 0,
+          e = 0,
+          f = 0,
+          h = 0;h = Sb(b) | 0;d = h ? 0 : 2;e = a + 380 | 0;f = a + 384 | 0;if (!(h | T(g[e >> 2]) == b ? (c[f >> 2] | 0) == (d | 0) : 0)) {
+        g[e >> 2] = b;c[f >> 2] = d;nc(a);
+      }return;
+    }function vd(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;e = b + 380 | 0;d = c[e + 4 >> 2] | 0;b = a;c[b >> 2] = c[e >> 2];c[b + 4 >> 2] = d;return;
+    }function wd(a, b) {
+      a = a | 0;b = T(b);var d = 0,
+          e = 0,
+          f = 0,
+          h = 0;h = Sb(b) | 0;d = (h ^ 1) & 1;e = a + 388 | 0;f = a + 392 | 0;if (!(h | T(g[e >> 2]) == b ? (c[f >> 2] | 0) == (d | 0) : 0)) {
+        g[e >> 2] = b;c[f >> 2] = d;nc(a);
+      }return;
+    }function xd(a, b) {
+      a = a | 0;b = T(b);var d = 0,
+          e = 0,
+          f = 0,
+          h = 0;h = Sb(b) | 0;d = h ? 0 : 2;e = a + 388 | 0;f = a + 392 | 0;if (!(h | T(g[e >> 2]) == b ? (c[f >> 2] | 0) == (d | 0) : 0)) {
+        g[e >> 2] = b;c[f >> 2] = d;nc(a);
+      }return;
+    }function yd(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;e = b + 388 | 0;d = c[e + 4 >> 2] | 0;b = a;c[b >> 2] = c[e >> 2];c[b + 4 >> 2] = d;return;
+    }function zd(a, b) {
+      a = a | 0;b = T(b);var c = 0;c = a + 396 | 0;if (T(g[c >> 2]) != b) {
+        g[c >> 2] = b;nc(a);
+      }return;
+    }function Ad(a) {
+      a = a | 0;return T(g[a + 396 >> 2]);
+    }function Bd(a) {
+      a = a | 0;return T(g[a + 400 >> 2]);
+    }function Cd(a) {
+      a = a | 0;return T(g[a + 404 >> 2]);
+    }function Dd(a) {
+      a = a | 0;return T(g[a + 408 >> 2]);
+    }function Ed(a) {
+      a = a | 0;return T(g[a + 412 >> 2]);
+    }function Fd(a) {
+      a = a | 0;return T(g[a + 416 >> 2]);
+    }function Gd(a) {
+      a = a | 0;return T(g[a + 420 >> 2]);
+    }function Hd(a, b) {
+      a = a | 0;b = b | 0;ec(a, (b | 0) < 6, 2918);switch (b | 0) {case 0:
+          {
+            b = (c[a + 496 >> 2] | 0) == 2 ? 5 : 4;break;
+          }case 2:
+          {
+            b = (c[a + 496 >> 2] | 0) == 2 ? 4 : 5;break;
+          }default:
+}return T(g[a + 424 + (b << 2) >> 2]);
+    }function Id(a, b) {
+      a = a | 0;b = b | 0;ec(a, (b | 0) < 6, 2918);switch (b | 0) {case 0:
+          {
+            b = (c[a + 496 >> 2] | 0) == 2 ? 5 : 4;break;
+          }case 2:
+          {
+            b = (c[a + 496 >> 2] | 0) == 2 ? 4 : 5;break;
+          }default:
+}return T(g[a + 448 + (b << 2) >> 2]);
+    }function Jd(a, b) {
+      a = a | 0;b = b | 0;ec(a, (b | 0) < 6, 2918);switch (b | 0) {case 0:
+          {
+            b = (c[a + 496 >> 2] | 0) == 2 ? 5 : 4;break;
+          }case 2:
+          {
+            b = (c[a + 496 >> 2] | 0) == 2 ? 4 : 5;break;
+          }default:
+}return T(g[a + 472 + (b << 2) >> 2]);
+    }function Kd(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = ib;d = c[a + 4 >> 2] | 0;if ((d | 0) == (c[b + 4 >> 2] | 0)) {
+        if (!d) a = 1;else {
+          e = T(g[a >> 2]);a = T(C(T(e - T(g[b >> 2])))) < T(.0000999999974);
+        }
+      } else a = 0;return a | 0;
+    }function Ld(a, b) {
+      a = T(a);b = T(b);var c = 0;if (Sb(a) | 0) c = Sb(b) | 0;else c = T(C(T(a - b))) < T(.0000999999974);return c | 0;
+    }function Md(a, b) {
+      a = a | 0;b = b | 0;Nd(a, b);return;
+    }function Nd(b, d) {
+      b = b | 0;d = d | 0;var e = 0,
+          f = 0;e = l;l = l + 16 | 0;f = e + 4 | 0;c[f >> 2] = 0;c[f + 4 >> 2] = 0;c[f + 8 >> 2] = 0;Ka(f | 0, b | 0, d | 0, 0);Vd(b, 3, (a[f + 11 >> 0] | 0) < 0 ? c[f >> 2] | 0 : f, e);tC(f);l = e;return;
+    }function Od(a, b, c, d) {
+      a = T(a);b = T(b);c = c | 0;d = d | 0;var e = ib;a = T(a * b);e = T(gC(a, T(1.0)));do if (!(Ld(e, T(0.0)) | 0)) {
+        a = T(a - e);if (Ld(e, T(1.0)) | 0) {
+          a = T(a + T(1.0));break;
+        }if (c) {
+          a = T(a + T(1.0));break;
+        }if (!d) {
+          if (e > T(.5)) e = T(1.0);else {
+            d = Ld(e, T(.5)) | 0;e = d ? T(1.0) : T(0.0);
+          }a = T(a + e);
+        }
+      } else a = T(a - e); while (0);return T(a / b);
+    }function Pd(a, b, c, d, e, f, h, i, j, k, l, m, n) {
+      a = a | 0;b = T(b);c = c | 0;d = T(d);e = e | 0;f = T(f);h = h | 0;i = T(i);j = T(j);k = T(k);l = T(l);m = T(m);n = n | 0;var o = 0,
+          p = ib,
+          q = ib,
+          r = ib,
+          s = ib,
+          t = ib,
+          u = ib;if (j < T(0.0) | k < T(0.0)) n = 0;else {
+        if ((n | 0) != 0 ? (p = T(g[n + 4 >> 2]), p != T(0.0)) : 0) {
+          r = T(Od(b, p, 0, 0));s = T(Od(d, p, 0, 0));q = T(Od(f, p, 0, 0));p = T(Od(i, p, 0, 0));
+        } else {
+          q = f;r = b;p = i;s = d;
+        }if ((e | 0) == (a | 0)) o = Ld(q, r) | 0;else o = 0;if ((h | 0) == (c | 0)) n = Ld(p, s) | 0;else n = 0;if ((!o ? (t = T(b - l), !(Qd(a, t, j) | 0)) : 0) ? !(Rd(a, t, e, j) | 0) : 0) o = Sd(a, t, e, f, j) | 0;else o = 1;if ((!n ? (u = T(d - m), !(Qd(c, u, k) | 0)) : 0) ? !(Rd(c, u, h, k) | 0) : 0) n = Sd(c, u, h, i, k) | 0;else n = 1;n = o & n;
+      }return n | 0;
+    }function Qd(a, b, c) {
+      a = a | 0;b = T(b);c = T(c);if ((a | 0) == 1) a = Ld(b, c) | 0;else a = 0;return a | 0;
+    }function Rd(a, b, c, d) {
+      a = a | 0;b = T(b);c = c | 0;d = T(d);if ((a | 0) == 2 & (c | 0) == 0) {
+        if (!(b >= d)) a = Ld(b, d) | 0;else a = 1;
+      } else a = 0;return a | 0;
+    }function Sd(a, b, c, d, e) {
+      a = a | 0;b = T(b);c = c | 0;d = T(d);e = T(e);if ((a | 0) == 2 & (c | 0) == 2 & d > b) {
+        if (!(e <= b)) a = Ld(b, e) | 0;else a = 1;
+      } else a = 0;return a | 0;
+    }function Td(b, d, e, f, i, j, k, m, n, o, p) {
+      b = b | 0;d = T(d);e = T(e);f = f | 0;i = i | 0;j = j | 0;k = T(k);m = T(m);n = n | 0;o = o | 0;p = p | 0;var q = 0,
+          r = 0,
+          s = 0,
+          t = 0,
+          u = ib,
+          v = ib,
+          w = 0,
+          x = 0,
+          y = 0,
+          z = 0,
+          A = 0,
+          B = 0,
+          C = 0,
+          D = 0,
+          E = 0,
+          F = 0,
+          G = 0,
+          H = ib,
+          I = ib,
+          J = ib,
+          K = 0.0,
+          L = 0.0;G = l;l = l + 160 | 0;D = G + 152 | 0;C = G + 120 | 0;B = G + 104 | 0;y = G + 72 | 0;t = G + 56 | 0;A = G + 8 | 0;x = G;z = (c[2279] | 0) + 1 | 0;c[2279] = z;E = b + 984 | 0;if ((a[E >> 0] | 0) != 0 ? (c[b + 512 >> 2] | 0) != (c[2278] | 0) : 0) w = 4;else if ((c[b + 516 >> 2] | 0) == (f | 0)) F = 0;else w = 4;if ((w | 0) == 4) {
+        c[b + 520 >> 2] = 0;c[b + 924 >> 2] = -1;c[b + 928 >> 2] = -1;g[b + 932 >> 2] = T(-1.0);g[b + 936 >> 2] = T(-1.0);F = 1;
+      }a: do if (!(c[b + 964 >> 2] | 0)) {
+        if (n) {
+          q = b + 916 | 0;if (!(Ld(T(g[q >> 2]), d) | 0)) {
+            w = 21;break;
+          }if (!(Ld(T(g[b + 920 >> 2]), e) | 0)) {
+            w = 21;break;
+          }if ((c[b + 924 >> 2] | 0) != (i | 0)) {
+            w = 21;break;
+          }q = (c[b + 928 >> 2] | 0) == (j | 0) ? q : 0;w = 22;break;
+        }s = c[b + 520 >> 2] | 0;if (!s) w = 21;else {
+          r = 0;while (1) {
+            q = b + 524 + (r * 24 | 0) | 0;if (((Ld(T(g[q >> 2]), d) | 0 ? Ld(T(g[b + 524 + (r * 24 | 0) + 4 >> 2]), e) | 0 : 0) ? (c[b + 524 + (r * 24 | 0) + 8 >> 2] | 0) == (i | 0) : 0) ? (c[b + 524 + (r * 24 | 0) + 12 >> 2] | 0) == (j | 0) : 0) {
+              w = 22;break a;
+            }r = r + 1 | 0;if (r >>> 0 >= s >>> 0) {
+              w = 21;break;
+            }
+          }
+        }
+      } else {
+        u = T(Ud(b, 2, k));v = T(Ud(b, 0, k));q = b + 916 | 0;J = T(g[q >> 2]);I = T(g[b + 920 >> 2]);H = T(g[b + 932 >> 2]);if (!(Pd(i, d, j, e, c[b + 924 >> 2] | 0, J, c[b + 928 >> 2] | 0, I, H, T(g[b + 936 >> 2]), u, v, p) | 0)) {
+          s = c[b + 520 >> 2] | 0;if (!s) w = 21;else {
+            r = 0;while (1) {
+              q = b + 524 + (r * 24 | 0) | 0;H = T(g[q >> 2]);I = T(g[b + 524 + (r * 24 | 0) + 4 >> 2]);J = T(g[b + 524 + (r * 24 | 0) + 16 >> 2]);if (Pd(i, d, j, e, c[b + 524 + (r * 24 | 0) + 8 >> 2] | 0, H, c[b + 524 + (r * 24 | 0) + 12 >> 2] | 0, I, J, T(g[b + 524 + (r * 24 | 0) + 20 >> 2]), u, v, p) | 0) {
+                w = 22;break a;
+              }r = r + 1 | 0;if (r >>> 0 >= s >>> 0) {
+                w = 21;break;
+              }
+            }
+          }
+        } else w = 22;
+      } while (0);do if ((w | 0) == 21) {
+        if (!(a[11697] | 0)) {
+          q = 0;w = 31;
+        } else {
+          q = 0;w = 28;
+        }
+      } else if ((w | 0) == 22) {
+        r = (a[11697] | 0) != 0;if (!((q | 0) != 0 & (F ^ 1))) if (r) {
+          w = 28;break;
+        } else {
+          w = 31;break;
+        }t = q + 16 | 0;c[b + 908 >> 2] = c[t >> 2];s = q + 20 | 0;c[b + 912 >> 2] = c[s >> 2];if (!((a[11698] | 0) == 0 | r ^ 1)) {
+          c[x >> 2] = Wd(z) | 0;c[x + 4 >> 2] = z;Vd(b, 4, 2972, x);r = c[b + 972 >> 2] | 0;if (r | 0) nb[r & 127](b);i = Xd(i, n) | 0;j = Xd(j, n) | 0;L = +T(g[t >> 2]);K = +T(g[s >> 2]);c[A >> 2] = i;c[A + 4 >> 2] = j;h[A + 8 >> 3] = +d;h[A + 16 >> 3] = +e;h[A + 24 >> 3] = L;h[A + 32 >> 3] = K;c[A + 40 >> 2] = o;Vd(b, 4, 2989, A);
+        }
+      } while (0);if ((w | 0) == 28) {
+        r = Wd(z) | 0;c[t >> 2] = r;c[t + 4 >> 2] = z;c[t + 8 >> 2] = F ? 3047 : 11699;Vd(b, 4, 3038, t);r = c[b + 972 >> 2] | 0;if (r | 0) nb[r & 127](b);A = Xd(i, n) | 0;w = Xd(j, n) | 0;c[y >> 2] = A;c[y + 4 >> 2] = w;h[y + 8 >> 3] = +d;h[y + 16 >> 3] = +e;c[y + 24 >> 2] = o;Vd(b, 4, 3049, y);w = 31;
+      }if ((w | 0) == 31) {
+        Yd(b, d, e, f, i, j, k, m, n, p);if (a[11697] | 0) {
+          r = c[2279] | 0;A = Wd(r) | 0;c[B >> 2] = A;c[B + 4 >> 2] = r;c[B + 8 >> 2] = F ? 3047 : 11699;Vd(b, 4, 3083, B);r = c[b + 972 >> 2] | 0;if (r | 0) nb[r & 127](b);A = Xd(i, n) | 0;B = Xd(j, n) | 0;K = +T(g[b + 908 >> 2]);L = +T(g[b + 912 >> 2]);c[C >> 2] = A;c[C + 4 >> 2] = B;h[C + 8 >> 3] = K;h[C + 16 >> 3] = L;c[C + 24 >> 2] = o;Vd(b, 4, 3092, C);
+        }c[b + 516 >> 2] = f;if (!q) {
+          r = b + 520 | 0;q = c[r >> 2] | 0;if ((q | 0) == 16) {
+            if (a[11697] | 0) Vd(b, 4, 3124, D);c[r >> 2] = 0;q = 0;
+          }if (n) q = b + 916 | 0;else {
+            c[r >> 2] = q + 1;q = b + 524 + (q * 24 | 0) | 0;
+          }g[q >> 2] = d;g[q + 4 >> 2] = e;c[q + 8 >> 2] = i;c[q + 12 >> 2] = j;c[q + 16 >> 2] = c[b + 908 >> 2];c[q + 20 >> 2] = c[b + 912 >> 2];q = 0;
+        }
+      }if (n) {
+        c[b + 416 >> 2] = c[b + 908 >> 2];c[b + 420 >> 2] = c[b + 912 >> 2];a[b + 985 >> 0] = 1;a[E >> 0] = 0;
+      }c[2279] = (c[2279] | 0) + -1;c[b + 512 >> 2] = c[2278];l = G;return F | (q | 0) == 0 | 0;
+    }function Ud(a, b, c) {
+      a = a | 0;b = b | 0;c = T(c);var d = ib;d = T(me(a, b, c));return T(d + T(ne(a, b, c)));
+    }function Vd(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0;g = l;l = l + 16 | 0;f = g;c[f >> 2] = e;if (!a) e = 0;else e = c[a + 976 >> 2] | 0;ge(e, a, b, d, f);l = g;return;
+    }function Wd(a) {
+      a = a | 0;return (a >>> 0 > 60 ? 3201 : 3201 + (60 - a) | 0) | 0;
+    }function Xd(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0;f = l;l = l + 32 | 0;d = f + 12 | 0;e = f;c[d >> 2] = c[254];c[d + 4 >> 2] = c[255];c[d + 8 >> 2] = c[256];c[e >> 2] = c[257];c[e + 4 >> 2] = c[258];c[e + 8 >> 2] = c[259];if ((a | 0) > 2) a = 11699;else a = c[(b ? e : d) + (a << 2) >> 2] | 0;l = f;return a | 0;
+    }function Yd(b, e, f, h, i, k, m, n, o, p) {
+      b = b | 0;e = T(e);f = T(f);h = h | 0;i = i | 0;k = k | 0;m = T(m);n = T(n);o = o | 0;p = p | 0;var q = 0,
+          r = 0,
+          s = 0,
+          t = 0,
+          u = ib,
+          v = ib,
+          w = ib,
+          x = ib,
+          y = ib,
+          z = ib,
+          A = ib,
+          B = 0,
+          C = 0,
+          D = 0,
+          E = ib,
+          F = ib,
+          G = 0,
+          H = ib,
+          I = 0,
+          J = 0,
+          K = 0,
+          L = 0,
+          M = 0,
+          N = 0,
+          O = 0,
+          P = 0,
+          Q = 0,
+          R = 0,
+          S = 0,
+          U = 0,
+          V = 0,
+          W = 0,
+          X = 0,
+          Y = 0,
+          Z = 0,
+          _ = 0,
+          $ = ib,
+          aa = ib,
+          ba = ib,
+          ca = ib,
+          da = ib,
+          ea = 0,
+          fa = 0,
+          ga = 0,
+          ha = 0,
+          ia = 0,
+          ja = ib,
+          ka = ib,
+          la = ib,
+          ma = ib,
+          na = ib,
+          oa = ib,
+          pa = 0,
+          qa = ib,
+          ra = ib,
+          sa = ib,
+          ta = ib,
+          ua = ib,
+          va = ib,
+          wa = 0,
+          xa = 0,
+          ya = ib,
+          za = ib,
+          Aa = 0,
+          Ba = 0,
+          Ca = 0,
+          Da = 0,
+          Ea = ib,
+          Fa = 0,
+          Ga = 0,
+          Ha = 0,
+          Ia = 0,
+          Ja = 0,
+          Ka = 0,
+          La = 0,
+          Ma = ib,
+          Na = 0,
+          Oa = 0;La = l;l = l + 16 | 0;ea = La + 12 | 0;fa = La + 8 | 0;ga = La + 4 | 0;ha = La;ec(b, (i | 0) == 0 | (Sb(e) | 0) ^ 1, 3326);ec(b, (k | 0) == 0 | (Sb(f) | 0) ^ 1, 3406);Ga = qe(b, h) | 0;c[b + 496 >> 2] = Ga;Ja = re(2, Ga) | 0;Ka = re(0, Ga) | 0;g[b + 440 >> 2] = T(me(b, Ja, m));g[b + 444 >> 2] = T(ne(b, Ja, m));g[b + 428 >> 2] = T(me(b, Ka, m));g[b + 436 >> 2] = T(ne(b, Ka, m));g[b + 464 >> 2] = T(se(b, Ja));g[b + 468 >> 2] = T(te(b, Ja));g[b + 452 >> 2] = T(se(b, Ka));g[b + 460 >> 2] = T(te(b, Ka));g[b + 488 >> 2] = T(ue(b, Ja, m));g[b + 492 >> 2] = T(ve(b, Ja, m));g[b + 476 >> 2] = T(ue(b, Ka, m));g[b + 484 >> 2] = T(ve(b, Ka, m));do if (!(c[b + 964 >> 2] | 0)) {
+        Ha = b + 948 | 0;Ia = (c[b + 952 >> 2] | 0) - (c[Ha >> 2] | 0) >> 2;if (!Ia) {
+          xe(b, e, f, i, k, m, n);break;
+        }if (!o ? ye(b, e, f, i, k, m, n) | 0 : 0) break;lc(b);Y = b + 508 | 0;a[Y >> 0] = 0;Ja = re(c[b + 4 >> 2] | 0, Ga) | 0;Ka = ze(Ja, Ga) | 0;Fa = oe(Ja) | 0;Z = c[b + 8 >> 2] | 0;Ba = b + 28 | 0;_ = (c[Ba >> 2] | 0) != 0;ua = Fa ? m : n;ya = Fa ? n : m;$ = T(Ae(b, Ja, m));aa = T(Be(b, Ja, m));u = T(Ae(b, Ka, m));va = T(Ce(b, Ja, m));za = T(Ce(b, Ka, m));D = Fa ? i : k;Aa = Fa ? k : i;Ea = Fa ? va : za;y = Fa ? za : va;ta = T(Ud(b, 2, m));x = T(Ud(b, 0, m));v = T(T(be(b + 364 | 0, m)) - Ea);w = T(T(be(b + 380 | 0, m)) - Ea);z = T(T(be(b + 372 | 0, n)) - y);A = T(T(be(b + 388 | 0, n)) - y);ba = Fa ? v : z;ca = Fa ? w : A;ta = T(e - ta);e = T(ta - Ea);if (Sb(e) | 0) Ea = e;else Ea = T(cC(T(eC(e, w)), v));ra = T(f - x);e = T(ra - y);if (Sb(e) | 0) sa = e;else sa = T(cC(T(eC(e, A)), z));v = Fa ? Ea : sa;qa = Fa ? sa : Ea;a: do if ((D | 0) == 1) {
+          h = 0;r = 0;while (1) {
+            q = ac(b, r) | 0;if (!h) {
+              if (T(Ee(q)) > T(0.0) ? T(Fe(q)) > T(0.0) : 0) h = q;else h = 0;
+            } else if (De(q) | 0) {
+              t = 0;break a;
+            }r = r + 1 | 0;if (r >>> 0 >= Ia >>> 0) {
+              t = h;break;
+            }
+          }
+        } else t = 0; while (0);B = t + 500 | 0;C = t + 504 | 0;h = 0;q = 0;e = T(0.0);s = 0;do {
+          r = c[(c[Ha >> 2] | 0) + (s << 2) >> 2] | 0;if ((c[r + 36 >> 2] | 0) == 1) {
+            Ge(r);a[r + 985 >> 0] = 1;a[r + 984 >> 0] = 0;
+          } else {
+            $d(r);if (o) ce(r, qe(r, Ga) | 0, v, qa, Ea);do if ((c[r + 24 >> 2] | 0) != 1) {
+              if ((r | 0) == (t | 0)) {
+                c[B >> 2] = c[2278];g[C >> 2] = T(0.0);break;
+              } else {
+                He(b, r, Ea, i, sa, Ea, sa, k, Ga, p);break;
+              }
+            } else {
+              if (q | 0) c[q + 960 >> 2] = r;c[r + 960 >> 2] = 0;q = r;h = (h | 0) == 0 ? r : h;
+            } while (0);oa = T(g[r + 504 >> 2]);e = T(e + T(oa + T(Ud(r, Ja, Ea))));
+          }s = s + 1 | 0;
+        } while ((s | 0) != (Ia | 0));K = e > v;pa = _ & ((D | 0) == 2 & K) ? 1 : D;I = (Aa | 0) == 1;M = I & (o ^ 1);N = (pa | 0) == 1;O = (pa | 0) == 2;P = 976 + (Ja << 2) | 0;Q = (Aa | 2 | 0) == 2;W = I & (_ ^ 1);R = 1040 + (Ka << 2) | 0;S = 1040 + (Ja << 2) | 0;U = 976 + (Ka << 2) | 0;V = (Aa | 0) != 1;K = _ & ((D | 0) != 0 & K);J = b + 976 | 0;I = I ^ 1;e = v;G = 0;L = 0;oa = T(0.0);da = T(0.0);while (1) {
+          b: do if (G >>> 0 < Ia >>> 0) {
+            C = c[Ha >> 2] | 0;s = 0;A = T(0.0);z = T(0.0);w = T(0.0);v = T(0.0);r = 0;q = 0;t = G;while (1) {
+              B = c[C + (t << 2) >> 2] | 0;if ((c[B + 36 >> 2] | 0) != 1 ? (c[B + 940 >> 2] = L, (c[B + 24 >> 2] | 0) != 1) : 0) {
+                x = T(Ud(B, Ja, Ea));X = c[P >> 2] | 0;f = T(be(B + 380 + (X << 3) | 0, ua));y = T(g[B + 504 >> 2]);f = T(eC(f, y));f = T(cC(T(be(B + 364 + (X << 3) | 0, ua)), f));if (_ & (s | 0) != 0 & T(x + T(z + f)) > e) {
+                  k = s;x = A;D = t;break b;
+                }x = T(x + f);f = T(z + x);x = T(A + x);if (De(B) | 0) {
+                  w = T(w + T(Ee(B)));v = T(v - T(y * T(Fe(B))));
+                }if (q | 0) c[q + 960 >> 2] = B;c[B + 960 >> 2] = 0;s = s + 1 | 0;q = B;r = (r | 0) == 0 ? B : r;
+              } else {
+                x = A;f = z;
+              }t = t + 1 | 0;if (t >>> 0 < Ia >>> 0) {
+                A = x;z = f;
+              } else {
+                k = s;D = t;break;
+              }
+            }
+          } else {
+            k = 0;x = T(0.0);w = T(0.0);v = T(0.0);r = 0;D = G;
+          } while (0);X = w > T(0.0) & w < T(1.0);E = X ? T(1.0) : w;X = v > T(0.0) & v < T(1.0);A = X ? T(1.0) : v;do if (!N) {
+            if (!(x < ba & ((Sb(ba) | 0) ^ 1))) {
+              if (!(x > ca & ((Sb(ca) | 0) ^ 1))) {
+                if (!(a[(c[J >> 2] | 0) + 3 >> 0] | 0)) {
+                  if (!(E == T(0.0)) ? !(T(Ee(b)) == T(0.0)) : 0) {
+                    X = 53;break;
+                  }e = x;X = 53;
+                } else X = 51;
+              } else {
+                e = ca;X = 51;
+              }
+            } else {
+              e = ba;X = 51;
+            }
+          } else X = 51; while (0);if ((X | 0) == 51) {
+            X = 0;if (Sb(e) | 0) X = 53;else {
+              F = T(e - x);H = e;
+            }
+          }if ((X | 0) == 53) {
+            X = 0;if (x < T(0.0)) {
+              F = T(-x);H = e;
+            } else {
+              F = T(0.0);H = e;
+            }
+          }if (!M ? (ia = (r | 0) == 0, !ia) : 0) {
+            s = c[P >> 2] | 0;t = F < T(0.0);y = T(F / A);B = F > T(0.0);z = T(F / E);w = T(0.0);x = T(0.0);e = T(0.0);q = r;do {
+              f = T(be(q + 380 + (s << 3) | 0, ua));v = T(be(q + 364 + (s << 3) | 0, ua));v = T(eC(f, T(cC(v, T(g[q + 504 >> 2])))));if (t) {
+                f = T(v * T(Fe(q)));if (f != T(-0.0) ? (Ma = T(v - T(y * f)), ja = T(Ie(q, Ja, Ma, H, Ea)), Ma != ja) : 0) {
+                  w = T(w - T(ja - v));e = T(e + f);
+                }
+              } else if ((B ? (ka = T(Ee(q)), ka != T(0.0)) : 0) ? (Ma = T(v + T(z * ka)), la = T(Ie(q, Ja, Ma, H, Ea)), Ma != la) : 0) {
+                w = T(w - T(la - v));x = T(x - ka);
+              }q = c[q + 960 >> 2] | 0;
+            } while ((q | 0) != 0);e = T(A + e);v = T(F + w);if (!ia) {
+              y = T(E + x);t = c[P >> 2] | 0;B = v < T(0.0);C = e == T(0.0);z = T(v / e);s = v > T(0.0);y = T(v / y);e = T(0.0);do {
+                Ma = T(be(r + 380 + (t << 3) | 0, ua));w = T(be(r + 364 + (t << 3) | 0, ua));w = T(eC(Ma, T(cC(w, T(g[r + 504 >> 2])))));if (B) {
+                  Ma = T(w * T(Fe(r)));v = T(-Ma);if (Ma != T(-0.0)) {
+                    Ma = T(z * v);v = T(Ie(r, Ja, T(w + (C ? v : Ma)), H, Ea));
+                  } else v = w;
+                } else if (s ? (ma = T(Ee(r)), ma != T(0.0)) : 0) v = T(Ie(r, Ja, T(w + T(y * ma)), H, Ea));else v = w;e = T(e - T(v - w));x = T(Ud(r, Ja, Ea));f = T(Ud(r, Ka, Ea));v = T(v + x);g[fa >> 2] = v;c[ha >> 2] = 1;w = T(g[r + 396 >> 2]);c: do if (Sb(w) | 0) {
+                  q = Sb(qa) | 0;do if (!q) {
+                    if (K | (ae(r, Ka, qa) | 0 | I)) break;if ((Je(b, r) | 0) != 4) break;if ((c[(Ke(r, Ka) | 0) + 4 >> 2] | 0) == 3) break;if ((c[(Le(r, Ka) | 0) + 4 >> 2] | 0) == 3) break;g[ea >> 2] = qa;c[ga >> 2] = 1;break c;
+                  } while (0);if (ae(r, Ka, qa) | 0) {
+                    q = c[r + 992 + (c[U >> 2] << 2) >> 2] | 0;Ma = T(f + T(be(q, qa)));g[ea >> 2] = Ma;q = V & (c[q + 4 >> 2] | 0) == 2;c[ga >> 2] = ((Sb(Ma) | 0 | q) ^ 1) & 1;break;
+                  } else {
+                    g[ea >> 2] = qa;c[ga >> 2] = q ? 0 : 2;break;
+                  }
+                } else {
+                  Ma = T(v - x);E = T(Ma / w);Ma = T(w * Ma);c[ga >> 2] = 1;g[ea >> 2] = T(f + (Fa ? E : Ma));
+                } while (0);Me(r, Ja, H, Ea, ha, fa);Me(r, Ka, qa, Ea, ga, ea);do if (!(ae(r, Ka, qa) | 0) ? (Je(b, r) | 0) == 4 : 0) {
+                  if ((c[(Ke(r, Ka) | 0) + 4 >> 2] | 0) == 3) {
+                    q = 0;break;
+                  }q = (c[(Le(r, Ka) | 0) + 4 >> 2] | 0) != 3;
+                } else q = 0; while (0);Ma = T(g[fa >> 2]);E = T(g[ea >> 2]);Na = c[ha >> 2] | 0;Oa = c[ga >> 2] | 0;Td(r, Fa ? Ma : E, Fa ? E : Ma, Ga, Fa ? Na : Oa, Fa ? Oa : Na, Ea, sa, o & (q ^ 1), 3488, p) | 0;a[Y >> 0] = a[Y >> 0] | a[r + 508 >> 0];r = c[r + 960 >> 2] | 0;
+              } while ((r | 0) != 0);
+            } else e = T(0.0);
+          } else e = T(0.0);e = T(F + e);Oa = e < T(0.0) & 1;a[Y >> 0] = Oa | d[Y >> 0];if (O & e > T(0.0)) {
+            q = c[P >> 2] | 0;if ((c[b + 364 + (q << 3) + 4 >> 2] | 0) != 0 ? (na = T(be(b + 364 + (q << 3) | 0, ua)), na >= T(0.0)) : 0) v = T(cC(T(0.0), T(na - T(H - e))));else v = T(0.0);
+          } else v = e;B = G >>> 0 < D >>> 0;if (B) {
+            t = c[Ha >> 2] | 0;s = G;q = 0;do {
+              r = c[t + (s << 2) >> 2] | 0;if (!(c[r + 24 >> 2] | 0)) {
+                q = ((c[(Ke(r, Ja) | 0) + 4 >> 2] | 0) == 3 & 1) + q | 0;q = q + ((c[(Le(r, Ja) | 0) + 4 >> 2] | 0) == 3 & 1) | 0;
+              }s = s + 1 | 0;
+            } while ((s | 0) != (D | 0));if (q) {
+              x = T(0.0);f = T(0.0);
+            } else X = 101;
+          } else X = 101;d: do if ((X | 0) == 101) {
+            X = 0;switch (Z | 0) {case 1:
+                {
+                  q = 0;x = T(v * T(.5));f = T(0.0);break d;
+                }case 2:
+                {
+                  q = 0;x = v;f = T(0.0);break d;
+                }case 3:
+                {
+                  if (k >>> 0 <= 1) {
+                    q = 0;x = T(0.0);f = T(0.0);break d;
+                  }f = T((k + -1 | 0) >>> 0);q = 0;x = T(0.0);f = T(T(cC(v, T(0.0))) / f);break d;
+                }case 5:
+                {
+                  f = T(v / T((k + 1 | 0) >>> 0));q = 0;x = f;break d;
+                }case 4:
+                {
+                  f = T(v / T(k >>> 0));q = 0;x = T(f * T(.5));break d;
+                }default:
+                {
+                  q = 0;x = T(0.0);f = T(0.0);break d;
+                }}
+          } while (0);e = T($ + x);if (B) {
+            w = T(v / T(q | 0));s = c[Ha >> 2] | 0;r = G;v = T(0.0);do {
+              q = c[s + (r << 2) >> 2] | 0;e: do if ((c[q + 36 >> 2] | 0) != 1) {
+                switch (c[q + 24 >> 2] | 0) {case 1:
+                    {
+                      if (Ne(q, Ja) | 0) {
+                        if (!o) break e;Ma = T(Oe(q, Ja, H));Ma = T(Ma + T(se(b, Ja)));Ma = T(Ma + T(me(q, Ja, Ea)));g[q + 400 + (c[S >> 2] << 2) >> 2] = Ma;break e;
+                      }break;
+                    }case 0:
+                    {
+                      Oa = (c[(Ke(q, Ja) | 0) + 4 >> 2] | 0) == 3;Ma = T(w + e);e = Oa ? Ma : e;if (o) {
+                        Oa = q + 400 + (c[S >> 2] << 2) | 0;g[Oa >> 2] = T(e + T(g[Oa >> 2]));
+                      }Oa = (c[(Le(q, Ja) | 0) + 4 >> 2] | 0) == 3;Ma = T(w + e);e = Oa ? Ma : e;if (M) {
+                        Ma = T(f + T(Ud(q, Ja, Ea)));v = qa;e = T(e + T(Ma + T(g[q + 504 >> 2])));break e;
+                      } else {
+                        e = T(e + T(f + T(Pe(q, Ja, Ea))));v = T(cC(v, T(Pe(q, Ka, Ea))));break e;
+                      }
+                    }default:
+}if (o) {
+                  Ma = T(x + T(se(b, Ja)));Oa = q + 400 + (c[S >> 2] << 2) | 0;g[Oa >> 2] = T(Ma + T(g[Oa >> 2]));
+                }
+              } while (0);r = r + 1 | 0;
+            } while ((r | 0) != (D | 0));
+          } else v = T(0.0);f = T(aa + e);if (Q) x = T(T(Ie(b, Ka, T(za + v), ya, m)) - za);else x = qa;w = T(T(Ie(b, Ka, T(za + (W ? qa : v)), ya, m)) - za);if (B & o) {
+            r = G;do {
+              s = c[(c[Ha >> 2] | 0) + (r << 2) >> 2] | 0;do if ((c[s + 36 >> 2] | 0) != 1) {
+                if ((c[s + 24 >> 2] | 0) == 1) {
+                  if (Ne(s, Ka) | 0) {
+                    Ma = T(Oe(s, Ka, qa));Ma = T(Ma + T(se(b, Ka)));Ma = T(Ma + T(me(s, Ka, Ea)));q = c[R >> 2] | 0;g[s + 400 + (q << 2) >> 2] = Ma;if (!(Sb(Ma) | 0)) break;
+                  } else q = c[R >> 2] | 0;Ma = T(se(b, Ka));g[s + 400 + (q << 2) >> 2] = T(Ma + T(me(s, Ka, Ea)));break;
+                }q = Je(b, s) | 0;do if ((q | 0) == 4) {
+                  if ((c[(Ke(s, Ka) | 0) + 4 >> 2] | 0) == 3) {
+                    X = 139;break;
+                  }if ((c[(Le(s, Ka) | 0) + 4 >> 2] | 0) == 3) {
+                    X = 139;break;
+                  }if (ae(s, Ka, qa) | 0) {
+                    e = u;break;
+                  }Na = c[s + 908 + (c[P >> 2] << 2) >> 2] | 0;c[ea >> 2] = Na;e = T(g[s + 396 >> 2]);Oa = Sb(e) | 0;v = (c[j >> 2] = Na, T(g[j >> 2]));if (Oa) e = w;else {
+                    F = T(Ud(s, Ka, Ea));Ma = T(v / e);e = T(e * v);e = T(F + (Fa ? Ma : e));
+                  }g[fa >> 2] = e;g[ea >> 2] = T(T(Ud(s, Ja, Ea)) + v);c[ga >> 2] = 1;c[ha >> 2] = 1;Me(s, Ja, H, Ea, ga, ea);Me(s, Ka, qa, Ea, ha, fa);e = T(g[ea >> 2]);F = T(g[fa >> 2]);Ma = Fa ? e : F;e = Fa ? F : e;Oa = ((Sb(Ma) | 0) ^ 1) & 1;Td(s, Ma, e, Ga, Oa, ((Sb(e) | 0) ^ 1) & 1, Ea, sa, 1, 3493, p) | 0;e = u;
+                } else X = 139; while (0);f: do if ((X | 0) == 139) {
+                  X = 0;e = T(x - T(Pe(s, Ka, Ea)));do if ((c[(Ke(s, Ka) | 0) + 4 >> 2] | 0) == 3) {
+                    if ((c[(Le(s, Ka) | 0) + 4 >> 2] | 0) != 3) break;e = T(u + T(cC(T(0.0), T(e * T(.5)))));break f;
+                  } while (0);if ((c[(Le(s, Ka) | 0) + 4 >> 2] | 0) == 3) {
+                    e = u;break;
+                  }if ((c[(Ke(s, Ka) | 0) + 4 >> 2] | 0) == 3) {
+                    e = T(u + T(cC(T(0.0), e)));break;
+                  }switch (q | 0) {case 1:
+                      {
+                        e = u;break f;
+                      }case 2:
+                      {
+                        e = T(u + T(e * T(.5)));break f;
+                      }default:
+                      {
+                        e = T(u + e);break f;
+                      }}
+                } while (0);Ma = T(oa + e);Oa = s + 400 + (c[R >> 2] << 2) | 0;g[Oa >> 2] = T(Ma + T(g[Oa >> 2]));
+              } while (0);r = r + 1 | 0;
+            } while ((r | 0) != (D | 0));
+          }oa = T(oa + w);da = T(cC(da, f));k = L + 1 | 0;if (D >>> 0 >= Ia >>> 0) break;else {
+            e = H;G = D;L = k;
+          }
+        }do if (o) {
+          q = k >>> 0 > 1;if (!q ? !(Qe(b) | 0) : 0) break;if (!(Sb(qa) | 0)) {
+            e = T(qa - oa);g: do switch (c[b + 12 >> 2] | 0) {case 3:
+                {
+                  u = T(u + e);z = T(0.0);break;
+                }case 2:
+                {
+                  u = T(u + T(e * T(.5)));z = T(0.0);break;
+                }case 4:
+                {
+                  if (qa > oa) z = T(e / T(k >>> 0));else z = T(0.0);break;
+                }case 7:
+                if (qa > oa) {
+                  u = T(u + T(e / T(k << 1 >>> 0)));z = T(e / T(k >>> 0));z = q ? z : T(0.0);break g;
+                } else {
+                  u = T(u + T(e * T(.5)));z = T(0.0);break g;
+                }case 6:
+                {
+                  z = T(e / T(L >>> 0));z = qa > oa & q ? z : T(0.0);break;
+                }default:
+                z = T(0.0);} while (0);if (k | 0) {
+              B = 1040 + (Ka << 2) | 0;C = 976 + (Ka << 2) | 0;t = 0;r = 0;while (1) {
+                h: do if (r >>> 0 < Ia >>> 0) {
+                  v = T(0.0);w = T(0.0);e = T(0.0);s = r;while (1) {
+                    q = c[(c[Ha >> 2] | 0) + (s << 2) >> 2] | 0;do if ((c[q + 36 >> 2] | 0) != 1 ? (c[q + 24 >> 2] | 0) == 0 : 0) {
+                      if ((c[q + 940 >> 2] | 0) != (t | 0)) break h;if (Re(q, Ka) | 0) {
+                        Ma = T(g[q + 908 + (c[C >> 2] << 2) >> 2]);e = T(cC(e, T(Ma + T(Ud(q, Ka, Ea)))));
+                      }if ((Je(b, q) | 0) != 5) break;na = T(Se(q));na = T(na + T(me(q, 0, Ea)));Ma = T(g[q + 912 >> 2]);Ma = T(T(Ma + T(Ud(q, 0, Ea))) - na);na = T(cC(w, na));Ma = T(cC(v, Ma));v = Ma;w = na;e = T(cC(e, T(na + Ma)));
+                    } while (0);q = s + 1 | 0;if (q >>> 0 < Ia >>> 0) s = q;else {
+                      s = q;break;
+                    }
+                  }
+                } else {
+                  w = T(0.0);e = T(0.0);s = r;
+                } while (0);y = T(z + e);f = u;u = T(u + y);if (r >>> 0 < s >>> 0) {
+                  x = T(f + w);q = r;do {
+                    r = c[(c[Ha >> 2] | 0) + (q << 2) >> 2] | 0;i: do if ((c[r + 36 >> 2] | 0) != 1 ? (c[r + 24 >> 2] | 0) == 0 : 0) switch (Je(b, r) | 0) {case 1:
+                        {
+                          Ma = T(f + T(me(r, Ka, Ea)));g[r + 400 + (c[B >> 2] << 2) >> 2] = Ma;break i;
+                        }case 3:
+                        {
+                          Ma = T(T(u - T(ne(r, Ka, Ea))) - T(g[r + 908 + (c[C >> 2] << 2) >> 2]));g[r + 400 + (c[B >> 2] << 2) >> 2] = Ma;break i;
+                        }case 2:
+                        {
+                          Ma = T(f + T(T(y - T(g[r + 908 + (c[C >> 2] << 2) >> 2])) * T(.5)));g[r + 400 + (c[B >> 2] << 2) >> 2] = Ma;break i;
+                        }case 4:
+                        {
+                          Ma = T(f + T(me(r, Ka, Ea)));g[r + 400 + (c[B >> 2] << 2) >> 2] = Ma;if (ae(r, Ka, qa) | 0) break i;if (Fa) {
+                            v = T(g[r + 908 >> 2]);e = T(v + T(Ud(r, Ja, Ea)));w = y;
+                          } else {
+                            w = T(g[r + 912 >> 2]);w = T(w + T(Ud(r, Ka, Ea)));e = y;v = T(g[r + 908 >> 2]);
+                          }if (Ld(e, v) | 0 ? Ld(w, T(g[r + 912 >> 2])) | 0 : 0) break i;Td(r, e, w, Ga, 1, 1, Ea, sa, 1, 3501, p) | 0;break i;
+                        }case 5:
+                        {
+                          g[r + 404 >> 2] = T(T(x - T(Se(r))) + T(Oe(r, 0, qa)));break i;
+                        }default:
+                        break i;} while (0);q = q + 1 | 0;
+                  } while ((q | 0) != (s | 0));
+                }t = t + 1 | 0;if ((t | 0) == (k | 0)) break;else r = s;
+              }
+            }
+          }
+        } while (0);g[b + 908 >> 2] = T(Ie(b, 2, ta, m, m));g[b + 912 >> 2] = T(Ie(b, 0, ra, n, m));if ((pa | 0) != 0 ? (wa = c[b + 32 >> 2] | 0, xa = (pa | 0) == 2, !(xa & (wa | 0) != 2)) : 0) {
+          if (xa & (wa | 0) == 2) {
+            e = T(va + H);e = T(cC(T(eC(e, T(Te(b, Ja, da, ua)))), va));X = 198;
+          }
+        } else {
+          e = T(Ie(b, Ja, da, ua, m));X = 198;
+        }if ((X | 0) == 198) g[b + 908 + (c[976 + (Ja << 2) >> 2] << 2) >> 2] = e;if ((Aa | 0) != 0 ? (Ca = c[b + 32 >> 2] | 0, Da = (Aa | 0) == 2, !(Da & (Ca | 0) != 2)) : 0) {
+          if (Da & (Ca | 0) == 2) {
+            e = T(za + qa);e = T(cC(T(eC(e, T(Te(b, Ka, T(za + oa), ya)))), za));X = 204;
+          }
+        } else {
+          e = T(Ie(b, Ka, T(za + oa), ya, m));X = 204;
+        }if ((X | 0) == 204) g[b + 908 + (c[976 + (Ka << 2) >> 2] << 2) >> 2] = e;if (o) {
+          if ((c[Ba >> 2] | 0) == 2) {
+            r = 976 + (Ka << 2) | 0;s = 1040 + (Ka << 2) | 0;q = 0;do {
+              t = ac(b, q) | 0;if (!(c[t + 24 >> 2] | 0)) {
+                Na = c[r >> 2] | 0;Ma = T(g[b + 908 + (Na << 2) >> 2]);Oa = t + 400 + (c[s >> 2] << 2) | 0;Ma = T(Ma - T(g[Oa >> 2]));g[Oa >> 2] = T(Ma - T(g[t + 908 + (Na << 2) >> 2]));
+              }q = q + 1 | 0;
+            } while ((q | 0) != (Ia | 0));
+          }if (h | 0) {
+            q = Fa ? pa : i;do {
+              Ue(b, h, Ea, q, sa, Ga, p);h = c[h + 960 >> 2] | 0;
+            } while ((h | 0) != 0);
+          }q = (Ja | 2 | 0) == 3;r = (Ka | 2 | 0) == 3;if (q | r) {
+            h = 0;do {
+              s = c[(c[Ha >> 2] | 0) + (h << 2) >> 2] | 0;if ((c[s + 36 >> 2] | 0) != 1) {
+                if (q) Ve(b, s, Ja);if (r) Ve(b, s, Ka);
+              }h = h + 1 | 0;
+            } while ((h | 0) != (Ia | 0));
+          }
+        }
+      } else we(b, e, f, i, k, m, n); while (0);l = La;return;
+    }function Zd(a, b) {
+      a = a | 0;b = T(b);var c = 0;Vb(a, b >= T(0.0), 3147);c = b == T(0.0);g[a + 4 >> 2] = c ? T(0.0) : b;return;
+    }function _d(b, d, e, f) {
+      b = b | 0;d = T(d);e = T(e);f = f | 0;var h = ib,
+          i = ib,
+          j = 0,
+          k = 0,
+          l = 0;c[2278] = (c[2278] | 0) + 1;$d(b);if (!(ae(b, 2, d) | 0)) {
+        h = T(be(b + 380 | 0, d));if (!(h >= T(0.0))) {
+          l = ((Sb(d) | 0) ^ 1) & 1;h = d;
+        } else l = 2;
+      } else {
+        h = T(be(c[b + 992 >> 2] | 0, d));l = 1;h = T(h + T(Ud(b, 2, d)));
+      }if (!(ae(b, 0, e) | 0)) {
+        i = T(be(b + 388 | 0, e));if (!(i >= T(0.0))) {
+          k = ((Sb(e) | 0) ^ 1) & 1;i = e;
+        } else k = 2;
+      } else {
+        i = T(be(c[b + 996 >> 2] | 0, e));k = 1;i = T(i + T(Ud(b, 0, d)));
+      }j = b + 976 | 0;if (Td(b, h, i, f, l, k, d, e, 1, 3189, c[j >> 2] | 0) | 0 ? (ce(b, c[b + 496 >> 2] | 0, d, e, d), de(b, T(g[(c[j >> 2] | 0) + 4 >> 2]), T(0.0), T(0.0)), a[11696] | 0) : 0) Md(b, 7);return;
+    }function $d(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;i = l;l = l + 32 | 0;h = i + 24 | 0;g = i + 16 | 0;e = i + 8 | 0;f = i;d = 0;do {
+        b = a + 380 + (d << 3) | 0;if (!((c[a + 380 + (d << 3) + 4 >> 2] | 0) != 0 ? (j = b, k = c[j + 4 >> 2] | 0, m = e, c[m >> 2] = c[j >> 2], c[m + 4 >> 2] = k, m = a + 364 + (d << 3) | 0, k = c[m + 4 >> 2] | 0, j = f, c[j >> 2] = c[m >> 2], c[j + 4 >> 2] = k, c[g >> 2] = c[e >> 2], c[g + 4 >> 2] = c[e + 4 >> 2], c[h >> 2] = c[f >> 2], c[h + 4 >> 2] = c[f + 4 >> 2], Kd(g, h) | 0) : 0)) b = a + 348 + (d << 3) | 0;c[a + 992 + (d << 2) >> 2] = b;d = d + 1 | 0;
+      } while ((d | 0) != 2);l = i;return;
+    }function ae(a, b, d) {
+      a = a | 0;b = b | 0;d = T(d);var e = 0;a = c[a + 992 + (c[976 + (b << 2) >> 2] << 2) >> 2] | 0;switch (c[a + 4 >> 2] | 0) {case 0:case 3:
+          {
+            a = 0;break;
+          }case 1:
+          {
+            if (T(g[a >> 2]) < T(0.0)) a = 0;else e = 5;break;
+          }case 2:
+          {
+            if (T(g[a >> 2]) < T(0.0)) a = 0;else a = (Sb(d) | 0) ^ 1;break;
+          }default:
+          e = 5;}if ((e | 0) == 5) a = 1;return a | 0;
+    }function be(a, b) {
+      a = a | 0;b = T(b);switch (c[a + 4 >> 2] | 0) {case 2:
+          {
+            b = T(T(T(g[a >> 2]) * b) / T(100.0));break;
+          }case 1:
+          {
+            b = T(g[a >> 2]);break;
+          }default:
+          b = T(t);}return T(b);
+    }function ce(a, b, d, e, f) {
+      a = a | 0;b = b | 0;d = T(d);e = T(e);f = T(f);var h = 0,
+          i = ib;b = c[a + 944 >> 2] | 0 ? b : 1;h = re(c[a + 4 >> 2] | 0, b) | 0;b = ze(h, b) | 0;d = T($e(a, h, d));e = T($e(a, b, e));i = T(d + T(me(a, h, f)));g[a + 400 + (c[1040 + (h << 2) >> 2] << 2) >> 2] = i;d = T(d + T(ne(a, h, f)));g[a + 400 + (c[1e3 + (h << 2) >> 2] << 2) >> 2] = d;d = T(e + T(me(a, b, f)));g[a + 400 + (c[1040 + (b << 2) >> 2] << 2) >> 2] = d;f = T(e + T(ne(a, b, f)));g[a + 400 + (c[1e3 + (b << 2) >> 2] << 2) >> 2] = f;return;
+    }function de(a, b, d, e) {
+      a = a | 0;b = T(b);d = T(d);e = T(e);var f = 0,
+          h = 0,
+          i = ib,
+          j = ib,
+          k = 0,
+          l = 0,
+          m = ib,
+          n = 0,
+          o = ib,
+          p = ib,
+          q = ib,
+          r = ib;if (!(b == T(0.0))) {
+        f = a + 400 | 0;r = T(g[f >> 2]);h = a + 404 | 0;q = T(g[h >> 2]);n = a + 416 | 0;p = T(g[n >> 2]);l = a + 420 | 0;i = T(g[l >> 2]);o = T(r + d);m = T(q + e);e = T(o + p);j = T(m + i);k = (c[a + 988 >> 2] | 0) == 1;g[f >> 2] = T(Od(r, b, 0, k));g[h >> 2] = T(Od(q, b, 0, k));d = T(gC(T(p * b), T(1.0)));if (Ld(d, T(0.0)) | 0) h = 0;else h = (Ld(d, T(1.0)) | 0) ^ 1;d = T(gC(T(i * b), T(1.0)));if (Ld(d, T(0.0)) | 0) f = 0;else f = (Ld(d, T(1.0)) | 0) ^ 1;r = T(Od(e, b, k & h, k & (h ^ 1)));g[n >> 2] = T(r - T(Od(o, b, 0, k)));r = T(Od(j, b, k & f, k & (f ^ 1)));g[l >> 2] = T(r - T(Od(m, b, 0, k)));h = (c[a + 952 >> 2] | 0) - (c[a + 948 >> 2] | 0) >> 2;if (h | 0) {
+          f = 0;do {
+            de(ac(a, f) | 0, b, o, m);f = f + 1 | 0;
+          } while ((f | 0) != (h | 0));
+        }
+      }return;
+    }function ee(a, b, d, e, f) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;f = f | 0;switch (d | 0) {case 5:case 0:
+          {
+            a = CB(c[489] | 0, e, f) | 0;break;
+          }default:
+          a = iC(e, f) | 0;}return a | 0;
+    }function fe(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0;f = l;l = l + 16 | 0;g = f;c[g >> 2] = e;ge(a, 0, b, d, g);l = f;return;
+    }function ge(a, b, d, e, f) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;f = f | 0;a = a | 0 ? a : 956;Bb[c[a + 8 >> 2] & 1](a, b, d, e, f) | 0;if ((d | 0) == 5) Ta();else return;
+    }function he(b, c, d) {
+      b = b | 0;c = c | 0;d = d | 0;a[b + c >> 0] = d & 1;return;
+    }function ie(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;c[a >> 2] = 0;c[a + 4 >> 2] = 0;c[a + 8 >> 2] = 0;d = b + 4 | 0;e = (c[d >> 2] | 0) - (c[b >> 2] | 0) >> 2;if (e | 0) {
+        je(a, e);ke(a, c[b >> 2] | 0, c[d >> 2] | 0, e);
+      }return;
+    }function je(a, b) {
+      a = a | 0;b = b | 0;var d = 0;if ((le(a) | 0) >>> 0 < b >>> 0) jC(a);if (b >>> 0 > 1073741823) Ta();else {
+        d = qC(b << 2) | 0;c[a + 4 >> 2] = d;c[a >> 2] = d;c[a + 8 >> 2] = d + (b << 2);return;
+      }
+    }function ke(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;e = a + 4 | 0;a = d - b | 0;if ((a | 0) > 0) {
+        BC(c[e >> 2] | 0, b | 0, a | 0) | 0;c[e >> 2] = (c[e >> 2] | 0) + (a >>> 2 << 2);
+      }return;
+    }function le(a) {
+      a = a | 0;return 1073741823;
+    }function me(a, b, d) {
+      a = a | 0;b = b | 0;d = T(d);if (oe(b) | 0 ? (c[a + 96 >> 2] | 0) != 0 : 0) a = a + 92 | 0;else a = Tb(a + 60 | 0, c[1040 + (b << 2) >> 2] | 0, 992) | 0;return T(pe(a, d));
+    }function ne(a, b, d) {
+      a = a | 0;b = b | 0;d = T(d);if (oe(b) | 0 ? (c[a + 104 >> 2] | 0) != 0 : 0) a = a + 100 | 0;else a = Tb(a + 60 | 0, c[1e3 + (b << 2) >> 2] | 0, 992) | 0;return T(pe(a, d));
+    }function oe(a) {
+      a = a | 0;return (a | 1 | 0) == 3 | 0;
+    }function pe(a, b) {
+      a = a | 0;b = T(b);if ((c[a + 4 >> 2] | 0) == 3) b = T(0.0);else b = T(be(a, b));return T(b);
+    }function qe(a, b) {
+      a = a | 0;b = b | 0;a = c[a >> 2] | 0;return ((a | 0) == 0 ? (b | 0) > 1 ? b : 1 : a) | 0;
+    }function re(a, b) {
+      a = a | 0;b = b | 0;var c = 0;a: do if ((b | 0) == 2) {
+        switch (a | 0) {case 2:
+            {
+              a = 3;break a;
+            }case 3:
+            break;default:
+            {
+              c = 4;break a;
+            }}a = 2;
+      } else c = 4; while (0);return a | 0;
+    }function se(a, b) {
+      a = a | 0;b = b | 0;var d = ib;if (!((oe(b) | 0 ? (c[a + 312 >> 2] | 0) != 0 : 0) ? (d = T(g[a + 308 >> 2]), d >= T(0.0)) : 0)) d = T(cC(T(g[(Tb(a + 276 | 0, c[1040 + (b << 2) >> 2] | 0, 992) | 0) >> 2]), T(0.0)));return T(d);
+    }function te(a, b) {
+      a = a | 0;b = b | 0;var d = ib;if (!((oe(b) | 0 ? (c[a + 320 >> 2] | 0) != 0 : 0) ? (d = T(g[a + 316 >> 2]), d >= T(0.0)) : 0)) d = T(cC(T(g[(Tb(a + 276 | 0, c[1e3 + (b << 2) >> 2] | 0, 992) | 0) >> 2]), T(0.0)));return T(d);
+    }function ue(a, b, d) {
+      a = a | 0;b = b | 0;d = T(d);var e = ib;if (!((oe(b) | 0 ? (c[a + 240 >> 2] | 0) != 0 : 0) ? (e = T(be(a + 236 | 0, d)), e >= T(0.0)) : 0)) e = T(cC(T(be(Tb(a + 204 | 0, c[1040 + (b << 2) >> 2] | 0, 992) | 0, d)), T(0.0)));return T(e);
+    }function ve(a, b, d) {
+      a = a | 0;b = b | 0;d = T(d);var e = ib;if (!((oe(b) | 0 ? (c[a + 248 >> 2] | 0) != 0 : 0) ? (e = T(be(a + 244 | 0, d)), e >= T(0.0)) : 0)) e = T(cC(T(be(Tb(a + 204 | 0, c[1e3 + (b << 2) >> 2] | 0, 992) | 0, d)), T(0.0)));return T(e);
+    }function we(a, b, d, e, f, h, i) {
+      a = a | 0;b = T(b);d = T(d);e = e | 0;f = f | 0;h = T(h);i = T(i);var j = ib,
+          k = ib,
+          m = ib,
+          n = ib,
+          o = ib,
+          p = ib,
+          q = 0,
+          r = 0,
+          s = 0;s = l;l = l + 16 | 0;q = s;r = a + 964 | 0;ec(a, (c[r >> 2] | 0) != 0, 3519);j = T(Ce(a, 2, b));k = T(Ce(a, 0, b));m = T(Ud(a, 2, b));n = T(Ud(a, 0, b));if (Sb(b) | 0) o = b;else o = T(cC(T(0.0), T(T(b - m) - j)));if (Sb(d) | 0) p = d;else p = T(cC(T(0.0), T(T(d - n) - k)));if ((e | 0) == 1 & (f | 0) == 1) {
+        g[a + 908 >> 2] = T(Ie(a, 2, T(b - m), h, h));b = T(Ie(a, 0, T(d - n), i, h));
+      } else {
+        Db[c[r >> 2] & 1](q, a, o, e, p, f);o = T(j + T(g[q >> 2]));p = T(b - m);g[a + 908 >> 2] = T(Ie(a, 2, (e | 2 | 0) == 2 ? o : p, h, h));p = T(k + T(g[q + 4 >> 2]));b = T(d - n);b = T(Ie(a, 0, (f | 2 | 0) == 2 ? p : b, i, h));
+      }g[a + 912 >> 2] = b;l = s;return;
+    }function xe(a, b, c, d, e, f, h) {
+      a = a | 0;b = T(b);c = T(c);d = d | 0;e = e | 0;f = T(f);h = T(h);var i = ib,
+          j = ib,
+          k = ib,
+          l = ib;k = T(Ce(a, 2, f));i = T(Ce(a, 0, f));l = T(Ud(a, 2, f));j = T(Ud(a, 0, f));b = T(b - l);g[a + 908 >> 2] = T(Ie(a, 2, (d | 2 | 0) == 2 ? k : b, f, f));c = T(c - j);g[a + 912 >> 2] = T(Ie(a, 0, (e | 2 | 0) == 2 ? i : c, h, f));return;
+    }function ye(a, b, c, d, e, f, h) {
+      a = a | 0;b = T(b);c = T(c);d = d | 0;e = e | 0;f = T(f);h = T(h);var i = 0,
+          j = ib,
+          k = ib;i = (d | 0) == 2;if ((!(b <= T(0.0) & i) ? !(c <= T(0.0) & (e | 0) == 2) : 0) ? !((d | 0) == 1 & (e | 0) == 1) : 0) a = 0;else {
+        j = T(Ud(a, 0, f));k = T(Ud(a, 2, f));i = b < T(0.0) & i | (Sb(b) | 0);b = T(b - k);g[a + 908 >> 2] = T(Ie(a, 2, i ? T(0.0) : b, f, f));b = T(c - j);i = c < T(0.0) & (e | 0) == 2 | (Sb(c) | 0);g[a + 912 >> 2] = T(Ie(a, 0, i ? T(0.0) : b, h, f));a = 1;
+      }return a | 0;
+    }function ze(a, b) {
+      a = a | 0;b = b | 0;if (We(a) | 0) a = re(2, b) | 0;else a = 0;return a | 0;
+    }function Ae(a, b, c) {
+      a = a | 0;b = b | 0;c = T(c);c = T(ue(a, b, c));return T(c + T(se(a, b)));
+    }function Be(a, b, c) {
+      a = a | 0;b = b | 0;c = T(c);c = T(ve(a, b, c));return T(c + T(te(a, b)));
+    }function Ce(a, b, c) {
+      a = a | 0;b = b | 0;c = T(c);var d = ib;d = T(Ae(a, b, c));return T(d + T(Be(a, b, c)));
+    }function De(a) {
+      a = a | 0;if (!(c[a + 24 >> 2] | 0)) {
+        if (T(Ee(a)) != T(0.0)) a = 1;else a = T(Fe(a)) != T(0.0);
+      } else a = 0;return a | 0;
+    }function Ee(a) {
+      a = a | 0;var b = ib;if (c[a + 944 >> 2] | 0) {
+        b = T(g[a + 44 >> 2]);if (Sb(b) | 0) {
+          b = T(g[a + 40 >> 2]);a = b > T(0.0) & ((Sb(b) | 0) ^ 1);return T(a ? b : T(0.0));
+        }
+      } else b = T(0.0);return T(b);
+    }function Fe(b) {
+      b = b | 0;var d = ib,
+          e = 0,
+          f = ib;do if (c[b + 944 >> 2] | 0) {
+        d = T(g[b + 48 >> 2]);if (Sb(d) | 0) {
+          e = a[(c[b + 976 >> 2] | 0) + 2 >> 0] | 0;if (e << 24 >> 24 == 0 ? (f = T(g[b + 40 >> 2]), f < T(0.0) & ((Sb(f) | 0) ^ 1)) : 0) {
+            d = T(-f);break;
+          }d = e << 24 >> 24 ? T(1.0) : T(0.0);
+        }
+      } else d = T(0.0); while (0);return T(d);
+    }function Ge(b) {
+      b = b | 0;var d = 0,
+          e = 0;yC(b + 400 | 0, 0, 540) | 0;a[b + 985 >> 0] = 1;lc(b);e = $b(b) | 0;if (e | 0) {
+        d = b + 948 | 0;b = 0;do {
+          Ge(c[(c[d >> 2] | 0) + (b << 2) >> 2] | 0);b = b + 1 | 0;
+        } while ((b | 0) != (e | 0));
+      }return;
+    }function He(a, b, d, e, f, h, i, j, k, m) {
+      a = a | 0;b = b | 0;d = T(d);e = e | 0;f = T(f);h = T(h);i = T(i);j = j | 0;k = k | 0;m = m | 0;var n = 0,
+          o = ib,
+          p = 0,
+          q = 0,
+          r = ib,
+          s = ib,
+          u = 0,
+          v = ib,
+          w = 0,
+          x = ib,
+          y = 0,
+          z = 0,
+          A = 0,
+          B = 0,
+          C = 0,
+          D = 0,
+          E = 0,
+          F = 0,
+          G = 0,
+          H = 0;G = l;l = l + 16 | 0;A = G + 12 | 0;B = G + 8 | 0;C = G + 4 | 0;D = G;F = re(c[a + 4 >> 2] | 0, k) | 0;y = oe(F) | 0;o = T(be(Xe(b) | 0, y ? h : i));z = ae(b, 2, h) | 0;E = ae(b, 0, i) | 0;do if (!(Sb(o) | 0) ? !(Sb(y ? d : f) | 0) : 0) {
+        n = b + 504 | 0;if (!(Sb(T(g[n >> 2])) | 0)) {
+          if (!(Ye(c[b + 976 >> 2] | 0, 0) | 0)) break;if ((c[b + 500 >> 2] | 0) == (c[2278] | 0)) break;
+        }g[n >> 2] = T(cC(o, T(Ce(b, F, h))));
+      } else p = 7; while (0);do if ((p | 0) == 7) {
+        w = y ^ 1;if (!(w | z ^ 1)) {
+          i = T(be(c[b + 992 >> 2] | 0, h));g[b + 504 >> 2] = T(cC(i, T(Ce(b, 2, h))));break;
+        }if (!(y | E ^ 1)) {
+          i = T(be(c[b + 996 >> 2] | 0, i));g[b + 504 >> 2] = T(cC(i, T(Ce(b, 0, h))));break;
+        }g[A >> 2] = T(t);g[B >> 2] = T(t);c[C >> 2] = 0;c[D >> 2] = 0;v = T(Ud(b, 2, h));x = T(Ud(b, 0, h));if (z) {
+          r = T(v + T(be(c[b + 992 >> 2] | 0, h)));g[A >> 2] = r;c[C >> 2] = 1;q = 1;
+        } else {
+          q = 0;r = T(t);
+        }if (E) {
+          o = T(x + T(be(c[b + 996 >> 2] | 0, i)));g[B >> 2] = o;c[D >> 2] = 1;n = 1;
+        } else {
+          n = 0;o = T(t);
+        }p = c[a + 32 >> 2] | 0;if (!(y & (p | 0) == 2)) {
+          if (Sb(r) | 0 ? !(Sb(d) | 0) : 0) {
+            g[A >> 2] = d;c[C >> 2] = 2;q = 2;r = d;
+          }
+        } else p = 2;if ((!((p | 0) == 2 & w) ? Sb(o) | 0 : 0) ? !(Sb(f) | 0) : 0) {
+          g[B >> 2] = f;c[D >> 2] = 2;n = 2;o = f;
+        }s = T(g[b + 396 >> 2]);u = Sb(s) | 0;do if (!u) {
+          if ((q | 0) == 1 & w) {
+            g[B >> 2] = T(T(r - v) / s);c[D >> 2] = 1;n = 1;p = 1;break;
+          }if (y & (n | 0) == 1) {
+            g[A >> 2] = T(s * T(o - x));c[C >> 2] = 1;n = 1;p = 1;
+          } else p = q;
+        } else p = q; while (0);H = Sb(d) | 0;q = (Je(a, b) | 0) != 4;if (!(y | z | ((e | 0) != 1 | H) | (q | (p | 0) == 1)) ? (g[A >> 2] = d, c[C >> 2] = 1, !u) : 0) {
+          g[B >> 2] = T(T(d - v) / s);c[D >> 2] = 1;n = 1;
+        }if (!(E | w | ((j | 0) != 1 | (Sb(f) | 0)) | (q | (n | 0) == 1)) ? (g[B >> 2] = f, c[D >> 2] = 1, !u) : 0) {
+          g[A >> 2] = T(s * T(f - x));c[C >> 2] = 1;
+        }Me(b, 2, h, h, C, A);Me(b, 0, i, h, D, B);d = T(g[A >> 2]);f = T(g[B >> 2]);Td(b, d, f, k, c[C >> 2] | 0, c[D >> 2] | 0, h, i, 0, 3565, m) | 0;i = T(g[b + 908 + (c[976 + (F << 2) >> 2] << 2) >> 2]);g[b + 504 >> 2] = T(cC(i, T(Ce(b, F, h))));
+      } while (0);c[b + 500 >> 2] = c[2278];l = G;return;
+    }function Ie(a, b, c, d, e) {
+      a = a | 0;b = b | 0;c = T(c);d = T(d);e = T(e);d = T(Te(a, b, c, d));return T(cC(d, T(Ce(a, b, e))));
+    }function Je(a, b) {
+      a = a | 0;b = b | 0;b = b + 20 | 0;b = c[((c[b >> 2] | 0) == 0 ? a + 16 | 0 : b) >> 2] | 0;if ((b | 0) == 5 ? We(c[a + 4 >> 2] | 0) | 0 : 0) b = 1;return b | 0;
+    }function Ke(a, b) {
+      a = a | 0;b = b | 0;if (oe(b) | 0 ? (c[a + 96 >> 2] | 0) != 0 : 0) b = 4;else b = c[1040 + (b << 2) >> 2] | 0;return a + 60 + (b << 3) | 0;
+    }function Le(a, b) {
+      a = a | 0;b = b | 0;if (oe(b) | 0 ? (c[a + 104 >> 2] | 0) != 0 : 0) b = 5;else b = c[1e3 + (b << 2) >> 2] | 0;return a + 60 + (b << 3) | 0;
+    }function Me(a, b, d, e, f, h) {
+      a = a | 0;b = b | 0;d = T(d);e = T(e);f = f | 0;h = h | 0;d = T(be(a + 380 + (c[976 + (b << 2) >> 2] << 3) | 0, d));d = T(d + T(Ud(a, b, e)));switch (c[f >> 2] | 0) {case 2:case 1:
+          {
+            f = Sb(d) | 0;e = T(g[h >> 2]);g[h >> 2] = f | e < d ? e : d;break;
+          }case 0:
+          {
+            if (!(Sb(d) | 0)) {
+              c[f >> 2] = 2;g[h >> 2] = d;
+            }break;
+          }default:
+}return;
+    }function Ne(a, b) {
+      a = a | 0;b = b | 0;a = a + 132 | 0;if (oe(b) | 0 ? (c[(Tb(a, 4, 948) | 0) + 4 >> 2] | 0) != 0 : 0) a = 1;else a = (c[(Tb(a, c[1040 + (b << 2) >> 2] | 0, 948) | 0) + 4 >> 2] | 0) != 0;return a | 0;
+    }function Oe(a, b, d) {
+      a = a | 0;b = b | 0;d = T(d);var e = 0,
+          f = 0;a = a + 132 | 0;if (oe(b) | 0 ? (e = Tb(a, 4, 948) | 0, (c[e + 4 >> 2] | 0) != 0) : 0) f = 4;else {
+        e = Tb(a, c[1040 + (b << 2) >> 2] | 0, 948) | 0;if (!(c[e + 4 >> 2] | 0)) d = T(0.0);else f = 4;
+      }if ((f | 0) == 4) d = T(be(e, d));return T(d);
+    }function Pe(a, b, d) {
+      a = a | 0;b = b | 0;d = T(d);var e = ib;e = T(g[a + 908 + (c[976 + (b << 2) >> 2] << 2) >> 2]);e = T(e + T(me(a, b, d)));return T(e + T(ne(a, b, d)));
+    }function Qe(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;a: do if (!(We(c[a + 4 >> 2] | 0) | 0)) {
+        if ((c[a + 16 >> 2] | 0) != 5) {
+          d = $b(a) | 0;if (!d) b = 0;else {
+            b = 0;while (1) {
+              e = ac(a, b) | 0;if ((c[e + 24 >> 2] | 0) == 0 ? (c[e + 20 >> 2] | 0) == 5 : 0) {
+                b = 1;break a;
+              }b = b + 1 | 0;if (b >>> 0 >= d >>> 0) {
+                b = 0;break;
+              }
+            }
+          }
+        } else b = 1;
+      } else b = 0; while (0);return b | 0;
+    }function Re(a, b) {
+      a = a | 0;b = b | 0;var d = ib;d = T(g[a + 908 + (c[976 + (b << 2) >> 2] << 2) >> 2]);return d >= T(0.0) & ((Sb(d) | 0) ^ 1) | 0;
+    }function Se(a) {
+      a = a | 0;var b = ib,
+          d = 0,
+          e = 0,
+          f = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = ib;d = c[a + 968 >> 2] | 0;if (!d) {
+        h = $b(a) | 0;do if (h | 0) {
+          d = 0;f = 0;while (1) {
+            e = ac(a, f) | 0;if (c[e + 940 >> 2] | 0) {
+              i = 8;break;
+            }if ((c[e + 24 >> 2] | 0) != 1) {
+              j = (Je(a, e) | 0) == 5;if (j) {
+                d = e;break;
+              } else d = (d | 0) == 0 ? e : d;
+            }f = f + 1 | 0;if (f >>> 0 >= h >>> 0) {
+              i = 8;break;
+            }
+          }if ((i | 0) == 8) if (!d) break;b = T(Se(d));return T(b + T(g[d + 404 >> 2]));
+        } while (0);b = T(g[a + 912 >> 2]);
+      } else {
+        k = T(g[a + 908 >> 2]);b = T(g[a + 912 >> 2]);b = T(mb[d & 0](a, k, b));ec(a, (Sb(b) | 0) ^ 1, 3573);
+      }return T(b);
+    }function Te(a, b, c, d) {
+      a = a | 0;b = b | 0;c = T(c);d = T(d);var e = ib,
+          f = 0;if (!(We(b) | 0)) {
+        if (oe(b) | 0) {
+          b = 0;f = 3;
+        } else {
+          d = T(t);e = T(t);
+        }
+      } else {
+        b = 1;f = 3;
+      }if ((f | 0) == 3) {
+        e = T(be(a + 364 + (b << 3) | 0, d));d = T(be(a + 380 + (b << 3) | 0, d));
+      }f = d < c & (d >= T(0.0) & ((Sb(d) | 0) ^ 1));c = f ? d : c;f = e >= T(0.0) & ((Sb(e) | 0) ^ 1) & c < e;return T(f ? e : c);
+    }function Ue(a, b, d, e, f, h, i) {
+      a = a | 0;b = b | 0;d = T(d);e = e | 0;f = T(f);h = h | 0;i = i | 0;var j = ib,
+          k = ib,
+          l = 0,
+          m = 0,
+          n = ib,
+          o = ib,
+          p = ib,
+          q = 0,
+          r = 0,
+          s = 0,
+          u = 0,
+          v = ib,
+          w = 0;s = re(c[a + 4 >> 2] | 0, h) | 0;q = ze(s, h) | 0;r = oe(s) | 0;n = T(Ud(b, 2, d));o = T(Ud(b, 0, d));if (!(ae(b, 2, d) | 0)) {
+        if (Ne(b, 2) | 0 ? Ze(b, 2) | 0 : 0) {
+          j = T(g[a + 908 >> 2]);k = T(se(a, 2));k = T(j - T(k + T(te(a, 2))));j = T(Oe(b, 2, d));j = T(Ie(b, 2, T(k - T(j + T(_e(b, 2, d)))), d, d));
+        } else j = T(t);
+      } else j = T(n + T(be(c[b + 992 >> 2] | 0, d)));if (!(ae(b, 0, f) | 0)) {
+        if (Ne(b, 0) | 0 ? Ze(b, 0) | 0 : 0) {
+          k = T(g[a + 912 >> 2]);v = T(se(a, 0));v = T(k - T(v + T(te(a, 0))));k = T(Oe(b, 0, f));k = T(Ie(b, 0, T(v - T(k + T(_e(b, 0, f)))), f, d));
+        } else k = T(t);
+      } else k = T(o + T(be(c[b + 996 >> 2] | 0, f)));l = Sb(j) | 0;m = Sb(k) | 0;do if (l ^ m ? (p = T(g[b + 396 >> 2]), !(Sb(p) | 0)) : 0) if (l) {
+        j = T(n + T(T(k - o) * p));break;
+      } else {
+        v = T(o + T(T(j - n) / p));k = m ? v : k;break;
+      } while (0);m = Sb(j) | 0;l = Sb(k) | 0;if (m | l) {
+        w = (m ^ 1) & 1;e = d > T(0.0) & ((e | 0) != 0 & m);j = r ? j : e ? d : j;Td(b, j, k, h, r ? w : e ? 2 : w, m & (l ^ 1) & 1, j, k, 0, 3623, i) | 0;j = T(g[b + 908 >> 2]);j = T(j + T(Ud(b, 2, d)));k = T(g[b + 912 >> 2]);k = T(k + T(Ud(b, 0, d)));
+      }Td(b, j, k, h, 1, 1, j, k, 1, 3635, i) | 0;if (Ze(b, s) | 0 ? !(Ne(b, s) | 0) : 0) {
+        w = c[976 + (s << 2) >> 2] | 0;v = T(g[a + 908 + (w << 2) >> 2]);v = T(v - T(g[b + 908 + (w << 2) >> 2]));v = T(v - T(te(a, s)));v = T(v - T(ne(b, s, d)));v = T(v - T(_e(b, s, r ? d : f)));g[b + 400 + (c[1040 + (s << 2) >> 2] << 2) >> 2] = v;
+      } else u = 21;do if ((u | 0) == 21) {
+        if (!(Ne(b, s) | 0) ? (c[a + 8 >> 2] | 0) == 1 : 0) {
+          w = c[976 + (s << 2) >> 2] | 0;v = T(g[a + 908 + (w << 2) >> 2]);v = T(T(v - T(g[b + 908 + (w << 2) >> 2])) * T(.5));g[b + 400 + (c[1040 + (s << 2) >> 2] << 2) >> 2] = v;break;
+        }if (!(Ne(b, s) | 0) ? (c[a + 8 >> 2] | 0) == 2 : 0) {
+          w = c[976 + (s << 2) >> 2] | 0;v = T(g[a + 908 + (w << 2) >> 2]);v = T(v - T(g[b + 908 + (w << 2) >> 2]));g[b + 400 + (c[1040 + (s << 2) >> 2] << 2) >> 2] = v;
+        }
+      } while (0);if (Ze(b, q) | 0 ? !(Ne(b, q) | 0) : 0) {
+        w = c[976 + (q << 2) >> 2] | 0;v = T(g[a + 908 + (w << 2) >> 2]);v = T(v - T(g[b + 908 + (w << 2) >> 2]));v = T(v - T(te(a, q)));v = T(v - T(ne(b, q, d)));v = T(v - T(_e(b, q, r ? f : d)));g[b + 400 + (c[1040 + (q << 2) >> 2] << 2) >> 2] = v;
+      } else u = 30;do if ((u | 0) == 30 ? !(Ne(b, q) | 0) : 0) {
+        if ((Je(a, b) | 0) == 2) {
+          w = c[976 + (q << 2) >> 2] | 0;v = T(g[a + 908 + (w << 2) >> 2]);v = T(T(v - T(g[b + 908 + (w << 2) >> 2])) * T(.5));g[b + 400 + (c[1040 + (q << 2) >> 2] << 2) >> 2] = v;break;
+        }w = (Je(a, b) | 0) == 3;if (w ^ (c[a + 28 >> 2] | 0) == 2) {
+          w = c[976 + (q << 2) >> 2] | 0;v = T(g[a + 908 + (w << 2) >> 2]);v = T(v - T(g[b + 908 + (w << 2) >> 2]));g[b + 400 + (c[1040 + (q << 2) >> 2] << 2) >> 2] = v;
+        }
+      } while (0);return;
+    }function Ve(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = ib,
+          f = 0;f = c[976 + (d << 2) >> 2] | 0;e = T(g[b + 908 + (f << 2) >> 2]);e = T(T(g[a + 908 + (f << 2) >> 2]) - e);e = T(e - T(g[b + 400 + (c[1040 + (d << 2) >> 2] << 2) >> 2]));g[b + 400 + (c[1e3 + (d << 2) >> 2] << 2) >> 2] = e;return;
+    }function We(a) {
+      a = a | 0;return (a | 1 | 0) == 1 | 0;
+    }function Xe(b) {
+      b = b | 0;var d = ib;switch (c[b + 56 >> 2] | 0) {case 0:case 3:
+          {
+            d = T(g[b + 40 >> 2]);if (d > T(0.0) & ((Sb(d) | 0) ^ 1)) b = a[(c[b + 976 >> 2] | 0) + 2 >> 0] | 0 ? 1056 : 992;else b = 1056;break;
+          }default:
+          b = b + 52 | 0;}return b | 0;
+    }function Ye(b, c) {
+      b = b | 0;c = c | 0;return (a[b + c >> 0] | 0) != 0 | 0;
+    }function Ze(a, b) {
+      a = a | 0;b = b | 0;a = a + 132 | 0;if (oe(b) | 0 ? (c[(Tb(a, 5, 948) | 0) + 4 >> 2] | 0) != 0 : 0) a = 1;else a = (c[(Tb(a, c[1e3 + (b << 2) >> 2] | 0, 948) | 0) + 4 >> 2] | 0) != 0;return a | 0;
+    }function _e(a, b, d) {
+      a = a | 0;b = b | 0;d = T(d);var e = 0,
+          f = 0;a = a + 132 | 0;if (oe(b) | 0 ? (e = Tb(a, 5, 948) | 0, (c[e + 4 >> 2] | 0) != 0) : 0) f = 4;else {
+        e = Tb(a, c[1e3 + (b << 2) >> 2] | 0, 948) | 0;if (!(c[e + 4 >> 2] | 0)) d = T(0.0);else f = 4;
+      }if ((f | 0) == 4) d = T(be(e, d));return T(d);
+    }function $e(a, b, c) {
+      a = a | 0;b = b | 0;c = T(c);if (Ne(a, b) | 0) c = T(Oe(a, b, c));else c = T(-T(_e(a, b, c)));return T(c);
+    }function af(a) {
+      a = T(a);return (g[j >> 2] = a, c[j >> 2] | 0) | 0;
+    }function bf(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 1073741823) Ta();else {
+          f = qC(b << 2) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d << 2) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b << 2);return;
+    }function cf(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (0 - (f >> 2) << 2) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function df(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~((e + -4 - b | 0) >>> 2) << 2);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function ef(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0;h = a + 4 | 0;i = c[h >> 2] | 0;f = i - e | 0;g = f >> 2;a = b + (g << 2) | 0;if (a >>> 0 < d >>> 0) {
+        e = i;do {
+          c[e >> 2] = c[a >> 2];a = a + 4 | 0;e = (c[h >> 2] | 0) + 4 | 0;c[h >> 2] = e;
+        } while (a >>> 0 < d >>> 0);
+      }if (g | 0) GC(i + (0 - g << 2) | 0, b | 0, f | 0) | 0;return;
+    }function ff(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0;i = b + 4 | 0;j = c[i >> 2] | 0;f = c[a >> 2] | 0;h = d;g = h - f | 0;e = j + (0 - (g >> 2) << 2) | 0;c[i >> 2] = e;if ((g | 0) > 0) BC(e | 0, f | 0, g | 0) | 0;f = a + 4 | 0;g = b + 8 | 0;e = (c[f >> 2] | 0) - h | 0;if ((e | 0) > 0) {
+        BC(c[g >> 2] | 0, d | 0, e | 0) | 0;c[g >> 2] = (c[g >> 2] | 0) + (e >>> 2 << 2);
+      }h = c[a >> 2] | 0;c[a >> 2] = c[i >> 2];c[i >> 2] = h;h = c[f >> 2] | 0;c[f >> 2] = c[g >> 2];c[g >> 2] = h;h = a + 8 | 0;d = b + 12 | 0;a = c[h >> 2] | 0;c[h >> 2] = c[d >> 2];c[d >> 2] = a;c[b >> 2] = c[i >> 2];return j | 0;
+    }function gf(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;h = c[b >> 2] | 0;g = c[d >> 2] | 0;if ((h | 0) != (g | 0)) {
+        f = a + 8 | 0;d = ((g + -4 - h | 0) >>> 2) + 1 | 0;a = h;e = c[f >> 2] | 0;do {
+          c[e >> 2] = c[a >> 2];e = (c[f >> 2] | 0) + 4 | 0;c[f >> 2] = e;a = a + 4 | 0;
+        } while ((a | 0) != (g | 0));c[b >> 2] = h + (d << 2);
+      }return;
+    }function hf() {
+      Qb();return;
+    }function jf() {
+      var a = 0;a = qC(4) | 0;kf(a);return a | 0;
+    }function kf(a) {
+      a = a | 0;c[a >> 2] = gc() | 0;return;
+    }function lf(a) {
+      a = a | 0;if (a | 0) {
+        mf(a);sC(a);
+      }return;
+    }function mf(a) {
+      a = a | 0;ic(c[a >> 2] | 0);return;
+    }function nf(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;he(c[a >> 2] | 0, b, d);return;
+    }function of(a, b) {
+      a = a | 0;b = T(b);Zd(c[a >> 2] | 0, b);return;
+    }function pf(a, b) {
+      a = a | 0;b = b | 0;return Ye(c[a >> 2] | 0, b) | 0;
+    }function qf() {
+      var a = 0;a = qC(8) | 0;rf(a, 0);return a | 0;
+    }function rf(a, b) {
+      a = a | 0;b = b | 0;if (!b) b = Wb() | 0;else b = Ub(c[b >> 2] | 0) | 0;c[a >> 2] = b;c[a + 4 >> 2] = 0;vc(b, a);return;
+    }function sf(a) {
+      a = a | 0;var b = 0;b = qC(8) | 0;rf(b, a);return b | 0;
+    }function tf(a) {
+      a = a | 0;if (a | 0) {
+        uf(a);sC(a);
+      }return;
+    }function uf(a) {
+      a = a | 0;var b = 0;Zb(c[a >> 2] | 0);b = a + 4 | 0;a = c[b >> 2] | 0;c[b >> 2] = 0;if (a | 0) {
+        vf(a);sC(a);
+      }return;
+    }function vf(a) {
+      a = a | 0;wf(a);return;
+    }function wf(a) {
+      a = a | 0;a = c[a >> 2] | 0;if (a | 0) ab(a | 0);return;
+    }function xf(a) {
+      a = a | 0;return wc(a) | 0;
+    }function yf(a) {
+      a = a | 0;var b = 0,
+          d = 0;d = a + 4 | 0;b = c[d >> 2] | 0;c[d >> 2] = 0;if (b | 0) {
+        vf(b);sC(b);
+      }dc(c[a >> 2] | 0);return;
+    }function zf(a, b) {
+      a = a | 0;b = b | 0;sc(c[a >> 2] | 0, c[b >> 2] | 0);return;
+    }function Af(a, b) {
+      a = a | 0;b = b | 0;Hc(c[a >> 2] | 0, b);return;
+    }function Bf(a, b, d) {
+      a = a | 0;b = b | 0;d = +d;Vc(c[a >> 2] | 0, b, T(d));return;
+    }function Cf(a, b, d) {
+      a = a | 0;b = b | 0;d = +d;Wc(c[a >> 2] | 0, b, T(d));return;
+    }function Df(a, b) {
+      a = a | 0;b = b | 0;Bc(c[a >> 2] | 0, b);return;
+    }function Ef(a, b) {
+      a = a | 0;b = b | 0;Dc(c[a >> 2] | 0, b);return;
+    }function Ff(a, b) {
+      a = a | 0;b = b | 0;Fc(c[a >> 2] | 0, b);return;
+    }function Gf(a, b) {
+      a = a | 0;b = b | 0;xc(c[a >> 2] | 0, b);return;
+    }function Hf(a, b) {
+      a = a | 0;b = b | 0;Jc(c[a >> 2] | 0, b);return;
+    }function If(a, b) {
+      a = a | 0;b = b | 0;zc(c[a >> 2] | 0, b);return;
+    }function Jf(a, b, d) {
+      a = a | 0;b = b | 0;d = +d;Yc(c[a >> 2] | 0, b, T(d));return;
+    }function Kf(a, b, d) {
+      a = a | 0;b = b | 0;d = +d;Zc(c[a >> 2] | 0, b, T(d));return;
+    }function Lf(a, b) {
+      a = a | 0;b = b | 0;$c(c[a >> 2] | 0, b);return;
+    }function Mf(a, b) {
+      a = a | 0;b = b | 0;Lc(c[a >> 2] | 0, b);return;
+    }function Nf(a, b) {
+      a = a | 0;b = b | 0;Nc(c[a >> 2] | 0, b);return;
+    }function Of(a, b) {
+      a = a | 0;b = +b;Pc(c[a >> 2] | 0, T(b));return;
+    }function Pf(a, b) {
+      a = a | 0;b = +b;Sc(c[a >> 2] | 0, T(b));return;
+    }function Qf(a, b) {
+      a = a | 0;b = +b;Tc(c[a >> 2] | 0, T(b));return;
+    }function Rf(a, b) {
+      a = a | 0;b = +b;Qc(c[a >> 2] | 0, T(b));return;
+    }function Sf(a, b) {
+      a = a | 0;b = +b;Rc(c[a >> 2] | 0, T(b));return;
+    }function Tf(a, b) {
+      a = a | 0;b = +b;fd(c[a >> 2] | 0, T(b));return;
+    }function Uf(a, b) {
+      a = a | 0;b = +b;gd(c[a >> 2] | 0, T(b));return;
+    }function Vf(a) {
+      a = a | 0;hd(c[a >> 2] | 0);return;
+    }function Wf(a, b) {
+      a = a | 0;b = +b;jd(c[a >> 2] | 0, T(b));return;
+    }function Xf(a, b) {
+      a = a | 0;b = +b;kd(c[a >> 2] | 0, T(b));return;
+    }function Yf(a) {
+      a = a | 0;ld(c[a >> 2] | 0);return;
+    }function Zf(a, b) {
+      a = a | 0;b = +b;nd(c[a >> 2] | 0, T(b));return;
+    }function _f(a, b) {
+      a = a | 0;b = +b;od(c[a >> 2] | 0, T(b));return;
+    }function $f(a, b) {
+      a = a | 0;b = +b;qd(c[a >> 2] | 0, T(b));return;
+    }function ag(a, b) {
+      a = a | 0;b = +b;rd(c[a >> 2] | 0, T(b));return;
+    }function bg(a, b) {
+      a = a | 0;b = +b;td(c[a >> 2] | 0, T(b));return;
+    }function cg(a, b) {
+      a = a | 0;b = +b;ud(c[a >> 2] | 0, T(b));return;
+    }function dg(a, b) {
+      a = a | 0;b = +b;wd(c[a >> 2] | 0, T(b));return;
+    }function eg(a, b) {
+      a = a | 0;b = +b;xd(c[a >> 2] | 0, T(b));return;
+    }function fg(a, b) {
+      a = a | 0;b = +b;zd(c[a >> 2] | 0, T(b));return;
+    }function gg(a, b, d) {
+      a = a | 0;b = b | 0;d = +d;dd(c[a >> 2] | 0, b, T(d));return;
+    }function hg(a, b, d) {
+      a = a | 0;b = b | 0;d = +d;ad(c[a >> 2] | 0, b, T(d));return;
+    }function ig(a, b, d) {
+      a = a | 0;b = b | 0;d = +d;bd(c[a >> 2] | 0, b, T(d));return;
+    }function jg(a) {
+      a = a | 0;return Ic(c[a >> 2] | 0) | 0;
+    }function kg(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0;e = l;l = l + 16 | 0;f = e;Xc(f, c[b >> 2] | 0, d);lg(a, f);l = e;return;
+    }function lg(a, b) {
+      a = a | 0;b = b | 0;mg(a, c[b + 4 >> 2] | 0, +T(g[b >> 2]));return;
+    }function mg(a, b, d) {
+      a = a | 0;b = b | 0;d = +d;c[a >> 2] = b;h[a + 8 >> 3] = d;return;
+    }function ng(a) {
+      a = a | 0;return Cc(c[a >> 2] | 0) | 0;
+    }function og(a) {
+      a = a | 0;return Ec(c[a >> 2] | 0) | 0;
+    }function pg(a) {
+      a = a | 0;return Gc(c[a >> 2] | 0) | 0;
+    }function qg(a) {
+      a = a | 0;return yc(c[a >> 2] | 0) | 0;
+    }function rg(a) {
+      a = a | 0;return Kc(c[a >> 2] | 0) | 0;
+    }function sg(a) {
+      a = a | 0;return Ac(c[a >> 2] | 0) | 0;
+    }function tg(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0;e = l;l = l + 16 | 0;f = e;_c(f, c[b >> 2] | 0, d);lg(a, f);l = e;return;
+    }function ug(a) {
+      a = a | 0;return Mc(c[a >> 2] | 0) | 0;
+    }function vg(a) {
+      a = a | 0;return Oc(c[a >> 2] | 0) | 0;
+    }function wg(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;d = l;l = l + 16 | 0;e = d;Uc(e, c[b >> 2] | 0);lg(a, e);l = d;return;
+    }function xg(a) {
+      a = a | 0;return + +T(tc(c[a >> 2] | 0));
+    }function yg(a) {
+      a = a | 0;return + +T(uc(c[a >> 2] | 0));
+    }function zg(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;d = l;l = l + 16 | 0;e = d;id(e, c[b >> 2] | 0);lg(a, e);l = d;return;
+    }function Ag(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;d = l;l = l + 16 | 0;e = d;md(e, c[b >> 2] | 0);lg(a, e);l = d;return;
+    }function Bg(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;d = l;l = l + 16 | 0;e = d;pd(e, c[b >> 2] | 0);lg(a, e);l = d;return;
+    }function Cg(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;d = l;l = l + 16 | 0;e = d;sd(e, c[b >> 2] | 0);lg(a, e);l = d;return;
+    }function Dg(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;d = l;l = l + 16 | 0;e = d;vd(e, c[b >> 2] | 0);lg(a, e);l = d;return;
+    }function Eg(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;d = l;l = l + 16 | 0;e = d;yd(e, c[b >> 2] | 0);lg(a, e);l = d;return;
+    }function Fg(a) {
+      a = a | 0;return + +T(Ad(c[a >> 2] | 0));
+    }function Gg(a, b) {
+      a = a | 0;b = b | 0;return + +T(ed(c[a >> 2] | 0, b));
+    }function Hg(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0;e = l;l = l + 16 | 0;f = e;cd(f, c[b >> 2] | 0, d);lg(a, f);l = e;return;
+    }function Ig(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;kc(c[a >> 2] | 0, c[b >> 2] | 0, d);return;
+    }function Jg(a, b) {
+      a = a | 0;b = b | 0;cc(c[a >> 2] | 0, c[b >> 2] | 0);return;
+    }function Kg(a) {
+      a = a | 0;return $b(c[a >> 2] | 0) | 0;
+    }function Lg(a) {
+      a = a | 0;a = pc(c[a >> 2] | 0) | 0;if (!a) a = 0;else a = xf(a) | 0;return a | 0;
+    }function Mg(a, b) {
+      a = a | 0;b = b | 0;a = ac(c[a >> 2] | 0, b) | 0;if (!a) a = 0;else a = xf(a) | 0;return a | 0;
+    }function Ng(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;e = qC(4) | 0;Og(e, b);d = a + 4 | 0;b = c[d >> 2] | 0;c[d >> 2] = e;if (b | 0) {
+        vf(b);sC(b);
+      }jc(c[a >> 2] | 0, 1);return;
+    }function Og(a, b) {
+      a = a | 0;b = b | 0;gh(a, b);return;
+    }function Pg(a, b, c, d, e, f) {
+      a = a | 0;b = b | 0;c = T(c);d = d | 0;e = T(e);f = f | 0;var i = 0,
+          j = 0;i = l;l = l + 16 | 0;j = i;Qg(j, wc(b) | 0, +c, d, +e, f);g[a >> 2] = T(+h[j >> 3]);g[a + 4 >> 2] = T(+h[j + 8 >> 3]);l = i;return;
+    }function Qg(a, b, d, e, f, g) {
+      a = a | 0;b = b | 0;d = +d;e = e | 0;f = +f;g = g | 0;var i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;i = l;l = l + 32 | 0;n = i + 8 | 0;m = i + 20 | 0;k = i;j = i + 16 | 0;h[n >> 3] = d;c[m >> 2] = e;h[k >> 3] = f;c[j >> 2] = g;Rg(a, c[b + 4 >> 2] | 0, n, m, k, j);l = i;return;
+    }function Rg(a, b, d, e, f, g) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;f = f | 0;g = g | 0;var i = 0,
+          j = 0;i = l;l = l + 16 | 0;j = i;UA(j);b = Sg(b) | 0;Tg(a, b, +h[d >> 3], c[e >> 2] | 0, +h[f >> 3], c[g >> 2] | 0);WA(j);l = i;return;
+    }function Sg(a) {
+      a = a | 0;return c[a >> 2] | 0;
+    }function Tg(a, b, c, d, e, f) {
+      a = a | 0;b = b | 0;c = +c;d = d | 0;e = +e;f = f | 0;var g = 0;g = Vg(Ug() | 0) | 0;c = +Wg(c);d = Xg(d) | 0;e = +Wg(e);Yg(a, cb(0, g | 0, b | 0, +c, d | 0, +e, Xg(f) | 0) | 0);return;
+    }function Ug() {
+      var b = 0;if (!(a[7608] | 0)) {
+        dh(9120);b = 7608;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 9120;
+    }function Vg(a) {
+      a = a | 0;return c[a + 8 >> 2] | 0;
+    }function Wg(a) {
+      a = +a;return + +ch(a);
+    }function Xg(a) {
+      a = a | 0;return bh(a) | 0;
+    }function Yg(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0;f = l;l = l + 32 | 0;d = f;e = b;if (!(e & 1)) {
+        c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = c[b + 4 >> 2];c[a + 8 >> 2] = c[b + 8 >> 2];c[a + 12 >> 2] = c[b + 12 >> 2];
+      } else {
+        Zg(d, 0);Ja(e | 0, d | 0) | 0;_g(a, d);$g(d);
+      }l = f;return;
+    }function Zg(b, d) {
+      b = b | 0;d = d | 0;ah(b, d);c[b + 8 >> 2] = 0;a[b + 24 >> 0] = 0;return;
+    }function _g(a, b) {
+      a = a | 0;b = b | 0;b = b + 8 | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = c[b + 4 >> 2];c[a + 8 >> 2] = c[b + 8 >> 2];c[a + 12 >> 2] = c[b + 12 >> 2];return;
+    }function $g(b) {
+      b = b | 0;a[b + 24 >> 0] = 0;return;
+    }function ah(a, b) {
+      a = a | 0;b = b | 0;c[a >> 2] = b;return;
+    }function bh(a) {
+      a = a | 0;return a | 0;
+    }function ch(a) {
+      a = +a;return +a;
+    }function dh(a) {
+      a = a | 0;fh(a, eh() | 0, 4);return;
+    }function eh() {
+      return 1064;
+    }function fh(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;c[a >> 2] = b;c[a + 4 >> 2] = d;c[a + 8 >> 2] = _a(b | 0, d + 1 | 0) | 0;return;
+    }function gh(a, b) {
+      a = a | 0;b = b | 0;b = c[b >> 2] | 0;c[a >> 2] = b;Aa(b | 0);return;
+    }function hh(a) {
+      a = a | 0;var b = 0,
+          d = 0;d = a + 4 | 0;b = c[d >> 2] | 0;c[d >> 2] = 0;if (b | 0) {
+        vf(b);sC(b);
+      }jc(c[a >> 2] | 0, 0);return;
+    }function ih(a) {
+      a = a | 0;qc(c[a >> 2] | 0);return;
+    }function jh(a) {
+      a = a | 0;return rc(c[a >> 2] | 0) | 0;
+    }function kh(a, b, d, e) {
+      a = a | 0;b = +b;d = +d;e = e | 0;_d(c[a >> 2] | 0, T(b), T(d), e);return;
+    }function lh(a) {
+      a = a | 0;return + +T(Bd(c[a >> 2] | 0));
+    }function mh(a) {
+      a = a | 0;return + +T(Dd(c[a >> 2] | 0));
+    }function nh(a) {
+      a = a | 0;return + +T(Cd(c[a >> 2] | 0));
+    }function oh(a) {
+      a = a | 0;return + +T(Ed(c[a >> 2] | 0));
+    }function ph(a) {
+      a = a | 0;return + +T(Fd(c[a >> 2] | 0));
+    }function qh(a) {
+      a = a | 0;return + +T(Gd(c[a >> 2] | 0));
+    }function rh(a, b) {
+      a = a | 0;b = b | 0;h[a >> 3] = +T(Bd(c[b >> 2] | 0));h[a + 8 >> 3] = +T(Dd(c[b >> 2] | 0));h[a + 16 >> 3] = +T(Cd(c[b >> 2] | 0));h[a + 24 >> 3] = +T(Ed(c[b >> 2] | 0));h[a + 32 >> 3] = +T(Fd(c[b >> 2] | 0));h[a + 40 >> 3] = +T(Gd(c[b >> 2] | 0));return;
+    }function sh(a, b) {
+      a = a | 0;b = b | 0;return + +T(Hd(c[a >> 2] | 0, b));
+    }function th(a, b) {
+      a = a | 0;b = b | 0;return + +T(Id(c[a >> 2] | 0, b));
+    }function uh(a, b) {
+      a = a | 0;b = b | 0;return + +T(Jd(c[a >> 2] | 0, b));
+    }function vh() {
+      return fc() | 0;
+    }function wh() {
+      xh();yh();zh();Ah();Bh();Ch();return;
+    }function xh() {
+      kv(11713, 4938, 1);return;
+    }function yh() {
+      yu(10448);return;
+    }function zh() {
+      eu(10408);return;
+    }function Ah() {
+      vt(10324);return;
+    }function Bh() {
+      or(10096);return;
+    }function Ch() {
+      Dh(9132);return;
+    }function Dh(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0,
+          p = 0,
+          q = 0,
+          r = 0,
+          s = 0,
+          t = 0,
+          u = 0,
+          v = 0,
+          w = 0,
+          x = 0,
+          y = 0,
+          z = 0,
+          A = 0,
+          B = 0,
+          C = 0,
+          D = 0,
+          E = 0,
+          F = 0,
+          G = 0,
+          H = 0,
+          I = 0,
+          J = 0,
+          K = 0,
+          L = 0,
+          M = 0,
+          N = 0,
+          O = 0,
+          P = 0,
+          Q = 0,
+          R = 0,
+          S = 0,
+          T = 0,
+          U = 0,
+          V = 0,
+          W = 0,
+          X = 0,
+          Y = 0,
+          Z = 0,
+          _ = 0,
+          $ = 0,
+          aa = 0,
+          ba = 0,
+          ca = 0,
+          da = 0,
+          ea = 0,
+          fa = 0,
+          ga = 0,
+          ha = 0,
+          ia = 0,
+          ja = 0,
+          ka = 0,
+          la = 0,
+          ma = 0,
+          na = 0,
+          oa = 0,
+          pa = 0,
+          qa = 0,
+          ra = 0,
+          sa = 0,
+          ta = 0,
+          ua = 0,
+          va = 0,
+          wa = 0,
+          xa = 0,
+          ya = 0,
+          za = 0,
+          Aa = 0,
+          Ba = 0,
+          Ca = 0,
+          Da = 0,
+          Ea = 0,
+          Fa = 0,
+          Ga = 0;b = l;l = l + 672 | 0;d = b + 656 | 0;Ga = b + 648 | 0;Fa = b + 640 | 0;Ea = b + 632 | 0;Da = b + 624 | 0;Ca = b + 616 | 0;Ba = b + 608 | 0;Aa = b + 600 | 0;za = b + 592 | 0;ya = b + 584 | 0;xa = b + 576 | 0;wa = b + 568 | 0;va = b + 560 | 0;ua = b + 552 | 0;ta = b + 544 | 0;sa = b + 536 | 0;ra = b + 528 | 0;qa = b + 520 | 0;pa = b + 512 | 0;oa = b + 504 | 0;na = b + 496 | 0;ma = b + 488 | 0;la = b + 480 | 0;ka = b + 472 | 0;ja = b + 464 | 0;ia = b + 456 | 0;ha = b + 448 | 0;ga = b + 440 | 0;fa = b + 432 | 0;ea = b + 424 | 0;da = b + 416 | 0;ca = b + 408 | 0;ba = b + 400 | 0;aa = b + 392 | 0;$ = b + 384 | 0;_ = b + 376 | 0;Z = b + 368 | 0;Y = b + 360 | 0;X = b + 352 | 0;W = b + 344 | 0;V = b + 336 | 0;U = b + 328 | 0;T = b + 320 | 0;S = b + 312 | 0;R = b + 304 | 0;Q = b + 296 | 0;P = b + 288 | 0;O = b + 280 | 0;N = b + 272 | 0;M = b + 264 | 0;L = b + 256 | 0;K = b + 248 | 0;J = b + 240 | 0;I = b + 232 | 0;H = b + 224 | 0;G = b + 216 | 0;F = b + 208 | 0;E = b + 200 | 0;D = b + 192 | 0;C = b + 184 | 0;B = b + 176 | 0;A = b + 168 | 0;z = b + 160 | 0;y = b + 152 | 0;x = b + 144 | 0;w = b + 136 | 0;v = b + 128 | 0;u = b + 120 | 0;t = b + 112 | 0;s = b + 104 | 0;r = b + 96 | 0;q = b + 88 | 0;p = b + 80 | 0;o = b + 72 | 0;n = b + 64 | 0;m = b + 56 | 0;k = b + 48 | 0;j = b + 40 | 0;i = b + 32 | 0;h = b + 24 | 0;g = b + 16 | 0;f = b + 8 | 0;e = b;Eh(a, 3646);Fh(a, 3651, 2) | 0;Gh(a, 3665, 2) | 0;Hh(a, 3682, 18) | 0;c[Ga >> 2] = 19;c[Ga + 4 >> 2] = 0;c[d >> 2] = c[Ga >> 2];c[d + 4 >> 2] = c[Ga + 4 >> 2];Ih(a, 3690, d) | 0;c[Fa >> 2] = 1;c[Fa + 4 >> 2] = 0;c[d >> 2] = c[Fa >> 2];c[d + 4 >> 2] = c[Fa + 4 >> 2];Jh(a, 3696, d) | 0;c[Ea >> 2] = 2;c[Ea + 4 >> 2] = 0;c[d >> 2] = c[Ea >> 2];c[d + 4 >> 2] = c[Ea + 4 >> 2];Kh(a, 3706, d) | 0;c[Da >> 2] = 1;c[Da + 4 >> 2] = 0;c[d >> 2] = c[Da >> 2];c[d + 4 >> 2] = c[Da + 4 >> 2];Lh(a, 3722, d) | 0;c[Ca >> 2] = 2;c[Ca + 4 >> 2] = 0;c[d >> 2] = c[Ca >> 2];c[d + 4 >> 2] = c[Ca + 4 >> 2];Lh(a, 3734, d) | 0;c[Ba >> 2] = 3;c[Ba + 4 >> 2] = 0;c[d >> 2] = c[Ba >> 2];c[d + 4 >> 2] = c[Ba + 4 >> 2];Kh(a, 3753, d) | 0;c[Aa >> 2] = 4;c[Aa + 4 >> 2] = 0;c[d >> 2] = c[Aa >> 2];c[d + 4 >> 2] = c[Aa + 4 >> 2];Kh(a, 3769, d) | 0;c[za >> 2] = 5;c[za + 4 >> 2] = 0;c[d >> 2] = c[za >> 2];c[d + 4 >> 2] = c[za + 4 >> 2];Kh(a, 3783, d) | 0;c[ya >> 2] = 6;c[ya + 4 >> 2] = 0;c[d >> 2] = c[ya >> 2];c[d + 4 >> 2] = c[ya + 4 >> 2];Kh(a, 3796, d) | 0;c[xa >> 2] = 7;c[xa + 4 >> 2] = 0;c[d >> 2] = c[xa >> 2];c[d + 4 >> 2] = c[xa + 4 >> 2];Kh(a, 3813, d) | 0;c[wa >> 2] = 8;c[wa + 4 >> 2] = 0;c[d >> 2] = c[wa >> 2];c[d + 4 >> 2] = c[wa + 4 >> 2];Kh(a, 3825, d) | 0;c[va >> 2] = 3;c[va + 4 >> 2] = 0;c[d >> 2] = c[va >> 2];c[d + 4 >> 2] = c[va + 4 >> 2];Lh(a, 3843, d) | 0;c[ua >> 2] = 4;c[ua + 4 >> 2] = 0;c[d >> 2] = c[ua >> 2];c[d + 4 >> 2] = c[ua + 4 >> 2];Lh(a, 3853, d) | 0;c[ta >> 2] = 9;c[ta + 4 >> 2] = 0;c[d >> 2] = c[ta >> 2];c[d + 4 >> 2] = c[ta + 4 >> 2];Kh(a, 3870, d) | 0;c[sa >> 2] = 10;c[sa + 4 >> 2] = 0;c[d >> 2] = c[sa >> 2];c[d + 4 >> 2] = c[sa + 4 >> 2];Kh(a, 3884, d) | 0;c[ra >> 2] = 11;c[ra + 4 >> 2] = 0;c[d >> 2] = c[ra >> 2];c[d + 4 >> 2] = c[ra + 4 >> 2];Kh(a, 3896, d) | 0;c[qa >> 2] = 1;c[qa + 4 >> 2] = 0;c[d >> 2] = c[qa >> 2];c[d + 4 >> 2] = c[qa + 4 >> 2];Mh(a, 3907, d) | 0;c[pa >> 2] = 2;c[pa + 4 >> 2] = 0;c[d >> 2] = c[pa >> 2];c[d + 4 >> 2] = c[pa + 4 >> 2];Mh(a, 3915, d) | 0;c[oa >> 2] = 3;c[oa + 4 >> 2] = 0;c[d >> 2] = c[oa >> 2];c[d + 4 >> 2] = c[oa + 4 >> 2];Mh(a, 3928, d) | 0;c[na >> 2] = 4;c[na + 4 >> 2] = 0;c[d >> 2] = c[na >> 2];c[d + 4 >> 2] = c[na + 4 >> 2];Mh(a, 3948, d) | 0;c[ma >> 2] = 5;c[ma + 4 >> 2] = 0;c[d >> 2] = c[ma >> 2];c[d + 4 >> 2] = c[ma + 4 >> 2];Mh(a, 3960, d) | 0;c[la >> 2] = 6;c[la + 4 >> 2] = 0;c[d >> 2] = c[la >> 2];c[d + 4 >> 2] = c[la + 4 >> 2];Mh(a, 3974, d) | 0;c[ka >> 2] = 7;c[ka + 4 >> 2] = 0;c[d >> 2] = c[ka >> 2];c[d + 4 >> 2] = c[ka + 4 >> 2];Mh(a, 3983, d) | 0;c[ja >> 2] = 20;c[ja + 4 >> 2] = 0;c[d >> 2] = c[ja >> 2];c[d + 4 >> 2] = c[ja + 4 >> 2];Ih(a, 3999, d) | 0;c[ia >> 2] = 8;c[ia + 4 >> 2] = 0;c[d >> 2] = c[ia >> 2];c[d + 4 >> 2] = c[ia + 4 >> 2];Mh(a, 4012, d) | 0;c[ha >> 2] = 9;c[ha + 4 >> 2] = 0;c[d >> 2] = c[ha >> 2];c[d + 4 >> 2] = c[ha + 4 >> 2];Mh(a, 4022, d) | 0;c[ga >> 2] = 21;c[ga + 4 >> 2] = 0;c[d >> 2] = c[ga >> 2];c[d + 4 >> 2] = c[ga + 4 >> 2];Ih(a, 4039, d) | 0;c[fa >> 2] = 10;c[fa + 4 >> 2] = 0;c[d >> 2] = c[fa >> 2];c[d + 4 >> 2] = c[fa + 4 >> 2];Mh(a, 4053, d) | 0;c[ea >> 2] = 11;c[ea + 4 >> 2] = 0;c[d >> 2] = c[ea >> 2];c[d + 4 >> 2] = c[ea + 4 >> 2];Mh(a, 4065, d) | 0;c[da >> 2] = 12;c[da + 4 >> 2] = 0;c[d >> 2] = c[da >> 2];c[d + 4 >> 2] = c[da + 4 >> 2];Mh(a, 4084, d) | 0;c[ca >> 2] = 13;c[ca + 4 >> 2] = 0;c[d >> 2] = c[ca >> 2];c[d + 4 >> 2] = c[ca + 4 >> 2];Mh(a, 4097, d) | 0;c[ba >> 2] = 14;c[ba + 4 >> 2] = 0;c[d >> 2] = c[ba >> 2];c[d + 4 >> 2] = c[ba + 4 >> 2];Mh(a, 4117, d) | 0;c[aa >> 2] = 15;c[aa + 4 >> 2] = 0;c[d >> 2] = c[aa >> 2];c[d + 4 >> 2] = c[aa + 4 >> 2];Mh(a, 4129, d) | 0;c[$ >> 2] = 16;c[$ + 4 >> 2] = 0;c[d >> 2] = c[$ >> 2];c[d + 4 >> 2] = c[$ + 4 >> 2];Mh(a, 4148, d) | 0;c[_ >> 2] = 17;c[_ + 4 >> 2] = 0;c[d >> 2] = c[_ >> 2];c[d + 4 >> 2] = c[_ + 4 >> 2];Mh(a, 4161, d) | 0;c[Z >> 2] = 18;c[Z + 4 >> 2] = 0;c[d >> 2] = c[Z >> 2];c[d + 4 >> 2] = c[Z + 4 >> 2];Mh(a, 4181, d) | 0;c[Y >> 2] = 5;c[Y + 4 >> 2] = 0;c[d >> 2] = c[Y >> 2];c[d + 4 >> 2] = c[Y + 4 >> 2];Lh(a, 4196, d) | 0;c[X >> 2] = 6;c[X + 4 >> 2] = 0;c[d >> 2] = c[X >> 2];c[d + 4 >> 2] = c[X + 4 >> 2];Lh(a, 4206, d) | 0;c[W >> 2] = 7;c[W + 4 >> 2] = 0;c[d >> 2] = c[W >> 2];c[d + 4 >> 2] = c[W + 4 >> 2];Lh(a, 4217, d) | 0;c[V >> 2] = 3;c[V + 4 >> 2] = 0;c[d >> 2] = c[V >> 2];c[d + 4 >> 2] = c[V + 4 >> 2];Nh(a, 4235, d) | 0;c[U >> 2] = 1;c[U + 4 >> 2] = 0;c[d >> 2] = c[U >> 2];c[d + 4 >> 2] = c[U + 4 >> 2];Oh(a, 4251, d) | 0;c[T >> 2] = 4;c[T + 4 >> 2] = 0;c[d >> 2] = c[T >> 2];c[d + 4 >> 2] = c[T + 4 >> 2];Nh(a, 4263, d) | 0;c[S >> 2] = 5;c[S + 4 >> 2] = 0;c[d >> 2] = c[S >> 2];c[d + 4 >> 2] = c[S + 4 >> 2];Nh(a, 4279, d) | 0;c[R >> 2] = 6;c[R + 4 >> 2] = 0;c[d >> 2] = c[R >> 2];c[d + 4 >> 2] = c[R + 4 >> 2];Nh(a, 4293, d) | 0;c[Q >> 2] = 7;c[Q + 4 >> 2] = 0;c[d >> 2] = c[Q >> 2];c[d + 4 >> 2] = c[Q + 4 >> 2];Nh(a, 4306, d) | 0;c[P >> 2] = 8;c[P + 4 >> 2] = 0;c[d >> 2] = c[P >> 2];c[d + 4 >> 2] = c[P + 4 >> 2];Nh(a, 4323, d) | 0;c[O >> 2] = 9;c[O + 4 >> 2] = 0;c[d >> 2] = c[O >> 2];c[d + 4 >> 2] = c[O + 4 >> 2];Nh(a, 4335, d) | 0;c[N >> 2] = 2;c[N + 4 >> 2] = 0;c[d >> 2] = c[N >> 2];c[d + 4 >> 2] = c[N + 4 >> 2];Oh(a, 4353, d) | 0;c[M >> 2] = 12;c[M + 4 >> 2] = 0;c[d >> 2] = c[M >> 2];c[d + 4 >> 2] = c[M + 4 >> 2];Ph(a, 4363, d) | 0;c[L >> 2] = 1;c[L + 4 >> 2] = 0;c[d >> 2] = c[L >> 2];c[d + 4 >> 2] = c[L + 4 >> 2];Qh(a, 4376, d) | 0;c[K >> 2] = 2;c[K + 4 >> 2] = 0;c[d >> 2] = c[K >> 2];c[d + 4 >> 2] = c[K + 4 >> 2];Qh(a, 4388, d) | 0;c[J >> 2] = 13;c[J + 4 >> 2] = 0;c[d >> 2] = c[J >> 2];c[d + 4 >> 2] = c[J + 4 >> 2];Ph(a, 4402, d) | 0;c[I >> 2] = 14;c[I + 4 >> 2] = 0;c[d >> 2] = c[I >> 2];c[d + 4 >> 2] = c[I + 4 >> 2];Ph(a, 4411, d) | 0;c[H >> 2] = 15;c[H + 4 >> 2] = 0;c[d >> 2] = c[H >> 2];c[d + 4 >> 2] = c[H + 4 >> 2];Ph(a, 4421, d) | 0;c[G >> 2] = 16;c[G + 4 >> 2] = 0;c[d >> 2] = c[G >> 2];c[d + 4 >> 2] = c[G + 4 >> 2];Ph(a, 4433, d) | 0;c[F >> 2] = 17;c[F + 4 >> 2] = 0;c[d >> 2] = c[F >> 2];c[d + 4 >> 2] = c[F + 4 >> 2];Ph(a, 4446, d) | 0;c[E >> 2] = 18;c[E + 4 >> 2] = 0;c[d >> 2] = c[E >> 2];c[d + 4 >> 2] = c[E + 4 >> 2];Ph(a, 4458, d) | 0;c[D >> 2] = 3;c[D + 4 >> 2] = 0;c[d >> 2] = c[D >> 2];c[d + 4 >> 2] = c[D + 4 >> 2];Qh(a, 4471, d) | 0;c[C >> 2] = 1;c[C + 4 >> 2] = 0;c[d >> 2] = c[C >> 2];c[d + 4 >> 2] = c[C + 4 >> 2];Rh(a, 4486, d) | 0;c[B >> 2] = 10;c[B + 4 >> 2] = 0;c[d >> 2] = c[B >> 2];c[d + 4 >> 2] = c[B + 4 >> 2];Nh(a, 4496, d) | 0;c[A >> 2] = 11;c[A + 4 >> 2] = 0;c[d >> 2] = c[A >> 2];c[d + 4 >> 2] = c[A + 4 >> 2];Nh(a, 4508, d) | 0;c[z >> 2] = 3;c[z + 4 >> 2] = 0;c[d >> 2] = c[z >> 2];c[d + 4 >> 2] = c[z + 4 >> 2];Oh(a, 4519, d) | 0;c[y >> 2] = 4;c[y + 4 >> 2] = 0;c[d >> 2] = c[y >> 2];c[d + 4 >> 2] = c[y + 4 >> 2];Sh(a, 4530, d) | 0;c[x >> 2] = 19;c[x + 4 >> 2] = 0;c[d >> 2] = c[x >> 2];c[d + 4 >> 2] = c[x + 4 >> 2];Th(a, 4542, d) | 0;c[w >> 2] = 12;c[w + 4 >> 2] = 0;c[d >> 2] = c[w >> 2];c[d + 4 >> 2] = c[w + 4 >> 2];Uh(a, 4554, d) | 0;c[v >> 2] = 13;c[v + 4 >> 2] = 0;c[d >> 2] = c[v >> 2];c[d + 4 >> 2] = c[v + 4 >> 2];Vh(a, 4568, d) | 0;c[u >> 2] = 2;c[u + 4 >> 2] = 0;c[d >> 2] = c[u >> 2];c[d + 4 >> 2] = c[u + 4 >> 2];Wh(a, 4578, d) | 0;c[t >> 2] = 20;c[t + 4 >> 2] = 0;c[d >> 2] = c[t >> 2];c[d + 4 >> 2] = c[t + 4 >> 2];Xh(a, 4587, d) | 0;c[s >> 2] = 22;c[s + 4 >> 2] = 0;c[d >> 2] = c[s >> 2];c[d + 4 >> 2] = c[s + 4 >> 2];Ih(a, 4602, d) | 0;c[r >> 2] = 23;c[r + 4 >> 2] = 0;c[d >> 2] = c[r >> 2];c[d + 4 >> 2] = c[r + 4 >> 2];Ih(a, 4619, d) | 0;c[q >> 2] = 14;c[q + 4 >> 2] = 0;c[d >> 2] = c[q >> 2];c[d + 4 >> 2] = c[q + 4 >> 2];Yh(a, 4629, d) | 0;c[p >> 2] = 1;c[p + 4 >> 2] = 0;c[d >> 2] = c[p >> 2];c[d + 4 >> 2] = c[p + 4 >> 2];Zh(a, 4637, d) | 0;c[o >> 2] = 4;c[o + 4 >> 2] = 0;c[d >> 2] = c[o >> 2];c[d + 4 >> 2] = c[o + 4 >> 2];Qh(a, 4653, d) | 0;c[n >> 2] = 5;c[n + 4 >> 2] = 0;c[d >> 2] = c[n >> 2];c[d + 4 >> 2] = c[n + 4 >> 2];Qh(a, 4669, d) | 0;c[m >> 2] = 6;c[m + 4 >> 2] = 0;c[d >> 2] = c[m >> 2];c[d + 4 >> 2] = c[m + 4 >> 2];Qh(a, 4686, d) | 0;c[k >> 2] = 7;c[k + 4 >> 2] = 0;c[d >> 2] = c[k >> 2];c[d + 4 >> 2] = c[k + 4 >> 2];Qh(a, 4701, d) | 0;c[j >> 2] = 8;c[j + 4 >> 2] = 0;c[d >> 2] = c[j >> 2];c[d + 4 >> 2] = c[j + 4 >> 2];Qh(a, 4719, d) | 0;c[i >> 2] = 9;c[i + 4 >> 2] = 0;c[d >> 2] = c[i >> 2];c[d + 4 >> 2] = c[i + 4 >> 2];Qh(a, 4736, d) | 0;c[h >> 2] = 21;c[h + 4 >> 2] = 0;c[d >> 2] = c[h >> 2];c[d + 4 >> 2] = c[h + 4 >> 2];_h(a, 4754, d) | 0;c[g >> 2] = 2;c[g + 4 >> 2] = 0;c[d >> 2] = c[g >> 2];c[d + 4 >> 2] = c[g + 4 >> 2];Rh(a, 4772, d) | 0;c[f >> 2] = 3;c[f + 4 >> 2] = 0;c[d >> 2] = c[f >> 2];c[d + 4 >> 2] = c[f + 4 >> 2];Rh(a, 4790, d) | 0;c[e >> 2] = 4;c[e + 4 >> 2] = 0;c[d >> 2] = c[e >> 2];c[d + 4 >> 2] = c[e + 4 >> 2];Rh(a, 4808, d) | 0;l = b;return;
+    }function Eh(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = dr() | 0;c[a >> 2] = d;er(d, b);Hv(c[a >> 2] | 0);return;
+    }function Fh(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;Oq(a, ai(b) | 0, c, 0);return a | 0;
+    }function Gh(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;tq(a, ai(b) | 0, c, 0);return a | 0;
+    }function Hh(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;cq(a, ai(b) | 0, c, 0);return a | 0;
+    }function Ih(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Lp(a, b, f);l = e;return a | 0;
+    }function Jh(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];pp(a, b, f);l = e;return a | 0;
+    }function Kh(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Yo(a, b, f);l = e;return a | 0;
+    }function Lh(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Fo(a, b, f);l = e;return a | 0;
+    }function Mh(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];mo(a, b, f);l = e;return a | 0;
+    }function Nh(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Un(a, b, f);l = e;return a | 0;
+    }function Oh(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Bn(a, b, f);l = e;return a | 0;
+    }function Ph(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Um(a, b, f);l = e;return a | 0;
+    }function Qh(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Bm(a, b, f);l = e;return a | 0;
+    }function Rh(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];im(a, b, f);l = e;return a | 0;
+    }function Sh(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Rl(a, b, f);l = e;return a | 0;
+    }function Th(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];vl(a, b, f);l = e;return a | 0;
+    }function Uh(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];bl(a, b, f);l = e;return a | 0;
+    }function Vh(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Kk(a, b, f);l = e;return a | 0;
+    }function Wh(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];nk(a, b, f);l = e;return a | 0;
+    }function Xh(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Rj(a, b, f);l = e;return a | 0;
+    }function Yh(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];xj(a, b, f);l = e;return a | 0;
+    }function Zh(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];_i(a, b, f);l = e;return a | 0;
+    }function _h(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];$h(a, b, f);l = e;return a | 0;
+    }function $h(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];bi(a, d, f, 1);l = e;return;
+    }function ai(a) {
+      a = a | 0;return a | 0;
+    }function bi(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = ci() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = di(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, ei(g, e) | 0, e);l = f;return;
+    }function ci() {
+      var b = 0,
+          d = 0;if (!(a[7616] | 0)) {
+        qi(9136);Ha(24, 9136, o | 0) | 0;d = 7616;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9136) | 0)) {
+        b = 9136;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));qi(9136);
+      }return 9136;
+    }function di(a) {
+      a = a | 0;return 0;
+    }function ei(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = ci() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];ki(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        li(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function fi(a, b, d, e, f, g) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;f = f | 0;g = g | 0;var h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0,
+          p = 0;h = l;l = l + 32 | 0;o = h + 24 | 0;n = h + 20 | 0;j = h + 16 | 0;m = h + 12 | 0;k = h + 8 | 0;i = h + 4 | 0;p = h;c[n >> 2] = b;c[j >> 2] = d;c[m >> 2] = e;c[k >> 2] = f;c[i >> 2] = g;g = a + 28 | 0;c[p >> 2] = c[g >> 2];c[o >> 2] = c[p >> 2];gi(a + 24 | 0, o, n, m, k, j, i) | 0;c[g >> 2] = c[c[g >> 2] >> 2];l = h;return;
+    }function gi(a, b, d, e, f, g, h) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;f = f | 0;g = g | 0;h = h | 0;a = hi(b) | 0;b = qC(24) | 0;ii(b + 4 | 0, c[d >> 2] | 0, c[e >> 2] | 0, c[f >> 2] | 0, c[g >> 2] | 0, c[h >> 2] | 0);c[b >> 2] = c[a >> 2];c[a >> 2] = b;return b | 0;
+    }function hi(a) {
+      a = a | 0;return c[a >> 2] | 0;
+    }function ii(a, b, d, e, f, g) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;f = f | 0;g = g | 0;c[a >> 2] = b;c[a + 4 >> 2] = d;c[a + 8 >> 2] = e;c[a + 12 >> 2] = f;c[a + 16 >> 2] = g;return;
+    }function ji(a, b) {
+      a = a | 0;b = b | 0;return b | a | 0;
+    }function ki(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function li(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = mi(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;ni(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];ki(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;oi(a, i);pi(i);l = k;return;
+      }
+    }function mi(a) {
+      a = a | 0;return 357913941;
+    }function ni(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function oi(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function pi(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function qi(a) {
+      a = a | 0;ui(a);return;
+    }function ri(a) {
+      a = a | 0;ti(a + 24 | 0);return;
+    }function si(a) {
+      a = a | 0;return c[a >> 2] | 0;
+    }function ti(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function ui(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 3, b, wi() | 0, 0);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function vi() {
+      return 9228;
+    }function wi() {
+      return 1140;
+    }function xi(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0;d = l;l = l + 16 | 0;e = d + 8 | 0;f = d;g = zi(a) | 0;a = c[g + 4 >> 2] | 0;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = a;c[e >> 2] = c[f >> 2];c[e + 4 >> 2] = c[f + 4 >> 2];b = Ai(b, e) | 0;l = d;return b | 0;
+    }function yi(a, b, d, e, f, g) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;f = f | 0;g = g | 0;c[a >> 2] = b;c[a + 4 >> 2] = d;c[a + 8 >> 2] = e;c[a + 12 >> 2] = f;c[a + 16 >> 2] = g;return;
+    }function zi(a) {
+      a = a | 0;return (c[(ci() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function Ai(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0;f = l;l = l + 48 | 0;e = f;d = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) d = c[(c[a >> 2] | 0) + d >> 2] | 0;ob[d & 31](e, a);e = Bi(e) | 0;l = f;return e | 0;
+    }function Bi(a) {
+      a = a | 0;var b = 0,
+          c = 0,
+          d = 0,
+          e = 0;e = l;l = l + 32 | 0;b = e + 12 | 0;c = e;d = Di(Ci() | 0) | 0;if (!d) a = Ii(a) | 0;else {
+        Ei(b, d);Fi(c, b);Gi(a, c);a = Hi(b) | 0;
+      }l = e;return a | 0;
+    }function Ci() {
+      var b = 0;if (!(a[7632] | 0)) {
+        Ti(9184);Ha(25, 9184, o | 0) | 0;b = 7632;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 9184;
+    }function Di(a) {
+      a = a | 0;return c[a + 36 >> 2] | 0;
+    }function Ei(a, b) {
+      a = a | 0;b = b | 0;c[a >> 2] = b;c[a + 4 >> 2] = a;c[a + 8 >> 2] = 0;return;
+    }function Fi(a, b) {
+      a = a | 0;b = b | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = c[b + 4 >> 2];c[a + 8 >> 2] = 0;return;
+    }function Gi(a, b) {
+      a = a | 0;b = b | 0;Ni(b, a, a + 8 | 0, a + 16 | 0, a + 24 | 0, a + 32 | 0, a + 40 | 0) | 0;return;
+    }function Hi(a) {
+      a = a | 0;return c[(c[a + 4 >> 2] | 0) + 8 >> 2] | 0;
+    }function Ii(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0;j = l;l = l + 16 | 0;d = j + 4 | 0;e = j;f = jy(8) | 0;g = f;h = qC(48) | 0;i = h;b = i + 48 | 0;do {
+        c[i >> 2] = c[a >> 2];i = i + 4 | 0;a = a + 4 | 0;
+      } while ((i | 0) < (b | 0));b = g + 4 | 0;c[b >> 2] = h;i = qC(8) | 0;h = c[b >> 2] | 0;c[e >> 2] = 0;c[d >> 2] = c[e >> 2];Ji(i, h, d);c[f >> 2] = i;l = j;return g | 0;
+    }function Ji(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;c[a >> 2] = b;d = qC(16) | 0;c[d + 4 >> 2] = 0;c[d + 8 >> 2] = 0;c[d >> 2] = 1092;c[d + 12 >> 2] = b;c[a + 4 >> 2] = d;return;
+    }function Ki(a) {
+      a = a | 0;kC(a);sC(a);return;
+    }function Li(a) {
+      a = a | 0;a = c[a + 12 >> 2] | 0;if (a | 0) sC(a);return;
+    }function Mi(a) {
+      a = a | 0;sC(a);return;
+    }function Ni(a, b, d, e, f, g, h) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;f = f | 0;g = g | 0;h = h | 0;g = Oi(c[a >> 2] | 0, b, d, e, f, g, h) | 0;h = a + 4 | 0;c[(c[h >> 2] | 0) + 8 >> 2] = g;return c[(c[h >> 2] | 0) + 8 >> 2] | 0;
+    }function Oi(a, b, c, d, e, f, g) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;e = e | 0;f = f | 0;g = g | 0;var i = 0,
+          j = 0;i = l;l = l + 16 | 0;j = i;UA(j);a = Sg(a) | 0;g = Pi(a, +h[b >> 3], +h[c >> 3], +h[d >> 3], +h[e >> 3], +h[f >> 3], +h[g >> 3]) | 0;WA(j);l = i;return g | 0;
+    }function Pi(a, b, c, d, e, f, g) {
+      a = a | 0;b = +b;c = +c;d = +d;e = +e;f = +f;g = +g;var h = 0;h = Vg(Qi() | 0) | 0;b = +Wg(b);c = +Wg(c);d = +Wg(d);e = +Wg(e);f = +Wg(f);return ya(0, h | 0, a | 0, +b, +c, +d, +e, +f, + +Wg(g)) | 0;
+    }function Qi() {
+      var b = 0;if (!(a[7624] | 0)) {
+        Ri(9172);b = 7624;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 9172;
+    }function Ri(a) {
+      a = a | 0;fh(a, Si() | 0, 6);return;
+    }function Si() {
+      return 1112;
+    }function Ti(a) {
+      a = a | 0;Zi(a);return;
+    }function Ui(a) {
+      a = a | 0;Vi(a + 24 | 0);Wi(a + 16 | 0);return;
+    }function Vi(a) {
+      a = a | 0;Yi(a);return;
+    }function Wi(a) {
+      a = a | 0;Xi(a);return;
+    }function Xi(a) {
+      a = a | 0;var b = 0,
+          d = 0;b = c[a >> 2] | 0;if (b | 0) do {
+        d = b;b = c[b >> 2] | 0;sC(d);
+      } while ((b | 0) != 0);c[a >> 2] = 0;return;
+    }function Yi(a) {
+      a = a | 0;var b = 0,
+          d = 0;b = c[a >> 2] | 0;if (b | 0) do {
+        d = b;b = c[b >> 2] | 0;sC(d);
+      } while ((b | 0) != 0);c[a >> 2] = 0;return;
+    }function Zi(b) {
+      b = b | 0;var d = 0;c[b + 16 >> 2] = 0;c[b + 20 >> 2] = 0;d = b + 24 | 0;c[d >> 2] = 0;c[b + 28 >> 2] = d;c[b + 36 >> 2] = 0;a[b + 40 >> 0] = 0;a[b + 41 >> 0] = 0;return;
+    }function _i(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];$i(a, d, f, 0);l = e;return;
+    }function $i(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = aj() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = bj(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, cj(g, e) | 0, e);l = f;return;
+    }function aj() {
+      var b = 0,
+          d = 0;if (!(a[7640] | 0)) {
+        jj(9232);Ha(26, 9232, o | 0) | 0;d = 7640;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9232) | 0)) {
+        b = 9232;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));jj(9232);
+      }return 9232;
+    }function bj(a) {
+      a = a | 0;return 0;
+    }function cj(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = aj() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];dj(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        ej(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function dj(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function ej(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = fj(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;gj(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];dj(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;hj(a, i);ij(i);l = k;return;
+      }
+    }function fj(a) {
+      a = a | 0;return 357913941;
+    }function gj(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function hj(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function ij(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function jj(a) {
+      a = a | 0;mj(a);return;
+    }function kj(a) {
+      a = a | 0;lj(a + 24 | 0);return;
+    }function lj(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function mj(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 1, b, nj() | 0, 3);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function nj() {
+      return 1144;
+    }function oj(a, b, d, e, f) {
+      a = a | 0;b = b | 0;d = +d;e = +e;f = f | 0;var g = 0,
+          h = 0,
+          i = 0,
+          j = 0;g = l;l = l + 16 | 0;h = g + 8 | 0;i = g;j = pj(a) | 0;a = c[j + 4 >> 2] | 0;c[i >> 2] = c[j >> 2];c[i + 4 >> 2] = a;c[h >> 2] = c[i >> 2];c[h + 4 >> 2] = c[i + 4 >> 2];qj(b, h, d, e, f);l = g;return;
+    }function pj(a) {
+      a = a | 0;return (c[(aj() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function qj(a, b, d, e, f) {
+      a = a | 0;b = b | 0;d = +d;e = +e;f = f | 0;var g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0;k = l;l = l + 16 | 0;h = k + 2 | 0;i = k + 1 | 0;j = k;g = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) g = c[(c[a >> 2] | 0) + g >> 2] | 0;d = +sj(h, d);e = +sj(i, e);j = uj(j, f) | 0;qb[g & 1](a, d, e, j);l = k;return;
+    }function sj(a, b) {
+      a = a | 0;b = +b;return + +wj(b);
+    }function uj(a, b) {
+      a = a | 0;b = b | 0;return vj(b) | 0;
+    }function vj(a) {
+      a = a | 0;return a | 0;
+    }function wj(a) {
+      a = +a;return +a;
+    }function xj(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];yj(a, d, f, 1);l = e;return;
+    }function yj(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = zj() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = Aj(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, Bj(g, e) | 0, e);l = f;return;
+    }function zj() {
+      var b = 0,
+          d = 0;if (!(a[7648] | 0)) {
+        Ij(9268);Ha(27, 9268, o | 0) | 0;d = 7648;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9268) | 0)) {
+        b = 9268;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));Ij(9268);
+      }return 9268;
+    }function Aj(a) {
+      a = a | 0;return 0;
+    }function Bj(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = zj() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];Cj(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        Dj(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function Cj(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function Dj(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = Ej(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;Fj(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];Cj(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;Gj(a, i);Hj(i);l = k;return;
+      }
+    }function Ej(a) {
+      a = a | 0;return 357913941;
+    }function Fj(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function Gj(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function Hj(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function Ij(a) {
+      a = a | 0;Lj(a);return;
+    }function Jj(a) {
+      a = a | 0;Kj(a + 24 | 0);return;
+    }function Kj(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function Lj(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 4, b, Mj() | 0, 0);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function Mj() {
+      return 1160;
+    }function Nj(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0;d = l;l = l + 16 | 0;e = d + 8 | 0;f = d;g = Oj(a) | 0;a = c[g + 4 >> 2] | 0;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = a;c[e >> 2] = c[f >> 2];c[e + 4 >> 2] = c[f + 4 >> 2];b = Pj(b, e) | 0;l = d;return b | 0;
+    }function Oj(a) {
+      a = a | 0;return (c[(zj() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function Pj(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) d = c[(c[a >> 2] | 0) + d >> 2] | 0;return Qj(pb[d & 31](a) | 0) | 0;
+    }function Qj(a) {
+      a = a | 0;return a & 1 | 0;
+    }function Rj(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Sj(a, d, f, 0);l = e;return;
+    }function Sj(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = Tj() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = Uj(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, Vj(g, e) | 0, e);l = f;return;
+    }function Tj() {
+      var b = 0,
+          d = 0;if (!(a[7656] | 0)) {
+        ak(9304);Ha(28, 9304, o | 0) | 0;d = 7656;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9304) | 0)) {
+        b = 9304;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));ak(9304);
+      }return 9304;
+    }function Uj(a) {
+      a = a | 0;return 0;
+    }function Vj(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = Tj() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];Wj(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        Xj(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function Wj(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function Xj(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = Yj(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;Zj(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];Wj(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;_j(a, i);$j(i);l = k;return;
+      }
+    }function Yj(a) {
+      a = a | 0;return 357913941;
+    }function Zj(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function _j(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function $j(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function ak(a) {
+      a = a | 0;dk(a);return;
+    }function bk(a) {
+      a = a | 0;ck(a + 24 | 0);return;
+    }function ck(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function dk(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 5, b, ek() | 0, 1);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function ek() {
+      return 1164;
+    }function fk(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = gk(a) | 0;a = c[h + 4 >> 2] | 0;c[g >> 2] = c[h >> 2];c[g + 4 >> 2] = a;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];hk(b, f, d);l = e;return;
+    }function gk(a) {
+      a = a | 0;return (c[(Tj() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function hk(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0;g = l;l = l + 16 | 0;f = g;e = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) e = c[(c[a >> 2] | 0) + e >> 2] | 0;ik(f, d);d = jk(f, d) | 0;ob[e & 31](a, d);kk(f);l = g;return;
+    }function ik(a, b) {
+      a = a | 0;b = b | 0;lk(a, b);return;
+    }function jk(a, b) {
+      a = a | 0;b = b | 0;return a | 0;
+    }function kk(a) {
+      a = a | 0;vf(a);return;
+    }function lk(a, b) {
+      a = a | 0;b = b | 0;mk(a, b);return;
+    }function mk(a, b) {
+      a = a | 0;b = b | 0;c[a >> 2] = b;return;
+    }function nk(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];ok(a, d, f, 0);l = e;return;
+    }function ok(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = pk() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = qk(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, rk(g, e) | 0, e);l = f;return;
+    }function pk() {
+      var b = 0,
+          d = 0;if (!(a[7664] | 0)) {
+        yk(9340);Ha(29, 9340, o | 0) | 0;d = 7664;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9340) | 0)) {
+        b = 9340;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));yk(9340);
+      }return 9340;
+    }function qk(a) {
+      a = a | 0;return 0;
+    }function rk(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = pk() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];sk(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        tk(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function sk(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function tk(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = uk(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;vk(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];sk(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;wk(a, i);xk(i);l = k;return;
+      }
+    }function uk(a) {
+      a = a | 0;return 357913941;
+    }function vk(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function wk(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function xk(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function yk(a) {
+      a = a | 0;Bk(a);return;
+    }function zk(a) {
+      a = a | 0;Ak(a + 24 | 0);return;
+    }function Ak(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function Bk(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 4, b, Ck() | 0, 1);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function Ck() {
+      return 1180;
+    }function Dk(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = Ek(a) | 0;a = c[h + 4 >> 2] | 0;c[g >> 2] = c[h >> 2];c[g + 4 >> 2] = a;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];d = Fk(b, f, d) | 0;l = e;return d | 0;
+    }function Ek(a) {
+      a = a | 0;return (c[(pk() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function Fk(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0;g = l;l = l + 16 | 0;f = g;e = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) e = c[(c[a >> 2] | 0) + e >> 2] | 0;f = Hk(f, d) | 0;f = Ik(wb[e & 15](a, f) | 0) | 0;l = g;return f | 0;
+    }function Hk(a, b) {
+      a = a | 0;b = b | 0;return Jk(b) | 0;
+    }function Ik(a) {
+      a = a | 0;return a | 0;
+    }function Jk(a) {
+      a = a | 0;return a | 0;
+    }function Kk(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Lk(a, d, f, 0);l = e;return;
+    }function Lk(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = Mk() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = Nk(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, Ok(g, e) | 0, e);l = f;return;
+    }function Mk() {
+      var b = 0,
+          d = 0;if (!(a[7672] | 0)) {
+        Vk(9376);Ha(30, 9376, o | 0) | 0;d = 7672;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9376) | 0)) {
+        b = 9376;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));Vk(9376);
+      }return 9376;
+    }function Nk(a) {
+      a = a | 0;return 0;
+    }function Ok(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = Mk() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];Pk(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        Qk(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function Pk(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function Qk(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = Rk(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;Sk(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];Pk(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;Tk(a, i);Uk(i);l = k;return;
+      }
+    }function Rk(a) {
+      a = a | 0;return 357913941;
+    }function Sk(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function Tk(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function Uk(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function Vk(a) {
+      a = a | 0;Yk(a);return;
+    }function Wk(a) {
+      a = a | 0;Xk(a + 24 | 0);return;
+    }function Xk(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function Yk(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 5, b, Zk() | 0, 0);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function Zk() {
+      return 1196;
+    }function _k(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0;d = l;l = l + 16 | 0;e = d + 8 | 0;f = d;g = $k(a) | 0;a = c[g + 4 >> 2] | 0;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = a;c[e >> 2] = c[f >> 2];c[e + 4 >> 2] = c[f + 4 >> 2];b = al(b, e) | 0;l = d;return b | 0;
+    }function $k(a) {
+      a = a | 0;return (c[(Mk() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function al(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) d = c[(c[a >> 2] | 0) + d >> 2] | 0;return Ik(pb[d & 31](a) | 0) | 0;
+    }function bl(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];cl(a, d, f, 1);l = e;return;
+    }function cl(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = dl() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = el(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, fl(g, e) | 0, e);l = f;return;
+    }function dl() {
+      var b = 0,
+          d = 0;if (!(a[7680] | 0)) {
+        ml(9412);Ha(31, 9412, o | 0) | 0;d = 7680;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9412) | 0)) {
+        b = 9412;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));ml(9412);
+      }return 9412;
+    }function el(a) {
+      a = a | 0;return 0;
+    }function fl(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = dl() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];gl(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        hl(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function gl(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function hl(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = il(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;jl(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];gl(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;kl(a, i);ll(i);l = k;return;
+      }
+    }function il(a) {
+      a = a | 0;return 357913941;
+    }function jl(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function kl(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function ll(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function ml(a) {
+      a = a | 0;pl(a);return;
+    }function nl(a) {
+      a = a | 0;ol(a + 24 | 0);return;
+    }function ol(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function pl(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 6, b, ql() | 0, 0);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function ql() {
+      return 1200;
+    }function rl(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0;d = l;l = l + 16 | 0;e = d + 8 | 0;f = d;g = sl(a) | 0;a = c[g + 4 >> 2] | 0;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = a;c[e >> 2] = c[f >> 2];c[e + 4 >> 2] = c[f + 4 >> 2];b = tl(b, e) | 0;l = d;return b | 0;
+    }function sl(a) {
+      a = a | 0;return (c[(dl() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function tl(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) d = c[(c[a >> 2] | 0) + d >> 2] | 0;return ul(pb[d & 31](a) | 0) | 0;
+    }function ul(a) {
+      a = a | 0;return a | 0;
+    }function vl(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];wl(a, d, f, 0);l = e;return;
+    }function wl(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = xl() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = yl(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, zl(g, e) | 0, e);l = f;return;
+    }function xl() {
+      var b = 0,
+          d = 0;if (!(a[7688] | 0)) {
+        Gl(9448);Ha(32, 9448, o | 0) | 0;d = 7688;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9448) | 0)) {
+        b = 9448;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));Gl(9448);
+      }return 9448;
+    }function yl(a) {
+      a = a | 0;return 0;
+    }function zl(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = xl() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];Al(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        Bl(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function Al(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function Bl(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = Cl(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;Dl(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];Al(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;El(a, i);Fl(i);l = k;return;
+      }
+    }function Cl(a) {
+      a = a | 0;return 357913941;
+    }function Dl(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function El(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function Fl(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function Gl(a) {
+      a = a | 0;Jl(a);return;
+    }function Hl(a) {
+      a = a | 0;Il(a + 24 | 0);return;
+    }function Il(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function Jl(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 6, b, Kl() | 0, 1);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function Kl() {
+      return 1204;
+    }function Ll(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = Ml(a) | 0;a = c[h + 4 >> 2] | 0;c[g >> 2] = c[h >> 2];c[g + 4 >> 2] = a;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Nl(b, f, d);l = e;return;
+    }function Ml(a) {
+      a = a | 0;return (c[(xl() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function Nl(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0;g = l;l = l + 16 | 0;f = g;e = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) e = c[(c[a >> 2] | 0) + e >> 2] | 0;f = Pl(f, d) | 0;ob[e & 31](a, f);l = g;return;
+    }function Pl(a, b) {
+      a = a | 0;b = b | 0;return Ql(b) | 0;
+    }function Ql(a) {
+      a = a | 0;return a | 0;
+    }function Rl(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Sl(a, d, f, 0);l = e;return;
+    }function Sl(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = Tl() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = Ul(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, Vl(g, e) | 0, e);l = f;return;
+    }function Tl() {
+      var b = 0,
+          d = 0;if (!(a[7696] | 0)) {
+        am(9484);Ha(33, 9484, o | 0) | 0;d = 7696;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9484) | 0)) {
+        b = 9484;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));am(9484);
+      }return 9484;
+    }function Ul(a) {
+      a = a | 0;return 0;
+    }function Vl(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = Tl() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];Wl(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        Xl(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function Wl(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function Xl(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = Yl(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;Zl(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];Wl(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;_l(a, i);$l(i);l = k;return;
+      }
+    }function Yl(a) {
+      a = a | 0;return 357913941;
+    }function Zl(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function _l(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function $l(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function am(a) {
+      a = a | 0;dm(a);return;
+    }function bm(a) {
+      a = a | 0;cm(a + 24 | 0);return;
+    }function cm(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function dm(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 1, b, em() | 0, 2);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function em() {
+      return 1212;
+    }function fm(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0;f = l;l = l + 16 | 0;g = f + 8 | 0;h = f;i = gm(a) | 0;a = c[i + 4 >> 2] | 0;c[h >> 2] = c[i >> 2];c[h + 4 >> 2] = a;c[g >> 2] = c[h >> 2];c[g + 4 >> 2] = c[h + 4 >> 2];hm(b, g, d, e);l = f;return;
+    }function gm(a) {
+      a = a | 0;return (c[(Tl() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function hm(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0;i = l;l = l + 16 | 0;g = i + 1 | 0;h = i;f = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) f = c[(c[a >> 2] | 0) + f >> 2] | 0;g = Pl(g, d) | 0;h = Hk(h, e) | 0;Eb[f & 15](a, g, h);l = i;return;
+    }function im(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];jm(a, d, f, 1);l = e;return;
+    }function jm(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = km() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = lm(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, mm(g, e) | 0, e);l = f;return;
+    }function km() {
+      var b = 0,
+          d = 0;if (!(a[7704] | 0)) {
+        tm(9520);Ha(34, 9520, o | 0) | 0;d = 7704;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9520) | 0)) {
+        b = 9520;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));tm(9520);
+      }return 9520;
+    }function lm(a) {
+      a = a | 0;return 0;
+    }function mm(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = km() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];nm(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        om(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function nm(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function om(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = pm(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;qm(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];nm(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;rm(a, i);sm(i);l = k;return;
+      }
+    }function pm(a) {
+      a = a | 0;return 357913941;
+    }function qm(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function rm(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function sm(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function tm(a) {
+      a = a | 0;wm(a);return;
+    }function um(a) {
+      a = a | 0;vm(a + 24 | 0);return;
+    }function vm(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function wm(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 1, b, xm() | 0, 1);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function xm() {
+      return 1224;
+    }function ym(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0.0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;f = l;l = l + 16 | 0;g = f + 8 | 0;h = f;i = zm(a) | 0;a = c[i + 4 >> 2] | 0;c[h >> 2] = c[i >> 2];c[h + 4 >> 2] = a;c[g >> 2] = c[h >> 2];c[g + 4 >> 2] = c[h + 4 >> 2];e = +Am(b, g, d);l = f;return +e;
+    }function zm(a) {
+      a = a | 0;return (c[(km() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function Am(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0.0;g = l;l = l + 16 | 0;f = g;e = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) e = c[(c[a >> 2] | 0) + e >> 2] | 0;f = uj(f, d) | 0;h = +ch(+zb[e & 7](a, f));l = g;return +h;
+    }function Bm(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Cm(a, d, f, 1);l = e;return;
+    }function Cm(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = Dm() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = Em(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, Fm(g, e) | 0, e);l = f;return;
+    }function Dm() {
+      var b = 0,
+          d = 0;if (!(a[7712] | 0)) {
+        Mm(9556);Ha(35, 9556, o | 0) | 0;d = 7712;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9556) | 0)) {
+        b = 9556;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));Mm(9556);
+      }return 9556;
+    }function Em(a) {
+      a = a | 0;return 0;
+    }function Fm(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = Dm() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];Gm(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        Hm(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function Gm(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function Hm(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = Im(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;Jm(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];Gm(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;Km(a, i);Lm(i);l = k;return;
+      }
+    }function Im(a) {
+      a = a | 0;return 357913941;
+    }function Jm(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function Km(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function Lm(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function Mm(a) {
+      a = a | 0;Pm(a);return;
+    }function Nm(a) {
+      a = a | 0;Om(a + 24 | 0);return;
+    }function Om(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function Pm(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 5, b, Qm() | 0, 0);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function Qm() {
+      return 1232;
+    }function Rm(a, b) {
+      a = a | 0;b = b | 0;var d = 0.0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = Sm(a) | 0;a = c[h + 4 >> 2] | 0;c[g >> 2] = c[h >> 2];c[g + 4 >> 2] = a;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];d = +Tm(b, f);l = e;return +d;
+    }function Sm(a) {
+      a = a | 0;return (c[(Dm() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function Tm(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) d = c[(c[a >> 2] | 0) + d >> 2] | 0;return + +ch(+ub[d & 15](a));
+    }function Um(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Vm(a, d, f, 1);l = e;return;
+    }function Vm(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = Wm() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = Xm(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, Ym(g, e) | 0, e);l = f;return;
+    }function Wm() {
+      var b = 0,
+          d = 0;if (!(a[7720] | 0)) {
+        dn(9592);Ha(36, 9592, o | 0) | 0;d = 7720;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9592) | 0)) {
+        b = 9592;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));dn(9592);
+      }return 9592;
+    }function Xm(a) {
+      a = a | 0;return 0;
+    }function Ym(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = Wm() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];Zm(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        _m(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function Zm(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function _m(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = $m(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;an(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];Zm(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;bn(a, i);cn(i);l = k;return;
+      }
+    }function $m(a) {
+      a = a | 0;return 357913941;
+    }function an(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function bn(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function cn(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function dn(a) {
+      a = a | 0;gn(a);return;
+    }function en(a) {
+      a = a | 0;fn(a + 24 | 0);return;
+    }function fn(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function gn(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 7, b, hn() | 0, 0);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function hn() {
+      return 1276;
+    }function jn(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0;d = l;l = l + 16 | 0;e = d + 8 | 0;f = d;g = kn(a) | 0;a = c[g + 4 >> 2] | 0;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = a;c[e >> 2] = c[f >> 2];c[e + 4 >> 2] = c[f + 4 >> 2];b = ln(b, e) | 0;l = d;return b | 0;
+    }function kn(a) {
+      a = a | 0;return (c[(Wm() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function ln(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0;f = l;l = l + 16 | 0;e = f;d = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) d = c[(c[a >> 2] | 0) + d >> 2] | 0;ob[d & 31](e, a);e = mn(e) | 0;l = f;return e | 0;
+    }function mn(a) {
+      a = a | 0;var b = 0,
+          c = 0,
+          d = 0,
+          e = 0;e = l;l = l + 32 | 0;b = e + 12 | 0;c = e;d = Di(nn() | 0) | 0;if (!d) a = pn(a) | 0;else {
+        Ei(b, d);Fi(c, b);on(a, c);a = Hi(b) | 0;
+      }l = e;return a | 0;
+    }function nn() {
+      var b = 0;if (!(a[7736] | 0)) {
+        An(9640);Ha(25, 9640, o | 0) | 0;b = 7736;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 9640;
+    }function on(a, b) {
+      a = a | 0;b = b | 0;un(b, a, a + 8 | 0) | 0;return;
+    }
+    function pn(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;d = l;l = l + 16 | 0;f = d + 4 | 0;h = d;e = jy(8) | 0;b = e;i = qC(16) | 0;c[i >> 2] = c[a >> 2];c[i + 4 >> 2] = c[a + 4 >> 2];c[i + 8 >> 2] = c[a + 8 >> 2];c[i + 12 >> 2] = c[a + 12 >> 2];g = b + 4 | 0;c[g >> 2] = i;a = qC(8) | 0;g = c[g >> 2] | 0;c[h >> 2] = 0;c[f >> 2] = c[h >> 2];qn(a, g, f);c[e >> 2] = a;l = d;return b | 0;
+    }function qn(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;c[a >> 2] = b;d = qC(16) | 0;c[d + 4 >> 2] = 0;c[d + 8 >> 2] = 0;c[d >> 2] = 1244;c[d + 12 >> 2] = b;c[a + 4 >> 2] = d;return;
+    }function rn(a) {
+      a = a | 0;kC(a);sC(a);return;
+    }function sn(a) {
+      a = a | 0;a = c[a + 12 >> 2] | 0;if (a | 0) sC(a);return;
+    }function tn(a) {
+      a = a | 0;sC(a);return;
+    }function un(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;b = vn(c[a >> 2] | 0, b, d) | 0;d = a + 4 | 0;c[(c[d >> 2] | 0) + 8 >> 2] = b;return c[(c[d >> 2] | 0) + 8 >> 2] | 0;
+    }function vn(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0;e = l;l = l + 16 | 0;f = e;UA(f);a = Sg(a) | 0;d = wn(a, c[b >> 2] | 0, +h[d >> 3]) | 0;WA(f);l = e;return d | 0;
+    }function wn(a, b, c) {
+      a = a | 0;b = b | 0;c = +c;var d = 0;d = Vg(xn() | 0) | 0;b = Xg(b) | 0;return za(0, d | 0, a | 0, b | 0, + +Wg(c)) | 0;
+    }function xn() {
+      var b = 0;if (!(a[7728] | 0)) {
+        yn(9628);b = 7728;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 9628;
+    }function yn(a) {
+      a = a | 0;fh(a, zn() | 0, 2);return;
+    }function zn() {
+      return 1264;
+    }function An(a) {
+      a = a | 0;Zi(a);return;
+    }function Bn(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Cn(a, d, f, 1);l = e;return;
+    }function Cn(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = Dn() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = En(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, Fn(g, e) | 0, e);l = f;return;
+    }function Dn() {
+      var b = 0,
+          d = 0;if (!(a[7744] | 0)) {
+        Mn(9684);Ha(37, 9684, o | 0) | 0;d = 7744;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9684) | 0)) {
+        b = 9684;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));Mn(9684);
+      }return 9684;
+    }function En(a) {
+      a = a | 0;return 0;
+    }function Fn(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = Dn() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];Gn(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        Hn(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function Gn(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function Hn(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = In(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;Jn(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];Gn(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;Kn(a, i);Ln(i);l = k;return;
+      }
+    }function In(a) {
+      a = a | 0;return 357913941;
+    }function Jn(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function Kn(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function Ln(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function Mn(a) {
+      a = a | 0;Pn(a);return;
+    }function Nn(a) {
+      a = a | 0;On(a + 24 | 0);return;
+    }function On(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function Pn(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 5, b, Qn() | 0, 1);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function Qn() {
+      return 1280;
+    }function Rn(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = Sn(a) | 0;a = c[h + 4 >> 2] | 0;c[g >> 2] = c[h >> 2];c[g + 4 >> 2] = a;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];d = Tn(b, f, d) | 0;l = e;return d | 0;
+    }function Sn(a) {
+      a = a | 0;return (c[(Dn() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function Tn(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;h = l;l = l + 32 | 0;f = h;g = h + 16 | 0;e = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) e = c[(c[a >> 2] | 0) + e >> 2] | 0;g = uj(g, d) | 0;Eb[e & 15](f, a, g);g = mn(f) | 0;l = h;return g | 0;
+    }function Un(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Vn(a, d, f, 1);l = e;return;
+    }function Vn(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = Wn() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = Xn(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, Yn(g, e) | 0, e);l = f;return;
+    }function Wn() {
+      var b = 0,
+          d = 0;if (!(a[7752] | 0)) {
+        eo(9720);Ha(38, 9720, o | 0) | 0;d = 7752;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9720) | 0)) {
+        b = 9720;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));eo(9720);
+      }return 9720;
+    }function Xn(a) {
+      a = a | 0;return 0;
+    }function Yn(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = Wn() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];Zn(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        _n(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function Zn(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function _n(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = $n(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;ao(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];Zn(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;bo(a, i);co(i);l = k;return;
+      }
+    }function $n(a) {
+      a = a | 0;return 357913941;
+    }function ao(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function bo(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function co(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function eo(a) {
+      a = a | 0;ho(a);return;
+    }function fo(a) {
+      a = a | 0;go(a + 24 | 0);return;
+    }function go(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function ho(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 8, b, io() | 0, 0);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function io() {
+      return 1288;
+    }function jo(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0;d = l;l = l + 16 | 0;e = d + 8 | 0;f = d;g = ko(a) | 0;a = c[g + 4 >> 2] | 0;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = a;c[e >> 2] = c[f >> 2];c[e + 4 >> 2] = c[f + 4 >> 2];b = lo(b, e) | 0;l = d;return b | 0;
+    }function ko(a) {
+      a = a | 0;return (c[(Wn() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function lo(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) d = c[(c[a >> 2] | 0) + d >> 2] | 0;return bh(pb[d & 31](a) | 0) | 0;
+    }function mo(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];no(a, d, f, 0);l = e;return;
+    }function no(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = oo() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = po(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, qo(g, e) | 0, e);l = f;return;
+    }function oo() {
+      var b = 0,
+          d = 0;if (!(a[7760] | 0)) {
+        xo(9756);Ha(39, 9756, o | 0) | 0;d = 7760;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9756) | 0)) {
+        b = 9756;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));xo(9756);
+      }return 9756;
+    }function po(a) {
+      a = a | 0;return 0;
+    }function qo(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = oo() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];ro(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        so(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function ro(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function so(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = to(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;uo(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];ro(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;vo(a, i);wo(i);l = k;return;
+      }
+    }function to(a) {
+      a = a | 0;return 357913941;
+    }function uo(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function vo(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function wo(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function xo(a) {
+      a = a | 0;Ao(a);return;
+    }function yo(a) {
+      a = a | 0;zo(a + 24 | 0);return;
+    }function zo(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function Ao(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 8, b, Bo() | 0, 1);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function Bo() {
+      return 1292;
+    }function Co(a, b, d) {
+      a = a | 0;b = b | 0;d = +d;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = Do(a) | 0;a = c[h + 4 >> 2] | 0;c[g >> 2] = c[h >> 2];c[g + 4 >> 2] = a;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Eo(b, f, d);l = e;return;
+    }function Do(a) {
+      a = a | 0;return (c[(oo() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function Eo(a, b, d) {
+      a = a | 0;b = b | 0;d = +d;var e = 0,
+          f = 0,
+          g = 0;g = l;l = l + 16 | 0;f = g;e = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) e = c[(c[a >> 2] | 0) + e >> 2] | 0;d = +sj(f, d);lb[e & 31](a, d);l = g;return;
+    }function Fo(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Go(a, d, f, 0);l = e;return;
+    }function Go(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = Ho() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = Io(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, Jo(g, e) | 0, e);l = f;return;
+    }function Ho() {
+      var b = 0,
+          d = 0;if (!(a[7768] | 0)) {
+        Qo(9792);Ha(40, 9792, o | 0) | 0;d = 7768;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9792) | 0)) {
+        b = 9792;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));Qo(9792);
+      }return 9792;
+    }function Io(a) {
+      a = a | 0;return 0;
+    }function Jo(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = Ho() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];Ko(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        Lo(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function Ko(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function Lo(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = Mo(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;No(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];Ko(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;Oo(a, i);Po(i);l = k;return;
+      }
+    }function Mo(a) {
+      a = a | 0;return 357913941;
+    }function No(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function Oo(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function Po(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function Qo(a) {
+      a = a | 0;To(a);return;
+    }function Ro(a) {
+      a = a | 0;So(a + 24 | 0);return;
+    }function So(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function To(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 1, b, Uo() | 0, 2);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function Uo() {
+      return 1300;
+    }function Vo(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = +e;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0;f = l;l = l + 16 | 0;g = f + 8 | 0;h = f;i = Wo(a) | 0;a = c[i + 4 >> 2] | 0;c[h >> 2] = c[i >> 2];c[h + 4 >> 2] = a;c[g >> 2] = c[h >> 2];c[g + 4 >> 2] = c[h + 4 >> 2];Xo(b, g, d, e);l = f;return;
+    }function Wo(a) {
+      a = a | 0;return (c[(Ho() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function Xo(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = +e;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0;i = l;l = l + 16 | 0;g = i + 1 | 0;h = i;f = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) f = c[(c[a >> 2] | 0) + f >> 2] | 0;g = uj(g, d) | 0;e = +sj(h, e);Gb[f & 15](a, g, e);l = i;return;
+    }function Yo(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Zo(a, d, f, 0);l = e;return;
+    }function Zo(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = _o() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = $o(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, ap(g, e) | 0, e);l = f;return;
+    }function _o() {
+      var b = 0,
+          d = 0;if (!(a[7776] | 0)) {
+        hp(9828);Ha(41, 9828, o | 0) | 0;d = 7776;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9828) | 0)) {
+        b = 9828;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));hp(9828);
+      }return 9828;
+    }function $o(a) {
+      a = a | 0;return 0;
+    }function ap(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = _o() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];bp(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        cp(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function bp(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function cp(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = dp(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;ep(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];bp(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;fp(a, i);gp(i);l = k;return;
+      }
+    }function dp(a) {
+      a = a | 0;return 357913941;
+    }function ep(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function fp(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function gp(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function hp(a) {
+      a = a | 0;kp(a);return;
+    }function ip(a) {
+      a = a | 0;jp(a + 24 | 0);return;
+    }function jp(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function kp(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 7, b, lp() | 0, 1);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function lp() {
+      return 1312;
+    }function mp(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = np(a) | 0;a = c[h + 4 >> 2] | 0;c[g >> 2] = c[h >> 2];c[g + 4 >> 2] = a;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];op(b, f, d);l = e;return;
+    }function np(a) {
+      a = a | 0;return (c[(_o() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function op(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0;g = l;l = l + 16 | 0;f = g;e = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) e = c[(c[a >> 2] | 0) + e >> 2] | 0;f = uj(f, d) | 0;ob[e & 31](a, f);l = g;return;
+    }function pp(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];qp(a, d, f, 0);l = e;return;
+    }function qp(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = rp() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = sp(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, tp(g, e) | 0, e);l = f;return;
+    }function rp() {
+      var b = 0,
+          d = 0;if (!(a[7784] | 0)) {
+        Ap(9864);Ha(42, 9864, o | 0) | 0;d = 7784;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9864) | 0)) {
+        b = 9864;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));Ap(9864);
+      }return 9864;
+    }function sp(a) {
+      a = a | 0;return 0;
+    }function tp(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = rp() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];up(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        vp(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function up(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function vp(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = wp(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;xp(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];up(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;yp(a, i);zp(i);l = k;return;
+      }
+    }function wp(a) {
+      a = a | 0;return 357913941;
+    }function xp(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function yp(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function zp(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function Ap(a) {
+      a = a | 0;Dp(a);return;
+    }function Bp(a) {
+      a = a | 0;Cp(a + 24 | 0);return;
+    }function Cp(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function Dp(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 8, b, Ep() | 0, 1);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function Ep() {
+      return 1320;
+    }function Fp(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = Gp(a) | 0;a = c[h + 4 >> 2] | 0;c[g >> 2] = c[h >> 2];c[g + 4 >> 2] = a;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Hp(b, f, d);l = e;return;
+    }function Gp(a) {
+      a = a | 0;return (c[(rp() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function Hp(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0;g = l;l = l + 16 | 0;f = g;e = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) e = c[(c[a >> 2] | 0) + e >> 2] | 0;f = Jp(f, d) | 0;ob[e & 31](a, f);l = g;return;
+    }function Jp(a, b) {
+      a = a | 0;b = b | 0;return Kp(b) | 0;
+    }function Kp(a) {
+      a = a | 0;return a | 0;
+    }function Lp(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Mp(a, d, f, 0);l = e;return;
+    }function Mp(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = Np() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = Op(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, Pp(g, e) | 0, e);l = f;return;
+    }function Np() {
+      var b = 0,
+          d = 0;if (!(a[7792] | 0)) {
+        Wp(9900);Ha(43, 9900, o | 0) | 0;d = 7792;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9900) | 0)) {
+        b = 9900;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));Wp(9900);
+      }return 9900;
+    }function Op(a) {
+      a = a | 0;return 0;
+    }function Pp(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = Np() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];Qp(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        Rp(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function Qp(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function Rp(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = Sp(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;Tp(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];Qp(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;Up(a, i);Vp(i);l = k;return;
+      }
+    }function Sp(a) {
+      a = a | 0;return 357913941;
+    }function Tp(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function Up(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function Vp(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function Wp(a) {
+      a = a | 0;Zp(a);return;
+    }function Xp(a) {
+      a = a | 0;Yp(a + 24 | 0);return;
+    }function Yp(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function Zp(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 22, b, _p() | 0, 0);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function _p() {
+      return 1344;
+    }function $p(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0;d = l;l = l + 16 | 0;e = d + 8 | 0;f = d;g = aq(a) | 0;a = c[g + 4 >> 2] | 0;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = a;c[e >> 2] = c[f >> 2];c[e + 4 >> 2] = c[f + 4 >> 2];bq(b, e);l = d;return;
+    }function aq(a) {
+      a = a | 0;return (c[(Np() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function bq(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) d = c[(c[a >> 2] | 0) + d >> 2] | 0;nb[d & 127](a);return;
+    }function cq(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0;g = c[a >> 2] | 0;f = dq() | 0;a = eq(d) | 0;fi(g, b, f, a, fq(d, e) | 0, e);return;
+    }function dq() {
+      var b = 0,
+          d = 0;if (!(a[7800] | 0)) {
+        mq(9936);Ha(44, 9936, o | 0) | 0;d = 7800;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9936) | 0)) {
+        b = 9936;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));mq(9936);
+      }return 9936;
+    }function eq(a) {
+      a = a | 0;return a | 0;
+    }function fq(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0;i = l;l = l + 16 | 0;f = i;g = i + 4 | 0;c[f >> 2] = a;j = dq() | 0;h = j + 24 | 0;b = ji(b, 4) | 0;c[g >> 2] = b;d = j + 28 | 0;e = c[d >> 2] | 0;if (e >>> 0 < (c[j + 32 >> 2] | 0) >>> 0) {
+        gq(e, a, b);b = (c[d >> 2] | 0) + 8 | 0;c[d >> 2] = b;
+      } else {
+        hq(h, f, g);b = c[d >> 2] | 0;
+      }l = i;return (b - (c[h >> 2] | 0) >> 3) + -1 | 0;
+    }function gq(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;c[a >> 2] = b;c[a + 4 >> 2] = d;return;
+    }function hq(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;i = l;l = l + 32 | 0;f = i;g = a + 4 | 0;h = ((c[g >> 2] | 0) - (c[a >> 2] | 0) >> 3) + 1 | 0;e = iq(a) | 0;if (e >>> 0 < h >>> 0) jC(a);else {
+        j = c[a >> 2] | 0;m = (c[a + 8 >> 2] | 0) - j | 0;k = m >> 2;jq(f, m >> 3 >>> 0 < e >>> 1 >>> 0 ? k >>> 0 < h >>> 0 ? h : k : e, (c[g >> 2] | 0) - j >> 3, a + 8 | 0);h = f + 8 | 0;gq(c[h >> 2] | 0, c[b >> 2] | 0, c[d >> 2] | 0);c[h >> 2] = (c[h >> 2] | 0) + 8;kq(a, f);lq(f);l = i;return;
+      }
+    }function iq(a) {
+      a = a | 0;return 536870911;
+    }function jq(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 536870911) Ta();else {
+          f = qC(b << 3) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d << 3) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b << 3);return;
+    }function kq(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (0 - (f >> 3) << 3) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function lq(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~((e + -8 - b | 0) >>> 3) << 3);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function mq(a) {
+      a = a | 0;pq(a);return;
+    }function nq(a) {
+      a = a | 0;oq(a + 24 | 0);return;
+    }function oq(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -8 - e | 0) >>> 3) << 3);sC(d);
+      }return;
+    }function pq(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 1, 23, b, Kl() | 0, 1);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function qq(a, b) {
+      a = a | 0;b = b | 0;sq(c[(rq(a) | 0) >> 2] | 0, b);return;
+    }function rq(a) {
+      a = a | 0;return (c[(dq() | 0) + 24 >> 2] | 0) + (a << 3) | 0;
+    }function sq(a, b) {
+      a = a | 0;b = b | 0;var c = 0,
+          d = 0;c = l;l = l + 16 | 0;d = c;b = Pl(d, b) | 0;nb[a & 127](b);l = c;return;
+    }function tq(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0;g = c[a >> 2] | 0;f = uq() | 0;a = vq(d) | 0;fi(g, b, f, a, wq(d, e) | 0, e);return;
+    }function uq() {
+      var b = 0,
+          d = 0;if (!(a[7808] | 0)) {
+        Dq(9972);Ha(45, 9972, o | 0) | 0;d = 7808;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(9972) | 0)) {
+        b = 9972;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));Dq(9972);
+      }return 9972;
+    }function vq(a) {
+      a = a | 0;return a | 0;
+    }function wq(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0;i = l;l = l + 16 | 0;f = i;g = i + 4 | 0;c[f >> 2] = a;j = uq() | 0;h = j + 24 | 0;b = ji(b, 4) | 0;c[g >> 2] = b;d = j + 28 | 0;e = c[d >> 2] | 0;if (e >>> 0 < (c[j + 32 >> 2] | 0) >>> 0) {
+        xq(e, a, b);b = (c[d >> 2] | 0) + 8 | 0;c[d >> 2] = b;
+      } else {
+        yq(h, f, g);b = c[d >> 2] | 0;
+      }l = i;return (b - (c[h >> 2] | 0) >> 3) + -1 | 0;
+    }function xq(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;c[a >> 2] = b;c[a + 4 >> 2] = d;return;
+    }function yq(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;i = l;l = l + 32 | 0;f = i;g = a + 4 | 0;h = ((c[g >> 2] | 0) - (c[a >> 2] | 0) >> 3) + 1 | 0;e = zq(a) | 0;if (e >>> 0 < h >>> 0) jC(a);else {
+        j = c[a >> 2] | 0;m = (c[a + 8 >> 2] | 0) - j | 0;k = m >> 2;Aq(f, m >> 3 >>> 0 < e >>> 1 >>> 0 ? k >>> 0 < h >>> 0 ? h : k : e, (c[g >> 2] | 0) - j >> 3, a + 8 | 0);h = f + 8 | 0;xq(c[h >> 2] | 0, c[b >> 2] | 0, c[d >> 2] | 0);c[h >> 2] = (c[h >> 2] | 0) + 8;Bq(a, f);Cq(f);l = i;return;
+      }
+    }function zq(a) {
+      a = a | 0;return 536870911;
+    }function Aq(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 536870911) Ta();else {
+          f = qC(b << 3) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d << 3) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b << 3);return;
+    }function Bq(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (0 - (f >> 3) << 3) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function Cq(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~((e + -8 - b | 0) >>> 3) << 3);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function Dq(a) {
+      a = a | 0;Gq(a);return;
+    }function Eq(a) {
+      a = a | 0;Fq(a + 24 | 0);return;
+    }function Fq(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -8 - e | 0) >>> 3) << 3);sC(d);
+      }return;
+    }function Gq(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 1, 9, b, Hq() | 0, 1);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function Hq() {
+      return 1348;
+    }function Iq(a, b) {
+      a = a | 0;b = b | 0;return Kq(c[(Jq(a) | 0) >> 2] | 0, b) | 0;
+    }function Jq(a) {
+      a = a | 0;return (c[(uq() | 0) + 24 >> 2] | 0) + (a << 3) | 0;
+    }function Kq(a, b) {
+      a = a | 0;b = b | 0;var c = 0,
+          d = 0;c = l;l = l + 16 | 0;d = c;b = Mq(d, b) | 0;b = Ik(pb[a & 31](b) | 0) | 0;l = c;return b | 0;
+    }function Mq(a, b) {
+      a = a | 0;b = b | 0;return Nq(b) | 0;
+    }function Nq(a) {
+      a = a | 0;return a | 0;
+    }function Oq(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0;g = c[a >> 2] | 0;f = Pq() | 0;a = Qq(d) | 0;fi(g, b, f, a, Rq(d, e) | 0, e);return;
+    }function Pq() {
+      var b = 0,
+          d = 0;if (!(a[7816] | 0)) {
+        Yq(10008);Ha(46, 10008, o | 0) | 0;d = 7816;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(10008) | 0)) {
+        b = 10008;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));Yq(10008);
+      }return 10008;
+    }function Qq(a) {
+      a = a | 0;return a | 0;
+    }function Rq(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0;i = l;l = l + 16 | 0;f = i;g = i + 4 | 0;c[f >> 2] = a;j = Pq() | 0;h = j + 24 | 0;b = ji(b, 4) | 0;c[g >> 2] = b;d = j + 28 | 0;e = c[d >> 2] | 0;if (e >>> 0 < (c[j + 32 >> 2] | 0) >>> 0) {
+        Sq(e, a, b);b = (c[d >> 2] | 0) + 8 | 0;c[d >> 2] = b;
+      } else {
+        Tq(h, f, g);b = c[d >> 2] | 0;
+      }l = i;return (b - (c[h >> 2] | 0) >> 3) + -1 | 0;
+    }function Sq(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;c[a >> 2] = b;c[a + 4 >> 2] = d;return;
+    }function Tq(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;i = l;l = l + 32 | 0;f = i;g = a + 4 | 0;h = ((c[g >> 2] | 0) - (c[a >> 2] | 0) >> 3) + 1 | 0;e = Uq(a) | 0;if (e >>> 0 < h >>> 0) jC(a);else {
+        j = c[a >> 2] | 0;m = (c[a + 8 >> 2] | 0) - j | 0;k = m >> 2;Vq(f, m >> 3 >>> 0 < e >>> 1 >>> 0 ? k >>> 0 < h >>> 0 ? h : k : e, (c[g >> 2] | 0) - j >> 3, a + 8 | 0);h = f + 8 | 0;Sq(c[h >> 2] | 0, c[b >> 2] | 0, c[d >> 2] | 0);c[h >> 2] = (c[h >> 2] | 0) + 8;Wq(a, f);Xq(f);l = i;return;
+      }
+    }function Uq(a) {
+      a = a | 0;return 536870911;
+    }function Vq(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 536870911) Ta();else {
+          f = qC(b << 3) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d << 3) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b << 3);return;
+    }function Wq(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (0 - (f >> 3) << 3) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function Xq(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~((e + -8 - b | 0) >>> 3) << 3);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function Yq(a) {
+      a = a | 0;$q(a);return;
+    }function Zq(a) {
+      a = a | 0;_q(a + 24 | 0);return;
+    }function _q(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -8 - e | 0) >>> 3) << 3);sC(d);
+      }return;
+    }function $q(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 1, 15, b, Zk() | 0, 0);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function ar(a) {
+      a = a | 0;return cr(c[(br(a) | 0) >> 2] | 0) | 0;
+    }function br(a) {
+      a = a | 0;return (c[(Pq() | 0) + 24 >> 2] | 0) + (a << 3) | 0;
+    }function cr(a) {
+      a = a | 0;return Ik(Ab[a & 7]() | 0) | 0;
+    }function dr() {
+      var b = 0;if (!(a[7832] | 0)) {
+        nr(10052);Ha(25, 10052, o | 0) | 0;b = 7832;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 10052;
+    }function er(a, b) {
+      a = a | 0;b = b | 0;c[a >> 2] = fr() | 0;c[a + 4 >> 2] = gr() | 0;c[a + 12 >> 2] = b;c[a + 8 >> 2] = hr() | 0;c[a + 32 >> 2] = 2;return;
+    }function fr() {
+      return 11709;
+    }function gr() {
+      return 1188;
+    }function hr() {
+      return lr() | 0;
+    }function ir(a, b, c, d) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;if ((jr(d, 896) | 0) == 512) {
+        if (c | 0) {
+          kr(c);sC(c);
+        }
+      } else if (b | 0) {
+        uf(b);sC(b);
+      }return;
+    }function jr(a, b) {
+      a = a | 0;b = b | 0;return b & a | 0;
+    }function kr(a) {
+      a = a | 0;a = c[a + 4 >> 2] | 0;if (a | 0) oC(a);return;
+    }function lr() {
+      var b = 0;if (!(a[7824] | 0)) {
+        c[2511] = mr() | 0;c[2512] = 0;b = 7824;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 10044;
+    }function mr() {
+      return 0;
+    }function nr(a) {
+      a = a | 0;Zi(a);return;
+    }function or(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0,
+          f = 0,
+          g = 0;b = l;l = l + 32 | 0;d = b + 24 | 0;g = b + 16 | 0;f = b + 8 | 0;e = b;pr(a, 4827);qr(a, 4834, 3) | 0;rr(a, 3682, 47) | 0;c[g >> 2] = 9;c[g + 4 >> 2] = 0;c[d >> 2] = c[g >> 2];c[d + 4 >> 2] = c[g + 4 >> 2];sr(a, 4841, d) | 0;c[f >> 2] = 1;c[f + 4 >> 2] = 0;c[d >> 2] = c[f >> 2];c[d + 4 >> 2] = c[f + 4 >> 2];tr(a, 4871, d) | 0;c[e >> 2] = 10;c[e + 4 >> 2] = 0;c[d >> 2] = c[e >> 2];c[d + 4 >> 2] = c[e + 4 >> 2];ur(a, 4891, d) | 0;l = b;return;
+    }function pr(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = nt() | 0;c[a >> 2] = d;ot(d, b);Hv(c[a >> 2] | 0);return;
+    }function qr(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;Ws(a, ai(b) | 0, c, 0);return a | 0;
+    }function rr(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;Es(a, ai(b) | 0, c, 0);return a | 0;
+    }function sr(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];is(a, b, f);l = e;return a | 0;
+    }function tr(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Or(a, b, f);l = e;return a | 0;
+    }function ur(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = c[d + 4 >> 2] | 0;c[g >> 2] = c[d >> 2];c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];vr(a, b, f);l = e;return a | 0;
+    }function vr(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];wr(a, d, f, 1);l = e;return;
+    }function wr(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = xr() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = yr(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, zr(g, e) | 0, e);l = f;return;
+    }function xr() {
+      var b = 0,
+          d = 0;if (!(a[7840] | 0)) {
+        Gr(10100);Ha(48, 10100, o | 0) | 0;d = 7840;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(10100) | 0)) {
+        b = 10100;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));Gr(10100);
+      }return 10100;
+    }function yr(a) {
+      a = a | 0;return 0;
+    }function zr(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = xr() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];Ar(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        Br(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function Ar(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function Br(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = Cr(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;Dr(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];Ar(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;Er(a, i);Fr(i);l = k;return;
+      }
+    }function Cr(a) {
+      a = a | 0;return 357913941;
+    }function Dr(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function Er(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function Fr(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function Gr(a) {
+      a = a | 0;Jr(a);return;
+    }function Hr(a) {
+      a = a | 0;Ir(a + 24 | 0);return;
+    }function Ir(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function Jr(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 6, b, Kr() | 0, 1);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function Kr() {
+      return 1364;
+    }function Lr(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = Mr(a) | 0;a = c[h + 4 >> 2] | 0;c[g >> 2] = c[h >> 2];c[g + 4 >> 2] = a;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];d = Nr(b, f, d) | 0;l = e;return d | 0;
+    }function Mr(a) {
+      a = a | 0;return (c[(xr() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function Nr(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0;g = l;l = l + 16 | 0;f = g;e = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) e = c[(c[a >> 2] | 0) + e >> 2] | 0;f = uj(f, d) | 0;f = Qj(wb[e & 15](a, f) | 0) | 0;l = g;return f | 0;
+    }function Or(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];Pr(a, d, f, 0);l = e;return;
+    }function Pr(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = Qr() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = Rr(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, Sr(g, e) | 0, e);l = f;return;
+    }function Qr() {
+      var b = 0,
+          d = 0;if (!(a[7848] | 0)) {
+        Zr(10136);Ha(49, 10136, o | 0) | 0;d = 7848;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(10136) | 0)) {
+        b = 10136;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));Zr(10136);
+      }return 10136;
+    }function Rr(a) {
+      a = a | 0;return 0;
+    }function Sr(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = Qr() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];Tr(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        Ur(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function Tr(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function Ur(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = Vr(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;Wr(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];Tr(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;Xr(a, i);Yr(i);l = k;return;
+      }
+    }function Vr(a) {
+      a = a | 0;return 357913941;
+    }function Wr(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function Xr(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function Yr(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function Zr(a) {
+      a = a | 0;as(a);return;
+    }function _r(a) {
+      a = a | 0;$r(a + 24 | 0);return;
+    }function $r(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function as(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 9, b, bs() | 0, 1);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function bs() {
+      return 1372;
+    }function cs(a, b, d) {
+      a = a | 0;b = b | 0;d = +d;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;h = ds(a) | 0;a = c[h + 4 >> 2] | 0;c[g >> 2] = c[h >> 2];c[g + 4 >> 2] = a;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];es(b, f, d);l = e;return;
+    }function ds(a) {
+      a = a | 0;return (c[(Qr() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function es(a, b, d) {
+      a = a | 0;b = b | 0;d = +d;var e = 0,
+          f = 0,
+          g = 0,
+          h = ib;g = l;l = l + 16 | 0;f = g;e = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) e = c[(c[a >> 2] | 0) + e >> 2] | 0;h = T(gs(f, d));kb[e & 1](a, h);l = g;return;
+    }function gs(a, b) {
+      a = a | 0;b = +b;return T(hs(b));
+    }function hs(a) {
+      a = +a;return T(a);
+    }function is(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 16 | 0;f = e + 8 | 0;g = e;i = c[d >> 2] | 0;h = c[d + 4 >> 2] | 0;d = ai(b) | 0;c[g >> 2] = i;c[g + 4 >> 2] = h;c[f >> 2] = c[g >> 2];c[f + 4 >> 2] = c[g + 4 >> 2];js(a, d, f, 0);l = e;return;
+    }function js(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;f = l;l = l + 32 | 0;g = f + 16 | 0;m = f + 8 | 0;i = f;k = c[d >> 2] | 0;j = c[d + 4 >> 2] | 0;h = c[a >> 2] | 0;a = ks() | 0;c[m >> 2] = k;c[m + 4 >> 2] = j;c[g >> 2] = c[m >> 2];c[g + 4 >> 2] = c[m + 4 >> 2];d = ls(g) | 0;c[i >> 2] = k;c[i + 4 >> 2] = j;c[g >> 2] = c[i >> 2];c[g + 4 >> 2] = c[i + 4 >> 2];fi(h, b, a, d, ms(g, e) | 0, e);l = f;return;
+    }function ks() {
+      var b = 0,
+          d = 0;if (!(a[7856] | 0)) {
+        ts(10172);Ha(50, 10172, o | 0) | 0;d = 7856;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(10172) | 0)) {
+        b = 10172;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));ts(10172);
+      }return 10172;
+    }function ls(a) {
+      a = a | 0;return 0;
+    }function ms(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;m = l;l = l + 32 | 0;f = m + 24 | 0;h = m + 16 | 0;i = m;j = m + 8 | 0;g = c[a >> 2] | 0;e = c[a + 4 >> 2] | 0;c[i >> 2] = g;c[i + 4 >> 2] = e;n = ks() | 0;k = n + 24 | 0;a = ji(b, 4) | 0;c[j >> 2] = a;b = n + 28 | 0;d = c[b >> 2] | 0;if (d >>> 0 < (c[n + 32 >> 2] | 0) >>> 0) {
+        c[h >> 2] = g;c[h + 4 >> 2] = e;c[f >> 2] = c[h >> 2];c[f + 4 >> 2] = c[h + 4 >> 2];ns(d, f, a);a = (c[b >> 2] | 0) + 12 | 0;c[b >> 2] = a;
+      } else {
+        os(k, i, j);a = c[b >> 2] | 0;
+      }l = m;return ((a - (c[k >> 2] | 0) | 0) / 12 | 0) + -1 | 0;
+    }function ns(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0;e = c[b + 4 >> 2] | 0;c[a >> 2] = c[b >> 2];c[a + 4 >> 2] = e;c[a + 8 >> 2] = d;return;
+    }function os(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0;k = l;l = l + 48 | 0;e = k + 32 | 0;h = k + 24 | 0;i = k;j = a + 4 | 0;f = (((c[j >> 2] | 0) - (c[a >> 2] | 0) | 0) / 12 | 0) + 1 | 0;g = ps(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        m = c[a >> 2] | 0;o = ((c[a + 8 >> 2] | 0) - m | 0) / 12 | 0;n = o << 1;qs(i, o >>> 0 < g >>> 1 >>> 0 ? n >>> 0 < f >>> 0 ? f : n : g, ((c[j >> 2] | 0) - m | 0) / 12 | 0, a + 8 | 0);j = i + 8 | 0;g = c[j >> 2] | 0;f = c[b + 4 >> 2] | 0;d = c[d >> 2] | 0;c[h >> 2] = c[b >> 2];c[h + 4 >> 2] = f;c[e >> 2] = c[h >> 2];c[e + 4 >> 2] = c[h + 4 >> 2];ns(g, e, d);c[j >> 2] = (c[j >> 2] | 0) + 12;rs(a, i);ss(i);l = k;return;
+      }
+    }function ps(a) {
+      a = a | 0;return 357913941;
+    }function qs(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 357913941) Ta();else {
+          f = qC(b * 12 | 0) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d * 12 | 0) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b * 12 | 0);return;
+    }function rs(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (((f | 0) / -12 | 0) * 12 | 0) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function ss(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~(((e + -12 - b | 0) >>> 0) / 12 | 0) * 12 | 0);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function ts(a) {
+      a = a | 0;ws(a);return;
+    }function us(a) {
+      a = a | 0;vs(a + 24 | 0);return;
+    }function vs(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~(((b + -12 - e | 0) >>> 0) / 12 | 0) * 12 | 0);sC(d);
+      }return;
+    }function ws(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 2, 3, b, xs() | 0, 2);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function xs() {
+      return 1380;
+    }function ys(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0;f = l;l = l + 16 | 0;g = f + 8 | 0;h = f;i = zs(a) | 0;a = c[i + 4 >> 2] | 0;c[h >> 2] = c[i >> 2];c[h + 4 >> 2] = a;c[g >> 2] = c[h >> 2];c[g + 4 >> 2] = c[h + 4 >> 2];As(b, g, d, e);l = f;return;
+    }function zs(a) {
+      a = a | 0;return (c[(ks() | 0) + 24 >> 2] | 0) + (a * 12 | 0) | 0;
+    }function As(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0;i = l;l = l + 16 | 0;g = i + 1 | 0;h = i;f = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;a = a + (b >> 1) | 0;if (b & 1) f = c[(c[a >> 2] | 0) + f >> 2] | 0;g = uj(g, d) | 0;h = Cs(h, e) | 0;Eb[f & 15](a, g, h);l = i;return;
+    }function Cs(a, b) {
+      a = a | 0;b = b | 0;return Ds(b) | 0;
+    }function Ds(a) {
+      a = a | 0;return (a | 0) != 0 | 0;
+    }function Es(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0;g = c[a >> 2] | 0;f = Fs() | 0;a = Gs(d) | 0;fi(g, b, f, a, Hs(d, e) | 0, e);return;
+    }function Fs() {
+      var b = 0,
+          d = 0;if (!(a[7864] | 0)) {
+        Os(10208);Ha(51, 10208, o | 0) | 0;d = 7864;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(10208) | 0)) {
+        b = 10208;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));Os(10208);
+      }return 10208;
+    }function Gs(a) {
+      a = a | 0;return a | 0;
+    }function Hs(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0;i = l;l = l + 16 | 0;f = i;g = i + 4 | 0;c[f >> 2] = a;j = Fs() | 0;h = j + 24 | 0;b = ji(b, 4) | 0;c[g >> 2] = b;d = j + 28 | 0;e = c[d >> 2] | 0;if (e >>> 0 < (c[j + 32 >> 2] | 0) >>> 0) {
+        Is(e, a, b);b = (c[d >> 2] | 0) + 8 | 0;c[d >> 2] = b;
+      } else {
+        Js(h, f, g);b = c[d >> 2] | 0;
+      }l = i;return (b - (c[h >> 2] | 0) >> 3) + -1 | 0;
+    }function Is(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;c[a >> 2] = b;c[a + 4 >> 2] = d;return;
+    }function Js(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;i = l;l = l + 32 | 0;f = i;g = a + 4 | 0;h = ((c[g >> 2] | 0) - (c[a >> 2] | 0) >> 3) + 1 | 0;e = Ks(a) | 0;if (e >>> 0 < h >>> 0) jC(a);else {
+        j = c[a >> 2] | 0;m = (c[a + 8 >> 2] | 0) - j | 0;k = m >> 2;Ls(f, m >> 3 >>> 0 < e >>> 1 >>> 0 ? k >>> 0 < h >>> 0 ? h : k : e, (c[g >> 2] | 0) - j >> 3, a + 8 | 0);h = f + 8 | 0;Is(c[h >> 2] | 0, c[b >> 2] | 0, c[d >> 2] | 0);c[h >> 2] = (c[h >> 2] | 0) + 8;Ms(a, f);Ns(f);l = i;return;
+      }
+    }function Ks(a) {
+      a = a | 0;return 536870911;
+    }function Ls(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 536870911) Ta();else {
+          f = qC(b << 3) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d << 3) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b << 3);return;
+    }function Ms(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (0 - (f >> 3) << 3) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function Ns(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~((e + -8 - b | 0) >>> 3) << 3);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function Os(a) {
+      a = a | 0;Rs(a);return;
+    }function Ps(a) {
+      a = a | 0;Qs(a + 24 | 0);return;
+    }function Qs(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -8 - e | 0) >>> 3) << 3);sC(d);
+      }return;
+    }function Rs(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 1, 24, b, Ss() | 0, 1);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function Ss() {
+      return 1392;
+    }function Ts(a, b) {
+      a = a | 0;b = b | 0;Vs(c[(Us(a) | 0) >> 2] | 0, b);return;
+    }function Us(a) {
+      a = a | 0;return (c[(Fs() | 0) + 24 >> 2] | 0) + (a << 3) | 0;
+    }function Vs(a, b) {
+      a = a | 0;b = b | 0;var c = 0,
+          d = 0;c = l;l = l + 16 | 0;d = c;b = Mq(d, b) | 0;nb[a & 127](b);l = c;return;
+    }function Ws(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0;g = c[a >> 2] | 0;f = Xs() | 0;a = Ys(d) | 0;fi(g, b, f, a, Zs(d, e) | 0, e);return;
+    }function Xs() {
+      var b = 0,
+          d = 0;if (!(a[7872] | 0)) {
+        et(10244);Ha(52, 10244, o | 0) | 0;d = 7872;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(10244) | 0)) {
+        b = 10244;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));et(10244);
+      }return 10244;
+    }function Ys(a) {
+      a = a | 0;return a | 0;
+    }function Zs(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0;i = l;l = l + 16 | 0;f = i;g = i + 4 | 0;c[f >> 2] = a;j = Xs() | 0;h = j + 24 | 0;b = ji(b, 4) | 0;c[g >> 2] = b;d = j + 28 | 0;e = c[d >> 2] | 0;if (e >>> 0 < (c[j + 32 >> 2] | 0) >>> 0) {
+        _s(e, a, b);b = (c[d >> 2] | 0) + 8 | 0;c[d >> 2] = b;
+      } else {
+        $s(h, f, g);b = c[d >> 2] | 0;
+      }l = i;return (b - (c[h >> 2] | 0) >> 3) + -1 | 0;
+    }function _s(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;c[a >> 2] = b;c[a + 4 >> 2] = d;return;
+    }function $s(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;i = l;l = l + 32 | 0;f = i;g = a + 4 | 0;h = ((c[g >> 2] | 0) - (c[a >> 2] | 0) >> 3) + 1 | 0;e = at(a) | 0;if (e >>> 0 < h >>> 0) jC(a);else {
+        j = c[a >> 2] | 0;m = (c[a + 8 >> 2] | 0) - j | 0;k = m >> 2;bt(f, m >> 3 >>> 0 < e >>> 1 >>> 0 ? k >>> 0 < h >>> 0 ? h : k : e, (c[g >> 2] | 0) - j >> 3, a + 8 | 0);h = f + 8 | 0;_s(c[h >> 2] | 0, c[b >> 2] | 0, c[d >> 2] | 0);c[h >> 2] = (c[h >> 2] | 0) + 8;ct(a, f);dt(f);l = i;return;
+      }
+    }function at(a) {
+      a = a | 0;return 536870911;
+    }function bt(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 536870911) Ta();else {
+          f = qC(b << 3) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d << 3) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b << 3);return;
+    }function ct(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (0 - (f >> 3) << 3) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function dt(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~((e + -8 - b | 0) >>> 3) << 3);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function et(a) {
+      a = a | 0;ht(a);return;
+    }function ft(a) {
+      a = a | 0;gt(a + 24 | 0);return;
+    }function gt(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -8 - e | 0) >>> 3) << 3);sC(d);
+      }return;
+    }function ht(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 1, 16, b, it() | 0, 0);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function it() {
+      return 1400;
+    }function jt(a) {
+      a = a | 0;return lt(c[(kt(a) | 0) >> 2] | 0) | 0;
+    }function kt(a) {
+      a = a | 0;return (c[(Xs() | 0) + 24 >> 2] | 0) + (a << 3) | 0;
+    }function lt(a) {
+      a = a | 0;return mt(Ab[a & 7]() | 0) | 0;
+    }function mt(a) {
+      a = a | 0;return a | 0;
+    }function nt() {
+      var b = 0;if (!(a[7880] | 0)) {
+        ut(10280);Ha(25, 10280, o | 0) | 0;b = 7880;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 10280;
+    }function ot(a, b) {
+      a = a | 0;b = b | 0;c[a >> 2] = pt() | 0;c[a + 4 >> 2] = qt() | 0;c[a + 12 >> 2] = b;c[a + 8 >> 2] = rt() | 0;c[a + 32 >> 2] = 4;return;
+    }function pt() {
+      return 11711;
+    }function qt() {
+      return 1356;
+    }function rt() {
+      return lr() | 0;
+    }function st(a, b, c, d) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;if ((jr(d, 896) | 0) == 512) {
+        if (c | 0) {
+          tt(c);sC(c);
+        }
+      } else if (b | 0) {
+        mf(b);sC(b);
+      }return;
+    }function tt(a) {
+      a = a | 0;a = c[a + 4 >> 2] | 0;if (a | 0) oC(a);return;
+    }function ut(a) {
+      a = a | 0;Zi(a);return;
+    }function vt(a) {
+      a = a | 0;wt(a, 4920);xt(a) | 0;yt(a) | 0;return;
+    }function wt(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = nn() | 0;c[a >> 2] = d;Yt(d, b);Hv(c[a >> 2] | 0);return;
+    }function xt(a) {
+      a = a | 0;var b = 0;b = c[a >> 2] | 0;At(b, Mt() | 0);return a | 0;
+    }function yt(a) {
+      a = a | 0;var b = 0;b = c[a >> 2] | 0;At(b, zt() | 0);return a | 0;
+    }function zt() {
+      var b = 0;if (!(a[7888] | 0)) {
+        Bt(10328);Ha(53, 10328, o | 0) | 0;b = 7888;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }if (!(si(10328) | 0)) Bt(10328);return 10328;
+    }function At(a, b) {
+      a = a | 0;b = b | 0;fi(a, 0, b, 0, 0, 0);return;
+    }function Bt(a) {
+      a = a | 0;Et(a);Gt(a, 10);return;
+    }function Ct(a) {
+      a = a | 0;Dt(a + 24 | 0);return;
+    }function Dt(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -8 - e | 0) >>> 3) << 3);sC(d);
+      }return;
+    }function Et(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 5, 1, b, Jt() | 0, 2);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function Ft(a, b, c) {
+      a = a | 0;b = b | 0;c = +c;Ht(a, b, c);return;
+    }function Gt(a, b) {
+      a = a | 0;b = b | 0;c[a + 20 >> 2] = b;return;
+    }function Ht(a, b, d) {
+      a = a | 0;b = b | 0;d = +d;var e = 0,
+          f = 0,
+          g = 0,
+          i = 0,
+          j = 0;e = l;l = l + 16 | 0;g = e + 8 | 0;j = e + 13 | 0;f = e;i = e + 12 | 0;c[g >> 2] = uj(j, b) | 0;h[f >> 3] = +sj(i, d);It(a, g, f);l = e;return;
+    }function It(b, d, e) {
+      b = b | 0;d = d | 0;e = e | 0;mg(b + 8 | 0, c[d >> 2] | 0, +h[e >> 3]);a[b + 24 >> 0] = 1;return;
+    }function Jt() {
+      return 1404;
+    }function Kt(a, b) {
+      a = a | 0;b = +b;return Lt(a, b) | 0;
+    }function Lt(a, b) {
+      a = a | 0;b = +b;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0;e = l;l = l + 16 | 0;g = e + 4 | 0;h = e + 8 | 0;i = e;f = jy(8) | 0;d = f;j = qC(16) | 0;a = uj(g, a) | 0;mg(j, a, +sj(h, b));h = d + 4 | 0;c[h >> 2] = j;a = qC(8) | 0;h = c[h >> 2] | 0;c[i >> 2] = 0;c[g >> 2] = c[i >> 2];qn(a, h, g);c[f >> 2] = a;l = e;return d | 0;
+    }function Mt() {
+      var b = 0;if (!(a[7896] | 0)) {
+        Nt(10364);Ha(54, 10364, o | 0) | 0;b = 7896;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }if (!(si(10364) | 0)) Nt(10364);return 10364;
+    }function Nt(a) {
+      a = a | 0;Qt(a);Gt(a, 55);return;
+    }function Ot(a) {
+      a = a | 0;Pt(a + 24 | 0);return;
+    }function Pt(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -8 - e | 0) >>> 3) << 3);sC(d);
+      }return;
+    }function Qt(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 5, 4, b, Vt() | 0, 0);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function Rt(a) {
+      a = a | 0;St(a);return;
+    }function St(a) {
+      a = a | 0;Tt(a);return;
+    }function Tt(b) {
+      b = b | 0;Ut(b + 8 | 0);a[b + 24 >> 0] = 1;return;
+    }function Ut(a) {
+      a = a | 0;c[a >> 2] = 0;h[a + 8 >> 3] = 0.0;return;
+    }function Vt() {
+      return 1424;
+    }function Wt() {
+      return Xt() | 0;
+    }function Xt() {
+      var a = 0,
+          b = 0,
+          d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;b = l;l = l + 16 | 0;f = b + 4 | 0;h = b;d = jy(8) | 0;a = d;e = qC(16) | 0;Ut(e);g = a + 4 | 0;c[g >> 2] = e;e = qC(8) | 0;g = c[g >> 2] | 0;c[h >> 2] = 0;c[f >> 2] = c[h >> 2];qn(e, g, f);c[d >> 2] = e;l = b;return a | 0;
+    }function Yt(a, b) {
+      a = a | 0;b = b | 0;c[a >> 2] = Zt() | 0;c[a + 4 >> 2] = _t() | 0;c[a + 12 >> 2] = b;c[a + 8 >> 2] = $t() | 0;c[a + 32 >> 2] = 5;return;
+    }function Zt() {
+      return 11710;
+    }function _t() {
+      return 1416;
+    }function $t() {
+      return cu() | 0;
+    }function au(a, b, c, d) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;if ((jr(d, 896) | 0) == 512) {
+        if (c | 0) {
+          bu(c);sC(c);
+        }
+      } else if (b | 0) sC(b);return;
+    }function bu(a) {
+      a = a | 0;a = c[a + 4 >> 2] | 0;if (a | 0) oC(a);return;
+    }function cu() {
+      var b = 0;if (!(a[7904] | 0)) {
+        c[2600] = du() | 0;c[2601] = 0;b = 7904;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 10400;
+    }function du() {
+      return c[357] | 0;
+    }function eu(a) {
+      a = a | 0;fu(a, 4926);gu(a) | 0;return;
+    }function fu(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = Ci() | 0;c[a >> 2] = d;su(d, b);Hv(c[a >> 2] | 0);return;
+    }function gu(a) {
+      a = a | 0;var b = 0;b = c[a >> 2] | 0;At(b, hu() | 0);return a | 0;
+    }function hu() {
+      var b = 0;if (!(a[7912] | 0)) {
+        iu(10412);Ha(56, 10412, o | 0) | 0;b = 7912;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }if (!(si(10412) | 0)) iu(10412);return 10412;
+    }function iu(a) {
+      a = a | 0;lu(a);Gt(a, 57);return;
+    }function ju(a) {
+      a = a | 0;ku(a + 24 | 0);return;
+    }function ku(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -8 - e | 0) >>> 3) << 3);sC(d);
+      }return;
+    }function lu(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 5, 5, b, pu() | 0, 0);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function mu(a) {
+      a = a | 0;nu(a);return;
+    }function nu(a) {
+      a = a | 0;ou(a);return;
+    }function ou(b) {
+      b = b | 0;var d = 0,
+          e = 0;d = b + 8 | 0;e = d + 48 | 0;do {
+        c[d >> 2] = 0;d = d + 4 | 0;
+      } while ((d | 0) < (e | 0));a[b + 56 >> 0] = 1;return;
+    }function pu() {
+      return 1432;
+    }function qu() {
+      return ru() | 0;
+    }function ru() {
+      var a = 0,
+          b = 0,
+          d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;h = l;l = l + 16 | 0;a = h + 4 | 0;b = h;d = jy(8) | 0;e = d;f = qC(48) | 0;g = f;i = g + 48 | 0;do {
+        c[g >> 2] = 0;g = g + 4 | 0;
+      } while ((g | 0) < (i | 0));g = e + 4 | 0;c[g >> 2] = f;i = qC(8) | 0;g = c[g >> 2] | 0;c[b >> 2] = 0;c[a >> 2] = c[b >> 2];Ji(i, g, a);c[d >> 2] = i;l = h;return e | 0;
+    }function su(a, b) {
+      a = a | 0;b = b | 0;c[a >> 2] = tu() | 0;c[a + 4 >> 2] = uu() | 0;c[a + 12 >> 2] = b;c[a + 8 >> 2] = vu() | 0;c[a + 32 >> 2] = 6;return;
+    }function tu() {
+      return 11704;
+    }function uu() {
+      return 1436;
+    }function vu() {
+      return cu() | 0;
+    }function wu(a, b, c, d) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;if ((jr(d, 896) | 0) == 512) {
+        if (c | 0) {
+          xu(c);sC(c);
+        }
+      } else if (b | 0) sC(b);return;
+    }function xu(a) {
+      a = a | 0;a = c[a + 4 >> 2] | 0;if (a | 0) oC(a);return;
+    }function yu(a) {
+      a = a | 0;zu(a, 4933);Au(a) | 0;Bu(a) | 0;return;
+    }function zu(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = cv() | 0;c[a >> 2] = d;dv(d, b);Hv(c[a >> 2] | 0);return;
+    }function Au(a) {
+      a = a | 0;var b = 0;b = c[a >> 2] | 0;At(b, Su() | 0);return a | 0;
+    }function Bu(a) {
+      a = a | 0;var b = 0;b = c[a >> 2] | 0;At(b, Cu() | 0);return a | 0;
+    }function Cu() {
+      var b = 0;if (!(a[7920] | 0)) {
+        Du(10452);Ha(58, 10452, o | 0) | 0;b = 7920;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }if (!(si(10452) | 0)) Du(10452);return 10452;
+    }function Du(a) {
+      a = a | 0;Gu(a);Gt(a, 1);return;
+    }function Eu(a) {
+      a = a | 0;Fu(a + 24 | 0);return;
+    }function Fu(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -8 - e | 0) >>> 3) << 3);sC(d);
+      }return;
+    }function Gu(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 5, 1, b, Lu() | 0, 2);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function Hu(a, b, c) {
+      a = a | 0;b = +b;c = +c;Iu(a, b, c);return;
+    }function Iu(a, b, c) {
+      a = a | 0;b = +b;c = +c;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          i = 0;d = l;l = l + 32 | 0;f = d + 8 | 0;i = d + 17 | 0;e = d;g = d + 16 | 0;h[f >> 3] = +sj(i, b);h[e >> 3] = +sj(g, c);Ju(a, f, e);l = d;return;
+    }function Ju(b, c, d) {
+      b = b | 0;c = c | 0;d = d | 0;Ku(b + 8 | 0, +h[c >> 3], +h[d >> 3]);a[b + 24 >> 0] = 1;return;
+    }function Ku(a, b, c) {
+      a = a | 0;b = +b;c = +c;h[a >> 3] = b;h[a + 8 >> 3] = c;return;
+    }function Lu() {
+      return 1472;
+    }function Mu(a, b) {
+      a = +a;b = +b;return Nu(a, b) | 0;
+    }function Nu(a, b) {
+      a = +a;b = +b;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0;e = l;l = l + 16 | 0;h = e + 4 | 0;i = e + 8 | 0;j = e;f = jy(8) | 0;d = f;g = qC(16) | 0;a = +sj(h, a);Ku(g, a, +sj(i, b));i = d + 4 | 0;c[i >> 2] = g;g = qC(8) | 0;i = c[i >> 2] | 0;c[j >> 2] = 0;c[h >> 2] = c[j >> 2];Ou(g, i, h);c[f >> 2] = g;l = e;return d | 0;
+    }function Ou(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;c[a >> 2] = b;d = qC(16) | 0;c[d + 4 >> 2] = 0;c[d + 8 >> 2] = 0;c[d >> 2] = 1452;c[d + 12 >> 2] = b;c[a + 4 >> 2] = d;return;
+    }function Pu(a) {
+      a = a | 0;kC(a);sC(a);return;
+    }function Qu(a) {
+      a = a | 0;a = c[a + 12 >> 2] | 0;if (a | 0) sC(a);return;
+    }function Ru(a) {
+      a = a | 0;sC(a);return;
+    }function Su() {
+      var b = 0;if (!(a[7928] | 0)) {
+        Tu(10488);Ha(59, 10488, o | 0) | 0;b = 7928;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }if (!(si(10488) | 0)) Tu(10488);return 10488;
+    }function Tu(a) {
+      a = a | 0;Wu(a);Gt(a, 60);return;
+    }function Uu(a) {
+      a = a | 0;Vu(a + 24 | 0);return;
+    }function Vu(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -8 - e | 0) >>> 3) << 3);sC(d);
+      }return;
+    }function Wu(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 5, 6, b, $u() | 0, 0);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function Xu(a) {
+      a = a | 0;Yu(a);return;
+    }function Yu(a) {
+      a = a | 0;Zu(a);return;
+    }function Zu(b) {
+      b = b | 0;_u(b + 8 | 0);a[b + 24 >> 0] = 1;return;
+    }function _u(a) {
+      a = a | 0;c[a >> 2] = 0;c[a + 4 >> 2] = 0;c[a + 8 >> 2] = 0;c[a + 12 >> 2] = 0;return;
+    }function $u() {
+      return 1492;
+    }function av() {
+      return bv() | 0;
+    }function bv() {
+      var a = 0,
+          b = 0,
+          d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;b = l;l = l + 16 | 0;f = b + 4 | 0;h = b;d = jy(8) | 0;a = d;e = qC(16) | 0;_u(e);g = a + 4 | 0;c[g >> 2] = e;e = qC(8) | 0;g = c[g >> 2] | 0;c[h >> 2] = 0;c[f >> 2] = c[h >> 2];Ou(e, g, f);c[d >> 2] = e;l = b;return a | 0;
+    }function cv() {
+      var b = 0;if (!(a[7936] | 0)) {
+        jv(10524);Ha(25, 10524, o | 0) | 0;b = 7936;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 10524;
+    }function dv(a, b) {
+      a = a | 0;b = b | 0;c[a >> 2] = ev() | 0;c[a + 4 >> 2] = fv() | 0;c[a + 12 >> 2] = b;c[a + 8 >> 2] = gv() | 0;c[a + 32 >> 2] = 7;return;
+    }function ev() {
+      return 11700;
+    }function fv() {
+      return 1484;
+    }function gv() {
+      return cu() | 0;
+    }function hv(a, b, c, d) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;if ((jr(d, 896) | 0) == 512) {
+        if (c | 0) {
+          iv(c);sC(c);
+        }
+      } else if (b | 0) sC(b);return;
+    }function iv(a) {
+      a = a | 0;a = c[a + 4 >> 2] | 0;if (a | 0) oC(a);return;
+    }function jv(a) {
+      a = a | 0;Zi(a);return;
+    }function kv(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;a = ai(b) | 0;b = lv(c) | 0;c = mv(c, 0) | 0;Zv(a, b, c, nv() | 0, 0);return;
+    }function lv(a) {
+      a = a | 0;return a | 0;
+    }function mv(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0;i = l;l = l + 16 | 0;f = i;g = i + 4 | 0;c[f >> 2] = a;j = nv() | 0;h = j + 24 | 0;b = ji(b, 4) | 0;c[g >> 2] = b;d = j + 28 | 0;e = c[d >> 2] | 0;if (e >>> 0 < (c[j + 32 >> 2] | 0) >>> 0) {
+        vv(e, a, b);b = (c[d >> 2] | 0) + 8 | 0;c[d >> 2] = b;
+      } else {
+        wv(h, f, g);b = c[d >> 2] | 0;
+      }l = i;return (b - (c[h >> 2] | 0) >> 3) + -1 | 0;
+    }function nv() {
+      var b = 0,
+          d = 0;if (!(a[7944] | 0)) {
+        ov(10568);Ha(61, 10568, o | 0) | 0;d = 7944;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(10568) | 0)) {
+        b = 10568;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));ov(10568);
+      }return 10568;
+    }function ov(a) {
+      a = a | 0;rv(a);return;
+    }function pv(a) {
+      a = a | 0;qv(a + 24 | 0);return;
+    }function qv(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -8 - e | 0) >>> 3) << 3);sC(d);
+      }return;
+    }function rv(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 1, 17, b, ql() | 0, 0);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function sv(a) {
+      a = a | 0;return uv(c[(tv(a) | 0) >> 2] | 0) | 0;
+    }function tv(a) {
+      a = a | 0;return (c[(nv() | 0) + 24 >> 2] | 0) + (a << 3) | 0;
+    }function uv(a) {
+      a = a | 0;return ul(Ab[a & 7]() | 0) | 0;
+    }function vv(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;c[a >> 2] = b;c[a + 4 >> 2] = d;return;
+    }function wv(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;i = l;l = l + 32 | 0;f = i;g = a + 4 | 0;h = ((c[g >> 2] | 0) - (c[a >> 2] | 0) >> 3) + 1 | 0;e = xv(a) | 0;if (e >>> 0 < h >>> 0) jC(a);else {
+        j = c[a >> 2] | 0;m = (c[a + 8 >> 2] | 0) - j | 0;k = m >> 2;yv(f, m >> 3 >>> 0 < e >>> 1 >>> 0 ? k >>> 0 < h >>> 0 ? h : k : e, (c[g >> 2] | 0) - j >> 3, a + 8 | 0);h = f + 8 | 0;vv(c[h >> 2] | 0, c[b >> 2] | 0, c[d >> 2] | 0);c[h >> 2] = (c[h >> 2] | 0) + 8;zv(a, f);Av(f);l = i;return;
+      }
+    }function xv(a) {
+      a = a | 0;return 536870911;
+    }function yv(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 536870911) Ta();else {
+          f = qC(b << 3) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d << 3) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b << 3);return;
+    }function zv(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (0 - (f >> 3) << 3) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function Av(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~((e + -8 - b | 0) >>> 3) << 3);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function Bv() {
+      Cv();return;
+    }function Cv() {
+      Dv(10604);return;
+    }function Dv(a) {
+      a = a | 0;Ev(a, 4955);return;
+    }function Ev(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = Fv() | 0;c[a >> 2] = d;Gv(d, b);Hv(c[a >> 2] | 0);return;
+    }function Fv() {
+      var b = 0;if (!(a[7952] | 0)) {
+        Rv(10612);Ha(25, 10612, o | 0) | 0;b = 7952;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 10612;
+    }function Gv(a, b) {
+      a = a | 0;b = b | 0;c[a >> 2] = Mv() | 0;c[a + 4 >> 2] = Nv() | 0;c[a + 12 >> 2] = b;c[a + 8 >> 2] = Ov() | 0;c[a + 32 >> 2] = 8;return;
+    }function Hv(a) {
+      a = a | 0;var b = 0,
+          d = 0;b = l;l = l + 16 | 0;d = b;Iv() | 0;c[d >> 2] = a;Jv(10608, d);l = b;return;
+    }function Iv() {
+      if (!(a[11714] | 0)) {
+        c[2652] = 0;Ha(62, 10608, o | 0) | 0;a[11714] = 1;
+      }return 10608;
+    }function Jv(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = qC(8) | 0;c[d + 4 >> 2] = c[b >> 2];c[d >> 2] = c[a >> 2];c[a >> 2] = d;return;
+    }function Kv(a) {
+      a = a | 0;Lv(a);return;
+    }function Lv(a) {
+      a = a | 0;var b = 0,
+          d = 0;b = c[a >> 2] | 0;if (b | 0) do {
+        d = b;b = c[b >> 2] | 0;sC(d);
+      } while ((b | 0) != 0);c[a >> 2] = 0;return;
+    }function Mv() {
+      return 11715;
+    }function Nv() {
+      return 1496;
+    }function Ov() {
+      return lr() | 0;
+    }function Pv(a, b, c, d) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;if ((jr(d, 896) | 0) == 512) {
+        if (c | 0) {
+          Qv(c);sC(c);
+        }
+      } else if (b | 0) sC(b);return;
+    }function Qv(a) {
+      a = a | 0;a = c[a + 4 >> 2] | 0;if (a | 0) oC(a);return;
+    }function Rv(a) {
+      a = a | 0;Zi(a);return;
+    }function Sv(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;Iv() | 0;d = c[2652] | 0;a: do if (d | 0) {
+        while (1) {
+          e = c[d + 4 >> 2] | 0;if (e | 0 ? (AB(Tv(e) | 0, a) | 0) == 0 : 0) break;d = c[d >> 2] | 0;if (!d) break a;
+        }Uv(e, b);
+      } while (0);return;
+    }function Tv(a) {
+      a = a | 0;return c[a + 12 >> 2] | 0;
+    }function Uv(a, b) {
+      a = a | 0;b = b | 0;var d = 0;a = a + 36 | 0;d = c[a >> 2] | 0;if (d | 0) {
+        vf(d);sC(d);
+      }d = qC(4) | 0;Og(d, b);c[a >> 2] = d;return;
+    }function Vv() {
+      if (!(a[11716] | 0)) {
+        c[2664] = 0;Ha(63, 10656, o | 0) | 0;a[11716] = 1;
+      }return 10656;
+    }function Wv() {
+      var b = 0;if (!(a[11717] | 0)) {
+        Xv();c[2665] = 1504;a[11717] = 1;b = 1504;
+      } else b = c[2665] | 0;return b | 0;
+    }function Xv() {
+      if (!(a[11740] | 0)) {
+        a[11718] = ji(ji(8, 0) | 0, 0) | 0;a[11719] = ji(ji(0, 0) | 0, 0) | 0;a[11720] = ji(ji(0, 16) | 0, 0) | 0;a[11721] = ji(ji(8, 0) | 0, 0) | 0;a[11722] = ji(ji(0, 0) | 0, 0) | 0;a[11723] = ji(ji(8, 0) | 0, 0) | 0;a[11724] = ji(ji(0, 0) | 0, 0) | 0;a[11725] = ji(ji(8, 0) | 0, 0) | 0;a[11726] = ji(ji(0, 0) | 0, 0) | 0;a[11727] = ji(ji(8, 0) | 0, 0) | 0;a[11728] = ji(ji(0, 0) | 0, 0) | 0;a[11729] = ji(ji(0, 0) | 0, 32) | 0;a[11730] = ji(ji(0, 0) | 0, 32) | 0;a[11740] = 1;
+      }return;
+    }function Yv() {
+      return 1572;
+    }function Zv(a, b, d, e, f) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;f = f | 0;var g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;g = l;l = l + 32 | 0;m = g + 16 | 0;k = g + 12 | 0;j = g + 8 | 0;i = g + 4 | 0;h = g;c[m >> 2] = a;c[k >> 2] = b;c[j >> 2] = d;c[i >> 2] = e;c[h >> 2] = f;Vv() | 0;_v(10656, m, k, j, i, h);l = g;return;
+    }function _v(a, b, d, e, f, g) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;f = f | 0;g = g | 0;var h = 0;h = qC(24) | 0;ii(h + 4 | 0, c[b >> 2] | 0, c[d >> 2] | 0, c[e >> 2] | 0, c[f >> 2] | 0, c[g >> 2] | 0);c[h >> 2] = c[a >> 2];c[a >> 2] = h;return;
+    }function $v(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0,
+          p = 0,
+          q = 0,
+          r = 0,
+          s = 0,
+          t = 0,
+          u = 0;u = l;l = l + 32 | 0;q = u + 20 | 0;r = u + 8 | 0;s = u + 4 | 0;t = u;b = c[b >> 2] | 0;if (b | 0) {
+        p = q + 4 | 0;j = q + 8 | 0;k = r + 4 | 0;m = r + 8 | 0;n = r + 8 | 0;o = q + 8 | 0;do {
+          h = b + 4 | 0;i = aw(h) | 0;if (i | 0) {
+            f = bw(i) | 0;c[q >> 2] = 0;c[p >> 2] = 0;c[j >> 2] = 0;e = (cw(i) | 0) + 1 | 0;dw(q, e);if (e | 0) while (1) {
+              e = e + -1 | 0;OA(r, c[f >> 2] | 0);g = c[p >> 2] | 0;if (g >>> 0 < (c[o >> 2] | 0) >>> 0) {
+                c[g >> 2] = c[r >> 2];c[p >> 2] = (c[p >> 2] | 0) + 4;
+              } else ew(q, r);if (!e) break;else f = f + 4 | 0;
+            }e = fw(i) | 0;c[r >> 2] = 0;c[k >> 2] = 0;c[m >> 2] = 0;a: do if (c[e >> 2] | 0) {
+              f = 0;g = 0;while (1) {
+                if ((f | 0) == (g | 0)) gw(r, e);else {
+                  c[f >> 2] = c[e >> 2];c[k >> 2] = (c[k >> 2] | 0) + 4;
+                }e = e + 4 | 0;if (!(c[e >> 2] | 0)) break a;f = c[k >> 2] | 0;g = c[n >> 2] | 0;
+              }
+            } while (0);c[s >> 2] = hw(h) | 0;c[t >> 2] = si(i) | 0;iw(d, a, s, t, q, r);jw(r);kw(q);
+          }b = c[b >> 2] | 0;
+        } while ((b | 0) != 0);
+      }l = u;return;
+    }function aw(a) {
+      a = a | 0;return c[a + 12 >> 2] | 0;
+    }function bw(a) {
+      a = a | 0;return c[a + 12 >> 2] | 0;
+    }function cw(a) {
+      a = a | 0;return c[a + 16 >> 2] | 0;
+    }function dw(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0;f = l;l = l + 32 | 0;d = f;e = c[a >> 2] | 0;if ((c[a + 8 >> 2] | 0) - e >> 2 >>> 0 < b >>> 0) {
+        Rw(d, b, (c[a + 4 >> 2] | 0) - e >> 2, a + 8 | 0);Sw(a, d);Tw(d);
+      }l = f;return;
+    }function ew(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0;h = l;l = l + 32 | 0;d = h;e = a + 4 | 0;f = ((c[e >> 2] | 0) - (c[a >> 2] | 0) >> 2) + 1 | 0;g = Nw(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        i = c[a >> 2] | 0;k = (c[a + 8 >> 2] | 0) - i | 0;j = k >> 1;Rw(d, k >> 2 >>> 0 < g >>> 1 >>> 0 ? j >>> 0 < f >>> 0 ? f : j : g, (c[e >> 2] | 0) - i >> 2, a + 8 | 0);g = d + 8 | 0;c[c[g >> 2] >> 2] = c[b >> 2];c[g >> 2] = (c[g >> 2] | 0) + 4;Sw(a, d);Tw(d);l = h;return;
+      }
+    }function fw(a) {
+      a = a | 0;return c[a + 8 >> 2] | 0;
+    }function gw(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0;h = l;l = l + 32 | 0;d = h;e = a + 4 | 0;f = ((c[e >> 2] | 0) - (c[a >> 2] | 0) >> 2) + 1 | 0;g = Kw(a) | 0;if (g >>> 0 < f >>> 0) jC(a);else {
+        i = c[a >> 2] | 0;k = (c[a + 8 >> 2] | 0) - i | 0;j = k >> 1;Ow(d, k >> 2 >>> 0 < g >>> 1 >>> 0 ? j >>> 0 < f >>> 0 ? f : j : g, (c[e >> 2] | 0) - i >> 2, a + 8 | 0);g = d + 8 | 0;c[c[g >> 2] >> 2] = c[b >> 2];c[g >> 2] = (c[g >> 2] | 0) + 4;Pw(a, d);Qw(d);l = h;return;
+      }
+    }function hw(a) {
+      a = a | 0;return c[a >> 2] | 0;
+    }function iw(a, b, c, d, e, f) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;e = e | 0;f = f | 0;lw(a, b, c, d, e, f);return;
+    }function jw(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -4 - e | 0) >>> 2) << 2);sC(d);
+      }return;
+    }function kw(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -4 - e | 0) >>> 2) << 2);sC(d);
+      }return;
+    }function lw(a, b, d, e, f, g) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;f = f | 0;g = g | 0;var h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;h = l;l = l + 48 | 0;m = h + 40 | 0;i = h + 32 | 0;n = h + 24 | 0;j = h + 12 | 0;k = h;UA(i);a = Sg(a) | 0;c[n >> 2] = c[b >> 2];d = c[d >> 2] | 0;e = c[e >> 2] | 0;mw(j, f);nw(k, g);c[m >> 2] = c[n >> 2];ow(a, m, d, e, j, k);jw(k);kw(j);WA(i);l = h;return;
+    }function mw(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;c[a >> 2] = 0;c[a + 4 >> 2] = 0;c[a + 8 >> 2] = 0;d = b + 4 | 0;e = (c[d >> 2] | 0) - (c[b >> 2] | 0) >> 2;if (e | 0) {
+        Lw(a, e);Mw(a, c[b >> 2] | 0, c[d >> 2] | 0, e);
+      }return;
+    }function nw(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;c[a >> 2] = 0;c[a + 4 >> 2] = 0;c[a + 8 >> 2] = 0;d = b + 4 | 0;e = (c[d >> 2] | 0) - (c[b >> 2] | 0) >> 2;if (e | 0) {
+        Iw(a, e);Jw(a, c[b >> 2] | 0, c[d >> 2] | 0, e);
+      }return;
+    }function ow(a, b, d, e, f, g) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;f = f | 0;g = g | 0;var h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0;h = l;l = l + 32 | 0;m = h + 28 | 0;n = h + 24 | 0;i = h + 12 | 0;j = h;k = Vg(pw() | 0) | 0;c[n >> 2] = c[b >> 2];c[m >> 2] = c[n >> 2];b = qw(m) | 0;d = rw(d) | 0;e = sw(e) | 0;c[i >> 2] = c[f >> 2];m = f + 4 | 0;c[i + 4 >> 2] = c[m >> 2];n = f + 8 | 0;c[i + 8 >> 2] = c[n >> 2];c[n >> 2] = 0;c[m >> 2] = 0;c[f >> 2] = 0;f = tw(i) | 0;c[j >> 2] = c[g >> 2];m = g + 4 | 0;c[j + 4 >> 2] = c[m >> 2];n = g + 8 | 0;c[j + 8 >> 2] = c[n >> 2];c[n >> 2] = 0;c[m >> 2] = 0;c[g >> 2] = 0;Ba(0, k | 0, a | 0, b | 0, d | 0, e | 0, f | 0, uw(j) | 0) | 0;jw(j);kw(i);l = h;return;
+    }function pw() {
+      var b = 0;if (!(a[7968] | 0)) {
+        Gw(10708);b = 7968;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 10708;
+    }function qw(a) {
+      a = a | 0;return yw(a) | 0;
+    }function rw(a) {
+      a = a | 0;return ww(a) | 0;
+    }function sw(a) {
+      a = a | 0;return ul(a) | 0;
+    }function tw(a) {
+      a = a | 0;return xw(a) | 0;
+    }function uw(a) {
+      a = a | 0;return vw(a) | 0;
+    }function vw(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;e = (c[a + 4 >> 2] | 0) - (c[a >> 2] | 0) | 0;d = e >> 2;e = jy(e + 4 | 0) | 0;c[e >> 2] = d;if (d | 0) {
+        b = 0;do {
+          c[e + 4 + (b << 2) >> 2] = ww(c[(c[a >> 2] | 0) + (b << 2) >> 2] | 0) | 0;b = b + 1 | 0;
+        } while ((b | 0) != (d | 0));
+      }return e | 0;
+    }function ww(a) {
+      a = a | 0;return a | 0;
+    }function xw(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;e = (c[a + 4 >> 2] | 0) - (c[a >> 2] | 0) | 0;d = e >> 2;e = jy(e + 4 | 0) | 0;c[e >> 2] = d;if (d | 0) {
+        b = 0;do {
+          c[e + 4 + (b << 2) >> 2] = yw((c[a >> 2] | 0) + (b << 2) | 0) | 0;b = b + 1 | 0;
+        } while ((b | 0) != (d | 0));
+      }return e | 0;
+    }function yw(a) {
+      a = a | 0;var b = 0,
+          c = 0,
+          d = 0,
+          e = 0;e = l;l = l + 32 | 0;b = e + 12 | 0;c = e;d = Di(zw() | 0) | 0;if (!d) a = Aw(a) | 0;else {
+        Ei(b, d);Fi(c, b);RA(a, c);a = Hi(b) | 0;
+      }l = e;return a | 0;
+    }function zw() {
+      var b = 0;if (!(a[7960] | 0)) {
+        Fw(10664);Ha(25, 10664, o | 0) | 0;b = 7960;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 10664;
+    }function Aw(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;d = l;l = l + 16 | 0;f = d + 4 | 0;h = d;e = jy(8) | 0;b = e;i = qC(4) | 0;c[i >> 2] = c[a >> 2];g = b + 4 | 0;c[g >> 2] = i;a = qC(8) | 0;g = c[g >> 2] | 0;c[h >> 2] = 0;c[f >> 2] = c[h >> 2];Bw(a, g, f);c[e >> 2] = a;l = d;return b | 0;
+    }function Bw(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;c[a >> 2] = b;d = qC(16) | 0;c[d + 4 >> 2] = 0;c[d + 8 >> 2] = 0;c[d >> 2] = 1656;c[d + 12 >> 2] = b;c[a + 4 >> 2] = d;return;
+    }function Cw(a) {
+      a = a | 0;kC(a);sC(a);return;
+    }function Dw(a) {
+      a = a | 0;a = c[a + 12 >> 2] | 0;if (a | 0) sC(a);return;
+    }function Ew(a) {
+      a = a | 0;sC(a);return;
+    }function Fw(a) {
+      a = a | 0;Zi(a);return;
+    }function Gw(a) {
+      a = a | 0;fh(a, Hw() | 0, 5);return;
+    }function Hw() {
+      return 1676;
+    }function Iw(a, b) {
+      a = a | 0;b = b | 0;var d = 0;if ((Kw(a) | 0) >>> 0 < b >>> 0) jC(a);if (b >>> 0 > 1073741823) Ta();else {
+        d = qC(b << 2) | 0;c[a + 4 >> 2] = d;c[a >> 2] = d;c[a + 8 >> 2] = d + (b << 2);return;
+      }
+    }function Jw(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;e = a + 4 | 0;a = d - b | 0;if ((a | 0) > 0) {
+        BC(c[e >> 2] | 0, b | 0, a | 0) | 0;c[e >> 2] = (c[e >> 2] | 0) + (a >>> 2 << 2);
+      }return;
+    }function Kw(a) {
+      a = a | 0;return 1073741823;
+    }function Lw(a, b) {
+      a = a | 0;b = b | 0;var d = 0;if ((Nw(a) | 0) >>> 0 < b >>> 0) jC(a);if (b >>> 0 > 1073741823) Ta();else {
+        d = qC(b << 2) | 0;c[a + 4 >> 2] = d;c[a >> 2] = d;c[a + 8 >> 2] = d + (b << 2);return;
+      }
+    }function Mw(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;e = a + 4 | 0;a = d - b | 0;if ((a | 0) > 0) {
+        BC(c[e >> 2] | 0, b | 0, a | 0) | 0;c[e >> 2] = (c[e >> 2] | 0) + (a >>> 2 << 2);
+      }return;
+    }function Nw(a) {
+      a = a | 0;return 1073741823;
+    }function Ow(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 1073741823) Ta();else {
+          f = qC(b << 2) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d << 2) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b << 2);return;
+    }function Pw(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (0 - (f >> 2) << 2) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function Qw(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~((e + -4 - b | 0) >>> 2) << 2);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function Rw(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 1073741823) Ta();else {
+          f = qC(b << 2) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d << 2) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b << 2);return;
+    }function Sw(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (0 - (f >> 2) << 2) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function Tw(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~((e + -4 - b | 0) >>> 2) << 2);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function Uw(a, b, d, e, f) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;f = f | 0;var g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0,
+          p = 0,
+          q = 0,
+          r = 0;r = l;l = l + 32 | 0;m = r + 20 | 0;n = r + 12 | 0;k = r + 16 | 0;o = r + 4 | 0;p = r;q = r + 8 | 0;i = Wv() | 0;g = c[i >> 2] | 0;h = c[g >> 2] | 0;if (h | 0) {
+        j = c[i + 8 >> 2] | 0;i = c[i + 4 >> 2] | 0;while (1) {
+          OA(m, h);Vw(a, m, i, j);g = g + 4 | 0;h = c[g >> 2] | 0;if (!h) break;else {
+            j = j + 1 | 0;i = i + 1 | 0;
+          }
+        }
+      }g = Yv() | 0;h = c[g >> 2] | 0;if (h | 0) do {
+        OA(m, h);c[n >> 2] = c[g + 4 >> 2];Ww(b, m, n);g = g + 8 | 0;h = c[g >> 2] | 0;
+      } while ((h | 0) != 0);g = c[(Iv() | 0) >> 2] | 0;if (g | 0) do {
+        b = c[g + 4 >> 2] | 0;OA(m, c[(Xw(b) | 0) >> 2] | 0);c[n >> 2] = Tv(b) | 0;Yw(d, m, n);g = c[g >> 2] | 0;
+      } while ((g | 0) != 0);OA(k, 0);g = Vv() | 0;c[m >> 2] = c[k >> 2];$v(m, g, f);g = c[(Iv() | 0) >> 2] | 0;if (g | 0) {
+        a = m + 4 | 0;b = m + 8 | 0;d = m + 8 | 0;do {
+          j = c[g + 4 >> 2] | 0;OA(n, c[(Xw(j) | 0) >> 2] | 0);_w(o, Zw(j) | 0);h = c[o >> 2] | 0;if (h | 0) {
+            c[m >> 2] = 0;c[a >> 2] = 0;c[b >> 2] = 0;do {
+              OA(p, c[(Xw(c[h + 4 >> 2] | 0) | 0) >> 2] | 0);i = c[a >> 2] | 0;if (i >>> 0 < (c[d >> 2] | 0) >>> 0) {
+                c[i >> 2] = c[p >> 2];c[a >> 2] = (c[a >> 2] | 0) + 4;
+              } else ew(m, p);h = c[h >> 2] | 0;
+            } while ((h | 0) != 0);$w(e, n, m);kw(m);
+          }c[q >> 2] = c[n >> 2];k = ax(j) | 0;c[m >> 2] = c[q >> 2];$v(m, k, f);Wi(o);g = c[g >> 2] | 0;
+        } while ((g | 0) != 0);
+      }l = r;return;
+    }function Vw(a, b, c, d) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;ox(a, b, c, d);return;
+    }function Ww(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;nx(a, b, c);return;
+    }function Xw(a) {
+      a = a | 0;return a | 0;
+    }function Yw(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;ix(a, b, c);return;
+    }function Zw(a) {
+      a = a | 0;return a + 16 | 0;
+    }function _w(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0;g = l;l = l + 16 | 0;f = g + 8 | 0;d = g;c[a >> 2] = 0;e = c[b >> 2] | 0;c[f >> 2] = e;c[d >> 2] = a;d = gx(d) | 0;if (e | 0) {
+        e = qC(12) | 0;h = (hx(f) | 0) + 4 | 0;a = c[h + 4 >> 2] | 0;b = e + 4 | 0;c[b >> 2] = c[h >> 2];c[b + 4 >> 2] = a;b = c[c[f >> 2] >> 2] | 0;c[f >> 2] = b;if (!b) a = e;else {
+          b = e;while (1) {
+            a = qC(12) | 0;j = (hx(f) | 0) + 4 | 0;i = c[j + 4 >> 2] | 0;h = a + 4 | 0;c[h >> 2] = c[j >> 2];c[h + 4 >> 2] = i;c[b >> 2] = a;h = c[c[f >> 2] >> 2] | 0;c[f >> 2] = h;if (!h) break;else b = a;
+          }
+        }c[a >> 2] = c[d >> 2];c[d >> 2] = e;
+      }l = g;return;
+    }function $w(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;bx(a, b, c);return;
+    }function ax(a) {
+      a = a | 0;return a + 24 | 0;
+    }function bx(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 32 | 0;h = e + 24 | 0;f = e + 16 | 0;i = e + 12 | 0;g = e;UA(f);a = Sg(a) | 0;c[i >> 2] = c[b >> 2];mw(g, d);c[h >> 2] = c[i >> 2];cx(a, h, g);kw(g);WA(f);l = e;return;
+    }function cx(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;e = l;l = l + 32 | 0;h = e + 16 | 0;i = e + 12 | 0;f = e;g = Vg(dx() | 0) | 0;c[i >> 2] = c[b >> 2];c[h >> 2] = c[i >> 2];b = qw(h) | 0;c[f >> 2] = c[d >> 2];h = d + 4 | 0;c[f + 4 >> 2] = c[h >> 2];i = d + 8 | 0;c[f + 8 >> 2] = c[i >> 2];c[i >> 2] = 0;c[h >> 2] = 0;c[d >> 2] = 0;xa(0, g | 0, a | 0, b | 0, tw(f) | 0) | 0;kw(f);l = e;return;
+    }function dx() {
+      var b = 0;if (!(a[7976] | 0)) {
+        ex(10720);b = 7976;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 10720;
+    }function ex(a) {
+      a = a | 0;fh(a, fx() | 0, 2);return;
+    }function fx() {
+      return 1732;
+    }function gx(a) {
+      a = a | 0;return c[a >> 2] | 0;
+    }function hx(a) {
+      a = a | 0;return c[a >> 2] | 0;
+    }function ix(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 32 | 0;g = e + 16 | 0;f = e + 8 | 0;h = e;UA(f);a = Sg(a) | 0;c[h >> 2] = c[b >> 2];d = c[d >> 2] | 0;c[g >> 2] = c[h >> 2];jx(a, g, d);WA(f);l = e;return;
+    }function jx(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 16 | 0;g = e + 4 | 0;h = e;f = Vg(kx() | 0) | 0;c[h >> 2] = c[b >> 2];c[g >> 2] = c[h >> 2];b = qw(g) | 0;xa(0, f | 0, a | 0, b | 0, rw(d) | 0) | 0;l = e;return;
+    }function kx() {
+      var b = 0;if (!(a[7984] | 0)) {
+        lx(10732);b = 7984;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 10732;
+    }function lx(a) {
+      a = a | 0;fh(a, mx() | 0, 2);return;
+    }function mx() {
+      return 1744;
+    }function nx(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = l;l = l + 32 | 0;g = e + 16 | 0;f = e + 8 | 0;h = e;UA(f);a = Sg(a) | 0;c[h >> 2] = c[b >> 2];d = c[d >> 2] | 0;c[g >> 2] = c[h >> 2];jx(a, g, d);WA(f);l = e;return;
+    }function ox(b, d, e, f) {
+      b = b | 0;d = d | 0;e = e | 0;f = f | 0;var g = 0,
+          h = 0,
+          i = 0,
+          j = 0;g = l;l = l + 32 | 0;i = g + 16 | 0;h = g + 8 | 0;j = g;UA(h);b = Sg(b) | 0;c[j >> 2] = c[d >> 2];e = a[e >> 0] | 0;f = a[f >> 0] | 0;c[i >> 2] = c[j >> 2];px(b, i, e, f);WA(h);l = g;return;
+    }function px(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0;f = l;l = l + 16 | 0;h = f + 4 | 0;i = f;g = Vg(qx() | 0) | 0;c[i >> 2] = c[b >> 2];c[h >> 2] = c[i >> 2];b = qw(h) | 0;d = rx(d) | 0;$a(0, g | 0, a | 0, b | 0, d | 0, rx(e) | 0) | 0;l = f;return;
+    }function qx() {
+      var b = 0;if (!(a[7992] | 0)) {
+        tx(10744);b = 7992;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 10744;
+    }function rx(a) {
+      a = a | 0;return sx(a) | 0;
+    }function sx(a) {
+      a = a | 0;return a & 255 | 0;
+    }function tx(a) {
+      a = a | 0;fh(a, ux() | 0, 3);return;
+    }function ux() {
+      return 1756;
+    }function vx(b, d, e) {
+      b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0,
+          p = 0;p = l;l = l + 32 | 0;j = p + 8 | 0;k = p + 4 | 0;m = p + 20 | 0;n = p;mk(b, 0);f = QA(d) | 0;c[j >> 2] = 0;o = j + 4 | 0;c[o >> 2] = 0;c[j + 8 >> 2] = 0;switch (f << 24 >> 24) {case 0:
+          {
+            a[m >> 0] = 0;wx(k, e, m);xx(b, k) | 0;wf(k);break;
+          }case 8:
+          {
+            o = PA(d) | 0;a[m >> 0] = 8;OA(n, c[o + 4 >> 2] | 0);yx(k, e, m, n, o + 8 | 0);xx(b, k) | 0;wf(k);break;
+          }case 9:
+          {
+            h = PA(d) | 0;d = c[h + 4 >> 2] | 0;if (d | 0) {
+              i = j + 8 | 0;g = h + 12 | 0;while (1) {
+                d = d + -1 | 0;OA(k, c[g >> 2] | 0);f = c[o >> 2] | 0;if (f >>> 0 < (c[i >> 2] | 0) >>> 0) {
+                  c[f >> 2] = c[k >> 2];c[o >> 2] = (c[o >> 2] | 0) + 4;
+                } else ew(j, k);if (!d) break;else g = g + 4 | 0;
+              }
+            }a[m >> 0] = 9;OA(n, c[h + 8 >> 2] | 0);zx(k, e, m, n, j);xx(b, k) | 0;wf(k);break;
+          }default:
+          {
+            o = PA(d) | 0;a[m >> 0] = f;OA(n, c[o + 4 >> 2] | 0);Ax(k, e, m, n);xx(b, k) | 0;wf(k);
+          }}kw(j);l = p;return;
+    }function wx(b, c, d) {
+      b = b | 0;c = c | 0;d = d | 0;var e = 0,
+          f = 0;e = l;l = l + 16 | 0;f = e;UA(f);c = Sg(c) | 0;Ox(b, c, a[d >> 0] | 0);WA(f);l = e;return;
+    }function xx(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = c[a >> 2] | 0;if (d | 0) ab(d | 0);c[a >> 2] = c[b >> 2];c[b >> 2] = 0;return a | 0;
+    }function yx(b, d, e, f, g) {
+      b = b | 0;d = d | 0;e = e | 0;f = f | 0;g = g | 0;var h = 0,
+          i = 0,
+          j = 0,
+          k = 0;h = l;l = l + 32 | 0;j = h + 16 | 0;i = h + 8 | 0;k = h;UA(i);d = Sg(d) | 0;e = a[e >> 0] | 0;c[k >> 2] = c[f >> 2];g = c[g >> 2] | 0;c[j >> 2] = c[k >> 2];Kx(b, d, e, j, g);WA(i);l = h;return;
+    }function zx(b, d, e, f, g) {
+      b = b | 0;d = d | 0;e = e | 0;f = f | 0;g = g | 0;var h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;h = l;l = l + 32 | 0;k = h + 24 | 0;i = h + 16 | 0;m = h + 12 | 0;j = h;UA(i);d = Sg(d) | 0;e = a[e >> 0] | 0;c[m >> 2] = c[f >> 2];mw(j, g);c[k >> 2] = c[m >> 2];Gx(b, d, e, k, j);kw(j);WA(i);l = h;return;
+    }function Ax(b, d, e, f) {
+      b = b | 0;d = d | 0;e = e | 0;f = f | 0;var g = 0,
+          h = 0,
+          i = 0,
+          j = 0;g = l;l = l + 32 | 0;i = g + 16 | 0;h = g + 8 | 0;j = g;UA(h);d = Sg(d) | 0;e = a[e >> 0] | 0;c[j >> 2] = c[f >> 2];c[i >> 2] = c[j >> 2];Bx(b, d, e, i);WA(h);l = g;return;
+    }function Bx(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0;f = l;l = l + 16 | 0;g = f + 4 | 0;i = f;h = Vg(Cx() | 0) | 0;d = rx(d) | 0;c[i >> 2] = c[e >> 2];c[g >> 2] = c[i >> 2];Dx(a, xa(0, h | 0, b | 0, d | 0, qw(g) | 0) | 0);l = f;return;
+    }function Cx() {
+      var b = 0;if (!(a[8e3] | 0)) {
+        Ex(10756);b = 8e3;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 10756;
+    }function Dx(a, b) {
+      a = a | 0;b = b | 0;mk(a, b);return;
+    }function Ex(a) {
+      a = a | 0;fh(a, Fx() | 0, 2);return;
+    }function Fx() {
+      return 1772;
+    }function Gx(a, b, d, e, f) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;f = f | 0;var g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0;g = l;l = l + 32 | 0;j = g + 16 | 0;k = g + 12 | 0;h = g;i = Vg(Hx() | 0) | 0;d = rx(d) | 0;c[k >> 2] = c[e >> 2];c[j >> 2] = c[k >> 2];e = qw(j) | 0;c[h >> 2] = c[f >> 2];j = f + 4 | 0;c[h + 4 >> 2] = c[j >> 2];k = f + 8 | 0;c[h + 8 >> 2] = c[k >> 2];c[k >> 2] = 0;c[j >> 2] = 0;c[f >> 2] = 0;Dx(a, $a(0, i | 0, b | 0, d | 0, e | 0, tw(h) | 0) | 0);kw(h);l = g;return;
+    }function Hx() {
+      var b = 0;if (!(a[8008] | 0)) {
+        Ix(10768);b = 8008;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 10768;
+    }function Ix(a) {
+      a = a | 0;fh(a, Jx() | 0, 3);return;
+    }function Jx() {
+      return 1784;
+    }function Kx(a, b, d, e, f) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;f = f | 0;var g = 0,
+          h = 0,
+          i = 0,
+          j = 0;g = l;l = l + 16 | 0;i = g + 4 | 0;j = g;h = Vg(Lx() | 0) | 0;d = rx(d) | 0;c[j >> 2] = c[e >> 2];c[i >> 2] = c[j >> 2];e = qw(i) | 0;Dx(a, $a(0, h | 0, b | 0, d | 0, e | 0, sw(f) | 0) | 0);l = g;return;
+    }function Lx() {
+      var b = 0;if (!(a[8016] | 0)) {
+        Mx(10780);b = 8016;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 10780;
+    }function Mx(a) {
+      a = a | 0;fh(a, Nx() | 0, 3);return;
+    }function Nx() {
+      return 1800;
+    }function Ox(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;var d = 0;d = Vg(Px() | 0) | 0;Dx(a, bb(0, d | 0, b | 0, rx(c) | 0) | 0);return;
+    }function Px() {
+      var b = 0;if (!(a[8024] | 0)) {
+        Qx(10792);b = 8024;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 10792;
+    }function Qx(a) {
+      a = a | 0;fh(a, Rx() | 0, 1);return;
+    }function Rx() {
+      return 1816;
+    }function Sx() {
+      Tx();Ux();Vx();return;
+    }function Tx() {
+      c[2702] = rC(65536) | 0;return;
+    }function Ux() {
+      qy(10856);return;
+    }function Vx() {
+      Wx(10816);return;
+    }function Wx(a) {
+      a = a | 0;Xx(a, 5044);Yx(a) | 0;return;
+    }function Xx(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = zw() | 0;c[a >> 2] = d;ky(d, b);Hv(c[a >> 2] | 0);return;
+    }function Yx(a) {
+      a = a | 0;var b = 0;b = c[a >> 2] | 0;At(b, Zx() | 0);return a | 0;
+    }function Zx() {
+      var b = 0;if (!(a[8032] | 0)) {
+        _x(10820);Ha(64, 10820, o | 0) | 0;b = 8032;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }if (!(si(10820) | 0)) _x(10820);return 10820;
+    }function _x(a) {
+      a = a | 0;by(a);Gt(a, 25);return;
+    }function $x(a) {
+      a = a | 0;ay(a + 24 | 0);return;
+    }function ay(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -8 - e | 0) >>> 3) << 3);sC(d);
+      }return;
+    }function by(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 5, 18, b, gy() | 0, 1);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function cy(a, b) {
+      a = a | 0;b = b | 0;dy(a, b);return;
+    }function dy(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0;d = l;l = l + 16 | 0;e = d;f = d + 4 | 0;c[e >> 2] = Hk(f, b) | 0;ey(a, e);l = d;return;
+    }function ey(b, d) {
+      b = b | 0;d = d | 0;fy(b + 4 | 0, c[d >> 2] | 0);a[b + 8 >> 0] = 1;return;
+    }function fy(a, b) {
+      a = a | 0;b = b | 0;c[a >> 2] = b;return;
+    }function gy() {
+      return 1824;
+    }function hy(a) {
+      a = a | 0;return iy(a) | 0;
+    }function iy(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0;d = l;l = l + 16 | 0;f = d + 4 | 0;h = d;e = jy(8) | 0;b = e;i = qC(4) | 0;fy(i, Hk(f, a) | 0);g = b + 4 | 0;c[g >> 2] = i;a = qC(8) | 0;g = c[g >> 2] | 0;c[h >> 2] = 0;c[f >> 2] = c[h >> 2];Bw(a, g, f);c[e >> 2] = a;l = d;return b | 0;
+    }function jy(a) {
+      a = a | 0;var b = 0,
+          d = 0;a = a + 7 & -8;if (a >>> 0 <= 32768 ? (b = c[2701] | 0, a >>> 0 <= (65536 - b | 0) >>> 0) : 0) {
+        d = (c[2702] | 0) + b | 0;c[2701] = b + a;a = d;
+      } else {
+        a = rC(a + 8 | 0) | 0;c[a >> 2] = c[2703];c[2703] = a;a = a + 8 | 0;
+      }return a | 0;
+    }function ky(a, b) {
+      a = a | 0;b = b | 0;c[a >> 2] = ly() | 0;c[a + 4 >> 2] = my() | 0;c[a + 12 >> 2] = b;c[a + 8 >> 2] = ny() | 0;c[a + 32 >> 2] = 9;return;
+    }function ly() {
+      return 11744;
+    }function my() {
+      return 1832;
+    }function ny() {
+      return cu() | 0;
+    }function oy(a, b, c, d) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;if ((jr(d, 896) | 0) == 512) {
+        if (c | 0) {
+          py(c);sC(c);
+        }
+      } else if (b | 0) sC(b);return;
+    }function py(a) {
+      a = a | 0;a = c[a + 4 >> 2] | 0;if (a | 0) oC(a);return;
+    }function qy(a) {
+      a = a | 0;ry(a, 5052);sy(a) | 0;ty(a, 5058, 26) | 0;uy(a, 5069, 1) | 0;vy(a, 5077, 10) | 0;wy(a, 5087, 19) | 0;yy(a, 5094, 27) | 0;return;
+    }function ry(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = GA() | 0;c[a >> 2] = d;HA(d, b);Hv(c[a >> 2] | 0);return;
+    }function sy(a) {
+      a = a | 0;var b = 0;b = c[a >> 2] | 0;At(b, rA() | 0);return a | 0;
+    }function ty(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;Yz(a, ai(b) | 0, c, 0);return a | 0;
+    }function uy(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;Gz(a, ai(b) | 0, c, 0);return a | 0;
+    }function vy(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;hz(a, ai(b) | 0, c, 0);return a | 0;
+    }function wy(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;Ry(a, ai(b) | 0, c, 0);return a | 0;
+    }function xy(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;a: while (1) {
+        d = c[2703] | 0;while (1) {
+          if ((d | 0) == (b | 0)) break a;e = c[d >> 2] | 0;c[2703] = e;if (!d) d = e;else break;
+        }sC(d);
+      }c[2701] = a;return;
+    }function yy(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;zy(a, ai(b) | 0, c, 0);return a | 0;
+    }function zy(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0;g = c[a >> 2] | 0;f = Ay() | 0;a = By(d) | 0;fi(g, b, f, a, Cy(d, e) | 0, e);return;
+    }function Ay() {
+      var b = 0,
+          d = 0;if (!(a[8040] | 0)) {
+        Jy(10860);Ha(65, 10860, o | 0) | 0;d = 8040;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(10860) | 0)) {
+        b = 10860;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));Jy(10860);
+      }return 10860;
+    }function By(a) {
+      a = a | 0;return a | 0;
+    }function Cy(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0;i = l;l = l + 16 | 0;f = i;g = i + 4 | 0;c[f >> 2] = a;j = Ay() | 0;h = j + 24 | 0;b = ji(b, 4) | 0;c[g >> 2] = b;d = j + 28 | 0;e = c[d >> 2] | 0;if (e >>> 0 < (c[j + 32 >> 2] | 0) >>> 0) {
+        Dy(e, a, b);b = (c[d >> 2] | 0) + 8 | 0;c[d >> 2] = b;
+      } else {
+        Ey(h, f, g);b = c[d >> 2] | 0;
+      }l = i;return (b - (c[h >> 2] | 0) >> 3) + -1 | 0;
+    }function Dy(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;c[a >> 2] = b;c[a + 4 >> 2] = d;return;
+    }function Ey(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;i = l;l = l + 32 | 0;f = i;g = a + 4 | 0;h = ((c[g >> 2] | 0) - (c[a >> 2] | 0) >> 3) + 1 | 0;e = Fy(a) | 0;if (e >>> 0 < h >>> 0) jC(a);else {
+        j = c[a >> 2] | 0;m = (c[a + 8 >> 2] | 0) - j | 0;k = m >> 2;Gy(f, m >> 3 >>> 0 < e >>> 1 >>> 0 ? k >>> 0 < h >>> 0 ? h : k : e, (c[g >> 2] | 0) - j >> 3, a + 8 | 0);h = f + 8 | 0;Dy(c[h >> 2] | 0, c[b >> 2] | 0, c[d >> 2] | 0);c[h >> 2] = (c[h >> 2] | 0) + 8;Hy(a, f);Iy(f);l = i;return;
+      }
+    }function Fy(a) {
+      a = a | 0;return 536870911;
+    }function Gy(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 536870911) Ta();else {
+          f = qC(b << 3) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d << 3) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b << 3);return;
+    }function Hy(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (0 - (f >> 3) << 3) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function Iy(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~((e + -8 - b | 0) >>> 3) << 3);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function Jy(a) {
+      a = a | 0;My(a);return;
+    }function Ky(a) {
+      a = a | 0;Ly(a + 24 | 0);return;
+    }function Ly(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -8 - e | 0) >>> 3) << 3);sC(d);
+      }return;
+    }function My(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 1, 11, b, Ny() | 0, 2);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function Ny() {
+      return 1840;
+    }function Oy(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;Qy(c[(Py(a) | 0) >> 2] | 0, b, d);return;
+    }function Py(a) {
+      a = a | 0;return (c[(Ay() | 0) + 24 >> 2] | 0) + (a << 3) | 0;
+    }function Qy(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;var d = 0,
+          e = 0,
+          f = 0;d = l;l = l + 16 | 0;f = d + 1 | 0;e = d;b = Hk(f, b) | 0;c = Hk(e, c) | 0;ob[a & 31](b, c);l = d;return;
+    }function Ry(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0;g = c[a >> 2] | 0;f = Sy() | 0;a = Ty(d) | 0;fi(g, b, f, a, Uy(d, e) | 0, e);return;
+    }function Sy() {
+      var b = 0,
+          d = 0;if (!(a[8048] | 0)) {
+        $y(10896);Ha(66, 10896, o | 0) | 0;d = 8048;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(10896) | 0)) {
+        b = 10896;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));$y(10896);
+      }return 10896;
+    }function Ty(a) {
+      a = a | 0;return a | 0;
+    }function Uy(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0;i = l;l = l + 16 | 0;f = i;g = i + 4 | 0;c[f >> 2] = a;j = Sy() | 0;h = j + 24 | 0;b = ji(b, 4) | 0;c[g >> 2] = b;d = j + 28 | 0;e = c[d >> 2] | 0;if (e >>> 0 < (c[j + 32 >> 2] | 0) >>> 0) {
+        Vy(e, a, b);b = (c[d >> 2] | 0) + 8 | 0;c[d >> 2] = b;
+      } else {
+        Wy(h, f, g);b = c[d >> 2] | 0;
+      }l = i;return (b - (c[h >> 2] | 0) >> 3) + -1 | 0;
+    }function Vy(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;c[a >> 2] = b;c[a + 4 >> 2] = d;return;
+    }function Wy(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;i = l;l = l + 32 | 0;f = i;g = a + 4 | 0;h = ((c[g >> 2] | 0) - (c[a >> 2] | 0) >> 3) + 1 | 0;e = Xy(a) | 0;if (e >>> 0 < h >>> 0) jC(a);else {
+        j = c[a >> 2] | 0;m = (c[a + 8 >> 2] | 0) - j | 0;k = m >> 2;Yy(f, m >> 3 >>> 0 < e >>> 1 >>> 0 ? k >>> 0 < h >>> 0 ? h : k : e, (c[g >> 2] | 0) - j >> 3, a + 8 | 0);h = f + 8 | 0;Vy(c[h >> 2] | 0, c[b >> 2] | 0, c[d >> 2] | 0);c[h >> 2] = (c[h >> 2] | 0) + 8;Zy(a, f);_y(f);l = i;return;
+      }
+    }function Xy(a) {
+      a = a | 0;return 536870911;
+    }function Yy(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 536870911) Ta();else {
+          f = qC(b << 3) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d << 3) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b << 3);return;
+    }function Zy(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (0 - (f >> 3) << 3) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function _y(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~((e + -8 - b | 0) >>> 3) << 3);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function $y(a) {
+      a = a | 0;cz(a);return;
+    }function az(a) {
+      a = a | 0;bz(a + 24 | 0);return;
+    }function bz(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -8 - e | 0) >>> 3) << 3);sC(d);
+      }return;
+    }function cz(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 1, 11, b, dz() | 0, 1);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function dz() {
+      return 1852;
+    }function ez(a, b) {
+      a = a | 0;b = b | 0;return gz(c[(fz(a) | 0) >> 2] | 0, b) | 0;
+    }function fz(a) {
+      a = a | 0;return (c[(Sy() | 0) + 24 >> 2] | 0) + (a << 3) | 0;
+    }function gz(a, b) {
+      a = a | 0;b = b | 0;var c = 0,
+          d = 0;c = l;l = l + 16 | 0;d = c;b = Hk(d, b) | 0;b = ul(pb[a & 31](b) | 0) | 0;l = c;return b | 0;
+    }function hz(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0;g = c[a >> 2] | 0;f = iz() | 0;a = jz(d) | 0;fi(g, b, f, a, kz(d, e) | 0, e);return;
+    }function iz() {
+      var b = 0,
+          d = 0;if (!(a[8056] | 0)) {
+        rz(10932);Ha(67, 10932, o | 0) | 0;d = 8056;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(10932) | 0)) {
+        b = 10932;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));rz(10932);
+      }return 10932;
+    }function jz(a) {
+      a = a | 0;return a | 0;
+    }function kz(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0;i = l;l = l + 16 | 0;f = i;g = i + 4 | 0;c[f >> 2] = a;j = iz() | 0;h = j + 24 | 0;b = ji(b, 4) | 0;c[g >> 2] = b;d = j + 28 | 0;e = c[d >> 2] | 0;if (e >>> 0 < (c[j + 32 >> 2] | 0) >>> 0) {
+        lz(e, a, b);b = (c[d >> 2] | 0) + 8 | 0;c[d >> 2] = b;
+      } else {
+        mz(h, f, g);b = c[d >> 2] | 0;
+      }l = i;return (b - (c[h >> 2] | 0) >> 3) + -1 | 0;
+    }function lz(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;c[a >> 2] = b;c[a + 4 >> 2] = d;return;
+    }function mz(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;i = l;l = l + 32 | 0;f = i;g = a + 4 | 0;h = ((c[g >> 2] | 0) - (c[a >> 2] | 0) >> 3) + 1 | 0;e = nz(a) | 0;if (e >>> 0 < h >>> 0) jC(a);else {
+        j = c[a >> 2] | 0;m = (c[a + 8 >> 2] | 0) - j | 0;k = m >> 2;oz(f, m >> 3 >>> 0 < e >>> 1 >>> 0 ? k >>> 0 < h >>> 0 ? h : k : e, (c[g >> 2] | 0) - j >> 3, a + 8 | 0);h = f + 8 | 0;lz(c[h >> 2] | 0, c[b >> 2] | 0, c[d >> 2] | 0);c[h >> 2] = (c[h >> 2] | 0) + 8;pz(a, f);qz(f);l = i;return;
+      }
+    }function nz(a) {
+      a = a | 0;return 536870911;
+    }function oz(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 536870911) Ta();else {
+          f = qC(b << 3) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d << 3) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b << 3);return;
+    }function pz(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (0 - (f >> 3) << 3) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function qz(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~((e + -8 - b | 0) >>> 3) << 3);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function rz(a) {
+      a = a | 0;uz(a);return;
+    }function sz(a) {
+      a = a | 0;tz(a + 24 | 0);return;
+    }function tz(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -8 - e | 0) >>> 3) << 3);sC(d);
+      }return;
+    }function uz(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 1, 7, b, vz() | 0, 2);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function vz() {
+      return 1860;
+    }function wz(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;return yz(c[(xz(a) | 0) >> 2] | 0, b, d) | 0;
+    }function xz(a) {
+      a = a | 0;return (c[(iz() | 0) + 24 >> 2] | 0) + (a << 3) | 0;
+    }function yz(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0;e = l;l = l + 32 | 0;h = e + 12 | 0;g = e + 8 | 0;i = e;j = e + 16 | 0;f = e + 4 | 0;Az(i, j, b);ik(f, d);d = jk(f, d) | 0;c[h >> 2] = c[i >> 2];Eb[a & 15](g, h, d);d = Bz(g) | 0;wf(g);kk(f);l = e;return d | 0;
+    }function Az(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;Cz(a, c);return;
+    }function Bz(a) {
+      a = a | 0;return Sg(a) | 0;
+    }function Cz(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0;f = l;l = l + 16 | 0;d = f;e = b;if (!(e & 1)) c[a >> 2] = c[b >> 2];else {
+        Dz(d, 0);Ja(e | 0, d | 0) | 0;Ez(a, d);Fz(d);
+      }l = f;return;
+    }function Dz(b, d) {
+      b = b | 0;d = d | 0;ah(b, d);c[b + 4 >> 2] = 0;a[b + 8 >> 0] = 0;return;
+    }function Ez(a, b) {
+      a = a | 0;b = b | 0;c[a >> 2] = c[b + 4 >> 2];return;
+    }function Fz(b) {
+      b = b | 0;a[b + 8 >> 0] = 0;return;
+    }function Gz(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0;g = c[a >> 2] | 0;f = Hz() | 0;a = Iz(d) | 0;fi(g, b, f, a, Jz(d, e) | 0, e);return;
+    }function Hz() {
+      var b = 0,
+          d = 0;if (!(a[8064] | 0)) {
+        Qz(10968);Ha(68, 10968, o | 0) | 0;d = 8064;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(10968) | 0)) {
+        b = 10968;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));Qz(10968);
+      }return 10968;
+    }function Iz(a) {
+      a = a | 0;return a | 0;
+    }function Jz(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0;i = l;l = l + 16 | 0;f = i;g = i + 4 | 0;c[f >> 2] = a;j = Hz() | 0;h = j + 24 | 0;b = ji(b, 4) | 0;c[g >> 2] = b;d = j + 28 | 0;e = c[d >> 2] | 0;if (e >>> 0 < (c[j + 32 >> 2] | 0) >>> 0) {
+        Kz(e, a, b);b = (c[d >> 2] | 0) + 8 | 0;c[d >> 2] = b;
+      } else {
+        Lz(h, f, g);b = c[d >> 2] | 0;
+      }l = i;return (b - (c[h >> 2] | 0) >> 3) + -1 | 0;
+    }function Kz(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;c[a >> 2] = b;c[a + 4 >> 2] = d;return;
+    }function Lz(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;i = l;l = l + 32 | 0;f = i;g = a + 4 | 0;h = ((c[g >> 2] | 0) - (c[a >> 2] | 0) >> 3) + 1 | 0;e = Mz(a) | 0;if (e >>> 0 < h >>> 0) jC(a);else {
+        j = c[a >> 2] | 0;m = (c[a + 8 >> 2] | 0) - j | 0;k = m >> 2;Nz(f, m >> 3 >>> 0 < e >>> 1 >>> 0 ? k >>> 0 < h >>> 0 ? h : k : e, (c[g >> 2] | 0) - j >> 3, a + 8 | 0);h = f + 8 | 0;Kz(c[h >> 2] | 0, c[b >> 2] | 0, c[d >> 2] | 0);c[h >> 2] = (c[h >> 2] | 0) + 8;Oz(a, f);Pz(f);l = i;return;
+      }
+    }function Mz(a) {
+      a = a | 0;return 536870911;
+    }function Nz(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 536870911) Ta();else {
+          f = qC(b << 3) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d << 3) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b << 3);return;
+    }function Oz(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (0 - (f >> 3) << 3) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function Pz(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~((e + -8 - b | 0) >>> 3) << 3);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function Qz(a) {
+      a = a | 0;Tz(a);return;
+    }function Rz(a) {
+      a = a | 0;Sz(a + 24 | 0);return;
+    }function Sz(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -8 - e | 0) >>> 3) << 3);sC(d);
+      }return;
+    }function Tz(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 1, 1, b, Uz() | 0, 5);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function Uz() {
+      return 1872;
+    }function Vz(a, b, d, e, f, g) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;f = f | 0;g = g | 0;Xz(c[(Wz(a) | 0) >> 2] | 0, b, d, e, f, g);return;
+    }function Wz(a) {
+      a = a | 0;return (c[(Hz() | 0) + 24 >> 2] | 0) + (a << 3) | 0;
+    }function Xz(a, b, c, d, e, f) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;e = e | 0;f = f | 0;var g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;g = l;l = l + 32 | 0;h = g + 16 | 0;i = g + 12 | 0;j = g + 8 | 0;k = g + 4 | 0;m = g;ik(h, b);b = jk(h, b) | 0;ik(i, c);c = jk(i, c) | 0;ik(j, d);d = jk(j, d) | 0;ik(k, e);e = jk(k, e) | 0;ik(m, f);f = jk(m, f) | 0;jb[a & 1](b, c, d, e, f);kk(m);kk(k);kk(j);kk(i);kk(h);l = g;return;
+    }function Yz(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0;g = c[a >> 2] | 0;f = Zz() | 0;a = _z(d) | 0;fi(g, b, f, a, $z(d, e) | 0, e);return;
+    }function Zz() {
+      var b = 0,
+          d = 0;if (!(a[8072] | 0)) {
+        gA(11004);Ha(69, 11004, o | 0) | 0;d = 8072;c[d >> 2] = 1;c[d + 4 >> 2] = 0;
+      }if (!(si(11004) | 0)) {
+        b = 11004;d = b + 36 | 0;do {
+          c[b >> 2] = 0;b = b + 4 | 0;
+        } while ((b | 0) < (d | 0));gA(11004);
+      }return 11004;
+    }function _z(a) {
+      a = a | 0;return a | 0;
+    }function $z(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0;i = l;l = l + 16 | 0;f = i;g = i + 4 | 0;c[f >> 2] = a;j = Zz() | 0;h = j + 24 | 0;b = ji(b, 4) | 0;c[g >> 2] = b;d = j + 28 | 0;e = c[d >> 2] | 0;if (e >>> 0 < (c[j + 32 >> 2] | 0) >>> 0) {
+        aA(e, a, b);b = (c[d >> 2] | 0) + 8 | 0;c[d >> 2] = b;
+      } else {
+        bA(h, f, g);b = c[d >> 2] | 0;
+      }l = i;return (b - (c[h >> 2] | 0) >> 3) + -1 | 0;
+    }function aA(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;c[a >> 2] = b;c[a + 4 >> 2] = d;return;
+    }function bA(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0;i = l;l = l + 32 | 0;f = i;g = a + 4 | 0;h = ((c[g >> 2] | 0) - (c[a >> 2] | 0) >> 3) + 1 | 0;e = cA(a) | 0;if (e >>> 0 < h >>> 0) jC(a);else {
+        j = c[a >> 2] | 0;m = (c[a + 8 >> 2] | 0) - j | 0;k = m >> 2;dA(f, m >> 3 >>> 0 < e >>> 1 >>> 0 ? k >>> 0 < h >>> 0 ? h : k : e, (c[g >> 2] | 0) - j >> 3, a + 8 | 0);h = f + 8 | 0;aA(c[h >> 2] | 0, c[b >> 2] | 0, c[d >> 2] | 0);c[h >> 2] = (c[h >> 2] | 0) + 8;eA(a, f);fA(f);l = i;return;
+      }
+    }function cA(a) {
+      a = a | 0;return 536870911;
+    }function dA(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0;c[a + 12 >> 2] = 0;c[a + 16 >> 2] = e;do if (b) {
+        if (b >>> 0 > 536870911) Ta();else {
+          f = qC(b << 3) | 0;break;
+        }
+      } else f = 0; while (0);c[a >> 2] = f;e = f + (d << 3) | 0;c[a + 8 >> 2] = e;c[a + 4 >> 2] = e;c[a + 12 >> 2] = f + (b << 3);return;
+    }function eA(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;e = c[a >> 2] | 0;h = a + 4 | 0;g = b + 4 | 0;f = (c[h >> 2] | 0) - e | 0;d = (c[g >> 2] | 0) + (0 - (f >> 3) << 3) | 0;c[g >> 2] = d;if ((f | 0) > 0) {
+        BC(d | 0, e | 0, f | 0) | 0;e = g;d = c[g >> 2] | 0;
+      } else e = g;g = c[a >> 2] | 0;c[a >> 2] = d;c[e >> 2] = g;g = b + 8 | 0;f = c[h >> 2] | 0;c[h >> 2] = c[g >> 2];c[g >> 2] = f;g = a + 8 | 0;h = b + 12 | 0;a = c[g >> 2] | 0;c[g >> 2] = c[h >> 2];c[h >> 2] = a;c[b >> 2] = c[e >> 2];return;
+    }function fA(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;b = c[a + 4 >> 2] | 0;d = a + 8 | 0;e = c[d >> 2] | 0;if ((e | 0) != (b | 0)) c[d >> 2] = e + (~((e + -8 - b | 0) >>> 3) << 3);a = c[a >> 2] | 0;if (a | 0) sC(a);return;
+    }function gA(a) {
+      a = a | 0;jA(a);return;
+    }function hA(a) {
+      a = a | 0;iA(a + 24 | 0);return;
+    }function iA(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -8 - e | 0) >>> 3) << 3);sC(d);
+      }return;
+    }function jA(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 1, 12, b, kA() | 0, 2);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function kA() {
+      return 1896;
+    }function lA(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;nA(c[(mA(a) | 0) >> 2] | 0, b, d);return;
+    }function mA(a) {
+      a = a | 0;return (c[(Zz() | 0) + 24 >> 2] | 0) + (a << 3) | 0;
+    }function nA(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;var d = 0,
+          e = 0,
+          f = 0;d = l;l = l + 16 | 0;f = d + 4 | 0;e = d;b = pA(f, b) | 0;ik(e, c);c = jk(e, c) | 0;ob[a & 31](b, c);kk(e);l = d;return;
+    }function pA(a, b) {
+      a = a | 0;b = b | 0;return qA(b) | 0;
+    }function qA(a) {
+      a = a | 0;return a | 0;
+    }function rA() {
+      var b = 0;if (!(a[8080] | 0)) {
+        sA(11040);Ha(70, 11040, o | 0) | 0;b = 8080;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }if (!(si(11040) | 0)) sA(11040);return 11040;
+    }function sA(a) {
+      a = a | 0;vA(a);Gt(a, 71);return;
+    }function tA(a) {
+      a = a | 0;uA(a + 24 | 0);return;
+    }function uA(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0;d = c[a >> 2] | 0;e = d;if (d | 0) {
+        a = a + 4 | 0;b = c[a >> 2] | 0;if ((b | 0) != (d | 0)) c[a >> 2] = b + (~((b + -8 - e | 0) >>> 3) << 3);sC(d);
+      }return;
+    }function vA(a) {
+      a = a | 0;var b = 0;b = vi() | 0;yi(a, 5, 7, b, zA() | 0, 0);c[a + 24 >> 2] = 0;c[a + 28 >> 2] = 0;c[a + 32 >> 2] = 0;return;
+    }function wA(a) {
+      a = a | 0;xA(a);return;
+    }function xA(a) {
+      a = a | 0;yA(a);return;
+    }function yA(b) {
+      b = b | 0;a[b + 8 >> 0] = 1;return;
+    }function zA() {
+      return 1936;
+    }function AA() {
+      return BA() | 0;
+    }function BA() {
+      var a = 0,
+          b = 0,
+          d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0;b = l;l = l + 16 | 0;f = b + 4 | 0;h = b;d = jy(8) | 0;a = d;g = a + 4 | 0;c[g >> 2] = qC(1) | 0;e = qC(8) | 0;g = c[g >> 2] | 0;c[h >> 2] = 0;c[f >> 2] = c[h >> 2];CA(e, g, f);c[d >> 2] = e;l = b;return a | 0;
+    }function CA(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;c[a >> 2] = b;d = qC(16) | 0;c[d + 4 >> 2] = 0;c[d + 8 >> 2] = 0;c[d >> 2] = 1916;c[d + 12 >> 2] = b;c[a + 4 >> 2] = d;return;
+    }function DA(a) {
+      a = a | 0;kC(a);sC(a);return;
+    }function EA(a) {
+      a = a | 0;a = c[a + 12 >> 2] | 0;if (a | 0) sC(a);return;
+    }function FA(a) {
+      a = a | 0;sC(a);return;
+    }function GA() {
+      var b = 0;if (!(a[8088] | 0)) {
+        NA(11076);Ha(25, 11076, o | 0) | 0;b = 8088;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 11076;
+    }function HA(a, b) {
+      a = a | 0;b = b | 0;c[a >> 2] = IA() | 0;c[a + 4 >> 2] = JA() | 0;c[a + 12 >> 2] = b;c[a + 8 >> 2] = KA() | 0;c[a + 32 >> 2] = 10;return;
+    }function IA() {
+      return 11745;
+    }function JA() {
+      return 1940;
+    }function KA() {
+      return lr() | 0;
+    }function LA(a, b, c, d) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;if ((jr(d, 896) | 0) == 512) {
+        if (c | 0) {
+          MA(c);sC(c);
+        }
+      } else if (b | 0) sC(b);return;
+    }function MA(a) {
+      a = a | 0;a = c[a + 4 >> 2] | 0;if (a | 0) oC(a);return;
+    }function NA(a) {
+      a = a | 0;Zi(a);return;
+    }function OA(a, b) {
+      a = a | 0;b = b | 0;c[a >> 2] = b;return;
+    }function PA(a) {
+      a = a | 0;return c[a >> 2] | 0;
+    }function QA(b) {
+      b = b | 0;return a[c[b >> 2] >> 0] | 0;
+    }function RA(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;d = l;l = l + 16 | 0;e = d;c[e >> 2] = c[a >> 2];SA(b, e) | 0;l = d;return;
+    }function SA(a, b) {
+      a = a | 0;b = b | 0;var d = 0;d = TA(c[a >> 2] | 0, b) | 0;b = a + 4 | 0;c[(c[b >> 2] | 0) + 8 >> 2] = d;return c[(c[b >> 2] | 0) + 8 >> 2] | 0;
+    }function TA(a, b) {
+      a = a | 0;b = b | 0;var d = 0,
+          e = 0;d = l;l = l + 16 | 0;e = d;UA(e);a = Sg(a) | 0;b = VA(a, c[b >> 2] | 0) | 0;WA(e);l = d;return b | 0;
+    }function UA(a) {
+      a = a | 0;c[a >> 2] = c[2701];c[a + 4 >> 2] = c[2703];return;
+    }function VA(a, b) {
+      a = a | 0;b = b | 0;var c = 0;c = Vg(XA() | 0) | 0;return bb(0, c | 0, a | 0, sw(b) | 0) | 0;
+    }function WA(a) {
+      a = a | 0;xy(c[a >> 2] | 0, c[a + 4 >> 2] | 0);return;
+    }function XA() {
+      var b = 0;if (!(a[8096] | 0)) {
+        YA(11120);b = 8096;c[b >> 2] = 1;c[b + 4 >> 2] = 0;
+      }return 11120;
+    }function YA(a) {
+      a = a | 0;fh(a, ZA() | 0, 1);return;
+    }function ZA() {
+      return 1948;
+    }function _A() {
+      $A();return;
+    }function $A() {
+      var b = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0,
+          p = 0,
+          q = 0,
+          r = 0,
+          s = 0,
+          t = 0;s = l;l = l + 16 | 0;o = s + 4 | 0;p = s;Ea(65536, 10804, c[2702] | 0, 10812);f = Wv() | 0;e = c[f >> 2] | 0;b = c[e >> 2] | 0;if (b | 0) {
+        g = c[f + 8 >> 2] | 0;f = c[f + 4 >> 2] | 0;while (1) {
+          Ma(b | 0, d[f >> 0] | 0 | 0, a[g >> 0] | 0);e = e + 4 | 0;b = c[e >> 2] | 0;if (!b) break;else {
+            g = g + 1 | 0;f = f + 1 | 0;
+          }
+        }
+      }b = Yv() | 0;e = c[b >> 2] | 0;if (e | 0) do {
+        Na(e | 0, c[b + 4 >> 2] | 0);b = b + 8 | 0;e = c[b >> 2] | 0;
+      } while ((e | 0) != 0);Na(aB() | 0, 5167);n = Iv() | 0;b = c[n >> 2] | 0;a: do if (b | 0) {
+        do {
+          bB(c[b + 4 >> 2] | 0);b = c[b >> 2] | 0;
+        } while ((b | 0) != 0);b = c[n >> 2] | 0;if (b | 0) {
+          m = n;do {
+            while (1) {
+              h = b;b = c[b >> 2] | 0;h = c[h + 4 >> 2] | 0;if (!(cB(h) | 0)) break;c[p >> 2] = m;c[o >> 2] = c[p >> 2];dB(n, o) | 0;if (!b) break a;
+            }eB(h);m = c[m >> 2] | 0;e = fB(h) | 0;i = Va() | 0;j = l;l = l + ((1 * (e << 2) | 0) + 15 & -16) | 0;k = l;l = l + ((1 * (e << 2) | 0) + 15 & -16) | 0;e = c[(Zw(h) | 0) >> 2] | 0;if (e | 0) {
+              f = j;g = k;while (1) {
+                c[f >> 2] = c[(Xw(c[e + 4 >> 2] | 0) | 0) >> 2];c[g >> 2] = c[e + 8 >> 2];e = c[e >> 2] | 0;if (!e) break;else {
+                  f = f + 4 | 0;g = g + 4 | 0;
+                }
+              }
+            }t = Xw(h) | 0;e = gB(h) | 0;f = fB(h) | 0;g = hB(h) | 0;Ra(t | 0, e | 0, j | 0, k | 0, f | 0, g | 0, Tv(h) | 0);Ga(i | 0);
+          } while ((b | 0) != 0);
+        }
+      } while (0);b = c[(Vv() | 0) >> 2] | 0;if (b | 0) do {
+        t = b + 4 | 0;n = aw(t) | 0;h = fw(n) | 0;i = bw(n) | 0;j = (cw(n) | 0) + 1 | 0;k = iB(n) | 0;m = jB(t) | 0;n = si(n) | 0;o = hw(t) | 0;p = kB(t) | 0;Pa(0, h | 0, i | 0, j | 0, k | 0, m | 0, n | 0, o | 0, p | 0, lB(t) | 0);b = c[b >> 2] | 0;
+      } while ((b | 0) != 0);b = c[(Iv() | 0) >> 2] | 0;b: do if (b | 0) {
+        c: while (1) {
+          e = c[b + 4 >> 2] | 0;if (e | 0 ? (q = c[(Xw(e) | 0) >> 2] | 0, r = c[(ax(e) | 0) >> 2] | 0, r | 0) : 0) {
+            f = r;do {
+              e = f + 4 | 0;g = aw(e) | 0;d: do if (g | 0) switch (si(g) | 0) {case 0:
+                  break c;case 4:case 3:case 2:
+                  {
+                    k = fw(g) | 0;m = bw(g) | 0;n = (cw(g) | 0) + 1 | 0;o = iB(g) | 0;p = si(g) | 0;t = hw(e) | 0;Pa(q | 0, k | 0, m | 0, n | 0, o | 0, 0, p | 0, t | 0, kB(e) | 0, lB(e) | 0);break d;
+                  }case 1:
+                  {
+                    j = fw(g) | 0;k = bw(g) | 0;m = (cw(g) | 0) + 1 | 0;n = iB(g) | 0;o = jB(e) | 0;p = si(g) | 0;t = hw(e) | 0;Pa(q | 0, j | 0, k | 0, m | 0, n | 0, o | 0, p | 0, t | 0, kB(e) | 0, lB(e) | 0);break d;
+                  }case 5:
+                  {
+                    n = fw(g) | 0;o = bw(g) | 0;p = (cw(g) | 0) + 1 | 0;t = iB(g) | 0;Pa(q | 0, n | 0, o | 0, p | 0, t | 0, mB(g) | 0, si(g) | 0, 0, 0, 0);break d;
+                  }default:
+                  break d;} while (0);f = c[f >> 2] | 0;
+            } while ((f | 0) != 0);
+          }b = c[b >> 2] | 0;if (!b) break b;
+        }Ta();
+      } while (0);Sa();l = s;return;
+    }function aB() {
+      return 11703;
+    }function bB(b) {
+      b = b | 0;a[b + 40 >> 0] = 0;return;
+    }function cB(b) {
+      b = b | 0;return (a[b + 40 >> 0] | 0) != 0 | 0;
+    }function dB(a, b) {
+      a = a | 0;b = b | 0;b = nB(b) | 0;a = c[b >> 2] | 0;c[b >> 2] = c[a >> 2];sC(a);return c[b >> 2] | 0;
+    }function eB(b) {
+      b = b | 0;a[b + 40 >> 0] = 1;return;
+    }function fB(a) {
+      a = a | 0;return c[a + 20 >> 2] | 0;
+    }function gB(a) {
+      a = a | 0;return c[a + 8 >> 2] | 0;
+    }function hB(a) {
+      a = a | 0;return c[a + 32 >> 2] | 0;
+    }function iB(a) {
+      a = a | 0;return c[a + 4 >> 2] | 0;
+    }function jB(a) {
+      a = a | 0;return c[a + 4 >> 2] | 0;
+    }function kB(a) {
+      a = a | 0;return c[a + 8 >> 2] | 0;
+    }function lB(a) {
+      a = a | 0;return c[a + 16 >> 2] | 0;
+    }function mB(a) {
+      a = a | 0;return c[a + 20 >> 2] | 0;
+    }function nB(a) {
+      a = a | 0;return c[a >> 2] | 0;
+    }
+    function oB(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0,
+          p = 0,
+          q = 0,
+          r = 0,
+          s = 0,
+          t = 0,
+          u = 0,
+          v = 0,
+          w = 0,
+          x = 0;x = l;l = l + 16 | 0;o = x;do if (a >>> 0 < 245) {
+        k = a >>> 0 < 11 ? 16 : a + 11 & -8;a = k >>> 3;n = c[2783] | 0;d = n >>> a;if (d & 3 | 0) {
+          b = (d & 1 ^ 1) + a | 0;a = 11172 + (b << 1 << 2) | 0;d = a + 8 | 0;e = c[d >> 2] | 0;f = e + 8 | 0;g = c[f >> 2] | 0;if ((a | 0) == (g | 0)) c[2783] = n & ~(1 << b);else {
+            c[g + 12 >> 2] = a;c[d >> 2] = g;
+          }w = b << 3;c[e + 4 >> 2] = w | 3;w = e + w + 4 | 0;c[w >> 2] = c[w >> 2] | 1;w = f;l = x;return w | 0;
+        }m = c[2785] | 0;if (k >>> 0 > m >>> 0) {
+          if (d | 0) {
+            b = 2 << a;b = d << a & (b | 0 - b);b = (b & 0 - b) + -1 | 0;h = b >>> 12 & 16;b = b >>> h;d = b >>> 5 & 8;b = b >>> d;f = b >>> 2 & 4;b = b >>> f;a = b >>> 1 & 2;b = b >>> a;e = b >>> 1 & 1;e = (d | h | f | a | e) + (b >>> e) | 0;b = 11172 + (e << 1 << 2) | 0;a = b + 8 | 0;f = c[a >> 2] | 0;h = f + 8 | 0;d = c[h >> 2] | 0;if ((b | 0) == (d | 0)) {
+              a = n & ~(1 << e);c[2783] = a;
+            } else {
+              c[d + 12 >> 2] = b;c[a >> 2] = d;a = n;
+            }g = (e << 3) - k | 0;c[f + 4 >> 2] = k | 3;e = f + k | 0;c[e + 4 >> 2] = g | 1;c[e + g >> 2] = g;if (m | 0) {
+              f = c[2788] | 0;b = m >>> 3;d = 11172 + (b << 1 << 2) | 0;b = 1 << b;if (!(a & b)) {
+                c[2783] = a | b;b = d;a = d + 8 | 0;
+              } else {
+                a = d + 8 | 0;b = c[a >> 2] | 0;
+              }c[a >> 2] = f;c[b + 12 >> 2] = f;c[f + 8 >> 2] = b;c[f + 12 >> 2] = d;
+            }c[2785] = g;c[2788] = e;w = h;l = x;return w | 0;
+          }i = c[2784] | 0;if (i) {
+            d = (i & 0 - i) + -1 | 0;h = d >>> 12 & 16;d = d >>> h;g = d >>> 5 & 8;d = d >>> g;j = d >>> 2 & 4;d = d >>> j;e = d >>> 1 & 2;d = d >>> e;a = d >>> 1 & 1;a = c[11436 + ((g | h | j | e | a) + (d >>> a) << 2) >> 2] | 0;d = (c[a + 4 >> 2] & -8) - k | 0;e = c[a + 16 + (((c[a + 16 >> 2] | 0) == 0 & 1) << 2) >> 2] | 0;if (!e) {
+              j = a;g = d;
+            } else {
+              do {
+                h = (c[e + 4 >> 2] & -8) - k | 0;j = h >>> 0 < d >>> 0;d = j ? h : d;a = j ? e : a;e = c[e + 16 + (((c[e + 16 >> 2] | 0) == 0 & 1) << 2) >> 2] | 0;
+              } while ((e | 0) != 0);j = a;g = d;
+            }h = j + k | 0;if (j >>> 0 < h >>> 0) {
+              f = c[j + 24 >> 2] | 0;b = c[j + 12 >> 2] | 0;do if ((b | 0) == (j | 0)) {
+                a = j + 20 | 0;b = c[a >> 2] | 0;if (!b) {
+                  a = j + 16 | 0;b = c[a >> 2] | 0;if (!b) {
+                    d = 0;break;
+                  }
+                }while (1) {
+                  d = b + 20 | 0;e = c[d >> 2] | 0;if (e | 0) {
+                    b = e;a = d;continue;
+                  }d = b + 16 | 0;e = c[d >> 2] | 0;if (!e) break;else {
+                    b = e;a = d;
+                  }
+                }c[a >> 2] = 0;d = b;
+              } else {
+                d = c[j + 8 >> 2] | 0;c[d + 12 >> 2] = b;c[b + 8 >> 2] = d;d = b;
+              } while (0);do if (f | 0) {
+                b = c[j + 28 >> 2] | 0;a = 11436 + (b << 2) | 0;if ((j | 0) == (c[a >> 2] | 0)) {
+                  c[a >> 2] = d;if (!d) {
+                    c[2784] = i & ~(1 << b);break;
+                  }
+                } else {
+                  c[f + 16 + (((c[f + 16 >> 2] | 0) != (j | 0) & 1) << 2) >> 2] = d;if (!d) break;
+                }c[d + 24 >> 2] = f;b = c[j + 16 >> 2] | 0;if (b | 0) {
+                  c[d + 16 >> 2] = b;c[b + 24 >> 2] = d;
+                }b = c[j + 20 >> 2] | 0;if (b | 0) {
+                  c[d + 20 >> 2] = b;c[b + 24 >> 2] = d;
+                }
+              } while (0);if (g >>> 0 < 16) {
+                w = g + k | 0;c[j + 4 >> 2] = w | 3;w = j + w + 4 | 0;c[w >> 2] = c[w >> 2] | 1;
+              } else {
+                c[j + 4 >> 2] = k | 3;c[h + 4 >> 2] = g | 1;c[h + g >> 2] = g;if (m | 0) {
+                  e = c[2788] | 0;b = m >>> 3;d = 11172 + (b << 1 << 2) | 0;b = 1 << b;if (!(n & b)) {
+                    c[2783] = n | b;b = d;a = d + 8 | 0;
+                  } else {
+                    a = d + 8 | 0;b = c[a >> 2] | 0;
+                  }c[a >> 2] = e;c[b + 12 >> 2] = e;c[e + 8 >> 2] = b;c[e + 12 >> 2] = d;
+                }c[2785] = g;c[2788] = h;
+              }w = j + 8 | 0;l = x;return w | 0;
+            } else n = k;
+          } else n = k;
+        } else n = k;
+      } else if (a >>> 0 <= 4294967231) {
+        a = a + 11 | 0;k = a & -8;j = c[2784] | 0;if (j) {
+          e = 0 - k | 0;a = a >>> 8;if (a) {
+            if (k >>> 0 > 16777215) i = 31;else {
+              n = (a + 1048320 | 0) >>> 16 & 8;v = a << n;m = (v + 520192 | 0) >>> 16 & 4;v = v << m;i = (v + 245760 | 0) >>> 16 & 2;i = 14 - (m | n | i) + (v << i >>> 15) | 0;i = k >>> (i + 7 | 0) & 1 | i << 1;
+            }
+          } else i = 0;d = c[11436 + (i << 2) >> 2] | 0;a: do if (!d) {
+            d = 0;a = 0;v = 57;
+          } else {
+            a = 0;h = k << ((i | 0) == 31 ? 0 : 25 - (i >>> 1) | 0);g = 0;while (1) {
+              f = (c[d + 4 >> 2] & -8) - k | 0;if (f >>> 0 < e >>> 0) if (!f) {
+                a = d;e = 0;f = d;v = 61;break a;
+              } else {
+                a = d;e = f;
+              }f = c[d + 20 >> 2] | 0;d = c[d + 16 + (h >>> 31 << 2) >> 2] | 0;g = (f | 0) == 0 | (f | 0) == (d | 0) ? g : f;f = (d | 0) == 0;if (f) {
+                d = g;v = 57;break;
+              } else h = h << ((f ^ 1) & 1);
+            }
+          } while (0);if ((v | 0) == 57) {
+            if ((d | 0) == 0 & (a | 0) == 0) {
+              a = 2 << i;a = j & (a | 0 - a);if (!a) {
+                n = k;break;
+              }n = (a & 0 - a) + -1 | 0;h = n >>> 12 & 16;n = n >>> h;g = n >>> 5 & 8;n = n >>> g;i = n >>> 2 & 4;n = n >>> i;m = n >>> 1 & 2;n = n >>> m;d = n >>> 1 & 1;a = 0;d = c[11436 + ((g | h | i | m | d) + (n >>> d) << 2) >> 2] | 0;
+            }if (!d) {
+              i = a;h = e;
+            } else {
+              f = d;v = 61;
+            }
+          }if ((v | 0) == 61) while (1) {
+            v = 0;d = (c[f + 4 >> 2] & -8) - k | 0;n = d >>> 0 < e >>> 0;d = n ? d : e;a = n ? f : a;f = c[f + 16 + (((c[f + 16 >> 2] | 0) == 0 & 1) << 2) >> 2] | 0;if (!f) {
+              i = a;h = d;break;
+            } else {
+              e = d;v = 61;
+            }
+          }if ((i | 0) != 0 ? h >>> 0 < ((c[2785] | 0) - k | 0) >>> 0 : 0) {
+            g = i + k | 0;if (i >>> 0 >= g >>> 0) {
+              w = 0;l = x;return w | 0;
+            }f = c[i + 24 >> 2] | 0;b = c[i + 12 >> 2] | 0;do if ((b | 0) == (i | 0)) {
+              a = i + 20 | 0;b = c[a >> 2] | 0;if (!b) {
+                a = i + 16 | 0;b = c[a >> 2] | 0;if (!b) {
+                  b = 0;break;
+                }
+              }while (1) {
+                d = b + 20 | 0;e = c[d >> 2] | 0;if (e | 0) {
+                  b = e;a = d;continue;
+                }d = b + 16 | 0;e = c[d >> 2] | 0;if (!e) break;else {
+                  b = e;a = d;
+                }
+              }c[a >> 2] = 0;
+            } else {
+              w = c[i + 8 >> 2] | 0;c[w + 12 >> 2] = b;c[b + 8 >> 2] = w;
+            } while (0);do if (f) {
+              a = c[i + 28 >> 2] | 0;d = 11436 + (a << 2) | 0;if ((i | 0) == (c[d >> 2] | 0)) {
+                c[d >> 2] = b;if (!b) {
+                  e = j & ~(1 << a);c[2784] = e;break;
+                }
+              } else {
+                c[f + 16 + (((c[f + 16 >> 2] | 0) != (i | 0) & 1) << 2) >> 2] = b;if (!b) {
+                  e = j;break;
+                }
+              }c[b + 24 >> 2] = f;a = c[i + 16 >> 2] | 0;if (a | 0) {
+                c[b + 16 >> 2] = a;c[a + 24 >> 2] = b;
+              }a = c[i + 20 >> 2] | 0;if (a) {
+                c[b + 20 >> 2] = a;c[a + 24 >> 2] = b;e = j;
+              } else e = j;
+            } else e = j; while (0);do if (h >>> 0 >= 16) {
+              c[i + 4 >> 2] = k | 3;c[g + 4 >> 2] = h | 1;c[g + h >> 2] = h;b = h >>> 3;if (h >>> 0 < 256) {
+                d = 11172 + (b << 1 << 2) | 0;a = c[2783] | 0;b = 1 << b;if (!(a & b)) {
+                  c[2783] = a | b;b = d;a = d + 8 | 0;
+                } else {
+                  a = d + 8 | 0;b = c[a >> 2] | 0;
+                }c[a >> 2] = g;c[b + 12 >> 2] = g;c[g + 8 >> 2] = b;c[g + 12 >> 2] = d;break;
+              }b = h >>> 8;if (b) {
+                if (h >>> 0 > 16777215) b = 31;else {
+                  v = (b + 1048320 | 0) >>> 16 & 8;w = b << v;u = (w + 520192 | 0) >>> 16 & 4;w = w << u;b = (w + 245760 | 0) >>> 16 & 2;b = 14 - (u | v | b) + (w << b >>> 15) | 0;b = h >>> (b + 7 | 0) & 1 | b << 1;
+                }
+              } else b = 0;d = 11436 + (b << 2) | 0;c[g + 28 >> 2] = b;a = g + 16 | 0;c[a + 4 >> 2] = 0;c[a >> 2] = 0;a = 1 << b;if (!(e & a)) {
+                c[2784] = e | a;c[d >> 2] = g;c[g + 24 >> 2] = d;c[g + 12 >> 2] = g;c[g + 8 >> 2] = g;break;
+              }a = h << ((b | 0) == 31 ? 0 : 25 - (b >>> 1) | 0);d = c[d >> 2] | 0;while (1) {
+                if ((c[d + 4 >> 2] & -8 | 0) == (h | 0)) {
+                  v = 97;break;
+                }e = d + 16 + (a >>> 31 << 2) | 0;b = c[e >> 2] | 0;if (!b) {
+                  v = 96;break;
+                } else {
+                  a = a << 1;d = b;
+                }
+              }if ((v | 0) == 96) {
+                c[e >> 2] = g;c[g + 24 >> 2] = d;c[g + 12 >> 2] = g;c[g + 8 >> 2] = g;break;
+              } else if ((v | 0) == 97) {
+                v = d + 8 | 0;w = c[v >> 2] | 0;c[w + 12 >> 2] = g;c[v >> 2] = g;c[g + 8 >> 2] = w;c[g + 12 >> 2] = d;c[g + 24 >> 2] = 0;break;
+              }
+            } else {
+              w = h + k | 0;c[i + 4 >> 2] = w | 3;w = i + w + 4 | 0;c[w >> 2] = c[w >> 2] | 1;
+            } while (0);w = i + 8 | 0;l = x;return w | 0;
+          } else n = k;
+        } else n = k;
+      } else n = -1; while (0);d = c[2785] | 0;if (d >>> 0 >= n >>> 0) {
+        b = d - n | 0;a = c[2788] | 0;if (b >>> 0 > 15) {
+          w = a + n | 0;c[2788] = w;c[2785] = b;c[w + 4 >> 2] = b | 1;c[w + b >> 2] = b;c[a + 4 >> 2] = n | 3;
+        } else {
+          c[2785] = 0;c[2788] = 0;c[a + 4 >> 2] = d | 3;w = a + d + 4 | 0;c[w >> 2] = c[w >> 2] | 1;
+        }w = a + 8 | 0;l = x;return w | 0;
+      }h = c[2786] | 0;if (h >>> 0 > n >>> 0) {
+        u = h - n | 0;c[2786] = u;w = c[2789] | 0;v = w + n | 0;c[2789] = v;c[v + 4 >> 2] = u | 1;c[w + 4 >> 2] = n | 3;w = w + 8 | 0;l = x;return w | 0;
+      }if (!(c[2901] | 0)) {
+        c[2903] = 4096;c[2902] = 4096;c[2904] = -1;c[2905] = -1;c[2906] = 0;c[2894] = 0;a = o & -16 ^ 1431655768;c[o >> 2] = a;c[2901] = a;a = 4096;
+      } else a = c[2903] | 0;i = n + 48 | 0;j = n + 47 | 0;g = a + j | 0;f = 0 - a | 0;k = g & f;if (k >>> 0 <= n >>> 0) {
+        w = 0;l = x;return w | 0;
+      }a = c[2893] | 0;if (a | 0 ? (m = c[2891] | 0, o = m + k | 0, o >>> 0 <= m >>> 0 | o >>> 0 > a >>> 0) : 0) {
+        w = 0;l = x;return w | 0;
+      }b: do if (!(c[2894] & 4)) {
+        d = c[2789] | 0;c: do if (d) {
+          e = 11580;while (1) {
+            a = c[e >> 2] | 0;if (a >>> 0 <= d >>> 0 ? (r = e + 4 | 0, (a + (c[r >> 2] | 0) | 0) >>> 0 > d >>> 0) : 0) break;a = c[e + 8 >> 2] | 0;if (!a) {
+              v = 118;break c;
+            } else e = a;
+          }b = g - h & f;if (b >>> 0 < 2147483647) {
+            a = FC(b | 0) | 0;if ((a | 0) == ((c[e >> 2] | 0) + (c[r >> 2] | 0) | 0)) {
+              if ((a | 0) != (-1 | 0)) {
+                h = b;g = a;v = 135;break b;
+              }
+            } else {
+              e = a;v = 126;
+            }
+          } else b = 0;
+        } else v = 118; while (0);do if ((v | 0) == 118) {
+          d = FC(0) | 0;if ((d | 0) != (-1 | 0) ? (b = d, p = c[2902] | 0, q = p + -1 | 0, b = ((q & b | 0) == 0 ? 0 : (q + b & 0 - p) - b | 0) + k | 0, p = c[2891] | 0, q = b + p | 0, b >>> 0 > n >>> 0 & b >>> 0 < 2147483647) : 0) {
+            r = c[2893] | 0;if (r | 0 ? q >>> 0 <= p >>> 0 | q >>> 0 > r >>> 0 : 0) {
+              b = 0;break;
+            }a = FC(b | 0) | 0;if ((a | 0) == (d | 0)) {
+              h = b;g = d;v = 135;break b;
+            } else {
+              e = a;v = 126;
+            }
+          } else b = 0;
+        } while (0);do if ((v | 0) == 126) {
+          d = 0 - b | 0;if (!(i >>> 0 > b >>> 0 & (b >>> 0 < 2147483647 & (e | 0) != (-1 | 0)))) if ((e | 0) == (-1 | 0)) {
+            b = 0;break;
+          } else {
+            h = b;g = e;v = 135;break b;
+          }a = c[2903] | 0;a = j - b + a & 0 - a;if (a >>> 0 >= 2147483647) {
+            h = b;g = e;v = 135;break b;
+          }if ((FC(a | 0) | 0) == (-1 | 0)) {
+            FC(d | 0) | 0;b = 0;break;
+          } else {
+            h = a + b | 0;g = e;v = 135;break b;
+          }
+        } while (0);c[2894] = c[2894] | 4;v = 133;
+      } else {
+        b = 0;v = 133;
+      } while (0);if (((v | 0) == 133 ? k >>> 0 < 2147483647 : 0) ? (u = FC(k | 0) | 0, r = FC(0) | 0, s = r - u | 0, t = s >>> 0 > (n + 40 | 0) >>> 0, !((u | 0) == (-1 | 0) | t ^ 1 | u >>> 0 < r >>> 0 & ((u | 0) != (-1 | 0) & (r | 0) != (-1 | 0)) ^ 1)) : 0) {
+        h = t ? s : b;g = u;v = 135;
+      }if ((v | 0) == 135) {
+        b = (c[2891] | 0) + h | 0;c[2891] = b;if (b >>> 0 > (c[2892] | 0) >>> 0) c[2892] = b;j = c[2789] | 0;do if (j) {
+          b = 11580;while (1) {
+            a = c[b >> 2] | 0;d = b + 4 | 0;e = c[d >> 2] | 0;if ((g | 0) == (a + e | 0)) {
+              v = 145;break;
+            }f = c[b + 8 >> 2] | 0;if (!f) break;else b = f;
+          }if (((v | 0) == 145 ? (c[b + 12 >> 2] & 8 | 0) == 0 : 0) ? j >>> 0 < g >>> 0 & j >>> 0 >= a >>> 0 : 0) {
+            c[d >> 2] = e + h;w = j + 8 | 0;w = (w & 7 | 0) == 0 ? 0 : 0 - w & 7;v = j + w | 0;w = (c[2786] | 0) + (h - w) | 0;c[2789] = v;c[2786] = w;c[v + 4 >> 2] = w | 1;c[v + w + 4 >> 2] = 40;c[2790] = c[2905];break;
+          }if (g >>> 0 < (c[2787] | 0) >>> 0) c[2787] = g;d = g + h | 0;b = 11580;while (1) {
+            if ((c[b >> 2] | 0) == (d | 0)) {
+              v = 153;break;
+            }a = c[b + 8 >> 2] | 0;if (!a) break;else b = a;
+          }if ((v | 0) == 153 ? (c[b + 12 >> 2] & 8 | 0) == 0 : 0) {
+            c[b >> 2] = g;m = b + 4 | 0;c[m >> 2] = (c[m >> 2] | 0) + h;m = g + 8 | 0;m = g + ((m & 7 | 0) == 0 ? 0 : 0 - m & 7) | 0;b = d + 8 | 0;b = d + ((b & 7 | 0) == 0 ? 0 : 0 - b & 7) | 0;k = m + n | 0;i = b - m - n | 0;c[m + 4 >> 2] = n | 3;do if ((b | 0) != (j | 0)) {
+              if ((b | 0) == (c[2788] | 0)) {
+                w = (c[2785] | 0) + i | 0;c[2785] = w;c[2788] = k;c[k + 4 >> 2] = w | 1;c[k + w >> 2] = w;break;
+              }a = c[b + 4 >> 2] | 0;if ((a & 3 | 0) == 1) {
+                h = a & -8;e = a >>> 3;d: do if (a >>> 0 < 256) {
+                  a = c[b + 8 >> 2] | 0;d = c[b + 12 >> 2] | 0;if ((d | 0) == (a | 0)) {
+                    c[2783] = c[2783] & ~(1 << e);break;
+                  } else {
+                    c[a + 12 >> 2] = d;c[d + 8 >> 2] = a;break;
+                  }
+                } else {
+                  g = c[b + 24 >> 2] | 0;a = c[b + 12 >> 2] | 0;do if ((a | 0) == (b | 0)) {
+                    e = b + 16 | 0;d = e + 4 | 0;a = c[d >> 2] | 0;if (!a) {
+                      a = c[e >> 2] | 0;if (!a) {
+                        a = 0;break;
+                      } else d = e;
+                    }while (1) {
+                      e = a + 20 | 0;f = c[e >> 2] | 0;if (f | 0) {
+                        a = f;d = e;continue;
+                      }e = a + 16 | 0;f = c[e >> 2] | 0;if (!f) break;else {
+                        a = f;d = e;
+                      }
+                    }c[d >> 2] = 0;
+                  } else {
+                    w = c[b + 8 >> 2] | 0;c[w + 12 >> 2] = a;c[a + 8 >> 2] = w;
+                  } while (0);if (!g) break;d = c[b + 28 >> 2] | 0;e = 11436 + (d << 2) | 0;do if ((b | 0) != (c[e >> 2] | 0)) {
+                    c[g + 16 + (((c[g + 16 >> 2] | 0) != (b | 0) & 1) << 2) >> 2] = a;if (!a) break d;
+                  } else {
+                    c[e >> 2] = a;if (a | 0) break;c[2784] = c[2784] & ~(1 << d);break d;
+                  } while (0);c[a + 24 >> 2] = g;d = b + 16 | 0;e = c[d >> 2] | 0;if (e | 0) {
+                    c[a + 16 >> 2] = e;c[e + 24 >> 2] = a;
+                  }d = c[d + 4 >> 2] | 0;if (!d) break;c[a + 20 >> 2] = d;c[d + 24 >> 2] = a;
+                } while (0);b = b + h | 0;f = h + i | 0;
+              } else f = i;b = b + 4 | 0;c[b >> 2] = c[b >> 2] & -2;c[k + 4 >> 2] = f | 1;c[k + f >> 2] = f;b = f >>> 3;if (f >>> 0 < 256) {
+                d = 11172 + (b << 1 << 2) | 0;a = c[2783] | 0;b = 1 << b;if (!(a & b)) {
+                  c[2783] = a | b;b = d;a = d + 8 | 0;
+                } else {
+                  a = d + 8 | 0;b = c[a >> 2] | 0;
+                }c[a >> 2] = k;c[b + 12 >> 2] = k;c[k + 8 >> 2] = b;c[k + 12 >> 2] = d;break;
+              }b = f >>> 8;do if (!b) b = 0;else {
+                if (f >>> 0 > 16777215) {
+                  b = 31;break;
+                }v = (b + 1048320 | 0) >>> 16 & 8;w = b << v;u = (w + 520192 | 0) >>> 16 & 4;w = w << u;b = (w + 245760 | 0) >>> 16 & 2;b = 14 - (u | v | b) + (w << b >>> 15) | 0;b = f >>> (b + 7 | 0) & 1 | b << 1;
+              } while (0);e = 11436 + (b << 2) | 0;c[k + 28 >> 2] = b;a = k + 16 | 0;c[a + 4 >> 2] = 0;c[a >> 2] = 0;a = c[2784] | 0;d = 1 << b;if (!(a & d)) {
+                c[2784] = a | d;c[e >> 2] = k;c[k + 24 >> 2] = e;c[k + 12 >> 2] = k;c[k + 8 >> 2] = k;break;
+              }a = f << ((b | 0) == 31 ? 0 : 25 - (b >>> 1) | 0);d = c[e >> 2] | 0;while (1) {
+                if ((c[d + 4 >> 2] & -8 | 0) == (f | 0)) {
+                  v = 194;break;
+                }e = d + 16 + (a >>> 31 << 2) | 0;b = c[e >> 2] | 0;if (!b) {
+                  v = 193;break;
+                } else {
+                  a = a << 1;d = b;
+                }
+              }if ((v | 0) == 193) {
+                c[e >> 2] = k;c[k + 24 >> 2] = d;c[k + 12 >> 2] = k;c[k + 8 >> 2] = k;break;
+              } else if ((v | 0) == 194) {
+                v = d + 8 | 0;w = c[v >> 2] | 0;c[w + 12 >> 2] = k;c[v >> 2] = k;c[k + 8 >> 2] = w;c[k + 12 >> 2] = d;c[k + 24 >> 2] = 0;break;
+              }
+            } else {
+              w = (c[2786] | 0) + i | 0;c[2786] = w;c[2789] = k;c[k + 4 >> 2] = w | 1;
+            } while (0);w = m + 8 | 0;l = x;return w | 0;
+          }b = 11580;while (1) {
+            a = c[b >> 2] | 0;if (a >>> 0 <= j >>> 0 ? (w = a + (c[b + 4 >> 2] | 0) | 0, w >>> 0 > j >>> 0) : 0) break;b = c[b + 8 >> 2] | 0;
+          }f = w + -47 | 0;a = f + 8 | 0;a = f + ((a & 7 | 0) == 0 ? 0 : 0 - a & 7) | 0;f = j + 16 | 0;a = a >>> 0 < f >>> 0 ? j : a;b = a + 8 | 0;d = g + 8 | 0;d = (d & 7 | 0) == 0 ? 0 : 0 - d & 7;v = g + d | 0;d = h + -40 - d | 0;c[2789] = v;c[2786] = d;c[v + 4 >> 2] = d | 1;c[v + d + 4 >> 2] = 40;c[2790] = c[2905];d = a + 4 | 0;c[d >> 2] = 27;c[b >> 2] = c[2895];c[b + 4 >> 2] = c[2896];c[b + 8 >> 2] = c[2897];c[b + 12 >> 2] = c[2898];c[2895] = g;c[2896] = h;c[2898] = 0;c[2897] = b;b = a + 24 | 0;do {
+            v = b;b = b + 4 | 0;c[b >> 2] = 7;
+          } while ((v + 8 | 0) >>> 0 < w >>> 0);if ((a | 0) != (j | 0)) {
+            g = a - j | 0;c[d >> 2] = c[d >> 2] & -2;c[j + 4 >> 2] = g | 1;c[a >> 2] = g;b = g >>> 3;if (g >>> 0 < 256) {
+              d = 11172 + (b << 1 << 2) | 0;a = c[2783] | 0;b = 1 << b;if (!(a & b)) {
+                c[2783] = a | b;b = d;a = d + 8 | 0;
+              } else {
+                a = d + 8 | 0;b = c[a >> 2] | 0;
+              }c[a >> 2] = j;c[b + 12 >> 2] = j;c[j + 8 >> 2] = b;c[j + 12 >> 2] = d;break;
+            }b = g >>> 8;if (b) {
+              if (g >>> 0 > 16777215) d = 31;else {
+                v = (b + 1048320 | 0) >>> 16 & 8;w = b << v;u = (w + 520192 | 0) >>> 16 & 4;w = w << u;d = (w + 245760 | 0) >>> 16 & 2;d = 14 - (u | v | d) + (w << d >>> 15) | 0;d = g >>> (d + 7 | 0) & 1 | d << 1;
+              }
+            } else d = 0;e = 11436 + (d << 2) | 0;c[j + 28 >> 2] = d;c[j + 20 >> 2] = 0;c[f >> 2] = 0;b = c[2784] | 0;a = 1 << d;if (!(b & a)) {
+              c[2784] = b | a;c[e >> 2] = j;c[j + 24 >> 2] = e;c[j + 12 >> 2] = j;c[j + 8 >> 2] = j;break;
+            }a = g << ((d | 0) == 31 ? 0 : 25 - (d >>> 1) | 0);d = c[e >> 2] | 0;while (1) {
+              if ((c[d + 4 >> 2] & -8 | 0) == (g | 0)) {
+                v = 216;break;
+              }e = d + 16 + (a >>> 31 << 2) | 0;b = c[e >> 2] | 0;if (!b) {
+                v = 215;break;
+              } else {
+                a = a << 1;d = b;
+              }
+            }if ((v | 0) == 215) {
+              c[e >> 2] = j;c[j + 24 >> 2] = d;c[j + 12 >> 2] = j;c[j + 8 >> 2] = j;break;
+            } else if ((v | 0) == 216) {
+              v = d + 8 | 0;w = c[v >> 2] | 0;c[w + 12 >> 2] = j;c[v >> 2] = j;c[j + 8 >> 2] = w;c[j + 12 >> 2] = d;c[j + 24 >> 2] = 0;break;
+            }
+          }
+        } else {
+          w = c[2787] | 0;if ((w | 0) == 0 | g >>> 0 < w >>> 0) c[2787] = g;c[2895] = g;c[2896] = h;c[2898] = 0;c[2792] = c[2901];c[2791] = -1;b = 0;do {
+            w = 11172 + (b << 1 << 2) | 0;c[w + 12 >> 2] = w;c[w + 8 >> 2] = w;b = b + 1 | 0;
+          } while ((b | 0) != 32);w = g + 8 | 0;w = (w & 7 | 0) == 0 ? 0 : 0 - w & 7;v = g + w | 0;w = h + -40 - w | 0;c[2789] = v;c[2786] = w;c[v + 4 >> 2] = w | 1;c[v + w + 4 >> 2] = 40;c[2790] = c[2905];
+        } while (0);b = c[2786] | 0;if (b >>> 0 > n >>> 0) {
+          u = b - n | 0;c[2786] = u;w = c[2789] | 0;v = w + n | 0;c[2789] = v;c[v + 4 >> 2] = u | 1;c[w + 4 >> 2] = n | 3;w = w + 8 | 0;l = x;return w | 0;
+        }
+      }c[(vB() | 0) >> 2] = 12;w = 0;l = x;return w | 0;
+    }function pB(a) {
+      a = a | 0;var b = 0,
+          d = 0,
+          e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0;if (!a) return;d = a + -8 | 0;f = c[2787] | 0;a = c[a + -4 >> 2] | 0;b = a & -8;j = d + b | 0;do if (!(a & 1)) {
+        e = c[d >> 2] | 0;if (!(a & 3)) return;h = d + (0 - e) | 0;g = e + b | 0;if (h >>> 0 < f >>> 0) return;if ((h | 0) == (c[2788] | 0)) {
+          a = j + 4 | 0;b = c[a >> 2] | 0;if ((b & 3 | 0) != 3) {
+            i = h;b = g;break;
+          }c[2785] = g;c[a >> 2] = b & -2;c[h + 4 >> 2] = g | 1;c[h + g >> 2] = g;return;
+        }d = e >>> 3;if (e >>> 0 < 256) {
+          a = c[h + 8 >> 2] | 0;b = c[h + 12 >> 2] | 0;if ((b | 0) == (a | 0)) {
+            c[2783] = c[2783] & ~(1 << d);i = h;b = g;break;
+          } else {
+            c[a + 12 >> 2] = b;c[b + 8 >> 2] = a;i = h;b = g;break;
+          }
+        }f = c[h + 24 >> 2] | 0;a = c[h + 12 >> 2] | 0;do if ((a | 0) == (h | 0)) {
+          d = h + 16 | 0;b = d + 4 | 0;a = c[b >> 2] | 0;if (!a) {
+            a = c[d >> 2] | 0;if (!a) {
+              a = 0;break;
+            } else b = d;
+          }while (1) {
+            d = a + 20 | 0;e = c[d >> 2] | 0;if (e | 0) {
+              a = e;b = d;continue;
+            }d = a + 16 | 0;e = c[d >> 2] | 0;if (!e) break;else {
+              a = e;b = d;
+            }
+          }c[b >> 2] = 0;
+        } else {
+          i = c[h + 8 >> 2] | 0;c[i + 12 >> 2] = a;c[a + 8 >> 2] = i;
+        } while (0);if (f) {
+          b = c[h + 28 >> 2] | 0;d = 11436 + (b << 2) | 0;if ((h | 0) == (c[d >> 2] | 0)) {
+            c[d >> 2] = a;if (!a) {
+              c[2784] = c[2784] & ~(1 << b);i = h;b = g;break;
+            }
+          } else {
+            c[f + 16 + (((c[f + 16 >> 2] | 0) != (h | 0) & 1) << 2) >> 2] = a;if (!a) {
+              i = h;b = g;break;
+            }
+          }c[a + 24 >> 2] = f;b = h + 16 | 0;d = c[b >> 2] | 0;if (d | 0) {
+            c[a + 16 >> 2] = d;c[d + 24 >> 2] = a;
+          }b = c[b + 4 >> 2] | 0;if (b) {
+            c[a + 20 >> 2] = b;c[b + 24 >> 2] = a;i = h;b = g;
+          } else {
+            i = h;b = g;
+          }
+        } else {
+          i = h;b = g;
+        }
+      } else {
+        i = d;h = d;
+      } while (0);if (h >>> 0 >= j >>> 0) return;a = j + 4 | 0;e = c[a >> 2] | 0;if (!(e & 1)) return;if (!(e & 2)) {
+        a = c[2788] | 0;if ((j | 0) == (c[2789] | 0)) {
+          j = (c[2786] | 0) + b | 0;c[2786] = j;c[2789] = i;c[i + 4 >> 2] = j | 1;if ((i | 0) != (a | 0)) return;c[2788] = 0;c[2785] = 0;return;
+        }if ((j | 0) == (a | 0)) {
+          j = (c[2785] | 0) + b | 0;c[2785] = j;c[2788] = h;c[i + 4 >> 2] = j | 1;c[h + j >> 2] = j;return;
+        }f = (e & -8) + b | 0;d = e >>> 3;do if (e >>> 0 < 256) {
+          b = c[j + 8 >> 2] | 0;a = c[j + 12 >> 2] | 0;if ((a | 0) == (b | 0)) {
+            c[2783] = c[2783] & ~(1 << d);break;
+          } else {
+            c[b + 12 >> 2] = a;c[a + 8 >> 2] = b;break;
+          }
+        } else {
+          g = c[j + 24 >> 2] | 0;a = c[j + 12 >> 2] | 0;do if ((a | 0) == (j | 0)) {
+            d = j + 16 | 0;b = d + 4 | 0;a = c[b >> 2] | 0;if (!a) {
+              a = c[d >> 2] | 0;if (!a) {
+                d = 0;break;
+              } else b = d;
+            }while (1) {
+              d = a + 20 | 0;e = c[d >> 2] | 0;if (e | 0) {
+                a = e;b = d;continue;
+              }d = a + 16 | 0;e = c[d >> 2] | 0;if (!e) break;else {
+                a = e;b = d;
+              }
+            }c[b >> 2] = 0;d = a;
+          } else {
+            d = c[j + 8 >> 2] | 0;c[d + 12 >> 2] = a;c[a + 8 >> 2] = d;d = a;
+          } while (0);if (g | 0) {
+            a = c[j + 28 >> 2] | 0;b = 11436 + (a << 2) | 0;if ((j | 0) == (c[b >> 2] | 0)) {
+              c[b >> 2] = d;if (!d) {
+                c[2784] = c[2784] & ~(1 << a);break;
+              }
+            } else {
+              c[g + 16 + (((c[g + 16 >> 2] | 0) != (j | 0) & 1) << 2) >> 2] = d;if (!d) break;
+            }c[d + 24 >> 2] = g;a = j + 16 | 0;b = c[a >> 2] | 0;if (b | 0) {
+              c[d + 16 >> 2] = b;c[b + 24 >> 2] = d;
+            }a = c[a + 4 >> 2] | 0;if (a | 0) {
+              c[d + 20 >> 2] = a;c[a + 24 >> 2] = d;
+            }
+          }
+        } while (0);c[i + 4 >> 2] = f | 1;c[h + f >> 2] = f;if ((i | 0) == (c[2788] | 0)) {
+          c[2785] = f;return;
+        }
+      } else {
+        c[a >> 2] = e & -2;c[i + 4 >> 2] = b | 1;c[h + b >> 2] = b;f = b;
+      }a = f >>> 3;if (f >>> 0 < 256) {
+        d = 11172 + (a << 1 << 2) | 0;b = c[2783] | 0;a = 1 << a;if (!(b & a)) {
+          c[2783] = b | a;a = d;b = d + 8 | 0;
+        } else {
+          b = d + 8 | 0;a = c[b >> 2] | 0;
+        }c[b >> 2] = i;c[a + 12 >> 2] = i;c[i + 8 >> 2] = a;c[i + 12 >> 2] = d;return;
+      }a = f >>> 8;if (a) {
+        if (f >>> 0 > 16777215) a = 31;else {
+          h = (a + 1048320 | 0) >>> 16 & 8;j = a << h;g = (j + 520192 | 0) >>> 16 & 4;j = j << g;a = (j + 245760 | 0) >>> 16 & 2;a = 14 - (g | h | a) + (j << a >>> 15) | 0;a = f >>> (a + 7 | 0) & 1 | a << 1;
+        }
+      } else a = 0;e = 11436 + (a << 2) | 0;c[i + 28 >> 2] = a;c[i + 20 >> 2] = 0;c[i + 16 >> 2] = 0;b = c[2784] | 0;d = 1 << a;do if (b & d) {
+        b = f << ((a | 0) == 31 ? 0 : 25 - (a >>> 1) | 0);d = c[e >> 2] | 0;while (1) {
+          if ((c[d + 4 >> 2] & -8 | 0) == (f | 0)) {
+            a = 73;break;
+          }e = d + 16 + (b >>> 31 << 2) | 0;a = c[e >> 2] | 0;if (!a) {
+            a = 72;break;
+          } else {
+            b = b << 1;d = a;
+          }
+        }if ((a | 0) == 72) {
+          c[e >> 2] = i;c[i + 24 >> 2] = d;c[i + 12 >> 2] = i;c[i + 8 >> 2] = i;break;
+        } else if ((a | 0) == 73) {
+          h = d + 8 | 0;j = c[h >> 2] | 0;c[j + 12 >> 2] = i;c[h >> 2] = i;c[i + 8 >> 2] = j;c[i + 12 >> 2] = d;c[i + 24 >> 2] = 0;break;
+        }
+      } else {
+        c[2784] = b | d;c[e >> 2] = i;c[i + 24 >> 2] = e;c[i + 12 >> 2] = i;c[i + 8 >> 2] = i;
+      } while (0);j = (c[2791] | 0) + -1 | 0;c[2791] = j;if (!j) a = 11588;else return;while (1) {
+        a = c[a >> 2] | 0;if (!a) break;else a = a + 8 | 0;
+      }c[2791] = -1;return;
+    }function qB() {
+      return 11628;
+    }function rB(a) {
+      a = a | 0;var b = 0,
+          d = 0;b = l;l = l + 16 | 0;d = b;c[d >> 2] = yB(c[a + 60 >> 2] | 0) | 0;a = uB(db(6, d | 0) | 0) | 0;l = b;return a | 0;
+    }function sB(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0,
+          p = 0;n = l;l = l + 48 | 0;k = n + 16 | 0;g = n;f = n + 32 | 0;i = a + 28 | 0;e = c[i >> 2] | 0;c[f >> 2] = e;j = a + 20 | 0;e = (c[j >> 2] | 0) - e | 0;c[f + 4 >> 2] = e;c[f + 8 >> 2] = b;c[f + 12 >> 2] = d;e = e + d | 0;h = a + 60 | 0;c[g >> 2] = c[h >> 2];c[g + 4 >> 2] = f;c[g + 8 >> 2] = 2;g = uB(gb(146, g | 0) | 0) | 0;a: do if ((e | 0) != (g | 0)) {
+        b = 2;while (1) {
+          if ((g | 0) < 0) break;e = e - g | 0;p = c[f + 4 >> 2] | 0;o = g >>> 0 > p >>> 0;f = o ? f + 8 | 0 : f;b = (o << 31 >> 31) + b | 0;p = g - (o ? p : 0) | 0;c[f >> 2] = (c[f >> 2] | 0) + p;o = f + 4 | 0;c[o >> 2] = (c[o >> 2] | 0) - p;c[k >> 2] = c[h >> 2];c[k + 4 >> 2] = f;c[k + 8 >> 2] = b;g = uB(gb(146, k | 0) | 0) | 0;if ((e | 0) == (g | 0)) {
+            m = 3;break a;
+          }
+        }c[a + 16 >> 2] = 0;c[i >> 2] = 0;c[j >> 2] = 0;c[a >> 2] = c[a >> 2] | 32;if ((b | 0) == 2) d = 0;else d = d - (c[f + 4 >> 2] | 0) | 0;
+      } else m = 3; while (0);if ((m | 0) == 3) {
+        p = c[a + 44 >> 2] | 0;c[a + 16 >> 2] = p + (c[a + 48 >> 2] | 0);c[i >> 2] = p;c[j >> 2] = p;
+      }l = n;return d | 0;
+    }function tB(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0;f = l;l = l + 32 | 0;g = f;e = f + 20 | 0;c[g >> 2] = c[a + 60 >> 2];c[g + 4 >> 2] = 0;c[g + 8 >> 2] = b;c[g + 12 >> 2] = e;c[g + 16 >> 2] = d;if ((uB(fb(140, g | 0) | 0) | 0) < 0) {
+        c[e >> 2] = -1;a = -1;
+      } else a = c[e >> 2] | 0;l = f;return a | 0;
+    }function uB(a) {
+      a = a | 0;if (a >>> 0 > 4294963200) {
+        c[(vB() | 0) >> 2] = 0 - a;a = -1;
+      }return a | 0;
+    }function vB() {
+      return (wB() | 0) + 64 | 0;
+    }function wB() {
+      return xB() | 0;
+    }function xB() {
+      return 2084;
+    }function yB(a) {
+      a = a | 0;return a | 0;
+    }function zB(b, d, e) {
+      b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0;g = l;l = l + 32 | 0;f = g;c[b + 36 >> 2] = 1;if ((c[b >> 2] & 64 | 0) == 0 ? (c[f >> 2] = c[b + 60 >> 2], c[f + 4 >> 2] = 21523, c[f + 8 >> 2] = g + 16, Wa(54, f | 0) | 0) : 0) a[b + 75 >> 0] = -1;f = sB(b, d, e) | 0;l = g;return f | 0;
+    }function AB(b, c) {
+      b = b | 0;c = c | 0;var d = 0,
+          e = 0;d = a[b >> 0] | 0;e = a[c >> 0] | 0;if (d << 24 >> 24 == 0 ? 1 : d << 24 >> 24 != e << 24 >> 24) b = e;else {
+        do {
+          b = b + 1 | 0;c = c + 1 | 0;d = a[b >> 0] | 0;e = a[c >> 0] | 0;
+        } while (!(d << 24 >> 24 == 0 ? 1 : d << 24 >> 24 != e << 24 >> 24));b = e;
+      }return (d & 255) - (b & 255) | 0;
+    }function BB(b, c, d) {
+      b = b | 0;c = c | 0;d = d | 0;var e = 0,
+          f = 0;a: do if (!d) b = 0;else {
+        while (1) {
+          e = a[b >> 0] | 0;f = a[c >> 0] | 0;if (e << 24 >> 24 != f << 24 >> 24) break;d = d + -1 | 0;if (!d) {
+            b = 0;break a;
+          } else {
+            b = b + 1 | 0;c = c + 1 | 0;
+          }
+        }b = (e & 255) - (f & 255) | 0;
+      } while (0);return b | 0;
+    }function CB(b, d, e) {
+      b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0,
+          q = 0,
+          r = 0,
+          s = 0;s = l;l = l + 224 | 0;n = s + 120 | 0;o = s + 80 | 0;q = s;r = s + 136 | 0;f = o;g = f + 40 | 0;do {
+        c[f >> 2] = 0;f = f + 4 | 0;
+      } while ((f | 0) < (g | 0));c[n >> 2] = c[e >> 2];if ((DB(0, d, n, q, o) | 0) < 0) e = -1;else {
+e = c[b >> 2] | 0;m = e & 32;if ((a[b + 74 >> 0] | 0) < 1) c[b >> 2] = e & -33;f = b + 48 | 0;if (!(c[f >> 2] | 0)) {
+          g = b + 44 | 0;h = c[g >> 2] | 0;c[g >> 2] = r;i = b + 28 | 0;c[i >> 2] = r;j = b + 20 | 0;c[j >> 2] = r;c[f >> 2] = 80;k = b + 16 | 0;c[k >> 2] = r + 80;e = DB(b, d, n, q, o) | 0;if (h) {
+            sb[c[b + 36 >> 2] & 7](b, 0, 0) | 0;e = (c[j >> 2] | 0) == 0 ? -1 : e;c[g >> 2] = h;c[f >> 2] = 0;c[k >> 2] = 0;c[i >> 2] = 0;c[j >> 2] = 0;
+          }
+        } else e = DB(b, d, n, q, o) | 0;f = c[b >> 2] | 0;c[b >> 2] = f | m;e = (f & 32 | 0) == 0 ? e : -1;
+      }l = s;return e | 0;
+    }function DB(d, e, f, g, i) {
+      d = d | 0;e = e | 0;f = f | 0;g = g | 0;i = i | 0;var j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0,
+          p = 0,
+          q = 0,
+          r = 0,
+          s = 0,
+          t = 0,
+          u = 0,
+          v = 0,
+          w = 0,
+          x = 0,
+          y = 0,
+          z = 0,
+          B = 0,
+          C = 0,
+          D = 0,
+          E = 0,
+          F = 0,
+          G = 0,
+          H = 0;H = l;l = l + 64 | 0;D = H + 16 | 0;E = H;B = H + 24 | 0;F = H + 8 | 0;G = H + 20 | 0;c[D >> 2] = e;x = (d | 0) != 0;y = B + 40 | 0;z = y;B = B + 39 | 0;C = F + 4 | 0;k = 0;j = 0;p = 0;a: while (1) {
+        do if ((j | 0) > -1) if ((k | 0) > (2147483647 - j | 0)) {
+          c[(vB() | 0) >> 2] = 75;j = -1;break;
+        } else {
+          j = k + j | 0;break;
+        } while (0);k = a[e >> 0] | 0;if (!(k << 24 >> 24)) {
+          w = 87;break;
+        } else m = e;b: while (1) {
+          switch (k << 24 >> 24) {case 37:
+              {
+                k = m;w = 9;break b;
+              }case 0:
+              {
+                k = m;break b;
+              }default:
+}v = m + 1 | 0;c[D >> 2] = v;k = a[v >> 0] | 0;m = v;
+        }c: do if ((w | 0) == 9) while (1) {
+          w = 0;if ((a[m + 1 >> 0] | 0) != 37) break c;k = k + 1 | 0;m = m + 2 | 0;c[D >> 2] = m;if ((a[m >> 0] | 0) == 37) w = 9;else break;
+        } while (0);k = k - e | 0;if (x) GB(d, e, k);if (k | 0) {
+          e = m;continue;
+        }n = m + 1 | 0;k = (a[n >> 0] | 0) + -48 | 0;if (k >>> 0 < 10) {
+          v = (a[m + 2 >> 0] | 0) == 36;u = v ? k : -1;p = v ? 1 : p;n = v ? m + 3 | 0 : n;
+        } else u = -1;c[D >> 2] = n;k = a[n >> 0] | 0;m = (k << 24 >> 24) + -32 | 0;d: do if (m >>> 0 < 32) {
+          o = 0;q = k;while (1) {
+            k = 1 << m;if (!(k & 75913)) {
+              k = q;break d;
+            }o = k | o;n = n + 1 | 0;c[D >> 2] = n;k = a[n >> 0] | 0;m = (k << 24 >> 24) + -32 | 0;if (m >>> 0 >= 32) break;else q = k;
+          }
+        } else o = 0; while (0);if (k << 24 >> 24 == 42) {
+          m = n + 1 | 0;k = (a[m >> 0] | 0) + -48 | 0;if (k >>> 0 < 10 ? (a[n + 2 >> 0] | 0) == 36 : 0) {
+            c[i + (k << 2) >> 2] = 10;k = c[g + ((a[m >> 0] | 0) + -48 << 3) >> 2] | 0;p = 1;n = n + 3 | 0;
+          } else {
+            if (p | 0) {
+              j = -1;break;
+            }if (x) {
+              p = (c[f >> 2] | 0) + (4 - 1) & ~(4 - 1);k = c[p >> 2] | 0;c[f >> 2] = p + 4;p = 0;n = m;
+            } else {
+              k = 0;p = 0;n = m;
+            }
+          }c[D >> 2] = n;v = (k | 0) < 0;k = v ? 0 - k | 0 : k;o = v ? o | 8192 : o;
+        } else {
+          k = HB(D) | 0;if ((k | 0) < 0) {
+            j = -1;break;
+          }n = c[D >> 2] | 0;
+        }do if ((a[n >> 0] | 0) == 46) {
+          if ((a[n + 1 >> 0] | 0) != 42) {
+            c[D >> 2] = n + 1;m = HB(D) | 0;n = c[D >> 2] | 0;break;
+          }q = n + 2 | 0;m = (a[q >> 0] | 0) + -48 | 0;if (m >>> 0 < 10 ? (a[n + 3 >> 0] | 0) == 36 : 0) {
+            c[i + (m << 2) >> 2] = 10;m = c[g + ((a[q >> 0] | 0) + -48 << 3) >> 2] | 0;n = n + 4 | 0;c[D >> 2] = n;break;
+          }if (p | 0) {
+            j = -1;break a;
+          }if (x) {
+            v = (c[f >> 2] | 0) + (4 - 1) & ~(4 - 1);m = c[v >> 2] | 0;c[f >> 2] = v + 4;
+          } else m = 0;c[D >> 2] = q;n = q;
+        } else m = -1; while (0);t = 0;while (1) {
+          if (((a[n >> 0] | 0) + -65 | 0) >>> 0 > 57) {
+            j = -1;break a;
+          }v = n + 1 | 0;c[D >> 2] = v;q = a[(a[n >> 0] | 0) + -65 + (5178 + (t * 58 | 0)) >> 0] | 0;r = q & 255;if ((r + -1 | 0) >>> 0 < 8) {
+            t = r;n = v;
+          } else break;
+        }if (!(q << 24 >> 24)) {
+          j = -1;break;
+        }s = (u | 0) > -1;do if (q << 24 >> 24 == 19) {
+          if (s) {
+            j = -1;break a;
+          } else w = 49;
+        } else {
+          if (s) {
+            c[i + (u << 2) >> 2] = r;s = g + (u << 3) | 0;u = c[s + 4 >> 2] | 0;w = E;c[w >> 2] = c[s >> 2];c[w + 4 >> 2] = u;w = 49;break;
+          }if (!x) {
+            j = 0;break a;
+          }IB(E, r, f);
+        } while (0);if ((w | 0) == 49 ? (w = 0, !x) : 0) {
+          k = 0;e = v;continue;
+        }n = a[n >> 0] | 0;n = (t | 0) != 0 & (n & 15 | 0) == 3 ? n & -33 : n;s = o & -65537;u = (o & 8192 | 0) == 0 ? o : s;e: do switch (n | 0) {case 110:
+            switch ((t & 255) << 24 >> 24) {case 0:
+                {
+                  c[c[E >> 2] >> 2] = j;k = 0;e = v;continue a;
+                }case 1:
+                {
+                  c[c[E >> 2] >> 2] = j;k = 0;e = v;continue a;
+                }case 2:
+                {
+                  k = c[E >> 2] | 0;c[k >> 2] = j;c[k + 4 >> 2] = ((j | 0) < 0) << 31 >> 31;k = 0;e = v;continue a;
+                }case 3:
+                {
+                  b[c[E >> 2] >> 1] = j;k = 0;e = v;continue a;
+                }case 4:
+                {
+                  a[c[E >> 2] >> 0] = j;k = 0;e = v;continue a;
+                }case 6:
+                {
+                  c[c[E >> 2] >> 2] = j;k = 0;e = v;continue a;
+                }case 7:
+                {
+                  k = c[E >> 2] | 0;c[k >> 2] = j;c[k + 4 >> 2] = ((j | 0) < 0) << 31 >> 31;k = 0;e = v;continue a;
+                }default:
+                {
+                  k = 0;e = v;continue a;
+                }}case 112:
+            {
+              n = 120;m = m >>> 0 > 8 ? m : 8;e = u | 8;w = 61;break;
+            }case 88:case 120:
+            {
+              e = u;w = 61;break;
+            }case 111:
+            {
+              n = E;e = c[n >> 2] | 0;n = c[n + 4 >> 2] | 0;r = KB(e, n, y) | 0;s = z - r | 0;o = 0;q = 5642;m = (u & 8 | 0) == 0 | (m | 0) > (s | 0) ? m : s + 1 | 0;s = u;w = 67;break;
+            }case 105:case 100:
+            {
+              n = E;e = c[n >> 2] | 0;n = c[n + 4 >> 2] | 0;if ((n | 0) < 0) {
+                e = wC(0, 0, e | 0, n | 0) | 0;n = A;o = E;c[o >> 2] = e;c[o + 4 >> 2] = n;o = 1;q = 5642;w = 66;break e;
+              } else {
+                o = (u & 2049 | 0) != 0 & 1;q = (u & 2048 | 0) == 0 ? (u & 1 | 0) == 0 ? 5642 : 5644 : 5643;w = 66;break e;
+              }
+            }case 117:
+            {
+              n = E;o = 0;q = 5642;e = c[n >> 2] | 0;n = c[n + 4 >> 2] | 0;w = 66;break;
+            }case 99:
+            {
+              a[B >> 0] = c[E >> 2];e = B;o = 0;q = 5642;r = y;n = 1;m = s;break;
+            }case 109:
+            {
+              n = MB(c[(vB() | 0) >> 2] | 0) | 0;w = 71;break;
+            }case 115:
+            {
+              n = c[E >> 2] | 0;n = n | 0 ? n : 5652;w = 71;break;
+            }case 67:
+            {
+              c[F >> 2] = c[E >> 2];c[C >> 2] = 0;c[E >> 2] = F;r = -1;n = F;w = 75;break;
+            }case 83:
+            {
+              e = c[E >> 2] | 0;if (!m) {
+                OB(d, 32, k, 0, u);e = 0;w = 84;
+              } else {
+                r = m;n = e;w = 75;
+              }break;
+            }case 65:case 71:case 70:case 69:case 97:case 103:case 102:case 101:
+            {
+              k = QB(d, +h[E >> 3], k, m, u, n) | 0;e = v;continue a;
+            }default:
+            {
+              o = 0;q = 5642;r = y;n = m;m = u;
+            }} while (0);f: do if ((w | 0) == 61) {
+          u = E;t = c[u >> 2] | 0;u = c[u + 4 >> 2] | 0;r = JB(t, u, y, n & 32) | 0;q = (e & 8 | 0) == 0 | (t | 0) == 0 & (u | 0) == 0;o = q ? 0 : 2;q = q ? 5642 : 5642 + (n >> 4) | 0;s = e;e = t;n = u;w = 67;
+        } else if ((w | 0) == 66) {
+          r = LB(e, n, y) | 0;s = u;w = 67;
+        } else if ((w | 0) == 71) {
+          w = 0;u = NB(n, 0, m) | 0;t = (u | 0) == 0;e = n;o = 0;q = 5642;r = t ? n + m | 0 : u;n = t ? m : u - n | 0;m = s;
+        } else if ((w | 0) == 75) {
+          w = 0;q = n;e = 0;m = 0;while (1) {
+            o = c[q >> 2] | 0;if (!o) break;m = PB(G, o) | 0;if ((m | 0) < 0 | m >>> 0 > (r - e | 0) >>> 0) break;e = m + e | 0;if (r >>> 0 > e >>> 0) q = q + 4 | 0;else break;
+          }if ((m | 0) < 0) {
+            j = -1;break a;
+          }OB(d, 32, k, e, u);if (!e) {
+            e = 0;w = 84;
+          } else {
+            o = 0;while (1) {
+              m = c[n >> 2] | 0;if (!m) {
+                w = 84;break f;
+              }m = PB(G, m) | 0;o = m + o | 0;if ((o | 0) > (e | 0)) {
+                w = 84;break f;
+              }GB(d, G, m);if (o >>> 0 >= e >>> 0) {
+                w = 84;break;
+              } else n = n + 4 | 0;
+            }
+          }
+        } while (0);if ((w | 0) == 67) {
+          w = 0;n = (e | 0) != 0 | (n | 0) != 0;u = (m | 0) != 0 | n;n = ((n ^ 1) & 1) + (z - r) | 0;e = u ? r : y;r = y;n = u ? (m | 0) > (n | 0) ? m : n : m;m = (m | 0) > -1 ? s & -65537 : s;
+        } else if ((w | 0) == 84) {
+          w = 0;OB(d, 32, k, e, u ^ 8192);k = (k | 0) > (e | 0) ? k : e;e = v;continue;
+        }t = r - e | 0;s = (n | 0) < (t | 0) ? t : n;u = s + o | 0;k = (k | 0) < (u | 0) ? u : k;OB(d, 32, k, u, m);GB(d, q, o);OB(d, 48, k, u, m ^ 65536);OB(d, 48, s, t, 0);GB(d, e, t);OB(d, 32, k, u, m ^ 8192);e = v;
+      }g: do if ((w | 0) == 87) if (!d) if (!p) j = 0;else {
+        j = 1;while (1) {
+          e = c[i + (j << 2) >> 2] | 0;if (!e) break;IB(g + (j << 3) | 0, e, f);j = j + 1 | 0;if ((j | 0) >= 10) {
+            j = 1;break g;
+          }
+        }while (1) {
+          if (c[i + (j << 2) >> 2] | 0) {
+            j = -1;break g;
+          }j = j + 1 | 0;if ((j | 0) >= 10) {
+            j = 1;break;
+          }
+        }
+      } while (0);l = H;return j | 0;
+    }function GB(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;if (!(c[a >> 2] & 32)) aC(b, d, a) | 0;return;
+    }function HB(b) {
+      b = b | 0;var d = 0,
+          e = 0,
+          f = 0;e = c[b >> 2] | 0;f = (a[e >> 0] | 0) + -48 | 0;if (f >>> 0 < 10) {
+        d = 0;do {
+          d = f + (d * 10 | 0) | 0;e = e + 1 | 0;c[b >> 2] = e;f = (a[e >> 0] | 0) + -48 | 0;
+        } while (f >>> 0 < 10);
+      } else d = 0;return d | 0;
+    }function IB(a, b, d) {
+      a = a | 0;b = b | 0;d = d | 0;var e = 0,
+          f = 0,
+          g = 0.0;a: do if (b >>> 0 <= 20) do switch (b | 0) {case 9:
+          {
+            e = (c[d >> 2] | 0) + (4 - 1) & ~(4 - 1);b = c[e >> 2] | 0;c[d >> 2] = e + 4;c[a >> 2] = b;break a;
+          }case 10:
+          {
+            e = (c[d >> 2] | 0) + (4 - 1) & ~(4 - 1);b = c[e >> 2] | 0;c[d >> 2] = e + 4;e = a;c[e >> 2] = b;c[e + 4 >> 2] = ((b | 0) < 0) << 31 >> 31;break a;
+          }case 11:
+          {
+            e = (c[d >> 2] | 0) + (4 - 1) & ~(4 - 1);b = c[e >> 2] | 0;c[d >> 2] = e + 4;e = a;c[e >> 2] = b;c[e + 4 >> 2] = 0;break a;
+          }case 12:
+          {
+            e = (c[d >> 2] | 0) + (8 - 1) & ~(8 - 1);b = e;f = c[b >> 2] | 0;b = c[b + 4 >> 2] | 0;c[d >> 2] = e + 8;e = a;c[e >> 2] = f;c[e + 4 >> 2] = b;break a;
+          }case 13:
+          {
+            f = (c[d >> 2] | 0) + (4 - 1) & ~(4 - 1);e = c[f >> 2] | 0;c[d >> 2] = f + 4;e = (e & 65535) << 16 >> 16;f = a;c[f >> 2] = e;c[f + 4 >> 2] = ((e | 0) < 0) << 31 >> 31;break a;
+          }case 14:
+          {
+            f = (c[d >> 2] | 0) + (4 - 1) & ~(4 - 1);e = c[f >> 2] | 0;c[d >> 2] = f + 4;f = a;c[f >> 2] = e & 65535;c[f + 4 >> 2] = 0;break a;
+          }case 15:
+          {
+            f = (c[d >> 2] | 0) + (4 - 1) & ~(4 - 1);e = c[f >> 2] | 0;c[d >> 2] = f + 4;e = (e & 255) << 24 >> 24;f = a;c[f >> 2] = e;c[f + 4 >> 2] = ((e | 0) < 0) << 31 >> 31;break a;
+          }case 16:
+          {
+            f = (c[d >> 2] | 0) + (4 - 1) & ~(4 - 1);e = c[f >> 2] | 0;c[d >> 2] = f + 4;f = a;c[f >> 2] = e & 255;c[f + 4 >> 2] = 0;break a;
+          }case 17:
+          {
+            f = (c[d >> 2] | 0) + (8 - 1) & ~(8 - 1);g = +h[f >> 3];c[d >> 2] = f + 8;h[a >> 3] = g;break a;
+          }case 18:
+          {
+            f = (c[d >> 2] | 0) + (8 - 1) & ~(8 - 1);g = +h[f >> 3];c[d >> 2] = f + 8;h[a >> 3] = g;break a;
+          }default:
+          break a;} while (0); while (0);return;
+    }function JB(b, c, e, f) {
+      b = b | 0;c = c | 0;e = e | 0;f = f | 0;if (!((b | 0) == 0 & (c | 0) == 0)) do {
+        e = e + -1 | 0;a[e >> 0] = d[5694 + (b & 15) >> 0] | 0 | f;b = AC(b | 0, c | 0, 4) | 0;c = A;
+      } while (!((b | 0) == 0 & (c | 0) == 0));return e | 0;
+    }function KB(b, c, d) {
+      b = b | 0;c = c | 0;d = d | 0;if (!((b | 0) == 0 & (c | 0) == 0)) do {
+        d = d + -1 | 0;a[d >> 0] = b & 7 | 48;b = AC(b | 0, c | 0, 3) | 0;c = A;
+      } while (!((b | 0) == 0 & (c | 0) == 0));return d | 0;
+    }function LB(b, c, d) {
+      b = b | 0;c = c | 0;d = d | 0;var e = 0;if (c >>> 0 > 0 | (c | 0) == 0 & b >>> 0 > 4294967295) {
+        while (1) {
+          e = HC(b | 0, c | 0, 10, 0) | 0;d = d + -1 | 0;a[d >> 0] = e & 255 | 48;e = b;b = EC(b | 0, c | 0, 10, 0) | 0;if (!(c >>> 0 > 9 | (c | 0) == 9 & e >>> 0 > 4294967295)) break;else c = A;
+        }c = b;
+      } else c = b;if (c) while (1) {
+        d = d + -1 | 0;a[d >> 0] = (c >>> 0) % 10 | 0 | 48;if (c >>> 0 < 10) break;else c = (c >>> 0) / 10 | 0;
+      }return d | 0;
+    }function MB(a) {
+      a = a | 0;return XB(a, c[(WB() | 0) + 188 >> 2] | 0) | 0;
+    }function NB(b, d, e) {
+      b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0;h = d & 255;f = (e | 0) != 0;a: do if (f & (b & 3 | 0) != 0) {
+        g = d & 255;while (1) {
+          if ((a[b >> 0] | 0) == g << 24 >> 24) {
+            i = 6;break a;
+          }b = b + 1 | 0;e = e + -1 | 0;f = (e | 0) != 0;if (!(f & (b & 3 | 0) != 0)) {
+            i = 5;break;
+          }
+        }
+      } else i = 5; while (0);if ((i | 0) == 5) if (f) i = 6;else e = 0;b: do if ((i | 0) == 6) {
+        g = d & 255;if ((a[b >> 0] | 0) != g << 24 >> 24) {
+          f = P(h, 16843009) | 0;c: do if (e >>> 0 > 3) while (1) {
+            h = c[b >> 2] ^ f;if ((h & -2139062144 ^ -2139062144) & h + -16843009 | 0) break;b = b + 4 | 0;e = e + -4 | 0;if (e >>> 0 <= 3) {
+              i = 11;break c;
+            }
+          } else i = 11; while (0);if ((i | 0) == 11) if (!e) {
+            e = 0;break;
+          }while (1) {
+            if ((a[b >> 0] | 0) == g << 24 >> 24) break b;b = b + 1 | 0;e = e + -1 | 0;if (!e) {
+              e = 0;break;
+            }
+          }
+        }
+      } while (0);return (e | 0 ? b : 0) | 0;
+    }function OB(a, b, c, d, e) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0;g = l;l = l + 256 | 0;f = g;if ((c | 0) > (d | 0) & (e & 73728 | 0) == 0) {
+        e = c - d | 0;yC(f | 0, b | 0, (e >>> 0 < 256 ? e : 256) | 0) | 0;if (e >>> 0 > 255) {
+          b = c - d | 0;do {
+            GB(a, f, 256);e = e + -256 | 0;
+          } while (e >>> 0 > 255);e = b & 255;
+        }GB(a, f, e);
+      }l = g;return;
+    }function PB(a, b) {
+      a = a | 0;b = b | 0;if (!a) a = 0;else a = UB(a, b, 0) | 0;return a | 0;
+    }function QB(b, e, f, g, h, i) {
+      b = b | 0;e = +e;f = f | 0;g = g | 0;h = h | 0;i = i | 0;var j = 0,
+          k = 0,
+          m = 0,
+          n = 0,
+          o = 0,
+          p = 0,
+          q = 0,
+          r = 0.0,
+          s = 0,
+          t = 0,
+          u = 0,
+          v = 0,
+          w = 0,
+          x = 0,
+          y = 0,
+          z = 0,
+          B = 0,
+          C = 0,
+          D = 0,
+          E = 0,
+          F = 0,
+          G = 0,
+          H = 0;H = l;l = l + 560 | 0;m = H + 8 | 0;u = H;G = H + 524 | 0;F = G;n = H + 512 | 0;c[u >> 2] = 0;E = n + 12 | 0;RB(e) | 0;if ((A | 0) < 0) {
+        e = -e;C = 1;B = 5659;
+      } else {
+        C = (h & 2049 | 0) != 0 & 1;B = (h & 2048 | 0) == 0 ? (h & 1 | 0) == 0 ? 5660 : 5665 : 5662;
+      }RB(e) | 0;D = A & 2146435072;do if (D >>> 0 < 2146435072 | (D | 0) == 2146435072 & 0 < 0) {
+        r = +SB(e, u) * 2.0;j = r != 0.0;if (j) c[u >> 2] = (c[u >> 2] | 0) + -1;w = i | 32;if ((w | 0) == 97) {
+          s = i & 32;q = (s | 0) == 0 ? B : B + 9 | 0;p = C | 2;j = 12 - g | 0;do if (!(g >>> 0 > 11 | (j | 0) == 0)) {
+            e = 8.0;do {
+              j = j + -1 | 0;e = e * 16.0;
+            } while ((j | 0) != 0);if ((a[q >> 0] | 0) == 45) {
+              e = -(e + (-r - e));break;
+            } else {
+              e = r + e - e;break;
+            }
+          } else e = r; while (0);k = c[u >> 2] | 0;j = (k | 0) < 0 ? 0 - k | 0 : k;j = LB(j, ((j | 0) < 0) << 31 >> 31, E) | 0;if ((j | 0) == (E | 0)) {
+            j = n + 11 | 0;a[j >> 0] = 48;
+          }a[j + -1 >> 0] = (k >> 31 & 2) + 43;o = j + -2 | 0;a[o >> 0] = i + 15;n = (g | 0) < 1;m = (h & 8 | 0) == 0;j = G;do {
+            D = ~~e;k = j + 1 | 0;a[j >> 0] = d[5694 + D >> 0] | s;e = (e - +(D | 0)) * 16.0;if ((k - F | 0) == 1 ? !(m & (n & e == 0.0)) : 0) {
+              a[k >> 0] = 46;j = j + 2 | 0;
+            } else j = k;
+          } while (e != 0.0);D = j - F | 0;F = E - o | 0;E = (g | 0) != 0 & (D + -2 | 0) < (g | 0) ? g + 2 | 0 : D;j = F + p + E | 0;OB(b, 32, f, j, h);GB(b, q, p);OB(b, 48, f, j, h ^ 65536);GB(b, G, D);OB(b, 48, E - D | 0, 0, 0);GB(b, o, F);OB(b, 32, f, j, h ^ 8192);break;
+        }k = (g | 0) < 0 ? 6 : g;if (j) {
+          j = (c[u >> 2] | 0) + -28 | 0;c[u >> 2] = j;e = r * 268435456.0;
+        } else {
+          e = r;j = c[u >> 2] | 0;
+        }D = (j | 0) < 0 ? m : m + 288 | 0;m = D;do {
+          y = ~~e >>> 0;c[m >> 2] = y;m = m + 4 | 0;e = (e - +(y >>> 0)) * 1.0e9;
+        } while (e != 0.0);if ((j | 0) > 0) {
+          n = D;p = m;while (1) {
+            o = (j | 0) < 29 ? j : 29;j = p + -4 | 0;if (j >>> 0 >= n >>> 0) {
+              m = 0;do {
+                x = zC(c[j >> 2] | 0, 0, o | 0) | 0;x = xC(x | 0, A | 0, m | 0, 0) | 0;y = A;v = HC(x | 0, y | 0, 1e9, 0) | 0;c[j >> 2] = v;m = EC(x | 0, y | 0, 1e9, 0) | 0;j = j + -4 | 0;
+              } while (j >>> 0 >= n >>> 0);if (m) {
+                n = n + -4 | 0;c[n >> 2] = m;
+              }
+            }m = p;while (1) {
+              if (m >>> 0 <= n >>> 0) break;j = m + -4 | 0;if (!(c[j >> 2] | 0)) m = j;else break;
+            }j = (c[u >> 2] | 0) - o | 0;c[u >> 2] = j;if ((j | 0) > 0) p = m;else break;
+          }
+        } else n = D;if ((j | 0) < 0) {
+          g = ((k + 25 | 0) / 9 | 0) + 1 | 0;t = (w | 0) == 102;do {
+            s = 0 - j | 0;s = (s | 0) < 9 ? s : 9;if (n >>> 0 < m >>> 0) {
+              o = (1 << s) + -1 | 0;p = 1e9 >>> s;q = 0;j = n;do {
+                y = c[j >> 2] | 0;c[j >> 2] = (y >>> s) + q;q = P(y & o, p) | 0;j = j + 4 | 0;
+              } while (j >>> 0 < m >>> 0);j = (c[n >> 2] | 0) == 0 ? n + 4 | 0 : n;if (!q) {
+                n = j;j = m;
+              } else {
+                c[m >> 2] = q;n = j;j = m + 4 | 0;
+              }
+            } else {
+              n = (c[n >> 2] | 0) == 0 ? n + 4 | 0 : n;j = m;
+            }m = t ? D : n;m = (j - m >> 2 | 0) > (g | 0) ? m + (g << 2) | 0 : j;j = (c[u >> 2] | 0) + s | 0;c[u >> 2] = j;
+          } while ((j | 0) < 0);j = n;g = m;
+        } else {
+          j = n;g = m;
+        }y = D;if (j >>> 0 < g >>> 0) {
+          m = (y - j >> 2) * 9 | 0;o = c[j >> 2] | 0;if (o >>> 0 >= 10) {
+            n = 10;do {
+              n = n * 10 | 0;m = m + 1 | 0;
+            } while (o >>> 0 >= n >>> 0);
+          }
+        } else m = 0;t = (w | 0) == 103;v = (k | 0) != 0;n = k - ((w | 0) != 102 ? m : 0) + ((v & t) << 31 >> 31) | 0;if ((n | 0) < (((g - y >> 2) * 9 | 0) + -9 | 0)) {
+          n = n + 9216 | 0;s = D + 4 + (((n | 0) / 9 | 0) + -1024 << 2) | 0;n = ((n | 0) % 9 | 0) + 1 | 0;if ((n | 0) < 9) {
+            o = 10;do {
+              o = o * 10 | 0;n = n + 1 | 0;
+            } while ((n | 0) != 9);
+          } else o = 10;p = c[s >> 2] | 0;q = (p >>> 0) % (o >>> 0) | 0;n = (s + 4 | 0) == (g | 0);if (!(n & (q | 0) == 0)) {
+            r = (((p >>> 0) / (o >>> 0) | 0) & 1 | 0) == 0 ? 9007199254740992.0 : 9007199254740994.0;x = (o | 0) / 2 | 0;e = q >>> 0 < x >>> 0 ? .5 : n & (q | 0) == (x | 0) ? 1.0 : 1.5;if (C) {
+              x = (a[B >> 0] | 0) == 45;e = x ? -e : e;r = x ? -r : r;
+            }n = p - q | 0;c[s >> 2] = n;if (r + e != r) {
+              x = n + o | 0;c[s >> 2] = x;if (x >>> 0 > 999999999) {
+                m = s;while (1) {
+                  n = m + -4 | 0;c[m >> 2] = 0;if (n >>> 0 < j >>> 0) {
+                    j = j + -4 | 0;c[j >> 2] = 0;
+                  }x = (c[n >> 2] | 0) + 1 | 0;c[n >> 2] = x;if (x >>> 0 > 999999999) m = n;else break;
+                }
+              } else n = s;m = (y - j >> 2) * 9 | 0;p = c[j >> 2] | 0;if (p >>> 0 >= 10) {
+                o = 10;do {
+                  o = o * 10 | 0;m = m + 1 | 0;
+                } while (p >>> 0 >= o >>> 0);
+              }
+            } else n = s;
+          } else n = s;n = n + 4 | 0;n = g >>> 0 > n >>> 0 ? n : g;x = j;
+        } else {
+          n = g;x = j;
+        }w = n;while (1) {
+          if (w >>> 0 <= x >>> 0) {
+            u = 0;break;
+          }j = w + -4 | 0;if (!(c[j >> 2] | 0)) w = j;else {
+            u = 1;break;
+          }
+        }g = 0 - m | 0;do if (t) {
+          j = ((v ^ 1) & 1) + k | 0;if ((j | 0) > (m | 0) & (m | 0) > -5) {
+            o = i + -1 | 0;k = j + -1 - m | 0;
+          } else {
+            o = i + -2 | 0;k = j + -1 | 0;
+          }j = h & 8;if (!j) {
+            if (u ? (z = c[w + -4 >> 2] | 0, (z | 0) != 0) : 0) {
+              if (!((z >>> 0) % 10 | 0)) {
+                n = 0;j = 10;do {
+                  j = j * 10 | 0;n = n + 1 | 0;
+                } while (!((z >>> 0) % (j >>> 0) | 0 | 0));
+              } else n = 0;
+            } else n = 9;j = ((w - y >> 2) * 9 | 0) + -9 | 0;if ((o | 32 | 0) == 102) {
+              s = j - n | 0;s = (s | 0) > 0 ? s : 0;k = (k | 0) < (s | 0) ? k : s;s = 0;break;
+            } else {
+              s = j + m - n | 0;s = (s | 0) > 0 ? s : 0;k = (k | 0) < (s | 0) ? k : s;s = 0;break;
+            }
+          } else s = j;
+        } else {
+          o = i;s = h & 8;
+        } while (0);t = k | s;p = (t | 0) != 0 & 1;q = (o | 32 | 0) == 102;if (q) {
+          v = 0;j = (m | 0) > 0 ? m : 0;
+        } else {
+          j = (m | 0) < 0 ? g : m;j = LB(j, ((j | 0) < 0) << 31 >> 31, E) | 0;n = E;if ((n - j | 0) < 2) do {
+            j = j + -1 | 0;a[j >> 0] = 48;
+          } while ((n - j | 0) < 2);a[j + -1 >> 0] = (m >> 31 & 2) + 43;j = j + -2 | 0;a[j >> 0] = o;v = j;j = n - j | 0;
+        }j = C + 1 + k + p + j | 0;OB(b, 32, f, j, h);GB(b, B, C);OB(b, 48, f, j, h ^ 65536);if (q) {
+          o = x >>> 0 > D >>> 0 ? D : x;s = G + 9 | 0;p = s;q = G + 8 | 0;n = o;do {
+            m = LB(c[n >> 2] | 0, 0, s) | 0;if ((n | 0) == (o | 0)) {
+              if ((m | 0) == (s | 0)) {
+                a[q >> 0] = 48;m = q;
+              }
+            } else if (m >>> 0 > G >>> 0) {
+              yC(G | 0, 48, m - F | 0) | 0;do m = m + -1 | 0; while (m >>> 0 > G >>> 0);
+            }GB(b, m, p - m | 0);n = n + 4 | 0;
+          } while (n >>> 0 <= D >>> 0);if (t | 0) GB(b, 5710, 1);if (n >>> 0 < w >>> 0 & (k | 0) > 0) while (1) {
+            m = LB(c[n >> 2] | 0, 0, s) | 0;if (m >>> 0 > G >>> 0) {
+              yC(G | 0, 48, m - F | 0) | 0;do m = m + -1 | 0; while (m >>> 0 > G >>> 0);
+            }GB(b, m, (k | 0) < 9 ? k : 9);n = n + 4 | 0;m = k + -9 | 0;if (!(n >>> 0 < w >>> 0 & (k | 0) > 9)) {
+              k = m;break;
+            } else k = m;
+          }OB(b, 48, k + 9 | 0, 9, 0);
+        } else {
+          t = u ? w : x + 4 | 0;if ((k | 0) > -1) {
+            u = G + 9 | 0;s = (s | 0) == 0;g = u;p = 0 - F | 0;q = G + 8 | 0;o = x;do {
+              m = LB(c[o >> 2] | 0, 0, u) | 0;if ((m | 0) == (u | 0)) {
+                a[q >> 0] = 48;m = q;
+              }do if ((o | 0) == (x | 0)) {
+                n = m + 1 | 0;GB(b, m, 1);if (s & (k | 0) < 1) {
+                  m = n;break;
+                }GB(b, 5710, 1);m = n;
+              } else {
+                if (m >>> 0 <= G >>> 0) break;yC(G | 0, 48, m + p | 0) | 0;do m = m + -1 | 0; while (m >>> 0 > G >>> 0);
+              } while (0);F = g - m | 0;GB(b, m, (k | 0) > (F | 0) ? F : k);k = k - F | 0;o = o + 4 | 0;
+            } while (o >>> 0 < t >>> 0 & (k | 0) > -1);
+          }OB(b, 48, k + 18 | 0, 18, 0);GB(b, v, E - v | 0);
+        }OB(b, 32, f, j, h ^ 8192);
+      } else {
+        G = (i & 32 | 0) != 0;j = C + 3 | 0;OB(b, 32, f, j, h & -65537);GB(b, B, C);GB(b, e != e | 0.0 != 0.0 ? G ? 5686 : 5690 : G ? 5678 : 5682, 3);OB(b, 32, f, j, h ^ 8192);
+      } while (0);l = H;return ((j | 0) < (f | 0) ? f : j) | 0;
+    }function RB(a) {
+      a = +a;var b = 0;h[j >> 3] = a;b = c[j >> 2] | 0;A = c[j + 4 >> 2] | 0;return b | 0;
+    }function SB(a, b) {
+      a = +a;b = b | 0;return + +TB(a, b);
+    }function TB(a, b) {
+      a = +a;b = b | 0;var d = 0,
+          e = 0,
+          f = 0;h[j >> 3] = a;d = c[j >> 2] | 0;e = c[j + 4 >> 2] | 0;f = AC(d | 0, e | 0, 52) | 0;switch (f & 2047) {case 0:
+          {
+            if (a != 0.0) {
+              a = +TB(a * 18446744073709551616.0, b);d = (c[b >> 2] | 0) + -64 | 0;
+            } else d = 0;c[b >> 2] = d;break;
+          }case 2047:
+          break;default:
+          {
+            c[b >> 2] = (f & 2047) + -1022;c[j >> 2] = d;c[j + 4 >> 2] = e & -2146435073 | 1071644672;a = +h[j >> 3];
+          }}return +a;
+    }function UB(b, d, e) {
+      b = b | 0;d = d | 0;e = e | 0;do if (b) {
+        if (d >>> 0 < 128) {
+          a[b >> 0] = d;b = 1;break;
+        }if (!(c[c[(VB() | 0) + 188 >> 2] >> 2] | 0)) if ((d & -128 | 0) == 57216) {
+          a[b >> 0] = d;b = 1;break;
+        } else {
+          c[(vB() | 0) >> 2] = 84;b = -1;break;
+        }if (d >>> 0 < 2048) {
+          a[b >> 0] = d >>> 6 | 192;a[b + 1 >> 0] = d & 63 | 128;b = 2;break;
+        }if (d >>> 0 < 55296 | (d & -8192 | 0) == 57344) {
+          a[b >> 0] = d >>> 12 | 224;a[b + 1 >> 0] = d >>> 6 & 63 | 128;a[b + 2 >> 0] = d & 63 | 128;b = 3;break;
+        }if ((d + -65536 | 0) >>> 0 < 1048576) {
+          a[b >> 0] = d >>> 18 | 240;a[b + 1 >> 0] = d >>> 12 & 63 | 128;a[b + 2 >> 0] = d >>> 6 & 63 | 128;a[b + 3 >> 0] = d & 63 | 128;b = 4;break;
+        } else {
+          c[(vB() | 0) >> 2] = 84;b = -1;break;
+        }
+      } else b = 1; while (0);return b | 0;
+    }function VB() {
+      return xB() | 0;
+    }function WB() {
+      return xB() | 0;
+    }function XB(b, e) {
+      b = b | 0;e = e | 0;var f = 0,
+          g = 0;g = 0;while (1) {
+        if ((d[5712 + g >> 0] | 0) == (b | 0)) {
+          b = 2;break;
+        }f = g + 1 | 0;if ((f | 0) == 87) {
+          f = 5800;g = 87;b = 5;break;
+        } else g = f;
+      }if ((b | 0) == 2) if (!g) f = 5800;else {
+        f = 5800;b = 5;
+      }if ((b | 0) == 5) while (1) {
+        do {
+          b = f;f = f + 1 | 0;
+        } while ((a[b >> 0] | 0) != 0);g = g + -1 | 0;if (!g) break;else b = 5;
+      }return YB(f, c[e + 20 >> 2] | 0) | 0;
+    }function YB(a, b) {
+      a = a | 0;b = b | 0;return ZB(a, b) | 0;
+    }function ZB(a, b) {
+      a = a | 0;b = b | 0;if (!b) b = 0;else b = _B(c[b >> 2] | 0, c[b + 4 >> 2] | 0, a) | 0;return (b | 0 ? b : a) | 0;
+    }function _B(b, d, e) {
+      b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          l = 0,
+          m = 0,
+          n = 0,
+          o = 0;o = (c[b >> 2] | 0) + 1794895138 | 0;h = $B(c[b + 8 >> 2] | 0, o) | 0;f = $B(c[b + 12 >> 2] | 0, o) | 0;g = $B(c[b + 16 >> 2] | 0, o) | 0;a: do if ((h >>> 0 < d >>> 2 >>> 0 ? (n = d - (h << 2) | 0, f >>> 0 < n >>> 0 & g >>> 0 < n >>> 0) : 0) ? ((g | f) & 3 | 0) == 0 : 0) {
+        n = f >>> 2;m = g >>> 2;l = 0;while (1) {
+          j = h >>> 1;k = l + j | 0;i = k << 1;g = i + n | 0;f = $B(c[b + (g << 2) >> 2] | 0, o) | 0;g = $B(c[b + (g + 1 << 2) >> 2] | 0, o) | 0;if (!(g >>> 0 < d >>> 0 & f >>> 0 < (d - g | 0) >>> 0)) {
+            f = 0;break a;
+          }if (a[b + (g + f) >> 0] | 0) {
+            f = 0;break a;
+          }f = AB(e, b + g | 0) | 0;if (!f) break;f = (f | 0) < 0;if ((h | 0) == 1) {
+            f = 0;break a;
+          } else {
+            l = f ? l : k;h = f ? j : h - j | 0;
+          }
+        }f = i + m | 0;g = $B(c[b + (f << 2) >> 2] | 0, o) | 0;f = $B(c[b + (f + 1 << 2) >> 2] | 0, o) | 0;if (f >>> 0 < d >>> 0 & g >>> 0 < (d - f | 0) >>> 0) f = (a[b + (f + g) >> 0] | 0) == 0 ? b + f | 0 : 0;else f = 0;
+      } else f = 0; while (0);return f | 0;
+    }function $B(a, b) {
+      a = a | 0;b = b | 0;var c = 0;c = IC(a | 0) | 0;return ((b | 0) == 0 ? a : c) | 0;
+    }function aC(b, d, e) {
+      b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0,
+          j = 0;f = e + 16 | 0;g = c[f >> 2] | 0;if (!g) {
+        if (!(bC(e) | 0)) {
+          g = c[f >> 2] | 0;h = 5;
+        } else f = 0;
+      } else h = 5;a: do if ((h | 0) == 5) {
+        j = e + 20 | 0;i = c[j >> 2] | 0;f = i;if ((g - i | 0) >>> 0 < d >>> 0) {
+          f = sb[c[e + 36 >> 2] & 7](e, b, d) | 0;break;
+        }b: do if ((a[e + 75 >> 0] | 0) > -1) {
+          i = d;while (1) {
+            if (!i) {
+              h = 0;g = b;break b;
+            }g = i + -1 | 0;if ((a[b + g >> 0] | 0) == 10) break;else i = g;
+          }f = sb[c[e + 36 >> 2] & 7](e, b, i) | 0;if (f >>> 0 < i >>> 0) break a;h = i;g = b + i | 0;d = d - i | 0;f = c[j >> 2] | 0;
+        } else {
+          h = 0;g = b;
+        } while (0);BC(f | 0, g | 0, d | 0) | 0;c[j >> 2] = (c[j >> 2] | 0) + d;f = h + d | 0;
+      } while (0);return f | 0;
+    }function bC(b) {
+      b = b | 0;var d = 0,
+          e = 0;d = b + 74 | 0;e = a[d >> 0] | 0;a[d >> 0] = e + 255 | e;d = c[b >> 2] | 0;if (!(d & 8)) {
+        c[b + 8 >> 2] = 0;c[b + 4 >> 2] = 0;e = c[b + 44 >> 2] | 0;c[b + 28 >> 2] = e;c[b + 20 >> 2] = e;c[b + 16 >> 2] = e + (c[b + 48 >> 2] | 0);b = 0;
+      } else {
+        c[b >> 2] = d | 32;b = -1;
+      }return b | 0;
+    }function cC(a, b) {
+      a = T(a);b = T(b);var c = 0,
+          d = 0;c = dC(a) | 0;do if ((c & 2147483647) >>> 0 <= 2139095040) {
+        d = dC(b) | 0;if ((d & 2147483647) >>> 0 <= 2139095040) if ((d ^ c | 0) < 0) {
+          a = (c | 0) < 0 ? b : a;break;
+        } else {
+          a = a < b ? b : a;break;
+        }
+      } else a = b; while (0);return T(a);
+    }function dC(a) {
+      a = T(a);return (g[j >> 2] = a, c[j >> 2] | 0) | 0;
+    }function eC(a, b) {
+      a = T(a);b = T(b);var c = 0,
+          d = 0;c = fC(a) | 0;do if ((c & 2147483647) >>> 0 <= 2139095040) {
+        d = fC(b) | 0;if ((d & 2147483647) >>> 0 <= 2139095040) if ((d ^ c | 0) < 0) {
+          a = (c | 0) < 0 ? a : b;break;
+        } else {
+          a = a < b ? a : b;break;
+        }
+      } else a = b; while (0);return T(a);
+    }function fC(a) {
+      a = T(a);return (g[j >> 2] = a, c[j >> 2] | 0) | 0;
+    }function gC(a, b) {
+      a = T(a);b = T(b);var d = 0,
+          e = 0,
+          f = 0,
+          h = 0,
+          i = 0,
+          k = 0,
+          l = 0,
+          m = 0;h = (g[j >> 2] = a, c[j >> 2] | 0);k = (g[j >> 2] = b, c[j >> 2] | 0);d = h >>> 23 & 255;i = k >>> 23 & 255;l = h & -2147483648;f = k << 1;a: do if ((f | 0) != 0 ? !((d | 0) == 255 | ((hC(b) | 0) & 2147483647) >>> 0 > 2139095040) : 0) {
+        e = h << 1;if (e >>> 0 <= f >>> 0) {
+          b = T(a * T(0.0));return T((e | 0) == (f | 0) ? b : a);
+        }if (!d) {
+          d = h << 9;if ((d | 0) > -1) {
+            e = d;d = 0;do {
+              d = d + -1 | 0;e = e << 1;
+            } while ((e | 0) > -1);
+          } else d = 0;e = h << 1 - d;
+        } else e = h & 8388607 | 8388608;if (!i) {
+          h = k << 9;if ((h | 0) > -1) {
+            f = 0;do {
+              f = f + -1 | 0;h = h << 1;
+            } while ((h | 0) > -1);
+          } else f = 0;i = f;k = k << 1 - f;
+        } else k = k & 8388607 | 8388608;f = e - k | 0;h = (f | 0) > -1;b: do if ((d | 0) > (i | 0)) {
+          while (1) {
+            if (h) if (!f) break;else e = f;e = e << 1;d = d + -1 | 0;f = e - k | 0;h = (f | 0) > -1;if ((d | 0) <= (i | 0)) break b;
+          }b = T(a * T(0.0));break a;
+        } while (0);if (h) if (!f) {
+          b = T(a * T(0.0));break;
+        } else e = f;if (e >>> 0 < 8388608) do {
+          e = e << 1;d = d + -1 | 0;
+        } while (e >>> 0 < 8388608);if ((d | 0) > 0) d = e + -8388608 | d << 23;else d = e >>> (1 - d | 0);b = (c[j >> 2] = d | l, T(g[j >> 2]));
+      } else m = 3; while (0);if ((m | 0) == 3) {
+        b = T(a * b);b = T(b / b);
+      }return T(b);
+    }function hC(a) {
+      a = T(a);return (g[j >> 2] = a, c[j >> 2] | 0) | 0;
+    }function iC(a, b) {
+      a = a | 0;b = b | 0;return CB(c[582] | 0, a, b) | 0;
+    }function jC(a) {
+      a = a | 0;Ta();
+    }function kC(a) {
+      a = a | 0;return;
+    }function lC(a, b) {
+      a = a | 0;b = b | 0;return 0;
+    }function mC(a) {
+      a = a | 0;if ((nC(a + 4 | 0) | 0) == -1) {
+        nb[c[(c[a >> 2] | 0) + 8 >> 2] & 127](a);a = 1;
+      } else a = 0;return a | 0;
+    }function nC(a) {
+      a = a | 0;var b = 0;b = c[a >> 2] | 0;c[a >> 2] = b + -1;return b + -1 | 0;
+    }function oC(a) {
+      a = a | 0;if (mC(a) | 0) pC(a);return;
+    }function pC(a) {
+      a = a | 0;var b = 0;b = a + 8 | 0;if (!((c[b >> 2] | 0) != 0 ? (nC(b) | 0) != -1 : 0)) nb[c[(c[a >> 2] | 0) + 16 >> 2] & 127](a);return;
+    }function qC(a) {
+      a = a | 0;var b = 0;b = (a | 0) == 0 ? 1 : a;while (1) {
+        a = oB(b) | 0;if (a | 0) break;a = uC() | 0;if (!a) {
+          a = 0;break;
+        }Fb[a & 0]();
+      }return a | 0;
+    }function rC(a) {
+      a = a | 0;return qC(a) | 0;
+    }function sC(a) {
+      a = a | 0;pB(a);return;
+    }function tC(b) {
+      b = b | 0;if ((a[b + 11 >> 0] | 0) < 0) sC(c[b >> 2] | 0);return;
+    }function uC() {
+      var a = 0;a = c[2923] | 0;c[2923] = a + 0;return a | 0;
+    }function vC() {}function wC(a, b, c, d) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;d = b - d - (c >>> 0 > a >>> 0 | 0) >>> 0;return (A = d, a - c >>> 0 | 0) | 0;
+    }function xC(a, b, c, d) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;c = a + c >>> 0;return (A = b + d + (c >>> 0 < a >>> 0 | 0) >>> 0, c | 0) | 0;
+    }function yC(b, d, e) {
+      b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0,
+          i = 0;h = b + e | 0;d = d & 255;if ((e | 0) >= 67) {
+        while (b & 3) {
+          a[b >> 0] = d;b = b + 1 | 0;
+        }f = h & -4 | 0;g = f - 64 | 0;i = d | d << 8 | d << 16 | d << 24;while ((b | 0) <= (g | 0)) {
+          c[b >> 2] = i;c[b + 4 >> 2] = i;c[b + 8 >> 2] = i;c[b + 12 >> 2] = i;c[b + 16 >> 2] = i;c[b + 20 >> 2] = i;c[b + 24 >> 2] = i;c[b + 28 >> 2] = i;c[b + 32 >> 2] = i;c[b + 36 >> 2] = i;c[b + 40 >> 2] = i;c[b + 44 >> 2] = i;c[b + 48 >> 2] = i;c[b + 52 >> 2] = i;c[b + 56 >> 2] = i;c[b + 60 >> 2] = i;b = b + 64 | 0;
+        }while ((b | 0) < (f | 0)) {
+          c[b >> 2] = i;b = b + 4 | 0;
+        }
+      }while ((b | 0) < (h | 0)) {
+        a[b >> 0] = d;b = b + 1 | 0;
+      }return h - e | 0;
+    }function zC(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;if ((c | 0) < 32) {
+        A = b << c | (a & (1 << c) - 1 << 32 - c) >>> 32 - c;return a << c;
+      }A = a << c - 32;return 0;
+    }function AC(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;if ((c | 0) < 32) {
+        A = b >>> c;return a >>> c | (b & (1 << c) - 1) << 32 - c;
+      }A = 0;return b >>> c - 32 | 0;
+    }function BC(b, d, e) {
+      b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0,
+          h = 0;if ((e | 0) >= 8192) return Oa(b | 0, d | 0, e | 0) | 0;h = b | 0;g = b + e | 0;if ((b & 3) == (d & 3)) {
+        while (b & 3) {
+          if (!e) return h | 0;a[b >> 0] = a[d >> 0] | 0;b = b + 1 | 0;d = d + 1 | 0;e = e - 1 | 0;
+        }e = g & -4 | 0;f = e - 64 | 0;while ((b | 0) <= (f | 0)) {
+          c[b >> 2] = c[d >> 2];c[b + 4 >> 2] = c[d + 4 >> 2];c[b + 8 >> 2] = c[d + 8 >> 2];c[b + 12 >> 2] = c[d + 12 >> 2];c[b + 16 >> 2] = c[d + 16 >> 2];c[b + 20 >> 2] = c[d + 20 >> 2];c[b + 24 >> 2] = c[d + 24 >> 2];c[b + 28 >> 2] = c[d + 28 >> 2];c[b + 32 >> 2] = c[d + 32 >> 2];c[b + 36 >> 2] = c[d + 36 >> 2];c[b + 40 >> 2] = c[d + 40 >> 2];c[b + 44 >> 2] = c[d + 44 >> 2];c[b + 48 >> 2] = c[d + 48 >> 2];c[b + 52 >> 2] = c[d + 52 >> 2];c[b + 56 >> 2] = c[d + 56 >> 2];c[b + 60 >> 2] = c[d + 60 >> 2];b = b + 64 | 0;d = d + 64 | 0;
+        }while ((b | 0) < (e | 0)) {
+          c[b >> 2] = c[d >> 2];b = b + 4 | 0;d = d + 4 | 0;
+        }
+      } else {
+        e = g - 4 | 0;while ((b | 0) < (e | 0)) {
+          a[b >> 0] = a[d >> 0] | 0;a[b + 1 >> 0] = a[d + 1 >> 0] | 0;a[b + 2 >> 0] = a[d + 2 >> 0] | 0;a[b + 3 >> 0] = a[d + 3 >> 0] | 0;b = b + 4 | 0;d = d + 4 | 0;
+        }
+      }while ((b | 0) < (g | 0)) {
+        a[b >> 0] = a[d >> 0] | 0;b = b + 1 | 0;d = d + 1 | 0;
+      }return h | 0;
+    }function CC(b) {
+      b = b | 0;var c = 0;c = a[n + (b & 255) >> 0] | 0;if ((c | 0) < 8) return c | 0;c = a[n + (b >> 8 & 255) >> 0] | 0;if ((c | 0) < 8) return c + 8 | 0;c = a[n + (b >> 16 & 255) >> 0] | 0;if ((c | 0) < 8) return c + 16 | 0;return (a[n + (b >>> 24) >> 0] | 0) + 24 | 0;
+    }function DC(a, b, d, e, f) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;f = f | 0;var g = 0,
+          h = 0,
+          i = 0,
+          j = 0,
+          k = 0,
+          l = 0,
+          m = 0,
+          n = 0,
+          o = 0,
+          p = 0;l = a;j = b;k = j;h = d;n = e;i = n;if (!k) {
+        g = (f | 0) != 0;if (!i) {
+          if (g) {
+            c[f >> 2] = (l >>> 0) % (h >>> 0);c[f + 4 >> 2] = 0;
+          }n = 0;f = (l >>> 0) / (h >>> 0) >>> 0;return (A = n, f) | 0;
+        } else {
+          if (!g) {
+            n = 0;f = 0;return (A = n, f) | 0;
+          }c[f >> 2] = a | 0;c[f + 4 >> 2] = b & 0;n = 0;f = 0;return (A = n, f) | 0;
+        }
+      }g = (i | 0) == 0;do if (h) {
+        if (!g) {
+          g = (S(i | 0) | 0) - (S(k | 0) | 0) | 0;if (g >>> 0 <= 31) {
+            m = g + 1 | 0;i = 31 - g | 0;b = g - 31 >> 31;h = m;a = l >>> (m >>> 0) & b | k << i;b = k >>> (m >>> 0) & b;g = 0;i = l << i;break;
+          }if (!f) {
+            n = 0;f = 0;return (A = n, f) | 0;
+          }c[f >> 2] = a | 0;c[f + 4 >> 2] = j | b & 0;n = 0;f = 0;return (A = n, f) | 0;
+        }g = h - 1 | 0;if (g & h | 0) {
+          i = (S(h | 0) | 0) + 33 - (S(k | 0) | 0) | 0;p = 64 - i | 0;m = 32 - i | 0;j = m >> 31;o = i - 32 | 0;b = o >> 31;h = i;a = m - 1 >> 31 & k >>> (o >>> 0) | (k << m | l >>> (i >>> 0)) & b;b = b & k >>> (i >>> 0);g = l << p & j;i = (k << p | l >>> (o >>> 0)) & j | l << m & i - 33 >> 31;break;
+        }if (f | 0) {
+          c[f >> 2] = g & l;c[f + 4 >> 2] = 0;
+        }if ((h | 0) == 1) {
+          o = j | b & 0;p = a | 0 | 0;return (A = o, p) | 0;
+        } else {
+          p = CC(h | 0) | 0;o = k >>> (p >>> 0) | 0;p = k << 32 - p | l >>> (p >>> 0) | 0;return (A = o, p) | 0;
+        }
+      } else {
+        if (g) {
+          if (f | 0) {
+            c[f >> 2] = (k >>> 0) % (h >>> 0);c[f + 4 >> 2] = 0;
+          }o = 0;p = (k >>> 0) / (h >>> 0) >>> 0;return (A = o, p) | 0;
+        }if (!l) {
+          if (f | 0) {
+            c[f >> 2] = 0;c[f + 4 >> 2] = (k >>> 0) % (i >>> 0);
+          }o = 0;p = (k >>> 0) / (i >>> 0) >>> 0;return (A = o, p) | 0;
+        }g = i - 1 | 0;if (!(g & i)) {
+          if (f | 0) {
+            c[f >> 2] = a | 0;c[f + 4 >> 2] = g & k | b & 0;
+          }o = 0;p = k >>> ((CC(i | 0) | 0) >>> 0);return (A = o, p) | 0;
+        }g = (S(i | 0) | 0) - (S(k | 0) | 0) | 0;if (g >>> 0 <= 30) {
+          b = g + 1 | 0;i = 31 - g | 0;h = b;a = k << i | l >>> (b >>> 0);b = k >>> (b >>> 0);g = 0;i = l << i;break;
+        }if (!f) {
+          o = 0;p = 0;return (A = o, p) | 0;
+        }c[f >> 2] = a | 0;c[f + 4 >> 2] = j | b & 0;o = 0;p = 0;return (A = o, p) | 0;
+      } while (0);if (!h) {
+        k = i;j = 0;i = 0;
+      } else {
+        m = d | 0 | 0;l = n | e & 0;k = xC(m | 0, l | 0, -1, -1) | 0;d = A;j = i;i = 0;do {
+          e = j;j = g >>> 31 | j << 1;g = i | g << 1;e = a << 1 | e >>> 31 | 0;n = a >>> 31 | b << 1 | 0;wC(k | 0, d | 0, e | 0, n | 0) | 0;p = A;o = p >> 31 | ((p | 0) < 0 ? -1 : 0) << 1;i = o & 1;a = wC(e | 0, n | 0, o & m | 0, (((p | 0) < 0 ? -1 : 0) >> 31 | ((p | 0) < 0 ? -1 : 0) << 1) & l | 0) | 0;b = A;h = h - 1 | 0;
+        } while ((h | 0) != 0);k = j;j = 0;
+      }h = 0;if (f | 0) {
+        c[f >> 2] = a;c[f + 4 >> 2] = b;
+      }o = (g | 0) >>> 31 | (k | h) << 1 | (h << 1 | g >>> 31) & 0 | j;p = (g << 1 | 0 >>> 31) & -2 | i;return (A = o, p) | 0;
+    }function EC(a, b, c, d) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;return DC(a, b, c, d, 0) | 0;
+    }function FC(a) {
+      a = a | 0;var b = 0,
+          d = 0;d = a + 15 & -16 | 0;b = c[i >> 2] | 0;a = b + d | 0;if ((d | 0) > 0 & (a | 0) < (b | 0) | (a | 0) < 0) {
+        Y() | 0;Qa(12);return -1;
+      }c[i >> 2] = a;if ((a | 0) > (X() | 0) ? (W() | 0) == 0 : 0) {
+        c[i >> 2] = b;Qa(12);return -1;
+      }return b | 0;
+    }function GC(b, c, d) {
+      b = b | 0;c = c | 0;d = d | 0;var e = 0;if ((c | 0) < (b | 0) & (b | 0) < (c + d | 0)) {
+        e = b;c = c + d | 0;b = b + d | 0;while ((d | 0) > 0) {
+          b = b - 1 | 0;c = c - 1 | 0;d = d - 1 | 0;a[b >> 0] = a[c >> 0] | 0;
+        }b = e;
+      } else BC(b, c, d) | 0;return b | 0;
+    }function HC(a, b, d, e) {
+      a = a | 0;b = b | 0;d = d | 0;e = e | 0;var f = 0,
+          g = 0;g = l;l = l + 16 | 0;f = g | 0;DC(a, b, d, e, f) | 0;l = g;return (A = c[f + 4 >> 2] | 0, c[f >> 2] | 0) | 0;
+    }function IC(a) {
+      a = a | 0;return (a & 255) << 24 | (a >> 8 & 255) << 16 | (a >> 16 & 255) << 8 | a >>> 24 | 0;
+    }function JC(a, b, c, d, e, f) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;e = e | 0;f = f | 0;jb[a & 1](b | 0, c | 0, d | 0, e | 0, f | 0);
+    }function KC(a, b, c) {
+      a = a | 0;b = b | 0;c = T(c);kb[a & 1](b | 0, T(c));
+    }function LC(a, b, c) {
+      a = a | 0;b = b | 0;c = +c;lb[a & 31](b | 0, +c);
+    }function MC(a, b, c, d) {
+      a = a | 0;b = b | 0;c = T(c);d = T(d);return T(mb[a & 0](b | 0, T(c), T(d)));
+    }function NC(a, b) {
+      a = a | 0;b = b | 0;nb[a & 127](b | 0);
+    }function OC(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;ob[a & 31](b | 0, c | 0);
+    }function PC(a, b) {
+      a = a | 0;b = b | 0;return pb[a & 31](b | 0) | 0;
+    }function QC(a, b, c, d, e) {
+      a = a | 0;b = b | 0;c = +c;d = +d;e = e | 0;qb[a & 1](b | 0, +c, +d, e | 0);
+    }function RC(a, b, c, d) {
+      a = a | 0;b = b | 0;c = +c;d = +d;rb[a & 1](b | 0, +c, +d);
+    }function SC(a, b, c, d) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;return sb[a & 7](b | 0, c | 0, d | 0) | 0;
+    }function TC(a, b, c, d) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;return +tb[a & 1](b | 0, c | 0, d | 0);
+    }function UC(a, b) {
+      a = a | 0;b = b | 0;return +ub[a & 15](b | 0);
+    }function VC(a, b, c) {
+      a = a | 0;b = b | 0;c = +c;return vb[a & 1](b | 0, +c) | 0;
+    }function WC(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;return wb[a & 15](b | 0, c | 0) | 0;
+    }function XC(a, b, c, d, e, f) {
+      a = a | 0;b = b | 0;c = c | 0;d = +d;e = +e;f = f | 0;xb[a & 1](b | 0, c | 0, +d, +e, f | 0);
+    }function YC(a, b, c, d, e, f, g) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;e = e | 0;f = f | 0;g = g | 0;yb[a & 1](b | 0, c | 0, d | 0, e | 0, f | 0, g | 0);
+    }function ZC(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;return +zb[a & 7](b | 0, c | 0);
+    }function _C(a) {
+      a = a | 0;return Ab[a & 7]() | 0;
+    }function $C(a, b, c, d, e, f) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;e = e | 0;f = f | 0;return Bb[a & 1](b | 0, c | 0, d | 0, e | 0, f | 0) | 0;
+    }function aD(a, b, c, d, e) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;e = +e;Cb[a & 1](b | 0, c | 0, d | 0, +e);
+    }function bD(a, b, c, d, e, f, g) {
+      a = a | 0;b = b | 0;c = c | 0;d = T(d);e = e | 0;f = T(f);g = g | 0;Db[a & 1](b | 0, c | 0, T(d), e | 0, T(f), g | 0);
+    }function cD(a, b, c, d) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;Eb[a & 15](b | 0, c | 0, d | 0);
+    }function dD(a) {
+      a = a | 0;Fb[a & 0]();
+    }function eD(a, b, c, d) {
+      a = a | 0;b = b | 0;c = c | 0;d = +d;Gb[a & 15](b | 0, c | 0, +d);
+    }function fD(a, b, c) {
+      a = a | 0;b = +b;c = +c;return Hb[a & 1](+b, +c) | 0;
+    }function gD(a, b, c, d, e) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;e = e | 0;Ib[a & 15](b | 0, c | 0, d | 0, e | 0);
+    }function hD(a, b, c, d, e) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;e = e | 0;U(0);
+    }function iD(a, b) {
+      a = a | 0;b = T(b);U(1);
+    }function jD(a, b) {
+      a = a | 0;b = +b;U(2);
+    }function kD(a, b, c) {
+      a = a | 0;b = T(b);c = T(c);U(3);return ib;
+    }function lD(a) {
+      a = a | 0;U(4);
+    }function mD(a, b) {
+      a = a | 0;b = b | 0;U(5);
+    }function nD(a) {
+      a = a | 0;U(6);return 0;
+    }function oD(a, b, c, d) {
+      a = a | 0;b = +b;c = +c;d = d | 0;U(7);
+    }function pD(a, b, c) {
+      a = a | 0;b = +b;c = +c;U(8);
+    }function qD(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;U(9);return 0;
+    }function rD(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;U(10);return 0.0;
+    }function sD(a) {
+      a = a | 0;U(11);return 0.0;
+    }function tD(a, b) {
+      a = a | 0;b = +b;U(12);return 0;
+    }function uD(a, b) {
+      a = a | 0;b = b | 0;U(13);return 0;
+    }function vD(a, b, c, d, e) {
+      a = a | 0;b = b | 0;c = +c;d = +d;e = e | 0;U(14);
+    }function wD(a, b, c, d, e, f) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;e = e | 0;f = f | 0;U(15);
+    }function xD(a, b) {
+      a = a | 0;b = b | 0;U(16);return 0.0;
+    }function yD() {
+      U(17);return 0;
+    }function zD(a, b, c, d, e) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;e = e | 0;U(18);return 0;
+    }function AD(a, b, c, d) {
+      a = a | 0;b = b | 0;c = c | 0;d = +d;U(19);
+    }function BD(a, b, c, d, e, f) {
+      a = a | 0;b = b | 0;c = T(c);d = d | 0;e = T(e);f = f | 0;U(20);
+    }function CD(a, b, c) {
+      a = a | 0;b = b | 0;c = c | 0;U(21);
+    }function DD() {
+      U(22);
+    }function ED(a, b, c) {
+      a = a | 0;b = b | 0;c = +c;U(23);
+    }function FD(a, b) {
+      a = +a;b = +b;U(24);return 0;
+    }function GD(a, b, c, d) {
+      a = a | 0;b = b | 0;c = c | 0;d = d | 0;U(25);
+    }
+
+    // EMSCRIPTEN_END_FUNCS
+    var jb = [hD, Uw];var kb = [iD, of];var lb = [jD, Of, Pf, Qf, Rf, Sf, Tf, Uf, Wf, Xf, Zf, _f, $f, ag, bg, cg, dg, eg, fg, jD, jD, jD, jD, jD, jD, jD, jD, jD, jD, jD, jD, jD];var mb = [kD];var nb = [lD, kC, Ki, Li, Mi, rn, sn, tn, Pu, Qu, Ru, Cw, Dw, Ew, DA, EA, FA, Rb, tf, yf, Vf, Yf, hh, ih, ri, Ui, kj, Jj, bk, zk, Wk, nl, Hl, bm, um, Nm, en, Nn, fo, yo, Ro, ip, Bp, Xp, nq, Eq, Zq, lf, Hr, _r, us, Ps, ft, Ct, Ot, Rt, ju, mu, Eu, Uu, Xu, pv, Kv, Vi, $x, Ky, az, sz, Rz, hA, tA, wA, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD, lD];var ob = [mD, zf, Af, Df, Ef, Ff, Gf, Hf, If, Lf, Mf, Nf, wg, zg, Ag, Bg, Cg, Dg, Eg, Jg, Ng, rh, $p, qq, Ts, cy, Sv, xy, mD, mD, mD, mD];var pb = [nD, rB, sf, jg, ng, og, pg, qg, rg, sg, ug, vg, Kg, Lg, jh, ar, jt, sv, hy, jy, nD, nD, nD, nD, nD, nD, nD, nD, nD, nD, nD, nD];var qb = [oD, kh];var rb = [pD, Hu];var sb = [qD, sB, tB, zB, Dk, Rn, Lr, wz];var tb = [rD, ym];var ub = [sD, xg, yg, Fg, lh, mh, nh, oh, ph, qh, sD, sD, sD, sD, sD, sD];var vb = [tD, Kt];var wb = [uD, lC, Mg, xi, Nj, _k, rl, jn, jo, Iq, pf, ez, uD, uD, uD, uD];var xb = [vD, oj];var yb = [wD, Vz];var zb = [xD, Gg, sh, th, uh, Rm, xD, xD];var Ab = [yD, vh, qf, jf, Wt, qu, av, AA];var Bb = [zD, ee];var Cb = [AD, Vo];var Db = [BD, Pg];var Eb = [CD, kg, tg, Hg, Ig, fk, Ll, mp, Fp, nf, vx, Oy, lA, CD, CD, CD];var Fb = [DD];var Gb = [ED, Bf, Cf, Jf, Kf, gg, hg, ig, Co, cs, Ft, ED, ED, ED, ED, ED];var Hb = [FD, Mu];var Ib = [GD, fm, ir, ys, st, au, wu, hv, Pv, oy, LA, GD, GD, GD, GD, GD];return { _llvm_bswap_i32: IC, dynCall_idd: fD, dynCall_i: _C, _i64Subtract: wC, ___udivdi3: EC, dynCall_vif: KC, setThrew: Nb, dynCall_viii: cD, _bitshift64Lshr: AC, _bitshift64Shl: zC, dynCall_vi: NC, dynCall_viiddi: XC, dynCall_diii: TC, dynCall_iii: WC, _memset: yC, _sbrk: FC, _memcpy: BC, __GLOBAL__sub_I_Yoga_cpp: hf, dynCall_vii: OC, ___uremdi3: HC, dynCall_vid: LC, stackAlloc: Jb, _nbind_init: _A, getTempRet0: Pb, dynCall_di: UC, dynCall_iid: VC, setTempRet0: Ob, _i64Add: xC, dynCall_fiff: MC, dynCall_iiii: SC, _emscripten_get_global_libc: qB, dynCall_viid: eD, dynCall_viiid: aD, dynCall_viififi: bD, dynCall_ii: PC, __GLOBAL__sub_I_Binding_cc: Sx, dynCall_viiii: gD, dynCall_iiiiii: $C, stackSave: Kb, dynCall_viiiii: JC, __GLOBAL__sub_I_nbind_cc: wh, dynCall_vidd: RC, _free: pB, runPostSets: vC, dynCall_viiiiii: YC, establishStackSpace: Mb, _memmove: GC, stackRestore: Lb, _malloc: oB, __GLOBAL__sub_I_common_cc: Bv, dynCall_viddi: QC, dynCall_dii: ZC, dynCall_v: dD };
+  }(
+
+  // EMSCRIPTEN_END_ASM
+  Module.asmGlobalArg, Module.asmLibraryArg, buffer);var _llvm_bswap_i32 = Module["_llvm_bswap_i32"] = asm["_llvm_bswap_i32"];var getTempRet0 = Module["getTempRet0"] = asm["getTempRet0"];var ___udivdi3 = Module["___udivdi3"] = asm["___udivdi3"];var setThrew = Module["setThrew"] = asm["setThrew"];var _bitshift64Lshr = Module["_bitshift64Lshr"] = asm["_bitshift64Lshr"];var _bitshift64Shl = Module["_bitshift64Shl"] = asm["_bitshift64Shl"];var _memset = Module["_memset"] = asm["_memset"];var _sbrk = Module["_sbrk"] = asm["_sbrk"];var _memcpy = Module["_memcpy"] = asm["_memcpy"];var stackAlloc = Module["stackAlloc"] = asm["stackAlloc"];var ___uremdi3 = Module["___uremdi3"] = asm["___uremdi3"];var _nbind_init = Module["_nbind_init"] = asm["_nbind_init"];var _i64Subtract = Module["_i64Subtract"] = asm["_i64Subtract"];var setTempRet0 = Module["setTempRet0"] = asm["setTempRet0"];var _i64Add = Module["_i64Add"] = asm["_i64Add"];var _emscripten_get_global_libc = Module["_emscripten_get_global_libc"] = asm["_emscripten_get_global_libc"];var __GLOBAL__sub_I_Yoga_cpp = Module["__GLOBAL__sub_I_Yoga_cpp"] = asm["__GLOBAL__sub_I_Yoga_cpp"];var __GLOBAL__sub_I_Binding_cc = Module["__GLOBAL__sub_I_Binding_cc"] = asm["__GLOBAL__sub_I_Binding_cc"];var stackSave = Module["stackSave"] = asm["stackSave"];var __GLOBAL__sub_I_nbind_cc = Module["__GLOBAL__sub_I_nbind_cc"] = asm["__GLOBAL__sub_I_nbind_cc"];var _free = Module["_free"] = asm["_free"];var runPostSets = Module["runPostSets"] = asm["runPostSets"];var establishStackSpace = Module["establishStackSpace"] = asm["establishStackSpace"];var _memmove = Module["_memmove"] = asm["_memmove"];var stackRestore = Module["stackRestore"] = asm["stackRestore"];var _malloc = Module["_malloc"] = asm["_malloc"];var __GLOBAL__sub_I_common_cc = Module["__GLOBAL__sub_I_common_cc"] = asm["__GLOBAL__sub_I_common_cc"];var dynCall_viiiii = Module["dynCall_viiiii"] = asm["dynCall_viiiii"];var dynCall_vif = Module["dynCall_vif"] = asm["dynCall_vif"];var dynCall_vid = Module["dynCall_vid"] = asm["dynCall_vid"];var dynCall_fiff = Module["dynCall_fiff"] = asm["dynCall_fiff"];var dynCall_vi = Module["dynCall_vi"] = asm["dynCall_vi"];var dynCall_vii = Module["dynCall_vii"] = asm["dynCall_vii"];var dynCall_ii = Module["dynCall_ii"] = asm["dynCall_ii"];var dynCall_viddi = Module["dynCall_viddi"] = asm["dynCall_viddi"];var dynCall_vidd = Module["dynCall_vidd"] = asm["dynCall_vidd"];var dynCall_iiii = Module["dynCall_iiii"] = asm["dynCall_iiii"];var dynCall_diii = Module["dynCall_diii"] = asm["dynCall_diii"];var dynCall_di = Module["dynCall_di"] = asm["dynCall_di"];var dynCall_iid = Module["dynCall_iid"] = asm["dynCall_iid"];var dynCall_iii = Module["dynCall_iii"] = asm["dynCall_iii"];var dynCall_viiddi = Module["dynCall_viiddi"] = asm["dynCall_viiddi"];var dynCall_viiiiii = Module["dynCall_viiiiii"] = asm["dynCall_viiiiii"];var dynCall_dii = Module["dynCall_dii"] = asm["dynCall_dii"];var dynCall_i = Module["dynCall_i"] = asm["dynCall_i"];var dynCall_iiiiii = Module["dynCall_iiiiii"] = asm["dynCall_iiiiii"];var dynCall_viiid = Module["dynCall_viiid"] = asm["dynCall_viiid"];var dynCall_viififi = Module["dynCall_viififi"] = asm["dynCall_viififi"];var dynCall_viii = Module["dynCall_viii"] = asm["dynCall_viii"];var dynCall_v = Module["dynCall_v"] = asm["dynCall_v"];var dynCall_viid = Module["dynCall_viid"] = asm["dynCall_viid"];var dynCall_idd = Module["dynCall_idd"] = asm["dynCall_idd"];var dynCall_viiii = Module["dynCall_viiii"] = asm["dynCall_viiii"];Runtime.stackAlloc = Module["stackAlloc"];Runtime.stackSave = Module["stackSave"];Runtime.stackRestore = Module["stackRestore"];Runtime.establishStackSpace = Module["establishStackSpace"];Runtime.setTempRet0 = Module["setTempRet0"];Runtime.getTempRet0 = Module["getTempRet0"];Module["asm"] = asm;function ExitStatus(status) {
+    this.name = "ExitStatus";this.message = "Program terminated with exit(" + status + ")";this.status = status;
+  }ExitStatus.prototype = new Error();ExitStatus.prototype.constructor = ExitStatus;var initialStackTop;dependenciesFulfilled = function runCaller() {
+    if (!Module["calledRun"]) run();if (!Module["calledRun"]) dependenciesFulfilled = runCaller;
+  };Module["callMain"] = Module.callMain = function callMain(args) {
+    args = args || [];ensureInitRuntime();var argc = args.length + 1;function pad() {
+      for (var i = 0; i < 4 - 1; i++) {
+        argv.push(0);
+      }
+    }var argv = [allocate(intArrayFromString(Module["thisProgram"]), "i8", ALLOC_NORMAL)];pad();for (var i = 0; i < argc - 1; i = i + 1) {
+      argv.push(allocate(intArrayFromString(args[i]), "i8", ALLOC_NORMAL));pad();
+    }argv.push(0);argv = allocate(argv, "i32", ALLOC_NORMAL);try {
+      var ret = Module["_main"](argc, argv, 0);exit(ret, true);
+    } catch (e) {
+      if (e instanceof ExitStatus) {
+        return;
+      } else if (e == "SimulateInfiniteLoop") {
+        Module["noExitRuntime"] = true;return;
+      } else {
+        var toLog = e;if (e && typeof e === "object" && e.stack) {
+          toLog = [e, e.stack];
+        }Module.printErr("exception thrown: " + toLog);Module["quit"](1, e);
+      }
+    } finally {
+    }
+  };function run(args) {
+    args = args || Module["arguments"];if (runDependencies > 0) {
+      return;
+    }preRun();if (runDependencies > 0) return;if (Module["calledRun"]) return;function doRun() {
+      if (Module["calledRun"]) return;Module["calledRun"] = true;if (ABORT) return;ensureInitRuntime();preMain();if (Module["onRuntimeInitialized"]) Module["onRuntimeInitialized"]();if (Module["_main"] && shouldRunNow) Module["callMain"](args);postRun();
+    }if (Module["setStatus"]) {
+      Module["setStatus"]("Running...");setTimeout(function () {
+        setTimeout(function () {
+          Module["setStatus"]("");
+        }, 1);doRun();
+      }, 1);
+    } else {
+      doRun();
+    }
+  }Module["run"] = Module.run = run;function exit(status, implicit) {
+    if (implicit && Module["noExitRuntime"]) {
+      return;
+    }if (Module["noExitRuntime"]) {} else {
+      ABORT = true;STACKTOP = initialStackTop;exitRuntime();if (Module["onExit"]) Module["onExit"](status);
+    }if (ENVIRONMENT_IS_NODE) {
+      process["exit"](status);
+    }Module["quit"](status, new ExitStatus(status));
+  }Module["exit"] = Module.exit = exit;var abortDecorators = [];function abort(what) {
+    if (Module["onAbort"]) {
+      Module["onAbort"](what);
+    }if (what !== undefined) {
+      Module.print(what);Module.printErr(what);what = JSON.stringify(what);
+    } else {
+      what = "";
+    }ABORT = true;var extra = "\nIf this abort() is unexpected, build with -s ASSERTIONS=1 which can give more information.";var output = "abort(" + what + ") at " + stackTrace() + extra;if (abortDecorators) {
+      abortDecorators.forEach(function (decorator) {
+        output = decorator(output, what);
+      });
+    }throw output;
+  }Module["abort"] = Module.abort = abort;if (Module["preInit"]) {
+    if (typeof Module["preInit"] == "function") Module["preInit"] = [Module["preInit"]];while (Module["preInit"].length > 0) {
+      Module["preInit"].pop()();
+    }
+  }var shouldRunNow = true;if (Module["noInitialRun"]) {
+    shouldRunNow = false;
+  }run();
+});
+});
+
+/**
+ * Copyright (c) 2014-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ *
+ * 
+ * @format
+ */
+
+
+
+
+var ran = false;
+var ret = null;
+
+nbind({}, function (err, result) {
+  if (ran) {
+    return;
+  }
+
+  ran = true;
+
+  if (err) {
+    throw err;
+  }
+
+  ret = result;
+});
+
+if (!ran) {
+  throw new Error("Failed to load the yoga module - it needed to be loaded synchronously, but didn't");
+}
+
+// $FlowFixMe ret will not be null here
+var entryBrowser = entryCommon(ret.bind, ret.lib);
+
+return entryBrowser;
+
+})));
\ No newline at end of file