From 5bcc58b46734fb5c9e821ed041318cb53127feb5 Mon Sep 17 00:00:00 2001
From: Dan Vanderkam
Date: Mon, 5 Aug 2013 16:37:33 -0400
Subject: [PATCH] Add Dygraph.ready() method to simplify annotations usage.
---
auto_tests/tests/Util.js | 36 +++++++++++++++++
auto_tests/tests/annotations.js | 30 +++++++++++++++
docs/annotations.html | 85 ++++++++++++++++++++++++-----------------
dygraph.js | 28 ++++++++++++++
4 files changed, 144 insertions(+), 35 deletions(-)
diff --git a/auto_tests/tests/Util.js b/auto_tests/tests/Util.js
index 2d652ed..8a14e06 100644
--- a/auto_tests/tests/Util.js
+++ b/auto_tests/tests/Util.js
@@ -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;
+};
+
diff --git a/auto_tests/tests/annotations.js b/auto_tests/tests/annotations.js
index 76a56d2..d0a2f7f 100644
--- a/auto_tests/tests/annotations.js
+++ b/auto_tests/tests/annotations.js
@@ -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);
+};
diff --git a/docs/annotations.html b/docs/annotations.html
index 91fa004..24dff7f 100644
--- a/docs/annotations.html
+++ b/docs/annotations.html
@@ -26,43 +26,42 @@ them.
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'
+ }
+ ] );
+ });
Adding Annotations
@@ -159,6 +158,22 @@ g.setAnnotations(annotations); // causes a redraw
For a more real-life example, see the annotations demo
+Annotations and Data Sources
+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 new Dygraph
and so the call to
+g.setAnnotations
will fail. The best way around this is to use the
+ready()
method:
+
+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([
+ …
+ ]);
+});
+
+
Annotations property reference
These properties can all be set in the dictionary for an annotation. The use of each one is demonstrated on the annotations demo page.
diff --git a/dygraph.js b/dygraph.js
index 6ff258e..cc5f64b 100644
--- a/dygraph.js
+++ b/dygraph.js
@@ -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
--
2.7.4