Fix NaN handling for stacked graphs.
authorKlaus Weidner <klausw@google.com>
Tue, 13 Mar 2012 21:46:46 +0000 (14:46 -0700)
committerKlaus Weidner <klausw@google.com>
Tue, 13 Mar 2012 21:46:46 +0000 (14:46 -0700)
Dygraph was handling NaNs inconsistently, drawing lines and shaded areas
for values encountered while stacking until it ran into a NaN, then
stopping at that point due to NaN contagion, with the effect that it
drew some lines and partial gaps.

Change this to create a clean gap in the data by propagatng the NaN contagion
in both directions.

Note that users can preprocess the data to replace NaNs with zeros if
they prefer that, but this should not happen by default since it's
irreversible and can lead to confusing graphs that don't match actual
data.

auto_tests/tests/callback.js
dygraph.js

index 9551eb0..8c054a9 100644 (file)
@@ -309,6 +309,71 @@ CallbackTestCase.prototype.testNaNData = function() {
   assertEquals('c', res.seriesName);
 };
 
+/**
+ * This tests that stacked point searches work for data containing NaNs.
+ */
+CallbackTestCase.prototype.testNaNDataStack = function() {
+  var dataNaN = [
+    [9, -1, NaN, NaN],
+    [10, -1, 1, 2],
+    [11, 0, 3, 1],
+    [12, 1, NaN, 2],
+    [13, 0, 2, 3],
+    [14, -1, 1, 4],
+    [15, 0, 2, NaN],
+    [16, 1, 1, 3],
+    [17, 1, NaN, 3],
+    [18, 0, 2, 5],
+    [19, 0, 1, 4]];
+
+  var h_row;
+  var h_pts;
+
+  var highlightCallback  =  function(e, x, pts, row) {
+    h_row = row;
+    h_pts = pts;
+  };
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, dataNaN,
+      {
+        width: 600,
+        height: 400,
+        labels: ['x', 'a', 'b', 'c'],
+        visibility: [false, true, true],
+        stackedGraph: true,
+        highlightCallback: highlightCallback
+      });
+
+  DygraphOps.dispatchMouseMove(g, 10.1, 0.9);
+  //check correct row is returned
+  assertEquals(1, h_row);
+
+  // Explicitly test stacked point algorithm.
+  var dom = g.toDomCoords(10.1, 0.9);
+  var res = g.findStackedPoint(dom[0], dom[1]);
+  assertEquals(1, res.row);
+  assertEquals('c', res.seriesName);
+
+  // First gap, no data due to NaN contagion.
+  dom = g.toDomCoords(12.1, 0.9);
+  res = g.findStackedPoint(dom[0], dom[1]);
+  assertEquals(3, res.row);
+  assertEquals(undefined, res.seriesName);
+
+  // Second gap, no data due to NaN contagion.
+  dom = g.toDomCoords(15.1, 0.9);
+  res = g.findStackedPoint(dom[0], dom[1]);
+  assertEquals(6, res.row);
+  assertEquals(undefined, res.seriesName);
+
+  // Isolated points should work, finding series b in this case.
+  dom = g.toDomCoords(15.9, 3.1);
+  res = g.findStackedPoint(dom[0], dom[1]);
+  assertEquals(7, res.row);
+  assertEquals('b', res.seriesName);
+};
+
 CallbackTestCase.prototype.testGapHighlight = function() {
 var dataGap = [
     [1, null, 3],
index 2045e9e..79b53a9 100644 (file)
@@ -2318,6 +2318,26 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) {
     datasets[i] = series;
   }
 
+  // For stacked graphs, a NaN value for any point in the sum should create a
+  // clean gap in the graph. Back-propagate NaNs to all points at this X value.
+  if (this.attr_("stackedGraph")) {
+    for (k = datasets.length - 1; k >= 0; --k) {
+      // Use the first nonempty dataset to get X values.
+      if (!datasets[k]) continue;
+      for (j = 0; j < datasets[k].length; j++) {
+        var x = datasets[k][j][0];
+        if (isNaN(cumulative_y[x])) {
+          // Set all Y values to NaN at that X value.
+          for (i = datasets.length - 1; i >= 0; i--) {
+            if (!datasets[i]) continue;
+            datasets[i][j][1] = NaN;
+          }
+        }
+      }
+      break;
+    }
+  }
+
   return [ datasets, extremes, boundaryIds ];
 };