jqplot.canvasOverlay.js 36 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865
  1. /**
  2. * jqPlot
  3. * Pure JavaScript plotting plugin using jQuery
  4. *
  5. * Version: 1.0.2
  6. * Revision: 1108
  7. *
  8. * Copyright (c) 2009-2011 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. var objCounter = 0;
  33. // class: $.jqplot.CanvasOverlay
  34. $.jqplot.CanvasOverlay = function(opts){
  35. var options = opts || {};
  36. this.options = {
  37. show: $.jqplot.config.enablePlugins,
  38. deferDraw: false
  39. };
  40. // prop: objects
  41. this.objects = [];
  42. this.objectNames = [];
  43. this.canvas = null;
  44. this.markerRenderer = new $.jqplot.MarkerRenderer({style:'line'});
  45. this.markerRenderer.init();
  46. this.highlightObjectIndex = null;
  47. if (options.objects) {
  48. var objs = options.objects,
  49. obj;
  50. for (var i=0; i<objs.length; i++) {
  51. obj = objs[i];
  52. for (var n in obj) {
  53. switch (n) {
  54. case 'line':
  55. this.addLine(obj[n]);
  56. break;
  57. case 'horizontalLine':
  58. this.addHorizontalLine(obj[n]);
  59. break;
  60. case 'dashedHorizontalLine':
  61. this.addDashedHorizontalLine(obj[n]);
  62. break;
  63. case 'verticalLine':
  64. this.addVerticalLine(obj[n]);
  65. break;
  66. case 'dashedVerticalLine':
  67. this.addDashedVerticalLine(obj[n]);
  68. break;
  69. default:
  70. break;
  71. }
  72. }
  73. }
  74. }
  75. $.extend(true, this.options, options);
  76. };
  77. // called with scope of a plot object
  78. $.jqplot.CanvasOverlay.postPlotInit = function (target, data, opts) {
  79. var options = opts || {};
  80. // add a canvasOverlay attribute to the plot
  81. this.plugins.canvasOverlay = new $.jqplot.CanvasOverlay(options.canvasOverlay);
  82. };
  83. function LineBase() {
  84. this.uid = null;
  85. this.type = null;
  86. this.gridStart = null;
  87. this.gridStop = null;
  88. this.tooltipWidthFactor = 0;
  89. this.options = {
  90. // prop: name
  91. // Optional name for the overlay object.
  92. // Can be later used to retrieve the object by name.
  93. name: null,
  94. // prop: show
  95. // true to show (draw), false to not draw.
  96. show: true,
  97. // prop: lineWidth
  98. // Width of the line.
  99. lineWidth: 2,
  100. // prop: lineCap
  101. // Type of ending placed on the line ['round', 'butt', 'square']
  102. lineCap: 'round',
  103. // prop: color
  104. // color of the line
  105. color: '#666666',
  106. // prop: shadow
  107. // wether or not to draw a shadow on the line
  108. shadow: true,
  109. // prop: shadowAngle
  110. // Shadow angle in degrees
  111. shadowAngle: 45,
  112. // prop: shadowOffset
  113. // Shadow offset from line in pixels
  114. shadowOffset: 1,
  115. // prop: shadowDepth
  116. // Number of times shadow is stroked, each stroke offset shadowOffset from the last.
  117. shadowDepth: 3,
  118. // prop: shadowAlpha
  119. // Alpha channel transparency of shadow. 0 = transparent.
  120. shadowAlpha: '0.07',
  121. // prop: xaxis
  122. // X axis to use for positioning/scaling the line.
  123. xaxis: 'xaxis',
  124. // prop: yaxis
  125. // Y axis to use for positioning/scaling the line.
  126. yaxis: 'yaxis',
  127. // prop: showTooltip
  128. // Show a tooltip with data point values.
  129. showTooltip: false,
  130. // prop: showTooltipPrecision
  131. // Controls how close to line cursor must be to show tooltip.
  132. // Higher number = closer to line, lower number = farther from line.
  133. // 1.0 = cursor must be over line.
  134. showTooltipPrecision: 0.6,
  135. // prop: tooltipLocation
  136. // Where to position tooltip, 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
  137. tooltipLocation: 'nw',
  138. // prop: fadeTooltip
  139. // true = fade in/out tooltip, flase = show/hide tooltip
  140. fadeTooltip: true,
  141. // prop: tooltipFadeSpeed
  142. // 'slow', 'def', 'fast', or number of milliseconds.
  143. tooltipFadeSpeed: "fast",
  144. // prop: tooltipOffset
  145. // Pixel offset of tooltip from the highlight.
  146. tooltipOffset: 4,
  147. // prop: tooltipFormatString
  148. // Format string passed the x and y values of the cursor on the line.
  149. // e.g., 'Dogs: %.2f, Cats: %d'.
  150. tooltipFormatString: '%d, %d'
  151. };
  152. }
  153. /**
  154. * Class: Line
  155. * A straight line.
  156. */
  157. function Line(options) {
  158. LineBase.call(this);
  159. this.type = 'line';
  160. var opts = {
  161. // prop: start
  162. // [x, y] coordinates for the start of the line.
  163. start: [],
  164. // prop: stop
  165. // [x, y] coordinates for the end of the line.
  166. stop: []
  167. };
  168. $.extend(true, this.options, opts, options);
  169. if (this.options.showTooltipPrecision < 0.01) {
  170. this.options.showTooltipPrecision = 0.01;
  171. }
  172. }
  173. Line.prototype = new LineBase();
  174. Line.prototype.constructor = Line;
  175. /**
  176. * Class: HorizontalLine
  177. * A straight horizontal line.
  178. */
  179. function HorizontalLine(options) {
  180. LineBase.call(this);
  181. this.type = 'horizontalLine';
  182. var opts = {
  183. // prop: y
  184. // y value to position the line
  185. y: null,
  186. // prop: xmin
  187. // x value for the start of the line, null to scale to axis min.
  188. xmin: null,
  189. // prop: xmax
  190. // x value for the end of the line, null to scale to axis max.
  191. xmax: null,
  192. // prop xOffset
  193. // offset ends of the line inside the grid. Number
  194. xOffset: '6px', // number or string. Number interpreted as units, string as pixels.
  195. xminOffset: null,
  196. xmaxOffset: null
  197. };
  198. $.extend(true, this.options, opts, options);
  199. if (this.options.showTooltipPrecision < 0.01) {
  200. this.options.showTooltipPrecision = 0.01;
  201. }
  202. }
  203. HorizontalLine.prototype = new LineBase();
  204. HorizontalLine.prototype.constructor = HorizontalLine;
  205. /**
  206. * Class: DashedHorizontalLine
  207. * A straight dashed horizontal line.
  208. */
  209. function DashedHorizontalLine(options) {
  210. LineBase.call(this);
  211. this.type = 'dashedHorizontalLine';
  212. var opts = {
  213. y: null,
  214. xmin: null,
  215. xmax: null,
  216. xOffset: '6px', // number or string. Number interpreted as units, string as pixels.
  217. xminOffset: null,
  218. xmaxOffset: null,
  219. // prop: dashPattern
  220. // Array of line, space settings in pixels.
  221. // Default is 8 pixel of line, 8 pixel of space.
  222. // Note, limit to a 2 element array b/c of bug with higher order arrays.
  223. dashPattern: [8,8]
  224. };
  225. $.extend(true, this.options, opts, options);
  226. if (this.options.showTooltipPrecision < 0.01) {
  227. this.options.showTooltipPrecision = 0.01;
  228. }
  229. }
  230. DashedHorizontalLine.prototype = new LineBase();
  231. DashedHorizontalLine.prototype.constructor = DashedHorizontalLine;
  232. /**
  233. * Class: VerticalLine
  234. * A straight vertical line.
  235. */
  236. function VerticalLine(options) {
  237. LineBase.call(this);
  238. this.type = 'verticalLine';
  239. var opts = {
  240. x: null,
  241. ymin: null,
  242. ymax: null,
  243. yOffset: '6px', // number or string. Number interpreted as units, string as pixels.
  244. yminOffset: null,
  245. ymaxOffset: null
  246. };
  247. $.extend(true, this.options, opts, options);
  248. if (this.options.showTooltipPrecision < 0.01) {
  249. this.options.showTooltipPrecision = 0.01;
  250. }
  251. }
  252. VerticalLine.prototype = new LineBase();
  253. VerticalLine.prototype.constructor = VerticalLine;
  254. /**
  255. * Class: DashedVerticalLine
  256. * A straight dashed vertical line.
  257. */
  258. function DashedVerticalLine(options) {
  259. LineBase.call(this);
  260. this.type = 'dashedVerticalLine';
  261. this.start = null;
  262. this.stop = null;
  263. var opts = {
  264. x: null,
  265. ymin: null,
  266. ymax: null,
  267. yOffset: '6px', // number or string. Number interpreted as units, string as pixels.
  268. yminOffset: null,
  269. ymaxOffset: null,
  270. // prop: dashPattern
  271. // Array of line, space settings in pixels.
  272. // Default is 8 pixel of line, 8 pixel of space.
  273. // Note, limit to a 2 element array b/c of bug with higher order arrays.
  274. dashPattern: [8,8]
  275. };
  276. $.extend(true, this.options, opts, options);
  277. if (this.options.showTooltipPrecision < 0.01) {
  278. this.options.showTooltipPrecision = 0.01;
  279. }
  280. }
  281. DashedVerticalLine.prototype = new LineBase();
  282. DashedVerticalLine.prototype.constructor = DashedVerticalLine;
  283. $.jqplot.CanvasOverlay.prototype.addLine = function(opts) {
  284. var line = new Line(opts);
  285. line.uid = objCounter++;
  286. this.objects.push(line);
  287. this.objectNames.push(line.options.name);
  288. };
  289. $.jqplot.CanvasOverlay.prototype.addHorizontalLine = function(opts) {
  290. var line = new HorizontalLine(opts);
  291. line.uid = objCounter++;
  292. this.objects.push(line);
  293. this.objectNames.push(line.options.name);
  294. };
  295. $.jqplot.CanvasOverlay.prototype.addDashedHorizontalLine = function(opts) {
  296. var line = new DashedHorizontalLine(opts);
  297. line.uid = objCounter++;
  298. this.objects.push(line);
  299. this.objectNames.push(line.options.name);
  300. };
  301. $.jqplot.CanvasOverlay.prototype.addVerticalLine = function(opts) {
  302. var line = new VerticalLine(opts);
  303. line.uid = objCounter++;
  304. this.objects.push(line);
  305. this.objectNames.push(line.options.name);
  306. };
  307. $.jqplot.CanvasOverlay.prototype.addDashedVerticalLine = function(opts) {
  308. var line = new DashedVerticalLine(opts);
  309. line.uid = objCounter++;
  310. this.objects.push(line);
  311. this.objectNames.push(line.options.name);
  312. };
  313. $.jqplot.CanvasOverlay.prototype.removeObject = function(idx) {
  314. // check if integer, remove by index
  315. if ($.type(idx) == 'number') {
  316. this.objects.splice(idx, 1);
  317. this.objectNames.splice(idx, 1);
  318. }
  319. // if string, remove by name
  320. else {
  321. var id = $.inArray(idx, this.objectNames);
  322. if (id != -1) {
  323. this.objects.splice(id, 1);
  324. this.objectNames.splice(id, 1);
  325. }
  326. }
  327. };
  328. $.jqplot.CanvasOverlay.prototype.getObject = function(idx) {
  329. // check if integer, remove by index
  330. if ($.type(idx) == 'number') {
  331. return this.objects[idx];
  332. }
  333. // if string, remove by name
  334. else {
  335. var id = $.inArray(idx, this.objectNames);
  336. if (id != -1) {
  337. return this.objects[id];
  338. }
  339. }
  340. };
  341. // Set get as alias for getObject.
  342. $.jqplot.CanvasOverlay.prototype.get = $.jqplot.CanvasOverlay.prototype.getObject;
  343. $.jqplot.CanvasOverlay.prototype.clear = function(plot) {
  344. this.canvas._ctx.clearRect(0,0,this.canvas.getWidth(), this.canvas.getHeight());
  345. };
  346. $.jqplot.CanvasOverlay.prototype.draw = function(plot) {
  347. var obj,
  348. objs = this.objects,
  349. mr = this.markerRenderer,
  350. start,
  351. stop;
  352. if (this.options.show) {
  353. this.canvas._ctx.clearRect(0,0,this.canvas.getWidth(), this.canvas.getHeight());
  354. for (var k=0; k<objs.length; k++) {
  355. obj = objs[k];
  356. var opts = $.extend(true, {}, obj.options);
  357. if (obj.options.show) {
  358. // style and shadow properties should be set before
  359. // every draw of marker renderer.
  360. mr.shadow = obj.options.shadow;
  361. obj.tooltipWidthFactor = obj.options.lineWidth / obj.options.showTooltipPrecision;
  362. switch (obj.type) {
  363. case 'line':
  364. // style and shadow properties should be set before
  365. // every draw of marker renderer.
  366. mr.style = 'line';
  367. opts.closePath = false;
  368. start = [plot.axes[obj.options.xaxis].series_u2p(obj.options.start[0]), plot.axes[obj.options.yaxis].series_u2p(obj.options.start[1])];
  369. stop = [plot.axes[obj.options.xaxis].series_u2p(obj.options.stop[0]), plot.axes[obj.options.yaxis].series_u2p(obj.options.stop[1])];
  370. obj.gridStart = start;
  371. obj.gridStop = stop;
  372. mr.draw(start, stop, this.canvas._ctx, opts);
  373. break;
  374. case 'horizontalLine':
  375. // style and shadow properties should be set before
  376. // every draw of marker renderer.
  377. if (obj.options.y != null) {
  378. mr.style = 'line';
  379. opts.closePath = false;
  380. var xaxis = plot.axes[obj.options.xaxis],
  381. xstart,
  382. xstop,
  383. y = plot.axes[obj.options.yaxis].series_u2p(obj.options.y),
  384. xminoff = obj.options.xminOffset || obj.options.xOffset,
  385. xmaxoff = obj.options.xmaxOffset || obj.options.xOffset;
  386. if (obj.options.xmin != null) {
  387. xstart = xaxis.series_u2p(obj.options.xmin);
  388. }
  389. else if (xminoff != null) {
  390. if ($.type(xminoff) == "number") {
  391. xstart = xaxis.series_u2p(xaxis.min + xminoff);
  392. }
  393. else if ($.type(xminoff) == "string") {
  394. xstart = xaxis.series_u2p(xaxis.min) + parseFloat(xminoff);
  395. }
  396. }
  397. if (obj.options.xmax != null) {
  398. xstop = xaxis.series_u2p(obj.options.xmax);
  399. }
  400. else if (xmaxoff != null) {
  401. if ($.type(xmaxoff) == "number") {
  402. xstop = xaxis.series_u2p(xaxis.max - xmaxoff);
  403. }
  404. else if ($.type(xmaxoff) == "string") {
  405. xstop = xaxis.series_u2p(xaxis.max) - parseFloat(xmaxoff);
  406. }
  407. }
  408. if (xstop != null && xstart != null) {
  409. obj.gridStart = [xstart, y];
  410. obj.gridStop = [xstop, y];
  411. mr.draw([xstart, y], [xstop, y], this.canvas._ctx, opts);
  412. }
  413. }
  414. break;
  415. case 'dashedHorizontalLine':
  416. var dashPat = obj.options.dashPattern;
  417. var dashPatLen = 0;
  418. for (var i=0; i<dashPat.length; i++) {
  419. dashPatLen += dashPat[i];
  420. }
  421. // style and shadow properties should be set before
  422. // every draw of marker renderer.
  423. if (obj.options.y != null) {
  424. mr.style = 'line';
  425. opts.closePath = false;
  426. var xaxis = plot.axes[obj.options.xaxis],
  427. xstart,
  428. xstop,
  429. y = plot.axes[obj.options.yaxis].series_u2p(obj.options.y),
  430. xminoff = obj.options.xminOffset || obj.options.xOffset,
  431. xmaxoff = obj.options.xmaxOffset || obj.options.xOffset;
  432. if (obj.options.xmin != null) {
  433. xstart = xaxis.series_u2p(obj.options.xmin);
  434. }
  435. else if (xminoff != null) {
  436. if ($.type(xminoff) == "number") {
  437. xstart = xaxis.series_u2p(xaxis.min + xminoff);
  438. }
  439. else if ($.type(xminoff) == "string") {
  440. xstart = xaxis.series_u2p(xaxis.min) + parseFloat(xminoff);
  441. }
  442. }
  443. if (obj.options.xmax != null) {
  444. xstop = xaxis.series_u2p(obj.options.xmax);
  445. }
  446. else if (xmaxoff != null) {
  447. if ($.type(xmaxoff) == "number") {
  448. xstop = xaxis.series_u2p(xaxis.max - xmaxoff);
  449. }
  450. else if ($.type(xmaxoff) == "string") {
  451. xstop = xaxis.series_u2p(xaxis.max) - parseFloat(xmaxoff);
  452. }
  453. }
  454. if (xstop != null && xstart != null) {
  455. obj.gridStart = [xstart, y];
  456. obj.gridStop = [xstop, y];
  457. var numDash = Math.ceil((xstop - xstart)/dashPatLen);
  458. var b=xstart, e;
  459. for (var i=0; i<numDash; i++) {
  460. for (var j=0; j<dashPat.length; j+=2) {
  461. e = b+dashPat[j];
  462. mr.draw([b, y], [e, y], this.canvas._ctx, opts);
  463. b += dashPat[j];
  464. if (j < dashPat.length-1) {
  465. b += dashPat[j+1];
  466. }
  467. }
  468. }
  469. }
  470. }
  471. break;
  472. case 'verticalLine':
  473. // style and shadow properties should be set before
  474. // every draw of marker renderer.
  475. if (obj.options.x != null) {
  476. mr.style = 'line';
  477. opts.closePath = false;
  478. var yaxis = plot.axes[obj.options.yaxis],
  479. ystart,
  480. ystop,
  481. x = plot.axes[obj.options.xaxis].series_u2p(obj.options.x),
  482. yminoff = obj.options.yminOffset || obj.options.yOffset,
  483. ymaxoff = obj.options.ymaxOffset || obj.options.yOffset;
  484. if (obj.options.ymin != null) {
  485. ystart = yaxis.series_u2p(obj.options.ymin);
  486. }
  487. else if (yminoff != null) {
  488. if ($.type(yminoff) == "number") {
  489. ystart = yaxis.series_u2p(yaxis.min - yminoff);
  490. }
  491. else if ($.type(yminoff) == "string") {
  492. ystart = yaxis.series_u2p(yaxis.min) - parseFloat(yminoff);
  493. }
  494. }
  495. if (obj.options.ymax != null) {
  496. ystop = yaxis.series_u2p(obj.options.ymax);
  497. }
  498. else if (ymaxoff != null) {
  499. if ($.type(ymaxoff) == "number") {
  500. ystop = yaxis.series_u2p(yaxis.max + ymaxoff);
  501. }
  502. else if ($.type(ymaxoff) == "string") {
  503. ystop = yaxis.series_u2p(yaxis.max) + parseFloat(ymaxoff);
  504. }
  505. }
  506. if (ystop != null && ystart != null) {
  507. obj.gridStart = [x, ystart];
  508. obj.gridStop = [x, ystop];
  509. mr.draw([x, ystart], [x, ystop], this.canvas._ctx, opts);
  510. }
  511. }
  512. break;
  513. case 'dashedVerticalLine':
  514. var dashPat = obj.options.dashPattern;
  515. var dashPatLen = 0;
  516. for (var i=0; i<dashPat.length; i++) {
  517. dashPatLen += dashPat[i];
  518. }
  519. // style and shadow properties should be set before
  520. // every draw of marker renderer.
  521. if (obj.options.x != null) {
  522. mr.style = 'line';
  523. opts.closePath = false;
  524. var yaxis = plot.axes[obj.options.yaxis],
  525. ystart,
  526. ystop,
  527. x = plot.axes[obj.options.xaxis].series_u2p(obj.options.x),
  528. yminoff = obj.options.yminOffset || obj.options.yOffset,
  529. ymaxoff = obj.options.ymaxOffset || obj.options.yOffset;
  530. if (obj.options.ymin != null) {
  531. ystart = yaxis.series_u2p(obj.options.ymin);
  532. }
  533. else if (yminoff != null) {
  534. if ($.type(yminoff) == "number") {
  535. ystart = yaxis.series_u2p(yaxis.min - yminoff);
  536. }
  537. else if ($.type(yminoff) == "string") {
  538. ystart = yaxis.series_u2p(yaxis.min) - parseFloat(yminoff);
  539. }
  540. }
  541. if (obj.options.ymax != null) {
  542. ystop = yaxis.series_u2p(obj.options.ymax);
  543. }
  544. else if (ymaxoff != null) {
  545. if ($.type(ymaxoff) == "number") {
  546. ystop = yaxis.series_u2p(yaxis.max + ymaxoff);
  547. }
  548. else if ($.type(ymaxoff) == "string") {
  549. ystop = yaxis.series_u2p(yaxis.max) + parseFloat(ymaxoff);
  550. }
  551. }
  552. if (ystop != null && ystart != null) {
  553. obj.gridStart = [x, ystart];
  554. obj.gridStop = [x, ystop];
  555. var numDash = Math.ceil((ystart - ystop)/dashPatLen);
  556. var firstDashAdjust = ((numDash * dashPatLen) - (ystart - ystop))/2.0;
  557. var b=ystart, e, bs, es;
  558. for (var i=0; i<numDash; i++) {
  559. for (var j=0; j<dashPat.length; j+=2) {
  560. e = b - dashPat[j];
  561. if (e < ystop) {
  562. e = ystop;
  563. }
  564. if (b < ystop) {
  565. b = ystop;
  566. }
  567. // es = e;
  568. // if (i == 0) {
  569. // es += firstDashAdjust;
  570. // }
  571. mr.draw([x, b], [x, e], this.canvas._ctx, opts);
  572. b -= dashPat[j];
  573. if (j < dashPat.length-1) {
  574. b -= dashPat[j+1];
  575. }
  576. }
  577. }
  578. }
  579. }
  580. break;
  581. default:
  582. break;
  583. }
  584. }
  585. }
  586. }
  587. };
  588. // called within context of plot
  589. // create a canvas which we can draw on.
  590. // insert it before the eventCanvas, so eventCanvas will still capture events.
  591. $.jqplot.CanvasOverlay.postPlotDraw = function() {
  592. var co = this.plugins.canvasOverlay;
  593. // Memory Leaks patch
  594. if (co && co.highlightCanvas) {
  595. co.highlightCanvas.resetCanvas();
  596. co.highlightCanvas = null;
  597. }
  598. co.canvas = new $.jqplot.GenericCanvas();
  599. this.eventCanvas._elem.before(co.canvas.createElement(this._gridPadding, 'jqplot-overlayCanvas-canvas', this._plotDimensions, this));
  600. co.canvas.setContext();
  601. if (!co.deferDraw) {
  602. co.draw(this);
  603. }
  604. var elem = document.createElement('div');
  605. co._tooltipElem = $(elem);
  606. elem = null;
  607. co._tooltipElem.addClass('jqplot-canvasOverlay-tooltip');
  608. co._tooltipElem.css({position:'absolute', display:'none'});
  609. this.eventCanvas._elem.before(co._tooltipElem);
  610. this.eventCanvas._elem.bind('mouseleave', { elem: co._tooltipElem }, function (ev) { ev.data.elem.hide(); });
  611. var co = null;
  612. };
  613. function showTooltip(plot, obj, gridpos, datapos) {
  614. var co = plot.plugins.canvasOverlay;
  615. var elem = co._tooltipElem;
  616. var opts = obj.options, x, y;
  617. elem.html($.jqplot.sprintf(opts.tooltipFormatString, datapos[0], datapos[1]));
  618. switch (opts.tooltipLocation) {
  619. case 'nw':
  620. x = gridpos[0] + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset;
  621. y = gridpos[1] + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true);
  622. break;
  623. case 'n':
  624. x = gridpos[0] + plot._gridPadding.left - elem.outerWidth(true)/2;
  625. y = gridpos[1] + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true);
  626. break;
  627. case 'ne':
  628. x = gridpos[0] + plot._gridPadding.left + opts.tooltipOffset;
  629. y = gridpos[1] + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true);
  630. break;
  631. case 'e':
  632. x = gridpos[0] + plot._gridPadding.left + opts.tooltipOffset;
  633. y = gridpos[1] + plot._gridPadding.top - elem.outerHeight(true)/2;
  634. break;
  635. case 'se':
  636. x = gridpos[0] + plot._gridPadding.left + opts.tooltipOffset;
  637. y = gridpos[1] + plot._gridPadding.top + opts.tooltipOffset;
  638. break;
  639. case 's':
  640. x = gridpos[0] + plot._gridPadding.left - elem.outerWidth(true)/2;
  641. y = gridpos[1] + plot._gridPadding.top + opts.tooltipOffset;
  642. break;
  643. case 'sw':
  644. x = gridpos[0] + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset;
  645. y = gridpos[1] + plot._gridPadding.top + opts.tooltipOffset;
  646. break;
  647. case 'w':
  648. x = gridpos[0] + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset;
  649. y = gridpos[1] + plot._gridPadding.top - elem.outerHeight(true)/2;
  650. break;
  651. default: // same as 'nw'
  652. x = gridpos[0] + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset;
  653. y = gridpos[1] + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true);
  654. break;
  655. }
  656. elem.css('left', x);
  657. elem.css('top', y);
  658. if (opts.fadeTooltip) {
  659. // Fix for stacked up animations. Thnanks Trevor!
  660. elem.stop(true,true).fadeIn(opts.tooltipFadeSpeed);
  661. }
  662. else {
  663. elem.show();
  664. }
  665. elem = null;
  666. }
  667. function isNearLine(point, lstart, lstop, width) {
  668. // r is point to test, p and q are end points.
  669. var rx = point[0];
  670. var ry = point[1];
  671. var px = Math.round(lstop[0]);
  672. var py = Math.round(lstop[1]);
  673. var qx = Math.round(lstart[0]);
  674. var qy = Math.round(lstart[1]);
  675. var l = Math.sqrt(Math.pow(px-qx, 2) + Math.pow(py-qy, 2));
  676. // scale error term by length of line.
  677. var eps = width*l;
  678. var res = Math.abs((qx-px) * (ry-py) - (qy-py) * (rx-px));
  679. var ret = (res < eps) ? true : false;
  680. return ret;
  681. }
  682. function handleMove(ev, gridpos, datapos, neighbor, plot) {
  683. var co = plot.plugins.canvasOverlay;
  684. var objs = co.objects;
  685. var l = objs.length;
  686. var obj, haveHighlight=false;
  687. var elem;
  688. for (var i=0; i<l; i++) {
  689. obj = objs[i];
  690. if (obj.options.showTooltip) {
  691. var n = isNearLine([gridpos.x, gridpos.y], obj.gridStart, obj.gridStop, obj.tooltipWidthFactor);
  692. datapos = [plot.axes[obj.options.xaxis].series_p2u(gridpos.x), plot.axes[obj.options.yaxis].series_p2u(gridpos.y)];
  693. // cases:
  694. // near line, no highlighting
  695. // near line, highliting on this line
  696. // near line, highlighting another line
  697. // not near any line, highlighting
  698. // not near any line, no highlighting
  699. // near line, not currently highlighting
  700. if (n && co.highlightObjectIndex == null) {
  701. switch (obj.type) {
  702. case 'line':
  703. showTooltip(plot, obj, [gridpos.x, gridpos.y], datapos);
  704. break;
  705. case 'horizontalLine':
  706. case 'dashedHorizontalLine':
  707. showTooltip(plot, obj, [gridpos.x, obj.gridStart[1]], [datapos[0], obj.options.y]);
  708. break;
  709. case 'verticalLine':
  710. case 'dashedVerticalLine':
  711. showTooltip(plot, obj, [obj.gridStart[0], gridpos.y], [obj.options.x, datapos[1]]);
  712. break;
  713. default:
  714. break;
  715. }
  716. co.highlightObjectIndex = i;
  717. haveHighlight = true;
  718. break;
  719. }
  720. // near line, highlighting another line.
  721. else if (n && co.highlightObjectIndex !== i) {
  722. // turn off tooltip.
  723. elem = co._tooltipElem;
  724. if (obj.fadeTooltip) {
  725. elem.fadeOut(obj.tooltipFadeSpeed);
  726. }
  727. else {
  728. elem.hide();
  729. }
  730. // turn on right tooltip.
  731. switch (obj.type) {
  732. case 'line':
  733. showTooltip(plot, obj, [gridpos.x, gridpos.y], datapos);
  734. break;
  735. case 'horizontalLine':
  736. case 'dashedHorizontalLine':
  737. showTooltip(plot, obj, [gridpos.x, obj.gridStart[1]], [datapos[0], obj.options.y]);
  738. break;
  739. case 'verticalLine':
  740. case 'dashedVerticalLine':
  741. showTooltip(plot, obj, [obj.gridStart[0], gridpos.y], [obj.options.x, datapos[1]]);
  742. break;
  743. default:
  744. break;
  745. }
  746. co.highlightObjectIndex = i;
  747. haveHighlight = true;
  748. break;
  749. }
  750. // near line, already highlighting this line, update
  751. else if (n) {
  752. switch (obj.type) {
  753. case 'line':
  754. showTooltip(plot, obj, [gridpos.x, gridpos.y], datapos);
  755. break;
  756. case 'horizontalLine':
  757. case 'dashedHorizontalLine':
  758. showTooltip(plot, obj, [gridpos.x, obj.gridStart[1]], [datapos[0], obj.options.y]);
  759. break;
  760. case 'verticalLine':
  761. case 'dashedVerticalLine':
  762. showTooltip(plot, obj, [obj.gridStart[0], gridpos.y], [obj.options.x, datapos[1]]);
  763. break;
  764. default:
  765. break;
  766. }
  767. haveHighlight = true;
  768. break;
  769. }
  770. }
  771. }
  772. // check if we are highlighting and not near a line, turn it off.
  773. if (!haveHighlight && co.highlightObjectIndex !== null) {
  774. elem = co._tooltipElem;
  775. obj = co.getObject(co.highlightObjectIndex);
  776. if (obj.fadeTooltip) {
  777. elem.fadeOut(obj.tooltipFadeSpeed);
  778. }
  779. else {
  780. elem.hide();
  781. }
  782. co.highlightObjectIndex = null;
  783. }
  784. }
  785. $.jqplot.postInitHooks.push($.jqplot.CanvasOverlay.postPlotInit);
  786. $.jqplot.postDrawHooks.push($.jqplot.CanvasOverlay.postPlotDraw);
  787. $.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMove]);
  788. })(jQuery);