source: Dev/trunk/d3/src/svg/line.js @ 76

Last change on this file since 76 was 76, checked in by fpvanagthoven, 14 years ago

d3

File size: 11.5 KB
RevLine 
[76]1function 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
39d3.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.
47function 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].
70function d3_svg_lineX(d) {
71  return d[0];
72}
73
74// The default `y` property, which references d[1].
75function d3_svg_lineY(d) {
76  return d[1];
77}
78
79// The various interpolators supported by the `line` class.
80var 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.
95function 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.
106function 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.
117function 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.
128function 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.
136function 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.
145function 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.
153function 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.
200function 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.
218function 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.
246function 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.
271function 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
297function 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.
316function 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.
322var 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.
328function 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.
339function 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
345function 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
362function 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
414function 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}
Note: See TracBrowser for help on using the repository browser.