From 7d463f49a254b7d70e79ba4578333f3b185df959 Mon Sep 17 00:00:00 2001 From: Klaus Weidner Date: Tue, 13 Mar 2012 14:46:46 -0700 Subject: [PATCH] Fix NaN handling for stacked graphs. 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 | 65 ++++++++++++++++++++++++++++++++++++++++++++ dygraph.js | 20 ++++++++++++++ 2 files changed, 85 insertions(+) diff --git a/auto_tests/tests/callback.js b/auto_tests/tests/callback.js index 9551eb0..8c054a9 100644 --- a/auto_tests/tests/callback.js +++ b/auto_tests/tests/callback.js @@ -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], diff --git a/dygraph.js b/dygraph.js index 2045e9e..79b53a9 100644 --- a/dygraph.js +++ b/dygraph.js @@ -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 ]; }; -- 2.7.4