jqplot.cursor.js 45 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109
  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. /**
  33. * Class: $.jqplot.Cursor
  34. * Plugin class representing the cursor as displayed on the plot.
  35. */
  36. $.jqplot.Cursor = function(options) {
  37. // Group: Properties
  38. //
  39. // prop: style
  40. // CSS spec for cursor style
  41. this.style = 'crosshair';
  42. this.previousCursor = 'auto';
  43. // prop: show
  44. // whether to show the cursor or not.
  45. this.show = $.jqplot.config.enablePlugins;
  46. // prop: showTooltip
  47. // show a cursor position tooltip. Location of the tooltip
  48. // will be controlled by followMouse and tooltipLocation.
  49. this.showTooltip = true;
  50. // prop: followMouse
  51. // Tooltip follows the mouse, it is not at a fixed location.
  52. // Tooltip will show on the grid at the location given by
  53. // tooltipLocation, offset from the grid edge by tooltipOffset.
  54. this.followMouse = false;
  55. // prop: tooltipLocation
  56. // Where to position tooltip. If followMouse is true, this is
  57. // relative to the cursor, otherwise, it is relative to the grid.
  58. // One of 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
  59. this.tooltipLocation = 'se';
  60. // prop: tooltipOffset
  61. // Pixel offset of tooltip from the grid boudaries or cursor center.
  62. this.tooltipOffset = 6;
  63. // prop: showTooltipGridPosition
  64. // show the grid pixel coordinates of the mouse.
  65. this.showTooltipGridPosition = false;
  66. // prop: showTooltipUnitPosition
  67. // show the unit (data) coordinates of the mouse.
  68. this.showTooltipUnitPosition = true;
  69. // prop: showTooltipDataPosition
  70. // Used with showVerticalLine to show intersecting data points in the tooltip.
  71. this.showTooltipDataPosition = false;
  72. // prop: tooltipFormatString
  73. // sprintf format string for the tooltip.
  74. // Uses Ash Searle's javascript sprintf implementation
  75. // found here: http://hexmen.com/blog/2007/03/printf-sprintf/
  76. // See http://perldoc.perl.org/functions/sprintf.html for reference
  77. // Note, if showTooltipDataPosition is true, the default tooltipFormatString
  78. // will be set to the cursorLegendFormatString, not the default given here.
  79. this.tooltipFormatString = '%.4P, %.4P';
  80. // prop: useAxesFormatters
  81. // Use the x and y axes formatters to format the text in the tooltip.
  82. this.useAxesFormatters = true;
  83. // prop: tooltipAxisGroups
  84. // Show position for the specified axes.
  85. // This is an array like [['xaxis', 'yaxis'], ['xaxis', 'y2axis']]
  86. // Default is to compute automatically for all visible axes.
  87. this.tooltipAxisGroups = [];
  88. // prop: zoom
  89. // Enable plot zooming.
  90. this.zoom = false;
  91. // zoomProxy and zoomTarget properties are not directly set by user.
  92. // They Will be set through call to zoomProxy method.
  93. this.zoomProxy = false;
  94. this.zoomTarget = false;
  95. // prop: looseZoom
  96. // Will expand zoom range to provide more rounded tick values.
  97. // Works only with linear, log and date axes.
  98. this.looseZoom = true;
  99. // prop: clickReset
  100. // Will reset plot zoom if single click on plot without drag.
  101. this.clickReset = false;
  102. // prop: dblClickReset
  103. // Will reset plot zoom if double click on plot without drag.
  104. this.dblClickReset = true;
  105. // prop: showVerticalLine
  106. // draw a vertical line across the plot which follows the cursor.
  107. // When the line is near a data point, a special legend and/or tooltip can
  108. // be updated with the data values.
  109. this.showVerticalLine = false;
  110. // prop: showHorizontalLine
  111. // draw a horizontal line across the plot which follows the cursor.
  112. this.showHorizontalLine = false;
  113. // prop: constrainZoomTo
  114. // 'none', 'x' or 'y'
  115. this.constrainZoomTo = 'none';
  116. // // prop: autoscaleConstraint
  117. // // when a constrained axis is specified, true will
  118. // // auatoscale the adjacent axis.
  119. // this.autoscaleConstraint = true;
  120. this.shapeRenderer = new $.jqplot.ShapeRenderer();
  121. this._zoom = {start:[], end:[], started: false, zooming:false, isZoomed:false, axes:{start:{}, end:{}}, gridpos:{}, datapos:{}};
  122. this._tooltipElem;
  123. this.zoomCanvas;
  124. this.cursorCanvas;
  125. // prop: intersectionThreshold
  126. // pixel distance from data point or marker to consider cursor lines intersecting with point.
  127. // If data point markers are not shown, this should be >= 1 or will often miss point intersections.
  128. this.intersectionThreshold = 2;
  129. // prop: showCursorLegend
  130. // Replace the plot legend with an enhanced legend displaying intersection information.
  131. this.showCursorLegend = false;
  132. // prop: cursorLegendFormatString
  133. // Format string used in the cursor legend. If showTooltipDataPosition is true,
  134. // this will also be the default format string used by tooltipFormatString.
  135. this.cursorLegendFormatString = $.jqplot.Cursor.cursorLegendFormatString;
  136. // whether the cursor is over the grid or not.
  137. this._oldHandlers = {onselectstart: null, ondrag: null, onmousedown: null};
  138. // prop: constrainOutsideZoom
  139. // True to limit actual zoom area to edges of grid, even when zooming
  140. // outside of plot area. That is, can't zoom out by mousing outside plot.
  141. this.constrainOutsideZoom = true;
  142. // prop: showTooltipOutsideZoom
  143. // True will keep updating the tooltip when zooming of the grid.
  144. this.showTooltipOutsideZoom = false;
  145. // true if mouse is over grid, false if not.
  146. this.onGrid = false;
  147. $.extend(true, this, options);
  148. };
  149. $.jqplot.Cursor.cursorLegendFormatString = '%s x:%s, y:%s';
  150. // called with scope of plot
  151. $.jqplot.Cursor.init = function (target, data, opts){
  152. // add a cursor attribute to the plot
  153. var options = opts || {};
  154. this.plugins.cursor = new $.jqplot.Cursor(options.cursor);
  155. var c = this.plugins.cursor;
  156. if (c.show) {
  157. $.jqplot.eventListenerHooks.push(['jqplotMouseEnter', handleMouseEnter]);
  158. $.jqplot.eventListenerHooks.push(['jqplotMouseLeave', handleMouseLeave]);
  159. $.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMouseMove]);
  160. if (c.showCursorLegend) {
  161. opts.legend = opts.legend || {};
  162. opts.legend.renderer = $.jqplot.CursorLegendRenderer;
  163. opts.legend.formatString = this.plugins.cursor.cursorLegendFormatString;
  164. opts.legend.show = true;
  165. }
  166. if (c.zoom) {
  167. $.jqplot.eventListenerHooks.push(['jqplotMouseDown', handleMouseDown]);
  168. if (c.clickReset) {
  169. $.jqplot.eventListenerHooks.push(['jqplotClick', handleClick]);
  170. }
  171. if (c.dblClickReset) {
  172. $.jqplot.eventListenerHooks.push(['jqplotDblClick', handleDblClick]);
  173. }
  174. }
  175. this.resetZoom = function() {
  176. var axes = this.axes;
  177. if (!c.zoomProxy) {
  178. for (var ax in axes) {
  179. axes[ax].reset();
  180. axes[ax]._ticks = [];
  181. // fake out tick creation algorithm to make sure original auto
  182. // computed format string is used if _overrideFormatString is true
  183. if (c._zoom.axes[ax] !== undefined) {
  184. axes[ax]._autoFormatString = c._zoom.axes[ax].tickFormatString;
  185. }
  186. }
  187. this.redraw();
  188. }
  189. else {
  190. var ctx = this.plugins.cursor.zoomCanvas._ctx;
  191. ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
  192. ctx = null;
  193. }
  194. this.plugins.cursor._zoom.isZoomed = false;
  195. this.target.trigger('jqplotResetZoom', [this, this.plugins.cursor]);
  196. };
  197. if (c.showTooltipDataPosition) {
  198. c.showTooltipUnitPosition = false;
  199. c.showTooltipGridPosition = false;
  200. if (options.cursor.tooltipFormatString == undefined) {
  201. c.tooltipFormatString = $.jqplot.Cursor.cursorLegendFormatString;
  202. }
  203. }
  204. }
  205. };
  206. // called with context of plot
  207. $.jqplot.Cursor.postDraw = function() {
  208. var c = this.plugins.cursor;
  209. // Memory Leaks patch
  210. if (c.zoomCanvas) {
  211. c.zoomCanvas.resetCanvas();
  212. c.zoomCanvas = null;
  213. }
  214. if (c.cursorCanvas) {
  215. c.cursorCanvas.resetCanvas();
  216. c.cursorCanvas = null;
  217. }
  218. if (c._tooltipElem) {
  219. c._tooltipElem.emptyForce();
  220. c._tooltipElem = null;
  221. }
  222. if (c.zoom) {
  223. c.zoomCanvas = new $.jqplot.GenericCanvas();
  224. this.eventCanvas._elem.before(c.zoomCanvas.createElement(this._gridPadding, 'jqplot-zoom-canvas', this._plotDimensions, this));
  225. c.zoomCanvas.setContext();
  226. }
  227. var elem = document.createElement('div');
  228. c._tooltipElem = $(elem);
  229. elem = null;
  230. c._tooltipElem.addClass('jqplot-cursor-tooltip');
  231. c._tooltipElem.css({position:'absolute', display:'none'});
  232. if (c.zoomCanvas) {
  233. c.zoomCanvas._elem.before(c._tooltipElem);
  234. }
  235. else {
  236. this.eventCanvas._elem.before(c._tooltipElem);
  237. }
  238. if (c.showVerticalLine || c.showHorizontalLine) {
  239. c.cursorCanvas = new $.jqplot.GenericCanvas();
  240. this.eventCanvas._elem.before(c.cursorCanvas.createElement(this._gridPadding, 'jqplot-cursor-canvas', this._plotDimensions, this));
  241. c.cursorCanvas.setContext();
  242. }
  243. // if we are showing the positions in unit coordinates, and no axes groups
  244. // were specified, create a default set.
  245. if (c.showTooltipUnitPosition){
  246. if (c.tooltipAxisGroups.length === 0) {
  247. var series = this.series;
  248. var s;
  249. var temp = [];
  250. for (var i=0; i<series.length; i++) {
  251. s = series[i];
  252. var ax = s.xaxis+','+s.yaxis;
  253. if ($.inArray(ax, temp) == -1) {
  254. temp.push(ax);
  255. }
  256. }
  257. for (var i=0; i<temp.length; i++) {
  258. c.tooltipAxisGroups.push(temp[i].split(','));
  259. }
  260. }
  261. }
  262. };
  263. // Group: methods
  264. //
  265. // method: $.jqplot.Cursor.zoomProxy
  266. // links targetPlot to controllerPlot so that plot zooming of
  267. // targetPlot will be controlled by zooming on the controllerPlot.
  268. // controllerPlot will not actually zoom, but acts as an
  269. // overview plot. Note, the zoom options must be set to true for
  270. // zoomProxy to work.
  271. $.jqplot.Cursor.zoomProxy = function(targetPlot, controllerPlot) {
  272. var tc = targetPlot.plugins.cursor;
  273. var cc = controllerPlot.plugins.cursor;
  274. tc.zoomTarget = true;
  275. tc.zoom = true;
  276. tc.style = 'auto';
  277. tc.dblClickReset = false;
  278. cc.zoom = true;
  279. cc.zoomProxy = true;
  280. controllerPlot.target.bind('jqplotZoom', plotZoom);
  281. controllerPlot.target.bind('jqplotResetZoom', plotReset);
  282. function plotZoom(ev, gridpos, datapos, plot, cursor) {
  283. tc.doZoom(gridpos, datapos, targetPlot, cursor);
  284. }
  285. function plotReset(ev, plot, cursor) {
  286. targetPlot.resetZoom();
  287. }
  288. };
  289. $.jqplot.Cursor.prototype.resetZoom = function(plot, cursor) {
  290. var axes = plot.axes;
  291. var cax = cursor._zoom.axes;
  292. if (!plot.plugins.cursor.zoomProxy && cursor._zoom.isZoomed) {
  293. for (var ax in axes) {
  294. // axes[ax]._ticks = [];
  295. // axes[ax].min = cax[ax].min;
  296. // axes[ax].max = cax[ax].max;
  297. // axes[ax].numberTicks = cax[ax].numberTicks;
  298. // axes[ax].tickInterval = cax[ax].tickInterval;
  299. // // for date axes
  300. // axes[ax].daTickInterval = cax[ax].daTickInterval;
  301. axes[ax].reset();
  302. axes[ax]._ticks = [];
  303. // fake out tick creation algorithm to make sure original auto
  304. // computed format string is used if _overrideFormatString is true
  305. axes[ax]._autoFormatString = cax[ax].tickFormatString;
  306. }
  307. plot.redraw();
  308. cursor._zoom.isZoomed = false;
  309. }
  310. else {
  311. var ctx = cursor.zoomCanvas._ctx;
  312. ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
  313. ctx = null;
  314. }
  315. plot.target.trigger('jqplotResetZoom', [plot, cursor]);
  316. };
  317. $.jqplot.Cursor.resetZoom = function(plot) {
  318. plot.resetZoom();
  319. };
  320. $.jqplot.Cursor.prototype.doZoom = function (gridpos, datapos, plot, cursor) {
  321. var c = cursor;
  322. var axes = plot.axes;
  323. var zaxes = c._zoom.axes;
  324. var start = zaxes.start;
  325. var end = zaxes.end;
  326. var min, max, dp, span,
  327. newmin, newmax, curax, _numberTicks, ret;
  328. var ctx = plot.plugins.cursor.zoomCanvas._ctx;
  329. // don't zoom if zoom area is too small (in pixels)
  330. if ((c.constrainZoomTo == 'none' && Math.abs(gridpos.x - c._zoom.start[0]) > 6 && Math.abs(gridpos.y - c._zoom.start[1]) > 6) || (c.constrainZoomTo == 'x' && Math.abs(gridpos.x - c._zoom.start[0]) > 6) || (c.constrainZoomTo == 'y' && Math.abs(gridpos.y - c._zoom.start[1]) > 6)) {
  331. if (!plot.plugins.cursor.zoomProxy) {
  332. for (var ax in datapos) {
  333. // make a copy of the original axes to revert back.
  334. if (c._zoom.axes[ax] == undefined) {
  335. c._zoom.axes[ax] = {};
  336. c._zoom.axes[ax].numberTicks = axes[ax].numberTicks;
  337. c._zoom.axes[ax].tickInterval = axes[ax].tickInterval;
  338. // for date axes...
  339. c._zoom.axes[ax].daTickInterval = axes[ax].daTickInterval;
  340. c._zoom.axes[ax].min = axes[ax].min;
  341. c._zoom.axes[ax].max = axes[ax].max;
  342. c._zoom.axes[ax].tickFormatString = (axes[ax].tickOptions != null) ? axes[ax].tickOptions.formatString : '';
  343. }
  344. if ((c.constrainZoomTo == 'none') || (c.constrainZoomTo == 'x' && ax.charAt(0) == 'x') || (c.constrainZoomTo == 'y' && ax.charAt(0) == 'y')) {
  345. dp = datapos[ax];
  346. if (dp != null) {
  347. if (dp > start[ax]) {
  348. newmin = start[ax];
  349. newmax = dp;
  350. }
  351. else {
  352. span = start[ax] - dp;
  353. newmin = dp;
  354. newmax = start[ax];
  355. }
  356. curax = axes[ax];
  357. _numberTicks = null;
  358. // if aligning this axis, use number of ticks from previous axis.
  359. // Do I need to reset somehow if alignTicks is changed and then graph is replotted??
  360. if (curax.alignTicks) {
  361. if (curax.name === 'x2axis' && plot.axes.xaxis.show) {
  362. _numberTicks = plot.axes.xaxis.numberTicks;
  363. }
  364. else if (curax.name.charAt(0) === 'y' && curax.name !== 'yaxis' && curax.name !== 'yMidAxis' && plot.axes.yaxis.show) {
  365. _numberTicks = plot.axes.yaxis.numberTicks;
  366. }
  367. }
  368. if (this.looseZoom && (axes[ax].renderer.constructor === $.jqplot.LinearAxisRenderer || axes[ax].renderer.constructor === $.jqplot.LogAxisRenderer )) { //} || axes[ax].renderer.constructor === $.jqplot.DateAxisRenderer)) {
  369. ret = $.jqplot.LinearTickGenerator(newmin, newmax, curax._scalefact, _numberTicks);
  370. // if new minimum is less than "true" minimum of axis display, adjust it
  371. if (axes[ax].tickInset && ret[0] < axes[ax].min + axes[ax].tickInset * axes[ax].tickInterval) {
  372. ret[0] += ret[4];
  373. ret[2] -= 1;
  374. }
  375. // if new maximum is greater than "true" max of axis display, adjust it
  376. if (axes[ax].tickInset && ret[1] > axes[ax].max - axes[ax].tickInset * axes[ax].tickInterval) {
  377. ret[1] -= ret[4];
  378. ret[2] -= 1;
  379. }
  380. // for log axes, don't fall below current minimum, this will look bad and can't have 0 in range anyway.
  381. if (axes[ax].renderer.constructor === $.jqplot.LogAxisRenderer && ret[0] < axes[ax].min) {
  382. // remove a tick and shift min up
  383. ret[0] += ret[4];
  384. ret[2] -= 1;
  385. }
  386. axes[ax].min = ret[0];
  387. axes[ax].max = ret[1];
  388. axes[ax]._autoFormatString = ret[3];
  389. axes[ax].numberTicks = ret[2];
  390. axes[ax].tickInterval = ret[4];
  391. // for date axes...
  392. axes[ax].daTickInterval = [ret[4]/1000, 'seconds'];
  393. }
  394. else {
  395. axes[ax].min = newmin;
  396. axes[ax].max = newmax;
  397. axes[ax].tickInterval = null;
  398. axes[ax].numberTicks = null;
  399. // for date axes...
  400. axes[ax].daTickInterval = null;
  401. }
  402. axes[ax]._ticks = [];
  403. }
  404. }
  405. // if ((c.constrainZoomTo == 'x' && ax.charAt(0) == 'y' && c.autoscaleConstraint) || (c.constrainZoomTo == 'y' && ax.charAt(0) == 'x' && c.autoscaleConstraint)) {
  406. // dp = datapos[ax];
  407. // if (dp != null) {
  408. // axes[ax].max == null;
  409. // axes[ax].min = null;
  410. // }
  411. // }
  412. }
  413. ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
  414. plot.redraw();
  415. c._zoom.isZoomed = true;
  416. ctx = null;
  417. }
  418. plot.target.trigger('jqplotZoom', [gridpos, datapos, plot, cursor]);
  419. }
  420. };
  421. $.jqplot.preInitHooks.push($.jqplot.Cursor.init);
  422. $.jqplot.postDrawHooks.push($.jqplot.Cursor.postDraw);
  423. function updateTooltip(gridpos, datapos, plot) {
  424. var c = plot.plugins.cursor;
  425. var s = '';
  426. var addbr = false;
  427. if (c.showTooltipGridPosition) {
  428. s = gridpos.x+', '+gridpos.y;
  429. addbr = true;
  430. }
  431. if (c.showTooltipUnitPosition) {
  432. var g;
  433. for (var i=0; i<c.tooltipAxisGroups.length; i++) {
  434. g = c.tooltipAxisGroups[i];
  435. if (addbr) {
  436. s += '<br />';
  437. }
  438. if (c.useAxesFormatters) {
  439. for (var j=0; j<g.length; j++) {
  440. if (j) {
  441. s += ', ';
  442. }
  443. var af = plot.axes[g[j]]._ticks[0].formatter;
  444. var afstr = plot.axes[g[j]]._ticks[0].formatString;
  445. s += af(afstr, datapos[g[j]]);
  446. }
  447. }
  448. else {
  449. s += $.jqplot.sprintf(c.tooltipFormatString, datapos[g[0]], datapos[g[1]]);
  450. }
  451. addbr = true;
  452. }
  453. }
  454. if (c.showTooltipDataPosition) {
  455. var series = plot.series;
  456. var ret = getIntersectingPoints(plot, gridpos.x, gridpos.y);
  457. var addbr = false;
  458. for (var i = 0; i< series.length; i++) {
  459. if (series[i].show) {
  460. var idx = series[i].index;
  461. var label = series[i].label.toString();
  462. var cellid = $.inArray(idx, ret.indices);
  463. var sx = undefined;
  464. var sy = undefined;
  465. if (cellid != -1) {
  466. var data = ret.data[cellid].data;
  467. if (c.useAxesFormatters) {
  468. var xf = series[i]._xaxis._ticks[0].formatter;
  469. var yf = series[i]._yaxis._ticks[0].formatter;
  470. var xfstr = series[i]._xaxis._ticks[0].formatString;
  471. var yfstr = series[i]._yaxis._ticks[0].formatString;
  472. sx = xf(xfstr, data[0]);
  473. sy = yf(yfstr, data[1]);
  474. }
  475. else {
  476. sx = data[0];
  477. sy = data[1];
  478. }
  479. if (addbr) {
  480. s += '<br />';
  481. }
  482. s += $.jqplot.sprintf(c.tooltipFormatString, label, sx, sy);
  483. addbr = true;
  484. }
  485. }
  486. }
  487. }
  488. c._tooltipElem.html(s);
  489. }
  490. function moveLine(gridpos, plot) {
  491. var c = plot.plugins.cursor;
  492. var ctx = c.cursorCanvas._ctx;
  493. ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
  494. if (c.showVerticalLine) {
  495. c.shapeRenderer.draw(ctx, [[gridpos.x, 0], [gridpos.x, ctx.canvas.height]]);
  496. }
  497. if (c.showHorizontalLine) {
  498. c.shapeRenderer.draw(ctx, [[0, gridpos.y], [ctx.canvas.width, gridpos.y]]);
  499. }
  500. var ret = getIntersectingPoints(plot, gridpos.x, gridpos.y);
  501. if (c.showCursorLegend) {
  502. var cells = $(plot.targetId + ' td.jqplot-cursor-legend-label');
  503. for (var i=0; i<cells.length; i++) {
  504. var idx = $(cells[i]).data('seriesIndex');
  505. var series = plot.series[idx];
  506. var label = series.label.toString();
  507. var cellid = $.inArray(idx, ret.indices);
  508. var sx = undefined;
  509. var sy = undefined;
  510. if (cellid != -1) {
  511. var data = ret.data[cellid].data;
  512. if (c.useAxesFormatters) {
  513. var xf = series._xaxis._ticks[0].formatter;
  514. var yf = series._yaxis._ticks[0].formatter;
  515. var xfstr = series._xaxis._ticks[0].formatString;
  516. var yfstr = series._yaxis._ticks[0].formatString;
  517. sx = xf(xfstr, data[0]);
  518. sy = yf(yfstr, data[1]);
  519. }
  520. else {
  521. sx = data[0];
  522. sy = data[1];
  523. }
  524. }
  525. if (plot.legend.escapeHtml) {
  526. $(cells[i]).text($.jqplot.sprintf(c.cursorLegendFormatString, label, sx, sy));
  527. }
  528. else {
  529. $(cells[i]).html($.jqplot.sprintf(c.cursorLegendFormatString, label, sx, sy));
  530. }
  531. }
  532. }
  533. ctx = null;
  534. }
  535. function getIntersectingPoints(plot, x, y) {
  536. var ret = {indices:[], data:[]};
  537. var s, i, d0, d, j, r, p;
  538. var threshold;
  539. var c = plot.plugins.cursor;
  540. for (var i=0; i<plot.series.length; i++) {
  541. s = plot.series[i];
  542. r = s.renderer;
  543. if (s.show) {
  544. threshold = c.intersectionThreshold;
  545. if (s.showMarker) {
  546. threshold += s.markerRenderer.size/2;
  547. }
  548. for (var j=0; j<s.gridData.length; j++) {
  549. p = s.gridData[j];
  550. // check vertical line
  551. if (c.showVerticalLine) {
  552. if (Math.abs(x-p[0]) <= threshold) {
  553. ret.indices.push(i);
  554. ret.data.push({seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]});
  555. }
  556. }
  557. }
  558. }
  559. }
  560. return ret;
  561. }
  562. function moveTooltip(gridpos, plot) {
  563. var c = plot.plugins.cursor;
  564. var elem = c._tooltipElem;
  565. switch (c.tooltipLocation) {
  566. case 'nw':
  567. var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - c.tooltipOffset;
  568. var y = gridpos.y + plot._gridPadding.top - c.tooltipOffset - elem.outerHeight(true);
  569. break;
  570. case 'n':
  571. var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
  572. var y = gridpos.y + plot._gridPadding.top - c.tooltipOffset - elem.outerHeight(true);
  573. break;
  574. case 'ne':
  575. var x = gridpos.x + plot._gridPadding.left + c.tooltipOffset;
  576. var y = gridpos.y + plot._gridPadding.top - c.tooltipOffset - elem.outerHeight(true);
  577. break;
  578. case 'e':
  579. var x = gridpos.x + plot._gridPadding.left + c.tooltipOffset;
  580. var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
  581. break;
  582. case 'se':
  583. var x = gridpos.x + plot._gridPadding.left + c.tooltipOffset;
  584. var y = gridpos.y + plot._gridPadding.top + c.tooltipOffset;
  585. break;
  586. case 's':
  587. var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
  588. var y = gridpos.y + plot._gridPadding.top + c.tooltipOffset;
  589. break;
  590. case 'sw':
  591. var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - c.tooltipOffset;
  592. var y = gridpos.y + plot._gridPadding.top + c.tooltipOffset;
  593. break;
  594. case 'w':
  595. var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - c.tooltipOffset;
  596. var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
  597. break;
  598. default:
  599. var x = gridpos.x + plot._gridPadding.left + c.tooltipOffset;
  600. var y = gridpos.y + plot._gridPadding.top + c.tooltipOffset;
  601. break;
  602. }
  603. elem.css('left', x);
  604. elem.css('top', y);
  605. elem = null;
  606. }
  607. function positionTooltip(plot) {
  608. // fake a grid for positioning
  609. var grid = plot._gridPadding;
  610. var c = plot.plugins.cursor;
  611. var elem = c._tooltipElem;
  612. switch (c.tooltipLocation) {
  613. case 'nw':
  614. var a = grid.left + c.tooltipOffset;
  615. var b = grid.top + c.tooltipOffset;
  616. elem.css('left', a);
  617. elem.css('top', b);
  618. break;
  619. case 'n':
  620. var a = (grid.left + (plot._plotDimensions.width - grid.right))/2 - elem.outerWidth(true)/2;
  621. var b = grid.top + c.tooltipOffset;
  622. elem.css('left', a);
  623. elem.css('top', b);
  624. break;
  625. case 'ne':
  626. var a = grid.right + c.tooltipOffset;
  627. var b = grid.top + c.tooltipOffset;
  628. elem.css({right:a, top:b});
  629. break;
  630. case 'e':
  631. var a = grid.right + c.tooltipOffset;
  632. var b = (grid.top + (plot._plotDimensions.height - grid.bottom))/2 - elem.outerHeight(true)/2;
  633. elem.css({right:a, top:b});
  634. break;
  635. case 'se':
  636. var a = grid.right + c.tooltipOffset;
  637. var b = grid.bottom + c.tooltipOffset;
  638. elem.css({right:a, bottom:b});
  639. break;
  640. case 's':
  641. var a = (grid.left + (plot._plotDimensions.width - grid.right))/2 - elem.outerWidth(true)/2;
  642. var b = grid.bottom + c.tooltipOffset;
  643. elem.css({left:a, bottom:b});
  644. break;
  645. case 'sw':
  646. var a = grid.left + c.tooltipOffset;
  647. var b = grid.bottom + c.tooltipOffset;
  648. elem.css({left:a, bottom:b});
  649. break;
  650. case 'w':
  651. var a = grid.left + c.tooltipOffset;
  652. var b = (grid.top + (plot._plotDimensions.height - grid.bottom))/2 - elem.outerHeight(true)/2;
  653. elem.css({left:a, top:b});
  654. break;
  655. default: // same as 'se'
  656. var a = grid.right - c.tooltipOffset;
  657. var b = grid.bottom + c.tooltipOffset;
  658. elem.css({right:a, bottom:b});
  659. break;
  660. }
  661. elem = null;
  662. }
  663. function handleClick (ev, gridpos, datapos, neighbor, plot) {
  664. ev.preventDefault();
  665. ev.stopImmediatePropagation();
  666. var c = plot.plugins.cursor;
  667. if (c.clickReset) {
  668. c.resetZoom(plot, c);
  669. }
  670. var sel = window.getSelection;
  671. if (document.selection && document.selection.empty)
  672. {
  673. document.selection.empty();
  674. }
  675. else if (sel && !sel().isCollapsed) {
  676. sel().collapse();
  677. }
  678. return false;
  679. }
  680. function handleDblClick (ev, gridpos, datapos, neighbor, plot) {
  681. ev.preventDefault();
  682. ev.stopImmediatePropagation();
  683. var c = plot.plugins.cursor;
  684. if (c.dblClickReset) {
  685. c.resetZoom(plot, c);
  686. }
  687. var sel = window.getSelection;
  688. if (document.selection && document.selection.empty)
  689. {
  690. document.selection.empty();
  691. }
  692. else if (sel && !sel().isCollapsed) {
  693. sel().collapse();
  694. }
  695. return false;
  696. }
  697. function handleMouseLeave(ev, gridpos, datapos, neighbor, plot) {
  698. var c = plot.plugins.cursor;
  699. c.onGrid = false;
  700. if (c.show) {
  701. $(ev.target).css('cursor', c.previousCursor);
  702. if (c.showTooltip && !(c._zoom.zooming && c.showTooltipOutsideZoom && !c.constrainOutsideZoom)) {
  703. c._tooltipElem.empty();
  704. c._tooltipElem.hide();
  705. }
  706. if (c.zoom) {
  707. c._zoom.gridpos = gridpos;
  708. c._zoom.datapos = datapos;
  709. }
  710. if (c.showVerticalLine || c.showHorizontalLine) {
  711. var ctx = c.cursorCanvas._ctx;
  712. ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
  713. ctx = null;
  714. }
  715. if (c.showCursorLegend) {
  716. var cells = $(plot.targetId + ' td.jqplot-cursor-legend-label');
  717. for (var i=0; i<cells.length; i++) {
  718. var idx = $(cells[i]).data('seriesIndex');
  719. var series = plot.series[idx];
  720. var label = series.label.toString();
  721. if (plot.legend.escapeHtml) {
  722. $(cells[i]).text($.jqplot.sprintf(c.cursorLegendFormatString, label, undefined, undefined));
  723. }
  724. else {
  725. $(cells[i]).html($.jqplot.sprintf(c.cursorLegendFormatString, label, undefined, undefined));
  726. }
  727. }
  728. }
  729. }
  730. }
  731. function handleMouseEnter(ev, gridpos, datapos, neighbor, plot) {
  732. var c = plot.plugins.cursor;
  733. c.onGrid = true;
  734. if (c.show) {
  735. c.previousCursor = ev.target.style.cursor;
  736. ev.target.style.cursor = c.style;
  737. if (c.showTooltip) {
  738. updateTooltip(gridpos, datapos, plot);
  739. if (c.followMouse) {
  740. moveTooltip(gridpos, plot);
  741. }
  742. else {
  743. positionTooltip(plot);
  744. }
  745. c._tooltipElem.show();
  746. }
  747. if (c.showVerticalLine || c.showHorizontalLine) {
  748. moveLine(gridpos, plot);
  749. }
  750. }
  751. }
  752. function handleMouseMove(ev, gridpos, datapos, neighbor, plot) {
  753. var c = plot.plugins.cursor;
  754. if (c.show) {
  755. if (c.showTooltip) {
  756. updateTooltip(gridpos, datapos, plot);
  757. if (c.followMouse) {
  758. moveTooltip(gridpos, plot);
  759. }
  760. }
  761. if (c.showVerticalLine || c.showHorizontalLine) {
  762. moveLine(gridpos, plot);
  763. }
  764. }
  765. }
  766. function getEventPosition(ev) {
  767. var plot = ev.data.plot;
  768. var go = plot.eventCanvas._elem.offset();
  769. var gridPos = {x:ev.pageX - go.left, y:ev.pageY - go.top};
  770. //////
  771. // TO DO: handle yMidAxis
  772. //////
  773. var dataPos = {xaxis:null, yaxis:null, x2axis:null, y2axis:null, y3axis:null, y4axis:null, y5axis:null, y6axis:null, y7axis:null, y8axis:null, y9axis:null, yMidAxis:null};
  774. var an = ['xaxis', 'yaxis', 'x2axis', 'y2axis', 'y3axis', 'y4axis', 'y5axis', 'y6axis', 'y7axis', 'y8axis', 'y9axis', 'yMidAxis'];
  775. var ax = plot.axes;
  776. var n, axis;
  777. for (n=11; n>0; n--) {
  778. axis = an[n-1];
  779. if (ax[axis].show) {
  780. dataPos[axis] = ax[axis].series_p2u(gridPos[axis.charAt(0)]);
  781. }
  782. }
  783. return {offsets:go, gridPos:gridPos, dataPos:dataPos};
  784. }
  785. function handleZoomMove(ev) {
  786. var plot = ev.data.plot;
  787. var c = plot.plugins.cursor;
  788. // don't do anything if not on grid.
  789. if (c.show && c.zoom && c._zoom.started && !c.zoomTarget) {
  790. ev.preventDefault();
  791. var ctx = c.zoomCanvas._ctx;
  792. var positions = getEventPosition(ev);
  793. var gridpos = positions.gridPos;
  794. var datapos = positions.dataPos;
  795. c._zoom.gridpos = gridpos;
  796. c._zoom.datapos = datapos;
  797. c._zoom.zooming = true;
  798. var xpos = gridpos.x;
  799. var ypos = gridpos.y;
  800. var height = ctx.canvas.height;
  801. var width = ctx.canvas.width;
  802. if (c.showTooltip && !c.onGrid && c.showTooltipOutsideZoom) {
  803. updateTooltip(gridpos, datapos, plot);
  804. if (c.followMouse) {
  805. moveTooltip(gridpos, plot);
  806. }
  807. }
  808. if (c.constrainZoomTo == 'x') {
  809. c._zoom.end = [xpos, height];
  810. }
  811. else if (c.constrainZoomTo == 'y') {
  812. c._zoom.end = [width, ypos];
  813. }
  814. else {
  815. c._zoom.end = [xpos, ypos];
  816. }
  817. var sel = window.getSelection;
  818. if (document.selection && document.selection.empty)
  819. {
  820. document.selection.empty();
  821. }
  822. else if (sel && !sel().isCollapsed) {
  823. sel().collapse();
  824. }
  825. drawZoomBox.call(c);
  826. ctx = null;
  827. }
  828. }
  829. function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
  830. var c = plot.plugins.cursor;
  831. if(plot.plugins.mobile){
  832. $(document).one('vmouseup.jqplot_cursor', {plot:plot}, handleMouseUp);
  833. } else {
  834. $(document).one('mouseup.jqplot_cursor', {plot:plot}, handleMouseUp);
  835. }
  836. var axes = plot.axes;
  837. if (document.onselectstart != undefined) {
  838. c._oldHandlers.onselectstart = document.onselectstart;
  839. document.onselectstart = function () { return false; };
  840. }
  841. if (document.ondrag != undefined) {
  842. c._oldHandlers.ondrag = document.ondrag;
  843. document.ondrag = function () { return false; };
  844. }
  845. if (document.onmousedown != undefined) {
  846. c._oldHandlers.onmousedown = document.onmousedown;
  847. document.onmousedown = function () { return false; };
  848. }
  849. if (c.zoom) {
  850. if (!c.zoomProxy) {
  851. var ctx = c.zoomCanvas._ctx;
  852. ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
  853. ctx = null;
  854. }
  855. if (c.constrainZoomTo == 'x') {
  856. c._zoom.start = [gridpos.x, 0];
  857. }
  858. else if (c.constrainZoomTo == 'y') {
  859. c._zoom.start = [0, gridpos.y];
  860. }
  861. else {
  862. c._zoom.start = [gridpos.x, gridpos.y];
  863. }
  864. c._zoom.started = true;
  865. for (var ax in datapos) {
  866. // get zoom starting position.
  867. c._zoom.axes.start[ax] = datapos[ax];
  868. }
  869. if(plot.plugins.mobile){
  870. $(document).bind('vmousemove.jqplotCursor', {plot:plot}, handleZoomMove);
  871. } else {
  872. $(document).bind('mousemove.jqplotCursor', {plot:plot}, handleZoomMove);
  873. }
  874. }
  875. }
  876. function handleMouseUp(ev) {
  877. var plot = ev.data.plot;
  878. var c = plot.plugins.cursor;
  879. if (c.zoom && c._zoom.zooming && !c.zoomTarget) {
  880. var xpos = c._zoom.gridpos.x;
  881. var ypos = c._zoom.gridpos.y;
  882. var datapos = c._zoom.datapos;
  883. var height = c.zoomCanvas._ctx.canvas.height;
  884. var width = c.zoomCanvas._ctx.canvas.width;
  885. var axes = plot.axes;
  886. if (c.constrainOutsideZoom && !c.onGrid) {
  887. if (xpos < 0) { xpos = 0; }
  888. else if (xpos > width) { xpos = width; }
  889. if (ypos < 0) { ypos = 0; }
  890. else if (ypos > height) { ypos = height; }
  891. for (var axis in datapos) {
  892. if (datapos[axis]) {
  893. if (axis.charAt(0) == 'x') {
  894. datapos[axis] = axes[axis].series_p2u(xpos);
  895. }
  896. else {
  897. datapos[axis] = axes[axis].series_p2u(ypos);
  898. }
  899. }
  900. }
  901. }
  902. if (c.constrainZoomTo == 'x') {
  903. ypos = height;
  904. }
  905. else if (c.constrainZoomTo == 'y') {
  906. xpos = width;
  907. }
  908. c._zoom.end = [xpos, ypos];
  909. c._zoom.gridpos = {x:xpos, y:ypos};
  910. c.doZoom(c._zoom.gridpos, datapos, plot, c);
  911. }
  912. c._zoom.started = false;
  913. c._zoom.zooming = false;
  914. $(document).unbind('mousemove.jqplotCursor', handleZoomMove);
  915. if (document.onselectstart != undefined && c._oldHandlers.onselectstart != null){
  916. document.onselectstart = c._oldHandlers.onselectstart;
  917. c._oldHandlers.onselectstart = null;
  918. }
  919. if (document.ondrag != undefined && c._oldHandlers.ondrag != null){
  920. document.ondrag = c._oldHandlers.ondrag;
  921. c._oldHandlers.ondrag = null;
  922. }
  923. if (document.onmousedown != undefined && c._oldHandlers.onmousedown != null){
  924. document.onmousedown = c._oldHandlers.onmousedown;
  925. c._oldHandlers.onmousedown = null;
  926. }
  927. }
  928. function drawZoomBox() {
  929. var start = this._zoom.start;
  930. var end = this._zoom.end;
  931. var ctx = this.zoomCanvas._ctx;
  932. var l, t, h, w;
  933. if (end[0] > start[0]) {
  934. l = start[0];
  935. w = end[0] - start[0];
  936. }
  937. else {
  938. l = end[0];
  939. w = start[0] - end[0];
  940. }
  941. if (end[1] > start[1]) {
  942. t = start[1];
  943. h = end[1] - start[1];
  944. }
  945. else {
  946. t = end[1];
  947. h = start[1] - end[1];
  948. }
  949. ctx.fillStyle = 'rgba(0,0,0,0.2)';
  950. ctx.strokeStyle = '#999999';
  951. ctx.lineWidth = 1.0;
  952. ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
  953. ctx.fillRect(0,0,ctx.canvas.width, ctx.canvas.height);
  954. ctx.clearRect(l, t, w, h);
  955. // IE won't show transparent fill rect, so stroke a rect also.
  956. ctx.strokeRect(l,t,w,h);
  957. ctx = null;
  958. }
  959. $.jqplot.CursorLegendRenderer = function(options) {
  960. $.jqplot.TableLegendRenderer.call(this, options);
  961. this.formatString = '%s';
  962. };
  963. $.jqplot.CursorLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
  964. $.jqplot.CursorLegendRenderer.prototype.constructor = $.jqplot.CursorLegendRenderer;
  965. // called in context of a Legend
  966. $.jqplot.CursorLegendRenderer.prototype.draw = function() {
  967. if (this._elem) {
  968. this._elem.emptyForce();
  969. this._elem = null;
  970. }
  971. if (this.show) {
  972. var series = this._series, s;
  973. // make a table. one line label per row.
  974. var elem = document.createElement('table');
  975. this._elem = $(elem);
  976. elem = null;
  977. this._elem.addClass('jqplot-legend jqplot-cursor-legend');
  978. this._elem.css('position', 'absolute');
  979. var pad = false;
  980. for (var i = 0; i< series.length; i++) {
  981. s = series[i];
  982. if (s.show && s.showLabel) {
  983. var lt = $.jqplot.sprintf(this.formatString, s.label.toString());
  984. if (lt) {
  985. var color = s.color;
  986. if (s._stack && !s.fill) {
  987. color = '';
  988. }
  989. addrow.call(this, lt, color, pad, i);
  990. pad = true;
  991. }
  992. // let plugins add more rows to legend. Used by trend line plugin.
  993. for (var j=0; j<$.jqplot.addLegendRowHooks.length; j++) {
  994. var item = $.jqplot.addLegendRowHooks[j].call(this, s);
  995. if (item) {
  996. addrow.call(this, item.label, item.color, pad);
  997. pad = true;
  998. }
  999. }
  1000. }
  1001. }
  1002. series = s = null;
  1003. delete series;
  1004. delete s;
  1005. }
  1006. function addrow(label, color, pad, idx) {
  1007. var rs = (pad) ? this.rowSpacing : '0';
  1008. var tr = $('<tr class="jqplot-legend jqplot-cursor-legend"></tr>').appendTo(this._elem);
  1009. tr.data('seriesIndex', idx);
  1010. $('<td class="jqplot-legend jqplot-cursor-legend-swatch" style="padding-top:'+rs+';">'+
  1011. '<div style="border:1px solid #cccccc;padding:0.2em;">'+
  1012. '<div class="jqplot-cursor-legend-swatch" style="background-color:'+color+';"></div>'+
  1013. '</div></td>').appendTo(tr);
  1014. var td = $('<td class="jqplot-legend jqplot-cursor-legend-label" style="vertical-align:middle;padding-top:'+rs+';"></td>');
  1015. td.appendTo(tr);
  1016. td.data('seriesIndex', idx);
  1017. if (this.escapeHtml) {
  1018. td.text(label);
  1019. }
  1020. else {
  1021. td.html(label);
  1022. }
  1023. tr = null;
  1024. td = null;
  1025. }
  1026. return this._elem;
  1027. };
  1028. })(jQuery);