import "../arrays/map"; import "../core/functor"; import "../core/identity"; import "../core/true"; import "../geom/point"; import "../math/abs"; import "../math/trigonometry"; import "svg"; function d3_svg_line(projection) { var x = d3_geom_pointX, y = d3_geom_pointY, defined = d3_true, interpolate = d3_svg_lineLinear, interpolateKey = interpolate.key, tension = 0.7; function line(data) { var segments = [], points = [], i = -1, n = data.length, d, fx = d3_functor(x), fy = d3_functor(y); function segment() { segments.push("M", interpolate(projection(points), tension)); } while (++i < n) { if (defined.call(this, d = data[i], i)) { points.push([+fx.call(this, d, i), +fy.call(this, d, i)]); } else if (points.length) { segment(); points = []; } } if (points.length) segment(); return segments.length ? segments.join("") : null; } line.x = function(_) { if (!arguments.length) return x; x = _; return line; }; line.y = function(_) { if (!arguments.length) return y; y = _; return line; }; line.defined = function(_) { if (!arguments.length) return defined; defined = _; return line; }; line.interpolate = function(_) { if (!arguments.length) return interpolateKey; if (typeof _ === "function") interpolateKey = interpolate = _; else interpolateKey = (interpolate = d3_svg_lineInterpolators.get(_) || d3_svg_lineLinear).key; return line; }; line.tension = function(_) { if (!arguments.length) return tension; tension = _; return line; }; return line; } d3.svg.line = function() { return d3_svg_line(d3_identity); }; // The various interpolators supported by the `line` class. var d3_svg_lineInterpolators = d3.map({ "linear": d3_svg_lineLinear, "linear-closed": d3_svg_lineLinearClosed, "step": d3_svg_lineStep, "step-before": d3_svg_lineStepBefore, "step-after": d3_svg_lineStepAfter, "basis": d3_svg_lineBasis, "basis-open": d3_svg_lineBasisOpen, "basis-closed": d3_svg_lineBasisClosed, "bundle": d3_svg_lineBundle, "cardinal": d3_svg_lineCardinal, "cardinal-open": d3_svg_lineCardinalOpen, "cardinal-closed": d3_svg_lineCardinalClosed, "monotone": d3_svg_lineMonotone }); d3_svg_lineInterpolators.forEach(function(key, value) { value.key = key; value.closed = /-closed$/.test(key); }); // Linear interpolation; generates "L" commands. function d3_svg_lineLinear(points) { return points.length > 1 ? points.join("L") : points + "Z"; } function d3_svg_lineLinearClosed(points) { return points.join("L") + "Z"; } // Step interpolation; generates "H" and "V" commands. function d3_svg_lineStep(points) { var i = 0, n = points.length, p = points[0], path = [p[0], ",", p[1]]; while (++i < n) path.push("H", (p[0] + (p = points[i])[0]) / 2, "V", p[1]); if (n > 1) path.push("H", p[0]); return path.join(""); } // Step interpolation; generates "H" and "V" commands. function d3_svg_lineStepBefore(points) { var i = 0, n = points.length, p = points[0], path = [p[0], ",", p[1]]; while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]); return path.join(""); } // Step interpolation; generates "H" and "V" commands. function d3_svg_lineStepAfter(points) { var i = 0, n = points.length, p = points[0], path = [p[0], ",", p[1]]; while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]); return path.join(""); } // Open cardinal spline interpolation; generates "C" commands. function d3_svg_lineCardinalOpen(points, tension) { return points.length < 4 ? d3_svg_lineLinear(points) : points[1] + d3_svg_lineHermite(points.slice(1, -1), d3_svg_lineCardinalTangents(points, tension)); } // Closed cardinal spline interpolation; generates "C" commands. function d3_svg_lineCardinalClosed(points, tension) { return points.length < 3 ? d3_svg_lineLinearClosed(points) : points[0] + d3_svg_lineHermite((points.push(points[0]), points), d3_svg_lineCardinalTangents([points[points.length - 2]] .concat(points, [points[1]]), tension)); } // Cardinal spline interpolation; generates "C" commands. function d3_svg_lineCardinal(points, tension) { return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineCardinalTangents(points, tension)); } // 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 d3_svg_lineLinear(points); } var quad = points.length != tangents.length, path = "", p0 = points[0], p = points[1], t0 = tangents[0], t = t0, pi = 1; if (quad) { path += "Q" + (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++; path += "C" + (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]; path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) + "," + p[0] + "," + p[1]; } } if (quad) { var lp = points[pi]; path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) + "," + lp[0] + "," + lp[1]; } return path; } // Generates tangents for a cardinal spline. function d3_svg_lineCardinalTangents(points, tension) { var tangents = [], a = (1 - tension) / 2, p0, p1 = points[0], p2 = points[1], i = 1, n = points.length; while (++i < n) { p0 = p1; p1 = p2; p2 = points[i]; tangents.push([a * (p2[0] - p0[0]), a * (p2[1] - p0[1])]); } return tangents; } // B-spline interpolation; generates "C" commands. function d3_svg_lineBasis(points) { if (points.length < 3) return d3_svg_lineLinear(points); var i = 1, n = points.length, pi = points[0], x0 = pi[0], y0 = pi[1], px = [x0, x0, x0, (pi = points[1])[0]], py = [y0, y0, y0, pi[1]], path = [x0, ",", y0, "L", d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py)]; points.push(points[n - 1]); while (++i <= n) { pi = points[i]; px.shift(); px.push(pi[0]); py.shift(); py.push(pi[1]); d3_svg_lineBasisBezier(path, px, py); } points.pop(); path.push("L", pi); return path.join(""); } // Open B-spline interpolation; generates "C" commands. function d3_svg_lineBasisOpen(points) { if (points.length < 4) return d3_svg_lineLinear(points); var path = [], i = -1, n = points.length, pi, px = [0], py = [0]; while (++i < 3) { pi = points[i]; px.push(pi[0]); py.push(pi[1]); } path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py)); --i; while (++i < n) { pi = points[i]; px.shift(); px.push(pi[0]); py.shift(); py.push(pi[1]); d3_svg_lineBasisBezier(path, px, py); } return path.join(""); } // Closed B-spline interpolation; generates "C" commands. function d3_svg_lineBasisClosed(points) { var path, i = -1, n = points.length, m = n + 4, pi, px = [], py = []; while (++i < 4) { pi = points[i % n]; px.push(pi[0]); py.push(pi[1]); } path = [ d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) ]; --i; while (++i < m) { pi = points[i % n]; px.shift(); px.push(pi[0]); py.shift(); py.push(pi[1]); d3_svg_lineBasisBezier(path, px, py); } return path.join(""); } function d3_svg_lineBundle(points, tension) { var n = points.length - 1; if (n) { var x0 = points[0][0], y0 = points[0][1], dx = points[n][0] - x0, dy = points[n][1] - y0, i = -1, p, t; while (++i <= n) { p = points[i]; t = i / n; p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx); p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy); } } return d3_svg_lineBasis(points); } // Returns the dot product of the given four-element vectors. function d3_svg_lineDot4(a, b) { return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; } // Matrix to transform basis (b-spline) control points to bezier // control points. Derived from FvD 11.2.8. var d3_svg_lineBasisBezier1 = [0, 2/3, 1/3, 0], d3_svg_lineBasisBezier2 = [0, 1/3, 2/3, 0], d3_svg_lineBasisBezier3 = [0, 1/6, 2/3, 1/6]; // Pushes a "C" Bézier curve onto the specified path array, given the // two specified four-element arrays which define the control points. function d3_svg_lineBasisBezier(path, x, y) { path.push( "C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y)); } // Computes the slope from points p0 to p1. function d3_svg_lineSlope(p0, p1) { return (p1[1] - p0[1]) / (p1[0] - p0[0]); } // Compute three-point differences for the given points. // http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Finite_difference 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; } // 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. if (abs(d) < ε) { 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 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; } function d3_svg_lineMonotone(points) { return points.length < 3 ? d3_svg_lineLinear(points) : points[0] + d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points)); }