Skip to content

Commit

Permalink
preserve group input order (#1959)
Browse files Browse the repository at this point in the history
* preserve group input order

* sort: "z"; fix tests

* reduce z docs

* fix tests

* Update docs/transforms/group.md

Co-authored-by: Philippe Rivière <[email protected]>

---------

Co-authored-by: Philippe Rivière <[email protected]>
  • Loading branch information
mbostock and Fil authored Dec 27, 2023
1 parent 3a9581b commit b5366f9
Show file tree
Hide file tree
Showing 70 changed files with 2,313 additions and 2,299 deletions.
1 change: 1 addition & 0 deletions docs/transforms/bin.md
Original file line number Diff line number Diff line change
Expand Up @@ -274,6 +274,7 @@ The following named reducers are supported:
* *y* - the middle of the bin’s *y* extent (when binning on *y*)
* *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*)
* *z* <VersionBadge pr="1959" /> - the bin’s *z* value (*z*, *fill*, or *stroke*)

In addition, a reducer may be specified as:

Expand Down
3 changes: 2 additions & 1 deletion docs/transforms/group.md
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ Although barX applies an implicit stackX transform, [textX](../marks/text.md) do

## Group options

Given input *data* = [*d₀*, *d₁*, *d₂*, …], by default the resulting grouped data is an array of arrays where each inner array is a subset of the input data such as [[*d₁*, *d₂*, …], [*d₀*, …], …]. Each inner array is in input order. The outer array is in natural ascending order according to the associated dimension (*x* then *y*).
Given input *data* = [*d₀*, *d₁*, *d₂*, …], by default the resulting grouped data is an array of arrays where each inner array is a subset of the input data such as [[*d₁*, *d₂*, …], [*d₀*, …], …]. Each inner array is in input order. The outer array is in input order according to the first element of each group.

By specifying a different reducer for the **data** output, as described below, you can change how the grouped data is computed. The outputs may also include **filter** and **sort** options specified as reducers, and a **reverse** option to reverse the order of generated groups. By default, empty groups are omitted, and non-empty groups are generated in ascending (natural) order.

Expand Down Expand Up @@ -370,6 +370,7 @@ The following named reducers are supported:
* *identity* - the array of values
* *x* <VersionBadge version="0.6.12" pr="1916" /> - the group’s *x* value (when grouping on *x*)
* *y* <VersionBadge version="0.6.12" pr="1916" /> - the group’s *y* value (when grouping on *y*)
* *z* <VersionBadge pr="1959" /> - the group’s *z* value (*z*, *fill*, or *stroke*)

In addition, a reducer may be specified as:

Expand Down
4 changes: 3 additions & 1 deletion src/transforms/bin.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ export interface BinOptions {
* - *y* - the middle of the bin’s **y** extent (when binning on **y**)
* - *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**)
* - *z* - the bin’s **z** value (when grouping on **z**, **fill**, or **stroke**)
* - a function that takes an array of values and returns the reduced value
* - an object that implements the *reduceIndex* method
*
Expand All @@ -132,7 +133,8 @@ export type BinReducer =
| "x2"
| "y"
| "y1"
| "y2";
| "y2"
| "z";

/**
* A shorthand functional bin reducer implementation: given an array of input
Expand Down
8 changes: 6 additions & 2 deletions src/transforms/bin.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ import {
maybeSubgroup,
reduceCount,
reduceFirst,
reduceIdentity
reduceIdentity,
reduceZ
} from "./group.js";
import {maybeInsetX, maybeInsetY} from "./inset.js";

Expand Down Expand Up @@ -180,6 +181,7 @@ function binn(
for (const [f, I] of maybeGroup(facet, G)) {
for (const [k, g] of maybeGroup(I, K)) {
for (const [b, extent] of bin(g)) {
if (G) extent.z = f;
if (filter && !filter.reduce(b, extent)) continue;
groupFacet.push(i++);
groupData.push(reduceData.reduceIndex(b, data, extent));
Expand All @@ -190,7 +192,7 @@ function binn(
if (BX1) BX1.push(extent.x1), BX2.push(extent.x2);
if (BY1) BY1.push(extent.y1), BY2.push(extent.y2);
for (const o of outputs) o.reduce(b, extent);
if (sort) sort.reduce(b);
if (sort) sort.reduce(b, extent);
}
}
}
Expand Down Expand Up @@ -355,6 +357,8 @@ function maybeBinReduceFallback(reduce) {
return reduceY1;
case "y2":
return reduceY2;
case "z":
return reduceZ;
}
throw new Error(`invalid bin reduce: ${reduce}`);
}
Expand Down
3 changes: 2 additions & 1 deletion src/transforms/group.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,14 @@ export interface GroupOutputOptions<T = Reducer> {
* - a generic reducer name, such as *count* or *first*
* - *x* - the group’s **x** value (when grouping on **x**)
* - *y* - the group’s **y** value (when grouping on **y**)
* - *z* - the group’s **z** value (when grouping on **z**, **fill**, or **stroke**)
* - a function that takes an array of values and returns the reduced value
* - an object that implements the *reduceIndex* method
*
* When a reducer function or implementation is used with the group transform,
* it is passed the group extent {x, y} as an additional argument.
*/
export type GroupReducer = Reducer | GroupReducerFunction | GroupReducerImplementation | "x" | "y";
export type GroupReducer = Reducer | GroupReducerFunction | GroupReducerImplementation | "x" | "y" | "z";

/**
* A shorthand functional group reducer implementation: given an array of input
Expand Down
17 changes: 10 additions & 7 deletions src/transforms/group.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
import {ascendingDefined} from "../defined.js";
import {
column,
first,
identity,
isObject,
isTemporal,
Expand Down Expand Up @@ -137,6 +136,7 @@ function groupn(
const extent = {data};
if (X) extent.x = x;
if (Y) extent.y = y;
if (G) extent.z = f;
if (filter && !filter.reduce(g, extent)) continue;
groupFacet.push(i++);
groupData.push(reduceData.reduceIndex(g, data, extent));
Expand Down Expand Up @@ -230,12 +230,7 @@ export function maybeEvaluator(name, reduce, inputs, asReduce = maybeReduce) {
}

export function maybeGroup(I, X) {
return X
? sort(
grouper(I, (i) => X[i]),
first
)
: [[, I]];
return X ? grouper(I, (i) => X[i]) : [[, I]];
}

export function maybeReduce(reduce, value, fallback = invalidReduce) {
Expand Down Expand Up @@ -309,6 +304,8 @@ function maybeGroupReduceFallback(reduce) {
return reduceX;
case "y":
return reduceY;
case "z":
return reduceZ;
}
throw new Error(`invalid group reduce: ${reduce}`);
}
Expand Down Expand Up @@ -437,6 +434,12 @@ const reduceY = {
}
};

export const reduceZ = {
reduceIndex(I, X, {z}) {
return z;
}
};

export function find(test) {
if (typeof test !== "function") throw new Error(`invalid test function: ${test}`);
return {
Expand Down
32 changes: 16 additions & 16 deletions test/output/athletesBirthdays.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit b5366f9

Please sign in to comment.