diff --git a/src/plot.js b/src/plot.js
index a659b2a924..45cdcfce2e 100644
--- a/src/plot.js
+++ b/src/plot.js
@@ -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;
@@ -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: newIntantiatedScales, ...newScales} = createScaleFunctions(newScaleDescriptors);
Object.assign(scaleDescriptors, newScaleDescriptors);
Object.assign(scales, newScales);
+ Object.assign(scales.scales, newIntantiatedScales);
}
// Sort and filter the facets to match the fx and fy domains; this is needed
@@ -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();
diff --git a/src/scales.d.ts b/src/scales.d.ts
index 3ed4d489c8..8dd27fc66a 100644
--- a/src/scales.d.ts
+++ b/src/scales.d.ts
@@ -161,9 +161,11 @@ 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:
diff --git a/src/scales.js b/src/scales.js
index 5b993c18e7..8a6ca78d4f 100644
--- a/src/scales.js
+++ b/src/scales.js
@@ -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);
+ 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!
@@ -513,10 +517,10 @@ 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];
};
}
diff --git a/test/output/renderInstantiatedScales.svg b/test/output/renderInstantiatedScales.svg
new file mode 100644
index 0000000000..ec7b3e066a
--- /dev/null
+++ b/test/output/renderInstantiatedScales.svg
@@ -0,0 +1,270 @@
+
\ No newline at end of file
diff --git a/test/plots/index.ts b/test/plots/index.ts
index 65c6cfb5c6..f50407b08f 100644
--- a/test/plots/index.ts
+++ b/test/plots/index.ts
@@ -240,6 +240,7 @@ export * from "./raster-vapor.js";
export * from "./raster-walmart.js";
export * from "./rect-band.js";
export * from "./reducer-scale-override.js";
+export * from "./render.js";
export * from "./seattle-precipitation-density.js";
export * from "./seattle-precipitation-rule.js";
export * from "./seattle-precipitation-sum.js";
diff --git a/test/plots/render.ts b/test/plots/render.ts
new file mode 100644
index 0000000000..2e173775ae
--- /dev/null
+++ b/test/plots/render.ts
@@ -0,0 +1,37 @@
+import * as Plot from "@observablehq/plot";
+import * as d3 from "d3";
+import * as htl from "htl";
+
+export async function renderInstantiatedScales() {
+ const penguins = await d3.csv("data/penguins.csv", d3.autoType);
+ return Plot.dot(
+ penguins,
+ Plot.hexbin(
+ {fill: "count", r: "count"},
+ {
+ stroke: "white",
+ x: "culmen_length_mm",
+ y: "culmen_depth_mm",
+ render(index, scales, values, dimensions, context, next) {
+ const w = (dimensions.width + dimensions.marginLeft - dimensions.marginRight) / 2;
+ const h = (dimensions.height + dimensions.marginTop - dimensions.marginBottom) / 2;
+ return htl.svg`
+ ${next(index, scales, values, dimensions, context)}
+ ${d3
+ .select(context.ownerSVGElement)
+ .append("g")
+ .call((g) =>
+ g
+ .selectAll()
+ .data(Object.entries(scales.scales))
+ .join("text")
+ .attr("x", w)
+ .attr("y", (d, i) => h + 16 * i)
+ .text(([key, scale]) => `${key}: ${JSON.stringify(scale)}`)
+ )
+ .node()}`;
+ }
+ }
+ )
+ ).plot({width: 700, color: {scheme: "Blues", range: [0, 0.2]}});
+}