Skip to content

Commit 36dd5ea

Browse files
committed
Merge branch 'main' into mbostock/difference
2 parents 60b48f9 + c6c1bcd commit 36dd5ea

File tree

14 files changed

+525
-38
lines changed

14 files changed

+525
-38
lines changed

docs/transforms/group.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,8 @@ The following named reducers are supported:
366366
* *deviation* - the standard deviation
367367
* *variance* - the variance per [Welford’s algorithm](https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm)
368368
* *identity* - the array of values
369+
* *x* - the group’s *x* value (when grouping on *x*)
370+
* *y* - the group’s *y* value (when grouping on *y*)
369371

370372
In addition, a reducer may be specified as:
371373

docs/transforms/hexbin.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,9 +174,9 @@ Plot.plot({
174174

175175
The *options* must specify the **x** and **y** channels. The **binWidth** option (default 20) defines the distance between centers of neighboring hexagons in pixels. If any of **z**, **fill**, or **stroke** is a channel, the first of these channels will be used to subdivide bins.
176176

177-
The *outputs* options are similar to the [bin transform](./bin.md); each output channel receives as input, for each hexagon, the subset of the data which has been matched to its center. The outputs object specifies the aggregation method for each output channel.
177+
The *outputs* options are similar to the [bin transform](./bin.md); for each hexagon, an output channel value is derived by reducing the corresponding binned input channel values. The *outputs* object specifies the reducer for each output channel.
178178

179-
The following aggregation methods are supported:
179+
The following named reducers are supported:
180180

181181
* *first* - the first value, in input order
182182
* *last* - the last value, in input order
@@ -195,13 +195,22 @@ The following aggregation methods are supported:
195195
* *variance* - the variance per [Welford’s algorithm](https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Welford's_online_algorithm)
196196
* *mode* - the value with the most occurrences
197197
* *identity* - the array of values
198-
* a function to be passed the array of values for each bin and the extent of the bin
198+
* *x* - the hexagon’s *x* center
199+
* *y* - the hexagon’s *y* center
200+
201+
In addition, a reducer may be specified as:
202+
203+
* a function to be passed the array of values for each bin and the center of the bin
199204
* an object with a *reduceIndex* method
200205

206+
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 center of the bin (an object {data, x, y}); it must then return the corresponding aggregate value for the bin.
207+
208+
Most reducers 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*.
209+
201210
## hexbin(*outputs*, *options*) {#hexbin}
202211

203212
```js
204213
Plot.dot(olympians, Plot.hexbin({fill: "count"}, {x: "weight", y: "height"}))
205214
```
206215

207-
Bins (hexagonally) on **x** and **y**. Also groups on the first channel of **z**, **fill**, or **stroke**, if any.
216+
Bins hexagonally on **x** and **y**. Also groups on the first channel of **z**, **fill**, or **stroke**, if any.

src/transforms/dodge.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export interface DodgeYOptions extends DodgeOptions {
6060
* If *dodgeOptions* is a string, it is shorthand for the dodge **anchor**.
6161
*/
6262
export function dodgeX<T>(options?: T & DodgeXOptions): Initialized<T>;
63-
export function dodgeX<T>(dodgeOptions: DodgeXOptions | DodgeXOptions["anchor"], options?: T): Initialized<T>;
63+
export function dodgeX<T>(dodgeOptions?: DodgeXOptions | DodgeXOptions["anchor"], options?: T): Initialized<T>;
6464

6565
/**
6666
* Given an **x** position channel, derives a new **y** position channel that
@@ -72,4 +72,4 @@ export function dodgeX<T>(dodgeOptions: DodgeXOptions | DodgeXOptions["anchor"],
7272
* If *dodgeOptions* is a string, it is shorthand for the dodge **anchor**.
7373
*/
7474
export function dodgeY<T>(options?: T & DodgeYOptions): Initialized<T>;
75-
export function dodgeY<T>(dodgeOptions: DodgeYOptions | DodgeYOptions["anchor"], options?: T): Initialized<T>;
75+
export function dodgeY<T>(dodgeOptions?: DodgeYOptions | DodgeYOptions["anchor"], options?: T): Initialized<T>;

src/transforms/group.d.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,42 @@ export interface GroupOutputOptions<T = Reducer> {
3838
z?: ChannelValue;
3939
}
4040

41+
/**
42+
* How to reduce grouped values; one of:
43+
*
44+
* - a generic reducer name, such as *count* or *first*
45+
* - *x* - the group’s **x** value (when grouping on **x**)
46+
* - *y* - the group’s **y** value (when grouping on **y**)
47+
* - a function that takes an array of values and returns the reduced value
48+
* - an object that implements the *reduceIndex* method
49+
*
50+
* When a reducer function or implementation is used with the group transform,
51+
* it is passed the group extent {x, y} as an additional argument.
52+
*/
53+
export type GroupReducer = Reducer | GroupReducerFunction | GroupReducerImplementation | "x" | "y";
54+
55+
/**
56+
* A shorthand functional group reducer implementation: given an array of input
57+
* channel *values*, and the current group’s *extent*, returns the corresponding
58+
* reduced output value.
59+
*/
60+
export type GroupReducerFunction<S = any, T = S> = (values: S[], extent: {x: any; y: any}) => T;
61+
62+
/** A group reducer implementation. */
63+
export interface GroupReducerImplementation<S = any, T = S> {
64+
/**
65+
* Given an *index* representing the contents of the current group, the input
66+
* channel’s array of *values*, and the current group’s *extent*, returns the
67+
* corresponding reduced output value. If no input channel is supplied (e.g.,
68+
* as with the *count* reducer) then *values* may be undefined.
69+
*/
70+
reduceIndex(index: number[], values: S[], extent: {x: any; y: any}): T;
71+
// TODO scope
72+
// TODO label
73+
}
74+
4175
/** Output channels (and options) for the group transform. */
42-
export type GroupOutputs = ChannelReducers | GroupOutputOptions;
76+
export type GroupOutputs = ChannelReducers<GroupReducer> | GroupOutputOptions<GroupReducer>;
4377

4478
/**
4579
* Groups on the first channel of **z**, **fill**, or **stroke**, if any, and

src/transforms/group.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,7 @@ function invalidReduce(reduce) {
287287
throw new Error(`invalid reduce: ${reduce}`);
288288
}
289289

290-
function maybeGroupOutputs(outputs, inputs) {
290+
export function maybeGroupOutputs(outputs, inputs) {
291291
return maybeOutputs(outputs, inputs, maybeGroupOutput);
292292
}
293293

src/transforms/hexbin.d.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type {ChannelReducers, ChannelValue} from "../channel.js";
22
import type {Initialized} from "./basic.js";
3+
import type {GroupReducer} from "./group.js";
34

45
/** Options for the hexbin transform. */
56
export interface HexbinOptions {
@@ -43,4 +44,4 @@ export interface HexbinOptions {
4344
*
4445
* To draw empty hexagons, see the hexgrid mark.
4546
*/
46-
export function hexbin<T>(outputs?: ChannelReducers, options?: T & HexbinOptions): Initialized<T>;
47+
export function hexbin<T>(outputs?: ChannelReducers<GroupReducer>, options?: T & HexbinOptions): Initialized<T>;

src/transforms/hexbin.js

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import {map, number, valueof} from "../options.js";
22
import {applyPosition} from "../projection.js";
33
import {sqrt3} from "../symbol.js";
44
import {initializer} from "./basic.js";
5-
import {hasOutput, maybeGroup, maybeOutputs, maybeSubgroup} from "./group.js";
5+
import {hasOutput, maybeGroup, maybeGroupOutputs, maybeSubgroup} from "./group.js";
66

77
// We don’t want the hexagons to align with the edges of the plot frame, as that
88
// would cause extreme x-values (the upper bound of the default x-scale domain)
@@ -16,9 +16,8 @@ export function hexbin(outputs = {fill: "count"}, {binWidth, ...options} = {}) {
1616
const {z} = options;
1717

1818
// TODO filter e.g. to show empty hexbins?
19-
// TODO disallow x, x1, x2, y, y1, y2 reducers?
2019
binWidth = binWidth === undefined ? 20 : number(binWidth);
21-
outputs = maybeOutputs(outputs, options);
20+
outputs = maybeGroupOutputs(outputs, options);
2221

2322
// A fill output means a fill channel; declaring the channel here instead of
2423
// waiting for the initializer allows the mark constructor to determine that
@@ -65,15 +64,15 @@ export function hexbin(outputs = {fill: "count"}, {binWidth, ...options} = {}) {
6564
const binFacet = [];
6665
for (const o of outputs) o.scope("facet", facet);
6766
for (const [f, I] of maybeGroup(facet, G)) {
68-
for (const bin of hbin(I, X, Y, binWidth)) {
67+
for (const {index: b, extent} of hbin(data, I, X, Y, binWidth)) {
6968
binFacet.push(++i);
70-
BX.push(bin.x);
71-
BY.push(bin.y);
72-
if (Z) GZ.push(G === Z ? f : Z[bin[0]]);
73-
if (F) GF.push(G === F ? f : F[bin[0]]);
74-
if (S) GS.push(G === S ? f : S[bin[0]]);
75-
if (Q) GQ.push(G === Q ? f : Q[bin[0]]);
76-
for (const o of outputs) o.reduce(bin);
69+
BX.push(extent.x);
70+
BY.push(extent.y);
71+
if (Z) GZ.push(G === Z ? f : Z[b[0]]);
72+
if (F) GF.push(G === F ? f : F[b[0]]);
73+
if (S) GS.push(G === S ? f : S[b[0]]);
74+
if (Q) GQ.push(G === Q ? f : Q[b[0]]);
75+
for (const o of outputs) o.reduce(b, extent);
7776
}
7877
}
7978
binFacets.push(binFacet);
@@ -106,7 +105,7 @@ export function hexbin(outputs = {fill: "count"}, {binWidth, ...options} = {}) {
106105
});
107106
}
108107

109-
function hbin(I, X, Y, dx) {
108+
function hbin(data, I, X, Y, dx) {
110109
const dy = dx * (1.5 / sqrt3);
111110
const bins = new Map();
112111
for (const i of I) {
@@ -127,11 +126,10 @@ function hbin(I, X, Y, dx) {
127126
const key = `${pi},${pj}`;
128127
let bin = bins.get(key);
129128
if (bin === undefined) {
130-
bins.set(key, (bin = []));
131-
bin.x = (pi + (pj & 1) / 2) * dx + ox;
132-
bin.y = pj * dy + oy;
129+
bin = {index: [], extent: {data, x: (pi + (pj & 1) / 2) * dx + ox, y: pj * dy + oy}};
130+
bins.set(key, bin);
133131
}
134-
bin.push(i);
132+
bin.index.push(i);
135133
}
136134
return bins.values();
137135
}

src/transforms/normalize.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ export interface NormalizeOptions {
5757
* *x₀*, …] as in an index chart.
5858
*/
5959
export function normalizeX<T>(options?: T & NormalizeOptions): Transformed<T>;
60-
export function normalizeX<T>(basis: NormalizeBasis, options?: T): Transformed<T>;
60+
export function normalizeX<T>(basis?: NormalizeBasis, options?: T): Transformed<T>;
6161

6262
/**
6363
* Groups data into series using the first channel of **z**, **fill**, or
@@ -68,7 +68,7 @@ export function normalizeX<T>(basis: NormalizeBasis, options?: T): Transformed<T
6868
* *y₀*, …] as in an index chart.
6969
*/
7070
export function normalizeY<T>(options?: T & NormalizeOptions): Transformed<T>;
71-
export function normalizeY<T>(basis: NormalizeBasis, options?: T): Transformed<T>;
71+
export function normalizeY<T>(basis?: NormalizeBasis, options?: T): Transformed<T>;
7272

7373
/**
7474
* Given a normalize *basis*, returns a corresponding map implementation for use

src/transforms/stack.d.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,23 +120,23 @@ export interface StackOptions {
120120
* one.
121121
*/
122122
export function stackX<T>(options?: T & StackOptions): Transformed<T>;
123-
export function stackX<T>(stackOptions: StackOptions, options?: T): Transformed<T>;
123+
export function stackX<T>(stackOptions?: StackOptions, options?: T): Transformed<T>;
124124

125125
/**
126126
* Like **stackX**, but returns the starting position **x1** as the **x**
127127
* channel, for example to position a dot on the left-hand side of each element
128128
* of a stack.
129129
*/
130130
export function stackX1<T>(options?: T & StackOptions): Transformed<T>;
131-
export function stackX1<T>(stackOptions: StackOptions, options?: T): Transformed<T>;
131+
export function stackX1<T>(stackOptions?: StackOptions, options?: T): Transformed<T>;
132132

133133
/**
134134
* Like **stackX**, but returns the starting position **x2** as the **x**
135135
* channel, for example to position a dot on the right-hand side of each element
136136
* of a stack.
137137
*/
138138
export function stackX2<T>(options?: T & StackOptions): Transformed<T>;
139-
export function stackX2<T>(stackOptions: StackOptions, options?: T): Transformed<T>;
139+
export function stackX2<T>(stackOptions?: StackOptions, options?: T): Transformed<T>;
140140

141141
/**
142142
* Transforms a length channel **y** into starting and ending position channels
@@ -148,19 +148,19 @@ export function stackX2<T>(stackOptions: StackOptions, options?: T): Transformed
148148
* specified, the input channel **y** defaults to the constant one.
149149
*/
150150
export function stackY<T>(options?: T & StackOptions): Transformed<T>;
151-
export function stackY<T>(stackOptions: StackOptions, options?: T): Transformed<T>;
151+
export function stackY<T>(stackOptions?: StackOptions, options?: T): Transformed<T>;
152152

153153
/**
154154
* Like **stackY**, but returns the starting position **y1** as the **y**
155155
* channel, for example to position a dot at the bottom of each element of a
156156
* stack.
157157
*/
158158
export function stackY1<T>(options?: T & StackOptions): Transformed<T>;
159-
export function stackY1<T>(stackOptions: StackOptions, options?: T): Transformed<T>;
159+
export function stackY1<T>(stackOptions?: StackOptions, options?: T): Transformed<T>;
160160

161161
/**
162162
* Like **stackY**, but returns the ending position **y2** as the **y** channel,
163163
* for example to position a dot at the top of each element of a stack.
164164
*/
165165
export function stackY2<T>(options?: T & StackOptions): Transformed<T>;
166-
export function stackY2<T>(stackOptions: StackOptions, options?: T): Transformed<T>;
166+
export function stackY2<T>(stackOptions?: StackOptions, options?: T): Transformed<T>;

src/transforms/window.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ export interface WindowOptions {
109109
* If *windowOptions* is a number, it is shorthand for the window size **k**.
110110
*/
111111
export function windowX<T>(options?: T & WindowOptions): Transformed<T>;
112-
export function windowX<T>(windowOptions: WindowOptions | WindowOptions["k"], options?: T): Transformed<T>;
112+
export function windowX<T>(windowOptions?: WindowOptions | WindowOptions["k"], options?: T): Transformed<T>;
113113

114114
/**
115115
* Groups data into series using the first channel of *z*, *fill*, or *stroke*
@@ -124,7 +124,7 @@ export function windowX<T>(windowOptions: WindowOptions | WindowOptions["k"], op
124124
* If *windowOptions* is a number, it is shorthand for the window size **k**.
125125
*/
126126
export function windowY<T>(options?: T & WindowOptions): Transformed<T>;
127-
export function windowY<T>(windowOptions: WindowOptions | WindowOptions["k"], options?: T): Transformed<T>;
127+
export function windowY<T>(windowOptions?: WindowOptions | WindowOptions["k"], options?: T): Transformed<T>;
128128

129129
/**
130130
* Given the specified window *options*, returns a corresponding map

test/output/hexbinFillX.svg

Lines changed: 273 additions & 0 deletions
Loading

0 commit comments

Comments
 (0)