Skip to content
Merged
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
16 changes: 8 additions & 8 deletions src/interval.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,21 @@ export type TimeIntervalName =

export interface IntervalImplementation<T> {
floor(value: T): T;
offset(value: T, offset: number): T;
offset(value: T, offset?: number): T;
}

export interface RangeIntervalImplementation<T> extends IntervalImplementation<T> {
range(start: T, stop: T): T[];
}

export type TimeInterval = TimeIntervalName | IntervalImplementation<Date>;

export type TimeRangeInterval = TimeIntervalName | RangeIntervalImplementation<Date>;
export interface NiceIntervalImplementation<T> extends RangeIntervalImplementation<T> {
ceil(value: T): T;
}

export type NumberInterval = number | IntervalImplementation<number>;
type LiteralInterval<T> = T extends Date ? TimeIntervalName : T extends number ? number : never;

export type NumberRangeInterval = number | RangeIntervalImplementation<number>;
export type Interval<T = any> = LiteralInterval<T> | IntervalImplementation<T>;

export type Interval = TimeInterval | NumberInterval;
export type RangeInterval<T = any> = LiteralInterval<T> | RangeIntervalImplementation<T>;

export type RangeInterval = TimeRangeInterval | NumberRangeInterval;
export type NiceInterval<T = any> = LiteralInterval<T> | NiceIntervalImplementation<T>;
4 changes: 2 additions & 2 deletions src/marks/axis.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ export interface AxisXOptions extends AxisOptions, TickXOptions {}

export interface AxisYOptions extends AxisOptions, TickYOptions {}

export interface GridXOptions extends GridOptions, RuleXOptions {}
export interface GridXOptions extends GridOptions, Omit<RuleXOptions, "interval"> {}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed because the rule mark allows a base (non-range) interval, whereas the grid mark requires a range interval. You can widen a type when extending, but you can’t narrow it.


export interface GridYOptions extends GridOptions, RuleYOptions {}
export interface GridYOptions extends GridOptions, Omit<RuleYOptions, "interval"> {}

export function axisY(options?: AxisYOptions): CompoundMark;

Expand Down
4 changes: 2 additions & 2 deletions src/marks/axis.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import {formatDefault} from "../format.js";
import {marks} from "../mark.js";
import {radians} from "../math.js";
import {range, valueof, arrayify, constant, keyword, identity, number} from "../options.js";
import {isNoneish, isIterable, isTemporal, maybeInterval, orderof} from "../options.js";
import {isNoneish, isIterable, isTemporal, maybeRangeInterval, orderof} from "../options.js";
import {isTemporalScale} from "../scales.js";
import {offset} from "../style.js";
import {initializer} from "../transforms/basic.js";
Expand Down Expand Up @@ -511,7 +511,7 @@ function axisMark(mark, k, ariaLabel, data, options, initialize) {
if (ticks !== undefined) {
data = scale.ticks(ticks);
} else {
interval = maybeInterval(interval === undefined ? scale.interval : interval, scale.type); // TODO check for RangeInterval
interval = maybeRangeInterval(interval === undefined ? scale.interval : interval, scale.type);
if (interval !== undefined) {
// For time scales, we could pass the interval directly to
// scale.ticks because it’s supported by d3.utcTicks; but
Expand Down
14 changes: 14 additions & 0 deletions src/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,20 @@ export function maybeInterval(interval, type) {
return interval;
}

// Like maybeInterval, but requires a range method too.
export function maybeRangeInterval(interval, type) {
interval = maybeInterval(interval, type);
if (interval && typeof interval.range !== "function") throw new Error("invalid interval: missing range method");
return interval;
}

// Like maybeRangeInterval, but requires a ceil method too.
export function maybeNiceInterval(interval, type) {
interval = maybeRangeInterval(interval, type);
if (interval && typeof interval.ceil !== "function") throw new Error("invalid interval: missing ceil method");
return interval;
}

// This distinguishes between per-dimension options and a standalone value.
export function maybeValue(value) {
return value === undefined || isOptions(value) ? value : {value};
Expand Down
12 changes: 6 additions & 6 deletions src/scales.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {ColorSchemeName} from "./color.js";
import type {InsetOptions} from "./inset.js";
import type {Interpolate} from "./interpolate.js";
import type {Interval} from "./interval.js";
import type {NiceInterval, RangeInterval} from "./interval.js";
import type {LegendType} from "./legends.js";
import type {AxisAnchor} from "./marks/axis.js";

Expand Down Expand Up @@ -34,15 +34,15 @@ export type ScaleType =

export interface ScaleDefaults extends InsetOptions {
clamp?: boolean;
nice?: boolean | number | Interval;
nice?: boolean | number | NiceInterval;
zero?: boolean;
round?: boolean;
align?: number;
padding?: number;

// axis options
axis?: AxisAnchor | "both" | boolean | null; // for position scales
grid?: boolean | string | Interval | Iterable<any>;
grid?: boolean | string | RangeInterval | Iterable<any>;
label?: string | null;
}

Expand Down Expand Up @@ -101,7 +101,7 @@ export interface ScaleOptions extends ScaleDefaults {
transform?: (t: any) => any;

// quantitative scale options
interval?: Interval; // TODO RangeInterval?
interval?: RangeInterval;
percent?: boolean;

// color scale options
Expand Down Expand Up @@ -131,7 +131,7 @@ export interface ScaleOptions extends ScaleDefaults {

// axis and legend options
legend?: LegendType | boolean | null; // for color, opacity, and symbol scales
ticks?: number | Interval | Iterable<any>; // TODO RangeInterval?
ticks?: number | RangeInterval | Iterable<any>;
tickSize?: number;
tickSpacing?: number;
tickPadding?: number;
Expand All @@ -153,7 +153,7 @@ export interface Scale {
transform?: (t: any) => any;
percent?: boolean;
unknown?: any;
interval?: Interval; // TODO RangeInterval?
interval?: RangeInterval;
interpolate?: Interpolate;
clamp?: boolean;
pivot?: any;
Expand Down
6 changes: 3 additions & 3 deletions src/scales/ordinal.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {InternSet, extent, quantize, reverse as reverseof, sort, symbolsFill, symbolsStroke} from "d3";
import {scaleBand, scaleOrdinal, scalePoint, scaleImplicit} from "d3";
import {ascendingDefined} from "../defined.js";
import {isNoneish, map, maybeInterval} from "../options.js";
import {isNoneish, map, maybeRangeInterval} from "../options.js";
import {maybeSymbol} from "../symbol.js";
import {registry, color, position, symbol} from "./index.js";
import {maybeBooleanRange, ordinalScheme, quantitativeScheme} from "./schemes.js";
Expand All @@ -13,7 +13,7 @@ import {maybeBooleanRange, ordinalScheme, quantitativeScheme} from "./schemes.js
export const ordinalImplicit = Symbol("ordinal");

function createScaleO(key, scale, channels, {type, interval, domain, range, reverse, hint}) {
interval = maybeInterval(interval, type);
interval = maybeRangeInterval(interval, type);
if (domain === undefined) domain = inferDomain(channels, interval, key);
if (type === "categorical" || type === ordinalImplicit) type = "ordinal"; // shorthand for color schemes
if (reverse) domain = reverseof(domain);
Expand All @@ -27,7 +27,7 @@ function createScaleO(key, scale, channels, {type, interval, domain, range, reve
}

export function createScaleOrdinal(key, channels, {type, interval, domain, range, scheme, unknown, ...options}) {
interval = maybeInterval(interval, type);
interval = maybeRangeInterval(interval, type);
if (domain === undefined) domain = inferDomain(channels, interval, key);
let hint;
if (registry.get(key) === symbol) {
Expand Down
10 changes: 7 additions & 3 deletions src/scales/quantitative.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import {
ticks
} from "d3";
import {positive, negative, finite} from "../defined.js";
import {arrayify, constant, orderof, slice, maybeInterval} from "../options.js";
import {arrayify, constant, orderof, slice, maybeNiceInterval, maybeRangeInterval} from "../options.js";
import {ordinalRange, quantitativeScheme} from "./schemes.js";
import {registry, radius, opacity, color, length} from "./index.js";

Expand Down Expand Up @@ -78,7 +78,7 @@ export function createScaleQ(
reverse
}
) {
interval = maybeInterval(interval, type);
interval = maybeRangeInterval(interval, type);
if (type === "cyclical" || type === "sequential") type = "linear"; // shorthand for color schemes
reverse = !!reverse;

Expand Down Expand Up @@ -121,12 +121,16 @@ export function createScaleQ(

if (reverse) domain = reverseof(domain);
scale.domain(domain).unknown(unknown);
if (nice) scale.nice(nice === true ? undefined : nice), (domain = scale.domain());
if (nice) scale.nice(maybeNice(nice, type)), (domain = scale.domain());
if (range !== undefined) scale.range(range);
if (clamp) scale.clamp(clamp);
return {type, domain, range, scale, interpolate, interval};
}

function maybeNice(nice, type) {
return nice === true ? undefined : typeof nice === "number" ? nice : maybeNiceInterval(nice, type);
}

export function createScaleLinear(key, channels, options) {
return createScaleQ(key, scaleLinear(), channels, options);
}
Expand Down
4 changes: 2 additions & 2 deletions src/transforms/bin.d.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import type {ChannelReducers} from "../channel.js";
import type {Interval, RangeInterval} from "../interval.js";
import type {RangeInterval} from "../interval.js";
import type {Reducer} from "../reducer.js";
import type {Transformed} from "./basic.js";

export type ThresholdsName = "freedman-diaconis" | "scott" | "sturges" | "auto";

export type ThresholdsFunction = (values: any[], min: any, max: any) => any[];

export type Thresholds = ThresholdsName | ThresholdsFunction | Interval;
export type Thresholds = ThresholdsName | ThresholdsFunction | RangeInterval;

export interface BinOptions {
cumulative?: boolean | number;
Expand Down
16 changes: 4 additions & 12 deletions src/transforms/bin.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
coerceDate,
coerceNumbers,
maybeColumn,
maybeInterval,
maybeRangeInterval,
maybeTuple,
maybeColorChannel,
maybeValue,
Expand All @@ -24,6 +24,7 @@ import {
isIterable,
map
} from "../options.js";
import {maybeUtcInterval} from "../time.js";
import {basic} from "./basic.js";
import {
hasOutput,
Expand Down Expand Up @@ -311,20 +312,11 @@ export function maybeThresholds(thresholds, interval, defaultThresholds = thresh
case "auto":
return thresholdAuto;
}
const interval = maybeInterval(thresholds);
if (interval !== undefined) return interval;
throw new Error(`invalid thresholds: ${thresholds}`);
return maybeUtcInterval(thresholds);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point, we’ve already checked that typeof thresholds === "string", so the other code paths should not be possible.

}
return thresholds; // pass array, count, or function to bin.thresholds
}

// Unlike the interval transform, we require a range method, too.
function maybeRangeInterval(interval) {
interval = maybeInterval(interval);
if (!isInterval(interval)) throw new Error(`invalid interval: ${interval}`);
return interval;
}

function thresholdAuto(values, min, max) {
return Math.min(200, thresholdScott(values, min, max));
}
Expand All @@ -338,7 +330,7 @@ function isTimeInterval(t) {
}

function isInterval(t) {
return t ? typeof t.range === "function" : false;
return typeof t?.range === "function";
}

function bing(EX, EY) {
Expand Down
8 changes: 4 additions & 4 deletions test/plots/electricity-demand.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ export async function electricityDemand() {
return Plot.plot({
width: 960,
marginLeft: 50,
x: {round: true, nice: d3.utcWeek},
x: {round: true, nice: "week"},
y: {insetTop: 6},
marks: [
Plot.frame({fill: "#efefef"}),
Plot.ruleY([0]),
Plot.axisX({ticks: d3.utcYear, tickSize: 28, tickPadding: -11, tickFormat: " %Y", textAnchor: "start"}),
Plot.axisX({ticks: d3.utcMonth, tickSize: 16, tickPadding: -11, tickFormat: " %B", textAnchor: "start"}),
Plot.gridX({ticks: d3.utcWeek, stroke: "#fff", strokeOpacity: 1, insetBottom: -0.5}),
Plot.axisX({ticks: "year", tickSize: 28, tickPadding: -11, tickFormat: " %Y", textAnchor: "start"}),
Plot.axisX({ticks: "month", tickSize: 16, tickPadding: -11, tickFormat: " %B", textAnchor: "start"}),
Plot.gridX({ticks: "week", stroke: "#fff", strokeOpacity: 1, insetBottom: -0.5}),
Plot.dot(electricity, {x: "date", y: "mwh", stroke: "red", strokeOpacity: 0.3}),
Plot.line(electricity, Plot.windowY(24, {x: "date", y: "mwh"}))
]
Expand Down