Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add invert to interpolated scales. #191

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Expand Up @@ -573,6 +573,10 @@ See [*continuous*.clamp](#continuous_clamp).

If *interpolator* is specified, sets the scale’s interpolator to the specified function. If *interpolator* is not specified, returns the scale’s current interpolator.

<a name="sequential_invert" href="#sequential_invert">#</a> <i>sequential</i>.<b>invert</b>(<i>fraction</i>) · [Source](https://github.com/d3/d3-scale/blob/master/src/sequential.js)

Inverts the given interpolation *fraction* and returns the corresponding domain value. This method does not invert the output of the interpolator function (which may be non-invertible), but rather inverts fractional values that serve as input to the interpolator.

<a name="sequential_range" href="#sequential_range">#</a> <i>sequential</i>.<b>range</b>([<i>range</i>]) · [Source](https://github.com/d3/d3-scale/blob/master/src/sequential.js), [Examples](https://observablehq.com/@d3/sequential-scales)

See [*continuous*.range](#continuous_range). If *range* is specified, implicitly uses [d3.interpolate](https://github.com/d3/d3-interpolate/blob/master/README.md#interpolate) as the interpolator.
Expand Down Expand Up @@ -637,6 +641,10 @@ See [*continuous*.clamp](#continuous_clamp).

If *interpolator* is specified, sets the scale’s interpolator to the specified function. If *interpolator* is not specified, returns the scale’s current interpolator.

<a name="diverging_invert" href="#diverging_invert">#</a> <i>diverging</i>.<b>invert</b>(<i>fraction</i>) · [Source](https://github.com/d3/d3-scale/blob/master/src/diverging.js)

Inverts the given interpolation *fraction* and returns the corresponding domain value. This method does not invert the output of the interpolator function (which may be non-invertible), but rather inverts fractional values that serve as input to the interpolator.

<a name="diverging_range" href="#diverging_range">#</a> <i>diverging</i>.<b>range</b>([<i>range</i>]) · [Source](https://github.com/d3/d3-scale/blob/master/src/diverging.js), [Examples](https://observablehq.com/@d3/diverging-scales)

See [*continuous*.range](#continuous_range). If *range* is specified, implicitly uses [d3.interpolate](https://github.com/d3/d3-interpolate/blob/master/README.md#interpolate) as the interpolator.
Expand Down
13 changes: 10 additions & 3 deletions src/diverging.js
Expand Up @@ -19,13 +19,20 @@ function transformer() {
k21,
interpolator = identity,
transform,
untransform,
clamp = false,
unknown;

function scale(x) {
return isNaN(x = +x) ? unknown : (x = 0.5 + ((x = +transform(x)) - t1) * (s * x < s * t1 ? k10 : k21), interpolator(clamp ? Math.max(0, Math.min(1, x)) : x));
}

scale.invert = function(_) {
_ = clamp ? Math.max(0, Math.min(1, _)) : _;
return _ === 0 ? x0 : _ === 1 ? x2 : _ === 0.5 ? x1
: untransform((_ - 0.5) / (_ < 0.5 ? k10 : k21) + t1);
};

scale.domain = function(_) {
return arguments.length ? ([x0, x1, x2] = _, t0 = transform(x0 = +x0), t1 = transform(x1 = +x1), t2 = transform(x2 = +x2), k10 = t0 === t1 ? 0 : 0.5 / (t1 - t0), k21 = t1 === t2 ? 0 : 0.5 / (t2 - t1), s = t1 < t0 ? -1 : 1, scale) : [x0, x1, x2];
};
Expand Down Expand Up @@ -53,14 +60,14 @@ function transformer() {
return arguments.length ? (unknown = _, scale) : unknown;
};

return function(t) {
transform = t, t0 = t(x0), t1 = t(x1), t2 = t(x2), k10 = t0 === t1 ? 0 : 0.5 / (t1 - t0), k21 = t1 === t2 ? 0 : 0.5 / (t2 - t1), s = t1 < t0 ? -1 : 1;
return function(t, u) {
transform = t, t0 = t(x0), t1 = t(x1), t2 = t(x2), k10 = t0 === t1 ? 0 : 0.5 / (t1 - t0), k21 = t1 === t2 ? 0 : 0.5 / (t2 - t1), s = t1 < t0 ? -1 : 1, untransform = u;
return scale;
};
}

export default function diverging() {
var scale = linearish(transformer()(identity));
var scale = linearish(transformer()(identity, identity));

scale.copy = function() {
return copy(scale, diverging());
Expand Down
12 changes: 9 additions & 3 deletions src/sequential.js
Expand Up @@ -13,6 +13,7 @@ function transformer() {
t1,
k10,
transform,
untransform,
interpolator = identity,
clamp = false,
unknown;
Expand All @@ -21,6 +22,11 @@ function transformer() {
return isNaN(x = +x) ? unknown : interpolator(k10 === 0 ? 0.5 : (x = (transform(x) - t0) * k10, clamp ? Math.max(0, Math.min(1, x)) : x));
}

scale.invert = function(_) {
_ = clamp ? Math.max(0, Math.min(1, _)) : _;
return _ === 0 ? x0 : _ === 1 ? x1 : untransform(_ / k10 + t0);
};

scale.domain = function(_) {
return arguments.length ? ([x0, x1] = _, t0 = transform(x0 = +x0), t1 = transform(x1 = +x1), k10 = t0 === t1 ? 0 : 1 / (t1 - t0), scale) : [x0, x1];
};
Expand Down Expand Up @@ -48,8 +54,8 @@ function transformer() {
return arguments.length ? (unknown = _, scale) : unknown;
};

return function(t) {
transform = t, t0 = t(x0), t1 = t(x1), k10 = t0 === t1 ? 0 : 1 / (t1 - t0);
return function(t, u) {
transform = t, t0 = t(x0), t1 = t(x1), k10 = t0 === t1 ? 0 : 1 / (t1 - t0), untransform = u;
return scale;
};
}
Expand All @@ -63,7 +69,7 @@ export function copy(source, target) {
}

export default function sequential() {
var scale = linearish(transformer()(identity));
var scale = linearish(transformer()(identity, identity));

scale.copy = function() {
return copy(scale, sequential());
Expand Down
25 changes: 25 additions & 0 deletions test/diverging-test.js
Expand Up @@ -162,3 +162,28 @@ tape("scaleDiverging(range) sets the interpolator", function(test) {
test.deepEqual(s.range(), [1, 3, 10]);
test.end();
});

tape("scaleDiverging.invert(value) inverts interpolation fractions", function(test) {
var s = scale.scaleDiverging().domain([1,2,4]);
test.equal(s.invert(0), 1);
test.equal(s.invert(0.25), 1.5);
test.equal(s.invert(0.50), 2);
test.equal(s.invert(0.75), 3);
test.equal(s.invert(1), 4);
test.equal(s.invert(-0.5), 0);
test.equal(s.invert(1.5), 6);
test.end();
});

tape("scaleDivergingLog.invert(value) inverts interpolation fractions", function(test) {
var d = [1, 20, 100];
var s = scale.scaleDivergingLog().domain(d);
test.equal(s.invert(0), d[0]);
test.equal(s.invert(0.25), Math.exp(Math.log(d[0]) + 0.5 * (Math.log(d[1]) - Math.log(d[0]))));
test.equal(s.invert(0.50), d[1]);
test.equal(s.invert(0.75), Math.exp(Math.log(d[1]) + 0.5 * (Math.log(d[2]) - Math.log(d[1]))));
test.equal(s.invert(1), d[2]);
test.inDelta(s.invert(-0.5), Math.exp(Math.log(d[0]) - 1 * (Math.log(d[1]) - Math.log(d[0]))));
test.inDelta(s.invert(1.5), Math.exp(Math.log(d[1]) + 2 * (Math.log(d[2]) - Math.log(d[1]))));
test.end();
});
25 changes: 25 additions & 0 deletions test/sequential-test.js
Expand Up @@ -131,3 +131,28 @@ tape("scaleSequential(range) sets the interpolator", function(test) {
test.deepEqual(s.range(), [1, 3]);
test.end();
});

tape("scaleSequential.invert(value) inverts interpolation fractions", function(test) {
var s = scale.scaleSequential().domain([1,5]);
test.equal(s.invert(0), 1);
test.equal(s.invert(0.25), 2);
test.equal(s.invert(0.50), 3);
test.equal(s.invert(0.75), 4);
test.equal(s.invert(1), 5);
test.equal(s.invert(-1), -3);
test.equal(s.invert(2), 9);
test.end();
});

tape("scaleSequentialLog.invert(value) inverts interpolation fractions", function(test) {
var d = [1, 100];
var s = scale.scaleSequentialLog().domain(d);
test.equal(s.invert(0), d[0]);
test.equal(s.invert(0.25), Math.exp(Math.log(d[0]) + 0.25 * (Math.log(d[1]) - Math.log(d[0]))));
test.equal(s.invert(0.50), Math.exp(Math.log(d[0]) + 0.50 * (Math.log(d[1]) - Math.log(d[0]))));
test.equal(s.invert(0.75), Math.exp(Math.log(d[0]) + 0.75 * (Math.log(d[1]) - Math.log(d[0]))));
test.equal(s.invert(1), d[1]);
test.inDelta(s.invert(-1), d[0] / d[1]);
test.inDelta(s.invert(2), d[1] * d[1]);
test.end();
});