diff --git a/README.md b/README.md index c6522d0..d5571dd 100644 --- a/README.md +++ b/README.md @@ -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)`. diff --git a/src/interpolate/catmull-rom.js b/src/interpolate/catmull-rom.js new file mode 100644 index 0000000..c386fc4 --- /dev/null +++ b/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; diff --git a/src/line.js b/src/line.js index 2fa5db4..36299b8 100644 --- a/src/line.js +++ b/src/line.js @@ -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"; @@ -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; }