diff --git a/src/channel.js b/src/channel.js index fc4fa63843..1311d80554 100644 --- a/src/channel.js +++ b/src/channel.js @@ -1,5 +1,5 @@ import {ascending, descending, rollup, sort} from "d3"; -import {first, isColor, isEvery, isIterable, labelof, map, maybeValue, range, valueof} from "./options.js"; +import {first, isColor, isEvery, isIterable, isOpacity, labelof, map, maybeValue, range, valueof} from "./options.js"; import {registry} from "./scales/index.js"; import {isSymbol, maybeSymbol} from "./symbols.js"; import {maybeReduce} from "./transforms/group.js"; @@ -42,14 +42,14 @@ export function inferChannelScale(name, channel) { case "fill": case "stroke": case "color": - channel.scale = isEvery(value, isColor) ? null : "color"; + channel.scale = scale !== true && isEvery(value, isColor) ? null : "color"; break; case "fillOpacity": case "strokeOpacity": - channel.scale = "opacity"; + channel.scale = scale !== true && isEvery(value, isOpacity) ? null : "opacity"; break; case "symbol": - if (isEvery(value, isSymbol)) { + if (scale !== true && isEvery(value, isSymbol)) { channel.scale = null; channel.value = map(value, maybeSymbol); } else { @@ -60,6 +60,8 @@ export function inferChannelScale(name, channel) { channel.scale = registry.has(name) ? name : null; break; } + } else if (scale === false) { + channel.scale = null; } else if (scale != null && !registry.has(scale)) { throw new Error(`unknown scale: ${scale}`); } diff --git a/src/marks/raster.js b/src/marks/raster.js index 2e57efcc74..3351c92ff8 100644 --- a/src/marks/raster.js +++ b/src/marks/raster.js @@ -248,7 +248,7 @@ export function sampler(name, options = {}) { } } } - return {data: V, facets, channels: {[name]: {value: V, scale: true}}}; + return {data: V, facets, channels: {[name]: {value: V, scale: "auto"}}}; }); } diff --git a/src/options.js b/src/options.js index 7ee83d095b..4e073724ff 100644 --- a/src/options.js +++ b/src/options.js @@ -367,6 +367,10 @@ export function isColor(value) { ); } +export function isOpacity(value) { + return typeof value === "number" && ((0 <= value && value <= 1) || isNaN(value)); +} + export function isNoneish(value) { return value == null || isNone(value); } diff --git a/src/style.js b/src/style.js index 09185365c8..d7f1ef0975 100644 --- a/src/style.js +++ b/src/style.js @@ -144,11 +144,11 @@ export function styles( href: {value: href, optional: true}, ariaLabel: {value: variaLabel, optional: true}, fill: {value: vfill, scale: "auto", optional: true}, - fillOpacity: {value: vfillOpacity, scale: "opacity", optional: true}, + fillOpacity: {value: vfillOpacity, scale: "auto", optional: true}, stroke: {value: vstroke, scale: "auto", optional: true}, - strokeOpacity: {value: vstrokeOpacity, scale: "opacity", optional: true}, + strokeOpacity: {value: vstrokeOpacity, scale: "auto", optional: true}, strokeWidth: {value: vstrokeWidth, optional: true}, - opacity: {value: vopacity, scale: "opacity", optional: true} + opacity: {value: vopacity, scale: "auto", optional: true} }; } diff --git a/src/transforms/group.js b/src/transforms/group.js index 8e1008efd7..eeeda79cf6 100644 --- a/src/transforms/group.js +++ b/src/transforms/group.js @@ -177,18 +177,18 @@ export function maybeOutputs(outputs, inputs) { if (inputs.href != null && outputs.href === undefined) entries.push(["href", reduceFirst]); return entries .filter(([, reduce]) => reduce !== undefined) - .map(([name, reduce]) => { - return reduce === null ? {name, initialize() {}, scope() {}, reduce() {}} : maybeOutput(name, reduce, inputs); - }); + .map(([name, reduce]) => (reduce === null ? nullOutput(name) : maybeOutput(name, reduce, inputs))); } export function maybeOutput(name, reduce, inputs) { + let scale; // optional per-channel scale override + if (isObject(reduce) && typeof reduce.reduce !== "function") (scale = reduce.scale), (reduce = reduce.reduce); const evaluator = maybeEvaluator(name, reduce, inputs); const [output, setOutput] = column(evaluator.label); let O; return { name, - output, + output: scale === undefined ? output : {value: output, scale}, initialize(data) { evaluator.initialize(data); O = setOutput([]); @@ -202,6 +202,10 @@ export function maybeOutput(name, reduce, inputs) { }; } +function nullOutput(name) { + return {name, initialize() {}, scope() {}, reduce() {}}; +} + export function maybeEvaluator(name, reduce, inputs) { const input = maybeInput(name, inputs); const reducer = maybeReduce(reduce, input); diff --git a/src/transforms/hexbin.js b/src/transforms/hexbin.js index 464270461a..370839e9df 100644 --- a/src/transforms/hexbin.js +++ b/src/transforms/hexbin.js @@ -83,13 +83,13 @@ export function hexbin(outputs = {fill: "count"}, {binWidth, ...options} = {}) { x: {value: BX}, y: {value: BY}, ...(Z && {z: {value: GZ}}), - ...(F && {fill: {value: GF, scale: true}}), - ...(S && {stroke: {value: GS, scale: true}}), - ...(Q && {symbol: {value: GQ, scale: true}}), + ...(F && {fill: {value: GF, scale: "auto"}}), + ...(S && {stroke: {value: GS, scale: "auto"}}), + ...(Q && {symbol: {value: GQ, scale: "auto"}}), ...Object.fromEntries( outputs.map(({name, output}) => [ name, - {scale: true, radius: name === "r" ? binWidth / 2 : undefined, value: output.transform()} + {scale: "auto", radius: name === "r" ? binWidth / 2 : undefined, value: output.transform()} ]) ) }; diff --git a/test/output/markovChain.svg b/test/output/markovChain.svg index 575735f9d9..5e1030551f 100644 --- a/test/output/markovChain.svg +++ b/test/output/markovChain.svg @@ -19,15 +19,15 @@ - - - - - - - - - + + + + + + + + + A diff --git a/test/scales/scales-test.js b/test/scales/scales-test.js index c8c8dd5a52..c1404d8f41 100644 --- a/test/scales/scales-test.js +++ b/test/scales/scales-test.js @@ -1252,7 +1252,10 @@ it("plot(…).scale('opacity') can return a linear scale for penguins", async () it("plot(…).scale('opacity') respects the percent option, affecting domain", async () => { const penguins = await d3.csv("data/penguins.csv", d3.autoType); - const plot = Plot.rectX(penguins, Plot.binX({fillOpacity: "proportion"}, {x: "body_mass_g", thresholds: 20})).plot({ + const plot = Plot.rectX( + penguins, + Plot.binX({fillOpacity: {reduce: "proportion", scale: true}}, {x: "body_mass_g", thresholds: 20}) + ).plot({ opacity: {percent: true} }); scaleEqual(plot.scale("opacity"), {