Skip to content

Commit

Permalink
Allow custom time intervals. Fixes #7.
Browse files Browse the repository at this point in the history
  • Loading branch information
mbostock committed Jul 6, 2015
1 parent be1a261 commit 040ea4a
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 27 deletions.
19 changes: 14 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -436,16 +436,18 @@ Note: the [default interpolator](https://github.com/d3/d3-interpolate#interpolat
If *clamp* is specified, enables or disables clamping accordingly. If clamping is disabled and the scale is passed a value outside the [domain](#time_domain), the scale may return a value outside the [range](#time_range) through extrapolation. If clamping is enabled, the return value of the scale is always within the scale’s range. Clamping similarly applies to [*time*.invert](#time_invert). See [*linear*.clamp](#linear_clamp) for examples. If *clamp* is not specified, returns whether or not the scale currently clamps values to within the range.

<a name="time_nice" href="#time_nice">#</a> <i>time</i>.<b>nice</b>([<i>count</i>])
<br><a name="time_nice" href="#time_nice">#</a> <i>time</i>.<b>nice</b>([<i>interval</i>[, <i>step</i>]])
<br><a name="time_nice" href="#time_nice">#</a> <i>time</i>.<b>nice</b>([<i>intervalName</i>[, <i>step</i>]])
<br><a name="time_nice" href="#time_nice">#</a> <i>time</i>.<b>nice</b>([<i>interval</i>])

Extends the [domain](#time_domain) so that it starts and ends on nice round values. This method typically modifies the scale’s domain, and may only extend the bounds to the nearest round value.

An optional tick *count* argument allows greater control over the step size used to extend the bounds, guaranteeing that the returned [ticks](#time_ticks) will exactly cover the domain. Alternatively, the name of a time *interval* may be specified to explicitly set the ticks. If an *interval* name is specified, an optional *step* may also be specified to skip some ticks. For example, `nice("seconds", 10)` will extend the domain to an even ten seconds (0, 10, 20, <i>etc.</i>). See [*time*.ticks](#time_ticks) for further detail.
An optional tick *count* argument allows greater control over the step size used to extend the bounds, guaranteeing that the returned [ticks](#time_ticks) will exactly cover the domain. Alternatively, a [time *interval*](https://github.com/d3/d3-time#intervals) or *intervalName* may be specified to explicitly set the ticks. If an *intervalName* is specified, an optional *step* may also be specified to skip some ticks. For example, `nice("seconds", 10)` will extend the domain to an even ten seconds (0, 10, 20, <i>etc.</i>). See [*time*.ticks](#time_ticks) for further detail.

Nicing is useful if the domain is computed from data, say using [extent](https://github.com/d3/d3-arrays#extent), and may be irregular. For example, for a domain of [2009-07-13T00:02, 2009-07-13T23:48], the nice domain is [2009-07-13, 2009-07-14]. If the domain has more than two values, nicing the domain only affects the first and last value.

<a name="time_ticks" href="#time_ticks">#</a> <i>time</i>.<b>ticks</b>([<i>count</i>])
<br><a name="time_ticks" href="#time_ticks">#</a> <i>time</i>.<b>ticks</b>([<i>interval</i>[, <i>step</i>]])
<br><a name="time_ticks" href="#time_ticks">#</a> <i>time</i>.<b>ticks</b>([<i>intervalName</i>[, <i>step</i>]])
<br><a name="time_ticks" href="#time_ticks">#</a> <i>time</i>.<b>ticks</b>([<i>interval</i>])

Returns representative dates from the scale’s [domain](#time_domain). The returned tick values are uniformly-spaced (mostly), have sensible values (such every day at midnight), and are guaranteed to be within the extent of the domain. Ticks are often used to display reference lines, or tick marks, in conjunction with the visualized data.

Expand Down Expand Up @@ -475,7 +477,7 @@ The following time intervals are considered for automatic ticks:
* 1- and 3-month.
* 1-year.

In lieu of a *count*, the name of a time *interval* may be explicitly specified. The following interval names are supported:
In lieu of a *count*, a [time *interval*](https://github.com/d3/d3-time#intervals) or *intervalName* may be explicitly specified. The following interval names are supported:

* `milliseconds`
* `seconds`
Expand All @@ -486,7 +488,7 @@ In lieu of a *count*, the name of a time *interval* may be explicitly specified.
* `months`
* `years`

If an *interval* name is specified, an optional *step* may also be specified to prune generated ticks. For example, `ticks("minutes", 15)` will generate ticks at 15-minute intervals:
If an *intervalName* is specified, an optional *step* may also be specified to prune generated ticks. For example, `ticks("minutes", 15)` will generate ticks at 15-minute intervals:

```js
var s = time().domain([new Date(2000, 0, 1, 0), new Date(2000, 0, 1, 2)]);
Expand All @@ -502,6 +504,13 @@ s.ticks("minutes", 15);
// Sat Jan 01 2000 02:00:00 GMT-0800 (PST)]
```

This is equivalent to [minute](https://github.com/d3/d3-time#minute).[filter](https://github.com/d3/d3-time#interval_filter) as follows:

```js
var s = time().domain([new Date(2000, 0, 1, 0), new Date(2000, 0, 1, 2)]);
s.ticks(minute.filter(function(d) { return d.getMinutes() % 15 === 0; }));
```

Note: in some cases, such as with day ticks, specifying a *step* can result in irregular spacing of ticks because months have varying length.

<a name="time_tickFormat" href="#time_tickFormat">#</a> <i>time</i>.<b>tickFormat</b>([<i>specifier</i>])
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "d3-scale",
"version": "0.1.0",
"version": "0.1.1",
"description": "Encodings that map abstract data to visual representation.",
"keywords": [
"d3",
Expand Down
20 changes: 7 additions & 13 deletions src/time.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,24 +29,18 @@ export function newTime(linear, timeInterval, tickFormat, format) {

// If a desired tick count is specified, pick a reasonable tick interval
// based on the extent of the domain and a rough estimate of tick size.
if (typeof interval === "number") {
interval = chooseTickInterval(start, stop, interval);
step = interval[1], interval = interval[0];
}

// Otherwise, a named interval such as "seconds" was specified.
// If a step is also specified, then skip some ticks.
else {
step = step == null ? 1 : Math.floor(step), interval += "";
// If a named interval such as "seconds" was specified, convert to the
// corresponding time interval and optionally filter using the step.
// Otherwise, assume interval is already a time interval and use it.
switch (typeof interval) {
case "number": interval = chooseTickInterval(start, stop, interval), step = interval[1], interval = interval[0]; break;
case "string": step = step == null ? 1 : Math.floor(step); break;
default: return interval;
}

return isFinite(step) && step > 0 ? timeInterval(interval, step) : null;
}

// ticks() - generate about ten ticks
// ticks(10) - generate about ten ticks
// ticks("seconds") - generate a tick every second
// ticks("seconds", 10) - generate a tick every ten seconds
scale.ticks = function(interval, step) {
var domain = linear.domain(),
t0 = domain[0],
Expand Down
29 changes: 25 additions & 4 deletions test/time-test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
var tape = require("tape"),
color = require("d3-color"),
time = require("d3-time"),
scale = require("../"),
date = require("./date");

Expand Down Expand Up @@ -35,6 +36,15 @@ tape("time.nice(count) nices using the specified tick count", function(test) {
});

tape("time.nice(interval) nices using the specified time interval", function(test) {
var x = scale.time().domain([date.local(2009, 0, 1, 0, 12), date.local(2009, 0, 1, 23, 48)]);
test.deepEqual(x.nice(time.day).domain(), [date.local(2009, 0, 1), date.local(2009, 0, 2)]);
test.deepEqual(x.nice(time.week).domain(), [date.local(2008, 11, 28), date.local(2009, 0, 4)]);
test.deepEqual(x.nice(time.month).domain(), [date.local(2008, 11, 1), date.local(2009, 1, 1)]);
test.deepEqual(x.nice(time.year).domain(), [date.local(2008, 0, 1), date.local(2010, 0, 1)]);
test.end();
});

tape("time.nice(intervalName) nices using the specified named time interval", function(test) {
var x = scale.time().domain([date.local(2009, 0, 1, 0, 12), date.local(2009, 0, 1, 23, 48)]);
test.deepEqual(x.nice("days").domain(), [date.local(2009, 0, 1), date.local(2009, 0, 2)]);
test.deepEqual(x.nice("weeks").domain(), [date.local(2008, 11, 28), date.local(2009, 0, 4)]);
Expand All @@ -43,19 +53,19 @@ tape("time.nice(interval) nices using the specified time interval", function(tes
test.end();
});

tape("time.nice(interval) can nice empty domains", function(test) {
tape("time.nice(intervalName) can nice empty domains", function(test) {
var x = scale.time().domain([date.local(2009, 0, 1, 0, 12), date.local(2009, 0, 1, 0, 12)]);
test.deepEqual(x.nice("days").domain(), [date.local(2009, 0, 1), date.local(2009, 0, 2)]);
test.end();
});

tape("time.nice(interval) can nice a polylinear domain, only affecting its extent", function(test) {
tape("time.nice(intervalName) can nice a polylinear domain, only affecting its extent", function(test) {
var x = scale.time().domain([date.local(2009, 0, 1, 0, 12), date.local(2009, 0, 1, 23, 48), date.local(2009, 0, 2, 23, 48)]).nice("days");
test.deepEqual(x.domain(), [date.local(2009, 0, 1), date.local(2009, 0, 1, 23, 48), date.local(2009, 0, 3)]);
test.end();
});

tape("time.nice(interval, step) nices using the specified time interval and step", function(test) {
tape("time.nice(intervalName, step) nices using the specified time interval and step", function(test) {
var x = scale.time().domain([date.local(2009, 0, 1, 0, 12), date.local(2009, 0, 1, 23, 48)]);
test.deepEqual(x.nice("days", 3).domain(), [date.local(2009, 0, 1), date.local(2009, 0, 4)]);
test.deepEqual(x.nice("weeks", 2).domain(), [date.local(2008, 11, 21), date.local(2009, 0, 4)]);
Expand Down Expand Up @@ -117,6 +127,17 @@ tape("time.copy() isolates changes to clamping", function(test) {
});

tape("time.ticks(interval) observes the specified tick interval", function(test) {
var x = scale.time().domain([date.local(2011, 0, 1, 12, 1, 0), date.local(2011, 0, 1, 12, 4, 4)]);
test.deepEqual(x.ticks(time.minute), [
date.local(2011, 0, 1, 12, 1),
date.local(2011, 0, 1, 12, 2),
date.local(2011, 0, 1, 12, 3),
date.local(2011, 0, 1, 12, 4)
]);
test.end();
});

tape("time.ticks(intervalName) observes the specified named tick interval", function(test) {
var x = scale.time().domain([date.local(2011, 0, 1, 12, 1, 0), date.local(2011, 0, 1, 12, 4, 4)]);
test.deepEqual(x.ticks("minutes"), [
date.local(2011, 0, 1, 12, 1),
Expand All @@ -127,7 +148,7 @@ tape("time.ticks(interval) observes the specified tick interval", function(test)
test.end();
});

tape("time.ticks(interval, step) observes the specified tick interval and step", function(test) {
tape("time.ticks(intervalName, step) observes the specified tick interval and step", function(test) {
var x = scale.time().domain([date.local(2011, 0, 1, 12, 0, 0), date.local(2011, 0, 1, 12, 33, 4)]);
test.deepEqual(x.ticks("minutes", 10), [
date.local(2011, 0, 1, 12, 0),
Expand Down
29 changes: 25 additions & 4 deletions test/utcTime-test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
var tape = require("tape"),
color = require("d3-color"),
time = require("d3-time"),
scale = require("../"),
date = require("./date");

Expand Down Expand Up @@ -35,6 +36,15 @@ tape("utcTime.nice(count) nices using the specified tick count", function(test)
});

tape("utcTime.nice(interval) nices using the specified time interval", function(test) {
var x = scale.utcTime().domain([date.utc(2009, 0, 1, 0, 12), date.utc(2009, 0, 1, 23, 48)]);
test.deepEqual(x.nice(time.utcDay).domain(), [date.utc(2009, 0, 1), date.utc(2009, 0, 2)]);
test.deepEqual(x.nice(time.utcWeek).domain(), [date.utc(2008, 11, 28), date.utc(2009, 0, 4)]);
test.deepEqual(x.nice(time.utcMonth).domain(), [date.utc(2008, 11, 1), date.utc(2009, 1, 1)]);
test.deepEqual(x.nice(time.utcYear).domain(), [date.utc(2008, 0, 1), date.utc(2010, 0, 1)]);
test.end();
});

tape("utcTime.nice(intervalName) nices using the specified named time interval", function(test) {
var x = scale.utcTime().domain([date.utc(2009, 0, 1, 0, 12), date.utc(2009, 0, 1, 23, 48)]);
test.deepEqual(x.nice("days").domain(), [date.utc(2009, 0, 1), date.utc(2009, 0, 2)]);
test.deepEqual(x.nice("weeks").domain(), [date.utc(2008, 11, 28), date.utc(2009, 0, 4)]);
Expand All @@ -43,19 +53,19 @@ tape("utcTime.nice(interval) nices using the specified time interval", function(
test.end();
});

tape("utcTime.nice(interval) can nice empty domains", function(test) {
tape("utcTime.nice(intervalName) can nice empty domains", function(test) {
var x = scale.utcTime().domain([date.utc(2009, 0, 1, 0, 12), date.utc(2009, 0, 1, 0, 12)]);
test.deepEqual(x.nice("days").domain(), [date.utc(2009, 0, 1), date.utc(2009, 0, 2)]);
test.end();
});

tape("utcTime.nice(interval) can nice a polylinear domain, only affecting its extent", function(test) {
tape("utcTime.nice(intervalName) can nice a polylinear domain, only affecting its extent", function(test) {
var x = scale.utcTime().domain([date.utc(2009, 0, 1, 0, 12), date.utc(2009, 0, 1, 23, 48), date.utc(2009, 0, 2, 23, 48)]).nice("days");
test.deepEqual(x.domain(), [date.utc(2009, 0, 1), date.utc(2009, 0, 1, 23, 48), date.utc(2009, 0, 3)]);
test.end();
});

tape("utcTime.nice(interval, step) nices using the specified time interval and step", function(test) {
tape("utcTime.nice(intervalName, step) nices using the specified time interval and step", function(test) {
var x = scale.utcTime().domain([date.utc(2009, 0, 1, 0, 12), date.utc(2009, 0, 1, 23, 48)]);
test.deepEqual(x.nice("days", 3).domain(), [date.utc(2009, 0, 1), date.utc(2009, 0, 4)]);
test.deepEqual(x.nice("weeks", 2).domain(), [date.utc(2008, 11, 21), date.utc(2009, 0, 4)]);
Expand Down Expand Up @@ -117,6 +127,17 @@ tape("utcTime.copy() isolates changes to clamping", function(test) {
});

tape("utcTime.ticks(interval) observes the specified tick interval", function(test) {
var x = scale.utcTime().domain([date.utc(2011, 0, 1, 12, 1, 0), date.utc(2011, 0, 1, 12, 4, 4)]);
test.deepEqual(x.ticks(time.utcMinute), [
date.utc(2011, 0, 1, 12, 1),
date.utc(2011, 0, 1, 12, 2),
date.utc(2011, 0, 1, 12, 3),
date.utc(2011, 0, 1, 12, 4)
]);
test.end();
});

tape("utcTime.ticks(intervalName) observes the specified named tick interval", function(test) {
var x = scale.utcTime().domain([date.utc(2011, 0, 1, 12, 1, 0), date.utc(2011, 0, 1, 12, 4, 4)]);
test.deepEqual(x.ticks("minutes"), [
date.utc(2011, 0, 1, 12, 1),
Expand All @@ -127,7 +148,7 @@ tape("utcTime.ticks(interval) observes the specified tick interval", function(te
test.end();
});

tape("utcTime.ticks(interval, step) observes the specified tick interval and step", function(test) {
tape("utcTime.ticks(intervalName, step) observes the specified tick interval and step", function(test) {
var x = scale.utcTime().domain([date.utc(2011, 0, 1, 12, 0, 0), date.utc(2011, 0, 1, 12, 33, 4)]);
test.deepEqual(x.ticks("minutes", 10), [
date.utc(2011, 0, 1, 12, 0),
Expand Down

0 comments on commit 040ea4a

Please sign in to comment.