Lint as part of Travis tests
[dygraphs.git] / plugins / range-selector.js
index 70b8065..af9e679 100644 (file)
@@ -52,12 +52,12 @@ rangeSelector.prototype.destroy = function() {
 // Private methods
 //------------------------------------------------------------------
 
-rangeSelector.prototype.getOption_ = function(name) {
-  return this.dygraph_.getOption(name);
+rangeSelector.prototype.getOption_ = function(name, opt_series) {
+  return this.dygraph_.getOption(name, opt_series);
 };
 
 rangeSelector.prototype.setDefaultOption_ = function(name, value) {
-  return this.dygraph_.attrs_[name] = value;
+  this.dygraph_.attrs_[name] = value;
 };
 
 /**
@@ -74,7 +74,7 @@ rangeSelector.prototype.createInterface_ = function() {
 
   // Range selector and animatedZooms have a bad interaction. See issue 359.
   if (this.getOption_('animatedZooms')) {
-    this.dygraph_.warn('Animated zooms and range selector are not compatible; disabling animatedZooms.');
+    console.warn('Animated zooms and range selector are not compatible; disabling animatedZooms.');
     this.dygraph_.updateOptions({animatedZooms: false}, true);
   }
 
@@ -166,17 +166,27 @@ rangeSelector.prototype.updateVisibility_ = function() {
  * Resizes the range selector.
  */
 rangeSelector.prototype.resize_ = function() {
-  function setElementRect(canvas, rect) {
+  function setElementRect(canvas, context, rect) {
+    var canvasScale = Dygraph.getContextPixelRatio(context);
+
     canvas.style.top = rect.y + 'px';
     canvas.style.left = rect.x + 'px';
-    canvas.width = rect.w;
-    canvas.height = rect.h;
-    canvas.style.width = canvas.width + 'px';    // for IE
-    canvas.style.height = canvas.height + 'px';  // for IE
+    canvas.width = rect.w * canvasScale;
+    canvas.height = rect.h * canvasScale;
+    canvas.style.width = rect.w + 'px';
+    canvas.style.height = rect.h + 'px';
+
+    if(canvasScale != 1) {
+      context.scale(canvasScale, canvasScale);
+    }
   }
 
   var plotArea = this.dygraph_.layout_.getPlotArea();
-  var xAxisLabelHeight = this.getOption_('xAxisHeight') || (this.getOption_('axisLabelFontSize') + 2 * this.getOption_('axisTickSize'));
+  
+  var xAxisLabelHeight = 0;
+  if (this.dygraph_.getOptionForAxis('drawAxis', 'x')) {
+    xAxisLabelHeight = this.getOption_('xAxisHeight') || (this.getOption_('axisLabelFontSize') + 2 * this.getOption_('axisTickSize'));
+  }
   this.canvasRect_ = {
     x: plotArea.x,
     y: plotArea.y + plotArea.h + xAxisLabelHeight + 4,
@@ -184,8 +194,8 @@ rangeSelector.prototype.resize_ = function() {
     h: this.getOption_('rangeSelectorHeight')
   };
 
-  setElementRect(this.bgcanvas_, this.canvasRect_);
-  setElementRect(this.fgcanvas_, this.canvasRect_);
+  setElementRect(this.bgcanvas_, this.bgcanvas_ctx_, this.canvasRect_);
+  setElementRect(this.fgcanvas_, this.fgcanvas_ctx_, this.canvasRect_);
 };
 
 /**
@@ -264,8 +274,8 @@ rangeSelector.prototype.createZoomHandles_ = function() {
  */
 rangeSelector.prototype.initInteraction_ = function() {
   var self = this;
-  var topElem = this.isIE_ ? document : window;
-  var xLast = 0;
+  var topElem = document;
+  var clientXLast = 0;
   var handle = null;
   var isZooming = false;
   var isPanning = false;
@@ -278,7 +288,7 @@ rangeSelector.prototype.initInteraction_ = function() {
   // functions, defined below.  Defining them this way (rather than with
   // "function foo() {...}" makes JSHint happy.
   var toXDataWindow, onZoomStart, onZoom, onZoomEnd, doZoom, isMouseInPanZone,
-      onPanStart, onPan, onPanEnd, doPan, onCanvasMouseMove, applyBrowserZoomLevel;
+      onPanStart, onPan, onPanEnd, doPan, onCanvasHover;
 
   // Touch event functions
   var onZoomHandleTouchEvent, onCanvasTouchEvent, addTouchEvents;
@@ -291,22 +301,16 @@ rangeSelector.prototype.initInteraction_ = function() {
     return [xDataMin, xDataMax];
   };
 
-  applyBrowserZoomLevel = function(delX) {
-    var zoom = window.outerWidth/document.documentElement.clientWidth;
-    if (!isNaN(zoom)) {
-      return delX/zoom;
-    } else {
-      return delX;
-    }
-  };
-
   onZoomStart = function(e) {
     Dygraph.cancelEvent(e);
     isZooming = true;
-    xLast = e.screenX;
+    clientXLast = e.clientX;
     handle = e.target ? e.target : e.srcElement;
-    self.dygraph_.addEvent(topElem, 'mousemove', onZoom);
-    self.dygraph_.addEvent(topElem, 'mouseup', onZoomEnd);
+    if (e.type === 'mousedown' || e.type === 'dragstart') {
+      // These events are removed manually.
+      Dygraph.addEvent(topElem, 'mousemove', onZoom);
+      Dygraph.addEvent(topElem, 'mouseup', onZoomEnd);
+    }
     self.fgcanvas_.style.cursor = 'col-resize';
     tarp.cover();
     return true;
@@ -317,13 +321,12 @@ rangeSelector.prototype.initInteraction_ = function() {
       return false;
     }
     Dygraph.cancelEvent(e);
-    var delX = e.screenX - xLast;
-    if (Math.abs(delX) < 4 || e.screenX === 0) {
-      // First iPad move event seems to have screenX = 0
+
+    var delX = e.clientX - clientXLast;
+    if (Math.abs(delX) < 4) {
       return true;
     }
-    xLast = e.screenX;
-    delX = applyBrowserZoomLevel(delX);
+    clientXLast = e.clientX;
 
     // Move handle.
     var zoomHandleStatus = self.getZoomHandleStatus_();
@@ -396,9 +399,12 @@ rangeSelector.prototype.initInteraction_ = function() {
     if (!isPanning && isMouseInPanZone(e) && self.getZoomHandleStatus_().isZoomed) {
       Dygraph.cancelEvent(e);
       isPanning = true;
-      xLast = e.screenX;
-      self.dygraph_.addEvent(topElem, 'mousemove', onPan);
-      self.dygraph_.addEvent(topElem, 'mouseup', onPanEnd);
+      clientXLast = e.clientX;
+      if (e.type === 'mousedown') {
+        // These events are removed manually.
+        Dygraph.addEvent(topElem, 'mousemove', onPan);
+        Dygraph.addEvent(topElem, 'mouseup', onPanEnd);
+      }
       return true;
     }
     return false;
@@ -410,12 +416,11 @@ rangeSelector.prototype.initInteraction_ = function() {
     }
     Dygraph.cancelEvent(e);
 
-    var delX = e.screenX - xLast;
+    var delX = e.clientX - clientXLast;
     if (Math.abs(delX) < 4) {
       return true;
     }
-    xLast = e.screenX;
-    delX = applyBrowserZoomLevel(delX);
+    clientXLast = e.clientX;
 
     // Move range view
     var zoomHandleStatus = self.getZoomHandleStatus_();
@@ -468,7 +473,7 @@ rangeSelector.prototype.initInteraction_ = function() {
     }
   };
 
-  onCanvasMouseMove = function(e) {
+  onCanvasHover = function(e) {
     if (isZooming || isPanning) {
       return;
     }
@@ -509,7 +514,7 @@ rangeSelector.prototype.initInteraction_ = function() {
   addTouchEvents = function(elem, fn) {
     var types = ['touchstart', 'touchend', 'touchmove', 'touchcancel'];
     for (var i = 0; i < types.length; i++) {
-      self.dygraph_.addEvent(elem, types[i], fn);
+      self.dygraph_.addAndTrackEvent(elem, types[i], fn);
     }
   };
 
@@ -517,14 +522,14 @@ rangeSelector.prototype.initInteraction_ = function() {
   this.setDefaultOption_('panEdgeFraction', 0.0001);
 
   var dragStartEvent = window.opera ? 'mousedown' : 'dragstart';
-  this.dygraph_.addEvent(this.leftZoomHandle_, dragStartEvent, onZoomStart);
-  this.dygraph_.addEvent(this.rightZoomHandle_, dragStartEvent, onZoomStart);
+  this.dygraph_.addAndTrackEvent(this.leftZoomHandle_, dragStartEvent, onZoomStart);
+  this.dygraph_.addAndTrackEvent(this.rightZoomHandle_, dragStartEvent, onZoomStart);
 
   if (this.isUsingExcanvas_) {
-    this.dygraph_.addEvent(this.iePanOverlay_, 'mousedown', onPanStart);
+    this.dygraph_.addAndTrackEvent(this.iePanOverlay_, 'mousedown', onPanStart);
   } else {
-    this.dygraph_.addEvent(this.fgcanvas_, 'mousedown', onPanStart);
-    this.dygraph_.addEvent(this.fgcanvas_, 'mousemove', onCanvasMouseMove);
+    this.dygraph_.addAndTrackEvent(this.fgcanvas_, 'mousedown', onPanStart);
+    this.dygraph_.addAndTrackEvent(this.fgcanvas_, 'mousemove', onCanvasHover);
   }
 
   // Touch events
@@ -545,7 +550,7 @@ rangeSelector.prototype.drawStaticLayer_ = function() {
   try {
     this.drawMiniPlot_();
   } catch(ex) {
-    Dygraph.warn(ex);
+    console.warn(ex);
   }
 
   var margin = 0.5;
@@ -595,6 +600,13 @@ rangeSelector.prototype.drawMiniPlot_ = function() {
     var dataPoint = combinedSeriesData.data[i];
     var x = ((dataPoint[0] !== null) ? ((dataPoint[0] - xExtremes[0])*xFact) : NaN);
     var y = ((dataPoint[1] !== null) ? (canvasHeight - (dataPoint[1] - combinedSeriesData.yMin)*yFact) : NaN);
+
+    // Skip points that don't change the x-value. Overly fine-grained points
+    // can cause major slowdowns with the ctx.fill() call below.
+    if (!stepPlot && prevX !== null && Math.round(x) == Math.round(prevX)) {
+      continue;
+    }
+
     if (isFinite(x) && isFinite(y)) {
       if(prevX === null) {
         ctx.lineTo(x, canvasHeight);
@@ -639,96 +651,63 @@ rangeSelector.prototype.drawMiniPlot_ = function() {
 
 /**
  * @private
- * Computes and returns the combinded series data along with min/max for the mini plot.
- * @return {Object} An object containing combinded series array, ymin, ymax.
+ * Computes and returns the combined series data along with min/max for the mini plot.
+ * The combined series consists of averaged values for all series.
+ * When series have error bars, the error bars are ignored.
+ * @return {Object} An object containing combined series array, ymin, ymax.
  */
 rangeSelector.prototype.computeCombinedSeriesAndLimits_ = function() {
-  var data = this.dygraph_.rawData_;
+  var g = this.dygraph_;
   var logscale = this.getOption_('logscale');
-
-  // Create a combined series (average of all series values).
-  var combinedSeries = [];
-  var sum;
-  var count;
-  var mutipleValues;
-  var i, j, k;
-  var xVal, yVal;
-
-  // Find out if data has multiple values per datapoint.
-  // Go to first data point that actually has values (see http://code.google.com/p/dygraphs/issues/detail?id=246)
-  for (i = 0; i < data.length; i++) {
-    if (data[i].length > 1 && data[i][1] !== null) {
-      mutipleValues = typeof data[i][1] != 'number';
-      if (mutipleValues) {
-        sum = [];
-        count = [];
-        for (k = 0; k < data[i][1].length; k++) {
-          sum.push(0);
-          count.push(0);
-        }
-      }
-      break;
-    }
+  var i;
+
+  // Select series to combine. By default, all series are combined.
+  var numColumns = g.numColumns();
+  var labels = g.getLabels();
+  var includeSeries = new Array(numColumns);
+  var anySet = false;
+  for (i = 1; i < numColumns; i++) {
+    var include = this.getOption_('showInRangeSelector', labels[i]);
+    includeSeries[i] = include;
+    if (include !== null) anySet = true;  // it's set explicitly for this series
   }
-
-  for (i = 0; i < data.length; i++) {
-    var dataPoint = data[i];
-    xVal = dataPoint[0];
-
-    if (mutipleValues) {
-      for (k = 0; k < sum.length; k++) {
-        sum[k] = count[k] = 0;
-      }
-    } else {
-      sum = count = 0;
-    }
-
-    for (j = 1; j < dataPoint.length; j++) {
-      if (this.dygraph_.visibility()[j-1]) {
-        var y;
-        if (mutipleValues) {
-          for (k = 0; k < sum.length; k++) {
-            y = dataPoint[j][k];
-            if (y === null || isNaN(y)) continue;
-            sum[k] += y;
-            count[k]++;
-          }
-        } else {
-          y = dataPoint[j];
-          if (y === null || isNaN(y)) continue;
-          sum += y;
-          count++;
-        }
-      }
-    }
-
-    if (mutipleValues) {
-      for (k = 0; k < sum.length; k++) {
-        sum[k] /= count[k];
-      }
-      yVal = sum.slice(0);
-    } else {
-      yVal = sum/count;
-    }
-
-    combinedSeries.push([xVal, yVal]);
+  if (!anySet) {
+    for (i = 0; i < includeSeries.length; i++) includeSeries[i] = true;
   }
 
-  // Account for roll period, fractions.
-  combinedSeries = this.dygraph_.rollingAverage(combinedSeries, this.dygraph_.rollPeriod_);
+  // Create a combined series (average of selected series values).
+  // TODO(danvk): short-circuit if there's only one series.
+  var rolledSeries = [];
+  var dataHandler = g.dataHandler_;
+  var options = g.attributes_;
+  for (i = 1; i < g.numColumns(); i++) {
+    if (!includeSeries[i]) continue;
+    var series = dataHandler.extractSeries(g.rawData_, i, options);
+    if (g.rollPeriod() > 1) {
+      series = dataHandler.rollingAverage(series, g.rollPeriod(), options);
+    }
+    
+    rolledSeries.push(series);
+  }
 
-  if (typeof combinedSeries[0][1] != 'number') {
-    for (i = 0; i < combinedSeries.length; i++) {
-      yVal = combinedSeries[i][1];
-      combinedSeries[i][1] = yVal[0];
-    }
+  var combinedSeries = [];
+  for (i = 0; i < rolledSeries[0].length; i++) {
+    var sum = 0;
+    var count = 0;
+    for (var j = 0; j < rolledSeries.length; j++) {
+      var y = rolledSeries[j][i][1];
+      if (y === null || isNaN(y)) continue;
+      count++;
+      sum += y;
+    }
+    combinedSeries.push([rolledSeries[0][i][0], sum / count]);
   }
 
   // Compute the y range.
   var yMin = Number.MAX_VALUE;
   var yMax = -Number.MAX_VALUE;
   for (i = 0; i < combinedSeries.length; i++) {
-    yVal = combinedSeries[i][1];
+    var yVal = combinedSeries[i][1];
     if (yVal !== null && isFinite(yVal) && (!logscale || yVal > 0)) {
       yMin = Math.min(yMin, yVal);
       yMax = Math.max(yMax, yVal);