add back Dygraph.floatFormat
authorDan Vanderkam <dan@dygraphs.com>
Sun, 24 Apr 2011 05:16:32 +0000 (01:16 -0400)
committerDan Vanderkam <dan@dygraphs.com>
Sun, 24 Apr 2011 05:16:32 +0000 (01:16 -0400)
dygraph.js

index 377e830..698d8da 100644 (file)
@@ -103,6 +103,7 @@ Dygraph.DEFAULT_ATTRS = {
   yValueFormatter: function(a,b) { return Dygraph.numberFormatter(a,b); },
   digitsAfterDecimal: 2,
   maxNumberWidth: 6,
+  sigFigs: null,
 
   strokeWidth: 1.0,
 
@@ -1792,8 +1793,46 @@ Dygraph.prototype.getSelection = function() {
   return -1;
 };
 
-Dygraph.zeropad = function(x) {
-  if (x < 10) return "0" + x; else return "" + x;
+/**
+ * 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);
 };
 
 /**
@@ -1803,17 +1842,30 @@ Dygraph.zeropad = function(x) {
  * @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');
 
-  if (Math.abs(x) >= Math.pow(10, maxNumberWidth) ||
-      Math.abs(x) < Math.pow(10, -digits)) {
-    // switch to scientific notation.
+  // 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.
  * @param {Number} date The JavaScript date (ms since epoch)