curvedLines.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316
  1. /* The MIT License
  2. Copyright (c) 2011 by Michael Zinsmaier and nergal.dev
  3. Copyright (c) 2012 by Thomas Ritou
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated documentation files (the "Software"), to deal
  6. in the Software without restriction, including without limitation the rights
  7. to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. copies of the Software, and to permit persons to whom the Software is
  9. furnished to do so, subject to the following conditions:
  10. The above copyright notice and this permission notice shall be included in
  11. all copies or substantial portions of the Software.
  12. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  13. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  14. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  15. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  16. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  17. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  18. THE SOFTWARE.
  19. */
  20. /*
  21. ____________________________________________________
  22. what it is:
  23. ____________________________________________________
  24. curvedLines is a plugin for flot, that tries to display lines in a smoother way.
  25. The plugin is based on nergal.dev's work https://code.google.com/p/flot/issues/detail?id=226
  26. and further extended with a mode that forces the min/max points of the curves to be on the
  27. points. Both modes are achieved through adding of more data points
  28. => 1) with large data sets you may get trouble
  29. => 2) if you want to display the points too, you have to plot them as 2nd data series over the lines
  30. && 3) consecutive x data points are not allowed to have the same value
  31. This is version 0.5 of curvedLines so it will probably not work in every case. However
  32. the basic form of use descirbed next works (:
  33. Feel free to further improve the code
  34. ____________________________________________________
  35. how to use it:
  36. ____________________________________________________
  37. var d1 = [[5,5],[7,3],[9,12]];
  38. var options = { series: { curvedLines: { active: true }}};
  39. $.plot($("#placeholder"), [{data = d1, lines: { show: true}, curvedLines: {apply: true}}], options);
  40. _____________________________________________________
  41. options:
  42. _____________________________________________________
  43. active: bool true => plugin can be used
  44. apply: bool true => series will be drawn as curved line
  45. fit: bool true => forces the max,mins of the curve to be on the datapoints
  46. curvePointFactor int defines how many "virtual" points are used per "real" data point to
  47. emulate the curvedLines (points total = real points * curvePointFactor)
  48. fitPointDist: int defines the x axis distance of the additional two points that are used
  49. to enforce the min max condition.
  50. + line options (since v0.5 curved lines use flots line implementation for drawing
  51. => line options like fill, show ... are supported out of the box)
  52. */
  53. /*
  54. * v0.1 initial commit
  55. * v0.15 negative values should work now (outcommented a negative -> 0 hook hope it does no harm)
  56. * v0.2 added fill option (thanks to monemihir) and multi axis support (thanks to soewono effendi)
  57. * v0.3 improved saddle handling and added basic handling of Dates
  58. * v0.4 rewritten fill option (thomas ritou) mostly from original flot code (now fill between points rather than to graph bottom), corrected fill Opacity bug
  59. * v0.5 rewritten instead of implementing a own draw function CurvedLines is now based on the processDatapoints flot hook (credits go to thomas ritou).
  60. * This change breakes existing code however CurvedLines are now just many tiny straight lines to flot and therefore all flot lines options (like gradient fill,
  61. * shadow) are now supported out of the box
  62. * v0.6 flot 0.8 compatibility and some bug fixes
  63. */
  64. (function($) {
  65. var options = {
  66. series : {
  67. curvedLines : {
  68. active : false,
  69. apply: false,
  70. fit : false,
  71. curvePointFactor : 20,
  72. fitPointDist : undefined
  73. }
  74. }
  75. };
  76. function init(plot) {
  77. plot.hooks.processOptions.push(processOptions);
  78. //if the plugin is active register processDatapoints method
  79. function processOptions(plot, options) {
  80. if (options.series.curvedLines.active) {
  81. plot.hooks.processDatapoints.unshift(processDatapoints);
  82. }
  83. }
  84. //only if the plugin is active
  85. function processDatapoints(plot, series, datapoints) {
  86. var nrPoints = datapoints.points.length / datapoints.pointsize;
  87. var EPSILON = 0.5; //pretty large epsilon but save
  88. if (series.curvedLines.apply == true && series.originSeries === undefined && nrPoints > (1 + EPSILON)) {
  89. if (series.lines.fill) {
  90. var pointsTop = calculateCurvePoints(datapoints, series.curvedLines, 1)
  91. ,pointsBottom = calculateCurvePoints(datapoints, series.curvedLines, 2); //flot makes sure for us that we've got a second y point if fill is true !
  92. //Merge top and bottom curve
  93. datapoints.pointsize = 3;
  94. datapoints.points = [];
  95. var j = 0;
  96. var k = 0;
  97. var i = 0;
  98. var ps = 2;
  99. while (i < pointsTop.length || j < pointsBottom.length) {
  100. if (pointsTop[i] == pointsBottom[j]) {
  101. datapoints.points[k] = pointsTop[i];
  102. datapoints.points[k + 1] = pointsTop[i + 1];
  103. datapoints.points[k + 2] = pointsBottom[j + 1];
  104. j += ps;
  105. i += ps;
  106. } else if (pointsTop[i] < pointsBottom[j]) {
  107. datapoints.points[k] = pointsTop[i];
  108. datapoints.points[k + 1] = pointsTop[i + 1];
  109. datapoints.points[k + 2] = k > 0 ? datapoints.points[k-1] : null;
  110. i += ps;
  111. } else {
  112. datapoints.points[k] = pointsBottom[j];
  113. datapoints.points[k + 1] = k > 1 ? datapoints.points[k-2] : null;
  114. datapoints.points[k + 2] = pointsBottom[j + 1];
  115. j += ps;
  116. }
  117. k += 3;
  118. }
  119. } else if (series.lines.lineWidth > 0) {
  120. datapoints.points = calculateCurvePoints(datapoints, series.curvedLines, 1);
  121. datapoints.pointsize = 2;
  122. }
  123. }
  124. }
  125. //no real idea whats going on here code mainly from https://code.google.com/p/flot/issues/detail?id=226
  126. //if fit option is selected additional datapoints get inserted before the curve calculations in nergal.dev s code.
  127. function calculateCurvePoints(datapoints, curvedLinesOptions, yPos) {
  128. var points = datapoints.points, ps = datapoints.pointsize;
  129. var num = curvedLinesOptions.curvePointFactor * (points.length / ps);
  130. var xdata = new Array;
  131. var ydata = new Array;
  132. var curX = -1;
  133. var curY = -1;
  134. var j = 0;
  135. if (curvedLinesOptions.fit) {
  136. //insert a point before and after the "real" data point to force the line
  137. //to have a max,min at the data point.
  138. var fpDist;
  139. if(typeof curvedLinesOptions.fitPointDist == 'undefined') {
  140. //estimate it
  141. var minX = points[0];
  142. var maxX = points[points.length-ps];
  143. fpDist = (maxX - minX) / (500 * 100); //x range / (estimated pixel length of placeholder * factor)
  144. } else {
  145. //use user defined value
  146. fpDist = curvedLinesOptions.fitPointDist;
  147. }
  148. for (var i = 0; i < points.length; i += ps) {
  149. var frontX;
  150. var backX;
  151. curX = i;
  152. curY = i + yPos;
  153. //add point X s
  154. frontX = points[curX] - fpDist;
  155. backX = points[curX] + fpDist;
  156. var factor = 2;
  157. while (frontX == points[curX] || backX == points[curX]) {
  158. //inside the ulp
  159. frontX = points[curX] - (fpDist * factor);
  160. backX = points[curX] + (fpDist * factor);
  161. factor++;
  162. }
  163. //add curve points
  164. xdata[j] = frontX;
  165. ydata[j] = points[curY];
  166. j++;
  167. xdata[j] = points[curX];
  168. ydata[j] = points[curY];
  169. j++;
  170. xdata[j] = backX;
  171. ydata[j] = points[curY];
  172. j++;
  173. }
  174. } else {
  175. //just use the datapoints
  176. for (var i = 0; i < points.length; i += ps) {
  177. curX = i;
  178. curY = i + yPos;
  179. xdata[j] = points[curX];
  180. ydata[j] = points[curY];
  181. j++;
  182. }
  183. }
  184. var n = xdata.length;
  185. var y2 = new Array();
  186. var delta = new Array();
  187. y2[0] = 0;
  188. y2[n - 1] = 0;
  189. delta[0] = 0;
  190. for (var i = 1; i < n - 1; ++i) {
  191. var d = (xdata[i + 1] - xdata[i - 1]);
  192. if (d == 0) {
  193. //point before current point and after current point need some space in between
  194. return [];
  195. }
  196. var s = (xdata[i] - xdata[i - 1]) / d;
  197. var p = s * y2[i - 1] + 2;
  198. y2[i] = (s - 1) / p;
  199. delta[i] = (ydata[i + 1] - ydata[i]) / (xdata[i + 1] - xdata[i]) - (ydata[i] - ydata[i - 1]) / (xdata[i] - xdata[i - 1]);
  200. delta[i] = (6 * delta[i] / (xdata[i + 1] - xdata[i - 1]) - s * delta[i - 1]) / p;
  201. }
  202. for (var j = n - 2; j >= 0; --j) {
  203. y2[j] = y2[j] * y2[j + 1] + delta[j];
  204. }
  205. // xmax - xmin / #points
  206. var step = (xdata[n - 1] - xdata[0]) / (num - 1);
  207. var xnew = new Array;
  208. var ynew = new Array;
  209. var result = new Array;
  210. xnew[0] = xdata[0];
  211. ynew[0] = ydata[0];
  212. result.push(xnew[0]);
  213. result.push(ynew[0]);
  214. for ( j = 1; j < num; ++j) {
  215. //new x point (sampling point for the created curve)
  216. xnew[j] = xnew[0] + j * step;
  217. var max = n - 1;
  218. var min = 0;
  219. while (max - min > 1) {
  220. var k = Math.round((max + min) / 2);
  221. if (xdata[k] > xnew[j]) {
  222. max = k;
  223. } else {
  224. min = k;
  225. }
  226. }
  227. //found point one to the left and one to the right of generated new point
  228. var h = (xdata[max] - xdata[min]);
  229. if (h == 0) {
  230. //similar to above two points from original x data need some space between them
  231. return [];
  232. }
  233. var a = (xdata[max] - xnew[j]) / h;
  234. var b = (xnew[j] - xdata[min]) / h;
  235. ynew[j] = a * ydata[min] + b * ydata[max] + ((a * a * a - a) * y2[min] + (b * b * b - b) * y2[max]) * (h * h) / 6;
  236. result.push(xnew[j]);
  237. result.push(ynew[j]);
  238. }
  239. return result;
  240. }
  241. }//end init
  242. $.plot.plugins.push({
  243. init : init,
  244. options : options,
  245. name : 'curvedLines',
  246. version : '0.5'
  247. });
  248. })(jQuery);