Add Dygraph.ready() method to simplify annotations usage.
authorDan Vanderkam <danvdk@gmail.com>
Mon, 5 Aug 2013 20:37:33 +0000 (16:37 -0400)
committerDan Vanderkam <danvdk@gmail.com>
Mon, 5 Aug 2013 20:37:33 +0000 (16:37 -0400)
auto_tests/tests/Util.js
auto_tests/tests/annotations.js
docs/annotations.html
dygraph.js

index 2d652ed..8a14e06 100644 (file)
@@ -95,3 +95,39 @@ Util.samplePixel = function(canvas, x, y) {
   var d = imageData.data;
   return [d[i], d[i+1], d[i+2], d[i+3]];
 };
+
+/**
+ * Overrides the browser's built-in XMLHttpRequest with a mock.
+ * Usage:
+ *
+ * var mockXhr = Util.overrideXMLHttpRequest(your_data);
+ * ... call code that does an XHR ...
+ * mockXhr.respond();  // restores default behavior.
+ * ... do your assertions ...
+ */
+Util.overrideXMLHttpRequest = function(data) {
+  var originalXMLHttpRequest = XMLHttpRequest;
+
+  var requests = [];
+  var FakeXMLHttpRequest = function () {
+    requests.push(this);
+  };
+  FakeXMLHttpRequest.prototype.open = function () {};
+  FakeXMLHttpRequest.prototype.send = function () {
+    this.readyState = 4;
+    this.status = 200;
+    this.responseText = data;
+  };
+  FakeXMLHttpRequest.restore = function() {
+    XMLHttpRequest = originalXMLHttpRequest;
+  };
+  FakeXMLHttpRequest.respond = function() {
+    for (var i = 0; i < requests.length; i++) {
+      requests[i].onreadystatechange();
+    }
+    FakeXMLHttpRequest.restore();
+  };
+  XMLHttpRequest = FakeXMLHttpRequest;
+  return FakeXMLHttpRequest;
+};
+
index 76a56d2..d0a2f7f 100644 (file)
@@ -240,3 +240,33 @@ AnnotationsTestCase.prototype.testAnnotationsStacked = function() {
   assertEquals(annEls[0].offsetLeft, annEls[1].offsetLeft);
   assert(annEls[1].offsetTop < annEls[0].offsetTop - 10);
 };
+
+
+// Test the .ready() method, which is most often used with setAnnotations().
+AnnotationsTestCase.prototype.testReady = function() {
+  var data = 'X,Y1,Y2\n' +
+      '0,1,2\n' +
+      '1,2,3\n';
+  var mockXhr = Util.overrideXMLHttpRequest(data);
+
+  var graph = document.getElementById("graph");
+  var g = new Dygraph(graph, "data.csv", {
+    width: 480,
+    height: 320
+  });
+
+  var ready_calls = 0;
+  g.ready(function() { ready_calls++; });
+
+  assertEquals(0, ready_calls);
+  mockXhr.respond();
+  assertEquals(1, ready_calls);
+
+  // Make sure that ready isn't called on redraws.
+  g.updateOptions({});
+  assertEquals(1, ready_calls);
+
+  // Or data changes.
+  g.updateOptions({file: data});
+  assertEquals(1, ready_calls);
+};
index 91fa004..24dff7f 100644 (file)
@@ -26,43 +26,42 @@ them.</p>
       showRoller: true,
       customBars: true,
       labelsKMB: true,
-      labelsDivWidth: 300,
-      drawCallback: function(g, is_initial) {
-        if (!is_initial) return;
-
-        g.setAnnotations( [
-        {
-          series: "Real",
-          x: "1929-08-15",
-          shortText: "A",
-          text: "1929 Stock Market Peak",
-          cssClass: 'annotation'
-        },
-        {
-          series: "Nominal",
-          x: "1987-08-15",
-          shortText: "B",
-          text: "1987 Crash",
-          cssClass: 'annotation'
-        },
-        {
-          series: "Nominal",
-          x: "1999-12-15",
-          shortText: "C",
-          text: "1999 (.com) Peak",
-          cssClass: 'annotation'
-        },
-        {
-          series: "Nominal",
-          x: "2007-10-15",
-          shortText: "D",
-          text: "All-Time Market Peak",
-          cssClass: 'annotation'
-        }
-        ] );
-      }
+      labelsDivWidth: 300
     }
   );
+
+  stockchart.ready(function(g) {
+    g.setAnnotations( [
+    {
+      series: "Real",
+      x: "1929-08-15",
+      shortText: "A",
+      text: "1929 Stock Market Peak",
+      cssClass: 'annotation'
+    },
+    {
+      series: "Nominal",
+      x: "1987-08-15",
+      shortText: "B",
+      text: "1987 Crash",
+      cssClass: 'annotation'
+    },
+    {
+      series: "Nominal",
+      x: "1999-12-15",
+      shortText: "C",
+      text: "1999 (.com) Peak",
+      cssClass: 'annotation'
+    },
+    {
+      series: "Nominal",
+      x: "2007-10-15",
+      shortText: "D",
+      text: "All-Time Market Peak",
+      cssClass: 'annotation'
+    }
+    ] );
+  });
 </script>
 
 <h3>Adding Annotations</h3>
@@ -159,6 +158,22 @@ g.setAnnotations(annotations);  // causes a redraw
 <p>For a more real-life example, see the <a
 href="tests/annotation.html">annotations demo</a></p>
 
+<h3>Annotations and Data Sources</h3>
+<p>When you pass a URL as the data source to dygraphs, it must issue a request
+for the data before drawing the chart. This means that the chart data is not yet
+available immediately after you call <code>new Dygraph</code> and so the call to
+<code>g.setAnnotations</code> will fail. The best way around this is to use the
+<code>ready()</code> method:</p>
+
+<pre>g = new Dygraph(div, "path/to/data.csv");
+g.ready(function() {
+  // This is called when data.csv comes back and the chart draws.
+  g.setAnnotations([
+    &hellip;
+  ]);
+});
+</pre>
+
 <h3>Annotations property reference</h3>
 <p>These properties can all be set in the dictionary for an annotation. The use of each one is demonstrated on the <a href="tests/annotation.html">annotations demo</a> page.</p>
 
index 6ff258e..cc5f64b 100644 (file)
@@ -441,6 +441,7 @@ Dygraph.prototype.__init__ = function(div, file, attrs) {
 
   this.is_initial_draw_ = true;
   this.annotations_ = [];
+  this.readyFns_ = [];
 
   // Zoomed indicators - These indicate when the graph has been zoomed and on what axis.
   this.zoomed_x_ = false;
@@ -2612,6 +2613,13 @@ Dygraph.prototype.renderGraph_ = function(is_initial_draw) {
   if (this.attr_("drawCallback") !== null) {
     this.attr_("drawCallback")(this, is_initial_draw);
   }
+  if (is_initial_draw) {
+    this.readyFired_ = true;
+    while (this.readyFns_.length > 0) {
+      var fn = this.readyFns_.pop();
+      fn(this);
+    }
+  }
 };
 
 /**
@@ -3805,6 +3813,26 @@ Dygraph.prototype.indexFromSetName = function(name) {
 };
 
 /**
+ * Trigger a callback when the dygraph has drawn itself and is ready to be
+ * manipulated. This is primarily useful when dygraphs has to do an XHR for the
+ * data (i.e. a URL is passed as the data source) and the chart is drawn
+ * asynchronously. If the chart has already drawn, the callback will fire
+ * immediately.
+ *
+ * This is a good place to call setAnnotation().
+ *
+ * @param {function(!Dygraph)} callback The callback to trigger when the chart
+ *     is ready.
+ */
+Dygraph.prototype.ready = function(callback) {
+  if (this.is_initial_draw_) {
+    this.readyFns_.push(callback);
+  } else {
+    callback(this);
+  }
+};
+
+/**
  * @private
  * Adds a default style for the annotation CSS classes to the document. This is
  * only executed when annotations are actually used. It is designed to only be