diff --git a/.gitignore b/.gitignore index 8abcf6a..7d1118c 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ dist/ node_modules npm-debug.log +.idea diff --git a/README.md b/README.md index c38a90c..c594b2d 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,26 @@ Returns a uniform nonrational B-spline interpolator through the specified array Returns a uniform nonrational B-spline interpolator through the specified array of *values*, which must be numbers. The control points are implicitly repeated such that the resulting one-dimensional spline has cyclical C² continuity when repeated around *t* in [0,1]. See also [d3.curveBasisClosed](https://github.com/d3/d3-shape/blob/master/README.md#curveBasisClosed). +# d3.interpolateCardinal(values) · [Source](https://github.com/d3/d3-interpolate/blob/master/src/cardinal.js) + +Returns interpolator based on cubic Cardinal spline. + +# d3.interpolateCatmullRom(values) · [Source](https://github.com/d3/d3-interpolate/blob/master/src/catmullRom.js) + +Returns interpolator based on a cubic Catmull–Rom spline. + +# d3.interpolateMonotoneX(values) · [Source](https://github.com/d3/d3-interpolate/blob/master/src/monotoneX.js) + +Returns interpolator based on MonotoneX spline. + +# d3.interpolateFromCurve(values, curve, epsilon, samples) · [Source](https://github.com/d3/d3-interpolate/blob/master/src/fromCurve.js) + +Returns interpolator based on d3.curve function. + +```js +var interpolator = d3.interpolateFromCurve([1,2,7,2], d3.curveMonotoneX, 0.00001, 100); +``` + ### Piecewise # d3.piecewise(interpolate, values) · [Source](https://github.com/d3/d3-interpolate/blob/master/src/piecewise.js), [Examples](https://observablehq.com/@d3/d3-piecewise) diff --git a/package.json b/package.json index ccc3a0a..a9e0a6d 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,9 @@ "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": "1", + "d3-shape": "1", + "d3-array": "2" }, "devDependencies": { "eslint": "5", diff --git a/src/cardinal.js b/src/cardinal.js new file mode 100644 index 0000000..fd4fa31 --- /dev/null +++ b/src/cardinal.js @@ -0,0 +1,6 @@ +import {default as fromCurve} from "./fromCurve"; +import {curveCardinal} from "d3-shape"; + +export default function(values) { + return fromCurve(values, curveCardinal) +} diff --git a/src/catmullRom.js b/src/catmullRom.js new file mode 100644 index 0000000..580ae0c --- /dev/null +++ b/src/catmullRom.js @@ -0,0 +1,6 @@ +import {default as fromCurve} from "./fromCurve"; +import {curveCatmullRom} from "d3-shape"; + +export default function(values) { + return fromCurve(values, curveCatmullRom) +} diff --git a/src/fromCurve.js b/src/fromCurve.js new file mode 100644 index 0000000..927530b --- /dev/null +++ b/src/fromCurve.js @@ -0,0 +1,69 @@ +import {line as shapeLine} from "d3-shape"; +import {range as arrayRange} from "d3-array"; + +function curvePolator(points, curve, epsilon, samples) { + const path = shapeLine().curve(curve)(points); + + return svgPathInterpolator(path, epsilon, samples); +} + +function svgPathInterpolator(path, epsilon, samples) { + // Create detached SVG path + path = path || "M0,0L1,1"; + + const area = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + area.innerHTML = ``; + const svgpath = area.querySelector('path'); + svgpath.setAttribute('d', path); + + // Calculate lengths and max points + const totalLength = svgpath.getTotalLength(); + const minPoint = svgpath.getPointAtLength(0); + const maxPoint = svgpath.getPointAtLength(totalLength); + let reverse = maxPoint.x < minPoint.x; + const range = reverse ? [maxPoint, minPoint] : [minPoint, maxPoint]; + reverse = reverse ? -1 : 1; + + // Return function + return function(x) { + const targetX = x === 0 ? 0 : x || minPoint.x; // Check for 0 and null/undefined + if (targetX < range[0].x) return range[0]; // Clamp + if (targetX > range[1].x) return range[1]; + + function estimateLength(l, mn, mx) { + let delta = svgpath.getPointAtLength(l).x - targetX; + let nextDelta = 0; + let iter = 0; + + while (Math.abs(delta) > epsilon && iter < samples) { + if (iter > samples) return false; + iter++; + + if (reverse * delta < 0) { + mn = l; + l = (l + mx) / 2; + } else { + mx = l; + l = (mn + l) / 2; + } + nextDelta = svgpath.getPointAtLength(l).x - targetX; + + delta = nextDelta; + } + + return l; + } + + const estimatedLength = estimateLength(totalLength / 2, 0, totalLength); + + return svgpath.getPointAtLength(estimatedLength).y; + } +} + +export default function(values, curve, epsilon = 0.00001, samples = 100) { + const length = values.length; + const xrange = arrayRange(length).map(function(d, i) { return i * (1 / (length - 1)); }); + const points = values.map((v, i) => [xrange[i], v]); + + return curvePolator(points, curve, epsilon, samples); +} \ No newline at end of file diff --git a/src/index.js b/src/index.js index 8f545f6..3393fc7 100644 --- a/src/index.js +++ b/src/index.js @@ -8,6 +8,10 @@ export {default as interpolateHue} from "./hue"; export {default as interpolateNumber} from "./number"; export {default as interpolateObject} from "./object"; export {default as interpolateRound} from "./round"; +export {default as interpolateFromCurve} from "./fromCurve"; +export {default as interpolateCardinal} from "./cardinal"; +export {default as interpolateCatmullRom} from "./catmullRom"; +export {default as interpolateMonotoneX} from "./monotoneX"; export {default as interpolateString} from "./string"; export {interpolateTransformCss, interpolateTransformSvg} from "./transform/index"; export {default as interpolateZoom} from "./zoom"; diff --git a/src/monotoneX.js b/src/monotoneX.js new file mode 100644 index 0000000..9969a2b --- /dev/null +++ b/src/monotoneX.js @@ -0,0 +1,6 @@ +import {default as fromCurve} from "./fromCurve"; +import {curveMonotoneX} from "d3-shape"; + +export default function(values) { + return fromCurve(values, curveMonotoneX) +}