-
Notifications
You must be signed in to change notification settings - Fork 185
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
expose {number,time,utc}Interval #2075
Merged
Merged
Changes from all commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
<script setup> | ||
|
||
import * as Plot from "@observablehq/plot"; | ||
import * as d3 from "d3"; | ||
|
||
</script> | ||
|
||
# Intervals <VersionBadge pr="2075" /> | ||
|
||
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
@@ -1,4 +1,4 @@ | ||||||
// For internal use. | ||||||
/** A named interval. */ | ||||||
export type LiteralTimeInterval = | ||||||
| "3 months" | ||||||
| "10 years" | ||||||
|
@@ -124,3 +124,16 @@ export type RangeInterval<T = any> = LiteralInterval<T> | RangeIntervalImplement | |||||
* - a number (for number intervals), defining intervals at integer multiples of *n* | ||||||
*/ | ||||||
export type NiceInterval<T = any> = LiteralInterval<T> | NiceIntervalImplementation<T>; | ||||||
|
||||||
/** | ||||||
* 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. | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
same suggestion/question |
||||||
*/ | ||||||
export function numberInterval(period: number): RangeIntervalImplementation<number>; | ||||||
|
||||||
/** Given a string *period*, returns a corresponding local time nice interval. */ | ||||||
export function timeInterval(period: LiteralTimeInterval): NiceIntervalImplementation<Date>; | ||||||
|
||||||
/** Given a string *period*, returns a corresponding UTC nice interval. */ | ||||||
export function utcInterval(period: LiteralTimeInterval): NiceIntervalImplementation<Date>; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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); | ||
}); | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove repeat — or did you mean "when period is a fractional value"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, that’s what I meant, and no, I’m not repeating myself. There are two statements here:
If period is a negative number that is not a negative integer, it still works, but there’s not really any reason to do it. So these are saying two separate things.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(I’m not documenting that a period of 0.5 is automatically promoted to -2, but I feel like that’s an implementation detail that doesn’t need to be documented.)