text: /*
  
   * jquery.flot.tooltip
  
   * 
  
   * description: easy-to-use tooltips for Flot charts
  
   * version: 0.9.0
  
   * authors: Krzysztof Urbas @krzysu [myviews.pl],Evan Steinkerchner @Roundaround
  
   * website: https://github.com/krzysu/flot.tooltip
  
   * 
  
   * build on 2016-07-26
  
   * released under MIT License, 2012
  
  */ 
  
  (function ($) {
  
      // plugin options, default values
  
      var defaultOptions = {
  
          tooltip: {
  
              show: false,
  
              cssClass: "flotTip",
  
              content: "%s | X: %x | Y: %y",
  
              // allowed templates are:
  
              // %s -> series label,
  
              // %c -> series color,
  
              // %lx -> x axis label (requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels),
  
              // %ly -> y axis label (requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels),
  
              // %x -> X value,
  
              // %y -> Y value,
  
              // %x.2 -> precision of X value,
  
              // %p -> percent
  
              // %n -> value (not percent) of pie chart
  
              xDateFormat: null,
  
              yDateFormat: null,
  
              monthNames: null,
  
              dayNames: null,
  
              shifts: {
  
                  x: 10,
  
                  y: 20
  
              },
  
              defaultTheme: true,
  
              snap: true,
  
              lines: false,
  
              clickTips: false,
  
  
  
              // callbacks
  
              onHover: function (flotItem, $tooltipEl) {},
  
  
  
              $compat: false
  
          }
  
      };
  
  
  
      // dummy default options object for legacy code (<0.8.5) - is deleted later
  
      defaultOptions.tooltipOpts = defaultOptions.tooltip;
  
  
  
      // object
  
      var FlotTooltip = function (plot) {
  
          // variables
  
          this.tipPosition = {x: 0, y: 0};
  
  
  
          this.init(plot);
  
      };
  
  
  
      // main plugin function
  
      FlotTooltip.prototype.init = function (plot) {
  
          var that = this;
  
  
  
          // detect other flot plugins
  
          var plotPluginsLength = $.plot.plugins.length;
  
          this.plotPlugins = [];
  
  
  
          if (plotPluginsLength) {
  
              for (var p = 0; p < plotPluginsLength; p++) {
  
                  this.plotPlugins.push($.plot.plugins[p].name);
  
              }
  
          }
  
  
  
          plot.hooks.bindEvents.push(function (plot, eventHolder) {
  
  
  
              // get plot options
  
              that.plotOptions = plot.getOptions();
  
  
  
              // for legacy (<0.8.5) implementations
  
              if (typeof(that.plotOptions.tooltip) === 'boolean') {
  
                  that.plotOptions.tooltipOpts.show = that.plotOptions.tooltip;
  
                  that.plotOptions.tooltip = that.plotOptions.tooltipOpts;
  
                  delete that.plotOptions.tooltipOpts;
  
              }
  
  
  
              // if not enabled return
  
              if (that.plotOptions.tooltip.show === false || typeof that.plotOptions.tooltip.show === 'undefined') return;
  
  
  
              // shortcut to access tooltip options
  
              that.tooltipOptions = that.plotOptions.tooltip;
  
  
  
              if (that.tooltipOptions.$compat) {
  
                  that.wfunc = 'width';
  
                  that.hfunc = 'height';
  
              } else {
  
                  that.wfunc = 'innerWidth';
  
                  that.hfunc = 'innerHeight';
  
              }
  
  
  
              // create tooltip DOM element
  
              var $tip = that.getDomElement();
  
  
  
              // bind event
  
              $( plot.getPlaceholder() ).bind("plothover", plothover);
  
              if (that.tooltipOptions.clickTips) {
  
                  $( plot.getPlaceholder() ).bind("plotclick", plotclick);
  
              }
  
              that.clickmode = false;
  
  
  
              $(eventHolder).bind('mousemove', mouseMove);
  
          });
  
  
  
          plot.hooks.shutdown.push(function (plot, eventHolder){
  
              $(plot.getPlaceholder()).unbind("plothover", plothover);
  
              $(plot.getPlaceholder()).unbind("plotclick", plotclick);
  
              plot.removeTooltip();
  
              $(eventHolder).unbind("mousemove", mouseMove);
  
          });
  
  
  
          function mouseMove(e){
  
              var pos = {};
  
              pos.x = e.pageX;
  
              pos.y = e.pageY;
  
              plot.setTooltipPosition(pos);
  
          }
  
  
  
          /**
  
           *  open the tooltip (if not already open) and freeze it on the current position till the next click
  
           */
  
          function plotclick(event, pos, item) {
  
              if (! that.clickmode) {
  
                  // it is the click activating the clicktip
  
                  plothover(event, pos, item);
  
                  if (that.getDomElement().is(":visible")) {
  
                      $(plot.getPlaceholder()).unbind("plothover", plothover);
  
                      that.clickmode = true;
  
                  }
  
              } else {
  
                  // it is the click deactivating the clicktip
  
                  $( plot.getPlaceholder() ).bind("plothover", plothover);
  
                  plot.hideTooltip();
  
                  that.clickmode = false;
  
              }
  
          }
  
  
  
          function plothover(event, pos, item) {
  
              // 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);
  
                  }
  
              };
  
  
  
              if (item) {
  
                  plot.showTooltip(item, that.tooltipOptions.snap ? item : pos);
  
              } else if (that.plotOptions.series.lines.show && that.tooltipOptions.lines === true) {
  
                  var maxDistance = that.plotOptions.grid.mouseActiveRadius;
  
  
  
                  var closestTrace = {
  
                      distance: maxDistance + 1
  
                  };
  
  
  
                  var ttPos = pos;
  
  
  
                  $.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) {
  
                          plot.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(series.xaxis.p2c(pos.x), series.yaxis.p2c(pos.y), series.xaxis.p2c(pointPrev.x),
  
                          series.yaxis.p2c(pointPrev.y), series.xaxis.p2c(pointNext.x), series.yaxis.p2c(pointNext.y), false);
  
  
  
                      if (distToLine < closestTrace.distance) {
  
  
  
                          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
  
                          };
  
  
  
                          closestTrace = {
  
                              distance: distToLine,
  
                              item: item
  
                          };
  
  
  
                          if (that.tooltipOptions.snap) {
  
                              ttPos = {
  
                                  pageX: series.xaxis.p2c(pointOnLine[0]),
  
                                  pageY: series.yaxis.p2c(pointOnLine[1])
  
                              };
  
                          }
  
                      }
  
                  });
  
  
  
                  if (closestTrace.distance < maxDistance + 1)
  
                      plot.showTooltip(closestTrace.item, ttPos);
  
                  else
  
                      plot.hideTooltip();
  
              } else {
  
                  plot.hideTooltip();
  
              }
  
          }
  
  
  
          // Quick little function for setting the tooltip position.
  
          plot.setTooltipPosition = function (pos) {
  
              var $tip = that.getDomElement();
  
  
  
              var totalTipWidth = $tip.outerWidth() + that.tooltipOptions.shifts.x;
  
              var totalTipHeight = $tip.outerHeight() + that.tooltipOptions.shifts.y;
  
              if ((pos.x - $(window).scrollLeft()) > ($(window)[that.wfunc]() - totalTipWidth)) {
  
                  pos.x -= totalTipWidth;
  
                  pos.x = Math.max(pos.x, 0);
  
              }
  
              if ((pos.y - $(window).scrollTop()) > ($(window)[that.hfunc]() - totalTipHeight)) {
  
                  pos.y -= totalTipHeight;
  
              }
  
  
  
  	    /*
  
  	       The section applies the new positioning ONLY if pos.x and pos.y
  
  	       are numbers. If they are undefined or not a number, use the last
  
  	       known numerical position. This hack fixes a bug that kept pie
  
  	       charts from keeping their tooltip positioning.
  
  	     */
  
  
  
              if (isNaN(pos.x)) {
  
  		that.tipPosition.x = that.tipPosition.xPrev;
  
  	    }
  
  	    else {
  
  		that.tipPosition.x = pos.x;
  
  		that.tipPosition.xPrev = pos.x;
  
  	    }
  
  	    if (isNaN(pos.y)) {
  
  		that.tipPosition.y = that.tipPosition.yPrev;
  
  	    }
  
  	    else {
  
  		that.tipPosition.y = pos.y;
  
  		that.tipPosition.yPrev = pos.y;
  
  	    }
  
  
  
          };
  
  
  
          // Quick little function for showing the tooltip.
  
          plot.showTooltip = function (target, position, targetPosition) {
  
              var $tip = that.getDomElement();
  
  
  
              // convert tooltip content template to real tipText
  
              var tipText = that.stringFormat(that.tooltipOptions.content, target);
  
              if (tipText === '')
  
                  return;
  
  
  
              $tip.html(tipText);
  
              plot.setTooltipPosition({ x: that.tipPosition.x, y: that.tipPosition.y });
  
              $tip.css({
  
                  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') {
  
                  that.tooltipOptions.onHover(target, $tip);
  
              }
  
          };
  
  
  
          // Quick little function for hiding the tooltip.
  
          plot.hideTooltip = function () {
  
              that.getDomElement().hide().html('');
  
          };
  
  
  
          plot.removeTooltip = function() {
  
              that.getDomElement().remove();
  
          };
  
      };
  
  
  
      /**
  
       * get or create tooltip DOM element
  
       * @return jQuery object
  
       */
  
      FlotTooltip.prototype.getDomElement = function () {
  
          var $tip = $('<div>');
  
          if (this.tooltipOptions && this.tooltipOptions.cssClass) {
  
              $tip = $('.' + this.tooltipOptions.cssClass);
  
  
  
              if( $tip.length === 0 ){
  
                  $tip = $('<div />').addClass(this.tooltipOptions.cssClass);
  
                  $tip.appendTo('body').hide().css({position: 'absolute'});
  
  
  
                  if(this.tooltipOptions.defaultTheme) {
  
                      $tip.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'
  
                      });
  
                  }
  
              }
  
          }
  
  
  
          return $tip;
  
      };
  
  
  
      /**
  
       * core function, create tooltip content
  
       * @param  {string} content - template with tooltip content
  
       * @param  {object} item - Flot item
  
       * @return {string} real tooltip content for current item
  
       */
  
      FlotTooltip.prototype.stringFormat = function (content, item) {
  
          var percentPattern = /%p\.{0,1}(\d{0,})/;
  
          var seriesPattern = /%s/;
  
          var colorPattern = /%c/;
  
          var xLabelPattern = /%lx/; // requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels, will be ignored if plugin isn't loaded
  
          var yLabelPattern = /%ly/; // requires flot-axislabels plugin https://github.com/markrcote/flot-axislabels, will be ignored if plugin isn't loaded
  
          var xPattern = /%x\.{0,1}(\d{0,})/;
  
          var yPattern = /%y\.{0,1}(\d{0,})/;
  
          var xPatternWithoutPrecision = "%x";
  
          var yPatternWithoutPrecision = "%y";
  
          var customTextPattern = "%ct";
  
  	var nPiePattern = "%n";
  
  
  
          var x, y, customText, p, n;
  
  
  
          // for threshold plugin we need to read data from different place
  
          if (typeof item.series.threshold !== "undefined") {
  
              x = item.datapoint[0];
  
              y = item.datapoint[1];
  
              customText = item.datapoint[2];
  
  	}
  
  
  
  	// for CurvedLines plugin we need to read data from different place
  
  	    else if (typeof item.series.curvedLines !== "undefined") {
  
  		x = item.datapoint[0];
  
  		y = item.datapoint[1];
  
  	    }
  
  
  
          else if (typeof item.series.lines !== "undefined" && item.series.lines.steps) {
  
              x = item.series.datapoints.points[item.dataIndex * 2];
  
              y = item.series.datapoints.points[item.dataIndex * 2 + 1];
  
              // TODO: where to find custom text in this variant?
  
              customText = "";
  
          } else {
  
              x = item.series.data[item.dataIndex][0];
  
              y = item.series.data[item.dataIndex][1];
  
              customText = item.series.data[item.dataIndex][2];
  
          }
  
  
  
          // I think this is only in case of threshold plugin
  
          if (item.series.label === null && item.series.originSeries) {
  
              item.series.label = item.series.originSeries.label;
  
          }
  
  
  
          // if it is a function callback get the content string
  
          if (typeof(content) === 'function') {
  
              content = content(item.series.label, x, y, item);
  
          }
  
  
  
          // the case where the passed content is equal to false
  
          if (typeof(content) === 'boolean' && !content) {
  
              return '';
  
          }
  
  
  
          /* replacement of %ct and other multi-character templates must
  
             precede the replacement of single-character templates
  
             to avoid conflict between '%c' and '%ct'  and similar substrings
  
          */
  
          if (customText) {
  
              content = content.replace(customTextPattern, customText);
  
          }
  
  
  
          // percent match for pie charts and stacked percent
  
          if (typeof (item.series.percent) !== 'undefined') {
  
              p = item.series.percent;
  
          } else if (typeof (item.series.percents) !== 'undefined') {
  
              p = item.series.percents[item.dataIndex];
  
          }
  
          if (typeof p === 'number') {
  
              content = this.adjustValPrecision(percentPattern, content, p);
  
          }
  
  
  
          // replace %n with number of items represented by slice in pie charts
  
          if (item.series.hasOwnProperty('pie')) {
  
              if (typeof item.series.data[0][1] !== 'undefined') {
  
                  n = item.series.data[0][1];
  
              }
  
          }
  
          if (typeof n === 'number') {
  
              content = content.replace(nPiePattern, n);
  
          }
  
  
  
          // series match
  
          if (typeof(item.series.label) !== 'undefined') {
  
              content = content.replace(seriesPattern, item.series.label);
  
          } else {
  
              //remove %s if label is undefined
  
              content = content.replace(seriesPattern, "");
  
          }
  
  
  
          // color match
  
          if (typeof(item.series.color) !== 'undefined') {
  
              content = content.replace(colorPattern, item.series.color);
  
          } else {
  
              //remove %s if color is undefined
  
              content = content.replace(colorPattern, "");
  
          }
  
  
  
          // x axis label match
  
          if (this.hasAxisLabel('xaxis', item)) {
  
              content = content.replace(xLabelPattern, item.series.xaxis.options.axisLabel);
  
          } else {
  
              //remove %lx if axis label is undefined or axislabels plugin not present
  
              content = content.replace(xLabelPattern, "");
  
          }
  
  
  
          // y axis label match
  
          if (this.hasAxisLabel('yaxis', item)) {
  
              content = content.replace(yLabelPattern, item.series.yaxis.options.axisLabel);
  
          } else {
  
              //remove %ly if axis label is undefined or axislabels plugin not present
  
              content = content.replace(yLabelPattern, "");
  
          }
  
  
  
          // time mode axes with custom dateFormat
  
          if (this.isTimeMode('xaxis', item) && this.isXDateFormat(item)) {
  
              content = content.replace(xPattern, this.timestampToDate(x, this.tooltipOptions.xDateFormat, item.series.xaxis.options));
  
          }
  
          if (this.isTimeMode('yaxis', item) && this.isYDateFormat(item)) {
  
              content = content.replace(yPattern, this.timestampToDate(y, this.tooltipOptions.yDateFormat, item.series.yaxis.options));
  
          }
  
  
  
          // set precision if defined
  
          if (typeof x === 'number') {
  
              content = this.adjustValPrecision(xPattern, content, x);
  
          }
  
          if (typeof y === 'number') {
  
              content = this.adjustValPrecision(yPattern, content, y);
  
          }
  
  
  
          // change x from number to given label, if given
  
          if (typeof item.series.xaxis.ticks !== 'undefined') {
  
  
  
              var ticks;
  
              if (this.hasRotatedXAxisTicks(item)) {
  
                  // xaxis.ticks will be an empty array if tickRotor is being used, but the values are available in rotatedTicks
  
                  ticks = 'rotatedTicks';
  
              } else {
  
                  ticks = 'ticks';
  
              }
  
  
  
              // see https://github.com/krzysu/flot.tooltip/issues/65
  
              var tickIndex = item.dataIndex + item.seriesIndex;
  
  
  
              for (var xIndex in item.series.xaxis[ticks]) {
  
                  if (item.series.xaxis[ticks].hasOwnProperty(tickIndex) && !this.isTimeMode('xaxis', item)) {
  
                      var valueX = (this.isCategoriesMode('xaxis', item)) ? item.series.xaxis[ticks][tickIndex].label : item.series.xaxis[ticks][tickIndex].v;
  
                      if (valueX === x) {
  
                          content = content.replace(xPattern, item.series.xaxis[ticks][tickIndex].label.replace(/\$/g, '$$$$'));
  
                      }
  
                  }
  
              }
  
          }
  
  
  
          // change y from number to given label, if given
  
          if (typeof item.series.yaxis.ticks !== 'undefined') {
  
              for (var yIndex in item.series.yaxis.ticks) {
  
                  if (item.series.yaxis.ticks.hasOwnProperty(yIndex)) {
  
                      var valueY = (this.isCategoriesMode('yaxis', item)) ? item.series.yaxis.ticks[yIndex].label : item.series.yaxis.ticks[yIndex].v;
  
                      if (valueY === y) {
  
                          content = content.replace(yPattern, item.series.yaxis.ticks[yIndex].label.replace(/\$/g, '$$$$'));
  
                      }
  
                  }
  
              }
  
          }
  
  
  
          // if no value customization, use tickFormatter by default
  
          if (typeof item.series.xaxis.tickFormatter !== 'undefined') {
  
              //escape dollar
  
              content = content.replace(xPatternWithoutPrecision, item.series.xaxis.tickFormatter(x, item.series.xaxis).replace(/\$/g, '$$'));
  
          }
  
          if (typeof item.series.yaxis.tickFormatter !== 'undefined') {
  
              //escape dollar
  
              content = content.replace(yPatternWithoutPrecision, item.series.yaxis.tickFormatter(y, item.series.yaxis).replace(/\$/g, '$$'));
  
          }
  
  
  
          return content;
  
      };
  
  
  
      // helpers just for readability
  
      FlotTooltip.prototype.isTimeMode = function (axisName, item) {
  
          return (typeof item.series[axisName].options.mode !== 'undefined' && item.series[axisName].options.mode === 'time');
  
      };
  
  
  
      FlotTooltip.prototype.isXDateFormat = function (item) {
  
          return (typeof this.tooltipOptions.xDateFormat !== 'undefined' && this.tooltipOptions.xDateFormat !== null);
  
      };
  
  
  
      FlotTooltip.prototype.isYDateFormat = function (item) {
  
          return (typeof this.tooltipOptions.yDateFormat !== 'undefined' && this.tooltipOptions.yDateFormat !== null);
  
      };
  
  
  
      FlotTooltip.prototype.isCategoriesMode = function (axisName, item) {
  
          return (typeof item.series[axisName].options.mode !== 'undefined' && item.series[axisName].options.mode === 'categories');
  
      };
  
  
  
      //
  
      FlotTooltip.prototype.timestampToDate = function (tmst, dateFormat, options) {
  
          var theDate = $.plot.dateGenerator(tmst, options);
  
          return $.plot.formatDate(theDate, dateFormat, this.tooltipOptions.monthNames, this.tooltipOptions.dayNames);
  
      };
  
  
  
      //
  
      FlotTooltip.prototype.adjustValPrecision = function (pattern, content, value) {
  
  
  
          var precision;
  
          var matchResult = content.match(pattern);
  
          if( matchResult !== null ) {
  
              if(RegExp.$1 !== '') {
  
                  precision = RegExp.$1;
  
                  value = value.toFixed(precision);
  
  
  
                  // only replace content if precision exists, in other case use thickformater
  
                  content = content.replace(pattern, value);
  
              }
  
          }
  
          return content;
  
      };
  
  
  
      // other plugins detection below
  
  
  
      // check if flot-axislabels plugin (https://github.com/markrcote/flot-axislabels) is used and that an axis label is given
  
      FlotTooltip.prototype.hasAxisLabel = function (axisName, item) {
  
          return ($.inArray('axisLabels', this.plotPlugins) !== -1 && typeof item.series[axisName].options.axisLabel !== 'undefined' && item.series[axisName].options.axisLabel.length > 0);
  
      };
  
  
  
      // check whether flot-tickRotor, a plugin which allows rotation of X-axis ticks, is being used
  
      FlotTooltip.prototype.hasRotatedXAxisTicks = function (item) {
  
          return ($.inArray('tickRotor',this.plotPlugins) !== -1 && typeof item.series.xaxis.rotatedTicks !== 'undefined');
  
      };
  
  
  
      //
  
      var init = function (plot) {
  
        new FlotTooltip(plot);
  
      };
  
  
  
      // define Flot plugin
  
      $.plot.plugins.push({
  
          init: init,
  
          options: defaultOptions,
  
          name: 'tooltip',
  
          version: '0.8.5'
  
      });
  
  
  
  })(jQuery);
  
  