Skip to content

expose instantiated scales descriptors in the render API #1810

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Aug 16, 2023
7 changes: 4 additions & 3 deletions src/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,11 +141,11 @@ export function plot(options = {}) {

// Initalize the scales and dimensions.
const scaleDescriptors = createScales(addScaleChannels(channelsByScale, stateByMark, options), options);
const scales = createScaleFunctions(scaleDescriptors);
const dimensions = createDimensions(scaleDescriptors, marks, options);

autoScaleRange(scaleDescriptors, dimensions);

const scales = createScaleFunctions(scaleDescriptors);
const {fx, fy} = scales;
const subdimensions = fx || fy ? innerDimensions(scaleDescriptors, dimensions) : dimensions;
const superdimensions = fx || fy ? actualDimensions(scales, dimensions) : dimensions;
Expand Down Expand Up @@ -221,9 +221,10 @@ export function plot(options = {}) {
addScaleChannels(newChannelsByScale, stateByMark, options, (key) => newByScale.has(key));
addScaleChannels(channelsByScale, stateByMark, options, (key) => newByScale.has(key));
const newScaleDescriptors = inheritScaleLabels(createScales(newChannelsByScale, options), scaleDescriptors);
const newScales = createScaleFunctions(newScaleDescriptors);
const {scales: newExposedScales, ...newScales} = createScaleFunctions(newScaleDescriptors);
Object.assign(scaleDescriptors, newScaleDescriptors);
Object.assign(scales, newScales);
Object.assign(scales.scales, newExposedScales);
}

// Sort and filter the facets to match the fx and fy domains; this is needed
Expand Down Expand Up @@ -333,7 +334,7 @@ export function plot(options = {}) {
if (caption != null) figure.append(createFigcaption(document, caption));
}

figure.scale = exposeScales(scaleDescriptors);
figure.scale = exposeScales(scales.scales);
figure.legend = exposeLegends(scaleDescriptors, context, options);

const w = consumeWarnings();
Expand Down
4 changes: 2 additions & 2 deletions src/scales.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,9 @@ export type ScaleName = "x" | "y" | "fx" | "fy" | "r" | "color" | "opacity" | "s

/**
* The instantiated scales’ apply functions; passed to marks and initializers
* for rendering.
* for rendering. The scales property exposes all the scale definitions.
*/
export type ScaleFunctions = {[key in ScaleName]?: (value: any) => any};
export type ScaleFunctions = {[key in ScaleName]?: (value: any) => any} & {scales: {[key in ScaleName]?: Scale}};

/**
* The supported scale types. For quantitative data, one of:
Expand Down
33 changes: 19 additions & 14 deletions src/scales.js
Original file line number Diff line number Diff line change
Expand Up @@ -97,17 +97,21 @@ export function createScales(
return scales;
}

export function createScaleFunctions(scales) {
return Object.fromEntries(
Object.entries(scales)
.filter(([, {scale}]) => scale) // drop identity scales
.map(([name, {scale, type, interval, label}]) => {
scale.type = type; // for axis
if (interval != null) scale.interval = interval; // for axis
if (label != null) scale.label = label; // for axis
return [name, scale];
})
);
export function createScaleFunctions(descriptors) {
const scales = {};
const scaleFunctions = {scales};
for (const [key, desc] of Object.entries(descriptors)) {
const {scale, type, interval, label} = desc;
scales[key] = exposeScale(desc);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This involves a bunch of copying and these exposed scales are rarely used. I think we should investigate whether we can make this lazy using a getter (but probably a caching getter so that if you access the same scale multiple times it returns the same instance).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fil’s comment: We’ll need these exposed scales in the near future anyway when we adopt them internally for axes etc., and therefore it’s premature optimization (or even slower) to make these lazy.

if (scale) {
scaleFunctions[key] = scale; // drop identity scales
// TODO: pass these properties, which are needed for axes, in the descriptor.
scale.type = type;
if (interval != null) scale.interval = interval;
if (label != null) scale.label = label;
}
}
return scaleFunctions;
}

// Mutates scale.range!
Expand Down Expand Up @@ -513,17 +517,18 @@ export function scale(options = {}) {
return scale;
}

export function exposeScales(scaleDescriptors) {
export function exposeScales(scales) {
return (key) => {
if (!registry.has((key = `${key}`))) throw new Error(`unknown scale: ${key}`);
return key in scaleDescriptors ? exposeScale(scaleDescriptors[key]) : undefined;
return scales[key];
};
}

// Note: axis- and legend-related properties (such as label, ticks and
// tickFormat) are not included here as they do not affect the scale’s behavior.
function exposeScale({scale, type, domain, range, interpolate, interval, transform, percent, pivot}) {
if (type === "identity") return {type: "identity", apply: (d) => d, invert: (d) => d};
if (type === "identity")
return {type: "identity", apply: (d) => d, invert: (d) => d, ...(range !== undefined && {range})};
const unknown = scale.unknown ? scale.unknown() : undefined;
return {
type,
Expand Down
Loading