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