Skip to content

Commit 84d88b2

Browse files
authored
fix zero for descending domains (#556)
1 parent d8327ea commit 84d88b2

File tree

3 files changed

+52
-7
lines changed

3 files changed

+52
-7
lines changed

src/scales.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ export function scaleOrder({range, domain = range}) {
188188
return Math.sign(order(domain)) * Math.sign(order(range));
189189
}
190190

191-
function order(values) {
191+
export function order(values) {
192192
if (values == null) return;
193193
const first = values[0];
194194
const last = values[values.length - 1];

src/scales/quantitative.js

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
ascending,
3+
extent,
34
interpolateHcl,
45
interpolateHsl,
56
interpolateLab,
@@ -24,6 +25,7 @@ import {ordinalRange, quantitativeScheme} from "./schemes.js";
2425
import {registry, radius, opacity, color} from "./index.js";
2526
import {positive, negative} from "../defined.js";
2627
import {constant} from "../mark.js";
28+
import {order} from "../scales.js";
2729

2830
export const flip = i => t => i(1 - t);
2931

@@ -88,13 +90,17 @@ export function ScaleQ(key, scale, channels, {
8890
interpolate = scale.interpolate();
8991
}
9092

91-
// TODO describe zero option
93+
// If a zero option is specified, we assume that the domain is numeric, and we
94+
// want to ensure that the domain crosses zero. However, note that the domain
95+
// may be reversed (descending) so we shouldn’t assume that the first value is
96+
// smaller than the last; and also it’s possible that the domain has more than
97+
// two values for a “poly” scale. And lastly be careful not to mutate input!
9298
if (zero) {
93-
domain = [...domain]; // copy before write
94-
if (domain[0] > 0) {
95-
domain[0] = 0;
96-
} else if (domain[domain.length - 1] < 0) {
97-
domain[domain.length - 1] = 0;
99+
const [min, max] = extent(domain);
100+
if ((min > 0) || (max < 0)) {
101+
domain = Array.from(domain);
102+
if (order(domain) < 0) domain[domain.length - 1] = 0;
103+
else domain[0] = 0;
98104
}
99105
}
100106

test/scales/scales-test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,45 @@ it("plot(…).scale(name) promotes the given zero option to the domain", async (
247247
});
248248
});
249249

250+
it("plot(…).scale(name) handles the zero option correctly for descending domains", async () => {
251+
const penguins = await d3.csv("data/penguins.csv", d3.autoType);
252+
const plot = Plot.dotX(penguins, {x: "body_mass_g"}).plot({x: {zero: true, domain: [4000, 2000]}});
253+
assert.deepStrictEqual(plot.scale("x"), {
254+
type: "linear",
255+
domain: [4000, 0],
256+
range: [20, 620],
257+
interpolate: d3.interpolate,
258+
clamp: false,
259+
label: "← body_mass_g"
260+
});
261+
});
262+
263+
it("plot(…).scale(name) handles the zero option correctly for polylinear domains", async () => {
264+
const penguins = await d3.csv("data/penguins.csv", d3.autoType);
265+
const plot = Plot.dotX(penguins, {x: "body_mass_g"}).plot({x: {type: "linear", zero: true, domain: [1000, 2000, 4000]}});
266+
assert.deepStrictEqual(plot.scale("x"), {
267+
type: "linear",
268+
domain: [0, 2000, 4000],
269+
range: [20, 320, 620],
270+
interpolate: d3.interpolate,
271+
clamp: false,
272+
label: "body_mass_g →"
273+
});
274+
});
275+
276+
it("plot(…).scale(name) handles the zero option correctly for descending polylinear domains", async () => {
277+
const penguins = await d3.csv("data/penguins.csv", d3.autoType);
278+
const plot = Plot.dotX(penguins, {x: "body_mass_g"}).plot({x: {type: "linear", zero: true, domain: [4000, 2000, 1000]}});
279+
assert.deepStrictEqual(plot.scale("x"), {
280+
type: "linear",
281+
domain: [4000, 2000, 0],
282+
range: [20, 320, 620],
283+
interpolate: d3.interpolate,
284+
clamp: false,
285+
label: "← body_mass_g"
286+
});
287+
});
288+
250289
it("plot(…).scale('color') can return undefined if no color scale is present", () => {
251290
const plot = Plot.dot([1, 2], {x: d => d}).plot();
252291
assert.strictEqual(plot.scale("color"), undefined);

0 commit comments

Comments
 (0)