Skip to content

Commit

Permalink
Merge pull request #88 from d3/two
Browse files Browse the repository at this point in the history
Two
  • Loading branch information
Fil committed Aug 23, 2020
2 parents b1074db + 1d48123 commit 0ef66dc
Show file tree
Hide file tree
Showing 11 changed files with 193 additions and 70 deletions.
14 changes: 10 additions & 4 deletions README.md
Expand Up @@ -31,13 +31,13 @@ Note that the generic value interpolator detects not only nested objects and arr

## Installing

If you use NPM, `npm install d3-interpolate`. Otherwise, download the [latest release](https://github.com/d3/d3-interpolate/releases/latest). You can also load directly from [d3js.org](https://d3js.org), either as a [standalone library](https://d3js.org/d3-interpolate.v1.min.js) or as part of [D3](https://github.com/d3/d3). AMD, CommonJS, and vanilla environments are supported.
If you use NPM, `npm install d3-interpolate`. Otherwise, download the [latest release](https://github.com/d3/d3-interpolate/releases/latest). You can also load directly from [d3js.org](https://d3js.org), either as a [standalone library](https://d3js.org/d3-interpolate.v2.min.js) or as part of [D3](https://github.com/d3/d3). AMD, CommonJS, and vanilla environments are supported.

In vanilla, a `d3` global is exported. (If using [color interpolation](#color-spaces), also load [d3-color](https://github.com/d3/d3-color).)

```html
<script src="https://d3js.org/d3-color.v1.min.js"></script>
<script src="https://d3js.org/d3-interpolate.v1.min.js"></script>
<script src="https://d3js.org/d3-color.v2.min.js"></script>
<script src="https://d3js.org/d3-interpolate.v2.min.js"></script>
<script>
var interpolate = d3.interpolateRgb("steelblue", "brown");
Expand Down 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 Expand Up @@ -243,10 +247,12 @@ Returns a uniform nonrational B-spline interpolator through the specified array

### Piecewise

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

Returns a piecewise interpolator, composing interpolators for each adjacent pair of *values*. The returned interpolator maps *t* in [0, 1 / (*n* - 1)] to *interpolate*(*values*[0], *values*[1]), *t* in [1 / (*n* - 1), 2 / (*n* - 1)] to *interpolate*(*values*[1], *values*[2]), and so on, where *n* = *values*.length. In effect, this is a lightweight [linear scale](https://github.com/d3/d3-scale/blob/master/README.md#linear-scales). For example, to blend through red, green and blue:

```js
var interpolate = d3.piecewise(d3.interpolateRgb.gamma(2.2), ["red", "green", "blue"]);
```

If *interpolate* is not specified, defaults to [d3.interpolate](#interpolate).
7 changes: 5 additions & 2 deletions package.json
@@ -1,6 +1,9 @@
{
"name": "d3-interpolate",
"version": "1.4.0",
"version": "2.0.0-rc.1",
"publishConfig": {
"tag": "next"
},
"description": "Interpolate numbers, colors, strings, arrays, objects, whatever!",
"keywords": [
"d3",
Expand Down Expand Up @@ -34,7 +37,7 @@
"postpublish": "git push && git push --tags && cd ../d3.github.com && git pull && cp ../${npm_package_name}/dist/${npm_package_name}.js ${npm_package_name}.v${npm_package_version%%.*}.js && cp ../${npm_package_name}/dist/${npm_package_name}.min.js ${npm_package_name}.v${npm_package_version%%.*}.min.js && git add ${npm_package_name}.v${npm_package_version%%.*}.js ${npm_package_name}.v${npm_package_version%%.*}.min.js && git commit -m \"${npm_package_name} ${npm_package_version}\" && git push && cd - && zip -j dist/${npm_package_name}.zip -- LICENSE README.md dist/${npm_package_name}.js dist/${npm_package_name}.min.js"
},
"dependencies": {
"d3-color": "1"
"d3-color": ">=2.0.0-rc.1"
},
"sideEffects": false,
"devDependencies": {
Expand Down
8 changes: 7 additions & 1 deletion rollup.config.js
@@ -1,6 +1,11 @@
import {terser} from "rollup-plugin-terser";
import * as meta from "./package.json";

function onwarn(message, warn) {
if (message.code === "CIRCULAR_DEPENDENCY") return;
warn(message);
}

const config = {
input: "src/index.js",
external: Object.keys(meta.dependencies || {}).filter(key => /^d3-/.test(key)),
Expand All @@ -13,7 +18,8 @@ const config = {
banner: `// ${meta.homepage} v${meta.version} Copyright ${(new Date).getFullYear()} ${meta.author.name}`,
globals: Object.assign({}, ...Object.keys(meta.dependencies || {}).filter(key => /^d3-/.test(key)).map(key => ({[key]: "d3"})))
},
plugins: []
plugins: [],
onwarn
};

export default [
Expand Down
6 changes: 1 addition & 5 deletions src/constant.js
@@ -1,5 +1 @@
export default function(x) {
return function() {
return x;
};
}
export default x => () => x;
3 changes: 3 additions & 0 deletions src/piecewise.js
@@ -1,4 +1,7 @@
import {default as value} from "./value.js";

export default function piecewise(interpolate, values) {
if (values === undefined) values = interpolate, interpolate = value;
var i = 0, n = values.length - 1, v = values[0], I = new Array(n < 0 ? 0 : n);
while (i < n) I[i] = interpolate(v, v = values[++i]);
return function(t) {
Expand Down
15 changes: 4 additions & 11 deletions src/transform/parse.js
@@ -1,18 +1,11 @@
import decompose, {identity} from "./decompose.js";

var cssNode,
cssRoot,
cssView,
svgNode;
var svgNode;

/* eslint-disable no-undef */
export function parseCss(value) {
if (value === "none") return identity;
if (!cssNode) cssNode = document.createElement("DIV"), cssRoot = document.documentElement, cssView = document.defaultView;
cssNode.style.transform = value;
value = cssView.getComputedStyle(cssRoot.appendChild(cssNode), null).getPropertyValue("transform");
cssRoot.removeChild(cssNode);
value = value.slice(7, -1).split(",");
return decompose(+value[0], +value[1], +value[2], +value[3], +value[4], +value[5]);
const m = new (typeof DOMMatrix === "function" ? DOMMatrix : WebKitCSSMatrix)(value + "");
return m.isIdentity ? identity : decompose(m.a, m.b, m.c, m.d, m.e, m.f);
}

export function parseSvg(value) {
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;
}
38 changes: 38 additions & 0 deletions test/piecewise-test.js
@@ -0,0 +1,38 @@
var tape = require("tape"),
interpolate = require("../");

tape("piecewise(interpolate, values)(t) returns the expected values", function(test) {
var i = interpolate.piecewise(interpolate.interpolate, [0,2,10]);
test.strictEqual(i(-1), -4);
test.strictEqual(i(0), 0);
test.strictEqual(i(0.19), 0.76);
test.strictEqual(i(0.21), 0.84);
test.strictEqual(i(0.5), 2);
test.strictEqual(i(0.75), 6);
test.strictEqual(i(1), 10);
test.end();
});

tape("piecewise(values) uses the default interpolator", function(test) {
var i = interpolate.piecewise([0,2,10]);
test.strictEqual(i(-1), -4);
test.strictEqual(i(0), 0);
test.strictEqual(i(0.19), 0.76);
test.strictEqual(i(0.21), 0.84);
test.strictEqual(i(0.5), 2);
test.strictEqual(i(0.75), 6);
test.strictEqual(i(1), 10);
test.end();
});

tape("piecewise(values) uses the default interpolator/2", function(test) {
var i = interpolate.piecewise(["a0","a2","a10"]);
test.strictEqual(i(-1), "a-4");
test.strictEqual(i(0), "a0");
test.strictEqual(i(0.19), "a0.76");
test.strictEqual(i(0.21), "a0.84");
test.strictEqual(i(0.5), "a2");
test.strictEqual(i(0.75), "a6");
test.strictEqual(i(1), "a10");
test.end();
});
26 changes: 26 additions & 0 deletions test/transformCss-test.js
@@ -0,0 +1,26 @@
var tape = require("tape"),
interpolate = require("../");

/*
// see https://github.com/d3/d3-interpolate/pull/83
// and https://github.com/Automattic/node-canvas/issues/1313
global.DOMMatrix = require("Canvas").DOMMatrix;
tape("interpolateTransformCss(a, b) transforms as expected", function(test) {
test.equal(interpolate.interpolateTransformCss(
"translateY(12px) scale(2)",
"translateX(3em) rotate(5deg)"
)(0.5), "translate(24px, 6px) rotate(2.5deg) scale(1.5,1.5)");
test.deepEqual(interpolate.interpolateTransformCss(
"matrix(1.0, 2.0, 3.0, 4.0, 5.0, 6.0)",
"translate(3px,90px)"
)(0.5), "translate(4px, 48px) rotate(-58.282525588538995deg) skewX(-39.847576765616985deg) scale(-0.6180339887498949,0.9472135954999579)");
test.deepEqual(interpolate.interpolateTransformCss(
"skewX(-60)",
"skewX(60) translate(280,0)"
)(0.5), "translate(140, 0) skewX(0)");
test.end();
});
*/
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 0ef66dc

Please sign in to comment.