jqplot.lineRenderer.js 51 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227
  1. /**
  2. * jqPlot
  3. * Pure JavaScript plotting plugin using jQuery
  4. *
  5. * Version: @VERSION
  6. * Revision: @REVISION
  7. *
  8. * Copyright (c) 2009-2013 Chris Leonello
  9. * jqPlot is currently available for use in all personal or commercial projects
  10. * under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
  11. * version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
  12. * choose the license that best suits your project and use it accordingly.
  13. *
  14. * Although not required, the author would appreciate an email letting him
  15. * know of any substantial use of jqPlot. You can reach the author at:
  16. * chris at jqplot dot com or see http://www.jqplot.com/info.php .
  17. *
  18. * If you are feeling kind and generous, consider supporting the project by
  19. * making a donation at: http://www.jqplot.com/donate.php .
  20. *
  21. * sprintf functions contained in jqplot.sprintf.js by Ash Searle:
  22. *
  23. * version 2007.04.27
  24. * author Ash Searle
  25. * http://hexmen.com/blog/2007/03/printf-sprintf/
  26. * http://hexmen.com/js/sprintf.js
  27. * The author (Ash Searle) has placed this code in the public domain:
  28. * "This code is unrestricted: you are free to use it however you like."
  29. *
  30. */
  31. (function($) {
  32. // Class: $.jqplot.LineRenderer
  33. // The default line renderer for jqPlot, this class has no options beyond the <Series> class.
  34. // Draws series as a line.
  35. $.jqplot.LineRenderer = function(){
  36. this.shapeRenderer = new $.jqplot.ShapeRenderer();
  37. this.shadowRenderer = new $.jqplot.ShadowRenderer();
  38. };
  39. // called with scope of series.
  40. $.jqplot.LineRenderer.prototype.init = function(options, plot) {
  41. // Group: Properties
  42. //
  43. options = options || {};
  44. this._type='line';
  45. this.renderer.animation = {
  46. show: false,
  47. direction: 'left',
  48. speed: 2500,
  49. _supported: true
  50. };
  51. // prop: smooth
  52. // True to draw a smoothed (interpolated) line through the data points
  53. // with automatically computed number of smoothing points.
  54. // Set to an integer number > 2 to specify number of smoothing points
  55. // to use between each data point.
  56. this.renderer.smooth = false; // true or a number > 2 for smoothing.
  57. this.renderer.tension = null; // null to auto compute or a number typically > 6. Fewer points requires higher tension.
  58. // prop: constrainSmoothing
  59. // True to use a more accurate smoothing algorithm that will
  60. // not overshoot any data points. False to allow overshoot but
  61. // produce a smoother looking line.
  62. this.renderer.constrainSmoothing = true;
  63. // this is smoothed data in grid coordinates, like gridData
  64. this.renderer._smoothedData = [];
  65. // this is smoothed data in plot units (plot coordinates), like plotData.
  66. this.renderer._smoothedPlotData = [];
  67. this.renderer._hiBandGridData = [];
  68. this.renderer._lowBandGridData = [];
  69. this.renderer._hiBandSmoothedData = [];
  70. this.renderer._lowBandSmoothedData = [];
  71. // prop: bandData
  72. // Data used to draw error bands or confidence intervals above/below a line.
  73. //
  74. // bandData can be input in 3 forms. jqPlot will figure out which is the
  75. // low band line and which is the high band line for all forms:
  76. //
  77. // A 2 dimensional array like [[yl1, yl2, ...], [yu1, yu2, ...]] where
  78. // [yl1, yl2, ...] are y values of the lower line and
  79. // [yu1, yu2, ...] are y values of the upper line.
  80. // In this case there must be the same number of y data points as data points
  81. // in the series and the bands will inherit the x values of the series.
  82. //
  83. // A 2 dimensional array like [[[xl1, yl1], [xl2, yl2], ...], [[xh1, yh1], [xh2, yh2], ...]]
  84. // where [xl1, yl1] are x,y data points for the lower line and
  85. // [xh1, yh1] are x,y data points for the high line.
  86. // x values do not have to correspond to the x values of the series and can
  87. // be of any arbitrary length.
  88. //
  89. // Can be of form [[yl1, yu1], [yl2, yu2], [yl3, yu3], ...] where
  90. // there must be 3 or more arrays and there must be the same number of arrays
  91. // as there are data points in the series. In this case,
  92. // [yl1, yu1] specifies the lower and upper y values for the 1st
  93. // data point and so on. The bands will inherit the x
  94. // values from the series.
  95. this.renderer.bandData = [];
  96. // Group: bands
  97. // Banding around line, e.g error bands or confidence intervals.
  98. this.renderer.bands = {
  99. // prop: show
  100. // true to show the bands. If bandData or interval is
  101. // supplied, show will be set to true by default.
  102. show: false,
  103. hiData: [],
  104. lowData: [],
  105. // prop: color
  106. // color of lines at top and bottom of bands [default: series color].
  107. color: this.color,
  108. // prop: showLines
  109. // True to show lines at top and bottom of bands [default: false].
  110. showLines: false,
  111. // prop: fill
  112. // True to fill area between bands [default: true].
  113. fill: true,
  114. // prop: fillColor
  115. // css color spec for filled area. [default: series color].
  116. fillColor: null,
  117. _min: null,
  118. _max: null,
  119. // prop: interval
  120. // User specified interval above and below line for bands [default: '3%''].
  121. // Can be a value like 3 or a string like '3%'
  122. // or an upper/lower array like [1, -2] or ['2%', '-1.5%']
  123. interval: '3%'
  124. };
  125. var lopts = {highlightMouseOver: options.highlightMouseOver, highlightMouseDown: options.highlightMouseDown, highlightColor: options.highlightColor};
  126. delete (options.highlightMouseOver);
  127. delete (options.highlightMouseDown);
  128. delete (options.highlightColor);
  129. $.extend(true, this.renderer, options);
  130. this.renderer.options = options;
  131. // if we are given some band data, and bands aren't explicity set to false in options, turn them on.
  132. if (this.renderer.bandData.length > 1 && (!options.bands || options.bands.show == null)) {
  133. this.renderer.bands.show = true;
  134. }
  135. // if we are given an interval, and bands aren't explicity set to false in options, turn them on.
  136. else if (options.bands && options.bands.show == null && options.bands.interval != null) {
  137. this.renderer.bands.show = true;
  138. }
  139. // if plot is filled, turn off bands.
  140. if (this.fill) {
  141. this.renderer.bands.show = false;
  142. }
  143. if (this.renderer.bands.show) {
  144. this.renderer.initBands.call(this, this.renderer.options, plot);
  145. }
  146. // smoothing is not compatible with stacked lines, disable
  147. if (this._stack) {
  148. this.renderer.smooth = false;
  149. }
  150. // set the shape renderer options
  151. var opts = {lineJoin:this.lineJoin, lineCap:this.lineCap, fill:this.fill, isarc:false, strokeStyle:this.color, fillStyle:this.fillColor, lineWidth:this.lineWidth, linePattern:this.linePattern, closePath:this.fill};
  152. this.renderer.shapeRenderer.init(opts);
  153. var shadow_offset = options.shadowOffset;
  154. // set the shadow renderer options
  155. if (shadow_offset == null) {
  156. // scale the shadowOffset to the width of the line.
  157. if (this.lineWidth > 2.5) {
  158. shadow_offset = 1.25 * (1 + (Math.atan((this.lineWidth/2.5))/0.785398163 - 1)*0.6);
  159. // var shadow_offset = this.shadowOffset;
  160. }
  161. // for skinny lines, don't make such a big shadow.
  162. else {
  163. shadow_offset = 1.25 * Math.atan((this.lineWidth/2.5))/0.785398163;
  164. }
  165. }
  166. var sopts = {lineJoin:this.lineJoin, lineCap:this.lineCap, fill:this.fill, isarc:false, angle:this.shadowAngle, offset:shadow_offset, alpha:this.shadowAlpha, depth:this.shadowDepth, lineWidth:this.lineWidth, linePattern:this.linePattern, closePath:this.fill};
  167. this.renderer.shadowRenderer.init(sopts);
  168. this._areaPoints = [];
  169. this._boundingBox = [[],[]];
  170. if (!this.isTrendline && this.fill || this.renderer.bands.show) {
  171. // Group: Properties
  172. //
  173. // prop: highlightMouseOver
  174. // True to highlight area on a filled plot when moused over.
  175. // This must be false to enable highlightMouseDown to highlight when clicking on an area on a filled plot.
  176. this.highlightMouseOver = true;
  177. // prop: highlightMouseDown
  178. // True to highlight when a mouse button is pressed over an area on a filled plot.
  179. // This will be disabled if highlightMouseOver is true.
  180. this.highlightMouseDown = false;
  181. // prop: highlightColor
  182. // color to use when highlighting an area on a filled plot.
  183. this.highlightColor = null;
  184. // if user has passed in highlightMouseDown option and not set highlightMouseOver, disable highlightMouseOver
  185. if (lopts.highlightMouseDown && lopts.highlightMouseOver == null) {
  186. lopts.highlightMouseOver = false;
  187. }
  188. $.extend(true, this, {highlightMouseOver: lopts.highlightMouseOver, highlightMouseDown: lopts.highlightMouseDown, highlightColor: lopts.highlightColor});
  189. if (!this.highlightColor) {
  190. var fc = (this.renderer.bands.show) ? this.renderer.bands.fillColor : this.fillColor;
  191. this.highlightColor = $.jqplot.computeHighlightColors(fc);
  192. }
  193. // turn off (disable) the highlighter plugin
  194. if (this.highlighter) {
  195. this.highlighter.show = false;
  196. }
  197. }
  198. if (!this.isTrendline && plot) {
  199. plot.plugins.lineRenderer = {};
  200. plot.postInitHooks.addOnce(postInit);
  201. plot.postDrawHooks.addOnce(postPlotDraw);
  202. plot.eventListenerHooks.addOnce('jqplotMouseMove', handleMove);
  203. plot.eventListenerHooks.addOnce('jqplotMouseDown', handleMouseDown);
  204. plot.eventListenerHooks.addOnce('jqplotMouseUp', handleMouseUp);
  205. plot.eventListenerHooks.addOnce('jqplotClick', handleClick);
  206. plot.eventListenerHooks.addOnce('jqplotRightClick', handleRightClick);
  207. }
  208. };
  209. $.jqplot.LineRenderer.prototype.initBands = function(options, plot) {
  210. // use bandData if no data specified in bands option
  211. //var bd = this.renderer.bandData;
  212. var bd = options.bandData || [];
  213. var bands = this.renderer.bands;
  214. bands.hiData = [];
  215. bands.lowData = [];
  216. var data = this.data;
  217. bands._max = null;
  218. bands._min = null;
  219. // If 2 arrays, and each array greater than 2 elements, assume it is hi and low data bands of y values.
  220. if (bd.length == 2) {
  221. // Do we have an array of x,y values?
  222. // like [[[1,1], [2,4], [3,3]], [[1,3], [2,6], [3,5]]]
  223. if ($.isArray(bd[0][0])) {
  224. // since an arbitrary array of points, spin through all of them to determine max and min lines.
  225. var p;
  226. var bdminidx = 0, bdmaxidx = 0;
  227. for (var i = 0, l = bd[0].length; i<l; i++) {
  228. p = bd[0][i];
  229. if ((p[1] != null && p[1] > bands._max) || bands._max == null) {
  230. bands._max = p[1];
  231. }
  232. if ((p[1] != null && p[1] < bands._min) || bands._min == null) {
  233. bands._min = p[1];
  234. }
  235. }
  236. for (var i = 0, l = bd[1].length; i<l; i++) {
  237. p = bd[1][i];
  238. if ((p[1] != null && p[1] > bands._max) || bands._max == null) {
  239. bands._max = p[1];
  240. bdmaxidx = 1;
  241. }
  242. if ((p[1] != null && p[1] < bands._min) || bands._min == null) {
  243. bands._min = p[1];
  244. bdminidx = 1;
  245. }
  246. }
  247. if (bdmaxidx === bdminidx) {
  248. bands.show = false;
  249. }
  250. bands.hiData = bd[bdmaxidx];
  251. bands.lowData = bd[bdminidx];
  252. }
  253. // else data is arrays of y values
  254. // like [[1,4,3], [3,6,5]]
  255. // must have same number of band data points as points in series
  256. else if (bd[0].length === data.length && bd[1].length === data.length) {
  257. var hi = (bd[0][0] > bd[1][0]) ? 0 : 1;
  258. var low = (hi) ? 0 : 1;
  259. for (var i=0, l=data.length; i < l; i++) {
  260. bands.hiData.push([data[i][0], bd[hi][i]]);
  261. bands.lowData.push([data[i][0], bd[low][i]]);
  262. }
  263. }
  264. // we don't have proper data array, don't show bands.
  265. else {
  266. bands.show = false;
  267. }
  268. }
  269. // if more than 2 arrays, have arrays of [ylow, yhi] values.
  270. // note, can't distinguish case of [[ylow, yhi], [ylow, yhi]] from [[ylow, ylow], [yhi, yhi]]
  271. // this is assumed to be of the latter form.
  272. else if (bd.length > 2 && !$.isArray(bd[0][0])) {
  273. var hi = (bd[0][0] > bd[0][1]) ? 0 : 1;
  274. var low = (hi) ? 0 : 1;
  275. for (var i=0, l=bd.length; i<l; i++) {
  276. bands.hiData.push([data[i][0], bd[i][hi]]);
  277. bands.lowData.push([data[i][0], bd[i][low]]);
  278. }
  279. }
  280. // don't have proper data, auto calculate
  281. else {
  282. var intrv = bands.interval;
  283. var a = null;
  284. var b = null;
  285. var afunc = null;
  286. var bfunc = null;
  287. if ($.isArray(intrv)) {
  288. a = intrv[0];
  289. b = intrv[1];
  290. }
  291. else {
  292. a = intrv;
  293. }
  294. if (isNaN(a)) {
  295. // we have a string
  296. if (a.charAt(a.length - 1) === '%') {
  297. afunc = 'multiply';
  298. a = parseFloat(a)/100 + 1;
  299. }
  300. }
  301. else {
  302. a = parseFloat(a);
  303. afunc = 'add';
  304. }
  305. if (b !== null && isNaN(b)) {
  306. // we have a string
  307. if (b.charAt(b.length - 1) === '%') {
  308. bfunc = 'multiply';
  309. b = parseFloat(b)/100 + 1;
  310. }
  311. }
  312. else if (b !== null) {
  313. b = parseFloat(b);
  314. bfunc = 'add';
  315. }
  316. if (a !== null) {
  317. if (b === null) {
  318. b = -a;
  319. bfunc = afunc;
  320. if (bfunc === 'multiply') {
  321. b += 2;
  322. }
  323. }
  324. // make sure a always applies to hi band.
  325. if (a < b) {
  326. var temp = a;
  327. a = b;
  328. b = temp;
  329. temp = afunc;
  330. afunc = bfunc;
  331. bfunc = temp;
  332. }
  333. for (var i=0, l = data.length; i < l; i++) {
  334. switch (afunc) {
  335. case 'add':
  336. bands.hiData.push([data[i][0], data[i][1] + a]);
  337. break;
  338. case 'multiply':
  339. bands.hiData.push([data[i][0], data[i][1] * a]);
  340. break;
  341. }
  342. switch (bfunc) {
  343. case 'add':
  344. bands.lowData.push([data[i][0], data[i][1] + b]);
  345. break;
  346. case 'multiply':
  347. bands.lowData.push([data[i][0], data[i][1] * b]);
  348. break;
  349. }
  350. }
  351. }
  352. else {
  353. bands.show = false;
  354. }
  355. }
  356. var hd = bands.hiData;
  357. var ld = bands.lowData;
  358. for (var i = 0, l = hd.length; i<l; i++) {
  359. if ((hd[i][1] != null && hd[i][1] > bands._max) || bands._max == null) {
  360. bands._max = hd[i][1];
  361. }
  362. }
  363. for (var i = 0, l = ld.length; i<l; i++) {
  364. if ((ld[i][1] != null && ld[i][1] < bands._min) || bands._min == null) {
  365. bands._min = ld[i][1];
  366. }
  367. }
  368. // one last check for proper data
  369. // these don't apply any more since allowing arbitrary x,y values
  370. // if (bands.hiData.length != bands.lowData.length) {
  371. // bands.show = false;
  372. // }
  373. // if (bands.hiData.length != this.data.length) {
  374. // bands.show = false;
  375. // }
  376. if (bands.fillColor === null) {
  377. var c = $.jqplot.getColorComponents(bands.color);
  378. // now adjust alpha to differentiate fill
  379. c[3] = c[3] * 0.5;
  380. bands.fillColor = 'rgba(' + c[0] +', '+ c[1] +', '+ c[2] +', '+ c[3] + ')';
  381. }
  382. };
  383. function getSteps (d, f) {
  384. return (3.4182054+f) * Math.pow(d, -0.3534992);
  385. }
  386. function computeSteps (d1, d2) {
  387. var s = Math.sqrt(Math.pow((d2[0]- d1[0]), 2) + Math.pow ((d2[1] - d1[1]), 2));
  388. return 5.7648 * Math.log(s) + 7.4456;
  389. }
  390. function tanh (x) {
  391. var a = (Math.exp(2*x) - 1) / (Math.exp(2*x) + 1);
  392. return a;
  393. }
  394. //////////
  395. // computeConstrainedSmoothedData
  396. // An implementation of the constrained cubic spline interpolation
  397. // method as presented in:
  398. //
  399. // Kruger, CJC, Constrained Cubic Spine Interpolation for Chemical Engineering Applications
  400. // http://www.korf.co.uk/spline.pdf
  401. //
  402. // The implementation below borrows heavily from the sample Visual Basic
  403. // implementation by CJC Kruger found in http://www.korf.co.uk/spline.xls
  404. //
  405. /////////
  406. // called with scope of series
  407. function computeConstrainedSmoothedData (gd) {
  408. var smooth = this.renderer.smooth;
  409. var dim = this.canvas.getWidth();
  410. var xp = this._xaxis.series_p2u;
  411. var yp = this._yaxis.series_p2u;
  412. var steps =null;
  413. var _steps = null;
  414. var dist = gd.length/dim;
  415. var _smoothedData = [];
  416. var _smoothedPlotData = [];
  417. if (!isNaN(parseFloat(smooth))) {
  418. steps = parseFloat(smooth);
  419. }
  420. else {
  421. steps = getSteps(dist, 0.5);
  422. }
  423. var yy = [];
  424. var xx = [];
  425. for (var i=0, l = gd.length; i<l; i++) {
  426. yy.push(gd[i][1]);
  427. xx.push(gd[i][0]);
  428. }
  429. function dxx(x1, x0) {
  430. if (x1 - x0 == 0) {
  431. return Math.pow(10,10);
  432. }
  433. else {
  434. return x1 - x0;
  435. }
  436. }
  437. var A, B, C, D;
  438. // loop through each line segment. Have # points - 1 line segments. Nmber segments starting at 1.
  439. var nmax = gd.length - 1;
  440. for (var num = 1, gdl = gd.length; num<gdl; num++) {
  441. var gxx = [];
  442. var ggxx = [];
  443. // point at each end of segment.
  444. for (var j = 0; j < 2; j++) {
  445. var i = num - 1 + j; // point number, 0 to # points.
  446. if (i == 0 || i == nmax) {
  447. gxx[j] = Math.pow(10, 10);
  448. }
  449. else if (yy[i+1] - yy[i] == 0 || yy[i] - yy[i-1] == 0) {
  450. gxx[j] = 0;
  451. }
  452. else if (((xx[i+1] - xx[i]) / (yy[i+1] - yy[i]) + (xx[i] - xx[i-1]) / (yy[i] - yy[i-1])) == 0 ) {
  453. gxx[j] = 0;
  454. }
  455. else if ( (yy[i+1] - yy[i]) * (yy[i] - yy[i-1]) < 0 ) {
  456. gxx[j] = 0;
  457. }
  458. else {
  459. gxx[j] = 2 / (dxx(xx[i + 1], xx[i]) / (yy[i + 1] - yy[i]) + dxx(xx[i], xx[i - 1]) / (yy[i] - yy[i - 1]));
  460. }
  461. }
  462. // Reset first derivative (slope) at first and last point
  463. if (num == 1) {
  464. // First point has 0 2nd derivative
  465. gxx[0] = 3 / 2 * (yy[1] - yy[0]) / dxx(xx[1], xx[0]) - gxx[1] / 2;
  466. }
  467. else if (num == nmax) {
  468. // Last point has 0 2nd derivative
  469. gxx[1] = 3 / 2 * (yy[nmax] - yy[nmax - 1]) / dxx(xx[nmax], xx[nmax - 1]) - gxx[0] / 2;
  470. }
  471. // Calc second derivative at points
  472. ggxx[0] = -2 * (gxx[1] + 2 * gxx[0]) / dxx(xx[num], xx[num - 1]) + 6 * (yy[num] - yy[num - 1]) / Math.pow(dxx(xx[num], xx[num - 1]), 2);
  473. ggxx[1] = 2 * (2 * gxx[1] + gxx[0]) / dxx(xx[num], xx[num - 1]) - 6 * (yy[num] - yy[num - 1]) / Math.pow(dxx(xx[num], xx[num - 1]), 2);
  474. // Calc constants for cubic interpolation
  475. D = 1 / 6 * (ggxx[1] - ggxx[0]) / dxx(xx[num], xx[num - 1]);
  476. C = 1 / 2 * (xx[num] * ggxx[0] - xx[num - 1] * ggxx[1]) / dxx(xx[num], xx[num - 1]);
  477. B = (yy[num] - yy[num - 1] - C * (Math.pow(xx[num], 2) - Math.pow(xx[num - 1], 2)) - D * (Math.pow(xx[num], 3) - Math.pow(xx[num - 1], 3))) / dxx(xx[num], xx[num - 1]);
  478. A = yy[num - 1] - B * xx[num - 1] - C * Math.pow(xx[num - 1], 2) - D * Math.pow(xx[num - 1], 3);
  479. var increment = (xx[num] - xx[num - 1]) / steps;
  480. var temp, tempx;
  481. for (var j = 0, l = steps; j < l; j++) {
  482. temp = [];
  483. tempx = xx[num - 1] + j * increment;
  484. temp.push(tempx);
  485. temp.push(A + B * tempx + C * Math.pow(tempx, 2) + D * Math.pow(tempx, 3));
  486. _smoothedData.push(temp);
  487. _smoothedPlotData.push([xp(temp[0]), yp(temp[1])]);
  488. }
  489. }
  490. _smoothedData.push(gd[i]);
  491. _smoothedPlotData.push([xp(gd[i][0]), yp(gd[i][1])]);
  492. return [_smoothedData, _smoothedPlotData];
  493. }
  494. ///////
  495. // computeHermiteSmoothedData
  496. // A hermite spline smoothing of the plot data.
  497. // This implementation is derived from the one posted
  498. // by krypin on the jqplot-users mailing list:
  499. //
  500. // http://groups.google.com/group/jqplot-users/browse_thread/thread/748be6a445723cea?pli=1
  501. //
  502. // with a blog post:
  503. //
  504. // http://blog.statscollector.com/a-plugin-renderer-for-jqplot-to-draw-a-hermite-spline/
  505. //
  506. // and download of the original plugin:
  507. //
  508. // http://blog.statscollector.com/wp-content/uploads/2010/02/jqplot.hermiteSplineRenderer.js
  509. //////////
  510. // called with scope of series
  511. function computeHermiteSmoothedData (gd) {
  512. var smooth = this.renderer.smooth;
  513. var tension = this.renderer.tension;
  514. var dim = this.canvas.getWidth();
  515. var xp = this._xaxis.series_p2u;
  516. var yp = this._yaxis.series_p2u;
  517. var steps =null;
  518. var _steps = null;
  519. var a = null;
  520. var a1 = null;
  521. var a2 = null;
  522. var slope = null;
  523. var slope2 = null;
  524. var temp = null;
  525. var t, s, h1, h2, h3, h4;
  526. var TiX, TiY, Ti1X, Ti1Y;
  527. var pX, pY, p;
  528. var sd = [];
  529. var spd = [];
  530. var dist = gd.length/dim;
  531. var min, max, stretch, scale, shift;
  532. var _smoothedData = [];
  533. var _smoothedPlotData = [];
  534. if (!isNaN(parseFloat(smooth))) {
  535. steps = parseFloat(smooth);
  536. }
  537. else {
  538. steps = getSteps(dist, 0.5);
  539. }
  540. if (!isNaN(parseFloat(tension))) {
  541. tension = parseFloat(tension);
  542. }
  543. for (var i=0, l = gd.length-1; i < l; i++) {
  544. if (tension === null) {
  545. slope = Math.abs((gd[i+1][1] - gd[i][1]) / (gd[i+1][0] - gd[i][0]));
  546. min = 0.3;
  547. max = 0.6;
  548. stretch = (max - min)/2.0;
  549. scale = 2.5;
  550. shift = -1.4;
  551. temp = slope/scale + shift;
  552. a1 = stretch * tanh(temp) - stretch * tanh(shift) + min;
  553. // if have both left and right line segments, will use minimum tension.
  554. if (i > 0) {
  555. slope2 = Math.abs((gd[i][1] - gd[i-1][1]) / (gd[i][0] - gd[i-1][0]));
  556. }
  557. temp = slope2/scale + shift;
  558. a2 = stretch * tanh(temp) - stretch * tanh(shift) + min;
  559. a = (a1 + a2)/2.0;
  560. }
  561. else {
  562. a = tension;
  563. }
  564. for (t=0; t < steps; t++) {
  565. s = t / steps;
  566. h1 = (1 + 2*s)*Math.pow((1-s),2);
  567. h2 = s*Math.pow((1-s),2);
  568. h3 = Math.pow(s,2)*(3-2*s);
  569. h4 = Math.pow(s,2)*(s-1);
  570. if (gd[i-1]) {
  571. TiX = a * (gd[i+1][0] - gd[i-1][0]);
  572. TiY = a * (gd[i+1][1] - gd[i-1][1]);
  573. } else {
  574. TiX = a * (gd[i+1][0] - gd[i][0]);
  575. TiY = a * (gd[i+1][1] - gd[i][1]);
  576. }
  577. if (gd[i+2]) {
  578. Ti1X = a * (gd[i+2][0] - gd[i][0]);
  579. Ti1Y = a * (gd[i+2][1] - gd[i][1]);
  580. } else {
  581. Ti1X = a * (gd[i+1][0] - gd[i][0]);
  582. Ti1Y = a * (gd[i+1][1] - gd[i][1]);
  583. }
  584. pX = h1*gd[i][0] + h3*gd[i+1][0] + h2*TiX + h4*Ti1X;
  585. pY = h1*gd[i][1] + h3*gd[i+1][1] + h2*TiY + h4*Ti1Y;
  586. p = [pX, pY];
  587. _smoothedData.push(p);
  588. _smoothedPlotData.push([xp(pX), yp(pY)]);
  589. }
  590. }
  591. _smoothedData.push(gd[l]);
  592. _smoothedPlotData.push([xp(gd[l][0]), yp(gd[l][1])]);
  593. return [_smoothedData, _smoothedPlotData];
  594. }
  595. // setGridData
  596. // converts the user data values to grid coordinates and stores them
  597. // in the gridData array.
  598. // Called with scope of a series.
  599. $.jqplot.LineRenderer.prototype.setGridData = function(plot) {
  600. // recalculate the grid data
  601. var xp = this._xaxis.series_u2p;
  602. var yp = this._yaxis.series_u2p;
  603. var data = this._plotData;
  604. var pdata = this._prevPlotData;
  605. this.gridData = [];
  606. this._prevGridData = [];
  607. this.renderer._smoothedData = [];
  608. this.renderer._smoothedPlotData = [];
  609. this.renderer._hiBandGridData = [];
  610. this.renderer._lowBandGridData = [];
  611. this.renderer._hiBandSmoothedData = [];
  612. this.renderer._lowBandSmoothedData = [];
  613. var bands = this.renderer.bands;
  614. var hasNull = false;
  615. for (var i=0, l=data.length; i < l; i++) {
  616. // if not a line series or if no nulls in data, push the converted point onto the array.
  617. if (data[i][0] != null && data[i][1] != null) {
  618. this.gridData.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]);
  619. }
  620. // else if there is a null, preserve it.
  621. else if (data[i][0] == null) {
  622. hasNull = true;
  623. this.gridData.push([null, yp.call(this._yaxis, data[i][1])]);
  624. }
  625. else if (data[i][1] == null) {
  626. hasNull = true;
  627. this.gridData.push([xp.call(this._xaxis, data[i][0]), null]);
  628. }
  629. // if not a line series or if no nulls in data, push the converted point onto the array.
  630. if (pdata[i] != null && pdata[i][0] != null && pdata[i][1] != null) {
  631. this._prevGridData.push([xp.call(this._xaxis, pdata[i][0]), yp.call(this._yaxis, pdata[i][1])]);
  632. }
  633. // else if there is a null, preserve it.
  634. else if (pdata[i] != null && pdata[i][0] == null) {
  635. this._prevGridData.push([null, yp.call(this._yaxis, pdata[i][1])]);
  636. }
  637. else if (pdata[i] != null && pdata[i][0] != null && pdata[i][1] == null) {
  638. this._prevGridData.push([xp.call(this._xaxis, pdata[i][0]), null]);
  639. }
  640. }
  641. // don't do smoothing or bands on broken lines.
  642. if (hasNull) {
  643. this.renderer.smooth = false;
  644. if (this._type === 'line') {
  645. bands.show = false;
  646. }
  647. }
  648. if (this._type === 'line' && bands.show) {
  649. for (var i=0, l=bands.hiData.length; i<l; i++) {
  650. this.renderer._hiBandGridData.push([xp.call(this._xaxis, bands.hiData[i][0]), yp.call(this._yaxis, bands.hiData[i][1])]);
  651. }
  652. for (var i=0, l=bands.lowData.length; i<l; i++) {
  653. this.renderer._lowBandGridData.push([xp.call(this._xaxis, bands.lowData[i][0]), yp.call(this._yaxis, bands.lowData[i][1])]);
  654. }
  655. }
  656. // calculate smoothed data if enough points and no nulls
  657. if (this._type === 'line' && this.renderer.smooth && this.gridData.length > 2) {
  658. var ret;
  659. if (this.renderer.constrainSmoothing) {
  660. ret = computeConstrainedSmoothedData.call(this, this.gridData);
  661. this.renderer._smoothedData = ret[0];
  662. this.renderer._smoothedPlotData = ret[1];
  663. if (bands.show) {
  664. ret = computeConstrainedSmoothedData.call(this, this.renderer._hiBandGridData);
  665. this.renderer._hiBandSmoothedData = ret[0];
  666. ret = computeConstrainedSmoothedData.call(this, this.renderer._lowBandGridData);
  667. this.renderer._lowBandSmoothedData = ret[0];
  668. }
  669. ret = null;
  670. }
  671. else {
  672. ret = computeHermiteSmoothedData.call(this, this.gridData);
  673. this.renderer._smoothedData = ret[0];
  674. this.renderer._smoothedPlotData = ret[1];
  675. if (bands.show) {
  676. ret = computeHermiteSmoothedData.call(this, this.renderer._hiBandGridData);
  677. this.renderer._hiBandSmoothedData = ret[0];
  678. ret = computeHermiteSmoothedData.call(this, this.renderer._lowBandGridData);
  679. this.renderer._lowBandSmoothedData = ret[0];
  680. }
  681. ret = null;
  682. }
  683. }
  684. };
  685. // makeGridData
  686. // converts any arbitrary data values to grid coordinates and
  687. // returns them. This method exists so that plugins can use a series'
  688. // linerenderer to generate grid data points without overwriting the
  689. // grid data associated with that series.
  690. // Called with scope of a series.
  691. $.jqplot.LineRenderer.prototype.makeGridData = function(data, plot) {
  692. // recalculate the grid data
  693. var xp = this._xaxis.series_u2p;
  694. var yp = this._yaxis.series_u2p;
  695. var gd = [];
  696. var pgd = [];
  697. this.renderer._smoothedData = [];
  698. this.renderer._smoothedPlotData = [];
  699. this.renderer._hiBandGridData = [];
  700. this.renderer._lowBandGridData = [];
  701. this.renderer._hiBandSmoothedData = [];
  702. this.renderer._lowBandSmoothedData = [];
  703. var bands = this.renderer.bands;
  704. var hasNull = false;
  705. for (var i=0; i<data.length; i++) {
  706. // if not a line series or if no nulls in data, push the converted point onto the array.
  707. if (data[i][0] != null && data[i][1] != null) {
  708. gd.push([xp.call(this._xaxis, data[i][0]), yp.call(this._yaxis, data[i][1])]);
  709. }
  710. // else if there is a null, preserve it.
  711. else if (data[i][0] == null) {
  712. hasNull = true;
  713. gd.push([null, yp.call(this._yaxis, data[i][1])]);
  714. }
  715. else if (data[i][1] == null) {
  716. hasNull = true;
  717. gd.push([xp.call(this._xaxis, data[i][0]), null]);
  718. }
  719. }
  720. // don't do smoothing or bands on broken lines.
  721. if (hasNull) {
  722. this.renderer.smooth = false;
  723. if (this._type === 'line') {
  724. bands.show = false;
  725. }
  726. }
  727. if (this._type === 'line' && bands.show) {
  728. for (var i=0, l=bands.hiData.length; i<l; i++) {
  729. this.renderer._hiBandGridData.push([xp.call(this._xaxis, bands.hiData[i][0]), yp.call(this._yaxis, bands.hiData[i][1])]);
  730. }
  731. for (var i=0, l=bands.lowData.length; i<l; i++) {
  732. this.renderer._lowBandGridData.push([xp.call(this._xaxis, bands.lowData[i][0]), yp.call(this._yaxis, bands.lowData[i][1])]);
  733. }
  734. }
  735. if (this._type === 'line' && this.renderer.smooth && gd.length > 2) {
  736. var ret;
  737. if (this.renderer.constrainSmoothing) {
  738. ret = computeConstrainedSmoothedData.call(this, gd);
  739. this.renderer._smoothedData = ret[0];
  740. this.renderer._smoothedPlotData = ret[1];
  741. if (bands.show) {
  742. ret = computeConstrainedSmoothedData.call(this, this.renderer._hiBandGridData);
  743. this.renderer._hiBandSmoothedData = ret[0];
  744. ret = computeConstrainedSmoothedData.call(this, this.renderer._lowBandGridData);
  745. this.renderer._lowBandSmoothedData = ret[0];
  746. }
  747. ret = null;
  748. }
  749. else {
  750. ret = computeHermiteSmoothedData.call(this, gd);
  751. this.renderer._smoothedData = ret[0];
  752. this.renderer._smoothedPlotData = ret[1];
  753. if (bands.show) {
  754. ret = computeHermiteSmoothedData.call(this, this.renderer._hiBandGridData);
  755. this.renderer._hiBandSmoothedData = ret[0];
  756. ret = computeHermiteSmoothedData.call(this, this.renderer._lowBandGridData);
  757. this.renderer._lowBandSmoothedData = ret[0];
  758. }
  759. ret = null;
  760. }
  761. }
  762. return gd;
  763. };
  764. // called within scope of series.
  765. $.jqplot.LineRenderer.prototype.draw = function(ctx, gd, options, plot) {
  766. var i;
  767. // get a copy of the options, so we don't modify the original object.
  768. var opts = $.extend(true, {}, options);
  769. var shadow = (opts.shadow != undefined) ? opts.shadow : this.shadow;
  770. var showLine = (opts.showLine != undefined) ? opts.showLine : this.showLine;
  771. var fill = (opts.fill != undefined) ? opts.fill : this.fill;
  772. var fillAndStroke = (opts.fillAndStroke != undefined) ? opts.fillAndStroke : this.fillAndStroke;
  773. var xmin, ymin, xmax, ymax;
  774. ctx.save();
  775. if (gd.length) {
  776. if (showLine) {
  777. // if we fill, we'll have to add points to close the curve.
  778. if (fill) {
  779. if (this.fillToZero) {
  780. // have to break line up into shapes at axis crossings
  781. var negativeColor = this.negativeColor;
  782. if (! this.useNegativeColors) {
  783. negativeColor = opts.fillStyle;
  784. }
  785. var isnegative = false;
  786. var posfs = opts.fillStyle;
  787. // if stoking line as well as filling, get a copy of line data.
  788. if (fillAndStroke) {
  789. var fasgd = gd.slice(0);
  790. }
  791. // if not stacked, fill down to axis
  792. if (this.index == 0 || !this._stack) {
  793. var tempgd = [];
  794. var pd = (this.renderer.smooth) ? this.renderer._smoothedPlotData : this._plotData;
  795. this._areaPoints = [];
  796. var pyzero = this._yaxis.series_u2p(this.fillToValue);
  797. var pxzero = this._xaxis.series_u2p(this.fillToValue);
  798. opts.closePath = true;
  799. if (this.fillAxis == 'y') {
  800. tempgd.push([gd[0][0], pyzero]);
  801. this._areaPoints.push([gd[0][0], pyzero]);
  802. for (var i=0; i<gd.length-1; i++) {
  803. tempgd.push(gd[i]);
  804. this._areaPoints.push(gd[i]);
  805. // do we have an axis crossing?
  806. if (pd[i][1] * pd[i+1][1] <= 0) {
  807. if (pd[i][1] < 0) {
  808. isnegative = true;
  809. opts.fillStyle = negativeColor;
  810. }
  811. else {
  812. isnegative = false;
  813. opts.fillStyle = posfs;
  814. }
  815. var xintercept = gd[i][0] + (gd[i+1][0] - gd[i][0]) * (pyzero-gd[i][1])/(gd[i+1][1] - gd[i][1]);
  816. tempgd.push([xintercept, pyzero]);
  817. this._areaPoints.push([xintercept, pyzero]);
  818. // now draw this shape and shadow.
  819. if (shadow) {
  820. this.renderer.shadowRenderer.draw(ctx, tempgd, opts);
  821. }
  822. this.renderer.shapeRenderer.draw(ctx, tempgd, opts);
  823. // now empty temp array and continue
  824. tempgd = [[xintercept, pyzero]];
  825. // this._areaPoints = [[xintercept, pyzero]];
  826. }
  827. }
  828. if (pd[gd.length-1][1] < 0) {
  829. isnegative = true;
  830. opts.fillStyle = negativeColor;
  831. }
  832. else {
  833. isnegative = false;
  834. opts.fillStyle = posfs;
  835. }
  836. tempgd.push(gd[gd.length-1]);
  837. this._areaPoints.push(gd[gd.length-1]);
  838. tempgd.push([gd[gd.length-1][0], pyzero]);
  839. this._areaPoints.push([gd[gd.length-1][0], pyzero]);
  840. }
  841. // now draw the last area.
  842. if (shadow) {
  843. this.renderer.shadowRenderer.draw(ctx, tempgd, opts);
  844. }
  845. this.renderer.shapeRenderer.draw(ctx, tempgd, opts);
  846. // var gridymin = this._yaxis.series_u2p(0);
  847. // // IE doesn't return new length on unshift
  848. // gd.unshift([gd[0][0], gridymin]);
  849. // len = gd.length;
  850. // gd.push([gd[len - 1][0], gridymin]);
  851. }
  852. // if stacked, fill to line below
  853. else {
  854. var prev = this._prevGridData;
  855. for (var i=prev.length; i>0; i--) {
  856. gd.push(prev[i-1]);
  857. // this._areaPoints.push(prev[i-1]);
  858. }
  859. if (shadow) {
  860. this.renderer.shadowRenderer.draw(ctx, gd, opts);
  861. }
  862. this._areaPoints = gd;
  863. this.renderer.shapeRenderer.draw(ctx, gd, opts);
  864. }
  865. }
  866. /////////////////////////
  867. // Not filled to zero
  868. ////////////////////////
  869. else {
  870. // if stoking line as well as filling, get a copy of line data.
  871. if (fillAndStroke) {
  872. var fasgd = gd.slice(0);
  873. }
  874. // if not stacked, fill down to axis
  875. if (this.index == 0 || !this._stack) {
  876. // var gridymin = this._yaxis.series_u2p(this._yaxis.min) - this.gridBorderWidth / 2;
  877. var gridymin = ctx.canvas.height;
  878. // IE doesn't return new length on unshift
  879. gd.unshift([gd[0][0], gridymin]);
  880. var len = gd.length;
  881. gd.push([gd[len - 1][0], gridymin]);
  882. }
  883. // if stacked, fill to line below
  884. else {
  885. var prev = this._prevGridData;
  886. for (var i=prev.length; i>0; i--) {
  887. gd.push(prev[i-1]);
  888. }
  889. }
  890. this._areaPoints = gd;
  891. if (shadow) {
  892. this.renderer.shadowRenderer.draw(ctx, gd, opts);
  893. }
  894. this.renderer.shapeRenderer.draw(ctx, gd, opts);
  895. }
  896. if (fillAndStroke) {
  897. var fasopts = $.extend(true, {}, opts, {fill:false, closePath:false});
  898. this.renderer.shapeRenderer.draw(ctx, fasgd, fasopts);
  899. //////////
  900. // TODO: figure out some way to do shadows nicely
  901. // if (shadow) {
  902. // this.renderer.shadowRenderer.draw(ctx, fasgd, fasopts);
  903. // }
  904. // now draw the markers
  905. if (this.markerRenderer.show) {
  906. if (this.renderer.smooth) {
  907. fasgd = this.gridData;
  908. }
  909. for (i=0; i<fasgd.length; i++) {
  910. this.markerRenderer.draw(fasgd[i][0], fasgd[i][1], ctx, opts.markerOptions);
  911. }
  912. }
  913. }
  914. }
  915. else {
  916. if (this.renderer.bands.show) {
  917. var bdat;
  918. var bopts = $.extend(true, {}, opts);
  919. if (this.renderer.bands.showLines) {
  920. bdat = (this.renderer.smooth) ? this.renderer._hiBandSmoothedData : this.renderer._hiBandGridData;
  921. this.renderer.shapeRenderer.draw(ctx, bdat, opts);
  922. bdat = (this.renderer.smooth) ? this.renderer._lowBandSmoothedData : this.renderer._lowBandGridData;
  923. this.renderer.shapeRenderer.draw(ctx, bdat, bopts);
  924. }
  925. if (this.renderer.bands.fill) {
  926. if (this.renderer.smooth) {
  927. bdat = this.renderer._hiBandSmoothedData.concat(this.renderer._lowBandSmoothedData.reverse());
  928. }
  929. else {
  930. bdat = this.renderer._hiBandGridData.concat(this.renderer._lowBandGridData.reverse());
  931. }
  932. this._areaPoints = bdat;
  933. bopts.closePath = true;
  934. bopts.fill = true;
  935. bopts.fillStyle = this.renderer.bands.fillColor;
  936. this.renderer.shapeRenderer.draw(ctx, bdat, bopts);
  937. }
  938. }
  939. if (shadow) {
  940. this.renderer.shadowRenderer.draw(ctx, gd, opts);
  941. }
  942. this.renderer.shapeRenderer.draw(ctx, gd, opts);
  943. }
  944. }
  945. // calculate the bounding box
  946. var xmin = xmax = ymin = ymax = null;
  947. for (i=0; i<this._areaPoints.length; i++) {
  948. var p = this._areaPoints[i];
  949. if (xmin > p[0] || xmin == null) {
  950. xmin = p[0];
  951. }
  952. if (ymax < p[1] || ymax == null) {
  953. ymax = p[1];
  954. }
  955. if (xmax < p[0] || xmax == null) {
  956. xmax = p[0];
  957. }
  958. if (ymin > p[1] || ymin == null) {
  959. ymin = p[1];
  960. }
  961. }
  962. if (this.type === 'line' && this.renderer.bands.show) {
  963. ymax = this._yaxis.series_u2p(this.renderer.bands._min);
  964. ymin = this._yaxis.series_u2p(this.renderer.bands._max);
  965. }
  966. this._boundingBox = [[xmin, ymax], [xmax, ymin]];
  967. // now draw the markers
  968. if (this.markerRenderer.show && !fill) {
  969. if (this.renderer.smooth) {
  970. gd = this.gridData;
  971. }
  972. for (i=0; i<gd.length; i++) {
  973. if (gd[i][0] != null && gd[i][1] != null) {
  974. this.markerRenderer.draw(gd[i][0], gd[i][1], ctx, opts.markerOptions);
  975. }
  976. }
  977. }
  978. }
  979. ctx.restore();
  980. };
  981. $.jqplot.LineRenderer.prototype.drawShadow = function(ctx, gd, options) {
  982. // This is a no-op, shadows drawn with lines.
  983. };
  984. // called with scope of plot.
  985. // make sure to not leave anything highlighted.
  986. function postInit(target, data, options) {
  987. for (var i=0; i<this.series.length; i++) {
  988. if (this.series[i].renderer.constructor == $.jqplot.LineRenderer) {
  989. // don't allow mouseover and mousedown at same time.
  990. if (this.series[i].highlightMouseOver) {
  991. this.series[i].highlightMouseDown = false;
  992. }
  993. }
  994. }
  995. }
  996. // called within context of plot
  997. // create a canvas which we can draw on.
  998. // insert it before the eventCanvas, so eventCanvas will still capture events.
  999. function postPlotDraw() {
  1000. // Memory Leaks patch
  1001. if (this.plugins.lineRenderer && this.plugins.lineRenderer.highlightCanvas) {
  1002. this.plugins.lineRenderer.highlightCanvas.resetCanvas();
  1003. this.plugins.lineRenderer.highlightCanvas = null;
  1004. }
  1005. this.plugins.lineRenderer.highlightedSeriesIndex = null;
  1006. this.plugins.lineRenderer.highlightCanvas = new $.jqplot.GenericCanvas();
  1007. this.eventCanvas._elem.before(this.plugins.lineRenderer.highlightCanvas.createElement(this._gridPadding, 'jqplot-lineRenderer-highlight-canvas', this._plotDimensions, this));
  1008. this.plugins.lineRenderer.highlightCanvas.setContext();
  1009. this.eventCanvas._elem.bind('mouseleave', {plot:this}, function (ev) { unhighlight(ev.data.plot); });
  1010. }
  1011. function highlight (plot, sidx, pidx, points) {
  1012. var s = plot.series[sidx];
  1013. var canvas = plot.plugins.lineRenderer.highlightCanvas;
  1014. canvas._ctx.clearRect(0,0,canvas._ctx.canvas.width, canvas._ctx.canvas.height);
  1015. s._highlightedPoint = pidx;
  1016. plot.plugins.lineRenderer.highlightedSeriesIndex = sidx;
  1017. var opts = {fillStyle: s.highlightColor};
  1018. if (s.type === 'line' && s.renderer.bands.show) {
  1019. opts.fill = true;
  1020. opts.closePath = true;
  1021. }
  1022. s.renderer.shapeRenderer.draw(canvas._ctx, points, opts);
  1023. canvas = null;
  1024. }
  1025. function unhighlight (plot) {
  1026. var canvas = plot.plugins.lineRenderer.highlightCanvas;
  1027. canvas._ctx.clearRect(0,0, canvas._ctx.canvas.width, canvas._ctx.canvas.height);
  1028. for (var i=0; i<plot.series.length; i++) {
  1029. plot.series[i]._highlightedPoint = null;
  1030. }
  1031. plot.plugins.lineRenderer.highlightedSeriesIndex = null;
  1032. plot.target.trigger('jqplotDataUnhighlight');
  1033. canvas = null;
  1034. }
  1035. function handleMove(ev, gridpos, datapos, neighbor, plot) {
  1036. if (neighbor) {
  1037. var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
  1038. var evt1 = jQuery.Event('jqplotDataMouseOver');
  1039. evt1.pageX = ev.pageX;
  1040. evt1.pageY = ev.pageY;
  1041. plot.target.trigger(evt1, ins);
  1042. if (plot.series[ins[0]].highlightMouseOver && !(ins[0] == plot.plugins.lineRenderer.highlightedSeriesIndex)) {
  1043. var evt = jQuery.Event('jqplotDataHighlight');
  1044. evt.which = ev.which;
  1045. evt.pageX = ev.pageX;
  1046. evt.pageY = ev.pageY;
  1047. plot.target.trigger(evt, ins);
  1048. highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
  1049. }
  1050. }
  1051. else if (neighbor == null) {
  1052. unhighlight (plot);
  1053. }
  1054. }
  1055. function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
  1056. if (neighbor) {
  1057. var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
  1058. if (plot.series[ins[0]].highlightMouseDown && !(ins[0] == plot.plugins.lineRenderer.highlightedSeriesIndex)) {
  1059. var evt = jQuery.Event('jqplotDataHighlight');
  1060. evt.which = ev.which;
  1061. evt.pageX = ev.pageX;
  1062. evt.pageY = ev.pageY;
  1063. plot.target.trigger(evt, ins);
  1064. highlight (plot, neighbor.seriesIndex, neighbor.pointIndex, neighbor.points);
  1065. }
  1066. }
  1067. else if (neighbor == null) {
  1068. unhighlight (plot);
  1069. }
  1070. }
  1071. function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
  1072. var idx = plot.plugins.lineRenderer.highlightedSeriesIndex;
  1073. if (idx != null && plot.series[idx].highlightMouseDown) {
  1074. unhighlight(plot);
  1075. }
  1076. }
  1077. function handleClick(ev, gridpos, datapos, neighbor, plot) {
  1078. if (neighbor) {
  1079. var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
  1080. var evt = jQuery.Event('jqplotDataClick');
  1081. evt.which = ev.which;
  1082. evt.pageX = ev.pageX;
  1083. evt.pageY = ev.pageY;
  1084. plot.target.trigger(evt, ins);
  1085. }
  1086. }
  1087. function handleRightClick(ev, gridpos, datapos, neighbor, plot) {
  1088. if (neighbor) {
  1089. var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data];
  1090. var idx = plot.plugins.lineRenderer.highlightedSeriesIndex;
  1091. if (idx != null && plot.series[idx].highlightMouseDown) {
  1092. unhighlight(plot);
  1093. }
  1094. var evt = jQuery.Event('jqplotDataRightClick');
  1095. evt.which = ev.which;
  1096. evt.pageX = ev.pageX;
  1097. evt.pageY = ev.pageY;
  1098. plot.target.trigger(evt, ins);
  1099. }
  1100. }
  1101. })(jQuery);