diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 5aaf69e194..351b8e372d 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -68,6 +68,7 @@ export default defineConfig({ {text: "Legends", link: "/features/legends"}, {text: "Curves", link: "/features/curves"}, {text: "Formats", link: "/features/formats"}, + {text: "Intervals", link: "/features/intervals"}, {text: "Markers", link: "/features/markers"}, {text: "Shorthand", link: "/features/shorthand"}, {text: "Accessibility", link: "/features/accessibility"} diff --git a/docs/data/api.data.ts b/docs/data/api.data.ts index 3efbb7c156..bbabdd8cce 100644 --- a/docs/data/api.data.ts +++ b/docs/data/api.data.ts @@ -43,6 +43,7 @@ function getHref(name: string, path: string): string { switch (path) { case "features/curve": case "features/format": + case "features/interval": case "features/mark": case "features/marker": case "features/plot": diff --git a/docs/features/intervals.md b/docs/features/intervals.md new file mode 100644 index 0000000000..835c8d7c22 --- /dev/null +++ b/docs/features/intervals.md @@ -0,0 +1,61 @@ + + +# Intervals + +Plot provides several built-in interval implementations for use with the **tick** option for [scales](./scales.md), as the **thresholds** option for a [bin transform](../transforms/bin.md), or other use. See also [d3-time](https://d3js.org/d3-time). You can also implement custom intervals. + +At a minimum, intervals implement *interval*.**floor** and *interval*.**offset**. Range intervals additionally implement *interval*.**range**, and nice intervals additionally implement *interval*.**ceil**. These latter implementations are required in some contexts; see Plot’s TypeScript definitions for details. + +The *interval*.**floor** method takes a *value* and returns the corresponding value representing the greatest interval boundary less than or equal to the specified *value*. For example, for the “day” time interval, it returns the preceding midnight: + +```js +Plot.utcInterval("day").floor(new Date("2013-04-12T12:34:56Z")) // 2013-04-12 +``` + +The *interval*.**offset** method takes a *value* and returns the corresponding value equal to *value* plus *step* intervals. If *step* is not specified it defaults to 1. If *step* is negative, then the returned value will be less than the specified *value*. For example: + +```js +Plot.utcInterval("day").offset(new Date("2013-04-12T12:34:56Z"), 1) // 2013-04-13T12:34:56Z +Plot.utcInterval("day").offset(new Date("2013-04-12T12:34:56Z"), -2) // 2013-03-22T12:34:56Z +``` + +The *interval*.**range** method returns an array of values representing every interval boundary greater than or equal to *start* (inclusive) and less than *stop* (exclusive). The first value in the returned array is the least boundary greater than or equal to *start*; subsequent values are offset by intervals and floored. + +```js +Plot.utcInterval("week").range(new Date("2013-04-12T12:34:56Z"), new Date("2013-05-12T12:34:56Z")) // [2013-04-14, 2013-04-21, 2013-04-28, 2013-05-05, 2013-05-12] +``` + +The *interval*.**ceil** method returns the value representing the least interval boundary value greater than or equal to the specified *value*. For example, for the “day” time interval, it returns the preceding midnight: + +```js +Plot.utcInterval("day").ceil(new Date("2013-04-12T12:34:56Z")) // 2013-04-13 +``` + +## numberInterval(*period*) {#numberInterval} + +```js +Plot.numberInterval(2) +``` + +Given a number *period*, returns a corresponding range interval implementation. If *period* is a negative number, the resulting interval uses 1 / -*period*; this allows more precise results when *period* is a negative integer. The returned interval implements the *interval*.range, *interval*.floor, and *interval*.offset methods. + +## timeInterval(*period*) {#timeInterval} + +```js +Plot.timeInterval("2 days") +``` + +Given a string *period* describing a local time interval, returns a corresponding nice interval implementation. The period can be *second*, *minute*, *hour*, *day*, *week*, *month*, *quarter*, *half*, *year*, *monday*, *tuesday*, *wednesday*, *thursday*, *friday*, *saturday*, or *sunday*, or a skip interval consisting of a number followed by the interval name (possibly pluralized), such as *3 months* or *10 years*. The returned interval implements the *interval*.range, *interval*.floor, *interval*.ceil, and *interval*.offset methods. + +## utcInterval(*period*) {#utcInterval} + +```js +Plot.utcInterval("2 days") +``` + +Given a string *period* describing a UTC time interval, returns a corresponding nice interval implementation. The period can be *second*, *minute*, *hour*, *day*, *week*, *month*, *quarter*, *half*, *year*, *monday*, *tuesday*, *wednesday*, *thursday*, *friday*, *saturday*, or *sunday*, or a skip interval consisting of a number followed by the interval name (possibly pluralized), such as *3 months* or *10 years*. The returned interval implements the *interval*.range, *interval*.floor, *interval*.ceil, and *interval*.offset methods. diff --git a/src/index.js b/src/index.js index c3a87668ae..df33d998d4 100644 --- a/src/index.js +++ b/src/index.js @@ -56,3 +56,5 @@ export {pointer, pointerX, pointerY} from "./interactions/pointer.js"; export {formatIsoDate, formatNumber, formatWeekday, formatMonth} from "./format.js"; export {scale} from "./scales.js"; export {legend} from "./legends.js"; +export {numberInterval} from "./options.js"; +export {timeInterval, utcInterval} from "./time.js"; diff --git a/src/interval.d.ts b/src/interval.d.ts index 29882a04ff..2c76a015a4 100644 --- a/src/interval.d.ts +++ b/src/interval.d.ts @@ -1,4 +1,4 @@ -// For internal use. +/** A named interval. */ export type LiteralTimeInterval = | "3 months" | "10 years" @@ -124,3 +124,16 @@ export type RangeInterval = LiteralInterval | RangeIntervalImplement * - a number (for number intervals), defining intervals at integer multiples of *n* */ export type NiceInterval = LiteralInterval | NiceIntervalImplementation; + +/** + * Given a number *period*, returns a corresponding numeric range interval. If + * *period* is a negative number, the returned interval uses 1 / -*period*, + * allowing greater precision when *period* is a negative integer. + */ +export function numberInterval(period: number): RangeIntervalImplementation; + +/** Given a string *period*, returns a corresponding local time nice interval. */ +export function timeInterval(period: LiteralTimeInterval): NiceIntervalImplementation; + +/** Given a string *period*, returns a corresponding UTC nice interval. */ +export function utcInterval(period: LiteralTimeInterval): NiceIntervalImplementation; diff --git a/src/options.js b/src/options.js index 387daa94fb..a170e4c003 100644 --- a/src/options.js +++ b/src/options.js @@ -1,7 +1,7 @@ import {quantile, range as rangei} from "d3"; import {parse as isoParse} from "isoformat"; import {defined} from "./defined.js"; -import {maybeTimeInterval, maybeUtcInterval} from "./time.js"; +import {timeInterval, utcInterval} from "./time.js"; // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray export const TypedArray = Object.getPrototypeOf(Uint8Array); @@ -322,27 +322,30 @@ export function maybeIntervalTransform(interval, type) { // range} object similar to a D3 time interval. export function maybeInterval(interval, type) { if (interval == null) return; - if (typeof interval === "number") { - if (0 < interval && interval < 1 && Number.isInteger(1 / interval)) interval = -1 / interval; - const n = Math.abs(interval); - return interval < 0 - ? { - floor: (d) => Math.floor(d * n) / n, - offset: (d) => (d * n + 1) / n, // note: no optional step for simplicity - range: (lo, hi) => rangei(Math.ceil(lo * n), hi * n).map((x) => x / n) - } - : { - floor: (d) => Math.floor(d / n) * n, - offset: (d) => d + n, // note: no optional step for simplicity - range: (lo, hi) => rangei(Math.ceil(lo / n), hi / n).map((x) => x * n) - }; - } - if (typeof interval === "string") return (type === "time" ? maybeTimeInterval : maybeUtcInterval)(interval); + if (typeof interval === "number") return numberInterval(interval); + if (typeof interval === "string") return (type === "time" ? timeInterval : utcInterval)(interval); if (typeof interval.floor !== "function") throw new Error("invalid interval; missing floor method"); if (typeof interval.offset !== "function") throw new Error("invalid interval; missing offset method"); return interval; } +export function numberInterval(interval) { + interval = +interval; + if (0 < interval && interval < 1 && Number.isInteger(1 / interval)) interval = -1 / interval; + const n = Math.abs(interval); + return interval < 0 + ? { + floor: (d) => Math.floor(d * n) / n, + offset: (d, s = 1) => (d * n + Math.floor(s)) / n, + range: (lo, hi) => rangei(Math.ceil(lo * n), hi * n).map((x) => x / n) + } + : { + floor: (d) => Math.floor(d / n) * n, + offset: (d, s = 1) => d + n * Math.floor(s), + range: (lo, hi) => rangei(Math.ceil(lo / n), hi / n).map((x) => x * n) + }; +} + // Like maybeInterval, but requires a range method too. export function maybeRangeInterval(interval, type) { interval = maybeInterval(interval, type); diff --git a/src/time.js b/src/time.js index 63722f587c..118c99daad 100644 --- a/src/time.js +++ b/src/time.js @@ -183,11 +183,11 @@ export function parseTimeInterval(input) { return [name, period]; } -export function maybeTimeInterval(input) { +export function timeInterval(input) { return asInterval(parseTimeInterval(input), "time"); } -export function maybeUtcInterval(input) { +export function utcInterval(input) { return asInterval(parseTimeInterval(input), "utc"); } @@ -209,7 +209,7 @@ export function generalizeTimeInterval(interval, n) { if (!tickIntervals.some(([, d]) => d === duration)) return; // nonstandard or unknown interval if (duration % durationDay === 0 && durationDay < duration && duration < durationMonth) return; // not generalizable const [i] = tickIntervals[bisector(([, step]) => Math.log(step)).center(tickIntervals, Math.log(duration * n))]; - return (interval[intervalType] === "time" ? maybeTimeInterval : maybeUtcInterval)(i); + return (interval[intervalType] === "time" ? timeInterval : utcInterval)(i); } function formatTimeInterval(name, type, anchor) { diff --git a/src/transforms/bin.js b/src/transforms/bin.js index 16abb9d883..02321a3345 100644 --- a/src/transforms/bin.js +++ b/src/transforms/bin.js @@ -28,7 +28,7 @@ import { mid, valueof } from "../options.js"; -import {maybeUtcInterval} from "../time.js"; +import {utcInterval} from "../time.js"; import {basic} from "./basic.js"; import { hasOutput, @@ -322,7 +322,7 @@ export function maybeThresholds(thresholds, interval, defaultThresholds = thresh case "auto": return thresholdAuto; } - return maybeUtcInterval(thresholds); + return utcInterval(thresholds); } return thresholds; // pass array, count, or function to bin.thresholds } diff --git a/test/interval-test.js b/test/interval-test.js new file mode 100644 index 0000000000..4f7fcefbf9 --- /dev/null +++ b/test/interval-test.js @@ -0,0 +1,64 @@ +import assert from "assert"; +import {numberInterval} from "../src/options.js"; + +describe("numberInterval(interval)", () => { + it("coerces the given interval to a number", () => { + assert.deepStrictEqual(numberInterval("1").range(0, 10), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + }); + it("implements range", () => { + assert.deepStrictEqual(numberInterval(1).range(0, 10), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + assert.deepStrictEqual(numberInterval(1).range(1, 9), [1, 2, 3, 4, 5, 6, 7, 8]); + assert.deepStrictEqual(numberInterval(2).range(1, 9), [2, 4, 6, 8]); + assert.deepStrictEqual(numberInterval(-1).range(2, 5), [2, 3, 4]); + assert.deepStrictEqual(numberInterval(-2).range(2, 5), [2, 2.5, 3, 3.5, 4, 4.5]); + assert.deepStrictEqual(numberInterval(2).range(0, 10), [0, 2, 4, 6, 8]); + assert.deepStrictEqual(numberInterval(-2).range(0, 5), [0, 0.5, 1, 1.5, 2, 2.5, 3, 3.5, 4, 4.5]); + }); + it("considers descending ranges to be empty", () => { + assert.deepStrictEqual(numberInterval(1).range(10, 0), []); + assert.deepStrictEqual(numberInterval(1).range(-1, -9), []); + }); + it("considers invalid ranges to be empty", () => { + assert.deepStrictEqual(numberInterval(1).range(0, Infinity), []); + assert.deepStrictEqual(numberInterval(1).range(NaN, 0), []); + }); + it("considers invalid intervals to be empty", () => { + assert.deepStrictEqual(numberInterval(NaN).range(0, 10), []); + assert.deepStrictEqual(numberInterval(-Infinity).range(0, 10), []); + assert.deepStrictEqual(numberInterval(0).range(0, 10), []); + }); + it("implements floor", () => { + assert.strictEqual(numberInterval(1).floor(9.9), 9); + assert.strictEqual(numberInterval(2).floor(9), 8); + assert.strictEqual(numberInterval(-2).floor(8.6), 8.5); + }); + it("implements offset", () => { + assert.strictEqual(numberInterval(1).offset(8), 9); + assert.strictEqual(numberInterval(2).offset(8), 10); + assert.strictEqual(numberInterval(-2).offset(8), 8.5); + }); + it("implements offset with step", () => { + assert.strictEqual(numberInterval(1).offset(8, 2), 10); + assert.strictEqual(numberInterval(2).offset(8, 2), 12); + assert.strictEqual(numberInterval(-2).offset(8, 2), 9); + }); + it("does not require an aligned offset", () => { + assert.strictEqual(numberInterval(2).offset(7), 9); + assert.strictEqual(numberInterval(-2).offset(7.1), 7.6); + }); + it("floors the offset step", () => { + assert.strictEqual(numberInterval(1).offset(8, 2.5), 10); + assert.strictEqual(numberInterval(2).offset(8, 2.5), 12); + assert.strictEqual(numberInterval(-2).offset(8, 2.5), 9); + }); + it("coerces the offset step", () => { + assert.strictEqual(numberInterval(1).offset(8, "2.5"), 10); + assert.strictEqual(numberInterval(2).offset(8, "2.5"), 12); + assert.strictEqual(numberInterval(-2).offset(8, "2.5"), 9); + }); + it("allows a negative offset step", () => { + assert.strictEqual(numberInterval(1).offset(8, -2), 6); + assert.strictEqual(numberInterval(2).offset(8, -2), 4); + assert.strictEqual(numberInterval(-2).offset(8, -2), 7); + }); +}); diff --git a/test/marks/time-test.js b/test/marks/time-test.js index 8445bcd820..250cb150df 100644 --- a/test/marks/time-test.js +++ b/test/marks/time-test.js @@ -1,155 +1,155 @@ import assert from "assert"; import * as d3 from "d3"; -import {maybeTimeInterval, maybeUtcInterval} from "../../src/time.js"; +import {timeInterval, utcInterval} from "../../src/time.js"; -it("maybeTimeInterval('period') returns the expected time interval", () => { - assert.strictEqual(maybeTimeInterval("second"), d3.timeSecond); - assert.strictEqual(maybeTimeInterval("minute"), d3.timeMinute); - assert.strictEqual(maybeTimeInterval("hour"), d3.timeHour); - assert.strictEqual(maybeTimeInterval("day"), d3.timeDay); - assert.strictEqual(maybeTimeInterval("week"), d3.timeWeek); - assert.strictEqual(maybeTimeInterval("month"), d3.timeMonth); - assert.strictEqual(maybeTimeInterval("year"), d3.timeYear); - assert.strictEqual(maybeTimeInterval("monday"), d3.timeMonday); - assert.strictEqual(maybeTimeInterval("tuesday"), d3.timeTuesday); - assert.strictEqual(maybeTimeInterval("wednesday"), d3.timeWednesday); - assert.strictEqual(maybeTimeInterval("thursday"), d3.timeThursday); - assert.strictEqual(maybeTimeInterval("friday"), d3.timeFriday); - assert.strictEqual(maybeTimeInterval("saturday"), d3.timeSaturday); - assert.strictEqual(maybeTimeInterval("sunday"), d3.timeSunday); +it("timeInterval('period') returns the expected time interval", () => { + assert.strictEqual(timeInterval("second"), d3.timeSecond); + assert.strictEqual(timeInterval("minute"), d3.timeMinute); + assert.strictEqual(timeInterval("hour"), d3.timeHour); + assert.strictEqual(timeInterval("day"), d3.timeDay); + assert.strictEqual(timeInterval("week"), d3.timeWeek); + assert.strictEqual(timeInterval("month"), d3.timeMonth); + assert.strictEqual(timeInterval("year"), d3.timeYear); + assert.strictEqual(timeInterval("monday"), d3.timeMonday); + assert.strictEqual(timeInterval("tuesday"), d3.timeTuesday); + assert.strictEqual(timeInterval("wednesday"), d3.timeWednesday); + assert.strictEqual(timeInterval("thursday"), d3.timeThursday); + assert.strictEqual(timeInterval("friday"), d3.timeFriday); + assert.strictEqual(timeInterval("saturday"), d3.timeSaturday); + assert.strictEqual(timeInterval("sunday"), d3.timeSunday); }); -it("maybeTimeInterval('periods') returns the expected time interval", () => { - assert.strictEqual(maybeTimeInterval("seconds"), d3.timeSecond); - assert.strictEqual(maybeTimeInterval("minutes"), d3.timeMinute); - assert.strictEqual(maybeTimeInterval("hours"), d3.timeHour); - assert.strictEqual(maybeTimeInterval("days"), d3.timeDay); - assert.strictEqual(maybeTimeInterval("weeks"), d3.timeWeek); - assert.strictEqual(maybeTimeInterval("months"), d3.timeMonth); - assert.strictEqual(maybeTimeInterval("years"), d3.timeYear); - assert.strictEqual(maybeTimeInterval("mondays"), d3.timeMonday); - assert.strictEqual(maybeTimeInterval("tuesdays"), d3.timeTuesday); - assert.strictEqual(maybeTimeInterval("wednesdays"), d3.timeWednesday); - assert.strictEqual(maybeTimeInterval("thursdays"), d3.timeThursday); - assert.strictEqual(maybeTimeInterval("fridays"), d3.timeFriday); - assert.strictEqual(maybeTimeInterval("saturdays"), d3.timeSaturday); - assert.strictEqual(maybeTimeInterval("sundays"), d3.timeSunday); +it("timeInterval('periods') returns the expected time interval", () => { + assert.strictEqual(timeInterval("seconds"), d3.timeSecond); + assert.strictEqual(timeInterval("minutes"), d3.timeMinute); + assert.strictEqual(timeInterval("hours"), d3.timeHour); + assert.strictEqual(timeInterval("days"), d3.timeDay); + assert.strictEqual(timeInterval("weeks"), d3.timeWeek); + assert.strictEqual(timeInterval("months"), d3.timeMonth); + assert.strictEqual(timeInterval("years"), d3.timeYear); + assert.strictEqual(timeInterval("mondays"), d3.timeMonday); + assert.strictEqual(timeInterval("tuesdays"), d3.timeTuesday); + assert.strictEqual(timeInterval("wednesdays"), d3.timeWednesday); + assert.strictEqual(timeInterval("thursdays"), d3.timeThursday); + assert.strictEqual(timeInterval("fridays"), d3.timeFriday); + assert.strictEqual(timeInterval("saturdays"), d3.timeSaturday); + assert.strictEqual(timeInterval("sundays"), d3.timeSunday); }); -it("maybeTimeInterval('1 periods) returns the expected time interval", () => { - assert.strictEqual(maybeTimeInterval("1 second"), d3.timeSecond); - assert.strictEqual(maybeTimeInterval("1 minute"), d3.timeMinute); - assert.strictEqual(maybeTimeInterval("1 hour"), d3.timeHour); - assert.strictEqual(maybeTimeInterval("1 day"), d3.timeDay); - assert.strictEqual(maybeTimeInterval("1 week"), d3.timeWeek); - assert.strictEqual(maybeTimeInterval("1 month"), d3.timeMonth); - assert.strictEqual(maybeTimeInterval("1 year"), d3.timeYear); - assert.strictEqual(maybeTimeInterval("1 monday"), d3.timeMonday); - assert.strictEqual(maybeTimeInterval("1 tuesday"), d3.timeTuesday); - assert.strictEqual(maybeTimeInterval("1 wednesday"), d3.timeWednesday); - assert.strictEqual(maybeTimeInterval("1 thursday"), d3.timeThursday); - assert.strictEqual(maybeTimeInterval("1 friday"), d3.timeFriday); - assert.strictEqual(maybeTimeInterval("1 saturday"), d3.timeSaturday); - assert.strictEqual(maybeTimeInterval("1 sunday"), d3.timeSunday); +it("timeInterval('1 periods) returns the expected time interval", () => { + assert.strictEqual(timeInterval("1 second"), d3.timeSecond); + assert.strictEqual(timeInterval("1 minute"), d3.timeMinute); + assert.strictEqual(timeInterval("1 hour"), d3.timeHour); + assert.strictEqual(timeInterval("1 day"), d3.timeDay); + assert.strictEqual(timeInterval("1 week"), d3.timeWeek); + assert.strictEqual(timeInterval("1 month"), d3.timeMonth); + assert.strictEqual(timeInterval("1 year"), d3.timeYear); + assert.strictEqual(timeInterval("1 monday"), d3.timeMonday); + assert.strictEqual(timeInterval("1 tuesday"), d3.timeTuesday); + assert.strictEqual(timeInterval("1 wednesday"), d3.timeWednesday); + assert.strictEqual(timeInterval("1 thursday"), d3.timeThursday); + assert.strictEqual(timeInterval("1 friday"), d3.timeFriday); + assert.strictEqual(timeInterval("1 saturday"), d3.timeSaturday); + assert.strictEqual(timeInterval("1 sunday"), d3.timeSunday); }); -it("maybeTimeInterval('1 periods') returns the expected time interval", () => { - assert.strictEqual(maybeTimeInterval("1 seconds"), d3.timeSecond); - assert.strictEqual(maybeTimeInterval("1 minutes"), d3.timeMinute); - assert.strictEqual(maybeTimeInterval("1 hours"), d3.timeHour); - assert.strictEqual(maybeTimeInterval("1 days"), d3.timeDay); - assert.strictEqual(maybeTimeInterval("1 weeks"), d3.timeWeek); - assert.strictEqual(maybeTimeInterval("1 months"), d3.timeMonth); - assert.strictEqual(maybeTimeInterval("1 years"), d3.timeYear); - assert.strictEqual(maybeTimeInterval("1 mondays"), d3.timeMonday); - assert.strictEqual(maybeTimeInterval("1 tuesdays"), d3.timeTuesday); - assert.strictEqual(maybeTimeInterval("1 wednesdays"), d3.timeWednesday); - assert.strictEqual(maybeTimeInterval("1 thursdays"), d3.timeThursday); - assert.strictEqual(maybeTimeInterval("1 fridays"), d3.timeFriday); - assert.strictEqual(maybeTimeInterval("1 saturdays"), d3.timeSaturday); - assert.strictEqual(maybeTimeInterval("1 sundays"), d3.timeSunday); +it("timeInterval('1 periods') returns the expected time interval", () => { + assert.strictEqual(timeInterval("1 seconds"), d3.timeSecond); + assert.strictEqual(timeInterval("1 minutes"), d3.timeMinute); + assert.strictEqual(timeInterval("1 hours"), d3.timeHour); + assert.strictEqual(timeInterval("1 days"), d3.timeDay); + assert.strictEqual(timeInterval("1 weeks"), d3.timeWeek); + assert.strictEqual(timeInterval("1 months"), d3.timeMonth); + assert.strictEqual(timeInterval("1 years"), d3.timeYear); + assert.strictEqual(timeInterval("1 mondays"), d3.timeMonday); + assert.strictEqual(timeInterval("1 tuesdays"), d3.timeTuesday); + assert.strictEqual(timeInterval("1 wednesdays"), d3.timeWednesday); + assert.strictEqual(timeInterval("1 thursdays"), d3.timeThursday); + assert.strictEqual(timeInterval("1 fridays"), d3.timeFriday); + assert.strictEqual(timeInterval("1 saturdays"), d3.timeSaturday); + assert.strictEqual(timeInterval("1 sundays"), d3.timeSunday); }); -it("maybeTimeInterval('n seconds') returns the expected time interval", () => { +it("timeInterval('n seconds') returns the expected time interval", () => { const start = new Date("2012-01-01T12:01:02"); const end = new Date("2012-01-01T12:14:08"); - assert.deepStrictEqual(maybeTimeInterval("5 seconds").range(start, end), d3.timeSecond.every(5).range(start, end)); - assert.deepStrictEqual(maybeTimeInterval("15 seconds").range(start, end), d3.timeSecond.every(15).range(start, end)); - assert.deepStrictEqual(maybeTimeInterval("45 seconds").range(start, end), d3.timeSecond.every(45).range(start, end)); + assert.deepStrictEqual(timeInterval("5 seconds").range(start, end), d3.timeSecond.every(5).range(start, end)); + assert.deepStrictEqual(timeInterval("15 seconds").range(start, end), d3.timeSecond.every(15).range(start, end)); + assert.deepStrictEqual(timeInterval("45 seconds").range(start, end), d3.timeSecond.every(45).range(start, end)); }); -it("maybeUtcInterval('period') returns the expected UTC interval", () => { - assert.strictEqual(maybeUtcInterval("second"), d3.utcSecond); - assert.strictEqual(maybeUtcInterval("minute"), d3.utcMinute); - assert.strictEqual(maybeUtcInterval("hour"), d3.utcHour); - assert.strictEqual(maybeUtcInterval("day"), d3.unixDay); - assert.strictEqual(maybeUtcInterval("week"), d3.utcWeek); - assert.strictEqual(maybeUtcInterval("month"), d3.utcMonth); - assert.strictEqual(maybeUtcInterval("year"), d3.utcYear); - assert.strictEqual(maybeUtcInterval("monday"), d3.utcMonday); - assert.strictEqual(maybeUtcInterval("tuesday"), d3.utcTuesday); - assert.strictEqual(maybeUtcInterval("wednesday"), d3.utcWednesday); - assert.strictEqual(maybeUtcInterval("thursday"), d3.utcThursday); - assert.strictEqual(maybeUtcInterval("friday"), d3.utcFriday); - assert.strictEqual(maybeUtcInterval("saturday"), d3.utcSaturday); - assert.strictEqual(maybeUtcInterval("sunday"), d3.utcSunday); +it("utcInterval('period') returns the expected UTC interval", () => { + assert.strictEqual(utcInterval("second"), d3.utcSecond); + assert.strictEqual(utcInterval("minute"), d3.utcMinute); + assert.strictEqual(utcInterval("hour"), d3.utcHour); + assert.strictEqual(utcInterval("day"), d3.unixDay); + assert.strictEqual(utcInterval("week"), d3.utcWeek); + assert.strictEqual(utcInterval("month"), d3.utcMonth); + assert.strictEqual(utcInterval("year"), d3.utcYear); + assert.strictEqual(utcInterval("monday"), d3.utcMonday); + assert.strictEqual(utcInterval("tuesday"), d3.utcTuesday); + assert.strictEqual(utcInterval("wednesday"), d3.utcWednesday); + assert.strictEqual(utcInterval("thursday"), d3.utcThursday); + assert.strictEqual(utcInterval("friday"), d3.utcFriday); + assert.strictEqual(utcInterval("saturday"), d3.utcSaturday); + assert.strictEqual(utcInterval("sunday"), d3.utcSunday); }); -it("maybeUtcInterval('periods') returns the expected UTC interval", () => { - assert.strictEqual(maybeUtcInterval("seconds"), d3.utcSecond); - assert.strictEqual(maybeUtcInterval("minutes"), d3.utcMinute); - assert.strictEqual(maybeUtcInterval("hours"), d3.utcHour); - assert.strictEqual(maybeUtcInterval("days"), d3.unixDay); - assert.strictEqual(maybeUtcInterval("weeks"), d3.utcWeek); - assert.strictEqual(maybeUtcInterval("months"), d3.utcMonth); - assert.strictEqual(maybeUtcInterval("years"), d3.utcYear); - assert.strictEqual(maybeUtcInterval("mondays"), d3.utcMonday); - assert.strictEqual(maybeUtcInterval("tuesdays"), d3.utcTuesday); - assert.strictEqual(maybeUtcInterval("wednesdays"), d3.utcWednesday); - assert.strictEqual(maybeUtcInterval("thursdays"), d3.utcThursday); - assert.strictEqual(maybeUtcInterval("fridays"), d3.utcFriday); - assert.strictEqual(maybeUtcInterval("saturdays"), d3.utcSaturday); - assert.strictEqual(maybeUtcInterval("sundays"), d3.utcSunday); +it("utcInterval('periods') returns the expected UTC interval", () => { + assert.strictEqual(utcInterval("seconds"), d3.utcSecond); + assert.strictEqual(utcInterval("minutes"), d3.utcMinute); + assert.strictEqual(utcInterval("hours"), d3.utcHour); + assert.strictEqual(utcInterval("days"), d3.unixDay); + assert.strictEqual(utcInterval("weeks"), d3.utcWeek); + assert.strictEqual(utcInterval("months"), d3.utcMonth); + assert.strictEqual(utcInterval("years"), d3.utcYear); + assert.strictEqual(utcInterval("mondays"), d3.utcMonday); + assert.strictEqual(utcInterval("tuesdays"), d3.utcTuesday); + assert.strictEqual(utcInterval("wednesdays"), d3.utcWednesday); + assert.strictEqual(utcInterval("thursdays"), d3.utcThursday); + assert.strictEqual(utcInterval("fridays"), d3.utcFriday); + assert.strictEqual(utcInterval("saturdays"), d3.utcSaturday); + assert.strictEqual(utcInterval("sundays"), d3.utcSunday); }); -it("maybeUtcInterval('1 periods) returns the expected UTC interval", () => { - assert.strictEqual(maybeUtcInterval("1 second"), d3.utcSecond); - assert.strictEqual(maybeUtcInterval("1 minute"), d3.utcMinute); - assert.strictEqual(maybeUtcInterval("1 hour"), d3.utcHour); - assert.strictEqual(maybeUtcInterval("1 day"), d3.unixDay); - assert.strictEqual(maybeUtcInterval("1 week"), d3.utcWeek); - assert.strictEqual(maybeUtcInterval("1 month"), d3.utcMonth); - assert.strictEqual(maybeUtcInterval("1 year"), d3.utcYear); - assert.strictEqual(maybeUtcInterval("1 monday"), d3.utcMonday); - assert.strictEqual(maybeUtcInterval("1 tuesday"), d3.utcTuesday); - assert.strictEqual(maybeUtcInterval("1 wednesday"), d3.utcWednesday); - assert.strictEqual(maybeUtcInterval("1 thursday"), d3.utcThursday); - assert.strictEqual(maybeUtcInterval("1 friday"), d3.utcFriday); - assert.strictEqual(maybeUtcInterval("1 saturday"), d3.utcSaturday); - assert.strictEqual(maybeUtcInterval("1 sunday"), d3.utcSunday); +it("utcInterval('1 periods) returns the expected UTC interval", () => { + assert.strictEqual(utcInterval("1 second"), d3.utcSecond); + assert.strictEqual(utcInterval("1 minute"), d3.utcMinute); + assert.strictEqual(utcInterval("1 hour"), d3.utcHour); + assert.strictEqual(utcInterval("1 day"), d3.unixDay); + assert.strictEqual(utcInterval("1 week"), d3.utcWeek); + assert.strictEqual(utcInterval("1 month"), d3.utcMonth); + assert.strictEqual(utcInterval("1 year"), d3.utcYear); + assert.strictEqual(utcInterval("1 monday"), d3.utcMonday); + assert.strictEqual(utcInterval("1 tuesday"), d3.utcTuesday); + assert.strictEqual(utcInterval("1 wednesday"), d3.utcWednesday); + assert.strictEqual(utcInterval("1 thursday"), d3.utcThursday); + assert.strictEqual(utcInterval("1 friday"), d3.utcFriday); + assert.strictEqual(utcInterval("1 saturday"), d3.utcSaturday); + assert.strictEqual(utcInterval("1 sunday"), d3.utcSunday); }); -it("maybeUtcInterval('1 periods') returns the expected UTC interval", () => { - assert.strictEqual(maybeUtcInterval("1 seconds"), d3.utcSecond); - assert.strictEqual(maybeUtcInterval("1 minutes"), d3.utcMinute); - assert.strictEqual(maybeUtcInterval("1 hours"), d3.utcHour); - assert.strictEqual(maybeUtcInterval("1 days"), d3.unixDay); - assert.strictEqual(maybeUtcInterval("1 weeks"), d3.utcWeek); - assert.strictEqual(maybeUtcInterval("1 months"), d3.utcMonth); - assert.strictEqual(maybeUtcInterval("1 years"), d3.utcYear); - assert.strictEqual(maybeUtcInterval("1 mondays"), d3.utcMonday); - assert.strictEqual(maybeUtcInterval("1 tuesdays"), d3.utcTuesday); - assert.strictEqual(maybeUtcInterval("1 wednesdays"), d3.utcWednesday); - assert.strictEqual(maybeUtcInterval("1 thursdays"), d3.utcThursday); - assert.strictEqual(maybeUtcInterval("1 fridays"), d3.utcFriday); - assert.strictEqual(maybeUtcInterval("1 saturdays"), d3.utcSaturday); - assert.strictEqual(maybeUtcInterval("1 sundays"), d3.utcSunday); +it("utcInterval('1 periods') returns the expected UTC interval", () => { + assert.strictEqual(utcInterval("1 seconds"), d3.utcSecond); + assert.strictEqual(utcInterval("1 minutes"), d3.utcMinute); + assert.strictEqual(utcInterval("1 hours"), d3.utcHour); + assert.strictEqual(utcInterval("1 days"), d3.unixDay); + assert.strictEqual(utcInterval("1 weeks"), d3.utcWeek); + assert.strictEqual(utcInterval("1 months"), d3.utcMonth); + assert.strictEqual(utcInterval("1 years"), d3.utcYear); + assert.strictEqual(utcInterval("1 mondays"), d3.utcMonday); + assert.strictEqual(utcInterval("1 tuesdays"), d3.utcTuesday); + assert.strictEqual(utcInterval("1 wednesdays"), d3.utcWednesday); + assert.strictEqual(utcInterval("1 thursdays"), d3.utcThursday); + assert.strictEqual(utcInterval("1 fridays"), d3.utcFriday); + assert.strictEqual(utcInterval("1 saturdays"), d3.utcSaturday); + assert.strictEqual(utcInterval("1 sundays"), d3.utcSunday); }); -it("maybeUtcInterval('n seconds') returns the expected UTC interval", () => { +it("utcInterval('n seconds') returns the expected UTC interval", () => { const start = new Date("2012-01-01T12:01:02"); const end = new Date("2012-01-01T12:14:08"); - assert.deepStrictEqual(maybeUtcInterval("5 seconds").range(start, end), d3.utcSecond.every(5).range(start, end)); - assert.deepStrictEqual(maybeUtcInterval("15 seconds").range(start, end), d3.utcSecond.every(15).range(start, end)); - assert.deepStrictEqual(maybeUtcInterval("45 seconds").range(start, end), d3.utcSecond.every(45).range(start, end)); + assert.deepStrictEqual(utcInterval("5 seconds").range(start, end), d3.utcSecond.every(5).range(start, end)); + assert.deepStrictEqual(utcInterval("15 seconds").range(start, end), d3.utcSecond.every(15).range(start, end)); + assert.deepStrictEqual(utcInterval("45 seconds").range(start, end), d3.utcSecond.every(45).range(start, end)); });