fix some range calculation bugs with error bars, gaps with error bars
authorDan Vanderkam <danvdk@gmail.com>
Sat, 28 Nov 2009 20:53:49 +0000 (15:53 -0500)
committerDan Vanderkam <danvdk@gmail.com>
Sat, 28 Nov 2009 20:53:49 +0000 (15:53 -0500)
dygraph-canvas.js
dygraph.js
tests/label-div.html
tests/missing-data.html
tests/native-format.html

index 1c5b502..9bf7512 100644 (file)
@@ -204,6 +204,10 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
       var errorTrapezoid = function(ctx_,point) {
         count++;
         if (point.name == setName) {
+          if (!point.y || isNaN(point.y)) {
+            prevX = -1;
+            return;
+          }
           var newYs = [ point.y - point.errorPlus * yscale,
                         point.y + point.errorMinus * yscale ];
           newYs[0] = this.area.h * newYs[0] + this.area.y;
index 30ea8f6..ef06082 100644 (file)
@@ -962,6 +962,46 @@ Dygraph.prototype.addYTicks_ = function(minY, maxY) {
                                 yTicks: ticks } );
 };
 
+// Computes the range of the data series (including confidence intervals).
+// series is either [ [x1, y1], [x2, y2], ... ] or
+// [ [x1, [y1, dev_low, dev_high]], [x2, [y2, dev_low, dev_high]], ...
+// Returns [low, high]
+Dygraph.prototype.extremeValues_ = function(series) {
+  var minY = null, maxY = null;
+
+  var bars = this.attr_("errorBars") || this.customBars_;
+  if (bars) {
+    // With custom bars, maxY is the max of the high values.
+    for (var j = 0; j < series.length; j++) {
+      var y = series[j][1][0];
+      if (!y) continue;
+      var low = y - series[j][1][1];
+      var high = y + series[j][1][2];
+      if (low > y) low = y;    // this can happen with custom bars,
+      if (high < y) high = y;  // e.g. in tests/custom-bars.html
+      if (maxY == null || high > maxY) {
+        maxY = high;
+      }
+      if (minY == null || low < minY) {
+        minY = low;
+      }
+    }
+  } else {
+    for (var j = 0; j < series.length; j++) {
+      var y = series[j][1];
+      if (!y) continue;
+      if (maxY == null || y > maxY) {
+        maxY = y;
+      }
+      if (minY == null || y < minY) {
+        minY = y;
+      }
+    }
+  }
+
+  return [minY, maxY];
+};
+
 /**
  * Update the graph with new data. Data is in the format
  * [ [date1, val1, val2, ...], [date2, val1, val2, ...] if errorBars=false
@@ -993,41 +1033,15 @@ Dygraph.prototype.drawGraph_ = function(data) {
       for (var k = 0; k < series.length; k++) {
         if (series[k][0] >= low && series[k][0] <= high) {
           pruned.push(series[k]);
-          var y = bars ? series[k][1][0] : series[k][1];
-          if (!y) continue;
-          if (maxY == null || y > maxY) maxY = y;
-          if (minY == null || y < minY) minY = y;
         }
       }
       series = pruned;
-    } else {
-      if (!this.customBars_) {
-        for (var j = 0; j < series.length; j++) {
-          var y = bars ? series[j][1][0] : series[j][1];
-          if (!y) continue;
-          if (maxY == null || y > maxY) {
-            maxY = bars ? y + series[j][1][1] : y;
-          }
-          if (minY == null || y < minY) {
-            minY = bars ? y + series[j][1][1] : y;
-          }
-        }
-      } else {
-        // With custom bars, maxY is the max of the high values.
-        for (var j = 0; j < series.length; j++) {
-          var y = series[j][1][0];
-          var high = series[j][1][2];
-          if (high > y) y = high;
-          if (maxY == null || y > maxY) {
-            maxY = y;
-          }
-          if (minY == null || y < minY) {
-            minY = y;
-          }
-        }
-      }
     }
 
+    [thisMinY, thisMaxY] = this.extremeValues_(series);
+    if (!minY || thisMinY < minY) minY = thisMinY;
+    if (!maxY || thisMaxY > maxY) maxY = thisMaxY;
+
     if (bars) {
       var vals = [];
       for (var j=0; j<series.length; j++)
@@ -1152,14 +1166,14 @@ Dygraph.prototype.rollingAverage = function(originalData, rollPeriod) {
                                               1.0 * (high - mid) / count ]];
     }
   } else {
-    if (rollPeriod == 1) {
-      return originalData;
-    }
-
     // Calculate the rolling average for the first rollPeriod - 1 points where
     // there is not enough data to roll over the full number of days
     var num_init_points = Math.min(rollPeriod - 1, originalData.length - 2);
     if (!this.attr_("errorBars")){
+      if (rollPeriod == 1) {
+        return originalData;
+      }
+
       for (var i = 0; i < num_init_points; i++) {
         var sum = 0;
         for (var j = 0; j < i + 1; j++)
@@ -1179,13 +1193,21 @@ Dygraph.prototype.rollingAverage = function(originalData, rollPeriod) {
       for (var i = 0; i < num_init_points; i++) {
         var sum = 0;
         var variance = 0;
+        var num_ok = 0;
         for (var j = 0; j < i + 1; j++) {
-          sum += originalData[j][1][0];
+          var y = originalData[j][1][0];
+          if (!y || isNaN(y)) continue;
+          num_ok++;
+          sum += y;
           variance += Math.pow(originalData[j][1][1], 2);
         }
-        var stddev = Math.sqrt(variance)/(i+1);
-        rollingData[i] = [originalData[i][0],
-                          [sum/(i+1), sigma * stddev, sigma * stddev]];
+        if (num_ok) {
+          var stddev = Math.sqrt(variance)/num_ok;
+          rollingData[i] = [originalData[i][0],
+                            [sum/num_ok, sigma * stddev, sigma * stddev]];
+        } else {
+          rollingData[i] = [originalData[i][0], [null, null, null]];
+        }
       }
       // Calculate the rolling average for the remaining points
       for (var i = Math.min(rollPeriod - 1, originalData.length - 2);
@@ -1193,13 +1215,21 @@ Dygraph.prototype.rollingAverage = function(originalData, rollPeriod) {
           i++) {
         var sum = 0;
         var variance = 0;
+        var num_ok = 0;
         for (var j = i - rollPeriod + 1; j < i + 1; j++) {
+          var y = originalData[j][1][0];
+          if (!y || isNaN(y)) continue;
+          num_ok++;
           sum += originalData[j][1][0];
           variance += Math.pow(originalData[j][1][1], 2);
         }
-        var stddev = Math.sqrt(variance) / rollPeriod;
-        rollingData[i] = [originalData[i][0],
-                          [sum / rollPeriod, sigma * stddev, sigma * stddev]];
+        if (num_ok) {
+          var stddev = Math.sqrt(variance) / num_ok;
+          rollingData[i] = [originalData[i][0],
+                            [sum / num_ok, sigma * stddev, sigma * stddev]];
+        } else {
+          rollingData[i] = [originalData[i][0], [null, null, null]];
+        }
       }
     }
   }
index cfda39e..56d791d 100644 (file)
@@ -21,7 +21,7 @@
       g2 = new Dygraph(document.getElementById("graphdiv2"),
                        data_nolabel,
                        {
-                         labels: [ "High", "Low" ],
+                         labels: [ "Date", "High", "Low" ],
                          labelsDiv: document.getElementById("labels")
                        });
     </script>
index 0206852..e6eb648 100644 (file)
     <script type="text/javascript" src="../dygraph.js"></script>
   </head>
   <body>
+    <table>
+    <tr><td valign=top>
     <div id="graph"></div>
     <div id="graph2"></div>
+    </td><td valign=top>
     <div id="graph3"></div>
+    </td></tr>
+    </table>
 
     <script type="text/javascript">
     new Dygraph(
       ],
       { labels: ["Date","GapSeries1","GapSeries2"] }
     );
+
+    new Dygraph(
+      document.getElementById("graph3"),
+      [
+        [1, [10, 2], [20, 3]],
+        [2, [12, 2], [22, 3]],
+        [3, [ 8, 2], [25, 3]],
+        [4, [null, 2], [18, 3]],
+        [5, [11, 2], [20, 3]],
+        [6, [ 9, 2], [22, 3]],
+        [7, [10, 2], [23, 3]],
+      ],
+      {
+        errorBars: true,
+        labels: [ "X", "Series1", "Series2" ]
+      }
+    );
     </script>
   </body>
 </html>
index 09a5235..16bd9f1 100644 (file)
@@ -32,7 +32,7 @@
                          [4,70,80],
                        ],
                        {
-                         labels: [ "A", "B" ]
+                         labels: [ "x", "A", "B" ]
                        });
     </script>
   </body>