Skip to content

Commit ed0848d

Browse files
authored
sort aliases (#770)
* sort aliases * width and height, not dx and dy
1 parent 9d7d9c2 commit ed0848d

File tree

8 files changed

+8964
-23
lines changed

8 files changed

+8964
-23
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -420,7 +420,7 @@ If an ordinal scale’s domain is not set, it defaults to natural ascending orde
420420
Plot.barY(alphabet, {x: "letter", y: "frequency", sort: {x: "y"}})
421421
```
422422

423-
The sort option is an object whose keys are ordinal scale names, such as *x* or *fx*, and whose values are mark channel names, such as *y*, *y1*, or *y2*. By specifying an existing channel rather than a new value, you avoid repeating the order definition and can refer to channels derived by [transforms](#transforms) (such as [stack](#stack) or [bin](#bin)). For marks that implicitly stack ([area](#area), [bar](#bar), and [rect](#rect)), the stacked dimension is aliased: when stacking on *x*, *x* is an alias for *x2*, and when stacking on *y*, *y* is an alias for *y2*.
423+
The sort option is an object whose keys are ordinal scale names, such as *x* or *fx*, and whose values are mark channel names, such as *y*, *y1*, or *y2*. By specifying an existing channel rather than a new value, you avoid repeating the order definition and can refer to channels derived by [transforms](#transforms) (such as [stack](#stack) or [bin](#bin)). When sorting on the *x*, if no such channel is defined, the *x2* channel will be used instead if available, and similarly for *y* and *y2*; this is useful for marks that implicitly stack such as [area](#area), [bar](#bar), and [rect](#rect). A sort value may also be specified as *width* or *height*, representing derived channels |*x2* - *x1*| and |*y2* - *y1*| respectively.
424424

425425
Note that there may be multiple associated values in the secondary dimension for a given value in the primary ordinal dimension. The secondary values are therefore grouped for each associated primary value, and each group is then aggregated by applying a reducer. Lastly the primary values are sorted based on the associated reduced value in natural ascending order to produce the domain. The default reducer is *max*, but may be changed by specifying the *reduce* option. The above code is shorthand for:
426426

src/channel.js

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ export function channelSort(channels, facetChannels, data, options) {
1919
const {reverse: defaultReverse, reduce: defaultReduce = true, limit: defaultLimit} = options;
2020
for (const x in options) {
2121
if (!registry.has(x)) continue; // ignore unknown scale keys
22-
const {value: y, reverse = defaultReverse, reduce = defaultReduce, limit = defaultLimit} = maybeValue(options[x]);
22+
let {value: y, reverse = defaultReverse, reduce = defaultReduce, limit = defaultLimit} = maybeValue(options[x]);
23+
if (reverse === undefined) reverse = y === "width" || y === "height"; // default to descending for lengths
2324
if (reduce == null || reduce === false) continue; // disabled reducer
2425
const X = channels.find(([, {scale}]) => scale === x) || facetChannels && facetChannels.find(([, {scale}]) => scale === x);
2526
if (!X) throw new Error(`missing channel for scale: ${x}`);
@@ -33,14 +34,10 @@ export function channelSort(channels, facetChannels, data, options) {
3334
return domain;
3435
};
3536
} else {
36-
let YV;
37-
if (y === "data") {
38-
YV = data;
39-
} else {
40-
const Y = channels.find(([name]) => name === y);
41-
if (!Y) throw new Error(`missing channel: ${y}`);
42-
YV = Y[1].value;
43-
}
37+
const YV = y === "data" ? data
38+
: y === "height" ? difference(channels, "y1", "y2")
39+
: y === "width" ? difference(channels, "x1", "x2")
40+
: values(channels, y, y === "y" ? "y2" : y === "x" ? "x2" : undefined);
4441
const reducer = maybeReduce(reduce === true ? "max" : reduce, YV);
4542
X[1].domain = () => {
4643
let domain = rollup(range(XV), I => reducer.reduce(I, YV), i => XV[i]);
@@ -52,6 +49,19 @@ export function channelSort(channels, facetChannels, data, options) {
5249
}
5350
}
5451

52+
function difference(channels, k1, k2) {
53+
const X1 = values(channels, k1);
54+
const X2 = values(channels, k2);
55+
return Float64Array.from(X2, (x2, i) => Math.abs(x2 - X1[i]));
56+
}
57+
58+
function values(channels, name, alias) {
59+
let channel = channels.find(([n]) => n === name);
60+
if (!channel && alias !== undefined) channel = channels.find(([n]) => n === alias);
61+
if (channel) return channel[1].value;
62+
throw new Error(`missing channel: ${name}`);
63+
}
64+
5565
function ascendingGroup([ak, av], [bk, bv]) {
5666
return ascending(av, bv) || ascending(ak, bk);
5767
}

src/transforms/stack.js

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {InternMap, cumsum, group, groupSort, greatest, max, min, rollup, sum} from "d3";
22
import {ascendingDefined} from "../defined.js";
3-
import {field, lazyChannel, maybeLazyChannel, maybeZ, mid, range, valueof, maybeZero, isOptions, maybeValue} from "../options.js";
3+
import {field, lazyChannel, maybeLazyChannel, maybeZ, mid, range, valueof, maybeZero} from "../options.js";
44
import {basic} from "./basic.js";
55

66
export function stackX(stackOptions = {}, options = {}) {
@@ -46,29 +46,17 @@ export function stackY2(stackOptions = {}, options = {}) {
4646
}
4747

4848
export function maybeStackX({x, x1, x2, ...options} = {}) {
49-
options = aliasSort(options, "x");
5049
if (x1 === undefined && x2 === undefined) return stackX({x, ...options});
5150
([x1, x2] = maybeZero(x, x1, x2));
5251
return {...options, x1, x2};
5352
}
5453

5554
export function maybeStackY({y, y1, y2, ...options} = {}) {
56-
options = aliasSort(options, "y");
5755
if (y1 === undefined && y2 === undefined) return stackY({y, ...options});
5856
([y1, y2] = maybeZero(y, y1, y2));
5957
return {...options, y1, y2};
6058
}
6159

62-
function aliasSort(options, name) {
63-
let {sort} = options;
64-
if (!isOptions(sort)) return options;
65-
for (const x in sort) {
66-
const {value: y, ...rest} = maybeValue(sort[x]);
67-
if (y === name) sort = {...sort, [x]: {value: `${y}2`, ...rest}};
68-
}
69-
return {...options, sort};
70-
}
71-
7260
// The reverse option is ambiguous: it is both a stack option and a basic
7361
// transform. If only one options object is specified, we interpret it as a
7462
// stack option, and therefore must remove it from the propagated options.

test/data/README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ https://github.com/hemanrobinson/preattentive/blob/a58dd4795d0ee063a38a2d7bf3381
4343
ggplot2 “diamonds” dataset (carat and price columns only)
4444
https://github.com/tidyverse/ggplot2/blob/master/data-raw/diamonds.csv
4545

46+
## energy-production.csv
47+
U.S. Energy Information Administration; monthly energy review, primary energy production by source, Jan. 2022
48+
https://www.eia.gov/totalenergy/data/monthly/index.php
49+
4650
## gistemp.csv
4751
NASA Goddard Institute for Space Studies
4852
https://data.giss.nasa.gov/gistemp/

0 commit comments

Comments
 (0)