diff --git a/LICENSE b/LICENSE index f154b09bb067d..cde4728a2cc06 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2010, Michael Bostock +Copyright (c) 2012, Michael Bostock All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/Makefile b/Makefile index 0799fb43420d3..bc96960be5fc0 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,9 @@ d3.core.js: \ src/compat/date.js \ src/compat/style.js \ src/core/core.js \ + src/core/class.js \ src/core/array.js \ + src/core/map.js \ src/core/this.js \ src/core/functor.js \ src/core/rebind.js \ @@ -87,8 +89,8 @@ d3.core.js: \ src/core/selection-insert.js \ src/core/selection-remove.js \ src/core/selection-data.js \ + src/core/selection-datum.js \ src/core/selection-filter.js \ - src/core/selection-map.js \ src/core/selection-order.js \ src/core/selection-sort.js \ src/core/selection-on.js \ @@ -113,6 +115,8 @@ d3.core.js: \ src/core/transition-transition.js \ src/core/timer.js \ src/core/transform.js \ + src/core/mouse.js \ + src/core/touches.js \ src/core/noop.js d3.scale.js: \ @@ -127,7 +131,8 @@ d3.scale.js: \ src/scale/ordinal.js \ src/scale/category.js \ src/scale/quantile.js \ - src/scale/quantize.js + src/scale/quantize.js \ + src/scale/identity.js d3.svg.js: \ src/svg/svg.js \ @@ -189,21 +194,14 @@ d3.time.js: \ src/time/format.js \ src/time/format-utc.js \ src/time/format-iso.js \ - src/time/range.js \ + src/time/interval.js \ src/time/second.js \ - src/time/seconds.js \ src/time/minute.js \ - src/time/minutes.js \ src/time/hour.js \ - src/time/hours.js \ src/time/day.js \ - src/time/days.js \ src/time/week.js \ - src/time/weeks.js \ src/time/month.js \ - src/time/months.js \ src/time/year.js \ - src/time/years.js \ src/time/scale.js \ src/time/scale-utc.js diff --git a/d3.v2.js b/d3.v2.js index e2c31e39b3a87..384cfada269b1 100644 --- a/d3.v2.js +++ b/d3.v2.js @@ -10,7 +10,19 @@ try { d3_style_setProperty.call(this, name, value + "", priority); }; } -d3 = {version: "2.7.5"}; // semver +d3 = {version: "2.8.0"}; // semver +function d3_class(ctor, properties) { + try { + for (var key in properties) { + Object.defineProperty(ctor.prototype, key, { + value: properties[key], + enumerable: false + }); + } + } catch (e) { + ctor.prototype = properties; + } +} var d3_array = d3_arraySlice; // conversion for NodeLists function d3_arrayCopy(pseudoarray) { @@ -40,6 +52,54 @@ function(array, prototype) { function(array, prototype) { for (var property in prototype) array[property] = prototype[property]; }; +d3.map = function(object) { + var map = new d3_Map; + 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; + }, + 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; + }, + values: function() { + var values = []; + this.forEach(function(key, value) { values.push(value); }); + return values; + }, + entries: function() { + var entries = []; + this.forEach(function(key, value) { entries.push({key: key, value: value}); }); + return entries; + }, + forEach: function(f) { + 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_this() { return this; } @@ -191,44 +251,34 @@ d3.zip = function() { function d3_zipLength(d) { return d.length; } -// Locate the insertion point for x in a to maintain sorted order. The -// arguments lo and hi may be used to specify a subset of the array which should -// be considered; by default the entire array is used. If x is already present -// in a, the insertion point will be before (to the left of) any existing -// entries. The return value is suitable for use as the first argument to -// `array.splice` assuming that a is already sorted. -// -// The returned insertion point i partitions the array a into two halves so that -// all v < x for v in a[lo:i] for the left side and all v >= x for v in a[i:hi] -// for the right side. -d3.bisectLeft = 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 (a[mid] < x) lo = mid + 1; - else hi = mid; - } - return lo; -}; - -// Similar to bisectLeft, but returns an insertion point which comes after (to -// the right of) any existing entries of x in a. -// -// The returned insertion point i partitions the array into two halves so that -// all v <= x for v in a[lo:i] for the left side and all v > x for v in a[i:hi] -// for the right side. -d3.bisect = -d3.bisectRight = 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 (x < a[mid]) hi = mid; - else lo = mid + 1; - } - return lo; +d3.bisector = function(f) { + 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; + else hi = mid; + } + return lo; + }, + right: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >> 1; + if (x < f.call(a, a[mid], mid)) 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; d3.first = function(array, f) { var i = 0, n = array.length, @@ -273,19 +323,21 @@ d3.nest = function() { key = keys[depth++], keyValue, object, + valuesByKey = new d3_Map, + values, o = {}; while (++i < n) { - if ((keyValue = key(object = array[i])) in o) { - o[keyValue].push(object); + if (values = valuesByKey.get(keyValue = key(object = array[i]))) { + values.push(object); } else { - o[keyValue] = [object]; + valuesByKey.set(keyValue, [object]); } } - for (keyValue in o) { - o[keyValue] = map(o[keyValue], depth); - } + valuesByKey.forEach(function(keyValue) { + o[keyValue] = map(valuesByKey.get(keyValue), depth); + }); return o; } @@ -482,14 +534,19 @@ var d3_nsPrefix = { d3.ns = { prefix: d3_nsPrefix, qualify: function(name) { - var i = name.indexOf(":"); - return i < 0 ? (name in d3_nsPrefix - ? {space: d3_nsPrefix[name], local: name} : name) - : {space: d3_nsPrefix[name.substring(0, i)], local: name.substring(i + 1)}; + var i = name.indexOf(":"), + prefix = name; + if (i >= 0) { + prefix = name.substring(0, i); + name = name.substring(i + 1); + } + return d3_nsPrefix.hasOwnProperty(prefix) + ? {space: d3_nsPrefix[prefix], local: name} + : name; } }; d3.dispatch = function() { - var dispatch = new d3_dispatch(), + var dispatch = new d3_dispatch, i = -1, n = arguments.length; while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); @@ -515,7 +572,7 @@ d3_dispatch.prototype.on = function(type, listener) { function d3_dispatch_event(dispatch) { var listeners = [], - listenerByName = {}; + listenerByName = new d3_Map; function event() { var z = listeners, // defensive reference @@ -527,22 +584,21 @@ function d3_dispatch_event(dispatch) { } event.on = function(name, listener) { - var l, i; + var l = listenerByName.get(name), + i; // return the current listener, if any - if (arguments.length < 2) return (l = listenerByName[name]) && l.on; + if (arguments.length < 2) return l && l.on; // remove the old listener, if any (with copy-on-write) - if (l = listenerByName[name]) { + if (l) { l.on = null; listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1)); - delete listenerByName[name]; + listenerByName.remove(name); } // add the new listener, if any - if (listener) { - listeners.push(listenerByName[name] = {on: listener}); - } + if (listener) listeners.push(listenerByName.set(name, {on: listener})); return dispatch; }; @@ -581,7 +637,7 @@ d3.format = function(specifier) { // If no precision is specified for r, fallback to general notation. if (type == "r" && !precision) type = "g"; - type = d3_format_types[type] || d3_format_typeDefault; + type = d3_format_types.get(type) || d3_format_typeDefault; return function(value) { @@ -626,12 +682,12 @@ d3.format = function(specifier) { // [[fill]align][sign][#][0][width][,][.precision][type] var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/; -var d3_format_types = { +var d3_format_types = d3.map({ g: function(x, p) { return x.toPrecision(p); }, e: function(x, p) { return x.toExponential(p); }, f: function(x, p) { return x.toFixed(p); }, r: function(x, p) { return d3.round(x, p = d3_format_precision(x, p)).toFixed(Math.max(0, Math.min(20, p))); } -}; +}); function d3_format_precision(x, p) { return p - (x ? 1 + Math.floor(Math.log(x + Math.pow(10, 1 + Math.floor(Math.log(x) / Math.LN10) - p)) / Math.LN10) : 1); @@ -705,10 +761,11 @@ function d3_formatPrefix(d, i) { */ var d3_ease_quad = d3_ease_poly(2), - d3_ease_cubic = d3_ease_poly(3); + d3_ease_cubic = d3_ease_poly(3), + d3_ease_default = function() { return d3_ease_identity; }; -var d3_ease = { - linear: function() { return d3_ease_linear; }, +var d3_ease = d3.map({ + linear: d3_ease_default, poly: d3_ease_poly, quad: function() { return d3_ease_quad; }, cubic: function() { return d3_ease_cubic; }, @@ -718,20 +775,22 @@ var d3_ease = { elastic: d3_ease_elastic, back: d3_ease_back, bounce: function() { return d3_ease_bounce; } -}; +}); -var d3_ease_mode = { - "in": function(f) { return f; }, +var d3_ease_mode = d3.map({ + "in": d3_ease_identity, "out": d3_ease_reverse, "in-out": d3_ease_reflect, "out-in": function(f) { return d3_ease_reflect(d3_ease_reverse(f)); } -}; +}); d3.ease = function(name) { var i = name.indexOf("-"), t = i >= 0 ? name.substring(0, i) : name, m = i >= 0 ? name.substring(i + 1) : "in"; - return d3_ease_clamp(d3_ease_mode[m](d3_ease[t].apply(null, Array.prototype.slice.call(arguments, 1)))); + t = d3_ease.get(t) || d3_ease_default; + m = d3_ease_mode.get(m) || d3_ease_identity; + return d3_ease_clamp(m(t.apply(null, Array.prototype.slice.call(arguments, 1)))); }; function d3_ease_clamp(f) { @@ -752,14 +811,14 @@ function d3_ease_reflect(f) { }; } -function d3_ease_linear(t) { +function d3_ease_identity(t) { return t; } function d3_ease_poly(e) { return function(t) { return Math.pow(t, e); - } + }; } function d3_ease_sin(t) { @@ -803,6 +862,49 @@ function d3_eventCancel() { d3.event.stopPropagation(); d3.event.preventDefault(); } + +function d3_eventSource() { + var e = d3.event, s; + while (s = e.sourceEvent) e = s; + return e; +} + +// Like d3.dispatch, but for custom events abstracting native UI events. These +// events have a target component (such as a brush), a target element (such as +// the svg:g element containing the brush) and the standard arguments `d` (the +// target element's data) and `i` (the selection index of the target element). +function d3_eventDispatch(target) { + var dispatch = new d3_dispatch, + i = 0, + n = arguments.length; + + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + + // Creates a dispatch context for the specified `thiz` (typically, the target + // DOM element that received the source event) and `argumentz` (typically, the + // data `d` and index `i` of the target element). The returned function can be + // used to dispatch an event to any registered listeners; the function takes a + // single argument as input, being the event to dispatch. The event must have + // a "type" attribute which corresponds to a type registered in the + // constructor. This context will automatically populate the "sourceEvent" and + // "target" attributes of the event, as well as setting the `d3.event` global + // for the duration of the notification. + dispatch.of = function(thiz, argumentz) { + return function(e1) { + try { + var e0 = + e1.sourceEvent = d3.event; + e1.target = target; + d3.event = e1; + dispatch[e1.type].apply(thiz, argumentz); + } finally { + d3.event = e0; + } + }; + }; + + return dispatch; +} d3.interpolate = function(a, b) { var i = d3.interpolators.length, f; while (--i >= 0 && !(f = d3.interpolators[i](a, b))); @@ -1030,7 +1132,7 @@ d3.interpolators = [ d3.interpolateObject, function(a, b) { return (b instanceof Array) && 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" ? b in d3_rgb_names || /^(#|rgb\(|hsl\()/.test(b) : b instanceof d3_Rgb || b instanceof d3_Hsl) && d3.interpolateRgb(a, b); }, + function(a, b) { return (typeof b === "string" ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) : b instanceof d3_Rgb || b instanceof d3_Hsl) && d3.interpolateRgb(a, b); }, function(a, b) { return !isNaN(a = +a) && !isNaN(b = +b) && d3.interpolateNumber(a, b); } ]; function d3_uninterpolateNumber(a, b) { @@ -1128,7 +1230,7 @@ function d3_rgb_parse(format, rgb, hsl) { } /* Named colors. */ - if (name = d3_rgb_names[format]) return rgb(name.r, name.g, name.b); + if (name = d3_rgb_names.get(format)) return rgb(name.r, name.g, name.b); /* Hexadecimal colors: #rgb and #rrggbb. */ if (format != null && format.charAt(0) === "#") { @@ -1173,7 +1275,7 @@ function d3_rgb_parseNumber(c) { // either integer or percentage return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f; } -var d3_rgb_names = { +var d3_rgb_names = d3.map({ aliceblue: "#f0f8ff", antiquewhite: "#faebd7", aqua: "#00ffff", @@ -1321,14 +1423,11 @@ var d3_rgb_names = { whitesmoke: "#f5f5f5", yellow: "#ffff00", yellowgreen: "#9acd32" -}; +}); -for (var d3_rgb_name in d3_rgb_names) { - d3_rgb_names[d3_rgb_name] = d3_rgb_parse( - d3_rgb_names[d3_rgb_name], - d3_rgb, - d3_hsl_rgb); -} +d3_rgb_names.forEach(function(key, value) { + d3_rgb_names.set(key, d3_rgb_parse(value, d3_rgb, d3_hsl_rgb)); +}); d3.hsl = function(h, s, l) { return arguments.length === 1 ? (h instanceof d3_Hsl ? d3_hsl(h.h, h.s, h.l) @@ -1683,11 +1782,22 @@ d3_selectionPrototype.remove = function() { if (parent) parent.removeChild(this); }); }; -// TODO data(null) for clearing data? -d3_selectionPrototype.data = function(data, join) { - var enter = [], - update = [], - exit = []; +d3_selectionPrototype.data = function(value, key) { + var i = -1, + n = this.length, + group, + node; + + // If no value is specified, return the first value. + if (!arguments.length) { + value = new Array(n = (group = this[0]).length); + while (++i < n) { + if (node = group[i]) { + value[i] = node.__data__; + } + } + return value; + } function bind(group, groupData) { var i, @@ -1701,37 +1811,37 @@ d3_selectionPrototype.data = function(data, join) { node, nodeData; - if (join) { - var nodeByKey = {}, - keys = [], - key, + if (key) { + var nodeByKeyValue = new d3_Map, + keyValues = [], + keyValue, j = groupData.length; for (i = -1; ++i < n;) { - key = join.call(node = group[i], node.__data__, i); - if (key in nodeByKey) { + keyValue = key.call(node = group[i], node.__data__, i); + if (nodeByKeyValue.has(keyValue)) { exitNodes[j++] = node; // duplicate key } else { - nodeByKey[key] = node; + nodeByKeyValue.set(keyValue, node); } - keys.push(key); + keyValues.push(keyValue); } for (i = -1; ++i < m;) { - node = nodeByKey[key = join.call(groupData, nodeData = groupData[i], i)]; - if (node) { + keyValue = key.call(groupData, nodeData = groupData[i], i) + if (nodeByKeyValue.has(keyValue)) { + updateNodes[i] = node = nodeByKeyValue.get(keyValue); node.__data__ = nodeData; - updateNodes[i] = node; enterNodes[i] = exitNodes[i] = null; } else { enterNodes[i] = d3_selection_dataNode(nodeData); updateNodes[i] = exitNodes[i] = null; } - delete nodeByKey[key]; + nodeByKeyValue.remove(keyValue); } for (i = -1; ++i < n;) { - if (keys[i] in nodeByKey) { + if (nodeByKeyValue.has(keyValues[i])) { exitNodes[i] = group[i]; } } @@ -1771,28 +1881,34 @@ d3_selectionPrototype.data = function(data, join) { exit.push(exitNodes); } - var i = -1, - n = this.length, - group; - if (typeof data === "function") { + var enter = d3_selection_enter([]), + update = d3_selection([]), + exit = d3_selection([]); + + if (typeof value === "function") { while (++i < n) { - bind(group = this[i], data.call(group, group.parentNode.__data__, i)); + bind(group = this[i], value.call(group, group.parentNode.__data__, i)); } } else { while (++i < n) { - bind(group = this[i], data); + bind(group = this[i], value); } } - var selection = d3_selection(update); - selection.enter = function() { return d3_selection_enter(enter); }; - selection.exit = function() { return d3_selection(exit); }; - return selection; + update.enter = function() { return enter; }; + update.exit = function() { return exit; }; + return update; }; function d3_selection_dataNode(data) { return {__data__: data}; } +d3_selectionPrototype.datum = +d3_selectionPrototype.map = function(value) { + return arguments.length < 1 + ? this.property("__data__") + : this.property("__data__", value); +}; d3_selectionPrototype.filter = function(filter) { var subgroups = [], subgroup, @@ -1819,11 +1935,6 @@ function d3_selection_filter(selector) { return d3_selectMatches(this, selector); }; } -d3_selectionPrototype.map = function(map) { - return this.each(function() { - this.__data__ = map.apply(this, arguments); - }); -}; d3_selectionPrototype.order = function() { for (var j = -1, m = this.length; ++j < m;) { for (var group = this[j], i = group.length - 1, next = group[i], node; --i >= 0;) { @@ -1861,10 +1972,20 @@ d3_selectionPrototype.on = function(type, listener, capture) { // remove the old event listener, and add the new event listener return this.each(function(d, i) { - var node = this; + var node = this, + o = node[name]; - if (node[name]) node.removeEventListener(type, node[name], capture); - if (listener) node.addEventListener(type, node[name] = l, capture); + // remove the old listener, if any (using the previously-set capture) + if (o) { + node.removeEventListener(type, o, o.$); + delete node[name]; + } + + // add the new listener, if any (remembering the capture flag) + if (listener) { + node.addEventListener(type, node[name] = l, l.$ = capture); + l._ = listener; // stash the unwrapped listener for get + } // wrapped event listener that preserves i function l(e) { @@ -1876,9 +1997,6 @@ d3_selectionPrototype.on = function(type, listener, capture) { d3.event = o; } } - - // stash the unwrapped listener for retrieval - l._ = listener; }); }; d3_selectionPrototype.each = function(callback) { @@ -1954,6 +2072,9 @@ function d3_selection_enter(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.insert = d3_selectionPrototype.insert; d3_selection_enterPrototype.empty = d3_selectionPrototype.empty; @@ -1985,7 +2106,7 @@ d3_selection_enterPrototype.select = function(selector) { function d3_transition(groups, id, time) { d3_arraySubclass(groups, d3_transitionPrototype); - var tweens = {}, + var tweens = new d3_Map, event = d3.dispatch("start", "end"), ease = d3_transitionEase; @@ -1994,9 +2115,9 @@ function d3_transition(groups, id, time) { groups.time = time; groups.tween = function(name, tween) { - if (arguments.length < 2) return tweens[name]; - if (tween == null) delete tweens[name]; - else tweens[name] = tween; + if (arguments.length < 2) return tweens.get(name); + if (tween == null) tweens.remove(name); + else tweens.set(name, tween); return groups; }; @@ -2028,11 +2149,11 @@ function d3_transition(groups, id, time) { if (lock.active > id) return stop(); lock.active = id; - for (var tween in tweens) { - if (tween = tweens[tween].call(node, d, i)) { + tweens.forEach(function(key, value) { + if (tween = value.call(node, d, i)) { tweened.push(tween); } - } + }); event.start.call(node, d, i); if (!tick(elapsed)) d3.timer(tick, 0, time); @@ -2204,14 +2325,14 @@ d3_transitionPrototype.remove = function() { d3_transitionPrototype.delay = function(value) { var groups = this; return groups.each(typeof value === "function" - ? function(d, i, j) { groups[j][i].delay = +value.apply(this, arguments); } - : (value = +value, function(d, i, j) { groups[j][i].delay = value; })); + ? function(d, i, j) { groups[j][i].delay = value.apply(this, arguments) | 0; } + : (value = value | 0, function(d, i, j) { groups[j][i].delay = value; })); }; d3_transitionPrototype.duration = function(value) { var groups = this; return groups.each(typeof value === "function" - ? function(d, i, j) { groups[j][i].duration = +value.apply(this, arguments); } - : (value = +value, function(d, i, j) { groups[j][i].duration = value; })); + ? function(d, i, j) { groups[j][i].duration = Math.max(1, value.apply(this, arguments) | 0); } + : (value = Math.max(1, value | 0), function(d, i, j) { groups[j][i].duration = value; })); }; function d3_transition_each(callback) { for (var j = 0, m = this.length; j < m; j++) { @@ -2389,6 +2510,48 @@ function d3_transformCombine(a, b, k) { } var d3_transformDegrees = 180 / Math.PI; +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(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) && (window.scrollX || window.scrollY)) { + svg = d3.select(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.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 d3_noop() {} d3.scale = {}; @@ -2715,7 +2878,7 @@ function d3_scale_ordinal(domain, ranger) { rangeBand; function scale(x) { - return range[((index[x] || (index[x] = domain.push(x))) - 1) % range.length]; + return range[((index.get(x) || index.set(x, domain.push(x))) - 1) % range.length]; } function steps(start, step) { @@ -2725,9 +2888,9 @@ function d3_scale_ordinal(domain, ranger) { scale.domain = function(x) { if (!arguments.length) return domain; domain = []; - index = {}; + index = new d3_Map; var i = -1, n = x.length, xi; - while (++i < n) if (!index[xi = x[i]]) index[xi] = domain.push(xi); + while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi)); return scale[ranger.t](ranger.x, ranger.p); }; @@ -2752,10 +2915,12 @@ function d3_scale_ordinal(domain, ranger) { scale.rangeBands = function(x, padding) { if (arguments.length < 2) padding = 0; - var start = x[0], - stop = x[1], + var reverse = x[1] < x[0], + start = x[reverse - 0], + stop = x[1 - reverse], step = (stop - start) / (domain.length + padding); range = steps(start + step * padding, step); + if (reverse) range.reverse(); rangeBand = step * (1 - padding); ranger = {t: "rangeBands", x: x, p: padding}; return scale; @@ -2763,10 +2928,13 @@ function d3_scale_ordinal(domain, ranger) { scale.rangeRoundBands = function(x, padding) { if (arguments.length < 2) padding = 0; - var start = x[0], - stop = x[1], - step = Math.floor((stop - start) / (domain.length + padding)); - range = steps(start + Math.round((stop - start - (domain.length - padding) * step) / 2), step); + var reverse = x[1] < x[0], + start = x[reverse - 0], + stop = x[1 - reverse], + step = Math.floor((stop - start) / (domain.length + padding)), + error = stop - start - (domain.length - padding) * step; + range = steps(start + Math.round(error / 2), step); + if (reverse) range.reverse(); rangeBand = Math.round(step * (1 - padding)); ranger = {t: "rangeRoundBands", x: x, p: padding}; return scale; @@ -2777,7 +2945,7 @@ function d3_scale_ordinal(domain, ranger) { }; scale.rangeExtent = function() { - return ranger.t === "range" ? d3_scaleExtent(ranger.x) : ranger.x; + return d3_scaleExtent(ranger.x); }; scale.copy = function() { @@ -2919,6 +3087,36 @@ function d3_scale_quantize(x0, x1, range) { return rescale(); } +d3.scale.identity = function() { + return d3_scale_identity([0, 1]); +}; + +function d3_scale_identity(domain) { + + function identity(x) { return +x; } + + identity.invert = identity; + + identity.domain = identity.range = function(x) { + if (!arguments.length) return domain; + domain = x.map(identity); + return identity; + }; + + identity.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + + identity.tickFormat = function(m) { + return d3_scale_linearTickFormat(domain, m); + }; + + identity.copy = function() { + return d3_scale_identity(domain); + }; + + return identity; +} d3.svg = {}; d3.svg.arc = function() { var innerRadius = d3_svg_arcInnerRadius, @@ -3018,8 +3216,8 @@ function d3_svg_arcEndAngle(d) { function d3_svg_line(projection) { var x = d3_svg_lineX, y = d3_svg_lineY, - interpolate = "linear", - interpolator = d3_svg_lineInterpolators[interpolate], + interpolate = d3_svg_lineInterpolatorDefault, + interpolator = d3_svg_lineInterpolators.get(interpolate), tension = .7; function line(d) { @@ -3040,7 +3238,8 @@ function d3_svg_line(projection) { line.interpolate = function(v) { if (!arguments.length) return interpolate; - interpolator = d3_svg_lineInterpolators[interpolate = v]; + if (!d3_svg_lineInterpolators.has(v += "")) v = d3_svg_lineInterpolatorDefault; + interpolator = d3_svg_lineInterpolators.get(interpolate = v); return line; }; @@ -3093,8 +3292,10 @@ function d3_svg_lineY(d) { return d[1]; } +var d3_svg_lineInterpolatorDefault = "linear"; + // The various interpolators supported by the `line` class. -var d3_svg_lineInterpolators = { +var d3_svg_lineInterpolators = d3.map({ "linear": d3_svg_lineLinear, "step-before": d3_svg_lineStepBefore, "step-after": d3_svg_lineStepAfter, @@ -3106,7 +3307,7 @@ var d3_svg_lineInterpolators = { "cardinal-open": d3_svg_lineCardinalOpen, "cardinal-closed": d3_svg_lineCardinalClosed, "monotone": d3_svg_lineMonotone -}; +}); // Linear interpolation; generates "L" commands. function d3_svg_lineLinear(points) { @@ -3509,7 +3710,8 @@ function d3_svg_area(projection) { area.interpolate = function(x) { if (!arguments.length) return interpolate; - i0 = d3_svg_lineInterpolators[interpolate = x]; + if (!d3_svg_lineInterpolators.has(x += "")) x = d3_svg_lineInterpolatorDefault; + i0 = d3_svg_lineInterpolators.get(interpolate = x); i1 = i0.reverse || i0; return area; }; @@ -3710,51 +3912,15 @@ function d3_svg_diagonalRadialProjection(projection) { return [r * Math.cos(a), r * Math.sin(a)]; }; } -d3.svg.mouse = function(container) { - return d3_svg_mousePoint(container, d3.event); -}; - -// https://bugs.webkit.org/show_bug.cgi?id=44083 -var d3_mouse_bug44083 = /WebKit/.test(navigator.userAgent) ? -1 : 0; - -function d3_svg_mousePoint(container, e) { - var point = (container.ownerSVGElement || container).createSVGPoint(); - if ((d3_mouse_bug44083 < 0) && (window.scrollX || window.scrollY)) { - var svg = d3.select(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]; -}; -d3.svg.touches = function(container, touches) { - if (arguments.length < 2) touches = d3.event.touches; - - return touches ? d3_array(touches).map(function(touch) { - var point = d3_svg_mousePoint(container, touch); - point.identifier = touch.identifier; - return point; - }) : []; -}; +d3.svg.mouse = d3.mouse; +d3.svg.touches = d3.touches; d3.svg.symbol = function() { var type = d3_svg_symbolType, size = d3_svg_symbolSize; function symbol(d, i) { - return (d3_svg_symbols[type.call(this, d, i)] - || d3_svg_symbols.circle) + return (d3_svg_symbols.get(type.call(this, d, i)) + || d3_svg_symbolCircle) (size.call(this, d, i)); } @@ -3782,15 +3948,17 @@ function d3_svg_symbolType() { return "circle"; } +function d3_svg_symbolCircle(size) { + var r = Math.sqrt(size / Math.PI); + return "M0," + r + + "A" + r + "," + r + " 0 1,1 0," + (-r) + + "A" + r + "," + r + " 0 1,1 0," + r + + "Z"; +} + // TODO cross-diagonal? -var d3_svg_symbols = { - "circle": function(size) { - var r = Math.sqrt(size / Math.PI); - return "M0," + r - + "A" + r + "," + r + " 0 1,1 0," + (-r) - + "A" + r + "," + r + " 0 1,1 0," + r - + "Z"; - }, +var d3_svg_symbols = d3.map({ + "circle": d3_svg_symbolCircle, "cross": function(size) { var r = Math.sqrt(size / 5) / 2; return "M" + -3 * r + "," + -r @@ -3840,9 +4008,9 @@ var d3_svg_symbols = { + " " + -rx + "," + ry + "Z"; } -}; +}); -d3.svg.symbolTypes = d3.keys(d3_svg_symbols); +d3.svg.symbolTypes = d3_svg_symbols.keys(); var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * Math.PI / 180); @@ -3854,6 +4022,7 @@ d3.svg.axis = function() { tickEndSize = 6, tickPadding = 3, tickArguments_ = [10], + tickValues = null, tickFormat_, tickSubdivide = 0; @@ -3876,7 +4045,7 @@ d3.svg.axis = function() { } : Object; // Ticks, or domain values for ordinal scales. - var ticks = scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain(), + var ticks = tickValues == null ? (scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain()) : tickValues, tickFormat = tickFormat_ == null ? (scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments_) : String) : tickFormat_; // Minor ticks. @@ -3997,6 +4166,12 @@ d3.svg.axis = function() { return axis; }; + axis.tickValues = function(x) { + if (!arguments.length) return tickValues; + tickValues = x; + return axis; + }; + axis.tickFormat = function(x) { if (!arguments.length) return tickFormat_; tickFormat_ = x; @@ -4059,29 +4234,30 @@ function d3_svg_axisSubdivide(scale, ticks, m) { return subticks; } d3.svg.brush = function() { - var event = d3.dispatch("brushstart", "brush", "brushend"), + var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x, // x-scale, optional y, // y-scale, optional + resizes = d3_svg_brushResizes[0], extent = [[0, 0], [0, 0]]; // [x0, y0], [x1, y1] function brush(g) { - var resizes = x && y ? ["n", "e", "s", "w", "nw", "ne", "se", "sw"] - : x ? ["e", "w"] - : y ? ["n", "s"] - : []; - g.each(function() { - var g = d3.select(this).on("mousedown.brush", down), + var g = d3.select(this), bg = g.selectAll(".background").data([0]), fg = g.selectAll(".extent").data([0]), tz = g.selectAll(".resize").data(resizes, String), e; + // Prepare the brush container for events. + g + .style("pointer-events", "all") + .on("mousedown.brush", brushstart) + .on("touchstart.brush", brushstart); + // An invisible, mouseable area for starting a new brush. bg.enter().append("rect") .attr("class", "background") .style("visibility", "hidden") - .style("pointer-events", "all") .style("cursor", "crosshair"); // The visible brush extent; style this as you like! @@ -4090,15 +4266,18 @@ d3.svg.brush = function() { .style("cursor", "move"); // More invisible rects for resizing the extent. - tz.enter().append("rect") + tz.enter().append("g") .attr("class", function(d) { return "resize " + d; }) + .style("cursor", function(d) { return d3_svg_brushCursor[d]; }) + .append("rect") + .attr("x", function(d) { return /[ew]$/.test(d) ? -3 : null; }) + .attr("y", function(d) { return /^[ns]/.test(d) ? -3 : null; }) .attr("width", 6) .attr("height", 6) - .style("visibility", "hidden") - .style("cursor", function(d) { return d3_svg_brushCursor[d]; }); + .style("visibility", "hidden"); - // Update the resizers. - tz.style("pointer-events", brush.empty() ? "none" : "all"); + // Show or hide the resizers. + tz.style("display", brush.empty() ? "none" : null); // Remove any superfluous resizers. tz.exit().remove(); @@ -4108,77 +4287,224 @@ d3.svg.brush = function() { if (x) { e = d3_scaleRange(x); bg.attr("x", e[0]).attr("width", e[1] - e[0]); - d3_svg_brushRedrawX(g, extent); + redrawX(g); } if (y) { e = d3_scaleRange(y); bg.attr("y", e[0]).attr("height", e[1] - e[0]); - d3_svg_brushRedrawY(g, extent); + redrawY(g); } + redraw(g); }); } - function down() { - var target = d3.select(d3.event.target); + function redraw(g) { + g.selectAll(".resize").attr("transform", function(d) { + return "translate(" + extent[+/e$/.test(d)][0] + "," + extent[+/^s/.test(d)][1] + ")"; + }); + } + + function redrawX(g) { + g.select(".extent").attr("x", extent[0][0]); + g.selectAll(".extent,.n>rect,.s>rect").attr("width", extent[1][0] - extent[0][0]); + } + + function redrawY(g) { + g.select(".extent").attr("y", extent[0][1]); + g.selectAll(".extent,.e>rect,.w>rect").attr("height", extent[1][1] - extent[0][1]); + } - // Store some global state for the duration of the brush gesture. - d3_svg_brush = brush; - d3_svg_brushTarget = this; - d3_svg_brushExtent = extent; - d3_svg_brushOffset = d3.svg.mouse(d3_svg_brushTarget); + function brushstart() { + var target = this, + eventTarget = d3.select(d3.event.target), + event_ = event.of(target, arguments), + g = d3.select(target), + resizing = eventTarget.datum(), + resizingX = !/^(n|s)$/.test(resizing) && x, + resizingY = !/^(e|w)$/.test(resizing) && y, + dragging = eventTarget.classed("extent"), + center, + origin = mouse(), + offset; + + var w = d3.select(window) + .on("mousemove.brush", brushmove) + .on("mouseup.brush", brushend) + .on("touchmove.brush", brushmove) + .on("touchend.brush", brushend) + .on("keydown.brush", keydown) + .on("keyup.brush", keyup); // If the extent was clicked on, drag rather than brush; - // store the offset between the mouse and extent origin instead. - if (d3_svg_brushDrag = target.classed("extent")) { - d3_svg_brushOffset[0] = extent[0][0] - d3_svg_brushOffset[0]; - d3_svg_brushOffset[1] = extent[0][1] - d3_svg_brushOffset[1]; + // store the point between the mouse and extent origin instead. + if (dragging) { + origin[0] = extent[0][0] - origin[0]; + origin[1] = extent[0][1] - origin[1]; } // If a resizer was clicked on, record which side is to be resized. - // Also, set the offset to the opposite side. - else if (target.classed("resize")) { - d3_svg_brushResize = d3.event.target.__data__; - d3_svg_brushOffset[0] = extent[+/w$/.test(d3_svg_brushResize)][0]; - d3_svg_brushOffset[1] = extent[+/^n/.test(d3_svg_brushResize)][1]; + // Also, set the origin to the opposite side. + else if (resizing) { + var ex = +/w$/.test(resizing), + ey = +/^n/.test(resizing); + offset = [extent[1 - ex][0] - origin[0], extent[1 - ey][1] - origin[1]]; + origin[0] = extent[ex][0]; + origin[1] = extent[ey][1]; } // If the ALT key is down when starting a brush, the center is at the mouse. - else if (d3.event.altKey) { - d3_svg_brushCenter = d3_svg_brushOffset.slice(); - } + else if (d3.event.altKey) center = origin.slice(); - // Restrict which dimensions are resized. - d3_svg_brushX = !/^(n|s)$/.test(d3_svg_brushResize) && x; - d3_svg_brushY = !/^(e|w)$/.test(d3_svg_brushResize) && y; + // Propagate the active cursor to the body for the drag duration. + g.style("pointer-events", "none").selectAll(".resize").style("display", null); + d3.select("body").style("cursor", eventTarget.style("cursor")); // Notify listeners. - d3_svg_brushDispatch = dispatcher(this, arguments); - d3_svg_brushDispatch("brushstart"); - d3_svg_brushMove(); + event_({type: "brushstart"}); + brushmove(); d3_eventCancel(); - } - function dispatcher(that, argumentz) { - return function(type) { - var e = d3.event; - try { - d3.event = {type: type, target: brush}; - event[type].apply(that, argumentz); - } finally { - d3.event = e; + function mouse() { + var touches = d3.event.changedTouches; + return touches ? d3.touches(target, touches)[0] : d3.mouse(target); + } + + function keydown() { + if (d3.event.keyCode == 32) { + if (!dragging) { + center = null; + origin[0] -= extent[1][0]; + origin[1] -= extent[1][1]; + dragging = 2; + } + d3_eventCancel(); } - }; + } + + function keyup() { + if (d3.event.keyCode == 32 && dragging == 2) { + origin[0] += extent[1][0]; + origin[1] += extent[1][1]; + dragging = 0; + d3_eventCancel(); + } + } + + function brushmove() { + var point = mouse(), + moved = false; + + // Preserve the offset for thick resizers. + if (offset) { + point[0] += offset[0]; + point[1] += offset[1]; + } + + if (!dragging) { + + // If needed, determine the center from the current extent. + if (d3.event.altKey) { + if (!center) center = [(extent[0][0] + extent[1][0]) / 2, (extent[0][1] + extent[1][1]) / 2]; + + // Update the origin, for when the ALT key is released. + origin[0] = extent[+(point[0] < center[0])][0]; + origin[1] = extent[+(point[1] < center[1])][1]; + } + + // When the ALT key is released, we clear the center. + else center = null; + } + + // Update the brush extent for each dimension. + if (resizingX && move1(point, x, 0)) { + redrawX(g); + moved = true; + } + if (resizingY && move1(point, y, 1)) { + redrawY(g); + moved = true; + } + + // Final redraw and notify listeners. + if (moved) { + redraw(g); + event_({type: "brush"}); + } + } + + function move1(point, scale, i) { + var range = d3_scaleRange(scale), + r0 = range[0], + r1 = range[1], + position = origin[i], + size = extent[1][i] - extent[0][i], + min, + max; + + // When dragging, reduce the range by the extent size and position. + if (dragging) { + r0 -= position; + r1 -= size + position; + } + + // Clamp the point so that the extent fits within the range extent. + min = Math.max(r0, Math.min(r1, point[i])); + + // Compute the new extent bounds. + if (dragging) { + max = (min += position) + size; + } else { + + // If the ALT key is pressed, then preserve the center of the extent. + if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min)); + + // Compute the min and max of the position and point. + if (position < min) { + max = min; + min = position; + } else { + max = position; + } + } + + // Update the stored bounds. + if (extent[0][i] !== min || extent[1][i] !== max) { + extent[0][i] = min; + extent[1][i] = max; + return true; + } + } + + function brushend() { + brushmove(); + + // reset the cursor styles + g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null); + d3.select("body").style("cursor", null); + + w .on("mousemove.brush", null) + .on("mouseup.brush", null) + .on("touchmove.brush", null) + .on("touchend.brush", null) + .on("keydown.brush", null) + .on("keyup.brush", null); + + event_({type: "brushend"}); + d3_eventCancel(); + } } brush.x = function(z) { if (!arguments.length) return x; x = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; // fore! return brush; }; brush.y = function(z) { if (!arguments.length) return y; y = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; // fore! return brush; }; @@ -4232,158 +4558,9 @@ d3.svg.brush = function() { || (y && extent[0][1] === extent[1][1]); }; - d3.select(window) - .on("mousemove.brush", d3_svg_brushMove) - .on("mouseup.brush", d3_svg_brushUp) - .on("keydown.brush", d3_svg_brushKeydown) - .on("keyup.brush", d3_svg_brushKeyup); - return d3.rebind(brush, event, "on"); }; -var d3_svg_brush, - d3_svg_brushDispatch, - d3_svg_brushTarget, - d3_svg_brushX, - d3_svg_brushY, - d3_svg_brushExtent, - d3_svg_brushDrag, - d3_svg_brushResize, - d3_svg_brushCenter, - d3_svg_brushOffset; - -function d3_svg_brushRedrawX(g, extent) { - g.select(".extent").attr("x", extent[0][0]); - g.selectAll(".n,.s,.w,.nw,.sw").attr("x", extent[0][0] - 2); - g.selectAll(".e,.ne,.se").attr("x", extent[1][0] - 3); - g.selectAll(".extent,.n,.s").attr("width", extent[1][0] - extent[0][0]); -} - -function d3_svg_brushRedrawY(g, extent) { - g.select(".extent").attr("y", extent[0][1]); - g.selectAll(".n,.e,.w,.nw,.ne").attr("y", extent[0][1] - 3); - g.selectAll(".s,.se,.sw").attr("y", extent[1][1] - 4); - g.selectAll(".extent,.e,.w").attr("height", extent[1][1] - extent[0][1]); -} - -function d3_svg_brushKeydown() { - if (d3.event.keyCode == 32 && d3_svg_brushTarget && !d3_svg_brushDrag) { - d3_svg_brushCenter = null; - d3_svg_brushOffset[0] -= d3_svg_brushExtent[1][0]; - d3_svg_brushOffset[1] -= d3_svg_brushExtent[1][1]; - d3_svg_brushDrag = 2; - d3_eventCancel(); - } -} - -function d3_svg_brushKeyup() { - if (d3.event.keyCode == 32 && d3_svg_brushDrag == 2) { - d3_svg_brushOffset[0] += d3_svg_brushExtent[1][0]; - d3_svg_brushOffset[1] += d3_svg_brushExtent[1][1]; - d3_svg_brushDrag = 0; - d3_eventCancel(); - } -} - -function d3_svg_brushMove() { - if (d3_svg_brushOffset) { - var mouse = d3.svg.mouse(d3_svg_brushTarget), - g = d3.select(d3_svg_brushTarget); - - if (!d3_svg_brushDrag) { - - // If needed, determine the center from the current extent. - if (d3.event.altKey) { - if (!d3_svg_brushCenter) { - d3_svg_brushCenter = [ - (d3_svg_brushExtent[0][0] + d3_svg_brushExtent[1][0]) / 2, - (d3_svg_brushExtent[0][1] + d3_svg_brushExtent[1][1]) / 2 - ]; - } - - // Update the offset, for when the ALT key is released. - d3_svg_brushOffset[0] = d3_svg_brushExtent[+(mouse[0] < d3_svg_brushCenter[0])][0]; - d3_svg_brushOffset[1] = d3_svg_brushExtent[+(mouse[1] < d3_svg_brushCenter[1])][1]; - } - - // When the ALT key is released, we clear the center. - else d3_svg_brushCenter = null; - } - - // Update the brush extent for each dimension. - if (d3_svg_brushX) { - d3_svg_brushMove1(mouse, d3_svg_brushX, 0); - d3_svg_brushRedrawX(g, d3_svg_brushExtent); - } - if (d3_svg_brushY) { - d3_svg_brushMove1(mouse, d3_svg_brushY, 1); - d3_svg_brushRedrawY(g, d3_svg_brushExtent); - } - - // Notify listeners. - d3_svg_brushDispatch("brush"); - } -} - -function d3_svg_brushMove1(mouse, scale, i) { - var range = d3_scaleRange(scale), - r0 = range[0], - r1 = range[1], - offset = d3_svg_brushOffset[i], - size = d3_svg_brushExtent[1][i] - d3_svg_brushExtent[0][i], - min, - max; - - // When dragging, reduce the range by the extent size and offset. - if (d3_svg_brushDrag) { - r0 -= offset; - r1 -= size + offset; - } - - // Clamp the mouse so that the extent fits within the range extent. - min = Math.max(r0, Math.min(r1, mouse[i])); - - // Compute the new extent bounds. - if (d3_svg_brushDrag) { - max = (min += offset) + size; - } else { - - // If the ALT key is pressed, then preserve the center of the extent. - if (d3_svg_brushCenter) offset = Math.max(r0, Math.min(r1, 2 * d3_svg_brushCenter[i] - min)); - - // Compute the min and max of the offset and mouse. - if (offset < min) { - max = min; - min = offset; - } else { - max = offset; - } - } - - // Update the stored bounds. - d3_svg_brushExtent[0][i] = min; - d3_svg_brushExtent[1][i] = max; -} - -function d3_svg_brushUp() { - if (d3_svg_brushOffset) { - d3_svg_brushMove(); - d3.select(d3_svg_brushTarget).selectAll(".resize").style("pointer-events", d3_svg_brush.empty() ? "none" : "all"); - d3_svg_brushDispatch("brushend"); - d3_svg_brush = - d3_svg_brushDispatch = - d3_svg_brushTarget = - d3_svg_brushX = - d3_svg_brushY = - d3_svg_brushExtent = - d3_svg_brushDrag = - d3_svg_brushResize = - d3_svg_brushCenter = - d3_svg_brushOffset = null; - d3_eventCancel(); - } -} - var d3_svg_brushCursor = { n: "ns-resize", e: "ew-resize", @@ -4394,228 +4571,265 @@ var d3_svg_brushCursor = { se: "nwse-resize", sw: "nesw-resize" }; + +var d3_svg_brushResizes = [ + ["n", "e", "s", "w", "nw", "ne", "se", "sw"], + ["e", "w"], + ["n", "s"], + [] +]; d3.behavior = {}; // TODO Track touch points by identifier. d3.behavior.drag = function() { - var event = d3.dispatch("drag", "dragstart", "dragend"), + var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null; function drag() { - this - .on("mousedown.drag", mousedown) + this.on("mousedown.drag", mousedown) .on("touchstart.drag", mousedown); + } + + function mousedown() { + var target = this, + event_ = event.of(target, arguments), + eventTarget = d3.event.target, + offset, + origin_ = point(), + moved = 0; + + var w = d3.select(window) + .on("mousemove.drag", dragmove) + .on("touchmove.drag", dragmove) + .on("mouseup.drag", dragend, true) + .on("touchend.drag", dragend, true); - d3.select(window) - .on("mousemove.drag", d3_behavior_dragMove) - .on("touchmove.drag", d3_behavior_dragMove) - .on("mouseup.drag", d3_behavior_dragUp, true) - .on("touchend.drag", d3_behavior_dragUp, true) - .on("click.drag", d3_behavior_dragClick, true); - } - - // snapshot the local context for subsequent dispatch - function start() { - d3_behavior_dragEvent = event; - d3_behavior_dragEventTarget = d3.event.target; - d3_behavior_dragTarget = this; - d3_behavior_dragArguments = arguments; - d3_behavior_dragOrigin = d3_behavior_dragPoint(); if (origin) { - d3_behavior_dragOffset = origin.apply(d3_behavior_dragTarget, d3_behavior_dragArguments); - d3_behavior_dragOffset = [d3_behavior_dragOffset.x - d3_behavior_dragOrigin[0], d3_behavior_dragOffset.y - d3_behavior_dragOrigin[1]]; + offset = origin.apply(target, arguments); + offset = [offset.x - origin_[0], offset.y - origin_[1]]; } else { - d3_behavior_dragOffset = [0, 0]; + offset = [0, 0]; + } + + event_({type: "dragstart"}); + + function point() { + var p = target.parentNode, + t = d3.event.changedTouches; + return t ? d3.touches(p, t)[0] : d3.mouse(p); + } + + function dragmove() { + if (!target.parentNode) return dragend(); // target removed from DOM + + var p = point(), + dx = p[0] - origin_[0], + dy = p[1] - origin_[1]; + + moved |= dx | dy; + origin_ = p; + d3_eventCancel(); + + event_({type: "drag", x: p[0] + offset[0], y: p[1] + offset[1], dx: dx, dy: dy}); + } + + function dragend() { + event_({type: "dragend"}); + + // if moved, prevent the mouseup (and possibly click) from propagating + if (moved) { + d3_eventCancel(); + if (d3.event.target === eventTarget) w.on("click.drag", click, true); + } + + w .on("mousemove.drag", null) + .on("touchmove.drag", null) + .on("mouseup.drag", null) + .on("touchend.drag", null); + } + + // prevent the subsequent click from propagating (e.g., for anchors) + function click() { + d3_eventCancel(); + w.on("click.drag", null); } - d3_behavior_dragMoved = 0; } - function mousedown() { - start.apply(this, arguments); - d3_behavior_dragDispatch("dragstart"); + drag.origin = function(x) { + if (!arguments.length) return origin; + origin = x; + return drag; + }; + + return d3.rebind(drag, event, "on"); +}; +d3.behavior.zoom = function() { + var translate = [0, 0], + translate0, // translate when we started zooming (to avoid drift) + scale = 1, + scale0, // scale when we started touching + scaleExtent = d3_behavior_zoomInfinity, + event = d3_eventDispatch(zoom, "zoom"), + x0, + x1, + y0, + y1, + touchtime; // time of last touchstart (to detect double-tap) + + function zoom() { + this + .on("mousedown.zoom", mousedown) + .on("mousewheel.zoom", mousewheel) + .on("mousemove.zoom", mousemove) + .on("DOMMouseScroll.zoom", mousewheel) + .on("dblclick.zoom", dblclick) + .on("touchstart.zoom", touchstart) + .on("touchmove.zoom", touchmove) + .on("touchend.zoom", touchstart); } - drag.origin = function(x) { - if (!arguments.length) return origin; - origin = x; - return drag; + zoom.translate = function(x) { + if (!arguments.length) return translate; + translate = x.map(Number); + return zoom; + }; + + zoom.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + return zoom; }; - return d3.rebind(drag, event, "on"); -}; + zoom.scaleExtent = function(x) { + if (!arguments.length) return scaleExtent; + scaleExtent = x == null ? d3_behavior_zoomInfinity : x.map(Number); + return zoom; + }; -var d3_behavior_dragEvent, - d3_behavior_dragEventTarget, - d3_behavior_dragTarget, - d3_behavior_dragArguments, - d3_behavior_dragOffset, - d3_behavior_dragOrigin, - d3_behavior_dragMoved; + zoom.x = function(z) { + if (!arguments.length) return x1; + x1 = z; + x0 = z.copy(); + return zoom; + }; -function d3_behavior_dragDispatch(type) { - var p = d3_behavior_dragPoint(), - o = d3.event, - e = d3.event = {type: type}; + zoom.y = function(z) { + if (!arguments.length) return y1; + y1 = z; + y0 = z.copy(); + return zoom; + }; - if (p) { - e.x = p[0] + d3_behavior_dragOffset[0]; - e.y = p[1] + d3_behavior_dragOffset[1]; - e.dx = p[0] - d3_behavior_dragOrigin[0]; - e.dy = p[1] - d3_behavior_dragOrigin[1]; - d3_behavior_dragMoved |= e.dx | e.dy; - d3_behavior_dragOrigin = p; + function location(p) { + return [(p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale]; } - try { - d3_behavior_dragEvent[type].apply(d3_behavior_dragTarget, d3_behavior_dragArguments); - } finally { - d3.event = o; + function point(l) { + return [l[0] * scale + translate[0], l[1] * scale + translate[1]]; } - o.stopPropagation(); - o.preventDefault(); -} - -function d3_behavior_dragPoint() { - var p = d3_behavior_dragTarget.parentNode, - t = d3.event.changedTouches; - return p && (t - ? d3.svg.touches(p, t)[0] - : d3.svg.mouse(p)); -} - -function d3_behavior_dragMove() { - if (!d3_behavior_dragTarget) return; - var parent = d3_behavior_dragTarget.parentNode; - - // O NOES! The drag element was removed from the DOM. - if (!parent) return d3_behavior_dragUp(); - - d3_behavior_dragDispatch("drag"); - d3_eventCancel(); -} + function scaleTo(s) { + scale = Math.max(scaleExtent[0], Math.min(scaleExtent[1], s)); + } -function d3_behavior_dragUp() { - if (!d3_behavior_dragTarget) return; - d3_behavior_dragDispatch("dragend"); + function translateTo(p, l) { + l = point(l); + translate[0] += p[0] - l[0]; + translate[1] += p[1] - l[1]; + } - // If the node was moved, prevent the mouseup from propagating. - // Also prevent the subsequent click from propagating (e.g., for anchors). - if (d3_behavior_dragMoved) { - d3_eventCancel(); - d3_behavior_dragMoved = d3.event.target === d3_behavior_dragEventTarget; + function dispatch(event) { + 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)); + d3.event.preventDefault(); + event({type: "zoom", scale: scale, translate: translate}); } - d3_behavior_dragEvent = - d3_behavior_dragEventTarget = - d3_behavior_dragTarget = - d3_behavior_dragArguments = - d3_behavior_dragOffset = - d3_behavior_dragOrigin = null; -} + function mousedown() { + var target = this, + event_ = event.of(target, arguments), + eventTarget = d3.event.target, + moved = 0, + w = d3.select(window).on("mousemove.zoom", mousemove).on("mouseup.zoom", mouseup), + l = location(d3.mouse(target)); -function d3_behavior_dragClick() { - if (d3_behavior_dragMoved) { + window.focus(); d3_eventCancel(); - d3_behavior_dragMoved = 0; - } -} -// TODO unbind zoom behavior? -d3.behavior.zoom = function() { - var xyz = [0, 0, 0], - event = d3.dispatch("zoom"), - extent = d3_behavior_zoomInfiniteExtent; - function zoom() { - this - .on("mousedown.zoom", mousedown) - .on("mousewheel.zoom", mousewheel) - .on("DOMMouseScroll.zoom", mousewheel) - .on("dblclick.zoom", dblclick) - .on("touchstart.zoom", touchstart); + function mousemove() { + moved = 1; + translateTo(d3.mouse(target), l); + dispatch(event_); + } - d3.select(window) - .on("mousemove.zoom", d3_behavior_zoomMousemove) - .on("mouseup.zoom", d3_behavior_zoomMouseup) - .on("touchmove.zoom", d3_behavior_zoomTouchmove) - .on("touchend.zoom", d3_behavior_zoomTouchup) - .on("click.zoom", d3_behavior_zoomClick, true); - } + 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); + } - // snapshot the local context for subsequent dispatch - function start() { - d3_behavior_zoomXyz = xyz; - d3_behavior_zoomExtent = extent; - d3_behavior_zoomDispatch = event.zoom; - d3_behavior_zoomEventTarget = d3.event.target; - d3_behavior_zoomTarget = this; - d3_behavior_zoomArguments = arguments; + function click() { + d3_eventCancel(); + w.on("click.zoom", null); + } } - function mousedown() { - start.apply(this, arguments); - d3_behavior_zoomPanning = d3_behavior_zoomLocation(d3.svg.mouse(d3_behavior_zoomTarget)); - d3_behavior_zoomMoved = 0; - d3.event.preventDefault(); - window.focus(); + 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)); } - // store starting mouse location - function mousewheel() { - start.apply(this, arguments); - if (!d3_behavior_zoomZooming) d3_behavior_zoomZooming = d3_behavior_zoomLocation(d3.svg.mouse(d3_behavior_zoomTarget)); - d3_behavior_zoomTo(d3_behavior_zoomDelta() + xyz[2], d3.svg.mouse(d3_behavior_zoomTarget), d3_behavior_zoomZooming); + function mousemove() { + translate0 = null; } function dblclick() { - start.apply(this, arguments); - var mouse = d3.svg.mouse(d3_behavior_zoomTarget); - d3_behavior_zoomTo(d3.event.shiftKey ? Math.ceil(xyz[2] - 1) : Math.floor(xyz[2] + 1), mouse, d3_behavior_zoomLocation(mouse)); + var p = d3.mouse(this), l = location(p); + scaleTo(d3.event.shiftKey ? scale / 2 : scale * 2); + translateTo(p, l); + dispatch(event.of(this, arguments)); } - // doubletap detection function touchstart() { - start.apply(this, arguments); - var touches = d3_behavior_zoomTouchup(), - touch, + var touches = d3.touches(this), now = Date.now(); - if ((touches.length === 1) && (now - d3_behavior_zoomLast < 300)) { - d3_behavior_zoomTo(1 + Math.floor(xyz[2]), touch = touches[0], d3_behavior_zoomLocations[touch.identifier]); + + scale0 = scale; + translate0 = {}; + touches.forEach(function(t) { translate0[t.identifier] = location(t); }); + d3_eventCancel(); + + if ((touches.length === 1) && (now - touchtime < 500)) { // dbltap + var p = touches[0], l = location(touches[0]); + scaleTo(scale * 2); + translateTo(p, l); + dispatch(event.of(this, arguments)); } - d3_behavior_zoomLast = now; + touchtime = now; } - zoom.extent = function(x) { - if (!arguments.length) return extent; - extent = x == null ? d3_behavior_zoomInfiniteExtent : x; - return zoom; - }; + 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); + dispatch(event.of(this, arguments)); + } return d3.rebind(zoom, event, "on"); }; -var d3_behavior_zoomDiv, - d3_behavior_zoomPanning, - d3_behavior_zoomZooming, - d3_behavior_zoomLocations = {}, // identifier -> location - d3_behavior_zoomLast = 0, - d3_behavior_zoomXyz, - d3_behavior_zoomExtent, - d3_behavior_zoomDispatch, - d3_behavior_zoomEventTarget, - d3_behavior_zoomTarget, - d3_behavior_zoomArguments, - d3_behavior_zoomMoved; - -function d3_behavior_zoomLocation(point) { - return [ - point[0] - d3_behavior_zoomXyz[0], - point[1] - d3_behavior_zoomXyz[1], - d3_behavior_zoomXyz[2] - ]; -} +var d3_behavior_zoomDiv, // for interpreting mousewheel events + d3_behavior_zoomInfinity = [0, Infinity]; // default scale extent -// detect the pixels that would be scrolled by this wheel event function d3_behavior_zoomDelta() { // mousewheel events are totally broken! @@ -4642,125 +4856,7 @@ function d3_behavior_zoomDelta() { delta = e.wheelDelta || (-e.detail * 5); } - return delta * .005; -} - -// Note: Since we don't rotate, it's possible for the touches to become -// slightly detached from their original positions. Thus, we recompute the -// touch points on touchend as well as touchstart! -function d3_behavior_zoomTouchup() { - var touches = d3.svg.touches(d3_behavior_zoomTarget), - i = -1, - n = touches.length, - touch; - while (++i < n) d3_behavior_zoomLocations[(touch = touches[i]).identifier] = d3_behavior_zoomLocation(touch); - return touches; -} - -function d3_behavior_zoomTouchmove() { - var touches = d3.svg.touches(d3_behavior_zoomTarget); - switch (touches.length) { - - // single-touch pan - case 1: { - var touch = touches[0]; - d3_behavior_zoomTo(d3_behavior_zoomXyz[2], touch, d3_behavior_zoomLocations[touch.identifier]); - break; - } - - // double-touch pan + zoom - case 2: { - var p0 = touches[0], - p1 = touches[1], - p2 = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2], - l0 = d3_behavior_zoomLocations[p0.identifier], - l1 = d3_behavior_zoomLocations[p1.identifier], - l2 = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2, l0[2]]; - d3_behavior_zoomTo(Math.log(d3.event.scale) / Math.LN2 + l0[2], p2, l2); - break; - } - } -} - -function d3_behavior_zoomMousemove() { - d3_behavior_zoomZooming = null; - if (d3_behavior_zoomPanning) { - d3_behavior_zoomMoved = 1; - d3_behavior_zoomTo(d3_behavior_zoomXyz[2], d3.svg.mouse(d3_behavior_zoomTarget), d3_behavior_zoomPanning); - } -} - -function d3_behavior_zoomMouseup() { - if (d3_behavior_zoomPanning) { - if (d3_behavior_zoomMoved) { - d3_eventCancel(); - d3_behavior_zoomMoved = d3_behavior_zoomEventTarget === d3.event.target; - } - - d3_behavior_zoomXyz = - d3_behavior_zoomExtent = - d3_behavior_zoomDispatch = - d3_behavior_zoomEventTarget = - d3_behavior_zoomTarget = - d3_behavior_zoomArguments = - d3_behavior_zoomPanning = null; - } -} - -function d3_behavior_zoomClick() { - if (d3_behavior_zoomMoved) { - d3_eventCancel(); - d3_behavior_zoomMoved = 0; - } -} - -function d3_behavior_zoomTo(z, x0, x1) { - z = d3_behavior_zoomExtentClamp(z, 2); - var j = Math.pow(2, d3_behavior_zoomXyz[2]), - k = Math.pow(2, z), - K = Math.pow(2, (d3_behavior_zoomXyz[2] = z) - x1[2]), - x_ = d3_behavior_zoomXyz[0], - y_ = d3_behavior_zoomXyz[1], - x = d3_behavior_zoomXyz[0] = d3_behavior_zoomExtentClamp((x0[0] - x1[0] * K), 0, k), - y = d3_behavior_zoomXyz[1] = d3_behavior_zoomExtentClamp((x0[1] - x1[1] * K), 1, k), - o = d3.event; // Events can be reentrant (e.g., focus). - - d3.event = { - scale: k, - translate: [x, y], - transform: function(sx, sy) { - if (sx) transform(sx, x_, x); - if (sy) transform(sy, y_, y); - } - }; - - function transform(scale, a, b) { - scale.domain(scale.range().map(function(v) { return scale.invert(((v - b) * j) / k + a); })); - } - - try { - d3_behavior_zoomDispatch.apply(d3_behavior_zoomTarget, d3_behavior_zoomArguments); - } finally { - d3.event = o; - } - - o.preventDefault(); -} - -var d3_behavior_zoomInfiniteExtent = [ - [-Infinity, Infinity], - [-Infinity, Infinity], - [-Infinity, Infinity] -]; - -function d3_behavior_zoomExtentClamp(x, i, k) { - var range = d3_behavior_zoomExtent[i], - r0 = range[0], - r1 = range[1]; - return arguments.length === 3 - ? Math.max(r1 * (r1 === Infinity ? -Infinity : 1 / k - 1), - Math.min(r0 === -Infinity ? Infinity : r0, x / k)) * k - : Math.max(r0, Math.min(r1, x)); + return delta; } d3.layout = {}; // Implements hierarchical edge bundling using Holten's algorithm. For each @@ -4976,7 +5072,7 @@ d3.layout.chord = function() { // A rudimentary force layout using Gauss-Seidel. d3.layout.force = function() { var force = {}, - event = d3.dispatch("tick"), + event = d3.dispatch("start", "tick", "end"), size = [1, 1], drag, alpha, @@ -5018,9 +5114,12 @@ d3.layout.force = function() { }; } - function tick() { + force.tick = function() { // simulated annealing, basically - if ((alpha *= .99) < .005) return true; + if ((alpha *= .99) < .005) { + event.end({type: "end", alpha: alpha = 0}); + return true; + } var n = nodes.length, m = links.length, @@ -5086,7 +5185,7 @@ d3.layout.force = function() { } event.tick({type: "tick", alpha: alpha}); - } + }; force.nodes = function(x) { if (!arguments.length) return nodes; @@ -5145,6 +5244,20 @@ d3.layout.force = function() { return force; }; + force.alpha = function(x) { + if (!arguments.length) return alpha; + + if (alpha) { // if we're already running + if (x > 0) alpha = x; // we might keep it hot + else alpha = 0; // or, next tick will dispatch "end" + } else if (x > 0) { // otherwise, fire it up! + event.start({type: "start", alpha: alpha = x}); + d3.timer(force.tick); + } + + return force; + }; + force.start = function() { var i, j, @@ -5221,14 +5334,11 @@ d3.layout.force = function() { }; force.resume = function() { - alpha = .1; - d3.timer(tick); - return force; + return force.alpha(.1); }; force.stop = function() { - alpha = 0; - return force; + return force.alpha(0); }; // use `node.call(force.drag)` to make nodes draggable @@ -5264,7 +5374,6 @@ function d3_layout_forceDragOut(d) { } function d3_layout_forceDragEnd() { - d3_layout_forceDrag(); d3_layout_forceDragNode.fixed &= 1; d3_layout_forceDragForce = d3_layout_forceDragNode = null; } @@ -5459,8 +5568,8 @@ var d3_layout_pieSortByValue = {}; // data is two-dimensional array of x,y; we populate y0 d3.layout.stack = function() { var values = Object, - order = d3_layout_stackOrders["default"], - offset = d3_layout_stackOffsets["zero"], + order = d3_layout_stackOrderDefault, + offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY; @@ -5511,13 +5620,13 @@ d3.layout.stack = function() { stack.order = function(x) { if (!arguments.length) return order; - order = typeof x === "function" ? x : d3_layout_stackOrders[x]; + order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault; return stack; }; stack.offset = function(x) { if (!arguments.length) return offset; - offset = typeof x === "function" ? x : d3_layout_stackOffsets[x]; + offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero; return stack; }; @@ -5555,7 +5664,7 @@ function d3_layout_stackOut(d, y0, y) { d.y = y; } -var d3_layout_stackOrders = { +var d3_layout_stackOrders = d3.map({ "inside-out": function(data) { var n = data.length, @@ -5585,13 +5694,11 @@ var d3_layout_stackOrders = { return d3.range(data.length).reverse(); }, - "default": function(data) { - return d3.range(data.length); - } + "default": d3_layout_stackOrderDefault -}; +}); -var d3_layout_stackOffsets = { +var d3_layout_stackOffsets = d3.map({ "silhouette": function(data) { var n = data.length, @@ -5661,15 +5768,21 @@ var d3_layout_stackOffsets = { return y0; }, - "zero": function(data) { - var j = -1, - m = data[0].length, - y0 = []; - while (++j < m) y0[j] = 0; - return y0; - } + "zero": d3_layout_stackOffsetZero -}; +}); + +function d3_layout_stackOrderDefault(data) { + return d3.range(data.length); +} + +function d3_layout_stackOffsetZero(data) { + var j = -1, + m = data[0].length, + y0 = []; + while (++j < m) y0[j] = 0; + return y0; +} function d3_layout_stackMaxIndex(array) { var i = 1, @@ -7087,7 +7200,7 @@ d3.geo.mercator = function() { }; function d3_geo_type(types, defaultValue) { return function(object) { - return object && object.type in types ? types[object.type](object) : defaultValue; + return object && types.hasOwnProperty(object.type) ? types[object.type](object) : defaultValue; }; } /** @@ -7382,7 +7495,7 @@ d3.geo.bounds = function(feature) { }; function d3_geo_bounds(o, f) { - if (o.type in d3_geo_boundsTypes) d3_geo_boundsTypes[o.type](o, f); + if (d3_geo_boundsTypes.hasOwnProperty(o.type)) d3_geo_boundsTypes[o.type](o, f); } var d3_geo_boundsTypes = { @@ -8508,6 +8621,35 @@ function d3_geom_quadtreePoint(p) { d3.time = {}; var d3_time = Date; + +function d3_time_utc() { + this._ = new Date(arguments.length > 1 + ? Date.UTC.apply(this, arguments) + : arguments[0]); +} + +d3_time_utc.prototype = { + getDate: function() { return this._.getUTCDate(); }, + getDay: function() { return this._.getUTCDay(); }, + getFullYear: function() { return this._.getUTCFullYear(); }, + getHours: function() { return this._.getUTCHours(); }, + getMilliseconds: function() { return this._.getUTCMilliseconds(); }, + getMinutes: function() { return this._.getUTCMinutes(); }, + getMonth: function() { return this._.getUTCMonth(); }, + getSeconds: function() { return this._.getUTCSeconds(); }, + getTime: function() { return this._.getTime(); }, + getTimezoneOffset: function() { return 0; }, + valueOf: function() { return this._.valueOf(); }, + setDate: function(x) { this._.setUTCDate(x); }, + setDay: function(x) { this._.setUTCDay(x); }, + setFullYear: function(x) { this._.setUTCFullYear(x); }, + setHours: function(x) { this._.setUTCHours(x); }, + setMilliseconds: function(x) { this._.setUTCMilliseconds(x); }, + setMinutes: function(x) { this._.setUTCMinutes(x); }, + setMonth: function(x) { this._.setUTCMonth(x); }, + setSeconds: function(x) { this._.setUTCSeconds(x); }, + setTime: function(x) { this._.setTime(x); } +}; d3.time.format = function(template) { var n = template.length; @@ -8584,15 +8726,15 @@ var d3_time_formats = { e: function(d) { return d3_time_sfill2(d.getDate()); }, H: function(d) { return d3_time_zfill2(d.getHours()); }, I: function(d) { return d3_time_zfill2(d.getHours() % 12 || 12); }, - j: d3_time_dayOfYear, + j: function(d) { return d3_time_zfill3(1 + d3.time.dayOfYear(d)); }, L: function(d) { return d3_time_zfill3(d.getMilliseconds()); }, m: function(d) { return d3_time_zfill2(d.getMonth() + 1); }, M: function(d) { return d3_time_zfill2(d.getMinutes()); }, p: function(d) { return d.getHours() >= 12 ? "PM" : "AM"; }, S: function(d) { return d3_time_zfill2(d.getSeconds()); }, - U: d3_time_weekNumberSunday, + U: function(d) { return d3_time_zfill2(d3.time.sundayOfYear(d)); }, w: function(d) { return d.getDay(); }, - W: d3_time_weekNumberMonday, + W: function(d) { return d3_time_zfill2(d3.time.mondayOfYear(d)); }, x: d3.time.format("%m/%d/%y"), X: d3.time.format("%H:%M:%S"), y: function(d) { return d3_time_zfill2(d.getFullYear() % 100); }, @@ -8631,19 +8773,9 @@ var d3_time_parsers = { // Note: weekday is validated, but does not set the date. function d3_time_parseWeekdayAbbrev(date, string, i) { - return string.substring(i, i += 3).toLowerCase() in d3_time_weekdayAbbrevLookup ? i : -1; + return d3_time_weekdayAbbrevRe.test(string.substring(i, i += 3)) ? i : -1; } -var d3_time_weekdayAbbrevLookup = { - sun: 3, - mon: 3, - tue: 3, - wed: 3, - thu: 3, - fri: 3, - sat: 3 -}; - // Note: weekday is validated, but does not set the date. function d3_time_parseWeekday(date, string, i) { d3_time_weekdayRe.lastIndex = 0; @@ -8651,24 +8783,16 @@ function d3_time_parseWeekday(date, string, i) { return n ? i += n[0].length : -1; } -var d3_time_weekdayRe = /^(?:Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday)/ig; - -var d3_time_weekdays = [ - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday" -]; +var d3_time_weekdayAbbrevRe = /^(?:sun|mon|tue|wed|thu|fri|sat)/i, + d3_time_weekdayRe = /^(?:Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday)/i; + d3_time_weekdays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; function d3_time_parseMonthAbbrev(date, string, i) { - var n = d3_time_monthAbbrevLookup[string.substring(i, i += 3).toLowerCase()]; + var n = d3_time_monthAbbrevLookup.get(string.substring(i, i += 3).toLowerCase()); return n == null ? -1 : (date.setMonth(n), i); } -var d3_time_monthAbbrevLookup = { +var d3_time_monthAbbrevLookup = d3.map({ jan: 0, feb: 1, mar: 2, @@ -8681,17 +8805,17 @@ var d3_time_monthAbbrevLookup = { oct: 9, nov: 10, dec: 11 -}; +}); function d3_time_parseMonth(date, string, i) { d3_time_monthRe.lastIndex = 0; var n = d3_time_monthRe.exec(string.substring(i, i + 12)); - return n ? (date.setMonth(d3_time_monthLookup[n[0].toLowerCase()]), i += n[0].length) : -1; + return n ? (date.setMonth(d3_time_monthLookup.get(n[0].toLowerCase())), i += n[0].length) : -1; } var d3_time_monthRe = /^(?:January|February|March|April|May|June|July|August|September|October|November|December)/ig; -var d3_time_monthLookup = { +var d3_time_monthLookup = d3.map({ january: 0, february: 1, march: 2, @@ -8704,7 +8828,7 @@ var d3_time_monthLookup = { october: 9, november: 10, december: 11 -}; +}); var d3_time_months = [ "January", @@ -8796,36 +8920,14 @@ function d3_time_parseMilliseconds(date, string, i) { var d3_time_numberRe = /\s*\d+/; function d3_time_parseAmPm(date, string, i) { - var n = d3_time_amPmLookup[string.substring(i, i += 2).toLowerCase()]; + var n = d3_time_amPmLookup.get(string.substring(i, i += 2).toLowerCase()); return n == null ? -1 : (date.hour12pm = n, i); } -var d3_time_amPmLookup = { +var d3_time_amPmLookup = d3.map({ am: 0, pm: 1 -}; - -function d3_time_year(d) { - return new d3_time(d.getFullYear(), 0, 1); -} - -function d3_time_daysElapsed(d0, d1) { - return ~~((d1 - d0) / 864e5 - (d1.getTimezoneOffset() - d0.getTimezoneOffset()) / 1440); -} - -function d3_time_dayOfYear(d) { - return d3_time_zfill3(1 + d3_time_daysElapsed(d3_time_year(d), d)); -} - -function d3_time_weekNumberSunday(d) { - var d0 = d3_time_year(d); - return d3_time_zfill2(~~((d3_time_daysElapsed(d0, d) + d0.getDay()) / 7)); -} - -function d3_time_weekNumberMonday(d) { - var d0 = d3_time_year(d); - return d3_time_zfill2(~~((d3_time_daysElapsed(d0, d) + (d0.getDay() + 6) % 7) / 7)); -} +}); // TODO table of time zone offset names? function d3_time_zone(d) { @@ -8840,7 +8942,7 @@ d3.time.format.utc = function(template) { function format(date) { try { - d3_time = d3_time_format_utc; + d3_time = d3_time_utc; var utc = new d3_time(); utc._ = date; return local(utc); @@ -8851,7 +8953,7 @@ d3.time.format.utc = function(template) { format.parse = function(string) { try { - d3_time = d3_time_format_utc; + d3_time = d3_time_utc; var date = local.parse(string); return date && date._; } finally { @@ -8863,31 +8965,6 @@ d3.time.format.utc = function(template) { return format; }; - -function d3_time_format_utc() { - this._ = new Date(Date.UTC.apply(this, arguments)); -} - -d3_time_format_utc.prototype = { - getDate: function() { return this._.getUTCDate(); }, - getDay: function() { return this._.getUTCDay(); }, - getFullYear: function() { return this._.getUTCFullYear(); }, - getHours: function() { return this._.getUTCHours(); }, - getMilliseconds: function() { return this._.getUTCMilliseconds(); }, - getMinutes: function() { return this._.getUTCMinutes(); }, - getMonth: function() { return this._.getUTCMonth(); }, - getSeconds: function() { return this._.getUTCSeconds(); }, - getTimezoneOffset: function() { return 0; }, - valueOf: function() { return this._.getTime(); }, - setDate: function(x) { this._.setUTCDate(x); }, - setDay: function(x) { this._.setUTCDay(x); }, - setFullYear: function(x) { this._.setUTCFullYear(x); }, - setHours: function(x) { this._.setUTCHours(x); }, - setMilliseconds: function(x) { this._.setUTCMilliseconds(x); }, - setMinutes: function(x) { this._.setUTCMinutes(x); }, - setMonth: function(x) { this._.setUTCMonth(x); }, - setSeconds: function(x) { this._.setUTCSeconds(x); } -}; var d3_time_formatIso = d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ"); d3.time.format.iso = Date.prototype.toISOString ? d3_time_formatIsoNative : d3_time_formatIso; @@ -8901,143 +8978,168 @@ d3_time_formatIsoNative.parse = function(string) { }; d3_time_formatIsoNative.toString = d3_time_formatIso.toString; -function d3_time_range(floor, step, number) { - return function(t0, t1, dt) { - var time = floor(t0), times = []; - if (time < t0) step(time); +function d3_time_interval(local, step, number) { + + function round(date) { + var d0 = local(date), d1 = offset(d0, 1); + return date - d0 < d1 - date ? d0 : d1; + } + + function ceil(date) { + step(date = local(new d3_time(date - 1)), 1); + return date; + } + + function offset(date, k) { + step(date = new d3_time(+date), k); + return date; + } + + function range(t0, t1, dt) { + var time = ceil(t0), times = []; if (dt > 1) { while (time < t1) { - var date = new Date(+time); - if (!(number(date) % dt)) times.push(date); - step(time); + if (!(number(time) % dt)) times.push(new Date(+time)); + step(time, 1); } } else { - while (time < t1) times.push(new Date(+time)), step(time); + while (time < t1) times.push(new Date(+time)), step(time, 1); } return times; - }; -} -d3.time.second = function(date) { - return new Date(~~(date / 1e3) * 1e3); -}; - -d3.time.second.utc = d3.time.second; -d3.time.seconds = d3_time_range(d3.time.second, function(date) { - date.setTime(date.getTime() + 1e3); -}, function(date) { - return date.getSeconds(); -}); + } -d3.time.seconds.utc = d3.time.seconds; -d3.time.minute = function(date) { - return new Date(~~(date / 6e4) * 6e4); -}; + function range_utc(t0, t1, dt) { + try { + d3_time = d3_time_utc; + var utc = new d3_time_utc(); + utc._ = t0; + return range(utc, t1, dt); + } finally { + d3_time = Date; + } + } -d3.time.minute.utc = d3.time.minute;d3.time.minutes = d3_time_range(d3.time.minute, d3_time_minutesStep, function(date) { - return date.getMinutes(); -}); + local.floor = local; + local.round = round; + local.ceil = ceil; + local.offset = offset; + local.range = range; -d3.time.minutes.utc = d3_time_range(d3.time.minute, d3_time_minutesStep, function(date) { - return date.getUTCMinutes(); -}); + var utc = local.utc = d3_time_interval_utc(local); + utc.floor = utc; + utc.round = d3_time_interval_utc(round); + utc.ceil = d3_time_interval_utc(ceil); + utc.offset = d3_time_interval_utc(offset); + utc.range = range_utc; -function d3_time_minutesStep(date) { - date.setTime(date.getTime() + 6e4); // assumes no leap seconds + return local; } -d3.time.hour = function(date) { - var offset = date.getTimezoneOffset() / 60; - return new Date((~~(date / 36e5 - offset) + offset) * 36e5); -}; - -d3.time.hour.utc = function(date) { - return new Date(~~(date / 36e5) * 36e5); -}; -d3.time.hours = d3_time_range(d3.time.hour, d3_time_hoursStep, function(date) { - return date.getHours(); -}); - -d3.time.hours.utc = d3_time_range(d3.time.hour.utc, d3_time_hoursStep, function(date) { - return date.getUTCHours(); -}); -function d3_time_hoursStep(date) { - date.setTime(date.getTime() + 36e5); +function d3_time_interval_utc(method) { + return function(date, k) { + try { + d3_time = d3_time_utc; + var utc = new d3_time_utc(); + utc._ = date; + return method(utc, k)._; + } finally { + d3_time = Date; + } + }; } -d3.time.day = function(date) { - return new Date(date.getFullYear(), date.getMonth(), date.getDate()); -}; - -d3.time.day.utc = function(date) { - return new Date(~~(date / 864e5) * 864e5); -}; -d3.time.days = d3_time_range(d3.time.day, function(date) { - date.setDate(date.getDate() + 1); +d3.time.second = d3_time_interval(function(date) { + return new d3_time(Math.floor(date / 1e3) * 1e3); +}, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 1e3); // DST breaks setSeconds }, function(date) { - return date.getDate() - 1; + return date.getSeconds(); }); -d3.time.days.utc = d3_time_range(d3.time.day.utc, function(date) { - date.setUTCDate(date.getUTCDate() + 1); +d3.time.seconds = d3.time.second.range; +d3.time.seconds.utc = d3.time.second.utc.range; +d3.time.minute = d3_time_interval(function(date) { + return new d3_time(Math.floor(date / 6e4) * 6e4); +}, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 6e4); // DST breaks setMinutes }, function(date) { - return date.getUTCDate() - 1; + return date.getMinutes(); }); -d3.time.week = function(date) { - (date = d3.time.day(date)).setDate(date.getDate() - date.getDay()); - return date; -}; -d3.time.week.utc = function(date) { - (date = d3.time.day.utc(date)).setUTCDate(date.getUTCDate() - date.getUTCDay()); - return date; -}; -d3.time.weeks = d3_time_range(d3.time.week, function(date) { - date.setDate(date.getDate() + 7); +d3.time.minutes = d3.time.minute.range; +d3.time.minutes.utc = d3.time.minute.utc.range; +d3.time.hour = d3_time_interval(function(date) { + var timezone = date.getTimezoneOffset() / 60; + return new d3_time((Math.floor(date / 36e5 - timezone) + timezone) * 36e5); +}, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 36e5); // DST breaks setHours }, function(date) { - return ~~((date - new Date(date.getFullYear(), 0, 1)) / 6048e5); + return date.getHours(); }); -d3.time.weeks.utc = d3_time_range(d3.time.week.utc, function(date) { - date.setUTCDate(date.getUTCDate() + 7); +d3.time.hours = d3.time.hour.range; +d3.time.hours.utc = d3.time.hour.utc.range; +d3.time.day = d3_time_interval(function(date) { + return new d3_time(date.getFullYear(), date.getMonth(), date.getDate()); +}, function(date, offset) { + date.setDate(date.getDate() + offset); }, function(date) { - return ~~((date - Date.UTC(date.getUTCFullYear(), 0, 1)) / 6048e5); + return date.getDate() - 1; }); -d3.time.month = function(date) { - return new Date(date.getFullYear(), date.getMonth(), 1); -}; -d3.time.month.utc = function(date) { - return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1)); +d3.time.days = d3.time.day.range; +d3.time.days.utc = d3.time.day.utc.range; + +d3.time.dayOfYear = function(date) { + var year = d3.time.year(date); + return Math.floor((date - year) / 864e5 - (date.getTimezoneOffset() - year.getTimezoneOffset()) / 1440); }; -d3.time.months = d3_time_range(d3.time.month, function(date) { - date.setMonth(date.getMonth() + 1); -}, function(date) { - return date.getMonth(); +d3_time_weekdays.forEach(function(day, i) { + day = day.toLowerCase(); + i = 7 - i; + + var interval = d3.time[day] = d3_time_interval(function(date) { + (date = d3.time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7); + return date; + }, function(date, offset) { + date.setDate(date.getDate() + Math.floor(offset) * 7); + }, function(date) { + var day = d3.time.year(date).getDay(); + return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i); + }); + + d3.time[day + "s"] = interval.range; + d3.time[day + "s"].utc = interval.utc.range; + + d3.time[day + "OfYear"] = function(date) { + var day = d3.time.year(date).getDay(); + return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7); + }; }); -d3.time.months.utc = d3_time_range(d3.time.month.utc, function(date) { - date.setUTCMonth(date.getUTCMonth() + 1); +d3.time.week = d3.time.sunday; +d3.time.weeks = d3.time.sunday.range; +d3.time.weeks.utc = d3.time.sunday.utc.range; +d3.time.weekOfYear = d3.time.sundayOfYear; +d3.time.month = d3_time_interval(function(date) { + return new d3_time(date.getFullYear(), date.getMonth(), 1); +}, function(date, offset) { + date.setMonth(date.getMonth() + offset); }, function(date) { - return date.getUTCMonth(); + return date.getMonth(); }); -d3.time.year = function(date) { - return new Date(date.getFullYear(), 0, 1); -}; -d3.time.year.utc = function(date) { - return new Date(Date.UTC(date.getUTCFullYear(), 0, 1)); -}; -d3.time.years = d3_time_range(d3.time.year, function(date) { - date.setFullYear(date.getFullYear() + 1); +d3.time.months = d3.time.month.range; +d3.time.months.utc = d3.time.month.utc.range; +d3.time.year = d3_time_interval(function(date) { + return new d3_time(date.getFullYear(), 0, 1); +}, function(date, offset) { + date.setFullYear(date.getFullYear() + offset); }, function(date) { return date.getFullYear(); }); -d3.time.years.utc = d3_time_range(d3.time.year.utc, function(date) { - date.setUTCFullYear(date.getUTCFullYear() + 1); -}, function(date) { - return date.getUTCFullYear(); -}); -// TODO nice +d3.time.years = d3.time.year.range; +d3.time.years.utc = d3.time.year.utc.range; function d3_time_scale(linear, methods, format) { function scale(x) { @@ -9054,6 +9156,11 @@ function d3_time_scale(linear, methods, format) { return scale; }; + scale.nice = function(m) { + var extent = d3_time_scaleExtent(scale.domain()); + return scale.domain([m.floor(extent[0]), m.ceil(extent[1])]); + }; + scale.ticks = function(m, k) { var extent = d3_time_scaleExtent(scale.domain()); if (typeof m !== "function") { @@ -9065,7 +9172,7 @@ function d3_time_scale(linear, methods, format) { if (Math.log(target / d3_time_scaleSteps[i - 1]) < Math.log(d3_time_scaleSteps[i] / target)) --i; m = methods[i]; k = m[1]; - m = m[0]; + m = m[0].range; } return m(extent[0], new Date(+extent[1] + 1), k); // inclusive upper bound }; @@ -9135,24 +9242,24 @@ var d3_time_scaleSteps = [ ]; var d3_time_scaleLocalMethods = [ - [d3.time.seconds, 1], - [d3.time.seconds, 5], - [d3.time.seconds, 15], - [d3.time.seconds, 30], - [d3.time.minutes, 1], - [d3.time.minutes, 5], - [d3.time.minutes, 15], - [d3.time.minutes, 30], - [d3.time.hours, 1], - [d3.time.hours, 3], - [d3.time.hours, 6], - [d3.time.hours, 12], - [d3.time.days, 1], - [d3.time.days, 2], - [d3.time.weeks, 1], - [d3.time.months, 1], - [d3.time.months, 3], - [d3.time.years, 1] + [d3.time.second, 1], + [d3.time.second, 5], + [d3.time.second, 15], + [d3.time.second, 30], + [d3.time.minute, 1], + [d3.time.minute, 5], + [d3.time.minute, 15], + [d3.time.minute, 30], + [d3.time.hour, 1], + [d3.time.hour, 3], + [d3.time.hour, 6], + [d3.time.hour, 12], + [d3.time.day, 1], + [d3.time.day, 2], + [d3.time.week, 1], + [d3.time.month, 1], + [d3.time.month, 3], + [d3.time.year, 1] ]; var d3_time_scaleLocalFormats = [ @@ -9176,26 +9283,9 @@ d3_time_scaleLocalMethods.year = function(extent, m) { d3.time.scale = function() { return d3_time_scale(d3.scale.linear(), d3_time_scaleLocalMethods, d3_time_scaleLocalFormat); }; -var d3_time_scaleUTCMethods = [ - [d3.time.seconds.utc, 1], - [d3.time.seconds.utc, 5], - [d3.time.seconds.utc, 15], - [d3.time.seconds.utc, 30], - [d3.time.minutes.utc, 1], - [d3.time.minutes.utc, 5], - [d3.time.minutes.utc, 15], - [d3.time.minutes.utc, 30], - [d3.time.hours.utc, 1], - [d3.time.hours.utc, 3], - [d3.time.hours.utc, 6], - [d3.time.hours.utc, 12], - [d3.time.days.utc, 1], - [d3.time.days.utc, 2], - [d3.time.weeks.utc, 1], - [d3.time.months.utc, 1], - [d3.time.months.utc, 3], - [d3.time.years.utc, 1] -]; +var d3_time_scaleUTCMethods = d3_time_scaleLocalMethods.map(function(m) { + return [m[0].utc, m[1]]; +}); var d3_time_scaleUTCFormats = [ [d3.time.format.utc("%Y"), function(d) { return true; }], diff --git a/d3.v2.min.js b/d3.v2.min.js index 04bcdb9d17230..e34f898500b3d 100644 --- a/d3.v2.min.js +++ b/d3.v2.min.js @@ -1,4 +1,4 @@ -(function(){function f(a){var b=-1,c=a.length,d=[];while(++b=0?a.substring(b):(b=a.length,""),d=[];while(b>0)d.push(a.substring(b-=3,b+3));return d.reverse().join(",")+c}function A(a,b){return{scale:Math.pow(10,(8-b)*3),symbol:a}}function F(a){return function(b){return b<=0?0:b>=1?1:a(b)}}function G(a){return function(b){return 1-a(1-b)}}function H(a){return function(b){return.5*(b<.5?a(2*b):2-a(2-2*b))}}function I(a){return a}function J(a){return function(b){return Math.pow(b,a)}}function K(a){return 1-Math.cos(a*Math.PI/2)}function L(a){return Math.pow(2,10*(a-1))}function M(a){return 1-Math.sqrt(1-a*a)}function N(a,b){var c;return arguments.length<2&&(b=.45),arguments.length<1?(a=1,c=b/4):c=b/(2*Math.PI)*Math.asin(1/a),function(d){return 1+a*Math.pow(2,10*-d)*Math.sin((d-c)*2*Math.PI/b)}}function O(a){return a||(a=1.70158),function(b){return b*b*((a+1)*b-a)}}function P(a){return a<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+.9375:7.5625*(a-=2.625/2.75)*a+.984375}function Q(){d3.event.stopPropagation(),d3.event.preventDefault()}function S(a){return a=="transform"?d3.interpolateTransform:d3.interpolate}function T(a,b){return b=b-(a=+a)?1/(b-a):0,function(c){return(c-a)*b}}function U(a,b){return b=b-(a=+a)?1/(b-a):0,function(c){return Math.max(0,Math.min(1,(c-a)*b))}}function V(a,b,c){return new W(a,b,c)}function W(a,b,c){this.r=a,this.g=b,this.b=c}function X(a){return a<16?"0"+Math.max(0,a).toString(16):Math.min(255,a).toString(16)}function Y(a,b,c){var d=0,e=0,f=0,g,h,i;g=/([a-z]+)\((.*)\)/i.exec(a);if(g){h=g[2].split(",");switch(g[1]){case"hsl":return c(parseFloat(h[0]),parseFloat(h[1])/100,parseFloat(h[2])/100);case"rgb":return b($(h[0]),$(h[1]),$(h[2]))}}return(i=_[a])?b(i.r,i.g,i.b):(a!=null&&a.charAt(0)==="#"&&(a.length===4?(d=a.charAt(1),d+=d,e=a.charAt(2),e+=e,f=a.charAt(3),f+=f):a.length===7&&(d=a.substring(1,3),e=a.substring(3,5),f=a.substring(5,7)),d=parseInt(d,16),e=parseInt(e,16),f=parseInt(f,16)),b(d,e,f))}function Z(a,b,c){var d=Math.min(a/=255,b/=255,c/=255),e=Math.max(a,b,c),f=e-d,g,h,i=(e+d)/2;return f?(h=i<.5?f/(e+d):f/(2-e-d),a==e?g=(b-c)/f+(b360?a-=360:a<0&&(a+=360),a<60?d+(e-d)*a/60:a<180?e:a<240?d+(e-d)*(240-a)/60:d}function g(a){return Math.round(f(a)*255)}var d,e;return a%=360,a<0&&(a+=360),b=b<0?0:b>1?1:b,c=c<0?0:c>1?1:c,e=c<=.5?c*(1+b):c+b-c*b,d=2*c-e,V(g(a+120),g(a),g(a-120))}function be(a){return i(a,bk),a}function bl(a){return function(){return bf(a,this)}}function bm(a){return function(){return bg(a,this)}}function bo(a,b){function f(){if(b=this.classList)return b.add(a);var b=this.className,d=b.baseVal!=null,e=d?b.baseVal:b;c.lastIndex=0,c.test(e)||(e=o(e+" "+a),d?b.baseVal=e:this.className=e)}function g(){if(b=this.classList)return b.remove(a);var b=this.className,d=b.baseVal!=null,e=d?b.baseVal:b;e=o(e.replace(c," ")),d?b.baseVal=e:this.className=e}function h(){(b.apply(this,arguments)?f:g).call(this)}var c=new RegExp("(^|\\s+)"+d3.requote(a)+"(\\s+|$)","g");if(arguments.length<2){var d=this.node();if(e=d.classList)return e.contains(a);var e=d.className;return c.lastIndex=0,c.test(e.baseVal!=null?e.baseVal:e)}return this.each(typeof b=="function"?h:b?f:g)}function bp(a){return{__data__:a}}function bq(a){return function(){return bj(this,a)}}function br(a){return arguments.length||(a=d3.ascending),function(b,c){return a(b&&b.__data__,c&&c.__data__)}}function bt(a){return i(a,bu),a}function bv(a,b,c){i(a,bz);var d={},e=d3.dispatch("start","end"),f=bC;return a.id=b,a.time=c,a.tween=function(b,c){return arguments.length<2?d[b]:(c==null?delete d[b]:d[b]=c,a)},a.ease=function(b){return arguments.length?(f=typeof b=="function"?b:d3.ease.apply(d3,arguments),a):f},a.each=function(b,c){return arguments.length<2?bD.call(a,b):(e.on(b,c),a)},d3.timer(function(g){return a.each(function(h,i,j){function p(a){if(o.active>b)return r();o.active=b;for(var f in d)(f=d[f].call(l,h,i))&&k.push(f);return e.start.call(l,h,i),q(a)||d3.timer(q,0,c),1}function q(a){if(o.active!==b)return r();var c=(a-m)/n,d=f(c),g=k.length;while(g>0)k[--g].call(l,d);if(c>=1)return r(),bB=b,e.end.call(l,h,i),bB=0,1}function r(){return--o.count||delete l.__transition__,1}var k=[],l=this,m=a[j][i].delay,n=a[j][i].duration,o=l.__transition__||(l.__transition__={active:0,count:0});++o.count,m<=g?p(g):d3.timer(p,m,c)}),1},0,c),a}function bx(a,b,c){return c!=""&&bw}function by(a,b){function d(a,d,e){var f=b.call(this,a,d);return f==null?e!=""&&bw:e!=f&&c(e,f)}function e(a,d,e){return e!=b&&c(e,b)}var c=S(a);return typeof b=="function"?d:b==null?bx:(b+="",e)}function bD(a){for(var b=0,c=this.length;b=c.delay&&(c.flush=c.callback(a)),c=c.next;var d=bI()-b;d>24?(isFinite(d)&&(clearTimeout(bG),bG=setTimeout(bH,d)),bF=0):(bF=1,bJ(bH))}function bI(){var a=null,b=bE,c=Infinity;while(b)b.flush?b=a?a.next=b.next:bE=b.next:(c=Math.min(c,b.then+b.delay),b=(a=b).next);return c}function bK(a){var b=[a.a,a.b],c=[a.c,a.d],d=bM(b),e=bL(b,c),f=bM(bN(c,b,-e))||0;b[0]*c[1]2?b_:b$,i=d?U:T;return e=g(a,b,i,c),f=g(b,a,i,d3.interpolate),h}function h(a){return e(a)}var e,f;return h.invert=function(a){return f(a)},h.domain=function(b){return arguments.length?(a=b.map(Number),g()):a},h.range=function(a){return arguments.length?(b=a,g()):b},h.rangeRound=function(a){return h.range(a).interpolate(d3.interpolateRound)},h.clamp=function(a){return arguments.length?(d=a,g()):d},h.interpolate=function(a){return arguments.length?(c=a,g()):c},h.ticks=function(b){return bY(a,b)},h.tickFormat=function(b){return bZ(a,b)},h.nice=function(){return bS(a,bW),g()},h.copy=function(){return bU(a,b,c,d)},g()}function bV(a,b){return d3.rebind(a,b,"range","rangeRound","interpolate","clamp")}function bW(a){return a=Math.pow(10,Math.round(Math.log(a)/Math.LN10)-1),{floor:function(b){return Math.floor(b/a)*a},ceil:function(b){return Math.ceil(b/a)*a}}}function bX(a,b){var c=bQ(a),d=c[1]-c[0],e=Math.pow(10,Math.floor(Math.log(d/b)/Math.LN10)),f=b/d*e;return f<=.15?e*=10:f<=.35?e*=5:f<=.75&&(e*=2),c[0]=Math.ceil(c[0]/e)*e,c[1]=Math.floor(c[1]/e)*e+e*.5,c[2]=e,c}function bY(a,b){return d3.range.apply(d3,bX(a,b))}function bZ(a,b){return d3.format(",."+Math.max(0,-Math.floor(Math.log(bX(a,b)[2])/Math.LN10+.01))+"f")}function b$(a,b,c,d){var e=c(a[0],a[1]),f=d(b[0],b[1]);return function(a){return f(e(a))}}function b_(a,b,c,d){var e=[],f=[],g=0,h=Math.min(a.length,b.length)-1;a[h]0;j--)e.push(c(f)*j)}else{for(;fi;g--);e=e.slice(f,g)}return e},d.tickFormat=function(a,e){arguments.length<2&&(e=cb);if(arguments.length<1)return e;var f=a/d.ticks().length,g=b===cd?(h=-1e-12,Math.floor):(h=1e-12,Math.ceil),h;return function(a){return a/c(g(b(a)+h))0?0:-a)/Math.LN10}function ce(a,b){function e(b){return a(c(b))}var c=cf(b),d=cf(1/b);return e.invert=function(b){return d(a.invert(b))},e.domain=function(b){return arguments.length?(a.domain(b.map(c)),e):a.domain().map(d)},e.ticks=function(a){return bY(e.domain(),a)},e.tickFormat=function(a){return bZ(e.domain(),a)},e.nice=function(){return e.domain(bS(e.domain(),bW))},e.exponent=function(a){if(!arguments.length)return b;var f=e.domain();return c=cf(b=a),d=cf(1/b),e.domain(f)},e.copy=function(){return ce(a.copy(),b)},bV(e,a)}function cf(a){return function(b){return b<0?-Math.pow(-b,a):Math.pow(b,a)}}function cg(a,b){function f(b){return d[((c[b]||(c[b]=a.push(b)))-1)%d.length]}function g(b,c){return d3.range(a.length).map(function(a){return b+c*a})}var c,d,e;return f.domain=function(d){if(!arguments.length)return a;a=[],c={};var e=-1,g=d.length,h;while(++e1){h=b[1],f=a[i],i++,d+="C"+(e[0]+g[0])+","+(e[1]+g[1])+","+(f[0]-h[0])+","+(f[1]-h[1])+","+f[0]+","+f[1];for(var j=2;j9&&(f=c*3/Math.sqrt(f),g[h]=f*d,g[h+1]=f*e));h=-1;while(++h<=i)f=(a[Math.min(i,h+1)][0]-a[Math.max(0,h-1)][0])/(6*(1+g[h]*g[h])),b.push([f||0,g[h]*f||0]);return b}function cS(a){return a.length<3?cy(a):a[0]+cE(a,cR(a))}function cT(a){var b,c=-1,d=a.length,e,f;while(++c1){var d=bQ(a.domain()),e,f=-1,g=b.length,h=(b[1]-b[0])/++c,i,j;while(++f0;)(j=+b[f]-i*h)>=d[0]&&e.push(j);for(--f,i=0;++id&&(c=b,d=e);return c}function eE(a){return a.reduce(eF,0)}function eF(a,b){return a+b[1]}function eG(a,b){return eH(a,Math.ceil(Math.log(b.length)/Math.LN2+1))}function eH(a,b){var c=-1,d=+a[0],e=(a[1]-d)/b,f=[];while(++c<=b)f[c]=e*c+d;return f}function eI(a){return[d3.min(a),d3.max(a)]}function eJ(a,b){return d3.rebind(a,b,"sort","children","value"),a.links=eN,a.nodes=function(b){return eO=!0,(a.nodes=a)(b)},a}function eK(a){return a.children}function eL(a){return a.value}function eM(a,b){return b.value-a.value}function eN(a){return d3.merge(a.map(function(a){return(a.children||[]).map(function(b){return{source:a,target:b}})}))}function eP(a,b){return a.value-b.value}function eQ(a,b){var c=a._pack_next;a._pack_next=b,b._pack_prev=a,b._pack_next=c,c._pack_prev=b}function eR(a,b){a._pack_next=b,b._pack_prev=a}function eS(a,b){var c=b.x-a.x,d=b.y-a.y,e=a.r+b.r;return e*e-c*c-d*d>.001}function eT(a){function l(a){b=Math.min(a.x-a.r,b),c=Math.max(a.x+a.r,c),d=Math.min(a.y-a.r,d),e=Math.max(a.y+a.r,e)}var b=Infinity,c=-Infinity,d=Infinity,e=-Infinity,f=a.length,g,h,i,j,k;a.forEach(eU),g=a[0],g.x=-g.r,g.y=0,l(g);if(f>1){h=a[1],h.x=h.r,h.y=0,l(h);if(f>2){i=a[2],eY(g,h,i),l(i),eQ(g,i),g._pack_prev=i,eQ(i,h),h=g._pack_next;for(var m=3;m0&&(a=d)}return a}function ff(a,b){return a.x-b.x}function fg(a,b){return b.x-a.x}function fh(a,b){return a.depth-b.depth}function fi(a,b){function c(a,d){var e=a.children;if(e&&(i=e.length)){var f,g=null,h=-1,i;while(++h=0)f=d[e]._tree,f.prelim+=b,f.mod+=b,b+=f.shift+(c+=f.change)}function fk(a,b,c){a=a._tree,b=b._tree;var d=c/(b.number-a.number);a.change+=d,b.change-=d,b.shift+=c,b.prelim+=c,b.mod+=c}function fl(a,b,c){return a._tree.ancestor.parent==b.parent?a._tree.ancestor:c}function fm(a){return{x:a.x,y:a.y,dx:a.dx,dy:a.dy}}function fn(a,b){var c=a.x+b[3],d=a.y+b[0],e=a.dx-b[1]-b[3],f=a.dy-b[0]-b[2];return e<0&&(c+=e/2,e=0),f<0&&(d+=f/2,f=0),{x:c,y:d,dx:e,dy:f}}function fo(a){return a.map(fp).join(",")}function fp(a){return/[",\n]/.test(a)?'"'+a.replace(/\"/g,'""')+'"':a}function fr(a,b){return function(c){return c&&c.type in a?a[c.type](c):b}}function fs(a){return"m0,"+a+"a"+a+","+a+" 0 1,1 0,"+ -2*a+"a"+a+","+a+" 0 1,1 0,"+2*a+"z"}function ft(a,b){a.type in fu&&fu[a.type](a,b)}function fv(a,b){ft(a.geometry,b)}function fw(a,b){for(var c=a.features,d=0,e=c.length;d0}function fK(a,b,c){return(c[0]-b[0])*(a[1]-b[1])<(c[1]-b[1])*(a[0]-b[0])}function fL(a,b,c,d){var e=a[0],f=b[0],g=c[0],h=d[0],i=a[1],j=b[1],k=c[1],l=d[1],m=e-g,n=f-e,o=h-g,p=i-k,q=j-i,r=l-k,s=(o*p-r*m)/(r*n-o*q);return[e+s*n,i+s*q]}function fN(a,b){var c={list:a.map(function(a,b){return{index:b,x:a[0],y:a[1]}}).sort(function(a,b){return a.yb.y?1:a.xb.x?1:0}),bottomSite:null},d={list:[],leftEnd:null,rightEnd:null,init:function(){d.leftEnd=d.createHalfEdge(null,"l"),d.rightEnd=d.createHalfEdge(null,"l"),d.leftEnd.r=d.rightEnd,d.rightEnd.l=d.leftEnd,d.list.unshift(d.leftEnd,d.rightEnd)},createHalfEdge:function(a,b){return{edge:a,side:b,vertex:null,l:null,r:null}},insert:function(a,b){b.l=a,b.r=a.r,a.r.l=b,a.r=b},leftBound:function(a){var b=d.leftEnd;do b=b.r;while(b!=d.rightEnd&&e.rightOf(b,a));return b=b.l,b},del:function(a){a.l.r=a.r,a.r.l=a.l,a.edge=null},right:function(a){return a.r},left:function(a){return a.l},leftRegion:function(a){return a.edge==null?c.bottomSite:a.edge.region[a.side]},rightRegion:function(a){return a.edge==null?c.bottomSite:a.edge.region[fM[a.side]]}},e={bisect:function(a,b){var c={region:{l:a,r:b},ep:{l:null,r:null}},d=b.x-a.x,e=b.y-a.y,f=d>0?d:-d,g=e>0?e:-e;return c.c=a.x*d+a.y*e+(d*d+e*e)*.5,f>g?(c.a=1,c.b=e/d,c.c/=d):(c.b=1,c.a=d/e,c.c/=e),c},intersect:function(a,b){var c=a.edge,d=b.edge;if(!c||!d||c.region.r==d.region.r)return null;var e=c.a*d.b-c.b*d.a;if(Math.abs(e)<1e-10)return null;var f=(c.c*d.b-d.c*c.b)/e,g=(d.c*c.a-c.c*d.a)/e,h=c.region.r,i=d.region.r,j,k;h.y=k.region.r.x;return l&&j.side==="l"||!l&&j.side==="r"?null:{x:f,y:g}},rightOf:function(a,b){var c=a.edge,d=c.region.r,e=b.x>d.x;if(e&&a.side==="l")return 1;if(!e&&a.side==="r")return 0;if(c.a===1){var f=b.y-d.y,g=b.x-d.x,h=0,i=0;!e&&c.b<0||e&&c.b>=0?i=h=f>=c.b*g:(i=b.x+b.y*c.b>c.c,c.b<0&&(i=!i),i||(h=1));if(!h){var j=d.x-c.region.l.x;i=c.b*(g*g-f*f)m*m+n*n}return a.side==="l"?i:!i},endPoint:function(a,c,d){a.ep[c]=d;if(!a.ep[fM[c]])return;b(a)},distance:function(a,b){var c=a.x-b.x,d=a.y-b.y;return Math.sqrt(c*c+d*d)}},f={list:[],insert:function(a,b,c){a.vertex=b,a.ystar=b.y+c;for(var d=0,e=f.list,g=e.length;dh.ystar||a.ystar==h.ystar&&b.x>h.vertex.x)continue;break}e.splice(d,0,a)},del:function(a){for(var b=0,c=f.list,d=c.length;bo.y&&(p=n,n=o,o=p,t="r"),s=e.bisect(n,o),m=d.createHalfEdge(s,t),d.insert(k,m),e.endPoint(s,fM[t],r),q=e.intersect(k,m),q&&(f.del(k),f.insert(k,q,e.distance(q,n))),q=e.intersect(m,l),q&&f.insert(m,q,e.distance(q,n));else break}for(i=d.right(d.leftEnd);i!=d.rightEnd;i=d.right(i))b(i.edge)}function fO(){return{leaf:!0,nodes:[],point:null}}function fP(a,b,c,d,e,f){if(!a(b,c,d,e,f)){var g=(c+e)*.5,h=(d+f)*.5,i=b.nodes;i[0]&&fP(a,i[0],c,d,g,h),i[1]&&fP(a,i[1],g,d,e,h),i[2]&&fP(a,i[2],c,h,g,f),i[3]&&fP(a,i[3],g,h,e,f)}}function fQ(a){return{x:a[0],y:a[1]}}function fS(a,b,c,d){var e,f,g=0,h=b.length,i=c.length;while(g=i)return-1;e=b.charCodeAt(g++);if(e==37){f=fY[b.charAt(g++)];if(!f||(d=f(a,c,d))<0)return-1}else if(e!=c.charCodeAt(d++))return-1}return d}function fZ(a,b,c){return b.substring(c,c+=3).toLowerCase()in f$?c:-1}function f_(a,b,c){ga.lastIndex=0;var d=ga.exec(b.substring(c,c+10));return d?c+=d[0].length:-1}function gc(a,b,c){var d=gd[b.substring(c,c+=3).toLowerCase()];return d==null?-1:(a.setMonth(d),c)}function ge(a,b,c){gf.lastIndex=0;var d=gf.exec(b.substring(c,c+12));return d?(a.setMonth(gg[d[0].toLowerCase()]),c+=d[0].length):-1}function gi(a,b,c){return fS(a,fX.c.toString(),b,c)}function gj(a,b,c){return fS(a,fX.x.toString(),b,c)}function gk(a,b,c){return fS(a,fX.X.toString(),b,c)}function gl(a,b,c){gv.lastIndex=0;var d=gv.exec(b.substring(c,c+4));return d?(a.setFullYear(d[0]),c+=d[0].length):-1}function gm(a,b,c){gv.lastIndex=0;var d=gv.exec(b.substring(c,c+2));return d?(a.setFullYear(gn()+ +d[0]),c+=d[0].length):-1}function gn(){return~~((new Date).getFullYear()/1e3)*1e3}function go(a,b,c){gv.lastIndex=0;var d=gv.exec(b.substring(c,c+2));return d?(a.setMonth(d[0]-1),c+=d[0].length):-1}function gp(a,b,c){gv.lastIndex=0;var d= -gv.exec(b.substring(c,c+2));return d?(a.setDate(+d[0]),c+=d[0].length):-1}function gq(a,b,c){gv.lastIndex=0;var d=gv.exec(b.substring(c,c+2));return d?(a.setHours(+d[0]),c+=d[0].length):-1}function gr(a,b,c){return a.hour12=!0,gq(a,b,c)}function gs(a,b,c){gv.lastIndex=0;var d=gv.exec(b.substring(c,c+2));return d?(a.setMinutes(+d[0]),c+=d[0].length):-1}function gt(a,b,c){gv.lastIndex=0;var d=gv.exec(b.substring(c,c+2));return d?(a.setSeconds(+d[0]),c+=d[0].length):-1}function gu(a,b,c){gv.lastIndex=0;var d=gv.exec(b.substring(c,c+3));return d?(a.setMilliseconds(+d[0]),c+=d[0].length):-1}function gw(a,b,c){var d=gx[b.substring(c,c+=2).toLowerCase()];return d==null?-1:(a.hour12pm=d,c)}function gy(a){return new fR(a.getFullYear(),0,1)}function gz(a,b){return~~((b-a)/864e5-(b.getTimezoneOffset()-a.getTimezoneOffset())/1440)}function gA(a){return fU(1+gz(gy(a),a))}function gB(a){var b=gy(a);return fT(~~((gz(b,a)+b.getDay())/7))}function gC(a){var b=gy(a);return fT(~~((gz(b,a)+(b.getDay()+6)%7)/7))}function gD(a){var b=a.getTimezoneOffset(),c=b>0?"-":"+",d=~~(Math.abs(b)/60),e=Math.abs(b)%60;return c+fT(d)+fT(e)}function gE(){this._=new Date(Date.UTC.apply(this,arguments))}function gG(a){return a.toISOString()}function gH(a,b,c){return function(d,e,f){var g=a(d),h=[];g1)while(gb?1:a>=b?0:NaN},d3.descending=function(a,b){return ba?1:b>=a?0:NaN},d3.mean=function(a,b){var c=a.length,d,e=0,f=-1,g=0;if(arguments.length===1)while(++f1&&(a=a.map(b)),a=a.filter(l),a.length?d3.quantile(a.sort(d3.ascending),.5):undefined},d3.min=function(a,b){var c=-1,d=a.length,e,f;if(arguments.length===1){while(++cf&&(e=f)}else{while(++cf&&(e=f)}return e},d3.max=function(a,b){var c=-1,d=a.length,e,f;if(arguments.length===1){while(++ce&&(e=f)}else{while(++ce&&(e=f)}return e},d3.extent=function(a,b){var c=-1,d=a.length,e,f,g;if(arguments.length===1){while(++cf&&(e=f),gf&&(e=f),g1);return a+b*c*Math.sqrt(-2*Math.log(e)/e)}}},d3.sum=function(a,b){var c=0,d=a.length,e,f=-1;if(arguments.length===1)while(++f>1;a[e]>1;b0&&(e=f);return e},d3.last=function(a,b){var c=0,d=a.length,e=a[0],f;arguments.length===1&&(b=d3.ascending);while(++c=b.length)return e?e.call(a,c):d?c.sort(d):c;var h=-1,i=c.length,j=b[g++],k,l,m={};while(++h=b.length)return a;var e=[],f=c[d++],h;for(h in a)e.push({key:h,values:g(a[h],d)});return f&&e.sort(function(a,b){return f(a.key,b.key)}),e}var a={},b=[],c=[],d,e;return a.map=function(a){return f(a,0)},a.entries=function(a){return g(f(a,0),0)},a.key=function(c){return b.push(c),a},a.sortKeys=function(d){return c[b.length-1]=d,a},a.sortValues=function(b){return d=b,a},a.rollup=function(b){return e=b,a},a},d3.keys=function(a){var b=[];for(var c in a)b.push(c);return b},d3.values=function(a){var b=[];for(var c in a)b.push(a[c]);return b},d3.entries=function(a){var b=[];for(var c in a)b.push({key:c,value:a[c]});return b},d3.permute=function(a,b){var c=[],d=-1,e=b.length;while(++db)d.push(g/e);else while((g=a+c*++f)0&&(d=a.substring(c+1),a=a.substring(0,c)),arguments.length<2?this[a].on(d):this[a].on(d,b)},d3.format=function(a){var b=u.exec(a),c=b[1]||" ",d=b[3]||"",e=b[5],f=+b[6],g=b[7],h=b[8],i=b[9],j=1,k="",l=!1;h&&(h=+h.substring(1)),e&&(c="0",g&&(f-=Math.floor((f-1)/4)));switch(i){case"n":g=!0,i="g";break;case"%":j=100,k="%",i="f";break;case"p":j=100,k="%",i="r";break;case"d":l=!0,h=0;break;case"s":j=-1,i="r"}return i=="r"&&!h&&(i="g"),i=v[i]||x,function(a){if(l&&a%1)return"";var b=a<0&&(a=-a)?"−":d;if(j<0){var m=d3.formatPrefix(a,h);a*=m.scale,k=m.symbol}else a*=j;a=i(a,h);if(e){var n=a.length+b.length;n=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/,v={g:function(a,b){return a.toPrecision(b)},e:function(a,b){return a.toExponential(b)},f:function(a,b){return a.toFixed(b)},r:function(a,b){return d3.round(a,b=w(a,b)).toFixed(Math.max(0,Math.min(20,b)))}},z=["y","z","a","f","p","n","μ","m","","k","M","G","T","P","E","Z","Y"].map(A);d3.formatPrefix=function(a,b){var c=0;return a&&(a<0&&(a*=-1),b&&(a=d3.round(a,w(a,b))),c=1+Math.floor(1e-12+Math.log(a)/Math.LN10),c=Math.max(-24,Math.min(24,Math.floor((c<=0?c+1:c-1)/3)*3))),z[8+c/3]};var B=J(2),C=J(3),D={linear:function(){return I},poly:J,quad:function(){return B},cubic:function(){return C},sin:function(){return K},exp:function(){return L},circle:function(){return M},elastic:N,back:O,bounce:function(){return P}},E={"in":function(a){return a},out:G,"in-out":H,"out-in":function(a){return H(G(a))}};d3.ease=function(a){var b=a.indexOf("-"),c=b>=0?a.substring(0,b):a,d=b>=0?a.substring(b+1):"in";return F(E[d](D[c].apply(null,Array.prototype.slice.call(arguments,1))))},d3.event=null,d3.interpolate=function(a,b){var c=d3.interpolators.length,d;while(--c>=0&&!(d=d3.interpolators[c](a,b)));return d},d3.interpolateNumber=function(a,b){return b-=a,function(c){return a+b*c}},d3.interpolateRound=function(a,b){return b-=a,function(c){return Math.round(a+b*c)}},d3.interpolateString=function(a,b){var c,d,e,f=0,g=0,h=[],i=[],j,k;R.lastIndex=0;for(d=0;c=R.exec(b);++d)c.index&&h.push(b.substring(f,g=c.index)),i.push({i:h.length,x:c[0]}),h.push(null),f=R.lastIndex;f1){while(++e=0;)if(f=c[d])e&&e!==f.nextSibling&&e.parentNode.insertBefore(f,e),e=f;return this},bk.sort=function(a){a=br.apply(this,arguments);for(var b=-1,c=this.length;++b0&&(a=a.substring(0,e)),arguments.length<2?(e=this.node()[d])&&e._:this.each(function(e,f){function h(a){var c=d3.event;d3.event=a;try{b.call(g,g.__data__,f)}finally{d3.event=c}}var g=this;g[d]&&g.removeEventListener(a,g[d],c),b&&g.addEventListener(a,g[d]=h,c),h._=b})},bk.each=function(a){for(var b=-1,c=this.length;++b=co?e?"M0,"+f+"A"+f+","+f+" 0 1,1 0,"+ -f+"A"+f+","+f+" 0 1,1 0,"+f+"M0,"+e+"A"+e+","+e+" 0 1,0 0,"+ -e+"A"+e+","+e+" 0 1,0 0,"+e+"Z":"M0,"+f+"A"+f+","+f+" 0 1,1 0,"+ -f+"A"+f+","+f+" 0 1,1 0,"+f+"Z":e?"M"+f*k+","+f*l+"A"+f+","+f+" 0 "+j+",1 "+f*m+","+f*n+"L"+e*m+","+e*n+"A"+e+","+e+" 0 "+j+",0 "+e*k+","+e*l+"Z":"M"+f*k+","+f*l+"A"+f+","+f+" 0 "+j+",1 "+f*m+","+f*n+"L0,0"+"Z"}var a=cp,b=cq,c=cr,d=cs;return e.innerRadius=function(b){return arguments.length?(a=d3.functor(b),e):a},e.outerRadius=function(a){return arguments.length?(b=d3.functor(a),e):b},e.startAngle=function(a){return arguments.length?(c=d3.functor(a),e):c},e.endAngle=function(a){return arguments.length?(d=d3.functor(a),e):d},e.centroid=function(){var e=(a.apply(this,arguments)+b.apply(this,arguments))/2,f=(c.apply(this,arguments)+d.apply(this,arguments))/2+cn;return[Math.cos(f)*e,Math.sin(f)*e]},e};var cn=-Math.PI/2,co=2*Math.PI-1e-6;d3.svg.line=function(){return ct(Object)};var cx={linear:cy,"step-before":cz,"step-after":cA,basis:cG,"basis-open":cH,"basis-closed":cI,bundle:cJ,cardinal:cD,"cardinal-open":cB,"cardinal-closed":cC,monotone:cS},cL=[0,2/3,1/3,0],cM=[0,1/3,2/3,0],cN=[0,1/6,2/3,1/6];d3.svg.line.radial=function(){var a=ct(cT);return a.radius=a.x,delete a.x,a.angle=a.y,delete a.y,a},cz.reverse=cA,cA.reverse=cz,d3.svg.area=function(){return cU(Object)},d3.svg.area.radial=function(){var a=cU(cT);return a.radius=a.x,delete a.x,a.innerRadius=a.x0,delete a.x0,a.outerRadius=a.x1,delete a.x1,a.angle=a.y,delete a.y,a.startAngle=a.y0,delete a.y0,a.endAngle=a.y1,delete a.y1,a},d3.svg.chord=function(){function f(c,d){var e=g(this,a,c,d),f=g(this,b,c,d);return"M"+e.p0+i(e.r,e.p1,e.a1-e.a0)+(h(e,f)?j(e.r,e.p1,e.r,e.p0):j(e.r,e.p1,f.r,f.p0)+i(f.r,f.p1,f.a1-f.a0)+j(f.r,f.p1,e.r,e.p0))+"Z"}function g(a,b,f,g){var h=b.call(a,f,g),i=c.call(a,h,g),j=d.call(a,h,g)+cn,k=e.call(a,h,g)+cn;return{r:i,a0:j,a1:k,p0:[i*Math.cos(j),i*Math.sin(j)],p1:[i*Math.cos(k),i*Math.sin(k)]}}function h(a,b){return a.a0==b.a0&&a.a1==b.a1}function i(a,b,c){return"A"+a+","+a+" 0 "+ +(c>Math.PI)+",1 "+b}function j(a,b,c,d){return"Q 0,0 "+d}var a=cX,b=cY,c=cZ,d=cr,e=cs;return f.radius=function(a){return arguments.length?(c=d3.functor(a),f):c},f.source=function(b){return arguments.length?(a=d3.functor(b),f):a},f.target=function(a){return arguments.length?(b=d3.functor(a),f):b},f.startAngle=function(a){return arguments.length?(d=d3.functor(a),f):d},f.endAngle=function(a){return arguments.length?(e=d3.functor(a),f):e},f},d3.svg.diagonal=function(){function d(d,e){var f=a.call(this,d,e),g=b.call(this,d,e),h=(f.y+g.y)/2,i=[f,{x:f.x,y:h},{x:g.x,y:h},g];return i=i.map(c),"M"+i[0]+"C"+i[1]+" "+i[2]+" "+i[3]}var a=cX,b=cY,c=da;return d.source=function(b){return arguments.length?(a=d3.functor(b),d):a},d.target=function(a){return arguments.length?(b=d3.functor(a),d):b},d.projection=function(a){return arguments.length?(c=a,d):c},d},d3.svg.diagonal.radial=function(){var a=d3.svg.diagonal(),b=da,c=a.projection;return a.projection=function(a){return arguments.length?c(db(b=a)):b},a},d3.svg.mouse=function(a){return dd(a,d3.event)};var dc=/WebKit/.test(navigator -.userAgent)?-1:0;d3.svg.touches=function(a,b){return arguments.length<2&&(b=d3.event.touches),b?e(b).map(function(b){var c=dd(a,b);return c.identifier=b.identifier,c}):[]},d3.svg.symbol=function(){function c(c,d){return(dg[a.call(this,c,d)]||dg.circle)(b.call(this,c,d))}var a=df,b=de;return c.type=function(b){return arguments.length?(a=d3.functor(b),c):a},c.size=function(a){return arguments.length?(b=d3.functor(a),c):b},c};var dg={circle:function(a){var b=Math.sqrt(a/Math.PI);return"M0,"+b+"A"+b+","+b+" 0 1,1 0,"+ -b+"A"+b+","+b+" 0 1,1 0,"+b+"Z"},cross:function(a){var b=Math.sqrt(a/5)/2;return"M"+ -3*b+","+ -b+"H"+ -b+"V"+ -3*b+"H"+b+"V"+ -b+"H"+3*b+"V"+b+"H"+b+"V"+3*b+"H"+ -b+"V"+b+"H"+ -3*b+"Z"},diamond:function(a){var b=Math.sqrt(a/(2*di)),c=b*di;return"M0,"+ -b+"L"+c+",0"+" 0,"+b+" "+ -c+",0"+"Z"},square:function(a){var b=Math.sqrt(a)/2;return"M"+ -b+","+ -b+"L"+b+","+ -b+" "+b+","+b+" "+ -b+","+b+"Z"},"triangle-down":function(a){var b=Math.sqrt(a/dh),c=b*dh/2;return"M0,"+c+"L"+b+","+ -c+" "+ -b+","+ -c+"Z"},"triangle-up":function(a){var b=Math.sqrt(a/dh),c=b*dh/2;return"M0,"+ -c+"L"+b+","+c+" "+ -b+","+c+"Z"}};d3.svg.symbolTypes=d3.keys(dg);var dh=Math.sqrt(3),di=Math.tan(30*Math.PI/180);d3.svg.axis=function(){function j(j){j.each(function(k,l,m){var n=d3.select(this),o=j.delay?function(a){var b=bB;try{return bB=j.id,a.transition().delay(j[m][l].delay).duration(j[m][l].duration).ease(j.ease())}finally{bB=b}}:Object,p=a.ticks?a.ticks.apply(a,g):a.domain(),q=h==null?a.tickFormat?a.tickFormat.apply(a,g):String:h,r=dl(a,p,i),s=n.selectAll(".minor").data(r,String),t=s.enter().insert("line","g").attr("class","tick minor").style("opacity",1e-6),u=o(s.exit()).style("opacity",1e-6).remove(),v=o(s).style("opacity",1),w=n.selectAll("g").data(p,String),x=w.enter().insert("g","path").style("opacity",1e-6),y=o(w.exit()).style("opacity",1e-6).remove(),z=o(w).style("opacity",1),A,B=bR(a),C=n.selectAll(".domain").data([0]),D=C.enter().append("path").attr("class","domain"),E=o(C),F=a.copy(),G=this.__chart__||F;this.__chart__=F,x.append("line").attr("class","tick"),x.append("text"),z.select("text").text(q);switch(b){case"bottom":A=dj,t.attr("y2",d),v.attr("x2",0).attr("y2",d),x.select("line").attr("y2",c),x.select("text").attr("y",Math.max(c,0)+f),z.select("line").attr("x2",0).attr("y2",c),z.select("text").attr("x",0).attr("y",Math.max(c,0)+f).attr("dy",".71em").attr("text-anchor","middle"),E.attr("d","M"+B[0]+","+e+"V0H"+B[1]+"V"+e);break;case"top":A=dj,t.attr("y2",-d),v.attr("x2",0).attr("y2",-d),x.select("line").attr("y2",-c),x.select("text").attr("y",-(Math.max(c,0)+f)),z.select("line").attr("x2",0).attr("y2",-c),z.select("text").attr("x",0).attr("y",-(Math.max(c,0)+f)).attr("dy","0em").attr("text-anchor","middle"),E.attr("d","M"+B[0]+","+ -e+"V0H"+B[1]+"V"+ -e);break;case"left":A=dk,t.attr("x2",-d),v.attr("x2",-d).attr("y2",0),x.select("line").attr("x2",-c),x.select("text").attr("x",-(Math.max(c,0)+f)),z.select("line").attr("x2",-c).attr("y2",0),z.select("text").attr("x",-(Math.max(c,0)+f)).attr("y",0).attr("dy",".32em").attr("text-anchor","end"),E.attr("d","M"+ -e+","+B[0]+"H0V"+B[1]+"H"+ -e);break;case"right":A=dk,t.attr("x2",d),v.attr("x2",d).attr("y2",0),x.select("line").attr("x2",c),x.select("text").attr("x",Math.max(c,0)+f),z.select("line").attr("x2",c).attr("y2",0),z.select("text").attr("x",Math.max(c,0)+f).attr("y",0).attr("dy",".32em").attr("text-anchor","start"),E.attr("d","M"+e+","+B[0]+"H0V"+B[1]+"H"+e)}if(a.ticks)x.call(A,G),z.call(A,F),y.call(A,F),t.call(A,G),v.call(A,F),u.call(A,F);else{var H=F.rangeBand()/2,I=function(a){return F(a)+H};x.call(A,I),z.call(A,I)}})}var a=d3.scale.linear(),b="bottom",c=6,d=6,e=6,f=3,g=[10],h,i=0;return j.scale=function(b){return arguments.length?(a=b,j):a},j.orient=function(a){return arguments.length?(b=a,j):b},j.ticks=function(){return arguments.length?(g=arguments,j):g},j.tickFormat=function(a){return arguments.length?(h=a,j):h},j.tickSize=function(a,b,f){if(!arguments.length)return c;var g=arguments.length-1;return c=+a,d=g>1?+b:c,e=g>0?+arguments[g]:c,j},j.tickPadding=function(a){return arguments.length?(f=+a,j):f},j.tickSubdivide=function(a){return arguments.length?(i=+a,j):i},j},d3.svg.brush=function(){function e(a){var g=b&&c?["n","e","s","w","nw","ne","se","sw"]:b?["e","w"]:c?["n","s"]:[];a.each(function(){var a=d3.select(this).on("mousedown.brush",f),h=a.selectAll(".background").data([0]),i=a.selectAll(".extent").data([0]),j=a.selectAll(".resize").data(g,String),k;h.enter().append("rect").attr("class","background").style("visibility","hidden").style("pointer-events","all").style("cursor","crosshair"),i.enter().append("rect").attr("class","extent").style("cursor","move"),j.enter().append("rect").attr("class",function(a){return"resize "+a}).attr("width",6).attr("height",6).style("visibility","hidden").style("cursor",function(a){return dE[a]}),j.style("pointer-events",e.empty()?"none":"all"),j.exit().remove(),b&&(k=bR(b),h.attr("x",k[0]).attr("width",k[1]-k[0]),dx(a,d)),c&&(k=bR(c),h.attr("y",k[0]).attr("height",k[1]-k[0]),dy(a,d))})}function f(){var a=d3.select(d3.event.target);dm=e,dp=this,ds=d,dw=d3.svg.mouse(dp),(dt=a.classed("extent"))?(dw[0]=d[0][0]-dw[0],dw[1]=d[0][1]-dw[1]):a.classed("resize")?(du=d3.event.target.__data__,dw[0]=d[+/w$/.test(du)][0],dw[1]=d[+/^n/.test(du)][1]):d3.event.altKey&&(dv=dw.slice()),dq=!/^(n|s)$/.test(du)&&b,dr=!/^(e|w)$/.test(du)&&c,dn=g(this,arguments),dn("brushstart"),dB(),Q()}function g(b,c){return function(d){var f=d3.event;try{d3.event={type:d,target:e},a[d].apply(b,c)}finally{d3.event=f}}}var a=d3.dispatch("brushstart","brush","brushend"),b,c,d=[[0,0],[0,0]];return e.x=function(a){return arguments.length?(b=a,e):b},e.y=function(a){return arguments.length?(c=a,e):c},e.extent=function(a){var f,g,h,i,j;return arguments.length?(b&&(f=a[0],g=a[1],c&&(f=f[0],g=g[0]),b.invert&&(f=b(f),g=b(g)),ge&&(e=h),d.push(h)}for(g=0;g=i[0]&&o<=i[1]&&(k=g[d3.bisect(j,o,1,m)-1],k.y+=n,k.push(e[f]));return g}var a=!0,b=Number,c=eI,d=eG;return e.value=function(a){return arguments.length?(b=a,e):b},e.range=function(a){return arguments.length?(c=d3.functor(a),e):c},e.bins=function(a){return arguments.length?(d=typeof a=="number"?function(b){return eH(b,a)}:d3.functor(a),e):d},e.frequency=function(b){return arguments.length?(a=!!b,e):a},e},d3.layout.hierarchy=function(){function e(f,h,i){var j=b.call(g,f,h),k=eO?f:{data:f};k.depth=h,i.push(k);if(j&&(m=j.length)){var l=-1,m,n=k.children=[],o=0,p=h+1;while(++l0&&(fk(fl(g,a,d),a,m),i+=m,j+=m),k+=g._tree.mod,i+=e._tree.mod,l+=h._tree.mod,j+=f._tree.mod;g&&!fd(f)&&(f._tree.thread=g,f._tree.mod+=k-j),e&&!fc(h)&&(h._tree.thread=e,h._tree.mod+=i-l,d=a)}return d}var f=a.call(this,d,e),g=f[0];fi(g,function(a,b){a._tree={ancestor:a,prelim:0,mod:0,change:0,shift:0,number:b?b._tree.number+1:0}}),h(g),i(g,-g._tree.prelim);var k=fe(g,fg),l=fe(g,ff),m=fe(g,fh),n=k.x-b(k,l)/2,o=l.x+b(l,k)/2,p=m.depth||1;return fi(g,function(a){a.x=(a.x-n)/(o-n)*c[0],a.y=a.depth/p*c[1],delete a._tree}),f}var a=d3.layout.hierarchy().sort(null).value(null),b=fb,c=[1,1];return d.separation=function(a){return arguments.length?(b=a,d):b},d.size=function(a){return arguments.length?(c=a,d):c},eJ(d,a)},d3.layout.treemap=function(){function i(a,b){var c=-1,d=a.length,e,f;while(++c0)d.push(g=f[o-1]),d.area+=g.area,(k=l(d,n))<=h?(f.pop(),h=k):(d.area-=d.pop().area,m(d,n,c,!1),n=Math.min(c.dx,c.dy),d.length=d.area=0,h=Infinity);d.length&&(m(d,n,c,!0),d.length=d.area=0),b.forEach(j)}}function k(a){var b=a.children;if(b&&b.length){var c=e(a),d=b.slice(),f,g=[];i(d,c.dx*c.dy/a.value),g.area=0;while(f=d.pop())g.push(f),g.area+=f.area,f.z!=null&&(m(g,f.z?c.dx:c.dy,c,!d.length),g.length=g.area=0);b.forEach(k)}}function l(a,b){var c=a.area,d,e=0,f=Infinity,g=-1,i=a.length;while(++ge&&(e=d)}return c*=c,b*=b,c?Math.max(b*e*h/c,c/(b*f*h)):Infinity}function m(a,c,d,e){var f=-1,g=a.length,h=d.x,i=d.y,j=c?b(a.area/c):0,k;if(c==d.dx){if(e||j>d.dy)j=d.dy;while(++fd.dx)j=d.dx;while(++f=a.length)return d;if(i)return i=!1,c;var b=f.lastIndex;if(a.charCodeAt(b)===34){var e=b;while(e++50?b:f<-140?c:g<21?d:a)(e)}var a=d3.geo.albers(),b=d3.geo.albers().origin([-160,60]).parallels([55,65]),c=d3.geo.albers().origin([-160,20]).parallels([8,18]),d=d3.geo.albers().origin([-60,10]).parallels([8,18]);return e.scale=function(f){return arguments.length?(a.scale(f),b.scale(f*.6),c.scale(f),d.scale(f*1.5),e.translate(a.translate())):a.scale()},e.translate=function(f){if(!arguments.length)return a.translate();var g=a.scale()/1e3,h=f[0],i=f[1];return a.translate(f),b.translate([h-400*g,i+170*g]),c.translate([h-190*g,i+200*g]),d.translate([h+580*g,i+430*g]),e},e.scale(a.scale())},d3.geo.bonne=function(){function g(g){var h=g[0]*fq-c,i=g[1]*fq-d;if(e){var j=f+e-i,k=h*Math.cos(i)/j;h=j*Math.sin(k),i=j*Math.cos(k)-f}else h*=Math.cos(i),i*=-1;return[a*h+b[0],a*i+b[1]]}var a=200,b=[480,250],c,d,e,f;return g.invert=function(d){var g=(d[0]-b[0])/a,h=(d[1]-b[1])/a;if(e){var i=f+h,j=Math.sqrt(g*g+i*i);h=f+e-j,g=c+j*Math.atan2(g,i)/Math.cos(h)}else h*=-1,g/=Math.cos(h);return[g/fq,h/fq]},g.parallel=function(a){return arguments.length?(f=1/Math.tan(e=a*fq),g):e/fq},g.origin=function(a){return arguments.length?(c=a[0]*fq,d=a[1]*fq,g):[c/fq,d/fq]},g.scale=function(b){return arguments.length?(a=+b,g):a},g.translate=function(a){return arguments.length?(b=[+a[0],+a[1]],g):b},g.origin([0,0]).parallel(45)},d3.geo.equirectangular=function(){function c(c){var d=c[0]/360,e=-c[1]/360;return[a*d+b[0],a*e+b[1]]}var a=500,b=[480,250];return c.invert=function(c){var d=(c[0]-b[0])/a,e=(c[1]-b[1])/a;return[360*d,-360*e]},c.scale=function(b){return arguments.length?(a=+b,c):a},c.translate=function(a){return arguments.length?(b=[+a[0],+a[1]],c):b},c},d3.geo.mercator=function(){function c(c){var d=c[0]/360,e=-(Math.log(Math.tan(Math.PI/4+c[1]*fq/2))/fq)/360;return[a*d+b[0],a*Math.max(-0.5,Math.min(.5,e))+b[1]]}var a=500,b=[480,250];return c.invert=function(c){var d=(c[0]-b[0])/a,e=(c[1]-b[1])/a;return[360*d,2*Math.atan(Math.exp(-360*e*fq))/fq-90]},c.scale=function(b){return arguments.length?(a=+b,c):a},c.translate=function(a){return arguments.length?(b=[+a[0],+a[1]],c):b},c},d3.geo.path=function(){function d(c,d){return typeof a=="function"&&(b=fs(a.apply(this,arguments))),f(c)||null}function e(a){return c(a).join(",")}function h(a){var b=k(a[0]),c=0,d=a.length;while(++c0){b.push("M");while(++h0){b.push("M");while(++kd&&(d=a),fe&&(e=f)}),[[b,c],[d,e]]};var fu={Feature:fv,FeatureCollection:fw,GeometryCollection:fx,LineString:fy,MultiLineString:fz,MultiPoint:fy,MultiPolygon:fA,Point:fB,Polygon:fC};d3.geo.circle=function(){function e(){}function f(a){return d.distance(a)=k*k+l*l?d[f].index=-1:(d[m].index=-1,o=d[f].angle,m=f,n=g)):(o=d[f].angle,m=f,n=g);e.push(h);for(f=0,g=0;f<2;++g)d[g].index!==-1&&(e.push(d[g].index),f++);p=e.length;for(;g=0?(c=a.ep.r,d=a.ep.l):(c=a.ep.l,d=a.ep.r),a.a===1?(g=c?c.y:-1e6,e=a.c-a.b*g,h=d?d.y:1e6,f=a.c-a.b*h):(e=c?c.x:-1e6,g=a.c-a.a*e,f=d?d.x:1e6,h=a.c-a.a*f);var i=[e,g],j=[f,h];b[a.region.l.index].push(i,j),b[a.region.r.index].push(i,j)}),b.map(function(b,c){var d=a[c][0],e=a[c][1];return b.forEach(function(a){a.angle=Math.atan2(a[0]-d,a[1]-e)}),b.sort(function(a,b){return a.angle-b.angle}).filter(function(a,c){return!c||a.angle-b[c-1].angle>1e-10})})};var fM={l:"r",r:"l"};d3.geom.delaunay=function(a){var b=a.map(function(){return[]}),c=[];return fN(a,function(c){b[c.region.l.index].push(a[c.region.r.index])}),b.forEach(function(b,d){var e=a[d],f=e[0],g=e[1];b.forEach(function(a){a.angle=Math.atan2(a[0]-f,a[1]-g)}),b.sort(function(a,b){return a.angle-b.angle});for(var h=0,i=b.length-1;h=g,j=b.y>=h,l=(j<<1)+i;a.leaf=!1,a=a.nodes[l]||(a.nodes[l]=fO()),i?c=g:e=g,j?d=h:f=h,k(a,b,c,d,e,f)}var f,g=-1,h=a.length;h&&isNaN(a[0].x)&&(a=a.map(fQ));if(arguments.length<5)if(arguments.length===3)e=d=c,c=b;else{b=c=Infinity,d=e=-Infinity;while(++gd&&(d=f.x),f.y>e&&(e=f.y);var i=d-b,j=e-c;i>j?e=c+i:d=b+j}var m=fO();return m.add=function(a){k(m,a,b,c,d,e)},m.visit=function(a){fP(a,m,b,c,d,e)},a.forEach(m.add),m},d3.time={};var fR=Date;d3.time.format=function(a){function c(c){var d=[],e=-1,f=0,g,h;while(++e=12?"PM":"AM"},S:function(a){return fT(a.getSeconds())},U:gB,w:function(a){return a.getDay()},W:gC,x:d3.time.format("%m/%d/%y"),X:d3.time.format("%H:%M:%S"),y:function(a){return fT(a.getFullYear()%100)},Y:function(a){return fV(a.getFullYear()%1e4)},Z:gD,"%":function(a){return"%"}},fY={a:fZ,A:f_,b:gc,B:ge,c:gi,d:gp,e:gp,H:gq,I:gr,L:gu,m:go,M:gs,p:gw,S:gt,x:gj,X:gk,y:gm,Y:gl},f$={sun:3,mon:3,tue:3,wed:3,thu:3,fri:3,sat:3},ga=/^(?:Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday)/ig,gb=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],gd={jan:0,feb:1,mar:2,apr:3,may:4,jun:5,jul:6,aug:7,sep:8,oct:9,nov:10,dec:11},gf=/^(?:January|February|March|April|May|June|July|August|September|October|November|December)/ig,gg={january:0,february:1,march:2,april:3,may:4,june:5,july:6,august:7,september:8,october:9,november:10,december:11},gh=["January","February","March","April","May","June","July","August","September","October","November","December"],gv=/\s*\d+/,gx={am:0,pm:1};d3.time.format.utc=function(a){function c(a){try{fR=gE;var c=new fR;return c._=a,b(c)}finally{fR=Date}}var b=d3.time.format(a);return c.parse=function(a){try{fR=gE;var c=b.parse(a);return c&&c._}finally{fR=Date}},c.toString=b.toString,c},gE.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.getTime()},setDate:function(a){this._.setUTCDate(a)},setDay:function(a){this._.setUTCDay(a)},setFullYear:function(a){this._.setUTCFullYear(a)},setHours:function(a){this._.setUTCHours(a)},setMilliseconds:function(a){this._.setUTCMilliseconds(a)},setMinutes:function(a){this._.setUTCMinutes(a)},setMonth:function(a){this._.setUTCMonth(a)},setSeconds:function(a){this._.setUTCSeconds(a)}};var gF=d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ");d3.time.format.iso=Date.prototype.toISOString?gG:gF,gG.parse=function(a){return new Date(a)},gG.toString=gF.toString,d3.time.second=function(a){return new Date(~~(a/1e3)*1e3)},d3.time.second.utc=d3.time.second,d3.time.seconds=gH(d3.time.second,function(a){a.setTime(a.getTime()+1e3)},function(a){return a.getSeconds()}),d3.time.seconds.utc=d3.time.seconds,d3.time.minute=function(a){return new Date(~~(a/6e4)*6e4)},d3.time.minute.utc=d3.time.minute,d3.time.minutes=gH(d3.time.minute,gI,function(a){return a.getMinutes()}),d3.time.minutes.utc=gH(d3.time.minute,gI,function(a){return a.getUTCMinutes()}),d3.time.hour=function(a){var b=a.getTimezoneOffset()/60;return new Date((~~(a/36e5-b)+b)*36e5)},d3.time.hour.utc=function(a){return new Date(~~(a/36e5)*36e5)},d3.time.hours=gH(d3.time.hour,gJ,function(a){return a.getHours()}),d3.time.hours.utc=gH(d3.time.hour.utc,gJ,function(a){return a.getUTCHours()}),d3.time.day=function(a){return new Date(a.getFullYear(),a.getMonth(),a.getDate())},d3.time.day.utc=function(a){return new Date(~~(a/864e5)*864e5)},d3.time.days=gH(d3.time.day,function(a){a.setDate(a.getDate()+1)},function(a){return a.getDate()-1}),d3.time.days.utc=gH(d3.time.day.utc,function(a){a.setUTCDate(a.getUTCDate()+1)},function(a){return a.getUTCDate()-1}),d3.time.week=function(a){return(a=d3.time.day(a)).setDate(a.getDate()-a.getDay()),a},d3.time.week.utc=function(a){return(a=d3.time.day.utc(a)).setUTCDate(a.getUTCDate()-a.getUTCDay()),a},d3.time.weeks=gH(d3.time.week,function(a){a.setDate(a.getDate()+7)},function(a){return~~((a-new Date(a.getFullYear(),0,1))/6048e5)}),d3.time.weeks.utc=gH(d3.time.week.utc,function(a){a.setUTCDate(a.getUTCDate()+7)},function(a){return~~((a-Date.UTC(a.getUTCFullYear(),0,1))/6048e5)}),d3.time.month=function(a){return new Date(a.getFullYear(),a.getMonth(),1)},d3.time.month.utc=function(a){return new Date(Date.UTC(a.getUTCFullYear(),a.getUTCMonth(),1))},d3.time.months=gH(d3.time.month,function(a){a.setMonth(a.getMonth()+1)},function(a){return a.getMonth()}),d3.time.months.utc=gH(d3.time.month.utc,function(a){a.setUTCMonth(a.getUTCMonth()+1)},function(a){return a.getUTCMonth()}),d3.time.year=function(a){return new Date(a.getFullYear(),0,1)},d3.time.year.utc=function(a){return new Date(Date.UTC(a.getUTCFullYear(),0,1))},d3.time.years=gH(d3.time.year,function(a){a.setFullYear(a.getFullYear()+1)},function(a){return a.getFullYear()}),d3.time.years.utc=gH(d3.time.year.utc,function(a){a.setUTCFullYear(a.getUTCFullYear()+1)},function(a){return a.getUTCFullYear()});var gQ=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],gR=[[d3.time.seconds,1],[d3.time.seconds,5],[d3.time.seconds,15],[d3.time.seconds,30],[d3.time.minutes,1],[d3.time.minutes,5],[d3.time.minutes,15],[d3.time.minutes,30],[d3.time.hours,1],[d3.time.hours,3],[d3.time.hours,6],[d3.time.hours,12],[d3.time.days,1],[d3.time.days,2],[d3.time.weeks,1],[d3.time.months,1],[d3.time.months,3],[d3.time.years,1]],gS=[[d3.time.format("%Y"),function(a){return!0}],[d3.time.format("%B"),function(a){return a.getMonth()}],[d3.time.format("%b %d"),function(a){return a.getDate()!=1}],[d3.time.format("%a %d"),function(a){return a.getDay()&&a.getDate()!=1}],[d3.time.format("%I %p"),function(a){return a.getHours()}],[d3.time.format("%I:%M"),function(a){return a.getMinutes()}],[d3.time.format(":%S"),function(a){return a.getSeconds()}],[d3.time.format(".%L"),function(a){return a.getMilliseconds()}]],gT=d3.scale.linear(),gU=gN(gS);gR.year=function(a,b){return gT.domain(a.map(gP)).ticks(b).map(gO)},d3.time.scale=function(){return gK(d3.scale.linear(),gR,gU)};var gV=[[d3.time.seconds.utc,1],[d3.time.seconds.utc,5],[d3.time.seconds.utc,15],[d3.time.seconds.utc,30],[d3.time.minutes.utc,1],[d3.time.minutes.utc,5],[d3.time.minutes.utc,15],[d3.time.minutes.utc,30],[d3.time.hours.utc,1],[d3.time.hours.utc,3],[d3.time.hours.utc,6],[d3.time.hours.utc,12],[d3.time.days.utc,1],[d3.time.days.utc,2],[d3.time.weeks.utc,1],[d3.time.months.utc,1],[d3.time.months.utc,3],[d3.time.years.utc,1]],gW=[[d3.time.format.utc("%Y"),function(a){return!0}],[d3.time.format.utc("%B"),function(a){return a.getUTCMonth()}],[d3.time.format.utc("%b %d"),function(a){return a.getUTCDate()!=1}],[d3.time.format.utc("%a %d"),function(a){return a.getUTCDay()&&a.getUTCDate()!=1}],[d3.time.format.utc("%I %p"),function(a){return a.getUTCHours()}],[d3.time.format.utc("%I:%M"),function(a){return a.getUTCMinutes()}],[d3.time.format.utc(":%S"),function(a){return a.getUTCSeconds()}],[d3.time.format.utc(".%L"),function(a){return a.getUTCMilliseconds()}]],gX=gN(gW);gV.year=function(a,b){return gT.domain(a.map(gZ)).ticks(b).map(gY)},d3.time.scale.utc=function(){return gK(d3.scale.linear(),gV,gX)}})(); \ No newline at end of file +(function(){function e(a,b){try{for(var c in b)Object.defineProperty(a.prototype,c,{value:b[c],enumerable:!1})}catch(d){a.prototype=b}}function g(a){var b=-1,c=a.length,d=[];while(++b=0?a.substring(b):(b=a.length,""),d=[];while(b>0)d.push(a.substring(b-=3,b+3));return d.reverse().join(",")+c}function F(a,b){return{scale:Math.pow(10,(8-b)*3),symbol:a}}function L(a){return function(b){return b<=0?0:b>=1?1:a(b)}}function M(a){return function(b){return 1-a(1-b)}}function N(a){return function(b){return.5*(b<.5?a(2*b):2-a(2-2*b))}}function O(a){return a}function P(a){return function(b){return Math.pow(b,a)}}function Q(a){return 1-Math.cos(a*Math.PI/2)}function R(a){return Math.pow(2,10*(a-1))}function S(a){return 1-Math.sqrt(1-a*a)}function T(a,b){var c;return arguments.length<2&&(b=.45),arguments.length<1?(a=1,c=b/4):c=b/(2*Math.PI)*Math.asin(1/a),function(d){return 1+a*Math.pow(2,10*-d)*Math.sin((d-c)*2*Math.PI/b)}}function U(a){return a||(a=1.70158),function(b){return b*b*((a+1)*b-a)}}function V(a){return a<1/2.75?7.5625*a*a:a<2/2.75?7.5625*(a-=1.5/2.75)*a+.75:a<2.5/2.75?7.5625*(a-=2.25/2.75)*a+.9375:7.5625*(a-=2.625/2.75)*a+.984375}function W(){d3.event.stopPropagation(),d3.event.preventDefault()}function X(){var a=d3.event,b;while(b=a.sourceEvent)a=b;return a}function Y(a){var b=new x,c=0,d=arguments.length;while(++c360?a-=360:a<0&&(a+=360),a<60?d+(e-d)*a/60:a<180?e:a<240?d+(e-d)*(240-a)/60:d}function g(a){return Math.round(f(a)*255)}var d,e;return a%=360,a<0&&(a+=360),b=b<0?0:b>1?1:b,c=c<0?0:c>1?1:c,e=c<=.5?c*(1+b):c+b-c*b,d=2*c-e,bb(g(a+120),g(a),g(a-120))}function bl(a){return j(a,br),a}function bs(a){return function(){return bm(a,this)}}function bt(a){return function(){return bn(a,this)}}function bv(a,b){function f(){if(b=this.classList)return b.add(a);var b=this.className,d=b.baseVal!=null,e=d?b.baseVal:b;c.lastIndex=0,c.test(e)||(e=t(e+" "+a),d?b.baseVal=e:this.className=e)}function g(){if(b=this.classList)return b.remove(a);var b=this.className,d=b.baseVal!=null,e=d?b.baseVal:b;e=t(e.replace(c," ")),d?b.baseVal=e:this.className=e}function h(){(b.apply(this,arguments)?f:g).call(this)}var c=new RegExp("(^|\\s+)"+d3.requote(a)+"(\\s+|$)","g");if(arguments.length<2){var d=this.node();if(e=d.classList)return e.contains(a);var e=d.className;return c.lastIndex=0,c.test(e.baseVal!=null?e.baseVal:e)}return this.each(typeof b=="function"?h:b?f:g)}function bw(a){return{__data__:a}}function bx(a){return function(){return bq(this,a)}}function by(a){return arguments.length||(a=d3.ascending),function(b,c){return a(b&&b.__data__,c&&c.__data__)}}function bA(a){return j(a,bB),a}function bC(a,b,c){j(a,bG);var d=new k,e=d3.dispatch("start","end"),f=bJ;return a.id=b,a.time=c,a.tween=function(b,c){return arguments.length<2?d.get(b):(c==null?d.remove(b):d.set(b,c),a)},a.ease=function(b){return arguments.length?(f=typeof b=="function"?b:d3.ease.apply(d3,arguments),a):f},a.each=function(b,c){return arguments.length<2?bK.call(a,b):(e.on(b,c),a)},d3.timer(function(g){return a.each(function(h,i,j){function p(a){return o.active>b?r():(o.active=b,d.forEach(function(a,b){(tween=b.call(l,h,i))&&k.push(tween)}),e.start.call(l,h,i),q(a)||d3.timer(q,0,c),1)}function q(a){if(o.active!==b)return r();var c=(a-m)/n,d=f(c),g=k.length;while(g>0)k[--g].call(l,d);if(c>=1)return r(),bI=b,e.end.call(l,h,i),bI=0,1}function r(){return--o.count||delete l.__transition__,1}var k=[],l=this,m=a[j][i].delay,n=a[j][i].duration,o=l.__transition__||(l.__transition__={active:0,count:0});++o.count,m<=g?p(g):d3.timer(p,m,c)}),1},0,c),a}function bE(a,b,c){return c!=""&&bD}function bF(a,b){function d(a,d,e){var f=b.call(this,a,d);return f==null?e!=""&&bD:e!=f&&c(e,f)}function e(a,d,e){return e!=b&&c(e,b)}var c=$(a);return typeof b=="function"?d:b==null?bE:(b+="",e)}function bK(a){for(var b=0,c=this.length;b=c.delay&&(c.flush=c.callback(a)),c=c.next;var d=bP()-b;d>24?(isFinite(d)&&(clearTimeout(bN),bN=setTimeout(bO,d)),bM=0):(bM=1,bQ(bO))}function bP(){var a=null,b=bL,c=Infinity;while(b)b.flush?b=a?a.next=b.next:bL=b.next:(c=Math.min(c,b.then+b.delay),b=(a=b).next);return c}function bR(a){var b=[a.a,a.b],c=[a.c,a.d],d=bT(b),e=bS(b,c),f=bT(bU(c,b,-e))||0;b[0]*c[1]2?ci:ch,i=d?ba:_;return e=g(a,b,i,c),f=g(b,a,i,d3.interpolate),h}function h(a){return e(a)}var e,f;return h.invert=function(a){return f(a)},h.domain=function(b){return arguments.length?(a=b.map(Number),g()):a},h.range=function(a){return arguments.length?(b=a,g()):b},h.rangeRound=function(a){return h.range(a).interpolate(d3.interpolateRound)},h.clamp=function(a){return arguments.length?(d=a,g()):d},h.interpolate=function(a){return arguments.length?(c=a,g()):c},h.ticks=function(b){return cf(a,b)},h.tickFormat=function(b){return cg(a,b)},h.nice=function(){return b_(a,cd),g()},h.copy=function(){return cb(a,b,c,d)},g()}function cc(a,b){return d3.rebind(a,b,"range","rangeRound","interpolate","clamp")}function cd(a){return a=Math.pow(10,Math.round(Math.log(a)/Math.LN10)-1),{floor:function(b){return Math.floor(b/a)*a},ceil:function(b){return Math.ceil(b/a)*a}}}function ce(a,b){var c=bZ(a),d=c[1]-c[0],e=Math.pow(10,Math.floor(Math.log(d/b)/Math.LN10)),f=b/d*e;return f<=.15?e*=10:f<=.35?e*=5:f<=.75&&(e*=2),c[0]=Math.ceil(c[0]/e)*e,c[1]=Math.floor(c[1]/e)*e+e*.5,c[2]=e,c}function cf(a,b){return d3.range.apply(d3,ce(a,b))}function cg(a,b){return d3.format(",."+Math.max(0,-Math.floor(Math.log(ce(a,b)[2])/Math.LN10+.01))+"f")}function ch(a,b,c,d){var e=c(a[0],a[1]),f=d(b[0],b[1]);return function(a){return f(e(a))}}function ci(a,b,c,d){var e=[],f=[],g=0,h=Math.min(a.length,b.length)-1;a[h]0;j--)e.push(c(f)*j)}else{for(;fi;g--);e=e.slice(f,g)}return e},d.tickFormat=function(a,e){arguments.length<2&&(e=ck);if(arguments.length<1)return e;var f=a/d.ticks().length,g=b===cm?(h=-1e-12,Math.floor):(h=1e-12,Math.ceil),h;return function(a){return a/c(g(b(a)+h))0?0:-a)/Math.LN10}function cn(a,b){function e(b){return a(c(b))}var c=co(b),d=co(1/b);return e.invert=function(b){return d(a.invert(b))},e.domain=function(b){return arguments.length?(a.domain(b.map(c)),e):a.domain().map(d)},e.ticks=function(a){return cf(e.domain(),a)},e.tickFormat=function(a){return cg(e.domain(),a)},e.nice=function(){return e.domain(b_(e.domain(),cd))},e.exponent=function(a){if(!arguments.length)return b;var f=e.domain();return c=co(b=a),d=co(1/b),e.domain(f)},e.copy=function(){return cn(a.copy(),b)},cc(e,a)}function co(a){return function(b){return b<0?-Math.pow(-b,a):Math.pow(b,a)}}function cp(a,b){function f(b){return d[((c.get(b)||c.set(b,a.push(b)))-1)%d.length]}function g(b,c){return d3.range(a.length).map(function(a){return b+c*a})}var c,d,e;return f.domain=function(d){if(!arguments.length)return a;a=[],c=new k;var e=-1,g=d.length,h;while(++e1){h=b[1],f=a[i],i++,d+="C"+(e[0]+g[0])+","+(e[1]+g[1])+","+(f[0]-h[0])+","+(f[1]-h[1])+","+f[0]+","+f[1];for(var j=2;j9&&(f=c*3/Math.sqrt(f),g[h]=f*d,g[h+1]=f*e));h=-1;while(++h<=i)f=(a[Math.min(i,h+1)][0]-a[Math.max(0,h-1)][0])/(6*(1+g[h]*g[h])),b.push([f||0,g[h]*f||0]);return b}function db(a){return a.length<3?cJ(a):a[0]+cP(a,da(a))}function dc(a){var b,c=-1,d=a.length,e,f;while(++c1){var d=bZ(a.domain()),e,f=-1,g=b.length,h=(b[1]-b[0])/++c,i,j;while(++f0;)(j=+b[f]-i*h)>=d[0]&&e.push(j);for(--f,i=0;++id&&(c=b,d=e);return c}function dX(a){return a.reduce(dY,0)}function dY(a,b){return a+b[1]}function dZ(a,b){return d$(a,Math.ceil(Math.log(b.length)/Math.LN2+1))}function d$(a,b){var c=-1,d=+a[0],e=(a[1]-d)/b,f=[];while(++c<=b)f[c]=e*c+d;return f}function d_(a){return[d3.min(a),d3.max(a)]}function ea(a,b){return d3.rebind(a,b,"sort","children","value"),a.links=ee,a.nodes=function(b){return ef=!0,(a.nodes=a)(b)},a}function eb(a){return a.children}function ec(a){return a.value}function ed(a,b){return b.value-a.value}function ee(a){return d3.merge(a.map(function(a){return(a.children||[]).map(function(b){return{source:a,target:b}})}))}function eg(a,b){return a.value-b.value}function eh(a,b){var c=a._pack_next;a._pack_next=b,b._pack_prev=a,b._pack_next=c,c._pack_prev=b}function ei(a,b){a._pack_next=b,b._pack_prev=a}function ej(a,b){var c=b.x-a.x,d=b.y-a.y,e=a.r+b.r;return e*e-c*c-d*d>.001}function ek(a){function l(a){b=Math.min(a.x-a.r,b),c=Math.max(a.x+a.r,c),d=Math.min(a.y-a.r,d),e=Math.max(a.y+a.r,e)}var b=Infinity,c=-Infinity,d=Infinity,e=-Infinity,f=a.length,g,h,i,j,k;a.forEach(el),g=a[0],g.x=-g.r,g.y=0,l(g);if(f>1){h=a[1],h.x=h.r,h.y=0,l(h);if(f>2){i=a[2],ep(g,h,i),l(i),eh(g,i),g._pack_prev=i,eh(i,h),h=g._pack_next;for(var m=3;m0&&(a=d)}return a}function ey(a,b){return a.x-b.x}function ez(a,b){return b.x-a.x}function eA(a,b){return a.depth-b.depth}function eB(a,b){function c(a,d){var e=a.children;if(e&&(i=e.length)){var f,g=null,h=-1,i;while(++h=0)f=d[e]._tree,f.prelim+=b,f.mod+=b,b+=f.shift+(c+=f.change)}function eD(a,b,c){a=a._tree,b=b._tree;var d=c/(b.number-a.number);a.change+=d,b.change-=d,b.shift+=c,b.prelim+=c,b.mod+=c}function eE(a,b,c){return a._tree.ancestor.parent==b.parent?a._tree.ancestor:c}function eF(a){return{x:a.x,y:a.y,dx:a.dx,dy:a.dy}}function eG(a,b){var c=a.x+b[3],d=a.y+b[0],e=a.dx-b[1]-b[3],f=a.dy-b[0]-b[2];return e<0&&(c+=e/2,e=0),f<0&&(d+=f/2,f=0),{x:c,y:d,dx:e,dy:f}}function eH(a){return a.map(eI).join(",")}function eI(a){return/[",\n]/.test(a)?'"'+a.replace(/\"/g,'""')+'"':a}function eK(a,b){return function(c){return c&&a.hasOwnProperty(c.type)?a[c.type](c):b}}function eL(a){return"m0,"+a+"a"+a+","+a+" 0 1,1 0,"+ -2*a+"a"+a+","+a+" 0 1,1 0,"+2*a+"z"}function eM(a,b){eN.hasOwnProperty(a.type)&&eN[a.type](a,b)}function eO(a,b){eM(a.geometry,b)}function eP(a,b){for(var c=a.features,d=0,e=c.length;d0}function fb(a,b,c){return(c[0]-b[0])*(a[1]-b[1])<(c[1]-b[1])*(a[0]-b[0])}function fc(a,b,c,d){var e=a[0],f=b[0],g=c[0],h=d[0],i=a[1],j=b[1],k=c[1],l=d[1],m=e-g,n=f-e,o=h-g,p=i-k,q=j-i,r=l-k,s=(o*p-r*m)/(r*n-o*q);return[e+s*n,i+s*q]}function fe(a,b){var c={list:a.map(function(a,b){return{index:b,x:a[0],y:a[1]}}).sort(function(a,b){return a.yb.y?1:a.xb.x?1:0}),bottomSite:null},d={list:[],leftEnd:null,rightEnd:null,init:function(){d.leftEnd=d.createHalfEdge(null,"l"),d.rightEnd=d.createHalfEdge(null,"l"),d.leftEnd.r=d.rightEnd,d.rightEnd.l=d.leftEnd,d.list.unshift(d.leftEnd,d.rightEnd)},createHalfEdge:function(a,b){return{edge:a,side:b,vertex:null,l:null,r:null}},insert:function(a,b){b.l=a,b.r=a.r,a.r.l=b,a.r=b},leftBound:function(a){var b=d.leftEnd;do b=b.r;while(b!=d.rightEnd&&e.rightOf(b,a));return b=b.l,b},del:function(a){a.l.r=a.r,a.r.l=a.l,a.edge=null},right:function(a){return a.r},left:function(a){return a.l},leftRegion:function(a){return a.edge==null?c.bottomSite:a.edge.region[a.side]},rightRegion:function(a){return a.edge==null?c.bottomSite:a.edge.region[fd[a.side]]}},e={bisect:function(a,b){var c={region:{l:a,r:b},ep:{l:null,r:null}},d=b.x-a.x,e=b.y-a.y,f=d>0?d:-d,g=e>0?e:-e;return c.c=a.x*d+a.y*e+(d*d+e*e)*.5,f>g?(c.a=1,c.b=e/d,c.c/=d):(c.b=1,c.a=d/e,c.c/=e),c},intersect:function(a,b){var c=a.edge,d=b.edge;if(!c||!d||c.region.r==d.region.r)return null;var e=c.a*d.b-c.b*d.a;if(Math.abs(e)<1e-10)return null;var f=(c.c*d.b-d.c*c.b)/e,g=(d.c*c.a-c.c*d.a)/e,h=c.region.r,i=d.region.r,j,k;h.y=k.region.r.x;return l&&j.side==="l"||!l&&j.side==="r"?null:{x:f,y:g}},rightOf:function(a,b){var c=a.edge,d=c.region.r,e=b.x>d.x;if(e&&a.side==="l")return 1;if(!e&&a.side==="r")return 0;if(c.a===1){var f=b.y-d.y,g=b.x-d.x,h=0,i=0;!e&&c.b<0||e&&c.b>=0?i=h=f>=c.b*g:(i=b.x+b.y*c.b>c.c,c.b<0&&(i=!i),i||(h=1));if(!h){var j=d.x-c.region.l.x;i=c.b*(g*g-f*f)m*m+n*n}return a.side==="l"?i:!i},endPoint:function(a,c,d){a.ep[c]=d;if(!a.ep[fd[c]])return;b(a)},distance:function(a,b){var c=a.x-b.x,d=a.y-b.y;return Math.sqrt(c*c+d*d)}},f={list:[],insert:function(a,b,c){a.vertex=b,a.ystar=b.y+c;for(var d=0,e=f.list,g=e.length;dh.ystar||a.ystar==h.ystar&&b.x>h.vertex.x)continue;break}e.splice(d,0,a)},del:function(a){for(var b=0,c=f.list,d=c.length;bo.y&&(p=n,n=o,o=p,t="r"),s=e.bisect(n,o),m=d.createHalfEdge(s,t),d.insert(k,m),e.endPoint(s,fd[t],r),q=e.intersect(k,m),q&&(f.del(k),f.insert(k,q,e.distance(q,n))),q=e.intersect(m,l),q&&f.insert(m,q,e.distance(q,n));else break}for(i=d.right(d.leftEnd);i!=d.rightEnd;i=d.right(i))b(i.edge)}function ff(){return{leaf:!0,nodes:[],point:null}}function fg(a,b,c,d,e,f){if(!a(b,c,d,e,f)){var g=(c+e)*.5,h=(d+f)*.5,i=b.nodes;i[0]&&fg(a,i[0],c,d,g,h),i[1]&&fg(a,i[1],g,d,e,h),i[2]&&fg(a,i[2],c,h,g,f),i[3]&&fg(a,i[3],g,h,e,f)}}function fh(a){return{x:a[0],y:a[1]}}function fj(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function fk(a,b,c,d){var e,f,g=0,h=b.length,i=c.length;while(g=i)return-1;e=b.charCodeAt(g++);if(e==37){f=fq[b.charAt(g++)];if(!f||(d=f(a,c,d))<0)return-1}else if(e!=c.charCodeAt(d++))return-1}return d}function fr(a,b,c){return ft.test(b.substring(c,c+=3))?c:-1}function fs(a,b,c){fu.lastIndex=0;var d=fu.exec(b.substring(c,c+10));return d?c+=d[0].length:-1}function fv(a,b,c){var d=fw.get(b.substring(c,c+=3).toLowerCase());return d==null?-1:(a.setMonth(d),c)}function fx(a,b,c){fy.lastIndex=0;var d=fy.exec(b.substring(c,c+12));return d?(a.setMonth(fz.get(d[0].toLowerCase())),c+=d[0].length):-1}function fB(a,b,c){return fk(a,fp.c.toString(),b,c)}function fC(a,b,c){return fk(a,fp.x.toString(),b,c)}function fD(a,b,c){return fk(a,fp.X.toString(),b,c)}function fE(a,b,c){fO.lastIndex=0;var d=fO.exec(b.substring(c,c+4));return d?(a.setFullYear(d[0]),c+=d[0].length):-1}function fF(a,b,c){fO.lastIndex=0;var d=fO.exec(b.substring(c,c+2));return d?(a.setFullYear(fG()+ +d[0]),c+=d[0].length):-1}function fG(){return~~((new Date).getFullYear()/1e3)*1e3}function fH(a,b,c){fO.lastIndex=0;var d=fO.exec(b.substring(c,c+2));return d?(a.setMonth(d[0]-1),c+=d[0].length):-1}function fI(a,b,c){fO.lastIndex=0;var d=fO.exec(b.substring(c,c+2));return d?(a.setDate(+d[0]),c+=d[0].length):-1}function fJ(a,b,c){fO.lastIndex=0;var d=fO.exec(b.substring(c,c+2));return d?(a.setHours(+d[0]),c+=d[0].length):-1}function fK(a,b,c){return a.hour12=!0,fJ(a,b,c)}function fL(a,b,c){fO.lastIndex=0;var d=fO.exec(b.substring(c,c+2));return d?(a.setMinutes(+d[0]),c+=d[0].length):-1}function fM(a,b,c){fO.lastIndex=0;var d=fO.exec(b.substring(c,c+2));return d?(a.setSeconds(+d[0]),c+=d[0].length):-1}function fN(a,b,c){fO.lastIndex=0;var d=fO.exec(b.substring(c,c+3));return d?(a.setMilliseconds(+d[0]),c+=d[0].length):-1}function fP(a,b,c){var d=fQ.get(b.substring(c,c+=2).toLowerCase());return d==null?-1:(a.hour12pm=d,c)}function fR(a){var b=a.getTimezoneOffset(),c=b>0?"-":"+",d=~~(Math.abs(b)/60),e=Math.abs(b)%60;return c+fl(d)+fl(e)}function fT(a){return a.toISOString()}function fU(a,b,c){function d(b){var c=a(b),d=f(c,1);return b-c1)while(gb?1:a>=b?0:NaN},d3.descending=function(a,b){return ba?1:b>=a?0:NaN},d3.mean=function(a,b){var c=a.length,d,e=0,f=-1,g=0;if(arguments.length===1)while(++f1&&(a=a.map(b)),a=a.filter(p),a.length?d3.quantile(a.sort(d3.ascending),.5):undefined},d3.min=function(a,b){var c=-1,d=a.length,e,f;if(arguments.length===1){while(++cf&&(e=f)}else{while(++cf&&(e=f)}return e},d3.max=function(a,b){var c=-1,d=a.length,e,f;if(arguments.length===1){while(++ce&&(e=f)}else{while(++ce&&(e=f)}return e},d3.extent=function(a,b){var c=-1,d=a.length,e,f,g;if(arguments.length===1){while(++cf&&(e=f),gf&&(e=f),g1);return a+b*c*Math.sqrt(-2*Math.log(e)/e)}}},d3.sum=function(a,b){var c=0,d=a.length,e,f=-1;if(arguments.length===1)while(++f>1;a.call(b,b[f],f)>1;c0&&(e=f);return e},d3.last=function(a,b){var c=0,d=a.length,e=a[0],f;arguments.length===1&&(b=d3.ascending);while(++c=b.length)return e?e.call(a,c):d?c.sort(d):c;var h=-1,i=c.length,j=b[g++],l,m,n=new k,o,p={};while(++h=b.length)return a;var e=[],f=c[d++],h;for(h in a)e.push({key:h,values:g(a[h],d)});return f&&e.sort(function(a,b){return f(a.key,b.key)}),e}var a={},b=[],c=[],d,e;return a.map=function(a){return f(a,0)},a.entries=function(a){return g(f(a,0),0)},a.key=function(c){return b.push(c),a},a.sortKeys=function(d){return c[b.length-1]=d,a},a.sortValues=function(b){return d=b,a},a.rollup=function(b){return e=b,a},a},d3.keys=function(a){var b=[];for(var c in a)b.push(c);return b},d3.values=function(a){var b=[];for(var c in a)b.push(a[c]);return b},d3.entries=function(a){var b=[];for(var c in a)b.push({key:c,value:a[c]});return b},d3.permute=function(a,b){var c=[],d=-1,e=b.length;while(++db)d.push(g/e);else while((g=a+c*++f)=0&&(c=a.substring(0,b),a=a.substring(b+1)),w.hasOwnProperty(c)?{space:w[c],local:a}:a}},d3.dispatch=function(){var a=new x,b=-1,c=arguments.length;while(++b0&&(d=a.substring(c+1),a=a.substring(0,c)),arguments.length<2?this[a].on(d):this[a].on(d,b)},d3.format=function(a){var b=z.exec(a),c=b[1]||" ",d=b[3]||"",e=b[5],f=+b[6],g=b[7],h=b[8],i=b[9],j=1,k="",l=!1;h&&(h=+h.substring(1)),e&&(c="0",g&&(f-=Math.floor((f-1)/4)));switch(i){case"n":g=!0,i="g";break;case"%":j=100,k="%",i="f";break;case"p":j=100,k="%",i="r";break;case"d":l=!0,h=0;break;case"s":j=-1,i="r"}return i=="r"&&!h&&(i="g"),i=A.get(i)||C,function(a){if(l&&a%1)return"";var b=a<0&&(a=-a)?"−":d;if(j<0){var m=d3.formatPrefix(a,h);a*=m.scale,k=m.symbol}else a*=j;a=i(a,h);if(e){var n=a.length+b.length;n=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/,A=d3.map({g:function(a,b){return a.toPrecision(b)},e:function(a,b){return a.toExponential(b)},f:function(a,b){return a.toFixed(b)},r:function(a,b){return d3.round(a,b=B(a,b)).toFixed(Math.max(0,Math.min(20,b)))}}),E=["y","z","a","f","p","n","μ","m","","k","M","G","T","P","E","Z","Y"].map(F);d3.formatPrefix=function(a,b){var c=0;return a&&(a<0&&(a*=-1),b&&(a=d3.round(a,B(a,b))),c=1+Math.floor(1e-12+Math.log(a)/Math.LN10),c=Math.max(-24,Math.min(24,Math.floor((c<=0?c+1:c-1)/3)*3))),E[8+c/3]};var G=P(2),H=P(3),I=function(){return O},J=d3.map({linear:I,poly:P,quad:function(){return G},cubic:function(){return H},sin:function(){return Q},exp:function(){return R},circle:function(){return S},elastic:T,back:U,bounce:function(){return V}}),K=d3.map({"in":O,out:M,"in-out":N,"out-in":function(a){return N(M(a))}});d3.ease=function(a){var b=a.indexOf("-"),c=b>=0?a.substring(0,b):a,d=b>=0?a.substring(b+1):"in";return c=J.get(c)||I,d=K.get(d)||O,L(d(c.apply(null,Array.prototype.slice.call(arguments,1))))},d3.event=null,d3.interpolate=function(a,b){var c=d3.interpolators.length,d;while(--c>=0&&!(d=d3.interpolators[c](a,b)));return d},d3.interpolateNumber=function(a,b){return b-=a,function(c){return a+b*c}},d3.interpolateRound=function(a,b){return b-=a,function(c){return Math.round(a+b*c)}},d3.interpolateString=function(a,b){var c,d,e,f=0,g=0,h=[],i=[],j,k;Z.lastIndex=0;for(d=0;c=Z.exec(b);++d)c.index&&h.push(b.substring(f,g=c.index)),i.push({i:h.length,x:c[0]}),h.push(null),f=Z.lastIndex;f1){while(++e=0;)if(f=c[d])e&&e!==f.nextSibling&&e.parentNode.insertBefore(f,e),e=f;return this},br.sort=function(a){a=by.apply(this,arguments);for(var b=-1,c=this.length;++b0&&(a=a.substring(0,e)),arguments.length<2?(e=this.node()[d])&&e._:this.each(function(e,f){function i(a){var c=d3.event;d3.event=a;try{b.call(g,g.__data__,f)}finally{d3.event=c}}var g=this,h=g[d];h&&(g.removeEventListener(a,h,h.$),delete g[d]),b&&(g.addEventListener(a,g[d]=i,i.$=c),i._=b)})},br.each=function(a){for(var b=-1,c=this.length;++b=cy?e?"M0,"+f+"A"+f+","+f+" 0 1,1 0,"+ -f+"A"+f+","+f+" 0 1,1 0,"+f+"M0,"+e+"A"+e+","+e+" 0 1,0 0,"+ -e+"A"+e+","+e+" 0 1,0 0,"+e+"Z":"M0,"+f+"A"+f+","+f+" 0 1,1 0,"+ -f+"A"+f+","+f+" 0 1,1 0,"+f+"Z":e?"M"+f*k+","+f*l+"A"+f+","+f+" 0 "+j+",1 "+f*m+","+f*n+"L"+e*m+","+e*n+"A"+e+","+e+" 0 "+j+",0 "+e*k+","+e*l+"Z":"M"+f*k+","+f*l+"A"+f+","+f+" 0 "+j+",1 "+f*m+","+f*n+"L0,0"+"Z"}var a=cz,b=cA,c=cB,d=cC;return e.innerRadius=function(b){return arguments.length?(a=d3.functor(b),e):a},e.outerRadius=function(a){return arguments.length?(b=d3.functor(a),e):b},e.startAngle=function(a){return arguments.length?(c=d3.functor(a),e):c},e.endAngle=function(a){return arguments.length?(d=d3.functor(a),e):d},e.centroid=function(){var e=(a.apply(this,arguments)+b.apply(this,arguments))/2,f=(c.apply(this,arguments)+d.apply(this,arguments))/2+cx;return[Math.cos(f)*e,Math.sin(f)*e]},e};var cx=-Math.PI/2,cy=2*Math.PI-1e-6;d3.svg.line=function(){return cD(Object)};var cH="linear",cI=d3.map({linear:cJ,"step-before":cK,"step-after":cL,basis:cR,"basis-open":cS,"basis-closed":cT,bundle:cU,cardinal:cO,"cardinal-open":cM,"cardinal-closed":cN,monotone:db}),cW=[0,2/3,1/3,0],cX=[0,1/3,2/3,0],cY=[0,1/6,2/3,1/6];d3.svg.line.radial=function(){var a=cD(dc);return a.radius=a.x,delete a.x,a.angle=a.y,delete a.y,a},cK.reverse=cL,cL.reverse=cK,d3.svg.area=function(){return dd(Object)},d3.svg.area.radial=function(){var a=dd(dc);return a.radius=a.x,delete a.x,a.innerRadius=a.x0,delete a.x0,a.outerRadius=a.x1,delete a.x1,a.angle=a.y,delete a.y,a.startAngle=a.y0,delete a.y0,a.endAngle=a.y1,delete a.y1,a},d3.svg.chord=function(){function f(c,d){var e=g(this,a,c,d),f=g(this,b,c,d);return"M"+e.p0+i(e.r,e.p1,e.a1-e.a0)+(h(e,f)?j(e.r,e.p1,e.r,e.p0):j(e.r,e.p1,f.r,f.p0)+i(f.r,f.p1,f.a1-f.a0)+j(f.r,f.p1,e.r,e.p0))+"Z"}function g(a,b,f,g){var h=b.call(a,f,g),i=c.call(a,h,g),j=d.call(a,h,g)+cx,k=e.call(a,h,g)+cx;return{r:i,a0:j,a1:k,p0:[i*Math.cos(j),i*Math.sin(j)],p1:[i*Math.cos(k),i*Math.sin(k)]}}function h(a,b){return a.a0==b.a0&&a.a1==b.a1}function i(a,b,c){return"A"+a+","+a+" 0 "+ +(c>Math.PI)+",1 "+b}function j(a,b,c,d){return"Q 0,0 "+d}var a=dg,b=dh,c=di,d=cB,e=cC;return f.radius=function(a){return arguments.length?(c=d3.functor(a),f):c},f.source=function(b){return arguments.length?(a=d3.functor(b),f):a},f.target=function(a){return arguments.length?(b=d3.functor(a),f):b},f.startAngle=function(a){return arguments.length?(d=d3.functor(a),f):d},f.endAngle=function(a){return arguments.length?(e=d3.functor(a),f):e},f},d3.svg.diagonal=function(){function d(d,e){var f=a.call(this,d,e),g=b.call(this,d,e),h=(f.y+g.y)/2,i=[f,{x:f.x,y:h},{x:g.x,y:h},g];return i=i.map(c),"M"+i[0]+"C"+i[1]+" "+i[2]+" "+i[3]}var a=dg,b=dh,c=dl;return d.source=function(b){return arguments.length?(a=d3.functor(b),d):a},d.target=function(a){return arguments.length?(b=d3.functor(a),d):b},d.projection=function(a){return arguments.length?(c=a,d):c},d},d3.svg.diagonal.radial=function(){var a=d3.svg.diagonal(),b=dl,c=a.projection;return a.projection=function(a){return arguments.length?c(dm(b=a)):b},a},d3.svg.mouse=d3.mouse,d3.svg.touches=d3.touches,d3.svg.symbol=function(){function c(c,d){return(dr.get(a.call(this,c,d))||dq)(b.call(this,c,d))}var a=dp,b=dn;return c.type=function(b){return arguments.length?(a=d3.functor(b),c):a},c.size=function(a){return arguments.length?(b=d3.functor(a),c):b},c};var dr=d3.map({circle:dq,cross:function(a){var b= +Math.sqrt(a/5)/2;return"M"+ -3*b+","+ -b+"H"+ -b+"V"+ -3*b+"H"+b+"V"+ -b+"H"+3*b+"V"+b+"H"+b+"V"+3*b+"H"+ -b+"V"+b+"H"+ -3*b+"Z"},diamond:function(a){var b=Math.sqrt(a/(2*dt)),c=b*dt;return"M0,"+ -b+"L"+c+",0"+" 0,"+b+" "+ -c+",0"+"Z"},square:function(a){var b=Math.sqrt(a)/2;return"M"+ -b+","+ -b+"L"+b+","+ -b+" "+b+","+b+" "+ -b+","+b+"Z"},"triangle-down":function(a){var b=Math.sqrt(a/ds),c=b*ds/2;return"M0,"+c+"L"+b+","+ -c+" "+ -b+","+ -c+"Z"},"triangle-up":function(a){var b=Math.sqrt(a/ds),c=b*ds/2;return"M0,"+ -c+"L"+b+","+c+" "+ -b+","+c+"Z"}});d3.svg.symbolTypes=dr.keys();var ds=Math.sqrt(3),dt=Math.tan(30*Math.PI/180);d3.svg.axis=function(){function k(k){k.each(function(l,m,n){var o=d3.select(this),p=k.delay?function(a){var b=bI;try{return bI=k.id,a.transition().delay(k[n][m].delay).duration(k[n][m].duration).ease(k.ease())}finally{bI=b}}:Object,q=h==null?a.ticks?a.ticks.apply(a,g):a.domain():h,r=i==null?a.tickFormat?a.tickFormat.apply(a,g):String:i,s=dw(a,q,j),t=o.selectAll(".minor").data(s,String),u=t.enter().insert("line","g").attr("class","tick minor").style("opacity",1e-6),v=p(t.exit()).style("opacity",1e-6).remove(),w=p(t).style("opacity",1),x=o.selectAll("g").data(q,String),y=x.enter().insert("g","path").style("opacity",1e-6),z=p(x.exit()).style("opacity",1e-6).remove(),A=p(x).style("opacity",1),B,C=b$(a),D=o.selectAll(".domain").data([0]),E=D.enter().append("path").attr("class","domain"),F=p(D),G=a.copy(),H=this.__chart__||G;this.__chart__=G,y.append("line").attr("class","tick"),y.append("text"),A.select("text").text(r);switch(b){case"bottom":B=du,u.attr("y2",d),w.attr("x2",0).attr("y2",d),y.select("line").attr("y2",c),y.select("text").attr("y",Math.max(c,0)+f),A.select("line").attr("x2",0).attr("y2",c),A.select("text").attr("x",0).attr("y",Math.max(c,0)+f).attr("dy",".71em").attr("text-anchor","middle"),F.attr("d","M"+C[0]+","+e+"V0H"+C[1]+"V"+e);break;case"top":B=du,u.attr("y2",-d),w.attr("x2",0).attr("y2",-d),y.select("line").attr("y2",-c),y.select("text").attr("y",-(Math.max(c,0)+f)),A.select("line").attr("x2",0).attr("y2",-c),A.select("text").attr("x",0).attr("y",-(Math.max(c,0)+f)).attr("dy","0em").attr("text-anchor","middle"),F.attr("d","M"+C[0]+","+ -e+"V0H"+C[1]+"V"+ -e);break;case"left":B=dv,u.attr("x2",-d),w.attr("x2",-d).attr("y2",0),y.select("line").attr("x2",-c),y.select("text").attr("x",-(Math.max(c,0)+f)),A.select("line").attr("x2",-c).attr("y2",0),A.select("text").attr("x",-(Math.max(c,0)+f)).attr("y",0).attr("dy",".32em").attr("text-anchor","end"),F.attr("d","M"+ -e+","+C[0]+"H0V"+C[1]+"H"+ -e);break;case"right":B=dv,u.attr("x2",d),w.attr("x2",d).attr("y2",0),y.select("line").attr("x2",c),y.select("text").attr("x",Math.max(c,0)+f),A.select("line").attr("x2",c).attr("y2",0),A.select("text").attr("x",Math.max(c,0)+f).attr("y",0).attr("dy",".32em").attr("text-anchor","start"),F.attr("d","M"+e+","+C[0]+"H0V"+C[1]+"H"+e)}if(a.ticks)y.call(B,H),A.call(B,G),z.call(B,G),u.call(B,H),w.call(B,G),v.call(B,G);else{var I=G.rangeBand()/2,J=function(a){return G(a)+I};y.call(B,J),A.call(B,J)}})}var a=d3.scale.linear(),b="bottom",c=6,d=6,e=6,f=3,g=[10],h=null,i,j=0;return k.scale=function(b){return arguments.length?(a=b,k):a},k.orient=function(a){return arguments.length?(b=a,k):b},k.ticks=function(){return arguments.length?(g=arguments,k):g},k.tickValues=function(a){return arguments.length?(h=a,k):h},k.tickFormat=function(a){return arguments.length?(i=a,k):i},k.tickSize=function(a,b,f){if(!arguments.length)return c;var g=arguments.length-1;return c=+a,d=g>1?+b:c,e=g>0?+arguments[g]:c,k},k.tickPadding=function(a){return arguments.length?(f=+a,k):f},k.tickSubdivide=function(a){return arguments.length?(j=+a,k):j},k},d3.svg.brush=function(){function f(a){a.each(function(){var a=d3.select(this),e=a.selectAll(".background").data([0]),k=a.selectAll(".extent").data([0]),l=a.selectAll(".resize").data(d,String),m;a.style("pointer-events","all").on("mousedown.brush",j).on("touchstart.brush",j),e.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),k.enter().append("rect").attr("class","extent").style("cursor","move"),l.enter().append("g").attr("class",function(a){return"resize "+a}).style("cursor",function(a){return dx[a]}).append("rect").attr("x",function(a){return/[ew]$/.test(a)?-3:null}).attr("y",function(a){return/^[ns]/.test(a)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),l.style("display",f.empty()?"none":null),l.exit().remove(),b&&(m=b$(b),e.attr("x",m[0]).attr("width",m[1]-m[0]),h(a)),c&&(m=b$(c),e.attr("y",m[0]).attr("height",m[1]-m[0]),i(a)),g(a)})}function g(a){a.selectAll(".resize").attr("transform",function(a){return"translate("+e[+/e$/.test(a)][0]+","+e[+/^s/.test(a)][1]+")"})}function h(a){a.select(".extent").attr("x",e[0][0]),a.selectAll(".extent,.n>rect,.s>rect").attr("width",e[1][0]-e[0][0])}function i(a){a.select(".extent").attr("y",e[0][1]),a.selectAll(".extent,.e>rect,.w>rect").attr("height",e[1][1]-e[0][1])}function j(){function w(){var a=d3.event.changedTouches;return a?d3.touches(d,a)[0]:d3.mouse(d)}function x(){d3.event.keyCode==32&&(p||(q=null,r[0]-=e[1][0],r[1]-=e[1][1],p=2),W())}function y(){d3.event.keyCode==32&&p==2&&(r[0]+=e[1][0],r[1]+=e[1][1],p=0,W())}function z(){var a=w(),d=!1;s&&(a[0]+=s[0],a[1]+=s[1]),p||(d3.event.altKey?(q||(q=[(e[0][0]+e[1][0])/2,(e[0][1]+e[1][1])/2]),r[0]=e[+(a[0]0?e=c:e=0:c>0&&(b.start({type:"start",alpha:e=c}),d3.timer(a.tick)),a):e},a.start=function(){function s(a,c){var d=t(b),e=-1,f=d.length,g;while(++ee&&(e=h),d.push(h)}for(g=0;g=i[0]&&o<=i[1]&&(k=g[d3.bisect(j,o,1,m)-1],k.y+=n,k.push(e[f]));return g}var a=!0,b=Number,c=d_,d=dZ;return e.value=function(a){return arguments.length?(b=a,e):b},e.range=function(a){return arguments.length?(c=d3.functor(a),e):c},e.bins=function(a){return arguments.length?(d=typeof a=="number"?function(b){return d$(b,a)}:d3.functor(a),e):d},e.frequency=function(b){return arguments.length?(a=!!b,e):a},e},d3.layout.hierarchy=function(){function e(f,h,i){var j=b.call(g,f,h),k=ef?f:{data:f};k.depth=h,i.push(k);if(j&&(m=j.length)){var l=-1,m,n=k.children=[],o=0,p=h+1;while(++l0&&(eD(eE(g,a,d),a,m),i+=m,j+=m),k+=g._tree.mod,i+=e._tree.mod,l+=h._tree.mod,j+=f._tree.mod;g&&!ew(f)&&(f._tree.thread=g,f._tree.mod+=k-j),e&&!ev(h)&&(h._tree.thread=e,h._tree.mod+=i-l,d=a)}return d}var f=a.call(this,d,e),g=f[0];eB(g,function(a,b){a._tree={ancestor:a,prelim:0,mod:0,change:0,shift:0,number:b?b._tree.number+1:0}}),h(g),i(g,-g._tree.prelim);var k=ex(g,ez),l=ex(g,ey),m=ex(g,eA),n=k.x-b(k,l)/2,o=l.x+b(l,k)/2,p=m.depth||1;return eB(g,function(a){a.x=(a.x-n)/(o-n)*c[0],a.y=a.depth/p*c[1],delete a._tree}),f}var a=d3.layout.hierarchy().sort(null).value(null),b=eu,c=[1,1];return d.separation=function(a){return arguments.length?(b=a,d):b},d.size=function(a){return arguments.length?(c=a,d):c},ea(d,a)},d3.layout.treemap=function(){function i(a,b){var c=-1,d=a.length,e,f;while(++c0)d.push(g=f[o-1]),d.area+=g.area,(k=l(d,n))<=h?(f.pop(),h=k):(d.area-=d.pop().area,m(d,n,c,!1),n=Math.min(c.dx,c.dy),d.length=d.area=0,h=Infinity);d.length&&(m(d,n,c,!0),d.length=d.area=0),b.forEach(j)}}function k(a){var b=a.children;if(b&&b.length){var c=e(a),d=b.slice(),f,g=[];i(d,c.dx*c.dy/a.value),g.area=0;while(f=d.pop())g.push(f),g.area+=f.area,f.z!=null&&(m(g,f.z?c.dx:c.dy,c,!d.length),g.length=g.area=0);b.forEach(k)}}function l(a,b){var c=a.area,d,e=0,f=Infinity,g=-1,i=a.length;while(++ge&&(e=d)}return c*=c,b*=b,c?Math.max(b*e*h/c,c/(b*f*h)):Infinity}function m(a,c,d,e){var f=-1,g=a.length,h=d.x,i=d.y,j=c?b(a.area/c):0,k;if(c==d.dx){if(e||j>d.dy)j=d.dy;while(++fd.dx)j=d.dx;while(++f=a.length)return d;if(i)return i=!1,c;var b=f.lastIndex;if(a.charCodeAt(b)===34){var e=b;while(e++50?b:f<-140?c:g<21?d:a)(e)}var a=d3.geo.albers(),b=d3.geo.albers().origin([-160,60]).parallels([55,65]),c=d3.geo.albers().origin([-160,20]).parallels([8,18]),d=d3.geo.albers().origin([-60,10]).parallels([8,18]);return e.scale=function(f){return arguments.length?(a.scale(f),b.scale(f*.6),c.scale(f),d.scale(f*1.5),e.translate(a.translate())):a.scale()},e.translate=function(f){if(!arguments.length)return a.translate();var g=a.scale()/1e3,h=f[0],i=f[1];return a.translate(f),b.translate([h-400*g,i+170*g]),c.translate([h-190*g,i+200*g]),d.translate([h+580*g,i+430*g]),e},e.scale(a.scale())},d3.geo.bonne=function(){function g(g){var h=g[0]*eJ-c,i=g[1]*eJ-d;if(e){var j=f+e-i,k=h*Math.cos(i)/j;h=j*Math.sin(k),i=j*Math.cos(k)-f}else h*=Math.cos(i),i*=-1;return[a*h+b[0],a*i+b[1]]}var a=200,b=[480,250],c,d,e,f;return g.invert=function(d){var g=(d[0]-b[0])/a,h=(d[1]-b[1])/a;if(e){var i=f+h,j=Math.sqrt(g*g+i*i);h=f+e-j,g=c+j*Math.atan2(g,i)/Math.cos(h)}else h*=-1,g/=Math.cos(h);return[g/eJ,h/eJ]},g.parallel=function(a){return arguments.length?(f=1/Math.tan(e=a*eJ),g):e/eJ},g.origin=function(a){return arguments.length?(c=a[0]*eJ,d=a[1]*eJ,g):[c/eJ,d/eJ]},g.scale=function(b){return arguments.length?(a=+b,g):a},g.translate=function(a){return arguments.length?(b=[+a[0],+a[1]],g):b},g.origin([0,0]).parallel(45)},d3.geo.equirectangular=function(){function c(c){var d=c[0]/360,e=-c[1]/360;return[a*d+b[0],a*e+b[1]]}var a=500,b=[480,250];return c.invert=function(c){var d=(c[0]-b[0])/a,e=(c[1]-b[1])/a;return[360*d,-360*e]},c.scale=function(b){return arguments.length?(a=+b,c):a},c.translate=function(a){return arguments.length?(b=[+a[0],+a[1]],c):b},c},d3.geo.mercator=function(){function c(c){var d=c[0]/360,e=-(Math.log(Math.tan(Math.PI/4+c[1]*eJ/2))/eJ)/360;return[a*d+b[0],a*Math.max(-0.5,Math.min(.5,e))+b[1]]}var a=500,b=[480,250];return c.invert=function(c){var d=(c[0]-b[0])/a,e=(c[1]-b[1])/a;return[360*d,2*Math.atan(Math.exp(-360*e*eJ))/eJ-90]},c.scale=function(b){return arguments.length?(a=+b,c):a},c.translate=function(a){return arguments.length?(b=[+a[0],+a[1]],c):b},c},d3.geo.path=function(){function d(c,d){return typeof a=="function"&&(b=eL(a.apply(this,arguments))),f(c)||null}function e(a){return c(a).join(",")}function h(a){var b=k(a[0]),c=0,d=a.length;while(++c0){b.push("M");while(++h0){b.push("M");while(++kd&&(d=a),fe&&(e=f)}),[[b,c],[d,e]]};var eN={Feature:eO,FeatureCollection:eP,GeometryCollection:eQ,LineString:eR,MultiLineString:eS,MultiPoint:eR,MultiPolygon:eT,Point:eU,Polygon:eV};d3.geo.circle=function(){function e(){}function f(a){return d.distance(a)=k*k+l*l?d[f].index=-1:(d[m].index=-1,o=d[f].angle,m=f,n=g)):(o=d[f].angle,m=f,n=g);e.push(h);for(f=0,g=0;f<2;++g)d[g].index!==-1&&(e.push(d[g].index),f++);p=e.length;for(;g=0?(c=a.ep.r,d=a.ep.l):(c=a.ep.l,d=a.ep.r),a.a===1?(g=c?c.y:-1e6,e=a.c-a.b*g,h=d?d.y:1e6,f=a.c-a.b*h):(e=c?c.x:-1e6,g=a.c-a.a*e,f=d?d.x:1e6,h=a.c-a.a*f);var i=[e,g],j=[f,h];b[a.region.l.index].push(i,j),b[a.region.r.index].push(i,j)}),b.map(function(b,c){var d=a[c][0],e=a[c][1];return b.forEach(function(a){a.angle=Math.atan2(a[0]-d,a[1]-e)}),b.sort(function(a,b){return a.angle-b.angle}).filter(function(a,c){return!c||a.angle-b[c-1].angle>1e-10})})};var fd={l:"r",r:"l"};d3.geom.delaunay=function(a){var b=a.map(function(){return[]}),c=[];return fe(a,function(c){b[c.region.l.index].push(a[c.region.r.index])}),b.forEach(function(b,d){var e=a[d],f=e[0],g=e[1];b.forEach(function(a){a.angle=Math.atan2(a[0]-f,a[1]-g)}),b.sort(function(a,b){return a.angle-b.angle});for(var h=0,i=b.length-1;h=g,j=b.y>=h,l=(j<<1)+i;a.leaf=!1,a=a.nodes[l]||(a.nodes[l]=ff()),i?c=g:e=g,j?d=h:f=h,k(a,b,c,d,e,f)}var f,g=-1,h=a.length;h&&isNaN(a[0].x)&&(a=a.map(fh));if(arguments.length<5)if(arguments.length===3)e=d=c,c=b;else{b=c=Infinity,d=e=-Infinity;while(++gd&&(d=f.x),f.y>e&&(e=f.y);var i=d-b,j=e-c;i>j?e=c+i:d=b+j}var m=ff();return m.add=function(a){k(m,a,b,c,d,e)},m.visit=function(a){fg(a,m,b,c,d,e)},a.forEach(m.add),m},d3.time={};var fi=Date;fj.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(a){this._.setUTCDate(a)},setDay:function(a){this._.setUTCDay(a)},setFullYear:function(a){this._.setUTCFullYear(a)},setHours:function(a){this._.setUTCHours(a)},setMilliseconds:function(a){this._.setUTCMilliseconds(a)},setMinutes:function(a){this._.setUTCMinutes(a)},setMonth:function(a){this._.setUTCMonth(a)},setSeconds:function(a){this._.setUTCSeconds(a)},setTime:function(a){this._.setTime(a)}},d3.time.format=function(a){function c(c){var d=[],e=-1,f=0,g,h;while(++e=12?"PM":"AM"},S:function(a){return fl(a.getSeconds())},U:function(a){return fl(d3.time.sundayOfYear(a))},w:function(a){return a.getDay()},W:function(a){return fl(d3.time.mondayOfYear(a))},x:d3.time.format("%m/%d/%y"),X:d3.time.format("%H:%M:%S"),y:function(a){return fl(a.getFullYear()%100)},Y:function(a){return fn(a.getFullYear()%1e4)},Z:fR,"%":function(a){return"%"}},fq={a:fr,A:fs,b:fv,B:fx,c:fB,d:fI,e:fI,H:fJ,I:fK,L:fN,m:fH,M:fL,p:fP,S:fM,x:fC,X:fD,y:fF,Y:fE},ft=/^(?:sun|mon|tue|wed|thu|fri|sat)/i,fu=/^(?:Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday)/i;d3_time_weekdays=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];var fw=d3.map({jan:0,feb:1,mar:2,apr:3,may:4,jun:5,jul:6,aug:7,sep:8,oct:9,nov:10,dec:11}),fy=/^(?:January|February|March|April|May|June|July|August|September|October|November|December)/ig,fz=d3.map({january:0,february:1,march:2,april:3,may:4,june:5,july:6,august:7,september:8,october:9,november:10,december:11}),fA=["January","February","March","April","May","June","July","August","September","October","November","December"],fO=/\s*\d+/,fQ=d3.map({am:0,pm:1});d3.time.format.utc=function(a){function c(a){try{fi=fj;var c=new fi;return c._=a,b(c)}finally{fi=Date}}var b=d3.time.format(a);return c.parse=function(a){try{fi=fj;var c=b.parse(a);return c&&c._}finally{fi=Date}},c.toString=b.toString,c};var fS=d3.time.format.utc("%Y-%m-%dT%H:%M:%S.%LZ");d3.time.format.iso=Date.prototype.toISOString?fT:fS,fT.parse=function(a){return new Date(a)},fT.toString=fS.toString,d3.time.second=fU(function(a){return new fi(Math.floor(a/1e3)*1e3)},function(a,b){a.setTime(a.getTime()+Math.floor(b)*1e3)},function(a){return a.getSeconds()}),d3.time.seconds=d3.time.second.range,d3.time.seconds.utc=d3.time.second.utc.range,d3.time.minute=fU(function(a){return new fi(Math.floor(a/6e4)*6e4)},function(a,b){a.setTime(a.getTime()+Math.floor(b)*6e4)},function(a){return a.getMinutes()}),d3.time.minutes=d3.time.minute.range,d3.time.minutes.utc=d3.time.minute.utc.range,d3.time.hour=fU(function(a){var b=a.getTimezoneOffset()/60;return new fi((Math.floor(a/36e5-b)+b)*36e5)},function(a,b){a.setTime(a.getTime()+Math.floor(b)*36e5)},function(a){return a.getHours()}),d3.time.hours=d3.time.hour.range,d3.time.hours.utc=d3.time.hour.utc.range,d3.time.day=fU(function(a){return new fi(a.getFullYear(),a.getMonth(),a.getDate())},function(a,b){a.setDate(a.getDate()+b)},function(a){return a.getDate()-1}),d3.time.days=d3.time.day.range,d3.time.days.utc=d3.time.day.utc.range,d3.time.dayOfYear=function(a){var b=d3.time.year(a);return Math.floor((a-b)/864e5-(a.getTimezoneOffset()-b.getTimezoneOffset())/1440)},d3_time_weekdays.forEach(function(a,b){a=a.toLowerCase(),b=7-b;var c=d3.time[a]=fU(function(a){return(a=d3.time.day(a)).setDate(a.getDate()-(a.getDay()+b)%7),a},function(a,b){a.setDate(a.getDate()+Math.floor(b)*7)},function(a){var c=d3.time.year(a).getDay();return Math.floor((d3.time.dayOfYear(a)+(c+b)%7)/7)-(c!==b)});d3.time[a+"s"]=c.range,d3.time[a+"s"].utc=c.utc.range,d3.time[a+"OfYear"]=function(a){var c=d3.time.year(a).getDay();return Math.floor((d3.time.dayOfYear(a)+(c+b)%7)/7)}}),d3.time.week=d3.time.sunday,d3.time.weeks=d3.time.sunday.range,d3.time.weeks.utc=d3.time.sunday.utc.range,d3.time.weekOfYear=d3.time.sundayOfYear,d3.time.month=fU(function(a){return new fi(a.getFullYear(),a.getMonth(),1)},function(a,b){a.setMonth(a.getMonth()+b)},function(a){return a.getMonth()}),d3.time.months=d3.time.month.range,d3.time.months.utc=d3.time.month.utc.range,d3.time.year=fU(function(a){return new fi(a.getFullYear(),0,1)},function(a,b){a.setFullYear(a.getFullYear()+b)},function(a){return a.getFullYear()}),d3.time.years=d3.time.year.range,d3.time.years.utc=d3.time.year.utc.range;var ga=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],gb=[[d3.time.second,1],[d3.time.second,5],[d3.time.second,15],[d3.time.second,30],[d3.time.minute,1],[d3.time.minute,5],[d3.time.minute,15],[d3.time.minute,30],[d3.time.hour,1],[d3.time.hour,3],[d3.time.hour,6],[d3.time.hour,12],[d3.time.day,1],[d3.time.day,2],[d3.time.week,1],[d3.time.month,1],[d3.time.month,3],[d3.time.year,1]],gc=[[d3.time.format("%Y"),function(a){return!0}],[d3.time.format("%B"),function(a){return a.getMonth()}],[d3.time.format("%b %d"),function(a){return a.getDate()!=1}],[d3.time.format("%a %d"),function(a){return a.getDay()&&a.getDate()!=1}],[d3.time.format("%I %p"),function(a){return a.getHours()}],[d3.time.format("%I:%M"),function(a){return a.getMinutes()}],[d3.time.format(":%S"),function(a){return a.getSeconds()}],[d3.time.format(".%L"),function(a){return a.getMilliseconds()}]],gd=d3.scale.linear(),ge=fZ(gc);gb.year=function(a,b){return gd.domain(a.map(f_)).ticks(b).map(f$)},d3.time.scale=function(){return fW(d3.scale.linear(),gb,ge)};var gf=gb.map(function(a){return[a[0].utc,a[1]]}),gg=[[d3.time.format.utc("%Y"),function(a){return!0}],[d3.time.format.utc("%B"),function(a){return a.getUTCMonth()}],[d3.time.format.utc("%b %d"),function(a){return a.getUTCDate()!=1}],[d3.time.format.utc("%a %d"),function(a){return a.getUTCDay()&&a.getUTCDate()!=1}],[d3.time.format.utc("%I %p"),function(a){return a.getUTCHours()}],[d3.time.format.utc("%I:%M"),function(a){return a.getUTCMinutes()}],[d3.time.format.utc(":%S"),function(a){return a.getUTCSeconds()}],[d3.time.format.utc(".%L"),function(a){return a.getUTCMilliseconds()}]],gh=fZ(gg);gf.year=function(a,b){return gd.domain(a.map(gj)).ticks(b).map(gi)},d3.time.scale.utc=function(){return fW(d3.scale.linear(),gf,gh)}})(); \ No newline at end of file diff --git a/examples/axis/axis-explicit-ticks.html b/examples/axis/axis-explicit-ticks.html new file mode 100644 index 0000000000000..bb97a409ae0aa --- /dev/null +++ b/examples/axis/axis-explicit-ticks.html @@ -0,0 +1,46 @@ + + + + + + + + + + diff --git a/examples/box/box.js b/examples/box/box.js index 3d5ee866854b2..122397f98a162 100644 --- a/examples/box/box.js +++ b/examples/box/box.js @@ -37,7 +37,7 @@ d3.csv("../data/morley.csv", function(csv) { chart.duration(1000); window.transition = function() { - vis.map(randomize).call(chart); + vis.datum(randomize).call(chart); }; }); diff --git a/examples/brush/brush-ordinal.html b/examples/brush/brush-ordinal.html index a7e88502331a6..ddac092985a1b 100644 --- a/examples/brush/brush-ordinal.html +++ b/examples/brush/brush-ordinal.html @@ -69,7 +69,7 @@ .attr("class", "brush") .call(d3.svg.brush().x(x) .on("brushstart", brushstart) - .on("brush", brush) + .on("brush", brushmove) .on("brushend", brushend)) .selectAll("rect") .attr("height", h); @@ -78,7 +78,7 @@ svg.classed("selecting", true); } -function brush() { +function brushmove() { var s = d3.event.target.extent(); symbol.classed("selected", function(d) { return s[0] <= (d = x(d)) && d <= s[1]; }); } diff --git a/examples/brush/brush-x-resizer.html b/examples/brush/brush-x-resizer.html new file mode 100644 index 0000000000000..492c7ca64be80 --- /dev/null +++ b/examples/brush/brush-x-resizer.html @@ -0,0 +1,108 @@ + + + + + Brush + + + + + + + diff --git a/examples/brush/brush-x.html b/examples/brush/brush-x.html index b9d9804ffecf2..0dcec2adf3151 100644 --- a/examples/brush/brush-x.html +++ b/examples/brush/brush-x.html @@ -59,7 +59,7 @@ .attr("transform", "translate(0," + h + ")") .call(d3.svg.axis().scale(x).orient("bottom")); -var circle = svg.selectAll("circle") +var circle = svg.append("g").selectAll("circle") .data(data) .enter().append("circle") .attr("transform", function(d) { return "translate(" + x(d) + "," + y() + ")"; }) @@ -69,7 +69,7 @@ .attr("class", "brush") .call(d3.svg.brush().x(x) .on("brushstart", brushstart) - .on("brush", brush) + .on("brush", brushmove) .on("brushend", brushend)) .selectAll("rect") .attr("height", h); @@ -78,7 +78,7 @@ svg.classed("selecting", true); } -function brush() { +function brushmove() { var s = d3.event.target.extent(); circle.classed("selected", function(d) { return s[0] <= d && d <= s[1]; }); } diff --git a/examples/brush/brush-y.html b/examples/brush/brush-y.html index 308561126425e..ba8de49aa760b 100644 --- a/examples/brush/brush-y.html +++ b/examples/brush/brush-y.html @@ -58,7 +58,7 @@ .attr("class", "y axis") .call(d3.svg.axis().scale(y).orient("left")); -var circle = svg.selectAll("circle") +var circle = svg.append("g").selectAll("circle") .data(data) .enter().append("circle") .attr("transform", function(d) { return "translate(" + x() + "," + y(d) + ")"; }) @@ -68,7 +68,7 @@ .attr("class", "brush") .call(d3.svg.brush().y(y) .on("brushstart", brushstart) - .on("brush", brush) + .on("brush", brushmove) .on("brushend", brushend)) .selectAll("rect") .attr("width", w); @@ -77,7 +77,7 @@ svg.classed("selecting", true); } -function brush() { +function brushmove() { var e = d3.event.target.extent(); circle.classed("selected", function(d) { return e[0] <= d && d <= e[1]; }); } diff --git a/examples/brush/brush.html b/examples/brush/brush.html index 2017fbc9d0a70..e55c8b61a7a3a 100644 --- a/examples/brush/brush.html +++ b/examples/brush/brush.html @@ -64,7 +64,7 @@ .attr("class", "y axis") .call(d3.svg.axis().scale(y).orient("left")); -var circle = svg.selectAll("circle") +var circle = svg.append("g").selectAll("circle") .data(data) .enter().append("circle") .attr("transform", function(d) { return "translate(" + x(d[0]) + "," + y(d[1]) + ")"; }) @@ -74,14 +74,14 @@ .attr("class", "brush") .call(d3.svg.brush().x(x).y(y) .on("brushstart", brushstart) - .on("brush", brush) + .on("brush", brushmove) .on("brushend", brushend)); function brushstart() { svg.classed("selecting", true); } -function brush() { +function brushmove() { var e = d3.event.target.extent(); circle.classed("selected", function(d) { return e[0][0] <= d[0] && d[0] <= e[1][0] diff --git a/examples/bullet/bullet.js b/examples/bullet/bullet.js index dd590d1c8a1f9..54526ea053599 100644 --- a/examples/bullet/bullet.js +++ b/examples/bullet/bullet.js @@ -33,7 +33,7 @@ d3.json("bullets.json", function(data) { chart.duration(1000); window.transition = function() { - vis.map(randomize).call(chart); + vis.datum(randomize).call(chart); }; }); diff --git a/examples/calendar/dji.js b/examples/calendar/dji.js index c7493e41c7bd5..098315e4e9ed5 100644 --- a/examples/calendar/dji.js +++ b/examples/calendar/dji.js @@ -34,7 +34,7 @@ var rect = svg.selectAll("rect.day") .attr("height", z) .attr("x", function(d) { return week(d) * z; }) .attr("y", function(d) { return day(d) * z; }) - .map(format); + .datum(format); rect.append("title") .text(function(d) { return d; }); diff --git a/examples/calendar/vix.js b/examples/calendar/vix.js index 744977b5c8b2a..3d7cf18142b8e 100644 --- a/examples/calendar/vix.js +++ b/examples/calendar/vix.js @@ -32,7 +32,7 @@ var rect = svg.selectAll("rect.day") .attr("height", z) .attr("x", function(d) { return week(d) * z; }) .attr("y", function(d) { return day(d) * z; }) - .map(format); + .datum(format); rect.append("title") .text(function(d) { return d; }); diff --git a/examples/contour/contour.html b/examples/contour/contour.html index 953ff755faccb..5c8f94a3c49a7 100644 --- a/examples/contour/contour.html +++ b/examples/contour/contour.html @@ -41,7 +41,7 @@ if (x < 0 || y < 0 || x >= dw || y >= dh) return 0; return data[y][x]==2; } - + var data = [[0, 0, 0, 0, 0, 0, 0, 0, 0, 0], [0, 0, 0, 2, 2, 2, 0, 0, 0, 0], [0, 0, 0, 2, 2, 2, 2, 0, 0, 0], @@ -96,7 +96,7 @@ grid = fn[d]; if (d > 0) { // map mouse to grid coords, then find left edge - var p = d3.svg.mouse(svg[0][0]); + var p = d3.mouse(svg[0][0]); s = [Math.floor(p[0]/sz), Math.floor(p[1]/sz)]; while (grid(s[0]-1,s[1])) s[0]--; } diff --git a/examples/force/force-dynamic.html b/examples/force/force-dynamic.html index a0f64bdc10024..730499ed962b8 100644 --- a/examples/force/force-dynamic.html +++ b/examples/force/force-dynamic.html @@ -67,11 +67,11 @@ }); vis.on("mousemove", function() { - cursor.attr("transform", "translate(" + d3.svg.mouse(this) + ")"); + cursor.attr("transform", "translate(" + d3.mouse(this) + ")"); }); vis.on("mousedown", function() { - var point = d3.svg.mouse(this), + var point = d3.mouse(this), node = {x: point[0], y: point[1]}, n = nodes.push(node); diff --git a/examples/force/force-html.html b/examples/force/force-html.html new file mode 100644 index 0000000000000..4f161143d2c4f --- /dev/null +++ b/examples/force/force-html.html @@ -0,0 +1,93 @@ + + + + Force-Directed Layout + + + + + + + diff --git a/examples/force/force.js b/examples/force/force.js index 2fa1789ae675c..760f9cec16b6e 100644 --- a/examples/force/force.js +++ b/examples/force/force.js @@ -1,38 +1,35 @@ -var w = 960, - h = 500, - fill = d3.scale.category20(); +var width = 960, + height = 500; -var vis = d3.select("#chart").append("svg") - .attr("width", w) - .attr("height", h); +var color = d3.scale.category20(); + +var force = d3.layout.force() + .charge(-120) + .linkDistance(30) + .size([width, height]); + +var svg = d3.select("#chart").append("svg") + .attr("width", width) + .attr("height", height); d3.json("miserables.json", function(json) { - var force = d3.layout.force() - .charge(-120) - .linkDistance(30) + force .nodes(json.nodes) .links(json.links) - .size([w, h]) .start(); - var link = vis.selectAll("line.link") + var link = svg.selectAll("line.link") .data(json.links) .enter().append("line") .attr("class", "link") - .style("stroke-width", function(d) { return Math.sqrt(d.value); }) - .attr("x1", function(d) { return d.source.x; }) - .attr("y1", function(d) { return d.source.y; }) - .attr("x2", function(d) { return d.target.x; }) - .attr("y2", function(d) { return d.target.y; }); + .style("stroke-width", function(d) { return Math.sqrt(d.value); }); - var node = vis.selectAll("circle.node") + var node = svg.selectAll("circle.node") .data(json.nodes) .enter().append("circle") .attr("class", "node") - .attr("cx", function(d) { return d.x; }) - .attr("cy", function(d) { return d.y; }) .attr("r", 5) - .style("fill", function(d) { return fill(d.group); }) + .style("fill", function(d) { return color(d.group); }) .call(force.drag); node.append("title") diff --git a/examples/hull/hull.html b/examples/hull/hull.html index 8e79aee170970..055b42a9e93da 100644 --- a/examples/hull/hull.html +++ b/examples/hull/hull.html @@ -61,12 +61,12 @@ } function move() { - vertices[0] = d3.svg.mouse(this); + vertices[0] = d3.mouse(this); update(); } function click() { - vertices.push(d3.svg.mouse(this)); + vertices.push(d3.mouse(this)); update(); } diff --git a/examples/mercator/mercator-zoom-constrained.html b/examples/mercator/mercator-zoom-constrained.html new file mode 100644 index 0000000000000..aa6f0d36c7081 --- /dev/null +++ b/examples/mercator/mercator-zoom-constrained.html @@ -0,0 +1,71 @@ + + + + + + diff --git a/examples/mercator/mercator-zoom.html b/examples/mercator/mercator-zoom.html new file mode 100644 index 0000000000000..2db63c2da80d9 --- /dev/null +++ b/examples/mercator/mercator-zoom.html @@ -0,0 +1,66 @@ + + + + + + diff --git a/examples/moire/moire.html b/examples/moire/moire.html index 6625a11de6e2a..cb6f28f0fc860 100644 --- a/examples/moire/moire.html +++ b/examples/moire/moire.html @@ -40,7 +40,7 @@ .attr("r", function(d) { return d * 3; }); svg.on("mousemove", function() { - var mouse = d3.svg.mouse(this), + var mouse = d3.mouse(this), r = (Math.sqrt(mouse[0]) + 10) / 10; circle diff --git a/examples/mouse/mouse-html.html b/examples/mouse/mouse-html.html new file mode 100644 index 0000000000000..731e2c63396d0 --- /dev/null +++ b/examples/mouse/mouse-html.html @@ -0,0 +1,24 @@ + + + + HTML Mouse Test + + + + + + diff --git a/examples/qq/qq.js b/examples/qq/qq.js index 873bcc4c19ee2..4affda44fcec1 100644 --- a/examples/qq/qq.js +++ b/examples/qq/qq.js @@ -54,7 +54,7 @@ d3.json("turkers.json", function(turkers) { chart.duration(1000); window.transition = function() { - g.map(randomize).call(chart); + g.datum(randomize).call(chart); }; }); diff --git a/examples/quadtree/quadtree.html b/examples/quadtree/quadtree.html index 0f1fccaa56f03..dd89ceab79126 100644 --- a/examples/quadtree/quadtree.html +++ b/examples/quadtree/quadtree.html @@ -61,14 +61,14 @@ // Highlight selected nodes using the quadtree. vis.on("mousedown", function() { - var m0 = d3.svg.mouse(this); + var m0 = d3.mouse(this); var rect = d3.select(this).append("rect") .style("fill", "#999") .style("fill-opacity", .5); d3.select(window).on("mousemove", function() { - var m1 = d3.svg.mouse(rect.node()), + var m1 = d3.mouse(rect.node()), x0 = Math.min(w, m0[0], m1[0]), y0 = Math.min(w, m0[1], m1[1]), x1 = Math.max(0, m0[0], m1[0]), diff --git a/examples/showreel/showreel.html b/examples/showreel/showreel.html index 2d90c51e9753d..ae8c194f706b7 100644 --- a/examples/showreel/showreel.html +++ b/examples/showreel/showreel.html @@ -547,7 +547,7 @@ arc = d3.svg.arc(); svg.selectAll(".symbol path") - .map(function(d, i) { + .datum(function(d, i) { d = pie1[i]; d.innerRadius = r0; d.outerRadius = r1; diff --git a/examples/spline/spline.js b/examples/spline/spline.js index 819d8f5df34ce..0163d5b226336 100644 --- a/examples/spline/spline.js +++ b/examples/spline/spline.js @@ -13,7 +13,7 @@ vis.append("rect") .attr("width", w) .attr("height", h) .on("mousedown", function() { - points.push(selected = dragged = d3.svg.mouse(vis.node())); + points.push(selected = dragged = d3.mouse(vis.node())); update(); }); @@ -83,7 +83,7 @@ function update() { function mousemove() { if (!dragged) return; - var m = d3.svg.mouse(vis.node()); + var m = d3.mouse(vis.node()); dragged[0] = Math.max(0, Math.min(w, m[0])); dragged[1] = Math.max(0, Math.min(h, m[1])); update(); diff --git a/examples/touch/touch.html b/examples/touch/touch.html index a0c5b3a305c3d..0a06545849655 100644 --- a/examples/touch/touch.html +++ b/examples/touch/touch.html @@ -38,7 +38,7 @@ d3.event.preventDefault(); var circle = svg.selectAll("circle.touch") - .data(d3.svg.touches(svg.node()), function(d) { return d.identifier; }) + .data(d3.touches(svg.node()), function(d) { return d.identifier; }) .attr("cx", function(d) { return d[0]; }) .attr("cy", function(d) { return d[1]; }); diff --git a/examples/voroboids/voroboids.js b/examples/voroboids/voroboids.js index 68fc1a185a475..2d9801dd5cb4a 100644 --- a/examples/voroboids/voroboids.js +++ b/examples/voroboids/voroboids.js @@ -24,7 +24,7 @@ var svg = d3.select("#vis") .attr("height", h) .attr("class", "PiYG") .on("mousemove", function() { - var m = d3.svg.mouse(this); + var m = d3.mouse(this); mouse[0] = m[0]; mouse[1] = m[1]; }) diff --git a/examples/voronoi/voronoi.js b/examples/voronoi/voronoi.js index 61133574e2a51..76e5d71f84138 100644 --- a/examples/voronoi/voronoi.js +++ b/examples/voronoi/voronoi.js @@ -25,7 +25,7 @@ svg.selectAll("circle") .attr("r", 2); function update() { - vertices[0] = d3.svg.mouse(this); + vertices[0] = d3.mouse(this); svg.selectAll("path") .data(d3.geom.voronoi(vertices) .map(function(d) { return "M" + d.join("L") + "Z"; })) diff --git a/examples/zoom-pan/zoom-pan-transform.html b/examples/zoom-pan/zoom-pan-transform.html index 39537339943d7..965135805bf7b 100644 --- a/examples/zoom-pan/zoom-pan-transform.html +++ b/examples/zoom-pan/zoom-pan-transform.html @@ -1,96 +1,62 @@ - - - - Zoom + Pan - - - - - - - + diff --git a/examples/zoom-pan/zoom-pan.html b/examples/zoom-pan/zoom-pan.html index d84c52d0ec7d6..4acea66e43da7 100644 --- a/examples/zoom-pan/zoom-pan.html +++ b/examples/zoom-pan/zoom-pan.html @@ -1,120 +1,73 @@ - - - - Zoom + Pan - - - - - - - + diff --git a/package.json b/package.json index 8bc8a045be648..4fbc670dafc84 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "d3", - "version": "2.7.5", + "version": "2.8.0", "description": "A small, free JavaScript library for manipulating documents based on data.", "keywords": [ "dom", diff --git a/src/behavior/drag.js b/src/behavior/drag.js index 7e780468fff5d..a356f7f3492eb 100644 --- a/src/behavior/drag.js +++ b/src/behavior/drag.js @@ -1,41 +1,77 @@ // TODO Track touch points by identifier. d3.behavior.drag = function() { - var event = d3.dispatch("drag", "dragstart", "dragend"), + var event = d3_eventDispatch(drag, "drag", "dragstart", "dragend"), origin = null; function drag() { - this - .on("mousedown.drag", mousedown) + this.on("mousedown.drag", mousedown) .on("touchstart.drag", mousedown); - - d3.select(window) - .on("mousemove.drag", d3_behavior_dragMove) - .on("touchmove.drag", d3_behavior_dragMove) - .on("mouseup.drag", d3_behavior_dragUp, true) - .on("touchend.drag", d3_behavior_dragUp, true) - .on("click.drag", d3_behavior_dragClick, true); } - // snapshot the local context for subsequent dispatch - function start() { - d3_behavior_dragEvent = event; - d3_behavior_dragEventTarget = d3.event.target; - d3_behavior_dragTarget = this; - d3_behavior_dragArguments = arguments; - d3_behavior_dragOrigin = d3_behavior_dragPoint(); + function mousedown() { + var target = this, + event_ = event.of(target, arguments), + eventTarget = d3.event.target, + offset, + origin_ = point(), + moved = 0; + + var w = d3.select(window) + .on("mousemove.drag", dragmove) + .on("touchmove.drag", dragmove) + .on("mouseup.drag", dragend, true) + .on("touchend.drag", dragend, true); + if (origin) { - d3_behavior_dragOffset = origin.apply(d3_behavior_dragTarget, d3_behavior_dragArguments); - d3_behavior_dragOffset = [d3_behavior_dragOffset.x - d3_behavior_dragOrigin[0], d3_behavior_dragOffset.y - d3_behavior_dragOrigin[1]]; + offset = origin.apply(target, arguments); + offset = [offset.x - origin_[0], offset.y - origin_[1]]; } else { - d3_behavior_dragOffset = [0, 0]; + offset = [0, 0]; } - d3_behavior_dragMoved = 0; - } - function mousedown() { - start.apply(this, arguments); - d3_behavior_dragDispatch("dragstart"); + event_({type: "dragstart"}); + + function point() { + var p = target.parentNode, + t = d3.event.changedTouches; + return t ? d3.touches(p, t)[0] : d3.mouse(p); + } + + function dragmove() { + if (!target.parentNode) return dragend(); // target removed from DOM + + var p = point(), + dx = p[0] - origin_[0], + dy = p[1] - origin_[1]; + + moved |= dx | dy; + origin_ = p; + d3_eventCancel(); + + event_({type: "drag", x: p[0] + offset[0], y: p[1] + offset[1], dx: dx, dy: dy}); + } + + function dragend() { + event_({type: "dragend"}); + + // if moved, prevent the mouseup (and possibly click) from propagating + if (moved) { + d3_eventCancel(); + if (d3.event.target === eventTarget) w.on("click.drag", click, true); + } + + w .on("mousemove.drag", null) + .on("touchmove.drag", null) + .on("mouseup.drag", null) + .on("touchend.drag", null); + } + + // prevent the subsequent click from propagating (e.g., for anchors) + function click() { + d3_eventCancel(); + w.on("click.drag", null); + } } drag.origin = function(x) { @@ -46,80 +82,3 @@ d3.behavior.drag = function() { return d3.rebind(drag, event, "on"); }; - -var d3_behavior_dragEvent, - d3_behavior_dragEventTarget, - d3_behavior_dragTarget, - d3_behavior_dragArguments, - d3_behavior_dragOffset, - d3_behavior_dragOrigin, - d3_behavior_dragMoved; - -function d3_behavior_dragDispatch(type) { - var p = d3_behavior_dragPoint(), - o = d3.event, - e = d3.event = {type: type}; - - if (p) { - e.x = p[0] + d3_behavior_dragOffset[0]; - e.y = p[1] + d3_behavior_dragOffset[1]; - e.dx = p[0] - d3_behavior_dragOrigin[0]; - e.dy = p[1] - d3_behavior_dragOrigin[1]; - d3_behavior_dragMoved |= e.dx | e.dy; - d3_behavior_dragOrigin = p; - } - - try { - d3_behavior_dragEvent[type].apply(d3_behavior_dragTarget, d3_behavior_dragArguments); - } finally { - d3.event = o; - } - - o.stopPropagation(); - o.preventDefault(); -} - -function d3_behavior_dragPoint() { - var p = d3_behavior_dragTarget.parentNode, - t = d3.event.changedTouches; - return p && (t - ? d3.svg.touches(p, t)[0] - : d3.svg.mouse(p)); -} - -function d3_behavior_dragMove() { - if (!d3_behavior_dragTarget) return; - var parent = d3_behavior_dragTarget.parentNode; - - // O NOES! The drag element was removed from the DOM. - if (!parent) return d3_behavior_dragUp(); - - d3_behavior_dragDispatch("drag"); - d3_eventCancel(); -} - -function d3_behavior_dragUp() { - if (!d3_behavior_dragTarget) return; - d3_behavior_dragDispatch("dragend"); - - // If the node was moved, prevent the mouseup from propagating. - // Also prevent the subsequent click from propagating (e.g., for anchors). - if (d3_behavior_dragMoved) { - d3_eventCancel(); - d3_behavior_dragMoved = d3.event.target === d3_behavior_dragEventTarget; - } - - d3_behavior_dragEvent = - d3_behavior_dragEventTarget = - d3_behavior_dragTarget = - d3_behavior_dragArguments = - d3_behavior_dragOffset = - d3_behavior_dragOrigin = null; -} - -function d3_behavior_dragClick() { - if (d3_behavior_dragMoved) { - d3_eventCancel(); - d3_behavior_dragMoved = 0; - } -} diff --git a/src/behavior/zoom.js b/src/behavior/zoom.js index 66b84cd4f0948..8e281db78ad54 100644 --- a/src/behavior/zoom.js +++ b/src/behavior/zoom.js @@ -1,99 +1,170 @@ -// TODO unbind zoom behavior? d3.behavior.zoom = function() { - var xyz = [0, 0, 0], - event = d3.dispatch("zoom"), - extent = d3_behavior_zoomInfiniteExtent; + var translate = [0, 0], + translate0, // translate when we started zooming (to avoid drift) + scale = 1, + scale0, // scale when we started touching + scaleExtent = d3_behavior_zoomInfinity, + event = d3_eventDispatch(zoom, "zoom"), + x0, + x1, + y0, + y1, + touchtime; // time of last touchstart (to detect double-tap) function zoom() { this .on("mousedown.zoom", mousedown) .on("mousewheel.zoom", mousewheel) + .on("mousemove.zoom", mousemove) .on("DOMMouseScroll.zoom", mousewheel) .on("dblclick.zoom", dblclick) - .on("touchstart.zoom", touchstart); - - d3.select(window) - .on("mousemove.zoom", d3_behavior_zoomMousemove) - .on("mouseup.zoom", d3_behavior_zoomMouseup) - .on("touchmove.zoom", d3_behavior_zoomTouchmove) - .on("touchend.zoom", d3_behavior_zoomTouchup) - .on("click.zoom", d3_behavior_zoomClick, true); + .on("touchstart.zoom", touchstart) + .on("touchmove.zoom", touchmove) + .on("touchend.zoom", touchstart); } - // snapshot the local context for subsequent dispatch - function start() { - d3_behavior_zoomXyz = xyz; - d3_behavior_zoomExtent = extent; - d3_behavior_zoomDispatch = event.zoom; - d3_behavior_zoomEventTarget = d3.event.target; - d3_behavior_zoomTarget = this; - d3_behavior_zoomArguments = arguments; + zoom.translate = function(x) { + if (!arguments.length) return translate; + translate = x.map(Number); + return zoom; + }; + + zoom.scale = function(x) { + if (!arguments.length) return scale; + scale = +x; + return zoom; + }; + + zoom.scaleExtent = function(x) { + if (!arguments.length) return scaleExtent; + scaleExtent = x == null ? d3_behavior_zoomInfinity : x.map(Number); + return zoom; + }; + + zoom.x = function(z) { + if (!arguments.length) return x1; + x1 = z; + x0 = z.copy(); + return zoom; + }; + + zoom.y = function(z) { + if (!arguments.length) return y1; + y1 = z; + y0 = z.copy(); + return zoom; + }; + + function location(p) { + return [(p[0] - translate[0]) / scale, (p[1] - translate[1]) / scale]; } - function mousedown() { - start.apply(this, arguments); - d3_behavior_zoomPanning = d3_behavior_zoomLocation(d3.svg.mouse(d3_behavior_zoomTarget)); - d3_behavior_zoomMoved = 0; + function point(l) { + return [l[0] * scale + translate[0], l[1] * scale + translate[1]]; + } + + function scaleTo(s) { + scale = 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]; + } + + function dispatch(event) { + 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)); d3.event.preventDefault(); + event({type: "zoom", scale: scale, translate: translate}); + } + + function mousedown() { + var target = this, + event_ = event.of(target, arguments), + eventTarget = d3.event.target, + moved = 0, + w = d3.select(window).on("mousemove.zoom", mousemove).on("mouseup.zoom", mouseup), + l = location(d3.mouse(target)); + window.focus(); + d3_eventCancel(); + + 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); + } + + function click() { + d3_eventCancel(); + w.on("click.zoom", null); + } } - // store starting mouse location function mousewheel() { - start.apply(this, arguments); - if (!d3_behavior_zoomZooming) d3_behavior_zoomZooming = d3_behavior_zoomLocation(d3.svg.mouse(d3_behavior_zoomTarget)); - d3_behavior_zoomTo(d3_behavior_zoomDelta() + xyz[2], d3.svg.mouse(d3_behavior_zoomTarget), d3_behavior_zoomZooming); + 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 mousemove() { + translate0 = null; } function dblclick() { - start.apply(this, arguments); - var mouse = d3.svg.mouse(d3_behavior_zoomTarget); - d3_behavior_zoomTo(d3.event.shiftKey ? Math.ceil(xyz[2] - 1) : Math.floor(xyz[2] + 1), mouse, d3_behavior_zoomLocation(mouse)); + var p = d3.mouse(this), l = location(p); + scaleTo(d3.event.shiftKey ? scale / 2 : scale * 2); + translateTo(p, l); + dispatch(event.of(this, arguments)); } - // doubletap detection function touchstart() { - start.apply(this, arguments); - var touches = d3_behavior_zoomTouchup(), - touch, + var touches = d3.touches(this), now = Date.now(); - if ((touches.length === 1) && (now - d3_behavior_zoomLast < 300)) { - d3_behavior_zoomTo(1 + Math.floor(xyz[2]), touch = touches[0], d3_behavior_zoomLocations[touch.identifier]); + + scale0 = scale; + translate0 = {}; + touches.forEach(function(t) { translate0[t.identifier] = location(t); }); + d3_eventCancel(); + + if ((touches.length === 1) && (now - touchtime < 500)) { // dbltap + var p = touches[0], l = location(touches[0]); + scaleTo(scale * 2); + translateTo(p, l); + dispatch(event.of(this, arguments)); } - d3_behavior_zoomLast = now; + touchtime = now; } - zoom.extent = function(x) { - if (!arguments.length) return extent; - extent = x == null ? d3_behavior_zoomInfiniteExtent : x; - return zoom; - }; + 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); + dispatch(event.of(this, arguments)); + } return d3.rebind(zoom, event, "on"); }; -var d3_behavior_zoomDiv, - d3_behavior_zoomPanning, - d3_behavior_zoomZooming, - d3_behavior_zoomLocations = {}, // identifier -> location - d3_behavior_zoomLast = 0, - d3_behavior_zoomXyz, - d3_behavior_zoomExtent, - d3_behavior_zoomDispatch, - d3_behavior_zoomEventTarget, - d3_behavior_zoomTarget, - d3_behavior_zoomArguments, - d3_behavior_zoomMoved; - -function d3_behavior_zoomLocation(point) { - return [ - point[0] - d3_behavior_zoomXyz[0], - point[1] - d3_behavior_zoomXyz[1], - d3_behavior_zoomXyz[2] - ]; -} +var d3_behavior_zoomDiv, // for interpreting mousewheel events + d3_behavior_zoomInfinity = [0, Infinity]; // default scale extent -// detect the pixels that would be scrolled by this wheel event function d3_behavior_zoomDelta() { // mousewheel events are totally broken! @@ -120,123 +191,5 @@ function d3_behavior_zoomDelta() { delta = e.wheelDelta || (-e.detail * 5); } - return delta * .005; -} - -// Note: Since we don't rotate, it's possible for the touches to become -// slightly detached from their original positions. Thus, we recompute the -// touch points on touchend as well as touchstart! -function d3_behavior_zoomTouchup() { - var touches = d3.svg.touches(d3_behavior_zoomTarget), - i = -1, - n = touches.length, - touch; - while (++i < n) d3_behavior_zoomLocations[(touch = touches[i]).identifier] = d3_behavior_zoomLocation(touch); - return touches; -} - -function d3_behavior_zoomTouchmove() { - var touches = d3.svg.touches(d3_behavior_zoomTarget); - switch (touches.length) { - - // single-touch pan - case 1: { - var touch = touches[0]; - d3_behavior_zoomTo(d3_behavior_zoomXyz[2], touch, d3_behavior_zoomLocations[touch.identifier]); - break; - } - - // double-touch pan + zoom - case 2: { - var p0 = touches[0], - p1 = touches[1], - p2 = [(p0[0] + p1[0]) / 2, (p0[1] + p1[1]) / 2], - l0 = d3_behavior_zoomLocations[p0.identifier], - l1 = d3_behavior_zoomLocations[p1.identifier], - l2 = [(l0[0] + l1[0]) / 2, (l0[1] + l1[1]) / 2, l0[2]]; - d3_behavior_zoomTo(Math.log(d3.event.scale) / Math.LN2 + l0[2], p2, l2); - break; - } - } -} - -function d3_behavior_zoomMousemove() { - d3_behavior_zoomZooming = null; - if (d3_behavior_zoomPanning) { - d3_behavior_zoomMoved = 1; - d3_behavior_zoomTo(d3_behavior_zoomXyz[2], d3.svg.mouse(d3_behavior_zoomTarget), d3_behavior_zoomPanning); - } -} - -function d3_behavior_zoomMouseup() { - if (d3_behavior_zoomPanning) { - if (d3_behavior_zoomMoved) { - d3_eventCancel(); - d3_behavior_zoomMoved = d3_behavior_zoomEventTarget === d3.event.target; - } - - d3_behavior_zoomXyz = - d3_behavior_zoomExtent = - d3_behavior_zoomDispatch = - d3_behavior_zoomEventTarget = - d3_behavior_zoomTarget = - d3_behavior_zoomArguments = - d3_behavior_zoomPanning = null; - } -} - -function d3_behavior_zoomClick() { - if (d3_behavior_zoomMoved) { - d3_eventCancel(); - d3_behavior_zoomMoved = 0; - } -} - -function d3_behavior_zoomTo(z, x0, x1) { - z = d3_behavior_zoomExtentClamp(z, 2); - var j = Math.pow(2, d3_behavior_zoomXyz[2]), - k = Math.pow(2, z), - K = Math.pow(2, (d3_behavior_zoomXyz[2] = z) - x1[2]), - x_ = d3_behavior_zoomXyz[0], - y_ = d3_behavior_zoomXyz[1], - x = d3_behavior_zoomXyz[0] = d3_behavior_zoomExtentClamp((x0[0] - x1[0] * K), 0, k), - y = d3_behavior_zoomXyz[1] = d3_behavior_zoomExtentClamp((x0[1] - x1[1] * K), 1, k), - o = d3.event; // Events can be reentrant (e.g., focus). - - d3.event = { - scale: k, - translate: [x, y], - transform: function(sx, sy) { - if (sx) transform(sx, x_, x); - if (sy) transform(sy, y_, y); - } - }; - - function transform(scale, a, b) { - scale.domain(scale.range().map(function(v) { return scale.invert(((v - b) * j) / k + a); })); - } - - try { - d3_behavior_zoomDispatch.apply(d3_behavior_zoomTarget, d3_behavior_zoomArguments); - } finally { - d3.event = o; - } - - o.preventDefault(); -} - -var d3_behavior_zoomInfiniteExtent = [ - [-Infinity, Infinity], - [-Infinity, Infinity], - [-Infinity, Infinity] -]; - -function d3_behavior_zoomExtentClamp(x, i, k) { - var range = d3_behavior_zoomExtent[i], - r0 = range[0], - r1 = range[1]; - return arguments.length === 3 - ? Math.max(r1 * (r1 === Infinity ? -Infinity : 1 / k - 1), - Math.min(r0 === -Infinity ? Infinity : r0, x / k)) * k - : Math.max(r0, Math.min(r1, x)); + return delta; } diff --git a/src/core/bisect.js b/src/core/bisect.js index 018197865244f..4e20a729905f2 100644 --- a/src/core/bisect.js +++ b/src/core/bisect.js @@ -1,38 +1,28 @@ -// Locate the insertion point for x in a to maintain sorted order. The -// arguments lo and hi may be used to specify a subset of the array which should -// be considered; by default the entire array is used. If x is already present -// in a, the insertion point will be before (to the left of) any existing -// entries. The return value is suitable for use as the first argument to -// `array.splice` assuming that a is already sorted. -// -// The returned insertion point i partitions the array a into two halves so that -// all v < x for v in a[lo:i] for the left side and all v >= x for v in a[i:hi] -// for the right side. -d3.bisectLeft = 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 (a[mid] < x) lo = mid + 1; - else hi = mid; - } - return lo; +d3.bisector = function(f) { + 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; + else hi = mid; + } + return lo; + }, + right: function(a, x, lo, hi) { + if (arguments.length < 3) lo = 0; + if (arguments.length < 4) hi = a.length; + while (lo < hi) { + var mid = lo + hi >> 1; + if (x < f.call(a, a[mid], mid)) hi = mid; + else lo = mid + 1; + } + return lo; + } + }; }; -// Similar to bisectLeft, but returns an insertion point which comes after (to -// the right of) any existing entries of x in a. -// -// The returned insertion point i partitions the array into two halves so that -// all v <= x for v in a[lo:i] for the left side and all v > x for v in a[i:hi] -// for the right side. -d3.bisect = -d3.bisectRight = 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 (x < a[mid]) 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; diff --git a/src/core/class.js b/src/core/class.js new file mode 100644 index 0000000000000..ca0fab7f30f4a --- /dev/null +++ b/src/core/class.js @@ -0,0 +1,12 @@ +function d3_class(ctor, properties) { + try { + for (var key in properties) { + Object.defineProperty(ctor.prototype, key, { + value: properties[key], + enumerable: false + }); + } + } catch (e) { + ctor.prototype = properties; + } +} diff --git a/src/core/core.js b/src/core/core.js index 7776e9db7eaee..26c142d26e506 100644 --- a/src/core/core.js +++ b/src/core/core.js @@ -1 +1 @@ -d3 = {version: "2.7.5"}; // semver +d3 = {version: "2.8.0"}; // semver diff --git a/src/core/dispatch.js b/src/core/dispatch.js index 7a77e8bfcad22..1bfe857748f7c 100644 --- a/src/core/dispatch.js +++ b/src/core/dispatch.js @@ -1,5 +1,5 @@ d3.dispatch = function() { - var dispatch = new d3_dispatch(), + var dispatch = new d3_dispatch, i = -1, n = arguments.length; while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); @@ -25,7 +25,7 @@ d3_dispatch.prototype.on = function(type, listener) { function d3_dispatch_event(dispatch) { var listeners = [], - listenerByName = {}; + listenerByName = new d3_Map; function event() { var z = listeners, // defensive reference @@ -37,22 +37,21 @@ function d3_dispatch_event(dispatch) { } event.on = function(name, listener) { - var l, i; + var l = listenerByName.get(name), + i; // return the current listener, if any - if (arguments.length < 2) return (l = listenerByName[name]) && l.on; + if (arguments.length < 2) return l && l.on; // remove the old listener, if any (with copy-on-write) - if (l = listenerByName[name]) { + if (l) { l.on = null; listeners = listeners.slice(0, i = listeners.indexOf(l)).concat(listeners.slice(i + 1)); - delete listenerByName[name]; + listenerByName.remove(name); } // add the new listener, if any - if (listener) { - listeners.push(listenerByName[name] = {on: listener}); - } + if (listener) listeners.push(listenerByName.set(name, {on: listener})); return dispatch; }; diff --git a/src/core/ease.js b/src/core/ease.js index b11a9050d1b1c..614de889a7c45 100644 --- a/src/core/ease.js +++ b/src/core/ease.js @@ -34,10 +34,11 @@ */ var d3_ease_quad = d3_ease_poly(2), - d3_ease_cubic = d3_ease_poly(3); + d3_ease_cubic = d3_ease_poly(3), + d3_ease_default = function() { return d3_ease_identity; }; -var d3_ease = { - linear: function() { return d3_ease_linear; }, +var d3_ease = d3.map({ + linear: d3_ease_default, poly: d3_ease_poly, quad: function() { return d3_ease_quad; }, cubic: function() { return d3_ease_cubic; }, @@ -47,20 +48,22 @@ var d3_ease = { elastic: d3_ease_elastic, back: d3_ease_back, bounce: function() { return d3_ease_bounce; } -}; +}); -var d3_ease_mode = { - "in": function(f) { return f; }, +var d3_ease_mode = d3.map({ + "in": d3_ease_identity, "out": d3_ease_reverse, "in-out": d3_ease_reflect, "out-in": function(f) { return d3_ease_reflect(d3_ease_reverse(f)); } -}; +}); d3.ease = function(name) { var i = name.indexOf("-"), t = i >= 0 ? name.substring(0, i) : name, m = i >= 0 ? name.substring(i + 1) : "in"; - return d3_ease_clamp(d3_ease_mode[m](d3_ease[t].apply(null, Array.prototype.slice.call(arguments, 1)))); + t = d3_ease.get(t) || d3_ease_default; + m = d3_ease_mode.get(m) || d3_ease_identity; + return d3_ease_clamp(m(t.apply(null, Array.prototype.slice.call(arguments, 1)))); }; function d3_ease_clamp(f) { @@ -81,14 +84,14 @@ function d3_ease_reflect(f) { }; } -function d3_ease_linear(t) { +function d3_ease_identity(t) { return t; } function d3_ease_poly(e) { return function(t) { return Math.pow(t, e); - } + }; } function d3_ease_sin(t) { diff --git a/src/core/event.js b/src/core/event.js index 947cf63195e40..f58c024f3bab0 100644 --- a/src/core/event.js +++ b/src/core/event.js @@ -4,3 +4,46 @@ function d3_eventCancel() { d3.event.stopPropagation(); d3.event.preventDefault(); } + +function d3_eventSource() { + var e = d3.event, s; + while (s = e.sourceEvent) e = s; + return e; +} + +// Like d3.dispatch, but for custom events abstracting native UI events. These +// events have a target component (such as a brush), a target element (such as +// the svg:g element containing the brush) and the standard arguments `d` (the +// target element's data) and `i` (the selection index of the target element). +function d3_eventDispatch(target) { + var dispatch = new d3_dispatch, + i = 0, + n = arguments.length; + + while (++i < n) dispatch[arguments[i]] = d3_dispatch_event(dispatch); + + // Creates a dispatch context for the specified `thiz` (typically, the target + // DOM element that received the source event) and `argumentz` (typically, the + // data `d` and index `i` of the target element). The returned function can be + // used to dispatch an event to any registered listeners; the function takes a + // single argument as input, being the event to dispatch. The event must have + // a "type" attribute which corresponds to a type registered in the + // constructor. This context will automatically populate the "sourceEvent" and + // "target" attributes of the event, as well as setting the `d3.event` global + // for the duration of the notification. + dispatch.of = function(thiz, argumentz) { + return function(e1) { + try { + var e0 = + e1.sourceEvent = d3.event; + e1.target = target; + d3.event = e1; + dispatch[e1.type].apply(thiz, argumentz); + } finally { + d3.event = e0; + } + }; + }; + + return dispatch; +} diff --git a/src/core/format.js b/src/core/format.js index d916fca2d5081..ec58b6e3d0f59 100644 --- a/src/core/format.js +++ b/src/core/format.js @@ -30,7 +30,7 @@ d3.format = function(specifier) { // If no precision is specified for r, fallback to general notation. if (type == "r" && !precision) type = "g"; - type = d3_format_types[type] || d3_format_typeDefault; + type = d3_format_types.get(type) || d3_format_typeDefault; return function(value) { @@ -75,12 +75,12 @@ d3.format = function(specifier) { // [[fill]align][sign][#][0][width][,][.precision][type] var d3_format_re = /(?:([^{])?([<>=^]))?([+\- ])?(#)?(0)?([0-9]+)?(,)?(\.[0-9]+)?([a-zA-Z%])?/; -var d3_format_types = { +var d3_format_types = d3.map({ g: function(x, p) { return x.toPrecision(p); }, e: function(x, p) { return x.toExponential(p); }, f: function(x, p) { return x.toFixed(p); }, r: function(x, p) { return d3.round(x, p = d3_format_precision(x, p)).toFixed(Math.max(0, Math.min(20, p))); } -}; +}); function d3_format_precision(x, p) { return p - (x ? 1 + Math.floor(Math.log(x + Math.pow(10, 1 + Math.floor(Math.log(x) / Math.LN10) - p)) / Math.LN10) : 1); diff --git a/src/core/interpolate.js b/src/core/interpolate.js index c60250627a04e..0f7f91ef18bc3 100644 --- a/src/core/interpolate.js +++ b/src/core/interpolate.js @@ -225,6 +225,6 @@ d3.interpolators = [ d3.interpolateObject, function(a, b) { return (b instanceof Array) && 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" ? b in d3_rgb_names || /^(#|rgb\(|hsl\()/.test(b) : b instanceof d3_Rgb || b instanceof d3_Hsl) && d3.interpolateRgb(a, b); }, + function(a, b) { return (typeof b === "string" ? d3_rgb_names.has(b) || /^(#|rgb\(|hsl\()/.test(b) : b instanceof d3_Rgb || b instanceof d3_Hsl) && d3.interpolateRgb(a, b); }, function(a, b) { return !isNaN(a = +a) && !isNaN(b = +b) && d3.interpolateNumber(a, b); } ]; diff --git a/src/core/map.js b/src/core/map.js new file mode 100644 index 0000000000000..b30ebe2a83b85 --- /dev/null +++ b/src/core/map.js @@ -0,0 +1,48 @@ +d3.map = function(object) { + var map = new d3_Map; + 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; + }, + 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; + }, + values: function() { + var values = []; + this.forEach(function(key, value) { values.push(value); }); + return values; + }, + entries: function() { + var entries = []; + this.forEach(function(key, value) { entries.push({key: key, value: value}); }); + return entries; + }, + forEach: function(f) { + 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); diff --git a/src/core/mouse.js b/src/core/mouse.js new file mode 100644 index 0000000000000..52e1a86b82562 --- /dev/null +++ b/src/core/mouse.js @@ -0,0 +1,34 @@ +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(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) && (window.scrollX || window.scrollY)) { + svg = d3.select(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]; +}; diff --git a/src/core/nest.js b/src/core/nest.js index aaa0f7a35f425..d26ffeab04da8 100644 --- a/src/core/nest.js +++ b/src/core/nest.js @@ -16,19 +16,21 @@ d3.nest = function() { key = keys[depth++], keyValue, object, + valuesByKey = new d3_Map, + values, o = {}; while (++i < n) { - if ((keyValue = key(object = array[i])) in o) { - o[keyValue].push(object); + if (values = valuesByKey.get(keyValue = key(object = array[i]))) { + values.push(object); } else { - o[keyValue] = [object]; + valuesByKey.set(keyValue, [object]); } } - for (keyValue in o) { - o[keyValue] = map(o[keyValue], depth); - } + valuesByKey.forEach(function(keyValue) { + o[keyValue] = map(valuesByKey.get(keyValue), depth); + }); return o; } diff --git a/src/core/ns.js b/src/core/ns.js index a680f0b6cf076..e23a653ba4918 100644 --- a/src/core/ns.js +++ b/src/core/ns.js @@ -9,9 +9,14 @@ var d3_nsPrefix = { d3.ns = { prefix: d3_nsPrefix, qualify: function(name) { - var i = name.indexOf(":"); - return i < 0 ? (name in d3_nsPrefix - ? {space: d3_nsPrefix[name], local: name} : name) - : {space: d3_nsPrefix[name.substring(0, i)], local: name.substring(i + 1)}; + var i = name.indexOf(":"), + prefix = name; + if (i >= 0) { + prefix = name.substring(0, i); + name = name.substring(i + 1); + } + return d3_nsPrefix.hasOwnProperty(prefix) + ? {space: d3_nsPrefix[prefix], local: name} + : name; } }; diff --git a/src/core/rgb.js b/src/core/rgb.js index 89037b32329a3..aa8fb20473e0a 100644 --- a/src/core/rgb.js +++ b/src/core/rgb.js @@ -84,7 +84,7 @@ function d3_rgb_parse(format, rgb, hsl) { } /* Named colors. */ - if (name = d3_rgb_names[format]) return rgb(name.r, name.g, name.b); + if (name = d3_rgb_names.get(format)) return rgb(name.r, name.g, name.b); /* Hexadecimal colors: #rgb and #rrggbb. */ if (format != null && format.charAt(0) === "#") { @@ -129,7 +129,7 @@ function d3_rgb_parseNumber(c) { // either integer or percentage return c.charAt(c.length - 1) === "%" ? Math.round(f * 2.55) : f; } -var d3_rgb_names = { +var d3_rgb_names = d3.map({ aliceblue: "#f0f8ff", antiquewhite: "#faebd7", aqua: "#00ffff", @@ -277,11 +277,8 @@ var d3_rgb_names = { whitesmoke: "#f5f5f5", yellow: "#ffff00", yellowgreen: "#9acd32" -}; +}); -for (var d3_rgb_name in d3_rgb_names) { - d3_rgb_names[d3_rgb_name] = d3_rgb_parse( - d3_rgb_names[d3_rgb_name], - d3_rgb, - d3_hsl_rgb); -} +d3_rgb_names.forEach(function(key, value) { + d3_rgb_names.set(key, d3_rgb_parse(value, d3_rgb, d3_hsl_rgb)); +}); diff --git a/src/core/selection-data.js b/src/core/selection-data.js index a54d4e14467bc..c84f5cfffaa40 100644 --- a/src/core/selection-data.js +++ b/src/core/selection-data.js @@ -1,8 +1,19 @@ -// TODO data(null) for clearing data? -d3_selectionPrototype.data = function(data, join) { - var enter = [], - update = [], - exit = []; +d3_selectionPrototype.data = function(value, key) { + var i = -1, + n = this.length, + group, + node; + + // If no value is specified, return the first value. + if (!arguments.length) { + value = new Array(n = (group = this[0]).length); + while (++i < n) { + if (node = group[i]) { + value[i] = node.__data__; + } + } + return value; + } function bind(group, groupData) { var i, @@ -16,37 +27,37 @@ d3_selectionPrototype.data = function(data, join) { node, nodeData; - if (join) { - var nodeByKey = {}, - keys = [], - key, + if (key) { + var nodeByKeyValue = new d3_Map, + keyValues = [], + keyValue, j = groupData.length; for (i = -1; ++i < n;) { - key = join.call(node = group[i], node.__data__, i); - if (key in nodeByKey) { + keyValue = key.call(node = group[i], node.__data__, i); + if (nodeByKeyValue.has(keyValue)) { exitNodes[j++] = node; // duplicate key } else { - nodeByKey[key] = node; + nodeByKeyValue.set(keyValue, node); } - keys.push(key); + keyValues.push(keyValue); } for (i = -1; ++i < m;) { - node = nodeByKey[key = join.call(groupData, nodeData = groupData[i], i)]; - if (node) { + keyValue = key.call(groupData, nodeData = groupData[i], i) + if (nodeByKeyValue.has(keyValue)) { + updateNodes[i] = node = nodeByKeyValue.get(keyValue); node.__data__ = nodeData; - updateNodes[i] = node; enterNodes[i] = exitNodes[i] = null; } else { enterNodes[i] = d3_selection_dataNode(nodeData); updateNodes[i] = exitNodes[i] = null; } - delete nodeByKey[key]; + nodeByKeyValue.remove(keyValue); } for (i = -1; ++i < n;) { - if (keys[i] in nodeByKey) { + if (nodeByKeyValue.has(keyValues[i])) { exitNodes[i] = group[i]; } } @@ -86,23 +97,23 @@ d3_selectionPrototype.data = function(data, join) { exit.push(exitNodes); } - var i = -1, - n = this.length, - group; - if (typeof data === "function") { + var enter = d3_selection_enter([]), + update = d3_selection([]), + exit = d3_selection([]); + + if (typeof value === "function") { while (++i < n) { - bind(group = this[i], data.call(group, group.parentNode.__data__, i)); + bind(group = this[i], value.call(group, group.parentNode.__data__, i)); } } else { while (++i < n) { - bind(group = this[i], data); + bind(group = this[i], value); } } - var selection = d3_selection(update); - selection.enter = function() { return d3_selection_enter(enter); }; - selection.exit = function() { return d3_selection(exit); }; - return selection; + update.enter = function() { return enter; }; + update.exit = function() { return exit; }; + return update; }; function d3_selection_dataNode(data) { diff --git a/src/core/selection-datum.js b/src/core/selection-datum.js new file mode 100644 index 0000000000000..a04660c3fb488 --- /dev/null +++ b/src/core/selection-datum.js @@ -0,0 +1,6 @@ +d3_selectionPrototype.datum = +d3_selectionPrototype.map = function(value) { + return arguments.length < 1 + ? this.property("__data__") + : this.property("__data__", value); +}; diff --git a/src/core/selection-enter.js b/src/core/selection-enter.js index 32714da0689f4..bc9071b177e2d 100644 --- a/src/core/selection-enter.js +++ b/src/core/selection-enter.js @@ -5,6 +5,9 @@ function d3_selection_enter(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.insert = d3_selectionPrototype.insert; d3_selection_enterPrototype.empty = d3_selectionPrototype.empty; diff --git a/src/core/selection-map.js b/src/core/selection-map.js deleted file mode 100644 index 3e7609d2408fc..0000000000000 --- a/src/core/selection-map.js +++ /dev/null @@ -1,5 +0,0 @@ -d3_selectionPrototype.map = function(map) { - return this.each(function() { - this.__data__ = map.apply(this, arguments); - }); -}; diff --git a/src/core/selection-on.js b/src/core/selection-on.js index 7c36f497f0624..d8d9a4380b04a 100644 --- a/src/core/selection-on.js +++ b/src/core/selection-on.js @@ -12,10 +12,20 @@ d3_selectionPrototype.on = function(type, listener, capture) { // remove the old event listener, and add the new event listener return this.each(function(d, i) { - var node = this; + var node = this, + o = node[name]; - if (node[name]) node.removeEventListener(type, node[name], capture); - if (listener) node.addEventListener(type, node[name] = l, capture); + // remove the old listener, if any (using the previously-set capture) + if (o) { + node.removeEventListener(type, o, o.$); + delete node[name]; + } + + // add the new listener, if any (remembering the capture flag) + if (listener) { + node.addEventListener(type, node[name] = l, l.$ = capture); + l._ = listener; // stash the unwrapped listener for get + } // wrapped event listener that preserves i function l(e) { @@ -27,8 +37,5 @@ d3_selectionPrototype.on = function(type, listener, capture) { d3.event = o; } } - - // stash the unwrapped listener for retrieval - l._ = listener; }); }; diff --git a/src/core/touches.js b/src/core/touches.js new file mode 100644 index 0000000000000..2323754214ffb --- /dev/null +++ b/src/core/touches.js @@ -0,0 +1,8 @@ +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; + }) : []; +}; diff --git a/src/core/transition-delay.js b/src/core/transition-delay.js index aa7accd578913..fe4ec2bd54fe9 100644 --- a/src/core/transition-delay.js +++ b/src/core/transition-delay.js @@ -1,6 +1,6 @@ d3_transitionPrototype.delay = function(value) { var groups = this; return groups.each(typeof value === "function" - ? function(d, i, j) { groups[j][i].delay = +value.apply(this, arguments); } - : (value = +value, function(d, i, j) { groups[j][i].delay = value; })); + ? function(d, i, j) { groups[j][i].delay = value.apply(this, arguments) | 0; } + : (value = value | 0, function(d, i, j) { groups[j][i].delay = value; })); }; diff --git a/src/core/transition-duration.js b/src/core/transition-duration.js index 55cc0f0ceb613..443cecb2658a8 100644 --- a/src/core/transition-duration.js +++ b/src/core/transition-duration.js @@ -1,6 +1,6 @@ d3_transitionPrototype.duration = function(value) { var groups = this; return groups.each(typeof value === "function" - ? function(d, i, j) { groups[j][i].duration = +value.apply(this, arguments); } - : (value = +value, function(d, i, j) { groups[j][i].duration = value; })); + ? function(d, i, j) { groups[j][i].duration = Math.max(1, value.apply(this, arguments) | 0); } + : (value = Math.max(1, value | 0), function(d, i, j) { groups[j][i].duration = value; })); }; diff --git a/src/core/transition.js b/src/core/transition.js index 691d6eff56235..14187c04bada6 100644 --- a/src/core/transition.js +++ b/src/core/transition.js @@ -1,7 +1,7 @@ function d3_transition(groups, id, time) { d3_arraySubclass(groups, d3_transitionPrototype); - var tweens = {}, + var tweens = new d3_Map, event = d3.dispatch("start", "end"), ease = d3_transitionEase; @@ -10,9 +10,9 @@ function d3_transition(groups, id, time) { groups.time = time; groups.tween = function(name, tween) { - if (arguments.length < 2) return tweens[name]; - if (tween == null) delete tweens[name]; - else tweens[name] = tween; + if (arguments.length < 2) return tweens.get(name); + if (tween == null) tweens.remove(name); + else tweens.set(name, tween); return groups; }; @@ -44,11 +44,11 @@ function d3_transition(groups, id, time) { if (lock.active > id) return stop(); lock.active = id; - for (var tween in tweens) { - if (tween = tweens[tween].call(node, d, i)) { + tweens.forEach(function(key, value) { + if (tween = value.call(node, d, i)) { tweened.push(tween); } - } + }); event.start.call(node, d, i); if (!tick(elapsed)) d3.timer(tick, 0, time); diff --git a/src/externs.js b/src/externs.js deleted file mode 100644 index 5289462cae334..0000000000000 --- a/src/externs.js +++ /dev/null @@ -1,6 +0,0 @@ -// JavaScript built-ins. -var console, - JSON; - -// d3 -var d3; diff --git a/src/geo/bounds.js b/src/geo/bounds.js index 9fbfc01a1aace..741a084d233d0 100644 --- a/src/geo/bounds.js +++ b/src/geo/bounds.js @@ -19,7 +19,7 @@ d3.geo.bounds = function(feature) { }; function d3_geo_bounds(o, f) { - if (o.type in d3_geo_boundsTypes) d3_geo_boundsTypes[o.type](o, f); + if (d3_geo_boundsTypes.hasOwnProperty(o.type)) d3_geo_boundsTypes[o.type](o, f); } var d3_geo_boundsTypes = { diff --git a/src/geo/type.js b/src/geo/type.js index d214a1b37d3fe..7998269333f01 100644 --- a/src/geo/type.js +++ b/src/geo/type.js @@ -1,5 +1,5 @@ function d3_geo_type(types, defaultValue) { return function(object) { - return object && object.type in types ? types[object.type](object) : defaultValue; + return object && types.hasOwnProperty(object.type) ? types[object.type](object) : defaultValue; }; } diff --git a/src/layout/force.js b/src/layout/force.js index 6ae7f0d3db0f0..0ca0c3a3cee9e 100644 --- a/src/layout/force.js +++ b/src/layout/force.js @@ -1,7 +1,7 @@ // A rudimentary force layout using Gauss-Seidel. d3.layout.force = function() { var force = {}, - event = d3.dispatch("tick"), + event = d3.dispatch("start", "tick", "end"), size = [1, 1], drag, alpha, @@ -43,9 +43,12 @@ d3.layout.force = function() { }; } - function tick() { + force.tick = function() { // simulated annealing, basically - if ((alpha *= .99) < .005) return true; + if ((alpha *= .99) < .005) { + event.end({type: "end", alpha: alpha = 0}); + return true; + } var n = nodes.length, m = links.length, @@ -111,7 +114,7 @@ d3.layout.force = function() { } event.tick({type: "tick", alpha: alpha}); - } + }; force.nodes = function(x) { if (!arguments.length) return nodes; @@ -170,6 +173,20 @@ d3.layout.force = function() { return force; }; + force.alpha = function(x) { + if (!arguments.length) return alpha; + + if (alpha) { // if we're already running + if (x > 0) alpha = x; // we might keep it hot + else alpha = 0; // or, next tick will dispatch "end" + } else if (x > 0) { // otherwise, fire it up! + event.start({type: "start", alpha: alpha = x}); + d3.timer(force.tick); + } + + return force; + }; + force.start = function() { var i, j, @@ -246,14 +263,11 @@ d3.layout.force = function() { }; force.resume = function() { - alpha = .1; - d3.timer(tick); - return force; + return force.alpha(.1); }; force.stop = function() { - alpha = 0; - return force; + return force.alpha(0); }; // use `node.call(force.drag)` to make nodes draggable @@ -289,7 +303,6 @@ function d3_layout_forceDragOut(d) { } function d3_layout_forceDragEnd() { - d3_layout_forceDrag(); d3_layout_forceDragNode.fixed &= 1; d3_layout_forceDragForce = d3_layout_forceDragNode = null; } diff --git a/src/layout/stack.js b/src/layout/stack.js index 2fd1b7f4e01ef..12890c587f1c4 100644 --- a/src/layout/stack.js +++ b/src/layout/stack.js @@ -1,8 +1,8 @@ // data is two-dimensional array of x,y; we populate y0 d3.layout.stack = function() { var values = Object, - order = d3_layout_stackOrders["default"], - offset = d3_layout_stackOffsets["zero"], + order = d3_layout_stackOrderDefault, + offset = d3_layout_stackOffsetZero, out = d3_layout_stackOut, x = d3_layout_stackX, y = d3_layout_stackY; @@ -53,13 +53,13 @@ d3.layout.stack = function() { stack.order = function(x) { if (!arguments.length) return order; - order = typeof x === "function" ? x : d3_layout_stackOrders[x]; + order = typeof x === "function" ? x : d3_layout_stackOrders.get(x) || d3_layout_stackOrderDefault; return stack; }; stack.offset = function(x) { if (!arguments.length) return offset; - offset = typeof x === "function" ? x : d3_layout_stackOffsets[x]; + offset = typeof x === "function" ? x : d3_layout_stackOffsets.get(x) || d3_layout_stackOffsetZero; return stack; }; @@ -97,7 +97,7 @@ function d3_layout_stackOut(d, y0, y) { d.y = y; } -var d3_layout_stackOrders = { +var d3_layout_stackOrders = d3.map({ "inside-out": function(data) { var n = data.length, @@ -127,13 +127,11 @@ var d3_layout_stackOrders = { return d3.range(data.length).reverse(); }, - "default": function(data) { - return d3.range(data.length); - } + "default": d3_layout_stackOrderDefault -}; +}); -var d3_layout_stackOffsets = { +var d3_layout_stackOffsets = d3.map({ "silhouette": function(data) { var n = data.length, @@ -203,15 +201,21 @@ var d3_layout_stackOffsets = { return y0; }, - "zero": function(data) { - var j = -1, - m = data[0].length, - y0 = []; - while (++j < m) y0[j] = 0; - return y0; - } + "zero": d3_layout_stackOffsetZero -}; +}); + +function d3_layout_stackOrderDefault(data) { + return d3.range(data.length); +} + +function d3_layout_stackOffsetZero(data) { + var j = -1, + m = data[0].length, + y0 = []; + while (++j < m) y0[j] = 0; + return y0; +} function d3_layout_stackMaxIndex(array) { var i = 1, diff --git a/src/scale/identity.js b/src/scale/identity.js new file mode 100644 index 0000000000000..759cb6c856b50 --- /dev/null +++ b/src/scale/identity.js @@ -0,0 +1,30 @@ +d3.scale.identity = function() { + return d3_scale_identity([0, 1]); +}; + +function d3_scale_identity(domain) { + + function identity(x) { return +x; } + + identity.invert = identity; + + identity.domain = identity.range = function(x) { + if (!arguments.length) return domain; + domain = x.map(identity); + return identity; + }; + + identity.ticks = function(m) { + return d3_scale_linearTicks(domain, m); + }; + + identity.tickFormat = function(m) { + return d3_scale_linearTickFormat(domain, m); + }; + + identity.copy = function() { + return d3_scale_identity(domain); + }; + + return identity; +} diff --git a/src/scale/ordinal.js b/src/scale/ordinal.js index 9201c79f2f42b..6da3e671f66d5 100644 --- a/src/scale/ordinal.js +++ b/src/scale/ordinal.js @@ -8,7 +8,7 @@ function d3_scale_ordinal(domain, ranger) { rangeBand; function scale(x) { - return range[((index[x] || (index[x] = domain.push(x))) - 1) % range.length]; + return range[((index.get(x) || index.set(x, domain.push(x))) - 1) % range.length]; } function steps(start, step) { @@ -18,9 +18,9 @@ function d3_scale_ordinal(domain, ranger) { scale.domain = function(x) { if (!arguments.length) return domain; domain = []; - index = {}; + index = new d3_Map; var i = -1, n = x.length, xi; - while (++i < n) if (!index[xi = x[i]]) index[xi] = domain.push(xi); + while (++i < n) if (!index.has(xi = x[i])) index.set(xi, domain.push(xi)); return scale[ranger.t](ranger.x, ranger.p); }; @@ -45,10 +45,12 @@ function d3_scale_ordinal(domain, ranger) { scale.rangeBands = function(x, padding) { if (arguments.length < 2) padding = 0; - var start = x[0], - stop = x[1], + var reverse = x[1] < x[0], + start = x[reverse - 0], + stop = x[1 - reverse], step = (stop - start) / (domain.length + padding); range = steps(start + step * padding, step); + if (reverse) range.reverse(); rangeBand = step * (1 - padding); ranger = {t: "rangeBands", x: x, p: padding}; return scale; @@ -56,10 +58,13 @@ function d3_scale_ordinal(domain, ranger) { scale.rangeRoundBands = function(x, padding) { if (arguments.length < 2) padding = 0; - var start = x[0], - stop = x[1], - step = Math.floor((stop - start) / (domain.length + padding)); - range = steps(start + Math.round((stop - start - (domain.length - padding) * step) / 2), step); + var reverse = x[1] < x[0], + start = x[reverse - 0], + stop = x[1 - reverse], + step = Math.floor((stop - start) / (domain.length + padding)), + error = stop - start - (domain.length - padding) * step; + range = steps(start + Math.round(error / 2), step); + if (reverse) range.reverse(); rangeBand = Math.round(step * (1 - padding)); ranger = {t: "rangeRoundBands", x: x, p: padding}; return scale; @@ -70,7 +75,7 @@ function d3_scale_ordinal(domain, ranger) { }; scale.rangeExtent = function() { - return ranger.t === "range" ? d3_scaleExtent(ranger.x) : ranger.x; + return d3_scaleExtent(ranger.x); }; scale.copy = function() { diff --git a/src/svg/area.js b/src/svg/area.js index 5229d0a7b102b..0eaa34984d7a9 100644 --- a/src/svg/area.js +++ b/src/svg/area.js @@ -55,7 +55,8 @@ function d3_svg_area(projection) { area.interpolate = function(x) { if (!arguments.length) return interpolate; - i0 = d3_svg_lineInterpolators[interpolate = x]; + if (!d3_svg_lineInterpolators.has(x += "")) x = d3_svg_lineInterpolatorDefault; + i0 = d3_svg_lineInterpolators.get(interpolate = x); i1 = i0.reverse || i0; return area; }; diff --git a/src/svg/axis.js b/src/svg/axis.js index c8455d953ad51..21d14c0508c91 100644 --- a/src/svg/axis.js +++ b/src/svg/axis.js @@ -6,6 +6,7 @@ d3.svg.axis = function() { tickEndSize = 6, tickPadding = 3, tickArguments_ = [10], + tickValues = null, tickFormat_, tickSubdivide = 0; @@ -28,7 +29,7 @@ d3.svg.axis = function() { } : Object; // Ticks, or domain values for ordinal scales. - var ticks = scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain(), + var ticks = tickValues == null ? (scale.ticks ? scale.ticks.apply(scale, tickArguments_) : scale.domain()) : tickValues, tickFormat = tickFormat_ == null ? (scale.tickFormat ? scale.tickFormat.apply(scale, tickArguments_) : String) : tickFormat_; // Minor ticks. @@ -149,6 +150,12 @@ d3.svg.axis = function() { return axis; }; + axis.tickValues = function(x) { + if (!arguments.length) return tickValues; + tickValues = x; + return axis; + }; + axis.tickFormat = function(x) { if (!arguments.length) return tickFormat_; tickFormat_ = x; diff --git a/src/svg/brush.js b/src/svg/brush.js index 8436e8ed3282d..2f12bc887c7c1 100644 --- a/src/svg/brush.js +++ b/src/svg/brush.js @@ -1,27 +1,28 @@ d3.svg.brush = function() { - var event = d3.dispatch("brushstart", "brush", "brushend"), + var event = d3_eventDispatch(brush, "brushstart", "brush", "brushend"), x, // x-scale, optional y, // y-scale, optional + resizes = d3_svg_brushResizes[0], extent = [[0, 0], [0, 0]]; // [x0, y0], [x1, y1] function brush(g) { - var resizes = x && y ? ["n", "e", "s", "w", "nw", "ne", "se", "sw"] - : x ? ["e", "w"] - : y ? ["n", "s"] - : []; - g.each(function() { - var g = d3.select(this).on("mousedown.brush", down), + var g = d3.select(this), bg = g.selectAll(".background").data([0]), fg = g.selectAll(".extent").data([0]), tz = g.selectAll(".resize").data(resizes, String), e; + // Prepare the brush container for events. + g + .style("pointer-events", "all") + .on("mousedown.brush", brushstart) + .on("touchstart.brush", brushstart); + // An invisible, mouseable area for starting a new brush. bg.enter().append("rect") .attr("class", "background") .style("visibility", "hidden") - .style("pointer-events", "all") .style("cursor", "crosshair"); // The visible brush extent; style this as you like! @@ -30,15 +31,18 @@ d3.svg.brush = function() { .style("cursor", "move"); // More invisible rects for resizing the extent. - tz.enter().append("rect") + tz.enter().append("g") .attr("class", function(d) { return "resize " + d; }) + .style("cursor", function(d) { return d3_svg_brushCursor[d]; }) + .append("rect") + .attr("x", function(d) { return /[ew]$/.test(d) ? -3 : null; }) + .attr("y", function(d) { return /^[ns]/.test(d) ? -3 : null; }) .attr("width", 6) .attr("height", 6) - .style("visibility", "hidden") - .style("cursor", function(d) { return d3_svg_brushCursor[d]; }); + .style("visibility", "hidden"); - // Update the resizers. - tz.style("pointer-events", brush.empty() ? "none" : "all"); + // Show or hide the resizers. + tz.style("display", brush.empty() ? "none" : null); // Remove any superfluous resizers. tz.exit().remove(); @@ -48,77 +52,224 @@ d3.svg.brush = function() { if (x) { e = d3_scaleRange(x); bg.attr("x", e[0]).attr("width", e[1] - e[0]); - d3_svg_brushRedrawX(g, extent); + redrawX(g); } if (y) { e = d3_scaleRange(y); bg.attr("y", e[0]).attr("height", e[1] - e[0]); - d3_svg_brushRedrawY(g, extent); + redrawY(g); } + redraw(g); }); } - function down() { - var target = d3.select(d3.event.target); + function redraw(g) { + g.selectAll(".resize").attr("transform", function(d) { + return "translate(" + extent[+/e$/.test(d)][0] + "," + extent[+/^s/.test(d)][1] + ")"; + }); + } + + function redrawX(g) { + g.select(".extent").attr("x", extent[0][0]); + g.selectAll(".extent,.n>rect,.s>rect").attr("width", extent[1][0] - extent[0][0]); + } + + function redrawY(g) { + g.select(".extent").attr("y", extent[0][1]); + g.selectAll(".extent,.e>rect,.w>rect").attr("height", extent[1][1] - extent[0][1]); + } - // Store some global state for the duration of the brush gesture. - d3_svg_brush = brush; - d3_svg_brushTarget = this; - d3_svg_brushExtent = extent; - d3_svg_brushOffset = d3.svg.mouse(d3_svg_brushTarget); + function brushstart() { + var target = this, + eventTarget = d3.select(d3.event.target), + event_ = event.of(target, arguments), + g = d3.select(target), + resizing = eventTarget.datum(), + resizingX = !/^(n|s)$/.test(resizing) && x, + resizingY = !/^(e|w)$/.test(resizing) && y, + dragging = eventTarget.classed("extent"), + center, + origin = mouse(), + offset; + + var w = d3.select(window) + .on("mousemove.brush", brushmove) + .on("mouseup.brush", brushend) + .on("touchmove.brush", brushmove) + .on("touchend.brush", brushend) + .on("keydown.brush", keydown) + .on("keyup.brush", keyup); // If the extent was clicked on, drag rather than brush; - // store the offset between the mouse and extent origin instead. - if (d3_svg_brushDrag = target.classed("extent")) { - d3_svg_brushOffset[0] = extent[0][0] - d3_svg_brushOffset[0]; - d3_svg_brushOffset[1] = extent[0][1] - d3_svg_brushOffset[1]; + // store the point between the mouse and extent origin instead. + if (dragging) { + origin[0] = extent[0][0] - origin[0]; + origin[1] = extent[0][1] - origin[1]; } // If a resizer was clicked on, record which side is to be resized. - // Also, set the offset to the opposite side. - else if (target.classed("resize")) { - d3_svg_brushResize = d3.event.target.__data__; - d3_svg_brushOffset[0] = extent[+/w$/.test(d3_svg_brushResize)][0]; - d3_svg_brushOffset[1] = extent[+/^n/.test(d3_svg_brushResize)][1]; + // Also, set the origin to the opposite side. + else if (resizing) { + var ex = +/w$/.test(resizing), + ey = +/^n/.test(resizing); + offset = [extent[1 - ex][0] - origin[0], extent[1 - ey][1] - origin[1]]; + origin[0] = extent[ex][0]; + origin[1] = extent[ey][1]; } // If the ALT key is down when starting a brush, the center is at the mouse. - else if (d3.event.altKey) { - d3_svg_brushCenter = d3_svg_brushOffset.slice(); - } + else if (d3.event.altKey) center = origin.slice(); - // Restrict which dimensions are resized. - d3_svg_brushX = !/^(n|s)$/.test(d3_svg_brushResize) && x; - d3_svg_brushY = !/^(e|w)$/.test(d3_svg_brushResize) && y; + // Propagate the active cursor to the body for the drag duration. + g.style("pointer-events", "none").selectAll(".resize").style("display", null); + d3.select("body").style("cursor", eventTarget.style("cursor")); // Notify listeners. - d3_svg_brushDispatch = dispatcher(this, arguments); - d3_svg_brushDispatch("brushstart"); - d3_svg_brushMove(); + event_({type: "brushstart"}); + brushmove(); d3_eventCancel(); - } - function dispatcher(that, argumentz) { - return function(type) { - var e = d3.event; - try { - d3.event = {type: type, target: brush}; - event[type].apply(that, argumentz); - } finally { - d3.event = e; + function mouse() { + var touches = d3.event.changedTouches; + return touches ? d3.touches(target, touches)[0] : d3.mouse(target); + } + + function keydown() { + if (d3.event.keyCode == 32) { + if (!dragging) { + center = null; + origin[0] -= extent[1][0]; + origin[1] -= extent[1][1]; + dragging = 2; + } + d3_eventCancel(); + } + } + + function keyup() { + if (d3.event.keyCode == 32 && dragging == 2) { + origin[0] += extent[1][0]; + origin[1] += extent[1][1]; + dragging = 0; + d3_eventCancel(); + } + } + + function brushmove() { + var point = mouse(), + moved = false; + + // Preserve the offset for thick resizers. + if (offset) { + point[0] += offset[0]; + point[1] += offset[1]; + } + + if (!dragging) { + + // If needed, determine the center from the current extent. + if (d3.event.altKey) { + if (!center) center = [(extent[0][0] + extent[1][0]) / 2, (extent[0][1] + extent[1][1]) / 2]; + + // Update the origin, for when the ALT key is released. + origin[0] = extent[+(point[0] < center[0])][0]; + origin[1] = extent[+(point[1] < center[1])][1]; + } + + // When the ALT key is released, we clear the center. + else center = null; + } + + // Update the brush extent for each dimension. + if (resizingX && move1(point, x, 0)) { + redrawX(g); + moved = true; + } + if (resizingY && move1(point, y, 1)) { + redrawY(g); + moved = true; + } + + // Final redraw and notify listeners. + if (moved) { + redraw(g); + event_({type: "brush"}); + } + } + + function move1(point, scale, i) { + var range = d3_scaleRange(scale), + r0 = range[0], + r1 = range[1], + position = origin[i], + size = extent[1][i] - extent[0][i], + min, + max; + + // When dragging, reduce the range by the extent size and position. + if (dragging) { + r0 -= position; + r1 -= size + position; + } + + // Clamp the point so that the extent fits within the range extent. + min = Math.max(r0, Math.min(r1, point[i])); + + // Compute the new extent bounds. + if (dragging) { + max = (min += position) + size; + } else { + + // If the ALT key is pressed, then preserve the center of the extent. + if (center) position = Math.max(r0, Math.min(r1, 2 * center[i] - min)); + + // Compute the min and max of the position and point. + if (position < min) { + max = min; + min = position; + } else { + max = position; + } + } + + // Update the stored bounds. + if (extent[0][i] !== min || extent[1][i] !== max) { + extent[0][i] = min; + extent[1][i] = max; + return true; } - }; + } + + function brushend() { + brushmove(); + + // reset the cursor styles + g.style("pointer-events", "all").selectAll(".resize").style("display", brush.empty() ? "none" : null); + d3.select("body").style("cursor", null); + + w .on("mousemove.brush", null) + .on("mouseup.brush", null) + .on("touchmove.brush", null) + .on("touchend.brush", null) + .on("keydown.brush", null) + .on("keyup.brush", null); + + event_({type: "brushend"}); + d3_eventCancel(); + } } brush.x = function(z) { if (!arguments.length) return x; x = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; // fore! return brush; }; brush.y = function(z) { if (!arguments.length) return y; y = z; + resizes = d3_svg_brushResizes[!x << 1 | !y]; // fore! return brush; }; @@ -172,158 +323,9 @@ d3.svg.brush = function() { || (y && extent[0][1] === extent[1][1]); }; - d3.select(window) - .on("mousemove.brush", d3_svg_brushMove) - .on("mouseup.brush", d3_svg_brushUp) - .on("keydown.brush", d3_svg_brushKeydown) - .on("keyup.brush", d3_svg_brushKeyup); - return d3.rebind(brush, event, "on"); }; -var d3_svg_brush, - d3_svg_brushDispatch, - d3_svg_brushTarget, - d3_svg_brushX, - d3_svg_brushY, - d3_svg_brushExtent, - d3_svg_brushDrag, - d3_svg_brushResize, - d3_svg_brushCenter, - d3_svg_brushOffset; - -function d3_svg_brushRedrawX(g, extent) { - g.select(".extent").attr("x", extent[0][0]); - g.selectAll(".n,.s,.w,.nw,.sw").attr("x", extent[0][0] - 2); - g.selectAll(".e,.ne,.se").attr("x", extent[1][0] - 3); - g.selectAll(".extent,.n,.s").attr("width", extent[1][0] - extent[0][0]); -} - -function d3_svg_brushRedrawY(g, extent) { - g.select(".extent").attr("y", extent[0][1]); - g.selectAll(".n,.e,.w,.nw,.ne").attr("y", extent[0][1] - 3); - g.selectAll(".s,.se,.sw").attr("y", extent[1][1] - 4); - g.selectAll(".extent,.e,.w").attr("height", extent[1][1] - extent[0][1]); -} - -function d3_svg_brushKeydown() { - if (d3.event.keyCode == 32 && d3_svg_brushTarget && !d3_svg_brushDrag) { - d3_svg_brushCenter = null; - d3_svg_brushOffset[0] -= d3_svg_brushExtent[1][0]; - d3_svg_brushOffset[1] -= d3_svg_brushExtent[1][1]; - d3_svg_brushDrag = 2; - d3_eventCancel(); - } -} - -function d3_svg_brushKeyup() { - if (d3.event.keyCode == 32 && d3_svg_brushDrag == 2) { - d3_svg_brushOffset[0] += d3_svg_brushExtent[1][0]; - d3_svg_brushOffset[1] += d3_svg_brushExtent[1][1]; - d3_svg_brushDrag = 0; - d3_eventCancel(); - } -} - -function d3_svg_brushMove() { - if (d3_svg_brushOffset) { - var mouse = d3.svg.mouse(d3_svg_brushTarget), - g = d3.select(d3_svg_brushTarget); - - if (!d3_svg_brushDrag) { - - // If needed, determine the center from the current extent. - if (d3.event.altKey) { - if (!d3_svg_brushCenter) { - d3_svg_brushCenter = [ - (d3_svg_brushExtent[0][0] + d3_svg_brushExtent[1][0]) / 2, - (d3_svg_brushExtent[0][1] + d3_svg_brushExtent[1][1]) / 2 - ]; - } - - // Update the offset, for when the ALT key is released. - d3_svg_brushOffset[0] = d3_svg_brushExtent[+(mouse[0] < d3_svg_brushCenter[0])][0]; - d3_svg_brushOffset[1] = d3_svg_brushExtent[+(mouse[1] < d3_svg_brushCenter[1])][1]; - } - - // When the ALT key is released, we clear the center. - else d3_svg_brushCenter = null; - } - - // Update the brush extent for each dimension. - if (d3_svg_brushX) { - d3_svg_brushMove1(mouse, d3_svg_brushX, 0); - d3_svg_brushRedrawX(g, d3_svg_brushExtent); - } - if (d3_svg_brushY) { - d3_svg_brushMove1(mouse, d3_svg_brushY, 1); - d3_svg_brushRedrawY(g, d3_svg_brushExtent); - } - - // Notify listeners. - d3_svg_brushDispatch("brush"); - } -} - -function d3_svg_brushMove1(mouse, scale, i) { - var range = d3_scaleRange(scale), - r0 = range[0], - r1 = range[1], - offset = d3_svg_brushOffset[i], - size = d3_svg_brushExtent[1][i] - d3_svg_brushExtent[0][i], - min, - max; - - // When dragging, reduce the range by the extent size and offset. - if (d3_svg_brushDrag) { - r0 -= offset; - r1 -= size + offset; - } - - // Clamp the mouse so that the extent fits within the range extent. - min = Math.max(r0, Math.min(r1, mouse[i])); - - // Compute the new extent bounds. - if (d3_svg_brushDrag) { - max = (min += offset) + size; - } else { - - // If the ALT key is pressed, then preserve the center of the extent. - if (d3_svg_brushCenter) offset = Math.max(r0, Math.min(r1, 2 * d3_svg_brushCenter[i] - min)); - - // Compute the min and max of the offset and mouse. - if (offset < min) { - max = min; - min = offset; - } else { - max = offset; - } - } - - // Update the stored bounds. - d3_svg_brushExtent[0][i] = min; - d3_svg_brushExtent[1][i] = max; -} - -function d3_svg_brushUp() { - if (d3_svg_brushOffset) { - d3_svg_brushMove(); - d3.select(d3_svg_brushTarget).selectAll(".resize").style("pointer-events", d3_svg_brush.empty() ? "none" : "all"); - d3_svg_brushDispatch("brushend"); - d3_svg_brush = - d3_svg_brushDispatch = - d3_svg_brushTarget = - d3_svg_brushX = - d3_svg_brushY = - d3_svg_brushExtent = - d3_svg_brushDrag = - d3_svg_brushResize = - d3_svg_brushCenter = - d3_svg_brushOffset = null; - d3_eventCancel(); - } -} - var d3_svg_brushCursor = { n: "ns-resize", e: "ew-resize", @@ -334,3 +336,10 @@ var d3_svg_brushCursor = { se: "nwse-resize", sw: "nesw-resize" }; + +var d3_svg_brushResizes = [ + ["n", "e", "s", "w", "nw", "ne", "se", "sw"], + ["e", "w"], + ["n", "s"], + [] +]; diff --git a/src/svg/line.js b/src/svg/line.js index 36df47be96059..061fc4aa023eb 100644 --- a/src/svg/line.js +++ b/src/svg/line.js @@ -1,8 +1,8 @@ function d3_svg_line(projection) { var x = d3_svg_lineX, y = d3_svg_lineY, - interpolate = "linear", - interpolator = d3_svg_lineInterpolators[interpolate], + interpolate = d3_svg_lineInterpolatorDefault, + interpolator = d3_svg_lineInterpolators.get(interpolate), tension = .7; function line(d) { @@ -23,7 +23,8 @@ function d3_svg_line(projection) { line.interpolate = function(v) { if (!arguments.length) return interpolate; - interpolator = d3_svg_lineInterpolators[interpolate = v]; + if (!d3_svg_lineInterpolators.has(v += "")) v = d3_svg_lineInterpolatorDefault; + interpolator = d3_svg_lineInterpolators.get(interpolate = v); return line; }; @@ -76,8 +77,10 @@ function d3_svg_lineY(d) { return d[1]; } +var d3_svg_lineInterpolatorDefault = "linear"; + // The various interpolators supported by the `line` class. -var d3_svg_lineInterpolators = { +var d3_svg_lineInterpolators = d3.map({ "linear": d3_svg_lineLinear, "step-before": d3_svg_lineStepBefore, "step-after": d3_svg_lineStepAfter, @@ -89,7 +92,7 @@ var d3_svg_lineInterpolators = { "cardinal-open": d3_svg_lineCardinalOpen, "cardinal-closed": d3_svg_lineCardinalClosed, "monotone": d3_svg_lineMonotone -}; +}); // Linear interpolation; generates "L" commands. function d3_svg_lineLinear(points) { diff --git a/src/svg/mouse.js b/src/svg/mouse.js index e7b4639a4c908..e700491b3f347 100644 --- a/src/svg/mouse.js +++ b/src/svg/mouse.js @@ -1,29 +1 @@ -d3.svg.mouse = function(container) { - return d3_svg_mousePoint(container, d3.event); -}; - -// https://bugs.webkit.org/show_bug.cgi?id=44083 -var d3_mouse_bug44083 = /WebKit/.test(navigator.userAgent) ? -1 : 0; - -function d3_svg_mousePoint(container, e) { - var point = (container.ownerSVGElement || container).createSVGPoint(); - if ((d3_mouse_bug44083 < 0) && (window.scrollX || window.scrollY)) { - var svg = d3.select(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]; -}; +d3.svg.mouse = d3.mouse; diff --git a/src/svg/symbol.js b/src/svg/symbol.js index f555542506538..6ffc9846a1d56 100644 --- a/src/svg/symbol.js +++ b/src/svg/symbol.js @@ -3,8 +3,8 @@ d3.svg.symbol = function() { size = d3_svg_symbolSize; function symbol(d, i) { - return (d3_svg_symbols[type.call(this, d, i)] - || d3_svg_symbols.circle) + return (d3_svg_symbols.get(type.call(this, d, i)) + || d3_svg_symbolCircle) (size.call(this, d, i)); } @@ -32,15 +32,17 @@ function d3_svg_symbolType() { return "circle"; } +function d3_svg_symbolCircle(size) { + var r = Math.sqrt(size / Math.PI); + return "M0," + r + + "A" + r + "," + r + " 0 1,1 0," + (-r) + + "A" + r + "," + r + " 0 1,1 0," + r + + "Z"; +} + // TODO cross-diagonal? -var d3_svg_symbols = { - "circle": function(size) { - var r = Math.sqrt(size / Math.PI); - return "M0," + r - + "A" + r + "," + r + " 0 1,1 0," + (-r) - + "A" + r + "," + r + " 0 1,1 0," + r - + "Z"; - }, +var d3_svg_symbols = d3.map({ + "circle": d3_svg_symbolCircle, "cross": function(size) { var r = Math.sqrt(size / 5) / 2; return "M" + -3 * r + "," + -r @@ -90,9 +92,9 @@ var d3_svg_symbols = { + " " + -rx + "," + ry + "Z"; } -}; +}); -d3.svg.symbolTypes = d3.keys(d3_svg_symbols); +d3.svg.symbolTypes = d3_svg_symbols.keys(); var d3_svg_symbolSqrt3 = Math.sqrt(3), d3_svg_symbolTan30 = Math.tan(30 * Math.PI / 180); diff --git a/src/svg/touches.js b/src/svg/touches.js index d82a9d64db667..585997ac4275e 100644 --- a/src/svg/touches.js +++ b/src/svg/touches.js @@ -1,9 +1 @@ -d3.svg.touches = function(container, touches) { - if (arguments.length < 2) touches = d3.event.touches; - - return touches ? d3_array(touches).map(function(touch) { - var point = d3_svg_mousePoint(container, touch); - point.identifier = touch.identifier; - return point; - }) : []; -}; +d3.svg.touches = d3.touches; diff --git a/src/time/day.js b/src/time/day.js index b270c2b55e6ac..8443d09d1b463 100644 --- a/src/time/day.js +++ b/src/time/day.js @@ -1,7 +1,15 @@ -d3.time.day = function(date) { - return new Date(date.getFullYear(), date.getMonth(), date.getDate()); -}; +d3.time.day = d3_time_interval(function(date) { + return new d3_time(date.getFullYear(), date.getMonth(), date.getDate()); +}, function(date, offset) { + date.setDate(date.getDate() + offset); +}, function(date) { + return date.getDate() - 1; +}); + +d3.time.days = d3.time.day.range; +d3.time.days.utc = d3.time.day.utc.range; -d3.time.day.utc = function(date) { - return new Date(~~(date / 864e5) * 864e5); +d3.time.dayOfYear = function(date) { + var year = d3.time.year(date); + return Math.floor((date - year) / 864e5 - (date.getTimezoneOffset() - year.getTimezoneOffset()) / 1440); }; diff --git a/src/time/days.js b/src/time/days.js deleted file mode 100644 index a1f8270a5c321..0000000000000 --- a/src/time/days.js +++ /dev/null @@ -1,11 +0,0 @@ -d3.time.days = d3_time_range(d3.time.day, function(date) { - date.setDate(date.getDate() + 1); -}, function(date) { - return date.getDate() - 1; -}); - -d3.time.days.utc = d3_time_range(d3.time.day.utc, function(date) { - date.setUTCDate(date.getUTCDate() + 1); -}, function(date) { - return date.getUTCDate() - 1; -}); diff --git a/src/time/format-utc.js b/src/time/format-utc.js index 24285abef50f0..f40e175f09ed8 100644 --- a/src/time/format-utc.js +++ b/src/time/format-utc.js @@ -3,7 +3,7 @@ d3.time.format.utc = function(template) { function format(date) { try { - d3_time = d3_time_format_utc; + d3_time = d3_time_utc; var utc = new d3_time(); utc._ = date; return local(utc); @@ -14,7 +14,7 @@ d3.time.format.utc = function(template) { format.parse = function(string) { try { - d3_time = d3_time_format_utc; + d3_time = d3_time_utc; var date = local.parse(string); return date && date._; } finally { @@ -26,28 +26,3 @@ d3.time.format.utc = function(template) { return format; }; - -function d3_time_format_utc() { - this._ = new Date(Date.UTC.apply(this, arguments)); -} - -d3_time_format_utc.prototype = { - getDate: function() { return this._.getUTCDate(); }, - getDay: function() { return this._.getUTCDay(); }, - getFullYear: function() { return this._.getUTCFullYear(); }, - getHours: function() { return this._.getUTCHours(); }, - getMilliseconds: function() { return this._.getUTCMilliseconds(); }, - getMinutes: function() { return this._.getUTCMinutes(); }, - getMonth: function() { return this._.getUTCMonth(); }, - getSeconds: function() { return this._.getUTCSeconds(); }, - getTimezoneOffset: function() { return 0; }, - valueOf: function() { return this._.getTime(); }, - setDate: function(x) { this._.setUTCDate(x); }, - setDay: function(x) { this._.setUTCDay(x); }, - setFullYear: function(x) { this._.setUTCFullYear(x); }, - setHours: function(x) { this._.setUTCHours(x); }, - setMilliseconds: function(x) { this._.setUTCMilliseconds(x); }, - setMinutes: function(x) { this._.setUTCMinutes(x); }, - setMonth: function(x) { this._.setUTCMonth(x); }, - setSeconds: function(x) { this._.setUTCSeconds(x); } -}; diff --git a/src/time/format.js b/src/time/format.js index cc070815b865d..a1171bd36bb78 100644 --- a/src/time/format.js +++ b/src/time/format.js @@ -74,15 +74,15 @@ var d3_time_formats = { e: function(d) { return d3_time_sfill2(d.getDate()); }, H: function(d) { return d3_time_zfill2(d.getHours()); }, I: function(d) { return d3_time_zfill2(d.getHours() % 12 || 12); }, - j: d3_time_dayOfYear, + j: function(d) { return d3_time_zfill3(1 + d3.time.dayOfYear(d)); }, L: function(d) { return d3_time_zfill3(d.getMilliseconds()); }, m: function(d) { return d3_time_zfill2(d.getMonth() + 1); }, M: function(d) { return d3_time_zfill2(d.getMinutes()); }, p: function(d) { return d.getHours() >= 12 ? "PM" : "AM"; }, S: function(d) { return d3_time_zfill2(d.getSeconds()); }, - U: d3_time_weekNumberSunday, + U: function(d) { return d3_time_zfill2(d3.time.sundayOfYear(d)); }, w: function(d) { return d.getDay(); }, - W: d3_time_weekNumberMonday, + W: function(d) { return d3_time_zfill2(d3.time.mondayOfYear(d)); }, x: d3.time.format("%m/%d/%y"), X: d3.time.format("%H:%M:%S"), y: function(d) { return d3_time_zfill2(d.getFullYear() % 100); }, @@ -121,19 +121,9 @@ var d3_time_parsers = { // Note: weekday is validated, but does not set the date. function d3_time_parseWeekdayAbbrev(date, string, i) { - return string.substring(i, i += 3).toLowerCase() in d3_time_weekdayAbbrevLookup ? i : -1; + return d3_time_weekdayAbbrevRe.test(string.substring(i, i += 3)) ? i : -1; } -var d3_time_weekdayAbbrevLookup = { - sun: 3, - mon: 3, - tue: 3, - wed: 3, - thu: 3, - fri: 3, - sat: 3 -}; - // Note: weekday is validated, but does not set the date. function d3_time_parseWeekday(date, string, i) { d3_time_weekdayRe.lastIndex = 0; @@ -141,24 +131,16 @@ function d3_time_parseWeekday(date, string, i) { return n ? i += n[0].length : -1; } -var d3_time_weekdayRe = /^(?:Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday)/ig; - -var d3_time_weekdays = [ - "Sunday", - "Monday", - "Tuesday", - "Wednesday", - "Thursday", - "Friday", - "Saturday" -]; +var d3_time_weekdayAbbrevRe = /^(?:sun|mon|tue|wed|thu|fri|sat)/i, + d3_time_weekdayRe = /^(?:Sunday|Monday|Tuesday|Wednesday|Thursday|Friday|Saturday)/i; + d3_time_weekdays = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"]; function d3_time_parseMonthAbbrev(date, string, i) { - var n = d3_time_monthAbbrevLookup[string.substring(i, i += 3).toLowerCase()]; + var n = d3_time_monthAbbrevLookup.get(string.substring(i, i += 3).toLowerCase()); return n == null ? -1 : (date.setMonth(n), i); } -var d3_time_monthAbbrevLookup = { +var d3_time_monthAbbrevLookup = d3.map({ jan: 0, feb: 1, mar: 2, @@ -171,17 +153,17 @@ var d3_time_monthAbbrevLookup = { oct: 9, nov: 10, dec: 11 -}; +}); function d3_time_parseMonth(date, string, i) { d3_time_monthRe.lastIndex = 0; var n = d3_time_monthRe.exec(string.substring(i, i + 12)); - return n ? (date.setMonth(d3_time_monthLookup[n[0].toLowerCase()]), i += n[0].length) : -1; + return n ? (date.setMonth(d3_time_monthLookup.get(n[0].toLowerCase())), i += n[0].length) : -1; } var d3_time_monthRe = /^(?:January|February|March|April|May|June|July|August|September|October|November|December)/ig; -var d3_time_monthLookup = { +var d3_time_monthLookup = d3.map({ january: 0, february: 1, march: 2, @@ -194,7 +176,7 @@ var d3_time_monthLookup = { october: 9, november: 10, december: 11 -}; +}); var d3_time_months = [ "January", @@ -286,36 +268,14 @@ function d3_time_parseMilliseconds(date, string, i) { var d3_time_numberRe = /\s*\d+/; function d3_time_parseAmPm(date, string, i) { - var n = d3_time_amPmLookup[string.substring(i, i += 2).toLowerCase()]; + var n = d3_time_amPmLookup.get(string.substring(i, i += 2).toLowerCase()); return n == null ? -1 : (date.hour12pm = n, i); } -var d3_time_amPmLookup = { +var d3_time_amPmLookup = d3.map({ am: 0, pm: 1 -}; - -function d3_time_year(d) { - return new d3_time(d.getFullYear(), 0, 1); -} - -function d3_time_daysElapsed(d0, d1) { - return ~~((d1 - d0) / 864e5 - (d1.getTimezoneOffset() - d0.getTimezoneOffset()) / 1440); -} - -function d3_time_dayOfYear(d) { - return d3_time_zfill3(1 + d3_time_daysElapsed(d3_time_year(d), d)); -} - -function d3_time_weekNumberSunday(d) { - var d0 = d3_time_year(d); - return d3_time_zfill2(~~((d3_time_daysElapsed(d0, d) + d0.getDay()) / 7)); -} - -function d3_time_weekNumberMonday(d) { - var d0 = d3_time_year(d); - return d3_time_zfill2(~~((d3_time_daysElapsed(d0, d) + (d0.getDay() + 6) % 7) / 7)); -} +}); // TODO table of time zone offset names? function d3_time_zone(d) { diff --git a/src/time/hour.js b/src/time/hour.js index d54d0f4897d4d..1e8f0f760dc67 100644 --- a/src/time/hour.js +++ b/src/time/hour.js @@ -1,8 +1,11 @@ -d3.time.hour = function(date) { - var offset = date.getTimezoneOffset() / 60; - return new Date((~~(date / 36e5 - offset) + offset) * 36e5); -}; +d3.time.hour = d3_time_interval(function(date) { + var timezone = date.getTimezoneOffset() / 60; + return new d3_time((Math.floor(date / 36e5 - timezone) + timezone) * 36e5); +}, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 36e5); // DST breaks setHours +}, function(date) { + return date.getHours(); +}); -d3.time.hour.utc = function(date) { - return new Date(~~(date / 36e5) * 36e5); -}; +d3.time.hours = d3.time.hour.range; +d3.time.hours.utc = d3.time.hour.utc.range; diff --git a/src/time/hours.js b/src/time/hours.js deleted file mode 100644 index 37a32fa9f7582..0000000000000 --- a/src/time/hours.js +++ /dev/null @@ -1,11 +0,0 @@ -d3.time.hours = d3_time_range(d3.time.hour, d3_time_hoursStep, function(date) { - return date.getHours(); -}); - -d3.time.hours.utc = d3_time_range(d3.time.hour.utc, d3_time_hoursStep, function(date) { - return date.getUTCHours(); -}); - -function d3_time_hoursStep(date) { - date.setTime(date.getTime() + 36e5); -} diff --git a/src/time/interval.js b/src/time/interval.js new file mode 100644 index 0000000000000..6914412854468 --- /dev/null +++ b/src/time/interval.js @@ -0,0 +1,69 @@ +function d3_time_interval(local, step, number) { + + function round(date) { + var d0 = local(date), d1 = offset(d0, 1); + return date - d0 < d1 - date ? d0 : d1; + } + + function ceil(date) { + step(date = local(new d3_time(date - 1)), 1); + return date; + } + + function offset(date, k) { + step(date = new d3_time(+date), k); + return date; + } + + function range(t0, t1, dt) { + var time = ceil(t0), times = []; + if (dt > 1) { + while (time < t1) { + if (!(number(time) % dt)) times.push(new Date(+time)); + step(time, 1); + } + } else { + while (time < t1) times.push(new Date(+time)), step(time, 1); + } + return times; + } + + function range_utc(t0, t1, dt) { + try { + d3_time = d3_time_utc; + var utc = new d3_time_utc(); + utc._ = t0; + return range(utc, t1, dt); + } finally { + d3_time = Date; + } + } + + local.floor = local; + local.round = round; + local.ceil = ceil; + local.offset = offset; + local.range = range; + + var utc = local.utc = d3_time_interval_utc(local); + utc.floor = utc; + utc.round = d3_time_interval_utc(round); + utc.ceil = d3_time_interval_utc(ceil); + utc.offset = d3_time_interval_utc(offset); + utc.range = range_utc; + + return local; +} + +function d3_time_interval_utc(method) { + return function(date, k) { + try { + d3_time = d3_time_utc; + var utc = new d3_time_utc(); + utc._ = date; + return method(utc, k)._; + } finally { + d3_time = Date; + } + }; +} diff --git a/src/time/minute.js b/src/time/minute.js index e686160b4df11..b23d29b106b55 100644 --- a/src/time/minute.js +++ b/src/time/minute.js @@ -1,5 +1,10 @@ -d3.time.minute = function(date) { - return new Date(~~(date / 6e4) * 6e4); -}; +d3.time.minute = d3_time_interval(function(date) { + return new d3_time(Math.floor(date / 6e4) * 6e4); +}, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 6e4); // DST breaks setMinutes +}, function(date) { + return date.getMinutes(); +}); -d3.time.minute.utc = d3.time.minute; \ No newline at end of file +d3.time.minutes = d3.time.minute.range; +d3.time.minutes.utc = d3.time.minute.utc.range; diff --git a/src/time/minutes.js b/src/time/minutes.js deleted file mode 100644 index 46d7c2b03dee4..0000000000000 --- a/src/time/minutes.js +++ /dev/null @@ -1,11 +0,0 @@ -d3.time.minutes = d3_time_range(d3.time.minute, d3_time_minutesStep, function(date) { - return date.getMinutes(); -}); - -d3.time.minutes.utc = d3_time_range(d3.time.minute, d3_time_minutesStep, function(date) { - return date.getUTCMinutes(); -}); - -function d3_time_minutesStep(date) { - date.setTime(date.getTime() + 6e4); // assumes no leap seconds -} diff --git a/src/time/month.js b/src/time/month.js index fe841b5621f82..2161c3af66ec8 100644 --- a/src/time/month.js +++ b/src/time/month.js @@ -1,7 +1,10 @@ -d3.time.month = function(date) { - return new Date(date.getFullYear(), date.getMonth(), 1); -}; +d3.time.month = d3_time_interval(function(date) { + return new d3_time(date.getFullYear(), date.getMonth(), 1); +}, function(date, offset) { + date.setMonth(date.getMonth() + offset); +}, function(date) { + return date.getMonth(); +}); -d3.time.month.utc = function(date) { - return new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1)); -}; +d3.time.months = d3.time.month.range; +d3.time.months.utc = d3.time.month.utc.range; diff --git a/src/time/months.js b/src/time/months.js deleted file mode 100644 index 9f2c315b0961d..0000000000000 --- a/src/time/months.js +++ /dev/null @@ -1,11 +0,0 @@ -d3.time.months = d3_time_range(d3.time.month, function(date) { - date.setMonth(date.getMonth() + 1); -}, function(date) { - return date.getMonth(); -}); - -d3.time.months.utc = d3_time_range(d3.time.month.utc, function(date) { - date.setUTCMonth(date.getUTCMonth() + 1); -}, function(date) { - return date.getUTCMonth(); -}); diff --git a/src/time/range.js b/src/time/range.js deleted file mode 100644 index dac749b9f53bd..0000000000000 --- a/src/time/range.js +++ /dev/null @@ -1,16 +0,0 @@ -function d3_time_range(floor, step, number) { - return function(t0, t1, dt) { - var time = floor(t0), times = []; - if (time < t0) step(time); - if (dt > 1) { - while (time < t1) { - var date = new Date(+time); - if (!(number(date) % dt)) times.push(date); - step(time); - } - } else { - while (time < t1) times.push(new Date(+time)), step(time); - } - return times; - }; -} diff --git a/src/time/scale-utc.js b/src/time/scale-utc.js index c10010875df01..89a57b9e61ce7 100644 --- a/src/time/scale-utc.js +++ b/src/time/scale-utc.js @@ -1,23 +1,6 @@ -var d3_time_scaleUTCMethods = [ - [d3.time.seconds.utc, 1], - [d3.time.seconds.utc, 5], - [d3.time.seconds.utc, 15], - [d3.time.seconds.utc, 30], - [d3.time.minutes.utc, 1], - [d3.time.minutes.utc, 5], - [d3.time.minutes.utc, 15], - [d3.time.minutes.utc, 30], - [d3.time.hours.utc, 1], - [d3.time.hours.utc, 3], - [d3.time.hours.utc, 6], - [d3.time.hours.utc, 12], - [d3.time.days.utc, 1], - [d3.time.days.utc, 2], - [d3.time.weeks.utc, 1], - [d3.time.months.utc, 1], - [d3.time.months.utc, 3], - [d3.time.years.utc, 1] -]; +var d3_time_scaleUTCMethods = d3_time_scaleLocalMethods.map(function(m) { + return [m[0].utc, m[1]]; +}); var d3_time_scaleUTCFormats = [ [d3.time.format.utc("%Y"), function(d) { return true; }], diff --git a/src/time/scale.js b/src/time/scale.js index a6ecda28f4d53..41e8f26bfcc1d 100644 --- a/src/time/scale.js +++ b/src/time/scale.js @@ -1,4 +1,3 @@ -// TODO nice function d3_time_scale(linear, methods, format) { function scale(x) { @@ -15,6 +14,11 @@ function d3_time_scale(linear, methods, format) { return scale; }; + scale.nice = function(m) { + var extent = d3_time_scaleExtent(scale.domain()); + return scale.domain([m.floor(extent[0]), m.ceil(extent[1])]); + }; + scale.ticks = function(m, k) { var extent = d3_time_scaleExtent(scale.domain()); if (typeof m !== "function") { @@ -26,7 +30,7 @@ function d3_time_scale(linear, methods, format) { if (Math.log(target / d3_time_scaleSteps[i - 1]) < Math.log(d3_time_scaleSteps[i] / target)) --i; m = methods[i]; k = m[1]; - m = m[0]; + m = m[0].range; } return m(extent[0], new Date(+extent[1] + 1), k); // inclusive upper bound }; @@ -96,24 +100,24 @@ var d3_time_scaleSteps = [ ]; var d3_time_scaleLocalMethods = [ - [d3.time.seconds, 1], - [d3.time.seconds, 5], - [d3.time.seconds, 15], - [d3.time.seconds, 30], - [d3.time.minutes, 1], - [d3.time.minutes, 5], - [d3.time.minutes, 15], - [d3.time.minutes, 30], - [d3.time.hours, 1], - [d3.time.hours, 3], - [d3.time.hours, 6], - [d3.time.hours, 12], - [d3.time.days, 1], - [d3.time.days, 2], - [d3.time.weeks, 1], - [d3.time.months, 1], - [d3.time.months, 3], - [d3.time.years, 1] + [d3.time.second, 1], + [d3.time.second, 5], + [d3.time.second, 15], + [d3.time.second, 30], + [d3.time.minute, 1], + [d3.time.minute, 5], + [d3.time.minute, 15], + [d3.time.minute, 30], + [d3.time.hour, 1], + [d3.time.hour, 3], + [d3.time.hour, 6], + [d3.time.hour, 12], + [d3.time.day, 1], + [d3.time.day, 2], + [d3.time.week, 1], + [d3.time.month, 1], + [d3.time.month, 3], + [d3.time.year, 1] ]; var d3_time_scaleLocalFormats = [ diff --git a/src/time/second.js b/src/time/second.js index 9feeac53b0f88..d9cdd65bee214 100644 --- a/src/time/second.js +++ b/src/time/second.js @@ -1,5 +1,10 @@ -d3.time.second = function(date) { - return new Date(~~(date / 1e3) * 1e3); -}; +d3.time.second = d3_time_interval(function(date) { + return new d3_time(Math.floor(date / 1e3) * 1e3); +}, function(date, offset) { + date.setTime(date.getTime() + Math.floor(offset) * 1e3); // DST breaks setSeconds +}, function(date) { + return date.getSeconds(); +}); -d3.time.second.utc = d3.time.second; +d3.time.seconds = d3.time.second.range; +d3.time.seconds.utc = d3.time.second.utc.range; diff --git a/src/time/seconds.js b/src/time/seconds.js deleted file mode 100644 index 584247bbc9019..0000000000000 --- a/src/time/seconds.js +++ /dev/null @@ -1,7 +0,0 @@ -d3.time.seconds = d3_time_range(d3.time.second, function(date) { - date.setTime(date.getTime() + 1e3); -}, function(date) { - return date.getSeconds(); -}); - -d3.time.seconds.utc = d3.time.seconds; diff --git a/src/time/time.js b/src/time/time.js index f9a2192149072..22d9f572d74e1 100644 --- a/src/time/time.js +++ b/src/time/time.js @@ -1,3 +1,32 @@ d3.time = {}; var d3_time = Date; + +function d3_time_utc() { + this._ = new Date(arguments.length > 1 + ? Date.UTC.apply(this, arguments) + : arguments[0]); +} + +d3_time_utc.prototype = { + getDate: function() { return this._.getUTCDate(); }, + getDay: function() { return this._.getUTCDay(); }, + getFullYear: function() { return this._.getUTCFullYear(); }, + getHours: function() { return this._.getUTCHours(); }, + getMilliseconds: function() { return this._.getUTCMilliseconds(); }, + getMinutes: function() { return this._.getUTCMinutes(); }, + getMonth: function() { return this._.getUTCMonth(); }, + getSeconds: function() { return this._.getUTCSeconds(); }, + getTime: function() { return this._.getTime(); }, + getTimezoneOffset: function() { return 0; }, + valueOf: function() { return this._.valueOf(); }, + setDate: function(x) { this._.setUTCDate(x); }, + setDay: function(x) { this._.setUTCDay(x); }, + setFullYear: function(x) { this._.setUTCFullYear(x); }, + setHours: function(x) { this._.setUTCHours(x); }, + setMilliseconds: function(x) { this._.setUTCMilliseconds(x); }, + setMinutes: function(x) { this._.setUTCMinutes(x); }, + setMonth: function(x) { this._.setUTCMonth(x); }, + setSeconds: function(x) { this._.setUTCSeconds(x); }, + setTime: function(x) { this._.setTime(x); } +}; diff --git a/src/time/week.js b/src/time/week.js index 2164ad09bee2a..4d3ddf835c067 100644 --- a/src/time/week.js +++ b/src/time/week.js @@ -1,9 +1,27 @@ -d3.time.week = function(date) { - (date = d3.time.day(date)).setDate(date.getDate() - date.getDay()); - return date; -}; - -d3.time.week.utc = function(date) { - (date = d3.time.day.utc(date)).setUTCDate(date.getUTCDate() - date.getUTCDay()); - return date; -}; +d3_time_weekdays.forEach(function(day, i) { + day = day.toLowerCase(); + i = 7 - i; + + var interval = d3.time[day] = d3_time_interval(function(date) { + (date = d3.time.day(date)).setDate(date.getDate() - (date.getDay() + i) % 7); + return date; + }, function(date, offset) { + date.setDate(date.getDate() + Math.floor(offset) * 7); + }, function(date) { + var day = d3.time.year(date).getDay(); + return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7) - (day !== i); + }); + + d3.time[day + "s"] = interval.range; + d3.time[day + "s"].utc = interval.utc.range; + + d3.time[day + "OfYear"] = function(date) { + var day = d3.time.year(date).getDay(); + return Math.floor((d3.time.dayOfYear(date) + (day + i) % 7) / 7); + }; +}); + +d3.time.week = d3.time.sunday; +d3.time.weeks = d3.time.sunday.range; +d3.time.weeks.utc = d3.time.sunday.utc.range; +d3.time.weekOfYear = d3.time.sundayOfYear; diff --git a/src/time/weeks.js b/src/time/weeks.js deleted file mode 100644 index 1da48e6ce95b7..0000000000000 --- a/src/time/weeks.js +++ /dev/null @@ -1,11 +0,0 @@ -d3.time.weeks = d3_time_range(d3.time.week, function(date) { - date.setDate(date.getDate() + 7); -}, function(date) { - return ~~((date - new Date(date.getFullYear(), 0, 1)) / 6048e5); -}); - -d3.time.weeks.utc = d3_time_range(d3.time.week.utc, function(date) { - date.setUTCDate(date.getUTCDate() + 7); -}, function(date) { - return ~~((date - Date.UTC(date.getUTCFullYear(), 0, 1)) / 6048e5); -}); diff --git a/src/time/year.js b/src/time/year.js index 013766826d018..400ea6f6a5521 100644 --- a/src/time/year.js +++ b/src/time/year.js @@ -1,7 +1,10 @@ -d3.time.year = function(date) { - return new Date(date.getFullYear(), 0, 1); -}; +d3.time.year = d3_time_interval(function(date) { + return new d3_time(date.getFullYear(), 0, 1); +}, function(date, offset) { + date.setFullYear(date.getFullYear() + offset); +}, function(date) { + return date.getFullYear(); +}); -d3.time.year.utc = function(date) { - return new Date(Date.UTC(date.getUTCFullYear(), 0, 1)); -}; +d3.time.years = d3.time.year.range; +d3.time.years.utc = d3.time.year.utc.range; diff --git a/src/time/years.js b/src/time/years.js deleted file mode 100644 index 495d872ec2b46..0000000000000 --- a/src/time/years.js +++ /dev/null @@ -1,11 +0,0 @@ -d3.time.years = d3_time_range(d3.time.year, function(date) { - date.setFullYear(date.getFullYear() + 1); -}, function(date) { - return date.getFullYear(); -}); - -d3.time.years.utc = d3_time_range(d3.time.year.utc, function(date) { - date.setUTCFullYear(date.getUTCFullYear() + 1); -}, function(date) { - return date.getUTCFullYear(); -}); diff --git a/test/core/bisect-test.js b/test/core/bisect-test.js index 6e9a771f10251..4a8a0cc121af3 100644 --- a/test/core/bisect-test.js +++ b/test/core/bisect-test.js @@ -99,4 +99,100 @@ suite.addBatch({ } }); +suite.addBatch({ + "bisector(key)": { + topic: function() { + return d3.bisector(function(d) { return d.key; }); + }, + "left": { + topic: function(bisector) { + return bisector.left; + }, + "finds the index of an exact match": function(bisect) { + var array = [{key: 1}, {key: 2}, {key: 3}]; + assert.equal(bisect(array, 1), 0); + assert.equal(bisect(array, 2), 1); + assert.equal(bisect(array, 3), 2); + }, + "finds the index of the first match": function(bisect) { + var array = [{key: 1}, {key: 2}, {key: 2}, {key: 3}]; + assert.equal(bisect(array, 1), 0); + assert.equal(bisect(array, 2), 1); + assert.equal(bisect(array, 3), 3); + }, + "finds the insertion point of a non-exact match": function(bisect) { + var array = [{key: 1}, {key: 2}, {key: 3}]; + assert.equal(bisect(array, 0.5), 0); + assert.equal(bisect(array, 1.5), 1); + assert.equal(bisect(array, 2.5), 2); + assert.equal(bisect(array, 3.5), 3); + }, + "observes the optional lower bound": function(bisect) { + var array = [{key: 1}, {key: 2}, {key: 3}, {key: 4}, {key: 5}]; + assert.equal(bisect(array, 0, 2), 2); + assert.equal(bisect(array, 1, 2), 2); + assert.equal(bisect(array, 2, 2), 2); + assert.equal(bisect(array, 3, 2), 2); + assert.equal(bisect(array, 4, 2), 3); + assert.equal(bisect(array, 5, 2), 4); + assert.equal(bisect(array, 6, 2), 5); + }, + "observes the optional bounds": function(bisect) { + var array = [{key: 1}, {key: 2}, {key: 3}, {key: 4}, {key: 5}]; + assert.equal(bisect(array, 0, 2, 3), 2); + assert.equal(bisect(array, 1, 2, 3), 2); + assert.equal(bisect(array, 2, 2, 3), 2); + assert.equal(bisect(array, 3, 2, 3), 2); + assert.equal(bisect(array, 4, 2, 3), 3); + assert.equal(bisect(array, 5, 2, 3), 3); + assert.equal(bisect(array, 6, 2, 3), 3); + } + }, + "right": { + topic: function(bisector) { + return bisector.right; + }, + "finds the index after an exact match": function(bisect) { + var array = [{key: 1}, {key: 2}, {key: 3}]; + assert.equal(bisect(array, 1), 1); + assert.equal(bisect(array, 2), 2); + assert.equal(bisect(array, 3), 3); + }, + "finds the index after the last match": function(bisect) { + var array = [{key: 1}, {key: 2}, {key: 2}, {key: 3}]; + assert.equal(bisect(array, 1), 1); + assert.equal(bisect(array, 2), 3); + assert.equal(bisect(array, 3), 4); + }, + "finds the insertion point of a non-exact match": function(bisect) { + var array = [{key: 1}, {key: 2}, {key: 3}]; + assert.equal(bisect(array, 0.5), 0); + assert.equal(bisect(array, 1.5), 1); + assert.equal(bisect(array, 2.5), 2); + assert.equal(bisect(array, 3.5), 3); + }, + "observes the optional lower bound": function(bisect) { + var array = [{key: 1}, {key: 2}, {key: 3}, {key: 4}, {key: 5}]; + assert.equal(bisect(array, 0, 2), 2); + assert.equal(bisect(array, 1, 2), 2); + assert.equal(bisect(array, 2, 2), 2); + assert.equal(bisect(array, 3, 2), 3); + assert.equal(bisect(array, 4, 2), 4); + assert.equal(bisect(array, 5, 2), 5); + assert.equal(bisect(array, 6, 2), 5); + }, + "observes the optional bounds": function(bisect) { + var array = [{key: 1}, {key: 2}, {key: 3}, {key: 4}, {key: 5}]; + assert.equal(bisect(array, 0, 2, 3), 2); + assert.equal(bisect(array, 1, 2, 3), 2); + assert.equal(bisect(array, 2, 2, 3), 2); + assert.equal(bisect(array, 3, 2, 3), 3); + assert.equal(bisect(array, 4, 2, 3), 3); + assert.equal(bisect(array, 5, 2, 3), 3); + assert.equal(bisect(array, 6, 2, 3), 3); + } + } + } +}); + suite.export(module); diff --git a/test/core/ease-test.js b/test/core/ease-test.js index 359b03a57beac..dd92b97a1fd14 100644 --- a/test/core/ease-test.js +++ b/test/core/ease-test.js @@ -50,6 +50,16 @@ suite.addBatch({ var e = ease("bounce"); assert.inDelta(e(.5), 0.765625, 1e-6); }, + "invalid eases and modes default to linear-in": function(ease) { + var e = ease("__proto__-__proto__"); + assert.equal(e(0), 0); + assert.equal(e(.5), .5); + assert.equal(e(1), 1); + var e = ease("hasOwnProperty-constructor"); + assert.equal(e(0), 0); + assert.equal(e(.5), .5); + assert.equal(e(1), 1); + }, "all easing functions return exactly 0 for t = 0": function(ease) { assert.equal(ease("linear")(0), 0); assert.equal(ease("poly", 2)(0), 0); diff --git a/test/core/interpolate-test.js b/test/core/interpolate-test.js index e683e5fcbff03..daaeaafe4bb29 100644 --- a/test/core/interpolate-test.js +++ b/test/core/interpolate-test.js @@ -26,6 +26,28 @@ suite.addBatch({ }, "interpolates objects": function(interpolate) { assert.deepEqual(interpolate({foo: 2}, {foo: 12})(.4), {foo: 6}); + }, + "may or may not interpolate between enumerable and non-enumerable properties": function(interpolate) { + var a = Object.create({}, {foo: {value: 1, enumerable: true}}), + b = Object.create({}, {foo: {value: 2, enumerable: false}}); + try { + assert.deepEqual(interpolate(a, b)(1), {}); + } catch (e) { + assert.deepEqual(interpolate(a, b)(1), {foo: 2}); + } + try { + assert.deepEqual(interpolate(b, a)(1), {}); + } catch (e) { + assert.deepEqual(interpolate(b, a)(1), {foo: 1}); + } + }, + "interpolates inherited properties of objects": function(interpolate) { + var a = Object.create({foo: 0}), + b = Object.create({foo: 2}); + assert.deepEqual(interpolate(a, b)(.5), {foo: 1}); + }, + "doesn't interpret properties in the default object's prototype chain as RGB": function(interpolate) { + assert.equal(interpolate("hasOwnProperty", "hasOwnProperty")(0), "hasOwnProperty"); } } }); diff --git a/test/core/map-test.js b/test/core/map-test.js new file mode 100644 index 0000000000000..aa729a6af08bb --- /dev/null +++ b/test/core/map-test.js @@ -0,0 +1,212 @@ +require("../env"); + +var vows = require("vows"), + assert = require("assert"); + +var suite = vows.describe("d3.map"); + +suite.addBatch({ + "constructor": { + "map() returns an empty map": function() { + var map = d3.map(); + assert.deepEqual(map.keys(), []); + }, + "map(null) returns an empty map": function() { + var map = d3.map(null); + assert.deepEqual(map.keys(), []); + }, + "map(object) copies enumerable keys": function() { + var map = d3.map({foo: 42}); + assert.isTrue(map.has("foo")); + assert.equal(map.get("foo"), 42); + var map = d3.map(Object.create(null, {foo: {value: 42, enumerable: true}})); + assert.isTrue(map.has("foo")); + assert.equal(map.get("foo"), 42); + }, + "map(object) copies inherited keys": function() { + function Foo() {} + Foo.prototype.foo = 42; + var map = d3.map(Object.create({foo: 42})); + assert.isTrue(map.has("foo")); + assert.equal(map.get("foo"), 42); + var map = d3.map(new Foo()); + assert.isTrue(map.has("foo")); + assert.equal(map.get("foo"), 42); + }, + "map(object) does not copy non-enumerable keys": function() { + var map = d3.map({__proto__: 42}); // because __proto__ isn't enumerable + assert.isFalse(map.has("__proto__")); + assert.isUndefined(map.get("__proto__")); + var map = d3.map(Object.create(null, {foo: {value: 42, enumerable: false}})); + assert.isFalse(map.has("foo")); + assert.isUndefined(map.get("foo")); + } + }, + "forEach": { + "empty maps have an empty keys array": function() { + var map = d3.map(); + assert.deepEqual(map.entries(), []); + map.set("foo", "bar"); + assert.deepEqual(map.entries(), [{key: "foo", value: "bar"}]); + map.remove("foo"); + assert.deepEqual(map.entries(), []); + }, + "keys are returned in arbitrary order": function() { + var map = d3.map({foo: 1, bar: "42"}); + assert.deepEqual(map.entries().sort(ascendingByKey), [{key: "bar", value: "42"}, {key: "foo", value: 1}]); + var map = d3.map({bar: "42", foo: 1}); + assert.deepEqual(map.entries().sort(ascendingByKey), [{key: "bar", value: "42"}, {key: "foo", value: 1}]); + }, + "observes changes via set and remove": function() { + var map = d3.map({foo: 1, bar: "42"}); + assert.deepEqual(map.entries().sort(ascendingByKey), [{key: "bar", value: "42"}, {key: "foo", value: 1}]); + map.remove("foo"); + assert.deepEqual(map.entries(), [{key: "bar", value: "42"}]); + map.set("bar", "bar"); + assert.deepEqual(map.entries(), [{key: "bar", value: "bar"}]); + map.set("foo", "foo"); + assert.deepEqual(map.entries().sort(ascendingByKey), [{key: "bar", value: "bar"}, {key: "foo", value: "foo"}]); + map.remove("bar"); + assert.deepEqual(map.entries(), [{key: "foo", value: "foo"}]); + map.remove("foo"); + assert.deepEqual(map.entries(), []); + map.remove("foo"); + assert.deepEqual(map.entries(), []); + } + }, + "keys": { + "returns an array of string keys": function() { + var map = d3.map({foo: 1, bar: "42"}); + assert.deepEqual(map.keys().sort(), ["bar", "foo"]); + } + }, + "values": { + "returns an array of arbitrary values": function() { + var map = d3.map({foo: 1, bar: "42"}); + assert.deepEqual(map.values().sort(), [1, "42"]); + } + }, + "entries": { + "returns an array of key-value objects": function() { + var map = d3.map({foo: 1, bar: "42"}); + assert.deepEqual(map.entries().sort(ascendingByKey), [{key: "bar", value: "42"}, {key: "foo", value: 1}]); + } + }, + "has": { + "empty maps do not have object built-ins": function() { + var map = d3.map(); + assert.isFalse(map.has("__proto__")); + assert.isFalse(map.has("hasOwnProperty")); + }, + "can has keys using built-in names": function() { + var map = d3.map(); + map.set("__proto__", 42); + assert.isTrue(map.has("__proto__")); + }, + "can has keys with null or undefined properties": function() { + var map = d3.map(); + map.set("", ""); + map.set("null", null); + map.set("undefined", undefined); + assert.isTrue(map.has("")); + assert.isTrue(map.has("null")); + assert.isTrue(map.has("undefined")); + }, + "coerces keys to strings": function() { + var map = d3.map({"42": "foo", "null": 1, "undefined": 2}); + assert.isTrue(map.has(42)); + assert.isTrue(map.has(null)); + assert.isTrue(map.has(undefined)); + }, + "returns the latest value": function() { + var map = d3.map({foo: 42}); + assert.isTrue(map.has("foo")); + map.set("foo", 43); + assert.isTrue(map.has("foo")); + map.remove("foo"); + assert.isFalse(map.has("foo")); + map.set("foo", "bar"); + assert.isTrue(map.has("foo")); + }, + "returns undefined for missing keys": function() { + var map = d3.map({foo: 42}); + assert.isFalse(map.has("bar")); + } + }, + "get": { + "empty maps do not have object built-ins": function() { + var map = d3.map(); + assert.isUndefined(map.get("__proto__")); + assert.isUndefined(map.get("hasOwnProperty")); + }, + "can get keys using built-in names": function() { + var map = d3.map(); + map.set("__proto__", 42); + assert.equal(map.get("__proto__"), 42); + }, + "coerces keys to strings": function() { + var map = d3.map({"42": 1, "null": 2, "undefined": 3}); + assert.equal(map.get(42), 1); + assert.equal(map.get(null), 2); + assert.equal(map.get(undefined), 3); + }, + "returns the latest value": function() { + var map = d3.map({foo: 42}); + assert.equal(map.get("foo"), 42); + map.set("foo", 43); + assert.equal(map.get("foo"), 43); + map.remove("foo"); + assert.isUndefined(map.get("foo")); + map.set("foo", "bar"); + assert.equal(map.get("foo"), "bar"); + }, + "returns undefined for missing keys": function() { + var map = d3.map({foo: 42}); + assert.isUndefined(map.get("bar")); + } + }, + "set": { + "returns the set value": function() { + var map = d3.map(); + assert.equal(map.set("foo", 42), 42); + }, + "can set keys using built-in names": function() { + var map = d3.map(); + map.set("__proto__", 42); + assert.equal(map.get("__proto__"), 42); + }, + "coerces keys to strings": function() { + var map = d3.map(); + map.set(42, 1); + assert.equal(map.get(42), 1); + map.set(null, 2); + assert.equal(map.get(null), 2); + map.set(undefined, 3); + assert.equal(map.get(undefined), 3); + assert.deepEqual(map.keys().sort(), ["42", "null", "undefined"]); + }, + "can replace values": function() { + var map = d3.map({foo: 42}); + assert.equal(map.get("foo"), 42); + map.set("foo", 43); + assert.equal(map.get("foo"), 43); + map.set("foo", "bar"); + assert.equal(map.get("foo"), "bar"); + }, + "can set null, undefined or empty string values": function() { + var map = d3.map(); + map.set("", ""); + map.set("null", null); + map.set("undefined", undefined); + assert.equal(map.get(""), ""); + assert.isNull(map.get("null")); + assert.isUndefined(map.get("undefined")); + } + } +}); + +function ascendingByKey(a, b) { + return d3.ascending(a.key, b.key); +} + +suite.export(module); diff --git a/test/core/nest-test.js b/test/core/nest-test.js index b445be9139553..59b8da9f8f0ad 100644 --- a/test/core/nest-test.js +++ b/test/core/nest-test.js @@ -228,6 +228,12 @@ suite.addBatch({ "if no keys are specified, the input array is returned": function(nest) { var array = [new Object()]; assert.strictEqual(nest().map(array), array); + }, + "handles keys that are built-in prototype properties": function(nest) { + var keys = nest() + .key(String) + .map(["hasOwnProperty"]); + assert.deepEqual(keys, {hasOwnProperty: ["hasOwnProperty"]}); } } }); diff --git a/test/core/ns-test.js b/test/core/ns-test.js index 3f1b002c20df2..653990aba7df9 100644 --- a/test/core/ns-test.js +++ b/test/core/ns-test.js @@ -41,15 +41,16 @@ suite.addBatch({ assert.equal(name.space, "http://www.w3.org/2000/svg"); assert.equal(name.local, "path"); }, - "unknown qualified name returns undefined and local": function() { - var name = d3.ns.qualify("foo:bar"); - assert.isUndefined(name.space); - assert.equal(name.local, "bar"); + "unknown qualified name returns name": function() { + assert.equal(d3.ns.qualify("foo:bar"), "bar"); }, "known local name returns space and local": function() { var name = d3.ns.qualify("svg"); assert.equal(name.space, "http://www.w3.org/2000/svg"); assert.equal(name.local, "svg"); + }, + "names that collide with built-ins are ignored": function(qualify) { + assert.equal(qualify("hasOwnProperty:test"), "test"); } } }); diff --git a/test/core/rgb-test.js b/test/core/rgb-test.js index 53ebdd0b0c65c..dbc32c2478852 100644 --- a/test/core/rgb-test.js +++ b/test/core/rgb-test.js @@ -13,6 +13,11 @@ suite.addBatch({ "floors channel values": function(rgb) { assert.rgbEqual(rgb(1.2, 2.6, 42.9), 1, 2, 42); }, + "defaults to black for invalid inputs": function(rgb) { + assert.rgbEqual(rgb("invalid"), 0, 0, 0); + assert.rgbEqual(rgb("hasOwnProperty"), 0, 0, 0); + assert.rgbEqual(rgb("__proto__"), 0, 0, 0); + }, "does not clamp channel values": function(rgb) { assert.rgbEqual(rgb(-10, -20, -30), -10, -20, -30); assert.rgbEqual(rgb(300, 400, 500), 300, 400, 500); diff --git a/test/core/selection-data-test.js b/test/core/selection-data-test.js index c0732f8035754..2f0efd725d1a5 100644 --- a/test/core/selection-data-test.js +++ b/test/core/selection-data-test.js @@ -28,6 +28,16 @@ suite.addBatch({ }, "returns a new selection": function(body) { assert.isFalse(body.data([1]) === body); + }, + "with no arguments, returns an array of data": function(body) { + var data = new Object(); + body.data([data]); + assert.deepEqual(body.data(), [data]); + assert.strictEqual(body.data()[0], data); + }, + "throws an error if data is null or undefined": function(body) { + assert.throws(function() { body.data(null); }, Error); + assert.throws(function() { body.data(function() {}); }, Error); } } }); @@ -58,6 +68,25 @@ suite.addBatch({ }, "returns a new selection": function(div) { assert.isFalse(div.data([0, 1]) === div); + }, + "throws an error if data is null or undefined": function(div) { + assert.throws(function() { div.data(null); }, Error); + assert.throws(function() { div.data(function() {}); }, Error); + }, + "with no arguments, returns an array of data": function(div) { + var a = new Object(), b = new Object(), actual = []; + div[0][0].__data__ = a; + div[0][1].__data__ = b; + assert.deepEqual(div.data(), [a, b]); + }, + "with no arguments, returned array has undefined for null nodes": function(div) { + var b = new Object(), actual = []; + div[0][0] = null; + div[0][1].__data__ = b; + var data = div.data(); + assert.isUndefined(data[0]); + assert.strictEqual(data[1], b); + assert.equal(data.length, 2); } } }); @@ -160,6 +189,17 @@ suite.addBatch({ assert.domNull(exit[0][1]); assert.domEqual(exit[1][0], span[1][0]); assert.domNull(exit[1][1]); + }, + "handles keys that are in the default object's prototype chain": function(span) { + // This also applies to the non-standard "watch" and "unwatch" in Mozilla Firefox. + var update = span.data(["hasOwnProperty", "isPrototypeOf", "toLocaleString", "toString", "valueOf"], String); + assert.domNull(update[0][0]); + assert.domNull(update[0][1]); + assert.domNull(update[0][2]); + assert.domNull(update[0][3]); + assert.domNull(update[0][4]); + // This throws an error if Object.hasOwnProperty isn't used. + span.data([0], function() { return "hasOwnProperty"; }); } } }); diff --git a/test/core/selection-datum-test.js b/test/core/selection-datum-test.js new file mode 100644 index 0000000000000..610f112a68897 --- /dev/null +++ b/test/core/selection-datum-test.js @@ -0,0 +1,82 @@ +require("../env"); + +var vows = require("vows"), + assert = require("assert"); + +var suite = vows.describe("selection.datum"); + +suite.addBatch({ + "select(body)": { + topic: function() { + return d3.select("body").html(""); + }, + "updates the data according to the specified function": function(body) { + body.data([42]).datum(function(d, i) { return d + i; }); + assert.equal(document.body.__data__, 42); + }, + "updates the data to the specified constant": function(body) { + body.datum(43); + assert.equal(document.body.__data__, 43); + }, + "deletes the data if the function returns null": function(body) { + body.data([42]).datum(function() { return null; }); + assert.isFalse("__data__" in document.body); + }, + "deletes the data if the constant is null": function(body) { + body.data([42]).datum(null); + assert.isFalse("__data__" in document.body); + }, + "returns the current selection": function(body) { + assert.isTrue(body.datum(function() { return 1; }) === body); + assert.isTrue(body.datum(2) === body); + }, + "with no arguments, returns the first node's datum": function(body) { + body.data([42]); + assert.equal(body.datum(), 42); + } + } +}); + +suite.addBatch({ + "selectAll(div)": { + topic: function() { + return d3.select("body").html("").selectAll("div").data(d3.range(2)).enter().append("div"); + }, + "updates the data according to the specified function": function(div) { + div.data([42, 43]).datum(function(d, i) { return d + i; }); + assert.equal(div[0][0].__data__, 42); + assert.equal(div[0][1].__data__, 44); + }, + "updates the data to the specified constant": function(div) { + div.datum(44); + assert.equal(div[0][0].__data__, 44); + assert.equal(div[0][1].__data__, 44); + }, + "deletes the data if the function returns null": function(div) { + div.datum(function() { return null; }); + assert.isFalse("__data__" in div[0][0]); + assert.isFalse("__data__" in div[0][1]); + }, + "deletes the data if the constant is null": function(div) { + div.datum(null); + assert.isFalse("__data__" in div[0][0]); + assert.isFalse("__data__" in div[0][1]); + }, + "returns the current selection": function(div) { + assert.isTrue(div.datum(function() { return 1; }) === div); + assert.isTrue(div.datum(2) === div); + }, + "ignores null nodes": function(div) { + var some = d3.selectAll("div").data([42, 43]); + some[0][1] = null; + some.datum(function() { return 1; }); + assert.equal(div[0][0].__data__, 1); + assert.equal(div[0][1].__data__, 43); + some.datum(2); + assert.equal(div[0][0].__data__, 2); + assert.equal(div[0][1].__data__, 43); + } + } +}); + +suite.export(module); diff --git a/test/core/selection-enter-test.js b/test/core/selection-enter-test.js new file mode 100644 index 0000000000000..257583501b472 --- /dev/null +++ b/test/core/selection-enter-test.js @@ -0,0 +1,25 @@ +require("../env"); + +var vows = require("vows"), + assert = require("assert"); + +var suite = vows.describe("selection.enter"); + +suite.addBatch({ + "selectAll(div)": { + topic: function() { + return d3.select("body").html("").selectAll("div").data(d3.range(2)).enter(); + }, + "is an instanceof d3.selection.enter": function(enter) { + assert.instanceOf(enter, d3.selection.enter); + }, + "selection prototype can be extended": function(enter) { + d3.selection.enter.prototype.foo = function() { return this.append("foo"); }; + var selection = enter.foo(); + assert.equal(document.body.innerHTML, ""); + delete d3.selection.enter.prototype.foo; + } + } +}); + +suite.export(module); diff --git a/test/core/selection-map-test.js b/test/core/selection-map-test.js deleted file mode 100644 index dd7610dbc0df3..0000000000000 --- a/test/core/selection-map-test.js +++ /dev/null @@ -1,46 +0,0 @@ -require("../env"); - -var vows = require("vows"), - assert = require("assert"); - -var suite = vows.describe("selection.map"); - -suite.addBatch({ - "select(body)": { - topic: function() { - return d3.select("body").html(""); - }, - "updates the data according to the map function": function(body) { - body.data([42]).map(function(d, i) { return d + i; }); - assert.equal(document.body.__data__, 42); - }, - "returns the current selection": function(body) { - assert.isTrue(body.map(function() { return 1; }) === body); - } - } -}); - -suite.addBatch({ - "selectAll(div)": { - topic: function() { - return d3.select("body").html("").selectAll("div").data(d3.range(2)).enter().append("div"); - }, - "updates the data according to the map function": function(div) { - div.data([42, 43]).map(function(d, i) { return d + i; }); - assert.equal(div[0][0].__data__, 42); - assert.equal(div[0][1].__data__, 44); - }, - "returns the current selection": function(div) { - assert.isTrue(div.map(function() { return 1; }) === div); - }, - "ignores null nodes": function(div) { - var some = d3.selectAll("div").data([42, 43]); - some[0][1] = null; - some.map(function() { return 1; }); - assert.equal(div[0][0].__data__, 1); - assert.equal(div[0][1].__data__, 43); - } - } -}); - -suite.export(module); diff --git a/test/core/selection-on-test.js b/test/core/selection-on-test.js index 2d7844615ba85..242590cd14c27 100644 --- a/test/core/selection-on-test.js +++ b/test/core/selection-on-test.js @@ -30,7 +30,18 @@ suite.addBatch({ form.on("submit", null); form.append("input").attr("type", "submit").node().click(); assert.equal(fail, 0); + assert.isUndefined(form.on("submit")); }, + /* Regrettably, JSDOM ignores the capture flag, so we can't test this yet… + "removing a listener doesn't require the capture flag": function(body) { + var form = body.append("form"), fail = 0; + form.on("submit", function() { ++fail; }, true); + form.on("submit", null); + form.append("input").attr("type", "submit").node().click(); + assert.equal(fail, 0); + assert.isUndefined(form.on("submit")); + }, + */ "ignores removal of non-matching event listener": function(body) { body.append("form").on("submit", null); }, @@ -42,8 +53,7 @@ suite.addBatch({ assert.equal(foo, 1); assert.equal(bar, 1); }, - /* - Not really sure how to test this one… + /* Not really sure how to test this one… "observes the specified capture flag": function(body) { }, */ diff --git a/test/core/selection-test.js b/test/core/selection-test.js index 72b203db71aae..5a747e488f0c0 100644 --- a/test/core/selection-test.js +++ b/test/core/selection-test.js @@ -16,11 +16,11 @@ suite.addBatch({ assert.equal(selection[0][0], document); }, "is an instanceof d3.selection": function(selection) { - assert.isTrue(selection instanceof d3.selection); + assert.instanceOf(selection, d3.selection); }, "subselections are also instanceof d3.selection": function(selection) { - assert.isTrue(selection.select("body") instanceof d3.selection); - assert.isTrue(selection.selectAll("body") instanceof d3.selection); + assert.instanceOf(selection.select("body"), d3.selection); + assert.instanceOf(selection.selectAll("body"), d3.selection); }, "selection prototype can be extended": function(selection) { d3.selection.prototype.foo = function(v) { return this.attr("foo", v); }; diff --git a/test/core/transition-test-delay.js b/test/core/transition-test-delay.js index a1729c7ed2077..4f244e3672ac7 100644 --- a/test/core/transition-test-delay.js +++ b/test/core/transition-test-delay.js @@ -22,6 +22,24 @@ module.exports = { assert.strictEqual(t[0][0].delay, 250); assert.strictEqual(t[0][1].delay, 250); }, + "can specify delay as a negative number": function(selection) { + var t = selection.transition().delay(-250); + assert.strictEqual(t[0][0].delay, -250); + assert.strictEqual(t[0][1].delay, -250); + }, + "NaN delays are treated as 0ms": function(selection) { + var t = selection.transition().delay(NaN); + assert.strictEqual(t[0][0].delay, 0); + assert.strictEqual(t[0][1].delay, 0); + }, + "floating-point durations are floored to integers": function(selection) { + var t = selection.transition().delay(14.6); + assert.strictEqual(t[0][0].delay, 14); + assert.strictEqual(t[0][1].delay, 14); + var t = selection.transition().delay("16.99"); + assert.strictEqual(t[0][0].delay, 16); + assert.strictEqual(t[0][1].delay, 16); + }, "can specify delay as a function": function(selection) { var dd = [], ii = [], tt = [], t = selection.transition().delay(f); function f(d, i) { dd.push(d); ii.push(i); tt.push(this); return i * 20; } diff --git a/test/core/transition-test-duration.js b/test/core/transition-test-duration.js index 2d123b2f7e3ff..bce5185653a6a 100644 --- a/test/core/transition-test-duration.js +++ b/test/core/transition-test-duration.js @@ -22,11 +22,33 @@ module.exports = { assert.strictEqual(t[0][0].duration, 50); assert.strictEqual(t[0][1].duration, 50); }, + "NaN, zero, or negative durations are treated as 1ms": function(selection) { + var t = selection.transition().duration(NaN); + assert.strictEqual(t[0][0].duration, 1); + assert.strictEqual(t[0][1].duration, 1); + t.duration(0); + assert.strictEqual(t[0][0].duration, 1); + assert.strictEqual(t[0][1].duration, 1); + t.duration(-10); + assert.strictEqual(t[0][0].duration, 1); + assert.strictEqual(t[0][1].duration, 1); + t.duration(-Infinity); + assert.strictEqual(t[0][0].duration, 1); + assert.strictEqual(t[0][1].duration, 1); + }, + "floating-point durations are floored to integers": function(selection) { + var t = selection.transition().duration(14.6); + assert.strictEqual(t[0][0].duration, 14); + assert.strictEqual(t[0][1].duration, 14); + var t = selection.transition().duration("16.99"); + assert.strictEqual(t[0][0].duration, 16); + assert.strictEqual(t[0][1].duration, 16); + }, "can specify duration as a function": function(selection) { var dd = [], ii = [], tt = [], t = selection.transition().duration(f); - function f(d, i) { dd.push(d); ii.push(i); tt.push(this); return i * 20; } - assert.strictEqual(t[0][0].duration, 0); - assert.strictEqual(t[0][1].duration, 20); + function f(d, i) { dd.push(d); ii.push(i); tt.push(this); return i * 20 + 10; } + assert.strictEqual(t[0][0].duration, 10); + assert.strictEqual(t[0][1].duration, 30); assert.deepEqual(dd, ["foo", "bar"], "expected data, got {actual}"); assert.deepEqual(ii, [0, 1], "expected index, got {actual}"); assert.domEqual(tt[0], t[0][0].node, "expected this, got {actual}"); diff --git a/test/geo/path-test.js b/test/geo/path-test.js index ab24a6a1466c2..0bf84f015a782 100644 --- a/test/geo/path-test.js +++ b/test/geo/path-test.js @@ -16,6 +16,15 @@ suite.addBatch({ coordinates: [[[-63.03, 18.02], [-63.14, 18.06], [-63.01, 18.07], [-63.03, 18.02]]] }, }), "M984.5652086349427,468.99159422596244L981.8396467935554,467.9114977057422L985.0785139575695,467.688661596079Z"); + }, + "bogus type name": function(path) { + assert.isNull(path({ + type: "Feature", + geometry: { + type: "__proto__", + coordinates: [[[-63.03, 18.02], [-63.14, 18.06], [-63.01, 18.07], [-63.03, 18.02]]] + }, + })); } } }); diff --git a/test/scale/identity-test.js b/test/scale/identity-test.js new file mode 100644 index 0000000000000..866cba14b3f8d --- /dev/null +++ b/test/scale/identity-test.js @@ -0,0 +1,166 @@ +require("../env"); + +var vows = require("vows"), + assert = require("assert"); + +var suite = vows.describe("d3.scale.identity"); + +suite.addBatch({ + "identity": { + topic: function() { + return d3.scale.identity; + }, + + "domain and range": { + "are identical": function(identity) { + var x = identity(); + assert.strictEqual(x.domain, x.range); + assert.strictEqual(x.domain(), x.range()); + var x = identity().domain([-10, 0, 100]); + assert.deepEqual(x.range(), [-10, 0, 100]); + var x = identity().range([-10, 0, 100]); + assert.deepEqual(x.domain(), [-10, 0, 100]); + }, + "default to [0, 1]": function(identity) { + var x = identity(); + assert.deepEqual(x.domain(), [0, 1]); + assert.deepEqual(x.range(), [0, 1]); + assert.strictEqual(x(.5), .5); + }, + "coerce values to numbers": function(identity) { + var x = identity().domain([new Date(1990, 0, 1), new Date(1991, 0, 1)]); + assert.typeOf(x.domain()[0], "number"); + assert.typeOf(x.domain()[1], "number"); + assert.strictEqual(x.domain()[0], +new Date(1990, 0, 1)); + assert.strictEqual(x.domain()[1], +new Date(1991, 0, 1)); + assert.typeOf(x(new Date(1989, 09, 20)), "number"); + assert.strictEqual(x(new Date(1989, 09, 20)), +new Date(1989, 09, 20)); + var x = identity().domain(["0", "1"]); + assert.typeOf(x.domain()[0], "number"); + assert.typeOf(x.domain()[1], "number"); + assert.strictEqual(x(.5), .5); + var x = identity().domain([new Number(0), new Number(1)]); + assert.typeOf(x.domain()[0], "number"); + assert.typeOf(x.domain()[1], "number"); + assert.strictEqual(x(.5), .5); + + var x = identity().range([new Date(1990, 0, 1), new Date(1991, 0, 1)]); + assert.typeOf(x.range()[0], "number"); + assert.typeOf(x.range()[1], "number"); + assert.strictEqual(x.range()[0], +new Date(1990, 0, 1)); + assert.strictEqual(x.range()[1], +new Date(1991, 0, 1)); + assert.typeOf(x(new Date(1989, 09, 20)), "number"); + assert.strictEqual(x(new Date(1989, 09, 20)), +new Date(1989, 09, 20)); + var x = identity().range(["0", "1"]); + assert.typeOf(x.range()[0], "number"); + assert.typeOf(x.range()[1], "number"); + assert.strictEqual(x(.5), .5); + var x = identity().range([new Number(0), new Number(1)]); + assert.typeOf(x.range()[0], "number"); + assert.typeOf(x.range()[1], "number"); + assert.strictEqual(x(.5), .5); + }, + "can specify a polyidentity domain and range": function(identity) { + var x = identity().domain([-10, 0, 100]); + assert.deepEqual(x.domain(), [-10, 0, 100]); + assert.strictEqual(x(-5), -5); + assert.strictEqual(x(50), 50); + assert.strictEqual(x(75), 75); + + var x = identity().range([-10, 0, 100]); + assert.deepEqual(x.range(), [-10, 0, 100]); + assert.strictEqual(x(-5), -5); + assert.strictEqual(x(50), 50); + assert.strictEqual(x(75), 75); + }, + "do not affect the identity function": function(identity) { + var x = identity().domain([Infinity, NaN]); + assert.strictEqual(x(42), 42); + assert.strictEqual(x.invert(-42), -42); + } + }, + + "is the identity function": function(identity) { + var x = identity().domain([1, 2]); + assert.strictEqual(x(.5), .5); + assert.strictEqual(x(1), 1); + assert.strictEqual(x(1.5), 1.5); + assert.strictEqual(x(2), 2); + assert.strictEqual(x(2.5), 2.5); + }, + "coerces input to a number": function(identity) { + var x = identity().domain([1, 2]); + assert.strictEqual(x("2"), 2); + }, + + "invert": { + "is the identity function": function(identity) { + var x = identity().domain([1, 2]); + assert.strictEqual(x.invert(.5), .5); + assert.strictEqual(x.invert(1), 1); + assert.strictEqual(x.invert(1.5), 1.5); + assert.strictEqual(x.invert(2), 2); + assert.strictEqual(x.invert(2.5), 2.5); + }, + "coerces range value to numbers": function(identity) { + var x = identity().range(["0", "2"]); + assert.strictEqual(x.invert("1"), 1); + var x = identity().range([new Date(1990, 0, 1), new Date(1991, 0, 1)]); + assert.strictEqual(x.invert(new Date(1990, 6, 2, 13)), +new Date(1990, 6, 2, 13)); + var x = identity().range(["#000", "#fff"]); + assert.isNaN(x.invert("#999")); + }, + "coerces input to a number": function(identity) { + var x = identity().domain([1, 2]); + assert.strictEqual(x.invert("2"), 2); + } + }, + + "ticks": { + "generates ticks of varying degree": function(identity) { + var x = identity(); + assert.deepEqual(x.ticks(1).map(x.tickFormat(1)), [0, 1]); + assert.deepEqual(x.ticks(2).map(x.tickFormat(2)), [0, .5, 1]); + assert.deepEqual(x.ticks(5).map(x.tickFormat(5)), [0, .2, .4, .6, .8, 1]); + assert.deepEqual(x.ticks(10).map(x.tickFormat(10)), [0, .1, .2, .3, .4, .5, .6, .7, .8, .9, 1]); + var x = identity().domain([1, 0]); + assert.deepEqual(x.ticks(1).map(x.tickFormat(1)), [0, 1]); + assert.deepEqual(x.ticks(2).map(x.tickFormat(2)), [0, .5, 1]); + assert.deepEqual(x.ticks(5).map(x.tickFormat(5)), [0, .2, .4, .6, .8, 1]); + assert.deepEqual(x.ticks(10).map(x.tickFormat(10)), [0, .1, .2, .3, .4, .5, .6, .7, .8, .9, 1]); + }, + "formats ticks with the appropriate precision": function(identity) { + var x = identity().domain([.123456789, 1.23456789]); + assert.strictEqual(x.tickFormat(1)(x.ticks(1)[0]), "1"); + assert.strictEqual(x.tickFormat(2)(x.ticks(2)[0]), "0.5"); + assert.strictEqual(x.tickFormat(4)(x.ticks(4)[0]), "0.2"); + assert.strictEqual(x.tickFormat(8)(x.ticks(8)[0]), "0.2"); + assert.strictEqual(x.tickFormat(16)(x.ticks(16)[0]), "0.2"); + assert.strictEqual(x.tickFormat(32)(x.ticks(32)[0]), "0.15"); + assert.strictEqual(x.tickFormat(64)(x.ticks(64)[0]), "0.14"); + assert.strictEqual(x.tickFormat(128)(x.ticks(128)[0]), "0.13"); + assert.strictEqual(x.tickFormat(256)(x.ticks(256)[0]), "0.125"); + } + }, + + "copy": { + "changes to the domain or range are isolated": function(identity) { + var x = identity(), y = x.copy(); + x.domain([1, 2]); + assert.deepEqual(y.domain(), [0, 1]); + y.domain([2, 3]); + assert.deepEqual(x.domain(), [1, 2]); + assert.deepEqual(y.domain(), [2, 3]); + + var x = identity(), y = x.copy(); + x.range([1, 2]); + assert.deepEqual(y.range(), [0, 1]); + y.range([2, 3]); + assert.deepEqual(x.range(), [1, 2]); + assert.deepEqual(y.range(), [2, 3]); + } + } + } +}); + +suite.export(module); diff --git a/test/scale/ordinal-test.js b/test/scale/ordinal-test.js index 7b43445c3db15..e7836d892cc45 100644 --- a/test/scale/ordinal-test.js +++ b/test/scale/ordinal-test.js @@ -61,6 +61,12 @@ suite.addBatch({ assert.deepEqual(x.domain(), [0, 1]); assert.typeOf(x.domain()[0], "number"); assert.typeOf(x.domain()[1], "number"); + }, + "does not barf on object built-ins": function(ordinal) { + var x = ordinal().domain(["__proto__", "hasOwnProperty"]).range([42, 43]); + assert.equal(x("__proto__"), 42); + assert.equal(x("hasOwnProperty"), 43); + assert.deepEqual(x.domain(), ["__proto__", "hasOwnProperty"]); } }, @@ -113,6 +119,17 @@ suite.addBatch({ var x = ordinal().domain(["a", "b", "c"]).rangePoints([0, 120], 2); assert.deepEqual(x.range(), [30, 60, 90]); assert.equal(x.rangeBand(), 0); + }, + "can be set to a descending range": function(ordinal) { + var x = ordinal().domain(["a", "b", "c"]).rangePoints([120, 0]); + assert.deepEqual(x.range(), [120, 60,0]); + assert.equal(x.rangeBand(), 0); + var x = ordinal().domain(["a", "b", "c"]).rangePoints([120, 0], 1); + assert.deepEqual(x.range(), [100, 60, 20]); + assert.equal(x.rangeBand(), 0); + var x = ordinal().domain(["a", "b", "c"]).rangePoints([120, 0], 2); + assert.deepEqual(x.range(), [90, 60, 30]); + assert.equal(x.rangeBand(), 0); } }, @@ -132,6 +149,14 @@ suite.addBatch({ x.domain(["a", "b", "c", "d"]); assert.deepEqual(x.range(), [0, 25, 50, 75]); assert.equal(x.rangeBand(), 25); + }, + "can be set to a descending range": function(ordinal) { + var x = ordinal().domain(["a", "b", "c"]).rangeBands([120, 0]); + assert.deepEqual(x.range(), [80, 40, 0]); + assert.equal(x.rangeBand(), 40); + var x = ordinal().domain(["a", "b", "c"]).rangeBands([120, 0], .2); + assert.deepEqual(x.range(), [82.5, 45, 7.5]); + assert.equal(x.rangeBand(), 30); } }, @@ -143,6 +168,14 @@ suite.addBatch({ var x = ordinal().domain(["a", "b", "c"]).rangeRoundBands([0, 100], .2); assert.deepEqual(x.range(), [7, 38, 69]); assert.equal(x.rangeBand(), 25); + }, + "can be set to a descending range": function(ordinal) { + var x = ordinal().domain(["a", "b", "c"]).rangeRoundBands([100, 0]); + assert.deepEqual(x.range(), [67, 34, 1]); + assert.equal(x.rangeBand(), 33); + var x = ordinal().domain(["a", "b", "c"]).rangeRoundBands([100, 0], .2); + assert.deepEqual(x.range(), [69, 38, 7]); + assert.equal(x.rangeBand(), 25); } }, @@ -156,6 +189,10 @@ suite.addBatch({ assert.deepEqual(x.rangeExtent(), [0, 100]); var x = ordinal().domain(["a", "b", "c"]).range([0, 20, 100]); assert.deepEqual(x.rangeExtent(), [0, 100]); + }, + "can handle descending ranges": function(ordinal) { + var x = ordinal().domain(["a", "b", "c"]).rangeBands([100, 0]); + assert.deepEqual(x.rangeExtent(), [0, 100]); } }, diff --git a/test/svg/area-test.js b/test/svg/area-test.js index fc6582748620f..d42d9daac4385 100644 --- a/test/svg/area-test.js +++ b/test/svg/area-test.js @@ -138,6 +138,9 @@ suite.addBatch({ assert.pathEqual(a([[0, 0], [1, 1]]), "M0,0V1H1L1,0H0V0Z"); assert.equal(a.interpolate(), "step-before"); }, + "invalid interpolates fallback to linear": function(area) { + assert.equal(area().interpolate("__proto__").interpolate(), "linear"); + }, "tension defaults to .7": function(area) { assert.equal(area().tension(), .7); diff --git a/test/svg/axis-test.js b/test/svg/axis-test.js index bd6a96ed01295..05298b6e83292 100644 --- a/test/svg/axis-test.js +++ b/test/svg/axis-test.js @@ -188,7 +188,7 @@ suite.addBatch({ }, "ticks": { - "defaults to 10": function(axis) { + "defaults to [10]": function(axis) { var a = axis(); assert.deepEqual(a.ticks(), [10]); }, @@ -234,6 +234,46 @@ suite.addBatch({ } }, + "tickValues": { + "defaults to null": function(axis) { + var a = axis().tickValues(); + assert.isNull(a); + }, + "can be given as array of positions": function(axis) { + var l = [1, 2.5, 3], a = axis().tickValues(l), t = a.tickValues(); + assert.equal(t, l); + assert.equal(t.length, 3); + }, + "does not change the tick arguments": function(axis) { + var b = {}, a = axis().ticks(b, 42).tickValues([10]), t = a.ticks(); + assert.equal(t[0], b); + assert.equal(t[1], 42); + assert.equal(t.length, 2); + }, + "does not change the arguments passed to the scale's tickFormat function": function(axis) { + var x = d3.scale.linear(), + a = axis().scale(x).ticks(10).tickValues([1, 2, 3]), + g = d3.select("body").html("").append("svg:g"), + aa = []; + + x.tickFormat = function() { + aa.push(arguments); + return String; + }; + + g.call(a); + assert.equal(aa.length, 1); + assert.equal(aa[0].length, 1); + assert.equal(aa[0][0], 10); + }, + "affects the generated ticks": function(axis) { + var a = axis().ticks(20), + g = d3.select("body").html("").append("svg:g").call(a), + t = g.selectAll("g"); + assert.equal(t[0].length, 21); + } + }, + "tickSubdivide": { "defaults to zero": function(axis) { var a = axis(); diff --git a/test/svg/line-test.js b/test/svg/line-test.js index 945062a9160b3..4753c64a1d7db 100644 --- a/test/svg/line-test.js +++ b/test/svg/line-test.js @@ -57,6 +57,9 @@ suite.addBatch({ assert.pathEqual(l([[0, 0], [1, 1]]), "M0,0V1H1"); assert.equal(l.interpolate(), "step-before"); }, + "invalid interpolates fallback to linear": function(line) { + assert.equal(line().interpolate("__proto__").interpolate(), "linear"); + }, "tension defaults to .7": function(line) { assert.equal(line().tension(), .7); diff --git a/test/svg/symbol-test.js b/test/svg/symbol-test.js index 80e8bac47deba..fd401b3307663 100644 --- a/test/svg/symbol-test.js +++ b/test/svg/symbol-test.js @@ -56,6 +56,7 @@ suite.addBatch({ var a = symbol().type(String); assert.pathEqual(a(), "M0,4.51351666838205A4.51351666838205,4.51351666838205 0 1,1 0,-4.51351666838205A4.51351666838205,4.51351666838205 0 1,1 0,4.51351666838205Z"); assert.pathEqual(a("invalid"), "M0,4.51351666838205A4.51351666838205,4.51351666838205 0 1,1 0,-4.51351666838205A4.51351666838205,4.51351666838205 0 1,1 0,4.51351666838205Z"); + assert.pathEqual(a("hasOwnProperty"), "M0,4.51351666838205A4.51351666838205,4.51351666838205 0 1,1 0,-4.51351666838205A4.51351666838205,4.51351666838205 0 1,1 0,4.51351666838205Z"); }, "can specify type accessor as a function": function(symbol) { var a = symbol().type(String); diff --git a/test/time/day-test.js b/test/time/day-test.js index b7c120c6a4062..505ca45893fdc 100644 --- a/test/time/day-test.js +++ b/test/time/day-test.js @@ -10,54 +10,170 @@ suite.addBatch({ topic: function() { return d3.time.day; }, - "returns midnights": function(floor) { - assert.deepEqual(floor(local(2010, 11, 31, 23)), local(2010, 11, 31)); - assert.deepEqual(floor(local(2011, 0, 1, 0)), local(2011, 0, 1)); - assert.deepEqual(floor(local(2011, 0, 1, 1)), local(2011, 0, 1)); + "defaults to floor": function(interval) { + assert.strictEqual(interval, interval.floor); }, - "observes start of daylight savings time": function(floor) { - assert.deepEqual(floor(utc(2011, 2, 13, 7)), local(2011, 2, 12)); - assert.deepEqual(floor(utc(2011, 2, 13, 8)), local(2011, 2, 13)); - assert.deepEqual(floor(utc(2011, 2, 13, 9)), local(2011, 2, 13)); - assert.deepEqual(floor(utc(2011, 2, 13, 10)), local(2011, 2, 13)); + "floor": { + topic: function(interval) { + return interval.floor; + }, + "returns midnights": function(floor) { + assert.deepEqual(floor(local(2010, 11, 31, 23)), local(2010, 11, 31)); + assert.deepEqual(floor(local(2011, 00, 01, 00)), local(2011, 00, 01)); + assert.deepEqual(floor(local(2011, 00, 01, 01)), local(2011, 00, 01)); + }, + "observes start of daylight savings time": function(floor) { + assert.deepEqual(floor(utc(2011, 02, 13, 07)), local(2011, 02, 12)); + assert.deepEqual(floor(utc(2011, 02, 13, 08)), local(2011, 02, 13)); + assert.deepEqual(floor(utc(2011, 02, 13, 09)), local(2011, 02, 13)); + assert.deepEqual(floor(utc(2011, 02, 13, 10)), local(2011, 02, 13)); + }, + "observes end of daylight savings time": function(floor) { + assert.deepEqual(floor(utc(2011, 10, 06, 07)), local(2011, 10, 06)); + assert.deepEqual(floor(utc(2011, 10, 06, 08)), local(2011, 10, 06)); + assert.deepEqual(floor(utc(2011, 10, 06, 09)), local(2011, 10, 06)); + assert.deepEqual(floor(utc(2011, 10, 06, 10)), local(2011, 10, 06)); + } + }, + "ceil": { + topic: function(interval) { + return interval.ceil; + }, + "returns midnights": function(ceil) { + assert.deepEqual(ceil(local(2010, 11, 30, 23)), local(2010, 11, 31)); + assert.deepEqual(ceil(local(2010, 11, 31, 00)), local(2010, 11, 31)); + assert.deepEqual(ceil(local(2010, 11, 31, 01)), local(2011, 00, 01)); + }, + "observes start of daylight savings time": function(ceil) { + assert.deepEqual(ceil(utc(2011, 02, 13, 07)), local(2011, 02, 13)); + assert.deepEqual(ceil(utc(2011, 02, 13, 08)), local(2011, 02, 13)); + assert.deepEqual(ceil(utc(2011, 02, 13, 09)), local(2011, 02, 14)); + assert.deepEqual(ceil(utc(2011, 02, 13, 10)), local(2011, 02, 14)); + }, + "observes end of daylight savings time": function(ceil) { + assert.deepEqual(ceil(utc(2011, 10, 06, 07)), local(2011, 10, 06)); + assert.deepEqual(ceil(utc(2011, 10, 06, 08)), local(2011, 10, 07)); + assert.deepEqual(ceil(utc(2011, 10, 06, 09)), local(2011, 10, 07)); + assert.deepEqual(ceil(utc(2011, 10, 06, 10)), local(2011, 10, 07)); + } }, - "observes end of daylight savings time": function(floor) { - assert.deepEqual(floor(utc(2011, 10, 6, 7)), local(2011, 10, 6)); - assert.deepEqual(floor(utc(2011, 10, 6, 8)), local(2011, 10, 6)); - assert.deepEqual(floor(utc(2011, 10, 6, 9)), local(2011, 10, 6)); - assert.deepEqual(floor(utc(2011, 10, 6, 10)), local(2011, 10, 6)); + "offset": { + topic: function(interval) { + return interval.offset; + }, + "does not modify the passed-in date": function(offset) { + var date = local(2010, 11, 31, 23, 59, 59, 999); + offset(date, +1); + assert.deepEqual(date, local(2010, 11, 31, 23, 59, 59, 999)); + }, + "does not round the passed-in-date": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 999), +1), local(2011, 00, 01, 23, 59, 59, 999)); + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 456), -2), local(2010, 11, 29, 23, 59, 59, 456)); + }, + "allows negative offsets": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31), -1), local(2010, 11, 30)); + assert.deepEqual(offset(local(2011, 00, 01), -2), local(2010, 11, 30)); + assert.deepEqual(offset(local(2011, 00, 01), -1), local(2010, 11, 31)); + }, + "allows positive offsets": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31), +1), local(2011, 00, 01)); + assert.deepEqual(offset(local(2010, 11, 30), +2), local(2011, 00, 01)); + assert.deepEqual(offset(local(2010, 11, 30), +1), local(2010, 11, 31)); + }, + "allows zero offset": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 999), 0), local(2010, 11, 31, 23, 59, 59, 999)); + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 58, 000), 0), local(2010, 11, 31, 23, 59, 58, 000)); + } }, "UTC": { - topic: function(floor) { - return floor.utc; + topic: function(interval) { + return interval.utc; }, - "returns midnights": function(floor) { - assert.deepEqual(floor(utc(2010, 11, 31, 23)), utc(2010, 11, 31)); - assert.deepEqual(floor(utc(2011, 0, 1, 0)), utc(2011, 0, 1)); - assert.deepEqual(floor(utc(2011, 0, 1, 1)), utc(2011, 0, 1)); - }, - "does not observe the start of daylight savings time": function(floor) { - assert.deepEqual(floor(utc(2011, 2, 13, 7)), utc(2011, 2, 13)); - assert.deepEqual(floor(utc(2011, 2, 13, 8)), utc(2011, 2, 13)); - assert.deepEqual(floor(utc(2011, 2, 13, 9)), utc(2011, 2, 13)); - assert.deepEqual(floor(utc(2011, 2, 13, 10)), utc(2011, 2, 13)); - }, - "does not observe the end of daylight savings time": function(floor) { - assert.deepEqual(floor(utc(2011, 10, 6, 5)), utc(2011, 10, 6)); - assert.deepEqual(floor(utc(2011, 10, 6, 6)), utc(2011, 10, 6)); - assert.deepEqual(floor(utc(2011, 10, 6, 7)), utc(2011, 10, 6)); - assert.deepEqual(floor(utc(2011, 10, 6, 8)), utc(2011, 10, 6)); + "defaults to floor": function(interval) { + assert.strictEqual(interval, interval.floor); + }, + "floor": { + topic: function(interval) { + return interval.floor; + }, + "returns midnights": function(floor) { + assert.deepEqual(floor(utc(2010, 11, 31, 23)), utc(2010, 11, 31)); + assert.deepEqual(floor(utc(2011, 00, 01, 00)), utc(2011, 00, 01)); + assert.deepEqual(floor(utc(2011, 00, 01, 01)), utc(2011, 00, 01)); + }, + "does not observe the start of daylight savings time": function(floor) { + assert.deepEqual(floor(utc(2011, 02, 13, 07)), utc(2011, 02, 13)); + assert.deepEqual(floor(utc(2011, 02, 13, 08)), utc(2011, 02, 13)); + assert.deepEqual(floor(utc(2011, 02, 13, 09)), utc(2011, 02, 13)); + assert.deepEqual(floor(utc(2011, 02, 13, 10)), utc(2011, 02, 13)); + }, + "does not observe the end of daylight savings time": function(floor) { + assert.deepEqual(floor(utc(2011, 10, 06, 05)), utc(2011, 10, 06)); + assert.deepEqual(floor(utc(2011, 10, 06, 06)), utc(2011, 10, 06)); + assert.deepEqual(floor(utc(2011, 10, 06, 07)), utc(2011, 10, 06)); + assert.deepEqual(floor(utc(2011, 10, 06, 08)), utc(2011, 10, 06)); + } + }, + "ceil": { + topic: function(interval) { + return interval.ceil; + }, + "returns midnights": function(ceil) { + assert.deepEqual(ceil(utc(2010, 11, 30, 23)), utc(2010, 11, 31)); + assert.deepEqual(ceil(utc(2010, 11, 31, 00)), utc(2010, 11, 31)); + assert.deepEqual(ceil(utc(2010, 11, 31, 01)), utc(2011, 00, 01)); + }, + "does not observe the start of daylight savings time": function(ceil) { + assert.deepEqual(ceil(utc(2011, 02, 13, 07)), utc(2011, 02, 14)); + assert.deepEqual(ceil(utc(2011, 02, 13, 08)), utc(2011, 02, 14)); + assert.deepEqual(ceil(utc(2011, 02, 13, 09)), utc(2011, 02, 14)); + assert.deepEqual(ceil(utc(2011, 02, 13, 10)), utc(2011, 02, 14)); + }, + "does not observe the end of daylight savings time": function(ceil) { + assert.deepEqual(ceil(utc(2011, 10, 06, 05)), utc(2011, 10, 07)); + assert.deepEqual(ceil(utc(2011, 10, 06, 06)), utc(2011, 10, 07)); + assert.deepEqual(ceil(utc(2011, 10, 06, 07)), utc(2011, 10, 07)); + assert.deepEqual(ceil(utc(2011, 10, 06, 08)), utc(2011, 10, 07)); + } + }, + "offset": { + topic: function(interval) { + return interval.offset; + }, + "does not modify the passed-in date": function(offset) { + var date = utc(2010, 11, 31, 23, 59, 59, 999); + offset(date, +1); + assert.deepEqual(date, utc(2010, 11, 31, 23, 59, 59, 999)); + }, + "does not round the passed-in-date": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 999), +1), utc(2011, 00, 01, 23, 59, 59, 999)); + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 456), -2), utc(2010, 11, 29, 23, 59, 59, 456)); + }, + "allows negative offsets": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31), -1), utc(2010, 11, 30)); + assert.deepEqual(offset(utc(2011, 00, 01), -2), utc(2010, 11, 30)); + assert.deepEqual(offset(utc(2011, 00, 01), -1), utc(2010, 11, 31)); + }, + "allows positive offsets": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31), +1), utc(2011, 00, 01)); + assert.deepEqual(offset(utc(2010, 11, 30), +2), utc(2011, 00, 01)); + assert.deepEqual(offset(utc(2010, 11, 30), +1), utc(2010, 11, 31)); + }, + "allows zero offset": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 999), 0), utc(2010, 11, 31, 23, 59, 59, 999)); + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 58, 000), 0), utc(2010, 11, 31, 23, 59, 58, 000)); + } } } } }); function local(year, month, day, hours, minutes, seconds) { - return new Date(year, month, day, hours || 0, minutes || 0, seconds || 0); + return new Date(year, month, day, hours || 00, minutes || 00, seconds || 00); } function utc(year, month, day, hours, minutes, seconds) { - return new Date(Date.UTC(year, month, day, hours || 0, minutes || 0, seconds || 0)); + return new Date(Date.UTC(year, month, day, hours || 00, minutes || 00, seconds || 00)); } suite.export(module); diff --git a/test/time/format-test.js b/test/time/format-test.js index f00b88d9d5f44..98c58776b359e 100644 --- a/test/time/format-test.js +++ b/test/time/format-test.js @@ -368,6 +368,15 @@ suite.addBatch({ assert.deepEqual(p("12:00:01 pm"), local(1900, 0, 1, 12, 0, 1)); assert.deepEqual(p("11:59:59 PM"), local(1900, 0, 1, 23, 59, 59)); }, + "doesn't crash when given weird strings": function(format) { + try { + Object.prototype.foo = 10; + var p = format("%b %d, %Y").parse; + assert.isNull(p("foo 1, 1990")); + } finally { + delete Object.prototype.foo; + } + }, "UTC": { topic: function(format) { return format.utc; diff --git a/test/time/hour-test.js b/test/time/hour-test.js index 15d36097b7181..126fd52f4851e 100644 --- a/test/time/hour-test.js +++ b/test/time/hour-test.js @@ -10,76 +10,214 @@ suite.addBatch({ topic: function() { return d3.time.hour; }, - "returns hours": function(floor) { - assert.deepEqual(floor(local(2010, 11, 31, 23, 59)), local(2010, 11, 31, 23)); - assert.deepEqual(floor(local(2011, 0, 1, 0, 0)), local(2011, 0, 1, 0)); - assert.deepEqual(floor(local(2011, 0, 1, 0, 1)), local(2011, 0, 1, 0)); + "defaults to floor": function(interval) { + assert.strictEqual(interval, interval.floor); }, - "observes start of daylight savings time": function(floor) { - assert.deepEqual(floor(utc(2011, 2, 13, 8, 59)), utc(2011, 2, 13, 8)); - assert.deepEqual(floor(utc(2011, 2, 13, 9, 0)), utc(2011, 2, 13, 9)); - assert.deepEqual(floor(utc(2011, 2, 13, 9, 1)), utc(2011, 2, 13, 9)); - assert.deepEqual(floor(utc(2011, 2, 13, 9, 59)), utc(2011, 2, 13, 9)); - assert.deepEqual(floor(utc(2011, 2, 13, 10, 0)), utc(2011, 2, 13, 10)); - assert.deepEqual(floor(utc(2011, 2, 13, 10, 1)), utc(2011, 2, 13, 10)); - }, - "observes end of daylight savings time": function(floor) { - assert.deepEqual(floor(utc(2011, 10, 6, 7, 59)), utc(2011, 10, 6, 7)); - assert.deepEqual(floor(utc(2011, 10, 6, 8, 0)), utc(2011, 10, 6, 8)); - assert.deepEqual(floor(utc(2011, 10, 6, 8, 1)), utc(2011, 10, 6, 8)); - assert.deepEqual(floor(utc(2011, 10, 6, 8, 59)), utc(2011, 10, 6, 8)); - assert.deepEqual(floor(utc(2011, 10, 6, 9, 0)), utc(2011, 10, 6, 9)); - assert.deepEqual(floor(utc(2011, 10, 6, 9, 1)), utc(2011, 10, 6, 9)); + "floor": { + topic: function(interval) { + return interval.floor; + }, + "returns hours": function(floor) { + assert.deepEqual(floor(local(2010, 11, 31, 23, 59)), local(2010, 11, 31, 23)); + assert.deepEqual(floor(local(2011, 00, 01, 00, 00)), local(2011, 00, 01, 00)); + assert.deepEqual(floor(local(2011, 00, 01, 00, 01)), local(2011, 00, 01, 00)); + }, + "observes start of daylight savings time": function(floor) { + assert.deepEqual(floor(utc(2011, 02, 13, 08, 59)), utc(2011, 02, 13, 08)); + assert.deepEqual(floor(utc(2011, 02, 13, 09, 00)), utc(2011, 02, 13, 09)); + assert.deepEqual(floor(utc(2011, 02, 13, 09, 01)), utc(2011, 02, 13, 09)); + assert.deepEqual(floor(utc(2011, 02, 13, 09, 59)), utc(2011, 02, 13, 09)); + assert.deepEqual(floor(utc(2011, 02, 13, 10, 00)), utc(2011, 02, 13, 10)); + assert.deepEqual(floor(utc(2011, 02, 13, 10, 01)), utc(2011, 02, 13, 10)); + }, + "observes end of daylight savings time": function(floor) { + assert.deepEqual(floor(utc(2011, 10, 06, 07, 59)), utc(2011, 10, 06, 07)); + assert.deepEqual(floor(utc(2011, 10, 06, 08, 00)), utc(2011, 10, 06, 08)); + assert.deepEqual(floor(utc(2011, 10, 06, 08, 01)), utc(2011, 10, 06, 08)); + assert.deepEqual(floor(utc(2011, 10, 06, 08, 59)), utc(2011, 10, 06, 08)); + assert.deepEqual(floor(utc(2011, 10, 06, 09, 00)), utc(2011, 10, 06, 09)); + assert.deepEqual(floor(utc(2011, 10, 06, 09, 01)), utc(2011, 10, 06, 09)); + }, + "NPT": { + "observes 15-minute offset": tz("Asia/Kathmandu", function(floor) { + assert.deepEqual(floor(local(2010, 11, 31, 23, 59, 59)), utc(2010, 11, 31, 17, 15)); + assert.deepEqual(floor(local(2011, 00, 01, 00, 00, 00)), utc(2010, 11, 31, 18, 15)); + assert.deepEqual(floor(local(2011, 00, 01, 00, 00, 01)), utc(2010, 11, 31, 18, 15)); + }) + }, + "IST": { + "observes 30-minute offset": tz("Asia/Calcutta", function(floor) { + assert.deepEqual(floor(local(2010, 11, 31, 23, 59, 59)), utc(2010, 11, 31, 17, 30)); + assert.deepEqual(floor(local(2011, 00, 01, 00, 00, 00)), utc(2010, 11, 31, 18, 30)); + assert.deepEqual(floor(local(2011, 00, 01, 00, 00, 01)), utc(2010, 11, 31, 18, 30)); + }) + } }, - "NPT": { - "observes 15-minute offset": tz("Asia/Kathmandu", function(floor) { - assert.deepEqual(floor(local(2010, 11, 31, 23, 59, 59)), utc(2010, 11, 31, 17, 15)); - assert.deepEqual(floor(local(2011, 0, 1, 0, 0, 0)), utc(2010, 11, 31, 18, 15)); - assert.deepEqual(floor(local(2011, 0, 1, 0, 0, 1)), utc(2010, 11, 31, 18, 15)); - }) + "ceil": { + topic: function(interval) { + return interval.ceil; + }, + "returns hours": function(ceil) { + assert.deepEqual(ceil(local(2010, 11, 31, 23, 59)), local(2011, 00, 01, 00)); + assert.deepEqual(ceil(local(2011, 00, 01, 00, 00)), local(2011, 00, 01, 00)); + assert.deepEqual(ceil(local(2011, 00, 01, 00, 01)), local(2011, 00, 01, 01)); + }, + "observes start of daylight savings time": function(ceil) { + assert.deepEqual(ceil(utc(2011, 02, 13, 08, 59)), utc(2011, 02, 13, 09)); + assert.deepEqual(ceil(utc(2011, 02, 13, 09, 00)), utc(2011, 02, 13, 09)); + assert.deepEqual(ceil(utc(2011, 02, 13, 09, 01)), utc(2011, 02, 13, 10)); + assert.deepEqual(ceil(utc(2011, 02, 13, 09, 59)), utc(2011, 02, 13, 10)); + assert.deepEqual(ceil(utc(2011, 02, 13, 10, 00)), utc(2011, 02, 13, 10)); + assert.deepEqual(ceil(utc(2011, 02, 13, 10, 01)), utc(2011, 02, 13, 11)); + }, + "observes end of daylight savings time": function(ceil) { + assert.deepEqual(ceil(utc(2011, 10, 06, 07, 59)), utc(2011, 10, 06, 08)); + assert.deepEqual(ceil(utc(2011, 10, 06, 08, 00)), utc(2011, 10, 06, 08)); + assert.deepEqual(ceil(utc(2011, 10, 06, 08, 01)), utc(2011, 10, 06, 09)); + assert.deepEqual(ceil(utc(2011, 10, 06, 08, 59)), utc(2011, 10, 06, 09)); + assert.deepEqual(ceil(utc(2011, 10, 06, 09, 00)), utc(2011, 10, 06, 09)); + assert.deepEqual(ceil(utc(2011, 10, 06, 09, 01)), utc(2011, 10, 06, 10)); + }, + "NPT": { + "observes 15-minute offset": tz("Asia/Kathmandu", function(ceil) { + assert.deepEqual(ceil(local(2010, 11, 31, 23, 59, 59)), utc(2010, 11, 31, 18, 15)); + assert.deepEqual(ceil(local(2011, 00, 01, 00, 00, 00)), utc(2010, 11, 31, 18, 15)); + assert.deepEqual(ceil(local(2011, 00, 01, 00, 00, 01)), utc(2010, 11, 31, 19, 15)); + }) + }, + "IST": { + "observes 30-minute offset": tz("Asia/Calcutta", function(ceil) { + assert.deepEqual(ceil(local(2010, 11, 31, 23, 59, 59)), utc(2010, 11, 31, 18, 30)); + assert.deepEqual(ceil(local(2011, 00, 01, 00, 00, 00)), utc(2010, 11, 31, 18, 30)); + assert.deepEqual(ceil(local(2011, 00, 01, 00, 00, 01)), utc(2010, 11, 31, 19, 30)); + }) + } }, - "IST": { - "observes 30-minute offset": tz("Asia/Calcutta", function(floor) { - assert.deepEqual(floor(local(2010, 11, 31, 23, 59, 59)), utc(2010, 11, 31, 17, 30)); - assert.deepEqual(floor(local(2011, 0, 1, 0, 0, 0)), utc(2010, 11, 31, 18, 30)); - assert.deepEqual(floor(local(2011, 0, 1, 0, 0, 1)), utc(2010, 11, 31, 18, 30)); - }) + "offset": { + topic: function(interval) { + return interval.offset; + }, + "does not modify the passed-in date": function(offset) { + var date = local(2010, 11, 31, 23, 59, 59, 999); + offset(date, +1); + assert.deepEqual(date, local(2010, 11, 31, 23, 59, 59, 999)); + }, + "does not round the passed-in-date": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 999), +1), local(2011, 00, 01, 00, 59, 59, 999)); + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 456), -2), local(2010, 11, 31, 21, 59, 59, 456)); + }, + "allows negative offsets": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31, 12), -1), local(2010, 11, 31, 11)); + assert.deepEqual(offset(local(2011, 00, 01, 01), -2), local(2010, 11, 31, 23)); + assert.deepEqual(offset(local(2011, 00, 01, 00), -1), local(2010, 11, 31, 23)); + }, + "allows positive offsets": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31, 11), +1), local(2010, 11, 31, 12)); + assert.deepEqual(offset(local(2010, 11, 31, 23), +2), local(2011, 00, 01, 01)); + assert.deepEqual(offset(local(2010, 11, 31, 23), +1), local(2011, 00, 01, 00)); + }, + "allows zero offset": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 999), 0), local(2010, 11, 31, 23, 59, 59, 999)); + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 58, 000), 0), local(2010, 11, 31, 23, 59, 58, 000)); + } }, "UTC": { - topic: function(floor) { - return floor.utc; + topic: function(interval) { + return interval.utc; }, - "returns hours": function(floor) { - assert.deepEqual(floor(utc(2010, 11, 31, 23, 59)), utc(2010, 11, 31, 23)); - assert.deepEqual(floor(utc(2011, 0, 1, 0, 0)), utc(2011, 0, 1, 0)); - assert.deepEqual(floor(utc(2011, 0, 1, 0, 1)), utc(2011, 0, 1, 0)); - }, - "does not observe the start of daylight savings time": function(floor) { - assert.deepEqual(floor(utc(2011, 2, 13, 8, 59)), utc(2011, 2, 13, 8)); - assert.deepEqual(floor(utc(2011, 2, 13, 9, 0)), utc(2011, 2, 13, 9)); - assert.deepEqual(floor(utc(2011, 2, 13, 9, 1)), utc(2011, 2, 13, 9)); - assert.deepEqual(floor(utc(2011, 2, 13, 9, 59)), utc(2011, 2, 13, 9)); - assert.deepEqual(floor(utc(2011, 2, 13, 10, 0)), utc(2011, 2, 13, 10)); - assert.deepEqual(floor(utc(2011, 2, 13, 10, 1)), utc(2011, 2, 13, 10)); - }, - "does not observe the end of daylight savings time": function(floor) { - assert.deepEqual(floor(utc(2011, 10, 6, 7, 59)), utc(2011, 10, 6, 7)); - assert.deepEqual(floor(utc(2011, 10, 6, 8, 0)), utc(2011, 10, 6, 8)); - assert.deepEqual(floor(utc(2011, 10, 6, 8, 1)), utc(2011, 10, 6, 8)); - assert.deepEqual(floor(utc(2011, 10, 6, 8, 59)), utc(2011, 10, 6, 8)); - assert.deepEqual(floor(utc(2011, 10, 6, 9, 0)), utc(2011, 10, 6, 9)); - assert.deepEqual(floor(utc(2011, 10, 6, 9, 1)), utc(2011, 10, 6, 9)); + "defaults to floor": function(interval) { + assert.strictEqual(interval, interval.floor); + }, + "floor": { + topic: function(interval) { + return interval.floor; + }, + "returns hours": function(floor) { + assert.deepEqual(floor(utc(2010, 11, 31, 23, 59)), utc(2010, 11, 31, 23)); + assert.deepEqual(floor(utc(2011, 00, 01, 00, 00)), utc(2011, 00, 01, 00)); + assert.deepEqual(floor(utc(2011, 00, 01, 00, 01)), utc(2011, 00, 01, 00)); + }, + "does not observe the start of daylight savings time": function(floor) { + assert.deepEqual(floor(utc(2011, 02, 13, 08, 59)), utc(2011, 02, 13, 08)); + assert.deepEqual(floor(utc(2011, 02, 13, 09, 00)), utc(2011, 02, 13, 09)); + assert.deepEqual(floor(utc(2011, 02, 13, 09, 01)), utc(2011, 02, 13, 09)); + assert.deepEqual(floor(utc(2011, 02, 13, 09, 59)), utc(2011, 02, 13, 09)); + assert.deepEqual(floor(utc(2011, 02, 13, 10, 00)), utc(2011, 02, 13, 10)); + assert.deepEqual(floor(utc(2011, 02, 13, 10, 01)), utc(2011, 02, 13, 10)); + }, + "does not observe the end of daylight savings time": function(floor) { + assert.deepEqual(floor(utc(2011, 10, 06, 07, 59)), utc(2011, 10, 06, 07)); + assert.deepEqual(floor(utc(2011, 10, 06, 08, 00)), utc(2011, 10, 06, 08)); + assert.deepEqual(floor(utc(2011, 10, 06, 08, 01)), utc(2011, 10, 06, 08)); + assert.deepEqual(floor(utc(2011, 10, 06, 08, 59)), utc(2011, 10, 06, 08)); + assert.deepEqual(floor(utc(2011, 10, 06, 09, 00)), utc(2011, 10, 06, 09)); + assert.deepEqual(floor(utc(2011, 10, 06, 09, 01)), utc(2011, 10, 06, 09)); + } + }, + "ceil": { + topic: function(interval) { + return interval.ceil; + }, + "returns hours": function(ceil) { + assert.deepEqual(ceil(utc(2010, 11, 31, 23, 59)), utc(2011, 00, 01, 00)); + assert.deepEqual(ceil(utc(2011, 00, 01, 00, 00)), utc(2011, 00, 01, 00)); + assert.deepEqual(ceil(utc(2011, 00, 01, 00, 01)), utc(2011, 00, 01, 01)); + }, + "does not observe the start of daylight savings time": function(ceil) { + assert.deepEqual(ceil(utc(2011, 02, 13, 08, 59)), utc(2011, 02, 13, 09)); + assert.deepEqual(ceil(utc(2011, 02, 13, 09, 00)), utc(2011, 02, 13, 09)); + assert.deepEqual(ceil(utc(2011, 02, 13, 09, 01)), utc(2011, 02, 13, 10)); + assert.deepEqual(ceil(utc(2011, 02, 13, 09, 59)), utc(2011, 02, 13, 10)); + assert.deepEqual(ceil(utc(2011, 02, 13, 10, 00)), utc(2011, 02, 13, 10)); + assert.deepEqual(ceil(utc(2011, 02, 13, 10, 01)), utc(2011, 02, 13, 11)); + }, + "does not observe the end of daylight savings time": function(ceil) { + assert.deepEqual(ceil(utc(2011, 10, 06, 07, 59)), utc(2011, 10, 06, 08)); + assert.deepEqual(ceil(utc(2011, 10, 06, 08, 00)), utc(2011, 10, 06, 08)); + assert.deepEqual(ceil(utc(2011, 10, 06, 08, 01)), utc(2011, 10, 06, 09)); + assert.deepEqual(ceil(utc(2011, 10, 06, 08, 59)), utc(2011, 10, 06, 09)); + assert.deepEqual(ceil(utc(2011, 10, 06, 09, 00)), utc(2011, 10, 06, 09)); + assert.deepEqual(ceil(utc(2011, 10, 06, 09, 01)), utc(2011, 10, 06, 10)); + } + }, + "offset": { + topic: function(interval) { + return interval.offset; + }, + "does not modify the passed-in date": function(offset) { + var date = utc(2010, 11, 31, 23, 59, 59, 999); + offset(date, +1); + assert.deepEqual(date, utc(2010, 11, 31, 23, 59, 59, 999)); + }, + "does not round the passed-in-date": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 999), +1), utc(2011, 00, 01, 00, 59, 59, 999)); + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 456), -2), utc(2010, 11, 31, 21, 59, 59, 456)); + }, + "allows negative offsets": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31, 12), -1), utc(2010, 11, 31, 11)); + assert.deepEqual(offset(utc(2011, 00, 01, 01), -2), utc(2010, 11, 31, 23)); + assert.deepEqual(offset(utc(2011, 00, 01, 00), -1), utc(2010, 11, 31, 23)); + }, + "allows positive offsets": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31, 11), +1), utc(2010, 11, 31, 12)); + assert.deepEqual(offset(utc(2010, 11, 31, 23), +2), utc(2011, 00, 01, 01)); + assert.deepEqual(offset(utc(2010, 11, 31, 23), +1), utc(2011, 00, 01, 00)); + }, + "allows zero offset": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 999), 0), utc(2010, 11, 31, 23, 59, 59, 999)); + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 58, 000), 0), utc(2010, 11, 31, 23, 59, 58, 000)); + } } } } }); function local(year, month, day, hours, minutes, seconds) { - return new Date(year, month, day, hours || 0, minutes || 0, seconds || 0); + return new Date(year, month, day, hours || 00, minutes || 00, seconds || 00); } function utc(year, month, day, hours, minutes, seconds) { - return new Date(Date.UTC(year, month, day, hours || 0, minutes || 0, seconds || 0)); + return new Date(Date.UTC(year, month, day, hours || 00, minutes || 00, seconds || 00)); } function tz(tz, scope) { diff --git a/test/time/minute-test.js b/test/time/minute-test.js index 3447e457a4c51..5929a991b8e17 100644 --- a/test/time/minute-test.js +++ b/test/time/minute-test.js @@ -10,32 +10,126 @@ suite.addBatch({ topic: function() { return d3.time.minute; }, - "returns minutes": function(floor) { - assert.deepEqual(floor(local(2010, 11, 31, 23, 59, 59)), local(2010, 11, 31, 23, 59)); - assert.deepEqual(floor(local(2011, 0, 1, 0, 0, 0)), local(2011, 0, 1, 0, 0)); - assert.deepEqual(floor(local(2011, 0, 1, 0, 0, 59)), local(2011, 0, 1, 0, 0)); - assert.deepEqual(floor(local(2011, 0, 1, 0, 1, 0)), local(2011, 0, 1, 0, 1)); + "defaults to floor": function(interval) { + assert.strictEqual(interval, interval.floor); }, - "UTC": { - topic: function(floor) { - return floor.utc; + "floor": { + topic: function(interval) { + return interval.floor; }, "returns minutes": function(floor) { - assert.deepEqual(floor(utc(2010, 11, 31, 23, 59, 59)), utc(2010, 11, 31, 23, 59)); - assert.deepEqual(floor(utc(2011, 0, 1, 0, 0, 0)), utc(2011, 0, 1, 0, 0)); - assert.deepEqual(floor(utc(2011, 0, 1, 0, 0, 59)), utc(2011, 0, 1, 0, 0)); - assert.deepEqual(floor(utc(2011, 0, 1, 0, 1, 0)), utc(2011, 0, 1, 0, 1)); + assert.deepEqual(floor(local(2010, 11, 31, 23, 59, 59)), local(2010, 11, 31, 23, 59)); + assert.deepEqual(floor(local(2011, 00, 01, 00, 00, 00)), local(2011, 00, 01, 00, 00)); + assert.deepEqual(floor(local(2011, 00, 01, 00, 00, 59)), local(2011, 00, 01, 00, 00)); + assert.deepEqual(floor(local(2011, 00, 01, 00, 01, 00)), local(2011, 00, 01, 00, 01)); + } + }, + "ceil": { + topic: function(interval) { + return interval.ceil; + }, + "returns minutes": function(ceil) { + assert.deepEqual(ceil(local(2010, 11, 31, 23, 59, 59)), local(2011, 00, 01, 00, 00)); + assert.deepEqual(ceil(local(2011, 00, 01, 00, 00, 00)), local(2011, 00, 01, 00, 00)); + assert.deepEqual(ceil(local(2011, 00, 01, 00, 00, 59)), local(2011, 00, 01, 00, 01)); + assert.deepEqual(ceil(local(2011, 00, 01, 00, 01, 00)), local(2011, 00, 01, 00, 01)); + } + }, + "offset": { + topic: function(interval) { + return interval.offset; + }, + "does not modify the passed-in date": function(offset) { + var date = local(2010, 11, 31, 23, 59, 59, 999); + offset(date, +1); + assert.deepEqual(date, local(2010, 11, 31, 23, 59, 59, 999)); + }, + "does not round the passed-in-date": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 999), +1), local(2011, 00, 01, 00, 00, 59, 999)); + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 456), -2), local(2010, 11, 31, 23, 57, 59, 456)); + }, + "allows negative offsets": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31, 23, 12), -1), local(2010, 11, 31, 23, 11)); + assert.deepEqual(offset(local(2011, 00, 01, 00, 01), -2), local(2010, 11, 31, 23, 59)); + assert.deepEqual(offset(local(2011, 00, 01, 00, 00), -1), local(2010, 11, 31, 23, 59)); + }, + "allows positive offsets": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31, 23, 11), +1), local(2010, 11, 31, 23, 12)); + assert.deepEqual(offset(local(2010, 11, 31, 23, 59), +2), local(2011, 00, 01, 00, 01)); + assert.deepEqual(offset(local(2010, 11, 31, 23, 59), +1), local(2011, 00, 01, 00, 00)); + }, + "allows zero offset": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 999), 0), local(2010, 11, 31, 23, 59, 59, 999)); + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 58, 000), 0), local(2010, 11, 31, 23, 59, 58, 000)); + } + }, + "UTC": { + topic: function(interval) { + return interval.utc; + }, + "defaults to floor": function(interval) { + assert.strictEqual(interval, interval.floor); + }, + "floor": { + topic: function(interval) { + return interval.floor; + }, + "returns minutes": function(floor) { + assert.deepEqual(floor(utc(2010, 11, 31, 23, 59, 59)), utc(2010, 11, 31, 23, 59)); + assert.deepEqual(floor(utc(2011, 00, 01, 00, 00, 00)), utc(2011, 00, 01, 00, 00)); + assert.deepEqual(floor(utc(2011, 00, 01, 00, 00, 59)), utc(2011, 00, 01, 00, 00)); + assert.deepEqual(floor(utc(2011, 00, 01, 00, 01, 00)), utc(2011, 00, 01, 00, 01)); + } + }, + "ceil": { + topic: function(interval) { + return interval.ceil; + }, + "returns minutes": function(ceil) { + assert.deepEqual(ceil(utc(2010, 11, 31, 23, 59, 59)), utc(2011, 00, 01, 00, 00)); + assert.deepEqual(ceil(utc(2011, 00, 01, 00, 00, 00)), utc(2011, 00, 01, 00, 00)); + assert.deepEqual(ceil(utc(2011, 00, 01, 00, 00, 59)), utc(2011, 00, 01, 00, 01)); + assert.deepEqual(ceil(utc(2011, 00, 01, 00, 01, 00)), utc(2011, 00, 01, 00, 01)); + } + }, + "offset": { + topic: function(interval) { + return interval.offset; + }, + "does not modify the passed-in date": function(offset) { + var date = utc(2010, 11, 31, 23, 59, 59, 999); + offset(date, +1); + assert.deepEqual(date, utc(2010, 11, 31, 23, 59, 59, 999)); + }, + "does not round the passed-in-date": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 999), +1), utc(2011, 00, 01, 00, 00, 59, 999)); + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 456), -2), utc(2010, 11, 31, 23, 57, 59, 456)); + }, + "allows negative offsets": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31, 23, 12), -1), utc(2010, 11, 31, 23, 11)); + assert.deepEqual(offset(utc(2011, 00, 01, 00, 01), -2), utc(2010, 11, 31, 23, 59)); + assert.deepEqual(offset(utc(2011, 00, 01, 00, 00), -1), utc(2010, 11, 31, 23, 59)); + }, + "allows positive offsets": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31, 23, 11), +1), utc(2010, 11, 31, 23, 12)); + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59), +2), utc(2011, 00, 01, 00, 01)); + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59), +1), utc(2011, 00, 01, 00, 00)); + }, + "allows zero offset": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 999), 0), utc(2010, 11, 31, 23, 59, 59, 999)); + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 58, 000), 0), utc(2010, 11, 31, 23, 59, 58, 000)); + } } } } }); function local(year, month, day, hours, minutes, seconds) { - return new Date(year, month, day, hours || 0, minutes || 0, seconds || 0); + return new Date(year, month, day, hours || 00, minutes || 00, seconds || 00); } function utc(year, month, day, hours, minutes, seconds) { - return new Date(Date.UTC(year, month, day, hours || 0, minutes || 0, seconds || 0)); + return new Date(Date.UTC(year, month, day, hours || 00, minutes || 00, seconds || 00)); } suite.export(module); diff --git a/test/time/month-test.js b/test/time/month-test.js index 8b02e4063eb74..a26242ae9e108 100644 --- a/test/time/month-test.js +++ b/test/time/month-test.js @@ -10,42 +10,146 @@ suite.addBatch({ topic: function() { return d3.time.month; }, - "returns months": function(floor) { - assert.deepEqual(floor(local(2010, 11, 31, 23, 59, 59)), local(2010, 11, 1)); - assert.deepEqual(floor(local(2011, 0, 1, 0, 0, 0)), local(2011, 0, 1)); - assert.deepEqual(floor(local(2011, 0, 1, 0, 0, 1)), local(2011, 0, 1)); + "defaults to floor": function(interval) { + assert.strictEqual(interval, interval.floor); }, - "observes the start of daylight savings time": function(floor) { - assert.deepEqual(floor(local(2011, 2, 13, 1)), local(2011, 2, 1)); + "floor": { + topic: function(interval) { + return interval.floor; + }, + "returns months": function(floor) { + assert.deepEqual(floor(local(2010, 11, 31, 23, 59, 59)), local(2010, 11, 01)); + assert.deepEqual(floor(local(2011, 00, 01, 00, 00, 00)), local(2011, 00, 01)); + assert.deepEqual(floor(local(2011, 00, 01, 00, 00, 01)), local(2011, 00, 01)); + }, + "observes the start of daylight savings time": function(floor) { + assert.deepEqual(floor(local(2011, 02, 13, 01)), local(2011, 02, 01)); + }, + "observes the end of the daylight savings time": function(floor) { + assert.deepEqual(floor(local(2011, 10, 06, 01)), local(2011, 10, 01)); + } + }, + "ceil": { + topic: function(interval) { + return interval.ceil; + }, + "returns months": function(ceil) { + assert.deepEqual(ceil(local(2010, 11, 31, 23, 59, 59)), local(2011, 00, 01)); + assert.deepEqual(ceil(local(2011, 00, 01, 00, 00, 00)), local(2011, 00, 01)); + assert.deepEqual(ceil(local(2011, 00, 01, 00, 00, 01)), local(2011, 01, 01)); + }, + "observes the start of daylight savings time": function(ceil) { + assert.deepEqual(ceil(local(2011, 02, 13, 01)), local(2011, 03, 01)); + }, + "observes the end of the daylight savings time": function(ceil) { + assert.deepEqual(ceil(local(2011, 10, 06, 01)), local(2011, 11, 01)); + } }, - "observes the end of the daylight savings time": function(floor) { - assert.deepEqual(floor(local(2011, 10, 6, 1)), local(2011, 10, 1)); + "offset": { + topic: function(interval) { + return interval.offset; + }, + "does not modify the passed-in date": function(offset) { + var date = local(2010, 11, 31, 23, 59, 59, 999); + offset(date, +1); + assert.deepEqual(date, local(2010, 11, 31, 23, 59, 59, 999)); + }, + "does not round the passed-in-date": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 999), +1), local(2011, 00, 31, 23, 59, 59, 999)); + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 456), -2), local(2010, 09, 31, 23, 59, 59, 456)); + }, + "allows negative offsets": function(offset) { + assert.deepEqual(offset(local(2010, 11, 01), -1), local(2010, 10, 01)); + assert.deepEqual(offset(local(2011, 00, 01), -2), local(2010, 10, 01)); + assert.deepEqual(offset(local(2011, 00, 01), -1), local(2010, 11, 01)); + }, + "allows positive offsets": function(offset) { + assert.deepEqual(offset(local(2010, 10, 01), +1), local(2010, 11, 01)); + assert.deepEqual(offset(local(2010, 10, 01), +2), local(2011, 00, 01)); + assert.deepEqual(offset(local(2010, 11, 01), +1), local(2011, 00, 01)); + }, + "allows zero offset": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 999), 0), local(2010, 11, 31, 23, 59, 59, 999)); + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 58, 000), 0), local(2010, 11, 31, 23, 59, 58, 000)); + } }, "UTC": { - topic: function(floor) { - return floor.utc; + topic: function(interval) { + return interval.utc; }, - "returns months": function(floor) { - assert.deepEqual(floor(utc(2010, 11, 31, 23, 59, 59)), utc(2010, 11, 1)); - assert.deepEqual(floor(utc(2011, 0, 1, 0, 0, 0)), utc(2011, 0, 1)); - assert.deepEqual(floor(utc(2011, 0, 1, 0, 0, 1)), utc(2011, 0, 1)); + "defaults to floor": function(interval) { + assert.strictEqual(interval, interval.floor); + }, + "floor": { + topic: function(interval) { + return interval.floor; + }, + "returns months": function(floor) { + assert.deepEqual(floor(utc(2010, 11, 31, 23, 59, 59)), utc(2010, 11, 01)); + assert.deepEqual(floor(utc(2011, 00, 01, 00, 00, 00)), utc(2011, 00, 01)); + assert.deepEqual(floor(utc(2011, 00, 01, 00, 00, 01)), utc(2011, 00, 01)); + }, + "does not observe the start of daylight savings time": function(floor) { + assert.deepEqual(floor(utc(2011, 02, 13, 01)), utc(2011, 02, 01)); + }, + "does not observe the end of the daylight savings time": function(floor) { + assert.deepEqual(floor(utc(2011, 10, 06, 01)), utc(2011, 10, 01)); + } }, - "does not observe the start of daylight savings time": function(floor) { - assert.deepEqual(floor(utc(2011, 2, 13, 1)), utc(2011, 2, 1)); + "ceil": { + topic: function(interval) { + return interval.ceil; + }, + "returns months": function(ceil) { + assert.deepEqual(ceil(utc(2010, 11, 31, 23, 59, 59)), utc(2011, 00, 01)); + assert.deepEqual(ceil(utc(2011, 00, 01, 00, 00, 00)), utc(2011, 00, 01)); + assert.deepEqual(ceil(utc(2011, 00, 01, 00, 00, 01)), utc(2011, 01, 01)); + }, + "does not observe the start of daylight savings time": function(ceil) { + assert.deepEqual(ceil(utc(2011, 02, 13, 01)), utc(2011, 03, 01)); + }, + "does not observe the end of the daylight savings time": function(ceil) { + assert.deepEqual(ceil(utc(2011, 10, 06, 01)), utc(2011, 11, 01)); + } }, - "does not observe the end of the daylight savings time": function(floor) { - assert.deepEqual(floor(utc(2011, 10, 6, 1)), utc(2011, 10, 1)); + "offset": { + topic: function(interval) { + return interval.offset; + }, + "does not modify the passed-in date": function(offset) { + var date = utc(2010, 11, 31, 23, 59, 59, 999); + offset(date, +1); + assert.deepEqual(date, utc(2010, 11, 31, 23, 59, 59, 999)); + }, + "does not round the passed-in-date": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 999), +1), utc(2011, 00, 31, 23, 59, 59, 999)); + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 456), -2), utc(2010, 09, 31, 23, 59, 59, 456)); + }, + "allows negative offsets": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 01), -1), utc(2010, 10, 01)); + assert.deepEqual(offset(utc(2011, 00, 01), -2), utc(2010, 10, 01)); + assert.deepEqual(offset(utc(2011, 00, 01), -1), utc(2010, 11, 01)); + }, + "allows positive offsets": function(offset) { + assert.deepEqual(offset(utc(2010, 10, 01), +1), utc(2010, 11, 01)); + assert.deepEqual(offset(utc(2010, 10, 01), +2), utc(2011, 00, 01)); + assert.deepEqual(offset(utc(2010, 11, 01), +1), utc(2011, 00, 01)); + }, + "allows zero offset": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 999), 0), utc(2010, 11, 31, 23, 59, 59, 999)); + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 58, 000), 0), utc(2010, 11, 31, 23, 59, 58, 000)); + } } } } }); function local(year, month, day, hours, minutes, seconds) { - return new Date(year, month, day, hours || 0, minutes || 0, seconds || 0); + return new Date(year, month, day, hours || 00, minutes || 00, seconds || 00); } function utc(year, month, day, hours, minutes, seconds) { - return new Date(Date.UTC(year, month, day, hours || 0, minutes || 0, seconds || 0)); + return new Date(Date.UTC(year, month, day, hours || 00, minutes || 00, seconds || 00)); } suite.export(module); diff --git a/test/time/second-test.js b/test/time/second-test.js index 788dff41ea9e4..117b768562885 100644 --- a/test/time/second-test.js +++ b/test/time/second-test.js @@ -10,30 +10,142 @@ suite.addBatch({ topic: function() { return d3.time.second; }, - "returns seconds": function(floor) { - assert.deepEqual(floor(local(2010, 11, 31, 23, 59, 59, 999)), local(2010, 11, 31, 23, 59, 59)); - assert.deepEqual(floor(local(2011, 0, 1, 0, 0, 0, 0)), local(2011, 0, 1, 0, 0, 0)); - assert.deepEqual(floor(local(2011, 0, 1, 0, 0, 0, 1)), local(2011, 0, 1, 0, 0, 0)); + "defaults to floor": function(interval) { + assert.strictEqual(interval, interval.floor); }, - "UTC": { - topic: function(floor) { - return floor.utc; + "floor": { + topic: function(interval) { + return interval.floor; }, "returns seconds": function(floor) { - assert.deepEqual(floor(utc(2010, 11, 31, 23, 59, 59, 999)), utc(2010, 11, 31, 23, 59, 59)); - assert.deepEqual(floor(utc(2011, 0, 1, 0, 0, 0, 0)), utc(2011, 0, 1, 0, 0, 0)); - assert.deepEqual(floor(utc(2011, 0, 1, 0, 0, 0, 1)), utc(2011, 0, 1, 0, 0, 0)); + assert.deepEqual(floor(local(2010, 11, 31, 23, 59, 59, 999)), local(2010, 11, 31, 23, 59, 59)); + assert.deepEqual(floor(local(2011, 00, 01, 00, 00, 00, 000)), local(2011, 00, 01, 00, 00, 00)); + assert.deepEqual(floor(local(2011, 00, 01, 00, 00, 00, 001)), local(2011, 00, 01, 00, 00, 00)); + } + }, + "round": { + topic: function(interval) { + return interval.round; + }, + "returns seconds": function(round) { + assert.deepEqual(round(local(2010, 11, 31, 23, 59, 59, 999)), local(2011, 00, 01, 00, 00, 00)); + assert.deepEqual(round(local(2011, 00, 01, 00, 00, 00, 499)), local(2011, 00, 01, 00, 00, 00)); + assert.deepEqual(round(local(2011, 00, 01, 00, 00, 00, 500)), local(2011, 00, 01, 00, 00, 01)); + } + }, + "ceil": { + topic: function(interval) { + return interval.ceil; + }, + "returns seconds": function(ceil) { + assert.deepEqual(ceil(local(2010, 11, 31, 23, 59, 59, 999)), local(2011, 00, 01, 00, 00, 00)); + assert.deepEqual(ceil(local(2011, 00, 01, 00, 00, 00, 000)), local(2011, 00, 01, 00, 00, 00)); + assert.deepEqual(ceil(local(2011, 00, 01, 00, 00, 00, 001)), local(2011, 00, 01, 00, 00, 01)); + } + }, + "offset": { + topic: function(interval) { + return interval.offset; + }, + "does not modify the passed-in date": function(offset) { + var date = local(2010, 11, 31, 23, 59, 59, 999); + offset(date, +1); + assert.deepEqual(date, local(2010, 11, 31, 23, 59, 59, 999)); + }, + "does not round the passed-in-date": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 999), +1), local(2011, 00, 01, 00, 00, 00, 999)); + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 456), -2), local(2010, 11, 31, 23, 59, 57, 456)); + }, + "allows negative offsets": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59), -1), local(2010, 11, 31, 23, 59, 58)); + assert.deepEqual(offset(local(2011, 00, 01, 00, 00, 00), -2), local(2010, 11, 31, 23, 59, 58)); + assert.deepEqual(offset(local(2011, 00, 01, 00, 00, 00), -1), local(2010, 11, 31, 23, 59, 59)); + }, + "allows positive offsets": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 58), +1), local(2010, 11, 31, 23, 59, 59)); + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 58), +2), local(2011, 00, 01, 00, 00, 00)); + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59), +1), local(2011, 00, 01, 00, 00, 00)); + }, + "allows zero offset": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 999), 0), local(2010, 11, 31, 23, 59, 59, 999)); + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 58, 000), 0), local(2010, 11, 31, 23, 59, 58, 000)); + } + }, + "UTC": { + topic: function(interval) { + return interval.utc; + }, + "defaults to floor": function(interval) { + assert.strictEqual(interval, interval.floor); + }, + "floor": { + topic: function(interval) { + return interval.floor; + }, + "returns seconds": function(floor) { + assert.deepEqual(floor(utc(2010, 11, 31, 23, 59, 59, 999)), utc(2010, 11, 31, 23, 59, 59)); + assert.deepEqual(floor(utc(2011, 00, 01, 00, 00, 00, 000)), utc(2011, 00, 01, 00, 00, 00)); + assert.deepEqual(floor(utc(2011, 00, 01, 00, 00, 00, 001)), utc(2011, 00, 01, 00, 00, 00)); + } + }, + "round": { + topic: function(interval) { + return interval.round; + }, + "returns seconds": function(round) { + assert.deepEqual(round(utc(2010, 11, 31, 23, 59, 59, 999)), utc(2011, 00, 01, 00, 00, 00)); + assert.deepEqual(round(utc(2011, 00, 01, 00, 00, 00, 499)), utc(2011, 00, 01, 00, 00, 00)); + assert.deepEqual(round(utc(2011, 00, 01, 00, 00, 00, 500)), utc(2011, 00, 01, 00, 00, 01)); + } + }, + "ceil": { + topic: function(interval) { + return interval.ceil; + }, + "returns seconds": function(ceil) { + assert.deepEqual(ceil(utc(2010, 11, 31, 23, 59, 59, 999)), utc(2011, 00, 01, 00, 00, 00)); + assert.deepEqual(ceil(utc(2011, 00, 01, 00, 00, 00, 000)), utc(2011, 00, 01, 00, 00, 00)); + assert.deepEqual(ceil(utc(2011, 00, 01, 00, 00, 00, 001)), utc(2011, 00, 01, 00, 00, 01)); + } + }, + "offset": { + topic: function(interval) { + return interval.offset; + }, + "does not modify the passed-in date": function(offset) { + var date = utc(2010, 11, 31, 23, 59, 59, 999); + offset(date, +1); + assert.deepEqual(date, utc(2010, 11, 31, 23, 59, 59, 999)); + }, + "does not round the passed-in-date": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 999), +1), utc(2011, 00, 01, 00, 00, 00, 999)); + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 456), -2), utc(2010, 11, 31, 23, 59, 57, 456)); + }, + "allows negative offsets": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59), -1), utc(2010, 11, 31, 23, 59, 58)); + assert.deepEqual(offset(utc(2011, 00, 01, 00, 00, 00), -2), utc(2010, 11, 31, 23, 59, 58)); + assert.deepEqual(offset(utc(2011, 00, 01, 00, 00, 00), -1), utc(2010, 11, 31, 23, 59, 59)); + }, + "allows positive offsets": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 58), +1), utc(2010, 11, 31, 23, 59, 59)); + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 58), +2), utc(2011, 00, 01, 00, 00, 00)); + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59), +1), utc(2011, 00, 01, 00, 00, 00)); + }, + "allows zero offset": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 999), 0), utc(2010, 11, 31, 23, 59, 59, 999)); + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 58, 000), 0), utc(2010, 11, 31, 23, 59, 58, 000)); + } } } } }); function local(year, month, day, hours, minutes, seconds, milliseconds) { - return new Date(year, month, day, hours || 0, minutes || 0, seconds || 0, milliseconds || 0); + return new Date(year, month, day, hours || 00, minutes || 00, seconds || 00, milliseconds || 00); } function utc(year, month, day, hours, minutes, seconds, milliseconds) { - return new Date(Date.UTC(year, month, day, hours || 0, minutes || 0, seconds || 0, milliseconds || 0)); + return new Date(Date.UTC(year, month, day, hours || 00, minutes || 00, seconds || 00, milliseconds || 00)); } suite.export(module); diff --git a/test/time/week-test.js b/test/time/week-test.js index a8bd7c293266e..4b0251511f416 100644 --- a/test/time/week-test.js +++ b/test/time/week-test.js @@ -10,48 +10,158 @@ suite.addBatch({ topic: function() { return d3.time.week; }, - "returns weeks": function(floor) { - assert.deepEqual(floor(local(2010, 11, 31, 23, 59, 59)), local(2010, 11, 26)); - assert.deepEqual(floor(local(2011, 0, 1, 0, 0, 0)), local(2010, 11, 26)); - assert.deepEqual(floor(local(2011, 0, 1, 0, 0, 1)), local(2010, 11, 26)); - assert.deepEqual(floor(local(2011, 0, 1, 23, 59, 59)), local(2010, 11, 26)); - assert.deepEqual(floor(local(2011, 0, 2, 0, 0, 0)), local(2011, 0, 2)); - assert.deepEqual(floor(local(2011, 0, 2, 0, 0, 1)), local(2011, 0, 2)); + "defaults to floor": function(interval) { + assert.strictEqual(interval, interval.floor); }, - "observes the start of daylight savings time": function(floor) { - assert.deepEqual(floor(local(2011, 2, 13, 1)), local(2011, 2, 13)); + "floor": { + topic: function(interval) { + return interval.floor; + }, + "returns sundays": function(floor) { + assert.deepEqual(floor(local(2010, 11, 31, 23, 59, 59)), local(2010, 11, 26)); + assert.deepEqual(floor(local(2011, 00, 01, 00, 00, 00)), local(2010, 11, 26)); + assert.deepEqual(floor(local(2011, 00, 01, 00, 00, 01)), local(2010, 11, 26)); + assert.deepEqual(floor(local(2011, 00, 01, 23, 59, 59)), local(2010, 11, 26)); + assert.deepEqual(floor(local(2011, 00, 02, 00, 00, 00)), local(2011, 00, 02)); + assert.deepEqual(floor(local(2011, 00, 02, 00, 00, 01)), local(2011, 00, 02)); + }, + "observes the start of daylight savings time": function(floor) { + assert.deepEqual(floor(local(2011, 02, 13, 01)), local(2011, 02, 13)); + }, + "observes the end of the daylight savings time": function(floor) { + assert.deepEqual(floor(local(2011, 10, 06, 01)), local(2011, 10, 06)); + } }, - "observes the end of the daylight savings time": function(floor) { - assert.deepEqual(floor(local(2011, 10, 6, 1)), local(2011, 10, 6)); + "ceil": { + topic: function(interval) { + return interval.ceil; + }, + "returns sundays": function(ceil) { + assert.deepEqual(ceil(local(2010, 11, 31, 23, 59, 59)), local(2011, 00, 02)); + assert.deepEqual(ceil(local(2011, 00, 01, 00, 00, 00)), local(2011, 00, 02)); + assert.deepEqual(ceil(local(2011, 00, 01, 00, 00, 01)), local(2011, 00, 02)); + assert.deepEqual(ceil(local(2011, 00, 01, 23, 59, 59)), local(2011, 00, 02)); + assert.deepEqual(ceil(local(2011, 00, 02, 00, 00, 00)), local(2011, 00, 02)); + assert.deepEqual(ceil(local(2011, 00, 02, 00, 00, 01)), local(2011, 00, 09)); + }, + "observes the start of daylight savings time": function(ceil) { + assert.deepEqual(ceil(local(2011, 02, 13, 01)), local(2011, 02, 20)); + }, + "observes the end of the daylight savings time": function(ceil) { + assert.deepEqual(ceil(local(2011, 10, 06, 01)), local(2011, 10, 13)); + } + }, + "offset": { + topic: function(interval) { + return interval.offset; + }, + "does not modify the passed-in date": function(offset) { + var date = local(2010, 11, 31, 23, 59, 59, 999); + offset(date, +1); + assert.deepEqual(date, local(2010, 11, 31, 23, 59, 59, 999)); + }, + "does not round the passed-in-date": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 999), +1), local(2011, 00, 07, 23, 59, 59, 999)); + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 456), -2), local(2010, 11, 17, 23, 59, 59, 456)); + }, + "allows negative offsets": function(offset) { + assert.deepEqual(offset(local(2010, 11, 01), -1), local(2010, 10, 24)); + assert.deepEqual(offset(local(2011, 00, 01), -2), local(2010, 11, 18)); + assert.deepEqual(offset(local(2011, 00, 01), -1), local(2010, 11, 25)); + }, + "allows positive offsets": function(offset) { + assert.deepEqual(offset(local(2010, 10, 24), +1), local(2010, 11, 01)); + assert.deepEqual(offset(local(2010, 11, 18), +2), local(2011, 00, 01)); + assert.deepEqual(offset(local(2010, 11, 25), +1), local(2011, 00, 01)); + }, + "allows zero offset": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 999), 0), local(2010, 11, 31, 23, 59, 59, 999)); + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 58, 000), 0), local(2010, 11, 31, 23, 59, 58, 000)); + } }, "UTC": { - topic: function(floor) { - return floor.utc; - }, - "returns weeks": function(floor) { - assert.deepEqual(floor(utc(2010, 11, 31, 23, 59, 59)), utc(2010, 11, 26)); - assert.deepEqual(floor(utc(2011, 0, 1, 0, 0, 0)), utc(2010, 11, 26)); - assert.deepEqual(floor(utc(2011, 0, 1, 0, 0, 1)), utc(2010, 11, 26)); - assert.deepEqual(floor(utc(2011, 0, 1, 23, 59, 59)), utc(2010, 11, 26)); - assert.deepEqual(floor(utc(2011, 0, 2, 0, 0, 0)), utc(2011, 0, 2)); - assert.deepEqual(floor(utc(2011, 0, 2, 0, 0, 1)), utc(2011, 0, 2)); - }, - "does not observe the start of daylight savings time": function(floor) { - assert.deepEqual(floor(utc(2011, 2, 13, 1)), utc(2011, 2, 13)); - }, - "does not observe the end of the daylight savings time": function(floor) { - assert.deepEqual(floor(utc(2011, 10, 6, 1)), utc(2011, 10, 6)); + topic: function(interval) { + return interval.utc; + }, + "defaults to floor": function(interval) { + assert.strictEqual(interval, interval.floor); + }, + "floor": { + topic: function(interval) { + return interval.floor; + }, + "returns sundays": function(floor) { + assert.deepEqual(floor(utc(2010, 11, 31, 23, 59, 59)), utc(2010, 11, 26)); + assert.deepEqual(floor(utc(2011, 00, 01, 00, 00, 00)), utc(2010, 11, 26)); + assert.deepEqual(floor(utc(2011, 00, 01, 00, 00, 01)), utc(2010, 11, 26)); + assert.deepEqual(floor(utc(2011, 00, 01, 23, 59, 59)), utc(2010, 11, 26)); + assert.deepEqual(floor(utc(2011, 00, 02, 00, 00, 00)), utc(2011, 00, 02)); + assert.deepEqual(floor(utc(2011, 00, 02, 00, 00, 01)), utc(2011, 00, 02)); + }, + "does not observe the start of daylight savings time": function(floor) { + assert.deepEqual(floor(utc(2011, 02, 13, 01)), utc(2011, 02, 13)); + }, + "does not observe the end of the daylight savings time": function(floor) { + assert.deepEqual(floor(utc(2011, 10, 06, 01)), utc(2011, 10, 06)); + } + }, + "ceil": { + topic: function(interval) { + return interval.ceil; + }, + "returns sundays": function(ceil) { + assert.deepEqual(ceil(utc(2010, 11, 31, 23, 59, 59)), utc(2011, 00, 02)); + assert.deepEqual(ceil(utc(2011, 00, 01, 00, 00, 00)), utc(2011, 00, 02)); + assert.deepEqual(ceil(utc(2011, 00, 01, 00, 00, 01)), utc(2011, 00, 02)); + assert.deepEqual(ceil(utc(2011, 00, 01, 23, 59, 59)), utc(2011, 00, 02)); + assert.deepEqual(ceil(utc(2011, 00, 02, 00, 00, 00)), utc(2011, 00, 02)); + assert.deepEqual(ceil(utc(2011, 00, 02, 00, 00, 01)), utc(2011, 00, 09)); + }, + "does not observe the start of daylight savings time": function(ceil) { + assert.deepEqual(ceil(utc(2011, 02, 13, 01)), utc(2011, 02, 20)); + }, + "does not observe the end of the daylight savings time": function(ceil) { + assert.deepEqual(ceil(utc(2011, 10, 06, 01)), utc(2011, 10, 13)); + } + } + }, + "offset": { + topic: function(interval) { + return interval.offset; + }, + "does not modify the passed-in date": function(offset) { + var date = utc(2010, 11, 31, 23, 59, 59, 999); + offset(date, +1); + assert.deepEqual(date, utc(2010, 11, 31, 23, 59, 59, 999)); + }, + "does not round the passed-in-date": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 999), +1), utc(2011, 00, 07, 23, 59, 59, 999)); + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 456), -2), utc(2010, 11, 17, 23, 59, 59, 456)); + }, + "allows negative offsets": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 01), -1), utc(2010, 10, 24)); + assert.deepEqual(offset(utc(2011, 00, 01), -2), utc(2010, 11, 18)); + assert.deepEqual(offset(utc(2011, 00, 01), -1), utc(2010, 11, 25)); + }, + "allows positive offsets": function(offset) { + assert.deepEqual(offset(utc(2010, 10, 24), +1), utc(2010, 11, 01)); + assert.deepEqual(offset(utc(2010, 11, 18), +2), utc(2011, 00, 01)); + assert.deepEqual(offset(utc(2010, 11, 25), +1), utc(2011, 00, 01)); + }, + "allows zero offset": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 999), 0), utc(2010, 11, 31, 23, 59, 59, 999)); + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 58, 000), 0), utc(2010, 11, 31, 23, 59, 58, 000)); } } } }); function local(year, month, day, hours, minutes, seconds) { - return new Date(year, month, day, hours || 0, minutes || 0, seconds || 0); + return new Date(year, month, day, hours || 00, minutes || 00, seconds || 00); } function utc(year, month, day, hours, minutes, seconds) { - return new Date(Date.UTC(year, month, day, hours || 0, minutes || 0, seconds || 0)); + return new Date(Date.UTC(year, month, day, hours || 00, minutes || 00, seconds || 00)); } suite.export(module); diff --git a/test/time/year-test.js b/test/time/year-test.js index 01b0336344d79..c58ee8f018aa0 100644 --- a/test/time/year-test.js +++ b/test/time/year-test.js @@ -10,30 +10,122 @@ suite.addBatch({ topic: function() { return d3.time.year; }, - "returns years": function(floor) { - assert.deepEqual(floor(local(2010, 11, 31, 23, 59, 59)), local(2010, 0, 1)); - assert.deepEqual(floor(local(2011, 0, 1, 0, 0, 0)), local(2011, 0, 1)); - assert.deepEqual(floor(local(2011, 0, 1, 0, 0, 1)), local(2011, 0, 1)); + "defaults to floor": function(interval) { + assert.strictEqual(interval, interval.floor); }, - "UTC": { - topic: function(floor) { - return floor.utc; + "floor": { + topic: function(interval) { + return interval.floor; }, "returns years": function(floor) { - assert.deepEqual(floor(utc(2010, 11, 31, 23, 59, 59)), utc(2010, 0, 1)); - assert.deepEqual(floor(utc(2011, 0, 1, 0, 0, 0)), utc(2011, 0, 1)); - assert.deepEqual(floor(utc(2011, 0, 1, 0, 0, 1)), utc(2011, 0, 1)); + assert.deepEqual(floor(local(2010, 11, 31, 23, 59, 59)), local(2010, 00, 01)); + assert.deepEqual(floor(local(2011, 00, 01, 00, 00, 00)), local(2011, 00, 01)); + assert.deepEqual(floor(local(2011, 00, 01, 00, 00, 01)), local(2011, 00, 01)); + } + }, + "ceil": { + topic: function(interval) { + return interval.ceil; + }, + "returns years": function(ceil) { + assert.deepEqual(ceil(local(2010, 11, 31, 23, 59, 59)), local(2011, 00, 01)); + assert.deepEqual(ceil(local(2011, 00, 01, 00, 00, 00)), local(2011, 00, 01)); + assert.deepEqual(ceil(local(2011, 00, 01, 00, 00, 01)), local(2012, 00, 01)); + } + }, + "offset": { + topic: function(interval) { + return interval.offset; + }, + "does not modify the passed-in date": function(offset) { + var date = local(2010, 11, 31, 23, 59, 59, 999); + offset(date, +1); + assert.deepEqual(date, local(2010, 11, 31, 23, 59, 59, 999)); + }, + "does not round the passed-in-date": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 999), +1), local(2011, 11, 31, 23, 59, 59, 999)); + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 456), -2), local(2008, 11, 31, 23, 59, 59, 456)); + }, + "allows negative offsets": function(offset) { + assert.deepEqual(offset(local(2010, 11, 01), -1), local(2009, 11, 01)); + assert.deepEqual(offset(local(2011, 00, 01), -2), local(2009, 00, 01)); + assert.deepEqual(offset(local(2011, 00, 01), -1), local(2010, 00, 01)); + }, + "allows positive offsets": function(offset) { + assert.deepEqual(offset(local(2009, 11, 01), +1), local(2010, 11, 01)); + assert.deepEqual(offset(local(2009, 00, 01), +2), local(2011, 00, 01)); + assert.deepEqual(offset(local(2010, 00, 01), +1), local(2011, 00, 01)); + }, + "allows zero offset": function(offset) { + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 59, 999), 0), local(2010, 11, 31, 23, 59, 59, 999)); + assert.deepEqual(offset(local(2010, 11, 31, 23, 59, 58, 000), 0), local(2010, 11, 31, 23, 59, 58, 000)); + } + }, + "UTC": { + topic: function(interval) { + return interval.utc; + }, + "defaults to floor": function(interval) { + assert.strictEqual(interval, interval.floor); + }, + "floor": { + topic: function(interval) { + return interval.floor; + }, + "returns years": function(floor) { + assert.deepEqual(floor(utc(2010, 11, 31, 23, 59, 59)), utc(2010, 00, 01)); + assert.deepEqual(floor(utc(2011, 00, 01, 00, 00, 00)), utc(2011, 00, 01)); + assert.deepEqual(floor(utc(2011, 00, 01, 00, 00, 01)), utc(2011, 00, 01)); + } + }, + "ceil": { + topic: function(interval) { + return interval.ceil; + }, + "returns years": function(ceil) { + assert.deepEqual(ceil(utc(2010, 11, 31, 23, 59, 59)), utc(2011, 00, 01)); + assert.deepEqual(ceil(utc(2011, 00, 01, 00, 00, 00)), utc(2011, 00, 01)); + assert.deepEqual(ceil(utc(2011, 00, 01, 00, 00, 01)), utc(2012, 00, 01)); + } + }, + "offset": { + topic: function(interval) { + return interval.offset; + }, + "does not modify the passed-in date": function(offset) { + var date = utc(2010, 11, 31, 23, 59, 59, 999); + offset(date, +1); + assert.deepEqual(date, utc(2010, 11, 31, 23, 59, 59, 999)); + }, + "does not round the passed-in-date": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 999), +1), utc(2011, 11, 31, 23, 59, 59, 999)); + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 456), -2), utc(2008, 11, 31, 23, 59, 59, 456)); + }, + "allows negative offsets": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 01), -1), utc(2009, 11, 01)); + assert.deepEqual(offset(utc(2011, 00, 01), -2), utc(2009, 00, 01)); + assert.deepEqual(offset(utc(2011, 00, 01), -1), utc(2010, 00, 01)); + }, + "allows positive offsets": function(offset) { + assert.deepEqual(offset(utc(2009, 11, 01), +1), utc(2010, 11, 01)); + assert.deepEqual(offset(utc(2009, 00, 01), +2), utc(2011, 00, 01)); + assert.deepEqual(offset(utc(2010, 00, 01), +1), utc(2011, 00, 01)); + }, + "allows zero offset": function(offset) { + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 59, 999), 0), utc(2010, 11, 31, 23, 59, 59, 999)); + assert.deepEqual(offset(utc(2010, 11, 31, 23, 59, 58, 000), 0), utc(2010, 11, 31, 23, 59, 58, 000)); + } } } } }); function local(year, month, day, hours, minutes, seconds) { - return new Date(year, month, day, hours || 0, minutes || 0, seconds || 0); + return new Date(year, month, day, hours || 00, minutes || 00, seconds || 00); } function utc(year, month, day, hours, minutes, seconds) { - return new Date(Date.UTC(year, month, day, hours || 0, minutes || 0, seconds || 0)); + return new Date(Date.UTC(year, month, day, hours || 00, minutes || 00, seconds || 00)); } suite.export(module);