166 lines
3.4 KiB
JavaScript
166 lines
3.4 KiB
JavaScript
'use strict';
|
|
|
|
/**
|
|
* Convert a series of points to a monotone cubic spline
|
|
* Algorithm based on https://github.com/mbostock/d3
|
|
* https://github.com/yr/monotone-cubic-spline
|
|
* @copyright Yr
|
|
* @license MIT
|
|
*/
|
|
|
|
var ε = 1e-6;
|
|
|
|
module.exports = {
|
|
/**
|
|
* Convert 'points' to bezier
|
|
* @param {Array} points
|
|
* @returns {Array}
|
|
*/
|
|
points: function points(_points) {
|
|
var tgts = tangents(_points);
|
|
|
|
var p = _points[1];
|
|
var p0 = _points[0];
|
|
var pts = [];
|
|
var t = tgts[1];
|
|
var t0 = tgts[0];
|
|
|
|
// Add starting 'M' and 'C' points
|
|
pts.push(p0, [p0[0] + t0[0], p0[1] + t0[1], p[0] - t[0], p[1] - t[1], p[0], p[1]]);
|
|
|
|
// Add 'S' points
|
|
for (var i = 2, n = tgts.length; i < n; i++) {
|
|
var _p = _points[i];
|
|
var _t = tgts[i];
|
|
|
|
pts.push([_p[0] - _t[0], _p[1] - _t[1], _p[0], _p[1]]);
|
|
}
|
|
|
|
return pts;
|
|
},
|
|
|
|
|
|
/**
|
|
* Slice out a segment of 'points'
|
|
* @param {Array} points
|
|
* @param {Number} start
|
|
* @param {Number} end
|
|
* @returns {Array}
|
|
*/
|
|
slice: function slice(points, start, end) {
|
|
var pts = points.slice(start, end);
|
|
|
|
if (start) {
|
|
// Add additional 'C' points
|
|
if (pts[1].length < 6) {
|
|
var n = pts[0].length;
|
|
|
|
pts[1] = [pts[0][n - 2] * 2 - pts[0][n - 4], pts[0][n - 1] * 2 - pts[0][n - 3]].concat(pts[1]);
|
|
}
|
|
// Remove control points for 'M'
|
|
pts[0] = pts[0].slice(-2);
|
|
}
|
|
|
|
return pts;
|
|
},
|
|
|
|
|
|
/**
|
|
* Convert 'points' to svg path
|
|
* @param {Array} points
|
|
* @returns {String}
|
|
*/
|
|
svgPath: function svgPath(points) {
|
|
var p = '';
|
|
|
|
for (var i = 0; i < points.length; i++) {
|
|
var point = points[i];
|
|
var n = point.length;
|
|
|
|
if (!i) {
|
|
p += 'M' + point[n - 2] + ' ' + point[n - 1];
|
|
} else if (n > 4) {
|
|
p += 'C' + point[0] + ', ' + point[1];
|
|
p += ', ' + point[2] + ', ' + point[3];
|
|
p += ', ' + point[4] + ', ' + point[5];
|
|
} else {
|
|
p += 'S' + point[0] + ', ' + point[1];
|
|
p += ', ' + point[2] + ', ' + point[3];
|
|
}
|
|
}
|
|
|
|
return p;
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Generate tangents for 'points'
|
|
* @param {Array} points
|
|
* @returns {Array}
|
|
*/
|
|
function tangents(points) {
|
|
var m = finiteDifferences(points);
|
|
var n = points.length - 1;
|
|
|
|
var tgts = [];
|
|
var a = void 0,
|
|
b = void 0,
|
|
d = void 0,
|
|
s = void 0;
|
|
|
|
for (var i = 0; i < n; i++) {
|
|
d = slope(points[i], points[i + 1]);
|
|
|
|
if (Math.abs(d) < ε) {
|
|
m[i] = m[i + 1] = 0;
|
|
} else {
|
|
a = m[i] / d;
|
|
b = m[i + 1] / d;
|
|
s = a * a + b * b;
|
|
if (s > 9) {
|
|
s = d * 3 / Math.sqrt(s);
|
|
m[i] = s * a;
|
|
m[i + 1] = s * b;
|
|
}
|
|
}
|
|
}
|
|
|
|
for (var _i = 0; _i <= n; _i++) {
|
|
s = (points[Math.min(n, _i + 1)][0] - points[Math.max(0, _i - 1)][0]) / (6 * (1 + m[_i] * m[_i]));
|
|
tgts.push([s || 0, m[_i] * s || 0]);
|
|
}
|
|
|
|
return tgts;
|
|
}
|
|
|
|
/**
|
|
* Compute slope from point 'p0' to 'p1'
|
|
* @param {Array} p0
|
|
* @param {Array} p1
|
|
* @returns {Number}
|
|
*/
|
|
function slope(p0, p1) {
|
|
return (p1[1] - p0[1]) / (p1[0] - p0[0]);
|
|
}
|
|
|
|
/**
|
|
* Compute three-point differences for 'points'
|
|
* @param {Array} points
|
|
* @returns {Array}
|
|
*/
|
|
function finiteDifferences(points) {
|
|
var m = [];
|
|
var p0 = points[0];
|
|
var p1 = points[1];
|
|
var d = m[0] = slope(p0, p1);
|
|
var i = 1;
|
|
|
|
for (var n = points.length - 1; i < n; i++) {
|
|
p0 = p1;
|
|
p1 = points[i + 1];
|
|
m[i] = (d + (d = slope(p0, p1))) * 0.5;
|
|
}
|
|
m[i] = d;
|
|
|
|
return m;
|
|
} |