Skip to content

Commit fb4f0cb

Browse files
mbostockFil
authored andcommitted
descending shorthand (observablehq#1591)
* descending shorthand * no double reverse * minus shorthand only changes default order * edits * Update docs/features/scales.md Co-authored-by: Philippe Rivière <[email protected]> --------- Co-authored-by: Philippe Rivière <[email protected]>
1 parent e343fcb commit fb4f0cb

28 files changed

+271
-45
lines changed

docs/features/facets.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ Plot.plot({
4242
y: "variety",
4343
fy: "site",
4444
stroke: "year",
45-
sort: {y: "x", fy: "x", reduce: "median", reverse: true}
45+
sort: {y: "-x", fy: "-x", reduce: "median"}
4646
})
4747
]
4848
})
@@ -81,7 +81,7 @@ Plot.plot({
8181
fy: "site",
8282
stroke: "yield",
8383
strokeWidth: 2,
84-
sort: {y: "x1", fy: "x1", reduce: "median", reverse: true}
84+
sort: {y: "-x1", fy: "-x1", reduce: "median"}
8585
}))
8686
]
8787
})

docs/features/scales.md

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -969,21 +969,35 @@ Plot.barY(alphabet, {x: "letter", y: "frequency", sort: {x: "y"}})
969969

970970
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.md) (such as [stack](../transforms/stack.md) or [bin](../transforms/bin.md)). When sorting the *x* domain, if no **x** channel is defined, **x2** will be used instead if available, and similarly for *y* and **y2**; this is useful for marks that implicitly stack such as [area](../marks/area.md), [bar](../marks/bar.md), and [rect](../marks/rect.md). A sort value may also be specified as *width* or *height*, representing derived channels |*x2* - *x1*| and |*y2* - *y1*| respectively.
971971

972-
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:
972+
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. The default reducer is *max*, but may be changed by specifying the **reduce** option. Lastly the primary values are by default sorted based on the associated reduced value in natural ascending order to produce the domain. The above code is shorthand for:
973973

974974
```js
975-
Plot.barY(alphabet, {x: "letter", y: "frequency", sort: {x: "y", reduce: "max"}})
975+
Plot.barY(alphabet, {x: "letter", y: "frequency", sort: {x: "y", reduce: "max", order: "ascending"}})
976976
```
977977

978978
Generally speaking, a reducer only needs to be specified when there are multiple secondary values for a given primary value. See the [group transform](../transforms/group.md) for the list of supported reducers.
979979

980-
For descending rather than ascending order, use the *reverse* option:
980+
For descending rather than ascending order, set the **order** option to *descending*:
981+
982+
```js
983+
Plot.barY(alphabet, {x: "letter", y: "frequency", sort: {x: "y", order: "descending"}})
984+
```
985+
986+
Alternatively, the *-channel* shorthand option, which changes the default **order** to *descending*:
987+
988+
```js
989+
Plot.barY(alphabet, {x: "letter", y: "frequency", sort: {x: "-y"}})
990+
```
991+
992+
Setting **order** to null will disable sorting, preserving the order of the data. (When an aggregating transform is used, such as [group](../transforms/group.md) or [bin](../transforms/bin.md), note that the data may already have been sorted and thus the order may differ from the input data.)
993+
994+
Alternatively, set the **reverse** option to true. This produces a different result than descending order for null or unorderable values: descending order puts nulls last, whereas reversed ascending order puts nulls first.
981995

982996
```js
983997
Plot.barY(alphabet, {x: "letter", y: "frequency", sort: {x: "y", reverse: true}})
984998
```
985999

986-
An additional *limit* option truncates the domain to the first *n* values after sorting. If *limit* is negative, the last *n* values are used instead. Hence, a positive *limit* with *reverse* = true will return the top *n* values in descending order. If *limit* is an array [*lo*, *hi*], the *i*th values with *lo**i* < *hi* will be selected. (Note that like the [basic filter transform](../transforms/filter.md), limiting the *x* domain here does not affect the computation of the *y* domain, which is computed independently without respect to filtering.)
1000+
An additional **limit** option truncates the domain to the first *n* values after ordering. If **limit** is negative, the last *n* values are used instead. Hence, a positive **limit** with **reverse** = true will return the top *n* values in descending order. If **limit** is an array [*lo*, *hi*], the *i*th values with *lo**i* < *hi* will be selected. (Note that like the [basic filter transform](../transforms/filter.md), limiting the *x* domain here does not affect the computation of the *y* domain, which is computed independently without respect to filtering.)
9871001

9881002
```js
9891003
Plot.barY(alphabet, {x: "letter", y: "frequency", sort: {x: "y", limit: 5}})
@@ -992,7 +1006,7 @@ Plot.barY(alphabet, {x: "letter", y: "frequency", sort: {x: "y", limit: 5}})
9921006
If different sort options are needed for different ordinal scales, the channel name can be replaced with a *value* object with additional per-scale options.
9931007

9941008
```js
995-
Plot.barY(alphabet, {x: "letter", y: "frequency", sort: {x: {value: "y", reverse: true}}})
1009+
Plot.barY(alphabet, {x: "letter", y: "frequency", sort: {x: {value: "y", order: "descending"}}})
9961010
```
9971011

9981012
If the input channel is *data*, then the reducer is passed groups of the mark’s data; this is typically used in conjunction with a custom reducer function, as when the built-in single-channel reducers are insufficient.

docs/features/shorthand.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ Plot.tickX(numbers).plot()
156156
```
157157
:::
158158

159-
We could even use [Plot.vectorX](../marks/vector.md) here to draw little up-pointing arrows. (Typically the vector mark is used in conjunction with the *rotate* and *length* options to control the direction and magnitude of each vector.)
159+
We could even use [Plot.vectorX](../marks/vector.md) here to draw little up-pointing arrows. (Typically the vector mark is used in conjunction with the **rotate** and **length** options to control the direction and magnitude of each vector.)
160160

161161
:::plot https://observablehq.com/@observablehq/plot-shorthand-one-dimensional-vector
162162
```js

docs/marks/bar.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ Ordinal domains are sorted naturally (alphabetically) by default. Either set the
4141

4242
:::plot https://observablehq.com/@observablehq/plot-vertical-bars
4343
```js
44-
Plot.barY(alphabet, {x: "letter", y: "frequency", sort: {x: "y", reverse: true}}).plot()
44+
Plot.barY(alphabet, {x: "letter", y: "frequency", sort: {x: "-y"}}).plot()
4545
```
4646
:::
4747

docs/transforms/group.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ Plot.plot({
5252
x: {label: null, tickRotate: 90},
5353
y: {grid: true},
5454
marks: [
55-
Plot.barY(olympians, Plot.groupX({y: "count"}, {x: "sport", sort: {x: "y", reverse: true}})),
55+
Plot.barY(olympians, Plot.groupX({y: "count"}, {x: "sport", sort: {x: "-y"}})),
5656
Plot.ruleY([0])
5757
]
5858
})

docs/transforms/sort.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ Plot.plot({
4949
fill: "currentColor",
5050
stroke: "var(--vp-c-bg)",
5151
strokeWidth: 1,
52-
sort: sorted ? {channel: "r", order: "descending"} : null
52+
sort: sorted ? {channel: "-r"} : null
5353
}))
5454
]
5555
})
@@ -134,7 +134,7 @@ Sorts the data by the specified *order*, which is one of:
134134
- a field name
135135
- a {*channel*, *order*} object
136136

137-
In the object case, the **channel** option specifies the name of the channel, while the **order** option specifies *ascending* (the default) or *descending* order. For example, `sort: {channel: "r", order: "descending"}` will sort by descending radius (**r**).
137+
In the object case, the **channel** option specifies the name of the channel, while the **order** option specifies *ascending* (the default) or *descending* order. You can also use the shorthand *-name* to sort by descending order of the channel with the given *name*. For example, `sort: {channel: "-r"}` will sort by descending radius (**r**).
138138

139139
In the function case, if the sort function does not take exactly one argument, it is interpreted as a comparator function; otherwise it is interpreted as an accessor function.
140140

src/channel.d.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,9 @@ export type ChannelValueBinSpec = ChannelValue | ({value: ChannelValue} & BinOpt
162162
*/
163163
export type ChannelValueDenseBinSpec = ChannelValue | ({value: ChannelValue; scale?: Channel["scale"]} & Omit<BinOptions, "interval">); // prettier-ignore
164164

165+
/** A channel name, or an implied one for domain sorting. */
166+
type ChannelDomainName = ChannelName | "data" | "width" | "height";
167+
165168
/**
166169
* The available inputs for imputing scale domains. In addition to a named
167170
* channel, an input may be specified as:
@@ -177,7 +180,7 @@ export type ChannelValueDenseBinSpec = ChannelValue | ({value: ChannelValue; sca
177180
* custom **reduce** function, as when the built-in single-channel reducers are
178181
* insufficient.
179182
*/
180-
export type ChannelDomainValue = ChannelName | "data" | "width" | "height" | null;
183+
export type ChannelDomainValue = ChannelDomainName | `-${ChannelDomainName}` | null;
181184

182185
/** Options for imputing scale domains from channel values. */
183186
export interface ChannelDomainOptions {

src/channel.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,9 @@ export function channelDomain(data, facets, channels, facetChannels, options) {
8282
for (const x in options) {
8383
if (!registry.has(x)) continue; // ignore unknown scale keys (including generic options)
8484
let {value: y, order = defaultOrder, reverse = defaultReverse, reduce = defaultReduce, limit = defaultLimit} = maybeValue(options[x]); // prettier-ignore
85-
order = order === undefined ? y === "width" || y === "height" ? descendingGroup : ascendingGroup : maybeOrder(order); // prettier-ignore
85+
const negate = y?.startsWith("-");
86+
if (negate) y = y.slice(1);
87+
order = order === undefined ? negate !== (y === "width" || y === "height") ? descendingGroup : ascendingGroup : maybeOrder(order); // prettier-ignore
8688
if (reduce == null || reduce === false) continue; // disabled reducer
8789
const X = x === "fx" || x === "fy" ? reindexFacetChannel(facets, facetChannels[x]) : findScaleChannel(channels, x);
8890
if (!X) throw new Error(`missing channel for scale: ${x}`);

src/mark.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ export interface MarkOptions {
102102
* with a *value* object and per-scale options:
103103
*
104104
* ```js
105-
* sort: {y: {value: "x", reverse: true}}
105+
* sort: {y: {value: "-x"}}
106106
* ```
107107
*
108108
* When sorting the mark’s index, the **sort** option is instead one of:

src/marks/dot.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ const defaults = {
2323
};
2424

2525
export function withDefaultSort(options) {
26-
return options.sort === undefined && options.reverse === undefined
27-
? sort({channel: "r", order: "descending"}, options)
28-
: options;
26+
return options.sort === undefined && options.reverse === undefined ? sort({channel: "-r"}, options) : options;
2927
}
3028

3129
export class Dot extends Mark {

0 commit comments

Comments
 (0)