When pan is the default behavior, clearSelection can't be called on
[dygraphs.git] / dygraph.js
index ac1bc03..c10ef05 100644 (file)
@@ -153,17 +153,17 @@ Dygraph.DEFAULT_ATTRS = {
   xLabelHeight: 18,
   yLabelWidth: 18,
 
-  // From renderer
   drawXAxis: true,
   drawYAxis: true,
   axisLineColor: "black",
-    "axisLineWidth": 0.3,
-    "axisLabelColor": "black",
-    "axisLabelFont": "Arial",  // TODO(danvk): is this implemented?
-    "axisLabelWidth": 50,
-    "drawYGrid": true,
-    "drawXGrid": true,
-    "gridLineColor": "rgb(128,128,128)",
+  axisLineWidth: 0.3,
+  gridLineWidth: 0.3,
+  axisLabelColor: "black",
+  axisLabelFont: "Arial",  // TODO(danvk): is this implemented?
+  axisLabelWidth: 50,
+  drawYGrid: true,
+  drawXGrid: true,
+  gridLineColor: "rgb(128,128,128)",
 
   interactionModel: null  // will be set to Dygraph.defaultInteractionModel.
 };
@@ -304,9 +304,6 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
 
   this.boundaryIds_ = [];
 
-  // Make a note of whether labels will be pulled from the CSV file.
-  this.labelsFromCSV_ = (this.attr_("labels") == null);
-
   // Create the containing DIV and other interactive elements
   this.createInterface_();
 
@@ -896,6 +893,8 @@ Dygraph.prototype.setColors_ = function() {
       this.colors_.push(colorStr);
     }
   }
+
+  this.plotter_.setColors(this.colors_);
 };
 
 /**
@@ -1228,7 +1227,7 @@ Dygraph.Interaction.movePan = function(event, g, context) {
     }
   }
 
-  g.drawGraph_();
+  g.drawGraph_(false);
 };
 
 /**
@@ -1255,6 +1254,16 @@ Dygraph.Interaction.endPan = function(event, g, context) {
   context.valueRange = null;
   context.boundedDates = null;
   context.boundedValues = null;
+
+  var dragEndX = g.dragGetX_(event, context);
+  var dragEndY = g.dragGetY_(event, context);
+  var regionWidth = Math.abs(context.dragEndX - context.dragStartX);
+  var regionHeight = Math.abs(context.dragEndY - context.dragStartY);
+
+  if (regionWidth < 2 && regionHeight < 2 &&
+      g.lastx_ != undefined && g.lastx_ != -1) {
+    Dygraph.Interaction.treatMouseOpAsClick(g);
+  }
 };
 
 /**
@@ -1312,6 +1321,33 @@ Dygraph.Interaction.moveZoom = function(event, g, context) {
   context.prevDragDirection = context.dragDirection;
 };
 
+Dygraph.Interaction.treatMouseOpAsClick = function(g) {
+  // TODO(danvk): pass along more info about the points, e.g. 'x'
+  if (g.attr_('clickCallback') != null) {
+    g.attr_('clickCallback')(event, g.lastx_, g.selPoints_);
+  }
+  if (g.attr_('pointClickCallback')) {
+    // check if the click was on a particular point.
+    var closestIdx = -1;
+    var closestDistance = 0;
+    for (var i = 0; i < g.selPoints_.length; i++) {
+      var p = g.selPoints_[i];
+      var distance = Math.pow(p.canvasx - context.dragEndX, 2) +
+                     Math.pow(p.canvasy - context.dragEndY, 2);
+      if (closestIdx == -1 || distance < closestDistance) {
+        closestDistance = distance;
+        closestIdx = i;
+      }
+    }
+
+    // Allow any click within two pixels of the dot.
+    var radius = g.attr_('highlightCircleSize') + 2;
+    if (closestDistance <= 5 * 5) {
+      g.attr_('pointClickCallback')(event, g.selPoints_[closestIdx]);
+    }
+  }
+}
+
 /**
  * Called in response to an interaction model operation that
  * responds to an event that performs a zoom based on previously defined
@@ -1327,7 +1363,6 @@ Dygraph.Interaction.moveZoom = function(event, g, context) {
  * dragStartX/dragStartY/etc. properties). This function modifies the context.
  */
 Dygraph.Interaction.endZoom = function(event, g, context) {
-  // TODO(konigsberg): Refactor or rename this fn -- it deals with clicks, too.
   context.isZooming = false;
   context.dragEndX = g.dragGetX_(event, context);
   context.dragEndY = g.dragGetY_(event, context);
@@ -1336,30 +1371,7 @@ Dygraph.Interaction.endZoom = function(event, g, context) {
 
   if (regionWidth < 2 && regionHeight < 2 &&
       g.lastx_ != undefined && g.lastx_ != -1) {
-    // TODO(danvk): pass along more info about the points, e.g. 'x'
-    if (g.attr_('clickCallback') != null) {
-      g.attr_('clickCallback')(event, g.lastx_, g.selPoints_);
-    }
-    if (g.attr_('pointClickCallback')) {
-      // check if the click was on a particular point.
-      var closestIdx = -1;
-      var closestDistance = 0;
-      for (var i = 0; i < g.selPoints_.length; i++) {
-        var p = g.selPoints_[i];
-        var distance = Math.pow(p.canvasx - context.dragEndX, 2) +
-                       Math.pow(p.canvasy - context.dragEndY, 2);
-        if (closestIdx == -1 || distance < closestDistance) {
-          closestDistance = distance;
-          closestIdx = i;
-        }
-      }
-
-      // Allow any click within two pixels of the dot.
-      var radius = g.attr_('highlightCircleSize') + 2;
-      if (closestDistance <= 5 * 5) {
-        g.attr_('pointClickCallback')(event, g.selPoints_[closestIdx]);
-      }
-    }
+    Dygraph.Interaction.treatMouseOpAsClick(g);
   }
 
   if (regionWidth >= 10 && context.dragDirection == Dygraph.HORIZONTAL) {
@@ -1679,6 +1691,9 @@ Dygraph.prototype.doUnzoom_ = function() {
     }
   }
 
+  // Clear any selection, since it's likely to be drawn in the wrong place.
+  this.clearSelection();
+
   if (dirty) {
     // Putting the drawing operation before the callback because it resets
     // yAxisRange.
@@ -2659,9 +2674,19 @@ Dygraph.prototype.predraw_ = function() {
  * Update the graph with new data. This method is called when the viewing area
  * has changed. If the underlying data or options have changed, predraw_ will
  * be called before drawGraph_ is called.
+ *
+ * clearSelection, when undefined or true, causes this.clearSelection to be
+ * called at the end of the draw operation. This should rarely be defined,
+ * and never true (that is it should be undefined most of the time, and
+ * rarely false.)
+ *
  * @private
  */
-Dygraph.prototype.drawGraph_ = function() {
+Dygraph.prototype.drawGraph_ = function(clearSelection) {
+  if (typeof clearSelection === 'undefined') {
+    clearSelection = true;
+  }
+
   var data = this.rawData_;
 
   // This is used to set the second parameter to drawCallback, below.
@@ -2804,11 +2829,15 @@ Dygraph.prototype.drawGraph_ = function() {
     // Generate a static legend before any particular point is selected.
     this.setLegendHTML_();
   } else {
-    if (typeof(this.selPoints_) !== 'undefined' && this.selPoints_.length) {
-      this.lastx_ = this.selPoints_[0].xval;
-      this.updateSelection_();
-    } else {
-      this.clearSelection();
+    if (clearSelection) {
+      if (typeof(this.selPoints_) !== 'undefined' && this.selPoints_.length) {
+        // We should select the point nearest the page x/y here, but it's easier
+        // to just clear the selection. This prevents erroneous hover dots from
+        // being displayed.
+        this.clearSelection();
+      } else {
+        this.clearSelection();
+      }
     }
   }
 
@@ -3313,9 +3342,10 @@ Dygraph.prototype.parseCSV_ = function(data) {
   }
 
   var start = 0;
-  if (this.labelsFromCSV_) {
+  if (!('labels' in this.user_attrs_)) {
+    // User hasn't explicitly set labels, so they're (presumably) in the CSV.
     start = 1;
-    this.attrs_.labels = lines[0].split(delim);
+    this.attrs_.labels = lines[0].split(delim);  // NOTE: _not_ user_attrs_.
   }
   var line_no = 0;
 
@@ -3731,10 +3761,20 @@ Dygraph.prototype.start_ = function() {
  * <li>errorBars: changes whether the data contains stddev</li>
  * </ul>
  *
+ * There's a huge variety of options that can be passed to this method. For a
+ * full list, see http://dygraphs.com/options.html.
+ *
  * @param {Object} attrs The new properties and values
+ * @param {Boolean} [block_redraw] Usually the chart is redrawn after every
+ * call to updateOptions(). If you know better, you can pass true to explicitly
+ * block the redraw. This can be useful for chaining updateOptions() calls,
+ * avoiding the occasional infinite loop and preventing redraws when it's not
+ * necessary (e.g. when updating a callback).
  */
-Dygraph.prototype.updateOptions = function(attrs) {
-  // TODO(danvk): this is a mess. Rethink this function.
+Dygraph.prototype.updateOptions = function(attrs, block_redraw) {
+  if (typeof(block_redraw) == 'undefined') block_redraw = false;
+
+  // TODO(danvk): this is a mess. Move these options into attr_.
   if ('rollPeriod' in attrs) {
     this.rollPeriod_ = attrs.rollPeriod;
   }
@@ -3757,13 +3797,11 @@ Dygraph.prototype.updateOptions = function(attrs) {
 
   Dygraph.update(this.user_attrs_, attrs);
 
-  this.labelsFromCSV_ = (this.attr_("labels") == null);
-
   if (attrs['file']) {
     this.file_ = attrs['file'];
-    this.start_();
+    if (!block_redraw) this.start_();
   } else {
-    this.predraw_();
+    if (!block_redraw) this.predraw_();
   }
 };
 
@@ -4276,14 +4314,14 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
   "colorSaturation": {
     "default": "1.0",
     "labels": ["Data Series Colors"],
-    "type": "0.0 - 1.0",
+    "type": "float (0.0 - 1.0)",
     "description": "If <strong>colors</strong> is not specified, saturation of the automatically-generated data series colors."
   },
   "yAxisLabelWidth": {
     "default": "50",
     "labels": ["Axis display"],
     "type": "integer",
-    "description": "Width, in pixels, of the y-axis labels."
+    "description": "Width, in pixels, of the y-axis labels. This also affects the amount of space available for a y-axis chart label."
   },
   "hideOverlayOnMouseOut": {
     "default": "true",
@@ -4450,6 +4488,66 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "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."
   },
+  "drawXGrid": {
+    "default": "true",
+    "labels": ["Grid"],
+    "type": "boolean",
+    "description" : "Whether to display vertical gridlines under the chart."
+  },
+  "drawYGrid": {
+    "default": "true",
+    "labels": ["Grid"],
+    "type": "boolean",
+    "description" : "Whether to display horizontal gridlines under the chart."
+  },
+  "drawXAxis": {
+    "default": "true",
+    "labels": ["Axis display"],
+    "type": "boolean",
+    "description" : "Whether to draw the x-axis. Setting this to false also prevents x-axis ticks from being drawn and reclaims the space for the chart grid/lines."
+  },
+  "drawYAxis": {
+    "default": "true",
+    "labels": ["Axis display"],
+    "type": "boolean",
+    "description" : "Whether to draw the y-axis. Setting this to false also prevents y-axis ticks from being drawn and reclaims the space for the chart grid/lines."
+  },
+  "gridLineWidth": {
+    "default": "0.3",
+    "labels": ["Grid"],
+    "type": "float",
+    "description" : "Thickness (in pixels) of the gridlines drawn under the chart. The vertical/horizontal gridlines can be turned off entirely by using the drawXGrid and drawYGrid options."
+  },
+  "axisLineWidth": {
+    "default": "0.3",
+    "labels": ["Axis display"],
+    "type": "float",
+    "description" : "Thickness (in pixels) of the x- and y-axis lines."
+  },
+  "axisLineColor": {
+    "default": "black",
+    "labels": ["Axis display"],
+    "type": "string",
+    "description" : "Color of the x- and y-axis lines. Accepts any value which the HTML canvas strokeStyle attribute understands, e.g. 'black' or 'rgb(0, 100, 255)'."
+  },
+  "fillAlpha": {
+    "default": "0.15",
+    "labels": ["Error bars", "Data Series Colors"],
+    "type": "float (0.0 - 1.0)",
+    "description" : "Error bars (or custom bars) for each series are drawn in the same color as the series, but with partial transparency. This sets the transparency. A value of 0.0 means that the error bars will not be drawn, whereas a value of 1.0 means that the error bars will be as dark as the line for the series itself. This can be used to produce chart lines whose thickness varies at each point."
+  },
+  "axisLabelColor": {
+    "default": "black",
+    "labels": ["Axis display"],
+    "type": "string",
+    "description" : "Color for x- and y-axis labels. This is a CSS color string."
+  },
+  "axisLabelWidth": {
+    "default": "50",
+    "labels": ["Axis display", "Chart labels"],
+    "type": "integer",
+    "description" : "Width (in pixels) of the containing divs for x- and y-axis labels. For the y-axis, this also controls "
+  },
   "sigFigs" : {
     "default": "null",
     "labels": ["Value display/formatting"],
@@ -4467,6 +4565,12 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "labels": ["Value display/formatting"],
     "type": "integer",
     "description": "When displaying numbers in normal (not scientific) mode, large numbers will be displayed with many trailing zeros (e.g. 100000000 instead of 1e9). This can lead to unwieldy y-axis labels. If there are more than <code>maxNumberWidth</code> digits to the left of the decimal in a number, dygraphs will switch to scientific notation, even when not operating in scientific mode. If you'd like to see all those digits, set this to something large, like 20 or 30."
+  },
+  "file": {
+    "default": "(set when constructed)",
+    "labels": ["Data"],
+    "type": "string (URL of CSV or CSV), GViz DataTable or 2D Array",
+    "description": "Sets the data being displayed in the chart. This can only be set when calling updateOptions; it cannot be set from the constructor. For a full description of valid data formats, see the <a href='http://dygraphs.com/data.html'>Data Formats</a> page."
   }
 }
 ;  // </JSON>
@@ -4485,6 +4589,7 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
    'Chart labels',
    'CSV parsing',
    'Callbacks',
+   'Data',
    'Data Line display',
    'Data Series Colors',
    'Error Bars',