Skip to content

pass the "exposed scales" to the render function #1265

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions src/axes.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import {format, utcFormat} from "d3";
import {formatIsoDate} from "./format.js";
import {constant, isTemporal, string} from "./options.js";
import {isOrdinalScale} from "./scales.js";

export function inferFontVariant(scale) {
return isOrdinalScale(scale) && scale.interval === undefined ? undefined : "tabular-nums";
}

// D3 doesn’t provide a tick format for ordinal scales; we want shorthand when
// an ordinal domain is numbers or dates, and we want null to mean the empty
Expand Down
2 changes: 1 addition & 1 deletion src/channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export function createChannels(channels, data) {
export function valueObject(channels, scales) {
const values = Object.fromEntries(
Object.entries(channels).map(([name, {scale: scaleName, value}]) => {
const scale = scaleName == null ? null : scales[scaleName];
const scale = scaleName == null ? null : scales[scaleName]?.apply;
return [name, scale == null ? value : map(value, scale)];
})
);
Expand Down
10 changes: 5 additions & 5 deletions src/facet.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ export function facetGroups(data, {fx, fy}) {
}

export function facetTranslate(fx, fy, {marginTop, marginLeft}) {
return fx && fy
? ({x, y}) => `translate(${fx(x) - marginLeft},${fy(y) - marginTop})`
: fx
? ({x}) => `translate(${fx(x) - marginLeft},0)`
: ({y}) => `translate(0,${fy(y) - marginTop})`;
return fx?.apply && fy?.apply
? ({x, y}) => `translate(${fx.apply(x) - marginLeft},${fy.apply(y) - marginTop})`
: fx?.apply
? ({x}) => `translate(${fx.apply(x) - marginLeft},0)`
: ({y}) => `translate(0,${fy.apply(y) - marginTop})`;
}

// Returns an index that for each facet lists all the elements present in other
Expand Down
2 changes: 1 addition & 1 deletion src/legends/ramp.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {quantize, interpolateNumber, piecewise, format, scaleBand, scaleLinear, axisBottom} from "d3";
import {inferFontVariant} from "../axes.js";
import {inferFontVariant} from "../marks/axis.js";
import {createContext, create} from "../context.js";
import {map} from "../options.js";
import {interpolatePiecewise} from "../scales/quantitative.js";
Expand Down
3 changes: 2 additions & 1 deletion src/legends/swatches.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {pathRound as path} from "d3";
import {inferFontVariant, maybeAutoTickFormat} from "../axes.js";
import {inferFontVariant} from "../marks/axis.js";
import {maybeAutoTickFormat} from "../axes.js";
import {createContext, create} from "../context.js";
import {isNoneish, maybeColorChannel, maybeNumberChannel} from "../options.js";
import {isOrdinalScale, isThresholdScale} from "../scales.js";
Expand Down
8 changes: 4 additions & 4 deletions src/mark.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type {ChannelDomainSort, Channels, ChannelValue, ChannelValues, ChannelVa
import type {Context} from "./context.js";
import type {Dimensions} from "./dimensions.js";
import type {plot} from "./plot.js";
import type {ScaleFunctions} from "./scales.js";
import type {InstanciatedScales} from "./scales.js";
import type {InitializerFunction, SortOrder, TransformFunction} from "./transforms/basic.js";

/**
Expand Down Expand Up @@ -40,8 +40,8 @@ export type Data = Iterable<any> | ArrayLike<any>;
export type RenderFunction = (
/** The mark’s (filtered and transformed) index. */
index: number[],
/** The plot’s scale functions. */
scales: ScaleFunctions,
/** The plot’s instanciated scales. */
scales: InstanciatedScales,
/** The mark’s (possibly scaled and transformed) channel values. */
values: ChannelValues,
/** The plot’s dimensions. */
Expand Down Expand Up @@ -459,5 +459,5 @@ export class RenderableMark extends Mark {
/** A compound Mark, comprising other marks. */
export type CompoundMark = Markish[] & Pick<Mark, "plot">;

/** Given an array of marks, returns a compound mark; supports *mark.plot shorthand. */
/** Given an array of marks, returns a compound mark; supports *mark*.plot shorthand. */
export function marks(...marks: Markish[]): CompoundMark;
40 changes: 19 additions & 21 deletions src/marks/axis.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {marks} from "../mark.js";
import {radians} from "../math.js";
import {range, valueof, arrayify, constant, keyword, identity, number} from "../options.js";
import {isNoneish, isIterable, isTemporal, maybeRangeInterval, orderof} from "../options.js";
import {isTemporalScale} from "../scales.js";
import {isOrdinalScale, isTemporalScale} from "../scales.js";
import {offset} from "../style.js";
import {initializer} from "../transforms/basic.js";
import {ruleX, ruleY} from "./rule.js";
Expand Down Expand Up @@ -136,7 +136,7 @@ function axisKy(
initializer: function (data, facets, channels, scales, dimensions) {
const scale = scales[k];
const {marginTop, marginRight, marginBottom, marginLeft} = (k === "y" && dimensions.inset) || dimensions;
const cla = labelAnchor ?? (scale.bandwidth ? "center" : "top");
const cla = labelAnchor ?? (scale.bandwidth === undefined ? "top" : "center");
const clo = labelOffset ?? (anchor === "right" ? marginRight : marginLeft) - 3;
if (cla === "center") {
this.textAnchor = undefined; // middle
Expand Down Expand Up @@ -245,7 +245,7 @@ function axisKx(
initializer: function (data, facets, channels, scales, dimensions) {
const scale = scales[k];
const {marginTop, marginRight, marginBottom, marginLeft} = (k === "x" && dimensions.inset) || dimensions;
const cla = labelAnchor ?? (scale.bandwidth ? "center" : "right");
const cla = labelAnchor ?? (scale.bandwidth === undefined ? "right" : "center");
const clo = labelOffset ?? (anchor === "top" ? marginTop : marginBottom) - 3;
if (cla === "center") {
this.frameAnchor = anchor;
Expand Down Expand Up @@ -507,26 +507,26 @@ function axisMark(mark, k, ariaLabel, data, options, initialize) {
if (data == null) {
if (isIterable(ticks)) {
data = arrayify(ticks);
} else if (scale.ticks) {
} else if (scale._tickFunction) {
if (ticks !== undefined) {
data = scale.ticks(ticks);
data = scale._tickFunction(ticks);
} else {
interval = maybeRangeInterval(interval === undefined ? scale.interval : interval, scale.type);
if (interval !== undefined) {
// For time scales, we could pass the interval directly to
// scale.ticks because it’s supported by d3.utcTicks; but
// quantitative scales and d3.ticks do not support numeric
// intervals for scale.ticks, so we compute them here.
const [min, max] = extent(scale.domain());
const [min, max] = extent(scale.domain);
data = interval.range(min, interval.offset(interval.floor(max))); // inclusive max
} else {
const [min, max] = extent(scale.range());
const [min, max] = extent(scale.range);
ticks = (max - min) / (tickSpacing === undefined ? (k === "x" ? 80 : 35) : tickSpacing);
data = scale.ticks(ticks);
data = scale._tickFunction(ticks);
}
}
} else {
data = scale.domain();
data = scale.domain;
}
if (k === "y" || k === "x") {
facets = [range(data)];
Expand Down Expand Up @@ -563,12 +563,12 @@ function inferTextChannel(scale, ticks, tickFormat) {
// domain (or ticks) are numbers or dates (say because we’re applying a time
// interval to the ordinal scale), we want Plot’s default formatter.
function inferTickFormat(scale, ticks, tickFormat) {
return scale.tickFormat
? scale.tickFormat(isIterable(ticks) ? null : ticks, tickFormat)
return scale._tickFormat
? scale._tickFormat(isIterable(ticks) ? null : ticks, tickFormat)
: tickFormat === undefined
? formatDefault
: typeof tickFormat === "string"
? (isTemporal(scale.domain()) ? utcFormat : format)(tickFormat)
? (isTemporal(scale.domain) ? utcFormat : format)(tickFormat)
: constant(tickFormat);
}

Expand Down Expand Up @@ -600,24 +600,18 @@ const shapeTickRight = {
}
};

// TODO Unify this with the other inferFontVariant; here we only have a scale
// function rather than a scale descriptor.
function inferFontVariant(scale) {
return scale.bandwidth && scale.interval === undefined ? undefined : "tabular-nums";
}

// Determines whether the scale points in the “positive” (right or down) or
// “negative” (left or up) direction; if the scale order cannot be determined,
// returns NaN; used to assign an appropriate label arrow.
function inferScaleOrder(scale) {
return Math.sign(orderof(scale.domain())) * Math.sign(orderof(scale.range()));
return Math.sign(orderof(scale.domain)) * Math.sign(orderof(scale.range));
}

// Takes the scale label, and if this is not an ordinal scale and the label was
// inferred from an associated channel, adds an orientation-appropriate arrow.
function inferAxisLabel(key, scale, labelAnchor) {
const label = scale.label;
if (scale.bandwidth || !label?.inferred) return label;
const label = scale._label;
if (scale.bandwidth !== undefined || !label?.inferred) return label;
const order = inferScaleOrder(scale);
return order
? key === "x" || labelAnchor === "center"
Expand All @@ -627,3 +621,7 @@ function inferAxisLabel(key, scale, labelAnchor) {
: `${order < 0 ? "↑ " : "↓ "}${label}`
: label;
}

export function inferFontVariant(scale) {
return isOrdinalScale(scale) && scale.interval === undefined ? undefined : "tabular-nums";
}
4 changes: 2 additions & 2 deletions src/marks/bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@ export class AbstractBar extends Mark {
}
_width({x}, {x: X}, {marginRight, marginLeft, width}) {
const {insetLeft, insetRight} = this;
const bandwidth = X && x ? x.bandwidth() : width - marginRight - marginLeft;
const bandwidth = X && x ? x.bandwidth : width - marginRight - marginLeft;
return Math.max(0, bandwidth - insetLeft - insetRight);
}
_height({y}, {y: Y}, {marginTop, marginBottom, height}) {
const {insetTop, insetBottom} = this;
const bandwidth = Y && y ? y.bandwidth() : height - marginTop - marginBottom;
const bandwidth = Y && y ? y.bandwidth : height - marginTop - marginBottom;
return Math.max(0, bandwidth - insetTop - insetBottom);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/marks/geo.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ function scaleProjection({x: X, y: Y}) {
Y ??= (y) => y;
return geoTransform({
point(x, y) {
this.stream.point(X(x), Y(y));
this.stream.point(X.apply(x), Y.apply(y));
}
});
}
Expand Down
2 changes: 1 addition & 1 deletion src/marks/raster.js
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ export class Raster extends AbstractRaster {
return super.scale(channels, scales, context);
}
render(index, scales, values, dimensions, context) {
const color = scales[values.channels.fill?.scale] ?? ((x) => x);
const color = scales[values.channels.fill?.scale]?.apply ?? ((x) => x);
const {x: X, y: Y} = values;
const {document} = context;
const [x1, y1, x2, y2] = renderBounds(values, dimensions, context);
Expand Down
4 changes: 2 additions & 2 deletions src/marks/tick.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export class TickX extends AbstractTick {
}
_y2({y}, {y: Y}, {height, marginBottom}) {
const {insetBottom} = this;
return Y && y ? (i) => Y[i] + y.bandwidth() - insetBottom : height - marginBottom - insetBottom;
return Y && y ? (i) => Y[i] + y.bandwidth - insetBottom : height - marginBottom - insetBottom;
}
}

Expand All @@ -90,7 +90,7 @@ export class TickY extends AbstractTick {
}
_x2({x}, {x: X}, {width, marginRight}) {
const {insetRight} = this;
return X && x ? (i) => X[i] + x.bandwidth() - insetRight : width - marginRight - insetRight;
return X && x ? (i) => X[i] + x.bandwidth - insetRight : width - marginRight - insetRight;
}
_y1(scales, {y: Y}) {
return (i) => Y[i];
Expand Down
12 changes: 6 additions & 6 deletions src/plot.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,11 +133,11 @@ export function plot(options = {}) {

// Initalize the scales and dimensions.
const scaleDescriptors = createScales(addScaleChannels(channelsByScale, stateByMark), 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 @@ -235,7 +235,7 @@ export function plot(options = {}) {

// Render facets.
if (facets !== undefined) {
const facetDomains = {x: fx?.domain(), y: fy?.domain()};
const facetDomains = {x: fx?.domain, y: fy?.domain};

// Sort the facets to match the fx and fy domains; this is needed because
// the facets were constructed prior to the fx and fy scales.
Expand Down Expand Up @@ -638,9 +638,9 @@ function actualDimensions({fx, fy}, dimensions) {
}

function outerRange(scale) {
const domain = scale.domain();
let x1 = scale(domain[0]);
let x2 = scale(domain[domain.length - 1]);
const {domain} = scale;
let x1 = scale.apply(domain[0]);
let x2 = scale.apply(domain[domain.length - 1]);
if (x2 < x1) [x1, x2] = [x2, x1];
return [x1, x2 + scale.bandwidth()];
return [x1, x2 + scale.bandwidth];
}
5 changes: 2 additions & 3 deletions src/scales.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,10 +160,9 @@ export type ColorScheme = ColorSchemeCase | (Lowercase<ColorSchemeCase> & Record
export type ScaleName = "x" | "y" | "fx" | "fy" | "r" | "color" | "opacity" | "symbol" | "length";

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

/**
* The supported scale types. For quantitative data, one of:
Expand Down
23 changes: 14 additions & 9 deletions src/scales.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,17 @@ 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];
})
.map(([name, scale]) => [
name,
{
...exposeScale(scale),
// for axis
_label: scale.label,
_tickFormat: scale.scale.tickFormat,
_tickFunction: scale.scale.ticks,
...(scale.type === "identity" && {range: slice(scale.range)})
}
])
);
}

Expand Down Expand Up @@ -472,10 +477,10 @@ export function isDivergingScale({type}) {
// dimension (whereas a dot will simply be drawn in the center).
export function isCollapsed(scale) {
if (scale === undefined) return true; // treat missing scale as collapsed
const domain = scale.domain();
const value = scale(domain[0]);
const {domain} = scale;
const value = scale.apply(domain[0]);
for (let i = 1, n = domain.length; i < n; ++i) {
if (scale(domain[i]) - value) {
if (scale.apply(domain[i]) - value) {
return false;
}
}
Expand Down
4 changes: 2 additions & 2 deletions src/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,8 @@ export function applyStyle(selection, name, value) {
export function applyTransform(selection, mark, {x, y}, tx = offset, ty = offset) {
tx += mark.dx;
ty += mark.dy;
if (x?.bandwidth) tx += x.bandwidth() / 2;
if (y?.bandwidth) ty += y.bandwidth() / 2;
if (x?.bandwidth !== undefined) tx += x.bandwidth / 2;
if (y?.bandwidth !== undefined) ty += y.bandwidth / 2;
if (tx || ty) selection.attr("transform", `translate(${tx},${ty})`);
}

Expand Down
4 changes: 2 additions & 2 deletions src/transforms/basic.d.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type {ChannelName, Channels, ChannelValue} from "../channel.js";
import type {Context} from "../context.js";
import type {Dimensions} from "../dimensions.js";
import type {ScaleFunctions} from "../scales.js";
import type {InstanciatedScales} from "../scales.js";

/**
* A mark transform function is passed the mark’s *data* and a nested index into
Expand Down Expand Up @@ -40,7 +40,7 @@ export type InitializerFunction = (
data: any[],
facets: number[][],
channels: Channels,
scales: ScaleFunctions,
scales: InstanciatedScales,
dimensions: Dimensions,
context: Context
) => {
Expand Down
2 changes: 1 addition & 1 deletion src/transforms/dodge.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ function dodge(y, x, anchor, padding, options) {
if (!channels[x]) throw new Error(`missing channel: ${x}`);
({[x]: X} = applyPosition(channels, scales, context));
const r = R ? undefined : this.r !== undefined ? this.r : options.r !== undefined ? number(options.r) : 3;
if (R) R = valueof(R.value, scales[R.scale] || identity, Float64Array);
if (R) R = valueof(R.value, scales[R.scale]?.apply || identity, Float64Array);
let [ky, ty] = anchor(dimensions);
const compare = ky ? compareAscending : compareSymmetric;
const Y = new Float64Array(X.length);
Expand Down
2 changes: 1 addition & 1 deletion test/transforms/remap.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export function remap(outputs = {}, options) {
if (!channel) throw new Error(`missing channel: ${name}`);
const V = Array.from(channel.value);
const n = V.length;
const scale = scales[channel.scale];
const scale = scales[channel.scale]?.apply;
if (scale) for (let i = 0; i < n; ++i) V[i] = scale(V[i]);
for (let i = 0; i < n; ++i) V[i] = map(V[i], i, V);
return [name, {value: V}];
Expand Down