jqplot.canvasOverlay.js 44 KB

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