Skip to content

Commit

Permalink
Fix #18 - Catmull–Rom spline interpolation!
Browse files Browse the repository at this point in the history
  • Loading branch information
mbostock committed Nov 13, 2015
1 parent 4a695bb commit 93fba29
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 2 deletions.
4 changes: 2 additions & 2 deletions README.md
Expand Up @@ -12,6 +12,6 @@ If you use NPM, `npm install d3-shape`. Otherwise, download the [latest release]

## Changes from D3 3.x:

* The behavior of the cardinal interpolation tension parameter has been standardized. The default tension is now 0, not 0.7.
* The behavior of Cardinal interpolation tension has been fixed. The default tension is now 0 (corresponding to a uniform Catmull–Rom spline), not 0.7; the new value of 0 is equivalent to an old value of 2 / 3, so the default behavior is only slightly changed.

* To specify cardinal interpolation tension *t*, use `line.interpolate("cardinal", t)` instead of `line.interpolate("cardinal").tension(t)`.
* To specify a Cardinal interpolation tension of *t*, use `line.interpolate("cardinal", t)` instead of `line.interpolate("cardinal").tension(t)`.
95 changes: 95 additions & 0 deletions src/interpolate/catmull-rom.js
@@ -0,0 +1,95 @@
// TODO Check if n or m is zero, and avoid NaN.
// n is zero if (x0,y0) and (x1,y1) are coincident.
// m is zero if (x2,y2) and (x3,y3) are coincident.

function catmullRom(alpha) {
return function(context) {
return new CatmullRom(context, alpha);
};
}

function CatmullRom(context, alpha) {
this._context = context;
this._alpha2 = (this._alpha = alpha == null ? 0 : +alpha) / 2;
}

CatmullRom.prototype = {
lineStart: function() {
this._x0 = this._x1 = this._x2 =
this._y0 = this._y1 = this._y2 =
this._l01_a = this._l12_a = this._l23_a =
this._l01_2a = this._l12_2a = this._l23_2a = NaN;
this._state = 0;
},
lineEnd: function() {
switch (this._state) {
case 1: this._context.closePath(); break;
case 2: this._context.lineTo(this._x2, this._y2); break;
case 3: {
var a = 2 * this._l01_2a + 3 * this._l01_a * this._l12_a + this._l12_2a,
n = 3 * this._l01_a * (this._l01_a + this._l12_a);
this._context.bezierCurveTo(
(this._x1 * a - this._x0 * this._l12_2a + this._x2 * this._l01_2a) / n,
(this._y1 * a - this._y0 * this._l12_2a + this._y2 * this._l01_2a) / n,
this._x2,
this._y2,
this._x2,
this._y2
);
break;
}
}
},
point: function(x, y) {
x = +x, y = +y;

if (this._state) {
var x23 = this._x2 - x,
y23 = this._y2 - y,
l23_2 = x23 * x23 + y23 * y23;
this._l23_a = Math.pow(l23_2, this._alpha2);
this._l23_2a = Math.pow(l23_2, this._alpha);
}

switch (this._state) {
case 0: this._state = 1; this._context.moveTo(x, y); break;
case 1: this._state = 2; break;
case 2: {
var b = 2 * this._l23_2a + 3 * this._l23_a * this._l12_a + this._l12_2a,
m = 3 * this._l23_a * (this._l23_a + this._l12_a);
this._state = 3;
this._context.bezierCurveTo(
this._x1,
this._y1,
(this._x2 * b + this._x1 * this._l23_2a - x * this._l12_2a) / m,
(this._y2 * b + this._y1 * this._l23_2a - y * this._l12_2a) / m,
this._x2,
this._y2
);
break;
}
default: {
var a = 2 * this._l01_2a + 3 * this._l01_a * this._l12_a + this._l12_2a,
b = 2 * this._l23_2a + 3 * this._l23_a * this._l12_a + this._l12_2a,
n = 3 * this._l01_a * (this._l01_a + this._l12_a),
m = 3 * this._l23_a * (this._l23_a + this._l12_a);
this._context.bezierCurveTo(
(this._x1 * a - this._x0 * this._l12_2a + this._x2 * this._l01_2a) / n,
(this._y1 * a - this._y0 * this._l12_2a + this._y2 * this._l01_2a) / n,
(this._x2 * b + this._x1 * this._l23_2a - x * this._l12_2a) / m,
(this._y2 * b + this._y1 * this._l23_2a - y * this._l12_2a) / m,
this._x2,
this._y2
);
break;
}
}

this._l01_a = this._l12_a, this._l12_a = this._l23_a;
this._l01_2a = this._l12_2a, this._l12_2a = this._l23_2a;
this._x0 = this._x1, this._x1 = this._x2, this._x2 = x;
this._y0 = this._y1, this._y1 = this._y2, this._y2 = y;
}
};

export default catmullRom;
2 changes: 2 additions & 0 deletions src/line.js
Expand Up @@ -4,6 +4,7 @@ import basisOpen from "./interpolate/basis-open";
import cardinal from "./interpolate/cardinal";
import cardinalClosed from "./interpolate/cardinal-closed";
import cardinalOpen from "./interpolate/cardinal-open";
import catmullRom from "./interpolate/catmull-rom";
import cubic from "./interpolate/cubic";
import linear from "./interpolate/linear";
import linearClosed from "./interpolate/linear-closed";
Expand Down Expand Up @@ -88,6 +89,7 @@ export default function() {
case "cardinal": interpolate = cardinal(a); break;
case "cardinal-open": interpolate = cardinalOpen(a); break;
case "cardinal-closed": interpolate = cardinalClosed(a); break;
case "catmull-rom": interpolate = catmullRom(a); break;
case "cubic": interpolate = cubic; break;
default: interpolate = linear; break;
}
Expand Down

0 comments on commit 93fba29

Please sign in to comment.