diff --git a/src/legends/ramp.js b/src/legends/ramp.js index 3dcaf411c6..81b0b0a2d6 100644 --- a/src/legends/ramp.js +++ b/src/legends/ramp.js @@ -1,4 +1,4 @@ -import {quantize, interpolateNumber, piecewise, format, scaleBand, scaleLinear, axisBottom} from "d3"; +import {quantize, interpolateNumber, piecewise, format, scaleBand, scaleLinear, axisBottom, scaleThreshold} from "d3"; import {inferFontVariant} from "../axes.js"; import {createContext, create} from "../context.js"; import {map, maybeNumberChannel} from "../options.js"; @@ -58,7 +58,7 @@ export function legendRamp(color, options) { // Some D3 scales use scale.interpolate, some scale.interpolator, and some // scale.round; this normalizes the API so it works with all scale types. - const applyRange = round ? (x, range) => x.rangeRound(range) : (x, range) => x.range(range); + const applyRange = round ? (x, range) => (x.rangeRound ?? x.range)(range) : (x, range) => x.range(range); const {type, domain, range, interpolate, scale, pivot} = color; @@ -70,18 +70,27 @@ export function legendRamp(color, options) { const interpolator = range === undefined ? interpolate + : type === "threshold" + ? scaleThreshold( + Array.from(domain, (_, i) => (i + 1) / (domain.length + 1)), + Array.from(range, interpolate) + ) : piecewise(interpolate.length === 1 ? interpolatePiecewise(interpolate) : interpolate, range); // Construct a D3 scale of the same type, but with a range that evenly // divides the horizontal extent of the legend. (In the common case, the // domain.length is two, and so the range is simply the extent.) For a // diverging scale, we need an extra point in the range for the pivot such - // that the pivot is always drawn in the middle. + // that the pivot is always drawn in the middle. For a threshold scale, we + // add some space below and above. x = applyRange( scale.copy(), quantize( interpolateNumber(marginLeft, width - marginRight), - Math.min(domain.length + (pivot !== undefined), range === undefined ? Infinity : range.length) + Math.min( + domain.length + (pivot !== undefined) + 2 * (type === "threshold"), + range === undefined ? Infinity : range.length + +(type === "threshold") + ) ) ); diff --git a/test/output/ordinalOpacityRamp.html b/test/output/ordinalOpacityRamp.html new file mode 100644 index 0000000000..d8b388244d --- /dev/null +++ b/test/output/ordinalOpacityRamp.html @@ -0,0 +1,109 @@ +
+ + + + + + 0 + + + + 1 + + + + 2 + + + + 3 + + + + 4 + + + + 5 + + + + 6 + + + + 7 + + + + 8 + + + + 9 + + + + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/test/output/ordinalOpacityThreshold.html b/test/output/ordinalOpacityThreshold.html new file mode 100644 index 0000000000..36b2f21727 --- /dev/null +++ b/test/output/ordinalOpacityThreshold.html @@ -0,0 +1,81 @@ +
+ + + + + + 2 + + + + 5 + + + + 8 + + + + + + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/test/plots/ordinal-opacity.ts b/test/plots/ordinal-opacity.ts index 65438dfb5c..b0396ece1d 100644 --- a/test/plots/ordinal-opacity.ts +++ b/test/plots/ordinal-opacity.ts @@ -8,3 +8,15 @@ export async function ordinalOpacity() { export async function ordinalOpacityImplicitZero() { return Plot.cellX(d3.range(2, 10), {fill: "red", opacity: Plot.identity}).plot({opacity: {type: "ordinal"}}); } + +export async function ordinalOpacityRamp() { + return Plot.cellX(d3.range(10), {fill: "red", opacity: Plot.identity}).plot({ + opacity: {type: "ordinal", legend: "ramp"} + }); +} + +export async function ordinalOpacityThreshold() { + return Plot.cellX(d3.range(10), {fill: "red", opacity: Plot.identity}).plot({ + opacity: {type: "threshold", legend: true, domain: [2, 5, 8], range: [0.2, 0.4, 0.6, 0.8]} + }); +}