1
0

jquery.flot.spline.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. /**
  2. * Flot plugin that provides spline interpolation for line graphs
  3. * author: Alex Bardas < alex.bardas@gmail.com >
  4. * based on the spline interpolation described at:
  5. * http://scaledinnovation.com/analytics/splines/aboutSplines.html
  6. *
  7. * Example usage: (add in plot options series object)
  8. * for linespline:
  9. * series: {
  10. * ...
  11. * lines: {
  12. * show: false
  13. * },
  14. * splines: {
  15. * show: true,
  16. * tension: x, (float between 0 and 1, defaults to 0.5),
  17. * lineWidth: y (number, defaults to 2)
  18. * },
  19. * ...
  20. * }
  21. * areaspline:
  22. * series: {
  23. * ...
  24. * lines: {
  25. * show: true,
  26. * lineWidth: 0, // line drawing will not execute
  27. * fill: x, // float between 0 .. 1, as in flot documentation
  28. * ...
  29. * },
  30. * splines: {
  31. * show: true,
  32. * tension: 0.5, (float between 0 and 1)
  33. * },
  34. * ...
  35. * }
  36. *
  37. */
  38. (function ($) {
  39. 'use strict'
  40. /**
  41. * @param {Number} x0, y0, x1, y1: coordinates of the end (knot) points of the segment
  42. * @param {Number} x2, y2: the next knot (not connected, but needed to calculate p2)
  43. * @param {Number} tension: control how far the control points spread
  44. * @return {Array}: p1 -> control point, from x1 back toward x0
  45. * p2 -> the next control point, returned to become the next segment's p1
  46. *
  47. * @api private
  48. */
  49. function getControlPoints(x0, y0, x1, y1, x2, y2, tension) {
  50. var pow = Math.pow, sqrt = Math.sqrt, d01, d12, fa, fb, p1x, p1y, p2x, p2y;
  51. // Scaling factors: distances from this knot to the previous and following knots.
  52. d01 = sqrt(pow(x1 - x0, 2) + pow(y1 - y0, 2));
  53. d12 = sqrt(pow(x2 - x1, 2) + pow(y2 - y1, 2));
  54. fa = tension * d01 / (d01 + d12);
  55. fb = tension - fa;
  56. p1x = x1 + fa * (x0 - x2);
  57. p1y = y1 + fa * (y0 - y2);
  58. p2x = x1 - fb * (x0 - x2);
  59. p2y = y1 - fb * (y0 - y2);
  60. return [p1x, p1y, p2x, p2y];
  61. }
  62. /**
  63. * @param {Object} ctx: canvas context
  64. * @param {String} type: accepted strings: 'bezier' or 'quadratic' (defaults to quadratic)
  65. * @param {Array} points: 2 points for which to draw the interpolation
  66. * @param {Array} cpoints: control points for those segment points
  67. *
  68. * @api private
  69. */
  70. function draw(ctx, type, points, cpoints) {
  71. // if type is undefined or bad, it defaults to 'quadratic'
  72. if (type === void 0 || (type !== 'bezier' && type !== 'quadratic')) {
  73. type = 'quadratic';
  74. }
  75. type = type + 'CurveTo';
  76. ctx.beginPath();
  77. // move to first point position
  78. ctx.moveTo(points[0], points[1]);
  79. // draw a bezier curve from the first point to the second point,
  80. // using the given set of control points
  81. ctx[type].apply(ctx, cpoints.concat(points.slice(2)));
  82. ctx.stroke();
  83. ctx.closePath();
  84. }
  85. /**
  86. * @param {Object} plot
  87. * @param {Object} ctx: canvas context
  88. * @param {Object} series
  89. *
  90. * @api private
  91. */
  92. function drawSpline(plot, ctx, series) {
  93. // Not interested if spline is not requested
  94. if (series.splines.show !== true) {
  95. return;
  96. }
  97. var cp = [], // array of control points
  98. tension = series.splines.tension || 0.5,
  99. idx,
  100. x,
  101. y,
  102. points = series.datapoints.points,
  103. ps = series.datapoints.pointsize,
  104. plotOffset = plot.getPlotOffset(),
  105. len = points.length,
  106. pts = [];
  107. // Cannot display a linespline/areaspline if there are less than 3 points
  108. if (len / ps < 4) {
  109. $.extend(series.lines, series.splines);
  110. return;
  111. }
  112. for (idx = 0; idx < len; idx += ps) {
  113. x = points[idx];
  114. y = points[idx + 1];
  115. if (x == null || x < series.xaxis.min || x > series.xaxis.max ||
  116. y < series.yaxis.min || y > series.yaxis.max) {
  117. continue;
  118. }
  119. pts.push(series.xaxis.p2c(x), series.yaxis.p2c(y));
  120. }
  121. len = pts.length;
  122. // Draw an open curve, not connected at the ends
  123. for (idx = 0; idx < len - 4; idx += 2) {
  124. cp = cp.concat(getControlPoints.apply(this, pts.slice(idx, idx + 6).concat([tension])));
  125. }
  126. ctx.save();
  127. ctx.translate(plotOffset.left, plotOffset.top);
  128. ctx.strokeStyle = series.color;
  129. ctx.lineWidth = series.splines.lineWidth;
  130. for (idx = 2; idx < len - 5; idx += 2) {
  131. draw(ctx, 'bezier', pts.slice(idx, idx + 4), cp.slice(2 * idx - 2, 2 * idx + 2));
  132. }
  133. // For open curves the first and last arcs are simple quadratics
  134. draw(ctx, 'quadratic', pts.slice(0, 4), cp.slice(0, 2));
  135. draw(ctx, 'quadratic', pts.slice(len - 2, len), [cp[2 * len - 10], cp[2 * len - 9], pts[len -4], pts[len - 3]]);
  136. ctx.restore();
  137. }
  138. $.plot.plugins.push({
  139. init: function(plot) {
  140. plot.hooks.drawSeries.push(drawSpline);
  141. },
  142. options: {
  143. series: {
  144. splines: {
  145. show: false,
  146. lineWidth: 2,
  147. tension: 0.5
  148. }
  149. }
  150. },
  151. name: 'spline',
  152. version: '0.8.1'
  153. });
  154. })(jQuery);