Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 
 
 
 

1051 rader
42 KiB

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