Skip to content

Commit ff5c71f

Browse files
committed
interval-aware bin, stack
1 parent d891f91 commit ff5c71f

File tree

12 files changed

+12045
-53
lines changed

12 files changed

+12045
-53
lines changed

src/options.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import {color, descending, quantile, range as rangei} from "d3";
12
import {parse as isoParse} from "isoformat";
2-
import {color, descending, range as rangei, quantile} from "d3";
3+
import {defined} from "./defined.js";
34
import {maybeTimeInterval, maybeUtcInterval} from "./time.js";
45

56
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray
@@ -269,6 +270,18 @@ export function mid(x1, x2) {
269270
};
270271
}
271272

273+
// If the scale options declare an interval, applies it to the values V.
274+
export function maybeApplyInterval(V, scale) {
275+
const t = maybeIntervalTransform(scale?.interval, scale?.type);
276+
return t ? map(V, t) : V;
277+
}
278+
279+
// Returns the equivalent scale transform for the specified interval option.
280+
export function maybeIntervalTransform(interval, type) {
281+
const i = maybeInterval(interval, type);
282+
return i && ((v) => (defined(v) ? i.floor(v) : v));
283+
}
284+
272285
// If interval is not nullish, converts interval shorthand such as a number (for
273286
// multiples) or a time interval name (such as “day”) to a {floor, offset,
274287
// range} object similar to a D3 time interval.

src/plot.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {createLegends, exposeLegends} from "./legends.js";
77
import {Mark} from "./mark.js";
88
import {axisFx, axisFy, axisX, axisY, gridFx, gridFy, gridX, gridY} from "./marks/axis.js";
99
import {frame} from "./marks/frame.js";
10-
import {arrayify, isColor, isIterable, isNone, isScaleOptions, map, yes, maybeInterval} from "./options.js";
10+
import {arrayify, isColor, isIterable, isNone, isScaleOptions, map, yes, maybeIntervalTransform} from "./options.js";
1111
import {createScales, createScaleFunctions, autoScaleRange, exposeScales} from "./scales.js";
1212
import {innerDimensions, outerDimensions} from "./scales.js";
1313
import {position, registry as scaleRegistry} from "./scales/index.js";
@@ -363,7 +363,7 @@ function applyScaleTransform(channel, options) {
363363
type,
364364
percent,
365365
interval,
366-
transform = percent ? (x) => x * 100 : maybeInterval(interval, type)?.floor
366+
transform = percent ? (x) => x * 100 : maybeIntervalTransform(interval, type)
367367
} = options[scale] ?? {};
368368
if (transform != null) channel.value = map(channel.value, transform);
369369
}

src/transforms/bin.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ import {
4040
reduceIdentity
4141
} from "./group.js";
4242
import {maybeInsetX, maybeInsetY} from "./inset.js";
43+
import {maybeApplyInterval} from "../options.js";
4344

4445
export function binX(outputs = {y: "count"}, options = {}) {
4546
// Group on {z, fill, stroke}, then optionally on y, then bin x.
@@ -143,8 +144,8 @@ function binn(
143144
...("z" in inputs && {z: GZ || z}),
144145
...("fill" in inputs && {fill: GF || fill}),
145146
...("stroke" in inputs && {stroke: GS || stroke}),
146-
...basic(options, (data, facets) => {
147-
const K = valueof(data, k);
147+
...basic(options, (data, facets, plotOptions) => {
148+
const K = maybeApplyInterval(valueof(data, k), plotOptions?.[gk]);
148149
const Z = valueof(data, z);
149150
const F = valueof(data, vfill);
150151
const S = valueof(data, vstroke);

src/transforms/group.js

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,10 @@ import {
2222
isObject,
2323
isTemporal,
2424
labelof,
25-
map,
25+
maybeApplyInterval,
2626
maybeColorChannel,
2727
maybeColumn,
2828
maybeInput,
29-
maybeInterval,
3029
maybeTuple,
3130
percentile,
3231
range,
@@ -159,11 +158,6 @@ function groupn(
159158
};
160159
}
161160

162-
function maybeApplyInterval(V, scale) {
163-
const i = maybeInterval(scale?.interval, scale?.type);
164-
return i ? map(V, (v) => i.floor(v)) : V;
165-
}
166-
167161
export function hasOutput(outputs, ...names) {
168162
for (const {name} of outputs) {
169163
if (names.includes(name)) {

src/transforms/stack.js

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,47 @@ import {InternMap, cumsum, group, groupSort, greatest, max, min, rollup, sum} fr
22
import {ascendingDefined} from "../defined.js";
33
import {field, column, maybeColumn, maybeZ, mid, range, valueof, maybeZero, one} from "../options.js";
44
import {basic} from "./basic.js";
5+
import {maybeApplyInterval} from "../options.js";
56

6-
export function stackX(stack = {}, options = {}) {
7-
if (arguments.length === 1) [stack, options] = mergeOptions(stack);
7+
export function stackX(stackOptions = {}, options = {}) {
8+
if (arguments.length === 1) [stackOptions, options] = mergeOptions(stackOptions);
89
const {y1, y = y1, x, ...rest} = options; // note: consumes x!
9-
const [transform, Y, x1, x2] = stackAlias(y, x, "x", stack, rest);
10+
const [transform, Y, x1, x2] = stack(y, x, "y", "x", stackOptions, rest);
1011
return {...transform, y1, y: Y, x1, x2, x: mid(x1, x2)};
1112
}
1213

13-
export function stackX1(stack = {}, options = {}) {
14-
if (arguments.length === 1) [stack, options] = mergeOptions(stack);
14+
export function stackX1(stackOptions = {}, options = {}) {
15+
if (arguments.length === 1) [stackOptions, options] = mergeOptions(stackOptions);
1516
const {y1, y = y1, x} = options;
16-
const [transform, Y, X] = stackAlias(y, x, "x", stack, options);
17+
const [transform, Y, X] = stack(y, x, "x", "y", stackOptions, options);
1718
return {...transform, y1, y: Y, x: X};
1819
}
1920

20-
export function stackX2(stack = {}, options = {}) {
21-
if (arguments.length === 1) [stack, options] = mergeOptions(stack);
21+
export function stackX2(stackOptions = {}, options = {}) {
22+
if (arguments.length === 1) [stackOptions, options] = mergeOptions(stackOptions);
2223
const {y1, y = y1, x} = options;
23-
const [transform, Y, , X] = stackAlias(y, x, "x", stack, options);
24+
const [transform, Y, , X] = stack(y, x, "y", "x", stackOptions, options);
2425
return {...transform, y1, y: Y, x: X};
2526
}
2627

27-
export function stackY(stack = {}, options = {}) {
28-
if (arguments.length === 1) [stack, options] = mergeOptions(stack);
28+
export function stackY(stackOptions = {}, options = {}) {
29+
if (arguments.length === 1) [stackOptions, options] = mergeOptions(stackOptions);
2930
const {x1, x = x1, y, ...rest} = options; // note: consumes y!
30-
const [transform, X, y1, y2] = stackAlias(x, y, "y", stack, rest);
31+
const [transform, X, y1, y2] = stack(x, y, "x", "y", stackOptions, rest);
3132
return {...transform, x1, x: X, y1, y2, y: mid(y1, y2)};
3233
}
3334

34-
export function stackY1(stack = {}, options = {}) {
35-
if (arguments.length === 1) [stack, options] = mergeOptions(stack);
35+
export function stackY1(stackOptions = {}, options = {}) {
36+
if (arguments.length === 1) [stackOptions, options] = mergeOptions(stackOptions);
3637
const {x1, x = x1, y} = options;
37-
const [transform, X, Y] = stackAlias(x, y, "y", stack, options);
38+
const [transform, X, Y] = stack(x, y, "x", "y", stackOptions, options);
3839
return {...transform, x1, x: X, y: Y};
3940
}
4041

41-
export function stackY2(stack = {}, options = {}) {
42-
if (arguments.length === 1) [stack, options] = mergeOptions(stack);
42+
export function stackY2(stackOptions = {}, options = {}) {
43+
if (arguments.length === 1) [stackOptions, options] = mergeOptions(stackOptions);
4344
const {x1, x = x1, y} = options;
44-
const [transform, X, , Y] = stackAlias(x, y, "y", stack, options);
45+
const [transform, X, , Y] = stack(x, y, "x", "y", stackOptions, options);
4546
return {...transform, x1, x: X, y: Y};
4647
}
4748

@@ -65,16 +66,16 @@ function mergeOptions(options) {
6566
return [{offset, order, reverse}, rest];
6667
}
6768

68-
function stack(x, y = one, ky, {offset, order, reverse}, options) {
69+
function stack(x, y = one, kx, ky, {offset, order, reverse}, options) {
6970
const z = maybeZ(options);
7071
const [X, setX] = maybeColumn(x);
7172
const [Y1, setY1] = column(y);
7273
const [Y2, setY2] = column(y);
7374
offset = maybeOffset(offset);
7475
order = maybeOrder(order, offset, ky);
7576
return [
76-
basic(options, (data, facets) => {
77-
const X = x == null ? undefined : setX(valueof(data, x));
77+
basic(options, (data, facets, plotOptions) => {
78+
const X = x == null ? undefined : setX(maybeApplyInterval(valueof(data, x), plotOptions?.[kx]));
7879
const Y = valueof(data, y, Float64Array);
7980
const Z = valueof(data, z);
8081
const O = order && order(data, X, Y, Z);
@@ -107,9 +108,6 @@ function stack(x, y = one, ky, {offset, order, reverse}, options) {
107108
];
108109
}
109110

110-
// This is used internally so we can use `stack` as an argument name.
111-
const stackAlias = stack;
112-
113111
function maybeOffset(offset) {
114112
if (offset == null) return;
115113
if (typeof offset === "function") return offset;

0 commit comments

Comments
 (0)