Skip to content

Commit 2060947

Browse files
committed
filter, sort, and reverse transforms
1 parent 7e45462 commit 2060947

File tree

12 files changed

+100
-68
lines changed

12 files changed

+100
-68
lines changed

src/index.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ export {Rect, rect, rectX, rectY} from "./marks/rect.js";
1111
export {RuleX, RuleY, ruleX, ruleY} from "./marks/rule.js";
1212
export {Text, text, textX, textY} from "./marks/text.js";
1313
export {TickX, TickY, tickX, tickY} from "./marks/tick.js";
14+
export {filter} from "./transforms/filter.js";
15+
export {reverse} from "./transforms/reverse.js";
16+
export {sort} from "./transforms/sort.js";
1417
export {bin, binX, binY} from "./transforms/bin.js";
1518
export {group, groupX, groupY, groupZ} from "./transforms/group.js";
1619
export {normalizeX, normalizeY} from "./transforms/normalize.js";

src/mark.js

Lines changed: 3 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {color} from "d3";
2-
import {ascendingDefined, nonempty} from "./defined.js";
2+
import {nonempty} from "./defined.js";
33
import {plot} from "./plot.js";
4+
import {basic} from "./transforms/basic.js";
45

56
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray
67
const TypedArray = Object.getPrototypeOf(Uint8Array);
@@ -11,7 +12,7 @@ export class Mark {
1112
const names = new Set();
1213
this.data = data;
1314
this.facet = facet ? keyword(facet === true ? "include" : facet, "facet", ["auto", "include", "exclude"]) : null;
14-
const {transform} = maybeTransform(options);
15+
const {transform} = basic(options);
1516
this.transform = transform;
1617
this.channels = channels.filter(channel => {
1718
const {name, value, optional} = channel;
@@ -222,23 +223,6 @@ export function maybeLazyChannel(source) {
222223
return source == null ? [source] : lazyChannel(source);
223224
}
224225

225-
// If both t1 and t2 are defined, returns a composite transform that first
226-
// applies t1 and then applies t2.
227-
export function maybeTransform({
228-
filter: f1,
229-
sort: s1,
230-
reverse: r1,
231-
transform: t1,
232-
...options
233-
} = {}, t2) {
234-
if (t1 === undefined) {
235-
if (f1 != null) t1 = filter(f1);
236-
if (s1 != null) t1 = compose(t1, sort(s1));
237-
if (r1) t1 = compose(t1, reverse);
238-
}
239-
return {...options, transform: compose(t1, t2)};
240-
}
241-
242226
// Assuming that both x1 and x2 and lazy channels (per above), this derives a
243227
// new a channel that’s the average of the two, and which inherits the channel
244228
// label (if any). Both input channels are assumed to be quantitative. If either
@@ -263,45 +247,6 @@ export function maybeValue(value) {
263247
typeof value.transform !== "function") ? value : {value};
264248
}
265249

266-
function compose(t1, t2) {
267-
if (t1 == null) return t2 === null ? undefined : t2;
268-
if (t2 == null) return t1 === null ? undefined : t1;
269-
return (data, facets) => {
270-
({data, facets} = t1(data, facets));
271-
return t2(arrayify(data), facets);
272-
};
273-
}
274-
275-
function sort(value) {
276-
return (typeof value === "function" && value.length !== 1 ? sortCompare : sortValue)(value);
277-
}
278-
279-
function sortCompare(compare) {
280-
return (data, facets) => {
281-
const compareData = (i, j) => compare(data[i], data[j]);
282-
return {data, facets: facets.map(I => I.slice().sort(compareData))};
283-
};
284-
}
285-
286-
function sortValue(value) {
287-
return (data, facets) => {
288-
const V = valueof(data, value);
289-
const compareValue = (i, j) => ascendingDefined(V[i], V[j]);
290-
return {data, facets: facets.map(I => I.slice().sort(compareValue))};
291-
};
292-
}
293-
294-
function filter(value) {
295-
return (data, facets) => {
296-
const V = valueof(data, value);
297-
return {data, facets: facets.map(I => I.filter(i => V[i]))};
298-
};
299-
}
300-
301-
function reverse(data, facets) {
302-
return {data, facets: facets.map(I => I.slice().reverse())};
303-
}
304-
305250
export function numberChannel(source) {
306251
return {
307252
transform: data => valueof(data, source, Float64Array),

src/transforms/basic.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import {composeTransform} from "./compose.js";
2+
import {filterTransform} from "./filter.js";
3+
import {reverseTransform} from "./reverse.js";
4+
import {sortTransform} from "./sort.js";
5+
6+
// If both t1 and t2 are defined, returns a composite transform that first
7+
// applies t1 and then applies t2.
8+
export function basic({
9+
filter: f1,
10+
sort: s1,
11+
reverse: r1,
12+
transform: t1,
13+
...options
14+
} = {}, t2) {
15+
if (t1 === undefined) { // explicit transform overrides filter, sort, and reverse
16+
if (f1 != null) t1 = filterTransform(f1);
17+
if (s1 != null) t1 = composeTransform(t1, sortTransform(s1));
18+
if (r1) t1 = composeTransform(t1, reverseTransform);
19+
}
20+
return {...options, transform: composeTransform(t1, t2)};
21+
}

src/transforms/bin.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {bin as binner, extent, thresholdFreedmanDiaconis, thresholdScott, thresholdSturges, utcTickInterval} from "d3";
2-
import {valueof, range, identity, maybeLazyChannel, maybeTransform, maybeTuple, maybeColor, maybeValue, mid, labelof, isTemporal} from "../mark.js";
2+
import {valueof, range, identity, maybeLazyChannel, maybeTuple, maybeColor, maybeValue, mid, labelof, isTemporal} from "../mark.js";
33
import {offset} from "../style.js";
4+
import {basic} from "./basic.js";
45
import {maybeGroup, maybeOutputs, maybeReduce, maybeSubgroup, reduceIdentity} from "./group.js";
56

67
// Group on {z, fill, stroke}, then optionally on y, then bin x.
@@ -69,7 +70,7 @@ function binn(
6970
..."z" in inputs && {z: GZ || z},
7071
..."fill" in inputs && {fill: GF || fill},
7172
..."stroke" in inputs && {stroke: GS || stroke},
72-
...maybeTransform(options, (data, facets) => {
73+
...basic(options, (data, facets) => {
7374
const K = valueof(data, k);
7475
const Z = valueof(data, z);
7576
const F = valueof(data, vfill);

src/transforms/compose.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import {arrayify} from "../mark.js";
2+
3+
export function composeTransform(t1, t2) {
4+
if (t1 == null) return t2 === null ? undefined : t2;
5+
if (t2 == null) return t1 === null ? undefined : t1;
6+
return (data, facets) => {
7+
({data, facets} = t1(data, facets));
8+
return t2(arrayify(data), facets);
9+
};
10+
}

src/transforms/filter.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {valueof} from "../mark.js";
2+
import {basic} from "./basic.js";
3+
4+
export function filter(value, options) {
5+
return basic(options, filterTransform(value));
6+
}
7+
8+
export function filterTransform(value) {
9+
return (data, facets) => {
10+
const V = valueof(data, value);
11+
return {data, facets: facets.map(I => I.filter(i => V[i]))};
12+
};
13+
}

src/transforms/group.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {group as grouper, sort, sum, deviation, min, max, mean, median, mode, variance, InternSet} from "d3";
22
import {firstof} from "../defined.js";
3-
import {valueof, maybeColor, maybeInput, maybeTransform, maybeTuple, maybeLazyChannel, lazyChannel, first, identity, take, labelof, range} from "../mark.js";
3+
import {valueof, maybeColor, maybeInput, maybeTuple, maybeLazyChannel, lazyChannel, first, identity, take, labelof, range} from "../mark.js";
4+
import {basic} from "./basic.js";
45

56
// Group on {z, fill, stroke}.
67
export function groupZ(outputs, options) {
@@ -57,7 +58,7 @@ function groupn(
5758
..."z" in inputs && {z: GZ || z},
5859
..."fill" in inputs && {fill: GF || fill},
5960
..."stroke" in inputs && {stroke: GS || stroke},
60-
...maybeTransform(options, (data, facets) => {
61+
...basic(options, (data, facets) => {
6162
const X = valueof(data, x);
6263
const Y = valueof(data, y);
6364
const Z = valueof(data, z);

src/transforms/map.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {group} from "d3";
2-
import {maybeTransform, maybeZ, take, valueof, maybeInput, lazyChannel} from "../mark.js";
2+
import {maybeZ, take, valueof, maybeInput, lazyChannel} from "../mark.js";
3+
import {basic} from "./basic.js";
34

45
export function mapX(m, options = {}) {
56
return map(Object.fromEntries(["x", "x1", "x2"]
@@ -22,7 +23,7 @@ export function map(outputs = {}, options = {}) {
2223
return {key, input, output, setOutput, map: maybeMap(map)};
2324
});
2425
return {
25-
...maybeTransform(options, (data, facets) => {
26+
...basic(options, (data, facets) => {
2627
const Z = valueof(data, z);
2728
const X = channels.map(({input}) => valueof(data, input));
2829
const MX = channels.map(({setOutput}) => setOutput(new Array(data.length)));

src/transforms/reverse.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import {basic} from "./basic.js";
2+
3+
export function reverse(options) {
4+
return basic(options, reverseTransform);
5+
}
6+
7+
export function reverseTransform(data, facets) {
8+
return {data, facets: facets.map(I => I.slice().reverse())};
9+
}

src/transforms/select.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {greatest, group, least} from "d3";
2-
import {maybeTransform, maybeZ, valueof} from "../mark.js";
2+
import {maybeZ, valueof} from "../mark.js";
3+
import {basic} from "./basic.js";
34

45
export function selectFirst(options) {
56
return select(first, undefined, options);
@@ -53,7 +54,7 @@ function* max(I, X) {
5354

5455
function select(selectIndex, v, options) {
5556
const z = maybeZ(options);
56-
return maybeTransform(options, (data, facets) => {
57+
return basic(options, (data, facets) => {
5758
const Z = valueof(data, z);
5859
const V = valueof(data, v);
5960
const selectFacets = [];

src/transforms/sort.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import {ascendingDefined} from "../defined.js";
2+
import {valueof} from "../mark.js";
3+
import {basic} from "./basic.js";
4+
5+
export function sort(value, options) {
6+
return basic(options, sortTransform(value));
7+
}
8+
9+
export function sortTransform(value) {
10+
return (typeof value === "function" && value.length !== 1 ? sortCompare : sortValue)(value);
11+
}
12+
13+
function sortCompare(compare) {
14+
return (data, facets) => {
15+
const compareData = (i, j) => compare(data[i], data[j]);
16+
return {data, facets: facets.map(I => I.slice().sort(compareData))};
17+
};
18+
}
19+
20+
function sortValue(value) {
21+
return (data, facets) => {
22+
const V = valueof(data, value);
23+
const compareValue = (i, j) => ascendingDefined(V[i], V[j]);
24+
return {data, facets: facets.map(I => I.slice().sort(compareValue))};
25+
};
26+
}

src/transforms/stack.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {InternMap, cumsum, group, groupSort, greatest, rollup, sum, min} from "d3";
22
import {ascendingDefined} from "../defined.js";
3-
import {field, lazyChannel, maybeTransform, maybeLazyChannel, maybeZ, mid, range, valueof, identity, maybeZero} from "../mark.js";
3+
import {field, lazyChannel, maybeLazyChannel, maybeZ, mid, range, valueof, identity, maybeZero} from "../mark.js";
4+
import {basic} from "./basic.js";
45

56
export function stackX({y1, y = y1, x, ...options} = {}) {
67
const [transform, Y, x1, x2] = stack(y, x, "x", options);
@@ -58,7 +59,7 @@ function stack(x, y = () => 1, ky, {offset, order, reverse, ...options} = {}) {
5859
offset = maybeOffset(offset);
5960
order = maybeOrder(order, offset, ky);
6061
return [
61-
maybeTransform(options, (data, facets) => {
62+
basic(options, (data, facets) => {
6263
const X = x == null ? undefined : setX(valueof(data, x));
6364
const Y = valueof(data, y, Float64Array);
6465
const Z = valueof(data, z);

0 commit comments

Comments
 (0)