diff --git a/js/lib/d3.v3.js b/js/lib/d3.v3.js index 222849502..93b676f46 100644 --- a/js/lib/d3.v3.js +++ b/js/lib/d3.v3.js @@ -237,6 +237,14 @@ var H = (values.length - 1) * p + 1, h = Math.floor(H), v = values[h - 1], e = H - h; return e ? v + e * (values[h] - v) : v; }; + d3.shuffle = function(array) { + var m = array.length, t, i; + while (m) { + i = Math.random() * m-- | 0; + t = array[m], array[m] = array[i], array[i] = t; + } + return array; + }; d3.transpose = function(matrix) { return d3.zip.apply(d3, matrix); }; @@ -390,13 +398,14 @@ return n ? Math.round(x * (n = Math.pow(10, n))) / n : Math.round(x); }; d3.xhr = function(url, mimeType, callback) { - var xhr = {}, dispatch = d3.dispatch("progress", "load", "error"), headers = {}, response = d3_identity, request = new XMLHttpRequest(); - request.onreadystatechange = function() { - if (request.readyState === 4) { - var s = request.status; - !s && request.response || s >= 200 && s < 300 || s === 304 ? dispatch.load.call(xhr, response.call(xhr, request)) : dispatch.error.call(xhr, request); - } + var xhr = {}, dispatch = d3.dispatch("progress", "load", "error"), headers = {}, response = d3_identity, request = new (window.XDomainRequest && /^(http(s)?:)?\/\//.test(url) ? XDomainRequest : XMLHttpRequest)(); + "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); + } request.onprogress = function(event) { var o = d3.event; d3.event = event; @@ -430,7 +439,7 @@ if (arguments.length === 2 && typeof data === "function") callback = data, data = null; request.open(method, url, true); if (mimeType != null && !("accept" in headers)) headers["accept"] = mimeType + ",*/*"; - for (var name in headers) request.setRequestHeader(name, headers[name]); + if (request.setRequestHeader) for (var name in headers) request.setRequestHeader(name, headers[name]); if (mimeType != null && request.overrideMimeType) request.overrideMimeType(mimeType); if (callback != null) xhr.on("error", callback).on("load", function(request) { callback(null, request); @@ -3703,21 +3712,21 @@ this.on("mousedown.drag", mousedown).on("touchstart.drag", mousedown); } function mousedown() { - var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, touchId = d3.event.touches && d3.event.changedTouches[0].identifier, offset, origin_ = point(), moved = 0; - var w = d3.select(window).on(touchId ? "touchmove.drag-" + touchId : "mousemove.drag", dragmove).on(touchId ? "touchend.drag-" + touchId : "mouseup.drag", dragend, true); + var target = this, event_ = event.of(target, arguments), eventTarget = d3.event.target, touchId = d3.event.touches ? d3.event.changedTouches[0].identifier : null, offset, origin_ = point(), moved = 0; + var w = d3.select(window).on(touchId != null ? "touchmove.drag-" + touchId : "mousemove.drag", dragmove).on(touchId != null ? "touchend.drag-" + touchId : "mouseup.drag", dragend, true); if (origin) { offset = origin.apply(target, arguments); offset = [ offset.x - origin_[0], offset.y - origin_[1] ]; } else { offset = [ 0, 0 ]; } - if (!touchId) d3_eventCancel(); + if (touchId == null) d3_eventCancel(); event_({ type: "dragstart" }); function point() { var p = target.parentNode; - return touchId ? d3.touches(p).filter(function(p) { + return touchId != null ? d3.touches(p).filter(function(p) { return p.identifier === touchId; })[0] : d3.mouse(p); } @@ -3743,7 +3752,7 @@ d3_eventCancel(); if (d3.event.target === eventTarget) w.on("click.drag", click, true); } - w.on(touchId ? "touchmove.drag-" + touchId : "mousemove.drag", null).on(touchId ? "touchend.drag-" + touchId : "mouseup.drag", null); + w.on(touchId != null ? "touchmove.drag-" + touchId : "mousemove.drag", null).on(touchId != null ? "touchend.drag-" + touchId : "mouseup.drag", null); } function click() { d3_eventCancel(); @@ -4267,8 +4276,7 @@ this.on("mouseover.force", d3_layout_forceMouseover).on("mouseout.force", d3_layout_forceMouseout).call(drag); }; function dragmove(d) { - d.px = d3.event.x; - d.py = d3.event.y; + d.px = d3.event.x, d.py = d3.event.y; force.resume(); } return d3.rebind(force, event, "on"); @@ -4281,6 +4289,7 @@ } function d3_layout_forceMouseover(d) { d.fixed |= 4; + d.px = d.x, d.py = d.y; } function d3_layout_forceMouseout(d) { d.fixed &= 3; @@ -5365,6 +5374,7 @@ Polygon: function(polygon) { this.polygon(polygon.coordinates); }, + Sphere: d3_noop, object: function(object) { return d3_geo_typeObjects.hasOwnProperty(object.type) ? this[object.type](object) : this.geometry(object); }, @@ -5387,7 +5397,8 @@ MultiPoint: 1, MultiPolygon: 1, Point: 1, - Polygon: 1 + Polygon: 1, + Sphere: 1 }; var d3_geo_typeObjects = { Feature: 1, @@ -5496,7 +5507,7 @@ var o = typeof origin === "function" ? origin.apply(this, arguments) : origin; rotate = d3_geo_rotation(-o[0] * d3_radians, -o[1] * d3_radians, 0); var ring = []; - d3_geo_circleInterpolateCircle(interpolate, { + interpolate(null, null, 1, { lineTo: function(λ, φ) { var point = rotate.invert(λ, φ); point[0] *= d3_degrees; @@ -5521,13 +5532,13 @@ }; circle.precision = function(_) { if (!arguments.length) return precision; - interpolate = d3_geo_circleInterpolate(radians, (precision = +_) * d3_radians); + interpolate = d3_geo_circleInterpolate(angle * d3_radians, (precision = +_) * d3_radians); return circle; }; return circle.angle(90); }; function d3_geo_circleClip(degrees, rotate) { - var radians = degrees * d3_radians, cr = Math.cos(radians), center = [ cr, 0, 0 ], angle = d3_geo_circleAngle(center), interpolate = d3_geo_circleInterpolate(radians, 6 * d3_radians); + var radians = degrees * d3_radians, cr = Math.cos(radians), interpolate = d3_geo_circleInterpolate(radians, 6 * d3_radians); return { point: function(coordinates, context) { if (visible(coordinates = rotate(coordinates))) { @@ -5538,46 +5549,57 @@ clipLine(coordinates, context); }, polygon: function(polygon, context) { - d3_geo_circleClipPolygon(polygon, context, clipLine, interpolate, angle); + d3_geo_circleClipPolygon(polygon, context, clipLine, interpolate); + }, + sphere: function(context) { + d3_geo_projectionSphere(context, interpolate); } }; function visible(point) { return Math.cos(point[1]) * Math.cos(point[0]) > cr; } - function clipLine(coordinates, context, winding) { - if (!(n = coordinates.length)) return; - var point0 = rotate(coordinates[0]), point2, inside = visible(point0), keepWinding = winding != null, closed = keepWinding && inside, n, move0, line0; - if (inside) context.moveTo((move0 = point0)[0], point0[1]); + function clipLine(coordinates, context, ring) { + if (!(n = coordinates.length)) return [ ring && 0, false ]; + var point0 = rotate(coordinates[0]), point1, point2, v0 = visible(point0), v00 = ring && v0, v, n, clean = ring, area = 0, p, x0, x, y0, y; + if (clean) { + x0 = (p = d3_geo_stereographic(point0[0] + (v0 ? 0 : π), point0[1]))[0]; + y0 = p[1]; + } + if (v0) context.moveTo(point0[0], point0[1]); for (var i = 1; i < n; i++) { - var point1 = rotate(coordinates[i]), v = visible(point1); - if (v !== inside) { - if (inside = v) { - point2 = intersect(point1, point0); - if (!line0 || Math.abs(line0[0] - point2[0]) > ε || Math.abs(line0[1] - point2[1]) > ε) { - if (move0) keepWinding = false; - context.moveTo((move0 = point2)[0], point2[1]); - } - if (keepWinding) winding += d3_geo_circleWinding(point2, point1); - point0 = point2; - } else { - line0 = point2 = intersect(point0, point1); - context.lineTo(point2[0], point2[1]); - if (keepWinding) { - if (Math.abs(move0[0] - point2[0]) > ε || Math.abs(move0[1] - point2[1]) > ε) { - keepWinding = false; - } else { - winding += d3_geo_circleWinding(point0, move0); - } - } - point0 = point2; + point1 = rotate(coordinates[i]); + v = visible(point1); + if (v !== v0) { + point2 = intersect(point0, point1); + if (d3_geo_circlePointsEqual(point0, point2) || d3_geo_circlePointsEqual(point1, point2)) { + point1[0] += ε; + point1[1] += ε; + v = visible(point1); } } - if (keepWinding) winding += d3_geo_circleWinding(point0, point1); - if (v) context.lineTo(point1[0], point1[1]); + if (v !== v0) { + clean = false; + if (v0 = v) { + point2 = intersect(point1, point0); + context.moveTo(point2[0], point2[1]); + } else { + point2 = intersect(point0, point1); + context.lineTo(point2[0], point2[1]); + } + point0 = point2; + } + if (clean) { + p = d3_geo_stereographic(point1[0] + (v ? 0 : π), point1[1]); + x = p[0]; + y = p[1]; + area += y0 * x - x0 * y; + x0 = x; + y0 = y; + } + if (v && !d3_geo_circlePointsEqual(point0, point1)) context.lineTo(point1[0], point1[1]); point0 = point1; } - if (closed && v) context.closePath(); - return keepWinding && (!move0 || Math.abs(move0[0] - point0[0]) < ε && Math.abs(move0[1] - point0[1]) < ε) && winding; + return [ clean && area * .5, v00 && v ]; } function intersect(a, b) { var pa = d3_geo_circleCartesian(a, [ 0, 0, 0 ]), pb = d3_geo_circleCartesian(b, [ 0, 0, 0 ]); @@ -5593,44 +5615,50 @@ function d3_geo_circleInterpolate(radians, precision) { var cr = Math.cos(radians), sr = Math.sin(radians); return function(from, to, direction, context) { - var step = direction * precision; - from = from.angle; - to = to.angle; - if (from < to) from += 2 * π; - for (var step = precision, t = from; direction > 0 ? t > to : t < to; t -= step) { + 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 * π; + } else { + from = radians + direction * 2 * π; + to = radians; + } + for (var step = direction * precision, t = from; direction > 0 ? t > to : t < to; t -= step) { var c = Math.cos(t), s = Math.sin(t), point = d3_geo_circleSpherical([ cr, -sr * c, -sr * s ]); context.lineTo(point[0], point[1]); } }; } - function d3_geo_circleClipPolygon(coordinates, context, clipLine, interpolate, angle) { - var subject = [], clip = [], segments = [], buffer = d3_geo_circleBufferSegments(clipLine), winding = 0, count = 0; + function d3_geo_circleClipPolygon(coordinates, context, clipLine, interpolate) { + var subject = [], clip = [], segments = [], buffer = d3_geo_circleBufferSegments(clipLine), draw = [], visibleArea = 0, invisibleArea = 0, invisible = false; coordinates.forEach(function(ring) { - var x = buffer(ring, context), ringSegments = x[1]; - winding += x[0]; - var n = ringSegments.length; - if (!n) return; - count += n; - if (typeof x[0] === "number") { - var segment = ringSegments[0], point = segment[0], n = segment.length - 1, i = 0; + var x = buffer(ring, context), ringSegments = x[1], segment, n = ringSegments.length; + if (!n) { + invisible = true; + invisibleArea += x[0][0]; + return; + } + if (x[0][0] !== false) { + visibleArea += x[0][0]; + draw.push(segment = ringSegments[0]); + var point = segment[0], n = segment.length - 1, i = 0; context.moveTo(point[0], point[1]); while (++i < n) context.lineTo((point = segment[i])[0], point[1]); context.closePath(); return; } - segments = segments.concat(ringSegments); + if (n > 1 && x[0][1]) ringSegments.push(ringSegments.pop().concat(ringSegments.shift())); + segments = segments.concat(ringSegments.filter(d3_geo_circleSegmentLength1)); }); - if (count ? winding > 0 : winding < 0) { - var moved = false; - d3_geo_circleInterpolateCircle(interpolate, { - lineTo: function(x, y) { - (moved ? context.lineTo : (moved = true, context.moveTo))(x, y); - } - }); - context.closePath(); + if (!segments.length) { + if (visibleArea < 0 || invisible && invisibleArea < 0) { + d3_geo_projectionSphere(context, interpolate); + } } segments.forEach(function(segment) { - var p0 = segment[0], p1 = segment[segment.length - 1], a = { + var n = segment.length; + if (n <= 1) return; + var p0 = segment[0], p1 = segment[n - 1], a = { point: p0, points: segment, other: null, @@ -5639,7 +5667,6 @@ subject: true }, b = { point: p0, - angle: angle(p0), points: [ p0 ], other: a, visited: false, @@ -5659,7 +5686,6 @@ }; b = { point: p1, - angle: angle(p1), points: [ p1 ], other: a, visited: false, @@ -5686,7 +5712,7 @@ if (current.subject) { for (var i = 0; i < points.length; i++) context.lineTo((point = points[i])[0], point[1]); } else { - interpolate(current, current.next, 1, context); + interpolate(current.point, current.next.point, 1, context); } current = current.next; } else { @@ -5694,7 +5720,7 @@ points = current.prev.points; for (var i = points.length; --i >= 0; ) context.lineTo((point = points[i])[0], point[1]); } else { - interpolate(current, current.prev, -1, context); + interpolate(current.point, current.prev.point, -1, context); } current = current.prev; } @@ -5712,15 +5738,13 @@ } } function d3_geo_circleClipSort(a, b) { - return b.angle - a.angle; + 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_circleAngle(center) { - return function(point) { - var a = d3_geo_circleCartesian(point, center); - d3_geo_circleNormalize(a); - var angle = Math.acos(Math.max(-1, Math.min(1, -a[1]))); - return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI) % (2 * Math.PI); - }; + function d3_geo_circleAngle(cr, point) { + var a = d3_geo_circleCartesian(point, [ cr, 0, 0 ]); + d3_geo_circleNormalize(a); + var angle = Math.acos(Math.max(-1, Math.min(1, -a[1]))); + return ((-a[2] < 0 ? -angle : angle) + 2 * Math.PI - ε) % (2 * Math.PI); } function d3_geo_circleCartesian(point, origin) { var p0 = point[0], p1 = point[1], c1 = Math.cos(p1); @@ -5753,40 +5777,20 @@ return function(coordinates) { var segments = [], segment; return [ f(coordinates, { - point: d3_noop, moveTo: function(x, y) { segments.push(segment = [ [ x, y ] ]); }, lineTo: function(x, y) { segment.push([ x, y ]); - }, - closePath: function() { - if (segments.length < 2) return; - segments.pop(); - segments.push(segment = segment.concat(segments.shift())); } - }, 0), segments ]; + }, true), segments ]; }; } - function d3_geo_circleWinding(p0, p) { - var c0 = Math.cos(p0[1]), k0 = 1 + Math.cos(p0[0]) * c0, c = Math.cos(p[1]), k = 1 + Math.cos(p[0]) * c; - p0 = [ c0 * Math.sin(p0[0]) / k0, Math.sin(p0[1]) / k0 ]; - p = [ c * Math.sin(p[0]) / k, Math.sin(p[1]) / k ]; - if (p0[1] <= 0) { - if (p[1] > 0 && (p0[0] - p[0]) * p0[1] + p0[0] * (p[1] - p0[1]) > 0) return 1; - } else { - if (p[1] <= 0 && (p0[0] - p[0]) * p0[1] + p0[0] * (p[1] - p0[1]) < 0) return -1; - } - return 0; + function d3_geo_circlePointsEqual(a, b) { + return Math.abs(a[0] - b[0]) < ε && Math.abs(a[1] - b[1]) < ε; } - function d3_geo_circleInterpolateCircle(interpolate, context) { - for (var i = 0; i < 4; i++) { - interpolate({ - angle: -i * π / 2 - }, { - angle: -(i + 1) * π / 2 - }, 1, context); - } + function d3_geo_circleSegmentLength1(segment) { + return segment.length > 1; } function d3_geo_compose(a, b) { if (a === d3_geo_equirectangular) return b; @@ -5969,6 +5973,54 @@ buffer.push("Z"); } }; + var area, centroidWeight, x00, y00, x0, y0, cx, cy; + var areaContext = { + point: d3_noop, + moveTo: moveTo, + lineTo: function(x, y) { + area += y0 * x - x0 * y; + x0 = x; + y0 = y; + }, + closePath: closePath + }; + var lineCentroidContext = { + point: function(x, y) { + cx += x; + cy += y; + ++centroidWeight; + }, + moveTo: moveTo, + lineTo: function(x, y) { + var dx = x - x0, dy = y - y0, δ = Math.sqrt(dx * dx + dy * dy); + centroidWeight += δ; + cx += δ * (x0 + x) / 2; + cy += δ * (y0 + y) / 2; + x0 = x; + y0 = y; + }, + closePath: closePath + }; + var polygonCentroidContext = { + point: d3_noop, + moveTo: moveTo, + lineTo: function(x, y) { + var δ = y0 * x - x0 * y; + centroidWeight += δ * 3; + cx += δ * (x0 + x); + cy += δ * (y0 + y); + x0 = x; + y0 = y; + }, + closePath: closePath + }; + function moveTo(x, y) { + x00 = x0 = x; + y00 = y0 = y; + } + function closePath() { + this.lineTo(x00, y00); + } var context = bufferContext; function path(object) { var result = null; @@ -5988,6 +6040,9 @@ }, point: function(coordinates) { projection.point(coordinates, context); + }, + Sphere: function() { + projection.sphere(context); } }); var areaType = d3_geo_type({ @@ -6009,13 +6064,18 @@ Point: d3_zero, Polygon: function(polygon) { return polygonArea(polygon.coordinates); - } + }, + Sphere: sphereArea }); - function ringArea(coordinates) { - return Math.abs(d3.geom.polygon(coordinates.map(projection)).area()); - } function polygonArea(coordinates) { - return ringArea(coordinates[0]) - d3.sum(coordinates.slice(1), ringArea); + area = 0; + projection.polygon(coordinates, areaContext); + return Math.abs(area) / 2; + } + function sphereArea() { + area = 0; + projection.sphere(areaContext); + return Math.abs(area) / 2; } path.area = function(object) { return areaType.object(object); @@ -6024,42 +6084,37 @@ Feature: function(feature) { return centroidType.geometry(feature.geometry); }, - LineString: d3_geo_pathCentroid1(lineCentroid), - MultiLineString: d3_geo_pathCentroid2(lineCentroid), - MultiPoint: d3_geo_pathCentroid2(pointCentroid), - MultiPolygon: d3_geo_pathCentroid3(ringCentroid), - Point: d3_geo_pathCentroid1(pointCentroid), - Polygon: d3_geo_pathCentroid2(ringCentroid) + LineString: weightedCentroid(function(lineString) { + projection.line(lineString.coordinates, lineCentroidContext); + }), + MultiLineString: weightedCentroid(function(multiLineString) { + var coordinates = multiLineString.coordinates, i = -1, n = coordinates.length; + while (++i < n) projection.line(coordinates[i], lineCentroidContext); + }), + MultiPoint: weightedCentroid(function(multiPoint) { + var coordinates = multiPoint.coordinates, i = -1, n = coordinates.length; + while (++i < n) projection.point(coordinates[i], lineCentroidContext); + }), + MultiPolygon: weightedCentroid(function(multiPolygon) { + var coordinates = multiPolygon.coordinates, i = -1, n = coordinates.length; + while (++i < n) projection.polygon(coordinates[i], polygonCentroidContext); + }), + Point: weightedCentroid(function(point) { + projection.point(point.coordinates, lineCentroidContext); + }), + Polygon: weightedCentroid(function(polygon) { + projection.polygon(polygon.coordinates, polygonCentroidContext); + }), + Sphere: weightedCentroid(function() { + projection.sphere(polygonCentroidContext); + }) }); - function pointCentroid(centroid, point) { - point = projection(point); - centroid[0] += point[0]; - centroid[1] += point[1]; - return 1; - } - function lineCentroid(centroid, line) { - if (!(n = line.length)) return 0; - var n, point = projection(line[0]), x0 = point[0], y0 = point[1], x1, y1, dx, dy, i = 0, δ, z = 0; - while (++i < n) { - point = projection(line[i]); - x1 = point[0]; - y1 = point[1]; - dx = x1 - x0; - dy = y1 - y0; - z += δ = Math.sqrt(dx * dx + dy * dy); - centroid[0] += δ * (x0 + x1) / 2; - centroid[1] += δ * (y0 + y1) / 2; - x0 = x1; - y0 = y1; - } - return z; - } - function ringCentroid(centroid, ring, i) { - var polygon = d3.geom.polygon(ring.map(projection)), area = polygon.area(), point = polygon.centroid(area < 0 ? (area *= -1, - 1) : -1); - centroid[0] += point[0]; - centroid[1] += point[1]; - return area * (i > 0 ? -6 : 6); + function weightedCentroid(f) { + return function() { + centroidWeight = cx = cy = 0; + f.apply(this, arguments); + return centroidWeight ? [ cx / centroidWeight, cy / centroidWeight ] : null; + }; } path.bounds = function(object) { return (bounds || (bounds = d3_geo_bounds(projection)))(object); @@ -6089,30 +6144,17 @@ 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_pathCentroid1(weightedCentroid) { - return function(line) { - var centroid = [ 0, 0 ], z = weightedCentroid(centroid, line.coordinates, 0); - return z ? (centroid[0] /= z, centroid[1] /= z, centroid) : null; - }; - } - function d3_geo_pathCentroid2(weightedCentroid) { - return function(polygon) { - for (var centroid = [ 0, 0 ], z = 0, rings = polygon.coordinates, i = 0, n = rings.length; i < n; ++i) { - z += weightedCentroid(centroid, rings[i], i); - } - return z ? (centroid[0] /= z, centroid[1] /= z, centroid) : null; - }; - } - function d3_geo_pathCentroid3(weightedCentroid) { - return function(multiPolygon) { - for (var centroid = [ 0, 0 ], z = 0, polygons = multiPolygon.coordinates, i = 0, n = polygons.length; i < n; ++i) { - for (var rings = polygons[i], j = 0, m = rings.length; j < m; ++j) { - z += weightedCentroid(centroid, rings[j], j); - } - } - return z ? (centroid[0] /= z, centroid[1] /= z, centroid) : null; - }; - } + var d3_geo_pathIdentity = d3.geo.path().projection({ + polygon: function(polygon, context) { + polygon.forEach(function(ring) { + var n = ring.length, i = 0, point; + context.moveTo((point = ring[0])[0], point[1]); + while (++i < n) context.lineTo((point = ring[i])[0], point[1]); + context.closePath(); + }); + } + }); + d3.geo.centroid = d3_geo_pathIdentity.centroid; d3.geo.projection = d3_geo_projection; d3.geo.projectionMutator = d3_geo_projectionMutator; function d3_geo_projection(project) { @@ -6145,6 +6187,11 @@ clip.polygon(coordinates, resample); context = null; }; + projection.sphere = function(c) { + context = c; + clip.sphere(resample); + context = null; + }; projection.clipAngle = function(_) { if (!arguments.length) return clipAngle; clip = _ == null ? (clipAngle = _, d3_geo_projectionCutAntemeridian(rotatePoint)) : d3_geo_circleClip(clipAngle = +_, rotatePoint); @@ -6248,9 +6295,9 @@ var point = rotatePoint(coordinates); context.point(point[0], point[1]); }, - line: function(coordinates, context, winding) { - if (!(n = coordinates.length)) return; - var point = rotatePoint(coordinates[0]), keepWinding = true, λ0 = point[0], φ0 = point[1], λ1, φ1, sλ0 = λ0 > 0 ? π : -π, sλ1, dλ, i = 0, n; + line: function(coordinates, context, ring) { + if (!(n = coordinates.length)) return [ ring && 0, false ]; + var point = rotatePoint(coordinates[0]), λ0 = point[0], φ0 = point[1], λ1, φ1, sλ0 = λ0 > 0 ? π : -π, sλ1, dλ, i = 0, n, clean = ring, area = 0, x0 = (point = d3_geo_stereographic(λ0, φ0))[0], x, y0 = point[1], y; context.moveTo(λ0, φ0); while (++i < n) { point = rotatePoint(coordinates[i]); @@ -6263,35 +6310,51 @@ context.lineTo(sλ0, φ0); context.moveTo(sλ1, φ0); context.lineTo(λ1, φ0); - context.lineTo(λ0 = λ1, φ0 = φ1); - keepWinding = false; + clean = false; } else if (sλ0 !== sλ1 && dλ >= π) { + if (Math.abs(λ0 - sλ0) < ε) λ0 -= sλ0 * ε; + if (Math.abs(λ1 - sλ1) < ε) λ1 -= sλ1 * ε; φ0 = d3_geo_projectionIntersectAntemeridian(λ0, φ0, λ1, φ1); - if (Math.abs(λ0 - sλ0) > ε) context.lineTo(sλ0, φ0); - if (Math.abs(λ1 - sλ1) > ε) context.moveTo(sλ1, φ0), context.lineTo(λ0 = λ1, φ0 = φ1); else context.moveTo(λ0 = λ1, φ0 = φ1); - keepWinding = false; - } else { - context.lineTo(λ0 = λ1, φ0 = φ1); + context.lineTo(sλ0, φ0); + context.moveTo(sλ1, φ0); + clean = false; } + if (clean) { + x = (point = d3_geo_stereographic(λ1, φ1))[0]; + y = point[1]; + area += y0 * x - x0 * y; + x0 = x; + y0 = y; + } + context.lineTo(λ0 = λ1, φ0 = φ1); sλ0 = sλ1; } - if (winding != null) context.closePath(); - return keepWinding && winding; + return [ clean && area, true ]; }, polygon: function(polygon, context) { - d3_geo_circleClipPolygon(polygon, context, clip.line, d3_geo_antemeridianInterpolate, d3_geo_antemeridianAngle); + d3_geo_circleClipPolygon(polygon, context, clip.line, d3_geo_antemeridianInterpolate); + }, + sphere: function(context) { + d3_geo_projectionSphere(context, d3_geo_antemeridianInterpolate); } }; return clip; } - function d3_geo_antemeridianAngle(point) { - return -(point[0] < 0 ? point[1] - π / 2 : π / 2 - point[1]); - } function d3_geo_antemeridianInterpolate(from, to, direction, context) { - from = from.point; - to = to.point; - if (Math.abs(from[0] - to[0]) > ε) { - var s = (from[0] < to[0] ? 1 : -1) * direction * π, φ = s / 2; + var φ; + if (from == null) { + φ = direction * π / 2; + context.lineTo(-π, φ); + context.lineTo(0, φ); + context.lineTo(π, φ); + context.lineTo(π, 0); + context.lineTo(π, -φ); + context.lineTo(0, -φ); + context.lineTo(-π, -φ); + context.lineTo(-π, 0); + } else if (Math.abs(from[0] - to[0]) > ε) { + var s = (from[0] < to[0] ? 1 : -1) * π; + φ = direction * s / 2; context.lineTo(-s, φ); context.lineTo(0, φ); context.lineTo(s, φ); @@ -6299,6 +6362,15 @@ context.lineTo(to[0], to[1]); } } + function d3_geo_projectionSphere(context, interpolate) { + var moved = false; + interpolate(null, null, 1, { + lineTo: function(x, y) { + (moved ? context.lineTo : (moved = true, context.moveTo))(x, y); + } + }); + context.closePath(); + } function d3_geo_rotation(δλ, δφ, δγ) { return δλ ? δφ || δγ ? d3_geo_compose(d3_geo_rotationλ(δλ), d3_geo_rotationφγ(δφ, δγ)) : d3_geo_rotationλ(δλ) : δφ || δγ ? d3_geo_rotationφγ(δφ, δγ) : d3_geo_identityRotation; } @@ -6432,12 +6504,11 @@ } d3.geom.polygon = function(coordinates) { coordinates.area = function() { - var i = 0, n = coordinates.length, a = coordinates[n - 1][0] * coordinates[0][1], b = coordinates[n - 1][1] * coordinates[0][0]; + var i = 0, n = coordinates.length, area = coordinates[n - 1][1] * coordinates[0][0] - coordinates[n - 1][0] * coordinates[0][1]; while (++i < n) { - a += coordinates[i - 1][0] * coordinates[i][1]; - b += coordinates[i - 1][1] * coordinates[i][0]; + area += coordinates[i - 1][1] * coordinates[i][0] - coordinates[i - 1][0] * coordinates[i][1]; } - return (b - a) * .5; + return area * .5; }; coordinates.centroid = function(k) { var i = -1, n = coordinates.length, x = 0, y = 0, a, b = coordinates[n - 1], c; diff --git a/js/lib/lodash.js b/js/lib/lodash.js index 9eae931c2..10fcb392f 100644 --- a/js/lib/lodash.js +++ b/js/lib/lodash.js @@ -1,7 +1,7 @@ /*! - * Lo-Dash v0.9.2 + * Lo-Dash 1.0.0-rc.2 * (c) 2012 John-David Dalton - * Based on Underscore.js 1.4.2 + * Based on Underscore.js 1.4.3 * (c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. * Available under MIT license */ @@ -92,7 +92,6 @@ hasOwnProperty = objectRef.hasOwnProperty, push = arrayRef.push, propertyIsEnumerable = objectRef.propertyIsEnumerable, - slice = arrayRef.slice, toString = objectRef.toString; /* Native method shortcuts for methods with the same name as other `lodash` methods */ @@ -116,6 +115,17 @@ regexpClass = '[object RegExp]', stringClass = '[object String]'; + /** Detect various environments */ + var isFirefox = !/1/.test(Function('1')), + 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: * @@ -157,9 +167,6 @@ /** Detect if an `arguments` object's [[Class]] is unresolvable (Firefox < 4, IE < 9) */ var noArgsClass = !isArguments(arguments); - /** Detect if `Array#slice` cannot be used to convert strings to arrays (Opera < 10.52) */ - var noArraySliceOnStrings = slice.call('x')[0] != 'x'; - /** * Detect lack of support for accessing string characters by index: * @@ -174,20 +181,14 @@ * a string without a `toString` property value of `typeof` "function". */ try { - var noNodeClass = ({ 'toString': 0 } + '', toString.call(window.document || 0) == objectClass); + var noNodeClass = ({ 'toString': 0 } + '', toString.call(document) == objectClass); } catch(e) { } - /* Detect if `Function#bind` exists and is inferred to be fast (all but V8) */ - var isBindFast = nativeBind && /\n|Opera/.test(nativeBind + toString.call(window.opera)); - - /* Detect if `Object.keys` exists and is inferred to be fast (IE, Opera, V8) */ - var isKeysFast = nativeKeys && /^.+$|true/.test(nativeKeys + !!window.attachEvent); - /** * Detect if sourceURL syntax is usable without erroring: * - * The JS engine in Adobe products, like InDesign, will throw a syntax error - * when it encounters a single line comment beginning with the `@` symbol. + * 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. @@ -197,15 +198,26 @@ * http://msdn.microsoft.com/en-us/library/121hztk3(v=vs.94).aspx */ try { - var useSourceURL = (Function('//@')(), !window.attachEvent); + var useSourceURL = (Function('//@')(), !isIeOpera); } catch(e) { } /** Used to identify object classifications that `_.clone` supports */ var cloneableClasses = {}; - cloneableClasses[argsClass] = cloneableClasses[funcClass] = false; - cloneableClasses[arrayClass] = cloneableClasses[boolClass] = cloneableClasses[dateClass] = - cloneableClasses[numberClass] = cloneableClasses[objectClass] = cloneableClasses[regexpClass] = - cloneableClasses[stringClass] = true; + cloneableClasses[funcClass] = false; + cloneableClasses[argsClass] = cloneableClasses[arrayClass] = + cloneableClasses[boolClass] = cloneableClasses[dateClass] = + 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 to determine if values are of the language type Object */ var objectTypes = { @@ -240,8 +252,8 @@ * @returns {Object} Returns a `lodash` instance. */ function lodash(value) { - // exit early if already wrapped - if (value && value.__wrapped__) { + // 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 @@ -301,6 +313,25 @@ /*--------------------------------------------------------------------------*/ + /** + * Creates a function from the given `args` and `body` strings. + * + * @private + * @param {String} args The comma separated function arguments. + * @param {String} body The function body. + * @returns {Function} The new function. + */ + function createFunction(args, body) { + // the newline, in `'\n}'`, is required to avoid errors if `body` ends + // with a single line comment + return window.eval('(function(' + args + ') {' + body + '\n})'); + } + // use `eval` to avoid Firefox's unoptimized `Function` constructor + // http://bugzil.la/804933 + if (isIeOpera || isV8 || !isFirefox) { + createFunction = Function; + } + /** * The template used to create iterator functions. * @@ -310,10 +341,10 @@ */ var iteratorTemplate = template( // conditional strict mode - '<% if (obj.useStrict) { %>\'use strict\';\n<% } %>' + + "<% if (obj.useStrict) { %>'use strict';\n<% } %>" + // the `iteratee` may be reassigned by the `top` snippet - 'var index, value, iteratee = <%= firstArg %>, ' + + 'var index, iteratee = <%= firstArg %>, ' + // assign the `result` variable an initial value 'result = <%= firstArg %>;\n' + // exit early if the first argument is falsey @@ -324,18 +355,17 @@ // array-like iteration: '<% if (arrayLoop) { %>' + 'var length = iteratee.length; index = -1;\n' + - 'if (typeof length == \'number\') {' + + "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' + + " iteratee = iteratee.split('')\n" + ' }' + ' <% } %>\n' + // iterate over the array-like value ' while (++index < length) {\n' + - ' value = iteratee[index];\n' + ' <%= arrayLoop %>\n' + ' }\n' + '}\n' + @@ -347,7 +377,7 @@ ' var length = iteratee.length; index = -1;\n' + ' if (length && isArguments(iteratee)) {\n' + ' while (++index < length) {\n' + - ' value = iteratee[index += \'\'];\n' + + " index += '';\n" + ' <%= objectLoop %>\n' + ' }\n' + ' } else {' + @@ -360,8 +390,8 @@ // 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' + + " var skipProto = typeof iteratee == 'function' && \n" + + " propertyIsEnumerable.call(iteratee, 'prototype');\n" + ' <% } %>' + // iterate own properties using `Object.keys` if it's fast @@ -371,8 +401,7 @@ ' length = ownProps.length;\n\n' + ' while (++ownIndex < length) {\n' + ' index = ownProps[ownIndex];\n' + - ' <% if (!hasDontEnumBug) { %>if (!(skipProto && index == \'prototype\')) {\n <% } %>' + - ' value = iteratee[index];\n' + + " <% if (!hasDontEnumBug) { %>if (!(skipProto && index == 'prototype')) {\n <% } %>" + ' <%= objectLoop %>\n' + ' <% if (!hasDontEnumBug) { %>}\n<% } %>' + ' }' + @@ -381,12 +410,11 @@ ' <% } else { %>\n' + ' for (index in iteratee) {<%' + ' if (!hasDontEnumBug || useHas) { %>\n if (<%' + - ' if (!hasDontEnumBug) { %>!(skipProto && index == \'prototype\')<% }' + + " if (!hasDontEnumBug) { %>!(skipProto && index == 'prototype')<% }" + ' if (!hasDontEnumBug && useHas) { %> && <% }' + ' if (useHas) { %>hasOwnProperty.call(iteratee, index)<% }' + ' %>) {' + ' <% } %>\n' + - ' value = iteratee[index];\n' + ' <%= objectLoop %>;' + ' <% if (!hasDontEnumBug || useHas) { %>\n }<% } %>\n' + ' }' + @@ -399,12 +427,11 @@ ' <% if (hasDontEnumBug) { %>\n\n' + ' var ctor = iteratee.constructor;\n' + ' <% for (var k = 0; k < 7; k++) { %>\n' + - ' index = \'<%= shadowed[k] %>\';\n' + + " index = '<%= shadowed[k] %>';\n" + ' if (<%' + - ' if (shadowed[k] == \'constructor\') {' + + " if (shadowed[k] == 'constructor') {" + ' %>!(ctor && ctor.prototype === iteratee) && <%' + ' } %>hasOwnProperty.call(iteratee, index)) {\n' + - ' value = iteratee[index];\n' + ' <%= objectLoop %>\n' + ' }' + ' <% } %>' + @@ -417,25 +444,24 @@ 'return result' ); + /** 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}' + }; + /** * Reusable iterator options shared by `forEach`, `forIn`, and `forOwn`. */ var forEachIteratorOptions = { 'args': 'collection, callback, thisArg', - 'top': 'callback = createCallback(callback, thisArg)', - 'arrayLoop': 'if (callback(value, index, collection) === false) return result', - 'objectLoop': 'if (callback(value, index, collection) === false) return result' - }; - - /** Reusable iterator options for `defaults`, and `extend` */ - var extendIteratorOptions = { - 'useHas': false, - 'args': 'object', - 'top': - 'for (var argsIndex = 1, argsLength = arguments.length; argsIndex < argsLength; argsIndex++) {\n' + - ' if (iteratee = arguments[argsIndex]) {', - 'objectLoop': 'result[index] = value', - 'bottom': ' }\n}' + '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' }; /** Reusable iterator options for `forIn` and `forOwn` */ @@ -513,10 +539,10 @@ // 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 || a === undefined) { + if (a > b || typeof a == 'undefined') { return 1; } - if (a < b || b === undefined) { + if (a < b || typeof b == 'undefined') { return -1; } } @@ -537,12 +563,15 @@ function createBound(func, thisArg, partialArgs) { var isFunc = isFunction(func), isPartial = !partialArgs, - methodName = func; + key = thisArg; // juggle arguments if (isPartial) { partialArgs = thisArg; } + if (!isFunc) { + thisArg = func; + } function bound() { // `Function#bind` spec @@ -551,24 +580,23 @@ thisBinding = isPartial ? this : thisArg; if (!isFunc) { - func = thisArg[methodName]; + func = thisArg[key]; } if (partialArgs.length) { args = args.length - ? partialArgs.concat(slice.call(args)) + ? partialArgs.concat(slice(args)) : partialArgs; } if (this instanceof bound) { - // get `func` instance if `bound` is invoked in a `new` expression + // ensure `new bound` is an instance of `bound` and `func` noop.prototype = func.prototype; thisBinding = new noop; + noop.prototype = null; // mimic the constructor's `return` behavior // http://es5.github.com/#x13.2.2 var result = func.apply(thisBinding, args); - return isObject(result) - ? result - : thisBinding + return isObject(result) ? result : thisBinding; } return func.apply(thisBinding, args); } @@ -583,9 +611,11 @@ * @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 {Boolean} [accumulating] A flag to indicate creating a callback + * that accepts an `accumulator` argument. * @returns {Function} Returns a callback function. */ - function createCallback(func, thisArg) { + function createCallback(func, thisArg, accumulating) { if (!func) { return identity; } @@ -594,7 +624,12 @@ return object[func]; }; } - if (thisArg !== undefined) { + 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); }; @@ -640,7 +675,7 @@ data.firstArg = /^[^,]+/.exec(args)[0]; // create the function factory - var factory = Function( + var factory = createFunction( 'createCallback, hasOwnProperty, isArguments, isString, objectTypes, ' + 'nativeKeys, propertyIsEnumerable', 'return function(' + args + ') {\n' + iteratorTemplate(data) + '\n}' @@ -675,6 +710,19 @@ return htmlEscapes[match]; } + /** + * 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`. + */ + function isNode(value) { + // IE < 9 presents DOM nodes as `Object` objects except they have `toString` + // methods that are `typeof` "string" and still can coerce nodes to strings + return typeof value.toString != 'function' && typeof (value + '') == 'string'; + } + /** * A no-operation function. * @@ -684,6 +732,34 @@ // no operation performed } + /** + * 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 + * 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. + * @returns {Array} Returns the new array. + */ + function slice(array, start, end) { + start || (start = 0); + if (typeof end == 'undefined') { + end = array ? array.length : 0; + } + var index = -1, + length = end - start || 0, + result = Array(length < 0 ? 0 : length); + + while (++index < length) { + result[index] = array[start + index]; + } + return result; + } + /** * Used by `unescape` to convert HTML entities to characters. * @@ -697,6 +773,25 @@ /*--------------------------------------------------------------------------*/ + /** + * Assigns own enumerable properties of source object(s) to the `destination` + * object. Subsequent sources will overwrite propery assignments of previous + * sources. + * + * @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 + * + * _.assign({ 'name': 'moe' }, { 'age': 40 }); + * // => { 'name': 'moe', 'age': 40 } + */ + var assign = createIterator(assignIteratorOptions); + /** * Checks if `value` is an `arguments` object. * @@ -733,7 +828,7 @@ * @memberOf _ * @category Objects * @param {Object} object The object to iterate over. - * @param {Function} callback The function called per iteration. + * @param {Function} [callback=identity] The function called per iteration. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns `object`. * @example @@ -756,7 +851,7 @@ }); /** - * Iterates over `object`'s own enumerable properties, executing the `callback` + * 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`. @@ -765,7 +860,7 @@ * @memberOf _ * @category Objects * @param {Object} object The object to iterate over. - * @param {Function} callback The function called per iteration. + * @param {Function} [callback=identity] The function called per iteration. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Object} Returns `object`. * @example @@ -793,12 +888,9 @@ if (!(value && typeof value == 'object') || isArguments(value)) { return result; } - // IE < 9 presents DOM nodes as `Object` objects except they have `toString` - // methods that are `typeof` "string" and still can coerce nodes to strings. - // Also check that the constructor is `Object` (i.e. `Object instanceof Object`) + // check that the constructor is `Object` (i.e. `Object instanceof Object`) var ctor = value.constructor; - if ((!noNodeClass || !(typeof value.toString != 'function' && typeof (value + '') == 'string')) && - (!isFunction(ctor) || ctor instanceof ctor)) { + 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. @@ -859,9 +951,13 @@ /** * Creates a clone of `value`. If `deep` is `true`, all nested objects will - * also be cloned otherwise they will be assigned by reference. Functions, DOM - * nodes, `arguments` objects, and objects created by constructors other than - * `Object` are **not** cloned. + * also be cloned, otherwise they will be assigned by reference. 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: Lo-Dash's deep clone functionality 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 _ @@ -890,7 +986,7 @@ * // => true * * var deep = _.clone(stooges, true); - * shallow[0] === stooges[0]; + * deep[0] === stooges[0]; * // => false */ function clone(value, deep, guard, stackA, stackB) { @@ -903,23 +999,19 @@ // inspect [[Class]] var isObj = isObject(value); if (isObj) { - // don't clone `arguments` objects, functions, or non-object Objects var className = toString.call(value); - if (!cloneableClasses[className] || (noArgsClass && isArguments(value))) { + if (!cloneableClasses[className] || (noNodeClass && isNode(value))) { return value; } - var isArr = className == arrayClass; - isObj = isArr || (className == objectClass ? isPlainObject(value) : isObj); + var isArr = isArray(value); } // shallow clone if (!isObj || !deep) { - // don't clone functions return isObj - ? (isArr ? slice.call(value) : extend({}, value)) + ? (isArr ? slice(value) : assign({}, value)) : value; } - - var ctor = value.constructor; + var ctor = ctorByClass[className]; switch (className) { case boolClass: case dateClass: @@ -955,11 +1047,20 @@ result[key] = clone(objValue, deep, null, 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; + } + } return result; } /** - * Assigns enumerable properties of the default object(s) to the `destination` + * 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. @@ -976,28 +1077,10 @@ * _.defaults(iceCream, { 'flavor': 'vanilla', 'sprinkles': 'rainbow' }); * // => { 'flavor': 'chocolate', 'sprinkles': 'rainbow' } */ - var defaults = createIterator(extendIteratorOptions, { - 'objectLoop': 'if (result[index] == null) ' + extendIteratorOptions.objectLoop + var defaults = createIterator(assignIteratorOptions, { + 'objectLoop': 'if (result[index] == null) ' + assignIteratorOptions.objectLoop }); - /** - * Assigns enumerable properties of the source object(s) to the `destination` - * object. Subsequent sources will overwrite propery assignments of previous - * sources. - * - * @static - * @memberOf _ - * @category Objects - * @param {Object} object The destination object. - * @param {Object} [source1, source2, ...] The source objects. - * @returns {Object} Returns the destination object. - * @example - * - * _.extend({ 'name': 'moe' }, { 'age': 40 }); - * // => { 'name': 'moe', 'age': 40 } - */ - var extend = createIterator(extendIteratorOptions); - /** * Creates a sorted array of all enumerable properties, own and inherited, * of `object` that have function values. @@ -1208,8 +1291,16 @@ return a === b; } // compare [[Class]] names - var className = toString.call(a); - if (className != toString.call(b)) { + var className = toString.call(a), + otherName = toString.call(b); + + if (className == argsClass) { + className = objectClass; + } + if (otherName == argsClass) { + otherName = objectClass; + } + if (className != otherName) { return false; } switch (className) { @@ -1232,24 +1323,19 @@ // treat string primitives and their corresponding object instances as equal return a == b + ''; } - // exit early, in older browsers, if `a` is array-like but not `b` - var isArr = className == arrayClass || className == argsClass; - if (noArgsClass && !isArr && (isArr = isArguments(a)) && !isArguments(b)) { - return false; - } + var isArr = className == arrayClass; if (!isArr) { // unwrap any `lodash` wrapped values if (a.__wrapped__ || b.__wrapped__) { return isEqual(a.__wrapped__ || a, b.__wrapped__ || b); } // exit for functions and DOM nodes - if (className != objectClass || (noNodeClass && ( - (typeof a.toString != 'function' && typeof (a + '') == 'string') || - (typeof b.toString != 'function' && typeof (b + '') == 'string')))) { + if (className != objectClass || (noNodeClass && (isNode(a) || isNode(b)))) { return false; } - var ctorA = a.constructor, - ctorB = b.constructor; + // in older versions of Opera, `arguments` objects have `Array` constructors + var ctorA = noArgsClass && isArguments(a) ? Object : a.constructor, + ctorB = noArgsClass && isArguments(b) ? Object : b.constructor; // non `Object` object instances with different constructors are not equal if (ctorA != ctorB && !( @@ -1271,7 +1357,6 @@ return stackB[length] == b; } } - var index = -1, result = true, size = 0; @@ -1296,38 +1381,27 @@ } return result; } - // deep compare objects - for (var key in a) { + // 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)) { // count the number of properties. size++; // deep compare each property value. - if (!(hasOwnProperty.call(b, key) && isEqual(a[key], b[key], stackA, stackB))) { - return false; + return (result = hasOwnProperty.call(b, key) && isEqual(value, b[key], stackA, stackB)); + } + }); + + if (result) { + // 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` + return (result = --size > -1); } - } + }); } - // ensure both objects have the same number of properties - for (key in b) { - // The JS engine in Adobe products, like InDesign, has a bug that causes - // `!size--` to throw an error so it must be wrapped in parentheses. - // https://github.com/documentcloud/underscore/issues/355 - if (hasOwnProperty.call(b, key) && !(size--)) { - // `size` will be `-1` if `b` has more properties than `a` - return false; - } - } - // handle JScript [[DontEnum]] bug - if (hasDontEnumBug) { - while (++index < 7) { - key = shadowed[index]; - if (hasOwnProperty.call(a, key) && - !(hasOwnProperty.call(b, key) && isEqual(a[key], b[key], stackA, stackB))) { - return false; - } - } - } - return true; + return result; } /** @@ -1336,7 +1410,6 @@ * 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. * - * @deprecated * @static * @memberOf _ * @category Objects @@ -1417,10 +1490,9 @@ /** * Checks if `value` is `NaN`. * - * Note: This is not the same as native `isNaN`, which will return true for + * 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. * - * @deprecated * @static * @memberOf _ * @category Objects @@ -1443,13 +1515,12 @@ 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 toString.call(value) == numberClass && value != +value + return isNumber(value) && value != +value } /** * Checks if `value` is `null`. * - * @deprecated * @static * @memberOf _ * @category Objects @@ -1481,7 +1552,7 @@ * // => true */ function isNumber(value) { - return toString.call(value) == numberClass; + return typeof value == 'number' || toString.call(value) == numberClass; } /** @@ -1551,13 +1622,12 @@ * // => true */ function isString(value) { - return toString.call(value) == stringClass; + return typeof value == 'string' || toString.call(value) == stringClass; } /** * Checks if `value` is `undefined`. * - * @deprecated * @static * @memberOf _ * @category Objects @@ -1569,7 +1639,7 @@ * // => true */ function isUndefined(value) { - return value === undefined; + return typeof value == 'undefined'; } /** @@ -1630,10 +1700,14 @@ stackA = args[3], stackB = args[4]; - if (indicator !== objectRef) { + if (indicator !== indicatorObject) { stackA = []; stackB = []; - length = args.length; + + // work with `_.reduce` by only using its callback `accumulator` and `value` arguments + if (typeof indicator != 'number') { + length = args.length; + } } while (++index < length) { forOwn(args[index], function(source, key) { @@ -1658,7 +1732,7 @@ : (isPlainObject(value) ? value : {}) ); // recursively merge objects and arrays (susceptible to call stack limits) - object[key] = merge(value, source, objectRef, stackA, stackB); + object[key] = merge(value, source, indicatorObject, stackA, stackB); } } else if (source != null) { object[key] = source; @@ -1836,18 +1910,23 @@ */ function contains(collection, target, fromIndex) { var index = -1, - length = collection ? collection.length : 0; + length = collection ? collection.length : 0, + result = false; fromIndex = (fromIndex < 0 ? nativeMax(0, length + fromIndex) : fromIndex) || 0; if (typeof length == 'number') { - return (isString(collection) + result = (isString(collection) ? collection.indexOf(target, fromIndex) : indexOf(collection, target, fromIndex) ) > -1; + } else { + forEach(collection, function(value) { + if (++index >= fromIndex) { + return !(result = value === target); + } + }); } - return some(collection, function(value) { - return ++index >= fromIndex && value === target; - }); + return result; } /** @@ -1879,6 +1958,7 @@ 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); @@ -1947,11 +2027,24 @@ function filter(collection, callback, thisArg) { var result = []; callback = createCallback(callback, thisArg); - forEach(collection, function(value, index, collection) { - if (callback(value, index, collection)) { - result.push(value); + + if (isArray(collection)) { + var index = -1, + length = collection.length; + + while (++index < length) { + var value = collection[index]; + if (callback(value, index, collection)) { + result.push(value); + } } - }); + } else { + forEach(collection, function(value, index, collection) { + if (callback(value, index, collection)) { + result.push(value); + } + }); + } return result; } @@ -1966,7 +2059,7 @@ * @alias detect * @category Collections * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function} callback The function called per iteration. + * @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`. @@ -1978,6 +2071,7 @@ function find(collection, callback, thisArg) { var result; callback = createCallback(callback, thisArg); + forEach(collection, function(value, index, collection) { if (callback(value, index, collection)) { result = value; @@ -1998,7 +2092,7 @@ * @alias each * @category Collections * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function} callback The function called per iteration. + * @param {Function} [callback=identity] The function called per iteration. * @param {Mixed} [thisArg] The `this` binding of `callback`. * @returns {Array|Object|String} Returns `collection`. * @example @@ -2007,7 +2101,7 @@ * // => alerts each number and returns '1,2,3' * * _.forEach({ 'one': 1, 'two': 2, 'three': 3 }, alert); - * // => alerts each number (order is not guaranteed) + * // => alerts each number value (order is not guaranteed) */ var forEach = createIterator(forEachIteratorOptions); @@ -2040,6 +2134,7 @@ function groupBy(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] = []).push(value); @@ -2070,7 +2165,7 @@ * // => [['1', '2', '3'], ['4', '5', '6']] */ function invoke(collection, methodName) { - var args = slice.call(arguments, 2), + var args = slice(arguments, 2), isFunc = typeof methodName == 'function', result = []; @@ -2239,11 +2334,7 @@ * // => ['moe', 'larry', 'curly'] */ function pluck(collection, property) { - var result = []; - forEach(collection, function(value) { - result.push(value[property]); - }); - return result; + return map(collection, property + ''); } /** @@ -2257,7 +2348,7 @@ * @alias foldl, inject * @category Collections * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function} callback The function called per iteration. + * @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. @@ -2268,12 +2359,25 @@ */ function reduce(collection, callback, accumulator, thisArg) { var noaccum = arguments.length < 3; - callback = createCallback(callback, thisArg); - forEach(collection, function(value, index, collection) { - accumulator = noaccum - ? (noaccum = false, value) - : callback(accumulator, value, index, collection) - }); + callback = createCallback(callback, thisArg, true); + + if (isArray(collection)) { + var index = -1, + length = collection.length; + + if (noaccum) { + accumulator = collection[++index]; + } + while (++index < length) { + accumulator = callback(accumulator, collection[index], index, collection); + } + } else { + forEach(collection, function(value, index, collection) { + accumulator = noaccum + ? (noaccum = false, value) + : callback(accumulator, value, index, collection) + }); + } return accumulator; } @@ -2285,7 +2389,7 @@ * @alias foldr * @category Collections * @param {Array|Object|String} collection The collection to iterate over. - * @param {Function} callback The function called per iteration. + * @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. @@ -2306,11 +2410,12 @@ } else if (noCharByIndex && isString(collection)) { iteratee = collection.split(''); } + callback = createCallback(callback, thisArg, true); forEach(collection, function(value, index, collection) { index = props ? props[--length] : --length; accumulator = noaccum ? (noaccum = false, iteratee[index]) - : callback.call(thisArg, accumulator, iteratee[index], index, collection); + : callback(accumulator, iteratee[index], index, collection); }); return accumulator; } @@ -2407,7 +2512,7 @@ * else `false`. * @example * - * _.some([null, 0, 'yes', false]); + * _.some([null, 0, 'yes', false], Boolean); * // => true */ function some(collection, callback, thisArg) { @@ -2419,7 +2524,7 @@ length = collection.length; while (++index < length) { - if (result = callback(collection[index], index, collection)) { + if ((result = callback(collection[index], index, collection))) { break; } } @@ -2459,6 +2564,7 @@ function sortBy(collection, callback, thisArg) { var result = []; callback = createCallback(callback, thisArg); + forEach(collection, function(value, index, collection) { result.push({ 'criteria': callback(value, index, collection), @@ -2476,7 +2582,7 @@ } /** - * Converts the `collection`, to an array. + * Converts the `collection` to an array. * * @static * @memberOf _ @@ -2489,10 +2595,11 @@ * // => [2, 3, 4] */ function toArray(collection) { - if (collection && typeof collection.length == 'number') { - return (noArraySliceOnStrings ? isString(collection) : typeof collection == 'string') + var length = collection ? collection.length : 0; + if (typeof length == 'number') { + return noCharByIndex && isString(collection) ? collection.split('') - : slice.call(collection); + : slice(collection); } return values(collection); } @@ -2519,10 +2626,7 @@ * // => [{ 'name': 'moe', 'age': 40 }] */ function where(collection, properties) { - var props = []; - forIn(properties, function(value, prop) { - props.push(prop); - }); + var props = keys(properties); return filter(collection, function(object) { var length = props.length; while (length--) { @@ -2609,8 +2713,8 @@ * @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`. + * @returns {Mixed} Returns the first element, or an array of the first `n` + * elements, of `array`. * @example * * _.first([5, 4, 3, 2, 1]); @@ -2618,7 +2722,10 @@ */ function first(array, n, guard) { if (array) { - return (n == null || guard) ? array[0] : slice.call(array, 0, n); + var length = array.length; + return (n == null || guard) + ? array[0] + : slice(array, 0, nativeMin(nativeMax(0, n), length)); } } @@ -2711,16 +2818,19 @@ * @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`. + * @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) { - return array - ? slice.call(array, 0, -((n == null || guard) ? 1 : n)) - : []; + if (!array) { + return []; + } + var length = array.length; + n = n == null || guard ? 1 : n || 0; + return slice(array, 0, nativeMin(nativeMax(0, length - n), length)); } /** @@ -2769,8 +2879,8 @@ * @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`. + * @returns {Mixed} Returns the last element, or an array of the last `n` + * elements, of `array`. * @example * * _.last([3, 2, 1]); @@ -2779,7 +2889,7 @@ function last(array, n, guard) { if (array) { var length = array.length; - return (n == null || guard) ? array[length - 1] : slice.call(array, -n || length); + return (n == null || guard) ? array[length - 1] : slice(array, nativeMax(0, length - n)); } } @@ -2887,7 +2997,7 @@ start = 0; } // use `Array(length)` so V8 will avoid the slower "dictionary" mode - // http://www.youtube.com/watch?v=XAqIpGU8ZZk#t=16m27s + // http://youtu.be/XAqIpGU8ZZk#t=17m25s var index = -1, length = nativeMax(0, ceil((end - start) / step)), result = Array(length); @@ -2911,16 +3021,14 @@ * @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 value or `n` values of `array`. + * @returns {Array} Returns all but the first element, or `n` elements, of `array`. * @example * * _.rest([3, 2, 1]); * // => [2, 1] */ function rest(array, n, guard) { - return array - ? slice.call(array, (n == null || guard) ? 1 : n) - : []; + return slice(array, (n == null || guard) ? 1 : nativeMax(0, n)); } /** @@ -2967,9 +3075,10 @@ var low = 0, high = array ? array.length : low; - // explicitly reference `identity` for better engine inlining + // explicitly reference `identity` for better inlining in Firefox callback = callback ? createCallback(callback, thisArg) : identity; value = callback(value); + while (low < high) { var mid = (low + high) >>> 1; callback(array[mid]) < value @@ -3056,11 +3165,13 @@ if (isLarge) { // manually coerce `computed` to a string because `hasOwnProperty`, in // some older versions of Firefox, coerces objects incorrectly - seen = hasOwnProperty.call(cache, computed + '') ? cache[computed] : (cache[computed] = []); + var inited = hasOwnProperty.call(cache, computed + '') + ? !(seen = cache[computed]) + : (seen = []); } if (isSorted ? !index || seen[seen.length - 1] !== computed - : indexOf(seen, computed) < 0 + : inited || indexOf(seen, computed) < 0 ) { if (callback || isLarge) { seen.push(computed); @@ -3188,7 +3299,7 @@ // (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.call(arguments, 2)); + : createBound(func, thisArg, slice(arguments, 2)); } /** @@ -3225,6 +3336,44 @@ return object; } + /** + * 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. + * + * @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. + * @example + * + * var object = { + * 'name': 'moe', + * 'greet': function(greeting) { + * return greeting + ' ' + this.name; + * } + * }; + * + * var func = _.bindKey(object, 'greet', 'hi'); + * func(); + * // => 'hi moe' + * + * object.greet = function(greeting) { + * return greeting + ', ' + this.name + '!'; + * }; + * + * func(); + * // => 'hi, moe!' + */ + function bindKey(object, key) { + return createBound(object, key, slice(arguments, 2)); + } + /** * Creates a function that is the composition of the passed functions, * where each function consumes the return value of the function that follows. @@ -3322,7 +3471,7 @@ * // => 'logged later' (Appears after one second.) */ function delay(func, wait) { - var args = slice.call(arguments, 2); + var args = slice(arguments, 2); return setTimeout(function() { func.apply(undefined, args); }, wait); } @@ -3342,47 +3491,10 @@ * // returns from the function before `alert` is called */ function defer(func) { - var args = slice.call(arguments, 1); + var args = slice(arguments, 1); return setTimeout(function() { func.apply(undefined, args); }, 1); } - /** - * Creates a function that, when called, invokes `object[methodName]` and - * prepends any additional `lateBind` 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. - * - * @static - * @memberOf _ - * @category Functions - * @param {Object} object The object the method belongs to. - * @param {String} methodName The method name. - * @param {Mixed} [arg1, arg2, ...] Arguments to be partially applied. - * @returns {Function} Returns the new bound function. - * @example - * - * var object = { - * 'name': 'moe', - * 'greet': function(greeting) { - * return greeting + ' ' + this.name; - * } - * }; - * - * var func = _.lateBind(object, 'greet', 'hi'); - * func(); - * // => 'hi moe' - * - * object.greet = function(greeting) { - * return greeting + ', ' + this.name + '!'; - * }; - * - * func(); - * // => 'hi, moe!' - */ - function lateBind(object, methodName) { - return createBound(methodName, object, slice.call(arguments, 2)); - } - /** * 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 @@ -3465,7 +3577,7 @@ * // => 'hi: moe' */ function partial(func) { - return createBound(func, slice.call(arguments, 1)); + return createBound(func, slice(arguments, 1)); } /** @@ -3507,6 +3619,7 @@ if (remaining <= 0) { clearTimeout(timeoutId); + timeoutId = null; lastCalled = now; result = func.apply(thisArg, args); } @@ -3560,7 +3673,7 @@ * @example * * _.escape('Moe, Larry & Curly'); - * // => "Moe, Larry & Curly" + * // => 'Moe, Larry & Curly' */ function escape(string) { return string == null ? '' : (string + '').replace(reUnescapedHtml, escapeHtmlChar); @@ -3569,7 +3682,7 @@ /** * This function returns the first argument passed to it. * - * Note: It is used throughout Lo-Dash as a default callback. + * Note: This function is used throughout Lo-Dash as a default callback. * * @static * @memberOf _ @@ -3617,11 +3730,7 @@ push.apply(args, arguments); var result = func.apply(lodash, args); - if (this.__chain__) { - result = new lodash(result); - result.__chain__ = true; - } - return result; + return new lodash(result); }; }); } @@ -3678,7 +3787,6 @@ * it will be invoked and its result returned, else the property value is * returned. If `object` is falsey, then `null` is returned. * - * @deprecated * @static * @memberOf _ * @category Utilities @@ -3817,13 +3925,21 @@ source += text.slice(index, offset).replace(reUnescapedString, escapeStringChar); // replace delimiters with snippets - source += - escapeValue ? "' +\n__e(" + escapeValue + ") +\n'" : - evaluateValue ? "';\n" + evaluateValue + ";\n__p += '" : - interpolateValue ? "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'" : ''; - + if (escapeValue) { + source += "' +\n__e(" + escapeValue + ") +\n'"; + } + if (evaluateValue) { + source += "';\n" + evaluateValue + ";\n__p += '"; + } + if (interpolateValue) { + source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'"; + } isEvaluating || (isEvaluating = evaluateValue || reComplexDelimiter.test(escapeValue || interpolateValue)); index = offset + match.length; + + // the JS engine embedded in Adobe products requires returning the `match` + // string in order to produce the correct `offset` value + return match; }); source += "';\n"; @@ -3853,10 +3969,10 @@ // frame code as the function body source = 'function(' + variable + ') {\n' + (hasVariable ? '' : variable + ' || (' + variable + ' = {});\n') + - 'var __t, __p = \'\', __e = _.escape' + + "var __t, __p = '', __e = _.escape" + (isEvaluating ? ', __j = Array.prototype.join;\n' + - 'function print() { __p += __j.call(arguments, \'\') }\n' + "function print() { __p += __j.call(arguments, '') }\n" : (hasVariable ? '' : ', __d = ' + variable + '.' + variable + ' || ' + variable) + ';\n' ) + source + @@ -3869,7 +3985,7 @@ : ''; try { - result = Function('_', 'return ' + source + sourceURL)(lodash); + result = createFunction('_', 'return ' + source + sourceURL)(lodash); } catch(e) { e.source = source; throw e; @@ -3932,29 +4048,30 @@ * @example * * _.unescape('Moe, Larry & Curly'); - * // => "Moe, Larry & Curly" + * // => 'Moe, Larry & Curly' */ function unescape(string) { return string == null ? '' : (string + '').replace(reEscapedHtml, unescapeHtmlChar); } /** - * Generates a unique id. If `prefix` is passed, the id will be appended to it. + * Generates a unique ID. If `prefix` is passed, the ID will be appended to it. * * @static * @memberOf _ * @category Utilities - * @param {String} [prefix] The value to prefix the id with. - * @returns {Number|String} Returns a numeric id if no prefix is passed, else - * a string id may be returned. + * @param {String} [prefix] The value to prefix the ID with. + * @returns {String} Returns the unique ID. * @example * * _.uniqueId('contact_'); * // => 'contact_104' + * + * _.uniqueId(); + * // => '105' */ function uniqueId(prefix) { - var id = idCounter++; - return prefix ? prefix + id : id; + return (prefix == null ? '' : prefix + '') + (++idCounter); } /*--------------------------------------------------------------------------*/ @@ -3978,14 +4095,11 @@ * var youngest = _.chain(stooges) * .sortBy(function(stooge) { return stooge.age; }) * .map(function(stooge) { return stooge.name + ' is ' + stooge.age; }) - * .first() - * .value(); + * .first(); * // => 'moe is 40' */ function chain(value) { - value = new lodash(value); - value.__chain__ = true; - return value; + return new lodash(value); } /** @@ -4004,7 +4118,7 @@ * _.chain([1, 2, 3, 200]) * .filter(function(num) { return num % 2 == 0; }) * .tap(alert) - * .map(function(num) { return num * num }) + * .map(function(num) { return num * num; }) * .value(); * // => // [2, 200] (alerted) * // => [4, 40000] @@ -4015,7 +4129,11 @@ } /** - * Enables method chaining on the wrapper object. + * This function returns the wrapper object. + * + * Note: This function is defined to ensure the existing wrapper object is + * returned, instead of creating a new wrapper object like the `_.chain` + * method does. * * @name chain * @deprecated @@ -4024,75 +4142,124 @@ * @returns {Mixed} Returns the wrapper object. * @example * - * _([1, 2, 3]).value(); - * // => [1, 2, 3] + * var wrapped = _([1, 2, 3]); + * wrapped === wrapped.chain(); + * // => true */ function wrapperChain() { - this.__chain__ = true; return this; } + /** + * Produces the `toString` result of the wrapped value. + * + * @name toString + * @memberOf _ + * @category Chaining + * @returns {String} Returns the string result. + * @example + * + * _([1, 2, 3]).toString(); + * // => '1,2,3' + */ + function wrapperToString() { + return String(this.__wrapped__); + } + /** * Extracts the wrapped value. * - * @name value + * @name valueOf * @memberOf _ + * @alias value * @category Chaining * @returns {Mixed} Returns the wrapped value. * @example * - * _([1, 2, 3]).value(); + * _([1, 2, 3]).valueOf(); * // => [1, 2, 3] */ - function wrapperValue() { + function wrapperValueOf() { return this.__wrapped__; } /*--------------------------------------------------------------------------*/ - /** - * The semantic version number. - * - * @static - * @memberOf _ - * @type String - */ - lodash.VERSION = '0.9.2'; - - // assign static methods - lodash.after = after; - lodash.bind = bind; + // add functions that return wrapped values when chaining + lodash.assign = assign; lodash.bindAll = bindAll; lodash.chain = chain; - lodash.clone = clone; lodash.compact = compact; - lodash.compose = compose; - lodash.contains = contains; lodash.countBy = countBy; - lodash.debounce = debounce; lodash.defaults = defaults; - lodash.defer = defer; - lodash.delay = delay; lodash.difference = difference; - lodash.escape = escape; - lodash.every = every; - lodash.extend = extend; lodash.filter = filter; - lodash.find = find; - lodash.first = first; lodash.flatten = flatten; lodash.forEach = forEach; lodash.forIn = forIn; lodash.forOwn = forOwn; lodash.functions = functions; lodash.groupBy = groupBy; - lodash.has = has; - lodash.identity = identity; - lodash.indexOf = indexOf; lodash.initial = initial; lodash.intersection = intersection; lodash.invert = invert; lodash.invoke = invoke; + lodash.keys = keys; + lodash.map = map; + lodash.max = max; + lodash.merge = merge; + lodash.min = min; + lodash.object = object; + lodash.omit = omit; + lodash.pairs = pairs; + lodash.pick = pick; + lodash.pluck = pluck; + lodash.range = range; + lodash.reject = reject; + lodash.rest = rest; + lodash.shuffle = shuffle; + lodash.sortBy = sortBy; + lodash.tap = tap; + lodash.times = times; + lodash.toArray = toArray; + lodash.union = union; + lodash.uniq = uniq; + lodash.values = values; + lodash.where = where; + lodash.without = without; + lodash.zip = zip; + + // add aliases + lodash.collect = map; + lodash.drop = rest; + lodash.each = forEach; + lodash.extend = assign; + lodash.methods = functions; + lodash.select = filter; + lodash.tail = rest; + lodash.unique = uniq; + + // add functions to `lodash.prototype` + mixin(lodash); + + /*--------------------------------------------------------------------------*/ + + // add functions that return unwrapped values when chaining + lodash.after = after; + lodash.bind = bind; + lodash.bindKey = bindKey; + lodash.clone = clone; + lodash.compose = compose; + lodash.contains = contains; + lodash.debounce = debounce; + lodash.defer = defer; + lodash.delay = delay; + lodash.escape = escape; + lodash.every = every; + lodash.find = find; + lodash.has = has; + lodash.identity = identity; + lodash.indexOf = indexOf; lodash.isArguments = isArguments; lodash.isArray = isArray; lodash.isBoolean = isBoolean; @@ -4110,119 +4277,110 @@ lodash.isRegExp = isRegExp; lodash.isString = isString; lodash.isUndefined = isUndefined; - lodash.keys = keys; - lodash.last = last; lodash.lastIndexOf = lastIndexOf; - lodash.lateBind = lateBind; - lodash.map = map; - lodash.max = max; lodash.memoize = memoize; - lodash.merge = merge; - lodash.min = min; lodash.mixin = mixin; lodash.noConflict = noConflict; - lodash.object = object; - lodash.omit = omit; lodash.once = once; - lodash.pairs = pairs; lodash.partial = partial; - lodash.pick = pick; - lodash.pluck = pluck; lodash.random = random; - lodash.range = range; lodash.reduce = reduce; lodash.reduceRight = reduceRight; - lodash.reject = reject; - lodash.rest = rest; lodash.result = result; - lodash.shuffle = shuffle; lodash.size = size; lodash.some = some; - lodash.sortBy = sortBy; lodash.sortedIndex = sortedIndex; - lodash.tap = tap; lodash.template = template; lodash.throttle = throttle; - lodash.times = times; - lodash.toArray = toArray; lodash.unescape = unescape; - lodash.union = union; - lodash.uniq = uniq; lodash.uniqueId = uniqueId; - lodash.values = values; - lodash.where = where; - lodash.without = without; lodash.wrap = wrap; - lodash.zip = zip; - // assign aliases + // add aliases lodash.all = every; lodash.any = some; - lodash.collect = map; lodash.detect = find; - lodash.drop = rest; - lodash.each = forEach; lodash.foldl = reduce; lodash.foldr = reduceRight; - lodash.head = first; lodash.include = contains; lodash.inject = reduce; - lodash.methods = functions; - lodash.select = filter; - lodash.tail = rest; - lodash.take = first; - lodash.unique = uniq; - // add pseudo private property to be used and removed during the build process - lodash._iteratorTemplate = iteratorTemplate; + forOwn(lodash, function(func, methodName) { + if (!lodash.prototype[methodName]) { + lodash.prototype[methodName] = function() { + var args = [this.__wrapped__]; + push.apply(args, arguments); + return func.apply(lodash, args); + }; + } + }); /*--------------------------------------------------------------------------*/ - // add all static functions to `lodash.prototype` - mixin(lodash); + // add functions capable of returning wrapped and unwrapped values when chaining + lodash.first = first; + lodash.last = last; - // add `lodash.prototype.chain` after calling `mixin()` to avoid overwriting - // it with the wrapped `lodash.chain` + // add aliases + lodash.take = first; + lodash.head = first; + + forOwn(lodash, function(func, methodName) { + if (!lodash.prototype[methodName]) { + lodash.prototype[methodName]= function(n, guard) { + var result = func(this.__wrapped__, n, guard); + return (n == null || guard) ? result : new lodash(result); + }; + } + }); + + /*--------------------------------------------------------------------------*/ + + /** + * The semantic version number. + * + * @static + * @memberOf _ + * @type String + */ + lodash.VERSION = '1.0.0-rc.2'; + + // add "Chaining" functions to the wrapper lodash.prototype.chain = wrapperChain; - lodash.prototype.value = wrapperValue; + lodash.prototype.toString = wrapperToString; + lodash.prototype.value = wrapperValueOf; + lodash.prototype.valueOf = wrapperValueOf; - // add all mutator Array functions to the wrapper. + // add mutator `Array` functions to the wrapper forEach(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(methodName) { var func = arrayRef[methodName]; - lodash.prototype[methodName] = function() { var value = this.__wrapped__; func.apply(value, arguments); - // avoid array-like object bugs with `Array#shift` and `Array#splice` in - // Firefox < 10 and IE < 9 + // avoid array-like object bugs with `Array#shift` and `Array#splice` + // in Firefox < 10 and IE < 9 if (hasObjectSpliceBug && value.length === 0) { delete value[0]; } - if (this.__chain__) { - value = new lodash(value); - value.__chain__ = true; - } - return value; + return this; }; }); - // add all accessor Array functions to the wrapper. + // add accessor `Array` functions to the wrapper forEach(['concat', 'join', 'slice'], function(methodName) { var func = arrayRef[methodName]; - lodash.prototype[methodName] = function() { var value = this.__wrapped__, result = func.apply(value, arguments); - if (this.__chain__) { - result = new lodash(result); - result.__chain__ = true; - } - return result; + return new lodash(result); }; }); + // add pseudo private property to be used and removed during the build process + lodash._iteratorTemplate = iteratorTemplate; + /*--------------------------------------------------------------------------*/ // expose Lo-Dash