Merge branch 'master' into reorganize-points reorganize-points
authorDan Vanderkam <dan@dygraphs.com>
Wed, 25 Jul 2012 17:54:28 +0000 (13:54 -0400)
committerDan Vanderkam <dan@dygraphs.com>
Wed, 25 Jul 2012 17:54:28 +0000 (13:54 -0400)
auto_tests/misc/local.html
auto_tests/tests/error_bars.js
auto_tests/tests/stacked.js [new file with mode: 0644]
dygraph-canvas.js
dygraph-layout.js
dygraph-options-reference.js
dygraph.js
tests/demo.html

index 5afde2d..89c7efc 100644 (file)
@@ -42,6 +42,7 @@
   -->
   <script type="text/javascript" src="../tests/to_dom_coords.js"></script>
   <script type="text/javascript" src="../tests/update_while_panning.js"></script>
+  <script type="text/javascript" src="../tests/stacked.js"></script>
   <script type="text/javascript" src="../tests/update_options.js"></script>
   <script type="text/javascript" src="../tests/utils_test.js"></script>
 
index c9e3a38..1ede89b 100644 (file)
@@ -89,3 +89,57 @@ errorBarsTestCase.prototype.testErrorBarsDrawn = function() {
   CanvasAssertions.assertBalancedSaveRestore(htx);
 };
 
+errorBarsTestCase.prototype.testErrorBarsCorrectColors = function() {
+  // Two constant series with constant error.
+  var data = [
+    [0, [100, 50], [200, 50]],
+    [1, [100, 50], [200, 50]]
+  ];
+
+  var opts = {
+    errorBars: true,
+    sigma: 1.0,
+    fillAlpha: 0.15,
+    colors: ['#00ff00', '#0000ff'],
+    drawXGrid: false,
+    drawYGrid: false,
+    drawXAxis: false,
+    drawYAxis: false,
+    width: 400,
+    height: 300,
+    valueRange: [0, 300],
+    labels: ['X', 'Y1', 'Y2']
+  };
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, opts);
+
+  // y-pixels (0=top, 299=bottom)
+  //   0- 48: empty (white)
+  //  49- 98: Y2 error bar
+  //  99:     Y2 center line
+  // 100-148: Y2 error bar
+  // 149-198: Y1 error bar
+  // 199:     Y1 center line
+  // 200-248: Y1 error bar
+  // 249-299: empty (white)
+  // TODO(danvk): test the edges of these regions.
+
+  var ctx = g.hidden_.getContext("2d");  // bypass Proxy
+  var imageData = ctx.getImageData(0, 0, 400, 300);
+
+  assertEquals(400, imageData.width);
+  assertEquals(300, imageData.height);
+
+  // returns an (r, g, b, alpha) tuple for the pixel.
+  // values are in [0, 255].
+  var getPixel = function(imageData, x, y) {
+    var i = 4 * (x + imageData.width * y);
+    var d = imageData.data;
+    return [d[i], d[i+1], d[i+2], d[i+3]];
+  };
+
+  assertEquals([0, 0, 255, 38], getPixel(imageData, 200, 75));
+  assertEquals([0, 0, 255, 38], getPixel(imageData, 200, 125));
+  assertEquals([0, 255, 0, 38], getPixel(imageData, 200, 175));
+  assertEquals([0, 255, 0, 38], getPixel(imageData, 200, 225));
+}
diff --git a/auto_tests/tests/stacked.js b/auto_tests/tests/stacked.js
new file mode 100644 (file)
index 0000000..e1f2cbc
--- /dev/null
@@ -0,0 +1,62 @@
+/**
+ * @fileoverview Tests using the "stackedGraph" option.
+ *
+ * @author dan@dygraphs.com (Dan Vanderkam)
+ */
+var stackedTestCase = TestCase("stacked");
+
+stackedTestCase.prototype.setUp = function() {
+  document.body.innerHTML = "<div id='graph'></div>";
+};
+
+stackedTestCase.prototype.tearDown = function() {
+};
+
+stackedTestCase.prototype.testCorrectColors = function() {
+  var opts = {
+    width: 400,
+    height: 300,
+    stackedGraph: true,
+    drawXGrid: false,
+    drawYGrid: false,
+    drawXAxis: false,
+    drawYAxis: false,
+    valueRange: [0, 3],
+    colors: ['#00ff00', '#0000ff'],
+    fillAlpha: 0.15
+  };
+  var data = "X,Y1,Y2\n" +
+      "0,1,1\n" +
+      "1,1,1\n" +
+      "2,1,1\n" +
+      "3,1,1\n"
+  ;
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, data, opts);
+
+  // y pixels 299-201 = y2 = transparent blue
+  // y pixel 200 = y2 line (blue)
+  // y pixels 199-101 = y1 = transparent green
+  // y pixel 100 = y1 line (green)
+  // y pixels 0-99 = nothing (white)
+
+  // TODO(danvk): factor this and getPixel() into a utility usable by all tests.
+  var ctx = g.hidden_ctx_;
+  var imageData = ctx.getImageData(0, 0, 400, 300);
+
+  assertEquals(400, imageData.width);
+  assertEquals(300, imageData.height);
+
+  // returns an (r, g, b, alpha) tuple for the pixel.
+  // values are in [0, 255].
+  var getPixel = function(imageData, x, y) {
+    var i = 4 * (x + imageData.width * y);
+    var d = imageData.data;
+    return [d[i], d[i+1], d[i+2], d[i+3]];
+  };
+
+  // 38 = round(0.15 * 255)
+  assertEquals([0, 0, 255, 38], getPixel(imageData, 200, 250));
+  assertEquals([0, 255, 0, 38], getPixel(imageData, 200, 150));
+};
index 450c8a9..825a4c2 100644 (file)
@@ -246,19 +246,17 @@ DygraphCanvasRenderer._predicateThatSkipsEmptyPoints =
  * @private
  */
 DygraphCanvasRenderer.prototype._drawStyledLine = function(
-    ctx, i, setName, color, strokeWidth, strokePattern, drawPoints,
+    ctx, setIdx, setName, color, strokeWidth, strokePattern, drawPoints,
     drawPointCallback, pointSize) {
   // TODO(konigsberg): Compute attributes outside this method call.
   var stepPlot = this.attr_("stepPlot");
-  var firstIndexInSet = this.layout.setPointsOffsets[i];
-  var setLength = this.layout.setPointsLengths[i];
-  var points = this.layout.points;
   if (!Dygraph.isArrayLike(strokePattern)) {
     strokePattern = null;
   }
   var drawGapPoints = this.dygraph_.attr_('drawGapEdgePoints', setName);
 
-  var iter = Dygraph.createIterator(points, firstIndexInSet, setLength,
+  var points = this.layout.points[setIdx];
+  var iter = Dygraph.createIterator(points, 0, points.length,
       DygraphCanvasRenderer._getIteratorPredicate(
           this.attr_("connectSeparatedPoints")));
 
@@ -427,11 +425,14 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
   // unaffected.  An alternative is to reduce the stroke width in the
   // transformed coordinate space, but you can't specify different values for
   // each dimension (as you can with .scale()). The speedup here is ~12%.
-  var points = this.layout.points;
-  for (i = points.length; i--;) {
-    var point = points[i];
-    point.canvasx = this.area.w * point.x + this.area.x;
-    point.canvasy = this.area.h * point.y + this.area.y;
+  var sets = this.layout.points;
+  for (i = sets.length; i--;) {
+    var points = sets[i];
+    for (var j = points.length; j--;) {
+      var point = points[j];
+      point.canvasx = this.area.w * point.x + this.area.x;
+      point.canvasy = this.area.h * point.y + this.area.y;
+    }
   }
 
   // Draw any "fills", i.e. error bars or the filled area under a series.
@@ -473,15 +474,13 @@ DygraphCanvasRenderer.prototype.drawErrorBars_ = function(points) {
 
   var newYs;
 
-  for (var i = 0; i < setCount; i++) {
-    var setName = setNames[i];
+  for (var setIdx = 0; setIdx < setCount; setIdx++) {
+    var setName = setNames[setIdx];
     var axis = this.dygraph_.axisPropertiesForSeries(setName);
     var color = this.colors[setName];
 
-    var firstIndexInSet = this.layout.setPointsOffsets[i];
-    var setLength = this.layout.setPointsLengths[i];
-
-    var iter = Dygraph.createIterator(points, firstIndexInSet, setLength,
+    var points = this.layout.points[setIdx];
+    var iter = Dygraph.createIterator(points, 0, points.length,
         DygraphCanvasRenderer._getIteratorPredicate(
             this.attr_("connectSeparatedPoints")));
 
@@ -551,18 +550,17 @@ DygraphCanvasRenderer.prototype.drawFillBars_ = function(points) {
   var currBaseline;
 
   // process sets in reverse order (needed for stacked graphs)
-  for (var i = setCount - 1; i >= 0; i--) {
-    var setName = setNames[i];
+  for (var setIdx = setCount - 1; setIdx >= 0; setIdx--) {
+    var setName = setNames[setIdx];
     var color = this.colors[setName];
     var axis = this.dygraph_.axisPropertiesForSeries(setName);
     var axisY = 1.0 + axis.minyval * axis.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 firstIndexInSet = this.layout.setPointsOffsets[i];
-    var setLength = this.layout.setPointsLengths[i];
 
-    var iter = Dygraph.createIterator(points, firstIndexInSet, setLength,
+    var points = this.layout.points[setIdx];
+    var iter = Dygraph.createIterator(points, 0, points.length,
         DygraphCanvasRenderer._getIteratorPredicate(
             this.attr_("connectSeparatedPoints")));
 
index a5e6908..e25afba 100644 (file)
@@ -35,6 +35,7 @@ var DygraphLayout = function(dygraph) {
   this.setNames = [];
   this.annotations = [];
   this.yAxes_ = null;
+  this.points = null;
 
   // TODO(danvk): it's odd that xTicks_ and yTicks_ are inputs, but xticks and
   // yticks are outputs. Clean this up.
@@ -218,35 +219,24 @@ DygraphLayout._calcYNormal = function(axis, value) {
 };
 
 DygraphLayout.prototype._evaluateLineCharts = function() {
-  // An array to keep track of how many points will be drawn for each set.
-  // This will allow for the canvas renderer to not have to check every point
-  // for every data set since the points are added in order of the sets in
-  // datasets.
-  this.setPointsLengths = [];
-  this.setPointsOffsets = [];
-
   var connectSeparated = this.attr_('connectSeparatedPoints');
+
+  // series index -> point index in series -> |point| structure
+  this.points = new Array(this.datasets.length);
+
   // TODO(bhs): these loops are a hot-spot for high-point-count charts. In fact,
   // on chrome+linux, they are 6 times more expensive than iterating through the
   // points and drawing the lines. The brunt of the cost comes from allocating
   // the |point| structures.
-  var i = 0;
-  var setIdx;
-
-  // Preallocating the size of points reduces reallocations, and therefore,
-  // calls to collect garbage.
-  var totalPoints = 0;
-  for (setIdx = 0; setIdx < this.datasets.length; ++setIdx) {
-    totalPoints += this.datasets[setIdx].length;
-  }
-  this.points = new Array(totalPoints);
-
-  for (setIdx = 0; setIdx < this.datasets.length; ++setIdx) {
-    this.setPointsOffsets.push(i);
+  for (var setIdx = 0; setIdx < this.datasets.length; setIdx++) {
     var dataset = this.datasets[setIdx];
     var setName = this.setNames[setIdx];
     var axis = this.dygraph_.axisPropertiesForSeries(setName);
 
+    // Preallocating the size of points reduces reallocations, and therefore,
+    // calls to collect garbage.
+    var seriesPoints = new Array(dataset.length);
+
     for (var j = 0; j < dataset.length; j++) {
       var item = dataset[j];
       var xValue = DygraphLayout.parseFloat_(item[0]);
@@ -257,20 +247,21 @@ DygraphLayout.prototype._evaluateLineCharts = function() {
       // Range from 0-1 where 0 represents top and 1 represents bottom
       var yNormal = DygraphLayout._calcYNormal(axis, yValue);
 
+      // TODO(danvk): drop the point in this case, don't null it.
+      // The nulls create complexity in DygraphCanvasRenderer._drawSeries.
       if (connectSeparated && item[1] === null) {
         yValue = null;
       }
-      this.points[i] = {
-        // TODO(danvk): here
+      seriesPoints[j] = {
         x: xNormal,
         y: yNormal,
         xval: xValue,
         yval: yValue,
-        name: setName
+        name: setName  // TODO(danvk): is this really necessary?
       };
-      i++;
     }
-    this.setPointsLengths.push(i - this.setPointsOffsets[setIdx]);
+
+    this.points[setIdx] = seriesPoints;
   }
 };
 
@@ -326,6 +317,7 @@ DygraphLayout.prototype.evaluateWithError = function() {
   // Copy over the error terms
   var i = 0;  // index in this.points
   for (var setIdx = 0; setIdx < this.datasets.length; ++setIdx) {
+    var points = this.points[setIdx];
     var j = 0;
     var dataset = this.datasets[setIdx];
     var setName = this.setNames[setIdx];
@@ -335,15 +327,15 @@ DygraphLayout.prototype.evaluateWithError = function() {
       var xv = DygraphLayout.parseFloat_(item[0]);
       var yv = DygraphLayout.parseFloat_(item[1]);
 
-      if (xv == this.points[i].xval &&
-          yv == this.points[i].yval) {
+      if (xv == points[j].xval &&
+          yv == points[j].yval) {
         var errorMinus = DygraphLayout.parseFloat_(item[2]);
         var errorPlus = DygraphLayout.parseFloat_(item[3]);
 
         var yv_minus = yv - errorMinus;
         var yv_plus = yv + errorPlus;
-        this.points[i].y_top = DygraphLayout._calcYNormal(axis, yv_minus);
-        this.points[i].y_bottom = DygraphLayout._calcYNormal(axis, yv_plus);
+        points[j].y_top = DygraphLayout._calcYNormal(axis, yv_minus);
+        points[j].y_bottom = DygraphLayout._calcYNormal(axis, yv_plus);
       }
     }
   }
@@ -367,12 +359,15 @@ DygraphLayout.prototype._evaluateAnnotations = function() {
   }
 
   // TODO(antrob): loop through annotations not points.
-  for (i = 0; i < this.points.length; i++) {
-    var p = this.points[i];
-    var k = p.xval + "," + p.name;
-    if (k in annotations) {
-      p.annotation = annotations[k];
-      this.annotated_points.push(p);
+  for (var setIdx = 0; setIdx < this.points.length; setIdx++) {
+    var points = this.points[setIdx];
+    for (i = 0; i < points.length; i++) {
+      var p = points[i];
+      var k = p.xval + "," + p.name;
+      if (k in annotations) {
+        p.annotation = annotations[k];
+        this.annotated_points.push(p);
+      }
     }
   }
 };
@@ -395,8 +390,8 @@ DygraphLayout.prototype.removeAllDatasets = function() {
  * Return a copy of the point at the indicated index, with its yval unstacked.
  * @param int index of point in layout_.points
  */
-DygraphLayout.prototype.unstackPointAtIndex = function(idx) {
-  var point = this.points[idx];
+DygraphLayout.prototype.unstackPointAtIndex = function(setIdx, row) {
+  var point = this.points[setIdx][row];
   // If the point is missing, no unstacking is necessary
   if (!point.yval) {
     return point;
@@ -414,9 +409,10 @@ DygraphLayout.prototype.unstackPointAtIndex = function(idx) {
 
   // The unstacked yval is equal to the current yval minus the yval of the
   // next point at the same xval.
-  for (var i = idx+1; i < this.points.length; i++) {
-    if ((this.points[i].xval == point.xval) && this.points[i].yval) {
-      unstackedPoint.yval -= this.points[i].yval;
+  var points = this.points[setIdx];
+  for (var i = row + 1; i < points.length; i++) {
+    if ((points[i].xval == point.xval) && points[i].yval) {
+      unstackedPoint.yval -= points[i].yval;
       break;
     }
   }
index 69bed8a..04f13d7 100644 (file)
@@ -23,7 +23,7 @@ Dygraph.OPTIONS_REFERENCE =  // <JSON>
     "default": "false",
     "labels": ["Data Line display"],
     "type": "boolean",
-    "description": "If set, stack series on top of one another rather than drawing them independently."
+    "description": "If set, stack series on top of one another rather than drawing them independently. The first series specified in the input data will wind up on top of the chart and the last will be on bottom."
   },
   "pointSize": {
     "default": "1",
index bd3a4cc..3a60180 100644 (file)
@@ -1597,19 +1597,25 @@ Dygraph.prototype.eventToDomCoords = function(event) {
  */
 Dygraph.prototype.findClosestRow = function(domX) {
   var minDistX = Infinity;
-  var idx = -1;
-  var points = this.layout_.points;
-  var l = points.length;
-  for (var i = 0; i < l; i++) {
-    var point = points[i];
-    if (!Dygraph.isValidPoint(point, true)) continue;
-    var dist = Math.abs(point.canvasx - domX);
-    if (dist < minDistX) {
-      minDistX = dist;
-      idx = i;
+  var pointIdx = -1, setIdx = -1;
+  var sets = this.layout_.points;
+  for (var i = 0; i < sets.length; i++) {
+    var points = sets[i];
+    var len = points.length;
+    for (var j = 0; j < len; j++) {
+      var point = points[j];
+      if (!Dygraph.isValidPoint(point, true)) continue;
+      var dist = Math.abs(point.canvasx - domX);
+      if (dist < minDistX) {
+        minDistX = dist;
+        setIdx = i;
+        pointIdx = j;
+      }
     }
   }
-  return this.idxToRow_(idx);
+
+  // TODO(danvk): remove this function; it's trivial and has only one use.
+  return this.idxToRow_(setIdx, pointIdx);
 };
 
 /**
@@ -1627,13 +1633,11 @@ Dygraph.prototype.findClosestRow = function(domX) {
 Dygraph.prototype.findClosestPoint = function(domX, domY) {
   var minDist = Infinity;
   var idx = -1;
-  var points = this.layout_.points;
   var dist, dx, dy, point, closestPoint, closestSeries;
   for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
-    var first = this.layout_.setPointsOffsets[setIdx];
-    var len = this.layout_.setPointsLengths[setIdx];
-    for (var i = 0; i < len; ++i) {
-      var point = points[first + i];
+    var points = this.layout_.points[setIdx];
+    for (var i = 0; i < points.length; ++i) {
+      var point = points[i];
       if (!Dygraph.isValidPoint(point)) continue;
       dx = point.canvasx - domX;
       dy = point.canvasy - domY;
@@ -1670,18 +1674,17 @@ Dygraph.prototype.findStackedPoint = function(domX, domY) {
   var row = this.findClosestRow(domX);
   var boundary = this.getLeftBoundary_();
   var rowIdx = row - boundary;
-  var points = this.layout_.points;
+  var sets = this.layout_.points;
   var closestPoint, closestSeries;
   for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
-    var first = this.layout_.setPointsOffsets[setIdx];
-    var len = this.layout_.setPointsLengths[setIdx];
-    if (rowIdx >= len) continue;
-    var p1 = points[first + rowIdx];
+    var points = this.layout_.points[setIdx];
+    if (rowIdx >= points.length) continue;
+    var p1 = points[rowIdx];
     if (!Dygraph.isValidPoint(p1)) continue;
     var py = p1.canvasy;
-    if (domX > p1.canvasx && rowIdx + 1 < len) {
+    if (domX > p1.canvasx && rowIdx + 1 < points.length) {
       // interpolate series Y value using next point
-      var p2 = points[first + rowIdx + 1];
+      var p2 = points[rowIdx + 1];
       if (Dygraph.isValidPoint(p2)) {
         var dx = p2.canvasx - p1.canvasx;
         if (dx > 0) {
@@ -1691,7 +1694,7 @@ Dygraph.prototype.findStackedPoint = function(domX, domY) {
       }
     } else if (domX < p1.canvasx && rowIdx > 0) {
       // interpolate series Y value using previous point
-      var p0 = points[first + rowIdx - 1];
+      var p0 = points[rowIdx - 1];
       if (Dygraph.isValidPoint(p0)) {
         var dx = p1.canvasx - p0.canvasx;
         if (dx > 0) {
@@ -1724,7 +1727,7 @@ Dygraph.prototype.findStackedPoint = function(domX, domY) {
 Dygraph.prototype.mouseMove_ = function(event) {
   // This prevents JS errors when mousing over the canvas before data loads.
   var points = this.layout_.points;
-  if (points === undefined) return;
+  if (points === undefined || points === null) return;
 
   var canvasCoords = this.eventToDomCoords(event);
   var canvasx = canvasCoords[0];
@@ -1770,18 +1773,19 @@ Dygraph.prototype.getLeftBoundary_ = function() {
  * @return int row number, or -1 if none could be found.
  * @private
  */
-Dygraph.prototype.idxToRow_ = function(idx) {
-  if (idx < 0) return -1;
+Dygraph.prototype.idxToRow_ = function(setIdx, rowIdx) {
+  if (rowIdx < 0) return -1;
 
   var boundary = this.getLeftBoundary_();
-  for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
-    var set = this.layout_.datasets[setIdx];
-    if (idx < set.length) {
-      return boundary + idx;
-    }
-    idx -= set.length;
-  }
-  return -1;
+  return boundary + rowIdx;
+  // for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
+  //   var set = this.layout_.datasets[setIdx];
+  //   if (idx < set.length) {
+  //     return boundary + idx;
+  //   }
+  //   idx -= set.length;
+  // }
+  // return -1;
 };
 
 Dygraph.prototype.animateSelection_ = function(direction) {
@@ -1906,7 +1910,6 @@ Dygraph.prototype.updateSelection_ = function(opt_animFraction) {
 Dygraph.prototype.setSelection = function(row, opt_seriesName) {
   // Extract the points we've selected
   this.selPoints_ = [];
-  var pos = 0;
 
   if (row !== false) {
     row -= this.getLeftBoundary_();
@@ -1919,15 +1922,14 @@ Dygraph.prototype.setSelection = function(row, opt_seriesName) {
     for (var setIdx = 0; setIdx < this.layout_.datasets.length; ++setIdx) {
       var set = this.layout_.datasets[setIdx];
       if (row < set.length) {
-        var point = this.layout_.points[pos+row];
+        var point = this.layout_.points[setIdx][row];
 
         if (this.attr_("stackedGraph")) {
-          point = this.layout_.unstackPointAtIndex(pos+row);
+          point = this.layout_.unstackPointAtIndex(setIdx, row);
         }
 
         if (!(point.yval === null)) this.selPoints_.push(point);
       }
-      pos += set.length;
     }
   } else {
     if (this.lastRow_ >= 0) changed = true;
@@ -1996,9 +1998,12 @@ Dygraph.prototype.getSelection = function() {
     return -1;
   }
 
-  for (var row=0; row<this.layout_.points.length; row++ ) {
-    if (this.layout_.points[row].x == this.selPoints_[0].x) {
-      return row + this.getLeftBoundary_();
+  for (var setIdx = 0; setIdx < this.layout_.points.length; setIdx++) {
+    var points = this.layout_.points[setIdx];
+    for (var row = 0; row < points.length; row++) {
+      if (points[row].x == this.selPoints_[0].x) {
+        return row + this.getLeftBoundary_();
+      }
     }
   }
   return -1;
@@ -2168,10 +2173,10 @@ Dygraph.prototype.gatherDatasets_ = function(rolledSeries, dateWindow) {
     if (!this.visibility()[i - 1]) continue;
 
     // TODO(danvk): is this copy really necessary?
-    var series = [];
-    for (j = 0; j < rolledSeries[i].length; j++) {
-      series.push(rolledSeries[i][j]);
-    }
+    var series = rolledSeries[i];  // [];
+    // for (j = 0; j < rolledSeries[i].length; j++) {
+    //   series.push(rolledSeries[i][j]);
+    // }
 
     // Prune down to the desired range, if necessary (for zooming)
     // Because there can be lines going to points outside of the visible area,
@@ -2634,10 +2639,12 @@ Dygraph.prototype.computeYAxisRanges_ = function(extremes) {
  *
  * This is where undesirable points (i.e. negative values on log scales and
  * missing values through which we wish to connect lines) are dropped.
+ * TODO(danvk): the "missing values" bit above doesn't seem right.
  * 
  * @private
  */
 Dygraph.prototype.extractSeries_ = function(rawData, i, logScale) {
+  // TODO(danvk): pre-allocate series here.
   var series = [];
   for (var j = 0; j < rawData.length; j++) {
     var x = rawData[j][0];
@@ -3034,6 +3041,13 @@ Dygraph.prototype.parseArray_ = function(data) {
     for (i = 1; i < data[0].length; i++) {
       this.attrs_.labels.push("Y" + i);
     }
+  } else {
+    var num_labels = this.attr_("labels");
+    if (num_labels.length != data[0].length) {
+      this.error("Mismatch between number of labels (" + num_labels +
+          ") and number of columns in array (" + data[0].length + ")");
+      return null;
+    }
   }
 
   if (Dygraph.isDateLike(data[0][0])) {
index 94582dc..9184f4c 100644 (file)
@@ -47,8 +47,7 @@
                 title: 'Interesting Shapes',
                 xlabel: 'Date',
                 ylabel: 'Count',
-                axisLineColor: 'white',
-                gridLineWidth: 10
+                axisLineColor: 'white'
                 // drawXGrid: false
               }
           );