Skip to content

Commit 0dc6658

Browse files
authored
symmetric diverging scales by domain extension (#563)
1 parent 835919b commit 0dc6658

File tree

2 files changed

+64
-23
lines changed

2 files changed

+64
-23
lines changed

src/scales/diverging.js

Lines changed: 46 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,11 @@ function ScaleD(key, scale, transform, channels, {
5151

5252
// Normalize the interpolator for symmetric difference around the pivot.
5353
if (symmetric) {
54-
const mindelta = Math.abs(transform(min) - transform(pivot));
55-
const maxdelta = Math.abs(transform(max) - transform(pivot));
56-
if (mindelta < maxdelta) interpolate = truncateLower(interpolate, mindelta / maxdelta);
57-
else if (mindelta > maxdelta) interpolate = truncateUpper(interpolate, maxdelta / mindelta);
54+
const mid = transform.apply(pivot);
55+
const mindelta = mid - transform.apply(min);
56+
const maxdelta = transform.apply(max) - mid;
57+
if (mindelta < maxdelta) min = transform.invert(mid - maxdelta);
58+
else if (mindelta > maxdelta) max = transform.invert(mid + mindelta);
5859
}
5960

6061
scale.domain([min, pivot, max]).unknown(unknown).interpolator(interpolate);
@@ -64,37 +65,66 @@ function ScaleD(key, scale, transform, channels, {
6465
}
6566

6667
export function ScaleDiverging(key, channels, options) {
67-
return ScaleD(key, scaleDiverging(), x => x, channels, options);
68+
return ScaleD(key, scaleDiverging(), transformIdentity, channels, options);
6869
}
6970

7071
export function ScaleDivergingSqrt(key, channels, options) {
7172
return ScaleDivergingPow(key, channels, {...options, exponent: 0.5});
7273
}
7374

7475
export function ScaleDivergingPow(key, channels, {exponent = 1, ...options}) {
75-
return ScaleD(key, scaleDivergingPow().exponent(exponent), transformPow(exponent), channels, {...options, type: "diverging-pow"});
76+
return ScaleD(key, scaleDivergingPow().exponent(exponent = +exponent), transformPow(exponent), channels, {...options, type: "diverging-pow"});
7677
}
7778

7879
export function ScaleDivergingLog(key, channels, {base = 10, pivot = 1, domain = inferDomain(channels, pivot < 0 ? negative : positive), ...options}) {
79-
return ScaleD(key, scaleDivergingLog().base(base), Math.log, channels, {domain, pivot, ...options});
80+
return ScaleD(key, scaleDivergingLog().base(base = +base), transformLog, channels, {domain, pivot, ...options});
8081
}
8182

8283
export function ScaleDivergingSymlog(key, channels, {constant = 1, ...options}) {
83-
return ScaleD(key, scaleDivergingSymlog().constant(constant), transformSymlog(constant), channels, options);
84+
return ScaleD(key, scaleDivergingSymlog().constant(constant = +constant), transformSymlog(constant), channels, options);
8485
}
8586

86-
function truncateLower(interpolate, k) {
87-
return t => interpolate(t < 0.5 ? t * k + (1 - k) / 2 : t);
88-
}
87+
const transformIdentity = {
88+
apply(x) {
89+
return x;
90+
},
91+
invert(x) {
92+
return x;
93+
}
94+
};
8995

90-
function truncateUpper(interpolate, k) {
91-
return t => interpolate(t > 0.5 ? t * k + (1 - k) / 2 : t);
92-
}
96+
const transformLog = {
97+
apply: Math.log,
98+
invert: Math.exp
99+
};
100+
101+
const transformSqrt = {
102+
apply(x) {
103+
return Math.sign(x) * Math.sqrt(Math.abs(x));
104+
},
105+
invert(x) {
106+
return Math.sign(x) * (x * x);
107+
}
108+
};
93109

94110
function transformPow(exponent) {
95-
return exponent === 0.5 ? Math.sqrt : x => Math.sign(x) * Math.pow(Math.abs(x), exponent);
111+
return exponent === 0.5 ? transformSqrt : {
112+
apply(x) {
113+
return Math.sign(x) * Math.pow(Math.abs(x), exponent);
114+
},
115+
invert(x) {
116+
return Math.sign(x) * Math.pow(Math.abs(x), 1 / exponent);
117+
}
118+
};
96119
}
97120

98121
function transformSymlog(constant) {
99-
return x => Math.sign(x) * Math.log1p(Math.abs(x / constant));
122+
return {
123+
apply(x) {
124+
return Math.sign(x) * Math.log1p(Math.abs(x / constant));
125+
},
126+
invert(x) {
127+
return Math.sign(x) * Math.expm1(Math.abs(x)) * constant;
128+
}
129+
};
100130
}

test/scales/scales-test.js

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -393,18 +393,14 @@ it("plot(…).scale('color') can return an asymmetric diverging scale", async ()
393393
it("plot(…).scale('color') can return a symmetric diverging scale", async () => {
394394
const gistemp = await d3.csv("data/gistemp.csv", d3.autoType);
395395
const plot = Plot.dot(gistemp, {x: "Date", stroke: "Anomaly"}).plot({color: {type: "diverging"}});
396-
const {interpolate, ...color} = plot.scale("color");
397-
assert.deepStrictEqual(color, {
396+
assert.deepStrictEqual(plot.scale("color"), {
398397
type: "diverging",
399-
domain: [-0.78, 1.35],
398+
domain: [-1.35, 1.35],
399+
interpolate: d3.interpolateRdBu,
400400
pivot: 0,
401401
clamp: false,
402402
label: "Anomaly"
403403
});
404-
const k = 0.78 / 1.35;
405-
for (const t of d3.ticks(0, 1, 100)) {
406-
assert.strictEqual(interpolate(t), d3.interpolateRdBu(t < 0.5 ? t * k + (1 - k) / 2: t));
407-
}
408404
});
409405

410406
it("plot(…).scale('color') can return a diverging scale with an explicit range", async () => {
@@ -455,6 +451,21 @@ it("plot(…).scale('color') can return a transformed diverging scale", async ()
455451
});
456452
});
457453

454+
it("plot(…).scale('color') can return a transformed symmetric diverging scale", async () => {
455+
const transform = d => d * 100;
456+
const gistemp = await d3.csv("data/gistemp.csv", d3.autoType);
457+
const plot = Plot.dot(gistemp, {x: "Date", stroke: "Anomaly"}).plot({color: {type: "diverging", transform}});
458+
assert.deepStrictEqual(plot.scale("color"), {
459+
type: "diverging",
460+
domain: [-135, 135],
461+
pivot: 0,
462+
transform,
463+
interpolate: d3.interpolateRdBu,
464+
clamp: false,
465+
label: "Anomaly"
466+
});
467+
});
468+
458469
it("plot(…).scale('color') can return an asymmetric diverging pow scale with an explicit scheme", async () => {
459470
const gistemp = await d3.csv("data/gistemp.csv", d3.autoType);
460471
const plot = Plot.dot(gistemp, {x: "Date", stroke: "Anomaly"}).plot({color: {type: "diverging-sqrt", symmetric: false, scheme: "piyg"}});

0 commit comments

Comments
 (0)