X-Git-Url: https://git.openstreetmap.org./rails.git/blobdiff_plain/7a98913d0cf74887180aca3520c628a52b0e7071..e3b0b7813c31e92d86ebe61deddfc735cba056b7:/vendor/assets/iD/iD.js?ds=sidebyside diff --git a/vendor/assets/iD/iD.js b/vendor/assets/iD/iD.js index 11dd2f7b0..e38326bb7 100644 --- a/vendor/assets/iD/iD.js +++ b/vendor/assets/iD/iD.js @@ -174,11 +174,13 @@ } })(this); -d3 = (function(){ - var d3 = {version: "3.1.4"}; // semver -d3.ascending = function(a, b) { +!function(){ + var d3 = {version: "3.4.6"}; // semver +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; }; @@ -188,10 +190,10 @@ d3.min = function(array, f) { a, b; if (arguments.length === 1) { - while (++i < n && ((a = array[i]) == null || a != a)) a = undefined; + while (++i < n && !((a = array[i]) != null && a <= a)) a = undefined; while (++i < n) if ((b = array[i]) != null && a > b) a = b; } else { - while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined; + while (++i < n && !((a = f.call(array, array[i], i)) != null && a <= a)) a = undefined; while (++i < n) if ((b = f.call(array, array[i], i)) != null && a > b) a = b; } return a; @@ -202,10 +204,10 @@ d3.max = function(array, f) { a, b; if (arguments.length === 1) { - while (++i < n && ((a = array[i]) == null || a != a)) a = undefined; + while (++i < n && !((a = array[i]) != null && a <= a)) a = undefined; while (++i < n) if ((b = array[i]) != null && b > a) a = b; } else { - while (++i < n && ((a = f.call(array, array[i], i)) == null || a != a)) a = undefined; + while (++i < n && !((a = f.call(array, array[i], i)) != null && a <= a)) a = undefined; while (++i < n) if ((b = f.call(array, array[i], i)) != null && b > a) a = b; } return a; @@ -217,13 +219,13 @@ d3.extent = function(array, f) { b, c; if (arguments.length === 1) { - while (++i < n && ((a = c = array[i]) == null || a != a)) a = c = undefined; + while (++i < n && !((a = c = array[i]) != null && a <= a)) a = c = undefined; while (++i < n) if ((b = array[i]) != null) { if (a > b) a = b; if (c < b) c = b; } } else { - while (++i < n && ((a = c = f.call(array, array[i], i)) == null || a != a)) a = undefined; + while (++i < n && !((a = c = f.call(array, array[i], i)) != null && a <= a)) a = undefined; while (++i < n) if ((b = f.call(array, array[i], i)) != null) { if (a > b) a = b; if (c < b) c = b; @@ -250,17 +252,17 @@ function d3_number(x) { } d3.mean = function(array, f) { - var n = array.length, + var s = 0, + n = array.length, a, - m = 0, i = -1, - j = 0; + j = n; if (arguments.length === 1) { - while (++i < n) if (d3_number(a = array[i])) m += (a - m) / ++j; + while (++i < n) if (d3_number(a = array[i])) s += a; else --j; } else { - while (++i < n) if (d3_number(a = f.call(array, array[i], i))) m += (a - m) / ++j; + while (++i < n) if (d3_number(a = f.call(array, array[i], i))) s += a; else --j; } - return j ? m : undefined; + return j ? s / j : undefined; }; // R-7 per d3.quantile = function(values, p) { @@ -274,16 +276,17 @@ d3.quantile = function(values, p) { d3.median = function(array, f) { if (arguments.length > 1) array = array.map(f); array = array.filter(d3_number); - return array.length ? d3.quantile(array.sort(d3.ascending), .5) : undefined; + return array.length ? d3.quantile(array.sort(d3_ascending), .5) : undefined; }; -d3.bisector = function(f) { + +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 (f.call(a, a[mid], mid) < x) lo = mid + 1; + if (compare(a[mid], x) < 0) lo = mid + 1; else hi = mid; } return lo; @@ -293,17 +296,23 @@ d3.bisector = function(f) { if (arguments.length < 4) hi = a.length; while (lo < hi) { var mid = lo + hi >>> 1; - if (x < f.call(a, a[mid], mid)) hi = mid; + if (compare(a[mid], x) > 0) hi = mid; else lo = mid + 1; } return lo; } }; -}; +} -var d3_bisector = d3.bisector(function(d) { return d; }); -d3.bisectLeft = d3_bisector.left; -d3.bisect = d3.bisectRight = d3_bisector.right; +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) { var m = array.length, t, i; while (m) { @@ -313,12 +322,15 @@ d3.shuffle = function(array) { return array; }; d3.permute = function(array, indexes) { - var permutes = [], - i = -1, - n = indexes.length; - while (++i < n) permutes[i] = array[indexes[i]]; + 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.zip = function() { if (!(n = arguments.length)) return []; @@ -353,8 +365,28 @@ d3.entries = function(map) { return entries; }; d3.merge = function(arrays) { - return Array.prototype.concat.apply([], 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; @@ -365,7 +397,7 @@ d3.range = function(start, stop, step) { } if ((stop - start) / step === Infinity) throw new Error("infinite range"); var range = [], - k = d3_range_integerScale(Math.abs(step)), + k = d3_range_integerScale(abs(step)), i = -1, j; start *= k, stop *= k, step *= k; @@ -394,31 +426,23 @@ function d3_class(ctor, properties) { d3.map = function(object) { var map = new d3_Map; - for (var key in object) map.set(key, object[key]); + if (object instanceof d3_Map) object.forEach(function(key, value) { map.set(key, value); }); + else for (var key in object) map.set(key, object[key]); return map; }; function d3_Map() {} d3_class(d3_Map, { - has: function(key) { - return d3_map_prefix + key in this; - }, + has: d3_map_has, get: function(key) { return this[d3_map_prefix + key]; }, set: function(key, value) { return this[d3_map_prefix + key] = value; }, - remove: function(key) { - key = d3_map_prefix + key; - return key in this && delete this[key]; - }, - keys: function() { - var keys = []; - this.forEach(function(key) { keys.push(key); }); - return keys; - }, + remove: d3_map_remove, + keys: d3_map_keys, values: function() { var values = []; this.forEach(function(key, value) { values.push(value); }); @@ -429,18 +453,42 @@ d3_class(d3_Map, { this.forEach(function(key, value) { entries.push({key: key, value: value}); }); return entries; }, + size: d3_map_size, + empty: d3_map_empty, forEach: function(f) { - for (var key in this) { - if (key.charCodeAt(0) === d3_map_prefixCode) { - f.call(this, key.substring(1), this[key]); - } - } + for (var key in this) if (key.charCodeAt(0) === d3_map_prefixCode) f.call(this, key.substring(1), this[key]); } }); var d3_map_prefix = "\0", // prevent collision with built-ins d3_map_prefixCode = d3_map_prefix.charCodeAt(0); +function d3_map_has(key) { + return d3_map_prefix + key in this; +} + +function d3_map_remove(key) { + key = d3_map_prefix + key; + return key in this && delete this[key]; +} + +function d3_map_keys() { + var keys = []; + this.forEach(function(key) { keys.push(key); }); + return keys; +} + +function d3_map_size() { + var size = 0; + for (var key in this) if (key.charCodeAt(0) === d3_map_prefixCode) ++size; + return size; +} + +function d3_map_empty() { + for (var key in this) if (key.charCodeAt(0) === d3_map_prefixCode) return false; + return true; +} + d3.nest = function() { var nest = {}, keys = [], @@ -538,17 +586,15 @@ d3.nest = function() { }; d3.set = function(array) { - var set = new d3_Set(); - if (array) for (var i = 0; i < array.length; i++) set.add(array[i]); + 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() {} d3_class(d3_Set, { - has: function(value) { - return d3_map_prefix + value in this; - }, + has: d3_map_has, add: function(value) { this[d3_map_prefix + value] = true; return value; @@ -557,24 +603,31 @@ d3_class(d3_Set, { value = d3_map_prefix + value; return value in this && delete this[value]; }, - values: function() { - var values = []; - this.forEach(function(value) { - values.push(value); - }); - return values; - }, + values: d3_map_keys, + size: d3_map_size, + empty: d3_map_empty, forEach: function(f) { - for (var value in this) { - if (value.charCodeAt(0) === d3_map_prefixCode) { - f.call(this, value.substring(1)); - } - } + for (var value in this) if (value.charCodeAt(0) === d3_map_prefixCode) f.call(this, value.substring(1)); } }); d3.behavior = {}; +var d3_arraySlice = [].slice, + d3_array = function(list) { return d3_arraySlice.call(list); }; // conversion for NodeLists + var d3_document = document, + d3_documentElement = d3_document.documentElement, d3_window = window; + +// Redefine d3_array if the browser doesn’t support slice-based conversion. +try { + d3_array(d3_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; + }; +} // Copies a variable number of methods from source to target. d3.rebind = function(target, source) { var i = 1, n = arguments.length, method; @@ -592,6 +645,18 @@ function d3_rebind(target, source, method) { }; } +function d3_vendorSymbol(object, name) { + if (name in object) return name; + name = name.charAt(0).toUpperCase() + name.substring(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, @@ -662,9 +727,13 @@ function d3_dispatch_event(dispatch) { d3.event = null; +function d3_eventPreventDefault() { + d3.event.preventDefault(); +} + function d3_eventCancel() { - d3.event.stopPropagation(); d3.event.preventDefault(); + d3.event.stopPropagation(); } function d3_eventSource() { @@ -709,105 +778,45 @@ function d3_eventDispatch(target) { return dispatch; } - -d3.mouse = function(container) { - return d3_mousePoint(container, d3_eventSource()); -}; - -// https://bugs.webkit.org/show_bug.cgi?id=44083 -var d3_mouse_bug44083 = /WebKit/.test(d3_window.navigator.userAgent) ? -1 : 0; - -function d3_mousePoint(container, e) { - var svg = container.ownerSVGElement || container; - if (svg.createSVGPoint) { - var point = svg.createSVGPoint(); - if (d3_mouse_bug44083 < 0 && (d3_window.scrollX || d3_window.scrollY)) { - svg = d3.select(d3_document.body).append("svg") - .style("position", "absolute") - .style("top", 0) - .style("left", 0); - 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.requote = function(s) { + return s.replace(d3_requote_re, "\\$&"); }; -var d3_array = d3_arraySlice; // conversion for NodeLists - -function d3_arrayCopy(pseudoarray) { - var i = -1, n = pseudoarray.length, array = []; - while (++i < n) array.push(pseudoarray[i]); - return array; -} - -function d3_arraySlice(pseudoarray) { - return Array.prototype.slice.call(pseudoarray); -} - -try { - d3_array(d3_document.documentElement.childNodes)[0].nodeType; -} catch(e) { - d3_array = d3_arrayCopy; -} - -var d3_arraySubclass = [].__proto__? +var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; +var d3_subclass = {}.__proto__? // Until ECMAScript supports array subclassing, prototype injection works well. -function(array, prototype) { - array.__proto__ = prototype; +function(object, prototype) { + object.__proto__ = prototype; }: // And if your browser doesn't support __proto__, we'll use direct extension. -function(array, prototype) { - for (var property in prototype) array[property] = prototype[property]; -}; - -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; - }) : []; +function(object, prototype) { + for (var property in prototype) object[property] = prototype[property]; }; function d3_selection(groups) { - d3_arraySubclass(groups, d3_selectionPrototype); + 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_selectRoot = d3_document.documentElement, - d3_selectMatcher = d3_selectRoot.matchesSelector || d3_selectRoot.webkitMatchesSelector || d3_selectRoot.mozMatchesSelector || d3_selectRoot.msMatchesSelector || d3_selectRoot.oMatchesSelector, + d3_selectMatcher = d3_documentElement[d3_vendorSymbol(d3_documentElement, "matchesSelector")], d3_selectMatches = function(n, s) { return d3_selectMatcher.call(n, s); }; // Prefer Sizzle, if available. if (typeof Sizzle === "function") { d3_select = function(s, n) { return Sizzle(s, n)[0] || null; }; - d3_selectAll = function(s, n) { return Sizzle.uniqueSort(Sizzle(s, n)); }; + d3_selectAll = Sizzle; d3_selectMatches = Sizzle.matchesSelector; } -var d3_selectionPrototype = []; - d3.selection = function() { return d3_selectionRoot; }; -d3.selection.prototype = d3_selectionPrototype; +var d3_selectionPrototype = d3.selection.prototype = []; d3_selectionPrototype.select = function(selector) { @@ -817,14 +826,14 @@ d3_selectionPrototype.select = function(selector) { group, node; - if (typeof selector !== "function") selector = d3_selection_selector(selector); + 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)); + subgroup.push(subnode = selector.call(node, node.__data__, i, j)); if (subnode && "__data__" in node) subnode.__data__ = node.__data__; } else { subgroup.push(null); @@ -836,7 +845,7 @@ d3_selectionPrototype.select = function(selector) { }; function d3_selection_selector(selector) { - return function() { + return typeof selector === "function" ? selector : function() { return d3_select(selector, this); }; } @@ -846,12 +855,12 @@ d3_selectionPrototype.selectAll = function(selector) { subgroup, node; - if (typeof selector !== "function") selector = d3_selection_selectorAll(selector); + 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))); + subgroups.push(subgroup = d3_array(selector.call(node, node.__data__, i, j))); subgroup.parentNode = node; } } @@ -861,7 +870,7 @@ d3_selectionPrototype.selectAll = function(selector) { }; function d3_selection_selectorAll(selector) { - return function() { + return typeof selector === "function" ? selector : function() { return d3_selectAll(selector, this); }; } @@ -950,11 +959,6 @@ function d3_selection_attr(name, value) { function d3_collapse(s) { return s.trim().replace(/\s+/g, " "); } -d3.requote = function(s) { - return s.replace(d3_requote_re, "\\$&"); -}; - -var d3_requote_re = /[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g; d3_selectionPrototype.classed = function(name, value) { if (arguments.length < 2) { @@ -964,7 +968,7 @@ d3_selectionPrototype.classed = function(name, value) { // probably doesn't support it on SVG elements (which can be animated). if (typeof name === "string") { var node = this.node(), - n = (name = name.trim().split(/^|\s+/g)).length, + n = (name = d3_selection_classes(name)).length, i = -1; if (value = node.classList) { while (++i < n) if (!value.contains(name[i])) return false; @@ -989,9 +993,13 @@ function d3_selection_classedRe(name) { return new RegExp("(?:^|\\s+)" + d3.requote(name) + "(?:\\s+|$)", "g"); } +function d3_selection_classes(name) { + return name.trim().split(/^|\s+/); +} + // Multiple class names are allowed (e.g., "foo bar"). function d3_selection_classed(name, value) { - name = name.trim().split(/\s+/).map(d3_selection_classedName); + name = d3_selection_classes(name).map(d3_selection_classedName); var n = name.length; function classedConstant() { @@ -1125,8 +1133,8 @@ 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; }) + ? function() { if (this.textContent !== "") this.textContent = ""; } + : function() { if (this.textContent !== value) this.textContent = value; }) : this.node().textContent; }; @@ -1139,40 +1147,25 @@ d3_selectionPrototype.html = function(value) { : this.node().innerHTML; }; -// TODO append(node)? -// TODO append(function)? d3_selectionPrototype.append = function(name) { - name = d3.ns.qualify(name); - - function append() { - return this.appendChild(d3_document.createElementNS(this.namespaceURI, name)); - } - - function appendNS() { - return this.appendChild(d3_document.createElementNS(name.space, name.local)); - } - - return this.select(name.local ? appendNS : append); + name = d3_selection_creator(name); + return this.select(function() { + return this.appendChild(name.apply(this, arguments)); + }); }; -d3_selectionPrototype.insert = function(name, before) { - name = d3.ns.qualify(name); - - if (typeof before !== "function") before = d3_selection_selector(before); - - function insert(d, i) { - return this.insertBefore( - d3_document.createElementNS(this.namespaceURI, name), - before.call(this, d, i)); - } - - function insertNS(d, i) { - return this.insertBefore( - d3_document.createElementNS(name.space, name.local), - before.call(this, d, i)); - } +function d3_selection_creator(name) { + return typeof name === "function" ? name + : (name = d3.ns.qualify(name)).local ? function() { return this.ownerDocument.createElementNS(name.space, name.local); } + : function() { return this.ownerDocument.createElementNS(this.namespaceURI, name); }; +} - return this.select(name.local ? insertNS : insert); +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); + }); }; // TODO remove(selector)? @@ -1319,7 +1312,7 @@ d3_selectionPrototype.filter = function(filter) { 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)) { + if ((node = group[i]) && filter.call(node, node.__data__, i, j)) { subgroup.push(node); } } @@ -1353,12 +1346,157 @@ d3_selectionPrototype.sort = function(comparator) { }; function d3_selection_sortComparator(comparator) { - if (!arguments.length) comparator = d3.ascending; + if (!arguments.length) comparator = d3_ascending; return function(a, b) { - return (!a - !b) || comparator(a.__data__, b.__data__); + return a && b ? comparator(a.__data__, b.__data__) : !a - !b; }; } -function d3_noop() {} + +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; + this.each(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; + }; +} + +// import "../transition/transition"; + +d3_selectionPrototype.transition = function() { + var id = d3_transitionInheritId || ++d3_transitionId, + 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, id, transition); + subgroup.push(node); + } + } + + return d3_transition(subgroups, id); +}; +// import "../transition/transition"; + +d3_selectionPrototype.interrupt = function() { + return this.each(d3_selection_interrupt); +}; + +function d3_selection_interrupt() { + var lock = this.__transition__; + if (lock) ++lock.active; +} + +// TODO fast singleton implementation? +d3.select = function(node) { + var group = [typeof node === "string" ? d3_select(node, d3_document) : node]; + group.parentNode = d3_documentElement; + return d3_selection([group]); +}; + +d3.selectAll = function(nodes) { + var group = d3_array(typeof nodes === "string" ? d3_selectAll(nodes, d3_document) : nodes); + group.parentNode = d3_documentElement; + return d3_selection([group]); +}; + +var d3_selectionRoot = d3.select(d3_documentElement); d3_selectionPrototype.on = function(type, listener, capture) { var n = arguments.length; @@ -1458,161 +1596,239 @@ function d3_selection_onFilter(listener, argumentz) { }; } -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); - } +var d3_event_dragSelect = "onselectstart" in d3_document ? null : d3_vendorSymbol(d3_documentElement.style, "userSelect"), + d3_event_dragId = 0; + +function d3_event_dragSuppress() { + var name = ".dragsuppress-" + ++d3_event_dragId, + click = "click" + name, + w = d3.select(d3_window) + .on("touchmove" + name, d3_eventPreventDefault) + .on("dragstart" + name, d3_eventPreventDefault) + .on("selectstart" + name, d3_eventPreventDefault); + if (d3_event_dragSelect) { + var style = d3_documentElement.style, + select = style[d3_event_dragSelect]; + style[d3_event_dragSelect] = "none"; } - return groups; + return function(suppressClick) { + w.on(name, null); + if (d3_event_dragSelect) style[d3_event_dragSelect] = select; + if (suppressClick) { // suppress the next click, but only if it’s immediate + function off() { w.on(click, null); } + w.on(click, function() { d3_eventCancel(); off(); }, true); + setTimeout(off, 0); + } + }; } -d3_selectionPrototype.call = function(callback) { - var args = d3_array(arguments); - callback.apply(args[0] = this, args); - return this; +d3.mouse = function(container) { + return d3_mousePoint(container, d3_eventSource()); }; -d3_selectionPrototype.empty = function() { - return !this.node(); +function d3_mousePoint(container, e) { + if (e.changedTouches) e = e.changedTouches[0]; + var svg = container.ownerSVGElement || container; + if (svg.createSVGPoint) { + var point = svg.createSVGPoint(); + 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_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.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 π = Math.PI, + τ = 2 * π, + halfπ = π / 2, + ε = 1e-6, + ε2 = ε * ε, + d3_radians = π / 180, + d3_degrees = 180 / π; -function d3_selection_enter(selection) { - d3_arraySubclass(selection, d3_selection_enterPrototype); - return selection; +function d3_sgn(x) { + return x > 0 ? 1 : x < 0 ? -1 : 0; } -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.insert = d3_selectionPrototype.insert; -d3_selection_enterPrototype.empty = d3_selectionPrototype.empty; -d3_selection_enterPrototype.node = d3_selectionPrototype.node; +// Returns the 2D cross product of AB and AC vectors, i.e., the z-component of +// the 3D cross product in a quadrant I Cartesian coordinate system (+x is +// right, +y is up). Returns a positive value if ABC is counter-clockwise, +// negative if clockwise, and zero if the points are collinear. +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); +} -d3_selection_enterPrototype.select = function(selector) { - var subgroups = [], - subgroup, - subnode, - upgroup, - group, - node; +function d3_asin(x) { + return x > 1 ? halfπ : x < -1 ? -halfπ : Math.asin(x); +} - 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)); - subnode.__data__ = node.__data__; - } else { - subgroup.push(null); - } - } - } +function d3_sinh(x) { + return ((x = Math.exp(x)) - 1 / x) / 2; +} - return d3_selection(subgroups); -}; +function d3_cosh(x) { + return ((x = Math.exp(x)) + 1 / x) / 2; +} -d3_selectionPrototype.transition = function() { - var id = d3_transitionInheritId || ++d3_transitionId, - subgroups = [], - subgroup, - node, - transition = Object.create(d3_transitionInherit); +function d3_tanh(x) { + return ((x = Math.exp(2 * x)) - 1) / (x + 1); +} - transition.time = Date.now(); +function d3_haversin(x) { + return (x = Math.sin(x / 2)) * x; +} - 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, id, transition); - subgroup.push(node); - } +var ρ = Math.SQRT2, + ρ2 = 2, + ρ4 = 4; + +// p0 = [ux0, uy0, w0] +// p1 = [ux1, uy1, w1] +d3.interpolateZoom = function(p0, p1) { + var ux0 = p0[0], uy0 = p0[1], w0 = p0[2], + ux1 = p1[0], uy1 = p1[1], w1 = p1[2]; + + var dx = ux1 - ux0, + dy = uy1 - uy0, + d2 = dx * dx + dy * dy, + 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), + dr = r1 - r0, + S = (dr || Math.log(w1 / w0)) / ρ; + + function interpolate(t) { + var s = t * S; + if (dr) { + // General case. + var 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) + ]; + } + // Special case for u0 ~= u1. + return [ + ux0 + t * dx, + uy0 + t * dy, + w0 * Math.exp(ρ * s) + ]; } - return d3_transition(subgroups, id); -}; - -var d3_selectionRoot = d3_selection([[d3_document]]); - -d3_selectionRoot[0].parentNode = d3_selectRoot; - -// TODO fast singleton implementation! -// TODO select(function) -d3.select = function(selector) { - return typeof selector === "string" - ? d3_selectionRoot.select(selector) - : d3_selection([[selector]]); // assume node -}; + interpolate.duration = S * 1000; -// TODO selectAll(function) -d3.selectAll = function(selector) { - return typeof selector === "string" - ? d3_selectionRoot.selectAll(selector) - : d3_selection([d3_array(selector)]); // assume node[] + return interpolate; }; d3.behavior.zoom = function() { - var translate = [0, 0], + var view = {x: 0, y: 0, k: 1}, translate0, // translate when we started zooming (to avoid drift) - scale = 1, - scale0, // scale when we started touching + center, // desired position of translate0 after zooming + size = [960, 500], // viewport size; required for zoom interpolation scaleExtent = d3_behavior_zoomInfinity, - event = d3_eventDispatch(zoom, "zoom"), + mousedown = "mousedown.zoom", + mousemove = "mousemove.zoom", + mouseup = "mouseup.zoom", + mousewheelTimer, + touchstart = "touchstart.zoom", + touchtime, // time of last touchstart (to detect double-tap) + event = d3_eventDispatch(zoom, "zoomstart", "zoom", "zoomend"), x0, x1, y0, - y1, - touchtime; // time of last touchstart (to detect double-tap) - - function zoom() { - this.on("mousedown.zoom", mousedown) - .on("mousemove.zoom", mousemove) - .on(d3_behavior_zoomWheel + ".zoom", mousewheel) - .on("dblclick.zoom", dblclick) - .on("touchstart.zoom", touchstart) - .on("touchmove.zoom", touchmove) - .on("touchend.zoom", touchstart); + y1; + + function zoom(g) { + g .on(mousedown, mousedowned) + .on(d3_behavior_zoomWheel + ".zoom", mousewheeled) + .on(mousemove, mousewheelreset) + .on("dblclick.zoom", dblclicked) + .on(touchstart, touchstarted); } - zoom.translate = function(x) { - if (!arguments.length) return translate; - translate = x.map(Number); + 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}; // pre-transition state + zoomstarted(dispatch); + }) + .tween("zoom:zoom", function() { + var dx = size[0], + dy = size[1], + cx = dx / 2, + cy = 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("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}; // copy-on-write rescale(); return zoom; }; - zoom.scale = function(x) { - if (!arguments.length) return scale; - scale = +x; + zoom.scale = function(_) { + if (!arguments.length) return view.k; + view = {x: view.x, y: view.y, k: +_}; // copy-on-write rescale(); return zoom; }; - zoom.scaleExtent = function(x) { + zoom.scaleExtent = function(_) { if (!arguments.length) return scaleExtent; - scaleExtent = x == null ? d3_behavior_zoomInfinity : x.map(Number); + 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; }; @@ -1620,8 +1836,7 @@ d3.behavior.zoom = function() { if (!arguments.length) return x1; x1 = z; x0 = z.copy(); - translate = [0, 0]; - scale = 1; + view = {x: 0, y: 0, k: 1}; // copy-on-write return zoom; }; @@ -1629,126 +1844,200 @@ d3.behavior.zoom = function() { if (!arguments.length) return y1; y1 = z; y0 = z.copy(); - translate = [0, 0]; - scale = 1; + view = {x: 0, y: 0, k: 1}; // copy-on-write return zoom; }; function location(p) { - return [(p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale]; + return [(p[0] - view.x) / view.k, (p[1] - view.y) / view.k]; } function point(l) { - return [l[0] * scale + translate[0], l[1] * scale + translate[1]]; + return [l[0] * view.k + view.x, l[1] * view.k + view.y]; } function scaleTo(s) { - scale = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s)); + view.k = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s)); } function translateTo(p, l) { l = point(l); - translate[0] += p[0] - l[0]; - translate[1] += p[1] - l[1]; + view.x += p[0] - l[0]; + view.y += p[1] - l[1]; } function rescale() { - if (x1) x1.domain(x0.range().map(function(x) { return (x - translate[0]) / scale; }).map(x0.invert)); - if (y1) y1.domain(y0.range().map(function(y) { return (y - translate[1]) / scale; }).map(y0.invert)); + 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 dispatch(event) { + function zoomstarted(dispatch) { + dispatch({type: "zoomstart"}); + } + + function zoomed(dispatch) { rescale(); - d3.event.preventDefault(); - event({type: "zoom", scale: scale, translate: translate}); + dispatch({type: "zoom", scale: view.k, translate: [view.x, view.y]}); + } + + function zoomended(dispatch) { + dispatch({type: "zoomend"}); } - function mousedown() { - var target = this, - event_ = event.of(target, arguments), - eventTarget = d3.event.target, - moved = 0, - w = d3.select(d3_window).on("mousemove.zoom", mousemove).on("mouseup.zoom", mouseup), - l = location(d3.mouse(target)); + function mousedowned() { + var that = this, + target = d3.event.target, + dispatch = event.of(that, arguments), + dragged = 0, + subject = d3.select(d3_window).on(mousemove, moved).on(mouseup, ended), + location0 = location(d3.mouse(that)), + dragRestore = d3_event_dragSuppress(); - d3_window.focus(); - d3_eventCancel(); + d3_selection_interrupt.call(that); + zoomstarted(dispatch); - function mousemove() { - moved = 1; - translateTo(d3.mouse(target), l); - dispatch(event_); - } - - function mouseup() { - if (moved) d3_eventCancel(); - w.on("mousemove.zoom", null).on("mouseup.zoom", null); - if (moved && d3.event.target === eventTarget) { - w.on("click.zoom", click, true); - window.setTimeout(function() { - // Remove click block if click didn't fire - w.on("click.zoom", null); - }, 0); + function moved() { + dragged = 1; + translateTo(d3.mouse(that), location0); + zoomed(dispatch); + } + + function ended() { + subject.on(mousemove, d3_window === that ? mousewheelreset : null).on(mouseup, null); + dragRestore(dragged && d3.event.target === target); + zoomended(dispatch); + } + } + + // These closures persist for as long as at least one touch is active. + function touchstarted() { + var that = this, + dispatch = event.of(that, arguments), + locations0 = {}, // touchstart locations + distance0 = 0, // distance² between initial touches + scale0, // scale when we started touching + zoomName = ".zoom-" + d3.event.changedTouches[0].identifier, + touchmove = "touchmove" + zoomName, + touchend = "touchend" + zoomName, + target = d3.select(d3.event.target).on(touchmove, moved).on(touchend, ended), + subject = d3.select(that).on(mousedown, null).on(touchstart, started), // prevent duplicate events + dragRestore = d3_event_dragSuppress(); + + d3_selection_interrupt.call(that); + started(); + zoomstarted(dispatch); + + // Updates locations of any touches in locations0. + 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; + } + + // Temporarily override touchstart while gesture is active. + function started() { + // Only track touches started on the target element. + 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) { // dbltap + var p = touches[0], l = locations0[p.identifier]; + scaleTo(view.k * 2); + translateTo(p, l); + d3_eventPreventDefault(); + zoomed(dispatch); + } + 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 click() { - d3_eventCancel(); - w.on("click.zoom", null); + function moved() { + var touches = d3.touches(that), + p0, l0, + p1, l1; + 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 there are any globally-active touches remaining, remove the ended + // touches from locations0. + 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]; + } + // If locations0 is not empty, then relocate and continue listening for + // touchmove and touchend. + for (var identifier in locations0) { + return void relocate(); // locations may have detached due to rotation + } + } + // Otherwise, remove touchmove and touchend listeners. + target.on(zoomName, null); + subject.on(mousedown, mousedowned).on(touchstart, touchstarted); + dragRestore(); + zoomended(dispatch); } } - function mousewheel() { - if (!translate0) translate0 = location(d3.mouse(this)); - scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * scale); - translateTo(d3.mouse(this), translate0); - dispatch(event.of(this, arguments)); + function mousewheeled() { + var dispatch = event.of(this, arguments); + if (mousewheelTimer) clearTimeout(mousewheelTimer); + else d3_selection_interrupt.call(this), zoomstarted(dispatch); + mousewheelTimer = setTimeout(function() { mousewheelTimer = null; zoomended(dispatch); }, 50); + d3_eventPreventDefault(); + var point = center || d3.mouse(this); + if (!translate0) translate0 = location(point); + scaleTo(Math.pow(2, d3_behavior_zoomDelta() * .002) * view.k); + translateTo(point, translate0); + zoomed(dispatch); } - function mousemove() { + function mousewheelreset() { translate0 = null; } - function dblclick() { - var p = d3.mouse(this), l = location(p), k = Math.log(scale) / Math.LN2; + function dblclicked() { + var dispatch = event.of(this, arguments), + p = d3.mouse(this), + l = location(p), + k = Math.log(view.k) / Math.LN2; + zoomstarted(dispatch); scaleTo(Math.pow(2, d3.event.shiftKey ? Math.ceil(k) - 1 : Math.floor(k) + 1)); translateTo(p, l); - dispatch(event.of(this, arguments)); - } - - function touchstart() { - var touches = d3.touches(this), - now = Date.now(); - - scale0 = scale; - translate0 = {}; - touches.forEach(function(t) { translate0[t.identifier] = location(t); }); - d3_eventCancel(); - - if (touches.length === 1) { - if (now - touchtime < 500) { // dbltap - var p = touches[0], l = location(touches[0]); - scaleTo(scale * 2); - translateTo(p, l); - dispatch(event.of(this, arguments)); - } - touchtime = now; - } - } - - function touchmove() { - var touches = d3.touches(this), - p0 = touches[0], - l0 = translate0[p0.identifier]; - if (p1 = touches[1]) { - var p1, l1 = translate0[p1.identifier]; - p0 = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2]; - l0 = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2]; - scaleTo(d3.event.scale * scale0); - } - translateTo(p0, l0); - touchtime = null; - dispatch(event.of(this, arguments)); + zoomed(dispatch); + zoomended(dispatch); } return d3.rebind(zoom, event, "on"); @@ -1767,34 +2056,33 @@ function d3_functor(v) { d3.functor = d3_functor; -var d3_timer_id = 0, - d3_timer_byId = {}, - d3_timer_queue = null, +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); + } + } +}; + +var d3_timer_queueHead, + d3_timer_queueTail, d3_timer_interval, // is an interval (or frame) active? - d3_timer_timeout; // is a timeout active? + d3_timer_timeout, // is a timeout active? + d3_timer_active, // active timer object + d3_timer_frame = d3_window[d3_vendorSymbol(d3_window, "requestAnimationFrame")] || function(callback) { setTimeout(callback, 17); }; // The timer will continue to fire until callback returns true. d3.timer = function(callback, delay, then) { - if (arguments.length < 3) { - if (arguments.length < 2) delay = 0; - else if (!isFinite(delay)) return; - then = Date.now(); - } - - // If the callback's already in the queue, update it. - var timer = d3_timer_byId[callback.id]; - if (timer && timer.callback === callback) { - timer.then = then; - timer.delay = delay; - } + var n = arguments.length; + if (n < 2) delay = 0; + if (n < 3) then = Date.now(); - // Otherwise, add the callback to the queue. - else d3_timer_byId[callback.id = ++d3_timer_id] = d3_timer_queue = { - callback: callback, - then: then, - delay: delay, - next: d3_timer_queue - }; + // Add the callback to the tail of the queue. + var time = then + delay, timer = {c: callback, t: time, f: false, n: null}; + if (d3_timer_queueTail) d3_timer_queueTail.n = timer; + else d3_timer_queueHead = timer; + d3_timer_queueTail = timer; // Start animatin'! if (!d3_timer_interval) { @@ -1805,17 +2093,8 @@ d3.timer = function(callback, delay, then) { }; function d3_timer_step() { - var elapsed, - now = Date.now(), - t1 = d3_timer_queue; - - while (t1) { - elapsed = now - t1.then; - if (elapsed >= t1.delay) t1.flush = t1.callback(elapsed); - t1 = t1.next; - } - - var delay = d3_timer_flush() - now; + var now = d3_timer_mark(), + delay = d3_timer_sweep() - now; if (delay > 24) { if (isFinite(delay)) { clearTimeout(d3_timer_timeout); @@ -1829,69 +2108,36 @@ function d3_timer_step() { } d3.timer.flush = function() { - var elapsed, - now = Date.now(), - t1 = d3_timer_queue; + d3_timer_mark(); + d3_timer_sweep(); +}; - while (t1) { - elapsed = now - t1.then; - if (!t1.delay) t1.flush = t1.callback(elapsed); - t1 = t1.next; +function d3_timer_mark() { + var now = Date.now(); + d3_timer_active = d3_timer_queueHead; + while (d3_timer_active) { + if (now >= d3_timer_active.t) d3_timer_active.f = d3_timer_active.c(now - d3_timer_active.t); + d3_timer_active = d3_timer_active.n; } - - d3_timer_flush(); -}; + return now; +} // Flush after callbacks to avoid concurrent queue modification. -function d3_timer_flush() { - var t0 = null, - t1 = d3_timer_queue, - then = Infinity; +// Returns the time of the earliest active timer, post-sweep. +function d3_timer_sweep() { + var t0, + t1 = d3_timer_queueHead, + time = Infinity; while (t1) { - if (t1.flush) { - delete d3_timer_byId[t1.callback.id]; - t1 = t0 ? t0.next = t1.next : d3_timer_queue = t1.next; + if (t1.f) { + t1 = t0 ? t0.n = t1.n : d3_timer_queueHead = t1.n; } else { - then = Math.min(then, t1.then + t1.delay); - t1 = (t0 = t1).next; + if (t1.t < time) time = t1.t; + t1 = (t0 = t1).n; } } - return then; -} - -var d3_timer_frame = d3_window.requestAnimationFrame - || d3_window.webkitRequestAnimationFrame - || d3_window.mozRequestAnimationFrame - || d3_window.oRequestAnimationFrame - || d3_window.msRequestAnimationFrame - || function(callback) { setTimeout(callback, 17); }; -var π = Math.PI, - ε = 1e-6, - d3_radians = π / 180, - d3_degrees = 180 / π; - -function d3_sgn(x) { - return x > 0 ? 1 : x < 0 ? -1 : 0; -} - -function d3_acos(x) { - return Math.acos(Math.max(-1, Math.min(1, x))); -} - -function d3_asin(x) { - return x > 1 ? π / 2 : x < -1 ? -π / 2 : Math.asin(x); -} - -function d3_sinh(x) { - return (Math.exp(x) - Math.exp(-x)) / 2; -} - -function d3_cosh(x) { - return (Math.exp(x) + Math.exp(-x)) / 2; -} - -function d3_haversin(x) { - return (x = Math.sin(x / 2)) * x; + d3_timer_queueTail = t0; + return time; } d3.geo = {}; function d3_identity(d) { @@ -1904,18 +2150,18 @@ function d3_true() { function d3_geo_spherical(cartesian) { return [ Math.atan2(cartesian[1], cartesian[0]), - Math.asin(Math.max(-1, Math.min(1, cartesian[2]))) + d3_asin(cartesian[2]) ]; } function d3_geo_sphericalEqual(a, b) { - return Math.abs(a[0] - b[0]) < ε && Math.abs(a[1] - b[1]) < ε; + return abs(a[0] - b[0]) < ε && abs(a[1] - b[1]) < ε; } // General spherical polygon clipping algorithm: takes a polygon, cuts it into // visible line segments and rejoins the segments by interpolating along the // clip edge. -function d3_geo_clipPolygon(segments, compare, inside, interpolate, listener) { +function d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener) { var subject = [], clip = []; @@ -1934,14 +2180,14 @@ function d3_geo_clipPolygon(segments, compare, inside, interpolate, listener) { return; } - var a = {point: p0, points: segment, other: null, visited: false, entry: true, subject: true}, - b = {point: p0, points: [p0], other: a, visited: false, entry: false, subject: false}; - a.other = b; + 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 = {point: p1, points: [p1], other: null, visited: false, entry: false, subject: true}; - b = {point: p1, points: [p1], other: a, visited: false, entry: true, subject: false}; - a.other = 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); }); @@ -1950,41 +2196,42 @@ function d3_geo_clipPolygon(segments, compare, inside, interpolate, listener) { d3_geo_clipPolygonLinkCircular(clip); if (!subject.length) return; - if (inside) for (var i = 1, e = !inside(clip[0].point), n = clip.length; i < n; ++i) { - clip[i].entry = (e = !e); + for (var i = 0, entry = clipStartInside, n = clip.length; i < n; ++i) { + clip[i].e = entry = !entry; } var start = subject[0], - current, points, point; while (1) { // Find first unvisited intersection. - current = start; - while (current.visited) if ((current = current.next) === start) return; - points = current.points; + var current = start, + isSubject = true; + while (current.v) if ((current = current.n) === start) return; + points = current.z; listener.lineStart(); do { - current.visited = current.other.visited = true; - if (current.entry) { - if (current.subject) { - for (var i = 0; i < points.length; i++) listener.point((point = points[i])[0], point[1]); + 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.point, current.next.point, 1, listener); + interpolate(current.x, current.n.x, 1, listener); } - current = current.next; + current = current.n; } else { - if (current.subject) { - points = current.prev.points; - for (var i = points.length; --i >= 0;) listener.point((point = points[i])[0], point[1]); + 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.point, current.prev.point, -1, listener); + interpolate(current.x, current.p.x, -1, listener); } - current = current.prev; + current = current.p; } - current = current.other; - points = current.points; - } while (!current.visited); + current = current.o; + points = current.z; + isSubject = !isSubject; + } while (!current.v); listener.lineEnd(); } } @@ -1996,17 +2243,27 @@ function d3_geo_clipPolygonLinkCircular(array) { a = array[0], b; while (++i < n) { - a.next = b = array[i]; - b.prev = a; + a.n = b = array[i]; + b.p = a; a = b; } - a.next = b = array[0]; - b.prev = a; + a.n = b = array[0]; + b.p = a; } -function d3_geo_clip(pointVisible, clipLine, interpolate) { - return function(listener) { - var line = clipLine(listener); +function d3_geo_clipPolygonIntersection(point, points, other, entry) { + this.x = point; + this.z = points; + this.o = other; // another intersection + this.e = entry; // is an entry? + this.v = false; // visited + this.n = this.p = null; // next & previous +} + +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, @@ -2016,10 +2273,8 @@ function d3_geo_clip(pointVisible, clipLine, interpolate) { clip.point = pointRing; clip.lineStart = ringStart; clip.lineEnd = ringEnd; - invisible = false; - invisibleArea = visibleArea = 0; segments = []; - listener.polygonStart(); + polygon = []; }, polygonEnd: function() { clip.point = point; @@ -2027,15 +2282,18 @@ function d3_geo_clip(pointVisible, clipLine, interpolate) { clip.lineEnd = lineEnd; segments = d3.merge(segments); + var clipStartInside = d3_geo_pointInPolygon(rotatedClipStart, polygon); if (segments.length) { - d3_geo_clipPolygon(segments, d3_geo_clipSort, null, interpolate, listener); - } else if (visibleArea < -ε || invisible && invisibleArea < -ε) { + 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(); } - listener.polygonEnd(); - segments = null; + if (polygonStarted) listener.polygonEnd(), polygonStarted = false; + segments = polygon = null; }, sphere: function() { listener.polygonStart(); @@ -2046,23 +2304,29 @@ function d3_geo_clip(pointVisible, clipLine, interpolate) { } }; - function point(λ, φ) { if (pointVisible(λ, φ)) listener.point(λ, φ); } - function pointLine(λ, φ) { line.point(λ, φ); } + 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, - visibleArea, - invisibleArea, - invisible; + var segments; var buffer = d3_geo_clipBufferListener(), ringListener = clipLine(buffer), + polygonStarted = false, + polygon, ring; function pointRing(λ, φ) { - ringListener.point(λ, φ); ring.push([λ, φ]); + var point = rotate(λ, φ); + ringListener.point(point[0], point[1]); } function ringStart() { @@ -2079,26 +2343,24 @@ function d3_geo_clip(pointVisible, clipLine, interpolate) { segment, n = ringSegments.length; - // TODO compute on-the-fly? - if (!n) { - invisible = true; - invisibleArea += d3_geo_clipAreaRing(ring, -1); - ring = null; - return; - } + ring.pop(); + polygon.push(ring); ring = null; + if (!n) return; + // No intersections. - // TODO compute on-the-fly? if (clean & 1) { segment = ringSegments[0]; - visibleArea += d3_geo_clipAreaRing(segment, 1); var n = segment.length - 1, i = -1, point; - listener.lineStart(); - while (++i < n) listener.point((point = segment[i])[0], point[1]); - listener.lineEnd(); + 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; } @@ -2136,67 +2398,299 @@ function d3_geo_clipBufferListener() { }; } -// Approximate polygon ring area (×2, since we only need the sign). -// For an invisible polygon ring, we rotate longitudinally by 180°. -// The invisible parameter should be 1, or -1 to rotate longitudinally. -// Based on Robert. G. Chamberlain and William H. Duquette, -// “Some Algorithms for Polygons on a Sphere”, -// http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409 -function d3_geo_clipAreaRing(ring, invisible) { - if (!(n = ring.length)) return 0; - var n, - i = 0, - area = 0, - p = ring[0], - λ = p[0], - φ = p[1], - cosφ = Math.cos(φ), - x0 = Math.atan2(invisible * Math.sin(λ) * cosφ, Math.sin(φ)), - y0 = 1 - invisible * Math.cos(λ) * cosφ, - x1 = x0, - x, // λ'; λ rotated to south pole. - y; // φ' = 1 + sin(φ); φ rotated to south pole. - while (++i < n) { - p = ring[i]; - cosφ = Math.cos(φ = p[1]); - x = Math.atan2(invisible * Math.sin(λ = p[0]) * cosφ, Math.sin(φ)); - y = 1 - invisible * Math.cos(λ) * cosφ; +// Intersection points are sorted along the clip edge. For both antimeridian +// cutting and circle clipping, the same comparison is used. +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]); +} +// Adds floating point numbers with twice the normal precision. +// Reference: J. R. Shewchuk, Adaptive Precision Floating-Point Arithmetic and +// Fast Robust Geometric Predicates, Discrete & Computational Geometry 18(3) +// 305–363 (1997). +// Code adapted from GeographicLib by Charles F. F. Karney, +// http://geographiclib.sourceforge.net/ +// See lib/geographiclib/LICENSE for details. + +function d3_adder() {} + +d3_adder.prototype = { + s: 0, // rounded value + t: 0, // exact error + 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; + } +}; - // If both the current point and the previous point are at the north pole, - // skip this point. - if (Math.abs(y0 - 2) < ε && Math.abs(y - 2) < ε) continue; +var d3_adderTemp = new d3_adder; - // If this or the previous point is at the south pole, or if this segment - // goes through the south pole, the area is 0. - if (Math.abs(y) < ε || Math.abs(y0) < ε) {} +function d3_adderSum(a, b, o) { + var x = o.s = a + b, // a + b + bv = x - a, av = x - bv; // b_virtual & a_virtual + o.t = (a - av) + (b - bv); // a_roundoff + b_roundoff +} - // If this segment goes through either pole… - else if (Math.abs(Math.abs(x - x0) - π) < ε) { - // For the north pole, compute lune area. - if (y + y0 > 2) area += 4 * (x - x0); - // For the south pole, the area is zero. - } +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); + } +}; - // If the previous point is at the north pole, then compute lune area. - else if (Math.abs(y0 - 2) < ε) area += 4 * (x - x1); +function d3_geo_streamGeometry(geometry, listener) { + if (geometry && d3_geo_streamGeometryType.hasOwnProperty(geometry.type)) { + d3_geo_streamGeometryType[geometry.type](geometry, listener); + } +} - // Otherwise, the spherical triangle area is approximately - // δλ * (1 + sinφ0 + 1 + sinφ) / 2. - else area += ((3 * π + x - x0) % (2 * π) - π) * (y0 + y); +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); + } +}; - x1 = x0, x0 = x, y0 = y; +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); } - return area; +}; + +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(); } -// Intersection points are sorted along the clip edge. For both antimeridian -// cutting and circle clipping, the same comparison is used. -function d3_geo_clipSort(a, b) { - return ((a = a.point)[0] < 0 ? a[1] - π / 2 - ε : π / 2 - a[1]) - - ((b = b.point)[0] < 0 ? b[1] - π / 2 - ε : π / 2 - b[1]); +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, + + // Only count area for polygon rings. + 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; // start point and previous point + + // For the first point, … + 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(φ); + }; + + // For subsequent points, … + function nextPoint(λ, φ) { + λ *= d3_radians; + φ = φ * d3_radians / 2 + π / 4; // half the angular distance from south pole + + // Spherical excess E for a spherical triangle with vertices: south pole, + // previous point, current point. Uses a formula derived from Cagnoli’s + // theorem. See Todhunter, Spherical Trig. (1871), Sec. 103, Eq. (2). + 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)); + + // Advance the previous points. + λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ; + } + + // For the last point, return to the start. + d3_geo_area.lineEnd = function() { + nextPoint(λ00, φ00); + }; } +// TODO +// cross and scale return new vectors, +// whereas add and normalize operate in-place -var d3_geo_clipAntimeridian = d3_geo_clip(d3_true, d3_geo_clipAntimeridianLine, d3_geo_clipAntimeridianInterpolate); +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_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λ; + + // Are the longitudes either side of the point's meridian, and are the + // latitudes smaller than the parallel? + 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; + } + } + + // First, determine whether the South pole is inside or outside: + // + // It is inside if: + // * the polygon winds around it in a clockwise direction. + // * the polygon does not (cumulatively) wind around it, but has a negative + // (counter-clockwise) area. + // + // Second, count the (signed) number of times a segment crosses a meridian + // from the point to the South pole. If it is zero, then the point is the + // same side as the South pole. + + return (polarAngle < -ε || polarAngle < ε && d3_geo_areaRingSum < 0) ^ (winding & 1); +} + +var d3_geo_clipAntimeridian = d3_geo_clip( + d3_true, + d3_geo_clipAntimeridianLine, + d3_geo_clipAntimeridianInterpolate, + [-π, -π / 2]); // Takes a line and cuts into visible segments. Return values: // 0: there were intersections or the line was empty. @@ -2216,19 +2710,19 @@ function d3_geo_clipAntimeridianLine(listener) { }, point: function(λ1, φ1) { var sλ1 = λ1 > 0 ? π : -π, - dλ = Math.abs(λ1 - λ0); - if (Math.abs(dλ - π) < ε) { // line crosses a pole - listener.point(λ0, φ0 = (φ0 + φ1) / 2 > 0 ? π / 2 : -π / 2); + dλ = abs(λ1 - λ0); + if (abs(dλ - π) < ε) { // line crosses a pole + 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); + listener.point(λ1, φ0); clean = 0; } else if (sλ0 !== sλ1 && dλ >= π) { // line crosses antimeridian // handle degeneracies - if (Math.abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε; - if (Math.abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε; + 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(); @@ -2252,7 +2746,7 @@ function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) { var cosφ0, cosφ1, sinλ0_λ1 = Math.sin(λ0 - λ1); - return Math.abs(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)) @@ -2262,7 +2756,7 @@ function d3_geo_clipAntimeridianIntersect(λ0, φ0, λ1, φ1) { function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) { var φ; if (from == null) { - φ = direction * π / 2; + φ = direction * halfπ; listener.point(-π, φ); listener.point( 0, φ); listener.point( π, φ); @@ -2272,8 +2766,8 @@ function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) { listener.point(-π, -φ); listener.point(-π, 0); listener.point(-π, φ); - } else if (Math.abs(from[0] - to[0]) > ε) { - var s = (from[0] < to[0] ? 1 : -1) * π; + } else if (abs(from[0] - to[0]) > ε) { + var s = from[0] < to[0] ? π : -π; φ = direction * s / 2; listener.point(-s, φ); listener.point( 0, φ); @@ -2282,53 +2776,6 @@ function d3_geo_clipAntimeridianInterpolate(from, to, direction, listener) { listener.point(to[0], to[1]); } } -// TODO -// cross and scale return new vectors, -// whereas add and normalize operate in-place - -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_equirectangular(λ, φ) { return [λ, φ]; @@ -2354,17 +2801,23 @@ d3.geo.rotation = function(rotate) { return forward; }; +function d3_geo_identityRotation(λ, φ) { + return [λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ]; +} + +d3_geo_identityRotation.invert = d3_geo_equirectangular; + // Note: |δλ| must be < 2π function d3_geo_rotation(δλ, δφ, δγ) { return δλ ? (δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ)) : (δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) - : d3_geo_equirectangular); + : d3_geo_identityRotation); } function d3_geo_forwardRotationλ(δλ) { return function(λ, φ) { - return λ += δλ, [λ > π ? λ - 2 * π : λ < -π ? λ + 2 * π : λ, φ]; + return λ += δλ, [λ > π ? λ - τ : λ < -π ? λ + τ : λ, φ]; }; } @@ -2388,7 +2841,7 @@ function d3_geo_rotationφγ(δφ, δγ) { k = z * cosδφ + x * sinδφ; return [ Math.atan2(y * cosδγ - k * sinδγ, x * cosδφ - z * sinδφ), - Math.asin(Math.max(-1, Math.min(1, k * cosδγ + y * sinδγ))) + d3_asin(k * cosδγ + y * sinδγ) ]; } @@ -2400,7 +2853,7 @@ function d3_geo_rotationφγ(δφ, δγ) { k = z * cosδγ - y * sinδγ; return [ Math.atan2(y * cosδγ + z * sinδγ, x * cosδφ + k * sinδφ), - Math.asin(Math.max(-1, Math.min(1, k * cosδφ - x * sinδφ))) + d3_asin(k * cosδφ - x * sinδφ) ]; }; @@ -2455,16 +2908,16 @@ 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 * 2 * π; + if (direction > 0 ? from < to: from > to) from += direction * τ; } else { - from = radius + direction * 2 * π; - to = radius; + from = radius + direction * τ; + to = radius - .5 * step; } - var point; - for (var step = direction * precision, t = from; direction > 0 ? t > to : t < to; t -= 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), @@ -2487,10 +2940,10 @@ function d3_geo_circleAngle(cr, point) { function d3_geo_clipCircle(radius) { var cr = Math.cos(radius), smallRadius = cr > 0, - notHemisphere = Math.abs(cr) > ε, // TODO optimise for this common case + notHemisphere = abs(cr) > ε, // TODO optimise for this common case interpolate = d3_geo_circleInterpolate(radius, 6 * d3_radians); - return d3_geo_clip(visible, clipLine, interpolate); + return d3_geo_clip(visible, clipLine, interpolate, smallRadius ? [0, -radius] : [-π, radius - π]); function visible(λ, φ) { return Math.cos(λ) * Math.cos(φ) > cr; @@ -2624,7 +3077,7 @@ function d3_geo_clipCircle(radius) { z; if (λ1 < λ0) z = λ0, λ0 = λ1, λ1 = z; var δλ = λ1 - λ0, - polar = Math.abs(δλ - π) < ε, + polar = abs(δλ - π) < ε, meridian = polar || δλ < ε; if (!polar && φ1 < φ0) z = φ0, φ0 = φ1, φ1 = z; @@ -2632,7 +3085,7 @@ function d3_geo_clipCircle(radius) { // Check that the first point is between a and b. if (meridian ? polar - ? φ0 + φ1 > 0 ^ q[1] < (Math.abs(q[0] - λ0) < ε ? φ0 : φ1) + ? φ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); @@ -2654,12 +3107,99 @@ function d3_geo_clipCircle(radius) { } } -var d3_geo_clipViewMAX = 1e9; +// Liang–Barsky line clipping. +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; // allow caching by d3.geo.path + 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_clipView(x0, y0, x1, y1) { +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; @@ -2672,40 +3212,42 @@ function d3_geo_clipView(x0, y0, x1, y1) { listener = bufferListener; segments = []; polygon = []; + clean = true; }, polygonEnd: function() { listener = listener_; - if ((segments = d3.merge(segments)).length) { + segments = d3.merge(segments); + var clipStartInside = insidePolygon([x0, y1]), + inside = clean && clipStartInside, + visible = segments.length; + if (inside || visible) { listener.polygonStart(); - d3_geo_clipPolygon(segments, compare, inside, interpolate, listener); + if (inside) { + listener.lineStart(); + interpolate(null, null, 1, listener); + listener.lineEnd(); + } + if (visible) { + d3_geo_clipPolygon(segments, compare, clipStartInside, interpolate, listener); + } listener.polygonEnd(); - } else if (insidePolygon([x0, y0])) { - listener.polygonStart(), listener.lineStart(); - interpolate(null, null, 1, listener); - listener.lineEnd(), listener.polygonEnd(); } segments = polygon = ring = null; } }; - function inside(point) { - var a = corner(point, -1), - i = insidePolygon([a === 0 || a === 3 ? x0 : x1, a > 1 ? y1 : y0]); - return i; - } - function insidePolygon(p) { var wn = 0, // the winding number counter 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]; j < m; ++j) { + 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 && isLeft(a, b, p) > 0) ++wn; + if (b[1] > y && d3_cross2d(a, b, p) > 0) ++wn; } else { - if (b[1] <= y && isLeft(a, b, p) < 0) --wn; + if (b[1] <= y && d3_cross2d(a, b, p) < 0) --wn; } a = b; } @@ -2713,10 +3255,6 @@ function d3_geo_clipView(x0, y0, x1, y1) { return wn !== 0; } - function isLeft(a, b, c) { - return (b[0] - a[0]) * (c[1] - a[1]) - (c[0] - a[0]) * (b[1] - a[1]); - } - function interpolate(from, to, direction, listener) { var a = 0, a1 = 0; if (from == null || @@ -2730,17 +3268,18 @@ function d3_geo_clipView(x0, y0, x1, y1) { } } - function visible(x, y) { + function pointVisible(x, y) { return x0 <= x && x <= x1 && y0 <= y && y <= y1; } function point(x, y) { - if (visible(x, y)) listener.point(x, y); + if (pointVisible(x, y)) listener.point(x, y); } var x__, y__, v__, // first point x_, y_, v_, // previous point - first; + first, + clean; function lineStart() { clip.point = linePoint; @@ -2764,9 +3303,9 @@ function d3_geo_clipView(x0, y0, x1, y1) { } function linePoint(x, y) { - x = Math.max(-d3_geo_clipViewMAX, Math.min(d3_geo_clipViewMAX, x)); - y = Math.max(-d3_geo_clipViewMAX, Math.min(d3_geo_clipViewMAX, y)); - var v = visible(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; @@ -2778,18 +3317,19 @@ function d3_geo_clipView(x0, y0, x1, y1) { } else { if (v && v_) listener.point(x, y); else { - var a = [x_, y_], - b = [x, y]; - if (clipLine(a, b)) { + var l = {a: {x: x_, y: y_}, b: {x: x, y: y}}; + if (clipLine(l)) { if (!v_) { listener.lineStart(); - listener.point(a[0], a[1]); + listener.point(l.a.x, l.a.y); } - listener.point(b[0], b[1]); + listener.point(l.b.x, l.b.y); if (!v) listener.lineEnd(); - } else { + clean = false; + } else if (v) { listener.lineStart(); listener.point(x, y); + clean = false; } } } @@ -2800,14 +3340,14 @@ function d3_geo_clipView(x0, y0, x1, y1) { }; function corner(p, direction) { - return Math.abs(p[0] - x0) < ε ? direction > 0 ? 0 : 3 - : Math.abs(p[0] - x1) < ε ? direction > 0 ? 2 : 1 - : Math.abs(p[1] - y0) < ε ? direction > 0 ? 1 : 0 - : direction > 0 ? 3 : 2; // Math.abs(p[1] - y1) < ε + 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; // abs(p[1] - y1) < ε } function compare(a, b) { - return comparePoints(a.point, b.point); + return comparePoints(a.x, b.x); } function comparePoints(a, b) { @@ -2819,47 +3359,6 @@ function d3_geo_clipView(x0, y0, x1, y1) { : ca === 2 ? a[1] - b[1] : b[0] - a[0]; } - - // Liang–Barsky line clipping. - function clipLine(a, b) { - var dx = b[0] - a[0], - dy = b[1] - a[1], - t = [0, 1]; - - if (Math.abs(dx) < ε && Math.abs(dy) < ε) return x0 <= a[0] && a[0] <= x1 && y0 <= a[1] && a[1] <= y1; - - if (d3_geo_clipViewT(x0 - a[0], dx, t) && - d3_geo_clipViewT(a[0] - x1, -dx, t) && - d3_geo_clipViewT(y0 - a[1], dy, t) && - d3_geo_clipViewT(a[1] - y1, -dy, t)) { - if (t[1] < 1) { - b[0] = a[0] + t[1] * dx; - b[1] = a[1] + t[1] * dy; - } - if (t[0] > 0) { - a[0] += t[0] * dx; - a[1] += t[0] * dy; - } - return true; - } - - return false; - } -} - -function d3_geo_clipViewT(num, denominator, t) { - if (Math.abs(denominator) < ε) return num <= 0; - - var u = num / denominator; - - if (denominator > 0) { - if (u > t[1]) return false; - if (u > t[0]) t[0] = u; - } else { - if (u < t[0]) return false; - if (u < t[1]) t[1] = u; - } - return true; } function d3_geo_compose(a, b) { @@ -2874,320 +3373,6 @@ function d3_geo_compose(a, b) { return compose; } -d3.geo.stream = function(object, listener) { - if (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 (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) { - var coordinate = object.coordinates; - listener.point(coordinate[0], coordinate[1]); - }, - MultiPoint: function(object, listener) { - var coordinates = object.coordinates, i = -1, n = coordinates.length, coordinate; - while (++i < n) coordinate = coordinates[i], listener.point(coordinate[0], coordinate[1]); - }, - 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]); - 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(); -} - -function d3_geo_resample(project) { - var δ2 = .5, // precision, px² - maxDepth = 16; - - function resample(stream) { - var λ0, x0, y0, a0, b0, c0; // previous point - - var resample = { - point: point, - lineStart: lineStart, - lineEnd: lineEnd, - polygonStart: function() { stream.polygonStart(); resample.lineStart = polygonLineStart; }, - 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 polygonLineStart() { - var λ00, φ00, x00, y00, a00, b00, c00; // first point - - lineStart(); - - resample.point = function(λ, φ) { - linePoint(λ00 = λ, φ00 = φ), x00 = x0, y00 = y0, a00 = a0, b00 = b0, c00 = c0; - resample.point = linePoint; - }; - - resample.lineEnd = function() { - 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 = Math.abs(Math.abs(c) - 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 || Math.abs((dx * dx2 + dy * dy2) / d2 - .5) > .3) { - 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.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, // scale - x = 480, y = 250, // translate - λ = 0, φ = 0, // center - δλ = 0, δφ = 0, δγ = 0, // rotate - δx, δy, // center - preclip = d3_geo_clipAntimeridian, - postclip = d3_identity, - clipAngle = null, - clipExtent = null; - - 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(stream) { - return d3_geo_projectionRadiansRotate(rotate, preclip(projectResample(postclip(stream)))); - }; - - projection.clipAngle = function(_) { - if (!arguments.length) return clipAngle; - preclip = _ == null ? (clipAngle = _, d3_geo_clipAntimeridian) : d3_geo_clipCircle((clipAngle = +_) * d3_radians); - return projection; - }; - - projection.clipExtent = function(_) { - if (!arguments.length) return clipExtent; - clipExtent = _; - postclip = _ == null ? d3_identity : d3_geo_clipView(_[0][0], _[0][1], _[1][0], _[1][1]); - return projection; - }; - - 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 projection; - } - - return function() { - project = projectAt.apply(this, arguments); - projection.invert = project.invert && invert; - return reset(); - }; -} - -function d3_geo_projectionRadiansRotate(rotate, stream) { - return { - point: function(x, y) { - y = rotate(x * d3_radians, y * d3_radians), x = y[0]; - stream.point(x > π ? x - 2 * π : x < -π ? x + 2 * π : x, y[1]); - }, - sphere: function() { stream.sphere(); }, - lineStart: function() { stream.lineStart(); }, - lineEnd: function() { stream.lineEnd(); }, - polygonStart: function() { stream.polygonStart(); }, - polygonEnd: function() { stream.polygonEnd(); } - }; -} - -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)) - π / 2]; -}; - -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; - function d3_geo_conic(projectAt) { var φ0 = 0, φ1 = π / 3, @@ -3220,7 +3405,7 @@ function d3_geo_conicEqualArea(φ0, φ1) { var ρ0_y = ρ0 - y; return [ Math.atan2(x, ρ0_y) / n, - Math.asin((C - (x * x + ρ0_y * ρ0_y) * n * n) / (2 * n)) + d3_asin((C - (x * x + ρ0_y * ρ0_y) * n * n) / (2 * n)) ]; }; @@ -3231,253 +3416,346 @@ function d3_geo_conicEqualArea(φ0, φ1) { return d3_geo_conic(d3_geo_conicEqualArea); }).raw = d3_geo_conicEqualArea; -// A composite projection for the United States, 960×500. The set of standard -// parallels for each region comes from USGS, which is published here: +// ESRI:102003 +d3.geo.albers = function() { + return d3.geo.conicEqualArea() + .rotate([96, 0]) + .center([-.6, 38.7]) + .parallels([29.5, 45.5]) + .scale(1070); +}; + +// A composite projection for the United States, configured by default for +// 960×500. Also works quite well at 960×600 with scale 1285. The set of +// standard parallels for each region comes from USGS, which is published here: // http://egsc.usgs.gov/isb/pubs/MapProjections/projections.html#albers d3.geo.albersUsa = function() { - var lower48 = d3.geo.conicEqualArea() - .rotate([98, 0]) - .center([0, 38]) - .parallels([29.5, 45.5]); + var lower48 = d3.geo.albers(); + // EPSG:3338 var alaska = d3.geo.conicEqualArea() - .rotate([160, 0]) - .center([0, 60]) + .rotate([154, 0]) + .center([-2, 58.5]) .parallels([55, 65]); + // ESRI:102007 var hawaii = d3.geo.conicEqualArea() - .rotate([160, 0]) - .center([0, 20]) - .parallels([8, 18]); - - var puertoRico = d3.geo.conicEqualArea() - .rotate([60, 0]) - .center([0, 10]) + .rotate([157, 0]) + .center([-3, 19.9]) .parallels([8, 18]); - var alaskaInvert, - hawaiiInvert, - puertoRicoInvert; + var point, + pointStream = {point: function(x, y) { point = [x, y]; }}, + lower48Point, + alaskaPoint, + hawaiiPoint; function albersUsa(coordinates) { - return projection(coordinates)(coordinates); - } - - function projection(point) { - var lon = point[0], - lat = point[1]; - return lat > 50 ? alaska - : lon < -140 ? hawaii - : lat < 21 ? puertoRico - : lower48; + 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) { - return alaskaInvert(coordinates) || hawaiiInvert(coordinates) || puertoRicoInvert(coordinates) || lower48.invert(coordinates); + var k = lower48.scale(), + t = lower48.translate(), + x = (coordinates[0] - t[0]) / k, + y = (coordinates[1] - t[1]) / k; + return (y >= .120 && y < .234 && x >= -.425 && x < -.214 ? alaska + : y >= .166 && y < .234 && x >= -.214 && x < -.115 ? hawaii + : lower48).invert(coordinates); + }; + + // A naïve multi-projection stream. + // The projections must have mutually exclusive clip regions on the sphere, + // as this will avoid emitting interleaving lines and polygons. + 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.scale = function(x) { + 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(x); - alaska.scale(x * .6); - hawaii.scale(x); - puertoRico.scale(x * 1.5); + lower48.scale(_); + alaska.scale(_ * .35); + hawaii.scale(_); return albersUsa.translate(lower48.translate()); }; - albersUsa.translate = function(x) { + albersUsa.translate = function(_) { if (!arguments.length) return lower48.translate(); - var dz = lower48.scale(), - dx = x[0], - dy = x[1]; - lower48.translate(x); - alaska.translate([dx - .40 * dz, dy + .17 * dz]); - hawaii.translate([dx - .19 * dz, dy + .20 * dz]); - puertoRico.translate([dx + .58 * dz, dy + .43 * dz]); - - alaskaInvert = d3_geo_albersUsaInvert(alaska, [[-180, 50], [-130, 72]]); - hawaiiInvert = d3_geo_albersUsaInvert(hawaii, [[-164, 18], [-154, 24]]); - puertoRicoInvert = d3_geo_albersUsaInvert(puertoRico, [[-67.5, 17.5], [-65, 19]]); + var k = lower48.scale(), x = +_[0], y = +_[1]; - return albersUsa; - }; + lower48Point = lower48 + .translate(_) + .clipExtent([[x - .455 * k, y - .238 * k], [x + .455 * k, y + .238 * k]]) + .stream(pointStream).point; - return albersUsa.scale(1000); -}; + alaskaPoint = alaska + .translate([x - .307 * k, y + .201 * k]) + .clipExtent([[x - .425 * k + ε, y + .120 * k + ε], [x - .214 * k - ε, y + .234 * k - ε]]) + .stream(pointStream).point; -function d3_geo_albersUsaInvert(projection, extent) { - var a = projection(extent[0]), - b = projection([.5 * (extent[0][0] + extent[1][0]), extent[0][1]]), - c = projection([extent[1][0], extent[0][1]]), - d = projection(extent[1]); - - var dya = b[1]- a[1], - dxa = b[0]- a[0], - dyb = c[1]- b[1], - dxb = c[0]- b[0]; - - var ma = dya / dxa, - mb = dyb / dxb; - - // Find center of circle going through points [a, b, c]. - var cx = .5 * (ma * mb * (a[1] - c[1]) + mb * (a[0] + b[0]) - ma * (b[0] + c[0])) / (mb - ma), - cy = (.5 * (a[0] + b[0]) - cx) / ma + .5 * (a[1] + b[1]); - - // Radial distance² from center. - var dx0 = d[0] - cx, - dy0 = d[1] - cy, - dx1 = a[0] - cx, - dy1 = a[1] - cy, - r0 = dx0 * dx0 + dy0 * dy0, - r1 = dx1 * dx1 + dy1 * dy1; - - // Angular extent. - var a0 = Math.atan2(dy0, dx0), - a1 = Math.atan2(dy1, dx1); - - return function(coordinates) { - var dx = coordinates[0] - cx, - dy = coordinates[1] - cy, - r = dx * dx + dy * dy, - a = Math.atan2(dy, dx); - if (r0 < r && r < r1 && a0 < a && a < a1) return projection.invert(coordinates); + 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; }; -} -d3.geo.area = function(object) { - d3_geo_areaSum = 0; - d3.geo.stream(object, d3_geo_area); - return d3_geo_areaSum; + return albersUsa.scale(1070); }; -var d3_geo_areaSum, - d3_geo_areaRingU, - d3_geo_areaRingV; - -var d3_geo_area = { - sphere: function() { d3_geo_areaSum += 4 * π; }, - point: d3_noop, - lineStart: d3_noop, - lineEnd: d3_noop, - - // Only count area for polygon rings. - polygonStart: function() { - d3_geo_areaRingU = 1, d3_geo_areaRingV = 0; - d3_geo_area.lineStart = d3_geo_areaRingStart; - }, - polygonEnd: function() { - var area = 2 * Math.atan2(d3_geo_areaRingV, d3_geo_areaRingU); - d3_geo_areaSum += area < 0 ? 4 * π + area : area; - d3_geo_area.lineStart = d3_geo_area.lineEnd = d3_geo_area.point = d3_noop; - } -}; +d3.geo.bounds = (function() { + var λ0, φ0, λ1, φ1, // bounds + λ_, // previous λ-coordinate + λ__, φ__, // first point + p0, // previous 3D point + dλSum, + ranges, + range; -function d3_geo_areaRingStart() { - var λ00, φ00, λ0, cosφ0, sinφ0; // start point and two previous points + var bound = { + point: point, + lineStart: lineStart, + lineEnd: lineEnd, - // For the first point, … - 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(φ); + 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; + } }; - // For subsequent points, … - function nextPoint(λ, φ) { - λ *= d3_radians; - φ = φ * d3_radians / 2 + π / 4; // half the angular distance from south pole - - // Spherical excess E for a spherical triangle with vertices: south pole, - // previous point, current point. Uses a formula derived from Cagnoli’s - // theorem. See Todhunter, Spherical Trig. (1871), Sec. 103, Eq. (2). - var dλ = λ - λ0, - cosφ = Math.cos(φ), - sinφ = Math.sin(φ), - k = sinφ0 * sinφ, - u0 = d3_geo_areaRingU, - v0 = d3_geo_areaRingV, - u = cosφ0 * cosφ + k * Math.cos(dλ), - v = k * Math.sin(dλ); - // ∑ arg(z) = arg(∏ z), where z = u + iv. - d3_geo_areaRingU = u0 * u - v0 * v; - d3_geo_areaRingV = v0 * u + u0 * v; + function point(λ, φ) { + ranges.push(range = [λ0 = λ, λ1 = λ]); + if (φ < φ0) φ0 = φ; + if (φ > φ1) φ1 = φ; + } - // Advance the previous points. - λ0 = λ, cosφ0 = cosφ, sinφ0 = sinφ; + 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, λ_ = λ; } - // For the last point, return to the start. - d3_geo_area.lineEnd = function() { - nextPoint(λ00, φ00); - }; -} + function lineStart() { bound.point = linePoint; } + function lineEnd() { + range[0] = λ0, range[1] = λ1; + bound.point = point; + p0 = null; + } -d3.geo.bounds = d3_geo_bounds(d3_identity); + function ringPoint(λ, φ) { + if (p0) { + var dλ = λ - λ_; + dλSum += abs(dλ) > 180 ? dλ + (dλ > 0 ? 360 : -360) : dλ; + } else λ__ = λ, φ__ = φ; + d3_geo_area.point(λ, φ); + linePoint(λ, φ); + } -function d3_geo_bounds(projectStream) { - var x0, y0, x1, y1; + function ringStart() { + d3_geo_area.lineStart(); + } - var bound = { - point: boundPoint, - lineStart: d3_noop, - lineEnd: d3_noop, + function ringEnd() { + ringPoint(λ__, φ__); + d3_geo_area.lineEnd(); + if (abs(dλSum) > ε) λ0 = -(λ1 = 180); + range[0] = λ0, range[1] = λ1; + p0 = null; + } - // While inside a polygon, ignore points in holes. - polygonStart: function() { bound.lineEnd = boundPolygonLineEnd; }, - polygonEnd: function() { bound.point = boundPoint; } - }; + // Finds the left-right distance between two longitudes. + // This is almost the same as (λ1 - λ0 + 360°) % 360°, except that we want + // the distance between ±180° to be 360°. + function angle(λ0, λ1) { return (λ1 -= λ0) < 0 ? λ1 + 360 : λ1; } - function boundPoint(x, y) { - if (x < x0) x0 = x; - if (x > x1) x1 = x; - if (y < y0) y0 = y; - if (y > y1) y1 = y; - } + function compareRanges(a, b) { return a[0] - b[0]; } - function boundPolygonLineEnd() { - bound.point = bound.lineEnd = d3_noop; + function withinRange(x, range) { + return range[0] <= range[1] ? range[0] <= x && x <= range[1] : x < range[0] || range[1] < x; } return function(feature) { - y1 = x1 = -(x0 = y0 = Infinity); - d3.geo.stream(feature, projectStream(bound)); - return [[x0, y0], [x1, y1]]; + φ1 = λ1 = -(λ0 = φ0 = Infinity); + ranges = []; + + d3.geo.stream(feature, bound); + + var n = ranges.length; + if (n) { + // First, sort ranges by their minimum longitudes. + ranges.sort(compareRanges); + + // Then, merge any ranges that overlap. + 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); + } + } + + // Finally, find the largest gap between the merged ranges. + // The final bounding box will be the inverse of this gap. + 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_centroidDimension = d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + 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 m; - if (d3_geo_centroidW && - Math.abs(m = Math.sqrt(d3_geo_centroidX * d3_geo_centroidX + d3_geo_centroidY * d3_geo_centroidY + d3_geo_centroidZ * d3_geo_centroidZ)) > ε) { - return [ - Math.atan2(d3_geo_centroidY, d3_geo_centroidX) * d3_degrees, - Math.asin(Math.max(-1, Math.min(1, d3_geo_centroidZ / m))) * d3_degrees - ]; + + var x = d3_geo_centroidX2, + y = d3_geo_centroidY2, + z = d3_geo_centroidZ2, + m = x * x + y * y + z * z; + + // If the area-weighted centroid is undefined, fall back to length-weighted centroid. + if (m < ε2) { + x = d3_geo_centroidX1, y = d3_geo_centroidY1, z = d3_geo_centroidZ1; + // If the feature has zero length, fall back to arithmetic mean of point vectors. + if (d3_geo_centroidW1 < ε) x = d3_geo_centroidX0, y = d3_geo_centroidY0, z = d3_geo_centroidZ0; + m = x * x + y * y + z * z; + // If the feature still has an undefined centroid, then return. + if (m < ε2) return [NaN, NaN]; } + + return [Math.atan2(y, x) * d3_degrees, d3_asin(z / Math.sqrt(m)) * d3_degrees]; }; -var d3_geo_centroidDimension, - d3_geo_centroidW, - d3_geo_centroidX, - d3_geo_centroidY, - d3_geo_centroidZ; +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: function() { - if (d3_geo_centroidDimension < 2) { - d3_geo_centroidDimension = 2; - d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; - } - }, + sphere: d3_noop, point: d3_geo_centroidPoint, lineStart: d3_geo_centroidLineStart, lineEnd: d3_geo_centroidLineEnd, polygonStart: function() { - if (d3_geo_centroidDimension < 2) { - d3_geo_centroidDimension = 2; - d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; - } d3_geo_centroid.lineStart = d3_geo_centroidRingStart; }, polygonEnd: function() { @@ -3487,42 +3765,21 @@ var d3_geo_centroid = { // Arithmetic mean of Cartesian vectors. function d3_geo_centroidPoint(λ, φ) { - if (d3_geo_centroidDimension) return; - ++d3_geo_centroidW; λ *= d3_radians; var cosφ = Math.cos(φ *= d3_radians); - d3_geo_centroidX += (cosφ * Math.cos(λ) - d3_geo_centroidX) / d3_geo_centroidW; - d3_geo_centroidY += (cosφ * Math.sin(λ) - d3_geo_centroidY) / d3_geo_centroidW; - d3_geo_centroidZ += (Math.sin(φ) - d3_geo_centroidZ) / d3_geo_centroidW; + d3_geo_centroidPointXYZ(cosφ * Math.cos(λ), cosφ * Math.sin(λ), Math.sin(φ)); } -function d3_geo_centroidRingStart() { - var λ00, φ00; // first point - - d3_geo_centroidDimension = 1; - d3_geo_centroidLineStart(); - d3_geo_centroidDimension = 2; - - var linePoint = d3_geo_centroid.point; - d3_geo_centroid.point = function(λ, φ) { - linePoint(λ00 = λ, φ00 = φ); - }; - d3_geo_centroid.lineEnd = function() { - d3_geo_centroid.point(λ00, φ00); - d3_geo_centroidLineEnd(); - d3_geo_centroid.lineEnd = d3_geo_centroidLineEnd; - }; +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; // previous point - if (d3_geo_centroidDimension > 1) return; - if (d3_geo_centroidDimension < 1) { - d3_geo_centroidDimension = 1; - d3_geo_centroidW = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; - } - d3_geo_centroid.point = function(λ, φ) { λ *= d3_radians; var cosφ = Math.cos(φ *= d3_radians); @@ -3530,6 +3787,7 @@ function d3_geo_centroidLineStart() { y0 = cosφ * Math.sin(λ); z0 = Math.sin(φ); d3_geo_centroid.point = nextPoint; + d3_geo_centroidPointXYZ(x0, y0, z0); }; function nextPoint(λ, φ) { @@ -3541,10 +3799,11 @@ function d3_geo_centroidLineStart() { 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_centroidW += w; - d3_geo_centroidX += w * (x0 + (x0 = x)); - d3_geo_centroidY += w * (y0 + (y0 = y)); - d3_geo_centroidZ += w * (z0 + (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); } } @@ -3552,6 +3811,53 @@ function d3_geo_centroidLineEnd() { d3_geo_centroid.point = d3_geo_centroidPoint; } +// See J. E. Brock, The Inertia Tensor for a Spherical Triangle, +// J. Applied Mechanics 42, 239 (1975). +function d3_geo_centroidRingStart() { + var λ00, φ00, // first point + x0, y0, z0; // previous point + + 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, // area weight + w = Math.atan2(m, u); // line weight + 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); + } +} + // TODO Unify this code with d3.geom.polygon area? var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = { @@ -3566,7 +3872,7 @@ var d3_geo_pathAreaSum, d3_geo_pathAreaPolygon, d3_geo_pathArea = { }, polygonEnd: function() { d3_geo_pathArea.lineStart = d3_geo_pathArea.lineEnd = d3_geo_pathArea.point = d3_noop; - d3_geo_pathAreaSum += Math.abs(d3_geo_pathAreaPolygon / 2); + d3_geo_pathAreaSum += abs(d3_geo_pathAreaPolygon / 2); } }; @@ -3590,8 +3896,28 @@ function d3_geo_pathAreaRingStart() { 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_pathCircle(4.5), + var pointCircle = d3_geo_pathBufferCircle(4.5), buffer = []; var stream = { @@ -3606,7 +3932,7 @@ function d3_geo_pathBuffer() { polygonEnd: function() { stream.lineEnd = lineEnd; stream.point = point; }, pointRadius: function(_) { - pointCircle = d3_geo_pathCircle(_); + pointCircle = d3_geo_pathBufferCircle(_); return stream; }, @@ -3643,6 +3969,13 @@ function d3_geo_pathBuffer() { 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"; +} + // TODO Unify this code with d3.geom.polygon centroid? // TODO Enforce positive area for exterior, negative area for interior? @@ -3665,33 +3998,25 @@ var d3_geo_pathCentroid = { }; function d3_geo_pathCentroidPoint(x, y) { - if (d3_geo_centroidDimension) return; - d3_geo_centroidX += x; - d3_geo_centroidY += y; - ++d3_geo_centroidZ; + d3_geo_centroidX0 += x; + d3_geo_centroidY0 += y; + ++d3_geo_centroidZ0; } function d3_geo_pathCentroidLineStart() { var x0, y0; - if (d3_geo_centroidDimension !== 1) { - if (d3_geo_centroidDimension < 1) { - d3_geo_centroidDimension = 1; - d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; - } else return; - } - d3_geo_pathCentroid.point = function(x, y) { d3_geo_pathCentroid.point = nextPoint; - x0 = x, y0 = y; + 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_centroidX += z * (x0 + x) / 2; - d3_geo_centroidY += z * (y0 + y) / 2; - d3_geo_centroidZ += z; - x0 = x, y0 = y; + 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); } } @@ -3702,24 +4027,24 @@ function d3_geo_pathCentroidLineEnd() { function d3_geo_pathCentroidRingStart() { var x00, y00, x0, y0; - if (d3_geo_centroidDimension < 2) { - d3_geo_centroidDimension = 2; - d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; - } - // For the first point, … d3_geo_pathCentroid.point = function(x, y) { d3_geo_pathCentroid.point = nextPoint; - x00 = x0 = x, y00 = y0 = y; + d3_geo_pathCentroidPoint(x00 = x0 = x, y00 = y0 = y); }; // For subsequent points, … function nextPoint(x, y) { - var z = y0 * x - x0 * y; - d3_geo_centroidX += z * (x0 + x); - d3_geo_centroidY += z * (y0 + y); - d3_geo_centroidZ += z * 3; - x0 = x, y0 = 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); } // For the last point, return to the start. @@ -3752,7 +4077,7 @@ function d3_geo_pathContext(context) { function point(x, y) { context.moveTo(x, y); - context.arc(x, y, pointRadius, 0, 2 * π); + context.arc(x, y, pointRadius, 0, τ); } function pointLineStart(x, y) { @@ -3775,148 +4100,455 @@ function d3_geo_pathContext(context) { return stream; } -d3.geo.path = function() { - var pointRadius = 4.5, - projection, - context, - projectStream, - contextStream; +function d3_geo_resample(project) { + var δ2 = .5, // precision, px² + cosMinDistance = Math.cos(30 * d3_radians), // cos(minimum angular distance) + maxDepth = 16; - function path(object) { - if (object) d3.geo.stream(object, projectStream( - contextStream.pointRadius(typeof pointRadius === "function" - ? +pointRadius.apply(this, arguments) - : pointRadius))); - return contextStream.result(); + function resample(stream) { + return (maxDepth ? resampleRecursive : resampleNone)(stream); } - path.area = function(object) { - d3_geo_pathAreaSum = 0; - d3.geo.stream(object, projectStream(d3_geo_pathArea)); - return d3_geo_pathAreaSum; - }; + 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, // first point + λ0, x0, y0, a0, b0, c0; // previous point + + 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 // perpendicular projected distance + || abs((dx * dx2 + dy * dy2) / d2 - .5) > .3 // midpoint close to an end + || a0 * a1 + b0 * b1 + c0 * c1 < cosMinDistance) { // angular distance + 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_centroidDimension = d3_geo_centroidX = d3_geo_centroidY = d3_geo_centroidZ = 0; + 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_centroidZ ? [d3_geo_centroidX / d3_geo_centroidZ, d3_geo_centroidY / d3_geo_centroidZ] : undefined; + 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) { - return d3_geo_bounds(projectStream)(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 path; + return reset(); }; path.context = function(_) { if (!arguments.length) return context; contextStream = (context = _) == null ? new d3_geo_pathBuffer : new d3_geo_pathContext(_); - return path; + if (typeof pointRadius !== "function") contextStream.pointRadius(pointRadius); + return reset(); }; path.pointRadius = function(_) { if (!arguments.length) return pointRadius; - pointRadius = typeof _ === "function" ? _ : +_; + pointRadius = typeof _ === "function" ? _ : (contextStream.pointRadius(+_), +_); return path; }; + function reset() { + cacheStream = null; + return path; + } + return path.projection(d3.geo.albersUsa()).context(null); }; -function d3_geo_pathCircle(radius) { - return "m0," + radius - + "a" + radius + "," + radius + " 0 1,1 0," + (-2 * radius) - + "a" + radius + "," + radius + " 0 1,1 0," + (+2 * radius) - + "z"; +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)); }; } -function d3_geo_pathProjectStream(project) { - var resample = d3_geo_resample(function(λ, φ) { return project([λ * d3_degrees, φ * d3_degrees]); }); - return function(stream) { - stream = resample(stream); - return { - point: function(λ, φ) { stream.point(λ * d3_radians, φ * d3_radians); }, - sphere: function() { stream.sphere(); }, - lineStart: function() { stream.lineStart(); }, - lineEnd: function() { stream.lineEnd(); }, - polygonStart: function() { stream.polygonStart(); }, - polygonEnd: function() { stream.polygonEnd(); } - }; +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.geom = {}; -d3.geom.polygon = function(coordinates) { +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(); } +}; - coordinates.area = function() { - var i = 0, - n = coordinates.length, - area = coordinates[n - 1][1] * coordinates[0][0] - coordinates[n - 1][0] * coordinates[0][1]; - while (++i < n) { - area += coordinates[i - 1][1] * coordinates[i][0] - coordinates[i - 1][0] * coordinates[i][1]; - } - return area * .5; +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(); }, }; +} - coordinates.centroid = function(k) { - var i = -1, - n = coordinates.length, - x = 0, - y = 0, - a, - b = coordinates[n - 1], - c; - if (!arguments.length) k = -1 / (6 * coordinates.area()); - while (++i < n) { - a = b; - b = coordinates[i]; - c = a[0] * b[1] - b[0] * a[1]; - x += (a[0] + b[0]) * c; - y += (a[1] + b[1]) * c; +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, // scale + x = 480, y = 250, // translate + λ = 0, φ = 0, // center + δλ = 0, δφ = 0, δγ = 0, // rotate + δx, δy, // center + 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; // allow caching by d3.geo.path + 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_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 [x * k, y * k]; + return v; }; - // The Sutherland-Hodgman clipping algorithm. - // Note: requires the clip polygon to be counterclockwise and convex. - coordinates.clip = function(subject) { - var input, - i = -1, - n = coordinates.length, - j, - m, - a = coordinates[n - 1], - b, - c, - d; - while (++i < n) { - input = subject.slice(); - subject.length = 0; - b = coordinates[i]; - c = input[(m = input.length) - 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)) { + return m.clipExtent(null); +} + +(d3.geo.mercator = function() { + return d3_geo_mercatorProjection(d3_geo_mercator); +}).raw = d3_geo_mercator; +d3.geom = {}; + +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]; +}; + +// The Sutherland-Hodgman clipping algorithm. +// Note: requires the clip polygon to be counterclockwise and convex. +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)); } - c = d; + subject.push(d); + } else if (d3_geom_polygonInside(c, a, b)) { + subject.push(d3_geom_polygonIntersect(c, d, a, b)); } - a = b; + c = d; } - return subject; - }; + if (closed) subject.push(subject[0]); + a = b; + } - return coordinates; + return subject; }; function d3_geom_polygonInside(p, a, b) { @@ -3931,6 +4563,138 @@ function d3_geom_polygonIntersect(c, d, a, b) { return [x1 + ua * x21, y1 + ua * y21]; } +// Returns true if the polygon is closed. +function d3_geom_polygonClosed(coordinates) { + var a = coordinates[0], + b = coordinates[coordinates.length - 1]; + return !(a[0] - b[0] || a[1] - b[1]); +} +function d3_geom_pointX(d) { + return d[0]; +} + +function d3_geom_pointY(d) { + return d[1]; +} + +/** + * Computes the 2D convex hull of a set of points using Graham's scanning + * algorithm. The algorithm has been implemented as described in Cormen, + * Leiserson, and Rivest's Introduction to Algorithms. The running time of + * this algorithm is O(n log n), where n is the number of input points. + * + * @param vertices [[x1, y1], [x2, y2], …] + * @returns polygon [[x1, y1], [x2, y2], …] + */ +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), + n = data.length, + vertices, // TODO use parallel arrays + plen = n - 1, + points = [], + stack = [], + d, + i, j, h = 0, x1, y1, x2, y2, u, v, a, sp; + + if (fx === d3_geom_pointX && y === d3_geom_pointY) vertices = data; + else for (i = 0, vertices = []; i < n; ++i) { + vertices.push([+fx.call(this, d = data[i], i), +fy.call(this, d, i)]); + } + + // find the starting ref point: leftmost point with the minimum y coord + for (i = 1; i < n; ++i) { + if (vertices[i][1] < vertices[h][1] + || vertices[i][1] == vertices[h][1] + && vertices[i][0] < vertices[h][0]) h = i; + } + + // calculate polar angles from ref point and sort + for (i = 0; i < n; ++i) { + if (i === h) continue; + y1 = vertices[i][1] - vertices[h][1]; + x1 = vertices[i][0] - vertices[h][0]; + points.push({angle: Math.atan2(y1, x1), index: i}); + } + points.sort(function(a, b) { return a.angle - b.angle; }); + + // toss out duplicate angles + a = points[0].angle; + v = points[0].index; + u = 0; + for (i = 1; i < plen; ++i) { + j = points[i].index; + if (a == points[i].angle) { + // keep angle for point most distant from the reference + x1 = vertices[v][0] - vertices[h][0]; + y1 = vertices[v][1] - vertices[h][1]; + x2 = vertices[j][0] - vertices[h][0]; + y2 = vertices[j][1] - vertices[h][1]; + if (x1 * x1 + y1 * y1 >= x2 * x2 + y2 * y2) { + points[i].index = -1; + continue; + } else { + points[u].index = -1; + } + } + a = points[i].angle; + u = i; + v = j; + } + + // initialize the stack + stack.push(h); + for (i = 0, j = 0; i < 2; ++j) { + if (points[j].index > -1) { + stack.push(points[j].index); + i++; + } + } + sp = stack.length; + + // do graham's scan + for (; j < plen; ++j) { + if (points[j].index < 0) continue; // skip tossed out points + while (!d3_geom_hullCCW(stack[sp - 2], stack[sp - 1], points[j].index, vertices)) { + --sp; + } + stack[sp++] = points[j].index; + } + + // construct the hull + var poly = []; + for (i = sp - 1; i >= 0; --i) poly.push(data[stack[i]]); + return poly; + } + + hull.x = function(_) { + return arguments.length ? (x = _, hull) : x; + }; + + hull.y = function(_) { + return arguments.length ? (y = _, hull) : y; + }; + + return hull; +}; + +// are three points in counter-clockwise order? +function d3_geom_hullCCW(i1, i2, i3, v) { + var t, a, b, c, d, e, f; + t = v[i1]; a = t[0]; b = t[1]; + t = v[i2]; c = t[0]; d = t[1]; + t = v[i3]; e = t[0]; f = t[1]; + return (f - b) * (c - a) - (d - b) * (e - a) > 0; +} + var d3_ease_default = function() { return d3_identity; }; var d3_ease = d3.map({ @@ -3959,7 +4723,7 @@ d3.ease = function(name) { m = i >= 0 ? name.substring(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, Array.prototype.slice.call(arguments, 1)))); + return d3_ease_clamp(m(t.apply(null, d3_arraySlice.call(arguments, 1)))); }; function d3_ease_clamp(f) { @@ -4003,7 +4767,7 @@ function d3_ease_poly(e) { } function d3_ease_sin(t) { - return 1 - Math.cos(t * π / 2); + return 1 - Math.cos(t * halfπ); } function d3_ease_exp(t) { @@ -4017,10 +4781,10 @@ function d3_ease_circle(t) { function d3_ease_elastic(a, p) { var s; if (arguments.length < 2) p = 0.45; - if (arguments.length) s = p / (2 * π) * Math.asin(1 / a); + 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) * 2 * π / p); + return 1 + a * Math.pow(2, -10 * t) * Math.sin((t - s) * τ / p); }; } @@ -4039,7 +4803,7 @@ function d3_ease_bounce(t) { } function d3_transition(groups, id) { - d3_arraySubclass(groups, d3_transitionPrototype); + d3_subclass(groups, d3_transitionPrototype); groups.id = id; // Note: read-only! @@ -4049,11 +4813,12 @@ function d3_transition(groups, id) { var d3_transitionPrototype = [], d3_transitionId = 0, d3_transitionInheritId, - d3_transitionInherit = {ease: d3_ease_cubicInOut, delay: 0, duration: 250}; + 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) { return arguments.length @@ -4071,12 +4836,12 @@ d3_transitionPrototype.select = function(selector) { subnode, node; - if (typeof selector !== "function") selector = d3_selection_selector(selector); + 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))) { + if ((node = group[i]) && (subnode = selector.call(node, node.__data__, i, j))) { if ("__data__" in node) subnode.__data__ = node.__data__; d3_transitionNode(subnode, i, id, node.__transition__[id]); subgroup.push(subnode); @@ -4098,16 +4863,16 @@ d3_transitionPrototype.selectAll = function(selector) { subnode, transition; - if (typeof selector !== "function") selector = d3_selection_selectorAll(selector); + 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.__transition__[id]; - subnodes = selector.call(node, node.__data__, i); + subnodes = selector.call(node, node.__data__, i, j); subgroups.push(subgroup = []); for (var k = -1, o = subnodes.length; ++k < o;) { - d3_transitionNode(subnode = subnodes[k], k, id, transition); + if (subnode = subnodes[k]) d3_transitionNode(subnode, k, id, transition); subgroup.push(subnode); } } @@ -4128,13 +4893,13 @@ d3_transitionPrototype.filter = function(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)) { + if ((node = group[i]) && filter.call(node, node.__data__, i, j)) { subgroup.push(node); } } } - return d3_transition(subgroups, this.id, this.time).ease(this.ease()); + return d3_transition(subgroups, this.id); }; function d3_Color() {} @@ -4180,8 +4945,8 @@ function d3_hsl_rgb(h, s, l) { m2; /* Some simple corrections for h, s and l. */ - h = h % 360; if (h < 0) h += 360; - s = s < 0 ? 0 : s > 1 ? 1 : s; + 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; /* From FvD 13.37, CSS Color Module Level 3 */ @@ -4237,6 +5002,8 @@ d3_hclPrototype.rgb = function() { }; function d3_hcl_lab(h, c, l) { + if (isNaN(h)) h = 0; + if (isNaN(c)) c = 0; return d3_lab(l, Math.cos(h *= d3_radians) * c, Math.sin(h) * c); } @@ -4295,7 +5062,9 @@ function d3_lab_rgb(l, a, b) { } function d3_lab_hcl(l, a, b) { - return d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l); + return l > 0 + ? d3_hcl(Math.atan2(b, a) * d3_degrees, Math.sqrt(a * a + b * b), l) + : d3_hcl(NaN, NaN, l); } function d3_lab_xyz(x) { @@ -4316,6 +5085,14 @@ d3.rgb = function(r, g, b) { : d3_rgb(~~r, ~~g, ~~b); }; +function d3_rgbNumber(value) { + return d3_rgb(value >> 16, value >> 8 & 0xff, value & 0xff); +} + +function d3_rgbString(value) { + return d3_rgbNumber(value) + ""; +} + function d3_rgb(r, g, b) { return new d3_Rgb(r, g, b); } @@ -4338,18 +5115,12 @@ d3_rgbPrototype.brighter = function(k) { if (r && r < i) r = i; if (g && g < i) g = i; if (b && b < i) b = i; - return d3_rgb( - Math.min(255, Math.floor(r / k)), - Math.min(255, Math.floor(g / k)), - Math.min(255, Math.floor(b / k))); + return 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(0.7, arguments.length ? k : 1); - return d3_rgb( - Math.floor(k * this.r), - Math.floor(k * this.g), - Math.floor(k * this.b)); + return d3_rgb(~~(k * this.r), ~~(k * this.g), ~~(k * this.b)); }; d3_rgbPrototype.hsl = function() { @@ -4372,7 +5143,7 @@ function d3_rgb_parse(format, rgb, hsl) { b = 0, // blue channel; int in [0, 255] m1, // CSS color specification match m2, // CSS color specification type (e.g., rgb) - name; + color; /* Handle hsl, rgb. */ m1 = /([a-z]+)\((.*)\)/i.exec(format); @@ -4397,22 +5168,19 @@ function d3_rgb_parse(format, rgb, hsl) { } /* Named colors. */ - if (name = d3_rgb_names.get(format)) return rgb(name.r, name.g, name.b); + if (color = d3_rgb_names.get(format)) return rgb(color.r, color.g, color.b); /* Hexadecimal colors: #rgb and #rrggbb. */ - if (format != null && format.charAt(0) === "#") { + if (format != null && format.charAt(0) === "#" && !isNaN(color = parseInt(format.substring(1), 16))) { if (format.length === 4) { - r = format.charAt(1); r += r; - g = format.charAt(2); g += g; - b = format.charAt(3); b += b; + r = (color & 0xf00) >> 4; r = (r >> 4) | r; + g = (color & 0xf0); g = (g >> 4) | g; + b = (color & 0xf); b = (b << 4) | b; } else if (format.length === 7) { - r = format.substring(1, 3); - g = format.substring(3, 5); - b = format.substring(5, 7); + r = (color & 0xff0000) >> 16; + g = (color & 0xff00) >> 8; + b = (color & 0xff); } - r = parseInt(r, 16); - g = parseInt(g, 16); - b = parseInt(b, 16); } return rgb(r, g, b); @@ -4432,7 +5200,8 @@ function d3_rgb_hsl(r, g, b) { else h = (r - g) / d + 4; h *= 60; } else { - s = h = 0; + h = NaN; + s = l > 0 && l < 1 ? 0 : h; } return d3_hsl(h, s, l); } @@ -4457,157 +5226,157 @@ function d3_rgb_parseNumber(c) { // either integer or percentage } var d3_rgb_names = d3.map({ - aliceblue: "#f0f8ff", - antiquewhite: "#faebd7", - aqua: "#00ffff", - aquamarine: "#7fffd4", - azure: "#f0ffff", - beige: "#f5f5dc", - bisque: "#ffe4c4", - black: "#000000", - blanchedalmond: "#ffebcd", - blue: "#0000ff", - blueviolet: "#8a2be2", - brown: "#a52a2a", - burlywood: "#deb887", - cadetblue: "#5f9ea0", - chartreuse: "#7fff00", - chocolate: "#d2691e", - coral: "#ff7f50", - cornflowerblue: "#6495ed", - cornsilk: "#fff8dc", - crimson: "#dc143c", - cyan: "#00ffff", - darkblue: "#00008b", - darkcyan: "#008b8b", - darkgoldenrod: "#b8860b", - darkgray: "#a9a9a9", - darkgreen: "#006400", - darkgrey: "#a9a9a9", - darkkhaki: "#bdb76b", - darkmagenta: "#8b008b", - darkolivegreen: "#556b2f", - darkorange: "#ff8c00", - darkorchid: "#9932cc", - darkred: "#8b0000", - darksalmon: "#e9967a", - darkseagreen: "#8fbc8f", - darkslateblue: "#483d8b", - darkslategray: "#2f4f4f", - darkslategrey: "#2f4f4f", - darkturquoise: "#00ced1", - darkviolet: "#9400d3", - deeppink: "#ff1493", - deepskyblue: "#00bfff", - dimgray: "#696969", - dimgrey: "#696969", - dodgerblue: "#1e90ff", - firebrick: "#b22222", - floralwhite: "#fffaf0", - forestgreen: "#228b22", - fuchsia: "#ff00ff", - gainsboro: "#dcdcdc", - ghostwhite: "#f8f8ff", - gold: "#ffd700", - goldenrod: "#daa520", - gray: "#808080", - green: "#008000", - greenyellow: "#adff2f", - grey: "#808080", - honeydew: "#f0fff0", - hotpink: "#ff69b4", - indianred: "#cd5c5c", - indigo: "#4b0082", - ivory: "#fffff0", - khaki: "#f0e68c", - lavender: "#e6e6fa", - lavenderblush: "#fff0f5", - lawngreen: "#7cfc00", - lemonchiffon: "#fffacd", - lightblue: "#add8e6", - lightcoral: "#f08080", - lightcyan: "#e0ffff", - lightgoldenrodyellow: "#fafad2", - lightgray: "#d3d3d3", - lightgreen: "#90ee90", - lightgrey: "#d3d3d3", - lightpink: "#ffb6c1", - lightsalmon: "#ffa07a", - lightseagreen: "#20b2aa", - lightskyblue: "#87cefa", - lightslategray: "#778899", - lightslategrey: "#778899", - lightsteelblue: "#b0c4de", - lightyellow: "#ffffe0", - lime: "#00ff00", - limegreen: "#32cd32", - linen: "#faf0e6", - magenta: "#ff00ff", - maroon: "#800000", - mediumaquamarine: "#66cdaa", - mediumblue: "#0000cd", - mediumorchid: "#ba55d3", - mediumpurple: "#9370db", - mediumseagreen: "#3cb371", - mediumslateblue: "#7b68ee", - mediumspringgreen: "#00fa9a", - mediumturquoise: "#48d1cc", - mediumvioletred: "#c71585", - midnightblue: "#191970", - mintcream: "#f5fffa", - mistyrose: "#ffe4e1", - moccasin: "#ffe4b5", - navajowhite: "#ffdead", - navy: "#000080", - oldlace: "#fdf5e6", - olive: "#808000", - olivedrab: "#6b8e23", - orange: "#ffa500", - orangered: "#ff4500", - orchid: "#da70d6", - palegoldenrod: "#eee8aa", - palegreen: "#98fb98", - paleturquoise: "#afeeee", - palevioletred: "#db7093", - papayawhip: "#ffefd5", - peachpuff: "#ffdab9", - peru: "#cd853f", - pink: "#ffc0cb", - plum: "#dda0dd", - powderblue: "#b0e0e6", - purple: "#800080", - red: "#ff0000", - rosybrown: "#bc8f8f", - royalblue: "#4169e1", - saddlebrown: "#8b4513", - salmon: "#fa8072", - sandybrown: "#f4a460", - seagreen: "#2e8b57", - seashell: "#fff5ee", - sienna: "#a0522d", - silver: "#c0c0c0", - skyblue: "#87ceeb", - slateblue: "#6a5acd", - slategray: "#708090", - slategrey: "#708090", - snow: "#fffafa", - springgreen: "#00ff7f", - steelblue: "#4682b4", - tan: "#d2b48c", - teal: "#008080", - thistle: "#d8bfd8", - tomato: "#ff6347", - turquoise: "#40e0d0", - violet: "#ee82ee", - wheat: "#f5deb3", - white: "#ffffff", - whitesmoke: "#f5f5f5", - yellow: "#ffff00", - yellowgreen: "#9acd32" + aliceblue: 0xf0f8ff, + antiquewhite: 0xfaebd7, + aqua: 0x00ffff, + aquamarine: 0x7fffd4, + azure: 0xf0ffff, + beige: 0xf5f5dc, + bisque: 0xffe4c4, + black: 0x000000, + blanchedalmond: 0xffebcd, + blue: 0x0000ff, + blueviolet: 0x8a2be2, + brown: 0xa52a2a, + burlywood: 0xdeb887, + cadetblue: 0x5f9ea0, + chartreuse: 0x7fff00, + chocolate: 0xd2691e, + coral: 0xff7f50, + cornflowerblue: 0x6495ed, + cornsilk: 0xfff8dc, + crimson: 0xdc143c, + cyan: 0x00ffff, + darkblue: 0x00008b, + darkcyan: 0x008b8b, + darkgoldenrod: 0xb8860b, + darkgray: 0xa9a9a9, + darkgreen: 0x006400, + darkgrey: 0xa9a9a9, + darkkhaki: 0xbdb76b, + darkmagenta: 0x8b008b, + darkolivegreen: 0x556b2f, + darkorange: 0xff8c00, + darkorchid: 0x9932cc, + darkred: 0x8b0000, + darksalmon: 0xe9967a, + darkseagreen: 0x8fbc8f, + darkslateblue: 0x483d8b, + darkslategray: 0x2f4f4f, + darkslategrey: 0x2f4f4f, + darkturquoise: 0x00ced1, + darkviolet: 0x9400d3, + deeppink: 0xff1493, + deepskyblue: 0x00bfff, + dimgray: 0x696969, + dimgrey: 0x696969, + dodgerblue: 0x1e90ff, + firebrick: 0xb22222, + floralwhite: 0xfffaf0, + forestgreen: 0x228b22, + fuchsia: 0xff00ff, + gainsboro: 0xdcdcdc, + ghostwhite: 0xf8f8ff, + gold: 0xffd700, + goldenrod: 0xdaa520, + gray: 0x808080, + green: 0x008000, + greenyellow: 0xadff2f, + grey: 0x808080, + honeydew: 0xf0fff0, + hotpink: 0xff69b4, + indianred: 0xcd5c5c, + indigo: 0x4b0082, + ivory: 0xfffff0, + khaki: 0xf0e68c, + lavender: 0xe6e6fa, + lavenderblush: 0xfff0f5, + lawngreen: 0x7cfc00, + lemonchiffon: 0xfffacd, + lightblue: 0xadd8e6, + lightcoral: 0xf08080, + lightcyan: 0xe0ffff, + lightgoldenrodyellow: 0xfafad2, + lightgray: 0xd3d3d3, + lightgreen: 0x90ee90, + lightgrey: 0xd3d3d3, + lightpink: 0xffb6c1, + lightsalmon: 0xffa07a, + lightseagreen: 0x20b2aa, + lightskyblue: 0x87cefa, + lightslategray: 0x778899, + lightslategrey: 0x778899, + lightsteelblue: 0xb0c4de, + lightyellow: 0xffffe0, + lime: 0x00ff00, + limegreen: 0x32cd32, + linen: 0xfaf0e6, + magenta: 0xff00ff, + maroon: 0x800000, + mediumaquamarine: 0x66cdaa, + mediumblue: 0x0000cd, + mediumorchid: 0xba55d3, + mediumpurple: 0x9370db, + mediumseagreen: 0x3cb371, + mediumslateblue: 0x7b68ee, + mediumspringgreen: 0x00fa9a, + mediumturquoise: 0x48d1cc, + mediumvioletred: 0xc71585, + midnightblue: 0x191970, + mintcream: 0xf5fffa, + mistyrose: 0xffe4e1, + moccasin: 0xffe4b5, + navajowhite: 0xffdead, + navy: 0x000080, + oldlace: 0xfdf5e6, + olive: 0x808000, + olivedrab: 0x6b8e23, + orange: 0xffa500, + orangered: 0xff4500, + orchid: 0xda70d6, + palegoldenrod: 0xeee8aa, + palegreen: 0x98fb98, + paleturquoise: 0xafeeee, + palevioletred: 0xdb7093, + papayawhip: 0xffefd5, + peachpuff: 0xffdab9, + peru: 0xcd853f, + pink: 0xffc0cb, + plum: 0xdda0dd, + powderblue: 0xb0e0e6, + purple: 0x800080, + red: 0xff0000, + rosybrown: 0xbc8f8f, + royalblue: 0x4169e1, + saddlebrown: 0x8b4513, + salmon: 0xfa8072, + sandybrown: 0xf4a460, + seagreen: 0x2e8b57, + seashell: 0xfff5ee, + sienna: 0xa0522d, + silver: 0xc0c0c0, + skyblue: 0x87ceeb, + slateblue: 0x6a5acd, + slategray: 0x708090, + slategrey: 0x708090, + snow: 0xfffafa, + springgreen: 0x00ff7f, + steelblue: 0x4682b4, + tan: 0xd2b48c, + teal: 0x008080, + thistle: 0xd8bfd8, + tomato: 0xff6347, + turquoise: 0x40e0d0, + violet: 0xee82ee, + wheat: 0xf5deb3, + white: 0xffffff, + whitesmoke: 0xf5f5f5, + yellow: 0xffff00, + yellowgreen: 0x9acd32 }); d3_rgb_names.forEach(function(key, value) { - d3_rgb_names.set(key, d3_rgb_parse(value, d3_rgb, d3_hsl_rgb)); + d3_rgb_names.set(key, d3_rgbNumber(value)); }); d3.interpolateRgb = d3_interpolateRgb; @@ -4629,11 +5398,133 @@ function d3_interpolateRgb(a, b) { }; } +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.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; + }; +} +d3.interpolateNumber = d3_interpolateNumber; + +function d3_interpolateNumber(a, b) { + b -= a = +a; + return function(t) { return a + b * t; }; +} + +d3.interpolateString = d3_interpolateString; + +function d3_interpolateString(a, b) { + var bi = d3_interpolate_numberA.lastIndex = d3_interpolate_numberB.lastIndex = 0, // scan index for next number in b + am, // current match in a + bm, // current match in b + bs, // string preceding current number in b, if any + i = -1, // index in s + s = [], // string constants and placeholders + q = []; // number interpolators + + // Coerce inputs to strings. + a = a + "", b = b + ""; + + // Interpolate pairs of numbers in a & b. + while ((am = d3_interpolate_numberA.exec(a)) + && (bm = d3_interpolate_numberB.exec(b))) { + if ((bs = bm.index) > bi) { // a string precedes the next number in b + bs = b.substring(bi, bs); + if (s[i]) s[i] += bs; // coalesce with previous string + else s[++i] = bs; + } + if ((am = am[0]) === (bm = bm[0])) { // numbers in a & b match + if (s[i]) s[i] += bm; // coalesce with previous string + else s[++i] = bm; + } else { // interpolate non-matching numbers + s[++i] = null; + q.push({i: i, x: d3_interpolateNumber(am, bm)}); + } + bi = d3_interpolate_numberB.lastIndex; + } + + // Add remains of b. + if (bi < b.length) { + bs = b.substring(bi); + if (s[i]) s[i] += bs; // coalesce with previous string + else s[++i] = bs; + } + + // Special optimization for only a single match. + // Otherwise, interpolate each of the numbers and rejoin the string. + 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) || /^(#|rgb\(|hsl\()/.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.transform = function(string) { var g = d3_document.createElementNS(d3.ns.prefix.svg, "g"); return (d3.transform = function(string) { - g.setAttribute("transform", string); - var t = g.transform.baseVal.consolidate(); + if (string != null) { + g.setAttribute("transform", string); + var t = g.transform.baseVal.consolidate(); + } return new d3_transform(t ? t.matrix : d3_transformIdentity); })(string); }; @@ -4688,12 +5579,6 @@ function d3_transformCombine(a, b, k) { } var d3_transformIdentity = {a: 1, b: 0, c: 0, d: 1, e: 0, f: 0}; -d3.interpolateNumber = d3_interpolateNumber; - -function d3_interpolateNumber(a, b) { - b -= a; - return function(t) { return a + b * t; }; -} d3.interpolateTransform = d3_interpolateTransform; @@ -4749,152 +5634,6 @@ function d3_interpolateTransform(a, b) { }; } -d3.interpolateObject = d3_interpolateObject; - -function d3_interpolateObject(a, b) { - var i = {}, - c = {}, - k; - for (k in a) { - if (k in b) { - i[k] = d3_interpolateByName(k)(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.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; - }; -} - -d3.interpolateString = d3_interpolateString; - -function d3_interpolateString(a, b) { - var m, // current match - i, // current index - j, // current index (for coalescing) - s0 = 0, // start index of current string prefix - s1 = 0, // end index of current string prefix - s = [], // string constants and placeholders - q = [], // number interpolators - n, // q.length - o; - - // Reset our regular expression! - d3_interpolate_number.lastIndex = 0; - - // Find all numbers in b. - for (i = 0; m = d3_interpolate_number.exec(b); ++i) { - if (m.index) s.push(b.substring(s0, s1 = m.index)); - q.push({i: s.length, x: m[0]}); - s.push(null); - s0 = d3_interpolate_number.lastIndex; - } - if (s0 < b.length) s.push(b.substring(s0)); - - // Find all numbers in a. - for (i = 0, n = q.length; (m = d3_interpolate_number.exec(a)) && i < n; ++i) { - o = q[i]; - if (o.x == m[0]) { // The numbers match, so coalesce. - if (o.i) { - if (s[o.i + 1] == null) { // This match is followed by another number. - s[o.i - 1] += o.x; - s.splice(o.i, 1); - for (j = i + 1; j < n; ++j) q[j].i--; - } else { // This match is followed by a string, so coalesce twice. - s[o.i - 1] += o.x + s[o.i + 1]; - s.splice(o.i, 2); - for (j = i + 1; j < n; ++j) q[j].i -= 2; - } - } else { - if (s[o.i + 1] == null) { // This match is followed by another number. - s[o.i] = o.x; - } else { // This match is followed by a string, so coalesce twice. - s[o.i] = o.x + s[o.i + 1]; - s.splice(o.i + 1, 1); - for (j = i + 1; j < n; ++j) q[j].i--; - } - } - q.splice(i, 1); - n--; - i--; - } else { - o.x = d3_interpolateNumber(parseFloat(m[0]), parseFloat(o.x)); - } - } - - // Remove any numbers in b not found in a. - while (i < n) { - o = q.pop(); - if (s[o.i + 1] == null) { // This match is followed by another number. - s[o.i] = o.x; - } else { // This match is followed by a string, so coalesce twice. - s[o.i] = o.x + s[o.i + 1]; - s.splice(o.i + 1, 1); - } - n--; - } - - // Special optimization for only a single match. - if (s.length === 1) { - return s[0] == null ? q[0].x : function() { return b; }; - } - - // Otherwise, interpolate each of the numbers and rejoin the string. - return function(t) { - for (i = 0; i < n; ++i) s[(o = q[i]).i] = o.x(t); - return s.join(""); - }; -} - -var d3_interpolate_number = /[-+]?(?:\d+\.?\d*|\.?\d+)(?:[eE][-+]?\d+)?/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; -} - -function d3_interpolateByName(name) { - return name == "transform" - ? d3_interpolateTransform - : d3_interpolate; -} - -d3.interpolators = [ - d3_interpolateObject, - function(a, b) { return Array.isArray(b) && d3_interpolateArray(a, b); }, - function(a, b) { return (typeof a === "string" || typeof b === "string") && d3_interpolateString(a + "", b + ""); }, - function(a, b) { return (typeof b === "string" ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) : b instanceof d3_Color) && d3_interpolateRgb(a, b); }, - function(a, b) { return !isNaN(a = +a) && !isNaN(b = +b) && d3_interpolateNumber(a, b); } -]; - d3_transitionPrototype.tween = function(name, tween) { var id = this.id; if (arguments.length < 2) return this.node().__transition__[id].tween.get(name); @@ -4920,7 +5659,7 @@ d3_transitionPrototype.attr = function(nameNS, value) { return this; } - var interpolate = d3_interpolateByName(nameNS), + var interpolate = nameNS == "transform" ? d3_interpolateTransform : d3_interpolate, name = d3.ns.qualify(nameNS); // For attr(string, null), remove the attribute with the specified name. @@ -4931,21 +5670,21 @@ d3_transitionPrototype.attr = function(nameNS, value) { this.removeAttributeNS(name.space, name.local); } - return d3_transition_tween(this, "attr." + nameNS, value, function(b) { - - // For attr(string, string), set the attribute with the specified name. - function attrString() { + // For attr(string, string), set the attribute with the specified name. + 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 attrStringNS() { + }); + } + 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 b == null ? (name.local ? attrNullNS : attrNull) - : (b += "", name.local ? attrStringNS : attrString); - }); + return d3_transition_tween(this, "attr." + nameNS, value, name.local ? attrTweenNS : attrTween); }; d3_transitionPrototype.attrTween = function(nameNS, tween) { @@ -4955,7 +5694,6 @@ d3_transitionPrototype.attrTween = function(nameNS, tween) { 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)); }; @@ -4983,36 +5721,35 @@ d3_transitionPrototype.style = function(name, value, priority) { priority = ""; } - var interpolate = d3_interpolateByName(name); - // For style(name, null) or style(name, null, priority), remove the style // property with the specified name. The priority is ignored. function styleNull() { this.style.removeProperty(name); } + // For style(name, string) or style(name, string, priority), set the style + // property with the specified name, using the specified priority. // Otherwise, a name, value and priority are specified, and handled as below. - return d3_transition_tween(this, "style." + name, value, function(b) { - - // For style(name, string) or style(name, string, priority), set the style - // property with the specified name, using the specified priority. - function styleString() { + function styleString(b) { + return b == null ? styleNull : (b += "", function() { var a = d3_window.getComputedStyle(this, null).getPropertyValue(name), i; - return a !== b && (i = interpolate(a, b), function(t) { this.style.setProperty(name, i(t), priority); }); - } + return a !== b && (i = d3_interpolate(a, b), function(t) { this.style.setProperty(name, i(t), priority); }); + }); + } - return b == null ? styleNull - : (b += "", styleString); - }); + return d3_transition_tween(this, "style." + name, value, styleString); }; d3_transitionPrototype.styleTween = function(name, tween, priority) { if (arguments.length < 3) priority = ""; - return this.tween("style." + name, function(d, i) { + + function styleTween(d, i) { var f = tween.call(this, d, i, d3_window.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); @@ -5026,7 +5763,7 @@ function d3_transition_text(b) { d3_transitionPrototype.remove = function() { return this.each("end.transition", function() { var p; - if (!this.__transition__ && (p = this.parentNode)) p.removeChild(this); + if (this.__transition__.count < 2 && (p = this.parentNode)) p.removeChild(this); }); }; @@ -5039,16 +5776,18 @@ d3_transitionPrototype.ease = function(value) { d3_transitionPrototype.delay = function(value) { var id = this.id; + if (arguments.length < 1) return this.node().__transition__[id].delay; return d3_selection_each(this, typeof value === "function" - ? function(node, i, j) { node.__transition__[id].delay = value.call(node, node.__data__, i, j) | 0; } - : (value |= 0, function(node) { node.__transition__[id].delay = value; })); + ? function(node, i, j) { node.__transition__[id].delay = +value.call(node, node.__data__, i, j); } + : (value = +value, function(node) { node.__transition__[id].delay = value; })); }; d3_transitionPrototype.duration = function(value) { var id = this.id; + if (arguments.length < 1) return this.node().__transition__[id].duration; return d3_selection_each(this, typeof value === "function" - ? function(node, i, j) { node.__transition__[id].duration = Math.max(1, value.call(node, node.__data__, i, j) | 0); } - : (value = Math.max(1, value | 0), function(node) { node.__transition__[id].duration = value; })); + ? function(node, i, j) { node.__transition__[id].duration = Math.max(1, value.call(node, node.__data__, i, j)); } + : (value = Math.max(1, value), function(node) { node.__transition__[id].duration = value; })); }; d3_transitionPrototype.each = function(type, listener) { @@ -5065,7 +5804,8 @@ d3_transitionPrototype.each = function(type, listener) { d3_transitionInheritId = inheritId; } else { d3_selection_each(this, function(node) { - node.__transition__[id].event.on(type, listener); + var transition = node.__transition__[id]; + (transition.event || (transition.event = d3.dispatch("start", "end"))).on(type, listener); }); } return this; @@ -5104,7 +5844,6 @@ function d3_transitionNode(node, i, id, inherit) { transition = lock[id] = { tween: new d3_Map, - event: d3.dispatch("start", "end"), // TODO construct lazily? time: time, ease: inherit.ease, delay: inherit.delay, @@ -5116,19 +5855,19 @@ function d3_transitionNode(node, i, id, inherit) { d3.timer(function(elapsed) { var d = node.__data__, ease = transition.ease, - event = transition.event, delay = transition.delay, duration = transition.duration, + timer = d3_timer_active, tweened = []; - return delay <= elapsed - ? start(elapsed) - : d3.timer(start, delay, time), 1; + timer.t = delay + time; + if (delay <= elapsed) return start(elapsed - delay); + timer.c = start; function start(elapsed) { if (lock.active > id) return stop(); lock.active = id; - event.start.call(node, d, i); + transition.event && transition.event.start.call(node, d, i); transition.tween.forEach(function(key, value) { if (value = value.call(node, d, i)) { @@ -5136,14 +5875,16 @@ function d3_transitionNode(node, i, id, inherit) { } }); - if (!tick(elapsed)) d3.timer(tick, 0, time); - return 1; + d3.timer(function() { // defer to end of current frame + timer.c = tick(elapsed || 1) ? d3_true : tick; + return 1; + }, 0, time); } function tick(elapsed) { if (lock.active !== id) return stop(); - var t = (elapsed - delay) / duration, + var t = elapsed / duration, e = ease(t), n = tweened.length; @@ -5152,9 +5893,8 @@ function d3_transitionNode(node, i, id, inherit) { } if (t >= 1) { - stop(); - event.end.call(node, d, i); - return 1; + transition.event && transition.event.end.call(node, d, i); + return stop(); } } @@ -5164,27 +5904,47 @@ function d3_transitionNode(node, i, id, inherit) { return 1; } }, 0, time); - - return transition; } } -d3.xhr = function(url, mimeType, callback) { +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("progress", "load", "error"), + dispatch = d3.dispatch("beforesend", "progress", "load", "error"), headers = {}, - response = d3_identity, - request = new (d3_window.XDomainRequest && /^(http(s)?:)?\/\//.test(url) ? XDomainRequest : XMLHttpRequest); + request = new XMLHttpRequest, + responseType = null; + + // If IE does not support CORS, use XDomainRequest. + if (d3_window.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 s = request.status; - !s && request.responseText || s >= 200 && s < 300 || s === 304 - ? dispatch.load.call(xhr, response.call(xhr, request)) - : dispatch.error.call(xhr, request); + var status = request.status, result; + if (!status && request.responseText || 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) { @@ -5209,6 +5969,14 @@ d3.xhr = function(url, mimeType, callback) { return xhr; }; + // Specifies what type the response value should take; + // for instance, arraybuffer, blob, document, or text. + xhr.responseType = function(value) { + if (!arguments.length) return responseType; + responseType = value; + return xhr; + }; + // Specify how to convert the response content to a specific type; // changes the callback value on "load" events. xhr.response = function(value) { @@ -5230,7 +5998,9 @@ d3.xhr = function(url, mimeType, callback) { 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; }; @@ -5242,7 +6012,6 @@ d3.xhr = function(url, mimeType, callback) { d3.rebind(xhr, dispatch, "on"); - if (arguments.length === 2 && typeof mimeType === "function") callback = mimeType, mimeType = null; return callback == null ? xhr : xhr.get(d3_xhr_fixCallback(callback)); }; @@ -5252,16 +6021,12 @@ function d3_xhr_fixCallback(callback) { : callback; } -d3.text = function() { - return d3.xhr.apply(d3, arguments).response(d3_text); -}; - -function d3_text(request) { +d3.text = d3_xhrType(function(request) { return request.responseText; -} +}); d3.json = function(url, callback) { - return d3.xhr(url, "application/json", callback).response(d3_json); + return d3_xhr(url, "application/json", d3_json, callback); }; function d3_json(request) { @@ -5269,7 +6034,7 @@ function d3_json(request) { } d3.html = function(url, callback) { - return d3.xhr(url, "text/html", callback).response(d3_html); + return d3_xhr(url, "text/html", d3_html, callback); }; function d3_html(request) { @@ -5278,23 +6043,26 @@ function d3_html(request) { return range.createContextualFragment(request.responseText); } -d3.xml = function() { - return d3.xhr.apply(d3, arguments).response(d3_xml); -}; - -function d3_xml(request) { +d3.xml = d3_xhrType(function(request) { return request.responseXML; -} - return d3; -})(); +}); + if (typeof define === "function" && define.amd) { + define(d3); + } else if (typeof module === "object" && module.exports) { + module.exports = d3; + } else { + this.d3 = d3; + } +}(); d3.combobox = function() { var event = d3.dispatch('accept'), - id = d3.combobox.id ++, - data = []; + data = [], + suggestions = [], + minItems = 2; - var fetcher = function(val, data, cb) { + var fetcher = function(val, cb) { cb(data.filter(function(d) { - return d.title + return d.value .toString() .toLowerCase() .indexOf(val.toLowerCase()) !== -1; @@ -5302,51 +6070,72 @@ d3.combobox = function() { }; var combobox = function(input) { - var idx = -1, container, shown = false; + var idx = -1, + container = d3.select(document.body) + .selectAll('div.combobox') + .filter(function(d) { return d === input.node(); }), + shown = !container.empty(); input .classed('combobox-input', true) + .on('focus.typeahead', focus) + .on('blur.typeahead', blur) + .on('keydown.typeahead', keydown) + .on('keyup.typeahead', keyup) + .on('input.typeahead', change) .each(function() { var parent = this.parentNode, sibling = this.nextSibling; - d3.select(parent) - .insert('div', function() { return sibling; }) - .attr('class', 'combobox-carat') + + var caret = d3.select(parent).selectAll('.combobox-caret') + .filter(function(d) { return d === input.node(); }) + .data([input.node()]); + + caret.enter().insert('div', function() { return sibling; }) + .attr('class', 'combobox-caret'); + + caret .on('mousedown', function () { // prevent the form element from blurring. it blurs // on mousedown d3.event.stopPropagation(); d3.event.preventDefault(); - mousedown(); + if (!shown) { + input.node().focus(); + fetch('', render); + } else { + hide(); + } }); }); - function updateSize() { - var rect = input.node().getBoundingClientRect(); - container.style({ - 'left': rect.left + 'px', - 'width': rect.width + 'px', - 'top': rect.height + rect.top + 'px' - }); + function focus() { + fetch(value(), render); } function blur() { - // hide the combobox whenever the input element - // loses focus - slowHide(); + window.setTimeout(hide, 150); } function show() { if (!shown) { container = d3.select(document.body) .insert('div', ':first-child') + .datum(input.node()) .attr('class', 'combobox') .style({ position: 'absolute', display: 'block', left: '0px' + }) + .on('mousedown', function () { + // prevent moving focus out of the text field + d3.event.preventDefault(); }); + d3.select(document.body) + .on('scroll.combobox', render, true); + shown = true; } } @@ -5355,28 +6144,43 @@ d3.combobox = function() { if (shown) { idx = -1; container.remove(); + + d3.select(document.body) + .on('scroll.combobox', null); + shown = false; } } - function slowHide() { - window.setTimeout(hide, 150); - } function keydown() { - if (!shown) return; switch (d3.event.keyCode) { - // down arrow - case 40: - next(); + // backspace, delete + case 8: + case 46: + input.on('input.typeahead', function() { + idx = -1; + render(); + var start = input.property('selectionStart'); + input.node().setSelectionRange(start, start); + input.on('input.typeahead', change); + }); + break; + // tab + case 9: + container.selectAll('a.selected').each(event.accept); + break; + // return + case 13: d3.event.preventDefault(); break; // up arrow case 38: - prev(); + nav(-1); d3.event.preventDefault(); break; - // escape, tab - case 13: + // down arrow + case 40: + nav(+1); d3.event.preventDefault(); break; } @@ -5389,175 +6193,118 @@ d3.combobox = function() { case 27: hide(); break; - // escape, tab - case 9: + // return case 13: - if (!shown) return; - accept(); + container.selectAll('a.selected').each(event.accept); + hide(); break; - default: - update(); - d3.event.preventDefault(); - } - d3.event.stopPropagation(); - } - - function accept() { - if (container.select('a.selected').node()) { - select(container.select('a.selected').datum()); } - hide(); } - function next() { - var len = container.selectAll('a').data().length; - idx = Math.min(idx + 1, len - 1); - highlight(); + function change() { + fetch(value(), function() { + autocomplete(); + render(); + }); } - function prev() { - idx = Math.max(idx - 1, 0); - highlight(); + function nav(dir) { + idx = Math.max(Math.min(idx + dir, suggestions.length - 1), 0); + input.property('value', suggestions[idx].value); + render(); + ensureVisible(); } - var prevValue, prevCompletion; - - function autocomplete(e, data) { - + function value() { var value = input.property('value'), - match; + start = input.property('selectionStart'), + end = input.property('selectionEnd'); - for (var i = 0; i < data.length; i++) { - if (data[i].value.toLowerCase().indexOf(value.toLowerCase()) === 0) { - match = data[i].value; - break; - } + if (start && end) { + value = value.substring(0, start); } - // backspace - if (e.keyCode === 8) { - prevValue = value; - prevCompletion = ''; + return value; + } - } else if (value && match && value !== prevValue + prevCompletion) { - prevValue = value; - prevCompletion = match.substr(value.length); - input.property('value', prevValue + prevCompletion); - input.node().setSelectionRange(value.length, value.length + prevCompletion.length); - } + function fetch(v, cb) { + fetcher.call(input, v, function(_) { + suggestions = _; + cb(); + }); } + function autocomplete() { + var v = value(); - function highlight() { - container - .selectAll('a') - .classed('selected', function(d, i) { return i == idx; }); - var height = container.node().offsetHeight, - top = container.select('a.selected').node().offsetTop, - selectedHeight = container.select('a.selected').node().offsetHeight; - if ((top + selectedHeight) < height) { - container.node().scrollTop = 0; - } else { - container.node().scrollTop = top; - } - } + idx = -1; - function update(value) { + if (!v) return; - if (typeof value === 'undefined') { - value = input.property('value'); + for (var i = 0; i < suggestions.length; i++) { + if (suggestions[i].value.toLowerCase().indexOf(v.toLowerCase()) === 0) { + var completion = v + suggestions[i].value.substr(v.length); + idx = i; + input.property('value', completion); + input.node().setSelectionRange(v.length, completion.length); + return; + } } + } - var e = d3.event; - - function render(data) { + function render() { + if (suggestions.length >= minItems && document.activeElement === input.node()) { + show(); + } else { + hide(); + return; + } - if (data.length && - document.activeElement === input.node()) show(); - else return hide(); + var options = container + .selectAll('a.combobox-option') + .data(suggestions, function(d) { return d.value; }); - autocomplete(e, data); + options.enter().append('a') + .attr('class', 'combobox-option') + .text(function(d) { return d.value; }); - updateSize(); + options + .attr('title', function(d) { return d.title; }) + .classed('selected', function(d, i) { return i == idx; }) + .on('mouseover', select) + .on('click', accept) + .order(); - var options = container - .selectAll('a.combobox-option') - .data(data, function(d) { return d.value; }); + options.exit() + .remove(); - options.enter() - .append('a') - .text(function(d) { return d.value; }) - .attr('class', 'combobox-option') - .attr('title', function(d) { return d.title; }) - .on('click', select); + var rect = input.node().getBoundingClientRect(); - options.exit().remove(); + container.style({ + 'left': rect.left + 'px', + 'width': rect.width + 'px', + 'top': rect.height + rect.top + 'px' + }); + } - options - .classed('selected', function(d, i) { return i == idx; }) - .order(); - } + function select(d, i) { + idx = i; + render(); + } - fetcher.apply(input, [value, data, render]); + function ensureVisible() { + var node = container.selectAll('a.selected').node(); + if (node) node.scrollIntoView(); } - // select the choice given as d - function select(d) { + function accept(d) { + if (!shown) return; input .property('value', d.value) .trigger('change'); event.accept(d); hide(); } - - function mousedown() { - - if (shown) return hide(); - - input.node().focus(); - update(''); - - if (!container) return; - - var entries = container.selectAll('a'), - height = container.node().scrollHeight / entries[0].length, - w = d3.select(window); - - function getIndex(m) { - return Math.floor((m[1] + container.node().scrollTop) / height); - } - - function withinBounds(m) { - var n = container.node(); - return m[0] >= 0 && m[0] < n.offsetWidth && - m[1] >= 0 && m[1] < n.offsetHeight; - } - - w.on('mousemove.typeahead', function() { - var m = d3.mouse(container.node()); - var within = withinBounds(m); - var n = getIndex(m); - entries.classed('selected', function(d, i) { return within && i === n; }); - }); - - w.on('mouseup.typeahead', function() { - var m = d3.mouse(container.node()); - if (withinBounds(m)) select(d3.select(entries[0][getIndex(m)]).datum()); - entries.classed('selected', false); - w.on('mouseup.typeahead', null); - w.on('mousemove.typeahead', null); - }); - } - - input - .on('blur.typeahead', blur) - .on('keydown.typeahead', keydown) - .on('keyup.typeahead', keyup) - .on('mousedown.typeahead', mousedown); - - d3.select(document.body).on('scroll.combo' + id, function() { - if (shown) updateSize(); - }, true); }; combobox.fetcher = function(_) { @@ -5572,10 +6319,14 @@ d3.combobox = function() { return combobox; }; + combobox.minItems = function(_) { + if (!arguments.length) return minItems; + minItems = _; + return combobox; + }; + return d3.rebind(combobox, event, 'on'); }; - -d3.combobox.id = 0; d3.geo.tile = function() { var size = [960, 500], scale = 256, @@ -5806,14 +6557,20 @@ d3.keybinding = function(namespace) { // Up Arrow Key, or ↓ '↓': 40, down: 40, 'arrow-down': 40, // odities, printing characters that come out wrong: + // Firefox Equals + 'ffequals': 61, // Num-Multiply, or * '*': 106, star: 106, asterisk: 106, multiply: 106, // Num-Plus or + '+': 107, 'plus': 107, // Num-Subtract, or - '-': 109, subtract: 109, + // Firefox Minus + 'ffplus': 171, + // Firefox Minus + 'ffminus': 173, // Semicolon - ';': 186, semicolon:186, + ';': 186, semicolon: 186, // = or equals '=': 187, 'equals': 187, // Comma, or , @@ -5871,13 +6628,13 @@ d3.selection.prototype.one = function (type, listener, capture) { target.on(typeOnce, one, capture); return this; }; -d3.selection.prototype.size = function (size) { +d3.selection.prototype.dimensions = function (dimensions) { if (!arguments.length) { var node = this.node(); return [node.offsetWidth, node.offsetHeight]; } - return this.attr({width: size[0], height: size[1]}); + return this.attr({width: dimensions[0], height: dimensions[1]}); }; d3.selection.prototype.trigger = function (type) { this.each(function() { @@ -6061,10 +6818,10 @@ d3.curtain = function() { var html = parts[0] ? '' + parts[0] + '' : ''; if (parts[1]) html += '' + parts[1] + ''; - var size = tooltip.classed('in', true) + var dimensions = tooltip.classed('in', true) .select('.tooltip-inner') .html(html) - .size(); + .dimensions(); var pos; @@ -6073,23 +6830,23 @@ d3.curtain = function() { if (box.top + box.height < Math.min(100, box.width + box.left)) { side = 'bottom'; - pos = [box.left + box.width / 2 - size[0]/ 2, box.top + box.height]; + pos = [box.left + box.width / 2 - dimensions[0]/ 2, box.top + box.height]; } else if (box.left + box.width + 300 < window.innerWidth) { side = 'right'; - pos = [box.left + box.width, box.top + box.height / 2 - size[1] / 2]; + pos = [box.left + box.width, box.top + box.height / 2 - dimensions[1] / 2]; } else if (box.left > 300) { side = 'left'; - pos = [box.left - 200, box.top + box.height / 2 - size[1] / 2]; + pos = [box.left - 200, box.top + box.height / 2 - dimensions[1] / 2]; } else { side = 'bottom'; pos = [box.left, box.top + box.height]; } pos = [ - Math.min(Math.max(10, pos[0]), w - size[0] - 10), - Math.min(Math.max(10, pos[1]), h - size[1] - 10) + Math.min(Math.max(10, pos[0]), w - dimensions[0] - 10), + Math.min(Math.max(10, pos[1]), h - dimensions[1] - 10) ]; @@ -6133,6 +6890,32 @@ d3.curtain = function() { return d3.rebind(curtain, event, 'on'); }; +// Like selection.property('value', ...), but avoids no-op value sets, +// which can result in layout/repaint thrashing in some situations. +d3.selection.prototype.value = function(value) { + function d3_selection_value(value) { + function valueNull() { + delete this.value; + } + + function valueConstant() { + if (this.value !== value) this.value = value; + } + + function valueFunction() { + var x = value.apply(this, arguments); + if (x == null) delete this.value; + else if (this.value !== x) this.value = x; + } + + return value == null + ? valueNull : (typeof value === "function" + ? valueFunction : valueConstant); + } + + if (!arguments.length) return this.property('value'); + return this.each(d3_selection_value(value)); +}; var JXON = new (function () { var sValueProp = "keyValue", sAttributesProp = "keyAttributes", sAttrPref = "@", /* you can customize these values */ @@ -6274,211 +7057,63 @@ var JXON = new (function () { // var newDoc = JXON.unbuild(myObject); // we got our Document instance! try: alert((new XMLSerializer()).serializeToString(newDoc)); -/*! - * Lo-Dash 1.0.0-rc.3 - * (c) 2012 John-David Dalton - * Based on Underscore.js 1.4.3 - * (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. +/** + * @license + * Lo-Dash 2.3.0 (Custom Build) + * Build: `lodash --debug --output js/lib/lodash.js include="any,assign,bind,clone,compact,contains,debounce,difference,each,every,extend,filter,find,first,forEach,groupBy,indexOf,intersection,isEmpty,isEqual,isFunction,keys,last,map,omit,pairs,pluck,reject,some,throttle,union,uniq,unique,values,without,flatten,value,chain,cloneDeep,merge,pick,reduce" exports="global,node"` + * Copyright 2012-2013 The Dojo Foundation + * Based on Underscore.js 1.5.2 + * Copyright 2009-2013 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors * Available under MIT license */ -;(function(window, undefined) { - - /** Detect free variable `exports` */ - var freeExports = typeof exports == 'object' && exports; - - /** Detect free variable `global` and use it as `window` */ - var freeGlobal = typeof global == 'object' && global; - if (freeGlobal.global === freeGlobal) { - window = freeGlobal; - } +;(function() { - /** Used for array and object method references */ - var arrayRef = [], - // avoid a Closure Compiler bug by creatively creating an object - objectRef = new function(){}; + /** Used as a safe reference for `undefined` in pre ES5 environments */ + var undefined; - /** Used to generate unique IDs */ - var idCounter = 0; + /** Used to pool arrays and objects used internally */ + var arrayPool = [], + objectPool = []; /** Used internally to indicate various things */ - var indicatorObject = objectRef; - - /** Used by `cachedContains` as the default size when optimizations are enabled for large arrays */ - var largeArraySize = 30; - - /** Used to restore the original `_` reference in `noConflict` */ - var oldDash = window._; + var indicatorObject = {}; - /** Used to detect template delimiter values that require a with-statement */ - var reComplexDelimiter = /[-?+=!~*%&^<>|{(\/]|\[\D|\b(?:delete|in|instanceof|new|typeof|void)\b/; + /** Used to prefix keys to avoid issues with `__proto__` and properties on `Object.prototype` */ + var keyPrefix = +new Date + ''; - /** Used to match HTML entities */ - var reEscapedHtml = /&(?:amp|lt|gt|quot|#x27);/g; + /** Used as the size when optimizations are enabled for large arrays */ + var largeArraySize = 75; - /** 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 as the max size of the `arrayPool` and `objectPool` */ + var maxPoolSize = 40; /** Used to match regexp flags from their coerced string values */ var reFlags = /\w*$/; - /** Used to insert the data object variable into compiled template source */ - var reInsertVariable = /(?:__e|__t = )\(\s*(?![\d\s"']|this\.)/g; + /** Used to detected named functions */ + var reFuncName = /^\s*function[ \n\r\t]+\w/; - /** Used to detect if a method is native */ - var reNative = RegExp('^' + - (objectRef.valueOf + '') - .replace(/[.*+?^=!:${}()|[\]\/\\]/g, '\\$&') - .replace(/valueOf|for [^\]]+/g, '.+?') + '$' - ); - - /** - * Used to match ES6 template delimiters - * http://people.mozilla.org/~jorendorff/es6-draft.html#sec-7.8.6 - */ - var reEsTemplate = /\$\{((?:(?=\\?)\\?[\s\S])*?)}/g; - - /** Used to match "interpolate" template delimiters */ - var reInterpolate = /<%=([\s\S]+?)%>/g; - - /** Used to ensure capturing order of template delimiters */ - var reNoMatch = /($^)/; - - /** Used to match HTML characters */ - var reUnescapedHtml = /[&<>"']/g; - - /** Used to match unescaped characters in compiled string literals */ - var reUnescapedString = /['\n\r\t\u2028\u2029\\]/g; + /** Used to detect functions containing a `this` reference */ + var reThis = /\bthis\b/; /** Used to fix the JScript [[DontEnum]] bug */ - var shadowed = [ + var shadowedProps = [ 'constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'valueOf' ]; - /** Used to make template sourceURLs easier to identify */ - var templateCounter = 0; - - /** Native method shortcuts */ - var ceil = Math.ceil, - concat = arrayRef.concat, - floor = Math.floor, - getPrototypeOf = reNative.test(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf, - hasOwnProperty = objectRef.hasOwnProperty, - push = arrayRef.push, - propertyIsEnumerable = objectRef.propertyIsEnumerable, - toString = objectRef.toString; - - /* Native method shortcuts for methods with the same name as other `lodash` methods */ - var nativeBind = reNative.test(nativeBind = slice.bind) && nativeBind, - nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray, - nativeIsFinite = window.isFinite, - nativeIsNaN = window.isNaN, - nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys, - nativeMax = Math.max, - nativeMin = Math.min, - nativeRandom = Math.random; - /** `Object#toString` result shortcuts */ var argsClass = '[object Arguments]', arrayClass = '[object Array]', boolClass = '[object Boolean]', dateClass = '[object Date]', + errorClass = '[object Error]', funcClass = '[object Function]', numberClass = '[object Number]', objectClass = '[object Object]', regexpClass = '[object RegExp]', stringClass = '[object String]'; - /** Detect various environments */ - var isIeOpera = !!window.attachEvent, - isV8 = nativeBind && !/\n|true/.test(nativeBind + isIeOpera); - - /* Detect if `Function#bind` exists and is inferred to be fast (all but V8) */ - var isBindFast = nativeBind && !isV8; - - /* Detect if `Object.keys` exists and is inferred to be fast (IE, Opera, V8) */ - var isKeysFast = nativeKeys && (isIeOpera || isV8); - - /** - * Detect the JScript [[DontEnum]] bug: - * - * In IE < 9 an objects own properties, shadowing non-enumerable ones, are - * made non-enumerable as well. - */ - var hasDontEnumBug; - - /** Detect if own properties are iterated after inherited properties (IE < 9) */ - var iteratesOwnLast; - - /** - * Detect if `Array#shift` and `Array#splice` augment array-like objects - * incorrectly: - * - * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()` - * and `splice()` functions that fail to remove the last element, `value[0]`, - * of array-like objects even though the `length` property is set to `0`. - * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()` - * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9. - */ - var hasObjectSpliceBug = (hasObjectSpliceBug = { '0': 1, 'length': 1 }, - arrayRef.splice.call(hasObjectSpliceBug, 0, 1), hasObjectSpliceBug[0]); - - /** Detect if an `arguments` object's indexes are non-enumerable (IE < 9) */ - var nonEnumArgs = true; - - (function() { - var props = []; - function ctor() { this.x = 1; } - ctor.prototype = { 'valueOf': 1, 'y': 1 }; - for (var prop in new ctor) { props.push(prop); } - for (prop in arguments) { nonEnumArgs = !prop; } - - hasDontEnumBug = !/valueOf/.test(props); - iteratesOwnLast = props[0] != 'x'; - }(1)); - - /** Detect if `arguments` objects are `Object` objects (all but Opera < 10.5) */ - var argsAreObjects = arguments.constructor == Object; - - /** Detect if `arguments` objects [[Class]] is unresolvable (Firefox < 4, IE < 9) */ - var noArgsClass = !isArguments(arguments); - - /** - * Detect lack of support for accessing string characters by index: - * - * IE < 8 can't access characters by index and IE 8 can only access - * characters by index on string literals. - */ - var noCharByIndex = ('x'[0] + Object('x')[0]) != 'xx'; - - /** - * Detect if a node's [[Class]] is unresolvable (IE < 9) - * and that the JS engine won't error when attempting to coerce an object to - * a string without a `toString` property value of `typeof` "function". - */ - try { - var noNodeClass = ({ 'toString': 0 } + '', toString.call(document) == objectClass); - } catch(e) { } - - /** - * Detect if sourceURL syntax is usable without erroring: - * - * The JS engine embedded in Adobe products will throw a syntax error when - * it encounters a single line comment beginning with the `@` symbol. - * - * The JS engine in Narwhal will generate the function `function anonymous(){//}` - * and throw a syntax error. - * - * Avoid comments beginning `@` symbols in IE because they are part of its - * non-standard conditional compilation support. - * http://msdn.microsoft.com/en-us/library/121hztk3(v=vs.94).aspx - */ - try { - var useSourceURL = (Function('//@')(), !isIeOpera); - } catch(e) { } - /** Used to identify object classifications that `_.clone` supports */ var cloneableClasses = {}; cloneableClasses[funcClass] = false; @@ -6487,15 +7122,35 @@ var JXON = new (function () { cloneableClasses[numberClass] = cloneableClasses[objectClass] = cloneableClasses[regexpClass] = cloneableClasses[stringClass] = true; - /** Used to lookup a built-in constructor by [[Class]] */ - var ctorByClass = {}; - ctorByClass[arrayClass] = Array; - ctorByClass[boolClass] = Boolean; - ctorByClass[dateClass] = Date; - ctorByClass[objectClass] = Object; - ctorByClass[numberClass] = Number; - ctorByClass[regexpClass] = RegExp; - ctorByClass[stringClass] = String; + /** Used as an internal `_.debounce` options object */ + var debounceOptions = { + 'leading': false, + 'maxWait': 0, + 'trailing': false + }; + + /** Used as the property descriptor for `__bindData__` */ + var descriptor = { + 'configurable': false, + 'enumerable': false, + 'value': null, + 'writable': false + }; + + /** Used as the data object for `iteratorTemplate` */ + var iteratorData = { + 'args': '', + 'array': null, + 'bottom': '', + 'firstArg': '', + 'init': '', + 'keys': null, + 'loop': '', + 'shadowedProps': null, + 'support': null, + 'top': '', + 'useHas': false + }; /** Used to determine if values are of the language type Object */ var objectTypes = { @@ -6507,511 +7162,171 @@ var JXON = new (function () { 'undefined': false }; - /** Used to escape characters for inclusion in compiled string literals */ - var stringEscapes = { - '\\': '\\', - "'": "'", - '\n': 'n', - '\r': 'r', - '\t': 't', - '\u2028': 'u2028', - '\u2029': 'u2029' - }; + /** Used as a reference to the global object */ + var root = (objectTypes[typeof window] && window) || this; - /*--------------------------------------------------------------------------*/ - - /** - * Creates a `lodash` object, that wraps the given `value`, to enable - * method chaining. - * - * The chainable wrapper functions are: - * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`, `compose`, - * `concat`, `countBy`, `debounce`, `defaults`, `defer`, `delay`, `difference`, - * `filter`, `flatten`, `forEach`, `forIn`, `forOwn`, `functions`, `groupBy`, - * `initial`, `intersection`, `invert`, `invoke`, `keys`, `map`, `max`, `memoize`, - * `merge`, `min`, `object`, `omit`, `once`, `pairs`, `partial`, `pick`, `pluck`, - * `push`, `range`, `reject`, `rest`, `reverse`, `shuffle`, `slice`, `sort`, - * `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `union`, `uniq`, - * `unshift`, `values`, `where`, `without`, `wrap`, and `zip` - * - * The non-chainable wrapper functions are: - * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `has`, `identity`, - * `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`, `isEmpty`, - * `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`, `isObject`, - * `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`, `lastIndexOf`, - * `mixin`, `noConflict`, `pop`, `random`, `reduce`, `reduceRight`, `result`, - * `shift`, `size`, `some`, `sortedIndex`, `template`, `unescape`, and `uniqueId` - * - * The wrapper functions `first` and `last` return wrapped values when `n` is - * passed, otherwise they return unwrapped values. - * - * @name _ - * @constructor - * @category Chaining - * @param {Mixed} value The value to wrap in a `lodash` instance. - * @returns {Object} Returns a `lodash` instance. - */ - function lodash(value) { - // exit early if already wrapped, even if wrapped by a different `lodash` constructor - if (value && typeof value == 'object' && value.__wrapped__) { - return value; - } - // allow invoking `lodash` without the `new` operator - if (!(this instanceof lodash)) { - return new lodash(value); - } - this.__wrapped__ = value; - } - - /** - * By default, the template delimiters used by Lo-Dash are similar to 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. - * - * @static - * @memberOf _.templateSettings - * @type RegExp - */ - 'escape': /<%-([\s\S]+?)%>/g, - - /** - * Used to detect code to be evaluated. - * - * @static - * @memberOf _.templateSettings - * @type RegExp - */ - 'evaluate': /<%([\s\S]+?)%>/g, - - /** - * Used to detect `data` property values to inject. - * - * @static - * @memberOf _.templateSettings - * @type RegExp - */ - 'interpolate': reInterpolate, - - /** - * Used to reference the data object in the template text. - * - * @static - * @memberOf _.templateSettings - * @type String - */ - 'variable': '' - }; - - /*--------------------------------------------------------------------------*/ - - /** - * The template used to create iterator functions. - * - * @private - * @param {Obect} data The data object used to populate the text. - * @returns {String} Returns the interpolated text. - */ - var iteratorTemplate = template( - // conditional strict mode - "<% if (obj.useStrict) { %>'use strict';\n<% } %>" + - - // the `iteratee` may be reassigned by the `top` snippet - 'var index, iteratee = <%= firstArg %>, ' + - // assign the `result` variable an initial value - 'result = <%= firstArg %>;\n' + - // exit early if the first argument is falsey - 'if (!<%= firstArg %>) return result;\n' + - // add code before the iteration branches - '<%= top %>;\n' + - - // array-like iteration: - '<% if (arrayLoop) { %>' + - 'var length = iteratee.length; index = -1;\n' + - "if (typeof length == 'number') {" + - - // add support for accessing string characters by index if needed - ' <% if (noCharByIndex) { %>\n' + - ' if (isString(iteratee)) {\n' + - " iteratee = iteratee.split('')\n" + - ' }' + - ' <% } %>\n' + - - // iterate over the array-like value - ' while (++index < length) {\n' + - ' <%= arrayLoop %>\n' + - ' }\n' + - '}\n' + - 'else {' + - - // object iteration: - // add support for iterating over `arguments` objects if needed - ' <% } else if (nonEnumArgs) { %>\n' + - ' var length = iteratee.length; index = -1;\n' + - ' if (length && isArguments(iteratee)) {\n' + - ' while (++index < length) {\n' + - " index += '';\n" + - ' <%= objectLoop %>\n' + - ' }\n' + - ' } else {' + - ' <% } %>' + - - // Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 - // (if the prototype or a property on the prototype has been set) - // incorrectly sets a function's `prototype` property [[Enumerable]] - // value to `true`. Because of this Lo-Dash standardizes on skipping - // the the `prototype` property of functions regardless of its - // [[Enumerable]] value. - ' <% if (!hasDontEnumBug) { %>\n' + - " var skipProto = typeof iteratee == 'function' && \n" + - " propertyIsEnumerable.call(iteratee, 'prototype');\n" + - ' <% } %>' + - - // iterate own properties using `Object.keys` if it's fast - ' <% if (isKeysFast && useHas) { %>\n' + - ' var ownIndex = -1,\n' + - ' ownProps = objectTypes[typeof iteratee] ? nativeKeys(iteratee) : [],\n' + - ' length = ownProps.length;\n\n' + - ' while (++ownIndex < length) {\n' + - ' index = ownProps[ownIndex];\n' + - " <% if (!hasDontEnumBug) { %>if (!(skipProto && index == 'prototype')) {\n <% } %>" + - ' <%= objectLoop %>\n' + - ' <% if (!hasDontEnumBug) { %>}\n<% } %>' + - ' }' + - - // else using a for-in loop - ' <% } else { %>\n' + - ' for (index in iteratee) {<%' + - ' if (!hasDontEnumBug || useHas) { %>\n if (<%' + - " if (!hasDontEnumBug) { %>!(skipProto && index == 'prototype')<% }" + - ' if (!hasDontEnumBug && useHas) { %> && <% }' + - ' if (useHas) { %>hasOwnProperty.call(iteratee, index)<% }' + - ' %>) {' + - ' <% } %>\n' + - ' <%= objectLoop %>;' + - ' <% if (!hasDontEnumBug || useHas) { %>\n }<% } %>\n' + - ' }' + - ' <% } %>' + - - // Because IE < 9 can't set the `[[Enumerable]]` attribute of an - // existing property and the `constructor` property of a prototype - // defaults to non-enumerable, Lo-Dash skips the `constructor` - // property when it infers it's iterating over a `prototype` object. - ' <% if (hasDontEnumBug) { %>\n\n' + - ' var ctor = iteratee.constructor;\n' + - ' <% for (var k = 0; k < 7; k++) { %>\n' + - " index = '<%= shadowed[k] %>';\n" + - ' if (<%' + - " if (shadowed[k] == 'constructor') {" + - ' %>!(ctor && ctor.prototype === iteratee) && <%' + - ' } %>hasOwnProperty.call(iteratee, index)) {\n' + - ' <%= objectLoop %>\n' + - ' }' + - ' <% } %>' + - ' <% } %>' + - ' <% if (arrayLoop || nonEnumArgs) { %>\n}<% } %>\n' + - - // add code to the bottom of the iteration function - '<%= bottom %>;\n' + - // finally, return the `result` - 'return result' - ); + /** Detect free variable `exports` */ + var freeExports = objectTypes[typeof exports] && exports && !exports.nodeType && exports; - /** Reusable iterator options for `assign` and `defaults` */ - var assignIteratorOptions = { - 'args': 'object, source, guard', - 'top': - "for (var argsIndex = 1, argsLength = typeof guard == 'number' ? 2 : arguments.length; argsIndex < argsLength; argsIndex++) {\n" + - ' if ((iteratee = arguments[argsIndex])) {', - 'objectLoop': 'result[index] = iteratee[index]', - 'bottom': ' }\n}' - }; + /** Detect free variable `module` */ + var freeModule = objectTypes[typeof module] && module && !module.nodeType && module; - /** - * Reusable iterator options shared by `each`, `forIn`, and `forOwn`. - */ - var eachIteratorOptions = { - 'args': 'collection, callback, thisArg', - 'top': "callback = callback && typeof thisArg == 'undefined' ? callback : createCallback(callback, thisArg)", - 'arrayLoop': 'if (callback(iteratee[index], index, collection) === false) return result', - 'objectLoop': 'if (callback(iteratee[index], index, collection) === false) return result' - }; + /** Detect the popular CommonJS extension `module.exports` */ + var moduleExports = freeModule && freeModule.exports === freeExports && freeExports; - /** Reusable iterator options for `forIn` and `forOwn` */ - var forOwnIteratorOptions = { - 'arrayLoop': null - }; + /** Detect free variable `global` from Node.js or Browserified code and use it as `root` */ + var freeGlobal = objectTypes[typeof global] && global; + if (freeGlobal && (freeGlobal.global === freeGlobal || freeGlobal.window === freeGlobal)) { + root = freeGlobal; + } /*--------------------------------------------------------------------------*/ /** - * Creates a function optimized to search large arrays for a given `value`, - * starting at `fromIndex`, using strict equality for comparisons, i.e. `===`. + * The base implementation of `_.indexOf` without support for binary searches + * or `fromIndex` constraints. * * @private * @param {Array} array The array to search. - * @param {Mixed} value The value to search for. - * @param {Number} [fromIndex=0] The index to search from. - * @param {Number} [largeSize=30] The length at which an array is considered large. - * @returns {Boolean} Returns `true` if `value` is found, else `false`. + * @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 or `-1`. */ - function cachedContains(array, fromIndex, largeSize) { - fromIndex || (fromIndex = 0); - - var length = array.length, - isLarge = (length - fromIndex) >= (largeSize || largeArraySize); - - if (isLarge) { - var cache = {}, - index = fromIndex - 1; + function baseIndexOf(array, value, fromIndex) { + var index = (fromIndex || 0) - 1, + length = array ? array.length : 0; - while (++index < length) { - // manually coerce `value` to a string because `hasOwnProperty`, in some - // older versions of Firefox, coerces objects incorrectly - var key = array[index] + ''; - (hasOwnProperty.call(cache, key) ? cache[key] : (cache[key] = [])).push(array[index]); - } - } - return function(value) { - if (isLarge) { - var key = value + ''; - return hasOwnProperty.call(cache, key) && indexOf(cache[key], value) > -1; + while (++index < length) { + if (array[index] === value) { + return index; } - return indexOf(array, value, fromIndex) > -1; } + return -1; } /** - * Used by `_.max` and `_.min` as the default `callback` when a given - * `collection` is a string value. - * - * @private - * @param {String} value The character to inspect. - * @returns {Number} Returns the code unit of given character. - */ - function charAtCallback(value) { - return value.charCodeAt(0); - } - - /** - * Used by `sortBy` to compare transformed `collection` values, stable sorting - * them in ascending order. + * An implementation of `_.contains` for cache objects that mimics the return + * signature of `_.indexOf` by returning `0` if the value is found, else `-1`. * * @private - * @param {Object} a The object to compare to `b`. - * @param {Object} b The object to compare to `a`. - * @returns {Number} Returns the sort order indicator of `1` or `-1`. + * @param {Object} cache The cache object to inspect. + * @param {*} value The value to search for. + * @returns {number} Returns `0` if `value` is found, else `-1`. */ - function compareAscending(a, b) { - var ai = a.index, - bi = b.index; - - a = a.criteria; - b = b.criteria; + function cacheIndexOf(cache, value) { + var type = typeof value; + cache = cache.cache; - // ensure a stable sort in V8 and other engines - // http://code.google.com/p/v8/issues/detail?id=90 - if (a !== b) { - if (a > b || typeof a == 'undefined') { - return 1; - } - if (a < b || typeof b == 'undefined') { - return -1; - } + if (type == 'boolean' || value == null) { + return cache[value] ? 0 : -1; + } + if (type != 'number' && type != 'string') { + type = 'object'; } - return ai < bi ? -1 : 1; + var key = type == 'number' ? value : keyPrefix + value; + cache = (cache = cache[type]) && cache[key]; + + return type == 'object' + ? (cache && baseIndexOf(cache, value) > -1 ? 0 : -1) + : (cache ? 0 : -1); } /** - * Creates a function that, when called, invokes `func` with the `this` - * binding of `thisArg` and prepends any `partailArgs` to the arguments passed - * to the bound function. + * Adds a given value to the corresponding cache object. * * @private - * @param {Function|String} func The function to bind or the method name. - * @param {Mixed} [thisArg] The `this` binding of `func`. - * @param {Array} partialArgs An array of arguments to be partially applied. - * @returns {Function} Returns the new bound function. + * @param {*} value The value to add to the cache. */ - function createBound(func, thisArg, partialArgs) { - var isFunc = isFunction(func), - isPartial = !partialArgs, - key = thisArg; + function cachePush(value) { + var cache = this.cache, + type = typeof value; - // juggle arguments - if (isPartial) { - partialArgs = thisArg; - } - if (!isFunc) { - thisArg = func; - } - - function bound() { - // `Function#bind` spec - // http://es5.github.com/#x15.3.4.5 - var args = arguments, - thisBinding = isPartial ? this : thisArg; - - if (!isFunc) { - func = thisArg[key]; - } - if (partialArgs.length) { - args = args.length - ? partialArgs.concat(slice(args)) - : partialArgs; + if (type == 'boolean' || value == null) { + cache[value] = true; + } else { + if (type != 'number' && type != 'string') { + type = 'object'; } - if (this instanceof bound) { - // ensure `new bound` is an instance of `bound` and `func` - noop.prototype = func.prototype; - thisBinding = new noop; - noop.prototype = null; + var key = type == 'number' ? value : keyPrefix + value, + typeCache = cache[type] || (cache[type] = {}); - // mimic the constructor's `return` behavior - // http://es5.github.com/#x13.2.2 - var result = func.apply(thisBinding, args); - return isObject(result) ? result : thisBinding; + if (type == 'object') { + (typeCache[key] || (typeCache[key] = [])).push(value); + } else { + typeCache[key] = true; } - return func.apply(thisBinding, args); } - return bound; } /** - * Produces an iteration callback bound to an optional `thisArg`. If `func` is - * a property name, the callback will return the property value for a given element. + * Creates a cache object to optimize linear searches of large arrays. * * @private - * @param {Function|String} [func=identity|property] The function called per - * iteration or property name to query. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @param {Object} [accumulating] Used to indicate that the callback should - * accept an `accumulator` argument. - * @returns {Function} Returns a callback function. + * @param {Array} [array=[]] The array to search. + * @returns {null|Object} Returns the cache object or `null` if caching should not be used. */ - function createCallback(func, thisArg, accumulating) { - if (!func) { - return identity; - } - if (typeof func != 'function') { - return function(object) { - return object[func]; - }; - } - if (typeof thisArg != 'undefined') { - if (accumulating) { - return function(accumulator, value, index, object) { - return func.call(thisArg, accumulator, value, index, object); - }; - } - return function(value, index, object) { - return func.call(thisArg, value, index, object); - }; + function createCache(array) { + var index = -1, + length = array.length, + first = array[0], + mid = array[(length / 2) | 0], + last = array[length - 1]; + + if (first && typeof first == 'object' && + mid && typeof mid == 'object' && last && typeof last == 'object') { + return false; } - return func; - } + var cache = getObject(); + cache['false'] = cache['null'] = cache['true'] = cache['undefined'] = false; - /** - * Creates compiled iteration functions. - * - * @private - * @param {Object} [options1, options2, ...] The compile options object(s). - * useHas - A boolean to specify using `hasOwnProperty` checks in the object loop. - * args - A string of comma separated arguments the iteration function will accept. - * top - A string of code to execute before the iteration branches. - * arrayLoop - A string of code to execute in the array loop. - * objectLoop - A string of code to execute in the object loop. - * bottom - A string of code to execute after the iteration branches. - * - * @returns {Function} Returns the compiled function. - */ - function createIterator() { - var data = { - 'arrayLoop': '', - 'bottom': '', - 'hasDontEnumBug': hasDontEnumBug, - 'isKeysFast': isKeysFast, - 'objectLoop': '', - 'nonEnumArgs': nonEnumArgs, - 'noCharByIndex': noCharByIndex, - 'shadowed': shadowed, - 'top': '', - 'useHas': true - }; + var result = getObject(); + result.array = array; + result.cache = cache; + result.push = cachePush; - // merge options into a template data object - for (var object, index = 0; object = arguments[index]; index++) { - for (var key in object) { - data[key] = object[key]; - } + while (++index < length) { + result.push(array[index]); } - var args = data.args; - data.firstArg = /^[^,]+/.exec(args)[0]; - - // create the function factory - var factory = Function( - 'createCallback, hasOwnProperty, isArguments, isString, objectTypes, ' + - 'nativeKeys, propertyIsEnumerable', - 'return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' - ); - // return the compiled function - return factory( - createCallback, hasOwnProperty, isArguments, isString, objectTypes, - nativeKeys, propertyIsEnumerable - ); + return result; } /** - * A function compiled to iterate `arguments` objects, arrays, objects, and - * strings consistenly across environments, executing the `callback` for each - * element in the `collection`. The `callback` is bound to `thisArg` and invoked - * with three arguments; (value, index|key, collection). Callbacks may exit - * iteration early by explicitly returning `false`. - * - * @private - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Array|Object|String} Returns `collection`. - */ - var each = createIterator(eachIteratorOptions); - - /** - * Used by `template` to escape characters for inclusion in compiled - * string literals. + * Gets an array from the array pool or creates a new one if the pool is empty. * * @private - * @param {String} match The matched character to escape. - * @returns {String} Returns the escaped character. + * @returns {Array} The array from the pool. */ - function escapeStringChar(match) { - return '\\' + stringEscapes[match]; + function getArray() { + return arrayPool.pop() || []; } /** - * Used by `escape` to convert characters to HTML entities. + * Gets an object from the object pool or creates a new one if the pool is empty. * * @private - * @param {String} match The matched character to escape. - * @returns {String} Returns the escaped character. + * @returns {Object} The object from the pool. */ - function escapeHtmlChar(match) { - return htmlEscapes[match]; + function getObject() { + return objectPool.pop() || { + 'array': null, + 'cache': null, + 'false': false, + 'null': false, + 'number': null, + 'object': null, + 'push': null, + 'string': null, + 'true': false, + 'undefined': false + }; } /** * Checks if `value` is a DOM node in IE < 9. * * @private - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a DOM node, else `false`. + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is a DOM node, else `false`. */ function isNode(value) { // IE < 9 presents DOM nodes as `Object` objects except they have `toString` @@ -7020,25 +7335,46 @@ var JXON = new (function () { } /** - * A no-operation function. + * Releases the given array back to the array pool. * * @private + * @param {Array} [array] The array to release. */ - function noop() { - // no operation performed + function releaseArray(array) { + array.length = 0; + if (arrayPool.length < maxPoolSize) { + arrayPool.push(array); + } + } + + /** + * Releases the given object back to the object pool. + * + * @private + * @param {Object} [object] The object to release. + */ + function releaseObject(object) { + var cache = object.cache; + if (cache) { + releaseObject(cache); + } + object.array = object.cache =object.object = object.number = object.string =null; + if (objectPool.length < maxPoolSize) { + objectPool.push(object); + } } /** * Slices the `collection` from the `start` index up to, but not including, * the `end` index. * - * Note: This function is used, instead of `Array#slice`, to support node lists + * Note: This function is used instead of `Array#slice` to support node lists * in IE < 9 and to ensure dense arrays are returned. * * @private - * @param {Array|Object|String} collection The collection to slice. - * @param {Number} start The start index. - * @param {Number} end The end index. + * @param {Array|Object|string} collection The collection to slice. + * @param {number} start The start index. + * @param {number} end The end index. * @returns {Array} Returns the new array. */ function slice(array, start, end) { @@ -7056,618 +7392,862 @@ var JXON = new (function () { return result; } + /*--------------------------------------------------------------------------*/ + /** - * Used by `unescape` to convert HTML entities to characters. + * Used for `Array` method references. * - * @private - * @param {String} match The matched character to unescape. - * @returns {String} Returns the unescaped character. + * Normally `Array.prototype` would suffice, however, using an array literal + * avoids issues in Narwhal. */ - function unescapeHtmlChar(match) { - return htmlUnescapes[match]; - } + var arrayRef = []; + + /** Used for native method references */ + var errorProto = Error.prototype, + objectProto = Object.prototype, + stringProto = String.prototype; + + /** Used to resolve the internal [[Class]] of values */ + var toString = objectProto.toString; + + /** Used to detect if a method is native */ + var reNative = RegExp('^' + + String(toString) + .replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + .replace(/toString| for [^\]]+/g, '.*?') + '$' + ); + + /** Native method shortcuts */ + var fnToString = Function.prototype.toString, + getPrototypeOf = reNative.test(getPrototypeOf = Object.getPrototypeOf) && getPrototypeOf, + hasOwnProperty = objectProto.hasOwnProperty, + now = reNative.test(now = Date.now) && now || function() { return +new Date; }, + push = arrayRef.push, + propertyIsEnumerable = objectProto.propertyIsEnumerable; + + /** Used to set meta data on functions */ + var defineProperty = (function() { + // IE 8 only accepts DOM elements + try { + var o = {}, + func = reNative.test(func = Object.defineProperty) && func, + result = func(o, o, o) && func; + } catch(e) { } + return result; + }()); + + /* Native method shortcuts for methods with the same name as other `lodash` methods */ + var nativeCreate = reNative.test(nativeCreate = Object.create) && nativeCreate, + nativeIsArray = reNative.test(nativeIsArray = Array.isArray) && nativeIsArray, + nativeKeys = reNative.test(nativeKeys = Object.keys) && nativeKeys, + nativeMax = Math.max, + nativeMin = Math.min; + + /** Used to lookup a built-in constructor by [[Class]] */ + var ctorByClass = {}; + ctorByClass[arrayClass] = Array; + ctorByClass[boolClass] = Boolean; + ctorByClass[dateClass] = Date; + ctorByClass[funcClass] = Function; + ctorByClass[objectClass] = Object; + ctorByClass[numberClass] = Number; + ctorByClass[regexpClass] = RegExp; + ctorByClass[stringClass] = String; + + /** Used to avoid iterating non-enumerable properties in IE < 9 */ + var nonEnumProps = {}; + nonEnumProps[arrayClass] = nonEnumProps[dateClass] = nonEnumProps[numberClass] = { 'constructor': true, 'toLocaleString': true, 'toString': true, 'valueOf': true }; + nonEnumProps[boolClass] = nonEnumProps[stringClass] = { 'constructor': true, 'toString': true, 'valueOf': true }; + nonEnumProps[errorClass] = nonEnumProps[funcClass] = nonEnumProps[regexpClass] = { 'constructor': true, 'toString': true }; + nonEnumProps[objectClass] = { 'constructor': true }; + + (function() { + var length = shadowedProps.length; + while (length--) { + var key = shadowedProps[length]; + for (var className in nonEnumProps) { + if (hasOwnProperty.call(nonEnumProps, className) && !hasOwnProperty.call(nonEnumProps[className], key)) { + nonEnumProps[className][key] = false; + } + } + } + }()); /*--------------------------------------------------------------------------*/ /** - * Assigns own enumerable properties of source object(s) to the `destination` - * object. Subsequent sources will overwrite propery assignments of previous - * sources. + * Creates a `lodash` object which wraps the given value to enable intuitive + * method chaining. * - * @static - * @memberOf _ - * @alias extend - * @category Objects - * @param {Object} object The destination object. - * @param {Object} [source1, source2, ...] The source objects. - * @returns {Object} Returns the destination object. - * @example + * In addition to Lo-Dash methods, wrappers also have the following `Array` methods: + * `concat`, `join`, `pop`, `push`, `reverse`, `shift`, `slice`, `sort`, `splice`, + * and `unshift` * - * _.assign({ 'name': 'moe' }, { 'age': 40 }); - * // => { 'name': 'moe', 'age': 40 } - */ - var assign = createIterator(assignIteratorOptions); - - /** - * Checks if `value` is an `arguments` object. + * Chaining is supported in custom builds as long as the `value` method is + * implicitly or explicitly included in the build. * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is an `arguments` object, else `false`. - * @example + * The chainable wrapper functions are: + * `after`, `assign`, `bind`, `bindAll`, `bindKey`, `chain`, `compact`, + * `compose`, `concat`, `countBy`, `create`, `createCallback`, `curry`, + * `debounce`, `defaults`, `defer`, `delay`, `difference`, `filter`, `flatten`, + * `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, `forOwnRight`, + * `functions`, `groupBy`, `indexBy`, `initial`, `intersection`, `invert`, + * `invoke`, `keys`, `map`, `max`, `memoize`, `merge`, `min`, `object`, `omit`, + * `once`, `pairs`, `partial`, `partialRight`, `pick`, `pluck`, `pull`, `push`, + * `range`, `reject`, `remove`, `rest`, `reverse`, `shuffle`, `slice`, `sort`, + * `sortBy`, `splice`, `tap`, `throttle`, `times`, `toArray`, `transform`, + * `union`, `uniq`, `unshift`, `unzip`, `values`, `where`, `without`, `wrap`, + * and `zip` * - * (function() { return _.isArguments(arguments); })(1, 2, 3); - * // => true + * The non-chainable wrapper functions are: + * `clone`, `cloneDeep`, `contains`, `escape`, `every`, `find`, `findIndex`, + * `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `has`, `identity`, + * `indexOf`, `isArguments`, `isArray`, `isBoolean`, `isDate`, `isElement`, + * `isEmpty`, `isEqual`, `isFinite`, `isFunction`, `isNaN`, `isNull`, `isNumber`, + * `isObject`, `isPlainObject`, `isRegExp`, `isString`, `isUndefined`, `join`, + * `lastIndexOf`, `mixin`, `noConflict`, `parseInt`, `pop`, `random`, `reduce`, + * `reduceRight`, `result`, `shift`, `size`, `some`, `sortedIndex`, `runInContext`, + * `template`, `unescape`, `uniqueId`, and `value` * - * _.isArguments([1, 2, 3]); - * // => false - */ - function isArguments(value) { - return toString.call(value) == argsClass; - } - // fallback for browsers that can't detect `arguments` objects by [[Class]] - if (noArgsClass) { - isArguments = function(value) { - return value ? hasOwnProperty.call(value, 'callee') : false; - }; - } - - /** - * Iterates over `object`'s own and inherited enumerable properties, executing - * the `callback` for each property. The `callback` is bound to `thisArg` and - * invoked with three arguments; (value, key, object). Callbacks may exit iteration - * early by explicitly returning `false`. + * The wrapper functions `first` and `last` return wrapped values when `n` is + * provided, otherwise they return unwrapped values. * - * @static - * @memberOf _ - * @category Objects - * @param {Object} object The object to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Object} Returns `object`. + * Explicit chaining can be enabled by using the `_.chain` method. + * + * @name _ + * @constructor + * @category Chaining + * @param {*} value The value to wrap in a `lodash` instance. + * @returns {Object} Returns a `lodash` instance. * @example * - * function Dog(name) { - * this.name = name; - * } + * var wrapped = _([1, 2, 3]); * - * Dog.prototype.bark = function() { - * alert('Woof, woof!'); - * }; + * // returns an unwrapped value + * wrapped.reduce(function(sum, num) { + * return sum + num; + * }); + * // => 6 * - * _.forIn(new Dog('Dagny'), function(value, key) { - * alert(key); + * // returns a wrapped value + * var squares = wrapped.map(function(num) { + * return num * num; * }); - * // => alerts 'name' and 'bark' (order is not guaranteed) - */ - var forIn = createIterator(eachIteratorOptions, forOwnIteratorOptions, { - 'useHas': false - }); - - /** - * Iterates over an object's own enumerable properties, executing the `callback` - * for each property. The `callback` is bound to `thisArg` and invoked with three - * arguments; (value, key, object). Callbacks may exit iteration early by explicitly - * returning `false`. * - * @static - * @memberOf _ - * @category Objects - * @param {Object} object The object to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Object} Returns `object`. - * @example + * _.isArray(squares); + * // => false * - * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) { - * alert(key); - * }); - * // => alerts '0', '1', and 'length' (order is not guaranteed) + * _.isArray(squares.value()); + * // => true */ - var forOwn = createIterator(eachIteratorOptions, forOwnIteratorOptions); + function lodash(value) { + // don't wrap if already wrapped, even if wrapped by a different `lodash` constructor + return (value && typeof value == 'object' && !isArray(value) && hasOwnProperty.call(value, '__wrapped__')) + ? value + : new lodashWrapper(value); + } /** - * A fallback implementation of `isPlainObject` that checks if a given `value` - * is an object created by the `Object` constructor, assuming objects created - * by the `Object` constructor have no inherited enumerable properties and that - * there are no `Object.prototype` extensions. + * A fast path for creating `lodash` wrapper objects. * * @private - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if `value` is a plain object, else `false`. + * @param {*} value The value to wrap in a `lodash` instance. + * @param {boolean} chainAll A flag to enable chaining for all methods + * @returns {Object} Returns a `lodash` instance. */ - function shimIsPlainObject(value) { - // avoid non-objects and false positives for `arguments` objects - var result = false; - if (!(value && typeof value == 'object') || isArguments(value)) { - return result; - } - // check that the constructor is `Object` (i.e. `Object instanceof Object`) - var ctor = value.constructor; - if ((!isFunction(ctor) && (!noNodeClass || !isNode(value))) || ctor instanceof ctor) { - // 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. - if (iteratesOwnLast) { - forIn(value, function(value, key, object) { - result = !hasOwnProperty.call(object, key); - return false; - }); - return result === false; - } - // 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. - forIn(value, function(value, key) { - result = key; - }); - return result === false || hasOwnProperty.call(value, result); - } - return result; + function lodashWrapper(value, chainAll) { + this.__chain__ = !!chainAll; + this.__wrapped__ = value; } + // ensure `new lodashWrapper` is an instance of `lodash` + lodashWrapper.prototype = lodash.prototype; /** - * A fallback implementation of `Object.keys` that produces an array of the - * given object's own enumerable property names. + * An object used to flag environments features. * - * @private - * @param {Object} object The object to inspect. - * @returns {Array} Returns a new array of property names. + * @static + * @memberOf _ + * @type Object */ - function shimKeys(object) { - var result = []; - forOwn(object, function(value, key) { - result.push(key); - }); - return result; - } + var support = lodash.support = {}; + + (function() { + var ctor = function() { this.x = 1; }, + object = { '0': 1, 'length': 1 }, + props = []; + + ctor.prototype = { 'valueOf': 1, 'y': 1 }; + for (var key in new ctor) { props.push(key); } + for (key in arguments) { } + + /** + * Detect if an `arguments` object's [[Class]] is resolvable (all but Firefox < 4, IE < 9). + * + * @memberOf _.support + * @type boolean + */ + support.argsClass = toString.call(arguments) == argsClass; + + /** + * Detect if `arguments` objects are `Object` objects (all but Narwhal and Opera < 10.5). + * + * @memberOf _.support + * @type boolean + */ + support.argsObject = arguments.constructor == Object && !(arguments instanceof Array); + + /** + * Detect if `name` or `message` properties of `Error.prototype` are + * enumerable by default. (IE < 9, Safari < 5.1) + * + * @memberOf _.support + * @type boolean + */ + support.enumErrorProps = propertyIsEnumerable.call(errorProto, 'message') || propertyIsEnumerable.call(errorProto, 'name'); + + /** + * Detect if `prototype` properties are enumerable by default. + * + * Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1 + * (if the prototype or a property on the prototype has been set) + * incorrectly sets a function's `prototype` property [[Enumerable]] + * value to `true`. + * + * @memberOf _.support + * @type boolean + */ + support.enumPrototypes = propertyIsEnumerable.call(ctor, 'prototype'); + + /** + * Detect if functions can be decompiled by `Function#toString` + * (all but PS3 and older Opera mobile browsers & avoided in Windows 8 apps). + * + * @memberOf _.support + * @type boolean + */ + support.funcDecomp = !reNative.test(root.WinRTError) && reThis.test(function() { return this; }); + + /** + * Detect if `Function#name` is supported (all but IE). + * + * @memberOf _.support + * @type boolean + */ + support.funcNames = typeof Function.name == 'string'; + + /** + * Detect if `arguments` object indexes are non-enumerable + * (Firefox < 4, IE < 9, PhantomJS, Safari < 5.1). + * + * @memberOf _.support + * @type boolean + */ + support.nonEnumArgs = key != 0; + + /** + * Detect if properties shadowing those on `Object.prototype` are non-enumerable. + * + * In IE < 9 an objects own properties, shadowing non-enumerable ones, are + * made non-enumerable as well (a.k.a the JScript [[DontEnum]] bug). + * + * @memberOf _.support + * @type boolean + */ + support.nonEnumShadows = !/valueOf/.test(props); + + /** + * Detect if own properties are iterated after inherited properties (all but IE < 9). + * + * @memberOf _.support + * @type boolean + */ + support.ownLast = props[0] != 'x'; + + /** + * Detect if `Array#shift` and `Array#splice` augment array-like objects correctly. + * + * Firefox < 10, IE compatibility mode, and IE < 9 have buggy Array `shift()` + * and `splice()` functions that fail to remove the last element, `value[0]`, + * of array-like objects even though the `length` property is set to `0`. + * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()` + * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9. + * + * @memberOf _.support + * @type boolean + */ + support.spliceObjects = (arrayRef.splice.call(object, 0, 1), !object[0]); + + /** + * Detect lack of support for accessing string characters by index. + * + * IE < 8 can't access characters by index and IE 8 can only access + * characters by index on string literals. + * + * @memberOf _.support + * @type boolean + */ + support.unindexedChars = ('x'[0] + Object('x')[0]) != 'xx'; + + /** + * Detect if a DOM node's [[Class]] is resolvable (all but IE < 9) + * and that the JS engine errors when attempting to coerce an object to + * a string without a `toString` function. + * + * @memberOf _.support + * @type boolean + */ + try { + support.nodeClass = !(toString.call(document) == objectClass && !({ 'toString': 0 } + '')); + } catch(e) { + support.nodeClass = true; + } + }(1)); + + /*--------------------------------------------------------------------------*/ /** - * Used to convert characters to HTML entities: + * The template used to create iterator functions. * - * Though the `>` character is escaped for symmetry, characters like `>` and `/` - * don't require escaping in HTML and have no special meaning unless they're part - * of a tag or an unquoted attribute value. - * http://mathiasbynens.be/notes/ambiguous-ampersands (under "semi-related fun fact") + * @private + * @param {Object} data The data object used to populate the text. + * @returns {string} Returns the interpolated text. */ - var htmlEscapes = { - '&': '&', - '<': '<', - '>': '>', - '"': '"', - "'": ''' - }; + var iteratorTemplate = function(obj) { + + var __p = 'var index, iterable = ' + + (obj.firstArg) + + ', result = ' + + (obj.init) + + ';\nif (!iterable) return result;\n' + + (obj.top) + + ';'; + if (obj.array) { + __p += '\nvar length = iterable.length; index = -1;\nif (' + + (obj.array) + + ') { '; + if (support.unindexedChars) { + __p += '\n if (isString(iterable)) {\n iterable = iterable.split(\'\')\n } '; + } + __p += '\n while (++index < length) {\n ' + + (obj.loop) + + ';\n }\n}\nelse { '; + } else if (support.nonEnumArgs) { + __p += '\n var length = iterable.length; index = -1;\n if (length && isArguments(iterable)) {\n while (++index < length) {\n index += \'\';\n ' + + (obj.loop) + + ';\n }\n } else { '; + } + + if (support.enumPrototypes) { + __p += '\n var skipProto = typeof iterable == \'function\';\n '; + } + + if (support.enumErrorProps) { + __p += '\n var skipErrorProps = iterable === errorProto || iterable instanceof Error;\n '; + } + + var conditions = []; if (support.enumPrototypes) { conditions.push('!(skipProto && index == "prototype")'); } if (support.enumErrorProps) { conditions.push('!(skipErrorProps && (index == "message" || index == "name"))'); } + + if (obj.useHas && obj.keys) { + __p += '\n var ownIndex = -1,\n ownProps = objectTypes[typeof iterable] && keys(iterable),\n length = ownProps ? ownProps.length : 0;\n\n while (++ownIndex < length) {\n index = ownProps[ownIndex];\n'; + if (conditions.length) { + __p += ' if (' + + (conditions.join(' && ')) + + ') {\n '; + } + __p += + (obj.loop) + + '; '; + if (conditions.length) { + __p += '\n }'; + } + __p += '\n } '; + } else { + __p += '\n for (index in iterable) {\n'; + if (obj.useHas) { conditions.push("hasOwnProperty.call(iterable, index)"); } if (conditions.length) { + __p += ' if (' + + (conditions.join(' && ')) + + ') {\n '; + } + __p += + (obj.loop) + + '; '; + if (conditions.length) { + __p += '\n }'; + } + __p += '\n } '; + if (support.nonEnumShadows) { + __p += '\n\n if (iterable !== objectProto) {\n var ctor = iterable.constructor,\n isProto = iterable === (ctor && ctor.prototype),\n className = iterable === stringProto ? stringClass : iterable === errorProto ? errorClass : toString.call(iterable),\n nonEnum = nonEnumProps[className];\n '; + for (k = 0; k < 7; k++) { + __p += '\n index = \'' + + (obj.shadowedProps[k]) + + '\';\n if ((!(isProto && nonEnum[index]) && hasOwnProperty.call(iterable, index))'; + if (!obj.useHas) { + __p += ' || (!nonEnum[index] && iterable[index] !== objectProto[index])'; + } + __p += ') {\n ' + + (obj.loop) + + ';\n } '; + } + __p += '\n } '; + } + + } - /** Used to convert HTML entities to characters */ - var htmlUnescapes = invert(htmlEscapes); + if (obj.array || support.nonEnumArgs) { + __p += '\n}'; + } + __p += + (obj.bottom) + + ';\nreturn result'; + + return __p + }; /*--------------------------------------------------------------------------*/ /** - * Creates a clone of `value`. If `deep` is `true`, nested objects will also - * be cloned, otherwise they will be assigned by reference. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to clone. - * @param {Boolean} deep A flag to indicate a deep clone. - * @param- {Object} [guard] Internally used to allow this method to work with - * others like `_.map` without using their callback `index` argument for `deep`. - * @param- {Array} [stackA=[]] Internally used to track traversed source objects. - * @param- {Array} [stackB=[]] Internally used to associate clones with their - * source counterparts. - * @returns {Mixed} Returns the cloned `value`. - * @example - * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 }, - * { 'name': 'curly', 'age': 60 } - * ]; - * - * var shallow = _.clone(stooges); - * shallow[0] === stooges[0]; - * // => true + * The base implementation of `_.bind` that creates the bound function and + * sets its meta data. * - * var deep = _.clone(stooges, true); - * deep[0] === stooges[0]; - * // => false + * @private + * @param {Array} bindData The bind data array. + * @returns {Function} Returns the new bound function. */ - function clone(value, deep, guard, stackA, stackB) { - if (value == null) { - return value; + function baseBind(bindData) { + var func = bindData[0], + partialArgs = bindData[2], + thisArg = bindData[4]; + + function bound() { + // `Function#bind` spec + // http://es5.github.io/#x15.3.4.5 + if (partialArgs) { + var args = partialArgs.slice(); + push.apply(args, arguments); + } + // mimic the constructor's `return` behavior + // http://es5.github.io/#x13.2.2 + if (this instanceof bound) { + // ensure `new bound` is an instance of `func` + var thisBinding = baseCreate(func.prototype), + result = func.apply(thisBinding, args || arguments); + return isObject(result) ? result : thisBinding; + } + return func.apply(thisArg, args || arguments); } - if (guard) { - deep = false; + setBindData(bound, bindData); + return bound; + } + + /** + * The base implementation of `_.clone` without argument juggling or support + * for `thisArg` binding. + * + * @private + * @param {*} value The value to clone. + * @param {boolean} [isDeep=false] Specify a deep clone. + * @param {Function} [callback] The function to customize cloning values. + * @param {Array} [stackA=[]] Tracks traversed source objects. + * @param {Array} [stackB=[]] Associates clones with source counterparts. + * @returns {*} Returns the cloned value. + */ + function baseClone(value, isDeep, callback, stackA, stackB) { + if (callback) { + var result = callback(value); + if (typeof result != 'undefined') { + return result; + } } // inspect [[Class]] var isObj = isObject(value); if (isObj) { var className = toString.call(value); - if (!cloneableClasses[className] || (noNodeClass && isNode(value))) { + if (!cloneableClasses[className] || (!support.nodeClass && isNode(value))) { return value; } - var isArr = isArray(value); - } - // shallow clone - if (!isObj || !deep) { - return isObj - ? (isArr ? slice(value) : assign({}, value)) - : value; + var ctor = ctorByClass[className]; + switch (className) { + case boolClass: + case dateClass: + return new ctor(+value); + + case numberClass: + case stringClass: + return new ctor(value); + + case regexpClass: + result = ctor(value.source, reFlags.exec(value)); + result.lastIndex = value.lastIndex; + return result; + } + } else { + return value; } - var ctor = ctorByClass[className]; - switch (className) { - case boolClass: - case dateClass: - return new ctor(+value); - - case numberClass: - case stringClass: - return new ctor(value); + var isArr = isArray(value); + if (isDeep) { + // check for circular references and return corresponding clone + var initedStack = !stackA; + stackA || (stackA = getArray()); + stackB || (stackB = getArray()); - case regexpClass: - return ctor(value.source, reFlags.exec(value)); + var length = stackA.length; + while (length--) { + if (stackA[length] == value) { + return stackB[length]; + } + } + result = isArr ? ctor(value.length) : {}; } - // check for circular references and return corresponding clone - stackA || (stackA = []); - stackB || (stackB = []); - - var length = stackA.length; - while (length--) { - if (stackA[length] == value) { - return stackB[length]; + else { + result = isArr ? slice(value) : assign({}, value); + } + // add array properties assigned by `RegExp#exec` + if (isArr) { + if (hasOwnProperty.call(value, 'index')) { + result.index = value.index; + } + if (hasOwnProperty.call(value, 'input')) { + result.input = value.input; } } - // init cloned object - var result = isArr ? ctor(value.length) : {}; - + // exit for shallow clone + if (!isDeep) { + return result; + } // 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 ? forEach : forOwn)(value, function(objValue, key) { - result[key] = clone(objValue, deep, null, stackA, stackB); + (isArr ? baseEach : forOwn)(value, function(objValue, key) { + result[key] = baseClone(objValue, isDeep, callback, stackA, stackB); }); - // add array properties assigned by `RegExp#exec` - if (isArr) { - if (hasOwnProperty.call(value, 'index')) { - result.index = value.index; - } - if (hasOwnProperty.call(value, 'input')) { - result.input = value.input; - } + if (initedStack) { + releaseArray(stackA); + releaseArray(stackB); } return result; } /** - * Creates a deep clone of `value`. Functions and DOM nodes are **not** cloned. - * The enumerable properties of `arguments` objects and objects created by - * constructors other than `Object` are cloned to plain `Object` objects. - * - * Note: This function is loosely based on the structured clone algorithm. - * See http://www.w3.org/TR/html5/common-dom-interfaces.html#internal-structured-cloning-algorithm. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to deep clone. - * @returns {Mixed} Returns the deep cloned `value`. - * @example - * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 }, - * { 'name': 'curly', 'age': 60 } - * ]; + * The base implementation of `_.create` without support for assigning + * properties to the created object. * - * var deep = _.cloneDeep(stooges); - * deep[0] === stooges[0]; - * // => false + * @private + * @param {Object} prototype The object to inherit from. + * @returns {Object} Returns the new object. */ - function cloneDeep(value) { - return clone(value, true); + function baseCreate(prototype, properties) { + return isObject(prototype) ? nativeCreate(prototype) : {}; + } + // fallback for browsers without `Object.create` + if (!nativeCreate) { + baseCreate = (function() { + function Object() {} + return function(prototype) { + if (isObject(prototype)) { + Object.prototype = prototype; + var result = new Object; + Object.prototype = null; + } + return result || root.Object(); + }; + }()); } /** - * Assigns own enumerable properties of source object(s) to the `destination` - * object for all `destination` properties that resolve to `null`/`undefined`. - * Once a property is set, additional defaults of the same property will be - * ignored. - * - * @static - * @memberOf _ - * @category Objects - * @param {Object} object The destination object. - * @param {Object} [default1, default2, ...] The default objects. - * @returns {Object} Returns the destination object. - * @example - * - * var iceCream = { 'flavor': 'chocolate' }; - * _.defaults(iceCream, { 'flavor': 'vanilla', 'sprinkles': 'rainbow' }); - * // => { 'flavor': 'chocolate', 'sprinkles': 'rainbow' } - */ - var defaults = createIterator(assignIteratorOptions, { - 'objectLoop': 'if (result[index] == null) ' + assignIteratorOptions.objectLoop - }); - - /** - * Creates a sorted array of all enumerable properties, own and inherited, - * of `object` that have function values. - * - * @static - * @memberOf _ - * @alias methods - * @category Objects - * @param {Object} object The object to inspect. - * @returns {Array} Returns a new array of property names that have function values. - * @example + * The base implementation of `_.createCallback` without support for creating + * "_.pluck" or "_.where" style callbacks. * - * _.functions(_); - * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...] + * @private + * @param {*} [func=identity] The value to convert to a callback. + * @param {*} [thisArg] The `this` binding of the created callback. + * @param {number} [argCount] The number of arguments the callback accepts. + * @returns {Function} Returns a callback function. */ - function functions(object) { - var result = []; - forIn(object, function(value, key) { - if (isFunction(value)) { - result.push(key); + function baseCreateCallback(func, thisArg, argCount) { + if (typeof func != 'function') { + return identity; + } + // exit early for no `thisArg` or already bound by `Function#bind` + if (typeof thisArg == 'undefined' || !('prototype' in func)) { + return func; + } + var bindData = func.__bindData__; + if (typeof bindData == 'undefined') { + if (support.funcNames) { + bindData = !func.name; } - }); - return result.sort(); + bindData = bindData || !support.funcDecomp; + if (!bindData) { + var source = fnToString.call(func); + if (!support.funcNames) { + bindData = !reFuncName.test(source); + } + if (!bindData) { + // checks if `func` references the `this` keyword and stores the result + bindData = reThis.test(source); + setBindData(func, bindData); + } + } + } + // exit early if there are no `this` references or `func` is bound + if (bindData === false || (bindData !== true && bindData[1] & 1)) { + return func; + } + switch (argCount) { + case 1: return function(value) { + return func.call(thisArg, value); + }; + case 2: return function(a, b) { + return func.call(thisArg, a, b); + }; + 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); + }; + } + return bind(func, thisArg); } /** - * Checks if the specified object `property` exists and is a direct property, - * instead of an inherited property. - * - * @static - * @memberOf _ - * @category Objects - * @param {Object} object The object to check. - * @param {String} property The property to check for. - * @returns {Boolean} Returns `true` if key is a direct property, else `false`. - * @example + * The base implementation of `createWrapper` that creates the wrapper and + * sets its meta data. * - * _.has({ 'a': 1, 'b': 2, 'c': 3 }, 'b'); - * // => true + * @private + * @param {Array} bindData The bind data array. + * @returns {Function} Returns the new function. */ - function has(object, property) { - return object ? hasOwnProperty.call(object, property) : false; - } + function baseCreateWrapper(bindData) { + var func = bindData[0], + bitmask = bindData[1], + partialArgs = bindData[2], + partialRightArgs = bindData[3], + thisArg = bindData[4], + arity = bindData[5]; + + var isBind = bitmask & 1, + isBindKey = bitmask & 2, + isCurry = bitmask & 4, + isCurryBound = bitmask & 8, + key = func; - /** - * Creates an object composed of the inverted keys and values of the given `object`. - * - * @static - * @memberOf _ - * @category Objects - * @param {Object} object The object to invert. - * @returns {Object} Returns the created inverted object. - * @example - * - * _.invert({ 'first': 'Moe', 'second': 'Larry', 'third': 'Curly' }); - * // => { 'Moe': 'first', 'Larry': 'second', 'Curly': 'third' } (order is not guaranteed) - */ - function invert(object) { - var result = {}; - forOwn(object, function(value, key) { - result[value] = key; - }); - return result; + function bound() { + var thisBinding = isBind ? thisArg : this; + if (partialArgs) { + var args = partialArgs.slice(); + push.apply(args, arguments); + } + if (partialRightArgs || isCurry) { + args || (args = slice(arguments)); + if (partialRightArgs) { + push.apply(args, partialRightArgs); + } + if (isCurry && args.length < arity) { + bitmask |= 16 & ~32; + return baseCreateWrapper([func, (isCurryBound ? bitmask : bitmask & ~3), args, null, thisArg, arity]); + } + } + args || (args = arguments); + if (isBindKey) { + func = thisBinding[key]; + } + if (this instanceof bound) { + thisBinding = baseCreate(func.prototype); + var result = func.apply(thisBinding, args); + return isObject(result) ? result : thisBinding; + } + return func.apply(thisBinding, args); + } + setBindData(bound, bindData); + return bound; } /** - * Checks if `value` is an array. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is an array, else `false`. - * @example + * The base implementation of `_.difference` that accepts a single array + * of values to exclude. * - * (function() { return _.isArray(arguments); })(); - * // => false - * - * _.isArray([1, 2, 3]); - * // => true + * @private + * @param {Array} array The array to process. + * @param {Array} [values] The array of values to exclude. + * @returns {Array} Returns a new array of filtered values. */ - var isArray = nativeIsArray || function(value) { - // `instanceof` may cause a memory leak in IE 7 if `value` is a host object - // http://ajaxian.com/archives/working-aroung-the-instanceof-memory-leak - return (argsAreObjects && value instanceof Array) || toString.call(value) == arrayClass; - }; + function baseDifference(array, values) { + var index = -1, + indexOf = getIndexOf(), + length = array ? array.length : 0, + isLarge = length >= largeArraySize && indexOf === baseIndexOf, + result = []; - /** - * Checks if `value` is a boolean (`true` or `false`) value. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a boolean value, else `false`. - * @example - * - * _.isBoolean(null); - * // => false - */ - function isBoolean(value) { - return value === true || value === false || toString.call(value) == boolClass; + if (isLarge) { + var cache = createCache(values); + if (cache) { + indexOf = cacheIndexOf; + values = cache; + } else { + isLarge = false; + } + } + while (++index < length) { + var value = array[index]; + if (indexOf(values, value) < 0) { + result.push(value); + } + } + if (isLarge) { + releaseObject(values); + } + return result; } /** - * Checks if `value` is a date. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a date, else `false`. - * @example + * The base implementation of `_.flatten` without support for callback + * shorthands or `thisArg` binding. * - * _.isDate(new Date); - * // => true + * @private + * @param {Array} array The array to flatten. + * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level. + * @param {boolean} [isStrict=false] A flag to restrict flattening to arrays and `arguments` objects. + * @param {number} [fromIndex=0] The index to start from. + * @returns {Array} Returns a new flattened array. */ - function isDate(value) { - return value instanceof Date || toString.call(value) == dateClass; - } + function baseFlatten(array, isShallow, isStrict, fromIndex) { + var index = (fromIndex || 0) - 1, + length = array ? array.length : 0, + result = []; - /** - * Checks if `value` is a DOM element. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a DOM element, else `false`. - * @example - * - * _.isElement(document.body); - * // => true - */ - function isElement(value) { - return value ? value.nodeType === 1 : false; - } + while (++index < length) { + var value = array[index]; - /** - * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a - * length of `0` and objects with no own enumerable properties are considered - * "empty". - * - * @static - * @memberOf _ - * @category Objects - * @param {Array|Object|String} value The value to inspect. - * @returns {Boolean} Returns `true` if the `value` is empty, else `false`. - * @example - * - * _.isEmpty([1, 2, 3]); - * // => false - * - * _.isEmpty({}); - * // => true - * - * _.isEmpty(''); - * // => true - */ - function isEmpty(value) { - var result = true; - if (!value) { - return result; - } - var className = toString.call(value), - length = value.length; + if (value && typeof value == 'object' && typeof value.length == 'number' + && (isArray(value) || isArguments(value))) { + // recursively flatten arrays (susceptible to call stack limits) + if (!isShallow) { + value = baseFlatten(value, isShallow, isStrict); + } + var valIndex = -1, + valLength = value.length, + resIndex = result.length; - if ((className == arrayClass || className == stringClass || - className == argsClass || (noArgsClass && isArguments(value))) || - (className == objectClass && typeof length == 'number' && isFunction(value.splice))) { - return !length; + result.length += valLength; + while (++valIndex < valLength) { + result[resIndex++] = value[valIndex]; + } + } else if (!isStrict) { + result.push(value); + } } - forOwn(value, function() { - return (result = false); - }); return result; } /** - * Performs a deep comparison between two values to determine if they are - * equivalent to each other. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} a The value to compare. - * @param {Mixed} b The other value to compare. - * @param- {Object} [stackA=[]] Internally used track traversed `a` objects. - * @param- {Object} [stackB=[]] Internally used track traversed `b` objects. - * @returns {Boolean} Returns `true` if the values are equvalent, else `false`. - * @example - * - * var moe = { 'name': 'moe', 'luckyNumbers': [13, 27, 34] }; - * var clone = { 'name': 'moe', 'luckyNumbers': [13, 27, 34] }; - * - * moe == clone; - * // => false + * The base implementation of `_.isEqual`, without support for `thisArg` binding, + * that allows partial "_.where" style comparisons. * - * _.isEqual(moe, clone); - * // => true + * @private + * @param {*} a The value to compare. + * @param {*} b The other value to compare. + * @param {Function} [callback] The function to customize comparing values. + * @param {Function} [isWhere=false] A flag to indicate performing partial comparisons. + * @param {Array} [stackA=[]] Tracks traversed `a` objects. + * @param {Array} [stackB=[]] Tracks traversed `b` objects. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. */ - function isEqual(a, b, stackA, stackB) { + function baseIsEqual(a, b, callback, isWhere, stackA, stackB) { + // used to indicate that when comparing objects, `a` has at least the properties of `b` + if (callback) { + var result = callback(a, b); + if (typeof result != 'undefined') { + return !!result; + } + } // exit early for identical values if (a === b) { // treat `+0` vs. `-0` as not equal return a !== 0 || (1 / a == 1 / b); } - // a strict comparison is necessary because `null == undefined` + var type = typeof a, + otherType = typeof b; + + // exit early for unlike primitive values + if (a === a && + !(a && objectTypes[type]) && + !(b && objectTypes[otherType])) { + return false; + } + // exit early for `null` and `undefined` avoiding ES3's Function#call behavior + // http://es5.github.io/#x15.3.4.4 if (a == null || b == null) { return a === b; } // compare [[Class]] names var className = toString.call(a), - otherName = toString.call(b); + otherClass = toString.call(b); if (className == argsClass) { className = objectClass; } - if (otherName == argsClass) { - otherName = objectClass; + if (otherClass == argsClass) { + otherClass = objectClass; } - if (className != otherName) { + if (className != otherClass) { return false; } switch (className) { case boolClass: case dateClass: // coerce dates and booleans to numbers, dates to milliseconds and booleans - // to `1` or `0`, treating invalid dates coerced to `NaN` as not equal + // to `1` or `0` treating invalid dates coerced to `NaN` as not equal return +a == +b; case numberClass: // treat `NaN` vs. `NaN` as equal - return a != +a + return (a != +a) ? b != +b // but treat `+0` vs. `-0` as not equal : (a == 0 ? (1 / a == 1 / b) : a == +b); case regexpClass: case stringClass: - // coerce regexes to strings (http://es5.github.com/#x15.10.6.4) + // coerce regexes to strings (http://es5.github.io/#x15.10.6.4) // treat string primitives and their corresponding object instances as equal - return a == b + ''; + return a == String(b); } var isArr = className == arrayClass; if (!isArr) { // unwrap any `lodash` wrapped values - if (a.__wrapped__ || b.__wrapped__) { - return isEqual(a.__wrapped__ || a, b.__wrapped__ || b); + var aWrapped = hasOwnProperty.call(a, '__wrapped__'), + bWrapped = hasOwnProperty.call(b, '__wrapped__'); + + if (aWrapped || bWrapped) { + return baseIsEqual(aWrapped ? a.__wrapped__ : a, bWrapped ? b.__wrapped__ : b, callback, isWhere, stackA, stackB); } // exit for functions and DOM nodes - if (className != objectClass || (noNodeClass && (isNode(a) || isNode(b)))) { + if (className != objectClass || (!support.nodeClass && (isNode(a) || isNode(b)))) { return false; } // in older versions of Opera, `arguments` objects have `Array` constructors - var ctorA = !argsAreObjects && isArguments(a) ? Object : a.constructor, - ctorB = !argsAreObjects && isArguments(b) ? Object : b.constructor; + var ctorA = !support.argsObject && isArguments(a) ? Object : a.constructor, + ctorB = !support.argsObject && isArguments(b) ? Object : b.constructor; // non `Object` object instances with different constructors are not equal - if (ctorA != ctorB && !( - isFunction(ctorA) && ctorA instanceof ctorA && - isFunction(ctorB) && ctorB instanceof ctorB - )) { + if (ctorA != ctorB && + !(isFunction(ctorA) && ctorA instanceof ctorA && isFunction(ctorB) && ctorB instanceof ctorB) && + ('constructor' in a && 'constructor' in b) + ) { return false; } } // assume cyclic structures are equal // the algorithm for detecting cyclic structures is adapted from ES 5.1 - // section 15.12.3, abstract operation `JO` (http://es5.github.com/#x15.12.3) - stackA || (stackA = []); - stackB || (stackB = []); + // section 15.12.3, abstract operation `JO` (http://es5.github.io/#x15.12.3) + var initedStack = !stackA; + stackA || (stackA = getArray()); + stackB || (stackB = getArray()); var length = stackA.length; while (length--) { @@ -7675,9 +8255,8 @@ var JXON = new (function () { return stackB[length] == b; } } - var index = -1, - result = true, - size = 0; + var size = 0; + result = true; // add `a` and `b` to the stack of traversed objects stackA.push(a); @@ -7685,428 +8264,1080 @@ var JXON = new (function () { // recursively compare objects and arrays (susceptible to call stack limits) if (isArr) { - // compare lengths to determine if a deep comparison is necessary - size = a.length; - result = size == b.length; + length = a.length; + size = b.length; - if (result) { - // deep compare the contents, ignoring non-numeric properties - while (size--) { - if (!(result = isEqual(a[size], b[size], stackA, stackB))) { - break; + // compare lengths to determine if a deep comparison is necessary + result = size == a.length; + if (!result && !isWhere) { + return result; + } + // deep compare the contents, ignoring non-numeric properties + while (size--) { + var index = length, + value = b[size]; + + if (isWhere) { + while (index--) { + if ((result = baseIsEqual(a[index], value, callback, isWhere, stackA, stackB))) { + break; + } } + } else if (!(result = baseIsEqual(a[size], value, callback, isWhere, stackA, stackB))) { + break; } } return result; } // deep compare objects using `forIn`, instead of `forOwn`, to avoid `Object.keys` // which, in this case, is more costly - forIn(a, function(value, key, a) { - if (hasOwnProperty.call(a, key)) { + forIn(b, function(value, key, b) { + if (hasOwnProperty.call(b, key)) { // count the number of properties. size++; // deep compare each property value. - return (result = hasOwnProperty.call(b, key) && isEqual(value, b[key], stackA, stackB)); + return (result = hasOwnProperty.call(a, key) && baseIsEqual(a[key], value, callback, isWhere, stackA, stackB)); } }); - if (result) { + if (result && !isWhere) { // ensure both objects have the same number of properties - forIn(b, function(value, key, b) { - if (hasOwnProperty.call(b, key)) { - // `size` will be `-1` if `b` has more properties than `a` + forIn(a, function(value, key, a) { + if (hasOwnProperty.call(a, key)) { + // `size` will be `-1` if `a` has more properties than `b` return (result = --size > -1); } }); } + if (initedStack) { + releaseArray(stackA); + releaseArray(stackB); + } return result; } /** - * Checks if `value` is, or can be coerced to, a finite number. - * - * Note: This is not the same as native `isFinite`, which will return true for - * booleans and empty strings. See http://es5.github.com/#x15.1.2.5. - * - * @static - * @memberOf _ - * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a finite number, else `false`. - * @example - * - * _.isFinite(-101); - * // => true - * - * _.isFinite('10'); - * // => true + * The base implementation of `_.merge` without argument juggling or support + * for `thisArg` binding. * - * _.isFinite(true); - * // => false + * @private + * @param {Object} object The destination object. + * @param {Object} source The source object. + * @param {Function} [callback] The function to customize merging properties. + * @param {Array} [stackA=[]] Tracks traversed source objects. + * @param {Array} [stackB=[]] Associates values with source counterparts. + */ + function baseMerge(object, source, callback, stackA, stackB) { + (isArray(source) ? forEach : forOwn)(source, function(source, key) { + var found, + isArr, + result = source, + value = object[key]; + + if (source && ((isArr = isArray(source)) || isPlainObject(source))) { + // avoid merging previously merged cyclic sources + var stackLength = stackA.length; + while (stackLength--) { + if ((found = stackA[stackLength] == source)) { + value = stackB[stackLength]; + break; + } + } + if (!found) { + var isShallow; + if (callback) { + result = callback(value, source); + if ((isShallow = typeof result != 'undefined')) { + value = result; + } + } + if (!isShallow) { + value = isArr + ? (isArray(value) ? value : []) + : (isPlainObject(value) ? value : {}); + } + // add `source` and associated `value` to the stack of traversed objects + stackA.push(source); + stackB.push(value); + + // recursively merge objects and arrays (susceptible to call stack limits) + if (!isShallow) { + baseMerge(value, source, callback, stackA, stackB); + } + } + } + else { + if (callback) { + result = callback(value, source); + if (typeof result == 'undefined') { + result = source; + } + } + if (typeof result != 'undefined') { + value = result; + } + } + object[key] = value; + }); + } + + /** + * The base implementation of `_.uniq` without support for callback shorthands + * or `thisArg` binding. * - * _.isFinite(''); - * // => false + * @private + * @param {Array} array The array to process. + * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted. + * @param {Function} [callback] The function called per iteration. + * @returns {Array} Returns a duplicate-value-free array. + */ + function baseUniq(array, isSorted, callback) { + var index = -1, + indexOf = getIndexOf(), + length = array ? array.length : 0, + result = []; + + var isLarge = !isSorted && length >= largeArraySize && indexOf === baseIndexOf, + seen = (callback || isLarge) ? getArray() : result; + + if (isLarge) { + var cache = createCache(seen); + if (cache) { + indexOf = cacheIndexOf; + seen = cache; + } else { + isLarge = false; + seen = callback ? seen : (releaseArray(seen), result); + } + } + while (++index < length) { + var value = array[index], + computed = callback ? callback(value, index, array) : value; + + if (isSorted + ? !index || seen[seen.length - 1] !== computed + : indexOf(seen, computed) < 0 + ) { + if (callback || isLarge) { + seen.push(computed); + } + result.push(value); + } + } + if (isLarge) { + releaseArray(seen.array); + releaseObject(seen); + } else if (callback) { + releaseArray(seen); + } + return result; + } + + /** + * Creates a function that aggregates a collection, creating an object composed + * of keys generated from the results of running each element of the collection + * through a callback. The given `setter` function sets the keys and values + * of the composed object. * - * _.isFinite(Infinity); - * // => false + * @private + * @param {Function} setter The setter function. + * @returns {Function} Returns the new aggregator function. + */ + function createAggregator(setter) { + return function(collection, callback, thisArg) { + var result = {}; + callback = lodash.createCallback(callback, thisArg, 3); + + if (isArray(collection)) { + var index = -1, + length = collection.length; + + while (++index < length) { + var value = collection[index]; + setter(result, value, callback(value, index, collection), collection); + } + } else { + baseEach(collection, function(value, key, collection) { + setter(result, value, callback(value, key, collection), collection); + }); + } + return result; + }; + } + + /** + * Creates a function that, when called, either curries or invokes `func` + * with an 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 method flags to compose. + * The bitmask may be composed of the following flags: + * 1 - `_.bind` + * 2 - `_.bindKey` + * 4 - `_.curry` + * 8 - `_.curry` (bound) + * 16 - `_.partial` + * 32 - `_.partialRight` + * @param {Array} [partialArgs] An array of arguments to prepend to those + * provided to the new function. + * @param {Array} [partialRightArgs] An array of arguments to append to those + * provided to the new function. + * @param {*} [thisArg] The `this` binding of `func`. + * @param {number} [arity] The arity of `func`. + * @returns {Function} Returns the new function. */ - function isFinite(value) { - return nativeIsFinite(value) && !nativeIsNaN(parseFloat(value)); + function createWrapper(func, bitmask, partialArgs, partialRightArgs, thisArg, arity) { + var isBind = bitmask & 1, + isBindKey = bitmask & 2, + isCurry = bitmask & 4, + isCurryBound = bitmask & 8, + isPartial = bitmask & 16, + isPartialRight = bitmask & 32; + + if (!isBindKey && !isFunction(func)) { + throw new TypeError; + } + if (isPartial && !partialArgs.length) { + bitmask &= ~16; + isPartial = partialArgs = false; + } + if (isPartialRight && !partialRightArgs.length) { + bitmask &= ~32; + isPartialRight = partialRightArgs = false; + } + var bindData = func && func.__bindData__; + if (bindData && bindData !== true) { + bindData = bindData.slice(); + + // set `thisBinding` is not previously bound + if (isBind && !(bindData[1] & 1)) { + bindData[4] = thisArg; + } + // set if previously bound but not currently (subsequent curried functions) + if (!isBind && bindData[1] & 1) { + bitmask |= 8; + } + // set curried arity if not yet set + if (isCurry && !(bindData[1] & 4)) { + bindData[5] = arity; + } + // append partial left arguments + if (isPartial) { + push.apply(bindData[2] || (bindData[2] = []), partialArgs); + } + // append partial right arguments + if (isPartialRight) { + push.apply(bindData[3] || (bindData[3] = []), partialRightArgs); + } + // merge flags + bindData[1] |= bitmask; + return createWrapper.apply(null, bindData); + } + // fast path for `_.bind` + var creater = (bitmask == 1 || bitmask === 17) ? baseBind : baseCreateWrapper; + return creater([func, bitmask, partialArgs, partialRightArgs, thisArg, arity]); } /** - * Checks if `value` is a function. + * Creates compiled iteration functions. + * + * @private + * @param {...Object} [options] The compile options object(s). + * @param {string} [options.array] Code to determine if the iterable is an array or array-like. + * @param {boolean} [options.useHas] Specify using `hasOwnProperty` checks in the object loop. + * @param {Function} [options.keys] A reference to `_.keys` for use in own property iteration. + * @param {string} [options.args] A comma separated string of iteration function arguments. + * @param {string} [options.top] Code to execute before the iteration branches. + * @param {string} [options.loop] Code to execute in the object loop. + * @param {string} [options.bottom] Code to execute after the iteration branches. + * @returns {Function} Returns the compiled function. + */ + function createIterator() { + // data properties + iteratorData.shadowedProps = shadowedProps; + + // iterator options + iteratorData.array = iteratorData.bottom = iteratorData.loop = iteratorData.top = ''; + iteratorData.init = 'iterable'; + iteratorData.useHas = true; + + // merge options into a template data object + for (var object, index = 0; object = arguments[index]; index++) { + for (var key in object) { + iteratorData[key] = object[key]; + } + } + var args = iteratorData.args; + iteratorData.firstArg = /^[^,]+/.exec(args)[0]; + + // create the function factory + var factory = Function( + 'baseCreateCallback, errorClass, errorProto, hasOwnProperty, ' + + 'indicatorObject, isArguments, isArray, isString, keys, objectProto, ' + + 'objectTypes, nonEnumProps, stringClass, stringProto, toString', + 'return function(' + args + ') {\n' + iteratorTemplate(iteratorData) + '\n}' + ); + + // return the compiled function + return factory( + baseCreateCallback, errorClass, errorProto, hasOwnProperty, + indicatorObject, isArguments, isArray, isString, iteratorData.keys, objectProto, + objectTypes, nonEnumProps, stringClass, stringProto, toString + ); + } + + /** + * Gets the appropriate "indexOf" function. If the `_.indexOf` method is + * customized, this method returns the custom method, otherwise it returns + * the `baseIndexOf` function. + * + * @private + * @returns {Function} Returns the "indexOf" function. + */ + function getIndexOf() { + var result = (result = lodash.indexOf) === indexOf ? baseIndexOf : result; + return result; + } + + /** + * Sets `this` binding data on a given function. + * + * @private + * @param {Function} func The function to set data on. + * @param {Array} value The data array to set. + */ + var setBindData = !defineProperty ? noop : function(func, value) { + descriptor.value = value; + defineProperty(func, '__bindData__', descriptor); + }; + + /** + * A fallback implementation of `isPlainObject` which checks if a given value + * is an object created by the `Object` constructor, assuming objects created + * by the `Object` constructor have no inherited enumerable properties and that + * there are no `Object.prototype` extensions. + * + * @private + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. + */ + function shimIsPlainObject(value) { + var ctor, + result; + + // avoid non Object objects, `arguments` objects, and DOM elements + if (!(value && toString.call(value) == objectClass) || + (ctor = value.constructor, isFunction(ctor) && !(ctor instanceof ctor)) || + (!support.argsClass && isArguments(value)) || + (!support.nodeClass && isNode(value))) { + 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. + if (support.ownLast) { + forIn(value, function(value, key, object) { + result = hasOwnProperty.call(object, key); + return false; + }); + return result !== false; + } + // 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. + forIn(value, function(value, key) { + result = key; + }); + return typeof result == 'undefined' || hasOwnProperty.call(value, result); + } + + /*--------------------------------------------------------------------------*/ + + /** + * Checks if `value` is an `arguments` object. * * @static * @memberOf _ * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a function, else `false`. + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is an `arguments` object, else `false`. * @example * - * _.isFunction(_); + * (function() { return _.isArguments(arguments); })(1, 2, 3); * // => true + * + * _.isArguments([1, 2, 3]); + * // => false */ - function isFunction(value) { - return typeof value == 'function'; + function isArguments(value) { + return value && typeof value == 'object' && typeof value.length == 'number' && + toString.call(value) == argsClass || false; } - // fallback for older versions of Chrome and Safari - if (isFunction(/x/)) { - isFunction = function(value) { - return value instanceof Function || toString.call(value) == funcClass; + // fallback for browsers that can't detect `arguments` objects by [[Class]] + if (!support.argsClass) { + isArguments = function(value) { + return value && typeof value == 'object' && typeof value.length == 'number' && + hasOwnProperty.call(value, 'callee') && !propertyIsEnumerable.call(value, 'callee') || false; }; } /** - * Checks if `value` is the language type of Object. - * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) + * Checks if `value` is an array. * * @static * @memberOf _ + * @type Function * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is an object, else `false`. + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is an array, else `false`. * @example * - * _.isObject({}); - * // => true + * (function() { return _.isArray(arguments); })(); + * // => false * - * _.isObject([1, 2, 3]); + * _.isArray([1, 2, 3]); * // => true + */ + var isArray = nativeIsArray || function(value) { + return value && typeof value == 'object' && typeof value.length == 'number' && + toString.call(value) == arrayClass || false; + }; + + /** + * A fallback implementation of `Object.keys` which produces an array of the + * given object's own enumerable property names. * - * _.isObject(1); - * // => false + * @private + * @type Function + * @param {Object} object The object to inspect. + * @returns {Array} Returns an array of property names. */ - function isObject(value) { - // check if the value is the ECMAScript language type of Object - // http://es5.github.com/#x8 - // and avoid a V8 bug - // http://code.google.com/p/v8/issues/detail?id=2291 - return value ? objectTypes[typeof value] : false; - } + var shimKeys = createIterator({ + 'args': 'object', + 'init': '[]', + 'top': 'if (!(objectTypes[typeof object])) return result', + 'loop': 'result.push(index)' + }); + + /** + * Creates an array composed of the own enumerable property names of an object. + * + * @static + * @memberOf _ + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns an array of property names. + * @example + * + * _.keys({ 'one': 1, 'two': 2, 'three': 3 }); + * // => ['one', 'two', 'three'] (property order is not guaranteed across environments) + */ + var keys = !nativeKeys ? shimKeys : function(object) { + if (!isObject(object)) { + return []; + } + if ((support.enumPrototypes && typeof object == 'function') || + (support.nonEnumArgs && object.length && isArguments(object))) { + return shimKeys(object); + } + return nativeKeys(object); + }; + + /** Reusable iterator options shared by `each`, `forIn`, and `forOwn` */ + var eachIteratorOptions = { + 'args': 'collection, callback, thisArg', + 'top': "callback = callback && typeof thisArg == 'undefined' ? callback : baseCreateCallback(callback, thisArg, 3)", + 'array': "typeof length == 'number'", + 'keys': keys, + 'loop': 'if (callback(iterable[index], index, collection) === false) return result' + }; + + /** Reusable iterator options for `assign` and `defaults` */ + var defaultsIteratorOptions = { + 'args': 'object, source, guard', + 'top': + 'var args = arguments,\n' + + ' argsIndex = 0,\n' + + " argsLength = typeof guard == 'number' ? 2 : args.length;\n" + + 'while (++argsIndex < argsLength) {\n' + + ' iterable = args[argsIndex];\n' + + ' if (iterable && objectTypes[typeof iterable]) {', + 'keys': keys, + 'loop': "if (typeof result[index] == 'undefined') result[index] = iterable[index]", + 'bottom': ' }\n}' + }; + + /** Reusable iterator options for `forIn` and `forOwn` */ + var forOwnIteratorOptions = { + 'top': 'if (!objectTypes[typeof iterable]) return result;\n' + eachIteratorOptions.top, + 'array': false + }; /** - * Checks if `value` is `NaN`. + * A function compiled to iterate `arguments` objects, arrays, objects, and + * strings consistenly across environments, executing the callback for each + * element in the collection. The callback is bound to `thisArg` and invoked + * with three arguments; (value, index|key, collection). Callbacks may exit + * iteration early by explicitly returning `false`. * - * Note: This is not the same as native `isNaN`, which will return `true` for - * `undefined` and other values. See http://es5.github.com/#x15.1.2.4. + * @private + * @type Function + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Array|Object|string} Returns `collection`. + */ + var baseEach = createIterator(eachIteratorOptions); + + /*--------------------------------------------------------------------------*/ + + /** + * Assigns own enumerable properties of source object(s) to the destination + * object. Subsequent sources will overwrite property assignments of previous + * sources. If a callback is provided it will be executed to produce the + * assigned values. The callback is bound to `thisArg` and invoked with two + * arguments; (objectValue, sourceValue). * * @static * @memberOf _ + * @type Function + * @alias extend * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is `NaN`, else `false`. + * @param {Object} object The destination object. + * @param {...Object} [source] The source objects. + * @param {Function} [callback] The function to customize assigning values. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns the destination object. * @example * - * _.isNaN(NaN); - * // => true + * _.assign({ 'name': 'fred' }, { 'employer': 'slate' }); + * // => { 'name': 'fred', 'employer': 'slate' } * - * _.isNaN(new Number(NaN)); - * // => true + * var defaults = _.partialRight(_.assign, function(a, b) { + * return typeof a == 'undefined' ? b : a; + * }); + * + * var object = { 'name': 'barney' }; + * defaults(object, { 'name': 'fred', 'employer': 'slate' }); + * // => { 'name': 'barney', 'employer': 'slate' } + */ + var assign = createIterator(defaultsIteratorOptions, { + 'top': + defaultsIteratorOptions.top.replace(';', + ';\n' + + "if (argsLength > 3 && typeof args[argsLength - 2] == 'function') {\n" + + ' var callback = baseCreateCallback(args[--argsLength - 1], args[argsLength--], 2);\n' + + "} else if (argsLength > 2 && typeof args[argsLength - 1] == 'function') {\n" + + ' callback = args[--argsLength];\n' + + '}' + ), + 'loop': 'result[index] = callback ? callback(result[index], iterable[index]) : iterable[index]' + }); + + /** + * Creates a clone of `value`. If `isDeep` is `true` nested objects will also + * be cloned, otherwise they will be assigned by reference. If a callback + * is provided it will be executed to produce the cloned values. If the + * callback returns `undefined` cloning will be handled by the method instead. + * The callback is bound to `thisArg` and invoked with one argument; (value). + * + * @static + * @memberOf _ + * @category Objects + * @param {*} value The value to clone. + * @param {boolean} [isDeep=false] Specify a deep clone. + * @param {Function} [callback] The function to customize cloning values. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {*} Returns the cloned value. + * @example + * + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } + * ]; * - * isNaN(undefined); + * var shallow = _.clone(characters); + * shallow[0] === characters[0]; * // => true * - * _.isNaN(undefined); + * var deep = _.clone(characters, true); + * deep[0] === characters[0]; * // => false + * + * _.mixin({ + * 'clone': _.partialRight(_.clone, function(value) { + * return _.isElement(value) ? value.cloneNode(false) : undefined; + * }) + * }); + * + * var clone = _.clone(document.body); + * clone.childNodes.length; + * // => 0 */ - function isNaN(value) { - // `NaN` as a primitive is the only value that is not equal to itself - // (perform the [[Class]] check first to avoid errors with some host objects in IE) - return isNumber(value) && value != +value + function clone(value, isDeep, callback, thisArg) { + // allows working with "Collections" methods without using their `index` + // and `collection` arguments for `isDeep` and `callback` + if (typeof isDeep != 'boolean' && isDeep != null) { + thisArg = callback; + callback = isDeep; + isDeep = false; + } + return baseClone(value, isDeep, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1)); } /** - * Checks if `value` is `null`. + * Creates a deep clone of `value`. If a callback is provided it will be + * executed to produce the cloned values. If the callback returns `undefined` + * cloning will be handled by the method instead. The callback is bound to + * `thisArg` and invoked with one argument; (value). + * + * Note: This method is loosely based on the structured clone algorithm. Functions + * and DOM nodes are **not** cloned. The enumerable properties of `arguments` objects and + * objects created by constructors other than `Object` are cloned to plain `Object` objects. + * See http://www.w3.org/TR/html5/infrastructure.html#internal-structured-cloning-algorithm. * * @static * @memberOf _ * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is `null`, else `false`. + * @param {*} value The value to deep clone. + * @param {Function} [callback] The function to customize cloning values. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {*} Returns the deep cloned value. * @example * - * _.isNull(null); - * // => true + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } + * ]; + * + * var deep = _.cloneDeep(characters); + * deep[0] === characters[0]; + * // => false + * + * var view = { + * 'label': 'docs', + * 'node': element + * }; + * + * var clone = _.cloneDeep(view, function(value) { + * return _.isElement(value) ? value.cloneNode(true) : undefined; + * }); * - * _.isNull(undefined); + * clone.node == view.node; * // => false */ - function isNull(value) { - return value === null; + function cloneDeep(value, callback, thisArg) { + return baseClone(value, true, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 1)); } /** - * Checks if `value` is a number. + * Iterates over own and inherited enumerable properties of an object, + * executing the callback for each property. The callback is bound to `thisArg` + * and invoked with three arguments; (value, key, object). Callbacks may exit + * iteration early by explicitly returning `false`. * * @static * @memberOf _ + * @type Function * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a number, else `false`. + * @param {Object} object The object to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns `object`. * @example * - * _.isNumber(8.4 * 5); - * // => true + * function Shape() { + * this.x = 0; + * this.y = 0; + * } + * + * Shape.prototype.move = function(x, y) { + * this.x += x; + * this.y += y; + * }; + * + * _.forIn(new Shape, function(value, key) { + * console.log(key); + * }); + * // => logs 'x', 'y', and 'move' (property order is not guaranteed across environments) */ - function isNumber(value) { - return typeof value == 'number' || toString.call(value) == numberClass; - } + var forIn = createIterator(eachIteratorOptions, forOwnIteratorOptions, { + 'useHas': false + }); /** - * Checks if a given `value` is an object created by the `Object` constructor. + * Iterates over own enumerable properties of an object, executing the callback + * for each property. The callback is bound to `thisArg` and invoked with three + * arguments; (value, key, object). Callbacks may exit iteration early by + * explicitly returning `false`. * * @static * @memberOf _ + * @type Function * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if `value` is a plain object, else `false`. + * @param {Object} object The object to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns `object`. * @example * - * function Stooge(name, age) { - * this.name = name; - * this.age = age; - * } + * _.forOwn({ '0': 'zero', '1': 'one', 'length': 2 }, function(num, key) { + * console.log(key); + * }); + * // => logs '0', '1', and 'length' (property order is not guaranteed across environments) + */ + var forOwn = createIterator(eachIteratorOptions, forOwnIteratorOptions); + + /** + * Creates a sorted array of property names of all enumerable properties, + * own and inherited, of `object` that have function values. * - * _.isPlainObject(new Stooge('moe', 40)); - * // => false + * @static + * @memberOf _ + * @alias methods + * @category Objects + * @param {Object} object The object to inspect. + * @returns {Array} Returns an array of property names that have function values. + * @example * - * _.isPlainObject([1, 2, 3]); + * _.functions(_); + * // => ['all', 'any', 'bind', 'bindAll', 'clone', 'compact', 'compose', ...] + */ + function functions(object) { + var result = []; + forIn(object, function(value, key) { + if (isFunction(value)) { + result.push(key); + } + }); + return result.sort(); + } + + /** + * Checks if `value` is empty. Arrays, strings, or `arguments` objects with a + * length of `0` and objects with no own enumerable properties are considered + * "empty". + * + * @static + * @memberOf _ + * @category Objects + * @param {Array|Object|string} value The value to inspect. + * @returns {boolean} Returns `true` if the `value` is empty, else `false`. + * @example + * + * _.isEmpty([1, 2, 3]); * // => false * - * _.isPlainObject({ 'name': 'moe', 'age': 40 }); + * _.isEmpty({}); + * // => true + * + * _.isEmpty(''); * // => true */ - var isPlainObject = !getPrototypeOf ? shimIsPlainObject : function(value) { - if (!(value && typeof value == 'object')) { - return false; + function isEmpty(value) { + var result = true; + if (!value) { + return result; } - var valueOf = value.valueOf, - objProto = typeof valueOf == 'function' && (objProto = getPrototypeOf(valueOf)) && getPrototypeOf(objProto); + var className = toString.call(value), + length = value.length; - return objProto - ? value == objProto || (getPrototypeOf(value) == objProto && !isArguments(value)) - : shimIsPlainObject(value); - }; + if ((className == arrayClass || className == stringClass || + (support.argsClass ? className == argsClass : isArguments(value))) || + (className == objectClass && typeof length == 'number' && isFunction(value.splice))) { + return !length; + } + forOwn(value, function() { + return (result = false); + }); + return result; + } /** - * Checks if `value` is a regular expression. + * Performs a deep comparison between two values to determine if they are + * equivalent to each other. If a callback is provided it will be executed + * to compare values. If the callback returns `undefined` comparisons will + * be handled by the method instead. The callback is bound to `thisArg` and + * invoked with two arguments; (a, b). * * @static * @memberOf _ * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a regular expression, else `false`. + * @param {*} a The value to compare. + * @param {*} b The other value to compare. + * @param {Function} [callback] The function to customize comparing values. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {boolean} Returns `true` if the values are equivalent, else `false`. * @example * - * _.isRegExp(/moe/); + * var object = { 'name': 'fred' }; + * var copy = { 'name': 'fred' }; + * + * object == copy; + * // => false + * + * _.isEqual(object, copy); + * // => true + * + * var words = ['hello', 'goodbye']; + * var otherWords = ['hi', 'goodbye']; + * + * _.isEqual(words, otherWords, function(a, b) { + * var reGreet = /^(?:hello|hi)$/i, + * aGreet = _.isString(a) && reGreet.test(a), + * bGreet = _.isString(b) && reGreet.test(b); + * + * return (aGreet || bGreet) ? (aGreet == bGreet) : undefined; + * }); * // => true */ - function isRegExp(value) { - return value instanceof RegExp || toString.call(value) == regexpClass; + function isEqual(a, b, callback, thisArg) { + return baseIsEqual(a, b, typeof callback == 'function' && baseCreateCallback(callback, thisArg, 2)); } /** - * Checks if `value` is a string. + * Checks if `value` is a function. * * @static * @memberOf _ * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is a string, else `false`. + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is a function, else `false`. * @example * - * _.isString('moe'); + * _.isFunction(_); * // => true */ - function isString(value) { - return typeof value == 'string' || toString.call(value) == stringClass; + function isFunction(value) { + return typeof value == 'function'; + } + // fallback for older versions of Chrome and Safari + if (isFunction(/x/)) { + isFunction = function(value) { + return typeof value == 'function' && toString.call(value) == funcClass; + }; } /** - * Checks if `value` is `undefined`. + * Checks if `value` is the language type of Object. + * (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) * * @static * @memberOf _ * @category Objects - * @param {Mixed} value The value to check. - * @returns {Boolean} Returns `true` if the `value` is `undefined`, else `false`. + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is an object, else `false`. * @example * - * _.isUndefined(void 0); + * _.isObject({}); + * // => true + * + * _.isObject([1, 2, 3]); * // => true + * + * _.isObject(1); + * // => false */ - function isUndefined(value) { - return typeof value == 'undefined'; + function isObject(value) { + // check if the value is the ECMAScript language type of Object + // http://es5.github.io/#x8 + // and avoid a V8 bug + // http://code.google.com/p/v8/issues/detail?id=2291 + return !!(value && objectTypes[typeof value]); } /** - * Creates an array composed of the own enumerable property names of `object`. + * Checks if `value` is an object created by the `Object` constructor. * * @static * @memberOf _ * @category Objects - * @param {Object} object The object to inspect. - * @returns {Array} Returns a new array of property names. + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if `value` is a plain object, else `false`. * @example * - * _.keys({ 'one': 1, 'two': 2, 'three': 3 }); - * // => ['one', 'two', 'three'] (order is not guaranteed) + * function Shape() { + * this.x = 0; + * this.y = 0; + * } + * + * _.isPlainObject(new Shape); + * // => false + * + * _.isPlainObject([1, 2, 3]); + * // => false + * + * _.isPlainObject({ 'x': 0, 'y': 0 }); + * // => true */ - var keys = !nativeKeys ? shimKeys : function(object) { - // avoid iterating over the `prototype` property - return typeof object == 'function' && propertyIsEnumerable.call(object, 'prototype') - ? shimKeys(object) - : (isObject(object) ? nativeKeys(object) : []); + var isPlainObject = !getPrototypeOf ? shimIsPlainObject : function(value) { + if (!(value && toString.call(value) == objectClass) || (!support.argsClass && isArguments(value))) { + return false; + } + var valueOf = value.valueOf, + objProto = typeof valueOf == 'function' && (objProto = getPrototypeOf(valueOf)) && getPrototypeOf(objProto); + + return objProto + ? (value == objProto || getPrototypeOf(value) == objProto) + : shimIsPlainObject(value); }; /** - * Merges enumerable properties of the source object(s) into the `destination` - * object. Subsequent sources will overwrite propery assignments of previous - * sources. + * Checks if `value` is a string. + * + * @static + * @memberOf _ + * @category Objects + * @param {*} value The value to check. + * @returns {boolean} Returns `true` if the `value` is a string, else `false`. + * @example + * + * _.isString('fred'); + * // => true + */ + function isString(value) { + return typeof value == 'string' || + value && typeof value == 'object' && toString.call(value) == stringClass || false; + } + + /** + * Recursively merges own enumerable properties of the source object(s), that + * don't resolve to `undefined` into the destination object. Subsequent sources + * will overwrite property assignments of previous sources. If a callback is + * provided it will be executed to produce the merged values of the destination + * and source properties. If the callback returns `undefined` merging will + * be handled by the method instead. The callback is bound to `thisArg` and + * invoked with two arguments; (objectValue, sourceValue). * * @static * @memberOf _ * @category Objects * @param {Object} object The destination object. - * @param {Object} [source1, source2, ...] The source objects. - * @param- {Object} [indicator] Internally used to indicate that the `stack` - * argument is an array of traversed objects instead of another source object. - * @param- {Array} [stackA=[]] Internally used to track traversed source objects. - * @param- {Array} [stackB=[]] Internally used to associate values with their - * source counterparts. + * @param {...Object} [source] The source objects. + * @param {Function} [callback] The function to customize merging properties. + * @param {*} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns the destination object. * @example * - * var stooges = [ - * { 'name': 'moe' }, - * { 'name': 'larry' } - * ]; + * var names = { + * 'characters': [ + * { 'name': 'barney' }, + * { 'name': 'fred' } + * ] + * }; * - * var ages = [ - * { 'age': 40 }, - * { 'age': 50 } - * ]; + * var ages = { + * 'characters': [ + * { 'age': 36 }, + * { 'age': 40 } + * ] + * }; + * + * _.merge(names, ages); + * // => { 'characters': [{ 'name': 'barney', 'age': 36 }, { 'name': 'fred', 'age': 40 }] } + * + * var food = { + * 'fruits': ['apple'], + * 'vegetables': ['beet'] + * }; + * + * var otherFood = { + * 'fruits': ['banana'], + * 'vegetables': ['carrot'] + * }; * - * _.merge(stooges, ages); - * // => [{ 'name': 'moe', 'age': 40 }, { 'name': 'larry', 'age': 50 }] + * _.merge(food, otherFood, function(a, b) { + * return _.isArray(a) ? a.concat(b) : undefined; + * }); + * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot] } */ - function merge(object, source, indicator) { + function merge(object) { var args = arguments, - index = 0, - length = 2, - stackA = args[3], - stackB = args[4]; - - if (indicator !== indicatorObject) { - stackA = []; - stackB = []; - - // work with `_.reduce` by only using its callback `accumulator` and `value` arguments - if (typeof indicator != 'number') { - length = args.length; - } + length = 2; + + if (!isObject(object)) { + return object; + } + + // allows working with `_.reduce` and `_.reduceRight` without using + // their `index` and `collection` arguments + if (typeof args[2] != 'number') { + length = args.length; } + if (length > 3 && typeof args[length - 2] == 'function') { + var callback = baseCreateCallback(args[--length - 1], args[length--], 2); + } else if (length > 2 && typeof args[length - 1] == 'function') { + callback = args[--length]; + } + var sources = slice(arguments, 1, length), + index = -1, + stackA = getArray(), + stackB = getArray(); + while (++index < length) { - forOwn(args[index], function(source, key) { - var found, isArr, value; - if (source && ((isArr = isArray(source)) || isPlainObject(source))) { - // avoid merging previously merged cyclic sources - var stackLength = stackA.length; - while (stackLength--) { - found = stackA[stackLength] == source; - if (found) { - break; - } - } - if (found) { - object[key] = stackB[stackLength]; - } - else { - // add `source` and associated `value` to the stack of traversed objects - stackA.push(source); - stackB.push(value = (value = object[key], isArr) - ? (isArray(value) ? value : []) - : (isPlainObject(value) ? value : {}) - ); - // recursively merge objects and arrays (susceptible to call stack limits) - object[key] = merge(value, source, indicatorObject, stackA, stackB); - } - } else if (source != null) { - object[key] = source; - } - }); + baseMerge(object, sources[index], callback, stackA, stackB); } + releaseArray(stackA); + releaseArray(stackB); return object; } /** * Creates a shallow clone of `object` excluding the specified properties. * Property names may be specified as individual arguments or as arrays of - * property names. If `callback` is passed, it will be executed for each property - * in the `object`, omitting the properties `callback` returns truthy for. The - * `callback` is bound to `thisArg` and invoked with three arguments; (value, key, object). + * property names. If a callback is provided it will be executed for each + * property of `object` omitting the properties the callback returns truey + * for. The callback is bound to `thisArg` and invoked with three arguments; + * (value, key, object). * * @static * @memberOf _ * @category Objects * @param {Object} object The source object. - * @param {Function|String} callback|[prop1, prop2, ...] The properties to omit - * or the function called per iteration. - * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @param {Function|...string|string[]} [callback] The properties to omit or the + * function called per iteration. + * @param {*} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns an object without the omitted properties. * @example * - * _.omit({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'userid'); - * // => { 'name': 'moe', 'age': 40 } + * _.omit({ 'name': 'fred', 'age': 40 }, 'age'); + * // => { 'name': 'fred' } * - * _.omit({ 'name': 'moe', '_hint': 'knucklehead', '_seed': '96c4eb' }, function(value, key) { - * return key.charAt(0) == '_'; + * _.omit({ 'name': 'fred', 'age': 40 }, function(value) { + * return typeof value == 'number'; * }); - * // => { 'name': 'moe' } + * // => { 'name': 'fred' } */ function omit(object, callback, thisArg) { - var isFunc = typeof callback == 'function', - result = {}; + var result = {}; + if (typeof callback != 'function') { + var props = []; + forIn(object, function(value, key) { + props.push(key); + }); + props = baseDifference(props, baseFlatten(arguments, true, false, 1)); + + var index = -1, + length = props.length; - if (isFunc) { - callback = createCallback(callback, thisArg); + while (++index < length) { + var key = props[index]; + result[key] = object[key]; + } } else { - var props = concat.apply(arrayRef, arguments); + callback = lodash.createCallback(callback, thisArg, 3); + forIn(object, function(value, key, object) { + if (!callback(value, key, object)) { + result[key] = value; + } + }); } - forIn(object, function(value, key, object) { - if (isFunc - ? !callback(value, key, object) - : indexOf(props, key, 1) < 0 - ) { - result[key] = value; - } - }); return result; } /** - * Creates a two dimensional array of the given object's key-value pairs, + * Creates a two dimensional array of an object's key-value pairs, * i.e. `[[key1, value1], [key2, value2]]`. * * @static @@ -8116,48 +9347,55 @@ var JXON = new (function () { * @returns {Array} Returns new array of key-value pairs. * @example * - * _.pairs({ 'moe': 30, 'larry': 40, 'curly': 50 }); - * // => [['moe', 30], ['larry', 40], ['curly', 50]] (order is not guaranteed) + * _.pairs({ 'barney': 36, 'fred': 40 }); + * // => [['barney', 36], ['fred', 40]] (property order is not guaranteed across environments) */ function pairs(object) { - var result = []; - forOwn(object, function(value, key) { - result.push([key, value]); - }); + 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 a shallow clone of `object` composed of the specified properties. * Property names may be specified as individual arguments or as arrays of - * property names. If `callback` is passed, it will be executed for each property - * in the `object`, picking the properties `callback` returns truthy for. The - * `callback` is bound to `thisArg` and invoked with three arguments; (value, key, object). + * property names. If a callback is provided it will be executed for each + * property of `object` picking the properties the callback returns truey + * for. The callback is bound to `thisArg` and invoked with three arguments; + * (value, key, object). * * @static * @memberOf _ * @category Objects * @param {Object} object The source object. - * @param {Function|String} callback|[prop1, prop2, ...] The properties to pick - * or the function called per iteration. - * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @param {Function|...string|string[]} [callback] The function called per + * iteration or property names to pick, specified as individual property + * names or arrays of property names. + * @param {*} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns an object composed of the picked properties. * @example * - * _.pick({ 'name': 'moe', 'age': 40, 'userid': 'moe1' }, 'name', 'age'); - * // => { 'name': 'moe', 'age': 40 } + * _.pick({ 'name': 'fred', '_userid': 'fred1' }, 'name'); + * // => { 'name': 'fred' } * - * _.pick({ 'name': 'moe', '_hint': 'knucklehead', '_seed': '96c4eb' }, function(value, key) { + * _.pick({ 'name': 'fred', '_userid': 'fred1' }, function(value, key) { * return key.charAt(0) != '_'; * }); - * // => { 'name': 'moe' } + * // => { 'name': 'fred' } */ function pick(object, callback, thisArg) { var result = {}; if (typeof callback != 'function') { - var index = 0, - props = concat.apply(arrayRef, arguments), - length = props.length; + var index = -1, + props = baseFlatten(arguments, true, false, 1), + length = isObject(object) ? props.length : 0; while (++index < length) { var key = props[index]; @@ -8166,7 +9404,7 @@ var JXON = new (function () { } } } else { - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg, 3); forIn(object, function(value, key, object) { if (callback(value, key, object)) { result[key] = value; @@ -8183,35 +9421,39 @@ var JXON = new (function () { * @memberOf _ * @category Objects * @param {Object} object The object to inspect. - * @returns {Array} Returns a new array of property values. + * @returns {Array} Returns an array of property values. * @example * * _.values({ 'one': 1, 'two': 2, 'three': 3 }); - * // => [1, 2, 3] + * // => [1, 2, 3] (property order is not guaranteed across environments) */ function values(object) { - var result = []; - forOwn(object, function(value) { - result.push(value); - }); + var index = -1, + props = keys(object), + length = props.length, + result = Array(length); + + while (++index < length) { + result[index] = object[props[index]]; + } return result; } /*--------------------------------------------------------------------------*/ /** - * Checks if a given `target` element is present in a `collection` using strict - * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used - * as the offset from the end of the collection. + * Checks if a given value is present in a collection using strict equality + * for comparisons, i.e. `===`. If `fromIndex` is negative, it is used as the + * offset from the end of the collection. * * @static * @memberOf _ * @alias include * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Mixed} target The value to check for. - * @param {Number} [fromIndex=0] The index to search from. - * @returns {Boolean} Returns `true` if the `target` element is found, else `false`. + * @param {Array|Object|string} collection The collection to iterate over. + * @param {*} target The value to check for. + * @param {number} [fromIndex=0] The index to search from. + * @returns {boolean} Returns `true` if the `target` element is found, else `false`. * @example * * _.contains([1, 2, 3], 1); @@ -8220,25 +9462,25 @@ var JXON = new (function () { * _.contains([1, 2, 3], 1, 2); * // => false * - * _.contains({ 'name': 'moe', 'age': 40 }, 'moe'); + * _.contains({ 'name': 'fred', 'age': 40 }, 'fred'); * // => true * - * _.contains('curly', 'ur'); + * _.contains('pebbles', 'eb'); * // => true */ function contains(collection, target, fromIndex) { var index = -1, + indexOf = getIndexOf(), length = collection ? collection.length : 0, result = false; fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) || 0; - if (typeof length == 'number') { - result = (isString(collection) - ? collection.indexOf(target, fromIndex) - : indexOf(collection, target, fromIndex) - ) > -1; + if (isArray(collection)) { + result = indexOf(collection, target, fromIndex) > -1; + } else if (typeof length == 'number') { + result = (isString(collection) ? collection.indexOf(target, fromIndex) : indexOf(collection, target, fromIndex)) > -1; } else { - each(collection, function(value) { + baseEach(collection, function(value) { if (++index >= fromIndex) { return !(result = value === target); } @@ -8248,64 +9490,49 @@ var JXON = new (function () { } /** - * Creates an object composed of keys returned from running each element of - * `collection` through a `callback`. The corresponding value of each key is - * the number of times the key was returned by `callback`. The `callback` is - * bound to `thisArg` and invoked with three arguments; (value, index|key, collection). - * The `callback` argument may also be the name of a property to count by (e.g. 'length'). - * - * @static - * @memberOf _ - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function|String} callback|property The function called per iteration - * or property name to count by. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Object} Returns the composed aggregate object. - * @example - * - * _.countBy([4.3, 6.1, 6.4], function(num) { return Math.floor(num); }); - * // => { '4': 1, '6': 2 } + * Checks if the given callback returns truey value for **all** elements of + * a collection. The callback is bound to `thisArg` and invoked with three + * arguments; (value, index|key, collection). * - * _.countBy([4.3, 6.1, 6.4], function(num) { return this.floor(num); }, Math); - * // => { '4': 1, '6': 2 } + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. * - * _.countBy(['one', 'two', 'three'], 'length'); - * // => { '3': 2, '5': 1 } - */ - function countBy(collection, callback, thisArg) { - var result = {}; - callback = createCallback(callback, thisArg); - - forEach(collection, function(value, key, collection) { - key = callback(value, key, collection); - (hasOwnProperty.call(result, key) ? result[key]++ : result[key] = 1); - }); - return result; - } - - /** - * Checks if the `callback` returns a truthy value for **all** elements of a - * `collection`. The `callback` is bound to `thisArg` and invoked with three - * arguments; (value, index|key, collection). + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. * * @static * @memberOf _ * @alias all * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Boolean} Returns `true` if all elements pass the callback check, + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {boolean} Returns `true` if all elements passed the callback check, * else `false`. * @example * - * _.every([true, 1, null, 'yes'], Boolean); + * _.every([true, 1, null, 'yes']); + * // => false + * + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } + * ]; + * + * // using "_.pluck" callback shorthand + * _.every(characters, 'age'); + * // => true + * + * // using "_.where" callback shorthand + * _.every(characters, { 'age': 36 }); * // => false */ function every(collection, callback, thisArg) { var result = true; - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg, 3); if (isArray(collection)) { var index = -1, @@ -8317,7 +9544,7 @@ var JXON = new (function () { } } } else { - each(collection, function(value, index, collection) { + baseEach(collection, function(value, index, collection) { return (result = !!callback(value, index, collection)); }); } @@ -8325,26 +9552,48 @@ var JXON = new (function () { } /** - * Examines each element in a `collection`, returning an array of all elements - * the `callback` returns truthy for. The `callback` is bound to `thisArg` and + * Iterates over elements of a collection, returning an array of all elements + * the callback returns truey for. The callback is bound to `thisArg` and * invoked with three arguments; (value, index|key, collection). * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * * @static * @memberOf _ * @alias select * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. * @returns {Array} Returns a new array of elements that passed the callback check. * @example * * var evens = _.filter([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); * // => [2, 4, 6] + * + * var characters = [ + * { 'name': 'barney', 'age': 36, 'blocked': false }, + * { 'name': 'fred', 'age': 40, 'blocked': true } + * ]; + * + * // using "_.pluck" callback shorthand + * _.filter(characters, 'blocked'); + * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }] + * + * // using "_.where" callback shorthand + * _.filter(characters, { 'age': 36 }); + * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }] */ function filter(collection, callback, thisArg) { var result = []; - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg, 3); if (isArray(collection)) { var index = -1, @@ -8357,7 +9606,7 @@ var JXON = new (function () { } } } else { - each(collection, function(value, index, collection) { + baseEach(collection, function(value, index, collection) { if (callback(value, index, collection)) { result.push(value); } @@ -8367,158 +9616,175 @@ var JXON = new (function () { } /** - * Examines each element in a `collection`, returning the first one the `callback` - * returns truthy for. The function returns as soon as it finds an acceptable - * element, and does not iterate over the entire `collection`. The `callback` is - * bound to `thisArg` and invoked with three arguments; (value, index|key, collection). + * Iterates over elements of a collection, returning the first element that + * the callback returns truey for. The callback is bound to `thisArg` and + * invoked with three arguments; (value, index|key, collection). * - * @static - * @memberOf _ - * @alias detect - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Mixed} Returns the element that passed the callback check, - * else `undefined`. - * @example + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. * - * var even = _.find([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); - * // => 2 - */ - function find(collection, callback, thisArg) { - var result; - callback = createCallback(callback, thisArg); - - forEach(collection, function(value, index, collection) { - if (callback(value, index, collection)) { - result = value; - return false; - } - }); - return result; - } - - /** - * Iterates over a `collection`, executing the `callback` for each element in - * the `collection`. The `callback` is bound to `thisArg` and invoked with three - * arguments; (value, index|key, collection). Callbacks may exit iteration early - * by explicitly returning `false`. + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. * * @static * @memberOf _ - * @alias each + * @alias detect, findWhere * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Array|Object|String} Returns `collection`. + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {*} Returns the found element, else `undefined`. * @example * - * _([1, 2, 3]).forEach(alert).join(','); - * // => alerts each number and returns '1,2,3' + * var characters = [ + * { 'name': 'barney', 'age': 36, 'blocked': false }, + * { 'name': 'fred', 'age': 40, 'blocked': true }, + * { 'name': 'pebbles', 'age': 1, 'blocked': false } + * ]; + * + * _.find(characters, function(chr) { + * return chr.age < 40; + * }); + * // => { 'name': 'barney', 'age': 36, 'blocked': false } + * + * // using "_.where" callback shorthand + * _.find(characters, { 'age': 1 }); + * // => { 'name': 'pebbles', 'age': 1, 'blocked': false } * - * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, alert); - * // => alerts each number value (order is not guaranteed) + * // using "_.pluck" callback shorthand + * _.find(characters, 'blocked'); + * // => { 'name': 'fred', 'age': 40, 'blocked': true } */ - function forEach(collection, callback, thisArg) { - if (callback && typeof thisArg == 'undefined' && isArray(collection)) { + function find(collection, callback, thisArg) { + callback = lodash.createCallback(callback, thisArg, 3); + + if (isArray(collection)) { var index = -1, length = collection.length; while (++index < length) { - if (callback(collection[index], index, collection) === false) { - break; + var value = collection[index]; + if (callback(value, index, collection)) { + return value; } } } else { - each(collection, callback, thisArg); + var result; + baseEach(collection, function(value, index, collection) { + if (callback(value, index, collection)) { + result = value; + return false; + } + }); + return result; } - return collection; } /** - * Creates an object composed of keys returned from running each element of - * `collection` through a `callback`. The corresponding value of each key is an - * array of elements passed to `callback` that returned the key. The `callback` - * is bound to `thisArg` and invoked with three arguments; (value, index|key, collection). - * The `callback` argument may also be the name of a property to group by (e.g. 'length'). + * Iterates over elements of a collection, executing the callback for each + * element. The callback is bound to `thisArg` and invoked with three arguments; + * (value, index|key, collection). Callbacks 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 Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function|String} callback|property The function called per iteration - * or property name to group by. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Object} Returns the composed aggregate object. + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function} [callback=identity] The function called per iteration. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Array|Object|string} Returns `collection`. * @example * - * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); }); - * // => { '4': [4.2], '6': [6.1, 6.4] } - * - * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math); - * // => { '4': [4.2], '6': [6.1, 6.4] } + * _([1, 2, 3]).forEach(function(num) { console.log(num); }).join(','); + * // => logs each number and returns '1,2,3' * - * _.groupBy(['one', 'two', 'three'], 'length'); - * // => { '3': ['one', 'two'], '5': ['three'] } + * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { console.log(num); }); + * // => logs each number and returns the object (property order is not guaranteed across environments) */ - function groupBy(collection, callback, thisArg) { - var result = {}; - callback = createCallback(callback, thisArg); + function forEach(collection, callback, thisArg) { + if (callback && typeof thisArg == 'undefined' && isArray(collection)) { + var index = -1, + length = collection.length; - forEach(collection, function(value, key, collection) { - key = callback(value, key, collection); - (hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value); - }); - return result; + while (++index < length) { + if (callback(collection[index], index, collection) === false) { + break; + } + } + } else { + baseEach(collection, callback, thisArg); + } + return collection; } /** - * Invokes the method named by `methodName` on each element in the `collection`, - * returning an array of the results of each invoked method. Additional arguments - * will be passed to each invoked method. If `methodName` is a function it will - * be invoked for, and `this` bound to, each element in the `collection`. + * Creates an object composed of keys generated from the results of running + * each element of a collection through the callback. The corresponding value + * of each key is an array of the elements responsible for generating the key. + * The callback is bound to `thisArg` and invoked with three arguments; + * (value, index|key, collection). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false` * * @static * @memberOf _ * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function|String} methodName The name of the method to invoke or - * the function invoked per iteration. - * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with. - * @returns {Array} Returns a new array of the results of each invoked method. + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Object} Returns the composed aggregate object. * @example * - * _.invoke([[5, 1, 7], [3, 2, 1]], 'sort'); - * // => [[1, 5, 7], [1, 2, 3]] + * _.groupBy([4.2, 6.1, 6.4], function(num) { return Math.floor(num); }); + * // => { '4': [4.2], '6': [6.1, 6.4] } + * + * _.groupBy([4.2, 6.1, 6.4], function(num) { return this.floor(num); }, Math); + * // => { '4': [4.2], '6': [6.1, 6.4] } * - * _.invoke([123, 456], String.prototype.split, ''); - * // => [['1', '2', '3'], ['4', '5', '6']] + * // using "_.pluck" callback shorthand + * _.groupBy(['one', 'two', 'three'], 'length'); + * // => { '3': ['one', 'two'], '5': ['three'] } */ - function invoke(collection, methodName) { - var args = slice(arguments, 2), - isFunc = typeof methodName == 'function', - result = []; - - forEach(collection, function(value) { - result.push((isFunc ? methodName : value[methodName]).apply(value, args)); - }); - return result; - } + var groupBy = createAggregator(function(result, value, key) { + (hasOwnProperty.call(result, key) ? result[key] : result[key] = []).push(value); + }); /** - * Creates an array of values by running each element in the `collection` - * through a `callback`. The `callback` is bound to `thisArg` and invoked with + * Creates an array of values by running each element in the collection + * through the callback. The callback is bound to `thisArg` and invoked with * three arguments; (value, index|key, collection). * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. + * * @static * @memberOf _ * @alias collect * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. * @returns {Array} Returns a new array of the results of each `callback` execution. * @example * @@ -8526,20 +9792,29 @@ var JXON = new (function () { * // => [3, 6, 9] * * _.map({ 'one': 1, 'two': 2, 'three': 3 }, function(num) { return num * 3; }); - * // => [3, 6, 9] (order is not guaranteed) + * // => [3, 6, 9] (property order is not guaranteed across environments) + * + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } + * ]; + * + * // using "_.pluck" callback shorthand + * _.map(characters, 'name'); + * // => ['barney', 'fred'] */ function map(collection, callback, thisArg) { var index = -1, length = collection ? collection.length : 0, result = Array(typeof length == 'number' ? length : 0); - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg, 3); if (isArray(collection)) { while (++index < length) { result[index] = callback(collection[index], index, collection); } } else { - each(collection, function(value, key, collection) { + baseEach(collection, function(value, key, collection) { result[++index] = callback(value, key, collection); }); } @@ -8547,151 +9822,60 @@ var JXON = new (function () { } /** - * Retrieves the maximum value of an `array`. If `callback` is passed, - * it will be executed for each value in the `array` to generate the - * criterion by which the value is ranked. The `callback` is bound to - * `thisArg` and invoked with three arguments; (value, index, collection). - * - * @static - * @memberOf _ - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function} [callback] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Mixed} Returns the maximum value. - * @example - * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 }, - * { 'name': 'curly', 'age': 60 } - * ]; - * - * _.max(stooges, function(stooge) { return stooge.age; }); - * // => { 'name': 'curly', 'age': 60 }; - */ - function max(collection, callback, thisArg) { - var computed = -Infinity, - index = -1, - length = collection ? collection.length : 0, - result = computed; - - if (callback || !isArray(collection)) { - callback = !callback && isString(collection) - ? charAtCallback - : createCallback(callback, thisArg); - - each(collection, function(value, index, collection) { - var current = callback(value, index, collection); - if (current > computed) { - computed = current; - result = value; - } - }); - } else { - while (++index < length) { - if (collection[index] > result) { - result = collection[index]; - } - } - } - return result; - } - - /** - * Retrieves the minimum value of an `array`. If `callback` is passed, - * it will be executed for each value in the `array` to generate the - * criterion by which the value is ranked. The `callback` is bound to `thisArg` - * and invoked with three arguments; (value, index, collection). - * - * @static - * @memberOf _ - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function} [callback] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Mixed} Returns the minimum value. - * @example - * - * _.min([10, 5, 100, 2, 1000]); - * // => 2 - */ - function min(collection, callback, thisArg) { - var computed = Infinity, - index = -1, - length = collection ? collection.length : 0, - result = computed; - - if (callback || !isArray(collection)) { - callback = !callback && isString(collection) - ? charAtCallback - : createCallback(callback, thisArg); - - each(collection, function(value, index, collection) { - var current = callback(value, index, collection); - if (current < computed) { - computed = current; - result = value; - } - }); - } else { - while (++index < length) { - if (collection[index] < result) { - result = collection[index]; - } - } - } - return result; - } - - /** - * Retrieves the value of a specified property from all elements in - * the `collection`. + * Retrieves the value of a specified property from all elements in the collection. * * @static * @memberOf _ + * @type Function * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {String} property The property to pluck. + * @param {Array|Object|string} collection The collection to iterate over. + * @param {string} property The property to pluck. * @returns {Array} Returns a new array of property values. * @example * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 }, - * { 'name': 'curly', 'age': 60 } + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } * ]; * - * _.pluck(stooges, 'name'); - * // => ['moe', 'larry', 'curly'] + * _.pluck(characters, 'name'); + * // => ['barney', 'fred'] */ - function pluck(collection, property) { - return map(collection, property + ''); - } + var pluck = map; /** - * Boils down a `collection` to a single value. The initial state of the - * reduction is `accumulator` and each successive step of it should be returned - * by the `callback`. The `callback` is bound to `thisArg` and invoked with 4 - * arguments; for arrays they are (accumulator, value, index|key, collection). + * Reduces a collection to a value which is the accumulated result of running + * each element in the collection through the callback, where each successive + * callback execution consumes the return value of the previous execution. If + * `accumulator` is not provided the first element of the collection will be + * used as the initial `accumulator` value. The callback is bound to `thisArg` + * and invoked with four arguments; (accumulator, value, index|key, collection). * * @static * @memberOf _ * @alias foldl, inject * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. + * @param {Array|Object|string} collection The collection to iterate over. * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [accumulator] Initial value of the accumulator. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Mixed} Returns the accumulated value. + * @param {*} [accumulator] Initial value of the accumulator. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {*} Returns the accumulated value. * @example * - * var sum = _.reduce([1, 2, 3], function(memo, num) { return memo + num; }); + * var sum = _.reduce([1, 2, 3], function(sum, num) { + * return sum + num; + * }); * // => 6 + * + * var mapped = _.reduce({ 'a': 1, 'b': 2, 'c': 3 }, function(result, num, key) { + * result[key] = num * 3; + * return result; + * }, {}); + * // => { 'a': 3, 'b': 6, 'c': 9 } */ function reduce(collection, callback, accumulator, thisArg) { var noaccum = arguments.length < 3; - callback = createCallback(callback, thisArg, indicatorObject); + callback = lodash.createCallback(callback, thisArg, 4); if (isArray(collection)) { var index = -1, @@ -8704,7 +9888,7 @@ var JXON = new (function () { accumulator = callback(accumulator, collection[index], index, collection); } } else { - each(collection, function(value, index, collection) { + baseEach(collection, function(value, index, collection) { accumulator = noaccum ? (noaccum = false, value) : callback(accumulator, value, index, collection) @@ -8714,142 +9898,95 @@ var JXON = new (function () { } /** - * The right-associative version of `_.reduce`. + * The opposite of `_.filter` this method returns the elements of a + * collection that the callback does **not** return truey for. * - * @static - * @memberOf _ - * @alias foldr - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [accumulator] Initial value of the accumulator. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Mixed} Returns the accumulated value. - * @example + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. * - * var list = [[0, 1], [2, 3], [4, 5]]; - * var flat = _.reduceRight(list, function(a, b) { return a.concat(b); }, []); - * // => [4, 5, 2, 3, 0, 1] - */ - function reduceRight(collection, callback, accumulator, thisArg) { - var iteratee = collection, - length = collection ? collection.length : 0, - noaccum = arguments.length < 3; - - if (typeof length != 'number') { - var props = keys(collection); - length = props.length; - } else if (noCharByIndex && isString(collection)) { - iteratee = collection.split(''); - } - callback = createCallback(callback, thisArg, indicatorObject); - forEach(collection, function(value, index, collection) { - index = props ? props[--length] : --length; - accumulator = noaccum - ? (noaccum = false, iteratee[index]) - : callback(accumulator, iteratee[index], index, collection); - }); - return accumulator; - } - - /** - * The opposite of `_.filter`, this method returns the values of a - * `collection` that `callback` does **not** return truthy for. + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. * * @static * @memberOf _ * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Array} Returns a new array of elements that did **not** pass the - * callback check. + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {Array} Returns a new array of elements that failed the callback check. * @example * * var odds = _.reject([1, 2, 3, 4, 5, 6], function(num) { return num % 2 == 0; }); * // => [1, 3, 5] + * + * var characters = [ + * { 'name': 'barney', 'age': 36, 'blocked': false }, + * { 'name': 'fred', 'age': 40, 'blocked': true } + * ]; + * + * // using "_.pluck" callback shorthand + * _.reject(characters, 'blocked'); + * // => [{ 'name': 'barney', 'age': 36, 'blocked': false }] + * + * // using "_.where" callback shorthand + * _.reject(characters, { 'age': 36 }); + * // => [{ 'name': 'fred', 'age': 40, 'blocked': true }] */ function reject(collection, callback, thisArg) { - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg, 3); return filter(collection, function(value, index, collection) { return !callback(value, index, collection); }); } /** - * Creates an array of shuffled `array` values, using a version of the - * Fisher-Yates shuffle. See http://en.wikipedia.org/wiki/Fisher-Yates_shuffle. - * - * @static - * @memberOf _ - * @category Collections - * @param {Array|Object|String} collection The collection to shuffle. - * @returns {Array} Returns a new shuffled collection. - * @example - * - * _.shuffle([1, 2, 3, 4, 5, 6]); - * // => [4, 1, 6, 3, 5, 2] - */ - function shuffle(collection) { - var index = -1, - result = Array(collection ? collection.length : 0); - - forEach(collection, function(value) { - var rand = floor(nativeRandom() * (++index + 1)); - result[index] = result[rand]; - result[rand] = value; - }); - return result; - } - - /** - * Gets the size of the `collection` by returning `collection.length` for arrays - * and array-like objects or the number of own enumerable properties for objects. - * - * @static - * @memberOf _ - * @category Collections - * @param {Array|Object|String} collection The collection to inspect. - * @returns {Number} Returns `collection.length` or number of own enumerable properties. - * @example - * - * _.size([1, 2]); - * // => 2 + * Checks if the callback returns a truey value for **any** element of a + * collection. The function returns as soon as it finds a passing value and + * does not iterate over the entire collection. The callback is bound to + * `thisArg` and invoked with three arguments; (value, index|key, collection). * - * _.size({ 'one': 1, 'two': 2, 'three': 3 }); - * // => 3 + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. * - * _.size('curly'); - * // => 5 - */ - function size(collection) { - var length = collection ? collection.length : 0; - return typeof length == 'number' ? length : keys(collection).length; - } - - /** - * Checks if the `callback` returns a truthy value for **any** element of a - * `collection`. The function returns as soon as it finds passing value, and - * does not iterate over the entire `collection`. The `callback` is bound to - * `thisArg` and invoked with three arguments; (value, index|key, collection). + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. * * @static * @memberOf _ * @alias any * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Boolean} Returns `true` if any element passes the callback check, + * @param {Array|Object|string} collection The collection to iterate over. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {boolean} Returns `true` if any element passed the callback check, * else `false`. * @example * * _.some([null, 0, 'yes', false], Boolean); * // => true + * + * var characters = [ + * { 'name': 'barney', 'age': 36, 'blocked': false }, + * { 'name': 'fred', 'age': 40, 'blocked': true } + * ]; + * + * // using "_.pluck" callback shorthand + * _.some(characters, 'blocked'); + * // => true + * + * // using "_.where" callback shorthand + * _.some(characters, { 'age': 1 }); + * // => false */ function some(collection, callback, thisArg) { var result; - callback = createCallback(callback, thisArg); + callback = lodash.createCallback(callback, thisArg, 3); if (isArray(collection)) { var index = -1, @@ -8861,127 +9998,24 @@ var JXON = new (function () { } } } else { - each(collection, function(value, index, collection) { + baseEach(collection, function(value, index, collection) { return !(result = callback(value, index, collection)); }); } return !!result; } - /** - * Creates an array, stable sorted in ascending order by the results of - * running each element of `collection` through a `callback`. The `callback` - * is bound to `thisArg` and invoked with three arguments; (value, index|key, collection). - * The `callback` argument may also be the name of a property to sort by (e.g. 'length'). - * - * @static - * @memberOf _ - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function|String} callback|property The function called per iteration - * or property name to sort by. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Array} Returns a new array of sorted elements. - * @example - * - * _.sortBy([1, 2, 3], function(num) { return Math.sin(num); }); - * // => [3, 1, 2] - * - * _.sortBy([1, 2, 3], function(num) { return this.sin(num); }, Math); - * // => [3, 1, 2] - * - * _.sortBy(['larry', 'brendan', 'moe'], 'length'); - * // => ['moe', 'larry', 'brendan'] - */ - function sortBy(collection, callback, thisArg) { - var result = []; - callback = createCallback(callback, thisArg); - - forEach(collection, function(value, index, collection) { - result.push({ - 'criteria': callback(value, index, collection), - 'index': index, - 'value': value - }); - }); - - var length = result.length; - result.sort(compareAscending); - while (length--) { - result[length] = result[length].value; - } - return result; - } - - /** - * Converts the `collection` to an array. - * - * @static - * @memberOf _ - * @category Collections - * @param {Array|Object|String} collection The collection to convert. - * @returns {Array} Returns the new converted array. - * @example - * - * (function() { return _.toArray(arguments).slice(1); })(1, 2, 3, 4); - * // => [2, 3, 4] - */ - function toArray(collection) { - var length = collection ? collection.length : 0; - if (typeof length == 'number') { - return noCharByIndex && isString(collection) - ? collection.split('') - : slice(collection); - } - return values(collection); - } - - /** - * Examines each element in a `collection`, returning an array of all elements - * that contain the given `properties`. - * - * @static - * @memberOf _ - * @category Collections - * @param {Array|Object|String} collection The collection to iterate over. - * @param {Object} properties The object of property values to filter by. - * @returns {Array} Returns a new array of elements that contain the given `properties`. - * @example - * - * var stooges = [ - * { 'name': 'moe', 'age': 40 }, - * { 'name': 'larry', 'age': 50 }, - * { 'name': 'curly', 'age': 60 } - * ]; - * - * _.where(stooges, { 'age': 40 }); - * // => [{ 'name': 'moe', 'age': 40 }] - */ - function where(collection, properties) { - var props = keys(properties); - return filter(collection, function(object) { - var length = props.length; - while (length--) { - var result = object[props[length]] === properties[props[length]]; - if (!result) { - break; - } - } - return !!result; - }); - } - /*--------------------------------------------------------------------------*/ /** - * Creates an array with all falsey values of `array` removed. The values - * `false`, `null`, `0`, `""`, `undefined` and `NaN` are all falsey. + * Creates an array with all falsey values removed. The values `false`, `null`, + * `0`, `""`, `undefined`, and `NaN` are all falsey. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to compact. - * @returns {Array} Returns a new filtered array. + * @returns {Array} Returns a new array of filtered values. * @example * * _.compact([0, 1, false, 2, '', 3]); @@ -9002,74 +10036,117 @@ var JXON = new (function () { } /** - * Creates an array of `array` elements not present in the other arrays - * using strict equality for comparisons, i.e. `===`. + * Creates an array excluding all values of the provided arrays using strict + * equality for comparisons, i.e. `===`. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to process. - * @param {Array} [array1, array2, ...] Arrays to check. - * @returns {Array} Returns a new array of `array` elements not present in the - * other arrays. + * @param {...Array} [values] The arrays of values to exclude. + * @returns {Array} Returns a new array of filtered values. * @example * * _.difference([1, 2, 3, 4, 5], [5, 2, 10]); * // => [1, 3, 4] */ function difference(array) { - var index = -1, - length = array ? array.length : 0, - flattened = concat.apply(arrayRef, arguments), - contains = cachedContains(flattened, length), - result = []; - - while (++index < length) { - var value = array[index]; - if (!contains(value)) { - result.push(value); - } - } - return result; + return baseDifference(array, baseFlatten(arguments, true, true, 1)); } /** - * Gets the first element of the `array`. Pass `n` to return the first `n` - * elements of the `array`. + * Gets the first element or first `n` elements of an array. If a callback + * is provided elements at the beginning of the array are returned as long + * as the callback returns truey. The callback is bound to `thisArg` and + * invoked with three arguments; (value, index, array). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. * * @static * @memberOf _ * @alias head, take * @category Arrays * @param {Array} array The array to query. - * @param {Number} [n] The number of elements to return. - * @param- {Object} [guard] Internally used to allow this method to work with - * others like `_.map` without using their callback `index` argument for `n`. - * @returns {Mixed} Returns the first element, or an array of the first `n` - * elements, of `array`. + * @param {Function|Object|number|string} [callback] The function called + * per element or the number of elements to return. If a property name or + * object is provided it will be used to create a "_.pluck" or "_.where" + * style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {*} Returns the first element(s) of `array`. * @example * - * _.first([5, 4, 3, 2, 1]); - * // => 5 + * _.first([1, 2, 3]); + * // => 1 + * + * _.first([1, 2, 3], 2); + * // => [1, 2] + * + * _.first([1, 2, 3], function(num) { + * return num < 3; + * }); + * // => [1, 2] + * + * var characters = [ + * { 'name': 'barney', 'blocked': true, 'employer': 'slate' }, + * { 'name': 'fred', 'blocked': false, 'employer': 'slate' }, + * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' } + * ]; + * + * // using "_.pluck" callback shorthand + * _.first(characters, 'blocked'); + * // => [{ 'name': 'barney', 'blocked': true, 'employer': 'slate' }] + * + * // using "_.where" callback shorthand + * _.pluck(_.first(characters, { 'employer': 'slate' }), 'name'); + * // => ['barney', 'fred'] */ - function first(array, n, guard) { - if (array) { - var length = array.length; - return (n == null || guard) - ? array[0] - : slice(array, 0, nativeMin(nativeMax(0, n), length)); + function first(array, callback, thisArg) { + var n = 0, + length = array ? array.length : 0; + + if (typeof callback != 'number' && callback != null) { + var index = -1; + callback = lodash.createCallback(callback, thisArg, 3); + while (++index < length && callback(array[index], index, array)) { + n++; + } + } else { + n = callback; + if (n == null || thisArg) { + return array ? array[0] : undefined; + } } + return slice(array, 0, nativeMin(nativeMax(0, n), length)); } /** - * Flattens a nested array (the nesting can be to any depth). If `shallow` is - * truthy, `array` will only be flattened a single level. + * Flattens a nested array (the nesting can be to any depth). If `isShallow` + * is truey, the array will only be flattened a single level. If a callback + * is provided each element of the array is passed through the callback before + * flattening. The callback is bound to `thisArg` and invoked with three + * arguments; (value, index, array). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. * * @static * @memberOf _ * @category Arrays - * @param {Array} array The array to compact. - * @param {Boolean} shallow A flag to indicate only flattening a single level. + * @param {Array} array The array to flatten. + * @param {boolean} [isShallow=false] A flag to restrict flattening to a single level. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. * @returns {Array} Returns a new flattened array. * @example * @@ -9078,38 +10155,42 @@ var JXON = new (function () { * * _.flatten([1, [2], [3, [[4]]]], true); * // => [1, 2, 3, [[4]]]; + * + * var characters = [ + * { 'name': 'barney', 'age': 30, 'pets': ['hoppy'] }, + * { 'name': 'fred', 'age': 40, 'pets': ['baby puss', 'dino'] } + * ]; + * + * // using "_.pluck" callback shorthand + * _.flatten(characters, 'pets'); + * // => ['hoppy', 'baby puss', 'dino'] */ - function flatten(array, shallow) { - var index = -1, - length = array ? array.length : 0, - result = []; - - while (++index < length) { - var value = array[index]; - - // recursively flatten arrays (susceptible to call stack limits) - if (isArray(value)) { - push.apply(result, shallow ? value : flatten(value)); - } else { - result.push(value); - } + function flatten(array, isShallow, callback, thisArg) { + // juggle arguments + if (typeof isShallow != 'boolean' && isShallow != null) { + thisArg = callback; + callback = (typeof isShallow != 'function' && thisArg && thisArg[isShallow] === array) ? null : isShallow; + isShallow = false; } - return result; + if (callback != null) { + array = map(array, callback, thisArg); + } + return baseFlatten(array, isShallow); } /** * Gets the index at which the first occurrence of `value` is found using - * strict equality for comparisons, i.e. `===`. If the `array` is already - * sorted, passing `true` for `fromIndex` will run a faster binary search. + * strict equality for comparisons, i.e. `===`. If the array is already sorted + * providing `true` for `fromIndex` will run a faster binary search. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to search. - * @param {Mixed} 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 or `-1`. + * @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 or `-1`. * @example * * _.indexOf([1, 2, 3, 1, 2, 3], 2); @@ -9122,59 +10203,25 @@ var JXON = new (function () { * // => 2 */ function indexOf(array, value, fromIndex) { - var index = -1, - length = array ? array.length : 0; - if (typeof fromIndex == 'number') { - index = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0) - 1; + var length = array ? array.length : 0; + fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex || 0); } else if (fromIndex) { - index = sortedIndex(array, value); + var index = sortedIndex(array, value); return array[index] === value ? index : -1; } - while (++index < length) { - if (array[index] === value) { - return index; - } - } - return -1; - } - - /** - * Gets all but the last element of `array`. Pass `n` to exclude the last `n` - * elements from the result. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} array The array to query. - * @param {Number} [n=1] The number of elements to exclude. - * @param- {Object} [guard] Internally used to allow this method to work with - * others like `_.map` without using their callback `index` argument for `n`. - * @returns {Array} Returns all but the last element, or `n` elements, of `array`. - * @example - * - * _.initial([3, 2, 1]); - * // => [3, 2] - */ - function initial(array, n, guard) { - if (!array) { - return []; - } - var length = array.length; - n = n == null || guard ? 1 : n || 0; - return slice(array, 0, nativeMin(nativeMax(0, length - n), length)); + return baseIndexOf(array, value, fromIndex); } /** - * Computes the intersection of all the passed-in arrays using strict equality - * for comparisons, i.e. `===`. + * Creates an array of unique values present in all provided arrays using + * strict equality for comparisons, i.e. `===`. * * @static * @memberOf _ * @category Arrays - * @param {Array} [array1, array2, ...] Arrays to process. - * @returns {Array} Returns a new array of unique elements that are present - * in **all** of the arrays. + * @param {...Array} [array] The arrays to inspect. + * @returns {Array} Returns an array of composite values. * @example * * _.intersection([1, 2, 3], [101, 2, 1, 10], [2, 1]); @@ -9183,224 +10230,148 @@ var JXON = new (function () { function intersection(array) { var args = arguments, argsLength = args.length, - cache = { '0': {} }, + argsIndex = -1, + caches = getArray(), index = -1, + indexOf = getIndexOf(), length = array ? array.length : 0, - isLarge = length >= 100, result = [], - seen = result; + seen = getArray(); + while (++argsIndex < argsLength) { + var value = args[argsIndex]; + caches[argsIndex] = indexOf === baseIndexOf && + (value ? value.length : 0) >= largeArraySize && + createCache(argsIndex ? args[argsIndex] : seen); + } outer: while (++index < length) { - var value = array[index]; - if (isLarge) { - var key = value + ''; - var inited = hasOwnProperty.call(cache[0], key) - ? !(seen = cache[0][key]) - : (seen = cache[0][key] = []); - } - if (inited || indexOf(seen, value) < 0) { - if (isLarge) { - seen.push(value); - } - var argsIndex = argsLength; + var cache = caches[0]; + value = array[index]; + + if ((cache ? cacheIndexOf(cache, value) : indexOf(seen, value)) < 0) { + argsIndex = argsLength; + (cache || seen).push(value); while (--argsIndex) { - if (!(cache[argsIndex] || (cache[argsIndex] = cachedContains(args[argsIndex], 0, 100)))(value)) { + cache = caches[argsIndex]; + if ((cache ? cacheIndexOf(cache, value) : indexOf(args[argsIndex], value)) < 0) { continue outer; } } result.push(value); } } - return result; - } - - /** - * Gets the last element of the `array`. Pass `n` to return the last `n` - * elements of the `array`. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} array The array to query. - * @param {Number} [n] The number of elements to return. - * @param- {Object} [guard] Internally used to allow this method to work with - * others like `_.map` without using their callback `index` argument for `n`. - * @returns {Mixed} Returns the last element, or an array of the last `n` - * elements, of `array`. - * @example - * - * _.last([3, 2, 1]); - * // => 1 - */ - function last(array, n, guard) { - if (array) { - var length = array.length; - return (n == null || guard) ? array[length - 1] : slice(array, nativeMax(0, length - n)); - } - } - - /** - * Gets the index at which the last occurrence of `value` is found using strict - * equality for comparisons, i.e. `===`. If `fromIndex` is negative, it is used - * as the offset from the end of the collection. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} array The array to search. - * @param {Mixed} 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 or `-1`. - * @example - * - * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2); - * // => 4 - * - * _.lastIndexOf([1, 2, 3, 1, 2, 3], 2, 3); - * // => 1 - */ - function lastIndexOf(array, value, fromIndex) { - var index = array ? array.length : 0; - if (typeof fromIndex == 'number') { - index = (fromIndex < 0 ? nativeMax(0, index + fromIndex) : nativeMin(fromIndex, index - 1)) + 1; - } - while (index--) { - if (array[index] === value) { - return index; + while (argsLength--) { + cache = caches[argsLength]; + if (cache) { + releaseObject(cache); } } - return -1; + releaseArray(caches); + releaseArray(seen); + return result; } /** - * Creates an object composed from arrays of `keys` and `values`. Pass either - * a single two dimensional array, i.e. `[[key1, value1], [key2, value2]]`, or - * two arrays, one of `keys` and one of corresponding `values`. + * Gets the last element or last `n` elements of an array. If a callback is + * provided elements at the end of the array are returned as long as the + * callback returns truey. The callback is bound to `thisArg` and invoked + * with three arguments; (value, index, array). * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} keys The array of keys. - * @param {Array} [values=[]] The array of values. - * @returns {Object} Returns an object composed of the given keys and - * corresponding values. - * @example + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. * - * _.object(['moe', 'larry', 'curly'], [30, 40, 50]); - * // => { 'moe': 30, 'larry': 40, 'curly': 50 } - */ - function object(keys, values) { - var index = -1, - length = keys ? keys.length : 0, - result = {}; - - while (++index < length) { - var key = keys[index]; - if (values) { - result[key] = values[index]; - } else { - result[key[0]] = key[1]; - } - } - return result; - } - - /** - * Creates an array of numbers (positive and/or negative) progressing from - * `start` up to but not including `stop`. This method is a port of Python's - * `range()` function. See http://docs.python.org/library/functions.html#range. + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. * * @static * @memberOf _ * @category Arrays - * @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 descrement by. - * @returns {Array} Returns a new range array. + * @param {Array} array The array to query. + * @param {Function|Object|number|string} [callback] The function called + * per element or the number of elements to return. If a property name or + * object is provided it will be used to create a "_.pluck" or "_.where" + * style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {*} Returns the last element(s) of `array`. * @example * - * _.range(10); - * // => [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + * _.last([1, 2, 3]); + * // => 3 + * + * _.last([1, 2, 3], 2); + * // => [2, 3] * - * _.range(1, 11); - * // => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] + * _.last([1, 2, 3], function(num) { + * return num > 1; + * }); + * // => [2, 3] * - * _.range(0, 30, 5); - * // => [0, 5, 10, 15, 20, 25] + * var characters = [ + * { 'name': 'barney', 'blocked': false, 'employer': 'slate' }, + * { 'name': 'fred', 'blocked': true, 'employer': 'slate' }, + * { 'name': 'pebbles', 'blocked': true, 'employer': 'na' } + * ]; * - * _.range(0, -10, -1); - * // => [0, -1, -2, -3, -4, -5, -6, -7, -8, -9] + * // using "_.pluck" callback shorthand + * _.pluck(_.last(characters, 'blocked'), 'name'); + * // => ['fred', 'pebbles'] * - * _.range(0); - * // => [] + * // using "_.where" callback shorthand + * _.last(characters, { 'employer': 'na' }); + * // => [{ 'name': 'pebbles', 'blocked': true, 'employer': 'na' }] */ - function range(start, end, step) { - start = +start || 0; - step = +step || 1; - - if (end == null) { - end = start; - start = 0; - } - // use `Array(length)` so V8 will avoid the slower "dictionary" mode - // http://youtu.be/XAqIpGU8ZZk#t=17m25s - var index = -1, - length = nativeMax(0, ceil((end - start) / step)), - result = Array(length); + function last(array, callback, thisArg) { + var n = 0, + length = array ? array.length : 0; - while (++index < length) { - result[index] = start; - start += step; + if (typeof callback != 'number' && callback != null) { + var index = length; + callback = lodash.createCallback(callback, thisArg, 3); + while (index-- && callback(array[index], index, array)) { + n++; + } + } else { + n = callback; + if (n == null || thisArg) { + return array ? array[length - 1] : undefined; + } } - return result; + return slice(array, nativeMax(0, length - n)); } /** - * The opposite of `_.initial`, this method gets all but the first value of - * `array`. Pass `n` to exclude the first `n` values from the result. + * Uses a binary search to determine the smallest index at which a value + * should be inserted into a given sorted array in order to maintain the sort + * order of the array. If a callback is provided it will be executed for + * `value` and each element of `array` to compute their sort ranking. The + * callback is bound to `thisArg` and invoked with one argument; (value). * - * @static - * @memberOf _ - * @alias drop, tail - * @category Arrays - * @param {Array} array The array to query. - * @param {Number} [n=1] The number of elements to exclude. - * @param- {Object} [guard] Internally used to allow this method to work with - * others like `_.map` without using their callback `index` argument for `n`. - * @returns {Array} Returns all but the first element, or `n` elements, of `array`. - * @example + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. * - * _.rest([3, 2, 1]); - * // => [2, 1] - */ - function rest(array, n, guard) { - return slice(array, (n == null || guard) ? 1 : nativeMax(0, n)); - } - - /** - * Uses a binary search to determine the smallest index at which the `value` - * should be inserted into `array` in order to maintain the sort order of the - * sorted `array`. If `callback` is passed, it will be executed for `value` and - * each element in `array` to compute their sort ranking. The `callback` is - * bound to `thisArg` and invoked with one argument; (value). The `callback` - * argument may also be the name of a property to order by. + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. * * @static * @memberOf _ * @category Arrays - * @param {Array} array The array to iterate over. - * @param {Mixed} value The value to evaluate. - * @param {Function|String} [callback=identity|property] The function called - * per iteration or property name to order by. - * @param {Mixed} [thisArg] The `this` binding of `callback`. - * @returns {Number} Returns the index at which the value should be inserted + * @param {Array} array The array to inspect. + * @param {*} value The value to evaluate. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. + * @returns {number} Returns the index at which `value` should be inserted * into `array`. * @example * * _.sortedIndex([20, 30, 50], 40); * // => 2 * + * // using "_.pluck" callback shorthand * _.sortedIndex([{ 'x': 20 }, { 'x': 30 }, { 'x': 50 }], { 'x': 40 }, 'x'); * // => 2 * @@ -9423,12 +10394,12 @@ var JXON = new (function () { high = array ? array.length : low; // explicitly reference `identity` for better inlining in Firefox - callback = callback ? createCallback(callback, thisArg) : identity; + callback = callback ? lodash.createCallback(callback, thisArg, 1) : identity; value = callback(value); while (low < high) { var mid = (low + high) >>> 1; - callback(array[mid]) < value + (callback(array[mid]) < value) ? low = mid + 1 : high = mid; } @@ -9436,39 +10407,48 @@ var JXON = new (function () { } /** - * Computes the union of the passed-in arrays using strict equality for - * comparisons, i.e. `===`. + * Creates an array of unique values, in order, of the provided arrays using + * strict equality for comparisons, i.e. `===`. * * @static * @memberOf _ * @category Arrays - * @param {Array} [array1, array2, ...] Arrays to process. - * @returns {Array} Returns a new array of unique values, in order, that are - * present in one or more of the arrays. + * @param {...Array} [array] The arrays to inspect. + * @returns {Array} Returns an array of composite values. * @example * * _.union([1, 2, 3], [101, 2, 1, 10], [2, 1]); * // => [1, 2, 3, 101, 10] */ - function union() { - return uniq(concat.apply(arrayRef, arguments)); + function union(array) { + return baseUniq(baseFlatten(arguments, true, true)); } /** - * Creates a duplicate-value-free version of the `array` using strict equality - * for comparisons, i.e. `===`. If the `array` is already sorted, passing `true` - * for `isSorted` will run a faster algorithm. If `callback` is passed, each - * element of `array` is passed through a callback` before uniqueness is computed. - * The `callback` is bound to `thisArg` and invoked with three arguments; (value, index, array). + * Creates a duplicate-value-free version of an array using strict equality + * for comparisons, i.e. `===`. If the array is sorted, providing + * `true` for `isSorted` will use a faster algorithm. If a callback is provided + * each element of `array` is passed through the callback before uniqueness + * is computed. The callback is bound to `thisArg` and invoked with three + * arguments; (value, index, array). + * + * If a property name is provided for `callback` the created "_.pluck" style + * callback will return the property value of the given element. + * + * If an object is provided for `callback` the created "_.where" style callback + * will return `true` for elements that have the properties of the given object, + * else `false`. * * @static * @memberOf _ * @alias unique * @category Arrays * @param {Array} array The array to process. - * @param {Boolean} [isSorted=false] A flag to indicate that the `array` is already sorted. - * @param {Function} [callback=identity] The function called per iteration. - * @param {Mixed} [thisArg] The `this` binding of `callback`. + * @param {boolean} [isSorted=false] A flag to indicate that `array` is sorted. + * @param {Function|Object|string} [callback=identity] The function called + * per iteration. If a property name or object is provided it will be used + * to create a "_.pluck" or "_.where" style callback, respectively. + * @param {*} [thisArg] The `this` binding of `callback`. * @returns {Array} Returns a duplicate-value-free array. * @example * @@ -9478,157 +10458,61 @@ var JXON = new (function () { * _.uniq([1, 1, 2, 2, 3], true); * // => [1, 2, 3] * - * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return Math.floor(num); }); - * // => [1, 2, 3] + * _.uniq(['A', 'b', 'C', 'a', 'B', 'c'], function(letter) { return letter.toLowerCase(); }); + * // => ['A', 'b', 'C'] * - * _.uniq([1, 2, 1.5, 3, 2.5], function(num) { return this.floor(num); }, Math); - * // => [1, 2, 3] + * _.uniq([1, 2.5, 3, 1.5, 2, 3.5], function(num) { return this.floor(num); }, Math); + * // => [1, 2.5, 3] + * + * // using "_.pluck" callback shorthand + * _.uniq([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x'); + * // => [{ 'x': 1 }, { 'x': 2 }] */ function uniq(array, isSorted, callback, thisArg) { - var index = -1, - length = array ? array.length : 0, - result = [], - seen = result; - // juggle arguments - if (typeof isSorted == 'function') { + if (typeof isSorted != 'boolean' && isSorted != null) { thisArg = callback; - callback = isSorted; + callback = (typeof isSorted != 'function' && thisArg && thisArg[isSorted] === array) ? null : isSorted; isSorted = false; } - // init value cache for large arrays - var isLarge = !isSorted && length >= 75; - if (isLarge) { - var cache = {}; - } - if (callback) { - seen = []; - callback = createCallback(callback, thisArg); - } - while (++index < length) { - var value = array[index], - computed = callback ? callback(value, index, array) : value; - - if (isLarge) { - var key = computed + ''; - var inited = hasOwnProperty.call(cache, key) - ? !(seen = cache[key]) - : (seen = cache[key] = []); - } - if (isSorted - ? !index || seen[seen.length - 1] !== computed - : inited || indexOf(seen, computed) < 0 - ) { - if (callback || isLarge) { - seen.push(computed); - } - result.push(value); - } + if (callback != null) { + callback = lodash.createCallback(callback, thisArg, 3); } - return result; + return baseUniq(array, isSorted, callback); } /** - * Creates an array with all occurrences of the passed values removed using - * strict equality for comparisons, i.e. `===`. + * Creates an array excluding all provided values using strict equality for + * comparisons, i.e. `===`. * * @static * @memberOf _ * @category Arrays * @param {Array} array The array to filter. - * @param {Mixed} [value1, value2, ...] Values to remove. - * @returns {Array} Returns a new filtered array. + * @param {...*} [value] The values to exclude. + * @returns {Array} Returns a new array of filtered values. * @example * * _.without([1, 2, 1, 0, 3, 1, 4], 0, 1); * // => [2, 3, 4] */ function without(array) { - var index = -1, - length = array ? array.length : 0, - contains = cachedContains(arguments, 1, 20), - result = []; - - while (++index < length) { - var value = array[index]; - if (!contains(value)) { - result.push(value); - } - } - return result; - } - - /** - * Groups the elements of each array at their corresponding indexes. Useful for - * separate data sources that are coordinated through matching array indexes. - * For a matrix of nested arrays, `_.zip.apply(...)` can transpose the matrix - * in a similar fashion. - * - * @static - * @memberOf _ - * @category Arrays - * @param {Array} [array1, array2, ...] Arrays to process. - * @returns {Array} Returns a new array of grouped elements. - * @example - * - * _.zip(['moe', 'larry', 'curly'], [30, 40, 50], [true, false, false]); - * // => [['moe', 30, true], ['larry', 40, false], ['curly', 50, false]] - */ - function zip(array) { - var index = -1, - length = array ? max(pluck(arguments, 'length')) : 0, - result = Array(length); - - while (++index < length) { - result[index] = pluck(arguments, index); - } - return result; + return baseDifference(array, slice(arguments, 1)); } /*--------------------------------------------------------------------------*/ - /** - * Creates a function that is restricted to executing `func` only after it is - * called `n` times. The `func` is executed with the `this` binding of the - * created function. - * - * @static - * @memberOf _ - * @category Functions - * @param {Number} n The number of times the function must be called before - * it is executed. - * @param {Function} func The function to restrict. - * @returns {Function} Returns the new restricted function. - * @example - * - * var renderNotes = _.after(notes.length, render); - * _.forEach(notes, function(note) { - * note.asyncSave({ 'success': renderNotes }); - * }); - * // `renderNotes` is run once, after all notes have saved - */ - function after(n, func) { - if (n < 1) { - return func(); - } - return function() { - if (--n < 1) { - return func.apply(this, arguments); - } - }; - } - /** * Creates a function that, when called, invokes `func` with the `this` * binding of `thisArg` and prepends any additional `bind` arguments to those - * passed to the bound function. + * provided to the bound function. * * @static * @memberOf _ * @category Functions * @param {Function} func The function to bind. - * @param {Mixed} [thisArg] The `this` binding of `func`. - * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. + * @param {*} [thisArg] The `this` binding of `func`. + * @param {...*} [arg] Arguments to be partially applied. * @returns {Function} Returns the new bound function. * @example * @@ -9636,407 +10520,289 @@ var JXON = new (function () { * return greeting + ' ' + this.name; * }; * - * func = _.bind(func, { 'name': 'moe' }, 'hi'); + * func = _.bind(func, { 'name': 'fred' }, 'hi'); * func(); - * // => 'hi moe' + * // => 'hi fred' */ function bind(func, thisArg) { - // use `Function#bind` if it exists and is fast - // (in V8 `Function#bind` is slower except when partially applied) - return isBindFast || (nativeBind && arguments.length > 2) - ? nativeBind.call.apply(nativeBind, arguments) - : createBound(func, thisArg, slice(arguments, 2)); - } - - /** - * Binds methods on `object` to `object`, overwriting the existing method. - * If no method names are provided, all the function properties of `object` - * will be bound. - * - * @static - * @memberOf _ - * @category Functions - * @param {Object} object The object to bind and assign the bound methods to. - * @param {String} [methodName1, methodName2, ...] Method names on the object to bind. - * @returns {Object} Returns `object`. - * @example - * - * var buttonView = { - * 'label': 'lodash', - * 'onClick': function() { alert('clicked: ' + this.label); } - * }; - * - * _.bindAll(buttonView); - * jQuery('#lodash_button').on('click', buttonView.onClick); - * // => When the button is clicked, `this.label` will have the correct value - */ - function bindAll(object) { - var funcs = arguments, - index = funcs.length > 1 ? 0 : (funcs = functions(object), -1), - length = funcs.length; - - while (++index < length) { - var key = funcs[index]; - object[key] = bind(object[key], object); - } - return object; + return arguments.length > 2 + ? createWrapper(func, 17, slice(arguments, 2), null, thisArg) + : createWrapper(func, 1, null, null, thisArg); } /** - * Creates a function that, when called, invokes the method at `object[key]` - * and prepends any additional `bindKey` arguments to those passed to the bound - * function. This method differs from `_.bind` by allowing bound functions to - * reference methods that will be redefined or don't yet exist. - * See http://michaux.ca/articles/lazy-function-definition-pattern. + * Produces a callback bound to an optional `thisArg`. If `func` is a property + * name the created callback will return the property value for a given element. + * If `func` is an object the created callback will return `true` for elements + * that contain the equivalent object properties, otherwise it will return `false`. * * @static * @memberOf _ * @category Functions - * @param {Object} object The object the method belongs to. - * @param {String} key The key of the method. - * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. - * @returns {Function} Returns the new bound function. + * @param {*} [func=identity] The value to convert to a callback. + * @param {*} [thisArg] The `this` binding of the created callback. + * @param {number} [argCount] The number of arguments the callback accepts. + * @returns {Function} Returns a callback function. * @example * - * var object = { - * 'name': 'moe', - * 'greet': function(greeting) { - * return greeting + ' ' + this.name; - * } - * }; - * - * var func = _.bindKey(object, 'greet', 'hi'); - * func(); - * // => 'hi moe' + * var characters = [ + * { 'name': 'barney', 'age': 36 }, + * { 'name': 'fred', 'age': 40 } + * ]; * - * object.greet = function(greeting) { - * return greeting + ', ' + this.name + '!'; - * }; + * // wrap to create custom callback shorthands + * _.createCallback = _.wrap(_.createCallback, function(func, callback, thisArg) { + * var match = /^(.+?)__([gl]t)(.+)$/.exec(callback); + * return !match ? func(callback, thisArg) : function(object) { + * return match[2] == 'gt' ? object[match[1]] > match[3] : object[match[1]] < match[3]; + * }; + * }); * - * func(); - * // => 'hi, moe!' + * _.filter(characters, 'age__gt38'); + * // => [{ 'name': 'fred', 'age': 40 }] */ - function bindKey(object, key) { - return createBound(object, key, slice(arguments, 2)); - } + function createCallback(func, thisArg, argCount) { + var type = typeof func; + if (func == null || type == 'function') { + return baseCreateCallback(func, thisArg, argCount); + } + // handle "_.pluck" style callback shorthands + if (type != 'object') { + return function(object) { + return object[func]; + }; + } + var props = keys(func), + key = props[0], + a = func[key]; - /** - * Creates a function that is the composition of the passed functions, - * where each function consumes the return value of the function that follows. - * In math terms, composing the functions `f()`, `g()`, and `h()` produces `f(g(h()))`. - * Each function is executed with the `this` binding of the composed function. - * - * @static - * @memberOf _ - * @category Functions - * @param {Function} [func1, func2, ...] Functions to compose. - * @returns {Function} Returns the new composed function. - * @example - * - * var greet = function(name) { return 'hi: ' + name; }; - * var exclaim = function(statement) { return statement + '!'; }; - * var welcome = _.compose(exclaim, greet); - * welcome('moe'); - * // => 'hi: moe!' - */ - function compose() { - var funcs = arguments; - return function() { - var args = arguments, - length = funcs.length; + // handle "_.where" style callback shorthands + if (props.length == 1 && a === a && !isObject(a)) { + // fast path the common case of providing an object with a single + // property containing a primitive value + return function(object) { + var b = object[key]; + return a === b && (a !== 0 || (1 / a == 1 / b)); + }; + } + return function(object) { + var length = props.length, + result = false; while (length--) { - args = [funcs[length].apply(this, args)]; + if (!(result = baseIsEqual(object[props[length]], func[props[length]], null, true))) { + break; + } } - return args[0]; + return result; }; } /** * Creates a function that will delay the execution of `func` until after - * `wait` milliseconds have elapsed since the last time it was invoked. Pass - * `true` for `immediate` to cause debounce to invoke `func` on the leading, - * instead of the trailing, edge of the `wait` timeout. Subsequent calls to - * the debounced function will return the result of the last `func` call. + * `wait` milliseconds have elapsed since the last time it was invoked. + * 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 will return the result of the last `func` call. + * + * Note: If `leading` and `trailing` options are `true` `func` will be called + * on the trailing edge of the timeout only if the the debounced function is + * invoked more than once during the `wait` timeout. * * @static * @memberOf _ * @category Functions * @param {Function} func The function to debounce. - * @param {Number} wait The number of milliseconds to delay. - * @param {Boolean} immediate A flag to indicate execution is on the leading - * edge of the timeout. + * @param {number} wait The number of milliseconds to delay. + * @param {Object} [options] The options object. + * @param {boolean} [options.leading=false] Specify execution on the leading edge of the timeout. + * @param {number} [options.maxWait] The maximum time `func` is allowed to be delayed before it's called. + * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout. * @returns {Function} Returns the new debounced function. * @example * - * var lazyLayout = _.debounce(calculateLayout, 300); + * // avoid costly calculations while the window size is in flux + * var lazyLayout = _.debounce(calculateLayout, 150); * jQuery(window).on('resize', lazyLayout); + * + * // execute `sendMail` when the click event is fired, debouncing subsequent calls + * jQuery('#postbox').on('click', _.debounce(sendMail, 300, { + * 'leading': true, + * 'trailing': false + * }); + * + * // ensure `batchLog` is executed once after 1 second of debounced calls + * var source = new EventSource('/stream'); + * source.addEventListener('message', _.debounce(batchLog, 250, { + * 'maxWait': 1000 + * }, false); */ - function debounce(func, wait, immediate) { + function debounce(func, wait, options) { var args, + maxTimeoutId, result, + stamp, thisArg, - timeoutId; + timeoutId, + trailingCall, + lastCalled = 0, + maxWait = false, + trailing = true; + + if (!isFunction(func)) { + throw new TypeError; + } + wait = nativeMax(0, wait) || 0; + if (options === true) { + var leading = true; + trailing = false; + } else if (isObject(options)) { + leading = options.leading; + maxWait = 'maxWait' in options && (nativeMax(wait, options.maxWait) || 0); + trailing = 'trailing' in options ? options.trailing : trailing; + } + var delayed = function() { + var remaining = wait - (now() - stamp); + if (remaining <= 0) { + if (maxTimeoutId) { + clearTimeout(maxTimeoutId); + } + var isCalled = trailingCall; + maxTimeoutId = timeoutId = trailingCall = undefined; + if (isCalled) { + lastCalled = now(); + result = func.apply(thisArg, args); + if (!timeoutId && !maxTimeoutId) { + args = thisArg = null; + } + } + } else { + timeoutId = setTimeout(delayed, remaining); + } + }; - function delayed() { - timeoutId = null; - if (!immediate) { + var maxDelayed = function() { + if (timeoutId) { + clearTimeout(timeoutId); + } + maxTimeoutId = timeoutId = trailingCall = undefined; + if (trailing || (maxWait !== wait)) { + lastCalled = now(); result = func.apply(thisArg, args); + if (!timeoutId && !maxTimeoutId) { + args = thisArg = null; + } } - } + }; + return function() { - var isImmediate = immediate && !timeoutId; args = arguments; + stamp = now(); thisArg = this; + trailingCall = trailing && (timeoutId || !leading); - clearTimeout(timeoutId); - timeoutId = setTimeout(delayed, wait); + if (maxWait === false) { + var leadingCall = leading && !timeoutId; + } else { + if (!maxTimeoutId && !leading) { + lastCalled = stamp; + } + var remaining = maxWait - (stamp - lastCalled), + isCalled = remaining <= 0; - if (isImmediate) { + 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); } - return result; - }; - } - - /** - * Executes the `func` function after `wait` milliseconds. Additional arguments - * will be passed to `func` when it is invoked. - * - * @static - * @memberOf _ - * @category Functions - * @param {Function} func The function to delay. - * @param {Number} wait The number of milliseconds to delay execution. - * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with. - * @returns {Number} Returns the `setTimeout` timeout id. - * @example - * - * var log = _.bind(console.log, console); - * _.delay(log, 1000, 'logged later'); - * // => 'logged later' (Appears after one second.) - */ - function delay(func, wait) { - var args = slice(arguments, 2); - return setTimeout(function() { func.apply(undefined, args); }, wait); - } - - /** - * Defers executing the `func` function until the current call stack has cleared. - * Additional arguments will be passed to `func` when it is invoked. - * - * @static - * @memberOf _ - * @category Functions - * @param {Function} func The function to defer. - * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the function with. - * @returns {Number} Returns the `setTimeout` timeout id. - * @example - * - * _.defer(function() { alert('deferred'); }); - * // returns from the function before `alert` is called - */ - function defer(func) { - var args = slice(arguments, 1); - return setTimeout(function() { func.apply(undefined, args); }, 1); - } - - /** - * Creates a function that memoizes the result of `func`. If `resolver` is - * passed, it will be used to determine the cache key for storing the result - * based on the arguments passed to the memoized function. By default, the first - * argument passed to the memoized function is used as the cache key. The `func` - * is executed with the `this` binding of the memoized function. - * - * @static - * @memberOf _ - * @category Functions - * @param {Function} func The function to have its output memoized. - * @param {Function} [resolver] A function used to resolve the cache key. - * @returns {Function} Returns the new memoizing function. - * @example - * - * var fibonacci = _.memoize(function(n) { - * return n < 2 ? n : fibonacci(n - 1) + fibonacci(n - 2); - * }); - */ - function memoize(func, resolver) { - var cache = {}; - return function() { - var key = resolver ? resolver.apply(this, arguments) : arguments[0]; - return hasOwnProperty.call(cache, key) - ? cache[key] - : (cache[key] = func.apply(this, arguments)); - }; - } - - /** - * Creates a function that is restricted to execute `func` once. Repeat calls to - * the function will return the value of the first call. The `func` is executed - * with the `this` binding of the created function. - * - * @static - * @memberOf _ - * @category Functions - * @param {Function} func The function to restrict. - * @returns {Function} Returns the new restricted function. - * @example - * - * var initialize = _.once(createApplication); - * initialize(); - * initialize(); - * // Application is only created once. - */ - function once(func) { - var result, - ran = false; - - return function() { - if (ran) { - return result; + if (isCalled && !timeoutId && !maxTimeoutId) { + args = thisArg = null; } - ran = true; - result = func.apply(this, arguments); - - // clear the `func` variable so the function may be garbage collected - func = null; return result; }; } /** - * Creates a function that, when called, invokes `func` with any additional - * `partial` arguments prepended to those passed to the new function. This - * method is similar to `bind`, except it does **not** alter the `this` binding. + * Creates a function that, when executed, will only call the `func` function + * at most once per every `wait` milliseconds. 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 will + * return the result of the last `func` call. * - * @static - * @memberOf _ - * @category Functions - * @param {Function} func The function to partially apply arguments to. - * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. - * @returns {Function} Returns the new partially applied function. - * @example - * - * var greet = function(greeting, name) { return greeting + ': ' + name; }; - * var hi = _.partial(greet, 'hi'); - * hi('moe'); - * // => 'hi: moe' - */ - function partial(func) { - return createBound(func, slice(arguments, 1)); - } - - /** - * Creates a function that, when executed, will only call the `func` - * function at most once per every `wait` milliseconds. If the throttled - * function is invoked more than once during the `wait` timeout, `func` will - * also be called on the trailing edge of the timeout. Subsequent calls to the - * throttled function will return the result of the last `func` call. + * Note: If `leading` and `trailing` options are `true` `func` will be called + * on the trailing edge of the timeout only if the the throttled function is + * invoked more than once during the `wait` timeout. * * @static * @memberOf _ * @category Functions * @param {Function} func The function to throttle. - * @param {Number} wait The number of milliseconds to throttle executions to. + * @param {number} wait The number of milliseconds to throttle executions to. + * @param {Object} [options] The options object. + * @param {boolean} [options.leading=true] Specify execution on the leading edge of the timeout. + * @param {boolean} [options.trailing=true] Specify execution on the trailing edge of the timeout. * @returns {Function} Returns the new throttled function. * @example * + * // avoid excessively updating the position while scrolling * var throttled = _.throttle(updatePosition, 100); * jQuery(window).on('scroll', throttled); + * + * // execute `renewToken` when the click event is fired, but not more than once every 5 minutes + * jQuery('.interactive').on('click', _.throttle(renewToken, 300000, { + * 'trailing': false + * })); */ - function throttle(func, wait) { - var args, - result, - thisArg, - timeoutId, - lastCalled = 0; + function throttle(func, wait, options) { + var leading = true, + trailing = true; - function trailingCall() { - lastCalled = new Date; - timeoutId = null; - result = func.apply(thisArg, args); + if (!isFunction(func)) { + throw new TypeError; } - return function() { - var now = new Date, - remaining = wait - (now - lastCalled); - - args = arguments; - thisArg = this; - - if (remaining <= 0) { - clearTimeout(timeoutId); - timeoutId = null; - lastCalled = now; - result = func.apply(thisArg, args); - } - else if (!timeoutId) { - timeoutId = setTimeout(trailingCall, remaining); - } - return result; - }; - } + if (options === false) { + leading = false; + } else if (isObject(options)) { + leading = 'leading' in options ? options.leading : leading; + trailing = 'trailing' in options ? options.trailing : trailing; + } + debounceOptions.leading = leading; + debounceOptions.maxWait = wait; + debounceOptions.trailing = trailing; - /** - * Creates a function that passes `value` to the `wrapper` function as its - * first argument. Additional arguments passed to the function are appended - * to those passed to the `wrapper` function. The `wrapper` is executed with - * the `this` binding of the created function. - * - * @static - * @memberOf _ - * @category Functions - * @param {Mixed} value The value to wrap. - * @param {Function} wrapper The wrapper function. - * @returns {Function} Returns the new function. - * @example - * - * var hello = function(name) { return 'hello ' + name; }; - * hello = _.wrap(hello, function(func) { - * return 'before, ' + func('moe') + ', after'; - * }); - * hello(); - * // => 'before, hello moe, after' - */ - function wrap(value, wrapper) { - return function() { - var args = [value]; - push.apply(args, arguments); - return wrapper.apply(this, args); - }; + return debounce(func, wait, debounceOptions); } /*--------------------------------------------------------------------------*/ /** - * Converts the characters `&`, `<`, `>`, `"`, and `'` in `string` to their - * corresponding HTML entities. + * This method returns the first argument provided to it. * * @static * @memberOf _ * @category Utilities - * @param {String} string The string to escape. - * @returns {String} Returns the escaped string. + * @param {*} value Any value. + * @returns {*} Returns `value`. * @example * - * _.escape('Moe, Larry & Curly'); - * // => 'Moe, Larry & Curly' - */ - function escape(string) { - return string == null ? '' : (string + '').replace(reUnescapedHtml, escapeHtmlChar); - } - - /** - * This function returns the first argument passed to it. - * - * @static - * @memberOf _ - * @category Utilities - * @param {Mixed} value Any value. - * @returns {Mixed} Returns `value`. - * @example - * - * var moe = { 'name': 'moe' }; - * moe === _.identity(moe); + * var object = { 'name': 'fred' }; + * _.identity(object) === object; * // => true */ function identity(value) { @@ -10044,13 +10810,14 @@ var JXON = new (function () { } /** - * Adds functions properties of `object` to the `lodash` function and chainable - * wrapper. + * Adds function properties of a source object to the `lodash` function and + * chainable wrapper. * * @static * @memberOf _ * @category Utilities * @param {Object} object The object of function properties to add to `lodash`. + * @param {Object} object The object of function properties to add to `lodash`. * @example * * _.mixin({ @@ -10059,391 +10826,117 @@ var JXON = new (function () { * } * }); * - * _.capitalize('larry'); - * // => 'Larry' - * - * _('curly').capitalize(); - * // => 'Curly' - */ - function mixin(object) { - forEach(functions(object), function(methodName) { - var func = lodash[methodName] = object[methodName]; - - lodash.prototype[methodName] = function() { - var args = [this.__wrapped__]; - push.apply(args, arguments); - - var result = func.apply(lodash, args); - return new lodash(result); - }; - }); - } - - /** - * Reverts the '_' variable to its previous value and returns a reference to - * the `lodash` function. - * - * @static - * @memberOf _ - * @category Utilities - * @returns {Function} Returns the `lodash` function. - * @example + * _.capitalize('fred'); + * // => 'Fred' * - * var lodash = _.noConflict(); + * _('fred').capitalize(); + * // => 'Fred' */ - function noConflict() { - window._ = oldDash; - return this; - } - - /** - * Produces a random number between `min` and `max` (inclusive). If only one - * argument is passed, a number between `0` and the given number will be returned. - * - * @static - * @memberOf _ - * @category Utilities - * @param {Number} [min=0] The minimum possible value. - * @param {Number} [max=1] The maximum possible value. - * @returns {Number} Returns a random number. - * @example - * - * _.random(0, 5); - * // => a number between 1 and 5 - * - * _.random(5); - * // => also a number between 1 and 5 - */ - function random(min, max) { - if (min == null && max == null) { - max = 1; - } - min = +min || 0; - if (max == null) { - max = min; - min = 0; - } - return min + floor(nativeRandom() * ((+max || 0) - min + 1)); - } - - /** - * Resolves the value of `property` on `object`. If `property` is a function - * it will be invoked and its result returned, else the property value is - * returned. If `object` is falsey, then `null` is returned. - * - * @static - * @memberOf _ - * @category Utilities - * @param {Object} object The object to inspect. - * @param {String} property The property to get the value of. - * @returns {Mixed} Returns the resolved value. - * @example - * - * var object = { - * 'cheese': 'crumpets', - * 'stuff': function() { - * return 'nonsense'; - * } - * }; - * - * _.result(object, 'cheese'); - * // => 'crumpets' - * - * _.result(object, 'stuff'); - * // => 'nonsense' - */ - function result(object, property) { - // based on Backbone's private `getValue` function - // https://github.com/documentcloud/backbone/blob/0.9.2/backbone.js#L1419-1424 - var value = object ? object[property] : null; - return isFunction(value) ? object[property]() : value; - } - - /** - * A micro-templating method that handles arbitrary delimiters, preserves - * whitespace, and correctly escapes quotes within interpolated code. - * - * Note: In the development build `_.template` utilizes sourceURLs for easier - * debugging. See http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl - * - * Note: Lo-Dash may be used in Chrome extensions by either creating a `lodash csp` - * build and avoiding `_.template` use, or loading Lo-Dash in a sandboxed page. - * See http://developer.chrome.com/trunk/extensions/sandboxingEval.html - * - * @static - * @memberOf _ - * @category Utilities - * @param {String} text The template text. - * @param {Obect} data The data object used to populate the text. - * @param {Object} options The options object. - * escape - The "escape" delimiter regexp. - * evaluate - The "evaluate" delimiter regexp. - * interpolate - The "interpolate" delimiter regexp. - * sourceURL - The sourceURL of the template's compiled source. - * variable - The data object variable name. - * - * @returns {Function|String} Returns a compiled function when no `data` object - * is given, else it returns the interpolated text. - * @example - * - * // using a compiled template - * var compiled = _.template('hello <%= name %>'); - * compiled({ 'name': 'moe' }); - * // => 'hello moe' - * - * var list = '<% _.forEach(people, function(name) { %>
  • <%= name %>
  • <% }); %>'; - * _.template(list, { 'people': ['moe', 'larry', 'curly'] }); - * // => '
  • moe
  • larry
  • curly
  • ' - * - * // using the "escape" delimiter to escape HTML in data property values - * _.template('<%- value %>', { 'value': '