[76] | 1 | function d3_svg_line(projection) { |
---|
| 2 | var x = d3_svg_lineX, |
---|
| 3 | y = d3_svg_lineY, |
---|
| 4 | interpolate = "linear", |
---|
| 5 | interpolator = d3_svg_lineInterpolators[interpolate], |
---|
| 6 | tension = .7; |
---|
| 7 | |
---|
| 8 | function line(d) { |
---|
| 9 | return d.length < 1 ? null : "M" + interpolator(projection(d3_svg_linePoints(this, d, x, y)), tension); |
---|
| 10 | } |
---|
| 11 | |
---|
| 12 | line.x = function(v) { |
---|
| 13 | if (!arguments.length) return x; |
---|
| 14 | x = v; |
---|
| 15 | return line; |
---|
| 16 | }; |
---|
| 17 | |
---|
| 18 | line.y = function(v) { |
---|
| 19 | if (!arguments.length) return y; |
---|
| 20 | y = v; |
---|
| 21 | return line; |
---|
| 22 | }; |
---|
| 23 | |
---|
| 24 | line.interpolate = function(v) { |
---|
| 25 | if (!arguments.length) return interpolate; |
---|
| 26 | interpolator = d3_svg_lineInterpolators[interpolate = v]; |
---|
| 27 | return line; |
---|
| 28 | }; |
---|
| 29 | |
---|
| 30 | line.tension = function(v) { |
---|
| 31 | if (!arguments.length) return tension; |
---|
| 32 | tension = v; |
---|
| 33 | return line; |
---|
| 34 | }; |
---|
| 35 | |
---|
| 36 | return line; |
---|
| 37 | } |
---|
| 38 | |
---|
| 39 | d3.svg.line = function() { |
---|
| 40 | return d3_svg_line(Object); |
---|
| 41 | }; |
---|
| 42 | |
---|
| 43 | // Converts the specified array of data into an array of points |
---|
| 44 | // (x-y tuples), by evaluating the specified `x` and `y` functions on each |
---|
| 45 | // data point. The `this` context of the evaluated functions is the specified |
---|
| 46 | // "self" object; each function is passed the current datum and index. |
---|
| 47 | function d3_svg_linePoints(self, d, x, y) { |
---|
| 48 | var points = [], |
---|
| 49 | i = -1, |
---|
| 50 | n = d.length, |
---|
| 51 | fx = typeof x === "function", |
---|
| 52 | fy = typeof y === "function", |
---|
| 53 | value; |
---|
| 54 | if (fx && fy) { |
---|
| 55 | while (++i < n) points.push([ |
---|
| 56 | x.call(self, value = d[i], i), |
---|
| 57 | y.call(self, value, i) |
---|
| 58 | ]); |
---|
| 59 | } else if (fx) { |
---|
| 60 | while (++i < n) points.push([x.call(self, d[i], i), y]); |
---|
| 61 | } else if (fy) { |
---|
| 62 | while (++i < n) points.push([x, y.call(self, d[i], i)]); |
---|
| 63 | } else { |
---|
| 64 | while (++i < n) points.push([x, y]); |
---|
| 65 | } |
---|
| 66 | return points; |
---|
| 67 | } |
---|
| 68 | |
---|
| 69 | // The default `x` property, which references d[0]. |
---|
| 70 | function d3_svg_lineX(d) { |
---|
| 71 | return d[0]; |
---|
| 72 | } |
---|
| 73 | |
---|
| 74 | // The default `y` property, which references d[1]. |
---|
| 75 | function d3_svg_lineY(d) { |
---|
| 76 | return d[1]; |
---|
| 77 | } |
---|
| 78 | |
---|
| 79 | // The various interpolators supported by the `line` class. |
---|
| 80 | var d3_svg_lineInterpolators = { |
---|
| 81 | "linear": d3_svg_lineLinear, |
---|
| 82 | "step-before": d3_svg_lineStepBefore, |
---|
| 83 | "step-after": d3_svg_lineStepAfter, |
---|
| 84 | "basis": d3_svg_lineBasis, |
---|
| 85 | "basis-open": d3_svg_lineBasisOpen, |
---|
| 86 | "basis-closed": d3_svg_lineBasisClosed, |
---|
| 87 | "bundle": d3_svg_lineBundle, |
---|
| 88 | "cardinal": d3_svg_lineCardinal, |
---|
| 89 | "cardinal-open": d3_svg_lineCardinalOpen, |
---|
| 90 | "cardinal-closed": d3_svg_lineCardinalClosed, |
---|
| 91 | "monotone": d3_svg_lineMonotone |
---|
| 92 | }; |
---|
| 93 | |
---|
| 94 | // Linear interpolation; generates "L" commands. |
---|
| 95 | function d3_svg_lineLinear(points) { |
---|
| 96 | var path = [], |
---|
| 97 | i = 0, |
---|
| 98 | n = points.length, |
---|
| 99 | p = points[0]; |
---|
| 100 | path.push(p[0], ",", p[1]); |
---|
| 101 | while (++i < n) path.push("L", (p = points[i])[0], ",", p[1]); |
---|
| 102 | return path.join(""); |
---|
| 103 | } |
---|
| 104 | |
---|
| 105 | // Step interpolation; generates "H" and "V" commands. |
---|
| 106 | function d3_svg_lineStepBefore(points) { |
---|
| 107 | var path = [], |
---|
| 108 | i = 0, |
---|
| 109 | n = points.length, |
---|
| 110 | p = points[0]; |
---|
| 111 | path.push(p[0], ",", p[1]); |
---|
| 112 | while (++i < n) path.push("V", (p = points[i])[1], "H", p[0]); |
---|
| 113 | return path.join(""); |
---|
| 114 | } |
---|
| 115 | |
---|
| 116 | // Step interpolation; generates "H" and "V" commands. |
---|
| 117 | function d3_svg_lineStepAfter(points) { |
---|
| 118 | var path = [], |
---|
| 119 | i = 0, |
---|
| 120 | n = points.length, |
---|
| 121 | p = points[0]; |
---|
| 122 | path.push(p[0], ",", p[1]); |
---|
| 123 | while (++i < n) path.push("H", (p = points[i])[0], "V", p[1]); |
---|
| 124 | return path.join(""); |
---|
| 125 | } |
---|
| 126 | |
---|
| 127 | // Open cardinal spline interpolation; generates "C" commands. |
---|
| 128 | function d3_svg_lineCardinalOpen(points, tension) { |
---|
| 129 | return points.length < 4 |
---|
| 130 | ? d3_svg_lineLinear(points) |
---|
| 131 | : points[1] + d3_svg_lineHermite(points.slice(1, points.length - 1), |
---|
| 132 | d3_svg_lineCardinalTangents(points, tension)); |
---|
| 133 | } |
---|
| 134 | |
---|
| 135 | // Closed cardinal spline interpolation; generates "C" commands. |
---|
| 136 | function d3_svg_lineCardinalClosed(points, tension) { |
---|
| 137 | return points.length < 3 |
---|
| 138 | ? d3_svg_lineLinear(points) |
---|
| 139 | : points[0] + d3_svg_lineHermite((points.push(points[0]), points), |
---|
| 140 | d3_svg_lineCardinalTangents([points[points.length - 2]] |
---|
| 141 | .concat(points, [points[1]]), tension)); |
---|
| 142 | } |
---|
| 143 | |
---|
| 144 | // Cardinal spline interpolation; generates "C" commands. |
---|
| 145 | function d3_svg_lineCardinal(points, tension, closed) { |
---|
| 146 | return points.length < 3 |
---|
| 147 | ? d3_svg_lineLinear(points) |
---|
| 148 | : points[0] + d3_svg_lineHermite(points, |
---|
| 149 | d3_svg_lineCardinalTangents(points, tension)); |
---|
| 150 | } |
---|
| 151 | |
---|
| 152 | // Hermite spline construction; generates "C" commands. |
---|
| 153 | function d3_svg_lineHermite(points, tangents) { |
---|
| 154 | if (tangents.length < 1 |
---|
| 155 | || (points.length != tangents.length |
---|
| 156 | && points.length != tangents.length + 2)) { |
---|
| 157 | return d3_svg_lineLinear(points); |
---|
| 158 | } |
---|
| 159 | |
---|
| 160 | var quad = points.length != tangents.length, |
---|
| 161 | path = "", |
---|
| 162 | p0 = points[0], |
---|
| 163 | p = points[1], |
---|
| 164 | t0 = tangents[0], |
---|
| 165 | t = t0, |
---|
| 166 | pi = 1; |
---|
| 167 | |
---|
| 168 | if (quad) { |
---|
| 169 | path += "Q" + (p[0] - t0[0] * 2 / 3) + "," + (p[1] - t0[1] * 2 / 3) |
---|
| 170 | + "," + p[0] + "," + p[1]; |
---|
| 171 | p0 = points[1]; |
---|
| 172 | pi = 2; |
---|
| 173 | } |
---|
| 174 | |
---|
| 175 | if (tangents.length > 1) { |
---|
| 176 | t = tangents[1]; |
---|
| 177 | p = points[pi]; |
---|
| 178 | pi++; |
---|
| 179 | path += "C" + (p0[0] + t0[0]) + "," + (p0[1] + t0[1]) |
---|
| 180 | + "," + (p[0] - t[0]) + "," + (p[1] - t[1]) |
---|
| 181 | + "," + p[0] + "," + p[1]; |
---|
| 182 | for (var i = 2; i < tangents.length; i++, pi++) { |
---|
| 183 | p = points[pi]; |
---|
| 184 | t = tangents[i]; |
---|
| 185 | path += "S" + (p[0] - t[0]) + "," + (p[1] - t[1]) |
---|
| 186 | + "," + p[0] + "," + p[1]; |
---|
| 187 | } |
---|
| 188 | } |
---|
| 189 | |
---|
| 190 | if (quad) { |
---|
| 191 | var lp = points[pi]; |
---|
| 192 | path += "Q" + (p[0] + t[0] * 2 / 3) + "," + (p[1] + t[1] * 2 / 3) |
---|
| 193 | + "," + lp[0] + "," + lp[1]; |
---|
| 194 | } |
---|
| 195 | |
---|
| 196 | return path; |
---|
| 197 | } |
---|
| 198 | |
---|
| 199 | // Generates tangents for a cardinal spline. |
---|
| 200 | function d3_svg_lineCardinalTangents(points, tension) { |
---|
| 201 | var tangents = [], |
---|
| 202 | a = (1 - tension) / 2, |
---|
| 203 | p0, |
---|
| 204 | p1 = points[0], |
---|
| 205 | p2 = points[1], |
---|
| 206 | i = 1, |
---|
| 207 | n = points.length; |
---|
| 208 | while (++i < n) { |
---|
| 209 | p0 = p1; |
---|
| 210 | p1 = p2; |
---|
| 211 | p2 = points[i]; |
---|
| 212 | tangents.push([a * (p2[0] - p0[0]), a * (p2[1] - p0[1])]); |
---|
| 213 | } |
---|
| 214 | return tangents; |
---|
| 215 | } |
---|
| 216 | |
---|
| 217 | // B-spline interpolation; generates "C" commands. |
---|
| 218 | function d3_svg_lineBasis(points) { |
---|
| 219 | if (points.length < 3) return d3_svg_lineLinear(points); |
---|
| 220 | var path = [], |
---|
| 221 | i = 1, |
---|
| 222 | n = points.length, |
---|
| 223 | pi = points[0], |
---|
| 224 | x0 = pi[0], |
---|
| 225 | y0 = pi[1], |
---|
| 226 | px = [x0, x0, x0, (pi = points[1])[0]], |
---|
| 227 | py = [y0, y0, y0, pi[1]]; |
---|
| 228 | path.push(x0, ",", y0); |
---|
| 229 | d3_svg_lineBasisBezier(path, px, py); |
---|
| 230 | while (++i < n) { |
---|
| 231 | pi = points[i]; |
---|
| 232 | px.shift(); px.push(pi[0]); |
---|
| 233 | py.shift(); py.push(pi[1]); |
---|
| 234 | d3_svg_lineBasisBezier(path, px, py); |
---|
| 235 | } |
---|
| 236 | i = -1; |
---|
| 237 | while (++i < 2) { |
---|
| 238 | px.shift(); px.push(pi[0]); |
---|
| 239 | py.shift(); py.push(pi[1]); |
---|
| 240 | d3_svg_lineBasisBezier(path, px, py); |
---|
| 241 | } |
---|
| 242 | return path.join(""); |
---|
| 243 | } |
---|
| 244 | |
---|
| 245 | // Open B-spline interpolation; generates "C" commands. |
---|
| 246 | function d3_svg_lineBasisOpen(points) { |
---|
| 247 | if (points.length < 4) return d3_svg_lineLinear(points); |
---|
| 248 | var path = [], |
---|
| 249 | i = -1, |
---|
| 250 | n = points.length, |
---|
| 251 | pi, |
---|
| 252 | px = [0], |
---|
| 253 | py = [0]; |
---|
| 254 | while (++i < 3) { |
---|
| 255 | pi = points[i]; |
---|
| 256 | px.push(pi[0]); |
---|
| 257 | py.push(pi[1]); |
---|
| 258 | } |
---|
| 259 | path.push(d3_svg_lineDot4(d3_svg_lineBasisBezier3, px) |
---|
| 260 | + "," + d3_svg_lineDot4(d3_svg_lineBasisBezier3, py)); |
---|
| 261 | --i; while (++i < n) { |
---|
| 262 | pi = points[i]; |
---|
| 263 | px.shift(); px.push(pi[0]); |
---|
| 264 | py.shift(); py.push(pi[1]); |
---|
| 265 | d3_svg_lineBasisBezier(path, px, py); |
---|
| 266 | } |
---|
| 267 | return path.join(""); |
---|
| 268 | } |
---|
| 269 | |
---|
| 270 | // Closed B-spline interpolation; generates "C" commands. |
---|
| 271 | function d3_svg_lineBasisClosed(points) { |
---|
| 272 | var path, |
---|
| 273 | i = -1, |
---|
| 274 | n = points.length, |
---|
| 275 | m = n + 4, |
---|
| 276 | pi, |
---|
| 277 | px = [], |
---|
| 278 | py = []; |
---|
| 279 | while (++i < 4) { |
---|
| 280 | pi = points[i % n]; |
---|
| 281 | px.push(pi[0]); |
---|
| 282 | py.push(pi[1]); |
---|
| 283 | } |
---|
| 284 | path = [ |
---|
| 285 | d3_svg_lineDot4(d3_svg_lineBasisBezier3, px), ",", |
---|
| 286 | d3_svg_lineDot4(d3_svg_lineBasisBezier3, py) |
---|
| 287 | ]; |
---|
| 288 | --i; while (++i < m) { |
---|
| 289 | pi = points[i % n]; |
---|
| 290 | px.shift(); px.push(pi[0]); |
---|
| 291 | py.shift(); py.push(pi[1]); |
---|
| 292 | d3_svg_lineBasisBezier(path, px, py); |
---|
| 293 | } |
---|
| 294 | return path.join(""); |
---|
| 295 | } |
---|
| 296 | |
---|
| 297 | function d3_svg_lineBundle(points, tension) { |
---|
| 298 | var n = points.length - 1, |
---|
| 299 | x0 = points[0][0], |
---|
| 300 | y0 = points[0][1], |
---|
| 301 | dx = points[n][0] - x0, |
---|
| 302 | dy = points[n][1] - y0, |
---|
| 303 | i = -1, |
---|
| 304 | p, |
---|
| 305 | t; |
---|
| 306 | while (++i <= n) { |
---|
| 307 | p = points[i]; |
---|
| 308 | t = i / n; |
---|
| 309 | p[0] = tension * p[0] + (1 - tension) * (x0 + t * dx); |
---|
| 310 | p[1] = tension * p[1] + (1 - tension) * (y0 + t * dy); |
---|
| 311 | } |
---|
| 312 | return d3_svg_lineBasis(points); |
---|
| 313 | } |
---|
| 314 | |
---|
| 315 | // Returns the dot product of the given four-element vectors. |
---|
| 316 | function d3_svg_lineDot4(a, b) { |
---|
| 317 | return a[0] * b[0] + a[1] * b[1] + a[2] * b[2] + a[3] * b[3]; |
---|
| 318 | } |
---|
| 319 | |
---|
| 320 | // Matrix to transform basis (b-spline) control points to bezier |
---|
| 321 | // control points. Derived from FvD 11.2.8. |
---|
| 322 | var d3_svg_lineBasisBezier1 = [0, 2/3, 1/3, 0], |
---|
| 323 | d3_svg_lineBasisBezier2 = [0, 1/3, 2/3, 0], |
---|
| 324 | d3_svg_lineBasisBezier3 = [0, 1/6, 2/3, 1/6]; |
---|
| 325 | |
---|
| 326 | // Pushes a "C" Bézier curve onto the specified path array, given the |
---|
| 327 | // two specified four-element arrays which define the control points. |
---|
| 328 | function d3_svg_lineBasisBezier(path, x, y) { |
---|
| 329 | path.push( |
---|
| 330 | "C", d3_svg_lineDot4(d3_svg_lineBasisBezier1, x), |
---|
| 331 | ",", d3_svg_lineDot4(d3_svg_lineBasisBezier1, y), |
---|
| 332 | ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, x), |
---|
| 333 | ",", d3_svg_lineDot4(d3_svg_lineBasisBezier2, y), |
---|
| 334 | ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, x), |
---|
| 335 | ",", d3_svg_lineDot4(d3_svg_lineBasisBezier3, y)); |
---|
| 336 | } |
---|
| 337 | |
---|
| 338 | // Computes the slope from points p0 to p1. |
---|
| 339 | function d3_svg_lineSlope(p0, p1) { |
---|
| 340 | return (p1[1] - p0[1]) / (p1[0] - p0[0]); |
---|
| 341 | } |
---|
| 342 | |
---|
| 343 | // Compute three-point differences for the given points. |
---|
| 344 | // http://en.wikipedia.org/wiki/Cubic_Hermite_spline#Finite_difference |
---|
| 345 | function d3_svg_lineFiniteDifferences(points) { |
---|
| 346 | var i = 0, |
---|
| 347 | j = points.length - 1, |
---|
| 348 | m = [], |
---|
| 349 | p0 = points[0], |
---|
| 350 | p1 = points[1], |
---|
| 351 | d = m[0] = d3_svg_lineSlope(p0, p1); |
---|
| 352 | while (++i < j) { |
---|
| 353 | m[i] = d + (d = d3_svg_lineSlope(p0 = p1, p1 = points[i + 1])); |
---|
| 354 | } |
---|
| 355 | m[i] = d; |
---|
| 356 | return m; |
---|
| 357 | } |
---|
| 358 | |
---|
| 359 | // Interpolates the given points using Fritsch-Carlson Monotone cubic Hermite |
---|
| 360 | // interpolation. Returns an array of tangent vectors. For details, see |
---|
| 361 | // http://en.wikipedia.org/wiki/Monotone_cubic_interpolation |
---|
| 362 | function d3_svg_lineMonotoneTangents(points) { |
---|
| 363 | var tangents = [], |
---|
| 364 | d, |
---|
| 365 | a, |
---|
| 366 | b, |
---|
| 367 | s, |
---|
| 368 | m = d3_svg_lineFiniteDifferences(points), |
---|
| 369 | i = -1, |
---|
| 370 | j = points.length - 1; |
---|
| 371 | |
---|
| 372 | // The first two steps are done by computing finite-differences: |
---|
| 373 | // 1. Compute the slopes of the secant lines between successive points. |
---|
| 374 | // 2. Initialize the tangents at every point as the average of the secants. |
---|
| 375 | |
---|
| 376 | // Then, for each segment⊠|
---|
| 377 | while (++i < j) { |
---|
| 378 | d = d3_svg_lineSlope(points[i], points[i + 1]); |
---|
| 379 | |
---|
| 380 | // 3. If two successive yk = y{k + 1} are equal (i.e., d is zero), then set |
---|
| 381 | // mk = m{k + 1} = 0 as the spline connecting these points must be flat to |
---|
| 382 | // preserve monotonicity. Ignore step 4 and 5 for those k. |
---|
| 383 | |
---|
| 384 | if (Math.abs(d) < 1e-6) { |
---|
| 385 | m[i] = m[i + 1] = 0; |
---|
| 386 | } else { |
---|
| 387 | // 4. Let ak = mk / dk and bk = m{k + 1} / dk. |
---|
| 388 | a = m[i] / d; |
---|
| 389 | b = m[i + 1] / d; |
---|
| 390 | |
---|
| 391 | // 5. Prevent overshoot and ensure monotonicity by restricting the |
---|
| 392 | // magnitude of vector <ak, bk> to a circle of radius 3. |
---|
| 393 | s = a * a + b * b; |
---|
| 394 | if (s > 9) { |
---|
| 395 | s = d * 3 / Math.sqrt(s); |
---|
| 396 | m[i] = s * a; |
---|
| 397 | m[i + 1] = s * b; |
---|
| 398 | } |
---|
| 399 | } |
---|
| 400 | } |
---|
| 401 | |
---|
| 402 | // Compute the normalized tangent vector from the slopes. Note that if x is |
---|
| 403 | // not monotonic, it's possible that the slope will be infinite, so we protect |
---|
| 404 | // against NaN by setting the coordinate to zero. |
---|
| 405 | i = -1; while (++i <= j) { |
---|
| 406 | s = (points[Math.min(j, i + 1)][0] - points[Math.max(0, i - 1)][0]) |
---|
| 407 | / (6 * (1 + m[i] * m[i])); |
---|
| 408 | tangents.push([s || 0, m[i] * s || 0]); |
---|
| 409 | } |
---|
| 410 | |
---|
| 411 | return tangents; |
---|
| 412 | } |
---|
| 413 | |
---|
| 414 | function d3_svg_lineMonotone(points) { |
---|
| 415 | return points.length < 3 |
---|
| 416 | ? d3_svg_lineLinear(points) |
---|
| 417 | : points[0] + |
---|
| 418 | d3_svg_lineHermite(points, d3_svg_lineMonotoneTangents(points)); |
---|
| 419 | } |
---|