/**
* jqPlot
* Pure JavaScript plotting plugin using jQuery
*
* Version: 1.0.9
* Revision: d96a669
*
* Copyright (c) 2009-2016 Chris Leonello
* jqPlot is currently available for use in all personal or commercial projects
* under both the MIT (http://www.opensource.org/licenses/mit-license.php) and GPL
* version 2.0 (http://www.gnu.org/licenses/gpl-2.0.html) licenses. This means that you can
* choose the license that best suits your project and use it accordingly.
*
* Although not required, the author would appreciate an email letting him
* know of any substantial use of jqPlot. You can reach the author at:
* chris at jqplot dot com or see http://www.jqplot.com/info.php .
*
* If you are feeling kind and generous, consider supporting the project by
* making a donation at: http://www.jqplot.com/donate.php .
*
* sprintf functions contained in jqplot.sprintf.js by Ash Searle:
*
* version 2007.04.27
* author Ash Searle
* http://hexmen.com/blog/2007/03/printf-sprintf/
* http://hexmen.com/js/sprintf.js
* The author (Ash Searle) has placed this code in the public domain:
* "This code is unrestricted: you are free to use it however you like."
*
*/
(function($) {
var objCounter = 0;
// class: $.jqplot.CanvasOverlay
$.jqplot.CanvasOverlay = function(opts){
var options = opts || {};
this.options = {
show: $.jqplot.config.enablePlugins,
deferDraw: false
};
// prop: objects
this.objects = [];
this.objectNames = [];
this.canvas = null;
this.markerRenderer = new $.jqplot.MarkerRenderer({style:'line'});
this.markerRenderer.init();
this.highlightObjectIndex = null;
if (options.objects) {
var objs = options.objects,
obj;
for (var i=0; i<objs.length; i++) {
obj = objs[i];
for (var n in obj) {
switch (n) {
case 'line':
this.addLine(obj[n]);
break;
case 'horizontalLine':
this.addHorizontalLine(obj[n]);
break;
case 'dashedHorizontalLine':
this.addDashedHorizontalLine(obj[n]);
break;
case 'verticalLine':
this.addVerticalLine(obj[n]);
break;
case 'dashedVerticalLine':
this.addDashedVerticalLine(obj[n]);
break;
case 'rectangle':
this.addRectangle(obj[n]);
break;
default:
break;
}
}
}
}
$.extend(true, this.options, options);
};
// called with scope of a plot object
$.jqplot.CanvasOverlay.postPlotInit = function (target, data, opts) {
var options = opts || {};
// add a canvasOverlay attribute to the plot
this.plugins.canvasOverlay = new $.jqplot.CanvasOverlay(options.canvasOverlay);
};
function LineBase() {
this.uid = null;
this.type = null;
this.gridStart = null;
this.gridStop = null;
this.tooltipWidthFactor = 0;
this.options = {
// prop: name
// Optional name for the overlay object.
// Can be later used to retrieve the object by name.
name: null,
// prop: show
// true to show (draw), false to not draw.
show: true,
// prop: lineWidth
// Width of the line.
lineWidth: 2,
// prop: lineCap
// Type of ending placed on the line ['round', 'butt', 'square']
lineCap: 'round',
// prop: color
// color of the line
color: '#666666',
// prop: shadow
// whether or not to draw a shadow on the line
shadow: true,
// prop: shadowAngle
// Shadow angle in degrees
shadowAngle: 45,
// prop: shadowOffset
// Shadow offset from line in pixels
shadowOffset: 1,
// prop: shadowDepth
// Number of times shadow is stroked, each stroke offset shadowOffset from the last.
shadowDepth: 3,
// prop: shadowAlpha
// Alpha channel transparency of shadow. 0 = transparent.
shadowAlpha: '0.07',
// prop: xaxis
// X axis to use for positioning/scaling the line.
xaxis: 'xaxis',
// prop: yaxis
// Y axis to use for positioning/scaling the line.
yaxis: 'yaxis',
// prop: showTooltip
// Show a tooltip with data point values.
showTooltip: false,
// prop: showTooltipPrecision
// Controls how close to line cursor must be to show tooltip.
// Higher number = closer to line, lower number = farther from line.
// 1.0 = cursor must be over line.
showTooltipPrecision: 0.6,
// prop: tooltipLocation
// Where to position tooltip, 'n', 'ne', 'e', 'se', 's', 'sw', 'w', 'nw'
tooltipLocation: 'nw',
// prop: fadeTooltip
// true = fade in/out tooltip, flase = show/hide tooltip
fadeTooltip: true,
// prop: tooltipFadeSpeed
// 'slow', 'def', 'fast', or number of milliseconds.
tooltipFadeSpeed: "fast",
// prop: tooltipOffset
// Pixel offset of tooltip from the highlight.
tooltipOffset: 4,
// prop: tooltipFormatString
// Format string passed the x and y values of the cursor on the line.
// e.g., 'Dogs: %.2f, Cats: %d'.
tooltipFormatString: '%d, %d'
};
}
function Rectangle(options) {
LineBase.call(this);
this.type = 'rectangle';
var opts = {
// prop: xmin
// x value for the start of the line, null to scale to axis min.
xmin: null,
// prop: xmax
// x value for the end of the line, null to scale to axis max.
xmax: null,
// prop xOffset
// offset ends of the line inside the grid. Number
xOffset: '6px', // number or string. Number interpreted as units, string as pixels.
xminOffset: null,
xmaxOffset: null,
ymin: null,
ymax: null,
yOffset: '6px', // number or string. Number interpreted as units, string as pixels.
yminOffset: null,
ymaxOffset: null
};
$.extend(true, this.options, opts, options);
if (this.options.showTooltipPrecision < 0.01) {
this.options.showTooltipPrecision = 0.01;
}
}
Rectangle.prototype = new LineBase();
Rectangle.prototype.constructor = Rectangle;
/**
* Class: Line
* A straight line.
*/
function Line(options) {
LineBase.call(this);
this.type = 'line';
var opts = {
// prop: start
// [x, y] coordinates for the start of the line.
start: [],
// prop: stop
// [x, y] coordinates for the end of the line.
stop: []
};
$.extend(true, this.options, opts, options);
if (this.options.showTooltipPrecision < 0.01) {
this.options.showTooltipPrecision = 0.01;
}
}
Line.prototype = new LineBase();
Line.prototype.constructor = Line;
/**
* Class: HorizontalLine
* A straight horizontal line.
*/
function HorizontalLine(options) {
LineBase.call(this);
this.type = 'horizontalLine';
var opts = {
// prop: y
// y value to position the line
y: null,
// prop: xmin
// x value for the start of the line, null to scale to axis min.
xmin: null,
// prop: xmax
// x value for the end of the line, null to scale to axis max.
xmax: null,
// prop xOffset
// offset ends of the line inside the grid. Number
xOffset: '6px', // number or string. Number interpreted as units, string as pixels.
xminOffset: null,
xmaxOffset: null
};
$.extend(true, this.options, opts, options);
if (this.options.showTooltipPrecision < 0.01) {
this.options.showTooltipPrecision = 0.01;
}
}
HorizontalLine.prototype = new LineBase();
HorizontalLine.prototype.constructor = HorizontalLine;
/**
* Class: DashedHorizontalLine
* A straight dashed horizontal line.
*/
function DashedHorizontalLine(options) {
LineBase.call(this);
this.type = 'dashedHorizontalLine';
var opts = {
y: null,
xmin: null,
xmax: null,
xOffset: '6px', // number or string. Number interpreted as units, string as pixels.
xminOffset: null,
xmaxOffset: null,
// prop: dashPattern
// Array of line, space settings in pixels.
// Default is 8 pixel of line, 8 pixel of space.
// Note, limit to a 2 element array b/c of bug with higher order arrays.
dashPattern: [8,8]
};
$.extend(true, this.options, opts, options);
if (this.options.showTooltipPrecision < 0.01) {
this.options.showTooltipPrecision = 0.01;
}
}
DashedHorizontalLine.prototype = new LineBase();
DashedHorizontalLine.prototype.constructor = DashedHorizontalLine;
/**
* Class: VerticalLine
* A straight vertical line.
*/
function VerticalLine(options) {
LineBase.call(this);
this.type = 'verticalLine';
var opts = {
x: null,
ymin: null,
ymax: null,
yOffset: '6px', // number or string. Number interpreted as units, string as pixels.
yminOffset: null,
ymaxOffset: null
};
$.extend(true, this.options, opts, options);
if (this.options.showTooltipPrecision < 0.01) {
this.options.showTooltipPrecision = 0.01;
}
}
VerticalLine.prototype = new LineBase();
VerticalLine.prototype.constructor = VerticalLine;
/**
* Class: DashedVerticalLine
* A straight dashed vertical line.
*/
function DashedVerticalLine(options) {
LineBase.call(this);
this.type = 'dashedVerticalLine';
this.start = null;
this.stop = null;
var opts = {
x: null,
ymin: null,
ymax: null,
yOffset: '6px', // number or string. Number interpreted as units, string as pixels.
yminOffset: null,
ymaxOffset: null,
// prop: dashPattern
// Array of line, space settings in pixels.
// Default is 8 pixel of line, 8 pixel of space.
// Note, limit to a 2 element array b/c of bug with higher order arrays.
dashPattern: [8,8]
};
$.extend(true, this.options, opts, options);
if (this.options.showTooltipPrecision < 0.01) {
this.options.showTooltipPrecision = 0.01;
}
}
DashedVerticalLine.prototype = new LineBase();
DashedVerticalLine.prototype.constructor = DashedVerticalLine;
$.jqplot.CanvasOverlay.prototype.addLine = function(opts) {
var line = new Line(opts);
line.uid = objCounter++;
this.objects.push(line);
this.objectNames.push(line.options.name);
};
$.jqplot.CanvasOverlay.prototype.addHorizontalLine = function(opts) {
var line = new HorizontalLine(opts);
line.uid = objCounter++;
this.objects.push(line);
this.objectNames.push(line.options.name);
};
$.jqplot.CanvasOverlay.prototype.addDashedHorizontalLine = function(opts) {
var line = new DashedHorizontalLine(opts);
line.uid = objCounter++;
this.objects.push(line);
this.objectNames.push(line.options.name);
};
$.jqplot.CanvasOverlay.prototype.addVerticalLine = function(opts) {
var line = new VerticalLine(opts);
line.uid = objCounter++;
this.objects.push(line);
this.objectNames.push(line.options.name);
};
$.jqplot.CanvasOverlay.prototype.addDashedVerticalLine = function(opts) {
var line = new DashedVerticalLine(opts);
line.uid = objCounter++;
this.objects.push(line);
this.objectNames.push(line.options.name);
};
$.jqplot.CanvasOverlay.prototype.addRectangle = function(opts) {
var line = new Rectangle(opts);
line.uid = objCounter++;
this.objects.push(line);
this.objectNames.push(line.options.name);
};
$.jqplot.CanvasOverlay.prototype.removeObject = function(idx) {
// check if integer, remove by index
if ($.type(idx) == 'number') {
this.objects.splice(idx, 1);
this.objectNames.splice(idx, 1);
}
// if string, remove by name
else {
var id = $.inArray(idx, this.objectNames);
if (id != -1) {
this.objects.splice(id, 1);
this.objectNames.splice(id, 1);
}
}
};
$.jqplot.CanvasOverlay.prototype.getObject = function(idx) {
// check if integer, remove by index
if ($.type(idx) == 'number') {
return this.objects[idx];
}
// if string, remove by name
else {
var id = $.inArray(idx, this.objectNames);
if (id != -1) {
return this.objects[id];
}
}
};
// Set get as alias for getObject.
$.jqplot.CanvasOverlay.prototype.get = $.jqplot.CanvasOverlay.prototype.getObject;
$.jqplot.CanvasOverlay.prototype.clear = function(plot) {
this.canvas._ctx.clearRect(0,0,this.canvas.getWidth(), this.canvas.getHeight());
};
$.jqplot.CanvasOverlay.prototype.draw = function(plot) {
var obj,
objs = this.objects,
mr = this.markerRenderer,
start,
stop;
if (this.options.show) {
this.canvas._ctx.clearRect(0,0,this.canvas.getWidth(), this.canvas.getHeight());
for (var k=0; k<objs.length; k++) {
obj = objs[k];
var opts = $.extend(true, {}, obj.options);
if (obj.options.show) {
// style and shadow properties should be set before
// every draw of marker renderer.
mr.shadow = obj.options.shadow;
obj.tooltipWidthFactor = obj.options.lineWidth / obj.options.showTooltipPrecision;
switch (obj.type) {
case 'line':
// style and shadow properties should be set before
// every draw of marker renderer.
mr.style = 'line';
opts.closePath = false;
start = [plot.axes[obj.options.xaxis].series_u2p(obj.options.start[0]), plot.axes[obj.options.yaxis].series_u2p(obj.options.start[1])];
stop = [plot.axes[obj.options.xaxis].series_u2p(obj.options.stop[0]), plot.axes[obj.options.yaxis].series_u2p(obj.options.stop[1])];
obj.gridStart = start;
obj.gridStop = stop;
mr.draw(start, stop, this.canvas._ctx, opts);
break;
case 'horizontalLine':
// style and shadow properties should be set before
// every draw of marker renderer.
if (obj.options.y != null) {
mr.style = 'line';
opts.closePath = false;
var xaxis = plot.axes[obj.options.xaxis],
xstart,
xstop,
y = plot.axes[obj.options.yaxis].series_u2p(obj.options.y),
xminoff = obj.options.xminOffset || obj.options.xOffset,
xmaxoff = obj.options.xmaxOffset || obj.options.xOffset;
if (obj.options.xmin != null) {
xstart = xaxis.series_u2p(obj.options.xmin);
}
else if (xminoff != null) {
if ($.type(xminoff) == "number") {
xstart = xaxis.series_u2p(xaxis.min + xminoff);
}
else if ($.type(xminoff) == "string") {
xstart = xaxis.series_u2p(xaxis.min) + parseFloat(xminoff);
}
}
if (obj.options.xmax != null) {
xstop = xaxis.series_u2p(obj.options.xmax);
}
else if (xmaxoff != null) {
if ($.type(xmaxoff) == "number") {
xstop = xaxis.series_u2p(xaxis.max - xmaxoff);
}
else if ($.type(xmaxoff) == "string") {
xstop = xaxis.series_u2p(xaxis.max) - parseFloat(xmaxoff);
}
}
if (xstop != null && xstart != null) {
obj.gridStart = [xstart, y];
obj.gridStop = [xstop, y];
mr.draw([xstart, y], [xstop, y], this.canvas._ctx, opts);
}
}
break;
case 'dashedHorizontalLine':
var dashPat = obj.options.dashPattern;
var dashPatLen = 0;
for (var i=0; i<dashPat.length; i++) {
dashPatLen += dashPat[i];
}
// style and shadow properties should be set before
// every draw of marker renderer.
if (obj.options.y != null) {
mr.style = 'line';
opts.closePath = false;
var xaxis = plot.axes[obj.options.xaxis],
xstart,
xstop,
y = plot.axes[obj.options.yaxis].series_u2p(obj.options.y),
xminoff = obj.options.xminOffset || obj.options.xOffset,
xmaxoff = obj.options.xmaxOffset || obj.options.xOffset;
if (obj.options.xmin != null) {
xstart = xaxis.series_u2p(obj.options.xmin);
}
else if (xminoff != null) {
if ($.type(xminoff) == "number") {
xstart = xaxis.series_u2p(xaxis.min + xminoff);
}
else if ($.type(xminoff) == "string") {
xstart = xaxis.series_u2p(xaxis.min) + parseFloat(xminoff);
}
}
if (obj.options.xmax != null) {
xstop = xaxis.series_u2p(obj.options.xmax);
}
else if (xmaxoff != null) {
if ($.type(xmaxoff) == "number") {
xstop = xaxis.series_u2p(xaxis.max - xmaxoff);
}
else if ($.type(xmaxoff) == "string") {
xstop = xaxis.series_u2p(xaxis.max) - parseFloat(xmaxoff);
}
}
if (xstop != null && xstart != null) {
obj.gridStart = [xstart, y];
obj.gridStop = [xstop, y];
var numDash = Math.ceil((xstop - xstart)/dashPatLen);
var b=xstart, e;
for (var i=0; i<numDash; i++) {
for (var j=0; j<dashPat.length; j+=2) {
e = b+dashPat[j];
mr.draw([b, y], [e, y], this.canvas._ctx, opts);
b += dashPat[j];
if (j < dashPat.length-1) {
b += dashPat[j+1];
}
}
}
}
}
break;
case 'verticalLine':
// style and shadow properties should be set before
// every draw of marker renderer.
if (obj.options.x != null) {
mr.style = 'line';
opts.closePath = false;
var yaxis = plot.axes[obj.options.yaxis],
ystart,
ystop,
x = plot.axes[obj.options.xaxis].series_u2p(obj.options.x),
yminoff = obj.options.yminOffset || obj.options.yOffset,
ymaxoff = obj.options.ymaxOffset || obj.options.yOffset;
if (obj.options.ymin != null) {
ystart = yaxis.series_u2p(obj.options.ymin);
}
else if (yminoff != null) {
if ($.type(yminoff) == "number") {
ystart = yaxis.series_u2p(yaxis.min - yminoff);
}
else if ($.type(yminoff) == "string") {
ystart = yaxis.series_u2p(yaxis.min) - parseFloat(yminoff);
}
}
if (obj.options.ymax != null) {
ystop = yaxis.series_u2p(obj.options.ymax);
}
else if (ymaxoff != null) {
if ($.type(ymaxoff) == "number") {
ystop = yaxis.series_u2p(yaxis.max + ymaxoff);
}
else if ($.type(ymaxoff) == "string") {
ystop = yaxis.series_u2p(yaxis.max) + parseFloat(ymaxoff);
}
}
if (ystop != null && ystart != null) {
obj.gridStart = [x, ystart];
obj.gridStop = [x, ystop];
mr.draw([x, ystart], [x, ystop], this.canvas._ctx, opts);
}
}
break;
case 'dashedVerticalLine':
var dashPat = obj.options.dashPattern;
var dashPatLen = 0;
for (var i=0; i<dashPat.length; i++) {
dashPatLen += dashPat[i];
}
// style and shadow properties should be set before
// every draw of marker renderer.
if (obj.options.x != null) {
mr.style = 'line';
opts.closePath = false;
var yaxis = plot.axes[obj.options.yaxis],
ystart,
ystop,
x = plot.axes[obj.options.xaxis].series_u2p(obj.options.x),
yminoff = obj.options.yminOffset || obj.options.yOffset,
ymaxoff = obj.options.ymaxOffset || obj.options.yOffset;
if (obj.options.ymin != null) {
ystart = yaxis.series_u2p(obj.options.ymin);
}
else if (yminoff != null) {
if ($.type(yminoff) == "number") {
ystart = yaxis.series_u2p(yaxis.min - yminoff);
}
else if ($.type(yminoff) == "string") {
ystart = yaxis.series_u2p(yaxis.min) - parseFloat(yminoff);
}
}
if (obj.options.ymax != null) {
ystop = yaxis.series_u2p(obj.options.ymax);
}
else if (ymaxoff != null) {
if ($.type(ymaxoff) == "number") {
ystop = yaxis.series_u2p(yaxis.max + ymaxoff);
}
else if ($.type(ymaxoff) == "string") {
ystop = yaxis.series_u2p(yaxis.max) + parseFloat(ymaxoff);
}
}
if (ystop != null && ystart != null) {
obj.gridStart = [x, ystart];
obj.gridStop = [x, ystop];
var numDash = Math.ceil((ystart - ystop)/dashPatLen);
var firstDashAdjust = ((numDash * dashPatLen) - (ystart - ystop))/2.0;
var b=ystart, e, bs, es;
for (var i=0; i<numDash; i++) {
for (var j=0; j<dashPat.length; j+=2) {
e = b - dashPat[j];
if (e < ystop) {
e = ystop;
}
if (b < ystop) {
b = ystop;
}
// es = e;
// if (i == 0) {
// es += firstDashAdjust;
// }
mr.draw([x, b], [x, e], this.canvas._ctx, opts);
b -= dashPat[j];
if (j < dashPat.length-1) {
b -= dashPat[j+1];
}
}
}
}
}
break;
case 'rectangle':
// style and shadow properties should be set before
// every draw of marker renderer.
mr.style = 'line';
opts.closePath = true;
var xaxis = plot.axes[obj.options.xaxis],
xstart,
xstop,
y = plot.axes[obj.options.yaxis].series_u2p(obj.options.y),
xminoff = obj.options.xminOffset || obj.options.xOffset,
xmaxoff = obj.options.xmaxOffset || obj.options.xOffset;
if (obj.options.xmin != null) {
xstart = xaxis.series_u2p(obj.options.xmin);
}
else if (xminoff != null) {
if ($.type(xminoff) == "number") {
xstart = xaxis.series_u2p(xaxis.min + xminoff);
}
else if ($.type(xminoff) == "string") {
xstart = xaxis.series_u2p(xaxis.min) + parseFloat(xminoff);
}
}
if (obj.options.xmax != null) {
xstop = xaxis.series_u2p(obj.options.xmax);
}
else if (xmaxoff != null) {
if ($.type(xmaxoff) == "number") {
xstop = xaxis.series_u2p(xaxis.max - xmaxoff);
}
else if ($.type(xmaxoff) == "string") {
xstop = xaxis.series_u2p(xaxis.max) - parseFloat(xmaxoff);
}
}
var yaxis = plot.axes[obj.options.yaxis],
ystart,
ystop,
x = plot.axes[obj.options.xaxis].series_u2p(obj.options.x),
yminoff = obj.options.yminOffset || obj.options.yOffset,
ymaxoff = obj.options.ymaxOffset || obj.options.yOffset;
if (obj.options.ymin != null) {
ystart = yaxis.series_u2p(obj.options.ymin);
}
else if (yminoff != null) {
if ($.type(yminoff) == "number") {
ystart = yaxis.series_u2p(yaxis.min - yminoff);
}
else if ($.type(yminoff) == "string") {
ystart = yaxis.series_u2p(yaxis.min) - parseFloat(yminoff);
}
}
if (obj.options.ymax != null) {
ystop = yaxis.series_u2p(obj.options.ymax);
}
else if (ymaxoff != null) {
if ($.type(ymaxoff) == "number") {
ystop = yaxis.series_u2p(yaxis.max + ymaxoff);
}
else if ($.type(ymaxoff) == "string") {
ystop = yaxis.series_u2p(yaxis.max) + parseFloat(ymaxoff);
}
}
if (xstop != null && xstart != null && ystop != null && ystart != null) {
obj.gridStart = [xstart, ystart];
obj.gridStop = [xstop, ystop];
this.canvas._ctx.fillStyle = obj.options.color;
this.canvas._ctx.fillRect(xstart, ystart, xstop - xstart, ystop - ystart);
}
break;
default:
break;
}
}
}
}
};
// called within context of plot
// create a canvas which we can draw on.
// insert it before the eventCanvas, so eventCanvas will still capture events.
$.jqplot.CanvasOverlay.postPlotDraw = function() {
var co = this.plugins.canvasOverlay;
// Memory Leaks patch
if (co && co.highlightCanvas) {
co.highlightCanvas.resetCanvas();
co.highlightCanvas = null;
}
co.canvas = new $.jqplot.GenericCanvas();
this.eventCanvas._elem.before(co.canvas.createElement(this._gridPadding, 'jqplot-overlayCanvas-canvas', this._plotDimensions, this));
co.canvas.setContext();
if (!co.deferDraw) {
co.draw(this);
}
var elem = document.createElement('div');
co._tooltipElem = $(elem);
elem = null;
co._tooltipElem.addClass('jqplot-canvasOverlay-tooltip');
co._tooltipElem.css({position:'absolute', display:'none'});
this.eventCanvas._elem.before(co._tooltipElem);
this.eventCanvas._elem.bind('mouseleave', { elem: co._tooltipElem }, function (ev) { ev.data.elem.hide(); });
var co = null;
};
function showTooltip(plot, obj, gridpos, datapos) {
var co = plot.plugins.canvasOverlay;
var elem = co._tooltipElem;
var opts = obj.options, x, y;
elem.html($.jqplot.sprintf(opts.tooltipFormatString, datapos[0], datapos[1]));
switch (opts.tooltipLocation) {
case 'nw':
x = gridpos[0] + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset;
y = gridpos[1] + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true);
break;
case 'n':
x = gridpos[0] + plot._gridPadding.left - elem.outerWidth(true)/2;
y = gridpos[1] + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true);
break;
case 'ne':
x = gridpos[0] + plot._gridPadding.left + opts.tooltipOffset;
y = gridpos[1] + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true);
break;
case 'e':
x = gridpos[0] + plot._gridPadding.left + opts.tooltipOffset;
y = gridpos[1] + plot._gridPadding.top - elem.outerHeight(true)/2;
break;
case 'se':
x = gridpos[0] + plot._gridPadding.left + opts.tooltipOffset;
y = gridpos[1] + plot._gridPadding.top + opts.tooltipOffset;
break;
case 's':
x = gridpos[0] + plot._gridPadding.left - elem.outerWidth(true)/2;
y = gridpos[1] + plot._gridPadding.top + opts.tooltipOffset;
break;
case 'sw':
x = gridpos[0] + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset;
y = gridpos[1] + plot._gridPadding.top + opts.tooltipOffset;
break;
case 'w':
x = gridpos[0] + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset;
y = gridpos[1] + plot._gridPadding.top - elem.outerHeight(true)/2;
break;
default: // same as 'nw'
x = gridpos[0] + plot._gridPadding.left - elem.outerWidth(true) - opts.tooltipOffset;
y = gridpos[1] + plot._gridPadding.top - opts.tooltipOffset - elem.outerHeight(true);
break;
}
elem.css('left', x);
elem.css('top', y);
if (opts.fadeTooltip) {
// Fix for stacked up animations. Thnanks Trevor!
elem.stop(true,true).fadeIn(opts.tooltipFadeSpeed);
}
else {
elem.show();
}
elem = null;
}
function isNearLine(point, lstart, lstop, width) {
// r is point to test, p and q are end points.
var rx = point[0];
var ry = point[1];
var px = Math.round(lstop[0]);
var py = Math.round(lstop[1]);
var qx = Math.round(lstart[0]);
var qy = Math.round(lstart[1]);
var l = Math.sqrt(Math.pow(px-qx, 2) + Math.pow(py-qy, 2));
// scale error term by length of line.
var eps = width*l;
var res = Math.abs((qx-px) * (ry-py) - (qy-py) * (rx-px));
var ret = (res < eps) ? true : false;
return ret;
}
function isNearRectangle(point, lstart, lstop, width) {
// r is point to test, p and q are end points.
var rx = point[0];
var ry = point[1];
var px = Math.round(lstop[0]);
var py = Math.round(lstop[1]);
var qx = Math.round(lstart[0]);
var qy = Math.round(lstart[1]);
var temp;
if (px > qx) { temp = px; px = qx; qx = temp; }
if (py > qy) { temp = py; py = qy; qy = temp; }
var ret = (rx >= px && rx <= qx && ry >= py && ry <= qy);
return ret;
}
function handleMove(ev, gridpos, datapos, neighbor, plot) {
var co = plot.plugins.canvasOverlay;
var objs = co.objects;
var l = objs.length;
var obj, haveHighlight=false;
var elem;
for (var i=0; i<l; i++) {
obj = objs[i];
if (obj.options.showTooltip) {
var n;
if (obj.type === 'rectangle') {
n = isNearRectangle([gridpos.x, gridpos.y], obj.gridStart, obj.gridStop, obj.tooltipWidthFactor);
} else {
n = isNearLine([gridpos.x, gridpos.y], obj.gridStart, obj.gridStop, obj.tooltipWidthFactor);
}
datapos = [plot.axes[obj.options.xaxis].series_p2u(gridpos.x), plot.axes[obj.options.yaxis].series_p2u(gridpos.y)];
// cases:
// near line, no highlighting
// near line, highliting on this line
// near line, highlighting another line
// not near any line, highlighting
// not near any line, no highlighting
// near line, not currently highlighting
if (n && co.highlightObjectIndex == null) {
switch (obj.type) {
case 'line':
showTooltip(plot, obj, [gridpos.x, gridpos.y], datapos);
break;
case 'horizontalLine':
case 'dashedHorizontalLine':
showTooltip(plot, obj, [gridpos.x, obj.gridStart[1]], [datapos[0], obj.options.y]);
break;
case 'verticalLine':
case 'dashedVerticalLine':
showTooltip(plot, obj, [obj.gridStart[0], gridpos.y], [obj.options.x, datapos[1]]);
break;
case 'rectangle':
showTooltip(plot, obj, [obj.gridStart[0], gridpos.y], [obj.options.x, datapos[1]]);
break;
default:
break;
}
co.highlightObjectIndex = i;
haveHighlight = true;
break;
}
// near line, highlighting another line.
else if (n && co.highlightObjectIndex !== i) {
// turn off tooltip.
elem = co._tooltipElem;
if (obj.fadeTooltip) {
elem.fadeOut(obj.tooltipFadeSpeed);
}
else {
elem.hide();
}
// turn on right tooltip.
switch (obj.type) {
case 'line':
showTooltip(plot, obj, [gridpos.x, gridpos.y], datapos);
break;
case 'horizontalLine':
case 'dashedHorizontalLine':
showTooltip(plot, obj, [gridpos.x, obj.gridStart[1]], [datapos[0], obj.options.y]);
break;
case 'verticalLine':
case 'dashedVerticalLine':
showTooltip(plot, obj, [obj.gridStart[0], gridpos.y], [obj.options.x, datapos[1]]);
break;
case 'rectangle':
showTooltip(plot, obj, [obj.gridStart[0], gridpos.y], [obj.options.x, datapos[1]]);
break;
default:
break;
}
co.highlightObjectIndex = i;
haveHighlight = true;
break;
}
// near line, already highlighting this line, update
else if (n) {
switch (obj.type) {
case 'line':
showTooltip(plot, obj, [gridpos.x, gridpos.y], datapos);
break;
case 'horizontalLine':
case 'dashedHorizontalLine':
showTooltip(plot, obj, [gridpos.x, obj.gridStart[1]], [datapos[0], obj.options.y]);
break;
case 'verticalLine':
case 'dashedVerticalLine':
showTooltip(plot, obj, [obj.gridStart[0], gridpos.y], [obj.options.x, datapos[1]]);
break;
case 'rectangle':
showTooltip(plot, obj, [obj.gridStart[0], gridpos.y], [obj.options.x, datapos[1]]);
break;
default:
break;
}
haveHighlight = true;
break;
}
}
}
// check if we are highlighting and not near a line, turn it off.
if (!haveHighlight && co.highlightObjectIndex !== null) {
elem = co._tooltipElem;
obj = co.getObject(co.highlightObjectIndex);
if (obj.fadeTooltip) {
elem.fadeOut(obj.tooltipFadeSpeed);
}
else {
elem.hide();
}
co.highlightObjectIndex = null;
}
}
$.jqplot.postInitHooks.push($.jqplot.CanvasOverlay.postPlotInit);
$.jqplot.postDrawHooks.push($.jqplot.CanvasOverlay.postPlotDraw);
$.jqplot.eventListenerHooks.push(['jqplotMouseMove', handleMove]);
})(jQuery);