Move KMB/KMG2 formatting into default formatter function (issue 414)
authorDan Vanderkam <danvdk@gmail.com>
Sun, 17 Feb 2013 03:27:18 +0000 (22:27 -0500)
committerDan Vanderkam <danvdk@gmail.com>
Sun, 17 Feb 2013 03:27:18 +0000 (22:27 -0500)
auto_tests/tests/axis_labels.js
dygraph-options.js
dygraph-tickers.js
dygraph-utils.js
dygraph.js

index 7ad62a7..787a3c3 100644 (file)
@@ -703,3 +703,50 @@ AxisLabelsTestCase.prototype.testAxisLabelColorNull = function() {
   assertColor($(".dygraph-axis-label-x"), "rgb(0, 0, 0)");
   assertColor($(".dygraph-axis-label-y"), "rgb(0, 0, 0)");
 }
+
+/*
+ * This test shows that the label formatter overrides labelsKMB for all values.
+ */
+AxisLabelsTestCase.prototype.testLabelFormatterOverridesLabelsKMB = function() {
+  var g = new Dygraph(
+      document.getElementById("graph"),
+      "X,a,b\n" +
+      "1,0,2000\n" +
+      "2,500,1500\n" +
+      "3,1000,1000\n" +
+      "4,2000,0\n", {
+        labelsKMB: true,
+        axisLabelFormatter: function (v) {
+          return v + ":X";
+        }
+      });
+  assertEquals(["0:X","500:X","1000:X","1500:X","2000:X"], Util.getYLabels());
+  assertEquals(["1:X","1.5:X","2:X","2.5:X","3:X","3.5:X"], Util.getXLabels());
+}
+
+/*
+ * This test shows that you can override labelsKMB on the axis level.
+ */
+AxisLabelsTestCase.prototype.testLabelsKMBIgnoredWhenOverridden = function() {
+  g = new Dygraph(
+      document.getElementById("graph"),
+      "x,a,b\n" +
+      "1,0,2000\n" +
+      "2,500,1500\n" +
+      "3,1000,1000\n" +
+      "4,2000,0\n", {
+        labelsKMB: true,
+        axes: {
+          y2: {
+            labelsKMB: false
+          }
+        },
+        series: {
+          b: {
+            axis: "y2"
+          },
+        }
+      });
+  assertEquals(["0","500","1K","1.5K","2K"], Util.getYLabels(1));
+  assertEquals(["0","500","1000","1500","2000"], Util.getYLabels(2));
+};
index f287ef1..a734b1d 100644 (file)
@@ -84,7 +84,7 @@ DygraphOptions.axisToIndex_ = function(axis) {
 
 /**
  * Reparses options that are all related to series. This typically occurs when
- * options are either updated, or source data has been made avaialble.
+ * options are either updated, or source data has been made available.
  *
  * TODO(konigsberg): The method name is kind of weak; fix.
  */
index 77f2f62..0e18394 100644 (file)
@@ -87,16 +87,6 @@ Dygraph.numericLinearTicks = function(a, b, pixels, opts, dygraph, vals) {
 
 /** @type {Dygraph.Ticker} */
 Dygraph.numericTicks = function(a, b, pixels, opts, dygraph, vals) {
-  // This masks some numeric issues in older versions of Firefox,
-  // where 1.0/Math.pow(10,2) != Math.pow(10,-2).
-  /** @type {function(number,number):number} */
-  var pow = function(base, exp) {
-    if (exp < 0) {
-      return 1.0 / Math.pow(base, -exp);
-    }
-    return Math.pow(base, exp);
-  };
-
   var pixels_per_tick = /** @type{number} */(opts('pixelsPerLabel'));
   var ticks = [];
   var i, j, tickV, nTicks;
@@ -201,58 +191,13 @@ Dygraph.numericTicks = function(a, b, pixels, opts, dygraph, vals) {
     }
   }
 
-  // Add formatted labels to the ticks.
-  var k;
-  var k_labels = [];
-  var m_labels = [];
-  if (opts("labelsKMB")) {
-    k = 1000;
-    k_labels = [ "K", "M", "B", "T", "Q" ];
-  }
-  if (opts("labelsKMG2")) {
-    if (k) Dygraph.warn("Setting both labelsKMB and labelsKMG2. Pick one!");
-    k = 1024;
-    k_labels = [ "k", "M", "G", "T", "P", "E", "Z", "Y" ];
-    m_labels = [ "m", "u", "n", "p", "f", "a", "z", "y" ];
-  }
-
-  k = k || 1; // If neither option is specified.
-
   var formatter = /**@type{AxisLabelFormatter}*/(opts('axisLabelFormatter'));
 
   // Add labels to the ticks.
-  var digitsAfterDecimal = /** @type{number} */(opts('digitsAfterDecimal'));
   for (i = 0; i < ticks.length; i++) {
     if (ticks[i].label !== undefined) continue;  // Use current label.
-    tickV = ticks[i].v;
-    var absTickV = Math.abs(tickV);
     // TODO(danvk): set granularity to something appropriate here.
-    var label = formatter(tickV, 0, opts, dygraph);
-    if (k_labels.length > 0) {
-      // TODO(danvk): should this be integrated into the axisLabelFormatter?
-      // Round up to an appropriate unit.
-      var n = pow(k, k_labels.length);
-      for (j = k_labels.length - 1; j >= 0; j--, n /= k) {
-        if (absTickV >= n) {
-          label = Dygraph.round_(tickV / n, digitsAfterDecimal) + k_labels[j];
-          break;
-        }
-      }
-    }
-    if(opts("labelsKMG2")){
-      tickV = String(tickV.toExponential());
-      if(tickV.split('e-').length === 2 && tickV.split('e-')[1] >= 3 && tickV.split('e-')[1] <= 24){
-        if(tickV.split('e-')[1] % 3 > 0) {
-          label = Dygraph.round_(tickV.split('e-')[0] /
-              pow(10,(tickV.split('e-')[1] % 3)),
-              digitsAfterDecimal);
-        } else {
-          label = Number(tickV.split('e-')[0]).toFixed(2);
-        }
-        label += m_labels[Math.floor(tickV.split('e-')[1] / 3) - 1];
-      }
-    }
-    ticks[i].label = label;
+    ticks[i].label = formatter(ticks[i].v, 0, opts, dygraph);
   }
 
   return ticks;
index 0554204..ffedb27 100644 (file)
@@ -1233,3 +1233,15 @@ Dygraph.isElementContainedBy = function(containee, container) {
   }
   return (containee === container);
 };
+
+
+// This masks some numeric issues in older versions of Firefox,
+// where 1.0/Math.pow(10,2) != Math.pow(10,-2).
+/** @type {function(number,number):number} */
+Dygraph.pow = function(base, exp) {
+  if (exp < 0) {
+    return 1.0 / Math.pow(base, -exp);
+  }
+  return Math.pow(base, exp);
+};
+
index 1d247ac..458ec45 100644 (file)
@@ -95,6 +95,10 @@ Dygraph.DEFAULT_HEIGHT = 320;
 Dygraph.ANIMATION_STEPS = 12;
 Dygraph.ANIMATION_DURATION = 200;
 
+Dygraph.KMB_LABELS = [ 'K', 'M', 'B', 'T', 'Q' ];
+Dygraph.KMG2_BIG_LABELS = [ 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y' ];
+Dygraph.KMG2_SMALL_LABELS = [ 'm', 'u', 'n', 'p', 'f', 'a', 'z', 'y' ];
+
 // These are defined before DEFAULT_ATTRS so that it can refer to them.
 /**
  * @private
@@ -116,14 +120,60 @@ Dygraph.numberValueFormatter = function(x, opts, pt, g) {
   var digits = opts('digitsAfterDecimal');
   var maxNumberWidth = opts('maxNumberWidth');
 
+  var kmb = opts('labelsKMB');
+  var kmg2 = opts('labelsKMG2');
+
+  var label;
+
   // 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);
+    label = x.toExponential(digits);
   } else {
-    return '' + Dygraph.round_(x, digits);
+    label = '' + Dygraph.round_(x, digits);
+  }
+
+  if (kmb || kmg2) {
+    var k;
+    var k_labels = [];
+    var m_labels = [];
+    if (kmb) {
+      k = 1000;
+      k_labels = [ "K", "M", "B", "T", "Q" ];
+    }
+    if (kmg2) {
+      if (kmb) Dygraph.warn("Setting both labelsKMB and labelsKMG2. Pick one!");
+      k = 1024;
+      k_labels = [ "k", "M", "G", "T", "P", "E", "Z", "Y" ];
+      m_labels = [ "m", "u", "n", "p", "f", "a", "z", "y" ];
+    }
+
+    var absx = Math.abs(x);
+    var n = Dygraph.pow(k, k_labels.length);
+    for (var j = k_labels.length - 1; j >= 0; j--, n /= k) {
+      if (absx >= n) {
+        label = Dygraph.round_(x / n, digits) + k_labels[j];
+        break;
+      }
+    }
+    if (kmg2) {
+      // TODO(danvk): clean up this logic. Why so different than kmb?
+      var x_parts = String(x.toExponential()).split('e-');
+      if (x_parts.length === 2 && x_parts[1] >= 3 && x_parts[1] <= 24) {
+        if (x_parts[1] % 3 > 0) {
+          label = Dygraph.round_(x_parts[0] /
+              Dygraph.pow(10, (x_parts[1] % 3)),
+              digits);
+        } else {
+          label = Number(x_parts[0]).toFixed(2);
+        }
+        label += m_labels[Math.floor(x_parts[1] / 3) - 1];
+      }
+    }
   }
+
+  return label;
 };
 
 /**
@@ -612,7 +662,7 @@ Dygraph.prototype.optionsViewForAxis_ = function(axis) {
   var self = this;
   return function(opt) {
     var axis_opts = self.user_attrs_.axes;
-    if (axis_opts && axis_opts[axis] && axis_opts[axis][opt]) {
+    if (axis_opts && axis_opts[axis] && axis_opts[axis].hasOwnProperty(opt)) {
       return axis_opts[axis][opt];
     }
     // user-specified attributes always trump defaults, even if they're less
@@ -622,7 +672,7 @@ Dygraph.prototype.optionsViewForAxis_ = function(axis) {
     }
 
     axis_opts = self.attrs_.axes;
-    if (axis_opts && axis_opts[axis] && axis_opts[axis][opt]) {
+    if (axis_opts && axis_opts[axis] && axis_opts[axis].hasOwnProperty(opt)) {
       return axis_opts[axis][opt];
     }
     // check old-style axis options