Merge branch 'master' of git://github.com/danvk/dygraphs
authorRobert Konigsberg <konigsberg@google.com>
Fri, 8 Oct 2010 14:21:30 +0000 (10:21 -0400)
committerRobert Konigsberg <konigsberg@google.com>
Fri, 8 Oct 2010 14:21:30 +0000 (10:21 -0400)
dygraph-canvas.js
dygraph.js
tests/linear-regression-addseries.html
tests/linear-regression.html
tests/per-series.html [new file with mode: 0644]
tests/stacked.html

index 182f29d..ac0e741 100644 (file)
@@ -195,6 +195,35 @@ DygraphLayout.prototype.updateOptions = function(new_options) {
   Dygraph.update(this.options, new_options ? new_options : {});
 };
 
+/**
+ * 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];
+  
+  // Clone the point since we modify it
+  var unstackedPoint = {};  
+  for (var i in point) {
+    unstackedPoint[i] = point[i];
+  }
+  
+  if (!this.attr_("stackedGraph")) {
+    return unstackedPoint;
+  }
+  
+  // 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) {
+      unstackedPoint.yval -= this.points[i].yval; 
+      break;
+    }
+  }
+  
+  return unstackedPoint;
+}  
+
 // Subclass PlotKit.CanvasRenderer to add:
 // 1. X/Y grid overlay
 // 2. Ability to draw error bars (if required)
@@ -748,13 +777,14 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
   for (var i = 0; i < setCount; i++) {
     var setName = setNames[i];
     var color = this.colors[setName];
+    var strokeWidth = this.dygraph_.attr_("strokeWidth", setName);
 
     // setup graphics context
     context.save();
     var point = this.layout.points[0];
-    var pointSize = this.dygraph_.attr_("pointSize");
+    var pointSize = this.dygraph_.attr_("pointSize", setName);
     var prevX = null, prevY = null;
-    var drawPoints = this.dygraph_.attr_("drawPoints");
+    var drawPoints = this.dygraph_.attr_("drawPoints", setName);
     var points = this.layout.points;
     for (var j = 0; j < points.length; j++) {
       var point = points[j];
@@ -772,17 +802,20 @@ DygraphCanvasRenderer.prototype._renderLineChart = function() {
             prevX = point.canvasx;
             prevY = point.canvasy;
           } else {
-            ctx.beginPath();
-            ctx.strokeStyle = color;
-            ctx.lineWidth = this.options.strokeWidth;
-            ctx.moveTo(prevX, prevY);
-            if (stepPlot) {
-              ctx.lineTo(point.canvasx, prevY);
+            // TODO(danvk): figure out why this conditional is necessary.
+            if (strokeWidth) {
+              ctx.beginPath();
+              ctx.strokeStyle = color;
+              ctx.lineWidth = strokeWidth;
+              ctx.moveTo(prevX, prevY);
+              if (stepPlot) {
+                ctx.lineTo(point.canvasx, prevY);
+              }
+              prevX = point.canvasx;
+              prevY = point.canvasy;
+              ctx.lineTo(prevX, prevY);
+              ctx.stroke();
             }
-            prevX = point.canvasx;
-            prevY = point.canvasy;
-            ctx.lineTo(prevX, prevY);
-            ctx.stroke();
           }
 
           if (drawPoints || isIsolated) {
index aea73ea..958f94b 100644 (file)
@@ -251,8 +251,13 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
   this.start_();
 };
 
-Dygraph.prototype.attr_ = function(name) {
-  if (typeof(this.user_attrs_[name]) != 'undefined') {
+Dygraph.prototype.attr_ = function(name, seriesName) {
+  if (seriesName &&
+      typeof(this.user_attrs_[seriesName]) != 'undefined' &&
+      this.user_attrs_[seriesName] != null &&
+      typeof(this.user_attrs_[seriesName][name]) != 'undefined') {
+    return this.user_attrs_[seriesName][name];
+  } else if (typeof(this.user_attrs_[name]) != 'undefined') {
     return this.user_attrs_[name];
   } else if (typeof(this.attrs_[name]) != 'undefined') {
     return this.attrs_[name];
@@ -1185,11 +1190,18 @@ Dygraph.prototype.mouseMove_ = function(event) {
  */
 Dygraph.prototype.updateSelection_ = function() {
   // Clear the previously drawn vertical, if there is one
-  var circleSize = this.attr_('highlightCircleSize');
   var ctx = this.canvas_.getContext("2d");
   if (this.previousVerticalX_ >= 0) {
+    // Determine the maximum highlight circle size.
+    var maxCircleSize = 0;
+    var labels = this.attr_('labels');
+    for (var i = 1; i < labels.length; i++) {
+      var r = this.attr_('highlightCircleSize', labels[i]);
+      if (r > maxCircleSize) maxCircleSize = r;
+    }
     var px = this.previousVerticalX_;
-    ctx.clearRect(px - circleSize - 1, 0, 2 * circleSize + 2, this.height_);
+    ctx.clearRect(px - maxCircleSize - 1, 0,
+                  2 * maxCircleSize + 2, this.height_);
   }
 
   var isOK = function(x) { return x && !isNaN(x); };
@@ -1205,7 +1217,7 @@ Dygraph.prototype.updateSelection_ = function() {
     if (this.attr_('showLabelsOnHighlight')) {
       // Set the status message to indicate the selected point(s)
       for (var i = 0; i < this.selPoints_.length; i++) {
-       if (!this.attr_("labelsShowZeroValues") && this.selPoints_[i].yval == 0) continue;        
+        if (!this.attr_("labelsShowZeroValues") && this.selPoints_[i].yval == 0) continue;
         if (!isOK(this.selPoints_[i].canvasy)) continue;
         if (this.attr_("labelsSeparateLines")) {
           replace += "<br/>";
@@ -1225,6 +1237,8 @@ Dygraph.prototype.updateSelection_ = function() {
     ctx.save();
     for (var i = 0; i < this.selPoints_.length; i++) {
       if (!isOK(this.selPoints_[i].canvasy)) continue;
+      var circleSize =
+        this.attr_('highlightCircleSize', this.selPoints_[i].name);
       ctx.beginPath();
       ctx.fillStyle = this.plotter_.colors[this.selPoints_[i].name];
       ctx.arc(canvasx, this.selPoints_[i].canvasy, circleSize,
@@ -1255,7 +1269,13 @@ Dygraph.prototype.setSelection = function(row) {
   if (row !== false && row >= 0) {
     for (var i in this.layout_.datasets) {
       if (row < this.layout_.datasets[i].length) {
-        this.selPoints_.push(this.layout_.points[pos+row]);
+        var point = this.layout_.points[pos+row];
+        
+        if (this.attr_("stackedGraph")) {
+          point = this.layout_.unstackPointAtIndex(pos+row);
+        }
+        
+        this.selPoints_.push(point);
       }
       pos += this.layout_.datasets[i].length;
     }
@@ -1749,8 +1769,6 @@ Dygraph.prototype.drawGraph_ = function(data) {
   this.setColors_();
   this.attrs_['pointSize'] = 0.5 * this.attr_('highlightCircleSize');
 
-  var connectSeparatedPoints = this.attr_('connectSeparatedPoints');
-
   // Loop over the fields (series).  Go from the last to the first,
   // because if they're stacked that's how we accumulate the values.
 
@@ -1761,6 +1779,8 @@ Dygraph.prototype.drawGraph_ = function(data) {
   for (var i = data[0].length - 1; i >= 1; i--) {
     if (!this.visibility()[i - 1]) continue;
 
+    var connectSeparatedPoints = this.attr_('connectSeparatedPoints', i);
+
     var series = [];
     for (var j = 0; j < data.length; j++) {
       if (data[j][i] != null || !connectSeparatedPoints) {
@@ -2305,6 +2325,7 @@ Dygraph.prototype.parseDataTable_ = function(data) {
   var labels = [data.getColumnLabel(0)];
   for (var i = 0; i < colIdx.length; i++) {
     labels.push(data.getColumnLabel(colIdx[i]));
+    if (this.attr_("errorBars")) i += 1;
   }
   this.attrs_.labels = labels;
   cols = labels.length;
@@ -2316,8 +2337,8 @@ Dygraph.prototype.parseDataTable_ = function(data) {
     var row = [];
     if (typeof(data.getValue(i, 0)) === 'undefined' ||
         data.getValue(i, 0) === null) {
-      this.warning("Ignoring row " + i +
-                   " of DataTable because of undefined or null first column.");
+      this.warn("Ignoring row " + i +
+                " of DataTable because of undefined or null first column.");
       continue;
     }
 
@@ -2474,6 +2495,14 @@ Dygraph.prototype.updateOptions = function(attrs) {
   if (attrs.valueRange) {
     this.valueRange_ = attrs.valueRange;
   }
+
+  // TODO(danvk): validate per-series options.
+  // Supported:
+  // strokeWidth
+  // pointSize
+  // drawPoints
+  // highlightCircleSize
+
   Dygraph.update(this.user_attrs_, attrs);
   Dygraph.update(this.renderOptions_, attrs);
 
@@ -2588,6 +2617,18 @@ Dygraph.prototype.annotations = function() {
   return this.annotations_;
 };
 
+/**
+ * Get the index of a series (column) given its name. The first column is the
+ * x-axis, so the data series start with index 1.
+ */
+Dygraph.prototype.indexFromSetName = function(name) {
+  var labels = this.attr_("labels");
+  for (var i = 0; i < labels.length; i++) {
+    if (labels[i] == name) return i;
+  }
+  return null;
+};
+
 Dygraph.addAnnotationRule = function() {
   if (Dygraph.addedAnnotationCSS) return;
 
index 3aa0632..183189d 100644 (file)
@@ -37,6 +37,7 @@
               {
                 labels: labels,
                 drawPoints: true,
+                strokeWidth: 0.0,
                 drawCallback: function(g, is_initial) {
                   if (!is_initial) return;
                   var c = g.getColors();
@@ -72,7 +73,7 @@
         var b = (sum_y - a * sum_x) / num;
 
         coeffs[series] = [b, a];
-        if (console) {
+        if (typeof(console) != 'undefined') {
           console.log("coeffs(" + series + "): [" + b + ", " + a + "]");
         }
 
         // Generate a new data set with the regression lines.
         var new_labels = [];
         var new_colors = [];
+        var new_opts = {};
         for (var i = 0; i < labels.length; i++) {
           new_labels.push(labels[i]);
           if (i) new_colors.push(orig_colors[i - 1]);
           if (coeffs[i]) {
             // Darken the series by 50% to generate its regression.
-            new_labels.push(labels[i] + " Regression");
+            var label = labels[i] + " Regression";
+            new_labels.push(label);
             var c = new RGBColor(orig_colors[i - 1]);
             c.r = Math.floor(255 - 0.5 * (255 - c.r));
             c.g = Math.floor(255 - 0.5 * (255 - c.g));
             c.b = Math.floor(255 - 0.5 * (255 - c.b));
             new_colors.push(c.toHex());
+            new_opts[label] = {
+              drawPoints: false,
+              strokeWidth: 1.0
+            };
           }
         }
 
           }
         }
 
-        // TODO(danvk): set colors intelligently.
-
-        g.updateOptions({
-          file: new_data,
-          labels: new_labels,
-          colors: new_colors
-        });
+        new_opts.file = new_data;
+        new_opts.labels = new_labels;
+        new_opts.colors = new_colors;
+        g.updateOptions(new_opts);
       }
 
       function clearLines() {
         for (var i = 0; i < coeffs.length; i++) coeffs[i] = null;
         updateChart();
       }
-
-      // function drawLines(ctx, area, layout) {
-      //   if (typeof(g) == 'undefined') return;  // won't be set on the initial draw.
-
-      //   var range = g.xAxisRange();
-      //   for (var i = 0; i < coeffs.length; i++) {
-      //     if (!coeffs[i]) continue;
-      //     var a = coeffs[i][1];
-      //     var b = coeffs[i][0];
-
-      //     var x1 = range[0];
-      //     var y1 = a * x1 + b;
-      //     var x2 = range[1];
-      //     var y2 = a * x2 + b;
-
-      //     var p1 = g.toDomCoords(x1, y1);
-      //     var p2 = g.toDomCoords(x2, y2);
-
-      //     var color = g.getColors()[i - 1];
-      //     ctx.save();
-      //     ctx.strokeStyle = color;
-      //     ctx.lineWidth = 1.0;
-      //     ctx.beginPath();
-      //     ctx.moveTo(p1[0], p1[1]);
-      //     ctx.lineTo(p2[0], p2[1]);
-      //     ctx.closePath();
-      //     ctx.stroke();
-      //     ctx.restore();
-      //   }
-      // }
       
     </script>
     
index bed74e0..03c95c2 100644 (file)
@@ -35,7 +35,8 @@
               {
                 labels: ['X', 'Y1', 'Y2'],
                 underlayCallback: drawLines,
-                drawPoints: true
+                drawPoints: true,
+                strokeWidth: 0.0
               }
           );
 
@@ -66,7 +67,7 @@
         var b = (sum_y - a * sum_x) / num;
 
         coeffs[series] = [b, a];
-        if (console) {
+        if (typeof(console) != 'undefined') {
           console.log("coeffs(" + series + "): [" + b + ", " + a + "]");
         }
 
diff --git a/tests/per-series.html b/tests/per-series.html
new file mode 100644 (file)
index 0000000..7b98dea
--- /dev/null
@@ -0,0 +1,55 @@
+<html>
+  <head>
+    <title>Per-Series Properties</title>
+    <!--[if IE]>
+    <script type="text/javascript" src="excanvas.js"></script>
+    <![endif]-->
+    <script type="text/javascript" src="../strftime/strftime-min.js"></script>
+    <script type="text/javascript" src="../rgbcolor/rgbcolor.js"></script>
+    <script type="text/javascript" src="../dygraph-canvas.js"></script>
+    <script type="text/javascript" src="../dygraph.js"></script>
+  </head>
+  <body>
+    <h2>Chart with per-series properties</h2>
+    <div id="demodiv"></div>
+    <br/><br/>
+    <div id="demodiv2"></div>
+
+    <script type="text/javascript">
+      g = new Dygraph(
+              document.getElementById("demodiv"),
+              function() {
+                var zp = function(x) { if (x < 10) return "0"+x; else return x; };
+                var r = "date,parabola,line,another line,sine wave\n";
+                for (var i=1; i<=31; i++) {
+                r += "200610" + zp(i);
+                r += "," + 10*(i*(31-i));
+                r += "," + 10*(8*i);
+                r += "," + 10*(250 - 8*i);
+                r += "," + 10*(125 + 125 * Math.sin(0.3*i));
+                r += "\n";
+                }
+                return r;
+              },
+              {
+                strokeWidth: 2,
+                'parabola': {
+                  strokeWidth: 0.0,
+                  drawPoints: true,
+                  pointSize: 4,
+                  highlightCircleSize: 6
+                },
+                'line': {
+                  strokeWidth: 1.0,
+                  drawPoints: true,
+                  pointSize: 1.5
+                },
+                'sine wave': {
+                  strokeWidth: 3,
+                  highlightCircleSize: 10
+                }
+              }
+          );
+    </script>
+</body>
+</html>
index 8973347..368d2e3 100644 (file)
     <div id="stacked_missing_div"></div>
     <p>Stacked graph with many series:</p>
     <div id="stacked_many_div"></div>
+    <p>Change selection/highlighting on all graphs:</p>
+    <div id="graph_selection_div">
+        <select onchange="javascript:setSelection(this.options[this.selectedIndex].value);">
+            <option value="" selected></option>
+            <option value="0">0</option>
+            <option value="10">10</option>
+            <option value="20">20</option>
+            <option value="30">30</option>
+            <option value="40">40</option>
+            <option value="50">50</option>
+            <option value="60">60</option>
+            <option value="70">70</option>
+            <option value="80">80</option>
+            <option value="90">90</option>
+            <option value="99">99</option>
+        </select>
+    </div>
 
     <script type="text/javascript">
       data = "X,x,100-x\n";
       for (var i = 0; i < 100; i++) {
         data += i + "," + i + "," + (100 - i) + "\n";
       }
+      
+      var graphs = [];
 
-      new Dygraph(document.getElementById("simple_div"),
-                  data);
-      new Dygraph(document.getElementById("stacked_div"),
-                  data,
-                  { stackedGraph: true });
+      graphs.push(
+          new Dygraph(
+              document.getElementById("simple_div"),
+              data));
+          
+      graphs.push(
+          new Dygraph(
+              document.getElementById("stacked_div"),
+              data,
+              { stackedGraph: true }));
 
       missing_data = "X,x,100-x\n";
       for (var i = 0; i < 100; i++) {
         }
       }
 
-      new Dygraph(document.getElementById("simple_missing_div"),
-                  missing_data);
-      new Dygraph(document.getElementById("stacked_missing_div"),
-                  missing_data,
-                  { stackedGraph: true });
+      graphs.push(
+          new Dygraph(
+              document.getElementById("simple_missing_div"),
+              missing_data));
+      
+      graphs.push(
+          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 += "\n";
       }
 
-      new Dygraph(document.getElementById("stacked_many_div"),
-                  many_data,
-                  { stackedGraph: true });
+      graphs.push(
+          new Dygraph(
+              document.getElementById("stacked_many_div"),
+              many_data,
+              { stackedGraph: true }));
+      
+      function setSelection(row) {
+        for (var i = 0; i < graphs.length; i++) {
+          graphs[i].setSelection(row ? row : false);
+        }
+      }
     </script>
   </body>
 </html>