From 6bea18ef4f4c311aa524b3737f2b73cab6e30d50 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Tue, 19 Nov 2024 08:28:50 -0800 Subject: [PATCH] handle degenerate domains (#2212) --- src/scales.js | 9 +++--- src/scales/quantitative.js | 10 +++++-- test/output/colorLegendDomainEmpty.html | 27 ++++++++++++++++++ test/output/colorLegendDomainUnary.html | 29 ++++++++++++++++++++ test/output/colorLegendLinearDomainEmpty.svg | 17 ++++++++++++ test/output/colorLegendLinearDomainUnary.svg | 22 +++++++++++++++ test/plots/legend-color.ts | 16 +++++++++++ 7 files changed, 124 insertions(+), 6 deletions(-) create mode 100644 test/output/colorLegendDomainEmpty.html create mode 100644 test/output/colorLegendDomainUnary.html create mode 100644 test/output/colorLegendLinearDomainEmpty.svg create mode 100644 test/output/colorLegendLinearDomainUnary.svg diff --git a/src/scales.js b/src/scales.js index 46a1f45c3c..7d01163ad0 100644 --- a/src/scales.js +++ b/src/scales.js @@ -425,10 +425,11 @@ function inferScaleType(key, channels, {type, domain, range, scheme, pivot, proj if (kind === opacity || kind === length) return "linear"; if (kind === symbol) return "ordinal"; - // If the domain or range has more than two values, assume it’s ordinal. You - // can still use a “piecewise” (or “polylinear”) scale, but you must set the - // type explicitly. - if ((domain || range || []).length > 2) return asOrdinalType(kind); + // If a domain or range is explicitly specified and doesn’t have two values, + // assume it’s ordinal. You can still use a “piecewise” (or “polylinear”) + // scale, but you must set the type explicitly. + const n = (domain ?? range)?.length; + if (n < 2 || n > 2) return asOrdinalType(kind); // Otherwise, infer the scale type from the data! Prefer the domain, if // present, over channels. (The domain and channels should be consistently diff --git a/src/scales/quantitative.js b/src/scales/quantitative.js index 88feec4d07..8cf9cadbbb 100644 --- a/src/scales/quantitative.js +++ b/src/scales/quantitative.js @@ -80,6 +80,7 @@ export function createScaleQ( reverse } ) { + domain = maybeRepeat(domain); interval = maybeRangeInterval(interval, type); if (type === "cyclical" || type === "sequential") type = "linear"; // shorthand for color schemes if (typeof interpolate !== "function") interpolate = maybeInterpolator(interpolate); // named interpolator @@ -88,8 +89,8 @@ export function createScaleQ( // If an explicit range is specified, and it has a different length than the // domain, then redistribute the range using a piecewise interpolator. if (range !== undefined) { - const n = (domain = arrayify(domain)).length; - const m = (range = arrayify(range)).length; + const n = domain.length; + const m = (range = maybeRepeat(range)).length; if (n !== m) { if (interpolate.length === 1) throw new Error("invalid piecewise interpolator"); // e.g., turbo interpolate = piecewise(interpolate, range); @@ -137,6 +138,11 @@ export function createScaleQ( return {type, domain, range, scale, interpolate, interval}; } +function maybeRepeat(values) { + values = arrayify(values); + return values.length >= 2 ? values : [values[0], values[0]]; +} + function maybeNice(nice, type) { return nice === true ? undefined : typeof nice === "number" ? nice : maybeNiceInterval(nice, type); } diff --git a/test/output/colorLegendDomainEmpty.html b/test/output/colorLegendDomainEmpty.html new file mode 100644 index 0000000000..5e9bfac54d --- /dev/null +++ b/test/output/colorLegendDomainEmpty.html @@ -0,0 +1,27 @@ +
+ +
\ No newline at end of file diff --git a/test/output/colorLegendDomainUnary.html b/test/output/colorLegendDomainUnary.html new file mode 100644 index 0000000000..1401790db6 --- /dev/null +++ b/test/output/colorLegendDomainUnary.html @@ -0,0 +1,29 @@ +
+ + + 0 +
\ No newline at end of file diff --git a/test/output/colorLegendLinearDomainEmpty.svg b/test/output/colorLegendLinearDomainEmpty.svg new file mode 100644 index 0000000000..4863ce6e97 --- /dev/null +++ b/test/output/colorLegendLinearDomainEmpty.svg @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/test/output/colorLegendLinearDomainUnary.svg b/test/output/colorLegendLinearDomainUnary.svg new file mode 100644 index 0000000000..cf6b6c2f70 --- /dev/null +++ b/test/output/colorLegendLinearDomainUnary.svg @@ -0,0 +1,22 @@ + + + + + + + 0 + + + \ No newline at end of file diff --git a/test/plots/legend-color.ts b/test/plots/legend-color.ts index 744e850f4f..031dfdbc78 100644 --- a/test/plots/legend-color.ts +++ b/test/plots/legend-color.ts @@ -34,6 +34,22 @@ export function colorLegendCategoricalReverse() { return Plot.legend({color: {domain: "ABCDEFGHIJ", reverse: true}}); } +export function colorLegendDomainUnary() { + return Plot.legend({color: {domain: [0]}}); +} + +export function colorLegendDomainEmpty() { + return Plot.legend({color: {domain: []}}); +} + +export function colorLegendLinearDomainUnary() { + return Plot.legend({color: {type: "linear", domain: [0]}}); +} + +export function colorLegendLinearDomainEmpty() { + return Plot.legend({color: {type: "linear", domain: []}}); +} + export function colorLegendOrdinal() { return Plot.legend({color: {type: "ordinal", domain: "ABCDEFGHIJ"}}); }