diff --git a/README.md b/README.md index 4172a9fb91..f690a153e9 100644 --- a/README.md +++ b/README.md @@ -2126,9 +2126,9 @@ The following aggregation methods are supported: * *y1* - the lower bound of the bin’s *y* extent (when binning on *y*) * *y2* - the upper bound of the bin’s *y* extent (when binning on *y*) * a function to be passed the array of values for each bin and the extent of the bin -* an object with a *reduce* method, and optionally a *scope* +* an object with a *reduceIndex* method, and optionally a *scope* -In the last case, the *reduce* method is repeatedly passed three arguments: the index for each bin (an array of integers), the input channel’s array of values, and the extent of the bin (an object {x1, x2, y1, y2}); it must then return the corresponding aggregate value for the bin. If the reducer object’s *scope* is “data”, then the *reduce* method is first invoked for the full data; the return value of the *reduce* method is then made available as a third argument (making the extent the fourth argument). Similarly if the *scope* is “facet”, then the *reduce* method is invoked for each facet, and the resulting reduce value is made available while reducing the facet’s bins. (This optional *scope* is used by the *proportion* and *proportion-facet* reducers.) +In the last case, the *reduceIndex* method is repeatedly passed three arguments: the index for each bin (an array of integers), the input channel’s array of values, and the extent of the bin (an object {x1, x2, y1, y2}); it must then return the corresponding aggregate value for the bin. If the reducer object’s *scope* is “data”, then the *reduceIndex* method is first invoked for the full data; the return value of the *reduceIndex* method is then made available as a third argument (making the extent the fourth argument). Similarly if the *scope* is “facet”, then the *reduceIndex* method is invoked for each facet, and the resulting reduce value is made available while reducing the facet’s bins. (This optional *scope* is used by the *proportion* and *proportion-facet* reducers.) Most aggregation methods require binding the output channel to an input channel; for example, if you want the **y** output channel to be a *sum* (not merely a count), there should be a corresponding **y** input channel specifying which values to sum. If there is not, *sum* will be equivalent to *count*. @@ -2277,9 +2277,9 @@ The following aggregation methods are supported: * *deviation* - the standard deviation * *variance* - the variance per [Welford’s algorithm](https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm) * a function - passed the array of values for each group -* an object with a *reduce* method, an optionally a *scope* +* an object with a *reduceIndex* method, an optionally a *scope* -In the last case, the *reduce* method is repeatedly passed two arguments: the index for each group (an array of integers), and the input channel’s array of values; it must then return the corresponding aggregate value for the group. If the reducer object’s *scope* is “data”, then the *reduce* method is first invoked for the full data; the return value of the *reduce* method is then made available as a third argument. Similarly if the *scope* is “facet”, then the *reduce* method is invoked for each facet, and the resulting reduce value is made available while reducing the facet’s groups. (This optional *scope* is used by the *proportion* and *proportion-facet* reducers.) +In the last case, the *reduceIndex* method is repeatedly passed two arguments: the index for each group (an array of integers), and the input channel’s array of values; it must then return the corresponding aggregate value for the group. If the reducer object’s *scope* is “data”, then the *reduceIndex* method is first invoked for the full data; the return value of the *reduceIndex* method is then made available as a third argument. Similarly if the *scope* is “facet”, then the *reduceIndex* method is invoked for each facet, and the resulting reduce value is made available while reducing the facet’s groups. (This optional *scope* is used by the *proportion* and *proportion-facet* reducers.) Most aggregation methods require binding the output channel to an input channel; for example, if you want the **y** output channel to be a *sum* (not merely a count), there should be a corresponding **y** input channel specifying which values to sum. If there is not, *sum* will be equivalent to *count*. @@ -2357,9 +2357,9 @@ The following map methods are supported: * *rank* - the rank of each value in the sorted array * *quantile* - the rank, normalized between 0 and 1 * a function to be passed an array of values, returning new values -* an object that implements the *map* method +* an object that implements the *mapIndex* method -If a function is used, it must return an array of the same length as the given input. If a *map* method is used, it is repeatedly passed the index for each series (an array of integers), the corresponding input channel’s array of values, and the output channel’s array of values; it must populate the slots specified by the index in the output array. +If a function is used, it must return an array of the same length as the given input. If a *mapIndex* method is used, it is repeatedly passed the index for each series (an array of integers), the corresponding input channel’s array of values, and the output channel’s array of values; it must populate the slots specified by the index in the output array. The Plot.normalizeX and Plot.normalizeY transforms normalize series values relative to the given basis. For example, if the series values are [*y₀*, *y₁*, *y₂*, …] and the *first* basis is used, the mapped series values would be [*y₀* / *y₀*, *y₁* / *y₀*, *y₂* / *y₀*, …] as in an index chart. The **basis** option specifies how to normalize the series values. The following basis methods are supported: @@ -2849,7 +2849,7 @@ The following aggregation methods are supported: * *variance* - the variance per [Welford’s algorithm](https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm) * *mode* - the value with the most occurrences * a function to be passed the array of values for each bin and the extent of the bin -* an object with a *reduce* method +* an object with a *reduceIndex* method See also the [hexgrid](#hexgrid) mark. diff --git a/src/channel.js b/src/channel.js index 2cd02dc2c0..bb67310dd7 100644 --- a/src/channel.js +++ b/src/channel.js @@ -107,7 +107,7 @@ export function channelDomain(data, facets, channels, facetChannels, options) { X.domain = () => { let domain = rollup( range(XV), - (I) => reducer.reduce(I, YV), + (I) => reducer.reduceIndex(I, YV), (i) => XV[i] ); domain = sort(domain, reverse ? descendingGroup : ascendingGroup); diff --git a/src/marks/auto.js b/src/marks/auto.js index 062bd43f46..851806e99e 100644 --- a/src/marks/auto.js +++ b/src/marks/auto.js @@ -294,7 +294,9 @@ function isOrdinalReduced(reduce, value) { // https://github.com/observablehq/plot/blob/818562649280e155136f730fc496e0b3d15ae464/src/transforms/group.js#L236 function isReducer(reduce) { - if (typeof reduce?.reduce === "function" && isObject(reduce)) return true; // N.B. array.reduce + if (reduce == null) return false; + if (typeof reduce.reduceIndex === "function") return true; + if (typeof reduce.reduce === "function" && isObject(reduce)) return true; // N.B. array.reduce if (/^p\d{2}$/i.test(reduce)) return true; switch (`${reduce}`.toLowerCase()) { case "first": diff --git a/src/reducer.d.ts b/src/reducer.d.ts index 9489442c5b..3ccc311069 100644 --- a/src/reducer.d.ts +++ b/src/reducer.d.ts @@ -29,7 +29,7 @@ export type ReducerFunction = (values: any[]) => any; // TODO scope, label export interface ReducerImplementation { - reduce(index: number[], values: any[]): any; + reduceIndex(index: number[], values: any[]): any; } export type Reducer = ReducerName | ReducerFunction | ReducerImplementation; diff --git a/src/transforms/bin.d.ts b/src/transforms/bin.d.ts index a99c811062..bde83fc51f 100644 --- a/src/transforms/bin.d.ts +++ b/src/transforms/bin.d.ts @@ -31,7 +31,7 @@ export type BinReducerFunction = (values: any[], extent: {x1: any; y1: any; x2: // TODO scope, label export interface BinReducerImplementation { - reduce(index: number[], values: any[], extent: {x1: any; y1: any; x2: any; y2: any}): any; + reduceIndex(index: number[], values: any[], extent: {x1: any; y1: any; x2: any; y2: any}): any; } export interface BinOutputOptions extends BinOptions { diff --git a/src/transforms/bin.js b/src/transforms/bin.js index e1644f22b2..c0d9128de1 100644 --- a/src/transforms/bin.js +++ b/src/transforms/bin.js @@ -174,7 +174,7 @@ function binn( for (const [b, extent] of bin(g)) { if (filter && !filter.reduce(b, extent)) continue; groupFacet.push(i++); - groupData.push(reduceData.reduce(b, data, extent)); + groupData.push(reduceData.reduceIndex(b, data, extent)); if (K) GK.push(k); if (Z) GZ.push(G === Z ? f : Z[b[0]]); if (F) GF.push(G === F ? f : F[b[0]]); @@ -437,37 +437,37 @@ function mid1(x1, x2) { } const reduceX = { - reduce(I, X, {x1, x2}) { + reduceIndex(I, X, {x1, x2}) { return mid1(x1, x2); } }; const reduceY = { - reduce(I, X, {y1, y2}) { + reduceIndex(I, X, {y1, y2}) { return mid1(y1, y2); } }; const reduceX1 = { - reduce(I, X, {x1}) { + reduceIndex(I, X, {x1}) { return x1; } }; const reduceX2 = { - reduce(I, X, {x2}) { + reduceIndex(I, X, {x2}) { return x2; } }; const reduceY1 = { - reduce(I, X, {y1}) { + reduceIndex(I, X, {y1}) { return y1; } }; const reduceY2 = { - reduce(I, X, {y2}) { + reduceIndex(I, X, {y2}) { return y2; } }; diff --git a/src/transforms/group.js b/src/transforms/group.js index 804e9e9174..6900b67179 100644 --- a/src/transforms/group.js +++ b/src/transforms/group.js @@ -135,7 +135,7 @@ function groupn( for (const [x, g] of maybeGroup(gg, X)) { if (filter && !filter.reduce(g)) continue; groupFacet.push(i++); - groupData.push(reduceData.reduce(g, data)); + groupData.push(reduceData.reduceIndex(g, data)); if (X) GX.push(x); if (Y) GY.push(y); if (Z) GZ.push(G === Z ? f : Z[g[0]]); @@ -178,7 +178,7 @@ export function maybeOutputs(outputs, inputs, asOutput = maybeOutput) { export function maybeOutput(name, reduce, inputs, asEvaluator = maybeEvaluator) { let scale; // optional per-channel scale override - if (isObject(reduce) && typeof reduce.reduce !== "function") (scale = reduce.scale), (reduce = reduce.reduce); + if (isObject(reduce) && "reduce" in reduce) (scale = reduce.scale), (reduce = reduce.reduce); // N.B. array.reduce const evaluator = asEvaluator(name, reduce, inputs); const [output, setOutput] = column(evaluator.label); let O; @@ -211,16 +211,16 @@ export function maybeEvaluator(name, reduce, inputs, asReduce = maybeReduce) { initialize(data) { V = input === undefined ? data : valueof(data, input); if (reducer.scope === "data") { - context = reducer.reduce(range(data), V); + context = reducer.reduceIndex(range(data), V); } }, scope(scope, I) { if (reducer.scope === scope) { - context = reducer.reduce(I, V); + context = reducer.reduceIndex(I, V); } }, reduce(I, extent) { - return reducer.scope == null ? reducer.reduce(I, V, extent) : reducer.reduce(I, V, context, extent); + return reducer.scope == null ? reducer.reduceIndex(I, V, extent) : reducer.reduceIndex(I, V, context, extent); } }; } @@ -235,7 +235,9 @@ export function maybeGroup(I, X) { } export function maybeReduce(reduce, value, fallback = invalidReduce) { - if (typeof reduce?.reduce === "function" && isObject(reduce)) return reduce; // N.B. array.reduce + if (reduce == null) return fallback(reduce); + if (typeof reduce.reduceIndex === "function") return reduce; + if (typeof reduce.reduce === "function" && isObject(reduce)) return reduceReduce(reduce); // N.B. array.reduce if (typeof reduce === "function") return reduceFunction(reduce); if (/^p\d{2}$/i.test(reduce)) return reduceAccessor(percentile(reduce)); switch (`${reduce}`.toLowerCase()) { @@ -299,9 +301,14 @@ export function maybeSort(facets, sort, reverse) { } } +function reduceReduce(reduce) { + console.warn("deprecated reduce interface; implement reduceIndex instead."); + return {...reduce, reduceIndex: reduce.reduce.bind(reduce)}; +} + function reduceFunction(f) { return { - reduce(I, X, extent) { + reduceIndex(I, X, extent) { return f(take(X, I), extent); } }; @@ -309,7 +316,7 @@ function reduceFunction(f) { function reduceAccessor(f) { return { - reduce(I, X) { + reduceIndex(I, X) { return f(I, (i) => X[i]); } }; @@ -317,7 +324,7 @@ function reduceAccessor(f) { function reduceMaybeTemporalAccessor(f) { return { - reduce(I, X) { + reduceIndex(I, X) { const x = f(I, (i) => X[i]); return isTemporal(X) ? new Date(x) : x; } @@ -325,19 +332,19 @@ function reduceMaybeTemporalAccessor(f) { } export const reduceIdentity = { - reduce(I, X) { + reduceIndex(I, X) { return take(X, I); } }; export const reduceFirst = { - reduce(I, X) { + reduceIndex(I, X) { return X[I[0]]; } }; const reduceTitle = { - reduce(I, X) { + reduceIndex(I, X) { const n = 5; const groups = sort( rollup( @@ -357,21 +364,21 @@ const reduceTitle = { }; const reduceLast = { - reduce(I, X) { + reduceIndex(I, X) { return X[I[I.length - 1]]; } }; export const reduceCount = { label: "Frequency", - reduce(I) { + reduceIndex(I) { return I.length; } }; const reduceDistinct = { label: "Distinct", - reduce: (I, X) => { + reduceIndex(I, X) { const s = new InternSet(); for (const i of I) s.add(X[i]); return s.size; @@ -382,6 +389,6 @@ const reduceSum = reduceAccessor(sum); function reduceProportion(value, scope) { return value == null - ? {scope, label: "Frequency", reduce: (I, V, basis = 1) => I.length / basis} - : {scope, reduce: (I, V, basis = 1) => sum(I, (i) => V[i]) / basis}; + ? {scope, label: "Frequency", reduceIndex: (I, V, basis = 1) => I.length / basis} + : {scope, reduceIndex: (I, V, basis = 1) => sum(I, (i) => V[i]) / basis}; } diff --git a/src/transforms/map.d.ts b/src/transforms/map.d.ts index 04e3c64067..30ecf9c53d 100644 --- a/src/transforms/map.d.ts +++ b/src/transforms/map.d.ts @@ -6,7 +6,7 @@ export type MapFunction = (values: S[]) => T[]; export type MapName = "cumsum" | "rank" | "quantile"; export interface MapImplementation { - map(index: number[], source: S[], target: T[]): void; + mapIndex(index: number[], source: S[], target: T[]): void; } export type Map = MapImplementation | MapFunction | MapName; diff --git a/src/transforms/map.js b/src/transforms/map.js index 23d08acc15..615fdf5c1f 100644 --- a/src/transforms/map.js +++ b/src/transforms/map.js @@ -1,5 +1,5 @@ import {count, group, rank} from "d3"; -import {maybeZ, take, valueof, maybeInput, column} from "../options.js"; +import {column, isObject, maybeInput, maybeZ, take, valueof} from "../options.js"; import {basic} from "./basic.js"; export function mapX(map, options = {}) { @@ -31,7 +31,7 @@ export function map(outputs = {}, options = {}) { const MX = channels.map(({setOutput}) => setOutput(new Array(data.length))); for (const facet of facets) { for (const I of Z ? group(facet, (i) => Z[i]).values() : [facet]) { - channels.forEach(({map}, i) => map.map(I, X[i], MX[i])); + channels.forEach(({map}, i) => map.mapIndex(I, X[i], MX[i])); } } return {data, facets}; @@ -44,7 +44,9 @@ export function map(outputs = {}, options = {}) { const mapAlias = map; function maybeMap(map) { - if (map && typeof map.map === "function") return map; + if (map == null) throw new Error("missing map"); + if (typeof map.mapIndex === "function") return map; + if (typeof map.map === "function" && isObject(map)) return mapMap(map); // N.B. array.map if (typeof map === "function") return mapFunction(map); switch (`${map}`.toLowerCase()) { case "cumsum": @@ -57,6 +59,11 @@ function maybeMap(map) { throw new Error(`invalid map: ${map}`); } +function mapMap(map) { + console.warn("deprecated map interface; implement mapIndex instead."); + return {mapIndex: map.map.bind(map)}; +} + function rankQuantile(V) { const n = count(V) - 1; return rank(V).map((r) => r / n); @@ -64,7 +71,7 @@ function rankQuantile(V) { function mapFunction(f) { return { - map(I, S, T) { + mapIndex(I, S, T) { const M = f(take(S, I)); if (M.length !== I.length) throw new Error("map function returned a mismatched length"); for (let i = 0, n = I.length; i < n; ++i) T[I[i]] = M[i]; @@ -73,7 +80,7 @@ function mapFunction(f) { } const mapCumsum = { - map(I, S, T) { + mapIndex(I, S, T) { let sum = 0; for (const i of I) T[i] = sum += S[i]; } diff --git a/src/transforms/normalize.js b/src/transforms/normalize.js index 3eb03bef0c..0967312325 100644 --- a/src/transforms/normalize.js +++ b/src/transforms/normalize.js @@ -42,7 +42,7 @@ export function normalize(basis) { function normalizeBasis(basis) { return { - map(I, S, T) { + mapIndex(I, S, T) { const b = +basis(I, S); for (const i of I) { T[i] = S[i] === null ? NaN : S[i] / b; @@ -56,7 +56,7 @@ function normalizeAccessor(f) { } const normalizeExtent = { - map(I, S, T) { + mapIndex(I, S, T) { const [s1, s2] = extent(I, (i) => S[i]), d = s2 - s1; for (const i of I) { @@ -80,7 +80,7 @@ const normalizeLast = normalizeBasis((I, S) => { }); const normalizeDeviation = { - map(I, S, T) { + mapIndex(I, S, T) { const m = mean(I, (i) => S[i]); const d = deviation(I, (i) => S[i]); for (const i of I) { diff --git a/src/transforms/window.js b/src/transforms/window.js index a0300c8085..8b8b5f9411 100644 --- a/src/transforms/window.js +++ b/src/transforms/window.js @@ -95,7 +95,7 @@ function reduceNumbers(f) { return (k, s, strict) => strict ? { - map(I, S, T) { + mapIndex(I, S, T) { const C = Float64Array.from(I, (i) => (S[i] === null ? NaN : S[i])); let nans = 0; for (let i = 0; i < k - 1; ++i) if (isNaN(C[i])) ++nans; @@ -107,7 +107,7 @@ function reduceNumbers(f) { } } : { - map(I, S, T) { + mapIndex(I, S, T) { const C = Float64Array.from(I, (i) => (S[i] === null ? NaN : S[i])); for (let i = -s; i < 0; ++i) { T[I[i + s]] = f(C.subarray(0, i + k)); @@ -123,7 +123,7 @@ function reduceArray(f) { return (k, s, strict) => strict ? { - map(I, S, T) { + mapIndex(I, S, T) { let count = 0; for (let i = 0; i < k - 1; ++i) count += defined(S[I[i]]); for (let i = 0, n = I.length - k + 1; i < n; ++i) { @@ -134,7 +134,7 @@ function reduceArray(f) { } } : { - map(I, S, T) { + mapIndex(I, S, T) { for (let i = -s; i < 0; ++i) { T[I[i + s]] = f(take(S, slice(I, 0, i + k))); } @@ -148,7 +148,7 @@ function reduceArray(f) { function reduceSum(k, s, strict) { return strict ? { - map(I, S, T) { + mapIndex(I, S, T) { let nans = 0; let sum = 0; for (let i = 0; i < k - 1; ++i) { @@ -168,7 +168,7 @@ function reduceSum(k, s, strict) { } } : { - map(I, S, T) { + mapIndex(I, S, T) { let sum = 0; const n = I.length; for (let i = 0, j = Math.min(n, k - s - 1); i < j; ++i) { @@ -187,8 +187,8 @@ function reduceMean(k, s, strict) { if (strict) { const sum = reduceSum(k, s, strict); return { - map(I, S, T) { - sum.map(I, S, T); + mapIndex(I, S, T) { + sum.mapIndex(I, S, T); for (let i = 0, n = I.length - k + 1; i < n; ++i) { T[I[i + s]] /= k; } @@ -196,7 +196,7 @@ function reduceMean(k, s, strict) { }; } else { return { - map(I, S, T) { + mapIndex(I, S, T) { let sum = 0; let count = 0; const n = I.length; @@ -247,7 +247,7 @@ function lastNumber(S, I, i, k) { function reduceDifference(k, s, strict) { return strict ? { - map(I, S, T) { + mapIndex(I, S, T) { for (let i = 0, n = I.length - k; i < n; ++i) { const a = S[I[i]]; const b = S[I[i + k - 1]]; @@ -256,7 +256,7 @@ function reduceDifference(k, s, strict) { } } : { - map(I, S, T) { + mapIndex(I, S, T) { for (let i = -s, n = I.length - k + s + 1; i < n; ++i) { T[I[i + s]] = lastNumber(S, I, i, k) - firstNumber(S, I, i, k); } @@ -267,7 +267,7 @@ function reduceDifference(k, s, strict) { function reduceRatio(k, s, strict) { return strict ? { - map(I, S, T) { + mapIndex(I, S, T) { for (let i = 0, n = I.length - k; i < n; ++i) { const a = S[I[i]]; const b = S[I[i + k - 1]]; @@ -276,7 +276,7 @@ function reduceRatio(k, s, strict) { } } : { - map(I, S, T) { + mapIndex(I, S, T) { for (let i = -s, n = I.length - k + s + 1; i < n; ++i) { T[I[i + s]] = lastNumber(S, I, i, k) / firstNumber(S, I, i, k); } @@ -287,14 +287,14 @@ function reduceRatio(k, s, strict) { function reduceFirst(k, s, strict) { return strict ? { - map(I, S, T) { + mapIndex(I, S, T) { for (let i = 0, n = I.length - k; i < n; ++i) { T[I[i + s]] = S[I[i]]; } } } : { - map(I, S, T) { + mapIndex(I, S, T) { for (let i = -s, n = I.length - k + s + 1; i < n; ++i) { T[I[i + s]] = firstDefined(S, I, i, k); } @@ -305,14 +305,14 @@ function reduceFirst(k, s, strict) { function reduceLast(k, s, strict) { return strict ? { - map(I, S, T) { + mapIndex(I, S, T) { for (let i = 0, n = I.length - k; i < n; ++i) { T[I[i + s]] = S[I[i + k - 1]]; } } } : { - map(I, S, T) { + mapIndex(I, S, T) { for (let i = -s, n = I.length - k + s + 1; i < n; ++i) { T[I[i + s]] = lastDefined(S, I, i, k); } diff --git a/test/output/reducerScaleOverrideFunction.svg b/test/output/reducerScaleOverrideFunction.svg new file mode 100644 index 0000000000..9c78ec2c99 --- /dev/null +++ b/test/output/reducerScaleOverrideFunction.svg @@ -0,0 +1,97 @@ + + + + + FEMALE + + + + + + + + + 0 + 20 + 40 + 60 + + + + + + + + + + MALE + + + + + + + + + 0 + 20 + 40 + 60 + + + + + + + + + + + + + + + + 0 + 20 + 40 + 60 + + + + + + + + Adelie + Chinstrap + Gentoo + + + + + + + + sex + + + ↑ Frequency + + + species + + \ No newline at end of file diff --git a/test/output/reducerScaleOverrideImplementation.svg b/test/output/reducerScaleOverrideImplementation.svg new file mode 100644 index 0000000000..9c78ec2c99 --- /dev/null +++ b/test/output/reducerScaleOverrideImplementation.svg @@ -0,0 +1,97 @@ + + + + + FEMALE + + + + + + + + + 0 + 20 + 40 + 60 + + + + + + + + + + MALE + + + + + + + + + 0 + 20 + 40 + 60 + + + + + + + + + + + + + + + + 0 + 20 + 40 + 60 + + + + + + + + Adelie + Chinstrap + Gentoo + + + + + + + + sex + + + ↑ Frequency + + + species + + \ No newline at end of file diff --git a/test/output/reducerScaleOverrideName.svg b/test/output/reducerScaleOverrideName.svg new file mode 100644 index 0000000000..9c78ec2c99 --- /dev/null +++ b/test/output/reducerScaleOverrideName.svg @@ -0,0 +1,97 @@ + + + + + FEMALE + + + + + + + + + 0 + 20 + 40 + 60 + + + + + + + + + + MALE + + + + + + + + + 0 + 20 + 40 + 60 + + + + + + + + + + + + + + + + 0 + 20 + 40 + 60 + + + + + + + + Adelie + Chinstrap + Gentoo + + + + + + + + sex + + + ↑ Frequency + + + species + + \ No newline at end of file diff --git a/test/plots/index.ts b/test/plots/index.ts index f7a1af5413..3f07523991 100644 --- a/test/plots/index.ts +++ b/test/plots/index.ts @@ -227,6 +227,7 @@ export * from "./raster-penguins.js"; export * from "./raster-vapor.js"; export * from "./raster-walmart.js"; export * from "./rect-band.js"; +export * from "./reducer-scale-override.js"; export * from "./seattle-precipitation-density.js"; export * from "./seattle-precipitation-rule.js"; export * from "./seattle-precipitation-sum.js"; diff --git a/test/plots/reducer-scale-override.ts b/test/plots/reducer-scale-override.ts new file mode 100644 index 0000000000..21ab0f761b --- /dev/null +++ b/test/plots/reducer-scale-override.ts @@ -0,0 +1,25 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; + +async function reducerScaleOverride(reduce: Plot.Reducer) { + const penguins = await d3.csv("data/penguins.csv", d3.autoType); + return Plot.barY( + penguins, + Plot.groupX( + {y: "count", fill: {reduce, scale: true}}, + {x: "species", fill: (d) => (d.island === "Biscoe" ? "orange" : "green"), fy: "sex"} + ) + ).plot(); +} + +export async function reducerScaleOverrideFunction() { + return reducerScaleOverride((values) => d3.mode(values)); +} + +export async function reducerScaleOverrideImplementation() { + return reducerScaleOverride({reduceIndex: (index, values) => d3.mode(index, (i) => values[i])}); +} + +export async function reducerScaleOverrideName() { + return reducerScaleOverride("mode"); +}