From 065c7f5296fbf711cef35a2129adc2520534cf40 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Fri, 26 May 2023 15:43:03 -0700 Subject: [PATCH 1/6] stack order comparator --- src/transforms/stack.d.ts | 3 +- src/transforms/stack.js | 30 ++++-- test/output/musicRevenueCustomOrder.svg | 134 ++++++++++++++++++++++++ test/plots/music-revenue.ts | 25 +++++ 4 files changed, 180 insertions(+), 12 deletions(-) create mode 100644 test/output/musicRevenueCustomOrder.svg diff --git a/src/transforms/stack.d.ts b/src/transforms/stack.d.ts index 116e2c72a9..ab81a104b0 100644 --- a/src/transforms/stack.d.ts +++ b/src/transforms/stack.d.ts @@ -63,9 +63,10 @@ export type StackOrderName = "value" | "x" | "y" | "z" | "sum" | "appearance" | * - a named stack order method such as *inside-out* or *sum* * - a field name, for natural order of the corresponding values * - a function of data, for natural order of the corresponding values + * - a comparator function for ordering data * - an array of explicit **z** values in the desired order */ -export type StackOrder = StackOrderName | (string & Record) | ((d: any, i: number) => any) | any[]; +export type StackOrder = StackOrderName | (string & Record) | ((a: any, b: any) => any) | any[]; /** Options for the stack transform. */ export interface StackOptions { diff --git a/src/transforms/stack.js b/src/transforms/stack.js index cad3675c54..f5b5d6162f 100644 --- a/src/transforms/stack.js +++ b/src/transforms/stack.js @@ -87,14 +87,14 @@ function stack(x, y = one, kx, ky, {offset, order, reverse}, options) { const X = x == null ? undefined : setX(maybeApplyInterval(valueof(data, x), plotOptions?.[kx])); const Y = valueof(data, y, Float64Array); const Z = valueof(data, z); - const O = order && order(data, X, Y, Z); + const compare = order && order(data, X, Y, Z); const n = data.length; const Y1 = setY1(new Float64Array(n)); const Y2 = setY2(new Float64Array(n)); const facetstacks = []; for (const facet of facets) { const stacks = X ? Array.from(group(facet, (i) => X[i]).values()) : [facet]; - if (O) applyOrder(stacks, O); + if (compare) applyOrder(stacks, compare); for (const stack of stacks) { let yn = 0; let yp = 0; @@ -244,21 +244,21 @@ function maybeOrder(order, offset, ky) { case "inside-out": return orderInsideOut; } - return orderFunction(field(order)); + return orderAccessor(field(order)); } - if (typeof order === "function") return orderFunction(order); + if (typeof order === "function") return (order.length === 1 ? orderAccessor : orderComparator)(order); if (Array.isArray(order)) return orderGiven(order); throw new Error(`invalid order: ${order}`); } // by value function orderY(data, X, Y) { - return Y; + return orderNatural(Y); } // by location function orderZ(order, X, Y, Z) { - return Z; + return orderNatural(Z); } // by sum of value (a.k.a. “ascending”) @@ -314,8 +314,16 @@ function orderInsideOut(data, X, Y, Z) { return orderZDomain(Z, Kn.reverse().concat(Kp)); } -function orderFunction(f) { - return (data) => valueof(data, f); +function orderNatural(O) { + return (i, j) => ascendingDefined(O[i], O[j]); +} + +function orderAccessor(f) { + return (data) => orderNatural(valueof(data, f)); +} + +function orderComparator(f) { + return (data) => (i, j) => f(data[i], data[j]); } function orderGiven(domain) { @@ -328,11 +336,11 @@ function orderGiven(domain) { function orderZDomain(Z, domain) { if (!Z) throw new Error("missing channel: z"); domain = new InternMap(domain.map((d, i) => [d, i])); - return Z.map((z) => domain.get(z)); + return orderNatural(Z.map((z) => domain.get(z))); } -function applyOrder(stacks, O) { +function applyOrder(stacks, compare) { for (const stack of stacks) { - stack.sort((i, j) => ascendingDefined(O[i], O[j])); + stack.sort(compare); } } diff --git a/test/output/musicRevenueCustomOrder.svg b/test/output/musicRevenueCustomOrder.svg new file mode 100644 index 0000000000..817f343266 --- /dev/null +++ b/test/output/musicRevenueCustomOrder.svg @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 0 + 2 + 4 + 6 + 8 + 10 + 12 + 14 + 16 + 18 + 20 + 22 + + + ↑ Annual revenue (billions, adj.) + + + + + + + + + + + + + + 1975 + 1980 + 1985 + 1990 + 1995 + 2000 + 2005 + 2010 + 2015 + + + 8 - Track + Tape + CD + Disc + CD Single + Disc + Cassette + Tape + Cassette Single + Tape + DVD Audio + Other + Download Album + Download + Download Music Video + Download + Download Single + Download + Kiosk + Other + LP/EP + Vinyl + Limited Tier Paid Subscription + Streaming + Music Video (Physical) + Other + On-Demand Streaming (Ad-Supported) + Streaming + Other Ad-Supported Streaming + Streaming + Other Digital + Download + Other Tapes + Tape + Paid Subscription + Streaming + Ringtones & Ringbacks + Download + SACD + Disc + SoundExchange Distributions + Streaming + Synchronization + Other + Vinyl Single + Vinyl + + + + + \ No newline at end of file diff --git a/test/plots/music-revenue.ts b/test/plots/music-revenue.ts index 7bd9dc8230..81b424583d 100644 --- a/test/plots/music-revenue.ts +++ b/test/plots/music-revenue.ts @@ -23,3 +23,28 @@ export async function musicRevenue() { ] }); } + +export async function musicRevenueCustomOrder() { + const data = await d3.csv("data/riaa-us-revenue.csv", d3.autoType); + const stack: Plot.AreaYOptions = { + x: "year", + y: "revenue", + z: "format", + order: (a, b) => d3.descending(a.group, b.group) || d3.ascending(a.revenue, b.revenue), + reverse: true + }; + return Plot.plot({ + y: { + grid: true, + label: "Annual revenue (billions, adj.)", + transform: (d) => d / 1000 + }, + marks: [ + Plot.areaY( + data, + Plot.stackY({...stack, fill: "group", stroke: "white", title: (d) => `${d.format}\n${d.group}`}) + ), + Plot.ruleY([0]) + ] + }); +} From f4f17fcbe16c83529af7647c08dca4b9832f4e32 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Fri, 26 May 2023 15:57:17 -0700 Subject: [PATCH 2/6] stack descending shorthand --- src/transforms/stack.d.ts | 7 +- src/transforms/stack.js | 132 ++++++++++++++++++++---------------- test/plots/music-revenue.ts | 6 +- 3 files changed, 80 insertions(+), 65 deletions(-) diff --git a/src/transforms/stack.d.ts b/src/transforms/stack.d.ts index ab81a104b0..35ffcc3058 100644 --- a/src/transforms/stack.d.ts +++ b/src/transforms/stack.d.ts @@ -66,7 +66,12 @@ export type StackOrderName = "value" | "x" | "y" | "z" | "sum" | "appearance" | * - a comparator function for ordering data * - an array of explicit **z** values in the desired order */ -export type StackOrder = StackOrderName | (string & Record) | ((a: any, b: any) => any) | any[]; +export type StackOrder = + | StackOrderName + | `-${StackOrderName}` + | (string & Record) + | ((a: any, b: any) => any) + | any[]; /** Options for the stack transform. */ export interface StackOptions { diff --git a/src/transforms/stack.js b/src/transforms/stack.js index f5b5d6162f..adf7ff2a4d 100644 --- a/src/transforms/stack.js +++ b/src/transforms/stack.js @@ -1,5 +1,5 @@ import {InternMap, cumsum, greatest, group, groupSort, max, min, rollup, sum} from "d3"; -import {ascendingDefined} from "../defined.js"; +import {ascendingDefined, descendingDefined} from "../defined.js"; import {withTip} from "../mark.js"; import {maybeApplyInterval, maybeColumn, maybeZ, maybeZero} from "../options.js"; import {column, field, mid, one, range, valueof} from "../options.js"; @@ -81,7 +81,7 @@ function stack(x, y = one, kx, ky, {offset, order, reverse}, options) { const [Y2, setY2] = column(y); Y1.hint = Y2.hint = lengthy; offset = maybeOffset(offset); - order = maybeOrder(order, offset, ky); // TODO shorthand -order with reverse? + order = maybeOrder(order, offset, ky); return [ basic(options, (data, facets, plotOptions) => { const X = x == null ? undefined : setX(maybeApplyInterval(valueof(data, x), plotOptions?.[kx])); @@ -228,21 +228,23 @@ function offsetCenterFacets(facetstacks, Y1, Y2) { } function maybeOrder(order, offset, ky) { - if (order === undefined && offset === offsetWiggle) return orderInsideOut; + if (order === undefined && offset === offsetWiggle) return orderInsideOut(orderAscending); if (order == null) return; if (typeof order === "string") { - switch (order.toLowerCase()) { + const negate = order.startsWith("-"); + const direction = negate ? orderDescending : orderAscending; + switch ((negate ? order.slice(1) : order).toLowerCase()) { case "value": case ky: - return orderY; + return orderY(direction); case "z": - return orderZ; + return orderZ(direction); case "sum": - return orderSum; + return orderSum(direction); case "appearance": - return orderAppearance; + return orderAppearance(direction); case "inside-out": - return orderInsideOut; + return orderInsideOut(direction); } return orderAccessor(field(order)); } @@ -252,74 +254,84 @@ function maybeOrder(order, offset, ky) { } // by value -function orderY(data, X, Y) { - return orderNatural(Y); +function orderY(order) { + return (data, X, Y) => order(Y); } // by location -function orderZ(order, X, Y, Z) { - return orderNatural(Z); +function orderZ(order) { + return (data, X, Y, Z) => order(Z); } // by sum of value (a.k.a. “ascending”) -function orderSum(data, X, Y, Z) { - return orderZDomain( - Z, - groupSort( - range(data), - (I) => sum(I, (i) => Y[i]), - (i) => Z[i] - ) - ); +function orderSum(order) { + return (data, X, Y, Z) => + orderZDomain( + order, + Z, + groupSort( + range(data), + (I) => sum(I, (i) => Y[i]), + (i) => Z[i] + ) + ); } // by x = argmax of value -function orderAppearance(data, X, Y, Z) { - return orderZDomain( - Z, - groupSort( - range(data), - (I) => X[greatest(I, (i) => Y[i])], - (i) => Z[i] - ) - ); +function orderAppearance(order) { + return (data, X, Y, Z) => + orderZDomain( + order, + Z, + groupSort( + range(data), + (I) => X[greatest(I, (i) => Y[i])], + (i) => Z[i] + ) + ); } // by x = argmax of value, but rearranged inside-out by alternating series // according to the sign of a running divergence of sums -function orderInsideOut(data, X, Y, Z) { - const I = range(data); - const K = groupSort( - I, - (I) => X[greatest(I, (i) => Y[i])], - (i) => Z[i] - ); - const sums = rollup( - I, - (I) => sum(I, (i) => Y[i]), - (i) => Z[i] - ); - const Kp = [], - Kn = []; - let s = 0; - for (const k of K) { - if (s < 0) { - s += sums.get(k); - Kp.push(k); - } else { - s -= sums.get(k); - Kn.push(k); +function orderInsideOut(order) { + return (data, X, Y, Z) => { + const I = range(data); + const K = groupSort( + I, + (I) => X[greatest(I, (i) => Y[i])], + (i) => Z[i] + ); + const sums = rollup( + I, + (I) => sum(I, (i) => Y[i]), + (i) => Z[i] + ); + const Kp = [], + Kn = []; + let s = 0; + for (const k of K) { + if (s < 0) { + s += sums.get(k); + Kp.push(k); + } else { + s -= sums.get(k); + Kn.push(k); + } } - } - return orderZDomain(Z, Kn.reverse().concat(Kp)); + return orderZDomain(order, Z, Kn.reverse().concat(Kp)); + }; } -function orderNatural(O) { +function orderAscending(O) { return (i, j) => ascendingDefined(O[i], O[j]); } +function orderDescending(O) { + return (i, j) => descendingDefined(O[i], O[j]); +} + function orderAccessor(f) { - return (data) => orderNatural(valueof(data, f)); + return (data) => orderAscending(valueof(data, f)); } function orderComparator(f) { @@ -327,16 +339,16 @@ function orderComparator(f) { } function orderGiven(domain) { - return (data, X, Y, Z) => orderZDomain(Z, domain); + return (data, X, Y, Z) => orderZDomain(orderAscending, Z, domain); } // Given an explicit ordering of distinct values in z, returns a parallel column // O that can be used with applyOrder to sort stacks. Note that this is a series // order: it will be consistent across stacks. -function orderZDomain(Z, domain) { +function orderZDomain(order, Z, domain) { if (!Z) throw new Error("missing channel: z"); domain = new InternMap(domain.map((d, i) => [d, i])); - return orderNatural(Z.map((z) => domain.get(z))); + return order(Z.map((z) => domain.get(z))); } function applyOrder(stacks, compare) { diff --git a/test/plots/music-revenue.ts b/test/plots/music-revenue.ts index 81b424583d..77f6d38d2f 100644 --- a/test/plots/music-revenue.ts +++ b/test/plots/music-revenue.ts @@ -7,8 +7,7 @@ export async function musicRevenue() { x: "year", y: "revenue", z: "format", - order: "appearance", - reverse: true + order: "-appearance" }; return Plot.plot({ y: { @@ -30,8 +29,7 @@ export async function musicRevenueCustomOrder() { x: "year", y: "revenue", z: "format", - order: (a, b) => d3.descending(a.group, b.group) || d3.ascending(a.revenue, b.revenue), - reverse: true + order: (a, b) => d3.ascending(a.group, b.group) || d3.descending(a.revenue, b.revenue) }; return Plot.plot({ y: { From edcd07b24dd4acce2cc23ae4113b957739aad149 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Fri, 26 May 2023 16:02:41 -0700 Subject: [PATCH 3/6] transpose orderZDomain --- src/transforms/stack.js | 52 +++++++++++++++++++---------------------- 1 file changed, 24 insertions(+), 28 deletions(-) diff --git a/src/transforms/stack.js b/src/transforms/stack.js index adf7ff2a4d..f10d44cced 100644 --- a/src/transforms/stack.js +++ b/src/transforms/stack.js @@ -265,36 +265,30 @@ function orderZ(order) { // by sum of value (a.k.a. “ascending”) function orderSum(order) { - return (data, X, Y, Z) => - orderZDomain( - order, - Z, - groupSort( - range(data), - (I) => sum(I, (i) => Y[i]), - (i) => Z[i] - ) - ); + return orderZDomain(order, (data, X, Y, Z) => + groupSort( + range(data), + (I) => sum(I, (i) => Y[i]), + (i) => Z[i] + ) + ); } // by x = argmax of value function orderAppearance(order) { - return (data, X, Y, Z) => - orderZDomain( - order, - Z, - groupSort( - range(data), - (I) => X[greatest(I, (i) => Y[i])], - (i) => Z[i] - ) - ); + return orderZDomain(order, (data, X, Y, Z) => + groupSort( + range(data), + (I) => X[greatest(I, (i) => Y[i])], + (i) => Z[i] + ) + ); } // by x = argmax of value, but rearranged inside-out by alternating series // according to the sign of a running divergence of sums function orderInsideOut(order) { - return (data, X, Y, Z) => { + return orderZDomain(order, (data, X, Y, Z) => { const I = range(data); const K = groupSort( I, @@ -318,8 +312,8 @@ function orderInsideOut(order) { Kn.push(k); } } - return orderZDomain(order, Z, Kn.reverse().concat(Kp)); - }; + return Kn.reverse().concat(Kp); + }); } function orderAscending(O) { @@ -339,16 +333,18 @@ function orderComparator(f) { } function orderGiven(domain) { - return (data, X, Y, Z) => orderZDomain(orderAscending, Z, domain); + return orderZDomain(orderAscending, () => domain); } // Given an explicit ordering of distinct values in z, returns a parallel column // O that can be used with applyOrder to sort stacks. Note that this is a series // order: it will be consistent across stacks. -function orderZDomain(order, Z, domain) { - if (!Z) throw new Error("missing channel: z"); - domain = new InternMap(domain.map((d, i) => [d, i])); - return order(Z.map((z) => domain.get(z))); +function orderZDomain(order, domain) { + return (data, X, Y, Z) => { + if (!Z) throw new Error("missing channel: z"); + const map = new InternMap(domain(data, X, Y, Z).map((d, i) => [d, i])); + return order(Z.map((z) => map.get(z))); + }; } function applyOrder(stacks, compare) { From e01e202e75acc117d38904d50cf68f6b240d19d6 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Fri, 26 May 2023 16:13:40 -0700 Subject: [PATCH 4/6] more inlining --- src/transforms/stack.js | 67 +++++++++++++++++------------------------ 1 file changed, 28 insertions(+), 39 deletions(-) diff --git a/src/transforms/stack.js b/src/transforms/stack.js index f10d44cced..7c92fc0d82 100644 --- a/src/transforms/stack.js +++ b/src/transforms/stack.js @@ -94,7 +94,7 @@ function stack(x, y = one, kx, ky, {offset, order, reverse}, options) { const facetstacks = []; for (const facet of facets) { const stacks = X ? Array.from(group(facet, (i) => X[i]).values()) : [facet]; - if (compare) applyOrder(stacks, compare); + if (compare) for (const stack of stacks) stack.sort(compare); for (const stack of stacks) { let yn = 0; let yp = 0; @@ -228,23 +228,23 @@ function offsetCenterFacets(facetstacks, Y1, Y2) { } function maybeOrder(order, offset, ky) { - if (order === undefined && offset === offsetWiggle) return orderInsideOut(orderAscending); + if (order === undefined && offset === offsetWiggle) return orderInsideOut(ascendingDefined); if (order == null) return; if (typeof order === "string") { const negate = order.startsWith("-"); - const direction = negate ? orderDescending : orderAscending; + const compare = negate ? descendingDefined : ascendingDefined; switch ((negate ? order.slice(1) : order).toLowerCase()) { case "value": case ky: - return orderY(direction); + return orderY(compare); case "z": - return orderZ(direction); + return orderZ(compare); case "sum": - return orderSum(direction); + return orderSum(compare); case "appearance": - return orderAppearance(direction); + return orderAppearance(compare); case "inside-out": - return orderInsideOut(direction); + return orderInsideOut(compare); } return orderAccessor(field(order)); } @@ -254,18 +254,18 @@ function maybeOrder(order, offset, ky) { } // by value -function orderY(order) { - return (data, X, Y) => order(Y); +function orderY(compare) { + return (data, X, Y) => (i, j) => compare(Y[i], Y[j]); } // by location -function orderZ(order) { - return (data, X, Y, Z) => order(Z); +function orderZ(compare) { + return (data, X, Y, Z) => (i, j) => compare(Z[i], Z[j]); } // by sum of value (a.k.a. “ascending”) -function orderSum(order) { - return orderZDomain(order, (data, X, Y, Z) => +function orderSum(compare) { + return orderZDomain(compare, (data, X, Y, Z) => groupSort( range(data), (I) => sum(I, (i) => Y[i]), @@ -275,8 +275,8 @@ function orderSum(order) { } // by x = argmax of value -function orderAppearance(order) { - return orderZDomain(order, (data, X, Y, Z) => +function orderAppearance(compare) { + return orderZDomain(compare, (data, X, Y, Z) => groupSort( range(data), (I) => X[greatest(I, (i) => Y[i])], @@ -287,8 +287,8 @@ function orderAppearance(order) { // by x = argmax of value, but rearranged inside-out by alternating series // according to the sign of a running divergence of sums -function orderInsideOut(order) { - return orderZDomain(order, (data, X, Y, Z) => { +function orderInsideOut(compare) { + return orderZDomain(compare, (data, X, Y, Z) => { const I = range(data); const K = groupSort( I, @@ -316,16 +316,11 @@ function orderInsideOut(order) { }); } -function orderAscending(O) { - return (i, j) => ascendingDefined(O[i], O[j]); -} - -function orderDescending(O) { - return (i, j) => descendingDefined(O[i], O[j]); -} - function orderAccessor(f) { - return (data) => orderAscending(valueof(data, f)); + return (data) => { + const O = valueof(data, f); + return (i, j) => ascendingDefined(O[i], O[j]); + }; } function orderComparator(f) { @@ -333,22 +328,16 @@ function orderComparator(f) { } function orderGiven(domain) { - return orderZDomain(orderAscending, () => domain); + return orderZDomain(ascendingDefined, () => domain); } -// Given an explicit ordering of distinct values in z, returns a parallel column -// O that can be used with applyOrder to sort stacks. Note that this is a series -// order: it will be consistent across stacks. -function orderZDomain(order, domain) { +// Given an ordering (domain) of distinct values in z that can be derived from +// the data, returns a comparator that can be used to sort stacks. Note that +// this is a series order: it will be consistent across stacks. +function orderZDomain(compare, domain) { return (data, X, Y, Z) => { if (!Z) throw new Error("missing channel: z"); const map = new InternMap(domain(data, X, Y, Z).map((d, i) => [d, i])); - return order(Z.map((z) => map.get(z))); + return (i, j) => compare(map.get(Z[i]), map.get(Z[j])); }; } - -function applyOrder(stacks, compare) { - for (const stack of stacks) { - stack.sort(compare); - } -} From 30c3f54c9b0ede1120405286d3b407c68939f879 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Fri, 26 May 2023 16:15:16 -0700 Subject: [PATCH 5/6] inline options --- test/plots/music-revenue.ts | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/test/plots/music-revenue.ts b/test/plots/music-revenue.ts index 77f6d38d2f..7c93ed48c8 100644 --- a/test/plots/music-revenue.ts +++ b/test/plots/music-revenue.ts @@ -2,7 +2,7 @@ import * as Plot from "@observablehq/plot"; import * as d3 from "d3"; export async function musicRevenue() { - const data = await d3.csv("data/riaa-us-revenue.csv", d3.autoType); + const riaa = await d3.csv("data/riaa-us-revenue.csv", d3.autoType); const stack: Plot.AreaYOptions = { x: "year", y: "revenue", @@ -16,21 +16,15 @@ export async function musicRevenue() { transform: (d) => d / 1000 }, marks: [ - Plot.areaY(data, Plot.stackY({...stack, fill: "group", title: (d) => `${d.format}\n${d.group}`})), - Plot.lineY(data, Plot.stackY2({...stack, stroke: "white", strokeWidth: 1})), + Plot.areaY(riaa, Plot.stackY({...stack, fill: "group", title: (d) => `${d.format}\n${d.group}`})), + Plot.lineY(riaa, Plot.stackY2({...stack, stroke: "white", strokeWidth: 1})), Plot.ruleY([0]) ] }); } export async function musicRevenueCustomOrder() { - const data = await d3.csv("data/riaa-us-revenue.csv", d3.autoType); - const stack: Plot.AreaYOptions = { - x: "year", - y: "revenue", - z: "format", - order: (a, b) => d3.ascending(a.group, b.group) || d3.descending(a.revenue, b.revenue) - }; + const riaa = await d3.csv("data/riaa-us-revenue.csv", d3.autoType); return Plot.plot({ y: { grid: true, @@ -39,8 +33,16 @@ export async function musicRevenueCustomOrder() { }, marks: [ Plot.areaY( - data, - Plot.stackY({...stack, fill: "group", stroke: "white", title: (d) => `${d.format}\n${d.group}`}) + riaa, + Plot.stackY({ + x: "year", + y: "revenue", + z: "format", + order: (a, b) => d3.ascending(a.group, b.group) || d3.descending(a.revenue, b.revenue), + fill: "group", + stroke: "white", + title: (d) => `${d.format}\n${d.group}` + }) ), Plot.ruleY([0]) ] From 6efe4b89e049059a400b6b1c530e61ae33e96225 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Sat, 27 May 2023 10:17:09 -0700 Subject: [PATCH 6/6] better comparator type --- src/transforms/stack.d.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/transforms/stack.d.ts b/src/transforms/stack.d.ts index 35ffcc3058..5d6ba75a47 100644 --- a/src/transforms/stack.d.ts +++ b/src/transforms/stack.d.ts @@ -1,5 +1,5 @@ import type {ChannelValue} from "../channel.js"; -import type {Transformed} from "./basic.js"; +import type {CompareFunction, Transformed} from "./basic.js"; /** * A built-in stack offset method; one of: @@ -62,7 +62,7 @@ export type StackOrderName = "value" | "x" | "y" | "z" | "sum" | "appearance" | * * - a named stack order method such as *inside-out* or *sum* * - a field name, for natural order of the corresponding values - * - a function of data, for natural order of the corresponding values + * - an accessor function, for natural order of the corresponding values * - a comparator function for ordering data * - an array of explicit **z** values in the desired order */ @@ -70,7 +70,8 @@ export type StackOrder = | StackOrderName | `-${StackOrderName}` | (string & Record) - | ((a: any, b: any) => any) + | ((d: any) => any) // accessor + | CompareFunction | any[]; /** Options for the stack transform. */