Skip to content

Commit a65c227

Browse files
committed
add a definition to the {transform} representation of a value; a mark can read that definition and decide that it wants to run the transform, or just use the definition. This allows the mark to decide, for example, if a color is a channel or a constant color. If a channel, then reindexation is applied lazily.
1 parent fc37827 commit a65c227

File tree

7 files changed

+68
-49
lines changed

7 files changed

+68
-49
lines changed

src/facet.js

Lines changed: 38 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
import {column, maybeColorChannel, maybeNumberChannel, slice, valueof} from "./options.js";
2-
import {maybeSymbolChannel} from "./symbols.js";
3-
import {maybeFontSizeChannel} from "./marks/text.js";
4-
import {maybePathChannel} from "./marks/image.js";
1+
import {column, slice, valueof} from "./options.js";
52

63
export function facetReindex(facets, n) {
74
if (facets.length === 1) return {facets};
@@ -54,44 +51,46 @@ export function maybeExpand(X, plan) {
5451

5552
// Iterate over the options and pull out any that represent columns of values.
5653
const knownChannels = [
57-
["x"],
58-
["x1"],
59-
["x2"],
60-
["y"],
61-
["y1"],
62-
["y2"],
63-
["z"],
64-
["ariaLabel"],
65-
["href"],
66-
["title"],
67-
["fill", (value) => maybeColorChannel(value)[0]],
68-
["stroke", (value) => maybeColorChannel(value)[0]],
69-
["fillOpacity", (value) => maybeNumberChannel(value)[0]],
70-
["strokeOpacity", (value) => maybeNumberChannel(value)[0]],
71-
["opacity", (value) => maybeNumberChannel(value)[0]],
72-
["strokeWidth", (value) => maybeNumberChannel(value)[0]],
73-
["symbol", (value) => maybeSymbolChannel(value)[0]], // dot
74-
["r", (value) => maybeNumberChannel(value)[0]], // dot
75-
["rotate", (value) => maybeNumberChannel(value)[0]], // dot, text
76-
["fontSize", (value) => maybeFontSizeChannel(value)[0]], // text
77-
["text"], // text
78-
["length", (value) => maybeNumberChannel(value)[0]], // vector
79-
["width", (value) => maybeNumberChannel(value)[0]], // image
80-
["height", (value) => maybeNumberChannel(value)[0]], // image
81-
["src", (value) => maybePathChannel(value)[0]], // image
82-
["weight", (value) => maybeNumberChannel(value)[0]] // density
54+
"x",
55+
"x1",
56+
"x2",
57+
"y",
58+
"y1",
59+
"y2",
60+
"z",
61+
"ariaLabel",
62+
"href",
63+
"title",
64+
"fill",
65+
"stroke",
66+
"fillOpacity",
67+
"strokeOpacity",
68+
"opacity",
69+
"strokeWidth",
70+
"symbol", // dot
71+
"r", // dot
72+
"rotate", // dot, text
73+
"fontSize", // text
74+
"text", // text
75+
"length", // vector
76+
"width", // image
77+
"height", // image
78+
"src", // image
79+
"weight" // density
8380
];
8481

85-
export function maybeExpandOutputs(options) {
86-
const other = {};
87-
const outputs = [];
88-
for (const [name, test = (value) => value] of knownChannels) {
89-
const value = test(options[name]);
82+
export function maybeExpandChannels(options) {
83+
const channels = {};
84+
const [{transform: plan}, setPlan] = column();
85+
for (const name of knownChannels) {
86+
let value = options[name];
9087
if (value != null) {
91-
const [V, setV] = column(value);
92-
other[name] = V;
93-
outputs.push((data, plan) => setV(maybeExpand(valueof(data, value), plan)));
88+
if (value.definition) continue; // already planned
89+
channels[name] = {
90+
definition: value,
91+
transform: (data) => maybeExpand(valueof(data, value), plan())
92+
};
9493
}
9594
}
96-
return [other, outputs];
95+
return [channels, setPlan];
9796
}

src/marks/image.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,13 @@ function isUrl(string) {
3434

3535
// Disambiguates a constant src definition from a channel. A path or URL string
3636
// is assumed to be a constant; any other string is assumed to be a field name.
37-
export function maybePathChannel(value) {
38-
return typeof value === "string" && (isPath(value) || isUrl(value)) ? [undefined, value] : [value, undefined];
37+
function maybePathChannel(value) {
38+
if (typeof value === "string" && (isPath(value) || isUrl(value))) return [undefined, value];
39+
if (value && value.definition) {
40+
const [, f] = maybePathChannel(value.definition);
41+
if (f !== undefined) return [undefined, f];
42+
}
43+
return [value, undefined];
3944
}
4045

4146
export class Image extends Mark {

src/marks/text.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,12 @@ const fontSizes = new Set([
241241
// - string <length>: e.g., "12px"
242242
// - string <percentage>: e.g., "80%"
243243
// Anything else is assumed to be a channel definition.
244-
export function maybeFontSizeChannel(fontSize) {
244+
function maybeFontSizeChannel(fontSize) {
245245
if (fontSize == null || typeof fontSize === "number") return [undefined, fontSize];
246+
if (fontSize && fontSize.definition) {
247+
const [, f] = maybeFontSizeChannel(fontSize.definition);
248+
if (f !== undefined) return [undefined, f];
249+
}
246250
if (typeof fontSize !== "string") return [fontSize, undefined];
247251
fontSize = fontSize.trim().toLowerCase();
248252
return fontSizes.has(fontSize) || /^[+-]?\d*\.?\d+(e[+-]?\d+)?(\w*|%)$/.test(fontSize)

src/options.js

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,15 @@ export function percentile(reduce) {
7171
// CSS color, use an accessor (d => d.red) instead.
7272
export function maybeColorChannel(value, defaultValue) {
7373
if (value === undefined) value = defaultValue;
74+
if (value && value.definition && isColor(value.definition)) return [undefined, value.definition];
7475
return value === null ? [undefined, "none"] : isColor(value) ? [undefined, value] : [value, undefined];
7576
}
7677

7778
// Similar to maybeColorChannel, this tests whether the given value is a number
7879
// indicating a constant, and otherwise assumes that it’s a channel value.
7980
export function maybeNumberChannel(value, defaultValue) {
8081
if (value === undefined) value = defaultValue;
82+
if (value && value.definition && typeof value.definition === "number") return [undefined, value.definition];
8183
return value === null || typeof value === "number" ? [undefined, value] : [value, undefined];
8284
}
8385

@@ -252,7 +254,15 @@ export function maybeColumn(source) {
252254
}
253255

254256
export function labelof(value, defaultValue) {
255-
return typeof value === "string" ? value : value && value.label !== undefined ? value.label : defaultValue;
257+
return typeof value === "string"
258+
? value
259+
: value
260+
? value.label !== undefined
261+
? value.label
262+
: typeof value.definition === "string"
263+
? value.definition
264+
: defaultValue
265+
: defaultValue;
256266
}
257267

258268
// Assuming that both x1 and x2 and lazy columns (per above), this derives a new

src/symbols.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export function maybeSymbol(symbol) {
5555

5656
export function maybeSymbolChannel(symbol) {
5757
if (symbol == null || isSymbolObject(symbol)) return [undefined, symbol];
58+
if (symbol && symbol.definition && isSymbolObject(symbol.definition)) return [undefined, symbol.definition];
5859
if (typeof symbol === "string") {
5960
const value = symbols.get(`${symbol}`.toLowerCase());
6061
if (value) return [undefined, value];

src/transforms/map.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {count, group, rank} from "d3";
22
import {maybeZ, take, valueof, maybeInput, column} from "../options.js";
33
import {basic} from "./basic.js";
4-
import {maybeExpand, facetReindex, maybeExpandOutputs} from "../facet.js";
4+
import {maybeExpand, facetReindex, maybeExpandChannels} from "../facet.js";
55

66
/**
77
* ```js
@@ -57,15 +57,15 @@ export function map(outputs = {}, options = {}) {
5757
const [output, setOutput] = column(input);
5858
return {key, input, output, setOutput, map: maybeMap(map)};
5959
});
60-
const [other, facetOutputs] = maybeExpandOutputs(options); // TODO wrap outputs in facetReindex
60+
const [other, setPlan] = maybeExpandChannels(options);
6161
return {
6262
...basic(options, (data, facets) => {
6363
let plan;
6464
({facets, plan} = facetReindex(facets, data.length)); // make facets exclusive
6565
const Z = maybeExpand(valueof(data, z), plan);
6666
const X = channels.map(({input}) => maybeExpand(valueof(data, input), plan));
6767
const MX = channels.map(({setOutput}) => setOutput(new Array(plan ? plan.length : data.length)));
68-
for (const o of facetOutputs) o(data, plan); // expand any extra channels
68+
setPlan(plan); // expand any extra channels
6969
for (const facet of facets) {
7070
for (const I of Z ? group(facet, (i) => Z[i]).values() : [facet]) {
7171
channels.forEach(({map}, i) => map.map(I, X[i], MX[i]));

src/transforms/stack.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import {InternMap, cumsum, group, groupSort, greatest, max, min, rollup, sum} from "d3";
22
import {ascendingDefined} from "../defined.js";
3-
import {maybeExpand, facetReindex, maybeExpandOutputs} from "../facet.js";
3+
import {maybeExpand, facetReindex, maybeExpandChannels} from "../facet.js";
44
import {field, column, mid, range, valueof, one} from "../options.js";
55
import {maybeColumn, maybeZ, maybeZero} from "../options.js";
66
import {basic} from "./basic.js";
@@ -147,7 +147,7 @@ function stack(x, y = one, ky, {offset, order, reverse}, options) {
147147
const [Y2, setY2] = column(y);
148148
offset = maybeOffset(offset);
149149
order = maybeOrder(order, offset, ky);
150-
const [other, outputs] = maybeExpandOutputs(options); // TODO wrap outputs in facetReindex
150+
const [other, setPlan] = maybeExpandChannels(options);
151151
return [
152152
basic(options, (data, facets) => {
153153
let plan;
@@ -159,8 +159,8 @@ function stack(x, y = one, ky, {offset, order, reverse}, options) {
159159
const Y = maybeExpand(YS, plan);
160160
const Z = maybeExpand(ZS, plan);
161161
const O = order && maybeExpand(order(data, XS, YS, ZS), plan);
162+
setPlan(plan); // expand any extra channels
162163
const n = plan ? plan.length : data.length;
163-
for (const o of outputs) o(data, plan); // expand any extra channels
164164
const Y1 = setY1(new Float64Array(n));
165165
const Y2 = setY2(new Float64Array(n));
166166
const facetstacks = [];

0 commit comments

Comments
 (0)