diff --git a/Gruntfile.js b/Gruntfile.js index fce2e17..dc85900 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -25,7 +25,7 @@ module.exports = function(grunt) { ' * \n' + ' * description: <%= pkg.description %>\n' + ' * version: <%= pkg.version %>\n' + - ' * author: <%= pkg.author %>\n' + + ' * authors: <%= pkg.authors %>\n' + ' * website: <%= pkg.website %>\n' + ' * \n' + ' * build on <%= grunt.template.today("yyyy-mm-dd") %>\n' + diff --git a/js/jquery.flot.tooltip.js b/js/jquery.flot.tooltip.js index c712742..af0e1ba 100644 --- a/js/jquery.flot.tooltip.js +++ b/js/jquery.flot.tooltip.js @@ -2,11 +2,11 @@ * jquery.flot.tooltip * * description: easy-to-use tooltips for Flot charts - * version: 0.7.1 - * author: Krzysztof Urbas @krzysu [myviews.pl] + * version: 0.8.0 + * authors: Krzysztof Urbas @krzysu [myviews.pl],Evan Steinkerchner @Roundaround * website: https://github.com/krzysu/flot.tooltip * - * build on 2014-06-22 + * build on 2014-08-01 * released under MIT License, 2012 */ // IE8 polyfill for Array.indexOf @@ -61,6 +61,10 @@ if (!Array.prototype.indexOf) { y: 20 }, defaultTheme: true, + lines: { + track: false, + threshold: 0.05 + }, // callbacks onHover: function(flotItem, $tooltipEl) {} @@ -124,28 +128,126 @@ if (!Array.prototype.indexOf) { } function plothover(event, pos, item) { - var $tip = that.getDomElement(); - if (item) { - var tipText; + // Simple distance formula. + var lineDistance = function (p1x, p1y, p2x, p2y) { + return Math.sqrt((p2x - p1x) * (p2x - p1x) + (p2y - p1y) * (p2y - p1y)); + }; + + // Here is some voodoo magic for determining the distance to a line form a given point {x, y}. + var dotLineLength = function (x, y, x0, y0, x1, y1, o) { + if (o && !(o = + function (x, y, x0, y0, x1, y1) { + if (typeof x0 !== 'undefined') return { x: x0, y: y }; + else if (typeof y0 !== 'undefined') return { x: x, y: y0 }; + + var left, + tg = -1 / ((y1 - y0) / (x1 - x0)); + + return { + x: left = (x1 * (x * tg - y + y0) + x0 * (x * -tg + y - y1)) / (tg * (x1 - x0) + y0 - y1), + y: tg * left - tg * x + y + }; + } (x, y, x0, y0, x1, y1), + o.x >= Math.min(x0, x1) && o.x <= Math.max(x0, x1) && o.y >= Math.min(y0, y1) && o.y <= Math.max(y0, y1)) + ) { + var l1 = lineDistance(x, y, x0, y0), l2 = lineDistance(x, y, x1, y1); + return l1 > l2 ? l2 : l1; + } else { + var a = y0 - y1, b = x1 - x0, c = x0 * y1 - y0 * x1; + return Math.abs(a * x + b * y + c) / Math.sqrt(a * a + b * b); + } + }; + + // Quick little function for showing the tooltip. + function showTooltip(item) { + var $tip = that.getDomElement(); // convert tooltip content template to real tipText - tipText = that.stringFormat(that.tooltipOptions.content, item); + var tipText = that.stringFormat(that.tooltipOptions.content, item); - $tip.html( tipText ); + $tip.html(tipText); that.updateTooltipPosition({ x: pos.pageX, y: pos.pageY }); $tip.css({ - left: that.tipPosition.x + that.tooltipOptions.shifts.x, - top: that.tipPosition.y + that.tooltipOptions.shifts.y - }) - .show(); + left: that.tipPosition.x + that.tooltipOptions.shifts.x, + top: that.tipPosition.y + that.tooltipOptions.shifts.y + }).show(); // run callback - if(typeof that.tooltipOptions.onHover === 'function') { + if (typeof that.tooltipOptions.onHover === 'function') { that.tooltipOptions.onHover(item, $tip); } } - else { - $tip.hide().html(''); + + // Quick little function for hiding the tooltip. + function hideTooltip() { + that.getDomElement().hide().html(''); + } + + if (item) { + showTooltip(item); + } else if (that.plotOptions.series.lines.show && that.tooltipOptions.lines.track === true) { + var closestTrace = { + distance: -1 + }; + + $.each(plot.getData(), function (i, series) { + var xBeforeIndex = 0, + xAfterIndex = -1; + + // Our search here assumes our data is sorted via the x-axis. + // TODO: Improve efficiency somehow - search smaller sets of data. + for (var j = 1; j < series.data.length; j++) { + if (series.data[j - 1][0] <= pos.x && series.data[j][0] >= pos.x) { + xBeforeIndex = j - 1; + xAfterIndex = j; + } + } + + if (xAfterIndex === -1) { + hideTooltip(); + return; + } + + var pointPrev = { x: series.data[xBeforeIndex][0], y: series.data[xBeforeIndex][1] }, + pointNext = { x: series.data[xAfterIndex][0], y: series.data[xAfterIndex][1] }; + + var distToLine = dotLineLength(pos.x, pos.y, pointPrev.x, pointPrev.y, pointNext.x, pointNext.y, false); + + if (distToLine < that.tooltipOptions.lines.threshold) { + + var closestIndex = lineDistance(pointPrev.x, pointPrev.y, pos.x, pos.y) < + lineDistance(pos.x, pos.y, pointNext.x, pointNext.y) ? xBeforeIndex : xAfterIndex; + + var pointSize = series.datapoints.pointsize; + + // Calculate the point on the line vertically closest to our cursor. + var pointOnLine = [ + pos.x, + pointPrev.y + ((pointNext.y - pointPrev.y) * ((pos.x - pointPrev.x) / (pointNext.x - pointPrev.x))) + ]; + + var item = { + datapoint: pointOnLine, + dataIndex: closestIndex, + series: series, + seriesIndex: i + }; + + if (closestTrace.distance === -1 || distToLine < closestTrace.distance) { + closestTrace = { + distance: distToLine, + item: item + }; + } + } + }); + + if (closestTrace.distance !== -1) + showTooltip(closestTrace.item); + else + hideTooltip(); + } else { + hideTooltip(); } } }; @@ -403,7 +505,7 @@ if (!Array.prototype.indexOf) { init: init, options: defaultOptions, name: 'tooltip', - version: '0.6.7' + version: '0.8.0' }); })(jQuery); diff --git a/js/jquery.flot.tooltip.min.js b/js/jquery.flot.tooltip.min.js index 0626fbf..b7377ef 100644 --- a/js/jquery.flot.tooltip.min.js +++ b/js/jquery.flot.tooltip.min.js @@ -2,11 +2,11 @@ * jquery.flot.tooltip * * description: easy-to-use tooltips for Flot charts - * version: 0.7.1 - * author: Krzysztof Urbas @krzysu [myviews.pl] + * version: 0.8.0 + * authors: Krzysztof Urbas @krzysu [myviews.pl],Evan Steinkerchner @Roundaround * website: https://github.com/krzysu/flot.tooltip * - * build on 2014-06-22 + * build on 2014-08-01 * released under MIT License, 2012 */ -Array.prototype.indexOf||(Array.prototype.indexOf=function(t,i){if(void 0===this||null===this)throw new TypeError('"this" is null or not defined');var e=this.length>>>0;for(i=+i||0,1/0===Math.abs(i)&&(i=0),0>i&&(i+=e,0>i&&(i=0));e>i;i++)if(this[i]===t)return i;return-1}),function(t){var i={tooltip:!1,tooltipOpts:{content:"%s | X: %x | Y: %y",xDateFormat:null,yDateFormat:null,monthNames:null,dayNames:null,shifts:{x:10,y:20},defaultTheme:!0,onHover:function(){}}},e=function(t){this.tipPosition={x:0,y:0},this.init(t)};e.prototype.init=function(i){function e(t){var i={};i.x=t.pageX,i.y=t.pageY,o.updateTooltipPosition(i)}function s(t,i,e){var s=o.getDomElement();if(e){var a;a=o.stringFormat(o.tooltipOptions.content,e),s.html(a),o.updateTooltipPosition({x:i.pageX,y:i.pageY}),s.css({left:o.tipPosition.x+o.tooltipOptions.shifts.x,top:o.tipPosition.y+o.tooltipOptions.shifts.y}).show(),"function"==typeof o.tooltipOptions.onHover&&o.tooltipOptions.onHover(e,s)}else s.hide().html("")}var o=this,a=t.plot.plugins.length;if(this.plotPlugins=[],a)for(var n=0;a>n;n++)this.plotPlugins.push(t.plot.plugins[n].name);i.hooks.bindEvents.push(function(i,a){o.plotOptions=i.getOptions(),o.plotOptions.tooltip!==!1&&void 0!==o.plotOptions.tooltip&&(o.tooltipOptions=o.plotOptions.tooltipOpts,o.getDomElement(),t(i.getPlaceholder()).bind("plothover",s),t(a).bind("mousemove",e))}),i.hooks.shutdown.push(function(i,o){t(i.getPlaceholder()).unbind("plothover",s),t(o).unbind("mousemove",e)})},e.prototype.getDomElement=function(){var i=t("#flotTip");return 0===i.length&&(i=t("
").attr("id","flotTip"),i.appendTo("body").hide().css({position:"absolute"}),this.tooltipOptions.defaultTheme&&i.css({background:"#fff","z-index":"1040",padding:"0.4em 0.6em","border-radius":"0.5em","font-size":"0.8em",border:"1px solid #111",display:"none","white-space":"nowrap"})),i},e.prototype.updateTooltipPosition=function(i){var e=t("#flotTip"),s=e.outerWidth()+this.tooltipOptions.shifts.x,o=e.outerHeight()+this.tooltipOptions.shifts.y;i.x-t(window).scrollLeft()>t(window).innerWidth()-s&&(i.x-=s),i.y-t(window).scrollTop()>t(window).innerHeight()-o&&(i.y-=o),this.tipPosition.x=i.x,this.tipPosition.y=i.y},e.prototype.stringFormat=function(t,i){var e,s,o,a=/%p\.{0,1}(\d{0,})/,n=/%s/,r=/%lx/,p=/%ly/,l=/%x\.{0,1}(\d{0,})/,d=/%y\.{0,1}(\d{0,})/,x="%x",h="%y",u="%ct";if(i.series.threshold!==void 0?(e=i.datapoint[0],s=i.datapoint[1],o=i.datapoint[2]):i.series.lines!==void 0&&i.series.lines.steps?(e=i.series.datapoints.points[2*i.dataIndex],s=i.series.datapoints.points[2*i.dataIndex+1],o=""):(e=i.series.data[i.dataIndex][0],s=i.series.data[i.dataIndex][1],o=i.series.data[i.dataIndex][2]),null===i.series.label&&i.series.originSeries&&(i.series.label=i.series.originSeries.label),"function"==typeof t&&(t=t(i.series.label,e,s,i)),i.series.percent!==void 0&&(t=this.adjustValPrecision(a,t,i.series.percent)),t=i.series.label!==void 0?t.replace(n,i.series.label):t.replace(n,""),t=this.hasAxisLabel("xaxis",i)?t.replace(r,i.series.xaxis.options.axisLabel):t.replace(r,""),t=this.hasAxisLabel("yaxis",i)?t.replace(p,i.series.yaxis.options.axisLabel):t.replace(p,""),this.isTimeMode("xaxis",i)&&this.isXDateFormat(i)&&(t=t.replace(l,this.timestampToDate(e,this.tooltipOptions.xDateFormat,i.series.xaxis.options))),this.isTimeMode("yaxis",i)&&this.isYDateFormat(i)&&(t=t.replace(d,this.timestampToDate(s,this.tooltipOptions.yDateFormat,i.series.yaxis.options))),"number"==typeof e&&(t=this.adjustValPrecision(l,t,e)),"number"==typeof s&&(t=this.adjustValPrecision(d,t,s)),i.series.xaxis.ticks!==void 0){var c;c=this.hasRotatedXAxisTicks(i)?"rotatedTicks":"ticks";var m=i.dataIndex+i.seriesIndex;if(i.series.xaxis[c].length>m&&!this.isTimeMode("xaxis",i)){var f=this.isCategoriesMode("xaxis",i)?i.series.xaxis[c][m].label:i.series.xaxis[c][m].v;f===e&&(t=t.replace(l,i.series.xaxis[c][m].label))}}if(i.series.yaxis.ticks!==void 0)for(var y in i.series.yaxis.ticks)if(i.series.yaxis.ticks.hasOwnProperty(y)){var v=this.isCategoriesMode("yaxis",i)?i.series.yaxis.ticks[y].label:i.series.yaxis.ticks[y].v;v===s&&(t=t.replace(d,i.series.yaxis.ticks[y].label))}return i.series.xaxis.tickFormatter!==void 0&&(t=t.replace(x,i.series.xaxis.tickFormatter(e,i.series.xaxis).replace(/\$/g,"$$"))),i.series.yaxis.tickFormatter!==void 0&&(t=t.replace(h,i.series.yaxis.tickFormatter(s,i.series.yaxis).replace(/\$/g,"$$"))),o&&(t=t.replace(u,o)),t},e.prototype.isTimeMode=function(t,i){return i.series[t].options.mode!==void 0&&"time"===i.series[t].options.mode},e.prototype.isXDateFormat=function(){return this.tooltipOptions.xDateFormat!==void 0&&null!==this.tooltipOptions.xDateFormat},e.prototype.isYDateFormat=function(){return this.tooltipOptions.yDateFormat!==void 0&&null!==this.tooltipOptions.yDateFormat},e.prototype.isCategoriesMode=function(t,i){return i.series[t].options.mode!==void 0&&"categories"===i.series[t].options.mode},e.prototype.timestampToDate=function(i,e,s){var o=t.plot.dateGenerator(i,s);return t.plot.formatDate(o,e,this.tooltipOptions.monthNames,this.tooltipOptions.dayNames)},e.prototype.adjustValPrecision=function(t,i,e){var s,o=i.match(t);return null!==o&&""!==RegExp.$1&&(s=RegExp.$1,e=e.toFixed(s),i=i.replace(t,e)),i},e.prototype.hasAxisLabel=function(t,i){return-1!==this.plotPlugins.indexOf("axisLabels")&&i.series[t].options.axisLabel!==void 0&&i.series[t].options.axisLabel.length>0},e.prototype.hasRotatedXAxisTicks=function(i){return 1===t.grep(t.plot.plugins,function(t){return"tickRotor"===t.name}).length&&i.series.xaxis.rotatedTicks!==void 0};var s=function(t){new e(t)};t.plot.plugins.push({init:s,options:i,name:"tooltip",version:"0.6.7"})}(jQuery); \ No newline at end of file +Array.prototype.indexOf||(Array.prototype.indexOf=function(a,b){if(void 0===this||null===this)throw new TypeError('"this" is null or not defined');var c=this.length>>>0;for(b=+b||0,1/0===Math.abs(b)&&(b=0),0>b&&(b+=c,0>b&&(b=0));c>b;b++)if(this[b]===a)return b;return-1}),function(a){var b={tooltip:!1,tooltipOpts:{content:"%s | X: %x | Y: %y",xDateFormat:null,yDateFormat:null,monthNames:null,dayNames:null,shifts:{x:10,y:20},defaultTheme:!0,lines:{track:!1,threshold:.05},onHover:function(){}}},c=function(a){this.tipPosition={x:0,y:0},this.init(a)};c.prototype.init=function(b){function c(a){var b={};b.x=a.pageX,b.y=a.pageY,e.updateTooltipPosition(b)}function d(c,d,f){function g(a){var b=e.getDomElement(),c=e.stringFormat(e.tooltipOptions.content,a);b.html(c),e.updateTooltipPosition({x:d.pageX,y:d.pageY}),b.css({left:e.tipPosition.x+e.tooltipOptions.shifts.x,top:e.tipPosition.y+e.tooltipOptions.shifts.y}).show(),"function"==typeof e.tooltipOptions.onHover&&e.tooltipOptions.onHover(a,b)}function h(){e.getDomElement().hide().html("")}var i=function(a,b,c,d){return Math.sqrt((c-a)*(c-a)+(d-b)*(d-b))},j=function(a,b,c,d,e,f,g){if(!g||(g=function(a,b,c,d,e,f){if("undefined"!=typeof c)return{x:c,y:b};if("undefined"!=typeof d)return{x:a,y:d};var g,h=-1/((f-d)/(e-c));return{x:g=(e*(a*h-b+d)+c*(a*-h+b-f))/(h*(e-c)+d-f),y:h*g-h*a+b}}(a,b,c,d,e,f),g.x>=Math.min(c,e)&&g.x<=Math.max(c,e)&&g.y>=Math.min(d,f)&&g.y<=Math.max(d,f))){var h=d-f,j=e-c,k=c*f-d*e;return Math.abs(h*a+j*b+k)/Math.sqrt(h*h+j*j)}var l=i(a,b,c,d),m=i(a,b,e,f);return l>m?m:l};if(f)g(f);else if(e.plotOptions.series.lines.show&&e.tooltipOptions.lines.track===!0){var k={distance:-1};a.each(b.getData(),function(a,b){for(var c=0,f=-1,g=1;g=d.x&&(c=g-1,f=g);if(-1===f)return void h();var l={x:b.data[c][0],y:b.data[c][1]},m={x:b.data[f][0],y:b.data[f][1]},n=j(d.x,d.y,l.x,l.y,m.x,m.y,!1);if(ng;g++)this.plotPlugins.push(a.plot.plugins[g].name);b.hooks.bindEvents.push(function(b,f){if(e.plotOptions=b.getOptions(),e.plotOptions.tooltip!==!1&&"undefined"!=typeof e.plotOptions.tooltip){e.tooltipOptions=e.plotOptions.tooltipOpts;{e.getDomElement()}a(b.getPlaceholder()).bind("plothover",d),a(f).bind("mousemove",c)}}),b.hooks.shutdown.push(function(b,e){a(b.getPlaceholder()).unbind("plothover",d),a(e).unbind("mousemove",c)})},c.prototype.getDomElement=function(){var b=a("#flotTip");return 0===b.length&&(b=a("
").attr("id","flotTip"),b.appendTo("body").hide().css({position:"absolute"}),this.tooltipOptions.defaultTheme&&b.css({background:"#fff","z-index":"1040",padding:"0.4em 0.6em","border-radius":"0.5em","font-size":"0.8em",border:"1px solid #111",display:"none","white-space":"nowrap"})),b},c.prototype.updateTooltipPosition=function(b){var c=a("#flotTip"),d=c.outerWidth()+this.tooltipOptions.shifts.x,e=c.outerHeight()+this.tooltipOptions.shifts.y;b.x-a(window).scrollLeft()>a(window).innerWidth()-d&&(b.x-=d),b.y-a(window).scrollTop()>a(window).innerHeight()-e&&(b.y-=e),this.tipPosition.x=b.x,this.tipPosition.y=b.y},c.prototype.stringFormat=function(a,b){var c,d,e,f=/%p\.{0,1}(\d{0,})/,g=/%s/,h=/%lx/,i=/%ly/,j=/%x\.{0,1}(\d{0,})/,k=/%y\.{0,1}(\d{0,})/,l="%x",m="%y",n="%ct";if("undefined"!=typeof b.series.threshold?(c=b.datapoint[0],d=b.datapoint[1],e=b.datapoint[2]):"undefined"!=typeof b.series.lines&&b.series.lines.steps?(c=b.series.datapoints.points[2*b.dataIndex],d=b.series.datapoints.points[2*b.dataIndex+1],e=""):(c=b.series.data[b.dataIndex][0],d=b.series.data[b.dataIndex][1],e=b.series.data[b.dataIndex][2]),null===b.series.label&&b.series.originSeries&&(b.series.label=b.series.originSeries.label),"function"==typeof a&&(a=a(b.series.label,c,d,b)),"undefined"!=typeof b.series.percent&&(a=this.adjustValPrecision(f,a,b.series.percent)),a="undefined"!=typeof b.series.label?a.replace(g,b.series.label):a.replace(g,""),a=this.hasAxisLabel("xaxis",b)?a.replace(h,b.series.xaxis.options.axisLabel):a.replace(h,""),a=this.hasAxisLabel("yaxis",b)?a.replace(i,b.series.yaxis.options.axisLabel):a.replace(i,""),this.isTimeMode("xaxis",b)&&this.isXDateFormat(b)&&(a=a.replace(j,this.timestampToDate(c,this.tooltipOptions.xDateFormat,b.series.xaxis.options))),this.isTimeMode("yaxis",b)&&this.isYDateFormat(b)&&(a=a.replace(k,this.timestampToDate(d,this.tooltipOptions.yDateFormat,b.series.yaxis.options))),"number"==typeof c&&(a=this.adjustValPrecision(j,a,c)),"number"==typeof d&&(a=this.adjustValPrecision(k,a,d)),"undefined"!=typeof b.series.xaxis.ticks){var o;o=this.hasRotatedXAxisTicks(b)?"rotatedTicks":"ticks";var p=b.dataIndex+b.seriesIndex;if(b.series.xaxis[o].length>p&&!this.isTimeMode("xaxis",b)){var q=this.isCategoriesMode("xaxis",b)?b.series.xaxis[o][p].label:b.series.xaxis[o][p].v;q===c&&(a=a.replace(j,b.series.xaxis[o][p].label))}}if("undefined"!=typeof b.series.yaxis.ticks)for(var r in b.series.yaxis.ticks)if(b.series.yaxis.ticks.hasOwnProperty(r)){var s=this.isCategoriesMode("yaxis",b)?b.series.yaxis.ticks[r].label:b.series.yaxis.ticks[r].v;s===d&&(a=a.replace(k,b.series.yaxis.ticks[r].label))}return"undefined"!=typeof b.series.xaxis.tickFormatter&&(a=a.replace(l,b.series.xaxis.tickFormatter(c,b.series.xaxis).replace(/\$/g,"$$"))),"undefined"!=typeof b.series.yaxis.tickFormatter&&(a=a.replace(m,b.series.yaxis.tickFormatter(d,b.series.yaxis).replace(/\$/g,"$$"))),e&&(a=a.replace(n,e)),a},c.prototype.isTimeMode=function(a,b){return"undefined"!=typeof b.series[a].options.mode&&"time"===b.series[a].options.mode},c.prototype.isXDateFormat=function(){return"undefined"!=typeof this.tooltipOptions.xDateFormat&&null!==this.tooltipOptions.xDateFormat},c.prototype.isYDateFormat=function(){return"undefined"!=typeof this.tooltipOptions.yDateFormat&&null!==this.tooltipOptions.yDateFormat},c.prototype.isCategoriesMode=function(a,b){return"undefined"!=typeof b.series[a].options.mode&&"categories"===b.series[a].options.mode},c.prototype.timestampToDate=function(b,c,d){var e=a.plot.dateGenerator(b,d);return a.plot.formatDate(e,c,this.tooltipOptions.monthNames,this.tooltipOptions.dayNames)},c.prototype.adjustValPrecision=function(a,b,c){var d,e=b.match(a);return null!==e&&""!==RegExp.$1&&(d=RegExp.$1,c=c.toFixed(d),b=b.replace(a,c)),b},c.prototype.hasAxisLabel=function(a,b){return-1!==this.plotPlugins.indexOf("axisLabels")&&"undefined"!=typeof b.series[a].options.axisLabel&&b.series[a].options.axisLabel.length>0},c.prototype.hasRotatedXAxisTicks=function(b){return 1===a.grep(a.plot.plugins,function(a){return"tickRotor"===a.name}).length&&"undefined"!=typeof b.series.xaxis.rotatedTicks};var d=function(a){new c(a)};a.plot.plugins.push({init:d,options:b,name:"tooltip",version:"0.8.0"})}(jQuery); \ No newline at end of file diff --git a/js/jquery.flot.tooltip.source.js b/js/jquery.flot.tooltip.source.js index 7bf604e..04c9fe6 100644 --- a/js/jquery.flot.tooltip.source.js +++ b/js/jquery.flot.tooltip.source.js @@ -50,6 +50,10 @@ if (!Array.prototype.indexOf) { y: 20 }, defaultTheme: true, + lines: { + track: false, + threshold: 0.05 + }, // callbacks onHover: function(flotItem, $tooltipEl) {} @@ -113,28 +117,126 @@ if (!Array.prototype.indexOf) { } function plothover(event, pos, item) { - var $tip = that.getDomElement(); - if (item) { - var tipText; + // Simple distance formula. + var lineDistance = function (p1x, p1y, p2x, p2y) { + return Math.sqrt((p2x - p1x) * (p2x - p1x) + (p2y - p1y) * (p2y - p1y)); + }; + + // Here is some voodoo magic for determining the distance to a line form a given point {x, y}. + var dotLineLength = function (x, y, x0, y0, x1, y1, o) { + if (o && !(o = + function (x, y, x0, y0, x1, y1) { + if (typeof x0 !== 'undefined') return { x: x0, y: y }; + else if (typeof y0 !== 'undefined') return { x: x, y: y0 }; + + var left, + tg = -1 / ((y1 - y0) / (x1 - x0)); + + return { + x: left = (x1 * (x * tg - y + y0) + x0 * (x * -tg + y - y1)) / (tg * (x1 - x0) + y0 - y1), + y: tg * left - tg * x + y + }; + } (x, y, x0, y0, x1, y1), + o.x >= Math.min(x0, x1) && o.x <= Math.max(x0, x1) && o.y >= Math.min(y0, y1) && o.y <= Math.max(y0, y1)) + ) { + var l1 = lineDistance(x, y, x0, y0), l2 = lineDistance(x, y, x1, y1); + return l1 > l2 ? l2 : l1; + } else { + var a = y0 - y1, b = x1 - x0, c = x0 * y1 - y0 * x1; + return Math.abs(a * x + b * y + c) / Math.sqrt(a * a + b * b); + } + }; + + // Quick little function for showing the tooltip. + function showTooltip(item) { + var $tip = that.getDomElement(); // convert tooltip content template to real tipText - tipText = that.stringFormat(that.tooltipOptions.content, item); + var tipText = that.stringFormat(that.tooltipOptions.content, item); - $tip.html( tipText ); + $tip.html(tipText); that.updateTooltipPosition({ x: pos.pageX, y: pos.pageY }); $tip.css({ - left: that.tipPosition.x + that.tooltipOptions.shifts.x, - top: that.tipPosition.y + that.tooltipOptions.shifts.y - }) - .show(); + left: that.tipPosition.x + that.tooltipOptions.shifts.x, + top: that.tipPosition.y + that.tooltipOptions.shifts.y + }).show(); // run callback - if(typeof that.tooltipOptions.onHover === 'function') { + if (typeof that.tooltipOptions.onHover === 'function') { that.tooltipOptions.onHover(item, $tip); } } - else { - $tip.hide().html(''); + + // Quick little function for hiding the tooltip. + function hideTooltip() { + that.getDomElement().hide().html(''); + } + + if (item) { + showTooltip(item); + } else if (that.plotOptions.series.lines.show && that.tooltipOptions.lines.track === true) { + var closestTrace = { + distance: -1 + }; + + $.each(plot.getData(), function (i, series) { + var xBeforeIndex = 0, + xAfterIndex = -1; + + // Our search here assumes our data is sorted via the x-axis. + // TODO: Improve efficiency somehow - search smaller sets of data. + for (var j = 1; j < series.data.length; j++) { + if (series.data[j - 1][0] <= pos.x && series.data[j][0] >= pos.x) { + xBeforeIndex = j - 1; + xAfterIndex = j; + } + } + + if (xAfterIndex === -1) { + hideTooltip(); + return; + } + + var pointPrev = { x: series.data[xBeforeIndex][0], y: series.data[xBeforeIndex][1] }, + pointNext = { x: series.data[xAfterIndex][0], y: series.data[xAfterIndex][1] }; + + var distToLine = dotLineLength(pos.x, pos.y, pointPrev.x, pointPrev.y, pointNext.x, pointNext.y, false); + + if (distToLine < that.tooltipOptions.lines.threshold) { + + var closestIndex = lineDistance(pointPrev.x, pointPrev.y, pos.x, pos.y) < + lineDistance(pos.x, pos.y, pointNext.x, pointNext.y) ? xBeforeIndex : xAfterIndex; + + var pointSize = series.datapoints.pointsize; + + // Calculate the point on the line vertically closest to our cursor. + var pointOnLine = [ + pos.x, + pointPrev.y + ((pointNext.y - pointPrev.y) * ((pos.x - pointPrev.x) / (pointNext.x - pointPrev.x))) + ]; + + var item = { + datapoint: pointOnLine, + dataIndex: closestIndex, + series: series, + seriesIndex: i + }; + + if (closestTrace.distance === -1 || distToLine < closestTrace.distance) { + closestTrace = { + distance: distToLine, + item: item + }; + } + } + }); + + if (closestTrace.distance !== -1) + showTooltip(closestTrace.item); + else + hideTooltip(); + } else { + hideTooltip(); } } }; @@ -392,7 +494,7 @@ if (!Array.prototype.indexOf) { init: init, options: defaultOptions, name: 'tooltip', - version: '0.6.7' + version: '0.8.0' }); })(jQuery); diff --git a/package.json b/package.json index 9e409c0..33d2c07 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "jquery.flot.tooltip", - "version": "0.7.1", + "version": "0.8.0", "description": "easy-to-use tooltips for Flot charts", "website": "https://github.com/krzysu/flot.tooltip", "directories": { @@ -29,7 +29,7 @@ "js", "grunt" ], - "author": "Krzysztof Urbas @krzysu [myviews.pl]", + "authors": ["Krzysztof Urbas @krzysu [myviews.pl]", "Evan Steinkerchner @Roundaround"], "license": "MIT", "readmeFilename": "README.md" }