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

Two #88

Merged
merged 19 commits into from Aug 23, 2020
Merged

Two #88

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
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();
});