jquery.flot.spline.js 5.7 KB

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