1 /* global jQuery */ |
|
2 |
|
3 /** |
|
4 ## jquery.flot.hover.js |
|
5 |
|
6 This plugin is used for mouse hover and tap on a point of plot series. |
|
7 It supports the following options: |
|
8 ```js |
|
9 grid: { |
|
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 |
|
15 It listens to native mouse move event or click, as well as artificial generated |
|
16 tap and touchevent. |
|
17 |
|
18 When the mouse is over a point or a tap on a point is performed, that point or |
|
19 the correscponding bar will be highlighted and a "plothover" event will be generated. |
|
20 |
|
21 Custom "touchevent" is triggered when any touch interaction is made. Hover plugin |
|
22 handles 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 |
|
24 the 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); |
|