merge upstream changes
authorDan Vanderkam <dan@dygraphs.com>
Sun, 24 Apr 2011 16:00:01 +0000 (12:00 -0400)
committerDan Vanderkam <dan@dygraphs.com>
Sun, 24 Apr 2011 16:00:01 +0000 (12:00 -0400)
1  2 
dygraph.js

diff --combined dygraph.js
@@@ -72,6 -72,59 +72,6 @@@ Dygraph.toString = function() 
    return this.__repr__();
  };
  
 -/**
 - * Formatting to use for an integer number.
 - *
 - * @param {Number} x The number to format
 - * @param {Number} unused_precision The precision to use, ignored.
 - * @return {String} A string formatted like %g in printf.  The max generated
 - *                  string length should be precision + 6 (e.g 1.123e+300).
 - */
 -Dygraph.intFormat = function(x, unused_precision) {
 -  return x.toString();
 -}
 -
 -/**
 - * Number formatting function which mimicks the behavior of %g in printf, i.e.
 - * either exponential or fixed format (without trailing 0s) is used depending on
 - * the length of the generated string.  The advantage of this format is that
 - * there is a predictable upper bound on the resulting string length,
 - * significant figures are not dropped, and normal numbers are not displayed in
 - * exponential notation.
 - *
 - * NOTE: JavaScript's native toPrecision() is NOT a drop-in replacement for %g.
 - * It creates strings which are too long for absolute values between 10^-4 and
 - * 10^-6.  See tests/number-format.html for output examples.
 - *
 - * @param {Number} x The number to format
 - * @param {Number} opt_precision The precision to use, default 2.
 - * @return {String} A string formatted like %g in printf.  The max generated
 - *                  string length should be precision + 6 (e.g 1.123e+300).
 - */
 -Dygraph.floatFormat = function(x, opt_precision) {
 -  // Avoid invalid precision values; [1, 21] is the valid range.
 -  var p = Math.min(Math.max(1, opt_precision || 2), 21);
 -
 -  // This is deceptively simple.  The actual algorithm comes from:
 -  //
 -  // Max allowed length = p + 4
 -  // where 4 comes from 'e+n' and '.'.
 -  //
 -  // Length of fixed format = 2 + y + p
 -  // where 2 comes from '0.' and y = # of leading zeroes.
 -  //
 -  // Equating the two and solving for y yields y = 2, or 0.00xxxx which is
 -  // 1.0e-3.
 -  //
 -  // Since the behavior of toPrecision() is identical for larger numbers, we
 -  // don't have to worry about the other bound.
 -  //
 -  // Finally, the argument for toExponential() is the number of trailing digits,
 -  // so we take off 1 for the value before the '.'.
 -  return (Math.abs(x) < 1.0e-3 && x != 0.0) ?
 -      x.toExponential(p - 1) : x.toPrecision(p);
 -};
 -
  // Various default values
  Dygraph.DEFAULT_ROLL_PERIOD = 1;
  Dygraph.DEFAULT_WIDTH = 480;
@@@ -100,10 -153,11 +100,10 @@@ Dygraph.DEFAULT_ATTRS = 
    labelsKMG2: false,
    showLabelsOnHighlight: true,
  
 -  yValueFormatter: function(x, opt_precision) {
 -    var s = Dygraph.floatFormat(x, opt_precision);
 -    var s2 = Dygraph.intFormat(x);
 -    return s.length < s2.length ? s : s2;
 -  },
 +  yValueFormatter: function(a,b) { return Dygraph.numberFormatter(a,b); },
 +  digitsAfterDecimal: 2,
 +  maxNumberWidth: 6,
 +  sigFigs: null,
  
    strokeWidth: 1.0,
  
@@@ -213,6 -267,20 +213,6 @@@ Dygraph.prototype.__init__ = function(d
    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.numYDigits_ = 2;
 -
 -  // When labeling x (if numeric) or y values in the legend, there are
 -  // numDigits + numExtraDigits of precision used.  For axes labels with N
 -  // digits of precision, the data should be displayed with at least N+1 digits
 -  // of precision. The reason for this is to divide each interval between
 -  // successive ticks into tenths (for 1) or hundredths (for 2), etc.  For
 -  // example, if the labels are [0, 1, 2], we want data to be displayed as
 -  // 0.1, 1.3, etc.
 -  this.numExtraDigits_ = 1;
 -
    // Clear the div. This ensure that, if multiple dygraphs are passed the same
    // div, then only one will be drawn.
    div.innerHTML = "";
@@@ -1631,15 -1699,17 +1631,16 @@@ Dygraph.prototype.generateLegendHTML_ 
      var labels = this.attr_('labels');
      var html = '';
      for (var i = 1; i < labels.length; i++) {
-       var c = new RGBColor(this.plotter_.colors[labels[i]]);
-       if (i > 1) html += (sepLines ? '<br/>' : ' ');
-       html += "<b><font color='" + c.toHex() + "'>&mdash;" + labels[i] +
-         "</font></b>";
+       if (!this.visibility()[i - 1]) continue;
+       var c = this.plotter_.colors[labels[i]];
+       if (html != '') html += (sepLines ? '<br/>' : ' ');
+       html += "<b><span style='color: " + c + ";'>&mdash;" + labels[i] +
+         "</span></b>";
      }
      return html;
    }
  
 -  var displayDigits = this.numXDigits_ + this.numExtraDigits_;
 -  var html = this.attr_('xValueFormatter')(x, displayDigits) + ":";
 +  var html = this.attr_('xValueFormatter')(x) + ":";
  
    var fmtFunc = this.attr_('yValueFormatter');
    var showZeros = this.attr_("labelsShowZeroValues");
      if (!Dygraph.isOK(pt.canvasy)) continue;
      if (sepLines) html += "<br/>";
  
-     var c = new RGBColor(this.plotter_.colors[pt.name]);
-     var yval = fmtFunc(pt.yval, this);
+     var c = this.plotter_.colors[pt.name];
+     var yval = fmtFunc(pt.yval, displayDigits);
      // TODO(danvk): use a template string here and make it an attribute.
-     html += " <b><font color='" + c.toHex() + "'>"
-       + pt.name + "</font></b>:"
+     html += " <b><span style='color: " + c + ";'>"
+       + pt.name + "</span></b>:"
        + yval;
    }
    return html;
@@@ -1741,7 -1811,6 +1742,6 @@@ Dygraph.prototype.setSelection = functi
      this.lastx_ = this.selPoints_[0].xval;
      this.updateSelection_();
    } else {
-     this.lastx_ = -1;
      this.clearSelection();
    }
  
@@@ -1791,80 -1860,11 +1791,80 @@@ Dygraph.prototype.getSelection = functi
      }
    }
    return -1;
 -}
 +};
 +
 +/**
 + * Number formatting function which mimicks the behavior of %g in printf, i.e.
 + * either exponential or fixed format (without trailing 0s) is used depending on
 + * the length of the generated string.  The advantage of this format is that
 + * there is a predictable upper bound on the resulting string length,
 + * significant figures are not dropped, and normal numbers are not displayed in
 + * exponential notation.
 + *
 + * NOTE: JavaScript's native toPrecision() is NOT a drop-in replacement for %g.
 + * It creates strings which are too long for absolute values between 10^-4 and
 + * 10^-6, e.g. '0.00001' instead of '1e-5'. See tests/number-format.html for
 + * output examples.
 + *
 + * @param {Number} x The number to format
 + * @param {Number} opt_precision The precision to use, default 2.
 + * @return {String} A string formatted like %g in printf.  The max generated
 + *                  string length should be precision + 6 (e.g 1.123e+300).
 + */
 +Dygraph.floatFormat = function(x, opt_precision) {
 +  // Avoid invalid precision values; [1, 21] is the valid range.
 +  var p = Math.min(Math.max(1, opt_precision || 2), 21);
 +
 +  // This is deceptively simple.  The actual algorithm comes from:
 +  //
 +  // Max allowed length = p + 4
 +  // where 4 comes from 'e+n' and '.'.
 +  //
 +  // Length of fixed format = 2 + y + p
 +  // where 2 comes from '0.' and y = # of leading zeroes.
 +  //
 +  // Equating the two and solving for y yields y = 2, or 0.00xxxx which is
 +  // 1.0e-3.
 +  //
 +  // Since the behavior of toPrecision() is identical for larger numbers, we
 +  // don't have to worry about the other bound.
 +  //
 +  // Finally, the argument for toExponential() is the number of trailing digits,
 +  // so we take off 1 for the value before the '.'.
 +  return (Math.abs(x) < 1.0e-3 && x != 0.0) ?
 +      x.toExponential(p - 1) : x.toPrecision(p);
 +};
 +
 +/**
 + * Return a string version of a number. This respects the digitsAfterDecimal
 + * and maxNumberWidth options.
 + * @param {Number} x The number to be formatted
 + * @param {Dygraph} g The dygraph object
 + */
 +Dygraph.numberFormatter = function(x, g) {
 +  var sigFigs = g.attr_('sigFigs');
 +
 +  if (sigFigs !== null) {
 +    // User has opted for a fixed number of significant figures.
 +    return Dygraph.floatFormat(x, sigFigs);
 +  }
 +
 +  var digits = g.attr_('digitsAfterDecimal');
 +  var maxNumberWidth = g.attr_('maxNumberWidth');
 +
 +  // switch to scientific notation if we underflow or overflow fixed display.
 +  if (x !== 0.0 &&
 +      (Math.abs(x) >= Math.pow(10, maxNumberWidth) ||
 +       Math.abs(x) < Math.pow(10, -digits))) {
 +    return x.toExponential(digits);
 +  } else {
 +    return '' + Dygraph.round_(x, digits);
 +  }
 +};
  
  Dygraph.zeropad = function(x) {
    if (x < 10) return "0" + x; else return "" + x;
 -}
 +};
  
  /**
   * Return a string version of the hours, minutes and seconds portion of a date.
@@@ -1882,7 -1882,7 +1882,7 @@@ Dygraph.hmsString_ = function(date) 
    } else {
      return zeropad(d.getHours()) + ":" + zeropad(d.getMinutes());
    }
 -}
 +};
  
  /**
   * Convert a JS date to a string appropriate to display on an axis that
@@@ -1905,7 -1905,7 +1905,7 @@@ Dygraph.dateAxisFormatter = function(da
        return Dygraph.hmsString_(date.getTime());
      }
    }
 -}
 +};
  
  /**
   * Convert a JS date (millis since epoch) to YYYY/MM/DD
@@@ -1932,18 -1932,6 +1932,18 @@@ Dygraph.dateString_ = function(date) 
  };
  
  /**
 + * Round a number to the specified number of digits past the decimal point.
 + * @param {Number} num The number to round
 + * @param {Number} places The number of decimals to which to round
 + * @return {Number} The rounded number
 + * @private
 + */
 +Dygraph.round_ = function(num, places) {
 +  var shift = Math.pow(10, places);
 +  return Math.round(num * shift)/shift;
 +};
 +
 +/**
   * Fires when there's data available to be graphed.
   * @param {String} data Raw CSV data to be plotted
   * @private
@@@ -1970,7 -1958,20 +1970,7 @@@ Dygraph.prototype.addXTicks_ = function
      range = [this.rawData_[0][0], this.rawData_[this.rawData_.length - 1][0]];
    }
  
 -  var formatter = this.attr_('xTicker');
 -  var ret = formatter(range[0], range[1], this);
 -  var xTicks = [];
 -
 -  // Note: numericTicks() returns a {ticks: [...], numDigits: yy} dictionary,
 -  // whereas dateTicker and user-defined tickers typically just return a ticks
 -  // array.
 -  if (ret.ticks !== undefined) {
 -    xTicks = ret.ticks;
 -    this.numXDigits_ = ret.numDigits;
 -  } else {
 -    xTicks = ret;
 -  }
 -
 +  var xTicks = this.attr_('xTicker')(range[0], range[1], this);
    this.layout_.updateOptions({xTicks: xTicks});
  };
  
@@@ -2218,6 -2219,43 +2218,6 @@@ Dygraph.binarySearch = function(val, ar
  };
  
  /**
 - * Determine the number of significant figures in a Number up to the specified
 - * precision.  Note that there is no way to determine if a trailing '0' is
 - * significant or not, so by convention we return 1 for all of the following
 - * inputs: 1, 1.0, 1.00, 1.000 etc.
 - * @param {Number} x The input value.
 - * @param {Number} opt_maxPrecision Optional maximum precision to consider.
 - *                                  Default and maximum allowed value is 13.
 - * @return {Number} The number of significant figures which is >= 1.
 - */
 -Dygraph.significantFigures = function(x, opt_maxPrecision) {
 -  var precision = Math.max(opt_maxPrecision || 13, 13);
 -
 -  // Convert the number to its exponential notation form and work backwards,
 -  // ignoring the 'e+xx' bit.  This may seem like a hack, but doing a loop and
 -  // dividing by 10 leads to roundoff errors.  By using toExponential(), we let
 -  // the JavaScript interpreter handle the low level bits of the Number for us.
 -  var s = x.toExponential(precision);
 -  var ePos = s.lastIndexOf('e');  // -1 case handled by return below.
 -
 -  for (var i = ePos - 1; i >= 0; i--) {
 -    if (s[i] == '.') {
 -      // Got to the decimal place.  We'll call this 1 digit of precision because
 -      // we can't know for sure how many trailing 0s are significant.
 -      return 1;
 -    } else if (s[i] != '0') {
 -      // Found the first non-zero digit.  Return the number of characters
 -      // except for the '.'.
 -      return i;  // This is i - 1 + 1 (-1 is for '.', +1 is for 0 based index).
 -    }
 -  }
 -
 -  // Occurs if toExponential() doesn't return a string containing 'e', which
 -  // should never happen.
 -  return 1;
 -};
 -
 -/**
   * Add ticks when the x axis has numbers on it (instead of dates)
   * TODO(konigsberg): Update comment.
   *
@@@ -2341,18 -2379,27 +2341,18 @@@ Dygraph.numericTicks = function(minV, m
    var formatter = attr('yAxisLabelFormatter') ?
        attr('yAxisLabelFormatter') : attr('yValueFormatter');
  
 -  // Determine the number of decimal places needed for the labels below by
 -  // taking the maximum number of significant figures for any label.  We must
 -  // take the max because we can't tell if trailing 0s are significant.
 -  var numDigits = 0;
 -  for (var i = 0; i < ticks.length; i++) {
 -    numDigits = Math.max(Dygraph.significantFigures(ticks[i].v), numDigits);
 -  }
 -
    // Add labels to the ticks.
    for (var i = 0; i < ticks.length; i++) {
      if (ticks[i].label !== undefined) continue;  // Use current label.
      var tickV = ticks[i].v;
      var absTickV = Math.abs(tickV);
 -    var label = (formatter !== undefined) ?
 -        formatter(tickV, numDigits) : tickV.toPrecision(numDigits);
 +    var label = formatter(tickV, self);
      if (k_labels.length > 0) {
        // Round up to an appropriate unit.
        var n = k*k*k*k;
        for (var j = 3; j >= 0; j--, n /= k) {
          if (absTickV >= n) {
 -          label = formatter(tickV / n, numDigits) + k_labels[j];
 +          label = Dygraph.round_(tickV / n, 1) + k_labels[j];
            break;
          }
        }
      ticks[i].label = label;
    }
  
 -  return {ticks: ticks, numDigits: numDigits};
 +  return ticks;
  };
  
  // Computes the range of the data series (including confidence intervals).
@@@ -2582,6 -2629,13 +2582,13 @@@ Dygraph.prototype.drawGraph_ = function
    if (is_initial_draw) {
      // Generate a static legend before any particular point is selected.
      this.attr_('labelsDiv').innerHTML = this.generateLegendHTML_();
+   } else {
+     if (typeof(this.selPoints_) !== 'undefined' && this.selPoints_.length) {
+       this.lastx_ = this.selPoints_[0].xval;
+       this.updateSelection_();
+     } else {
+       this.clearSelection();
+     }
    }
  
    if (this.attr_("drawCallback") !== null) {
@@@ -2796,11 -2850,13 +2803,11 @@@ Dygraph.prototype.computeYAxisRanges_ 
      // primary axis. However, if an axis is specifically marked as having
      // independent ticks, then that is permissible as well.
      if (i == 0 || axis.independentTicks) {
 -      var ret =
 +      axis.ticks =
          Dygraph.numericTicks(axis.computedValueRange[0],
                               axis.computedValueRange[1],
                               this,
                               axis);
 -      axis.ticks = ret.ticks;
 -      this.numYDigits_ = ret.numDigits;
      } else {
        var p_axis = this.axes_[0];
        var p_ticks = p_axis.ticks;
          tick_values.push(y_val);
        }
  
 -      var ret =
 +      axis.ticks =
          Dygraph.numericTicks(axis.computedValueRange[0],
                               axis.computedValueRange[1],
                               this, axis, tick_values);
 -      axis.ticks = ret.ticks;
 -      this.numYDigits_ = ret.numDigits;
      }
    }
  };
@@@ -3014,7 -3072,7 +3021,7 @@@ Dygraph.prototype.detectTypeFromString
      this.attrs_.xTicker = Dygraph.dateTicker;
      this.attrs_.xAxisLabelFormatter = Dygraph.dateAxisFormatter;
    } else {
 -    this.attrs_.xValueFormatter = this.attrs_.yValueFormatter;
 +    this.attrs_.xValueFormatter = function(x) { return x; };
      this.attrs_.xValueParser = function(x) { return parseFloat(x); };
      this.attrs_.xTicker = Dygraph.numericTicks;
      this.attrs_.xAxisLabelFormatter = this.attrs_.xValueFormatter;
@@@ -3138,10 -3196,21 +3145,21 @@@ Dygraph.prototype.parseCSV_ = function(
      } else if (this.attr_("customBars")) {
        // Bars are a low;center;high tuple
        for (var j = 1; j < inFields.length; j++) {
-         var vals = inFields[j].split(";");
-         fields[j] = [ this.parseFloat_(vals[0], i, line),
-                       this.parseFloat_(vals[1], i, line),
-                       this.parseFloat_(vals[2], i, line) ];
+         var val = inFields[j];
+         if (/^ *$/.test(val)) {
+           fields[j] = [null, null, null];
+         } else {
+           var vals = val.split(";");
+           if (vals.length == 3) {
+             fields[j] = [ this.parseFloat_(vals[0], i, line),
+                           this.parseFloat_(vals[1], i, line),
+                           this.parseFloat_(vals[2], i, line) ];
+           } else {
+             this.warning('When using customBars, values must be either blank ' +
+                          'or "low;center;high" tuples (got "' + val +
+                          '" on line ' + (1+i));
+           }
+         }
        }
      } else {
        // Values are just numbers
@@@ -3237,7 -3306,7 +3255,7 @@@ Dygraph.prototype.parseArray_ = functio
      return parsedData;
    } else {
      // Some intelligent defaults for a numeric x-axis.
 -    this.attrs_.xValueFormatter = this.attrs_.yValueFormatter;
 +    this.attrs_.xValueFormatter = function(x) { return x; };
      this.attrs_.xTicker = Dygraph.numericTicks;
      return data;
    }
@@@ -3263,7 -3332,7 +3281,7 @@@ Dygraph.prototype.parseDataTable_ = fun
      this.attrs_.xTicker = Dygraph.dateTicker;
      this.attrs_.xAxisLabelFormatter = Dygraph.dateAxisFormatter;
    } else if (indepType == 'number') {
 -    this.attrs_.xValueFormatter = this.attrs_.yValueFormatter;
 +    this.attrs_.xValueFormatter = function(x) { return x; };
      this.attrs_.xValueParser = function(x) { return parseFloat(x); };
      this.attrs_.xTicker = Dygraph.numericTicks;
      this.attrs_.xAxisLabelFormatter = this.attrs_.xValueFormatter;