html5 boiler plate
[scannr.git] / js / flotr2 / js / plugins / hit.js
blob:a/js/flotr2/js/plugins/hit.js -> blob:b/js/flotr2/js/plugins/hit.js
  (function () {
   
  var
  D = Flotr.DOM,
  _ = Flotr._,
  flotr = Flotr,
  S_MOUSETRACK = 'opacity:0.7;background-color:#000;color:#fff;display:none;position:absolute;padding:2px 8px;-moz-border-radius:4px;border-radius:4px;white-space:nowrap;';
   
  Flotr.addPlugin('hit', {
  callbacks: {
  'flotr:mousemove': function(e, pos) {
  this.hit.track(pos);
  },
  'flotr:click': function(pos) {
  var
  hit = this.hit.track(pos);
  _.defaults(pos, hit);
  },
  'flotr:mouseout': function() {
  this.hit.clearHit();
  },
  'flotr:destroy': function() {
  this.mouseTrack = null;
  }
  },
  track : function (pos) {
  if (this.options.mouse.track || _.any(this.series, function(s){return s.mouse && s.mouse.track;})) {
  return this.hit.hit(pos);
  }
  },
  /**
  * Try a method on a graph type. If the method exists, execute it.
  * @param {Object} series
  * @param {String} method Method name.
  * @param {Array} args Arguments applied to method.
  * @return executed successfully or failed.
  */
  executeOnType: function(s, method, args){
  var
  success = false,
  options;
   
  if (!_.isArray(s)) s = [s];
   
  function e(s, index) {
  _.each(_.keys(flotr.graphTypes), function (type) {
  if (s[type] && s[type].show && this[type][method]) {
  options = this.getOptions(s, type);
   
  options.fill = !!s.mouse.fillColor;
  options.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity});
  options.color = s.mouse.lineColor;
  options.context = this.octx;
  options.index = index;
   
  if (args) options.args = args;
  this[type][method].call(this[type], options);
  success = true;
  }
  }, this);
  }
  _.each(s, e, this);
   
  return success;
  },
  /**
  * Updates the mouse tracking point on the overlay.
  */
  drawHit: function(n){
  var octx = this.octx,
  s = n.series;
   
  if (s.mouse.lineColor) {
  octx.save();
  octx.lineWidth = (s.points ? s.points.lineWidth : 1);
  octx.strokeStyle = s.mouse.lineColor;
  octx.fillStyle = this.processColor(s.mouse.fillColor || '#ffffff', {opacity: s.mouse.fillOpacity});
  octx.translate(this.plotOffset.left, this.plotOffset.top);
   
  if (!this.hit.executeOnType(s, 'drawHit', n)) {
  var
  xa = n.xaxis,
  ya = n.yaxis;
   
  octx.beginPath();
  // TODO fix this (points) should move to general testable graph mixin
  octx.arc(xa.d2p(n.x), ya.d2p(n.y), s.points.hitRadius || s.points.radius || s.mouse.radius, 0, 2 * Math.PI, true);
  octx.fill();
  octx.stroke();
  octx.closePath();
  }
  octx.restore();
  this.clip(octx);
  }
  this.prevHit = n;
  },
  /**
  * Removes the mouse tracking point from the overlay.
  */
  clearHit: function(){
  var prev = this.prevHit,
  octx = this.octx,
  plotOffset = this.plotOffset;
  octx.save();
  octx.translate(plotOffset.left, plotOffset.top);
  if (prev) {
  if (!this.hit.executeOnType(prev.series, 'clearHit', this.prevHit)) {
  // TODO fix this (points) should move to general testable graph mixin
  var
  s = prev.series,
  lw = (s.points ? s.points.lineWidth : 1);
  offset = (s.points.hitRadius || s.points.radius || s.mouse.radius) + lw;
  octx.clearRect(
  prev.xaxis.d2p(prev.x) - offset,
  prev.yaxis.d2p(prev.y) - offset,
  offset*2,
  offset*2
  );
  }
  D.hide(this.mouseTrack);
  this.prevHit = null;
  }
  octx.restore();
  },
  /**
  * Retrieves the nearest data point from the mouse cursor. If it's within
  * a certain range, draw a point on the overlay canvas and display the x and y
  * value of the data.
  * @param {Object} mouse - Object that holds the relative x and y coordinates of the cursor.
  */
  hit : function (mouse) {
   
  var
  options = this.options,
  prevHit = this.prevHit,
  closest, sensibility, dataIndex, seriesIndex, series, value, xaxis, yaxis, n;
   
  if (this.series.length === 0) return;
   
  // Nearest data element.
  // dist, x, y, relX, relY, absX, absY, sAngle, eAngle, fraction, mouse,
  // xaxis, yaxis, series, index, seriesIndex
  n = {
  relX : mouse.relX,
  relY : mouse.relY,
  absX : mouse.absX,
  absY : mouse.absY
  };
   
  if (options.mouse.trackY &&
  !options.mouse.trackAll &&
  this.hit.executeOnType(this.series, 'hit', [mouse, n]) &&
  !_.isUndefined(n.seriesIndex))
  {
  series = this.series[n.seriesIndex];
  n.series = series;
  n.mouse = series.mouse;
  n.xaxis = series.xaxis;
  n.yaxis = series.yaxis;
  } else {
   
  closest = this.hit.closest(mouse);
   
  if (closest) {
   
  closest = options.mouse.trackY ? closest.point : closest.x;
  seriesIndex = closest.seriesIndex;
  series = this.series[seriesIndex];
  xaxis = series.xaxis;
  yaxis = series.yaxis;
  sensibility = 2 * series.mouse.sensibility;
   
  if
  (options.mouse.trackAll ||
  (closest.distanceX < sensibility / xaxis.scale &&
  (!options.mouse.trackY || closest.distanceY < sensibility / yaxis.scale)))
  {
  n.series = series;
  n.xaxis = series.xaxis;
  n.yaxis = series.yaxis;
  n.mouse = series.mouse;
  n.x = closest.x;
  n.y = closest.y;
  n.dist = closest.distance;
  n.index = closest.dataIndex;
  n.seriesIndex = seriesIndex;
  }
  }
  }
   
  if (!prevHit || (prevHit.index !== n.index || prevHit.seriesIndex !== n.seriesIndex)) {
  this.hit.clearHit();
  if (n.series && n.mouse && n.mouse.track) {
  this.hit.drawMouseTrack(n);
  this.hit.drawHit(n);
  Flotr.EventAdapter.fire(this.el, 'flotr:hit', [n, this]);
  }
  }
   
  return n;
  },
   
  closest : function (mouse) {
   
  var
  series = this.series,
  options = this.options,
  relX = mouse.relX,
  relY = mouse.relY,
  compare = Number.MAX_VALUE,
  compareX = Number.MAX_VALUE,
  closest = {},
  closestX = {},
  check = false,
  serie, data,
  distance, distanceX, distanceY,
  mouseX, mouseY,
  x, y, i, j;
   
  function setClosest (o) {
  o.distance = distance;
  o.distanceX = distanceX;
  o.distanceY = distanceY;
  o.seriesIndex = i;
  o.dataIndex = j;
  o.x = x;
  o.y = y;
  check = true;
  }
   
  for (i = 0; i < series.length; i++) {
   
  serie = series[i];
  data = serie.data;
  mouseX = serie.xaxis.p2d(relX);
  mouseY = serie.yaxis.p2d(relY);
   
  for (j = data.length; j--;) {
   
  x = data[j][0];
  y = data[j][1];
   
  if (x === null || y === null) continue;
   
  // don't check if the point isn't visible in the current range
  if (x < serie.xaxis.min || x > serie.xaxis.max) continue;
   
  distanceX = Math.abs(x - mouseX);
  distanceY = Math.abs(y - mouseY);
   
  // Skip square root for speed
  distance = distanceX * distanceX + distanceY * distanceY;
   
  if (distance < compare) {
  compare = distance;
  setClosest(closest);
  }
   
  if (distanceX < compareX) {
  compareX = distanceX;
  setClosest(closestX);
  }
  }
  }
   
  return check ? {
  point : closest,
  x : closestX
  } : false;
  },
   
  drawMouseTrack : function (n) {
   
  var
  pos = '',
  s = n.series,
  p = n.mouse.position,
  m = n.mouse.margin,
  x = n.x,
  y = n.y,
  elStyle = S_MOUSETRACK,
  mouseTrack = this.mouseTrack,
  plotOffset = this.plotOffset,
  left = plotOffset.left,
  right = plotOffset.right,
  bottom = plotOffset.bottom,
  top = plotOffset.top,
  decimals = n.mouse.trackDecimals,
  options = this.options;
   
  // Create
  if (!mouseTrack) {
  mouseTrack = D.node('<div class="flotr-mouse-value"></div>');
  this.mouseTrack = mouseTrack;
  D.insert(this.el, mouseTrack);
  }
   
  if (!n.mouse.relative) { // absolute to the canvas
   
  if (p.charAt(0) == 'n') pos += 'top:' + (m + top) + 'px;bottom:auto;';
  else if (p.charAt(0) == 's') pos += 'bottom:' + (m + bottom) + 'px;top:auto;';
  if (p.charAt(1) == 'e') pos += 'right:' + (m + right) + 'px;left:auto;';
  else if (p.charAt(1) == 'w') pos += 'left:' + (m + left) + 'px;right:auto;';
   
  // Pie
  } else if (s.pie && s.pie.show) {
  var center = {
  x: (this.plotWidth)/2,
  y: (this.plotHeight)/2
  },
  radius = (Math.min(this.canvasWidth, this.canvasHeight) * s.pie.sizeRatio) / 2,
  bisection = n.sAngle<n.eAngle ? (n.sAngle + n.eAngle) / 2: (n.sAngle + n.eAngle + 2* Math.PI) / 2;
   
  pos += 'bottom:' + (m - top - center.y - Math.sin(bisection) * radius/2 + this.canvasHeight) + 'px;top:auto;';
  pos += 'left:' + (m + left + center.x + Math.cos(bisection) * radius/2) + 'px;right:auto;';
   
  // Default
  } else {
  if (/n/.test(p)) pos += 'bottom:' + (m - top - n.yaxis.d2p(n.y) + this.canvasHeight) + 'px;top:auto;';
  else pos += 'top:' + (m + top + n.yaxis.d2p(n.y)) + 'px;bottom:auto;';
  if (/w/.test(p)) pos += 'right:' + (m - left - n.xaxis.d2p(n.x) + this.canvasWidth) + 'px;left:auto;';
  else pos += 'left:' + (m + left + n.xaxis.d2p(n.x)) + 'px;right:auto;';
  }
   
  elStyle += pos;
  mouseTrack.style.cssText = elStyle;
  if (!decimals || decimals < 0) decimals = 0;
   
  if (x && x.toFixed) x = x.toFixed(decimals);
   
  if (y && y.toFixed) y = y.toFixed(decimals);
   
  mouseTrack.innerHTML = n.mouse.trackFormatter({
  x: x ,
  y: y,
  series: n.series,
  index: n.index,
  nearest: n,
  fraction: n.fraction
  });
   
  D.show(mouseTrack);
   
  if (n.mouse.relative) {
  if (!/[ew]/.test(p)) {
  // Center Horizontally
  mouseTrack.style.left =
  (left + n.xaxis.d2p(n.x) - D.size(mouseTrack).width / 2) + 'px';
  } else
  if (!/[ns]/.test(p)) {
  // Center Vertically
  mouseTrack.style.top =
  (top + n.yaxis.d2p(n.y) - D.size(mouseTrack).height / 2) + 'px';
  }
  }
  }
   
  });
  })();