jquery.flot.hover.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. /* global jQuery */
  2. /**
  3. ## jquery.flot.hover.js
  4. This plugin is used for mouse hover and tap on a point of plot series.
  5. It supports the following options:
  6. ```js
  7. grid: {
  8. hoverable: false, //to trigger plothover event on mouse hover or tap on a point
  9. clickable: false //to trigger plotclick event on mouse hover
  10. }
  11. ```
  12. It listens to native mouse move event or click, as well as artificial generated
  13. tap and touchevent.
  14. When the mouse is over a point or a tap on a point is performed, that point or
  15. the correscponding bar will be highlighted and a "plothover" event will be generated.
  16. Custom "touchevent" is triggered when any touch interaction is made. Hover plugin
  17. handles this events by unhighlighting all of the previously highlighted points and generates
  18. "plothovercleanup" event to notify any part that is handling plothover (for exemple to cleanup
  19. the tooltip from webcharts).
  20. */
  21. (function($) {
  22. 'use strict';
  23. var options = {
  24. grid: {
  25. hoverable: false,
  26. clickable: false
  27. }
  28. };
  29. var browser = $.plot.browser;
  30. function init(plot) {
  31. plot.hooks.processOptions.push(initHover);
  32. }
  33. function initHover(plot, options) {
  34. var highlights = [];
  35. var eventType = {
  36. click: 'click',
  37. hover: 'hover'
  38. }
  39. var lastMouseMoveEvent = plot.getPlaceholder()[0].lastMouseMoveEvent;
  40. plot.highlight = highlight;
  41. plot.unhighlight = unhighlight;
  42. var tap = {
  43. generatePlothoverEvent: function (e) {
  44. var o = plot.getOptions(),
  45. newEvent = new CustomEvent('mouseevent');
  46. //transform from touch event to mouse event format
  47. newEvent.pageX = e.detail.changedTouches[0].pageX;
  48. newEvent.pageY = e.detail.changedTouches[0].pageY;
  49. newEvent.clientX = e.detail.changedTouches[0].clientX;
  50. newEvent.clientY = e.detail.changedTouches[0].clientY;
  51. if (o.grid.hoverable) {
  52. doTriggerClickHoverEvent(newEvent, eventType.hover, 30);
  53. }
  54. return false;
  55. }
  56. };
  57. function bindEvents(plot, eventHolder) {
  58. var o = plot.getOptions();
  59. if (o.grid.hoverable || o.grid.clickable) {
  60. eventHolder[0].addEventListener('touchevent', triggerCleanupEvent, false);
  61. eventHolder[0].addEventListener('tap', tap.generatePlothoverEvent, false);
  62. }
  63. if (options.grid.clickable) {
  64. eventHolder.click(onClick);
  65. }
  66. if (options.grid.hoverable) {
  67. eventHolder.mousemove(onMouseMove);
  68. // Use bind, rather than .mouseleave, because we officially
  69. // still support jQuery 1.2.6, which doesn't define a shortcut
  70. // for mouseenter or mouseleave. This was a bug/oversight that
  71. // was fixed somewhere around 1.3.x. We can return to using
  72. // .mouseleave when we drop support for 1.2.6.
  73. eventHolder.bind("mouseleave", onMouseLeave);
  74. }
  75. }
  76. function shutdown(plot, eventHolder) {
  77. eventHolder[0].removeEventListener('tap', tap.generatePlothoverEvent);
  78. eventHolder[0].removeEventListener('touchevent', triggerCleanupEvent);
  79. eventHolder.unbind("mousemove", onMouseMove);
  80. eventHolder.unbind("mouseleave", onMouseLeave);
  81. eventHolder.unbind("click", onClick);
  82. highlights = [];
  83. }
  84. function doTriggerClickHoverEvent(event, eventType, searchDistance) {
  85. var series = plot.getData();
  86. if (event !== undefined
  87. && series.length > 0
  88. && series[0].xaxis.c2p !== undefined
  89. && series[0].yaxis.c2p !== undefined) {
  90. var eventToTrigger = "plot" + eventType;
  91. var seriesFlag = eventType + "able";
  92. triggerClickHoverEvent(eventToTrigger, event,
  93. function(i) {
  94. return series[i][seriesFlag] !== false;
  95. }, searchDistance);
  96. }
  97. }
  98. if (options.grid.hoverable || options.grid.clickable) {
  99. plot.hooks.bindEvents.push(bindEvents);
  100. plot.hooks.shutdown.push(shutdown);
  101. plot.hooks.drawOverlay.push(drawOverlay);
  102. plot.hooks.processRawData.push(processRawData);
  103. }
  104. function onMouseMove(e) {
  105. lastMouseMoveEvent = e;
  106. plot.getPlaceholder()[0].lastMouseMoveEvent = e;
  107. doTriggerClickHoverEvent(e, eventType.hover);
  108. }
  109. function onMouseLeave(e) {
  110. lastMouseMoveEvent = undefined;
  111. plot.getPlaceholder()[0].lastMouseMoveEvent = undefined;
  112. triggerClickHoverEvent("plothover", e,
  113. function(i) {
  114. return false;
  115. });
  116. }
  117. function onClick(e) {
  118. doTriggerClickHoverEvent(e, eventType.click);
  119. }
  120. function triggerCleanupEvent() {
  121. plot.unhighlight();
  122. plot.getPlaceholder().trigger('plothovercleanup');
  123. }
  124. // trigger click or hover event (they send the same parameters
  125. // so we share their code)
  126. function triggerClickHoverEvent(eventname, event, seriesFilter, searchDistance) {
  127. var options = plot.getOptions(),
  128. offset = plot.offset(),
  129. page = browser.getPageXY(event),
  130. canvasX = page.X - offset.left,
  131. canvasY = page.Y - offset.top,
  132. pos = plot.c2p({
  133. left: canvasX,
  134. top: canvasY
  135. }),
  136. distance = searchDistance !== undefined ? searchDistance : options.grid.mouseActiveRadius;
  137. pos.pageX = page.X;
  138. pos.pageY = page.Y;
  139. var item = plot.findNearbyItem(canvasX, canvasY, seriesFilter, distance);
  140. if (item) {
  141. // fill in mouse pos for any listeners out there
  142. item.pageX = parseInt(item.series.xaxis.p2c(item.datapoint[0]) + offset.left, 10);
  143. item.pageY = parseInt(item.series.yaxis.p2c(item.datapoint[1]) + offset.top, 10);
  144. }
  145. if (options.grid.autoHighlight) {
  146. // clear auto-highlights
  147. for (var i = 0; i < highlights.length; ++i) {
  148. var h = highlights[i];
  149. if ((h.auto === eventname &&
  150. !(item && h.series === item.series &&
  151. h.point[0] === item.datapoint[0] &&
  152. h.point[1] === item.datapoint[1])) || !item) {
  153. unhighlight(h.series, h.point);
  154. }
  155. }
  156. if (item) {
  157. highlight(item.series, item.datapoint, eventname);
  158. }
  159. }
  160. plot.getPlaceholder().trigger(eventname, [pos, item]);
  161. }
  162. function highlight(s, point, auto) {
  163. if (typeof s === "number") {
  164. s = plot.getData()[s];
  165. }
  166. if (typeof point === "number") {
  167. var ps = s.datapoints.pointsize;
  168. point = s.datapoints.points.slice(ps * point, ps * (point + 1));
  169. }
  170. var i = indexOfHighlight(s, point);
  171. if (i === -1) {
  172. highlights.push({
  173. series: s,
  174. point: point,
  175. auto: auto
  176. });
  177. plot.triggerRedrawOverlay();
  178. } else if (!auto) {
  179. highlights[i].auto = false;
  180. }
  181. }
  182. function unhighlight(s, point) {
  183. if (s == null && point == null) {
  184. highlights = [];
  185. plot.triggerRedrawOverlay();
  186. return;
  187. }
  188. if (typeof s === "number") {
  189. s = plot.getData()[s];
  190. }
  191. if (typeof point === "number") {
  192. var ps = s.datapoints.pointsize;
  193. point = s.datapoints.points.slice(ps * point, ps * (point + 1));
  194. }
  195. var i = indexOfHighlight(s, point);
  196. if (i !== -1) {
  197. highlights.splice(i, 1);
  198. plot.triggerRedrawOverlay();
  199. }
  200. }
  201. function indexOfHighlight(s, p) {
  202. for (var i = 0; i < highlights.length; ++i) {
  203. var h = highlights[i];
  204. if (h.series === s &&
  205. h.point[0] === p[0] &&
  206. h.point[1] === p[1]) {
  207. return i;
  208. }
  209. }
  210. return -1;
  211. }
  212. function processRawData() {
  213. triggerCleanupEvent();
  214. doTriggerClickHoverEvent(lastMouseMoveEvent, eventType.hover);
  215. }
  216. function drawOverlay(plot, octx, overlay) {
  217. var plotOffset = plot.getPlotOffset(),
  218. i, hi;
  219. octx.save();
  220. octx.translate(plotOffset.left, plotOffset.top);
  221. for (i = 0; i < highlights.length; ++i) {
  222. hi = highlights[i];
  223. if (hi.series.bars.show) drawBarHighlight(hi.series, hi.point, octx);
  224. else drawPointHighlight(hi.series, hi.point, octx, plot);
  225. }
  226. octx.restore();
  227. }
  228. }
  229. function drawPointHighlight(series, point, octx, plot) {
  230. var x = point[0],
  231. y = point[1],
  232. axisx = series.xaxis,
  233. axisy = series.yaxis,
  234. highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString();
  235. if (x < axisx.min || x > axisx.max || y < axisy.min || y > axisy.max) {
  236. return;
  237. }
  238. var pointRadius = series.points.radius + series.points.lineWidth / 2;
  239. octx.lineWidth = pointRadius;
  240. octx.strokeStyle = highlightColor;
  241. var radius = 1.5 * pointRadius;
  242. x = axisx.p2c(x);
  243. y = axisy.p2c(y);
  244. octx.beginPath();
  245. var symbol = series.points.symbol;
  246. if (symbol === 'circle') {
  247. octx.arc(x, y, radius, 0, 2 * Math.PI, false);
  248. } else if (typeof symbol === 'string' && plot.drawSymbol && plot.drawSymbol[symbol]) {
  249. plot.drawSymbol[symbol](octx, x, y, radius, false);
  250. }
  251. octx.closePath();
  252. octx.stroke();
  253. }
  254. function drawBarHighlight(series, point, octx) {
  255. var highlightColor = (typeof series.highlightColor === "string") ? series.highlightColor : $.color.parse(series.color).scale('a', 0.5).toString(),
  256. fillStyle = highlightColor,
  257. barLeft;
  258. var barWidth = series.bars.barWidth[0] || series.bars.barWidth;
  259. switch (series.bars.align) {
  260. case "left":
  261. barLeft = 0;
  262. break;
  263. case "right":
  264. barLeft = -barWidth;
  265. break;
  266. default:
  267. barLeft = -barWidth / 2;
  268. }
  269. octx.lineWidth = series.bars.lineWidth;
  270. octx.strokeStyle = highlightColor;
  271. var fillTowards = series.bars.fillTowards || 0,
  272. bottom = fillTowards > series.yaxis.min ? Math.min(series.yaxis.max, fillTowards) : series.yaxis.min;
  273. $.plot.drawSeries.drawBar(point[0], point[1], point[2] || bottom, barLeft, barLeft + barWidth,
  274. function() {
  275. return fillStyle;
  276. }, series.xaxis, series.yaxis, octx, series.bars.horizontal, series.bars.lineWidth);
  277. }
  278. $.plot.plugins.push({
  279. init: init,
  280. options: options,
  281. name: 'hover',
  282. version: '0.1'
  283. });
  284. })(jQuery);