UNPKG

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