Skip to content

Commit 8b9016a

Browse files
mbostockFil
andauthored
exclusiveFacets (#1649)
* exclusiveFacets * pad the data with duplicates * reindex * test * done * reindex iterables * reindex symbol --------- Co-authored-by: Philippe Rivière <fil@rezo.net>
1 parent 6adee12 commit 8b9016a

File tree

6 files changed

+1224
-2
lines changed

6 files changed

+1224
-2
lines changed

src/options.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ import {maybeTimeInterval, maybeUtcInterval} from "./time.js";
77
export const TypedArray = Object.getPrototypeOf(Uint8Array);
88
const objectToString = Object.prototype.toString;
99

10+
// If a reindex is attached to the data, channel values expressed as arrays will
11+
// be reindexed when the channels are instantiated. See exclusiveFacets.
12+
export const reindex = Symbol("reindex");
13+
1014
export function valueof(data, value, type) {
1115
const valueType = typeof value;
1216
return valueType === "string"
@@ -17,7 +21,11 @@ export function valueof(data, value, type) {
1721
? map(data, constant(value), type)
1822
: typeof value?.transform === "function"
1923
? maybeTypedArrayify(value.transform(data), type)
20-
: maybeTypedArrayify(value, type);
24+
: maybeTake(maybeTypedArrayify(value, type), data?.[reindex]);
25+
}
26+
27+
function maybeTake(values, index) {
28+
return index ? take(values, index) : values;
2129
}
2230

2331
function maybeTypedMap(data, f, type) {
@@ -170,6 +178,7 @@ export function isScaleOptions(option) {
170178

171179
// Disambiguates an options object (e.g., {y: "x2"}) from a channel value
172180
// definition expressed as a channel transform (e.g., {transform: …}).
181+
// TODO Check typeof option[Symbol.iterator] !== "function"?
173182
export function isOptions(option) {
174183
return isObject(option) && typeof option.transform !== "function";
175184
}
@@ -223,7 +232,7 @@ export function where(data, test) {
223232

224233
// Returns an array [values[index[0]], values[index[1]], …].
225234
export function take(values, index) {
226-
return map(index, (i) => values[i]);
235+
return map(index, (i) => values[i], values.constructor);
227236
}
228237

229238
// If f does not take exactly one argument, wraps it in a function that uses take.

src/transforms/exclusiveFacets.js

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import {reindex, slice} from "../options.js";
2+
3+
export function exclusiveFacets(data, facets) {
4+
if (facets.length === 1) return {data, facets}; // only one facet; trivially exclusive
5+
6+
const n = data.length;
7+
const O = new Uint8Array(n);
8+
let overlaps = 0;
9+
10+
// Count the number of overlapping indexes across facets.
11+
for (const facet of facets) {
12+
for (const i of facet) {
13+
if (O[i]) ++overlaps;
14+
O[i] = 1;
15+
}
16+
}
17+
18+
// Do nothing if the facets are already exclusive.
19+
if (overlaps === 0) return {data, facets}; // facets are exclusive
20+
21+
// For each overlapping index (duplicate), assign a new unique index at the
22+
// end of the existing array, duplicating the datum. For example, [[0, 1, 2],
23+
// [2, 1, 3]] would become [[0, 1, 2], [4, 5, 3]]. Also attach a reindex to
24+
// the data to preserve the association of channel values specified as arrays.
25+
data = slice(data);
26+
const R = (data[reindex] = new Uint32Array(n + overlaps));
27+
facets = facets.map((facet) => slice(facet, Uint32Array));
28+
let j = n;
29+
O.fill(0);
30+
for (const facet of facets) {
31+
for (let k = 0, m = facet.length; k < m; ++k) {
32+
const i = facet[k];
33+
if (O[i]) (facet[k] = j), (data[j] = data[i]), (R[j] = i), ++j;
34+
else R[i] = i;
35+
O[i] = 1;
36+
}
37+
}
38+
39+
return {data, facets};
40+
}

src/transforms/stack.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {withTip} from "../mark.js";
44
import {maybeApplyInterval, maybeColumn, maybeZ, maybeZero} from "../options.js";
55
import {column, field, mid, one, range, valueof} from "../options.js";
66
import {basic} from "./basic.js";
7+
import {exclusiveFacets} from "./exclusiveFacets.js";
78

89
export function stackX(stackOptions = {}, options = {}) {
910
if (arguments.length === 1) [stackOptions, options] = mergeOptions(stackOptions);
@@ -85,6 +86,7 @@ function stack(x, y = one, kx, ky, {offset, order, reverse}, options) {
8586
order = maybeOrder(order, offset, ky);
8687
return [
8788
basic(options, (data, facets, plotOptions) => {
89+
({data, facets} = exclusiveFacets(data, facets));
8890
const X = x == null ? undefined : setX(maybeApplyInterval(valueof(data, x), plotOptions?.[kx]));
8991
const Y = valueof(data, y, Float64Array);
9092
const Z = valueof(data, z);

0 commit comments

Comments
 (0)