Fix issue 93: get the order of stacked graphs right.
authorDan Egnor <egnor@google.com>
Fri, 14 May 2010 01:37:04 +0000 (18:37 -0700)
committerDan Egnor <egnor@google.com>
Tue, 18 May 2010 00:17:34 +0000 (17:17 -0700)
Also fix a problem where stacked, filled graphs would pile up their
colors, which doesn't seem right for the normal semantics of stacked
graphs.

Also expand on the tests for stacked and filled graphs.

dygraph-canvas.js
dygraph.js
tests/fillGraph.html
tests/stacked.html

index b83e54a..e661d7e 100644 (file)
@@ -454,6 +454,7 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
   var fillAlpha = this.options.fillAlpha;
   var errorBars = this.layout.options.errorBars;
   var fillGraph = this.layout.options.fillGraph;
+  var stackedGraph = this.layout.options.stackedGraph;
 
   var setNames = [];
   for (var name in this.layout.datasets) {
@@ -491,11 +492,8 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
 
       // setup graphics context
       ctx.save();
-      ctx.strokeStyle = color;
-      ctx.lineWidth = this.options.strokeWidth;
       var prevX = NaN;
       var prevYs = [-1, -1];
-      var count = 0;
       var yscale = this.layout.yscale;
       // should be same color as the lines but only 15% opaque.
       var rgb = new RGBColor(color);
@@ -505,7 +503,6 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
       ctx.beginPath();
       for (var j = 0; j < this.layout.points.length; j++) {
         var point = this.layout.points[j];
-        count++;
         if (point.name == setName) {
           if (!isOK(point.y)) {
             prevX = NaN;
@@ -523,28 +520,29 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
             ctx.lineTo(prevX, prevYs[1]);
             ctx.closePath();
           }
-          prevYs[0] = newYs[0];
-          prevYs[1] = newYs[1];
+          prevYs = newYs;
           prevX = point.canvasx;
         }
       }
       ctx.fill();
     }
   } else if (fillGraph) {
-    // TODO(danvk): merge this code with the logic above; they're very similar.
-    for (var i = 0; i < setCount; i++) {
+    var axisY = 1.0 + this.layout.minyval * this.layout.yscale;
+    if (axisY < 0.0) axisY = 0.0;
+    else if (axisY > 1.0) axisY = 1.0;
+    axisY = this.area.h * axisY + this.area.y;
+
+    var baseline = []  // for stacked graphs: baseline for filling
+
+    // process sets in reverse order (needed for stacked graphs)
+    for (var i = setCount - 1; i >= 0; i--) {
       var setName = setNames[i];
-      var setNameLast;
-      if (i>0) setNameLast = setNames[i-1];
       var color = this.colors[setName];
 
       // setup graphics context
       ctx.save();
-      ctx.strokeStyle = color;
-      ctx.lineWidth = this.options.strokeWidth;
       var prevX = NaN;
       var prevYs = [-1, -1];
-      var count = 0;
       var yscale = this.layout.yscale;
       // should be same color as the lines but only 15% opaque.
       var rgb = new RGBColor(color);
@@ -554,18 +552,20 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
       ctx.beginPath();
       for (var j = 0; j < this.layout.points.length; j++) {
         var point = this.layout.points[j];
-        count++;
         if (point.name == setName) {
           if (!isOK(point.y)) {
             prevX = NaN;
             continue;
           }
-          var pX = 1.0 + this.layout.minyval * this.layout.yscale;
-          if (pX < 0.0) pX = 0.0;
-          else if (pX > 1.0) pX = 1.0;
-          var newYs = [ point.y, pX ];
-          newYs[0] = this.area.h * newYs[0] + this.area.y;
-          newYs[1] = this.area.h * newYs[1] + this.area.y;
+          var newYs;
+          if (stackedGraph) {
+            lastY = baseline[point.canvasx];
+            if (lastY === undefined) lastY = axisY;
+            baseline[point.canvasx] = point.canvasy;
+            newYs = [ point.canvasy, lastY ];
+          } else {
+            newYs = [ point.canvasy, axisY ];
+          }
           if (!isNaN(prevX)) {
             ctx.moveTo(prevX, prevYs[0]);
             ctx.lineTo(point.canvasx, newYs[0]);
@@ -573,8 +573,7 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
             ctx.lineTo(prevX, prevYs[1]);
             ctx.closePath();
           }
-          prevYs[0] = newYs[0];
-          prevYs[1] = newYs[1];
+          prevYs = newYs;
           prevX = point.canvasx;
         }
       }
index eff107c..51578bb 100644 (file)
@@ -908,9 +908,7 @@ Dygraph.prototype.mouseMove_ = function(event) {
 
   // Extract the points we've selected
   this.selPoints_ = [];
-  var cumulative_sum = 0;  // used only if we have a stackedGraph.
   var l = points.length;
-  var isStacked = this.attr_("stackedGraph");
   if (!this.attr_("stackedGraph")) {
     for (var i = 0; i < l; i++) {
       if (points[i].xval == lastx) {
@@ -918,11 +916,11 @@ Dygraph.prototype.mouseMove_ = function(event) {
       }
     }
   } else {
-    // Stacked points need to be examined in reverse order.
+    // Need to 'unstack' points starting from the bottom
+    var cumulative_sum = 0;
     for (var i = l - 1; i >= 0; i--) {
       if (points[i].xval == lastx) {
-        // Clone the point, since we need to 'unstack' it below.
-        var p = {};
+        var p = {};  // Clone the point since we modify it
         for (var k in points[i]) {
           p[k] = points[i][k];
         }
@@ -931,6 +929,7 @@ Dygraph.prototype.mouseMove_ = function(event) {
         this.selPoints_.push(p);
       }
     }
+    this.selPoints_.reverse();
   }
 
   if (this.attr_("highlightCallback")) {
@@ -1519,12 +1518,14 @@ Dygraph.prototype.drawGraph_ = function(data) {
 
   var connectSeparatedPoints = this.attr_('connectSeparatedPoints');
 
-  // For stacked series.
-  var cumulative_y = [];
-  var stacked_datasets = [];
+  // Loop over the fields (series).  Go from the last to the first,
+  // because if they're stacked that's how we accumulate the values.
 
-  // Loop over all fields in the dataset
-  for (var i = 1; i < data[0].length; i++) {
+  var cumulative_y = [];  // For stacked series.
+  var datasets = [];
+
+  // Loop over all fields and create datasets
+  for (var i = data[0].length - 1; i >= 1; i--) {
     if (!this.visibility()[i - 1]) continue;
 
     var series = [];
@@ -1575,38 +1576,35 @@ Dygraph.prototype.drawGraph_ = function(data) {
     if (maxY === null || thisMaxY > maxY) maxY = thisMaxY;
 
     if (bars) {
-      var vals = [];
-      for (var j=0; j<series.length; j++)
-        vals[j] = [series[j][0],
-                   series[j][1][0], series[j][1][1], series[j][1][2]];
-      this.layout_.addDataset(this.attr_("labels")[i], vals);
+      for (var j=0; j<series.length; j++) {
+        val = [series[j][0], series[j][1][0], series[j][1][1], series[j][1][2]];
+        series[j] = val;
+      }
     } else if (this.attr_("stackedGraph")) {
-      var vals = [];
       var l = series.length;
       var actual_y;
       for (var j = 0; j < l; j++) {
-        if (cumulative_y[series[j][0]] === undefined)
-          cumulative_y[series[j][0]] = 0;
+        // If one data set has a NaN, let all subsequent stacked
+        // sets inherit the NaN -- only start at 0 for the first set.
+        var x = series[j][0];
+        if (cumulative_y[x] === undefined)
+          cumulative_y[x] = 0;
 
         actual_y = series[j][1];
-        cumulative_y[series[j][0]] += actual_y;
+        cumulative_y[x] += actual_y;
 
-        vals[j] = [series[j][0], cumulative_y[series[j][0]]]
+        series[j] = [x, cumulative_y[x]]
 
-        if (!maxY || cumulative_y[series[j][0]] > maxY)
-          maxY = cumulative_y[series[j][0]];
+        if (!maxY || cumulative_y[x] > maxY)
+          maxY = cumulative_y[x];
       }
-      stacked_datasets.push([this.attr_("labels")[i], vals]);
-      //this.layout_.addDataset(this.attr_("labels")[i], vals);
-    } else {
-      this.layout_.addDataset(this.attr_("labels")[i], series);
     }
+
+    datasets[i] = series;
   }
 
-  if (stacked_datasets.length > 0) {
-    for (var i = (stacked_datasets.length - 1); i >= 0; i--) {
-      this.layout_.addDataset(stacked_datasets[i][0], stacked_datasets[i][1]);
-    }
+  for (var i = 1; i < datasets.length; i++) {
+    this.layout_.addDataset(this.attr_("labels")[i], datasets[i]);
   }
 
   // Use some heuristics to come up with a good maxY value, unless it's been
index b233a39..828c07f 100644 (file)
@@ -33,7 +33,7 @@
       );
 
       new Dygraph(
-        document.getElementById("div_g3"),
+        document.getElementById("div_g2"),
         function() {
           var ret = "X,Y1,Y2\n";
           for (var i = 0; i < 100; i++) {
@@ -46,7 +46,7 @@
       );
 
       new Dygraph(
-        document.getElementById("div_g2"),
+        document.getElementById("div_g3"),
         function() {
           var ret = "X,Y1,Y2\n";
           for (var i = 0; i < 100; i++) {
index ac2f86f..8973347 100644 (file)
   </head>
   <body>
     <p>Simple graph:</p>
-    <div id="graphdiv"></div>
+    <div id="simple_div"></div>
     <p>Stacked graph:</p>
-    <div id="graphdiv2"></div>
+    <div id="stacked_div"></div>
+    <p>Simple graph with missing data:</p>
+    <div id="simple_missing_div"></div>
+    <p>Stacked graph with missing data:</p>
+    <div id="stacked_missing_div"></div>
+    <p>Stacked graph with many series:</p>
+    <div id="stacked_many_div"></div>
 
     <script type="text/javascript">
       data = "X,x,100-x\n";
         data += i + "," + i + "," + (100 - i) + "\n";
       }
 
-      var g = new Dygraph(document.getElementById("graphdiv"),
-                           data);
-      var g2 = new Dygraph(document.getElementById("graphdiv2"),
-                           data,
-                           { stackedGraph: true });
+      new Dygraph(document.getElementById("simple_div"),
+                  data);
+      new Dygraph(document.getElementById("stacked_div"),
+                  data,
+                  { stackedGraph: true });
+
+      missing_data = "X,x,100-x\n";
+      for (var i = 0; i < 100; i++) {
+        if (i >= 20 && i < 40) {
+          missing_data += i + ",," + (100 - i) + "\n";
+        } else if (i >= 60 && i < 80) {
+          missing_data += i + "," + i + ",\n";
+        } else {
+          missing_data += i + "," + i + "," + (100 - i) + "\n";
+        }
+      }
+
+      new Dygraph(document.getElementById("simple_missing_div"),
+                  missing_data);
+      new Dygraph(document.getElementById("stacked_missing_div"),
+                  missing_data,
+                  { stackedGraph: true });
+
+      many_data = "X,a,b,c,d,e,100-a,100-b,100-c,100-d,100-e\n";
+      for (var i = 0; i < 100; i++) {
+        many_data += i + "," + i + "," + i + "," + i + "," + i + "," + i;
+        j = 100 - i;
+        many_data += "," + j + "," + j + "," + j + "," + j + "," + j;
+        many_data += "\n";
+      }
+
+      new Dygraph(document.getElementById("stacked_many_div"),
+                  many_data,
+                  { stackedGraph: true });
     </script>
   </body>
 </html>