merged cont.
[opensuse:yast-rest-service.git] / webyast / public / javascripts / plugin / jqplot.cursor.js
1 /**
2  * Copyright (c) 2009 Chris Leonello
3  * jqPlot is currently available for use in all personal or commercial projects 
4  * under both the MIT and GPL version 2.0 licenses. This means that you can 
5  * choose the license that best suits your project and use it accordingly. 
6  *
7  * The author would appreciate an email letting him know of any substantial
8  * use of jqPlot.  You can reach the author at: chris dot leonello at gmail 
9  * dot com or see http://www.jqplot.com/info.php .  This is, of course, 
10  * not required.
11  *
12  * If you are feeling kind and generous, consider supporting the project by
13  * making a donation at: http://www.jqplot.com/donate.php .
14  *
15  * Thanks for using jqPlot!
16  * 
17  */
18 (function($) {
19     
20     /**
21      * Class: $.jqplot.Cursor
22      * Plugin class representing the cursor as displayed on the plot.
23      */
24     $.jqplot.Cursor = function(options) {
25         // Group: Properties
26         //
27         // prop: style
28         // CSS spec for cursor style
29         this.style = 'crosshair';
30         this.previousCursor = 'auto';
31         // prop: show
32         // wether to show the cursor or not.
33         this.show = $.jqplot.config.enablePlugins;
34         // prop: showTooltip
35         // show a cursor position tooltip near the cursor
36         this.showTooltip = true;
37         // prop: followMouse
38         // Tooltip follows the mouse, it is not at a fixed location.
39         // Tooltip will show on the grid at the location given by
40         // tooltipLocation, offset from the grid edge by tooltipOffset.
41         this.followMouse = false;
42         // prop: tooltipLocation
43         // Where to position tooltip.  If followMouse is true, this is
44         // relative to the cursor, otherwise, it is relative to the grid.
45         // One of 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
46         this.tooltipLocation = 'se';
47         // prop: tooltipOffset
48         // Pixel offset of tooltip from the grid boudaries or cursor center.
49         this.tooltipOffset = 6;
50         // prop: showTooltipGridPosition
51         // show the grid pixel coordinates of the mouse.
52         this.showTooltipGridPosition = false;
53         // prop: showTooltipUnitPosition
54         // show the unit (data) coordinates of the mouse.
55         this.showTooltipUnitPosition = true;
56         // prop: showTooltipDataPosition
57         // Used with showVerticalLine to show intersecting data points in the tooltip.
58         this.showTooltipDataPosition = false;
59         // prop: tooltipFormatString
60         // sprintf format string for the tooltip.
61         // Uses Ash Searle's javascript sprintf implementation
62         // found here: http://hexmen.com/blog/2007/03/printf-sprintf/
63         // See http://perldoc.perl.org/functions/sprintf.html for reference
64         // Note, if showTooltipDataPosition is true, the default tooltipFormatString
65         // will be set to the cursorLegendFormatString, not the default given here.
66         this.tooltipFormatString = '%.4P, %.4P';
67         // prop: useAxesFormatters
68         // Use the x and y axes formatters to format the text in the tooltip.
69         this.useAxesFormatters = true;
70         // prop: tooltipAxisGroups
71         // Show position for the specified axes.
72         // This is an array like [['xaxis', 'yaxis'], ['xaxis', 'y2axis']]
73         // Default is to compute automatically for all visible axes.
74         this.tooltipAxisGroups = [];
75         // prop: zoom
76         // Enable plot zooming.
77         this.zoom = false;
78         // zoomProxy and zoomTarget properties are not directly set by user.  
79         // They Will be set through call to zoomProxy method.
80         this.zoomProxy = false;
81         this.zoomTarget = false;
82         // prop: clickReset
83         // Will reset plot zoom if single click on plot without drag.
84         this.clickReset = false;
85         // prop: dblClickReset
86         // Will reset plot zoom if double click on plot without drag.
87         this.dblClickReset = true;
88         // prop: showVerticalLine
89         // draw a vertical line across the plot which follows the cursor.
90         // When the line is near a data point, a special legend and/or tooltip can
91         // be updated with the data values.
92         this.showVerticalLine = false;
93         // prop: showHorizontalLine
94         // draw a horizontal line across the plot which follows the cursor.
95         this.showHorizontalLine = false;
96         // prop: constrainZoomTo
97         // 'none', 'x' or 'y'
98         this.constrainZoomTo = 'none';
99         // // prop: autoscaleConstraint
100         // // when a constrained axis is specified, true will
101         // // auatoscale the adjacent axis.
102         // this.autoscaleConstraint = true;
103         this.shapeRenderer = new $.jqplot.ShapeRenderer();
104         this._zoom = {start:[], end:[], started: false, zooming:false, isZoomed:false, axes:{start:{}, end:{}}};
105         this._tooltipElem;
106         this.zoomCanvas;
107         this.cursorCanvas;
108         // prop: intersectionThreshold
109         // pixel distance from data point or marker to consider cursor lines intersecting with point.
110         // If data point markers are not shown, this should be >= 1 or will often miss point intersections.
111         this.intersectionThreshold = 2;
112         // prop: showCursorLegend
113         // Replace the plot legend with an enhanced legend displaying intersection information.
114         this.showCursorLegend = false;
115         // prop: cursorLegendFormatString
116         // Format string used in the cursor legend.  If showTooltipDataPosition is true,
117         // this will also be the default format string used by tooltipFormatString.
118         this.cursorLegendFormatString = $.jqplot.Cursor.cursorLegendFormatString;
119         $.extend(true, this, options);
120     };
121     
122     $.jqplot.Cursor.cursorLegendFormatString = '%s x:%s, y:%s';
123     
124     // called with scope of plot
125     $.jqplot.Cursor.init = function (target, data, opts){
126         // add a cursor attribute to the plot
127         var options = opts || {};
128         this.plugins.cursor = new $.jqplot.Cursor(options.cursor);
129         var c = this.plugins.cursor;
130
131         if (c.show) {
132             $.jqplot.eventListenerHooks.push(['jqplotMouseEnter', handleMouseEnter]);
133             $.jqplot.eventListenerHooks.push(['jqplotMouseLeave', handleMouseLeave]);
134             $.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMouseMove]);
135             
136             if (c.showCursorLegend) {              
137                 opts.legend = opts.legend || {};
138                 opts.legend.renderer =  $.jqplot.CursorLegendRenderer;
139                 opts.legend.formatString = this.plugins.cursor.cursorLegendFormatString;
140                 opts.legend.show = true;
141             }
142             
143             if (c.zoom) {
144                 $.jqplot.eventListenerHooks.push(['jqplotMouseDown', handleMouseDown]);
145                 $.jqplot.eventListenerHooks.push(['jqplotMouseUp', handleMouseUp]);
146                 
147                 if (c.clickReset) {
148                     $.jqplot.eventListenerHooks.push(['jqplotClick', handleClick]);
149                 }
150                 
151                 if (c.dblClickReset) {
152                     $.jqplot.eventListenerHooks.push(['jqplotDblClick', handleDblClick]);
153                 }
154             }
155     
156             this.resetZoom = function() {
157                 var axes = this.axes;
158                 if (!c.zoomProxy) {
159                     for (var ax in axes) {
160                         axes[ax].reset();
161                     }
162                     this.redraw();
163                 }
164                 else {
165                     var ctx = this.plugins.cursor.zoomCanvas._ctx;
166                     ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
167                 }
168                 this.plugins.cursor._zoom.isZoomed = false;
169                 this.target.trigger('jqplotResetZoom', [this, this.plugins.cursor]);
170             };
171             
172
173             if (c.showTooltipDataPosition) {
174                 c.showTooltipUnitPosition = false;
175                 c.showTooltipGridPosition = false;
176                 if (options.cursor.tooltipFormatString == undefined) {
177                     c.tooltipFormatString = $.jqplot.Cursor.cursorLegendFormatString;
178                 }
179             }
180         }
181     };
182     
183     // called with context of plot
184     $.jqplot.Cursor.postDraw = function() {
185         var c = this.plugins.cursor;
186         // if (c.zoom) {
187         c.zoomCanvas = new $.jqplot.GenericCanvas();
188         this.eventCanvas._elem.before(c.zoomCanvas.createElement(this._gridPadding, 'jqplot-zoom-canvas', this._plotDimensions));
189         var zctx = c.zoomCanvas.setContext();
190         // }
191         c._tooltipElem = $('<div class="jqplot-cursor-tooltip" style="position:absolute;display:none"></div>');
192         c.zoomCanvas._elem.before(c._tooltipElem);
193         if (c.showVerticalLine || c.showHorizontalLine) {
194             c.cursorCanvas = new $.jqplot.GenericCanvas();
195             this.eventCanvas._elem.before(c.cursorCanvas.createElement(this._gridPadding, 'jqplot-cursor-canvas', this._plotDimensions));
196             var zctx = c.cursorCanvas.setContext();
197         }
198
199         // if we are showing the positions in unit coordinates, and no axes groups
200         // were specified, create a default set.
201         if (c.showTooltipUnitPosition){
202             if (c.tooltipAxisGroups.length === 0) {
203                 var series = this.series;
204                 var s;
205                 var temp = [];
206                 for (var i=0; i<series.length; i++) {
207                     s = series[i];
208                     var ax = s.xaxis+','+s.yaxis;
209                     if ($.inArray(ax, temp) == -1) {
210                         temp.push(ax);
211                     }
212                 }
213                 for (var i=0; i<temp.length; i++) {
214                     c.tooltipAxisGroups.push(temp[i].split(','));
215                 }
216             }
217         }
218     };
219     
220     // Group: methods
221     //
222     // method: $.jqplot.Cursor.zoomProxy
223     // links targetPlot to controllerPlot so that plot zooming of
224     // targetPlot will be controlled by zooming on the controllerPlot.
225     // controllerPlot will not actually zoom, but acts as an
226     // overview plot.  Note, the zoom options must be set to true for
227     // zoomProxy to work.
228     $.jqplot.Cursor.zoomProxy = function(targetPlot, controllerPlot) {
229         var tc = targetPlot.plugins.cursor;
230         var cc = controllerPlot.plugins.cursor;
231         tc.zoomTarget = true;
232         tc.zoom = true;
233         tc.style = 'auto';
234         tc.dblClickReset = false;
235         cc.zoom = true;
236         cc.zoomProxy = true;
237               
238         controllerPlot.target.bind('jqplotZoom', plotZoom);
239         controllerPlot.target.bind('jqplotResetZoom', plotReset);
240
241         function plotZoom(ev, gridpos, datapos, plot, cursor) {
242             tc.doZoom(gridpos, datapos, targetPlot, cursor);
243         } 
244
245         function plotReset(ev, plot, cursor) {
246             targetPlot.resetZoom();
247         }
248     };
249     
250     $.jqplot.Cursor.prototype.resetZoom = function(plot, cursor) {
251         var axes = plot.axes;
252         var cax = cursor._zoom.axes;
253         if (!plot.plugins.cursor.zoomProxy && cursor._zoom.isZoomed) {
254             for (var ax in axes) {
255                 axes[ax]._ticks = [];
256                 axes[ax].min = cax[ax].min;
257                 axes[ax].max = cax[ax].max;
258                 axes[ax].numberTicks = cax[ax].numberTicks; 
259                 axes[ax].tickInterval = cax[ax].tickInterval;
260                 // for date axes
261                 axes[ax].daTickInterval = cax[ax].daTickInterval;
262             }
263             plot.redraw();
264             cursor._zoom.isZoomed = false;
265         }
266         else {
267             var ctx = cursor.zoomCanvas._ctx;
268             ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
269         }
270         plot.target.trigger('jqplotResetZoom', [plot, cursor]);
271     };
272     
273     $.jqplot.Cursor.resetZoom = function(plot) {
274         plot.resetZoom();
275     };
276     
277     $.jqplot.Cursor.prototype.doZoom = function (gridpos, datapos, plot, cursor) {
278         var c = cursor;
279         var axes = plot.axes;
280         var zaxes = c._zoom.axes;
281         var start = zaxes.start;
282         var end = zaxes.end;
283         var min, max;
284         var ctx = plot.plugins.cursor.zoomCanvas._ctx;
285         // don't zoom is zoom area is too small (in pixels)
286         if ((c.constrainZoomTo == 'none' && Math.abs(gridpos.x - c._zoom.start[0]) > 6 && Math.abs(gridpos.y - c._zoom.start[1]) > 6) || (c.constrainZoomTo == 'x' && Math.abs(gridpos.x - c._zoom.start[0]) > 6) ||  (c.constrainZoomTo == 'y' && Math.abs(gridpos.y - c._zoom.start[1]) > 6)) {
287             if (!plot.plugins.cursor.zoomProxy) {
288                 for (var ax in datapos) {
289                     // make a copy of the original axes to revert back.
290                     if (c._zoom.axes[ax] == undefined) {
291                         c._zoom.axes[ax] = {};
292                         c._zoom.axes[ax].numberTicks = axes[ax].numberTicks;
293                         c._zoom.axes[ax].tickInterval = axes[ax].tickInterval;
294                         // for date axes...
295                         c._zoom.axes[ax].daTickInterval = axes[ax].daTickInterval;
296                         c._zoom.axes[ax].min = axes[ax].min;
297                         c._zoom.axes[ax].max = axes[ax].max;
298                     }
299                     if ((c.constrainZoomTo == 'none') || (c.constrainZoomTo == 'x' && ax.charAt(0) == 'x') || (c.constrainZoomTo == 'y' && ax.charAt(0) == 'y')) {   
300                         dp = datapos[ax];
301                         if (dp != null) {           
302                             if (dp > start[ax]) { 
303                                 axes[ax].min = start[ax];
304                                 axes[ax].max = dp;
305                             }
306                             else {
307                                 span = start[ax] - dp;
308                                 axes[ax].max = start[ax];
309                                 axes[ax].min = dp;
310                             }
311                             axes[ax].tickInterval = null;
312                             // for date axes...
313                             axes[ax].daTickInterval = null;
314                             axes[ax]._ticks = [];
315                         }
316                     }
317                             
318                     // if ((c.constrainZoomTo == 'x' && ax.charAt(0) == 'y' && c.autoscaleConstraint) || (c.constrainZoomTo == 'y' && ax.charAt(0) == 'x' && c.autoscaleConstraint)) {
319                     //     dp = datapos[ax];
320                     //     if (dp != null) {
321                     //         axes[ax].max == null;
322                     //         axes[ax].min = null;
323                     //     }
324                     // }
325                 }
326                 ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
327                 plot.redraw();
328                 c._zoom.isZoomed = true;
329             }
330             plot.target.trigger('jqplotZoom', [gridpos, datapos, plot, cursor]);
331         }
332     };
333     
334     $.jqplot.preInitHooks.push($.jqplot.Cursor.init);
335     $.jqplot.postDrawHooks.push($.jqplot.Cursor.postDraw);
336     
337     function updateTooltip(gridpos, datapos, plot) {
338         var c = plot.plugins.cursor;
339         var s = '';
340         var addbr = false;
341         if (c.showTooltipGridPosition) {
342             s = gridpos.x+', '+gridpos.y;
343             addbr = true;
344         }
345         if (c.showTooltipUnitPosition) {
346             var g;
347             for (var i=0; i<c.tooltipAxisGroups.length; i++) {
348                 g = c.tooltipAxisGroups[i];
349                 if (addbr) {
350                     s += '<br />';
351                 }
352                 if (c.useAxesFormatters) {
353                     var xf = plot.axes[g[0]]._ticks[0].formatter;
354                     var yf = plot.axes[g[1]]._ticks[0].formatter;
355                     var xfstr = plot.axes[g[0]]._ticks[0].formatString;
356                     var yfstr = plot.axes[g[1]]._ticks[0].formatString;
357                     s += xf(xfstr, datapos[g[0]]) + ', '+ yf(yfstr, datapos[g[1]]);
358                 }
359                 else {
360                     s += $.jqplot.sprintf(c.tooltipFormatString, datapos[g[0]], datapos[g[1]]);
361                 }
362                 addbr = true;
363             }
364         }
365         
366         if (c.showTooltipDataPosition) {
367             var series = plot.series; 
368             var ret = getIntersectingPoints(plot, gridpos.x, gridpos.y);
369             var addbr = false;
370         
371             for (var i = 0; i< series.length; i++) {
372                 if (series[i].show) {
373                     var idx = series[i].index;
374                     var label = series[i].label.toString();
375                     var cellid = $.inArray(idx, ret.indices);
376                     var sx = undefined;
377                     var sy = undefined;
378                     if (cellid != -1) {
379                         var data = ret.data[cellid].data;
380                         if (c.useAxesFormatters) {
381                             var xf = series[i]._xaxis._ticks[0].formatter;
382                             var yf = series[i]._yaxis._ticks[0].formatter;
383                             var xfstr = series[i]._xaxis._ticks[0].formatString;
384                             var yfstr = series[i]._yaxis._ticks[0].formatString;
385                             sx = xf(xfstr, data[0]);
386                             sy = yf(yfstr, data[1]);
387                         }
388                         else {
389                             sx = data[0];
390                             sy = data[1];
391                         }
392                         if (addbr) {
393                             s += '<br />';
394                         }
395                         s += $.jqplot.sprintf(c.tooltipFormatString, label, sx, sy);
396                         addbr = true;
397                     }
398                 }
399             }
400             
401         }
402         c._tooltipElem.html(s);
403     }
404     
405     function moveLine(gridpos, plot) {
406         var c = plot.plugins.cursor;
407         var ctx = c.cursorCanvas._ctx;
408         ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
409         if (c.showVerticalLine) {
410             c.shapeRenderer.draw(ctx, [[gridpos.x, 0], [gridpos.x, ctx.canvas.height]]);
411         }
412         if (c.showHorizontalLine) {
413             c.shapeRenderer.draw(ctx, [[0, gridpos.y], [ctx.canvas.width, gridpos.y]]);
414         }
415         var ret = getIntersectingPoints(plot, gridpos.x, gridpos.y);
416         if (c.showCursorLegend) {
417             var cells = $(plot.targetId + ' td.jqplot-cursor-legend-label');
418             for (var i=0; i<cells.length; i++) {
419                 var idx = $(cells[i]).data('seriesIndex');
420                 var series = plot.series[idx];
421                 var label = series.label.toString();
422                 var cellid = $.inArray(idx, ret.indices);
423                 var sx = undefined;
424                 var sy = undefined;
425                 if (cellid != -1) {
426                     var data = ret.data[cellid].data;
427                     if (c.useAxesFormatters) {
428                         var xf = series._xaxis._ticks[0].formatter;
429                         var yf = series._yaxis._ticks[0].formatter;
430                         var xfstr = series._xaxis._ticks[0].formatString;
431                         var yfstr = series._yaxis._ticks[0].formatString;
432                         sx = xf(xfstr, data[0]);
433                         sy = yf(yfstr, data[1]);
434                     }
435                     else {
436                         sx = data[0];
437                         sy = data[1];
438                     }
439                 }
440                 if (plot.legend.escapeHtml) {
441                     $(cells[i]).text($.jqplot.sprintf(c.cursorLegendFormatString, label, sx, sy));
442                 }
443                 else {
444                     $(cells[i]).html($.jqplot.sprintf(c.cursorLegendFormatString, label, sx, sy));
445                 }
446             }        
447         }
448     }
449         
450     function getIntersectingPoints(plot, x, y) {
451         var ret = {indices:[], data:[]};
452         var s, i, d0, d, j, r;
453         var threshold;
454         var c = plot.plugins.cursor;
455         for (var i=0; i<plot.series.length; i++) {
456             s = plot.series[i];
457             r = s.renderer;
458             if (s.show) {
459                 threshold = c.intersectionThreshold;
460                 if (s.showMarker) {
461                     threshold += s.markerRenderer.size/2;
462                 }
463                 for (var j=0; j<s.gridData.length; j++) {
464                     p = s.gridData[j];
465                     // check vertical line
466                     if (c.showVerticalLine) {
467                         if (Math.abs(x-p[0]) <= threshold) {
468                             ret.indices.push(i);
469                             ret.data.push({seriesIndex: i, pointIndex:j, gridData:p, data:s.data[j]});
470                         }
471                     }
472                 } 
473             }
474         }
475         return ret;
476     }
477     
478     function moveTooltip(gridpos, plot) {
479         var c = plot.plugins.cursor;  
480         var elem = c._tooltipElem;
481         switch (c.tooltipLocation) {
482             case 'nw':
483                 var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - c.tooltipOffset;
484                 var y = gridpos.y + plot._gridPadding.top - c.tooltipOffset - elem.outerHeight(true);
485                 break;
486             case 'n':
487                 var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
488                 var y = gridpos.y + plot._gridPadding.top - c.tooltipOffset - elem.outerHeight(true);
489                 break;
490             case 'ne':
491                 var x = gridpos.x + plot._gridPadding.left + c.tooltipOffset;
492                 var y = gridpos.y + plot._gridPadding.top - c.tooltipOffset - elem.outerHeight(true);
493                 break;
494             case 'e':
495                 var x = gridpos.x + plot._gridPadding.left + c.tooltipOffset;
496                 var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
497                 break;
498             case 'se':
499                 var x = gridpos.x + plot._gridPadding.left + c.tooltipOffset;
500                 var y = gridpos.y + plot._gridPadding.top + c.tooltipOffset;
501                 break;
502             case 's':
503                 var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true)/2;
504                 var y = gridpos.y + plot._gridPadding.top + c.tooltipOffset;
505                 break;
506             case 'sw':
507                 var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - c.tooltipOffset;
508                 var y = gridpos.y + plot._gridPadding.top + c.tooltipOffset;
509                 break;
510             case 'w':
511                 var x = gridpos.x + plot._gridPadding.left - elem.outerWidth(true) - c.tooltipOffset;
512                 var y = gridpos.y + plot._gridPadding.top - elem.outerHeight(true)/2;
513                 break;
514             default:
515                 var x = gridpos.x + plot._gridPadding.left + c.tooltipOffset;
516                 var y = gridpos.y + plot._gridPadding.top + c.tooltipOffset;
517                 break;
518         }
519             
520         c._tooltipElem.css('left', x);
521         c._tooltipElem.css('top', y);
522     }
523     
524     function positionTooltip(plot) { 
525         // fake a grid for positioning
526         var grid = plot._gridPadding; 
527         var c = plot.plugins.cursor;
528         var elem = c._tooltipElem;  
529         switch (c.tooltipLocation) {
530             case 'nw':
531                 var a = grid.left + c.tooltipOffset;
532                 var b = grid.top + c.tooltipOffset;
533                 elem.css('left', a);
534                 elem.css('top', b);
535                 break;
536             case 'n':
537                 var a = (grid.left + (plot._plotDimensions.width - grid.right))/2 - elem.outerWidth(true)/2;
538                 var b = grid.top + c.tooltipOffset;
539                 elem.css('left', a);
540                 elem.css('top', b);
541                 break;
542             case 'ne':
543                 var a = grid.right + c.tooltipOffset;
544                 var b = grid.top + c.tooltipOffset;
545                 elem.css({right:a, top:b});
546                 break;
547             case 'e':
548                 var a = grid.right + c.tooltipOffset;
549                 var b = (grid.top + (plot._plotDimensions.height - grid.bottom))/2 - elem.outerHeight(true)/2;
550                 elem.css({right:a, top:b});
551                 break;
552             case 'se':
553                 var a = grid.right + c.tooltipOffset;
554                 var b = grid.bottom + c.tooltipOffset;
555                 elem.css({right:a, bottom:b});
556                 break;
557             case 's':
558                 var a = (grid.left + (plot._plotDimensions.width - grid.right))/2 - elem.outerWidth(true)/2;
559                 var b = grid.bottom + c.tooltipOffset;
560                 elem.css({left:a, bottom:b});
561                 break;
562             case 'sw':
563                 var a = grid.left + c.tooltipOffset;
564                 var b = grid.bottom + c.tooltipOffset;
565                 elem.css({left:a, bottom:b});
566                 break;
567             case 'w':
568                 var a = grid.left + c.tooltipOffset;
569                 var b = (grid.top + (plot._plotDimensions.height - grid.bottom))/2 - elem.outerHeight(true)/2;
570                 elem.css({left:a, top:b});
571                 break;
572             default:  // same as 'se'
573                 var a = grid.right - c.tooltipOffset;
574                 var b = grid.bottom + c.tooltipOffset;
575                 elem.css({right:a, bottom:b});
576                 break;
577         }
578     }
579     
580     function handleClick (ev, gridpos, datapos, neighbor, plot) {
581         ev.stopPropagation();
582         ev.preventDefault();
583         var c = plot.plugins.cursor;
584         if (c.clickReset) {
585             c.resetZoom(plot, c);
586         }
587         return false;
588     }
589     
590     function handleDblClick (ev, gridpos, datapos, neighbor, plot) {
591         ev.stopPropagation();
592         ev.preventDefault();
593         var c = plot.plugins.cursor;
594         if (c.dblClickReset) {
595             c.resetZoom(plot, c);
596         }
597         return false;
598     }
599     
600     function handleMouseLeave(ev, gridpos, datapos, neighbor, plot) {
601         var c = plot.plugins.cursor;
602         if (c.show) {
603             $(ev.target).css('cursor', c.previousCursor);
604             if (c.showTooltip) {
605                 c._tooltipElem.hide();
606             }
607             if (c.zoom) {
608                 c._zoom.started = false;
609                 c._zoom.zooming = false;
610                 if (!c.zoomProxy) {
611                     var ctx = c.zoomCanvas._ctx;
612                     ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
613                 }
614             }
615             if (c.showVerticalLine || c.showHorizontalLine) {
616                 var ctx = c.cursorCanvas._ctx;
617                 ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
618             } if (c.showCursorLegend) {
619             var cells = $(plot.targetId + ' td.jqplot-cursor-legend-label');
620             for (var i=0; i<cells.length; i++) {
621                 var idx = $(cells[i]).data('seriesIndex');
622                 var series = plot.series[idx];
623                 var label = series.label.toString();
624                 if (plot.legend.escapeHtml) {
625                     $(cells[i]).text($.jqplot.sprintf(c.cursorLegendFormatString, label, undefined, undefined));
626                 }
627                 else {
628                     $(cells[i]).html($.jqplot.sprintf(c.cursorLegendFormatString, label, undefined, undefined));
629                 }
630                 
631             }        
632         }
633         }
634     }
635     
636     function handleMouseEnter(ev, gridpos, datapos, neighbor, plot) {
637         var c = plot.plugins.cursor;
638         if (c.show) {
639             c.previousCursor = ev.target.style.cursor;
640             ev.target.style.cursor = c.style;
641             if (c.showTooltip) {
642                 updateTooltip(gridpos, datapos, plot);
643                 if (c.followMouse) {
644                     moveTooltip(gridpos, plot);
645                 }
646                 else {
647                     positionTooltip(plot);
648                 }
649                 c._tooltipElem.show();
650             }
651             if (c.showVerticalLine || c.showHorizontalLine) {
652                 moveLine(gridpos, plot);
653             }
654         }
655     }
656     
657     function handleMouseMove(ev, gridpos, datapos, neighbor, plot) {
658         var c = plot.plugins.cursor;
659         var ctx = c.zoomCanvas._ctx;
660         if (c.show) {
661             if (c.showTooltip) {
662                 updateTooltip(gridpos, datapos, plot);
663                 if (c.followMouse) {
664                     moveTooltip(gridpos, plot);
665                 }
666             }
667             if (c.zoom && c._zoom.started && !c.zoomTarget) {
668                 c._zoom.zooming = true;
669                 if (c.constrainZoomTo == 'x') {
670                     c._zoom.end = [gridpos.x, ctx.canvas.height];
671                 }
672                 else if (c.constrainZoomTo == 'y') {
673                     c._zoom.end = [ctx.canvas.width, gridpos.y];
674                 }
675                 else {
676                     c._zoom.end = [gridpos.x, gridpos.y];
677                 }
678                 drawZoomBox.call(c);
679             }
680             if (c.showVerticalLine || c.showHorizontalLine) {
681                 moveLine(gridpos, plot);
682             }
683         }
684     }
685     
686     function handleMouseDown(ev, gridpos, datapos, neighbor, plot) {
687         var c = plot.plugins.cursor;
688         var axes = plot.axes;
689         if (c.zoom) {
690             if (!c.zoomProxy) {
691                 var ctx = c.zoomCanvas._ctx;
692                 ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
693             }
694             if (c.constrainZoomTo == 'x') {
695                 c._zoom.start = [gridpos.x, 0];
696             }
697             else if (c.constrainZoomTo == 'y') {
698                 c._zoom.start = [0, gridpos.y];
699             }
700             else {
701                 c._zoom.start = [gridpos.x, gridpos.y];
702             }
703             c._zoom.started = true;
704             for (var ax in datapos) {
705                 // get zoom starting position.
706                 c._zoom.axes.start[ax] = datapos[ax];
707             }
708         }
709     }
710     
711     function handleMouseUp(ev, gridpos, datapos, neighbor, plot) {
712         var c = plot.plugins.cursor;
713         if (c.zoom && c._zoom.zooming && !c.zoomTarget) {
714             c.doZoom(gridpos, datapos, plot, c);
715         }
716         c._zoom.started = false;
717         c._zoom.zooming = false;
718     }
719     
720     function drawZoomBox() {
721         var start = this._zoom.start;
722         var end = this._zoom.end;
723         var ctx = this.zoomCanvas._ctx;
724         var l, t, h, w;
725         if (end[0] > start[0]) {
726             l = start[0];
727             w = end[0] - start[0];
728         }
729         else {
730             l = end[0];
731             w = start[0] - end[0];
732         }
733         if (end[1] > start[1]) {
734             t = start[1];
735             h = end[1] - start[1];
736         }
737         else {
738             t = end[1];
739             h = start[1] - end[1];
740         }
741         ctx.fillStyle = 'rgba(0,0,0,0.2)';
742         ctx.strokeStyle = '#999999';
743         ctx.lineWidth = 1.0;
744         ctx.clearRect(0,0,ctx.canvas.width, ctx.canvas.height);
745         ctx.fillRect(0,0,ctx.canvas.width, ctx.canvas.height);
746         ctx.clearRect(l, t, w, h);
747         // IE won't show transparent fill rect, so stroke a rect also.
748         ctx.strokeRect(l,t,w,h);
749     }
750     
751     $.jqplot.CursorLegendRenderer = function(options) {
752         $.jqplot.TableLegendRenderer.call(this, options);
753         this.formatString = '%s';
754     };
755     
756     $.jqplot.CursorLegendRenderer.prototype = new $.jqplot.TableLegendRenderer();
757     $.jqplot.CursorLegendRenderer.prototype.constructor = $.jqplot.CursorLegendRenderer;
758     
759     // called in context of a Legend
760     $.jqplot.CursorLegendRenderer.prototype.draw = function() {
761         if (this.show) {
762             var series = this._series;
763             // make a table.  one line label per row.
764             this._elem = $('<table class="jqplot-legend jqplot-cursor-legend" style="position:absolute"></table>');
765         
766             var pad = false;
767             for (var i = 0; i< series.length; i++) {
768                 s = series[i];
769                 if (s.show) {
770                     var lt = $.jqplot.sprintf(this.formatString, s.label.toString());
771                     if (lt) {
772                         var color = s.color;
773                         if (s._stack && !s.fill) {
774                             color = '';
775                         }
776                         addrow.call(this, lt, color, pad, i);
777                         pad = true;
778                     }
779                     // let plugins add more rows to legend.  Used by trend line plugin.
780                     for (var j=0; j<$.jqplot.addLegendRowHooks.length; j++) {
781                         var item = $.jqplot.addLegendRowHooks[j].call(this, s);
782                         if (item) {
783                             addrow.call(this, item.label, item.color, pad);
784                             pad = true;
785                         } 
786                     }
787                 }
788             }
789         }
790         
791         function addrow(label, color, pad, idx) {
792             var rs = (pad) ? this.rowSpacing : '0';
793             var tr = $('<tr class="jqplot-legend jqplot-cursor-legend"></tr>').appendTo(this._elem);
794             tr.data('seriesIndex', idx);
795             $('<td class="jqplot-legend jqplot-cursor-legend-swatch" style="padding-top:'+rs+';">'+
796                 '<div style="border:1px solid #cccccc;padding:0.2em;">'+
797                 '<div class="jqplot-cursor-legend-swatch" style="background-color:'+color+';"></div>'+
798                 '</div></td>').appendTo(tr);
799             var td = $('<td class="jqplot-legend jqplot-cursor-legend-label" style="vertical-align:middle;padding-top:'+rs+';"></td>');
800             td.appendTo(tr);
801             td.data('seriesIndex', idx);
802             if (this.escapeHtml) {
803                 td.text(label);
804             }
805             else {
806                 td.html(label);
807             }
808         }
809         return this._elem;
810     };
811     
812 })(jQuery);