Merge branch 'master' of http://github.com/danvk/dygraphs
authorNeal Nelson <neal@makalumedia.com>
Thu, 18 Nov 2010 09:59:07 +0000 (10:59 +0100)
committerNeal Nelson <neal@makalumedia.com>
Thu, 18 Nov 2010 09:59:07 +0000 (10:59 +0100)
15 files changed:
commit-hook/commit.cgi [deleted file]
commit-hook/commit.sh [deleted file]
docs/index.html
dygraph-canvas.js
dygraph.js
excanvas.js
tests/avoidMinZero.html
tests/callback.html
tests/demo.html
tests/highlighted-region.html
tests/reverse-y-axis.html
tests/two-axes.html
tests/underlay-callback.html
tests/y-axis-formatter.html
tests/zoom.html

diff --git a/commit-hook/commit.cgi b/commit-hook/commit.cgi
deleted file mode 100755 (executable)
index 1494318..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-#!/usr/local/bin/perl
-use URI::Escape;
-use JSON;
-
-# Read in the POST data and URL-decode it.
-$data="";
-while (<>) {
-  $data .= $_;
-}
-$data = uri_unescape($data);
-$data =~ s/.*?=//;  # JSON::decode_json doesn't like the 'payload=' prefix.
-
-# Save for debugging.
-open (MYFILE, '>./postdata.txt');
-print MYFILE $data;
-close (MYFILE);
-
-# Parse the JSON
-$perl_scalar = decode_json $data;
-$id=$perl_scalar->{'after'};
-die unless $id =~ /^[0-9][a-f]*$/;
-print "Id: $id\n";
-
-# Run the actual commit hook.
-system("./commit.sh $id");
diff --git a/commit-hook/commit.sh b/commit-hook/commit.sh
deleted file mode 100755 (executable)
index 439cbf8..0000000
+++ /dev/null
@@ -1,19 +0,0 @@
-#!/bin/bash
-id=$1;
-
-echo id: $id > ./id.txt
-dir=git-$id
-mkdir $dir
-cd $dir
-
-# TODO(danvk): any way to clone at a particular revision?
-git clone --depth 0 git://github.com/danvk/dygraphs.git
-cd dygraphs
-./generate-combined.sh
-
-# Copy data over to http://www.danvk.org/dygraphs/
-cp tests/*.html tests/*.js ~/danvk.org/dygraphs/tests/
-cp dygraph.js dygraph-canvas.js dygraph-combined.js gadget.xml excanvas.js thumbnail.png docs/* ~/danvk.org/dygraphs/
-
-cd ../..
-rm -rf $dir
index fe36356..adc7c15 100644 (file)
@@ -853,7 +853,7 @@ perl -ne 'BEGIN{print "Month,Nominal,Real\n"} chomp; ($m,$cpi,$low,$close,$high)
 
         <tr>
           <td><strong>highlightCallback</strong></td>
-          <td><code>function(event, x, points)</code></td>
+          <td><code>function(event, x, points,row)</code></td>
           <td><code>null</code></td>
           <td>When set, this callback gets called every time a new point is highlighted. The parameters are the JavaScript mousemove event, the x-coordinate of the highlighted points and an array of highlighted points: <code>[ {name: 'series', yval: y-value}, &hellip; ]</code>
           <div class="tests">Tests: <a href="tests/callback.html">callback</a> <a href="tests/crosshair.html">crosshair</a> </div>
@@ -870,6 +870,20 @@ perl -ne 'BEGIN{print "Month,Nominal,Real\n"} chomp; ($m,$cpi,$low,$close,$high)
         </tr>
 
         <tr>
+          <td><strong>underlayCallback</strong></td>
+          <td><code>function(canvas, area, dygraph)</code></td>
+          <td><code>null</code></td>
+          <td>When set, this callback gets called before the chart is drawn. It
+            allows you to draw underneath the chart. See the tests for more
+            details on how to use this.
+            <div class="tests">Tests:
+              <a href="tests/underlay-callback.html">underlay-callback</a>
+              <a href="tests/highlighted-region.html">highlighted-region</a>
+            </div>
+          </td>
+        </tr>
+
+        <tr>
           <td><strong>strokeWidth</strong></td>
           <td><code>0.5, 2.0</code></td>
           <td><code>1.0</code></td>
index 29341e1..8dc502c 100644 (file)
@@ -393,7 +393,9 @@ DygraphCanvasRenderer.prototype.render = function() {
   function halfDown(y){return Math.round(y)-0.5};
 
   if (this.options.underlayCallback) {
-    this.options.underlayCallback(ctx, this.area, this.layout, this.dygraph_);
+    // NOTE: we pass the dygraph object to this callback twice to avoid breaking
+    // users who expect a deprecated form of this callback.
+    this.options.underlayCallback(ctx, this.area, this.dygraph_, this.dygraph_);
   }
 
   if (this.options.drawYGrid) {
index e7cb8f8..4b52555 100644 (file)
@@ -166,6 +166,16 @@ Dygraph.prototype.__old_init__ = function(div, file, labels, attrs) {
  * @private
  */
 Dygraph.prototype.__init__ = function(div, file, attrs) {
+  // Hack for IE: if we're using excanvas and the document hasn't finished
+  // loading yet (and hence may not have initialized whatever it needs to
+  // initialize), then keep calling this routine periodically until it has.
+  if (/MSIE/.test(navigator.userAgent) && !window.opera &&
+      typeof(G_vmlCanvasManager) != 'undefined' &&
+      document.readyState != 'complete') {
+    var self = this;
+    setTimeout(function() { self.__init__(div, file, attrs) }, 100);
+  }
+
   // Support two-argument constructor
   if (attrs == null) { attrs = {}; }
 
@@ -194,10 +204,10 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
   // If the div isn't already sized then inherit from our attrs or
   // give it a default size.
   if (div.style.width == '') {
-    div.style.width = attrs.width || Dygraph.DEFAULT_WIDTH + "px";
+    div.style.width = (attrs.width || Dygraph.DEFAULT_WIDTH) + "px";
   }
   if (div.style.height == '') {
-    div.style.height = attrs.height || Dygraph.DEFAULT_HEIGHT + "px";
+    div.style.height = (attrs.height || Dygraph.DEFAULT_HEIGHT) + "px";
   }
   this.width_ = parseInt(div.style.width, 10);
   this.height_ = parseInt(div.style.height, 10);
@@ -1173,6 +1183,8 @@ Dygraph.prototype.mouseMove_ = function(event) {
   var minDist = 1e+100;
   var idx = -1;
   for (var i = 0; i < points.length; i++) {
+    var point = points[i];
+    if (point == null) continue;
     var dist = Math.abs(points[i].canvasx - canvasx);
     if (dist > minDist) continue;
     minDist = dist;
@@ -1180,7 +1192,8 @@ Dygraph.prototype.mouseMove_ = function(event) {
   }
   if (idx >= 0) lastx = points[idx].xval;
   // Check that you can really highlight the last day's data
-  if (canvasx > points[points.length-1].canvasx)
+  var last = points[points.length-1];
+  if (last != null && canvasx > last.canvasx)
     lastx = points[points.length-1].xval;
 
   // Extract the points we've selected
@@ -1213,7 +1226,7 @@ Dygraph.prototype.mouseMove_ = function(event) {
     var px = this.lastx_;
     if (px !== null && lastx != px) {
       // only fire if the selected point has changed.
-      this.attr_("highlightCallback")(event, lastx, this.selPoints_);
+      this.attr_("highlightCallback")(event, lastx, this.selPoints_, this.idxToRow_(idx));
     }
   }
 
@@ -1224,6 +1237,24 @@ Dygraph.prototype.mouseMove_ = function(event) {
 };
 
 /**
+ * Transforms layout_.points index into data row number.
+ * @param int layout_.points index
+ * @return int row number, or -1 if none could be found.
+ * @private
+ */
+Dygraph.prototype.idxToRow_ = function(idx) {
+  if (idx < 0) return -1;
+
+  for (var i in this.layout_.datasets) {
+    if (idx < this.layout_.datasets[i].length) {
+      return this.boundaryIds_[0][0]+idx;
+    }
+    idx -= this.layout_.datasets[i].length;
+  }
+  return -1;
+};
+
+/**
  * Draw dots over the selectied points in the data series. This function
  * takes care of cleanup of previously-drawn dots.
  * @private
@@ -2900,30 +2931,36 @@ Dygraph.prototype.indexFromSetName = function(name) {
 Dygraph.addAnnotationRule = function() {
   if (Dygraph.addedAnnotationCSS) return;
 
-  var mysheet;
-  if (document.styleSheets.length > 0) {
-    mysheet = document.styleSheets[0];
-  } else {
-    var styleSheetElement = document.createElement("style");
-    styleSheetElement.type = "text/css";
-    document.getElementsByTagName("head")[0].appendChild(styleSheetElement);
-    for(i = 0; i < document.styleSheets.length; i++) {
-      if (document.styleSheets[i].disabled) continue;
-      mysheet = document.styleSheets[i];
-    }
-  }
-
   var rule = "border: 1px solid black; " +
              "background-color: white; " +
              "text-align: center;";
-  if (mysheet.insertRule) {  // Firefox
-    var idx = mysheet.cssRules ? mysheet.cssRules.length : 0;
-    mysheet.insertRule(".dygraphDefaultAnnotation { " + rule + " }", idx);
-  } else if (mysheet.addRule) {  // IE
-    mysheet.addRule(".dygraphDefaultAnnotation", rule);
+
+  var styleSheetElement = document.createElement("style");
+  styleSheetElement.type = "text/css";
+  document.getElementsByTagName("head")[0].appendChild(styleSheetElement);
+
+  // Find the first style sheet that we can access.
+  // We may not add a rule to a style sheet from another domain for security
+  // reasons. This sometimes comes up when using gviz, since the Google gviz JS
+  // adds its own style sheets from google.com.
+  for (var i = 0; i < document.styleSheets.length; i++) {
+    if (document.styleSheets[i].disabled) continue;
+    var mysheet = document.styleSheets[i];
+    try {
+      if (mysheet.insertRule) {  // Firefox
+        var idx = mysheet.cssRules ? mysheet.cssRules.length : 0;
+        mysheet.insertRule(".dygraphDefaultAnnotation { " + rule + " }", idx);
+      } else if (mysheet.addRule) {  // IE
+        mysheet.addRule(".dygraphDefaultAnnotation", rule);
+      }
+      Dygraph.addedAnnotationCSS = true;
+      return;
+    } catch(err) {
+      // Was likely a security exception.
+    }
   }
 
-  Dygraph.addedAnnotationCSS = true;
+  this.warn("Unable to add default annotation CSS rule; display may be off.");
 }
 
 /**
index 1d9ddb2..367764b 100644 (file)
@@ -138,6 +138,11 @@ if (!document.createElement('canvas').getContext) {
 
         el.getContext = getContext;
 
+        // Remove fallback content. There is no way to hide text nodes so we
+        // just remove all childNodes. We could hide all elements and remove
+        // text nodes but who really cares about the fallback content.
+        el.innerHTML = '';
+
         // do not use inline function because that will leak memory
         el.attachEvent('onpropertychange', onPropertyChange);
         el.attachEvent('onresize', onResize);
@@ -314,7 +319,6 @@ if (!document.createElement('canvas').getContext) {
   var contextPrototype = CanvasRenderingContext2D_.prototype;
   contextPrototype.clearRect = function() {
     this.element_.innerHTML = '';
-    this.currentPath_ = [];
   };
 
   contextPrototype.beginPath = function() {
@@ -422,27 +426,31 @@ if (!document.createElement('canvas').getContext) {
   };
 
   contextPrototype.strokeRect = function(aX, aY, aWidth, aHeight) {
-    // Will destroy any existing path (same as FF behaviour)
+    var oldPath = this.currentPath_;
     this.beginPath();
+
     this.moveTo(aX, aY);
     this.lineTo(aX + aWidth, aY);
     this.lineTo(aX + aWidth, aY + aHeight);
     this.lineTo(aX, aY + aHeight);
     this.closePath();
     this.stroke();
-    this.currentPath_ = [];
+
+    this.currentPath_ = oldPath;
   };
 
   contextPrototype.fillRect = function(aX, aY, aWidth, aHeight) {
-    // Will destroy any existing path (same as FF behaviour)
+    var oldPath = this.currentPath_;
     this.beginPath();
+
     this.moveTo(aX, aY);
     this.lineTo(aX + aWidth, aY);
     this.lineTo(aX + aWidth, aY + aHeight);
     this.lineTo(aX, aY + aHeight);
     this.closePath();
     this.fill();
-    this.currentPath_ = [];
+
+    this.currentPath_ = oldPath;
   };
 
   contextPrototype.createLinearGradient = function(aX0, aY0, aX1, aY1) {
@@ -789,6 +797,33 @@ if (!document.createElement('canvas').getContext) {
     this.m_ = this.mStack_.pop();
   };
 
+  function matrixIsFinite(m) {
+    for (var j = 0; j < 3; j++) {
+      for (var k = 0; k < 2; k++) {
+        if (!isFinite(m[j][k]) || isNaN(m[j][k])) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
+  function setM(ctx, m, updateLineScale) {
+    if (!matrixIsFinite(m)) {
+      return;
+    }
+    ctx.m_ = m;
+
+    if (updateLineScale) {
+      // Get the line scale.
+      // Determinant of this.m_ means how much the area is enlarged by the
+      // transformation. So its square root can be used as a scale factor
+      // for width.
+      var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
+      ctx.lineScale_ = sqrt(abs(det));
+    }
+  }
+
   contextPrototype.translate = function(aX, aY) {
     var m1 = [
       [1,  0,  0],
@@ -796,7 +831,7 @@ if (!document.createElement('canvas').getContext) {
       [aX, aY, 1]
     ];
 
-    this.m_ = matrixMultiply(m1, this.m_);
+    setM(this, matrixMultiply(m1, this.m_), false);
   };
 
   contextPrototype.rotate = function(aRot) {
@@ -809,7 +844,7 @@ if (!document.createElement('canvas').getContext) {
       [0,  0, 1]
     ];
 
-    this.m_ = matrixMultiply(m1, this.m_);
+    setM(this, matrixMultiply(m1, this.m_), false);
   };
 
   contextPrototype.scale = function(aX, aY) {
@@ -821,14 +856,27 @@ if (!document.createElement('canvas').getContext) {
       [0,  0,  1]
     ];
 
-    var m = this.m_ = matrixMultiply(m1, this.m_);
+    setM(this, matrixMultiply(m1, this.m_), true);
+  };
+
+  contextPrototype.transform = function(m11, m12, m21, m22, dx, dy) {
+    var m1 = [
+      [m11, m12, 0],
+      [m21, m22, 0],
+      [dx,  dy,  1]
+    ];
+
+    setM(this, matrixMultiply(m1, this.m_), true);
+  };
+
+  contextPrototype.setTransform = function(m11, m12, m21, m22, dx, dy) {
+    var m = [
+      [m11, m12, 0],
+      [m21, m22, 0],
+      [dx,  dy,  1]
+    ];
 
-    // Get the line scale.
-    // Determinant of this.m_ means how much the area is enlarged by the
-    // transformation. So its square root can be used as a scale factor
-    // for width.
-    var det = m[0][0] * m[1][1] - m[0][1] * m[1][0];
-    this.lineScale_ = sqrt(abs(det));
+    setM(this, m, true);
   };
 
   /******** STUBS ********/
index be33df5..e52d6c7 100644 (file)
@@ -2,7 +2,7 @@
   <head>
     <title>dygraph</title>
     <!--[if IE]>
-    <script type="text/javascript" src="excanvas.js"></script>
+    <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>
index 484689a..04ed8ea 100644 (file)
@@ -36,7 +36,7 @@
     <script type="text/javascript">
       s = document.getElementById("status");
       g = null;
-      pts_info = function(e, x, pts) {
+      pts_info = function(e, x, pts, row) {
         var str = "(" + x + ") ";
         for (var i = 0; i < pts.length; i++) {
           var p = pts[i];
@@ -49,6 +49,7 @@
         var dataXY = g.toDataCoords(x, y);
         str += ", (" + x + ", " + y + ")";
         str += " -> (" + dataXY[0] + ", " + dataXY[1] + ")";
+        str += ", row #"+row;
 
         return str;
       };
@@ -60,9 +61,9 @@
               showRoller: true,
               errorBars: true,
 
-              highlightCallback: function(e, x, pts) {
+              highlightCallback: function(e, x, pts, row) {
                 if (document.getElementById('highlight').checked) {
-                  s.innerHTML += "<b>Highlight</b> " + pts_info(e,x,pts) + "<br/>";
+                  s.innerHTML += "<b>Highlight</b> " + pts_info(e,x,pts,row) + "<br/>";
                 }
               },
 
index 32ea6f0..c13c2ad 100644 (file)
@@ -19,7 +19,7 @@
     </td>
     </tr></table>
     <script type="text/javascript">
-      g = new DateGraph(
+      g = new Dygraph(
               document.getElementById("demodiv"),
               function() {
                 var zp = function(x) { if (x < 10) return "0"+x; else return x; };
index 33d92e7..7852753 100644 (file)
@@ -37,7 +37,7 @@
           data,
           {
             labels: ['X', 'Est.', 'Actual'],
-            underlayCallback: function(canvas, area, layout, g) {
+            underlayCallback: function(canvas, area, g) {
               var bottom_left = g.toDomCoords(highlight_start, -20);
               var top_right = g.toDomCoords(highlight_end, +20);
 
index 92f3089..3a5240d 100644 (file)
@@ -20,7 +20,7 @@
     </td>
     </tr></table>
     <script type="text/javascript">
-      g = new DateGraph(
+      g = new Dygraph(
               document.getElementById("demodiv"),
               function() {
                 var zp = function(x) { if (x < 10) return "0"+x; else return x; };
index 78c0fb2..1afd13b 100644 (file)
@@ -2,7 +2,7 @@
   <head>
     <title>Multiple y-axes</title>
     <!--[if IE]>
-    <script type="text/javascript" src="excanvas.js"></script>
+    <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>
index 5c47582..762ed49 100644 (file)
               showRoller: true,
               errorBars: true,
 
-              underlayCallback: function(canvas, area, layout) {
-                var splitHeight = area.h * (layout.yscale * (2.25 - layout.minyval));
-                canvas.fillStyle = 'pink';
-                canvas.fillRect(area.x, area.y + area.h, area.w, -splitHeight);
+              underlayCallback: function(canvas, area, g) {
+                // Selecting a date in the middle of the graph. 
+                var splitDate = new Date("2006-11-19").getTime();
+                var coords = g.toDomCoords(splitDate, 2.25);
+
+                // splitX and splitY are the coordinates on the canvas for (2006-11-19, 2.25).
+                var splitX = coords[0];
+                var splitY = coords[1];
+
+                // The drawing area doesn't start at (0, 0), it starts at (area.x, area.y).
+                // That's why we subtract them from splitX and splitY. This gives us the
+                // actual distance from the upper-left hand corder of the graph itself.
+                var leftSideWidth = splitX - area.x;
+                var rightSideWidth = area.w - leftSideWidth;
+                var topHeight = splitY - area.y;
+                var bottomHeight = area.h - topHeight;
+
+                // fillRect(x, y, width, height)
+                // Top section: y = (2.25, +Infinity)
+                // left: (x < 2006-11-19)
                 canvas.fillStyle = 'lightblue';
-                canvas.fillRect(area.x, 0, area.w, area.y + area.h - splitHeight);
+                canvas.fillRect(area.x, area.y, leftSideWidth, topHeight);
+
+                // right: (x > 2006-11-19)
+                canvas.fillStyle = 'orange';
+                canvas.fillRect(splitX, area.y, rightSideWidth, topHeight);
+        
+                // Bottom section: y = (-Infinity, 2.25)
+                canvas.fillStyle = 'pink';
+                canvas.fillRect(area.x, splitY, area.w, bottomHeight);
               }
             }
           );
index 346f319..3853559 100644 (file)
@@ -2,7 +2,7 @@
     <head>
         <title>dygraph</title>
         <!--[if IE]>
-        <script type="text/javascript" src="excanvas.js"></script>
+        <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>
index 8c9e641..43ea545 100644 (file)
@@ -2,7 +2,7 @@
   <head>
     <title>zoom</title>
     <!--[if IE]>
-    <script type="text/javascript" src="excanvas.js"></script>
+    <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>