jqplot.highlighter.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465
  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. $.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMove]);
  33. /**
  34. * Class: $.jqplot.Highlighter
  35. * Plugin which will highlight data points when they are moused over.
  36. *
  37. * To use this plugin, include the js
  38. * file in your source:
  39. *
  40. * > <script type="text/javascript" src="plugins/jqplot.highlighter.js"></script>
  41. *
  42. * A tooltip providing information about the data point is enabled by default.
  43. * To disable the tooltip, set "showTooltip" to false.
  44. *
  45. * You can control what data is displayed in the tooltip with various
  46. * options. The "tooltipAxes" option controls whether the x, y or both
  47. * data values are displayed.
  48. *
  49. * Some chart types (e.g. hi-low-close) have more than one y value per
  50. * data point. To display the additional values in the tooltip, set the
  51. * "yvalues" option to the desired number of y values present (3 for a hlc chart).
  52. *
  53. * By default, data values will be formatted with the same formatting
  54. * specifiers as used to format the axis ticks. A custom format code
  55. * can be supplied with the tooltipFormatString option. This will apply
  56. * to all values in the tooltip.
  57. *
  58. * For more complete control, the "formatString" option can be set. This
  59. * Allows conplete control over tooltip formatting. Values are passed to
  60. * the format string in an order determined by the "tooltipAxes" and "yvalues"
  61. * options. So, if you have a hi-low-close chart and you just want to display
  62. * the hi-low-close values in the tooltip, you could set a formatString like:
  63. *
  64. * > highlighter: {
  65. * > tooltipAxes: 'y',
  66. * > yvalues: 3,
  67. * > formatString:'<table class="jqplot-highlighter">
  68. * > <tr><td>hi:</td><td>%s</td></tr>
  69. * > <tr><td>low:</td><td>%s</td></tr>
  70. * > <tr><td>close:</td><td>%s</td></tr></table>'
  71. * > }
  72. *
  73. */
  74. $.jqplot.Highlighter = function(options) {
  75. // Group: Properties
  76. //
  77. //prop: show
  78. // true to show the highlight.
  79. this.show = $.jqplot.config.enablePlugins;
  80. // prop: markerRenderer
  81. // Renderer used to draw the marker of the highlighted point.
  82. // Renderer will assimilate attributes from the data point being highlighted,
  83. // so no attributes need set on the renderer directly.
  84. // Default is to turn off shadow drawing on the highlighted point.
  85. this.markerRenderer = new $.jqplot.MarkerRenderer({shadow:false});
  86. // prop: showMarker
  87. // true to show the marker
  88. this.showMarker = true;
  89. // prop: lineWidthAdjust
  90. // Pixels to add to the lineWidth of the highlight.
  91. this.lineWidthAdjust = 2.5;
  92. // prop: sizeAdjust
  93. // Pixels to add to the overall size of the highlight.
  94. this.sizeAdjust = 5;
  95. // prop: showTooltip
  96. // Show a tooltip with data point values.
  97. this.showTooltip = true;
  98. // prop: tooltipLocation
  99. // Where to position tooltip, 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
  100. this.tooltipLocation = 'nw';
  101. // prop: fadeTooltip
  102. // true = fade in/out tooltip, flase = show/hide tooltip
  103. this.fadeTooltip = true;
  104. // prop: tooltipFadeSpeed
  105. // 'slow', 'def', 'fast', or number of milliseconds.
  106. this.tooltipFadeSpeed = "fast";
  107. // prop: tooltipOffset
  108. // Pixel offset of tooltip from the highlight.
  109. this.tooltipOffset = 2;
  110. // prop: tooltipAxes
  111. // Which axes to display in tooltip, 'x', 'y' or 'both', 'xy' or 'yx'
  112. // 'both' and 'xy' are equivalent, 'yx' reverses order of labels.
  113. this.tooltipAxes = 'both';
  114. // prop; tooltipSeparator
  115. // String to use to separate x and y axes in tooltip.
  116. this.tooltipSeparator = ', ';
  117. // prop; tooltipContentEditor
  118. // Function used to edit/augment/replace the formatted tooltip contents.
  119. // Called as str = tooltipContentEditor(str, seriesIndex, pointIndex)
  120. // where str is the generated tooltip html and seriesIndex and pointIndex identify
  121. // the data point being highlighted. Should return the html for the tooltip contents.
  122. this.tooltipContentEditor = null;
  123. // prop: useAxesFormatters
  124. // Use the x and y axes formatters to format the text in the tooltip.
  125. this.useAxesFormatters = true;
  126. // prop: tooltipFormatString
  127. // sprintf format string for the tooltip.
  128. // Uses Ash Searle's javascript sprintf implementation
  129. // found here: http://hexmen.com/blog/2007/03/printf-sprintf/
  130. // See http://perldoc.perl.org/functions/sprintf.html for reference.
  131. // Additional "p" and "P" format specifiers added by Chris Leonello.
  132. this.tooltipFormatString = '%.5P';
  133. // prop: formatString
  134. // alternative to tooltipFormatString
  135. // will format the whole tooltip text, populating with x, y values as
  136. // indicated by tooltipAxes option. So, you could have a tooltip like:
  137. // 'Date: %s, number of cats: %d' to format the whole tooltip at one go.
  138. // If useAxesFormatters is true, values will be formatted according to
  139. // Axes formatters and you can populate your tooltip string with
  140. // %s placeholders.
  141. this.formatString = null;
  142. // prop: yvalues
  143. // Number of y values to expect in the data point array.
  144. // Typically this is 1. Certain plots, like OHLC, will
  145. // have more y values in each data point array.
  146. this.yvalues = 1;
  147. // prop: bringSeriesToFront
  148. // This option requires jQuery 1.4+
  149. // True to bring the series of the highlighted point to the front
  150. // of other series.
  151. this.bringSeriesToFront = false;
  152. this._tooltipElem;
  153. this.isHighlighting = false;
  154. this.currentNeighbor = null;
  155. $.extend(true, this, options);
  156. };
  157. var locations = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w'];
  158. var locationIndicies = {'nw':0, 'n':1, 'ne':2, 'e':3, 'se':4, 's':5, 'sw':6, 'w':7};
  159. var oppositeLocations = ['se', 's', 'sw', 'w', 'nw', 'n', 'ne', 'e'];
  160. // axis.renderer.tickrenderer.formatter
  161. // called with scope of plot
  162. $.jqplot.Highlighter.init = function (target, data, opts){
  163. var options = opts || {};
  164. // add a highlighter attribute to the plot
  165. this.plugins.highlighter = new $.jqplot.Highlighter(options.highlighter);
  166. };
  167. // called within scope of series
  168. $.jqplot.Highlighter.parseOptions = function (defaults, options) {
  169. // Add a showHighlight option to the series
  170. // and set it to true by default.
  171. this.showHighlight = true;
  172. };
  173. // called within context of plot
  174. // create a canvas which we can draw on.
  175. // insert it before the eventCanvas, so eventCanvas will still capture events.
  176. $.jqplot.Highlighter.postPlotDraw = function() {
  177. // Memory Leaks patch
  178. if (this.plugins.highlighter && this.plugins.highlighter.highlightCanvas) {
  179. this.plugins.highlighter.highlightCanvas.resetCanvas();
  180. this.plugins.highlighter.highlightCanvas = null;
  181. }
  182. if (this.plugins.highlighter && this.plugins.highlighter._tooltipElem) {
  183. this.plugins.highlighter._tooltipElem.emptyForce();
  184. this.plugins.highlighter._tooltipElem = null;
  185. }
  186. this.plugins.highlighter.highlightCanvas = new $.jqplot.GenericCanvas();
  187. this.eventCanvas._elem.before(this.plugins.highlighter.highlightCanvas.createElement(this._gridPadding, 'jqplot-highlight-canvas', this._plotDimensions, this));
  188. this.plugins.highlighter.highlightCanvas.setContext();
  189. var elem = document.createElement('div');
  190. this.plugins.highlighter._tooltipElem = $(elem);
  191. elem = null;
  192. this.plugins.highlighter._tooltipElem.addClass('jqplot-highlighter-tooltip');
  193. this.plugins.highlighter._tooltipElem.css({position:'absolute', display:'none'});
  194. this.eventCanvas._elem.before(this.plugins.highlighter._tooltipElem);
  195. };
  196. $.jqplot.preInitHooks.push($.jqplot.Highlighter.init);
  197. $.jqplot.preParseSeriesOptionsHooks.push($.jqplot.Highlighter.parseOptions);
  198. $.jqplot.postDrawHooks.push($.jqplot.Highlighter.postPlotDraw);
  199. function draw(plot, neighbor) {
  200. var hl = plot.plugins.highlighter;
  201. var s = plot.series[neighbor.seriesIndex];
  202. var smr = s.markerRenderer;
  203. var mr = hl.markerRenderer;
  204. mr.style = smr.style;
  205. mr.lineWidth = smr.lineWidth + hl.lineWidthAdjust;
  206. mr.size = smr.size + hl.sizeAdjust;
  207. var rgba = $.jqplot.getColorComponents(smr.color);
  208. var newrgb = [rgba[0], rgba[1], rgba[2]];
  209. var alpha = (rgba[3] >= 0.6) ? rgba[3]*0.6 : rgba[3]*(2-rgba[3]);
  210. mr.color = 'rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+alpha+')';
  211. mr.init();
  212. mr.draw(s.gridData[neighbor.pointIndex][0], s.gridData[neighbor.pointIndex][1], hl.highlightCanvas._ctx);
  213. }
  214. function showTooltip(plot, series, neighbor) {
  215. // neighbor looks like: {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]}
  216. // gridData should be x,y pixel coords on the grid.
  217. // add the plot._gridPadding to that to get x,y in the target.
  218. var hl = plot.plugins.highlighter;
  219. var elem = hl._tooltipElem;
  220. var serieshl = series.highlighter || {};
  221. var opts = $.extend(true, {}, hl, serieshl);
  222. if (opts.useAxesFormatters) {
  223. var xf = series._xaxis._ticks[0].formatter;
  224. var yf = series._yaxis._ticks[0].formatter;
  225. var xfstr = series._xaxis._ticks[0].formatString;
  226. var yfstr = series._yaxis._ticks[0].formatString;
  227. var str;
  228. var xstr = xf(xfstr, neighbor.data[0]);
  229. var ystrs = [];
  230. for (var i=1; i<opts.yvalues+1; i++) {
  231. ystrs.push(yf(yfstr, neighbor.data[i]));
  232. }
  233. if (typeof opts.formatString === 'string') {
  234. switch (opts.tooltipAxes) {
  235. case 'both':
  236. case 'xy':
  237. ystrs.unshift(xstr);
  238. ystrs.unshift(opts.formatString);
  239. str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
  240. break;
  241. case 'yx':
  242. ystrs.push(xstr);
  243. ystrs.unshift(opts.formatString);
  244. str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
  245. break;
  246. case 'x':
  247. str = $.jqplot.sprintf.apply($.jqplot.sprintf, [opts.formatString, xstr]);
  248. break;
  249. case 'y':
  250. ystrs.unshift(opts.formatString);
  251. str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
  252. break;
  253. default: // same as xy
  254. ystrs.unshift(xstr);
  255. ystrs.unshift(opts.formatString);
  256. str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs);
  257. break;
  258. }
  259. }
  260. else {
  261. switch (opts.tooltipAxes) {
  262. case 'both':
  263. case 'xy':
  264. str = xstr;
  265. for (var i=0; i<ystrs.length; i++) {
  266. str += opts.tooltipSeparator + ystrs[i];
  267. }
  268. break;
  269. case 'yx':
  270. str = '';
  271. for (var i=0; i<ystrs.length; i++) {
  272. str += ystrs[i] + opts.tooltipSeparator;
  273. }
  274. str += xstr;
  275. break;
  276. case 'x':
  277. str = xstr;
  278. break;
  279. case 'y':
  280. str = ystrs.join(opts.tooltipSeparator);
  281. break;
  282. default: // same as 'xy'
  283. str = xstr;
  284. for (var i=0; i<ystrs.length; i++) {
  285. str += opts.tooltipSeparator + ystrs[i];
  286. }
  287. break;
  288. }
  289. }
  290. }
  291. else {
  292. var str;
  293. if (typeof opts.formatString === 'string') {
  294. str = $.jqplot.sprintf.apply($.jqplot.sprintf, [opts.formatString].concat(neighbor.data));
  295. }
  296. else {
  297. if (opts.tooltipAxes == 'both' || opts.tooltipAxes == 'xy') {
  298. str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[0]) + opts.tooltipSeparator + $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[1]);
  299. }
  300. else if (opts.tooltipAxes == 'yx') {
  301. str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[1]) + opts.tooltipSeparator + $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[0]);
  302. }
  303. else if (opts.tooltipAxes == 'x') {
  304. str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[0]);
  305. }
  306. else if (opts.tooltipAxes == 'y') {
  307. str = $.jqplot.sprintf(opts.tooltipFormatString, neighbor.data[1]);
  308. }
  309. }
  310. }
  311. if ($.isFunction(opts.tooltipContentEditor)) {
  312. // args str, seriesIndex, pointIndex are essential so the hook can look up
  313. // extra data for the point.
  314. str = opts.tooltipContentEditor(str, neighbor.seriesIndex, neighbor.pointIndex, plot);
  315. }
  316. elem.html(str);
  317. var gridpos = {x:neighbor.gridData[0], y:neighbor.gridData[1]};
  318. var ms = 0;
  319. var fact = 0.707;
  320. if (series.markerRenderer.show == true) {
  321. ms = (series.markerRenderer.size + opts.sizeAdjust)/2;
  322. }
  323. var loc = locations;
  324. if (series.fillToZero && series.fill && neighbor.data[1] < 0) {
  325. loc = oppositeLocations;
  326. }
  327. switch (loc[locationIndicies[opts.tooltipLocation]]) {
  328. case 'nw':
  329. var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - fact * ms;
  330. var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - fact * ms;
  331. break;
  332. case 'n':
  333. var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
  334. var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - ms;
  335. break;
  336. case 'ne':
  337. var x = gridpos.x + plot._gridPadding.left + opts.tooltipOffset + fact * ms;
  338. var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - fact * ms;
  339. break;
  340. case 'e':
  341. var x = gridpos.x + plot._gridPadding.left + opts.tooltipOffset + ms;
  342. var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
  343. break;
  344. case 'se':
  345. var x = gridpos.x + plot._gridPadding.left + opts.tooltipOffset + fact * ms;
  346. var y = gridpos.y + plot._gridPadding.top + opts.tooltipOffset + fact * ms;
  347. break;
  348. case 's':
  349. var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
  350. var y = gridpos.y + plot._gridPadding.top + opts.tooltipOffset + ms;
  351. break;
  352. case 'sw':
  353. var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - fact * ms;
  354. var y = gridpos.y + plot._gridPadding.top + opts.tooltipOffset + fact * ms;
  355. break;
  356. case 'w':
  357. var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - ms;
  358. var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
  359. break;
  360. default: // same as 'nw'
  361. var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset - fact * ms;
  362. var y = gridpos.y + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true) - fact * ms;
  363. break;
  364. }
  365. elem.css('left', x);
  366. elem.css('top', y);
  367. if (opts.fadeTooltip) {
  368. // Fix for stacked up animations. Thnanks Trevor!
  369. elem.stop(true,true).fadeIn(opts.tooltipFadeSpeed);
  370. }
  371. else {
  372. elem.show();
  373. }
  374. elem = null;
  375. }
  376. function handleMove(ev, gridpos, datapos, neighbor, plot) {
  377. var hl = plot.plugins.highlighter;
  378. var c = plot.plugins.cursor;
  379. if (hl.show) {
  380. if (neighbor == null && hl.isHighlighting) {
  381. var evt = jQuery.Event('jqplotHighlighterUnhighlight');
  382. plot.target.trigger(evt);
  383. var ctx = hl.highlightCanvas._ctx;
  384. ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  385. if (hl.fadeTooltip) {
  386. hl._tooltipElem.fadeOut(hl.tooltipFadeSpeed);
  387. }
  388. else {
  389. hl._tooltipElem.hide();
  390. }
  391. if (hl.bringSeriesToFront) {
  392. plot.restorePreviousSeriesOrder();
  393. }
  394. hl.isHighlighting = false;
  395. hl.currentNeighbor = null;
  396. ctx = null;
  397. }
  398. else if (neighbor != null && plot.series[neighbor.seriesIndex].showHighlight && !hl.isHighlighting) {
  399. var evt = jQuery.Event('jqplotHighlighterHighlight');
  400. evt.which = ev.which;
  401. evt.pageX = ev.pageX;
  402. evt.pageY = ev.pageY;
  403. var ins = [neighbor.seriesIndex, neighbor.pointIndex, neighbor.data, plot];
  404. plot.target.trigger(evt, ins);
  405. hl.isHighlighting = true;
  406. hl.currentNeighbor = neighbor;
  407. if (hl.showMarker) {
  408. draw(plot, neighbor);
  409. }
  410. if (plot.series[neighbor.seriesIndex].show && hl.showTooltip && (!c || !c._zoom.started)) {
  411. showTooltip(plot, plot.series[neighbor.seriesIndex], neighbor);
  412. }
  413. if (hl.bringSeriesToFront) {
  414. plot.moveSeriesToFront(neighbor.seriesIndex);
  415. }
  416. }
  417. // check to see if we're highlighting the wrong point.
  418. else if (neighbor != null && hl.isHighlighting && hl.currentNeighbor != neighbor) {
  419. // highlighting the wrong point.
  420. // if new series allows highlighting, highlight new point.
  421. if (plot.series[neighbor.seriesIndex].showHighlight) {
  422. var ctx = hl.highlightCanvas._ctx;
  423. ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  424. hl.isHighlighting = true;
  425. hl.currentNeighbor = neighbor;
  426. if (hl.showMarker) {
  427. draw(plot, neighbor);
  428. }
  429. if (plot.series[neighbor.seriesIndex].show && hl.showTooltip && (!c || !c._zoom.started)) {
  430. showTooltip(plot, plot.series[neighbor.seriesIndex], neighbor);
  431. }
  432. if (hl.bringSeriesToFront) {
  433. plot.moveSeriesToFront(neighbor.seriesIndex);
  434. }
  435. }
  436. }
  437. }
  438. }
  439. })(jQuery);