diff --git a/src/options.js b/src/options.js
index f70a118ef8..3fdcbd8bff 100644
--- a/src/options.js
+++ b/src/options.js
@@ -7,6 +7,10 @@ import {maybeTimeInterval, maybeUtcInterval} from "./time.js";
export const TypedArray = Object.getPrototypeOf(Uint8Array);
const objectToString = Object.prototype.toString;
+// If a reindex is attached to the data, channel values expressed as arrays will
+// be reindexed when the channels are instantiated. See exclusiveFacets.
+export const reindex = Symbol("reindex");
+
export function valueof(data, value, type) {
const valueType = typeof value;
return valueType === "string"
@@ -17,7 +21,11 @@ export function valueof(data, value, type) {
? map(data, constant(value), type)
: typeof value?.transform === "function"
? maybeTypedArrayify(value.transform(data), type)
- : maybeTypedArrayify(value, type);
+ : maybeTake(maybeTypedArrayify(value, type), data?.[reindex]);
+}
+
+function maybeTake(values, index) {
+ return index ? take(values, index) : values;
}
function maybeTypedMap(data, f, type) {
@@ -170,6 +178,7 @@ export function isScaleOptions(option) {
// Disambiguates an options object (e.g., {y: "x2"}) from a channel value
// definition expressed as a channel transform (e.g., {transform: …}).
+// TODO Check typeof option[Symbol.iterator] !== "function"?
export function isOptions(option) {
return isObject(option) && typeof option.transform !== "function";
}
@@ -223,7 +232,7 @@ export function where(data, test) {
// Returns an array [values[index[0]], values[index[1]], …].
export function take(values, index) {
- return map(index, (i) => values[i]);
+ return map(index, (i) => values[i], values.constructor);
}
// If f does not take exactly one argument, wraps it in a function that uses take.
diff --git a/src/transforms/exclusiveFacets.js b/src/transforms/exclusiveFacets.js
index a96c69ac8b..facf94bfb8 100644
--- a/src/transforms/exclusiveFacets.js
+++ b/src/transforms/exclusiveFacets.js
@@ -1,7 +1,5 @@
-import {slice} from "../options.js";
+import {reindex, slice} from "../options.js";
-// TODO How to reindex channels supplied as arrays? I don’t want to inspect
-// arbitrary values on the options; maybe we could use this.channels?
export function exclusiveFacets(data, facets) {
if (facets.length === 1) return {data, facets}; // only one facet; trivially exclusive
@@ -22,15 +20,18 @@ export function exclusiveFacets(data, facets) {
// For each overlapping index (duplicate), assign a new unique index at the
// end of the existing array, duplicating the datum. For example, [[0, 1, 2],
- // [2, 1, 3]] would become [[0, 1, 2], [4, 5, 3]].
+ // [2, 1, 3]] would become [[0, 1, 2], [4, 5, 3]]. Also attach a reindex to
+ // the data to preserve the association of channel values specified as arrays.
data = slice(data);
+ const R = (data[reindex] = new Uint32Array(n + overlaps));
facets = facets.map((facet) => slice(facet, Uint32Array));
let j = n;
O.fill(0);
for (const facet of facets) {
for (let k = 0, m = facet.length; k < m; ++k) {
const i = facet[k];
- if (O[i]) (facet[k] = j), (data[j] = data[i]), ++j;
+ if (O[i]) (facet[k] = j), (data[j] = data[i]), (R[j] = i), ++j;
+ else R[i] = i;
O[i] = 1;
}
}
diff --git a/test/output/facetReindex.svg b/test/output/facetReindex.svg
new file mode 100644
index 0000000000..db90254da3
--- /dev/null
+++ b/test/output/facetReindex.svg
@@ -0,0 +1,1136 @@
+
\ No newline at end of file
diff --git a/test/plots/facet-reindex.ts b/test/plots/facet-reindex.ts
new file mode 100644
index 0000000000..1d77563861
--- /dev/null
+++ b/test/plots/facet-reindex.ts
@@ -0,0 +1,34 @@
+import * as Plot from "@observablehq/plot";
+import * as d3 from "d3";
+
+export async function facetReindex() {
+ const penguins = await d3.csv("data/penguins.csv", d3.autoType);
+ const island = Plot.valueof(penguins, "island");
+ return Plot.plot({
+ width: 830,
+ marginLeft: 74,
+ marginRight: 68,
+ height: 130,
+ x: {domain: [0, penguins.length], round: true},
+ y: {label: "facet option", axis: "right"},
+ facet: {data: penguins, y: island},
+ fy: {label: "facet value"},
+ marks: [
+ Plot.barX(penguins, {
+ facet: "exclude",
+ fill: island, // array channel to be reindexed
+ x: 1,
+ y: () => "exclude",
+ fillOpacity: 0.5,
+ insetRight: 0.5
+ }),
+ Plot.barX(penguins, {
+ facet: "include",
+ fill: island,
+ x: 1,
+ y: () => "include"
+ }),
+ Plot.frame()
+ ]
+ });
+}
diff --git a/test/plots/index.ts b/test/plots/index.ts
index 65c6cfb5c6..82b3c2e88a 100644
--- a/test/plots/index.ts
+++ b/test/plots/index.ts
@@ -80,6 +80,7 @@ export * from "./empty-legend.js";
export * from "./empty-x.js";
export * from "./empty.js";
export * from "./energy-production.js";
+export * from "./facet-reindex.js";
export * from "./faithful-density-1d.js";
export * from "./faithful-density.js";
export * from "./federal-funds.js";