1 | /** |
---|
2 | * jqPlot |
---|
3 | * Pure JavaScript plotting plugin using jQuery |
---|
4 | * |
---|
5 | * Version: @VERSION |
---|
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 | $.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMove]); |
---|
32 | |
---|
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 wether 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 | |
---|
155 | $.extend(true, this, options); |
---|
156 | }; |
---|
157 | |
---|
158 | var locations = ['nw', 'n', 'ne', 'e', 'se', 's', 'sw', 'w']; |
---|
159 | var locationIndicies = {'nw':0, 'n':1, 'ne':2, 'e':3, 'se':4, 's':5, 'sw':6, 'w':7}; |
---|
160 | var oppositeLocations = ['se', 's', 'sw', 'w', 'nw', 'n', 'ne', 'e']; |
---|
161 | |
---|
162 | // axis.renderer.tickrenderer.formatter |
---|
163 | |
---|
164 | // called with scope of plot |
---|
165 | $.jqplot.Highlighter.init = function (target, data, opts){ |
---|
166 | var options = opts || {}; |
---|
167 | // add a highlighter attribute to the plot |
---|
168 | this.plugins.highlighter = new $.jqplot.Highlighter(options.highlighter); |
---|
169 | }; |
---|
170 | |
---|
171 | // called within scope of series |
---|
172 | $.jqplot.Highlighter.parseOptions = function (defaults, options) { |
---|
173 | // Add a showHighlight option to the series |
---|
174 | // and set it to true by default. |
---|
175 | this.showHighlight = true; |
---|
176 | }; |
---|
177 | |
---|
178 | // called within context of plot |
---|
179 | // create a canvas which we can draw on. |
---|
180 | // insert it before the eventCanvas, so eventCanvas will still capture events. |
---|
181 | $.jqplot.Highlighter.postPlotDraw = function() { |
---|
182 | // Memory Leaks patch |
---|
183 | if (this.plugins.highlighter && this.plugins.highlighter.highlightCanvas) { |
---|
184 | this.plugins.highlighter.highlightCanvas.resetCanvas(); |
---|
185 | this.plugins.highlighter.highlightCanvas = null; |
---|
186 | } |
---|
187 | |
---|
188 | if (this.plugins.highlighter && this.plugins.highlighter._tooltipElem) { |
---|
189 | this.plugins.highlighter._tooltipElem.emptyForce(); |
---|
190 | this.plugins.highlighter._tooltipElem = null; |
---|
191 | } |
---|
192 | |
---|
193 | this.plugins.highlighter.highlightCanvas = new $.jqplot.GenericCanvas(); |
---|
194 | |
---|
195 | this.eventCanvas._elem.before(this.plugins.highlighter.highlightCanvas.createElement(this._gridPadding, 'jqplot-highlight-canvas', this._plotDimensions, this)); |
---|
196 | this.plugins.highlighter.highlightCanvas.setContext(); |
---|
197 | |
---|
198 | var elem = document.createElement('div'); |
---|
199 | this.plugins.highlighter._tooltipElem = $(elem); |
---|
200 | elem = null; |
---|
201 | this.plugins.highlighter._tooltipElem.addClass('jqplot-highlighter-tooltip'); |
---|
202 | this.plugins.highlighter._tooltipElem.css({position:'absolute', display:'none'}); |
---|
203 | |
---|
204 | this.eventCanvas._elem.before(this.plugins.highlighter._tooltipElem); |
---|
205 | }; |
---|
206 | |
---|
207 | $.jqplot.preInitHooks.push($.jqplot.Highlighter.init); |
---|
208 | $.jqplot.preParseSeriesOptionsHooks.push($.jqplot.Highlighter.parseOptions); |
---|
209 | $.jqplot.postDrawHooks.push($.jqplot.Highlighter.postPlotDraw); |
---|
210 | |
---|
211 | function draw(plot, neighbor) { |
---|
212 | var hl = plot.plugins.highlighter; |
---|
213 | var s = plot.series[neighbor.seriesIndex]; |
---|
214 | var smr = s.markerRenderer; |
---|
215 | var mr = hl.markerRenderer; |
---|
216 | mr.style = smr.style; |
---|
217 | mr.lineWidth = smr.lineWidth + hl.lineWidthAdjust; |
---|
218 | mr.size = smr.size + hl.sizeAdjust; |
---|
219 | var rgba = $.jqplot.getColorComponents(smr.color); |
---|
220 | var newrgb = [rgba[0], rgba[1], rgba[2]]; |
---|
221 | var alpha = (rgba[3] >= 0.6) ? rgba[3]*0.6 : rgba[3]*(2-rgba[3]); |
---|
222 | mr.color = 'rgba('+newrgb[0]+','+newrgb[1]+','+newrgb[2]+','+alpha+')'; |
---|
223 | mr.init(); |
---|
224 | mr.draw(s.gridData[neighbor.pointIndex][0], s.gridData[neighbor.pointIndex][1], hl.highlightCanvas._ctx); |
---|
225 | } |
---|
226 | |
---|
227 | function showTooltip(plot, series, neighbor) { |
---|
228 | // neighbor looks like: {seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]} |
---|
229 | // gridData should be x,y pixel coords on the grid. |
---|
230 | // add the plot._gridPadding to that to get x,y in the target. |
---|
231 | var hl = plot.plugins.highlighter; |
---|
232 | var elem = hl._tooltipElem; |
---|
233 | if (hl.useAxesFormatters) { |
---|
234 | var xf = series._xaxis._ticks[0].formatter; |
---|
235 | var yf = series._yaxis._ticks[0].formatter; |
---|
236 | var xfstr = series._xaxis._ticks[0].formatString; |
---|
237 | var yfstr = series._yaxis._ticks[0].formatString; |
---|
238 | var str; |
---|
239 | var xstr = xf(xfstr, neighbor.data[0]); |
---|
240 | var ystrs = []; |
---|
241 | for (var i=1; i<hl.yvalues+1; i++) { |
---|
242 | ystrs.push(yf(yfstr, neighbor.data[i])); |
---|
243 | } |
---|
244 | if (hl.formatString) { |
---|
245 | switch (hl.tooltipAxes) { |
---|
246 | case 'both': |
---|
247 | case 'xy': |
---|
248 | ystrs.unshift(xstr); |
---|
249 | ystrs.unshift(hl.formatString); |
---|
250 | str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs); |
---|
251 | break; |
---|
252 | case 'yx': |
---|
253 | ystrs.push(xstr); |
---|
254 | ystrs.unshift(hl.formatString); |
---|
255 | str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs); |
---|
256 | break; |
---|
257 | case 'x': |
---|
258 | str = $.jqplot.sprintf.apply($.jqplot.sprintf, [hl.formatString, xstr]); |
---|
259 | break; |
---|
260 | case 'y': |
---|
261 | ystrs.unshift(hl.formatString); |
---|
262 | str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs); |
---|
263 | break; |
---|
264 | default: // same as xy |
---|
265 | ystrs.unshift(xstr); |
---|
266 | ystrs.unshift(hl.formatString); |
---|
267 | str = $.jqplot.sprintf.apply($.jqplot.sprintf, ystrs); |
---|
268 | break; |
---|
269 | } |
---|
270 | } |
---|
271 | else { |
---|
272 | switch (hl.tooltipAxes) { |
---|
273 | case 'both': |
---|
274 | case 'xy': |
---|
275 | str = xstr; |
---|
276 | for (var i=0; i<ystrs.length; i++) { |
---|
277 | str += hl.tooltipSeparator + ystrs[i]; |
---|
278 | } |
---|
279 | break; |
---|
280 | case 'yx': |
---|
281 | str = ''; |
---|
282 | for (var i=0; i<ystrs.length; i++) { |
---|
283 | str += ystrs[i] + hl.tooltipSeparator; |
---|
284 | } |
---|
285 | str += xstr; |
---|
286 | break; |
---|
287 | case 'x': |
---|
288 | str = xstr; |
---|
289 | break; |
---|
290 | case 'y': |
---|
291 | str = ystrs.join(hl.tooltipSeparator); |
---|
292 | break; |
---|
293 | default: // same as 'xy' |
---|
294 | str = xstr; |
---|
295 | for (var i=0; i<ystrs.length; i++) { |
---|
296 | str += hl.tooltipSeparator + ystrs[i]; |
---|
297 | } |
---|
298 | break; |
---|
299 | |
---|
300 | } |
---|
301 | } |
---|
302 | } |
---|
303 | else { |
---|
304 | var str; |
---|
305 | if (hl.tooltipAxes == 'both' || hl.tooltipAxes == 'xy') { |
---|
306 | str = $.jqplot.sprintf(hl.tooltipFormatString, neighbor.data[0]) + hl.tooltipSeparator + $.jqplot.sprintf(hl.tooltipFormatString, neighbor.data[1]); |
---|
307 | } |
---|
308 | else if (hl.tooltipAxes == 'yx') { |
---|
309 | str = $.jqplot.sprintf(hl.tooltipFormatString, neighbor.data[1]) + hl.tooltipSeparator + $.jqplot.sprintf(hl.tooltipFormatString, neighbor.data[0]); |
---|
310 | } |
---|
311 | else if (hl.tooltipAxes == 'x') { |
---|
312 | str = $.jqplot.sprintf(hl.tooltipFormatString, neighbor.data[0]); |
---|
313 | } |
---|
314 | else if (hl.tooltipAxes == 'y') { |
---|
315 | str = $.jqplot.sprintf(hl.tooltipFormatString, neighbor.data[1]); |
---|
316 | } |
---|
317 | } |
---|
318 | if ($.isFunction(hl.tooltipContentEditor)) { |
---|
319 | // args str, seriesIndex, pointIndex are essential so the hook can look up |
---|
320 | // extra data for the point. |
---|
321 | str = hl.tooltipContentEditor(str, neighbor.seriesIndex, neighbor.pointIndex, plot); |
---|
322 | } |
---|
323 | elem.html(str); |
---|
324 | var gridpos = {x:neighbor.gridData[0], y:neighbor.gridData[1]}; |
---|
325 | var ms = 0; |
---|
326 | var fact = 0.707; |
---|
327 | if (series.markerRenderer.show == true) { |
---|
328 | ms = (series.markerRenderer.size + hl.sizeAdjust)/2; |
---|
329 | } |
---|
330 | |
---|
331 | var loc = locations; |
---|
332 | if (series.fillToZero && series.fill && neighbor.data[1] < 0) { |
---|
333 | loc = oppositeLocations; |
---|
334 | } |
---|
335 | |
---|
336 | switch (loc[locationIndicies[hl.tooltipLocation]]) { |
---|
337 | case 'nw': |
---|
338 | var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - hl.tooltipOffset - fact * ms; |
---|
339 | var y = gridpos.y + plot._gridPadding.top - hl.tooltipOffset - elem.outerHeight(true) - fact * ms; |
---|
340 | break; |
---|
341 | case 'n': |
---|
342 | var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2; |
---|
343 | var y = gridpos.y + plot._gridPadding.top - hl.tooltipOffset - elem.outerHeight(true) - ms; |
---|
344 | break; |
---|
345 | case 'ne': |
---|
346 | var x = gridpos.x + plot._gridPadding.left + hl.tooltipOffset + fact * ms; |
---|
347 | var y = gridpos.y + plot._gridPadding.top - hl.tooltipOffset - elem.outerHeight(true) - fact * ms; |
---|
348 | break; |
---|
349 | case 'e': |
---|
350 | var x = gridpos.x + plot._gridPadding.left + hl.tooltipOffset + ms; |
---|
351 | var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2; |
---|
352 | break; |
---|
353 | case 'se': |
---|
354 | var x = gridpos.x + plot._gridPadding.left + hl.tooltipOffset + fact * ms; |
---|
355 | var y = gridpos.y + plot._gridPadding.top + hl.tooltipOffset + fact * ms; |
---|
356 | break; |
---|
357 | case 's': |
---|
358 | var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2; |
---|
359 | var y = gridpos.y + plot._gridPadding.top + hl.tooltipOffset + ms; |
---|
360 | break; |
---|
361 | case 'sw': |
---|
362 | var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - hl.tooltipOffset - fact * ms; |
---|
363 | var y = gridpos.y + plot._gridPadding.top + hl.tooltipOffset + fact * ms; |
---|
364 | break; |
---|
365 | case 'w': |
---|
366 | var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - hl.tooltipOffset - ms; |
---|
367 | var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2; |
---|
368 | break; |
---|
369 | default: // same as 'nw' |
---|
370 | var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - hl.tooltipOffset - fact * ms; |
---|
371 | var y = gridpos.y + plot._gridPadding.top - hl.tooltipOffset - elem.outerHeight(true) - fact * ms; |
---|
372 | break; |
---|
373 | } |
---|
374 | elem.css('left', x); |
---|
375 | elem.css('top', y); |
---|
376 | if (hl.fadeTooltip) { |
---|
377 | // Fix for stacked up animations. Thnanks Trevor! |
---|
378 | elem.stop(true,true).fadeIn(hl.tooltipFadeSpeed); |
---|
379 | } |
---|
380 | else { |
---|
381 | elem.show(); |
---|
382 | } |
---|
383 | elem = null; |
---|
384 | |
---|
385 | } |
---|
386 | |
---|
387 | function handleMove(ev, gridpos, datapos, neighbor, plot) { |
---|
388 | var hl = plot.plugins.highlighter; |
---|
389 | var c = plot.plugins.cursor; |
---|
390 | if (hl.show) { |
---|
391 | if (neighbor == null && hl.isHighlighting) { |
---|
392 | var ctx = hl.highlightCanvas._ctx; |
---|
393 | ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); |
---|
394 | if (hl.fadeTooltip) { |
---|
395 | hl._tooltipElem.fadeOut(hl.tooltipFadeSpeed); |
---|
396 | } |
---|
397 | else { |
---|
398 | hl._tooltipElem.hide(); |
---|
399 | } |
---|
400 | if (hl.bringSeriesToFront) { |
---|
401 | plot.restorePreviousSeriesOrder(); |
---|
402 | } |
---|
403 | hl.isHighlighting = false; |
---|
404 | ctx = null; |
---|
405 | |
---|
406 | } |
---|
407 | else if (neighbor != null && plot.series[neighbor.seriesIndex].showHighlight && !hl.isHighlighting) { |
---|
408 | hl.isHighlighting = true; |
---|
409 | if (hl.showMarker) { |
---|
410 | draw(plot, neighbor); |
---|
411 | } |
---|
412 | if (hl.showTooltip && (!c || !c._zoom.started)) { |
---|
413 | showTooltip(plot, plot.series[neighbor.seriesIndex], neighbor); |
---|
414 | } |
---|
415 | if (hl.bringSeriesToFront) { |
---|
416 | plot.moveSeriesToFront(neighbor.seriesIndex); |
---|
417 | } |
---|
418 | } |
---|
419 | } |
---|
420 | } |
---|
421 | })(jQuery); |
---|