addon/utils/shadow/line-interpolation/monotone.js
const {abs} = Math;
/**
* Returns an array of arguemnts for commands
* if it has 2 elements, moveTo / lineTo
* if it has 4 elements, smoothCurveTo
* if it has 6 elements, bezierCurveTo
*/
export default function monotone(points) {
if (points.length < 3) {
return points;
}
return [points[0]].concat(d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points)));
}
/*
The following code is lovelying borrowed from d3.js ❤️
https://github.com/mbostock/d3/blob/78e0a4bb81a6565bf61e3ef1b898ef8377478766/src/svg/line.js
*/
// Interpolates the given points using Fritsch-Carlson Monotone cubic Hermite
// interpolation. Returns an array of tangent vectors. For details, see
// http://en.wikipedia.org/wiki/Monotone_cubic_interpolation
function d3_svg_lineMonotoneTangents(points) {
var tangents = [],
d,
a,
b,
s,
m = d3_svg_lineFiniteDifferences(points),
i = -1,
j = points.length - 1;
// The first two steps are done by computing finite-differences:
// 1. Compute the slopes of the secant lines between successive points.
// 2. Initialize the tangents at every point as the average of the secants.
// Then, for each segment…
while (++i < j) {
d = d3_svg_lineSlope(points[i], points[i + 1]);
// 3. If two successive yk = y{k + 1} are equal (i.e., d is zero), then set
// mk = m{k + 1} = 0 as the spline connecting these points must be flat to
// preserve monotonicity. Ignore step 4 and 5 for those k.
// ε = 1e-6
if (abs(d) < 0.000001) {
m[i] = m[i + 1] = 0;
} else {
// 4. Let ak = mk / dk and bk = m{k + 1} / dk.
a = m[i] / d;
b = m[i + 1] / d;
// 5. Prevent overshoot and ensure monotonicity by restricting the
// magnitude of vector <ak, bk> to a circle of radius 3.
s = a * a + b * b;
if (s > 9) {
s = d * 3 / Math.sqrt(s);
m[i] = s * a;
m[i + 1] = s * b;
}
}
}
// Compute the normalized tangent vector from the slopes. Note that if x is
// not monotonic, it's possible that the slope will be infinite, so we protect
// against NaN by setting the coordinate to zero.
i = -1; while (++i <= j) {
s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) / (6 * (1 + m[i] * m[i]));
tangents.push([s || 0, m[i] * s || 0]);
}
return tangents;
}
// Hermite spline construction; generates "C" commands.
function d3_svg_lineHermite(points, tangents) {
if (tangents.length < 1 || (points.length !== tangents.length && points.length !== tangents.length + 2)) {
return points;
}
var quad = points.length !== tangents.length,
commands = [],
p0 = points[0],
p = points[1],
t0 = tangents[0],
t = t0,
pi = 1;
if (quad) {
commands.push([
p[0] - t0[0] * 2 / 3,
p[1] - t0[1] * 2 / 3,
p[0],
p[1]
]);
p0 = points[1];
pi = 2;
}
if (tangents.length > 1) {
t = tangents[1];
p = points[pi];
pi++;
commands.push([
p0[0] + t0[0],
p0[1] + t0[1],
p[0] - t[0],
p[1] - t[1],
p[0],
p[1]
]);
for (var i = 2; i < tangents.length; i++, pi++) {
p = points[pi];
t = tangents[i];
let lt = tangents[i - 1];
let lp = points[i - 1];
commands.push([
lp[0] + lt[0], // Add the last tangent but reflected
lp[1] + lt[1],
p[0] - t[0],
p[1] - t[1],
p[0],
p[1]
]);
}
}
if (quad) {
var lp = points[pi];
commands.push([
p[0] + t[0] * 2 / 3,
p[1] + t[1] * 2 / 3,
lp[0],
lp[1]
]);
}
return commands;
}
// Computes the slope from points p0 to p1.
function d3_svg_lineSlope(p0, p1) {
return (p1[1] - p0[1]) / (p1[0] - p0[0]);
}
function d3_svg_lineFiniteDifferences(points) {
var i = 0,
j = points.length - 1,
m = [],
p0 = points[0],
p1 = points[1],
d = m[0] = d3_svg_lineSlope(p0, p1);
while (++i < j) {
m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2;
}
m[i] = d;
return m;
}
function d3_svg_lineFiniteDifferences(points) {
var i = 0,
j = points.length - 1,
m = [],
p0 = points[0],
p1 = points[1],
d = m[0] = d3_svg_lineSlope(p0, p1);
while (++i < j) {
m[i] = (d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1]))) / 2;
}
m[i] = d;
return m;
}