Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/features/scales.md
Original file line number Diff line number Diff line change
Expand Up @@ -621,7 +621,7 @@ Plot.plot({
[Mark transforms](./transforms.md) typically consume values *before* they are passed through scales (_e.g._, when binning). In this case the mark transforms will see the values prior to the scale transform as input, and the scale transform will apply to the *output* of the mark transform.
:::

The **interval** scale option<a id="interval" href="#interval" aria-label="Permalink to &quot;interval&quot;"></a> <VersionBadge version="0.5.1" /> sets an ordinal scale’s **domain** to the start of every interval within the extent of the data. In addition, it implicitly sets the **transform** of the scale to *interval*.floor, rounding values down to the start of each interval. For example, below we generate a time-series bar chart; when an **interval** is specified, missing days are visible.
The **interval** scale option<a id="interval" href="#interval" aria-label="Permalink to &quot;interval&quot;"></a> <VersionBadge version="0.5.1" /> sets an ordinal scale’s **domain** to the start of every interval within the extent of the data. In addition, it implicitly sets the **transform** of the scale to *interval*.floor, rounding values down to the start of each interval. Set **transform** to null to disable this implicit rounding while still using the **interval** to align the domain. For example, below we generate a time-series bar chart; when an **interval** is specified, missing days are visible.

<p>
<label class="label-input">
Expand Down Expand Up @@ -724,7 +724,7 @@ The default range depends on the scale: for position scales (*x*, *y*, *fx*, and

The behavior of the **unknown** scale option depends on the scale type. For quantitative and temporal scales, the unknown value is used whenever the input value is undefined, null, or NaN. For ordinal or categorical scales, the unknown value is returned for any input value outside the domain. For band or point scales, the unknown option has no effect; it is effectively always equal to undefined. If the unknown option is set to undefined (the default), or null or NaN, then the affected input values will be considered undefined and filtered from the output.

For data at regular intervals, such as integer values or daily samples, the [**interval** option](#scale-transforms) can be used to enforce uniformity. The specified *interval* — such as d3.utcMonth — must expose an *interval*.floor(*value*), *interval*.offset(*value*), and *interval*.range(*start*, *stop*) functions. The option can also be specified as a number, in which case it will be promoted to a numeric interval with the given step. The option can alternatively be specified as a string (*second*, *minute*, *hour*, *day*, *week*, *month*, *quarter*, *half*, *year*, *monday*, *tuesday*, *wednesday*, *thursday*, *friday*, *saturday*, *sunday*) <VersionBadge version="0.6.2" /> naming the corresponding time interval, or a skip interval consisting of a number followed by the interval name (possibly pluralized), such as *3 months* or *10 years*. This option sets the default *scale*.transform to the given interval’s *interval*.floor function. In addition, the default *scale*.domain is an array of uniformly-spaced values spanning the extent of the values associated with the scale.
For data at regular intervals, such as integer values or daily samples, the [**interval** option](#scale-transforms) can be used to enforce uniformity. The specified *interval* — such as d3.utcMonth — must expose an *interval*.floor(*value*), *interval*.offset(*value*), and *interval*.range(*start*, *stop*) functions. The option can also be specified as a number, in which case it will be promoted to a numeric interval with the given step. The option can alternatively be specified as a string (*second*, *minute*, *hour*, *day*, *week*, *month*, *quarter*, *half*, *year*, *monday*, *tuesday*, *wednesday*, *thursday*, *friday*, *saturday*, *sunday*) <VersionBadge version="0.6.2" /> naming the corresponding time interval, or a skip interval consisting of a number followed by the interval name (possibly pluralized), such as *3 months* or *10 years*. This option sets the default *scale*.transform to the given interval’s *interval*.floor function, unless **transform** is set (including to null). In addition, the default *scale*.domain is an array of uniformly-spaced values spanning the extent of the values associated with the scale.

Quantitative scales can be further customized with additional options:

Expand Down
8 changes: 5 additions & 3 deletions src/scales.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,9 +435,10 @@ export interface ScaleOptions extends ScaleDefaults {
* y: {transform: (y) => (y) / 1000}
* ```
*
* See also the **percent** and **interval** shorthand options.
* See also the **percent** and **interval** shorthand options. If null,
* disables the implicit transform associated with those options.
*/
transform?: (t: any) => any;
transform?: ((t: any) => any) | null;

/**
* Enforces uniformity for data at regular intervals, such as integer values
Expand All @@ -449,7 +450,8 @@ export interface ScaleOptions extends ScaleDefaults {
*
* This option sets the default **transform** to the given interval’s
* *interval*.floor function. In addition, the default **domain** will align
* with interval boundaries.
* with interval boundaries. Set **transform** to null to disable the implicit
* transform while still using the interval to align the domain.
*/
interval?: RangeInterval;

Expand Down
15 changes: 15 additions & 0 deletions test/scales/scales-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2098,6 +2098,21 @@ it("plot(…).scale(name) reflects the given transform", async () => {
});
});

it("plot(…).scale(name) transform: null disables the implicit interval transform", () => {
const data = [1.2, 3.8];
const implicit = Plot.dotX(data, {x: (d) => d}).plot({x: {interval: 1}});
const disabled = Plot.dotX(data, {x: (d) => d}).plot({x: {interval: 1, transform: null}});
assert.deepStrictEqual(implicit.scale("x").domain, [1, 3]);
scaleEqual(disabled.scale("x"), {
type: "linear",
domain: [1.2, 3.8],
range: [20, 620],
clamp: false,
interpolate: d3.interpolateNumber,
interval: ["floor", "offset", "range"]
});
});

it("plot(…).scale(name) can return an identity scale, ignoring all other options", () => {
const plot = Plot.dot([1, 2], {x: (d) => d, fill: (d) => d}).plot({x: {type: "identity"}, color: {type: "identity"}});
scaleEqual(plot.scale("x"), {type: "identity"});
Expand Down
23 changes: 23 additions & 0 deletions test/types-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import {Project} from "ts-morph";
import {it} from "vitest";
import assert from "./assert.js";

it("ScaleOptions.transform accepts null under strictNullChecks", () => {
const project = new Project({
tsConfigFilePath: "tsconfig.json",
skipAddingFilesFromTsConfig: true,
compilerOptions: {strictNullChecks: true, noEmit: true}
});
project.addSourceFilesAtPaths("src/**/*.d.ts");
project.createSourceFile(
"check-scale-transform-null.ts",
`import type {ScaleOptions} from "./src/scales.js";
const options: ScaleOptions = {interval: "6 hours", transform: null};
`
);
const diagnostics = project
.getPreEmitDiagnostics()
.filter((d) => d.getSourceFile()?.getFilePath().endsWith("check-scale-transform-null.ts"))
.map((d) => String(d.getMessageText()));
assert.deepStrictEqual(diagnostics, []);
});