From ce3b378368f2f88c1509154ce947ca7abb845f70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Thu, 1 Jun 2023 09:19:38 +0200 Subject: [PATCH 1/5] reindex --- src/options.js | 2 ++ src/transforms/exclusiveFacets.js | 5 ++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/options.js b/src/options.js index f70a118ef8..7eb6c659e2 100644 --- a/src/options.js +++ b/src/options.js @@ -17,6 +17,8 @@ export function valueof(data, value, type) { ? map(data, constant(value), type) : typeof value?.transform === "function" ? maybeTypedArrayify(value.transform(data), type) + : value && data?.reindex + ? maybeTypedMap(data.reindex, (i) => value[i], type) : maybeTypedArrayify(value, type); } diff --git a/src/transforms/exclusiveFacets.js b/src/transforms/exclusiveFacets.js index a96c69ac8b..7c0443ea6f 100644 --- a/src/transforms/exclusiveFacets.js +++ b/src/transforms/exclusiveFacets.js @@ -24,13 +24,16 @@ export function exclusiveFacets(data, facets) { // end of the existing array, duplicating the datum. For example, [[0, 1, 2], // [2, 1, 3]] would become [[0, 1, 2], [4, 5, 3]]. data = slice(data); + // Attach a reindex map to the data, to interpret channels specified as arrays. + 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]), (data.reindex[j] = i), ++j; + else data.reindex[i] = i; O[i] = 1; } } From 6111cd7e19943c04a44a2ab17c4974aca35bada0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Thu, 1 Jun 2023 09:47:36 +0200 Subject: [PATCH 2/5] test --- test/output/facetReindex.svg | 1136 ++++++++++++++++++++++++++++++++++ test/plots/facet-reindex.ts | 34 + test/plots/index.ts | 1 + 3 files changed, 1171 insertions(+) create mode 100644 test/output/facetReindex.svg create mode 100644 test/plots/facet-reindex.ts 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 @@ + + + + + Biscoe + + + Dream + + + Torgersen + + + + facet value + + + + + + + + + + + + + + + + + + exclude + include + + + exclude + include + + + exclude + include + + + + facet option + + + + + + + + + + + + + + + 0 + 50 + 100 + 150 + 200 + 250 + 300 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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"; From daeb7d40f2ca98fc2868266bd59f00338c1a5f23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philippe=20Rivi=C3=A8re?= Date: Thu, 1 Jun 2023 09:48:18 +0200 Subject: [PATCH 3/5] done --- src/transforms/exclusiveFacets.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/transforms/exclusiveFacets.js b/src/transforms/exclusiveFacets.js index 7c0443ea6f..f186830dfb 100644 --- a/src/transforms/exclusiveFacets.js +++ b/src/transforms/exclusiveFacets.js @@ -1,7 +1,5 @@ import {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 From c7f9f69703f6127c85acb89b6c89716849db955b Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Fri, 18 Aug 2023 13:51:57 -0700 Subject: [PATCH 4/5] reindex iterables --- src/options.js | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/options.js b/src/options.js index 7eb6c659e2..e52732fde5 100644 --- a/src/options.js +++ b/src/options.js @@ -17,9 +17,11 @@ export function valueof(data, value, type) { ? map(data, constant(value), type) : typeof value?.transform === "function" ? maybeTypedArrayify(value.transform(data), type) - : value && data?.reindex - ? maybeTypedMap(data.reindex, (i) => value[i], 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) { @@ -172,6 +174,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"; } @@ -225,7 +228,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. From ad8600b617225de251dfc1eefe69cc002c4c23e6 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Fri, 18 Aug 2023 13:58:04 -0700 Subject: [PATCH 5/5] reindex symbol --- src/options.js | 6 +++++- src/transforms/exclusiveFacets.js | 12 ++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/src/options.js b/src/options.js index e52732fde5..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,7 @@ export function valueof(data, value, type) { ? map(data, constant(value), type) : typeof value?.transform === "function" ? maybeTypedArrayify(value.transform(data), type) - : maybeTake(maybeTypedArrayify(value, type), data?.reindex); + : maybeTake(maybeTypedArrayify(value, type), data?.[reindex]); } function maybeTake(values, index) { diff --git a/src/transforms/exclusiveFacets.js b/src/transforms/exclusiveFacets.js index f186830dfb..facf94bfb8 100644 --- a/src/transforms/exclusiveFacets.js +++ b/src/transforms/exclusiveFacets.js @@ -1,4 +1,4 @@ -import {slice} from "../options.js"; +import {reindex, slice} from "../options.js"; export function exclusiveFacets(data, facets) { if (facets.length === 1) return {data, facets}; // only one facet; trivially exclusive @@ -20,18 +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); - // Attach a reindex map to the data, to interpret channels specified as arrays. - data.reindex = new Uint32Array(n + overlaps); + 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]), (data.reindex[j] = i), ++j; - else data.reindex[i] = i; + if (O[i]) (facet[k] = j), (data[j] = data[i]), (R[j] = i), ++j; + else R[i] = i; O[i] = 1; } }