merge master
authorDan Vanderkam <dan@dygraphs.com>
Tue, 5 Apr 2011 14:35:42 +0000 (10:35 -0400)
committerDan Vanderkam <dan@dygraphs.com>
Tue, 5 Apr 2011 14:35:42 +0000 (10:35 -0400)
1  2 
dygraph.js

diff --combined dygraph.js
@@@ -193,11 -193,6 +193,11 @@@ Dygraph.DEFAULT_ATTRS = 
    stepPlot: false,
    avoidMinZero: false,
  
 +  // Sizes of the various chart labels.
 +  titleHeight: 28,
 +  xLabelHeight: 18,
 +  yLabelWidth: 18,
 +
    interactionModel: null  // will be set to Dygraph.defaultInteractionModel.
  };
  
@@@ -263,6 -258,10 +263,10 @@@ Dygraph.prototype.__init__ = function(d
    this.is_initial_draw_ = true;
    this.annotations_ = [];
  
+   // Zoomed indicators - These indicate when the graph has been zoomed and on what axis.
+   this.zoomed_x_ = false;
+   this.zoomed_y_ = false;
    // Number of digits to use when labeling the x (if numeric) and y axis
    // ticks.
    this.numXDigits_ = 2;
    this.start_();
  };
  
+ /**
+  * Returns the zoomed status of the chart for one or both axes.
+  *
+  * Axis is an optional parameter. Can be set to 'x' or 'y'.
+  *
+  * The zoomed status for an axis is set whenever a user zooms using the mouse
+  * or when the dateWindow or valueRange are updated (unless the isZoomedIgnoreProgrammaticZoom
+  * option is also specified).
+  */
+ Dygraph.prototype.isZoomed = function(axis) {
+   if (axis == null) return this.zoomed_x_ || this.zoomed_y_;
+   if (axis == 'x') return this.zoomed_x_;
+   if (axis == 'y') return this.zoomed_y_;
+   throw "axis parameter to Dygraph.isZoomed must be missing, 'x' or 'y'.";
+ };
  Dygraph.prototype.toString = function() {
    var maindiv = this.maindiv_;
    var id = (maindiv && maindiv.id) ? maindiv.id : maindiv
@@@ -944,9 -959,8 +964,9 @@@ Dygraph.prototype.createStatusMessage_ 
  };
  
  /**
 - * Position the labels div so that its right edge is flush with the right edge
 - * of the charting area.
 + * Position the labels div so that:
 + * - its right edge is flush with the right edge of the charting area
 + * - its top edge is flush with the top edge of the charting area
   */
  Dygraph.prototype.positionLabelsDiv_ = function() {
    // Don't touch a user-specified labelsDiv.
    var area = this.plotter_.area;
    var div = this.attr_("labelsDiv");
    div.style.left = area.x + area.w - this.attr_("labelsDivWidth") - 1 + "px";
 +  div.style.top = area.y + "px";
  };
  
  /**
@@@ -973,11 -986,10 +993,11 @@@ Dygraph.prototype.createRollInterface_ 
  
    var display = this.attr_('showRoller') ? 'block' : 'none';
  
 +  var area = this.plotter_.area;
    var textAttr = { "position": "absolute",
                     "zIndex": 10,
 -                   "top": (this.plotter_.area.h - 25) + "px",
 -                   "left": (this.plotter_.area.x + 1) + "px",
 +                   "top": (area.y + area.h - 25) + "px",
 +                   "left": (area.x + 1) + "px",
                     "display": display
                    };
    this.roller_.size = "2";
@@@ -1503,6 -1515,7 +1523,7 @@@ Dygraph.prototype.doZoomX_ = function(l
   */
  Dygraph.prototype.doZoomXDates_ = function(minDate, maxDate) {
    this.dateWindow_ = [minDate, maxDate];
+   this.zoomed_x_ = true;
    this.drawGraph_();
    if (this.attr_("zoomCallback")) {
      this.attr_("zoomCallback")(minDate, maxDate, this.yAxisRanges());
@@@ -1530,9 -1543,11 +1551,11 @@@ Dygraph.prototype.doZoomY_ = function(l
      valueRanges.push([low, hi]);
    }
  
+   this.zoomed_y_ = true;
    this.drawGraph_();
    if (this.attr_("zoomCallback")) {
      var xRange = this.xAxisRange();
+     var yRange = this.yAxisRange();
      this.attr_("zoomCallback")(xRange[0], xRange[1], this.yAxisRanges());
    }
  };
@@@ -1560,6 -1575,8 +1583,8 @@@ Dygraph.prototype.doUnzoom_ = function(
    if (dirty) {
      // Putting the drawing operation before the callback because it resets
      // yAxisRange.
+     this.zoomed_x_ = false;
+     this.zoomed_y_ = false;
      this.drawGraph_();
      if (this.attr_("zoomCallback")) {
        var minDate = this.rawData_[0][0];
@@@ -2588,11 -2605,13 +2613,13 @@@ Dygraph.prototype.drawGraph_ = function
    this.layout_.updateOptions( { yAxes: this.axes_,
                                  seriesToAxisMap: this.seriesToAxisMap_
                                } );
    this.addXTicks_();
  
+   // Save the X axis zoomed status as the updateOptions call will tend to set it errorneously
+   var tmp_zoomed_x = this.zoomed_x_;
    // Tell PlotKit to use this new data and render itself
    this.layout_.updateOptions({dateWindow: this.dateWindow_});
+   this.zoomed_x_ = tmp_zoomed_x;
    this.layout_.evaluateWithError();
    this.plotter_.clear();
    this.plotter_.render();
   *   indices are into the axes_ array.
   */
  Dygraph.prototype.computeYAxes_ = function() {
+   var valueWindows;
+   if (this.axes_ != undefined) {
+     // Preserve valueWindow settings.
+     valueWindows = [];
+     for (var index = 0; index < this.axes_.length; index++) {
+       valueWindows.push(this.axes_[index].valueWindow);
+     }
+   }
    this.axes_ = [{ yAxisId : 0, g : this }];  // always have at least one y-axis.
    this.seriesToAxisMap_ = {};
  
      if (vis[i - 1]) seriesToAxisFiltered[s] = this.seriesToAxisMap_[s];
    }
    this.seriesToAxisMap_ = seriesToAxisFiltered;
+   if (valueWindows != undefined) {
+     // Restore valueWindow settings.
+     for (var index = 0; index < valueWindows.length; index++) {
+       this.axes_[index].valueWindow = valueWindows[index];
+     }
+   }
  };
  
  /**
@@@ -2750,12 -2785,24 +2793,24 @@@ Dygraph.prototype.computeYAxisRanges_ 
        var series = seriesForAxis[i];
        var minY = Infinity;  // extremes[series[0]][0];
        var maxY = -Infinity;  // extremes[series[0]][1];
+       var extremeMinY, extremeMaxY;
        for (var j = 0; j < series.length; j++) {
-         minY = Math.min(extremes[series[j]][0], minY);
-         maxY = Math.max(extremes[series[j]][1], maxY);
+         // Only use valid extremes to stop null data series' from corrupting the scale.
+         extremeMinY = extremes[series[j]][0];
+         if (extremeMinY != null) {
+           minY = Math.min(extremeMinY, minY);
+         }
+         extremeMaxY = extremes[series[j]][1];
+         if (extremeMaxY != null) {
+           maxY = Math.max(extremeMaxY, maxY);
+         }
        }
        if (axis.includeZero && minY > 0) minY = 0;
  
+       // Ensure we have a valid scale, otherwise defualt to zero for safety.
+       if (minY == Infinity) minY = 0;
+       if (maxY == -Infinity) maxY = 0;
        // Add some padding and round up to an integer to be human-friendly.
        var span = maxY - minY;
        // special case: if we have no sense of scale, use +/-10% of the sole value.
@@@ -3477,6 -3524,7 +3532,7 @@@ Dygraph.prototype.start_ = function() 
   * <li>file: changes the source data for the graph</li>
   * <li>errorBars: changes whether the data contains stddev</li>
   * </ul>
+  *
   * @param {Object} attrs The new properties and values
   */
  Dygraph.prototype.updateOptions = function(attrs) {
    }
    if ('dateWindow' in attrs) {
      this.dateWindow_ = attrs.dateWindow;
+     if (!('isZoomedIgnoreProgrammaticZoom' in attrs)) {
+       this.zoomed_x_ = attrs.dateWindow != null;
+     }
+   }
+   if ('valueRange' in attrs && !('isZoomedIgnoreProgrammaticZoom' in attrs)) {
+     this.zoomed_y_ = attrs.valueRange != null;
    }
  
    // TODO(danvk): validate per-series options.
@@@ -4110,7 -4164,7 +4172,7 @@@ Dygraph.OPTIONS_REFERENCE =  // <JSON
    "sigma": {
      "default": "2.0",
      "labels": ["Error Bars"],
 -    "type": "integer",
 +    "type": "float",
      "description": "When errorBars is set, shade this many standard deviations above/below each point."
    },
    "customBars": {
      "default": "null",
      "description": "A value representing the farthest a graph may be panned, in percent of the display. For example, a value of 0.1 means that the graph can only be panned 10% pased the edges of the displayed values. null means no bounds."
    },
 +  "title": {
 +    "labels": ["Chart labels"],
 +    "type": "string",
 +    "default": "null",
 +    "description": "Text to display above the chart. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the 'dygraph-label' or 'dygraph-title' classes."
 +  },
 +  "titleHeight": {
 +    "default": "18",
 +    "labels": ["Chart labels"],
 +    "type": "integer",
 +    "description": "Height of the chart title, in pixels. This also controls the default font size of the title. If you style the title on your own, this controls how much space is set aside above the chart for the title's div."
 +  },
 +  "xlabel": {
 +    "labels": ["Chart labels"],
 +    "type": "string",
 +    "default": "null",
 +    "description": "Text to display below the chart's x-axis. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the 'dygraph-label' or 'dygraph-xlabel' classes."
 +  },
 +  "xLabelHeight": {
 +    "labels": ["Chart labels"],
 +    "type": "integer",
 +    "default": "18",
 +    "description": "Height of the x-axis label, in pixels. This also controls the default font size of the x-axis label. If you style the label on your own, this controls how much space is set aside below the chart for the x-axis label's div."
 +  },
 +  "ylabel": {
 +    "labels": ["Chart labels"],
 +    "type": "string",
 +    "default": "null",
 +    "description": "Text to display to the left of the chart's y-axis. You can supply any HTML for this value, not just text. If you wish to style it using CSS, use the 'dygraph-label' or 'dygraph-ylabel' classes. The text will be rotated 90 degrees by default, so CSS rules may behave in unintuitive ways. No additional space is set aside for a y-axis label. If you need more space, increase the width of the y-axis tick labels using the yAxisLabelWidth option. If you need a wider div for the y-axis label, either style it that way with CSS (but remember that it's rotated, so width is controlled by the 'height' property) or set the yLabelWidth option."
 +  },
 +  "yLabelWidth": {
 +    "labels": ["Chart labels"],
 +    "type": "integer",
 +    "default": "18",
 +    "description": "Width of the div which contains the y-axis label. Since the y-axis label appears rotated 90 degrees, this actually affects the height of its div."
++  },
+   "isZoomedIgnoreProgrammaticZoom" : {
+     "default": "false",
+     "labels": ["Zooming"],
+     "type": "boolean",
+     "description" : "When this option is passed to updateOptions() along with either the <code>dateWindow</code> or <code>valueRange</code> options, the zoom flags are not changed to reflect a zoomed state. This is primarily useful for when the display area of a chart is changed programmatically and also where manual zooming is allowed and use is made of the <code>isZoomed</code> method to determine this."
    }
  }
  ;  // </JSON>
  // NOTE: in addition to parsing as JS, this snippet is expected to be valid
  // JSON. This assumption cannot be checked in JS, but it will be checked when
 -// documentation is generated by the generate-documentation.py script.
 +// documentation is generated by the generate-documentation.py script. For the
 +// most part, this just means that you should always use double quotes.
  
  // Do a quick sanity check on the options reference.
  (function() {
    var valid_cats = [ 
     'Annotations',
     'Axis display',
 +   'Chart labels',
     'CSV parsing',
     'Callbacks',
     'Data Line display',
     'Legend',
     'Overall display',
     'Rolling Averages',
-    'Value display/formatting'
+    'Value display/formatting',
+    'Zooming'
    ];
    var cats = {};
    for (var i = 0; i < valid_cats.length; i++) cats[valid_cats[i]] = true;