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"), {