Skip to content

Commit

Permalink
Merge branch 'zoom-rho' into two
Browse files Browse the repository at this point in the history
  • Loading branch information
Fil committed Jul 10, 2020
2 parents 4b1156f + 1954d05 commit 77b7f74
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 47 deletions.
4 changes: 4 additions & 0 deletions README.md
Expand Up @@ -133,6 +133,10 @@ Returns an interpolator between the two views *a* and *b* of a two-dimensional p

The returned interpolator exposes a *duration* property which encodes the recommended transition duration in milliseconds. This duration is based on the path length of the curved trajectory through *x,y* space. If you want a slower or faster transition, multiply this by an arbitrary scale factor (<i>V</i> as described in the original paper).

<a name="interpolate_rho" href="#interpolate_rho">#</a> *interpolateZoom*.<b>rho</b>(<i>rho</i>) · [Source](https://github.com/d3/d3-interpolate/blob/master/src/zoom.js)<!-- , [Examples](https://observablehq.com/@d3/interpolatezoom-rho) -->

Given a [zoom interpolator](#interpolateZoom), returns a new zoom interpolator using the specified curvature *rho*. When *rho* is close to 0, the interpolator is almost linear. The default curvature is sqrt(2).

<a name="interpolateDiscrete" href="#interpolateDiscrete">#</a> d3.<b>interpolateDiscrete</b>(<i>values</i>) · [Source](https://github.com/d3/d3-interpolate/blob/master/src/discrete.js), [Examples](https://observablehq.com/@d3/d3-interpolatediscrete)

Returns a discrete interpolator for the given array of *values*. The returned interpolator maps *t* in [0, 1 / *n*) to *values*[0], *t* in [1 / *n*, 2 / *n*) to *values*[1], and so on, where *n* = *values*.length. In effect, this is a lightweight [quantize scale](https://github.com/d3/d3-scale/blob/master/README.md#quantize-scales) with a fixed domain of [0, 1].
Expand Down
95 changes: 51 additions & 44 deletions src/zoom.js
@@ -1,7 +1,4 @@
var rho = Math.SQRT2,
rho2 = 2,
rho4 = 4,
epsilon2 = 1e-12;
var epsilon2 = 1e-12;

function cosh(x) {
return ((x = Math.exp(x)) + 1 / x) / 2;
Expand All @@ -15,50 +12,60 @@ function tanh(x) {
return ((x = Math.exp(2 * x)) - 1) / (x + 1);
}

// p0 = [ux0, uy0, w0]
// p1 = [ux1, uy1, w1]
export default function(p0, p1) {
var ux0 = p0[0], uy0 = p0[1], w0 = p0[2],
ux1 = p1[0], uy1 = p1[1], w1 = p1[2],
dx = ux1 - ux0,
dy = uy1 - uy0,
d2 = dx * dx + dy * dy,
i,
S;
export default (function zoomRho(rho, rho2, rho4) {

// Special case for u0 ≅ u1.
if (d2 < epsilon2) {
S = Math.log(w1 / w0) / rho;
i = function(t) {
return [
ux0 + t * dx,
uy0 + t * dy,
w0 * Math.exp(rho * t * S)
];
// p0 = [ux0, uy0, w0]
// p1 = [ux1, uy1, w1]
function zoom(p0, p1) {
var ux0 = p0[0], uy0 = p0[1], w0 = p0[2],
ux1 = p1[0], uy1 = p1[1], w1 = p1[2],
dx = ux1 - ux0,
dy = uy1 - uy0,
d2 = dx * dx + dy * dy,
i,
S;

// Special case for u0 ≅ u1.
if (d2 < epsilon2) {
S = Math.log(w1 / w0) / rho;
i = function(t) {
return [
ux0 + t * dx,
uy0 + t * dy,
w0 * Math.exp(rho * t * S)
];
}
}
}

// General case.
else {
var d1 = Math.sqrt(d2),
b0 = (w1 * w1 - w0 * w0 + rho4 * d2) / (2 * w0 * rho2 * d1),
b1 = (w1 * w1 - w0 * w0 - rho4 * d2) / (2 * w1 * rho2 * d1),
r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0),
r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1);
S = (r1 - r0) / rho;
i = function(t) {
var s = t * S,
coshr0 = cosh(r0),
u = w0 / (rho2 * d1) * (coshr0 * tanh(rho * s + r0) - sinh(r0));
return [
ux0 + u * dx,
uy0 + u * dy,
w0 * coshr0 / cosh(rho * s + r0)
];
// General case.
else {
var d1 = Math.sqrt(d2),
b0 = (w1 * w1 - w0 * w0 + rho4 * d2) / (2 * w0 * rho2 * d1),
b1 = (w1 * w1 - w0 * w0 - rho4 * d2) / (2 * w1 * rho2 * d1),
r0 = Math.log(Math.sqrt(b0 * b0 + 1) - b0),
r1 = Math.log(Math.sqrt(b1 * b1 + 1) - b1);
S = (r1 - r0) / rho;
i = function(t) {
var s = t * S,
coshr0 = cosh(r0),
u = w0 / (rho2 * d1) * (coshr0 * tanh(rho * s + r0) - sinh(r0));
return [
ux0 + u * dx,
uy0 + u * dy,
w0 * coshr0 / cosh(rho * s + r0)
];
}
}

i.duration = S * 1000 * rho / Math.SQRT2;

return i;
}

i.duration = S * 1000;
zoom.rho = function(_) {
var _1 = Math.max(1e-3, +_), _2 = _1 * _1, _4 = _2 * _2;
return zoomRho(_1, _2, _4);
};

return i;
}
return zoom;
})(Math.SQRT2, 2, 4);
22 changes: 19 additions & 3 deletions test/inDelta.js
@@ -1,10 +1,26 @@
var tape = require("tape");

tape.Test.prototype.inDelta = function(actual, expected) {
this._assert(expected - 1e-6 < actual && actual < expected + 1e-6, {
message: "should be in delta",
tape.Test.prototype.inDelta = function(actual, expected, delta) {
delta = delta || 1e-6;
this._assert(inDelta(actual, expected, delta), {
message: "should be in delta " + delta,
operator: "inDelta",
actual: actual,
expected: expected
});
};

function inDelta(actual, expected, delta) {
return (Array.isArray(expected) ? inDeltaArray : inDeltaNumber)(actual, expected, delta);
}

function inDeltaArray(actual, expected, delta) {
var n = expected.length, i = -1;
if (actual.length !== n) return false;
while (++i < n) if (!inDelta(actual[i], expected[i], delta)) return false;
return true;
}

function inDeltaNumber(actual, expected, delta) {
return actual >= expected - delta && actual <= expected + delta;
}
29 changes: 29 additions & 0 deletions test/zoom-test.js
Expand Up @@ -5,3 +5,32 @@ tape("interpolateZoom(a, b) handles nearly-coincident points", function(test) {
test.deepEqual(interpolate.interpolateZoom([324.68721096803614, 59.43501602433761, 1.8827137399562621], [324.6872108946794, 59.43501601062763, 7.399052110984391])(0.5), [324.68721093135775, 59.43501601748262, 3.7323313186268305]);
test.end();
});

tape("interpolateZoom returns the expected duration", function(test) {
test.inDelta(interpolate.interpolateZoom([0, 0, 1], [0, 0, 1.1]).duration, 67, 1);
test.inDelta(interpolate.interpolateZoom([0, 0, 1], [0, 0, 2]).duration, 490, 1);
test.inDelta(interpolate.interpolateZoom([0, 0, 1], [10, 0, 8]).duration, 2872.5, 1);
test.end();
});

tape("interpolateZoom parameter rho() defaults to sqrt(2)", function(test) {
test.inDelta(
interpolate.interpolateZoom([0,0,1], [10, 10, 5])(0.5),
interpolate.interpolateZoom.rho(Math.sqrt(2))([0,0,1], [10, 10, 5])(0.5),
);
test.end();
});

tape("interpolateZoom.rho(0) is (almost) linear", function(test) {
const interp = interpolate.interpolateZoom.rho(0)([0, 0, 1], [10, 0, 8]);
test.inDelta(interp(0.5), [1.111, 0, Math.sqrt(8)], 1e-3);
test.equal(Math.round(interp.duration), 1470);
test.end();
});

tape("interpolateZoom parameter rho(2) has a high curvature and takes more time", function(test) {
const interp = interpolate.interpolateZoom.rho(2)([0, 0, 1], [10, 0, 8]);
test.inDelta(interp(0.5), [1.111, 0, 12.885], 1e-3);
test.equal(Math.round(interp.duration), 3775);
test.end();
});

0 comments on commit 77b7f74

Please sign in to comment.