Merge branch 'master' of https://github.com/danvk/dygraphs into i382
authorRobert Konigsberg <konigsberg@google.com>
Mon, 3 Jun 2013 14:04:05 +0000 (10:04 -0400)
committerRobert Konigsberg <konigsberg@google.com>
Mon, 3 Jun 2013 14:04:05 +0000 (10:04 -0400)
1  2 
dygraph-utils.js
dygraph.js
plugins/range-selector.js

diff --combined dygraph-utils.js
@@@ -135,7 -135,6 +135,6 @@@ Dygraph.prototype.warn = Dygraph.warn
  
  /**
   * @param {string} message
-  * @private
   */
  Dygraph.error = function(message) {
    Dygraph.log(Dygraph.ERROR, message);
@@@ -193,7 -192,7 +192,7 @@@ Dygraph.addEvent = function addEvent(el
   *     on the event. The function takes one parameter: the event object.
   * @private
   */
 -Dygraph.prototype.addEvent = function(elem, type, fn) {
 +Dygraph.prototype.addAndTrackEvent = function(elem, type, fn) {
    Dygraph.addEvent(elem, type, fn);
    this.registeredEvents_.push({ elem : elem, type : type, fn : fn });
  };
@@@ -221,17 -220,6 +220,17 @@@ Dygraph.removeEvent = function(elem, ty
    }
  };
  
 +Dygraph.prototype.removeTrackedEvents_ = function() {
 +  if (this.registeredEvents_) {
 +    for (var idx = 0; idx < this.registeredEvents_.length; idx++) {
 +      var reg = this.registeredEvents_[idx];
 +      Dygraph.removeEvent(reg.elem, reg.type, reg.fn);
 +    }
 +  }
 +
 +  this.registeredEvents_ = [];
 +}
 +
  /**
   * Cancels further processing of an event. This is useful to prevent default
   * browser actions, e.g. highlighting text on a double-click.
@@@ -645,7 -633,6 +644,6 @@@ Dygraph.dateStrToMillis = function(str
   * @param {!Object} self
   * @param {!Object} o
   * @return {!Object}
-  * @private
   */
  Dygraph.update = function(self, o) {
    if (typeof(o) != 'undefined' && o !== null) {
@@@ -829,7 -816,7 +827,7 @@@ Dygraph.Iterator.prototype.next = funct
  };
  
  /**
-  * Returns a new iterator over array, between indexes start and 
+  * Returns a new iterator over array, between indexes start and
   * start + length, and only returns entries that pass the accept function
   *
   * @param {!Array} array the array to iterate over.
@@@ -919,7 -906,7 +917,7 @@@ Dygraph.repeatAndCleanup = function(rep
   * This function will scan the option list and determine if they
   * require us to recalculate the pixel positions of each point.
   * @param {!Array.<string>} labels a list of options to check.
-  * @param {!Object} attrs 
+  * @param {!Object} attrs
   * @return {boolean} true if the graph needs new points else false.
   * @private
   */
@@@ -1023,7 -1010,7 +1021,7 @@@ Dygraph.isPixelChangingOptionList = fun
  
  /**
   * Compares two arrays to see if they are equal. If either parameter is not an
-  * array it will return false. Does a shallow compare 
+  * array it will return false. Does a shallow compare
   * Dygraph.compareArrays([[1,2], [3, 4]], [[1,2], [3,4]]) === false.
   * @param {!Array.<T>} array1 first array
   * @param {!Array.<T>} array2 second array
@@@ -1068,7 -1055,7 +1066,7 @@@ Dygraph.regularShape_ = function
    var computeCoordinates = function() {
      var x = cx + (Math.sin(angle) * radius);
      var y = cy + (-Math.cos(angle) * radius);
-     return [x, y]; 
+     return [x, y];
    };
  
    var initialCoordinates = computeCoordinates();
@@@ -1176,7 -1163,7 +1174,7 @@@ Dygraph.Circles = 
   *   };
   *   window.addEventListener('mouseup', mouseUpHandler);
   * };
-  * 
+  *
   * @constructor
   */
  Dygraph.IFrameTarp = function() {
@@@ -1247,20 -1234,21 +1245,21 @@@ Dygraph.detectLineDelimiter = function(
  };
  
  /**
-  * Is one element contained by another?
-  * @param {Element} containee The contained element.
-  * @param {Element} container The container element.
+  * Is one node contained by another?
+  * @param {Node} containee The contained node.
+  * @param {Node} container The container node.
   * @return {boolean} Whether containee is inside (or equal to) container.
   * @private
   */
- Dygraph.isElementContainedBy = function(containee, container) {
+ Dygraph.isNodeContainedBy = function(containee, container) {
    if (container === null || containee === null) {
      return false;
    }
-   while (containee && containee !== container) {
-     containee = containee.parentNode;
+   var containeeNode = /** @type {Node} */ (containee);
+   while (containeeNode && containeeNode !== container) {
+     containeeNode = containeeNode.parentNode;
    }
-   return (containee === container);
+   return (containeeNode === container);
  };
  
  
diff --combined dygraph.js
@@@ -344,18 -344,24 +344,24 @@@ Dygraph.DEFAULT_ATTRS = 
        pixelsPerLabel: 60,
        axisLabelFormatter: Dygraph.dateAxisFormatter,
        valueFormatter: Dygraph.dateString_,
+       drawGrid: true,
+       independentTicks: true,
        ticker: null  // will be set in dygraph-tickers.js
      },
      y: {
        pixelsPerLabel: 30,
        valueFormatter: Dygraph.numberValueFormatter,
        axisLabelFormatter: Dygraph.numberAxisLabelFormatter,
+       drawGrid: true,
+       independentTicks: true,
        ticker: null  // will be set in dygraph-tickers.js
      },
      y2: {
        pixelsPerLabel: 30,
        valueFormatter: Dygraph.numberValueFormatter,
        axisLabelFormatter: Dygraph.numberAxisLabelFormatter,
+       drawGrid: false,
+       independentTicks: false,
        ticker: null  // will be set in dygraph-tickers.js
      }
    }
@@@ -979,7 -985,8 +985,7 @@@ Dygraph.prototype.createInterface_ = fu
    var enclosing = this.maindiv_;
  
    this.graphDiv = document.createElement("div");
 -  this.graphDiv.style.width = this.width_ + "px";
 -  this.graphDiv.style.height = this.height_ + "px";
 +
    // TODO(danvk): any other styles that are useful to set here?
    this.graphDiv.style.textAlign = 'left';  // This is a CSS "reset"
    enclosing.appendChild(this.graphDiv);
    // Create the canvas for interactive parts of the chart.
    this.canvas_ = Dygraph.createCanvas();
    this.canvas_.style.position = "absolute";
 -  this.canvas_.width = this.width_;
 -  this.canvas_.height = this.height_;
 -  this.canvas_.style.width = this.width_ + "px";    // for IE
 -  this.canvas_.style.height = this.height_ + "px";  // for IE
 +
 +  this.resizeElements_();
  
    this.canvas_ctx_ = Dygraph.getContext(this.canvas_);
  
      // 2. e.relatedTarget is outside the chart
      var target = e.target || e.fromElement;
      var relatedTarget = e.relatedTarget || e.toElement;
-     if (Dygraph.isElementContainedBy(target, dygraph.graphDiv) &&
-         !Dygraph.isElementContainedBy(relatedTarget, dygraph.graphDiv)) {
+     if (Dygraph.isNodeContainedBy(target, dygraph.graphDiv) &&
+         !Dygraph.isNodeContainedBy(relatedTarget, dygraph.graphDiv)) {
        dygraph.mouseOut_(e);
      }
    };
  
 -  this.addEvent(window, 'mouseout', this.mouseOutHandler_);
 -  this.addEvent(this.mouseEventElement_, 'mousemove', this.mouseMoveHandler_);
 +  this.addAndTrackEvent(window, 'mouseout', this.mouseOutHandler_);
 +  this.addAndTrackEvent(this.mouseEventElement_, 'mousemove', this.mouseMoveHandler_);
  
    // Don't recreate and register the resize handler on subsequent calls.
    // This happens when the graph is resized.
  
      // Update when the window is resized.
      // TODO(danvk): drop frames depending on complexity of the chart.
 -    this.addEvent(window, 'resize', this.resizeHandler_);
 +    this.addAndTrackEvent(window, 'resize', this.resizeHandler_);
    }
  };
  
 +Dygraph.prototype.resizeElements_ = function() {
 +  this.graphDiv.style.width = this.width_ + "px";
 +  this.graphDiv.style.height = this.height_ + "px";
 +  this.canvas_.width = this.width_;
 +  this.canvas_.height = this.height_;
 +  this.canvas_.style.width = this.width_ + "px";    // for IE
 +  this.canvas_.style.height = this.height_ + "px";  // for IE
 +}
 +
  /**
   * Detach DOM elements in the dygraph and null out all data references.
   * Calling this when you're done with a dygraph can dramatically reduce memory
   * usage. See, e.g., the tests/perf.html example.
   */
  Dygraph.prototype.destroy = function() {
 +  this.canvas_ctx_.restore();
 +  this.hidden_ctx_.restore();
 +
    var removeRecursive = function(node) {
      while (node.hasChildNodes()) {
        removeRecursive(node.firstChild);
      }
    };
  
 -  if (this.registeredEvents_) {
 -    for (var idx = 0; idx < this.registeredEvents_.length; idx++) {
 -      var reg = this.registeredEvents_[idx];
 -      Dygraph.removeEvent(reg.elem, reg.type, reg.fn);
 -    }
 -  }
 -
 -  this.registeredEvents_ = [];
 +  this.removeTrackedEvents_();
  
    // remove mouse event handlers (This may not be necessary anymore)
    Dygraph.removeEvent(window, 'mouseout', this.mouseOutHandler_);
    Dygraph.removeEvent(this.mouseEventElement_, 'mousemove', this.mouseMoveHandler_);
 -  Dygraph.removeEvent(this.mouseEventElement_, 'mouseup', this.mouseUpHandler_);
  
    // remove window handlers
    Dygraph.removeEvent(window,'resize',this.resizeHandler_);
@@@ -1339,13 -1344,19 +1345,13 @@@ Dygraph.prototype.createDragInterface_ 
  
    for (var eventName in interactionModel) {
      if (!interactionModel.hasOwnProperty(eventName)) continue;
 -    this.addEvent(this.mouseEventElement_, eventName,
 +    this.addAndTrackEvent(this.mouseEventElement_, eventName,
          bindHandler(interactionModel[eventName]));
    }
  
 -  // unregister the handler on subsequent calls.
 -  // This happens when the graph is resized.
 -  if (this.mouseUpHandler_) {
 -    Dygraph.removeEvent(document, 'mouseup', this.mouseUpHandler_);
 -  }
 -
    // If the user releases the mouse button during a drag, but not over the
    // canvas, then it doesn't count as a zooming action.
 -  this.mouseUpHandler_ = function(event) {
 +  var mouseUpHandler = function(event) {
      if (context.isZooming || context.isPanning) {
        context.isZooming = false;
        context.dragStartX = null;
      context.tarp.uncover();
    };
  
 -  this.addEvent(document, 'mouseup', this.mouseUpHandler_);
 +  this.addAndTrackEvent(document, 'mouseup', this.mouseUpHandler);
  };
  
  /**
@@@ -1983,7 -1994,7 +1989,7 @@@ Dygraph.prototype.updateSelection_ = fu
        ctx.strokeStyle = color;
        ctx.fillStyle = color;
        callback(this.g, pt.name, ctx, canvasx, pt.canvasy,
-           color, circleSize);
+           color, circleSize, pt.idx);
      }
      ctx.restore();
  
@@@ -2225,15 -2236,6 +2231,15 @@@ Dygraph.prototype.predraw_ = function(
      this.cascadeEvents_('clearChart');
      this.plotter_.clear();
    }
 +
 +  if(!this.is_initial_draw_) {
 +    this.canvas_ctx_.restore();
 +    this.hidden_ctx_.restore();
 +  }
 +
 +  this.canvas_ctx_.save();
 +  this.hidden_ctx_.save();
 +
    this.plotter_ = new DygraphCanvasRenderer(this,
                                              this.hidden_,
                                              this.hidden_ctx_,
@@@ -2282,6 -2284,17 +2288,17 @@@ Dygraph.prototype.gatherDatasets_ = fun
    var datasets = [];
    var extremes = {};  // series name -> [low, high]
    var i, j, k;
+   var errorBars = this.attr_("errorBars");
+   var customBars = this.attr_("customBars");
+   var bars = errorBars || customBars;
+   var isValueNull = function(sample) {
+     if (!bars) {
+       return sample[1] === null;
+     } else {
+       return customBars ? sample[1][1] === null : 
+         errorBars ? sample[1][0] === null : false;
+     }
+   };
  
    // Loop over the fields (series).  Go from the last to the first,
    // because if they're stacked that's how we accumulate the values.
      // Prune down to the desired range, if necessary (for zooming)
      // Because there can be lines going to points outside of the visible area,
      // we actually prune to visible points, plus one on either side.
-     var bars = this.attr_("errorBars") || this.attr_("customBars");
      if (dateWindow) {
        var low = dateWindow[0];
        var high = dateWindow[1];
        var pruned = [];
        // TODO(danvk): do binary search instead of linear search.
        // TODO(danvk): pass firstIdx and lastIdx directly to the renderer.
        var firstIdx = null, lastIdx = null;
            lastIdx = k;
          }
        }
        if (firstIdx === null) firstIdx = 0;
-       if (firstIdx > 0) firstIdx--;
+       var correctedFirstIdx = firstIdx;
+       var isInvalidValue = true;
+       while (isInvalidValue && correctedFirstIdx > 0) {
+         correctedFirstIdx--;
+         isInvalidValue = isValueNull(series[correctedFirstIdx]);
+       }
        if (lastIdx === null) lastIdx = series.length - 1;
-       if (lastIdx < series.length - 1) lastIdx++;
-       boundaryIds[i-1] = [firstIdx, lastIdx];
+       var correctedLastIdx = lastIdx;
+       isInvalidValue = true;
+       while (isInvalidValue && correctedLastIdx < series.length - 1) {
+         correctedLastIdx++;
+         isInvalidValue = isValueNull(series[correctedLastIdx]);
+       }
+       boundaryIds[i-1] = [(firstIdx > 0) ? firstIdx - 1 : firstIdx, 
+           (lastIdx < series.length - 1) ? lastIdx + 1 : lastIdx];
+       if (correctedFirstIdx!==firstIdx) {
+         pruned.push(series[correctedFirstIdx]);
+       }
        for (k = firstIdx; k <= lastIdx; k++) {
          pruned.push(series[k]);
        }
+       if (correctedLastIdx !== lastIdx) {
+         pruned.push(series[correctedLastIdx]);
+       }
        series = pruned;
      } else {
        boundaryIds[i-1] = [0, series.length-1];
@@@ -2592,12 -2627,15 +2631,15 @@@ Dygraph.prototype.computeYAxisRanges_ 
    };
    var numAxes = this.attributes_.numAxes();
    var ypadCompat, span, series, ypad;
+   
+   var p_axis;
  
    // Compute extreme values, a span and tick marks for each axis.
    for (var i = 0; i < numAxes; i++) {
      var axis = this.axes_[i];
      var logscale = this.attributes_.getForAxis("logscale", i);
      var includeZero = this.attributes_.getForAxis("includeZero", i);
+     var independentTicks = this.attributes_.getForAxis("independentTicks", i);
      series = this.attributes_.seriesForAxis(i);
  
      // Add some padding. This supports two Y padding operation modes:
      } else {
        axis.computedValueRange = axis.extremeRange;
      }
-     // Add ticks. By default, all axes inherit the tick positions of the
-     // primary axis. However, if an axis is specifically marked as having
-     // independent ticks, then that is permissible as well.
-     var opts = this.optionsViewForAxis_('y' + (i ? '2' : ''));
-     var ticker = opts('ticker');
-     if (i === 0 || axis.independentTicks) {
+     
+     
+     if(independentTicks) {
+       axis.independentTicks = independentTicks;
+       var opts = this.optionsViewForAxis_('y' + (i ? '2' : ''));
+       var ticker = opts('ticker');
        axis.ticks = ticker(axis.computedValueRange[0],
-                           axis.computedValueRange[1],
-                           this.height_,  // TODO(danvk): should be area.height
-                           opts,
-                           this);
-     } else {
-       var p_axis = this.axes_[0];
+               axis.computedValueRange[1],
+               this.height_,  // TODO(danvk): should be area.height
+               opts,
+               this);
+       // Define the first independent axis as primary axis.
+       if (!p_axis) p_axis = axis;
+     }
+   }
+   if (p_axis === undefined) {
+     throw ("Configuration Error: At least one axis has to have the \"independentTicks\" option activated.");
+   }
+   // Add ticks. By default, all axes inherit the tick positions of the
+   // primary axis. However, if an axis is specifically marked as having
+   // independent ticks, then that is permissible as well.
+   for (var i = 0; i < numAxes; i++) {
+     var axis = this.axes_[i];
+     
+     if (!axis.independentTicks) {
+       var opts = this.optionsViewForAxis_('y' + (i ? '2' : ''));
+       var ticker = opts('ticker');
        var p_ticks = p_axis.ticks;
        var p_scale = p_axis.computedValueRange[1] - p_axis.computedValueRange[0];
        var scale = axis.computedValueRange[1] - axis.computedValueRange[0];
  Dygraph.prototype.extractSeries_ = function(rawData, i, logScale) {
    // TODO(danvk): pre-allocate series here.
    var series = [];
+   var errorBars = this.attr_("errorBars");
+   var customBars =  this.attr_("customBars");
    for (var j = 0; j < rawData.length; j++) {
      var x = rawData[j][0];
      var point = rawData[j][i];
          point = null;
        }
      }
-     series.push([x, point]);
+     // Fix null points to fit the display type standard.
+     if(point !== null) {
+       series.push([x, point]);
+     } else {
+       series.push([x, errorBars ? [null, null] : customBars ? [null, null, null] : point]);
+     }
    }
    return series;
  };
@@@ -3536,9 -3594,17 +3598,9 @@@ Dygraph.prototype.resize = function(wid
      this.height_ = this.maindiv_.clientHeight;
    }
  
 +  this.resizeElements_();
 +
    if (old_width != this.width_ || old_height != this.height_) {
 -    // TODO(danvk): there should be a clear() method.
 -    this.maindiv_.innerHTML = "";
 -    this.roller_ = null;
 -    this.attrs_.labelsDiv = null;
 -    this.createInterface_();
 -    if (this.annotations_.length) {
 -      // createInterface_ reset the layout, so we need to do this.
 -      this.layout_.setAnnotations(this.annotations_);
 -    }
 -    this.createDragInterface_();
      this.predraw_();
    }
  
@@@ -176,7 -176,11 +176,11 @@@ rangeSelector.prototype.resize_ = funct
    }
  
    var plotArea = this.dygraph_.layout_.getPlotArea();
-   var xAxisLabelHeight = this.getOption_('xAxisHeight') || (this.getOption_('axisLabelFontSize') + 2 * this.getOption_('axisTickSize'));
+   
+   var xAxisLabelHeight = 0;
+   if(this.getOption_('drawXAxis')){
+     xAxisLabelHeight = this.getOption_('xAxisHeight') || (this.getOption_('axisLabelFontSize') + 2 * this.getOption_('axisTickSize'));
+   }
    this.canvasRect_ = {
      x: plotArea.x,
      y: plotArea.y + plotArea.h + xAxisLabelHeight + 4,
@@@ -504,7 -508,7 +508,7 @@@ rangeSelector.prototype.initInteraction
    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);
      }
    };
  
    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', onCanvasHover);
 +    this.dygraph_.addAndTrackEvent(this.fgcanvas_, 'mousedown', onPanStart);
 +    this.dygraph_.addAndTrackEvent(this.fgcanvas_, 'mousemove', onCanvasHover);
    }
  
    // Touch events